Skip to content

Commit e41c68f

Browse files
authored
BE-209: HashQL: Implement data dependency analysis for MIR (#8158)
1 parent 6274897 commit e41c68f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+2216
-3
lines changed
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
use std::io::Write as _;
2+
3+
use hashql_ast::node::expr::Expr;
4+
use hashql_core::r#type::environment::Environment;
5+
use hashql_diagnostics::DiagnosticIssues;
6+
use hashql_mir::{
7+
context::MirContext,
8+
intern::Interner,
9+
pass::{Pass as _, analysis::DataDependencyAnalysis},
10+
};
11+
12+
use super::{RunContext, Suite, SuiteDiagnostic};
13+
use crate::suite::{
14+
common::{Header, process_issues},
15+
mir_reify::{mir_format_text, mir_reify},
16+
};
17+
18+
pub(crate) struct MirPassAnalysisDataDependency;
19+
20+
impl Suite for MirPassAnalysisDataDependency {
21+
fn name(&self) -> &'static str {
22+
"mir/pass/analysis/data-dependency"
23+
}
24+
25+
fn run<'heap>(
26+
&self,
27+
RunContext {
28+
heap, diagnostics, ..
29+
}: RunContext<'_, 'heap>,
30+
expr: Expr<'heap>,
31+
) -> Result<String, SuiteDiagnostic> {
32+
let mut environment = Environment::new(heap);
33+
let interner = Interner::new(heap);
34+
35+
let mut buffer = Vec::new();
36+
37+
let (root, mut bodies) = mir_reify(heap, expr, &interner, &mut environment, diagnostics)?;
38+
39+
writeln!(buffer, "{}\n", Header::new("MIR")).expect("should be able to write to buffer");
40+
mir_format_text(heap, &environment, &mut buffer, root, &bodies);
41+
42+
let mut context = MirContext {
43+
heap,
44+
env: &environment,
45+
interner: &interner,
46+
diagnostics: DiagnosticIssues::new(),
47+
};
48+
49+
let body = &mut bodies[root];
50+
let mut analysis = DataDependencyAnalysis::new();
51+
analysis.run(&mut context, body);
52+
let graph = analysis.finish();
53+
let transient = graph.transient(&interner);
54+
55+
let _ = writeln!(
56+
buffer,
57+
"\n{}\n\n{}\n\n{}\n\n{}",
58+
Header::new("Data Dependency Graph"),
59+
graph,
60+
Header::new("Transient Data Dependency Graph"),
61+
transient,
62+
);
63+
64+
process_issues(diagnostics, context.diagnostics)?;
65+
66+
Ok(String::from_utf8_lossy_owned(buffer))
67+
}
68+
}

libs/@local/hashql/compiletest/src/suite/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ mod hir_lower_normalization;
2020
mod hir_lower_specialization;
2121
mod hir_lower_thunking;
2222
mod hir_reify;
23+
mod mir_pass_analysis_data_dependency;
2324
mod mir_pass_transform_cfg_simplify;
2425
mod mir_reify;
2526
mod parse_syntax_dump;
@@ -50,6 +51,7 @@ use self::{
5051
hir_lower_normalization::HirLowerNormalizationSuite,
5152
hir_lower_specialization::HirLowerSpecializationSuite,
5253
hir_lower_thunking::HirLowerThunkingSuite, hir_reify::HirReifySuite,
54+
mir_pass_analysis_data_dependency::MirPassAnalysisDataDependency,
5355
mir_pass_transform_cfg_simplify::MirPassTransformCfgSimplify, mir_reify::MirReifySuite,
5456
parse_syntax_dump::ParseSyntaxDumpSuite,
5557
};
@@ -144,6 +146,7 @@ const SUITES: &[&dyn Suite] = &[
144146
&HirLowerTypeInferenceIntrinsicsSuite,
145147
&HirLowerTypeInferenceSuite,
146148
&HirReifySuite,
149+
&MirPassAnalysisDataDependency,
147150
&MirPassTransformCfgSimplify,
148151
&MirReifySuite,
149152
&ParseSyntaxDumpSuite,

libs/@local/hashql/core/src/graph/linked.rs

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,12 @@
4848
//! ```
4949
5050
use alloc::alloc::Global;
51-
use core::alloc::Allocator;
51+
use core::{alloc::Allocator, ops::Index};
5252

5353
use super::{
5454
DIRECTIONS, DirectedGraph, Direction, EdgeId, NodeId, Predecessors, Successors, Traverse,
5555
};
56-
use crate::id::{HasId, IdSlice, IdVec};
56+
use crate::id::{HasId, Id, IdSlice, IdVec};
5757

5858
/// Sentinel value indicating "no edge" in linked lists.
5959
///
@@ -69,6 +69,7 @@ const TOMBSTONE: EdgeId = EdgeId(usize::MAX);
6969
/// - Two linked list heads (outgoing and incoming edges)
7070
///
7171
/// The `edges` array stores the head of each linked list, indexed by [`Direction`].
72+
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
7273
pub struct Node<N> {
7374
/// Unique identifier for this node.
7475
id: NodeId,
@@ -102,6 +103,7 @@ impl<N> HasId for Node<N> {
102103
/// The edge participates in two separate linked lists simultaneously:
103104
/// 1. The source node's outgoing edge list
104105
/// 2. The target node's incoming edge list
106+
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
105107
pub struct Edge<E> {
106108
/// Unique identifier for this edge.
107109
id: EdgeId,
@@ -207,6 +209,7 @@ impl<E> Edge<E> {
207209
/// }
208210
/// # else { unreachable!() }
209211
/// ```
212+
#[derive(Debug, Clone)]
210213
pub struct LinkedGraph<N, E, A: Allocator = Global> {
211214
/// All nodes in the graph, indexed by [`NodeId`].
212215
nodes: IdVec<NodeId, Node<N>, A>,
@@ -284,6 +287,49 @@ impl<N, E, A: Allocator> LinkedGraph<N, E, A> {
284287
})
285288
}
286289

290+
/// Populates the graph with nodes derived from an existing indexed collection.
291+
///
292+
/// For each element in `domain`, calls `data` to produce the node data and adds a
293+
/// corresponding node to the graph. The resulting [`NodeId`]s will have the same
294+
/// numeric values as the source collection's indices, enabling direct ID translation
295+
/// between the domain and the graph.
296+
///
297+
/// # Panics
298+
///
299+
/// Panics if the graph already contains nodes. This method is intended for initial
300+
/// population only.
301+
///
302+
/// # Examples
303+
///
304+
/// ```rust
305+
/// # use hashql_core::graph::LinkedGraph;
306+
/// # use hashql_core::id::{Id, IdVec};
307+
/// #
308+
/// # hashql_core::id::newtype!(struct MyId(usize is 0..=usize::MAX));
309+
/// #
310+
/// let mut items: IdVec<MyId, &str> = IdVec::new();
311+
/// items.push("first");
312+
/// items.push("second");
313+
///
314+
/// let mut graph: LinkedGraph<&str, ()> = LinkedGraph::new();
315+
/// graph.derive(&items, |_id, &value| value);
316+
///
317+
/// // Node 0 corresponds to items[0], etc.
318+
/// assert_eq!(graph.nodes().len(), 2);
319+
/// ```
320+
pub fn derive<I, T>(&mut self, domain: &IdSlice<I, T>, mut data: impl FnMut(I, &T) -> N)
321+
where
322+
I: Id,
323+
{
324+
assert!(self.nodes.is_empty());
325+
326+
self.nodes.raw.reserve(domain.len());
327+
328+
for (id, item) in domain.iter_enumerated() {
329+
self.add_node(data(id, item));
330+
}
331+
}
332+
287333
/// Returns a reference to the node with the given identifier.
288334
///
289335
/// Returns [`None`] if no node exists with that identifier.
@@ -378,6 +424,14 @@ impl<N, E, A: Allocator> LinkedGraph<N, E, A> {
378424
self.edges.as_slice()
379425
}
380426

427+
/// Removes all edges from the graph while preserving nodes.
428+
pub fn clear_edges(&mut self) {
429+
self.edges.clear();
430+
for node in self.nodes.iter_mut() {
431+
node.edges = [TOMBSTONE; DIRECTIONS];
432+
}
433+
}
434+
381435
/// Returns an iterator over edges incident to a node in the given direction.
382436
///
383437
/// For [`Direction::Outgoing`], iterates edges where `node` is the source.
@@ -459,6 +513,12 @@ impl<N, E, A: Allocator> LinkedGraph<N, E, A> {
459513
pub fn outgoing_edges(&self, node: NodeId) -> IncidentEdges<'_, N, E, A> {
460514
IncidentEdges::new(self, Direction::Outgoing, node)
461515
}
516+
517+
/// Removes all nodes and edges from the graph.
518+
pub fn clear(&mut self) {
519+
self.nodes.clear();
520+
self.edges.clear();
521+
}
462522
}
463523

464524
impl<N, E> Default for LinkedGraph<N, E> {
@@ -467,6 +527,22 @@ impl<N, E> Default for LinkedGraph<N, E> {
467527
}
468528
}
469529

530+
impl<N, E, A: Allocator> Index<NodeId> for LinkedGraph<N, E, A> {
531+
type Output = Node<N>;
532+
533+
fn index(&self, index: NodeId) -> &Self::Output {
534+
&self.nodes[index]
535+
}
536+
}
537+
538+
impl<N, E, A: Allocator> Index<EdgeId> for LinkedGraph<N, E, A> {
539+
type Output = Edge<E>;
540+
541+
fn index(&self, index: EdgeId) -> &Self::Output {
542+
&self.edges[index]
543+
}
544+
}
545+
470546
impl<N, E, A: Allocator> DirectedGraph for LinkedGraph<N, E, A> {
471547
type Edge<'this>
472548
= &'this Edge<E>

libs/@local/hashql/mir/src/body/place.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
//! Places represent storage locations in the MIR, including local variables and complex paths
44
//! through data structures. Projections allow accessing nested data within structured types.
55
6-
use core::alloc::Allocator;
6+
use core::{alloc::Allocator, fmt};
77

88
use hashql_core::{id, intern::Interned, symbol::Symbol, r#type::TypeId};
99

@@ -400,3 +400,13 @@ pub enum ProjectionKind<'heap> {
400400
/// [`Local`] contains the index value that determines which element to access.
401401
Index(Local),
402402
}
403+
404+
impl fmt::Display for ProjectionKind<'_> {
405+
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
406+
match self {
407+
ProjectionKind::Field(index) => write!(fmt, ".{index}"),
408+
ProjectionKind::FieldByName(name) => write!(fmt, ".{name}"),
409+
ProjectionKind::Index(index) => write!(fmt, "[%{index}]"),
410+
}
411+
}
412+
}

0 commit comments

Comments
 (0)