diff --git a/py/mpconfig.h b/py/mpconfig.h
index 0c46bf3e5acc6ce5fb5254273c376975c6a639e7..cd9380aa6eeb94543c7234452bc80ed12ebad139 100644
--- a/py/mpconfig.h
+++ b/py/mpconfig.h
@@ -351,6 +351,22 @@
 /*****************************************************************************/
 /* Python internal features                                                  */
 
+// Hook for the VM at the start of the opcode loop (can contain variable
+// definitions usable by the other hook functions)
+#ifndef MICROPY_VM_HOOK_INIT
+#define MICROPY_VM_HOOK_INIT
+#endif
+
+// Hook for the VM during the opcode loop (but only after jump opcodes)
+#ifndef MICROPY_VM_HOOK_LOOP
+#define MICROPY_VM_HOOK_LOOP
+#endif
+
+// Hook for the VM just before return opcode is finished being interpreted
+#ifndef MICROPY_VM_HOOK_RETURN
+#define MICROPY_VM_HOOK_RETURN
+#endif
+
 // Whether to include the garbage collector
 #ifndef MICROPY_ENABLE_GC
 #define MICROPY_ENABLE_GC (0)
diff --git a/py/vm.c b/py/vm.c
index 141315ea8e1293c03ed58fab0f78e0a8d7e313b8..b8d38f78e424ec3a09cc45ec7a7ea0ea174c6408 100644
--- a/py/vm.c
+++ b/py/vm.c
@@ -179,6 +179,7 @@ outer_dispatch_loop:
             const byte *ip = code_state->ip;
             mp_obj_t *sp = code_state->sp;
             mp_obj_t obj_shared;
+            MICROPY_VM_HOOK_INIT
 
             // If we have exception to inject, now that we finish setting up
             // execution context, raise it. This works as if RAISE_VARARGS
@@ -1069,6 +1070,7 @@ unwind_return:
                     nlr_pop();
                     code_state->sp = sp;
                     assert(exc_sp == exc_stack - 1);
+                    MICROPY_VM_HOOK_RETURN
                     #if MICROPY_STACKLESS
                     if (code_state->prev != NULL) {
                         mp_obj_t res = *sp;
@@ -1252,6 +1254,7 @@ yield:
 #endif
 
 pending_exception_check:
+                MICROPY_VM_HOOK_LOOP
                 if (MP_STATE_VM(mp_pending_exception) != MP_OBJ_NULL) {
                     MARK_EXC_IP_SELECTIVE();
                     mp_obj_t obj = MP_STATE_VM(mp_pending_exception);