Commit Graph

27 Commits

Author SHA1 Message Date
agra
8cca3b9dde refactor(ffi-linkage): Phase 9.2d — rename foreign_path → runtime_path (coupled .sx↔.zig↔hook)
The JNI/runtime-class path (Decision 5, Runtime* family). Coordinated across the
hook boundary so the BuildOptions accessor + its registered hook string stay in sync:
- src/: RuntimeClassDecl.foreign_path→runtime_path, splitForeignPath→splitRuntimePath,
  foreignPathToJavaName→runtimePathToJavaName, jni_main_foreign_paths→
  jni_main_runtime_paths, hookJniMainForeignPathAt→hookJniMainRuntimePathAt, and the
  hook string 'BuildOptions.jni_main_foreign_path_at'→'…runtime_path_at'.
- library/: build.sx accessor jni_main_foreign_path_at→jni_main_runtime_path_at +
  bundle.sx call sites + the  local var → runtime_path + a comment.
- specs.md: the accessor name + <foreign_path_with_dots> doc refs.
- Regenerated 37 .ir snapshots: every program importing build declares the renamed
  @BuildOptions.jni_main_runtime_path_at hook stub — symbol-name change only (verified
  the .ir diff is ONLY this rename; reverted orthogonal empty-file normalization).
Suite green (646 corpus / 444 unit, 0 failed).
2026-06-15 09:20:30 +03:00
agra
e57a27205e feat: std.http pooled handler dispatch (PLAN-HTTPZ S7b)
thread_pool_count = 0 (default) keeps handlers inline on the loop
thread — the measured fast path (BENCH-HTTPZ.md). N > 0 dispatches
each parsed request to a std.thread Pool of N workers, completing the
httpz two-pool shape: the connection freezes as CONN_HANDLING (no
reads, growth, eviction, or recycling — the worker borrows views into
its read buffer), the worker runs the handler under a per-job arena
and serializes into job-owned bytes, the completion queues under the
PoolState mutex, and the loop wakes through the new std.event wake
channel (kqueue EVFILT_USER + EV_CLEAR; the epoll twin maps to
eventfd), attaches the response, compacts the buffer, and resumes
keep-alive/pipeline handling. A full backlog sheds with 503. Stale
completions (generation mismatch after close) are dropped. Pool mode
requires the server's constructing allocator to be thread-safe
(GPA/malloc), documented on the knob.

PoolState lives behind a heap pointer (it embeds a Mutex and is shared
with workers; the Server struct itself is returned by value).
serialize_response/run_handler_job share one serialize_bytes.

examples/1633 gains the pooled section (GET, body echo, 404 across
worker threads) plus the loop-wake path exercised end to end; AOT run
five times. examples/1632 unchanged but the Event struct gains `user`.
2026-06-12 22:31:27 +03:00
agra
7f23bb7530 feat: std.thread — Thread, Mutex/Cond, bounded worker Pool (PLAN-HTTPZ S6)
pthread bindings with darwin opaque sizes (mutex 64B, cond 48B; glibc
divergence is a C3 per-OS item). Mutex/Cond initialize IN PLACE and
Pool lives behind Pool.create's heap pointer — POSIX sync objects are
address-sensitive, so nothing here moves after setup. Thread.spawn
takes the C2 re-entry contract entry (callconv(.c), fabricates its own
Context); Pool workers do exactly that with a per-worker malloc-backed
GPA, then run default-conv tasks inside it. submit returns false on a
full backlog (httpz thread_pool backpressure); shutdown finishes
queued work and joins every worker.

examples/1637 pins: 4 raw threads x 1000 locked increments, 100 pool
tasks summing exactly once across 4 workers, a held worker + full
backlog refusing the next submit, clean shutdown. JIT + AOT (AOT run
three times). The std.sx barrel carries thread; .ir snapshot regen is
the usual renumbering.
2026-06-12 22:21:40 +03:00
agra
8641441fad feat: std.http read buffers grow on demand toward read_buf_cap
read_buf_cap is now the per-request LIMIT, not a preallocation: slots
start at 16K, double when full (one-step sizing when a Content-Length
declares the body), and keep their grown capacity for slot reuse. At
the limit the refusal distinguishes oversized headers (431) from an
oversized body (413). Unblocks A1: distd accepts multi-hundred-MB
artifact uploads — preallocating that per slot was never an option.
examples/1633 adds a body past the initial capacity echoing intact.
2026-06-12 21:29:13 +03:00
agra
3a97019aa7 feat: std.http handler carries an opaque ctx word (PLAN-HTTPZ A1 prep)
Server.init(cfg, handler, ctx); the handler signature gains a usize
third argument delivered verbatim per dispatch — typically a pointer
to the app's own state, since the server owns the call site. A bare
(req, resp) handler had no way to reach app state without globals.
examples/1633 pins the round trip.
2026-06-12 21:22:42 +03:00
agra
721793b4bf feat: std.http — single-worker HTTP/1.1 server core (PLAN-HTTPZ S7a)
The httpz shape, one worker, handlers inline over the std.event Loop:
nonblocking accept, per-connection state machine (reading -> writing ->
keepalive/close) with incremental parsing (request line, headers,
Content-Length body), partial-write continuation via on-demand write
interest, pipelined-request draining, and timeouts as EVICTION —
request-delivery and keepalive-idle deadlines on the monotonic clock,
checked after I/O each tick. Keep-alive is the HTTP/1.1 default;
Connection header, HTTP/1.0, or the per-connection request_count cap
turn it off. Config mirrors httpz: port/backlog/max_conn/read_buf_cap/
timeout_request_ms/timeout_keepalive_ms/request_count.

API: Server.init(cfg, handler) + tick(max_wait_ms); run() is the
forever-tick loop. tick makes the server drivable single-threaded —
examples/1633 runs a live server and its client sockets in ONE thread,
pinning: GET with keep-alive, actual connection reuse, the request cap
answering Connection: close then EOF, POST body echo, 404 routing, and
a half-header client evicted at the request deadline while a healthy
client keeps being served. Verified under sx run AND sx build.

Connection slots and read buffers are reused across connections
(httpz's min_conn/buffer-pool spirit); response buffers are allocated
per response and freed on completion. Serialization happens while
request views are valid, the served bytes are compacted, and only then
does sending start — write_more's pipelining check must see only the
remainder. The std.sx barrel carries http; .ir snapshot regen is the
usual mechanical renumbering.

S7b adds worker counts + the handler thread pool (needs C2/S6); the
epoll backend activates with the linux target (S4/S7c).
2026-06-12 21:16:56 +03:00
agra
92e220ee24 feat: std.event — OS-neutral readiness Loop over kqueue (PLAN-HTTPZ S5)
Loop.init/close, add_read/del_read/add_write/del_write with a
per-registration udata word, and wait() normalizing backend events
into Event{fd, udata, readable, writable, eof, err, nbytes}. The epoll
twin (S4) slots in behind this surface when the linux target lands.
No timer registrations by design: request/keepalive eviction is
deadline math — deadline_in/expired/remaining_ms over std.time's
monotonic clock, with remaining_ms feeding wait's timeout. std.sx
barrel carries ; .ir snapshot regen is the usual mechanical
renumbering. examples/1632 pins idle timeout (and that it honors the
deadline), readable with fd/udata/nbytes, immediate writability on an
empty send buffer, and the eof flag on peer close; JIT + AOT.
2026-06-12 21:05:56 +03:00
agra
659c43c8d6 feat: std.socket nonblocking surface — fcntl, errno, typed _nb wrappers (PLAN-HTTPZ S2)
set_nonblocking (C-variadic fcntl), errno via __error (darwin; C3
selects per-OS), and accept_nb/read_nb/write_nb returning a typed
SockErr — WouldBlock / Closed / Fault — so readiness-loop callers never
parse -1/errno pairs. EINTR retries internally; accept_nb skips
ECONNABORTED. Adds connect, shutdown, socketpair, AF_UNIX, SHUT_*.
examples/1630 pins the result algebra on a socketpair and a nonblocking
TCP listener (WouldBlock on empty backlog, accept after loopback
connect); verified under sx run AND sx build. The .ir snapshot regen is
mechanical: new std decls shift @str/@tag.str numbering and grow the
type table (179 -> 185).
2026-06-12 20:53:35 +03:00
agra
1d17b0abcf lang: introduce cstring — the C-boundary string (Odin model)
cstring is ONE pointer to a null-terminated u8 buffer, C's char*: thin
(8 bytes, no length; cstring_len walks to the terminator), crossing
#foreign boundaries verbatim in both directions, with ?cstring as the
nullable case lowering to the same bare pointer (null = absent).

Conversion discipline mirrors Odin: a string LITERAL coerces implicitly
(its bytes are terminated constants); any other string is rejected with
a diagnostic naming to_cstring (it may be an unterminated view); and
cstring never coerces to string implicitly — from_cstring(c) is the
explicit zero-copy view, pricing the strlen.

Plumbing: TypeId/TypeInfo builtin slot 18 (first_user 19), name
classifiers, size/align/name tables, LLVM ptr lowering, the ?T pointer
niche, the xx pointer ladder, the literal-gated coercion plan
(isConstString + data_ptr), and the reserved-spelling set. std gains
cstring_len/from_cstring/to_cstring (fmt.sx, re-exported); the old
cstring(size) allocator helper is renamed alloc_string everywhere;
getenv migrates to (name: cstring) -> ?cstring as the canonical user
and env() drops its manual strlen/memcpy.

Pinned: examples/1222 (FFI both directions, literal coercion,
?cstring null paths, round trip) and examples/1173 (both coercion
diagnostics); FAIL pre-feature. The alloc_string rename + getenv
signature shift the .ir snapshots — regenerated. zig build test
426/426; run_examples 604/604.

Spec: reserved spelling + cstring section + C-interop rows.
2026-06-12 14:50:53 +03:00
agra
d88bdd7242 fix(0128): foreign cstring returns + conflicting same-symbol bindings
Two genuine defects behind the 0128 filing (whose original repros were
both poisoned by binding getenv, which std already declares -> *u8):

1. Re-declaring a C symbol was silent first-wins: every call through
   the later declaration was typed by the older signature. Foreign
   registration now dedupes — equal signatures share one FuncId,
   conflicting ones are diagnosed.

2. Foreign -> string / -> ?string returns read garbage: C returns one
   char*, but the LLVM signature declared the fat {ptr,i64} (len =
   register garbage), and ?string was mis-declared SRET (the hidden
   out-pointer landed in the callee's first arg register). cstrRetKind
   now classifies such returns, declares them as plain ptr (never
   sret), and the call site synthesizes {ptr, strlen} via a
   branch-guarded strlen (NULL -> {null,0} / optional null), wrapping
   {string, i1} for ?string.

?[:0]u8 itself resolves fine (it is ?string); the spelling works in
return, param, local, and alias positions.

Regression: examples/1221 (plain + optional non-null + NULL paths) and
examples/1172 (conflict diagnostic); both FAIL pre-fix. The extern
dedupe collapses duplicate libc decls, so affected .ir snapshots were
regenerated. zig build test 426/426; run_examples 602/602;
distribution suite 21/21.
2026-06-12 14:13:01 +03:00
agra
d8076b9333 lang: rename signed integer types sN -> iN
Surface rename of the signed integer family: s1..s64 become i1..i64
(u1..u64, usize, isize unchanged). 'string' keeps the s-prefix arm in
name classification; width parsing moves to the i-prefix arm next to
isize.

Internal TypeId tags follow the surface (.s8/.s16/.s32/.s64 ->
.i8/.i16/.i32/.i64), as do mono-key mangle fragments (ptr_i64,
tu_i64_bool) and all display/diagnostic formatting (i{d}).

Migrated in the same sweep: stdlib + examples + issue repros + FFI C
companions (shared symbol names like ffi_id_i64), expected
stdout/stderr/ir snapshots, specs.md, readme.md, CLAUDE.md/AGENTS.md,
implementation_plan.md, docs/, issue writeups. Vendored stb_image and
historical flow state left untouched.

zig build test: 426/426; examples suite: 595/595.
2026-06-12 09:31:53 +03:00
agra
837b5d375f fix(0124): large stack arrays lower to in-place access, not first-class values
Two lowering sites materialized a local array as a whole LLVM value;
the legalizer scalarizes each such op into one SelectionDAG node per
element, and at ~64K elements the DAG combiner segfaults
(DAGCombiner::visitMERGE_VALUES → ReplaceAllUsesWith).

- lowerVarDecl: an array-typed `---` initializer emits NO store — the
  slot stays uninitialized instead of receiving a whole-array undef
  store. The tuple zero-init carve-out stays; non-array `---` keeps
  the undef store. The interp is unchanged either way (slots start
  .undef).
- lowerIndexExpr: element reads on an array with addressable storage
  GEP the storage and load one element — the general-expression
  sibling of 0110's lowerFor fix — without value-lowering the object
  (a dead whole-array load would still reach the DAG). Storage-less
  arrays keep the index_get fallback.

Sibling shape filed as 0125: any_to_string's per-array-type arms still
pass the array by value, so a 64K+ array type + any {} print crashes.

Regression: examples/0055-basic-large-stack-array.sx (sx build
segfaulted pre-fix). 22 .ir snapshots re-pinned: removed undef stores
and ig.tmp spills, in-place gep+load (instruction-shape-only churn,
reviewed).
2026-06-12 08:19:20 +03:00
agra
b3b78e25c0 std: organise the facade — thematic groups, aligned columns
Same 47 re-exports, regrouped: compiler-resolved types, type-system/
reflection builtins, output + libc escape hatch, formatting (print/
format up front, the *_to_string family together), string ops +
allocation helpers, fmt internals marked as legacy-surface, List, tail.
Zero output-stream diffs; 37 .ir snapshots re-pinned (renumbering from
decl reordering). Gates: suite 588/588, zig build test 0, m3te 23/23,
game builds + bundles.
2026-06-11 21:46:03 +03:00
agra
c75cd9c63c std: drop the redundant flat mem.sx import from the facade
The flat #import of mem.sx predated the namespace tail — the tail's
mem :: #import already puts mem.sx in the program graph, which is all
the ufcs helpers (context.allocator.create/alloc/free/clone) and the
CAllocator default-context machinery need; std.sx itself references no
mem name. Probe-verified the full mem surface + all gates: suite
588/588, zig build test 0, m3te 23/23, game builds + bundles. The
double import was also duplicating lowered IR — the 37 re-pinned .ir
snapshots net ~2.5k lines smaller; output streams byte-identical.
2026-06-11 21:25:32 +03:00
agra
49a36bb492 std: the prelude becomes a pure re-export facade — implementations move to std/core.sx, std/fmt.sx, std/list.sx
std.sx now contains only alias declarations (the re-export mechanism:
own decls carry one flat-import level) over three part-files: core.sx
(builtins, libc escape hatch, Source_Location/Allocator/Context/Into,
the reserved `string` decl — which needs and permits no alias), fmt.sx
(print/format/any_to_string/string ops/cstring/alloc_slice), list.sx
(List). The namespace tail is unchanged; the part-file namespaces
(core/fmt/list) carry alongside it. Consumer surface is byte-identical
— every bare prelude name resolves through the aliases (0120/0121
machinery). 37 .ir snapshots re-pinned: pure string-constant
renumbering from the changed import graph (digit-normalized diff is
empty). Gates: zig build test 426/426, suite 588/588, m3te 23/23,
game SxChess builds + bundles.
2026-06-11 19:25:49 +03:00
agra
51194a26d8 mem: BufAlloc.init returns the state by value — full buffer usable, no header carve 2026-06-11 17:31:20 +03:00
agra
84e0fb0752 mem: typed allocation helpers + drop bare malloc/free (Phase 2.2); resolve 0119 as |>-contract clarification 2026-06-11 16:17:39 +03:00
agra
88bae3c9f5 mem: rename Allocator primitives to alloc_bytes/dealloc_bytes (Phase 4 naming pulled forward, Agra-approved) 2026-06-11 15:33:35 +03:00
agra
8908e78943 lang: array-typed '::' consts as immutable globals (PLAN-CONST-AGG step 1)
K : [4]s64 : .[...] and the untyped A :: .[1, 2, 3] register as
is_const globals: one storage, reads GEP it, dead-global elimination
drops unused ones, source-aware reads come free via selectGlobalAuthor.

- registerConstArrayGlobal (scanDecls pass 2): typed via the annotation
  (array-ness + dimension/count checked), untyped via element-type
  unification — all ints s64; ANY float promotes the element type to
  f64 with ints converting exactly; bool/string homogeneous; a
  non-numeric mix or non-inferable element asks for an annotation.
- constExprValue converts int elements into float destinations exactly
  (the int+float promotion rule, element-wise).
- emitGlobals marks is_const globals LLVMSetGlobalConstant — also flips
  the comptime-backed #run globals and __sx_default_context to
  'constant' (37 pinned IR snapshots regenerated; runtime unchanged).
- Element shapes: nested arrays, struct elements, strings, bools.
  Non-constant elements / dim mismatch / mixed types diagnose loudly.

Examples: 0177 (feature matrix incl. @K reads through *[4]s64 — needs
the 0117 fix), 1159/1160/1161 (diagnostics), 0837 repointed to values.
2026-06-11 12:26:26 +03:00
agra
330c3aeef7 std: full namespace tail — fs/process/socket/json/cli/hash/test
With 0115's own-wins globals landed, the remaining tail modules join
std.sx: every '#import "modules/std.sx"' now carries mem/xml/log/fs/
process/socket/json/cli/hash/test as namespaces (trace stays a direct
import).

Enablers in the same change:
- emit: dead-global elimination — a plain-data global no instruction
  references is not emitted, so tail modules' data (hash's 64-entry K
  table, OS/ARCH/POINTER_SIZE) stays out of binaries that don't use it.
  Comptime-backed globals keep their #run evaluation. 37 pinned IR
  snapshots regenerated (dead globals dropped + string renumbering from
  the larger module).
- 1055/1056 stop pinning the global error-tag ordinal (it shifts with
  program composition); they assert nonzero + tag identity + name.
- specs/readme/CLAUDE.md tail docs updated.
2026-06-11 10:49:39 +03:00
agra
12bf61a9fc std: restructure step 3 — ffi/ moves, build.sx, math dir spelling, fixtures
- objc.sx, objc_block.sx (from std/) + sdl3/opengl/raylib/stb/stb_truetype/
  wasm vendor bindings (from modules/ root) -> modules/ffi/
- std/uikit.sx deleted: platform/uikit.sx already declares UIApplicationMain
  and imports objc; '#framework "UIKit"' cannot live in a file imported on
  macOS targets (unconditional link directive, UIKit is iOS-only), so the
  three iOS-only examples carry the 3-line glue inline. 1607/1608/1616 also
  un-rotted (dead ns_string -> 'xx "..."' Into conversions, callconv(.c)
  msgSend fn-ptrs) — all three build for ios-sim/ios again.
- math/math.sx -> math/scalar.sx; one spelling '#import "modules/math"'
  everywhere (4 pinned IR snapshots regenerated: dir import adds Vec2/Mat4
  to the type tables).
- compiler.sx -> build.sx (imports, CLAUDE.md bundling table, specs.md).
- testpkg/ + test_c.sx -> tests/fixtures/ (resolve CWD-relative from repo
  root, same as vendors/).
- library-internal imports use full modules/... paths (std.sx tail,
  platform/bundle.sx, fixtures).
2026-06-11 08:37:22 +03:00
agra
59f0aa7716 std: restructure — std/ modules, namespace tail, std/xml.sx
allocators/fs/process/socket/log/trace/test move under modules/std/
(allocators.sx becomes std/mem.sx; the Allocator protocol moves into
the std.sx prelude, impls stay in mem.sx). New std/xml.sx holds
xml_escape as xml.escape. std.sx gains the carried namespace tail —
flat-importing std.sx now also provides mem./xml./log. — with the
remaining modules (fs/process/socket/json/cli/hash/test) deferred from
the tail until the global last-wins maps are fully own-wins (pulling
them into every closure collides bare names corpus-wide; they stay
direct imports: modules/std/fs.sx etc.). log.sx's internal emit
renamed log_emit (it clobbered consumer fns named emit program-wide).
bundle.sx uses xml.escape via the carried alias. Consumer import paths
swept mechanically; .ir snapshots recaptured for the larger std
closure. m3te + game build unchanged.
2026-06-11 06:10:59 +03:00
agra
878c4226a6 fix(0109): hoist all per-instruction allocas to the function entry block
An alloca built at its use site re-executes on every pass through that
block, and LLVM reclaims allocas only at ret — so loop-body locals,
nested-loop index slots, and emitter spill temps (ig.tmp, sret slots, ABI
coercion temps, byval materialization) grew the stack per iteration and
long loops segfaulted on stack exhaustion.

New LLVMEmitter.buildEntryAlloca inserts after existing entry-block
allocas and restores the builder position; every LLVMBuildAlloca site
reachable during instruction emission now routes through it.
Initialization stores stay at the use site (per-iteration re-init is
unchanged), and entry slots become mem2reg-promotable. The 35 .ir
snapshot diffs are pure alloca position moves (type multisets verified
identical per file).

Regression: examples/0047-basic-loop-local-stack-reuse.sx (segfaulted
pre-fix on both the 1M-iteration body-local loop and the 3M-iteration
nested loop).
2026-06-10 17:27:11 +03:00
agra
5f64ee4426 fix(ir): reflection builtins on an Any read its runtime tag, not payload [F0.8]
`type_name` / `type_is_unsigned` on an `Any` argument unconditionally read
the Any's payload as a TypeId index. That is correct only when the Any holds
a Type value (`{ .any, tid }`); for an Any holding a runtime *value*
(`av : Any = 6`, tag s64, payload 6) it returned `types[6]` — `type_name(av)`
gave "u8" and `type_is_unsigned(av)` gave true.

Both backends now branch on the Any's runtime type-tag: tag == `.any` → the
box is a Type value, use the payload as the TypeId; otherwise the tag IS the
held value's type. So `type_name(av)` → "s64", `type_is_unsigned(av)` → false,
while `type_name(type_of(x))` still names the held type. The `{}` formatter is
unchanged (it already passed `type_of(val)`, a proper Type value).

- src/ir/interp.zig: shared `Value.reflectTypeId` tag-branching resolver; the
  `type_name` / `type_is_unsigned` interp arms route through it.
- src/backend/llvm/ops.zig: shared `Ops.reflectArgTypeId` emits
  extractvalue-tag / icmp-eq-.any / select for the runtime path; both
  reflection arms route through it. The two backends agree.
- examples/0164-types-reflection-any-tag.sx: regression pinning type_name /
  type_is_unsigned / print on an Any holding a value vs a Type.
- src/ir/interp.test.zig: unit test for `reflectTypeId`.
- 22 .ir snapshots: the new select appears in every std-importing program's
  IR (any_to_string embeds these builtins) — benign, verified structurally
  identical apart from the three new instructions.
- issues/0090, specs.md: documented the Any-tag rule.
2026-06-05 12:09:52 +03:00
agra
64f77e9779 fix(std): render integer formatter extremes — i64::MIN and unsigned all-ones [F0.8]
Resolves issue 0090. The `{}` integer formatter mis-rendered both ends of
the 64-bit range:

- `int_to_string` computed the magnitude as `0 - n`, which overflows for
  `s64::MIN` (its magnitude is unrepresentable as a positive s64) — the
  value stayed negative, the digit loop ran zero times, so only `-`
  printed. It now extracts digits straight from `n` (per-digit
  `|n % 10|`, `n` truncating toward zero), never negating MIN.

- `any_to_string`'s `case int:` formatted every integer as s64, so a u64
  all-ones value printed as `-1`. There was no `uint` type-category to
  distinguish signedness. Added an additive `type_is_unsigned(T)`
  reflection builtin (static fold + dynamic interp/LLVM paths, mirroring
  `type_name`), backed by the new `TypeTable.isUnsignedInt` predicate, and
  a `uint_to_string` formatter (unsigned decimal via long-division over
  four 16-bit limbs). `case int:` routes through `type_is_unsigned(type)`.

The 16-bit-limb split is factored into a shared `decompose_u16x4`, now
reused by `int_to_hex_string` (no second unsigned-math routine).

Regression: examples/0046-basic-int-formatter-extremes pins both extremes
plus a width spread; unit tests cover `isUnsignedInt`. Docs (specs.md
representation note, readme std API) updated for unsigned/extreme `{}`
behavior. IR snapshots refreshed for the two new std functions.
2026-06-05 09:05:37 +03:00
agra
263333bd26 fix(ir): serialize enum-literal global initializers (issue 0082)
A module-global initialized with an enum literal silently zero-initialized
to the first tag (`chosen : Color = .green` read back as `.red`), and an
enum tag inside a global array/struct was rejected as non-constant. The
constant serializer had no enum-literal arm.

Add `Lowering.constEnumLiteral`: serialize an enum literal to a
`ConstantValue.int` holding the variant's tag value, resolved against the
destination enum type and respecting explicit variant values; the global's
type drives the backing width at emit time. Wired into `globalInitValue`
(scalar global) and `constExprValue` (array element / struct field / nested
aggregate). A non-enum destination or unknown variant is diagnosed loudly,
never silently zero-initialized. The compiler-injected OS/ARCH globals now
serialize to their real `.unknown` tag (6 / 4); runtime reads are unchanged
(they resolve through comptime_constants), so only the static initializer in
the pinned .ir snapshots changes.

Remove the silent `func_ref => orelse LLVMConstNull` fallbacks in the LLVM
constant emitters: aggregate func_ref leaves carry a `require_resolved` flag
(transient null in Pass 0, loud diagnostic if still unresolved in the
Pass-1.5 re-emit), a top-level func_ref global is resolved in
initVtableGlobals, and the comptime (#run) path bails loudly instead of
emitting a null function pointer.

Regression: examples/0139-types-global-enum-literal-init.sx (scalar, array,
struct field, explicit-value enum u16 stride, struct-array with enum field);
negative: examples/1127-diagnostics-global-enum-literal-bad-variant.sx.
Mark issue 0082 RESOLVED.
2026-06-04 04:52:42 +03:00
agra
297f127821 test(ir): lock call lowering with .ir snapshots + classification tests (A3.2 convergence step 1)
Test-first scaffolding before the CallPlan convergence — no call-code
change. Locks current call behavior so the later lowerCall rewrite is
guarded.

- .ir snapshots for representative call forms: 0031 (direct local-fn +
  dot-shorthand enum ctor), 0032 (UFCS/struct method), 0301 (closure/
  fn-pointer slot), 0400 (protocol dispatch + static-through-impl).
- New focused example 0044-basic-default-arg-expansion + .ir snapshot,
  pinning call-site default expansion (scale(5)->scale(ctx,5,2),
  label(1)->label(ctx,1,"v","!")). Foreign-class instance+static is
  already pinned by the existing FFI .ir set.
- Broaden calls.test.zig (scope-free classification): remaining reflection
  builtins, sqrt->f64, cast->resolved type arg, enum_literal->target_type.

1033 (#caller_location) was rejected as a snapshot: it embeds the absolute
source path as a length-typed string that normalize_ir can't reconcile;
default-arg coverage uses the path-free 0044 instead.

Gate green: zig build, zig build test, tests/run_examples.sh -> 357/0.
2026-06-02 19:20:14 +03:00