Skip to content

Commit 2335ab8

Browse files
committed
Check for occupied niches
1 parent 2dbd623 commit 2335ab8

38 files changed

+1955
-16
lines changed

Diff for: compiler/rustc_codegen_ssa/src/common.rs

+7-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
use rustc_hir::LangItem;
44
use rustc_middle::mir;
5-
use rustc_middle::ty::{self, layout::TyAndLayout, Ty, TyCtxt};
5+
use rustc_middle::ty::{self, layout::TyAndLayout, GenericArg, Ty, TyCtxt};
66
use rustc_span::Span;
77

88
use crate::base;
@@ -120,10 +120,15 @@ pub fn build_langcall<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
120120
bx: &Bx,
121121
span: Option<Span>,
122122
li: LangItem,
123+
generic: Option<GenericArg<'tcx>>,
123124
) -> (Bx::FnAbiOfResult, Bx::Value) {
124125
let tcx = bx.tcx();
125126
let def_id = tcx.require_lang_item(li, span);
126-
let instance = ty::Instance::mono(tcx, def_id);
127+
let instance = if let Some(arg) = generic {
128+
ty::Instance::new(def_id, tcx.mk_args(&[arg]))
129+
} else {
130+
ty::Instance::mono(tcx, def_id)
131+
};
127132
(bx.fn_abi_of_instance(instance, ty::List::empty()), bx.get_fn_addr(instance))
128133
}
129134

Diff for: compiler/rustc_codegen_ssa/src/mir/block.rs

+21-6
Original file line numberDiff line numberDiff line change
@@ -631,7 +631,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
631631
}
632632
};
633633

634-
let (fn_abi, llfn) = common::build_langcall(bx, Some(span), lang_item);
634+
let (fn_abi, llfn) = common::build_langcall(bx, Some(span), lang_item, None);
635635

636636
// Codegen the actual panic invoke/call.
637637
let merging_succ = helper.do_call(self, bx, fn_abi, llfn, &args, None, unwind, &[], false);
@@ -650,7 +650,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
650650
self.set_debug_loc(bx, terminator.source_info);
651651

652652
// Obtain the panic entry point.
653-
let (fn_abi, llfn) = common::build_langcall(bx, Some(span), reason.lang_item());
653+
let (fn_abi, llfn) = common::build_langcall(bx, Some(span), reason.lang_item(), None);
654654

655655
// Codegen the actual panic invoke/call.
656656
let merging_succ = helper.do_call(
@@ -711,8 +711,12 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
711711
let msg = bx.const_str(&msg_str);
712712

713713
// Obtain the panic entry point.
714-
let (fn_abi, llfn) =
715-
common::build_langcall(bx, Some(source_info.span), LangItem::PanicNounwind);
714+
let (fn_abi, llfn) = common::build_langcall(
715+
bx,
716+
Some(source_info.span),
717+
LangItem::PanicNounwind,
718+
None,
719+
);
716720

717721
// Codegen the actual panic invoke/call.
718722
helper.do_call(
@@ -1186,6 +1190,17 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
11861190
) -> MergingSucc {
11871191
debug!("codegen_terminator: {:?}", terminator);
11881192

1193+
if bx.tcx().may_insert_niche_checks() {
1194+
if let mir::TerminatorKind::Return = terminator.kind {
1195+
let op = mir::Operand::Copy(mir::Place::return_place());
1196+
let ty = op.ty(self.mir, bx.tcx());
1197+
let ty = self.monomorphize(ty);
1198+
if let Some(niche) = bx.layout_of(ty).largest_niche {
1199+
self.codegen_niche_check(bx, op, niche, terminator.source_info);
1200+
}
1201+
}
1202+
}
1203+
11891204
let helper = TerminatorCodegenHelper { bb, terminator };
11901205

11911206
let mergeable_succ = || {
@@ -1454,7 +1469,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
14541469
tuple.layout.fields.count()
14551470
}
14561471

1457-
fn get_caller_location(
1472+
pub fn get_caller_location(
14581473
&mut self,
14591474
bx: &mut Bx,
14601475
source_info: mir::SourceInfo,
@@ -1595,7 +1610,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
15951610

15961611
self.set_debug_loc(&mut bx, mir::SourceInfo::outermost(self.mir.span));
15971612

1598-
let (fn_abi, fn_ptr) = common::build_langcall(&bx, None, reason.lang_item());
1613+
let (fn_abi, fn_ptr) = common::build_langcall(&bx, None, reason.lang_item(), None);
15991614
let fn_ty = bx.fn_decl_backend_type(fn_abi);
16001615

16011616
let llret = bx.call(fn_ty, None, Some(fn_abi), fn_ptr, &[], funclet.as_ref());

Diff for: compiler/rustc_codegen_ssa/src/mir/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ pub mod coverageinfo;
1818
pub mod debuginfo;
1919
mod intrinsic;
2020
mod locals;
21+
mod niche_check;
2122
pub mod operand;
2223
pub mod place;
2324
mod rvalue;

Diff for: compiler/rustc_codegen_ssa/src/mir/niche_check.rs

+291
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
use rustc_hir::LangItem;
2+
use rustc_middle::mir;
3+
use rustc_middle::mir::visit::Visitor;
4+
use rustc_middle::mir::visit::{NonMutatingUseContext, PlaceContext};
5+
use rustc_middle::ty::Mutability;
6+
use rustc_middle::ty::Ty;
7+
use rustc_middle::ty::TyCtxt;
8+
use rustc_middle::ty::TypeAndMut;
9+
use rustc_span::Span;
10+
use rustc_target::abi::Integer;
11+
use rustc_target::abi::Niche;
12+
use rustc_target::abi::Primitive;
13+
use rustc_target::abi::Size;
14+
15+
use super::FunctionCx;
16+
use crate::base;
17+
use crate::common;
18+
use crate::mir::OperandValue;
19+
use crate::traits::*;
20+
21+
pub struct NicheFinder<'s, 'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> {
22+
pub fx: &'s mut FunctionCx<'a, 'tcx, Bx>,
23+
pub bx: &'s mut Bx,
24+
pub places: Vec<(mir::Operand<'tcx>, Niche)>,
25+
}
26+
27+
impl<'s, 'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> Visitor<'tcx> for NicheFinder<'s, 'a, 'tcx, Bx> {
28+
fn visit_rvalue(&mut self, rvalue: &mir::Rvalue<'tcx>, location: mir::Location) {
29+
match rvalue {
30+
mir::Rvalue::Cast(mir::CastKind::Transmute, op, ty) => {
31+
let ty = self.fx.monomorphize(*ty);
32+
if let Some(niche) = self.bx.layout_of(ty).largest_niche {
33+
self.places.push((op.clone(), niche));
34+
}
35+
}
36+
_ => self.super_rvalue(rvalue, location),
37+
}
38+
}
39+
40+
fn visit_terminator(&mut self, terminator: &mir::Terminator<'tcx>, _location: mir::Location) {
41+
if let mir::TerminatorKind::Return = terminator.kind {
42+
let op = mir::Operand::Copy(mir::Place::return_place());
43+
let ty = op.ty(self.fx.mir, self.bx.tcx());
44+
let ty = self.fx.monomorphize(ty);
45+
if let Some(niche) = self.bx.layout_of(ty).largest_niche {
46+
self.places.push((op, niche));
47+
}
48+
}
49+
}
50+
51+
fn visit_place(
52+
&mut self,
53+
place: &mir::Place<'tcx>,
54+
context: PlaceContext,
55+
_location: mir::Location,
56+
) {
57+
match context {
58+
PlaceContext::NonMutatingUse(
59+
NonMutatingUseContext::Copy | NonMutatingUseContext::Move,
60+
) => {}
61+
_ => {
62+
return;
63+
}
64+
}
65+
66+
let ty = place.ty(self.fx.mir, self.bx.tcx()).ty;
67+
let ty = self.fx.monomorphize(ty);
68+
if let Some(niche) = self.bx.layout_of(ty).largest_niche {
69+
self.places.push((mir::Operand::Copy(*place), niche));
70+
};
71+
}
72+
}
73+
74+
use rustc_target::abi::Abi;
75+
use rustc_target::abi::Scalar;
76+
use rustc_target::abi::WrappingRange;
77+
impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
78+
fn value_in_niche(
79+
&mut self,
80+
bx: &mut Bx,
81+
op: crate::mir::OperandRef<'tcx, Bx::Value>,
82+
niche: Niche,
83+
) -> Option<Bx::Value> {
84+
let niche_ty = niche.ty(bx.tcx());
85+
let niche_layout = bx.layout_of(niche_ty);
86+
87+
let (imm, from_scalar, from_backend_ty) = match op.val {
88+
OperandValue::Immediate(imm) => {
89+
let Abi::Scalar(from_scalar) = op.layout.abi else { unreachable!() };
90+
let from_backend_ty = bx.backend_type(op.layout);
91+
(imm, from_scalar, from_backend_ty)
92+
}
93+
OperandValue::Pair(first, second) => {
94+
let Abi::ScalarPair(first_scalar, second_scalar) = op.layout.abi else {
95+
unreachable!()
96+
};
97+
if niche.offset == Size::ZERO {
98+
(first, first_scalar, bx.scalar_pair_element_backend_type(op.layout, 0, true))
99+
} else {
100+
// yolo
101+
(second, second_scalar, bx.scalar_pair_element_backend_type(op.layout, 1, true))
102+
}
103+
}
104+
OperandValue::ZeroSized => unreachable!(),
105+
OperandValue::Ref(ptr, _metadata, _align) => {
106+
// General case: Load the niche primitive via pointer arithmetic.
107+
let niche_ptr_ty =
108+
Ty::new_ptr(bx.tcx(), TypeAndMut { ty: niche_ty, mutbl: Mutability::Not });
109+
let ptr = bx.pointercast(ptr, bx.backend_type(bx.layout_of(niche_ptr_ty)));
110+
111+
let offset = niche.offset.bytes() / niche_layout.size.bytes();
112+
let niche_backend_ty = bx.backend_type(bx.layout_of(niche_ty));
113+
let ptr = bx.inbounds_gep(niche_backend_ty, ptr, &[bx.const_usize(offset)]);
114+
let value = bx.load(niche_backend_ty, ptr, rustc_target::abi::Align::ONE);
115+
return Some(value);
116+
}
117+
};
118+
119+
// Any type whose ABI is a Scalar bool is turned into an i1, so it cannot contain a value
120+
// outside of its niche.
121+
if from_scalar.is_bool() {
122+
return None;
123+
}
124+
125+
let to_scalar = Scalar::Initialized {
126+
value: niche.value,
127+
valid_range: WrappingRange::full(niche.size(bx.tcx())),
128+
};
129+
let to_backend_ty = bx.backend_type(niche_layout);
130+
if from_backend_ty == to_backend_ty {
131+
return Some(imm);
132+
}
133+
let value = self.transmute_immediate(
134+
bx,
135+
imm,
136+
from_scalar,
137+
from_backend_ty,
138+
to_scalar,
139+
to_backend_ty,
140+
);
141+
Some(value)
142+
}
143+
144+
#[instrument(level = "debug", skip(self, bx))]
145+
pub fn codegen_niche_check(
146+
&mut self,
147+
bx: &mut Bx,
148+
mir_op: mir::Operand<'tcx>,
149+
niche: Niche,
150+
source_info: mir::SourceInfo,
151+
) {
152+
let op_ty = self.monomorphize(mir_op.ty(self.mir, bx.tcx()));
153+
if op_ty == bx.tcx().types.bool {
154+
return;
155+
}
156+
157+
let op = self.codegen_operand(bx, &mir_op);
158+
159+
let Some(value_in_niche) = self.value_in_niche(bx, op, niche) else {
160+
return;
161+
};
162+
let size = niche.size(bx.tcx());
163+
164+
let start = niche.scalar(niche.valid_range.start, bx);
165+
let end = niche.scalar(niche.valid_range.end, bx);
166+
167+
let binop_le = base::bin_op_to_icmp_predicate(mir::BinOp::Le.to_hir_binop(), false);
168+
let binop_ge = base::bin_op_to_icmp_predicate(mir::BinOp::Ge.to_hir_binop(), false);
169+
let is_valid = if niche.valid_range.start == 0 {
170+
bx.icmp(binop_le, value_in_niche, end)
171+
} else if niche.valid_range.end == (u128::MAX >> 128 - size.bits()) {
172+
bx.icmp(binop_ge, value_in_niche, start)
173+
} else {
174+
// We need to check if the value is within a *wrapping* range. We could do this:
175+
// (niche >= start) && (niche <= end)
176+
// But what we're going to actually do is this:
177+
// max = end - start
178+
// (niche - start) <= max
179+
// The latter is much more complicated conceptually, but is actually less operations
180+
// because we can compute max in codegen.
181+
let mut max = niche.valid_range.end.wrapping_sub(niche.valid_range.start);
182+
let size = niche.size(bx.tcx());
183+
if size.bits() < 128 {
184+
let mask = (1 << size.bits()) - 1;
185+
max &= mask;
186+
}
187+
let max_adjusted_allowed_value = niche.scalar(max, bx);
188+
189+
let biased = bx.sub(value_in_niche, start);
190+
bx.icmp(binop_le, biased, max_adjusted_allowed_value)
191+
};
192+
193+
// Create destination blocks, branching on is_valid
194+
let panic = bx.append_sibling_block("panic");
195+
let success = bx.append_sibling_block("success");
196+
bx.cond_br(is_valid, success, panic);
197+
198+
// Switch to the failure block and codegen a call to the panic intrinsic
199+
bx.switch_to_block(panic);
200+
self.set_debug_loc(bx, source_info);
201+
let location = self.get_caller_location(bx, source_info).immediate();
202+
self.codegen_panic(
203+
bx,
204+
LangItem::PanicOccupiedNiche,
205+
&[value_in_niche, start, end, location],
206+
source_info.span,
207+
niche.ty(bx.tcx()),
208+
);
209+
210+
// Continue codegen in the success block.
211+
bx.switch_to_block(success);
212+
self.set_debug_loc(bx, source_info);
213+
}
214+
215+
#[instrument(level = "debug", skip(self, bx))]
216+
fn codegen_panic(
217+
&mut self,
218+
bx: &mut Bx,
219+
lang_item: LangItem,
220+
args: &[Bx::Value],
221+
span: Span,
222+
ty: Ty<'tcx>,
223+
) {
224+
let (fn_abi, fn_ptr) = common::build_langcall(bx, Some(span), lang_item, Some(ty.into()));
225+
let fn_ty = bx.fn_decl_backend_type(&fn_abi);
226+
let fn_attrs = if bx.tcx().def_kind(self.instance.def_id()).has_codegen_attrs() {
227+
Some(bx.tcx().codegen_fn_attrs(self.instance.def_id()))
228+
} else {
229+
None
230+
};
231+
232+
bx.call(fn_ty, fn_attrs, Some(&fn_abi), fn_ptr, args, None /* funclet */);
233+
bx.unreachable();
234+
}
235+
}
236+
237+
pub trait NicheExt {
238+
fn ty<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Ty<'tcx>;
239+
fn size<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Size;
240+
fn scalar<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(&self, val: u128, bx: &mut Bx) -> Bx::Value;
241+
}
242+
243+
impl NicheExt for Niche {
244+
fn ty<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Ty<'tcx> {
245+
let types = &tcx.types;
246+
match self.value {
247+
Primitive::Int(Integer::I8, _) => types.u8,
248+
Primitive::Int(Integer::I16, _) => types.u16,
249+
Primitive::Int(Integer::I32, _) => types.u32,
250+
Primitive::Int(Integer::I64, _) => types.u64,
251+
Primitive::Int(Integer::I128, _) => types.u128,
252+
Primitive::Pointer(_) => {
253+
Ty::new_ptr(tcx, TypeAndMut { ty: types.unit, mutbl: Mutability::Not })
254+
}
255+
Primitive::F32 => types.u32,
256+
Primitive::F64 => types.u64,
257+
}
258+
}
259+
260+
fn size<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Size {
261+
let bits = match self.value {
262+
Primitive::Int(Integer::I8, _) => 8,
263+
Primitive::Int(Integer::I16, _) => 16,
264+
Primitive::Int(Integer::I32, _) => 32,
265+
Primitive::Int(Integer::I64, _) => 64,
266+
Primitive::Int(Integer::I128, _) => 128,
267+
Primitive::Pointer(_) => tcx.sess.target.pointer_width as usize,
268+
Primitive::F32 => 32,
269+
Primitive::F64 => 64,
270+
};
271+
Size::from_bits(bits)
272+
}
273+
274+
fn scalar<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(&self, val: u128, bx: &mut Bx) -> Bx::Value {
275+
use rustc_middle::mir::interpret::Pointer;
276+
use rustc_middle::mir::interpret::Scalar;
277+
278+
let tcx = bx.tcx();
279+
let niche_ty = self.ty(tcx);
280+
let value = if niche_ty.is_any_ptr() {
281+
Scalar::from_maybe_pointer(Pointer::from_addr_invalid(val as u64), &tcx)
282+
} else {
283+
Scalar::from_uint(val, self.size(tcx))
284+
};
285+
let layout = rustc_target::abi::Scalar::Initialized {
286+
value: self.value,
287+
valid_range: WrappingRange::full(self.size(tcx)),
288+
};
289+
bx.scalar_to_backend(value, layout, bx.backend_type(bx.layout_of(self.ty(tcx))))
290+
}
291+
}

0 commit comments

Comments
 (0)