Emit function declarations for functions with #[linkage="extern_weak"]

Currently, when declaring an extern weak function in Rust, we use the
following syntax:
```rust
unsafe extern "C" {
   #[linkage = "extern_weak"]
   static FOO: Option<unsafe extern "C" fn() -> ()>;
}
```
This allows runtime-checking the extern weak symbol through the Option.

When emitting LLVM-IR, the Rust compiler currently emits this static
as an i8, and a pointer that is initialized with the value of the global
i8 and represents the nullabilty e.g.
```
@FOO = extern_weak global i8
@_rust_extern_with_linkage_FOO = internal global ptr @FOO
```

This approach does not work well with CFI, where we need to attach CFI
metadata to a concrete function declaration, which was pointed out in
https://github.com/rust-lang/rust/issues/115199.

This change switches to emitting a proper function declaration instead
of a global i8. This allows CFI to work for extern_weak functions.

We keep initializing the Rust internal symbol with the function
declaration, which preserves the correct behavior for runtime checking
the Option.

Co-authored-by: Jakob Koschel <jakobkoschel@google.com>
This commit is contained in:
Bastian Kersting 2025-03-11 11:00:42 +00:00
parent 705421b522
commit b30cf11b96
2 changed files with 48 additions and 4 deletions

View file

@ -5,6 +5,7 @@ use rustc_abi::{
};
use rustc_codegen_ssa::common;
use rustc_codegen_ssa::traits::*;
use rustc_hir::LangItem;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::DefId;
use rustc_middle::middle::codegen_fn_attrs::{CodegenFnAttrFlags, CodegenFnAttrs};
@ -12,9 +13,9 @@ use rustc_middle::mir::interpret::{
Allocation, ConstAllocation, ErrorHandled, InitChunk, Pointer, Scalar as InterpScalar,
read_target_uint,
};
use rustc_middle::mir::mono::MonoItem;
use rustc_middle::ty::Instance;
use rustc_middle::mir::mono::{Linkage, MonoItem};
use rustc_middle::ty::layout::{HasTypingEnv, LayoutOf};
use rustc_middle::ty::{self, Instance};
use rustc_middle::{bug, span_bug};
use tracing::{debug, instrument, trace};
@ -171,8 +172,27 @@ fn check_and_apply_linkage<'ll, 'tcx>(
if let Some(linkage) = attrs.import_linkage {
debug!("get_static: sym={} linkage={:?}", sym, linkage);
// Declare a symbol `foo` with the desired linkage.
let g1 = cx.declare_global(sym, cx.type_i8());
// Declare a symbol `foo`. If `foo` is an extern_weak symbol, we declare
// an extern_weak function, otherwise a global with the desired linkage.
let g1 = if matches!(attrs.import_linkage, Some(Linkage::ExternalWeak)) {
// An `extern_weak` function is represented as an `Option<unsafe extern ...>`,
// we extract the function signature and declare it as an extern_weak function
// instead of an extern_weak i8.
let instance = Instance::mono(cx.tcx, def_id);
if let ty::Adt(struct_def, args) = instance.ty(cx.tcx, cx.typing_env()).kind()
&& cx.tcx.is_lang_item(struct_def.did(), LangItem::Option)
&& let ty::FnPtr(sig, header) = args.type_at(0).kind()
{
let fn_sig = sig.with(*header);
let fn_abi = cx.fn_abi_of_fn_ptr(fn_sig, ty::List::empty());
cx.declare_fn(sym, &fn_abi, None)
} else {
cx.declare_global(sym, cx.type_i8())
}
} else {
cx.declare_global(sym, cx.type_i8())
};
llvm::set_linkage(g1, base::linkage_to_llvm(linkage));
// Declare an internal global `extern_with_linkage_foo` which

View file

@ -0,0 +1,24 @@
// Verifies that type metadata identifiers for for weakly-linked symbols are
// emitted correctly.
//
//@ needs-sanitizer-cfi
//@ compile-flags: -Clinker-plugin-lto -Copt-level=0 -Zsanitizer=cfi -Ctarget-feature=-crt-static
#![crate_type = "bin"]
#![feature(linkage)]
unsafe extern "C" {
#[linkage = "extern_weak"]
static FOO: Option<unsafe extern "C" fn(f64) -> ()>;
}
// CHECK: @_rust_extern_with_linkage_FOO = internal global ptr @FOO
fn main() {
unsafe {
if let Some(method) = FOO {
method(4.2);
// CHECK: call i1 @llvm.type.test(ptr {{%method|%0}}, metadata !"_ZTSFvdE")
}
}
}
// CHECK: declare !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} extern_weak void @FOO(double) unnamed_addr #{{[0-9]+}}