summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--py/vm.c32
-rw-r--r--tests/basics/builtin_slice.py37
-rw-r--r--tests/micropython/heapalloc_slice.py18
-rwxr-xr-xtests/run-tests.py3
4 files changed, 85 insertions, 5 deletions
diff --git a/py/vm.c b/py/vm.c
index f87e52c92..6f1179721 100644
--- a/py/vm.c
+++ b/py/vm.c
@@ -195,6 +195,22 @@
#define TRACE_TICK(current_ip, current_sp, is_exception)
#endif // MICROPY_PY_SYS_SETTRACE
+#if MICROPY_PY_BUILTINS_SLICE
+// This function is marked "no inline" so it doesn't increase the C stack usage of the main VM function.
+MP_NOINLINE static mp_obj_t *build_slice_stack_allocated(byte op, mp_obj_t *sp, mp_obj_t step) {
+ mp_obj_t stop = sp[2];
+ mp_obj_t start = sp[1];
+ mp_obj_slice_t slice = { .base = { .type = &mp_type_slice }, .start = start, .stop = stop, .step = step };
+ if (op == MP_BC_LOAD_SUBSCR) {
+ SET_TOP(mp_obj_subscr(TOP(), MP_OBJ_FROM_PTR(&slice), MP_OBJ_SENTINEL));
+ } else { // MP_BC_STORE_SUBSCR
+ mp_obj_subscr(TOP(), MP_OBJ_FROM_PTR(&slice), sp[-1]);
+ sp -= 2;
+ }
+ return sp;
+}
+#endif
+
// fastn has items in reverse order (fastn[0] is local[0], fastn[-1] is local[1], etc)
// sp points to bottom of stack which grows up
// returns:
@@ -849,9 +865,19 @@ unwind_jump:;
// 3-argument slice includes step
step = POP();
}
- mp_obj_t stop = POP();
- mp_obj_t start = TOP();
- SET_TOP(mp_obj_new_slice(start, stop, step));
+ if ((*ip == MP_BC_LOAD_SUBSCR || *ip == MP_BC_STORE_SUBSCR) && mp_obj_is_native_type(mp_obj_get_type(sp[-2]))) {
+ // Fast path optimisation for when the BUILD_SLICE is immediately followed
+ // by a LOAD/STORE_SUBSCR for a native type to avoid needing to allocate
+ // the slice on the heap. In some cases (e.g. a[1:3] = x) this can result
+ // in no allocations at all. We can't do this for instance types because
+ // the get/set/delattr implementation may keep a reference to the slice.
+ byte op = *ip++;
+ sp = build_slice_stack_allocated(op, sp - 2, step);
+ } else {
+ mp_obj_t stop = POP();
+ mp_obj_t start = TOP();
+ SET_TOP(mp_obj_new_slice(start, stop, step));
+ }
DISPATCH();
}
#endif
diff --git a/tests/basics/builtin_slice.py b/tests/basics/builtin_slice.py
index df84d5c57..5197a7cad 100644
--- a/tests/basics/builtin_slice.py
+++ b/tests/basics/builtin_slice.py
@@ -1,11 +1,44 @@
# test builtin slice
+# ensures that slices passed to user types are heap-allocated and can be
+# safely stored as well as not overriden by subsequent slices.
+
# print slice
class A:
def __getitem__(self, idx):
- print(idx)
+ print("get", idx)
+ print("abc"[1:])
+ print("get", idx)
+ return idx
+
+ def __setitem__(self, idx, value):
+ print("set", idx)
+ print("abc"[1:])
+ print("set", idx)
+ self.saved_idx = idx
+ return idx
+
+ def __delitem__(self, idx):
+ print("del", idx)
+ print("abc"[1:])
+ print("del", idx)
return idx
-s = A()[1:2:3]
+
+
+a = A()
+s = a[1:2:3]
+a[4:5:6] = s
+del a[7:8:9]
+
+print(a.saved_idx)
+
+# nested slicing
+print(A()[1 : A()[A()[2:3:4] : 5]])
+
+# tuple slicing
+a[1:2, 4:5, 7:8]
+a[1, 4:5, 7:8, 2]
+a[1:2, a[3:4], 5:6]
# check type
print(type(s) is slice)
diff --git a/tests/micropython/heapalloc_slice.py b/tests/micropython/heapalloc_slice.py
new file mode 100644
index 000000000..62d96595c
--- /dev/null
+++ b/tests/micropython/heapalloc_slice.py
@@ -0,0 +1,18 @@
+# slice operations that don't require allocation
+try:
+ from micropython import heap_lock, heap_unlock
+except (ImportError, AttributeError):
+ heap_lock = heap_unlock = lambda: 0
+
+b = bytearray(range(10))
+
+m = memoryview(b)
+
+heap_lock()
+
+b[3:5] = b"aa"
+m[5:7] = b"bb"
+
+heap_unlock()
+
+print(b)
diff --git a/tests/run-tests.py b/tests/run-tests.py
index 0eaee5278..fe338d7ff 100755
--- a/tests/run-tests.py
+++ b/tests/run-tests.py
@@ -855,6 +855,9 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1):
"micropython/emg_exc.py"
) # because native doesn't have proper traceback info
skip_tests.add(
+ "micropython/heapalloc_slice.py"
+ ) # because native doesn't do the stack-allocated slice optimisation
+ skip_tests.add(
"micropython/heapalloc_traceback.py"
) # because native doesn't have proper traceback info
skip_tests.add(