diff --git a/changelog.d/11819-typed-region-guard-storage-byte.md b/changelog.d/11819-typed-region-guard-storage-byte.md new file mode 100644 index 0000000000..331db3201f --- /dev/null +++ b/changelog.d/11819-typed-region-guard-storage-byte.md @@ -0,0 +1 @@ +- **perry-codegen**: a counted loop over a receiver not declared a plain `Array` (a `Float64Array`, but equally a `Buffer`) no longer fails LLVM IR parsing with "use of undefined value '@PERRY_TA_VIEW_GUARD'". #10741's typed region guard still loaded that process-wide global after #10516 had removed it; it now reads the receiver's own storage byte (header byte 10) like the other #10516 sites. This failed four modules of the OpenCode v1.18.30 build, including `id.ts`. diff --git a/crates/perry-codegen/src/expr/index_get/guarded_array.rs b/crates/perry-codegen/src/expr/index_get/guarded_array.rs index 05439012c7..eab69e398b 100644 --- a/crates/perry-codegen/src/expr/index_get/guarded_array.rs +++ b/crates/perry-codegen/src/expr/index_get/guarded_array.rs @@ -357,8 +357,8 @@ pub(crate) fn emit_array_region_guard( /// The `Float64Array` twin of [`emit_array_region_guard`]'s dense facts /// (#10741): a heap pointer whose GC header names a typed array (never /// forwarded: typed arrays live in the non-moving space), of element kind -/// `Float64`, with inline storage (`PERRY_TA_VIEW_GUARD == 0`: no aliasing -/// view exists, so every typed array's elements start at payload `+16`), and +/// `Float64`, with inline storage (its own storage byte, header byte 10, is +/// `TA_STORAGE_INLINE` — #10516 — so its elements start at payload `+16`), and /// the same bounds against its `length` (payload `+0`). On a pass it stores /// the element base into `base_slot`; F-body's reads and stores are then the /// same raw `f64` slots as a dense array's, its reads canonicalising a NaN @@ -399,9 +399,16 @@ pub(crate) fn emit_typed_f64_region_guard( let kind_ptr = blk.inttoptr(I64, &kind_addr); let kind = blk.load(I8, &kind_ptr); let is_f64 = blk.icmp_eq(I8, &kind, "7"); // KIND_FLOAT64 - let vg = blk.load(I64, "@PERRY_TA_VIEW_GUARD"); - let no_views = blk.icmp_eq(I64, &vg, "0"); - let ok = blk.and(I1, &is_ta, &no_views); + + // #10516: the receiver's own storage byte (header byte 10, + // `TA_STORAGE_INLINE` = 0) licenses `data == header + 16`. The + // process-wide `PERRY_TA_VIEW_GUARD` this used to read no longer + // exists, so referencing it left the symbol undefined. + let storage_addr = blk.add(I64, &handle, "10"); + let storage_ptr = blk.inttoptr(I64, &storage_addr); + let storage = blk.load(I8, &storage_ptr); + let inline_storage = blk.icmp_eq(I8, &storage, "0"); + let ok = blk.and(I1, &is_ta, &inline_storage); let ok = blk.and(I1, &ok, &is_f64); blk.cond_br(&ok, &len_label, &join_label); handle diff --git a/crates/perry-codegen/src/expr/region_array_loop_tests.rs b/crates/perry-codegen/src/expr/region_array_loop_tests.rs index 42acba08f8..e99fd4ed54 100644 --- a/crates/perry-codegen/src/expr/region_array_loop_tests.rs +++ b/crates/perry-codegen/src/expr/region_array_loop_tests.rs @@ -277,6 +277,33 @@ fn a_float64array_receiver_reads_canonicalise_nan() { ); } +#[test] +fn a_typed_region_guard_names_only_symbols_that_exist() { + // #10516 removed the process-wide `PERRY_TA_VIEW_GUARD` in favour of the + // typed array header's own storage byte. The typed region guard, merged + // after it (#10741), still loaded the removed global, and the text + // assertions above cannot see a reference to a symbol nothing declares. + // Any receiver not declared a plain Array takes this guard -- OpenCode's + // id.ts loops over Buffers -- so ask LLVM, the one check that sees an + // undefined value. + for (name, elem) in [ + ("rarr_typed_verify", Type::Named("Float64Array".to_string())), + ("rarr_buffer_verify", Type::Named("Buffer".to_string())), + ] { + let ir = probe_ir(name, elem, physics_body(), None); + assert!( + ir.contains("rloop.ta"), + "{name}: the loop must take the typed region guard for this test to mean anything:\n{ir}" + ); + assert!( + !ir.contains("@PERRY_TA_VIEW_GUARD"), + "{name}: the guard reads the receiver's storage byte, not a removed global:\n{ir}" + ); + crate::testing::verify_ir(&ir, name) + .unwrap_or_else(|e| panic!("{name}: LLVM rejected the module: {e}\n{ir}")); + } +} + #[test] fn a_call_in_the_body_sets_the_dirty_flag_and_the_next_iteration_rechecks() { let mut body = physics_body();