Support naked functions

Fixes rust-lang/rustc_codegen_cranelift#1203
This commit is contained in:
bjorn3 2024-05-12 18:12:48 +02:00
parent 0627c63ad9
commit cba05a7a14
7 changed files with 231 additions and 48 deletions

View File

@ -276,7 +276,7 @@ fn build_clif_sysroot_for_triple(
if channel == "release" { if channel == "release" {
build_cmd.arg("--release"); build_cmd.arg("--release");
} }
build_cmd.arg("--features").arg("compiler-builtins-no-asm backtrace panic-unwind"); build_cmd.arg("--features").arg("backtrace panic-unwind");
build_cmd.env("CARGO_PROFILE_RELEASE_DEBUG", "true"); build_cmd.env("CARGO_PROFILE_RELEASE_DEBUG", "true");
build_cmd.env("__CARGO_DEFAULT_LIB_METADATA", "cg_clif"); build_cmd.env("__CARGO_DEFAULT_LIB_METADATA", "cg_clif");
if compiler.triple.contains("apple") { if compiler.triple.contains("apple") {

View File

@ -4,6 +4,7 @@
never_type, never_type,
linkage, linkage,
extern_types, extern_types,
naked_functions,
thread_local, thread_local,
repr_simd, repr_simd,
raw_ref_op raw_ref_op
@ -340,6 +341,7 @@ struct ExternTypeWrapper {
))] ))]
unsafe { unsafe {
global_asm_test(); global_asm_test();
naked_test();
} }
// Both statics have a reference that points to the same anonymous allocation. // Both statics have a reference that points to the same anonymous allocation.
@ -395,6 +397,14 @@ fn stack_val_align() {
" "
} }
#[cfg(all(not(jit), not(no_unstable_features), target_arch = "x86_64"))]
#[naked]
extern "C" fn naked_test() {
unsafe {
asm!("ret", options(noreturn));
}
}
#[repr(C)] #[repr(C)]
enum c_void { enum c_void {
_1, _1,

View File

@ -6,6 +6,7 @@
use cranelift_module::ModuleError; use cranelift_module::ModuleError;
use rustc_ast::InlineAsmOptions; use rustc_ast::InlineAsmOptions;
use rustc_index::IndexVec; use rustc_index::IndexVec;
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
use rustc_middle::ty::adjustment::PointerCoercion; use rustc_middle::ty::adjustment::PointerCoercion;
use rustc_middle::ty::layout::FnAbiOf; use rustc_middle::ty::layout::FnAbiOf;
use rustc_middle::ty::print::with_no_trimmed_paths; use rustc_middle::ty::print::with_no_trimmed_paths;
@ -14,6 +15,7 @@
use crate::constant::ConstantCx; use crate::constant::ConstantCx;
use crate::debuginfo::{FunctionDebugContext, TypeDebugContext}; use crate::debuginfo::{FunctionDebugContext, TypeDebugContext};
use crate::inline_asm::codegen_naked_asm;
use crate::prelude::*; use crate::prelude::*;
use crate::pretty_clif::CommentWriter; use crate::pretty_clif::CommentWriter;
@ -32,7 +34,7 @@ pub(crate) fn codegen_fn<'tcx>(
cached_func: Function, cached_func: Function,
module: &mut dyn Module, module: &mut dyn Module,
instance: Instance<'tcx>, instance: Instance<'tcx>,
) -> CodegenedFunction { ) -> Option<CodegenedFunction> {
debug_assert!(!instance.args.has_infer()); debug_assert!(!instance.args.has_infer());
let symbol_name = tcx.symbol_name(instance).name.to_string(); let symbol_name = tcx.symbol_name(instance).name.to_string();
@ -48,6 +50,37 @@ pub(crate) fn codegen_fn<'tcx>(
String::from_utf8_lossy(&buf).into_owned() String::from_utf8_lossy(&buf).into_owned()
}); });
if tcx.codegen_fn_attrs(instance.def_id()).flags.contains(CodegenFnAttrFlags::NAKED) {
assert_eq!(mir.basic_blocks.len(), 1);
assert!(mir.basic_blocks[START_BLOCK].statements.is_empty());
match &mir.basic_blocks[START_BLOCK].terminator().kind {
TerminatorKind::InlineAsm {
template,
operands,
options,
line_spans: _,
targets: _,
unwind: _,
} => {
codegen_naked_asm(
tcx,
cx,
module,
instance,
mir.basic_blocks[START_BLOCK].terminator().source_info.span,
&symbol_name,
template,
operands,
*options,
);
}
_ => unreachable!(),
}
return None;
}
// Declare function // Declare function
let sig = get_function_sig(tcx, module.target_config().default_call_conv, instance); let sig = get_function_sig(tcx, module.target_config().default_call_conv, instance);
let func_id = module.declare_function(&symbol_name, Linkage::Local, &sig).unwrap(); let func_id = module.declare_function(&symbol_name, Linkage::Local, &sig).unwrap();
@ -128,7 +161,7 @@ pub(crate) fn codegen_fn<'tcx>(
// Verify function // Verify function
verify_func(tcx, &clif_comments, &func); verify_func(tcx, &clif_comments, &func);
CodegenedFunction { symbol_name, func_id, func, clif_comments, func_debug_cx } Some(CodegenedFunction { symbol_name, func_id, func, clif_comments, func_debug_cx })
} }
pub(crate) fn compile_fn( pub(crate) fn compile_fn(

View File

@ -482,15 +482,16 @@ fn module_codegen(
for (mono_item, _) in mono_items { for (mono_item, _) in mono_items {
match mono_item { match mono_item {
MonoItem::Fn(inst) => { MonoItem::Fn(inst) => {
let codegened_function = crate::base::codegen_fn( if let Some(codegened_function) = crate::base::codegen_fn(
tcx, tcx,
&mut cx, &mut cx,
&mut type_dbg, &mut type_dbg,
Function::new(), Function::new(),
&mut module, &mut module,
inst, inst,
); ) {
codegened_functions.push(codegened_function); codegened_functions.push(codegened_function);
}
} }
MonoItem::Static(def_id) => { MonoItem::Static(def_id) => {
let data_id = crate::constant::codegen_static(tcx, &mut module, def_id); let data_id = crate::constant::codegen_static(tcx, &mut module, def_id);

View File

@ -232,16 +232,16 @@ pub(crate) fn codegen_and_compile_fn<'tcx>(
crate::PrintOnPanic(|| format!("{:?} {}", instance, tcx.symbol_name(instance).name)); crate::PrintOnPanic(|| format!("{:?} {}", instance, tcx.symbol_name(instance).name));
let cached_func = std::mem::replace(&mut cached_context.func, Function::new()); let cached_func = std::mem::replace(&mut cached_context.func, Function::new());
let codegened_func = crate::base::codegen_fn( if let Some(codegened_func) = crate::base::codegen_fn(
tcx, tcx,
cx, cx,
&mut TypeDebugContext::default(), &mut TypeDebugContext::default(),
cached_func, cached_func,
module, module,
instance, instance,
); ) {
crate::base::compile_fn(cx, cached_context, module, codegened_func);
crate::base::compile_fn(cx, cached_context, module, codegened_func); }
}); });
} }

View File

@ -5,6 +5,7 @@
//! [`codegen_static`]: crate::constant::codegen_static //! [`codegen_static`]: crate::constant::codegen_static
use rustc_data_structures::profiling::SelfProfilerRef; use rustc_data_structures::profiling::SelfProfilerRef;
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
use rustc_middle::mir::mono::{MonoItem, MonoItemData}; use rustc_middle::mir::mono::{MonoItem, MonoItemData};
use crate::prelude::*; use crate::prelude::*;
@ -33,7 +34,20 @@ fn predefine_mono_items<'tcx>(
data.visibility, data.visibility,
is_compiler_builtins, is_compiler_builtins,
); );
module.declare_function(name, linkage, &sig).unwrap(); let is_naked = tcx
.codegen_fn_attrs(instance.def_id())
.flags
.contains(CodegenFnAttrFlags::NAKED);
module
.declare_function(
name,
// Naked functions are defined in a separate object
// file from the codegen unit rustc expects them to
// be defined in.
if is_naked { Linkage::Import } else { linkage },
&sig,
)
.unwrap();
} }
MonoItem::Static(_) | MonoItem::GlobalAsm(_) => {} MonoItem::Static(_) | MonoItem::GlobalAsm(_) => {}
} }

View File

@ -169,6 +169,7 @@ pub(crate) fn codegen_inline_asm_inner<'tcx>(
stack_slots_input: Vec::new(), stack_slots_input: Vec::new(),
stack_slots_output: Vec::new(), stack_slots_output: Vec::new(),
stack_slot_size: Size::from_bytes(0), stack_slot_size: Size::from_bytes(0),
is_naked: false,
}; };
asm_gen.allocate_registers(); asm_gen.allocate_registers();
asm_gen.allocate_stack_slots(); asm_gen.allocate_stack_slots();
@ -209,6 +210,121 @@ pub(crate) fn codegen_inline_asm_inner<'tcx>(
call_inline_asm(fx, &asm_name, asm_gen.stack_slot_size, inputs, outputs); call_inline_asm(fx, &asm_name, asm_gen.stack_slot_size, inputs, outputs);
} }
pub(crate) fn codegen_naked_asm<'tcx>(
tcx: TyCtxt<'tcx>,
cx: &mut crate::CodegenCx,
module: &mut dyn Module,
instance: Instance<'tcx>,
span: Span,
symbol_name: &str,
template: &[InlineAsmTemplatePiece],
operands: &[InlineAsmOperand<'tcx>],
options: InlineAsmOptions,
) {
// FIXME add .eh_frame unwind info directives
let operands = operands
.iter()
.map(|operand| match *operand {
InlineAsmOperand::In { .. }
| InlineAsmOperand::Out { .. }
| InlineAsmOperand::InOut { .. } => {
span_bug!(span, "invalid operand type for naked asm")
}
InlineAsmOperand::Const { ref value } => {
let cv = instance.instantiate_mir_and_normalize_erasing_regions(
tcx,
ty::ParamEnv::reveal_all(),
ty::EarlyBinder::bind(value.const_),
);
let const_value = cv
.eval(tcx, ty::ParamEnv::reveal_all(), value.span)
.expect("erroneous constant missed by mono item collection");
let value = rustc_codegen_ssa::common::asm_const_to_str(
tcx,
span,
const_value,
RevealAllLayoutCx(tcx).layout_of(cv.ty()),
);
CInlineAsmOperand::Const { value }
}
InlineAsmOperand::SymFn { ref value } => {
if cfg!(not(feature = "inline_asm_sym")) {
tcx.dcx()
.span_err(span, "asm! and global_asm! sym operands are not yet supported");
}
let const_ = instance.instantiate_mir_and_normalize_erasing_regions(
tcx,
ty::ParamEnv::reveal_all(),
ty::EarlyBinder::bind(value.const_),
);
if let ty::FnDef(def_id, args) = *const_.ty().kind() {
let instance = ty::Instance::resolve_for_fn_ptr(
tcx,
ty::ParamEnv::reveal_all(),
def_id,
args,
)
.unwrap();
let symbol = tcx.symbol_name(instance);
// Pass a wrapper rather than the function itself as the function itself may not
// be exported from the main codegen unit and may thus be unreachable from the
// object file created by an external assembler.
let inline_asm_index = cx.inline_asm_index.get();
cx.inline_asm_index.set(inline_asm_index + 1);
let wrapper_name = format!(
"__inline_asm_{}_wrapper_n{}",
cx.cgu_name.as_str().replace('.', "__").replace('-', "_"),
inline_asm_index
);
let sig =
get_function_sig(tcx, module.target_config().default_call_conv, instance);
create_wrapper_function(
module,
&mut cx.unwind_context,
sig,
&wrapper_name,
symbol.name,
);
CInlineAsmOperand::Symbol { symbol: wrapper_name }
} else {
span_bug!(span, "invalid type for asm sym (fn)");
}
}
InlineAsmOperand::SymStatic { def_id } => {
assert!(tcx.is_static(def_id));
let instance = Instance::mono(tcx, def_id);
CInlineAsmOperand::Symbol { symbol: tcx.symbol_name(instance).name.to_owned() }
}
InlineAsmOperand::Label { .. } => {
span_bug!(span, "asm! label operands are not yet supported");
}
})
.collect::<Vec<_>>();
let asm_gen = InlineAssemblyGenerator {
tcx,
arch: tcx.sess.asm_arch.unwrap(),
enclosing_def_id: instance.def_id(),
template,
operands: &operands,
options,
registers: Vec::new(),
stack_slots_clobber: Vec::new(),
stack_slots_input: Vec::new(),
stack_slots_output: Vec::new(),
stack_slot_size: Size::from_bytes(0),
is_naked: true,
};
let generated_asm = asm_gen.generate_asm_wrapper(symbol_name);
cx.global_asm.push_str(&generated_asm);
}
struct InlineAssemblyGenerator<'a, 'tcx> { struct InlineAssemblyGenerator<'a, 'tcx> {
tcx: TyCtxt<'tcx>, tcx: TyCtxt<'tcx>,
arch: InlineAsmArch, arch: InlineAsmArch,
@ -221,10 +337,13 @@ struct InlineAssemblyGenerator<'a, 'tcx> {
stack_slots_input: Vec<Option<Size>>, stack_slots_input: Vec<Option<Size>>,
stack_slots_output: Vec<Option<Size>>, stack_slots_output: Vec<Option<Size>>,
stack_slot_size: Size, stack_slot_size: Size,
is_naked: bool,
} }
impl<'tcx> InlineAssemblyGenerator<'_, 'tcx> { impl<'tcx> InlineAssemblyGenerator<'_, 'tcx> {
fn allocate_registers(&mut self) { fn allocate_registers(&mut self) {
assert!(!self.is_naked);
let sess = self.tcx.sess; let sess = self.tcx.sess;
let map = allocatable_registers( let map = allocatable_registers(
self.arch, self.arch,
@ -348,6 +467,8 @@ fn allocate_registers(&mut self) {
} }
fn allocate_stack_slots(&mut self) { fn allocate_stack_slots(&mut self) {
assert!(!self.is_naked);
let mut slot_size = Size::from_bytes(0); let mut slot_size = Size::from_bytes(0);
let mut slots_clobber = vec![None; self.operands.len()]; let mut slots_clobber = vec![None; self.operands.len()];
let mut slots_input = vec![None; self.operands.len()]; let mut slots_input = vec![None; self.operands.len()];
@ -468,30 +589,32 @@ fn generate_asm_wrapper(&self, asm_name: &str) -> String {
if is_x86 { if is_x86 {
generated_asm.push_str(".intel_syntax noprefix\n"); generated_asm.push_str(".intel_syntax noprefix\n");
} }
Self::prologue(&mut generated_asm, self.arch); if !self.is_naked {
Self::prologue(&mut generated_asm, self.arch);
// Save clobbered registers // Save clobbered registers
if !self.options.contains(InlineAsmOptions::NORETURN) { if !self.options.contains(InlineAsmOptions::NORETURN) {
for (reg, slot) in self
.registers
.iter()
.zip(self.stack_slots_clobber.iter().copied())
.filter_map(|(r, s)| r.zip(s))
{
Self::save_register(&mut generated_asm, self.arch, reg, slot);
}
}
// Write input registers
for (reg, slot) in self for (reg, slot) in self
.registers .registers
.iter() .iter()
.zip(self.stack_slots_clobber.iter().copied()) .zip(self.stack_slots_input.iter().copied())
.filter_map(|(r, s)| r.zip(s)) .filter_map(|(r, s)| r.zip(s))
{ {
Self::save_register(&mut generated_asm, self.arch, reg, slot); Self::restore_register(&mut generated_asm, self.arch, reg, slot);
} }
} }
// Write input registers
for (reg, slot) in self
.registers
.iter()
.zip(self.stack_slots_input.iter().copied())
.filter_map(|(r, s)| r.zip(s))
{
Self::restore_register(&mut generated_asm, self.arch, reg, slot);
}
if is_x86 && self.options.contains(InlineAsmOptions::ATT_SYNTAX) { if is_x86 && self.options.contains(InlineAsmOptions::ATT_SYNTAX) {
generated_asm.push_str(".att_syntax\n"); generated_asm.push_str(".att_syntax\n");
} }
@ -553,30 +676,32 @@ fn generate_asm_wrapper(&self, asm_name: &str) -> String {
generated_asm.push_str(".intel_syntax noprefix\n"); generated_asm.push_str(".intel_syntax noprefix\n");
} }
if !self.options.contains(InlineAsmOptions::NORETURN) { if !self.is_naked {
// Read output registers if !self.options.contains(InlineAsmOptions::NORETURN) {
for (reg, slot) in self // Read output registers
.registers for (reg, slot) in self
.iter() .registers
.zip(self.stack_slots_output.iter().copied()) .iter()
.filter_map(|(r, s)| r.zip(s)) .zip(self.stack_slots_output.iter().copied())
{ .filter_map(|(r, s)| r.zip(s))
Self::save_register(&mut generated_asm, self.arch, reg, slot); {
} Self::save_register(&mut generated_asm, self.arch, reg, slot);
}
// Restore clobbered registers // Restore clobbered registers
for (reg, slot) in self for (reg, slot) in self
.registers .registers
.iter() .iter()
.zip(self.stack_slots_clobber.iter().copied()) .zip(self.stack_slots_clobber.iter().copied())
.filter_map(|(r, s)| r.zip(s)) .filter_map(|(r, s)| r.zip(s))
{ {
Self::restore_register(&mut generated_asm, self.arch, reg, slot); Self::restore_register(&mut generated_asm, self.arch, reg, slot);
} }
Self::epilogue(&mut generated_asm, self.arch); Self::epilogue(&mut generated_asm, self.arch);
} else { } else {
Self::epilogue_noreturn(&mut generated_asm, self.arch); Self::epilogue_noreturn(&mut generated_asm, self.arch);
}
} }
if is_x86 { if is_x86 {