|
| 1 | +//! The goal of the "remove enable side effects" optimization pass is to delay any [Instruction::EnableSideEffects] |
| 2 | +//! instructions such that they cover the minimum number of instructions possible. |
| 3 | +//! |
| 4 | +//! The pass works as follows: |
| 5 | +//! - Insert instructions until an [Instruction::EnableSideEffects] is encountered, save this [InstructionId]. |
| 6 | +//! - Continue inserting instructions until either |
| 7 | +//! - Another [Instruction::EnableSideEffects] is encountered, if so then drop the previous [InstructionId] in favour |
| 8 | +//! of this one. |
| 9 | +//! - An [Instruction] with side-effects is encountered, if so then insert the currently saved [Instruction::EnableSideEffects] |
| 10 | +//! before the [Instruction]. Continue inserting instructions until the next [Instruction::EnableSideEffects] is encountered. |
| 11 | +use std::collections::HashSet; |
| 12 | + |
| 13 | +use acvm::FieldElement; |
| 14 | + |
| 15 | +use crate::ssa::{ |
| 16 | + ir::{ |
| 17 | + basic_block::BasicBlockId, |
| 18 | + dfg::DataFlowGraph, |
| 19 | + function::Function, |
| 20 | + instruction::{BinaryOp, Instruction, Intrinsic}, |
| 21 | + value::Value, |
| 22 | + }, |
| 23 | + ssa_gen::Ssa, |
| 24 | +}; |
| 25 | + |
| 26 | +impl Ssa { |
| 27 | + /// See [`remove_enable_side_effects`][self] module for more information. |
| 28 | + #[tracing::instrument(level = "trace", skip(self))] |
| 29 | + pub(crate) fn remove_enable_side_effects(mut self) -> Ssa { |
| 30 | + for function in self.functions.values_mut() { |
| 31 | + remove_enable_side_effects(function); |
| 32 | + } |
| 33 | + self |
| 34 | + } |
| 35 | +} |
| 36 | + |
| 37 | +fn remove_enable_side_effects(function: &mut Function) { |
| 38 | + let mut context = Context::default(); |
| 39 | + context.block_queue.push(function.entry_block()); |
| 40 | + |
| 41 | + while let Some(block) = context.block_queue.pop() { |
| 42 | + if context.visited_blocks.contains(&block) { |
| 43 | + continue; |
| 44 | + } |
| 45 | + |
| 46 | + context.visited_blocks.insert(block); |
| 47 | + context.remove_enable_side_effects_in_block(function, block); |
| 48 | + } |
| 49 | +} |
| 50 | + |
| 51 | +#[derive(Default)] |
| 52 | +struct Context { |
| 53 | + visited_blocks: HashSet<BasicBlockId>, |
| 54 | + block_queue: Vec<BasicBlockId>, |
| 55 | +} |
| 56 | + |
| 57 | +impl Context { |
| 58 | + fn remove_enable_side_effects_in_block( |
| 59 | + &mut self, |
| 60 | + function: &mut Function, |
| 61 | + block: BasicBlockId, |
| 62 | + ) { |
| 63 | + let instructions = function.dfg[block].take_instructions(); |
| 64 | + |
| 65 | + let mut last_side_effects_enabled_instruction = None; |
| 66 | + |
| 67 | + let mut new_instructions = Vec::with_capacity(instructions.len()); |
| 68 | + for instruction_id in instructions { |
| 69 | + let instruction = &function.dfg[instruction_id]; |
| 70 | + |
| 71 | + // If we run into another `Instruction::EnableSideEffects` before encountering any |
| 72 | + // instructions with side effects then we can drop the instruction we're holding and |
| 73 | + // continue with the new `Instruction::EnableSideEffects`. |
| 74 | + if let Instruction::EnableSideEffects { condition } = instruction { |
| 75 | + // If we're seeing an `enable_side_effects u1 1` then we want to insert it immediately. |
| 76 | + // This is because we want to maximize the effect it will have. |
| 77 | + if function |
| 78 | + .dfg |
| 79 | + .get_numeric_constant(*condition) |
| 80 | + .map_or(false, |condition| condition.is_one()) |
| 81 | + { |
| 82 | + new_instructions.push(instruction_id); |
| 83 | + last_side_effects_enabled_instruction = None; |
| 84 | + continue; |
| 85 | + } |
| 86 | + |
| 87 | + last_side_effects_enabled_instruction = Some(instruction_id); |
| 88 | + continue; |
| 89 | + } |
| 90 | + |
| 91 | + // If we hit an instruction which is affected by the side effects var then we must insert the |
| 92 | + // `Instruction::EnableSideEffects` before we insert this new instruction. |
| 93 | + if Self::responds_to_side_effects_var(&function.dfg, instruction) { |
| 94 | + if let Some(enable_side_effects_instruction_id) = |
| 95 | + last_side_effects_enabled_instruction.take() |
| 96 | + { |
| 97 | + new_instructions.push(enable_side_effects_instruction_id); |
| 98 | + } |
| 99 | + } |
| 100 | + new_instructions.push(instruction_id); |
| 101 | + } |
| 102 | + |
| 103 | + *function.dfg[block].instructions_mut() = new_instructions; |
| 104 | + |
| 105 | + self.block_queue.extend(function.dfg[block].successors()); |
| 106 | + } |
| 107 | + |
| 108 | + fn responds_to_side_effects_var(dfg: &DataFlowGraph, instruction: &Instruction) -> bool { |
| 109 | + use Instruction::*; |
| 110 | + match instruction { |
| 111 | + Binary(binary) => { |
| 112 | + if matches!(binary.operator, BinaryOp::Div | BinaryOp::Mod) { |
| 113 | + if let Some(rhs) = dfg.get_numeric_constant(binary.rhs) { |
| 114 | + rhs == FieldElement::zero() |
| 115 | + } else { |
| 116 | + true |
| 117 | + } |
| 118 | + } else { |
| 119 | + false |
| 120 | + } |
| 121 | + } |
| 122 | + |
| 123 | + Cast(_, _) |
| 124 | + | Not(_) |
| 125 | + | Truncate { .. } |
| 126 | + | Constrain(..) |
| 127 | + | RangeCheck { .. } |
| 128 | + | IncrementRc { .. } |
| 129 | + | DecrementRc { .. } => false, |
| 130 | + |
| 131 | + EnableSideEffects { .. } |
| 132 | + | ArrayGet { .. } |
| 133 | + | ArraySet { .. } |
| 134 | + | Allocate |
| 135 | + | Store { .. } |
| 136 | + | Load { .. } => true, |
| 137 | + |
| 138 | + // Some `Intrinsic`s have side effects so we must check what kind of `Call` this is. |
| 139 | + Call { func, .. } => match dfg[*func] { |
| 140 | + Value::Intrinsic(intrinsic) => match intrinsic { |
| 141 | + Intrinsic::SlicePushBack |
| 142 | + | Intrinsic::SlicePushFront |
| 143 | + | Intrinsic::SlicePopBack |
| 144 | + | Intrinsic::SlicePopFront |
| 145 | + | Intrinsic::SliceInsert |
| 146 | + | Intrinsic::SliceRemove => true, |
| 147 | + |
| 148 | + Intrinsic::ArrayLen |
| 149 | + | Intrinsic::AssertConstant |
| 150 | + | Intrinsic::ApplyRangeConstraint |
| 151 | + | Intrinsic::StrAsBytes |
| 152 | + | Intrinsic::ToBits(_) |
| 153 | + | Intrinsic::ToRadix(_) |
| 154 | + | Intrinsic::BlackBox(_) |
| 155 | + | Intrinsic::FromField |
| 156 | + | Intrinsic::AsField |
| 157 | + | Intrinsic::AsSlice => false, |
| 158 | + }, |
| 159 | + |
| 160 | + // We must assume that functions contain a side effect as we cannot inspect more deeply. |
| 161 | + Value::Function(_) => true, |
| 162 | + |
| 163 | + _ => false, |
| 164 | + }, |
| 165 | + } |
| 166 | + } |
| 167 | +} |
0 commit comments