Skip to content

assert_buffer_store_uses_dynamic_fallback greps the whole module for 'getelementptr inbounds i8' — the shadow frame emits one, so 16 tests misfire under PERRY_RS4GC=0 #7505

Description

@proggeramlug

Split out of #7493, which worked around this with a pin. The workaround is correct for today's default but the underlying assertion is wrong, and it will misfire again.

The assertion

crates/perry-codegen/tests/native_proof_regressions.rs:

fn assert_buffer_store_uses_dynamic_fallback(ir: &str) {
    assert!(ir.contains("call void @js_buffer_set"), ...);
    assert!(!ir.contains("getelementptr inbounds i8"), ...);   // <— this one
}

Same shape at native_proof_regressions.rs:846 and twice in native_proof_buffer_views.rs (:606, :682).

The intent is "no native buffer GEP was emitted for this store". What it actually says is "this module contains no getelementptr inbounds i8 anywhere".

Why that breaks

The shadow-stack lowering's inline slot addressing (#7088) computes the slot address with getelementptr inbounds i8, for reasons that have nothing to do with a buffer. So under PERRY_RS4GC=0 — the sweep a GC engineer runs, and the one #7493's own diagnosis was built on — sixteen tests report a stale proof that was never emitted:

native_proof_regressions/invalidation.rs (15): localset_invalidates_native_i32_alias_facts, update_invalidates_native_i32_alias_facts, localset_invalidates_min_length_facts, localset_invalidates_active_bounded_buffer_index_facts, inner_loop_bounded_buffer_fact_is_removed_after_outer_fact_invalidation, localset_invalidates_buffer_view_local_length_sources, update_invalidates_buffer_view_local_length_sources, negative_loop_counter_does_not_emit_inbounds_buffer_gep, decrementing_loop_update_does_not_emit_inbounds_buffer_gep, body_counter_mutation_does_not_emit_inbounds_buffer_gep, inclusive_length_loop_does_not_emit_inbounds_buffer_gep, inclusive_local_length_bound_does_not_use_local_length_bound_fact, negative_loop_counter_does_not_use_local_length_bound_fact, body_mutation_of_local_bound_does_not_use_local_length_bound_fact, negative_loop_counter_does_not_use_min_length_bound_fact

native_proof_buffer_views.rs (1): loop_length_bound_does_not_prove_multibyte_buffer_read_inbounds

#7493 pinned all sixteen to NativeRootsPin::native(). That is honest — the assertion is only valid under native roots today — and it stops the false alarm. It does not make the assertion right.

Why the pin is not the fix

The proxy is unsound in the other direction too. It cannot distinguish:

  • a native buffer GEP for the store under test (what it means to catch), from
  • a native buffer GEP for an unrelated proven access elsewhere in the same module (a false positive it would also report), from
  • any other inbounds i8 the backend happens to emit (what actually bit us).

Any future pass that emits an inbounds i8 — a string-data address, an inline-cache slot, a native field offset — turns all sixteen red for a reason unrelated to their subject, exactly as the shadow frame did.

What to build

Scope the search to the store/read site:

  • cut the function body under test (the file already has enclosing_function / init_ir style helpers), then
  • find the js_buffer_set / typed-array call the fixture drives, and assert that the operand feeding it was not produced by an inbounds GEP off the buffer's data pointer — i.e. follow the data-flow rather than grepping the text. temp_root_operand_temporaries.rs already walks defs backwards this way (def_of / reaches_alloc), so the technique is in-tree.

Then remove the sixteen NativeRootsPin::native() pins: the fact-invalidation contract is lowering-independent and should be asserted that way.

Acceptance

  • The sixteen pass under BOTH lowerings, unpinned.
  • Sabotage-checked: restoring the stale proof (letting the invalidated fact through) must fail each of them.
  • grep -rn 'contains("getelementptr inbounds i8")' crates/perry-codegen/tests/ returns nothing module-scoped.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugConfirmed defect or regression

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions