diff --git a/CLAUDE.md b/CLAUDE.md index 1570e98fc8..fe924e2522 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,7 +8,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co Perry is a native TypeScript compiler written in Rust that compiles TypeScript source code directly to native executables. It uses SWC for TypeScript parsing and LLVM for code generation. -**Current Version:** 0.5.1385 +**Current Version:** 0.5.1386 ## TypeScript Parity Status diff --git a/Cargo.lock b/Cargo.lock index 595c466a80..0d37bf2099 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5547,7 +5547,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "perry" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "anyhow", "base64", @@ -5607,14 +5607,14 @@ dependencies = [ [[package]] name = "perry-api-manifest" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "serde", ] [[package]] name = "perry-audio-miniaudio" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "cc", "libc", @@ -5622,7 +5622,7 @@ dependencies = [ [[package]] name = "perry-codegen" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "anyhow", "inkwell", @@ -5639,7 +5639,7 @@ dependencies = [ [[package]] name = "perry-codegen-arkts" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "anyhow", "perry-hir", @@ -5647,7 +5647,7 @@ dependencies = [ [[package]] name = "perry-codegen-glance" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "anyhow", "perry-hir", @@ -5655,7 +5655,7 @@ dependencies = [ [[package]] name = "perry-codegen-js" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "anyhow", "perry-dispatch", @@ -5664,7 +5664,7 @@ dependencies = [ [[package]] name = "perry-codegen-swiftui" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "anyhow", "perry-hir", @@ -5672,7 +5672,7 @@ dependencies = [ [[package]] name = "perry-codegen-wasm" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "anyhow", "base64", @@ -5684,7 +5684,7 @@ dependencies = [ [[package]] name = "perry-codegen-wear-tiles" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "anyhow", "perry-hir", @@ -5692,7 +5692,7 @@ dependencies = [ [[package]] name = "perry-container-compose" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "anyhow", "async-trait", @@ -5721,14 +5721,14 @@ dependencies = [ [[package]] name = "perry-container-e2e" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "anyhow", ] [[package]] name = "perry-diagnostics" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "serde", "serde_json", @@ -5736,7 +5736,7 @@ dependencies = [ [[package]] name = "perry-dispatch" -version = "0.5.1385" +version = "0.5.1386" [[package]] name = "perry-doc-fixture-my-bindings" @@ -5747,7 +5747,7 @@ dependencies = [ [[package]] name = "perry-doc-tests" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "anyhow", "clap", @@ -5762,7 +5762,7 @@ dependencies = [ [[package]] name = "perry-ext-ads" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "block2", "objc2", @@ -5772,7 +5772,7 @@ dependencies = [ [[package]] name = "perry-ext-argon2" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "argon2", "perry-ffi", @@ -5780,7 +5780,7 @@ dependencies = [ [[package]] name = "perry-ext-axios" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "perry-ffi", "reqwest", @@ -5789,7 +5789,7 @@ dependencies = [ [[package]] name = "perry-ext-bcrypt" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "bcrypt", "perry-ffi", @@ -5797,7 +5797,7 @@ dependencies = [ [[package]] name = "perry-ext-better-sqlite3" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "perry-ffi", "rusqlite", @@ -5805,7 +5805,7 @@ dependencies = [ [[package]] name = "perry-ext-cheerio" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "perry-ffi", "scraper", @@ -5813,7 +5813,7 @@ dependencies = [ [[package]] name = "perry-ext-commander" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "perry-ffi", "perry-runtime", @@ -5821,7 +5821,7 @@ dependencies = [ [[package]] name = "perry-ext-cron" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "chrono", "cron", @@ -5831,7 +5831,7 @@ dependencies = [ [[package]] name = "perry-ext-dayjs" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "chrono", "perry-ffi", @@ -5839,7 +5839,7 @@ dependencies = [ [[package]] name = "perry-ext-decimal" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "perry-ffi", "rust_decimal", @@ -5847,7 +5847,7 @@ dependencies = [ [[package]] name = "perry-ext-dotenv" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "perry-ffi", "serde_json", @@ -5855,7 +5855,7 @@ dependencies = [ [[package]] name = "perry-ext-ethers" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "perry-ffi", "rand 0.10.1", @@ -5863,7 +5863,7 @@ dependencies = [ [[package]] name = "perry-ext-events" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "perry-ffi", "perry-runtime", @@ -5871,14 +5871,14 @@ dependencies = [ [[package]] name = "perry-ext-exponential-backoff" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-fastify" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "bytes", "http-body-util", @@ -5896,7 +5896,7 @@ dependencies = [ [[package]] name = "perry-ext-fetch" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "bytes", "lazy_static", @@ -5909,7 +5909,7 @@ dependencies = [ [[package]] name = "perry-ext-http" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "bytes", "h2", @@ -5933,7 +5933,7 @@ dependencies = [ [[package]] name = "perry-ext-ioredis" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "lazy_static", "perry-ffi", @@ -5943,7 +5943,7 @@ dependencies = [ [[package]] name = "perry-ext-jsonwebtoken" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "base64", "jsonwebtoken", @@ -5954,7 +5954,7 @@ dependencies = [ [[package]] name = "perry-ext-lru-cache" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "lru", "perry-ffi", @@ -5963,7 +5963,7 @@ dependencies = [ [[package]] name = "perry-ext-moment" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "chrono", "perry-ffi", @@ -5971,7 +5971,7 @@ dependencies = [ [[package]] name = "perry-ext-mongodb" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "bson", "futures-util", @@ -5983,7 +5983,7 @@ dependencies = [ [[package]] name = "perry-ext-mysql2" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "chrono", "perry-ffi", @@ -5993,7 +5993,7 @@ dependencies = [ [[package]] name = "perry-ext-nanoid" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "nanoid", "perry-ffi", @@ -6002,7 +6002,7 @@ dependencies = [ [[package]] name = "perry-ext-net" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "bytes", "perry-ffi", @@ -6015,7 +6015,7 @@ dependencies = [ [[package]] name = "perry-ext-node-forge" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "const-oid 0.9.6", "der 0.7.10", @@ -6034,7 +6034,7 @@ dependencies = [ [[package]] name = "perry-ext-nodemailer" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "lettre", "perry-ffi", @@ -6044,7 +6044,7 @@ dependencies = [ [[package]] name = "perry-ext-pdf" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "perry-ffi", "printpdf", @@ -6052,7 +6052,7 @@ dependencies = [ [[package]] name = "perry-ext-pg" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "perry-ffi", "sqlx", @@ -6061,7 +6061,7 @@ dependencies = [ [[package]] name = "perry-ext-ratelimit" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "governor", "perry-ffi", @@ -6069,7 +6069,7 @@ dependencies = [ [[package]] name = "perry-ext-sharp" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "fast_image_resize", "image", @@ -6079,14 +6079,14 @@ dependencies = [ [[package]] name = "perry-ext-slugify" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-streams" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "lazy_static", "perry-ffi", @@ -6095,7 +6095,7 @@ dependencies = [ [[package]] name = "perry-ext-undici" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "perry-ffi", "perry-runtime", @@ -6104,7 +6104,7 @@ dependencies = [ [[package]] name = "perry-ext-uuid" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "perry-ffi", "uuid", @@ -6112,7 +6112,7 @@ dependencies = [ [[package]] name = "perry-ext-validator" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "perry-ffi", "regex", @@ -6122,7 +6122,7 @@ dependencies = [ [[package]] name = "perry-ext-ws" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "futures-util", "lazy_static", @@ -6135,7 +6135,7 @@ dependencies = [ [[package]] name = "perry-ext-zlib" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "brotli", "flate2", @@ -6145,7 +6145,7 @@ dependencies = [ [[package]] name = "perry-ffi" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "dashmap", "once_cell", @@ -6154,7 +6154,7 @@ dependencies = [ [[package]] name = "perry-hir" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "anyhow", "perry-api-manifest", @@ -6172,7 +6172,7 @@ dependencies = [ [[package]] name = "perry-parser" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "anyhow", "perry-diagnostics", @@ -6184,7 +6184,7 @@ dependencies = [ [[package]] name = "perry-runtime" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "anyhow", "base64", @@ -6226,14 +6226,14 @@ dependencies = [ [[package]] name = "perry-runtime-static" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "perry-runtime", ] [[package]] name = "perry-stdlib" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "aes 0.8.4", "aes 0.9.1", @@ -6328,14 +6328,14 @@ dependencies = [ [[package]] name = "perry-stdlib-static" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "perry-stdlib", ] [[package]] name = "perry-transform" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "anyhow", "perry-hir", @@ -6344,14 +6344,14 @@ dependencies = [ [[package]] name = "perry-ui" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "perry-ui-model", ] [[package]] name = "perry-ui-android" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "base64", "itoa", @@ -6368,7 +6368,7 @@ dependencies = [ [[package]] name = "perry-ui-geisterhand" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "rand 0.10.1", "serde", @@ -6378,7 +6378,7 @@ dependencies = [ [[package]] name = "perry-ui-gtk4" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "base64", "cairo-rs 0.22.0", @@ -6401,7 +6401,7 @@ dependencies = [ [[package]] name = "perry-ui-ios" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "base64", "block2", @@ -6417,7 +6417,7 @@ dependencies = [ [[package]] name = "perry-ui-macos" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "base64", "block2", @@ -6432,7 +6432,7 @@ dependencies = [ [[package]] name = "perry-ui-model" -version = "0.5.1385" +version = "0.5.1386" [[package]] name = "perry-ui-test" @@ -6443,11 +6443,11 @@ dependencies = [ [[package]] name = "perry-ui-testkit" -version = "0.5.1385" +version = "0.5.1386" [[package]] name = "perry-ui-tvos" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "base64", "block2", @@ -6463,7 +6463,7 @@ dependencies = [ [[package]] name = "perry-ui-visionos" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "base64", "block2", @@ -6479,7 +6479,7 @@ dependencies = [ [[package]] name = "perry-ui-watchos" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "block2", "libc", @@ -6492,7 +6492,7 @@ dependencies = [ [[package]] name = "perry-ui-windows" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "base64", "libc", @@ -6509,14 +6509,14 @@ dependencies = [ [[package]] name = "perry-ui-windows-winui" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "perry-ui-windows", ] [[package]] name = "perry-updater" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "anyhow", "base64", @@ -6532,7 +6532,7 @@ dependencies = [ [[package]] name = "perry-wasm-host" -version = "0.5.1385" +version = "0.5.1386" dependencies = [ "wasmi", ] diff --git a/Cargo.toml b/Cargo.toml index a25b6692d0..3513254ea5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -315,7 +315,7 @@ codegen-units = 16 codegen-units = 16 [workspace.package] -version = "0.5.1385" +version = "0.5.1386" edition = "2021" license = "MIT" repository = "https://github.com/PerryTS/perry" diff --git a/changelog.d/7681-alloca-type-prefix-match.md b/changelog.d/7681-alloca-type-prefix-match.md new file mode 100644 index 0000000000..bff9bd2d02 --- /dev/null +++ b/changelog.d/7681-alloca-type-prefix-match.md @@ -0,0 +1,37 @@ +### Fixed + +**The root-slot readers matched an alloca's whole def text, so one `align` +suffix would have made every negative rooting gate vacuous again (#7675 +follow-up).** + +`testing::temp_slots::temp_root_slots` selected slots with +`matches!(defs.get(slot), Some(&"alloca i64") | Some(&"alloca ptr addrspace(1)"))`. +If codegen ever printed `alloca i64, align 8`, that filter would match nothing, +`temp_root_slots` would return an empty vector, and **every +`assert_no_temp_rooting` in the tree would pass for a program that roots** — +which is the exact vacuity #7503 was opened to remove. The positive assertions +would have gone red, so the risk was bounded; the negatives would have lost +their meaning silently. + +All three places that compared whole def text now go through one shared +`root_slots::alloca_type`, which reads the type and ignores anything after it: +`temp_slots::temp_root_slots` (the silent case), `root_slots::classify` (loud — +an unmatched type hits its panic arm — but it would have panicked on a perfectly +valid spelling) and `root_slots::value_slot_barriers`. Two regression tests +(`an_aligned_alloca_is_still_recognised_as_a_temp_slot`, +`an_aligned_alloca_is_still_classified`) fail against the old compare and pass +against the new one; each asserts its own substitution applied, so neither can +pass by testing an unmodified fixture. + +Two smaller repairs from the same review: + +* `temp_root_coverage`'s accumulator write-back clause compared registers + exactly while every neighbouring clause went through `slot_holding`, which + tolerates the one NaN-boxing step a raw allocation result takes before it + reaches a slot. A lowering that boxed the push result would have failed a test + whose contract still held. It now uses `slot_holding` too. +* `zero_seeded_slots` had a load-specific second pass that re-inserted slots the + generic `, ptr %s` scan above it had already caught. Removed, with a comment + saying why there is deliberately no load-specific pass — an extra branch that + reads as coverage it does not add is the same category of problem as the rest + of this area. diff --git a/crates/perry-codegen/src/temp_root_coverage/mod.rs b/crates/perry-codegen/src/temp_root_coverage/mod.rs index cd145a6fbc..f54ee69e44 100644 --- a/crates/perry-codegen/src/temp_root_coverage/mod.rs +++ b/crates/perry-codegen/src/temp_root_coverage/mod.rs @@ -219,10 +219,13 @@ fn console_argument_accumulator_is_rooted_re_read_and_written_back() { let slot = slot_holding(&ir, &alloc).expect("assert_rooted_across just found it"); let push = first_call_result(&ir, "js_array_push_f64") .unwrap_or_else(|| panic!("{lowering}: no push:\n{ir}")); - assert!( - slot_traffic(&ir)[&slot] - .iter() - .any(|e| matches!(e, SlotEvent::Store { value, .. } if *value == push)), + // Through `slot_holding`, not an exact register compare: the store-back + // is allowed the same one boxing step every other clause here tolerates, + // so an exact match would fail on a lowering that boxes the push result + // while the contract still holds (#7675 review). + assert_eq!( + slot_holding(&ir, &push).as_deref(), + Some(slot.as_str()), "{lowering}: the reallocated array {push} must be written BACK into \ {slot}; rooting only the pre-push pointer protects the wrong \ allocation (#6951):\n{ir}" diff --git a/crates/perry-codegen/src/testing/root_slots.rs b/crates/perry-codegen/src/testing/root_slots.rs index 4ccb9df06c..c228640aef 100644 --- a/crates/perry-codegen/src/testing/root_slots.rs +++ b/crates/perry-codegen/src/testing/root_slots.rs @@ -73,12 +73,22 @@ fn bind_slot(line: &str) -> Option<&str> { slot.starts_with('%').then_some(slot) } +/// The TYPE of an `alloca` definition, ignoring anything after it. +/// +/// Matching the whole def text (`Some(&"alloca i64")`) is one `align 8` away +/// from matching nothing — and in `temp_slots::temp_root_slots`' sibling filter +/// that would have emptied the result and turned every negative assertion +/// vacuous again, which is the defect this whole area is being repaired for. +/// Raised by review on #7675. +pub fn alloca_type(def: &str) -> Option<&str> { + def.strip_prefix("alloca ") + .map(|rest| rest.split(',').next().unwrap_or(rest).trim()) +} + fn classify(fn_ir: &str, defs: &BTreeMap<&str, &str>, slot: &str) -> SlotKind { - match defs.get(slot) { - Some(&"alloca double") => SlotKind::Value, - Some(&"alloca i64") if fn_ir.contains(&format!("store i64 0, ptr {slot}\n")) => { - SlotKind::TempRoot - } + match defs.get(slot).copied().and_then(alloca_type) { + Some("double") => SlotKind::Value, + Some("i64") if fn_ir.contains(&format!("store i64 0, ptr {slot}\n")) => SlotKind::TempRoot, other => panic!( "root slot {slot} is bound but its alloca ({other:?}) belongs to no \ known slot family. Adding one is fine — classify it HERE, in \ @@ -166,7 +176,12 @@ pub fn value_slot_barriers(fn_ir: &str) -> usize { let defs = defs(fn_ir); barriers_by_slot(fn_ir) .into_iter() - .filter(|(slot, _)| matches!(defs.get(slot.as_str()), Some(&"alloca double"))) + .filter(|(slot, _)| { + matches!( + defs.get(slot.as_str()).copied().and_then(alloca_type), + Some("double") + ) + }) .map(|(_, count)| count) .sum() } @@ -296,6 +311,20 @@ entry.0: ); } + /// An `align` suffix must not push a known slot into the unclassified + /// panic arm. Sabotage: restore the exact `Some(&"alloca double")` compare + /// and this test panics instead of counting. + #[test] + fn an_aligned_alloca_is_still_classified() { + let aligned = MIXED + .replace("%v = alloca double", "%v = alloca double, align 8") + .replace("%t = alloca i64", "%t = alloca i64, align 8"); + assert_ne!(aligned, MIXED, "the substitution must actually apply"); + assert_eq!(value_slot_binds(&aligned), 1); + assert_eq!(temp_root_slot_binds(&aligned), 2); + assert_eq!(value_slot_barriers(&aligned), 1); + } + /// An `alloca i64` that is NOT null-initialised at entry is not a temp-root /// slot; the frame base-index alloca is one such, and mistaking it for a /// pool slot would let a real regression hide in the temp bucket. diff --git a/crates/perry-codegen/src/testing/temp_slots.rs b/crates/perry-codegen/src/testing/temp_slots.rs index f0c390db54..74c0e9afc5 100644 --- a/crates/perry-codegen/src/testing/temp_slots.rs +++ b/crates/perry-codegen/src/testing/temp_slots.rs @@ -388,6 +388,11 @@ pub fn zero_seeded_slots(fn_ir: &str) -> std::collections::BTreeSet { touched.insert(slot); continue; } + // Every other mention of the slot — a store of a real value, a load out + // of it, an operand — marks it touched. `, ptr %s` covers all of them, + // loads included (`%d = load i64, ptr %s`), so there is deliberately no + // second load-specific pass: an extra branch that re-inserts what this + // one already caught reads as coverage it does not add (#7675 review). for slot in line .split(", ptr ") .skip(1) @@ -397,20 +402,6 @@ pub fn zero_seeded_slots(fn_ir: &str) -> std::collections::BTreeSet { { touched.insert(slot); } - if let Some((_, def)) = line.split_once(" = ") { - for prefix in [ - "load i64, ptr ", - "load double, ptr ", - "load ptr addrspace(1), ptr ", - ] { - if let Some(rest) = def.trim().strip_prefix(prefix) { - let slot = rest.split(',').next().unwrap_or(rest).trim().to_string(); - if slot.starts_with('%') { - touched.insert(slot); - } - } - } - } } seeded } @@ -445,9 +436,16 @@ pub fn temp_root_slots(fn_ir: &str) -> Vec { .into_iter() .filter(|(slot, _)| seeded.contains(slot)) .filter(|(slot, _)| { + // Match the alloca's TYPE, not the whole def text: one `align 8` + // suffix away, an exact comparison matches nothing, this filter + // empties the result and every `assert_no_temp_rooting` in the tree + // goes vacuous — the exact failure #7503 exists to remove. Raised by + // review on #7675. matches!( - defs.get(slot.as_str()), - Some(&"alloca i64") | Some(&"alloca ptr addrspace(1)") + defs.get(slot.as_str()) + .copied() + .and_then(super::root_slots::alloca_type), + Some("i64") | Some("ptr addrspace(1)") ) }) .filter(|(_, events)| events.iter().any(|e| matches!(e, SlotEvent::Store { .. }))) @@ -601,6 +599,28 @@ entry.0: ); } + /// An `align` suffix on the alloca must not empty the slot set. + /// + /// This is the shape the exact `Some(&"alloca i64")` compare could not see, + /// and its failure mode was the silent one: `temp_root_slots` returns + /// nothing, so every `assert_no_temp_rooting` in the tree passes for a + /// program that roots. Sabotage: restore the exact compare and this test + /// fails while none of the positives do. + #[test] + fn an_aligned_alloca_is_still_recognised_as_a_temp_slot() { + let aligned = SHADOW.replace("%s = alloca i64", "%s = alloca i64, align 8"); + assert_ne!(aligned, SHADOW, "the substitution must actually apply"); + assert_eq!( + temp_root_slots(&aligned), + vec!["%s".to_string()], + "an `align` suffix must not hide the slot — an emptied slot set \ + makes every negative gate vacuous (#7675 review)" + ); + assert!( + std::panic::catch_unwind(move || assert_no_temp_rooting(&aligned, "sabotage")).is_err() + ); + } + #[test] fn the_negative_direction_sees_real_rooting() { assert!(