def replace_shortcut_ops(instructions): """Replace opcodes with a corresponding shortcut form.""" optimizations = [] # Replace division by 2. # OP_2 OP_DIV -> OP_2DIV optimizations.append(([types.Two(), types.Div()], lambda values: [types.Div2()])) # Replace subtraction by 1. # OP_1 OP_SUB -> OP_1SUB optimizations.append(([types.One(), types.Sub()], lambda values: [types.Sub1()])) # Replace 1 * -1 with -1. # OP_1 OP_NEGATE -> OP_1NEGATE optimizations.append(([types.One(), types.Negate()], lambda values: [types.NegativeOne()])) # Replace addition by 1. # OP_1 OP_ADD -> OP_1ADD for permutation in permutations([types.Push(), types.One()]) + permutations([types.SmallIntOpCode(), types.One()]): idx = 0 if not isinstance(permutation[0], types.One) else 1 optimizations.append((permutation + [types.Add()], lambda values, idx=idx: [values[idx], types.Add1()])) optimizations.append(([types.Assumption(), types.One(), types.Add()], lambda values: [values[0], types.Add1()])) # Replace multiplication by 2. # OP_2 OP_MUL -> OP_2MUL for permutation in permutations([types.Push(), types.Two()]) + permutations([types.SmallIntOpCode(), types.Two()]): idx = 0 if not isinstance(permutation[0], types.Two) else 1 optimizations.append((permutation + [types.Mul()], lambda values, idx=idx: [values[idx], types.Mul2()])) optimizations.append(([types.Assumption(), types.Two(), types.Mul()], lambda values: [values[0], types.Mul2()])) for template, callback in optimizations: instructions.replace_template(template, callback, strict=False)
def test_shortcut_ops(self): for script in [ [types.Five(), types.One(), types.Add()], [types.One(), types.Five(), types.Add()], ]: self._do_test('OP_5 OP_1ADD', script) self._do_test('OP_1ADD', [types.Assumption('testItem'), types.One(), types.Add()]) script = [types.Five(), types.One(), types.Sub()] self._do_test('OP_5 OP_1SUB', script) for script in [ [types.Five(), types.Two(), types.Mul()], [types.Two(), types.Five(), types.Mul()], ]: self._do_test('OP_5 OP_2MUL', script) self._do_test('OP_2MUL', [types.Assumption('testItem'), types.Two(), types.Mul()]) script = [types.Five(), types.Two(), types.Div()] self._do_test('OP_5 OP_2DIV', script) script = [types.Five(), types.One(), types.Negate()] self._do_test('OP_5 OP_1NEGATE', script)
def test_assume_to_pick_or_roll(self): script = [types.Five(), types.Assumption('testItem'), types.Add()] self._do_test('OP_5 OP_ADD', script) self._reset_table(['testItem']) script = [types.Five(), types.Five(), types.Assumption('testItem'), types.Add()] self._do_test('OP_5 OP_5 OP_2 OP_ROLL OP_ADD', script) self._reset_table(['testItem']) script = [types.Five(), types.Five(), types.Assumption('testItem'), types.Add(), types.Assumption('testItem')] self._do_test('OP_5 OP_5 OP_2 OP_PICK OP_ADD OP_2 OP_ROLL', script)
def find_duplicate_assumptions(self): """Find operations which use the same assumption more than once.""" base = [types.Assumption(), types.Assumption()] templates = [] # Collect opcodes that use the last two stack items as arguments. for opcode in types.get_opcodes().values(): if not issubclass(opcode, types.OpCode) or not opcode.args: continue if all(arg_idx in opcode.args for arg_idx in [1, 2]): templates.append(base + [opcode()]) for i in range(len(self.instructions)): for template in templates: if not self.instructions.matches_template( template, i, strict=False): continue ops = self.instructions[i:i + len(template)] # Continue if the assumptions aren't of the same value. if ops[0].var_name != ops[1].var_name: continue # Append the negative index of the operation to duplicated_assumptions[assumption_name]. self.duplicated_assumptions[ops[0].var_name].append( -1 * (len(self.instructions) - ops[-1].idx))
def inline(self, instructions, peephole_optimizer): """Perform inlining of variables in instructions. Inlining is performed by iterating through each instruction and calling visitor methods. If no result is returned, the next instruction is visited. If there is a result, the instruction is replaced with that result, and the iteration begins again. Inlining ends when all instructions have been iterated over without any result. """ if not isinstance(instructions, LInstructions): raise TypeError('A LInstructions instance is required') self.instructions = instructions self.stack.clear(clear_assumptions=True) stack_names = self.symbol_table.lookup('_stack_names') if stack_names: self.stack.add_stack_assumptions( [types.Assumption(var_name) for var_name in stack_names.value]) # Find operations that use the same assumed stack item more than once. self.contextualizer.contextualize(instructions) self.contextualizer.find_duplicate_assumptions() # Loop until no inlining can be done. while 1: self.stack.clear(clear_assumptions=False) peephole_optimizer.optimize(instructions) self.contextualizer.contextualize(instructions) inlined = False for i, node in enumerate(instructions): result = self.visit(node) if result is None: continue if not isinstance(result, list): result = [result] instructions.replace_slice(i, i + 1, result) inlined = True break if not inlined: break
def visit_Symbol(self, node): self.require_symbol_table('process symbol') symbol = self.symbol_table.lookup(node.name) if not symbol: raise IRError('Symbol "%s" was not declared.' % node.name) value = symbol.value type_ = symbol.type_ # Add an assumption for the stack item. if type_ == SymbolType.StackItem: # Fail if there are assumptions after a conditional and the conditional branches do not result in the # same number of stack items. if self.after_uneven_conditional: raise IRError("Conditional branches must result in the same number of stack values, or assumptions afterward are not supported.") return types.Assumption(symbol.name) # Push the bytes of the byte array. elif type_ in [SymbolType.ByteArray, SymbolType.Integer]: return self.visit(value) # If the type is an expression, then StructuralOptimizer could not simplify it. # Evaluate the expression as if it were encountered in the structural IR. elif type_ == SymbolType.Expr: return self.visit(value)
def test_implicit_assume(self): script = [types.Assumption('testItem'), types.Five(), types.Add()] self._do_test('OP_5 OP_ADD', script)