Skip to content

Commit da5ac26

Browse files
committed
WIP: declare imported functions as globals without a type, and apply more attributes at the call site
1 parent 6106b05 commit da5ac26

File tree

9 files changed

+184
-93
lines changed

9 files changed

+184
-93
lines changed

compiler/rustc_ast_passes/src/feature_gate.rs

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

compiler/rustc_codegen_llvm/src/attributes.rs

+59-43
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ fn create_alloc_family_attr(llcx: &llvm::Context) -> &llvm::Attribute {
328328

329329
/// Composite function which sets LLVM attributes for function depending on its AST (`#[attribute]`)
330330
/// attributes.
331-
pub fn from_fn_attrs<'ll, 'tcx>(
331+
pub fn llfn_attrs_from_fn<'ll, 'tcx>(
332332
cx: &CodegenCx<'ll, 'tcx>,
333333
llfn: &'ll Value,
334334
instance: ty::Instance<'tcx>,
@@ -410,48 +410,6 @@ pub fn from_fn_attrs<'ll, 'tcx>(
410410
// Need this for AArch64.
411411
to_add.push(llvm::CreateAttrStringValue(cx.llcx, "branch-target-enforcement", "false"));
412412
}
413-
if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::ALLOCATOR)
414-
|| codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::ALLOCATOR_ZEROED)
415-
{
416-
to_add.push(create_alloc_family_attr(cx.llcx));
417-
// apply to argument place instead of function
418-
let alloc_align = AttributeKind::AllocAlign.create_attr(cx.llcx);
419-
attributes::apply_to_llfn(llfn, AttributePlace::Argument(1), &[alloc_align]);
420-
to_add.push(llvm::CreateAllocSizeAttr(cx.llcx, 0));
421-
let mut flags = AllocKindFlags::Alloc | AllocKindFlags::Aligned;
422-
if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::ALLOCATOR) {
423-
flags |= AllocKindFlags::Uninitialized;
424-
} else {
425-
flags |= AllocKindFlags::Zeroed;
426-
}
427-
to_add.push(llvm::CreateAllocKindAttr(cx.llcx, flags));
428-
// apply to return place instead of function (unlike all other attributes applied in this function)
429-
let no_alias = AttributeKind::NoAlias.create_attr(cx.llcx);
430-
attributes::apply_to_llfn(llfn, AttributePlace::ReturnValue, &[no_alias]);
431-
}
432-
if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::REALLOCATOR) {
433-
to_add.push(create_alloc_family_attr(cx.llcx));
434-
to_add.push(llvm::CreateAllocKindAttr(
435-
cx.llcx,
436-
AllocKindFlags::Realloc | AllocKindFlags::Aligned,
437-
));
438-
// applies to argument place instead of function place
439-
let allocated_pointer = AttributeKind::AllocatedPointer.create_attr(cx.llcx);
440-
attributes::apply_to_llfn(llfn, AttributePlace::Argument(0), &[allocated_pointer]);
441-
// apply to argument place instead of function
442-
let alloc_align = AttributeKind::AllocAlign.create_attr(cx.llcx);
443-
attributes::apply_to_llfn(llfn, AttributePlace::Argument(2), &[alloc_align]);
444-
to_add.push(llvm::CreateAllocSizeAttr(cx.llcx, 3));
445-
let no_alias = AttributeKind::NoAlias.create_attr(cx.llcx);
446-
attributes::apply_to_llfn(llfn, AttributePlace::ReturnValue, &[no_alias]);
447-
}
448-
if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::DEALLOCATOR) {
449-
to_add.push(create_alloc_family_attr(cx.llcx));
450-
to_add.push(llvm::CreateAllocKindAttr(cx.llcx, AllocKindFlags::Free));
451-
// applies to argument place instead of function place
452-
let allocated_pointer = AttributeKind::AllocatedPointer.create_attr(cx.llcx);
453-
attributes::apply_to_llfn(llfn, AttributePlace::Argument(0), &[allocated_pointer]);
454-
}
455413
if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::CMSE_NONSECURE_ENTRY) {
456414
to_add.push(llvm::CreateAttrString(cx.llcx, "cmse_nonsecure_entry"));
457415
}
@@ -530,6 +488,64 @@ pub fn from_fn_attrs<'ll, 'tcx>(
530488
attributes::apply_to_llfn(llfn, Function, &to_add);
531489
}
532490

491+
pub fn callsite_attrs_from_fn<'ll, 'tcx>(
492+
cx: &CodegenCx<'ll, 'tcx>,
493+
callsite: &'ll Value,
494+
instance: ty::Instance<'tcx>,
495+
) {
496+
let codegen_fn_attrs = cx.tcx.codegen_fn_attrs(instance.def_id());
497+
498+
let mut to_add = SmallVec::<[_; 16]>::new();
499+
500+
if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::ALLOCATOR)
501+
|| codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::ALLOCATOR_ZEROED)
502+
{
503+
to_add.push(create_alloc_family_attr(cx.llcx));
504+
// apply to argument place instead of function
505+
let alloc_align = AttributeKind::AllocAlign.create_attr(cx.llcx);
506+
//attributes::apply_to_callsite(callsite, AttributePlace::Argument(1), &[alloc_align]);
507+
to_add.push(llvm::CreateAllocSizeAttr(cx.llcx, 0));
508+
let mut flags = AllocKindFlags::Alloc | AllocKindFlags::Aligned;
509+
if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::ALLOCATOR) {
510+
flags |= AllocKindFlags::Uninitialized;
511+
} else {
512+
flags |= AllocKindFlags::Zeroed;
513+
}
514+
to_add.push(llvm::CreateAllocKindAttr(cx.llcx, flags));
515+
// apply to return place instead of function (unlike all other attributes applied in this function)
516+
let no_alias = AttributeKind::NoAlias.create_attr(cx.llcx);
517+
//attributes::apply_to_callsite(callsite, AttributePlace::ReturnValue, &[no_alias]);
518+
}
519+
if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::REALLOCATOR) {
520+
to_add.push(create_alloc_family_attr(cx.llcx));
521+
to_add.push(llvm::CreateAllocKindAttr(
522+
cx.llcx,
523+
AllocKindFlags::Realloc | AllocKindFlags::Aligned,
524+
));
525+
// applies to argument place instead of function place
526+
let allocated_pointer = AttributeKind::AllocatedPointer.create_attr(cx.llcx);
527+
//attributes::apply_to_callsite(callsite, AttributePlace::Argument(0), &[allocated_pointer]);
528+
// apply to argument place instead of function
529+
let alloc_align = AttributeKind::AllocAlign.create_attr(cx.llcx);
530+
//attributes::apply_to_callsite(callsite, AttributePlace::Argument(2), &[alloc_align]);
531+
to_add.push(llvm::CreateAllocSizeAttr(cx.llcx, 3));
532+
let no_alias = AttributeKind::NoAlias.create_attr(cx.llcx);
533+
//attributes::apply_to_callsite(callsite, AttributePlace::ReturnValue, &[no_alias]);
534+
}
535+
if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::DEALLOCATOR) {
536+
to_add.push(create_alloc_family_attr(cx.llcx));
537+
to_add.push(llvm::CreateAllocKindAttr(cx.llcx, AllocKindFlags::Free));
538+
// applies to argument place instead of function place
539+
let allocated_pointer = AttributeKind::AllocatedPointer.create_attr(cx.llcx);
540+
//attributes::apply_to_callsite(callsite, AttributePlace::Argument(0), &[allocated_pointer]);
541+
}
542+
543+
if !to_add.is_empty() {
544+
eprintln!("applying attributes to call of {instance:?}");
545+
}
546+
attributes::apply_to_callsite(callsite, Function, &to_add);
547+
}
548+
533549
fn wasm_import_module(tcx: TyCtxt<'_>, id: DefId) -> Option<&String> {
534550
tcx.wasm_import_module_map(id.krate).get(&id)
535551
}

compiler/rustc_codegen_llvm/src/builder.rs

+9
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,9 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
262262
if let Some(fn_abi) = fn_abi {
263263
fn_abi.apply_attrs_callsite(self, invoke);
264264
}
265+
if let Some(instance) = instance {
266+
attributes::callsite_attrs_from_fn(self.cx, invoke, instance);
267+
}
265268
invoke
266269
}
267270

@@ -1287,6 +1290,9 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
12871290
if let Some(fn_abi) = fn_abi {
12881291
fn_abi.apply_attrs_callsite(self, call);
12891292
}
1293+
if let Some(instance) = instance {
1294+
attributes::callsite_attrs_from_fn(self.cx, call, instance);
1295+
}
12901296
call
12911297
}
12921298

@@ -1615,6 +1621,9 @@ impl<'a, 'll, 'tcx> Builder<'a, 'll, 'tcx> {
16151621
if let Some(fn_abi) = fn_abi {
16161622
fn_abi.apply_attrs_callsite(self, callbr);
16171623
}
1624+
if let Some(instance) = instance {
1625+
attributes::callsite_attrs_from_fn(self.cx, callbr, instance);
1626+
}
16181627
callbr
16191628
}
16201629

compiler/rustc_codegen_llvm/src/callee.rs

+42-30
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@
44
//! and methods are represented as just a fn ptr and not a full
55
//! closure.
66
7-
use crate::attributes;
87
use crate::common;
98
use crate::context::CodegenCx;
9+
use crate::declare::declare_as_global;
1010
use crate::llvm;
1111
use crate::value::Value;
1212

13+
use rustc_codegen_ssa::traits::BaseTypeMethods;
1314
use rustc_middle::ty::layout::{FnAbiOf, HasTyCtxt};
1415
use rustc_middle::ty::{self, Instance, TypeVisitableExt};
1516
use tracing::debug;
@@ -47,40 +48,51 @@ pub fn get_fn<'ll, 'tcx>(cx: &CodegenCx<'ll, 'tcx>, instance: Instance<'tcx>) ->
4748
llfn
4849
} else {
4950
let instance_def_id = instance.def_id();
50-
let llfn = if tcx.sess.target.arch == "x86"
51-
&& let Some(dllimport) = common::get_dllimport(tcx, instance_def_id, sym)
52-
{
53-
// Fix for https://github.com/rust-lang/rust/issues/104453
54-
// On x86 Windows, LLVM uses 'L' as the prefix for any private
55-
// global symbols, so when we create an undecorated function symbol
56-
// that begins with an 'L' LLVM misinterprets that as a private
57-
// global symbol that it created and so fails the compilation at a
58-
// later stage since such a symbol must have a definition.
59-
//
60-
// To avoid this, we set the Storage Class to "DllImport" so that
61-
// LLVM will prefix the name with `__imp_`. Ideally, we'd like the
62-
// existing logic below to set the Storage Class, but it has an
63-
// exemption for MinGW for backwards compatibility.
64-
let llfn = cx.declare_fn(
65-
&common::i686_decorated_name(
66-
dllimport,
67-
common::is_mingw_gnu_toolchain(&tcx.sess.target),
68-
true,
69-
),
70-
fn_abi,
71-
Some(instance),
72-
);
73-
unsafe {
74-
llvm::LLVMSetDLLStorageClass(llfn, llvm::DLLStorageClass::DllImport);
51+
let llfn = if declare_as_global(tcx, instance, sym) {
52+
// If this is a foreign function, make the declaration not promise *anything*.
53+
// The same foreign function could be declared multiple times in the same compilation
54+
// unit, and the last declaration will overwrite the previous ones, leading
55+
// to "action at a distance" -- including UB, if the last declaration adds attributes
56+
// that not all call sites are prepared for.
57+
58+
// Declare this as an empty array of `i8`.
59+
let ty = cx.type_array(cx.type_i8(), 0);
60+
61+
if tcx.sess.target.arch == "x86"
62+
&& let Some(dllimport) = common::get_dllimport(tcx, instance_def_id, sym)
63+
{
64+
// Fix for https://github.com/rust-lang/rust/issues/104453
65+
// On x86 Windows, LLVM uses 'L' as the prefix for any private
66+
// global symbols, so when we create an undecorated function symbol
67+
// that begins with an 'L' LLVM misinterprets that as a private
68+
// global symbol that it created and so fails the compilation at a
69+
// later stage since such a symbol must have a definition.
70+
//
71+
// To avoid this, we set the Storage Class to "DllImport" so that
72+
// LLVM will prefix the name with `__imp_`. Ideally, we'd like the
73+
// existing logic below to set the Storage Class, but it has an
74+
// exemption for MinGW for backwards compatibility.
75+
let llsym = cx.declare_global(
76+
&common::i686_decorated_name(
77+
dllimport,
78+
common::is_mingw_gnu_toolchain(&tcx.sess.target),
79+
true,
80+
),
81+
ty,
82+
);
83+
unsafe {
84+
llvm::LLVMSetDLLStorageClass(llsym, llvm::DLLStorageClass::DllImport);
85+
}
86+
llsym
87+
} else {
88+
cx.declare_global(sym, ty)
7589
}
76-
llfn
7790
} else {
78-
cx.declare_fn(sym, fn_abi, Some(instance))
91+
let llfn = cx.declare_fn(sym, fn_abi, Some(instance));
92+
llfn
7993
};
8094
debug!("get_fn: not casting pointer!");
8195

82-
attributes::from_fn_attrs(cx, llfn, instance);
83-
8496
// Apply an appropriate linkage/visibility value to our item that we
8597
// just declared.
8698
//

compiler/rustc_codegen_llvm/src/declare.rs

+25-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use crate::value::Value;
2121
use itertools::Itertools;
2222
use rustc_codegen_ssa::traits::TypeMembershipMethods;
2323
use rustc_data_structures::fx::FxIndexSet;
24-
use rustc_middle::ty::{Instance, Ty};
24+
use rustc_middle::ty::{Instance, Ty, TyCtxt};
2525
use rustc_sanitizers::{cfi, kcfi};
2626
use smallvec::SmallVec;
2727
use tracing::debug;
@@ -60,6 +60,18 @@ fn declare_raw_fn<'ll>(
6060
llfn
6161
}
6262

63+
/// Some functions are not declared as functions but as global names without any meaningful type.
64+
/// This is to deal with the fact that Rust can import the same external function with different
65+
/// signatures into the same compilation unit, but LLVM cannot.
66+
pub fn declare_as_global<'tcx>(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>, name: &str) -> bool {
67+
tcx.is_foreign_item(instance.def_id()) && {
68+
// If this is calling an LLVM intrinsic, we can't use the `global` trick.
69+
// These are also unstable to call so nobody but us can screw up their signature.
70+
let is_llvm_intrinsic = name.starts_with("llvm.");
71+
!is_llvm_intrinsic
72+
}
73+
}
74+
6375
impl<'ll, 'tcx> CodegenCx<'ll, 'tcx> {
6476
/// Declare a global value.
6577
///
@@ -127,6 +139,15 @@ impl<'ll, 'tcx> CodegenCx<'ll, 'tcx> {
127139
) -> &'ll Value {
128140
debug!("declare_rust_fn(name={:?}, fn_abi={:?})", name, fn_abi);
129141

142+
// If this is a foreign function, it should have been declared in a different way.
143+
// We can't declare these as "functions" because they can be imported multiple times
144+
// with different signatures, and any signature we tell LLVM might be wrong for
145+
// call sites that use another signature.
146+
assert!(
147+
!instance.is_some_and(|i| declare_as_global(self.tcx, i, name)),
148+
"{name} is a foreign function, it should be declared as a global",
149+
);
150+
130151
// Function addresses in Rust are never significant, allowing functions to
131152
// be merged.
132153
let llfn = declare_raw_fn(
@@ -138,6 +159,9 @@ impl<'ll, 'tcx> CodegenCx<'ll, 'tcx> {
138159
fn_abi.llvm_type(self),
139160
);
140161
fn_abi.apply_attrs_llfn(self, llfn);
162+
if let Some(instance) = instance {
163+
attributes::llfn_attrs_from_fn(self, llfn, instance);
164+
}
141165

142166
if self.tcx.sess.is_sanitizer_cfi_enabled() {
143167
if let Some(instance) = instance {

compiler/rustc_codegen_llvm/src/mono_item.rs

+15-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use crate::attributes;
21
use crate::base;
32
use crate::context::CodegenCx;
43
use crate::errors::SymbolAlreadyDefined;
@@ -60,6 +59,21 @@ impl<'tcx> PreDefineMethods<'tcx> for CodegenCx<'_, 'tcx> {
6059
) {
6160
assert!(!instance.args.has_infer());
6261

62+
// If this is a foreign function, make the declaration not promise *anything*.
63+
// The same foreign function could be declared multiple times in the same compilation
64+
// unit, and the last declaration will overwrite the previous ones, leading
65+
// to "action at a distance" -- including UB, if the last declaration adds attributes
66+
// that not all call sites are prepared for.
67+
let is_foreign_fn = self.tcx.is_foreign_item(instance.def_id());
68+
if is_foreign_fn {
69+
eprintln!("special treatment for extern fn {}", symbol_name);
70+
// Declare this as an empty array of `i8`.
71+
let ty = self.type_array(self.type_i8(), 0);
72+
let lldecl = self.declare_global(symbol_name, ty);
73+
self.instances.borrow_mut().insert(instance, lldecl);
74+
return;
75+
}
76+
6377
let fn_abi = self.fn_abi_of_instance(instance, ty::List::empty());
6478
let lldecl = self.declare_fn(symbol_name, fn_abi, Some(instance));
6579
unsafe { llvm::LLVMRustSetLinkage(lldecl, base::linkage_to_llvm(linkage)) };
@@ -88,8 +102,6 @@ impl<'tcx> PreDefineMethods<'tcx> for CodegenCx<'_, 'tcx> {
88102

89103
debug!("predefine_fn: instance = {:?}", instance);
90104

91-
attributes::from_fn_attrs(self, lldecl, instance);
92-
93105
unsafe {
94106
if self.should_assume_dso_local(lldecl, false) {
95107
llvm::LLVMRustSetDSOLocal(lldecl, true);

tests/codegen/declare-no-attrs.rs

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//@ compile-flags: -C opt-level=1
2+
3+
#![no_builtins]
4+
#![crate_type = "lib"]
5+
6+
// Make sure that there are no attributes or even types attached to this delcaration.
7+
// If there were, they could be used even for other call sites using a different
8+
// item importing the same function!
9+
// See <https://github.com/rust-lang/rust/issues/46188> for details.
10+
#[allow(improper_ctypes)]
11+
extern "C" {
12+
// CHECK: @malloc = external global [0 x i8]
13+
pub fn malloc(x: u64) -> &'static mut ();
14+
}
15+
16+
// `malloc` needs to be referenced or else it will disappear entirely.
17+
#[no_mangle]
18+
pub fn use_it() -> usize {
19+
let m: unsafe extern "C" fn(_) -> _ = malloc;
20+
malloc as *const () as usize
21+
}

tests/codegen/pic-relocation-model.rs

+5-8
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,15 @@
22

33
#![crate_type = "rlib"]
44

5+
// CHECK: @foreign_fn = external global
6+
extern "C" {
7+
fn foreign_fn() -> u8;
8+
}
9+
510
// CHECK: define i8 @call_foreign_fn()
611
#[no_mangle]
712
pub fn call_foreign_fn() -> u8 {
813
unsafe { foreign_fn() }
914
}
1015

11-
// (Allow but do not require `zeroext` here, because it is not worth effort to
12-
// spell out which targets have it and which ones do not; see rust#97800.)
13-
14-
// CHECK: declare{{( zeroext)?}} i8 @foreign_fn()
15-
extern "C" {
16-
fn foreign_fn() -> u8;
17-
}
18-
1916
// CHECK: !{i32 {{[78]}}, !"PIC Level", i32 2}

0 commit comments

Comments
 (0)