Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions changelog.d/7039-codegen-determinism.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Fixed non-deterministic LLVM IR: `emit_artifacts` iterated `hir.closure_source_text`
(a `HashMap`) when emitting retained closure-source constants, so the `@.str.N`
numbering of those constants was a per-process permutation — the same input
compiled to different IR on every run.
Comment on lines +1 to +4

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use probabilistic wording for the nondeterminism claim.

HashMap iteration can vary across processes, but independent runs may still produce the same order. Replace “different IR on every run” with “can produce different IR across runs.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@changelog.d/7039-codegen-determinism.md` around lines 1 - 4, Update the
changelog entry’s nondeterminism statement to use probabilistic wording: replace
the claim that the same input produces different IR on every run with wording
that it can produce different IR across runs. Preserve the explanation about
HashMap iteration and @.str.N numbering.


This was not cosmetic. It silently invalidates any A/B comparison of raw LLVM IR,
a technique several representation-selection and GC investigations relied on for
evidence. PR #7037 hit it directly: its first proof that `--opt-report` was
observational passed on nondeterministic output and had to be redone with four
runs per arm and a normalizer.

Measured over 6 compiles of one file (md5 of the concatenated per-module `.ll`):
5 distinct hashes before, 1 after. Emission order is the only thing that changes.
21 changes: 17 additions & 4 deletions crates/perry-codegen/src/codegen/artifacts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1858,10 +1858,23 @@ pub(super) fn emit_module_artifacts(c: ModuleArtifactsCtx<'_>) -> Result<()> {
}
}
}
for (func_id, src) in &hir.closure_source_text {
if registered_fn_ids.contains(func_id) || !materialized_closure_ids.contains(func_id) {
continue;
}
// Sorted, NOT raw `HashMap` iteration (#7038). The loop above walks
// `hir.functions` (a `Vec`) and is already deterministic; this one keyed off
// the map's iteration order, so the `@.str.N` numbering of the emitted
// string constants was a per-process permutation. Same input, different
// `.ll` on every run — which silently invalidates any A/B that compares raw
// IR, a technique several representation and GC investigations relied on.
// Emission order is the only thing that changes; sorting by `FuncId` makes
// it stable without altering what is emitted.
let mut materialized_closure_sources: Vec<(&perry_hir::types::FuncId, &String)> = hir
.closure_source_text
.iter()
.filter(|(func_id, _)| {
!registered_fn_ids.contains(*func_id) && materialized_closure_ids.contains(*func_id)
})
.collect();
materialized_closure_sources.sort_by_key(|(func_id, _)| **func_id);
for (func_id, src) in materialized_closure_sources {
let sym = format!("perry_closure_{}__{}", module_prefix, func_id);
user_fn_source.push((sym, src.clone()));
}
Expand Down
Loading