Mercurial > octave
changeset 32370:9155a67023bc
VM Support globals in scripts properly (bug #64705)
Fix globalization and deglobalization in scripts with the same top
scope.
* test/compile/bytecode.tst: Update tests
* test/compile/module.mk: Add new files
* test/compile/bytecode_script_topscope_setup.m: New
* test/compile/bytecode_script_topscope_assert.m: New
* test/compile/bytecode_script_topscope.m: New
* libinterp/corefcn/compile.cc: Support clearing scripts with __vm_compile__
* libinterp/corefcn/stack-frame.cc: Reorganize bytecode dynamic
stackframe to support scripts.
* libinterp/octave-value/ov-ref.cc: New function to notify globalness change
* libinterp/octave-value/ov-ref.h:
* libinterp/octave-value/ov-usr-fcn.cc: Limit script vm execution to top or vm frames
* libinterp/octave-value/ov.h: base_value_stack_frame friend to access is_ref
* libinterp/parse-tree/pt-bytecode-vm.cc: Remove inaccurate comment
* libinterp/parse-tree/pt-bytecode-walk.cc: Add function name to scope in
cleaner way. Set m_n_orig_scope_size.
* libinterp/parse-tree/pt-bytecode.h: Field m_n_orig_scope_size for original
amount of symbols in scope
author | Petter T. |
---|---|
date | Fri, 22 Sep 2023 15:46:36 +0200 |
parents | e0a6dd4f6fef |
children | ff0859c6f361 |
files | libinterp/corefcn/compile.cc libinterp/corefcn/stack-frame.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-walk.cc libinterp/parse-tree/pt-bytecode.h test/compile/bytecode.tst test/compile/bytecode_script_topscope.m test/compile/bytecode_script_topscope_assert.m test/compile/bytecode_script_topscope_setup.m test/compile/module.mk |
diffstat | 14 files changed, 784 insertions(+), 316 deletions(-) [+] |
line wrap: on
line diff
--- a/libinterp/corefcn/compile.cc Tue Oct 03 14:45:22 2023 +0200 +++ b/libinterp/corefcn/compile.cc Fri Sep 22 15:46:36 2023 +0200 @@ -380,7 +380,7 @@ error ("Function not defined: %s", name.c_str ()); } - octave_user_function *ufn = ov.user_function_value (); + octave_user_code *ufn = ov.user_code_value (); if (!ufn || !ufn->is_user_function ()) {
--- a/libinterp/corefcn/stack-frame.cc Tue Oct 03 14:45:22 2023 +0200 +++ b/libinterp/corefcn/stack-frame.cc Fri Sep 22 15:46:36 2023 +0200 @@ -102,12 +102,30 @@ // 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[0].size (); // TODO: Check m_n_locals instead? - - if (n_syms > m_orig_size) + m_orig_size = m_unwind_data->m_n_orig_scope_size; + + // If any symbols been added to the scope since the compilation we allocate room for + // them on the dynamic stack frame. Unless it is a script, which should put stuff in the + // eval frame. + if (n_syms > m_orig_size && !fcn->is_user_script ()) { - int n_to_add = m_orig_size - n_syms; - internal_resize (m_size + n_to_add); + int n_to_add = n_syms - m_orig_size; + // Make room for the extra symbols + internal_resize (internal_size () + n_to_add); + + // We need to get the names of the extra symbols too + auto scope = get_scope (); + auto &sym_map = scope.symbols (); + + for (auto &kv : sym_map) + { + symbol_record &sr = kv.second; + if (sr.frame_offset ()) + continue; + unsigned offset = sr.data_offset (); + if (offset >= m_orig_size) + internal_set_extra_name (external_to_local_offset (offset), sr.name ()); + } } } @@ -130,7 +148,6 @@ auto &lazy = lazy_data (); lazy.m_extra_slots = elt.m_lazy_data->m_extra_slots; - lazy.m_extra_flags = elt.m_lazy_data->m_extra_flags; lazy.m_extra_names = elt.m_lazy_data->m_extra_names; } } @@ -207,8 +224,13 @@ 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 + // Returns true if the symbol with the external offset and frame offset is stored in this frame. + // Note that this does not imply that frame offset is zero, if the return value is true. + bool maybe_external_to_local_offset (std::size_t external_offset, std::size_t frame_offset, + std::size_t &internal_offset_out, bool &allready_added_out) const { + allready_added_out = false; + if (frame_offset >= m_unwind_data->m_external_frame_offset_to_internal.size ()) return false; @@ -221,69 +243,18 @@ 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); + allready_added_out = internal_offset_out < internal_size (); return true; } else if (!found) return false; internal_offset_out = it->second; + allready_added_out = true; + return true; } - // Do an expensive check if the stack is in order. Call only during debugging. - void vm_dbg_check_scope () - { - symbol_scope scope = __get_interpreter__().get_current_scope (); - auto symbols = scope.symbol_list (); - - for (symbol_record sym_scope : symbols) - { - if (sym_scope.frame_offset ()) // Don't check nested function stuff becouse it aint working anyways - 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, 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 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 != offset2 && frame_name != "") - error ("VM stack check failed: %zu != %zu\n", scope_offset, offset2); - - if (internal_offset >= internal_size ()) - internal_resize (internal_offset + 1); - - auto flag = get_scope_flag (scope_offset); - if (flag == PERSISTENT || flag == GLOBAL) - continue; - - octave_value ov1 = varval (sym_scope); - octave_value ov2 = varval (scope_name); - octave_value ov3 = varval (scope_offset); - octave_value ov4 = varref (sym_scope, true); - octave_value ov5 = varref (scope_offset, true); - - if (!ov1.same_rep (ov2)) - error ("VM stack check failed: Same object differs 2\n"); - if (!ov1.same_rep (ov3)) - error ("VM stack check failed: Same object differs 3\n"); - if (!ov1.same_rep (ov4)) - error ("VM stack check failed: Same object differs 4\n"); - if (!ov1.same_rep (ov5)) - error ("VM stack check failed: Same object differs 5\n"); - } - - } - void vm_clear_for_cache () { m_parent_link = nullptr; @@ -300,7 +271,6 @@ // they can be accessed. void vm_unwinds () { - //vm_dbg_check_scope (); bool is_alone = m_weak_ptr_to_self.use_count () <= 2; // Two seems about right if (m_lazy_data) @@ -373,8 +343,22 @@ internal_resize (internal_size () + diff); } + void internal_set_extra_name (std::size_t local_offset, std::string name) + { + std::size_t extra_offset = local_offset - m_size; + std::size_t extra_size = (m_lazy_data ? m_lazy_data->m_extra_slots.size () : 0); + + if (!m_lazy_data || extra_offset >= extra_size) + panic ("VM internal panic: Trying to set extra slot name out of range"); + + m_lazy_data->m_extra_names.at (extra_offset) = name; + } + void internal_resize (std::size_t arg) { + // Not ok to resize for scripts. Extra slots should be in the eval frame. + CHECK_PANIC (!m_fcn->is_user_script ()); + int diff = static_cast<int> (arg) - static_cast<int>(internal_size ()); if (diff > 0) @@ -382,8 +366,6 @@ auto &lazy = lazy_data (); lazy.m_extra_slots.resize (lazy.m_extra_slots.size () + diff); - lazy.m_extra_flags.resize (lazy.m_extra_flags.size () + diff); - lazy.m_extra_names.resize (lazy.m_extra_names.size () + diff); } } @@ -394,17 +376,18 @@ { if (!m_lazy_data) panic ("bytecode_fcn_stack_frame::slot_is_global(%zu): Bad request", local_offset); - scope_flags flag = m_lazy_data->m_extra_flags.at (local_offset - m_size); - return flag == GLOBAL; + + octave_value ov = m_lazy_data->m_extra_slots.at (local_offset - m_size); + if (!ov.is_ref ()) + return false; + return ov.ref_rep ()->get_scope_flag () == GLOBAL; } octave_value &ov = m_stack_start [local_offset].ov; if (!ov.is_ref ()) return false; - if (ov.ref_rep ()->is_global_ref ()) - return true; - - return false; + + return ov.ref_rep ()->get_scope_flag () == GLOBAL; } bool slot_is_persistent (std::size_t local_offset) const @@ -413,26 +396,27 @@ { if (!m_lazy_data) panic ("bytecode_fcn_stack_frame::slot_is_global(%zu): Bad request", local_offset); - scope_flags flag = m_lazy_data->m_extra_flags.at (local_offset - m_size); - return flag == PERSISTENT; + octave_value ov = m_lazy_data->m_extra_slots.at (local_offset - m_size); + if (!ov.is_ref ()) + return false; + return ov.ref_rep ()->get_scope_flag () == PERSISTENT; } octave_value &ov = m_stack_start [local_offset].ov; if (!ov.is_ref ()) return false; - if (ov.ref_rep ()->is_persistent_ref ()) - return true; - return false; + return ov.ref_rep ()->get_scope_flag () == PERSISTENT; } stack_frame::scope_flags get_scope_flag_internal (std::size_t external_offset, std::size_t frame_offset) const { std::size_t local_offset; - bool found = maybe_external_to_local_offset (external_offset, frame_offset, local_offset); + bool allready_added; + bool in_this_frame = maybe_external_to_local_offset (external_offset, frame_offset, local_offset, allready_added); // 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 (!in_this_frame) { if (!m_fcn->is_nested_function ()) error ("VM internal error: Invalid call to get_scope_flag_internal"); @@ -450,10 +434,14 @@ return ov.ref_rep ()->get_scope_flag (); } - // TODO: Could use code above instead? size_t extra_offset = local_offset - m_size; - if (m_lazy_data && extra_offset < m_lazy_data->m_extra_flags.size ()) - return m_lazy_data->m_extra_flags.at (local_offset - m_size); + if (m_lazy_data && extra_offset < m_lazy_data->m_extra_slots.size ()) + { + octave_value ov = m_lazy_data->m_extra_slots.at (extra_offset); + if (!ov.is_ref ()) + return LOCAL; + return ov.ref_rep ()->get_scope_flag (); + } return LOCAL; } @@ -480,8 +468,6 @@ if (!m_lazy_data) error ("VM internal error: Trying to set scope flag on invalid offset"); - m_lazy_data->m_extra_flags.at (local_offset - m_size) = flag; - ov = &m_lazy_data->m_extra_slots.at (local_offset - m_size); name = m_lazy_data->m_extra_names.at (local_offset - m_size); } @@ -497,9 +483,19 @@ return; if (is_pers) error ("VM internal error: Trying to make persistent variable global"); - - *ov = octave_value {new octave_value_ref_global {name}}; - + if (name == "") + error ("VM internal error: Trying to make unnamed symbol global"); + + if (ov->is_ref ()) + { + octave_value_ref *r = ov->ref_rep (); + if (r->is_local_ref ()) + r->mark_globalness_in_owning_frame (true); + else + *ov = octave_value {new octave_value_ref_global {name}}; + } + else + *ov = octave_value {new octave_value_ref_global {name}}; return; } @@ -524,7 +520,16 @@ if (is_global || is_pers) { // Clear the ref in its slot - *ov = octave_value {}; + if (ov->is_ref ()) + { + octave_value_ref *r = ov->ref_rep (); + if (r->is_local_ref ()) + r->mark_globalness_in_owning_frame (false); + else + *ov = octave_value {}; + } + else + *ov = octave_value {}; } return; @@ -538,7 +543,34 @@ std::size_t external_offset = sym.data_offset (); std::size_t frame_offset = sym.frame_offset (); - return get_scope_flag_internal (external_offset, frame_offset); + if (m_fcn->is_nested_function ()) + return get_scope_flag_internal (external_offset, frame_offset); + + bool is_script = m_fcn->is_user_script (); + + std::size_t local_offset; + bool allready_added; + bool in_this_frame = maybe_external_to_local_offset (external_offset, frame_offset, local_offset, allready_added); + + if (frame_offset == 0 && is_script && !allready_added) + { + // Try to find the symbol in the root frame instead. + auto sym_in_root = access_link ()->lookup_symbol (sym.name ()); + return access_link ()->scope_flag (sym_in_root); + } + else if (frame_offset == 0 && in_this_frame) + return get_scope_flag_internal (external_offset, frame_offset); + + // If we have a frame offset and are in a bytecode script frame + // we need to follow the access links until we got the correct frame + auto access_frame = access_link (); + while (--frame_offset && access_frame) + access_frame = access_frame->access_link (); + + if (!access_frame) + error ("Invalid call to bytecode_fcn_stack_frame::scope_flag()"); + + return access_frame->get_scope_flag (external_offset); } virtual octave_value get_active_bytecode_call_arg_names () @@ -688,15 +720,19 @@ std::size_t external_offset = sym.data_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 ()) + bool allready_added; + bool in_this_frame = maybe_external_to_local_offset (external_offset, frame_offset, local_offset, allready_added); + + bool is_script = m_fcn->is_user_script (); + bool is_nested = m_fcn->is_nested_function (); + + if (!allready_added && (is_nested || is_script)) { // 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) + else if (!in_this_frame) 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, @@ -796,22 +832,27 @@ 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 ()) + bool allready_added; + bool in_this_frame = maybe_external_to_local_offset (external_offset, frame_offset, local_offset, allready_added); + + bool is_nested = m_fcn->is_nested_function (); + bool is_script = m_fcn->is_user_script (); + + if (!allready_added && (is_nested || is_script)) { // Try to find the symbol in the root frame instead. auto sym_in_root = access_link ()->insert_symbol (sym.name ()); + CHECK_PANIC (sym_in_root.frame_offset () == 0); return access_link ()->varref (sym_in_root, deref_refs); } - else if (!found) + else if (!in_this_frame) 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 ()) { internal_resize (local_offset + 1); + internal_set_extra_name (local_offset, sym.name ()); // If this bytecode frame is a script we need to add the symbol to the parent frames if (m_fcn->is_user_script ()) @@ -830,27 +871,20 @@ nm.c_str (), nm_src.c_str ()); } - bool is_global = slot_is_global (local_offset); - bool is_pers = slot_is_persistent (local_offset); - - if (is_global) - return m_evaluator.global_varref (sym.name ()); - if (is_pers) - return get_scope ().persistent_varref(external_offset); - - if (!add_to_parent_scriptframes) - return varref_internal (local_offset, deref_refs); - else + if (add_to_parent_scriptframes) { // Mirrors what happens in vm_enter_script (). // Essentially the octave_value in the enclosing script frame // is stolen and put in this frame, and a pointer to the value in this // frame is put in the enclosing frame. - + symbol_record script_sr = sym; std::string name = sym.name (); auto eval_frame = access_link (); auto eval_scope = eval_frame->get_scope (); + auto parent_frame = parent_link (); + bool caller_is_eval_frame = eval_frame == parent_frame; + symbol_record eval_scope_sr; @@ -864,17 +898,77 @@ else eval_scope_sr = p->second; - octave_value &p_ov = varref (external_offset, false); // Pointer to current VM stack frame - octave_value &orig = eval_frame->varref (eval_scope_sr, false); // Ref to value on parent's VM stack - p_ov = orig; // Move parent's value to current stack frame - orig = octave_value (new octave_value_ref_vmlocal {sym, this}); // Replace parents value with reference ov. - - // We need to do the opposite when unwinding. - lazy_data ().m_script_sr_originals.push_back (eval_scope_sr); - lazy_data ().m_script_local_refs.push_back (sym); - - return p_ov; + if (eval_frame->is_global (eval_scope_sr)) + { + mark_global (script_sr); + + octave_value &orig = eval_frame->varref (eval_scope_sr.data_offset (), false); + orig = octave_value (new octave_value_ref_vmlocal {script_sr, this}); + + if (!caller_is_eval_frame) + { + symbol_record sr_parent = parent_frame->lookup_symbol (script_sr.name ()); + CHECK_PANIC (sr_parent.is_valid ()); + CHECK_PANIC (sr_parent.frame_offset () == 0); + + octave_value &parent_ref = parent_frame->varref (sr_parent, false); + + CHECK_PANIC (parent_frame->is_global (sr_parent)); + + if (&parent_ref != &orig) + { + CHECK_PANIC (parent_ref.is_ref () && parent_ref.ref_rep ()->is_global_ref ()); + parent_ref = octave_value (new octave_value_ref_vmlocal {script_sr, this}); + } + } + } + else + { + octave_value &orig_ref = eval_frame->varref (eval_scope_sr, false); // Ref to value on parent's VM stack + octave_value &ref = varref (script_sr, false); // Pointer to current VM stack frame + + CHECK_PANIC (&ref != &orig_ref); + + if (!caller_is_eval_frame) + { + symbol_record sr_parent = parent_frame->lookup_symbol (script_sr.name ()); + CHECK_PANIC (sr_parent.is_valid ()); + + // If the symbol is not in the parent frame, we add it to it. + if (sr_parent.frame_offset ()) + { + // Move the value from whatever parent has the it to the current stack frame + ref = parent_frame->varref (sr_parent, false); + + sr_parent = parent_frame->insert_symbol (script_sr.name ()); // TODO: Need to be done to all parents missing it? + octave_value &parent_ref = parent_frame->varref (sr_parent, false); + + CHECK_PANIC (&parent_ref != &orig_ref); + CHECK_PANIC (&parent_ref != &ref); + + parent_ref = octave_value (new octave_value_ref_vmlocal {script_sr, this}); + } + else + { + octave_value &parent_ref = parent_frame->varref (sr_parent, false); + + CHECK_PANIC (&ref != &parent_ref); + CHECK_PANIC (&parent_ref != &orig_ref); + + ref = parent_ref; // Move parent's value to the current stack frame + parent_ref = octave_value (new octave_value_ref_vmlocal {script_sr, this}); + } + } + else + { + ref = orig_ref; // Move eval frame's value to current stack frame + } + + orig_ref = octave_value (new octave_value_ref_vmlocal {script_sr, this}); // Replace parents value with reference ov. + } } + + return varref_internal (local_offset, deref_refs); } void mark_scope (const symbol_record& sym, @@ -885,88 +979,70 @@ bool add_global_to_scripts = flag == GLOBAL && is_script; bool remove_global_from_scripts = flag != GLOBAL && is_script && was_global; + bool is_nested = m_fcn->is_nested_function (); + std::size_t external_offset = sym.data_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) + bool allready_added; + bool in_this_frame = maybe_external_to_local_offset (external_offset, frame_offset, local_offset, allready_added); + + if (!in_this_frame || ((is_script || is_nested) && !allready_added)) { - if (!m_fcn->is_nested_function ()) + if (!is_script && !is_nested) error ("VM internal error: Invalid call to mark_scope"); - auto sym_in_root = access_link ()->lookup_symbol (sym.name ()); + auto sym_in_root = access_link ()->insert_symbol (sym.name ()); // Adds symbol if doesn't exist access_link ()->mark_scope (sym_in_root, flag); return; } if (local_offset >= internal_size ()) - internal_resize (local_offset + 1); + { + internal_resize (local_offset + 1); + internal_set_extra_name (local_offset, sym.name ()); + } set_scope_flag (external_offset, flag); - // If we are executing a bytecode script, we need to mark a symbol as global in - // the nesting frames too. Likewise, if we are to "unglobal" a symbol in a - // bytecode script, we need to unglobal the symbol in all nesting bytecode script frames. - // - // TODO: Refactor - if (add_global_to_scripts || remove_global_from_scripts) + if (add_global_to_scripts) { - auto parent_frame = parent_link (); - auto nesting_frame = access_link (); - - bool do_access_frame = nesting_frame == parent_frame; - - if (!parent_frame->is_bytecode_fcn_frame ()) - do_access_frame = true; - - auto f = static_cast<bytecode_fcn_stack_frame*> (parent_frame.get ()); - bool f_is_script = f->m_lazy_data && f->m_lazy_data->m_is_script; - if (!f_is_script) - do_access_frame = true; - - // TODO: Make not recursive to allow deep stacks. - if (do_access_frame) + symbol_record sym_access = m_access_link->insert_symbol (sym.name ()); + octave_value &ov_access = m_access_link->varref (sym_access.data_offset (), false); + + bool make_ref = true; + + if (ov_access.is_ref ()) { - auto scope = nesting_frame->get_scope (); - const auto &symbols = scope.symbols (); - auto p = symbols.find (sym.name ()); - symbol_record sr; - - if (add_global_to_scripts && p == symbols.end ()) - sr = scope.insert (sym.name ()); - else if (remove_global_from_scripts && p == symbols.end ()) - return; - else - sr = p->second; - - // If top scope, clear slot without following refs. mark_scope() does this in itself - // for bytecode frames' mark_scope(). - if (!nesting_frame->is_bytecode_fcn_frame ()) - { - octave_value &ov = nesting_frame->varref (sr, false); - ov = {}; - } - - nesting_frame->mark_scope (sr, flag); // Will call parent bytecode scripts recursively + octave_value_ref *r = ov_access.ref_rep (); + if (r->is_local_ref ()) + make_ref = false; } - else + + m_access_link->mark_global (sym_access); + + if (make_ref) + ov_access = octave_value (new octave_value_ref_vmlocal {sym, this}); + } + else if (remove_global_from_scripts) + { + symbol_record sym_access = m_access_link->insert_symbol (sym.name ()); + octave_value &ov_access = m_access_link->varref (sym_access.data_offset (), false); + + bool make_ref = true; + + if (ov_access.is_ref ()) { - auto scope = f->get_scope (); - const auto &symbols = scope.symbols (); - auto p = symbols.find (sym.name ()); - symbol_record sr; - - if (add_global_to_scripts && p == symbols.end ()) - sr = scope.insert (sym.name ()); - else if (remove_global_from_scripts && p == symbols.end ()) - return; - else - sr = p->second; - - f->mark_scope (sr, flag); // Will call parent bytecode scripts recursively + octave_value_ref *r = ov_access.ref_rep (); + if (r->is_local_ref ()) + make_ref = false; } + + m_access_link->unmark_global (sym_access); + + if (make_ref) + ov_access = octave_value (new octave_value_ref_vmlocal {sym, this}); } } @@ -1008,7 +1084,11 @@ 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_returns = static_cast<signed char> (m_code[0]); + if (n_returns == -128) // Magic number for anonymous function's "dynamic amount of returns" + n_returns = 0; + else + n_returns = abs (n_returns); int n_args = abs (static_cast<signed char> (m_code[1])); if (local_offset < n_returns + n_args) ret.mark_formal (); @@ -1022,7 +1102,7 @@ { if (m_lazy_data->m_extra_names.at (i) == name) { - symbol_record ret (name, m_lazy_data->m_extra_flags.at (i)); + symbol_record ret (name, flag); ret.set_data_offset (m_orig_size + i); return ret; } @@ -1057,6 +1137,9 @@ symbol_record insert_symbol (const std::string& name) { + bool is_script = m_lazy_data && m_lazy_data->m_is_script; + bool allready_added; + // If the symbols is already in the immediate scope, there is // nothing more to do. @@ -1064,18 +1147,68 @@ symbol_record sym = scope.lookup_symbol (name); - if (sym) - return sym; - - // If we have not created the extra slots, now is the time - auto &lazy = lazy_data (); - lazy.m_extra_names.push_back (name); - lazy.m_extra_slots.push_back ({}); - lazy.m_extra_flags.push_back (LOCAL); - - sym = scope.find_symbol (name); - - panic_unless (sym.is_valid ()); + if (!sym) + { + // If we have not created the extra slots, now is the time + lazy_data (); + + sym = scope.find_symbol (name); + + CHECK_PANIC (sym.is_valid ()); + CHECK_PANIC (sym.frame_offset () == 0); + + unsigned local_offset = external_to_local_offset (sym.data_offset ()); + + if (local_offset >= internal_size ()) + internal_resize (local_offset + 1); + + internal_set_extra_name (local_offset, sym.name ()); + + allready_added = false; + } + else + allready_added = true; + + // If we are in a script, we might need to add the symbol to the eval frame too, + // and point a ref to the script's slot from the eval frame. + if (is_script) + { + CHECK_PANIC (m_access_link); + + bool access_allready_added; + + symbol_record sym_access = m_access_link->lookup_symbol (name); + if (sym_access.is_valid ()) + access_allready_added = true; + else + { + access_allready_added = false; + sym_access = m_access_link->insert_symbol (name); + } + + CHECK_PANIC ((allready_added && access_allready_added) || !allready_added); + + if (!allready_added) + { + octave_value &ov_access = m_access_link->varref (sym_access.data_offset (), false); + octave_value &ov = varref (sym.data_offset (), false); + + bool make_ref = true; + + if (ov_access.is_ref ()) + { + octave_value_ref *r = ov_access.ref_rep (); + if (r->is_local_ref ()) + make_ref = false; + } + + if (make_ref) + { + ov = ov_access; + ov_access = octave_value (new octave_value_ref_vmlocal {sym, this}); + } + } + } return sym; } @@ -1287,93 +1420,186 @@ void vm_enter_script () { + CHECK_PANIC (m_fcn->is_user_script ()); + // Check that there are no "extra slots" in the current frame. Those should have been added to the eval frame. + CHECK_PANIC (!(m_lazy_data && m_lazy_data->m_extra_slots.size () != 0)); + lazy_data ().m_is_script = true; auto eval_frame = access_link (); - auto eval_scope = eval_frame->get_scope (); - - if (!m_fcn->is_user_script ()) - panic ("Invalid call to vm_enter_script ()"); - - auto script_scope = m_fcn->scope (); - std::size_t num_script_symbols = script_scope.num_symbols (); - resize (num_script_symbols); - - const std::map<std::string, symbol_record>& script_symbols - = script_scope.symbols (); - const std::map<std::string, symbol_record>& eval_scope_symbols - = eval_scope.symbols (); - - // We want to steal all named ov:s of the eval scope, replace them - // with references and put the ov:s on the VM stack - - for (const auto& nm_sr : script_symbols) + auto parent_frame = parent_link (); + + bool caller_is_eval_frame = eval_frame == parent_frame; + bool eval_frame_is_bytecode = eval_frame->is_bytecode_fcn_frame (); + + if (!caller_is_eval_frame) { - std::string name = nm_sr.first; - symbol_record script_sr = nm_sr.second; - - auto p = eval_scope_symbols.find (name); - - symbol_record eval_scope_sr; - - if (p == eval_scope_symbols.end ()) - eval_scope_sr = eval_scope.insert (name); // TODO: Does this work with nested nested scripts? Do we need to add to all scopes? + CHECK_PANIC (parent_frame->is_bytecode_fcn_frame ()); + auto *parent_frame_bc = static_cast<bytecode_fcn_stack_frame*> (parent_frame.get ()); + + // Check that there are no "extra slots" in the parent frame. Those should have been added to the eval frame + CHECK_PANIC (!(parent_frame_bc->m_lazy_data && parent_frame_bc->m_lazy_data->m_extra_slots.size () != 0)); + + // Move all user symbol values from the parent frame to the eval frame. + // Replace the values in the parent frame with a pointer-like object "octave_value_ref_vmlocal" + // pointing to the eval frame. + for (std::string id_name : parent_frame_bc->m_unwind_data->m_set_user_locals_names) + { + symbol_record sr_eval = eval_frame->lookup_symbol (id_name); + if (!sr_eval.is_valid ()) + sr_eval = eval_frame->insert_symbol (id_name); + eval_frame->varref (sr_eval, false); // A bit silly, but allocates space for it + + // We need to use the varref(size_t) since it gets in "behind" any global value in + // top scope, directly to the m_value vector. While the varref(symbol_record) returns + // a ref to the global value itself. Unless the frame offset is set, in which case we + // need to use the varref(symbol_record) variant to walk access frames properly. E.g. + // 'nest.tst' need frame offset when a script tries to access a variable from a nested + // function. + // TODO: Fix this hack + octave_value *ov_eval = sr_eval.frame_offset () ? + &eval_frame->varref (sr_eval, false) : + &eval_frame->varref (sr_eval.data_offset (), false); + + symbol_record sr_parent = parent_frame->lookup_symbol (id_name); // TODO: Store slot nr instead? + CHECK_PANIC (sr_parent.is_valid () && sr_parent.frame_offset () == 0); + octave_value &ov_parent = parent_frame->varref (sr_parent.data_offset (), false); + + CHECK_PANIC (!(ov_parent.is_ref () && ov_parent.ref_rep ()->is_local_ref ())); + + bool is_global_in_eval_frame = eval_frame->is_global (sr_eval); + bool is_global_in_parent_frame = parent_frame->is_global (sr_parent); + CHECK_PANIC (is_global_in_eval_frame == is_global_in_parent_frame); + + if (is_global_in_parent_frame) + CHECK_PANIC (ov_parent.is_ref () && ov_parent.ref_rep ()->is_global_ref ()); + + if (!is_global_in_parent_frame || eval_frame_is_bytecode) + *ov_eval = ov_parent; + else + *ov_eval = {}; + + ov_parent = octave_value {new octave_value_ref_vmlocal {sr_eval, eval_frame.get ()}}; + } + } + + // Move all user symbols from the eval frame to the current frame we are entering. + // Replace the moved values in the eval frame with a pointer-like object "octave_value_ref_vmlocal" + // pointing to the current frame. + for (std::string id_name : m_unwind_data->m_set_user_locals_names) + { + symbol_record sr_eval = eval_frame->lookup_symbol (id_name); + if (!sr_eval.is_valid ()) + sr_eval = eval_frame->insert_symbol (id_name); + eval_frame->varref (sr_eval, false); // A bit silly, but allocates space for it + + octave_value *ov_eval = sr_eval.frame_offset () ? + &eval_frame->varref (sr_eval, false) : + &eval_frame->varref (sr_eval.data_offset (), false); + + symbol_record sr_current = lookup_symbol (id_name); + CHECK_PANIC (sr_current.is_valid () && sr_current.frame_offset () == 0); + octave_value &ov_current = varref (sr_current.data_offset (), false); + + CHECK_PANIC (!(ov_current.is_ref () && ov_current.ref_rep ()->is_local_ref ())); + CHECK_PANIC (!(ov_eval->is_ref () && ov_eval->ref_rep ()->is_local_ref ())); + + bool is_global_in_eval_frame = eval_frame->is_global (sr_eval); + + if (is_global_in_eval_frame) + ov_current = octave_value {new octave_value_ref_global {id_name}}; else - eval_scope_sr = p->second; - - stack_frame::scope_flags flag = eval_frame->scope_flag (eval_scope_sr); - - if (flag == stack_frame::scope_flags::GLOBAL) - { - mark_global (script_sr); - } - else - { - octave_value &orig = eval_frame->varref (eval_scope_sr, false); // Ref to value on parent's VM stack - octave_value &p_ov = varref (script_sr, false); // Pointer to current VM stack frame - - p_ov = orig; // Move parent's value to current stack frame - orig = octave_value (new octave_value_ref_vmlocal {script_sr, this}); // Replace parents value with reference ov. - - // We need to do the opposite when unwinding. - lazy_data ().m_script_sr_originals.push_back (eval_scope_sr); - lazy_data ().m_script_local_refs.push_back (script_sr); - } + ov_current = *ov_eval; + + *ov_eval = octave_value {new octave_value_ref_vmlocal {sr_current, this}}; } } void vm_exit_script () { + if (!m_fcn->is_user_script ()) // Nothing to do for non-script frames + return; + // Restore values from the VM stack frame to the original frame - if (!m_lazy_data || m_lazy_data->m_is_script != true) - return; + // Check that there are no "extra slots" in the current frame. Those should have been added to the eval frame. + CHECK_PANIC (!(m_lazy_data && m_lazy_data->m_extra_slots.size () != 0)); + + lazy_data ().m_is_script = true; auto eval_frame = access_link (); - auto eval_scope = eval_frame->get_scope (); - - if (!m_fcn->is_user_script ()) - panic ("Invalid call to vm_exit_script (). Not an user script."); - - auto &script_sr_originals = lazy_data ().m_script_sr_originals; - auto &script_local_refs = lazy_data ().m_script_local_refs; - - std::size_t n = script_sr_originals.size (); - - for (unsigned i = 0; i < n; i++) + auto parent_frame = parent_link (); + + bool caller_is_eval_frame = eval_frame == parent_frame; + bool eval_frame_is_bytecode = eval_frame->is_bytecode_fcn_frame (); + + // Move all user symbols from the current frame to the eval frame. + for (std::string id_name : m_unwind_data->m_set_user_locals_names) { - symbol_record sr_orig = script_sr_originals[i]; - symbol_record sr_this = script_local_refs[i]; - - if (is_global (sr_this)) - continue; - if (is_persistent (sr_this)) - continue; - - octave_value &ref = varref (sr_this, false); - octave_value &orig_ref = eval_frame->varref (sr_orig, false); - orig_ref = ref; - ref = octave_value {}; + symbol_record sr_eval = eval_frame->lookup_symbol (id_name); + CHECK_PANIC (sr_eval.is_valid ()); + octave_value *ov_eval = sr_eval.frame_offset () ? + &eval_frame->varref (sr_eval, false) : + &eval_frame->varref (sr_eval.data_offset (), false); + + symbol_record sr_current = lookup_symbol (id_name); + CHECK_PANIC (sr_current.is_valid () && sr_current.frame_offset () == 0); + octave_value &ov_current = varref (sr_current.data_offset (), false); + + CHECK_PANIC (!(ov_current.is_ref () && ov_current.ref_rep ()->is_local_ref ())); + + bool is_global_in_eval_frame = eval_frame->is_global (sr_eval); + bool is_global_in_current_frame = is_global (sr_current); + CHECK_PANIC (is_global_in_eval_frame == is_global_in_current_frame); + + if (is_global_in_current_frame) + CHECK_PANIC (ov_current.is_ref () && ov_current.ref_rep ()->is_global_ref ()); + + if (!is_global_in_current_frame || eval_frame_is_bytecode) + *ov_eval = ov_current; + else + *ov_eval = {}; + + ov_current = {}; + } + + // Move all values the parent frame needs to it from the eval frame + if (!caller_is_eval_frame) + { + CHECK_PANIC (parent_frame->is_bytecode_fcn_frame ()); + auto *parent_frame_bc = static_cast<bytecode_fcn_stack_frame*> (parent_frame.get ()); + + // Check that there are no "extra slots" in the parent frame. Those should have been added to the eval frame + CHECK_PANIC (!(parent_frame_bc->m_lazy_data && parent_frame_bc->m_lazy_data->m_extra_slots.size () != 0)); + + // Move all values the parent frame needs to it from the eval frame. + // In the eval frame, put a pointer-like object "octave_value_ref_vmlocal" + // pointing to the parent frame + for (std::string id_name : parent_frame_bc->m_unwind_data->m_set_user_locals_names) + { + symbol_record sr_eval = eval_frame->lookup_symbol (id_name); + CHECK_PANIC (sr_eval.is_valid ()); + octave_value *ov_eval = sr_eval.frame_offset () ? + &eval_frame->varref (sr_eval, false) : + &eval_frame->varref (sr_eval.data_offset (), false); + + symbol_record sr_parent = parent_frame->lookup_symbol (id_name); // TODO: Store slot nr instead? + CHECK_PANIC (sr_parent.is_valid () && sr_parent.frame_offset () == 0); + octave_value &ov_parent = parent_frame->varref (sr_parent.data_offset (), false); + + CHECK_PANIC (!(ov_eval->is_ref () && ov_eval->ref_rep ()->is_local_ref ())); + + bool is_global_in_eval_frame = eval_frame->is_global (sr_eval); + bool is_global_in_parent_frame = parent_frame->is_global (sr_parent); + CHECK_PANIC (is_global_in_eval_frame == is_global_in_parent_frame); + + if (!is_global_in_parent_frame || eval_frame_is_bytecode) + ov_parent = *ov_eval; + else + ov_parent = octave_value {new octave_value_ref_global {id_name}}; + + *ov_eval = octave_value {new octave_value_ref_vmlocal {sr_parent, parent_frame.get ()}}; + } } } @@ -1405,14 +1631,10 @@ std::vector<octave_value> m_extra_slots; std::vector<std::string> m_extra_names; - std::vector<scope_flags> m_extra_flags; unwind_protect *m_unwind_protect_frame = nullptr; stack_element *m_stack_cpy = nullptr; bool m_is_script; - - std::vector<symbol_record> m_script_sr_originals; - std::vector<symbol_record> m_script_local_refs; }; lazy_data_struct & lazy_data () @@ -1737,7 +1959,35 @@ void set_scope_flag (std::size_t data_offset, scope_flags flag) { + bool was_global = m_flags.at (data_offset) == scope_flags::GLOBAL; + m_flags.at (data_offset) = flag; + + bool is_global = flag == scope_flags::GLOBAL; + + // If the VM is running scripts it places octave_value_ref objects in + // the top scope to "steal" the variables from it to be able to keep the + // canonical copy on its active stack frame. When it unwinds, the top scope + // gets the value back. + // + // If e.g. evalin () changes the globalness state of a symbol in the top scope, + // the VM need to be notified, if the variable is on the VM stack. + if (was_global != is_global) + { + octave_value ov = m_values.at (data_offset); + // Only the VM spreads ref objects around, so this is only true if the VM is running + if (ov.is_ref ()) + { + octave_value cpy = ov; + // Pop the value in the top scope to avoid recursive loop, since mark_globalness_in_owning_frame() + // will call mark_global (), which will walk the stack frames down to the root. + m_values.at (data_offset) = octave_value {}; + octave_value_ref *ref = cpy.ref_rep (); + ref->mark_globalness_in_owning_frame (is_global); + // We need the ref back in place if the flag changes again + m_values.at (data_offset) = cpy; + } + } } octave_value get_auto_fcn_var (auto_var_type avt) const
--- a/libinterp/octave-value/ov-ref.cc Tue Oct 03 14:45:22 2023 +0200 +++ b/libinterp/octave-value/ov-ref.cc Fri Sep 22 15:46:36 2023 +0200 @@ -149,6 +149,21 @@ m_frame->varref (m_sym) = val; } +octave::stack_frame::scope_flags +octave_value_ref_vmlocal::get_scope_flag () +{ + return m_frame->scope_flag (m_sym); +} + +void +octave_value_ref_vmlocal::mark_globalness_in_owning_frame (bool should_be_global) +{ + if (should_be_global) + m_frame->mark_global (m_sym); + else + m_frame->unmark_global (m_sym); +} + octave_value & octave_value_ref_ptr::ref () {
--- a/libinterp/octave-value/ov-ref.h Tue Oct 03 14:45:22 2023 +0200 +++ b/libinterp/octave-value/ov-ref.h Fri Sep 22 15:46:36 2023 +0200 @@ -67,6 +67,7 @@ virtual bool is_local_ref () const { return false; } virtual octave::stack_frame::scope_flags get_scope_flag () = 0; + virtual void mark_globalness_in_owning_frame (bool /* should_be_global */) {} void maybe_call_dtor (); octave_value simple_subsasgn (char type, octave_value_list& idx, const octave_value& rhs); @@ -142,10 +143,9 @@ octave_value & ref (); void set_value (octave_value val); - octave::stack_frame::scope_flags get_scope_flag () - { - return octave::stack_frame::scope_flags::LOCAL; - } + octave::stack_frame::scope_flags get_scope_flag (); + + void mark_globalness_in_owning_frame (bool should_be_global); bool is_local_ref () const { return true; }
--- a/libinterp/octave-value/ov-usr-fcn.cc Tue Oct 03 14:45:22 2023 +0200 +++ b/libinterp/octave-value/ov-usr-fcn.cc Fri Sep 22 15:46:36 2023 +0200 @@ -199,7 +199,12 @@ const octave_value_list& args) { if (octave::vm::maybe_compile_or_compiled (this)) - return octave::vm::call (tw, nargout, args, this); + { + auto frame = tw.get_current_stack_frame (); + if (frame->is_scope_frame () || frame->is_bytecode_fcn_frame ()) + return octave::vm::call (tw, nargout, args, this); + } + tw.push_stack_frame (this);
--- a/libinterp/octave-value/ov.h Tue Oct 03 14:45:22 2023 +0200 +++ b/libinterp/octave-value/ov.h Fri Sep 22 15:46:36 2023 +0200 @@ -51,6 +51,7 @@ class vm; class bytecode_fcn_stack_frame; class scope_stack_frame; +class base_value_stack_frame; OCTAVE_END_NAMESPACE(octave) @@ -1548,6 +1549,7 @@ friend class octave::vm; friend class octave::bytecode_fcn_stack_frame; friend class octave::scope_stack_frame; + friend class octave::base_value_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 Tue Oct 03 14:45:22 2023 +0200 +++ b/libinterp/parse-tree/pt-bytecode-vm.cc Fri Sep 22 15:46:36 2023 +0200 @@ -4537,7 +4537,7 @@ if (!m_could_not_push_frame) { auto sf = m_tw->get_current_stack_frame (); - sf->vm_exit_script (); + sf->vm_exit_script (); // Just returns for non-scripts sf->vm_unwinds (); } @@ -4674,13 +4674,11 @@ goto unwind; } - auto sym = m_tw->get_current_stack_frame ()->insert_symbol (name); + auto frame = m_tw->get_current_stack_frame (); + auto sym = frame->insert_symbol (name); // Note: Install variable wont override global's value with nil ov from // the "{}" argument. - // - // Also install_variable () will write a true ov to the marker - // ov on the VM stack. - m_tw->get_current_stack_frame()->install_variable (sym, {}, 1); + frame->install_variable (sym, {}, 1); octave_value &ov_gbl = m_tw->global_varref (name); global_is_new_in_callstack = ov_gbl.is_undefined ();
--- a/libinterp/parse-tree/pt-bytecode-walk.cc Tue Oct 03 14:45:22 2023 +0200 +++ b/libinterp/parse-tree/pt-bytecode-walk.cc Fri Sep 22 15:46:36 2023 +0200 @@ -32,6 +32,7 @@ #include "symrec.h" #include "pt-walk.h" #include "ov-scalar.h" +#include "file-ops.h" //#pragma GCC optimize("Og") @@ -2229,6 +2230,9 @@ 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; + + if (frame_offset == 0) + m_code.m_unwind_data.m_set_user_locals_names.insert (name); } } @@ -2308,17 +2312,20 @@ } // Check that the mapping between external offsets and internal slots has no holes in it - if (m_n_nested_fn == 0) + int i = 0; + for (auto it : m_code.m_unwind_data.m_external_frame_offset_to_internal[0]) { - 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++; - } + int external_offset = it.first; + CHECK (external_offset == i); + i++; } + // Save how many symbols there were originally, so that the VM can easely check if more symbols + // have been added to the scope object when a frame is pushed to the call stack. + m_code.m_unwind_data.m_n_orig_scope_size = 0; + for (auto m : m_code.m_unwind_data.m_external_frame_offset_to_internal) + m_code.m_unwind_data.m_n_orig_scope_size += m.size (); + // 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 (); @@ -2478,6 +2485,9 @@ 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; + + if (frame_offset == 0) + m_code.m_unwind_data.m_set_user_locals_names.insert (name); } } // We need the arguments and return id:s in the map too. @@ -2532,46 +2542,66 @@ // // Note that there might be symbols added to the original scope by // eg. eval ("foo = 3"). We just ignore those. - std::string function_name = fcn.name (); - auto dot_idx = function_name.find_last_of ('.'); // Names might be e.g. "get.Count" but we only want "Count" - if (dot_idx != std::string::npos) - function_name = function_name.substr (dot_idx + 1); - + // // We need to keep track of which id is the function name so that // we can add the id to the id-table and get it's external offset. // // 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; + // + // Also, for nested functions any parent nesting function name need to + // be included too. + if (!fcn.is_anonymous_function ()) + { + auto scope = fcn.scope (); + std::string fn_name = scope.fcn_name (); + + // Names might be e.g. "get.Count" but we only want "Count" + auto dot_idx = fn_name.find_last_of ('.'); + if (dot_idx != std::string::npos) + fn_name = fn_name.substr (dot_idx + 1); + + symbol_record fn_sr = scope.find_symbol (fn_name); + CHECK (fn_sr.is_valid ()); + + std::size_t offset = fn_sr.data_offset (); + std::size_t frame_offset = fn_sr.frame_offset (); + + 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); + + int slot = add_id_to_table (fn_name); + m_code.m_unwind_data.m_external_frame_offset_to_internal[frame_offset][offset] = slot; + } + + // Add the parents' function names for nested functions if (m_n_nested_fn) { - idx_fn_name = fcn.scope ().find_symbol (function_name).data_offset (); + auto scope = fcn.scope (); + for (std::string parent_name : scope.parent_fcn_names ()) + { + symbol_record parent_sr = scope.find_symbol (parent_name); + CHECK (parent_sr.is_valid ()); + + std::size_t offset = parent_sr.data_offset (); + std::size_t frame_offset = parent_sr.frame_offset (); + + 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); + + int slot = add_id_to_table (parent_name); + m_code.m_unwind_data.m_external_frame_offset_to_internal[frame_offset][offset] = slot; + } } - else - idx_fn_name = n_returns + 1; // "+1" since 'ans' is always added first - + + // Add varargin, varargout, ans for (auto p : fcn.scope ().symbols ()) { std::string name = p.first; symbol_record sym = p.second; std::size_t offset = sym.data_offset (); - - bool is_fn_id = offset == idx_fn_name; // Are we at the function name id? - - auto it = m_map_locals_to_slot.find (name); - if (it == m_map_locals_to_slot.end ()) - { - if (is_fn_id) - { - // 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[0][offset] = slot; - } - else - continue; - } + std::size_t frame_offset = sym.frame_offset (); if (name == "varargin") m_code.m_unwind_data.m_external_frame_offset_to_internal[0][offset] = SLOT ("varargin"); @@ -2579,6 +2609,13 @@ 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[0][offset] = SLOT ("ans"); + else if (offset == 1 && frame_offset == 0 && !fcn.is_anonymous_function ()) + { + // If the function name and function identifier of the root function don't match ('foo.m' with 'bar' function) + // the identifier's name ('bar') will end up on offset 1. Make sure it is added by always adding + // the 1 offset symbol in the scope for nested functions. + m_code.m_unwind_data.m_external_frame_offset_to_internal[0][1] = add_id_to_table (sym.name ()); + } } CHECK (! (m_is_anon && m_n_nested_fn)); @@ -2796,6 +2833,12 @@ } } + // Save how many symbols there were originally, so that the VM can easely check if more symbols + // have been added to the scope object. + m_code.m_unwind_data.m_n_orig_scope_size = 0; + for (auto m : m_code.m_unwind_data.m_external_frame_offset_to_internal) + m_code.m_unwind_data.m_n_orig_scope_size += m.size (); + // 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 ();
--- a/libinterp/parse-tree/pt-bytecode.h Tue Oct 03 14:45:22 2023 +0200 +++ b/libinterp/parse-tree/pt-bytecode.h Fri Sep 22 15:46:36 2023 +0200 @@ -248,6 +248,7 @@ std::map<int, tree*> m_ip_to_tree; std::vector<arg_name_entry> m_argname_entries; std::vector<std::map<int,int>> m_external_frame_offset_to_internal; + std::set<std::string> m_set_user_locals_names; struct nested_var_offset { int m_depth; int m_slot_parent; int m_slot_nested; }; std::vector<nested_var_offset> m_v_nested_vars; @@ -269,6 +270,8 @@ int m_n_returns; int m_n_args; int m_n_locals; + // The amount of symbols originally in the scope object at compile time + int m_n_orig_scope_size; }; struct bytecode
--- a/test/compile/bytecode.tst Tue Oct 03 14:45:22 2023 +0200 +++ b/test/compile/bytecode.tst Fri Sep 22 15:46:36 2023 +0200 @@ -700,3 +700,48 @@ %! assert (cdef_bar_cnt == 0); %! clear -global cdef_bar_alive_objs cdef_bar_cnt glb_d glb_e glb_f +## Test script interaction when called from top scope +%!test +%! +%! __vm_enable__ (0, "local"); +%! clear all +%! +%! global bytecode_script_topscope_place; % Used in bytecode_script_topscope the select where to evalin +%! bytecode_script_topscope_place = "base"; +%! +%! global bytecode_script_topscope_call_self +%! bytecode_script_topscope_call_self = false; +%! +%! bytecode_script_topscope_setup; % Does some setups of globals and locals in base frame +%! evalin ("base", "bytecode_script_topscope"); +%! +%! bytecode_script_topscope_assert; % Does some asserts and cleans up the globals and locals added +%! +%! __vm_enable__ (1, "local"); +%! bytecode_script_topscope_setup; +%! assert (__vm_compile__ ("bytecode_script_topscope")); +%! evalin ("base", "bytecode_script_topscope"); +%! bytecode_script_topscope_assert; +%! +%! %% Redo the test, but nested in itself to get a stack frame in between. +%! bytecode_script_topscope_call_self = true; +%! +%! bytecode_script_topscope_setup; +%! assert (__vm_compile__ ("bytecode_script_topscope")); +%! evalin ("base", "bytecode_script_topscope"); +%! bytecode_script_topscope_assert; +%! +%! bytecode_script_topscope_call_self = false; +%! +%! %% Redo the test, but in a command line function instead of in the top scope. +%! bytecode_script_topscope_place = "caller"; % Governs some evalin() in nbytecode_script_topscope. Switch to "caller" +%! +%! eval ("function bytecode_script_topscope_cli_fn ()\nbytecode_script_topscope_setup ('caller');\nbytecode_script_topscope;\nbytecode_script_topscope_assert ('caller');\nend"); +%! +%! bytecode_script_topscope_cli_fn (); +%! bytecode_script_topscope_call_self = true; +%! bytecode_script_topscope_cli_fn (); +%! +%! clear global bytecode_script_topscope_call_self +%! clear global bytecode_script_topscope_place +%! clear bytecode_script_topscope_cli_fn \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/compile/bytecode_script_topscope.m Fri Sep 22 15:46:36 2023 +0200 @@ -0,0 +1,71 @@ +% (bug #64705) +% +% Test the interaction between a script called from top scope and +% global and local variables + +% If this global is true, we do a recursive call to this script +% to add another frame. +global bytecode_script_topscope_call_self +if bytecode_script_topscope_call_self + bytecode_script_topscope_call_self = false; + bytecode_script_topscope; + bytecode_script_topscope_call_self = true; + return +end + +% These should allready be global +assert (isglobal ("glb_a")) +assert (isglobal ("glb_b")) +assert (isglobal ("glb_c")) +assert (isglobal ("glb_d")) +assert (isglobal ("glb_f")) + +global glb_a % "re-global" +global glb_b % "re-global" +global glb_c % "re-global" +global glb_d = 1234; % re-global, init should not run due to being defined in top scope allready +global glb_e = 6; % Not defined in top scope +eval ("global glb_f"); % "re-global", dynamic frame + +assert (glb_a == 2) +assert (glb_b == 3) +assert (glb_c == 4) +assert (glb_d == 5) +assert (glb_e == 6) +eval ("assert (glb_f == 7)") + +assert (local_b == 103) + +glb_b = 33; + +clear global glb_c; + +glb_d = 55; + +%% Locals + +local_a = 102; % Will be added to top scope + +local_b = 113; % Added in top scope, change it + +clear local_c % Added in top scope, clear it here + +% Added in top scope. Clear after using +local_d = 123; +clear local_d; + +% Not added in top scope. Clear after using +local_e = 123; +clear local_e; + +global bytecode_script_topscope_place % "caller" or "base", set in the test script + +% Not added in top scope. Clear from top scope ... +local_f = 123; +evalin (bytecode_script_topscope_place, "clear local_f"); +assert (!exist ("local_f")) + +% Added in top scope. Clear from top scope ... +local_g = 123; +evalin (bytecode_script_topscope_place, "clear local_g"); +assert (!exist ("local_g"))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/compile/bytecode_script_topscope_assert.m Fri Sep 22 15:46:36 2023 +0200 @@ -0,0 +1,22 @@ +function bytecode_script_topscope_assert (place = "base") + evalin (place, "assert (isglobal ('glb_a'))"); + evalin (place, "assert (isglobal ('glb_b'))"); + evalin (place, "assert (!isglobal ('glb_c'))"); % Unglobalized in script + evalin (place, "assert (isglobal ('glb_d'))"); + evalin (place, "assert (isglobal ('glb_e'))"); + evalin (place, "assert (glb_a == 2)"); + evalin (place, "assert (glb_b == 33)"); + evalin (place, "assert (!exist ('glb_c'))"); + evalin (place, "assert (glb_d == 55)"); + evalin (place, "assert (glb_e == 6)"); % Added in the script + evalin (place, "assert (local_a == 102)"); % Local added in script + evalin (place, "assert (local_b == 113)"); + evalin (place, "assert (!exist ('local_c'))"); % Cleared in script + evalin (place, "assert (!exist ('local_d'))"); % Cleared in script + evalin (place, "assert (!exist ('local_e'))"); % Cleared in script + evalin (place, "assert (!exist ('local_f'))"); % Cleared in script + evalin (place, "assert (!exist ('local_g'))"); % Cleared in script + + evalin (place, "clear global glb_a glb_b glb_c glb_d glb_e glb_f"); + evalin (place, "clear local_a local_b local_c local_d local_e local_f local_g"); +endfunction \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/compile/bytecode_script_topscope_setup.m Fri Sep 22 15:46:36 2023 +0200 @@ -0,0 +1,11 @@ +function bytecode_script_topscope_setup (place = "base") + evalin (place, "global glb_a = 2"); + evalin (place, "global glb_b = 3"); + evalin (place, "global glb_c = 4"); + evalin (place, "global glb_d = 5"); + evalin (place, "global glb_f = 7"); + evalin (place, "local_b = 103;"); + evalin (place, "local_c = 104;"); + evalin (place, "local_d = 105;"); + evalin (place, "local_g = 108;"); +endfunction \ No newline at end of file
--- a/test/compile/module.mk Tue Oct 03 14:45:22 2023 +0200 +++ b/test/compile/module.mk Fri Sep 22 15:46:36 2023 +0200 @@ -26,6 +26,9 @@ %reldir%/bytecode_range.m \ %reldir%/bytecode_return.m \ %reldir%/bytecode_scripts.m \ + %reldir%/bytecode_script_topscope.m \ + %reldir%/bytecode_script_topscope_assert.m \ + %reldir%/bytecode_script_topscope_setup.m \ %reldir%/bytecode_struct.m \ %reldir%/bytecode_subfuncs.m \ %reldir%/bytecode_subsasgn.m \