diff --git a/py/emitbc.c b/py/emitbc.c
index 98e1d1bde75b0abe0fc4912e206ce681449a8341..6a46cfb5937ded9ce86a906d36a31b49c73d5131 100644
--- a/py/emitbc.c
+++ b/py/emitbc.c
@@ -331,6 +331,10 @@ void mp_emit_bc_start_pass(emit_t *emit, pass_kind_t pass, scope_t *scope) {
             // the highest slot in the state (fastn[0], see vm.c).
             n_state = 1;
         }
+        #if MICROPY_DEBUG_VM_STACK_OVERFLOW
+        // An extra slot in the stack is needed to detect VM stack overflow
+        n_state += 1;
+        #endif
         emit_write_code_info_uint(emit, n_state);
         emit_write_code_info_uint(emit, scope->exc_stack_size);
     }
diff --git a/py/mpconfig.h b/py/mpconfig.h
index 7b0d78914741a23910c53a21f425abbe1d1645ef..6f1619127cc3f7d9b0384a9c4c856f7f2e0aa346 100644
--- a/py/mpconfig.h
+++ b/py/mpconfig.h
@@ -409,6 +409,11 @@
 #define MICROPY_DEBUG_VERBOSE (0)
 #endif
 
+// Whether to enable a simple VM stack overflow check
+#ifndef MICROPY_DEBUG_VM_STACK_OVERFLOW
+#define MICROPY_DEBUG_VM_STACK_OVERFLOW (0)
+#endif
+
 /*****************************************************************************/
 /* Optimisations                                                             */
 
diff --git a/py/objfun.c b/py/objfun.c
index 112eadb4187e6f7446bed1f701e7b02298644ce9..6f6c4dc22fafc6d346467f80185fca3529dc0643 100644
--- a/py/objfun.c
+++ b/py/objfun.c
@@ -195,17 +195,12 @@ STATIC void dump_args(const mp_obj_t *a, size_t sz) {
 // than this will try to use the heap, with fallback to stack allocation.
 #define VM_MAX_STATE_ON_STACK (11 * sizeof(mp_uint_t))
 
-// Set this to 1 to enable a simple stack overflow check.
-#define VM_DETECT_STACK_OVERFLOW (0)
-
 #define DECODE_CODESTATE_SIZE(bytecode, n_state_out_var, state_size_out_var) \
     { \
         /* bytecode prelude: state size and exception stack size */               \
         n_state_out_var = mp_decode_uint_value(bytecode);                         \
         size_t n_exc_stack = mp_decode_uint_value(mp_decode_uint_skip(bytecode)); \
                                                                                   \
-        n_state_out_var += VM_DETECT_STACK_OVERFLOW;                              \
-                                                                                  \
         /* state size in bytes */                                                 \
         state_size_out_var = n_state_out_var * sizeof(mp_obj_t)                   \
                            + n_exc_stack * sizeof(mp_exc_stack_t);                \
@@ -270,9 +265,17 @@ STATIC mp_obj_t fun_bc_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const
     #else
     if (state_size > VM_MAX_STATE_ON_STACK) {
         code_state = m_new_obj_var_maybe(mp_code_state_t, byte, state_size);
+        #if MICROPY_DEBUG_VM_STACK_OVERFLOW
+        if (code_state != NULL) {
+            memset(code_state->state, 0, state_size);
+        }
+        #endif
     }
     if (code_state == NULL) {
         code_state = alloca(sizeof(mp_code_state_t) + state_size);
+        #if MICROPY_DEBUG_VM_STACK_OVERFLOW
+        memset(code_state->state, 0, state_size);
+        #endif
         state_size = 0; // indicate that we allocated using alloca
     }
     #endif
@@ -284,31 +287,34 @@ STATIC mp_obj_t fun_bc_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const
     mp_vm_return_kind_t vm_return_kind = mp_execute_bytecode(code_state, MP_OBJ_NULL);
     mp_globals_set(code_state->old_globals);
 
-#if VM_DETECT_STACK_OVERFLOW
+    #if MICROPY_DEBUG_VM_STACK_OVERFLOW
     if (vm_return_kind == MP_VM_RETURN_NORMAL) {
         if (code_state->sp < code_state->state) {
-            printf("VM stack underflow: " INT_FMT "\n", code_state->sp - code_state->state);
+            mp_printf(MICROPY_DEBUG_PRINTER, "VM stack underflow: " INT_FMT "\n", code_state->sp - code_state->state);
             assert(0);
         }
     }
-    // We can't check the case when an exception is returned in state[n_state - 1]
+    const byte *bytecode_ptr = mp_decode_uint_skip(mp_decode_uint_skip(self->bytecode));
+    size_t n_pos_args = bytecode_ptr[1];
+    size_t n_kwonly_args = bytecode_ptr[2];
+    // We can't check the case when an exception is returned in state[0]
     // and there are no arguments, because in this case our detection slot may have
     // been overwritten by the returned exception (which is allowed).
-    if (!(vm_return_kind == MP_VM_RETURN_EXCEPTION && self->n_pos_args + self->n_kwonly_args == 0)) {
+    if (!(vm_return_kind == MP_VM_RETURN_EXCEPTION && n_pos_args + n_kwonly_args == 0)) {
         // Just check to see that we have at least 1 null object left in the state.
         bool overflow = true;
-        for (size_t i = 0; i < n_state - self->n_pos_args - self->n_kwonly_args; i++) {
+        for (size_t i = 0; i < n_state - n_pos_args - n_kwonly_args; ++i) {
             if (code_state->state[i] == MP_OBJ_NULL) {
                 overflow = false;
                 break;
             }
         }
         if (overflow) {
-            printf("VM stack overflow state=%p n_state+1=" UINT_FMT "\n", code_state->state, n_state);
+            mp_printf(MICROPY_DEBUG_PRINTER, "VM stack overflow state=%p n_state+1=" UINT_FMT "\n", code_state->state, n_state);
             assert(0);
         }
     }
-#endif
+    #endif
 
     mp_obj_t result;
     if (vm_return_kind == MP_VM_RETURN_NORMAL) {