Skip to content

Commit 4954a7e

Browse files
committed
Auto merge of #104616 - RalfJung:ctfe-alignment, r=oli-obk,RalfJung
always check alignment during CTFE We originally disabled alignment checks because they got in the way -- there are some things we do with the interpreter during CTFE which does not correspond to actually running user-written code, but is purely administrative, and we didn't want alignment checks there, so we just disabled them entirely. But with `-Zextra-const-ub-checks` we anyway had to figure out how to disable those alignment checks while doing checks in regular code. So now it is easy to enable CTFE alignment checking by default. Let's see what the perf consequences of that are. r? `@oli-obk`
2 parents 984eab5 + 2d89027 commit 4954a7e

24 files changed

+543
-351
lines changed

compiler/rustc_const_eval/src/const_eval/error.rs

+65-70
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,59 @@ impl<'tcx> ConstEvalErr<'tcx> {
8686
self.report_decorated(tcx, message, |_| {})
8787
}
8888

89+
#[instrument(level = "trace", skip(self, decorate))]
90+
pub(super) fn decorate(&self, err: &mut Diagnostic, decorate: impl FnOnce(&mut Diagnostic)) {
91+
trace!("reporting const eval failure at {:?}", self.span);
92+
// Add some more context for select error types.
93+
match self.error {
94+
InterpError::Unsupported(
95+
UnsupportedOpInfo::ReadPointerAsBytes
96+
| UnsupportedOpInfo::PartialPointerOverwrite(_)
97+
| UnsupportedOpInfo::PartialPointerCopy(_),
98+
) => {
99+
err.help("this code performed an operation that depends on the underlying bytes representing a pointer");
100+
err.help("the absolute address of a pointer is not known at compile-time, so such operations are not supported");
101+
}
102+
_ => {}
103+
}
104+
// Add spans for the stacktrace. Don't print a single-line backtrace though.
105+
if self.stacktrace.len() > 1 {
106+
// Helper closure to print duplicated lines.
107+
let mut flush_last_line = |last_frame, times| {
108+
if let Some((line, span)) = last_frame {
109+
err.span_note(span, &line);
110+
// Don't print [... additional calls ...] if the number of lines is small
111+
if times < 3 {
112+
for _ in 0..times {
113+
err.span_note(span, &line);
114+
}
115+
} else {
116+
err.span_note(
117+
span,
118+
format!("[... {} additional calls {} ...]", times, &line),
119+
);
120+
}
121+
}
122+
};
123+
124+
let mut last_frame = None;
125+
let mut times = 0;
126+
for frame_info in &self.stacktrace {
127+
let frame = (frame_info.to_string(), frame_info.span);
128+
if last_frame.as_ref() == Some(&frame) {
129+
times += 1;
130+
} else {
131+
flush_last_line(last_frame, times);
132+
last_frame = Some(frame);
133+
times = 0;
134+
}
135+
}
136+
flush_last_line(last_frame, times);
137+
}
138+
// Let the caller attach any additional information it wants.
139+
decorate(err);
140+
}
141+
89142
/// Create a diagnostic for this const eval error.
90143
///
91144
/// Sets the message passed in via `message` and adds span labels with detailed error
@@ -101,88 +154,30 @@ impl<'tcx> ConstEvalErr<'tcx> {
101154
message: &str,
102155
decorate: impl FnOnce(&mut Diagnostic),
103156
) -> ErrorHandled {
104-
let finish = |err: &mut Diagnostic, span_msg: Option<String>| {
105-
trace!("reporting const eval failure at {:?}", self.span);
106-
if let Some(span_msg) = span_msg {
107-
err.span_label(self.span, span_msg);
108-
}
109-
// Add some more context for select error types.
110-
match self.error {
111-
InterpError::Unsupported(
112-
UnsupportedOpInfo::ReadPointerAsBytes
113-
| UnsupportedOpInfo::PartialPointerOverwrite(_)
114-
| UnsupportedOpInfo::PartialPointerCopy(_),
115-
) => {
116-
err.help("this code performed an operation that depends on the underlying bytes representing a pointer");
117-
err.help("the absolute address of a pointer is not known at compile-time, so such operations are not supported");
118-
}
119-
_ => {}
120-
}
121-
// Add spans for the stacktrace. Don't print a single-line backtrace though.
122-
if self.stacktrace.len() > 1 {
123-
// Helper closure to print duplicated lines.
124-
let mut flush_last_line = |last_frame, times| {
125-
if let Some((line, span)) = last_frame {
126-
err.span_note(span, &line);
127-
// Don't print [... additional calls ...] if the number of lines is small
128-
if times < 3 {
129-
for _ in 0..times {
130-
err.span_note(span, &line);
131-
}
132-
} else {
133-
err.span_note(
134-
span,
135-
format!("[... {} additional calls {} ...]", times, &line),
136-
);
137-
}
138-
}
139-
};
140-
141-
let mut last_frame = None;
142-
let mut times = 0;
143-
for frame_info in &self.stacktrace {
144-
let frame = (frame_info.to_string(), frame_info.span);
145-
if last_frame.as_ref() == Some(&frame) {
146-
times += 1;
147-
} else {
148-
flush_last_line(last_frame, times);
149-
last_frame = Some(frame);
150-
times = 0;
151-
}
152-
}
153-
flush_last_line(last_frame, times);
154-
}
155-
// Let the caller attach any additional information it wants.
156-
decorate(err);
157-
};
158-
159157
debug!("self.error: {:?}", self.error);
160158
// Special handling for certain errors
161159
match &self.error {
162160
// Don't emit a new diagnostic for these errors
163161
err_inval!(Layout(LayoutError::Unknown(_))) | err_inval!(TooGeneric) => {
164-
return ErrorHandled::TooGeneric;
165-
}
166-
err_inval!(AlreadyReported(error_reported)) => {
167-
return ErrorHandled::Reported(*error_reported);
162+
ErrorHandled::TooGeneric
168163
}
164+
err_inval!(AlreadyReported(error_reported)) => ErrorHandled::Reported(*error_reported),
169165
err_inval!(Layout(LayoutError::SizeOverflow(_))) => {
170166
// We must *always* hard error on these, even if the caller wants just a lint.
171167
// The `message` makes little sense here, this is a more serious error than the
172168
// caller thinks anyway.
173169
// See <https://github.com/rust-lang/rust/pull/63152>.
174170
let mut err = struct_error(tcx, &self.error.to_string());
175-
finish(&mut err, None);
176-
return ErrorHandled::Reported(err.emit());
171+
self.decorate(&mut err, decorate);
172+
ErrorHandled::Reported(err.emit())
177173
}
178-
_ => {}
179-
};
180-
181-
let err_msg = self.error.to_string();
182-
183-
// Report as hard error.
184-
let mut err = struct_error(tcx, message);
185-
finish(&mut err, Some(err_msg));
186-
ErrorHandled::Reported(err.emit())
174+
_ => {
175+
// Report as hard error.
176+
let mut err = struct_error(tcx, message);
177+
err.span_label(self.span, self.error.to_string());
178+
self.decorate(&mut err, decorate);
179+
ErrorHandled::Reported(err.emit())
180+
}
181+
}
187182
}
188183
}

compiler/rustc_const_eval/src/const_eval/eval_queries.rs

+8-7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::const_eval::CheckAlignment;
12
use std::borrow::Cow;
23

34
use either::{Left, Right};
@@ -76,7 +77,7 @@ fn eval_body_using_ecx<'mir, 'tcx>(
7677
None => InternKind::Constant,
7778
}
7879
};
79-
ecx.machine.check_alignment = false; // interning doesn't need to respect alignment
80+
ecx.machine.check_alignment = CheckAlignment::No; // interning doesn't need to respect alignment
8081
intern_const_alloc_recursive(ecx, intern_kind, &ret)?;
8182
// we leave alignment checks off, since this `ecx` will not be used for further evaluation anyway
8283

@@ -102,11 +103,7 @@ pub(super) fn mk_eval_cx<'mir, 'tcx>(
102103
tcx,
103104
root_span,
104105
param_env,
105-
CompileTimeInterpreter::new(
106-
tcx.const_eval_limit(),
107-
can_access_statics,
108-
/*check_alignment:*/ false,
109-
),
106+
CompileTimeInterpreter::new(tcx.const_eval_limit(), can_access_statics, CheckAlignment::No),
110107
)
111108
}
112109

@@ -311,7 +308,11 @@ pub fn eval_to_allocation_raw_provider<'tcx>(
311308
CompileTimeInterpreter::new(
312309
tcx.const_eval_limit(),
313310
/*can_access_statics:*/ is_static,
314-
/*check_alignment:*/ tcx.sess.opts.unstable_opts.extra_const_ub_checks,
311+
if tcx.sess.opts.unstable_opts.extra_const_ub_checks {
312+
CheckAlignment::Error
313+
} else {
314+
CheckAlignment::FutureIncompat
315+
},
315316
),
316317
);
317318

compiler/rustc_const_eval/src/const_eval/machine.rs

+55-4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
use rustc_hir::def::DefKind;
2-
use rustc_hir::LangItem;
2+
use rustc_hir::{LangItem, CRATE_HIR_ID};
33
use rustc_middle::mir;
44
use rustc_middle::mir::interpret::PointerArithmetic;
55
use rustc_middle::ty::layout::FnAbiOf;
66
use rustc_middle::ty::{self, Ty, TyCtxt};
7+
use rustc_session::lint::builtin::INVALID_ALIGNMENT;
78
use std::borrow::Borrow;
89
use std::hash::Hash;
910
use std::ops::ControlFlow;
@@ -47,14 +48,34 @@ pub struct CompileTimeInterpreter<'mir, 'tcx> {
4748
pub(super) can_access_statics: bool,
4849

4950
/// Whether to check alignment during evaluation.
50-
pub(super) check_alignment: bool,
51+
pub(super) check_alignment: CheckAlignment,
52+
}
53+
54+
#[derive(Copy, Clone)]
55+
pub enum CheckAlignment {
56+
/// Ignore alignment when following relocations.
57+
/// This is mainly used in interning.
58+
No,
59+
/// Hard error when dereferencing a misaligned pointer.
60+
Error,
61+
/// Emit a future incompat lint when dereferencing a misaligned pointer.
62+
FutureIncompat,
63+
}
64+
65+
impl CheckAlignment {
66+
pub fn should_check(&self) -> bool {
67+
match self {
68+
CheckAlignment::No => false,
69+
CheckAlignment::Error | CheckAlignment::FutureIncompat => true,
70+
}
71+
}
5172
}
5273

5374
impl<'mir, 'tcx> CompileTimeInterpreter<'mir, 'tcx> {
5475
pub(crate) fn new(
5576
const_eval_limit: Limit,
5677
can_access_statics: bool,
57-
check_alignment: bool,
78+
check_alignment: CheckAlignment,
5879
) -> Self {
5980
CompileTimeInterpreter {
6081
steps_remaining: const_eval_limit.0,
@@ -309,7 +330,7 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
309330
const PANIC_ON_ALLOC_FAIL: bool = false; // will be raised as a proper error
310331

311332
#[inline(always)]
312-
fn enforce_alignment(ecx: &InterpCx<'mir, 'tcx, Self>) -> bool {
333+
fn enforce_alignment(ecx: &InterpCx<'mir, 'tcx, Self>) -> CheckAlignment {
313334
ecx.machine.check_alignment
314335
}
315336

@@ -318,6 +339,36 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
318339
ecx.tcx.sess.opts.unstable_opts.extra_const_ub_checks
319340
}
320341

342+
fn alignment_check_failed(
343+
ecx: &InterpCx<'mir, 'tcx, Self>,
344+
has: Align,
345+
required: Align,
346+
check: CheckAlignment,
347+
) -> InterpResult<'tcx, ()> {
348+
let err = err_ub!(AlignmentCheckFailed { has, required }).into();
349+
match check {
350+
CheckAlignment::Error => Err(err),
351+
CheckAlignment::No => span_bug!(
352+
ecx.cur_span(),
353+
"`alignment_check_failed` called when no alignment check requested"
354+
),
355+
CheckAlignment::FutureIncompat => {
356+
let err = ConstEvalErr::new(ecx, err, None);
357+
ecx.tcx.struct_span_lint_hir(
358+
INVALID_ALIGNMENT,
359+
ecx.stack().iter().find_map(|frame| frame.lint_root()).unwrap_or(CRATE_HIR_ID),
360+
err.span,
361+
err.error.to_string(),
362+
|db| {
363+
err.decorate(db, |_| {});
364+
db
365+
},
366+
);
367+
Ok(())
368+
}
369+
}
370+
}
371+
321372
fn load_mir(
322373
ecx: &InterpCx<'mir, 'tcx, Self>,
323374
instance: ty::InstanceDef<'tcx>,

compiler/rustc_const_eval/src/interpret/eval_context.rs

+10-6
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,15 @@ impl<'mir, 'tcx, Prov: Provenance, Extra> Frame<'mir, 'tcx, Prov, Extra> {
248248
Right(span) => span,
249249
}
250250
}
251+
252+
pub fn lint_root(&self) -> Option<hir::HirId> {
253+
self.current_source_info().and_then(|source_info| {
254+
match &self.body.source_scopes[source_info.scope].local_data {
255+
mir::ClearCrossCrate::Set(data) => Some(data.lint_root),
256+
mir::ClearCrossCrate::Clear => None,
257+
}
258+
})
259+
}
251260
}
252261

253262
impl<'tcx> fmt::Display for FrameInfo<'tcx> {
@@ -954,12 +963,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
954963
// This deliberately does *not* honor `requires_caller_location` since it is used for much
955964
// more than just panics.
956965
for frame in stack.iter().rev() {
957-
let lint_root = frame.current_source_info().and_then(|source_info| {
958-
match &frame.body.source_scopes[source_info.scope].local_data {
959-
mir::ClearCrossCrate::Set(data) => Some(data.lint_root),
960-
mir::ClearCrossCrate::Clear => None,
961-
}
962-
});
966+
let lint_root = frame.lint_root();
963967
let span = frame.current_span();
964968

965969
frames.push(FrameInfo { span, instance: frame.instance, lint_root });

compiler/rustc_const_eval/src/interpret/machine.rs

+11-2
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@ use rustc_ast::{InlineAsmOptions, InlineAsmTemplatePiece};
1010
use rustc_middle::mir;
1111
use rustc_middle::ty::{self, Ty, TyCtxt};
1212
use rustc_span::def_id::DefId;
13-
use rustc_target::abi::Size;
13+
use rustc_target::abi::{Align, Size};
1414
use rustc_target::spec::abi::Abi as CallAbi;
1515

16+
use crate::const_eval::CheckAlignment;
17+
1618
use super::{
1719
AllocId, AllocRange, Allocation, ConstAllocation, Frame, ImmTy, InterpCx, InterpResult,
1820
MemoryKind, OpTy, Operand, PlaceTy, Pointer, Provenance, Scalar, StackPopUnwind,
@@ -122,14 +124,21 @@ pub trait Machine<'mir, 'tcx>: Sized {
122124
const PANIC_ON_ALLOC_FAIL: bool;
123125

124126
/// Whether memory accesses should be alignment-checked.
125-
fn enforce_alignment(ecx: &InterpCx<'mir, 'tcx, Self>) -> bool;
127+
fn enforce_alignment(ecx: &InterpCx<'mir, 'tcx, Self>) -> CheckAlignment;
126128

127129
/// Whether, when checking alignment, we should look at the actual address and thus support
128130
/// custom alignment logic based on whatever the integer address happens to be.
129131
///
130132
/// If this returns true, Provenance::OFFSET_IS_ADDR must be true.
131133
fn use_addr_for_alignment_check(ecx: &InterpCx<'mir, 'tcx, Self>) -> bool;
132134

135+
fn alignment_check_failed(
136+
ecx: &InterpCx<'mir, 'tcx, Self>,
137+
has: Align,
138+
required: Align,
139+
check: CheckAlignment,
140+
) -> InterpResult<'tcx, ()>;
141+
133142
/// Whether to enforce the validity invariant
134143
fn enforce_validity(ecx: &InterpCx<'mir, 'tcx, Self>) -> bool;
135144

0 commit comments

Comments
 (0)