Odin's New Inline Assembly Templates

gingerBill1 pts0 comments

Inline asm Templates Overview | Odin Programming Language Note: Currently amd64 targets only (e.g. windows_amd64, linux_amd64, darwin_amd64).

Overview #<br>An asm template is a callable entity, instantiated in place at each call like a<br>forced-inline procedure. It is not a statement block spliced into a surrounding<br>procedure; rather, it behaves like an intrinsic. Some platform-specific intrinsics<br>will be replaced by this system in the near future.<br>The general instruction [operand{, operand}] form is intended as a universal<br>syntax across instruction set architectures: every ISA shares this common grammar<br>while still exposing its own instructions and registers. The approach is modeled on<br>Go&rsquo;s Plan 9–derived assembler, which likewise uses one syntax across all its targets<br>(Go&rsquo;s assembler guide, the Plan 9 assembler manual).<br>That syntax originated with Plan 9 (Ken Thompson&rsquo;s toolchain) and was carried into Go.<br>Odin&rsquo;s inline assembly uses a context-free grammar, but this does not mean any<br>mnemonics are shared across ISAs—only the syntax itself is.<br>Declaration #<br>name :: asm(params) -> (results) [bindings] {<br>body

params - input operands. Plain names and types.<br>results - output operands. Plain names and types.<br>bindings - ties, pins, scratch, width-views, clobbers, and effects.<br>body - the instruction stream.<br>Both -> (results) and the [bindings] block are optional.<br>The body uses Intel operand order (dst, src); the backend lowers per-target.<br>Physical registers take a % sigil (%rax); parameter and scratch names are<br>bare (r, acc).<br>Results may be left unbound at the call site; the compiler ignores the unused<br>ones implicitly, so a template whose results are ABI artifacts need not be<br>destructured.<br>mfence :: asm() [ #volatile, #clobber memory ] { mfence }<br>// NOTE: `#volatile` and `#clobber memory` are both inferred here from<br>// the use of `mfence`, but are written for clarity.

Parameter types #<br>A parameter type is one of: integer, float, boolean, pointer, multi-pointer, or<br>#simd[N]T.<br>A $name parameter is a compile-time immediate ($ctrl: u8). It must not be<br>pointer-like, and its value is only known—and only range-checked—at<br>instantiation.<br>Bindings #<br>The [...] block holds everything that is not a plain input or output name.<br>Ties and pins are edges onto names in the signature; scratch, width-views, and<br>clobbers are declared in the block directly.<br>FormMeaningin -> outtie: out is read-write, sharing in&rsquo;s registername = %regpin name to a physical registerin -> out = %regin/out pinned to a fixed registername: Tscratch register of type Tname: T = %regscratch register of type T pinned to a physical registerview: T = srcwidth-view of src&rsquo;s register at width T#clobber xclobber a register, flags, or memory#volatilemarks a whole template as volatile#align_stackforces stack realignment on entry to the templateThe right-hand side disambiguates the two = forms: = %reg (a register) is a<br>pin; = src (a name) is a width-view of another operand. A bare -> leaves the<br>register to the compiler, while = %reg pins to a specific register for the<br>target platform.<br>Scratch declarations are template-lifetime registers, allocated once. They live<br>in the binding block, not the body—there is no block scope in a template.<br>Registers #<br>Explicit registers are prefixed with % to prevent namespace collisions with<br>user-provided parameters and with other global constants in parent scopes.<br>Register names are the target&rsquo;s own (e.g. %rax, %xmm0, %r11, %al on AMD64),<br>named specifically for each platform rather than given generalized names.<br>Depending on the template, it may be common to use explicit registers<br>everywhere, or common to use scratch parameters instead.<br>Ties #<br>in -> out binds an input and an output to one register. It lowers to a<br>read-write operand (+r). A tie with a pin (in -> out = %rax) fixes the<br>register; a tie without one lets the allocator choose.<br>add_one :: asm(x: u64) -> (r: u64) [ x -> r ] { inc r }

Pins #<br>name = %reg forces a specific physical register. Two operands may pin the same<br>register (an in-out that needs no tie):<br>divmod_u64 :: asm(n: u64, d: u64) -> (quo, rem: u64) [<br>n -> quo = %rax,<br>rem = %rdx,<br>#clobber flags,<br>] {<br>xor %rdx, %rdx<br>div d

Scratch #<br>name: T is a working register whose class comes from T (i64 → GP,<br>#simd[4]f32 → vector). Unpinned scratch is early-clobbered—it can never<br>alias an input. Pin scratch with name: T = %reg for a fixed register, or use<br>#clobber %reg if it is only trashed, not named.<br>Width-views #<br>view: T = src is a second name for src&rsquo;s register, seen at width T.<br>One register, two widths; with no pin, the allocator stays free. It is<br>integer-only, and T must be narrower than src&rsquo;s width (a view exists to name<br>a sub-register). For the setcc-then-arithmetic idiom:<br>count_less :: asm(x: []i64, n: i64, thr: i64) -> (count: i64) [<br>acc: i64,<br>pred: i64,<br>predb: u8 = pred, // low-8 view of pred<br>i: i64,<br>#clobber flags,<br>#clobber memory,<br>] {<br>// ... setl predb ; add acc, pred...

register name rsquo scratch width clobber

Related Articles