def setup_non_ext_dict(builder: IRBuilder, 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 = builder.call_c(py_hasattr_op, [metaclass, builder.load_str('__prepare__')], cdef.line) non_ext_dict = Register(dict_rprimitive) true_block, false_block, exit_block, = BasicBlock(), BasicBlock(), BasicBlock() builder.add_bool_branch(has_prepare, true_block, false_block) builder.activate_block(true_block) cls_name = builder.load_str(cdef.name) prepare_meth = builder.py_get_attr(metaclass, '__prepare__', cdef.line) prepare_dict = builder.py_call(prepare_meth, [cls_name, bases], cdef.line) builder.assign(non_ext_dict, prepare_dict, cdef.line) builder.goto(exit_block) builder.activate_block(false_block) builder.assign(non_ext_dict, builder.call_c(dict_new_op, [], cdef.line), cdef.line) builder.goto(exit_block) builder.activate_block(exit_block) return non_ext_dict
def faster_min_max(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Optional[Value]: if expr.arg_kinds == [ARG_POS, ARG_POS]: x, y = builder.accept(expr.args[0]), builder.accept(expr.args[1]) result = Register(builder.node_type(expr)) # CPython evaluates arguments reversely when calling min(...) or max(...) if callee.fullname == 'builtins.min': comparison = builder.binary_op(y, x, '<', expr.line) else: comparison = builder.binary_op(y, x, '>', expr.line) true_block, false_block, next_block = BasicBlock(), BasicBlock( ), BasicBlock() builder.add_bool_branch(comparison, true_block, false_block) builder.activate_block(true_block) builder.assign(result, builder.coerce(y, result.type, expr.line), expr.line) builder.goto(next_block) builder.activate_block(false_block) builder.assign(result, builder.coerce(x, result.type, expr.line), expr.line) builder.goto(next_block) builder.activate_block(next_block) return result return None
def try_finally_entry_blocks(builder: IRBuilder, err_handler: BasicBlock, return_entry: BasicBlock, main_entry: BasicBlock, finally_block: BasicBlock, ret_reg: Optional[Register]) -> Value: old_exc = Register(exc_rtuple) # Entry block for non-exceptional flow builder.activate_block(main_entry) if ret_reg: builder.add( Assign(ret_reg, builder.add(LoadErrorValue(builder.ret_types[-1])))) builder.goto(return_entry) builder.activate_block(return_entry) builder.add(Assign(old_exc, builder.add(LoadErrorValue(exc_rtuple)))) builder.goto(finally_block) # Entry block for errors builder.activate_block(err_handler) if ret_reg: builder.add( Assign(ret_reg, builder.add(LoadErrorValue(builder.ret_types[-1])))) builder.add(Assign(old_exc, builder.call_c(error_catch_op, [], -1))) builder.goto(finally_block) return old_exc
def gen_return(self, builder: 'IRBuilder', value: Value, line: int) -> None: if self.ret_reg is None: self.ret_reg = Register(builder.ret_types[-1]) builder.add(Assign(self.ret_reg, value)) builder.add(Goto(self.target))
def test_duplicate_op(self) -> None: arg_reg = Register(type=int32_rprimitive, name="r1") assign = Assign(dest=arg_reg, src=Integer(value=5, rtype=int32_rprimitive)) block = self.basic_block([assign, assign, Return(value=NONE_VALUE)]) fn = FuncIR( decl=self.func_decl(name="func_1"), arg_regs=[], blocks=[block], ) assert_has_error( fn, FnError(source=assign, desc="Func has a duplicate op"))
def add_local(self, symbol: SymbolNode, typ: RType, is_arg: bool = False) -> 'Register': """Add register that represents a symbol to the symbol table. Args: is_arg: is this a function argument """ assert isinstance(symbol, SymbolNode) reg = Register(typ, symbol.name, is_arg=is_arg, line=symbol.line) self.symtables[-1][symbol] = AssignmentTargetRegister(reg) if is_arg: self.builder.args.append(reg) return reg
def test_invalid_register_source(self) -> None: ret = Return(value=Register( type=none_rprimitive, name="r1", )) block = self.basic_block([ret]) fn = FuncIR( decl=self.func_decl(name="func_1"), arg_regs=[], blocks=[block], ) assert_has_error( fn, FnError(source=ret, desc="Invalid op reference to register r1"))
def test_invalid_assign(self) -> None: arg_reg = Register(type=int64_rprimitive, name="r1") assign = Assign(dest=arg_reg, src=Integer(value=5, rtype=int32_rprimitive)) ret = Return(value=NONE_VALUE) fn = FuncIR( decl=self.func_decl(name="func_1"), arg_regs=[arg_reg], blocks=[self.basic_block([assign, ret])], ) assert_has_error( fn, FnError(source=assign, desc="Cannot coerce source type int32 to dest type int64"), )
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 init(self, start_reg: Value, end_reg: Value, step: int) -> None: builder = self.builder self.start_reg = start_reg self.end_reg = end_reg self.step = step self.end_target = builder.maybe_spill(end_reg) if is_short_int_rprimitive(start_reg.type) and is_short_int_rprimitive(end_reg.type): index_type = short_int_rprimitive else: index_type = int_rprimitive index_reg = Register(index_type) builder.assign(index_reg, start_reg, -1) self.index_reg = builder.maybe_spill_assignable(index_reg) # Initialize loop index to 0. Assert that the index target is assignable. self.index_target = builder.get_assignment_target( self.index) # type: Union[Register, AssignmentTarget] builder.assign(self.index_target, builder.read(self.index_reg, self.line), self.line)
def maybe_spill_assignable(self, value: Value) -> Union[Register, AssignmentTarget]: """ Moves a given Value instance into the environment class for generator functions. For non-generator functions, allocate a temporary Register. Returns an AssignmentTarget associated with the Value for generator functions and an assignable Register for non-generator functions. """ if self.fn_info.is_generator: return self.spill(value) if isinstance(value, Register): return value # Allocate a temporary register for the assignable value. reg = Register(value.type) self.assign(reg, value, -1) return reg
def accept(self, node: Union[Statement, Expression]) -> Optional[Value]: """Transform an expression or a statement.""" with self.catch_errors(node.line): if isinstance(node, Expression): try: res = node.accept(self.visitor) res = self.coerce(res, self.node_type(node), node.line) # If we hit an error during compilation, we want to # keep trying, so we can produce more error # messages. Generate a temp of the right type to keep # from causing more downstream trouble. except UnsupportedException: res = Register(self.node_type(node)) return res else: try: node.accept(self.visitor) except UnsupportedException: pass return None
def except_body() -> None: # The body of the except is all implemented in a C function to # reduce how much code we need to generate. It returns a value # indicating whether to break or yield (or raise an exception). val = Register(object_rprimitive) val_address = builder.add(LoadAddress(object_pointer_rprimitive, val)) to_stop = builder.call_c(yield_from_except_op, [builder.read(iter_reg), val_address], o.line) ok, stop = BasicBlock(), BasicBlock() builder.add(Branch(to_stop, stop, ok, Branch.BOOL)) # The exception got swallowed. Continue, yielding the returned value builder.activate_block(ok) builder.assign(to_yield_reg, val, o.line) builder.nonlocal_control[-1].gen_continue(builder, o.line) # The exception was a StopIteration. Stop iterating. builder.activate_block(stop) builder.assign(result, val, o.line) builder.nonlocal_control[-1].gen_break(builder, o.line)
def generate_singledispatch_dispatch_function( builder: IRBuilder, main_singledispatch_function_name: str, fitem: FuncDef, ) -> None: line = fitem.line current_func_decl = builder.mapper.func_to_decl[fitem] arg_info = get_args(builder, current_func_decl.sig.args, line) dispatch_func_obj = builder.self() arg_type = builder.builder.get_type_of_obj(arg_info.args[0], line) dispatch_cache = builder.builder.get_attr(dispatch_func_obj, 'dispatch_cache', dict_rprimitive, line) call_find_impl, use_cache, call_func = BasicBlock(), BasicBlock( ), BasicBlock() get_result = builder.call_c(dict_get_method_with_none, [dispatch_cache, arg_type], line) is_not_none = builder.translate_is_op(get_result, builder.none_object(), 'is not', line) impl_to_use = Register(object_rprimitive) builder.add_bool_branch(is_not_none, use_cache, call_find_impl) builder.activate_block(use_cache) builder.assign(impl_to_use, get_result, line) builder.goto(call_func) builder.activate_block(call_find_impl) find_impl = builder.load_module_attr_by_fullname('functools._find_impl', line) registry = load_singledispatch_registry(builder, dispatch_func_obj, line) uncached_impl = builder.py_call(find_impl, [arg_type, registry], line) builder.call_c(dict_set_item_op, [dispatch_cache, arg_type, uncached_impl], line) builder.assign(impl_to_use, uncached_impl, line) builder.goto(call_func) builder.activate_block(call_func) gen_calls_to_correct_impl(builder, impl_to_use, arg_info, fitem, line)
def any_all_helper(builder: IRBuilder, gen: GeneratorExpr, initial_value: Callable[[], Value], modify: Callable[[Value], Value], new_value: Callable[[], Value]) -> Value: retval = Register(bool_rprimitive) builder.assign(retval, initial_value(), -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, new_value(), -1) builder.goto(exit_block) builder.activate_block(false_block) comprehension_helper(builder, loop_params, gen_inner_stmts, gen.line) builder.goto_and_activate(exit_block) return retval
def test_register(self) -> None: reg = Register(int_rprimitive) op = Assign(reg, Integer(5)) self.block.ops.append(op) 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', '}\n', ], result, msg='Generated code invalid')
def transform_conditional_expr(builder: IRBuilder, expr: ConditionalExpr) -> Value: if_body, else_body, next_block = BasicBlock(), BasicBlock(), BasicBlock() builder.process_conditional(expr.cond, if_body, else_body) expr_type = builder.node_type(expr) # Having actual Phi nodes would be really nice here! target = Register(expr_type) builder.activate_block(if_body) true_value = builder.accept(expr.if_expr) true_value = builder.coerce(true_value, expr_type, expr.line) builder.add(Assign(target, true_value)) builder.goto(next_block) builder.activate_block(else_body) false_value = builder.accept(expr.else_expr) false_value = builder.coerce(false_value, expr_type, expr.line) builder.add(Assign(target, false_value)) builder.goto(next_block) builder.activate_block(next_block) return target
def transform_assignment_stmt(builder: IRBuilder, stmt: AssignmentStmt) -> None: lvalues = stmt.lvalues assert len(lvalues) >= 1 builder.disallow_class_assignments(lvalues, stmt.line) first_lvalue = lvalues[0] if stmt.type and isinstance(stmt.rvalue, TempNode): # This is actually a variable annotation without initializer. Don't generate # an assignment but we need to call get_assignment_target since it adds a # name binding as a side effect. builder.get_assignment_target(first_lvalue, stmt.line) return # Special case multiple assignments like 'x, y = e1, e2'. if (isinstance(first_lvalue, (TupleExpr, ListExpr)) and isinstance(stmt.rvalue, (TupleExpr, ListExpr)) and len(first_lvalue.items) == len(stmt.rvalue.items) and all(is_simple_lvalue(item) for item in first_lvalue.items) and len(lvalues) == 1): temps = [] for right in stmt.rvalue.items: rvalue_reg = builder.accept(right) temp = Register(rvalue_reg.type) builder.assign(temp, rvalue_reg, stmt.line) temps.append(temp) for (left, temp) in zip(first_lvalue.items, temps): assignment_target = builder.get_assignment_target(left) builder.assign(assignment_target, temp, stmt.line) return line = stmt.rvalue.line rvalue_reg = builder.accept(stmt.rvalue) if builder.non_function_scope() and stmt.is_final_def: builder.init_final_static(first_lvalue, rvalue_reg) for lvalue in lvalues: target = builder.get_assignment_target(lvalue) builder.assign(target, rvalue_reg, line)
def translate_sum_call(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Optional[Value]: # specialized implementation is used if: # - only one or two arguments given (if not, sum() has been given invalid arguments) # - first argument is a Generator (there is no benefit to optimizing the performance of eg. # sum([1, 2, 3]), so non-Generator Iterables are not handled) if not (len(expr.args) in (1, 2) and expr.arg_kinds[0] == ARG_POS and isinstance(expr.args[0], GeneratorExpr)): return None # handle 'start' argument, if given if len(expr.args) == 2: # ensure call to sum() was properly constructed if not expr.arg_kinds[1] in (ARG_POS, ARG_NAMED): return None start_expr = expr.args[1] else: start_expr = IntExpr(0) gen_expr = expr.args[0] target_type = builder.node_type(expr) retval = Register(target_type) builder.assign(retval, builder.coerce(builder.accept(start_expr), target_type, -1), -1) def gen_inner_stmts() -> None: call_expr = builder.accept(gen_expr.left_expr) builder.assign(retval, builder.binary_op(retval, call_expr, '+', -1), -1) loop_params = list( zip(gen_expr.indices, gen_expr.sequences, gen_expr.condlists)) comprehension_helper(builder, loop_params, gen_inner_stmts, gen_expr.line) return retval
def register(name: str) -> Register: return Register(int_rprimitive, 'foo', is_arg=True)
def add_local(name: str, rtype: RType) -> Register: reg = Register(rtype, name) self.registers.append(reg) return reg
def setUp(self) -> None: self.arg = RuntimeArg('arg', int_rprimitive) self.reg = Register(int_rprimitive, 'arg') self.block = BasicBlock(0)
def setUp(self) -> None: self.n = Register(int_rprimitive, 'n') self.context = EmitterContext(NameGenerator([['mod']]))
def test_long_unsigned(self) -> None: a = Register(int64_rprimitive, 'a') self.assert_emit(Assign(a, Integer(1 << 31, int64_rprimitive)), """cpy_r_a = 2147483648U;""") self.assert_emit(Assign(a, Integer((1 << 31) - 1, int64_rprimitive)), """cpy_r_a = 2147483647;""")
def handle_yield_from_and_await(builder: IRBuilder, o: Union[YieldFromExpr, AwaitExpr]) -> Value: # This is basically an implementation of the code in PEP 380. # TODO: do we want to use the right types here? result = Register(object_rprimitive) to_yield_reg = Register(object_rprimitive) received_reg = Register(object_rprimitive) if isinstance(o, YieldFromExpr): iter_val = builder.call_c(iter_op, [builder.accept(o.expr)], o.line) else: iter_val = builder.call_c(coro_op, [builder.accept(o.expr)], o.line) iter_reg = builder.maybe_spill_assignable(iter_val) stop_block, main_block, done_block = BasicBlock(), BasicBlock( ), BasicBlock() _y_init = builder.call_c(next_raw_op, [builder.read(iter_reg)], o.line) builder.add(Branch(_y_init, stop_block, main_block, Branch.IS_ERROR)) # Try extracting a return value from a StopIteration and return it. # If it wasn't, this reraises the exception. builder.activate_block(stop_block) builder.assign(result, builder.call_c(check_stop_op, [], o.line), o.line) builder.goto(done_block) builder.activate_block(main_block) builder.assign(to_yield_reg, _y_init, o.line) # OK Now the main loop! loop_block = BasicBlock() builder.goto_and_activate(loop_block) def try_body() -> None: builder.assign(received_reg, emit_yield(builder, builder.read(to_yield_reg), o.line), o.line) def except_body() -> None: # The body of the except is all implemented in a C function to # reduce how much code we need to generate. It returns a value # indicating whether to break or yield (or raise an exception). val = Register(object_rprimitive) val_address = builder.add(LoadAddress(object_pointer_rprimitive, val)) to_stop = builder.call_c(yield_from_except_op, [builder.read(iter_reg), val_address], o.line) ok, stop = BasicBlock(), BasicBlock() builder.add(Branch(to_stop, stop, ok, Branch.BOOL)) # The exception got swallowed. Continue, yielding the returned value builder.activate_block(ok) builder.assign(to_yield_reg, val, o.line) builder.nonlocal_control[-1].gen_continue(builder, o.line) # The exception was a StopIteration. Stop iterating. builder.activate_block(stop) builder.assign(result, val, o.line) builder.nonlocal_control[-1].gen_break(builder, o.line) def else_body() -> None: # Do a next() or a .send(). It will return NULL on exception # but it won't automatically propagate. _y = builder.call_c( send_op, [builder.read(iter_reg), builder.read(received_reg)], o.line) ok, stop = BasicBlock(), BasicBlock() builder.add(Branch(_y, stop, ok, Branch.IS_ERROR)) # Everything's fine. Yield it. builder.activate_block(ok) builder.assign(to_yield_reg, _y, o.line) builder.nonlocal_control[-1].gen_continue(builder, o.line) # Try extracting a return value from a StopIteration and return it. # If it wasn't, this rereaises the exception. builder.activate_block(stop) builder.assign(result, builder.call_c(check_stop_op, [], o.line), o.line) builder.nonlocal_control[-1].gen_break(builder, o.line) builder.push_loop_stack(loop_block, done_block) transform_try_except(builder, try_body, [(None, None, except_body)], else_body, o.line) builder.pop_loop_stack() builder.goto_and_activate(done_block) return builder.read(result)
def test_assign_multi(self) -> None: t = RArray(object_rprimitive, 2) a = Register(t, 'a') self.registers.append(a) self.assert_emit(AssignMulti(a, [self.o, self.o2]), """PyObject *cpy_r_a[2] = {cpy_r_o, cpy_r_o2};""")
def test_long_signed(self) -> None: a = Register(int64_rprimitive, 'a') self.assert_emit(Assign(a, Integer(-(1 << 31) + 1, int64_rprimitive)), """cpy_r_a = -2147483647;""") self.assert_emit(Assign(a, Integer(-(1 << 31), int64_rprimitive)), """cpy_r_a = -2147483648LL;""")