changeset 32309:822314643f50

VM Add support for nested functions Nested functions and functions with nested functions can be evaluated by the VM. * libinterp/corefcn/call-stack.cc: Push bytecode frame for nested functions * libinterp/corefcn/call-stack.h * libinterp/corefcn/stack-frame.cc: Support saving bytecode frames, frameoffset ... * libinterp/corefcn/stack-frame.h * libinterp/octave-value/ov-fcn-handle.cc: Compile nested function handles Refactor VM call and is compiled check. * libinterp/octave-value/ov-fcn.cc: Refactor compiled check and VM call * libinterp/octave-value/ov-ref.cc: New ref octave_value_ref_ptr for nested frames * libinterp/octave-value/ov-ref.h * libinterp/octave-value/ov-usr-fcn.cc: Refactor vm call and compiled check * libinterp/octave-value/ov.h: octave_value_ref_ptr friend to call is_ref() and ref_rep () * libinterp/parse-tree/pt-bytecode-vm.cc: Function caches might be written to refs New vm::call () and vm::maybe_compiled_or_compile () Call bytecode for command functions. * libinterp/parse-tree/pt-bytecode-vm.h * libinterp/parse-tree/pt-bytecode-walk.cc: Compile nested functions, set up variables * libinterp/parse-tree/pt-bytecode-walk.h * libinterp/parse-tree/pt-bytecode.h: New opcode ENTER_NESTED * libinterp/parse-tree/pt-eval.cc: Push nested frame, refactor how VM is called * libinterp/parse-tree/pt-eval.h * test/compile/bytecode.tst: Tests added * test/compile/module.mk * test/compile/bytecode_nested.m * test/compile/cdef_bar.m: Self-counting test class
author Petter T.
date Mon, 04 Sep 2023 16:52:22 +0200
parents d8311055ebe1
children cad9584e7bad
files libinterp/corefcn/call-stack.cc libinterp/corefcn/call-stack.h libinterp/corefcn/stack-frame.cc libinterp/corefcn/stack-frame.h libinterp/octave-value/ov-fcn-handle.cc libinterp/octave-value/ov-fcn.cc libinterp/octave-value/ov-ref.cc libinterp/octave-value/ov-ref.h libinterp/octave-value/ov-usr-fcn.cc libinterp/octave-value/ov.h libinterp/parse-tree/pt-bytecode-vm.cc libinterp/parse-tree/pt-bytecode-vm.h libinterp/parse-tree/pt-bytecode-walk.cc libinterp/parse-tree/pt-bytecode-walk.h libinterp/parse-tree/pt-bytecode.h libinterp/parse-tree/pt-eval.cc libinterp/parse-tree/pt-eval.h test/compile/bytecode.tst test/compile/bytecode_nested.m test/compile/cdef_bar.m test/compile/module.mk
diffstat 21 files changed, 1422 insertions(+), 339 deletions(-) [+]
line wrap: on
line diff
--- a/libinterp/corefcn/call-stack.cc	Thu Aug 10 17:07:19 2023 +0200
+++ b/libinterp/corefcn/call-stack.cc	Mon Sep 04 16:52:22 2023 +0200
@@ -474,6 +474,26 @@
   m_curr_frame = new_frame_idx;
 }
 
+void call_stack::push (vm &vm, octave_user_function *fcn, int nargout, int nargin,
+                       const std::shared_ptr<stack_frame>& closure_frames)
+{
+  std::size_t new_frame_idx;
+  std::shared_ptr<stack_frame> parent_link;
+  std::shared_ptr<stack_frame> static_link;
+
+  get_new_frame_index_and_links (new_frame_idx, parent_link, static_link);
+
+  std::shared_ptr<stack_frame> new_frame
+    = stack_frame::create_bytecode (m_evaluator, fcn, vm,
+                                    new_frame_idx,
+                                    parent_link, static_link, closure_frames,
+                                    nargout, nargin);
+
+  m_cs.push_back (new_frame);
+
+  m_curr_frame = new_frame_idx;
+}
+
 void call_stack::push (vm &vm, octave_user_function *fcn, int nargout, int nargin)
 {
   std::size_t new_frame_idx;
--- a/libinterp/corefcn/call-stack.h	Thu Aug 10 17:07:19 2023 +0200
+++ b/libinterp/corefcn/call-stack.h	Mon Sep 04 16:52:22 2023 +0200
@@ -171,6 +171,9 @@
 
   void push (vm &vm, octave_user_script *fcn, int nargout, int nargin);
 
+  void push (vm &vm, octave_user_function *fcn, int nargout, int nargin,
+             const std::shared_ptr<stack_frame>& closure_frames);
+
   void set_location (int l, int c)
   {
     if (! m_cs.empty ())
--- a/libinterp/corefcn/stack-frame.cc	Thu Aug 10 17:07:19 2023 +0200
+++ b/libinterp/corefcn/stack-frame.cc	Mon Sep 04 16:52:22 2023 +0200
@@ -102,7 +102,7 @@
     // to resize the bytecode frame size.
     std::size_t n_syms =  fcn->scope_num_symbols ();
 
-    m_orig_size = m_unwind_data->m_external_frame_offset_to_internal.size ();
+    m_orig_size = m_unwind_data->m_external_frame_offset_to_internal[0].size (); // TODO: Check m_n_locals instead?
 
     if (n_syms > m_orig_size)
       {
@@ -152,7 +152,7 @@
             // Note: int nargout at offset 0
             for (unsigned i = 1; i < m_size; i++)
               m_lazy_data->m_stack_cpy[i].ov.~octave_value ();
-            delete m_lazy_data->m_stack_cpy;
+            delete [] m_lazy_data->m_stack_cpy;
           }
         delete m_lazy_data->m_unwind_protect_frame;
         delete m_lazy_data;
@@ -167,28 +167,37 @@
   }
 
 
-  size_t local_to_external_offset (size_t local_offset) const
+  void local_to_external_offset (size_t local_offset, size_t &external_offset_out, size_t &frame_offset_out) const
   {
-    for (auto it : m_unwind_data->m_external_frame_offset_to_internal)
+    for (size_t frame_offset = 0; frame_offset < m_unwind_data->m_external_frame_offset_to_internal.size (); frame_offset++)
       {
-        size_t local_tmp = it.second;
-        if (local_tmp != local_offset)
-          continue;
-
-        return it.first; // external offset
+        for (auto it : m_unwind_data->m_external_frame_offset_to_internal[frame_offset])
+          {
+            size_t local_tmp = it.second;
+            if (local_tmp != local_offset)
+              continue;
+
+            external_offset_out = it.first;
+            frame_offset_out = frame_offset;
+
+            return;
+          }
       }
 
     if (local_offset < m_size)
       error ("VM internal error: Invalid internal offset. Smaller than original size and not in table");
 
-    return local_offset - m_size + m_orig_size;
+    external_offset_out = local_offset - m_size + m_orig_size;
+    frame_offset_out = 0;
   }
 
-  std::size_t external_to_local_offset (std::size_t external_offset) const
+  std::size_t external_to_local_offset (std::size_t external_offset, std::size_t frame_offset = 0) const
   {
-    auto it = m_unwind_data->m_external_frame_offset_to_internal.find (external_offset);
-    if (it == m_unwind_data->m_external_frame_offset_to_internal.end ())
+    auto it = m_unwind_data->m_external_frame_offset_to_internal.at (frame_offset).find (external_offset);
+    if (it == m_unwind_data->m_external_frame_offset_to_internal[frame_offset].end ())
     {
+      CHECK_PANIC (frame_offset == 0);
+
       if (external_offset < m_orig_size)
         error ("VM internal error: Invalid external offset. Smaller than original size and not in table");
       // The offsets that are not in the original translation table are in the extra slots added dynamically
@@ -198,6 +207,29 @@
     return it->second;
   }
 
+  bool maybe_external_to_local_offset (std::size_t external_offset, std::size_t frame_offset, std::size_t &internal_offset_out) const
+  {
+    if (frame_offset >= m_unwind_data->m_external_frame_offset_to_internal.size ())
+      return false;
+
+    auto it = m_unwind_data->m_external_frame_offset_to_internal.at (frame_offset).find (external_offset);
+    bool found = it != m_unwind_data->m_external_frame_offset_to_internal[frame_offset].end ();
+
+    if (frame_offset == 0 && !found)
+      {
+        if (external_offset < m_orig_size)
+          error ("VM internal error: Invalid external offset. Smaller than original size and not in table");
+        // The offsets that are not in the original translation table are in the extra slots added dynamically
+        internal_offset_out = m_size + (external_offset - m_orig_size);
+        return true;
+      }
+    else if (!found)
+      return false;
+
+    internal_offset_out = it->second;
+    return true;
+  }
+
   // Do an expensive check if the stack is in order. Call only during debugging.
   void vm_dbg_check_scope ()
   {
@@ -210,21 +242,22 @@
           continue;
 
         std::size_t scope_offset = sym_scope.data_offset ();
+        std::size_t frame_offset = sym_scope.frame_offset ();
         std::string scope_name = sym_scope.name ();
 
-        std::size_t internal_offset = external_to_local_offset (scope_offset);
+        std::size_t internal_offset = external_to_local_offset (scope_offset, frame_offset);
         if (internal_offset >= m_size)
           continue; // We don't check the "extra" slots since these can change with eval and evalin:s etc
 
         symbol_record sym_frame = lookup_symbol (scope_name);
 
-        std::size_t frame_offset = sym_frame.data_offset ();
+        std::size_t offset2 = sym_frame.data_offset ();
         std::string frame_name = sym_frame.name ();
 
         if (scope_name != frame_name && frame_name != "")
           error ("VM stack check failed: %s != %s\n", scope_name.c_str (), frame_name.c_str ());
-        if (scope_offset != frame_offset && frame_name != "")
-          error ("VM stack check failed: %zu != %zu\n", scope_offset, frame_offset);
+        if (scope_offset != offset2 && frame_name != "")
+          error ("VM stack check failed: %zu != %zu\n", scope_offset, offset2);
 
         if (internal_offset >= internal_size ())
           internal_resize (internal_offset + 1);
@@ -303,7 +336,7 @@
 
     // These pointers might become invalid
     m_vm = nullptr;
-    m_unwind_data = nullptr;
+    //m_unwind_data  = nullptr;
 
     // Copy the stack to the frame
     size_t stack_slots = m_size;
@@ -311,13 +344,11 @@
     lazy_data ();
 
     m_lazy_data->m_stack_cpy = new octave::stack_element[stack_slots];
+
+    m_lazy_data->m_stack_cpy[0].i = m_stack_start[0].i; // Copy int nargout at offset 0
+    // Copy each octave_value in slots on the stack
     for (unsigned i = 1; i < m_size; i++)
-      new (&m_lazy_data->m_stack_cpy[i].ov) octave_value {};
-
-    // Note: int nargout at offset 0
-    m_lazy_data->m_stack_cpy[0].i = m_stack_start[0].i;
-    for (unsigned i = 1; i < m_size; i++)
-      m_lazy_data->m_stack_cpy[i].ov = m_stack_start[i].ov;
+      new (&m_lazy_data->m_stack_cpy[i].ov) octave_value {m_stack_start[i].ov};
 
     m_stack_start = m_lazy_data->m_stack_cpy;
   }
@@ -394,20 +425,29 @@
     return false;
   }
 
-  stack_frame::scope_flags get_scope_flag (std::size_t external_offset) const
+  stack_frame::scope_flags get_scope_flag_internal (std::size_t external_offset, std::size_t frame_offset) const
   {
-    std::size_t local_offset = external_to_local_offset (external_offset);
+    std::size_t local_offset;
+    bool found = maybe_external_to_local_offset (external_offset, frame_offset, local_offset);
+
+    // Note, quirk: A variable that is not added to a nested frame is not to be reported as global
+    // eventhough it is global in the parent frame.
+    if (!found)
+      {
+        if (!m_fcn->is_nested_function ())
+          error ("VM internal error: Invalid call to get_scope_flag_internal");
+
+        return LOCAL;
+      }
+
     // Is the slot on the original bytecode stack frame?
     if (local_offset < m_size)
       {
-        octave_value &ov = m_stack_start [local_offset].ov;
+        octave_value ov = m_stack_start [local_offset].ov;
+
         if (!ov.is_ref ())
           return LOCAL;
-        if (ov.ref_rep ()->is_global_ref ())
-          return GLOBAL;
-        if (ov.ref_rep ()->is_persistent_ref ())
-          return PERSISTENT;
-        return LOCAL;
+        return ov.ref_rep ()->get_scope_flag ();
       }
 
     // TODO: Could use code above instead?
@@ -418,6 +458,11 @@
     return LOCAL;
   }
 
+  stack_frame::scope_flags get_scope_flag (std::size_t external_offset) const
+  {
+    return get_scope_flag_internal (external_offset, 0);
+  }
+
   void set_scope_flag (std::size_t external_offset, scope_flags flag)
   {
     scope_flags current_flag = get_scope_flag (external_offset);
@@ -491,11 +536,9 @@
   stack_frame::scope_flags scope_flag (const symbol_record& sym) const
   {
     std::size_t external_offset = sym.data_offset ();
-
-    if (sym.frame_offset())
-      error ("TODO: Frame offset %d", __LINE__);
-
-    return get_scope_flag (external_offset);
+    std::size_t frame_offset = sym.frame_offset ();
+
+    return get_scope_flag_internal (external_offset, frame_offset);
   }
 
   virtual octave_value get_active_bytecode_call_arg_names ()
@@ -608,13 +651,11 @@
   using stack_frame::varval;
   using stack_frame::varref;
 
-  octave_value varval (std::size_t external_offset) const
+  octave_value varval_internal (std::size_t local_offset) const
   {
     size_t extra_size = (m_lazy_data ? m_lazy_data->m_extra_slots.size () : 0);
     size_t stack_slots = m_size;
 
-    std::size_t local_offset = external_to_local_offset (external_offset);
-
     if (local_offset == 0) // Handle native int %nargout specially
       return octave_value {m_stack_start [0].i};
     if (local_offset < stack_slots)
@@ -636,14 +677,27 @@
       }
   }
 
+  octave_value varval (std::size_t external_offset) const
+  {
+    std::size_t local_offset = external_to_local_offset (external_offset);
+    return varval_internal (local_offset);
+  }
+
   octave_value varval (const symbol_record& sym) const
   {
-    // We don't use frame offsets. Just return nil
-    if (sym.frame_offset())
-      return octave_value {};
-
     std::size_t external_offset = sym.data_offset ();
-    std::size_t local_offset = external_to_local_offset (external_offset);
+    std::size_t frame_offset = sym.frame_offset ();
+    std::size_t local_offset;
+    bool found = maybe_external_to_local_offset (external_offset, frame_offset, local_offset);
+
+    if (!found && m_fcn->is_nested_function ())
+      {
+        // Try to find the symbol in the root frame instead.
+        auto sym_in_root = access_link ()->lookup_symbol (sym.name ());
+        return access_link ()->varval (sym_in_root);
+      }
+    else if (!found)
+      error ("VM internal error: Invalid call to varval() with frame offset %zu, name '%s'", frame_offset, sym.name ().c_str ());
 
     // If the offset is out of range we return a nil ov, unless this is a script frame,
     // in which case we need to look in enclosing frames for the symbol.
@@ -696,18 +750,16 @@
     if (is_pers)
       return get_scope ().persistent_varval (external_offset);
 
-    return varval (external_offset);
+    return varval_internal (local_offset);
   }
 
-  octave_value& varref (std::size_t external_offset, bool deref_refs)
+  octave_value& varref_internal (std::size_t local_offset, bool deref_refs)
   {
     static octave_value fake_dummy_nargout{0};
 
     std::size_t extra_size = (m_lazy_data ? m_lazy_data->m_extra_slots.size () : 0);
     std::size_t stack_slots = m_size;
 
-    std::size_t local_offset = external_to_local_offset (external_offset);
-
     // Handle native int %nargout specially. Note that changing
     // the value of %nargout via the this ref wont work.
     if (local_offset == 0)
@@ -731,14 +783,30 @@
       }
   }
 
+  octave_value& varref (std::size_t external_offset, bool deref_refs)
+  {
+    std::size_t local_offset = external_to_local_offset (external_offset);
+    return varref_internal (local_offset, deref_refs);
+  }
+
   octave_value& varref (const symbol_record& sym, bool deref_refs)
   {
     bool add_to_parent_scriptframes = false;
     std::size_t external_offset = sym.data_offset ();
-    std::size_t local_offset = external_to_local_offset (external_offset);
-
-    if (sym.frame_offset())
-      error ("TODO: Frame offset");
+    std::size_t frame_offset = sym.frame_offset ();
+
+    std::size_t local_offset;
+    bool found = maybe_external_to_local_offset (external_offset, frame_offset, local_offset);
+
+    // Only nested functions should be able to not find the internal offset for an external offset.
+    if (!found && m_fcn->is_nested_function ())
+      {
+        // Try to find the symbol in the root frame instead.
+        auto sym_in_root = access_link ()->insert_symbol (sym.name ());
+        return access_link ()->varref (sym_in_root, deref_refs);
+      }
+    else if (!found)
+      panic ("VM internal error: Invalid call to varref() with frame offset %zu, name '%s'", frame_offset, sym.name ().c_str ());
 
     // If the offset is out of range we make room for it
     if (local_offset >= internal_size ())
@@ -771,7 +839,7 @@
       return get_scope ().persistent_varref(external_offset);
 
     if (!add_to_parent_scriptframes)
-      return varref (external_offset, deref_refs);
+      return varref_internal (local_offset, deref_refs);
     else
       {
         // Mirrors what happens in vm_enter_script ().
@@ -818,7 +886,20 @@
     bool remove_global_from_scripts = flag != GLOBAL && is_script && was_global;
 
     std::size_t external_offset = sym.data_offset ();
-    std::size_t local_offset = external_to_local_offset (external_offset);
+    std::size_t frame_offset = sym.frame_offset ();
+
+    std::size_t local_offset;
+    bool found = maybe_external_to_local_offset (external_offset, frame_offset, local_offset);
+
+    if (!found)
+      {
+        if (!m_fcn->is_nested_function ())
+          error ("VM internal error: Invalid call to mark_scope");
+
+        auto sym_in_root = access_link ()->lookup_symbol (sym.name ());
+        access_link ()->mark_scope (sym_in_root, flag);
+        return;
+      }
 
     if (local_offset >= internal_size ())
       internal_resize (local_offset + 1);
@@ -917,7 +998,15 @@
     if (local_offset >= 0)
       {
         symbol_record ret (name, flag);
-        ret.set_data_offset (local_to_external_offset (static_cast<std::size_t> (local_offset)));
+
+        size_t frame_offset;
+        size_t offset;
+
+        local_to_external_offset (static_cast<std::size_t> (local_offset), offset, frame_offset);
+
+        ret.set_data_offset (offset);
+        ret.set_frame_offset (frame_offset);
+
         // Check if the symbol is an argument or return symbol. Note: Negative count for vararg and varargin
         int n_returns = abs (static_cast<signed char> (m_code[0]));
         int n_args = abs (static_cast<signed char> (m_code[1]));
@@ -941,8 +1030,9 @@
       }
 
     // Search the "scope" object of this and any nested frame
-    // The scope object will have e.g. variables added by scripts
+    // The scope object will have e.g. variables added by scripts or eval
     const stack_frame *frame = this;
+    std::size_t frame_cntr = 0;
     while (frame)
       {
         symbol_scope scope = frame->get_scope ();
@@ -950,10 +1040,16 @@
         symbol_record sym = scope.lookup_symbol (name);
 
         if (sym)
-          return sym;
+          {
+            // Return symbol record with adjusted frame offset (relative to the one lookup is done on)
+            symbol_record new_sym = sym.dup ();
+            new_sym.set_frame_offset (frame_cntr);
+            return new_sym;
+          }
 
         std::shared_ptr<stack_frame> nxt = frame->access_link ();
         frame = nxt.get ();
+        frame_cntr++;
       }
 
     return symbol_record ();
@@ -1000,7 +1096,7 @@
     if (! m_vm)
       return -1;
 
-    loc_entry loc = vm::find_loc (m_ip, m_vm->m_unwind_data->m_loc_entry); // TODO: Does not work in nested bytecode stack frames
+    loc_entry loc = vm::find_loc (m_ip, m_unwind_data->m_loc_entry); // TODO: Does not work in nested bytecode stack frames
     return loc.m_line;
   }
 
@@ -1009,7 +1105,7 @@
     if (! m_vm)
       return -1;
 
-    loc_entry loc = m_vm->find_loc (m_ip, m_vm->m_unwind_data->m_loc_entry);
+    loc_entry loc = vm::find_loc (m_ip, m_unwind_data->m_loc_entry);
     return loc.m_col;
   }
 
@@ -1023,6 +1119,172 @@
 
   std::weak_ptr<stack_frame> m_weak_ptr_to_self;
 
+  void vm_enter_nested ()
+  {
+    // We got multiple scenarios.
+    //
+    // 1. The parents are bytecode functions in the order
+    //    according to the nesting of nested functions. This should be the
+    //    most common use. Self-recursive calls are handled too.
+    //
+    // 2. If a nested function calls a sibling nested function, the parent frames
+    //    which are not direct parents to the nested sibling funtion need to be skipped
+    //    while searching for the matriarch frame.
+    //
+    // 3. A nested function is called via a handle.
+
+    bool is_direct_call = true;
+
+    auto parent_frame = static_link ();
+
+    auto *child_bc_frame = this;
+
+    // Walk the parent(s) to see if they are in direct order and all bytecode frames.
+    // Collect the bsp. (base stack pointer)
+    int n_nested_depth = m_unwind_data->m_n_nested_fn;
+    std::vector<stack_element*> v_parent_bsps;
+    int i;
+    for (i = 0; i < n_nested_depth; i++)
+      {
+        bool collect_frame_bsp = true;
+        bool forward_child_ptr = true;
+
+        if (!parent_frame->is_bytecode_fcn_frame ())
+          {
+            is_direct_call = false;
+            break;
+          }
+
+        auto *parent_bc_frame = static_cast<bytecode_fcn_stack_frame*> (parent_frame.get ());
+
+        // Recursive self call?
+        if (parent_bc_frame->m_unwind_data->m_id == child_bc_frame->m_unwind_data->m_id)
+          {
+            collect_frame_bsp = false;
+            i--;
+          }
+        // Not direct parent?
+        else if (parent_bc_frame->m_unwind_data->m_id != child_bc_frame->m_unwind_data->m_parent_id)
+          {
+            // Sibling or their children?
+            if (parent_bc_frame->m_unwind_data->m_matriarch_id == child_bc_frame->m_unwind_data->m_matriarch_id)
+              {
+                collect_frame_bsp = false;
+                forward_child_ptr = false; // Keep looking for the parent of the current child ptr
+                i--;
+              }
+            else
+              {
+                is_direct_call = false;
+                break;
+              }
+          }
+
+        // We don't collect recursive self-calls' or siblings' or siblings' childrens' frames
+        if (collect_frame_bsp)
+          v_parent_bsps.push_back (parent_bc_frame->m_stack_start);
+
+        // Skip copying the shared pointer if we don't need it as there is no next iteration
+        // since shared_ptr:s are quite expensive to use.
+        if (i + 1 == n_nested_depth)
+          break;
+
+        parent_frame = parent_frame->static_link ();
+        if (forward_child_ptr)
+          child_bc_frame = parent_bc_frame;
+        parent_bc_frame = static_cast<bytecode_fcn_stack_frame*> (parent_frame.get ());
+      }
+
+    // Nested function handles have a closure context
+    bool has_closure = access_link ()->is_closure_context ();
+
+    if (is_direct_call && ! has_closure)
+      {
+        for (unwind_data::nested_var_offset &d : m_unwind_data->m_v_nested_vars)
+        {
+          int parent_slot = d.m_slot_parent;
+          int nested_slot = d.m_slot_nested;
+          int depth = d.m_depth;
+
+          stack_element *owner_bsp = v_parent_bsps.at (depth - 1);
+
+          octave_value &orig_ov = owner_bsp[parent_slot].ov; // On the parent stack
+          octave_value &nested_ov = m_stack_start[nested_slot].ov; // On the current stack
+
+          // If the ov on the parent stack is a pointer reference we need to follow it.
+          if (orig_ov.is_ref ())
+            {
+              auto ref_rep = orig_ov.ref_rep ();
+              if (ref_rep->is_ptr_ref ())
+                orig_ov = ref_rep->ref ();
+            }
+
+          CHECK_PANIC (&orig_ov != &nested_ov);
+
+          // Make the nested ov reference the ov on the parent stack.
+          nested_ov = new octave_value_ref_ptr (&orig_ov);
+        }
+      }
+    else
+      {
+        // For a nested function at nesting depth n we need to collect n
+        // access links.
+        auto first_context_frame = access_link ();
+        CHECK_PANIC (first_context_frame);
+
+        std::vector<decltype(first_context_frame)> v_frames {std::move (first_context_frame)};
+
+        for (int j = 1; j < m_unwind_data->m_n_nested_fn; j++) // 1, since first one already added
+          {
+            auto &upper_frame = v_frames.back ();
+            auto lower_frame = upper_frame->access_link ();
+            CHECK_PANIC (lower_frame);
+            v_frames.push_back (lower_frame);
+          }
+
+        // For each variable that refer to variables on the parent frames, we need to link
+        // the local variable of the current frame to the correct slot on the parents'.
+        for (unwind_data::nested_var_offset &d : m_unwind_data->m_v_nested_vars)
+          {
+            int parent_slot = d.m_slot_parent;
+            int nested_slot = d.m_slot_nested;
+            int depth = d.m_depth;
+
+            octave_value &nested_ov = m_stack_start[nested_slot].ov; // On the current stack
+
+            auto &context_frame = v_frames.at (depth - 1);
+            auto context_scope = context_frame->get_scope ();
+
+            auto sym = context_scope.find_symbol (m_name_data [nested_slot]);
+            CHECK_PANIC (sym.is_valid ());
+
+            // For bytecode frames we just do a pointer octave_value object refering to
+            // to address in memory. For other frames we need to access it via the dynamic frame pointer
+            // (since the memory in those can move around)
+            if (context_frame->is_bytecode_fcn_frame ())
+              {
+                auto *context_bc_frame = static_cast<bytecode_fcn_stack_frame*> (context_frame.get ());
+                auto owner_bsp = context_bc_frame->m_stack_start;
+                octave_value &orig_ov = owner_bsp[parent_slot].ov; // On the parent stack
+
+                // If the ov on the parent stack is a pointer reference we need to follow it.
+                if (orig_ov.is_ref ())
+                  {
+                    auto ref_rep = orig_ov.ref_rep ();
+                    if (ref_rep->is_ptr_ref ())
+                      orig_ov = ref_rep->ref ();
+                  }
+
+                CHECK_PANIC (&orig_ov != &nested_ov);
+
+                nested_ov = new octave_value_ref_ptr (&orig_ov); // Pointer object octave_value_ref_ptr to parent stack
+              }
+            else
+              nested_ov = new octave_value_ref_vmlocal (sym, context_frame.get ());
+          }
+      }
+  }
+
   void vm_enter_script ()
   {
     lazy_data ().m_is_script = true;
@@ -1115,6 +1377,24 @@
       }
   }
 
+  void break_closure_cycles (const std::shared_ptr<stack_frame> &frame)
+  {
+    if (m_stack_start)
+      {
+        for (unsigned i = 1; i < m_size; i++)
+          m_stack_start[i].ov.break_closure_cycles (frame);
+      }
+
+    if (m_lazy_data)
+      {
+        for (octave_value &ov : m_lazy_data->m_extra_slots)
+          ov.break_closure_cycles (frame);
+      }
+
+    if (m_access_link)
+      m_access_link->break_closure_cycles (frame);
+  }
+
   // To keep down the footprint of the frame some seldom used
   // variables are lazy initialized and stored in *m_lazy_data
   struct lazy_data_struct
@@ -2274,38 +2554,87 @@
                    std::size_t index,
                    const std::shared_ptr<stack_frame>& parent_link,
                    const std::shared_ptr<stack_frame>& static_link,
+                   const std::shared_ptr<stack_frame>& access_link,
+                   int nargout, int nargin)
+{
+  auto frame = create_bytecode (tw, fcn, vm, index, parent_link, static_link, nargout, nargin);
+  frame->m_access_link = access_link;
+
+  return frame;
+}
+
+std::shared_ptr<stack_frame> stack_frame::create_bytecode (
+                   tree_evaluator& tw,
+                   octave_user_code *fcn,
+                   vm &vm,
+                   std::size_t index,
+                   const std::shared_ptr<stack_frame>& parent_link,
+                   const std::shared_ptr<stack_frame>& static_link,
                    int nargout, int nargin)
 {
   // If we have any cached shared_ptr to empty bytecode_fcn_stack_frame objects
   // we use on of those
   if (vm.m_frame_ptr_cache.size ())
     {
-      std::shared_ptr<stack_frame> sp = std::move (vm.m_frame_ptr_cache.back ());
+      std::shared_ptr<stack_frame> new_frame = std::move (vm.m_frame_ptr_cache.back ());
       vm.m_frame_ptr_cache.pop_back ();
 
-      bytecode_fcn_stack_frame *p = static_cast<bytecode_fcn_stack_frame*> (sp.get ());
+      bytecode_fcn_stack_frame *p = static_cast<bytecode_fcn_stack_frame*> (new_frame.get ());
       // Most objects where cleared when the shared_ptr was put into the cache but call the
       // dtor anyways to be sure.
       p->~bytecode_fcn_stack_frame ();
       // Placement new into the storage managed by the shared_ptr
       new (p) bytecode_fcn_stack_frame (tw, fcn, index, parent_link, static_link, vm, nargout, nargin);
 
-      p->m_weak_ptr_to_self = sp;
-
-      return sp;
+      // The bytecode stackframe needs to know if it needs to save away
+      // all the stack variables. So it need to keep track of if it is saved
+      // somewhere outsite the VM
+      p->m_weak_ptr_to_self = new_frame;
+
+      // For nested frames we need to set the access link to the root function
+      if (fcn->is_nested_function ())
+        {
+          // If the parent have an access link, that should be the root function.
+          // E.g. if the current frame we are pushing is a nested nested frame.
+          //
+          // Otherwise, the parent_link is the root frame.
+          //
+          if (parent_link->m_access_link)
+            new_frame->m_access_link = parent_link->m_access_link;
+          else
+            new_frame->m_access_link = parent_link;
+        }
+
+      return new_frame;
     }
-
-  bytecode_fcn_stack_frame *new_frame_raw
-    = new bytecode_fcn_stack_frame (tw, fcn, index, parent_link, static_link,
-                                    vm, nargout, nargin);
-  std::shared_ptr<stack_frame> new_frame (new_frame_raw);
-
-  // The bytecode stackframe needs to know if it needs to save away
-  // all the stack variables. So it need to keep track of if it is saved
-  // somewhere outsite the VM
-  new_frame_raw->m_weak_ptr_to_self = new_frame;
-
-  return new_frame;
+  else
+    {
+      bytecode_fcn_stack_frame *new_frame_raw
+        = new bytecode_fcn_stack_frame (tw, fcn, index, parent_link, static_link,
+                                        vm, nargout, nargin);
+      std::shared_ptr<stack_frame> new_frame (new_frame_raw);
+
+      // The bytecode stackframe needs to know if it needs to save away
+      // all the stack variables. So it need to keep track of if it is saved
+      // somewhere outsite the VM
+      new_frame_raw->m_weak_ptr_to_self = new_frame;
+
+      // For nested frames we need to set the access link to the root function
+      if (fcn->is_nested_function ())
+        {
+          // If the parent have an access link, that should be the root function.
+          // E.g. if the current frame we are pushing is a nested nested frame.
+          //
+          // Otherwise, the parent_link is the root frame.
+          //
+          if (parent_link->m_access_link)
+            new_frame->m_access_link = parent_link->m_access_link;
+          else
+            new_frame->m_access_link = parent_link;
+        }
+
+      return new_frame;
+    }
 }
 
 // This function is only implemented and should only be called for
--- a/libinterp/corefcn/stack-frame.h	Thu Aug 10 17:07:19 2023 +0200
+++ b/libinterp/corefcn/stack-frame.h	Mon Sep 04 16:52:22 2023 +0200
@@ -195,6 +195,16 @@
 
   static std::shared_ptr<stack_frame>
   create_bytecode (tree_evaluator& tw,
+                   octave_user_code *fcn,
+                   vm &vm,
+                   std::size_t index,
+                   const std::shared_ptr<stack_frame>& parent_link,
+                   const std::shared_ptr<stack_frame>& static_link,
+                   const std::shared_ptr<stack_frame>& access_link,
+                   int nargout, int nargin);
+
+  static std::shared_ptr<stack_frame>
+  create_bytecode (tree_evaluator& tw,
                    octave_user_script *fcn,
                    vm &vm,
                    std::size_t index,
@@ -601,6 +611,7 @@
   virtual void vm_clear_for_cache () {}
   virtual void vm_enter_script () {}
   virtual void vm_exit_script () {}
+  virtual void vm_enter_nested () {}
 
 protected:
 
--- a/libinterp/octave-value/ov-fcn-handle.cc	Thu Aug 10 17:07:19 2023 +0200
+++ b/libinterp/octave-value/ov-fcn-handle.cc	Mon Sep 04 16:52:22 2023 +0200
@@ -2040,6 +2040,9 @@
 
   octave_user_function *oct_usr_fcn = m_fcn.user_function_value ();
 
+  if (octave::vm::maybe_compile_or_compiled (oct_usr_fcn))
+    return octave::vm::call (tw, nargout, args, oct_usr_fcn, m_stack_context);
+
   tw.push_stack_frame (oct_usr_fcn, m_stack_context);
 
   unwind_action act ([&tw] () { tw.pop_stack_frame (); });
@@ -2923,33 +2926,14 @@
 octave_value_list
 anonymous_fcn_handle::call (int nargout, const octave_value_list& args)
 {
-  bool is_compiled = false;
-
   tree_evaluator& tw = __get_evaluator__ ();
 
   octave_user_function *oct_usr_fcn = m_fcn.user_function_value ();
 
-  if (oct_usr_fcn)
-    {
-      is_compiled = oct_usr_fcn->is_compiled ();
-      if (octave::V__enable_vm_eval__ && !is_compiled && !oct_usr_fcn->m_compilation_failed)
-      {
-        try
-          {
-            octave::compile_anon_user_function (*oct_usr_fcn, false, m_local_vars);
-            is_compiled = true;
-          }
-        catch (std::exception &e)
-          {
-            warning ("Auto-compilation of anonymous function failed with message %s", e.what ());
-            oct_usr_fcn->m_compilation_failed = true;
-          }
-      }
-    }
-
-  // Bytecode functions push their own stack frames in the vm
-  if (!is_compiled)
-    tw.push_stack_frame (oct_usr_fcn, m_local_vars, m_stack_context);
+  if (octave::vm::maybe_compile_or_compiled (oct_usr_fcn, &m_local_vars))
+    return octave::vm::call (tw, nargout, args, oct_usr_fcn);
+
+  tw.push_stack_frame (oct_usr_fcn, m_local_vars, m_stack_context);
 
   unwind_action act ([&tw] () { tw.pop_stack_frame (); });
 
--- a/libinterp/octave-value/ov-fcn.cc	Thu Aug 10 17:07:19 2023 +0200
+++ b/libinterp/octave-value/ov-fcn.cc	Mon Sep 04 16:52:22 2023 +0200
@@ -59,30 +59,10 @@
                        const octave_value_list& args)
 {
   octave_user_function *usr = this->user_function_value(true);
+  if (octave::vm::maybe_compile_or_compiled (usr))
+    return octave::vm::call (tw, nargout, args, usr);
 
-  bool is_compiled = false;
-  if (usr)
-    {
-      is_compiled = usr->is_compiled ();
-      if (octave::V__enable_vm_eval__ && !is_compiled && !usr->m_compilation_failed)
-      {
-        try
-          {
-            octave::compile_user_function (*usr, false);
-            is_compiled = true;
-          }
-        catch (std::exception &e)
-          {
-            warning ("Compilation failed with message %s", e.what ());
-            usr->m_compilation_failed = true;
-          }
-      }
-    }
-
-  // Bytecode functions push their own stack frames in the vm
-
-  if (! usr || ! is_compiled)
-    tw.push_stack_frame (this);
+  tw.push_stack_frame (this);
 
   octave::unwind_action act ([&tw] () { tw.pop_stack_frame (); });
 
--- a/libinterp/octave-value/ov-ref.cc	Thu Aug 10 17:07:19 2023 +0200
+++ b/libinterp/octave-value/ov-ref.cc	Mon Sep 04 16:52:22 2023 +0200
@@ -45,6 +45,10 @@
                                      "local vm value reference",
                                      "local vm value reference");
 
+DEFINE_OV_TYPEID_FUNCTIONS_AND_DATA (octave_value_ref_ptr,
+                                     "local vm value pointer",
+                                     "local vm value pointer");
+
 void
 octave_value_ref::maybe_call_dtor ()
 {
@@ -144,3 +148,28 @@
 {
   m_frame->varref (m_sym) = val;
 }
+
+octave_value &
+octave_value_ref_ptr::ref ()
+{
+  if (m_pov->is_ref ())
+    return m_pov->ref_rep ()->ref ();
+  return *m_pov;
+}
+
+octave_value
+octave_value_ref_ptr::deref ()
+{
+  if (m_pov->is_ref ())
+    return m_pov->ref_rep ()->deref ();
+  return *m_pov;
+}
+
+void
+octave_value_ref_ptr::set_value (octave_value val)
+{
+  if (m_pov->is_ref ())
+    m_pov->ref_rep ()->set_value (val);
+  else
+    *m_pov = val;
+}
--- a/libinterp/octave-value/ov-ref.h	Thu Aug 10 17:07:19 2023 +0200
+++ b/libinterp/octave-value/ov-ref.h	Mon Sep 04 16:52:22 2023 +0200
@@ -32,6 +32,7 @@
 #include "ovl.h"
 #include "symscope.h"
 #include "symrec.h"
+#include "stack-frame.h"
 #include <string>
 #include <memory>
 
@@ -62,7 +63,10 @@
 
     virtual bool is_global_ref () const { return false; }
     virtual bool is_persistent_ref () const { return false; }
-    virtual bool is_vmlocal_ref () const { return true; }
+    virtual bool is_ptr_ref () const { return false; }
+    virtual bool is_local_ref () const { return false; }
+
+    virtual octave::stack_frame::scope_flags get_scope_flag () = 0;
 
     void maybe_call_dtor ();
     octave_value simple_subsasgn (char type, octave_value_list& idx, const octave_value& rhs);
@@ -87,6 +91,11 @@
 
     bool is_global_ref () const { return true; }
 
+    octave::stack_frame::scope_flags get_scope_flag ()
+    { 
+      return octave::stack_frame::scope_flags::GLOBAL; 
+    }
+
 private:
     std::string m_name;
 
@@ -108,6 +117,11 @@
 
     bool is_persistent_ref () const { return true; }
 
+    octave::stack_frame::scope_flags get_scope_flag ()
+    {
+      return octave::stack_frame::scope_flags::PERSISTENT;
+    }
+
 private:
     int m_offset;
     octave::symbol_scope m_scope;
@@ -128,7 +142,12 @@
     octave_value & ref ();
     void set_value (octave_value val);
 
-    bool is_vmlocal_ref () const { return true; }
+    octave::stack_frame::scope_flags get_scope_flag ()
+    {
+      return octave::stack_frame::scope_flags::LOCAL;
+    }
+
+    bool is_local_ref () const { return true; }
 
 private:
     octave::stack_frame *m_frame = nullptr;
@@ -137,4 +156,31 @@
     DECLARE_OV_TYPEID_FUNCTIONS_AND_DATA
 };
 
+class OCTINTERP_API
+octave_value_ref_ptr : public octave_value_ref
+{
+public:
+    octave_value_ref_ptr () = default;
+    ~octave_value_ref_ptr () = default;
+    octave_value_ref_ptr (octave_value *pov)
+        : m_pov (pov) { }
+
+    octave_value deref ();
+    octave_value & ref ();
+    void set_value (octave_value val);
+
+    octave::stack_frame::scope_flags get_scope_flag ()
+    {
+      if (m_pov->is_ref ())
+        return m_pov->ref_rep ()->get_scope_flag ();
+      return octave::stack_frame::scope_flags::LOCAL;
+    }
+
+    bool is_ptr_ref () const { return true; }
+private:
+    octave_value *m_pov;
+
+    DECLARE_OV_TYPEID_FUNCTIONS_AND_DATA
+};
+
 #endif
--- a/libinterp/octave-value/ov-usr-fcn.cc	Thu Aug 10 17:07:19 2023 +0200
+++ b/libinterp/octave-value/ov-usr-fcn.cc	Mon Sep 04 16:52:22 2023 +0200
@@ -50,6 +50,7 @@
 #include "pt-misc.h"
 #include "pt-pr-code.h"
 #include "pt-stmt.h"
+#include "pt-bytecode-vm.h"
 #include "pt-walk.h"
 #include "symtab.h"
 #include "interpreter-private.h"
@@ -197,28 +198,10 @@
 octave_user_script::call (octave::tree_evaluator& tw, int nargout,
                           const octave_value_list& args)
 {
-  bool is_compiled = false;
+  if (octave::vm::maybe_compile_or_compiled (this))
+    return octave::vm::call (tw, nargout, args, this);
 
-  is_compiled = this->is_compiled ();
-  if (octave::V__enable_vm_eval__ && !is_compiled && !m_compilation_failed)
-  {
-    try
-      {
-        octave::compile_user_function (*this, false);
-        is_compiled = true;
-      }
-    catch (std::exception &e)
-      {
-        warning ("Auto-compilation of %s failed with message %s", name().c_str (), e.what ());
-        this->m_compilation_failed = true;
-      }
-  }
-
-  // Bytecode functions push their own stack frames in the vm
-  if (!is_compiled)
-  {
-    tw.push_stack_frame (this);
-  }
+  tw.push_stack_frame (this);
 
   octave::unwind_action act ([&tw] () { tw.pop_stack_frame (); });
 
@@ -517,26 +500,10 @@
 octave_user_function::call (octave::tree_evaluator& tw, int nargout,
                             const octave_value_list& args)
 {
-  bool is_compiled = false;
+  if (octave::vm::maybe_compile_or_compiled (this))
+    return octave::vm::call (tw, nargout, args, this);
 
-  is_compiled = this->is_compiled ();
-  if (octave::V__enable_vm_eval__ && !is_compiled && !m_compilation_failed)
-  {
-    try
-      {
-        octave::compile_user_function (*this, false);
-        is_compiled = true;
-      }
-    catch (std::exception &e)
-      {
-        warning ("Auto-compilation of %s failed with message %s", name().c_str (), e.what ());
-        this->m_compilation_failed = true;
-      }
-  }
-
-  // Bytecode functions push their own stack frames in the vm
-  if (!is_compiled)
-    tw.push_stack_frame (this);
+  tw.push_stack_frame (this);
 
   octave::unwind_action act ([&tw] () { tw.pop_stack_frame (); });
 
--- a/libinterp/octave-value/ov.h	Thu Aug 10 17:07:19 2023 +0200
+++ b/libinterp/octave-value/ov.h	Mon Sep 04 16:52:22 2023 +0200
@@ -1548,6 +1548,7 @@
   friend class octave::vm;
   friend class octave::bytecode_fcn_stack_frame;
   friend class octave::scope_stack_frame;
+  friend class octave_value_ref_ptr;
 
   bool is_ref () const { return m_rep->is_ref (); }
 
--- a/libinterp/parse-tree/pt-bytecode-vm.cc	Thu Aug 10 17:07:19 2023 +0200
+++ b/libinterp/parse-tree/pt-bytecode-vm.cc	Mon Sep 04 16:52:22 2023 +0200
@@ -264,6 +264,7 @@
           PRINT_OP (PUSH_DBL_2);
           PRINT_OP (ENTER_SCRIPT_FRAME);
           PRINT_OP (EXIT_SCRIPT_FRAME);
+          PRINT_OP (ENTER_NESTED_FRAME);
 
           CASE_START (WIDE)
             wide_opext_active = true;
@@ -858,6 +859,7 @@
       &&ext_nargout,
       &&wordcmd_nx,
       &&anon_maybe_set_ignore_output,
+      &&enter_nested_frame,
     };
 
   if (OCTAVE_UNLIKELY (m_profiler_enabled))
@@ -897,8 +899,9 @@
 
   code = m_code;
   ip = code;
-
-  sp = bsp = rsp = m_stack;
+  m_ip = 0;
+
+  m_sp = m_bsp = m_rsp = sp = bsp = rsp = m_stack;
 
   // Read the meta data for constructing a stack frame.
   {
@@ -1735,19 +1738,15 @@
     else
       PANIC ("Invalid opcode");
 
-    // A global or persistent var should not be undefined but whatever check anyway
     bool ov_defined1 = ov.is_defined ();
-    if (is_ref && !ov_defined1)
-      {
-        (*sp++).ps = new std::string {name_data[slot]};
-        (*sp++).i = static_cast<int>(error_type::ID_UNDEFINED);
-        goto unwind;
-      }
 
     if (!ov_defined1 && ov.is_nil ())
       {
-        ov = bsp[slot].ov =
-          octave_value (new octave_fcn_cache (name_data[slot]));
+        ov = octave_value (new octave_fcn_cache (name_data[slot]));
+        if (bsp[slot].ov.is_ref ())
+          bsp[slot].ov.ref_rep ()->set_value (ov);
+        else
+          bsp[slot].ov = ov;
       }
 
     if (!ov_defined1 && ov.is_function_cache ())
@@ -1779,19 +1778,35 @@
         // TODO: Bytecode call
         if (fcn)
           {
-            try
+
+            if (fcn->is_compiled ())
               {
-                m_tw->set_active_bytecode_ip (ip - code);
-                octave_value_list ovl = fcn->call (*m_tw, nargout);
-
-                EXPAND_CSLIST_PUSH_N_OVL_ELEMENTS_TO_STACK (ovl, nargout);
+                octave_user_code *usr_fcn = static_cast<octave_user_code *> (fcn);
+
+                // Alot of code in this define
+                PUSH_OV (ov); // Calling convention anticipates object to call on the stack.
+                int n_args_on_stack = 0;
+                bool has_cs_list_arg = false;
+                MAKE_BYTECODE_CALL
+
+                // Now dispatch to first instruction in the
+                // called function
               }
-            CATCH_INTERRUPT_EXCEPTION
-            CATCH_INDEX_EXCEPTION
-            CATCH_EXECUTION_EXCEPTION
-            CATCH_BAD_ALLOC
-            CATCH_EXIT_EXCEPTION
-
+            else
+              {
+              try
+                {
+                  m_tw->set_active_bytecode_ip (ip - code);
+                  octave_value_list ovl = fcn->call (*m_tw, nargout);
+
+                  EXPAND_CSLIST_PUSH_N_OVL_ELEMENTS_TO_STACK (ovl, nargout);
+                }
+              CATCH_INTERRUPT_EXCEPTION
+              CATCH_INDEX_EXCEPTION
+              CATCH_EXECUTION_EXCEPTION
+              CATCH_BAD_ALLOC
+              CATCH_EXIT_EXCEPTION
+            }
           }
         else
           PUSH_OV (ov); // TODO: The walker does this. Sane?
@@ -2214,8 +2229,11 @@
           }
 
         // Put a function cache object in the slot and in the local ov
-        ov = bsp[slot].ov =
-          octave_value (new octave_fcn_cache (name_data[slot]));
+        ov = octave_value (new octave_fcn_cache (name_data[slot]));
+        if (bsp[slot].ov.is_ref ())
+          bsp[slot].ov.ref_rep ()->set_value (ov);
+        else
+          bsp[slot].ov = ov;
         goto querry_fcn_cache; // Jump into the if clause above
       }
   }
@@ -3870,8 +3888,11 @@
           }
 
         // Put a function cache object in the slot and in the local ov
-        ov = bsp[slot].ov =
-          octave_value (new octave_fcn_cache (name_data[slot]));
+        ov = octave_value (new octave_fcn_cache (name_data[slot]));
+        if (bsp[slot].ov.is_ref ())
+          bsp[slot].ov.ref_rep ()->set_value (ov);
+        else
+          bsp[slot].ov = ov;
         goto querry_fcn_cache2; // Jump into the if clause above
       }
   }
@@ -4217,8 +4238,11 @@
           }
 
         // Put a function cache object in the slot and in the local ov
-        ov = bsp[slot].ov =
-          octave_value (new octave_fcn_cache (name_data[slot]));
+        ov = octave_value (new octave_fcn_cache (name_data[slot]));
+        if (bsp[slot].ov.is_ref ())
+          bsp[slot].ov.ref_rep ()->set_value (ov);
+        else
+          bsp[slot].ov = ov;
         goto querry_fcn_cache3; // Jump into the if clause above
       }
   }
@@ -5521,8 +5545,11 @@
         // Put a function cache object in the slot and in the local ov
         // and jump into the if clause above to search for some function
         // to call.
-        ov = bsp[slot].ov =
-          octave_value (new octave_fcn_cache (name_data[slot]));
+        ov = octave_value (new octave_fcn_cache (name_data[slot]));
+        if (bsp[slot].ov.is_ref ())
+          bsp[slot].ov.ref_rep ()->set_value (ov);
+        else
+          bsp[slot].ov = ov;
         goto querry_fcn_cache_index_obj;
       }
   }
@@ -6161,6 +6188,13 @@
   }
   DISPATCH_1BYTEOP ();
 
+  enter_nested_frame:
+  {
+    auto fp = m_tw->get_current_stack_frame ();
+    fp->vm_enter_nested ();
+  }
+  DISPATCH_1BYTEOP ();
+
   __builtin_unreachable ();
 }
 
@@ -7291,6 +7325,103 @@
   vm.m_tw->set_lvalue_list (new_lval_list);
 }
 
+bool
+vm::maybe_compile_or_compiled (octave_user_code *fn, stack_frame::local_vars_map *locals)
+{
+  if (!fn)
+    return false;
+
+  if (fn->is_compiled ())
+    return true;
+
+  if (V__enable_vm_eval__ && !fn->m_compilation_failed)
+    {
+      try
+        {
+          if (fn->is_anonymous_function ())
+            octave::compile_anon_user_function (*fn, false, *locals);
+          else
+            octave::compile_user_function (*fn, false);
+
+          return true;
+        }
+      catch (std::exception &e)
+        {
+          warning ("Auto-compilation of %s failed with message %s", fn->name().c_str (), e.what ());
+          fn->m_compilation_failed = true;
+          return false;
+        }
+    }
+
+  return false;
+}
+
+octave_value_list
+vm::call (tree_evaluator& tw, int nargout, const octave_value_list& xargs,
+          octave_user_code *fn, std::shared_ptr<stack_frame> context)
+{
+  CHECK_PANIC (fn);
+  CHECK_PANIC (fn->is_compiled ());
+
+  bool call_script = fn->is_user_script ();
+
+  if (call_script && (xargs.length () != 0 || nargout != 0))
+    error ("invalid call to script %s", fn->name ().c_str ());
+
+  if (tw.m_call_stack.size () >= static_cast<std::size_t> (tw.m_max_recursion_depth))
+    error ("max_recursion_depth exceeded");
+
+  octave_value_list args (xargs);
+
+  bytecode &bc = fn->get_bytecode ();
+
+  vm vm (&tw, bc);
+
+  bool caller_is_bytecode = tw.get_current_stack_frame ()->is_bytecode_fcn_frame ();
+
+  // Pushes a bytecode stackframe. nargin is set inside the VM.
+  if (context)
+    tw.push_stack_frame (vm, fn, nargout, 0, context); // Closure context for nested frames
+  else
+    tw.push_stack_frame (vm, fn, nargout, 0);
+
+  // The arg names of root stackframe in VM need to be set here, unless the caller is bytecode.
+  // The caller can be bytecode if evalin("caller", ...) is used in some uncompiled function.
+  if (!caller_is_bytecode)
+    tw.set_auto_fcn_var (stack_frame::ARG_NAMES, Cell (xargs.name_tags ()));
+  if (!call_script)
+    {
+      Matrix ignored_outputs = tw.ignored_fcn_outputs ();
+      if (ignored_outputs.numel())
+        {
+          vm.caller_ignores_output ();
+          tw.set_auto_fcn_var (stack_frame::IGNORED, ignored_outputs);
+        }
+    }
+
+  octave_value_list ret;
+
+  try {
+    ret = vm.execute_code (args, nargout);
+  } catch (std::exception &e) {
+    if (vm.m_dbg_proper_return == false)
+      {
+        std::cout << e.what () << std::endl;
+        // TODO: Replace with panic when the VM almost works
+
+        // Some test code eats errors messages, so we print to stderr too.
+        fprintf (stderr, "VM error %d: " "Exception in %s escaped the VM\n", __LINE__, fn->name ().c_str());
+        error("VM error %d: " "Exception in %s escaped the VM\n", __LINE__, fn->name ().c_str());
+      }
+
+    tw.pop_stack_frame ();
+    throw;
+  }
+
+  tw.pop_stack_frame ();
+  return ret;
+}
+
 // Debugging functions to be called from gdb
 
 void
--- a/libinterp/parse-tree/pt-bytecode-vm.h	Thu Aug 10 17:07:19 2023 +0200
+++ b/libinterp/parse-tree/pt-bytecode-vm.h	Mon Sep 04 16:52:22 2023 +0200
@@ -591,6 +591,16 @@
 #  define OCTAVE_VM_EXECUTE_ATTR
 #endif
 
+  // Returns true if the VM should be used to call the function
+  static bool maybe_compile_or_compiled (octave_user_code *fn, stack_frame::local_vars_map *locals = nullptr);
+
+  // Allocate a VM and call the function
+  static octave_value_list call (tree_evaluator& tw,
+                                 int nargout,
+                                 const octave_value_list& args,
+                                 octave_user_code *fn,
+                                 std::shared_ptr<stack_frame> context = nullptr);
+
   octave_value_list execute_code (const octave_value_list &args, int root_nargout) OCTAVE_VM_EXECUTE_ATTR;
 
   octave_value find_fcn_for_cmd_call (std::string *name);
--- a/libinterp/parse-tree/pt-bytecode-walk.cc	Thu Aug 10 17:07:19 2023 +0200
+++ b/libinterp/parse-tree/pt-bytecode-walk.cc	Mon Sep 04 16:52:22 2023 +0200
@@ -37,6 +37,16 @@
 
 using namespace octave;
 
+#define ERR(msg) error("VM error %d: " msg, __LINE__)
+
+#define TODO(msg) error("VM error, Not done yet %d: " msg, __LINE__)
+
+#define CHECK(cond)                                                            \
+  do {                                                                         \
+    if (!(cond))                                                               \
+      ERR("Internal VM compiler consistency check failed, " #cond);             \
+  } while ((0))
+
 // Compiles an anonymous function.
 //
 // The compilation need to happen at runtime since the values of the variables are embedded into the bytecode
@@ -85,6 +95,52 @@
   }
 }
 
+void octave::compile_nested_user_function (octave_user_function &ufn, bool do_print, std::vector<octave_user_function *> v_parent_fns)
+{
+  try
+    {
+      if (ufn.is_classdef_constructor ())
+        error ("Classdef constructors are not supported by the VM yet"); // Needs special handling
+      CHECK (ufn.is_nested_function ());
+
+      // Begin with clearing the old bytecode, if any
+      ufn.clear_bytecode ();
+
+      bytecode_walker bw;
+      bw.m_n_nested_fn = v_parent_fns.size ();
+      bw.m_v_parent_fns = v_parent_fns;
+      bw.m_code.m_unwind_data.m_parent_id = v_parent_fns.back ()->get_bytecode ().m_unwind_data.m_id; // Direct parent (root or another nested function)
+      bw.m_code.m_unwind_data.m_matriarch_id = v_parent_fns.front ()->get_bytecode ().m_unwind_data.m_id; // Root parent (the function with all nested functions in it)
+
+      ufn.accept (bw);
+
+      if (do_print)
+        print_bytecode (bw.m_code);
+
+      bw.m_code.m_unwind_data.m_file = v_parent_fns.front ()->get_bytecode ().m_unwind_data.m_file;
+
+      ufn.set_bytecode (bw.m_code);
+
+      v_parent_fns.push_back (&ufn);
+
+      // Compile the subfunctions
+      auto subs = ufn.subfunctions ();
+      for (auto kv : subs)
+        {
+          octave_user_function *sub = kv.second.user_function_value ();
+
+          CHECK (sub->is_nested_function ());
+
+          compile_nested_user_function (*sub, do_print, v_parent_fns);
+        }
+    }
+  catch(...)
+  {
+    ufn.clear_bytecode ();
+    throw;
+  }
+}
+
 void octave::compile_user_function (octave_user_code &ufn, bool do_print)
 {
   try
@@ -111,7 +167,16 @@
       for (auto kv : subs)
         {
           octave_user_function *sub = kv.second.user_function_value ();
-          compile_user_function (*sub, do_print);
+
+          if (sub->is_nested_function ())
+            {
+              std::vector<octave_user_function *> v_parent_fns;
+              v_parent_fns.push_back (static_cast<octave_user_function *> (&ufn));
+              compile_nested_user_function (*sub, do_print, v_parent_fns);
+            }
+          else
+            compile_user_function (*sub, do_print);
+
           sub->get_bytecode ().m_unwind_data.m_file = ufn.fcn_file_name ();
         }
     }
@@ -236,7 +301,9 @@
 class collect_idnames_walker : tree_walker
 {
 public:
-  static std::vector<std::pair<std::string, int>> collect_id_names (tree_statement_list &l)
+  struct id_data { std::string m_name; std::size_t m_offset; std::size_t m_frame_offset; };
+
+  static std::vector<id_data> collect_id_names (tree_statement_list &l)
   {
     collect_idnames_walker walker;
 
@@ -249,7 +316,7 @@
     return walker.m_id_names_and_offset;
   }
 
-  static std::vector<std::pair<std::string, int>> collect_id_names (tree_expression &e)
+  static std::vector<id_data> collect_id_names (tree_expression &e)
   {
     collect_idnames_walker walker;
     e.accept (walker);
@@ -257,7 +324,7 @@
     return walker.m_id_names_and_offset;
   }
 
-  std::vector<std::pair<std::string, int>> m_id_names_and_offset;
+  std::vector<id_data> m_id_names_and_offset;
 
   void visit_identifier (tree_identifier &id)
   {
@@ -265,7 +332,7 @@
     if (name == "~") // We dont want this magic id
       return;
 
-    m_id_names_and_offset.push_back ({name, id.symbol ().data_offset ()});
+    m_id_names_and_offset.push_back ({name, id.symbol ().data_offset (), id.symbol ().frame_offset ()});
   }
 
   void visit_anon_fcn_handle (tree_anon_fcn_handle &)
@@ -283,16 +350,6 @@
   return tmp;
 }
 
-#define ERR(msg) error("VM error %d: " msg, __LINE__)
-
-#define TODO(msg) error("VM error, Not done yet %d: " msg, __LINE__)
-
-#define CHECK(cond)                                                            \
-  do {                                                                         \
-    if (!(cond))                                                               \
-      ERR("Internal VM compiler consistency check failed, " #cond);             \
-  } while ((0))
-
 #define PUSH_CODE(code_) do {\
     int code_check_s_ = static_cast<int> (code_); \
     unsigned char code_s_ = static_cast<unsigned char> (code_check_s_);     \
@@ -2120,6 +2177,8 @@
 {
   m_is_script = true;
 
+  m_code.m_unwind_data.m_external_frame_offset_to_internal.push_back ({});
+
   m_code.m_unwind_data.m_is_script = true;
   m_code.m_unwind_data.m_name = fcn.name ();
   m_code.m_unwind_data.m_file = fcn.fcn_file_name ();
@@ -2158,12 +2217,17 @@
 
       for (auto name_offset : v_names_offsets)
         {
-          std::string name = name_offset.first;
-          int frame_offset = name_offset.second;
-          add_id_to_table (name_offset.first);
+          std::string name = name_offset.m_name;
+          unsigned frame_offset = name_offset.m_frame_offset;
+          unsigned offset = name_offset.m_offset;
+
+          add_id_to_table (name);
           int slot = SLOT (name);
 
-          m_code.m_unwind_data.m_external_frame_offset_to_internal[frame_offset] = slot;
+          if (frame_offset >= m_code.m_unwind_data.m_external_frame_offset_to_internal.size ())
+            m_code.m_unwind_data.m_external_frame_offset_to_internal.resize (frame_offset + 1);
+
+          m_code.m_unwind_data.m_external_frame_offset_to_internal[frame_offset][offset] = slot;
         }
     }
 
@@ -2197,18 +2261,18 @@
               // Add the function name id to the table and add the correct external offset.
               // (The name might not be the call-name of the function.)
               int slot = add_id_to_table (name);
-              m_code.m_unwind_data.m_external_frame_offset_to_internal[offset] = slot;
+              m_code.m_unwind_data.m_external_frame_offset_to_internal[0][offset] = slot;
             }
           else
             continue;
         }
 
       if (name == "varargin")
-        m_code.m_unwind_data.m_external_frame_offset_to_internal[offset] = SLOT ("varargin");
+        m_code.m_unwind_data.m_external_frame_offset_to_internal[0][offset] = SLOT ("varargin");
       else if (name == "varargout")
-        m_code.m_unwind_data.m_external_frame_offset_to_internal[offset] = SLOT ("varargout");
+        m_code.m_unwind_data.m_external_frame_offset_to_internal[0][offset] = SLOT ("varargout");
       else if (name == "ans")
-        m_code.m_unwind_data.m_external_frame_offset_to_internal[offset] = SLOT ("ans");
+        m_code.m_unwind_data.m_external_frame_offset_to_internal[0][offset] = SLOT ("ans");
     }
 
   PUSH_CODE (INSTR::ENTER_SCRIPT_FRAME); // Special opcode to steal the "eval scopes" values
@@ -2243,23 +2307,33 @@
     }
 
   // Check that the mapping between external offsets and internal slots has no holes in it
-  int i = 0;
-  for (auto it : m_code.m_unwind_data.m_external_frame_offset_to_internal)
+  if (m_n_nested_fn == 0)
     {
-      int external_offset = it.first;
-      CHECK (external_offset == i);
-      i++;
+      int i = 0;
+      for (auto it : m_code.m_unwind_data.m_external_frame_offset_to_internal[0])
+        {
+          int external_offset = it.first;
+          CHECK (external_offset == i);
+          i++;
+        }
     }
 
   // The profiler needs to know these sizes when copying from pointers.
   m_code.m_unwind_data.m_code_size = m_code.m_code.size ();
   m_code.m_unwind_data.m_ids_size = m_code.m_ids.size ();
+
+  m_code.m_unwind_data.m_n_returns = 1; // Only %nargout
+  m_code.m_unwind_data.m_n_args = 0; // No args
+  m_code.m_unwind_data.m_n_locals = n_slots;
 }
 
 void
 bytecode_walker::
 visit_octave_user_function (octave_user_function& fcn)
 {
+  m_code.m_unwind_data.m_external_frame_offset_to_internal.push_back ({});
+  m_code.m_unwind_data.m_n_nested_fn = m_n_nested_fn;
+
   m_code.m_unwind_data.m_name = fcn.name ();
   m_code.m_unwind_data.m_file = fcn.fcn_file_name ();
   PUSH_DATA (fcn.name ());
@@ -2393,12 +2467,16 @@
 
       for (auto name_offset : v_names_offsets)
         {
-          std::string name = name_offset.first;
-          int frame_offset = name_offset.second;
-          add_id_to_table (name_offset.first);
+          std::string name = name_offset.m_name;
+          unsigned offset = name_offset.m_offset;
+          unsigned frame_offset = name_offset.m_frame_offset;
+          add_id_to_table (name);
           int slot = SLOT (name);
 
-          m_code.m_unwind_data.m_external_frame_offset_to_internal[frame_offset] = slot;
+          if (frame_offset >= m_code.m_unwind_data.m_external_frame_offset_to_internal.size ())
+            m_code.m_unwind_data.m_external_frame_offset_to_internal.resize (frame_offset + 1);
+
+          m_code.m_unwind_data.m_external_frame_offset_to_internal[frame_offset][offset] = slot;
         }
     }
   // We need the arguments and return id:s in the map too.
@@ -2408,9 +2486,9 @@
         {
           CHECK_NONNULL (*it);
           tree_identifier *id = (*it)->ident ();
-          int frame_offset = id->symbol ().data_offset ();
+          int offset = id->symbol ().data_offset ();
           int slot = SLOT (id->name ());
-          m_code.m_unwind_data.m_external_frame_offset_to_internal[frame_offset] = slot;
+          m_code.m_unwind_data.m_external_frame_offset_to_internal[0][offset] = slot;
 
           // If the parameter has an init expression e.g.
           // "function foo (a = sin (pi))"
@@ -2421,12 +2499,12 @@
               auto v_names_offsets = collect_idnames_walker::collect_id_names (*init_expr);
               for (auto name_offset : v_names_offsets)
                 {
-                  std::string name = name_offset.first;
-                  int frame_offset_i = name_offset.second;
-                  add_id_to_table (name_offset.first);
+                  std::string name = name_offset.m_name;
+                  int offset_i = name_offset.m_offset;
+                  add_id_to_table (name);
                   int slot_i = SLOT (name);
 
-                  m_code.m_unwind_data.m_external_frame_offset_to_internal[frame_offset_i] = slot_i;
+                  m_code.m_unwind_data.m_external_frame_offset_to_internal[0][offset_i] = slot_i;
                 }
             }
         }
@@ -2439,7 +2517,7 @@
           tree_identifier *id = (*it)->ident ();
           int frame_offset = id->symbol ().data_offset ();
           int slot = SLOT (name);
-          m_code.m_unwind_data.m_external_frame_offset_to_internal[frame_offset] = slot;
+          m_code.m_unwind_data.m_external_frame_offset_to_internal[0][frame_offset] = slot;
         }
     }
 
@@ -2464,7 +2542,13 @@
   // Note that the file 'bar.m' can have one function with the id name 'foo'
   // which will be added to the scope by the parser, but the function name
   // and thus call-name is 'bar'.
-  std::size_t idx_fn_name = n_returns + 1; // "+1" since 'ans' is always added first
+  std::size_t idx_fn_name;
+  if (m_n_nested_fn)
+    {
+      idx_fn_name = fcn.scope ().find_symbol (function_name).data_offset ();
+    }
+  else
+    idx_fn_name = n_returns + 1; // "+1" since 'ans' is always added first
 
   for (auto p : fcn.scope ().symbols ())
     {
@@ -2482,20 +2566,22 @@
               // Add the function name id to the table and add the correct external offset.
               // (The name might not be the call-name of the function.)
               int slot = add_id_to_table (name);
-              m_code.m_unwind_data.m_external_frame_offset_to_internal[offset] = slot;
+              m_code.m_unwind_data.m_external_frame_offset_to_internal[0][offset] = slot;
             }
           else
             continue;
         }
 
       if (name == "varargin")
-        m_code.m_unwind_data.m_external_frame_offset_to_internal[offset] = SLOT ("varargin");
+        m_code.m_unwind_data.m_external_frame_offset_to_internal[0][offset] = SLOT ("varargin");
       else if (name == "varargout")
-        m_code.m_unwind_data.m_external_frame_offset_to_internal[offset] = SLOT ("varargout");
+        m_code.m_unwind_data.m_external_frame_offset_to_internal[0][offset] = SLOT ("varargout");
       else if (name == "ans")
-        m_code.m_unwind_data.m_external_frame_offset_to_internal[offset] = SLOT ("ans");
+        m_code.m_unwind_data.m_external_frame_offset_to_internal[0][offset] = SLOT ("ans");
     }
 
+  CHECK (! (m_is_anon && m_n_nested_fn));
+
   // Add code to initialize variables in anonymous functions that took their value from
   // the parent scope.
   if (m_is_anon)
@@ -2521,6 +2607,75 @@
           PUSH_SLOT (slot);
         }
     }
+  if (m_n_nested_fn)
+    {
+      // So we are compiling a nested function. We need to put references to the proper parent stack
+      // from the nested functions stack for each shared variable. To do this we store which id:s in
+      // the nested stack that are shared with the parent stack in the unwind date.
+      // Then the opcode ENTER_NESTED_FRAME sets up the references at runtime.
+      //
+      // All variables of the parent is shared with the nested function, including 'ans'. The arguments
+      // and returns of the nested function are local to it. The rule is recursive for nested nested functions.
+      //
+      // A local slot number is only added once, and outer scopes take precedence.
+      CHECK (fcn.is_nested_function ());
+
+      std::vector<unwind_data::nested_var_offset> v_nested_vars;
+      std::set<int> set_locals_added;
+
+      for (unsigned i = 0; i < m_v_parent_fns.size (); i++)
+        {
+          unsigned depth = m_v_parent_fns.size () - i;
+
+          octave_user_function *parent_fn = m_v_parent_fns[i];
+
+          bytecode &bc = parent_fn->get_bytecode ();
+
+          for (unsigned parent_slot_idx = 1; parent_slot_idx < bc.m_ids.size (); parent_slot_idx++) // 1, for %nargout
+            {
+              std::string &parent_id_name = bc.m_ids[parent_slot_idx];
+
+              // Skip special id:s
+              if (parent_id_name.size ())
+                {
+                  switch (parent_id_name.front ())
+                    {
+                      case '%':
+                      case '#':
+                      case '!':
+                        continue;
+                      default:
+                        ;
+                    }
+                }
+
+              auto it = m_map_locals_to_slot.find (parent_id_name);
+              if (it == m_map_locals_to_slot.end ())
+                continue;
+
+              int local_slot_nr = it->second;
+
+              // The return values and arguments of the nested function are not shared with parents
+              if (local_slot_nr < 1 + n_returns + n_paras)
+                continue;
+
+              unwind_data::nested_var_offset data;
+              data.m_depth = depth;
+              data.m_slot_nested = local_slot_nr;
+              data.m_slot_parent = parent_slot_idx;
+
+               // Add only an local slot nr once to v_nested_vars
+              if (!set_locals_added.count (local_slot_nr))
+                {
+                  v_nested_vars.push_back (data);
+                  set_locals_added.insert (local_slot_nr);
+                }
+            }
+        }
+
+      m_code.m_unwind_data.m_v_nested_vars = std::move (v_nested_vars);
+      PUSH_CODE (INSTR::ENTER_NESTED_FRAME);
+    }
 
   // Add code to handle default arguments. If an argument is undefined or
   // "magic colon" it is to get its default value.
@@ -2607,17 +2762,24 @@
     }
 
   // Check that the mapping between external offsets and internal slots has no holes in it
-  int i = 0;
-  for (auto it : m_code.m_unwind_data.m_external_frame_offset_to_internal)
+  if (m_n_nested_fn == 0)
     {
-      int external_offset = it.first;
-      CHECK (external_offset == i);
-      i++;
+      int i = 0;
+      for (auto it : m_code.m_unwind_data.m_external_frame_offset_to_internal[0])
+        {
+          int external_offset = it.first;
+          CHECK (external_offset == i);
+          i++;
+        }
     }
 
   // The profiler needs to know these sizes when copying from pointers.
   m_code.m_unwind_data.m_code_size = m_code.m_code.size ();
   m_code.m_unwind_data.m_ids_size = m_code.m_ids.size ();
+
+  m_code.m_unwind_data.m_n_returns = n_returns;
+  m_code.m_unwind_data.m_n_args = n_paras;
+  m_code.m_unwind_data.m_n_locals = n_slots;
 }
 
 void
@@ -5310,3 +5472,5 @@
   PUSH_NEED_CONTINUE_TARGET (CODE_SIZE ());
   PUSH_CODE_SHORT (-1); // Placeholder
 }
+
+std::size_t unwind_data::m_id_cntr = 0;
--- a/libinterp/parse-tree/pt-bytecode-walk.h	Thu Aug 10 17:07:19 2023 +0200
+++ b/libinterp/parse-tree/pt-bytecode-walk.h	Mon Sep 04 16:52:22 2023 +0200
@@ -42,8 +42,9 @@
 
 namespace octave
 {
-  void compile_user_function (octave_user_code &fn, bool print);
-  void compile_anon_user_function (octave_user_code &fn, bool print, stack_frame::local_vars_map &locals);
+  void compile_user_function (octave_user_code &ufn, bool do_print);
+  void compile_nested_user_function (octave_user_function &ufn, bool do_print, std::vector<octave_user_function *> v_parent_fns);
+  void compile_anon_user_function (octave_user_code &ufn, bool do_print, stack_frame::local_vars_map &locals);
 
   // No separate visitor needed
   // Base classes only, so no need to include them.
@@ -155,6 +156,8 @@
     bool m_varargout = false;
     bool m_is_script = false;
     bool m_is_anon = false;
+    int m_n_nested_fn = 0;
+    std::vector<octave_user_function*> m_v_parent_fns; // Parent functions for nested functions
 
     std::vector<std::vector<int>> m_continue_target;
     std::vector<std::vector<int>> m_need_break_target;
--- a/libinterp/parse-tree/pt-bytecode.h	Thu Aug 10 17:07:19 2023 +0200
+++ b/libinterp/parse-tree/pt-bytecode.h	Mon Sep 04 16:52:22 2023 +0200
@@ -191,6 +191,7 @@
   EXT_NARGOUT,
   WORDCMD_NX,
   ANON_MAYBE_SET_IGNORE_OUTPUTS,
+  ENTER_NESTED_FRAME,
 };
 
 enum class unwind_entry_type
@@ -228,12 +229,26 @@
 
 struct unwind_data
 {
+  // Id to let nested children recognize their parents when they look for them on the stack.
+  std::size_t m_id = 0;
+  std::size_t m_parent_id = 0; // Id of parent, which could be the root function or another nested function.
+  std::size_t m_matriarch_id = 0; // Id of the root function, which have nested functions. Common for all nested functions in that function.
+  static std::size_t m_id_cntr;
+
+  unwind_data ()
+  {
+    m_id = ++m_id_cntr;
+  }
+
   std::vector<unwind_entry> m_unwind_entries;
   std::vector<loc_entry> m_loc_entry;
   std::map<int, int> m_slot_to_persistent_slot;
   std::map<int, tree*> m_ip_to_tree;
   std::vector<arg_name_entry> m_argname_entries;
-  std::map<int,int> m_external_frame_offset_to_internal;
+  std::vector<std::map<int,int>> m_external_frame_offset_to_internal;
+
+  struct nested_var_offset { int m_depth; int m_slot_parent; int m_slot_nested; };
+  std::vector<nested_var_offset> m_v_nested_vars;
 
   std::string m_name;
   std::string m_file;
@@ -243,6 +258,15 @@
 
   bool m_is_script = false;
   bool m_is_anon = false;
+  int m_n_nested_fn = 0;
+
+  // Note:
+  //  n locals includes n args and n returns.
+  //  n returns and n locals are not negative for varargout and varargin.
+  //  %nargout is included in the counts.
+  int m_n_returns;
+  int m_n_args;
+  int m_n_locals;
 };
 
 struct bytecode
--- a/libinterp/parse-tree/pt-eval.cc	Thu Aug 10 17:07:19 2023 +0200
+++ b/libinterp/parse-tree/pt-eval.cc	Mon Sep 04 16:52:22 2023 +0200
@@ -2489,6 +2489,12 @@
     m_call_stack.push (vm, static_cast<octave_user_script*> (fcn), nargout, nargin);
 }
 
+void tree_evaluator::push_stack_frame (vm &vm, octave_user_code *fcn, int nargout, int nargin,
+                                       const std::shared_ptr<stack_frame>& closure_frames)
+{
+  CHECK_PANIC (fcn->is_user_function ());
+  m_call_stack.push (vm, static_cast<octave_user_function*> (fcn), nargout, nargin, closure_frames);
+}
 
 void tree_evaluator::pop_stack_frame ()
 {
@@ -3477,56 +3483,24 @@
   if (m_call_stack.size () >= static_cast<std::size_t> (m_max_recursion_depth))
     error ("max_recursion_depth exceeded");
 
-  // Check if it has been compiled and execute the bytecode if so
-  if (user_script.is_compiled ())
-    {
-      bytecode &bc = user_script.get_bytecode ();
-
-      vm vm (this, bc);
-
-      // Pushes a bytecode stackframe. nargin is set inside the VM.
-      push_stack_frame (vm, &user_script, 0, 0);
-
-      octave_value_list ret;
-
-      try {
-        ret = vm.execute_code (args, 0);
-      } catch (std::exception &e) {
-        if (vm.m_dbg_proper_return == false)
-          {
-            std::cout << e.what () << std::endl;
-            // TODO: Replace with panic when the VM almost works
-
-            // Some test code eats errors messages, so we print to stderr too.
-            fprintf (stderr, "VM error %d: " "Exception in script %s escaped the VM\n", __LINE__, user_script.name ().c_str());
-            error("VM error %d: " "Exception in script %s escaped the VM\n", __LINE__, user_script.name ().c_str());
-          }
-        throw;
-      }
-
-      return ret;
-    }
-  else
-    {
-      unwind_protect_var<stmt_list_type> upv (m_statement_context, SC_SCRIPT);
-
-      profiler::enter<octave_user_script> block (m_profiler, user_script);
-
-      if (echo ())
-        push_echo_state (tree_evaluator::ECHO_SCRIPTS, file_name);
-
-      // FIXME: Should we be using tree_evaluator::eval here?
-
-      cmd_list->accept (*this);
-
-      if (m_returning)
-        m_returning = 0;
-
-      if (m_breaking)
-        m_breaking--;
-
-      return retval;
-    }
+  unwind_protect_var<stmt_list_type> upv (m_statement_context, SC_SCRIPT);
+
+  profiler::enter<octave_user_script> block (m_profiler, user_script);
+
+  if (echo ())
+    push_echo_state (tree_evaluator::ECHO_SCRIPTS, file_name);
+
+  // FIXME: Should we be using tree_evaluator::eval here?
+
+  cmd_list->accept (*this);
+
+  if (m_returning)
+    m_returning = 0;
+
+  if (m_breaking)
+    m_breaking--;
+
+  return retval;
 }
 
 void
@@ -3551,48 +3525,6 @@
   // FIXME: this probably shouldn't be a double-precision matrix.
   Matrix ignored_outputs = ignored_fcn_outputs ();
 
-  // Check if it has been compiled and execute the bytecode if so
-  if (user_function.is_compiled ())
-    {
-      bytecode &bc = user_function.get_bytecode ();
-
-      vm vm (this, bc);
-
-      bool caller_is_bytecode = get_current_stack_frame ()->is_bytecode_fcn_frame ();
-
-      // Pushes a bytecode stackframe. nargin is set inside the VM.
-      push_stack_frame (vm, &user_function, nargout, 0);
-
-      // The arg names of root stackframe in VM need to be set here, unless the caller is bytecode.
-      // The caller can be bytecode if evalin("caller", ...) is used in some uncompiled function.
-      if (!caller_is_bytecode)
-        set_auto_fcn_var (stack_frame::ARG_NAMES, Cell (xargs.name_tags ()));
-      if (ignored_outputs.numel())
-        {
-          vm.caller_ignores_output ();
-          set_auto_fcn_var (stack_frame::IGNORED, ignored_outputs);
-        }
-
-      octave_value_list ret;
-
-      try {
-        ret = vm.execute_code (args, nargout);
-      } catch (std::exception &e) {
-        if (vm.m_dbg_proper_return == false)
-          {
-            std::cout << e.what () << std::endl;
-            // TODO: Replace with panic when the VM almost works
-
-            // Some test code eats errors messages, so we print to stderr too.
-            fprintf (stderr, "VM error %d: " "Exception in function %s escaped the VM\n", __LINE__, user_function.name ().c_str());
-            error("VM error %d: " "Exception in function %s escaped the VM\n", __LINE__, user_function.name ().c_str());
-          }
-        throw;
-      }
-
-      return ret;
-    }
-
   octave_value_list ret_args;
 
   int nargin = args.length ();
--- a/libinterp/parse-tree/pt-eval.h	Thu Aug 10 17:07:19 2023 +0200
+++ b/libinterp/parse-tree/pt-eval.h	Mon Sep 04 16:52:22 2023 +0200
@@ -447,6 +447,8 @@
 
   void push_stack_frame (vm &vm, octave_user_code *fcn, int nargout, int nargin);
 
+  void push_stack_frame (vm &vm, octave_user_code *fcn, int nargout, int nargin, const std::shared_ptr<stack_frame>& closure_frames);
+
   void pop_stack_frame ();
 
   std::shared_ptr<stack_frame> pop_return_stack_frame ();
--- a/test/compile/bytecode.tst	Thu Aug 10 17:07:19 2023 +0200
+++ b/test/compile/bytecode.tst	Mon Sep 04 16:52:22 2023 +0200
@@ -679,3 +679,24 @@
 %! assert (__compile__ ("bytecode_scripts"));
 %! bytecode_scripts;
 %! assert (__prog_output_assert__ (key));
+
+## Test nested functions.
+%!test
+%! global cdef_bar_cnt
+%! cdef_bar_cnt = 0;
+%!
+%! __enable_vm_eval__ (0, "local");
+%! clear all
+%! __compile__ bytecode_nested clear;
+%! % These tests uses asserts in themself
+%! bytecode_nested;
+%!
+%! cdef_bar_cnt = 0;
+%!
+%! __enable_vm_eval__ (1, "local");
+%! assert (__compile__ ("bytecode_nested"));
+%! bytecode_nested;
+%!
+%! assert (cdef_bar_cnt == 0);
+%! clear -global cdef_bar_alive_objs cdef_bar_cnt glb_d glb_e glb_f
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/compile/bytecode_nested.m	Mon Sep 04 16:52:22 2023 +0200
@@ -0,0 +1,372 @@
+function bytecode_nested ()
+
+  %% Empty function
+  a = 1; b = 1; c = 1;
+  function nested1
+  end
+
+  nested1
+  nested1 ();
+
+  %% Changes value of a
+  function nested2 ()
+    assert (a == 1);
+    a = 111;
+    assert (a == 111);
+  end
+
+  nested2 ();
+  assert (a == 111);
+
+  %% Changes value of b
+  function nested3 (a)
+    assert (a == 2);
+    a = 222; % Does not update a in parent
+    b = 111;
+    assert (a == 222);
+  end
+
+  a = 1; b = 1; c = 1;
+  nested3 (2);
+  assert (a == 1); % a still same, since argument in nested3
+  assert (b == 111);
+
+  %% a, b and c are arguments and return values in nested4
+  %% and do not change in the outer frame
+  function c = nested4 (a, b)
+    assert (a == 3)
+    assert (b == 4);
+    a = 0;
+    b = 0;
+    c = 2;
+  end
+
+  a = 1; b = 1; c = 1;
+  assert (nested4 (3, 4) == 2);
+  assert (a == 1);
+  assert (b == 1);
+  assert (c == 1);
+
+  %% Sets local variable that should not leak to this frame
+  a = 1; b = 1; c = 1;
+  nested5;
+  assert (! exist ('d5'))
+
+  function nested5
+    d5 = 3;
+    assert (d5 == 3);
+  end
+
+  %% Calls empty nested function nested61
+  function nested6
+    function nested61
+    end
+  end
+
+  nested6;
+  nested6 ();
+
+  %% Nested function nested71
+  function nested7
+    a = 2;
+    d7 = 2;
+    function nested71
+      assert (a == 2)
+      assert (d7 == 2)
+      a = 3;
+      d7 = 3;
+    end
+
+    nested71;
+    assert (a == 3)
+    assert (d7 == 3)
+  end
+
+  a = 1; b = 1; c = 1;
+  nested7;
+  assert (a == 3)
+
+  %% Nested with args
+  function b = nested8(a)
+    assert (a == 2)
+    b = 2;
+
+    % Args and returns are not shared
+    function b = nested81(a)
+      assert (a == 3)
+      b = 3;
+      assert (b == 3)
+    end
+
+    nested81 (3);
+    assert (a == 2)
+    assert (b == 2)
+  end
+
+  a = 1; b = 1; c = 1;
+  nested8 (2);
+  assert (a == 1)
+  assert (b == 1)
+
+  %% Recursive nested function
+  function b = nested9 (a)
+    d9 = 0; % d9 is not shared with recursive calls
+    assert (c == a + 1)
+    c = a;
+
+    a_cpy = a; % Test that argument are not changed by children
+
+    if a == 0
+      b = 3;
+      d9 = 3; % d9 does not change in parent frames
+      return;
+    end
+
+    b = 0;
+
+    b_tmp = nested9 (a - 1);
+    assert (b == 0)
+    b = b_tmp;
+
+    assert (d9 == 0)
+    assert (b == 3)
+    assert (a == a_cpy)
+  end
+
+  a = 1; b = 1;
+  c = 9;
+  ret = nested9 (8);
+  assert (a == 1)
+  assert (b == 1)
+  assert (c == 0)
+  assert (ret == 3)
+
+  %% Call siblings
+  function b = nested10 (d10, e10=true)
+    a = 1; b = 1; c = 1;
+    nested7;
+    assert (a == 3)
+
+    a = 1; b = 1;
+    c = 9;
+    ret = nested9 (8);
+    assert (a == 1)
+    assert (b == 1)
+    assert (c == 0)
+    assert (ret == 3)
+
+    if ! d10
+      return;
+    end
+
+    nested10 (d10 - 1, e10); % Test siblings from recursive call
+
+    if e10
+      nested11 (); % Calls nested10
+    end
+  end
+
+  function nested11
+    nested10(2, false);
+
+    a = 1; b = 1; c = 1;
+    assert (nested4 (3, 4) == 2);
+    assert (a == 1);
+    assert (b == 1);
+    assert (c == 1);
+  end
+
+  a = 1; b = 1; c = 1;
+  nested10 (1);
+
+  %% Test globals
+
+  function nested12
+    assert (isglobal ("glb_d"))
+    % Note: If a global is not added to the frame of nested12,
+    % is is not marked as global.
+    assert (!isglobal ("glb_e")) % Not a global, since not added to nested12's frame
+    assert (glb_d == 3)
+    glb_d = 4;
+
+    eval ("glb_f = 24;"); % glb_f on dynamic frame in nested12
+
+    function nested12_1
+      assert (isglobal ("glb_d"))
+      assert (isglobal ("glb_e"))
+      assert (glb_d == 4)
+      assert (glb_e == 13)
+      eval ("assert (glb_f == 24)");
+      glb_d = 5;
+      glb_e = 14;
+    end
+
+    nested12_1;
+
+    nested_sibling13;
+  end
+
+  function nested_sibling13
+    assert (!isglobal ("glb_d"))
+    assert (isglobal ("glb_e"))
+    assert (glb_e == 14)
+    glb_e = 15;
+  end
+
+  global glb_d;
+  global glb_e;
+  global glb_f;
+
+  glb_d = 3;
+  glb_e = 13;
+  glb_f = 23;
+
+  nested12;
+  assert (glb_d == 5)
+  assert (glb_e == 15)
+  assert (glb_f == 24)
+
+  %% Can't add dynamic variables
+  function nested14
+    eval ("a14 = 3;")
+  end
+
+  try
+    nested14;
+  catch e
+    assert (regexp (e.message, "can not add variable"));
+  end
+
+  try
+    eval ("aaa = 3;") % Can't add dynamic variable in root either
+  catch e
+    assert (regexp (e.message, "can not add variable"));
+  end
+
+  %% evalin
+  a = 1; b = 1; c = 1;
+  function nested15
+    evalin ("caller", "assert (b == 3);");
+    a15 = 15;
+
+    function nested15_1 (a15)
+      evalin ("caller", "assert (a15 == 15);");
+    end
+    nested15_1 (10);
+  end
+
+  b = 3;
+  nested15;
+
+  %% nargout, isargout
+  a = 1; b = 1; c = 1;
+  function [a, b, c] = nested16 ()
+    function [a, b, c] = nested16_1 ()
+      a = nargout;
+      b = 2;
+      c = 3;
+    end
+    a = nargout;
+    b = 2;
+    c = 3;
+
+    glb_d = [isargout(1), isargout(2), isargout(3)];
+
+    [a2, b2, c2] = nested16_1 ();
+    assert (a2 == 3);
+    [a2, b2] = nested16_1 ();
+    assert (a2 == 2);
+    [a2] = nested16_1 ();
+    assert (a2 == 1);
+  end
+
+  [a, b, c] = nested16 ();
+  assert (a == 3);
+  assert (glb_d == [1 1 1]) % isargout stored in glb_d
+  [a, b] = nested16 ();
+  assert (a == 2);
+  assert (glb_d == [1 1 0])
+  [a] = nested16 ();
+  assert (a == 1);
+  assert (glb_d == [1 0 0])
+  [~] = nested16 ();
+  assert (glb_d == [0 0 0])
+  [~, b] = nested16 ();
+  assert (b == 2)
+  assert (glb_d == [0 1 0])
+  [~, b, ~] = nested16 ();
+  assert (b == 2)
+  assert (glb_d == [0 1 0])
+  [~, b, c] = nested16 ();
+  assert (b == 2)
+  assert (c == 3)
+  assert (glb_d == [0 1 1])
+
+  %% Call handle to nested function 1
+  function ret = nested17
+    ret = b;
+  end
+
+  b = 3;
+
+  h1 = @nested17;
+
+  assert (call_handle1 (h1) == 3);
+  assert (h1 () == 3)
+  b = 4;
+  assert (call_handle1 (h1) == 4);
+
+  %% Changes value in decoupled nested frame
+  h2 = sub_returns_nested_fn;
+  h3 = sub_returns_nested_fn;
+
+  assert (h2() == 33)
+  assert (h2() == 34)
+  assert (call_handle1(h2) == 35)
+  assert (h3() == 33)
+
+  c1 = cdef_bar ("1");
+
+  % Two levels of nested nesting
+  h4 = sub_returns_nested_fn2;
+  assert (h4 () == 1)
+  assert (h4 () == 2)
+end
+
+function subby
+end
+
+function a = call_handle1 (h)
+  a = h ();
+end
+
+function h = sub_returns_nested_fn ()
+  a = 33;
+  c2 = cdef_bar ("2");
+  function ret = sub_nested1
+    ret = a;
+    a++;
+    c3 = cdef_bar ("3");
+  end
+
+  h = @sub_nested1;
+end
+
+function h1 = sub_returns_nested_fn2
+  c2 = cdef_bar ("4");
+
+  function h2 = nested_fn1
+    a = 1;
+    c3 = cdef_bar ("5");
+
+    function ret = nested_fn2
+      c4 = cdef_bar ("6");
+      ret = a;
+      a++;
+    end
+
+    h2 = @nested_fn2;
+  end
+
+  h1 = nested_fn1 ();
+end
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/compile/cdef_bar.m	Mon Sep 04 16:52:22 2023 +0200
@@ -0,0 +1,52 @@
+% classdef that keeps track of alive objects of itself too be able
+% to ensure all are destroyed.
+%
+% Inspect cdef_bar_alive_objs to see which object with what message
+% that is alive during debugging.
+
+classdef cdef_bar < handle
+  properties
+    msg = "";
+  end
+  methods
+    function f = cdef_bar(msg = "")
+        global cdef_bar_cnt = 0;
+        global cdef_bar_alive_objs = struct;
+        f.msg = msg;
+        cdef_bar_cnt++;
+
+        if isfield (cdef_bar_alive_objs, msg)
+          entry = cdef_bar_alive_objs.(msg);
+          entry.cnt++;
+          cdef_bar_alive_objs.(msg) = entry;
+        else
+          entry = struct;
+          entry.cnt = 1;
+          cdef_bar_alive_objs.(msg) = entry;
+        end
+
+        %printf ("ctored %s cnt=%d\n", msg, cdef_bar_cnt);
+    end
+
+    function delete (self)
+      global cdef_bar_cnt = 0;
+      global cdef_bar_alive_objs = struct;
+      cdef_bar_cnt--;
+
+      if isfield (cdef_bar_alive_objs, self.msg)
+        entry = cdef_bar_alive_objs.(self.msg);
+        entry.cnt--;
+
+        if entry.cnt
+          cdef_bar_alive_objs.(self.msg) = entry;
+        else
+          cdef_bar_alive_objs = rmfield (cdef_bar_alive_objs, self.msg);
+        end
+      else
+        printf ("Unexpected missing alive objects entry for cdef_bar in cdef_bar.m")
+      end
+
+      %printf ("dtored %s cnt=%d\n", self.msg, cdef_bar_cnt);
+    endfunction
+  endmethods
+end
\ No newline at end of file
--- a/test/compile/module.mk	Thu Aug 10 17:07:19 2023 +0200
+++ b/test/compile/module.mk	Mon Sep 04 16:52:22 2023 +0200
@@ -21,6 +21,7 @@
   %reldir%/bytecode_matrix.m \
   %reldir%/bytecode_misc.m \
   %reldir%/bytecode_multi_assign.m \
+  %reldir%/bytecode_nested.m \
   %reldir%/bytecode_persistant.m \
   %reldir%/bytecode_range.m \
   %reldir%/bytecode_return.m \
@@ -36,6 +37,7 @@
   %reldir%/bytecode_varargout.m \
   %reldir%/bytecode_while.m \
   %reldir%/bytecode_wordlistcmd.m \
+  %reldir%/cdef_bar.m \
   %reldir%/cdef_foo.m \
   %reldir%/inputname_args.m \
   %reldir%/just_call_handle_with_arg.m \