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.
87 lines
4.5 KiB
Markdown
87 lines
4.5 KiB
Markdown
# 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.zig` — `emitStructGep` 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
|
|
```sx
|
|
#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.
|