Files
rust/compiler/rustc_codegen_llvm/src/allocator.rs
T
Flakebi 8e932ed79c Use convergent attribute to funcs for GPU targets
On targets with convergent operations, we need to add the convergent
attribute to all functions that run convergent operations. Following
clang, we can conservatively apply the attribute to all functions when
compiling for such a target and rely on LLVM optimizing away the
attribute in cases where it is not necessary.

This affects the amdgpu and nvptx targets.
2026-03-17 10:51:31 +01:00

185 lines
5.8 KiB
Rust

use libc::c_uint;
use rustc_ast::expand::allocator::{
AllocatorMethod, AllocatorTy, NO_ALLOC_SHIM_IS_UNSTABLE, SpecialAllocatorMethod,
default_fn_name, global_fn_name,
};
use rustc_codegen_ssa::traits::BaseTypeCodegenMethods as _;
use rustc_middle::bug;
use rustc_middle::middle::codegen_fn_attrs::{CodegenFnAttrFlags, CodegenFnAttrs};
use rustc_middle::ty::TyCtxt;
use rustc_session::config::DebugInfo;
use rustc_symbol_mangling::mangle_internal_symbol;
use crate::attributes::llfn_attrs_from_instance;
use crate::builder::SBuilder;
use crate::declare::declare_simple_fn;
use crate::llvm::{self, FromGeneric, TRUE, Type};
use crate::{SimpleCx, attributes, debuginfo};
pub(crate) unsafe fn codegen(
tcx: TyCtxt<'_>,
cx: SimpleCx<'_>,
module_name: &str,
methods: &[AllocatorMethod],
) {
let usize = match tcx.sess.target.pointer_width {
16 => cx.type_i16(),
32 => cx.type_i32(),
64 => cx.type_i64(),
tws => bug!("Unsupported target word size for int: {}", tws),
};
let i8p = cx.type_ptr();
for method in methods {
let mut args = Vec::with_capacity(method.inputs.len());
for input in method.inputs.iter() {
match input.ty {
AllocatorTy::Layout => {
args.push(usize); // size
args.push(usize); // align
}
AllocatorTy::Ptr => args.push(i8p),
AllocatorTy::Usize => args.push(usize),
AllocatorTy::Never | AllocatorTy::ResultPtr | AllocatorTy::Unit => {
panic!("invalid allocator arg")
}
}
}
let mut no_return = false;
let output = match method.output {
AllocatorTy::ResultPtr => Some(i8p),
AllocatorTy::Unit => None,
AllocatorTy::Never => {
no_return = true;
None
}
AllocatorTy::Layout | AllocatorTy::Usize | AllocatorTy::Ptr => {
panic!("invalid allocator output")
}
};
let from_name = mangle_internal_symbol(tcx, &global_fn_name(method.name));
let to_name = mangle_internal_symbol(tcx, &default_fn_name(method.name));
let alloc_attr_flag = match method.special {
Some(SpecialAllocatorMethod::Alloc) => CodegenFnAttrFlags::ALLOCATOR,
Some(SpecialAllocatorMethod::Dealloc) => CodegenFnAttrFlags::DEALLOCATOR,
Some(SpecialAllocatorMethod::Realloc) => CodegenFnAttrFlags::REALLOCATOR,
Some(SpecialAllocatorMethod::AllocZeroed) => CodegenFnAttrFlags::ALLOCATOR_ZEROED,
None => CodegenFnAttrFlags::empty(),
};
let mut attrs = CodegenFnAttrs::new();
attrs.flags |= alloc_attr_flag;
create_wrapper_function(
tcx,
&cx,
&from_name,
Some(&to_name),
&args,
output,
no_return,
&attrs,
);
}
// __rust_no_alloc_shim_is_unstable_v2
create_wrapper_function(
tcx,
&cx,
&mangle_internal_symbol(tcx, NO_ALLOC_SHIM_IS_UNSTABLE),
None,
&[],
None,
false,
&CodegenFnAttrs::new(),
);
if tcx.sess.opts.debuginfo != DebugInfo::None {
let dbg_cx = debuginfo::CodegenUnitDebugContext::new(cx.llmod);
debuginfo::metadata::build_compile_unit_di_node(tcx, module_name, &dbg_cx);
dbg_cx.finalize(tcx.sess);
}
}
fn create_wrapper_function(
tcx: TyCtxt<'_>,
cx: &SimpleCx<'_>,
from_name: &str,
to_name: Option<&str>,
args: &[&Type],
output: Option<&Type>,
no_return: bool,
attrs: &CodegenFnAttrs,
) {
let ty = cx.type_func(args, output.unwrap_or_else(|| cx.type_void()));
let llfn = declare_simple_fn(
&cx,
from_name,
llvm::CallConv::CCallConv,
llvm::UnnamedAddr::Global,
llvm::Visibility::from_generic(tcx.sess.default_visibility()),
ty,
);
llfn_attrs_from_instance(cx, tcx, llfn, attrs, None);
let no_return = if no_return {
// -> ! DIFlagNoReturn
let no_return = llvm::AttributeKind::NoReturn.create_attr(cx.llcx);
attributes::apply_to_llfn(llfn, llvm::AttributePlace::Function, &[no_return]);
Some(no_return)
} else {
None
};
if tcx.sess.target.is_like_gpu {
// Conservatively apply convergent to all functions in case they may call
// a convergent function. Rely on LLVM to optimize away the unnecessary
// convergent attributes.
attributes::apply_to_llfn(
llfn,
llvm::AttributePlace::Function,
&[llvm::AttributeKind::Convergent.create_attr(cx.llcx)],
);
}
let llbb = unsafe { llvm::LLVMAppendBasicBlockInContext(cx.llcx, llfn, c"entry".as_ptr()) };
let mut bx = SBuilder::build(&cx, llbb);
if let Some(to_name) = to_name {
let callee = declare_simple_fn(
&cx,
to_name,
llvm::CallConv::CCallConv,
llvm::UnnamedAddr::Global,
llvm::Visibility::Hidden,
ty,
);
if let Some(no_return) = no_return {
// -> ! DIFlagNoReturn
attributes::apply_to_llfn(callee, llvm::AttributePlace::Function, &[no_return]);
}
llvm::set_visibility(callee, llvm::Visibility::Hidden);
let args = args
.iter()
.enumerate()
.map(|(i, _)| llvm::get_param(llfn, i as c_uint))
.collect::<Vec<_>>();
let ret = bx.call(ty, callee, &args, None);
llvm::LLVMSetTailCall(ret, TRUE);
if output.is_some() {
bx.ret(ret);
} else {
bx.ret_void()
}
} else {
assert!(output.is_none());
bx.ret_void()
}
}