P5.4: migrate all callers to on_build; delete set_post_link_callback

on_build is now the sole post-build callback mechanism. Migrated the 9 callers
(0602/0603/1611/1614/1615/1616 + the platform bundle_main) from
opts.set_post_link_callback(cb) to on_build(cb), giving each callback the
(opt: BuildOptions) param. Deleted set_post_link_callback from build.sx,
compiler_lib (bound_fns + handleSetPostLinkCallback), and the VM arm.

Reworked the P5 smoke tests for the new semantics: an on_build override REPLACES
the build (must emit+link or delegate), unlike the old post-link callback which
ran after the auto-link. 1662 (queries) + 1664 (override+List-grow) now delegate
to default_pipeline for the real build; deleted 1661/1663 (the primitives are now
exercised by every AOT build). bundle_main invoked with pass_options=true.

Benign 37-.ir churn (build.sx shrank). 703/0 both gates.
This commit is contained in:
agra
2026-06-19 09:37:05 +03:00
parent d178454841
commit 65ac370683
60 changed files with 36334 additions and 36578 deletions

View File

@@ -18,7 +18,7 @@ bug :: (cmd: [:0]u8) -> ?R {
R.{ x = 1 }
}
post_link :: () -> bool {
post_link :: (opt: BuildOptions) -> bool abi(.compiler) {
if r := bug("echo hi") { puts("ok"); } else { puts("null"); }
true
}
@@ -28,7 +28,7 @@ post_link :: () -> bool {
// `set_post_link_callback()` compiler-API calls are permitted.
configure :: () abi(.compiler) {
opts := build_options();
opts.set_post_link_callback(post_link);
on_build(post_link);
}
#run configure();

View File

@@ -10,7 +10,7 @@
puts :: (s: [:0]u8) -> i32 extern libc;
cb :: () -> bool {
cb :: (opt: BuildOptions) -> bool abi(.compiler) {
a := format("{}", "x");
puts("1-arg ok");
b := format("{} {}", "x", "y");
@@ -25,7 +25,7 @@ cb :: () -> bool {
// `set_post_link_callback()` compiler-API calls are permitted.
configure :: () abi(.compiler) {
opts := build_options();
opts.set_post_link_callback(cb);
on_build(cb);
}
#run configure();

View File

@@ -10,14 +10,14 @@
puts :: (s: [:0]u8) -> i32 extern libc;
post_link :: () -> bool {
post_link :: (opt: BuildOptions) -> bool abi(.compiler) {
puts("[post-link] callback fired");
true
}
configure :: () abi(.compiler) {
opts := build_options();
opts.set_post_link_callback(post_link);
on_build(post_link);
}
#run configure();

View File

@@ -11,7 +11,7 @@ configure :: () abi(.compiler) {
opts := build_options();
opts.set_bundle_path("HelloApp.app");
opts.set_bundle_id("co.example.hello");
opts.set_post_link_callback(bundle_main);
on_build(bundle_main);
}
#run configure();

View File

@@ -13,7 +13,7 @@ configure :: () abi(.compiler) {
opts := build_options();
opts.set_bundle_path("IosSimApp.app");
opts.set_bundle_id("co.example.iossim");
opts.set_post_link_callback(bundle_main);
on_build(bundle_main);
}
#run configure();

View File

@@ -32,7 +32,7 @@ configure :: () abi(.compiler) {
opts.set_bundle_id("co.swipelab.sxprobe");
opts.set_codesign_identity("Apple Development: Alexandru Agrapine (DC8VVHJ9W4)");
opts.set_provisioning_profile("/Users/agra/Downloads/SwipeS32DevProfile.mobileprovision");
opts.set_post_link_callback(bundle_main);
on_build(bundle_main);
}
#run configure();

View File

@@ -1,33 +0,0 @@
#import "modules/std.sx";
#import "modules/build.sx";
// P5.1 smoke test — the post-link build driver runs on the comptime VM,
// not the legacy interpreter.
//
// The callback allocates and GROWS a `List` (three appends). Comptime List
// growth works only on the VM; the legacy interp fails it (issue 0141:
// `struct_get: base has no fields`). So this build SUCCEEDS only because
// `core.invokeByFuncId` routes the post-link callback through the VM.
//
// AOT note: the corpus snapshots an AOT example's *executed binary* streams,
// and the build step's own stdout is discarded — so the callback's success is
// observed via the build EXIT CODE. On the VM the callback returns true, the
// build links cleanly (exit 0), and the binary runs → "runtime main". If the
// driver regressed to the legacy interp the callback would bail, the build
// would exit non-zero, and this snapshot would fail.
post_link :: () -> bool abi(.compiler) {
names := List(string).{};
names.append("alpha");
names.append("beta");
names.append("gamma");
return names.len == 3;
}
configure :: () abi(.compiler) {
opts := build_options();
opts.set_post_link_callback(post_link);
}
#run configure();
main :: () { print("runtime main\n"); }

View File

@@ -2,16 +2,12 @@
#import "modules/build.sx";
#import "modules/compiler.sx";
// P5.2 smoke test — the build-pipeline query primitives (`c_object_paths` /
// `link_libraries` / `emit_object`) run on the comptime VM: the list queries
// return a `List(string)` the VM builds in flat memory; `emit_object` returns
// the compiler-emitted object path. Registered as a post-link callback (which
// runs on the VM — core.invokeByFuncId), so the build state is live there.
//
// The `#import c` source compiles to one `.o`, so `c_object_paths()` must return
// a one-element list whose single string is the (non-empty) object path. AOT
// snapshots the executed binary, so success is observed via the build exit code:
// the callback returns true only if the VM-built list is well-formed.
// P5.4 — the build-pipeline query primitives on the comptime VM. A custom
// `on_build` override inspects the build metadata (`c_object_paths` returns the
// VM-built `List(string)` of C companion objects), then DELEGATES to the stdlib
// `default_pipeline` for the real emit + link. The `#import c` source compiles to
// one object, so `c_object_paths()` must be a one-element list of a non-empty
// path. AOT runs the produced binary → "runtime main: 42".
#import c {
#source "1662-platform-build-pipeline-queries.c";
@@ -19,25 +15,18 @@
c_marker :: () -> i64 extern;
check :: () -> bool abi(.compiler) {
build :: (opt: BuildOptions) -> bool abi(.compiler) {
objs := c_object_paths();
libs := link_libraries();
obj := emit_object(); // the compiler-emitted sx object path
if obj.len == 0 { return false; }
if objs.len != 1 { return false; }
if objs.items[0].len == 0 { return false; }
// `link_libraries` must be a well-formed (possibly empty) list — touch each
// entry so a malformed fat pointer would fault rather than pass silently.
// link_libraries must be a well-formed (possibly empty) list — touch each.
libs := link_libraries();
sum : i64 = 0;
i : i64 = 0;
while i < libs.len { sum += libs.items[i].len; i += 1; }
return sum >= 0;
if sum < 0 { return false; }
return default_pipeline(opt); // the real emit + link
}
configure :: () abi(.compiler) {
opts := build_options();
opts.set_post_link_callback(check);
}
#run configure();
#run on_build(build);
main :: () { print("runtime main: {}\n", c_marker()); }

View File

@@ -1,31 +0,0 @@
#import "modules/std.sx";
#import "modules/build.sx";
#import "modules/compiler.sx";
// P5.2b smoke test — the `link` build-pipeline ACTION runs on the comptime VM,
// dispatching through the host-installed linker hook (the VM can't link itself).
// The post-link callback (which runs on the VM — core.invokeByFuncId) re-links
// the build's own objects into a temp output via the sx `link` primitive. A link
// failure bails → the build fails; success → the callback returns true and the
// build's binary runs ("runtime main"). AOT snapshots the binary, so the link's
// success is observed via the build exit code.
relink :: () -> bool abi(.compiler) {
objs := List(string).{};
cobjs := c_object_paths();
i : i64 = 0;
while i < cobjs.len { objs.append(cobjs.items[i]); i += 1; }
objs.append(emit_object());
fws := List(string).{};
flags := List(string).{};
link(objs, ".sx-tmp/1663-link-out", link_libraries(), fws, flags, "");
return true;
}
configure :: () abi(.compiler) {
opts := build_options();
opts.set_post_link_callback(relink);
}
#run configure();
main :: () { print("runtime main\n"); }

View File

@@ -1,17 +1,20 @@
#import "modules/std.sx";
#import "modules/build.sx";
#import "modules/compiler.sx";
// P5.3 smoke test — `on_build(build)` registers the build callback (the Phase 5
// form), and the compiler invokes it post-codegen WITH the BuildOptions handle
// as an arg (`build :: (opt: BuildOptions) -> bool`). This exercises the VM
// entry that passes an explicit arg after the implicit context (runEntryArgs).
// The callback uses the build-pipeline primitives (no opt accessors yet) and
// returns true; AOT observes success via the build exit code.
// P5.4 — the `on_build` override. A user `#run on_build(build);` replaces the
// stdlib `default_pipeline` as the build driver; the compiler invokes it after
// codegen with the `BuildOptions` handle. This build GROWS a `List` on the build
// VM (comptime List growth works only on the VM — issue 0141; the legacy interp
// fails it) then DELEGATES to `default_pipeline` for the real emit + link, so a
// working binary is still produced → "runtime main".
build :: (opt: BuildOptions) -> bool abi(.compiler) {
obj := emit_object();
return obj.len > 0;
names := List(string).{};
names.append("alpha");
names.append("beta");
names.append("gamma");
if names.len != 3 { return false; }
return default_pipeline(opt);
}
#run on_build(build);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -689,9 +689,6 @@ declare ptr @BuildOptions.jni_main_java_source_at(i64, i64) #0
; Function Attrs: nounwind
declare i64 @build_options() #0
; Function Attrs: nounwind
declare void @set_post_link_callback(i64, ptr) #0
; Function Attrs: nounwind
declare void @on_build(ptr) #0

View File

@@ -1191,9 +1191,6 @@ declare ptr @BuildOptions.jni_main_java_source_at(i64, i64) #0
; Function Attrs: nounwind
declare i64 @build_options() #0
; Function Attrs: nounwind
declare void @set_post_link_callback(i64, ptr) #0
; Function Attrs: nounwind
declare void @on_build(ptr) #0

View File

@@ -1207,9 +1207,6 @@ declare ptr @BuildOptions.jni_main_java_source_at(i64, i64) #0
; Function Attrs: nounwind
declare i64 @build_options() #0
; Function Attrs: nounwind
declare void @set_post_link_callback(i64, ptr) #0
; Function Attrs: nounwind
declare void @on_build(ptr) #0

File diff suppressed because one or more lines are too long

View File

@@ -1183,9 +1183,6 @@ declare ptr @BuildOptions.jni_main_java_source_at(i64, i64) #0
; Function Attrs: nounwind
declare i64 @build_options() #0
; Function Attrs: nounwind
declare void @set_post_link_callback(i64, ptr) #0
; Function Attrs: nounwind
declare void @on_build(ptr) #0

File diff suppressed because one or more lines are too long

View File

@@ -1209,9 +1209,6 @@ declare ptr @BuildOptions.jni_main_java_source_at(i64, i64) #0
; Function Attrs: nounwind
declare i64 @build_options() #0
; Function Attrs: nounwind
declare void @set_post_link_callback(i64, ptr) #0
; Function Attrs: nounwind
declare void @on_build(ptr) #0

View File

@@ -1185,9 +1185,6 @@ declare ptr @BuildOptions.jni_main_java_source_at(i64, i64) #0
; Function Attrs: nounwind
declare i64 @build_options() #0
; Function Attrs: nounwind
declare void @set_post_link_callback(i64, ptr) #0
; Function Attrs: nounwind
declare void @on_build(ptr) #0

View File

@@ -1183,9 +1183,6 @@ declare ptr @BuildOptions.jni_main_java_source_at(i64, i64) #0
; Function Attrs: nounwind
declare i64 @build_options() #0
; Function Attrs: nounwind
declare void @set_post_link_callback(i64, ptr) #0
; Function Attrs: nounwind
declare void @on_build(ptr) #0

View File

@@ -1183,9 +1183,6 @@ declare ptr @BuildOptions.jni_main_java_source_at(i64, i64) #0
; Function Attrs: nounwind
declare i64 @build_options() #0
; Function Attrs: nounwind
declare void @set_post_link_callback(i64, ptr) #0
; Function Attrs: nounwind
declare void @on_build(ptr) #0

View File

@@ -1183,9 +1183,6 @@ declare ptr @BuildOptions.jni_main_java_source_at(i64, i64) #0
; Function Attrs: nounwind
declare i64 @build_options() #0
; Function Attrs: nounwind
declare void @set_post_link_callback(i64, ptr) #0
; Function Attrs: nounwind
declare void @on_build(ptr) #0

View File

@@ -1183,9 +1183,6 @@ declare ptr @BuildOptions.jni_main_java_source_at(i64, i64) #0
; Function Attrs: nounwind
declare i64 @build_options() #0
; Function Attrs: nounwind
declare void @set_post_link_callback(i64, ptr) #0
; Function Attrs: nounwind
declare void @on_build(ptr) #0

View File

@@ -1183,9 +1183,6 @@ declare ptr @BuildOptions.jni_main_java_source_at(i64, i64) #0
; Function Attrs: nounwind
declare i64 @build_options() #0
; Function Attrs: nounwind
declare void @set_post_link_callback(i64, ptr) #0
; Function Attrs: nounwind
declare void @on_build(ptr) #0

View File

@@ -1183,9 +1183,6 @@ declare ptr @BuildOptions.jni_main_java_source_at(i64, i64) #0
; Function Attrs: nounwind
declare i64 @build_options() #0
; Function Attrs: nounwind
declare void @set_post_link_callback(i64, ptr) #0
; Function Attrs: nounwind
declare void @on_build(ptr) #0

View File

@@ -1183,9 +1183,6 @@ declare ptr @BuildOptions.jni_main_java_source_at(i64, i64) #0
; Function Attrs: nounwind
declare i64 @build_options() #0
; Function Attrs: nounwind
declare void @set_post_link_callback(i64, ptr) #0
; Function Attrs: nounwind
declare void @on_build(ptr) #0

View File

@@ -1183,9 +1183,6 @@ declare ptr @BuildOptions.jni_main_java_source_at(i64, i64) #0
; Function Attrs: nounwind
declare i64 @build_options() #0
; Function Attrs: nounwind
declare void @set_post_link_callback(i64, ptr) #0
; Function Attrs: nounwind
declare void @on_build(ptr) #0

View File

@@ -696,9 +696,6 @@ declare ptr @BuildOptions.jni_main_java_source_at(i64, i64) #0
; Function Attrs: nounwind
declare i64 @build_options() #0
; Function Attrs: nounwind
declare void @set_post_link_callback(i64, ptr) #0
; Function Attrs: nounwind
declare void @on_build(ptr) #0

View File

@@ -1 +0,0 @@
{ "aot": true }

View File

@@ -1 +0,0 @@
runtime main

View File

@@ -1 +0,0 @@
{ "aot": true }

View File

@@ -1 +0,0 @@
runtime main

View File

@@ -93,20 +93,12 @@ BuildOptions :: struct #compiler {
build_options :: () -> BuildOptions abi(.compiler);
// Post-link callback. Registers a sx function the compiler will invoke after
// `target.link()` returns. Used by the sx-side bundler
// (`platform.bundle.bundle_main`) and by user programs that want custom
// post-build steps. Return `false` to fail the build. Migrated off `#compiler`
// onto the comptime compiler-API (`abi(.compiler)`); called as
// `opts.set_post_link_callback(cb)` via UFCS, from inside a `#run { … }` block.
set_post_link_callback :: ufcs (self: BuildOptions, cb: () -> bool) abi(.compiler);
// The build callback registrar (Phase 5). Registers the sx function that drives
// the build — the compiler invokes it after codegen with the `BuildOptions`
// handle. Generalizes `set_post_link_callback` (a free fn, and the callback
// receives `opt`); `#run on_build(build);` is the override form, where
// `build :: (opt: BuildOptions) -> bool abi(.compiler) { … }`. The stdlib default
// implementation lives below (the default `build` script).
// handle. `#run on_build(build);` (from inside a `#run` block) is the override
// form, where `build :: (opt: BuildOptions) -> bool abi(.compiler) { … }`; the
// last registration wins. With no override the compiler auto-invokes the stdlib
// `default_pipeline` (below). This is the sole post-build callback mechanism.
on_build :: (cb: (opt: BuildOptions) -> bool abi(.compiler)) abi(.compiler);
// ── The default build script ────────────────────────────────────────────────

View File

@@ -29,7 +29,7 @@
// `abi(.compiler)` so the backend doesn't lower it and its `build_options()`
// compiler-API call is permitted (it would otherwise be rejected as a
// comptime-only function called at runtime).
bundle_main :: () -> bool abi(.compiler) {
bundle_main :: (opt: BuildOptions) -> bool abi(.compiler) {
opts := build_options();
binary := opts.binary_path();
bundle := opts.bundle_path();

View File

@@ -60,7 +60,6 @@ pub const bound_fns = [_]BoundFn{
.{ .sx_name = "register_type", .handler = handleRegisterType },
// ── BuildOptions (migrated off `#compiler` onto `abi(.compiler)`) ─────────
.{ .sx_name = "build_options", .handler = handleBuildOptions },
.{ .sx_name = "set_post_link_callback", .handler = handleSetPostLinkCallback },
.{ .sx_name = "on_build", .handler = handleOnBuild },
// ── build-pipeline metadata queries (Phase 5.2) ──────────────────────────
// VM-only: the post-link callback that calls these always runs on the VM
@@ -349,16 +348,6 @@ fn handleBuildOptions(_: *Interpreter, _: []const Value) InterpError!Value {
/// `set_post_link_callback(self, cb)` — record the callback `FuncId` on the build
/// config so `main.zig` re-enters the evaluator post-link. The `cb` arg is a
/// `.func_ref` value.
fn handleSetPostLinkCallback(interp: *Interpreter, args: []const Value) InterpError!Value {
if (args.len != 2) return error.TypeError;
const bc = interp.build_config orelse return error.CannotEvalComptime;
switch (args[1]) {
.func_ref => |id| bc.post_link_callback_fn = id,
else => return error.TypeError,
}
return .void_val;
}
/// `on_build(cb)` — register the build callback (the Phase 5 form, a free fn; cb
/// is arg 0, and `cb: (opt: BuildOptions) -> bool` so the callback is invoked with
/// the `BuildOptions` handle). Sets `post_link_takes_options` to distinguish it

View File

@@ -1480,18 +1480,6 @@ pub const Vm = struct {
if (std.mem.eql(u8, name, "build_options")) {
return @as(Reg, null_addr);
}
// `set_post_link_callback(self, cb)` — record the callback `FuncId` on the
// build config so `main.zig` re-enters the evaluator post-link. The cb arg is
// a `func_ref` word. Mirrors the legacy `hookSetPostLinkCallback`.
if (std.mem.eql(u8, name, "set_post_link_callback")) {
if (args.len != 2) return self.failMsg("comptime set_post_link_callback: expected (self, cb)");
const bc = self.build_config orelse
return self.failMsg("comptime set_post_link_callback: no build config threaded into the VM");
const fid = funcRefToId(frame.get(args[1].index())) orelse
return self.failMsg("comptime set_post_link_callback: cb arg is not a function value");
bc.post_link_callback_fn = fid;
return @as(Reg, null_addr);
}
// `on_build(cb)` — register the build callback (the Phase 5 form, `cb:
// (opt: BuildOptions) -> bool`). Like `set_post_link_callback` but a free
// fn (cb is arg 0, no self) and the callback receives the `BuildOptions`

View File

@@ -824,7 +824,7 @@ fn compileWithTimer(allocator: std.mem.Allocator, io: std.Io, input_path: []cons
} else if (comp.getPostLinkModule()) |mod_name| {
const qualified = try std.fmt.allocPrint(allocator, "{s}.bundle_main", .{mod_name});
defer allocator.free(qualified);
const ret_opt = comp.invokeByName(qualified, false) catch |err| {
const ret_opt = comp.invokeByName(qualified, true) catch |err| {
const label = try std.fmt.allocPrint(allocator, "post-link module '{s}.bundle_main'", .{mod_name});
defer allocator.free(label);
printInterpBailDiag(&comp, label, err);