P5.6 (macOS): default_pipeline drives bundling; fix issue 0125 (array-format blowup)
build.sx now `#import`s the sx bundler and `default_pipeline` delegates to its
`bundle_main` when a bundle was requested (emit + link, then wrap the binary into
the `.app`/`.apk`); otherwise it just emit+links via the shared `emit_and_link`
core. The Zig `--bundle`/`post_link_module` dispatch shim is removed — the CLI
bundle flags only feed `BuildConfig`, and `default_pipeline` branches on
`bundle_path()`. Validated end-to-end on macOS: `sx build --bundle App.app
--bundle-id … foo.sx` on a plain program AND auto-bundle from `set_bundle_path`
both produce a valid signed `.app` (correct `Contents/MacOS/` layout, Info.plist,
passes `codesign`, binary runs). Also fixed a pre-existing host-build bug:
target_triple was left empty for host builds → `is_macos()` false → wrong flat
layout; main.zig now exposes the host triple when `--target` is absent.
bundle_main no longer re-calls `build_options()` (the handle is already its `opts`
param).
Fix issue 0125 (root cause): the type-match dispatcher unboxed each interned array
tag to the concrete array type — a whole-array load — and passed it to
`array_to_string` by value, which LLVM scalarized into one SelectionDAG node per
element (~12s / segfault at [65536]u8). The bundler's `format("…{}…")` instantiates
`any_to_string`, so importing it into the prelude surfaced 0125 for any large-array
program. Fix (route 1): `any_to_string`'s `case array:` arm calls `slice_to_string`,
and `lowerRuntimeDispatchCall` detects an ARRAY tag bound to a SLICE param and builds
a `{ptr,len}` slice VIEW of the payload pointer (`unbox_any → [*]elem` is an
int-to-ptr with NO load, paired with the array length) instead of loading the array.
Output is byte-identical (`[a, b, c]`). Pinned as
examples/0056-basic-large-array-format-no-blowup.sx; 0055 drops 12s → 0.2s.
37 `.ir` snapshots regenerated (build.sx now pulls in the bundler's types + the
array-format lowering changed); verified `.ir`-only, zero behavior-stream diffs.
705/0 both gates.
This commit is contained in:
@@ -1483,13 +1483,33 @@ pub fn lowerRuntimeDispatchCall(
|
||||
|
||||
self.builder.switchBr(type_tag, cases.items, default_bb, &.{});
|
||||
|
||||
// Whether the cast-arg parameter is a SLICE (`[]$T`). When it is and the tag
|
||||
// is an ARRAY, we pass a slice VIEW of the array's storage rather than loading
|
||||
// the whole array as a value (issue 0125 — the giant load + per-element
|
||||
// SelectionDAG scalarization). The Any payload for an array IS a pointer to its
|
||||
// storage, so `unbox_any → [*]elem` (int-to-ptr, no load) + the array length
|
||||
// gives a `{ptr,len}` slice for free.
|
||||
const cast_param_is_slice = cast_arg_idx < fd.params.len and
|
||||
fd.params[cast_arg_idx].type_expr.data == .slice_type_expr;
|
||||
|
||||
for (match_tags, 0..) |tag, ti| {
|
||||
self.builder.switchToBlock(case_blocks.items[ti]);
|
||||
|
||||
const ty_id = TypeId.fromIndex(@intCast(tag));
|
||||
|
||||
// Unbox the Any value to the concrete type
|
||||
const unboxed = self.builder.emit(.{ .unbox_any = .{
|
||||
// Unbox the Any value to the concrete type — except an ARRAY tag bound to a
|
||||
// SLICE param, which becomes a no-load slice view of the array storage.
|
||||
const tag_is_array = !ty_id.isBuiltin() and self.module.types.get(ty_id) == .array;
|
||||
const unboxed = if (cast_param_is_slice and tag_is_array) blk: {
|
||||
const elem_ty = self.getElementType(ty_id);
|
||||
const arr_len = self.module.types.get(ty_id).array.length;
|
||||
const slice_ty = self.module.types.sliceOf(elem_ty);
|
||||
const ptr_ty = self.module.types.manyPtrTo(elem_ty);
|
||||
// The Any payload (the array's storage address) → `[*]elem` (no load).
|
||||
const ptr = self.builder.emit(.{ .unbox_any = .{ .operand = any_val } }, ptr_ty);
|
||||
const len = self.builder.constInt(@intCast(arr_len), .i64);
|
||||
break :blk self.builder.structInit(&.{ ptr, len }, slice_ty);
|
||||
} else self.builder.emit(.{ .unbox_any = .{
|
||||
.operand = any_val,
|
||||
} }, ty_id);
|
||||
|
||||
|
||||
64
src/main.zig
64
src/main.zig
@@ -747,7 +747,17 @@ fn compileWithTimer(allocator: std.mem.Allocator, io: std.Io, input_path: []cons
|
||||
// branching (iOS device vs simulator vs macOS) and `Frameworks/`
|
||||
// embedding. Slice fields point into the long-lived target_config /
|
||||
// CLI argv buffers, which outlive the post-link callback.
|
||||
if (merged_config.triple) |t| e.build_config.target_triple = std.mem.span(t);
|
||||
if (merged_config.triple) |t| {
|
||||
e.build_config.target_triple = std.mem.span(t);
|
||||
} else {
|
||||
// Host build (no `--target`): expose the HOST triple so the sx
|
||||
// bundler's `is_macos()`/`is_ios()`/… predicates resolve correctly.
|
||||
// Left empty, a host macOS `.app` would get the flat iOS-style layout
|
||||
// (is_macos() == false) instead of `Contents/MacOS/`.
|
||||
const host = sx.llvm_api.c.LLVMGetDefaultTargetTriple();
|
||||
defer sx.llvm_api.c.LLVMDisposeMessage(host);
|
||||
e.build_config.target_triple = allocator.dupe(u8, std.mem.span(host)) catch null;
|
||||
}
|
||||
e.build_config.target_frameworks = fws;
|
||||
e.build_config.target_framework_paths = merged_config.framework_paths;
|
||||
// Phase 5: the sx-driven build pipeline reads these via the
|
||||
@@ -790,28 +800,13 @@ fn compileWithTimer(allocator: std.mem.Allocator, io: std.Io, input_path: []cons
|
||||
}
|
||||
}
|
||||
|
||||
// CLI `--bundle <path>` migration shim. The legacy Zig bundler
|
||||
// path (target.createBundle) has been retired; the equivalent
|
||||
// logic now lives in `library/modules/platform/bundle.sx`. If the
|
||||
// user passed `--bundle` on the command line but did NOT register
|
||||
// a post-link callback themselves, point the resolver at
|
||||
// `platform.bundle.bundle_main`. The lookup is best-effort: if the
|
||||
// source doesn't `#import "modules/platform/bundle.sx"`,
|
||||
// `invokeByName` returns null and the existing "not found" branch
|
||||
// prints a clear migration message.
|
||||
if (comp.ir_emitter) |*e| {
|
||||
if (e.build_config.bundle_path != null and
|
||||
e.build_config.post_link_callback_fn == null and
|
||||
e.build_config.post_link_module == null)
|
||||
{
|
||||
e.build_config.post_link_module = "platform.bundle";
|
||||
}
|
||||
}
|
||||
|
||||
// Post-link callback: if the user registered one via
|
||||
// `BuildOptions.set_post_link_callback(fn)` or
|
||||
// `set_post_link_module("name")`, re-enter the IR interpreter and
|
||||
// invoke that sx function now. A `false` return fails the build.
|
||||
// Post-link build driver. Either the user registered an `on_build(cb)`
|
||||
// override (bundling is `#run on_build(bundle_main);` — bundle_main runs the
|
||||
// emit+link core then wraps the `.app`/`.apk`), or we run the stdlib
|
||||
// `default_pipeline` (emit + link; it fails with a precise hint if a bundle was
|
||||
// requested via `--bundle`/`--apk` but no bundler was registered). The CLI
|
||||
// bundle flags only feed `BuildConfig` (bundle_path/id/…) — there is no Zig
|
||||
// bundler shim; bundling is entirely sx-driven. A `false` return fails the build.
|
||||
if (comp.getPostLinkCallback()) |fid| {
|
||||
const ret = comp.invokeByFuncId(fid, comp.getPostLinkTakesOptions()) catch |err| {
|
||||
printInterpBailDiag(&comp, "post-link callback", err);
|
||||
@@ -821,29 +816,8 @@ fn compileWithTimer(allocator: std.mem.Allocator, io: std.Io, input_path: []cons
|
||||
std.debug.print("error: post-link callback returned false\n", .{});
|
||||
return error.CompileError;
|
||||
}
|
||||
} 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, 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);
|
||||
return error.CompileError;
|
||||
};
|
||||
if (ret_opt) |ret| {
|
||||
if (ret.asBool() == false) {
|
||||
std.debug.print("error: post-link module '{s}.bundle_main' returned false\n", .{mod_name});
|
||||
return error.CompileError;
|
||||
}
|
||||
} else {
|
||||
std.debug.print("error: post-link module '{s}.bundle_main' not found\n", .{mod_name});
|
||||
return error.CompileError;
|
||||
}
|
||||
} else {
|
||||
// No user/module override → run the stdlib default build pipeline. The
|
||||
// compiler force-lowers `default_pipeline` (well-known name); it emits +
|
||||
// links the program. Everything is sx-driven — this is the only build path
|
||||
// when the user hasn't overridden it.
|
||||
// No override → run the force-lowered stdlib `default_pipeline`.
|
||||
const ret_opt = comp.invokeByName("default_pipeline", true) catch |err| {
|
||||
printInterpBailDiag(&comp, "default build pipeline", err);
|
||||
return error.CompileError;
|
||||
|
||||
Reference in New Issue
Block a user