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 init(self, expr_reg: Value, target_type: RType, reverse: bool) -> None: builder = self.builder self.reverse = reverse # Define target to contain the expression, along with the index that will be used # for the for-loop. If we are inside of a generator function, spill these into the # environment class. self.expr_target = builder.maybe_spill(expr_reg) if not reverse: index_reg = Integer(0) # type: Value else: index_reg = builder.binary_op(self.load_len(self.expr_target), Integer(1), '-', self.line) self.index_target = builder.maybe_spill_assignable(index_reg) self.target_type = target_type
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 test_assign(self) -> None: reg = register('foo') n = Integer(2) op1 = Assign(reg, n) op2 = Assign(reg, n) block = make_block([op1, op2]) assert generate_names_for_ir([reg], [block]) == {reg: 'foo'}
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 gen_step(self) -> None: builder = self.builder line = self.line # Increment index register. If the range is known to fit in short ints, use # short ints. if (is_short_int_rprimitive(self.start_reg.type) and is_short_int_rprimitive(self.end_reg.type)): new_val = builder.int_op(short_int_rprimitive, builder.read(self.index_reg, line), Integer(self.step), IntOp.ADD, line) else: new_val = builder.binary_op( builder.read(self.index_reg, line), Integer(self.step), '+', line) builder.assign(self.index_reg, new_val, line) builder.assign(self.index_target, new_val, line)
def gen_step(self) -> None: # Step to the next item. builder = self.builder line = self.line step = 1 if not self.reverse else -1 add = builder.int_op(short_int_rprimitive, builder.read(self.index_target, line), Integer(step), IntOp.ADD, line) builder.assign(self.index_target, add, line)
def init(self) -> None: builder = self.builder # Create a register to store the state of the loop index and # initialize this register along with the loop index to 0. zero = Integer(0) self.index_reg = builder.maybe_spill_assignable(zero) self.index_target = builder.get_assignment_target( self.index) # type: Union[Register, AssignmentTarget] builder.assign(self.index_target, zero, self.line)
def gen_step(self) -> None: builder = self.builder line = self.line # We can safely assume that the integer is short, since we are not going to wrap # around a 63-bit integer. # NOTE: This would be questionable if short ints could be 32 bits. new_val = builder.int_op(short_int_rprimitive, builder.read(self.index_reg, line), Integer(1), IntOp.ADD, line) builder.assign(self.index_reg, new_val, line) builder.assign(self.index_target, new_val, line)
def test_invalid_return_type(self) -> None: ret = Return(value=Integer(value=5, rtype=int32_rprimitive)) fn = FuncIR( decl=self.func_decl(name="func_1", ret_type=int64_rprimitive), arg_regs=[], blocks=[self.basic_block([ret])], ) assert_has_error( fn, FnError(source=ret, desc="Cannot coerce source type int32 to dest type int64"), )
def join_formatted_bytes(builder: IRBuilder, literals: List[str], substitutions: List[Value], line: int) -> Value: """Merge the list of literals and the list of substitutions alternatively using 'bytes_build_op'.""" result_list: List[Value] = [Integer(0, c_pyssize_t_rprimitive)] for a, b in zip(literals, substitutions): if a: result_list.append(builder.load_bytes_from_str_literal(a)) result_list.append(b) if literals[-1]: result_list.append(builder.load_bytes_from_str_literal(literals[-1])) # Special case for empty bytes and literal if len(result_list) == 1: return builder.load_bytes_from_str_literal('') if not substitutions and len(result_list) == 2: return result_list[1] result_list[0] = Integer(len(result_list) - 1, c_pyssize_t_rprimitive) return builder.call_c(bytes_build_op, result_list, line)
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 join_formatted_strings(builder: IRBuilder, literals: Optional[List[str]], substitutions: List[Value], line: int) -> Value: """Merge the list of literals and the list of substitutions alternatively using 'str_build_op'. `substitutions` is the result value of formatting conversions. If the `literals` is set to None, we simply join the substitutions; Otherwise, the `literals` is the literal substrings of the original format string and its length should be exactly one more than substitutions. For example: (1) 'This is a %s and the value is %d' -> literals: ['This is a ', ' and the value is', ''] (2) '{} and the value is {}' -> literals: ['', ' and the value is', ''] """ # The first parameter for str_build_op is the total size of # the following PyObject* result_list: List[Value] = [Integer(0, c_pyssize_t_rprimitive)] if literals is not None: for a, b in zip(literals, substitutions): if a: result_list.append(builder.load_str(a)) result_list.append(b) if literals[-1]: result_list.append(builder.load_str(literals[-1])) else: result_list.extend(substitutions) # Special case for empty string and literal string if len(result_list) == 1: return builder.load_str("") if not substitutions and len(result_list) == 2: return result_list[1] result_list[0] = Integer(len(result_list) - 1, c_pyssize_t_rprimitive) return builder.call_c(str_build_op, result_list, line)
def translate_len(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Optional[Value]: if (len(expr.args) == 1 and expr.arg_kinds == [ARG_POS]): expr_rtype = builder.node_type(expr.args[0]) if isinstance(expr_rtype, RTuple): # len() of fixed-length tuple can be trivially determined # statically, though we still need to evaluate it. builder.accept(expr.args[0]) return Integer(len(expr_rtype.types)) else: obj = builder.accept(expr.args[0]) return builder.builtin_len(obj, expr.line) return None
def translate_dict_setdefault( builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Optional[Value]: """Special case for 'dict.setdefault' which would only construct default empty collection when needed. The dict_setdefault_spec_init_op checks whether the dict contains the key and would construct the empty collection only once. For example, this specializer works for the following cases: d.setdefault(key, set()).add(value) d.setdefault(key, []).append(value) d.setdefault(key, {})[inner_key] = inner_val """ if (len(expr.args) == 2 and expr.arg_kinds == [ARG_POS, ARG_POS] and isinstance(callee, MemberExpr)): arg = expr.args[1] if isinstance(arg, ListExpr): if len(arg.items): return None data_type = Integer(1, c_int_rprimitive, expr.line) elif isinstance(arg, DictExpr): if len(arg.items): return None data_type = Integer(2, c_int_rprimitive, expr.line) elif (isinstance(arg, CallExpr) and isinstance(arg.callee, NameExpr) and arg.callee.fullname == 'builtins.set'): if len(arg.args): return None data_type = Integer(3, c_int_rprimitive, expr.line) else: return None callee_dict = builder.accept(callee.expr) key_val = builder.accept(expr.args[0]) return builder.call_c(dict_setdefault_spec_init_op, [callee_dict, key_val, data_type], expr.line) return None
def init(self, expr_reg: Value, target_type: RType) -> None: builder = self.builder self.target_type = target_type # We add some variables to environment class, so they can be read across yield. self.expr_target = builder.maybe_spill(expr_reg) offset = Integer(0) self.offset_target = builder.maybe_spill_assignable(offset) self.size = builder.maybe_spill(self.load_len(self.expr_target)) # For dict class (not a subclass) this is the dictionary itself. iter_reg = builder.call_c(self.dict_iter_op, [expr_reg], self.line) self.iter_target = builder.maybe_spill(iter_reg)
def generate_singledispatch_callable_class_ctor(builder: IRBuilder) -> None: """Create an __init__ that sets registry and dispatch_cache to empty dicts""" line = -1 class_ir = builder.fn_info.callable_class.ir with builder.enter_method(class_ir, '__init__', bool_rprimitive): empty_dict = builder.call_c(dict_new_op, [], line) builder.add(SetAttr(builder.self(), 'registry', empty_dict, line)) cache_dict = builder.call_c(dict_new_op, [], line) dispatch_cache_str = builder.load_str('dispatch_cache') # use the py_setattr_op instead of SetAttr so that it also gets added to our __dict__ builder.call_c(py_setattr_op, [builder.self(), dispatch_cache_str, cache_dict], line) # the generated C code seems to expect that __init__ returns a char, so just return 1 builder.add(Return(Integer(1, bool_rprimitive, line), 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, Integer(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 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 emit_yield(builder: IRBuilder, val: Value, line: int) -> Value: retval = builder.coerce(val, builder.ret_types[-1], line) cls = builder.fn_info.generator_class # Create a new block for the instructions immediately following the yield expression, and # set the next label so that the next time '__next__' is called on the generator object, # the function continues at the new block. next_block = BasicBlock() next_label = len(cls.continuation_blocks) cls.continuation_blocks.append(next_block) builder.assign(cls.next_label_target, Integer(next_label), line) builder.add(Return(retval)) builder.activate_block(next_block) add_raise_exception_blocks_to_generator_class(builder, line) assert cls.send_arg_reg is not None return cls.send_arg_reg
def gen_condition(self) -> None: builder = self.builder line = self.line # TODO: Don't reload the length each time when iterating an immutable sequence? 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), Integer(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(self.expr_target) 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 instantiate_generator_class(builder: IRBuilder) -> Value: fitem = builder.fn_info.fitem generator_reg = builder.add(Call(builder.fn_info.generator_class.ir.ctor, [], fitem.line)) # Get the current environment register. If the current function is nested, then the # generator class gets instantiated from the callable class' '__call__' method, and hence # we use the callable class' environment register. Otherwise, we use the original # function's environment register. if builder.fn_info.is_nested: curr_env_reg = builder.fn_info.callable_class.curr_env_reg else: curr_env_reg = builder.fn_info.curr_env_reg # Set the generator class' environment attribute to point at the environment class # defined in the current scope. builder.add(SetAttr(generator_reg, ENV_ATTR_NAME, curr_env_reg, fitem.line)) # Set the generator class' environment class' NEXT_LABEL_ATTR_NAME attribute to 0. zero = Integer(0) builder.add(SetAttr(curr_env_reg, NEXT_LABEL_ATTR_NAME, zero, fitem.line)) return generator_reg
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 process_sequence_assignment(self, target: AssignmentTargetTuple, rvalue: Value, line: int) -> None: """Process assignment like 'x, y = s', where s is a variable-length list or tuple.""" # Check the length of sequence. expected_len = Integer(len(target.items), c_pyssize_t_rprimitive) self.builder.call_c(check_unpack_count_op, [rvalue, expected_len], line) # Read sequence items. values = [] for i in range(len(target.items)): item = target.items[i] index = self.builder.load_int(i) if is_list_rprimitive(rvalue.type): item_value = self.call_c(list_get_item_unsafe_op, [rvalue, index], line) else: item_value = self.builder.gen_method_call( rvalue, '__getitem__', [index], item.type, line) values.append(item_value) # Assign sequence items to the target lvalues. for lvalue, value in zip(target.items, values): self.assign(lvalue, value, line)
def process_iterator_tuple_assignment(self, target: AssignmentTargetTuple, rvalue_reg: Value, line: int) -> None: iterator = self.call_c(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.call_c(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.call_c(to_list, [iterator], line) iter_list_len = self.builtin_len(iter_list, line) post_star_len = Integer(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)) 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.call_c(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.call_c(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 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 make_for_loop_generator(builder: IRBuilder, index: Lvalue, expr: Expression, body_block: BasicBlock, loop_exit: BasicBlock, line: int, nested: bool = False) -> 'ForGenerator': """Return helper object for generating a for loop over an iterable. If "nested" is True, this is a nested iterator such as "e" in "enumerate(e)". """ rtyp = builder.node_type(expr) if is_sequence_rprimitive(rtyp): # Special case "for x in <list>". expr_reg = builder.accept(expr) target_type = builder.get_sequence_type(expr) for_list = ForSequence(builder, index, body_block, loop_exit, line, nested) for_list.init(expr_reg, target_type, reverse=False) return for_list if is_dict_rprimitive(rtyp): # Special case "for k in <dict>". expr_reg = builder.accept(expr) target_type = builder.get_dict_key_type(expr) for_dict = ForDictionaryKeys(builder, index, body_block, loop_exit, line, nested) for_dict.init(expr_reg, target_type) return for_dict if (isinstance(expr, CallExpr) and isinstance(expr.callee, RefExpr)): if (expr.callee.fullname == 'builtins.range' and (len(expr.args) <= 2 or (len(expr.args) == 3 and builder.extract_int(expr.args[2]) is not None)) and set(expr.arg_kinds) == {ARG_POS}): # Special case "for x in range(...)". # We support the 3 arg form but only for int literals, since it doesn't # seem worth the hassle of supporting dynamically determining which # direction of comparison to do. if len(expr.args) == 1: start_reg = Integer(0) # type: Value end_reg = builder.accept(expr.args[0]) else: start_reg = builder.accept(expr.args[0]) end_reg = builder.accept(expr.args[1]) if len(expr.args) == 3: step = builder.extract_int(expr.args[2]) assert step is not None if step == 0: builder.error("range() step can't be zero", expr.args[2].line) else: step = 1 for_range = ForRange(builder, index, body_block, loop_exit, line, nested) for_range.init(start_reg, end_reg, step) return for_range elif (expr.callee.fullname == 'builtins.enumerate' and len(expr.args) == 1 and expr.arg_kinds == [ARG_POS] and isinstance(index, TupleExpr) and len(index.items) == 2): # Special case "for i, x in enumerate(y)". lvalue1 = index.items[0] lvalue2 = index.items[1] for_enumerate = ForEnumerate(builder, index, body_block, loop_exit, line, nested) for_enumerate.init(lvalue1, lvalue2, expr.args[0]) return for_enumerate elif (expr.callee.fullname == 'builtins.zip' and len(expr.args) >= 2 and set(expr.arg_kinds) == {ARG_POS} and isinstance(index, TupleExpr) and len(index.items) == len(expr.args)): # Special case "for x, y in zip(a, b)". for_zip = ForZip(builder, index, body_block, loop_exit, line, nested) for_zip.init(index.items, expr.args) return for_zip if (expr.callee.fullname == 'builtins.reversed' and len(expr.args) == 1 and expr.arg_kinds == [ARG_POS] and is_sequence_rprimitive(builder.node_type(expr.args[0]))): # Special case "for x in reversed(<list>)". expr_reg = builder.accept(expr.args[0]) target_type = builder.get_sequence_type(expr) for_list = ForSequence(builder, index, body_block, loop_exit, line, nested) for_list.init(expr_reg, target_type, reverse=True) return for_list if (isinstance(expr, CallExpr) and isinstance(expr.callee, MemberExpr) and not expr.args): # Special cases for dictionary iterator methods, like dict.items(). rtype = builder.node_type(expr.callee.expr) if (is_dict_rprimitive(rtype) and expr.callee.name in ('keys', 'values', 'items')): expr_reg = builder.accept(expr.callee.expr) for_dict_type = None # type: Optional[Type[ForGenerator]] if expr.callee.name == 'keys': target_type = builder.get_dict_key_type(expr.callee.expr) for_dict_type = ForDictionaryKeys elif expr.callee.name == 'values': target_type = builder.get_dict_value_type(expr.callee.expr) for_dict_type = ForDictionaryValues else: target_type = builder.get_dict_item_type(expr.callee.expr) for_dict_type = ForDictionaryItems for_dict_gen = for_dict_type(builder, index, body_block, loop_exit, line, nested) for_dict_gen.init(expr_reg, target_type) return for_dict_gen # Default to a generic for loop. expr_reg = builder.accept(expr) for_obj = ForIterable(builder, index, body_block, loop_exit, line, nested) item_type = builder._analyze_iterable_item_type(expr) item_rtype = builder.type_to_rtype(item_type) for_obj.init(expr_reg, item_rtype) return for_obj
from mypyc.ir.ops import (BasicBlock, Op, Return, Integer, Goto, Register, LoadLiteral, Assign) from mypyc.ir.func_ir import FuncIR, FuncDecl, FuncSignature from mypyc.ir.pprint import format_func def assert_has_error(fn: FuncIR, error: FnError) -> None: errors = check_func_ir(fn) assert errors == [error] def assert_no_errors(fn: FuncIR) -> None: assert not check_func_ir(fn) NONE_VALUE = Integer(0, rtype=none_rprimitive) class TestIrcheck(unittest.TestCase): def setUp(self) -> None: self.label = 0 def basic_block(self, ops: List[Op]) -> BasicBlock: self.label += 1 block = BasicBlock(self.label) block.ops = ops return block def func_decl(self, name: str, ret_type: Optional[RType] = None) -> FuncDecl:
def test_integer(self) -> None: self.assert_emit(Assign(self.n, Integer(5)), "cpy_r_n = 10;") self.assert_emit(Assign(self.i32, Integer(5, c_int_rprimitive)), "cpy_r_i32 = 5;")
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;""")