# HG changeset patch # User Petter T. # Date 1698423603 -7200 # Fri Oct 27 18:20:03 2023 +0200 # Node ID d28004448cc0fdb2235a92da523474574add409b # Parent 393ba8df4b1df16fd9b59989997f652a81993d28 Split chained subsrefs to multiple instructions To allow for bytecode calls in the middle of a chained subsref, instead of allocating a new bytecode interpreter for chained calls. I.e. the vm need a separate instruction to return to, for each chained subsref. Use a wrapper octave_base_value derived class to support collecting arguments for a classdef subsref. * ov-base.h: New function is_vm_chainargs_wrapper * ov-cell.h,c: Implement simple_subsref, used in new opcode * ov-vm.h: New octave_base_value class octave_vm_chainargs_wrapper * ov.h: New function is_vm_chainargs_wrapper * pt-bytecode-vm-internal.h: Update calling convention * pt-bytecode-vm.cc: New opcode * pt-bytecode-walk.cc: Emit new opcode * pt-bytecode.h: New opcode diff -r 393ba8df4b1d -r d28004448cc0 libinterp/octave-value/ov-base.h --- a/libinterp/octave-value/ov-base.h Fri Oct 27 18:19:56 2023 +0200 +++ b/libinterp/octave-value/ov-base.h Fri Oct 27 18:20:03 2023 +0200 @@ -828,6 +828,8 @@ virtual bool is_ref () const { return false; } + virtual bool is_vm_chainargs_wrapper () const { return false; } + virtual octave_value_ref * ref_rep (); virtual octave_value diff -r 393ba8df4b1d -r d28004448cc0 libinterp/octave-value/ov-cell.cc --- a/libinterp/octave-value/ov-cell.cc Fri Oct 27 18:19:56 2023 +0200 +++ b/libinterp/octave-value/ov-cell.cc Fri Oct 27 18:20:03 2023 +0200 @@ -151,6 +151,50 @@ } octave_value_list +octave_cell::simple_subsref (char type, octave_value_list& idx, int) +{ + octave_value_list retval; + + switch (type) + { + case '(': + retval(0) = do_index_op (idx); + break; + + case '{': + { + if (idx.empty ()) + error ("invalid empty index expression {}, use {:} instead"); + + octave_value tmp = do_index_op (idx); + + Cell tcell = tmp.cell_value (); + + if (tcell.numel () == 1) + retval(0) = tcell(0, 0); + else + { + // Return a comma-separated list. + retval = octave_value (octave_value_list (tcell)); + } + } + break; + + case '.': + { + std::string nm = type_name (); + error ("%s cannot be indexed with %c", nm.c_str (), type); + } + break; + + default: + panic_impossible (); + } + + return retval; +} + +octave_value_list octave_cell::subsref (const std::string& type, const std::list& idx, int nargout) diff -r 393ba8df4b1d -r d28004448cc0 libinterp/octave-value/ov-cell.h --- a/libinterp/octave-value/ov-cell.h Fri Oct 27 18:19:56 2023 +0200 +++ b/libinterp/octave-value/ov-cell.h Fri Oct 27 18:20:03 2023 +0200 @@ -180,6 +180,9 @@ octave_base_value::vm_call_dispatch_type vm_dispatch_call (void) { return vm_call_dispatch_type::SUBSREF; } + octave_value_list + simple_subsref (char type, octave_value_list& idx, int nargout); + private: void clear_cellstr_cache () const diff -r 393ba8df4b1d -r d28004448cc0 libinterp/octave-value/ov-vm.h --- a/libinterp/octave-value/ov-vm.h Fri Oct 27 18:19:56 2023 +0200 +++ b/libinterp/octave-value/ov-vm.h Fri Oct 27 18:20:03 2023 +0200 @@ -200,4 +200,43 @@ octave_idx_type m_n_updated = 0; }; +// Class that is a wrapper around arguments and subsref type (i.e. '(','{' and '.') +// that are needed for building up the args for a classdef subsref. The object +// that will be called is set in the constructor of the wrapper. +// +// The reason it is in an octave_value is to make unwinding of the VM stack easier. +// +// INDEX_STRUCT_SUBCALL adds one set of args and type for each execution of itself, +// and the last INDEX_STRUCT_SUBCALL makes a subsref of the classdef object with all +// the args. +class octave_vm_chainargs_wrapper : public octave_base_value +{ +public: + octave_vm_chainargs_wrapper (octave_value obj_to_call) : m_obj_to_call (obj_to_call) {} + + // Invalid to call after steal + void append_args (octave_value_list &&ovl) + { + m_idxs.push_back (ovl); + } + + // Invalid to call after steal + void append_type (char type) + { + m_types.push_back (type); + } + + bool is_vm_chainargs_wrapper () const { return true; } + + // Only callable once + std::list && steal_idxs () { return std::move (m_idxs); } + std::string && steal_types () { return std::move (m_types); } + octave_value && steal_obj_to_call () { return std::move (m_obj_to_call); } + +private: + std::list m_idxs; + std::string m_types; + octave_value m_obj_to_call; +}; + #endif diff -r 393ba8df4b1d -r d28004448cc0 libinterp/octave-value/ov.h --- a/libinterp/octave-value/ov.h Fri Oct 27 18:19:56 2023 +0200 +++ b/libinterp/octave-value/ov.h Fri Oct 27 18:20:03 2023 +0200 @@ -1554,6 +1554,8 @@ bool is_ref () const { return m_rep->is_ref (); } + bool is_vm_chainargs_wrapper () const { return m_rep->is_vm_chainargs_wrapper (); } + octave_value_ref * ref_rep () { return m_rep->ref_rep (); } bool is_nil (void) const { return m_rep == nil_rep (); } diff -r 393ba8df4b1d -r d28004448cc0 libinterp/parse-tree/pt-bytecode-vm-internal.h --- a/libinterp/parse-tree/pt-bytecode-vm-internal.h Fri Oct 27 18:19:56 2023 +0200 +++ b/libinterp/parse-tree/pt-bytecode-vm-internal.h Fri Oct 27 18:20:03 2023 +0200 @@ -28,7 +28,7 @@ #include "octave-config.h" -#define EXPAND_CSLIST_PUSH_N_OVL_ELEMENTS_TO_STACK(ovl,n) \ +#define EXPAND_CSLIST_PUSH_N_OVL_ELEMENTS_TO_STACK(ovl,nargout) \ do {\ if (nargout <= 1)\ PUSH_OV (ovl.first_or_nil_ov());\ @@ -345,8 +345,12 @@ (*sp++).pse = bsp; \ \ /* Push the instruction pointer */ \ +(*sp++).puc = ip; \ + \ +/* The amount of return values the caller actually wants. Not necesserely the */ \ +/* same as the amount of return values the caller wants the callee to produce. */ \ /* (last on caller stack) */ \ -(*sp++).puc = ip; \ +(*sp++).u = caller_nvalback; \ \ /* set callee bsp */ \ m_sp = bsp = sp; \ diff -r 393ba8df4b1d -r d28004448cc0 libinterp/parse-tree/pt-bytecode-vm.cc --- a/libinterp/parse-tree/pt-bytecode-vm.cc Fri Oct 27 18:19:56 2023 +0200 +++ b/libinterp/parse-tree/pt-bytecode-vm.cc Fri Oct 27 18:20:03 2023 +0200 @@ -50,6 +50,7 @@ #include "ov-vm.h" #include "ov-fcn-handle.h" +#include "ov-cs-list.h" //#pragma GCC optimize("O0") @@ -330,6 +331,8 @@ CASE_START (POP_N_INTS) PCHAR () CASE_END () CASE_START (DUP_MOVE) PCHAR () CASE_END () + CASE_START (INDEX_STRUCT_SUBCALL) PCHAR () PCHAR () PCHAR () PCHAR () PCHAR_AS_CHAR () CASE_END () + CASE_START (MUL_CST) PCHAR () PCHAR () CASE_END () CASE_START (MUL_CST_DBL) PCHAR () PCHAR () CASE_END () CASE_START (DIV_CST) PCHAR () PCHAR () CASE_END () @@ -434,20 +437,9 @@ CASE_START (INDEX_STRUCT_CALL) PCHAR () - PCHAR () - PCHAR () - PCHAR () + PWSLOT () PCHAR () - int nn = *p; - s += " {"; - for (int i = 0; i < nn; i++) - { - PCHAR () - PCHAR_AS_CHAR () - if (i + 1 != nn) - s += ", "; - } - s += "} "; + PCHAR_AS_CHAR () CASE_END () CASE_START (LOAD_FAR_CST) PINT () CASE_END () @@ -720,6 +712,62 @@ ovl.append (cslist); } + +// Note: The function assumes the ip points to the next opcode. I.e. +// the current opcode it searches for entries for is at ip - 1. +arg_name_entry get_argname_entry (int ip, unwind_data *unwind_data) +{ + int best_match = -1; + int best_start = -1; + + auto &entries = unwind_data->m_argname_entries; + for (unsigned i = 0; i < entries.size (); i++) + { + int start = entries[i].m_ip_start; + int end = entries[i].m_ip_end; + + if (start > (ip - 1) || end < (ip - 1)) + continue; + + if (best_match != -1) + { + if (best_start > start) + continue; + } + + best_match = i; + best_start = start; + } + + if (best_match == -1) + return {}; + + return entries[best_match]; +} + +// Expand any cs-list among the args on the stack. +// I.e. if there is e.g. a two element cs-list +// among the args, we need to expand it and move up the +// rest of the args, on the stack. +// +// Modifies sp and n_args_on_stack ... +#define EXPAND_ARGS_CSLISTS_ON_STACK \ +{ \ + stack_element *pov = &sp[-n_args_on_stack]; \ + while (pov != sp) \ + { \ + if (OCTAVE_UNLIKELY (pov->ov.is_cs_list ())) \ + { \ + int n_change = expand_cslist_inplace (pov, sp); \ + sp += n_change; \ + pov += n_change; \ + n_args_on_stack += n_change; \ + } \ + \ + pov++; \ + } \ +} + #define POP_STACK_RANGE_TO_OVL(ovl, beg, end) \ do { \ stack_element *pbeg = beg; \ @@ -1047,6 +1095,7 @@ &&pow_cst, &&push_i, &&push_e, + &&index_struct_subcall, }; if (OCTAVE_UNLIKELY (m_profiler_enabled)) @@ -1371,8 +1420,10 @@ stack_element *caller_stack_end = sp - n_returns_callee; sp = caller_stack_end; // sp points to one past caller stack - int callee_nargout = caller_stack_end[0].i; - //TODO: Check nargout + // The amount of return values the caller wants, as stored last on the caller stack. + // Note that this is not necessarily the same as nargout, the amount of return values the caller + // want the callee to produce, stored first on callee stack. + int caller_nval_back = (*--sp).u; // Restore ip ip = (*--sp).puc; @@ -1403,7 +1454,7 @@ // Move the callee's return values to the top of the stack of the caller. // Renaming variables to keep my sanity. - int n_args_caller_expects = callee_nargout; + int n_args_caller_expects = caller_nval_back; int n_args_callee_has = n_returns_callee - 1; // Exclude %nargout int n_args_to_move = std::min (n_args_caller_expects, n_args_callee_has); int n_args_actually_moved = 0; @@ -1977,6 +2028,7 @@ // Alot of code in this define PUSH_OV (ov); // Calling convention anticipates object to call on the stack. int n_args_on_stack = 0; + int caller_nvalback = nargout; // Caller wants as many values returned as it wants the callee to produce MAKE_BYTECODE_CALL // Now dispatch to first instruction in the @@ -2473,6 +2525,7 @@ octave_user_code *usr_fcn = static_cast (fcn); // Alot of code in this define + int caller_nvalback = nargout; // Caller wants as many values returned as it wants the callee to produce MAKE_BYTECODE_CALL // Now dispatch to first instruction in the @@ -4129,6 +4182,7 @@ { octave_user_code *usr_fcn = static_cast (fcn); // Alot of code in this define + int caller_nvalback = nargout; // Caller wants as many values returned as it wants the callee to produce MAKE_BYTECODE_CALL // Now dispatch to first instruction in the @@ -4448,6 +4502,7 @@ { octave_user_code *usr_fcn = static_cast (fcn); // Alot of code in this define + int caller_nvalback = nargout; // Caller wants as many values returned as it wants the callee to produce MAKE_BYTECODE_CALL // Now dispatch to first instruction in the @@ -4522,8 +4577,8 @@ // Recreate first arg and n_args_on_stack // from the stack - stack_element *first_arg = sp[-8].pse; - int n_args_on_stack = (sp - 8) - first_arg; + stack_element *first_arg = sp[-9].pse; + int n_args_on_stack = (sp - 9) - first_arg; // Construct return values - note nargout // is allready pushed as a uint64 @@ -4785,6 +4840,9 @@ } } + // Skipp caller_nvalback + m_sp--; + // Restore ip ip = (*--m_sp).puc; @@ -5198,210 +5256,104 @@ index_struct_call: { + // This opcode is a setup for a chain of opcodes that each does a part + // in a chained subsref. + // + // It is done like this since there can be bytecode function calls in the middle + // of the chain and the call can only return to an opcode, not some arbitrary point in + // the C++ code. + int nargout = arg0; - // TODO: Kludge alert. Mirror the behaviour in ov_classdef::subsref - // where under certain conditions a magic number nargout of -1 is - // expected to maybe return a cs-list. "-1" in this context - // does not have the same meaning as in the VM, where it means - // a varargout with only one return symbol 'varargout'. - if (nargout == 255) - nargout = -1; - bool has_slot = *ip++; int slot = POP_CODE_USHORT (); - - int n_subs = POP_CODE (); - - std::list idx; - - std::vector v_n_args; - std::string types; - types.resize (n_subs); - - for (int i = 0; i < n_subs; i++) - { - v_n_args.push_back (POP_CODE ()); - types[i] = POP_CODE (); - } - - // Add the args of each sub indexing to the idx list - for (int i = 0; i < n_subs; i++) - { - octave_value_list ovl; - int n_args = v_n_args[n_subs - 1 - i]; // Note, from end to beginning - for (int j = 0; j < n_args; j++) + int n_args_on_stack = POP_CODE (); + char type = POP_CODE (); + + // The object being indexed is on the stack under the arguments + octave_value &ov = sp[-1 - n_args_on_stack].ov; + + // If there is a slot specified, we need to check if we need to call + // its octave_value (e.g. a handle) or a function corrensponding to its name. + if (slot) + { + if (ov.is_nil ()) { - octave_value &arg = TOP_OV (); - - if (arg.is_cs_list ()) - ovl.append (arg.list_value ().reverse ()); + // The slot the object being indexed was pushed from + octave_value &slot_ov = bsp[slot].ov; + + // Put a function cache object in the slot and in the local ov + ov = octave_value (new octave_fcn_cache (name_data[slot])); + if (slot_ov.is_ref()) + slot_ov.ref_rep ()->set_value (ov); else - ovl.append (std::move (arg)); - STACK_DESTROY (1); + slot_ov = ov; } - // args are pushed left to right to stack, so we need to reverse the ovl - ovl.reverse (); - idx.push_back (ovl); - } - idx.reverse (); // expressions was pushed left to right, so we need to reverse - - octave_value &ov = TOP_OV (); - octave_value_list retval; - try - { - m_tw->set_active_bytecode_ip (ip - code); - - // TODO: Need to figure out a clean way to do this kind of stuff. - // See tree_index_expression::evaluate_n() - // It should be general, because I don't think this will always work? - int cntr = 0; - while (ov_need_stepwise_subsrefs(ov) && types.size () > 0) + + // Should we call the object? + if (ov.vm_dispatch_call () == octave_base_value::vm_call_dispatch_type::CALL) { - bool eat_args = true; - std::string step_type {types.front ()}; - - std::list step_idx; - step_idx.push_back (idx.front ()); - - int step_nargout = types.size () > 1 ? 1 : nargout; - - if (cntr == 0 && ov.is_nil () && has_slot) + CHECK_PANIC (ov.has_function_cache ()); + + octave_function *fcn; + + octave_value_list ovl; + // The operands are on the top of the stack + POP_STACK_RANGE_TO_OVL (ovl, sp - n_args_on_stack, sp); + + try { - // Put a function cache object in the slot and in the local ov - ov = octave_value (new octave_fcn_cache (name_data[slot])); - - if (OCTAVE_UNLIKELY (bsp[slot].ov.is_ref ())) - bsp[slot].ov.ref_rep ()->set_value (ov); - else - bsp[slot].ov = ov; - - } - - if (cntr == 0 && ov.has_function_cache () && has_slot && !ov.is_classdef_meta ()) - { - octave_function *fcn; - try + if (type == '(') + { + fcn = ov.get_cached_fcn (ovl); + } + else // { or . { - if (step_type == "(") - { - octave_value_list &ovl = idx.front (); - fcn = ov.get_cached_fcn (ovl); - } - else // { or . - { - fcn = ov.get_cached_fcn (static_cast (nullptr), static_cast (nullptr)); - eat_args = false; // The function call has no args - } - } - CATCH_EXECUTION_EXCEPTION // parse errors might throw in classdefs - - if (! fcn) - { - (*sp++).ps = new std::string {name_data[slot]}; - (*sp++).i = static_cast(error_type::ID_UNDEFINED); - goto unwind; + fcn = ov.get_cached_fcn (static_cast (nullptr), static_cast (nullptr)); } - - try + } + CATCH_EXECUTION_EXCEPTION // parse errors might throw in classdefs + + if (! fcn) + { + (*sp++).ps = new std::string {name_data[slot]}; + (*sp++).i = static_cast(error_type::ID_UNDEFINED); + goto unwind; + } + + try + { + // TODO: Bytecode call + + octave_value_list retval; + m_tw->set_active_bytecode_ip (ip - code); + if (type == '(') { - m_tw->set_active_bytecode_ip (ip - code); - if (eat_args) - retval = fcn->call (*m_tw, step_nargout, idx.front ()); + // Skip the following subsref. Need to check for nargout extension. + if (*ip == static_cast (INSTR::EXT_NARGOUT)) + ip += 7; // Skip EXT_NARGOUT + STRUCT_INDEX_SUBCALL else - retval = fcn->call (*m_tw, step_nargout, {}); - // TODO: Bytecode call. - } - CATCH_INTERRUPT_EXCEPTION - CATCH_INDEX_EXCEPTION - CATCH_EXECUTION_EXCEPTION - CATCH_BAD_ALLOC - CATCH_EXIT_EXCEPTION - } - else if (ov.is_function () && !ov.is_classdef_meta ()) - { - octave_function *fcn = ov.function_value (true); - if (fcn) - { - octave_value_list args = idx.front (); - retval = fcn->call (*m_tw, step_nargout, args); + ip += 6; // Skip STRUCT_INDEX_SUBCALL + retval = fcn->call (*m_tw, nargout, ovl); } else - PANIC ("Silly state"); - } - else - { - try { - retval = ov.subsref (step_type.c_str (), step_idx, step_nargout); + retval = fcn->call (*m_tw, nargout, {}); } - CATCH_INTERRUPT_EXCEPTION - CATCH_INDEX_EXCEPTION_WITH_MAYBE_NAME (has_slot && cntr == 0) - CATCH_EXECUTION_EXCEPTION - CATCH_BAD_ALLOC - CATCH_EXIT_EXCEPTION + + STACK_DESTROY (1); // Destroy the ov being indexed + PUSH_OV (retval.first_or_nil_ov ()); // Push the next ov to be indexed } - - // If the first retval has zero length and we got more to go, it is an error - if (cntr == 0 && step_type.size() > 1 && retval.length () == 0) - { - (*sp++).pee = new execution_exception {"error","","indexing undefined value"}; - (*sp++).i = static_cast (error_type::EXECUTION_EXC); - goto unwind; - } - - ov = retval.first_or_nil_ov (); - - if (cntr != 0 && ov.is_cs_list () && step_type.size() > 1) - { - (*sp++).pee = new execution_exception {"error","","a cs-list cannot be further indexed"}; - (*sp++).i = static_cast (error_type::EXECUTION_EXC); - goto unwind; - } - - if (eat_args) - { - types = types.substr(1, types.size () - 1); - idx.pop_front (); - } - cntr++; + CATCH_INTERRUPT_EXCEPTION + CATCH_INDEX_EXCEPTION + CATCH_EXECUTION_EXCEPTION + CATCH_BAD_ALLOC + CATCH_EXIT_EXCEPTION } - - if ((!ov.is_function () || ov.is_classdef_meta ()) && types.size ()) - { - retval = ov.subsref (types.c_str (), idx, nargout); - idx.pop_front (); - } - - octave_value val = (retval.length () ? retval(0) : octave_value ()); - if (val.is_function ()) - { - octave_function *fcn = val.function_value (true); - - if (fcn) - { - octave_value_list args = idx.size () ? idx.front () : (octave_value_list){}; - retval = fcn->call (*m_tw, nargout, args); - } - } - - idx.clear (); - } - CATCH_INTERRUPT_EXCEPTION - CATCH_INDEX_EXCEPTION - CATCH_EXECUTION_EXCEPTION - CATCH_BAD_ALLOC - CATCH_EXIT_EXCEPTION - - STACK_DESTROY (1); - // TODO: Kludge alert. Mirror the behaviour in ov_classdef::subsref - // where under certain conditions a magic number nargout of -1 is - // expected to maybe return a cs-list. "-1" in this context - // does not have the same meaning as in the VM, where it means - // a varargout with only one return symbol 'varargout'. - if (nargout == -1) - nargout = 1; - EXPAND_CSLIST_PUSH_N_OVL_ELEMENTS_TO_STACK (retval, nargout); + } + + // The next instruction is a INDEX_STRUCT_SUBCALL + // One for each part of the chain } - DISPATCH (); + DISPATCH (); // TODO: Make hardcoded goto to index_struct_subcall: index_struct_n: { @@ -5755,6 +5707,7 @@ { octave_user_code *usr_fcn = static_cast (fcn); // Alot of code in this define + int caller_nvalback = nargout; // Caller wants as many values as it wants the callee to produce MAKE_BYTECODE_CALL // Now dispatch to first instruction in the @@ -6105,8 +6058,10 @@ stack_element *caller_stack_end = bsp; sp = caller_stack_end; // sp points to one past caller stack - int callee_nargout = caller_stack_end[0].i; - //TODO: Check nargout + // The amount of return values the caller wants, as stored last on the caller stack. + // Note that this is not necessarily the same as nargout, the amount of return values the caller + // want the callee to produce, stored first on callee stack. + int caller_nval_back = (*--sp).u; // Restore ip ip = (*--sp).puc; @@ -6137,7 +6092,7 @@ // Move the callee's return values to the top of the stack of the caller. // Renaming variables to keep my sanity. - int n_args_caller_expects = callee_nargout; + int n_args_caller_expects = caller_nval_back; int n_args_callee_has = n_returns_callee - 1; // Excludes %nargout int n_args_to_move = std::min (n_args_caller_expects, n_args_callee_has); int n_args_actually_moved = 0; @@ -6565,6 +6520,381 @@ MAKE_BINOP_CST_SELFMODIFYING(binary_op::op_pow, pow_cst_dbl, POW_CST_DBL) DISPATCH(); + index_struct_subcall: + { + // The INDEX_STRUCT_SUBCALL opcode is a chain of INDEX_STRUCT_SUBCALL opcodes, + // that are always proceded by INDEX_STRUCT_CALL. + // + // INDEX_STRUCT_SUBCALL executes a subexpression of a chain of subsrefs. + // + // The opcode is more or less a "stackification" of tree_index_expression::evaluate_n (). + + int nargout = arg0; + // TODO: Kludge alert. Mirror the behaviour in ov_classdef::subsref + // where under certain conditions a magic number nargout of -1 is + // expected to maybe return a cs-list. "-1" in this context + // does not have the same meaning as in the VM, where it means + // a varargout with only one return symbol 'varargout'. + int subsref_nargout = nargout; + if (nargout == 255) + { + nargout = 1; + subsref_nargout = -1; + } + int i = *ip++; + int n = *ip++; + int n_args_on_stack = *ip++; + char type = *ip++; + + // The object to index is before the args on the stack + octave_value &ov = (sp[-1 - n_args_on_stack]).ov; + + bool ov_is_vm_chainargs_wrapper = ov.is_vm_chainargs_wrapper (); + bool need_stepwise_subsref = ov_need_stepwise_subsrefs(ov); + + try + { + if (!ov_is_vm_chainargs_wrapper && need_stepwise_subsref) + { + switch (ov.vm_dispatch_call ()) + { + case octave_base_value::vm_call_dispatch_type::FN_LOOKUP: + { + (*sp++).pee = new execution_exception {"error", "", "invalid undefined value in chained index expression"}; // TODO: Uninformative? + (*sp++).i = static_cast (error_type::EXECUTION_EXC); + goto unwind; + } + case octave_base_value::vm_call_dispatch_type::SUBSREF: + { + octave_value_list ovl; + // The operands are on the top of the stack + POP_STACK_RANGE_TO_OVL (ovl, sp - n_args_on_stack, sp); + + CHECK_PANIC (! ov.is_function () || ov.is_classdef_meta ()); // TODO: Remove + + octave_value_list retval; + try + { + m_tw->set_active_bytecode_ip (ip - code); + retval = ov.simple_subsref (type, ovl, subsref_nargout); + } + CATCH_INTERRUPT_EXCEPTION + catch (index_exception& ie) + { + // Fetch the name of the left most object being indexed from the arg name entries. + int ip_offset = ip - code; + arg_name_entry entry = get_argname_entry (ip_offset, unwind_data); + + if (entry.m_obj_name != "") + { + // Only set the name if the object is defined (i.e. not a function call to e.g. 'zero') + // to match the error messages in tree_evaluator and pass index.tst + if (m_tw->get_current_stack_frame ()->varval (entry.m_obj_name).is_defined ()) + ie.set_var (entry.m_obj_name); + } + + (*sp++).pee = ie.dup (); + (*sp++).i = static_cast (error_type::INDEX_ERROR); + goto unwind; + } + CATCH_EXECUTION_EXCEPTION + CATCH_BAD_ALLOC + CATCH_EXIT_EXCEPTION + + ov = octave_value (); + STACK_DESTROY (1); // Destroy the object being indexed + + ovl.clear (); // Destroy the args + + bool is_last_iteration = i + 1 == n; + + if (is_last_iteration) + { + // TODO: Kludge for e.g. "m = containsers.Map;" which returns a function. + // Should preferably be done by .subsref? + // If only classdefs does this, this is not really needed here. + octave_value val = (retval.length () ? retval(0) : octave_value ()); + if (val.is_function ()) + { + octave_function *fcn = val.function_value (true); + + if (fcn) + { + retval = fcn->call (*m_tw, nargout, {}); + } + } + } + + // Push one value if this iteration of INDEX_STRUCT_SUBCALL + // is not the last one. + if (!is_last_iteration) + nargout = 1; + EXPAND_CSLIST_PUSH_N_OVL_ELEMENTS_TO_STACK (retval, nargout); + } + break; + + case octave_base_value::vm_call_dispatch_type::CALL: + case octave_base_value::vm_call_dispatch_type::HANDLE: + case octave_base_value::vm_call_dispatch_type::OBJECT: + { + CHECK_PANIC (ov.has_function_cache ()); // TODO :Remove + + octave_function *fcn; + try + { + stack_element *first_arg = &sp[-n_args_on_stack]; + stack_element *end_arg = &sp[0]; + fcn = ov.get_cached_fcn (first_arg, end_arg); + } + CATCH_EXECUTION_EXCEPTION // parse errors might throw in classdefs + + if (! fcn) + { + (*sp++).pee = new execution_exception {"error", "", "invalid return value in chained index expression"}; // TODO: Uninformative? + (*sp++).i = static_cast (error_type::EXECUTION_EXC); + goto unwind; + } + else if (fcn->is_compiled ()) + { + octave_user_code *usr_fcn = static_cast (fcn); + + // The last iteration the caller wants as many returned on the stack + // as it wants the callee to produce. + // In any other iteration the caller wants one value returned, but still + // wants the callee to produce nargout number of return values. + int caller_nvalback; + if (i + 1 != n) + caller_nvalback = 1; + else + caller_nvalback = nargout; + + MAKE_BYTECODE_CALL + + // Now dispatch to first instruction in the + // called function + } + else + { + try + { + octave_value_list ovl; + // The operands are on the top of the stack + POP_STACK_RANGE_TO_OVL (ovl, sp - n_args_on_stack, sp); + + m_tw->set_active_bytecode_ip (ip - code); + octave_value_list ret = fcn->call (*m_tw, nargout, ovl); + + STACK_DESTROY (1); // Destroy the object being indexed + + bool is_last_iteration = i + 1 == n; + + if (is_last_iteration) + { + // TODO: Kludge for e.g. "m = containsers.Map;" which returns a function. + // Should preferably be done by .subsref? + // If only classdefs does this, this is not really needed here. + octave_value val = (ret.length () ? ret(0) : octave_value ()); + if (val.is_function ()) + { + octave_function *fcn_final = val.function_value (true); + + if (fcn_final) + { + ret = fcn_final->call (*m_tw, nargout, {}); + } + } + } + + // Push one value if this iteration of INDEX_STRUCT_SUBCALL + // is not the last one. + if (!is_last_iteration) + nargout = 1; + EXPAND_CSLIST_PUSH_N_OVL_ELEMENTS_TO_STACK (ret, nargout); + } + CATCH_INTERRUPT_EXCEPTION + CATCH_INDEX_EXCEPTION + CATCH_EXECUTION_EXCEPTION + CATCH_BAD_ALLOC + CATCH_EXIT_EXCEPTION + } + } + break; + } + + } + else + { + if (!ov_is_vm_chainargs_wrapper) + { + // Replace the object being indexed on the stack with a wrapper object + // with the object being indexed in it. + ov = octave_value {new octave_vm_chainargs_wrapper {ov}}; + } + + octave_vm_chainargs_wrapper &ovb_chainargs = REP (octave_vm_chainargs_wrapper, ov); + + { // Limit ovl to this scope to not polute the whole C++ scope with an invalid ovl after the move + octave_value_list ovl; + // The operands are on the top of the stack + POP_STACK_RANGE_TO_OVL (ovl, sp - n_args_on_stack, sp); + + ovb_chainargs.append_args (std::move (ovl)); + ovb_chainargs.append_type (type); + } + + // In the last INDEX_STRUCT_SUBCALL the args have been collected and it is + // time to make the call. + if (i + 1 == n) + { + std::list idxs = ovb_chainargs.steal_idxs (); + std::string types = ovb_chainargs.steal_types (); + + // Replace the wrapper object with the object being indexed. + // Since ov and ovb_chainargs are the same octave value + // there has to be a intermediate copy to keep the refcounts + // correct. + { + octave_value tmp = ovb_chainargs.steal_obj_to_call (); + ov = tmp; + // Note: 'ovb_chainargs' is invalid from now on + } + + switch (ov.vm_dispatch_call ()) + { + case octave_base_value::vm_call_dispatch_type::FN_LOOKUP: + panic_impossible (); + break; + case octave_base_value::vm_call_dispatch_type::SUBSREF: + { + CHECK_PANIC (! ov.is_function () || ov.is_classdef_meta ()); // TODO: Remove + + octave_value_list retval; + try + { + m_tw->set_active_bytecode_ip (ip - code); + retval = ov.subsref (types, idxs, subsref_nargout); + idxs.clear (); + } + CATCH_INTERRUPT_EXCEPTION + catch (index_exception& ie) + { + // Fetch the name of the left most object being indexed from the arg name entries. + int ip_offset = ip - code - 1; // Minus one since ip points at next opcode now + arg_name_entry entry = get_argname_entry (ip_offset, unwind_data); + + if (entry.m_obj_name != "") + { + // Only set the name if the object is defined (i.e. not a function call to e.g. 'zero') + // to match the error messages in tree_evaluator and pass index.tst + if (m_tw->get_current_stack_frame ()->varval (entry.m_obj_name).is_defined ()) + ie.set_var (entry.m_obj_name); + } + + (*sp++).pee = ie.dup (); + (*sp++).i = static_cast (error_type::INDEX_ERROR); + goto unwind; + } + CATCH_EXECUTION_EXCEPTION + CATCH_BAD_ALLOC + CATCH_EXIT_EXCEPTION + + // TODO: Kludge for e.g. "m = containsers.Map;" which returns a function. + // Should preferably be done by subsref()/call()? + octave_value val = (retval.length () ? retval(0) : octave_value ()); + if (val.is_function ()) + { + octave_function *fcn_final = val.function_value (true); + + if (fcn_final) + { + retval = fcn_final->call (*m_tw, nargout, {}); + } + } + + STACK_DESTROY (1); // Destroy the object being indexed + EXPAND_CSLIST_PUSH_N_OVL_ELEMENTS_TO_STACK (retval, nargout); + } + break; + + case octave_base_value::vm_call_dispatch_type::CALL: + case octave_base_value::vm_call_dispatch_type::HANDLE: + case octave_base_value::vm_call_dispatch_type::OBJECT: + { + CHECK_PANIC (ov.is_function ()); // TODO :Remove + + octave_function *fcn = ov.function_value (); + + // tree_evaluator silently ignored nullptr here + if (! fcn) + { + (*sp++).pee = new execution_exception {"error", "", "invalid return value in chained index expression"}; // TODO: Uninformative? + (*sp++).i = static_cast (error_type::EXECUTION_EXC); + goto unwind; + } +#if 0 // TODO: Support for bytecode calls to ctors + else if (fcn->is_compiled ()) + { + octave_user_code *usr_fcn = static_cast (fcn); + + // Alot of code in this define + int nargout = 1; + MAKE_BYTECODE_CALL + + // Now dispatch to first instruction in the + // called function + } +#endif + else + { + try + { + if (idxs.size () != 1) + error ("unexpected extra index at end of expression"); + if (type != '(') + error ("invalid index type '%c' for function call", + type); + + octave_value_list final_args = idxs.front (); + + m_tw->set_active_bytecode_ip (ip - code); + octave_value_list ret = fcn->call (*m_tw, nargout, final_args); + + // TODO: Kludge for e.g. "m = containsers.Map;" which returns a function. + // Should preferably be done by subsref()/call()? + // Is this actually executed? See pt-idx.cc + octave_value val = (ret.length () ? ret(0) : octave_value ()); + if (val.is_function ()) + { + octave_function *fcn_final = val.function_value (true); + + if (fcn_final) + { + ret = fcn_final->call (*m_tw, nargout, final_args); // Called with same args as above ... + } + } + + STACK_DESTROY (1); // Destroy the object being indexed + EXPAND_CSLIST_PUSH_N_OVL_ELEMENTS_TO_STACK (ret, nargout); + } + CATCH_INTERRUPT_EXCEPTION + CATCH_INDEX_EXCEPTION + CATCH_EXECUTION_EXCEPTION + CATCH_BAD_ALLOC + CATCH_EXIT_EXCEPTION + } + } + break; + } + } + } + } + CATCH_INTERRUPT_EXCEPTION + CATCH_INDEX_EXCEPTION + CATCH_EXECUTION_EXCEPTION + CATCH_BAD_ALLOC + CATCH_EXIT_EXCEPTION + } + DISPATCH (); } octave_value diff -r 393ba8df4b1d -r d28004448cc0 libinterp/parse-tree/pt-bytecode-walk.cc --- a/libinterp/parse-tree/pt-bytecode-walk.cc Fri Oct 27 18:19:56 2023 +0200 +++ b/libinterp/parse-tree/pt-bytecode-walk.cc Fri Oct 27 18:20:03 2023 +0200 @@ -5389,26 +5389,21 @@ auto arg_type_tags_it = type_tags.begin (); tree_expression *first_expression = e; - // Iterate over the chained subexpressions + // Iterate over the chained subexpressions. Collect arg names and amount of args. std::vector v_n_args {}; // We pushed one field above std::vector v_types {};// The type is . while (arg_lists_it != arg_lists.end ()) { tree_argument_list *arg_list = *arg_lists_it++; string_vector field_names = *arg_names_it++; - tree_expression *dyn_expr = *arg_lists_dyn_it++; char type = *arg_type_tags_it++; v_types.push_back (type); if (type == '.') - { - emit_fields_for_visit_index_expression (field_names, dyn_expr, nullptr, nullptr); - v_n_args.push_back (1); - } + v_n_args.push_back (1); else if (arg_list) { - emit_args_for_visit_index_expression (arg_list, nullptr); v_n_args.push_back (arg_list->size ()); // Push the argnames for inputname () int n_args = field_names.numel (); @@ -5431,41 +5426,106 @@ m_ignored_ip_start = CODE_SIZE (); // visit_multi_assignment () need the code offset to set the proper range for the unwind protect } - maybe_emit_anon_maybe_ignore_outputs (); - - int nargout = NARGOUT (); - - arg_name_entry.m_ip_start = CODE_SIZE (); - - if (first_expression && first_expression->is_identifier ()) + // Reset the iterators to iterate over them all again + arg_names_it = arg_names.begin (); + arg_lists_it = arg_lists.begin (); + arg_lists_dyn_it = dyn_fields.begin (); + arg_type_tags_it = type_tags.begin (); + + // The first subcall need to be handled specially since it can be a function call + // by identifier name. + { + tree_argument_list *arg_list = *arg_lists_it++; + string_vector field_names = *arg_names_it++; + tree_expression *dyn_expr = *arg_lists_dyn_it++; + char type = *arg_type_tags_it++; + + if (type == '.') + emit_fields_for_visit_index_expression (field_names, dyn_expr, nullptr, nullptr); + else if (arg_list) + emit_args_for_visit_index_expression (arg_list, nullptr); + // For e.g. the call to "bar" in "foo.bar ()" no args need to be emitted + + + maybe_emit_anon_maybe_ignore_outputs (); + + int nargout = NARGOUT (); + + arg_name_entry.m_ip_start = CODE_SIZE (); + + if (first_expression && first_expression->is_identifier ()) + { + // Name of the left most identifier needed for error messages + // since INDEX_STRUCT_SUBCALL:s don't have any slot specified + arg_name_entry.m_obj_name = first_expression->name (); + + int slot = SLOT (first_expression->name ()); + MAYBE_PUSH_ANON_NARGOUT_OPEXT (nargout); + PUSH_CODE (INSTR::INDEX_STRUCT_CALL); + if (m1_magic_nargout) + PUSH_CODE (1); // Note, 1, not -1. Since there is no subsref in INDEX_STRUCT_CALL + else + PUSH_CODE (nargout); + PUSH_WSLOT (slot); // the slot + } + else + { + MAYBE_PUSH_ANON_NARGOUT_OPEXT (nargout); + PUSH_CODE (INSTR::INDEX_STRUCT_CALL); + if (m1_magic_nargout) + PUSH_CODE (1); // Note, 1, not -1. + else + PUSH_CODE (nargout); + PUSH_WSLOT (0); // slot ignored + } + + PUSH_CODE (v_n_args[0]); + PUSH_CODE (v_types[0]); // '.', '(' or '{' + + // The first subcall (INDEX_STRUCT_CALL) checks the identifier to see if it is function that should be called. + // Or an callable object that should be called. + // + // If it is, the following opcode is skipped for '(' type calls since those eat the args, whereas it is executed + // for '{' or '.' types. + MAYBE_PUSH_ANON_NARGOUT_OPEXT (nargout); + PUSH_CODE (INSTR::INDEX_STRUCT_SUBCALL); + if (m1_magic_nargout) + PUSH_CODE (-1); + else + PUSH_CODE (nargout); + PUSH_CODE (0); + PUSH_CODE (v_n_args.size ()); + PUSH_CODE (v_n_args[0]); + PUSH_CODE (v_types[0]); + } + + // Now do the rest of the subcalls + int cntr = 0; // The subcall number, starting at one since the first is allready done above + while (arg_lists_it != arg_lists.end ()) { - int slot = SLOT (first_expression->name ()); + cntr++; // One first iteration of the loop + tree_argument_list *arg_list = *arg_lists_it++; + string_vector field_names = *arg_names_it++; + tree_expression *dyn_expr = *arg_lists_dyn_it++; + char type = *arg_type_tags_it++; + + if (type == '.') + emit_fields_for_visit_index_expression (field_names, dyn_expr, nullptr, nullptr); + else if (arg_list) + emit_args_for_visit_index_expression (arg_list, nullptr); + // For e.g. the call to "bar" in "foo.bar ()" no args need to be emitted + + int nargout = NARGOUT (); MAYBE_PUSH_ANON_NARGOUT_OPEXT (nargout); - PUSH_CODE (INSTR::INDEX_STRUCT_CALL); + PUSH_CODE (INSTR::INDEX_STRUCT_SUBCALL); if (m1_magic_nargout) PUSH_CODE (-1); else PUSH_CODE (nargout); - PUSH_CODE (1); // has slot - PUSH_WSLOT (slot); // the slot - } - else - { - MAYBE_PUSH_ANON_NARGOUT_OPEXT (nargout); - PUSH_CODE (INSTR::INDEX_STRUCT_CALL); - if (m1_magic_nargout) - PUSH_CODE (-1); - else - PUSH_CODE (nargout); - PUSH_SLOT (0); // has slot - PUSH_WSLOT (0); // slot ignored - } - - PUSH_CODE (v_n_args.size ()); - for (unsigned i = 0; i < v_n_args.size (); i++) - { - PUSH_CODE (v_n_args[i]); - PUSH_CODE (v_types[i]); + PUSH_CODE (cntr); + PUSH_CODE (v_n_args.size ()); + PUSH_CODE (v_n_args[cntr]); + PUSH_CODE (v_types[cntr]); } arg_name_entry.m_ip_end = CODE_SIZE (); diff -r 393ba8df4b1d -r d28004448cc0 libinterp/parse-tree/pt-bytecode.h --- a/libinterp/parse-tree/pt-bytecode.h Fri Oct 27 18:19:56 2023 +0200 +++ b/libinterp/parse-tree/pt-bytecode.h Fri Oct 27 18:20:03 2023 +0200 @@ -218,6 +218,7 @@ POW_CST, PUSH_I, PUSH_E, + INDEX_STRUCT_SUBCALL, }; enum class unwind_entry_type