Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
type: "Bug Fix"
title: "A promoted array must be probed by every reader, and the probe lives at the CALL SITE, not in the runtime helper"
description: "An indexed PHP array in elephc is promoted to hash storage at RUN TIME by rt array set mixed key whenever a written key does not fit the packed 0..n 1 shape $a 5 = x on a 1 element array . Promotion is decided by the KEY"
resource: "src/codegen/lower_inst/iterators.rs"
tags: ["session-learning", "promoted-array", "hash-storage", "heap-kind", "codegen", "call-site-probe", "var_dump", "json_encode", "serialize", "implode", "foreach"]
timestamp: "2026-09-12T16:44:43.197Z"
x-kage-id: "repo:sparkling-jingling-flute:bug_fix:a-promoted-array-must-be-probed-by-every-reader-and-the-probe-lives-at-the-call-"
x-kage-type: "bug_fix"
x-kage-status: "approved"
x-kage-scope: "repo"
x-kage-visibility: "team"
x-kage-confidence: 0.7
x-kage-verified: "verified"
x-kage-paths: ["src/codegen/lower_inst/iterators.rs", "src/codegen/lower_inst/arrays.rs", "src/codegen/lower_inst/builtins/strings/split.rs", "src/codegen/lower_inst/builtins/arrays/values.rs", "tests/runtime_promoted_array_reader_tests.rs"]
x-kage-stack: ["rust", "php", "aarch64", "codegen"]
---

# A promoted array must be probed by every reader, and the probe lives at the CALL SITE, not in the runtime helper

> An indexed PHP array in elephc is promoted to hash storage at RUN TIME by rt array set mixed key whenever a written k…

An indexed PHP array in elephc is promoted to hash storage at RUN TIME by `__rt_array_set_mixed_key` whenever a written key does not fit the packed 0..n-1 shape (`$a[5] = x` on a 1-element array). Promotion is decided by the KEY, never by the element type, so an `array<int>` is promoted exactly like an `array<mixed>` — a reader may not trust its static type and skip the probe.

Every reader that walks the payload has to ask `__rt_heap_kind(p) == 3` (HASH) first and take the hash path when it answers yes. Before the fix, seven readers walked the packed slots unconditionally and rendered a promoted array as its stale pre-promotion prefix: `var_dump`, `print_r`, `serialize`, `var_export`, `json_encode` (both the int and the dynamic encoder), `implode`/`join`, `array_values`, and `foreach` over a boxed array.

WHERE THE PROBE ACTUALLY LIVES — the part that misleads a grep, and cost a wrong conclusion during a later reconciliation: it is emitted PER CALL SITE (~32 aarch64 instructions, ~130 bytes) in the LOWERING (`src/codegen/lower_inst/arrays.rs`, `iterators.rs`, `builtins/arrays/values.rs`, `builtins/strings/split.rs`), NOT inside the runtime helpers. Grepping `heap_kind` in `src/codegen_support/runtime/**` returns ZERO hits and looks like the fix is missing; it is not. The helpers are listed among this memory's paths only because they were touched. Per-call-site is deliberate: the extraction is parameterised by element layout (elem_size 8 vs 16, and the append is word / string-persist / mixed-box), matching the shape `array_values` already used. A helper-per-layout refactor (5 helpers) would buy the size back and is the obvious follow-up if implode call-site size ever matters.

Note the two DIFFERENT tag spaces, which is the other easy confusion: `__rt_heap_kind` answers 3 for HASH, while the var_dump/print_r value_type stamp uses 4=array and 5=hash. They are not the same numbering.

Verification that actually decides it: each half of the fix needs its own assertion AND its own sentinel — reverting one half must turn exactly one test red. That is how the implode half and the array_values half were each proven load-bearing.
Evidence: Original: 15 tests in tests/runtime_promoted_array_reader_tests.rs, every expectation taken from `php -n`; three sentinel runs proving each half load-bearing. Re-verified during reconciliation at commit 5de1cd8b4d: the suite is 15/15 green, and `__rt_heap_kind` call sites are present in src/codegen/lower_inst/arrays.rs and iterators.rs while the four runtime-helper files contain no reference to it — which is the documented design, not a gap.
Verified by: cargo test --test runtime_promoted_array_reader_tests (15 passed) re-run during this reconciliation

## Verification

Original: 15 tests in tests/runtime_promoted_array_reader_tests.rs, every expectation taken from `php -n`; three sentinel runs proving each half load-bearing. Re-verified during reconciliation at commit 5de1cd8b4d: the suite is 15/15 green, and `__rt_heap_kind` call sites are present in src/codegen/lower_inst/arrays.rs and iterators.rs while the four runtime-helper files contain no reference to it — which is the documented design, not a gap.

# Citations

[1] explicit_capture (2026-09-12T16:44:43.197Z)

## Kage state

Machine state for lossless round-trip; OKF consumers can ignore it.

```json kage-state
{"schema_version":2,"id":"repo:sparkling-jingling-flute:bug_fix:a-promoted-array-must-be-probed-by-every-reader-and-the-probe-lives-at-the-call-","title":"A promoted array must be probed by every reader, and the probe lives at the CALL SITE, not in the runtime helper","summary":"An indexed PHP array in elephc is promoted to hash storage at RUN TIME by rt array set mixed key whenever a written key does not fit the packed 0..n 1 shape $a 5 = x on a 1 element array . Promotion is decided by the KEY","body":"An indexed PHP array in elephc is promoted to hash storage at RUN TIME by `__rt_array_set_mixed_key` whenever a written key does not fit the packed 0..n-1 shape (`$a[5] = x` on a 1-element array). Promotion is decided by the KEY, never by the element type, so an `array<int>` is promoted exactly like an `array<mixed>` — a reader may not trust its static type and skip the probe.\n\nEvery reader that walks the payload has to ask `__rt_heap_kind(p) == 3` (HASH) first and take the hash path when it answers yes. Before the fix, seven readers walked the packed slots unconditionally and rendered a promoted array as its stale pre-promotion prefix: `var_dump`, `print_r`, `serialize`, `var_export`, `json_encode` (both the int and the dynamic encoder), `implode`/`join`, `array_values`, and `foreach` over a boxed array.\n\nWHERE THE PROBE ACTUALLY LIVES — the part that misleads a grep, and cost a wrong conclusion during a later reconciliation: it is emitted PER CALL SITE (~32 aarch64 instructions, ~130 bytes) in the LOWERING (`src/codegen/lower_inst/arrays.rs`, `iterators.rs`, `builtins/arrays/values.rs`, `builtins/strings/split.rs`), NOT inside the runtime helpers. Grepping `heap_kind` in `src/codegen_support/runtime/**` returns ZERO hits and looks like the fix is missing; it is not. The helpers are listed among this memory's paths only because they were touched. Per-call-site is deliberate: the extraction is parameterised by element layout (elem_size 8 vs 16, and the append is word / string-persist / mixed-box), matching the shape `array_values` already used. A helper-per-layout refactor (5 helpers) would buy the size back and is the obvious follow-up if implode call-site size ever matters.\n\nNote the two DIFFERENT tag spaces, which is the other easy confusion: `__rt_heap_kind` answers 3 for HASH, while the var_dump/print_r value_type stamp uses 4=array and 5=hash. They are not the same numbering.\n\nVerification that actually decides it: each half of the fix needs its own assertion AND its own sentinel — reverting one half must turn exactly one test red. That is how the implode half and the array_values half were each proven load-bearing.\nEvidence: Original: 15 tests in tests/runtime_promoted_array_reader_tests.rs, every expectation taken from `php -n`; three sentinel runs proving each half load-bearing. Re-verified during reconciliation at commit 5de1cd8b4d: the suite is 15/15 green, and `__rt_heap_kind` call sites are present in src/codegen/lower_inst/arrays.rs and iterators.rs while the four runtime-helper files contain no reference to it — which is the documented design, not a gap.\nVerified by: cargo test --test runtime_promoted_array_reader_tests (15 passed) re-run during this reconciliation","type":"bug_fix","scope":"repo","visibility":"team","sensitivity":"internal","status":"approved","confidence":0.7,"tags":["session-learning","promoted-array","hash-storage","heap-kind","codegen","call-site-probe","var_dump","json_encode","serialize","implode","foreach"],"paths":["src/codegen/lower_inst/iterators.rs","src/codegen/lower_inst/arrays.rs","src/codegen/lower_inst/builtins/strings/split.rs","src/codegen/lower_inst/builtins/arrays/values.rs","tests/runtime_promoted_array_reader_tests.rs"],"stack":["rust","php","aarch64","codegen"],"source_refs":[{"kind":"explicit_capture","captured_at":"2026-09-12T16:44:43.197Z"}],"context":{"fact":"An indexed PHP array in elephc is promoted to hash storage at RUN TIME by `__rt_array_set_mixed_key` whenever a written key does not fit the packed 0..n-1 shape (`$a[5] = x` on a 1-element array). Promotion is decided by the KEY, never by the element type, so an `array<int>` is promoted exactly like an `array<mixed>` — a reader may not trust its static type and skip the probe.","verification":"Original: 15 tests in tests/runtime_promoted_array_reader_tests.rs, every expectation taken from `php -n`; three sentinel runs proving each half load-bearing. Re-verified during reconciliation at commit 5de1cd8b4d: the suite is 15/15 green, and `__rt_heap_kind` call sites are present in src/codegen/lower_inst/arrays.rs and iterators.rs while the four runtime-helper files contain no reference to it — which is the documented design, not a gap."},"freshness":{"ttl_days":365,"last_verified_at":"2026-09-12T16:44:43.197Z","path_fingerprints":[{"path":"src/codegen/lower_inst/iterators.rs","sha256":"ea33eb4729d90b4277b8e9fb1b494fd3474c0ad2d98813aff04f9aab99f9a0c6","size":110910},{"path":"src/codegen/lower_inst/arrays.rs","sha256":"67cd2f80fbf2cde04cbbcae35a4674a3fc008c665f580c71fc2b80616d2c46ed","size":143233},{"path":"src/codegen/lower_inst/builtins/strings/split.rs","sha256":"cc1732570e9dcc5cae21f642bef4894d45a8b1a2894b8c4979c2302a6294caa9","size":36003},{"path":"src/codegen/lower_inst/builtins/arrays/values.rs","sha256":"359d2eeaae095649dc3a4e0697b46da810c38d1044ec0fafeb48566a499747ea","size":27017},{"path":"tests/runtime_promoted_array_reader_tests.rs","sha256":"b95a52bef13cdf51fa166e0438f5f747195ec5672e524d94715a04befa7b86e9","size":14317}],"path_fingerprint_policy":"source_hash_staleness","verification":"repo_local_agent_capture"},"edges":[{"relation":"supersedes","to":"repo:sparkling-jingling-flute:bug_fix:a-promoted-array-must-be-probed-by-every-reader-whatever-its-static-element-type","evidence":"Its path fingerprints were taken before its own fixing commit landed, so the four runtime-helper files hashed differently and it was withheld from recall despite being correct. Claim re-verified at 5de1cd8b4d (15/15 green) and carried over intact, re-grounded on the lowering files that actually carry the probe. Adds the navigational trap that caused a wrong reading during this reconciliation: grepping heap_kind in the runtime helpers returns zero hits because the probe is emitted per call site, and __rt_heap_kind's 3=HASH is a different tag space from the value_type stamp's 5=hash.","created_at":"2026-09-12T16:44:52.451Z"}],"quality":{"reviewer":"repo-local-agent","votes_up":0,"votes_down":0,"uses_30d":0,"reports_stale":0,"review_boundary":"git_or_pr","promotion_requires_review":true,"discovery_tokens":8000,"discovery_tokens_estimated":true,"score":76,"reasons":["high-value memory type","has source evidence","grounded to repo paths","tagged","actionable rationale or verification"],"risks":["possible duplicate memory"],"duplicate_candidates":[{"id":"repo:sparkling-jingling-flute:bug_fix:a-promoted-array-must-be-probed-by-every-reader-whatever-its-static-element-type","title":"A promoted array must be probed by every reader, whatever its static element type","score":0.59,"status":"approved"}],"estimated_tokens_saved":682},"created_at":"2026-09-12T16:44:43.197Z","updated_at":"2026-09-12T16:45:00.116Z","author_branch":"feat/opcache-runtime-cache"}
```

Loading
Loading