Skip to content

Commit d58809f

Browse files
committed
[WIP] Relative VTables for Rust
This is a WIP patch for implementing rust-lang/compiler-team#903. It adds a new unstable flag `-Zexperimental-relative-rust-abi-vtables` that makes vtables PIC-friendly. This is only supported for LLVM codegen and not supported for other backends. Early feedback on this is welcome. I'm not sure if how I implemented it is the best way of doing so since much of the actual vtable emission is heavily done during LLVM codegen. That is, the vtable to MIR looks like a normal table of pointers and byte arrays and I really only make the vtables relative on the codegen level. Locally, I can build the stage 1 compiler and runtimes with relative vtables, but I couldn't figure out how to tell the build system to only build stage 1 binaries with this flag, so I work around this by unconditionally enabling relative vtables in rustc. The end goal I think we'd like is either something akin to multilibs in clang where the compiler chooses which runtimes to use based off compilation flags, or binding this ABI to the target and have it be part of the default ABI for that target (just like how relative vtables are the default for Fuchsia in C++ with Clang). I think the later is what target modifiers do (#136966). Action Items: - I'm still experimenting with building Fuchsia with this to assert it works e2e and I still need to do some measurements to see if this is still worth pursuing. - More work will still be needed to ensure the correct relative intrinsics are emitted with CFI and LTO. Rn I'm experimenting on a normal build.
1 parent 213d946 commit d58809f

File tree

19 files changed

+452
-48
lines changed

19 files changed

+452
-48
lines changed

compiler/rustc_abi/src/lib.rs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ use std::fmt;
4343
#[cfg(feature = "nightly")]
4444
use std::iter::Step;
4545
use std::num::{NonZeroUsize, ParseIntError};
46-
use std::ops::{Add, AddAssign, Deref, Mul, RangeFull, RangeInclusive, Sub};
46+
use std::ops::{Add, AddAssign, Deref, Div, Mul, RangeFull, RangeInclusive, Sub};
4747
use std::str::FromStr;
4848

4949
use bitflags::bitflags;
@@ -819,6 +819,14 @@ impl Size {
819819
if bytes < dl.obj_size_bound() { Some(Size::from_bytes(bytes)) } else { None }
820820
}
821821

822+
#[inline]
823+
pub fn checked_div<C: HasDataLayout>(self, count: u64, cx: &C) -> Option<Size> {
824+
let dl = cx.data_layout();
825+
826+
let bytes = self.bytes().checked_div(count)?;
827+
if bytes < dl.obj_size_bound() { Some(Size::from_bytes(bytes)) } else { None }
828+
}
829+
822830
/// Truncates `value` to `self` bits and then sign-extends it to 128 bits
823831
/// (i.e., if it is negative, fill with 1's on the left).
824832
#[inline]
@@ -906,6 +914,25 @@ impl Mul<u64> for Size {
906914
}
907915
}
908916

917+
impl Div<Size> for u64 {
918+
type Output = Size;
919+
#[inline]
920+
fn div(self, size: Size) -> Size {
921+
size / self
922+
}
923+
}
924+
925+
impl Div<u64> for Size {
926+
type Output = Size;
927+
#[inline]
928+
fn div(self, count: u64) -> Size {
929+
match self.bytes().checked_div(count) {
930+
Some(bytes) => Size::from_bytes(bytes),
931+
None => panic!("Size::div: {} / {} doesn't fit in u64", self.bytes(), count),
932+
}
933+
}
934+
}
935+
909936
impl AddAssign for Size {
910937
#[inline]
911938
fn add_assign(&mut self, other: Size) {

compiler/rustc_codegen_llvm/src/builder.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,10 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
614614
}
615615
}
616616

617+
fn load_relative(&mut self, ptr: &'ll Value, byte_offset: &'ll Value) -> &'ll Value {
618+
unsafe { llvm::LLVMBuildLoadRelative(self.llbuilder, ptr, byte_offset) }
619+
}
620+
617621
fn volatile_load(&mut self, ty: &'ll Type, ptr: &'ll Value) -> &'ll Value {
618622
unsafe {
619623
let load = llvm::LLVMBuildLoad2(self.llbuilder, ty, ptr, UNNAMED);

compiler/rustc_codegen_llvm/src/common.rs

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -287,8 +287,12 @@ impl<'ll, 'tcx> ConstCodegenMethods for CodegenCx<'ll, 'tcx> {
287287
self.const_bitcast(llval, llty)
288288
};
289289
} else {
290-
let init =
291-
const_alloc_to_llvm(self, alloc.inner(), /*static*/ false);
290+
let init = const_alloc_to_llvm(
291+
self,
292+
alloc.inner(),
293+
/*static*/ false,
294+
/*vtable_base*/ None,
295+
);
292296
let alloc = alloc.inner();
293297
let value = match alloc.mutability {
294298
Mutability::Mut => self.static_addr_of_mut(init, alloc.align, None),
@@ -320,7 +324,12 @@ impl<'ll, 'tcx> ConstCodegenMethods for CodegenCx<'ll, 'tcx> {
320324
}),
321325
)))
322326
.unwrap_memory();
323-
let init = const_alloc_to_llvm(self, alloc.inner(), /*static*/ false);
327+
let init = const_alloc_to_llvm(
328+
self,
329+
alloc.inner(),
330+
/*static*/ false,
331+
/*vtable_base*/ None,
332+
);
324333
self.static_addr_of_impl(init, alloc.inner().align, None)
325334
}
326335
GlobalAlloc::Static(def_id) => {
@@ -354,7 +363,37 @@ impl<'ll, 'tcx> ConstCodegenMethods for CodegenCx<'ll, 'tcx> {
354363
}
355364

356365
fn const_data_from_alloc(&self, alloc: ConstAllocation<'_>) -> Self::Value {
357-
const_alloc_to_llvm(self, alloc.inner(), /*static*/ false)
366+
const_alloc_to_llvm(self, alloc.inner(), /*static*/ false, /*vtable_base*/ None)
367+
}
368+
369+
fn construct_vtable(
370+
&self,
371+
vtable_allocation: ConstAllocation<'_>,
372+
num_entries: u64,
373+
) -> Self::Value {
374+
// When constructing relative vtables, we need to create the global first before creating
375+
// the initializer so the initializer has references to the global we will bind it to.
376+
// Regular vtables aren't self-referential so we can just create the initializer on its
377+
// own.
378+
if self.sess().opts.unstable_opts.experimental_relative_rust_abi_vtables {
379+
let llty = self.type_array(self.type_i32(), num_entries);
380+
let vtable = self.static_addr_of_mut_from_type(
381+
llty,
382+
self.data_layout().i32_align.abi,
383+
Some("vtable"),
384+
);
385+
let init = const_alloc_to_llvm(
386+
self,
387+
vtable_allocation.inner(),
388+
/*static*/ false,
389+
Some(vtable),
390+
);
391+
self.static_addr_of_impl_for_gv(init, vtable)
392+
} else {
393+
let vtable_const = self.const_data_from_alloc(vtable_allocation);
394+
let align = self.data_layout().pointer_align().abi;
395+
self.static_addr_of(vtable_const, align, Some("vtable"))
396+
}
358397
}
359398

360399
fn const_ptr_byte_offset(&self, base_addr: Self::Value, offset: abi::Size) -> Self::Value {

compiler/rustc_codegen_llvm/src/consts.rs

Lines changed: 111 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::ops::Range;
22

3-
use rustc_abi::{Align, HasDataLayout, Primitive, Scalar, Size, WrappingRange};
3+
use rustc_abi::{Align, Endian, HasDataLayout, Primitive, Scalar, Size, WrappingRange};
44
use rustc_codegen_ssa::common;
55
use rustc_codegen_ssa::traits::*;
66
use rustc_hir::LangItem;
@@ -28,6 +28,7 @@ pub(crate) fn const_alloc_to_llvm<'ll>(
2828
cx: &CodegenCx<'ll, '_>,
2929
alloc: &Allocation,
3030
is_static: bool,
31+
vtable_base: Option<&'ll Value>,
3132
) -> &'ll Value {
3233
// We expect that callers of const_alloc_to_llvm will instead directly codegen a pointer or
3334
// integer for any &ZST where the ZST is a constant (i.e. not a static). We should never be
@@ -43,6 +44,8 @@ pub(crate) fn const_alloc_to_llvm<'ll>(
4344
let dl = cx.data_layout();
4445
let pointer_size = dl.pointer_size();
4546
let pointer_size_bytes = pointer_size.bytes() as usize;
47+
let use_relative_layout = cx.sess().opts.unstable_opts.experimental_relative_rust_abi_vtables
48+
&& vtable_base.is_some();
4649

4750
// Note: this function may call `inspect_with_uninit_and_ptr_outside_interpreter`, so `range`
4851
// must be within the bounds of `alloc` and not contain or overlap a pointer provenance.
@@ -51,7 +54,11 @@ pub(crate) fn const_alloc_to_llvm<'ll>(
5154
cx: &'a CodegenCx<'ll, 'b>,
5255
alloc: &'a Allocation,
5356
range: Range<usize>,
57+
use_relative_layout: bool,
5458
) {
59+
let dl = cx.data_layout();
60+
let pointer_size = dl.pointer_size();
61+
let pointer_size_bytes = pointer_size.bytes() as usize;
5562
let chunks = alloc.init_mask().range_as_init_chunks(range.clone().into());
5663

5764
let chunk_to_llval = move |chunk| match chunk {
@@ -74,7 +81,43 @@ pub(crate) fn const_alloc_to_llvm<'ll>(
7481
let allow_uninit_chunks = chunks.clone().take(max.saturating_add(1)).count() <= max;
7582

7683
if allow_uninit_chunks {
77-
llvals.extend(chunks.map(chunk_to_llval));
84+
if use_relative_layout {
85+
// Rather than being stored as a struct of pointers or byte-arrays, a relative
86+
// vtable is a pure i32 array, so its components must be chunks of i32s. Here we
87+
// explicitly group any sequence of bytes into i32s.
88+
//
89+
// Normally we can only do this if an 8-byte constant can fit into 4 bytes.
90+
for chunk in chunks {
91+
match chunk {
92+
InitChunk::Init(range) => {
93+
let range =
94+
(range.start.bytes() as usize)..(range.end.bytes() as usize);
95+
let bytes =
96+
alloc.inspect_with_uninit_and_ptr_outside_interpreter(range);
97+
for bytes in bytes.chunks_exact(pointer_size_bytes) {
98+
assert!(
99+
bytes[4..pointer_size_bytes].iter().all(|&x| x == 0),
100+
"Cannot fit constant into 4-bytes: {:?}",
101+
bytes
102+
);
103+
let bytes: [u8; 4] = bytes[0..4].try_into().unwrap();
104+
let val: u32 = match dl.endian {
105+
Endian::Big => u32::from_be_bytes(bytes),
106+
Endian::Little => u32::from_le_bytes(bytes),
107+
};
108+
llvals.push(cx.const_u32(val));
109+
}
110+
}
111+
InitChunk::Uninit(range) => {
112+
let len = range.end.bytes() - range.start.bytes();
113+
let val = cx.const_undef(cx.type_array(cx.type_i8(), len / 2));
114+
llvals.push(val);
115+
}
116+
};
117+
}
118+
} else {
119+
llvals.extend(chunks.map(chunk_to_llval));
120+
}
78121
} else {
79122
// If this allocation contains any uninit bytes, codegen as if it was initialized
80123
// (using some arbitrary value for uninit bytes).
@@ -92,7 +135,13 @@ pub(crate) fn const_alloc_to_llvm<'ll>(
92135
// This `inspect` is okay since we have checked that there is no provenance, it
93136
// is within the bounds of the allocation, and it doesn't affect interpreter execution
94137
// (we inspect the result after interpreter execution).
95-
append_chunks_of_init_and_uninit_bytes(&mut llvals, cx, alloc, next_offset..offset);
138+
append_chunks_of_init_and_uninit_bytes(
139+
&mut llvals,
140+
cx,
141+
alloc,
142+
next_offset..offset,
143+
use_relative_layout,
144+
);
96145
}
97146
let ptr_offset = read_target_uint(
98147
dl.endian,
@@ -108,38 +157,64 @@ pub(crate) fn const_alloc_to_llvm<'ll>(
108157

109158
let address_space = cx.tcx.global_alloc(prov.alloc_id()).address_space(cx);
110159

111-
llvals.push(cx.scalar_to_backend(
112-
InterpScalar::from_pointer(Pointer::new(prov, Size::from_bytes(ptr_offset)), &cx.tcx),
113-
Scalar::Initialized {
114-
value: Primitive::Pointer(address_space),
115-
valid_range: WrappingRange::full(pointer_size),
116-
},
117-
cx.type_ptr_ext(address_space),
118-
));
160+
let s = {
161+
let scalar = cx.scalar_to_backend(
162+
InterpScalar::from_pointer(
163+
Pointer::new(prov, Size::from_bytes(ptr_offset)),
164+
&cx.tcx,
165+
),
166+
Scalar::Initialized {
167+
value: Primitive::Pointer(address_space),
168+
valid_range: WrappingRange::full(pointer_size),
169+
},
170+
cx.type_ptr_ext(address_space),
171+
);
172+
173+
if use_relative_layout {
174+
unsafe {
175+
let fptr = llvm::LLVMDSOLocalEquivalent(scalar);
176+
let sub = llvm::LLVMConstSub(
177+
llvm::LLVMConstPtrToInt(fptr, cx.type_i64()),
178+
llvm::LLVMConstPtrToInt(vtable_base.unwrap(), cx.type_i64()),
179+
);
180+
llvm::LLVMConstTrunc(sub, cx.type_i32())
181+
}
182+
} else {
183+
scalar
184+
}
185+
};
186+
187+
llvals.push(s);
119188
next_offset = offset + pointer_size_bytes;
120189
}
121190
if alloc.len() >= next_offset {
122191
let range = next_offset..alloc.len();
123192
// This `inspect` is okay since we have check that it is after all provenance, it is
124193
// within the bounds of the allocation, and it doesn't affect interpreter execution (we
125194
// inspect the result after interpreter execution).
126-
append_chunks_of_init_and_uninit_bytes(&mut llvals, cx, alloc, range);
195+
append_chunks_of_init_and_uninit_bytes(&mut llvals, cx, alloc, range, use_relative_layout);
127196
}
128197

129198
// Avoid wrapping in a struct if there is only a single value. This ensures
130199
// that LLVM is able to perform the string merging optimization if the constant
131200
// is a valid C string. LLVM only considers bare arrays for this optimization,
132201
// not arrays wrapped in a struct. LLVM handles this at:
133202
// https://github.com/rust-lang/llvm-project/blob/acaea3d2bb8f351b740db7ebce7d7a40b9e21488/llvm/lib/Target/TargetLoweringObjectFile.cpp#L249-L280
134-
if let &[data] = &*llvals { data } else { cx.const_struct(&llvals, true) }
203+
if let &[data] = &*llvals {
204+
data
205+
} else if use_relative_layout {
206+
cx.const_array(cx.type_i32(), &llvals)
207+
} else {
208+
cx.const_struct(&llvals, true)
209+
}
135210
}
136211

137212
fn codegen_static_initializer<'ll, 'tcx>(
138213
cx: &CodegenCx<'ll, 'tcx>,
139214
def_id: DefId,
140215
) -> Result<(&'ll Value, ConstAllocation<'tcx>), ErrorHandled> {
141216
let alloc = cx.tcx.eval_static_initializer(def_id)?;
142-
Ok((const_alloc_to_llvm(cx, alloc.inner(), /*static*/ true), alloc))
217+
Ok((const_alloc_to_llvm(cx, alloc.inner(), /*static*/ true, /*vtable_base*/ None), alloc))
143218
}
144219

145220
fn set_global_alignment<'ll>(cx: &CodegenCx<'ll, '_>, gv: &'ll Value, mut align: Align) {
@@ -232,19 +307,29 @@ impl<'ll> CodegenCx<'ll, '_> {
232307
cv: &'ll Value,
233308
align: Align,
234309
kind: Option<&str>,
310+
) -> &'ll Value {
311+
let gv = self.static_addr_of_mut_from_type(self.val_ty(cv), align, kind);
312+
llvm::set_initializer(gv, cv);
313+
gv
314+
}
315+
316+
pub(crate) fn static_addr_of_mut_from_type(
317+
&self,
318+
ty: &'ll Type,
319+
align: Align,
320+
kind: Option<&str>,
235321
) -> &'ll Value {
236322
let gv = match kind {
237323
Some(kind) if !self.tcx.sess.fewer_names() => {
238324
let name = self.generate_local_symbol_name(kind);
239-
let gv = self.define_global(&name, self.val_ty(cv)).unwrap_or_else(|| {
325+
let gv = self.define_global(&name, ty).unwrap_or_else(|| {
240326
bug!("symbol `{}` is already defined", name);
241327
});
242328
llvm::set_linkage(gv, llvm::Linkage::PrivateLinkage);
243329
gv
244330
}
245-
_ => self.define_private_global(self.val_ty(cv)),
331+
_ => self.define_private_global(ty),
246332
};
247-
llvm::set_initializer(gv, cv);
248333
set_global_alignment(self, gv, align);
249334
llvm::set_unnamed_address(gv, llvm::UnnamedAddr::Global);
250335
gv
@@ -277,6 +362,15 @@ impl<'ll> CodegenCx<'ll, '_> {
277362
gv
278363
}
279364

365+
pub(crate) fn static_addr_of_impl_for_gv(&self, cv: &'ll Value, gv: &'ll Value) -> &'ll Value {
366+
assert!(!self.const_globals.borrow().contains_key(&cv));
367+
let mut binding = self.const_globals.borrow_mut();
368+
binding.insert(cv, gv);
369+
llvm::set_initializer(gv, cv);
370+
llvm::set_global_constant(gv, true);
371+
gv
372+
}
373+
280374
#[instrument(level = "debug", skip(self))]
281375
pub(crate) fn get_static(&self, def_id: DefId) -> &'ll Value {
282376
let instance = Instance::mono(self.tcx, def_id);

compiler/rustc_codegen_llvm/src/llvm/ffi.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1169,6 +1169,8 @@ unsafe extern "C" {
11691169
pub(crate) fn LLVMGetAggregateElement(ConstantVal: &Value, Idx: c_uint) -> Option<&Value>;
11701170
pub(crate) fn LLVMGetConstOpcode(ConstantVal: &Value) -> Opcode;
11711171
pub(crate) fn LLVMIsAConstantExpr(Val: &Value) -> Option<&Value>;
1172+
pub(crate) fn LLVMConstSub<'a>(LHS: &'a Value, RHS: &'a Value) -> &'a Value;
1173+
pub(crate) fn LLVMConstTrunc<'a>(ConstantVal: &'a Value, ToType: &'a Type) -> &'a Value;
11721174

11731175
// Operations on global variables, functions, and aliases (globals)
11741176
pub(crate) fn LLVMIsDeclaration(Global: &Value) -> Bool;
@@ -1198,6 +1200,13 @@ unsafe extern "C" {
11981200
pub(crate) safe fn LLVMSetTailCall(CallInst: &Value, IsTailCall: Bool);
11991201
pub(crate) safe fn LLVMRustSetTailCallKind(CallInst: &Value, Kind: TailCallKind);
12001202

1203+
pub(crate) fn LLVMDSOLocalEquivalent(GlobalVar: &Value) -> &Value;
1204+
pub(crate) fn LLVMBuildLoadRelative<'a>(
1205+
Builder: &Builder<'a>,
1206+
Ptr: &'a Value,
1207+
ByteOffset: &'a Value,
1208+
) -> &'a Value;
1209+
12011210
// Operations on attributes
12021211
pub(crate) fn LLVMCreateStringAttribute(
12031212
C: &Context,

compiler/rustc_codegen_llvm/src/type_.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,13 @@ impl<'ll, 'tcx> LayoutTypeCodegenMethods<'tcx> for CodegenCx<'ll, 'tcx> {
292292
fn fn_ptr_backend_type(&self, fn_abi: &FnAbi<'tcx, Ty<'tcx>>) -> &'ll Type {
293293
fn_abi.ptr_to_llvm_type(self)
294294
}
295+
fn vtable_component_type(&self, fn_abi: &FnAbi<'tcx, Ty<'tcx>>) -> &'ll Type {
296+
if self.sess().opts.unstable_opts.experimental_relative_rust_abi_vtables {
297+
self.type_i32()
298+
} else {
299+
fn_abi.ptr_to_llvm_type(self)
300+
}
301+
}
295302
fn reg_backend_type(&self, ty: &Reg) -> &'ll Type {
296303
ty.llvm_type(self)
297304
}

0 commit comments

Comments
 (0)