Files
sx/issues/0137-jit-run-no-main-segfault.md
agra f8e029d719 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).
2026-06-15 20:21:25 +03:00

70 lines
3.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 256273. 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 256273) 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.