P5.4 core: drive the whole build from sx default_pipeline (no auto-emit/link)
The compiler's post-IR role shrinks to: codegen -> invoke the build callback. There is NO Zig auto-emit / auto-link anymore; emit + link are sx-called actions. - emit_object() is now an ACTION (verify + emit via a host BuildHooks vtable), returning the object path. New query primitives build_output/build_target/ build_frameworks/build_flags (data reads from the merged BuildConfig). - library/modules/build.sx imports compiler.sx and defines default_pipeline: emit_object -> gather c_object_paths -> link(objs, output, libs, fws, flags, target). The std<->build import cycle is handled by the resolver. - The compiler FORCE-LOWERS default_pipeline (well-known name) and AUTO-INVOKES it post-codegen when no on_build/set_post_link_callback override was registered (driver's final fallback: invokeByName default_pipeline). - Prelude-less programs (e.g. asm tests) don't import build.sx, so the BUILD path auto-imports modules/build.sx (idempotent if already transitive) so default_pipeline is always available. JIT sx run is untouched (emits in-process). - Removed the build cache short-circuits (incompatible with the always-run sx driver; a future cache can live in default_pipeline). Benign 37-.ir churn (build.sx grew); zero behavior changes (verified diff is .ir-only). 705/0 both gates.
This commit is contained in:
@@ -71,11 +71,11 @@ pub const BuildConfig = struct {
|
||||
c_object_paths: []const []const u8 = &.{},
|
||||
link_libraries: []const []const u8 = &.{},
|
||||
|
||||
/// Path of the object file the compiler emitted for this build (`.sx-tmp/main.o`
|
||||
/// or the cached `.o`). Forwarded by main.zig before the post-link callback so
|
||||
/// the sx build driver can read it via the `emit_object()` compiler primitive
|
||||
/// (the compiler emits the object eagerly; the primitive returns its path).
|
||||
object_path: ?[]const u8 = null,
|
||||
/// The fully-merged link flags (CLI `extra_link_flags` + `#run` build-block
|
||||
/// flags), forwarded by main.zig. The sx driver reads them via `build_flags()`
|
||||
/// and passes them to `link`. (Distinct from `link_flags`, which holds only
|
||||
/// the `#run`-accumulated subset.)
|
||||
merged_link_flags: []const []const u8 = &.{},
|
||||
|
||||
/// Host-installed callbacks for build-pipeline ACTIONS the comptime VM can't
|
||||
/// perform itself (it can't depend on the driver — `core`/`main`/`target`).
|
||||
@@ -126,6 +126,10 @@ pub const BuildConfig = struct {
|
||||
/// error here and the VM surfaces it as a hard build error.
|
||||
pub const BuildHooks = struct {
|
||||
ctx: *anyopaque,
|
||||
/// Verify + emit the codegen'd module to its object file; return the path
|
||||
/// (ctx-owned). The `emit_object()` primitive — an ACTION, since the driver
|
||||
/// no longer auto-emits (everything is sx-driven via `default_pipeline`).
|
||||
emit_object: *const fn (ctx: *anyopaque) anyerror![]const u8,
|
||||
/// Link `objects` → `output`, with the given `libraries` / `frameworks` /
|
||||
/// link `flags` / `target` triple. (`objects` is the full object list; the
|
||||
/// adapter splits it for the underlying linker.)
|
||||
|
||||
@@ -72,6 +72,10 @@ pub const bound_fns = [_]BoundFn{
|
||||
.{ .sx_name = "link_libraries", .handler = handleBuildPipelineQuery },
|
||||
.{ .sx_name = "emit_object", .handler = handleBuildPipelineQuery },
|
||||
.{ .sx_name = "link", .handler = handleBuildPipelineQuery },
|
||||
.{ .sx_name = "build_output", .handler = handleBuildPipelineQuery },
|
||||
.{ .sx_name = "build_target", .handler = handleBuildPipelineQuery },
|
||||
.{ .sx_name = "build_frameworks", .handler = handleBuildPipelineQuery },
|
||||
.{ .sx_name = "build_flags", .handler = handleBuildPipelineQuery },
|
||||
};
|
||||
|
||||
/// Legacy-path stub for the Phase 5 build-pipeline primitives — see the
|
||||
|
||||
@@ -1523,17 +1523,42 @@ pub const Vm = struct {
|
||||
return self.failMsg("comptime link_libraries: no build config threaded into the VM");
|
||||
return try self.makeStringList(table, result_ty, bc.link_libraries);
|
||||
}
|
||||
// `emit_object() -> string` — the compiler emits the object eagerly (the Zig
|
||||
// driver, before the post-link callback); this returns its path. A query,
|
||||
// not an action — so no driver callback is needed (unlike `link`).
|
||||
// `emit_object() -> string` — ACTION: verify + emit the codegen'd module
|
||||
// to its object file and return the path. Dispatches through the
|
||||
// host-installed hook (the VM can't emit itself); the driver no longer
|
||||
// auto-emits (everything is sx-driven via `default_pipeline`).
|
||||
if (std.mem.eql(u8, name, "emit_object")) {
|
||||
if (args.len != 0) return self.failMsg("comptime emit_object: expected no args");
|
||||
const bc = self.build_config orelse
|
||||
return self.failMsg("comptime emit_object: no build config threaded into the VM");
|
||||
const path = bc.object_path orelse
|
||||
return self.failMsg("comptime emit_object: no object was emitted (object_path unset)");
|
||||
const hooks = bc.build_hooks orelse
|
||||
return self.failMsg("comptime emit_object: no build hooks installed (emit is a post-codegen-only action)");
|
||||
const path = hooks.emit_object(hooks.ctx) catch
|
||||
return self.failMsg("comptime emit_object: object emission failed");
|
||||
return try self.makeStringValue(table, path);
|
||||
}
|
||||
// Build-config metadata the sx driver passes to `link`. Read-only data
|
||||
// forwarded by `main.zig` (the merged CLI + `#run` build config).
|
||||
if (std.mem.eql(u8, name, "build_output")) {
|
||||
if (args.len != 0) return self.failMsg("comptime build_output: expected no args");
|
||||
const bc = self.build_config orelse return self.failMsg("comptime build_output: no build config");
|
||||
return try self.makeStringValue(table, bc.output_path orelse "");
|
||||
}
|
||||
if (std.mem.eql(u8, name, "build_target")) {
|
||||
if (args.len != 0) return self.failMsg("comptime build_target: expected no args");
|
||||
const bc = self.build_config orelse return self.failMsg("comptime build_target: no build config");
|
||||
return try self.makeStringValue(table, bc.target_triple orelse "");
|
||||
}
|
||||
if (std.mem.eql(u8, name, "build_frameworks")) {
|
||||
if (args.len != 0) return self.failMsg("comptime build_frameworks: expected no args");
|
||||
const bc = self.build_config orelse return self.failMsg("comptime build_frameworks: no build config");
|
||||
return try self.makeStringList(table, result_ty, bc.target_frameworks);
|
||||
}
|
||||
if (std.mem.eql(u8, name, "build_flags")) {
|
||||
if (args.len != 0) return self.failMsg("comptime build_flags: expected no args");
|
||||
const bc = self.build_config orelse return self.failMsg("comptime build_flags: no build config");
|
||||
return try self.makeStringList(table, result_ty, bc.merged_link_flags);
|
||||
}
|
||||
// `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
|
||||
|
||||
@@ -155,6 +155,14 @@ fn isExportedEntryName(name: []const u8) bool {
|
||||
std.mem.startsWith(u8, name, "Java_");
|
||||
}
|
||||
|
||||
/// The well-known stdlib build driver (`library/modules/build.sx`). It is invoked
|
||||
/// by the compiler post-codegen when no `#run on_build(...)` override exists, but
|
||||
/// is never CALLED from sx — so it must be force-lowered like an OS entry point,
|
||||
/// else lazy lowering leaves it a bodiless `declare` stub the VM can't run.
|
||||
fn isDefaultBuildPipeline(name: []const u8) bool {
|
||||
return std.mem.eql(u8, name, "default_pipeline");
|
||||
}
|
||||
|
||||
/// Lower all top-level declarations from a root node.
|
||||
/// Pass 1: Scan all declarations (register ASTs, types, extern stubs).
|
||||
/// Pass 2: Lower only `main` (everything else is lowered lazily on demand).
|
||||
@@ -222,6 +230,12 @@ pub fn lowerRoot(self: *Lowering, root: *const Node) void {
|
||||
self.checkInfiniteSize();
|
||||
// Pass 2: lower main (and comptime side-effects)
|
||||
self.lowerMainAndComptime(decls);
|
||||
// Pass 2b: force-lower the stdlib build driver `default_pipeline` (in the
|
||||
// flat-imported `modules/build.sx`, so NOT in the main `decls` above). The
|
||||
// compiler auto-invokes it post-codegen when no `#run on_build(...)` override
|
||||
// exists, but nothing CALLS it from sx — so without this it stays a bodiless
|
||||
// stub the build VM can't run. No-ops when build.sx isn't imported.
|
||||
self.lazyLowerFunction("default_pipeline");
|
||||
// Pass 3: lower deferred functions (any_to_string etc.) now that all types are registered
|
||||
self.lowerDeferredTypeFns();
|
||||
// Pass 4: target-specific entry-point sanity checks
|
||||
@@ -1457,7 +1471,7 @@ pub fn lowerMainAndComptime(self: *Lowering, decls: []const *const Node) void {
|
||||
// consumption (often never called from sx), so force-lower
|
||||
// them like OS-called entry points — else lazy lowering
|
||||
// leaves them as bodiless `declare` stubs (Phase 2).
|
||||
if (isExportedEntryName(cd.name) or cd.value.data.fn_decl.extern_export == .export_) {
|
||||
if (isExportedEntryName(cd.name) or cd.value.data.fn_decl.extern_export == .export_ or isDefaultBuildPipeline(cd.name)) {
|
||||
self.lazyLowerFunction(cd.name);
|
||||
}
|
||||
} else if (cd.value.data == .comptime_expr) {
|
||||
|
||||
Reference in New Issue
Block a user