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 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 coerce(self, src: Value, target_type: RType, line: int, force: bool = False) -> Value: """Generate a coercion/cast from one type to other (only if needed). For example, int -> object boxes the source int; int -> int emits nothing; object -> int unboxes the object. All conversions preserve object value. If force is true, always generate an op (even if it is just an assignment) so that the result will have exactly target_type as the type. Returns the register with the converted value (may be same as src). """ if src.type.is_unboxed and not target_type.is_unboxed: return self.box(src) if ((src.type.is_unboxed and target_type.is_unboxed) and not is_runtime_subtype(src.type, target_type)): # To go from one unboxed type to another, we go through a boxed # in-between value, for simplicity. tmp = self.box(src) return self.unbox_or_cast(tmp, target_type, line) if ((not src.type.is_unboxed and target_type.is_unboxed) or not is_subtype(src.type, target_type)): return self.unbox_or_cast(src, target_type, line) elif force: tmp = self.alloc_temp(target_type) self.add(Assign(tmp, src)) return tmp return src
def gen_return(self, builder: 'IRBuilder', value: Value, line: int) -> None: if self.ret_reg is None: self.ret_reg = builder.alloc_temp(builder.ret_types[-1]) builder.add(Assign(self.ret_reg, value)) builder.add(Goto(self.target))
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_entry_blocks(builder: IRBuilder, err_handler: BasicBlock, return_entry: BasicBlock, main_entry: BasicBlock, finally_block: BasicBlock, ret_reg: Optional[Register]) -> Value: old_exc = builder.alloc_temp(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.primitive_op(error_catch_op, [], -1))) builder.goto(finally_block) return old_exc
def box(self, src: Register, typ: RType, target: Optional[Register] = None) -> Register: if typ.supports_unbox: if target is None: target = self.alloc_temp(ObjectRType()) self.add(Box(target, src, typ)) return target else: # Already boxed if target is not None: self.add(Assign(target, src)) return target else: return src
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 get_using_binder(self, reg: Register, var: Var, expr: Expression) -> Register: var_type = self.type_to_rtype(var.type) target_type = self.node_type(expr) if var_type != target_type: # Cast/unbox to the narrower given by the binder. if self.targets[-1] < 0: target = self.alloc_temp(target_type) else: target = self.targets[-1] return self.unbox_or_cast(reg, target_type, target) else: # Regular register access -- binder is not active. if self.targets[-1] < 0: return reg else: target = self.targets[-1] self.add(Assign(target, reg)) return target
def transform_del_item(builder: IRBuilder, target: AssignmentTarget, line: int) -> None: if isinstance(target, AssignmentTargetIndex): builder.gen_method_call( target.base, '__delitem__', [target.index], result_type=None, line=line ) elif isinstance(target, AssignmentTargetAttr): key = builder.load_static_unicode(target.attr) builder.add(PrimitiveOp([target.obj, key], py_delattr_op, line)) elif isinstance(target, AssignmentTargetRegister): # Delete a local by assigning an error value to it, which will # prompt the insertion of uninit checks. builder.add(Assign(target.register, builder.add(LoadErrorValue(target.type, undefines=True)))) elif isinstance(target, AssignmentTargetTuple): for subtarget in target.items: transform_del_item(builder, subtarget, line)
def test_assign_int(self) -> None: self.assert_emit(Assign(self.m, self.n), "cpy_r_m = cpy_r_n;")
def visit_assign(self, op: Assign) -> GenAndKill: return set(op.sources()), {op.dest}