P5.2b: link() build-pipeline action on the VM via a host vtable

The one genuine action primitive: link(objects, output, libraries, frameworks,
flags, target) in library/modules/std/build.sx. Per the user decision to drop
fallibility from the build callback, link is plain VOID — a link failure bails
on the VM (hard build error), no -> ! / failable-tuple needed.

comptime_vm.zig can't depend on the driver (core/main/target), so link
dispatches through a new compiler_hooks.BuildHooks { ctx, link } vtable that
main.zig installs into BuildConfig.build_hooks before the post-link callback.
The driver side is main.LinkHooksCtx (unions explicit + CLI link flags, calls
target.link). New VM readers readStringList / readStringArg (inverse of
makeStringList) decode the List(string)/string args from flat memory.

Smoke test examples/1663-platform-build-pipeline-link (AOT): a post-link
callback re-links the build's own objects (c_object_paths + emit_object) into a
temp output via sx link — the relinked binary is a functional executable that
runs. Negative-probe verified (bad path -> ld fails -> ComptimeVmBail -> build
exit 1). The Zig driver still auto-links; removing that is P5.4.

704/0 both gates.
This commit is contained in:
agra
2026-06-19 08:11:36 +03:00
parent 83de0fa04d
commit f7362ee013
12 changed files with 206 additions and 14 deletions

View File

@@ -1480,6 +1480,27 @@ pub const Vm = struct {
return self.failMsg("comptime emit_object: no object was emitted (object_path unset)");
return try self.makeStringValue(table, path);
}
// `link(objects, output, libraries, frameworks, flags, target)` — the one
// genuine ACTION: dispatch to the host-installed linker (the VM can't link
// itself). Void return (the build callback isn't fallible — Phase 5
// decision); a link failure bails loudly → hard build error. `ref_types`
// gives each List(string) arg its concrete type for the flat-memory reader.
if (std.mem.eql(u8, name, "link")) {
if (args.len != 6) return self.failMsg("comptime link: expected (objects, output, libraries, frameworks, flags, target)");
const bc = self.build_config orelse
return self.failMsg("comptime link: no build config threaded into the VM");
const hooks = bc.build_hooks orelse
return self.failMsg("comptime link: no build hooks installed (link is a post-codegen-only action)");
const objects = try self.readStringList(table, ref_types[args[0].index()], frame.get(args[0].index()));
const output = try self.readStringArg(table, frame.get(args[1].index()));
const libraries = try self.readStringList(table, ref_types[args[2].index()], frame.get(args[2].index()));
const frameworks = try self.readStringList(table, ref_types[args[3].index()], frame.get(args[3].index()));
const flags = try self.readStringList(table, ref_types[args[4].index()], frame.get(args[4].index()));
const target_str = try self.readStringArg(table, frame.get(args[5].index()));
hooks.link(hooks.ctx, objects, output, libraries, frameworks, flags, target_str) catch
return self.failMsg("comptime link: linking failed");
return @as(Reg, null_addr); // void
}
return null; // not a known compiler function → caller bails to legacy
}
@@ -2258,4 +2279,31 @@ pub const Vm = struct {
try self.writeField(table, addr + fieldOffset(table, list_ty, 2), cap_fty, n);
return addr;
}
/// Read a `string` argument (a `{ptr, len}` fat pointer at `val`) as a host
/// `[]const u8`. The bytes are a VIEW into flat memory (Addr is a real host
/// pointer over a stable arena), valid for the duration of the call.
fn readStringArg(self: *Vm, table: *const types.TypeTable, val: Reg) Error![]const u8 {
const len: usize = @intCast(try self.sliceLen(val));
if (len == 0) return "";
return try self.machine.bytes(try self.sliceData(table, val), len);
}
/// Read a `List(string)` aggregate (at `addr`) into a host `[][]const u8` —
/// the inverse of `makeStringList`. Element string bytes are VIEWS into flat
/// memory (stable arena); the outer array is gpa-allocated (freed at
/// `Vm.deinit`). Used by the `link` primitive to read its List args.
fn readStringList(self: *Vm, table: *const types.TypeTable, list_ty: TypeId, addr: Addr) Error![]const []const u8 {
if (list_ty.isBuiltin() or table.get(list_ty) != .@"struct")
return self.failMsg("comptime List reader: arg type is not a List struct");
const items_ptr = try self.machine.readWord(addr + fieldOffset(table, list_ty, 0), table.pointer_size);
const len: usize = @intCast(try self.machine.readWord(addr + fieldOffset(table, list_ty, 1), 8));
const str_size = table.typeSizeBytes(.string);
const out = self.gpa.alloc([]const u8, len) catch return self.failMsg("comptime List reader: out of memory");
var i: usize = 0;
while (i < len) : (i += 1) {
out[i] = try self.readStringArg(table, items_ptr + i * str_size);
}
return out;
}
};