|
| 1 | +# Inline assembly |
| 2 | + |
| 3 | +<!-- toc --> |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +Inline assembly in rustc mostly revolves around taking an `asm!` macro invocation and plumbing it |
| 8 | +through all of the compiler layers down to LLVM codegen. Throughout the various stages, an |
| 9 | +`InlineAsm` generally consists of 3 components: |
| 10 | + |
| 11 | +- The template string, which is stored as an array of `InlineAsmTemplatePiece`. Each piece |
| 12 | +represents either a literal or a placeholder for an operand (just like format strings). |
| 13 | +```rust |
| 14 | +pub enum InlineAsmTemplatePiece { |
| 15 | + String(String), |
| 16 | + Placeholder { operand_idx: usize, modifier: Option<char>, span: Span }, |
| 17 | +} |
| 18 | +``` |
| 19 | + |
| 20 | +- The list of operands to the `asm!` (`in`, `[late]out`, `in[late]out`, `sym`, `const`). These are |
| 21 | +represented differently at each stage of lowering, but follow a common pattern: |
| 22 | + - `in`, `out` and `inout` all have an associated register class (`reg`) or explicit register |
| 23 | +(`"eax"`). |
| 24 | + - `inout` has 2 forms: one with a single expression that is both read from and written to, and |
| 25 | +one with two separate expressions for the input and output parts. |
| 26 | + - `out` and `inout` have a `late` flag (`lateout` / `inlateout`) to indicate that the register |
| 27 | +allocator is allowed to reuse an input register for this output. |
| 28 | + - `out` and the split variant of `inout` allow `_` to be specified for an output, which means |
| 29 | +that the output is discarded. This is used to allocate scratch registers for assembly code. |
| 30 | + - `const` refers to an anonymous constants and generally works like an inline const. |
| 31 | + - `sym` is a bit special since it only accepts a path expression, which must point to a `static` |
| 32 | +or a `fn`. |
| 33 | + |
| 34 | +- The options set at the end of the `asm!` macro. The only ones that are of particular interest to |
| 35 | +rustc are `NORETURN` which makes `asm!` return `!` instead of `()`, and `RAW` which disables format |
| 36 | +string parsing. The remaining options are mostly passed through to LLVM with little processing. |
| 37 | +```rust |
| 38 | +bitflags::bitflags! { |
| 39 | + pub struct InlineAsmOptions: u16 { |
| 40 | + const PURE = 1 << 0; |
| 41 | + const NOMEM = 1 << 1; |
| 42 | + const READONLY = 1 << 2; |
| 43 | + const PRESERVES_FLAGS = 1 << 3; |
| 44 | + const NORETURN = 1 << 4; |
| 45 | + const NOSTACK = 1 << 5; |
| 46 | + const ATT_SYNTAX = 1 << 6; |
| 47 | + const RAW = 1 << 7; |
| 48 | + const MAY_UNWIND = 1 << 8; |
| 49 | + } |
| 50 | +} |
| 51 | +``` |
| 52 | + |
| 53 | +## AST |
| 54 | + |
| 55 | +`InlineAsm` is represented as an expression in the AST: |
| 56 | + |
| 57 | +```rust |
| 58 | +pub struct InlineAsm { |
| 59 | + pub template: Vec<InlineAsmTemplatePiece>, |
| 60 | + pub template_strs: Box<[(Symbol, Option<Symbol>, Span)]>, |
| 61 | + pub operands: Vec<(InlineAsmOperand, Span)>, |
| 62 | + pub clobber_abi: Option<(Symbol, Span)>, |
| 63 | + pub options: InlineAsmOptions, |
| 64 | + pub line_spans: Vec<Span>, |
| 65 | +} |
| 66 | + |
| 67 | +pub enum InlineAsmRegOrRegClass { |
| 68 | + Reg(Symbol), |
| 69 | + RegClass(Symbol), |
| 70 | +} |
| 71 | + |
| 72 | +pub enum InlineAsmOperand { |
| 73 | + In { |
| 74 | + reg: InlineAsmRegOrRegClass, |
| 75 | + expr: P<Expr>, |
| 76 | + }, |
| 77 | + Out { |
| 78 | + reg: InlineAsmRegOrRegClass, |
| 79 | + late: bool, |
| 80 | + expr: Option<P<Expr>>, |
| 81 | + }, |
| 82 | + InOut { |
| 83 | + reg: InlineAsmRegOrRegClass, |
| 84 | + late: bool, |
| 85 | + expr: P<Expr>, |
| 86 | + }, |
| 87 | + SplitInOut { |
| 88 | + reg: InlineAsmRegOrRegClass, |
| 89 | + late: bool, |
| 90 | + in_expr: P<Expr>, |
| 91 | + out_expr: Option<P<Expr>>, |
| 92 | + }, |
| 93 | + Const { |
| 94 | + anon_const: AnonConst, |
| 95 | + }, |
| 96 | + Sym { |
| 97 | + expr: P<Expr>, |
| 98 | + }, |
| 99 | +} |
| 100 | +``` |
| 101 | + |
| 102 | +The `asm!` macro is implemented in `rustc_builtin_macros` and outputs an `InlineAsm` AST node. The |
| 103 | +template string is parsed using `fmt_macros`, positional and named operands are resolved to |
| 104 | +explicit operand indicies. Since target information is not available to macro invocations, |
| 105 | +validation of the registers and register classes is deferred to AST lowering. |
| 106 | + |
| 107 | +## HIR |
| 108 | + |
| 109 | +`InlineAsm` is represented as an expression in the HIR: |
| 110 | + |
| 111 | +```rust |
| 112 | +pub struct InlineAsm<'hir> { |
| 113 | + pub template: &'hir [InlineAsmTemplatePiece], |
| 114 | + pub template_strs: &'hir [(Symbol, Option<Symbol>, Span)], |
| 115 | + pub operands: &'hir [(InlineAsmOperand<'hir>, Span)], |
| 116 | + pub options: InlineAsmOptions, |
| 117 | + pub line_spans: &'hir [Span], |
| 118 | +} |
| 119 | + |
| 120 | +pub enum InlineAsmRegOrRegClass { |
| 121 | + Reg(InlineAsmReg), |
| 122 | + RegClass(InlineAsmRegClass), |
| 123 | +} |
| 124 | + |
| 125 | +pub enum InlineAsmOperand<'hir> { |
| 126 | + In { |
| 127 | + reg: InlineAsmRegOrRegClass, |
| 128 | + expr: Expr<'hir>, |
| 129 | + }, |
| 130 | + Out { |
| 131 | + reg: InlineAsmRegOrRegClass, |
| 132 | + late: bool, |
| 133 | + expr: Option<Expr<'hir>>, |
| 134 | + }, |
| 135 | + InOut { |
| 136 | + reg: InlineAsmRegOrRegClass, |
| 137 | + late: bool, |
| 138 | + expr: Expr<'hir>, |
| 139 | + }, |
| 140 | + SplitInOut { |
| 141 | + reg: InlineAsmRegOrRegClass, |
| 142 | + late: bool, |
| 143 | + in_expr: Expr<'hir>, |
| 144 | + out_expr: Option<Expr<'hir>>, |
| 145 | + }, |
| 146 | + Const { |
| 147 | + anon_const: AnonConst, |
| 148 | + }, |
| 149 | + Sym { |
| 150 | + expr: Expr<'hir>, |
| 151 | + }, |
| 152 | +} |
| 153 | +``` |
| 154 | + |
| 155 | +AST lowering is where `InlineAsmRegOrRegClass` is converted from `Symbol`s to an actual register or |
| 156 | +register class. If any modifiers are specified for a template string placeholder, these are |
| 157 | +validated against the set allowed for that operand type. Finally, explicit registers for inputs and |
| 158 | +outputs are checked for conflicts (same register used for different operands). |
| 159 | + |
| 160 | +## Type checking |
| 161 | + |
| 162 | +Each register class has a whitelist of types that it may be used with. After the types of all |
| 163 | +operands have been determined, the `intrinsicck` pass will check that these types are in the |
| 164 | +whitelist. It also checks that split `inout` operands have compatible types and that `const` |
| 165 | +operands are integers or floats. Suggestions are emitted where needed if a template modifier should |
| 166 | +be used for an operand based on the type that was passed into it. |
| 167 | + |
| 168 | +## THIR |
| 169 | + |
| 170 | +`InlineAsm` is represented as an expression in the THIR: |
| 171 | + |
| 172 | +```rust |
| 173 | +crate enum ExprKind<'tcx> { |
| 174 | + // [..] |
| 175 | + InlineAsm { |
| 176 | + template: &'tcx [InlineAsmTemplatePiece], |
| 177 | + operands: Box<[InlineAsmOperand<'tcx>]>, |
| 178 | + options: InlineAsmOptions, |
| 179 | + line_spans: &'tcx [Span], |
| 180 | + }, |
| 181 | +} |
| 182 | +crate enum InlineAsmOperand<'tcx> { |
| 183 | + In { |
| 184 | + reg: InlineAsmRegOrRegClass, |
| 185 | + expr: ExprId, |
| 186 | + }, |
| 187 | + Out { |
| 188 | + reg: InlineAsmRegOrRegClass, |
| 189 | + late: bool, |
| 190 | + expr: Option<ExprId>, |
| 191 | + }, |
| 192 | + InOut { |
| 193 | + reg: InlineAsmRegOrRegClass, |
| 194 | + late: bool, |
| 195 | + expr: ExprId, |
| 196 | + }, |
| 197 | + SplitInOut { |
| 198 | + reg: InlineAsmRegOrRegClass, |
| 199 | + late: bool, |
| 200 | + in_expr: ExprId, |
| 201 | + out_expr: Option<ExprId>, |
| 202 | + }, |
| 203 | + Const { |
| 204 | + value: &'tcx Const<'tcx>, |
| 205 | + span: Span, |
| 206 | + }, |
| 207 | + SymFn { |
| 208 | + expr: ExprId, |
| 209 | + }, |
| 210 | + SymStatic { |
| 211 | + def_id: DefId, |
| 212 | + }, |
| 213 | +} |
| 214 | +``` |
| 215 | + |
| 216 | +The only significant change compared to HIR is that `Sym` has been lowered to either a `SymFn` |
| 217 | +whose `expr` is a `Literal` ZST of the `fn`, or a `SymStatic` which points to the `DefId` of a |
| 218 | +`static`. |
| 219 | + |
| 220 | +## MIR |
| 221 | + |
| 222 | +`InlineAsm` is represented as a `Terminator` in the MIR: |
| 223 | + |
| 224 | +```rust |
| 225 | +pub enum TerminatorKind<'tcx> { |
| 226 | + // [..] |
| 227 | + |
| 228 | + /// Block ends with an inline assembly block. This is a terminator since |
| 229 | + /// inline assembly is allowed to diverge. |
| 230 | + InlineAsm { |
| 231 | + /// The template for the inline assembly, with placeholders. |
| 232 | + template: &'tcx [InlineAsmTemplatePiece], |
| 233 | + |
| 234 | + /// The operands for the inline assembly, as `Operand`s or `Place`s. |
| 235 | + operands: Vec<InlineAsmOperand<'tcx>>, |
| 236 | + |
| 237 | + /// Miscellaneous options for the inline assembly. |
| 238 | + options: InlineAsmOptions, |
| 239 | + |
| 240 | + /// Source spans for each line of the inline assembly code. These are |
| 241 | + /// used to map assembler errors back to the line in the source code. |
| 242 | + line_spans: &'tcx [Span], |
| 243 | + |
| 244 | + /// Destination block after the inline assembly returns, unless it is |
| 245 | + /// diverging (InlineAsmOptions::NORETURN). |
| 246 | + destination: Option<BasicBlock>, |
| 247 | + }, |
| 248 | +} |
| 249 | + |
| 250 | +pub enum InlineAsmOperand<'tcx> { |
| 251 | + In { |
| 252 | + reg: InlineAsmRegOrRegClass, |
| 253 | + value: Operand<'tcx>, |
| 254 | + }, |
| 255 | + Out { |
| 256 | + reg: InlineAsmRegOrRegClass, |
| 257 | + late: bool, |
| 258 | + place: Option<Place<'tcx>>, |
| 259 | + }, |
| 260 | + InOut { |
| 261 | + reg: InlineAsmRegOrRegClass, |
| 262 | + late: bool, |
| 263 | + in_value: Operand<'tcx>, |
| 264 | + out_place: Option<Place<'tcx>>, |
| 265 | + }, |
| 266 | + Const { |
| 267 | + value: Box<Constant<'tcx>>, |
| 268 | + }, |
| 269 | + SymFn { |
| 270 | + value: Box<Constant<'tcx>>, |
| 271 | + }, |
| 272 | + SymStatic { |
| 273 | + def_id: DefId, |
| 274 | + }, |
| 275 | +} |
| 276 | +``` |
| 277 | + |
| 278 | +As part of HAIR lowering, `InOut` and `SplitInOut` operands are lowered to a split form with a |
| 279 | +separate `in_value` and `out_place`. |
| 280 | + |
| 281 | +Semantically, the `InlineAsm` terminator is similar to the `Call` terminator except that it has |
| 282 | +multiple output places where a `Call` only has a single return place output. |
| 283 | + |
| 284 | +## Codegen |
| 285 | + |
| 286 | +Operands are lowered one more time before being passed to LLVM codegen: |
| 287 | + |
| 288 | +```rust |
| 289 | +pub enum InlineAsmOperandRef<'tcx, B: BackendTypes + ?Sized> { |
| 290 | + In { |
| 291 | + reg: InlineAsmRegOrRegClass, |
| 292 | + value: OperandRef<'tcx, B::Value>, |
| 293 | + }, |
| 294 | + Out { |
| 295 | + reg: InlineAsmRegOrRegClass, |
| 296 | + late: bool, |
| 297 | + place: Option<PlaceRef<'tcx, B::Value>>, |
| 298 | + }, |
| 299 | + InOut { |
| 300 | + reg: InlineAsmRegOrRegClass, |
| 301 | + late: bool, |
| 302 | + in_value: OperandRef<'tcx, B::Value>, |
| 303 | + out_place: Option<PlaceRef<'tcx, B::Value>>, |
| 304 | + }, |
| 305 | + Const { |
| 306 | + string: String, |
| 307 | + }, |
| 308 | + SymFn { |
| 309 | + instance: Instance<'tcx>, |
| 310 | + }, |
| 311 | + SymStatic { |
| 312 | + def_id: DefId, |
| 313 | + }, |
| 314 | +} |
| 315 | +``` |
| 316 | + |
| 317 | +The operands are lowered to LLVM operands and constraint codes as follow: |
| 318 | +- `out` and the output part of `inout` operands are added first, as required by LLVM. Late output |
| 319 | +operands have a `=` prefix added to their constraint code, non-late output operands have a `=&` |
| 320 | +prefix added to their constraint code. |
| 321 | +- `in` operands are added normally. |
| 322 | +- `inout` operands are tied to the matching output operand. |
| 323 | +- `sym` operands are passed as function pointers or pointers, using the `"s"` constraint. |
| 324 | +- `const` operands are formatted to a string and directly inserted in the template string. |
| 325 | + |
| 326 | +The template string is converted to LLVM form: |
| 327 | +- `$` characters are escaped as `$$`. |
| 328 | +- `const` operands are converted to strings and inserted directly. |
| 329 | +- Placeholders are formatted as `${X:M}` where `X` is the operand index and `M` is the modifier |
| 330 | +character. Modifiers are converted from the Rust form to the LLVM form. |
| 331 | + |
| 332 | +The various options are converted to clobber constraints or LLVM attributes, refer to the |
| 333 | +[RFC](https://github.com/Amanieu/rfcs/blob/inline-asm/text/0000-inline-asm.md#mapping-to-llvm-ir) |
| 334 | +for more details. |
| 335 | + |
| 336 | +Note that LLVM is sometimes rather picky about what types it accepts for certain constraint codes |
| 337 | +so we sometimes need to insert conversions to/from a supported type. See the target-specific |
| 338 | +ISelLowering.cpp files in LLVM for details of what types are supported for each register class. |
| 339 | + |
| 340 | +## Adding support for new architectures |
| 341 | + |
| 342 | +Adding inline assembly support to an architecture is mostly a matter of defining the registers and |
| 343 | +register classes for that architecture. All the definitions for register classes are located in |
| 344 | +`compiler/rustc_target/asm/`. |
| 345 | + |
| 346 | +Additionally you will need to implement lowering of these register classes to LLVM constraint codes |
| 347 | +in `compiler/rustc_codegen_llvm/asm.rs`. |
| 348 | + |
| 349 | +When adding a new architecture, make sure to cross-reference with the LLVM source code: |
| 350 | +- LLVM has restrictions on which types can be used with a particular constraint code. Refer to the |
| 351 | +`getRegForInlineAsmConstraint` function in `lib/Target/${ARCH}/${ARCH}ISelLowering.cpp`. |
| 352 | +- LLVM reserves certain registers for its internal use, which causes them to not be saved/restored |
| 353 | +properly around inline assembly blocks. These registers are listed in the `getReservedRegs` |
| 354 | +function in `lib/Target/${ARCH}/${ARCH}RegisterInfo.cpp`. Any "conditionally" reserved register |
| 355 | +such as the frame/base pointer must always be treated as reserved for Rust purposes because we |
| 356 | +can't know ahead of time whether a function will require a frame/base pointer. |
| 357 | + |
| 358 | +## Tests |
| 359 | + |
| 360 | +Various tests for inline assembly are available: |
| 361 | + |
| 362 | +- `src/test/assembly/asm` |
| 363 | +- `src/test/ui/asm` |
| 364 | +- `src/test/codegen/asm-*` |
| 365 | + |
| 366 | +Every architecture supported by inline assembly must have exhaustive tests in |
| 367 | +`src/test/assembly/asm` which test all combinations of register classes and types. |
0 commit comments