diff --git a/py/gc.c b/py/gc.c
index 734f5c3648035b3fdb426770c37a53503ac07525..5196954e2e43dff87aebf63407e804a6bc389e9d 100644
--- a/py/gc.c
+++ b/py/gc.c
@@ -320,11 +320,18 @@ void gc_collect_start(void) {
     #endif
     MP_STATE_MEM(gc_stack_overflow) = 0;
     MP_STATE_MEM(gc_sp) = MP_STATE_MEM(gc_stack);
+
     // Trace root pointers.  This relies on the root pointers being organised
     // correctly in the mp_state_ctx structure.  We scan nlr_top, dict_locals,
     // dict_globals, then the root pointer section of mp_state_vm.
     void **ptrs = (void**)(void*)&mp_state_ctx;
     gc_collect_root(ptrs, offsetof(mp_state_ctx_t, vm.qstr_last_chunk) / sizeof(void*));
+
+    #if MICROPY_ENABLE_PYSTACK
+    // Trace root pointers from the Python stack.
+    ptrs = (void**)(void*)MP_STATE_THREAD(pystack_start);
+    gc_collect_root(ptrs, (MP_STATE_THREAD(pystack_cur) - MP_STATE_THREAD(pystack_start)) / sizeof(void*));
+    #endif
 }
 
 void gc_collect_root(void **ptrs, size_t len) {
diff --git a/py/modmicropython.c b/py/modmicropython.c
index 2aac53adc79d72f6897e4671e8318173950da5ad..c14a0177de392e5631f7d95a1713d98e3de92c1d 100644
--- a/py/modmicropython.c
+++ b/py/modmicropython.c
@@ -112,6 +112,13 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_micropython_stack_use_obj, mp_micropython_st
 
 #endif // MICROPY_PY_MICROPYTHON_MEM_INFO
 
+#if MICROPY_ENABLE_PYSTACK
+STATIC mp_obj_t mp_micropython_pystack_use(void) {
+    return MP_OBJ_NEW_SMALL_INT(mp_pystack_usage());
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_micropython_pystack_use_obj, mp_micropython_pystack_use);
+#endif
+
 #if MICROPY_ENABLE_GC
 STATIC mp_obj_t mp_micropython_heap_lock(void) {
     gc_lock();
@@ -167,6 +174,9 @@ STATIC const mp_rom_map_elem_t mp_module_micropython_globals_table[] = {
 #if MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF && (MICROPY_EMERGENCY_EXCEPTION_BUF_SIZE == 0)
     { MP_ROM_QSTR(MP_QSTR_alloc_emergency_exception_buf), MP_ROM_PTR(&mp_alloc_emergency_exception_buf_obj) },
 #endif
+    #if MICROPY_ENABLE_PYSTACK
+    { MP_ROM_QSTR(MP_QSTR_pystack_use), MP_ROM_PTR(&mp_micropython_pystack_use_obj) },
+    #endif
     #if MICROPY_ENABLE_GC
     { MP_ROM_QSTR(MP_QSTR_heap_lock), MP_ROM_PTR(&mp_micropython_heap_lock_obj) },
     { MP_ROM_QSTR(MP_QSTR_heap_unlock), MP_ROM_PTR(&mp_micropython_heap_unlock_obj) },
diff --git a/py/modthread.c b/py/modthread.c
index cb071d0f86fae3ce5527b04ca049f8379cb48731..61ada5035150d00482ba7e19d53c9740eb0ea0da 100644
--- a/py/modthread.c
+++ b/py/modthread.c
@@ -165,6 +165,12 @@ STATIC void *thread_entry(void *args_in) {
     mp_stack_set_top(&ts + 1); // need to include ts in root-pointer scan
     mp_stack_set_limit(args->stack_size);
 
+    #if MICROPY_ENABLE_PYSTACK
+    // TODO threading and pystack is not fully supported, for now just make a small stack
+    mp_obj_t mini_pystack[128];
+    mp_pystack_init(mini_pystack, &mini_pystack[128]);
+    #endif
+
     // set locals and globals from the calling context
     mp_locals_set(args->dict_locals);
     mp_globals_set(args->dict_globals);
diff --git a/py/mpconfig.h b/py/mpconfig.h
index 7784367085d13f354b2b05ce9f08d04aa9c77deb..96cd8c651a91a3180a46616f5696016479858c1a 100644
--- a/py/mpconfig.h
+++ b/py/mpconfig.h
@@ -441,6 +441,17 @@
 #define MICROPY_ENABLE_FINALISER (0)
 #endif
 
+// Whether to enable a separate allocator for the Python stack.
+// If enabled then the code must call mp_pystack_init before mp_init.
+#ifndef MICROPY_ENABLE_PYSTACK
+#define MICROPY_ENABLE_PYSTACK (0)
+#endif
+
+// Number of bytes that memory returned by mp_pystack_alloc will be aligned by.
+#ifndef MICROPY_PYSTACK_ALIGN
+#define MICROPY_PYSTACK_ALIGN (8)
+#endif
+
 // Whether to check C stack usage. C stack used for calling Python functions,
 // etc. Not checking means segfault on overflow.
 #ifndef MICROPY_STACK_CHECK
diff --git a/py/mpstate.h b/py/mpstate.h
index 6a39ebdea96e155a747498cae140f7ee7afaf80e..d3b53f91515017110846865602634c7c0af5eeb9 100644
--- a/py/mpstate.h
+++ b/py/mpstate.h
@@ -224,6 +224,12 @@ typedef struct _mp_state_thread_t {
     #if MICROPY_STACK_CHECK
     size_t stack_limit;
     #endif
+
+    #if MICROPY_ENABLE_PYSTACK
+    uint8_t *pystack_start;
+    uint8_t *pystack_end;
+    uint8_t *pystack_cur;
+    #endif
 } mp_state_thread_t;
 
 // This structure combines the above 3 structures.
diff --git a/py/nlr.h b/py/nlr.h
index 63fe392d9e5de49d3cddc2cddc8425429cabd2ee..1235f1460948de3d8e61e4a8fc288e8c92604cf5 100644
--- a/py/nlr.h
+++ b/py/nlr.h
@@ -62,15 +62,32 @@ struct _nlr_buf_t {
 #if MICROPY_NLR_SETJMP
     jmp_buf jmpbuf;
 #endif
+
+    #if MICROPY_ENABLE_PYSTACK
+    void *pystack;
+    #endif
 };
 
+// Helper macros to save/restore the pystack state
+#if MICROPY_ENABLE_PYSTACK
+#define MP_NLR_SAVE_PYSTACK(nlr_buf) (nlr_buf)->pystack = MP_STATE_THREAD(pystack_cur)
+#define MP_NLR_RESTORE_PYSTACK(nlr_buf) MP_STATE_THREAD(pystack_cur) = (nlr_buf)->pystack
+#else
+#define MP_NLR_SAVE_PYSTACK(nlr_buf) (void)nlr_buf
+#define MP_NLR_RESTORE_PYSTACK(nlr_buf) (void)nlr_buf
+#endif
+
 #if MICROPY_NLR_SETJMP
 #include "py/mpstate.h"
 
 NORETURN void nlr_setjmp_jump(void *val);
 // nlr_push() must be defined as a macro, because "The stack context will be
 // invalidated if the function which called setjmp() returns."
-#define nlr_push(buf) ((buf)->prev = MP_STATE_THREAD(nlr_top), MP_STATE_THREAD(nlr_top) = (buf), setjmp((buf)->jmpbuf))
+#define nlr_push(buf) ( \
+    (buf)->prev = MP_STATE_THREAD(nlr_top), \
+    MP_NLR_SAVE_PYSTACK(buf), \
+    MP_STATE_THREAD(nlr_top) = (buf), \
+    setjmp((buf)->jmpbuf))
 #define nlr_pop() { MP_STATE_THREAD(nlr_top) = MP_STATE_THREAD(nlr_top)->prev; }
 #define nlr_jump(val) nlr_setjmp_jump(val)
 #else
diff --git a/py/nlrsetjmp.c b/py/nlrsetjmp.c
index 1fb45944034fab680b36843178f9ae959ef3628b..63376a5537400a695a9a26f2e1084eb83224d9bf 100644
--- a/py/nlrsetjmp.c
+++ b/py/nlrsetjmp.c
@@ -35,6 +35,7 @@ void nlr_setjmp_jump(void *val) {
         nlr_jump_fail(val);
     }
     top->ret_val = val;
+    MP_NLR_RESTORE_PYSTACK(top);
     *top_ptr = top->prev;
     longjmp(top->jmpbuf, 1);
 }
diff --git a/py/nlrthumb.c b/py/nlrthumb.c
index 6e7d717667f468c7ce5f6f86919573f14e53eeae..eab5759f2144aacbbd23b44b9887d1d388f4dd80 100644
--- a/py/nlrthumb.c
+++ b/py/nlrthumb.c
@@ -82,6 +82,7 @@ __attribute__((naked)) unsigned int nlr_push(nlr_buf_t *nlr) {
 __attribute__((used)) unsigned int nlr_push_tail(nlr_buf_t *nlr) {
     nlr_buf_t **top = &MP_STATE_THREAD(nlr_top);
     nlr->prev = *top;
+    MP_NLR_SAVE_PYSTACK(nlr);
     *top = nlr;
     return 0; // normal return
 }
@@ -99,6 +100,7 @@ NORETURN __attribute__((naked)) void nlr_jump(void *val) {
     }
 
     top->ret_val = val;
+    MP_NLR_RESTORE_PYSTACK(top);
     *top_ptr = top->prev;
 
     __asm volatile (
diff --git a/py/nlrx64.c b/py/nlrx64.c
index 847d10398e75f86f4da64d27f7330d2e9045a171..ddcd76166514a3405cc13757efd62d934236068e 100644
--- a/py/nlrx64.c
+++ b/py/nlrx64.c
@@ -91,6 +91,7 @@ unsigned int nlr_push(nlr_buf_t *nlr) {
 __attribute__((used)) unsigned int nlr_push_tail(nlr_buf_t *nlr) {
     nlr_buf_t **top = &MP_STATE_THREAD(nlr_top);
     nlr->prev = *top;
+    MP_NLR_SAVE_PYSTACK(nlr);
     *top = nlr;
     return 0; // normal return
 }
@@ -108,6 +109,7 @@ NORETURN void nlr_jump(void *val) {
     }
 
     top->ret_val = val;
+    MP_NLR_RESTORE_PYSTACK(top);
     *top_ptr = top->prev;
 
     __asm volatile (
diff --git a/py/nlrx86.c b/py/nlrx86.c
index 094dea3cc8c514f08160383fbabcbbe0d8767cca..3a27460eb64d13656fd01306a2cf0c2d5d5fde2e 100644
--- a/py/nlrx86.c
+++ b/py/nlrx86.c
@@ -73,6 +73,7 @@ unsigned int nlr_push(nlr_buf_t *nlr) {
 __attribute__((used)) unsigned int nlr_push_tail(nlr_buf_t *nlr) {
     nlr_buf_t **top = &MP_STATE_THREAD(nlr_top);
     nlr->prev = *top;
+    MP_NLR_SAVE_PYSTACK(nlr);
     *top = nlr;
     return 0; // normal return
 }
@@ -90,6 +91,7 @@ NORETURN void nlr_jump(void *val) {
     }
 
     top->ret_val = val;
+    MP_NLR_RESTORE_PYSTACK(top);
     *top_ptr = top->prev;
 
     __asm volatile (
diff --git a/py/nlrxtensa.c b/py/nlrxtensa.c
index 4520e7e7ac620339017665f5ea471f20eca32de4..5a969fc87cbeed7a5ff2252859f0d69ab1e6b639 100644
--- a/py/nlrxtensa.c
+++ b/py/nlrxtensa.c
@@ -58,6 +58,7 @@ unsigned int nlr_push(nlr_buf_t *nlr) {
 __attribute__((used)) unsigned int nlr_push_tail(nlr_buf_t *nlr) {
     nlr_buf_t **top = &MP_STATE_THREAD(nlr_top);
     nlr->prev = *top;
+    MP_NLR_SAVE_PYSTACK(nlr);
     *top = nlr;
     return 0; // normal return
 }
@@ -75,6 +76,7 @@ NORETURN void nlr_jump(void *val) {
     }
 
     top->ret_val = val;
+    MP_NLR_RESTORE_PYSTACK(top);
     *top_ptr = top->prev;
 
     __asm volatile (
diff --git a/py/py.mk b/py/py.mk
index f5faad1821c1ea71bfa752cdf4826af8b487a65a..0b5d5f8c400ad32d78fac5375bfa7bfc71420f07 100644
--- a/py/py.mk
+++ b/py/py.mk
@@ -110,6 +110,7 @@ PY_O_BASENAME = \
 	nlrsetjmp.o \
 	malloc.o \
 	gc.o \
+	pystack.o \
 	qstr.o \
 	vstr.o \
 	mpprint.o \
diff --git a/py/pystack.c b/py/pystack.c
new file mode 100644
index 0000000000000000000000000000000000000000..767c307e9a9d92345dfb1794194a46d43ef8a84a
--- /dev/null
+++ b/py/pystack.c
@@ -0,0 +1,56 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2017 Damien P. George
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdio.h>
+
+#include "py/runtime.h"
+
+#if MICROPY_ENABLE_PYSTACK
+
+void mp_pystack_init(void *start, void *end) {
+    MP_STATE_THREAD(pystack_start) = start;
+    MP_STATE_THREAD(pystack_end) = end;
+    MP_STATE_THREAD(pystack_cur) = start;
+}
+
+void *mp_pystack_alloc(size_t n_bytes) {
+    n_bytes = (n_bytes + (MICROPY_PYSTACK_ALIGN - 1)) & ~(MICROPY_PYSTACK_ALIGN - 1);
+    #if MP_PYSTACK_DEBUG
+    n_bytes += MICROPY_PYSTACK_ALIGN;
+    #endif
+    if (MP_STATE_THREAD(pystack_cur) + n_bytes > MP_STATE_THREAD(pystack_end)) {
+        // out of memory in the pystack
+        mp_raise_recursion_depth();
+    }
+    void *ptr = MP_STATE_THREAD(pystack_cur);
+    MP_STATE_THREAD(pystack_cur) += n_bytes;
+    #if MP_PYSTACK_DEBUG
+    *(size_t*)(MP_STATE_THREAD(pystack_cur) - MICROPY_PYSTACK_ALIGN) = n_bytes;
+    #endif
+    return ptr;
+}
+
+#endif
diff --git a/py/pystack.h b/py/pystack.h
new file mode 100644
index 0000000000000000000000000000000000000000..82ac3743d1c65433e7a9008228b032adb6b55d87
--- /dev/null
+++ b/py/pystack.h
@@ -0,0 +1,123 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2017 Damien P. George
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#ifndef MICROPY_INCLUDED_PY_PYSTACK_H
+#define MICROPY_INCLUDED_PY_PYSTACK_H
+
+#include "py/mpstate.h"
+
+// Enable this debugging option to check that the amount of memory freed is
+// consistent with amounts that were previously allocated.
+#define MP_PYSTACK_DEBUG (0)
+
+#if MICROPY_ENABLE_PYSTACK
+
+void mp_pystack_init(void *start, void *end);
+void *mp_pystack_alloc(size_t n_bytes);
+
+// This function can free multiple continuous blocks at once: just pass the
+// pointer to the block that was allocated first and it and all subsequently
+// allocated blocks will be freed.
+static inline void mp_pystack_free(void *ptr) {
+    assert((uint8_t*)ptr >= MP_STATE_THREAD(pystack_start));
+    assert((uint8_t*)ptr <= MP_STATE_THREAD(pystack_cur));
+    #if MP_PYSTACK_DEBUG
+    size_t n_bytes_to_free = MP_STATE_THREAD(pystack_cur) - (uint8_t*)ptr;
+    size_t n_bytes = *(size_t*)(MP_STATE_THREAD(pystack_cur) - MICROPY_PYSTACK_ALIGN);
+    while (n_bytes < n_bytes_to_free) {
+        n_bytes += *(size_t*)(MP_STATE_THREAD(pystack_cur) - n_bytes - MICROPY_PYSTACK_ALIGN);
+    }
+    if (n_bytes != n_bytes_to_free) {
+        mp_printf(&mp_plat_print, "mp_pystack_free() failed: %u != %u\n", (uint)n_bytes_to_free,
+            (uint)*(size_t*)(MP_STATE_THREAD(pystack_cur) - MICROPY_PYSTACK_ALIGN));
+        assert(0);
+    }
+    #endif
+    MP_STATE_THREAD(pystack_cur) = (uint8_t*)ptr;
+}
+
+static inline void mp_pystack_realloc(void *ptr, size_t n_bytes) {
+    mp_pystack_free(ptr);
+    mp_pystack_alloc(n_bytes);
+}
+
+static inline size_t mp_pystack_usage(void) {
+    return MP_STATE_THREAD(pystack_cur) - MP_STATE_THREAD(pystack_start);
+}
+
+static inline size_t mp_pystack_limit(void) {
+    return MP_STATE_THREAD(pystack_end) - MP_STATE_THREAD(pystack_start);
+}
+
+#endif
+
+#if !MICROPY_ENABLE_PYSTACK
+
+#define mp_local_alloc(n_bytes) alloca(n_bytes)
+
+static inline void mp_local_free(void *ptr) {
+    (void)ptr;
+}
+
+static inline void *mp_nonlocal_alloc(size_t n_bytes) {
+    return m_new(uint8_t, n_bytes);
+}
+
+static inline void *mp_nonlocal_realloc(void *ptr, size_t old_n_bytes, size_t new_n_bytes) {
+    return m_renew(uint8_t, ptr, old_n_bytes, new_n_bytes);
+}
+
+static inline void mp_nonlocal_free(void *ptr, size_t n_bytes) {
+    m_del(uint8_t, ptr, n_bytes);
+}
+
+#else
+
+static inline void *mp_local_alloc(size_t n_bytes) {
+    return mp_pystack_alloc(n_bytes);
+}
+
+static inline void mp_local_free(void *ptr) {
+    mp_pystack_free(ptr);
+}
+
+static inline void *mp_nonlocal_alloc(size_t n_bytes) {
+    return mp_pystack_alloc(n_bytes);
+}
+
+static inline void *mp_nonlocal_realloc(void *ptr, size_t old_n_bytes, size_t new_n_bytes) {
+    (void)old_n_bytes;
+    mp_pystack_realloc(ptr, new_n_bytes);
+    return ptr;
+}
+
+static inline void mp_nonlocal_free(void *ptr, size_t n_bytes) {
+    (void)n_bytes;
+    mp_pystack_free(ptr);
+}
+
+#endif
+
+#endif // MICROPY_INCLUDED_PY_PYSTACK_H
diff --git a/py/runtime.c b/py/runtime.c
index 5fd053e1a2723c6f8985c097f1ee2d96ab3beaab..3a4a8a93b003f1e74a783a5be2ad3b8111d14c29 100644
--- a/py/runtime.c
+++ b/py/runtime.c
@@ -1457,7 +1457,7 @@ NORETURN void mp_raise_NotImplementedError(const char *msg) {
     mp_raise_msg(&mp_type_NotImplementedError, msg);
 }
 
-#if MICROPY_STACK_CHECK
+#if MICROPY_STACK_CHECK || MICROPY_ENABLE_PYSTACK
 NORETURN void mp_raise_recursion_depth(void) {
     nlr_raise(mp_obj_new_exception_arg1(&mp_type_RuntimeError,
         MP_OBJ_NEW_QSTR(MP_QSTR_maximum_space_recursion_space_depth_space_exceeded)));
diff --git a/py/runtime.h b/py/runtime.h
index a19f64c0676fe9d08774dadb9b9b24741f3f521f..6288e88367f0f8aeb10771eaa1a1535a353d65de 100644
--- a/py/runtime.h
+++ b/py/runtime.h
@@ -27,6 +27,7 @@
 #define MICROPY_INCLUDED_PY_RUNTIME_H
 
 #include "py/mpstate.h"
+#include "py/pystack.h"
 
 typedef enum {
     MP_VM_RETURN_NORMAL,