def split_blocks_at_errors(blocks: List[BasicBlock], default_error_handler: BasicBlock, func_name: Optional[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: target = op # type: Value 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 negated = True elif op.error_kind == ERR_ALWAYS: variant = Branch.BOOL negated = True # this is a hack to represent the always fail # semantics, using a temporary bool with value false target = Integer(0, bool_rprimitive) 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. if op.error_kind != ERR_ALWAYS: assert not op.is_void, "void op generating errors?" branch = Branch(target, true_label=error_label, false_label=new_block, op=variant, line=op.line) branch.negated = negated if op.line != NO_TRACEBACK_LINE_NO and func_name is not None: branch.traceback_entry = (func_name, op.line) cur_block.ops.append(branch) cur_block = new_block return new_blocks
def test_branch_is_error_next_block(self) -> None: next_block = BasicBlock(8) b = Branch(self.b, next_block, BasicBlock(9), Branch.IS_ERROR) self.assert_emit(b, """if (cpy_r_b != 2) goto CPyL9;""", next_block=next_block) b = Branch(self.b, next_block, BasicBlock(9), Branch.IS_ERROR) b.negated = True self.assert_emit(b, """if (cpy_r_b == 2) goto CPyL9;""", next_block=next_block)
def test_branch_no_else_negated(self) -> None: next_block = BasicBlock(1) b = Branch(self.b, next_block, BasicBlock(2), Branch.BOOL) self.assert_emit(b, """if (!cpy_r_b) goto CPyL2;""", next_block=next_block) next_block = BasicBlock(1) b = Branch(self.b, next_block, BasicBlock(2), Branch.BOOL) b.negated = True self.assert_emit(b, """if (cpy_r_b) goto CPyL2;""", next_block=next_block)
def compare_tuples(self, lhs: Value, rhs: Value, op: str, line: int = -1) -> Value: """Compare two tuples item by item""" # type cast to pass mypy check assert isinstance(lhs.type, RTuple) and isinstance(rhs.type, RTuple) equal = True if op == '==' else False result = self.alloc_temp(bool_rprimitive) # empty tuples if len(lhs.type.types) == 0 and len(rhs.type.types) == 0: self.add( Assign(result, self.true() if equal else self.false(), line)) return result length = len(lhs.type.types) false_assign, true_assign, out = BasicBlock(), BasicBlock( ), BasicBlock() check_blocks = [BasicBlock() for i in range(length)] lhs_items = [self.add(TupleGet(lhs, i, line)) for i in range(length)] rhs_items = [self.add(TupleGet(rhs, i, line)) for i in range(length)] if equal: early_stop, final = false_assign, true_assign else: early_stop, final = true_assign, false_assign for i in range(len(lhs.type.types)): if i != 0: self.activate_block(check_blocks[i]) lhs_item = lhs_items[i] rhs_item = rhs_items[i] compare = self.binary_op(lhs_item, rhs_item, op, line) # Cast to bool if necessary since most types uses comparison returning a object type # See generic_ops.py for more information if not is_bool_rprimitive(compare.type): compare = self.call_c(bool_op, [compare], line) if i < len(lhs.type.types) - 1: branch = Branch(compare, early_stop, check_blocks[i + 1], Branch.BOOL_EXPR) else: branch = Branch(compare, early_stop, final, Branch.BOOL_EXPR) # if op is ==, we branch on false, else branch on true branch.negated = equal self.add(branch) self.activate_block(false_assign) self.add(Assign(result, self.false(), line)) self.goto(out) self.activate_block(true_assign) self.add(Assign(result, self.true(), line)) self.goto_and_activate(out) return result
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.call_c(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_get_attr_merged(self) -> None: op = GetAttr(self.r, 'y', 1) branch = Branch(op, BasicBlock(8), BasicBlock(9), Branch.IS_ERROR) branch.traceback_entry = ('foobar', 123) self.assert_emit(op, """\ cpy_r_r0 = ((mod___AObject *)cpy_r_r)->_y; if (unlikely(cpy_r_r0 == CPY_INT_TAG)) { CPy_AttributeError("prog.py", "foobar", "A", "y", 123, CPyStatic_prog___globals); goto CPyL8; } CPyTagged_INCREF(cpy_r_r0); goto CPyL9; """, next_branch=branch)
def gen_glue_ne_method(builder: IRBuilder, cls: ClassIR, line: int) -> None: """Generate a "__ne__" method from a "__eq__" method. """ builder.enter_method(cls, '__ne__', object_rprimitive) rhs_arg = builder.add_argument('rhs', object_rprimitive) # If __eq__ returns NotImplemented, then __ne__ should also not_implemented_block, regular_block = BasicBlock(), BasicBlock() eqval = builder.add(MethodCall(builder.self(), '__eq__', [rhs_arg], line)) not_implemented = builder.add(LoadAddress(not_implemented_op.type, not_implemented_op.src, line)) builder.add(Branch( builder.translate_is_op(eqval, not_implemented, 'is', line), not_implemented_block, regular_block, Branch.BOOL)) builder.activate_block(regular_block) retval = builder.coerce( builder.unary_op(eqval, 'not', line), object_rprimitive, line ) builder.add(Return(retval)) builder.activate_block(not_implemented_block) builder.add(Return(not_implemented)) builder.leave_method()
def gen_cleanup(self, builder: 'IRBuilder', line: int) -> None: # Restore the old exc_info target, cleanup = BasicBlock(), BasicBlock() builder.add(Branch(self.saved, target, cleanup, Branch.IS_ERROR)) builder.activate_block(cleanup) builder.call_c(restore_exc_info_op, [self.saved], line) builder.goto_and_activate(target)
def test_branch_is_error(self) -> None: b = Branch(self.b, BasicBlock(8), BasicBlock(9), Branch.IS_ERROR) self.assert_emit( b, """if (cpy_r_b == 2) { goto CPyL8; } else goto CPyL9; """) b = Branch(self.b, BasicBlock(8), BasicBlock(9), Branch.IS_ERROR) b.negated = True self.assert_emit( b, """if (cpy_r_b != 2) { goto CPyL8; } else goto CPyL9; """)
def split_blocks_at_uninits(blocks: List[BasicBlock], pre_must_defined: 'AnalysisDict[Value]') -> List[BasicBlock]: new_blocks: List[BasicBlock] = [] init_registers = [] init_registers_set = set() # 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 guaranteed to be # initialized is an operand to something other than a # check that it is defined, insert a check. # Note that for register operand in a LoadAddress op, # we should be able to use it without initialization # as we may need to use its address to update itself if (isinstance(src, Register) and src not in defined and not (isinstance(op, Branch) and op.op == Branch.IS_ERROR) and not isinstance(op, LoadAddress)): new_block, error_block = BasicBlock(), BasicBlock() new_block.error_handler = error_block.error_handler = cur_block.error_handler new_blocks += [error_block, new_block] if src not in init_registers_set: init_registers.append(src) init_registers_set.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) error_block.ops.append(raise_std) error_block.ops.append(Unreachable()) cur_block = new_block cur_block.ops.append(op) if init_registers: new_ops: List[Op] = [] for reg in init_registers: err = LoadErrorValue(reg.type, undefines=True) new_ops.append(err) new_ops.append(Assign(reg, err)) new_blocks[0].ops[0:0] = new_ops return new_blocks
def test_branch_rare(self) -> None: self.assert_emit( Branch(self.b, BasicBlock(8), BasicBlock(9), Branch.BOOL, rare=True), """if (unlikely(cpy_r_b)) { goto CPyL8; } else goto CPyL9; """) next_block = BasicBlock(9) self.assert_emit(Branch(self.b, BasicBlock(8), next_block, Branch.BOOL, rare=True), """if (unlikely(cpy_r_b)) goto CPyL8;""", next_block=next_block) next_block = BasicBlock(8) b = Branch(self.b, next_block, BasicBlock(9), Branch.BOOL, rare=True) self.assert_emit(b, """if (likely(!cpy_r_b)) goto CPyL9;""", next_block=next_block) next_block = BasicBlock(8) b = Branch(self.b, next_block, BasicBlock(9), Branch.BOOL, rare=True) b.negated = True self.assert_emit(b, """if (likely(cpy_r_b)) goto CPyL9;""", next_block=next_block)
def assign_if_null(self, target: AssignmentTargetRegister, get_val: Callable[[], Value], line: int) -> None: """Generate blocks for registers that NULL values.""" error_block, body_block = BasicBlock(), BasicBlock() self.add(Branch(target.register, error_block, body_block, Branch.IS_ERROR)) self.activate_block(error_block) self.add(Assign(target.register, self.coerce(get_val(), target.register.type, line))) self.goto(body_block) self.activate_block(body_block)
def compare_tagged(self, lhs: Value, rhs: Value, op: str, line: int) -> Value: """Compare two tagged integers using given op""" op_type, c_func_desc = int_logical_op_mapping[op] result = self.alloc_temp(bool_rprimitive) short_int_block, int_block, out = BasicBlock(), BasicBlock(), BasicBlock() check = self.check_tagged_short_int(lhs, line) branch = Branch(check, short_int_block, int_block, Branch.BOOL_EXPR) branch.negated = False self.add(branch) self.activate_block(short_int_block) eq = self.binary_int_op(bool_rprimitive, lhs, rhs, op_type, line) self.add(Assign(result, eq, line)) self.goto(out) self.activate_block(int_block) call = self.call_c(c_func_desc, [lhs, rhs], line) self.add(Assign(result, call, line)) self.goto_and_activate(out) return result
def assign_if_null(self, target: Register, get_val: Callable[[], Value], line: int) -> None: """If target is NULL, assign value produced by get_val to it.""" error_block, body_block = BasicBlock(), BasicBlock() self.add(Branch(target, error_block, body_block, Branch.IS_ERROR)) self.activate_block(error_block) self.add(Assign(target, self.coerce(get_val(), target.type, line))) self.goto(body_block) self.activate_block(body_block)
def test_cast_and_branch_no_merge_3(self) -> None: op = Cast(self.r, dict_rprimitive, 1) next_block = BasicBlock(9) branch = Branch(op, BasicBlock(8), next_block, Branch.BOOL) branch.traceback_entry = ('foobar', 123) self.assert_emit( op, """\ if (likely(PyDict_Check(cpy_r_r))) cpy_r_r0 = cpy_r_r; else { CPy_TypeError("dict", cpy_r_r); cpy_r_r0 = NULL; } """, next_block=next_block, next_branch=branch, )
def finally_body() -> None: out_block, exit_block = BasicBlock(), BasicBlock() builder.add( Branch(builder.read(exc), exit_block, out_block, Branch.BOOL)) 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 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.call_c(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 test_cast_and_branch_merge(self) -> None: op = Cast(self.r, dict_rprimitive, 1) next_block = BasicBlock(9) branch = Branch(op, BasicBlock(8), next_block, Branch.IS_ERROR) branch.traceback_entry = ('foobar', 123) self.assert_emit( op, """\ if (likely(PyDict_Check(cpy_r_r))) cpy_r_r0 = cpy_r_r; else { CPy_TypeErrorTraceback("prog.py", "foobar", 123, CPyStatic_prog___globals, "dict", cpy_r_r); goto CPyL8; } """, next_block=next_block, next_branch=branch, skip_next=True, )
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 compare_tagged(self, lhs: Value, rhs: Value, op: str, line: int) -> Value: """Compare two tagged integers using given op""" # generate fast binary logic ops on short ints if is_short_int_rprimitive(lhs.type) and is_short_int_rprimitive( rhs.type): return self.comparison_op(lhs, rhs, int_comparison_op_mapping[op][0], line) op_type, c_func_desc, negate_result, swap_op = int_comparison_op_mapping[ op] result = self.alloc_temp(bool_rprimitive) short_int_block, int_block, out = BasicBlock(), BasicBlock( ), BasicBlock() check_lhs = self.check_tagged_short_int(lhs, line) if op in ("==", "!="): check = check_lhs else: # for non-equal logical ops(less than, greater than, etc.), need to check both side check_rhs = self.check_tagged_short_int(rhs, line) check = self.binary_int_op(bool_rprimitive, check_lhs, check_rhs, BinaryIntOp.AND, line) branch = Branch(check, short_int_block, int_block, Branch.BOOL_EXPR) branch.negated = False self.add(branch) self.activate_block(short_int_block) eq = self.comparison_op(lhs, rhs, op_type, line) self.add(Assign(result, eq, line)) self.goto(out) self.activate_block(int_block) if swap_op: args = [rhs, lhs] else: args = [lhs, rhs] call = self.call_c(c_func_desc, args, line) if negate_result: # TODO: introduce UnaryIntOp? call_result = self.unary_op(call, "not", line) else: call_result = call self.add(Assign(result, call_result, line)) self.goto_and_activate(out) return result
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.call_c(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.call_c(keep_propagating_op, [], NO_TRACEBACK_LINE_NO) builder.add(Unreachable()) return out_block
def compare_strings(self, lhs: Value, rhs: Value, op: str, line: int) -> Value: """Compare two strings""" compare_result = self.call_c(unicode_compare, [lhs, rhs], line) error_constant = self.add(LoadInt(-1, line, c_int_rprimitive)) compare_error_check = self.add( ComparisonOp(compare_result, error_constant, ComparisonOp.EQ, line)) exception_check, propagate, final_compare = BasicBlock(), BasicBlock( ), BasicBlock() branch = Branch(compare_error_check, exception_check, final_compare, Branch.BOOL_EXPR) branch.negated = False self.add(branch) self.activate_block(exception_check) check_error_result = self.call_c(err_occurred_op, [], line) null = self.add(LoadInt(0, line, pointer_rprimitive)) compare_error_check = self.add( ComparisonOp(check_error_result, null, ComparisonOp.NEQ, line)) branch = Branch(compare_error_check, propagate, final_compare, Branch.BOOL_EXPR) branch.negated = False self.add(branch) self.activate_block(propagate) self.call_c(keep_propagating_op, [], line) self.goto(final_compare) self.activate_block(final_compare) op_type = ComparisonOp.EQ if op == '==' else ComparisonOp.NEQ return self.add( ComparisonOp(compare_result, self.add(LoadInt(0, line, c_int_rprimitive)), op_type, line))
def process_iterator_tuple_assignment_helper(self, litem: AssignmentTarget, ritem: Value, line: int) -> None: error_block, ok_block = BasicBlock(), BasicBlock() self.add(Branch(ritem, error_block, ok_block, Branch.IS_ERROR)) self.activate_block(error_block) self.add(RaiseStandardError(RaiseStandardError.VALUE_ERROR, 'not enough values to unpack', line)) self.add(Unreachable()) self.activate_block(ok_block) self.assign(litem, ritem, line)
def add_close_to_generator_class(builder: IRBuilder, fn_info: FuncInfo) -> None: """Generates the '__close__' method for a generator class.""" with builder.enter_method(fn_info.generator_class.ir, 'close', object_rprimitive, fn_info): except_block, else_block = BasicBlock(), BasicBlock() builder.builder.push_error_handler(except_block) builder.goto_and_activate(BasicBlock()) generator_exit = builder.load_module_attr_by_fullname( 'builtins.GeneratorExit', fn_info.fitem.line) builder.add( MethodCall( builder.self(), 'throw', [generator_exit, builder.none_object(), builder.none_object()])) builder.goto(else_block) builder.builder.pop_error_handler() builder.activate_block(except_block) old_exc = builder.call_c(error_catch_op, [], fn_info.fitem.line) builder.nonlocal_control.append( ExceptNonlocalControl(builder.nonlocal_control[-1], old_exc)) stop_iteration = builder.load_module_attr_by_fullname( 'builtins.StopIteration', fn_info.fitem.line) exceptions = builder.add( TupleSet([generator_exit, stop_iteration], fn_info.fitem.line)) matches = builder.call_c(exc_matches_op, [exceptions], fn_info.fitem.line) match_block, non_match_block = BasicBlock(), BasicBlock() builder.add(Branch(matches, match_block, non_match_block, Branch.BOOL)) builder.activate_block(match_block) builder.call_c(restore_exc_info_op, [builder.read(old_exc)], fn_info.fitem.line) builder.add(Return(builder.none_object())) builder.activate_block(non_match_block) builder.call_c(reraise_exception_op, [], NO_TRACEBACK_LINE_NO) builder.add(Unreachable()) builder.nonlocal_control.pop() builder.activate_block(else_block) builder.add( RaiseStandardError(RaiseStandardError.RUNTIME_ERROR, 'generator ignored GeneratorExit', fn_info.fitem.line)) builder.add(Unreachable())
def load_static_checked(self, typ: RType, identifier: str, module_name: Optional[str] = None, namespace: str = NAMESPACE_STATIC, line: int = -1, error_msg: Optional[str] = None) -> Value: if error_msg is None: error_msg = "name '{}' is not defined".format(identifier) ok_block, error_block = BasicBlock(), BasicBlock() value = self.add(LoadStatic(typ, identifier, module_name, namespace, line=line)) self.add(Branch(value, error_block, ok_block, Branch.IS_ERROR, rare=True)) self.activate_block(error_block) self.add(RaiseStandardError(RaiseStandardError.NAME_ERROR, error_msg, line)) self.add(Unreachable()) self.activate_block(ok_block) return value
def gen_condition(self) -> None: """Get next key/value pair, set new offset, and check if we should continue.""" builder = self.builder line = self.line self.next_tuple = self.builder.call_c( self.dict_next_op, [builder.read(self.iter_target, line), builder.read(self.offset_target, line)], line) # Do this here instead of in gen_step() to minimize variables in environment. new_offset = builder.add(TupleGet(self.next_tuple, 1, line)) builder.assign(self.offset_target, new_offset, line) should_continue = builder.add(TupleGet(self.next_tuple, 0, line)) builder.add( Branch(should_continue, self.body_block, self.loop_exit, Branch.BOOL_EXPR) )
def load_final_static(self, fullname: str, typ: RType, line: int, error_name: Optional[str] = None) -> Value: if error_name is None: error_name = fullname ok_block, error_block = BasicBlock(), BasicBlock() split_name = split_target(self.graph, fullname) assert split_name is not None value = self.add(LoadStatic(typ, split_name[1], split_name[0], line=line)) self.add(Branch(value, error_block, ok_block, Branch.IS_ERROR, rare=True)) self.activate_block(error_block) self.add(RaiseStandardError(RaiseStandardError.VALUE_ERROR, 'value for final name "{}" was not set'.format(error_name), line)) self.add(Unreachable()) self.activate_block(ok_block) return value
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 guaranteed to be # initialized is an operand to something other than a # check that it is defined, insert a check. # Note that for register operand in a LoadAddress op, # we should be able to use it without initialization # as we may need to use its address to update itself if (isinstance(src, Register) and src not in defined and not (isinstance(op, Branch) and op.op == Branch.IS_ERROR) and not isinstance(op, LoadAddress)): 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(builder: IRBuilder, cls: ClassIR, line: int) -> FuncIR: """Generate a "__ne__" method from a "__eq__" method. """ 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 = [ builder.read( builder.environment.add_local_reg( var, type, is_arg=True ), line ) for var, type in fake_vars ] # type: List[Value] builder.ret_types[-1] = object_rprimitive # If __eq__ returns NotImplemented, then __ne__ should also not_implemented_block, regular_block = BasicBlock(), BasicBlock() eqval = builder.add(MethodCall(args[0], '__eq__', [args[1]], line)) not_implemented = builder.add(LoadAddress(not_implemented_op.type, not_implemented_op.src, line)) builder.add(Branch( builder.translate_is_op(eqval, not_implemented, 'is', line), not_implemented_block, regular_block, Branch.BOOL_EXPR)) builder.activate_block(regular_block) retval = builder.coerce( builder.unary_op(eqval, 'not', line), object_rprimitive, line ) builder.add(Return(retval)) builder.activate_block(not_implemented_block) builder.add(Return(not_implemented)) blocks, env, ret_type, _ = builder.leave() return FuncIR( FuncDecl('__ne__', cls.name, builder.module_name, FuncSignature(rt_args, ret_type)), blocks, env)