Skip to content

Commit b594e6b

Browse files
[mypyc] Faster creation of a tuple from list/tuple (#10217)
Resolves mypyc/mypyc#769.
1 parent 641919f commit b594e6b

File tree

9 files changed

+394
-11
lines changed

9 files changed

+394
-11
lines changed

mypyc/irbuild/for_helpers.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,50 @@ def for_loop_helper(builder: IRBuilder, index: Lvalue, expr: Expression,
8686
builder.activate_block(exit_block)
8787

8888

89+
def for_loop_helper_with_index(builder: IRBuilder, index: Lvalue, expr: Expression,
90+
body_insts: Callable[[Value], None], line: int) -> None:
91+
"""Generate IR for a sequence iteration.
92+
93+
This function only works for sequence type. Compared to for_loop_helper,
94+
it would feed iteration index to body_insts.
95+
96+
Args:
97+
index: the loop index Lvalue
98+
expr: the expression to iterate over
99+
body_insts: a function that generates the body of the loop.
100+
It needs a index as parameter.
101+
"""
102+
expr_reg = builder.accept(expr)
103+
assert is_sequence_rprimitive(expr_reg.type)
104+
target_type = builder.get_sequence_type(expr)
105+
106+
body_block = BasicBlock()
107+
step_block = BasicBlock()
108+
exit_block = BasicBlock()
109+
condition_block = BasicBlock()
110+
111+
for_gen = ForSequence(builder, index, body_block, exit_block, line, False)
112+
for_gen.init(expr_reg, target_type, reverse=False)
113+
114+
builder.push_loop_stack(step_block, exit_block)
115+
116+
builder.goto_and_activate(condition_block)
117+
for_gen.gen_condition()
118+
119+
builder.activate_block(body_block)
120+
for_gen.begin_body()
121+
body_insts(builder.read(for_gen.index_target))
122+
123+
builder.goto_and_activate(step_block)
124+
for_gen.gen_step()
125+
builder.goto(condition_block)
126+
127+
for_gen.add_cleanup(exit_block)
128+
builder.pop_loop_stack()
129+
130+
builder.activate_block(exit_block)
131+
132+
89133
def translate_list_comprehension(builder: IRBuilder, gen: GeneratorExpr) -> Value:
90134
list_ops = builder.new_list_op([], gen.line)
91135
loop_params = list(zip(gen.indices, gen.sequences, gen.condlists))

mypyc/irbuild/ll_builder.py

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,16 @@
4747
from mypyc.primitives.list_ops import (
4848
list_extend_op, new_list_op
4949
)
50-
from mypyc.primitives.tuple_ops import list_tuple_op, new_tuple_op
50+
from mypyc.primitives.tuple_ops import (
51+
list_tuple_op, new_tuple_op, new_tuple_with_length_op
52+
)
5153
from mypyc.primitives.dict_ops import (
5254
dict_update_in_display_op, dict_new_op, dict_build_op, dict_size_op
5355
)
5456
from mypyc.primitives.generic_ops import (
55-
py_getattr_op, py_call_op, py_call_with_kwargs_op, py_method_call_op, generic_len_op,
56-
py_vectorcall_op, py_vectorcall_method_op
57+
py_getattr_op, py_call_op, py_call_with_kwargs_op, py_method_call_op,
58+
py_vectorcall_op, py_vectorcall_method_op,
59+
generic_len_op, generic_ssize_t_len_op
5760
)
5861
from mypyc.primitives.misc_ops import (
5962
none_object_op, fast_isinstance_op, bool_op
@@ -1079,35 +1082,56 @@ def int_op(self, type: RType, lhs: Value, rhs: Value, op: int, line: int) -> Val
10791082
def comparison_op(self, lhs: Value, rhs: Value, op: int, line: int) -> Value:
10801083
return self.add(ComparisonOp(lhs, rhs, op, line))
10811084

1082-
def builtin_len(self, val: Value, line: int) -> Value:
1085+
def builtin_len(self, val: Value, line: int,
1086+
use_pyssize_t: bool = False) -> Value:
1087+
"""Return short_int_rprimitive by default."""
10831088
typ = val.type
10841089
if is_list_rprimitive(typ) or is_tuple_rprimitive(typ):
10851090
elem_address = self.add(GetElementPtr(val, PyVarObject, 'ob_size'))
10861091
size_value = self.add(LoadMem(c_pyssize_t_rprimitive, elem_address))
10871092
self.add(KeepAlive([val]))
1093+
if use_pyssize_t:
1094+
return size_value
10881095
offset = Integer(1, c_pyssize_t_rprimitive, line)
10891096
return self.int_op(short_int_rprimitive, size_value, offset,
10901097
IntOp.LEFT_SHIFT, line)
10911098
elif is_dict_rprimitive(typ):
10921099
size_value = self.call_c(dict_size_op, [val], line)
1100+
if use_pyssize_t:
1101+
return size_value
10931102
offset = Integer(1, c_pyssize_t_rprimitive, line)
10941103
return self.int_op(short_int_rprimitive, size_value, offset,
10951104
IntOp.LEFT_SHIFT, line)
10961105
elif is_set_rprimitive(typ):
10971106
elem_address = self.add(GetElementPtr(val, PySetObject, 'used'))
10981107
size_value = self.add(LoadMem(c_pyssize_t_rprimitive, elem_address))
10991108
self.add(KeepAlive([val]))
1109+
if use_pyssize_t:
1110+
return size_value
11001111
offset = Integer(1, c_pyssize_t_rprimitive, line)
11011112
return self.int_op(short_int_rprimitive, size_value, offset,
11021113
IntOp.LEFT_SHIFT, line)
11031114
# generic case
11041115
else:
1105-
return self.call_c(generic_len_op, [val], line)
1116+
if use_pyssize_t:
1117+
return self.call_c(generic_ssize_t_len_op, [val], line)
1118+
else:
1119+
return self.call_c(generic_len_op, [val], line)
11061120

11071121
def new_tuple(self, items: List[Value], line: int) -> Value:
11081122
size = Integer(len(items), c_pyssize_t_rprimitive) # type: Value
11091123
return self.call_c(new_tuple_op, [size] + items, line)
11101124

1125+
def new_tuple_with_length(self, val: Value, line: int) -> Value:
1126+
"""Generate a new empty tuple with length from a list or tuple
1127+
1128+
Args:
1129+
val: a list or tuple
1130+
line: line number
1131+
"""
1132+
length = self.builtin_len(val, line, use_pyssize_t=True)
1133+
return self.call_c(new_tuple_with_length_op, [length], line)
1134+
11111135
# Internal helpers
11121136

11131137
def decompose_union_helper(self,

mypyc/irbuild/specialize.py

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,15 @@
2222
)
2323
from mypyc.ir.rtypes import (
2424
RType, RTuple, str_rprimitive, list_rprimitive, dict_rprimitive, set_rprimitive,
25-
bool_rprimitive, is_dict_rprimitive
25+
bool_rprimitive, is_dict_rprimitive, is_list_rprimitive, is_tuple_rprimitive
2626
)
2727
from mypyc.primitives.dict_ops import dict_keys_op, dict_values_op, dict_items_op
28+
from mypyc.primitives.tuple_ops import new_tuple_set_item_op
2829
from mypyc.irbuild.builder import IRBuilder
29-
from mypyc.irbuild.for_helpers import translate_list_comprehension, comprehension_helper
30+
from mypyc.irbuild.for_helpers import (
31+
translate_list_comprehension, comprehension_helper,
32+
for_loop_helper_with_index
33+
)
3034

3135

3236
# Specializers are attempted before compiling the arguments to the
@@ -135,13 +139,39 @@ def translate_safe_generator_call(
135139
+ [builder.accept(arg) for arg in expr.args[1:]]),
136140
builder.node_type(expr), expr.line, expr.arg_kinds, expr.arg_names)
137141
else:
142+
if callee.fullname == "builtins.tuple":
143+
val = tuple_from_generator_helper(builder, expr.args[0])
144+
if val is not None:
145+
return val
138146
return builder.call_refexpr_with_args(
139147
expr, callee,
140148
([translate_list_comprehension(builder, expr.args[0])]
141149
+ [builder.accept(arg) for arg in expr.args[1:]]))
142150
return None
143151

144152

153+
def tuple_from_generator_helper(builder: IRBuilder,
154+
gen: GeneratorExpr) -> Optional[Value]:
155+
156+
if len(gen.sequences) == 1 and len(gen.condlists[0]) == 0:
157+
# Currently we only optimize for simplest generator expression
158+
rtype = builder.node_type(gen.sequences[0])
159+
if is_list_rprimitive(rtype) or is_tuple_rprimitive(rtype):
160+
tuple_ops = builder.builder.new_tuple_with_length(builder.accept(gen.sequences[0]),
161+
gen.line)
162+
item, expr = gen.indices[0], gen.sequences[0]
163+
164+
def set_tuple_item(item_index: Value) -> None:
165+
e = builder.accept(gen.left_expr)
166+
builder.call_c(new_tuple_set_item_op, [tuple_ops, item_index, e], gen.line)
167+
168+
for_loop_helper_with_index(builder, item, expr,
169+
set_tuple_item, gen.line)
170+
171+
return tuple_ops
172+
return None
173+
174+
145175
@specialize_function('builtins.any')
146176
def translate_any_call(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Optional[Value]:
147177
if (len(expr.args) == 1

mypyc/lib-rt/CPy.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,7 @@ bool CPySet_Remove(PyObject *set, PyObject *key);
398398

399399
PyObject *CPySequenceTuple_GetItem(PyObject *tuple, CPyTagged index);
400400
PyObject *CPySequenceTuple_GetSlice(PyObject *obj, CPyTagged start, CPyTagged end);
401+
bool CPySequenceTuple_SetItemUnsafe(PyObject *tuple, CPyTagged index, PyObject *value);
401402

402403

403404
// Exception operations

mypyc/lib-rt/tuple_ops.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,17 @@ PyObject *CPySequenceTuple_GetSlice(PyObject *obj, CPyTagged start, CPyTagged en
4545
}
4646
return CPyObject_GetSlice(obj, start, end);
4747
}
48+
49+
// PyTuple_SET_ITEM does no error checking,
50+
// and should only be used to fill in brand new tuples.
51+
bool CPySequenceTuple_SetItemUnsafe(PyObject *tuple, CPyTagged index, PyObject *value)
52+
{
53+
if (CPyTagged_CheckShort(index)) {
54+
Py_ssize_t n = CPyTagged_ShortAsSsize_t(index);
55+
PyTuple_SET_ITEM(tuple, n, value);
56+
return true;
57+
} else {
58+
PyErr_SetString(PyExc_OverflowError, "Python int too large to convert to C ssize_t");
59+
return false;
60+
}
61+
}

mypyc/primitives/generic_ops.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from mypyc.ir.ops import ERR_NEVER, ERR_MAGIC
1313
from mypyc.ir.rtypes import (
1414
object_rprimitive, int_rprimitive, bool_rprimitive, c_int_rprimitive, pointer_rprimitive,
15-
object_pointer_rprimitive, c_size_t_rprimitive
15+
object_pointer_rprimitive, c_size_t_rprimitive, c_pyssize_t_rprimitive
1616
)
1717
from mypyc.primitives.registry import (
1818
binary_op, c_unary_op, method_op, function_op, custom_op, ERR_NEG_INT
@@ -239,7 +239,15 @@
239239
arg_types=[object_rprimitive],
240240
return_type=int_rprimitive,
241241
c_function_name='CPyObject_Size',
242-
error_kind=ERR_NEVER)
242+
error_kind=ERR_MAGIC)
243+
244+
# len(obj)
245+
# same as generic_len_op, however return py_ssize_t
246+
generic_ssize_t_len_op = custom_op(
247+
arg_types=[object_rprimitive],
248+
return_type=c_pyssize_t_rprimitive,
249+
c_function_name='PyObject_Size',
250+
error_kind=ERR_NEG_INT)
243251

244252
# iter(obj)
245253
iter_op = function_op(name='builtins.iter',

mypyc/primitives/tuple_ops.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
objects, i.e. tuple_rprimitive (RPrimitive), not RTuple.
55
"""
66

7-
from mypyc.ir.ops import ERR_MAGIC
7+
from mypyc.ir.ops import ERR_MAGIC, ERR_FALSE
88
from mypyc.ir.rtypes import (
9-
tuple_rprimitive, int_rprimitive, list_rprimitive, object_rprimitive, c_pyssize_t_rprimitive
9+
tuple_rprimitive, int_rprimitive, list_rprimitive, object_rprimitive,
10+
c_pyssize_t_rprimitive, bit_rprimitive
1011
)
1112
from mypyc.primitives.registry import method_op, function_op, custom_op
1213

@@ -27,6 +28,21 @@
2728
error_kind=ERR_MAGIC,
2829
var_arg_type=object_rprimitive)
2930

31+
new_tuple_with_length_op = custom_op(
32+
arg_types=[c_pyssize_t_rprimitive],
33+
return_type=tuple_rprimitive,
34+
c_function_name='PyTuple_New',
35+
error_kind=ERR_MAGIC)
36+
37+
# PyTuple_SET_ITEM does no error checking,
38+
# and should only be used to fill in brand new tuples.
39+
new_tuple_set_item_op = custom_op(
40+
arg_types=[tuple_rprimitive, int_rprimitive, object_rprimitive],
41+
return_type=bit_rprimitive,
42+
c_function_name='CPySequenceTuple_SetItemUnsafe',
43+
error_kind=ERR_FALSE,
44+
steals=[False, False, True])
45+
3046
# Construct tuple from a list.
3147
list_tuple_op = function_op(
3248
name='builtins.tuple',

0 commit comments

Comments
 (0)