rustc_codegen_ssa/mir/
naked_asm.rs

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