Skip to content

Commit 361796e

Browse files
committed
Fix unbounded premiums and validate finite bitset domain
1 parent 5e31131 commit 361796e

File tree

2 files changed

+50
-2
lines changed

2 files changed

+50
-2
lines changed

libs/@local/hashql/core/src/id/bit_vec/finite.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,8 @@ impl<I: Id, T: FiniteBitSetIntegral> FiniteBitSet<I, T> {
230230
I: [const] Id,
231231
T: [const] FiniteBitSetIntegral,
232232
{
233+
assert!(domain_size <= T::MAX_DOMAIN_SIZE as usize);
234+
233235
let Some((start, end)) = inclusive_start_end(bounds, domain_size) else {
234236
return;
235237
};
@@ -619,6 +621,13 @@ mod tests {
619621
}
620622
}
621623

624+
#[test]
625+
#[should_panic(expected = "assertion failed")]
626+
fn insert_range_domain_size_exceeds_capacity_panics() {
627+
let mut set: FiniteBitSet<TestId, u8> = FiniteBitSet::new_empty(8);
628+
set.insert_range(.., 9);
629+
}
630+
622631
#[test]
623632
fn union_combines_bits() {
624633
let mut a: FiniteBitSet<TestId, u8> = FiniteBitSet::new_empty(8);

libs/@local/hashql/mir/src/pass/execution/cost/analysis.rs

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use core::alloc::Allocator;
22

3-
use super::{ApproxCost, StatementCostVec};
3+
use super::{ApproxCost, Cost, StatementCostVec};
44
use crate::{
55
body::basic_block::{BasicBlock, BasicBlockId, BasicBlockSlice, BasicBlockVec},
66
pass::{
@@ -124,7 +124,7 @@ impl<A: Allocator> BasicBlockCostAnalysis<'_, A> {
124124
let load = range
125125
.saturating_mul(config.target_multiplier[target].get())
126126
.midpoint()
127-
.map_or(ApproxCost::INF, From::from);
127+
.map_or_else(|| ApproxCost::from(Cost::MAX), ApproxCost::from);
128128

129129
BasicBlockTargetCost { base, load }
130130
}
@@ -511,6 +511,45 @@ mod tests {
511511
assert!(interpreter_cost > interpreter_base);
512512
}
513513

514+
/// Unbounded transfer-size estimates should stay expensive but finite, so they remain
515+
/// valid candidates for solver search.
516+
#[test]
517+
fn unbounded_path_premium_stays_finite() {
518+
let heap = Heap::new();
519+
let interner = Interner::new(&heap);
520+
let env = Environment::new(&heap);
521+
522+
let body = body!(interner, env; [graph::read::filter]@0/2 -> ? {
523+
decl env: (), vertex: [Opaque sym::path::Entity; ?], val: ?;
524+
@proj properties = vertex.properties: ?;
525+
526+
bb0() {
527+
val = load properties;
528+
return val;
529+
}
530+
});
531+
532+
let targets = make_targets(&body, all_targets());
533+
let targets = BasicBlockSlice::from_raw(&targets);
534+
535+
let costs: TargetArray<StatementCostVec<Global>> =
536+
TargetArray::from_fn(|_| StatementCostVec::from_iter([1].into_iter(), Global));
537+
538+
let analysis = BasicBlockCostAnalysis {
539+
vertex: VertexType::Entity,
540+
assignments: targets,
541+
costs: &costs,
542+
};
543+
544+
let result = analysis.analyze_in(&default_config(), &body.basic_blocks, Global);
545+
let bb0 = BasicBlockId::new(0);
546+
547+
let interpreter_cost = result.cost(bb0, TargetId::Interpreter);
548+
let interpreter_base = costs[TargetId::Interpreter].sum_approx(bb0);
549+
assert!(interpreter_cost > interpreter_base);
550+
assert!(interpreter_cost.is_finite());
551+
}
552+
514553
/// Paths across multiple blocks are analyzed independently per block.
515554
#[test]
516555
fn paths_across_blocks_independent() {

0 commit comments

Comments
 (0)