rustc_codegen_ssa/mir/
naked_asm.rs

1use rustc_abi::{BackendRepr, Float, Integer, Primitive, RegKind};
2use rustc_attr_parsing::InstructionSetAttr;
3use rustc_hir::def_id::DefId;
4use rustc_middle::mir::mono::{Linkage, MonoItem, MonoItemData, Visibility};
5use rustc_middle::mir::{Body, InlineAsmOperand};
6use rustc_middle::ty::layout::{FnAbiOf, HasTyCtxt, HasTypingEnv, LayoutOf};
7use rustc_middle::ty::{Instance, Ty, TyCtxt};
8use rustc_middle::{bug, span_bug, ty};
9use rustc_span::sym;
10use rustc_target::callconv::{ArgAbi, FnAbi, PassMode};
11use rustc_target::spec::WasmCAbi;
12
13use crate::common;
14use crate::traits::{AsmCodegenMethods, BuilderMethods, GlobalAsmOperandRef, MiscCodegenMethods};
15
16pub(crate) fn codegen_naked_asm<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
17    cx: &'a Bx::CodegenCx,
18    mir: &Body<'tcx>,
19    instance: Instance<'tcx>,
20) {
21    let rustc_middle::mir::TerminatorKind::InlineAsm {
22        asm_macro: _,
23        template,
24        ref operands,
25        options,
26        line_spans,
27        targets: _,
28        unwind: _,
29    } = mir.basic_blocks.iter().next().unwrap().terminator().kind
30    else {
31        bug!("#[naked] functions should always terminate with an asm! block")
32    };
33
34    let operands: Vec<_> =
35        operands.iter().map(|op| inline_to_global_operand::<Bx>(cx, instance, op)).collect();
36
37    let item_data = cx.codegen_unit().items().get(&MonoItem::Fn(instance)).unwrap();
38    let name = cx.mangled_name(instance);
39    let fn_abi = cx.fn_abi_of_instance(instance, ty::List::empty());
40    let (begin, end) = prefix_and_suffix(cx.tcx(), instance, &name, item_data, fn_abi);
41
42    let mut template_vec = Vec::new();
43    template_vec.push(rustc_ast::ast::InlineAsmTemplatePiece::String(begin.into()));
44    template_vec.extend(template.iter().cloned());
45    template_vec.push(rustc_ast::ast::InlineAsmTemplatePiece::String(end.into()));
46
47    cx.codegen_global_asm(&template_vec, &operands, options, line_spans);
48}
49
50fn inline_to_global_operand<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
51    cx: &'a Bx::CodegenCx,
52    instance: Instance<'tcx>,
53    op: &InlineAsmOperand<'tcx>,
54) -> GlobalAsmOperandRef<'tcx> {
55    match op {
56        InlineAsmOperand::Const { value } => {
57            let const_value = instance
58                .instantiate_mir_and_normalize_erasing_regions(
59                    cx.tcx(),
60                    cx.typing_env(),
61                    ty::EarlyBinder::bind(value.const_),
62                )
63                .eval(cx.tcx(), cx.typing_env(), value.span)
64                .expect("erroneous constant missed by mono item collection");
65
66            let mono_type = instance.instantiate_mir_and_normalize_erasing_regions(
67                cx.tcx(),
68                cx.typing_env(),
69                ty::EarlyBinder::bind(value.ty()),
70            );
71
72            let string = common::asm_const_to_str(
73                cx.tcx(),
74                value.span,
75                const_value,
76                cx.layout_of(mono_type),
77            );
78
79            GlobalAsmOperandRef::Const { string }
80        }
81        InlineAsmOperand::SymFn { value } => {
82            let mono_type = instance.instantiate_mir_and_normalize_erasing_regions(
83                cx.tcx(),
84                cx.typing_env(),
85                ty::EarlyBinder::bind(value.ty()),
86            );
87
88            let instance = match mono_type.kind() {
89                &ty::FnDef(def_id, args) => Instance::new(def_id, args),
90                _ => bug!("asm sym is not a function"),
91            };
92
93            GlobalAsmOperandRef::SymFn { instance }
94        }
95        InlineAsmOperand::SymStatic { def_id } => {
96            GlobalAsmOperandRef::SymStatic { def_id: *def_id }
97        }
98        InlineAsmOperand::In { .. }
99        | InlineAsmOperand::Out { .. }
100        | InlineAsmOperand::InOut { .. }
101        | InlineAsmOperand::Label { .. } => {
102            bug!("invalid operand type for naked_asm!")
103        }
104    }
105}
106
107enum AsmBinaryFormat {
108    Elf,
109    Macho,
110    Coff,
111    Wasm,
112}
113
114impl AsmBinaryFormat {
115    fn from_target(target: &rustc_target::spec::Target) -> Self {
116        if target.is_like_windows {
117            Self::Coff
118        } else if target.is_like_osx {
119            Self::Macho
120        } else if target.is_like_wasm {
121            Self::Wasm
122        } else {
123            Self::Elf
124        }
125    }
126}
127
128fn prefix_and_suffix<'tcx>(
129    tcx: TyCtxt<'tcx>,
130    instance: Instance<'tcx>,
131    asm_name: &str,
132    item_data: &MonoItemData,
133    fn_abi: &FnAbi<'tcx, Ty<'tcx>>,
134) -> (String, String) {
135    use std::fmt::Write;
136
137    let asm_binary_format = AsmBinaryFormat::from_target(&tcx.sess.target);
138
139    let is_arm = tcx.sess.target.arch == "arm";
140    let is_thumb = tcx.sess.unstable_target_features.contains(&sym::thumb_mode);
141
142    let attrs = tcx.codegen_fn_attrs(instance.def_id());
143    let link_section = attrs.link_section.map(|symbol| symbol.as_str().to_string());
144
145    // function alignment can be set globally with the `-Zmin-function-alignment=<n>` flag;
146    // the alignment from a `#[repr(align(<n>))]` is used if it specifies a higher alignment.
147    // if no alignment is specified, an alignment of 4 bytes is used.
148    let min_function_alignment = tcx.sess.opts.unstable_opts.min_function_alignment;
149    let align = Ord::max(min_function_alignment, attrs.alignment).map(|a| a.bytes()).unwrap_or(4);
150
151    // In particular, `.arm` can also be written `.code 32` and `.thumb` as `.code 16`.
152    let (arch_prefix, arch_suffix) = if is_arm {
153        (
154            match attrs.instruction_set {
155                None => match is_thumb {
156                    true => ".thumb\n.thumb_func",
157                    false => ".arm",
158                },
159                Some(InstructionSetAttr::ArmT32) => ".thumb\n.thumb_func",
160                Some(InstructionSetAttr::ArmA32) => ".arm",
161            },
162            match is_thumb {
163                true => ".thumb",
164                false => ".arm",
165            },
166        )
167    } else {
168        ("", "")
169    };
170
171    let emit_fatal = |msg| tcx.dcx().span_fatal(tcx.def_span(instance.def_id()), msg);
172
173    // see https://godbolt.org/z/cPK4sxKor.
174    let write_linkage = |w: &mut String| -> std::fmt::Result {
175        match item_data.linkage {
176            Linkage::External => {
177                writeln!(w, ".globl {asm_name}")?;
178            }
179            Linkage::LinkOnceAny | Linkage::LinkOnceODR | Linkage::WeakAny | Linkage::WeakODR => {
180                match asm_binary_format {
181                    AsmBinaryFormat::Elf | AsmBinaryFormat::Coff | AsmBinaryFormat::Wasm => {
182                        writeln!(w, ".weak {asm_name}")?;
183                    }
184                    AsmBinaryFormat::Macho => {
185                        writeln!(w, ".globl {asm_name}")?;
186                        writeln!(w, ".weak_definition {asm_name}")?;
187                    }
188                }
189            }
190            Linkage::Internal => {
191                // write nothing
192            }
193            Linkage::Common => emit_fatal("Functions may not have common linkage"),
194            Linkage::AvailableExternally => {
195                // this would make the function equal an extern definition
196                emit_fatal("Functions may not have available_externally linkage")
197            }
198            Linkage::ExternalWeak => {
199                // FIXME: actually this causes a SIGILL in LLVM
200                emit_fatal("Functions may not have external weak linkage")
201            }
202        }
203
204        Ok(())
205    };
206
207    let mut begin = String::new();
208    let mut end = String::new();
209    match asm_binary_format {
210        AsmBinaryFormat::Elf => {
211            let section = link_section.unwrap_or(format!(".text.{asm_name}"));
212
213            let progbits = match is_arm {
214                true => "%progbits",
215                false => "@progbits",
216            };
217
218            let function = match is_arm {
219                true => "%function",
220                false => "@function",
221            };
222
223            writeln!(begin, ".pushsection {section},\"ax\", {progbits}").unwrap();
224            writeln!(begin, ".balign {align}").unwrap();
225            write_linkage(&mut begin).unwrap();
226            if let Visibility::Hidden = item_data.visibility {
227                writeln!(begin, ".hidden {asm_name}").unwrap();
228            }
229            writeln!(begin, ".type {asm_name}, {function}").unwrap();
230            if !arch_prefix.is_empty() {
231                writeln!(begin, "{}", arch_prefix).unwrap();
232            }
233            writeln!(begin, "{asm_name}:").unwrap();
234
235            writeln!(end).unwrap();
236            writeln!(end, ".size {asm_name}, . - {asm_name}").unwrap();
237            writeln!(end, ".popsection").unwrap();
238            if !arch_suffix.is_empty() {
239                writeln!(end, "{}", arch_suffix).unwrap();
240            }
241        }
242        AsmBinaryFormat::Macho => {
243            let section = link_section.unwrap_or("__TEXT,__text".to_string());
244            writeln!(begin, ".pushsection {},regular,pure_instructions", section).unwrap();
245            writeln!(begin, ".balign {align}").unwrap();
246            write_linkage(&mut begin).unwrap();
247            if let Visibility::Hidden = item_data.visibility {
248                writeln!(begin, ".private_extern {asm_name}").unwrap();
249            }
250            writeln!(begin, "{asm_name}:").unwrap();
251
252            writeln!(end).unwrap();
253            writeln!(end, ".popsection").unwrap();
254            if !arch_suffix.is_empty() {
255                writeln!(end, "{}", arch_suffix).unwrap();
256            }
257        }
258        AsmBinaryFormat::Coff => {
259            let section = link_section.unwrap_or(format!(".text.{asm_name}"));
260            writeln!(begin, ".pushsection {},\"xr\"", section).unwrap();
261            writeln!(begin, ".balign {align}").unwrap();
262            write_linkage(&mut begin).unwrap();
263            writeln!(begin, ".def {asm_name}").unwrap();
264            writeln!(begin, ".scl 2").unwrap();
265            writeln!(begin, ".type 32").unwrap();
266            writeln!(begin, ".endef {asm_name}").unwrap();
267            writeln!(begin, "{asm_name}:").unwrap();
268
269            writeln!(end).unwrap();
270            writeln!(end, ".popsection").unwrap();
271            if !arch_suffix.is_empty() {
272                writeln!(end, "{}", arch_suffix).unwrap();
273            }
274        }
275        AsmBinaryFormat::Wasm => {
276            let section = link_section.unwrap_or(format!(".text.{asm_name}"));
277
278            writeln!(begin, ".section {section},\"\",@").unwrap();
279            // wasm functions cannot be aligned, so skip
280            write_linkage(&mut begin).unwrap();
281            if let Visibility::Hidden = item_data.visibility {
282                writeln!(begin, ".hidden {asm_name}").unwrap();
283            }
284            writeln!(begin, ".type {asm_name}, @function").unwrap();
285            if !arch_prefix.is_empty() {
286                writeln!(begin, "{}", arch_prefix).unwrap();
287            }
288            writeln!(begin, "{asm_name}:").unwrap();
289            writeln!(
290                begin,
291                ".functype {asm_name} {}",
292                wasm_functype(tcx, fn_abi, instance.def_id())
293            )
294            .unwrap();
295
296            writeln!(end).unwrap();
297            // .size is ignored for function symbols, so we can skip it
298            writeln!(end, "end_function").unwrap();
299        }
300    }
301
302    (begin, end)
303}
304
305/// The webassembly type signature for the given function.
306///
307/// Used by the `.functype` directive on wasm targets.
308fn wasm_functype<'tcx>(tcx: TyCtxt<'tcx>, fn_abi: &FnAbi<'tcx, Ty<'tcx>>, def_id: DefId) -> String {
309    let mut signature = String::with_capacity(64);
310
311    let ptr_type = match tcx.data_layout.pointer_size.bits() {
312        32 => "i32",
313        64 => "i64",
314        other => bug!("wasm pointer size cannot be {other} bits"),
315    };
316
317    // FIXME: remove this once the wasm32-unknown-unknown ABI is fixed
318    // please also add `wasm32-unknown-unknown` back in `tests/assembly/wasm32-naked-fn.rs`
319    // basically the commit introducing this comment should be reverted
320    if let PassMode::Pair { .. } = fn_abi.ret.mode {
321        let _ = WasmCAbi::Legacy;
322        span_bug!(
323            tcx.def_span(def_id),
324            "cannot return a pair (the wasm32-unknown-unknown ABI is broken, see https://github.com/rust-lang/rust/issues/115666"
325        );
326    }
327
328    let hidden_return = matches!(fn_abi.ret.mode, PassMode::Indirect { .. });
329
330    signature.push('(');
331
332    if hidden_return {
333        signature.push_str(ptr_type);
334        if !fn_abi.args.is_empty() {
335            signature.push_str(", ");
336        }
337    }
338
339    let mut it = fn_abi.args.iter().peekable();
340    while let Some(arg_abi) = it.next() {
341        wasm_type(tcx, &mut signature, arg_abi, ptr_type, def_id);
342        if it.peek().is_some() {
343            signature.push_str(", ");
344        }
345    }
346
347    signature.push_str(") -> (");
348
349    if !hidden_return {
350        wasm_type(tcx, &mut signature, &fn_abi.ret, ptr_type, def_id);
351    }
352
353    signature.push(')');
354
355    signature
356}
357
358fn wasm_type<'tcx>(
359    tcx: TyCtxt<'tcx>,
360    signature: &mut String,
361    arg_abi: &ArgAbi<'_, Ty<'tcx>>,
362    ptr_type: &'static str,
363    def_id: DefId,
364) {
365    match arg_abi.mode {
366        PassMode::Ignore => { /* do nothing */ }
367        PassMode::Direct(_) => {
368            let direct_type = match arg_abi.layout.backend_repr {
369                BackendRepr::Scalar(scalar) => wasm_primitive(scalar.primitive(), ptr_type),
370                BackendRepr::Vector { .. } => "v128",
371                BackendRepr::Memory { .. } => {
372                    // FIXME: remove this branch once the wasm32-unknown-unknown ABI is fixed
373                    let _ = WasmCAbi::Legacy;
374                    span_bug!(
375                        tcx.def_span(def_id),
376                        "cannot use memory args (the wasm32-unknown-unknown ABI is broken, see https://github.com/rust-lang/rust/issues/115666"
377                    );
378                }
379                other => unreachable!("unexpected BackendRepr: {:?}", other),
380            };
381
382            signature.push_str(direct_type);
383        }
384        PassMode::Pair(_, _) => match arg_abi.layout.backend_repr {
385            BackendRepr::ScalarPair(a, b) => {
386                signature.push_str(wasm_primitive(a.primitive(), ptr_type));
387                signature.push_str(", ");
388                signature.push_str(wasm_primitive(b.primitive(), ptr_type));
389            }
390            other => unreachable!("{other:?}"),
391        },
392        PassMode::Cast { pad_i32, ref cast } => {
393            // For wasm, Cast is used for single-field primitive wrappers like `struct Wrapper(i64);`
394            assert!(!pad_i32, "not currently used by wasm calling convention");
395            assert!(cast.prefix[0].is_none(), "no prefix");
396            assert_eq!(cast.rest.total, arg_abi.layout.size, "single item");
397
398            let wrapped_wasm_type = match cast.rest.unit.kind {
399                RegKind::Integer => match cast.rest.unit.size.bytes() {
400                    ..=4 => "i32",
401                    ..=8 => "i64",
402                    _ => ptr_type,
403                },
404                RegKind::Float => match cast.rest.unit.size.bytes() {
405                    ..=4 => "f32",
406                    ..=8 => "f64",
407                    _ => ptr_type,
408                },
409                RegKind::Vector => "v128",
410            };
411
412            signature.push_str(wrapped_wasm_type);
413        }
414        PassMode::Indirect { .. } => signature.push_str(ptr_type),
415    }
416}
417
418fn wasm_primitive(primitive: Primitive, ptr_type: &'static str) -> &'static str {
419    match primitive {
420        Primitive::Int(integer, _) => match integer {
421            Integer::I8 | Integer::I16 | Integer::I32 => "i32",
422            Integer::I64 => "i64",
423            Integer::I128 => "i64, i64",
424        },
425        Primitive::Float(float) => match float {
426            Float::F16 | Float::F32 => "f32",
427            Float::F64 => "f64",
428            Float::F128 => "i64, i64",
429        },
430        Primitive::Pointer(_) => ptr_type,
431    }
432}