feat(asm): Phase A.1 — parse asm { … } into AsmExpr; loud lowering bail
`asm volatile? { "tmpl", [name]? "constraint" (-> Type | = expr), …,
clobbers(.…) }` now parses into a flat-operand AsmExpr/AsmOperand (ast.zig +
parser.zig parseAsmExpr, dispatched from parsePrimary on .kw_asm). `volatile`
and `clobbers` are recognized contextually (not reserved). `-> @place`
write-through is rejected with a clear "Phase 2" parse error.
Codegen is not implemented yet (IR op + LLVM emit are Phases C–E), so lowering
bails LOUD + named via an explicit .asm_expr arm in lower/expr.zig (not the
generic unknown_expr else) — emitPlaceholder makes hasErrors() abort the build
on the message.
The new asm_expr tag forced (and got) arms in three exhaustive Node.Data
switches: sema.zig analyzeNode + findNodeAtOffset, semantic_diagnostics.zig
checkBindingNames — each recurses into template + operand payloads.
Design: adopted the operand auto-naming rule (design §II.5) — name auto-derived
from a {reg} pin, explicit [name] only when it differs or for register-class
operands, echo form rejected. Typing-stage rule; parser stores name: ?[]const u8.
Locked with examples/1640-platform-asm-parse.sx (multi-output divmod: named
operands, register pins, clobbers — parses then bails, called from main).
Also files issue 0137 (pre-existing, orthogonal: `sx run` with no `main`
segfaults via an unguarded JIT entry lookup in target.zig — not an asm bug).
zig build test green (648 corpus, 445 unit).
This commit is contained in:
69
issues/0137-jit-run-no-main-segfault.md
Normal file
69
issues/0137-jit-run-no-main-segfault.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# 0137 — `sx run` on a program with no `main` segfaults (JIT entry lookup unguarded)
|
||||
|
||||
## Symptom
|
||||
|
||||
`sx run <file>` on a program that defines no `main` function **crashes**
|
||||
(SIGSEGV/abort, "Segmentation fault at address 0x60") instead of emitting a clean
|
||||
diagnostic like `error: no 'main' function found`.
|
||||
|
||||
- **Observed:** process crash, exit 134 (abort) / 139 (SIGSEGV); no diagnostic.
|
||||
- **Expected:** a normal compile-style error ("no `main` entry point") and a
|
||||
clean non-zero exit, the same way any other missing-entry condition reports.
|
||||
|
||||
Independent of inline assembly — surfaced while writing an ASM-stream probe that
|
||||
omitted `main`, but reproduces with an ordinary, asm-free program (see below).
|
||||
|
||||
## Reproduction
|
||||
|
||||
A file with only an (uncalled) function and no `main`:
|
||||
|
||||
```sx
|
||||
foo :: (n: u64) -> u64 { return n + 1; }
|
||||
```
|
||||
|
||||
```sh
|
||||
sx run that.sx
|
||||
# => "Segmentation fault at address 0x60", exit 134
|
||||
# expected: "error: no 'main' function found" (or similar), clean non-zero exit
|
||||
```
|
||||
|
||||
## Root cause (suspected)
|
||||
|
||||
`src/target.zig` JIT-run path, ~lines 256–273. After the ORC lookup:
|
||||
|
||||
```zig
|
||||
var main_addr: c.LLVMOrcExecutorAddress = 0;
|
||||
err = c.LLVMOrcLLJITLookup(jit, &main_addr, "main");
|
||||
if (err != null) { /* prints "JIT lookup error" and returns error.CompileError */ }
|
||||
|
||||
// no guard for main_addr == 0 here:
|
||||
const main_fn: *const fn () callconv(.c) i32 = @ptrFromInt(main_addr);
|
||||
const result = main_fn(); // <- calls a null/garbage pointer when no main
|
||||
```
|
||||
|
||||
When the module has no `main` symbol, the lookup leaves `main_addr` at `0` (or
|
||||
ORC returns a degenerate success), so `@ptrFromInt(main_addr)` + `main_fn()`
|
||||
calls into null → the crash. There is no `main_addr == 0` check.
|
||||
|
||||
## Investigation prompt (paste into a fresh session)
|
||||
|
||||
> `sx run` on a program with no `main` segfaults instead of diagnosing. The JIT
|
||||
> run path in `src/target.zig` (~lines 256–273) looks up `"main"` via
|
||||
> `LLVMOrcLLJITLookup`, then unconditionally casts `main_addr` to a function
|
||||
> pointer and calls it. When the program defines no `main`, `main_addr` is `0`
|
||||
> (or the lookup degenerately "succeeds"), so the call dereferences null and
|
||||
> crashes.
|
||||
>
|
||||
> Fix: after the lookup's `err` check, add `if (main_addr == 0) { … }` that emits
|
||||
> a clean user-facing error ("no `main` function found" / "program has no entry
|
||||
> point") and returns `error.CompileError` (matching the existing
|
||||
> `JIT lookup error` style), BEFORE the `@ptrFromInt` + call. Consider whether a
|
||||
> pre-JIT check (the module/program already knows whether a `main` decl exists —
|
||||
> e.g. emit_llvm.zig:631 already null-checks `LLVMGetNamedFunction(.., "main")`)
|
||||
> is the better choke point so the diagnostic carries a source span rather than a
|
||||
> bare message. Either is acceptable; the hard requirement is *no crash*.
|
||||
>
|
||||
> Verification: `printf 'foo :: (n: u64) -> u64 { return n + 1; }\n' > /tmp/x.sx
|
||||
> && sx run /tmp/x.sx` — expect a clean error message + non-zero exit, NOT a
|
||||
> segfault. Add a pinned repro under `issues/` (or an `examples/11xx-diagnostics-*`
|
||||
> once the message is settled) asserting the diagnostic on stderr + the exit code.
|
||||
Reference in New Issue
Block a user