Skip to content

Commit 0be0f46

Browse files
darmieclaude
andcommitted
fix: Replace HashMap with IndexMap to ensure deterministic compilation
HashMap's random iteration order caused non-deterministic LLVM IR generation, leading to phi nodes receiving incorrect values. While loops would sometimes enter infinite loops because the phi node received a constant instead of the computed value. Changes: - Convert all HashMap to IndexMap in HIR data structures (hir.rs) - Update SSA builder to use IndexMap for deterministic phi node construction - Fix LLVM backend to use IndexMap's insertion order instead of UUID-based sorting - Add serde feature to indexmap dependency for serialization support - Update all related files: hir_builder, optimization, async_support, lowering The while loop test now consistently returns 10 across all runs. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 87a8976 commit 0be0f46

9 files changed

Lines changed: 688 additions & 235 deletions

File tree

crates/compiler/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ crossbeam = { workspace = true }
1717
# Analysis and optimization
1818
petgraph = "0.6.4" # Graph algorithms
1919
smallvec = "1.13.2" # Optimized small vectors
20-
indexmap = "2.2.6" # Ordered maps
20+
indexmap = { version = "2.2.6", features = ["serde"] } # Ordered maps with serde support
2121
bit-set = "0.5.3" # Bit sets for liveness analysis
2222

2323
# Serialization for caching
@@ -47,7 +47,7 @@ cranelift-frontend = { version = "0.106", optional = true }
4747
cranelift-jit = { version = "0.106", optional = true }
4848
cranelift-module = { version = "0.106", optional = true }
4949
cranelift-native = { version = "0.106", optional = true }
50-
inkwell = { version = "0.4", features = ["llvm17-0"], optional = true } # LLVM bindings
50+
inkwell = { version = "0.7.1", features = ["llvm21-1"], optional = true } # LLVM bindings
5151

5252
[dev-dependencies]
5353
env_logger = "0.11"

crates/compiler/src/async_support.rs

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
//! async runtime integration for the HIR. This module provides the foundation
55
//! for async/await syntax and coroutine-based programming models.
66
7-
use std::collections::{HashMap, HashSet, VecDeque};
7+
use std::collections::{HashSet, VecDeque};
8+
use indexmap::IndexMap;
89
use crate::hir::*;
910
use crate::{CompilerResult, CompilerError};
1011
use zyntax_typed_ast::InternedString;
@@ -17,7 +18,7 @@ pub struct AsyncStateMachine {
1718
/// Original async function
1819
pub original_function: HirId,
1920
/// States in the state machine
20-
pub states: HashMap<AsyncStateId, AsyncState>,
21+
pub states: IndexMap<AsyncStateId, AsyncState>,
2122
/// Initial state
2223
pub initial_state: AsyncStateId,
2324
/// Final/completion state
@@ -138,7 +139,7 @@ pub struct AsyncCompiler {
138139
/// State machine counter
139140
next_state_id: u32,
140141
/// Generated state machines
141-
state_machines: HashMap<HirId, AsyncStateMachine>,
142+
state_machines: IndexMap<HirId, AsyncStateMachine>,
142143
/// Async runtime configuration
143144
runtime: Option<AsyncRuntime>,
144145
}
@@ -147,7 +148,7 @@ impl AsyncCompiler {
147148
pub fn new() -> Self {
148149
Self {
149150
next_state_id: 0,
150-
state_machines: HashMap::new(),
151+
state_machines: IndexMap::new(),
151152
runtime: None,
152153
}
153154
}
@@ -170,7 +171,7 @@ impl AsyncCompiler {
170171
let mut state_machine = AsyncStateMachine {
171172
id: machine_id,
172173
original_function: func.id,
173-
states: HashMap::new(),
174+
states: IndexMap::new(),
174175
initial_state,
175176
final_state,
176177
captures: self.analyze_captures(func)?,
@@ -461,8 +462,8 @@ impl AsyncCompiler {
461462
arena: &mut zyntax_typed_ast::arena::AstArena,
462463
) -> HirFunction {
463464
let entry_block_id = HirId::new();
464-
let mut blocks = HashMap::new();
465-
let mut values = HashMap::new();
465+
let mut blocks = IndexMap::new();
466+
let mut values = IndexMap::new();
466467
let mut instructions = Vec::new();
467468

468469
// Create constant for state = 0
@@ -577,7 +578,7 @@ impl AsyncCompiler {
577578
},
578579
entry_block: entry_block_id,
579580
blocks,
580-
locals: HashMap::new(),
581+
locals: IndexMap::new(),
581582
values,
582583
previous_version: None,
583584
is_external: false,
@@ -750,7 +751,7 @@ mod tests {
750751
let state_machine = AsyncStateMachine {
751752
id: HirId::new(),
752753
original_function: HirId::new(),
753-
states: HashMap::new(),
754+
states: IndexMap::new(),
754755
initial_state: AsyncStateId(0),
755756
final_state: AsyncStateId(1),
756757
captures: vec![capture],
@@ -801,9 +802,9 @@ mod tests {
801802
name: intern_str(&mut arena, "test_async"),
802803
signature: sig,
803804
entry_block: HirId::new(),
804-
blocks: HashMap::new(),
805-
locals: HashMap::new(),
806-
values: HashMap::new(),
805+
blocks: IndexMap::new(),
806+
locals: IndexMap::new(),
807+
values: IndexMap::new(),
807808
previous_version: None,
808809
is_external: false,
809810
calling_convention: CallingConvention::C,
@@ -821,7 +822,7 @@ mod tests {
821822
let state_machine = AsyncStateMachine {
822823
id: HirId::new(),
823824
original_function: func_id,
824-
states: HashMap::new(),
825+
states: IndexMap::new(),
825826
initial_state: AsyncStateId(0),
826827
final_state: AsyncStateId(1),
827828
captures: vec![capture],
@@ -867,7 +868,7 @@ mod tests {
867868
let state_machine = AsyncStateMachine {
868869
id: HirId::new(),
869870
original_function: HirId::new(),
870-
states: HashMap::new(),
871+
states: IndexMap::new(),
871872
initial_state: AsyncStateId(0),
872873
final_state: AsyncStateId(1),
873874
captures: vec![], // No captures
@@ -929,9 +930,9 @@ mod tests {
929930
name: intern_str(&mut arena, "multi_param_async"),
930931
signature: sig,
931932
entry_block: HirId::new(),
932-
blocks: HashMap::new(),
933-
locals: HashMap::new(),
934-
values: HashMap::new(),
933+
blocks: IndexMap::new(),
934+
locals: IndexMap::new(),
935+
values: IndexMap::new(),
935936
previous_version: None,
936937
is_external: false,
937938
calling_convention: CallingConvention::C,
@@ -963,7 +964,7 @@ mod tests {
963964
let state_machine = AsyncStateMachine {
964965
id: HirId::new(),
965966
original_function: func_id,
966-
states: HashMap::new(),
967+
states: IndexMap::new(),
967968
initial_state: AsyncStateId(0),
968969
final_state: AsyncStateId(1),
969970
captures,

crates/compiler/src/hir.rs

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
//! - Enables hot-reloading via function versioning
1313
//! - Memory safe with explicit lifetime tracking
1414
15-
use std::collections::{HashMap, HashSet};
15+
use std::collections::HashSet;
16+
use indexmap::IndexMap;
1617
use std::sync::Arc;
1718
use zyntax_typed_ast::{Type, TypeId, InternedString, Span};
1819
use uuid::Uuid;
@@ -98,9 +99,9 @@ impl HirId {
9899
pub struct HirModule {
99100
pub id: HirId,
100101
pub name: InternedString,
101-
pub functions: HashMap<HirId, HirFunction>,
102-
pub globals: HashMap<HirId, HirGlobal>,
103-
pub types: HashMap<TypeId, HirType>,
102+
pub functions: IndexMap<HirId, HirFunction>,
103+
pub globals: IndexMap<HirId, HirGlobal>,
104+
pub types: IndexMap<TypeId, HirType>,
104105
pub imports: Vec<HirImport>,
105106
pub exports: Vec<HirExport>,
106107
/// Metadata for hot-reloading support
@@ -115,10 +116,10 @@ pub struct HirFunction {
115116
pub name: InternedString,
116117
pub signature: HirFunctionSignature,
117118
pub entry_block: HirId,
118-
pub blocks: HashMap<HirId, HirBlock>,
119-
pub locals: HashMap<HirId, HirLocal>,
119+
pub blocks: IndexMap<HirId, HirBlock>,
120+
pub locals: IndexMap<HirId, HirLocal>,
120121
/// SSA values defined in this function
121-
pub values: HashMap<HirId, HirValue>,
122+
pub values: IndexMap<HirId, HirValue>,
122123
/// For hot-reloading: previous version of this function
123124
pub previous_version: Option<HirId>,
124125
pub is_external: bool,
@@ -494,8 +495,8 @@ pub enum HirTerminator {
494495

495496
impl HirInstruction {
496497
/// Replace uses of old values with new values according to the replacement map
497-
pub fn replace_uses(&mut self, replacements: &std::collections::HashMap<HirId, HirId>) {
498-
fn replace(id: &mut HirId, map: &std::collections::HashMap<HirId, HirId>) {
498+
pub fn replace_uses(&mut self, replacements: &IndexMap<HirId, HirId>) {
499+
fn replace(id: &mut HirId, map: &IndexMap<HirId, HirId>) {
499500
if let Some(&new_id) = map.get(id) {
500501
*id = new_id;
501502
}
@@ -616,8 +617,8 @@ impl HirInstruction {
616617

617618
impl HirTerminator {
618619
/// Replace uses of old values with new values according to the replacement map
619-
pub fn replace_uses(&mut self, replacements: &std::collections::HashMap<HirId, HirId>) {
620-
fn replace(id: &mut HirId, map: &std::collections::HashMap<HirId, HirId>) {
620+
pub fn replace_uses(&mut self, replacements: &IndexMap<HirId, HirId>) {
621+
fn replace(id: &mut HirId, map: &IndexMap<HirId, HirId>) {
621622
if let Some(&new_id) = map.get(id) {
622623
*id = new_id;
623624
}
@@ -1187,13 +1188,13 @@ pub enum TypeConstraint {
11871188
#[derive(Debug, Clone, Serialize, Deserialize)]
11881189
pub struct BorrowCheckContext {
11891190
/// Active borrows at each program point
1190-
pub active_borrows: HashMap<HirId, Vec<BorrowInfo>>,
1191+
pub active_borrows: IndexMap<HirId, Vec<BorrowInfo>>,
11911192
/// Move information
1192-
pub moves: HashMap<HirId, MoveInfo>,
1193+
pub moves: IndexMap<HirId, MoveInfo>,
11931194
/// Lifetime constraints
11941195
pub lifetime_constraints: Vec<LifetimeConstraint>,
11951196
/// Local variable lifetimes
1196-
pub local_lifetimes: HashMap<HirId, HirLifetime>,
1197+
pub local_lifetimes: IndexMap<HirId, HirLifetime>,
11971198
}
11981199

11991200
/// Information about an active borrow
@@ -1238,10 +1239,10 @@ pub enum OwnershipMode {
12381239
impl BorrowCheckContext {
12391240
pub fn new() -> Self {
12401241
Self {
1241-
active_borrows: HashMap::new(),
1242-
moves: HashMap::new(),
1242+
active_borrows: IndexMap::new(),
1243+
moves: IndexMap::new(),
12431244
lifetime_constraints: Vec::new(),
1244-
local_lifetimes: HashMap::new(),
1245+
local_lifetimes: IndexMap::new(),
12451246
}
12461247
}
12471248

@@ -1276,9 +1277,9 @@ impl HirModule {
12761277
Self {
12771278
id: HirId::new(),
12781279
name,
1279-
functions: HashMap::new(),
1280-
globals: HashMap::new(),
1281-
types: HashMap::new(),
1280+
functions: IndexMap::new(),
1281+
globals: IndexMap::new(),
1282+
types: IndexMap::new(),
12821283
imports: Vec::new(),
12831284
exports: Vec::new(),
12841285
version: 0,
@@ -1305,17 +1306,17 @@ impl HirModule {
13051306
impl HirFunction {
13061307
pub fn new(name: InternedString, signature: HirFunctionSignature) -> Self {
13071308
let entry_block_id = HirId::new();
1308-
let mut blocks = HashMap::new();
1309+
let mut blocks = IndexMap::new();
13091310
blocks.insert(entry_block_id, HirBlock::new(entry_block_id));
1310-
1311+
13111312
Self {
13121313
id: HirId::new(),
13131314
name,
13141315
signature,
13151316
entry_block: entry_block_id,
13161317
blocks,
1317-
locals: HashMap::new(),
1318-
values: HashMap::new(),
1318+
locals: IndexMap::new(),
1319+
values: IndexMap::new(),
13191320
previous_version: None,
13201321
is_external: false,
13211322
calling_convention: CallingConvention::Fast,

crates/compiler/src/hir_builder.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
//! ```
4848
4949
use crate::hir::*;
50-
use std::collections::HashMap;
50+
use indexmap::IndexMap;
5151
use zyntax_typed_ast::{AstArena, InternedString, TypeId, Span};
5252

5353
/// Main builder for constructing HIR modules
@@ -115,9 +115,9 @@ impl<'arena> HirBuilder<'arena> {
115115
module: HirModule {
116116
id: HirId::new(),
117117
name,
118-
functions: HashMap::new(),
119-
globals: HashMap::new(),
120-
types: HashMap::new(),
118+
functions: IndexMap::new(),
119+
globals: IndexMap::new(),
120+
types: IndexMap::new(),
121121
imports: Vec::new(),
122122
exports: Vec::new(),
123123
version: 0,

0 commit comments

Comments
 (0)