Skip to content

Commit 765acca

Browse files
authored
[mypyc] Add primitives for list, str and tuple slicing (#9283)
This speeds up some microbenchmarks from 40% (list) to 100% (str) when I run them on Ubuntu 20.04. Non-default strides aren't optimized, since they are fairly rare. `a[::-1]` for lists might be worth special casing in the future. Also, once we have primitives for `bytes`, it should also be special cased. Fixes mypyc/mypyc#725.
1 parent fa538f8 commit 765acca

22 files changed

+286
-16
lines changed

mypyc/common.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import sys
22
from typing import Dict, Any
3+
import sys
34

45
from typing_extensions import Final
56

@@ -22,7 +23,11 @@
2223

2324
# Max short int we accept as a literal is based on 32-bit platforms,
2425
# so that we can just always emit the same code.
25-
MAX_LITERAL_SHORT_INT = (1 << 30) - 1 # type: Final
26+
27+
# Maximum value for a short tagged integer.
28+
#
29+
# Note: Assume that the compiled code uses the same bit width as mypyc.
30+
MAX_LITERAL_SHORT_INT = sys.maxsize >> 1 # type: Final
2631

2732
TOP_LEVEL_NAME = '__top_level__' # type: Final # Special function representing module top level
2833

mypyc/doc/list_operations.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ Get item by integer index:
2929

3030
* ``lst[n]``
3131

32+
Slicing:
33+
34+
* ``lst[n:m]``, ``lst[n:]``, ``lst[:m]``, ``lst[:]``
35+
3236
Repeat list ``n`` times:
3337

3438
* ``lst * n``, ``n * lst``

mypyc/doc/str_operations.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ Indexing:
2424

2525
* ``s[n]`` (integer index)
2626

27+
Slicing:
28+
29+
* ``s[n:m]``, ``s[n:]``, ``s[:m]``
30+
2731
Comparisons:
2832

2933
* ``s1 == s2``, ``s1 != s2``

mypyc/doc/tuple_operations.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Operators
2020
---------
2121

2222
* ``tup[n]`` (integer index)
23+
* ``tup[n:m]``, ``tup[n:]``, ``tup[:m]`` (slicing)
2324

2425
Statements
2526
----------

mypyc/irbuild/builder.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,9 @@ def py_get_attr(self, obj: Value, attr: str, line: int) -> Value:
185185
def load_static_unicode(self, value: str) -> Value:
186186
return self.builder.load_static_unicode(value)
187187

188+
def load_static_int(self, value: int) -> Value:
189+
return self.builder.load_static_int(value)
190+
188191
def primitive_op(self, desc: OpDescription, args: List[Value], line: int) -> Value:
189192
return self.builder.primitive_op(desc, args, line)
190193

mypyc/irbuild/expression.py

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,22 @@
1515
)
1616
from mypy.types import TupleType, get_proper_type
1717

18+
from mypyc.common import MAX_LITERAL_SHORT_INT
1819
from mypyc.ir.ops import (
1920
Value, TupleGet, TupleSet, BasicBlock, Assign, LoadAddress
2021
)
21-
from mypyc.ir.rtypes import RTuple, object_rprimitive, is_none_rprimitive, is_int_rprimitive
22+
from mypyc.ir.rtypes import (
23+
RTuple, object_rprimitive, is_none_rprimitive, int_rprimitive, is_int_rprimitive
24+
)
2225
from mypyc.ir.func_ir import FUNC_CLASSMETHOD, FUNC_STATICMETHOD
2326
from mypyc.primitives.registry import CFunctionDescription, builtin_names
2427
from mypyc.primitives.generic_ops import iter_op
2528
from mypyc.primitives.misc_ops import new_slice_op, ellipsis_op, type_op
26-
from mypyc.primitives.list_ops import list_append_op, list_extend_op
27-
from mypyc.primitives.tuple_ops import list_tuple_op
29+
from mypyc.primitives.list_ops import list_append_op, list_extend_op, list_slice_op
30+
from mypyc.primitives.tuple_ops import list_tuple_op, tuple_slice_op
2831
from mypyc.primitives.dict_ops import dict_new_op, dict_set_item_op
2932
from mypyc.primitives.set_ops import new_set_op, set_add_op, set_update_op
33+
from mypyc.primitives.str_ops import str_slice_op
3034
from mypyc.primitives.int_ops import int_comparison_op_mapping
3135
from mypyc.irbuild.specialize import specializers
3236
from mypyc.irbuild.builder import IRBuilder
@@ -323,15 +327,59 @@ def transform_op_expr(builder: IRBuilder, expr: OpExpr) -> Value:
323327

324328
def transform_index_expr(builder: IRBuilder, expr: IndexExpr) -> Value:
325329
base = builder.accept(expr.base)
330+
index = expr.index
331+
332+
if isinstance(base.type, RTuple) and isinstance(index, IntExpr):
333+
return builder.add(TupleGet(base, index.value, expr.line))
326334

327-
if isinstance(base.type, RTuple) and isinstance(expr.index, IntExpr):
328-
return builder.add(TupleGet(base, expr.index.value, expr.line))
335+
if isinstance(index, SliceExpr):
336+
value = try_gen_slice_op(builder, base, index)
337+
if value:
338+
return value
329339

330340
index_reg = builder.accept(expr.index)
331341
return builder.gen_method_call(
332342
base, '__getitem__', [index_reg], builder.node_type(expr), expr.line)
333343

334344

345+
def try_gen_slice_op(builder: IRBuilder, base: Value, index: SliceExpr) -> Optional[Value]:
346+
"""Generate specialized slice op for some index expressions.
347+
348+
Return None if a specialized op isn't available.
349+
350+
This supports obj[x:y], obj[:x], and obj[x:] for a few types.
351+
"""
352+
if index.stride:
353+
# We can only handle the default stride of 1.
354+
return None
355+
356+
if index.begin_index:
357+
begin_type = builder.node_type(index.begin_index)
358+
else:
359+
begin_type = int_rprimitive
360+
if index.end_index:
361+
end_type = builder.node_type(index.end_index)
362+
else:
363+
end_type = int_rprimitive
364+
365+
# Both begin and end index must be int (or missing).
366+
if is_int_rprimitive(begin_type) and is_int_rprimitive(end_type):
367+
if index.begin_index:
368+
begin = builder.accept(index.begin_index)
369+
else:
370+
begin = builder.load_static_int(0)
371+
if index.end_index:
372+
end = builder.accept(index.end_index)
373+
else:
374+
# Replace missing end index with the largest short integer
375+
# (a sequence can't be longer).
376+
end = builder.load_static_int(MAX_LITERAL_SHORT_INT)
377+
candidates = [list_slice_op, tuple_slice_op, str_slice_op]
378+
return builder.builder.matching_call_c(candidates, [base, begin, end], index.line)
379+
380+
return None
381+
382+
335383
def transform_conditional_expr(builder: IRBuilder, expr: ConditionalExpr) -> Value:
336384
if_body, else_body, next = BasicBlock(), BasicBlock(), BasicBlock()
337385

mypyc/lib-rt/CPy.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,7 @@ CPyTagged CPyObject_Hash(PyObject *o);
303303
PyObject *CPyObject_GetAttr3(PyObject *v, PyObject *name, PyObject *defl);
304304
PyObject *CPyIter_Next(PyObject *iter);
305305
PyObject *CPyNumber_Power(PyObject *base, PyObject *index);
306+
PyObject *CPyObject_GetSlice(PyObject *obj, CPyTagged start, CPyTagged end);
306307

307308

308309
// List operations
@@ -318,6 +319,7 @@ CPyTagged CPyList_Count(PyObject *obj, PyObject *value);
318319
PyObject *CPyList_Extend(PyObject *o1, PyObject *o2);
319320
PyObject *CPySequence_Multiply(PyObject *seq, CPyTagged t_size);
320321
PyObject *CPySequence_RMultiply(CPyTagged t_size, PyObject *seq);
322+
PyObject *CPyList_GetSlice(PyObject *obj, CPyTagged start, CPyTagged end);
321323

322324

323325
// Dict operations
@@ -367,6 +369,7 @@ static inline char CPyDict_CheckSize(PyObject *dict, CPyTagged size) {
367369
PyObject *CPyStr_GetItem(PyObject *str, CPyTagged index);
368370
PyObject *CPyStr_Split(PyObject *str, PyObject *sep, CPyTagged max_split);
369371
PyObject *CPyStr_Append(PyObject *o1, PyObject *o2);
372+
PyObject *CPyStr_GetSlice(PyObject *obj, CPyTagged start, CPyTagged end);
370373

371374

372375
// Set operations
@@ -379,6 +382,7 @@ bool CPySet_Remove(PyObject *set, PyObject *key);
379382

380383

381384
PyObject *CPySequenceTuple_GetItem(PyObject *tuple, CPyTagged index);
385+
PyObject *CPySequenceTuple_GetSlice(PyObject *obj, CPyTagged start, CPyTagged end);
382386

383387

384388
// Exception operations

mypyc/lib-rt/generic_ops.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,20 @@ PyObject *CPyNumber_Power(PyObject *base, PyObject *index)
4040
{
4141
return PyNumber_Power(base, index, Py_None);
4242
}
43+
44+
PyObject *CPyObject_GetSlice(PyObject *obj, CPyTagged start, CPyTagged end) {
45+
PyObject *start_obj = CPyTagged_AsObject(start);
46+
PyObject *end_obj = CPyTagged_AsObject(end);
47+
if (unlikely(start_obj == NULL || end_obj == NULL)) {
48+
return NULL;
49+
}
50+
PyObject *slice = PySlice_New(start_obj, end_obj, NULL);
51+
Py_DECREF(start_obj);
52+
Py_DECREF(end_obj);
53+
if (unlikely(slice == NULL)) {
54+
return NULL;
55+
}
56+
PyObject *result = PyObject_GetItem(obj, slice);
57+
Py_DECREF(slice);
58+
return result;
59+
}

mypyc/lib-rt/list_ops.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,3 +123,19 @@ PyObject *CPySequence_Multiply(PyObject *seq, CPyTagged t_size) {
123123
PyObject *CPySequence_RMultiply(CPyTagged t_size, PyObject *seq) {
124124
return CPySequence_Multiply(seq, t_size);
125125
}
126+
127+
PyObject *CPyList_GetSlice(PyObject *obj, CPyTagged start, CPyTagged end) {
128+
if (likely(PyList_CheckExact(obj)
129+
&& CPyTagged_CheckShort(start) && CPyTagged_CheckShort(end))) {
130+
Py_ssize_t startn = CPyTagged_ShortAsSsize_t(start);
131+
Py_ssize_t endn = CPyTagged_ShortAsSsize_t(end);
132+
if (startn < 0) {
133+
startn += PyList_GET_SIZE(obj);
134+
}
135+
if (endn < 0) {
136+
endn += PyList_GET_SIZE(obj);
137+
}
138+
return PyList_GetSlice(obj, startn, endn);
139+
}
140+
return CPyObject_GetSlice(obj, start, end);
141+
}

mypyc/lib-rt/setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
version='0.1',
1818
ext_modules=[Extension(
1919
'test_capi',
20-
['test_capi.cc', 'init.c', 'int_ops.c', 'list_ops.c', 'exc_ops.c'],
20+
['test_capi.cc', 'init.c', 'int_ops.c', 'list_ops.c', 'exc_ops.c', 'generic_ops.c'],
2121
depends=['CPy.h', 'mypyc_util.h', 'pythonsupport.h'],
2222
extra_compile_args=['-Wno-unused-function', '-Wno-sign-compare'] + compile_args,
2323
library_dirs=['../external/googletest/make'],

0 commit comments

Comments
 (0)