# HG changeset patch # User Petter T. # Date 1692443133 -7200 # Sat Aug 19 13:05:33 2023 +0200 # Node ID 1f1a1ecc74996bf70a1289e8f3fbc5873ae32902 # Parent 1cda2e27d8ca55f9694fe130780eda7882be3a03 VM Add support for scripts The patch adds support for executing scripts in the VM. When a VM script frame is pushed, the VM steals the variables it needs from parent stacks, and puts a reference type value in its place. When exiting the script frame, the value is restored. * libinterp/corefcn/call-stack.cc: push() script frame * libinterp/corefcn/call-stack.h: New overload for push() * libinterp/corefcn/compile.cc: Accept scripts compiling * libinterp/corefcn/stack-frame.cc: Follow 'refs', assoc frames * libinterp/corefcn/stack-frame.h: New functions, signatures * libinterp/octave-value/ov-ref.cc: New ref class 'octave_value_ref_vmlocal' * libinterp/octave-value/ov-ref.h: * libinterp/octave-value/ov-usr-fcn.cc: Move functionality to parent class for scripts and functions. Auto-compile scripts. * libinterp/octave-value/ov-usr-fcn.h: * libinterp/octave-value/ov.h: Make ov and scope_stack_frame friends to call is_ref(). * libinterp/parse-tree/pt-bytecode-vm.cc: New opcodes for entering and exiting scripts. * libinterp/parse-tree/pt-bytecode-walk.cc: Compile scripts * libinterp/parse-tree/pt-bytecode-walk.h: * libinterp/parse-tree/pt-bytecode.h: New opcodes * libinterp/parse-tree/pt-eval.cc: Execute scripts in VM, push frames * libinterp/parse-tree/pt-eval.h: Updated tests for scripts: * test/compile/bytecode_scripts.m: Added * test/compile/script1.m: Added * test/compile/script11.m: Added * test/compile/script2.m: Added * test/compile/script3.m: Added * test/compile/bytecode.tst: Updated * test/compile/module.mk: Include new files diff -r 1cda2e27d8ca -r 1f1a1ecc7499 libinterp/corefcn/call-stack.cc --- a/libinterp/corefcn/call-stack.cc Sat Aug 19 12:57:29 2023 +0200 +++ b/libinterp/corefcn/call-stack.cc Sat Aug 19 13:05:33 2023 +0200 @@ -449,6 +449,31 @@ m_curr_frame = new_frame_idx; } + +void call_stack::push (vm &vm, octave_user_script *fcn, int nargout, int nargin) +{ + 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, // ??? index + parent_link, + static_link, + 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 1cda2e27d8ca -r 1f1a1ecc7499 libinterp/corefcn/call-stack.h --- a/libinterp/corefcn/call-stack.h Sat Aug 19 12:57:29 2023 +0200 +++ b/libinterp/corefcn/call-stack.h Sat Aug 19 13:05:33 2023 +0200 @@ -169,6 +169,8 @@ void push (vm &vm, octave_user_function *fcn, int nargout, int nargin); + void push (vm &vm, octave_user_script *fcn, int nargout, int nargin); + void set_location (int l, int c) { if (! m_cs.empty ()) diff -r 1cda2e27d8ca -r 1f1a1ecc7499 libinterp/corefcn/compile.cc --- a/libinterp/corefcn/compile.cc Sat Aug 19 12:57:29 2023 +0200 +++ b/libinterp/corefcn/compile.cc Sat Aug 19 13:05:33 2023 +0200 @@ -285,9 +285,9 @@ octave_user_function *ufn = ov.user_function_value (); - if (!ufn || !ufn->is_user_function ()) + if (!ufn || (!ufn->is_user_function () && !ufn->is_user_script ())) { - error ("Function not a user function: %s", fn_name.c_str ()); + error ("Function not a user function or script: %s", fn_name.c_str ()); } if (!ufn->is_compiled () && V__enable_vm_eval__) @@ -389,16 +389,16 @@ error ("Function not defined: %s", name.c_str ()); } - if (! ov.is_user_function ()) + if (!ov.is_user_function () && !ov.is_user_script ()) { - error ("Function is not a user function: %s", name.c_str ()); + error ("Function is not a user function or script: %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 ()) + if (!ufn || (!ufn->is_user_function () && !ufn->is_user_script ())) { - error ("Function is not really a user function: %s", name.c_str ()); + error ("Function is not really user function or script: %s", name.c_str ()); } // Throws on errors diff -r 1cda2e27d8ca -r 1f1a1ecc7499 libinterp/corefcn/stack-frame.cc --- a/libinterp/corefcn/stack-frame.cc Sat Aug 19 12:57:29 2023 +0200 +++ b/libinterp/corefcn/stack-frame.cc Sat Aug 19 13:05:33 2023 +0200 @@ -78,7 +78,7 @@ bytecode_fcn_stack_frame (void) = delete; bytecode_fcn_stack_frame (tree_evaluator& tw, - octave_user_function *fcn, + octave_user_code *fcn, std::size_t index, const std::shared_ptr& parent_link, const std::shared_ptr& static_link, @@ -236,8 +236,8 @@ octave_value ov1 = varval (sym_scope); octave_value ov2 = varval (scope_name); octave_value ov3 = varval (scope_offset); - octave_value ov4 = varref (sym_scope); - octave_value ov5 = varref (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"); @@ -278,7 +278,7 @@ // Restore warningstates if (m_fcn) { - auto usr_fn_p = m_fcn->user_function_value (); + auto usr_fn_p = m_fcn->user_function_value (true); if (usr_fn_p) usr_fn_p->restore_warning_states (); // TODO: octave_user_function::restore_warning_states() could be static. } @@ -410,6 +410,7 @@ return LOCAL; } + // 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); @@ -419,19 +420,31 @@ void set_scope_flag (std::size_t external_offset, scope_flags flag) { + scope_flags current_flag = get_scope_flag (external_offset); + + bool is_global = current_flag == GLOBAL; + bool is_pers = current_flag == PERSISTENT; + std::size_t local_offset = external_to_local_offset (external_offset); + + octave_value *ov; // Pointer to the slot + std::string name; // Used for globals + if (local_offset >= m_size) { 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; - return; + + ov = &m_lazy_data->m_extra_slots.at (local_offset - m_size); + name = m_lazy_data->m_extra_names.at (local_offset - m_size); } - - scope_flags current_flag = get_scope_flag (external_offset); - - bool is_global = current_flag == GLOBAL; - bool is_pers = current_flag == PERSISTENT; + else + { + ov = &m_stack_start [local_offset].ov; + name = m_name_data [local_offset]; + } if (flag == GLOBAL) { @@ -440,8 +453,7 @@ if (is_pers) error ("VM internal error: Trying to make persistent variable global"); - octave_value &ov = m_stack_start [local_offset].ov; - ov = octave_value {new octave_value_ref_global {m_name_data [local_offset]}}; + *ov = octave_value {new octave_value_ref_global {name}}; return; } @@ -453,8 +465,7 @@ if (is_global) error ("VM internal error: Trying to make global variable persistent"); - octave_value &ov = m_stack_start [local_offset].ov; - ov = octave_value {new octave_value_ref_persistent {get_scope (), static_cast (external_offset)}}; + *ov = octave_value {new octave_value_ref_persistent {get_scope (), static_cast (external_offset)}}; return; } @@ -468,8 +479,7 @@ if (is_global || is_pers) { // Clear the ref in its slot - octave_value& ov_ref = m_stack_start [local_offset].ov; - ov_ref = octave_value {}; + *ov = octave_value {}; } return; @@ -619,7 +629,10 @@ std::size_t extra_offset = local_offset - stack_slots; if (!m_lazy_data || extra_offset >= extra_size) error ("VM internal error: Trying to access extra slot out of range, %zu", extra_offset); - return m_lazy_data->m_extra_slots.at (extra_offset); + octave_value ov = m_lazy_data->m_extra_slots.at (extra_offset); + if (ov.is_ref ()) + return ov.ref_rep ()->deref (); + return ov; } } @@ -632,9 +645,36 @@ std::size_t external_offset = sym.data_offset (); std::size_t local_offset = external_to_local_offset (external_offset); - // If the offset is out of range we return a nil ov + // 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. + // + // Normaly any symbol will be moved to the current script frame, but a call to eval might + // refer to a symbol in an enclosing frame that has not been moved. if (local_offset >= internal_size ()) - return {}; + { + if (!m_fcn->is_user_script ()) + return {}; + else + { + // Look in the enclosing frames for the symbol + auto frame = access_link (); + std::string name = sym.name (); + + while (frame) + { + symbol_scope scope = frame->get_scope (); + + symbol_record rec = scope.lookup_symbol (name); + + if (rec) + return frame->varval (rec); + + frame = frame->access_link (); + } + + return {}; // Nothing found + } + } if (local_offset >= m_size) { std::string nm_src = sym.name (); @@ -659,7 +699,7 @@ return varval (external_offset); } - octave_value& varref (std::size_t external_offset) + octave_value& varref (std::size_t external_offset, bool deref_refs) { static octave_value fake_dummy_nargout{0}; @@ -675,7 +715,7 @@ if (local_offset < stack_slots) { octave_value &ov = m_stack_start [local_offset].ov; - if (ov.is_ref ()) + if (deref_refs && ov.is_ref ()) return ov.ref_rep ()->ref (); return ov; } @@ -684,12 +724,16 @@ std::size_t extra_offset = local_offset - stack_slots; if (!m_lazy_data || extra_offset >= extra_size) error ("VM internal error: Trying to access extra slot out of range, %zu", extra_offset); - return m_lazy_data->m_extra_slots.at (extra_offset); + octave_value &ov = m_lazy_data->m_extra_slots.at (extra_offset); + if (deref_refs && ov.is_ref ()) + return ov.ref_rep ()->ref (); + return ov; } } - octave_value& varref (const symbol_record& sym) + 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); @@ -698,7 +742,13 @@ // If the offset is out of range we make room for it if (local_offset >= internal_size ()) + { internal_resize (local_offset + 1); + + // If this bytecode frame is a script we need to add the symbol to the parent frames + if (m_fcn->is_user_script ()) + add_to_parent_scriptframes = true; + } if (local_offset >= m_size) { std::string nm_src = sym.name (); @@ -720,12 +770,53 @@ if (is_pers) return get_scope ().persistent_varref(external_offset); - return varref (external_offset); + if (!add_to_parent_scriptframes) + return varref (external_offset, deref_refs); + else + { + // 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. + + std::string name = sym.name (); + + auto eval_frame = access_link (); + auto eval_scope = eval_frame->get_scope (); + + symbol_record eval_scope_sr; + + const std::map& eval_scope_symbols + = eval_scope.symbols (); + + auto p = eval_scope_symbols.find (name); + + if (p == eval_scope_symbols.end ()) + eval_scope_sr = eval_scope.insert (name); + 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; + } } void mark_scope (const symbol_record& sym, scope_flags flag) { + bool was_global = is_global (sym); + bool is_script = m_lazy_data && m_lazy_data->m_is_script; + bool add_global_to_scripts = flag == GLOBAL && is_script; + 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); @@ -733,6 +824,69 @@ internal_resize (local_offset + 1); 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) + { + 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 (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) + { + 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 + } + else + { + 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 + } + } } bool is_bytecode_fcn_frame (void) const { return true; } @@ -869,7 +1023,98 @@ std::weak_ptr m_weak_ptr_to_self; -private: + void vm_enter_script () + { + 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& script_symbols + = script_scope.symbols (); + const std::map& 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) + { + 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? + 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); + } + } + } + + void vm_exit_script () + { + // Restore values from the VM stack frame to the original frame + + if (!m_lazy_data || m_lazy_data->m_is_script != true) + return; + + 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++) + { + 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 {}; + } + } + // 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 @@ -884,6 +1129,10 @@ unwind_protect *m_unwind_protect_frame = nullptr; stack_element *m_stack_cpy = nullptr; + bool m_is_script; + + std::vector m_script_sr_originals; + std::vector m_script_local_refs; }; lazy_data_struct & lazy_data () @@ -895,7 +1144,8 @@ lazy_data_struct *m_lazy_data = nullptr; - octave_function *m_fcn; +private: + octave_user_code *m_fcn; unwind_data *m_unwind_data; @@ -985,12 +1235,12 @@ return m_static_link->varval (sym); } - octave_value& varref (const symbol_record& sym) + octave_value& varref (const symbol_record& sym, bool deref_refs) { // Look in closest stack frame that contains values (either the // top scope, or a user-defined function or script). - return m_static_link->varref (sym); + return m_static_link->varref (sym, deref_refs); } void mark_scope (const symbol_record& sym, scope_flags flag) @@ -1116,7 +1366,7 @@ octave_value varval (const symbol_record& sym) const; - octave_value& varref (const symbol_record& sym); + octave_value& varref (const symbol_record& sym, bool deref_refs); void mark_scope (const symbol_record& sym, scope_flags flag); @@ -1233,7 +1483,7 @@ return m_values.at (data_offset); } - octave_value& varref (std::size_t data_offset) + octave_value& varref (std::size_t data_offset, bool) { return m_values.at (data_offset); } @@ -1356,7 +1606,7 @@ octave_value varval (const symbol_record& sym) const; - octave_value& varref (const symbol_record& sym); + octave_value& varref (const symbol_record& sym, bool deref_refs); void mark_scope (const symbol_record& sym, scope_flags flag); @@ -1426,7 +1676,7 @@ octave_value varval (const symbol_record& sym) const; - octave_value& varref (const symbol_record& sym); + octave_value& varref (const symbol_record& sym, bool deref_refs); void mark_scope (const symbol_record& sym, scope_flags flag); @@ -1983,12 +2233,48 @@ return new scope_stack_frame (tw, scope, index, parent_link, static_link); } -std::shared_ptr -stack_frame::create_bytecode (tree_evaluator& tw, octave_user_function *fcn, - vm &vm, std::size_t index, - const std::shared_ptr& parent_link, - const std::shared_ptr& static_link, - int nargout, int nargin) +std::shared_ptr stack_frame::create_bytecode ( + tree_evaluator& tw, + octave_user_script *fcn, + vm &vm, + std::size_t index, + const std::shared_ptr& parent_link, + const std::shared_ptr& static_link, + int nargout, int nargin) +{ + auto frame = create_bytecode (tw, static_cast (fcn), vm, index, parent_link, static_link, nargout, nargin); + + std::shared_ptr eval_frame = static_link; + + while (true) + { + if (eval_frame->is_user_script_frame ()) + eval_frame = eval_frame->access_link (); + else if (eval_frame->is_bytecode_fcn_frame ()) + { + bytecode_fcn_stack_frame *bcf = static_cast (eval_frame.get ()); + if (bcf->m_lazy_data && bcf->m_lazy_data->m_is_script) + eval_frame = eval_frame->access_link (); + else + break; + } + else + break; + } + + frame->m_access_link = eval_frame; + + 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 @@ -2270,7 +2556,7 @@ panic_impossible (); } -octave_value& stack_frame::varref (std::size_t) +octave_value& stack_frame::varref (std::size_t, bool) { // This function should only be called for user_fcn_stack_frame or // scope_stack_frame objects. Anything else indicates an error in @@ -2903,7 +3189,7 @@ error ("internal error: invalid switch case"); } -octave_value& script_stack_frame::varref (const symbol_record& sym) +octave_value& script_stack_frame::varref (const symbol_record& sym, bool deref_refs) { std::size_t frame_offset; std::size_t data_offset; @@ -2926,7 +3212,7 @@ switch (frame->get_scope_flag (data_offset)) { case LOCAL: - return frame->varref (data_offset); + return frame->varref (data_offset, deref_refs); case PERSISTENT: { @@ -3235,7 +3521,7 @@ error ("internal error: invalid switch case"); } -octave_value& user_fcn_stack_frame::varref (const symbol_record& sym) +octave_value& user_fcn_stack_frame::varref (const symbol_record& sym, bool deref_refs) { std::size_t frame_offset = sym.frame_offset (); std::size_t data_offset = sym.data_offset (); @@ -3257,7 +3543,7 @@ switch (frame->get_scope_flag (data_offset)) { case LOCAL: - return frame->varref (data_offset); + return frame->varref (data_offset, deref_refs); case PERSISTENT: { @@ -3374,8 +3660,13 @@ switch (get_scope_flag (data_offset)) { case LOCAL: - return m_values.at (data_offset); - + { + octave_value ov = m_values.at (data_offset); + if (ov.is_ref ()) + return ov.ref_rep ()->deref (); + else + return ov; + } case PERSISTENT: return m_scope.persistent_varval (data_offset); @@ -3386,7 +3677,7 @@ error ("internal error: invalid switch case"); } -octave_value& scope_stack_frame::varref (const symbol_record& sym) +octave_value& scope_stack_frame::varref (const symbol_record& sym, bool deref_refs) { // There is no access link for scope frames, so the frame // offset must be zero. @@ -3399,8 +3690,13 @@ switch (get_scope_flag (data_offset)) { case LOCAL: - return m_values.at (data_offset); - + { + octave_value &ov = m_values.at (data_offset); + if (deref_refs && ov.is_ref ()) + return ov.ref_rep ()->ref (); + else + return ov; + } case PERSISTENT: return m_scope.persistent_varref (data_offset); diff -r 1cda2e27d8ca -r 1f1a1ecc7499 libinterp/corefcn/stack-frame.h --- a/libinterp/corefcn/stack-frame.h Sat Aug 19 12:57:29 2023 +0200 +++ b/libinterp/corefcn/stack-frame.h Sat Aug 19 13:05:33 2023 +0200 @@ -186,7 +186,16 @@ // Bytecode function stackframe static std::shared_ptr create_bytecode (tree_evaluator& tw, - octave_user_function *fcn, + 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); + + static std::shared_ptr + create_bytecode (tree_evaluator& tw, + octave_user_script *fcn, vm &vm, std::size_t index, const std::shared_ptr& parent_link, @@ -472,9 +481,9 @@ return sym ? varval (sym) : octave_value (); } - virtual octave_value& varref (const symbol_record& sym) = 0; + virtual octave_value& varref (const symbol_record& sym, bool deref_refs = true) = 0; - virtual octave_value& varref (std::size_t data_offset); + virtual octave_value& varref (std::size_t data_offset, bool deref_refs = true); void assign (const symbol_record& sym, const octave_value& val) { @@ -610,6 +619,8 @@ virtual void vm_unwinds () {} virtual void vm_dbg_check_scope () {} virtual void vm_clear_for_cache () {} + virtual void vm_enter_script () {} + virtual void vm_exit_script () {} protected: diff -r 1cda2e27d8ca -r 1f1a1ecc7499 libinterp/octave-value/ov-ref.cc --- a/libinterp/octave-value/ov-ref.cc Sat Aug 19 12:57:29 2023 +0200 +++ b/libinterp/octave-value/ov-ref.cc Sat Aug 19 13:05:33 2023 +0200 @@ -40,6 +40,11 @@ DEFINE_OV_TYPEID_FUNCTIONS_AND_DATA (octave_value_ref_persistent, "global value persistent", "global value persistent"); + +DEFINE_OV_TYPEID_FUNCTIONS_AND_DATA (octave_value_ref_vmlocal, + "local vm value reference", + "local vm value reference"); + void octave_value_ref::maybe_call_dtor () { @@ -121,3 +126,21 @@ { return m_scope.persistent_varref (m_offset); } + +octave_value & +octave_value_ref_vmlocal::ref () +{ + return m_frame->varref (m_sym); +} + +octave_value +octave_value_ref_vmlocal::deref () +{ + return m_frame->varval (m_sym); +} + +void +octave_value_ref_vmlocal::set_value (octave_value val) +{ + m_frame->varref (m_sym) = val; +} diff -r 1cda2e27d8ca -r 1f1a1ecc7499 libinterp/octave-value/ov-ref.h --- a/libinterp/octave-value/ov-ref.h Sat Aug 19 12:57:29 2023 +0200 +++ b/libinterp/octave-value/ov-ref.h Sat Aug 19 13:05:33 2023 +0200 @@ -31,7 +31,7 @@ #include "ov-base.h" #include "ovl.h" #include "symscope.h" - +#include "symrec.h" #include #include @@ -60,8 +60,9 @@ virtual octave_value & ref () = 0; virtual void maybe_save_state () {}; - virtual bool is_global_ref () { return false; } - virtual bool is_persistent_ref () { return false; } + virtual bool is_global_ref () const { return false; } + virtual bool is_persistent_ref () const { return false; } + virtual bool is_vmlocal_ref () const { return true; } void maybe_call_dtor (); octave_value simple_subsasgn (char type, octave_value_list& idx, const octave_value& rhs); @@ -84,7 +85,7 @@ octave_value & ref (); void set_value (octave_value val); - bool is_global_ref () { return true; } + bool is_global_ref () const { return true; } private: std::string m_name; @@ -105,7 +106,7 @@ octave_value & ref (); void set_value (octave_value val); - bool is_persistent_ref () { return true; } + bool is_persistent_ref () const { return true; } private: int m_offset; @@ -114,4 +115,26 @@ DECLARE_OV_TYPEID_FUNCTIONS_AND_DATA }; +class OCTINTERP_API +octave_value_ref_vmlocal : public octave_value_ref +{ +public: + octave_value_ref_vmlocal () = default; + ~octave_value_ref_vmlocal () = default; + octave_value_ref_vmlocal (octave::symbol_record sym, octave::stack_frame *frame) + : m_frame (frame), m_sym (sym) { } + + octave_value deref (); + octave_value & ref (); + void set_value (octave_value val); + + bool is_vmlocal_ref () const { return true; } + +private: + octave::stack_frame *m_frame = nullptr; + octave::symbol_record m_sym; + + DECLARE_OV_TYPEID_FUNCTIONS_AND_DATA +}; + #endif diff -r 1cda2e27d8ca -r 1f1a1ecc7499 libinterp/octave-value/ov-usr-fcn.cc --- a/libinterp/octave-value/ov-usr-fcn.cc Sat Aug 19 12:57:29 2023 +0200 +++ b/libinterp/octave-value/ov-usr-fcn.cc Sat Aug 19 13:05:33 2023 +0200 @@ -97,6 +97,20 @@ m_file_name.c_str ()); } +void +octave_user_code::clear_bytecode () +{ + m_bytecode = octave::bytecode {}; + + auto subs = subfunctions (); + for (auto kv : subs) + { + octave_user_function *sub = kv.second.user_function_value (); + if (sub) + sub->clear_bytecode (); + } +} + std::string octave_user_code::get_code_line (std::size_t line) { @@ -183,7 +197,28 @@ octave_user_script::call (octave::tree_evaluator& tw, int nargout, const octave_value_list& args) { - tw.push_stack_frame (this); + bool is_compiled = false; + + 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); + } octave::unwind_action act ([&tw] () { tw.pop_stack_frame (); }); diff -r 1cda2e27d8ca -r 1f1a1ecc7499 libinterp/octave-value/ov-usr-fcn.h --- a/libinterp/octave-value/ov-usr-fcn.h Sat Aug 19 12:57:29 2023 +0200 +++ b/libinterp/octave-value/ov-usr-fcn.h Sat Aug 19 13:05:33 2023 +0200 @@ -120,8 +120,23 @@ octave_value dump () const; + void set_bytecode (octave::bytecode &bytecode) + { + m_bytecode = bytecode; + } + + void clear_bytecode (); + + bool is_compiled () const { return m_bytecode.m_code.size (); } + + octave::bytecode &get_bytecode () { return m_bytecode; } + + bool m_compilation_failed = false; + protected: + octave::bytecode m_bytecode; + void get_file_info (); // Our symbol table scope. @@ -242,6 +257,8 @@ int ending_line () const { return m_end_location_line; } int ending_column () const { return m_end_location_column; } + octave::symbol_scope scope (void) { return m_scope; } + void maybe_relocate_end (); void stash_parent_fcn_scope (const octave::symbol_scope& ps); @@ -400,34 +417,8 @@ octave_value dump () const; - void set_bytecode (octave::bytecode &bytecode) - { - m_bytecode = bytecode; - } - - void clear_bytecode () - { - m_bytecode = octave::bytecode {}; - - auto subs = subfunctions (); - for (auto kv : subs) - { - octave_user_function *sub = kv.second.user_function_value (); - if (sub) - sub->clear_bytecode (); - } - } - - bool is_compiled () const { return m_bytecode.m_code.size (); } - - octave::bytecode &get_bytecode () { return m_bytecode; } - - bool m_compilation_failed = false; - private: - octave::bytecode m_bytecode; - enum class_method_type { none, diff -r 1cda2e27d8ca -r 1f1a1ecc7499 libinterp/octave-value/ov.h --- a/libinterp/octave-value/ov.h Sat Aug 19 12:57:29 2023 +0200 +++ b/libinterp/octave-value/ov.h Sat Aug 19 13:05:33 2023 +0200 @@ -50,6 +50,7 @@ class type_info; class vm; class bytecode_fcn_stack_frame; +class scope_stack_frame; OCTAVE_END_NAMESPACE(octave) @@ -1546,6 +1547,7 @@ friend class octave_value_vm; friend class octave::vm; friend class octave::bytecode_fcn_stack_frame; + friend class octave::scope_stack_frame; bool is_ref () const { return m_rep->is_ref (); } diff -r 1cda2e27d8ca -r 1f1a1ecc7499 libinterp/parse-tree/pt-bytecode-vm.cc --- a/libinterp/parse-tree/pt-bytecode-vm.cc Sat Aug 19 12:57:29 2023 +0200 +++ b/libinterp/parse-tree/pt-bytecode-vm.cc Sat Aug 19 13:05:33 2023 +0200 @@ -258,6 +258,8 @@ PRINT_OP (PUSH_DBL_0); PRINT_OP (PUSH_DBL_1); PRINT_OP (PUSH_DBL_2); + PRINT_OP (ENTER_SCRIPT_FRAME); + PRINT_OP (EXIT_SCRIPT_FRAME); CASE_START (WIDE) wide_opext_active = true; @@ -838,6 +840,8 @@ &&set_folded_cst, // SET_FOLDED_CST, &&wide, // WIDE &&subassign_id_mat_2d, + &&enter_script_frame, + &&exit_script_frame, }; if (OCTAVE_UNLIKELY (m_profiler_enabled)) @@ -2145,7 +2149,7 @@ } else if (fcn->is_compiled ()) { - octave_user_function *usr_fcn = static_cast (fcn); + octave_user_code *usr_fcn = static_cast (fcn); // Alot of code in this define MAKE_BYTECODE_CALL @@ -3769,7 +3773,7 @@ if (fcn->is_compiled ()) { - octave_user_function *usr_fcn = static_cast (fcn); + octave_user_code *usr_fcn = static_cast (fcn); // Alot of code in this define bool has_cs_list_arg = false; MAKE_BYTECODE_CALL @@ -4111,7 +4115,7 @@ if (fcn->is_compiled ()) { - octave_user_function *usr_fcn = static_cast (fcn); + octave_user_code *usr_fcn = static_cast (fcn); // Alot of code in this define MAKE_BYTECODE_CALL @@ -4425,7 +4429,12 @@ goto bail_unwind; } - m_tw->get_current_stack_frame ()->vm_unwinds (); + if (!m_could_not_push_frame) + { + auto sf = m_tw->get_current_stack_frame (); + sf->vm_exit_script (); + sf->vm_unwinds (); + } // Destroy locals down to nargout while (m_sp != m_bsp + 1) @@ -4523,7 +4532,7 @@ octave_value &ov_slot = bsp[slot].ov; bool slot_already_live = ov_slot.is_defined (); - bool is_marked_in_VM = ov_slot.is_ref (); + bool is_marked_in_VM = ov_slot.is_ref (); // TODO: Can this be other refs? // The next instruction is whether the global declare has an // initialization value @@ -5390,7 +5399,7 @@ if (fcn->is_compiled ()) { - octave_user_function *usr_fcn = static_cast (fcn); + octave_user_code *usr_fcn = static_cast (fcn); // Alot of code in this define MAKE_BYTECODE_CALL @@ -5802,6 +5811,18 @@ ip += 2; // Forward ip so it points to after the widened argument goto *instr [opcode]; } + enter_script_frame: + { + auto fp = m_tw->get_current_stack_frame (); + fp->vm_enter_script (); + } + DISPATCH_1BYTEOP (); + exit_script_frame: + { + auto fp = m_tw->get_current_stack_frame (); + fp->vm_exit_script (); + } + DISPATCH_1BYTEOP (); __builtin_unreachable (); } diff -r 1cda2e27d8ca -r 1f1a1ecc7499 libinterp/parse-tree/pt-bytecode-walk.cc --- a/libinterp/parse-tree/pt-bytecode-walk.cc Sat Aug 19 12:57:29 2023 +0200 +++ b/libinterp/parse-tree/pt-bytecode-walk.cc Sat Aug 19 13:05:33 2023 +0200 @@ -37,7 +37,7 @@ using namespace octave; -void octave::compile_user_function (octave_user_function &ufn, bool do_print) +void octave::compile_user_function (octave_user_code &ufn, bool do_print) { try { @@ -2036,6 +2036,147 @@ void bytecode_walker:: +visit_octave_user_script (octave_user_script& fcn) +{ + m_is_script = true; + + m_code.m_unwind_data.m_name = fcn.name (); + m_code.m_unwind_data.m_file = fcn.fcn_file_name (); + PUSH_DATA (fcn.name ()); + PUSH_DATA (std::string {"user-script"}); + PUSH_DATA (fcn.profiler_name ()); + + tree_statement_list *cmd_list = fcn.body (); + + // The first instruction is the amount of return variables. + PUSH_CODE (1); // Only the dummy return '%nargout' + + // The second instruction is the amount of arguments + PUSH_CODE (0); + + // The third is the amount of locals, which need to be set + // after compiling the function. So we need to store the offset + // to it for later + m_offset_n_locals = CODE_SIZE (); + PUSH_CODE (-1); // Placeholder + PUSH_CODE (-1); + + // The first slot is a native int represenation nargout + // so we add a dummy slot object for it + add_id_to_table("%nargout"); + + // We always need the magic id "ans" + add_id_to_table ("ans"); + + // We add all identifiers in the body to the id-table. We also + // make a map mapping the interpreters frame offset of a id + // to the frame offset in the bytecode VM frame. + if (cmd_list) + { + auto v_names_offsets = collect_idnames_walker::collect_id_names (*cmd_list); + + 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); + int slot = SLOT (name); + + m_code.m_unwind_data.m_external_frame_offset_to_internal[frame_offset] = slot; + } + } + + // The function name should be in the frame as an id too + 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 = 1; // "1" since 'ans' is always added first + + 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[offset] = slot; + } + else + continue; + } + + if (name == "varargin") + m_code.m_unwind_data.m_external_frame_offset_to_internal[offset] = SLOT ("varargin"); + else if (name == "varargout") + m_code.m_unwind_data.m_external_frame_offset_to_internal[offset] = SLOT ("varargout"); + else if (name == "ans") + m_code.m_unwind_data.m_external_frame_offset_to_internal[offset] = SLOT ("ans"); + } + + PUSH_CODE (INSTR::ENTER_SCRIPT_FRAME); // Special opcode to steal the "eval scopes" values + + CHECK_NONNULL (cmd_list); + cmd_list->accept (*this); + + // EXIT_SCRIPT_FRAME is put before each RET during the walk. + + // Set the amount of locals that has a placeholder since earlier + SET_CODE_SHORT (m_offset_n_locals, m_n_locals); + + // We want to add the locals to the scope in slot order + // so we push all the locals' names to a vector by their slot + // number + unsigned n_slots = m_map_locals_to_slot.size (); + CHECK (n_slots == static_cast (m_n_locals)); + std::vector names (n_slots); + + auto iter = m_map_locals_to_slot.begin (); + for (unsigned i = 0; i < n_slots; i++) + { + auto kv = *iter++; + + const std::string& name = kv.first; + int slot = kv.second; + + CHECK (slot >= 0 && slot < static_cast (n_slots)); + CHECK (names[slot] == ""); // Check not duplicate slot number used + + names[slot] = name; + } + + // 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) + { + 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 (); +} + +void +bytecode_walker:: visit_octave_user_function (octave_user_function& fcn) { m_code.m_unwind_data.m_name = fcn.name (); @@ -2712,6 +2853,9 @@ } } + if (m_is_script) + PUSH_CODE (INSTR::EXIT_SCRIPT_FRAME); + PUSH_CODE (INSTR::RET); } diff -r 1cda2e27d8ca -r 1f1a1ecc7499 libinterp/parse-tree/pt-bytecode-walk.h --- a/libinterp/parse-tree/pt-bytecode-walk.h Sat Aug 19 12:57:29 2023 +0200 +++ b/libinterp/parse-tree/pt-bytecode-walk.h Sat Aug 19 13:05:33 2023 +0200 @@ -42,7 +42,7 @@ namespace octave { - void compile_user_function (octave_user_function &fn, bool print); + void compile_user_function (octave_user_code &fn, bool print); // No separate visitor needed // Base classes only, so no need to include them. @@ -152,6 +152,7 @@ symbol_scope m_scope; bool m_varargout = false; + bool m_is_script = false; std::vector> m_continue_target; std::vector> m_need_break_target; @@ -273,7 +274,7 @@ void visit_spmd_command (tree_spmd_command&) ERROR_NOT_IMPLEMENTED - void visit_octave_user_script (octave_user_script&) ERROR_NOT_IMPLEMENTED + void visit_octave_user_script (octave_user_script&); void visit_octave_user_function (octave_user_function&); diff -r 1cda2e27d8ca -r 1f1a1ecc7499 libinterp/parse-tree/pt-bytecode.h --- a/libinterp/parse-tree/pt-bytecode.h Sat Aug 19 12:57:29 2023 +0200 +++ b/libinterp/parse-tree/pt-bytecode.h Sat Aug 19 13:05:33 2023 +0200 @@ -182,6 +182,8 @@ SET_FOLDED_CST, WIDE, SUBASSIGN_ID_MAT_2D, + ENTER_SCRIPT_FRAME, + EXIT_SCRIPT_FRAME, }; enum class unwind_entry_type diff -r 1cda2e27d8ca -r 1f1a1ecc7499 libinterp/parse-tree/pt-eval.cc --- a/libinterp/parse-tree/pt-eval.cc Sat Aug 19 12:57:29 2023 +0200 +++ b/libinterp/parse-tree/pt-eval.cc Sat Aug 19 13:05:33 2023 +0200 @@ -2472,6 +2472,20 @@ m_call_stack.push (vm, fcn, nargout, nargin); } +void tree_evaluator::push_stack_frame (vm &vm, octave_user_script *fcn, int nargout, int nargin) +{ + m_call_stack.push (vm, fcn, nargout, nargin); +} + +void tree_evaluator::push_stack_frame (vm &vm, octave_user_code *fcn, int nargout, int nargin) +{ + if (fcn->is_user_function ()) + m_call_stack.push (vm, static_cast (fcn), nargout, nargin); + else + m_call_stack.push (vm, static_cast (fcn), nargout, nargin); +} + + void tree_evaluator::pop_stack_frame () { m_call_stack.pop (); @@ -3459,24 +3473,56 @@ if (m_call_stack.size () >= static_cast (m_max_recursion_depth)) error ("max_recursion_depth exceeded"); - 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; + // 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; + } } void diff -r 1cda2e27d8ca -r 1f1a1ecc7499 libinterp/parse-tree/pt-eval.h --- a/libinterp/parse-tree/pt-eval.h Sat Aug 19 12:57:29 2023 +0200 +++ b/libinterp/parse-tree/pt-eval.h Sat Aug 19 13:05:33 2023 +0200 @@ -443,6 +443,10 @@ void push_stack_frame (vm &vm, octave_user_function *fcn, int nargout, int nargin); + void push_stack_frame (vm &vm, octave_user_script *fcn, int nargout, int nargin); + + void push_stack_frame (vm &vm, octave_user_code *fcn, int nargout, int nargin); + void pop_stack_frame (); std::shared_ptr pop_return_stack_frame (); diff -r 1cda2e27d8ca -r 1f1a1ecc7499 test/compile/bytecode.tst --- a/test/compile/bytecode.tst Sat Aug 19 12:57:29 2023 +0200 +++ b/test/compile/bytecode.tst Sat Aug 19 13:05:33 2023 +0200 @@ -661,3 +661,17 @@ %! %! assert (n_c == __ref_count__ (c)) %! assert (n_d == __ref_count__ (d)) + +## Test scripts +%!test +%! __enable_vm_eval__ (0, "local"); +%! clear all +%! key = "0 1 3 4 5 3 "; +%! __compile__ bytecode_scripts clear; +%! bytecode_scripts; +%! assert (__prog_output_assert__ (key)); +%! +%! __enable_vm_eval__ (1, "local"); +%! assert (__compile__ ("bytecode_scripts")); +%! bytecode_scripts; +%! assert (__prog_output_assert__ (key)); diff -r 1cda2e27d8ca -r 1f1a1ecc7499 test/compile/bytecode_scripts.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/compile/bytecode_scripts.m Sat Aug 19 13:05:33 2023 +0200 @@ -0,0 +1,21 @@ +function bytecode_scripts + % Each variable is named after the corrensponding script that messes with them. + % When eval() is used, it is to not introduce the variable in the static precompiled + % stack frame, to test the dynamic stack frame. + + b1 = 2; + __printf_assert__ ("%d ", exist ("a1")); + script1 (); % defines 'a1', 'b1', 'c1', 'd1' + __printf_assert__ ("%d ", eval ("a1;")); + __printf_assert__ ("%d ", b1); + __printf_assert__ ("%d ", eval ("c1;")); + __printf_assert__ ("%d ", d1); + + try + script2 (); % defines 'a2 = 3' and then errors + catch + __printf_assert__ ("%d ", a2); + end + + script3 (); +end \ No newline at end of file diff -r 1cda2e27d8ca -r 1f1a1ecc7499 test/compile/module.mk --- a/test/compile/module.mk Sat Aug 19 12:57:29 2023 +0200 +++ b/test/compile/module.mk Sat Aug 19 13:05:33 2023 +0200 @@ -24,6 +24,7 @@ %reldir%/bytecode_persistant.m \ %reldir%/bytecode_range.m \ %reldir%/bytecode_return.m \ + %reldir%/bytecode_scripts.m \ %reldir%/bytecode_struct.m \ %reldir%/bytecode_subfuncs.m \ %reldir%/bytecode_subsasgn.m \ @@ -39,6 +40,10 @@ %reldir%/inputname_args.m \ %reldir%/just_call_handle_with_arg.m \ %reldir%/return_isargout.m \ + %reldir%/script1.m \ + %reldir%/script11.m \ + %reldir%/script2.m \ + %reldir%/script3.m \ %reldir%/shutup_operator_test/@double/display.m \ %reldir%/shutup_operator_test/@logical/display.m \ %reldir%/shutup_operator_test/bytecode_disp.m \ diff -r 1cda2e27d8ca -r 1f1a1ecc7499 test/compile/script1.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/compile/script1.m Sat Aug 19 13:05:33 2023 +0200 @@ -0,0 +1,11 @@ +a1 = 1; +b1 = 3; +eval ("c1 = 4;") +eval ("d1 = 5;") + +script11 (); + +assert (a11 == 1) +assert (b11 == 3) +eval ("assert (c11 == 4)") +eval ("assert (d11 == 5)") \ No newline at end of file diff -r 1cda2e27d8ca -r 1f1a1ecc7499 test/compile/script11.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/compile/script11.m Sat Aug 19 13:05:33 2023 +0200 @@ -0,0 +1,5 @@ +a11 = 1; +b11 = 3; + +eval ("c11 = 4;") +eval ("d11 = 5;") \ No newline at end of file diff -r 1cda2e27d8ca -r 1f1a1ecc7499 test/compile/script2.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/compile/script2.m Sat Aug 19 13:05:33 2023 +0200 @@ -0,0 +1,3 @@ +a2 = 3; + +error ("Error in script"); \ No newline at end of file diff -r 1cda2e27d8ca -r 1f1a1ecc7499 test/compile/script3.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/compile/script3.m Sat Aug 19 13:05:33 2023 +0200 @@ -0,0 +1,7 @@ +% Check that variables from script one are accessable +% and have the correct value. +assert (a1 == 1); +assert (b1 == 3); +assert (c1 == 4); +eval ("assert (d1 == 5)"); +assert (a2 == 3); \ No newline at end of file