Commit Graph

1294 Commits

Author SHA1 Message Date
agra
7044b8133b fibers: carve Stream B1 (PLAN-FIBERS + CHECKPOINT-FIBERS)
Carve the async-runtime fibers stream off PLAN-POST-METATYPE Stream B,
mirroring the atomics carve. Grounds the B1 compiler floor against the
tree:

- abi(.pure) exists in the ABI enum but is inert (type_resolver maps it
  to .default CC, emit emits no naked attr) -> B1.0 makes it emit LLVM
  naked + skip prologue/ctx. Corrected the design's callconv(.naked)
  spelling to the real abi(.pure).
- context is already an implicit *Context param (slot 0) + push Context
  is a stack alloca -> fiber-local for free; only shared root is the
  __sx_default_context global. B1.1 grounded as likely library-only
  (probe-first).
- B1.0 snapshot story corrected: naked body is raw per-arch asm -> two
  arch-gated examples (aarch64 + x86_64), not one host .ir.

Full xfail->green step detail + a B1.0a kickoff prompt. Baseline green
(721/0). No code change; first implementation step is B1.0a.
2026-06-20 14:16:39 +03:00
agra
3fad2d5a21 issue 0144: unrecognized $T-param #builtin silently returns 0
A bodiless #builtin with a $T: Type parameter that no recognizer matches folds
to 0 (exit 0) instead of erroring — while a non-type-param #builtin link-errors
loudly. Discovered during the atomics stream (Atomic methods ran to 0 before
recognition existed). The reflection/type-arg lowering path defaults instead of
rejecting (REJECTED-PATTERNS silent-fallback class). Repro + investigation prompt
in the issue. Open (unpinned — not added to the suite, since the repro currently
exits 0 by the bug).
2026-06-20 14:09:41 +03:00
agra
9bcb4159ef atomics: close out Stream A (feature-complete)
Final whole-stream adversarial review came back CLEAN (no CRITICAL/MEDIUM/LOW).
Close the one informational gap it noted: extend examples/1703 with a #run
comptime swap so swap's comptime VM arm is locked (742, matches runtime) — every
op now has comptime↔runtime corpus coverage.

Docs: PLAN-ATOMICS.md status banner (COMPLETE); PLAN-POST-METATYPE.md Stream A
marked done (unblocks B2-channels + C-parallel); readme.md gains a user-facing
Atomics section. Suite green (721/0).
2026-06-20 14:02:41 +03:00
agra
b65544a68c atomics A.3b: real swap (xchg) + fence emission + unit test (green)
emitAtomicRmw xchg arm (swap) and emitAtomicFence (LLVMBuildFence) now real.
examples/1703 (swap old=7/now=42, 'atomicrmw xchg') + 1704 (fence release/acquire/
seq_cst) green. Unit test 'emit: atomic swap (xchg) + fence'. Stream A
(atomics) is feature-complete: load/store, RMW (add/sub/and/or/xor/min/max),
compare_exchange[_weak], swap, fence. Suite green (721/0).
2026-06-20 13:51:36 +03:00
agra
fca4304f83 atomics A.3a: swap + fence ops + recognizer, emit bails (lock)
swap (atomicrmw xchg) and a standalone fence wired end-to-end except LLVM
emission (both bail loudly; A.3b makes them real).
- RmwKind += xchg; atomic_swap intrinsic + swap method reuse the atomic_rmw op.
- new atomic_fence op (+ AtomicFence) — ordering-only, void; fence($o)/atomic_fence
  intrinsic; recognizer rejects .relaxed (LLVM has no monotonic fence).
- comptime_vm: xchg = store operand/return old; fence = no-op (single-thread).
- examples 1703 (swap) + 1704 (fence) locked to bails; 1187 (relaxed-fence reject).
- 1186 converted to a direct-intrinsic call → stable user-file diagnostic span
  (the lib-forward-site span shifted when atomic.sx grew — fragile-snapshot fix).

Also fixes a latent A.2 comptime-CAS bug found while here: the success/null
has_value write was 'writeWord(addr, SIZE=0, val=1)' — a 0-byte no-op, correct
ONLY because allocBytes zero-inits (REJECTED-PATTERNS 'coincidentally correct').
Now writes the flag explicitly (size=1, val=0). Suite green (721/0).
2026-06-20 13:47:08 +03:00
agra
79895be401 atomics A.2b: real CAS emission (cmpxchg) + unit test (green)
emitAtomicCmpxchg: LLVMBuildAtomicCmpXchg (success/failure orderings,
singleThread=0) returns a {T, i1} pair; LLVMSetWeak for the weak variant. The
sx ?T result (null = SUCCESS) is built as { extractvalue 0 (actual value),
xor(extractvalue 1 (success), true) } -- has_value = NOT success. Integer-only
(recognizer guard), so never a pointer/niche optional.

examples/1702 green: successful CAS returns null (value updated), failing CAS
returns the actual value (unchanged), weak retry loop increments a counter
(100 -> 105). LLVM IR shows `cmpxchg ... acq_rel acquire` and `cmpxchg weak`.
Unit test `emit: atomic cmpxchg (strong + weak)` locks `cmpxchg` + the weak
marker. Suite green (718/0).
2026-06-20 10:57:01 +03:00
agra
dca396ed1f atomics A.2a: CAS ops + recognizer + methods, emit bails (lock)
compare_exchange/_weak wired end-to-end except LLVM emission (bails loudly;
A.2b makes it real). New IR op atomic_cmpxchg + AtomicCmpxchg{ptr, cmp, new,
val_ty, success_ordering, failure_ordering, weak}; result type = ?T (null =
SUCCESS, failure carries the actual value for retry). print arm; emit dispatch
-> emitAtomicCmpxchg (BAILS). comptime_vm arm does real single-thread CAS (read
actual / compare / store-on-equal / build ?T: success->none, failure->some;
weak == strong at comptime). Recognizer extended (atomic_cmpxchg/_weak, 6 args)
-- CAS restricted to INTEGER T (loud reject); BOTH orderings resolved via
atomicOrderingFromNode; dual-ordering validation (failure may not be
release/acq_rel nor stronger than success, via atomicOrderingRank). Methods
compare_exchange/_weak on Atomic($T) with comptime $success/$failure: Ordering.
examples/1702 locked to the bail; examples/1186 locks a rejected ordering pair.
Suite green (718/0).
2026-06-20 10:44:31 +03:00
agra
68ed732b79 atomics A.1c: fix comptime signed fetch_min/max (was unsigned compare)
Adversarial review CRITICAL: the comptime VM's atomic_rmw min/max arm called
@max/@min directly on Reg (=u64) values for SIGNED types, doing an UNSIGNED
compare — so comptime fetch_min/max on negatives diverged from the runtime LLVM
atomicrmw min/max (signed). Fix: reinterpret as i64 in the signed branch before
comparing, bitcast back (mirrors the unsigned branch + the emit-side signedness).

Closes the coverage gap that hid it: extend examples/1701 with signed min/max on
a negative at BOTH comptime (#run) and runtime — they now agree (3 / -5). Suite
green (716/0).
2026-06-20 10:32:50 +03:00
agra
05311646aa atomics A.1b: real RMW emission (atomicrmw) + unit test (green)
emitAtomicRmw: LLVMBuildAtomicRMW (binop from RmwKind; signed Min/Max vs
unsigned UMin/UMax from val_ty; singleThread=0; LLVM supplies ABI alignment).
examples/1701 green (add/sub/and/or/xor/min/max return old values, results
verified). Unit test 'emit: atomic rmw (add + signed/unsigned min)' locks
'atomicrmw add' + signed 'min' vs unsigned 'umin'. Suite green (716/0).
2026-06-20 10:19:44 +03:00
agra
718f27e27f atomics A.1a: RMW ops + recognizer + methods, emit bails (lock)
fetch_add/sub/and/or/xor/min/max wired end-to-end except LLVM emission (bails
loudly; A.1b makes it real). New IR op atomic_rmw + RmwKind (no nand) +
AtomicRmw{ptr, operand, val_ty, ordering, kind}. print arm; comptime_vm arm
implements real single-thread RMW (load/compute/store/return-old, signed|unsigned
min/max from val_ty). Recognizer extended (rmwKindFromName) — RMW restricted to
integer T (float fadd / pointer RMW out of scope, rejected loudly); all orderings
valid for RMW. Methods fetch_* on Atomic($T) with comptime $o: Ordering.
examples/1701 locked to the bail. Suite green (716/0).
2026-06-20 10:14:49 +03:00
agra
acf31839ea atomics A.0.5: full ordering surface (comptime $o: Ordering)
Migrate Atomic methods from seq_cst-only to the explicit ordering surface now
that comptime value params work on generic-struct methods (workers 3c4305f /
d7a6857 / d95ba0a):

- atomic.sx: load/store take a comptime $o: Ordering (explicit, Rust-style; no
  default, matching design 4.6). a.load(.acquire) -> 'load atomic .. acquire'.
- call.zig: atomicOrderingFromNode resolves a comptime-bound ordering identifier
  via comptimeIntNamed (+ atomicOrderingFromTag); documents the sx-Ordering <->
  IR-AtomicOrdering declaration-order invariant. The per-op validity guard fires
  through the method path (a.load(.release) is a compile error).
- 1700 migrated to explicit orderings (output unchanged 7/42/43).

Suite green (715/0).
2026-06-20 10:04:39 +03:00
agra
d95ba0a937 comptime value params: bind on generic-struct methods
A free function's $o comptime value param binds via lowerComptimeCall →
bindComptimeValueParams. The generic-struct-instance method path
(b.pick(.b)) took a different dispatch route: genericInstanceMethod →
ensureGenericInstanceMethodLowered emitted a plain call to the
monomorphized FuncId, never checking hasComptimeParams — so the method's
$o was never bound and lowered to 'unresolved o'.

Fix: when the selected generic-instance method declares comptime params,
route through the new lowerComptimeGenericInstanceMethod, which composes
the two mechanisms — installs the struct instance's type_bindings (so T /
*Box(T) resolve), pre-binds the receiver self as a normal pointer-param
alloca (so self.field reads work in the inlined body), then routes the
remaining ($) params through lowerComptimeCallArgsSkip(skip_params=1).
That reuses bindComptimeValueParams, so comptimeIntNamed /
comptimeValueRefNamed resolve the value param inside the method body,
identically to the free-function path.

lowerComptimeCall is refactored into lowerComptimeCallArgs(Skip) cores
parameterized over the effective arg-node slice + a leading skip count;
the original free-call entry point is unchanged behaviorally.

Loud-diagnostic behavior preserved: a non-constant / unknown-variant arg
still emits the value-param diagnostic, never a silent default. Int value
params ($n: i64) remain unbound — a pre-existing limitation shared with
free functions, orthogonal to this fix.

Locks examples/0642 (enum + tagged-union comptime value params on a
generic-struct method, incl. self.field read and comptimeIntNamed via a
type-position [o]i64).
2026-06-20 09:57:15 +03:00
agra
d7a6857ee1 comptime value params: generalize to tagged_union (+ aggregate hook)
`$s: <TaggedUnion>` now binds a constant variant-literal argument as a
compile-time-known value and resolves it in the inlined body — the
payload-bearing generalization of the enum value param (3c4305f). A bare
variant (`.point`) or a payload variant (`.circle(5.0)`) both bind:

  * the variant TAG goes into `comptime_value_bindings` (i64), so
    `comptimeIntNamed`/`if s == .circle` keep working and the param is
    readable in a TYPE position (`[s]i64`);
  * the full materialized `enum_init(tag, payload)` value goes into a new
    `comptime_value_ref_bindings` (param -> Ref) AND is scoped, so a
    payload read off the bound value (`s.rect`) resolves. A new
    lowering-time accessor `comptimeValueRefNamed(param)` reads it.

`bindEnumValueParams` is generalized to `bindComptimeValueParams`, which
switches on the constraint kind: `.@"enum"` -> tag-only bind,
`.tagged_union` -> tag + value bind. Other value kinds (struct/array
aggregates) are left with an explicit `else` (no silent default) and a
comment marking where the aggregate-const arm goes when a repro lands; a
non-constant arg / unknown variant is a loud, well-spanned diagnostic.

Locked by examples/0640-comptime-tagged-union-value-param.sx (bare +
payload variants, tag comparison, tag-as-dimension, payload read).
0627 (enum) stays green.
2026-06-20 09:39:10 +03:00
agra
8144a88a21 atomics A.0c: harden guards (scalar-kind, ordering validity, align bail)
Adversarial review of A.0 found two silent-wrong defects reachable via the public
atomic_load/atomic_store intrinsics (raw LLVM verifier errors, not clean sx
diagnostics) + a latent alignment fallback. All fixed:

- scalar-kind allowlist (call.zig): the size-only T guard admitted same-sized
  aggregates ([8]u8, 8-byte structs) -> invalid 'load atomic [8 x i8]'. Now an
  allowlist switch (integer/float/bool/pointer/enum/vector) rejects loudly.
- per-op ordering validity (call.zig): load cannot release/acq_rel, store cannot
  acquire/acq_rel -> loud diagnostic instead of invalid LLVM.
- val_ty align fallback (ops.zig): the 'else .i64' (align 8) default would
  over-align a sub-8 store -> now bails loudly on a missing val_ty.

Locked by examples 1130 (non-scalar) + 1131 (bad ordering). Suite green (713/0).
2026-06-20 09:26:53 +03:00
agra
3c4305f78f comptime enum value params: $o: EnumType binds+resolves variant tag
A comptime value param whose constraint is a plain enum ($o: Ord) now
binds its enum-literal argument to the variant tag during inlined
comptime-call lowering. The tag is recorded in comptime_value_bindings
(readable downstream via comptimeIntNamed / direct map lookup, and as an
array-dim style const-int leaf) AND the param is bound into scope as an
enum_init value so body comparisons like 'if o == .a' lower as ordinary
enum comparisons. Distinct ordering args monomorphize the inlined body
per value.

A non-constant argument or an unknown variant emits a loud diagnostic
and binds nothing — never a silent default.

Locked by examples/0627-comptime-enum-value-param.sx.
2026-06-20 09:19:18 +03:00
agra
64c7db5eb1 atomics A.0b: real seq_cst load/store emission (green)
Replace the A.0a emit bail with real LLVM atomic codegen:
- emitAtomicLoad: LLVMBuildLoad2 + LLVMSetOrdering + LLVMSetAlignment
- emitAtomicStore: LLVMBuildStore + LLVMSetOrdering + LLVMSetAlignment (value
  coerced to the pointee type, mirroring emitStore)
- llvmOrdering: explicit sx AtomicOrdering -> LLVMAtomicOrdering map (LLVM's enum
  is non-contiguous; never an identity cast)

examples/1700 now prints 7/42/43; IR is 'load atomic i64, ptr .. seq_cst, align 8'
+ 'store atomic ..'. Unit test 'emit: atomic load/store (seq_cst, aligned)' locks
the emission shape (load atomic/store atomic/seq_cst/align 8) without a fragile
full-module .ir snapshot. Suite green (710 examples + units).
2026-06-20 09:08:05 +03:00
agra
22af40413d atomics A.0a: lib + IR ops + recognizer, emit bails (lock commit)
Stream A (atomics) foundation. Net-new atomic load/store codegen path, wired
end-to-end except LLVM emission, which deliberately bails loudly so the example
locks to a clean diagnostic (A.0b turns it green — cadence: no commit both adds a
test and makes it pass).

- library/modules/std/atomic.sx: Ordering enum, Atomic($T) transparent wrapper
  (init/load/store, seq_cst-only for now), atomic_load/atomic_store #builtin
  intrinsics. Opt-in import, NOT in the universal std facade (Ordering in the
  prelude grows every program's type table + churns 37 .ir snapshots).
- IR: atomic_load/atomic_store ops + AtomicOrdering (all 5) + structs (inst.zig);
  print arms; comptime_vm arms reuse load/store (single-thread correct);
  recognizer tryLowerAtomicIntrinsic (const-ordering + scalar-size guards, both
  loud); emit dispatch -> emitAtomicLoad/Store bail via comptime_failed.
- examples/1700-atomics-load-store.sx locked to the bail diagnostic.

Full ordering surface (a.load(.acquire)) blocked on comptime-constant ordering
propagation (comptime enum value params) — A.0.5, migrated not legacy.
2026-06-20 08:47:07 +03:00
agra
ad1687c692 plan: correct grounded errors + harden async streams (post-metatype review)
Fold the adversarial-review corrections into the program plan + design-of-record:
- atomics is 100% net-new (no scaffolding; lower.zig 'ordering' is comparison-only)
- context is already an implicit *Context param (not TLS) — B1.1 rescoped
- abi(.pure) exists but is inert (no naked emission) — B1.0 rescoped
- B1.3 switch-stress harness is the first deliverable + mandatory stack guards
- Stream C gated on a named TSan/ASan + run-N stress harness, not a footnote
2026-06-20 08:47:07 +03:00
agra
f81d101fae checkpoint: P5.8 — Android + iOS-sim validated on emulator/simulator 2026-06-19 22:32:32 +03:00
agra
2ba36f6562 P5.8: add an Android .apk bundle smoke test to the corpus (first Android bundler coverage)
Mirror the macOS .app smoke test (1665) for Android. New `.build` `apk`
directive (ApkCheck = { out, bundle_id, expect }) cross-compiles via
`sx build --target android --apk ... --bundle-id ... -o lib*.so`, then
asserts the produced APK's zip entries (AndroidManifest.xml, classes.dex,
lib/arm64-v8a/) via `unzip -l`. Build+inspect only — aarch64-linux-android
can't execute on the host, so no exit/stdout/stderr snapshot; the apk
branch is self-contained and never falls through to stream comparison.

Gated on the Android SDK ($ANDROID_HOME / $ANDROID_SDK_ROOT /
~/Library/Android/sdk) AND a real JDK (`javac -version` exit 0 — the
macOS /usr/bin/javac stub fails the gate). Missing either → skip cleanly,
so a bare-host `zig build test` stays green. Cleanup rm -rf's the apk,
staged .so, .stage dir, .unaligned/.aligned intermediates, and the
apksigner .idsig sidecar.

Verified: default `zig build test` skips 1666 (709 examples ran, 0 failed;
476/476 unit). With JAVA_HOME set to Android Studio's jbr, 1666 RUNS and
PASSES (apk built + all three entries found).
2026-06-19 22:28:56 +03:00
agra
d8fb42501d comptime VM: support optional-of-word extern returns + string/any struct_init
Two host-FFI gaps surfaced by the sx Android bundler running on the VM
(default_pipeline calls env() -> getenv() -> ?cstring, and from_cstring builds
a string literal):

- callHostExtern: an extern returning an OPTIONAL whose child is a single
  register word (e.g. getenv() -> ?cstring) now wraps the bare C payload word
  into the {payload@0, has@sizeof(child)} optional aggregate (present iff
  non-null), mirroring emit_llvm's char*->?cstring handling. Previously bailed
  'non-word return'. The non-word bail now names the symbol + return type.
- struct_init: the builtin two-word aggregates string ({ptr,len}) and any
  ({tag,value}) can now be struct_init'd (e.g. string.{ ptr=, len= } in
  from_cstring). Previously bailed 'struct_init at a builtin result type'.

These let the full Android .apk bundling pipeline (javac/d8/aapt2/zipalign/
apksigner) run on the comptime VM. 709/0 corpus + 476/476 unit.
2026-06-19 22:14:57 +03:00
agra
3014c61236 docs: note [*]T does not implicitly coerce to []T (slice with ptr[0..len]) 2026-06-19 21:45:56 +03:00
agra
310461f651 checkpoint: P5.7 done — comptime VM is the sole evaluator, zero legacy
Update CHECKPOINT-COMPILER-API: Resume banner + Log entries for Step D
(metatype declare/define re-expressed as sx over the compiler-API) and the
empty-member-types-valid change. 709/0 corpus + 476/476 unit.
2026-06-19 21:45:27 +03:00
agra
538349611e comptime: empty-member types are valid for all kinds; keep never-defined declare rejected
A comptime-constructed type with NO members is now VALID for every kind
(empty struct, empty tuple, empty enum, empty tagged_union) — only a bare
`declare("X")` placeholder that is never completed by a matching `define`
stays rejected (it would panic codegen).

- comptime_vm.zig registerTypeVm: drop the blanket "a type with no members
  is never valid" rejection. The per-kind loops are vacuous for an empty
  member list and the dup-name checks stay correct.
- types.zig TaggedUnionInfo: add `defined: bool = true`. Every real
  construction (normal unions, error sets, register_type completion) is
  "defined" by default; only the two declare-PLACEHOLDER sites set it false:
  comptime_vm.declareNominal and lower/comptime.preregisterForwardTypes.
- lower/comptime.checkComptimeTypeResult: reject on `!defined` (never-defined
  placeholder) instead of `fields.len == 0`, so an explicitly-defined empty
  union passes through while a never-completed declare is still gated.
- types.zig typeSizeBytes(tagged_union): floor the payload area at 8 bytes
  when no field carries a payload, mirroring the LLVM lowering — fixes a
  verifySizes panic on an empty/all-void tagged_union (IR sized to tag-only,
  LLVM laid out tag + [8 x i8]).

Tests:
- examples/1179: repurposed from "empty enum rejected" (now valid) to the
  never-defined `declare` case (the remaining rejection); preserves its
  issue-0140 regression role.
- examples/1180 (duplicate variant): still rejected, unchanged output.
- examples/0641 (new): construct empty struct/tuple/enum/tagged_union via
  define/declare; instantiate the constructible ones; exit 0.
2026-06-19 21:41:07 +03:00
agra
ccba704378 P5.7 Step D: delete dead .define builtin arm, defineFromInfo, decodeTypeSlice
Now that define() is sx over register_type, remove the bespoke metatype define
surface from the comptime VM: the .define callBuiltinVm arm, the defineFromInfo
helper (kind-branching minting), and decodeTypeSlice (its only caller). Remove the
BuiltinId.define enum member. The .declare/.define interceptions in lowering and
their BuiltinIds are now gone; only type_info/field_type remain as metatype
builtins. register_type/decodeMemberSlice stay (shared by the sx define and the
compiler-API graph builder).
2026-06-19 21:14:05 +03:00
agra
7b1d8ceb83 P5.7 Step D: re-express metatype define() as sx over register_type
define(handle, info) is now an ordinary sx fn in modules/std/meta.sx: it matches
the TypeInfo union and calls the abi(.compiler) register_type primitive with the
matching kind code, decoding the variant/field/element list into []Member. An
all-void enum variant set registers as kind 2 (actual enum); any payload variant
as kind 3 (tagged_union).

To support matching the TypeInfo VALUE in the comptime VM, added tagged-union
value support: kindOf now treats tagged_union as a by-address aggregate, enum_tag
reads the tag word at offset 0, and a new enum_payload arm reads the active
payload at tag_size (both bail loudly on backing_type unions, whose layout
differs). register_type's duplicate-name diagnostics now include the offending
name. Dropped the define interception in tryLowerReflectionCall; the .enum(...)
arg infers TypeInfo from the sx fn's param type via the ordinary call path.

Regenerated 1179/1180 diagnostic snapshots (same span/line; the message now
names register_type instead of define()). define/type_info builtins still exist
pending dead-code removal.
2026-06-19 21:09:18 +03:00
agra
8850fcce70 P5.7 Step D: re-express metatype declare() as sx over declare_type
declare(name) is now an ordinary sx fn in modules/std/meta.sx that calls the
abi(.compiler) declare_type primitive — both mint/find the same forward nominal
slot. Removed the bespoke .declare arm from callBuiltinVm and the BuiltinId.declare
member; dropped the declare interception in tryLowerReflectionCall (the call now
routes to the sx fn). preregisterForwardTypes still scans for the literal
declare("Name") spelling so *Name self-references forward-register before the
body lowers (0618). define/type_info/field_type remain builtins.
2026-06-19 20:58:34 +03:00
agra
61f5700a36 P5.7 Step E: fix issue 0141 (reject silent [*]T -> []T coercion); land regression
The 0141 repro relied on a silent-wrong coercion: passing List.items (a
[*]T many-pointer, no length) to a []T parameter passed the bare 8-byte
pointer into a 16-byte {ptr,len} slot — garbage .len, at comptime a segfault
in the VM slice decoder (decodeMemberSlice), at runtime an LLVM verify failure.

Fix (root cause): classify [*]T -> []T as many_to_slice_reject in
conversions.zig and emit a build-gating diagnostic in coerce.zig telling the
user to slice with a length (ptr[0..len]). Guard runComptimeTypeFunc to skip
VM eval once diagnostics.hasErrors() — a type-fn body that failed coercion
holds malformed comptime data (a real host Addr) that would fault the VM's
Ref-level guards.

Land the corrected feature as examples/0640 (List-grown comptime enum via
vs.items[0..vs.len] -> green=7) and the rejection as
examples/1183-diagnostics-many-pointer-to-slice-rejected. Mark issue 0141
RESOLVED.

708/0 corpus + 476/476 unit.
2026-06-19 20:40:21 +03:00
agra
7b8be86834 P5.7 Step C: delete interp.zig — the comptime VM is the sole evaluator
The legacy tagged-Value Interpreter is gone. Relocate the Value result-DTO
+ decodeVariantElements into a new comptime_value.zig (the VM<->host
materialization boundary); repoint comptime_vm/emit_llvm/ir-barrel Value to
it and BuildConfig to compiler_hooks; delete the dead valueToReg bridge;
slim compiler_lib.zig to just the name registry (BoundFn{sx_name} + bound_fns
+ findFn — weldedCompilerFn only validates names); simplify printInterpBailDiag
to comptime_vm.last_bail_reason; drop the unused interp_mod import in lower.zig.
rm src/ir/interp.zig + interp.test.zig.

Value is relocated (not eliminated): it survives only as the slim result DTO
at the VM->valueToLLVMConst boundary; the execution-time marshaling the VM
pivot targeted is gone. Drop dead Value.asString/reflectTypeId.

706/0 corpus + 476/476 unit.
2026-06-19 20:05:57 +03:00
agra
103a156b26 P5.7 Step C2b: drop the Interpreter materialization context from emit_llvm
valueToLLVMConst / serializeAggregateValue took a *const Interpreter only to
resolve .heap_ptr data fields via interp.heapSlice — but the VM's regToValue
never produces .heap_ptr (it's interp-internal). Drop the param, remove the
dead interp_inst in emitGlobals, and drop the .heap_ptr fat-pointer data arm
(falls through to the loud bail). Remove the now-unused Interpreter alias.

500/500 unit + 706/0 corpus.
2026-06-19 19:52:44 +03:00
agra
cd8608c10c P5.7 Step C2a: fold inline comptime calls on the VM (ops.zig)
The emitCall inline comptime-call fold (zero-arg comptime callee -> constant)
was the last backend use of the legacy Interpreter. Route it through
comptime_vm.tryEval; a bail falls through to the normal call path. Drop the
interp_mod/Interpreter imports from ops.zig.

500/500 unit + 706/0 corpus.
2026-06-19 19:38:54 +03:00
agra
4d9f73f506 P5.7 Step C1: evaluate #insert on the comptime VM (sole evaluator)
evalComptimeString (the #insert lowering-time site) was the last user of
the legacy Interpreter.call. Route it through comptime_vm.tryEval instead:
the VM is hardened to bail (never panic) on malformed lowering-time IR
(0737's ret Ref.none), and regToValue dupes the result string into the
lowering allocator so it outlives the VM arena. Drop the now-unused
interp_mod / build_opts imports from comptime.zig.

500/500 unit + 706/0 corpus.
2026-06-19 17:25:12 +03:00
agra
64eb01918a P5.7 Step B2: remove the #compiler attribute + compiler_expr AST node
The #compiler struct attribute + #compiler-suffixed bodyless methods were
fully superseded by abi(.compiler) (P5.5) — no sx code uses them.

Remove the hash_compiler token (token/lexer/lsp), the is_compiler_struct /
struct_default_compiler parser machinery + the two compiler_expr body-
synthesis branches, the compiler_expr AST variant, and every
.builtin_expr/.compiler_expr switch arm + == .compiler_expr check across
sema/resolver/semantic_diagnostics/generic/decl/call/calls (kept .builtin_expr).
abi(.compiler) is untouched. Delete the obsolete calls.test.zig dispatch test.

500/500 unit + 706/0 corpus.
2026-06-19 17:18:45 +03:00
agra
e2971f272c P5.7 Step B1: remove the compiler_call IR op + the hook Registry
The compiler_call op + #compiler hook mechanism was fully superseded by
abi(.compiler) VM-native dispatch (P5.5) — no sx code emits it anymore.

Remove: the compiler_call op variant + CompilerCall struct (inst.zig); the
Builder.compilerCall emitter (module.zig); the two dead producer blocks in
lower/call.zig (compiler_expr-bodied free fns + methods); every consumer
switch arm (emit_llvm, ops.emitCompilerCall, print, interp dispatch); the
interp.hooks field + init/deinit. Strip compiler_hooks.zig down to the still-
live BuildConfig / BuildHooks / AssetDir (delete HookError/HookFn/Registry/
registerDefaults + all hookXxx, and the now-unused interp/Value imports).

Test refs that used compiler_call as a sample unported op now use vec_splat.

501/501 unit + 706/0 corpus.
2026-06-19 16:54:38 +03:00
agra
5d25e23143 P5.7 Step A: VM is the sole comptime evaluator at emit-time + type-fn sites (no fallback)
Remove the comptime_flat/need_vm gate and the vm_result-orelse-legacy
fallback from emit_llvm.zig (runComptimeSideEffects + emitGlobals const-init)
and comptime.zig (runComptimeTypeFunc). The comptime VM now always runs;
a bail is always a build-gating diagnostic, never a fallback. Delete the
now-moot entryNeedsVm. runComptimeSideEffects drops the Interpreter entirely
(VM writes #run output direct to fd 1); emitGlobals keeps a fresh interp_inst
only as the valueToLLVMConst materialization context (the regToValue bridge,
removed with interp.zig in a later step).

#insert (evalComptimeString) still routes through the legacy interp — deferred
until interp.zig deletion.

Reconcile 1654: the comptime asm-global #run now reports the VM's clean dlsym
bail instead of the legacy CannotEvalComptime wrapper (exit still 1).

501/501 unit + 706/0 corpus.
2026-06-19 16:44:52 +03:00
agra
ab8f0d41bb checkpoint: macOS .app corpus smoke test done (706/0); top-risk bundler-coverage gap closed 2026-06-19 16:06:20 +03:00
agra
445ae9705c P5.8: add a macOS .app bundle smoke test to the corpus (closes the no-bundler-coverage gap)
The corpus had ZERO bundler coverage (the stream's named top risk). Add a `.build`
`bundle` directive to the corpus runner: after a successful `aot` build it asserts
each `expect` entry exists under the produced `.app` (repo-relative), then `rm -rf`s
it. macOS-host only — the `.app` + codesign are Apple-specific, so the example is
skipped on other hosts.

`examples/1665-platform-macos-bundle-smoke.sx` sets `bundle_path`/`bundle_id` via a
`#run` config; `default_pipeline` auto-bundles (build.sx imports the bundler, no
explicit `on_build` needed). The directive asserts `Contents/MacOS`,
`Contents/Info.plist`, `Contents/_CodeSignature`. Verified: passes on BOTH gates
(the bundler runs on the legacy interp AND the VM), the `.app` is cleaned up, and a
bad `expect` entry correctly fails (the check is not vacuous). Unit test +
CLAUDE.md `.build`-directive docs updated. 706/0 both gates.
2026-06-19 16:06:02 +03:00
agra
224478fabf checkpoint: P5.8 partial — m3te + distribution validated with the new build pipeline 2026-06-19 15:40:51 +03:00
agra
a91b6e8ae0 checkpoint: P5.6 macOS bundling via default_pipeline + 0125 fix done; remaining iOS/Android validation 2026-06-19 15:32:39 +03:00
agra
48eb7bf48a 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.
2026-06-19 15:32:07 +03:00
agra
88730aa337 checkpoint: P5.5 + P5.6 bitwise/shift prereq done; record remaining P5.6 bundler-restructure 2026-06-19 14:21:03 +03:00
agra
994d6498fc P5.6 prereq: port bitwise/shift ops into the comptime VM
`comptime_vm` exec now handles `bit_and`/`bit_or`/`bit_xor`/`bit_not`/`shl`/`shr`
(a new `bitwise` helper next to `arith`), mirroring the legacy interp's i64 model
exactly: the shift amount clamps to `@min(rhs, 63)` and `shr` is an arithmetic
right shift (sign-extending).

These were unported and bailed; the `shr` gap surfaced via the iOS-device bundler
once P5.5 let it run further (1616). With the port, 1616's strict VM run reaches
the real bundler logic and stops only at the genuinely-unavailable iOS runtime on
macOS (`_UIApplicationMain` / no linked binary under `sx run`), as expected.

New corpus test `examples/0639-comptime-bitwise-shift.sx` folds AND/OR/XOR/NOT/
shl/shr/arith-shr as `::` consts — identical on both evaluators. 704/0 both gates.
2026-06-19 14:20:37 +03:00
agra
ba28488d99 P5.5: migrate the 35 BuildOptions accessors off #compiler to VM-native abi(.compiler)
`BuildOptions :: struct #compiler { ...35 methods... }` becomes
`BuildOptions :: struct { }` (an opaque null-sentinel handle) plus 35 free
`ufcs (self: BuildOptions, …) abi(.compiler)` decls in build.sx, each serviced
by a new `comptime_vm.callBuildOptionFn` arm (off `callCompilerFn`). No legacy
`compiler_lib` handler: the names are registered in `bound_fns` with a single
bailing stub only so `weldedCompilerFn` accepts them.

- String lifetime: setters dupe the arg into the persistent `Vm.gpa` (the
  Compilation allocator, threaded into both `tryEval` and `runBuildCallback` —
  not the per-eval VM arena) and write/append to the threaded `BuildConfig`.
  Getters read the field/slice or compute the target predicate from the triple.
- Dispatch routing (Option B): a `#run`/const-init entry that directly calls a
  compiler-domain/welded fn (`emit_llvm.entryNeedsVm`) runs on the VM with no
  legacy fallback regardless of the `-Dcomptime-flat` gate, so gate-OFF stays
  green without a legacy BuildOptions handler (P5.7 retires the legacy interp).
- Mark the 5 `platform/bundle.sx` getter-calling helpers `abi(.compiler)` (they
  are comptime-only bundler code; otherwise their now-welded getter calls trip
  the runtime-call gate).
- 37 `.ir` snapshots regenerated (std transitively imports build.sx → string-
  pool/type-table indices shift); verified `.ir`-only, zero behavior-stream diffs.

BuildOptions `compiler_call` strict bails gone (1609/1614/1615 strict-clean);
1616 now bails on a separate, pre-existing unported bitwise/shift VM gap (`shr`),
to port first in P5.6. 703/0 both gates.

Also sweep the outdated "flat memory" terminology to "comptime/byte-addressable"
across comptime_vm + the plan/checkpoint/CLAUDE docs: the comptime VM is
arena-backed, byte-addressable memory where `Addr` is a real host pointer, not a
flat contiguous address space (flag names `-Dcomptime-flat`/`SX_COMPTIME_FLAT` kept).
2026-06-19 13:21:09 +03:00
agra
af32c3823c plan: final direction — full migration, no legacy; all bundling/codesign in default_pipeline
Records the user's decision: drop gate-OFF entirely (VM is the sole comptime
evaluator; delete interp.zig); migrate BuildOptions directly to VM-native
abi(.compiler) arms with NO legacy handlers; ALL bundling + code signing for
every target (macOS/iOS-device/iOS-sim/Android) lives in the sx default_pipeline;
validate against ~/projects/m3te + ~/projects/distribution. Phase 5 steps
P5.5-P5.8 in PLAN-COMPILER-VM.md.
2026-06-19 09:49:17 +03:00
agra
37c982467b checkpoint: P5.4 core done; record remaining BuildOptions-migration plan 2026-06-19 09:42:45 +03:00
agra
65ac370683 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.
2026-06-19 09:37:05 +03:00
agra
d178454841 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.
2026-06-19 09:22:54 +03:00
agra
1f796e92ec checkpoint: record P5.3 on_build + consolidated P5.4 plan 2026-06-19 08:48:32 +03:00
agra
9cbee5e4bd P5.3: on_build(cb) build-callback registrar; callback takes BuildOptions
Per user design: on_build(build) is the build-callback registrar (a free fn),
generalizing set_post_link_callback — the callback is (opt: BuildOptions) ->
bool and the compiler invokes it post-codegen WITH the BuildOptions handle.

- VM: callCompilerFn 'on_build' arm + legacy handleOnBuild, both set
  post_link_callback_fn + a new BuildConfig.post_link_takes_options flag.
- comptime_vm: runEntry refactored to runEntryArgs(extra) (implicit ctx +
  explicit args); new public runBuildCallback(..., pass_options) passes the
  opaque BuildOptions handle (one word) after the ctx. The fat-config
  marshaling fear is moot — the handle is a single null-sentinel word.
- core.invokeByFuncId/invokeByName take pass_options (was an unused args
  slice); main.zig passes comp.getPostLinkTakesOptions().
- build.sx: on_build decl (set_post_link_callback kept for now).

Smoke test examples/1664-platform-on-build-callback (AOT): #run on_build(build)
with build :: (opt: BuildOptions) -> bool; the callback is invoked with the
handle arg (runEntryArgs param-count match) and runs the primitives.

Benign .ir churn (37 snapshots: type table +1 for the on_build fn type +
global renumber; behavior identical). 705/0 both gates.
2026-06-19 08:47:05 +03:00
agra
d8affd45e8 rename std/build.sx -> modules/compiler.sx (the compiler-API surface)
Per user direction: the low-level abi(.compiler) primitive surface is the
comptime 'compiler' library, so name the file compiler.sx (a peer of build.sx)
instead of the interim std/build.sx — which also frees the 'build' name for the
default build IMPLEMENTATION (default_build + on_build slot), which will live in
modules/build.sx alongside the BuildOptions DSL.

Updated the two example imports + the plan's Phase 5 file-split note. 704/0
both gates.
2026-06-19 08:17:35 +03:00