Skip to content

Commit 59f4914

Browse files
authored
[mypyc] handle negative_int_emit and Truncate op (#9050)
Related: mypyc/mypyc#734
1 parent 6ee562a commit 59f4914

File tree

8 files changed

+112
-43
lines changed

8 files changed

+112
-43
lines changed

mypyc/analysis.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
Value, ControlOp,
99
BasicBlock, OpVisitor, Assign, LoadInt, LoadErrorValue, RegisterOp, Goto, Branch, Return, Call,
1010
Environment, Box, Unbox, Cast, Op, Unreachable, TupleGet, TupleSet, GetAttr, SetAttr,
11-
LoadStatic, InitStatic, PrimitiveOp, MethodCall, RaiseStandardError, CallC, LoadGlobal
11+
LoadStatic, InitStatic, PrimitiveOp, MethodCall, RaiseStandardError, CallC, LoadGlobal,
12+
Truncate
1213
)
1314

1415

@@ -198,6 +199,9 @@ def visit_raise_standard_error(self, op: RaiseStandardError) -> GenAndKill:
198199
def visit_call_c(self, op: CallC) -> GenAndKill:
199200
return self.visit_register_op(op)
200201

202+
def visit_truncate(self, op: Truncate) -> GenAndKill:
203+
return self.visit_register_op(op)
204+
201205
def visit_load_global(self, op: LoadGlobal) -> GenAndKill:
202206
return self.visit_register_op(op)
203207

mypyc/codegen/emitfunc.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
OpVisitor, Goto, Branch, Return, Assign, LoadInt, LoadErrorValue, GetAttr, SetAttr,
1212
LoadStatic, InitStatic, TupleGet, TupleSet, Call, IncRef, DecRef, Box, Cast, Unbox,
1313
BasicBlock, Value, MethodCall, PrimitiveOp, EmitterInterface, Unreachable, NAMESPACE_STATIC,
14-
NAMESPACE_TYPE, NAMESPACE_MODULE, RaiseStandardError, CallC, LoadGlobal
14+
NAMESPACE_TYPE, NAMESPACE_MODULE, RaiseStandardError, CallC, LoadGlobal, Truncate
1515
)
1616
from mypyc.ir.rtypes import RType, RTuple, is_int32_rprimitive, is_int64_rprimitive
1717
from mypyc.ir.func_ir import FuncIR, FuncDecl, FUNC_STATICMETHOD, FUNC_CLASSMETHOD
@@ -426,6 +426,12 @@ def visit_call_c(self, op: CallC) -> None:
426426
args = ', '.join(self.reg(arg) for arg in op.args)
427427
self.emitter.emit_line("{}{}({});".format(dest, op.function_name, args))
428428

429+
def visit_truncate(self, op: Truncate) -> None:
430+
dest = self.reg(op)
431+
value = self.reg(op.src)
432+
# for C backend the generated code are straight assignments
433+
self.emit_line("{} = {};".format(dest, value))
434+
429435
def visit_load_global(self, op: LoadGlobal) -> None:
430436
dest = self.reg(op)
431437
ann = ''

mypyc/ir/ops.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1183,6 +1183,40 @@ def accept(self, visitor: 'OpVisitor[T]') -> T:
11831183
return visitor.visit_call_c(self)
11841184

11851185

1186+
class Truncate(RegisterOp):
1187+
"""truncate src: src_type to dst_type
1188+
1189+
Truncate a value from type with more bits to type with less bits
1190+
1191+
both src_type and dst_type should be non-reference counted integer types or bool
1192+
especially note that int_rprimitive is reference counted so should never be used here
1193+
"""
1194+
1195+
error_kind = ERR_NEVER
1196+
1197+
def __init__(self,
1198+
src: Value,
1199+
src_type: RType,
1200+
dst_type: RType,
1201+
line: int = -1) -> None:
1202+
super().__init__(line)
1203+
self.src = src
1204+
self.src_type = src_type
1205+
self.type = dst_type
1206+
1207+
def sources(self) -> List[Value]:
1208+
return [self.src]
1209+
1210+
def stolen(self) -> List[Value]:
1211+
return []
1212+
1213+
def to_str(self, env: Environment) -> str:
1214+
return env.format("%r = truncate %r: %r to %r", self, self.src, self.src_type, self.type)
1215+
1216+
def accept(self, visitor: 'OpVisitor[T]') -> T:
1217+
return visitor.visit_truncate(self)
1218+
1219+
11861220
class LoadGlobal(RegisterOp):
11871221
"""Load a global variable/pointer"""
11881222

@@ -1307,6 +1341,10 @@ def visit_raise_standard_error(self, op: RaiseStandardError) -> T:
13071341
def visit_call_c(self, op: CallC) -> T:
13081342
raise NotImplementedError
13091343

1344+
@abstractmethod
1345+
def visit_truncate(self, op: Truncate) -> T:
1346+
raise NotImplementedError
1347+
13101348
@abstractmethod
13111349
def visit_load_global(self, op: LoadGlobal) -> T:
13121350
raise NotImplementedError

mypyc/irbuild/ll_builder.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from mypyc.ir.ops import (
2020
BasicBlock, Environment, Op, LoadInt, Value, Register,
2121
Assign, Branch, Goto, Call, Box, Unbox, Cast, GetAttr,
22-
LoadStatic, MethodCall, PrimitiveOp, OpDescription, RegisterOp, CallC,
22+
LoadStatic, MethodCall, PrimitiveOp, OpDescription, RegisterOp, CallC, Truncate,
2323
RaiseStandardError, Unreachable, LoadErrorValue, LoadGlobal,
2424
NAMESPACE_TYPE, NAMESPACE_MODULE, NAMESPACE_STATIC,
2525
)
@@ -707,14 +707,19 @@ def call_c(self,
707707
coerced.append(arg)
708708
target = self.add(CallC(desc.c_function_name, coerced, desc.return_type, desc.steals,
709709
desc.error_kind, line, var_arg_idx))
710-
if result_type and not is_runtime_subtype(target.type, result_type):
710+
if desc.truncated_type is None:
711+
result = target
712+
else:
713+
truncate = self.add(Truncate(target, desc.return_type, desc.truncated_type))
714+
result = truncate
715+
if result_type and not is_runtime_subtype(result.type, result_type):
711716
if is_none_rprimitive(result_type):
712717
# Special case None return. The actual result may actually be a bool
713718
# and so we can't just coerce it.
714-
target = self.none()
719+
result = self.none()
715720
else:
716-
target = self.coerce(target, result_type, line)
717-
return target
721+
result = self.coerce(target, result_type, line)
722+
return result
718723

719724
def matching_call_c(self,
720725
candidates: List[CFunctionDescription],

mypyc/primitives/misc_ops.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
"""Miscellaneous primitive ops."""
22

3-
from mypyc.ir.ops import ERR_NEVER, ERR_MAGIC, ERR_FALSE
3+
from mypyc.ir.ops import ERR_NEVER, ERR_MAGIC, ERR_FALSE, ERR_NEG_INT
44
from mypyc.ir.rtypes import (
55
RTuple, none_rprimitive, bool_rprimitive, object_rprimitive, str_rprimitive,
6-
int_rprimitive, dict_rprimitive
6+
int_rprimitive, dict_rprimitive, c_int_rprimitive
77
)
88
from mypyc.primitives.registry import (
99
name_ref_op, simple_emit, unary_op, func_op, custom_op, call_emit, name_emit,
@@ -150,11 +150,14 @@
150150
is_borrowed=True)
151151

152152
# isinstance(obj, cls)
153-
func_op('builtins.isinstance',
154-
arg_types=[object_rprimitive, object_rprimitive],
155-
result_type=bool_rprimitive,
156-
error_kind=ERR_MAGIC,
157-
emit=call_negative_magic_emit('PyObject_IsInstance'))
153+
c_function_op(
154+
name='builtins.isinstance',
155+
arg_types=[object_rprimitive, object_rprimitive],
156+
return_type=c_int_rprimitive,
157+
c_function_name='PyObject_IsInstance',
158+
error_kind=ERR_NEG_INT,
159+
truncated_type=bool_rprimitive
160+
)
158161

159162
# Faster isinstance(obj, cls) that only works with native classes and doesn't perform
160163
# type checking of the type argument.

mypyc/primitives/registry.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
('arg_types', List[RType]),
4848
('return_type', RType),
4949
('var_arg_type', Optional[RType]),
50+
('truncated_type', Optional[RType]),
5051
('c_function_name', str),
5152
('error_kind', int),
5253
('steals', StealsDescription),
@@ -350,6 +351,7 @@ def c_method_op(name: str,
350351
c_function_name: str,
351352
error_kind: int,
352353
var_arg_type: Optional[RType] = None,
354+
truncated_type: Optional[RType] = None,
353355
steals: StealsDescription = False,
354356
priority: int = 1) -> CFunctionDescription:
355357
"""Define a c function call op that replaces a method call.
@@ -363,11 +365,14 @@ def c_method_op(name: str,
363365
c_function_name: name of the C function to call
364366
error_kind: how errors are represented in the result (one of ERR_*)
365367
var_arg_type: type of all variable arguments
368+
truncated_type: type to truncated to(See Truncate for info)
369+
if it's defined both return_type and it should be non-referenced
370+
integer types or bool type
366371
steals: description of arguments that this steals (ref count wise)
367372
priority: if multiple ops match, the one with the highest priority is picked
368373
"""
369374
ops = c_method_call_ops.setdefault(name, [])
370-
desc = CFunctionDescription(name, arg_types, return_type, var_arg_type,
375+
desc = CFunctionDescription(name, arg_types, return_type, var_arg_type, truncated_type,
371376
c_function_name, error_kind, steals, priority)
372377
ops.append(desc)
373378
return desc
@@ -379,6 +384,7 @@ def c_function_op(name: str,
379384
c_function_name: str,
380385
error_kind: int,
381386
var_arg_type: Optional[RType] = None,
387+
truncated_type: Optional[RType] = None,
382388
steals: StealsDescription = False,
383389
priority: int = 1) -> CFunctionDescription:
384390
"""Define a c function call op that replaces a function call.
@@ -392,7 +398,7 @@ def c_function_op(name: str,
392398
arg_types: positional argument types for which this applies
393399
"""
394400
ops = c_function_ops.setdefault(name, [])
395-
desc = CFunctionDescription(name, arg_types, return_type, var_arg_type,
401+
desc = CFunctionDescription(name, arg_types, return_type, var_arg_type, truncated_type,
396402
c_function_name, error_kind, steals, priority)
397403
ops.append(desc)
398404
return desc
@@ -404,6 +410,7 @@ def c_binary_op(name: str,
404410
c_function_name: str,
405411
error_kind: int,
406412
var_arg_type: Optional[RType] = None,
413+
truncated_type: Optional[RType] = None,
407414
steals: StealsDescription = False,
408415
priority: int = 1) -> CFunctionDescription:
409416
"""Define a c function call op for a binary operation.
@@ -414,7 +421,7 @@ def c_binary_op(name: str,
414421
are expected.
415422
"""
416423
ops = c_binary_ops.setdefault(name, [])
417-
desc = CFunctionDescription(name, arg_types, return_type, var_arg_type,
424+
desc = CFunctionDescription(name, arg_types, return_type, var_arg_type, truncated_type,
418425
c_function_name, error_kind, steals, priority)
419426
ops.append(desc)
420427
return desc
@@ -425,12 +432,13 @@ def c_custom_op(arg_types: List[RType],
425432
c_function_name: str,
426433
error_kind: int,
427434
var_arg_type: Optional[RType] = None,
435+
truncated_type: Optional[RType] = None,
428436
steals: StealsDescription = False) -> CFunctionDescription:
429437
"""Create a one-off CallC op that can't be automatically generated from the AST.
430438
431439
Most arguments are similar to c_method_op().
432440
"""
433-
return CFunctionDescription('<custom>', arg_types, return_type, var_arg_type,
441+
return CFunctionDescription('<custom>', arg_types, return_type, var_arg_type, truncated_type,
434442
c_function_name, error_kind, steals, 0)
435443

436444

@@ -439,6 +447,7 @@ def c_unary_op(name: str,
439447
return_type: RType,
440448
c_function_name: str,
441449
error_kind: int,
450+
truncated_type: Optional[RType] = None,
442451
steals: StealsDescription = False,
443452
priority: int = 1) -> CFunctionDescription:
444453
"""Define a c function call op for an unary operation.
@@ -449,7 +458,7 @@ def c_unary_op(name: str,
449458
is expected.
450459
"""
451460
ops = c_unary_ops.setdefault(name, [])
452-
desc = CFunctionDescription(name, [arg_type], return_type, None,
461+
desc = CFunctionDescription(name, [arg_type], return_type, None, truncated_type,
453462
c_function_name, error_kind, steals, priority)
454463
ops.append(desc)
455464
return desc

mypyc/test-data/irbuild-basic.test

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1502,19 +1502,21 @@ def main() -> None:
15021502
def foo(x):
15031503
x :: union[int, str]
15041504
r0 :: object
1505-
r1 :: bool
1506-
r2 :: __main__.B
1507-
r3 :: __main__.A
1505+
r1 :: int32
1506+
r2 :: bool
1507+
r3 :: __main__.B
1508+
r4 :: __main__.A
15081509
L0:
15091510
r0 = int
1510-
r1 = isinstance x, r0
1511-
if r1 goto L1 else goto L2 :: bool
1511+
r1 = PyObject_IsInstance(x, r0)
1512+
r2 = truncate r1: int32 to builtins.bool
1513+
if r2 goto L1 else goto L2 :: bool
15121514
L1:
1513-
r2 = B()
1514-
return r2
1515-
L2:
1516-
r3 = A()
1515+
r3 = B()
15171516
return r3
1517+
L2:
1518+
r4 = A()
1519+
return r4
15181520
def main():
15191521
r0 :: short_int
15201522
r1 :: object

mypyc/test-data/irbuild-optional.test

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -299,25 +299,27 @@ def f(x: Union[int, A]) -> int:
299299
def f(x):
300300
x :: union[int, __main__.A]
301301
r0 :: object
302-
r1 :: bool
303-
r2 :: int
304-
r3 :: short_int
305-
r4 :: int
306-
r5 :: __main__.A
307-
r6 :: int
302+
r1 :: int32
303+
r2 :: bool
304+
r3 :: int
305+
r4 :: short_int
306+
r5 :: int
307+
r6 :: __main__.A
308+
r7 :: int
308309
L0:
309310
r0 = int
310-
r1 = isinstance x, r0
311-
if r1 goto L1 else goto L2 :: bool
311+
r1 = PyObject_IsInstance(x, r0)
312+
r2 = truncate r1: int32 to builtins.bool
313+
if r2 goto L1 else goto L2 :: bool
312314
L1:
313-
r2 = unbox(int, x)
314-
r3 = 1
315-
r4 = CPyTagged_Add(r2, r3)
316-
return r4
315+
r3 = unbox(int, x)
316+
r4 = 1
317+
r5 = CPyTagged_Add(r3, r4)
318+
return r5
317319
L2:
318-
r5 = cast(__main__.A, x)
319-
r6 = r5.a
320-
return r6
320+
r6 = cast(__main__.A, x)
321+
r7 = r6.a
322+
return r7
321323
L3:
322324
unreachable
323325

0 commit comments

Comments
 (0)