From df828069ea98c621f4ebf92bfc573ebc0727817a Mon Sep 17 00:00:00 2001 From: Antoni Boucher Date: Sun, 23 Aug 2026 13:58:31 -0400 Subject: [PATCH 1/5] Fix and support more linkages --- src/base.rs | 59 ++++++++++++----- src/mono_item.rs | 2 +- tests/c/import_linkage.c | 16 +++++ tests/c/weak_function_linkage.c | 49 ++++++++++++++ tests/run/import_linkage.rs | 77 ++++++++++++++++++++++ tests/run/weak_function_linkage.rs | 100 +++++++++++++++++++++++++++++ 6 files changed, 285 insertions(+), 18 deletions(-) create mode 100644 tests/c/import_linkage.c create mode 100644 tests/c/weak_function_linkage.c create mode 100644 tests/run/import_linkage.rs create mode 100644 tests/run/weak_function_linkage.rs diff --git a/src/base.rs b/src/base.rs index 9c06c7090c8..a7ee26b4002 100644 --- a/src/base.rs +++ b/src/base.rs @@ -39,32 +39,57 @@ pub fn symbol_visibility_to_gcc(visibility: SymbolVisibility) -> gccjit::Visibil } } +/// The kind of a global declared with an explicit `#[linkage]`. +/// +/// This is only reached for imports (`extern { #[linkage = "..."] static X: *const T; }`), where +/// every flavour but `internal` is an undefined reference. `extern_weak` additionally gets +/// `VarAttribute::Weak` from the caller, so that an unresolved symbol reads as null. pub fn global_linkage_to_gcc(linkage: Linkage) -> GlobalKind { match linkage { - Linkage::External => GlobalKind::Imported, - Linkage::AvailableExternally => GlobalKind::Imported, - Linkage::LinkOnceAny => unimplemented!(), - Linkage::LinkOnceODR => unimplemented!(), - Linkage::WeakAny => unimplemented!(), - Linkage::WeakODR => unimplemented!(), Linkage::Internal => GlobalKind::Internal, - Linkage::ExternalWeak => GlobalKind::Imported, // FIXME(antoyo): should be weak linkage. - Linkage::Common => unimplemented!(), + Linkage::External + | Linkage::AvailableExternally + | Linkage::LinkOnceAny + | Linkage::LinkOnceODR + | Linkage::WeakAny + | Linkage::WeakODR + | Linkage::ExternalWeak + | Linkage::Common => GlobalKind::Imported, } } +/// The type of a function *definition* with an explicit `#[linkage]`. +/// +/// The flavours that another object file is allowed to override also need +/// `linkage_needs_weak_attribute` from the caller: `FunctionType` alone cannot express weakness. pub fn linkage_to_gcc(linkage: Linkage) -> FunctionType { match linkage { Linkage::External => FunctionType::Exported, - // FIXME(antoyo): set the attribute externally_visible. - Linkage::AvailableExternally => FunctionType::Extern, - Linkage::LinkOnceAny => unimplemented!(), - Linkage::LinkOnceODR => unimplemented!(), - Linkage::WeakAny => FunctionType::Exported, // FIXME(antoyo): should be similar to linkonce. - Linkage::WeakODR => unimplemented!(), - Linkage::Internal => FunctionType::Internal, - Linkage::ExternalWeak => unimplemented!(), - Linkage::Common => unimplemented!(), + // libgccjit cannot emit a definition that the linker discards in favour of the one in + // another object file, so emit a private copy of it instead. + Linkage::AvailableExternally | Linkage::Internal => FunctionType::Internal, + // libgccjit exposes no comdat, so `weak` stands in for every overridable flavour. + Linkage::LinkOnceAny + | Linkage::LinkOnceODR + | Linkage::WeakAny + | Linkage::WeakODR + | Linkage::ExternalWeak + | Linkage::Common => FunctionType::Exported, + } +} + +/// Whether a definition with this linkage must carry the `weak` attribute, so that a strong +/// definition in another object file wins over it instead of clashing with it. +#[cfg(feature = "master")] +pub fn linkage_needs_weak_attribute(linkage: Linkage) -> bool { + match linkage { + Linkage::LinkOnceAny + | Linkage::LinkOnceODR + | Linkage::WeakAny + | Linkage::WeakODR + | Linkage::ExternalWeak + | Linkage::Common => true, + Linkage::External | Linkage::AvailableExternally | Linkage::Internal => false, } } diff --git a/src/mono_item.rs b/src/mono_item.rs index 521c86e6274..371f3fd4996 100644 --- a/src/mono_item.rs +++ b/src/mono_item.rs @@ -172,7 +172,7 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { attributes::from_fn_attrs(self, fn_decl, instance, Some(fn_abi)); #[cfg(feature = "master")] - if linkage == Linkage::WeakAny { + if base::linkage_needs_weak_attribute(linkage) { fn_decl.add_attribute(FnAttribute::Weak); } diff --git a/tests/c/import_linkage.c b/tests/c/import_linkage.c new file mode 100644 index 00000000000..d725b86c6c1 --- /dev/null +++ b/tests/c/import_linkage.c @@ -0,0 +1,16 @@ +/* The symbols that `tests/run/import_linkage.rs` imports with an explicit `#[linkage]`. + * + * Such an import is a pointer whose value is the address of the symbol, so what the Rust side + * reads back is `&value_*`, not the pointer stored in it. The distinct values make a mix-up + * visible. */ + +#include + +int32_t external_value = 1; +int32_t available_externally_value = 2; +int32_t linkonce_value = 3; +int32_t linkonce_odr_value = 4; +int32_t weak_value = 5; +int32_t weak_odr_value = 6; +int32_t common_value = 7; +int32_t extern_weak_value = 8; diff --git a/tests/c/weak_function_linkage.c b/tests/c/weak_function_linkage.c new file mode 100644 index 00000000000..72ea483fd86 --- /dev/null +++ b/tests/c/weak_function_linkage.c @@ -0,0 +1,49 @@ +/* Strong definitions of the functions that `tests/run/weak_function_linkage.rs` also defines, but + * weakly. The linker has to keep these and drop the Rust ones. + * + * A backend that emits the Rust definitions as ordinary global symbols does not merely pick the + * wrong one: the link fails outright with a duplicate definition. */ + +#include + +int32_t weak_function(void) +{ + return 1; +} + +int32_t weak_odr_function(void) +{ + return 2; +} + +int32_t linkonce_function(void) +{ + return 3; +} + +int32_t linkonce_odr_function(void) +{ + return 4; +} + +int32_t common_function(void) +{ + return 5; +} + +/* Called from Rust, so that the calls also go through a caller that GCC compiled: a cg_gcc caller + * could inline the weak body it can see instead of calling the symbol. */ +int32_t c_call_all(void) +{ + if (weak_function() != 1) + return 11; + if (weak_odr_function() != 2) + return 12; + if (linkonce_function() != 3) + return 13; + if (linkonce_odr_function() != 4) + return 14; + if (common_function() != 5) + return 15; + return 0; +} diff --git a/tests/run/import_linkage.rs b/tests/run/import_linkage.rs new file mode 100644 index 00000000000..0b044529b9b --- /dev/null +++ b/tests/run/import_linkage.rs @@ -0,0 +1,77 @@ +// Compiler: +// +// Run-time: +// status: 0 + +// Checks the `#[linkage]` flavours an `extern` static can be imported with, against the symbols +// `tests/c/import_linkage.c` defines. `linkonce`, `linkonce_odr`, `weak`, `weak_odr` and `common` +// used to reach an `unimplemented!()` in `global_linkage_to_gcc`. +// +// The value of such an import is the address of the symbol rather than its contents, which is why +// the types are pointers: an `extern_weak` import of a symbol nobody defines reads as null instead +// of failing the link. + +#![feature(linkage, no_core)] +#![no_std] +#![no_core] +#![no_main] + +extern crate mini_core; +use mini_core::*; + +extern "C" { + #[linkage = "external"] + static external_value: *const i32; + #[linkage = "available_externally"] + static available_externally_value: *const i32; + #[linkage = "linkonce"] + static linkonce_value: *const i32; + #[linkage = "linkonce_odr"] + static linkonce_odr_value: *const i32; + #[linkage = "weak"] + static weak_value: *const i32; + #[linkage = "weak_odr"] + static weak_odr_value: *const i32; + #[linkage = "common"] + static common_value: *const i32; + #[linkage = "extern_weak"] + static extern_weak_value: *const i32; + + // Nothing defines this one, so it stays null instead of breaking the link. + #[linkage = "extern_weak"] + static undefined_value: *const i32; +} + +#[no_mangle] +extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 { + unsafe { + if *external_value != 1 { + return 1; + } + if *available_externally_value != 2 { + return 2; + } + if *linkonce_value != 3 { + return 3; + } + if *linkonce_odr_value != 4 { + return 4; + } + if *weak_value != 5 { + return 5; + } + if *weak_odr_value != 6 { + return 6; + } + if *common_value != 7 { + return 7; + } + if *extern_weak_value != 8 { + return 8; + } + if undefined_value as usize != 0 { + return 9; + } + } + 0 +} diff --git a/tests/run/weak_function_linkage.rs b/tests/run/weak_function_linkage.rs new file mode 100644 index 00000000000..68052136369 --- /dev/null +++ b/tests/run/weak_function_linkage.rs @@ -0,0 +1,100 @@ +// Compiler: +// +// Run-time: +// status: 0 + +// Checks that the `#[linkage]` flavours another object file is allowed to override are emitted as +// weak symbols, by linking against `tests/c/weak_function_linkage.c`, which defines the same +// symbols strongly. +// +// `weak` used to be emitted as an ordinary global symbol, which the C definitions clash with, and +// `weak_odr`, `linkonce`, `linkonce_odr` and `common` reached an `unimplemented!()` in +// `linkage_to_gcc`. `available_externally` reached libgccjit, which rejects a body on an imported +// function. + +#![feature(linkage, no_core)] +#![no_std] +#![no_core] +#![no_main] + +extern crate mini_core; +use mini_core::*; + +#[linkage = "weak"] +#[no_mangle] +extern "C" fn weak_function() -> i32 { + 0 +} + +#[linkage = "weak_odr"] +#[no_mangle] +extern "C" fn weak_odr_function() -> i32 { + 0 +} + +#[linkage = "linkonce"] +#[no_mangle] +extern "C" fn linkonce_function() -> i32 { + 0 +} + +#[linkage = "linkonce_odr"] +#[no_mangle] +extern "C" fn linkonce_odr_function() -> i32 { + 0 +} + +#[linkage = "common"] +#[no_mangle] +extern "C" fn common_function() -> i32 { + 0 +} + +// Not overridden by the C side: the definition here is the one that runs. +#[linkage = "weak"] +#[no_mangle] +extern "C" fn only_weak_function() -> i32 { + 6 +} + +// Emitted as a private copy of a definition that lives elsewhere, so it must still be callable. +#[linkage = "available_externally"] +#[no_mangle] +extern "C" fn available_externally_function() -> i32 { + 7 +} + +extern "C" { + fn c_call_all() -> i32; +} + +#[no_mangle] +extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 { + let result = unsafe { c_call_all() }; + if result != 0 { + return result; + } + + if weak_function() != 1 { + return 1; + } + if weak_odr_function() != 2 { + return 2; + } + if linkonce_function() != 3 { + return 3; + } + if linkonce_odr_function() != 4 { + return 4; + } + if common_function() != 5 { + return 5; + } + if only_weak_function() != 6 { + return 6; + } + if available_externally_function() != 7 { + return 7; + } + 0 +} From 642aaa8c9de2adb5099d90ce268b483e7bccbc55 Mon Sep 17 00:00:00 2001 From: Antoni Boucher Date: Sun, 23 Aug 2026 14:42:00 -0400 Subject: [PATCH 2/5] Implement linkage in predefine_static and fix internal linkage on extern statics --- src/base.rs | 20 +++++----- src/consts.rs | 17 +++++--- src/declare.rs | 6 ++- src/mono_item.rs | 18 +++++++-- tests/c/import_linkage.c | 1 + tests/c/static_linkage.c | 33 ++++++++++++++++ tests/run/import_linkage.rs | 9 ++++- tests/run/static_linkage.rs | 77 +++++++++++++++++++++++++++++++++++++ 8 files changed, 159 insertions(+), 22 deletions(-) create mode 100644 tests/c/static_linkage.c create mode 100644 tests/run/static_linkage.rs diff --git a/src/base.rs b/src/base.rs index a7ee26b4002..46f864bed98 100644 --- a/src/base.rs +++ b/src/base.rs @@ -39,22 +39,24 @@ pub fn symbol_visibility_to_gcc(visibility: SymbolVisibility) -> gccjit::Visibil } } -/// The kind of a global declared with an explicit `#[linkage]`. +/// The kind of a global *definition* with an explicit `#[linkage]`. /// -/// This is only reached for imports (`extern { #[linkage = "..."] static X: *const T; }`), where -/// every flavour but `internal` is an undefined reference. `extern_weak` additionally gets -/// `VarAttribute::Weak` from the caller, so that an unresolved symbol reads as null. +/// The flavours that another object file is allowed to override also need +/// `linkage_needs_weak_attribute` from the caller: `GlobalKind` alone cannot express weakness. pub fn global_linkage_to_gcc(linkage: Linkage) -> GlobalKind { match linkage { - Linkage::Internal => GlobalKind::Internal, - Linkage::External - | Linkage::AvailableExternally - | Linkage::LinkOnceAny + Linkage::External => GlobalKind::Exported, + // libgccjit cannot emit a definition that the linker discards in favour of the one in + // another object file, so emit a private copy of it instead. + Linkage::AvailableExternally | Linkage::Internal => GlobalKind::Internal, + // libgccjit exposes neither comdat nor common storage, so `weak` stands in for every + // overridable flavour. + Linkage::LinkOnceAny | Linkage::LinkOnceODR | Linkage::WeakAny | Linkage::WeakODR | Linkage::ExternalWeak - | Linkage::Common => GlobalKind::Imported, + | Linkage::Common => GlobalKind::Exported, } } diff --git a/src/consts.rs b/src/consts.rs index b1e06f88a23..061c09abcf1 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -21,7 +21,6 @@ use rustc_middle::ty::{self, Instance}; use rustc_middle::{bug, span_bug}; use rustc_span::def_id::DefId; -use crate::base; use crate::common::bytes_type_in_context; use crate::context::CodegenCx; use crate::type_::struct_attributes; @@ -469,10 +468,10 @@ fn check_and_apply_linkage<'gcc, 'tcx>( ) -> LValue<'gcc> { let is_tls = attrs.flags.contains(CodegenFnAttrFlags::THREAD_LOCAL); if let Some(linkage) = attrs.import_linkage { - // Declare a symbol `foo` with the desired linkage. - let global1 = - cx.declare_global_with_linkage(sym, cx.type_i8(), base::global_linkage_to_gcc(linkage)); + // Whatever the flavour, an import is an undefined reference to a symbol defined elsewhere. + let global1 = cx.declare_global_with_linkage(sym, cx.type_i8(), GlobalKind::Imported); + // Only `extern_weak` lets the symbol stay unresolved, in which case it reads as null. if linkage == Linkage::ExternalWeak { #[cfg(feature = "master")] global1.add_attribute(VarAttribute::Weak); @@ -486,8 +485,14 @@ fn check_and_apply_linkage<'gcc, 'tcx>( // zero. let real_name = format!("_rust_extern_with_linkage_{:016x}_{sym}", cx.tcx.stable_crate_id(LOCAL_CRATE)); - let global2 = cx.define_global(&real_name, gcc_type, is_tls, attrs.link_section); - // FIXME(antoyo): set linkage. + let global2 = cx.define_global( + &real_name, + gcc_type, + GlobalKind::Exported, + is_tls, + attrs.link_section, + ); + // FIXME(antoyo): set linkage: cg_llvm makes this helper global internal. let value = cx.const_ptrcast(global1.get_address(None), gcc_type); global2.global_set_initializer_rvalue(value); global2 diff --git a/src/declare.rs b/src/declare.rs index 9bf57fbf75b..32bb7c3aa34 100644 --- a/src/declare.rs +++ b/src/declare.rs @@ -14,6 +14,7 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { &self, name: &str, ty: Type<'gcc>, + global_kind: GlobalKind, is_tls: bool, link_section: Option, ) -> LValue<'gcc> { @@ -31,7 +32,7 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { } global } else { - self.declare_global(name, ty, GlobalKind::Exported, is_tls, link_section) + self.declare_global(name, ty, global_kind, is_tls, link_section) } } @@ -141,10 +142,11 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { &self, name: &str, ty: Type<'gcc>, + global_kind: GlobalKind, is_tls: bool, link_section: Option, ) -> LValue<'gcc> { - self.get_or_insert_global(name, ty, is_tls, link_section) + self.get_or_insert_global(name, ty, global_kind, is_tls, link_section) } pub fn get_declared_value(&self, name: &str) -> Option> { diff --git a/src/mono_item.rs b/src/mono_item.rs index 371f3fd4996..47889e1847e 100644 --- a/src/mono_item.rs +++ b/src/mono_item.rs @@ -21,7 +21,7 @@ impl<'gcc, 'tcx> PreDefineCodegenMethods<'tcx> for CodegenCx<'gcc, 'tcx> { fn predefine_static( &mut self, def_id: DefId, - _linkage: Linkage, + linkage: Linkage, visibility: Visibility, global_name: &str, ) { @@ -47,10 +47,20 @@ impl<'gcc, 'tcx> PreDefineCodegenMethods<'tcx> for CodegenCx<'gcc, 'tcx> { }; let is_tls = attrs.flags.contains(CodegenFnAttrFlags::THREAD_LOCAL); - let global = self.define_global(global_name, gcc_type, is_tls, attrs.link_section); + let global_kind = base::global_linkage_to_gcc(linkage); + let global = + self.define_global(global_name, gcc_type, global_kind, is_tls, attrs.link_section); #[cfg(feature = "master")] - global.add_attribute(VarAttribute::Visibility(base::visibility_to_gcc(visibility))); - // FIXME(antoyo): set linkage. + { + // GCC warns that it ignores `visibility` on an internal global, and cg_gcc turns + // libgccjit warnings into errors. + if !matches!(global_kind, GlobalKind::Internal) { + global.add_attribute(VarAttribute::Visibility(base::visibility_to_gcc(visibility))); + } + if base::linkage_needs_weak_attribute(linkage) { + global.add_attribute(VarAttribute::Weak); + } + } #[cfg(feature = "master")] self.add_static_aliases(gcc_type, global_name, attrs, &attrs.foreign_item_symbol_aliases); diff --git a/tests/c/import_linkage.c b/tests/c/import_linkage.c index d725b86c6c1..f2beb9603d0 100644 --- a/tests/c/import_linkage.c +++ b/tests/c/import_linkage.c @@ -14,3 +14,4 @@ int32_t weak_value = 5; int32_t weak_odr_value = 6; int32_t common_value = 7; int32_t extern_weak_value = 8; +int32_t internal_value = 9; diff --git a/tests/c/static_linkage.c b/tests/c/static_linkage.c new file mode 100644 index 00000000000..1a9b4ca5bd7 --- /dev/null +++ b/tests/c/static_linkage.c @@ -0,0 +1,33 @@ +/* Strong definitions of the statics that `tests/run/static_linkage.rs` also defines, but weakly. + * The linker has to keep these and drop the Rust ones; a backend that emits the Rust definitions + * as ordinary global symbols fails the link with a duplicate definition instead. + * + * `internal_static` is the opposite case: the Rust side keeps its own, and the two definitions + * coexist because the Rust one is local. */ + +#include + +int32_t weak_static = 1; +int32_t weak_odr_static = 2; +int32_t linkonce_static = 3; +int32_t linkonce_odr_static = 4; +int32_t common_static = 5; +int32_t internal_static = 200; + +/* Called from Rust, so that the reads also happen in a translation unit GCC compiled. */ +int32_t c_read_all(void) +{ + if (weak_static != 1) + return 11; + if (weak_odr_static != 2) + return 12; + if (linkonce_static != 3) + return 13; + if (linkonce_odr_static != 4) + return 14; + if (common_static != 5) + return 15; + if (internal_static != 200) + return 16; + return 0; +} diff --git a/tests/run/import_linkage.rs b/tests/run/import_linkage.rs index 0b044529b9b..c721309020e 100644 --- a/tests/run/import_linkage.rs +++ b/tests/run/import_linkage.rs @@ -36,6 +36,10 @@ extern "C" { static common_value: *const i32; #[linkage = "extern_weak"] static extern_weak_value: *const i32; + // An import is an undefined reference whatever the flavour says; this used to declare a + // private zeroed object of its own instead of reaching the definition in the C file. + #[linkage = "internal"] + static internal_value: *const i32; // Nothing defines this one, so it stays null instead of breaking the link. #[linkage = "extern_weak"] @@ -69,9 +73,12 @@ extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 { if *extern_weak_value != 8 { return 8; } - if undefined_value as usize != 0 { + if *internal_value != 9 { return 9; } + if undefined_value as usize != 0 { + return 10; + } } 0 } diff --git a/tests/run/static_linkage.rs b/tests/run/static_linkage.rs new file mode 100644 index 00000000000..adc43ab9fe3 --- /dev/null +++ b/tests/run/static_linkage.rs @@ -0,0 +1,77 @@ +// Compiler: +// +// Run-time: +// status: 0 + +// Checks that `#[linkage]` on a static that this crate defines reaches the symbol, against +// `tests/c/static_linkage.c`, which defines the overridable ones strongly. +// +// `predefine_static` used to ignore its `linkage` argument outright, so every static came out as +// an ordinary global symbol: the overridable ones clashed with the C definitions at link time, and +// `internal` exported a symbol it should have kept private. + +#![feature(linkage, no_core)] +#![no_std] +#![no_core] +#![no_main] + +extern crate mini_core; +use mini_core::*; + +#[linkage = "weak"] +#[no_mangle] +pub static weak_static: i32 = 0; + +#[linkage = "weak_odr"] +#[no_mangle] +pub static weak_odr_static: i32 = 0; + +#[linkage = "linkonce"] +#[no_mangle] +pub static linkonce_static: i32 = 0; + +#[linkage = "linkonce_odr"] +#[no_mangle] +pub static linkonce_odr_static: i32 = 0; + +#[linkage = "common"] +#[no_mangle] +pub static common_static: i32 = 0; + +// Private to this crate, so the C definition of the same name is a different object. +#[linkage = "internal"] +#[no_mangle] +pub static internal_static: i32 = 100; + +// Not overridden by the C side: the definition here is the one that survives. +#[linkage = "weak"] +#[no_mangle] +pub static only_weak_static: i32 = 6; + +// Emitted as a private copy of a definition that lives elsewhere, so it must still be readable. +#[linkage = "available_externally"] +#[no_mangle] +pub static available_externally_static: i32 = 7; + +extern "C" { + fn c_read_all() -> i32; +} + +#[no_mangle] +extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 { + let result = unsafe { c_read_all() }; + if result != 0 { + return result; + } + + if internal_static != 100 { + return 1; + } + if only_weak_static != 6 { + return 2; + } + if available_externally_static != 7 { + return 3; + } + 0 +} From 9ad916e901d1b9e6e2c6d21dafcd8cd535e254ff Mon Sep 17 00:00:00 2001 From: Antoni Boucher Date: Sun, 23 Aug 2026 16:56:27 -0400 Subject: [PATCH 3/5] Use internal linkage for check_and_apply_linkage --- src/consts.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/consts.rs b/src/consts.rs index 061c09abcf1..956b79b0cac 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -488,11 +488,10 @@ fn check_and_apply_linkage<'gcc, 'tcx>( let global2 = cx.define_global( &real_name, gcc_type, - GlobalKind::Exported, + GlobalKind::Internal, is_tls, attrs.link_section, ); - // FIXME(antoyo): set linkage: cg_llvm makes this helper global internal. let value = cx.const_ptrcast(global1.get_address(None), gcc_type); global2.global_set_initializer_rvalue(value); global2 From b0feb7ff8193d017e1b05007a1de343c05e586c1 Mon Sep 17 00:00:00 2001 From: Antoni Boucher Date: Mon, 24 Aug 2026 18:10:25 -0400 Subject: [PATCH 4/5] Fix ICE that happened on a weak function marked inline --- src/attributes.rs | 13 +++++++++++++ src/mono_item.rs | 11 ++++++++++- tests/run/weak_function_linkage.rs | 13 +++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/attributes.rs b/src/attributes.rs index 95d12480efa..e4d44d790d3 100644 --- a/src/attributes.rs +++ b/src/attributes.rs @@ -15,6 +15,8 @@ use rustc_target::callconv::FnAbi; #[cfg(feature = "master")] use rustc_target::spec::Arch; +#[cfg(feature = "master")] +use crate::base; use crate::context::CodegenCx; use crate::gcc_util::to_gcc_features; @@ -116,6 +118,17 @@ pub fn from_fn_attrs<'gcc, 'tcx>( } else { codegen_fn_attrs.inline }; + // GCC warns that `inline` and `weak` conflict, and cg_gcc turns libgccjit warnings into + // errors. The linkage is what has to survive: rustc lints `#[inline]` as ignored on a + // function with an explicit `#[linkage]` anyway. `inline(never)` does not conflict. + let inline = match inline { + InlineAttr::Always | InlineAttr::Hint | InlineAttr::Force { .. } + if codegen_fn_attrs.linkage.is_some_and(base::linkage_needs_weak_attribute) => + { + InlineAttr::None + } + inline => inline, + }; if let Some(attr) = inline_attr(cx, inline, instance) { if let FnAttribute::AlwaysInline = attr { func.add_attribute(FnAttribute::Inline); diff --git a/src/mono_item.rs b/src/mono_item.rs index 47889e1847e..cb133d9c233 100644 --- a/src/mono_item.rs +++ b/src/mono_item.rs @@ -55,7 +55,16 @@ impl<'gcc, 'tcx> PreDefineCodegenMethods<'tcx> for CodegenCx<'gcc, 'tcx> { // GCC warns that it ignores `visibility` on an internal global, and cg_gcc turns // libgccjit warnings into errors. if !matches!(global_kind, GlobalKind::Internal) { - global.add_attribute(VarAttribute::Visibility(base::visibility_to_gcc(visibility))); + // If we're compiling the compiler-builtins crate, e.g., the equivalent of + // compiler-rt, then we want to implicitly compile everything with hidden + // visibility as we're going to link this object all over the place but + // don't want the symbols to get exported. + let visibility = if self.tcx.is_compiler_builtins(LOCAL_CRATE) { + gccjit::Visibility::Hidden + } else { + base::visibility_to_gcc(visibility) + }; + global.add_attribute(VarAttribute::Visibility(visibility)); } if base::linkage_needs_weak_attribute(linkage) { global.add_attribute(VarAttribute::Weak); diff --git a/tests/run/weak_function_linkage.rs b/tests/run/weak_function_linkage.rs index 68052136369..82e1c3d2681 100644 --- a/tests/run/weak_function_linkage.rs +++ b/tests/run/weak_function_linkage.rs @@ -64,6 +64,16 @@ extern "C" fn available_externally_function() -> i32 { 7 } +// GCC warns that `inline` and `weak` conflict, and cg_gcc turns libgccjit warnings into errors, so +// this used to fail to compile at all. The inline hint is what gives way: rustc lints it as ignored +// on a function with an explicit `#[linkage]` anyway, hence the `allow`. +#[linkage = "weak"] +#[inline] +#[allow(unused_attributes)] +extern "C" fn weak_inline_function() -> i32 { + 8 +} + extern "C" { fn c_call_all() -> i32; } @@ -96,5 +106,8 @@ extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 { if available_externally_function() != 7 { return 7; } + if weak_inline_function() != 8 { + return 8; + } 0 } From 9f3034d809679a738621fc87088e7279d8a9cc33 Mon Sep 17 00:00:00 2001 From: Antoni Boucher Date: Tue, 25 Aug 2026 14:16:52 -0400 Subject: [PATCH 5/5] Cleanup --- tests/run/import_linkage.rs | 6 ++---- tests/run/static_linkage.rs | 6 +++--- tests/run/weak_function_linkage.rs | 5 ----- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/tests/run/import_linkage.rs b/tests/run/import_linkage.rs index c721309020e..bf83801d355 100644 --- a/tests/run/import_linkage.rs +++ b/tests/run/import_linkage.rs @@ -4,8 +4,7 @@ // status: 0 // Checks the `#[linkage]` flavours an `extern` static can be imported with, against the symbols -// `tests/c/import_linkage.c` defines. `linkonce`, `linkonce_odr`, `weak`, `weak_odr` and `common` -// used to reach an `unimplemented!()` in `global_linkage_to_gcc`. +// `tests/c/import_linkage.c` defines. // // The value of such an import is the address of the symbol rather than its contents, which is why // the types are pointers: an `extern_weak` import of a symbol nobody defines reads as null instead @@ -36,8 +35,7 @@ extern "C" { static common_value: *const i32; #[linkage = "extern_weak"] static extern_weak_value: *const i32; - // An import is an undefined reference whatever the flavour says; this used to declare a - // private zeroed object of its own instead of reaching the definition in the C file. + // An import is an undefined reference whatever the flavour says. #[linkage = "internal"] static internal_value: *const i32; diff --git a/tests/run/static_linkage.rs b/tests/run/static_linkage.rs index adc43ab9fe3..1a9b672de36 100644 --- a/tests/run/static_linkage.rs +++ b/tests/run/static_linkage.rs @@ -6,9 +6,9 @@ // Checks that `#[linkage]` on a static that this crate defines reaches the symbol, against // `tests/c/static_linkage.c`, which defines the overridable ones strongly. // -// `predefine_static` used to ignore its `linkage` argument outright, so every static came out as -// an ordinary global symbol: the overridable ones clashed with the C definitions at link time, and -// `internal` exported a symbol it should have kept private. +// If `predefine_static` were to ignore its `linkage` argument outright, every static would come out as +// an ordinary global symbol: the overridable ones would clash with the C definitions at link time, and +// `internal` would export a symbol it should have kept private. #![feature(linkage, no_core)] #![no_std] diff --git a/tests/run/weak_function_linkage.rs b/tests/run/weak_function_linkage.rs index 82e1c3d2681..353b71a1e62 100644 --- a/tests/run/weak_function_linkage.rs +++ b/tests/run/weak_function_linkage.rs @@ -6,11 +6,6 @@ // Checks that the `#[linkage]` flavours another object file is allowed to override are emitted as // weak symbols, by linking against `tests/c/weak_function_linkage.c`, which defines the same // symbols strongly. -// -// `weak` used to be emitted as an ordinary global symbol, which the C definitions clash with, and -// `weak_odr`, `linkonce`, `linkonce_odr` and `common` reached an `unimplemented!()` in -// `linkage_to_gcc`. `available_externally` reached libgccjit, which rejects a body on an imported -// function. #![feature(linkage, no_core)] #![no_std]