view libinterp/corefcn/compile.cc @ 33605:9e9725f13742 bytecode-interpreter tip

maint: Merge default to bytecode-interpreter.
author Nicholas R. Jankowski <jankowski.nicholas@gmail.com>
date Sat, 18 May 2024 22:26:52 -0400
parents a3b312564056
children
line wrap: on
line source

////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2023-2024 The Octave Project Developers
//
// See the file COPYRIGHT.md in the top-level directory of this
// distribution or <https://octave.org/copyright/>.
//
// This file is part of Octave.
//
// Octave is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Octave is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Octave; see the file COPYING.  If not, see
// <https://www.gnu.org/licenses/>.
//
////////////////////////////////////////////////////////////////////////

#if defined (HAVE_CONFIG_H)
#  include "config.h"
#endif

#include "ovl.h"
#include "ov.h"
#include "defun.h"
#include "variables.h"
#include "interpreter.h"

#if defined (OCTAVE_ENABLE_BYTECODE_EVALUATOR)
#  include "pt-bytecode-vm.h"
#  include "pt-bytecode-walk.h"
#endif

OCTAVE_BEGIN_NAMESPACE(octave)

#if defined (OCTAVE_ENABLE_BYTECODE_EVALUATOR)

// If TRUE, use VM evaluator rather than tree walker.
bool V__vm_enable__ = true;

// Cleverly hidden in pt-bytecode-vm.cc to prevent inlining here
extern "C" void dummy_mark_1 (void);
extern "C" void dummy_mark_2 (void);

#endif

DEFUN (__dummy_mark_1__, , ,
       doc: /* -*- texinfo -*-
@deftypefn {} {} __dummy_mark_1__ ()

Dummy function that calls the C-function @code{void dummy_mark_1 (void)}
that does nothing.

This is useful for marking start and end for Callgrind analysis or as an entry
point for @code{gdb}.

@end deftypefn */)
{
#if defined (OCTAVE_ENABLE_BYTECODE_EVALUATOR)

  dummy_mark_1 ();

  return {};

#else

  err_disabled_feature ("__dummy_mark_1__", "byte-compiled functions");

#endif
}

DEFUN (__dummy_mark_2__, , ,
       doc: /* -*- texinfo -*-
@deftypefn {} {} __dummy_mark_2__ ()

Dummy function that calls the C-function @code{void dummy_mark_2 (void)}
that does nothing.

This is useful for marking start and end for Callgrind analysis or as an entry
point for @code{gdb}.

@end deftypefn */)
{
#if defined (OCTAVE_ENABLE_BYTECODE_EVALUATOR)

  dummy_mark_2 ();

  return {};

#else

  err_disabled_feature ("__dummy_mark_2__", "byte-compiled functions");

#endif
}

DEFUN (__vm_clear_cache__, , ,
  doc: /* -*- texinfo -*-
@deftypefn {} {@var{val} =} __vm_clear_cache__ ()
Internal function.

Clear cache of bytecode-compiled functions.

@c FIXME: Use seealso macro when functions are no longer experimental.
See also: __vm_compile__.
@end deftypefn */)
{
#if defined (OCTAVE_ENABLE_BYTECODE_EVALUATOR)

  octave::load_path::signal_clear_fcn_cache ();

  return octave_value {true};

#else

  err_disabled_feature ("__vm_clear_cache__", "byte-compiled functions");

#endif
}

DEFUN (__vm_print_trace__, , ,
  doc: /* -*- texinfo -*-
@deftypefn {} {@var{print_trace} =} __vm_print_trace__ ()
Internal function.

Print a debug trace from the VM@.

The print state toggles on or off with each call to the function.

There must be a breakpoint set in an m-file for the trace to actually print
anything.

The return value is true if a trace will be printed and false otherwise.

@end deftypefn */)
{
#if defined (OCTAVE_ENABLE_BYTECODE_EVALUATOR)

  vm::m_trace_enabled = !vm::m_trace_enabled;

  return octave_value {vm::m_trace_enabled};

#else

  err_disabled_feature ("__vm_print_trace__", "byte-compiled functions");

#endif
}

DEFUN (__ref_count__, args, ,
  doc: /* -*- texinfo -*-
@deftypefn {} {@var{count} =} __ref_count__ (@var{obj})
Internal function.

Return the reference count for an object.
@end deftypefn */)
{
  int nargin = args.length ();

  if (nargin != 1)
    print_usage ();

  octave_value ov = args (0);

  return octave_value {ov.get_count ()};
}

DEFMETHOD (__vm_is_executing__, interp, , ,
  doc: /* -*- texinfo -*-
@deftypefn {} {@var{tf} =} __vm_is_executing__ ()
Internal function.

Return true if the VM is executing the function calling
@code{__vm_is_executing__ ()}, and false otherwise.

@c FIXME: Use seealso macro when functions are no longer experimental.
See also: __vm_enable__.
@end deftypefn */)
{
#if defined (OCTAVE_ENABLE_BYTECODE_EVALUATOR)

  auto frame = interp.get_evaluator ().get_current_stack_frame ();
  if (!frame)
    error ("Invalid current frame");

  auto caller_frame = frame->static_link ();
  if (!caller_frame)
    error ("Invalid caller frame");

  bool bytecode_running = caller_frame->is_bytecode_fcn_frame ();

  return octave_value {bytecode_running};

#else

  octave_unused_parameter (interp);

  err_disabled_feature ("__vm_is_executing__", "byte-compiled functions");

#endif
}

DEFMETHOD (__vm_profile__, interp, args, ,
  doc: /* -*- texinfo -*-
@deftypefn  {} {} __vm_profile__ on
@deftypefnx {} {} __vm_profile__ off
@deftypefnx {} {} __vm_profile__ resume
@deftypefnx {} {} __vm_profile__ clear
@deftypefnx {} {@var{T} =} __vm_profile__ ("info")
@deftypefnx {} {} __vm_profile__
Internal function.

Profile code running in the VM.

@table @code
@item __vm_profile__ on
Start the profiler.  Any previously collected data is cleared.

@item __vm_profile__ off
Stop profiling.  The collected data can be retrieved and examined with
@code{T = profile ("info")}.

@item __vm_profile__ clear
Clear all collected profiler data.

@item __vm_profile__ resume
Restart profiling without clearing existing data.  All newly collected
statistics are added to the existing ones.

@item __vm_profile__
Toggle between profiling and printing the result of the profiler.
Clears the profiler on each print.

@item __vm_profile__ info
Print the profiler data.

@end table

Programming Note: The calling form that returns profiler data in a variable
is not implemented yet.

@end deftypefn */)
{
#if defined (OCTAVE_ENABLE_BYTECODE_EVALUATOR)

  int nargin = args.length ();

  auto &evaler = interp.get_evaluator ();

  std::string arg0;

  if (nargin >= 1)
   arg0 = args (0).string_value ();

  if (!arg0.size ())
    {
      if (!vm::m_vm_profiler)
        {
          vm::m_vm_profiler = std::make_shared<vm_profiler> ();

          vm::m_profiler_enabled = true;
          evaler.vm_set_profiler_active (true);
        }
      else
        {
          evaler.vm_set_profiler_active (false);
          vm::m_profiler_enabled = false;
          auto p = vm::m_vm_profiler;
          vm::m_vm_profiler = nullptr;

          auto cpy = *p;
          cpy.print_to_stdout ();
        }
    }
  else if (arg0 == "on")
    {
      vm::m_profiler_enabled = false;
      vm::m_vm_profiler = std::make_shared<vm_profiler> ();
      vm::m_profiler_enabled = true;
      evaler.vm_set_profiler_active (true);
    }
  else if (arg0 == "resume")
    {
      if (!vm::m_vm_profiler)
        vm::m_vm_profiler = std::make_shared<vm_profiler> ();

      vm::m_profiler_enabled = true;
      evaler.vm_set_profiler_active (true);
    }
  else if (arg0 == "off")
    {
      evaler.vm_set_profiler_active (false);
      vm::m_profiler_enabled = false;
    }
  else if (arg0 == "clear")
    {
      evaler.vm_set_profiler_active (false);
      vm::m_profiler_enabled = false;
      vm::m_vm_profiler = nullptr;
    }
  else if (arg0 == "info")
    {
      auto p_vm_profiler = vm::m_vm_profiler;
      if (p_vm_profiler)
        {
          auto cpy = *p_vm_profiler;
          cpy.print_to_stdout ();
        }
      else
        warning ("Nothing recorded.");
    }
  else
    print_usage ();

  return octave_value {true};

#else

  octave_unused_parameter (interp);
  octave_unused_parameter (args);

  err_disabled_feature ("__vm_profile__", "byte-compiled functions");

#endif
}

DEFMETHOD (__vm_print_bytecode__, interp, args, ,
  doc: /* -*- texinfo -*-
@deftypefn  {} {@var{code} =} __vm_print_bytecode__ (@var{fcn_name})
@deftypefnx {} {@var{code} =} __vm_print_bytecode__ (@var{fcn_handle})
Internal function.

Print the bytecode of a function name or function handle.

@c FIXME: Use seealso macro when functions are no longer experimental.
See also: __vm_compile__.
@end deftypefn */)
{
#if defined (OCTAVE_ENABLE_BYTECODE_EVALUATOR)

  int nargin = args.length ();

  if (nargin != 1)
    print_usage ();

  octave_value ov;

  if (args (0).is_string ())
    {
      std::string fn_name = args(0).string_value ();
      symbol_table& symtab = interp.get_symbol_table ();

      ov = symtab.find_function (fn_name);

      if (!ov.is_defined ())
        {
          error ("Function not defined: %s", fn_name.c_str ());
        }
    }
  else
    ov = args (0);

  octave_user_code *ufn = nullptr;
  octave_fcn_handle *h = nullptr;

  if (ov.is_function_handle ())
    {
      h = ov.fcn_handle_value ();
      if (!h)
        error ("Invalid function handle");
      ufn = h->user_function_value ();
    }
  else
   ufn = ov.user_code_value ();

  std::string fn_name = ufn->name ();

  if (!ufn || (!ufn->is_user_function () && !ufn->is_user_script ()))
    {
      error ("Function not a user function or script: %s", fn_name.c_str ());
    }

  // Nested functions need to be compiled via their parent
  bool is_nested = ufn->is_nested_function ();

  bool try_compile = !ufn->is_compiled () && V__vm_enable__ && !is_nested;

  if (try_compile && h && h->is_anonymous ())
    h->compile ();
  else if (try_compile)
    vm::maybe_compile_or_compiled (ufn, 0);
  else if (!ufn->is_compiled ())
    error ("Function not compiled: %s", fn_name.c_str ());

  if (!ufn->is_compiled ())
    error ("Function can't be compiled: %s", fn_name.c_str ());

  auto bc = ufn->get_bytecode ();

  print_bytecode (bc);

  return octave_value {true};

#else

  octave_unused_parameter (interp);
  octave_unused_parameter (args);

  err_disabled_feature ("__vm_print_bytecode__", "byte-compiled functions");

#endif
}

DEFMETHOD (__vm_is_compiled__, interp, args, ,
  doc: /* -*- texinfo -*-
@deftypefn  {} {@var{tf} =} __vm_is_compiled__ (@var{fcn_name})
@deftypefnx {} {@var{tf} =} __vm_is_compiled__ (@var{fcn_handle})
Internal function.

Return true if the specified function name or function handle has been
compiled to bytecode, and false otherwise.

@c FIXME: Use seealso macro when functions are no longer experimental.
See also: __vm_compile__.
@end deftypefn */)
{
#if defined (OCTAVE_ENABLE_BYTECODE_EVALUATOR)

  int nargin = args.length ();

  if (nargin != 1)
    print_usage ();

  std::string fcn_to_compile;
  octave_fcn_handle *handle_to_compile = nullptr;

  bool do_handle = false;

  if (args (0).is_string ())
    fcn_to_compile = args (0).string_value ();
  else if (args (0).is_function_handle ())
    {
      handle_to_compile = args (0).fcn_handle_value ();
      do_handle = true;
    }
  else
    error ("First argument need to be a function name or function handle.");

  try
    {
      if (do_handle)
        {
          octave_user_function *ufn = handle_to_compile->user_function_value ();
          if (!ufn)
            return octave_value {false};
          return octave_value {ufn->is_compiled ()};
        }
      else
        {
          std::string name = fcn_to_compile;
          symbol_table& symtab = interp.get_symbol_table ();
          octave_value ov = symtab.find_function (name);

          if (!ov.is_defined ())
            return octave_value {false};

          octave_user_code *ufn = ov.user_code_value ();
          if (!ufn)
            return octave_value {false};

          return octave_value {ufn->is_compiled ()};
        }
    }
  catch (execution_exception &)
    {
      return octave_value {false};
    }

#else

  octave_unused_parameter (interp);
  octave_unused_parameter (args);

  err_disabled_feature ("__vm_is_compiled__", "byte-compiled functions");

#endif
}

DEFMETHOD (__vm_compile__, interp, args, ,
       doc: /* -*- texinfo -*-
@deftypefn  {} {@var{status} =} __vm_compile__ (@var{fcn_name})
@deftypefnx {} {@var{status} =} __vm_compile__ (@var{fcn_name}, "print")
@deftypefnx {} {@var{status} =} __vm_compile__ (@var{fcn_name}, "clear")
Internal function.

Compile the specified function to bytecode.

The compiled function and its subfunctions will be executed by the VM when
called.

The @qcode{"print"} option prints the bytecode after compilation.

The @qcode{"clear"} option removes the bytecode from the VM instead.

Return true on success, and false otherwise.

@strong{Note:}: Do not recompile or clear the bytecode of a running function
with @code{__vm_compile__}.

@c FIXME: Use seealso macro when functions are no longer experimental.
See also: __vm_print_bytecode__, __vm_clear_cache__.
@end deftypefn */)
{
#if defined (OCTAVE_ENABLE_BYTECODE_EVALUATOR)

  int nargin = args.length ();

  if (! nargin)
    print_usage ();

  std::string fcn_to_compile;
  octave_fcn_handle *handle_to_compile = nullptr;

  bool do_clear = false;
  bool do_print = false;

  if (args (0).is_string ())
    fcn_to_compile = args (0).string_value ();
  else if (args (0).is_function_handle ())
    handle_to_compile = args (0).fcn_handle_value ();
  else
    error ("First argument need to be a function name or function handle.");

  for (int i = 1; i < nargin; i++)
    {
      auto arg = args(i);

      if (! arg.is_string())
        error ("Non string argument");

      std::string arg_s = arg.string_value ();

      if (arg_s == "clear")
        do_clear = true;

      if (arg_s == "print")
        do_print = true;
    }

  if (do_clear && handle_to_compile)
    {
      octave_user_function *ufn = handle_to_compile->user_function_value ();
      if (!ufn)
        error ("Invalid function handle");

      ufn->clear_bytecode ();

      return octave_value {true};
    }
  else if (do_clear)
    {
      std::string name = fcn_to_compile;
      symbol_table& symtab = interp.get_symbol_table ();
      octave_value ov = symtab.find_function (name);

      if (!ov.is_defined ())
        {
          error ("Function not defined: %s", name.c_str ());
        }

      octave_user_code *ufn = ov.user_code_value ();

      if (!ufn || (!ufn->is_user_function () && !ufn->is_user_script ()))
        {
          error ("Function not an user function or script: %s", name.c_str ());
        }

      ufn->clear_bytecode ();

      return octave_value {true};
    }

  if (handle_to_compile)
    {
      octave_user_function *ufn = handle_to_compile->user_function_value ();
      if (!ufn)
        error ("Invalid function handle");

      if (ufn->is_nested_function ())
        error ("Nested functions need to be compiled via their parent");

      // Anonymous functions need to be compiled via their handle
      // to get the locals.
      if (handle_to_compile->is_anonymous ())
        {
          handle_to_compile->compile ();
          if (do_print && ufn->is_compiled ())
            {
              auto bc = ufn->get_bytecode ();
              print_bytecode (bc);
            }

            return octave_value {true};
        }
      else
        {
          // Throws on errors
          compile_user_function (*ufn, do_print);

          return octave_value {true};
        }
    }
  else
    {
      std::string name = fcn_to_compile;
      symbol_table& symtab = interp.get_symbol_table ();
      octave_value ov = symtab.find_function (name);

      if (!ov.is_defined ())
        {
          error ("Function not defined: %s", name.c_str ());
        }

      if (!ov.is_user_function () && !ov.is_user_script ())
        {
          error ("Function is not a user function or script: %s", name.c_str ());
        }

      octave_user_code *ufn = ov.user_code_value ();

      if (!ufn || (!ufn->is_user_function () && !ufn->is_user_script ()))
        {
          error ("Function is not really user function or script: %s", name.c_str ());
        }

      if (ufn->is_nested_function ())
        error ("Nested functions need to be compiled via their parent");

      // Throws on errors
      compile_user_function (*ufn, do_print);
    }

  return octave_value {true};

#else

  octave_unused_parameter (interp);
  octave_unused_parameter (args);

  err_disabled_feature ("__vm_compile__", "byte-compiled functions");

#endif
}

DEFUN (__vm_enable__, args, nargout,
       doc: /* -*- texinfo -*-
@deftypefn  {} {@var{val} =} __vm_enable__ ()
@deftypefnx {} {@var{old_val} =} __vm_enable__ (@var{new_val})
@deftypefnx {} {@var{old_val} =} __vm_enable__ (@var{new_val}, "local")
Query or set the internal variable that determines whether Octave automatically
compiles functions to bytecode and executes them in a virtual machine (VM).

@strong{Warning:} The virtual machine feature is experimental.

The default value is false while the VM is still experimental.  Users must
explicitly call @code{__vm_enable__ (1)} to use it.

When false, Octave uses a traditional tree walker to evaluate statements parsed
from m-code.  When true, Octave translates parsed statements to an intermediate
representation that is then evaluated by a virtual machine.

When called from inside a function with the @qcode{"local"} option, the setting
is changed locally for the function and any subroutines it calls.  The original
setting is restored when exiting the function.

Once compiled to bytecode, the function will always be evaluated by the VM
regardless of the state of @code{__vm_enable__}, until the bytecode is cleared
by, e.g., @qcode{"clear all"}, or a modification to the function's m-file.

@c FIXME: Use seealso macro when functions are no longer experimental.
See also: __vm_compile__.
@end deftypefn */)
{
#if defined (OCTAVE_ENABLE_BYTECODE_EVALUATOR)

  return set_internal_variable (V__vm_enable__, args, nargout,
                                "__vm_enable__");

#else

  octave_unused_parameter (args);
  octave_unused_parameter (nargout);

  err_disabled_feature ("__vm_enable__", "byte-compiled functions");

#endif
}

OCTAVE_END_NAMESPACE(octave)