diff --git a/libs/@local/hashql/mir/src/pass/analysis/dataflow/framework.rs b/libs/@local/hashql/mir/src/pass/analysis/dataflow/framework.rs index 481ee5bd027..69440bcdc84 100644 --- a/libs/@local/hashql/mir/src/pass/analysis/dataflow/framework.rs +++ b/libs/@local/hashql/mir/src/pass/analysis/dataflow/framework.rs @@ -63,6 +63,16 @@ pub enum Direction { Backward, } +impl Direction { + #[must_use] + pub const fn reverse(self) -> Self { + match self { + Self::Forward => Self::Backward, + Self::Backward => Self::Forward, + } + } +} + /// The results of a dataflow analysis after reaching a fixed point. /// /// Contains the computed abstract state at both entry and exit of each basic block. diff --git a/libs/@local/hashql/mir/src/pass/execution/placement/solve/csp/mod.rs b/libs/@local/hashql/mir/src/pass/execution/placement/solve/csp/mod.rs index 2480b0c4aec..59fd581633b 100644 --- a/libs/@local/hashql/mir/src/pass/execution/placement/solve/csp/mod.rs +++ b/libs/@local/hashql/mir/src/pass/execution/placement/solve/csp/mod.rs @@ -114,6 +114,21 @@ impl CyclicPlacementRegion<'_> { } } +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub(crate) enum ConstraintSatisfactionMode { + Initial, + Adjustment, +} + +impl ConstraintSatisfactionMode { + const fn config(self) -> CostEstimationConfig { + match self { + Self::Initial => CostEstimationConfig::LOOP, + Self::Adjustment => CostEstimationConfig::TRIVIAL, + } + } +} + /// CSP solver for assigning targets within a cyclic placement region. /// /// Borrows the parent [`PlacementSolver`] for cost estimation and target resolution. @@ -124,6 +139,7 @@ pub(crate) struct ConstraintSatisfaction<'ctx, 'parent, 'alloc, A: Allocator, S: pub region: CyclicPlacementRegion<'alloc>, pub depth: usize, + pub mode: ConstraintSatisfactionMode, // Branch-and-bound state (only used when members.len() <= BNB_CUTOFF) cost_deltas: [ApproxCost; BNB_CUTOFF], @@ -136,6 +152,7 @@ impl<'ctx, 'parent, 'alloc, A: Allocator, S: BumpAllocator> /// Creates a new CSP solver for the given cyclic `region`. pub(crate) const fn new( solver: &'ctx mut PlacementSolver<'parent, 'alloc, A, S>, + mode: ConstraintSatisfactionMode, id: PlacementRegionId, region: CyclicPlacementRegion<'alloc>, ) -> Self { @@ -144,6 +161,7 @@ impl<'ctx, 'parent, 'alloc, A: Allocator, S: BumpAllocator> id, region, depth: 0, + mode, cost_deltas: [ApproxCost::ZERO; BNB_CUTOFF], cost_so_far: ApproxCost::ZERO, } @@ -332,7 +350,7 @@ impl<'ctx, 'parent, 'alloc, A: Allocator, S: BumpAllocator> self.region.blocks.swap(self.depth, self.depth + offset); let mut heap = CostEstimation { - config: CostEstimationConfig::LOOP, + config: self.mode.config(), solver: self.solver, determine_target: |block| { if let Some(member) = self.region.find_block(block) { @@ -373,7 +391,7 @@ impl<'ctx, 'parent, 'alloc, A: Allocator, S: BumpAllocator> target: TargetId, ) -> ApproxCost { let estimator = CostEstimation { - config: CostEstimationConfig::LOOP, + config: self.mode.config(), solver: self.solver, determine_target: |block| { self.region.find_block(block).map_or_else( @@ -520,7 +538,7 @@ impl<'ctx, 'parent, 'alloc, A: Allocator, S: BumpAllocator> self.region.blocks.swap(self.depth, self.depth + offset); let heap = CostEstimation { - config: CostEstimationConfig::LOOP, + config: self.mode.config(), solver: self.solver, determine_target: |block| { if let Some(member) = self.region.find_block(block) { diff --git a/libs/@local/hashql/mir/src/pass/execution/placement/solve/csp/tests.rs b/libs/@local/hashql/mir/src/pass/execution/placement/solve/csp/tests.rs index e993a2a9474..bb7ebd4ec23 100644 --- a/libs/@local/hashql/mir/src/pass/execution/placement/solve/csp/tests.rs +++ b/libs/@local/hashql/mir/src/pass/execution/placement/solve/csp/tests.rs @@ -15,7 +15,7 @@ use crate::{ placement::solve::{ PlacementRegionId, PlacementSolverContext, condensation::PlacementRegionKind, - csp::ConstraintSatisfaction, + csp::{self, ConstraintSatisfaction}, tests::{ all_targets, bb, fix_block, make_block_costs, stmt_costs, target_set, terminators, }, @@ -87,7 +87,12 @@ fn narrow_restricts_successor_domain() { }; let mut solver = data.build_in(&body, &heap); let (region_id, region) = take_cyclic(&mut solver); - let mut csp = ConstraintSatisfaction::new(&mut solver, region_id, region); + let mut csp = ConstraintSatisfaction::new( + &mut solver, + csp::ConstraintSatisfactionMode::Initial, + region_id, + region, + ); csp.seed(); fix_block(&mut csp, bb(0), I); @@ -137,7 +142,12 @@ fn narrow_restricts_predecessor_domain() { }; let mut solver = data.build_in(&body, &heap); let (region_id, region) = take_cyclic(&mut solver); - let mut csp = ConstraintSatisfaction::new(&mut solver, region_id, region); + let mut csp = ConstraintSatisfaction::new( + &mut solver, + csp::ConstraintSatisfactionMode::Initial, + region_id, + region, + ); csp.seed(); fix_block(&mut csp, bb(0), I); @@ -187,7 +197,12 @@ fn narrow_to_empty_domain() { }; let mut solver = data.build_in(&body, &heap); let (region_id, region) = take_cyclic(&mut solver); - let mut csp = ConstraintSatisfaction::new(&mut solver, region_id, region); + let mut csp = ConstraintSatisfaction::new( + &mut solver, + csp::ConstraintSatisfactionMode::Initial, + region_id, + region, + ); csp.seed(); fix_block(&mut csp, bb(0), I); @@ -240,7 +255,12 @@ fn narrow_multiple_edges_intersect() { }; let mut solver = data.build_in(&body, &heap); let (region_id, region) = take_cyclic(&mut solver); - let mut csp = ConstraintSatisfaction::new(&mut solver, region_id, region); + let mut csp = ConstraintSatisfaction::new( + &mut solver, + csp::ConstraintSatisfactionMode::Initial, + region_id, + region, + ); csp.seed(); // Assign bb0 = I and narrow @@ -299,7 +319,12 @@ fn replay_narrowing_resets_then_repropagates() { }; let mut solver = data.build_in(&body, &heap); let (region_id, region) = take_cyclic(&mut solver); - let mut csp = ConstraintSatisfaction::new(&mut solver, region_id, region); + let mut csp = ConstraintSatisfaction::new( + &mut solver, + csp::ConstraintSatisfactionMode::Initial, + region_id, + region, + ); csp.seed(); // Step 1: assign bb0 = I, narrow @@ -377,7 +402,12 @@ fn lower_bound_min_block_cost_per_block() { }; let mut solver = data.build_in(&body, &heap); let (region_id, region) = take_cyclic(&mut solver); - let mut csp = ConstraintSatisfaction::new(&mut solver, region_id, region); + let mut csp = ConstraintSatisfaction::new( + &mut solver, + csp::ConstraintSatisfactionMode::Initial, + region_id, + region, + ); csp.seed(); // Fix bb0 at depth 0 @@ -432,7 +462,12 @@ fn lower_bound_min_transition_cost_per_edge() { }; let mut solver = data.build_in(&body, &heap); let (region_id, region) = take_cyclic(&mut solver); - let mut csp = ConstraintSatisfaction::new(&mut solver, region_id, region); + let mut csp = ConstraintSatisfaction::new( + &mut solver, + csp::ConstraintSatisfactionMode::Initial, + region_id, + region, + ); csp.seed(); fix_block(&mut csp, bb(0), I); @@ -486,7 +521,12 @@ fn lower_bound_skips_self_loop_edges() { }; let mut solver = data.build_in(&body, &heap); let (region_id, region) = take_cyclic(&mut solver); - let mut csp = ConstraintSatisfaction::new(&mut solver, region_id, region); + let mut csp = ConstraintSatisfaction::new( + &mut solver, + csp::ConstraintSatisfactionMode::Initial, + region_id, + region, + ); csp.seed(); csp.depth = 0; @@ -539,7 +579,12 @@ fn lower_bound_fixed_successor_uses_concrete_target() { }; let mut solver = data.build_in(&body, &heap); let (region_id, region) = take_cyclic(&mut solver); - let mut csp = ConstraintSatisfaction::new(&mut solver, region_id, region); + let mut csp = ConstraintSatisfaction::new( + &mut solver, + csp::ConstraintSatisfactionMode::Initial, + region_id, + region, + ); csp.seed(); // Fix bb0 and bb2 (target=P), leaving bb1 unfixed @@ -595,7 +640,12 @@ fn lower_bound_all_fixed_returns_zero() { }; let mut solver = data.build_in(&body, &heap); let (region_id, region) = take_cyclic(&mut solver); - let mut csp = ConstraintSatisfaction::new(&mut solver, region_id, region); + let mut csp = ConstraintSatisfaction::new( + &mut solver, + csp::ConstraintSatisfactionMode::Initial, + region_id, + region, + ); csp.seed(); // Fix both blocks @@ -650,7 +700,12 @@ fn mrv_selects_smallest_domain() { }; let mut solver = data.build_in(&body, &heap); let (region_id, region) = take_cyclic(&mut solver); - let mut csp = ConstraintSatisfaction::new(&mut solver, region_id, region); + let mut csp = ConstraintSatisfaction::new( + &mut solver, + csp::ConstraintSatisfactionMode::Initial, + region_id, + region, + ); csp.seed(); csp.depth = 0; @@ -700,7 +755,12 @@ fn mrv_tiebreak_by_constraint_degree() { }; let mut solver = data.build_in(&body, &heap); let (region_id, region) = take_cyclic(&mut solver); - let mut csp = ConstraintSatisfaction::new(&mut solver, region_id, region); + let mut csp = ConstraintSatisfaction::new( + &mut solver, + csp::ConstraintSatisfactionMode::Initial, + region_id, + region, + ); csp.seed(); csp.depth = 0; @@ -753,7 +813,12 @@ fn mrv_skips_fixed_blocks() { }; let mut solver = data.build_in(&body, &heap); let (region_id, region) = take_cyclic(&mut solver); - let mut csp = ConstraintSatisfaction::new(&mut solver, region_id, region); + let mut csp = ConstraintSatisfaction::new( + &mut solver, + csp::ConstraintSatisfactionMode::Initial, + region_id, + region, + ); csp.seed(); // Fix bb0 at position 0 @@ -810,7 +875,12 @@ fn greedy_solves_two_block_loop() { }; let mut solver = data.build_in(&body, &heap); let (region_id, region) = take_cyclic(&mut solver); - let mut csp = ConstraintSatisfaction::new(&mut solver, region_id, region); + let mut csp = ConstraintSatisfaction::new( + &mut solver, + csp::ConstraintSatisfactionMode::Initial, + region_id, + region, + ); csp.seed(); assert!(csp.run_greedy(&body)); @@ -864,7 +934,12 @@ fn greedy_rollback_finds_alternative() { }; let mut solver = data.build_in(&body, &heap); let (region_id, region) = take_cyclic(&mut solver); - let mut csp = ConstraintSatisfaction::new(&mut solver, region_id, region); + let mut csp = ConstraintSatisfaction::new( + &mut solver, + csp::ConstraintSatisfactionMode::Initial, + region_id, + region, + ); csp.seed(); assert!(csp.run_greedy(&body)); @@ -921,7 +996,12 @@ fn greedy_fails_when_infeasible() { }; let mut solver = data.build_in(&body, &heap); let (region_id, region) = take_cyclic(&mut solver); - let mut csp = ConstraintSatisfaction::new(&mut solver, region_id, region); + let mut csp = ConstraintSatisfaction::new( + &mut solver, + csp::ConstraintSatisfactionMode::Initial, + region_id, + region, + ); csp.seed(); assert!(!csp.run_greedy(&body)); @@ -978,7 +1058,12 @@ fn bnb_finds_optimal() { }; let mut solver = data.build_in(&body, &heap); let (region_id, region) = take_cyclic(&mut solver); - let mut csp = ConstraintSatisfaction::new(&mut solver, region_id, region); + let mut csp = ConstraintSatisfaction::new( + &mut solver, + csp::ConstraintSatisfactionMode::Initial, + region_id, + region, + ); assert!(csp.solve(&body)); // all-I = stmt(10+1+1) + trans(0) = 12 @@ -1033,7 +1118,12 @@ fn bnb_retains_ranked_solutions() { }; let mut solver = data.build_in(&body, &heap); let (region_id, region) = take_cyclic(&mut solver); - let mut csp = ConstraintSatisfaction::new(&mut solver, region_id, region); + let mut csp = ConstraintSatisfaction::new( + &mut solver, + csp::ConstraintSatisfactionMode::Initial, + region_id, + region, + ); assert!(csp.solve(&body)); @@ -1109,7 +1199,12 @@ fn bnb_pruning_preserves_optimal() { }; let mut solver = data.build_in(&body, &heap); let (region_id, region) = take_cyclic(&mut solver); - let mut csp = ConstraintSatisfaction::new(&mut solver, region_id, region); + let mut csp = ConstraintSatisfaction::new( + &mut solver, + csp::ConstraintSatisfactionMode::Initial, + region_id, + region, + ); assert!(csp.solve(&body)); // All blocks should get the same target (cost = 4) @@ -1165,7 +1260,12 @@ fn retry_returns_ranked_solutions_in_order() { }; let mut solver = data.build_in(&body, &heap); let (region_id, region) = take_cyclic(&mut solver); - let mut csp = ConstraintSatisfaction::new(&mut solver, region_id, region); + let mut csp = ConstraintSatisfaction::new( + &mut solver, + csp::ConstraintSatisfactionMode::Initial, + region_id, + region, + ); assert!(csp.solve(&body)); @@ -1235,7 +1335,12 @@ fn retry_exhausts_then_perturbs() { }; let mut solver = data.build_in(&body, &heap); let (region_id, region) = take_cyclic(&mut solver); - let mut csp = ConstraintSatisfaction::new(&mut solver, region_id, region); + let mut csp = ConstraintSatisfaction::new( + &mut solver, + csp::ConstraintSatisfactionMode::Initial, + region_id, + region, + ); assert!(csp.solve(&body)); // Only same-target transitions allowed, so valid assignments are (I,I) and (P,P). @@ -1302,7 +1407,12 @@ fn greedy_rollback_on_empty_heap() { }; let mut solver = data.build_in(&body, &heap); let (region_id, region) = take_cyclic(&mut solver); - let mut csp = ConstraintSatisfaction::new(&mut solver, region_id, region); + let mut csp = ConstraintSatisfaction::new( + &mut solver, + csp::ConstraintSatisfactionMode::Initial, + region_id, + region, + ); csp.seed(); assert!(csp.run_greedy(&body)); @@ -1372,7 +1482,12 @@ fn retry_perturbation_after_ranked_exhaustion() { }; let mut solver = data.build_in(&body, &heap); let (region_id, region) = take_cyclic(&mut solver); - let mut csp = ConstraintSatisfaction::new(&mut solver, region_id, region); + let mut csp = ConstraintSatisfaction::new( + &mut solver, + csp::ConstraintSatisfactionMode::Initial, + region_id, + region, + ); // solve() uses BnB (2 blocks ≤ BNB_CUTOFF=12), applies best solution assert!(csp.solve(&body)); diff --git a/libs/@local/hashql/mir/src/pass/execution/placement/solve/mod.rs b/libs/@local/hashql/mir/src/pass/execution/placement/solve/mod.rs index 690b2033352..502ba34ac7e 100644 --- a/libs/@local/hashql/mir/src/pass/execution/placement/solve/mod.rs +++ b/libs/@local/hashql/mir/src/pass/execution/placement/solve/mod.rs @@ -4,12 +4,14 @@ //! single-block regions and cyclic multi-block SCCs. [`CostEstimation`] ranks candidate targets //! for trivial regions, while [`ConstraintSatisfaction`] handles cyclic ones. //! -//! The forward pass processes regions in topological order, the backward pass in reverse for -//! refinement. When assignment fails, [`PlacementSolver::rewind`] walks backward to find a region -//! that can change its assignment. +//! The forward pass processes regions in topological order, assigning targets greedily. +//! Adjustment passes then alternate direction (backward, forward, backward, ...) until no +//! assignment changes, converging to a local minimum in which no single-region change reduces +//! cost. When assignment fails during the forward pass, [`PlacementSolver::rewind`] walks +//! backward to find a region that can change its assignment. //! //! Entry point: [`PlacementSolverContext::build_in`] constructs a [`PlacementSolver`], then -//! [`PlacementSolver::run_in`] executes both passes. +//! [`PlacementSolver::run_in`] executes the forward pass and iterates adjustment passes. use core::{alloc::Allocator, mem}; @@ -27,9 +29,12 @@ use crate::{ basic_block::{BasicBlockId, BasicBlockSlice, BasicBlockVec}, }, context::MirContext, - pass::execution::{ - ApproxCost, cost::BasicBlockCostVec, target::TargetId, - terminator_placement::TerminatorTransitionCostVec, + pass::{ + analysis::dataflow::framework::Direction, + execution::{ + ApproxCost, cost::BasicBlockCostVec, target::TargetId, + terminator_placement::TerminatorTransitionCostVec, + }, }, }; @@ -125,9 +130,9 @@ impl<'ctx, A: Allocator> PlacementSolverContext<'ctx, A> { /// Assigns execution targets to basic blocks by solving over the condensation graph. /// -/// Uses a two-pass approach: the forward pass assigns targets in topological order, the backward -/// pass refines them with full boundary context. Rewind-based backtracking recovers from -/// assignment failures in the forward pass. +/// The forward pass assigns targets in topological order. Adjustment passes then alternate +/// direction until convergence, refining assignments with progressively fuller boundary context. +/// Rewind-based backtracking recovers from assignment failures in the forward pass. // We need two allocators here, because the `BumpAllocator` trait does not carry a lifetime, but we // move `Copy` data into the bump allocator. pub(crate) struct PlacementSolver<'ctx, 'alloc, S1: Allocator, S2: BumpAllocator> { @@ -142,8 +147,8 @@ pub(crate) struct PlacementSolver<'ctx, 'alloc, S1: Allocator, S2: BumpAllocator } impl<'alloc, S1: Allocator, S: BumpAllocator> PlacementSolver<'_, 'alloc, S1, S> { - /// Runs the forward and backward passes, returning the chosen [`TargetId`] for each basic - /// block. + /// Runs the forward pass and iterates adjustment passes until convergence, returning the + /// chosen [`TargetId`] for each basic block. pub(crate) fn run_in<'heap, A: Allocator>( &mut self, context: &mut MirContext<'_, 'heap>, @@ -166,9 +171,16 @@ impl<'alloc, S1: Allocator, S: BumpAllocator> PlacementSolver<'_, 'alloc, S1, S> .diagnostics .push(unsatisfiable_placement(body.span, block_span, &failure)); } else { - // Only run the backward refinement pass if the forward pass succeeded — - // there is nothing to refine when blocks remain unassigned. - self.run_backwards_loop(body, ®ions); + // Iterate adjustment passes in alternating directions until convergence. + // Only entered when the forward pass succeeded — nothing to refine with + // unassigned blocks. + let mut has_changed = true; + let mut direction = Direction::Backward; + + while has_changed { + has_changed = self.run_adjustment(direction, body, ®ions); + direction = direction.reverse(); + } } // Collect the final assignments into the output vec. Unassigned blocks (from a @@ -221,7 +233,12 @@ impl<'alloc, S1: Allocator, S: BumpAllocator> PlacementSolver<'_, 'alloc, S1, S> self.condensation[region_id].kind = kind; } PlacementRegionKind::Cyclic(cyclic) => { - let mut csp = ConstraintSatisfaction::new(self, region_id, cyclic); + let mut csp = ConstraintSatisfaction::new( + self, + csp::ConstraintSatisfactionMode::Initial, + region_id, + cyclic, + ); if csp.retry(body) { // Found a perturbation — flush the new assignments, and resume. @@ -327,7 +344,12 @@ impl<'alloc, S1: Allocator, S: BumpAllocator> PlacementSolver<'_, 'alloc, S1, S> } PlacementRegionKind::Cyclic(cyclic) => { let members = cyclic.members; - let mut csp = ConstraintSatisfaction::new(self, region_id, cyclic); + let mut csp = ConstraintSatisfaction::new( + self, + csp::ConstraintSatisfactionMode::Initial, + region_id, + cyclic, + ); if !csp.solve(body) { let region = PlacementRegionKind::Cyclic(csp.region); @@ -362,27 +384,39 @@ impl<'alloc, S1: Allocator, S: BumpAllocator> PlacementSolver<'_, 'alloc, S1, S> Ok(()) } - /// Re-evaluates assignments in reverse topological order for refinement. - /// - /// Delegates to [`adjust_trivial`](Self::adjust_trivial) and - /// [`adjust_cyclic`](Self::adjust_cyclic). - fn run_backwards_loop(&mut self, body: &Body<'_>, regions: &[PlacementRegionId]) { + /// Re-evaluates assignments in the given `direction`, returning whether any assignment + /// changed. Called in alternating directions until convergence. + fn run_adjustment( + &mut self, + direction: Direction, + body: &Body<'_>, + regions: &[PlacementRegionId], + ) -> bool { debug_assert!(!regions.is_empty(), "at least the start block must exist"); - let mut ptr = regions.len(); - - while ptr > 0 { - ptr -= 1; + let mut iter = regions.iter(); + let mut changed = false; + + loop { + let Some(®ion_id) = (match direction { + Direction::Forward => iter.next(), + Direction::Backward => iter.next_back(), + }) else { + break changed; + }; - let region_id = regions[ptr]; let region = &mut self.condensation[region_id]; let kind = mem::replace(&mut region.kind, PlacementRegionKind::Unassigned); let kind = match kind { kind @ PlacementRegionKind::Trivial(TrivialPlacementRegion { block }) => { - self.adjust_trivial(body, region_id, block); + changed |= self.adjust_trivial(body, region_id, block); + kind + } + PlacementRegionKind::Cyclic(cyclic) => { + let (cyclic_changed, kind) = self.adjust_cyclic(body, region_id, cyclic); + changed |= cyclic_changed; kind } - PlacementRegionKind::Cyclic(cyclic) => self.adjust_cyclic(body, region_id, cyclic), PlacementRegionKind::Unassigned => { unreachable!( "previous iteration has not returned region {region_id:?} into the graph" @@ -402,7 +436,7 @@ impl<'alloc, S1: Allocator, S: BumpAllocator> PlacementSolver<'_, 'alloc, S1, S> body: &Body<'_>, region_id: PlacementRegionId, block: BasicBlockId, - ) { + ) -> bool { let estimator = CostEstimation { config: CostEstimationConfig::TRIVIAL, solver: self, @@ -422,11 +456,14 @@ impl<'alloc, S1: Allocator, S: BumpAllocator> PlacementSolver<'_, 'alloc, S1, S> let Some(elem) = heap.pop() else { // Re-estimation (unlikely) found no viable targets — keep the current assignment - return; + return false; }; if prev > elem.cost { self.targets[block] = Some(elem); + true + } else { + false } } @@ -439,19 +476,24 @@ impl<'alloc, S1: Allocator, S: BumpAllocator> PlacementSolver<'_, 'alloc, S1, S> body: &Body<'_>, region_id: PlacementRegionId, cyclic: CyclicPlacementRegion<'alloc>, - ) -> PlacementRegionKind<'alloc> { + ) -> (bool, PlacementRegionKind<'alloc>) { // Re-run with full boundary context — neighbor assignments may have changed since the // forward pass. - let mut csp = ConstraintSatisfaction::new(self, region_id, cyclic); + let mut csp = ConstraintSatisfaction::new( + self, + csp::ConstraintSatisfactionMode::Adjustment, + region_id, + cyclic, + ); if !csp.solve(body) { // New solve found nothing better — keep the forward-pass assignment - return PlacementRegionKind::Cyclic(csp.region); + return (false, PlacementRegionKind::Cyclic(csp.region)); } let region = csp.region; let prev_estimator = CostEstimation { - config: CostEstimationConfig::LOOP, + config: CostEstimationConfig::TRIVIAL, solver: self, determine_target: |block: BasicBlockId| self.targets[block], }; @@ -473,7 +515,7 @@ impl<'alloc, S1: Allocator, S: BumpAllocator> PlacementSolver<'_, 'alloc, S1, S> .sum(); let next_estimator = CostEstimation { - config: CostEstimationConfig::LOOP, + config: CostEstimationConfig::TRIVIAL, solver: self, determine_target: |block: BasicBlockId| { // Resolve SCC members from the candidate solution, everything else @@ -502,12 +544,14 @@ impl<'alloc, S1: Allocator, S: BumpAllocator> PlacementSolver<'_, 'alloc, S1, S> }) .sum(); + let mut changed = false; if prev_total_cost > next_total_cost { + changed = true; for block in &*region.blocks { self.targets[block.id] = Some(block.target); } } - PlacementRegionKind::Cyclic(region) + (changed, PlacementRegionKind::Cyclic(region)) } } diff --git a/libs/@local/hashql/mir/src/pass/execution/placement/solve/tests.rs b/libs/@local/hashql/mir/src/pass/execution/placement/solve/tests.rs index ea875739fd1..d40b22a5e86 100644 --- a/libs/@local/hashql/mir/src/pass/execution/placement/solve/tests.rs +++ b/libs/@local/hashql/mir/src/pass/execution/placement/solve/tests.rs @@ -1031,7 +1031,7 @@ fn backward_pass_keeps_assignment_when_csp_fails() { let PlacementRegionKind::Cyclic(cyclic) = kind else { panic!("expected cyclic region for bb1"); }; - let result_kind = solver.adjust_cyclic(&body, scc_region_id, cyclic); + let (_, result_kind) = solver.adjust_cyclic(&body, scc_region_id, cyclic); solver.condensation[scc_region_id].kind = result_kind; // Targets must be unchanged — adjust_cyclic kept the existing assignment