docs(asm): add Inline Assembly section to readme

Documents the `asm { … }` expression (template + `-> Type` / `= expr` operands +
clobbers), the §II.5 auto-naming rule (register pin → implicit name; echo form
rejected), the result-shape rule (0→void+volatile / 1→T / N→tuple), `#string`
multi-instruction templates, and top-level global asm + lib-less `extern`
call-into. Per the docs-track-changes rule (inline asm is a landed user-facing
feature). Examples are ones verified running in the corpus.
This commit is contained in:
agra
2026-06-15 22:28:10 +03:00
parent 4d75b9323c
commit b8800a234c
2 changed files with 63 additions and 2 deletions

View File

@@ -159,8 +159,8 @@ via lib-less `extern`). **Remaining feature gap:** `-> @place` write-through /
read-write / indirect-memory outputs (rejected at parse — Phase 2). Smaller
follow-ups: the comptime-call guard for global asm (`#run` into a module-asm
symbol should fail loud via dlsym-miss — pin a test), a JIT-vs-global-asm note
(`sx run` silently mishandles module-asm symbols; AOT is correct), the x86_64
syscall ir-only example, and a `readme.md` inline-asm section (docs-track-changes).
(`sx run` silently mishandles module-asm symbols; AOT is correct), and the x86_64
syscall ir-only example. `readme.md` now has an "Inline Assembly" section.
Known orthogonal bug: **issue 0137**`sx run` on a program with no `main`
segfaults (`src/target.zig:256-273`, unguarded JIT entry lookup). Pre-existing,

View File

@@ -429,6 +429,67 @@ Direct C header import:
};
```
### Inline Assembly
`asm` is an expression. The body is a brace block: a template string first, then
operands and an optional `clobbers(.…)` clause. Each operand is
`[name]? "constraint" <role>`, where the role is `-> Type` (a value output) or
`= expr` (an input). It compiles to an LLVM inline-asm call (AT&T syntax).
```sx
// one value output, two register-class inputs
add :: (a: i64, b: i64) -> i64 {
return asm { "add %[out], %[a], %[b]", [out] "=r" -> i64, [a] "r" = a, [b] "r" = b };
}
```
The `%[name]` in the template refers to an operand; `%%` is a literal `%`. An
operand pinned to a register (`"={rax}"`, `"{rdi}"`) is **auto-named after that
register**, so an explicit `[name]` is only needed for register-class (`=r`)
operands or to give a value a name distinct from its register. A label that just
echoes its register (`[rax] "={rax}"`) is rejected.
Outputs decide the result: **0** → `void` (and the asm must be `volatile`);
**1** → that type; **N** → a tuple, named by each operand's name.
```sx
// multiple value outputs → a destructurable tuple
split :: (x: u64) -> (lo: u64, hi: u64) {
return asm {
#string ASM
and %[l], %[x], #0xff
lsr %[h], %[x], #8
ASM,
[l] "=r" -> u64, [h] "=r" -> u64, [x] "r" = x,
};
}
lo, hi := split(0x1234); // (52, 18)
```
`asm volatile { … }` marks side effects (required when there are no outputs). A
multi-instruction template uses the `#string` heredoc (delivered verbatim — no
escape processing). `clobbers(.cc, .memory, .rax)` lists registers/flags the asm
trashes that aren't operands.
A top-level `asm { … }` block is **global assembly** — template only (no
operands or `volatile`), emitted as module-level asm. Symbols it defines are
reached with a lib-less `extern`:
```sx
asm {
#string ASM
.global _my_add
_my_add:
add x0, x0, x1
ret
ASM,
};
my_add :: (a: i64, b: i64) -> i64 extern;
```
Inline asm is target-specific and never runs at compile time. See
`examples/16xx-platform-asm-*` for the full matrix.
### Modules
```sx