From e898effb4bc7f4dad5f872fba48c4e2981ea930e Mon Sep 17 00:00:00 2001 From: agra Date: Mon, 1 Jun 2026 10:00:03 +0300 Subject: [PATCH] ERR/E4.2: value-carrying `-> (int, !)` main wrapper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extends the failable-main entry-point wrapper to a value-carrying main. `main :: () -> (int, !)` now exits the integer value on success (truncated to u8, like a plain integer main) and reports the header + trace to stderr + exits 1 on an escaping error (same reporter as the pure `-> !` form). - lower.zig validateMainSignature: accept a 2-field `{int, error_set}` tuple return (set needs_trace_runtime) instead of rejecting it. Multi- value `-> (T1, T2, !)` and non-integer value slots still reject — there's no single integer exit code to map them to (sharpened diagnostic). - emit_llvm.zig: the `.ret` arm detects a value-carrying main (tuple ending in `.error_set`) and extracts `{value, tag}` (extractvalue 0/1) before calling emitFailableMainRet, now generalized to take an optional `value` (null → pure `-> !`, success exits 0; present → success exits the value). C reporter unchanged. All E4.2 entry-point shapes (void / int / `-> !` / `-> (int, !)`) now done. examples/245-failable-main-value.sx (exit 64); 239 comment refreshed. --- examples/239-main-signature-reject.sx | 14 +++--- examples/245-failable-main-value.sx | 22 +++++++++ src/ir/emit_llvm.zig | 51 +++++++++++++------- src/ir/lower.zig | 14 +++++- tests/expected/239-main-signature-reject.txt | 4 +- tests/expected/245-failable-main-value.exit | 1 + tests/expected/245-failable-main-value.txt | 1 + 7 files changed, 78 insertions(+), 29 deletions(-) create mode 100644 examples/245-failable-main-value.sx create mode 100644 tests/expected/245-failable-main-value.exit create mode 100644 tests/expected/245-failable-main-value.txt diff --git a/examples/239-main-signature-reject.sx b/examples/239-main-signature-reject.sx index 4363de9..f102fdc 100644 --- a/examples/239-main-signature-reject.sx +++ b/examples/239-main-signature-reject.sx @@ -1,12 +1,10 @@ // Entry-point signature gate (ERR step E4.2). `main` must take no parameters -// and have a single-slot return: void, an integer (POSIX exit code), or `-> !` -// (the error tag rides the single return register). Anything else is a clean -// diagnostic — previously `main :: () -> string` SEGFAULTED (the JIT calls main -// as `() -> i32`, so a string return is read as garbage). The value-carrying -// failable `-> (T, !)` is also rejected for now: its multi-slot return ABI- -// mismatches the entry-point call (lands with the E4.2 wrapper). Accepted -// shapes are exercised elsewhere (e.g. 238 for integer-exit truncation). -// This file is expected to FAIL compilation (exit 1). +// and have one of: void, an integer (POSIX exit code), `-> !` (failable, no +// value), or `-> (int, !)` (failable + integer exit code). Anything else is a +// clean diagnostic — previously `main :: () -> string` SEGFAULTED (the JIT +// calls main as `() -> i32`, so a string return is read as garbage). Accepted +// shapes are exercised elsewhere (238 integer-exit truncation, 244 `-> !`, +// 245 `-> (int, !)`). This file is expected to FAIL compilation (exit 1). // // Run: ./zig-out/bin/sx run examples/239-main-signature-reject.sx diff --git a/examples/245-failable-main-value.sx b/examples/245-failable-main-value.sx new file mode 100644 index 0000000..e450291 --- /dev/null +++ b/examples/245-failable-main-value.sx @@ -0,0 +1,22 @@ +// Value-carrying failable main `-> (int, !)` (ERR step E4.2). The entry-point +// wrapper extracts the `{value, error}` tuple main returns: on success it exits +// with the integer value (truncated to u8, like a plain integer main); on an +// escaping error it prints the header + trace to stderr and exits 1 (the same +// reporter as the pure `-> !` form — see 244). This run takes the success path. +// Expected exit code: 64 (the returned value). + +#import "modules/std.sx"; + +ParseErr :: error { Empty, BadDigit }; + +inner :: (n: s32) -> (s32, !ParseErr) { + if n == 0 { raise error.Empty; } + if n < 0 { raise error.BadDigit; } + return n * 2; +} + +main :: () -> (s32, !ParseErr) { + v := try inner(32); // succeeds → v = 64 + print("v = {}\n", v); + return v; // success → exit code 64 +} diff --git a/src/ir/emit_llvm.zig b/src/ir/emit_llvm.zig index c177b05..16cd4ff 100644 --- a/src/ir/emit_llvm.zig +++ b/src/ir/emit_llvm.zig @@ -2469,14 +2469,28 @@ pub const LLVMEmitter = struct { .ret => |un| { var val = self.resolveRef(un.operand); const func = &self.ir_mod.functions.items[self.current_func_idx]; - // Failable `-> !` main: `val` is the bare u32 error tag - // (0 = success). Wrap it in the entry-point reporter (ERR E4.2) - // — exit 0 on success, else print the trace + tag to stderr and - // exit 1 — instead of returning the tag as the raw exit code. - if (self.current_func_is_main and self.ir_mod.types.get(func.ret) == .error_set) { - self.emitFailableMainRet(val); - self.advanceRefCounter(); - return; + // Failable main: wrap the return in the entry-point reporter + // (ERR E4.2) — exit 0 (or the value) on success, else print the + // trace + tag to stderr and exit 1 — instead of returning the + // tag/tuple as the raw exit code. Two shapes: + // `-> !` → `val` is the bare u32 error tag. + // `-> (int, !)` → `val` is a `{value, tag}` tuple; extract both. + if (self.current_func_is_main) { + const rinfo = self.ir_mod.types.get(func.ret); + if (rinfo == .error_set) { + self.emitFailableMainRet(null, val); + self.advanceRefCounter(); + return; + } + if (rinfo == .tuple and rinfo.tuple.fields.len == 2 and + self.ir_mod.types.get(rinfo.tuple.fields[1]) == .error_set) + { + const value = c.LLVMBuildExtractValue(self.builder, val, 0, "main.ret.val"); + const tag = c.LLVMBuildExtractValue(self.builder, val, 1, "main.ret.tag"); + self.emitFailableMainRet(value, tag); + self.advanceRefCounter(); + return; + } } // sret-shaped function: declared return-type-in-IR is // the struct, but the LLVM signature is void with a @@ -4690,13 +4704,15 @@ pub const LLVMEmitter = struct { return global; } - /// Failable `-> !` main entry-point wrapper (ERR E4.2). At the LLVM level - /// main returns i32; for a pure-failable main the IR `ret` carries the u32 - /// error tag (0 = "no error"). Emit the branch: tag == 0 → `ret i32 0` - /// (success); else resolve the tag name from the always-linked tag-name - /// table, hand it + the tag to `sx_trace_report_unhandled` (prints the - /// header + return trace to stderr), and `ret i32 1`. - fn emitFailableMainRet(self: *LLVMEmitter, tag_val: c.LLVMValueRef) void { + /// Failable main entry-point wrapper (ERR E4.2). At the LLVM level main + /// returns i32. `tag_val` is the u32 error tag (0 = "no error"); `value` is + /// the integer value slot for a value-carrying `-> (int, !)` main, or null + /// for a pure `-> !` main. Emit the branch: tag == 0 → `ret i32 ` + /// (success — exit code truncated to u8 downstream); else resolve the tag + /// name from the always-linked tag-name table, hand it + the tag to + /// `sx_trace_report_unhandled` (prints the header + return trace to stderr), + /// and `ret i32 1`. + fn emitFailableMainRet(self: *LLVMEmitter, value: ?c.LLVMValueRef, tag_val: c.LLVMValueRef) void { const llvm_func = c.LLVMGetBasicBlockParent(c.LLVMGetInsertBlock(self.builder)); const tag_i32 = self.coerceArg(tag_val, self.cached_i32); @@ -4705,9 +4721,10 @@ pub const LLVMEmitter = struct { const err_bb = c.LLVMAppendBasicBlockInContext(self.context, llvm_func, "main.err"); _ = c.LLVMBuildCondBr(self.builder, is_err, err_bb, ok_bb); - // Success: exit 0. + // Success: exit the value (truncated to u8 by the JIT/OS) or 0. c.LLVMPositionBuilderAtEnd(self.builder, ok_bb); - _ = c.LLVMBuildRet(self.builder, c.LLVMConstInt(self.cached_i32, 0, 0)); + const ok_ret = if (value) |v| self.coerceArg(v, self.cached_i32) else c.LLVMConstInt(self.cached_i32, 0, 0); + _ = c.LLVMBuildRet(self.builder, ok_ret); // Error: resolve the tag name, report to stderr, exit 1. c.LLVMPositionBuilderAtEnd(self.builder, err_bb); diff --git a/src/ir/lower.zig b/src/ir/lower.zig index 6f272ce..51d4c84 100644 --- a/src/ir/lower.zig +++ b/src/ir/lower.zig @@ -397,9 +397,19 @@ pub const Lowering = struct { self.needs_trace_runtime = true; return; } - // `-> (T..., !)` — a multi-slot tuple return; not yet wired. + // `-> (T, !)` — value-carrying failable. Accepted only for a single + // **integer** value slot (`{int, error_set}`): the wrapper extracts + // the value + tag from the returned tuple, exits `value as u8` on + // success / reports + exits 1 on error. Multi-value `-> (T1, T2, !)` + // or a non-integer value slot stays rejected — there's no single + // integer exit code to map it to. + const ti = self.module.types.get(rt); + if (ti == .tuple and ti.tuple.fields.len == 2 and self.isIntEx(ti.tuple.fields[0])) { + self.needs_trace_runtime = true; + return; + } if (self.diagnostics) |diags| { - diags.addFmt(.err, if (fd.return_type) |rtn| rtn.span else null, "a value-carrying failable `main` (`-> (T, !)`) is not yet supported — its multi-slot return ABI-mismatches the entry-point call; use `-> !` (no value) or a non-failable integer return, or absorb errors with `catch`", .{}); + diags.addFmt(.err, if (fd.return_type) |rtn| rtn.span else null, "a value-carrying failable `main` must be `-> (int, !)` (one integer value slot); got '{s}'. Use `-> !` (no value), `-> (int, !)`, or a non-failable integer return", .{self.formatTypeName(rt)}); } return; } diff --git a/tests/expected/239-main-signature-reject.txt b/tests/expected/239-main-signature-reject.txt index d1361a8..3d840f6 100644 --- a/tests/expected/239-main-signature-reject.txt +++ b/tests/expected/239-main-signature-reject.txt @@ -1,5 +1,5 @@ error: main: return type must be void, an integer, or `!`; got 'string' - --> /Users/agra/projects/sx/examples/239-main-signature-reject.sx:15:15 + --> /Users/agra/projects/sx/examples/239-main-signature-reject.sx:13:15 | -15 | main :: () -> string { // ERROR: return type must be void, an integer, or `!` +13 | main :: () -> string { // ERROR: return type must be void, an integer, or `!` | ^^^^^^ diff --git a/tests/expected/245-failable-main-value.exit b/tests/expected/245-failable-main-value.exit new file mode 100644 index 0000000..900731f --- /dev/null +++ b/tests/expected/245-failable-main-value.exit @@ -0,0 +1 @@ +64 diff --git a/tests/expected/245-failable-main-value.txt b/tests/expected/245-failable-main-value.txt new file mode 100644 index 0000000..a414b69 --- /dev/null +++ b/tests/expected/245-failable-main-value.txt @@ -0,0 +1 @@ +v = 64