/* $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. */ 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; } /* 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)osrp4(*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 */