Repository navigation
fix(compiler)!: write a run inside one of a run at two running indexes - #257
Merged
Merged
Conversation
A write to one of a run that is a field of one of another run, each at an index worked out while running (`ss[i].xs[j] = 9`), was refused as a fault in the compiler, in a local and inside an element. The inner run is now copied out of the one of the outer it is in, written and copied back, the way a read of one already went; inside an element the copy is taken after the value is worked out. Closes #252 BREAKING CHANGE: a program writing one of a run inside one of another run, each at an index worked out while running, with a field or an index written down between them, was refused with K0505 and now compiles and runs.
siracozmen01
commented
Nov 5, 2026
siracozmen01
left a comment
Contributor
Author
There was a problem hiding this comment.
Review at a8e5493: no change needed. I read the code; I did not run it.
The re-take after the value (inside an element). assign_through_runs keeps the array handle, the element's index and the outermost run's index on the stack, from compile_address with place_apart and compile_run_index. It does not keep an address. Each through_one for the outermost way, out or back, makes a new elem_place from those refs. The element is therefore found against the array as it is at that access, and the copy taken after the value comes from the element as the value left it:
- A value that grows the array is covered:
grownpushes 64, andxs[1]must read 85. - A value that writes other parts of the element being written is covered:
taggedwrites the tag andxs[0], andbumpedwritesxs[0]under-=. - Shrinking the array is not covered. If a value left
koutside the array, the re-take'sload.elem.runwould be refused against the array's current length. That is the sameK0604element refusal any element read gives ("index 5 is outside an array of length 0"); the write would not go to a stale block. The program does not exercise this. iitself cannot go out of range from the value. It indexes a fixed run inside the element, and it was worked out and kept before the value, so the value cannot move it. The run's count is the type's, andin_the_runholdsito it at each access.
The other paths.
- In a local,
=takes the copies after the value. An operator takes them before the value and does not take them again. That relies on nothing in a value writing the caller's frame, which is the same assumption the existing single-run operator path (read_in_place) already makes. - Each copy holds only the inner run, so writing it back rewrites the inner run and not its siblings.
ss[i].tagandrows[i].seenchecked after the write pin that. - The copies are written back innermost first, and the three-deep
rows[i].s[i - 1].xs[j]case pins that ordering.
Other checks.
- The four
K0604rows refuse the outer and the inner index, in a local and inside an element, the last with+=. - The body has Problem, Change and Verification.
- The code block's column-0 lines (
module m,fn main() -> i32 {,error[K0505]: ...) contain noword(with an open scope and notype(scope):line.
Merged
siracozmen01
pushed a commit
that referenced
this pull request
Nov 5, 2026
🤖 I have created a release *beep* *boop*
---
## 0.7.0 (2026-11-05)
### Changed
- **Breaking:** A program reads and writes one of a fixed run at an index worked out while running where the run is inside one of another run at an index written down, inside an element of an array (`boxes[0].ss[1].xs[j]`). The verifier refused it under every build and switch as a fault in the compiler, with `K0411` ("`load.at` at 102 reads layout 0 at 24 bytes into what an address is at, which has no such field there"): with `struct S { tag: i32; xs: [i32; 4] }`, `struct Box { ss: [S; 2] }`, `boxes` holding one `Box` of `S(1, [1, 2, 3, 4])` and `S(2, [5, 6, 7, 8])` and `j` worked out while running as 2, a program printing `boxes[0].ss[1].xs[j]`, writing 9 there and printing `boxes[0].ss[1].xs[2]` now prints `7` and `9`. A run of runs with its first index written down (`bs[0].grid[1][j]`) is the same case. Nothing the verifier accepted before changes, and no instruction the compiler writes changes. ([#254](#254))
- **Breaking:** A program writes one of a run of runs at indexes worked out while running, in a local and inside an element of an array, and writes one of a run in a local at such an index with an operator or into a field of one. Each was refused under every build and switch as a fault in the compiler, with `K0505` ("this assigns to something that is not a place, which the checker allowed"): with `struct P { x: i32; y: i32 }`, `grid: [[i32; 3]; 2]` holding `[[1, 2, 3], [4, 5, 6]]`, `xs: [i32; 3]` holding `[1, 2, 3]`, `ps: [P; 2]` holding `[P(1, 2), P(3, 4)]`, and `i` and `j` worked out while running as 1 and 2, `grid[i][j] = 9`, `xs[i] += 5` and `ps[i].y = 9` were each refused, and printing `{grid[1][2]} {xs[1]} {ps[1].y}` after them now prints `9 7 9`. Where more than one index is worked out while running, each is held to its own count, so an index past its run is refused with `K0604` naming that index and that run's count ("index 3 is outside 3 of them"), in every engine and in a body compiled to C. `kest emit` writes one new instruction, `in.run`, and `load.slots` and `store.slots` carry how many slots of one of the run they read or write. A program that compiled before compiles to the same instructions, with `load.slots` and `store.slots` each two bytes wider. ([#256](#256))
- **Breaking:** A program writes one of a run that is a field of one of another run, each at an index worked out while running (`ss[i].xs[j] = 9`), in a local and inside an element of an array, with `=` or an operator. Each was refused under every build and switch as a fault in the compiler, with `K0505` ("this assigns to something that is not a place, which the checker allowed"): with `struct S { tag: i32; xs: [i32; 4] }`, `ss: [S; 2]` holding `S(1, [1, 2, 3, 4])` and `S(2, [5, 6, 7, 8])`, and `i` and `j` worked out while running as 1 and 2, `ss[i].xs[j] = 9` followed by printing `ss[1].xs[2]` now prints `9`. An index past its run is refused with `K0604` and that run's count. Inside an element, a write whose right-hand side writes the same element, or grows the array, keeps what the right-hand side wrote. A program that compiled before compiles to the same instructions. ([#257](#257))
This was referenced Nov 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A program writing one of a run that is a field of one of another run, each at an index worked out while running, was refused as a fault in the compiler, in a local and inside an element of an array (#252). With
kest runrefused it under every build and switch withand it now prints
9.boxes[k].ss[i].xs[j] = 9, withssa field of an element ofboxes: [Box], was refused the same way, and so was every operator into either. A write at more than one running index reaches the runs as one run of what the innermost holds, which needs each run to be the whole of one of the run around it; with a field between them, one ofssis atagand then the run, so there was no single run to write into. Readingss[i].xs[j]already ran: it copiesxsout ofss[i]and indexes the copy.Change
src/compile.c:run_waysfinds the ways a write goes through to its target, innermost first, out to one whose runs are in a local's slots or an element's bytes; each way is whatrun_wayfinds since fix(compiler)!: write runs of runs and local runs at running indexes #256.assign_through_runswrites through more than one: the indexes are worked out in the order they were written and kept, the run each way past the innermost reaches is copied out of the copy of the next way out (or of the local or the element), the target is written in the innermost copy, and the copies are written back, innermost first. In a local the copies are taken once, before the value is worked out, since a call reaches nothing in the caller's frame. Inside an element they are taken again after it, because working it out may write the element or grow the array. The operator and the value of an assignment are worked out by one function,assigned_value, for both this and the ways that were there. A place of any other shape compiles exactly as before. No instruction is added: the copies areload.slots,store.slots,load.elem.runandstore.elem.run, each held to its own run's count, so an index outside its run is refused with that run'sK0604sentence.test/regress/runinfield.kest: in a local,=and every operator intoss[i].xs[j], a field of one of a run of structs, a run of runs inside, a run ofvec2added to, three runs deep with a field between each (rows[i].s[i - 1].xs[j]), and writes in a loop. Inside an element, the same shapes, a write whose value writes the tag and another of the same run of the element being written, the same with-=, and+=with a value that pushes 64 elements onto the array being written.tools/check-commands.sh:K0604for the outer index past its run and the inner past its own, in a local, and the same inside an element, the second with+=.docs/internals/compiler.md: how such a write goes.A program refused before now runs, so this takes
!. A program that compiled before compiles to the same instructions. No version moves: neither the ABI, the JSON a command writes, nor what a deterministic program that ran before observes changes.Verification
test/regress/runinfield.kestand the four newK0604programs are refused withK0505by akestbuilt from main (cd939e9). On this branch the example prints9, the program answers 0 underkestandkest-debug, each with nothing,KEST_PLAIN=1,KEST_NOOPT=1andKEST_WALK_EVERY=1, and built as C withkest build --release, and eachK0604program is refused with the line its row asks for.kest emitwritesss[i].xs[j] = 9asload 10andload.slots +1 5 of 2 4 of each(xsofss[i], copied out),store.n 12 4,load2 11 16andstore.slots +12 1 of 4(the copy written atj), thenload 10,load.n 12 4andstore.slots +1 5 of 2 4 of each(written back).src/compile.c, each linked into its ownkest: with the copy inside an element taken before the value for=, it answers 6 (the value's own write to the element is undone); with no second copy after the value of an operator inside an element, it answers 8; with the copies written back outermost first, it answers 3 (the three-deep write is lost).src/compile.cwithclang-20,clang-formatin the CI image, andkest fmtof the new program.kest emitof every other Kest file in the tree (170 of them) writes the same bytes as akestbuilt from main.heavy.sh, on adevbuild of this branch (on cd939e9, whose tree is the one fix(compiler)!: write runs of runs and local runs at running indexes #256 was checked at):examples/embed,examples/embed-debugandexamples/embed-debugwithKEST_WALK_EVERY=1pass,ctest --preset dev -j4passes all 21 tests (regressionsruns the new program under both builds, the heap walked at every allocation,KEST_PLAIN,KEST_NOOPTand compiled to C;commandshas the newK0604rows) andtools/check-costs.shpasses.tools/check-c.shover every Kest file ofexamples,libandbenchpasses.Closes #252
BREAKING CHANGE: a program writing one of a run inside one of another run, each at an index worked out while running, with a field or an index written down between them, was refused with K0505 and now compiles and runs.