def transform_raise_stmt(builder: IRBuilder, s: RaiseStmt) -> None: if s.expr is None: builder.call_c(reraise_exception_op, [], NO_TRACEBACK_LINE_NO) builder.add(Unreachable()) return exc = builder.accept(s.expr) builder.call_c(raise_exception_op, [exc], s.line) builder.add(Unreachable())
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 test_int_op(self) -> None: n1 = Integer(2) n2 = Integer(4) op1 = IntOp(int_rprimitive, n1, n2, IntOp.ADD) op2 = IntOp(int_rprimitive, op1, n2, IntOp.ADD) block = make_block([op1, op2, Unreachable()]) assert generate_names_for_ir([], [block]) == {op1: 'r0', op2: 'r1'}
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.call_c(raise_exception_op, [exc], a.line) builder.add(Unreachable()) builder.activate_block(ok_block)
def gen_return(self, builder: 'IRBuilder', value: Value, line: int) -> None: # Assign an invalid next label number so that the next time # __next__ is called, we jump to the case in which # StopIteration is raised. builder.assign(builder.fn_info.generator_class.next_label_target, Integer(-1), line) # Raise a StopIteration containing a field for the value that # should be returned. Before doing so, create a new block # without an error handler set so that the implicitly thrown # StopIteration isn't caught by except blocks inside of the # generator function. builder.builder.push_error_handler(None) builder.goto_and_activate(BasicBlock()) # Skip creating a traceback frame when we raise here, because # we don't care about the traceback frame and it is kind of # expensive since raising StopIteration is an extremely common # case. Also we call a special internal function to set # StopIteration instead of using RaiseStandardError because # the obvious thing doesn't work if the value is a tuple # (???). builder.call_c(set_stop_iteration_value, [value], NO_TRACEBACK_LINE_NO) builder.add(Unreachable()) builder.builder.pop_error_handler()
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 add_close_to_generator_class(builder: IRBuilder, fn_info: FuncInfo) -> None: """Generates the '__close__' method for a generator class.""" # TODO: Currently this method just triggers a runtime error. # We should fill this out (https://github.com/mypyc/mypyc/issues/790). with builder.enter_method(fn_info.generator_class.ir, 'close', object_rprimitive, fn_info): builder.add(RaiseStandardError(RaiseStandardError.RUNTIME_ERROR, 'close method on generator classes unimplemented', fn_info.fitem.line)) builder.add(Unreachable())
def gen_calls_to_correct_impl( builder: IRBuilder, impl_to_use: Value, arg_info: ArgInfo, fitem: FuncDef, line: int, ) -> None: current_func_decl = builder.mapper.func_to_decl[fitem] def gen_native_func_call_and_return(fdef: FuncDef) -> None: func_decl = builder.mapper.func_to_decl[fdef] ret_val = builder.builder.call(func_decl, arg_info.args, arg_info.arg_kinds, arg_info.arg_names, line) coerced = builder.coerce(ret_val, current_func_decl.sig.ret_type, line) builder.add(Return(coerced)) typ, src = builtin_names['builtins.int'] int_type_obj = builder.add(LoadAddress(typ, src, line)) is_int = builder.builder.type_is_op(impl_to_use, int_type_obj, line) native_call, non_native_call = BasicBlock(), BasicBlock() builder.add_bool_branch(is_int, native_call, non_native_call) builder.activate_block(native_call) passed_id = builder.add(Unbox(impl_to_use, int_rprimitive, line)) native_ids = get_native_impl_ids(builder, fitem) for impl, i in native_ids.items(): call_impl, next_impl = BasicBlock(), BasicBlock() current_id = builder.load_int(i) builder.builder.compare_tagged_condition( passed_id, current_id, '==', call_impl, next_impl, line, ) # Call the registered implementation builder.activate_block(call_impl) gen_native_func_call_and_return(impl) builder.activate_block(next_impl) # We've already handled all the possible integer IDs, so we should never get here builder.add(Unreachable()) builder.activate_block(non_native_call) ret_val = builder.py_call(impl_to_use, arg_info.args, line, arg_info.arg_kinds, arg_info.arg_names) coerced = builder.coerce(ret_val, current_func_decl.sig.ret_type, line) builder.add(Return(coerced))
def except_body() -> None: builder.assign(exc, builder.false(), 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.call_c(reraise_exception_op, [], NO_TRACEBACK_LINE_NO) builder.add(Unreachable()) builder.activate_block(out_block)
def transform_block(builder: IRBuilder, block: Block) -> None: if not block.is_unreachable: for stmt in block.body: builder.accept(stmt) # Raise a RuntimeError if we hit a non-empty unreachable block. # Don't complain about empty unreachable blocks, since mypy inserts # those after `if MYPY`. elif block.body: builder.add(RaiseStandardError(RaiseStandardError.RUNTIME_ERROR, 'Reached allegedly unreachable code!', block.line)) builder.add(Unreachable())
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 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 populate_switch_for_generator_class(builder: IRBuilder) -> None: cls = builder.fn_info.generator_class line = builder.fn_info.fitem.line builder.activate_block(cls.switch_block) for label, true_block in enumerate(cls.continuation_blocks): false_block = BasicBlock() comparison = builder.binary_op(cls.next_label_reg, builder.add(LoadInt(label)), '==', line) builder.add_bool_branch(comparison, true_block, false_block) builder.activate_block(false_block) builder.add( RaiseStandardError(RaiseStandardError.STOP_ITERATION, None, line)) builder.add(Unreachable())
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 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 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 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 = Register(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)) comprehension_helper(builder, 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_raise_exception_blocks_to_generator_class(builder: IRBuilder, line: int) -> None: """Add error handling blocks to a generator class. Generates blocks to check if error flags are set while calling the helper method for generator functions, and raises an exception if those flags are set. """ cls = builder.fn_info.generator_class assert cls.exc_regs is not None exc_type, exc_val, exc_tb = cls.exc_regs # Check to see if an exception was raised. error_block = BasicBlock() ok_block = BasicBlock() comparison = builder.translate_is_op(exc_type, builder.none_object(), 'is not', line) builder.add_bool_branch(comparison, error_block, ok_block) builder.activate_block(error_block) builder.call_c(raise_exception_with_tb_op, [exc_type, exc_val, exc_tb], line) builder.add(Unreachable()) builder.goto_and_activate(ok_block)
def add_close_to_generator_class(builder: IRBuilder, fn_info: FuncInfo) -> None: """Generates the '__close__' method for a generator class.""" # TODO: Currently this method just triggers a runtime error, # we should fill this out eventually. builder.enter(fn_info) add_self_to_env(builder.environment, fn_info.generator_class.ir) builder.add( RaiseStandardError(RaiseStandardError.RUNTIME_ERROR, 'close method on generator classes uimplemented', fn_info.fitem.line)) builder.add(Unreachable()) blocks, env, _, fn_info = builder.leave() # Next, add the actual function as a method of the generator class. sig = FuncSignature((RuntimeArg(SELF_NAME, object_rprimitive), ), object_rprimitive) close_fn_decl = FuncDecl('close', fn_info.generator_class.ir.name, builder.module_name, sig) close_fn_ir = FuncIR(close_fn_decl, blocks, env) fn_info.generator_class.ir.methods['close'] = close_fn_ir builder.functions.append(close_fn_ir)
def test_register(self) -> None: reg = Register(int_rprimitive) op = Assign(reg, Integer(5)) self.block.ops.append(op) self.block.ops.append(Unreachable()) fn = FuncIR( FuncDecl('myfunc', None, 'mod', FuncSignature([self.arg], list_rprimitive)), [self.reg], [self.block]) value_names = generate_names_for_ir(fn.arg_regs, fn.blocks) emitter = Emitter(EmitterContext(NameGenerator([['mod']])), value_names) generate_native_function(fn, emitter, 'prog.py', 'prog') result = emitter.fragments assert_string_arrays_equal([ 'PyObject *CPyDef_myfunc(CPyTagged cpy_r_arg) {\n', ' CPyTagged cpy_r_r0;\n', 'CPyL0: ;\n', ' cpy_r_r0 = 10;\n', ' CPy_Unreachable();\n', '}\n', ], result, msg='Generated code invalid')
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.call_c(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.call_c(exc_matches_op, [builder.accept(type)], type.line) builder.add(Branch(matches, body_block, next_block, Branch.BOOL)) builder.activate_block(body_block) if var: target = builder.get_assignment_target(var) builder.assign(target, builder.call_c(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.call_c(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.call_c(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.call_c(restore_exc_info_op, [builder.read(old_exc)], line) builder.call_c(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 test_int_op(self) -> None: op1 = LoadInt(2) op2 = LoadInt(4) op3 = IntOp(int_rprimitive, op1, op2, IntOp.ADD) block = make_block([op1, op2, op3, Unreachable()]) assert generate_names_for_ir([], [block]) == {op1: 'i0', op2: 'i1', op3: 'r0'}
def process_iterator_tuple_assignment(self, target: AssignmentTargetTuple, rvalue_reg: Value, line: int) -> None: iterator = self.primitive_op(iter_op, [rvalue_reg], line) # This may be the whole lvalue list if there is no starred value split_idx = target.star_idx if target.star_idx is not None else len( target.items) # Assign values before the first starred value for litem in target.items[:split_idx]: ritem = self.primitive_op(next_op, [iterator], line) 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) # Assign the starred value and all values after it if target.star_idx is not None: post_star_vals = target.items[split_idx + 1:] iter_list = self.primitive_op(to_list, [iterator], line) iter_list_len = self.primitive_op(list_len_op, [iter_list], line) post_star_len = self.add(LoadInt(len(post_star_vals))) condition = self.binary_op(post_star_len, iter_list_len, '<=', line) error_block, ok_block = BasicBlock(), BasicBlock() self.add(Branch(condition, ok_block, error_block, Branch.BOOL_EXPR)) 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) for litem in reversed(post_star_vals): ritem = self.primitive_op(list_pop_last, [iter_list], line) self.assign(litem, ritem, line) # Assign the starred value self.assign(target.items[target.star_idx], iter_list, line) # There is no starred value, so check if there are extra values in rhs that # have not been assigned. else: extra = self.primitive_op(next_op, [iterator], line) error_block, ok_block = BasicBlock(), BasicBlock() self.add(Branch(extra, ok_block, error_block, Branch.IS_ERROR)) self.activate_block(error_block) self.add( RaiseStandardError(RaiseStandardError.VALUE_ERROR, 'too many values to unpack', line)) self.add(Unreachable()) self.activate_block(ok_block)
def add_implicit_unreachable(self) -> None: block = self.builder.blocks[-1] if not block.terminated: self.add(Unreachable())