From 2039757b854b656a41c9e041776ccd5dad8ec5fe Mon Sep 17 00:00:00 2001
From: Paul Sokolovsky <pfalcon@users.sourceforge.net>
Date: Sat, 28 Mar 2015 01:14:44 +0200
Subject: [PATCH] vm: Initial support for calling bytecode functions w/o C
 stack ("stackless").

---
 py/bc.c       |  3 +++
 py/bc.h       |  4 ++++
 py/mpconfig.h |  5 +++++
 py/objfun.c   | 32 ++++++++++++++++++++++++++++++++
 py/vm.c       | 43 +++++++++++++++++++++++++++++++++++++++++--
 5 files changed, 85 insertions(+), 2 deletions(-)

diff --git a/py/bc.c b/py/bc.c
index 3a8dc5e04..af855e947 100644
--- a/py/bc.c
+++ b/py/bc.c
@@ -86,6 +86,9 @@ void mp_setup_code_state(mp_code_state *code_state, mp_obj_t self_in, mp_uint_t
     mp_obj_fun_bc_t *self = self_in;
     mp_uint_t n_state = code_state->n_state;
 
+    #if MICROPY_STACKLESS
+    code_state->prev = NULL;
+    #endif
     code_state->code_info = self->bytecode;
     code_state->sp = &code_state->state[0] - 1;
     code_state->exc_sp = (mp_exc_stack_t*)(code_state->state + n_state) - 1;
diff --git a/py/bc.h b/py/bc.h
index f3885587a..b4b4d8c55 100644
--- a/py/bc.h
+++ b/py/bc.h
@@ -46,6 +46,9 @@ typedef struct _mp_code_state {
     // bit 0 is saved currently_in_except_block value
     mp_exc_stack_t *exc_sp;
     mp_obj_dict_t *old_globals;
+    #if MICROPY_STACKLESS
+    struct _mp_code_state *prev;
+    #endif
     mp_uint_t n_state;
     // Variable-length
     mp_obj_t state[0];
@@ -56,6 +59,7 @@ typedef struct _mp_code_state {
 mp_uint_t mp_decode_uint(const byte **ptr);
 
 mp_vm_return_kind_t mp_execute_bytecode(mp_code_state *code_state, volatile mp_obj_t inject_exc);
+mp_code_state *mp_obj_fun_bc_prepare_codestate(mp_obj_t func, mp_uint_t n_args, mp_uint_t n_kw, const mp_obj_t *args);
 void mp_setup_code_state(mp_code_state *code_state, mp_obj_t self_in, mp_uint_t n_args, mp_uint_t n_kw, const mp_obj_t *args);
 void mp_bytecode_print(const void *descr, mp_uint_t n_total_args, const byte *code, mp_uint_t len);
 void mp_bytecode_print2(const byte *code, mp_uint_t len);
diff --git a/py/mpconfig.h b/py/mpconfig.h
index ae5e77625..94e2737de 100644
--- a/py/mpconfig.h
+++ b/py/mpconfig.h
@@ -125,6 +125,11 @@
 #define MICROPY_QSTR_BYTES_IN_LEN (1)
 #endif
 
+// Avoid using C stack when making Python function calls.
+#ifndef MICROPY_STACKLESS
+#define MICROPY_STACKLESS (0)
+#endif
+
 /*****************************************************************************/
 /* Micro Python emitters                                                     */
 
diff --git a/py/objfun.c b/py/objfun.c
index d8ea22f39..187bb5a59 100644
--- a/py/objfun.c
+++ b/py/objfun.c
@@ -142,6 +142,38 @@ STATIC void dump_args(const mp_obj_t *a, mp_uint_t sz) {
 // Set this to enable a simple stack overflow check.
 #define VM_DETECT_STACK_OVERFLOW (0)
 
+mp_code_state *mp_obj_fun_bc_prepare_codestate(mp_obj_t self_in, mp_uint_t n_args, mp_uint_t n_kw, const mp_obj_t *args) {
+    MP_STACK_CHECK();
+    mp_obj_fun_bc_t *self = self_in;
+
+    // skip code-info block
+    const byte *code_info = self->bytecode;
+    mp_uint_t code_info_size = mp_decode_uint(&code_info);
+    const byte *ip = self->bytecode + code_info_size;
+
+    // bytecode prelude: skip arg names
+    ip += (self->n_pos_args + self->n_kwonly_args) * sizeof(mp_obj_t);
+
+    // bytecode prelude: state size and exception stack size
+    mp_uint_t n_state = mp_decode_uint(&ip);
+    mp_uint_t n_exc_stack = mp_decode_uint(&ip);
+
+    // allocate state for locals and stack
+    mp_uint_t state_size = n_state * sizeof(mp_obj_t) + n_exc_stack * sizeof(mp_exc_stack_t);
+    mp_code_state *code_state;
+    code_state = m_new_obj_var(mp_code_state, byte, state_size);
+
+    code_state->n_state = n_state;
+    code_state->ip = ip;
+    mp_setup_code_state(code_state, self_in, n_args, n_kw, args);
+
+    // execute the byte code with the correct globals context
+    code_state->old_globals = mp_globals_get();
+    mp_globals_set(self->globals);
+
+    return code_state;
+}
+
 STATIC mp_obj_t fun_bc_call(mp_obj_t self_in, mp_uint_t n_args, mp_uint_t n_kw, const mp_obj_t *args) {
     MP_STACK_CHECK();
 
diff --git a/py/vm.c b/py/vm.c
index 29a9e4f6d..3a7a81697 100644
--- a/py/vm.c
+++ b/py/vm.c
@@ -129,9 +129,12 @@ mp_vm_return_kind_t mp_execute_bytecode(mp_code_state *code_state, volatile mp_o
     // loop and the exception handler, leading to very obscure bugs.
     #define RAISE(o) do { nlr_pop(); nlr.ret_val = o; goto exception_handler; } while(0)
 
+#if MICROPY_STACKLESS
+run_code_state: ;
+#endif
     // Pointers which are constant for particular invocation of mp_execute_bytecode()
-    mp_obj_t *const fastn = &code_state->state[code_state->n_state - 1];
-    mp_exc_stack_t *const exc_stack = (mp_exc_stack_t*)(code_state->state + code_state->n_state);
+    mp_obj_t */*const*/ fastn = &code_state->state[code_state->n_state - 1];
+    mp_exc_stack_t */*const*/ exc_stack = (mp_exc_stack_t*)(code_state->state + code_state->n_state);
 
     // variables that are visible to the exception handler (declared volatile)
     volatile bool currently_in_except_block = MP_TAGPTR_TAG0(code_state->exc_sp); // 0 or 1, to detect nested exceptions
@@ -865,6 +868,18 @@ unwind_jump:;
                     // unum & 0xff == n_positional
                     // (unum >> 8) & 0xff == n_keyword
                     sp -= (unum & 0xff) + ((unum >> 7) & 0x1fe);
+                    #if MICROPY_STACKLESS
+                    if (mp_obj_get_type(*sp) == &mp_type_fun_bc) {
+                        code_state->ip = ip;
+                        code_state->sp = sp;
+                        code_state->exc_sp = MP_TAGPTR_MAKE(exc_sp, currently_in_except_block);
+                        mp_code_state *new_state = mp_obj_fun_bc_prepare_codestate(*sp, unum & 0xff, (unum >> 8) & 0xff, sp + 1);
+                        new_state->prev = code_state;
+                        code_state = new_state;
+                        nlr_pop();
+                        goto run_code_state;
+                    }
+                    #endif
                     SET_TOP(mp_call_function_n_kw(*sp, unum & 0xff, (unum >> 8) & 0xff, sp + 1));
                     DISPATCH();
                 }
@@ -925,6 +940,15 @@ unwind_return:
                     nlr_pop();
                     code_state->sp = sp;
                     assert(exc_sp == exc_stack - 1);
+                    #if MICROPY_STACKLESS
+                    if (code_state->prev != NULL) {
+                        mp_obj_t res = *sp;
+                        mp_globals_set(code_state->old_globals);
+                        code_state = code_state->prev;
+                        *code_state->sp = res;
+                        goto run_code_state;
+                    }
+                    #endif
                     return MP_VM_RETURN_NORMAL;
 
                 ENTRY(MP_BC_RAISE_VARARGS): {
@@ -1122,6 +1146,9 @@ exception_handler:
                 goto outer_dispatch_loop; // continue with dispatch loop
             }
 
+#if MICROPY_STACKLESS
+unwind_loop:
+#endif
             // set file and line number that the exception occurred at
             // TODO: don't set traceback for exceptions re-raised by END_FINALLY.
             // But consider how to handle nested exceptions.
@@ -1185,6 +1212,18 @@ exception_handler:
                 PUSH(mp_obj_get_type(nlr.ret_val));
                 code_state->sp = sp;
 
+            #if MICROPY_STACKLESS
+            } else if (code_state->prev != NULL) {
+                mp_globals_set(code_state->old_globals);
+                code_state = code_state->prev;
+                fastn = &code_state->state[code_state->n_state - 1];
+                exc_stack = (mp_exc_stack_t*)(code_state->state + code_state->n_state);
+                // variables that are visible to the exception handler (declared volatile)
+                currently_in_except_block = MP_TAGPTR_TAG0(code_state->exc_sp); // 0 or 1, to detect nested exceptions
+                exc_sp = MP_TAGPTR_PTR(code_state->exc_sp); // stack grows up, exc_sp points to top of stack
+                goto unwind_loop;
+
+            #endif
             } else {
                 // propagate exception to higher level
                 // TODO what to do about ip and sp? they don't really make sense at this point
-- 
GitLab