ERR/E4.2: value-carrying -> (int, !) main wrapper
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.
This commit is contained in:
@@ -1,12 +1,10 @@
|
|||||||
// Entry-point signature gate (ERR step E4.2). `main` must take no parameters
|
// 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 `-> !`
|
// and have one of: void, an integer (POSIX exit code), `-> !` (failable, no
|
||||||
// (the error tag rides the single return register). Anything else is a clean
|
// value), or `-> (int, !)` (failable + integer exit code). Anything else is a
|
||||||
// diagnostic — previously `main :: () -> string` SEGFAULTED (the JIT calls main
|
// clean diagnostic — previously `main :: () -> string` SEGFAULTED (the JIT
|
||||||
// as `() -> i32`, so a string return is read as garbage). The value-carrying
|
// calls main as `() -> i32`, so a string return is read as garbage). Accepted
|
||||||
// failable `-> (T, !)` is also rejected for now: its multi-slot return ABI-
|
// shapes are exercised elsewhere (238 integer-exit truncation, 244 `-> !`,
|
||||||
// mismatches the entry-point call (lands with the E4.2 wrapper). Accepted
|
// 245 `-> (int, !)`). This file is expected to FAIL compilation (exit 1).
|
||||||
// shapes are exercised elsewhere (e.g. 238 for integer-exit truncation).
|
|
||||||
// This file is expected to FAIL compilation (exit 1).
|
|
||||||
//
|
//
|
||||||
// Run: ./zig-out/bin/sx run examples/239-main-signature-reject.sx
|
// Run: ./zig-out/bin/sx run examples/239-main-signature-reject.sx
|
||||||
|
|
||||||
|
|||||||
22
examples/245-failable-main-value.sx
Normal file
22
examples/245-failable-main-value.sx
Normal file
@@ -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
|
||||||
|
}
|
||||||
@@ -2469,14 +2469,28 @@ pub const LLVMEmitter = struct {
|
|||||||
.ret => |un| {
|
.ret => |un| {
|
||||||
var val = self.resolveRef(un.operand);
|
var val = self.resolveRef(un.operand);
|
||||||
const func = &self.ir_mod.functions.items[self.current_func_idx];
|
const func = &self.ir_mod.functions.items[self.current_func_idx];
|
||||||
// Failable `-> !` main: `val` is the bare u32 error tag
|
// Failable main: wrap the return in the entry-point reporter
|
||||||
// (0 = success). Wrap it in the entry-point reporter (ERR E4.2)
|
// (ERR E4.2) — exit 0 (or the value) on success, else print the
|
||||||
// — exit 0 on success, else print the trace + tag to stderr and
|
// trace + tag to stderr and exit 1 — instead of returning the
|
||||||
// exit 1 — instead of returning the tag as the raw exit code.
|
// tag/tuple as the raw exit code. Two shapes:
|
||||||
if (self.current_func_is_main and self.ir_mod.types.get(func.ret) == .error_set) {
|
// `-> !` → `val` is the bare u32 error tag.
|
||||||
self.emitFailableMainRet(val);
|
// `-> (int, !)` → `val` is a `{value, tag}` tuple; extract both.
|
||||||
self.advanceRefCounter();
|
if (self.current_func_is_main) {
|
||||||
return;
|
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
|
// sret-shaped function: declared return-type-in-IR is
|
||||||
// the struct, but the LLVM signature is void with a
|
// the struct, but the LLVM signature is void with a
|
||||||
@@ -4690,13 +4704,15 @@ pub const LLVMEmitter = struct {
|
|||||||
return global;
|
return global;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Failable `-> !` main entry-point wrapper (ERR E4.2). At the LLVM level
|
/// Failable main entry-point wrapper (ERR E4.2). At the LLVM level main
|
||||||
/// main returns i32; for a pure-failable main the IR `ret` carries the u32
|
/// returns i32. `tag_val` is the u32 error tag (0 = "no error"); `value` is
|
||||||
/// error tag (0 = "no error"). Emit the branch: tag == 0 → `ret i32 0`
|
/// the integer value slot for a value-carrying `-> (int, !)` main, or null
|
||||||
/// (success); else resolve the tag name from the always-linked tag-name
|
/// for a pure `-> !` main. Emit the branch: tag == 0 → `ret i32 <value-or-0>`
|
||||||
/// table, hand it + the tag to `sx_trace_report_unhandled` (prints the
|
/// (success — exit code truncated to u8 downstream); else resolve the tag
|
||||||
/// header + return trace to stderr), and `ret i32 1`.
|
/// name from the always-linked tag-name table, hand it + the tag to
|
||||||
fn emitFailableMainRet(self: *LLVMEmitter, tag_val: c.LLVMValueRef) void {
|
/// `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 llvm_func = c.LLVMGetBasicBlockParent(c.LLVMGetInsertBlock(self.builder));
|
||||||
const tag_i32 = self.coerceArg(tag_val, self.cached_i32);
|
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");
|
const err_bb = c.LLVMAppendBasicBlockInContext(self.context, llvm_func, "main.err");
|
||||||
_ = c.LLVMBuildCondBr(self.builder, is_err, err_bb, ok_bb);
|
_ = 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.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.
|
// Error: resolve the tag name, report to stderr, exit 1.
|
||||||
c.LLVMPositionBuilderAtEnd(self.builder, err_bb);
|
c.LLVMPositionBuilderAtEnd(self.builder, err_bb);
|
||||||
|
|||||||
@@ -397,9 +397,19 @@ pub const Lowering = struct {
|
|||||||
self.needs_trace_runtime = true;
|
self.needs_trace_runtime = true;
|
||||||
return;
|
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| {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
error: main: return type must be void, an integer, or `!`; got 'string'
|
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 `!`
|
||||||
| ^^^^^^
|
| ^^^^^^
|
||||||
|
|||||||
1
tests/expected/245-failable-main-value.exit
Normal file
1
tests/expected/245-failable-main-value.exit
Normal file
@@ -0,0 +1 @@
|
|||||||
|
64
|
||||||
1
tests/expected/245-failable-main-value.txt
Normal file
1
tests/expected/245-failable-main-value.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
v = 64
|
||||||
Reference in New Issue
Block a user