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
1 change: 1 addition & 0 deletions libs/@local/graph/api/src/rest/hashql/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ impl<'heap> Compilation<'heap> {
HashQlDiagnosticCategory::Mir(MirDiagnosticCategory::Reify(category))
})
.with_diagnostics(advisories)?;
scratch.reset();

// Lower the MIR
let Success {
Expand Down
9 changes: 9 additions & 0 deletions libs/@local/hashql/core/src/symbol/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,15 @@ impl<'heap> Symbol<'heap> {
}
}

impl From<ConstantSymbol> for Symbol<'_> {
#[inline]
#[expect(unsafe_code)]
fn from(value: ConstantSymbol) -> Self {
// SAFETY: The constant symbol is already interned, so the repr is valid.
unsafe { Symbol::from_repr(Repr::constant(value.repr)) }
}
}

impl AsRef<Self> for Symbol<'_> {
#[inline]
fn as_ref(&self) -> &Self {
Expand Down
2 changes: 2 additions & 0 deletions libs/@local/hashql/core/src/symbol/sym.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,8 @@ hashql_macros::define_symbols! {
json,
JsonPath,
JsonPathSegment,
lhs,
rhs,
// [tidy] sort alphabetically end

internal: {
Expand Down
1 change: 1 addition & 0 deletions libs/@local/hashql/mir/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
macro_metavar_expr_concat,
never_type,
const_trait_impl,
stmt_expr_attributes,

// Library Features
allocator_api,
Expand Down
56 changes: 45 additions & 11 deletions libs/@local/hashql/mir/src/reify/atom.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
use core::alloc::Allocator;

use hashql_core::{id::Id as _, r#type::kind::TypeKind, value::Primitive};
use hashql_hir::node::{
Node,
access::{Access, FieldAccess, IndexAccess},
data::Data,
kind::NodeKind,
variable::Variable,
use hashql_core::{id::Id as _, span::Spanned, r#type::kind::TypeKind, value::Primitive};
use hashql_hir::{
node::{
Node,
access::{Access, FieldAccess, IndexAccess},
data::Data,
kind::NodeKind,
variable::Variable,
},
path::QualifiedPath,
};

use super::{
Expand All @@ -20,6 +23,7 @@ use crate::{
operand::Operand,
place::{FieldIndex, Place, Projection, ProjectionKind},
},
def::DefId,
interpret::value::{Int, TryFromPrimitiveError},
reify::{
error::{field_index_too_large, local_variable_unmapped},
Expand Down Expand Up @@ -154,12 +158,42 @@ impl<'heap, A: Allocator, S: Allocator> Reifier<'_, '_, '_, '_, 'heap, A, S> {
}
}

pub(super) fn operand_qualified_variable(
&mut self,
node: Node<'heap>,
path: QualifiedPath<'heap>,
) -> Option<DefId> {
let synthetic = self.state.synthetics.find_or_insert(
self.context,
&mut self.state.diagnostics,
Spanned {
span: node.span,
value: node.id,
},
path,
)?;

// The value is called as a thunk, and therefore must be generated as such
let thunk = synthetic.thunk(self.context.bodies, self.context.mir.heap);
Some(thunk)
}

pub(super) fn operand(&mut self, node: Node<'heap>) -> Operand<'heap> {
match node.kind {
NodeKind::Variable(Variable::Qualified(_)) => {
self.state
.diagnostics
.push(external_modules_unsupported(node.span).generalize());
NodeKind::Variable(Variable::Qualified(variable)) => {
let diagnostics = self.state.diagnostics.critical();

if let Some(thunk) = self.operand_qualified_variable(node, variable.path) {
return Operand::Constant(Constant::FnPtr(thunk));
}

// The variable used is not a qualified variable, and therefore we must issue a
// diagnostic.
Comment thread
indietyp marked this conversation as resolved.
if diagnostics == self.state.diagnostics.critical() {
self.state
.diagnostics
.push(external_modules_unsupported(node.span).generalize());
}

// Return a bogus value so that lowering can continue
// In the future this would be a simple FnPtr
Expand Down
73 changes: 73 additions & 0 deletions libs/@local/hashql/mir/src/reify/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,3 +247,76 @@ pub(crate) fn fat_call_on_constant(span: SpanId) -> ReifyDiagnostic {

diagnostic
}

// Synthetic body diagnostics

/// ICE: monomorphized closure type has wrong parameter count for a synthetic binary
/// operation body.
#[coverage(off)]
pub(crate) fn synthetic_binary_arity_mismatch(
span: SpanId,
name: Symbol<'_>,
actual_params: usize,
) -> ReifyDiagnostic<Critical> {
let mut diagnostic = Diagnostic::new(ReifyDiagnosticCategory::TypeInvariant, Critical::BUG)
.primary(Label::new(
span,
format!("monomorphized type of `{name}` has {actual_params} parameters, expected 2"),
));

diagnostic.add_message(Message::note(
"binary operations require exactly 2 parameters after monomorphization",
));

diagnostic.add_message(Message::help(
"type checking should have ensured the correct arity before reification",
));

diagnostic
}

/// ICE: monomorphized closure type has wrong parameter count for a synthetic unary
/// operation body.
#[coverage(off)]
pub(crate) fn synthetic_unary_arity_mismatch(
span: SpanId,
name: Symbol<'_>,
actual_params: usize,
) -> ReifyDiagnostic<Critical> {
let mut diagnostic = Diagnostic::new(ReifyDiagnosticCategory::TypeInvariant, Critical::BUG)
.primary(Label::new(
span,
format!("monomorphized type of `{name}` has {actual_params} parameters, expected 1"),
));

diagnostic.add_message(Message::note(
"unary operations require exactly 1 parameter after monomorphization",
));

diagnostic.add_message(Message::help(
"type checking should have ensured the correct arity before reification",
));

diagnostic
}

/// Intrinsic cannot be used as a first-class value.
pub(crate) fn intrinsic_not_first_class(
span: SpanId,
name: Symbol<'_>,
) -> ReifyDiagnostic<Critical> {
let mut diagnostic =
Diagnostic::new(ReifyDiagnosticCategory::UnsupportedFeature, Critical::ERROR).primary(
Label::new(span, format!("`{name}` cannot be used as a value")),
);

diagnostic.add_message(Message::note(format!(
"`{name}` is a syntactic form that is only valid at a call site"
)));

diagnostic.add_message(Message::help(
"call this intrinsic directly instead of passing it as an argument",
));

diagnostic
}
6 changes: 6 additions & 0 deletions libs/@local/hashql/mir/src/reify/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ mod atom;
mod current;
mod error;
mod rvalue;
mod synthetic;
mod terminator;
mod transform;
mod types;
Expand Down Expand Up @@ -43,6 +44,7 @@ use self::{
error::{
expected_anf_thunk, expected_anf_variable, external_modules_unsupported, local_not_thunk,
},
synthetic::Synthetics,
types::unwrap_closure_type,
};
use crate::{
Expand Down Expand Up @@ -106,6 +108,9 @@ struct CrossCompileState<'heap, S: Allocator> {
/// Mapping of variable IDs to their thunk definitions.
thunks: Thunks<S>,

/// Synthetic bodies that have been generated during reification.
synthetics: Synthetics<S>,

/// Collection of diagnostics encountered during reification.
diagnostics: ReifyDiagnosticIssues,

Expand Down Expand Up @@ -506,6 +511,7 @@ pub fn from_hir<'heap, A: Allocator, S: Allocator + Clone>(
};
let mut state = CrossCompileState {
thunks,
synthetics: Synthetics::new_in(context.scratch.clone()),
ctor: FastHashMap::default(),
diagnostics: ReifyDiagnosticIssues::new(),
var_pool: MixedBitSetPool::with_recycler_in(
Expand Down
57 changes: 57 additions & 0 deletions libs/@local/hashql/mir/src/reify/rvalue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use core::alloc::Allocator;

use hashql_core::{
id::{Id as _, IdVec},
span::Spanned,
symbol::sym,
r#type::{TypeBuilder, Typed, builder},
value::Primitive,
Expand All @@ -17,6 +18,7 @@ use hashql_hir::node::{
BinaryOperation, InputOperation, Operation, TypeConstructor, TypeOperation, UnaryOperation,
},
thunk::Thunk,
variable::Variable,
};

use super::{
Expand Down Expand Up @@ -200,6 +202,10 @@ impl<'mir, 'heap, A: Allocator, S: Allocator> Reifier<'_, 'mir, '_, '_, 'heap, A
function: Node<'heap>,
call_arguments: &[CallArgument<'heap>],
) -> RValue<'heap> {
if let Some(value) = self.rvalue_call_thin_specialize(function, call_arguments) {
return value;
}

let mut arguments = IdVec::with_capacity_in(call_arguments.len(), self.context.mir.heap);

for CallArgument { span: _, value } in call_arguments {
Expand All @@ -214,6 +220,57 @@ impl<'mir, 'heap, A: Allocator, S: Allocator> Reifier<'_, 'mir, '_, '_, 'heap, A
})
}

/// Attempts to beta-reduce a thin call to a qualified intrinsic into a closure aggregate.
///
/// The thunking phase wraps every qualified variable in `Call(Thin, Qualified(...), [])`.
/// When the target is a known intrinsic, this produces the closure aggregate directly
/// instead of going through a thunk body that later passes would eliminate.
///
/// Returns [`None`] if the call has arguments, the target is not a qualified variable,
/// or the path is not a known intrinsic.
fn rvalue_call_thin_specialize(
&mut self,
function: Node<'heap>,
call_arguments: &[CallArgument<'heap>],
) -> Option<RValue<'heap>> {
if !call_arguments.is_empty() {
return None;
}

let NodeKind::Variable(Variable::Qualified(variable)) = function.kind else {
return None;
};

let diagnostics = self.state.diagnostics.critical();

let Some(synthetic) = self.state.synthetics.find_or_insert(
self.context,
&mut self.state.diagnostics,
Spanned {
span: function.span,
value: function.id,
},
variable.path,
) else {
// A diagnostic was already emitted (e.g. non-first-classable intrinsic).
// Return a bogus value to prevent the fallback from emitting a duplicate.
if diagnostics != self.state.diagnostics.critical() {
return Some(RValue::Load(Operand::Constant(Constant::Unit)));
}

return None;
};

let mut operands = IdVec::with_capacity_in(2, self.context.mir.heap);
operands.push(Operand::Constant(Constant::FnPtr(synthetic.body)));
operands.push(Operand::Constant(Constant::Unit));

Some(RValue::Aggregate(Aggregate {
kind: AggregateKind::Closure,
operands,
}))
}

fn rvalue_call_fat(
&mut self,
function: Node<'heap>,
Expand Down
Loading
Loading