diff --git a/libs/@local/hashql/mir/src/pass/execution/island/mod.rs b/libs/@local/hashql/mir/src/pass/execution/island/mod.rs index c8d866efff6..fb26e47b8c2 100644 --- a/libs/@local/hashql/mir/src/pass/execution/island/mod.rs +++ b/libs/@local/hashql/mir/src/pass/execution/island/mod.rs @@ -30,7 +30,6 @@ use crate::{ }; pub(crate) mod graph; -pub(crate) mod schedule; #[cfg(test)] mod tests; diff --git a/libs/@local/hashql/mir/src/pass/execution/island/schedule/mod.rs b/libs/@local/hashql/mir/src/pass/execution/island/schedule/mod.rs deleted file mode 100644 index 143842e4498..00000000000 --- a/libs/@local/hashql/mir/src/pass/execution/island/schedule/mod.rs +++ /dev/null @@ -1,129 +0,0 @@ -#[cfg(test)] -mod tests; - -use alloc::{alloc::Global, collections::VecDeque}; -use core::{alloc::Allocator, cmp}; - -use hashql_core::graph::{DirectedGraph as _, Predecessors as _, Successors as _}; - -use super::{IslandId, IslandVec, graph::IslandGraph}; - -/// An island with its assigned parallelism level. -/// -/// Islands at the same level have no dependencies between them and can execute concurrently. -/// Level 0 contains islands with no predecessors. -#[derive(Debug, Copy, Clone)] -pub struct ScheduledIsland { - /// The island this entry refers to. - pub island: IslandId, - /// The parallelism level. All islands at the same level are independent. - pub level: u32, -} - -/// Topological ordering of islands with parallelism levels. -/// -/// Produced by [`IslandGraph::schedule`]. Each island appears exactly once, -/// ordered so that all predecessors of an island appear before it. -#[derive(Debug)] -pub struct IslandSchedule { - entries: Vec, -} - -impl IslandSchedule { - /// Returns the scheduled entries in topological order. - #[inline] - #[must_use] - pub fn entries(&self) -> &[ScheduledIsland] { - &self.entries - } - - /// Returns the number of scheduled islands. - #[inline] - #[must_use] - pub const fn len(&self) -> usize { - self.entries.len() - } - - /// Returns `true` if the schedule contains no islands. - #[inline] - #[must_use] - pub const fn is_empty(&self) -> bool { - self.entries.is_empty() - } - - /// Iterates over the scheduled entries in topological order. - #[inline] - pub fn iter(&self) -> impl ExactSizeIterator { - self.entries.iter() - } - - #[inline] - pub fn level_count(&self) -> usize { - self.entries - .last() - .map_or(0, |entry| entry.level as usize + 1) - } - - #[inline] - pub fn levels(&self) -> impl Iterator { - self.entries.chunk_by(|lhs, rhs| lhs.level == rhs.level) - } -} - -impl IslandGraph { - #[must_use] - pub fn schedule(&self) -> IslandSchedule { - self.schedule_in(Global, Global) - } -} - -impl IslandGraph { - /// Computes a topological schedule with level assignment for parallelism. - /// - /// Each island is assigned the lowest level such that all its predecessors are at - /// strictly lower levels. Islands at the same level have no direct dependencies and - /// can execute concurrently. - #[expect(clippy::cast_possible_truncation)] - pub fn schedule_in(&self, scratch: S, alloc: B) -> IslandSchedule - where - B: Allocator, - S: Allocator + Clone, - { - let node_count = self.node_count(); - - let mut in_degree = IslandVec::from_elem_in(0_u32, node_count, scratch.clone()); - let mut levels = IslandVec::from_elem_in(0_u32, node_count, scratch.clone()); - - for (island_id, _) in self.iter_nodes() { - in_degree[island_id] = self.predecessors(island_id).count() as u32; - } - - let mut queue: VecDeque = VecDeque::new_in(scratch); - for (island_id, _) in self.iter_nodes() { - if in_degree[island_id] == 0 { - queue.push_back(island_id); - } - } - - let mut entries = Vec::with_capacity_in(node_count, alloc); - - while let Some(island_id) = queue.pop_front() { - entries.push(ScheduledIsland { - island: island_id, - level: levels[island_id], - }); - - for successor in self.successors(island_id) { - levels[successor] = cmp::max(levels[successor], levels[island_id] + 1); - in_degree[successor] -= 1; - - if in_degree[successor] == 0 { - queue.push_back(successor); - } - } - } - - entries.sort_by_key(|entry| entry.level); - IslandSchedule { entries } - } -} diff --git a/libs/@local/hashql/mir/src/pass/execution/island/schedule/tests.rs b/libs/@local/hashql/mir/src/pass/execution/island/schedule/tests.rs deleted file mode 100644 index 5439d7da89a..00000000000 --- a/libs/@local/hashql/mir/src/pass/execution/island/schedule/tests.rs +++ /dev/null @@ -1,232 +0,0 @@ -//! Tests for island schedule computation. -#![expect(clippy::min_ident_chars)] - -use hashql_core::{ - graph::DirectedGraph as _, heap::Heap, symbol::sym, r#type::environment::Environment, -}; - -use crate::{ - builder::body, - intern::Interner, - pass::execution::{ - island::graph::{IslandKind, tests::build_graph}, - target::TargetId, - }, -}; - -/// Data islands should be at a lower level than their consumers. -#[test] -fn data_island_before_consumer() { - let heap = Heap::new(); - let interner = Interner::new(&heap); - let env = Environment::new(&heap); - - let body = body!(interner, env; [graph::read::filter]@0/2 -> ? { - decl env: (), vertex: [Opaque sym::path::Entity; ?], val: ?; - @proj enc = vertex.encodings: ?, - vecs = enc.vectors: ?; - - bb0() { - val = load vecs; - return val; - } - }); - - let graph = build_graph(&body, &[TargetId::Interpreter]); - let schedule = graph.schedule(); - let entries = schedule.entries(); - - assert_eq!(entries.len(), 2); - - let exec_entry = entries - .iter() - .find(|entry| matches!(graph[entry.island].kind(), IslandKind::Exec(_))) - .expect("should have an exec island"); - let data_entry = entries - .iter() - .find(|entry| matches!(graph[entry.island].kind(), IslandKind::Data)) - .expect("should have a data island"); - - assert_eq!(data_entry.level, 0); - assert_eq!(exec_entry.level, 1); -} - -/// Every island in the graph appears exactly once in the schedule. -#[test] -fn covers_all_nodes() { - let heap = Heap::new(); - let interner = Interner::new(&heap); - let env = Environment::new(&heap); - - let body = body!(interner, env; [graph::read::filter]@0/2 -> ? { - decl env: (), vertex: [Opaque sym::path::Entity; ?], - val: ?, cond: Bool; - @proj props = vertex.properties: ?, - enc = vertex.encodings: ?, - vecs = enc.vectors: ?; - - bb0() { - cond = load true; - if cond then bb1() else bb2(); - }, - bb1() { - val = load props; - goto bb3(); - }, - bb2() { - val = load vecs; - goto bb3(); - }, - bb3() { - val = load vecs; - return val; - } - }); - - let graph = build_graph( - &body, - &[ - TargetId::Postgres, - TargetId::Interpreter, - TargetId::Embedding, - TargetId::Postgres, - ], - ); - - let schedule = graph.schedule(); - assert_eq!(schedule.entries().len(), graph.node_count()); -} - -/// Two levels: data island at level 0, exec island at level 1. -#[test] -fn level_count_with_data_dependency() { - let heap = Heap::new(); - let interner = Interner::new(&heap); - let env = Environment::new(&heap); - - let body = body!(interner, env; [graph::read::filter]@0/2 -> ? { - decl env: (), vertex: [Opaque sym::path::Entity; ?], val: ?; - @proj enc = vertex.encodings: ?, - vecs = enc.vectors: ?; - - bb0() { - val = load vecs; - return val; - } - }); - - let graph = build_graph(&body, &[TargetId::Interpreter]); - let schedule = graph.schedule(); - - assert_eq!(schedule.level_count(), 2); - assert_eq!(schedule.levels().count(), 2); -} - -/// Each level slice contains islands with the same level, and levels are ascending. -#[test] -fn levels_are_contiguous_and_ascending() { - let heap = Heap::new(); - let interner = Interner::new(&heap); - let env = Environment::new(&heap); - - let body = body!(interner, env; [graph::read::filter]@0/2 -> ? { - decl env: (), vertex: [Opaque sym::path::Entity; ?], - val: ?, cond: Bool; - @proj props = vertex.properties: ?, - enc = vertex.encodings: ?, - vecs = enc.vectors: ?; - - bb0() { - cond = load true; - if cond then bb1() else bb2(); - }, - bb1() { - val = load props; - goto bb3(); - }, - bb2() { - val = load vecs; - goto bb3(); - }, - bb3() { - val = load vecs; - return val; - } - }); - - let graph = build_graph( - &body, - &[ - TargetId::Postgres, - TargetId::Interpreter, - TargetId::Embedding, - TargetId::Postgres, - ], - ); - - let schedule = graph.schedule(); - let mut prev_level = None; - let mut total_entries = 0; - - for level_slice in schedule.levels() { - assert!(!level_slice.is_empty()); - - let expected_level = level_slice[0].level; - for entry in level_slice { - assert_eq!(entry.level, expected_level, "mixed levels within a slice"); - } - - if let Some(prev) = prev_level { - assert!(expected_level > prev, "levels not strictly ascending"); - } - prev_level = Some(expected_level); - total_entries += level_slice.len(); - } - - assert_eq!( - total_entries, - schedule.len(), - "levels must cover all entries" - ); -} - -/// Data islands appear in an earlier level slice than their exec consumers. -#[test] -fn levels_order_data_before_exec() { - let heap = Heap::new(); - let interner = Interner::new(&heap); - let env = Environment::new(&heap); - - let body = body!(interner, env; [graph::read::filter]@0/2 -> ? { - decl env: (), vertex: [Opaque sym::path::Entity; ?], val: ?; - @proj enc = vertex.encodings: ?, - vecs = enc.vectors: ?; - - bb0() { - val = load vecs; - return val; - } - }); - - let graph = build_graph(&body, &[TargetId::Interpreter]); - let schedule = graph.schedule(); - - let level_slices: Vec<_> = schedule.levels().collect(); - assert_eq!(level_slices.len(), 2); - - for entry in level_slices[0] { - assert_eq!(entry.level, 0); - assert!( - matches!(graph[entry.island].kind(), IslandKind::Data), - "level 0 should contain the data island" - ); - } - - for entry in level_slices[1] { - assert_eq!(entry.level, 1); - assert!( - matches!(graph[entry.island].kind(), IslandKind::Exec(_)), - "level 1 should contain the exec island" - ); - } -} diff --git a/libs/@local/hashql/mir/src/pass/execution/mod.rs b/libs/@local/hashql/mir/src/pass/execution/mod.rs index c1bd420e07b..ce0bddb23ef 100644 --- a/libs/@local/hashql/mir/src/pass/execution/mod.rs +++ b/libs/@local/hashql/mir/src/pass/execution/mod.rs @@ -39,7 +39,6 @@ pub use self::{ island::{ Island, IslandId, IslandVec, graph::{ExecIsland, IslandEdge, IslandGraph, IslandKind, IslandNode}, - schedule::{IslandSchedule, ScheduledIsland}, }, placement::error::PlacementDiagnosticCategory, target::TargetId,