def split_blocks_at_errors(blocks: List[BasicBlock], default_error_handler: BasicBlock, func: str) -> List[BasicBlock]: new_blocks = [] # type: List[BasicBlock] # First split blocks on ops that may raise. for block in blocks: ops = block.ops block.ops = [] cur_block = block new_blocks.append(cur_block) # If the block has an error handler specified, use it. Otherwise # fall back to the default. error_label = block.error_handler or default_error_handler block.error_handler = None for op in ops: cur_block.ops.append(op) if isinstance(op, RegisterOp) and op.error_kind != ERR_NEVER: # Split new_block = BasicBlock() new_blocks.append(new_block) if op.error_kind == ERR_MAGIC: # Op returns an error value on error that depends on result RType. variant = Branch.IS_ERROR negated = False elif op.error_kind == ERR_FALSE: # Op returns a C false value on error. variant = Branch.BOOL_EXPR negated = True else: assert False, 'unknown error kind %d' % op.error_kind # Void ops can't generate errors since error is always # indicated by a special value stored in a register. assert not op.is_void, "void op generating errors?" branch = Branch(op, true_label=error_label, false_label=new_block, op=variant, line=op.line) branch.negated = negated if op.line != NO_TRACEBACK_LINE_NO: branch.traceback_entry = (func, op.line) cur_block.ops.append(branch) cur_block = new_block return new_blocks
def gen_cleanup(self, builder: 'IRBuilder', line: int) -> None: # Do an error branch on the return value register, which # may be undefined. This will allow it to be properly # decrefed if it is not null. This is kind of a hack. if self.ret_reg: target = BasicBlock() builder.add(Branch(self.ret_reg, target, target, Branch.IS_ERROR)) builder.activate_block(target) # Restore the old exc_info target, cleanup = BasicBlock(), BasicBlock() builder.add(Branch(self.saved, target, cleanup, Branch.IS_ERROR)) builder.activate_block(cleanup) builder.primitive_op(restore_exc_info_op, [self.saved], line) builder.goto_and_activate(target)
def test_branch(self) -> None: self.assert_emit(Branch(self.b, BasicBlock(8), BasicBlock(9), Branch.BOOL_EXPR), """if (cpy_r_b) { goto CPyL8; } else goto CPyL9; """) b = Branch(self.b, BasicBlock(8), BasicBlock(9), Branch.BOOL_EXPR) b.negated = True self.assert_emit(b, """if (!cpy_r_b) { goto CPyL8; } else goto CPyL9; """)
def test_branch_eq(self) -> None: self.assert_emit( Branch(self.n, self.m, Label(8), Label(9), Branch.INT_EQ), """if (CPyTagged_IsEq(cpy_r_n, cpy_r_m)) goto CPyL8; else goto CPyL9; """) b = Branch(self.n, self.m, Label(8), Label(9), Branch.INT_LT) b.negated = True self.assert_emit( b, """if (!CPyTagged_IsLt(cpy_r_n, cpy_r_m)) goto CPyL8; else goto CPyL9; """)
def gen_condition(self) -> None: # We call __next__ on the iterator and check to see if the return value # is NULL, which signals either the end of the Iterable being traversed # or an exception being raised. Note that Branch.IS_ERROR checks only # for NULL (an exception does not necessarily have to be raised). builder = self.builder line = self.line self.next_reg = builder.primitive_op(next_op, [builder.read(self.iter_target, line)], line) builder.add(Branch(self.next_reg, self.loop_exit, self.body_block, Branch.IS_ERROR))
def add_bool_branch(self, value: Value, true: BasicBlock, false: BasicBlock) -> None: if is_runtime_subtype(value.type, int_rprimitive): zero = self.add(LoadInt(0)) value = self.binary_op(value, zero, '!=', value.line) elif is_same_type(value.type, list_rprimitive): length = self.primitive_op(list_len_op, [value], value.line) zero = self.add(LoadInt(0)) value = self.binary_op(length, zero, '!=', value.line) elif (isinstance(value.type, RInstance) and value.type.class_ir.is_ext_class and value.type.class_ir.has_method('__bool__')): # Directly call the __bool__ method on classes that have it. value = self.gen_method_call(value, '__bool__', [], bool_rprimitive, value.line) else: value_type = optional_value_type(value.type) if value_type is not None: is_none = self.binary_op(value, self.none_object(), 'is not', value.line) branch = Branch(is_none, true, false, Branch.BOOL_EXPR) self.add(branch) always_truthy = False if isinstance(value_type, RInstance): # check whether X.__bool__ is always just the default (object.__bool__) if (not value_type.class_ir.has_method('__bool__') and value_type.class_ir.is_method_final('__bool__')): always_truthy = True if not always_truthy: # Optional[X] where X may be falsey and requires a check branch.true = BasicBlock() self.activate_block(branch.true) # unbox_or_cast instead of coerce because we want the # type to change even if it is a subtype. remaining = self.unbox_or_cast(value, value_type, value.line) self.add_bool_branch(remaining, true, false) return elif not is_same_type(value.type, bool_rprimitive): value = self.primitive_op(bool_op, [value], value.line) self.add(Branch(value, true, false, Branch.BOOL_EXPR))
def finally_body() -> None: out_block, exit_block = BasicBlock(), BasicBlock() builder.add( Branch(builder.read(exc), exit_block, out_block, Branch.BOOL_EXPR) ) builder.activate_block(exit_block) none = builder.none_object() builder.py_call( builder.read(exit_), [builder.read(mgr), none, none, none], line ) builder.goto_and_activate(out_block)
def try_finally_resolve_control(builder: IRBuilder, cleanup_block: BasicBlock, finally_control: FinallyNonlocalControl, old_exc: Value, ret_reg: Optional[Value]) -> BasicBlock: """Resolve the control flow out of a finally block. This means returning if there was a return, propagating exceptions, break/continue (soon), or just continuing on. """ reraise, rest = BasicBlock(), BasicBlock() builder.add(Branch(old_exc, rest, reraise, Branch.IS_ERROR)) # Reraise the exception if there was one builder.activate_block(reraise) builder.primitive_op(reraise_exception_op, [], NO_TRACEBACK_LINE_NO) builder.add(Unreachable()) builder.builder.pop_error_handler() # If there was a return, keep returning if ret_reg: builder.activate_block(rest) return_block, rest = BasicBlock(), BasicBlock() builder.add(Branch(ret_reg, rest, return_block, Branch.IS_ERROR)) builder.activate_block(return_block) builder.nonlocal_control[-1].gen_return(builder, ret_reg, -1) # TODO: handle break/continue builder.activate_block(rest) out_block = BasicBlock() builder.goto(out_block) # If there was an exception, restore again builder.activate_block(cleanup_block) finally_control.gen_cleanup(builder, -1) builder.primitive_op(keep_propagating_op, [], NO_TRACEBACK_LINE_NO) builder.add(Unreachable()) return out_block
def split_blocks_at_uninits( env: Environment, blocks: List[BasicBlock], pre_must_defined: 'AnalysisDict[Value]') -> List[BasicBlock]: new_blocks = [] # type: List[BasicBlock] # First split blocks on ops that may raise. for block in blocks: ops = block.ops block.ops = [] cur_block = block new_blocks.append(cur_block) for i, op in enumerate(ops): defined = pre_must_defined[block, i] for src in op.unique_sources(): # If a register operand is not guarenteed to be # initialized is an operand to something other than a # check that it is defined, insert a check. if (isinstance(src, Register) and src not in defined and not (isinstance(op, Branch) and op.op == Branch.IS_ERROR)): new_block, error_block = BasicBlock(), BasicBlock() new_block.error_handler = error_block.error_handler = cur_block.error_handler new_blocks += [error_block, new_block] env.vars_needing_init.add(src) cur_block.ops.append( Branch(src, true_label=error_block, false_label=new_block, op=Branch.IS_ERROR, line=op.line)) raise_std = RaiseStandardError( RaiseStandardError.UNBOUND_LOCAL_ERROR, "local variable '{}' referenced before assignment". format(src.name), op.line) env.add_op(raise_std) error_block.ops.append(raise_std) error_block.ops.append(Unreachable()) cur_block = new_block cur_block.ops.append(op) return new_blocks
def gen_glue_ne_method(self, cls: ClassIR, line: int) -> FuncIR: """Generate a __ne__ method from a __eq__ method. """ self.builder.enter() rt_args = (RuntimeArg("self", RInstance(cls)), RuntimeArg("rhs", object_rprimitive)) # The environment operates on Vars, so we make some up fake_vars = [(Var(arg.name), arg.type) for arg in rt_args] args = [ self.builder.read( self.builder.environment.add_local_reg( var, type, is_arg=True ), line ) for var, type in fake_vars ] # type: List[Value] self.builder.ret_types[-1] = object_rprimitive # If __eq__ returns NotImplemented, then __ne__ should also not_implemented_block, regular_block = BasicBlock(), BasicBlock() eqval = self.add(MethodCall(args[0], '__eq__', [args[1]], line)) not_implemented = self.primitive_op(not_implemented_op, [], line) self.add(Branch( self.builder.binary_op(eqval, not_implemented, 'is', line), not_implemented_block, regular_block, Branch.BOOL_EXPR)) self.builder.activate_block(regular_block) retval = self.builder.coerce( self.builder.unary_op(eqval, 'not', line), object_rprimitive, line ) self.add(Return(retval)) self.builder.activate_block(not_implemented_block) self.add(Return(not_implemented)) blocks, env, ret_type, _ = self.builder.leave() return FuncIR( FuncDecl('__ne__', cls.name, self.module_name, FuncSignature(rt_args, ret_type)), blocks, env)
def visit_branch(self, op: Branch) -> GenAndKill: return set(op.sources()), set()
def split_blocks_at_errors(blocks: List[BasicBlock], default_error_handler: BasicBlock, func: str) -> List[BasicBlock]: new_blocks = [] # type: List[BasicBlock] mapping = {} partial_ops = set() # First split blocks on ops that may raise. for block in blocks: ops = block.ops i0 = 0 i = 0 next_block = BasicBlock() while i < len(ops) - 1: op = ops[i] if isinstance(op, RegisterOp) and op.error_kind != ERR_NEVER: # Split new_blocks.append(next_block) new_block = next_block next_block = BasicBlock() new_block.ops.extend(ops[i0:i + 1]) if op.error_kind == ERR_MAGIC: # Op returns an error value on error that depends on result RType. variant = Branch.IS_ERROR negated = False elif op.error_kind == ERR_FALSE: # Op returns a C false value on error. variant = Branch.BOOL_EXPR negated = True else: assert False, 'unknown error kind %d' % op.error_kind # Void ops can't generate errors since error is always # indicated by a special value stored in a register. assert not op.is_void, "void op generating errors?" # If the block has an error handler specified, use it. Otherwise # fall back to the default. error_label = block.error_handler or default_error_handler branch = Branch(op, true_label=error_label, false_label=next_block, op=variant, line=op.line) branch.negated = negated if op.line != NO_TRACEBACK_LINE_NO: branch.traceback_entry = (func, op.line) partial_ops.add(branch) # Only tweak true label of these new_block.ops.append(branch) if i0 == 0: mapping[block] = new_block i += 1 i0 = i else: i += 1 new_blocks.append(next_block) next_block.ops.extend(ops[i0:i + 1]) if i0 == 0: mapping[block] = next_block # Adjust all labels to reflect the new blocks. for block in new_blocks: for op in block.ops: if isinstance(op, Goto): op.label = mapping[op.label] elif isinstance(op, Branch): if op not in partial_ops: op.false = mapping[op.false] op.true = mapping[op.true] return new_blocks
def transform_try_except(builder: IRBuilder, body: GenFunc, handlers: Sequence[ Tuple[Optional[Expression], Optional[Expression], GenFunc]], else_body: Optional[GenFunc], line: int) -> None: """Generalized try/except/else handling that takes functions to gen the bodies. The point of this is to also be able to support with.""" assert handlers, "try needs except" except_entry, exit_block, cleanup_block = BasicBlock(), BasicBlock(), BasicBlock() double_except_block = BasicBlock() # If there is an else block, jump there after the try, otherwise just leave else_block = BasicBlock() if else_body else exit_block # Compile the try block with an error handler builder.builder.push_error_handler(except_entry) builder.goto_and_activate(BasicBlock()) body() builder.goto(else_block) builder.builder.pop_error_handler() # The error handler catches the error and then checks it # against the except clauses. We compile the error handler # itself with an error handler so that it can properly restore # the *old* exc_info if an exception occurs. # The exception chaining will be done automatically when the # exception is raised, based on the exception in exc_info. builder.builder.push_error_handler(double_except_block) builder.activate_block(except_entry) old_exc = builder.maybe_spill(builder.primitive_op(error_catch_op, [], line)) # Compile the except blocks with the nonlocal control flow overridden to clear exc_info builder.nonlocal_control.append( ExceptNonlocalControl(builder.nonlocal_control[-1], old_exc)) # Process the bodies for type, var, handler_body in handlers: next_block = None if type: next_block, body_block = BasicBlock(), BasicBlock() matches = builder.primitive_op( exc_matches_op, [builder.accept(type)], type.line ) builder.add(Branch(matches, body_block, next_block, Branch.BOOL_EXPR)) builder.activate_block(body_block) if var: target = builder.get_assignment_target(var) builder.assign( target, builder.primitive_op(get_exc_value_op, [], var.line), var.line ) handler_body() builder.goto(cleanup_block) if next_block: builder.activate_block(next_block) # Reraise the exception if needed if next_block: builder.primitive_op(reraise_exception_op, [], NO_TRACEBACK_LINE_NO) builder.add(Unreachable()) builder.nonlocal_control.pop() builder.builder.pop_error_handler() # Cleanup for if we leave except through normal control flow: # restore the saved exc_info information and continue propagating # the exception if it exists. builder.activate_block(cleanup_block) builder.primitive_op(restore_exc_info_op, [builder.read(old_exc)], line) builder.goto(exit_block) # Cleanup for if we leave except through a raised exception: # restore the saved exc_info information and continue propagating # the exception. builder.activate_block(double_except_block) builder.primitive_op(restore_exc_info_op, [builder.read(old_exc)], line) builder.primitive_op(keep_propagating_op, [], NO_TRACEBACK_LINE_NO) builder.add(Unreachable()) # If present, compile the else body in the obvious way if else_body: builder.activate_block(else_block) else_body() builder.goto(exit_block) builder.activate_block(exit_block)
def process_conditional(self, e: Node) -> List[Branch]: if isinstance(e, ComparisonExpr): # TODO: Verify operand types. assert len(e.operators) == 1, 'more than 1 operator not supported' op = e.operators[0] if op in ['==', '!=', '<', '<=', '>', '>=']: # TODO: check operand types left = self.accept(e.operands[0]) right = self.accept(e.operands[1]) opcode = self.int_relative_ops[op] branch = Branch(left, right, INVALID_LABEL, INVALID_LABEL, opcode) elif op in ['is', 'is not']: # TODO: check if right operand is None left = self.accept(e.operands[0]) branch = Branch(left, INVALID_REGISTER, INVALID_LABEL, INVALID_LABEL, Branch.IS_NONE) if op == 'is not': branch.negated = True elif op in ['in', 'not in']: left = self.accept(e.operands[0]) ltype = self.node_type(e.operands[0]) right = self.accept(e.operands[1]) rtype = self.node_type(e.operands[1]) target = self.alloc_temp(self.node_type(e)) self.binary_op(ltype, left, rtype, right, 'in', target=target) branch = Branch(target, INVALID_REGISTER, INVALID_LABEL, INVALID_LABEL, Branch.BOOL_EXPR) if op == 'not in': branch.negated = True else: assert False, "unsupported comparison epxression" self.add(branch) return [branch] elif isinstance(e, OpExpr) and e.op in ['and', 'or']: if e.op == 'and': # Short circuit 'and' in a conditional context. lbranches = self.process_conditional(e.left) new = self.new_block() self.set_branches(lbranches, True, new) rbranches = self.process_conditional(e.right) return lbranches + rbranches else: # Short circuit 'or' in a conditional context. lbranches = self.process_conditional(e.left) new = self.new_block() self.set_branches(lbranches, False, new) rbranches = self.process_conditional(e.right) return lbranches + rbranches elif isinstance(e, UnaryExpr) and e.op == 'not': branches = self.process_conditional(e.expr) for b in branches: b.invert() return branches # Catch-all for arbitrary expressions. else: reg = self.accept(e) branch = Branch(reg, INVALID_REGISTER, INVALID_LABEL, INVALID_LABEL, Branch.BOOL_EXPR) self.add(branch) return [branch]
def visit_for_stmt(self, s: ForStmt) -> Register: if (isinstance(s.expr, CallExpr) and isinstance(s.expr.callee, RefExpr) and s.expr.callee.fullname == 'builtins.range'): self.push_loop_stack() # Special case for x in range(...) # TODO: Check argument counts and kinds; check the lvalue end = s.expr.args[0] end_reg = self.accept(end) # Initialize loop index to 0. index_reg = self.assign(s.index, IntExpr(0), IntRType(), IntRType(), declare_new=True) goto = Goto(INVALID_LABEL) self.add(goto) # Add loop condition check. top = self.new_block() goto.label = top.label branch = Branch(index_reg, end_reg, INVALID_LABEL, INVALID_LABEL, Branch.INT_LT) self.add(branch) branches = [branch] body = self.new_block() self.set_branches(branches, True, body) s.body.accept(self) end_goto = Goto(INVALID_LABEL) self.add(end_goto) end_block = self.new_block() end_goto.label = end_block.label # Increment index register. one_reg = self.alloc_temp(IntRType()) self.add(LoadInt(one_reg, 1)) self.add( PrimitiveOp(index_reg, PrimitiveOp.INT_ADD, index_reg, one_reg)) # Go back to loop condition check. self.add(Goto(top.label)) next = self.new_block() self.set_branches(branches, False, next) self.pop_loop_stack(end_block, next) return INVALID_REGISTER if self.node_type(s.expr).name == 'list': self.push_loop_stack() expr_reg = self.accept(s.expr) index_reg = self.alloc_temp(IntRType()) self.add(LoadInt(index_reg, 0)) one_reg = self.alloc_temp(IntRType()) self.add(LoadInt(one_reg, 1)) assert isinstance(s.index, NameExpr) assert isinstance(s.index.node, Var) lvalue_reg = self.environment.add_local(s.index.node, self.node_type(s.index)) condition_block = self.goto_new_block() # For compatibility with python semantics we recalculate the length # at every iteration. len_reg = self.alloc_temp(IntRType()) self.add(PrimitiveOp(len_reg, PrimitiveOp.LIST_LEN, expr_reg)) branch = Branch(index_reg, len_reg, INVALID_LABEL, INVALID_LABEL, Branch.INT_LT) self.add(branch) branches = [branch] body_block = self.new_block() self.set_branches(branches, True, body_block) target_list_type = self.types[s.expr] assert isinstance(target_list_type, Instance) target_type = self.type_to_rtype(target_list_type.args[0]) value_box = self.alloc_temp(ObjectRType()) self.add( PrimitiveOp(value_box, PrimitiveOp.LIST_GET, expr_reg, index_reg)) self.unbox_or_cast(value_box, target_type, target=lvalue_reg) s.body.accept(self) end_block = self.goto_new_block() self.add( PrimitiveOp(index_reg, PrimitiveOp.INT_ADD, index_reg, one_reg)) self.add(Goto(condition_block.label)) next_block = self.new_block() self.set_branches(branches, False, next_block) self.pop_loop_stack(end_block, next_block) return INVALID_REGISTER assert False, 'for not supported'