# HG changeset patch # User Petter T. # Date 1693839142 -7200 # Mon Sep 04 16:52:22 2023 +0200 # Node ID 40f1294e80f1f5877c06731545c51c7b202dfd73 # Parent 7d924d061e9df5bda12933bc8864849550a147cc 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 diff -r 7d924d061e9d -r 40f1294e80f1 libinterp/corefcn/call-stack.cc --- 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& closure_frames) +{ + std::size_t new_frame_idx; + std::shared_ptr parent_link; + std::shared_ptr static_link; + + get_new_frame_index_and_links (new_frame_idx, parent_link, static_link); + + std::shared_ptr 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; diff -r 7d924d061e9d -r 40f1294e80f1 libinterp/corefcn/call-stack.h --- 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& closure_frames); + void set_location (int l, int c) { if (! m_cs.empty ()) diff -r 7d924d061e9d -r 40f1294e80f1 libinterp/corefcn/stack-frame.cc --- 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 (local_offset))); + + size_t frame_offset; + size_t offset; + + local_to_external_offset (static_cast (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 (m_code[0])); int n_args = abs (static_cast (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 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 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 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 (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 (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 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 (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 &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& parent_link, const std::shared_ptr& static_link, + const std::shared_ptr& 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::create_bytecode ( + tree_evaluator& tw, + octave_user_code *fcn, + vm &vm, + std::size_t index, + const std::shared_ptr& parent_link, + const std::shared_ptr& 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 sp = std::move (vm.m_frame_ptr_cache.back ()); + std::shared_ptr new_frame = std::move (vm.m_frame_ptr_cache.back ()); vm.m_frame_ptr_cache.pop_back (); - bytecode_fcn_stack_frame *p = static_cast (sp.get ()); + bytecode_fcn_stack_frame *p = static_cast (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 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 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 diff -r 7d924d061e9d -r 40f1294e80f1 libinterp/corefcn/stack-frame.h --- 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 create_bytecode (tree_evaluator& tw, + octave_user_code *fcn, + vm &vm, + std::size_t index, + const std::shared_ptr& parent_link, + const std::shared_ptr& static_link, + const std::shared_ptr& access_link, + int nargout, int nargin); + + static std::shared_ptr + create_bytecode (tree_evaluator& tw, octave_user_script *fcn, vm &vm, std::size_t index, @@ -621,6 +631,7 @@ virtual void vm_clear_for_cache () {} virtual void vm_enter_script () {} virtual void vm_exit_script () {} + virtual void vm_enter_nested () {} protected: diff -r 7d924d061e9d -r 40f1294e80f1 libinterp/octave-value/ov-fcn-handle.cc --- 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 @@ -2052,6 +2052,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 (); }); @@ -2935,33 +2938,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 (); }); diff -r 7d924d061e9d -r 40f1294e80f1 libinterp/octave-value/ov-fcn.cc --- 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 (); }); diff -r 7d924d061e9d -r 40f1294e80f1 libinterp/octave-value/ov-ref.cc --- 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; +} diff -r 7d924d061e9d -r 40f1294e80f1 libinterp/octave-value/ov-ref.h --- 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 #include @@ -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 diff -r 7d924d061e9d -r 40f1294e80f1 libinterp/octave-value/ov-usr-fcn.cc --- 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 (); }); diff -r 7d924d061e9d -r 40f1294e80f1 libinterp/octave-value/ov.h --- 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 (); } diff -r 7d924d061e9d -r 40f1294e80f1 libinterp/parse-tree/pt-bytecode-vm.cc --- 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(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 (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 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 (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 diff -r 7d924d061e9d -r 40f1294e80f1 libinterp/parse-tree/pt-bytecode-vm.h --- 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 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); diff -r 7d924d061e9d -r 40f1294e80f1 libinterp/parse-tree/pt-bytecode-walk.cc --- 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 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 v_parent_fns; + v_parent_fns.push_back (static_cast (&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> 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 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> collect_id_names (tree_expression &e) + static std::vector 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> m_id_names_and_offset; + std::vector 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 (code_); \ unsigned char code_s_ = static_cast (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 v_nested_vars; + std::set 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; diff -r 7d924d061e9d -r 40f1294e80f1 libinterp/parse-tree/pt-bytecode-walk.h --- 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 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 m_v_parent_fns; // Parent functions for nested functions std::vector> m_continue_target; std::vector> m_need_break_target; diff -r 7d924d061e9d -r 40f1294e80f1 libinterp/parse-tree/pt-bytecode.h --- 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 m_unwind_entries; std::vector m_loc_entry; std::map m_slot_to_persistent_slot; std::map m_ip_to_tree; std::vector m_argname_entries; - std::map m_external_frame_offset_to_internal; + std::vector> m_external_frame_offset_to_internal; + + struct nested_var_offset { int m_depth; int m_slot_parent; int m_slot_nested; }; + std::vector 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 diff -r 7d924d061e9d -r 40f1294e80f1 libinterp/parse-tree/pt-eval.cc --- 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 (fcn), nargout, nargin); } +void tree_evaluator::push_stack_frame (vm &vm, octave_user_code *fcn, int nargout, int nargin, + const std::shared_ptr& closure_frames) +{ + CHECK_PANIC (fcn->is_user_function ()); + m_call_stack.push (vm, static_cast (fcn), nargout, nargin, closure_frames); +} void tree_evaluator::pop_stack_frame () { @@ -3477,56 +3483,24 @@ if (m_call_stack.size () >= static_cast (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 upv (m_statement_context, SC_SCRIPT); - - profiler::enter 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 upv (m_statement_context, SC_SCRIPT); + + profiler::enter 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 (); diff -r 7d924d061e9d -r 40f1294e80f1 libinterp/parse-tree/pt-eval.h --- 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& closure_frames); + void pop_stack_frame (); std::shared_ptr pop_return_stack_frame (); diff -r 7d924d061e9d -r 40f1294e80f1 test/compile/bytecode.tst --- 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 + diff -r 7d924d061e9d -r 40f1294e80f1 test/compile/bytecode_nested.m --- /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 diff -r 7d924d061e9d -r 40f1294e80f1 test/compile/cdef_bar.m --- /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 diff -r 7d924d061e9d -r 40f1294e80f1 test/compile/module.mk --- 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 \