Skip to content

Commit a6ab096

Browse files
authored
[mypyc] Split BinaryIntOp and introduce ComparisonOp, implements is/is not op (python#9313)
BinaryIntOp used to represent arithmetic, bitwise and comparison operations on integer operations. However, this design prevents us to compare pointer types and all comparison operations should be of boolean return type while manually specifying this in BinaryIntOp is both verbose and error-prone. This PR splits BinaryIntOp and moves the comparison functionalities to ComparsionOp. Based on the new op, this PR also implements is and is not op.
1 parent df6894b commit a6ab096

18 files changed

+209
-121
lines changed

mypyc/analysis/dataflow.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
BasicBlock, OpVisitor, Assign, LoadInt, LoadErrorValue, RegisterOp, Goto, Branch, Return, Call,
1010
Environment, Box, Unbox, Cast, Op, Unreachable, TupleGet, TupleSet, GetAttr, SetAttr,
1111
LoadStatic, InitStatic, PrimitiveOp, MethodCall, RaiseStandardError, CallC, LoadGlobal,
12-
Truncate, BinaryIntOp, LoadMem, GetElementPtr, LoadAddress
12+
Truncate, BinaryIntOp, LoadMem, GetElementPtr, LoadAddress, ComparisonOp
1313
)
1414

1515

@@ -208,6 +208,9 @@ def visit_load_global(self, op: LoadGlobal) -> GenAndKill:
208208
def visit_binary_int_op(self, op: BinaryIntOp) -> GenAndKill:
209209
return self.visit_register_op(op)
210210

211+
def visit_comparison_op(self, op: ComparisonOp) -> GenAndKill:
212+
return self.visit_register_op(op)
213+
211214
def visit_load_mem(self, op: LoadMem) -> GenAndKill:
212215
return self.visit_register_op(op)
213216

mypyc/codegen/emitfunc.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
LoadStatic, InitStatic, TupleGet, TupleSet, Call, IncRef, DecRef, Box, Cast, Unbox,
1313
BasicBlock, Value, MethodCall, PrimitiveOp, EmitterInterface, Unreachable, NAMESPACE_STATIC,
1414
NAMESPACE_TYPE, NAMESPACE_MODULE, RaiseStandardError, CallC, LoadGlobal, Truncate,
15-
BinaryIntOp, LoadMem, GetElementPtr, LoadAddress
15+
BinaryIntOp, LoadMem, GetElementPtr, LoadAddress, ComparisonOp
1616
)
1717
from mypyc.ir.rtypes import (
1818
RType, RTuple, is_tagged, is_int32_rprimitive, is_int64_rprimitive, RStruct
@@ -448,13 +448,19 @@ def visit_load_global(self, op: LoadGlobal) -> None:
448448
self.emit_line('%s = %s;%s' % (dest, op.identifier, ann))
449449

450450
def visit_binary_int_op(self, op: BinaryIntOp) -> None:
451+
dest = self.reg(op)
452+
lhs = self.reg(op.lhs)
453+
rhs = self.reg(op.rhs)
454+
self.emit_line('%s = %s %s %s;' % (dest, lhs, op.op_str[op.op], rhs))
455+
456+
def visit_comparison_op(self, op: ComparisonOp) -> None:
451457
dest = self.reg(op)
452458
lhs = self.reg(op.lhs)
453459
rhs = self.reg(op.rhs)
454460
lhs_cast = ""
455461
rhs_cast = ""
456-
signed_op = {BinaryIntOp.SLT, BinaryIntOp.SGT, BinaryIntOp.SLE, BinaryIntOp.SGE}
457-
unsigned_op = {BinaryIntOp.ULT, BinaryIntOp.UGT, BinaryIntOp.ULE, BinaryIntOp.UGE}
462+
signed_op = {ComparisonOp.SLT, ComparisonOp.SGT, ComparisonOp.SLE, ComparisonOp.SGE}
463+
unsigned_op = {ComparisonOp.ULT, ComparisonOp.UGT, ComparisonOp.ULE, ComparisonOp.UGE}
458464
if op.op in signed_op:
459465
lhs_cast = self.emit_signed_int_cast(op.lhs.type)
460466
rhs_cast = self.emit_signed_int_cast(op.rhs.type)

mypyc/ir/ops.py

Lines changed: 56 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1263,7 +1263,7 @@ def accept(self, visitor: 'OpVisitor[T]') -> T:
12631263

12641264

12651265
class BinaryIntOp(RegisterOp):
1266-
"""Binary operations on integer types
1266+
"""Binary arithmetic and bitwise operations on integer types
12671267
12681268
These ops are low-level and will be eventually generated to simple x op y form.
12691269
The left and right values should be of low-level integer types that support those ops
@@ -1276,18 +1276,7 @@ class BinaryIntOp(RegisterOp):
12761276
MUL = 2 # type: Final
12771277
DIV = 3 # type: Final
12781278
MOD = 4 # type: Final
1279-
# logical
1280-
# S for signed and U for unsigned
1281-
EQ = 100 # type: Final
1282-
NEQ = 101 # type: Final
1283-
SLT = 102 # type: Final
1284-
SGT = 103 # type: Final
1285-
SLE = 104 # type: Final
1286-
SGE = 105 # type: Final
1287-
ULT = 106 # type: Final
1288-
UGT = 107 # type: Final
1289-
ULE = 108 # type: Final
1290-
UGE = 109 # type: Final
1279+
12911280
# bitwise
12921281
AND = 200 # type: Final
12931282
OR = 201 # type: Final
@@ -1301,6 +1290,53 @@ class BinaryIntOp(RegisterOp):
13011290
MUL: '*',
13021291
DIV: '/',
13031292
MOD: '%',
1293+
AND: '&',
1294+
OR: '|',
1295+
XOR: '^',
1296+
LEFT_SHIFT: '<<',
1297+
RIGHT_SHIFT: '>>',
1298+
} # type: Final
1299+
1300+
def __init__(self, type: RType, lhs: Value, rhs: Value, op: int, line: int = -1) -> None:
1301+
super().__init__(line)
1302+
self.type = type
1303+
self.lhs = lhs
1304+
self.rhs = rhs
1305+
self.op = op
1306+
1307+
def sources(self) -> List[Value]:
1308+
return [self.lhs, self.rhs]
1309+
1310+
def to_str(self, env: Environment) -> str:
1311+
return env.format('%r = %r %s %r', self, self.lhs,
1312+
self.op_str[self.op], self.rhs)
1313+
1314+
def accept(self, visitor: 'OpVisitor[T]') -> T:
1315+
return visitor.visit_binary_int_op(self)
1316+
1317+
1318+
class ComparisonOp(RegisterOp):
1319+
"""Comparison ops
1320+
1321+
The result type will always be boolean.
1322+
1323+
Support comparison between integer types and pointer types
1324+
"""
1325+
error_kind = ERR_NEVER
1326+
1327+
# S for signed and U for unsigned
1328+
EQ = 100 # type: Final
1329+
NEQ = 101 # type: Final
1330+
SLT = 102 # type: Final
1331+
SGT = 103 # type: Final
1332+
SLE = 104 # type: Final
1333+
SGE = 105 # type: Final
1334+
ULT = 106 # type: Final
1335+
UGT = 107 # type: Final
1336+
ULE = 108 # type: Final
1337+
UGE = 109 # type: Final
1338+
1339+
op_str = {
13041340
EQ: '==',
13051341
NEQ: '!=',
13061342
SLT: '<',
@@ -1311,16 +1347,11 @@ class BinaryIntOp(RegisterOp):
13111347
UGT: '>',
13121348
ULE: '<=',
13131349
UGE: '>=',
1314-
AND: '&',
1315-
OR: '|',
1316-
XOR: '^',
1317-
LEFT_SHIFT: '<<',
1318-
RIGHT_SHIFT: '>>',
13191350
} # type: Final
13201351

1321-
def __init__(self, type: RType, lhs: Value, rhs: Value, op: int, line: int = -1) -> None:
1352+
def __init__(self, lhs: Value, rhs: Value, op: int, line: int = -1) -> None:
13221353
super().__init__(line)
1323-
self.type = type
1354+
self.type = bool_rprimitive
13241355
self.lhs = lhs
13251356
self.rhs = rhs
13261357
self.op = op
@@ -1339,7 +1370,7 @@ def to_str(self, env: Environment) -> str:
13391370
self.op_str[self.op], self.rhs, sign_format)
13401371

13411372
def accept(self, visitor: 'OpVisitor[T]') -> T:
1342-
return visitor.visit_binary_int_op(self)
1373+
return visitor.visit_comparison_op(self)
13431374

13441375

13451376
class LoadMem(RegisterOp):
@@ -1531,6 +1562,10 @@ def visit_load_global(self, op: LoadGlobal) -> T:
15311562
def visit_binary_int_op(self, op: BinaryIntOp) -> T:
15321563
raise NotImplementedError
15331564

1565+
@abstractmethod
1566+
def visit_comparison_op(self, op: ComparisonOp) -> T:
1567+
raise NotImplementedError
1568+
15341569
@abstractmethod
15351570
def visit_load_mem(self, op: LoadMem) -> T:
15361571
raise NotImplementedError

mypyc/irbuild/builder.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,13 @@ def true(self) -> Value:
209209
def false(self) -> Value:
210210
return self.builder.false()
211211

212+
def translate_is_op(self,
213+
lreg: Value,
214+
rreg: Value,
215+
expr_op: str,
216+
line: int) -> Value:
217+
return self.builder.translate_is_op(lreg, rreg, expr_op, line)
218+
212219
def py_call(self,
213220
function: Value,
214221
arg_values: List[Value],
@@ -270,7 +277,7 @@ def gen_import(self, id: str, line: int) -> None:
270277

271278
needs_import, out = BasicBlock(), BasicBlock()
272279
first_load = self.load_module(id)
273-
comparison = self.binary_op(first_load, self.none_object(), 'is not', line)
280+
comparison = self.translate_is_op(first_load, self.none_object(), 'is not', line)
274281
self.add_bool_branch(comparison, out, needs_import)
275282

276283
self.activate_block(needs_import)

mypyc/irbuild/callable_class.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ def add_get_to_callable_class(builder: IRBuilder, fn_info: FuncInfo) -> None:
119119
# object. If accessed through an object, create a new bound
120120
# instance method object.
121121
instance_block, class_block = BasicBlock(), BasicBlock()
122-
comparison = builder.binary_op(
122+
comparison = builder.translate_is_op(
123123
builder.read(instance), builder.none_object(), 'is', line
124124
)
125125
builder.add_bool_branch(comparison, class_block, instance_block)

mypyc/irbuild/classdef.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,7 @@ def gen_glue_ne_method(builder: IRBuilder, cls: ClassIR, line: int) -> FuncIR:
396396
not_implemented = builder.add(LoadAddress(not_implemented_op.type,
397397
not_implemented_op.src, line))
398398
builder.add(Branch(
399-
builder.binary_op(eqval, not_implemented, 'is', line),
399+
builder.translate_is_op(eqval, not_implemented, 'is', line),
400400
not_implemented_block,
401401
regular_block,
402402
Branch.BOOL_EXPR))

mypyc/irbuild/expression.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
from mypyc.primitives.tuple_ops import list_tuple_op
2828
from mypyc.primitives.dict_ops import dict_new_op, dict_set_item_op
2929
from mypyc.primitives.set_ops import new_set_op, set_add_op, set_update_op
30-
from mypyc.primitives.int_ops import int_logical_op_mapping
30+
from mypyc.primitives.int_ops import int_comparison_op_mapping
3131
from mypyc.irbuild.specialize import specializers
3232
from mypyc.irbuild.builder import IRBuilder
3333
from mypyc.irbuild.for_helpers import translate_list_comprehension, comprehension_helper
@@ -394,7 +394,7 @@ def transform_basic_comparison(builder: IRBuilder,
394394
right: Value,
395395
line: int) -> Value:
396396
if (is_int_rprimitive(left.type) and is_int_rprimitive(right.type)
397-
and op in int_logical_op_mapping.keys()):
397+
and op in int_comparison_op_mapping.keys()):
398398
return builder.compare_tagged(left, right, op, line)
399399
negate = False
400400
if op == 'is not':

mypyc/irbuild/generator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ def add_raise_exception_blocks_to_generator_class(builder: IRBuilder, line: int)
110110
# Check to see if an exception was raised.
111111
error_block = BasicBlock()
112112
ok_block = BasicBlock()
113-
comparison = builder.binary_op(exc_type, builder.none_object(), 'is not', line)
113+
comparison = builder.translate_is_op(exc_type, builder.none_object(), 'is not', line)
114114
builder.add_bool_branch(comparison, error_block, ok_block)
115115

116116
builder.activate_block(error_block)

mypyc/irbuild/ll_builder.py

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
LoadStatic, MethodCall, PrimitiveOp, OpDescription, RegisterOp, CallC, Truncate,
2323
RaiseStandardError, Unreachable, LoadErrorValue, LoadGlobal,
2424
NAMESPACE_TYPE, NAMESPACE_MODULE, NAMESPACE_STATIC, BinaryIntOp, GetElementPtr,
25-
LoadMem, LoadAddress
25+
LoadMem, ComparisonOp, LoadAddress
2626
)
2727
from mypyc.ir.rtypes import (
2828
RType, RUnion, RInstance, optional_value_type, int_rprimitive, float_rprimitive,
@@ -55,7 +55,7 @@
5555
from mypyc.primitives.misc_ops import (
5656
none_object_op, fast_isinstance_op, bool_op, type_is_op
5757
)
58-
from mypyc.primitives.int_ops import int_logical_op_mapping
58+
from mypyc.primitives.int_ops import int_comparison_op_mapping
5959
from mypyc.rt_subtype import is_runtime_subtype
6060
from mypyc.subtype import is_subtype
6161
from mypyc.sametype import is_same_type
@@ -559,7 +559,11 @@ def binary_op(self,
559559
if value is not None:
560560
return value
561561

562-
if is_tagged(lreg.type) and is_tagged(rreg.type) and expr_op in int_logical_op_mapping:
562+
# Special case 'is' and 'is not'
563+
if expr_op in ('is', 'is not'):
564+
return self.translate_is_op(lreg, rreg, expr_op, line)
565+
566+
if is_tagged(lreg.type) and is_tagged(rreg.type) and expr_op in int_comparison_op_mapping:
563567
return self.compare_tagged(lreg, rreg, expr_op, line)
564568

565569
call_c_ops_candidates = c_binary_ops.get(expr_op, [])
@@ -577,16 +581,15 @@ def check_tagged_short_int(self, val: Value, line: int) -> Value:
577581
bitwise_and = self.binary_int_op(c_pyssize_t_rprimitive, val,
578582
int_tag, BinaryIntOp.AND, line)
579583
zero = self.add(LoadInt(0, line, rtype=c_pyssize_t_rprimitive))
580-
check = self.binary_int_op(bool_rprimitive, bitwise_and, zero, BinaryIntOp.EQ, line)
584+
check = self.comparison_op(bitwise_and, zero, ComparisonOp.EQ, line)
581585
return check
582586

583587
def compare_tagged(self, lhs: Value, rhs: Value, op: str, line: int) -> Value:
584588
"""Compare two tagged integers using given op"""
585589
# generate fast binary logic ops on short ints
586590
if is_short_int_rprimitive(lhs.type) and is_short_int_rprimitive(rhs.type):
587-
return self.binary_int_op(bool_rprimitive, lhs, rhs,
588-
int_logical_op_mapping[op][0], line)
589-
op_type, c_func_desc, negate_result, swap_op = int_logical_op_mapping[op]
591+
return self.comparison_op(lhs, rhs, int_comparison_op_mapping[op][0], line)
592+
op_type, c_func_desc, negate_result, swap_op = int_comparison_op_mapping[op]
590593
result = self.alloc_temp(bool_rprimitive)
591594
short_int_block, int_block, out = BasicBlock(), BasicBlock(), BasicBlock()
592595
check_lhs = self.check_tagged_short_int(lhs, line)
@@ -601,7 +604,7 @@ def compare_tagged(self, lhs: Value, rhs: Value, op: str, line: int) -> Value:
601604
branch.negated = False
602605
self.add(branch)
603606
self.activate_block(short_int_block)
604-
eq = self.binary_int_op(bool_rprimitive, lhs, rhs, op_type, line)
607+
eq = self.comparison_op(lhs, rhs, op_type, line)
605608
self.add(Assign(result, eq, line))
606609
self.goto(out)
607610
self.activate_block(int_block)
@@ -725,7 +728,7 @@ def add_bool_branch(self, value: Value, true: BasicBlock, false: BasicBlock) ->
725728
else:
726729
value_type = optional_value_type(value.type)
727730
if value_type is not None:
728-
is_none = self.binary_op(value, self.none_object(), 'is not', value.line)
731+
is_none = self.translate_is_op(value, self.none_object(), 'is not', value.line)
729732
branch = Branch(is_none, true, false, Branch.BOOL_EXPR)
730733
self.add(branch)
731734
always_truthy = False
@@ -822,6 +825,9 @@ def matching_call_c(self,
822825
def binary_int_op(self, type: RType, lhs: Value, rhs: Value, op: int, line: int) -> Value:
823826
return self.add(BinaryIntOp(type, lhs, rhs, op, line))
824827

828+
def comparison_op(self, lhs: Value, rhs: Value, op: int, line: int) -> Value:
829+
return self.add(ComparisonOp(lhs, rhs, op, line))
830+
825831
def builtin_len(self, val: Value, line: int) -> Value:
826832
typ = val.type
827833
if is_list_rprimitive(typ) or is_tuple_rprimitive(typ):
@@ -974,7 +980,7 @@ def translate_eq_cmp(self,
974980
if not class_ir.has_method('__eq__'):
975981
# There's no __eq__ defined, so just use object identity.
976982
identity_ref_op = 'is' if expr_op == '==' else 'is not'
977-
return self.binary_op(lreg, rreg, identity_ref_op, line)
983+
return self.translate_is_op(lreg, rreg, identity_ref_op, line)
978984

979985
return self.gen_method_call(
980986
lreg,
@@ -984,6 +990,21 @@ def translate_eq_cmp(self,
984990
line
985991
)
986992

993+
def translate_is_op(self,
994+
lreg: Value,
995+
rreg: Value,
996+
expr_op: str,
997+
line: int) -> Value:
998+
"""Create equality comparison operation between object identities
999+
1000+
Args:
1001+
expr_op: either 'is' or 'is not'
1002+
"""
1003+
op = ComparisonOp.EQ if expr_op == 'is' else ComparisonOp.NEQ
1004+
lhs = self.coerce(lreg, object_rprimitive, line)
1005+
rhs = self.coerce(rreg, object_rprimitive, line)
1006+
return self.add(ComparisonOp(lhs, rhs, op, line))
1007+
9871008
def _create_dict(self,
9881009
keys: List[Value],
9891010
values: List[Value],

mypyc/primitives/generic_ops.py

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -88,20 +88,6 @@
8888
ordering=[1, 0],
8989
priority=0)
9090

91-
binary_op('is',
92-
arg_types=[object_rprimitive, object_rprimitive],
93-
result_type=bool_rprimitive,
94-
error_kind=ERR_NEVER,
95-
emit=simple_emit('{dest} = {args[0]} == {args[1]};'),
96-
priority=0)
97-
98-
binary_op('is not',
99-
arg_types=[object_rprimitive, object_rprimitive],
100-
result_type=bool_rprimitive,
101-
error_kind=ERR_NEVER,
102-
emit=simple_emit('{dest} = {args[0]} != {args[1]};'),
103-
priority=0)
104-
10591

10692
# Unary operations
10793

0 commit comments

Comments
 (0)