Commit Graph

25 Commits

Author SHA1 Message Date
agra
6844fb90e7 comptime VM: dedicated Type builtin TypeId (8B), distinct from .any — foundation (dead)
Add TypeId.type_value (slot 19) + matching TypeInfo.type_value variant: an
8-byte type handle, distinct from the 16-byte boxed .any. All types.zig layout
handlers wired (size/align 8, display "Type", hash/eql); toLLVMTypeInfo -> i64.

Reserve builtin headroom: first_user 19 -> 100 (slots 20-99 padded with the
unresolved tripwire) so future builtins don't renumber user TypeIds / churn
sx ir snapshots. 22 IR snapshots regenerated (pure renumber to 100-base).

type_resolver still returns .any for "Type" — nothing produces .type_value
yet, so no behavior change. 697/0 both gates.
2026-06-18 13:03:21 +03:00
agra
27bc301651 comptime VM: Phase 3 — type_kind + type_field_value readers (read side complete)
The last two read-only readers the metatype's type_info(T) needs, each backed by
a TypeTable query both the legacy handler and the VM call (no drift):

  type_kind(t: TypeId) -> i64            (kindCode; stable discriminant, total — never bails)
  type_field_value(t: TypeId, idx) -> i64 (memberValue; enum explicit value or ordinal)

kindCode codes (compiler-owned, stable): 0 other / 1 struct / 2 enum /
3 tagged_union / 4 tuple / 5 union / 6 array / 7 vector / 8 error_set.

With these, the READ side is complete: find_type + type_kind + type_field_count +
type_field_{name,type} + type_nominal_name + type_field_value cover everything
reflectTypeInfo reads — a comptime sx fn can fully reflect a struct/enum/tuple
into data with no #builtin.

Example 0630 reflects Color / WindowFlags(flags) / Point. VM unit test added.

Revised forward direction: the write side will be ONE register_type(info) fn that
branches on the kind in the compiler (subsuming define's per-kind dispatch), not a
per-kind register_struct.

Parity 691/691 (gate OFF and -Dcomptime-flat).
2026-06-18 09:47:23 +03:00
agra
d23e208430 comptime VM: Phase 3 — field-level reflection readers
Three more read-only compiler-API readers on the TypeId-handle shape, each backed
by a new TypeTable query that both the legacy handler and the VM call (no drift):

  type_nominal_name(t: TypeId) -> StringId     (nominalName; loud-bail for unnamed types)
  type_field_name(t: TypeId, idx: i64) -> StringId   (memberName)
  type_field_type(t: TypeId, idx: i64) -> TypeId     (memberType)

All loud-bail on out-of-range idx / no-member — no silent default. First multi-arg
compiler fns (callCompilerFn now reads arg 1 = idx); added Vm.argHandle/argTypeId
range-checked arg readers and moved find_type/type_field_count onto them. Names use
the type_* family to avoid colliding with the std metatype builtins (field_name /
type_name in core.sx); the new TypeTable.nominalName is distinct from the existing
typeName(id) display-string renderer.

Example 0629 reflects Pair { lo: Point; hi: Point } — each field name + the nominal
name of a field's type, #run-folded, VM-HANDLED natively. VM unit test added.

Parity 690/690 (gate OFF and -Dcomptime-flat).
2026-06-18 09:34:36 +03:00
agra
a9302a8b50 comptime VM: Phase 3 — find_type + type_field_count reflection readers
First read-only compiler-API reflection readers, bound the same way as the
intern/text_of seed (compiler_lib.bound_fns + Vm.callCompilerFn, native on flat
memory, no marshaling). A type handle is a plain u32 TypeId (like StringId), so
both stay clean scalar host-calls:

  find_type(name: StringId) -> TypeId          (TypeTable.findByName; unresolved/0 if absent)
  type_field_count(t: TypeId) -> i64           (new TypeTable.memberCount; loud-bail, no silent 0)

memberCount is the single source both the legacy handler and the VM read, so the
two paths can't drift. find_type returns a non-optional TypeId using the
unresolved(0) sentinel for not-found rather than ?Type — a Type value is
.any-typed (which the flat-memory VM does not represent) and an optional can't
cross the legacy<->VM eval boundary; unresolved is the project-blessed "no type"
marker.

Example 0628 chains intern -> find_type -> type_field_count (+ a not-found
lookup), folded at #run, VM-HANDLED natively. VM unit test added.

Parity 689/689 (gate OFF and -Dcomptime-flat).
2026-06-18 09:25:26 +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
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
4b2a067991 wip(E2): partial nominal-identity/shadow work [stdlib E2 attempt-1 WIP checkpoint]
Incomplete WIP from a worker killed at the 30-min wall; committed as a
checkpoint so the resumed session continues on a clean tree. May not build.
2026-06-07 22:17:07 +03:00
agra
09666cb90e feat(types): nominal identity + key-safe TypeTable mutation, ban raw update [stdlib D]
Phase D of the unified resolver: make the TypeTable safe to key by nominal
identity before same-name type shadows land (Phase E). Behavior-preserving —
nominal_id=0 means structural (today's keying, byte-identical); single-author
names intern to the same TypeId as before.

types.zig:
- StructInfo/EnumInfo/UnionInfo/TaggedUnionInfo/ErrorSetInfo gain
  `nominal_id: u32 = 0`. hash/eql fold it into the nominal arms ONLY, and only
  when nonzero, so legacy (structural) interning hashes/compares byte-identically.
- internNominal(info, nominal_id): stamps the id into the nominal arm then
  interns; nonzero id on a non-nominal info trips an assert.
- updatePreservingKey(id, info): field-fill that asserts the intern key is
  unchanged (replaces the forward-decl stub→full pattern).
- replaceKeyedInfo(id, info): the one legitimate re-key (anon rename
  __anon → Parent.field); removes the stale key and installs the new one.
- findUniqueByName: quarantined findByName that asserts ≤1 match.
- type_decl_tids: decl-node → TypeId identity map (the fn_decl_fids analogue),
  consumed by the resolver in Phase E.

Ban raw TypeTable.update outside types.zig (the acceptance bar): every caller
in lower.zig / type_bridge.zig / protocols.zig is reclassified — forward-decl
field fills route through updatePreservingKey, qualifyAnonType's rename through
replaceKeyedInfo. The raw `update` method is removed. Inline named type-decl
registration ("current winners") routes through internNominal(info, 0).

Tests (types.test.zig): forward-decl field fill (stable key), anon rename
(re-key), generic struct instantiation, type-returning function, parameterized
protocol value struct, same display-name → distinct nominal ids, plus an
old==new assertion (internNominal(.,0) byte-identical to legacy intern),
findUniqueByName, and the type_decl_tids identity map.

Gate: zig build (0), zig build test (421/421), run_examples (477, byte-identical),
m3te ios-sim build via worktree binary (0). No shadows registered; stubs intact.
2026-06-07 13:27:10 +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
3ed1b3a7a0 refactor(ir): pack projections → PackResolver + retire the alias borrow (A2.3)
A2-merge gate: both parts in one commit, behavior-preserving (350/0).

Part 1 — retire the TypeTable.aliases borrow (build-enforced):
- type_bridge.zig: add `AliasMap` and thread it as an explicit param through
  every name-resolving fn (resolveAstType, bridgeType, resolveTypeName, the
  compound resolvers, resolveTupleLiteralAsType, resolveParameterizedType, the
  inline enum/struct/union + error resolvers). resolveTypeName now forwards the
  threaded map to TypeResolver.resolveNamed instead of reading table.aliases.
- lower.zig: all 31 resolveAstType callers pass
  &self.program_index.type_alias_map; drop the lowerRoot loan.
- types.zig: remove the now-unused TypeTable.aliases field.
- type_bridge.test.zig: alias test passes alias_map explicitly; other calls
  pass null.

Part 2 — pack projections get one owner + no .void failure sentinel:
- New packs.zig (PackResolver, a *Lowering facade): moves
  resolveClosure/Tuple/FunctionTypeWithBindings, packTypeElems, packTypeArgs,
  elementProtocolTypeArg out of Lowering. Call sites route through
  Lowering.packResolver(); barrel-wired in ir.zig.
- The missing-projection `orelse .void` in packTypeArgs now emits a diagnostic
  and fills the slot with .unresolved (the tripwire sentinel), never a real
  .void; OOM `catch return .void` in the moved fns became .unresolved too.
  Legitimate no-return-type `else .void` defaults are preserved.
- packs.test.zig: packTypeArgs bound/unbound/no-constraint/no-state cases +
  the missing-projection backstop (diagnostic + .unresolved slot).
2026-06-02 14:43:47 +03:00
agra
8fbaf9ca6a docs(ir): mark sema/types as editor-only, not compiler truth (A1.2)
Architecture phase A1.2 — documentation/comment only, no behavior change.

Resolve the ambiguity over which type model compiler decisions trust:

- src/sema.zig: file-level module doc stating it is the editor symbol/type
  index for the language server (navigation/completion), NOT a compiler
  semantic pass. Its Type values are editor metadata; the compiler uses the
  canonical TypeId/TypeTable model in src/ir/. sx requires no as-you-type type
  checking -- authoritative diagnostics are produced on save by the canonical
  pipeline. Added notes on SemaResult, Analyzer, resolveTypeNode, inferExprType.
  No public API renamed (would churn LSP call sites).
- src/types.zig: note that Type is editor metadata only, not compiler truth;
  do not expand for new compiler semantics (A8 deletes/reduces it).
- src/ir/types.zig: fix stale TypeTable.aliases comment -- it borrows
  Lowering.program_index.type_alias_map (post-A1.1b).

Deleting the LSP's parallel sema diagnostic stream is A8.1, not this step.

Gate green: zig build, zig build test, bash tests/run_examples.sh (350 passed).
2026-06-02 12:54:30 +03:00
agra
73232ce170 ERR/E1.1 (slice 1): error-set type + global tag registry + decl registration
First sema/types step. Implemented in the IR layer (ir/types.zig +
type_bridge.zig + lower.zig), NOT src/sema.zig — lowering doesn't consume
sema; the frontend Type is LSP-only. Mirrors how enums are handled.

- ir/types.zig: new `.error_set` TypeInfo kind (ErrorSetInfo {name, tags:
  []u32}; identity = name, like enum) with a u32 runtime layout (size/align
  4, LLVM i32) per the locked error-slot ABI. New TagRegistry on TypeTable
  (global tag pool: name -> u32, monotonic, id 0 reserved for "no error").
  internTag/getTagName/errorSetType helpers; `.error_set` arms in all 7
  exhaustive switches + findByName.
- emit_llvm: toLLVMTypeInfo -> i32. print: writeType -> set name.
- type_bridge: resolveInlineErrorSet (mirrors resolveInlineUnion) +
  .error_set_decl arm.
- lower.zig: registerErrorSetDecl (rejects empty `error { }` with a
  diagnostic) wired into both top-level decl switches + the block-local one.
- tests: ir/types.test (TagRegistry 0-reserved + identity; errorSetType u32
  layout + named display + dedup; sorted storage) and ir/type_bridge.test
  (decl -> type + tag interning + re-resolve dedup).

End-to-end: `Foo :: error { A, B }` + main compiles + runs (exit 0) — first
ERR syntax to survive the full pipeline; empty set rejects with a diagnostic.
Inferred bare `!`, error.X value, and == typing deferred to slice 2 / E1.2.

zig build, zig build test, and 254/254 examples green.
2026-05-31 17:39:11 +03:00
agra
55e62694d1 ir: dedicated TypeId.unresolved sentinel; kill inferred_type => .s64
An unannotated param resolving to a plausible .s64 was the classic
silent-default trap (root of the 2.5 multi-param-closure bug). Replace it
with a dedicated TypeId.unresolved at slot 0, so a zero-initialised or
forgotten TypeId trips the sentinel instead of masquerading as a real type.

- types.zig: TypeId.unresolved = 0 (void moves to 17); TypeInfo.unresolved;
  sizeOf/toLLVMType @panic on it (codegen tripwire); hash/eql/printer cover it.
- type_bridge: inferred_type => .unresolved (was .s64).
- resolveParamType: emit "parameter 'x' has no type annotation" for a
  genuinely-unannotated value param (comptime/variadic/pack params exempt --
  they resolve via per-call substitution).
- lowerLambda: resolve unannotated params from the target closure signature;
  otherwise emit "cannot infer type of lambda parameter".
- CLAUDE.md: .void documented as an UNACCEPTABLE failed-type sentinel (it
  conflates with a real, heavily-checked type); prescribe a distinct
  .unresolved-style value + codegen tripwire.

Snapshot churn: one .ir (ffi-objc-call-06) -- the runtime type-name table and
typeof match arms renumber by the new builtin slot; program output unchanged.
2026-05-29 22:25:45 +03:00
agra
92638ae9b5 lang 2.1: Pack as a type-system value
Add a `pack` variant to IR `TypeInfo` — an ordered, interned sequence of
per-position element types (`PackInfo { elements: []const TypeId }`) — with
constructor (`packType`), structural equality + hashing, and a `pack(T0, …)`
printer. A pack is comptime-only: it lowers to flat positional args before
codegen and has no runtime layout, so `sizeOf` and `toLLVMType` bail loudly
rather than inventing a size. 5 unit tests (N=0/1/3, dedup, order/arity
distinctness, distinct-from-tuple, printer).

Also: give TypeTable an arena for the slices its constructors dupe (freed at
deinit), and add the missing `usize`/`isize` arms to `sizeOf` (a latent
non-exhaustive switch) so types.test.zig compiles and runs leak-free.
2026-05-29 15:24:46 +03:00
agra
29bd182f3f ir: generalize type-alias resolution via TypeTable.aliases borrow
Previously, type aliases (`ShaderHandle :: u32`, `Vec4 ::
Vector(4, f32)`) were resolved at three explicit call sites:
- `resolveTypeWithBindings` fallthrough (lower.zig: was 10481-83)
- Protocol method param resolution (was 11154-61)
- Protocol method return resolution (was 11169-76)

Every other `type_bridge.resolveAstType` caller silently fell into
`resolveTypeName`'s "create empty struct stub" path at the bottom,
materialising the alias name as a fresh `{Name=}` struct instead of
its target type. Symptom: the IR call signature got `{}` parameters
where the user meant `u32` etc.

This pushes the alias check inside `resolveTypeName` itself. A new
`TypeTable.aliases: ?*const std.StringHashMap(TypeId)` borrow is
loaned at `lowerRoot` from the owning Lowering. `resolveTypeName`
consults it before falling through to the stub default. Every
caller of `resolveAstType` (and its recursive helpers — `*Alias`,
`[]Alias`, `?Alias`, etc.) now picks up the same resolution.

The three pre-check sites in lower.zig collapse:
- `resolveTypeWithBindings`: the trailing alias pre-check is gone;
  the comment now points at the new path.
- Protocol method param: the `Self → *void` short-circuit stays;
  the alias arm is gone — the fallthrough handles it.
- Protocol method return: same shape.

Tests:
- `type_bridge.test.zig` gains `resolveAstType: TypeTable.aliases
  resolves named alias` pinning the new behaviour. Demonstrates:
  (1) no alias set → unknown name becomes empty struct stub (the
  silent-fail shape we're fixing); (2) alias set → resolves to the
  alias target; (3) compound forms (`*Alias`) recurse into
  `resolveTypeName` for the inner name and pick up the alias.

224/224 example tests pass; zig build test green.
2026-05-28 15:12:53 +03:00
agra
3ac13b7442 ir: Type as first-class value (Any-shaped {tag, value})
Previously, `t : Type = f64` stored a boxed string carrying the literal
name "f64"; comparisons and `type_of`/`type_name` round-trips lost the
underlying TypeId. This switches `Type` to a runtime-representable Any
pair: `{ tag = .any.index() (meta-marker), value = TypeId.index() }`.

Mechanism:
- `const_type` emits a 16-byte Any aggregate via insertvalue.
- `TypeId.any` advertises 16 bytes / 8-byte alignment so structs that
  embed `t: Type` size correctly under verifySizes.
- `lowerBinaryOp` folds `==`/`!=` between static type-refs to a
  `const_bool`, and decomposes runtime Any-vs-Any compares via
  `unbox_any` so LLVM doesn't see icmp on aggregates.
- `lowerMatch`'s `is_type_match` path unboxes Any-typed subjects to
  the i64 type tag before the switch, so `case type:` etc. fire.
- `lowerRuntimeDispatchCall` (used by `case T: ... cast(t) val`) does
  the same unbox for the type-tag arg.
- `type_of(val: Any)` rebuilds an Any with `{.any, tag_of(val)}` so
  the result is itself a `Type` value, not a bare i64.
- `buildPackSliceValue` stops re-boxing const_type — the value is
  already canonical Any.
- `__sx_type_names` now indexes by TypeId across the whole table
  using the new `types.formatTypeName` (structural names for `*T`,
  `[]T`, `[N]T`, `?T`, `Vector(N,T)`, function/closure/tuple) so
  runtime `type_name(t)` works for compound types.
- `interp.zig`'s comptime `type_name` accepts either the bare
  `.type_tag` Value or the Any-boxed aggregate it now sees.
- `scanDecls` registers `Vec4 :: Vector(4, f32)` style aliases in
  `type_alias_map` (before the `fn_ast_map` check; `Vector` IS a
  `#builtin` fn). Lets `Vec4` in expression position lower as
  `const_type(<vector tid>)`.
- `isStaticTypeArg` becomes scope-aware: a name shadowed by a runtime
  local is not static. `isStaticTypeRef` is the symmetric helper for
  the eq fold.
- `inferExprType` returns `.any` for bare type names (identifier and
  type_expr) so pack arg types are correct.

Side effect: `print("{}", Vec4)` now prints the structural name
`Vector(4,f32)` rather than the alias literal `Vec4` — 12-meta's
expectation updated. Aliases stay pointer-equal to their target
(`Vec4 == Vector(4, f32)` is true).

Tests:
- examples/189-type-all-interactions.sx: 12-section comprehensive
  coverage — literal `==`, `type_of(value) == T`, `Type` var storage,
  `type_name` (static + runtime), printing Type values, generic
  dispatch via `$T: Type`, `identity($T, val)`, `Wrap($T)`, reflection
  builtins (`size_of`, `align_of`, `field_count`, `type_eq`),
  `..$args` pack walking, `Type` in struct field, compound type
  literals (`*Point`, `[4]s32`, `[]bool`, `?f64`).
- examples/12-meta.sx: expected output updated to reflect structural
  name for the Vec4 alias path.
- ffi-objc-call-06-sret-return.ir: regenerated to absorb the new
  type-name strings now emitted globally.

223/223 examples pass.
2026-05-28 14:02:10 +03:00
agra
65824494a7 ffi M5.A.next.1c.B: pack type rep — Closure(..$args) parses + interns
`parseTypeExpr`'s `Closure(...)` arm now accepts a trailing
`..$name` (sigil optional) as a variadic-pack marker. Pack must
be terminal — `)` is the only token accepted after the name.
`ClosureTypeExpr` AST gains `pack_name: ?[]const u8` carrying the
identifier so later slices can name the binding.

`FunctionInfo` / `ClosureInfo` in src/ir/types.zig grow a
`pack_start: ?u32 = null` field. `Closure(..$args) -> R` interns
as `params = []`, `pack_start = Some(0)` — distinct from any
concrete `Closure(...) -> R` shape thanks to updated hash/eql
arms. New constructor pair `closureTypePack` /
`functionTypePack` keeps the existing single-shape constructors
unchanged.

`type_bridge.resolveClosureType` calls `closureTypePack` when
`pack_name != null`. The pack starts after the fixed prefix,
so `Closure(Prefix, ..$args)` resolves with `params = [Prefix]`,
`pack_start = Some(1)`.

No semantic effect yet — the signature exists in the type table
but no matching code reads `pack_start`. Step 1d wires impl
matching: `Closure(..$args) -> $R` binds against any concrete
closure source type in `tryUserConversion` / `registerParamImpl`.

`examples/154-pack-type-rep.sx` flips from rejecting-with-error
to positive parse smoke (prints "pack type rep ok").

192/192 example tests + `zig build test` green.
2026-05-27 12:12:16 +03:00
agra
79419b99bd issue-0028: ?Protocol = null sentinel-shaped optional protocols
Protocol structs registered via registerProtocolDecl carry a new
is_protocol flag; the ?T paths in sizeOf/typeSizeBytes/toLLVMType
recognise it and lay out ?Protocol as the protocol struct itself
(ctx == null IS the "none" state), matching how ?Closure / ?*T are
sentinel-shaped — no extra storage.

Method dispatch on ?Protocol auto-unwraps in lowerCall's field-access
path; the unwrap is structurally a no-op so we just rebind obj_ty to
the payload type. resolveCallParamTypes extended for optional-protocol
receivers so enum-literal args (gpu.create_texture(.r8, ...)) get the
right target_type and don't silently collapse to tag=0 : s32 — same
issue-0031-class bug closed in Session 66, one type-system layer
deeper.

Library: UIRenderer / UIPipeline / GlyphCache migrated from the verbose
gpu: GPU = ---; has_gpu: bool pattern to gpu: ?GPU = null. set_gpu no
longer maintains a parallel bool flag.

Bundled: dock.sx threads delta_time as a struct field rather than via
a global pointer (cleanup unrelated to issue-0028, committed alongside).

Verified: 85/85 regression tests pass; iOS-sim chess + macOS chess
both render correctly post-migration.
2026-05-18 18:32:55 +03:00
agra
63565e41ff abi: pass >16B aggregates by ptr-in-next-reg (Apple ARM64 ABI) + Path B for fn-ptr casts
Three stacked compiler bugs were causing iOS-sim chess to crash inside
[MTLTexture replaceRegion:...]. Fixing them lets every replaceRegion call
site succeed (1×1 RGBA8, 1MB R8 atlas, 440×440 chess pieces).

Path B for callconv(.c) fn-pointer casts:
- FunctionInfo now carries call_conv: CallConv (TypeInfo.CallConv) so
  function-type interning distinguishes sx-CC from C-CC. Inst.zig's
  Function.CallingConvention aliases the same enum.
- Parser accepts an optional `callconv(.c)` suffix on fn-pointer type
  spellings (factored into parseOptionalCallConv() shared with parseFnDecl
  and parseLambda).
- resolveFunctionType passes the parsed CC through functionTypeCC().
- .call_indirect reads fp.call_conv == .c and applies the C-ABI
  alloca+materialize for >16B aggregate args (Path A's behaviour at .call).

Apple ARM64 ABI (drop LLVM byval):
- Side-by-side asm diff vs clang's emission for the equivalent C call site
  showed LLVM's `byval` attribute lowers Apple-arm64 byval on the stack,
  while clang passes the struct via a pointer in the next int register
  (x2 for replaceRegion:). The runtime objc_msgSend dispatch path expects
  clang's convention.
- Dropped the byval attribute from the function-signature emission and
  from both call sites (.call and .call_indirect). The materialize-into-
  alloca + pass-plain-ptr pattern stays — the call site now matches
  clang's `mov x2, sp` exactly.
- Path A's sx-to-sx case continues to work since both ends use plain ptr
  (caller does alloca+store+pass, callee loads from the ptr in prologue).

Protocol dispatch (emitProtocolDispatch):
- Untargeted `null` lowers as const_null with type .void (per
  target_type orelse .void). The "wrap-value-in-alloca-pass-pointer"
  branch alloca'd a void slot, which LLVM's IRBuilder asserts on —
  EXC_BREAKPOINT in getTypeSizeInBits, manifesting as exit 133 / SIGTRAP
  when building the chess game. Fixed by re-emitting as
  constNull(void_ptr) when arg_ty == .void && expected_ty == void_ptr.
- is_pointer_ty only recognized .pointer, so [*]T (many_pointer) was
  alloca-wrapped — the heap pixels pointer from stbi_load was stored
  into a stack slot and the slot's address was passed as the *void arg.
  Fixed by extending the check to `.pointer or .many_pointer`.

metal.sx call sites + lifecycle guards:
- msg_replace (replaceRegion:, MTLRegion = 48B) and the two setScissorRect:
  sites (MTLScissorRect = 32B) now spell their fn-pointer types with
  by-value params + callconv(.c) — the *MTLRegion/@local workaround is
  gone.
- metal_begin_frame_ios bails before nextDrawable when pixel_w/h are 0
  (drawableSize 0×0 makes nextDrawable abort via XPC).
- metal_init_ios only sets drawableSize when dims are positive.
- begin_frame's encoder/cmd_buffer failure paths now clear self.drawable
  so a partial failure doesn't leak a drawable back into the pool.

Examples + tests:
- examples/86-callconv-c-fnptr-large-aggregate.sx — new, covers Path B
  with C-CC fn-ptr cast.
- examples/87-fnptr-cast-large-aggregate.sx — renamed from issue-0025.sx,
  covers Path B with default sx-CC (the negative case).
- examples/85-cc-c-large-aggregate.sx — from Session 60, covers Path A.
- examples/issue-0014.sx, issue-0024.sx, issue-0025.sx — removed
  (resolved earlier this work).

71 regression tests pass, 0 failed. Chess game builds clean for iOS sim
and reaches its frame loop without aborting. Runtime: chess UI still
doesn't render — remaining issue is in the UIKit lifecycle / CAMetalLayer
setup (legacy-app vs scene-API hybrid), not a compiler bug. See
current/CHECKPOINT.md "Next step" for the diagnosis + options.
2026-05-18 00:11:23 +03:00
agra
1c32d54e01 ios + ir cleanup
- ios: --target ios/ios-sim shorthands, iOS SDK auto-discovery,
  #framework directive + BuildOptions.add_framework hook,
  .app bundle + Info.plist + codesign (ad-hoc and real),
  --codesign-identity/--provisioning-profile/--entitlements flags,
  modules/std/{objc,uikit}.sx, dynamic class registration,
  typed objc_msgSend cast pattern, UIApplicationMain handoff,
  UIWindow scene attach. Runs on iPhone hardware.
- ir: silent .s64 defaults → loud diagnostics,
  resolveReturnType infers from body, sub-byte int sizes match LLVM,
  tuple type interning includes names, compile errors exit 1
- issue-NNNN convention: resolved bugs rename to focused features
- 50 regression tests passing
2026-05-17 13:19:08 +03:00
agra
2f4f898d54 asm... 2026-03-02 17:19:41 +02:00
agra
f763765ea2 ir done'ish 2026-03-01 22:38:41 +02:00
agra
6a920dbd2c ir 2026-02-28 18:03:38 +02:00
agra
2552882ce6 05 2026-02-26 14:46:21 +02:00
agra
dd14f1206b ir 2026-02-26 02:25:02 +02:00