Skip to content

Commit

Permalink
[mypyc] Add BinaryIntOp for low-level integer operations (python#9108)
Browse files Browse the repository at this point in the history
Related: mypyc/mypyc#741

This PR introduces BinaryIntOp to represent all low-level integer binary 
operations.

The block-like logic described in mypyc/mypyc#743 would be handled 
differently, BinaryIntOp would be the building block of it.
  • Loading branch information
TH3CHARLie authored Jul 7, 2020
1 parent 259e0cf commit b98c47e
Show file tree
Hide file tree
Showing 10 changed files with 117 additions and 33 deletions.
5 changes: 4 additions & 1 deletion mypyc/analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
BasicBlock, OpVisitor, Assign, LoadInt, LoadErrorValue, RegisterOp, Goto, Branch, Return, Call,
Environment, Box, Unbox, Cast, Op, Unreachable, TupleGet, TupleSet, GetAttr, SetAttr,
LoadStatic, InitStatic, PrimitiveOp, MethodCall, RaiseStandardError, CallC, LoadGlobal,
Truncate
Truncate, BinaryIntOp
)


Expand Down Expand Up @@ -205,6 +205,9 @@ def visit_truncate(self, op: Truncate) -> GenAndKill:
def visit_load_global(self, op: LoadGlobal) -> GenAndKill:
return self.visit_register_op(op)

def visit_binary_int_op(self, op: BinaryIntOp) -> GenAndKill:
return self.visit_register_op(op)


class DefinedVisitor(BaseAnalysisVisitor):
"""Visitor for finding defined registers.
Expand Down
9 changes: 8 additions & 1 deletion mypyc/codegen/emitfunc.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
OpVisitor, Goto, Branch, Return, Assign, LoadInt, LoadErrorValue, GetAttr, SetAttr,
LoadStatic, InitStatic, TupleGet, TupleSet, Call, IncRef, DecRef, Box, Cast, Unbox,
BasicBlock, Value, MethodCall, PrimitiveOp, EmitterInterface, Unreachable, NAMESPACE_STATIC,
NAMESPACE_TYPE, NAMESPACE_MODULE, RaiseStandardError, CallC, LoadGlobal, Truncate
NAMESPACE_TYPE, NAMESPACE_MODULE, RaiseStandardError, CallC, LoadGlobal, Truncate,
BinaryIntOp
)
from mypyc.ir.rtypes import RType, RTuple, is_int32_rprimitive, is_int64_rprimitive
from mypyc.ir.func_ir import FuncIR, FuncDecl, FUNC_STATICMETHOD, FUNC_CLASSMETHOD
Expand Down Expand Up @@ -436,6 +437,12 @@ def visit_load_global(self, op: LoadGlobal) -> None:
ann = ' /* %s */' % s
self.emit_line('%s = %s;%s' % (dest, op.identifier, ann))

def visit_binary_int_op(self, op: BinaryIntOp) -> None:
dest = self.reg(op)
lhs = self.reg(op.lhs)
rhs = self.reg(op.rhs)
self.emit_line('%s = %s %s %s;' % (dest, lhs, op.op_str[op.op], rhs))

# Helpers

def label(self, label: BasicBlock) -> str:
Expand Down
68 changes: 68 additions & 0 deletions mypyc/ir/ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -1252,6 +1252,70 @@ def accept(self, visitor: 'OpVisitor[T]') -> T:
return visitor.visit_load_global(self)


class BinaryIntOp(RegisterOp):
"""Binary operations on integer types
These ops are low-level and will be eventually generated to simple x op y form.
The left and right values should be of low-level integer types that support those ops
"""
error_kind = ERR_NEVER

# arithmetic
ADD = 0 # type: Final
SUB = 1 # type: Final
MUL = 2 # type: Final
DIV = 3 # type: Final
MOD = 4 # type: Final
# logical
EQ = 100 # type: Final
NEQ = 101 # type: Final
LT = 102 # type: Final
GT = 103 # type: Final
LEQ = 104 # type: Final
GEQ = 105 # type: Final
# bitwise
AND = 200 # type: Final
OR = 201 # type: Final
XOR = 202 # type: Final
LEFT_SHIFT = 203 # type: Final
RIGHT_SHIFT = 204 # type: Final

op_str = {
ADD: '+',
SUB: '-',
MUL: '*',
DIV: '/',
MOD: '%',
EQ: '==',
NEQ: '!=',
LT: '<',
GT: '>',
LEQ: '<=',
GEQ: '>=',
AND: '&',
OR: '|',
XOR: '^',
LEFT_SHIFT: '<<',
RIGHT_SHIFT: '>>',
} # type: Final

def __init__(self, type: RType, lhs: Value, rhs: Value, op: int, line: int = -1) -> None:
super().__init__(line)
self.type = type
self.lhs = lhs
self.rhs = rhs
self.op = op

def sources(self) -> List[Value]:
return [self.lhs, self.rhs]

def to_str(self, env: Environment) -> str:
return env.format('%r = %r %s %r', self, self.lhs, self.op_str[self.op], self.rhs)

def accept(self, visitor: 'OpVisitor[T]') -> T:
return visitor.visit_binary_int_op(self)


@trait
class OpVisitor(Generic[T]):
"""Generic visitor over ops (uses the visitor design pattern)."""
Expand Down Expand Up @@ -1354,6 +1418,10 @@ def visit_truncate(self, op: Truncate) -> T:
def visit_load_global(self, op: LoadGlobal) -> T:
raise NotImplementedError

@abstractmethod
def visit_binary_int_op(self, op: BinaryIntOp) -> T:
raise NotImplementedError


# TODO: Should this live somewhere else?
LiteralsMap = Dict[Tuple[Type[object], Union[int, float, str, bytes, complex]], str]
Expand Down
3 changes: 3 additions & 0 deletions mypyc/irbuild/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,9 @@ def load_module(self, name: str) -> Value:
def call_c(self, desc: CFunctionDescription, args: List[Value], line: int) -> Value:
return self.builder.call_c(desc, args, line)

def binary_int_op(self, type: RType, lhs: Value, rhs: Value, op: int, line: int) -> Value:
return self.builder.binary_int_op(type, lhs, rhs, op, line)

@property
def environment(self) -> Environment:
return self.builder.environment
Expand Down
25 changes: 12 additions & 13 deletions mypyc/irbuild/for_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,16 @@
)
from mypyc.ir.ops import (
Value, BasicBlock, LoadInt, Branch, Register, AssignmentTarget, TupleGet,
AssignmentTargetTuple, TupleSet, OpDescription
AssignmentTargetTuple, TupleSet, OpDescription, BinaryIntOp
)
from mypyc.ir.rtypes import (
RType, is_short_int_rprimitive, is_list_rprimitive, is_sequence_rprimitive,
RTuple, is_dict_rprimitive
RTuple, is_dict_rprimitive, short_int_rprimitive
)
from mypyc.primitives.dict_ops import (
dict_next_key_op, dict_next_value_op, dict_next_item_op, dict_check_size_op,
dict_key_iter_op, dict_value_iter_op, dict_item_iter_op
)
from mypyc.primitives.int_ops import unsafe_short_add
from mypyc.primitives.list_ops import new_list_op, list_append_op, list_get_item_unsafe_op
from mypyc.primitives.generic_ops import iter_op, next_op
from mypyc.primitives.exc_ops import no_err_occurred_op
Expand Down Expand Up @@ -465,10 +464,10 @@ def gen_step(self) -> None:
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)
add = builder.binary_int_op(short_int_rprimitive,
builder.read(self.index_target, line),
builder.add(LoadInt(step)), BinaryIntOp.ADD, line)
builder.assign(self.index_target, add, line)


class ForDictionaryCommon(ForGenerator):
Expand Down Expand Up @@ -635,9 +634,9 @@ def gen_step(self) -> None:
# 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)
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(
Expand Down Expand Up @@ -665,9 +664,9 @@ def gen_step(self) -> None:
# 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)
new_val = builder.binary_int_op(short_int_rprimitive,
builder.read(self.index_reg, line),
builder.add(LoadInt(1)), BinaryIntOp.ADD, line)
builder.assign(self.index_reg, new_val, line)
builder.assign(self.index_target, new_val, line)

Expand Down
5 changes: 4 additions & 1 deletion mypyc/irbuild/ll_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
Assign, Branch, Goto, Call, Box, Unbox, Cast, GetAttr,
LoadStatic, MethodCall, PrimitiveOp, OpDescription, RegisterOp, CallC, Truncate,
RaiseStandardError, Unreachable, LoadErrorValue, LoadGlobal,
NAMESPACE_TYPE, NAMESPACE_MODULE, NAMESPACE_STATIC,
NAMESPACE_TYPE, NAMESPACE_MODULE, NAMESPACE_STATIC, BinaryIntOp
)
from mypyc.ir.rtypes import (
RType, RUnion, RInstance, optional_value_type, int_rprimitive, float_rprimitive,
Expand Down Expand Up @@ -750,6 +750,9 @@ def matching_call_c(self,
return target
return None

def binary_int_op(self, type: RType, lhs: Value, rhs: Value, op: int, line: int) -> Value:
return self.add(BinaryIntOp(type, lhs, rhs, op, line))

# Internal helpers

def decompose_union_helper(self,
Expand Down
8 changes: 4 additions & 4 deletions mypyc/test-data/irbuild-basic.test
Original file line number Diff line number Diff line change
Expand Up @@ -1917,7 +1917,7 @@ L6:
r20 = r0.append(r19) :: list
L7:
r21 = 1
r22 = r9 + r21 :: short_int
r22 = r9 + r21
r9 = r22
goto L1
L8:
Expand Down Expand Up @@ -1982,7 +1982,7 @@ L6:
r21 = r0.__setitem__(r19, r20) :: dict
L7:
r22 = 1
r23 = r9 + r22 :: short_int
r23 = r9 + r22
r9 = r23
goto L1
L8:
Expand Down Expand Up @@ -2032,7 +2032,7 @@ L2:
z = r8
L3:
r9 = 1
r10 = r1 + r9 :: short_int
r10 = r1 + r9
r1 = r10
goto L1
L4:
Expand All @@ -2058,7 +2058,7 @@ L6:
r24 = r11.append(r23) :: list
L7:
r25 = 1
r26 = r13 + r25 :: short_int
r26 = r13 + r25
r13 = r26
goto L5
L8:
Expand Down
2 changes: 1 addition & 1 deletion mypyc/test-data/irbuild-lists.test
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ L2:
r8 = CPyList_SetItem(l, i, r7)
L3:
r9 = 1
r10 = r2 + r9 :: short_int
r10 = r2 + r9
r2 = r10
i = r10
goto L1
Expand Down
23 changes: 12 additions & 11 deletions mypyc/test-data/irbuild-statements.test
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ L2:
x = r5
L3:
r6 = 1
r7 = r3 + r6 :: short_int
r7 = r3 + r6
r3 = r7
i = r7
goto L1
Expand Down Expand Up @@ -58,7 +58,7 @@ L1:
L2:
L3:
r4 = -1
r5 = r2 + r4 :: short_int
r5 = r2 + r4
r2 = r5
i = r5
goto L1
Expand Down Expand Up @@ -113,7 +113,7 @@ L2:
goto L4
L3:
r4 = 1
r5 = r2 + r4 :: short_int
r5 = r2 + r4
r2 = r5
n = r5
goto L1
Expand Down Expand Up @@ -202,7 +202,7 @@ L1:
L2:
L3:
r4 = 1
r5 = r2 + r4 :: short_int
r5 = r2 + r4
r2 = r5
n = r5
goto L1
Expand Down Expand Up @@ -281,7 +281,7 @@ L2:
y = r7
L3:
r8 = 1
r9 = r2 + r8 :: short_int
r9 = r2 + r8
r2 = r9
goto L1
L4:
Expand Down Expand Up @@ -868,11 +868,11 @@ L2:
r8 = CPyTagged_Add(i, x)
L3:
r9 = 1
r10 = r1 + r9 :: short_int
r10 = r1 + r9
r1 = r10
i = r10
r11 = 1
r12 = r3 + r11 :: short_int
r12 = r3 + r11
r3 = r12
goto L1
L4:
Expand Down Expand Up @@ -901,7 +901,7 @@ L2:
n = r4
L3:
r5 = 1
r6 = r1 + r5 :: short_int
r6 = r1 + r5
r1 = r6
i = r6
goto L1
Expand Down Expand Up @@ -961,7 +961,7 @@ L4:
L5:
L6:
r11 = 1
r12 = r1 + r11 :: short_int
r12 = r1 + r11
r1 = r12
goto L1
L7:
Expand Down Expand Up @@ -1012,10 +1012,10 @@ L4:
x = r13
L5:
r14 = 1
r15 = r2 + r14 :: short_int
r15 = r2 + r14
r2 = r15
r16 = 1
r17 = r5 + r16 :: short_int
r17 = r5 + r16
r5 = r17
z = r17
goto L1
Expand All @@ -1024,3 +1024,4 @@ L6:
L7:
r19 = None
return r19

2 changes: 1 addition & 1 deletion mypyc/test-data/irbuild-tuple.test
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ L2:
x = r5
L3:
r6 = 1
r7 = r1 + r6 :: short_int
r7 = r1 + r6
r1 = r7
goto L1
L4:
Expand Down

0 comments on commit b98c47e

Please sign in to comment.