1use crate::{
18 mir::{
19 BlockId, Function, Immediate, InstId, InstKind, MirType, Terminator, Value, ValueId,
20 utils::{self as mir_utils, repair_reachability_phis},
21 },
22 pass::FunctionPass,
23 utils::evm_word,
24};
25use alloy_primitives::U256;
26use solar_data_structures::map::{FxHashMap, FxHashSet};
27use std::collections::VecDeque;
28
29#[derive(Clone, Debug, PartialEq, Eq)]
31enum LatticeValue {
32 Top,
34 Constant(U256),
36 Bottom,
38}
39
40impl LatticeValue {
41 fn meet(&self, other: &Self) -> Self {
44 match (self, other) {
45 (Self::Top, x) | (x, Self::Top) => x.clone(),
46 (Self::Bottom, _) | (_, Self::Bottom) => Self::Bottom,
47 (Self::Constant(a), Self::Constant(b)) => {
48 if a == b {
49 Self::Constant(*a)
50 } else {
51 Self::Bottom
52 }
53 }
54 }
55 }
56}
57
58#[derive(Debug, Default, Clone)]
60pub struct SccpStats {
61 pub constants_folded: usize,
63 pub branches_folded: usize,
65 pub switches_folded: usize,
67 pub blocks_invalidated: usize,
69}
70
71#[derive(Debug, Default)]
73pub struct SccpPass {
74 pub stats: SccpStats,
76}
77
78pub struct SccpTransformPass;
80
81impl FunctionPass for SccpTransformPass {
82 fn name(&self) -> &str {
83 "sccp"
84 }
85
86 fn run_on_function(&mut self, func: &mut Function) -> bool {
87 SccpPass::new().run(func) != 0
88 }
89}
90
91impl SccpPass {
92 pub fn new() -> Self {
94 Self::default()
95 }
96
97 pub fn run(&mut self, func: &mut Function) -> usize {
100 self.stats = SccpStats::default();
101
102 let num_values = func.values.len();
103
104 let inst_to_value: FxHashMap<InstId, ValueId> = func
106 .values
107 .iter_enumerated()
108 .filter_map(
109 |(vid, val)| {
110 if let Value::Inst(iid) = val { Some((*iid, vid)) } else { None }
111 },
112 )
113 .collect();
114
115 let mut lattice: Vec<LatticeValue> = vec![LatticeValue::Top; num_values];
117
118 for (vid, val) in func.values.iter_enumerated() {
120 match val {
121 Value::Arg { .. } => lattice[vid.index()] = LatticeValue::Bottom,
122 Value::Immediate(imm) => {
123 if let Some(v) = imm.as_u256() {
124 lattice[vid.index()] = LatticeValue::Constant(v);
125 } else {
126 lattice[vid.index()] = LatticeValue::Bottom;
127 }
128 }
129 Value::Undef(_) | Value::Error(_) => lattice[vid.index()] = LatticeValue::Bottom,
130 Value::Inst(_) => {} }
132 }
133
134 let mut executable_blocks: FxHashSet<BlockId> = FxHashSet::default();
136 let mut executable_edges: FxHashSet<(BlockId, BlockId)> = FxHashSet::default();
138
139 let mut cfg_worklist: VecDeque<(BlockId, BlockId)> = VecDeque::new(); let mut ssa_worklist: VecDeque<ValueId> = VecDeque::new();
142
143 executable_blocks.insert(func.entry_block);
145 self.evaluate_phis_in_block(
146 func,
147 func.entry_block,
148 &inst_to_value,
149 &mut lattice,
150 &executable_edges,
151 &mut ssa_worklist,
152 );
153 self.evaluate_block(
155 func,
156 func.entry_block,
157 &inst_to_value,
158 &mut lattice,
159 &executable_blocks,
160 &executable_edges,
161 &mut cfg_worklist,
162 &mut ssa_worklist,
163 );
164
165 loop {
167 let mut made_progress = false;
168
169 while let Some((from, to)) = cfg_worklist.pop_front() {
171 if !executable_edges.insert((from, to)) {
172 continue; }
174 made_progress = true;
175
176 let newly_executable = executable_blocks.insert(to);
177
178 self.evaluate_phis_in_block(
180 func,
181 to,
182 &inst_to_value,
183 &mut lattice,
184 &executable_edges,
185 &mut ssa_worklist,
186 );
187
188 if newly_executable {
189 self.evaluate_block(
191 func,
192 to,
193 &inst_to_value,
194 &mut lattice,
195 &executable_blocks,
196 &executable_edges,
197 &mut cfg_worklist,
198 &mut ssa_worklist,
199 );
200 }
201 }
202
203 while let Some(vid) = ssa_worklist.pop_front() {
205 made_progress = true;
206 self.propagate_value(
208 func,
209 vid,
210 &inst_to_value,
211 &mut lattice,
212 &executable_blocks,
213 &executable_edges,
214 &mut cfg_worklist,
215 &mut ssa_worklist,
216 );
217 }
218
219 if !made_progress {
220 break;
221 }
222 }
223
224 self.rewrite(func, &lattice, &inst_to_value, &executable_blocks, &executable_edges)
226 }
227
228 #[allow(clippy::too_many_arguments)]
230 fn evaluate_block(
231 &self,
232 func: &Function,
233 block_id: BlockId,
234 inst_to_value: &FxHashMap<InstId, ValueId>,
235 lattice: &mut [LatticeValue],
236 _executable_blocks: &FxHashSet<BlockId>,
237 _executable_edges: &FxHashSet<(BlockId, BlockId)>,
238 cfg_worklist: &mut VecDeque<(BlockId, BlockId)>,
239 ssa_worklist: &mut VecDeque<ValueId>,
240 ) {
241 let block = &func.blocks[block_id];
242
243 for &inst_id in &block.instructions {
244 if matches!(func.instructions[inst_id].kind, InstKind::Phi(_)) {
245 continue;
246 }
247 if let Some(&vid) = inst_to_value.get(&inst_id) {
248 let new_val =
249 self.evaluate_instruction(func, &func.instructions[inst_id].kind, lattice);
250 if self.update_lattice(lattice, vid, new_val) {
251 ssa_worklist.push_back(vid);
252 }
253 }
254 }
255
256 if let Some(term) = &block.terminator {
258 self.evaluate_terminator(term, block_id, lattice, cfg_worklist);
259 }
260 }
261
262 fn evaluate_phis_in_block(
264 &self,
265 func: &Function,
266 block_id: BlockId,
267 inst_to_value: &FxHashMap<InstId, ValueId>,
268 lattice: &mut [LatticeValue],
269 executable_edges: &FxHashSet<(BlockId, BlockId)>,
270 ssa_worklist: &mut VecDeque<ValueId>,
271 ) {
272 let block = &func.blocks[block_id];
273 for &inst_id in &block.instructions {
274 let inst = &func.instructions[inst_id];
275 if let InstKind::Phi(incoming) = &inst.kind
276 && let Some(&vid) = inst_to_value.get(&inst_id)
277 {
278 let mut result = LatticeValue::Top;
280 for &(pred, operand) in incoming {
281 if executable_edges.contains(&(pred, block_id)) {
282 result = result.meet(&lattice[operand.index()]);
283 }
284 }
285 if self.update_lattice(lattice, vid, result) {
286 ssa_worklist.push_back(vid);
287 }
288 }
289 }
290 }
291
292 fn evaluate_instruction(
294 &self,
295 _func: &Function,
296 kind: &InstKind,
297 lattice: &[LatticeValue],
298 ) -> LatticeValue {
299 let get_const = |v: ValueId| -> Option<U256> {
301 match &lattice[v.index()] {
302 LatticeValue::Constant(c) => Some(*c),
303 _ => None,
304 }
305 };
306
307 match kind {
308 InstKind::Add(a, b) => match (get_const(*a), get_const(*b)) {
310 (Some(a), Some(b)) => LatticeValue::Constant(a.wrapping_add(b)),
311 _ => self.check_any_bottom(&[*a, *b], lattice),
312 },
313 InstKind::Sub(a, b) => match (get_const(*a), get_const(*b)) {
314 (Some(a), Some(b)) => LatticeValue::Constant(a.wrapping_sub(b)),
315 _ => self.check_any_bottom(&[*a, *b], lattice),
316 },
317 InstKind::Mul(a, b) => match (get_const(*a), get_const(*b)) {
318 (Some(a), Some(b)) => LatticeValue::Constant(a.wrapping_mul(b)),
319 _ => self.check_any_bottom(&[*a, *b], lattice),
320 },
321 InstKind::Div(a, b) => match (get_const(*a), get_const(*b)) {
323 (_, Some(b)) if b.is_zero() => LatticeValue::Constant(U256::ZERO),
324 (Some(a), Some(b)) => LatticeValue::Constant(a / b),
325 _ => self.check_any_bottom(&[*a, *b], lattice),
326 },
327 InstKind::SDiv(a, b) => match (get_const(*a), get_const(*b)) {
328 (_, Some(b)) if b.is_zero() => LatticeValue::Constant(U256::ZERO),
329 (Some(a), Some(b)) => LatticeValue::Constant(evm_word::signed_div(a, b)),
330 _ => self.check_any_bottom(&[*a, *b], lattice),
331 },
332 InstKind::Mod(a, b) => match (get_const(*a), get_const(*b)) {
333 (_, Some(b)) if b.is_zero() => LatticeValue::Constant(U256::ZERO),
334 (Some(a), Some(b)) => LatticeValue::Constant(a % b),
335 _ => self.check_any_bottom(&[*a, *b], lattice),
336 },
337 InstKind::SMod(a, b) => match (get_const(*a), get_const(*b)) {
338 (_, Some(b)) if b.is_zero() => LatticeValue::Constant(U256::ZERO),
339 (Some(a), Some(b)) => LatticeValue::Constant(evm_word::signed_mod(a, b)),
340 _ => self.check_any_bottom(&[*a, *b], lattice),
341 },
342 InstKind::AddMod(a, b, n) => match (get_const(*a), get_const(*b), get_const(*n)) {
344 (_, _, Some(n)) if n.is_zero() => LatticeValue::Constant(U256::ZERO),
345 (Some(a), Some(b), Some(n)) => LatticeValue::Constant(a.add_mod(b, n)),
346 _ => self.check_any_bottom(&[*a, *b, *n], lattice),
347 },
348 InstKind::MulMod(a, b, n) => match (get_const(*a), get_const(*b), get_const(*n)) {
349 (_, _, Some(n)) if n.is_zero() => LatticeValue::Constant(U256::ZERO),
350 (Some(a), Some(b), Some(n)) => LatticeValue::Constant(a.mul_mod(b, n)),
351 _ => self.check_any_bottom(&[*a, *b, *n], lattice),
352 },
353 InstKind::Exp(a, b) => match (get_const(*a), get_const(*b)) {
354 (Some(base), Some(exp)) => {
355 LatticeValue::Constant(base.wrapping_pow(exp))
357 }
358 _ => self.check_any_bottom(&[*a, *b], lattice),
359 },
360
361 InstKind::Lt(a, b) => match (get_const(*a), get_const(*b)) {
363 (Some(a), Some(b)) => LatticeValue::Constant(U256::from(a < b)),
364 _ => self.check_any_bottom(&[*a, *b], lattice),
365 },
366 InstKind::Gt(a, b) => match (get_const(*a), get_const(*b)) {
367 (Some(a), Some(b)) => LatticeValue::Constant(U256::from(a > b)),
368 _ => self.check_any_bottom(&[*a, *b], lattice),
369 },
370 InstKind::SLt(a, b) => match (get_const(*a), get_const(*b)) {
371 (Some(a), Some(b)) => LatticeValue::Constant(U256::from(evm_word::signed_lt(a, b))),
372 _ => self.check_any_bottom(&[*a, *b], lattice),
373 },
374 InstKind::SGt(a, b) => match (get_const(*a), get_const(*b)) {
375 (Some(a), Some(b)) => LatticeValue::Constant(U256::from(evm_word::signed_gt(a, b))),
376 _ => self.check_any_bottom(&[*a, *b], lattice),
377 },
378 InstKind::Eq(a, b) => match (get_const(*a), get_const(*b)) {
379 (Some(a), Some(b)) => LatticeValue::Constant(U256::from(a == b)),
380 _ => self.check_any_bottom(&[*a, *b], lattice),
381 },
382 InstKind::IsZero(a) => match get_const(*a) {
383 Some(a) => LatticeValue::Constant(U256::from(a.is_zero())),
384 None => self.check_any_bottom(&[*a], lattice),
385 },
386
387 InstKind::And(a, b) => match (get_const(*a), get_const(*b)) {
389 (Some(a), Some(b)) => LatticeValue::Constant(a & b),
390 _ => self.check_any_bottom(&[*a, *b], lattice),
391 },
392 InstKind::Or(a, b) => match (get_const(*a), get_const(*b)) {
393 (Some(a), Some(b)) => LatticeValue::Constant(a | b),
394 _ => self.check_any_bottom(&[*a, *b], lattice),
395 },
396 InstKind::Xor(a, b) => match (get_const(*a), get_const(*b)) {
397 (Some(a), Some(b)) => LatticeValue::Constant(a ^ b),
398 _ => self.check_any_bottom(&[*a, *b], lattice),
399 },
400 InstKind::Not(a) => match get_const(*a) {
401 Some(a) => LatticeValue::Constant(!a),
402 None => self.check_any_bottom(&[*a], lattice),
403 },
404 InstKind::Shl(shift, val) => match (get_const(*shift), get_const(*val)) {
405 (Some(s), Some(v)) => {
406 if s >= U256::from(256) {
407 LatticeValue::Constant(U256::ZERO)
408 } else {
409 LatticeValue::Constant(v << s.to::<usize>())
410 }
411 }
412 _ => self.check_any_bottom(&[*shift, *val], lattice),
413 },
414 InstKind::Shr(shift, val) => match (get_const(*shift), get_const(*val)) {
415 (Some(s), Some(v)) => {
416 if s >= U256::from(256) {
417 LatticeValue::Constant(U256::ZERO)
418 } else {
419 LatticeValue::Constant(v >> s.to::<usize>())
420 }
421 }
422 _ => self.check_any_bottom(&[*shift, *val], lattice),
423 },
424 InstKind::Sar(shift, val) => match (get_const(*shift), get_const(*val)) {
425 (Some(s), Some(v)) => LatticeValue::Constant(evm_word::sar(v, s)),
426 _ => self.check_any_bottom(&[*shift, *val], lattice),
427 },
428 InstKind::Byte(index, val) => match (get_const(*index), get_const(*val)) {
429 (Some(i), Some(v)) => LatticeValue::Constant(evm_word::byte(i, v)),
430 _ => self.check_any_bottom(&[*index, *val], lattice),
431 },
432 InstKind::SignExtend(size, val) => match (get_const(*size), get_const(*val)) {
433 (Some(s), Some(v)) => LatticeValue::Constant(evm_word::signextend(s, v)),
434 _ => self.check_any_bottom(&[*size, *val], lattice),
435 },
436
437 InstKind::Select(condition, then_value, else_value) => {
438 match &lattice[condition.index()] {
439 LatticeValue::Constant(c) => {
440 let chosen = if c.is_zero() { *else_value } else { *then_value };
441 lattice[chosen.index()].clone()
442 }
443 LatticeValue::Bottom => {
445 lattice[then_value.index()].meet(&lattice[else_value.index()])
446 }
447 LatticeValue::Top => match (get_const(*then_value), get_const(*else_value)) {
448 (Some(t), Some(e)) if t == e => LatticeValue::Constant(t),
449 _ => LatticeValue::Top,
450 },
451 }
452 }
453
454 _ => LatticeValue::Bottom,
457 }
458 }
459
460 fn check_any_bottom(&self, operands: &[ValueId], lattice: &[LatticeValue]) -> LatticeValue {
462 for &op in operands {
463 if matches!(lattice[op.index()], LatticeValue::Bottom) {
464 return LatticeValue::Bottom;
465 }
466 }
467 LatticeValue::Top
468 }
469
470 fn evaluate_terminator(
472 &self,
473 term: &Terminator,
474 block_id: BlockId,
475 lattice: &[LatticeValue],
476 cfg_worklist: &mut VecDeque<(BlockId, BlockId)>,
477 ) {
478 match term {
479 Terminator::Jump(target) => {
480 cfg_worklist.push_back((block_id, *target));
481 }
482 Terminator::Branch { condition, then_block, else_block } => {
483 match &lattice[condition.index()] {
484 LatticeValue::Constant(v) => {
485 if !v.is_zero() {
486 cfg_worklist.push_back((block_id, *then_block));
487 } else {
488 cfg_worklist.push_back((block_id, *else_block));
489 }
490 }
491 LatticeValue::Bottom => {
492 cfg_worklist.push_back((block_id, *then_block));
494 cfg_worklist.push_back((block_id, *else_block));
495 }
496 LatticeValue::Top => {}
497 }
498 }
499 Terminator::Switch { value, default, cases } => {
500 match &lattice[value.index()] {
501 LatticeValue::Constant(v) => {
502 for &(case_val, target) in cases {
508 match &lattice[case_val.index()] {
509 LatticeValue::Constant(cv) if cv == v => {
510 cfg_worklist.push_back((block_id, target));
511 return;
512 }
513 LatticeValue::Constant(_) => {}
514 LatticeValue::Bottom => {
515 cfg_worklist.push_back((block_id, target));
516 }
517 LatticeValue::Top => return,
518 }
519 }
520 cfg_worklist.push_back((block_id, *default));
521 }
522 LatticeValue::Bottom => {
523 cfg_worklist.push_back((block_id, *default));
525 for &(_, target) in cases {
526 cfg_worklist.push_back((block_id, target));
527 }
528 }
529 LatticeValue::Top => {}
530 }
531 }
532 Terminator::Return { .. }
533 | Terminator::Revert { .. }
534 | Terminator::ReturnData { .. }
535 | Terminator::Stop
536 | Terminator::SelfDestruct { .. }
537 | Terminator::Invalid => {
538 }
540 }
541 }
542
543 fn update_lattice(
546 &self,
547 lattice: &mut [LatticeValue],
548 vid: ValueId,
549 new_val: LatticeValue,
550 ) -> bool {
551 let old = &lattice[vid.index()];
552 let merged = old.meet(&new_val);
553 if merged != *old {
554 lattice[vid.index()] = merged;
555 true
556 } else {
557 false
558 }
559 }
560
561 #[allow(clippy::too_many_arguments)]
563 fn propagate_value(
564 &self,
565 func: &Function,
566 vid: ValueId,
567 inst_to_value: &FxHashMap<InstId, ValueId>,
568 lattice: &mut [LatticeValue],
569 executable_blocks: &FxHashSet<BlockId>,
570 executable_edges: &FxHashSet<(BlockId, BlockId)>,
571 cfg_worklist: &mut VecDeque<(BlockId, BlockId)>,
572 ssa_worklist: &mut VecDeque<ValueId>,
573 ) {
574 for block_id in executable_blocks {
575 self.evaluate_phis_in_block(
576 func,
577 *block_id,
578 inst_to_value,
579 lattice,
580 executable_edges,
581 ssa_worklist,
582 );
583 }
584
585 for (block_id, block) in func.blocks.iter_enumerated() {
587 if !executable_blocks.contains(&block_id) {
588 continue;
589 }
590 for &inst_id in &block.instructions {
591 let inst = &func.instructions[inst_id];
592 if matches!(inst.kind, InstKind::Phi(_)) {
593 continue;
594 }
595 let operands = inst.kind.operands();
596 if operands.contains(&vid)
597 && let Some(&result_vid) = inst_to_value.get(&inst_id)
598 {
599 let new_val = self.evaluate_instruction(func, &inst.kind, lattice);
600 if self.update_lattice(lattice, result_vid, new_val) {
601 ssa_worklist.push_back(result_vid);
602 }
603 }
604 }
605 if let Some(term) = &block.terminator {
607 let term_ops = term.operands();
608 if term_ops.contains(&vid) {
609 self.evaluate_terminator(term, block_id, lattice, cfg_worklist);
610 }
611 }
612 }
613 }
614
615 fn rewrite(
617 &mut self,
618 func: &mut Function,
619 lattice: &[LatticeValue],
620 inst_to_value: &FxHashMap<InstId, ValueId>,
621 executable_blocks: &FxHashSet<BlockId>,
622 executable_edges: &FxHashSet<(BlockId, BlockId)>,
623 ) -> usize {
624 let mut const_values: FxHashMap<ValueId, ValueId> = FxHashMap::default();
627 let mut dead_insts: FxHashSet<InstId> = FxHashSet::default();
628
629 for (&inst_id, &vid) in inst_to_value {
630 if let LatticeValue::Constant(c) = &lattice[vid.index()] {
631 if func.instructions[inst_id].kind.has_side_effects() {
633 continue;
634 }
635 let imm = immediate_for_type(func.instructions[inst_id].result_ty, *c);
637 let imm_vid = func.alloc_value(Value::Immediate(imm));
638 const_values.insert(vid, imm_vid);
639 dead_insts.insert(inst_id);
640 self.stats.constants_folded += 1;
641 }
642 }
643
644 let block_ids: Vec<BlockId> = func.blocks.indices().collect();
647 let mut control_rewrites: Vec<(BlockId, BlockId)> = Vec::new();
648 for &block_id in &block_ids {
649 if !executable_blocks.contains(&block_id) {
650 continue;
651 }
652 let Some(term) = &func.blocks[block_id].terminator else {
653 continue;
654 };
655 if !matches!(term, Terminator::Branch { .. } | Terminator::Switch { .. }) {
656 continue;
657 }
658
659 let executable_successors: FxHashSet<_> = term
660 .successors()
661 .into_iter()
662 .filter(|&successor| executable_edges.contains(&(block_id, successor)))
663 .collect();
664 if executable_successors.len() == 1 {
665 let target = executable_successors.into_iter().next().expect("checked len");
666 control_rewrites.push((block_id, target));
667 }
668 }
669
670 if !const_values.is_empty() {
672 let all_insts: Vec<InstId> = func
673 .blocks
674 .iter()
675 .flat_map(|block| block.instructions.iter().copied())
676 .filter(|id| !dead_insts.contains(id))
677 .collect();
678 for inst_id in all_insts {
679 mir_utils::replace_inst_uses(&mut func.instructions[inst_id].kind, &const_values);
680 }
681 for &block_id in &block_ids {
682 if let Some(term) = &mut func.blocks[block_id].terminator {
683 mir_utils::replace_terminator_uses(term, &const_values);
684 }
685 }
686 }
687
688 for &block_id in &block_ids {
690 func.blocks[block_id].instructions.retain(|id| !dead_insts.contains(id));
691 }
692
693 for (block_id, target) in control_rewrites {
695 let old_successors = func.blocks[block_id]
696 .terminator
697 .as_ref()
698 .map(Terminator::successors)
699 .unwrap_or_default();
700 let was_switch =
701 matches!(func.blocks[block_id].terminator, Some(Terminator::Switch { .. }));
702 for successor in old_successors {
703 func.blocks[successor].predecessors.retain(|pred| *pred != block_id);
704 }
705 if !func.blocks[target].predecessors.contains(&block_id) {
706 func.blocks[target].predecessors.push(block_id);
707 }
708 func.blocks[block_id].terminator = Some(Terminator::Jump(target));
709 if was_switch {
710 self.stats.switches_folded += 1;
711 } else {
712 self.stats.branches_folded += 1;
713 }
714 }
715
716 for &block_id in &block_ids {
718 if executable_blocks.contains(&block_id) {
719 continue;
720 }
721 let block = &mut func.blocks[block_id];
722 let already_invalid = block.instructions.is_empty()
727 && matches!(block.terminator, Some(Terminator::Invalid));
728 if already_invalid {
729 continue;
730 }
731 block.instructions.clear();
732 block.terminator = Some(Terminator::Invalid);
733 block.predecessors.clear();
734 self.stats.blocks_invalidated += 1;
735 }
736
737 let phis_repaired = repair_reachability_phis(func);
738
739 self.stats.constants_folded
740 + self.stats.branches_folded
741 + self.stats.switches_folded
742 + self.stats.blocks_invalidated
743 + usize::from(phis_repaired)
744 }
745}
746
747fn immediate_for_type(ty: Option<MirType>, value: U256) -> Immediate {
753 match ty {
754 Some(MirType::Bool) if value <= U256::from(1) => Immediate::Bool(!value.is_zero()),
755 Some(MirType::UInt(bits)) if fits_unsigned(value, bits) => Immediate::UInt(value, bits),
756 Some(MirType::Int(bits)) if fits_signed(value, bits) => Immediate::Int(value, bits),
757 _ => Immediate::uint256(value),
758 }
759}
760
761fn fits_unsigned(value: U256, bits: u16) -> bool {
762 bits >= 256 || value.bit_len() <= usize::from(bits)
763}
764
765fn fits_signed(value: U256, bits: u16) -> bool {
766 if bits >= 256 || bits == 0 {
767 return bits >= 256;
768 }
769 let bits = usize::from(bits);
770 if value.bit(bits - 1) { (!value).bit_len() < bits } else { value.bit_len() < bits }
773}
774
775#[cfg(test)]
776mod tests {
777 use super::*;
778
779 #[test]
780 fn immediate_for_type_preserves_result_types() {
781 let one = U256::from(1);
782 assert_eq!(immediate_for_type(Some(MirType::Bool), one), Immediate::Bool(true));
783 assert_eq!(immediate_for_type(Some(MirType::Bool), U256::ZERO), Immediate::Bool(false));
784 assert_eq!(immediate_for_type(Some(MirType::Int(256)), one), Immediate::Int(one, 256));
785 assert_eq!(immediate_for_type(Some(MirType::UInt(64)), one), Immediate::UInt(one, 64));
786 assert_eq!(immediate_for_type(Some(MirType::Address), one), Immediate::uint256(one));
788 assert_eq!(immediate_for_type(None, one), Immediate::uint256(one));
789 let two = U256::from(2);
791 assert_eq!(immediate_for_type(Some(MirType::Bool), two), Immediate::uint256(two));
792 let wide = U256::from(0x1ff);
794 assert_eq!(immediate_for_type(Some(MirType::UInt(8)), wide), Immediate::uint256(wide));
795 assert_eq!(immediate_for_type(Some(MirType::Int(8)), wide), Immediate::uint256(wide));
796 let minus_one = U256::MAX;
798 assert_eq!(
799 immediate_for_type(Some(MirType::Int(8)), minus_one),
800 Immediate::Int(minus_one, 8)
801 );
802 let i8_min = U256::MAX - U256::from(0x7f);
803 assert_eq!(immediate_for_type(Some(MirType::Int(8)), i8_min), Immediate::Int(i8_min, 8));
804 let i8_under = i8_min - U256::from(1);
805 assert_eq!(
806 immediate_for_type(Some(MirType::Int(8)), i8_under),
807 Immediate::uint256(i8_under)
808 );
809 }
810}