cfad47cfa3/tads3/vmrun.h
Commiter: Nikos Chantziaras
Author: Nikos Chantziaras
Revision: cfad47cfa3
File Size: 42.3 KB
(June 01, 2009 20:54 UTC) Almost 3 years ago
Initial commit.
Showing without highlighting since it looks like a big file and may slow your browser - show with highlighting
Show/hide line numbers/* $Header: d:/cvsroot/tads/tads3/vmrun.h,v 1.4 1999/07/11 00:46:59 MJRoberts Exp $ */
/*
* Copyright (c) 1998, 2002 Michael J. Roberts. All Rights Reserved.
*
* Please see the accompanying license file, LICENSE.TXT, for information
* on using and copying this software.
*/
/*
Name
vmrun.h - VM Execution
Function
Notes
Modified
11/12/98 MJRoberts - Creation
*/
#ifndef VMRUN_H
#define VMRUN_H
#include "vmglob.h"
#include "vmtype.h"
#include "vmstack.h"
#include "vmpool.h"
#include "vmobj.h"
#include "vmerr.h"
#include "vmerrnum.h"
#include "vmprofty.h"
/* ------------------------------------------------------------------------ */
/*
* for debugger use - interpreter context save structure
*/
struct vmrun_save_ctx
{
pool_ofs_t entry_ptr_;
vm_val_t *frame_ptr_;
size_t old_stack_depth_;
const uchar **pc_ptr_;
};
/* ------------------------------------------------------------------------ */
/*
* Procedure activation frame. The activation frame is arranged as
* follows (the stack index increases reading down the list):
*
*. argument N
*. argument N-1
*. ...
*. argument 2
*. argument 1
*. target property
*. original target object
*. defining object
*. self
*. offset in calling method of next instruction to execute
*. caller's entry pointer (EP) register value
*. actual parameter count
*. caller's frame pointer <<<--- CURRENT FRAME POINTER
*. local variable 1
*. local variable 2
*. local variable 3
*
* So, local variable 1 is at (FP+1), local variable 2 is at (FP+2), and
* so on; the argument count is at (FP-1); 'self' is at (FP-4); argument 1
* is at (FP-5), argument 2 is at (FP-6), and so on.
*/
/* offset from FP of first argument */
const int VMRUN_FPOFS_ARG1 = -8;
/* offset from FP of target property */
const int VMRUN_FPOFS_PROP = -7;
/* offset from FP of original target object */
const int VMRUN_FPOFS_ORIGTARG = -6;
/* offset from FP of defining object (definer of current method) */
const int VMRUN_FPOFS_DEFOBJ = -5;
/* offset from FP of 'self' */
const int VMRUN_FPOFS_SELF = -4;
/* offset from FP of return address */
const int VMRUN_FPOFS_RET = -3;
/* offset from FP of enclosing entry pointer */
const int VMRUN_FPOFS_ENC_EP = -2;
/* offset from FP of argument count */
const int VMRUN_FPOFS_ARGC = -1;
/* offset from FP of enclosing frame pointer */
const int VMRUN_FPOFS_ENC_FP = 0;
/* offset from FP of first local variable */
const int VMRUN_FPOFS_LCL1 = 1;
/* ------------------------------------------------------------------------ */
/*
* VM Execution Engine class. This class handles execution of byte
* code.
*/
class CVmRun
{
friend class CVmDebug;
public:
CVmRun();
~CVmRun();
/* initialize */
void init();
/* terminate */
void terminate();
/*
* Get/set the method header size. This size is stored in the image
* file; the image loader sets this at load time to the value
* retrieved from the image file. All method headers in an image
* file use the same size.
*/
void set_funchdr_size(size_t siz);
size_t get_funchdr_size() const { return funchdr_size_; }
/*
* Call a function or method. 'ofs' is the offset (in the code pool)
* of the code to invoke, and 'argc' is the number of arguments that
* the caller has pushed onto the stack.
*
* 'caller_ofs' is the method offset (the byte code offset from the
* current entry pointer) in the caller. If 'caller_ofs' is non-zero,
* we'll set up to begin execution in the target code and return the
* new program counter. If 'caller_ofs' is zero, we'll invoke the VM
* byte code interpreter recursively, so this function will return
* only after the called code returns. When calling recursively, set
* 'recurse_calling' to a descriptive string that can be used to show
* the system code calling the recursive code in case of error.
*
* When calling a function, 'self' should be VM_INVALID_OBJ.
* Otherwise, this value gives the object whose method is being
* invoked.
*
* The return value is the new program counter. For recursive
* invocations, this will simply return null.
*/
const uchar *do_call(VMG_ uint caller_ofs, pool_ofs_t ofs, uint argc,
vm_obj_id_t self, vm_prop_id_t target_prop,
vm_obj_id_t orig_target_obj,
vm_obj_id_t defining_obj,
const char *recurse_calling);
/* call a function, non-recursively */
const uchar *do_call_func_nr(VMG_ uint caller_ofs, pool_ofs_t ofs,
uint argc);
/*
* Call a function pointer value. If 'funcptr' contains a function
* pointer, we'll simply call the function; if it contains an
* anonymous function object, we'll call the anonymous function.
*/
const uchar *call_func_ptr(VMG_ const vm_val_t *funcptr, uint argc,
const char *recurse_name, uint caller_ofs);
/*
* Get the descriptive message, if any, from an exception object.
* The returned string will not be null-terminated, but the length
* will be stored in *msg_len. The returned string might point to
* constant pool data or data in an object, so it might not remain
* valid after a constant pool translation or garbage collection
* operation. If the exception has no message, we will return a
* null pointer.
*/
static const char *get_exc_message(VMG_ const CVmException *exc,
size_t *msg_len);
static const char *get_exc_message(VMG_ vm_obj_id_t obj, size_t *msg_len);
/*
* Get the descriptive message from an exception. If the exception
* has a program-generated exception object, we'll try to get the
* message from that object; if it's a VM exception with no
* underlying object, we'll retrieve the VM message.
*
* If add_unh_prefix is true, we'll add an "unhandled exception:"
* prefix to the message if we retrieve the message from a
* program-defined exception. Otherwise, if it's a program
* exception, we won't add any prefix at all.
*/
static void get_exc_message(VMG_ const CVmException *exc,
char *buf, size_t buflen, int add_unh_prefix);
/*
* Evaluate a property of an object. 'target_obj' is the object whose
* property is to be evaluated, 'target_prop' is the ID of the
* property to evaluate, and 'argc' is the number of arguments to the
* method.
*
* 'caller_ofs' is the current method offset (the offset from the
* current entry pointer to the current program counter) in the
* caller. If this is zero, we'll make a recursive call to the
* interpreter loop to execute any method code; thus, any method code
* will have run to completion by the time we return in this case.
*
* 'self' is the object in whose context we're to perform the code
* execution, if the property is a method. Note that 'self' may
* differ from 'target_obj' in some cases, particularly when
* inheriting.
*
* The return value is the new program counter from which execution
* should resume. This will be null (and can be ignored) for
* recursive invocations.
*/
const uchar *get_prop(VMG_ uint caller_ofs,
const vm_val_t *target_obj,
vm_prop_id_t target_prop,
const vm_val_t *self, uint argc);
/*
* Set a property of an object
*/
void set_prop(VMG_ vm_obj_id_t obj, vm_prop_id_t prop,
const vm_val_t *new_val);
/* get data register 0 (R0) */
vm_val_t *get_r0() { return &r0_; }
/* set the default "say" function */
void set_say_func(VMG_ const vm_val_t *val);
/* get the current default "say" function */
void get_say_func(vm_val_t *val) const;
/* set the default "say" method */
void set_say_method(vm_prop_id_t prop)
{
/* remember the property */
say_method_ = prop;
}
/* get the current "say" method */
vm_prop_id_t get_say_method() const { return say_method_; }
/* pop an integer value; throws an error if the value is not an integer */
void pop_int(VMG_ vm_val_t *val)
{
G_stk->pop(val);
if (val->typ != VM_INT)
err_throw(VMERR_INT_VAL_REQD);
}
/*
* Pop a numeric value; throws an error if the value is not numeric.
* (At the moment, this is equivalent to pop_int, since int is the
* only numeric type; however, we distinguish between numbers in
* general and integers in particular, in case additional numeric
* types [such as floating-point numbers] are added in the future.)
*/
void pop_num(VMG_ vm_val_t *val)
{
G_stk->pop(val);
if (!val->is_numeric())
err_throw(VMERR_NUM_VAL_REQD);
}
/* pop an object value */
void pop_obj(VMG_ vm_val_t *val)
{
G_stk->pop(val);
if (val->typ != VM_OBJ)
err_throw(VMERR_OBJ_VAL_REQD);
}
/* pop a property pointer value */
void pop_prop(VMG_ vm_val_t *val)
{
G_stk->pop(val);
if (val->typ != VM_PROP)
err_throw(VMERR_PROPPTR_VAL_REQD);
}
/* pop a function pointer value */
void pop_funcptr(VMG_ vm_val_t *val)
{
G_stk->pop(val);
if (val->typ != VM_FUNCPTR)
err_throw(VMERR_FUNCPTR_VAL_REQD);
}
/*
* Pop two values from the stack. The values are popped in reverse
* order, so val2 has the value at the top of the stack. If the
* left operand was pushed first, this results in placing the left
* operand in val1 and the right operand in val2.
*/
void popval_2(VMG_ vm_val_t *val1, vm_val_t *val2)
{
popval(vmg_ val2);
popval(vmg_ val1);
}
/*
* Pop two integers, throwing an error if either value is not an
* integer. Pops the item at the top of the stack into val2, and
* the next value into val1.
*/
void pop_int_2(VMG_ vm_val_t *val1, vm_val_t *val2)
{
popval(vmg_ val2);
popval(vmg_ val1);
if (val1->typ != VM_INT || val2->typ != VM_INT)
err_throw(VMERR_INT_VAL_REQD);
}
/*
* Pop two numbers, throwing an error if either value is not
* numeric.
*/
void pop_num_2(VMG_ vm_val_t *val1, vm_val_t *val2)
{
popval(vmg_ val2);
popval(vmg_ val1);
if (!val1->is_numeric() || !val2->is_numeric())
err_throw(VMERR_NUM_VAL_REQD);
}
/*
* get the active function's argument count - we read the value from
* the first item below the frame pointer in the current frame
*/
int get_cur_argc(VMG0_) const
{
return G_stk->get_from_frame(frame_ptr_, VMRUN_FPOFS_ARGC)
->val.intval;
}
/*
* Get a parameter value; 0 is the first parameter, 1 is the second,
* and so on.
*/
vm_val_t *get_param(VMG_ int idx) const
{ return get_param_from_frame(vmg_ frame_ptr_, idx); }
/* get a parameter from a given frame */
vm_val_t *get_param_from_frame(VMG_ vm_val_t *fp, int idx) const
{ return G_stk->get_from_frame(fp, VMRUN_FPOFS_ARG1 - idx); }
/* get a parameter at the given stack level */
vm_val_t *get_param_at_level(VMG_ int idx, int level) const
{
return get_param_from_frame(vmg_ get_fp_at_level(vmg_ level), idx);
}
/*
* get a local variable's value; 0 is the first local variable, 1 is
* the second, and so on
*/
vm_val_t *get_local(VMG_ int idx) const
{ return get_local_from_frame(vmg_ frame_ptr_, idx); }
/* get a local from a given frame */
vm_val_t *get_local_from_frame(VMG_ vm_val_t *fp, int idx) const
{ return G_stk->get_from_frame(fp, VMRUN_FPOFS_LCL1 + idx); }
/* get a local at the given stack level */
vm_val_t *get_local_at_level(VMG_ int idx, int level) const
{
return get_local_from_frame(vmg_ get_fp_at_level(vmg_ level), idx);
}
/*
* Get the frame pointer at the given stack level. Level 0 is the
* currently active frame, 1 is the first enclosing level, and so
* on. Throws an error if the enclosing frame is invalid.
*/
vm_val_t *get_fp_at_level(VMG_ int level) const;
/*
* Get the current frame depth. This is the stack depth of the
* current frame pointer. This can be used to compare two frame
* pointers to determine if one encloses the other - the pointer
* with the smaller depth value encloses the larger one.
*/
size_t get_frame_depth(VMG0_) const
{ return G_stk->ptr_to_index(frame_ptr_); }
/* get the current frame pointer */
vm_val_t *get_frame_ptr() const { return frame_ptr_; }
/* given a frame pointer, get the enclosing frame pointer */
static vm_val_t *get_enclosing_frame_ptr(VMG_ vm_val_t *fp)
{
return (vm_val_t *)
G_stk->get_from_frame(fp, VMRUN_FPOFS_ENC_FP)->val.ptr;
}
/* get the number of arguments from a given frame */
int get_argc_from_frame(VMG_ vm_val_t *fp) const
{ return G_stk->get_from_frame(fp, VMRUN_FPOFS_ARGC)->val.intval; }
/* get the argument counter from a given stack level */
int get_argc_at_level(VMG_ int level) const
{ return get_argc_from_frame(vmg_ get_fp_at_level(vmg_ level)); }
/* given a frame pointer, get the 'self' object for the frame */
static vm_obj_id_t get_self_from_frame(VMG_ vm_val_t *fp)
{
vm_val_t *self_val;
/* get the 'self' slot on the stack */
self_val = G_stk->get_from_frame(fp, VMRUN_FPOFS_SELF);
/* return the appropriate value */
return (self_val->typ == VM_NIL ? VM_INVALID_OBJ : self_val->val.obj);
}
/* get the 'self' object at a given stack level */
vm_obj_id_t get_self_at_level(VMG_ int level) const
{ return get_self_from_frame(vmg_ get_fp_at_level(vmg_ level)); }
/* given a frame pointer, get the target property for the frame */
static vm_prop_id_t get_target_prop_from_frame(VMG_ vm_val_t *fp)
{
vm_val_t *val;
/* get the 'self' slot on the stack */
val = G_stk->get_from_frame(fp, VMRUN_FPOFS_PROP);
/* return the appropriate value */
return (val->typ == VM_NIL ? VM_INVALID_PROP : val->val.prop);
}
/* get the target property at a given stack level */
vm_prop_id_t get_target_prop_at_level(VMG_ int level) const
{
return get_target_prop_from_frame(vmg_ get_fp_at_level(vmg_ level));
}
/* given a frame pointer, get the defining object from the frame */
vm_obj_id_t get_defining_obj_from_frame(VMG_ vm_val_t *fp) const
{
vm_val_t *val;
/* get the defining object slot on the stack */
val = G_stk->get_from_frame(fp, VMRUN_FPOFS_DEFOBJ);
/* return the appropriate value */
return (val->typ == VM_NIL ? VM_INVALID_OBJ : val->val.obj);
}
/* get the defining object at a given stack level */
vm_obj_id_t get_defining_obj_at_level(VMG_ int level) const
{
return get_defining_obj_from_frame(vmg_ get_fp_at_level(vmg_ level));
}
/* given a frame pointer, get the original target object */
vm_obj_id_t get_orig_target_obj_from_frame(VMG_ vm_val_t *fp) const
{
vm_val_t *val;
/* get the original target object slot on the stack */
val = G_stk->get_from_frame(fp, VMRUN_FPOFS_ORIGTARG);
/* return the appropriate value */
return (val->typ == VM_NIL ? VM_INVALID_OBJ : val->val.obj);
}
/* get the current original target object at a given stack level */
vm_obj_id_t get_orig_target_obj_at_level(VMG_ int level) const
{
return get_orig_target_obj_from_frame(
vmg_ get_fp_at_level(vmg_ level));
}
/*
* get the enclosing entry pointer from a given frame (it's the
* second item pushed before the enclosing frame ponter, hence it's
* at offset -2 in the frame)
*/
static pool_ofs_t get_enclosing_entry_ptr_from_frame(VMG_ vm_val_t *fp)
{ return G_stk->get_from_frame(fp, VMRUN_FPOFS_ENC_EP)->val.ofs; }
/*
* Get the return offset from a given frame. This is the offset of
* the return address from the start of the method header for the
* frame.
*/
static ulong get_return_ofs_from_frame(VMG_ vm_val_t *fp)
{ return G_stk->get_from_frame(fp, VMRUN_FPOFS_RET)->val.ofs; }
/*
* Get the return address from a given frame. (The return address
* is the third item pushed before the enclosing frame pointer,
* hence it's at offset -3 from the frame pointer.) Returns zero if
* we were called by recursive invocation of the VM - this is not
* ambiguous with an actual return address of zero, since zero is
* never a valid code address.
*/
static pool_ofs_t get_return_addr_from_frame(VMG_ vm_val_t *fp)
{
pool_ofs_t ofs;
/* get the return method offset from the stack */
ofs = get_return_ofs_from_frame(vmg_ fp);
/*
* zero is never a valid method offset, so if the offset is zero
* it means that we're at a recursive invocation of the VM -
* indicate this by returning zero as the absolute pool address.
*/
if (ofs == 0)
return 0;
/*
* add the offset to the enclosing entry pointer to yield the
* absolute pool address of the return point
*/
return ofs + get_enclosing_entry_ptr_from_frame(vmg_ fp);
}
/*
* Given a frame pointer, set up a function pointer for the return
* address from the frame.
*/
static void set_return_funcptr_from_frame(VMG_ class CVmFuncPtr *func_ptr,
vm_val_t *frame_ptr);
/*
* Determine if we're in a recursive VM invocation. Returns true if
* we're in a recursive invocation, false if we're in the top-level
* invocation. (A recursive invocation occurs when native code
* called from byte code calls back into the VM to call byte code
* itself; because the invocation is recursive, we must return to
* the native code when the inner byte code returns rather than
* continuing with enclosing byte code directly.)
*/
int is_recursive_invocation(VMG0_) const;
/* reset the machine registers to the initial conditions */
void reset(VMG0_);
/*
* Get the current "self" object. The "self" object is always the
* implicit first parameter to any method. Note that this version of
* the method *doesn't* check for nil - it assumes that the caller
* knows for sure that there's a valid "self", so dispenses with any
* checks to save time.
*/
vm_obj_id_t get_self(VMG0_) const
{
/* get the object value of the 'self' slot in the current frame */
return G_stk->get_from_frame(frame_ptr_, VMRUN_FPOFS_SELF)->val.obj;
}
/* get the pointer to the current "self" value */
vm_val_t *get_self_val(VMG0_) const
{ return G_stk->get_from_frame(frame_ptr_, VMRUN_FPOFS_SELF); }
/*
* Get the current "self" object, checking for nil. If "self" is nil,
* we'll return VM_INVALID_OBJ. This version (not get_self()) should
* be used whenever it's not certain from context that there's a valid
* "self".
*/
vm_obj_id_t get_self_check(VMG0_) const
{
/* get the 'self' slot from the stack frame */
vm_val_t *valp = G_stk->get_from_frame(frame_ptr_, VMRUN_FPOFS_SELF);
/* if it's nil, return VM_INVALID_OBJ; otherwise, return the obj ID */
return (valp->typ == VM_NIL ? VM_INVALID_OBJ : valp->val.obj);
}
/* set the current 'self' object */
void set_self(VMG_ const vm_val_t *val)
{
/* store the given value in the 'self' slot in the current frame */
*G_stk->get_from_frame(frame_ptr_, VMRUN_FPOFS_SELF) = *val;
}
/*
* Set the current execution context: the 'self' value, the target
* property, the original target object, and the defining object.
*/
void set_method_ctx(VMG_ vm_obj_id_t new_self,
vm_prop_id_t new_target_prop,
vm_obj_id_t new_target_obj,
vm_obj_id_t new_defining_obj)
{
/* set the "self" slot in the current stack frame */
G_stk->get_from_frame(frame_ptr_, VMRUN_FPOFS_SELF)
->set_obj(new_self);
/* set the target property slot in the frame */
G_stk->get_from_frame(frame_ptr_, VMRUN_FPOFS_PROP)
->set_propid(new_target_prop);
/* set the original target object slot in the frame */
G_stk->get_from_frame(frame_ptr_, VMRUN_FPOFS_ORIGTARG)
->set_obj(new_target_obj);
/* set the defining object slot in the frame */
G_stk->get_from_frame(frame_ptr_, VMRUN_FPOFS_DEFOBJ)
->set_obj(new_defining_obj);
}
/* get the current target property value */
vm_prop_id_t get_target_prop(VMG0_) const
{
return G_stk->get_from_frame(frame_ptr_, VMRUN_FPOFS_PROP)->val.prop;
}
/* get the current defining object */
vm_obj_id_t get_defining_obj(VMG0_) const
{
return G_stk->get_from_frame(frame_ptr_, VMRUN_FPOFS_DEFOBJ)->val.obj;
}
/* get the current original target object */
vm_obj_id_t get_orig_target_obj(VMG0_) const
{
return G_stk->get_from_frame(frame_ptr_, VMRUN_FPOFS_ORIGTARG)
->val.obj;
}
/* push an object ID */
static void push_obj(VMG_ vm_obj_id_t obj)
{ G_stk->push()->set_obj(obj); }
/* push a property ID */
static void push_prop(VMG_ vm_prop_id_t prop)
{ G_stk->push()->set_propid(prop); }
/* push a boolean value */
static void push_bool(VMG_ int flag)
{ G_stk->push()->set_logical(flag); }
/* push nil */
static void push_nil(VMG0_)
{ G_stk->push()->set_nil(); }
/* push a code offset value */
static void push_codeofs(VMG_ pool_ofs_t ofs)
{ G_stk->push()->set_codeofs(ofs); }
/* push a stack pointer value */
static void push_stackptr(VMG_ vm_val_t *stack_ptr)
{ G_stk->push()->set_stack((void *)stack_ptr); }
/* push an integer value */
static void push_int(VMG_ int32 intval)
{ G_stk->push()->set_int(intval); }
/* push an enumerator value */
static void push_enum(VMG_ uint32 intval)
{ G_stk->push()->set_enum(intval); }
/*
* Touch the code page containing the currently executing code. This
* should be called just after any code translates another code page
* pointer, to ensure that the current method's code page is
* re-established as the most recently used and is thus not swapped
* out.
*/
void touch_entry_ptr_page(VMG0_)
{
/* translate the entry pointer */
G_code_pool->get_ptr(entry_ptr_);
}
/*
* get the current entry pointer (the code pool offset of the start of
* the current method)
*/
pool_ofs_t get_entry_ptr() const { return entry_ptr_; }
/* get the current program counter offset from the entry pointer */
uint get_method_ofs() const
{
/*
* Return the current program counter minus the current entry
* pointer. If there is no current program counter, we're not
* executing in byte code, so there's no method offset.
*/
if (pc_ptr_ != 0)
return *pc_ptr_ - entry_ptr_native_;
else
return 0;
}
/*
* Convert a pointer to the currently executing method into an offset
* from the start of the current method.
*/
ulong pc_to_method_ofs(const uchar *p)
{
/*
* get the memory address of the current entry pointer, and
* subtract that from the given memory pointer to get an offset
* from the start of the current method
*/
return p - entry_ptr_native_;
}
/*
* Create an exception of the given imported class and throw it. If
* the class is not exported, we'll create a basic run-time exception;
* if that's not defined, we'll create an arbitrary object.
*
* Arguments to the exception constructor are on the stack, with the
* argument count in argc. If the imported class doesn't exist, we'll
* instead throw an intrinsic-class-general-error exception with the
* given fallback message as explanatory text.
*/
void throw_new_class(VMG_ vm_obj_id_t cls, uint argc,
const char *fallback_msg);
/*
* Save/restore the interpreter context. This is for use by the
* debugger when evaluating an expression in the course of execution,
* to ensure that everything is reset properly to the enclosing
* execution context when it's finished.
*/
void save_context(VMG_ vmrun_save_ctx *ctx);
void restore_context(VMG_ vmrun_save_ctx *ctx);
/*
* Get the boundaries of the given source-code statement in the given
* function. Fills in the line pointer and *stm_start and *stm_end
* with information on the source line containing the given offset in
* the given method. Returns true if source information is
* successfully located for the given machine code address, false if
* not.
*
* If no debugging information is available for the given code
* location, this function cannot get the source-code statement
* bounds, and returns false.
*/
static int get_stm_bounds(VMG_ const class CVmFuncPtr *func_ptr,
ulong pc_ofs,
class CVmDbgLinePtr *line_ptr,
ulong *stm_start, ulong *stm_end);
/* -------------------------------------------------------------------- */
/*
* Set the HALT VM flag. This allows the debugger to terminate the
* program immediately, without allowing any more byte-code
* instructions to execute.
*/
void set_halt_vm(int f) { halt_vm_ = f; }
/* -------------------------------------------------------------------- */
/*
* Start profiling. This deletes any old profiling records and starts
* a new profiling session. We'll capture profiling data until
* end_profiling() is called. This function is only included in the
* build if the profiler is included in the build.
*/
void start_profiling();
/* end profiling */
void end_profiling();
/*
* get the profiling data - we'll invoke the callback once for each
* function in our table of data
*/
void get_profiling_data(VMG_
void (*cb)(void *ctx, const char *func_name,
unsigned long time_direct,
unsigned long time_in_children,
unsigned long call_cnt),
void *cb_ctx);
protected:
/*
* Execute byte code starting at a given address. This function
* retains control until the byte code function invoked returns or
* throws an unhandled exception.
*
* If an exception occurs and is not handled by the byte code, we'll
* throw VMERR_UNHANDLED_EXC with the exception object as the first
* parameter.
*/
void run(VMG_ const uchar *p);
/*
* Display a dstring via the default string display function. This
* function pushes a string value (with the given constant pool
* offset), then does the same work as do_call() to invoke a function
* with one argument.
*
* The string is identified by its offset in the constant pool.
*/
const uchar *disp_dstring(VMG_ pool_ofs_t ofs, uint caller_ofs,
vm_obj_id_t self);
/*
* Display the value at top of stack via the default string display
* function. does the same work as do_call() to invoke the function
* with one argument, which must already be on the stack.
*/
const uchar *disp_string_val(VMG_ uint caller_ofs, vm_obj_id_t self);
/*
* Convert a pointer to the currently executing method into a code
* pool offset.
*/
pool_ofs_t pc_to_code_ofs(VMG_ const uchar *p)
{
/*
* get the offset from the start of the current method, and add
* it to the code pool offset of the start of the current method
* to yield the code pool offset of the pointer
*/
return (pool_ofs_t)pc_to_method_ofs(p) + entry_ptr_;
}
/*
* Set up a function header pointer for the current function
*/
void set_current_func_ptr(VMG_ class CVmFuncPtr *func_ptr);
/* call a built-in function */
void call_bif(VMG_ uint set_index, uint func_index, uint argc);
/*
* Throw an exception. Returns a non-null program counter if a
* handler was found, false if not. If a handler was found, byte-code
* execution can proceed; if not, the byte-code execution loop must
* pass the exception up to its caller.
*/
const uchar *do_throw(VMG_ const uchar *pc, vm_obj_id_t exception_obj);
/*
* Inherit a property - this is essentially the same as get_prop,
* but rather than getting the property from the given object, this
* ignores any such property defined directly by the object and goes
* directly to the inherited definition. However, the "self" object
* is still the same as the current "self" object, since we want to
* evaluate the inherited method in the context of the original
* target "self" object.
*/
const uchar *inh_prop(VMG_ uint caller_ofs, vm_prop_id_t prop, uint argc);
/*
* Look up a property value without evaluating it. Returns true if we
* found the property, false if not.
*/
inline static int get_prop_no_eval(VMG_ const vm_val_t **target_obj,
vm_prop_id_t target_prop,
uint *argc, vm_obj_id_t *srcobj,
vm_val_t *val,
const vm_val_t **self,
vm_val_t *new_self);
/*
* Evaluate a property value. If the value contains code, we'll
* execute the code; if it contains a self-printing string, we'll
* display the string; otherwise, we'll push the value onto the stack.
*
* 'found' indicates whether or not the property is defined by the
* object. False indicates that the property is not defined, true
* that it is defined. If the property isn't defined, we'll simply
* discard arguments and push nil.
*
* If 'caller_ofs' is zero, we'll recursively invoke the interpreter
* loop if it's necessary to run a method; otherwise, we'll set up at
* the beginning of the method's code and let the caller proceed into
* the code.
*/
inline const uchar *eval_prop_val(VMG_ int found, uint caller_ofs,
const vm_val_t *val, vm_obj_id_t self,
vm_prop_id_t target_prop,
const vm_val_t *orig_target_obj,
vm_obj_id_t defining_obj,
uint argc);
/*
* Check a property for validity in a speculative evaluation. If
* evaulating the property would cause any side effects, we'll throw
* an error (VMERR_BAD_SPEC_EXPR); otherwise, we won't do anything.
* Side effects include displaying a dstring or invoking a method.
*/
void check_prop_spec_eval(VMG_ vm_obj_id_t obj, vm_prop_id_t prop);
/*
* Return from a function or method. Returns the new program counter
* at which to continue execution, and restore machine registers to
* the enclosing frame.
*
* Returns a non-null program counter if execution should proceed,
* null if we're returning from the outermost stack level. When we
* return null, the caller must return control to the host
* environment, since the host environment called the function from
* which we're returning.
*/
const uchar *do_return(VMG0_);
/*
* Enter/leave a recursive call frame. These functions are used only
* when a debugger is present. 'pc_ptr' is a pointer to the program
* counter in the calling byte-code frame.
*/
void enter_recursive_frame(VMG_ int argc, const uchar **pc_ptr);
void leave_recursive_frame(VMG0_);
/*
* append a stack trace to the given string, returning a new string
* object
*/
vm_obj_id_t append_stack_trace(VMG_ vm_obj_id_t str_obj);
/* push a value onto the stack */
static void pushval(VMG_ const vm_val_t *val) { G_stk->push(val); }
/* pop a value off the stack */
void popval(VMG_ vm_val_t *val) { G_stk->pop(val); }
/* add two values, leaving the result in *val1 */
void compute_sum(VMG_ vm_val_t *val1, vm_val_t *val2);
/* subtract one value from another, leaving the result in *val1 */
void compute_diff(VMG_ vm_val_t *val1, vm_val_t *val2);
/* compute the product, leaving the result in *val1 */
void compute_product(VMG_ vm_val_t *val1, vm_val_t *val2);
/* compute the quotient val1/val2, leaving the result in *val2 */
void compute_quotient(VMG_ vm_val_t *val1, vm_val_t *val2);
/* XOR two values and push the result */
void xor_and_push(VMG_ vm_val_t *val1, vm_val_t *val2);
/*
* index container_val by index_val (i.e., compute
* container_val[index_val]), storing the result at *result
*/
void apply_index(VMG_ vm_val_t *result,
const vm_val_t *container_val,
const vm_val_t *index_val);
/*
* Set the element at index index_val in container_val to new_val,
* and push the new container value. The container may be a new
* object, since some types (lists, for example) cannot have their
* values changed but instead create new objects when an indexed
* element is modified.
*/
void set_index(VMG_ vm_val_t *container_val,
const vm_val_t *index_val,
const vm_val_t *new_val);
/*
* create a new object of the given index into the metaclass
* dependency table for the load image file, using the given number
* of parameters; removes the parameters from the stack, and leaves
* the new object reference in register R0
*/
const uchar *new_and_store_r0(VMG_ const uchar *pc,
uint metaclass_idx, uint argc,
int is_transient);
/*
* Compare the two values at top of stack for equality; returns true
* if the values are equal, false if not. Removes the two values from
* the stack.
*/
int pop2_equal(VMG0_)
{
/* compare the values and return the result */
int ret = G_stk->get(1)->equals(vmg_ G_stk->get(0));
/* discard the values */
G_stk->discard(2);
/* return the result */
return ret;
}
/*
* Compare the magnitude of the two values at the top of the stack.
* Returns 1 if the value at (TOS-1) is greater than the value at TOS,
* -1 if (TOS-1) is less than (TOS), and 0 if the two value are equal.
* Removes the two values from the stack.
*/
int pop2_compare(VMG0_)
{
/* compare the values and return the result */
int ret = G_stk->get(1)->compare_to(vmg_ G_stk->get(0));
/* discard the values */
G_stk->discard(2);
/* return the result */
return ret;
}
/* is TOS-1 < TOS ? */
int pop2_compare_lt(VMG0_)
{
/* compare the values and return the result */
int ret = G_stk->get(1)->is_lt(vmg_ G_stk->get(0));
/* discard the values */
G_stk->discard(2);
/* return the result */
return ret;
}
/* is TOS-1 <= TOS ? */
int pop2_compare_le(VMG0_)
{
/* compare the values and return the result */
int ret = G_stk->get(1)->is_le(vmg_ G_stk->get(0));
/* discard the values */
G_stk->discard(2);
/* return the result */
return ret;
}
/* is TOS-1 > TOS ? */
int pop2_compare_gt(VMG0_)
{
/* compare the values and return the result */
int ret = G_stk->get(1)->is_gt(vmg_ G_stk->get(0));
/* discard the values */
G_stk->discard(2);
/* return the result */
return ret;
}
/* is TOS-1 >= TOS ? */
int pop2_compare_ge(VMG0_)
{
/* compare the values and return the result */
int ret = G_stk->get(1)->is_ge(vmg_ G_stk->get(0));
/* discard the values */
G_stk->discard(2);
/* return the result */
return ret;
}
/* given a constant pool offset, get a pointer to the constant data */
const char *get_const_ptr(VMG_ pool_ofs_t ofs) const
{ return G_const_pool->get_ptr(ofs); }
/*
* get a signed 16-bit byte-code operand, incrementing the
* instruction pointer past the operand
*/
int16 get_op_int16(const uchar **p)
{
int16 ret = (int16)osrp2s(*p);
*p += 2;
return ret;
}
/* get an unsigned 16-bit byte-code operand */
uint16 get_op_uint16(const uchar **p)
{
uint16 ret = (uint16)osrp2(*p);
*p += 2;
return ret;
}
/* get a signed 32-bit byte-code operand */
int32 get_op_int32(const uchar **p)
{
int32 ret = (int32)osrp4(*p);
*p += 4;
return ret;
}
/* get an unsigned 32-bit byte-code operand */
uint32 get_op_uint32(const uchar **p)
{
uint32 ret = (uint32)t3rp4u(*p);
*p += 4;
return ret;
}
/* get a signed 8-bit byte-code operand */
int get_op_int8(const uchar **p)
{
int ret = (int)(signed char)**p;
++(*p);
return ret;
}
/* get an unsigned 8-bit byte-code operand */
uint get_op_uint8(const uchar **p)
{
uint ret = (uint)**p;
++(*p);
return ret;
}
/* record a function or method entry in the profiler data */
void prof_enter(pool_ofs_t call_ofs,
vm_obj_id_t obj, vm_prop_id_t prop);
/* record a function or method exit in the profiler data */
void prof_leave();
/* find or create a function entry in the master profiler table */
class CVmHashEntryProfiler
*prof_find_master_rec(const struct vm_profiler_rec *p);
/* calculate an elapsed time */
void prof_calc_elapsed(vm_prof_time *diff, const vm_prof_time *a,
const vm_prof_time *b);
/* add an elapsed time value to a cumulative elapsed time value */
void prof_add_elapsed(vm_prof_time *sum, const vm_prof_time *val);
/* hash table enumeration callback for dumping profiler data */
static void prof_enum_cb(void *ctx0, class CVmHashEntry *entry0);
/*
* Function header size - obtained from the image file upon loading
*/
size_t funchdr_size_;
/*
* A pointer to a global variable in the object table (CVmObjTable)
* containing the function to invoke for the SAY and SAYVAL opcodes.
* This can be a function pointer, a function object, or nil. If this
* is nil, the SAY opcode will throw an error.
*/
struct vm_globalvar_t *say_func_;
/*
* The method to invoke for the SAY and SAYVAL opcodes when a valid
* "self" object is available. If no method is defined, this will
* be set to VM_INVALID_PROP.
*/
vm_prop_id_t say_method_;
/*
* R0 - data register 0. This register stores function return
* values.
*/
vm_val_t r0_;
/*
* EP - entrypoint register. This register stores the code offset
* of the method header of the currently executing code.
*/
pool_ofs_t entry_ptr_;
/*
* native entry pointer value - this is simply the translated value of
* the entry pointer (i.e., G_code_pool->get_ptr(entry_ptr_))
*/
const uchar *entry_ptr_native_;
/*
* FP - frame pointer register. This points to the base of the
* current stack activation frame. Local variables and parameters
* are reachable relative to this register.
*/
vm_val_t *frame_ptr_;
/*
* Pointer to program counter - we use this in the debugger to create
* pseudo-stack frames for system code when we recursively invoke the
* VM, and for finding the current PC from intrinsic function code.
*/
const uchar **pc_ptr_;
/*
* Flag: VM is halting. This is used by the debugger to force the
* program to stop executing. This is not used except with the
* debug-mode interpreter.
*/
int halt_vm_;
/* flag: profiling is active */
int profiling_;
/* in case we have a profiler, include the profiler stack */
struct vm_profiler_rec *prof_stack_;
size_t prof_stack_max_;
/* next available index in the profiler stack */
size_t prof_stack_idx_;
/*
* Start of execution in the currently active function, since the last
* call or return. This uses the OS-specific high-precision timer
* (defined by os_prof_curtime() in vmprof.h).
*
* Each time we call a function or return from a function, we measure
* the delta from this value, then add that in to the cumulative time
* for the function.
*/
vm_prof_time prof_start_;
/*
* profiler master hash table - this is a table with one entry for
* every function and method called since we began profiling, keyed by
* method or function ID (the object.property for a method, or the
* entrypoint code offset for a function)
*/
class CVmHashTable *prof_master_table_;
};
#endif /* VMRUN_H */
|