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::{BinaryFormat, 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
107fn prefix_and_suffix<'tcx>(
108    tcx: TyCtxt<'tcx>,
109    instance: Instance<'tcx>,
110    asm_name: &str,
111    item_data: &MonoItemData,
112    fn_abi: &FnAbi<'tcx, Ty<'tcx>>,
113) -> (String, String) {
114    use std::fmt::Write;
115
116    let asm_binary_format = &tcx.sess.target.binary_format;
117
118    let is_arm = tcx.sess.target.arch == "arm";
119    let is_thumb = tcx.sess.unstable_target_features.contains(&sym::thumb_mode);
120
121    let attrs = tcx.codegen_fn_attrs(instance.def_id());
122    let link_section = attrs.link_section.map(|symbol| symbol.as_str().to_string());
123
124    // function alignment can be set globally with the `-Zmin-function-alignment=<n>` flag;
125    // the alignment from a `#[repr(align(<n>))]` is used if it specifies a higher alignment.
126    // if no alignment is specified, an alignment of 4 bytes is used.
127    let min_function_alignment = tcx.sess.opts.unstable_opts.min_function_alignment;
128    let align_bytes =
129        Ord::max(min_function_alignment, attrs.alignment).map(|a| a.bytes()).unwrap_or(4);
130
131    // In particular, `.arm` can also be written `.code 32` and `.thumb` as `.code 16`.
132    let (arch_prefix, arch_suffix) = if is_arm {
133        (
134            match attrs.instruction_set {
135                None => match is_thumb {
136                    true => ".thumb\n.thumb_func",
137                    false => ".arm",
138                },
139                Some(InstructionSetAttr::ArmT32) => ".thumb\n.thumb_func",
140                Some(InstructionSetAttr::ArmA32) => ".arm",
141            },
142            match is_thumb {
143                true => ".thumb",
144                false => ".arm",
145            },
146        )
147    } else {
148        ("", "")
149    };
150
151    let emit_fatal = |msg| tcx.dcx().span_fatal(tcx.def_span(instance.def_id()), msg);
152
153    // see https://godbolt.org/z/cPK4sxKor.
154    let write_linkage = |w: &mut String| -> std::fmt::Result {
155        match item_data.linkage {
156            Linkage::External => {
157                writeln!(w, ".globl {asm_name}")?;
158            }
159            Linkage::LinkOnceAny | Linkage::LinkOnceODR | Linkage::WeakAny | Linkage::WeakODR => {
160                match asm_binary_format {
161                    BinaryFormat::Elf | BinaryFormat::Coff | BinaryFormat::Wasm => {
162                        writeln!(w, ".weak {asm_name}")?;
163                    }
164                    BinaryFormat::Xcoff => {
165                        // FIXME: there is currently no way of defining a weak symbol in inline assembly
166                        // for AIX. See https://github.com/llvm/llvm-project/issues/130269
167                        emit_fatal(
168                            "cannot create weak symbols from inline assembly for this target",
169                        )
170                    }
171                    BinaryFormat::MachO => {
172                        writeln!(w, ".globl {asm_name}")?;
173                        writeln!(w, ".weak_definition {asm_name}")?;
174                    }
175                }
176            }
177            Linkage::Internal => {
178                // write nothing
179            }
180            Linkage::Common => emit_fatal("Functions may not have common linkage"),
181            Linkage::AvailableExternally => {
182                // this would make the function equal an extern definition
183                emit_fatal("Functions may not have available_externally linkage")
184            }
185            Linkage::ExternalWeak => {
186                // FIXME: actually this causes a SIGILL in LLVM
187                emit_fatal("Functions may not have external weak linkage")
188            }
189        }
190
191        Ok(())
192    };
193
194    let mut begin = String::new();
195    let mut end = String::new();
196    match asm_binary_format {
197        BinaryFormat::Elf => {
198            let section = link_section.unwrap_or(format!(".text.{asm_name}"));
199
200            let progbits = match is_arm {
201                true => "%progbits",
202                false => "@progbits",
203            };
204
205            let function = match is_arm {
206                true => "%function",
207                false => "@function",
208            };
209
210            writeln!(begin, ".pushsection {section},\"ax\", {progbits}").unwrap();
211            writeln!(begin, ".balign {align_bytes}").unwrap();
212            write_linkage(&mut begin).unwrap();
213            if let Visibility::Hidden = item_data.visibility {
214                writeln!(begin, ".hidden {asm_name}").unwrap();
215            }
216            writeln!(begin, ".type {asm_name}, {function}").unwrap();
217            if !arch_prefix.is_empty() {
218                writeln!(begin, "{}", arch_prefix).unwrap();
219            }
220            writeln!(begin, "{asm_name}:").unwrap();
221
222            writeln!(end).unwrap();
223            writeln!(end, ".size {asm_name}, . - {asm_name}").unwrap();
224            writeln!(end, ".popsection").unwrap();
225            if !arch_suffix.is_empty() {
226                writeln!(end, "{}", arch_suffix).unwrap();
227            }
228        }
229        BinaryFormat::MachO => {
230            let section = link_section.unwrap_or("__TEXT,__text".to_string());
231            writeln!(begin, ".pushsection {},regular,pure_instructions", section).unwrap();
232            writeln!(begin, ".balign {align_bytes}").unwrap();
233            write_linkage(&mut begin).unwrap();
234            if let Visibility::Hidden = item_data.visibility {
235                writeln!(begin, ".private_extern {asm_name}").unwrap();
236            }
237            writeln!(begin, "{asm_name}:").unwrap();
238
239            writeln!(end).unwrap();
240            writeln!(end, ".popsection").unwrap();
241            if !arch_suffix.is_empty() {
242                writeln!(end, "{}", arch_suffix).unwrap();
243            }
244        }
245        BinaryFormat::Coff => {
246            let section = link_section.unwrap_or(format!(".text.{asm_name}"));
247            writeln!(begin, ".pushsection {},\"xr\"", section).unwrap();
248            writeln!(begin, ".balign {align_bytes}").unwrap();
249            write_linkage(&mut begin).unwrap();
250            writeln!(begin, ".def {asm_name}").unwrap();
251            writeln!(begin, ".scl 2").unwrap();
252            writeln!(begin, ".type 32").unwrap();
253            writeln!(begin, ".endef").unwrap();
254            writeln!(begin, "{asm_name}:").unwrap();
255
256            writeln!(end).unwrap();
257            writeln!(end, ".popsection").unwrap();
258            if !arch_suffix.is_empty() {
259                writeln!(end, "{}", arch_suffix).unwrap();
260            }
261        }
262        BinaryFormat::Wasm => {
263            let section = link_section.unwrap_or(format!(".text.{asm_name}"));
264
265            writeln!(begin, ".section {section},\"\",@").unwrap();
266            // wasm functions cannot be aligned, so skip
267            write_linkage(&mut begin).unwrap();
268            if let Visibility::Hidden = item_data.visibility {
269                writeln!(begin, ".hidden {asm_name}").unwrap();
270            }
271            writeln!(begin, ".type {asm_name}, @function").unwrap();
272            if !arch_prefix.is_empty() {
273                writeln!(begin, "{}", arch_prefix).unwrap();
274            }
275            writeln!(begin, "{asm_name}:").unwrap();
276            writeln!(
277                begin,
278                ".functype {asm_name} {}",
279                wasm_functype(tcx, fn_abi, instance.def_id())
280            )
281            .unwrap();
282
283            writeln!(end).unwrap();
284            // .size is ignored for function symbols, so we can skip it
285            writeln!(end, "end_function").unwrap();
286        }
287        BinaryFormat::Xcoff => {
288            // the LLVM XCOFFAsmParser is extremely incomplete and does not implement many of the
289            // documented directives.
290            //
291            // - https://github.com/llvm/llvm-project/blob/1b25c0c4da968fe78921ce77736e5baef4db75e3/llvm/lib/MC/MCParser/XCOFFAsmParser.cpp
292            // - https://www.ibm.com/docs/en/ssw_aix_71/assembler/assembler_pdf.pdf
293            //
294            // Consequently, we try our best here but cannot do as good a job as for other binary
295            // formats.
296
297            // FIXME: start a section. `.csect` is not currently implemented in LLVM
298
299            // fun fact: according to the assembler documentation, .align takes an exponent,
300            // but LLVM only accepts powers of 2 (but does emit the exponent)
301            // so when we hand `.align 32` to LLVM, the assembly output will contain `.align 5`
302            writeln!(begin, ".align {}", align_bytes).unwrap();
303
304            write_linkage(&mut begin).unwrap();
305            if let Visibility::Hidden = item_data.visibility {
306                // FIXME apparently `.globl {asm_name}, hidden` is valid
307                // but due to limitations with `.weak` (see above) we can't really use that in general yet
308            }
309            writeln!(begin, "{asm_name}:").unwrap();
310
311            writeln!(end).unwrap();
312            // FIXME: end the section?
313        }
314    }
315
316    (begin, end)
317}
318
319/// The webassembly type signature for the given function.
320///
321/// Used by the `.functype` directive on wasm targets.
322fn wasm_functype<'tcx>(tcx: TyCtxt<'tcx>, fn_abi: &FnAbi<'tcx, Ty<'tcx>>, def_id: DefId) -> String {
323    let mut signature = String::with_capacity(64);
324
325    let ptr_type = match tcx.data_layout.pointer_size.bits() {
326        32 => "i32",
327        64 => "i64",
328        other => bug!("wasm pointer size cannot be {other} bits"),
329    };
330
331    // FIXME: remove this once the wasm32-unknown-unknown ABI is fixed
332    // please also add `wasm32-unknown-unknown` back in `tests/assembly/wasm32-naked-fn.rs`
333    // basically the commit introducing this comment should be reverted
334    if let PassMode::Pair { .. } = fn_abi.ret.mode {
335        let _ = WasmCAbi::Legacy { with_lint: true };
336        span_bug!(
337            tcx.def_span(def_id),
338            "cannot return a pair (the wasm32-unknown-unknown ABI is broken, see https://github.com/rust-lang/rust/issues/115666"
339        );
340    }
341
342    let hidden_return = matches!(fn_abi.ret.mode, PassMode::Indirect { .. });
343
344    signature.push('(');
345
346    if hidden_return {
347        signature.push_str(ptr_type);
348        if !fn_abi.args.is_empty() {
349            signature.push_str(", ");
350        }
351    }
352
353    let mut it = fn_abi.args.iter().peekable();
354    while let Some(arg_abi) = it.next() {
355        wasm_type(tcx, &mut signature, arg_abi, ptr_type, def_id);
356        if it.peek().is_some() {
357            signature.push_str(", ");
358        }
359    }
360
361    signature.push_str(") -> (");
362
363    if !hidden_return {
364        wasm_type(tcx, &mut signature, &fn_abi.ret, ptr_type, def_id);
365    }
366
367    signature.push(')');
368
369    signature
370}
371
372fn wasm_type<'tcx>(
373    tcx: TyCtxt<'tcx>,
374    signature: &mut String,
375    arg_abi: &ArgAbi<'_, Ty<'tcx>>,
376    ptr_type: &'static str,
377    def_id: DefId,
378) {
379    match arg_abi.mode {
380        PassMode::Ignore => { /* do nothing */ }
381        PassMode::Direct(_) => {
382            let direct_type = match arg_abi.layout.backend_repr {
383                BackendRepr::Scalar(scalar) => wasm_primitive(scalar.primitive(), ptr_type),
384                BackendRepr::SimdVector { .. } => "v128",
385                BackendRepr::Memory { .. } => {
386                    // FIXME: remove this branch once the wasm32-unknown-unknown ABI is fixed
387                    let _ = WasmCAbi::Legacy { with_lint: true };
388                    span_bug!(
389                        tcx.def_span(def_id),
390                        "cannot use memory args (the wasm32-unknown-unknown ABI is broken, see https://github.com/rust-lang/rust/issues/115666"
391                    );
392                }
393                other => unreachable!("unexpected BackendRepr: {:?}", other),
394            };
395
396            signature.push_str(direct_type);
397        }
398        PassMode::Pair(_, _) => match arg_abi.layout.backend_repr {
399            BackendRepr::ScalarPair(a, b) => {
400                signature.push_str(wasm_primitive(a.primitive(), ptr_type));
401                signature.push_str(", ");
402                signature.push_str(wasm_primitive(b.primitive(), ptr_type));
403            }
404            other => unreachable!("{other:?}"),
405        },
406        PassMode::Cast { pad_i32, ref cast } => {
407            // For wasm, Cast is used for single-field primitive wrappers like `struct Wrapper(i64);`
408            assert!(!pad_i32, "not currently used by wasm calling convention");
409            assert!(cast.prefix[0].is_none(), "no prefix");
410            assert_eq!(cast.rest.total, arg_abi.layout.size, "single item");
411
412            let wrapped_wasm_type = match cast.rest.unit.kind {
413                RegKind::Integer => match cast.rest.unit.size.bytes() {
414                    ..=4 => "i32",
415                    ..=8 => "i64",
416                    _ => ptr_type,
417                },
418                RegKind::Float => match cast.rest.unit.size.bytes() {
419                    ..=4 => "f32",
420                    ..=8 => "f64",
421                    _ => ptr_type,
422                },
423                RegKind::Vector => "v128",
424            };
425
426            signature.push_str(wrapped_wasm_type);
427        }
428        PassMode::Indirect { .. } => signature.push_str(ptr_type),
429    }
430}
431
432fn wasm_primitive(primitive: Primitive, ptr_type: &'static str) -> &'static str {
433    match primitive {
434        Primitive::Int(integer, _) => match integer {
435            Integer::I8 | Integer::I16 | Integer::I32 => "i32",
436            Integer::I64 => "i64",
437            Integer::I128 => "i64, i64",
438        },
439        Primitive::Float(float) => match float {
440            Float::F16 | Float::F32 => "f32",
441            Float::F64 => "f64",
442            Float::F128 => "i64, i64",
443        },
444        Primitive::Pointer(_) => ptr_type,
445    }
446}