Files
sx/issues/0086-vector-lane-store-unresolved-panic.md
agra a7ee179577 fix(ir): vector lane store resolves lane element type [F0.5]
Writing a Vector lane (`v.x = …`, `.y/.z/.w` + colour aliases) panicked
with "unresolved type reached LLVM emission". The store path had no
vector branch: a `.field_access` target on a Vector fell through to
struct-field lookup, matched nothing, left `field_ty = .unresolved`, and
built a `ptrTo(.unresolved)` that tripped the LLVM emission guard. The
read path resolved the lane fine — the two had diverged (issue-0083
two-resolver class).

Extract a shared `Lowering.vectorLaneIndex` resolver and route BOTH paths
through it. The read path (`lowerFieldAccessOnType`) delegates to it,
dropping its silent `else 0` fallback. A new vector branch in
`lowerAssignment` GEPs a typed pointer to the lane (`structGepTyped`) and
stores via `storeOrCompound` (plain + compound). `emitStructGep` now
addresses a vector base type with a `[0, lane]` GEP. A non-lane field now
reports field-not-found on both paths instead of silent-lane-0 / panic.

Regression: examples/1506-vectors-lane-store.sx (panicked pre-fix, now
reads back written values) + a vectorLaneIndex unit test. Resolves issue
0086; spec documents element assignment.
2026-06-05 01:32:35 +03:00

4.5 KiB

0086 — writing to a Vector lane (v.x = …) panics with "unresolved type reached LLVM emission"

RESOLVED (F0.5).

Root cause. The vector-lane STORE path had no vector branch. In Lowering.lowerAssignment (src/ir/lower.zig) a .field_access target on a Vector fell through to the struct-field lookup, where no field matched a lane name, so field_ty stayed .unresolved. The store then built a ptrTo(.unresolved) whose pointee reached LLVM in emitStore (src/backend/llvm/ops.zig) → toLLVMTypeInfo .unresolved tripwire panic. The READ path resolved the lane fine; the two paths had diverged (issue-0083 two-resolver class).

Fix (per file).

  • src/ir/lower.zig — extracted a shared Lowering.vectorLaneIndex(field) resolver (.x/.y/.z/.w + colour aliases .r/.g/.b/.a → lane 0..3, null otherwise). The READ path (lowerFieldAccessOnType) now delegates to it (dropping its silent else 0 fallback), and a new vector branch in lowerAssignment uses the SAME resolver to structGepTyped a typed pointer to the lane and storeOrCompound with the vector element type (plain and compound assignment). A non-lane field now reports a field-not-found error on both paths instead of silently reading lane 0 / panicking.
  • src/backend/llvm/ops.zigemitStructGep now addresses a vector base_type with a [0, lane] GEP2, yielding a pointer to the lane element for the scalar store.

Regression test. examples/1506-vectors-lane-store.sx.[…]-init and = ----init writes, every lane of a 4-lane vector, colour aliases, and a compound lane assignment, reading each value back. Unit test src/ir/lower.test.zig pins the vectorLaneIndex contract.

Symptom

Assigning to a component of a Vector local — v.x = 1.0 (also .y / .z / .w) — aborts the compiler with the internal panic:

thread … panic: unresolved type reached LLVM emission — a type resolution
failure was not diagnosed/aborted
  src/backend/llvm/types.zig:175  toLLVMTypeInfo  (.unresolved arm @panic)
  src/backend/llvm/ops.zig:358    emitStore       (.pointer => toLLVMType(p.pointee))

READING a lane (x := v.x) is fine; only the STORE side hits it. The init form (= --- undefined vs = .[…] literal) does not matter — both panic once a lane is written. A literal lane count (Vector(3, f32)) triggers it, so this is NOT the lane-count resolution class (issue 0083); it is a distinct bug in the vector-lane store path, where the store's pointee type resolves to the .unresolved sentinel instead of the lane element type.

Discovered while fixing issue 0083 (attempt 5). It is pre-existing and orthogonal — confirmed by reproducing on the pristine pre-0083-attempt-5 compiler — so it was NOT introduced by the lane-count fix. The standard vector idiom (construct via a .[…] literal / a constructor function returning .[…], then read components or use vector arithmetic, as in examples/1500-vectors-vector-math.sx) is unaffected; only component ASSIGNMENT is broken.

Reproduction

#import "modules/std.sx";
main :: () {
    v : Vector(3, f32) = .[0.0, 0.0, 0.0];
    v.x = 1.0;                    // panic here
    print("x={}\n", v.x);
}

./zig-out/bin/sx run panics. Removing the v.x = 1.0 line (read-only) prints x=0.000000 and exits 0.

Investigation prompt

A store to a Vector lane (v.x = …) lowers a pointer-to-lane whose pointee type reaches LLVM as .unresolved, so emitStore (src/backend/llvm/ops.zig:358, the .pointer => toLLVMType(p.pointee) arm) hits the .unresolved tripwire panic in src/backend/llvm/types.zig:175. The lane READ path computes the lane element type correctly, so compare the lvalue/store lowering for a vector-component assignment against the rvalue/load path — the component-write path is likely building the lane pointer's pointee from a vector .x/.y/.z/.w field resolution that returns .unresolved (or a vector field-access that resolves the element type on load but not on store). Find where a Vector swizzle/component assignment lowers its destination pointer (grep for vector component handling in lower.zig assignment lowering and in the LLVM emitStore GEP path) and resolve the lane element type there the same way the load path does. Verify with the repro (expect x=1.000000) plus a .[…]-init write and a write to each of .x/.y/.z/.w on a 4-lane vector, then zig build && zig build test && bash tests/run_examples.sh green.