diff --git a/libs/@local/hashql/mir/src/body/operand.rs b/libs/@local/hashql/mir/src/body/operand.rs index b64ff6ac739..4190f286b7d 100644 --- a/libs/@local/hashql/mir/src/body/operand.rs +++ b/libs/@local/hashql/mir/src/body/operand.rs @@ -59,6 +59,12 @@ impl<'heap> Operand<'heap> { } } +impl From for Operand<'_> { + fn from(value: !) -> Self { + value + } +} + impl From for Operand<'_> { fn from(local: Local) -> Self { Operand::Place(Place::local(local)) diff --git a/libs/@local/hashql/mir/src/builder/body.rs b/libs/@local/hashql/mir/src/builder/body.rs index ea100b613c8..f8ed7ed4c77 100644 --- a/libs/@local/hashql/mir/src/builder/body.rs +++ b/libs/@local/hashql/mir/src/builder/body.rs @@ -311,7 +311,10 @@ macro_rules! body { (@type $types:ident; Int) => { $types.integer() }; - (@type $types:ident; ($($sub:tt),*)) => { + (@type $types:ident; ()) => { + $types.tuple([] as [hashql_core::r#type::TypeId; 0]) + }; + (@type $types:ident; ($($sub:tt),+)) => { $types.tuple([$($crate::builder::body!(@type $types; $sub)),*]) }; (@type $types:ident; ($($name:ident: $sub:tt),*)) => { diff --git a/libs/@local/hashql/mir/src/builder/rvalue.rs b/libs/@local/hashql/mir/src/builder/rvalue.rs index b3dd1d539f1..7abaf762b80 100644 --- a/libs/@local/hashql/mir/src/builder/rvalue.rs +++ b/libs/@local/hashql/mir/src/builder/rvalue.rs @@ -224,6 +224,11 @@ macro_rules! rvalue { rv.closure($def, $env) }; $payload; $($rest)*) }; + ($resume:path; $payload:tt; tuple; $($rest:tt)*) => { + $resume!(@rvalue |rv| { + rv.tuple([] as [!; 0]) + }; $payload; $($rest)*) + }; ($resume:path; $payload:tt; tuple $($members:tt),+; $($rest:tt)*) => { $resume!(@rvalue |rv| { let members = [$($crate::builder::_private::operand!(rv; $members)),*]; diff --git a/libs/@local/hashql/mir/src/lib.rs b/libs/@local/hashql/mir/src/lib.rs index 894d4610dd9..7c231a73525 100644 --- a/libs/@local/hashql/mir/src/lib.rs +++ b/libs/@local/hashql/mir/src/lib.rs @@ -14,16 +14,15 @@ // Library Features allocator_api, assert_matches, + binary_heap_drain_sorted, const_type_name, iter_array_chunks, iter_collect_into, iter_intersperse, + maybe_uninit_fill, + step_trait, string_from_utf8_lossy_owned, try_trait_v2, - step_trait, - maybe_uninit_fill, - binary_heap_into_iter_sorted, - binary_heap_drain_sorted, )] #![expect(clippy::indexing_slicing)] extern crate alloc; diff --git a/libs/@local/hashql/mir/src/pass/transform/inst_simplify/mod.rs b/libs/@local/hashql/mir/src/pass/transform/inst_simplify/mod.rs index ae485e57db4..b309bd9a8dd 100644 --- a/libs/@local/hashql/mir/src/pass/transform/inst_simplify/mod.rs +++ b/libs/@local/hashql/mir/src/pass/transform/inst_simplify/mod.rs @@ -107,7 +107,7 @@ use crate::{ location::Location, operand::Operand, place::Place, - rvalue::{BinOp, Binary, RValue, Unary}, + rvalue::{Aggregate, AggregateKind, BinOp, Binary, RValue, Unary}, statement::Assign, }, context::MirContext, @@ -503,6 +503,19 @@ impl<'heap, A: Allocator> VisitorMut<'heap> for InstSimplifyVisitor<'_, 'heap, A Ok(()) } + fn visit_rvalue_aggregate( + &mut self, + _: Location, + aggregate: &mut Aggregate<'heap>, + ) -> Self::Result<()> { + // Specialize into a unit if it's an empty tuple + if aggregate.kind == AggregateKind::Tuple && aggregate.operands.is_empty() { + self.trampoline = Some(RValue::Load(Operand::Constant(Constant::Unit))); + } + + Ok(()) + } + fn visit_statement_assign( &mut self, location: Location, diff --git a/libs/@local/hashql/mir/src/pass/transform/inst_simplify/tests.rs b/libs/@local/hashql/mir/src/pass/transform/inst_simplify/tests.rs index ffbd46d8b1c..b42961a9811 100644 --- a/libs/@local/hashql/mir/src/pass/transform/inst_simplify/tests.rs +++ b/libs/@local/hashql/mir/src/pass/transform/inst_simplify/tests.rs @@ -1,5 +1,5 @@ #![expect(clippy::min_ident_chars, reason = "tests")] -use std::{io::Write as _, path::PathBuf}; +use std::{assert_matches::assert_matches, io::Write as _, path::PathBuf}; use bstr::ByteVec as _; use hashql_core::{ @@ -12,8 +12,20 @@ use insta::{Settings, assert_snapshot}; use super::InstSimplify; use crate::{ - body::Body, builder::body, context::MirContext, def::DefIdSlice, intern::Interner, - pass::TransformPass as _, pretty::TextFormat, + body::{ + Body, + basic_block::BasicBlockId, + constant::Constant, + operand::Operand, + rvalue::RValue, + statement::{Assign, StatementKind}, + }, + builder::body, + context::MirContext, + def::DefIdSlice, + intern::Interner, + pass::{Changed, TransformPass as _}, + pretty::TextFormat, }; #[track_caller] @@ -64,10 +76,6 @@ fn assert_inst_simplify_pass<'heap>( assert_snapshot!(name, value); } -// ============================================================================= -// Constant Folding (Bitwise on integers, Unary - not in source language) -// ============================================================================= - /// Tests constant folding for bitwise AND on integers. #[test] fn const_fold_bit_and() { @@ -180,10 +188,6 @@ fn const_fold_unary_neg() { ); } -// ============================================================================= -// Bitwise Identity on Integers (x | 0 => x - not in source language) -// ============================================================================= - /// Tests identity simplification for bitwise OR with zero. #[test] fn identity_bit_or_zero() { @@ -212,10 +216,6 @@ fn identity_bit_or_zero() { ); } -// ============================================================================= -// Identical Operand Patterns (BitAnd/BitOr on integers - not in source) -// ============================================================================= - /// Tests idempotent simplification for bitwise AND with identical operands. #[test] fn identical_operand_bit_and() { @@ -272,10 +272,6 @@ fn identical_operand_bit_or() { ); } -// ============================================================================= -// Block Parameter Propagation (requires CFG control) -// ============================================================================= - /// Tests constant propagation through block params with single predecessor. #[test] fn block_param_single_predecessor() { @@ -381,10 +377,6 @@ fn block_param_predecessors_disagree() { ); } -// ============================================================================= -// Idempotent to Constant Forwarding (requires bitwise op) -// ============================================================================= - /// Tests that idempotent simplification propagates constants through the result. #[test] fn idempotent_to_const_forwarding() { @@ -414,3 +406,38 @@ fn idempotent_to_const_forwarding() { }, ); } + +/// Tests that an empty tuple aggregate is simplified to a unit constant. +#[test] +fn empty_tuple_to_unit() { + let heap = Heap::new(); + let interner = Interner::new(&heap); + let env = Environment::new(&heap); + + let mut body = body!(interner, env; fn@0/0 -> () { + decl result: (); + + bb0() { + result = tuple; + return result; + } + }); + + let changed = InstSimplify::new().run( + &mut MirContext { + heap: &heap, + env: &env, + interner: &interner, + diagnostics: DiagnosticIssues::new(), + }, + &mut body, + ); + assert_eq!(changed, Changed::Yes); + assert_matches!( + body.basic_blocks[BasicBlockId::START].statements[0].kind, + StatementKind::Assign(Assign { + lhs: _, + rhs: RValue::Load(Operand::Constant(Constant::Unit)) + }) + ); +} diff --git a/libs/@local/hashql/mir/tests/ui/pass/inline/closure-inline.stdout b/libs/@local/hashql/mir/tests/ui/pass/inline/closure-inline.stdout index b6c1f3a900d..1ea3c9650a0 100644 --- a/libs/@local/hashql/mir/tests/ui/pass/inline/closure-inline.stdout +++ b/libs/@local/hashql/mir/tests/ui/pass/inline/closure-inline.stdout @@ -48,11 +48,9 @@ fn {closure#3}(%0: ()) -> ::graph::TimeAxis { thunk get_axis:0() -> () -> ::graph::TimeAxis { let %0: () -> ::graph::TimeAxis - let %1: () bb0(): { - %1 = () - %0 = closure(({closure#3} as FnPtr), %1) + %0 = closure(({closure#3} as FnPtr), ()) return %0 } @@ -60,11 +58,9 @@ thunk get_axis:0() -> () -> ::graph::TimeAxis { *thunk {thunk#2}() -> ::graph::TimeAxis { let %0: ::graph::TimeAxis - let %1: () bb0(): { - %1 = () - %0 = apply ({closure#3} as FnPtr) %1 + %0 = apply ({closure#3} as FnPtr) () return %0 } @@ -84,11 +80,9 @@ fn {closure#3}(%0: ()) -> ::graph::TimeAxis { thunk get_axis:0() -> () -> ::graph::TimeAxis { let %0: () -> ::graph::TimeAxis - let %1: () bb0(): { - %1 = () - %0 = closure(({closure#3} as FnPtr), %1) + %0 = closure(({closure#3} as FnPtr), ()) return %0 } @@ -97,12 +91,10 @@ thunk get_axis:0() -> () -> ::graph::TimeAxis { *thunk {thunk#2}() -> ::graph::TimeAxis { let %0: ::graph::TimeAxis let %1: () - let %2: () - let %3: ::graph::TimeAxis + let %2: ::graph::TimeAxis bb0(): { %1 = () - %2 = %1 goto -> bb2() } @@ -112,8 +104,8 @@ thunk get_axis:0() -> () -> ::graph::TimeAxis { } bb2(): { - %3 = input LOAD axis + %2 = input LOAD axis - goto -> bb1(%3) + goto -> bb1(%2) } } \ No newline at end of file diff --git a/libs/@local/hashql/mir/tests/ui/pass/inline/heuristic-inline.stdout b/libs/@local/hashql/mir/tests/ui/pass/inline/heuristic-inline.stdout index 21c4c182f62..3f6e4669586 100644 --- a/libs/@local/hashql/mir/tests/ui/pass/inline/heuristic-inline.stdout +++ b/libs/@local/hashql/mir/tests/ui/pass/inline/heuristic-inline.stdout @@ -90,11 +90,9 @@ fn {closure#7}(%0: ()) -> Boolean { thunk load_flag:0() -> () -> Boolean { let %0: () -> Boolean - let %1: () bb0(): { - %1 = () - %0 = closure(({closure#7} as FnPtr), %1) + %0 = closure(({closure#7} as FnPtr), ()) return %0 } @@ -103,19 +101,15 @@ thunk load_flag:0() -> () -> Boolean { fn {closure#10}(%0: ()) -> Boolean { let %1: Boolean let %2: Boolean - let %3: () - let %4: () bb0(): { - %3 = () - %1 = apply ({closure#7} as FnPtr) %3 + %1 = apply ({closure#7} as FnPtr) () switchInt(%1) -> [0: bb2(), 1: bb1()] } bb1(): { - %4 = () - %2 = apply ({closure#7} as FnPtr) %4 + %2 = apply ({closure#7} as FnPtr) () return %2 } @@ -127,11 +121,9 @@ fn {closure#10}(%0: ()) -> Boolean { thunk use_flag:0() -> () -> Boolean { let %0: () -> Boolean - let %1: () bb0(): { - %1 = () - %0 = closure(({closure#10} as FnPtr), %1) + %0 = closure(({closure#10} as FnPtr), ()) return %0 } @@ -139,11 +131,9 @@ thunk use_flag:0() -> () -> Boolean { *thunk {thunk#6}() -> Boolean { let %0: Boolean - let %1: () bb0(): { - %1 = () - %0 = apply ({closure#10} as FnPtr) %1 + %0 = apply ({closure#10} as FnPtr) () return %0 } @@ -163,11 +153,9 @@ fn {closure#7}(%0: ()) -> Boolean { thunk load_flag:0() -> () -> Boolean { let %0: () -> Boolean - let %1: () bb0(): { - %1 = () - %0 = closure(({closure#7} as FnPtr), %1) + %0 = closure(({closure#7} as FnPtr), ()) return %0 } @@ -177,22 +165,18 @@ fn {closure#10}(%0: ()) -> Boolean { let %1: Boolean let %2: Boolean let %3: () - let %4: () + let %4: Boolean let %5: () let %6: Boolean - let %7: () - let %8: Boolean bb0(): { - %3 = () - %7 = %3 + %5 = () goto -> bb6() } bb1(): { - %4 = () - %5 = %4 + %3 = () goto -> bb4() } @@ -206,9 +190,9 @@ fn {closure#10}(%0: ()) -> Boolean { } bb4(): { - %6 = input LOAD flag + %4 = input LOAD flag - goto -> bb3(%6) + goto -> bb3(%4) } bb5(%1): { @@ -216,19 +200,17 @@ fn {closure#10}(%0: ()) -> Boolean { } bb6(): { - %8 = input LOAD flag + %6 = input LOAD flag - goto -> bb5(%8) + goto -> bb5(%6) } } thunk use_flag:0() -> () -> Boolean { let %0: () -> Boolean - let %1: () bb0(): { - %1 = () - %0 = closure(({closure#10} as FnPtr), %1) + %0 = closure(({closure#10} as FnPtr), ()) return %0 } @@ -236,11 +218,9 @@ thunk use_flag:0() -> () -> Boolean { *thunk {thunk#6}() -> Boolean { let %0: Boolean - let %1: () bb0(): { - %1 = () - %0 = apply ({closure#10} as FnPtr) %1 + %0 = apply ({closure#10} as FnPtr) () return %0 } diff --git a/libs/@local/hashql/mir/tests/ui/pass/inline/too-large-to-inline.stdout b/libs/@local/hashql/mir/tests/ui/pass/inline/too-large-to-inline.stdout index f67b05b55cd..6de99535e24 100644 --- a/libs/@local/hashql/mir/tests/ui/pass/inline/too-large-to-inline.stdout +++ b/libs/@local/hashql/mir/tests/ui/pass/inline/too-large-to-inline.stdout @@ -72,11 +72,9 @@ fn {closure#5}(%0: ()) -> Boolean { thunk big_func:0() -> () -> Boolean { let %0: () -> Boolean - let %1: () bb0(): { - %1 = () - %0 = closure(({closure#5} as FnPtr), %1) + %0 = closure(({closure#5} as FnPtr), ()) return %0 } @@ -84,11 +82,9 @@ thunk big_func:0() -> () -> Boolean { fn {closure#7}(%0: ()) -> Boolean { let %1: Boolean - let %2: () bb0(): { - %2 = () - %1 = apply ({closure#5} as FnPtr) %2 + %1 = apply ({closure#5} as FnPtr) () return %1 } @@ -96,11 +92,9 @@ fn {closure#7}(%0: ()) -> Boolean { thunk caller:0() -> () -> Boolean { let %0: () -> Boolean - let %1: () bb0(): { - %1 = () - %0 = closure(({closure#7} as FnPtr), %1) + %0 = closure(({closure#7} as FnPtr), ()) return %0 } @@ -108,11 +102,9 @@ thunk caller:0() -> () -> Boolean { *thunk {thunk#4}() -> Boolean { let %0: Boolean - let %1: () bb0(): { - %1 = () - %0 = apply ({closure#5} as FnPtr) %1 + %0 = apply ({closure#5} as FnPtr) () return %0 } @@ -132,11 +124,9 @@ fn {closure#5}(%0: ()) -> Boolean { thunk big_func:0() -> () -> Boolean { let %0: () -> Boolean - let %1: () bb0(): { - %1 = () - %0 = closure(({closure#5} as FnPtr), %1) + %0 = closure(({closure#5} as FnPtr), ()) return %0 } @@ -144,11 +134,9 @@ thunk big_func:0() -> () -> Boolean { fn {closure#7}(%0: ()) -> Boolean { let %1: Boolean - let %2: () bb0(): { - %2 = () - %1 = apply ({closure#5} as FnPtr) %2 + %1 = apply ({closure#5} as FnPtr) () return %1 } @@ -156,11 +144,9 @@ fn {closure#7}(%0: ()) -> Boolean { thunk caller:0() -> () -> Boolean { let %0: () -> Boolean - let %1: () bb0(): { - %1 = () - %0 = closure(({closure#7} as FnPtr), %1) + %0 = closure(({closure#7} as FnPtr), ()) return %0 } @@ -168,11 +154,9 @@ thunk caller:0() -> () -> Boolean { *thunk {thunk#4}() -> Boolean { let %0: Boolean - let %1: () bb0(): { - %1 = () - %0 = apply ({closure#5} as FnPtr) %1 + %0 = apply ({closure#5} as FnPtr) () return %0 } diff --git a/libs/@local/hashql/mir/tests/ui/pass/pre_inlining/closure-with-dead-branch.stdout b/libs/@local/hashql/mir/tests/ui/pass/pre_inlining/closure-with-dead-branch.stdout index 3c0cc3a0675..8f3929550d0 100644 --- a/libs/@local/hashql/mir/tests/ui/pass/pre_inlining/closure-with-dead-branch.stdout +++ b/libs/@local/hashql/mir/tests/ui/pass/pre_inlining/closure-with-dead-branch.stdout @@ -63,11 +63,9 @@ fn {closure#4}(%0: (), %1: Boolean) -> Boolean { thunk identity:0() -> (Boolean) -> Boolean { let %0: (Boolean) -> Boolean - let %1: () bb0(): { - %1 = () - %0 = closure(({closure#4} as FnPtr), %1) + %0 = closure(({closure#4} as FnPtr), ()) return %0 } diff --git a/libs/@local/hashql/mir/tests/ui/pass/pre_inlining/thunk-with-dead-code.stdout b/libs/@local/hashql/mir/tests/ui/pass/pre_inlining/thunk-with-dead-code.stdout index 76085b26b55..c23b973b61c 100644 --- a/libs/@local/hashql/mir/tests/ui/pass/pre_inlining/thunk-with-dead-code.stdout +++ b/libs/@local/hashql/mir/tests/ui/pass/pre_inlining/thunk-with-dead-code.stdout @@ -69,11 +69,9 @@ fn {closure#4}(%0: ()) -> Boolean { thunk get_flag:0() -> () -> Boolean { let %0: () -> Boolean - let %1: () bb0(): { - %1 = () - %0 = closure(({closure#4} as FnPtr), %1) + %0 = closure(({closure#4} as FnPtr), ()) return %0 }