Skip to content

Commit 6ff8b5f

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 d5419f1 commit 6ff8b5f

File tree

19 files changed

+458
-48
lines changed

19 files changed

+458
-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;
@@ -818,6 +818,14 @@ impl Size {
818818
if bytes < dl.obj_size_bound() { Some(Size::from_bytes(bytes)) } else { None }
819819
}
820820

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

916+
impl Div<Size> for u64 {
917+
type Output = Size;
918+
#[inline]
919+
fn div(self, size: Size) -> Size {
920+
size / self
921+
}
922+
}
923+
924+
impl Div<u64> for Size {
925+
type Output = Size;
926+
#[inline]
927+
fn div(self, count: u64) -> Size {
928+
match self.bytes().checked_div(count) {
929+
Some(bytes) => Size::from_bytes(bytes),
930+
None => panic!("Size::div: {} / {} doesn't fit in u64", self.bytes(), count),
931+
}
932+
}
933+
}
934+
908935
impl AddAssign for Size {
909936
#[inline]
910937
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
@@ -623,6 +623,10 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
623623
}
624624
}
625625

626+
fn load_relative(&mut self, ptr: &'ll Value, byte_offset: &'ll Value) -> &'ll Value {
627+
unsafe { llvm::LLVMBuildLoadRelative(self.llbuilder, ptr, byte_offset) }
628+
}
629+
626630
fn volatile_load(&mut self, ty: &'ll Type, ptr: &'ll Value) -> &'ll Value {
627631
unsafe {
628632
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
@@ -289,8 +289,12 @@ impl<'ll, 'tcx> ConstCodegenMethods for CodegenCx<'ll, 'tcx> {
289289
self.const_bitcast(llval, llty)
290290
};
291291
} else {
292-
let init =
293-
const_alloc_to_llvm(self, alloc.inner(), /*static*/ false);
292+
let init = const_alloc_to_llvm(
293+
self,
294+
alloc.inner(),
295+
/*static*/ false,
296+
/*vtable_base*/ None,
297+
);
294298
let alloc = alloc.inner();
295299
let value = match alloc.mutability {
296300
Mutability::Mut => self.static_addr_of_mut(init, alloc.align, None),
@@ -322,7 +326,12 @@ impl<'ll, 'tcx> ConstCodegenMethods for CodegenCx<'ll, 'tcx> {
322326
}),
323327
)))
324328
.unwrap_memory();
325-
let init = const_alloc_to_llvm(self, alloc.inner(), /*static*/ false);
329+
let init = const_alloc_to_llvm(
330+
self,
331+
alloc.inner(),
332+
/*static*/ false,
333+
/*vtable_base*/ None,
334+
);
326335
self.static_addr_of_impl(init, alloc.inner().align, None)
327336
}
328337
GlobalAlloc::Static(def_id) => {
@@ -356,7 +365,37 @@ impl<'ll, 'tcx> ConstCodegenMethods for CodegenCx<'ll, 'tcx> {
356365
}
357366

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

362401
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;
@@ -29,6 +29,7 @@ pub(crate) fn const_alloc_to_llvm<'ll>(
2929
cx: &CodegenCx<'ll, '_>,
3030
alloc: &Allocation,
3131
is_static: bool,
32+
vtable_base: Option<&'ll Value>,
3233
) -> &'ll Value {
3334
// We expect that callers of const_alloc_to_llvm will instead directly codegen a pointer or
3435
// integer for any &ZST where the ZST is a constant (i.e. not a static). We should never be
@@ -44,6 +45,8 @@ pub(crate) fn const_alloc_to_llvm<'ll>(
4445
let dl = cx.data_layout();
4546
let pointer_size = dl.pointer_size();
4647
let pointer_size_bytes = pointer_size.bytes() as usize;
48+
let use_relative_layout = cx.sess().opts.unstable_opts.experimental_relative_rust_abi_vtables
49+
&& vtable_base.is_some();
4750

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

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

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

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

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

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

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

146221
fn set_global_alignment<'ll>(cx: &CodegenCx<'ll, '_>, gv: &'ll Value, mut align: Align) {
@@ -233,21 +308,31 @@ impl<'ll> CodegenCx<'ll, '_> {
233308
cv: &'ll Value,
234309
align: Align,
235310
kind: Option<&str>,
311+
) -> &'ll Value {
312+
let gv = self.static_addr_of_mut_from_type(self.val_ty(cv), align, kind);
313+
llvm::set_initializer(gv, cv);
314+
gv
315+
}
316+
317+
pub(crate) fn static_addr_of_mut_from_type(
318+
&self,
319+
ty: &'ll Type,
320+
align: Align,
321+
kind: Option<&str>,
236322
) -> &'ll Value {
237323
let gv = match kind {
238324
Some(kind) if !self.tcx.sess.fewer_names() => {
239325
let name = self.generate_local_symbol_name(kind);
240-
let gv = self.define_global(&name, self.val_ty(cv)).unwrap_or_else(|| {
326+
let gv = self.define_global(&name, ty).unwrap_or_else(|| {
241327
bug!("symbol `{}` is already defined", name);
242328
});
243329
gv
244330
}
245-
_ => self.define_global("", self.val_ty(cv)).unwrap_or_else(|| {
331+
_ => self.define_global("", ty).unwrap_or_else(|| {
246332
bug!("anonymous global symbol is already defined");
247333
}),
248334
};
249335
llvm::set_linkage(gv, llvm::Linkage::PrivateLinkage);
250-
llvm::set_initializer(gv, cv);
251336
set_global_alignment(self, gv, align);
252337
llvm::set_unnamed_address(gv, llvm::UnnamedAddr::Global);
253338
gv
@@ -280,6 +365,15 @@ impl<'ll> CodegenCx<'ll, '_> {
280365
gv
281366
}
282367

368+
pub(crate) fn static_addr_of_impl_for_gv(&self, cv: &'ll Value, gv: &'ll Value) -> &'ll Value {
369+
assert!(!self.const_globals.borrow().contains_key(&cv));
370+
let mut binding = self.const_globals.borrow_mut();
371+
binding.insert(cv, gv);
372+
llvm::set_initializer(gv, cv);
373+
llvm::set_global_constant(gv, true);
374+
gv
375+
}
376+
283377
#[instrument(level = "debug", skip(self))]
284378
pub(crate) fn get_static(&self, def_id: DefId) -> &'ll Value {
285379
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
@@ -1077,6 +1077,8 @@ unsafe extern "C" {
10771077
pub(crate) fn LLVMGetAggregateElement(ConstantVal: &Value, Idx: c_uint) -> Option<&Value>;
10781078
pub(crate) fn LLVMGetConstOpcode(ConstantVal: &Value) -> Opcode;
10791079
pub(crate) fn LLVMIsAConstantExpr(Val: &Value) -> Option<&Value>;
1080+
pub(crate) fn LLVMConstSub<'a>(LHS: &'a Value, RHS: &'a Value) -> &'a Value;
1081+
pub(crate) fn LLVMConstTrunc<'a>(ConstantVal: &'a Value, ToType: &'a Type) -> &'a Value;
10801082

10811083
// Operations on global variables, functions, and aliases (globals)
10821084
pub(crate) fn LLVMIsDeclaration(Global: &Value) -> Bool;
@@ -1107,6 +1109,13 @@ unsafe extern "C" {
11071109
pub(crate) safe fn LLVMSetTailCallKind(CallInst: &Value, kind: TailCallKind);
11081110
pub(crate) safe fn LLVMSetExternallyInitialized(GlobalVar: &Value, IsExtInit: Bool);
11091111

1112+
pub(crate) fn LLVMDSOLocalEquivalent(GlobalVar: &Value) -> &Value;
1113+
pub(crate) fn LLVMBuildLoadRelative<'a>(
1114+
Builder: &Builder<'a>,
1115+
Ptr: &'a Value,
1116+
ByteOffset: &'a Value,
1117+
) -> &'a Value;
1118+
11101119
// Operations on attributes
11111120
pub(crate) fn LLVMCreateStringAttribute(
11121121
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)