Skip to content

Commit c916c89

Browse files
committed
Auto merge of rust-lang#128247 - RalfJung:import-attribute-clash, r=<try>
codegen: do not set attributes on foreign function imports Currently we are setting all the usual attributes when translating an FFI import, like ```rust extern { pub fn myfun(x: NonZeroI32) -> &'static mut (); } ``` into LLVM. However, if the same function is imported multiple times as different items in Rust, that all becomes a single LLVM declaration. The attributes set on any one of them may then apply to calls done via other imported items. For instance, the import above, *even if it is never called*, can introduce UB if there is another part of the program that also imports `myfun`, calls it via that import, and passes 0 or `myfun` returns NULL. To fix that, this PR changes the function declarations we emit to all look like `declare `@fn()`.` The one exception are Rust's own allocator functions, which have a bunch of attributes that LLVM currently only honors in the declaration, not the call site -- though with llvm/llvm-project@1953629 we can maybe avoid that in the future. The hope is that this fixes rust-lang#46188. The only cases I can still think of where we emit different declarations with the same name and one of them "wins" are: - Rust's native allocation functions. Even then, the "wrong" declarations will just have no argument and return value so I don't think this should cause problems. - Two `extern static` with the same name but different type, or an `extern static` with the same name as an imported function. Again I doubt the "type of the static" will lead LLVM to deduce things about the function or vice versa, so probably the worst that can happen is LLVM crashes. (And of course if this static actually ends up resolving to a function, that's UB unless the static has size 0. And conversely, if it resolves to a static instead of code then calling the function is UB.) Fixes rust-lang/miri#3581 by making this not UB. Cc `@nikic`
2 parents d6c8169 + 51a6b6e commit c916c89

23 files changed

+332
-231
lines changed

compiler/rustc_ast_passes/src/feature_gate.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> {
324324
&self,
325325
link_llvm_intrinsics,
326326
i.span,
327-
"linking to LLVM intrinsics is experimental"
327+
"linking to LLVM intrinsics is an internal feature reserved for the standard library"
328328
);
329329
}
330330
}

compiler/rustc_codegen_llvm/src/abi.rs

+22-3
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,12 @@ pub(crate) trait FnAbiLlvmExt<'ll, 'tcx> {
321321
);
322322

323323
/// Apply attributes to a function call.
324-
fn apply_attrs_callsite(&self, bx: &mut Builder<'_, 'll, 'tcx>, callsite: &'ll Value);
324+
fn apply_attrs_callsite(
325+
&self,
326+
bx: &mut Builder<'_, 'll, 'tcx>,
327+
callsite: &'ll Value,
328+
instance: Option<ty::Instance<'tcx>>,
329+
);
325330
}
326331

327332
impl<'ll, 'tcx> FnAbiLlvmExt<'ll, 'tcx> for FnAbi<'tcx, Ty<'tcx>> {
@@ -527,11 +532,18 @@ impl<'ll, 'tcx> FnAbiLlvmExt<'ll, 'tcx> for FnAbi<'tcx, Ty<'tcx>> {
527532

528533
// If the declaration has an associated instance, compute extra attributes based on that.
529534
if let Some(instance) = instance {
530-
llfn_attrs_from_instance(cx, llfn, instance);
535+
llfn_attrs_from_instance(cx, instance, Some(llfn), |place, attrs| {
536+
attributes::apply_to_llfn(llfn, place, attrs)
537+
});
531538
}
532539
}
533540

534-
fn apply_attrs_callsite(&self, bx: &mut Builder<'_, 'll, 'tcx>, callsite: &'ll Value) {
541+
fn apply_attrs_callsite(
542+
&self,
543+
bx: &mut Builder<'_, 'll, 'tcx>,
544+
callsite: &'ll Value,
545+
instance: Option<ty::Instance<'tcx>>,
546+
) {
535547
let mut func_attrs = SmallVec::<[_; 2]>::new();
536548
if self.ret.layout.abi.is_uninhabited() {
537549
func_attrs.push(llvm::AttributeKind::NoReturn.create_attr(bx.cx.llcx));
@@ -649,6 +661,13 @@ impl<'ll, 'tcx> FnAbiLlvmExt<'ll, 'tcx> for FnAbi<'tcx, Ty<'tcx>> {
649661
&[element_type_attr],
650662
);
651663
}
664+
665+
// If the call site has an associated instance, compute extra attributes based on that.
666+
if let Some(instance) = instance {
667+
llfn_attrs_from_instance(bx.cx, instance, None, |place, attrs| {
668+
attributes::apply_to_callsite(callsite, place, attrs)
669+
});
670+
}
652671
}
653672
}
654673

compiler/rustc_codegen_llvm/src/attributes.rs

+39-29
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ use crate::context::CodegenCx;
1414
use crate::errors::{MissingFeatures, SanitizerMemtagRequiresMte, TargetFeatureDisableOrEnable};
1515
use crate::llvm::AttributePlace::Function;
1616
use crate::llvm::{self, AllocKindFlags, Attribute, AttributeKind, AttributePlace, MemoryEffects};
17+
use crate::llvm_util;
1718
use crate::value::Value;
18-
use crate::{attributes, llvm_util};
1919

2020
pub(crate) fn apply_to_llfn(llfn: &Value, idx: AttributePlace, attrs: &[&Attribute]) {
2121
if !attrs.is_empty() {
@@ -324,13 +324,18 @@ fn create_alloc_family_attr(llcx: &llvm::Context) -> &llvm::Attribute {
324324
llvm::CreateAttrStringValue(llcx, "alloc-family", "__rust_alloc")
325325
}
326326

327-
/// Helper for `FnAbi::apply_attrs_llfn`:
327+
/// Helper for `FnAbi::apply_attrs_*`:
328328
/// Composite function which sets LLVM attributes for function depending on its AST (`#[attribute]`)
329329
/// attributes.
330+
///
331+
/// `apply_attrs` is called to apply the attributes, so this can be used both for declarations and
332+
/// calls. However, some things are not represented as attributes and can only be set on
333+
/// declarations, so `declare_llfn` should be `Some` if this is a declaration.
330334
pub(crate) fn llfn_attrs_from_instance<'ll, 'tcx>(
331335
cx: &CodegenCx<'ll, 'tcx>,
332-
llfn: &'ll Value,
333336
instance: ty::Instance<'tcx>,
337+
declare_llfn: Option<&'ll Value>,
338+
apply_attrs: impl Fn(AttributePlace, &[&Attribute]),
334339
) {
335340
let codegen_fn_attrs = cx.tcx.codegen_fn_attrs(instance.def_id());
336341

@@ -440,7 +445,7 @@ pub(crate) fn llfn_attrs_from_instance<'ll, 'tcx>(
440445
to_add.push(create_alloc_family_attr(cx.llcx));
441446
// apply to argument place instead of function
442447
let alloc_align = AttributeKind::AllocAlign.create_attr(cx.llcx);
443-
attributes::apply_to_llfn(llfn, AttributePlace::Argument(1), &[alloc_align]);
448+
apply_attrs(AttributePlace::Argument(1), &[alloc_align]);
444449
to_add.push(llvm::CreateAllocSizeAttr(cx.llcx, 0));
445450
let mut flags = AllocKindFlags::Alloc | AllocKindFlags::Aligned;
446451
if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::ALLOCATOR) {
@@ -451,7 +456,7 @@ pub(crate) fn llfn_attrs_from_instance<'ll, 'tcx>(
451456
to_add.push(llvm::CreateAllocKindAttr(cx.llcx, flags));
452457
// apply to return place instead of function (unlike all other attributes applied in this function)
453458
let no_alias = AttributeKind::NoAlias.create_attr(cx.llcx);
454-
attributes::apply_to_llfn(llfn, AttributePlace::ReturnValue, &[no_alias]);
459+
apply_attrs(AttributePlace::ReturnValue, &[no_alias]);
455460
}
456461
if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::REALLOCATOR) {
457462
to_add.push(create_alloc_family_attr(cx.llcx));
@@ -461,26 +466,28 @@ pub(crate) fn llfn_attrs_from_instance<'ll, 'tcx>(
461466
));
462467
// applies to argument place instead of function place
463468
let allocated_pointer = AttributeKind::AllocatedPointer.create_attr(cx.llcx);
464-
attributes::apply_to_llfn(llfn, AttributePlace::Argument(0), &[allocated_pointer]);
469+
apply_attrs(AttributePlace::Argument(0), &[allocated_pointer]);
465470
// apply to argument place instead of function
466471
let alloc_align = AttributeKind::AllocAlign.create_attr(cx.llcx);
467-
attributes::apply_to_llfn(llfn, AttributePlace::Argument(2), &[alloc_align]);
472+
apply_attrs(AttributePlace::Argument(2), &[alloc_align]);
468473
to_add.push(llvm::CreateAllocSizeAttr(cx.llcx, 3));
469474
let no_alias = AttributeKind::NoAlias.create_attr(cx.llcx);
470-
attributes::apply_to_llfn(llfn, AttributePlace::ReturnValue, &[no_alias]);
475+
apply_attrs(AttributePlace::ReturnValue, &[no_alias]);
471476
}
472477
if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::DEALLOCATOR) {
473478
to_add.push(create_alloc_family_attr(cx.llcx));
474479
to_add.push(llvm::CreateAllocKindAttr(cx.llcx, AllocKindFlags::Free));
475480
// applies to argument place instead of function place
476481
let allocated_pointer = AttributeKind::AllocatedPointer.create_attr(cx.llcx);
477-
attributes::apply_to_llfn(llfn, AttributePlace::Argument(0), &[allocated_pointer]);
482+
apply_attrs(AttributePlace::Argument(0), &[allocated_pointer]);
478483
}
479484
if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::CMSE_NONSECURE_ENTRY) {
480485
to_add.push(llvm::CreateAttrString(cx.llcx, "cmse_nonsecure_entry"));
481486
}
482487
if let Some(align) = codegen_fn_attrs.alignment {
483-
llvm::set_alignment(llfn, align);
488+
if let Some(llfn) = declare_llfn {
489+
llvm::set_alignment(llfn, align);
490+
}
484491
}
485492
if let Some(backchain) = backchain_attr(cx) {
486493
to_add.push(backchain);
@@ -499,24 +506,27 @@ pub(crate) fn llfn_attrs_from_instance<'ll, 'tcx>(
499506
let function_features =
500507
codegen_fn_attrs.target_features.iter().map(|f| f.name.as_str()).collect::<Vec<&str>>();
501508

502-
if let Some(f) = llvm_util::check_tied_features(
503-
cx.tcx.sess,
504-
&function_features.iter().map(|f| (*f, true)).collect(),
505-
) {
506-
let span = cx
507-
.tcx
508-
.get_attrs(instance.def_id(), sym::target_feature)
509-
.next()
510-
.map_or_else(|| cx.tcx.def_span(instance.def_id()), |a| a.span);
511-
cx.tcx
512-
.dcx()
513-
.create_err(TargetFeatureDisableOrEnable {
514-
features: f,
515-
span: Some(span),
516-
missing_features: Some(MissingFeatures),
517-
})
518-
.emit();
519-
return;
509+
// HACK: Avoid emitting the lint twice.
510+
if declare_llfn.is_some() {
511+
if let Some(f) = llvm_util::check_tied_features(
512+
cx.tcx.sess,
513+
&function_features.iter().map(|f| (*f, true)).collect(),
514+
) {
515+
let span = cx
516+
.tcx
517+
.get_attrs(instance.def_id(), sym::target_feature)
518+
.next()
519+
.map_or_else(|| cx.tcx.def_span(instance.def_id()), |a| a.span);
520+
cx.tcx
521+
.dcx()
522+
.create_err(TargetFeatureDisableOrEnable {
523+
features: f,
524+
span: Some(span),
525+
missing_features: Some(MissingFeatures),
526+
})
527+
.emit();
528+
return;
529+
}
520530
}
521531

522532
let function_features = function_features
@@ -558,7 +568,7 @@ pub(crate) fn llfn_attrs_from_instance<'ll, 'tcx>(
558568
to_add.push(llvm::CreateAttrStringValue(cx.llcx, "target-features", &target_features));
559569
}
560570

561-
attributes::apply_to_llfn(llfn, Function, &to_add);
571+
apply_attrs(Function, &to_add);
562572
}
563573

564574
fn wasm_import_module(tcx: TyCtxt<'_>, id: DefId) -> Option<&String> {

compiler/rustc_codegen_llvm/src/builder.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
262262
)
263263
};
264264
if let Some(fn_abi) = fn_abi {
265-
fn_abi.apply_attrs_callsite(self, invoke);
265+
fn_abi.apply_attrs_callsite(self, invoke, instance);
266266
}
267267
invoke
268268
}
@@ -1307,7 +1307,7 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
13071307
)
13081308
};
13091309
if let Some(fn_abi) = fn_abi {
1310-
fn_abi.apply_attrs_callsite(self, call);
1310+
fn_abi.apply_attrs_callsite(self, call, instance);
13111311
}
13121312
call
13131313
}
@@ -1657,7 +1657,7 @@ impl<'a, 'll, 'tcx> Builder<'a, 'll, 'tcx> {
16571657
)
16581658
};
16591659
if let Some(fn_abi) = fn_abi {
1660-
fn_abi.apply_attrs_callsite(self, callbr);
1660+
fn_abi.apply_attrs_callsite(self, callbr, instance);
16611661
}
16621662
callbr
16631663
}

compiler/rustc_codegen_llvm/src/declare.rs

+38-1
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@
1212
//! * When in doubt, define.
1313
1414
use itertools::Itertools;
15-
use rustc_codegen_ssa::traits::TypeMembershipMethods;
15+
use rustc_codegen_ssa::traits::{BaseTypeMethods, TypeMembershipMethods};
1616
use rustc_data_structures::fx::FxIndexSet;
17+
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
1718
use rustc_middle::ty::{Instance, Ty};
1819
use rustc_sanitizers::{cfi, kcfi};
1920
use smallvec::SmallVec;
@@ -127,6 +128,42 @@ impl<'ll, 'tcx> CodegenCx<'ll, 'tcx> {
127128
) -> &'ll Value {
128129
debug!("declare_rust_fn(name={:?}, fn_abi={:?})", name, fn_abi);
129130

131+
// FFI imports need to be treated specially: in Rust one can import the same function
132+
// with different signatures, but LLVM can only import each function once. Within a
133+
// codegen unit, whatever declaration we set up last will "win"; across codegen units,
134+
// LLVM is doing some sort of unspecified merging as part of LTO.
135+
// This is partially unsound due to LLVM bugs (https://github.com/llvm/llvm-project/issues/58976),
136+
// but on the Rust side we try to ensure soundness by making the least amount of promises
137+
// for these imports: no matter the signatures, we declare all functions as `fn()`.
138+
// If the same symbol is declared both as a function and a static, we just hope that
139+
// doesn't lead to unsoundness (or LLVM crashes)...
140+
let is_foreign_item = instance.is_some_and(|i| self.tcx.is_foreign_item(i.def_id()));
141+
// If this is a Rust native allocator function, we want its attributes, so we do not
142+
// treat it like the other FFI imports. If a user declares `__rust_alloc` we're going to have
143+
// one import with the attributes and one with the default signature; that should hopefully be fine
144+
// even if LLVM only sees the default one.
145+
let is_rust_alloc_fn = instance.is_some_and(|i| {
146+
let codegen_fn_flags = self.tcx.codegen_fn_attrs(i.def_id()).flags;
147+
codegen_fn_flags.contains(CodegenFnAttrFlags::ALLOCATOR)
148+
|| codegen_fn_flags.contains(CodegenFnAttrFlags::ALLOCATOR_ZEROED)
149+
|| codegen_fn_flags.contains(CodegenFnAttrFlags::REALLOCATOR)
150+
|| codegen_fn_flags.contains(CodegenFnAttrFlags::DEALLOCATOR)
151+
});
152+
// If this is calling an LLVM intrinsic, we must set the right signature or LLVM complains.
153+
// These are also unstable to call so nobody but us can screw up their signature.
154+
let is_llvm_builtin = name.starts_with("llvm.");
155+
if is_foreign_item && !is_rust_alloc_fn && !is_llvm_builtin {
156+
return declare_raw_fn(
157+
self,
158+
name,
159+
llvm::CallConv::CCallConv,
160+
llvm::UnnamedAddr::Global,
161+
llvm::Visibility::Default,
162+
// The function type for `fn()`.
163+
self.type_func(&[], self.type_void()),
164+
);
165+
}
166+
130167
// Function addresses in Rust are never significant, allowing functions to
131168
// be merged.
132169
let llfn = declare_raw_fn(

tests/codegen/aarch64-struct-align-128.rs

+36-16
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,17 @@ pub struct Wrapped8 {
3939
}
4040

4141
extern "C" {
42-
// linux: declare void @test_8([2 x i64], [2 x i64], [2 x i64])
43-
// darwin: declare void @test_8([2 x i64], [2 x i64], [2 x i64])
44-
// win: declare void @test_8([2 x i64], [2 x i64], [2 x i64])
4542
fn test_8(a: Align8, b: Transparent8, c: Wrapped8);
4643
}
4744

45+
#[no_mangle]
46+
fn call_test_8(a: Align8, b: Transparent8, c: Wrapped8) {
47+
// linux: call void @test_8([2 x i64] {{%.*}}, [2 x i64] {{%.*}}, [2 x i64] {{%.*}})
48+
// darwin: call void @test_8([2 x i64] {{%.*}}, [2 x i64] {{%.*}}, [2 x i64] {{%.*}})
49+
// win: call void @test_8([2 x i64] {{%.*}}, [2 x i64] {{%.*}}, [2 x i64] {{%.*}})
50+
unsafe { test_8(a, b, c) }
51+
}
52+
4853
// Passed as `i128`, since it's an aggregate with size <= 128 bits, align = 128 bits.
4954
// EXCEPT on Linux, where there's a special case to use its unadjusted alignment,
5055
// making it the same as `Align8`, so it's be passed as `[i64 x 2]`.
@@ -69,12 +74,17 @@ pub struct Wrapped16 {
6974
}
7075

7176
extern "C" {
72-
// linux: declare void @test_16([2 x i64], [2 x i64], i128)
73-
// darwin: declare void @test_16(i128, i128, i128)
74-
// win: declare void @test_16(i128, i128, i128)
7577
fn test_16(a: Align16, b: Transparent16, c: Wrapped16);
7678
}
7779

80+
#[no_mangle]
81+
fn call_test_16(a: Align16, b: Transparent16, c: Wrapped16) {
82+
// linux: call void @test_16([2 x i64] {{%.*}}, [2 x i64] {{%.*}}, i128 {{%.*}})
83+
// darwin: call void @test_16(i128 {{%.*}}, i128 {{%.*}}, i128 {{%.*}})
84+
// win: call void @test_16(i128 {{%.*}}, i128 {{%.*}}, i128 {{%.*}})
85+
unsafe { test_16(a, b, c) }
86+
}
87+
7888
// Passed as `i128`, since it's an aggregate with size <= 128 bits, align = 128 bits.
7989
#[repr(C)]
8090
pub struct I128 {
@@ -94,12 +104,17 @@ pub struct WrappedI128 {
94104
}
95105

96106
extern "C" {
97-
// linux: declare void @test_i128(i128, i128, i128)
98-
// darwin: declare void @test_i128(i128, i128, i128)
99-
// win: declare void @test_i128(i128, i128, i128)
100107
fn test_i128(a: I128, b: TransparentI128, c: WrappedI128);
101108
}
102109

110+
#[no_mangle]
111+
fn call_test_i128(a: I128, b: TransparentI128, c: WrappedI128) {
112+
// linux: call void @test_i128(i128 {{%.*}}, i128 {{%.*}}, i128 {{%.*}})
113+
// darwin: call void @test_i128(i128 {{%.*}}, i128 {{%.*}}, i128 {{%.*}})
114+
// win: call void @test_i128(i128 {{%.*}}, i128 {{%.*}}, i128 {{%.*}})
115+
unsafe { test_i128(a, b, c) }
116+
}
117+
103118
// Passed as `[2 x i64]`, since it's an aggregate with size <= 128 bits, align < 128 bits.
104119
// Note that the Linux special case does not apply, because packing is not considered "adjustment".
105120
#[repr(C)]
@@ -121,12 +136,17 @@ pub struct WrappedPacked {
121136
}
122137

123138
extern "C" {
124-
// linux: declare void @test_packed([2 x i64], [2 x i64], [2 x i64])
125-
// darwin: declare void @test_packed([2 x i64], [2 x i64], [2 x i64])
126-
// win: declare void @test_packed([2 x i64], [2 x i64], [2 x i64])
127139
fn test_packed(a: Packed, b: TransparentPacked, c: WrappedPacked);
128140
}
129141

142+
#[no_mangle]
143+
fn call_test_packed(a: Packed, b: TransparentPacked, c: WrappedPacked) {
144+
// linux: call void @test_packed([2 x i64] {{%.*}}, [2 x i64] {{%.*}}, [2 x i64] {{%.*}})
145+
// darwin: call void @test_packed([2 x i64] {{%.*}}, [2 x i64] {{%.*}}, [2 x i64] {{%.*}})
146+
// win: call void @test_packed([2 x i64] {{%.*}}, [2 x i64] {{%.*}}, [2 x i64] {{%.*}})
147+
unsafe { test_packed(a, b, c) }
148+
}
149+
130150
pub unsafe fn main(
131151
a1: Align8,
132152
a2: Transparent8,
@@ -141,8 +161,8 @@ pub unsafe fn main(
141161
d2: TransparentPacked,
142162
d3: WrappedPacked,
143163
) {
144-
test_8(a1, a2, a3);
145-
test_16(b1, b2, b3);
146-
test_i128(c1, c2, c3);
147-
test_packed(d1, d2, d3);
164+
call_test_8(a1, a2, a3);
165+
call_test_16(b1, b2, b3);
166+
call_test_i128(c1, c2, c3);
167+
call_test_packed(d1, d2, d3);
148168
}

tests/codegen/align-byval-vector.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,18 @@ pub struct DoubleFoo {
3737
}
3838

3939
extern "C" {
40-
// x86-linux: declare void @f({{.*}}byval([32 x i8]) align 4{{.*}})
41-
// x86-darwin: declare void @f({{.*}}byval([32 x i8]) align 16{{.*}})
4240
fn f(foo: Foo);
4341

44-
// x86-linux: declare void @g({{.*}}byval([64 x i8]) align 4{{.*}})
45-
// x86-darwin: declare void @g({{.*}}byval([64 x i8]) align 16{{.*}})
4642
fn g(foo: DoubleFoo);
4743
}
4844

4945
pub fn main() {
46+
// x86-linux: call void @f({{.*}}byval([32 x i8]) align 4 {{.*}})
47+
// x86-darwin: call void @f({{.*}}byval([32 x i8]) align 16 {{.*}})
5048
unsafe { f(Foo { a: i32x4(1, 2, 3, 4), b: 0 }) }
5149

50+
// x86-linux: call void @g({{.*}}byval([64 x i8]) align 4 {{.*}})
51+
// x86-darwin: call void @g({{.*}}byval([64 x i8]) align 16 {{.*}})
5252
unsafe {
5353
g(DoubleFoo {
5454
one: Foo { a: i32x4(1, 2, 3, 4), b: 0 },

0 commit comments

Comments
 (0)