Skip to content

Commit 793cf18

Browse files
authored
[mypyc] Support var arg in CallC, replace new_dict_op (#8948)
Related: mypyc/mypyc#709, mypyc/mypyc#734 Summary: * introduce variable arguments in CallC * replace old new_dict_op (which relies on a specialized emit callback) with two CallC ops: dict_new_op and dict_build_op, which handles create an empty dict and a dict from k-v pairs. * fix related IR tests, especially separate the testDel case into three subcases which test list, dict and attributes respectively.
1 parent 3b2e114 commit 793cf18

File tree

11 files changed

+323
-210
lines changed

11 files changed

+323
-210
lines changed

mypyc/ir/ops.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1151,13 +1151,15 @@ def __init__(self,
11511151
ret_type: RType,
11521152
steals: StealsDescription,
11531153
error_kind: int,
1154-
line: int) -> None:
1154+
line: int,
1155+
var_arg_idx: int = -1) -> None:
11551156
self.error_kind = error_kind
11561157
super().__init__(line)
11571158
self.function_name = function_name
11581159
self.args = args
11591160
self.type = ret_type
11601161
self.steals = steals
1162+
self.var_arg_idx = var_arg_idx # the position of the first variable argument in args
11611163

11621164
def to_str(self, env: Environment) -> str:
11631165
args_str = ', '.join(env.format('%r', arg) for arg in self.args)

mypyc/irbuild/classdef.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
dataclass_sleight_of_hand, pytype_from_template_op, py_calc_meta_op, type_object_op,
2222
not_implemented_op, true_op
2323
)
24-
from mypyc.primitives.dict_ops import dict_set_item_op, new_dict_op
24+
from mypyc.primitives.dict_ops import dict_set_item_op, dict_new_op
2525
from mypyc.primitives.tuple_ops import new_tuple_op
2626
from mypyc.common import SELF_NAME
2727
from mypyc.irbuild.util import (
@@ -73,7 +73,7 @@ def transform_class_def(builder: IRBuilder, cdef: ClassDef) -> None:
7373
# We populate __annotations__ for non-extension classes
7474
# because dataclasses uses it to determine which attributes to compute on.
7575
# TODO: Maybe generate more precise types for annotations
76-
non_ext_anns = builder.primitive_op(new_dict_op, [], cdef.line)
76+
non_ext_anns = builder.call_c(dict_new_op, [], cdef.line)
7777
non_ext = NonExtClassInfo(non_ext_dict, non_ext_bases, non_ext_anns, non_ext_metaclass)
7878
dataclass_non_ext = None
7979
type_obj = None
@@ -258,7 +258,7 @@ def setup_non_ext_dict(builder: IRBuilder,
258258
builder.goto(exit_block)
259259

260260
builder.activate_block(false_block)
261-
builder.assign(non_ext_dict, builder.primitive_op(new_dict_op, [], cdef.line), cdef.line)
261+
builder.assign(non_ext_dict, builder.call_c(dict_new_op, [], cdef.line), cdef.line)
262262
builder.goto(exit_block)
263263
builder.activate_block(exit_block)
264264

@@ -518,9 +518,9 @@ def dataclass_non_ext_info(builder: IRBuilder, cdef: ClassDef) -> Optional[NonEx
518518
"""
519519
if is_dataclass(cdef):
520520
return NonExtClassInfo(
521-
builder.primitive_op(new_dict_op, [], cdef.line),
521+
builder.call_c(dict_new_op, [], cdef.line),
522522
builder.add(TupleSet([], cdef.line)),
523-
builder.primitive_op(new_dict_op, [], cdef.line),
523+
builder.call_c(dict_new_op, [], cdef.line),
524524
builder.primitive_op(type_object_op, [], cdef.line),
525525
)
526526
else:

mypyc/irbuild/expression.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from mypyc.primitives.misc_ops import new_slice_op, ellipsis_op, type_op
2626
from mypyc.primitives.list_ops import new_list_op, list_append_op, list_extend_op
2727
from mypyc.primitives.tuple_ops import list_tuple_op
28-
from mypyc.primitives.dict_ops import new_dict_op, dict_set_item_op
28+
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
3030
from mypyc.irbuild.specialize import specializers
3131
from mypyc.irbuild.builder import IRBuilder
@@ -541,7 +541,7 @@ def gen_inner_stmts() -> None:
541541

542542

543543
def transform_dictionary_comprehension(builder: IRBuilder, o: DictionaryComprehension) -> Value:
544-
d = builder.primitive_op(new_dict_op, [], o.line)
544+
d = builder.call_c(dict_new_op, [], o.line)
545545
loop_params = list(zip(o.indices, o.sequences, o.condlists))
546546

547547
def gen_inner_stmts() -> None:

mypyc/irbuild/ll_builder.py

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from mypyc.ir.rtypes import (
2727
RType, RUnion, RInstance, optional_value_type, int_rprimitive, float_rprimitive,
2828
bool_rprimitive, list_rprimitive, str_rprimitive, is_none_rprimitive, object_rprimitive,
29+
c_int_rprimitive
2930
)
3031
from mypyc.ir.func_ir import FuncDecl, FuncSignature
3132
from mypyc.ir.class_ir import ClassIR, all_concrete_classes
@@ -41,7 +42,9 @@
4142
list_extend_op, list_len_op, new_list_op
4243
)
4344
from mypyc.primitives.tuple_ops import list_tuple_op, new_tuple_op
44-
from mypyc.primitives.dict_ops import new_dict_op, dict_update_in_display_op
45+
from mypyc.primitives.dict_ops import (
46+
dict_update_in_display_op, dict_new_op, dict_build_op
47+
)
4548
from mypyc.primitives.generic_ops import (
4649
py_getattr_op, py_call_op, py_call_with_kwargs_op, py_method_call_op
4750
)
@@ -566,12 +569,14 @@ def unary_op(self,
566569

567570
def make_dict(self, key_value_pairs: Sequence[DictEntry], line: int) -> Value:
568571
result = None # type: Union[Value, None]
569-
initial_items = [] # type: List[Value]
572+
keys = [] # type: List[Value]
573+
values = [] # type: List[Value]
570574
for key, value in key_value_pairs:
571575
if key is not None:
572576
# key:value
573577
if result is None:
574-
initial_items.extend((key, value))
578+
keys.append(key)
579+
values.append(value)
575580
continue
576581

577582
self.translate_special_method_call(
@@ -583,7 +588,7 @@ def make_dict(self, key_value_pairs: Sequence[DictEntry], line: int) -> Value:
583588
else:
584589
# **value
585590
if result is None:
586-
result = self.primitive_op(new_dict_op, initial_items, line)
591+
result = self._create_dict(keys, values, line)
587592

588593
self.primitive_op(
589594
dict_update_in_display_op,
@@ -592,7 +597,7 @@ def make_dict(self, key_value_pairs: Sequence[DictEntry], line: int) -> Value:
592597
)
593598

594599
if result is None:
595-
result = self.primitive_op(new_dict_op, initial_items, line)
600+
result = self._create_dict(keys, values, line)
596601

597602
return result
598603

@@ -685,12 +690,22 @@ def call_c(self,
685690
result_type: Optional[RType] = None) -> Value:
686691
# handle void function via singleton RVoid instance
687692
coerced = []
688-
for i, arg in enumerate(args):
693+
# coerce fixed number arguments
694+
for i in range(min(len(args), len(desc.arg_types))):
689695
formal_type = desc.arg_types[i]
696+
arg = args[i]
690697
arg = self.coerce(arg, formal_type, line)
691698
coerced.append(arg)
699+
# coerce any var_arg
700+
var_arg_idx = -1
701+
if desc.var_arg_type is not None:
702+
var_arg_idx = len(desc.arg_types)
703+
for i in range(len(desc.arg_types), len(args)):
704+
arg = args[i]
705+
arg = self.coerce(arg, desc.var_arg_type, line)
706+
coerced.append(arg)
692707
target = self.add(CallC(desc.c_function_name, coerced, desc.return_type, desc.steals,
693-
desc.error_kind, line))
708+
desc.error_kind, line, var_arg_idx))
694709
if result_type and not is_runtime_subtype(target.type, result_type):
695710
if is_none_rprimitive(result_type):
696711
# Special case None return. The actual result may actually be a bool
@@ -859,3 +874,18 @@ def translate_eq_cmp(self,
859874
ltype,
860875
line
861876
)
877+
878+
def _create_dict(self,
879+
keys: List[Value],
880+
values: List[Value],
881+
line: int) -> Value:
882+
"""Create a dictionary(possibly empty) using keys and values"""
883+
# keys and values should have the same number of items
884+
size = len(keys)
885+
if size > 0:
886+
load_size_op = self.add(LoadInt(size, -1, c_int_rprimitive))
887+
# merge keys and values
888+
items = [i for t in list(zip(keys, values)) for i in t]
889+
return self.call_c(dict_build_op, [load_size_op] + items, line)
890+
else:
891+
return self.call_c(dict_new_op, [], line)

mypyc/primitives/dict_ops.py

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55
from mypyc.ir.ops import EmitterInterface, ERR_FALSE, ERR_MAGIC, ERR_NEVER
66
from mypyc.ir.rtypes import (
77
dict_rprimitive, object_rprimitive, bool_rprimitive, int_rprimitive,
8-
list_rprimitive, dict_next_rtuple_single, dict_next_rtuple_pair
8+
list_rprimitive, dict_next_rtuple_single, dict_next_rtuple_pair, c_int_rprimitive
99
)
1010

1111
from mypyc.primitives.registry import (
1212
name_ref_op, method_op, binary_op, func_op, custom_op,
1313
simple_emit, negative_int_emit, call_emit, call_negative_bool_emit,
14-
name_emit,
14+
name_emit, c_custom_op
1515
)
1616

1717

@@ -88,25 +88,22 @@
8888
error_kind=ERR_MAGIC,
8989
emit=simple_emit('{dest} = CPyDict_Get({args[0]}, {args[1]}, Py_None);'))
9090

91-
92-
def emit_new_dict(emitter: EmitterInterface, args: List[str], dest: str) -> None:
93-
if not args:
94-
emitter.emit_line('%s = PyDict_New();' % (dest,))
95-
return
96-
97-
emitter.emit_line('%s = CPyDict_Build(%s, %s);' % (dest, len(args) // 2, ', '.join(args)))
98-
91+
# Construct an empty dictionary.
92+
dict_new_op = c_custom_op(
93+
arg_types=[],
94+
return_type=dict_rprimitive,
95+
c_function_name='PyDict_New',
96+
error_kind=ERR_MAGIC)
9997

10098
# Construct a dictionary from keys and values.
101-
# Arguments are (key1, value1, ..., keyN, valueN).
102-
new_dict_op = custom_op(
103-
name='builtins.dict',
104-
arg_types=[object_rprimitive],
105-
is_var_arg=True,
106-
result_type=dict_rprimitive,
107-
format_str='{dest} = {{{colon_args}}}',
99+
# Positional argument is the number of key-value pairs
100+
# Variable arguments are (key1, value1, ..., keyN, valueN).
101+
dict_build_op = c_custom_op(
102+
arg_types=[c_int_rprimitive],
103+
return_type=dict_rprimitive,
104+
c_function_name='CPyDict_Build',
108105
error_kind=ERR_MAGIC,
109-
emit=emit_new_dict)
106+
var_arg_type=object_rprimitive,)
110107

111108
# Construct a dictionary from another dictionary.
112109
func_op(

mypyc/primitives/registry.py

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
'CFunctionDescription', [('name', str),
4747
('arg_types', List[RType]),
4848
('return_type', RType),
49+
('var_arg_type', Optional[RType]),
4950
('c_function_name', str),
5051
('error_kind', int),
5152
('steals', StealsDescription),
@@ -337,10 +338,25 @@ def c_method_op(name: str,
337338
return_type: RType,
338339
c_function_name: str,
339340
error_kind: int,
341+
var_arg_type: Optional[RType] = None,
340342
steals: StealsDescription = False,
341343
priority: int = 1) -> CFunctionDescription:
344+
"""Define a c function call op that replaces a method call.
345+
346+
This will be automatically generated by matching against the AST.
347+
348+
Args:
349+
name: short name of the method (for example, 'append')
350+
arg_types: argument types; the receiver is always the first argument
351+
return_type: type of the return value. Use void_rtype to represent void.
352+
c_function_name: name of the C function to call
353+
error_kind: how errors are represented in the result (one of ERR_*)
354+
var_arg_type: type of all variable arguments
355+
steals: description of arguments that this steals (ref count wise)
356+
priority: if multiple ops match, the one with the highest priority is picked
357+
"""
342358
ops = c_method_call_ops.setdefault(name, [])
343-
desc = CFunctionDescription(name, arg_types, return_type,
359+
desc = CFunctionDescription(name, arg_types, return_type, var_arg_type,
344360
c_function_name, error_kind, steals, priority)
345361
ops.append(desc)
346362
return desc
@@ -351,10 +367,21 @@ def c_function_op(name: str,
351367
return_type: RType,
352368
c_function_name: str,
353369
error_kind: int,
370+
var_arg_type: Optional[RType] = None,
354371
steals: StealsDescription = False,
355372
priority: int = 1) -> CFunctionDescription:
373+
"""Define a c function call op that replaces a function call.
374+
375+
This will be automatically generated by matching against the AST.
376+
377+
Most arguments are similar to c_method_op().
378+
379+
Args:
380+
name: full name of the function
381+
arg_types: positional argument types for which this applies
382+
"""
356383
ops = c_function_ops.setdefault(name, [])
357-
desc = CFunctionDescription(name, arg_types, return_type,
384+
desc = CFunctionDescription(name, arg_types, return_type, var_arg_type,
358385
c_function_name, error_kind, steals, priority)
359386
ops.append(desc)
360387
return desc
@@ -365,28 +392,58 @@ def c_binary_op(name: str,
365392
return_type: RType,
366393
c_function_name: str,
367394
error_kind: int,
395+
var_arg_type: Optional[RType] = None,
368396
steals: StealsDescription = False,
369397
priority: int = 1) -> CFunctionDescription:
398+
"""Define a c function call op for a binary operation.
399+
400+
This will be automatically generated by matching against the AST.
401+
402+
Most arguments are similar to c_method_op(), but exactly two argument types
403+
are expected.
404+
"""
370405
ops = c_binary_ops.setdefault(name, [])
371-
desc = CFunctionDescription(name, arg_types, return_type,
406+
desc = CFunctionDescription(name, arg_types, return_type, var_arg_type,
372407
c_function_name, error_kind, steals, priority)
373408
ops.append(desc)
374409
return desc
375410

376411

412+
def c_custom_op(arg_types: List[RType],
413+
return_type: RType,
414+
c_function_name: str,
415+
error_kind: int,
416+
var_arg_type: Optional[RType] = None,
417+
steals: StealsDescription = False) -> CFunctionDescription:
418+
"""Create a one-off CallC op that can't be automatically generated from the AST.
419+
420+
Most arguments are similar to c_method_op().
421+
"""
422+
return CFunctionDescription('<custom>', arg_types, return_type, var_arg_type,
423+
c_function_name, error_kind, steals, 0)
424+
425+
377426
def c_unary_op(name: str,
378427
arg_type: RType,
379428
return_type: RType,
380429
c_function_name: str,
381430
error_kind: int,
382431
steals: StealsDescription = False,
383432
priority: int = 1) -> CFunctionDescription:
433+
"""Define a c function call op for an unary operation.
434+
435+
This will be automatically generated by matching against the AST.
436+
437+
Most arguments are similar to c_method_op(), but exactly one argument type
438+
is expected.
439+
"""
384440
ops = c_unary_ops.setdefault(name, [])
385-
desc = CFunctionDescription(name, [arg_type], return_type,
441+
desc = CFunctionDescription(name, [arg_type], return_type, None,
386442
c_function_name, error_kind, steals, priority)
387443
ops.append(desc)
388444
return desc
389445

446+
390447
# Import various modules that set up global state.
391448
import mypyc.primitives.int_ops # noqa
392449
import mypyc.primitives.str_ops # noqa

0 commit comments

Comments
 (0)