Skip to content

Commit 3185bb5

Browse files
committed
py/obj: Add new type flag to indicate subscr accepts slice-on-stack.
The recently merged 5e9189d now allows temporary slices to be allocated on the C stack, which is much better than allocating them on the GC heap. Unfortunately there are cases where the C-allocated slice can escape and be retained as an object, which leads to crashes (because that object points to the C stack which now has other values on it). The fix here is to add a new `MP_TYPE_FLAG_SUBSCR_ALLOWS_STACK_SLICE`. Native types should set this flag if their subscr method is guaranteed not to hold on to a reference of the slice object. Fixes issue micropython#17733 (see also micropython#17723). Signed-off-by: Damien George <[email protected]>
1 parent 45aa65b commit 3185bb5

File tree

5 files changed

+33
-4
lines changed

5 files changed

+33
-4
lines changed

py/obj.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,8 @@ typedef mp_obj_t (*mp_fun_kw_t)(size_t n, const mp_obj_t *, mp_map_t *);
558558
// If MP_TYPE_FLAG_ITER_IS_STREAM is set then the type implicitly gets a "return self"
559559
// getiter, and mp_stream_unbuffered_iter for iternext.
560560
// If MP_TYPE_FLAG_INSTANCE_TYPE is set then this is an instance type (i.e. defined in Python).
561+
// If MP_TYPE_FLAG_SUBSCR_ALLOWS_STACK_SLICE is set then the "subscr" slot allows a stack
562+
// allocated slice to be passed in (no references to it will be retained after the call).
561563
#define MP_TYPE_FLAG_NONE (0x0000)
562564
#define MP_TYPE_FLAG_IS_SUBCLASSED (0x0001)
563565
#define MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS (0x0002)
@@ -571,6 +573,7 @@ typedef mp_obj_t (*mp_fun_kw_t)(size_t n, const mp_obj_t *, mp_map_t *);
571573
#define MP_TYPE_FLAG_ITER_IS_CUSTOM (0x0100)
572574
#define MP_TYPE_FLAG_ITER_IS_STREAM (MP_TYPE_FLAG_ITER_IS_ITERNEXT | MP_TYPE_FLAG_ITER_IS_CUSTOM)
573575
#define MP_TYPE_FLAG_INSTANCE_TYPE (0x0200)
576+
#define MP_TYPE_FLAG_SUBSCR_ALLOWS_STACK_SLICE (0x0400)
574577

575578
typedef enum {
576579
PRINT_STR = 0,

py/objarray.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -626,7 +626,7 @@ MP_DEFINE_CONST_OBJ_TYPE(
626626
MP_DEFINE_CONST_OBJ_TYPE(
627627
mp_type_bytearray,
628628
MP_QSTR_bytearray,
629-
MP_TYPE_FLAG_EQ_CHECKS_OTHER_TYPE | MP_TYPE_FLAG_ITER_IS_GETITER,
629+
MP_TYPE_FLAG_EQ_CHECKS_OTHER_TYPE | MP_TYPE_FLAG_ITER_IS_GETITER | MP_TYPE_FLAG_SUBSCR_ALLOWS_STACK_SLICE,
630630
make_new, bytearray_make_new,
631631
print, array_print,
632632
iter, array_iterator_new,
@@ -654,7 +654,7 @@ MP_DEFINE_CONST_OBJ_TYPE(
654654
MP_DEFINE_CONST_OBJ_TYPE(
655655
mp_type_memoryview,
656656
MP_QSTR_memoryview,
657-
MP_TYPE_FLAG_EQ_CHECKS_OTHER_TYPE | MP_TYPE_FLAG_ITER_IS_GETITER,
657+
MP_TYPE_FLAG_EQ_CHECKS_OTHER_TYPE | MP_TYPE_FLAG_ITER_IS_GETITER | MP_TYPE_FLAG_SUBSCR_ALLOWS_STACK_SLICE,
658658
make_new, memoryview_make_new,
659659
iter, array_iterator_new,
660660
unary_op, array_unary_op,

py/vm.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -865,9 +865,10 @@ unwind_jump:;
865865
// 3-argument slice includes step
866866
step = POP();
867867
}
868-
if ((*ip == MP_BC_LOAD_SUBSCR || *ip == MP_BC_STORE_SUBSCR) && mp_obj_is_native_type(mp_obj_get_type(sp[-2]))) {
868+
if ((*ip == MP_BC_LOAD_SUBSCR || *ip == MP_BC_STORE_SUBSCR)
869+
&& (mp_obj_get_type(sp[-2])->flags & MP_TYPE_FLAG_SUBSCR_ALLOWS_STACK_SLICE)) {
869870
// Fast path optimisation for when the BUILD_SLICE is immediately followed
870-
// by a LOAD/STORE_SUBSCR for a native type to avoid needing to allocate
871+
// by a LOAD/STORE_SUBSCR for an accepting type, to avoid needing to allocate
871872
// the slice on the heap. In some cases (e.g. a[1:3] = x) this can result
872873
// in no allocations at all. We can't do this for instance types because
873874
// the get/set/delattr implementation may keep a reference to the slice.

tests/basics/slice_optimise.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Test that the slice-on-stack optimisation does not break various uses of slice
2+
# (see MP_TYPE_FLAG_SUBSCR_ALLOWS_STACK_SLICE type option).
3+
#
4+
# Note: this test has a corresponding .py.exp file because hashing slice objects
5+
# was not allowed in CPython until 3.12.
6+
7+
try:
8+
from collections import OrderedDict
9+
except ImportError:
10+
print("SKIP")
11+
raise SystemExit
12+
13+
# Attempt to index with a slice, error should contain the slice (failed key).
14+
try:
15+
dict()[:]
16+
except KeyError as e:
17+
print("KeyError", e.args)
18+
19+
# Put a slice and another object into an OrderedDict, and retrieve them.
20+
x = OrderedDict()
21+
x[:"a"] = 1
22+
x["b"] = 2
23+
print(list(x.keys()), list(x.values()))

tests/basics/slice_optimise.py.exp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
KeyError (slice(None, None, None),)
2+
[slice(None, 'a', None), 'b'] [1, 2]

0 commit comments

Comments
 (0)