Drive a bundled `zig` as `zig cc` for the AOT link step, supplying lld + CRT + libc (musl/glibc/mingw) so `sx build` produces native binaries with no host toolchain. Default Linux output is static musl (portable-anywhere). - src/zig_backend.zig: discover zig ($SX_ZIG / bundled-next-to-exe / PATH); bundled-vs-PATH provenance gates auto-activation. - src/target.zig: selectZigLinker + emitZigLinkArgv + zigTargetTriple, dispatched before the per-OS branches; macOS/Linux/Windows in scope. - src/ir/emit_llvm.zig: LLVMNormalizeTargetTriple so vendor-less zig triples (e.g. x86_64-windows-gnu) parse to the correct OS/object format (COFF not ELF). - src/main.zig: --self-contained / --no-self-contained; linux-musl, linux-musl-arm, windows-gnu shorthands; de-vendor linux/linux-arm to match the corpus runner. - examples/1660: Windows Win32 print-42 + exit(0) via kernel32 (ir-only off-Windows). Auto-activates only for a bundled zig; a PATH-only zig engages under --self-contained, so native dev/CI builds are never silently rerouted. Docs: readme Cross-Compilation, design/bundled-zig-link-backend-design.md, current/PLAN-DIST.md.
5.7 KiB
PLAN-DIST — bundle zig as sx's hermetic link/libc backend
Goal
sx build produces a native binary by driving a bundled zig
(zig cc) as the linker, so a distributed sx on Linux needs no system
cc/lld/libc/CRT. sx run (JIT) is unaffected — it never links.
This is the "be like Zig" move: reuse Zig's hermetic toolchain (lld +
crt objects + musl/glibc, all bundled in the zig distribution) instead
of building our own lld-in-process + libc-from-source pipeline.
Configuration surface (env vars, flags, resolution order, activation truth table, target→ABI map, distribution layout) is specified in ../design/bundled-zig-link-backend-design.md — the design-of-record for how the backend is configured. Keep the two files in sync.
Locked decisions
- Default Linux output ABI = static musl (
x86_64-linux-musl,-static). Output runs on ANY Linux with zero deps — the property that makes Zig binaries portable. glibc/dynamic only via explicit--target x86_64-linux-gnu. - Activation = auto when a bundled/resolvable
zigexists AND the user passed no--linker. Falls back to systemccotherwise. - Dev uses PATH
zig(0.16.0 already installed). Defer copying a vendored toolchain intolibexec/until Phase 3 packaging.
Why zig cc, not raw ld.lld
zig cc is a clang-compatible driver, so it slots into the existing
cc-style argv branch in src/target.zig almost unchanged, and supplies
lld + crt objects + musl/glibc automatically per -target. Driving
ld.lld directly would force us to locate/pass crt1.o/crti.o/libc
ourselves — exactly the work we're avoiding.
Key code anchors (verified)
- Linker selection hook:
TargetConfig.getLinker()—src/target.zig:194-196(self.linker orelse "cc"). - Unix
cc-style link branch:src/target.zig:524-564(this is where the zig backend hooks in;-o/-L/-l/extra objects already pass through clang-compatibly). - Exe-relative resolution pattern to mirror for finding zig:
src/imports.zig:204-227(discoverStdlibPaths,$SX_STDLIB_PATHoverride +<exe>/..candidates). --linkerCLI flag parsing:src/main.zig:87-90.- Emit triple (must agree with link target):
src/ir/emit_llvm.zig(LLVMSetTarget, ~L246-284).
Phases
Phase 0 — Resolve a bundled/host zig
- New
src/zig_backend.zig:discoverZig(alloc) -> ?[]const u8. Resolution order:$SX_ZIGenv override.<exe>/../libexec/zig/zig(install layout, Phase 3).<exe>/../../zig-bundle/zig(dev vendored layout, Phase 3).zigonPATH(dev fallback — active now).
- Add
SX_DEBUG_ZIGtrace, matching existingSX_DEBUG_*hooks. - No behavior change yet; just resolution + a debug/print hook to confirm.
Phase 1 — zig cc link backend (core change)
src/target.zig: generalize the linker from a single token to a driver argv. TodaygetLinker()returns one string atargv[0]; introduce aLinkBackendso the internal backend contributes{zigPath, "cc"}as leading entries.- In the Unix branch (L524-564), when backend = zig:
- prepend
zig cc, - append
-target <mapped triple>, - add
-staticfor musl, - everything else (
-o,-L,-l, extra objects, extra link flags) passes through unchanged.
- prepend
- Add
sxTripleToZig()mapping (sx shorthand/triple → zig-target); unspecified-on-Linux →x86_64-linux-musl. - Align emit triple: when the zig backend is selected, set the LLVM
module triple in
emit_llvm.zigto match the link target (x86_64-linux), so the.olinks cleanly against musl crt.
Phase 2 — Activation
- Auto-enable: if
discoverZig()succeeds and no--linkeroverride, use the zig backend forsx build. Systemccremains the fallback. - Optional explicit
--self-contained/--no-self-containedto force. - Confirm
sx run/JIT path is untouched (no link step).
Phase 3 — Distribution packaging
build.zig: adiststep assemblingbin/sx(built with-Dstatic-llvm),libexec/zig/(vendored zig binary and itslib/, copied from a pinned ziglang.org release per host arch),library/(stdlib), into a relocatable tarball.
- Pin the zig version (currently 0.16.0).
Phase 4 — Verify & lock
- Manual first:
sx build hello.sx(auto zig backend) thenfile/lddthe output → expect "statically linked". - Honor snapshot-integrity + FFI-cadence rules before adding a corpus
test (host/arch-gated, likely a
.buildsidecar).
Risks / watch
- Bundle size: zig + its
lib/≈ 50–60 MB. - gnu vs musl ABI: pure codegen objects link fine against musl; TLS/stack-protector are the only realistic friction. Aligning the emit triple (Phase 1) covers the common path.
- macOS/Windows cross via the same
zig cc -targetis nearly free after Phase 1, but Apple-SDK linking has caveats — scope to Linux target first; treat the rest as follow-up. - c_import.zig also shells
ccfor C imports (JIT). Out of scope here; same backend can absorb it later.
Status
- Phase 0 — resolve zig (
src/zig_backend.zig) - Phase 1 — zig cc link backend (
target.zig+emit_llvmtriple normalize) - Phase 2 — activation (
--self-contained/--no-self-contained; auto on bundled zig) - Phase 3 — dist packaging (vendor
zigintolibexec/) - Phase 4 — verify & lock (manual ✓ macOS/Linux/Windows; corpus test pending runner
--self-containedsupport)
Scope landed as macOS + Linux + Windows (not Linux-first). See the "Implementation status" section in ../design/bundled-zig-link-backend-design.md for what refined the original locked decisions.