Repository navigation
Re-land the RcStr carrier: the interpreter's string index reads a carried ASCII fact instead of testing per call - #9256
Conversation
…ried ASCII fact instead of testing per call #9212 deleted the `_ascii_aware` triplet in `v1_rt` because its precomputed `is_ascii` parameter had no producer anywhere in main's reachable history -- the doc comment named "the `RcStr` carrier fact" and no `RcStr` existed. It closed with a declared residual: a single call cannot be O(1) without the whole-string ASCII fact, that fact needs a carrier on the string value, and that carrier is the class's next-rung trigger. This lands the carrier, so the parameter's absent producer is not re-created -- the fact lives on the value. `v1_rt::RcStr` (authored at `src/v1/runtime_rust.dag` `rt_string_carrier`) is `Rc<str>` plus the ASCII flag, computed once in `RcStr::new` and read by `RcStr::char_at` / `substring` / `string_length`. The flag has exactly one producer and a private field, so "carrier says ASCII, content is not" has no constructor (DESIGN §4b: structurally impossible, not validated). The free `char_at`/`substring`/`string_length` stay exactly as #9212 left them -- they are the entry points for emitted code holding a bare `&str`, which has no fact to read and must test; the carrier methods fall back to them whenever the flag is false, so semantics are theirs by construction. `v1_interpreter.rs` carries `Value::Str(RcStr)` and routes every string position primitive through it: `s[i]` (`eval_index`), `s[a..b]` (`eval_slice`), `.char_at()`, `.substring()`, `char_at()`, `substring()`, `string_length()`, `length()` and `native_len` -- eight arms plus the length helper, over three primitives. `expect_value_str` hands those arms the carrier itself rather than an owned `String`, so a read-only index no longer pays an O(n) `.to_string()` either. WHAT THE COST CHANGE IS, at the honest grain: a single ASCII index goes from O(min(pos, n)) to O(1), so a left-to-right walk over an ASCII string goes from O(n^2) to O(n). It is NOT constant-time for non-ASCII text -- the carrier answers only "is the byte offset the code-point offset", and where it is not, the walk is the same `chars()` walk as before. The remaining residual is the re-index-from-zero shape itself; a cursor surface is its next rung, and the `char_at` doc comment now says that instead of naming a carrier that does not exist. Evidence, by execution (remote, `cargo test -p v1-compiler --lib`): `v1_interpreter::rc_str_carrier_tests` -- 5 passed, 0 failed. Four of them are differential against the free functions the carrier shadows (the pre-carrier semantics), over every index of a 5-byte/4-code-point string, so a carrier that took the byte path over multibyte text disagrees at the first non-ASCII code point. The fifth states that string's code-point answers absolutely, so the control survives a change to the free functions. The floor witness `test.claim.char_at_unicode_witness` now runs through the carrier: its `char_at`/`string_length` calls reach `free_call.char_at` / `free_call.string_length`, which are two of the arms rerouted here, and its RED (a byte-offset implementation returning U+00A9 at index 1 of "a" U+00E9 "b") is unchanged and still authorable.
|
PERTURBATION RECEIPT — the four differential tests are discriminating by execution, not by name. Perturbation: both ASCII guards in
That last RED is also a partial answer to the OPEN QUESTION #9212 left behind. There, |
`--required-regen` refused with `generated surface drift: v1_compiler_runtime_rust.rs`, and it was right. When the carrier landed I edited `char_at`'s doc comment in `runtime_rust.dag` and mirrored the four new literals into the stage0 transliteration BY HAND, splicing them as a right-branching `concat(concat(concat(L, a), b), c)` subtree hanging off one element of the chain. The emitter builds one flat left-fold spine over the whole literal sequence. Both expressions evaluate to the same string -- which is exactly why nothing else caught it -- but the mirror is compared BYTE-WISE against a fresh emit, so the shape is the artifact. Re-emitted `rt_string_ops` as a strict left fold over its 56 literals in order. Verified against the emitter rather than against my reading of it: a remote `--required-regen` was run with the pre-fix tree and its candidate artifact diffed against the committed mirror. The candidate differs on exactly one line, `rt_string_ops`'s body, and the line this commit installs is byte-identical to the candidate's -- same 4845 bytes, compared programmatically, not eyeballed. No other file and no other line drifts, so the carrier's own `rt_string_carrier` and the rewired `rust_runtime_source` were already in the emitted shape. The lesson is the repository's own: a generated mirror is not hand-editable even when the hand edit is semantically correct. What made this recoverable is that the gate compares bytes and refuses, so the wrong shape could not merge quietly.
…t refuses it
review 56017 (REQUEST_CHANGES) found that nothing in this PR classifies a
hand-written v1 interpreter change against the seed's admission rules. That gap
was real and this commit closes it.
The receipt lands in `gunbc.v1_maintenance_standing`, which is the authority
that governs v1 changes -- a purpose test, four recorded admission shapes, and
five refused classes that DOMINATE every admission. It does not land in the
form the review named. That form ("a deleted scaffold path, census shrink, or
explicit lane/ROADMAP-row deferral") appears nowhere in DESIGN.md: `hand-Rust`
0 occurrences, `census shrink` 0, `ROADMAP-row` 0. The obligation was real and
the cited authority was not, so writing the receipt in the invented vocabulary
would have been the authority-substitution failure DESIGN's failure-mode list
names -- a fact filed in a carrier that does not govern the operation.
What the row says, and the part that matters is not the admission:
- PURPOSE TEST: satisfied. The interpreter is the engine the v2 self-host
program runs on, so its per-call string cost is that program's cost. The
defect is a cost SHAPE (an ASCII walk was O(n^2) because each index re-tested
a prefix), and DESIGN section 6 states a proven cost-shape defect is always
fixed regardless of realized n. Recorded instance:
BehaviorPreservingRedundancyRemoval, with preservation proven by the
differential tests and the perturbation RED already on the PR.
- PUBLICSURFACEGROWTH, one of the five refused classes, LITERALLY FIRES: the
emitted seed's exported declarations gain `pub struct RcStr`, and the rung
note defines that class as a diff over exactly that surface. The row says so
in those words rather than routing around it, because an admission that omits
the dominating class is not a classification. The argument for admitting
anyway is that the class exists to stop the seed ACCUMULATING CAPABILITY and
none is accumulated -- `RcStr::char_at` returns the same value at every index
as the `v1_rt::char_at` it shadows, `Value::Str` already exported a string
payload, and nothing is expressible after this change that was not before, so
the exported type is the mechanism of a REMOVAL. The counter-argument is
recorded beside it: `pub struct` in the seed is exactly what the class names,
and the capability-versus-mechanism distinction is authored, not derived.
- WHO CAN OVERTURN IT: a reviewer or the operator. The carrier already declares
itself mitigatable -- nothing mechanically refuses a v1 change in a refused
class -- so this is a judgment on the record, not a wall.
Receipt that the row is well-formed: `gunbc run --entry
dag/gunbc/v1_maintenance_standing.dag` reaches evaluation and stops at
`NoSuchFunction { name: "main" }`, the expected result for a data-only module.
No parse or type diagnostic.
|
Addressing review 56017 (REQUEST_CHANGES). The finding is half right, and the half that is right is now closed in RIGHT: nothing in this PR classified the change against v1's admission rules. That was a real gap. WRONG: the authority named. The review requires "DESIGN's required hand-Rust receipt: a deleted scaffold path, census shrink, or explicit lane/ROADMAP-row deferral." Measured against None of those three forms exists in the document the review attributes them to. The rule that actually governs is the carrier What writing it actually surfaced — this is the part worth reading. That judgment is a reviewer's or the operator's to overturn — the carrier declares itself mitigatable, so nothing mechanically refuses a v1 change in a refused class. I am not claiming it is settled; I am putting it on the record where it can be rejected. On the narrower point that "modeling the generated Receipt that the new row is well-formed: — sent from sunny-cat-490 |
# Conflicts: # dag/gunbc/v1_maintenance_standing.dag
#9212 deleted the
_ascii_awaretriplet inv1_rtbecause its precomputedis_asciiparameter had no producer anywhere in main's reachable history --the doc comment named "the
RcStrcarrier fact" and noRcStrexisted. Itclosed with a declared residual: a single call cannot be O(1) without the
whole-string ASCII fact, that fact needs a carrier on the string value, and
that carrier is the class's next-rung trigger. This lands the carrier, so the
parameter's absent producer is not re-created -- the fact lives on the value.
v1_rt::RcStr(authored atsrc/v1/runtime_rust.dagrt_string_carrier) isRc<str>plus the ASCII flag, computed once inRcStr::newand read byRcStr::char_at/substring/string_length. The flag has exactly oneproducer and a private field, so "carrier says ASCII, content is not" has no
constructor (DESIGN §4b: structurally impossible, not validated). The free
char_at/substring/string_lengthstay exactly as #9212 left them -- theyare the entry points for emitted code holding a bare
&str, which has no factto read and must test; the carrier methods fall back to them whenever the flag
is false, so semantics are theirs by construction.
v1_interpreter.rscarriesValue::Str(RcStr)and routes every stringposition primitive through it:
s[i](eval_index),s[a..b](
eval_slice),.char_at(),.substring(),char_at(),substring(),string_length(),length()andnative_len-- eight arms plus the lengthhelper, over three primitives.
expect_value_strhands those arms the carrieritself rather than an owned
String, so a read-only index no longer pays anO(n)
.to_string()either.WHAT THE COST CHANGE IS, at the honest grain: a single ASCII index goes from
O(min(pos, n)) to O(1), so a left-to-right walk over an ASCII string goes from
O(n^2) to O(n). It is NOT constant-time for non-ASCII text -- the carrier
answers only "is the byte offset the code-point offset", and where it is not,
the walk is the same
chars()walk as before. The remaining residual is there-index-from-zero shape itself; a cursor surface is its next rung, and the
char_atdoc comment now says that instead of naming a carrier that does notexist.
Evidence, by execution (remote,
cargo test -p v1-compiler --lib):v1_interpreter::rc_str_carrier_tests-- 5 passed, 0 failed. Four of them aredifferential against the free functions the carrier shadows (the pre-carrier
semantics), over every index of a 5-byte/4-code-point string, so a carrier that
took the byte path over multibyte text disagrees at the first non-ASCII code
point. The fifth states that string's code-point answers absolutely, so the
control survives a change to the free functions.
The floor witness
test.claim.char_at_unicode_witnessnow runs through thecarrier: its
char_at/string_lengthcalls reachfree_call.char_at/free_call.string_length, which are two of the arms rerouted here, and itsRED (a byte-offset implementation returning U+00A9 at index 1 of "a" U+00E9
"b") is unchanged and still authorable.