Skip to content
Open
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
11 changes: 11 additions & 0 deletions libs/@local/hashql/mir/src/builder/rvalue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,17 @@ macro_rules! rvalue {
rv.tuple(members)
}; $payload; $($rest)*)
};
($resume:path; $payload:tt; list; $($rest:tt)*) => {
$resume!(@rvalue |rv| {
rv.list([] as [!; 0])
}; $payload; $($rest)*)
};
($resume:path; $payload:tt; list $($members:tt),+; $($rest:tt)*) => {
$resume!(@rvalue |rv| {
let members = [$($crate::builder::_private::operand!(rv; $members)),*];
rv.list(members)
}; $payload; $($rest)*)
};
($resume:path; $payload:tt; struct $($field:ident : $value:tt),+ $(,)?; $($rest:tt)*) => {
$resume!(@rvalue |rv| {
let fields = [$(
Expand Down
1 change: 0 additions & 1 deletion libs/@local/hashql/mir/src/pass/analysis/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
mod callgraph;
mod data_dependency;
pub mod dataflow;
pub mod execution;
pub mod size_estimation;
pub use self::{
callgraph::{CallGraph, CallGraphAnalysis, CallKind, CallSite},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,31 @@ impl<T> Estimate<T> {
}
}

pub(crate) fn eval<M>(&self, monoid: &M, values: impl IntoIterator<Item: AsRef<T>>) -> T
where
T: Clone,
M: AdditiveMonoid<T>,
for<'a> &'a T: SaturatingMul<u16, Output = T>,
{
match self {
Self::Constant(value) => value.clone(),
Self::Affine(AffineEquation {
coefficients,
constant,
}) => {
let mut result = constant.clone();

// in case values are not provided, we assume they are zero, therefore cannot be
// added.
for (&coefficient, value) in coefficients.iter().zip(values) {
monoid.plus(&mut result, &value.as_ref().saturating_mul(coefficient));
}

result
}
}
}

/// Returns mutable access to the constant term.
pub(crate) const fn constant_mut(&mut self) -> &mut T {
match self {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use core::{alloc::Allocator, fmt};
use hashql_core::heap::TryCloneIn;

use super::{
InformationUnit,
affine::AffineEquation,
estimate::Estimate,
range::{Cardinality, InformationRange},
Expand Down Expand Up @@ -225,6 +226,29 @@ impl Footprint {
}
}

#[must_use]
pub fn average(
&self,
units: &[InformationRange],
cardinality: &[Cardinality],
) -> Option<InformationUnit> {
let units = self.units.eval(&SaturatingSemiring, units);
let cardinality = self.cardinality.eval(&SaturatingSemiring, cardinality);

if units.is_empty() || cardinality.is_empty() {
return Some(InformationUnit::new(0));
}

let max = units.inclusive_max()?;
let max = max.checked_mul(cardinality.inclusive_max()?)?;

let min = units.min();
let min = min.checked_mul(cardinality.min())?;

let avg = min.midpoint(max);
Some(avg)
}

/// Adds `other * coefficient` to this footprint (component-wise).
pub(crate) fn saturating_mul_add(
&mut self,
Expand Down
25 changes: 25 additions & 0 deletions libs/@local/hashql/mir/src/pass/analysis/size_estimation/range.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,12 +133,31 @@ macro_rules! range {
Self { min: one, max: Bound::Included(one) }
}

#[inline]
pub const fn zero() -> Self {
let zero = <$inner>::new(0);
Self { min: zero, max: Bound::Included(zero) }
}

#[inline]
pub const fn full() -> Self {
let zero = <$inner>::new(0);
Self { min: zero, max: Bound::Unbounded }
}

#[inline]
pub const fn min(self) -> $inner {
self.min
}

pub fn inclusive_max(self) -> Option<$inner> {
match self.max {
Bound::Included(max) => Some(max),
Bound::Excluded(max) => max.raw.checked_sub(1).map(<$inner>::new),
Bound::Unbounded => None,
}
}

#[inline]
pub const fn is_empty(&self) -> bool {
match self.max {
Expand Down Expand Up @@ -260,6 +279,12 @@ macro_rules! range {
}

forward_ref_binop!(impl SaturatingMul<u16>::saturating_mul for $name);

impl AsRef<$name> for $name {
fn as_ref(&self) -> &$name {
self
}
}
};
}

Expand Down
24 changes: 24 additions & 0 deletions libs/@local/hashql/mir/src/pass/analysis/size_estimation/unit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ macro_rules! unit {
Self { raw: inner }
}

#[inline]
pub const fn as_u32(self) -> u32 {
self.raw as u32
}

#[inline]
pub const fn checked_add(self, rhs: Self) -> Option<Self> {
match self.raw.checked_add(rhs.raw) {
Expand Down Expand Up @@ -157,6 +162,25 @@ unit!(
pub struct InformationUnit(u32)
);

impl InformationUnit {
#[inline]
#[must_use]
pub const fn checked_mul(self, cardinal: Cardinal) -> Option<Self> {
let raw = self.raw.checked_mul(cardinal.raw);

match raw {
Some(value) => Some(Self::new(value)),
None => None,
}
}

#[inline]
#[must_use]
pub const fn midpoint(self, other: Self) -> Self {
Self::new(u32::midpoint(self.raw, other.raw))
}
}

unit!(
/// A unit of cardinality (element count).
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,29 @@ use crate::{
pub struct Cost(core::num::niche_types::U32NotAllOnes);

impl Cost {
/// The maximum representable cost value (`u32::MAX - 1`).
///
/// Used as a sentinel for "effectively infinite" cost when exact values overflow.
///
/// ```
/// # use hashql_mir::pass::execution::Cost;
/// assert_eq!(Cost::MAX, Cost::new(u32::MAX - 1).unwrap());
/// ```
pub const MAX: Self = match core::num::niche_types::U32NotAllOnes::new(0xFFFF_FFFE) {
Some(cost) => Self(cost),
None => unreachable!(),
};

/// Creates a cost from a `u32` value, returning `None` if the value is `u32::MAX`.
///
/// The `u32::MAX` value is reserved as a niche for `Option<Cost>` optimization.
/// The `u32::MAX` value is reserved as a niche for [`Option<Cost>`] optimization.
///
/// ```
/// # use hashql_mir::pass::execution::Cost;
/// assert!(Cost::new(0).is_some());
/// assert!(Cost::new(100).is_some());
/// assert!(Cost::new(u32::MAX).is_none()); // Reserved for niche
/// ```
#[must_use]
pub const fn new(value: u32) -> Option<Self> {
match core::num::niche_types::U32NotAllOnes::new(value) {
Expand Down Expand Up @@ -69,6 +89,25 @@ impl Cost {
// SAFETY: The caller must ensure `value` is not `u32::MAX`.
Self(unsafe { core::num::niche_types::U32NotAllOnes::new_unchecked(value) })
}

/// Adds `other` to this cost, saturating at [`Cost::MAX`] on overflow.
///
/// ```
/// # use hashql_mir::pass::execution::Cost;
/// let cost = Cost::new(100).unwrap();
/// assert_eq!(cost.saturating_add(50), Cost::new(150).unwrap());
///
/// // Saturates at MAX instead of overflowing
/// let large = Cost::new(u32::MAX - 10).unwrap();
/// assert_eq!(large.saturating_add(100), Cost::MAX);
/// ```
#[inline]
#[must_use]
pub fn saturating_add(self, other: u32) -> Self {
let raw = self.0.as_inner();

Self::new(raw.saturating_add(other)).unwrap_or(Self::MAX)
}
}

impl fmt::Display for Cost {
Expand Down Expand Up @@ -109,6 +148,7 @@ impl<A: Allocator> TraversalCostVec<A> {
}
}

/// Iterates over all (local, cost) pairs that have assigned costs.
pub fn iter(&self) -> impl Iterator<Item = (Local, Cost)> {
self.costs
.iter_enumerated()
Expand Down Expand Up @@ -144,7 +184,6 @@ impl<A: Allocator> StatementCostVec<A> {
mut iter: impl ExactSizeIterator<Item = u32>,
alloc: A,
) -> (Box<BasicBlockSlice<u32>, A>, usize) {
// Try to reuse existing offsets if available and of correct length
let mut offsets = Box::new_uninit_slice_in(iter.len() + 1, alloc);

let mut offset = 0_u32;
Expand Down Expand Up @@ -193,6 +232,10 @@ impl<A: Allocator> StatementCostVec<A> {
)
}

/// Rebuilds the offset table for a new block layout.
///
/// Call after transforms that change statement counts per block. Does not resize or clear
/// the cost data β€” callers must ensure the total statement count remains unchanged.
#[expect(clippy::cast_possible_truncation)]
pub fn remap(&mut self, blocks: &BasicBlocks)
where
Expand All @@ -207,16 +250,21 @@ impl<A: Allocator> StatementCostVec<A> {
self.offsets = offsets;
}

/// Returns `true` if no statements have assigned costs.
pub fn is_empty(&self) -> bool {
self.costs.iter().all(Option::is_none)
}

/// Returns the cost slice for all statements in `block`.
///
/// The returned slice is indexed by statement position (0-based within the block).
pub fn of(&self, block: BasicBlockId) -> &[Option<Cost>] {
let range = (self.offsets[block] as usize)..(self.offsets[block.plus(1)] as usize);

&self.costs[range]
}

/// Returns a reference to the allocator used by this cost vector.
pub fn allocator(&self) -> &A {
Box::allocator(&self.offsets)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
macro_rules! cost {
($value:expr) => {
const { $crate::pass::analysis::execution::cost::Cost::new_panic($value) }
const { $crate::pass::execution::cost::Cost::new_panic($value) }
};
}

mod cost;
pub mod splitting;
pub mod statement_placement;
pub mod target;
pub mod terminator_placement;

pub use self::cost::{Cost, StatementCostVec, TraversalCostVec};
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use crate::{
builder::body,
context::MirContext,
intern::Interner,
pass::analysis::execution::{
pass::execution::{
StatementCostVec,
cost::Cost,
target::{TargetArray, TargetBitSet, TargetId},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ use crate::{
terminator::TerminatorKind,
},
context::MirContext,
pass::analysis::{
dataflow::{
pass::{
analysis::dataflow::{
framework::{DataflowAnalysis, DataflowResults},
lattice::PowersetLattice,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::{
body::{Body, Source, local::Local, operand::Operand, place::Place, rvalue::RValue},
context::MirContext,
pass::{
analysis::execution::{
execution::{
Cost, StatementCostVec,
cost::TraversalCostVec,
statement_placement::lookup::{Access, entity_projection_access},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::{
context::MirContext,
def::DefId,
intern::Interner,
pass::analysis::execution::statement_placement::{
pass::execution::statement_placement::{
EmbeddingStatementPlacement,
tests::{assert_placement, run_placement},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::{
},
context::MirContext,
pass::{
analysis::execution::{
execution::{
cost::{Cost, StatementCostVec, TraversalCostVec},
target::Interpreter,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::{
context::MirContext,
def::DefId,
intern::Interner,
pass::analysis::execution::statement_placement::{
pass::execution::statement_placement::{
InterpreterStatementPlacement,
tests::{assert_placement, run_placement},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ use crate::{
body::Body,
context::MirContext,
pass::{
analysis::execution::cost::{StatementCostVec, TraversalCostVec},
execution::cost::{StatementCostVec, TraversalCostVec},
transform::Traversals,
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use crate::{
},
context::MirContext,
pass::{
analysis::execution::{
execution::{
cost::{Cost, StatementCostVec, TraversalCostVec},
statement_placement::lookup::{Access, entity_projection_access},
target::Postgres,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use crate::{
intern::Interner,
op,
pass::{
analysis::execution::statement_placement::{
execution::statement_placement::{
PostgresStatementPlacement, StatementPlacement as _,
tests::{assert_placement, run_placement},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use crate::{
intern::Interner,
pass::{
Changed, TransformPass as _,
analysis::execution::{
execution::{
cost::{StatementCostVec, TraversalCostVec},
statement_placement::{EmbeddingStatementPlacement, PostgresStatementPlacement},
},
Expand Down
Loading
Loading