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 = builder.add(LoadInt(0)) else: index_reg = builder.binary_op(self.load_len(), builder.add(LoadInt(1)), '-', self.line) self.index_target = builder.maybe_spill_assignable(index_reg) self.target_type = target_type
def load_static_int(self, value: int) -> Value: """Loads a static integer Python 'int' object into a register.""" if abs(value) > MAX_LITERAL_SHORT_INT: static_symbol = self.literal_static_name(value) return self.add(LoadStatic(int_rprimitive, static_symbol, ann=value)) else: return self.add(LoadInt(value))
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.primitive_op( unsafe_short_add, [builder.read(self.index_reg, line), builder.add(LoadInt(self.step))], line) else: new_val = builder.binary_op( builder.read(self.index_reg, line), builder.add(LoadInt(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 builder.assign(self.index_target, builder.primitive_op( unsafe_short_add, [builder.read(self.index_target, line), builder.add(LoadInt(step))], line), 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 = builder.add(LoadInt(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 add_bool_branch(self, value: Value, true: BasicBlock, false: BasicBlock) -> None: if is_runtime_subtype(value.type, int_rprimitive): zero = self.add(LoadInt(0)) value = self.binary_op(value, zero, '!=', value.line) elif is_same_type(value.type, list_rprimitive): length = self.primitive_op(list_len_op, [value], value.line) zero = self.add(LoadInt(0)) value = self.binary_op(length, zero, '!=', value.line) elif (isinstance(value.type, RInstance) and value.type.class_ir.is_ext_class and value.type.class_ir.has_method('__bool__')): # Directly call the __bool__ method on classes that have it. value = self.gen_method_call(value, '__bool__', [], bool_rprimitive, value.line) else: value_type = optional_value_type(value.type) if value_type is not None: is_none = self.binary_op(value, self.none_object(), 'is not', value.line) branch = Branch(is_none, true, false, Branch.BOOL_EXPR) self.add(branch) always_truthy = False if isinstance(value_type, RInstance): # check whether X.__bool__ is always just the default (object.__bool__) if (not value_type.class_ir.has_method('__bool__') and value_type.class_ir.is_method_final('__bool__')): always_truthy = True if not always_truthy: # Optional[X] where X may be falsey and requires a check branch.true = BasicBlock() self.activate_block(branch.true) # unbox_or_cast instead of coerce because we want the # type to change even if it is a subtype. remaining = self.unbox_or_cast(value, value_type, value.line) self.add_bool_branch(remaining, true, false) return elif not is_same_type(value.type, bool_rprimitive): value = self.primitive_op(bool_op, [value], value.line) self.add(Branch(value, true, false, Branch.BOOL_EXPR))
def 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.primitive_op( unsafe_short_add, [builder.read(self.index_reg, line), builder.add(LoadInt(1))], line) builder.assign(self.index_reg, new_val, line) builder.assign(self.index_target, new_val, line)
def translate_len(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Optional[Value]: # Special case builtins.len 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 builder.add(LoadInt(len(expr_rtype.types))) return None
def visit_call_expr(self, expr: CallExpr) -> Register: if isinstance(expr.callee, MemberExpr): is_module_call = self.is_module_member_expr(expr.callee) if expr.callee.expr in self.types and not is_module_call: target = self.translate_special_method_call(expr.callee, expr) if target: return target # Either its a module call or translating to a special method call failed, so we have # to fallback to a PyCall function = self.accept(expr.callee) return self.py_call(function, expr.args, self.node_type(expr)) assert isinstance(expr.callee, NameExpr) fn = expr.callee.name # TODO: fullname if fn == 'len' and len(expr.args) == 1 and expr.arg_kinds == [ARG_POS]: target = self.alloc_target(IntRType()) arg = self.accept(expr.args[0]) expr_rtype = self.node_type(expr.args[0]) if expr_rtype.name == 'list': self.add(PrimitiveOp(target, PrimitiveOp.LIST_LEN, arg)) elif expr_rtype.name == 'sequence_tuple': self.add( PrimitiveOp(target, PrimitiveOp.HOMOGENOUS_TUPLE_LEN, arg)) elif isinstance(expr_rtype, TupleRType): self.add(LoadInt(target, len(expr_rtype.types))) else: assert False, "unsupported use of len" # Handle conversion to sequence tuple elif fn == 'tuple' and len( expr.args) == 1 and expr.arg_kinds == [ARG_POS]: target = self.alloc_target(SequenceTupleRType()) arg = self.accept(expr.args[0]) self.add( PrimitiveOp(target, PrimitiveOp.LIST_TO_HOMOGENOUS_TUPLE, arg)) else: target_type = self.node_type(expr) if not (self.is_native_name_expr(expr.callee)): function = self.accept(expr.callee) return self.py_call(function, expr.args, target_type) target = self.alloc_target(target_type) args = [self.accept(arg) for arg in expr.args] self.add(Call(target, fn, args)) return target
def test_register(self) -> None: self.temp = self.env.add_temp(IntRType()) self.block.ops.append(LoadInt(self.temp, 5)) fn = FuncIR('myfunc', [self.arg], ListRType(), [self.block], self.env) emitter = Emitter(EmitterContext()) generate_native_function(fn, emitter) result = emitter.fragments assert_string_arrays_equal([ 'static 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 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 test_register(self) -> None: self.env.temp_index = 0 op = LoadInt(5) self.block.ops.append(op) self.env.add_op(op) fn = FuncIR('myfunc', None, 'mod', FuncSignature([self.arg], list_rprimitive), [self.block], self.env) emitter = Emitter(EmitterContext(['mod'])) generate_native_function(fn, emitter, 'prog.py', 'prog') result = emitter.fragments assert_string_arrays_equal( [ 'static 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 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, builder.add(LoadInt(-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.error_handlers.append(None) builder.goto_new_block() # 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.primitive_op(set_stop_iteration_value, [value], NO_TRACEBACK_LINE_NO) builder.add(Unreachable()) builder.error_handlers.pop()
def test_load_int(self) -> None: self.assert_emit(LoadInt(5), "cpy_r_r0 = 10;")
def visit_for_stmt(self, s: ForStmt) -> Register: if (isinstance(s.expr, CallExpr) and isinstance(s.expr.callee, RefExpr) and s.expr.callee.fullname == 'builtins.range'): self.push_loop_stack() # Special case for x in range(...) # TODO: Check argument counts and kinds; check the lvalue end = s.expr.args[0] end_reg = self.accept(end) # Initialize loop index to 0. index_reg = self.assign(s.index, IntExpr(0), IntRType(), IntRType(), declare_new=True) goto = Goto(INVALID_LABEL) self.add(goto) # Add loop condition check. top = self.new_block() goto.label = top.label branch = Branch(index_reg, end_reg, INVALID_LABEL, INVALID_LABEL, Branch.INT_LT) self.add(branch) branches = [branch] body = self.new_block() self.set_branches(branches, True, body) s.body.accept(self) end_goto = Goto(INVALID_LABEL) self.add(end_goto) end_block = self.new_block() end_goto.label = end_block.label # Increment index register. one_reg = self.alloc_temp(IntRType()) self.add(LoadInt(one_reg, 1)) self.add( PrimitiveOp(index_reg, PrimitiveOp.INT_ADD, index_reg, one_reg)) # Go back to loop condition check. self.add(Goto(top.label)) next = self.new_block() self.set_branches(branches, False, next) self.pop_loop_stack(end_block, next) return INVALID_REGISTER if self.node_type(s.expr).name == 'list': self.push_loop_stack() expr_reg = self.accept(s.expr) index_reg = self.alloc_temp(IntRType()) self.add(LoadInt(index_reg, 0)) one_reg = self.alloc_temp(IntRType()) self.add(LoadInt(one_reg, 1)) assert isinstance(s.index, NameExpr) assert isinstance(s.index.node, Var) lvalue_reg = self.environment.add_local(s.index.node, self.node_type(s.index)) condition_block = self.goto_new_block() # For compatibility with python semantics we recalculate the length # at every iteration. len_reg = self.alloc_temp(IntRType()) self.add(PrimitiveOp(len_reg, PrimitiveOp.LIST_LEN, expr_reg)) branch = Branch(index_reg, len_reg, INVALID_LABEL, INVALID_LABEL, Branch.INT_LT) self.add(branch) branches = [branch] body_block = self.new_block() self.set_branches(branches, True, body_block) target_list_type = self.types[s.expr] assert isinstance(target_list_type, Instance) target_type = self.type_to_rtype(target_list_type.args[0]) value_box = self.alloc_temp(ObjectRType()) self.add( PrimitiveOp(value_box, PrimitiveOp.LIST_GET, expr_reg, index_reg)) self.unbox_or_cast(value_box, target_type, target=lvalue_reg) s.body.accept(self) end_block = self.goto_new_block() self.add( PrimitiveOp(index_reg, PrimitiveOp.INT_ADD, index_reg, one_reg)) self.add(Goto(condition_block.label)) next_block = self.new_block() self.set_branches(branches, False, next_block) self.pop_loop_stack(end_block, next_block) return INVALID_REGISTER assert False, 'for not supported'
def visit_int_expr(self, expr: IntExpr) -> Register: reg = self.alloc_target(IntRType()) self.add(LoadInt(reg, expr.value)) return reg
def init(self) -> None: builder = self.builder # Initialize loop index to 0. self.index_target = builder.get_assignment_target( self.index) # type: Union[Register, AssignmentTarget] builder.assign(self.index_target, builder.add(LoadInt(0)), self.line)