Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions compiler/rustc_attr_parsing/src/attributes/link_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,8 @@ impl<S: Stage> SingleAttributeParser<S> for LinkSectionParser {
Allow(Target::Method(MethodKind::Inherent)),
Allow(Target::Method(MethodKind::Trait { body: true })),
Allow(Target::Method(MethodKind::TraitImpl)),
Allow(Target::ForeignStatic),
Allow(Target::ForeignFn),
]);
const TEMPLATE: AttributeTemplate = template!(
NameValueStr: "name",
Expand Down
8 changes: 8 additions & 0 deletions compiler/rustc_codegen_llvm/src/callee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use rustc_middle::ty::{self, Instance, TypeVisitableExt};
use rustc_target::spec::{Arch, Env};
use tracing::debug;

use crate::base;
use crate::context::CodegenCx;
use crate::llvm::{self, Value};

Expand Down Expand Up @@ -152,6 +153,13 @@ pub(crate) fn get_fn<'ll, 'tcx>(cx: &CodegenCx<'ll, 'tcx>, instance: Instance<'t

cx.assume_dso_local(llfn, true);

if tcx.is_foreign_item(instance_def_id) {
base::set_link_section(llfn, tcx.codegen_fn_attrs(instance_def_id));
if tcx.sess.target.arch == Arch::Bpf {
cx.dbg_scope_foreign_fn(instance, fn_abi, Some(llfn));
}
}

llfn
};

Expand Down
7 changes: 7 additions & 0 deletions compiler/rustc_codegen_llvm/src/consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,13 @@ impl<'ll> CodegenCx<'ll, '_> {
llvm::set_dllimport_storage_class(g);
}

if self.tcx.is_foreign_item(def_id) {
Comment thread
altugbozkurt07 marked this conversation as resolved.
base::set_link_section(g, fn_attrs);
if self.tcx.sess.target.arch == Arch::Bpf {
debuginfo::build_extern_static_di_node(self, def_id, g);
}
}

self.instances.borrow_mut().insert(instance, g);
g
}
Expand Down
7 changes: 5 additions & 2 deletions compiler/rustc_codegen_llvm/src/debuginfo/di_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ pub(crate) trait DIBuilderExt<'ll> {
unsafe { llvm::LLVMDIBuilderCreateExpression(this, addr_ops.as_ptr(), addr_ops.len()) }
}

/// Creates a DIGlobalVariable debug info node.
fn create_static_variable(
&self,
scope: Option<&'ll llvm::Metadata>,
Expand All @@ -52,21 +53,22 @@ pub(crate) trait DIBuilderExt<'ll> {
line_number: c_uint,
ty: &'ll llvm::Metadata,
is_local_to_unit: bool,
is_definition: bool,
val: &'ll llvm::Value,
decl: Option<&'ll llvm::Metadata>,
align: Option<Align>,
) -> &'ll llvm::Metadata {
let this = self.as_di_builder();
let align_in_bits = align.map_or(0, |align| align.bits() as u32);

// `LLVMDIBuilderCreateGlobalVariableExpression` would assert if we
// `LLVMRustDIBuilderCreateGlobalVariableExpression` would assert if we
// gave it a null `Expr` pointer, so give it an empty expression
// instead, which is what the C++ `createGlobalVariableExpression`
// method would do if given a null `DIExpression` pointer.
let expr = self.create_expression(&[]);

let global_var_expr = unsafe {
llvm::LLVMDIBuilderCreateGlobalVariableExpression(
llvm::LLVMRustDIBuilderCreateGlobalVariableExpression(
this,
scope,
name.as_ptr(),
Expand All @@ -77,6 +79,7 @@ pub(crate) trait DIBuilderExt<'ll> {
line_number,
ty,
is_local_to_unit.to_llvm_bool(),
is_definition.to_llvm_bool(),
expr,
decl,
align_in_bits,
Expand Down
35 changes: 28 additions & 7 deletions compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1440,13 +1440,38 @@ fn build_generic_type_param_di_nodes<'ll, 'tcx>(
}
}

/// Creates debug information for the given global variable.
/// Creates debug information for the given global variable (definition).
///
/// Adds the created debuginfo nodes directly to the crate's IR.
pub(crate) fn build_global_var_di_node<'ll>(
cx: &CodegenCx<'ll, '_>,
def_id: DefId,
global: &'ll Value,
) {
let DefKind::Static { nested, .. } = cx.tcx.def_kind(def_id) else { bug!() };
if nested {
return;
}

let is_local_to_unit = is_node_local_to_unit(cx, def_id);
build_static_var_di_node_inner(cx, def_id, global, is_local_to_unit, true);
}

/// Creates debug information for a foreign static (declaration, not definition).
pub(crate) fn build_extern_static_di_node<'ll>(
cx: &CodegenCx<'ll, '_>,
def_id: DefId,
global: &'ll Value,
) {
build_static_var_di_node_inner(cx, def_id, global, false, false);
}

fn build_static_var_di_node_inner<'ll>(
cx: &CodegenCx<'ll, '_>,
def_id: DefId,
global: &'ll Value,
is_local_to_unit: bool,
is_definition: bool,
) {
if cx.dbg_cx.is_none() {
return;
Expand All @@ -1464,12 +1489,6 @@ pub(crate) fn build_global_var_di_node<'ll>(
let var_scope = get_namespace_for_item(cx, def_id);
let (file_metadata, line_number) = file_metadata_from_def_id(cx, Some(def_id));

let is_local_to_unit = is_node_local_to_unit(cx, def_id);

let DefKind::Static { nested, .. } = cx.tcx.def_kind(def_id) else { bug!() };
if nested {
return;
}
let variable_type = Instance::mono(cx.tcx, def_id).ty(cx.tcx, cx.typing_env());
let type_di_node = type_di_node(cx, variable_type);
let var_name = tcx.item_name(def_id);
Expand All @@ -1489,6 +1508,7 @@ pub(crate) fn build_global_var_di_node<'ll>(
line_number,
type_di_node,
is_local_to_unit,
is_definition,
global, // (value)
None, // (decl)
Some(global_align),
Expand Down Expand Up @@ -1767,6 +1787,7 @@ pub(crate) fn create_vtable_di_node<'ll, 'tcx>(
UNKNOWN_LINE_NUMBER,
vtable_type_di_node,
true, // (is_local_to_unit)
true, // (is_definition)
vtable, // (value)
None, // (decl)
None::<Align>,
Expand Down
87 changes: 86 additions & 1 deletion compiler/rustc_codegen_llvm/src/debuginfo/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ use tracing::debug;

use self::create_scope_map::compute_mir_scopes;
pub(crate) use self::di_builder::DIBuilderExt;
pub(crate) use self::metadata::build_global_var_di_node;
use self::metadata::{
UNKNOWN_COLUMN_NUMBER, UNKNOWN_LINE_NUMBER, file_metadata, spanned_type_di_node, type_di_node,
};
pub(crate) use self::metadata::{build_extern_static_di_node, build_global_var_di_node};
use self::namespace::mangled_name_of_instance;
use self::utils::{DIB, create_DIArray, is_node_local_to_unit};
use crate::builder::Builder;
Expand Down Expand Up @@ -759,3 +759,88 @@ impl<'ll, 'tcx> DebugInfoCodegenMethods<'tcx> for CodegenCx<'ll, 'tcx> {
}
}
}

impl<'ll, 'tcx> CodegenCx<'ll, 'tcx> {
/// Creates a `DISubprogram` for a foreign function declaration (without `SPFlagDefinition`).
pub(crate) fn dbg_scope_foreign_fn(
&self,
instance: Instance<'tcx>,
fn_abi: &FnAbi<'tcx, Ty<'tcx>>,
llfn: Option<&'ll Value>,
) {
if self.dbg_cx.is_none() {
return;
}

if self.sess().opts.debuginfo != DebugInfo::Full {
return;
}

let tcx = self.tcx;
let def_id = instance.def_id();

let scope = namespace::item_namespace(
self,
DefId {
krate: def_id.krate,
index: tcx.def_key(def_id).parent.expect("dbg_scope_foreign_fn: missing parent?"),
},
);

let span = tcx.def_span(def_id);
let loc = self.lookup_debug_loc(span.lo());
let file_metadata = file_metadata(self, &loc.file);

// Foreign function declarations do not need the platform-specific
// adjustments used for regular function definitions.
let signature: Vec<_> = iter::once(if fn_abi.ret.is_ignore() {
None
} else {
Some(type_di_node(self, fn_abi.ret.layout.ty))
})
.chain(fn_abi.args.iter().map(|arg| Some(type_di_node(self, arg.layout.ty))))
.collect();

let function_type_metadata = create_subroutine_type(self, &signature);

let mut name = String::with_capacity(64);
type_names::push_item_name(tcx, def_id, false, &mut name);

let linkage_name = &mangled_name_of_instance(self, instance).name;
let linkage_name = if &name == linkage_name { "" } else { linkage_name };

let scope_line = loc.line;

let mut flags = DIFlags::FlagPrototyped;
if fn_abi.ret.layout.is_uninhabited() {
flags |= DIFlags::FlagNoReturn;
}

let mut spflags = DISPFlags::SPFlagZero;
if self.sess().opts.optimize != config::OptLevel::No {
spflags |= DISPFlags::SPFlagOptimized;
}

let template_parameters = create_DIArray(DIB(self), &[]);

unsafe {
llvm::LLVMRustDIBuilderCreateFunction(
DIB(self),
scope,
name.as_c_char_ptr(),
name.len(),
linkage_name.as_c_char_ptr(),
linkage_name.len(),
file_metadata,
loc.line,
function_type_metadata,
scope_line,
flags,
spflags,
llfn, // Attach to LLVM function if provided
template_parameters,
None, // No decl
);
}
}
}
3 changes: 2 additions & 1 deletion compiler/rustc_codegen_llvm/src/llvm/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1895,7 +1895,7 @@ unsafe extern "C" {
Length: size_t,
) -> &'ll Metadata;

pub(crate) fn LLVMDIBuilderCreateGlobalVariableExpression<'ll>(
pub(crate) fn LLVMRustDIBuilderCreateGlobalVariableExpression<'ll>(
Builder: &DIBuilder<'ll>,
Scope: Option<&'ll Metadata>,
Name: *const c_uchar, // See "PTR_LEN_STR".
Expand All @@ -1906,6 +1906,7 @@ unsafe extern "C" {
LineNo: c_uint,
Ty: &'ll Metadata,
LocalToUnit: llvm::Bool,
IsDefined: llvm::Bool,
Expr: &'ll Metadata,
Decl: Option<&'ll Metadata>,
AlignInBits: u32,
Expand Down
16 changes: 16 additions & 0 deletions compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1147,6 +1147,22 @@ extern "C" LLVMMetadataRef LLVMRustDIBuilderCreateMethod(
return wrap(Sub);
}

// Wraps DIBuilder::createGlobalVariableExpression. Unlike the LLVM-C API
// (LLVMDIBuilderCreateGlobalVariableExpression), this exposes the IsDefined
// parameter instead of hard-coding it to true.
Copy link
Copy Markdown
Contributor

@vadorovsky vadorovsky Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

View changes since the review

This should be applied to the previous commit (Add FFI wrapper to expose isDefinition in DIBuilder).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be okay now

extern "C" LLVMMetadataRef LLVMRustDIBuilderCreateGlobalVariableExpression(
LLVMDIBuilderRef Builder, LLVMMetadataRef Scope, const char *Name,
size_t NameLen, const char *Linkage, size_t LinkLen, LLVMMetadataRef File,
unsigned LineNo, LLVMMetadataRef Ty, LLVMBool LocalToUnit,
LLVMBool IsDefined, LLVMMetadataRef Expr, LLVMMetadataRef Decl,
uint32_t AlignInBits) {
return wrap(unwrap(Builder)->createGlobalVariableExpression(
unwrapDI<DIScope>(Scope), {Name, NameLen}, {Linkage, LinkLen},
unwrapDI<DIFile>(File), LineNo, unwrapDI<DIType>(Ty), LocalToUnit,
IsDefined, unwrap<DIExpression>(Expr), unwrapDI<MDNode>(Decl), nullptr,
AlignInBits));
}

extern "C" LLVMMetadataRef LLVMRustDIBuilderCreateVariantPart(
LLVMDIBuilderRef Builder, LLVMMetadataRef Scope, const char *Name,
size_t NameLen, LLVMMetadataRef File, unsigned LineNumber,
Expand Down
35 changes: 35 additions & 0 deletions tests/codegen-llvm/bpf-extern-debuginfo.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Checks that BPF extern declarations are emitted as debug info declarations.
//
//@ only-bpf
//@ needs-llvm-components: bpf
//@ compile-flags: --target bpfel-unknown-none -C debuginfo=2

#![no_std]
#![no_main]
#![crate_type = "lib"]

extern "C" {
// CHECK: !DIGlobalVariable(name: "KERNEL_VERSION"
// CHECK-SAME: isLocal: false
// CHECK-SAME: isDefinition: false
#[link_section = ".ksyms"]
pub static KERNEL_VERSION: u64;
}

extern "C" {
// CHECK: !DISubprogram(name: "bpf_kfunc"
// CHECK-SAME: flags: DIFlagPrototyped
// CHECK-SAME: spFlags: DISPFlagZero
#[link_section = ".ksyms"]
pub fn bpf_kfunc(x: u64) -> u64;
}

#[no_mangle]
pub fn test_extern_items() -> u64 {
unsafe { KERNEL_VERSION + bpf_kfunc(42) }
}

#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
loop {}
}
24 changes: 24 additions & 0 deletions tests/codegen-llvm/extern-no-debuginfo-non-bpf.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Checks that extern declarations do not get debug info outside BPF targets.
//
//@ compile-flags: -C debuginfo=2

#![crate_type = "lib"]

extern "C" {
// CHECK: @EXTERN_STATIC = external {{.*}}global i32
// CHECK-NOT: !DIGlobalVariable(name: "EXTERN_STATIC"
pub static EXTERN_STATIC: i32;
}

extern "C" {
// CHECK: declare {{.*}}void @extern_fn()
// CHECK-NOT: !DISubprogram(name: "extern_fn"
pub fn extern_fn();
}

pub fn use_extern_items() -> i32 {
unsafe {
extern_fn();
EXTERN_STATIC
}
}
26 changes: 26 additions & 0 deletions tests/codegen-llvm/link-section-foreign.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Verifies that #[link_section] works on foreign (extern) items.
//
//@ ignore-wasm32 custom sections work differently on wasm
//@ compile-flags: -C no-prepopulate-passes

#![crate_type = "lib"]

extern "C" {
// CHECK: @EXTERN_STATIC = external global i32, section ".ksyms"
#[link_section = ".ksyms"]
pub static EXTERN_STATIC: i32;
}

extern "C" {
// CHECK: declare {{.*}}void @extern_fn(){{.*}} section ".ksyms"
#[link_section = ".ksyms"]
pub fn extern_fn();
}

// Ensure the extern items are used so they appear in the IR
pub fn use_extern_items() -> i32 {
unsafe {
extern_fn();
EXTERN_STATIC
}
}
2 changes: 1 addition & 1 deletion tests/ui/attributes/attr-on-mac-call.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ LL | #[link_section = "x"]
| ^^^^^^^^^^^^^^^^^^^^^
|
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
= help: `#[link_section]` can be applied to functions and statics
= help: `#[link_section]` can be applied to foreign statics, functions, and statics

warning: `#[link_ordinal]` attribute cannot be used on macro calls
--> $DIR/attr-on-mac-call.rs:33:5
Expand Down
Loading
Loading