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 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 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 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 }
193 Linkage::Common => emit_fatal("Functions may not have common linkage"),
194 Linkage::AvailableExternally => {
195 emit_fatal("Functions may not have available_externally linkage")
197 }
198 Linkage::ExternalWeak => {
199 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 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 writeln!(end, "end_function").unwrap();
299 }
300 }
301
302 (begin, end)
303}
304
305fn 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 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 => { }
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 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 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}