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);
+}