From 9929ed08f1cdba34abee7b2371e7de7f2ef35aad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Mon, 7 Sep 2026 19:46:20 +0200 Subject: [PATCH] fix(cache): key segment-view lowering env Include PERRY_SEGVIEW in the build-cache environment fingerprint so switching the lowering cannot reuse a binary built under the other mode. Record PERRY_SEGVIEW_DIAG as diagnostics-only, and cover the real cache miss with a qualifying Intl.Segmenter compile. Claude-Session: https://claude.ai/code/session_011dhBmdn4vGgNibjo3oZqTo --- changelog.d/9971-segview-build-cache-input.md | 6 +++ .../perry/src/commands/compile/build_cache.rs | 19 +++------ crates/perry/tests/native_link_cache.rs | 41 +++++++++++++++++++ 3 files changed, 53 insertions(+), 13 deletions(-) create mode 100644 changelog.d/9971-segview-build-cache-input.md diff --git a/changelog.d/9971-segview-build-cache-input.md b/changelog.d/9971-segview-build-cache-input.md new file mode 100644 index 0000000000..409d2acf24 --- /dev/null +++ b/changelog.d/9971-segview-build-cache-input.md @@ -0,0 +1,6 @@ +### Fixed + +- Register the segment-view lowering switch as a build-cache input so changing + `PERRY_SEGVIEW` cannot reuse a binary emitted under the opposite setting. + `PERRY_SEGVIEW_DIAG` remains diagnostic-only and is explicitly excluded from + the cache key. diff --git a/crates/perry/src/commands/compile/build_cache.rs b/crates/perry/src/commands/compile/build_cache.rs index 43d9730a4f..8693159d55 100644 --- a/crates/perry/src/commands/compile/build_cache.rs +++ b/crates/perry/src/commands/compile/build_cache.rs @@ -177,6 +177,9 @@ const BUILD_CACHE_ENV_VARS: &[&str] = &[ "PERRY_PTR_NUMARRAY_LOCALS", "PERRY_PTR_SHAPE_LOCALS", "PERRY_PTR_SHAPE_THIS", + // #9893: selects the segment-view lowering, which rewrites qualifying + // for-of loops to call the `js_segments_view_*` runtime entry points. + "PERRY_SEGVIEW", "PERRY_SPECIALIZED_ABI", "PERRY_SPECIALIZED_ABI_MAX", "PERRY_SPEC_PRESERVE_NONE", @@ -232,6 +235,9 @@ const BUILD_CACHE_ENV_EXCLUSIONS: &[&str] = &[ "PERRY_PACKED_LOOP_TRACE", // Entry outlining report output is observational only. "PERRY_OUTLINE_ENTRY_REPORT", + // Segment-view diagnostics only scan the final HIR and print counters; + // their checks inside the rewrite guard `eprintln!` calls only. + "PERRY_SEGVIEW_DIAG", // Only read on an already-fatal dialect-construction failure (a unit that // never parses); it writes a diagnostic IR dump to `/.ll` for // triage and cannot affect the bytes of any build that actually succeeds. @@ -866,19 +872,6 @@ fn eligibility(args: &CompileArgs, project_root: &Path) -> Result<(), String> { if std::env::var("PERRY_SEGVIEW_DIAG").is_ok() { return Err("segview-diag".to_string()); } - // #9843: `PERRY_SEGVIEW` is NOT a diagnostic — it changes the emitted - // code. It is not part of the build-cache fingerprint or any object-cache - // key, so without this a cached build can hand back a binary compiled with - // the OTHER setting: compile a file with the tier on, compile it again - // with the tier off, and the second can be served from the first. The - // A/B rig's whole shape is "one compiler binary, two compiles of one - // source differing only in this variable", which is exactly the collision. - // Excluded rather than keyed because the tier is experimental and default - // OFF; a cache key is the right fix when it ships on, and then a stale - // entry cannot silently become the measurement. - if std::env::var("PERRY_SEGVIEW").is_ok() { - return Err("segview-lowering".to_string()); - } if args.verify_native_regions || args.emit_attest || args.emit_sandbox { return Err("sidecar-or-verify".to_string()); } diff --git a/crates/perry/tests/native_link_cache.rs b/crates/perry/tests/native_link_cache.rs index b4a58cef23..a6afb06064 100644 --- a/crates/perry/tests/native_link_cache.rs +++ b/crates/perry/tests/native_link_cache.rs @@ -168,3 +168,44 @@ fn native_compile_skips_link_on_identical_second_build() { assert_codegen_cache(&missing_output, 2, 0, 2, 0, 0); assert!(output.exists()); } + +#[test] +fn segment_view_switch_misses_build_and_object_caches() { + let dir = tempfile::tempdir().expect("tempdir"); + let project = dir.path(); + let output = project.join("app"); + let entry = project.join("main.ts"); + fs::write( + project.join("package.json"), + "{\"name\":\"segview-cache-test\"}\n", + ) + .unwrap(); + fs::write( + &entry, + r#"const segmenter = new Intl.Segmenter("en"); +for (const { segment } of segmenter.segment("ab")) { + console.log(segment); +} +"#, + ) + .unwrap(); + + let disabled = compile_json_with_env(project, &entry, &output, &[("PERRY_SEGVIEW", "0")]); + assert_linked(&disabled); + assert_build_cache_miss(&disabled, "manifest-missing"); + assert_codegen_cache(&disabled, 0, 1, 0, 1, 0); + assert_eq!(run_binary(&output), "a\nb\n"); + + // This must reach codegen and produce a distinct object. Sabotage: + // removing PERRY_SEGVIEW from BUILD_CACHE_ENV_VARS makes it an erroneous + // whole-build cache hit, so the assertions below fail before codegen. + let enabled = compile_json_with_env(project, &entry, &output, &[("PERRY_SEGVIEW", "1")]); + assert_linked(&enabled); + assert_build_cache_miss(&enabled, "env"); + assert_codegen_cache(&enabled, 0, 1, 0, 1, 0); + assert_eq!(run_binary(&output), "a\nb\n"); + + let enabled_warm = compile_json_with_env(project, &entry, &output, &[("PERRY_SEGVIEW", "1")]); + assert_skipped(&enabled_warm); + assert_build_cache_hit(&enabled_warm); +}