def binary_op(self, lreg: Value, rreg: Value, expr_op: str, line: int) -> Value: # Special case == and != when we can resolve the method call statically. value = None if expr_op in ('==', '!='): value = self.translate_eq_cmp(lreg, rreg, expr_op, line) if value is not None: return value # generate fast binary logic ops on short ints if (is_short_int_rprimitive(lreg.type) and is_short_int_rprimitive(rreg.type) and expr_op in int_logical_op_mapping.keys()): return self.binary_int_op(bool_rprimitive, lreg, rreg, int_logical_op_mapping[expr_op][0], line) call_c_ops_candidates = c_binary_ops.get(expr_op, []) target = self.matching_call_c(call_c_ops_candidates, [lreg, rreg], line) if target: return target ops = binary_ops.get(expr_op, []) target = self.matching_primitive_op(ops, [lreg, rreg], line) assert target, 'Unsupported binary operation: %s' % expr_op return target
def convert_expr(builder: IRBuilder, format_ops: List[FormatOp], exprs: List[Expression], line: int) -> Optional[List[Value]]: """Convert expressions into string literals with the guidance of FormatOps.""" if len(format_ops) != len(exprs): return None converted = [] for x, format_op in zip(exprs, format_ops): node_type = builder.node_type(x) if format_op == FormatOp.STR: if is_str_rprimitive(node_type): var_str = builder.accept(x) elif is_int_rprimitive(node_type) or is_short_int_rprimitive( node_type): var_str = builder.call_c(int_to_str_op, [builder.accept(x)], line) else: var_str = builder.call_c(str_op, [builder.accept(x)], line) elif format_op == FormatOp.INT: if is_int_rprimitive(node_type) or is_short_int_rprimitive( node_type): var_str = builder.call_c(int_to_str_op, [builder.accept(x)], line) else: return None else: return None converted.append(var_str) return converted
def __init__(self, value: int, line: int = -1, rtype: RType = short_int_rprimitive) -> None: super().__init__(line) if is_short_int_rprimitive(rtype) or is_int_rprimitive(rtype): self.value = value * 2 else: self.value = value self.type = rtype
def emit_box(self, src: str, dest: str, typ: RType, declare_dest: bool = False, can_borrow: bool = False) -> None: """Emit code for boxing a value of given type. Generate a simple assignment if no boxing is needed. The source reference count is stolen for the result (no need to decref afterwards). """ # TODO: Always generate a new reference (if a reference type) if declare_dest: declaration = 'PyObject *' else: declaration = '' if is_int_rprimitive(typ) or is_short_int_rprimitive(typ): # Steal the existing reference if it exists. self.emit_line('{}{} = CPyTagged_StealAsObject({});'.format( declaration, dest, src)) elif is_bool_rprimitive(typ): # N.B: bool is special cased to produce a borrowed value # after boxing, so we don't need to increment the refcount # when this comes directly from a Box op. self.emit_lines('{}{} = {} ? Py_True : Py_False;'.format( declaration, dest, src)) if not can_borrow: self.emit_inc_ref(dest, object_rprimitive) elif is_none_rprimitive(typ): # N.B: None is special cased to produce a borrowed value # after boxing, so we don't need to increment the refcount # when this comes directly from a Box op. self.emit_lines('{}{} = Py_None;'.format(declaration, dest)) if not can_borrow: self.emit_inc_ref(dest, object_rprimitive) elif isinstance(typ, RTuple): self.declare_tuple_struct(typ) self.emit_line('{}{} = PyTuple_New({});'.format( declaration, dest, len(typ.types))) self.emit_line('if (unlikely({} == NULL))'.format(dest)) self.emit_line(' CPyError_OutOfMemory();') # TODO: Fail if dest is None for i in range(0, len(typ.types)): if not typ.is_unboxed: self.emit_line('PyTuple_SET_ITEM({}, {}, {}.f{}'.format( dest, i, src, i)) else: inner_name = self.temp_name() self.emit_box('{}.f{}'.format(src, i), inner_name, typ.types[i], declare_dest=True) self.emit_line('PyTuple_SET_ITEM({}, {}, {});'.format( dest, i, inner_name)) else: assert not typ.is_unboxed # Type is boxed -- trivially just assign. self.emit_line('{}{} = {};'.format(declaration, dest, src))
def gen_step(self) -> None: builder = self.builder line = self.line # Increment curr_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.binary_int_op(short_int_rprimitive, builder.read(self.index_reg, line), builder.add(LoadInt(self.step)), BinaryIntOp.ADD, 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 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 __init__(self, value: int, rtype: RType = short_int_rprimitive, line: int = -1) -> None: if is_short_int_rprimitive(rtype) or is_int_rprimitive(rtype): self.value = value * 2 else: self.value = value self.type = rtype self.line = line
def __init__(self, items: List[Value], line: int) -> None: super().__init__(line) self.items = items # Don't keep track of the fact that an int is short after it # is put into a tuple, since we don't properly implement # runtime subtyping for tuples. self.tuple_type = RTuple( [arg.type if not is_short_int_rprimitive(arg.type) else int_rprimitive for arg in items]) self.type = self.tuple_type
def compare_tagged(self, lhs: Value, rhs: Value, op: str, line: int) -> Value: """Compare two tagged integers using given op""" # generate fast binary logic ops on short ints if is_short_int_rprimitive(lhs.type) and is_short_int_rprimitive( rhs.type): return self.comparison_op(lhs, rhs, int_comparison_op_mapping[op][0], line) op_type, c_func_desc, negate_result, swap_op = int_comparison_op_mapping[ op] result = self.alloc_temp(bool_rprimitive) short_int_block, int_block, out = BasicBlock(), BasicBlock( ), BasicBlock() check_lhs = self.check_tagged_short_int(lhs, line) if op in ("==", "!="): check = check_lhs else: # for non-equal logical ops(less than, greater than, etc.), need to check both side check_rhs = self.check_tagged_short_int(rhs, line) check = self.binary_int_op(bool_rprimitive, check_lhs, check_rhs, BinaryIntOp.AND, line) branch = Branch(check, short_int_block, int_block, Branch.BOOL_EXPR) branch.negated = False self.add(branch) self.activate_block(short_int_block) eq = self.comparison_op(lhs, rhs, op_type, line) self.add(Assign(result, eq, line)) self.goto(out) self.activate_block(int_block) if swap_op: args = [rhs, lhs] else: args = [lhs, rhs] call = self.call_c(c_func_desc, args, line) if negate_result: # TODO: introduce UnaryIntOp? call_result = self.unary_op(call, "not", line) else: call_result = call self.add(Assign(result, call_result, line)) self.goto_and_activate(out) return result
def visit_rprimitive(self, left: RPrimitive) -> bool: right = self.right if is_bool_rprimitive(left): if is_int_rprimitive(right): return True elif is_bit_rprimitive(left): if is_bool_rprimitive(right) or is_int_rprimitive(right): return True elif is_short_int_rprimitive(left): if is_int_rprimitive(right): return True return left is right
def visit_rprimitive(self, left: RPrimitive) -> bool: if is_bool_rprimitive(left) and is_int_rprimitive(self.right): return True if is_short_int_rprimitive(left) and is_int_rprimitive(self.right): return True return left is self.right
def emit_unbox(self, src: str, dest: str, typ: RType, custom_failure: Optional[str] = None, declare_dest: bool = False, borrow: bool = False, optional: bool = False) -> None: """Emit code for unboxing a value of given type (from PyObject *). Evaluate C code in 'failure' if the value has an incompatible type. Always generate a new reference. Args: src: Name of source C variable dest: Name of target C variable typ: Type of value failure: What happens on error declare_dest: If True, also declare the variable 'dest' borrow: If True, create a borrowed reference """ # TODO: Verify refcount handling. raise_exc = 'CPy_TypeError("{}", {});'.format(self.pretty_name(typ), src) if custom_failure is not None: failure = [raise_exc, custom_failure] else: failure = [raise_exc, '%s = %s;' % (dest, self.c_error_value(typ))] if is_int_rprimitive(typ) or is_short_int_rprimitive(typ): if declare_dest: self.emit_line('CPyTagged {};'.format(dest)) self.emit_arg_check(src, dest, typ, '(likely(PyLong_Check({})))'.format(src), optional) if borrow: self.emit_line( ' {} = CPyTagged_BorrowFromObject({});'.format( dest, src)) else: self.emit_line(' {} = CPyTagged_FromObject({});'.format( dest, src)) self.emit_line('else {') self.emit_lines(*failure) self.emit_line('}') elif is_bool_rprimitive(typ) or is_bit_rprimitive(typ): # Whether we are borrowing or not makes no difference. if declare_dest: self.emit_line('char {};'.format(dest)) self.emit_arg_check(src, dest, typ, '(unlikely(!PyBool_Check({}))) {{'.format(src), optional) self.emit_lines(*failure) self.emit_line('} else') conversion = '{} == Py_True'.format(src) self.emit_line(' {} = {};'.format(dest, conversion)) elif is_none_rprimitive(typ): # Whether we are borrowing or not makes no difference. if declare_dest: self.emit_line('char {};'.format(dest)) self.emit_arg_check(src, dest, typ, '(unlikely({} != Py_None)) {{'.format(src), optional) self.emit_lines(*failure) self.emit_line('} else') self.emit_line(' {} = 1;'.format(dest)) elif isinstance(typ, RTuple): self.declare_tuple_struct(typ) if declare_dest: self.emit_line('{} {};'.format(self.ctype(typ), dest)) # HACK: The error handling for unboxing tuples is busted # and instead of fixing it I am just wrapping it in the # cast code which I think is right. This is not good. if optional: self.emit_line('if ({} == NULL) {{'.format(src)) self.emit_line('{} = {};'.format(dest, self.c_error_value(typ))) self.emit_line('} else {') cast_temp = self.temp_name() self.emit_tuple_cast(src, cast_temp, typ, declare_dest=True, err='', src_type=None) self.emit_line('if (unlikely({} == NULL)) {{'.format(cast_temp)) # self.emit_arg_check(src, dest, typ, # '(!PyTuple_Check({}) || PyTuple_Size({}) != {}) {{'.format( # src, src, len(typ.types)), optional) self.emit_lines(*failure) # TODO: Decrease refcount? self.emit_line('} else {') if not typ.types: self.emit_line('{}.empty_struct_error_flag = 0;'.format(dest)) for i, item_type in enumerate(typ.types): temp = self.temp_name() # emit_tuple_cast above checks the size, so this should not fail self.emit_line( 'PyObject *{} = PyTuple_GET_ITEM({}, {});'.format( temp, src, i)) temp2 = self.temp_name() # Unbox or check the item. if item_type.is_unboxed: self.emit_unbox(temp, temp2, item_type, custom_failure, declare_dest=True, borrow=borrow) else: if not borrow: self.emit_inc_ref(temp, object_rprimitive) self.emit_cast(temp, temp2, item_type, declare_dest=True) self.emit_line('{}.f{} = {};'.format(dest, i, temp2)) self.emit_line('}') if optional: self.emit_line('}') else: assert False, 'Unboxing not implemented: %s' % typ
def emit_unbox(self, src: str, dest: str, typ: RType, *, declare_dest: bool = False, error: Optional[ErrorHandler] = None, raise_exception: bool = True, optional: bool = False, borrow: bool = False) -> None: """Emit code for unboxing a value of given type (from PyObject *). By default, assign error value to dest if the value has an incompatible type and raise TypeError. These can be customized using 'error' and 'raise_exception'. Generate a new reference unless 'borrow' is True. Args: src: Name of source C variable dest: Name of target C variable typ: Type of value declare_dest: If True, also declare the variable 'dest' error: What happens on error raise_exception: If True, also raise TypeError on failure borrow: If True, create a borrowed reference """ error = error or AssignHandler() # TODO: Verify refcount handling. if isinstance(error, AssignHandler): failure = f'{dest} = {self.c_error_value(typ)};' elif isinstance(error, GotoHandler): failure = 'goto %s;' % error.label else: assert isinstance(error, ReturnHandler) failure = 'return %s;' % error.value if raise_exception: raise_exc = f'CPy_TypeError("{self.pretty_name(typ)}", {src}); ' failure = raise_exc + failure if is_int_rprimitive(typ) or is_short_int_rprimitive(typ): if declare_dest: self.emit_line(f'CPyTagged {dest};') self.emit_arg_check(src, dest, typ, f'(likely(PyLong_Check({src})))', optional) if borrow: self.emit_line(f' {dest} = CPyTagged_BorrowFromObject({src});') else: self.emit_line(f' {dest} = CPyTagged_FromObject({src});') self.emit_line('else {') self.emit_line(failure) self.emit_line('}') elif is_bool_rprimitive(typ) or is_bit_rprimitive(typ): # Whether we are borrowing or not makes no difference. if declare_dest: self.emit_line(f'char {dest};') self.emit_arg_check(src, dest, typ, f'(unlikely(!PyBool_Check({src}))) {{', optional) self.emit_line(failure) self.emit_line('} else') conversion = f'{src} == Py_True' self.emit_line(f' {dest} = {conversion};') elif is_none_rprimitive(typ): # Whether we are borrowing or not makes no difference. if declare_dest: self.emit_line(f'char {dest};') self.emit_arg_check(src, dest, typ, f'(unlikely({src} != Py_None)) {{', optional) self.emit_line(failure) self.emit_line('} else') self.emit_line(f' {dest} = 1;') elif isinstance(typ, RTuple): self.declare_tuple_struct(typ) if declare_dest: self.emit_line(f'{self.ctype(typ)} {dest};') # HACK: The error handling for unboxing tuples is busted # and instead of fixing it I am just wrapping it in the # cast code which I think is right. This is not good. if optional: self.emit_line(f'if ({src} == NULL) {{') self.emit_line(f'{dest} = {self.c_error_value(typ)};') self.emit_line('} else {') cast_temp = self.temp_name() self.emit_tuple_cast(src, cast_temp, typ, declare_dest=True, err='', src_type=None) self.emit_line(f'if (unlikely({cast_temp} == NULL)) {{') # self.emit_arg_check(src, dest, typ, # '(!PyTuple_Check({}) || PyTuple_Size({}) != {}) {{'.format( # src, src, len(typ.types)), optional) self.emit_line(failure) # TODO: Decrease refcount? self.emit_line('} else {') if not typ.types: self.emit_line(f'{dest}.empty_struct_error_flag = 0;') for i, item_type in enumerate(typ.types): temp = self.temp_name() # emit_tuple_cast above checks the size, so this should not fail self.emit_line(f'PyObject *{temp} = PyTuple_GET_ITEM({src}, {i});') temp2 = self.temp_name() # Unbox or check the item. if item_type.is_unboxed: self.emit_unbox(temp, temp2, item_type, raise_exception=raise_exception, error=error, declare_dest=True, borrow=borrow) else: if not borrow: self.emit_inc_ref(temp, object_rprimitive) self.emit_cast(temp, temp2, item_type, declare_dest=True) self.emit_line(f'{dest}.f{i} = {temp2};') self.emit_line('}') if optional: self.emit_line('}') else: assert False, 'Unboxing not implemented: %s' % typ