Skip to main content

solar_codegen/transform/
check_elim.rs

1//! Range-based overflow-check elimination.
2//!
3//! Checked 0.8.x arithmetic lowers every `add`/`sub`/`mul` with a wrap test
4//! that branches to a `Panic(0x11)` block. Most of those tests are dominated
5//! by a guard that already proves the operation cannot wrap:
6//!
7//! - a loop header guard `i < n` proves `i <= 2^256 - 2`, so the `i + 1` increment cannot overflow
8//!   and its `lt i+1, i` check is constant false;
9//! - `require(b <= a)` proves the following `a - b` cannot underflow, so its `lt a, b` check is
10//!   constant false;
11//! - a constant bound `x < C` proves `x * K` or `x + K` cannot wrap whenever `(C-1) * K` (resp.
12//!   `(C-1) + K`) fits in 256 bits, so the `div`-based mul check or the add check folds.
13//!
14//! The pass walks the dominator tree. On entry to a block with a unique
15//! predecessor ending in a two-way branch, it records what the branch
16//! condition implies on that edge: value ranges refined by constants
17//! (`x < C` => `x` in `[0, C-1]`) and relational predicates between SSA
18//! values (`!(a < b)` => `b <= a`). Facts attach to SSA values, which are
19//! never redefined, so a fact derived on a dominating edge holds in every
20//! dominated block. Branch conditions are then evaluated against the
21//! recorded facts with checked 256-bit arithmetic; a condition that is
22//! provably constant folds the branch to an unconditional jump, and the dead
23//! panic block is cleaned up by the existing CFG passes. Anything that is
24//! not provable is left untouched.
25
26use crate::{
27    analysis::CfgInfo,
28    mir::{
29        BlockId, Function, InstKind, Terminator, Value, ValueId, utils::repair_reachability_phis,
30    },
31    pass::FunctionPass,
32};
33use alloy_primitives::U256;
34use solar_data_structures::map::{FxHashMap, FxHashSet};
35
36/// Maximum recursion depth when evaluating value ranges and conditions.
37const MAX_DEPTH: usize = 12;
38
39/// Statistics from check elimination.
40#[derive(Debug, Default, Clone)]
41pub struct CheckElimStats {
42    /// Number of branches folded to unconditional jumps.
43    pub branches_folded: usize,
44}
45
46/// Function pass adapter for range-based overflow-check elimination.
47pub struct CheckElimPass;
48
49impl FunctionPass for CheckElimPass {
50    fn name(&self) -> &str {
51        "check-elim"
52    }
53
54    fn run_on_function(&mut self, func: &mut Function) -> bool {
55        CheckEliminator::new().run(func) != 0
56    }
57}
58
59/// An inclusive unsigned 256-bit interval.
60#[derive(Clone, Copy, Debug, PartialEq, Eq)]
61struct Range {
62    lo: U256,
63    hi: U256,
64}
65
66impl Range {
67    const FULL: Self = Self { lo: U256::ZERO, hi: U256::MAX };
68
69    const fn new(lo: U256, hi: U256) -> Self {
70        Self { lo, hi }
71    }
72
73    fn singleton(value: U256) -> Self {
74        Self { lo: value, hi: value }
75    }
76
77    fn is_singleton(self) -> bool {
78        self.lo == self.hi
79    }
80
81    /// Intersects two ranges. Returns `None` when the intersection is empty,
82    /// which means the current program point is dynamically unreachable.
83    fn intersect(self, other: Self) -> Option<Self> {
84        let lo = self.lo.max(other.lo);
85        let hi = self.hi.min(other.hi);
86        (lo <= hi).then_some(Self { lo, hi })
87    }
88
89    fn union(self, other: Self) -> Self {
90        Self { lo: self.lo.min(other.lo), hi: self.hi.max(other.hi) }
91    }
92}
93
94/// A relational predicate between two SSA values.
95///
96/// `Lt(a, b)` means `a < b` and `Le(a, b)` means `a <= b`, both unsigned.
97/// `Eq` and `Ne` are stored with operands ordered by index.
98#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
99enum Relation {
100    Lt(ValueId, ValueId),
101    Le(ValueId, ValueId),
102    Eq(ValueId, ValueId),
103    Ne(ValueId, ValueId),
104}
105
106fn ordered(a: ValueId, b: ValueId) -> (ValueId, ValueId) {
107    if a.index() <= b.index() { (a, b) } else { (b, a) }
108}
109
110/// Range-based overflow-check eliminator.
111#[derive(Default)]
112pub struct CheckEliminator {
113    /// Statistics from the last run.
114    pub stats: CheckElimStats,
115    ranges: FxHashMap<ValueId, Range>,
116    relations: FxHashSet<Relation>,
117    range_undo: Vec<(ValueId, Option<Range>)>,
118    relation_undo: Vec<Relation>,
119}
120
121impl CheckEliminator {
122    /// Creates a new check eliminator.
123    #[must_use]
124    pub fn new() -> Self {
125        Self::default()
126    }
127
128    /// Runs check elimination on a function. Returns the number of folded
129    /// branches.
130    pub fn run(&mut self, func: &mut Function) -> usize {
131        self.stats = CheckElimStats::default();
132        if func.blocks.is_empty() {
133            return 0;
134        }
135
136        let cfg = CfgInfo::new(func);
137
138        // Predecessors recomputed from reachable terminators: facts must only
139        // come from edges that can actually execute.
140        let mut preds: Vec<Vec<BlockId>> = vec![Vec::new(); func.blocks.len()];
141        for &block in cfg.rpo() {
142            for &succ in cfg.successors(block) {
143                preds[succ.index()].push(block);
144            }
145        }
146
147        let folds = self.collect_folds(func, &cfg, &preds);
148        self.ranges.clear();
149        self.relations.clear();
150        self.range_undo.clear();
151        self.relation_undo.clear();
152
153        if folds.is_empty() {
154            return 0;
155        }
156        for &(block, keep) in &folds {
157            func.blocks[block].terminator = Some(Terminator::Jump(keep));
158        }
159        repair_reachability_phis(func);
160        self.stats.branches_folded = folds.len();
161        folds.len()
162    }
163
164    /// Walks the dominator tree, recording edge facts and evaluating branch
165    /// conditions. Returns `(block, kept_target)` folds to apply.
166    fn collect_folds(
167        &mut self,
168        func: &Function,
169        cfg: &CfgInfo,
170        preds: &[Vec<BlockId>],
171    ) -> Vec<(BlockId, BlockId)> {
172        enum Walk {
173            Enter(BlockId),
174            Exit { range_mark: usize, relation_mark: usize },
175        }
176
177        let mut folds = Vec::new();
178        let mut stack = vec![Walk::Enter(func.entry_block)];
179        while let Some(item) = stack.pop() {
180            match item {
181                Walk::Exit { range_mark, relation_mark } => {
182                    while self.range_undo.len() > range_mark {
183                        let (value, old) = self.range_undo.pop().expect("checked len");
184                        match old {
185                            Some(range) => self.ranges.insert(value, range),
186                            None => self.ranges.remove(&value),
187                        };
188                    }
189                    while self.relation_undo.len() > relation_mark {
190                        let relation = self.relation_undo.pop().expect("checked len");
191                        self.relations.remove(&relation);
192                    }
193                }
194                Walk::Enter(block) => {
195                    stack.push(Walk::Exit {
196                        range_mark: self.range_undo.len(),
197                        relation_mark: self.relation_undo.len(),
198                    });
199
200                    if let Some((condition, is_true)) = dominating_edge_fact(func, preds, block) {
201                        self.assume(func, condition, is_true, MAX_DEPTH);
202                    }
203
204                    if let Some(Terminator::Branch { condition, then_block, else_block }) =
205                        func.blocks[block].terminator.as_ref()
206                        && then_block != else_block
207                        && let Some(truth) = self.eval_truth(func, *condition, MAX_DEPTH)
208                    {
209                        folds.push((block, if truth { *then_block } else { *else_block }));
210                    }
211
212                    for &child in cfg.dominators().children(block) {
213                        stack.push(Walk::Enter(child));
214                    }
215                }
216            }
217        }
218        folds
219    }
220
221    // === Fact recording ===
222
223    /// Records the consequences of `value` being `truth` on the current
224    /// dominator subtree.
225    fn assume(&mut self, func: &Function, value: ValueId, truth: bool, depth: usize) {
226        // The condition value itself is now known nonzero or zero.
227        if truth {
228            self.narrow(value, Range::new(U256::from(1), U256::MAX));
229        } else {
230            self.narrow(value, Range::singleton(U256::ZERO));
231        }
232        let Some(depth) = depth.checked_sub(1) else { return };
233        let Some(kind) = inst_kind(func, value) else { return };
234        match *kind {
235            InstKind::IsZero(a) => self.assume(func, a, !truth, depth),
236            InstKind::Lt(a, b) => self.assume_lt(func, a, b, truth, depth),
237            InstKind::Gt(a, b) => self.assume_lt(func, b, a, truth, depth),
238            InstKind::Eq(a, b) => self.assume_eq(func, a, b, truth, depth),
239            // `sub a, b` is nonzero iff `a != b`.
240            InstKind::Sub(a, b) | InstKind::Xor(a, b) => self.assume_eq(func, a, b, !truth, depth),
241            // `and a, b != 0` implies both operands are nonzero.
242            InstKind::And(a, b) if truth => {
243                self.assume(func, a, true, depth);
244                self.assume(func, b, true, depth);
245            }
246            // `or a, b == 0` implies both operands are zero.
247            InstKind::Or(a, b) if !truth => {
248                self.assume(func, a, false, depth);
249                self.assume(func, b, false, depth);
250            }
251            _ => {}
252        }
253    }
254
255    /// Records the consequences of `(a < b) == truth` (unsigned).
256    fn assume_lt(&mut self, func: &Function, a: ValueId, b: ValueId, truth: bool, depth: usize) {
257        if truth {
258            self.add_relation(Relation::Lt(a, b));
259            // a < b <= hi(b)  =>  a <= hi(b) - 1
260            let hi_b = self.range_of(func, b, depth).hi;
261            if hi_b > U256::ZERO {
262                self.narrow(a, Range::new(U256::ZERO, hi_b - U256::from(1)));
263            }
264            // lo(a) <= a < b  =>  b >= lo(a) + 1
265            let lo_a = self.range_of(func, a, depth).lo;
266            if lo_a < U256::MAX {
267                self.narrow(b, Range::new(lo_a + U256::from(1), U256::MAX));
268            }
269        } else {
270            // !(a < b)  =>  b <= a
271            self.add_relation(Relation::Le(b, a));
272            let lo_b = self.range_of(func, b, depth).lo;
273            self.narrow(a, Range::new(lo_b, U256::MAX));
274            let hi_a = self.range_of(func, a, depth).hi;
275            self.narrow(b, Range::new(U256::ZERO, hi_a));
276        }
277    }
278
279    /// Records the consequences of `(a == b) == truth`.
280    fn assume_eq(&mut self, func: &Function, a: ValueId, b: ValueId, truth: bool, depth: usize) {
281        let (x, y) = ordered(a, b);
282        if truth {
283            self.add_relation(Relation::Eq(x, y));
284            let range = self.range_of(func, a, depth);
285            self.narrow(b, range);
286            let range = self.range_of(func, b, depth);
287            self.narrow(a, range);
288        } else {
289            self.add_relation(Relation::Ne(x, y));
290            self.exclude_boundary(func, a, b, depth);
291            self.exclude_boundary(func, b, a, depth);
292        }
293    }
294
295    /// Given `a != b` with `b` a known singleton at a boundary of `a`'s
296    /// range, shrinks `a`'s range by one.
297    fn exclude_boundary(&mut self, func: &Function, a: ValueId, b: ValueId, depth: usize) {
298        let rb = self.range_of(func, b, depth);
299        if !rb.is_singleton() {
300            return;
301        }
302        let ra = self.range_of(func, a, depth);
303        if ra.is_singleton() {
304            return;
305        }
306        if ra.lo == rb.lo {
307            self.narrow(a, Range::new(ra.lo + U256::from(1), ra.hi));
308        } else if ra.hi == rb.hi {
309            self.narrow(a, Range::new(ra.lo, ra.hi - U256::from(1)));
310        }
311    }
312
313    /// Intersects the recorded range of `value` with `range`, logging the
314    /// previous entry for scope restoration. Contradictions (an empty
315    /// intersection means the current edge is dynamically dead) are skipped:
316    /// keeping the weaker fact is always sound.
317    fn narrow(&mut self, value: ValueId, range: Range) {
318        let old = self.ranges.get(&value).copied();
319        let Some(new) = old.unwrap_or(Range::FULL).intersect(range) else { return };
320        if Some(new) == old {
321            return;
322        }
323        self.range_undo.push((value, old));
324        self.ranges.insert(value, new);
325    }
326
327    fn add_relation(&mut self, relation: Relation) {
328        if self.relations.insert(relation) {
329            self.relation_undo.push(relation);
330        }
331    }
332
333    fn has_relation(&self, relation: Relation) -> bool {
334        self.relations.contains(&relation)
335    }
336
337    // === Evaluation ===
338
339    /// Computes a sound overapproximation of the values `value` can take at
340    /// the current program point.
341    fn range_of(&mut self, func: &Function, value: ValueId, depth: usize) -> Range {
342        if let Some(constant) = const_of(func, value) {
343            return Range::singleton(constant);
344        }
345        let mut range = self.ranges.get(&value).copied().unwrap_or(Range::FULL);
346        let Some(depth) = depth.checked_sub(1) else { return range };
347        let Some(kind) = inst_kind(func, value) else { return range };
348        let derived = match *kind {
349            InstKind::Add(a, b) => {
350                let ra = self.range_of(func, a, depth);
351                let rb = self.range_of(func, b, depth);
352                match ra.hi.checked_add(rb.hi) {
353                    Some(hi) => Range::new(ra.lo.wrapping_add(rb.lo), hi),
354                    None => Range::FULL,
355                }
356            }
357            InstKind::Sub(a, b) => {
358                let ra = self.range_of(func, a, depth);
359                let rb = self.range_of(func, b, depth);
360                if ra.lo >= rb.hi { Range::new(ra.lo - rb.hi, ra.hi - rb.lo) } else { Range::FULL }
361            }
362            InstKind::Mul(a, b) => {
363                let ra = self.range_of(func, a, depth);
364                let rb = self.range_of(func, b, depth);
365                match ra.hi.checked_mul(rb.hi) {
366                    Some(hi) => Range::new(ra.lo.wrapping_mul(rb.lo), hi),
367                    None => Range::FULL,
368                }
369            }
370            InstKind::Div(a, b) => {
371                // EVM division by zero yields zero, so the result never
372                // exceeds the dividend.
373                let ra = self.range_of(func, a, depth);
374                let rb = self.range_of(func, b, depth);
375                let lo = if rb.lo > U256::ZERO { ra.lo / rb.hi } else { U256::ZERO };
376                Range::new(lo, ra.hi)
377            }
378            InstKind::Mod(a, b) => {
379                // EVM modulo by zero yields zero; otherwise the result is
380                // less than the divisor and never exceeds the dividend.
381                let ra = self.range_of(func, a, depth);
382                let rb = self.range_of(func, b, depth);
383                let bound = if rb.hi > U256::ZERO { rb.hi - U256::from(1) } else { U256::ZERO };
384                Range::new(U256::ZERO, bound.min(ra.hi))
385            }
386            InstKind::And(a, b) => {
387                let ra = self.range_of(func, a, depth);
388                let rb = self.range_of(func, b, depth);
389                Range::new(U256::ZERO, ra.hi.min(rb.hi))
390            }
391            InstKind::Lt(..)
392            | InstKind::Gt(..)
393            | InstKind::SLt(..)
394            | InstKind::SGt(..)
395            | InstKind::Eq(..)
396            | InstKind::IsZero(..) => match self.eval_truth(func, value, depth) {
397                Some(true) => Range::singleton(U256::from(1)),
398                Some(false) => Range::singleton(U256::ZERO),
399                None => Range::new(U256::ZERO, U256::from(1)),
400            },
401            InstKind::Select(condition, then_value, else_value) => {
402                match self.eval_truth(func, condition, depth) {
403                    Some(true) => self.range_of(func, then_value, depth),
404                    Some(false) => self.range_of(func, else_value, depth),
405                    None => self
406                        .range_of(func, then_value, depth)
407                        .union(self.range_of(func, else_value, depth)),
408                }
409            }
410            _ => Range::FULL,
411        };
412        // Both bounds are sound, so their intersection is too. An empty
413        // intersection means this point is dynamically unreachable; keep the
414        // recorded fact in that case.
415        if let Some(intersection) = range.intersect(derived) {
416            range = intersection;
417        }
418        range
419    }
420
421    /// Evaluates the truthiness (`!= 0`) of `value`, if provable.
422    fn eval_truth(&mut self, func: &Function, value: ValueId, depth: usize) -> Option<bool> {
423        if let Some(constant) = const_of(func, value) {
424            return Some(!constant.is_zero());
425        }
426        if let Some(range) = self.ranges.get(&value) {
427            if range.lo > U256::ZERO {
428                return Some(true);
429            }
430            if range.hi.is_zero() {
431                return Some(false);
432            }
433        }
434        let depth = depth.checked_sub(1)?;
435        let kind = inst_kind(func, value)?;
436        match *kind {
437            InstKind::Lt(a, b) => self.eval_lt(func, a, b, depth),
438            InstKind::Gt(a, b) => self.eval_lt(func, b, a, depth),
439            InstKind::Eq(a, b) => self.eval_eq(func, a, b, depth),
440            InstKind::IsZero(a) => self.eval_truth(func, a, depth).map(|truth| !truth),
441            InstKind::Sub(a, b) | InstKind::Xor(a, b) => {
442                self.eval_eq(func, a, b, depth).map(|eq| !eq)
443            }
444            InstKind::And(a, b) => {
445                let ta = self.eval_truth(func, a, depth);
446                let tb = self.eval_truth(func, b, depth);
447                if ta == Some(false) || tb == Some(false) {
448                    return Some(false);
449                }
450                // Bitwise AND of two values both known to be exactly one.
451                let one = Range::singleton(U256::from(1));
452                if self.range_of(func, a, depth) == one && self.range_of(func, b, depth) == one {
453                    return Some(true);
454                }
455                None
456            }
457            InstKind::Or(a, b) => {
458                let ta = self.eval_truth(func, a, depth);
459                let tb = self.eval_truth(func, b, depth);
460                if ta == Some(true) || tb == Some(true) {
461                    return Some(true);
462                }
463                if ta == Some(false) && tb == Some(false) {
464                    return Some(false);
465                }
466                None
467            }
468            _ => {
469                let range = self.range_of(func, value, depth);
470                if range.lo > U256::ZERO {
471                    return Some(true);
472                }
473                if range.hi.is_zero() {
474                    return Some(false);
475                }
476                None
477            }
478        }
479    }
480
481    /// Evaluates `a < b` (unsigned), if provable.
482    fn eval_lt(&mut self, func: &Function, a: ValueId, b: ValueId, depth: usize) -> Option<bool> {
483        if a == b {
484            return Some(false);
485        }
486        let (x, y) = ordered(a, b);
487        if self.has_relation(Relation::Lt(a, b)) {
488            return Some(true);
489        }
490        if self.has_relation(Relation::Lt(b, a))
491            || self.has_relation(Relation::Le(b, a))
492            || self.has_relation(Relation::Eq(x, y))
493        {
494            return Some(false);
495        }
496
497        let ra = self.range_of(func, a, depth);
498        let rb = self.range_of(func, b, depth);
499        if ra.hi < rb.lo {
500            return Some(true);
501        }
502        if ra.lo >= rb.hi {
503            return Some(false);
504        }
505
506        // Overflow check for checked add: `lt (add x, y), x` is the wrap
507        // flag of `x + y`. If the maximum bounds cannot wrap the check is
508        // false; if even the minimum bounds wrap it is true.
509        if let Some(&InstKind::Add(x, y)) = inst_kind(func, a)
510            && (b == x || b == y)
511        {
512            let rx = self.range_of(func, x, depth);
513            let ry = self.range_of(func, y, depth);
514            if rx.hi.checked_add(ry.hi).is_some() {
515                return Some(false);
516            }
517            if rx.lo.checked_add(ry.lo).is_none() {
518                return Some(true);
519            }
520        }
521
522        // Underflow check variant `lt x, (sub x, y)`: equivalent to
523        // `lt x, y` for every `y` (with wrapping subtraction).
524        if let Some(&InstKind::Sub(x, y)) = inst_kind(func, b)
525            && a == x
526            && let Some(reduced_depth) = depth.checked_sub(1)
527        {
528            return self.eval_lt(func, x, y, reduced_depth);
529        }
530
531        None
532    }
533
534    /// Evaluates `a == b`, if provable.
535    fn eval_eq(&mut self, func: &Function, a: ValueId, b: ValueId, depth: usize) -> Option<bool> {
536        if a == b {
537            return Some(true);
538        }
539        let (x, y) = ordered(a, b);
540        if self.has_relation(Relation::Eq(x, y)) {
541            return Some(true);
542        }
543        if self.has_relation(Relation::Ne(x, y))
544            || self.has_relation(Relation::Lt(a, b))
545            || self.has_relation(Relation::Lt(b, a))
546        {
547            return Some(false);
548        }
549
550        let ra = self.range_of(func, a, depth);
551        let rb = self.range_of(func, b, depth);
552        if ra.hi < rb.lo || rb.hi < ra.lo {
553            return Some(false);
554        }
555        if ra.is_singleton() && ra == rb {
556            return Some(true);
557        }
558
559        // Overflow check for checked mul: `eq (div (mul x, y), y), x` holds
560        // iff `x * y` did not wrap, provided the divisor is nonzero.
561        if let Some(truth) = self.eval_muldiv_roundtrip(func, a, b, depth) {
562            return Some(truth);
563        }
564        if let Some(truth) = self.eval_muldiv_roundtrip(func, b, a, depth) {
565            return Some(truth);
566        }
567
568        None
569    }
570
571    /// Recognizes `div (mul x, y), d == x` with `d == y` and proves it true
572    /// when `x * y` cannot wrap and the divisor is provably nonzero.
573    fn eval_muldiv_roundtrip(
574        &mut self,
575        func: &Function,
576        div_value: ValueId,
577        expected: ValueId,
578        depth: usize,
579    ) -> Option<bool> {
580        let InstKind::Div(mul_value, divisor) = *inst_kind(func, div_value)? else { return None };
581        let InstKind::Mul(p, q) = *inst_kind(func, mul_value)? else { return None };
582        for (x, y) in [(p, q), (q, p)] {
583            if x != expected || !values_equal(func, divisor, y) {
584                continue;
585            }
586            let ry = self.range_of(func, y, depth);
587            if ry.lo.is_zero() {
588                continue;
589            }
590            let rx = self.range_of(func, x, depth);
591            if rx.hi.checked_mul(ry.hi).is_some() {
592                return Some(true);
593            }
594        }
595        None
596    }
597}
598
599/// Returns the fact implied on the unique dominating edge into `block`:
600/// the branch condition of its sole predecessor and whether it is true.
601fn dominating_edge_fact(
602    func: &Function,
603    preds: &[Vec<BlockId>],
604    block: BlockId,
605) -> Option<(ValueId, bool)> {
606    // The entry executes before any branch, so no edge fact holds on it even
607    // if malformed input gives it a predecessor.
608    if block == func.entry_block {
609        return None;
610    }
611    let preds = &preds[block.index()];
612    let (&first, rest) = preds.split_first()?;
613    if rest.iter().any(|&pred| pred != first) {
614        return None;
615    }
616    let Terminator::Branch { condition, then_block, else_block } =
617        func.blocks[first].terminator.as_ref()?
618    else {
619        return None;
620    };
621    // A branch with both arms on `block` implies nothing.
622    if then_block == else_block {
623        return None;
624    }
625    if *then_block == block {
626        Some((*condition, true))
627    } else if *else_block == block {
628        Some((*condition, false))
629    } else {
630        None
631    }
632}
633
634fn const_of(func: &Function, value: ValueId) -> Option<U256> {
635    match func.value(value) {
636        Value::Immediate(imm) => imm.as_u256(),
637        _ => None,
638    }
639}
640
641fn inst_kind(func: &Function, value: ValueId) -> Option<&InstKind> {
642    match func.value(value) {
643        Value::Inst(inst_id) => Some(&func.instructions[*inst_id].kind),
644        _ => None,
645    }
646}
647
648/// Returns true if both values are the same SSA value or the same constant.
649fn values_equal(func: &Function, a: ValueId, b: ValueId) -> bool {
650    if a == b {
651        return true;
652    }
653    match (const_of(func, a), const_of(func, b)) {
654        (Some(a), Some(b)) => a == b,
655        _ => false,
656    }
657}
658
659#[cfg(test)]
660mod tests {
661    use super::*;
662
663    #[test]
664    fn range_intersection_and_union() {
665        let a = Range::new(U256::from(0), U256::from(10));
666        let b = Range::new(U256::from(5), U256::from(20));
667        assert_eq!(a.intersect(b), Some(Range::new(U256::from(5), U256::from(10))));
668        assert_eq!(a.union(b), Range::new(U256::from(0), U256::from(20)));
669
670        let disjoint = Range::new(U256::from(11), U256::from(12));
671        assert_eq!(a.intersect(disjoint), None);
672    }
673}