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:
@@ -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,
|
||||
|
||||
61
readme.md
61
readme.md
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user