def setup_non_ext_dict(self, cdef: ClassDef, metaclass: Value, bases: Value) -> Value: """ Initialize the class dictionary for a non-extension class. This class dictionary is passed to the metaclass constructor. """ # Check if the metaclass defines a __prepare__ method, and if so, call it. has_prepare = self.primitive_op(py_hasattr_op, [metaclass, self.load_static_unicode('__prepare__')], cdef.line) non_ext_dict = self.builder.alloc_temp(dict_rprimitive) true_block, false_block, exit_block, = BasicBlock(), BasicBlock(), BasicBlock() self.builder.add_bool_branch(has_prepare, true_block, false_block) self.builder.activate_block(true_block) cls_name = self.load_static_unicode(cdef.name) prepare_meth = self.builder.py_get_attr(metaclass, '__prepare__', cdef.line) prepare_dict = self.builder.py_call(prepare_meth, [cls_name, bases], cdef.line) self.builder.assign(non_ext_dict, prepare_dict, cdef.line) self.builder.goto(exit_block) self.builder.activate_block(false_block) self.builder.assign(non_ext_dict, self.primitive_op(new_dict_op, [], cdef.line), cdef.line) self.builder.goto(exit_block) self.builder.activate_block(exit_block) return non_ext_dict
def shortcircuit_helper(self, op: str, expr_type: RType, left: Callable[[], Value], right: Callable[[], Value], line: int) -> Value: # Having actual Phi nodes would be really nice here! target = self.alloc_temp(expr_type) # left_body takes the value of the left side, right_body the right left_body, right_body, next = BasicBlock(), BasicBlock(), BasicBlock() # true_body is taken if the left is true, false_body if it is false. # For 'and' the value is the right side if the left is true, and for 'or' # it is the right side if the left is false. true_body, false_body = ((right_body, left_body) if op == 'and' else (left_body, right_body)) left_value = left() self.add_bool_branch(left_value, true_body, false_body) self.activate_block(left_body) left_coerced = self.coerce(left_value, expr_type, line) self.add(Assign(target, left_coerced)) self.goto(next) self.activate_block(right_body) right_value = right() right_coerced = self.coerce(right_value, expr_type, line) self.add(Assign(target, right_coerced)) self.goto(next) self.activate_block(next) return target
def decompose_union_helper(self, obj: Value, rtype: RUnion, result_type: RType, process_item: Callable[[Value], Value], line: int) -> Value: """Generate isinstance() + specialized operations for union items. Say, for Union[A, B] generate ops resembling this (pseudocode): if isinstance(obj, A): result = <result of process_item(cast(A, obj)> else: result = <result of process_item(cast(B, obj)> Args: obj: value with a union type rtype: the union type result_type: result of the operation process_item: callback to generate op for a single union item (arg is coerced to union item type) line: line number """ # TODO: Optimize cases where a single operation can handle multiple union items # (say a method is implemented in a common base class) fast_items = [] rest_items = [] for item in rtype.items: if isinstance(item, RInstance): fast_items.append(item) else: # For everything but RInstance we fall back to C API rest_items.append(item) exit_block = BasicBlock() result = self.alloc_temp(result_type) for i, item in enumerate(fast_items): more_types = i < len(fast_items) - 1 or rest_items if more_types: # We are not at the final item so we need one more branch op = self.isinstance_native(obj, item.class_ir, line) true_block, false_block = BasicBlock(), BasicBlock() self.add_bool_branch(op, true_block, false_block) self.activate_block(true_block) coerced = self.coerce(obj, item, line) temp = process_item(coerced) temp2 = self.coerce(temp, result_type, line) self.add(Assign(result, temp2)) self.goto(exit_block) if more_types: self.activate_block(false_block) if rest_items: # For everything else we use generic operation. Use force=True to drop the # union type. coerced = self.coerce(obj, object_rprimitive, line, force=True) temp = process_item(coerced) temp2 = self.coerce(temp, result_type, line) self.add(Assign(result, temp2)) self.goto(exit_block) self.activate_block(exit_block) return result
def transform_try_finally_stmt(builder: IRBuilder, try_body: GenFunc, finally_body: GenFunc) -> None: """Generalized try/finally handling that takes functions to gen the bodies. The point of this is to also be able to support with.""" # Finally is a big pain, because there are so many ways that # exits can occur. We emit 10+ basic blocks for every finally! err_handler, main_entry, return_entry, finally_block = ( BasicBlock(), BasicBlock(), BasicBlock(), BasicBlock()) # Compile the body of the try ret_reg = try_finally_try( builder, err_handler, return_entry, main_entry, try_body) # Set up the entry blocks for the finally statement old_exc = try_finally_entry_blocks( builder, err_handler, return_entry, main_entry, finally_block, ret_reg) # Compile the body of the finally cleanup_block, finally_control = try_finally_body( builder, finally_block, finally_body, ret_reg, old_exc) # Resolve the control flow out of the finally block out_block = try_finally_resolve_control( builder, cleanup_block, finally_control, old_exc, ret_reg) builder.activate_block(out_block)
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 except_body() -> None: builder.assign(exc, builder.primitive_op(false_op, [], -1), line) out_block, reraise_block = BasicBlock(), BasicBlock() builder.add_bool_branch( builder.py_call(builder.read(exit_), [builder.read(mgr)] + get_sys_exc_info(builder), line), out_block, reraise_block ) builder.activate_block(reraise_block) builder.primitive_op(reraise_exception_op, [], NO_TRACEBACK_LINE_NO) builder.add(Unreachable()) builder.activate_block(out_block)
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 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 transform_if_stmt(builder: IRBuilder, stmt: IfStmt) -> None: if_body, next = BasicBlock(), BasicBlock() else_body = BasicBlock() if stmt.else_body else next # If statements are normalized assert len(stmt.expr) == 1 builder.process_conditional(stmt.expr[0], if_body, else_body) builder.activate_block(if_body) builder.accept(stmt.body[0]) builder.goto(next) if stmt.else_body: builder.activate_block(else_body) builder.accept(stmt.else_body) builder.goto(next) builder.activate_block(next)
def add_block(ops: Iterable[Op], blocks: List[BasicBlock], label: Label) -> Label: block = BasicBlock(Label(len(blocks))) block.ops.extend(ops) block.ops.append(Goto(label)) blocks.append(block) return block.label
def add_handler_block(ir: FuncIR) -> BasicBlock: block = BasicBlock() ir.blocks.append(block) op = LoadErrorValue(ir.ret_type) block.ops.append(op) ir.env.add_op(op) block.ops.append(Return(op)) return 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 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 any_all_helper(builder: IRBuilder, gen: GeneratorExpr, initial_value_op: OpDescription, modify: Callable[[Value], Value], new_value_op: OpDescription) -> Value: retval = builder.alloc_temp(bool_rprimitive) builder.assign(retval, builder.primitive_op(initial_value_op, [], -1), -1) loop_params = list(zip(gen.indices, gen.sequences, gen.condlists)) true_block, false_block, exit_block = BasicBlock(), BasicBlock( ), BasicBlock() def gen_inner_stmts() -> None: comparison = modify(builder.accept(gen.left_expr)) builder.add_bool_branch(comparison, true_block, false_block) builder.activate_block(true_block) builder.assign(retval, builder.primitive_op(new_value_op, [], -1), -1) builder.goto(exit_block) builder.activate_block(false_block) builder.comprehension_helper(loop_params, gen_inner_stmts, gen.line) builder.goto_and_activate(exit_block) return retval
def transform_assert_stmt(builder: IRBuilder, a: AssertStmt) -> None: if builder.options.strip_asserts: return cond = builder.accept(a.expr) ok_block, error_block = BasicBlock(), BasicBlock() builder.add_bool_branch(cond, ok_block, error_block) builder.activate_block(error_block) if a.msg is None: # Special case (for simpler generated code) builder.add(RaiseStandardError(RaiseStandardError.ASSERTION_ERROR, None, a.line)) elif isinstance(a.msg, StrExpr): # Another special case builder.add(RaiseStandardError(RaiseStandardError.ASSERTION_ERROR, a.msg.value, a.line)) else: # The general case -- explicitly construct an exception instance message = builder.accept(a.msg) exc_type = builder.load_module_attr_by_fullname('builtins.AssertionError', a.line) exc = builder.py_call(exc_type, [message], a.line) builder.primitive_op(raise_exception_op, [exc], a.line) builder.add(Unreachable()) builder.activate_block(ok_block)
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 transform_while_stmt(builder: IRBuilder, s: WhileStmt) -> None: body, next, top, else_block = BasicBlock(), BasicBlock(), BasicBlock(), BasicBlock() normal_loop_exit = else_block if s.else_body is not None else next builder.push_loop_stack(top, next) # Split block so that we get a handle to the top of the loop. builder.goto_and_activate(top) builder.process_conditional(s.expr, body, normal_loop_exit) builder.activate_block(body) builder.accept(s.body) # Add branch to the top at the end of the body. builder.goto(top) builder.pop_loop_stack() if s.else_body is not None: builder.activate_block(else_block) builder.accept(s.else_body) builder.goto(next) builder.activate_block(next)
def init(self, indexes: List[Lvalue], exprs: List[Expression]) -> None: assert len(indexes) == len(exprs) # Condition check will require multiple basic blocks, since there will be # multiple conditions to check. self.cond_blocks = [BasicBlock() for _ in range(len(indexes) - 1)] + [self.body_block] self.gens = [] # type: List[ForGenerator] for index, expr, next_block in zip(indexes, exprs, self.cond_blocks): gen = self.builder.make_for_loop_generator( index, expr, next_block, self.loop_exit, self.line, nested=True) self.gens.append(gen)
def visit_conditional_expr(self, expr: ConditionalExpr) -> Value: if_body, else_body, next = BasicBlock(), BasicBlock(), BasicBlock() self.builder.process_conditional(expr.cond, if_body, else_body) expr_type = self.builder.node_type(expr) # Having actual Phi nodes would be really nice here! target = self.builder.alloc_temp(expr_type) self.builder.activate_block(if_body) true_value = self.builder.accept(expr.if_expr) true_value = self.builder.coerce(true_value, expr_type, expr.line) self.builder.add(Assign(target, true_value)) self.builder.goto(next) self.builder.activate_block(else_body) false_value = self.builder.accept(expr.else_expr) false_value = self.builder.coerce(false_value, expr_type, expr.line) self.builder.add(Assign(target, false_value)) self.builder.goto(next) self.builder.activate_block(next) return target
def try_finally_try(builder: IRBuilder, err_handler: BasicBlock, return_entry: BasicBlock, main_entry: BasicBlock, try_body: GenFunc) -> Optional[Register]: # Compile the try block with an error handler control = TryFinallyNonlocalControl(return_entry) builder.builder.push_error_handler(err_handler) builder.nonlocal_control.append(control) builder.goto_and_activate(BasicBlock()) try_body() builder.goto(main_entry) builder.nonlocal_control.pop() builder.builder.pop_error_handler() return control.ret_reg
def try_finally_body( builder: IRBuilder, finally_block: BasicBlock, finally_body: GenFunc, ret_reg: Optional[Value], old_exc: Value) -> Tuple[BasicBlock, FinallyNonlocalControl]: cleanup_block = BasicBlock() # Compile the finally block with the nonlocal control flow overridden to restore exc_info builder.builder.push_error_handler(cleanup_block) finally_control = FinallyNonlocalControl( builder.nonlocal_control[-1], ret_reg, old_exc) builder.nonlocal_control.append(finally_control) builder.activate_block(finally_block) finally_body() builder.nonlocal_control.pop() return cleanup_block, finally_control
def translate_next_call(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Optional[Value]: # Special case for calling next() on a generator expression, an # idiom that shows up some in mypy. # # For example, next(x for x in l if x.id == 12, None) will # generate code that searches l for an element where x.id == 12 # and produce the first such object, or None if no such element # exists. if not (expr.arg_kinds in ([ARG_POS], [ARG_POS, ARG_POS]) and isinstance(expr.args[0], GeneratorExpr)): return None gen = expr.args[0] retval = builder.alloc_temp(builder.node_type(expr)) default_val = None if len(expr.args) > 1: default_val = builder.accept(expr.args[1]) exit_block = BasicBlock() def gen_inner_stmts() -> None: # next takes the first element of the generator, so if # something gets produced, we are done. builder.assign(retval, builder.accept(gen.left_expr), gen.left_expr.line) builder.goto(exit_block) loop_params = list(zip(gen.indices, gen.sequences, gen.condlists)) builder.comprehension_helper(loop_params, gen_inner_stmts, gen.line) # Now we need the case for when nothing got hit. If there was # a default value, we produce it, and otherwise we raise # StopIteration. if default_val: builder.assign(retval, default_val, gen.left_expr.line) builder.goto(exit_block) else: builder.add( RaiseStandardError(RaiseStandardError.STOP_ITERATION, None, expr.line)) builder.add(Unreachable()) builder.activate_block(exit_block) return retval
def add_block(decincs: DecIncs, cache: BlockCache, blocks: List[BasicBlock], label: BasicBlock) -> BasicBlock: decs, incs = decincs if not decs and not incs: return label # TODO: be able to share *partial* results if (label, decincs) in cache: return cache[label, decincs] block = BasicBlock() blocks.append(block) block.ops.extend(DecRef(reg, is_xdec=xdec) for reg, xdec in decs) block.ops.extend(IncRef(reg) for reg in incs) block.ops.append(Goto(label)) cache[label, decincs] = block return block
def transform_block(block: BasicBlock, pre_live: AnalysisDict[Register], post_live: AnalysisDict[Register], pre_borrow: AnalysisDict[Register], env: Environment) -> None: old_ops = block.ops ops = [] # type: List[Op] for i, op in enumerate(old_ops): key = (block.label, i) if isinstance(op, (Assign, Cast, Box)): # These operations just copy/steal a reference and don't create new # references. if op.src in post_live[key] or op.src in pre_borrow[key]: ops.append(IncRef(op.src, env.types[op.src])) if (op.dest not in pre_borrow[key] and op.dest in pre_live[key]): ops.append(DecRef(op.dest, env.types[op.dest])) ops.append(op) if op.dest not in post_live[key]: ops.append(DecRef(op.dest, env.types[op.dest])) elif isinstance(op, RegisterOp): # These operations construct a new reference. tmp_reg = None # type: Optional[Register] if (op.dest not in pre_borrow[key] and op.dest in pre_live[key]): if op.dest not in op.sources(): ops.append(DecRef(op.dest, env.types[op.dest])) else: tmp_reg = env.add_temp(env.types[op.dest]) ops.append(Assign(tmp_reg, op.dest)) ops.append(op) for src in op.unique_sources(): # Decrement source that won't be live afterwards. if src not in post_live[key] and src not in pre_borrow[key]: if src != op.dest: ops.append(DecRef(src, env.types[src])) if op.dest is not None and op.dest not in post_live[key]: ops.append(DecRef(op.dest, env.types[op.dest])) if tmp_reg is not None: ops.append(DecRef(tmp_reg, env.types[tmp_reg])) elif isinstance(op, Return) and op.reg in pre_borrow[key]: # The return op returns a new reference. ops.append(IncRef(op.reg, env.types[op.reg])) ops.append(op) else: ops.append(op) block.ops = ops
def gen_condition(self) -> None: builder = self.builder line = self.line if self.reverse: # If we are iterating in reverse order, we obviously need # to check that the index is still positive. Somewhat less # obviously we still need to check against the length, # since it could shrink out from under us. comparison = builder.binary_op(builder.read(self.index_target, line), builder.add(LoadInt(0)), '>=', line) second_check = BasicBlock() builder.add_bool_branch(comparison, second_check, self.loop_exit) builder.activate_block(second_check) # For compatibility with python semantics we recalculate the length # at every iteration. len_reg = self.load_len() comparison = builder.binary_op(builder.read(self.index_target, line), len_reg, '<', line) builder.add_bool_branch(comparison, self.body_block, self.loop_exit)
def __init__(self, builder: 'mypyc.genops.IRBuilder', index: Lvalue, body_block: BasicBlock, loop_exit: BasicBlock, line: int, nested: bool) -> None: self.builder = builder self.index = index self.body_block = body_block self.line = line # Some for loops need a cleanup block that we execute at exit. We # create a cleanup block if needed. However, if we are generating a for # loop for a nested iterator, such as "e" in "enumerate(e)", the # outermost generator should generate the cleanup block -- we don't # need to do it here. if self.need_cleanup() and not nested: # Create a new block to handle cleanup after loop exit. self.loop_exit = BasicBlock() else: # Just use the existing loop exit block. self.loop_exit = loop_exit
def __init__(self, ir: ClassIR) -> None: super().__init__(ir) # This register holds the label number that the '__next__' function should go to the next # time it is called. self._next_label_reg = None # type: Optional[Value] self._next_label_target = None # type: Optional[AssignmentTarget] # These registers hold the error values for the generator object for the case that the # 'throw' function is called. self.exc_regs = None # type: Optional[Tuple[Value, Value, Value]] # Holds the arg passed to send self.send_arg_reg = None # type: Optional[Value] # The switch block is used to decide which instruction to go using the value held in the # next-label register. self.switch_block = BasicBlock() self.continuation_blocks = [] # type: List[BasicBlock]
def transform_block(block: BasicBlock, pre_live: 'AnalysisDict[Value]', post_live: 'AnalysisDict[Value]', pre_borrow: 'AnalysisDict[Value]', post_must_defined: 'AnalysisDict[Value]', env: Environment) -> None: old_ops = block.ops ops = [] # type: List[Op] for i, op in enumerate(old_ops): key = (block, i) assert op not in pre_live[key] dest = op.dest if isinstance(op, Assign) else op stolen = op.stolen() # Incref any references that are being stolen that stay live, were borrowed, # or are stolen more than once by this operation. for i, src in enumerate(stolen): if src in post_live[key] or src in pre_borrow[ key] or src in stolen[:i]: maybe_append_inc_ref(ops, src) # For assignments to registers that were already live, # decref the old value. if (dest not in pre_borrow[key] and dest in pre_live[key]): assert isinstance(op, Assign) maybe_append_dec_ref(ops, dest, post_must_defined, key) ops.append(op) # Control ops don't have any space to insert ops after them, so # their inc/decrefs get inserted by insert_branch_inc_and_decrefs. if isinstance(op, ControlOp): continue for src in op.unique_sources(): # Decrement source that won't be live afterwards. if src not in post_live[key] and src not in pre_borrow[ key] and src not in stolen: maybe_append_dec_ref(ops, src, post_must_defined, key) # Decrement the destination if it is dead after the op and # wasn't a borrowed RegisterOp if (not dest.is_void and dest not in post_live[key] and not (isinstance(op, RegisterOp) and dest.is_borrowed)): maybe_append_dec_ref(ops, dest, post_must_defined, key) block.ops = ops
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))