From c42b077c1d355e5c12aad903681108cd7164e4de Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 18 Jan 2024 00:09:34 +0200 Subject: [PATCH] fix[venom]: liveness analysis in some loops (#3732) fixes a small issue with the calculation of liveness when variables are consumed inside of loops by refactoring the liveness calculation algorithm to an iterative version over the recursive one additional QOL refactoring: - simplify convert_ir_basicblock - rename it to ir_node_to_venom - fix bb well-formedness for deploy ir - don't do deploy IR detection; rely on caller in CompilerData to call for both deploy and runtime IR - remove findIRnode, it's no longer needed - rename _convert_ir_basicblock to _convert_ir_bb - add _convert_ir_bb_list helper to handle arglists --------- Co-authored-by: Charles Cooper --- .../venom/test_convert_basicblock_simple.py | 41 ++++ vyper/compiler/phases.py | 5 +- vyper/venom/__init__.py | 10 +- vyper/venom/analysis.py | 36 +-- vyper/venom/basicblock.py | 4 +- vyper/venom/function.py | 2 +- vyper/venom/ir_node_to_venom.py | 217 ++++++++---------- 7 files changed, 162 insertions(+), 153 deletions(-) create mode 100644 tests/unit/compiler/venom/test_convert_basicblock_simple.py diff --git a/tests/unit/compiler/venom/test_convert_basicblock_simple.py b/tests/unit/compiler/venom/test_convert_basicblock_simple.py new file mode 100644 index 0000000000..fdaa341a81 --- /dev/null +++ b/tests/unit/compiler/venom/test_convert_basicblock_simple.py @@ -0,0 +1,41 @@ +from vyper.codegen.ir_node import IRnode +from vyper.venom.ir_node_to_venom import ir_node_to_venom + + +def test_simple(): + ir = IRnode.from_list(["calldatacopy", 32, 0, ["calldatasize"]]) + ir_node = IRnode.from_list(ir) + venom = ir_node_to_venom(ir_node) + assert venom is not None + + bb = venom.basic_blocks[0] + assert bb.instructions[0].opcode == "calldatasize" + assert bb.instructions[1].opcode == "calldatacopy" + + +def test_simple_2(): + ir = [ + "seq", + [ + "seq", + [ + "mstore", + ["add", 64, 0], + [ + "with", + "x", + ["calldataload", ["add", 4, 0]], + [ + "with", + "ans", + ["add", "x", 1], + ["seq", ["assert", ["ge", "ans", "x"]], "ans"], + ], + ], + ], + ], + 32, + ] + ir_node = IRnode.from_list(ir) + venom = ir_node_to_venom(ir_node) + assert venom is not None diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index ba6ccbda20..5b7decec7b 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -197,7 +197,10 @@ def function_signatures(self) -> dict[str, ContractFunctionT]: @cached_property def venom_functions(self): - return generate_ir(self.ir_nodes, self.settings.optimize) + deploy_ir, runtime_ir = self._ir_output + deploy_venom = generate_ir(deploy_ir, self.settings.optimize) + runtime_venom = generate_ir(runtime_ir, self.settings.optimize) + return deploy_venom, runtime_venom @cached_property def assembly(self) -> list: diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index 570aba771a..d1c2d0c342 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -12,7 +12,7 @@ ir_pass_remove_unreachable_blocks, ) from vyper.venom.function import IRFunction -from vyper.venom.ir_node_to_venom import convert_ir_basicblock +from vyper.venom.ir_node_to_venom import ir_node_to_venom from vyper.venom.passes.constant_propagation import ir_pass_constant_propagation from vyper.venom.passes.dft import DFTPass from vyper.venom.venom_to_assembly import VenomCompiler @@ -61,11 +61,9 @@ def _run_passes(ctx: IRFunction, optimize: OptimizationLevel) -> None: break -def generate_ir(ir: IRnode, optimize: OptimizationLevel) -> tuple[IRFunction, IRFunction]: +def generate_ir(ir: IRnode, optimize: OptimizationLevel) -> IRFunction: # Convert "old" IR to "new" IR - ctx, ctx_runtime = convert_ir_basicblock(ir) - + ctx = ir_node_to_venom(ir) _run_passes(ctx, optimize) - _run_passes(ctx_runtime, optimize) - return ctx, ctx_runtime + return ctx diff --git a/vyper/venom/analysis.py b/vyper/venom/analysis.py index eed579463e..daebd2560c 100644 --- a/vyper/venom/analysis.py +++ b/vyper/venom/analysis.py @@ -42,10 +42,12 @@ def _reset_liveness(ctx: IRFunction) -> None: inst.liveness = OrderedSet() -def _calculate_liveness_bb(bb: IRBasicBlock) -> None: +def _calculate_liveness(bb: IRBasicBlock) -> bool: """ Compute liveness of each instruction in the basic block. + Returns True if liveness changed """ + orig_liveness = bb.instructions[0].liveness.copy() liveness = bb.out_vars.copy() for instruction in reversed(bb.instructions): ops = instruction.get_inputs() @@ -60,29 +62,31 @@ def _calculate_liveness_bb(bb: IRBasicBlock) -> None: liveness.remove(out) instruction.liveness = liveness + return orig_liveness != bb.instructions[0].liveness -def _calculate_liveness_r(bb: IRBasicBlock, visited: dict) -> None: - assert isinstance(visited, dict) - for out_bb in bb.cfg_out: - if visited.get(bb) == out_bb: - continue - visited[bb] = out_bb - - # recurse - _calculate_liveness_r(out_bb, visited) +def _calculate_out_vars(bb: IRBasicBlock) -> bool: + """ + Compute out_vars of basic block. + Returns True if out_vars changed + """ + out_vars = bb.out_vars.copy() + for out_bb in bb.cfg_out: target_vars = input_vars_from(bb, out_bb) - - # the output stack layout for bb. it produces a stack layout - # which works for all possible cfg_outs from the bb. bb.out_vars = bb.out_vars.union(target_vars) - - _calculate_liveness_bb(bb) + return out_vars != bb.out_vars def calculate_liveness(ctx: IRFunction) -> None: _reset_liveness(ctx) - _calculate_liveness_r(ctx.basic_blocks[0], dict()) + while True: + changed = False + for bb in ctx.basic_blocks: + changed |= _calculate_out_vars(bb) + changed |= _calculate_liveness(bb) + + if not changed: + break # calculate the input variables into self from source diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 598b8af7d5..f86e9b330c 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -410,8 +410,8 @@ def copy(self): def __repr__(self) -> str: s = ( f"{repr(self.label)}: IN={[bb.label for bb in self.cfg_in]}" - f" OUT={[bb.label for bb in self.cfg_out]} => {self.out_vars} \n" + f" OUT={[bb.label for bb in self.cfg_out]} => {self.out_vars}\n" ) for instruction in self.instructions: - s += f" {instruction}\n" + s += f" {str(instruction).strip()}\n" return s diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 9f26fa8ec0..771dcf73ce 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -163,4 +163,4 @@ def __repr__(self) -> str: str += "Data segment:\n" for inst in self.data_segment: str += f"{inst}\n" - return str + return str.strip() diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index c86d3a3d67..6b47ac2415 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -87,35 +87,21 @@ def _get_symbols_common(a: dict, b: dict) -> dict: return ret -def _findIRnode(ir: IRnode, value: str) -> Optional[IRnode]: - if ir.value == value: - return ir - for arg in ir.args: - if isinstance(arg, IRnode): - ret = _findIRnode(arg, value) - if ret is not None: - return ret - return None - - -def convert_ir_basicblock(ir: IRnode) -> tuple[IRFunction, IRFunction]: - deploy_ir = _findIRnode(ir, "deploy") - assert deploy_ir is not None - - deploy_venom = IRFunction() - _convert_ir_basicblock(deploy_venom, ir, {}, OrderedSet(), {}) - deploy_venom.get_basic_block().append_instruction("stop") - - runtime_ir = deploy_ir.args[1] - runtime_venom = IRFunction() - _convert_ir_basicblock(runtime_venom, runtime_ir, {}, OrderedSet(), {}) - - # Connect unterminated blocks to the next with a jump - for i, bb in enumerate(runtime_venom.basic_blocks): - if not bb.is_terminated and i < len(runtime_venom.basic_blocks) - 1: - bb.append_instruction("jmp", runtime_venom.basic_blocks[i + 1].label) +# convert IRnode directly to venom +def ir_node_to_venom(ir: IRnode) -> IRFunction: + ctx = IRFunction() + _convert_ir_bb(ctx, ir, {}, OrderedSet(), {}) + + # Patch up basic blocks. Connect unterminated blocks to the next with + # a jump. terminate final basic block with STOP. + for i, bb in enumerate(ctx.basic_blocks): + if not bb.is_terminated: + if i < len(ctx.basic_blocks) - 1: + bb.append_instruction("jmp", ctx.basic_blocks[i + 1].label) + else: + bb.append_instruction("stop") - return deploy_venom, runtime_venom + return ctx def _convert_binary_op( @@ -127,10 +113,10 @@ def _convert_binary_op( swap: bool = False, ) -> Optional[IRVariable]: ir_args = ir.args[::-1] if swap else ir.args - arg_0 = _convert_ir_basicblock(ctx, ir_args[0], symbols, variables, allocated_variables) - arg_1 = _convert_ir_basicblock(ctx, ir_args[1], symbols, variables, allocated_variables) + arg_0, arg_1 = _convert_ir_bb_list(ctx, ir_args, symbols, variables, allocated_variables) - return ctx.get_basic_block().append_instruction(str(ir.value), arg_1, arg_0) + assert isinstance(ir.value, str) # mypy hint + return ctx.get_basic_block().append_instruction(ir.value, arg_1, arg_0) def _append_jmp(ctx: IRFunction, label: IRLabel) -> None: @@ -165,14 +151,12 @@ def _handle_self_call( if arg.is_literal: sym = symbols.get(f"&{arg.value}", None) if sym is None: - ret = _convert_ir_basicblock(ctx, arg, symbols, variables, allocated_variables) + ret = _convert_ir_bb(ctx, arg, symbols, variables, allocated_variables) ret_args.append(ret) else: ret_args.append(sym) # type: ignore else: - ret = _convert_ir_basicblock( - ctx, arg._optimized, symbols, variables, allocated_variables - ) + ret = _convert_ir_bb(ctx, arg._optimized, symbols, variables, allocated_variables) if arg.location and arg.location.load_op == "calldataload": bb = ctx.get_basic_block() ret = bb.append_instruction(arg.location.load_op, ret) @@ -225,9 +209,7 @@ def _convert_ir_simple_node( variables: OrderedSet, allocated_variables: dict[str, IRVariable], ) -> Optional[IRVariable]: - args = [ - _convert_ir_basicblock(ctx, arg, symbols, variables, allocated_variables) for arg in ir.args - ] + args = [_convert_ir_bb(ctx, arg, symbols, variables, allocated_variables) for arg in ir.args] return ctx.get_basic_block().append_instruction(ir.value, *args) # type: ignore @@ -266,7 +248,16 @@ def _append_return_for_stack_operand( bb.append_instruction("return", last_ir, new_var) # type: ignore -def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): +def _convert_ir_bb_list(ctx, ir, symbols, variables, allocated_variables): + ret = [] + for ir_node in ir: + venom = _convert_ir_bb(ctx, ir_node, symbols, variables, allocated_variables) + ret.append(venom) + return ret + + +def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): + assert isinstance(ir, IRnode) assert isinstance(variables, OrderedSet) global _break_target, _continue_target @@ -314,35 +305,22 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): ret = None for ir_node in ir.args: # NOTE: skip the last one - ret = _convert_ir_basicblock(ctx, ir_node, symbols, variables, allocated_variables) + ret = _convert_ir_bb(ctx, ir_node, symbols, variables, allocated_variables) return ret elif ir.value in ["staticcall", "call"]: # external call idx = 0 - gas = _convert_ir_basicblock(ctx, ir.args[idx], symbols, variables, allocated_variables) - address = _convert_ir_basicblock( - ctx, ir.args[idx + 1], symbols, variables, allocated_variables - ) + gas = _convert_ir_bb(ctx, ir.args[idx], symbols, variables, allocated_variables) + address = _convert_ir_bb(ctx, ir.args[idx + 1], symbols, variables, allocated_variables) value = None if ir.value == "call": - value = _convert_ir_basicblock( - ctx, ir.args[idx + 2], symbols, variables, allocated_variables - ) + value = _convert_ir_bb(ctx, ir.args[idx + 2], symbols, variables, allocated_variables) else: idx -= 1 - argsOffset = _convert_ir_basicblock( - ctx, ir.args[idx + 3], symbols, variables, allocated_variables - ) - argsSize = _convert_ir_basicblock( - ctx, ir.args[idx + 4], symbols, variables, allocated_variables - ) - retOffset = _convert_ir_basicblock( - ctx, ir.args[idx + 5], symbols, variables, allocated_variables - ) - retSize = _convert_ir_basicblock( - ctx, ir.args[idx + 6], symbols, variables, allocated_variables + argsOffset, argsSize, retOffset, retSize = _convert_ir_bb_list( + ctx, ir.args[idx + 3 : idx + 7], symbols, variables, allocated_variables ) if isinstance(argsOffset, IRLiteral): @@ -374,10 +352,10 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): return bb.append_instruction(ir.value, *args) elif ir.value == "if": cond = ir.args[0] - current_bb = ctx.get_basic_block() # convert the condition - cont_ret = _convert_ir_basicblock(ctx, cond, symbols, variables, allocated_variables) + cont_ret = _convert_ir_bb(ctx, cond, symbols, variables, allocated_variables) + current_bb = ctx.get_basic_block() else_block = IRBasicBlock(ctx.get_next_label(), ctx) ctx.append_basic_block(else_block) @@ -386,42 +364,44 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): else_ret_val = None else_syms = symbols.copy() if len(ir.args) == 3: - else_ret_val = _convert_ir_basicblock( + else_ret_val = _convert_ir_bb( ctx, ir.args[2], else_syms, variables, allocated_variables.copy() ) if isinstance(else_ret_val, IRLiteral): assert isinstance(else_ret_val.value, int) # help mypy else_ret_val = ctx.get_basic_block().append_instruction("store", else_ret_val) after_else_syms = else_syms.copy() + else_block = ctx.get_basic_block() # convert "then" then_block = IRBasicBlock(ctx.get_next_label(), ctx) ctx.append_basic_block(then_block) - then_ret_val = _convert_ir_basicblock( - ctx, ir.args[1], symbols, variables, allocated_variables - ) + then_ret_val = _convert_ir_bb(ctx, ir.args[1], symbols, variables, allocated_variables) if isinstance(then_ret_val, IRLiteral): then_ret_val = ctx.get_basic_block().append_instruction("store", then_ret_val) current_bb.append_instruction("jnz", cont_ret, then_block.label, else_block.label) after_then_syms = symbols.copy() + then_block = ctx.get_basic_block() # exit bb exit_label = ctx.get_next_label() - bb = IRBasicBlock(exit_label, ctx) - bb = ctx.append_basic_block(bb) + exit_bb = IRBasicBlock(exit_label, ctx) + exit_bb = ctx.append_basic_block(exit_bb) if_ret = None if then_ret_val is not None and else_ret_val is not None: - if_ret = bb.append_instruction( + if_ret = exit_bb.append_instruction( "phi", then_block.label, then_ret_val, else_block.label, else_ret_val ) common_symbols = _get_symbols_common(after_then_syms, after_else_syms) for sym, val in common_symbols.items(): - ret = bb.append_instruction("phi", then_block.label, val[0], else_block.label, val[1]) + ret = exit_bb.append_instruction( + "phi", then_block.label, val[0], else_block.label, val[1] + ) old_var = symbols.get(sym, None) symbols[sym] = ret if old_var is not None: @@ -430,15 +410,15 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): allocated_variables[idx] = ret # type: ignore if not else_block.is_terminated: - else_block.append_instruction("jmp", bb.label) + else_block.append_instruction("jmp", exit_bb.label) if not then_block.is_terminated: - then_block.append_instruction("jmp", bb.label) + then_block.append_instruction("jmp", exit_bb.label) return if_ret elif ir.value == "with": - ret = _convert_ir_basicblock( + ret = _convert_ir_bb( ctx, ir.args[1], symbols, variables, allocated_variables ) # initialization @@ -452,27 +432,25 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): else: with_symbols[sym.value] = ret # type: ignore - return _convert_ir_basicblock( - ctx, ir.args[2], with_symbols, variables, allocated_variables - ) # body + return _convert_ir_bb(ctx, ir.args[2], with_symbols, variables, allocated_variables) # body elif ir.value == "goto": _append_jmp(ctx, IRLabel(ir.args[0].value)) elif ir.value == "djump": - args = [_convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables)] + args = [_convert_ir_bb(ctx, ir.args[0], symbols, variables, allocated_variables)] for target in ir.args[1:]: args.append(IRLabel(target.value)) ctx.get_basic_block().append_instruction("djmp", *args) _new_block(ctx) elif ir.value == "set": sym = ir.args[0] - arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables, allocated_variables) + arg_1 = _convert_ir_bb(ctx, ir.args[1], symbols, variables, allocated_variables) new_var = ctx.get_basic_block().append_instruction("store", arg_1) # type: ignore symbols[sym.value] = new_var elif ir.value == "calldatacopy": - arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) - arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables, allocated_variables) - size = _convert_ir_basicblock(ctx, ir.args[2], symbols, variables, allocated_variables) + arg_0, arg_1, size = _convert_ir_bb_list( + ctx, ir.args, symbols, variables, allocated_variables + ) new_v = arg_0 var = ( @@ -492,9 +470,9 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): return new_v elif ir.value == "codecopy": - arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) - arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables, allocated_variables) - size = _convert_ir_basicblock(ctx, ir.args[2], symbols, variables, allocated_variables) + arg_0, arg_1, size = _convert_ir_bb_list( + ctx, ir.args, symbols, variables, allocated_variables + ) ctx.get_basic_block().append_instruction("codecopy", size, arg_1, arg_0) # type: ignore elif ir.value == "symbol": @@ -509,10 +487,10 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): elif isinstance(c, bytes): ctx.append_data("db", [c]) # type: ignore elif isinstance(c, IRnode): - data = _convert_ir_basicblock(ctx, c, symbols, variables, allocated_variables) + data = _convert_ir_bb(ctx, c, symbols, variables, allocated_variables) ctx.append_data("db", [data]) # type: ignore elif ir.value == "assert": - arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) + arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols, variables, allocated_variables) current_bb = ctx.get_basic_block() current_bb.append_instruction("assert", arg_0) elif ir.value == "label": @@ -522,7 +500,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): bb.append_instruction("jmp", label) bb = IRBasicBlock(label, ctx) ctx.append_basic_block(bb) - _convert_ir_basicblock(ctx, ir.args[2], symbols, variables, allocated_variables) + _convert_ir_bb(ctx, ir.args[2], symbols, variables, allocated_variables) elif ir.value == "exit_to": func_t = ir.passthrough_metadata.get("func_t", None) assert func_t is not None, "exit_to without func_t" @@ -545,15 +523,11 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): deleted = symbols[f"&{ret_var.value}"] del symbols[f"&{ret_var.value}"] for arg in ir.args[2:]: - last_ir = _convert_ir_basicblock( - ctx, arg, symbols, variables, allocated_variables - ) + last_ir = _convert_ir_bb(ctx, arg, symbols, variables, allocated_variables) if deleted is not None: symbols[f"&{ret_var.value}"] = deleted - ret_ir = _convert_ir_basicblock( - ctx, ret_var, symbols, variables, allocated_variables - ) + ret_ir = _convert_ir_bb(ctx, ret_var, symbols, variables, allocated_variables) bb = ctx.get_basic_block() @@ -612,12 +586,11 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): bb.append_instruction("ret", ret_by_value, symbols["return_pc"]) elif ir.value == "revert": - arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) - arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables, allocated_variables) + arg_0, arg_1 = _convert_ir_bb_list(ctx, ir.args, symbols, variables, allocated_variables) ctx.get_basic_block().append_instruction("revert", arg_1, arg_0) elif ir.value == "dload": - arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) + arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols, variables, allocated_variables) bb = ctx.get_basic_block() src = bb.append_instruction("add", arg_0, IRLabel("code_end")) @@ -625,11 +598,10 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): return bb.append_instruction("mload", MemoryPositions.FREE_VAR_SPACE) elif ir.value == "dloadbytes": - dst = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) - src_offset = _convert_ir_basicblock( - ctx, ir.args[1], symbols, variables, allocated_variables + dst, src_offset, len_ = _convert_ir_bb_list( + ctx, ir.args, symbols, variables, allocated_variables ) - len_ = _convert_ir_basicblock(ctx, ir.args[2], symbols, variables, allocated_variables) + bb = ctx.get_basic_block() src = bb.append_instruction("add", src_offset, IRLabel("code_end")) bb.append_instruction("dloadbytes", len_, src, dst) @@ -678,9 +650,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): else: return bb.append_instruction("mload", sym_ir.value) else: - new_var = _convert_ir_basicblock( - ctx, sym_ir, symbols, variables, allocated_variables - ) + new_var = _convert_ir_bb(ctx, sym_ir, symbols, variables, allocated_variables) # # Old IR gets it's return value as a reference in the stack # New IR gets it's return value in stack in case of 32 bytes or less @@ -692,8 +662,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): return bb.append_instruction("mload", new_var) elif ir.value == "mstore": - sym_ir = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) - arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables, allocated_variables) + sym_ir, arg_1 = _convert_ir_bb_list(ctx, ir.args, symbols, variables, allocated_variables) bb = ctx.get_basic_block() @@ -742,11 +711,10 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): return arg_1 elif ir.value in ["sload", "iload"]: - arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) + arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols, variables, allocated_variables) return ctx.get_basic_block().append_instruction(ir.value, arg_0) elif ir.value in ["sstore", "istore"]: - arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) - arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables, allocated_variables) + arg_0, arg_1 = _convert_ir_bb_list(ctx, ir.args, symbols, variables, allocated_variables) ctx.get_basic_block().append_instruction(ir.value, arg_1, arg_0) elif ir.value == "unique_symbol": sym = ir.args[0] @@ -763,18 +731,18 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): # 5) increment block # 6) exit block # TODO: Add the extra bounds check after clarify - def emit_body_block(): + def emit_body_blocks(): global _break_target, _continue_target old_targets = _break_target, _continue_target _break_target, _continue_target = exit_block, increment_block - _convert_ir_basicblock(ctx, body, symbols, variables, allocated_variables) + _convert_ir_bb(ctx, body, symbols, variables, allocated_variables) _break_target, _continue_target = old_targets sym = ir.args[0] - start = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables, allocated_variables) - end = _convert_ir_basicblock(ctx, ir.args[2], symbols, variables, allocated_variables) - # "bound" is not used - _ = _convert_ir_basicblock(ctx, ir.args[3], symbols, variables, allocated_variables) + start, end, _ = _convert_ir_bb_list( + ctx, ir.args[1:4], symbols, variables, allocated_variables + ) + body = ir.args[4] entry_block = ctx.get_basic_block() @@ -799,10 +767,9 @@ def emit_body_block(): cont_ret = cond_block.append_instruction("iszero", xor_ret) ctx.append_basic_block(cond_block) - # Do a dry run to get the symbols needing phi nodes start_syms = symbols.copy() ctx.append_basic_block(body_block) - emit_body_block() + emit_body_blocks() end_syms = symbols.copy() diff_syms = _get_symbols_common(start_syms, end_syms) @@ -828,8 +795,9 @@ def emit_body_block(): jump_up_block.append_instruction("jmp", increment_block.label) ctx.append_basic_block(jump_up_block) - increment_block.append_instruction(IRInstruction("add", ret, 1)) - increment_block.insert_instruction[-1].output = counter_inc_var + increment_block.insert_instruction( + IRInstruction("add", [ret, IRLiteral(1)], counter_inc_var), 0 + ) increment_block.append_instruction("jmp", cond_block.label) ctx.append_basic_block(increment_block) @@ -851,23 +819,20 @@ def emit_body_block(): return ctx.get_basic_block().append_instruction("returndatasize") elif ir.value == "returndatacopy": assert len(ir.args) == 3, "returndatacopy with wrong number of arguments" - arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) - arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables, allocated_variables) - size = _convert_ir_basicblock(ctx, ir.args[2], symbols, variables, allocated_variables) + arg_0, arg_1, size = _convert_ir_bb_list( + ctx, ir.args, symbols, variables, allocated_variables + ) new_var = ctx.get_basic_block().append_instruction("returndatacopy", arg_1, size) symbols[f"&{arg_0.value}"] = new_var return new_var elif ir.value == "selfdestruct": - arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) + arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols, variables, allocated_variables) ctx.get_basic_block().append_instruction("selfdestruct", arg_0) elif isinstance(ir.value, str) and ir.value.startswith("log"): args = reversed( - [ - _convert_ir_basicblock(ctx, arg, symbols, variables, allocated_variables) - for arg in ir.args - ] + [_convert_ir_bb(ctx, arg, symbols, variables, allocated_variables) for arg in ir.args] ) topic_count = int(ir.value[3:]) assert topic_count >= 0 and topic_count <= 4, "invalid topic count" @@ -895,9 +860,7 @@ def _convert_ir_opcode( inst_args = [] for arg in ir.args: if isinstance(arg, IRnode): - inst_args.append( - _convert_ir_basicblock(ctx, arg, symbols, variables, allocated_variables) - ) + inst_args.append(_convert_ir_bb(ctx, arg, symbols, variables, allocated_variables)) ctx.get_basic_block().append_instruction(opcode, *inst_args)