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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions libs/@local/hashql/core/src/collections/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,4 @@ pub fn fast_hash_set<T>() -> FastHashSet<T> {

pub type TinyVec<T> = smallvec::SmallVec<T, 4>;
pub type SmallVec<T> = smallvec::SmallVec<T, 16>;
pub type InlineVec<T, const N: usize> = smallvec::SmallVec<T, N>;
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,12 @@ where
self.inner.is_empty()
}

/// Returns `true` if the iterated dominance frontier contains the given node.
#[must_use]
pub fn contains(&self, node: N) -> bool {
self.inner.contains(node)
}

/// Returns the number of nodes in the iterated dominance frontier.
#[must_use]
pub fn count(&self) -> usize {
Expand Down
84 changes: 84 additions & 0 deletions libs/@local/hashql/core/src/id/slice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,90 @@ where
}
}

impl<I, T> IdSlice<I, Option<T>>
where
I: Id,
{
/// Removes and returns the value at the given ID index.
///
/// Returns `None` if the index is out of bounds or if the value was already `None`.
/// The vector is not shrunk after removal.
///
/// # Examples
///
/// ```
/// # use hashql_core::{id::{IdVec, Id as _}, newtype};
/// # newtype!(struct MyId(u32 is 0..=0xFFFF_FF00));
/// let mut vec = IdVec::<MyId, Option<String>>::new();
/// vec.insert(MyId::from_usize(0), "hello".to_string());
/// let removed = vec.remove(MyId::from_usize(0));
/// assert_eq!(removed, Some("hello".to_string()));
/// assert!(vec[MyId::from_usize(0)].is_none());
/// ```
pub fn remove(&mut self, index: I) -> Option<T> {
self.get_mut(index)?.take()
}

/// Returns `true` if the vector contains a value (not `None`) at the given ID index.
///
/// # Examples
///
/// ```
/// # use hashql_core::{id::{IdVec, Id as _}, newtype};
/// # newtype!(struct MyId(u32 is 0..=0xFFFF_FF00));
/// let mut vec = IdVec::<MyId, Option<String>>::new();
/// vec.insert(MyId::from_usize(0), "hello".to_string());
/// assert!(vec.contains(MyId::from_usize(0)));
/// assert!(!vec.contains(MyId::from_usize(1)));
/// ```
pub fn contains(&self, index: I) -> bool {
self.get(index).and_then(Option::as_ref).is_some()
}

/// Gets a reference to the inner value at `index`, if present.
///
/// Returns [`None`] if the index is out of bounds or if the value at that index is [`None`].
///
/// # Examples
///
/// ```
/// # use hashql_core::{id::{IdVec, Id as _}, newtype};
/// # newtype!(struct MyId(u32 is 0..=0xFFFF_FF00));
/// let mut vec = IdVec::<MyId, Option<String>>::new();
/// vec.insert(MyId::from_usize(0), "hello".to_string());
///
/// assert_eq!(vec.lookup(MyId::from_usize(0)), Some(&"hello".to_string()));
/// assert_eq!(vec.lookup(MyId::from_usize(1)), None); // out of bounds
/// ```
pub fn lookup(&self, index: I) -> Option<&T> {
self.get(index).and_then(Option::as_ref)
}

/// Gets a mutable reference to the inner value at `index`, if present.
///
/// Returns [`None`] if the index is out of bounds or if the value at that index is [`None`].
///
/// # Examples
///
/// ```
/// # use hashql_core::{id::{IdVec, Id as _}, newtype};
/// # newtype!(struct MyId(u32 is 0..=0xFFFF_FF00));
/// let mut vec = IdVec::<MyId, Option<String>>::new();
/// vec.insert(MyId::from_usize(0), "hello".to_string());
///
/// if let Some(value) = vec.lookup_mut(MyId::from_usize(0)) {
/// value.push_str(" world");
/// }
/// assert_eq!(
/// vec.lookup(MyId::from_usize(0)),
/// Some(&"hello world".to_string())
/// );
/// ```
pub fn lookup_mut(&mut self, index: I) -> Option<&mut T> {
self.get_mut(index).and_then(Option::as_mut)
}
}

impl<I, T> Debug for IdSlice<I, T>
where
I: Id,
Expand Down
36 changes: 0 additions & 36 deletions libs/@local/hashql/core/src/id/vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -367,42 +367,6 @@ where
self.fill_until(index, || None).replace(value)
}

/// Removes and returns the value at the given ID index.
///
/// Returns `None` if the index is out of bounds or if the value was already `None`.
/// The vector is not shrunk after removal.
///
/// # Examples
///
/// ```
/// # use hashql_core::{id::{IdVec, Id as _}, newtype};
/// # newtype!(struct MyId(u32 is 0..=0xFFFF_FF00));
/// let mut vec = IdVec::<MyId, Option<String>>::new();
/// vec.insert(MyId::from_usize(0), "hello".to_string());
/// let removed = vec.remove(MyId::from_usize(0));
/// assert_eq!(removed, Some("hello".to_string()));
/// assert!(vec[MyId::from_usize(0)].is_none());
/// ```
pub fn remove(&mut self, index: I) -> Option<T> {
self.get_mut(index)?.take()
}

/// Returns `true` if the vector contains a value (not `None`) at the given ID index.
///
/// # Examples
///
/// ```
/// # use hashql_core::{id::{IdVec, Id as _}, newtype};
/// # newtype!(struct MyId(u32 is 0..=0xFFFF_FF00));
/// let mut vec = IdVec::<MyId, Option<String>>::new();
/// vec.insert(MyId::from_usize(0), "hello".to_string());
/// assert!(vec.contains(MyId::from_usize(0)));
/// assert!(!vec.contains(MyId::from_usize(1)));
/// ```
pub fn contains(&self, index: I) -> bool {
self.get(index).and_then(Option::as_ref).is_some()
}

/// Gets the value at `index`, or inserts one by calling `value` if it doesn't exist.
///
/// This method works on `IdVec<I, Option<T>>` to provide map-like semantics.
Expand Down
1 change: 1 addition & 0 deletions libs/@local/hashql/mir/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ simple-mermaid = { workspace = true }

[dev-dependencies]
hashql-compiletest = { workspace = true }
insta = { workspace = true }

[lints]
workspace = true
Expand Down
12 changes: 12 additions & 0 deletions libs/@local/hashql/mir/src/body/operand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,15 @@ pub enum Operand<'heap> {
/// value.
Constant(Constant<'heap>),
}

impl<'heap> From<Place<'heap>> for Operand<'heap> {
fn from(place: Place<'heap>) -> Self {
Operand::Place(place)
}
}

impl<'heap> From<Constant<'heap>> for Operand<'heap> {
fn from(constant: Constant<'heap>) -> Self {
Operand::Constant(constant)
}
}
168 changes: 168 additions & 0 deletions libs/@local/hashql/mir/src/body/place.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,174 @@ id::newtype!(
pub struct FieldIndex(usize is 0..=usize::MAX)
);

/// Context for reading from a [`Place`].
///
/// Describes how a place is being read during MIR execution. This distinction is important
/// for dataflow analysis, as reading a base local through a projection differs semantically
/// from loading the entire value.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum PlaceReadContext {
/// The place's value is being loaded directly.
///
/// This represents a full read of the value at the place, such as using a variable
/// as an operand: `let y = x;` reads `x` with `Load` context.
Load,

/// The place is being read as the base of a projection.
///
/// When accessing `x.field`, the local `x` is read with `Projection` context,
/// indicating that only part of the value is being accessed. This is relevant
/// for analyses that track partial vs. full uses of values.
Projection,
}

/// Context for writing to a [`Place`].
///
/// Describes how a place is being written during MIR execution. The distinction between
/// full assignment and partial writes through projections is critical for dataflow analysis,
/// particularly for determining when a value is fully defined vs. partially modified.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum PlaceWriteContext {
/// The place is being fully assigned a new value.
///
/// This represents a complete definition of the place, such as the left-hand side
/// of an assignment statement: `x = value;` writes to `x` with `Assign` context.
Assign,

/// The place is being written through as the base of a projection.
///
/// When assigning to `x.field = value`, the local `x` is accessed with `Projection`
/// context. This indicates a partial write: the local `x` must already be initialized,
/// and only part of its value is being modified.
Projection,

/// The place is receiving a value as a basic block parameter.
///
/// When control flow enters a basic block with parameters, those parameters are
/// assigned values from the predecessor's target arguments. Semantically equivalent
/// to [`Assign`](Self::Assign) for dataflow purposes, but distinguished to allow
/// tracking of inter-block value flow.
BlockParam,
}

/// Context for liveness markers on a [`Place`].
///
/// Liveness markers indicate when storage for a local variable becomes active or inactive.
/// These are not uses or definitions in the dataflow sense, but rather metadata about
/// the storage lifetime that enables memory optimization and borrow checking.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum PlaceLivenessContext {
/// Storage for the place becomes live.
///
/// Corresponds to [`StatementKind::StorageLive`]. After this point, the storage
/// is allocated and the place can be written to. The value is not yet initialized.
///
/// [`StatementKind::StorageLive`]: crate::body::statement::StatementKind::StorageLive
Begin,

/// Storage for the place becomes dead.
///
/// Corresponds to [`StatementKind::StorageDead`]. After this point, the storage
/// may be deallocated or reused. Any subsequent access to this place before another
/// `Begin` is undefined behavior.
///
/// [`StatementKind::StorageDead`]: crate::body::statement::StatementKind::StorageDead
End,
}

/// The context in which a [`Place`] is accessed during MIR traversal.
///
/// [`PlaceContext`] categorizes every place access into one of three categories:
/// reading, writing, or liveness tracking. This information is essential for
/// dataflow analysis, optimization passes, and correctness checking.
///
/// # Dataflow Analysis
///
/// Use [`into_def_use`](Self::into_def_use) to convert a context into its dataflow
/// classification for def-use analysis. Liveness contexts return `None` as they
/// don't participate in value flow.
///
/// # Example
///
/// ```text
/// In the statement: x.field = y
/// - `x` is accessed with Write(Projection) - partial write through projection
/// - `y` is accessed with Read(Load) - full value read
/// ```
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum PlaceContext {
/// The place is being read from.
Read(PlaceReadContext),

/// The place is being written to.
Write(PlaceWriteContext),

/// The place's storage liveness is being marked.
Liveness(PlaceLivenessContext),
}

impl PlaceContext {
#[must_use]
pub const fn is_use(self) -> bool {
matches!(self, Self::Read(_) | Self::Write(_))
}

#[must_use]
pub const fn is_write(self) -> bool {
matches!(self, Self::Write(_))
}

#[must_use]
pub const fn is_read(self) -> bool {
matches!(self, Self::Read(_))
}

/// Converts this context to its def-use classification for dataflow analysis.
///
/// Returns `None` for liveness markers, which don't participate in value flow.
#[must_use]
pub const fn into_def_use(self) -> Option<DefUse> {
match self {
Self::Read(_) => Some(DefUse::Use),
Self::Write(PlaceWriteContext::Projection) => Some(DefUse::PartialDef),
Self::Write(PlaceWriteContext::Assign | PlaceWriteContext::BlockParam) => {
Some(DefUse::Def)
}
Self::Liveness(_) => None,
}
}
}

/// Classification of place accesses for def-use analysis.
///
/// This enum represents the three fundamental categories of place access in dataflow analysis:
/// definitions (writes that fully initialize), uses (reads), and partial writes (modifications
/// to part of an already-initialized value).
///
/// Obtained via [`PlaceContext::into_def_use`]. Liveness markers don't participate in
/// def-use analysis and return `None`.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum DefUse {
/// A full definition of the place.
///
/// The place is being assigned a complete new value, making any previous value irrelevant.
/// This corresponds to [`PlaceWriteContext::Assign`] and [`PlaceWriteContext::BlockParam`].
Def,

/// A partial modification of the place.
///
/// Part of the place's value is being written through a projection (e.g., `x.field = v`).
/// The place must already be initialized, and only a portion is being modified.
/// This corresponds to [`PlaceWriteContext::Projection`].
PartialDef,

/// A use of the place's value.
///
/// The place's current value is being read. This corresponds to all [`PlaceReadContext`]
/// variants, as both direct loads and projection-based reads consume the value.
Use,
}

/// A borrowed reference to a place at a specific projection depth.
///
/// [`PlaceRef`] represents an intermediate point in a place's projection chain,
Expand Down
4 changes: 4 additions & 0 deletions libs/@local/hashql/mir/src/intern.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use crate::body::{local::Local, operand::Operand, place::Projection};

#[derive(Debug)]
pub struct Interner<'heap> {
pub heap: &'heap Heap,

pub locals: InternSet<'heap, [Local]>,
pub symbols: InternSet<'heap, [Symbol<'heap>]>,
pub operands: InternSet<'heap, [Operand<'heap>]>,
Expand All @@ -13,6 +15,8 @@ pub struct Interner<'heap> {
impl<'heap> Interner<'heap> {
pub fn new(heap: &'heap Heap) -> Self {
Self {
heap,

locals: InternSet::new(heap),
symbols: InternSet::new(heap),
operands: InternSet::new(heap),
Expand Down
8 changes: 1 addition & 7 deletions libs/@local/hashql/mir/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,4 @@ pub mod reify;
pub mod visit;

#[cfg(test)]
mod tests {

#[test]
fn it_works() {
assert_eq!(2, 2); // if this isn't true, then something went *horribly* wrong in the universe.
}
}
pub(crate) mod tests;
1 change: 1 addition & 0 deletions libs/@local/hashql/mir/src/pass/transform/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pub mod error;
pub mod ssa_repair;
Loading
Loading