diff options
author | Miguel Sofer <miguel.sofer@gmail.com> | 2001-04-11 20:03:37 (GMT) |
---|---|---|
committer | Miguel Sofer <miguel.sofer@gmail.com> | 2001-04-11 20:03:37 (GMT) |
commit | 2034a5e2e208c1053fdb37c152aefad600c11bc5 (patch) | |
tree | 36c64b9049419a29486dc22c48e79da96ba4de78 | |
parent | e58fcf252f9d3d6e5c6d6f579d45044c60c903e0 (diff) | |
download | tcl-2034a5e2e208c1053fdb37c152aefad600c11bc5.zip tcl-2034a5e2e208c1053fdb37c152aefad600c11bc5.tar.gz tcl-2034a5e2e208c1053fdb37c152aefad600c11bc5.tar.bz2 |
First CVS version of the S4 engine (slight updates & bugfixes from
original)
-rw-r--r-- | generic/tclExecute.c | 5071 |
1 files changed, 5071 insertions, 0 deletions
diff --git a/generic/tclExecute.c b/generic/tclExecute.c new file mode 100644 index 0000000..7dc5786 --- /dev/null +++ b/generic/tclExecute.c @@ -0,0 +1,5071 @@ +/* + * tclExecute.c -- + * + * This file contains procedures that execute byte-compiled Tcl + * commands. + * + * Copyright (c) 1996-1997 Sun Microsystems, Inc. + * Copyright (c) 1998-2000 by Scriptics Corporation. + * + *********************************************************************** + * Experimental version of a new, hopefully faster bytecode engine (a + * previous version was announced under the nickname S4). + * Some compiler-dependent optimisations are defined in a few macros in + * the accompanying file tclExecute.h + *********************************************************************** + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * RCS: @(#) $Id: tclExecute.c,v 1.21.2.1 2001/04/11 20:03:37 msofer Exp $ + */ + +#include "tclInt.h" +#include "tclCompile.h" + +#ifdef NO_FLOAT_H +# include "../compat/float.h" +#else +# include <float.h> +#endif +#ifndef TCL_NO_MATH + +#include "tclMath.h" +#endif + +/* + * The stuff below is a bit of a hack so that this file can be used + * in environments that include no UNIX, i.e. no errno. Just define + * errno here. + */ + +#ifndef TCL_GENERIC_ONLY +#include "tclPort.h" +#else +#define NO_ERRNO_H +#endif + +#ifdef NO_ERRNO_H +int errno; +#define EDOM 33 +#define ERANGE 34 +#endif + +/* + * Boolean flag indicating whether the Tcl bytecode interpreter has been + * initialized. + */ + +static int execInitialized = 0; +TCL_DECLARE_MUTEX(execMutex) + +/* + * Variable that controls whether execution tracing is enabled and, if so, + * what level of tracing is desired: + * 0: no execution tracing + * 1: trace invocations of Tcl procs only + * 2: trace invocations of all (not compiled away) commands + * 3: display each instruction executed + * This variable is linked to the Tcl variable "tcl_traceExec". + */ + +int tclTraceExec = 0; + +typedef struct ThreadSpecificData { + /* + * The following global variable is use to signal matherr that Tcl + * is responsible for the arithmetic, so errors can be handled in a + * fashion appropriate for Tcl. Zero means no Tcl math is in + * progress; non-zero means Tcl is doing math. + */ + + int mathInProgress; + +} ThreadSpecificData; + +static Tcl_ThreadDataKey dataKey; + +/* + * The variable below serves no useful purpose except to generate + * a reference to matherr, so that the Tcl version of matherr is + * linked in rather than the system version. Without this reference + * the need for matherr won't be discovered during linking until after + * libtcl.a has been processed, so Tcl's version won't be used. + */ + +#ifdef NEED_MATHERR +extern int matherr(); +int (*tclMatherrPtr)() = matherr; +#endif + +/* + * Mapping from expression instruction opcodes to strings; used for error + * messages. Note that these entries must match the order and number of the + * expression opcodes (e.g., INST_LOR) in tclCompile.h. + */ + +static char *operatorStrings[] = { + "||", "&&", "|", "^", "&", "==", "!=", "<", ">", "<=", ">=", "<<", ">>", + "+", "-", "*", "/", "%", "+", "-", "~", "!", + "BUILTIN FUNCTION", "FUNCTION", + "", "", "", "", "", "", "", "", "eq", "ne", +}; + +/* + * Mapping from Tcl result codes to strings; used for error and debugging + * messages. + */ + +#ifdef TCL_COMPILE_DEBUG +static char *resultStrings[] = { + "TCL_OK", "TCL_ERROR", "TCL_RETURN", "TCL_BREAK", "TCL_CONTINUE" +}; +#endif + +/* + * These are used by evalstats to monitor object usage in Tcl. + */ + +#ifdef TCL_COMPILE_STATS +long tclObjsAlloced = 0; +long tclObjsFreed = 0; +#define TCL_MAX_SHARED_OBJ_STATS 5 +long tclObjsShared[TCL_MAX_SHARED_OBJ_STATS] = { 0, 0, 0, 0, 0 }; +#endif /* TCL_COMPILE_STATS */ + +/* + * Macros for testing floating-point values for certain special cases. Test + * for not-a-number by comparing a value against itself; test for infinity + * by comparing against the largest floating-point value. + */ + +#define IS_NAN(v) ((v) != (v)) +#ifdef DBL_MAX +# define IS_INF(v) (((v) > DBL_MAX) || ((v) < -DBL_MAX)) +#else +# define IS_INF(v) 0 +#endif + + +/* + * Macros used to cache often-referenced Tcl evaluation stack information + * in local variables. Note that a DECACHE_STACK_INFO()-CACHE_STACK_INFO() + * pair must surround any call inside TclExecuteByteCode (and a few other + * procedures that use this scheme) that could result in a recursive call + * to TclExecuteByteCode. + */ + +#define CACHE_STACK_INFO() tosPtr = eePtr->tosPtr + +#define DECACHE_STACK_INFO() eePtr->tosPtr = tosPtr + +/* + * Macros used to access items on the Tcl evaluation stack. PUSH_OBJECT + * increments the object's ref count since it makes the stack have another + * reference pointing to the object. However, POP_OBJECT does not decrement + * the ref count. This is because the stack may hold the only reference to + * the object, so the object would be destroyed if its ref count were + * decremented before the caller had a chance to, e.g., store it in a + * variable. It is the caller's responsibility to decrement the ref count + * when it is finished with an object. + * + * WARNING! It is essential that objPtr only appear once in the PUSH_OBJECT + * macro. The actual parameter might be an expression with side effects, + * and this ensures that it will be executed only once. + */ + +#define PUSH_OBJECT(objPtr) \ + Tcl_IncrRefCount(*(++tosPtr) = (objPtr)) + +#define POP_OBJECT() \ + (*tosPtr--) + +/* + * Set an object at stackTop, increase its refCount + */ +#define SET_TOS(objPtr) \ + Tcl_IncrRefCount(*tosPtr = (objPtr)) + +#define TOS \ + (*tosPtr) + + +/* + * Macros used to trace instruction execution. The macros TRACE, + * TRACE_WITH_OBJ, and O2S are only used inside TclExecuteByteCode. + * O2S is only used in TRACE* calls to get a string from an object. + */ + +#ifdef TCL_COMPILE_DEBUG +#define TRACE(a) \ + if (traceInstructions) { \ + fprintf(stdout, "%2d: %2d (%u) %s ", iPtr->numLevels, stackTop, \ + (unsigned int)(pc - codePtr->codeStart), \ + GetOpcodeName(pc)); \ + printf a; \ + } +#define TRACE_WITH_OBJ(a, objPtr) \ + if (traceInstructions) { \ + fprintf(stdout, "%2d: %2d (%u) %s ", iPtr->numLevels, stackTop, \ + (unsigned int)(pc - codePtr->codeStart), \ + GetOpcodeName(pc)); \ + printf a; \ + TclPrintObject(stdout, (objPtr), 30); \ + fprintf(stdout, "\n"); \ + } +#define O2S(objPtr) \ + Tcl_GetString(objPtr) +#else +#define TRACE(a) +#define TRACE_WITH_OBJ(a, objPtr) +#define O2S(objPtr) +#endif /* TCL_COMPILE_DEBUG */ + + +/* + * Declarations for local procedures to this file: + */ + +static void CallTraceProcedure _ANSI_ARGS_((Tcl_Interp *interp, + Trace *tracePtr, Command *cmdPtr, + char *command, int numChars, + int objc, Tcl_Obj *objv[])); +static void DupCmdNameInternalRep _ANSI_ARGS_((Tcl_Obj *objPtr, + Tcl_Obj *copyPtr)); +static int ExprAbsFunc _ANSI_ARGS_((Tcl_Interp *interp, + ExecEnv *eePtr, ClientData clientData)); +static int ExprBinaryFunc _ANSI_ARGS_((Tcl_Interp *interp, + ExecEnv *eePtr, ClientData clientData)); +static int ExprCallMathFunc _ANSI_ARGS_((Tcl_Interp *interp, + ExecEnv *eePtr, int objc, Tcl_Obj **objv)); +static int ExprDoubleFunc _ANSI_ARGS_((Tcl_Interp *interp, + ExecEnv *eePtr, ClientData clientData)); +static int ExprIntFunc _ANSI_ARGS_((Tcl_Interp *interp, + ExecEnv *eePtr, ClientData clientData)); +static int ExprRandFunc _ANSI_ARGS_((Tcl_Interp *interp, + ExecEnv *eePtr, ClientData clientData)); +static int ExprRoundFunc _ANSI_ARGS_((Tcl_Interp *interp, + ExecEnv *eePtr, ClientData clientData)); +static int ExprSrandFunc _ANSI_ARGS_((Tcl_Interp *interp, + ExecEnv *eePtr, ClientData clientData)); +static int ExprUnaryFunc _ANSI_ARGS_((Tcl_Interp *interp, + ExecEnv *eePtr, ClientData clientData)); +#ifdef TCL_COMPILE_STATS +static int EvalStatsCmd _ANSI_ARGS_((ClientData clientData, + Tcl_Interp *interp, int argc, char **argv)); +#endif +static void FreeCmdNameInternalRep _ANSI_ARGS_(( + Tcl_Obj *objPtr)); +#ifdef TCL_COMPILE_DEBUG +static char * GetOpcodeName _ANSI_ARGS_((unsigned char *pc)); +#endif +static ExceptionRange * GetExceptRangeForPc _ANSI_ARGS_((unsigned char *pc, + int catchOnly, ByteCode* codePtr)); +static char * GetSrcInfoForPc _ANSI_ARGS_((unsigned char *pc, + ByteCode* codePtr, int *lengthPtr)); +static void GrowEvaluationStack _ANSI_ARGS_((ExecEnv *eePtr)); +static void IllegalExprOperandType _ANSI_ARGS_(( + Tcl_Interp *interp, unsigned char *pc, + Tcl_Obj *opndPtr)); +static void InitByteCodeExecution _ANSI_ARGS_(( + Tcl_Interp *interp)); +#ifdef TCL_COMPILE_DEBUG +static void PrintByteCodeInfo _ANSI_ARGS_((ByteCode *codePtr)); +#endif +static int SetCmdNameFromAny _ANSI_ARGS_((Tcl_Interp *interp, + Tcl_Obj *objPtr)); +#ifdef TCL_COMPILE_DEBUG +static char * StringForResultCode _ANSI_ARGS_((int result)); +static void ValidatePcAndStackTop _ANSI_ARGS_(( + ByteCode *codePtr, unsigned char *pc, + int stackTop, int stackLowerBound, + int stackUpperBound)); +#endif +static int VerifyExprObjType _ANSI_ARGS_((Tcl_Interp *interp, + Tcl_Obj *objPtr)); + +/* + * Table describing the built-in math functions. Entries in this table are + * indexed by the values of the INST_CALL_BUILTIN_FUNC instruction's + * operand byte. + */ + +BuiltinFunc builtinFuncTable[] = { +#ifndef TCL_NO_MATH + {"acos", 1, {TCL_DOUBLE}, ExprUnaryFunc, (ClientData) acos}, + {"asin", 1, {TCL_DOUBLE}, ExprUnaryFunc, (ClientData) asin}, + {"atan", 1, {TCL_DOUBLE}, ExprUnaryFunc, (ClientData) atan}, + {"atan2", 2, {TCL_DOUBLE, TCL_DOUBLE}, ExprBinaryFunc, (ClientData) atan2}, + {"ceil", 1, {TCL_DOUBLE}, ExprUnaryFunc, (ClientData) ceil}, + {"cos", 1, {TCL_DOUBLE}, ExprUnaryFunc, (ClientData) cos}, + {"cosh", 1, {TCL_DOUBLE}, ExprUnaryFunc, (ClientData) cosh}, + {"exp", 1, {TCL_DOUBLE}, ExprUnaryFunc, (ClientData) exp}, + {"floor", 1, {TCL_DOUBLE}, ExprUnaryFunc, (ClientData) floor}, + {"fmod", 2, {TCL_DOUBLE, TCL_DOUBLE}, ExprBinaryFunc, (ClientData) fmod}, + {"hypot", 2, {TCL_DOUBLE, TCL_DOUBLE}, ExprBinaryFunc, (ClientData) hypot}, + {"log", 1, {TCL_DOUBLE}, ExprUnaryFunc, (ClientData) log}, + {"log10", 1, {TCL_DOUBLE}, ExprUnaryFunc, (ClientData) log10}, + {"pow", 2, {TCL_DOUBLE, TCL_DOUBLE}, ExprBinaryFunc, (ClientData) pow}, + {"sin", 1, {TCL_DOUBLE}, ExprUnaryFunc, (ClientData) sin}, + {"sinh", 1, {TCL_DOUBLE}, ExprUnaryFunc, (ClientData) sinh}, + {"sqrt", 1, {TCL_DOUBLE}, ExprUnaryFunc, (ClientData) sqrt}, + {"tan", 1, {TCL_DOUBLE}, ExprUnaryFunc, (ClientData) tan}, + {"tanh", 1, {TCL_DOUBLE}, ExprUnaryFunc, (ClientData) tanh}, +#endif + {"abs", 1, {TCL_EITHER}, ExprAbsFunc, 0}, + {"double", 1, {TCL_EITHER}, ExprDoubleFunc, 0}, + {"int", 1, {TCL_EITHER}, ExprIntFunc, 0}, + {"rand", 0, {TCL_EITHER}, ExprRandFunc, 0}, /* NOTE: rand takes no args. */ + {"round", 1, {TCL_EITHER}, ExprRoundFunc, 0}, + {"srand", 1, {TCL_INT}, ExprSrandFunc, 0}, + {0}, +}; + +/* + * The structure below defines the command name Tcl object type by means of + * procedures that can be invoked by generic object code. Objects of this + * type cache the Command pointer that results from looking up command names + * in the command hashtable. Such objects appear as the zeroth ("command + * name") argument in a Tcl command. + */ + +Tcl_ObjType tclCmdNameType = { + "cmdName", /* name */ + FreeCmdNameInternalRep, /* freeIntRepProc */ + DupCmdNameInternalRep, /* dupIntRepProc */ + (Tcl_UpdateStringProc *) NULL, /* updateStringProc */ + SetCmdNameFromAny /* setFromAnyProc */ +}; + +/* + *---------------------------------------------------------------------- + * + * InitByteCodeExecution -- + * + * This procedure is called once to initialize the Tcl bytecode + * interpreter. + * + * Results: + * None. + * + * Side effects: + * This procedure initializes the array of instruction names. If + * compiling with the TCL_COMPILE_STATS flag, it initializes the + * array that counts the executions of each instruction and it + * creates the "evalstats" command. It also registers the command name + * Tcl_ObjType. It also establishes the link between the Tcl + * "tcl_traceExec" and C "tclTraceExec" variables. + * + *---------------------------------------------------------------------- + */ + +static void +InitByteCodeExecution(interp) + Tcl_Interp *interp; /* Interpreter for which the Tcl variable + * "tcl_traceExec" is linked to control + * instruction tracing. */ +{ + Tcl_RegisterObjType(&tclCmdNameType); + if (Tcl_LinkVar(interp, "tcl_traceExec", (char *) &tclTraceExec, + TCL_LINK_INT) != TCL_OK) { + panic("InitByteCodeExecution: can't create link for tcl_traceExec variable"); + } +#ifdef TCL_COMPILE_STATS + Tcl_CreateCommand(interp, "evalstats", EvalStatsCmd, + (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL); +#endif /* TCL_COMPILE_STATS */ +} + +/* + *---------------------------------------------------------------------- + * + * TclCreateExecEnv -- + * + * This procedure creates a new execution environment for Tcl bytecode + * execution. An ExecEnv points to a Tcl evaluation stack. An ExecEnv + * is typically created once for each Tcl interpreter (Interp + * structure) and recursively passed to TclExecuteByteCode to execute + * ByteCode sequences for nested commands. + * + * Results: + * A newly allocated ExecEnv is returned. This points to an empty + * evaluation stack of the standard initial size. + * + * Side effects: + * The bytecode interpreter is also initialized here, as this + * procedure will be called before any call to TclExecuteByteCode. + * + *---------------------------------------------------------------------- + */ + +#define TCL_STACK_INITIAL_SIZE 2000 + +ExecEnv * +TclCreateExecEnv(interp) + Tcl_Interp *interp; /* Interpreter for which the execution + * environment is being created. */ +{ + ExecEnv *eePtr = (ExecEnv *) ckalloc(sizeof(ExecEnv)); + + eePtr->stackPtr = (Tcl_Obj **) + ckalloc((unsigned) (TCL_STACK_INITIAL_SIZE * sizeof(Tcl_Obj *))); + eePtr->tosPtr = eePtr->stackPtr - 1; + eePtr->stackEndPtr = eePtr->stackPtr + (TCL_STACK_INITIAL_SIZE - 1); + + Tcl_MutexLock(&execMutex); + if (!execInitialized) { + TclInitAuxDataTypeTable(); + InitByteCodeExecution(interp); + execInitialized = 1; + } + Tcl_MutexUnlock(&execMutex); + return eePtr; +} +#undef TCL_STACK_INITIAL_SIZE + + +/* + *---------------------------------------------------------------------- + * + * TclDeleteExecEnv -- + * + * Frees the storage for an ExecEnv. + * + * Results: + * None. + * + * Side effects: + * Storage for an ExecEnv and its contained storage (e.g. the + * evaluation stack) is freed. + * + *---------------------------------------------------------------------- + */ + +void +TclDeleteExecEnv(eePtr) + ExecEnv *eePtr; /* Execution environment to free. */ +{ + Tcl_EventuallyFree((ClientData)eePtr->stackPtr, TCL_DYNAMIC); + ckfree((char *) eePtr); +} + +/* + *---------------------------------------------------------------------- + * + * TclFinalizeExecution -- + * + * Finalizes the execution environment setup so that it can be + * later reinitialized. + * + * Results: + * None. + * + * Side effects: + * After this call, the next time TclCreateExecEnv will be called + * it will call InitByteCodeExecution. + * + *---------------------------------------------------------------------- + */ + +void +TclFinalizeExecution() +{ + Tcl_MutexLock(&execMutex); + execInitialized = 0; + Tcl_MutexUnlock(&execMutex); + TclFinalizeAuxDataTypeTable(); +} + +/* + *---------------------------------------------------------------------- + * + * GrowEvaluationStack -- + * + * This procedure grows a Tcl evaluation stack stored in an ExecEnv. + * + * Results: + * None. + * + * Side effects: + * The size of the evaluation stack is doubled. + * + *---------------------------------------------------------------------- + */ + +static void +GrowEvaluationStack(eePtr) + register ExecEnv *eePtr; /* Points to the ExecEnv with an evaluation + * stack to enlarge. */ +{ + /* + * The current Tcl stack elements are stored from eePtr->stackPtr + * to eePtr->stackEndPtr (inclusive). + */ + + int currElems = (eePtr->stackEndPtr - eePtr->stackPtr) + 1; + int newElems = 2*currElems; + int currBytes = currElems * sizeof(Tcl_Obj *); + int newBytes = 2*currBytes; + int currStackDiff = (eePtr->tosPtr - eePtr->stackPtr); + Tcl_Obj **stackPtr = (Tcl_Obj **) ckalloc((unsigned) newBytes); + + /* + * Copy the existing stack items to the new stack space, free the old + * storage if appropriate, and mark new space as malloc'ed. + */ + + memcpy((VOID *) stackPtr, (VOID *) eePtr->stackPtr, + (size_t) currBytes); + Tcl_EventuallyFree((ClientData)eePtr->stackPtr, TCL_DYNAMIC); + ckfree((char *) eePtr->stackPtr); + + eePtr->stackPtr = stackPtr; + eePtr->stackEndPtr = stackPtr + (newElems - 1); /* i.e. index of last usable item */ + eePtr->tosPtr = stackPtr + currStackDiff; +} + + +/* + * MACROS TO CREATE A STACK OF OBJECTS TO BE FREED + * + * The stack is emptied after each intruction that may have set something + * in the stack, and also at (abnormalReturn:) and (processCatch:). + * + * The freeing of the stack is done by a loop on TclDecrRefCount (inline version). + * + * The code contains different manners of DecrRefCount. + * + The standard Tcl two (TclDecrRefCount, Tcl_DecrRefCount) maintain their meaning + * as defined in tclInt.h and tcl.h. + * + For each of these two, a new version with suffix _Q is added, + * Suffix _Q stacks the requests. NEXT_Q is called at instructions + * that may decrease the refCount of variables. + * The main effect of the stack is to allow for fast processing (inline) without + * object code bloating (~ 2K); the speed effect of the stack (versus inline processing + * at all places) seems negligible. + * + * The stack has a constant size (set below at 4, which is actually too large); + * BE CAREFUL not to stack objects for freeing from within a loop! You may well cause + * an overflow of the stack, with dire consequences ... + * + * A further remark on DecrRefCount: as the processing of a catch automatically + * frees objects on the tcl stack, we sometimes just increase the pointer to + * get some objects to be freed included in that process. Look for instructions like + * "tosPtr++" before a "goto checkForCatch". + */ +#define TclDecrRefCount_Q(objPtr) *decrRefQTop++ = (objPtr) +#define Tcl_DecrRefCount_Q(objPtr) TclDecrRefCount_Q(objPtr) +#define NEXT_INSTR_Q goto instructions_start_Q +#define DECR_REF_STACK_empty() \ + {\ + Tcl_Obj **locQTop = decrRefQTop;\ + while (locQTop > decrRefQ) {\ + Tcl_Obj *objPtr = *(--locQTop); \ + TclDecrRefCount(objPtr);\ + }\ + decrRefQTop = locQTop;\ + } + + +/* ********************************* + * Common code; out here for clarity + * ********************************* + */ +/* Use the object at TOS if it is not shared; otherwise, + * create a new one. + */ +#define USE_OR_MAKE_THEN_SET(value,typeName) \ + {\ + Tcl_Obj *objPtr = TOS;\ + if (Tcl_IsShared(objPtr)) {\ + /* If it is shared, just decrease the refCount ... */\ + (objPtr)->refCount--;\ + SET_TOS(Tcl_New ## typeName ## Obj(value));\ + } else { /* reuse the valuePtr object */\ + /* valuePtr now on stk top has right r.c. */\ + Tcl_Set ## typeName ## Obj(objPtr, value);\ + }\ + } + +union AuxPtr { + Tcl_Obj *valuePtr; + Var *varPtr; +}; + +union AuxVar { + long i; + double d; +}; + +#define TRY_CONVERT_TO_NUM(valuePtr,X,tPtr) \ +/* Tcl_Obj *valuePtr; X is an AuxVar union, tPtr points to the\ + * type of the object after conversion */ \ + { \ + if ((tPtr) == &tclIntType) {\ + (X).i = (valuePtr)->internalRep.longValue;\ + } else if (((tPtr) == &tclDoubleType) && ((valuePtr)->bytes == NULL)) {\ + /*\ + * We can only use the internal rep directly if there is\ + * no string rep. Otherwise the string rep might actually\ + * look like an integer, which is preferred.\ + */\ + (X).d = (valuePtr)->internalRep.doubleValue;\ + } else {\ + if (TclLooksLikeInt(TclGetString(valuePtr),(valuePtr)->length)) {\ + long XX;\ + (void) Tcl_GetLongFromObj((Tcl_Interp *) NULL,\ + (valuePtr), &XX);\ + (X).i = XX;\ + } else {\ + double XX;\ + (void) Tcl_GetDoubleFromObj((Tcl_Interp *) NULL,\ + (valuePtr), &XX);\ + (X).d = XX;\ + }\ + (tPtr) = (valuePtr)->typePtr;\ + }\ + } + +/* + * INLINING from Tcl_GetByteArrayFromObj (tclBinary.c) requires this ... + */ + +typedef struct ByteArray { + int used; /* The number of bytes used in the byte + * array. */ + int allocated; /* The amount of space actually allocated + * minus 1 byte. */ + unsigned char bytes[4]; /* The array of bytes. The actual size of + * this field depends on the 'allocated' field + * above. */ +} ByteArray; + + +/* + * Include the compiler-dependent macros that determine the + * instruction-threading method used by tclExecute.c + * + * An instruction-threading method has to define the following macros: + * + * . _CASE(instruction) the labelling method for instruction start + * . _CASE_DECLS declarations of special variables required + * . _CASE_START start of the block containing instructions + * . _CASE_END end of the block containing instructions + * . CHECK_OPCODES 0/1, if the opcodes have to be checked before RT + * . NEXT_INSTR the jump to the next instruction + */ + +/* + * THIS IS ONLY FOR USE WHILE DEBUGGING! + * + * Set the default method here by uncommenting the corresponding line. + * If no line is uncommented, the choice will be according to the compiler + * used (see tclExecute.h for details) + * + * To enable bytecode tracing via [set tcl_traceExec 3] uncomment the + * last line (it only works for SWITCH method, and will set it accordingly) + */ + + /* #define JUMP_version GCC*/ + /* #define JUMP_version MSVC*/ + /* #define JUMP_version SWITCH*/ + + /* #define TCL_BYTECODE_DEBUG 1 */ + +#include "tclExecute.h" + + +/* + * TclVerifyOpcodes + * + * This function verifies that a given byteCode does not try to + * call an inexistent opCode; it is NOT NECESSARY for SWITCH method, + * as it performs the validity check at run time. + * + * If it is OK, it marks the bytecode as verified and returns 1; if there + * is a bad opCode, it panics with a message. + */ + +#if CHECK_OPCODES == 1 +static void +TclVerifyOpcodes(codePtr) + register ByteCode *codePtr; /* The bytecode sequence to verify. */ +{ + register unsigned char *pc = codePtr->codeStart; + unsigned char *pcEnd; + pcEnd = pc + codePtr->numCodeBytes; + while (pc < pcEnd) { + if (*pc <= LAST_INST_OPCODE) { + pc += instructionTable[*pc].numBytes; + } else { + panic("TclExecuteByteCode: unrecognized opCode %u", *pc); + } + } + codePtr->flags |= TCL_BYTECODE_OPCODES_OK; +} +#endif + + +/* + *---------------------------------------------------------------------- + * + * TclExecuteByteCode -- + * + * This procedure executes the instructions of a ByteCode structure. + * It returns when a "done" instruction is executed or an error occurs. + * + * Results: + * The return value is one of the return codes defined in tcl.h + * (such as TCL_OK), and interp->objResultPtr refers to a Tcl object + * that either contains the result of executing the code or an + * error message. + * + * Side effects: + * Almost certainly, depending on the ByteCode's instructions. + * + *---------------------------------------------------------------------- + */ + +int +TclExecuteByteCode(interp, codePtr) + Tcl_Interp *interp; /* Token for command interpreter. */ + ByteCode *codePtr; /* The bytecode sequence to interpret. */ +{ + Interp *iPtr = (Interp *) interp; + ExecEnv *eePtr = iPtr->execEnvPtr; /* Points to the execution environment. */ + Tcl_Obj **tosPtr = eePtr->tosPtr; /* Cached pointer to top of evaluation stack. */ + unsigned int initTos = tosPtr - eePtr->stackPtr; /* Stack top at start of execution. */ + unsigned char *pc = codePtr->codeStart; /* The current program counter. */ + int result = TCL_OK; /* Return code returned after execution. */ +#define DECR_REF_STACK_SIZE 4 + Tcl_Obj *decrRefQ[DECR_REF_STACK_SIZE]; /* structure for objs to be decrRef'ed */ +#undef DECR_REF_STACK_SIZE + Tcl_Obj **decrRefQTop = decrRefQ; + + _CASE_DECLS /* DO NOT PUT A SEMICOLON HERE, it can be empty ! */ + + /* + * This procedure uses a stack to hold information about catch commands. + * This information is the current operand stack top when starting to + * execute the code for each catch command. It starts out with stack- + * allocated space but uses dynamically-allocated storage if needed. + */ + +#define STATIC_CATCH_STACK_SIZE 4 + unsigned int catchStackStorage[STATIC_CATCH_STACK_SIZE]; + unsigned int *catchStackPtr = catchStackStorage; + unsigned int *catchTopPtr = catchStackStorage; + +#ifdef TCL_COMPILE_STATS + iPtr->stats.numExecutions++; +#endif + +#if CHECK_OPCODES == 1 + /* + * Make sure that the opcodes being called are all valid - not used + * by methods that check that at runtime (e.g., SWITCH). + * + * We do it only once per bytecode: TclVerifyOpcodes caches the result + * by setting a flag (codePtr->flags |= TCL_BYTECODE_OPCODES_OK) + * + *** Thanks to Donal Fellows for the idea! *** + */ + + if (!(codePtr->flags & TCL_BYTECODE_OPCODES_OK)) { + TclVerifyOpcodes(codePtr); + } +#endif + + /* + * Make sure the catch stack is large enough to hold the maximum number + * of catch commands that could ever be executing at the same time. This + * will be no more than the exception range array's depth. + */ + + if (codePtr->maxExceptDepth > STATIC_CATCH_STACK_SIZE) { + catchStackPtr = (unsigned int *) + ckalloc(codePtr->maxExceptDepth * sizeof(unsigned int)); + } + catchTopPtr = catchStackPtr; + + /* + * Make sure the stack has enough room to execute this ByteCode. + */ + + while ((tosPtr + codePtr->maxStackDepth) > eePtr->stackEndPtr) { + GrowEvaluationStack(eePtr); + CACHE_STACK_INFO(); + } + + /* + * Loop executing instructions until a "done" instruction, a TCL_RETURN, + * or some error. + */ + + NEXT_INSTR; + + instructions_start_Q: + DECR_REF_STACK_empty(); + + _CASE_START /* DO NOT PUT A SEMICOLON HERE, it can be a { ! */ + + _CASE(INST_DONE): /* tosPtr -= 1 */ + { + /* + * Pop the topmost object from the stack, set the interpreter's + * object result to point to it, and return. + */ + Tcl_Obj *valuePtr = POP_OBJECT(); + Tcl_SetObjResult(interp, valuePtr); + valuePtr->refCount--; /* result has a reference, IT IS SHARED! */ + + { + int currTos = tosPtr - eePtr->stackPtr; + if (currTos != initTos) { + /* + * if extra items in the stack, clean up the stack before return + */ + if (currTos > initTos) goto abnormalReturn; + + fprintf(stderr, "\nTclExecuteByteCode: done instruction at pc %u: stack top %d < entry stack top %d\n", + (unsigned int)(pc - codePtr->codeStart), + (unsigned int) (currTos), + (unsigned int) (initTos)); + panic("TclExecuteByteCode execution failure: end stack top < start stack top"); + } + } + + goto done; + } + + _CASE(INST_PUSH1): /* tosPtr += 1 */ + { + pc++; + PUSH_OBJECT(codePtr->objArrayPtr[TclGetUInt1AtPtr(pc)]); + pc++; + NEXT_INSTR; + } + + _CASE(INST_PUSH4): /* tosPtr += 1 */ + { + pc++; + PUSH_OBJECT(codePtr->objArrayPtr[TclGetUInt4AtPtr(pc)]); + pc += 4; + NEXT_INSTR; + } + + _CASE(INST_POP): /* tosPtr -= 1 */ + { + Tcl_Obj *valuePtr = POP_OBJECT(); + TclDecrRefCount(valuePtr); /* finished with pop'ed object. */ + pc++; + NEXT_INSTR; + } + + _CASE(INST_DUP): /* tosPtr += 1 */ + { + Tcl_Obj *item = TOS; + PUSH_OBJECT(Tcl_DuplicateObj(item)); + pc++; + NEXT_INSTR; + } + + _CASE(INST_CONCAT1): /* tosPtr -= (n-1) */ + { + int totalLen = 0; + Tcl_Obj **firstItem; + + pc++; + firstItem = tosPtr - (TclGetUInt1AtPtr(pc) - 1); + + /* + * Concatenate strings (with no separators) from the top + * opnd items on the stack starting with the deepest item. + * First, determine how many characters are needed. + */ + { + Tcl_Obj **itemPtr; + for (itemPtr = firstItem; itemPtr <= tosPtr; itemPtr++) { + Tcl_Obj* item = *itemPtr; + if (Tcl_GetString(item) != NULL) { + totalLen += item->length; + } + } + } + + /* + * Initialize the new append string object by appending the + * strings of the opnd stack objects. Also pop the objects. + */ + + { + Tcl_Obj *concatObjPtr; + TclNewObj(concatObjPtr); + if (totalLen > 0) { + char *p = (char *) ckalloc((unsigned) (totalLen + 1)); + Tcl_Obj **itemPtr; + concatObjPtr->bytes = p; + concatObjPtr->length = totalLen; + for (itemPtr = firstItem; itemPtr <= tosPtr; itemPtr++) { + Tcl_Obj *item = *itemPtr; + if (item->bytes != NULL) { + memcpy((VOID *) p, (VOID *) item->bytes, + (size_t) item->length); + p += item->length; + } + /* in a loop: do not _Q */ + TclDecrRefCount(item); + } + *p = '\0'; + } else { + for ( ; tosPtr >= firstItem; tosPtr--) { + Tcl_Obj *item = TOS; + /* in a loop: do not _Q */ + Tcl_DecrRefCount(item); + } + } + /* This pushes concatObjPtr */ + tosPtr = firstItem; + SET_TOS(concatObjPtr); + pc++; + NEXT_INSTR; + } + } + + _CASE(INST_INVOKE_STK4): /* tosPtr -= (n-1) */ + { + int objc; +#ifdef TCL_BYTECODE_DEBUG /* need the reference for messages! */ + unsigned char *oldPc; + oldPc = pc; +#endif + pc++; + objc = TclGetUInt4AtPtr(pc); + pc += 4; + goto doInvocation; + + _CASE(INST_INVOKE_STK1): +#ifdef TCL_BYTECODE_DEBUG + oldPc = pc; +#endif + pc++; + objc = TclGetUInt1AtPtr(pc); + pc++; + + doInvocation: + /* + * If the interpreter was deleted, return an error. + */ + + if (iPtr->flags & DELETED) { + pc = pc--; /* to get back within the scope of the cmd */ + Tcl_ResetResult(interp); + Tcl_AppendToObj(Tcl_GetObjResult(interp), + "attempt to call eval in deleted interpreter", -1); + Tcl_SetErrorCode(interp, "CORE", "IDELETE", + "attempt to call eval in deleted interpreter", + (char *) NULL); + result = TCL_ERROR; + goto checkForCatch; + } + { + Tcl_Obj **objv; /* The array of argument objects. */ + Command *cmdPtr; /* Points to command's Command struct. */ + Tcl_Obj **preservedStack; + /* Reference to memory block containing + * objv array (must be kept live throughout + * trace and command invokations.) */ + + /* + * Find the procedure to execute this command. If the + * command is not found, handle it with the "unknown" proc. + */ + + objv = tosPtr - (objc-1); + /* ONLY CALL: maybe inline, maybe using gcc function inlining? OJO: ahora son dos ...*/ + cmdPtr = (Command *) Tcl_GetCommandFromObj(interp, objv[0]); + if (cmdPtr == NULL) { + cmdPtr = (Command *) Tcl_FindCommand(interp, "unknown", + (Tcl_Namespace *) NULL, TCL_GLOBAL_ONLY); + if (cmdPtr == NULL) { + pc = pc--; /* to get back within the scope of the cmd */ + Tcl_ResetResult(interp); + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "invalid command name \"", + Tcl_GetString(objv[0]), "\"", + (char *) NULL); + result = TCL_ERROR; + goto checkForCatch; + } + { + Tcl_Obj** item; + for (item = tosPtr; item >= objv ; item--) { + item[1] = item[0]; + } + tosPtr++; /* need room for new inserted objv[0] */ + } + objc++; + objv[0] = Tcl_NewStringObj("unknown", -1); + Tcl_IncrRefCount(objv[0]); + } + + /* + * A reference to part of the stack vector itself + * escapes our control, so must use preserve/release + * to stop it from being deallocated by a recursive + * call to ourselves. The extra variable is needed + * because all others are liable to change due to the + * trace procedures. + */ + + preservedStack = eePtr->stackPtr; + Tcl_Preserve((ClientData) preservedStack); + + /* + * Call any trace procedures. + */ + + if (iPtr->tracePtr != NULL) { + Trace *tracePtr, *nextTracePtr; + + for (tracePtr = iPtr->tracePtr; tracePtr != NULL; + tracePtr = nextTracePtr) { + nextTracePtr = tracePtr->nextPtr; + if (iPtr->numLevels <= tracePtr->level) { + int numChars; + char *cmd = GetSrcInfoForPc(pc--, codePtr, &numChars); + if (cmd != NULL) { + DECACHE_STACK_INFO(); + CallTraceProcedure(interp, tracePtr, cmdPtr, + cmd, numChars, objc, objv); + CACHE_STACK_INFO(); + objv = tosPtr - (objc-1); /* ATTN: if stack grew, wrong ...*/ + + } + } + } + } + + /* + * Finally, invoke the command's Tcl_ObjCmdProc. First reset + * the interpreter's string and object results to their + * default empty values since they could have gotten changed + * by earlier invocations. + */ + + Tcl_ResetResult(interp); + +#ifdef TCL_BYTECODE_DEBUG + if (tclTraceExec >= 2) { + fprintf(stdout, "%d: (%u) invoking %s\n", iPtr->numLevels, + (unsigned int)(oldPc - codePtr->codeStart), + Tcl_GetString(objv[0])); + } +#endif + + iPtr->cmdCount++; + DECACHE_STACK_INFO(); + result = (*cmdPtr->objProc)(cmdPtr->objClientData, interp, + objc, objv); + if (Tcl_AsyncReady()) { + result = Tcl_AsyncInvoke(interp, result); + } + CACHE_STACK_INFO(); + + + /* + * Pop the objc top stack elements and decrement their ref + * counts. + */ + + objv = tosPtr - (objc-1); /* ATTN: if stack grew, value changed ... */ + for (; tosPtr >= objv; tosPtr--) { + Tcl_Obj *objPtr = TOS; + TclDecrRefCount(objPtr); + } + + /* + * If the old stack is going to be released, it is + * safe to do so now, since no references to objv are + * going to be used from now on. + */ + + Tcl_Release((ClientData)preservedStack); + } + if (result != TCL_OK) { + pc = pc--; /* to get back within the scope of the cmd */ + goto bad_return_from_invoke_or_eval; + } + /* + * If the interpreter has a non-empty string result, the + * result object is either empty or stale because some + * procedure set interp->result directly. If so, move the + * string result to the result object, then reset the + * string result. + */ + + if (*(iPtr->result) != 0) { + PUSH_OBJECT(Tcl_GetObjResult(interp)); + NEXT_INSTR; + } else { + PUSH_OBJECT(iPtr->objResultPtr); + NEXT_INSTR; + } + } + + _CASE(INST_EVAL_STK): /* tosPtr += 0 */ + { + Tcl_Obj *objPtr = TOS; + DECACHE_STACK_INFO(); + result = Tcl_EvalObjEx(interp, objPtr, 0); + CACHE_STACK_INFO(); + TclDecrRefCount(objPtr); + if (result != TCL_OK) { + tosPtr--; /* stack needs to be properly set here ! */ + goto bad_return_from_invoke_or_eval; + } + pc++; + if (*(iPtr->result) != 0) { + SET_TOS(Tcl_GetObjResult(interp)); + NEXT_INSTR; + } else { + SET_TOS(iPtr->objResultPtr); + NEXT_INSTR; + } + } + + _CASE(INST_EXPR_STK): /* tosPtr += 0 */ + { + Tcl_Obj *objPtr = TOS; + Tcl_Obj *valuePtr; + Tcl_ResetResult(interp); + DECACHE_STACK_INFO(); + result = Tcl_ExprObj(interp, objPtr, &valuePtr); + CACHE_STACK_INFO(); + if (result != TCL_OK) { + goto checkForCatch; /* it will decrRefCt TOS */ + } + TclDecrRefCount(objPtr); + TOS = valuePtr; /* already has right refct */ + pc++; + NEXT_INSTR; + } + + _CASE(INST_LOAD_SCALAR4): /* tosPtr += 1 */ + { + int index; + pc++; + index = TclGetUInt4AtPtr(pc); + pc += 4; + goto doLoadScalar; + + _CASE(INST_LOAD_SCALAR1): + pc++; + index = TclGetUInt1AtPtr(pc); + pc++; + + doLoadScalar: + { + Tcl_Obj *valuePtr; + { /* INLINING from TclGetIndexedScalar */ + Var *compiledLocals = iPtr->varFramePtr->compiledLocals; + Var *varPtr = &(compiledLocals[index]); + while (TclIsVarLink(varPtr)) varPtr = varPtr->value.linkPtr; + if ((varPtr->tracePtr == NULL) && TclIsVarScalarDefined(varPtr)) { + valuePtr = varPtr->value.objPtr; + } else { + /* original implementation */ + DECACHE_STACK_INFO(); + valuePtr = TclGetIndexedScalar(interp, index, /*leaveErrorMsg*/ 1); + CACHE_STACK_INFO(); + } + } + if (valuePtr == NULL) { + result = TCL_ERROR; + pc--; /* to get back within the scope of the cmd */ + goto checkForCatch; + } + PUSH_OBJECT(valuePtr); + NEXT_INSTR; + } + } + + _CASE(INST_LOAD_ARRAY_STK): /* tosPtr -= 1 */ + { + Tcl_Obj *valuePtr, *elemPtr; + + elemPtr = POP_OBJECT(); + TclDecrRefCount_Q(elemPtr); + goto doLoadStk; + + _CASE(INST_LOAD_STK): + _CASE(INST_LOAD_SCALAR_STK): /* tosPtr += 0 */ + elemPtr = NULL; + + doLoadStk: + { + Tcl_Obj *objPtr = TOS; + DECACHE_STACK_INFO(); + valuePtr = Tcl_ObjGetVar2(interp, objPtr, elemPtr, TCL_LEAVE_ERR_MSG); + CACHE_STACK_INFO(); + + if (valuePtr == NULL) { + result = TCL_ERROR; + goto checkForCatch; + } + TclDecrRefCount_Q(objPtr); + SET_TOS(valuePtr); + pc++; + NEXT_INSTR_Q; + } + + _CASE(INST_LOAD_ARRAY4): /* tosPtr += 0 */ + { + int index; + pc++; + index = TclGetUInt4AtPtr(pc); + pc += 4; + goto doLoadArray; + + _CASE(INST_LOAD_ARRAY1): + pc++; + index = TclGetUInt1AtPtr(pc); + pc++; + + doLoadArray: + { + Tcl_Obj *elemPtr = TOS; + Tcl_Obj *valuePtr; + + DECACHE_STACK_INFO(); + valuePtr = TclGetElementOfIndexedArray(interp, index, + elemPtr, /*leaveErrorMsg*/ 1); + CACHE_STACK_INFO(); + if (valuePtr == NULL) { + result = TCL_ERROR; + pc--; /* to get back within the scope of the cmd */ + goto checkForCatch; /* will decreRefCt elemPtr at TOS */ + } + TclDecrRefCount(elemPtr); + SET_TOS(valuePtr); + NEXT_INSTR; + } + } + + _CASE(INST_STORE_SCALAR4): /* tosPtr += 0 */ + { + int index; + pc++; + index = TclGetUInt4AtPtr(pc); + pc += 4; + goto doStoreScalar; + + _CASE(INST_STORE_SCALAR1): + pc++; + index = TclGetUInt1AtPtr(pc); + pc++; + + doStoreScalar: + { + Tcl_Obj *valuePtr = TOS; + Tcl_Obj *value2Ptr; + /* INLINING from TclSetIndexedScalar */ + Var *compiledLocals = iPtr->varFramePtr->compiledLocals; + Var *varPtr = &(compiledLocals[index]); + while (TclIsVarLink(varPtr)) varPtr = varPtr->value.linkPtr; + if ((varPtr->tracePtr == NULL) + && !TclIsVarArrayDefined(varPtr) + && !((varPtr->flags & VAR_IN_HASHTABLE) && (varPtr->hPtr == NULL))) { + value2Ptr = varPtr->value.objPtr; + TclSetVarScalarDefined(varPtr); + if (valuePtr != value2Ptr) { + varPtr->value.objPtr = valuePtr; + Tcl_IncrRefCount(valuePtr); + if (value2Ptr != NULL) { + TclDecrRefCount(value2Ptr); + } + } + } else { + /* original implementation */ + DECACHE_STACK_INFO(); + value2Ptr = TclSetIndexedScalar(interp, index, valuePtr, + /*leaveErrorMsg*/ 1); + CACHE_STACK_INFO(); + if (value2Ptr == NULL) { + result = TCL_ERROR; + pc--; /* to get back within the scope of the cmd */ + goto checkForCatch; /* will decrRefCt valuePtr at TOS */ + } else if (valuePtr != value2Ptr) { + Tcl_DecrRefCount(valuePtr); + SET_TOS(value2Ptr); + } + } + /* REMARK: on return, TOS has correct value AND refcount!*/ + NEXT_INSTR; + } + } + + _CASE(INST_STORE_ARRAY_STK): /* tosPtr -= 2 */ + { + Tcl_Obj *valuePtr; + Tcl_Obj *elemPtr; + + valuePtr = POP_OBJECT(); + elemPtr = POP_OBJECT(); + TclDecrRefCount_Q(elemPtr); + goto doStoreStk; + + _CASE(INST_STORE_STK): + _CASE(INST_STORE_SCALAR_STK): + valuePtr = POP_OBJECT(); + elemPtr = NULL; + + doStoreStk: + { + Tcl_Obj *objPtr = TOS; + Tcl_Obj *value2Ptr; + DECACHE_STACK_INFO(); + value2Ptr = Tcl_ObjSetVar2(interp, objPtr, elemPtr, valuePtr, + TCL_LEAVE_ERR_MSG); + CACHE_STACK_INFO(); + if (value2Ptr == NULL) { + Tcl_DecrRefCount_Q(valuePtr); + result = TCL_ERROR; + goto checkForCatch; /* will decrRefCt objPtr at TOS */ + } else if (valuePtr != value2Ptr) { + Tcl_DecrRefCount_Q(valuePtr); + Tcl_IncrRefCount(value2Ptr); + } + TclDecrRefCount_Q(objPtr); + TOS = value2Ptr; + } + pc++; + NEXT_INSTR_Q; + } + + _CASE(INST_STORE_ARRAY4): /* tosPtr += 1 */ + { + int index; + pc++; + index = TclGetUInt4AtPtr(pc); + pc += 4; + goto doStoreArray; + + _CASE(INST_STORE_ARRAY1): /* tosPtr += 1 */ + pc++; + index = TclGetUInt1AtPtr(pc); + pc++; + + doStoreArray: + { + Tcl_Obj *valuePtr = POP_OBJECT(); + Tcl_Obj *elemPtr = TOS; + Tcl_Obj *value2Ptr; + + DECACHE_STACK_INFO(); + value2Ptr = TclSetElementOfIndexedArray(interp, index, + elemPtr, valuePtr, TCL_LEAVE_ERR_MSG); + CACHE_STACK_INFO(); + if (value2Ptr == NULL) { + tosPtr++; /* let checkForCatch decrRefCt elemPtr and valuePtr */ + result = TCL_ERROR; + pc--; /* to get back within the scope of the cmd */ + goto checkForCatch; + } else if (value2Ptr != valuePtr) { + Tcl_DecrRefCount(valuePtr); + Tcl_IncrRefCount(value2Ptr); + } + TclDecrRefCount(elemPtr); + TOS = value2Ptr; + NEXT_INSTR; + } + } + + _CASE(INST_INCR_SCALAR1): /* tosPtr += 0 */ + { + long i; + int index; + { + Tcl_Obj *valuePtr = POP_OBJECT(); + + if (valuePtr->typePtr != &tclIntType) { + result = tclIntType.setFromAnyProc(interp, valuePtr); + if (result != TCL_OK) { + tosPtr++; /* it will decrRefCt valuePtr */ + goto checkForCatch; + } + } + i = valuePtr->internalRep.longValue; + TclDecrRefCount(valuePtr); + } + pc++; + index = TclGetUInt1AtPtr(pc); + goto doIncrScalar; + + _CASE(INST_INCR_SCALAR1_IMM): /* tosPtr += 1 */ + pc++; + index = TclGetUInt1AtPtr(pc); + pc++; + i = TclGetInt1AtPtr(pc); + + doIncrScalar: + { + Tcl_Obj *valuePtr; + /* INLINING from TclIncrIndexedScalar */ + Var *compiledLocals = iPtr->varFramePtr->compiledLocals; + Var *varPtr = &(compiledLocals[index]); + while (TclIsVarLink(varPtr)) varPtr = varPtr->value.linkPtr; + + if ((varPtr->tracePtr == NULL) && TclIsVarScalarDefined(varPtr) + && !((varPtr->flags & VAR_IN_HASHTABLE) && (varPtr->hPtr == NULL))) { + long currVal; + valuePtr = varPtr->value.objPtr; + result = Tcl_GetLongFromObj(interp, valuePtr, &currVal); + if (result != TCL_OK) goto doIncrScalarErrExit; + if (Tcl_IsShared(valuePtr)) { + (valuePtr->refCount)--; + valuePtr = Tcl_NewLongObj (i + currVal); + Tcl_IncrRefCount(valuePtr); + } else { + valuePtr->internalRep.longValue = i + currVal; + Tcl_InvalidateStringRep(valuePtr); + } + varPtr->value.objPtr = valuePtr; + } else { + /* original implementation */ + DECACHE_STACK_INFO(); + valuePtr = TclIncrIndexedScalar(interp, index, i); + CACHE_STACK_INFO(); + if (valuePtr == NULL) goto doIncrScalarErrExit; + } + PUSH_OBJECT(valuePtr); + pc++; + NEXT_INSTR; + + doIncrScalarErrExit: + result = TCL_ERROR; + goto checkForCatch; + } + } + + _CASE(INST_INCR_ARRAY_STK): /* tosPtr -= 2 */ + { + Tcl_Obj *elemPtr; + long i; + { + Tcl_Obj *valuePtr; + + valuePtr = POP_OBJECT(); + elemPtr = POP_OBJECT(); + TclDecrRefCount_Q(elemPtr); + + goto doIncrStk; + + _CASE(INST_INCR_SCALAR_STK): /* tosPtr -= 1 */ + _CASE(INST_INCR_STK): + valuePtr = POP_OBJECT(); + elemPtr = NULL; + + doIncrStk: + TclDecrRefCount_Q(valuePtr); + if (valuePtr->typePtr != &tclIntType) { + result = tclIntType.setFromAnyProc(interp, valuePtr); + if (result != TCL_OK) { + goto checkForCatch; + } + } + i = valuePtr->internalRep.longValue; + } + goto doIncrStkImm; + + _CASE(INST_INCR_ARRAY_STK_IMM): /* tosPtr -= 1 */ + elemPtr = POP_OBJECT(); + TclDecrRefCount_Q(elemPtr); + pc++; + i = TclGetInt1AtPtr(pc); + goto doIncrStkImm; + + _CASE(INST_INCR_SCALAR_STK_IMM): /* tosPtr += 0 */ + _CASE(INST_INCR_STK_IMM): + elemPtr = NULL; + pc++; + i = TclGetInt1AtPtr(pc); + + doIncrStkImm: + { + Tcl_Obj *value2Ptr; + Tcl_Obj *objPtr = TOS; /* variable or array name */ + DECACHE_STACK_INFO(); + value2Ptr = TclIncrVar2(interp, objPtr, elemPtr, i, + TCL_LEAVE_ERR_MSG); + CACHE_STACK_INFO(); + if (value2Ptr == NULL) { + result = TCL_ERROR; + goto checkForCatch; /* will decrRefCt objPtr = TOS */ + } + TclDecrRefCount_Q(objPtr); + SET_TOS(value2Ptr); + pc++; + NEXT_INSTR_Q; + } + } + + + _CASE(INST_INCR_ARRAY1): /* tosPtr -= 1 */ + { + long i; + int index; + + { + Tcl_Obj *valuePtr = POP_OBJECT(); + + if (valuePtr->typePtr != &tclIntType) { + result = tclIntType.setFromAnyProc(interp, valuePtr); + if (result != TCL_OK) { + tosPtr++; /* will decrRefCount valuePtr */ + goto checkForCatch; + } + } + i = valuePtr->internalRep.longValue; + TclDecrRefCount(valuePtr); + } + pc++; + index = TclGetUInt1AtPtr(pc); + goto doIncrArray1; + + _CASE(INST_INCR_ARRAY1_IMM): /* tosPtr += 0 */ + pc++; + index = TclGetUInt1AtPtr(pc); + pc++; + i = TclGetInt1AtPtr(pc); + + doIncrArray1: + { + Tcl_Obj *elemPtr = TOS; + Tcl_Obj *value2Ptr; + DECACHE_STACK_INFO(); + value2Ptr = TclIncrElementOfIndexedArray(interp, + index, elemPtr, i); + CACHE_STACK_INFO(); + if (value2Ptr == NULL) { + result = TCL_ERROR; + goto checkForCatch; /* will decrRefCt elemPtr = TOS */ + } + TclDecrRefCount(elemPtr); + SET_TOS(value2Ptr); + pc++; + NEXT_INSTR; + } + } + + _CASE(INST_JUMP1): /* tosPtr += 0 */ + { + pc += TclGetInt1AtPtr(pc+1); + NEXT_INSTR; + } + + _CASE(INST_JUMP4): /* tosPtr += 0 */ + { + pc += TclGetInt4AtPtr(pc+1); + NEXT_INSTR; + } + + _CASE(INST_JUMP_FALSE4): /* tosPtr += 0 */ + { + /* + * adj0 is the pcAdjustment for "false" + * adj1 is the pcAdjustment for "true" + */ + int adj0 = TclGetInt4AtPtr(pc+1); + int adj1 = 5; + goto doJumpTrue; + + _CASE(INST_JUMP_FALSE1): + adj0 = TclGetInt1AtPtr(pc+1); + adj1 = 2; + goto doJumpTrue; + + _CASE(INST_JUMP_TRUE4): + adj1 = TclGetInt4AtPtr(pc+1); + adj0 = 5; + goto doJumpTrue; + + _CASE(INST_JUMP_TRUE1): + adj1 = TclGetInt1AtPtr(pc+1); + adj0 = 2; + + doJumpTrue: + { + Tcl_Obj *valuePtr = POP_OBJECT(); + Tcl_ObjType *typePtr = valuePtr->typePtr; + int truth; + + if (typePtr == &tclIntType) { + truth = valuePtr->internalRep.longValue; + } else if (typePtr == &tclDoubleType) { + truth = (valuePtr->internalRep.doubleValue != 0.0); + } else { + result = Tcl_GetBooleanFromObj(interp, valuePtr, &truth); + if (result != TCL_OK) { + tosPtr++; /* to decrRefCt valuePtr */ + goto checkForCatch; + } + } + TclDecrRefCount(valuePtr); + pc += (truth ? adj1 : adj0); + NEXT_INSTR; + } + } + + + _CASE(INST_LOR): /* tosPtr -= 1 */ + _CASE(INST_LAND): + { + /* + * Operands must be boolean or numeric. No int->double + * conversions are performed. + */ + + int i1, i2; + + { + Tcl_Obj *value2Ptr = POP_OBJECT(); + Tcl_ObjType *t2Ptr = value2Ptr->typePtr; + + if ((t2Ptr == &tclIntType) || (t2Ptr == &tclBooleanType)) { + i2 = (value2Ptr->internalRep.longValue != 0); + } else if (t2Ptr == &tclDoubleType) { + i2 = (value2Ptr->internalRep.doubleValue != 0.0); + } else { + if (TclLooksLikeInt(TclGetString(value2Ptr), value2Ptr->length)) { + long i; + result = Tcl_GetLongFromObj((Tcl_Interp *) NULL, + value2Ptr, &i); + i2 = (int) i; + } else { + int i; + result = Tcl_GetBooleanFromObj((Tcl_Interp *) NULL, + value2Ptr, &i); + i2 = i; + } + if (result != TCL_OK) { + tosPtr++; /* to decrRefCt value2Ptr */ + IllegalExprOperandType(interp, pc, value2Ptr); + goto checkForCatch; + } + } + TclDecrRefCount(value2Ptr); + } + + { + Tcl_Obj *valuePtr = TOS; + Tcl_ObjType *t1Ptr = valuePtr->typePtr; + if ((t1Ptr == &tclIntType) || (t1Ptr == &tclBooleanType)) { + i1 = (valuePtr->internalRep.longValue != 0); + } else if (t1Ptr == &tclDoubleType) { + i1 = (valuePtr->internalRep.doubleValue != 0.0); + } else { + if (TclLooksLikeInt(TclGetString(valuePtr), valuePtr->length)) { + long i; + result = Tcl_GetLongFromObj((Tcl_Interp *) NULL, + valuePtr, &i); + i1 = (int) i; + } else { + int i; + result = Tcl_GetBooleanFromObj((Tcl_Interp *) NULL, + valuePtr, &i); + i1 = i; + } + if (result != TCL_OK) { + IllegalExprOperandType(interp, pc, valuePtr); + goto checkForCatch; + } + } + } + + /* + * Reuse the valuePtr object already on stack if possible. + */ + { + int i = (*pc++ == INST_LOR) ? (i1 || i2) : (i1 && i2); + USE_OR_MAKE_THEN_SET(i, Long); + } + NEXT_INSTR; + } + + _CASE(INST_STR_EQ): /* tosPtr -= 1 */ + _CASE(INST_STR_NEQ): + { + /* + * String (in)equality check + */ + + Tcl_Obj *value2Ptr = POP_OBJECT(); + Tcl_Obj *valuePtr = TOS; + int iResult; + + if (valuePtr == value2Ptr) { + /* + * On the off-chance that the objects are the same, + * we don't really have to think hard about equality. + */ + iResult = (*pc == INST_STR_EQ); + } else { + char *str1 = TclGetString(valuePtr); + char *str2 = TclGetString(value2Ptr); + if (valuePtr->length == value2Ptr->length) { + /* + * We only need to check (in)equality when we have equal + * length strings. + */ + int tmp = (strcmp(str1, str2)); + iResult = ((*pc == INST_STR_NEQ) ? (tmp != 0) : (tmp == 0)); + } else { + iResult = (*pc == INST_STR_NEQ); + } + } + TclDecrRefCount(value2Ptr); + USE_OR_MAKE_THEN_SET(iResult,Int); + pc++; + NEXT_INSTR; + } + + _CASE(INST_STR_CMP): /* tosPtr -= 1 */ + { + /* + * String compare + */ + int s1len, s2len, iResult; + { + Tcl_Obj *value2Ptr = POP_OBJECT(); + Tcl_Obj *valuePtr = TOS; + char *s1, *s2; + + s1 = TclGetString(valuePtr); + s2 = TclGetString(value2Ptr); + + /* + * Compare up to the minimum byte length + */ + s1len = valuePtr->length; + s2len = value2Ptr->length; + iResult = memcmp(s1, s2, (size_t) ((s1len < s2len) ? s1len : s2len)); + TclDecrRefCount(value2Ptr); + } + if (iResult == 0) { + iResult = s1len - s2len; + } else if (iResult < 0) { + iResult = -1; + } else { + iResult = 1; + } + USE_OR_MAKE_THEN_SET(iResult,Int); + pc++; + NEXT_INSTR; + } + + _CASE(INST_STR_LEN): /* tosPtr += 0 */ + { + int length1; + Tcl_Obj *valuePtr = TOS; + + /* INLINING from Tcl_GetByteArrayFromObj (tclBinary.c) */ + length1 = ((valuePtr->typePtr == &tclByteArrayType) ? + (((ByteArray *) (valuePtr)->internalRep.otherValuePtr)->used) : + Tcl_GetCharLength(valuePtr)); + USE_OR_MAKE_THEN_SET(length1,Int); + pc++; + NEXT_INSTR; + } + + _CASE(INST_STR_INDEX): /* tosPtr -= 1 */ + { + Tcl_Obj *idxPtr = POP_OBJECT(); /* the index to look for */ + Tcl_Obj *stringPtr = TOS; /* the string object */ + Tcl_Obj *objPtr; + int index, length; + + /* + * If we have a ByteArray object, avoid indexing in the + * Utf string since the byte array contains one byte per + * character. Otherwise, use the Unicode string rep to + * get the index'th char. + */ + + if (stringPtr->typePtr == &tclByteArrayType) { + /* INLINING from Tcl_GetByteArrayFromObj (tclBinary.c) */ + unsigned char *bytes; + { + ByteArray *byteArr = (ByteArray *) (stringPtr)->internalRep.otherValuePtr; + bytes = byteArr->bytes; + length = byteArr->used; + } + + if (idxPtr->typePtr == &tclIntType) { + index = (int) idxPtr->internalRep.longValue; + } else { + result = TclGetIntForIndex(interp, idxPtr, length - 1, + &index); + if (result != TCL_OK) { + tosPtr++; /* to decrRefCt idxPtr */ + goto checkForCatch; + } + } + if ((index >= 0) && (index < length)) { + objPtr = Tcl_NewByteArrayObj(&bytes[index], 1); + } else { + objPtr = Tcl_NewObj(); + } + } else { + /* + * Get Unicode char length to calculate what 'end' means. + */ + length = Tcl_GetCharLength(stringPtr); + + result = TclGetIntForIndex(interp, idxPtr, length - 1, + &index); + if (result != TCL_OK) { + tosPtr++; /* to decrRefCt idxPtr */ + goto checkForCatch; + } + + if ((index >= 0) && (index < length)) { + char buf[TCL_UTF_MAX]; + Tcl_UniChar ch; + + ch = Tcl_GetUniChar(stringPtr, index); + length = Tcl_UniCharToUtf(ch, buf); + objPtr = Tcl_NewStringObj(buf, length); + } else { + objPtr = Tcl_NewObj(); + } + } + TclDecrRefCount(stringPtr); + TclDecrRefCount(idxPtr); + SET_TOS(objPtr); + pc++; + NEXT_INSTR; + } + } + + _CASE(INST_STR_MATCH): /* tosPtr -= 2 */ + { + int nocase; + int match; + Tcl_Obj *valuePtr = POP_OBJECT(); /* String */ + Tcl_Obj *value2Ptr = POP_OBJECT(); /* Pattern */ + Tcl_Obj *objPtr = TOS; /* Case Sensitivity */ + + Tcl_GetBooleanFromObj(interp, objPtr, &nocase); + match = Tcl_UniCharCaseMatch(Tcl_GetUnicode(valuePtr), + Tcl_GetUnicode(value2Ptr), nocase); + + TclDecrRefCount(valuePtr); + TclDecrRefCount(value2Ptr); + USE_OR_MAKE_THEN_SET(match,Int); + pc++; + NEXT_INSTR; + } + + _CASE(INST_EQ): /* tosPtr -= 1 */ + _CASE(INST_NEQ): + _CASE(INST_LT): + _CASE(INST_GT): + _CASE(INST_LE): + _CASE(INST_GE): + { + /* + * Any type is allowed but the two operands must have the + * same type. We will compute value op value2. + */ + + long iResult = 0; /* Init. avoids compiler warning. */ + union AuxVar A, B; + Tcl_Obj *valueBPtr = POP_OBJECT(); + Tcl_Obj *valueAPtr = TOS; + Tcl_ObjType *tAPtr = valueAPtr->typePtr; + Tcl_ObjType *tBPtr = valueBPtr->typePtr; + + /* + * We only want to coerce numeric validation if + * neither type is NULL. A NULL type means the arg is + * essentially an empty object ("", {} or [list]). + */ + + if (!((((tAPtr == NULL) && (valueAPtr->bytes == NULL)) + || (valueAPtr->bytes && (valueAPtr->length == 0))) + || (((tBPtr == NULL) && (valueBPtr->bytes == NULL)) + || (valueBPtr->bytes && (valueBPtr->length == 0))))) { + TRY_CONVERT_TO_NUM(valueAPtr,A,tAPtr); + TRY_CONVERT_TO_NUM(valueBPtr,B,tBPtr); + } + + + if ((tAPtr == &tclIntType) && (tBPtr == &tclIntType)) { + /* Compare as ints. */ + switch (*pc) { + case INST_EQ: + iResult = (A.i == B.i); + break; + case INST_NEQ: + iResult = (A.i != B.i); + break; + case INST_LT: + iResult = (A.i < B.i); + break; + case INST_GT: + iResult = (A.i > B.i); + break; + case INST_LE: + iResult = (A.i <= B.i); + break; + case INST_GE: + iResult = (A.i >= B.i); + break; + } + } else if ((tAPtr == &tclDoubleType) && (tBPtr == &tclIntType)) { + B.d = (double) B.i; + goto compare_as_doubles; + /* UGLY, but effective ... */ + } else if ((tAPtr == &tclIntType) && (tBPtr == &tclDoubleType)) { + A.d = (double) A.i; + goto compare_as_doubles; + /* UGLY, but effective ... */ + } else if ((tAPtr == &tclDoubleType) && (tBPtr == &tclDoubleType)) { + compare_as_doubles: + switch (*pc) { + case INST_EQ: + iResult = (A.d == B.d); + break; + case INST_NEQ: + iResult = (A.d != B.d); + break; + case INST_LT: + iResult = (A.d < B.d); + break; + case INST_GT: + iResult = (A.d > B.d); + break; + case INST_LE: + iResult = (A.d <= B.d); + break; + case INST_GE: + iResult = (A.d >= B.d); + break; + } + } else { + /* One operand is not numeric. Compare as strings. */ + int cmpValue; + cmpValue = strcmp(TclGetString(valueAPtr), TclGetString(valueBPtr)); + switch (*pc) { + case INST_EQ: + iResult = (cmpValue == 0); + break; + case INST_NEQ: + iResult = (cmpValue != 0); + break; + case INST_LT: + iResult = (cmpValue < 0); + break; + case INST_GT: + iResult = (cmpValue > 0); + break; + case INST_LE: + iResult = (cmpValue <= 0); + break; + case INST_GE: + iResult = (cmpValue >= 0); + break; + } + } + + /* + * Reuse the valuePtr object already on stack if possible. + */ + + TclDecrRefCount(valueBPtr); + USE_OR_MAKE_THEN_SET(iResult,Long); + pc++; + NEXT_INSTR; + } + + _CASE(INST_MOD): /* tosPtr -= 1 */ + _CASE(INST_LSHIFT): + _CASE(INST_RSHIFT): + _CASE(INST_BITOR): + _CASE(INST_BITXOR): + _CASE(INST_BITAND): + { + /* + * Only integers are allowed. We compute value op value2. + */ + + long i1, i2; + { + Tcl_Obj *value2Ptr = POP_OBJECT(); + if (value2Ptr->typePtr == &tclIntType) { + i2 = value2Ptr->internalRep.longValue; + } else { + long i; + result = Tcl_GetLongFromObj((Tcl_Interp *) NULL, + value2Ptr, &i); + if (result != TCL_OK) { + IllegalExprOperandType(interp, pc, value2Ptr); + tosPtr++; /* it will decrRefCt value2Ptr */ + goto checkForCatch; + } else { + i2 = i; + } + } + TclDecrRefCount(value2Ptr); + } + { + Tcl_Obj *valuePtr = TOS; + if (valuePtr->typePtr == &tclIntType) { + i1 = valuePtr->internalRep.longValue; + } else { /* try to convert to int */ + long i; + result = Tcl_GetLongFromObj((Tcl_Interp *) NULL, + valuePtr, &i); + if (result != TCL_OK) { + IllegalExprOperandType(interp, pc, valuePtr); + goto checkForCatch; + } else { + i1 = i; + } + } + } + { + long iResult = 0; /* Init. avoids compiler warning. */ + switch (*pc) { + case INST_MOD: + /* + * This code is tricky: C doesn't guarantee much about + * the quotient or remainder, but Tcl does. The + * remainder always has the same sign as the divisor and + * a smaller absolute value. + */ + if (i2 == 0) { + goto divideByZero; + } + if (i2 < 0) { + iResult = i1 % (-i2); + if (iResult > 0) { + iResult += i2; + } + } else { + iResult = i1 % i2; + if (iResult < 0) { + iResult += i2; + } + } + break; + case INST_LSHIFT: + iResult = i1 << i2; + break; + case INST_RSHIFT: + /* + * The following code is a bit tricky: it ensures that + * right shifts propagate the sign bit even on machines + * where ">>" won't do it by default. + */ + if (i1 < 0) { + iResult = ~((~i1) >> i2); + } else { + iResult = i1 >> i2; + } + break; + case INST_BITOR: + iResult = i1 | i2; + break; + case INST_BITXOR: + iResult = i1 ^ i2; + break; + case INST_BITAND: + iResult = i1 & i2; + break; + } + + /* + * Reuse the valuePtr object already on stack if possible. + */ + + USE_OR_MAKE_THEN_SET(iResult,Long); + pc++; + NEXT_INSTR; + } + } + + _CASE(INST_ADD): /* tosPtr -= 1 */ + _CASE(INST_SUB): + _CASE(INST_MULT): + _CASE(INST_DIV): + { + /* + * Operands must be numeric and ints get converted to floats + * if necessary. We compute value op value2. + */ + + Tcl_ObjType *tAPtr, *tBPtr; + union AuxVar A, B; + { + Tcl_Obj *valueBPtr = POP_OBJECT(); + tBPtr = valueBPtr->typePtr; + TRY_CONVERT_TO_NUM(valueBPtr,B,tBPtr); + TclDecrRefCount_Q(valueBPtr); + } + { + Tcl_Obj *valueAPtr = *tosPtr; + tAPtr = valueAPtr->typePtr; + TRY_CONVERT_TO_NUM(valueAPtr,A,tAPtr); + } + { + union AuxVar R; + + if ((tAPtr == &tclIntType) && (tBPtr == &tclIntType)) { + /* Do integer arithmetic. */ + switch (*pc++) { + case INST_ADD: + R.i = A.i + B.i; + break; + case INST_SUB: + R.i = A.i - B.i; + break; + case INST_MULT: + R.i = A.i * B.i; + break; + case INST_DIV: + /* + * This code is tricky: C doesn't guarantee much + * about the quotient or remainder, but Tcl does. + * The remainder always has the same sign as the + * divisor and a smaller absolute value. + */ + if (B.i == 0) { + goto divideByZero; + } + if (B.i < 0) { + A.i = -A.i; + B.i = -B.i; + } + R.i = A.i / B.i; + if (A.i % B.i < 0) { + R.i -= 1; + } + break; + } + /* Reuse the valuePtr object already on stack if possible. */ + USE_OR_MAKE_THEN_SET(R.i,Long); + NEXT_INSTR_Q; + } else if ((tAPtr == &tclDoubleType) && (tBPtr == &tclIntType)) { + B.d = (double) B.i; /* promote value B to double */ + goto do_double_arithmetic; + /* UGLY, but effective ... */ + } else if ((tAPtr == &tclIntType) && (tBPtr == &tclDoubleType)) { + A.d = (double) A.i; /* promote value A to double */ + goto do_double_arithmetic; + /* UGLY, but effective ... */ + } else if ((tAPtr == &tclDoubleType) && (tBPtr == &tclDoubleType)) { + do_double_arithmetic: + switch (*pc++) { + case INST_ADD: + R.d = A.d + B.d; + break; + case INST_SUB: + R.d = A.d - B.d; + break; + case INST_MULT: + R.d = A.d * B.d; + break; + case INST_DIV: + if (B.d == 0.0) { + goto divideByZero; + } + R.d = A.d / B.d; + break; + } + + /* + * Check now for IEEE floating-point error. + */ + + if (IS_NAN(R.d) || IS_INF(R.d)) { + TclExprFloatError(interp, R.d); + result = TCL_ERROR; + goto checkForCatch; + } + /* Reuse the valuePtr object already on stack if possible. */ + USE_OR_MAKE_THEN_SET(R.d,Double); + NEXT_INSTR_Q; + } else { + /* + * at least one operand is not numeric: ERROR + */ + + if ((tAPtr != &tclIntType) && (tAPtr != &tclDoubleType)) { + IllegalExprOperandType(interp, pc, *tosPtr); + } else { + /* THIS is why we need to queue the decrRefCts! */ + IllegalExprOperandType(interp, pc, *(tosPtr+1)); + } + result = TCL_ERROR; + goto checkForCatch; + } + } + } + + _CASE(INST_UPLUS): /* tosPtr += 0 */ + { + /* + * Operand must be numeric. + */ + + Tcl_Obj *valuePtr = TOS; + Tcl_ObjType *tPtr = valuePtr->typePtr; + union AuxVar A; + + if (valuePtr->bytes != NULL) { + TRY_CONVERT_TO_NUM(valuePtr,A,tPtr); + } + + /* + * Ensure that the operand's string rep is the same as the + * formatted version of its internal rep. This makes sure + * that "expr +000123" yields "83", not "000123". We + * implement this by _discarding_ the string rep since we + * know it will be regenerated, if needed later, by + * formatting the internal rep's value. + */ + + if (Tcl_IsShared(valuePtr)) { + /* If it is shared, just decrease the refCount ... */ + valuePtr->refCount--; + if (tPtr == &tclIntType) { + SET_TOS(Tcl_NewLongObj(valuePtr->internalRep.longValue)); + } else if (tPtr == &tclDoubleType) { + SET_TOS(Tcl_NewDoubleObj(valuePtr->internalRep.doubleValue)); + } else { + IllegalExprOperandType(interp, pc, valuePtr); + result = TCL_ERROR; + tosPtr--; /* avoid second decrRefCt */ + goto checkForCatch; + } + } else { + if ((tPtr == &tclIntType) || (tPtr == &tclDoubleType)) { + Tcl_InvalidateStringRep(valuePtr); + } else { + IllegalExprOperandType(interp, pc, valuePtr); + result = TCL_ERROR; + goto checkForCatch; + } + } + pc++; + NEXT_INSTR; + } + + _CASE(INST_UMINUS): /* tosPtr += 0 */ + _CASE(INST_LNOT): + { + /* + * The operand must be numeric or a boolean string as + * accepted by Tcl_GetBooleanFromObj(). If the operand + * object is unshared modify it directly, otherwise + * create a copy to modify: this is "copy on write". + * Free any old string representation since it is now + * invalid. + */ + + Tcl_Obj *valuePtr = TOS; + Tcl_ObjType *tPtr = valuePtr->typePtr; + union AuxVar X; + + if ((tPtr == &tclBooleanType) && (valuePtr->bytes == NULL)) { + valuePtr->typePtr = &tclIntType; + } + TRY_CONVERT_TO_NUM(valuePtr,X,tPtr); + + if (tPtr == &tclIntType) { + USE_OR_MAKE_THEN_SET( + ((*pc++ == INST_UMINUS) ? -X.i : !X.i), Long); + NEXT_INSTR; + } else if (tPtr == &tclDoubleType) { + if (*pc++ == INST_UMINUS) { + USE_OR_MAKE_THEN_SET(-X.d,Double); + } else { + /* + * Should be able to use "!d", but apparently + * some compilers can't handle it. + */ + USE_OR_MAKE_THEN_SET(((X.d==0.0)? 1 : 0), Long); + } + NEXT_INSTR; + } else if (*pc == INST_LNOT) { + int boolvar; + result = Tcl_GetBooleanFromObj((Tcl_Interp *)NULL, + valuePtr, &boolvar); + if (result == TCL_OK) { + pc++; + X.i = (long) boolvar; /* i is long, not int! */ + USE_OR_MAKE_THEN_SET(!X.i, Long); + NEXT_INSTR; + } + } + /* + * Only got here if operation not applicable + */ + IllegalExprOperandType(interp, pc, valuePtr); + result = TCL_ERROR; + goto checkForCatch; /* this will decrrefCt valuePtr at TOS */ + } + + _CASE(INST_BITNOT): /* tosPtr += 0 */ + { + /* + * The operand must be an integer. If the operand object is + * unshared modify it directly, otherwise modify a copy. + * Free any old string representation since it is now + * invalid. + */ + + Tcl_Obj *valuePtr = TOS; + long i; + + if (valuePtr->typePtr == &tclIntType) { + i = valuePtr->internalRep.longValue; + } else { + long ii; + result = Tcl_GetLongFromObj((Tcl_Interp *) NULL, + valuePtr, &ii); + if (result != TCL_OK) { /* try to convert to double */ + IllegalExprOperandType(interp, pc, valuePtr); + goto checkForCatch; /* this will decrrefCt valuePtr at TOS */ + } else { + i = ii; + } + } + USE_OR_MAKE_THEN_SET(~i, Long); + pc++; + NEXT_INSTR; + } + + _CASE(INST_CALL_BUILTIN_FUNC1): /* tosPtr += 0 */ + { + /* + * Call one of the built-in Tcl math functions. + */ + + int opnd; + BuiltinFunc *mathFuncPtr; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + pc++; + opnd = TclGetUInt1AtPtr(pc); + if ((opnd < 0) || (opnd > LAST_BUILTIN_FUNC)) { + panic("TclExecuteByteCode: unrecognized builtin function code %d", opnd); + } + mathFuncPtr = &(builtinFuncTable[opnd]); + DECACHE_STACK_INFO(); + tsdPtr->mathInProgress++; + result = (*mathFuncPtr->proc)(interp, eePtr, + mathFuncPtr->clientData); + tsdPtr->mathInProgress--; + CACHE_STACK_INFO(); + if (result != TCL_OK) { + goto checkForCatch; + } + pc++; + NEXT_INSTR; + } + + _CASE(INST_CALL_FUNC1): /* tosPtr += 0 */ + { + /* + * Call a non-builtin Tcl math function previously + * registered by a call to Tcl_CreateMathFunc. + */ + + int objc; /* Number of arguments. The function name + * is the 0-th argument. */ + Tcl_Obj **objv; /* The array of arguments. The function + * name is objv[0]. */ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + pc++; + objc = TclGetUInt1AtPtr(pc); + objv = tosPtr - (objc-1); /* "objv[0]" */ + DECACHE_STACK_INFO(); + tsdPtr->mathInProgress++; + result = ExprCallMathFunc(interp, eePtr, objc, objv); + tsdPtr->mathInProgress--; + CACHE_STACK_INFO(); + if (result != TCL_OK) { + goto checkForCatch; + } + pc++; + NEXT_INSTR; + } + + _CASE(INST_TRY_CVT_TO_NUMERIC): /* tosPtr += 0 */ + { + /* + * Try to convert the topmost stack object to an int or + * double object. This is done in order to support Tcl's + * policy of interpreting operands if at all possible as + * first integers, else floating-point numbers. + */ + + Tcl_Obj *valuePtr = TOS; + Tcl_ObjType *tPtr = valuePtr->typePtr; + union AuxVar X; + + if ((tPtr == &tclBooleanType) && (valuePtr->bytes == NULL)) { + valuePtr->typePtr = &tclIntType; + } + TRY_CONVERT_TO_NUM(valuePtr,X,tPtr); + + /* + * Ensure that the topmost stack object, if numeric, has a + * string rep the same as the formatted version of its + * internal rep. This is used, e.g., to make sure that "expr + * {0001}" yields "1", not "0001". We implement this by + * _discarding_ the string rep since we know it will be + * regenerated, if needed later, by formatting the internal + * rep's value. Also check if there has been an IEEE + * floating point error. + */ + + if (tPtr == &tclIntType) { + if (Tcl_IsShared(valuePtr)) { + if (valuePtr->bytes != NULL) { + /* + * We only need to make a copy of the object + * when it already had a string rep + */ + SET_TOS(Tcl_NewLongObj(X.i)); + /* If it is shared, just decrease the refCount ... */ + valuePtr->refCount--; + } + } else { + Tcl_InvalidateStringRep(valuePtr); + } + } else if (tPtr == &tclDoubleType) { + if (Tcl_IsShared(valuePtr)) { + if (valuePtr->bytes != NULL) { + /* + * We only need to make a copy of the object + * when it already had a string rep + */ + SET_TOS(Tcl_NewDoubleObj(X.d)); + /* If it is shared, just decrease the refCount ... */ + valuePtr->refCount--; + } + } else { + Tcl_InvalidateStringRep(valuePtr); + } + + if (IS_NAN(X.d) || IS_INF(X.d)) { + TclExprFloatError(interp, X.d); + result = TCL_ERROR; + goto checkForCatch; /* this will decrRefCt valuePtr at TOS */ + } + } + pc++; + NEXT_INSTR; + } + + _CASE(INST_BREAK): /* tosPtr += 0 */ + { + /* + * First reset the interpreter's result. Then find the closest + * enclosing loop or catch exception range, if any. If a loop is + * found, terminate its execution. If the closest is a catch + * exception range, jump to its catchOffset. If no enclosing + * range is found, stop execution and return TCL_BREAK. + */ + ExceptionRange *rangePtr; + Tcl_ResetResult(interp); + rangePtr = GetExceptRangeForPc(pc, /*catchOnly*/ 0, codePtr); + if (rangePtr == NULL) { + result = TCL_BREAK; + goto abnormalReturn; /* no catch exists to check */ + } + switch (rangePtr->type) { + case LOOP_EXCEPTION_RANGE: + result = TCL_OK; + pc = (codePtr->codeStart + rangePtr->breakOffset); + NEXT_INSTR; /* restart outer instruction loop at pc */ + case CATCH_EXCEPTION_RANGE: + result = TCL_BREAK; + pc = (codePtr->codeStart + rangePtr->catchOffset); + goto processCatch; /* (it will use rangePtr) NOT ANYMORE */ + default: + panic("TclExecuteByteCode: unrecognized ExceptionRange type %d\n", rangePtr->type); + } + } + + _CASE(INST_CONTINUE): /* tosPtr += 0 */ + { + /* + * Find the closest enclosing loop or catch exception range, + * if any. If a loop is found, skip to its next iteration. + * If the closest is a catch exception range, jump to its + * catchOffset. If no enclosing range is found, stop + * execution and return TCL_CONTINUE. + */ + + ExceptionRange *rangePtr; + Tcl_ResetResult(interp); + rangePtr = GetExceptRangeForPc(pc, /*catchOnly*/ 0, codePtr); + if (rangePtr == NULL) { + result = TCL_CONTINUE; + goto abnormalReturn; + } + switch (rangePtr->type) { + case LOOP_EXCEPTION_RANGE: + if (rangePtr->continueOffset == -1) { + goto checkForCatch; + } else { + result = TCL_OK; + pc = (codePtr->codeStart + rangePtr->continueOffset); + NEXT_INSTR; /* restart outer instruction loop at pc */ + } + case CATCH_EXCEPTION_RANGE: + result = TCL_CONTINUE; + pc = (codePtr->codeStart + rangePtr->catchOffset); + goto processCatch; /* (it will use rangePtr) NOT ANYMORE */ + default: + panic("TclExecuteByteCode: unrecognized ExceptionRange type %d\n", rangePtr->type); + } + } + + _CASE(INST_FOREACH_START4): /* tosPtr += 0 */ + { + /* + * Initialize the temporary local var that holds the count + * of the number of iterations of the loop body to -1. + */ + + Var *iterVarPtr ; + Tcl_Obj *oldValuePtr; + pc++; + { + int opnd = TclGetUInt4AtPtr(pc); + ForeachInfo *infoPtr = (ForeachInfo *) + codePtr->auxDataArrayPtr[opnd].clientData; + int iterTmpIndex = infoPtr->loopCtTemp; + Var *compiledLocals = iPtr->varFramePtr->compiledLocals; + iterVarPtr = &(compiledLocals[iterTmpIndex]); + oldValuePtr = iterVarPtr->value.objPtr; + } + pc += 4; + if (oldValuePtr == NULL) { + iterVarPtr->value.objPtr = Tcl_NewLongObj(-1); + Tcl_IncrRefCount(iterVarPtr->value.objPtr); + } else { + Tcl_SetLongObj(oldValuePtr, -1); + } + TclSetVarScalarDefined(iterVarPtr); + NEXT_INSTR; + } + + _CASE(INST_FOREACH_STEP4): /* tosPtr += 0 */ + { + /* + * "Step" a foreach loop (i.e., begin its next iteration) by + * assigning the next value list element to each loop var. + */ + ForeachInfo *infoPtr; + int numLists; + Var *compiledLocals = iPtr->varFramePtr->compiledLocals; + int iterNum; + pc++; + { + Var *iterVarPtr; + Tcl_Obj *valuePtr; + int opnd = TclGetUInt4AtPtr(pc); + pc += 4; + + infoPtr = (ForeachInfo *) + codePtr->auxDataArrayPtr[opnd].clientData; + iterVarPtr = &(compiledLocals[infoPtr->loopCtTemp]); + numLists = infoPtr->numLists; + + /* + * Increment the temp holding the loop iteration number. + */ + valuePtr = iterVarPtr->value.objPtr; + iterNum = (valuePtr->internalRep.longValue + 1); + Tcl_SetLongObj(valuePtr, iterNum); + } + /* + * Check whether all value lists are exhausted and we should + * stop the loop. + */ + + { + int listTmpIndex = infoPtr->firstValueTemp; + long i; + int doneLoop = 1; + for (i = 0; i < numLists; (listTmpIndex++, i++) ) { + Var *listVarPtr = &(compiledLocals[listTmpIndex]); + Tcl_Obj *listPtr = listVarPtr->value.objPtr; + int minLen = iterNum * ((infoPtr->varLists[i])->numVars); + int listLen; + result = Tcl_ListObjLength(interp, listPtr, &listLen); + if (result != TCL_OK) { + pc--; /* to get back within the scope of cmd */ + goto checkForCatch; + } + if (listLen > minLen) { + doneLoop = 0; + } + } + if (doneLoop) { + PUSH_OBJECT(Tcl_NewLongObj(0)); + NEXT_INSTR; + } + } + + /* + * If some var in some var list still has a remaining list + * element iterate one more time. Assign to var the next + * element from its value list. We already checked above + * that each list temp holds a valid list object. + */ + + { + int listTmpIndex = infoPtr->firstValueTemp; + long i; + for (i = 0; i < numLists; (listTmpIndex++, i++) ) { + int j; + ForeachVarList *varListPtr = infoPtr->varLists[i]; + int numVars = varListPtr->numVars; + int valIndex = (iterNum * numVars); + Var *listVarPtr = &(compiledLocals[listTmpIndex]); + Tcl_Obj *listPtr = listVarPtr->value.objPtr; + List *listRepPtr = (List *) listPtr->internalRep.otherValuePtr; + int listLen = listRepPtr->elemCount; + + for (j = 0; j < numVars; (valIndex++, j++)) { + Tcl_Obj *valuePtr; + int setEmptyStr; + + if (valIndex >= listLen) { + setEmptyStr = 1; + valuePtr = Tcl_NewObj(); + } else { + setEmptyStr = 0; + valuePtr = listRepPtr->elements[valIndex]; + } + + /* varIndex = varListPtr->varIndexes[j]; */ + { + Tcl_Obj *value2Ptr; + DECACHE_STACK_INFO(); + value2Ptr = TclSetIndexedScalar(interp, + varListPtr->varIndexes[j], valuePtr, /*leaveErrorMsg*/ 1); + CACHE_STACK_INFO(); + if (value2Ptr == NULL) { + if (setEmptyStr) { + Tcl_DecrRefCount_Q(valuePtr); + } + result = TCL_ERROR; + pc--; /* to get back within the scope of cmd */ + goto checkForCatch; + } + } + } + } + } + + /* + * Push 1 if at least one value list had a remaining element + * and the loop should continue. Otherwise push 0. + */ + + PUSH_OBJECT(Tcl_NewLongObj(1)); + NEXT_INSTR; + } + + _CASE(INST_BEGIN_CATCH4): /* tosPtr += 0 */ + { + /* + * Record start of the catch command with exception range index + * equal to the operand. Push the current stack depth onto the + * special catch stack. + */ + *catchTopPtr++ = (tosPtr - eePtr->stackPtr); + pc += 5; + NEXT_INSTR; + } + + _CASE(INST_END_CATCH): /* tosPtr += 0 */ + { + catchTopPtr--; + result = TCL_OK; + pc++; + NEXT_INSTR; + } + + _CASE(INST_PUSH_RESULT): /* tosPtr += 1 */ + { + PUSH_OBJECT(Tcl_GetObjResult(interp)); + pc++; + NEXT_INSTR; + } + + _CASE(INST_PUSH_RETURN_CODE): /* tosPtr += 1 */ + { + PUSH_OBJECT(Tcl_NewLongObj(result)); + pc++; + NEXT_INSTR; + } + + /* end of switch on opCode */ + _CASE_END /* DO NOT PUT A SEMICOLON HERE, it can be empty ! */ + + + bad_return_from_invoke_or_eval: + { + /* + * Process the result of the Tcl_ObjCmdProc call. + * Used by INST_INVOKE and INST_EVAL + */ + ExceptionRange *rangePtr; + + int newPcOffset = 0; /* New inst offset for break, continue. */ + switch (result) { + case TCL_BREAK: + case TCL_CONTINUE: + /* + * The invoked command requested a break or continue. + * Find the closest enclosing loop or catch exception + * range, if any. If a loop is found, terminate its + * execution or skip to its next iteration. If the + * closest is a catch exception range, jump to its + * catchOffset. If no enclosing range is found, stop + * execution and return the TCL_BREAK or TCL_CONTINUE. + */ + rangePtr = GetExceptRangeForPc(pc, /*catchOnly*/ 0, + codePtr); + if (rangePtr == NULL) { + goto abnormalReturn; /* no catch exists to check */ + } + switch (rangePtr->type) { + case LOOP_EXCEPTION_RANGE: + if (result == TCL_BREAK) { + newPcOffset = rangePtr->breakOffset; + } else if (rangePtr->continueOffset == -1) { + newPcOffset = 0; /* lint ...*/ + goto checkForCatch; + } else { + newPcOffset = rangePtr->continueOffset; + } + result = TCL_OK; + pc = (codePtr->codeStart + newPcOffset); + NEXT_INSTR; /* restart outer instruction loop at pc */ + case CATCH_EXCEPTION_RANGE: + pc = (codePtr->codeStart + rangePtr->catchOffset); + goto processCatch; /* (it will use rangePtr) NOT ANYMORE */ + default: + panic("TclExecuteByteCode: bad ExceptionRange type\n"); + } + default: + /* handles TCL_ERROR, TCL_RETURN and unknown codes */ + goto checkForCatch; + } + } + + /* + * Division by zero in an expression. Control only reaches this + * point by "goto divideByZero". + */ + + divideByZero: + Tcl_ResetResult(interp); + Tcl_AppendToObj(Tcl_GetObjResult(interp), "divide by zero", -1); + Tcl_SetErrorCode(interp, "ARITH", "DIVZERO", "divide by zero", + (char *) NULL); + result = TCL_ERROR; + + /* + * Execution has generated an "exception" such as TCL_ERROR. If the + * exception is an error, record information about what was being + * executed when the error occurred. Find the closest enclosing + * catch range, if any. If no enclosing catch range is found, stop + * execution and return the "exception" code. + */ + + checkForCatch: + { + int length; + char *bytes; + ExceptionRange *rangePtr; + + if ((result == TCL_ERROR) && !(iPtr->flags & ERR_ALREADY_LOGGED)) { + bytes = GetSrcInfoForPc(pc, codePtr, &length); + if (bytes != NULL) { + Tcl_LogCommandInfo(interp, codePtr->source, bytes, length); + iPtr->flags |= ERR_ALREADY_LOGGED; + } + } + rangePtr = GetExceptRangeForPc(pc, /*catchOnly*/ 1, codePtr); + if (rangePtr == NULL) { + goto abnormalReturn; + } + /* this was previously done at processCatch ! */ + pc = (codePtr->codeStart + rangePtr->catchOffset); + } + + /* + * A catch exception range (rangePtr) was found to handle an + * "exception". It was found either by checkForCatch just above or + * by an instruction during break, continue, or error processing. + * Jump to its catchOffset after unwinding the operand stack to + * the depth it had when starting to execute the range's catch + * command. + */ + + processCatch: + { + Tcl_Obj **catchedTosPtr; + catchedTosPtr = eePtr->stackPtr + *(catchTopPtr-1); + while (tosPtr > catchedTosPtr) { + Tcl_Obj *valuePtr = POP_OBJECT(); + TclDecrRefCount(valuePtr); + } + /* This is now set before getting here + * pc = (codePtr->codeStart + rangePtr->catchOffset); + */ + NEXT_INSTR_Q; /* empty decrRef stack and restart execution loop at pc */ + } + +/* NO MORE INSTRUCTIONS CALLED AFTER HERE */ + + /* + * Abnormal return code. Restore the stack to state it had when starting + * to execute the ByteCode. + */ + + abnormalReturn: + DECR_REF_STACK_empty(); + { + Tcl_Obj **initTosPtr = eePtr->stackPtr + initTos; + for ( ; tosPtr > initTosPtr ; tosPtr--) { + TclDecrRefCount(TOS); + } + } + + /* + * Free the catch stack array if malloc'ed storage was used. + */ + + done: + if (catchStackPtr != catchStackStorage) { + ckfree((char *) catchStackPtr); + } + DECACHE_STACK_INFO(); + return result; +#undef STATIC_CATCH_STACK_SIZE +} + +#ifdef TCL_COMPILE_DEBUG +/* + *---------------------------------------------------------------------- + * + * PrintByteCodeInfo -- + * + * This procedure prints a summary about a bytecode object to stdout. + * It is called by TclExecuteByteCode when starting to execute the + * bytecode object if tclTraceExec has the value 2 or more. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static void +PrintByteCodeInfo(codePtr) + register ByteCode *codePtr; /* The bytecode whose summary is printed + * to stdout. */ +{ + Proc *procPtr = codePtr->procPtr; + Interp *iPtr = (Interp *) *codePtr->interpHandle; + + fprintf(stdout, "\nExecuting ByteCode 0x%x, refCt %u, epoch %u, interp 0x%x (epoch %u)\n", + (unsigned int) codePtr, codePtr->refCount, + codePtr->compileEpoch, (unsigned int) iPtr, + iPtr->compileEpoch); + + fprintf(stdout, " Source: "); + TclPrintSource(stdout, codePtr->source, 60); + + fprintf(stdout, "\n Cmds %d, src %d, inst %u, litObjs %u, aux %d, stkDepth %u, code/src %.2f\n", + codePtr->numCommands, codePtr->numSrcBytes, + codePtr->numCodeBytes, codePtr->numLitObjects, + codePtr->numAuxDataItems, codePtr->maxStackDepth, +#ifdef TCL_COMPILE_STATS + (codePtr->numSrcBytes? + ((float)codePtr->structureSize)/((float)codePtr->numSrcBytes) : 0.0)); +#else + 0.0); +#endif +#ifdef TCL_COMPILE_STATS + fprintf(stdout, " Code %d = header %d+inst %d+litObj %d+exc %d+aux %d+cmdMap %d\n", + codePtr->structureSize, + (sizeof(ByteCode) - (sizeof(size_t) + sizeof(Tcl_Time))), + codePtr->numCodeBytes, + (codePtr->numLitObjects * sizeof(Tcl_Obj *)), + (codePtr->numExceptRanges * sizeof(ExceptionRange)), + (codePtr->numAuxDataItems * sizeof(AuxData)), + codePtr->numCmdLocBytes); +#endif /* TCL_COMPILE_STATS */ + if (procPtr != NULL) { + fprintf(stdout, + " Proc 0x%x, refCt %d, args %d, compiled locals %d\n", + (unsigned int) procPtr, procPtr->refCount, + procPtr->numArgs, procPtr->numCompiledLocals); + } +} +#endif /* TCL_COMPILE_DEBUG */ + +/* + *---------------------------------------------------------------------- + * + * ValidatePcAndStackTop -- + * + * This procedure is called by TclExecuteByteCode when debugging to + * verify that the program counter and stack top are valid during + * execution. + * + * Results: + * None. + * + * Side effects: + * Prints a message to stderr and panics if either the pc or stack + * top are invalid. + * + *---------------------------------------------------------------------- + */ + +#ifdef TCL_COMPILE_DEBUG +static void +ValidatePcAndStackTop(codePtr, pc, stackTop, stackLowerBound, + stackUpperBound) + register ByteCode *codePtr; /* The bytecode whose summary is printed + * to stdout. */ + unsigned char *pc; /* Points to first byte of a bytecode + * instruction. The program counter. */ + int stackTop; /* Current stack top. Must be between + * stackLowerBound and stackUpperBound + * (inclusive). */ + int stackLowerBound; /* Smallest legal value for stackTop. */ + int stackUpperBound; /* Greatest legal value for stackTop. */ +{ + unsigned int relativePc = (unsigned int) (pc - codePtr->codeStart); + unsigned int codeStart = (unsigned int) codePtr->codeStart; + unsigned int codeEnd = (unsigned int) + (codePtr->codeStart + codePtr->numCodeBytes); + unsigned char opCode = *pc; + + if (((unsigned int) pc < codeStart) || ((unsigned int) pc > codeEnd)) { + fprintf(stderr, "\nBad instruction pc 0x%x in TclExecuteByteCode\n", + (unsigned int) pc); + panic("TclExecuteByteCode execution failure: bad pc"); + } + if ((unsigned int) opCode > LAST_INST_OPCODE) { + fprintf(stderr, "\nBad opcode %d at pc %u in TclExecuteByteCode\n", + (unsigned int) opCode, relativePc); + panic("TclExecuteByteCode execution failure: bad opcode"); + } + if ((stackTop < stackLowerBound) || (stackTop > stackUpperBound)) { + int numChars; + char *cmd = GetSrcInfoForPc(pc, codePtr, &numChars); + char *ellipsis = ""; + + fprintf(stderr, "\nBad stack top %d at pc %u in TclExecuteByteCode", + stackTop, relativePc); + if (cmd != NULL) { + if (numChars > 100) { + numChars = 100; + ellipsis = "..."; + } + fprintf(stderr, "\n executing %.*s%s\n", numChars, cmd, + ellipsis); + } else { + fprintf(stderr, "\n"); + } + panic("TclExecuteByteCode execution failure: bad stack top"); + } +} +#endif /* TCL_COMPILE_DEBUG */ + +/* + *---------------------------------------------------------------------- + * + * IllegalExprOperandType -- + * + * Used by TclExecuteByteCode to add an error message to errorInfo + * when an illegal operand type is detected by an expression + * instruction. The argument opndPtr holds the operand object in error. + * + * Results: + * None. + * + * Side effects: + * An error message is appended to errorInfo. + * + *---------------------------------------------------------------------- + */ + +static void +IllegalExprOperandType(interp, pc, opndPtr) + Tcl_Interp *interp; /* Interpreter to which error information + * pertains. */ + unsigned char *pc; /* Points to the instruction being executed + * when the illegal type was found. */ + Tcl_Obj *opndPtr; /* Points to the operand holding the value + * with the illegal type. */ +{ + unsigned char opCode = *pc; + + Tcl_ResetResult(interp); + if ((opndPtr->bytes == NULL) || (opndPtr->length == 0)) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "can't use empty string as operand of \"", + operatorStrings[opCode - INST_LOR], "\"", (char *) NULL); + } else { + char *msg = "non-numeric string"; + if (opndPtr->typePtr != &tclDoubleType) { + /* + * See if the operand can be interpreted as a double in order to + * improve the error message. + */ + + char *s = Tcl_GetString(opndPtr); + double d; + + if (Tcl_GetDouble((Tcl_Interp *) NULL, s, &d) == TCL_OK) { + /* + * Make sure that what appears to be a double + * (ie 08) isn't really a bad octal + */ + if (TclCheckBadOctal(NULL, Tcl_GetString(opndPtr))) { + msg = "invalid octal number"; + } else { + msg = "floating-point value"; + } + } + } + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "can't use ", + msg, " as operand of \"", operatorStrings[opCode - INST_LOR], + "\"", (char *) NULL); + } +} + +/* + *---------------------------------------------------------------------- + * + * CallTraceProcedure -- + * + * Invokes a trace procedure registered with an interpreter. These + * procedures trace command execution. Currently this trace procedure + * is called with the address of the string-based Tcl_CmdProc for the + * command, not the Tcl_ObjCmdProc. + * + * Results: + * None. + * + * Side effects: + * Those side effects made by the trace procedure. + * + *---------------------------------------------------------------------- + */ + +static void +CallTraceProcedure(interp, tracePtr, cmdPtr, command, numChars, objc, objv) + Tcl_Interp *interp; /* The current interpreter. */ + register Trace *tracePtr; /* Describes the trace procedure to call. */ + Command *cmdPtr; /* Points to command's Command struct. */ + char *command; /* Points to the first character of the + * command's source before substitutions. */ + int numChars; /* The number of characters in the + * command's source. */ + register int objc; /* Number of arguments for the command. */ + Tcl_Obj *objv[]; /* Pointers to Tcl_Obj of each argument. */ +{ + Interp *iPtr = (Interp *) interp; + register char **argv; + register int i; + int length; + char *p; + + /* + * Get the string rep from the objv argument objects and place their + * pointers in argv. First make sure argv is large enough to hold the + * objc args plus 1 extra word for the zero end-of-argv word. + */ + + argv = (char **) ckalloc((unsigned)(objc + 1) * sizeof(char *)); + for (i = 0; i < objc; i++) { + argv[i] = Tcl_GetStringFromObj(objv[i], &length); + } + argv[objc] = 0; + + /* + * Copy the command characters into a new string. + */ + + p = (char *) ckalloc((unsigned) (numChars + 1)); + memcpy((VOID *) p, (VOID *) command, (size_t) numChars); + p[numChars] = '\0'; + + /* + * Call the trace procedure then free allocated storage. + */ + + (*tracePtr->proc)(tracePtr->clientData, interp, iPtr->numLevels, + p, cmdPtr->proc, cmdPtr->clientData, objc, argv); + + ckfree((char *) argv); + ckfree((char *) p); +} + +/* + *---------------------------------------------------------------------- + * + * GetSrcInfoForPc -- + * + * Given a program counter value, finds the closest command in the + * bytecode code unit's CmdLocation array and returns information about + * that command's source: a pointer to its first byte and the number of + * characters. + * + * Results: + * If a command is found that encloses the program counter value, a + * pointer to the command's source is returned and the length of the + * source is stored at *lengthPtr. If multiple commands resulted in + * code at pc, information about the closest enclosing command is + * returned. If no matching command is found, NULL is returned and + * *lengthPtr is unchanged. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static char * +GetSrcInfoForPc(pc, codePtr, lengthPtr) + unsigned char *pc; /* The program counter value for which to + * return the closest command's source info. + * This points to a bytecode instruction + * in codePtr's code. */ + ByteCode *codePtr; /* The bytecode sequence in which to look + * up the command source for the pc. */ + int *lengthPtr; /* If non-NULL, the location where the + * length of the command's source should be + * stored. If NULL, no length is stored. */ +{ + register int pcOffset = (pc - codePtr->codeStart); + int numCmds = codePtr->numCommands; + unsigned char *codeDeltaNext, *codeLengthNext; + unsigned char *srcDeltaNext, *srcLengthNext; + int codeOffset, codeLen, codeEnd, srcOffset, srcLen, delta, i; + int bestDist = INT_MAX; /* Distance of pc to best cmd's start pc. */ + int bestSrcOffset = -1; /* Initialized to avoid compiler warning. */ + int bestSrcLength = -1; /* Initialized to avoid compiler warning. */ + + if ((pcOffset < 0) || (pcOffset >= codePtr->numCodeBytes)) { + return NULL; + } + + /* + * Decode the code and source offset and length for each command. The + * closest enclosing command is the last one whose code started before + * pcOffset. + */ + + codeDeltaNext = codePtr->codeDeltaStart; + codeLengthNext = codePtr->codeLengthStart; + srcDeltaNext = codePtr->srcDeltaStart; + srcLengthNext = codePtr->srcLengthStart; + codeOffset = srcOffset = 0; + for (i = 0; i < numCmds; i++) { + if ((unsigned int) (*codeDeltaNext) == (unsigned int) 0xFF) { + codeDeltaNext++; + delta = TclGetInt4AtPtr(codeDeltaNext); + codeDeltaNext += 4; + } else { + delta = TclGetInt1AtPtr(codeDeltaNext); + codeDeltaNext++; + } + codeOffset += delta; + + if ((unsigned int) (*codeLengthNext) == (unsigned int) 0xFF) { + codeLengthNext++; + codeLen = TclGetInt4AtPtr(codeLengthNext); + codeLengthNext += 4; + } else { + codeLen = TclGetInt1AtPtr(codeLengthNext); + codeLengthNext++; + } + codeEnd = (codeOffset + codeLen - 1); + + if ((unsigned int) (*srcDeltaNext) == (unsigned int) 0xFF) { + srcDeltaNext++; + delta = TclGetInt4AtPtr(srcDeltaNext); + srcDeltaNext += 4; + } else { + delta = TclGetInt1AtPtr(srcDeltaNext); + srcDeltaNext++; + } + srcOffset += delta; + + if ((unsigned int) (*srcLengthNext) == (unsigned int) 0xFF) { + srcLengthNext++; + srcLen = TclGetInt4AtPtr(srcLengthNext); + srcLengthNext += 4; + } else { + srcLen = TclGetInt1AtPtr(srcLengthNext); + srcLengthNext++; + } + + if (codeOffset > pcOffset) { /* best cmd already found */ + break; + } else if (pcOffset <= codeEnd) { /* this cmd's code encloses pc */ + int dist = (pcOffset - codeOffset); + if (dist <= bestDist) { + bestDist = dist; + bestSrcOffset = srcOffset; + bestSrcLength = srcLen; + } + } + } + + if (bestDist == INT_MAX) { + return NULL; + } + + if (lengthPtr != NULL) { + *lengthPtr = bestSrcLength; + } + return (codePtr->source + bestSrcOffset); +} + +/* + *---------------------------------------------------------------------- + * + * GetExceptRangeForPc -- + * + * Given a program counter value, return the closest enclosing + * ExceptionRange. + * + * Results: + * In the normal case, catchOnly is 0 (false) and this procedure + * returns a pointer to the most closely enclosing ExceptionRange + * structure regardless of whether it is a loop or catch exception + * range. This is appropriate when processing a TCL_BREAK or + * TCL_CONTINUE, which will be "handled" either by a loop exception + * range or a closer catch range. If catchOnly is nonzero, this + * procedure ignores loop exception ranges and returns a pointer to the + * closest catch range. If no matching ExceptionRange is found that + * encloses pc, a NULL is returned. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static ExceptionRange * +GetExceptRangeForPc(pc, catchOnly, codePtr) + unsigned char *pc; /* The program counter value for which to + * search for a closest enclosing exception + * range. This points to a bytecode + * instruction in codePtr's code. */ + int catchOnly; /* If 0, consider either loop or catch + * ExceptionRanges in search. If nonzero + * consider only catch ranges (and ignore + * any closer loop ranges). */ + ByteCode* codePtr; /* Points to the ByteCode in which to search + * for the enclosing ExceptionRange. */ +{ + ExceptionRange *rangeArrayPtr; + int numRanges = codePtr->numExceptRanges; + register ExceptionRange *rangePtr; + int pcOffset = (pc - codePtr->codeStart); + register int i, level; + + if (numRanges == 0) { + return NULL; + } + rangeArrayPtr = codePtr->exceptArrayPtr; + + for (level = codePtr->maxExceptDepth; level >= 0; level--) { + for (i = 0; i < numRanges; i++) { + rangePtr = &(rangeArrayPtr[i]); + if (rangePtr->nestingLevel == level) { + int start = rangePtr->codeOffset; + int end = (start + rangePtr->numCodeBytes); + if ((start <= pcOffset) && (pcOffset < end)) { + if ((!catchOnly) + || (rangePtr->type == CATCH_EXCEPTION_RANGE)) { + return rangePtr; + } + } + } + } + } + return NULL; +} + +/* + *---------------------------------------------------------------------- + * + * GetOpcodeName -- + * + * This procedure is called by the TRACE and TRACE_WITH_OBJ macros + * used in TclExecuteByteCode when debugging. It returns the name of + * the bytecode instruction at a specified instruction pc. + * + * Results: + * A character string for the instruction. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +#ifdef TCL_COMPILE_DEBUG +static char * +GetOpcodeName(pc) + unsigned char *pc; /* Points to the instruction whose name + * should be returned. */ +{ + unsigned char opCode = *pc; + + return instructionTable[opCode].name; +} +#endif /* TCL_COMPILE_DEBUG */ + +/* + *---------------------------------------------------------------------- + * + * VerifyExprObjType -- + * + * This procedure is called by the math functions to verify that + * the object is either an int or double, coercing it if necessary. + * If an error occurs during conversion, an error message is left + * in the interpreter's result unless "interp" is NULL. + * + * Results: + * TCL_OK if it was int or double, TCL_ERROR otherwise + * + * Side effects: + * objPtr is ensured to be either tclIntType of tclDoubleType. + * + *---------------------------------------------------------------------- + */ + +static int +VerifyExprObjType(interp, objPtr) + Tcl_Interp *interp; /* The interpreter in which to execute the + * function. */ + Tcl_Obj *objPtr; /* Points to the object to type check. */ +{ + if ((objPtr->typePtr == &tclIntType) || + (objPtr->typePtr == &tclDoubleType)) { + return TCL_OK; + } else { + int length, result = TCL_OK; + char *s = Tcl_GetStringFromObj(objPtr, &length); + + if (TclLooksLikeInt(s, length)) { + long i; + result = Tcl_GetLongFromObj((Tcl_Interp *) NULL, objPtr, &i); + } else { + double d; + result = Tcl_GetDoubleFromObj((Tcl_Interp *) NULL, objPtr, &d); + } + if ((result != TCL_OK) && (interp != NULL)) { + Tcl_ResetResult(interp); + if (TclCheckBadOctal((Tcl_Interp *) NULL, s)) { + Tcl_AppendToObj(Tcl_GetObjResult(interp), + "argument to math function was an invalid octal number", + -1); + } else { + Tcl_AppendToObj(Tcl_GetObjResult(interp), + "argument to math function didn't have numeric value", + -1); + } + } + return result; + } +} + +/* + *---------------------------------------------------------------------- + * + * Math Functions -- + * + * This page contains the procedures that implement all of the + * built-in math functions for expressions. + * + * Results: + * Each procedure returns TCL_OK if it succeeds and pushes an + * Tcl object holding the result. If it fails it returns TCL_ERROR + * and leaves an error message in the interpreter's result. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +ExprUnaryFunc(interp, eePtr, clientData) + Tcl_Interp *interp; /* The interpreter in which to execute the + * function. */ + ExecEnv *eePtr; /* Points to the environment for executing + * the function. */ + ClientData clientData; /* Contains the address of a procedure that + * takes one double argument and returns a + * double result. */ +{ + register Tcl_Obj **tosPtr; /* Cached top index of evaluation stack. */ + register Tcl_Obj *valuePtr; + double d, dResult; + int result; + + double (*func) _ANSI_ARGS_((double)) = + (double (*)_ANSI_ARGS_((double))) clientData; + + /* + * tosPtr from eePtr. + */ + + result = TCL_OK; + CACHE_STACK_INFO(); + + /* + * Pop the function's argument from the evaluation stack. Convert it + * to a double if necessary. + */ + + valuePtr = POP_OBJECT(); + + if (VerifyExprObjType(interp, valuePtr) != TCL_OK) { + result = TCL_ERROR; + goto done; + } + + if (valuePtr->typePtr == &tclIntType) { + d = (double) valuePtr->internalRep.longValue; + } else { + d = valuePtr->internalRep.doubleValue; + } + + errno = 0; + dResult = (*func)(d); + if ((errno != 0) || IS_NAN(dResult) || IS_INF(dResult)) { + TclExprFloatError(interp, dResult); + result = TCL_ERROR; + goto done; + } + + /* + * Push a Tcl object holding the result. + */ + + PUSH_OBJECT(Tcl_NewDoubleObj(dResult)); + + /* + * Reflect the change to tosPtr back in eePtr. + */ + + done: + TclDecrRefCount(valuePtr); + DECACHE_STACK_INFO(); + return result; +} + +static int +ExprBinaryFunc(interp, eePtr, clientData) + Tcl_Interp *interp; /* The interpreter in which to execute the + * function. */ + ExecEnv *eePtr; /* Points to the environment for executing + * the function. */ + ClientData clientData; /* Contains the address of a procedure that + * takes two double arguments and + * returns a double result. */ +{ + register Tcl_Obj **tosPtr; /* Cached top index of evaluation stack. */ + register Tcl_Obj *valuePtr, *value2Ptr; + double d1, d2, dResult; + int result; + + double (*func) _ANSI_ARGS_((double, double)) + = (double (*)_ANSI_ARGS_((double, double))) clientData; + + /* + * Set tosPtr from eePtr. + */ + + result = TCL_OK; + CACHE_STACK_INFO(); + + /* + * Pop the function's two arguments from the evaluation stack. Convert + * them to doubles if necessary. + */ + + value2Ptr = POP_OBJECT(); + valuePtr = POP_OBJECT(); + + if ((VerifyExprObjType(interp, valuePtr) != TCL_OK) || + (VerifyExprObjType(interp, value2Ptr) != TCL_OK)) { + result = TCL_ERROR; + goto done; + } + + if (valuePtr->typePtr == &tclIntType) { + d1 = (double) valuePtr->internalRep.longValue; + } else { + d1 = valuePtr->internalRep.doubleValue; + } + + if (value2Ptr->typePtr == &tclIntType) { + d2 = (double) value2Ptr->internalRep.longValue; + } else { + d2 = value2Ptr->internalRep.doubleValue; + } + + errno = 0; + dResult = (*func)(d1, d2); + if ((errno != 0) || IS_NAN(dResult) || IS_INF(dResult)) { + TclExprFloatError(interp, dResult); + result = TCL_ERROR; + goto done; + } + + /* + * Push a Tcl object holding the result. + */ + + PUSH_OBJECT(Tcl_NewDoubleObj(dResult)); + + /* + * Reflect the change to tosPtr back in eePtr. + */ + + done: + Tcl_DecrRefCount(valuePtr); + Tcl_DecrRefCount(value2Ptr); + DECACHE_STACK_INFO(); + return result; +} + +static int +ExprAbsFunc(interp, eePtr, clientData) + Tcl_Interp *interp; /* The interpreter in which to execute the + * function. */ + ExecEnv *eePtr; /* Points to the environment for executing + * the function. */ + ClientData clientData; /* Ignored. */ +{ + register Tcl_Obj **tosPtr; /* Cached top index of evaluation stack. */ + register Tcl_Obj *valuePtr; + long i, iResult; + double d, dResult; + int result; + + /* + * Set tosPtr from eePtr. + */ + + result = TCL_OK; + CACHE_STACK_INFO(); + + /* + * Pop the argument from the evaluation stack. + */ + + valuePtr = POP_OBJECT(); + + if (VerifyExprObjType(interp, valuePtr) != TCL_OK) { + result = TCL_ERROR; + goto done; + } + + /* + * Push a Tcl object with the result. + */ + if (valuePtr->typePtr == &tclIntType) { + i = valuePtr->internalRep.longValue; + if (i < 0) { + iResult = -i; + if (iResult < 0) { + Tcl_ResetResult(interp); + Tcl_AppendToObj(Tcl_GetObjResult(interp), + "integer value too large to represent", -1); + Tcl_SetErrorCode(interp, "ARITH", "IOVERFLOW", + "integer value too large to represent", (char *) NULL); + result = TCL_ERROR; + goto done; + } + } else { + iResult = i; + } + PUSH_OBJECT(Tcl_NewLongObj(iResult)); + } else { + d = valuePtr->internalRep.doubleValue; + if (d < 0.0) { + dResult = -d; + } else { + dResult = d; + } + if (IS_NAN(dResult) || IS_INF(dResult)) { + TclExprFloatError(interp, dResult); + result = TCL_ERROR; + goto done; + } + PUSH_OBJECT(Tcl_NewDoubleObj(dResult)); + } + + /* + * Reflect the change to tosPtr back in eePtr. + */ + + done: + Tcl_DecrRefCount(valuePtr); + DECACHE_STACK_INFO(); + return result; +} + +static int +ExprDoubleFunc(interp, eePtr, clientData) + Tcl_Interp *interp; /* The interpreter in which to execute the + * function. */ + ExecEnv *eePtr; /* Points to the environment for executing + * the function. */ + ClientData clientData; /* Ignored. */ +{ + register Tcl_Obj **tosPtr; /* Cached top index of evaluation stack. */ + register Tcl_Obj *valuePtr; + double dResult; + int result; + + /* + * Set tosPtr from eePtr. + */ + + result = TCL_OK; + CACHE_STACK_INFO(); + + /* + * Pop the argument from the evaluation stack. + */ + + valuePtr = POP_OBJECT(); + + if (VerifyExprObjType(interp, valuePtr) != TCL_OK) { + result = TCL_ERROR; + goto done; + } + + if (valuePtr->typePtr == &tclIntType) { + dResult = (double) valuePtr->internalRep.longValue; + } else { + dResult = valuePtr->internalRep.doubleValue; + } + + /* + * Push a Tcl object with the result. + */ + + PUSH_OBJECT(Tcl_NewDoubleObj(dResult)); + + /* + * Reflect the change to tosPtr back in eePtr. + */ + + done: + Tcl_DecrRefCount(valuePtr); + DECACHE_STACK_INFO(); + return result; +} + +static int +ExprIntFunc(interp, eePtr, clientData) + Tcl_Interp *interp; /* The interpreter in which to execute the + * function. */ + ExecEnv *eePtr; /* Points to the environment for executing + * the function. */ + ClientData clientData; /* Ignored. */ +{ + register Tcl_Obj **tosPtr; /* Cached top index of evaluation stack. */ + register Tcl_Obj *valuePtr; + long iResult; + double d; + int result; + + /* + * Set tosPtr from eePtr. + */ + + result = TCL_OK; + CACHE_STACK_INFO(); + + /* + * Pop the argument from the evaluation stack. + */ + + valuePtr = POP_OBJECT(); + + if (VerifyExprObjType(interp, valuePtr) != TCL_OK) { + result = TCL_ERROR; + goto done; + } + + if (valuePtr->typePtr == &tclIntType) { + iResult = valuePtr->internalRep.longValue; + } else { + d = valuePtr->internalRep.doubleValue; + if (d < 0.0) { + if (d < (double) (long) LONG_MIN) { + tooLarge: + Tcl_ResetResult(interp); + Tcl_AppendToObj(Tcl_GetObjResult(interp), + "integer value too large to represent", -1); + Tcl_SetErrorCode(interp, "ARITH", "IOVERFLOW", + "integer value too large to represent", (char *) NULL); + result = TCL_ERROR; + goto done; + } + } else { + if (d > (double) LONG_MAX) { + goto tooLarge; + } + } + if (IS_NAN(d) || IS_INF(d)) { + TclExprFloatError(interp, d); + result = TCL_ERROR; + goto done; + } + iResult = (long) d; + } + + /* + * Push a Tcl object with the result. + */ + + PUSH_OBJECT(Tcl_NewLongObj(iResult)); + + /* + * Reflect the change to tosPtr back in eePtr. + */ + + done: + Tcl_DecrRefCount(valuePtr); + DECACHE_STACK_INFO(); + return result; +} + +static int +ExprRandFunc(interp, eePtr, clientData) + Tcl_Interp *interp; /* The interpreter in which to execute the + * function. */ + ExecEnv *eePtr; /* Points to the environment for executing + * the function. */ + ClientData clientData; /* Ignored. */ +{ + register Tcl_Obj **tosPtr; /* Cached evaluation stack top pointer. */ + Interp *iPtr = (Interp *) interp; + double dResult; + long tmp; /* Algorithm assumes at least 32 bits. + * Only long guarantees that. See below. */ + + if (!(iPtr->flags & RAND_SEED_INITIALIZED)) { + iPtr->flags |= RAND_SEED_INITIALIZED; + iPtr->randSeed = TclpGetClicks(); + + /* + * Make sure 1 <= randSeed <= (2^31) - 2. See below. + */ + + iPtr->randSeed &= (unsigned long) 0x7fffffff; + if ((iPtr->randSeed == 0) || (iPtr->randSeed == 0x7fffffff)) { + iPtr->randSeed ^= 123459876; + } + } + + /* + * Set tosPtr from eePtr. + */ + + CACHE_STACK_INFO(); + + /* + * Generate the random number using the linear congruential + * generator defined by the following recurrence: + * seed = ( IA * seed ) mod IM + * where IA is 16807 and IM is (2^31) - 1. The recurrence maps + * a seed in the range [1, IM - 1] to a new seed in that same range. + * The recurrence maps IM to 0, and maps 0 back to 0, so those two + * values must not be allowed as initial values of seed. + * + * In order to avoid potential problems with integer overflow, the + * recurrence is implemented in terms of additional constants + * IQ and IR such that + * IM = IA*IQ + IR + * None of the operations in the implementation overflows a 32-bit + * signed integer, and the C type long is guaranteed to be at least + * 32 bits wide. + * + * For more details on how this algorithm works, refer to the following + * papers: + * + * S.K. Park & K.W. Miller, "Random number generators: good ones + * are hard to find," Comm ACM 31(10):1192-1201, Oct 1988 + * + * W.H. Press & S.A. Teukolsky, "Portable random number + * generators," Computers in Physics 6(5):522-524, Sep/Oct 1992. + */ + +#define RAND_IA 16807 +#define RAND_IM 2147483647 +#define RAND_IQ 127773 +#define RAND_IR 2836 +#define RAND_MASK 123459876 + + tmp = iPtr->randSeed/RAND_IQ; + iPtr->randSeed = RAND_IA*(iPtr->randSeed - tmp*RAND_IQ) - RAND_IR*tmp; + if (iPtr->randSeed < 0) { + iPtr->randSeed += RAND_IM; + } + + /* + * Since the recurrence keeps seed values in the range [1, RAND_IM - 1], + * dividing by RAND_IM yields a double in the range (0, 1). + */ + + dResult = iPtr->randSeed * (1.0/RAND_IM); + + /* + * Push a Tcl object with the result. + */ + + PUSH_OBJECT(Tcl_NewDoubleObj(dResult)); + + /* + * Reflect the change to stackTop back in eePtr. + */ + + DECACHE_STACK_INFO(); + return TCL_OK; +} + +static int +ExprRoundFunc(interp, eePtr, clientData) + Tcl_Interp *interp; /* The interpreter in which to execute the + * function. */ + ExecEnv *eePtr; /* Points to the environment for executing + * the function. */ + ClientData clientData; /* Ignored. */ +{ + register Tcl_Obj **tosPtr; /* Cached top index of evaluation stack. */ + Tcl_Obj *valuePtr; + long iResult; + double d, temp; + int result; + + /* + * Set stackPtr and tosPtr from eePtr. + */ + + result = TCL_OK; + CACHE_STACK_INFO(); + + /* + * Pop the argument from the evaluation stack. + */ + + valuePtr = POP_OBJECT(); + + if (VerifyExprObjType(interp, valuePtr) != TCL_OK) { + result = TCL_ERROR; + goto done; + } + + if (valuePtr->typePtr == &tclIntType) { + iResult = valuePtr->internalRep.longValue; + } else { + d = valuePtr->internalRep.doubleValue; + if (d < 0.0) { + if (d <= (((double) (long) LONG_MIN) - 0.5)) { + tooLarge: + Tcl_ResetResult(interp); + Tcl_AppendToObj(Tcl_GetObjResult(interp), + "integer value too large to represent", -1); + Tcl_SetErrorCode(interp, "ARITH", "IOVERFLOW", + "integer value too large to represent", + (char *) NULL); + result = TCL_ERROR; + goto done; + } + temp = (long) (d - 0.5); + } else { + if (d >= (((double) LONG_MAX + 0.5))) { + goto tooLarge; + } + temp = (long) (d + 0.5); + } + if (IS_NAN(temp) || IS_INF(temp)) { + TclExprFloatError(interp, temp); + result = TCL_ERROR; + goto done; + } + iResult = (long) temp; + } + + /* + * Push a Tcl object with the result. + */ + + PUSH_OBJECT(Tcl_NewLongObj(iResult)); + + /* + * Reflect the change to tosPtr back in eePtr. + */ + + done: + Tcl_DecrRefCount(valuePtr); + DECACHE_STACK_INFO(); + return result; +} + +static int +ExprSrandFunc(interp, eePtr, clientData) + Tcl_Interp *interp; /* The interpreter in which to execute the + * function. */ + ExecEnv *eePtr; /* Points to the environment for executing + * the function. */ + ClientData clientData; /* Ignored. */ +{ + Tcl_Obj **tosPtr; /* Cached evaluation stack top pointer. */ + Interp *iPtr = (Interp *) interp; + Tcl_Obj *valuePtr; + long i = 0; /* Initialized to avoid compiler warning. */ + int result; + + /* + * Set tosPtr from eePtr. + */ + + CACHE_STACK_INFO(); + + /* + * Pop the argument from the evaluation stack. Use the value + * to reset the random number seed. + */ + + valuePtr = POP_OBJECT(); + + if (VerifyExprObjType(interp, valuePtr) != TCL_OK) { + result = TCL_ERROR; + goto badValue; + } + + if (valuePtr->typePtr == &tclIntType) { + i = valuePtr->internalRep.longValue; + } else { + /* + * At this point, the only other possible type is double + */ + Tcl_ResetResult(interp); + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "can't use floating-point value as argument to srand", + (char *) NULL); + badValue: + Tcl_DecrRefCount(valuePtr); + DECACHE_STACK_INFO(); + return TCL_ERROR; + } + + /* + * Reset the seed. Make sure 1 <= randSeed <= 2^31 - 2. + * See comments in ExprRandFunc() for more details. + */ + + iPtr->flags |= RAND_SEED_INITIALIZED; + iPtr->randSeed = i; + iPtr->randSeed &= (unsigned long) 0x7fffffff; + if ((iPtr->randSeed == 0) || (iPtr->randSeed == 0x7fffffff)) { + iPtr->randSeed ^= 123459876; + } + + /* + * To avoid duplicating the random number generation code we simply + * clean up our state and call the real random number function. That + * function will always succeed. + */ + + Tcl_DecrRefCount(valuePtr); + DECACHE_STACK_INFO(); + + ExprRandFunc(interp, eePtr, clientData); + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * ExprCallMathFunc -- + * + * This procedure is invoked to call a non-builtin math function + * during the execution of an expression. + * + * Results: + * TCL_OK is returned if all went well and the function's value + * was computed successfully. If an error occurred, TCL_ERROR + * is returned and an error message is left in the interpreter's + * result. After a successful return this procedure pushes a Tcl object + * holding the result. + * + * Side effects: + * None, unless the called math function has side effects. + * + *---------------------------------------------------------------------- + */ + +static int +ExprCallMathFunc(interp, eePtr, objc, objv) + Tcl_Interp *interp; /* The interpreter in which to execute the + * function. */ + ExecEnv *eePtr; /* Points to the environment for executing + * the function. */ + int objc; /* Number of arguments. The function name is + * the 0-th argument. */ + Tcl_Obj **objv; /* The array of arguments. The function name + * is objv[0]. */ +{ + Interp *iPtr = (Interp *) interp; + register Tcl_Obj **tosPtr; /* Cached top index of evaluation stack. */ + char *funcName; + Tcl_HashEntry *hPtr; + MathFunc *mathFuncPtr; /* Information about math function. */ + Tcl_Value args[MAX_MATH_ARGS]; /* Arguments for function call. */ + Tcl_Value funcResult; /* Result of function call as Tcl_Value. */ + register Tcl_Obj *valuePtr; + long i; + double d; + int j, k, result; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + Tcl_ResetResult(interp); + + /* + * Set stackPtr and tosPtr from eePtr. + */ + + CACHE_STACK_INFO(); + + /* + * Look up the MathFunc record for the function. + */ + + funcName = Tcl_GetString(objv[0]); + hPtr = Tcl_FindHashEntry(&iPtr->mathFuncTable, funcName); + if (hPtr == NULL) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "unknown math function \"", funcName, "\"", (char *) NULL); + result = TCL_ERROR; + goto done; + } + mathFuncPtr = (MathFunc *) Tcl_GetHashValue(hPtr); + if (mathFuncPtr->numArgs != (objc-1)) { + panic("ExprCallMathFunc: expected number of args %d != actual number %d", + mathFuncPtr->numArgs, objc); + result = TCL_ERROR; + goto done; + } + + /* + * Collect the arguments for the function, if there are any, into the + * array "args". Note that args[0] will have the Tcl_Value that + * corresponds to objv[1]. + */ + + for (j = 1, k = 0; j < objc; j++, k++) { + valuePtr = objv[j]; + + if (VerifyExprObjType(interp, valuePtr) != TCL_OK) { + result = TCL_ERROR; + goto done; + } + + /* + * Copy the object's numeric value to the argument record, + * converting it if necessary. + */ + + if (valuePtr->typePtr == &tclIntType) { + i = valuePtr->internalRep.longValue; + if (mathFuncPtr->argTypes[k] == TCL_DOUBLE) { + args[k].type = TCL_DOUBLE; + args[k].doubleValue = i; + } else { + args[k].type = TCL_INT; + args[k].intValue = i; + } + } else { + d = valuePtr->internalRep.doubleValue; + if (mathFuncPtr->argTypes[k] == TCL_INT) { + args[k].type = TCL_INT; + args[k].intValue = (long) d; + } else { + args[k].type = TCL_DOUBLE; + args[k].doubleValue = d; + } + } + } + + /* + * Invoke the function and copy its result back into valuePtr. + */ + + tsdPtr->mathInProgress++; + result = (*mathFuncPtr->proc)(mathFuncPtr->clientData, interp, args, + &funcResult); + tsdPtr->mathInProgress--; + if (result != TCL_OK) { + goto done; + } + + /* + * Pop the objc top stack elements and decrement their ref counts. + */ + + { + Tcl_Obj **i = (tosPtr - (objc-1)); + while (i <= tosPtr) { + valuePtr = *i; + Tcl_DecrRefCount(valuePtr); + i++; + } + } + tosPtr -= objc; + + /* + * Push the call's object result. + */ + + if (funcResult.type == TCL_INT) { + PUSH_OBJECT(Tcl_NewLongObj(funcResult.intValue)); + } else { + d = funcResult.doubleValue; + if (IS_NAN(d) || IS_INF(d)) { + TclExprFloatError(interp, d); + result = TCL_ERROR; + goto done; + } + PUSH_OBJECT(Tcl_NewDoubleObj(d)); + } + + /* + * Reflect the change to tosPtr back in eePtr. + */ + + done: + DECACHE_STACK_INFO(); + return result; +} + +/* + *---------------------------------------------------------------------- + * + * TclExprFloatError -- + * + * This procedure is called when an error occurs during a + * floating-point operation. It reads errno and sets + * interp->objResultPtr accordingly. + * + * Results: + * interp->objResultPtr is set to hold an error message. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +void +TclExprFloatError(interp, value) + Tcl_Interp *interp; /* Where to store error message. */ + double value; /* Value returned after error; used to + * distinguish underflows from overflows. */ +{ + char *s; + + Tcl_ResetResult(interp); + if ((errno == EDOM) || (value != value)) { + s = "domain error: argument not in valid range"; + Tcl_AppendToObj(Tcl_GetObjResult(interp), s, -1); + Tcl_SetErrorCode(interp, "ARITH", "DOMAIN", s, (char *) NULL); + } else if ((errno == ERANGE) || IS_INF(value)) { + if (value == 0.0) { + s = "floating-point value too small to represent"; + Tcl_AppendToObj(Tcl_GetObjResult(interp), s, -1); + Tcl_SetErrorCode(interp, "ARITH", "UNDERFLOW", s, (char *) NULL); + } else { + s = "floating-point value too large to represent"; + Tcl_AppendToObj(Tcl_GetObjResult(interp), s, -1); + Tcl_SetErrorCode(interp, "ARITH", "OVERFLOW", s, (char *) NULL); + } + } else { + char msg[64 + TCL_INTEGER_SPACE]; + + sprintf(msg, "unknown floating-point error, errno = %d", errno); + Tcl_AppendToObj(Tcl_GetObjResult(interp), msg, -1); + Tcl_SetErrorCode(interp, "ARITH", "UNKNOWN", msg, (char *) NULL); + } +} + +/* + *---------------------------------------------------------------------- + * + * TclMathInProgress -- + * + * This procedure is called to find out if Tcl is doing math + * in this thread. + * + * Results: + * 0 or 1. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +int +TclMathInProgress() +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + return tsdPtr->mathInProgress; +} + +#ifdef TCL_COMPILE_STATS +/* + *---------------------------------------------------------------------- + * + * TclLog2 -- + * + * Procedure used while collecting compilation statistics to determine + * the log base 2 of an integer. + * + * Results: + * Returns the log base 2 of the operand. If the argument is less + * than or equal to zero, a zero is returned. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +int +TclLog2(value) + register int value; /* The integer for which to compute the + * log base 2. */ +{ + register int n = value; + register int result = 0; + + while (n > 1) { + n = n >> 1; + result++; + } + return result; +} + +/* + *---------------------------------------------------------------------- + * + * EvalStatsCmd -- + * + * Implements the "evalstats" command that prints instruction execution + * counts to stdout. + * + * Results: + * Standard Tcl results. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +EvalStatsCmd(unused, interp, argc, argv) + ClientData unused; /* Unused. */ + Tcl_Interp *interp; /* The current interpreter. */ + int argc; /* The number of arguments. */ + char **argv; /* The argument strings. */ +{ + Interp *iPtr = (Interp *) interp; + LiteralTable *globalTablePtr = &(iPtr->literalTable); + ByteCodeStats *statsPtr = &(iPtr->stats); + double totalCodeBytes, currentCodeBytes; + double totalLiteralBytes, currentLiteralBytes; + double objBytesIfUnshared, strBytesIfUnshared, sharingBytesSaved; + double strBytesSharedMultX, strBytesSharedOnce; + double numInstructions, currentHeaderBytes; + long numCurrentByteCodes, numByteCodeLits; + long refCountSum, literalMgmtBytes, sum; + int numSharedMultX, numSharedOnce; + int decadeHigh, minSizeDecade, maxSizeDecade, length, i; + char *litTableStats; + LiteralEntry *entryPtr; + + numInstructions = 0.0; + for (i = 0; i < 256; i++) { + if (statsPtr->instructionCount[i] != 0) { + numInstructions += statsPtr->instructionCount[i]; + } + } + + totalLiteralBytes = sizeof(LiteralTable) + + iPtr->literalTable.numBuckets * sizeof(LiteralEntry *) + + (statsPtr->numLiteralsCreated * sizeof(LiteralEntry)) + + (statsPtr->numLiteralsCreated * sizeof(Tcl_Obj)) + + statsPtr->totalLitStringBytes; + totalCodeBytes = statsPtr->totalByteCodeBytes + totalLiteralBytes; + + numCurrentByteCodes = + statsPtr->numCompilations - statsPtr->numByteCodesFreed; + currentHeaderBytes = numCurrentByteCodes + * (sizeof(ByteCode) - (sizeof(size_t) + sizeof(Tcl_Time))); + literalMgmtBytes = sizeof(LiteralTable) + + (iPtr->literalTable.numBuckets * sizeof(LiteralEntry *)) + + (iPtr->literalTable.numEntries * sizeof(LiteralEntry)); + currentLiteralBytes = literalMgmtBytes + + iPtr->literalTable.numEntries * sizeof(Tcl_Obj) + + statsPtr->currentLitStringBytes; + currentCodeBytes = statsPtr->currentByteCodeBytes + currentLiteralBytes; + + /* + * Summary statistics, total and current source and ByteCode sizes. + */ + + fprintf(stdout, "\n----------------------------------------------------------------\n"); + fprintf(stdout, + "Compilation and execution statistics for interpreter 0x%x\n", + (unsigned int) iPtr); + + fprintf(stdout, "\nNumber ByteCodes executed %ld\n", + statsPtr->numExecutions); + fprintf(stdout, "Number ByteCodes compiled %ld\n", + statsPtr->numCompilations); + fprintf(stdout, " Mean executions/compile %.1f\n", + ((float)statsPtr->numExecutions) / ((float)statsPtr->numCompilations)); + + fprintf(stdout, "\nInstructions executed %.0f\n", + numInstructions); + fprintf(stdout, " Mean inst/compile %.0f\n", + numInstructions / statsPtr->numCompilations); + fprintf(stdout, " Mean inst/execution %.0f\n", + numInstructions / statsPtr->numExecutions); + + fprintf(stdout, "\nTotal ByteCodes %ld\n", + statsPtr->numCompilations); + fprintf(stdout, " Source bytes %.6g\n", + statsPtr->totalSrcBytes); + fprintf(stdout, " Code bytes %.6g\n", + totalCodeBytes); + fprintf(stdout, " ByteCode bytes %.6g\n", + statsPtr->totalByteCodeBytes); + fprintf(stdout, " Literal bytes %.6g\n", + totalLiteralBytes); + fprintf(stdout, " table %d + bkts %d + entries %ld + objects %ld + strings %.6g\n", + sizeof(LiteralTable), + iPtr->literalTable.numBuckets * sizeof(LiteralEntry *), + statsPtr->numLiteralsCreated * sizeof(LiteralEntry), + statsPtr->numLiteralsCreated * sizeof(Tcl_Obj), + statsPtr->totalLitStringBytes); + fprintf(stdout, " Mean code/compile %.1f\n", + totalCodeBytes / statsPtr->numCompilations); + fprintf(stdout, " Mean code/source %.1f\n", + totalCodeBytes / statsPtr->totalSrcBytes); + + fprintf(stdout, "\nCurrent (active) ByteCodes %ld\n", + numCurrentByteCodes); + fprintf(stdout, " Source bytes %.6g\n", + statsPtr->currentSrcBytes); + fprintf(stdout, " Code bytes %.6g\n", + currentCodeBytes); + fprintf(stdout, " ByteCode bytes %.6g\n", + statsPtr->currentByteCodeBytes); + fprintf(stdout, " Literal bytes %.6g\n", + currentLiteralBytes); + fprintf(stdout, " table %d + bkts %d + entries %d + objects %d + strings %.6g\n", + sizeof(LiteralTable), + iPtr->literalTable.numBuckets * sizeof(LiteralEntry *), + iPtr->literalTable.numEntries * sizeof(LiteralEntry), + iPtr->literalTable.numEntries * sizeof(Tcl_Obj), + statsPtr->currentLitStringBytes); + fprintf(stdout, " Mean code/source %.1f\n", + currentCodeBytes / statsPtr->currentSrcBytes); + fprintf(stdout, " Code + source bytes %.6g (%0.1f mean code/src)\n", + (currentCodeBytes + statsPtr->currentSrcBytes), + (currentCodeBytes / statsPtr->currentSrcBytes) + 1.0); + + /* + * Tcl_IsShared statistics check + * + * This gives the refcount of each obj as Tcl_IsShared was called + * for it. Shared objects must be duplicated before they can be + * modified. + */ + + numSharedMultX = 0; + fprintf(stdout, "\nTcl_IsShared object check (all objects):\n"); + fprintf(stdout, " Object had refcount <=1 (not shared) %ld\n", + tclObjsShared[1]); + for (i = 2; i < TCL_MAX_SHARED_OBJ_STATS; i++) { + fprintf(stdout, " refcount ==%d %ld\n", + i, tclObjsShared[i]); + numSharedMultX += tclObjsShared[i]; + } + fprintf(stdout, " refcount >=%d %ld\n", + i, tclObjsShared[0]); + numSharedMultX += tclObjsShared[0]; + fprintf(stdout, " Total shared objects %d\n", + numSharedMultX); + + /* + * Literal table statistics. + */ + + numByteCodeLits = 0; + refCountSum = 0; + numSharedMultX = 0; + numSharedOnce = 0; + objBytesIfUnshared = 0.0; + strBytesIfUnshared = 0.0; + strBytesSharedMultX = 0.0; + strBytesSharedOnce = 0.0; + for (i = 0; i < globalTablePtr->numBuckets; i++) { + for (entryPtr = globalTablePtr->buckets[i]; entryPtr != NULL; + entryPtr = entryPtr->nextPtr) { + if (entryPtr->objPtr->typePtr == &tclByteCodeType) { + numByteCodeLits++; + } + (void) Tcl_GetStringFromObj(entryPtr->objPtr, &length); + refCountSum += entryPtr->refCount; + objBytesIfUnshared += (entryPtr->refCount * sizeof(Tcl_Obj)); + strBytesIfUnshared += (entryPtr->refCount * (length+1)); + if (entryPtr->refCount > 1) { + numSharedMultX++; + strBytesSharedMultX += (length+1); + } else { + numSharedOnce++; + strBytesSharedOnce += (length+1); + } + } + } + sharingBytesSaved = (objBytesIfUnshared + strBytesIfUnshared) + - currentLiteralBytes; + + fprintf(stdout, "\nTotal objects (all interps) %ld\n", + tclObjsAlloced); + fprintf(stdout, "Current objects %ld\n", + (tclObjsAlloced - tclObjsFreed)); + fprintf(stdout, "Total literal objects %ld\n", + statsPtr->numLiteralsCreated); + + fprintf(stdout, "\nCurrent literal objects %d (%0.1f%% of current objects)\n", + globalTablePtr->numEntries, + (globalTablePtr->numEntries * 100.0) / (tclObjsAlloced-tclObjsFreed)); + fprintf(stdout, " ByteCode literals %ld (%0.1f%% of current literals)\n", + numByteCodeLits, + (numByteCodeLits * 100.0) / globalTablePtr->numEntries); + fprintf(stdout, " Literals reused > 1x %d\n", + numSharedMultX); + fprintf(stdout, " Mean reference count %.2f\n", + ((double) refCountSum) / globalTablePtr->numEntries); + fprintf(stdout, " Mean len, str reused >1x %.2f\n", + (numSharedMultX? (strBytesSharedMultX/numSharedMultX) : 0.0)); + fprintf(stdout, " Mean len, str used 1x %.2f\n", + (numSharedOnce? (strBytesSharedOnce/numSharedOnce) : 0.0)); + fprintf(stdout, " Total sharing savings %.6g (%0.1f%% of bytes if no sharing)\n", + sharingBytesSaved, + (sharingBytesSaved * 100.0) / (objBytesIfUnshared + strBytesIfUnshared)); + fprintf(stdout, " Bytes with sharing %.6g\n", + currentLiteralBytes); + fprintf(stdout, " table %d + bkts %d + entries %d + objects %d + strings %.6g\n", + sizeof(LiteralTable), + iPtr->literalTable.numBuckets * sizeof(LiteralEntry *), + iPtr->literalTable.numEntries * sizeof(LiteralEntry), + iPtr->literalTable.numEntries * sizeof(Tcl_Obj), + statsPtr->currentLitStringBytes); + fprintf(stdout, " Bytes if no sharing %.6g = objects %.6g + strings %.6g\n", + (objBytesIfUnshared + strBytesIfUnshared), + objBytesIfUnshared, strBytesIfUnshared); + fprintf(stdout, " String sharing savings %.6g = unshared %.6g - shared %.6g\n", + (strBytesIfUnshared - statsPtr->currentLitStringBytes), + strBytesIfUnshared, statsPtr->currentLitStringBytes); + fprintf(stdout, " Literal mgmt overhead %ld (%0.1f%% of bytes with sharing)\n", + literalMgmtBytes, + (literalMgmtBytes * 100.0) / currentLiteralBytes); + fprintf(stdout, " table %d + buckets %d + entries %d\n", + sizeof(LiteralTable), + iPtr->literalTable.numBuckets * sizeof(LiteralEntry *), + iPtr->literalTable.numEntries * sizeof(LiteralEntry)); + + /* + * Breakdown of current ByteCode space requirements. + */ + + fprintf(stdout, "\nBreakdown of current ByteCode requirements:\n"); + fprintf(stdout, " Bytes Pct of Avg per\n"); + fprintf(stdout, " total ByteCode\n"); + fprintf(stdout, "Total %12.6g 100.00%% %8.1f\n", + statsPtr->currentByteCodeBytes, + statsPtr->currentByteCodeBytes / numCurrentByteCodes); + fprintf(stdout, "Header %12.6g %8.1f%% %8.1f\n", + currentHeaderBytes, + ((currentHeaderBytes * 100.0) / statsPtr->currentByteCodeBytes), + currentHeaderBytes / numCurrentByteCodes); + fprintf(stdout, "Instructions %12.6g %8.1f%% %8.1f\n", + statsPtr->currentInstBytes, + ((statsPtr->currentInstBytes * 100.0) / statsPtr->currentByteCodeBytes), + statsPtr->currentInstBytes / numCurrentByteCodes); + fprintf(stdout, "Literal ptr array %12.6g %8.1f%% %8.1f\n", + statsPtr->currentLitBytes, + ((statsPtr->currentLitBytes * 100.0) / statsPtr->currentByteCodeBytes), + statsPtr->currentLitBytes / numCurrentByteCodes); + fprintf(stdout, "Exception table %12.6g %8.1f%% %8.1f\n", + statsPtr->currentExceptBytes, + ((statsPtr->currentExceptBytes * 100.0) / statsPtr->currentByteCodeBytes), + statsPtr->currentExceptBytes / numCurrentByteCodes); + fprintf(stdout, "Auxiliary data %12.6g %8.1f%% %8.1f\n", + statsPtr->currentAuxBytes, + ((statsPtr->currentAuxBytes * 100.0) / statsPtr->currentByteCodeBytes), + statsPtr->currentAuxBytes / numCurrentByteCodes); + fprintf(stdout, "Command map %12.6g %8.1f%% %8.1f\n", + statsPtr->currentCmdMapBytes, + ((statsPtr->currentCmdMapBytes * 100.0) / statsPtr->currentByteCodeBytes), + statsPtr->currentCmdMapBytes / numCurrentByteCodes); + + /* + * Detailed literal statistics. + */ + + fprintf(stdout, "\nLiteral string sizes:\n"); + fprintf(stdout, " Up to length Percentage\n"); + maxSizeDecade = 0; + for (i = 31; i >= 0; i--) { + if (statsPtr->literalCount[i] > 0) { + maxSizeDecade = i; + break; + } + } + sum = 0; + for (i = 0; i <= maxSizeDecade; i++) { + decadeHigh = (1 << (i+1)) - 1; + sum += statsPtr->literalCount[i]; + fprintf(stdout, " %10d %8.0f%%\n", + decadeHigh, (sum * 100.0) / statsPtr->numLiteralsCreated); + } + + litTableStats = TclLiteralStats(globalTablePtr); + fprintf(stdout, "\nCurrent literal table statistics:\n%s\n", + litTableStats); + ckfree((char *) litTableStats); + + /* + * Source and ByteCode size distributions. + */ + + fprintf(stdout, "\nSource sizes:\n"); + fprintf(stdout, " Up to size Percentage\n"); + minSizeDecade = maxSizeDecade = 0; + for (i = 0; i < 31; i++) { + if (statsPtr->srcCount[i] > 0) { + minSizeDecade = i; + break; + } + } + for (i = 31; i >= 0; i--) { + if (statsPtr->srcCount[i] > 0) { + maxSizeDecade = i; + break; + } + } + sum = 0; + for (i = minSizeDecade; i <= maxSizeDecade; i++) { + decadeHigh = (1 << (i+1)) - 1; + sum += statsPtr->srcCount[i]; + fprintf(stdout, " %10d %8.0f%%\n", + decadeHigh, (sum * 100.0) / statsPtr->numCompilations); + } + + fprintf(stdout, "\nByteCode sizes:\n"); + fprintf(stdout, " Up to size Percentage\n"); + minSizeDecade = maxSizeDecade = 0; + for (i = 0; i < 31; i++) { + if (statsPtr->byteCodeCount[i] > 0) { + minSizeDecade = i; + break; + } + } + for (i = 31; i >= 0; i--) { + if (statsPtr->byteCodeCount[i] > 0) { + maxSizeDecade = i; + break; + } + } + sum = 0; + for (i = minSizeDecade; i <= maxSizeDecade; i++) { + decadeHigh = (1 << (i+1)) - 1; + sum += statsPtr->byteCodeCount[i]; + fprintf(stdout, " %10d %8.0f%%\n", + decadeHigh, (sum * 100.0) / statsPtr->numCompilations); + } + + fprintf(stdout, "\nByteCode longevity (excludes Current ByteCodes):\n"); + fprintf(stdout, " Up to ms Percentage\n"); + minSizeDecade = maxSizeDecade = 0; + for (i = 0; i < 31; i++) { + if (statsPtr->lifetimeCount[i] > 0) { + minSizeDecade = i; + break; + } + } + for (i = 31; i >= 0; i--) { + if (statsPtr->lifetimeCount[i] > 0) { + maxSizeDecade = i; + break; + } + } + sum = 0; + for (i = minSizeDecade; i <= maxSizeDecade; i++) { + decadeHigh = (1 << (i+1)) - 1; + sum += statsPtr->lifetimeCount[i]; + fprintf(stdout, " %12.3f %8.0f%%\n", + decadeHigh / 1000.0, + (sum * 100.0) / statsPtr->numByteCodesFreed); + } + + /* + * Instruction counts. + */ + + fprintf(stdout, "\nInstruction counts:\n"); + for (i = 0; i <= LAST_INST_OPCODE; i++) { + if (statsPtr->instructionCount[i]) { + fprintf(stdout, "%20s %8ld %6.1f%%\n", + instructionTable[i].name, + statsPtr->instructionCount[i], + (statsPtr->instructionCount[i]*100.0) / numInstructions); + } + } + + fprintf(stdout, "\nInstructions NEVER executed:\n"); + for (i = 0; i <= LAST_INST_OPCODE; i++) { + if (statsPtr->instructionCount[i] == 0) { + fprintf(stdout, "%20s\n", + instructionTable[i].name); + } + } + +#ifdef TCL_MEM_DEBUG + fprintf(stdout, "\nHeap Statistics:\n"); + TclDumpMemoryInfo(stdout); +#endif + fprintf(stdout, "\n----------------------------------------------------------------\n"); + return TCL_OK; +} +#endif /* TCL_COMPILE_STATS */ + +/* + *---------------------------------------------------------------------- + * + * Tcl_GetCommandFromObj -- + * + * Returns the command specified by the name in a Tcl_Obj. + * + * Results: + * Returns a token for the command if it is found. Otherwise, if it + * can't be found or there is an error, returns NULL. + * + * Side effects: + * May update the internal representation for the object, caching + * the command reference so that the next time this procedure is + * called with the same object, the command can be found quickly. + * + *---------------------------------------------------------------------- + */ +Tcl_Command +Tcl_GetCommandFromObj(interp, objPtr) + Tcl_Interp *interp; /* The interpreter in which to resolve the + * command and to report errors. */ + register Tcl_Obj *objPtr; /* The object containing the command's + * name. If the name starts with "::", will + * be looked up in global namespace. Else, + * looked up first in the current namespace + * if contextNsPtr is NULL, then in global + * namespace. */ +{ + Interp *iPtr = (Interp *) interp; + register ResolvedCmdName *resPtr; + register Command *cmdPtr; + Namespace *currNsPtr; + int result; + + /* + * Get the internal representation, converting to a command type if + * needed. The internal representation is a ResolvedCmdName that points + * to the actual command. + */ + + if (objPtr->typePtr != &tclCmdNameType) { + result = tclCmdNameType.setFromAnyProc(interp, objPtr); + if (result != TCL_OK) { + return (Tcl_Command) NULL; + } + resPtr = (ResolvedCmdName *) objPtr->internalRep.otherValuePtr; + if (resPtr != NULL) return (Tcl_Command) resPtr->cmdPtr; + } + + resPtr = (ResolvedCmdName *) objPtr->internalRep.otherValuePtr; + + /* + * Get the current namespace. + */ + + if (iPtr->varFramePtr != NULL) { + currNsPtr = iPtr->varFramePtr->nsPtr; + } else { + currNsPtr = iPtr->globalNsPtr; + } + + /* + * Check the context namespace and the namespace epoch of the resolved + * symbol to make sure that it is fresh. If not, then force another + * conversion to the command type, to discard the old rep and create a + * new one. Note that we verify that the namespace id of the context + * namespace is the same as the one we cached; this insures that the + * namespace wasn't deleted and a new one created at the same address + * with the same command epoch. + */ + + if ((resPtr != NULL) + && (resPtr->refNsPtr == currNsPtr) + && (resPtr->refNsId == currNsPtr->nsId) + && (resPtr->refNsCmdEpoch == currNsPtr->cmdRefEpoch)) { + cmdPtr = resPtr->cmdPtr; + if (cmdPtr->cmdEpoch == resPtr->cmdEpoch) { + return (Tcl_Command) cmdPtr; + } + } + + result = tclCmdNameType.setFromAnyProc(interp, objPtr); + if (result != TCL_OK) { + return (Tcl_Command) NULL; + } + resPtr = (ResolvedCmdName *) objPtr->internalRep.otherValuePtr; + if (resPtr != NULL) { + return (Tcl_Command) resPtr->cmdPtr; + } else { + return (Tcl_Command) NULL; + } +} + +/* + *---------------------------------------------------------------------- + * + * TclSetCmdNameObj -- + * + * Modify an object to be an CmdName object that refers to the argument + * Command structure. + * + * Results: + * None. + * + * Side effects: + * The object's old internal rep is freed. It's string rep is not + * changed. The refcount in the Command structure is incremented to + * keep it from being freed if the command is later deleted until + * TclExecuteByteCode has a chance to recognize that it was deleted. + * + *---------------------------------------------------------------------- + */ + +void +TclSetCmdNameObj(interp, objPtr, cmdPtr) + Tcl_Interp *interp; /* Points to interpreter containing command + * that should be cached in objPtr. */ + register Tcl_Obj *objPtr; /* Points to Tcl object to be changed to + * a CmdName object. */ + Command *cmdPtr; /* Points to Command structure that the + * CmdName object should refer to. */ +{ + Interp *iPtr = (Interp *) interp; + register ResolvedCmdName *resPtr; + Tcl_ObjType *oldTypePtr = objPtr->typePtr; + register Namespace *currNsPtr; + + if (oldTypePtr == &tclCmdNameType) { + return; + } + + /* + * Get the current namespace. + */ + + if (iPtr->varFramePtr != NULL) { + currNsPtr = iPtr->varFramePtr->nsPtr; + } else { + currNsPtr = iPtr->globalNsPtr; + } + + cmdPtr->refCount++; + resPtr = (ResolvedCmdName *) ckalloc(sizeof(ResolvedCmdName)); + resPtr->cmdPtr = cmdPtr; + resPtr->refNsPtr = currNsPtr; + resPtr->refNsId = currNsPtr->nsId; + resPtr->refNsCmdEpoch = currNsPtr->cmdRefEpoch; + resPtr->cmdEpoch = cmdPtr->cmdEpoch; + resPtr->refCount = 1; + + if ((oldTypePtr != NULL) && (oldTypePtr->freeIntRepProc != NULL)) { + oldTypePtr->freeIntRepProc(objPtr); + } + objPtr->internalRep.twoPtrValue.ptr1 = (VOID *) resPtr; + objPtr->internalRep.twoPtrValue.ptr2 = NULL; + objPtr->typePtr = &tclCmdNameType; +} + +/* + *---------------------------------------------------------------------- + * + * FreeCmdNameInternalRep -- + * + * Frees the resources associated with a cmdName object's internal + * representation. + * + * Results: + * None. + * + * Side effects: + * Decrements the ref count of any cached ResolvedCmdName structure + * pointed to by the cmdName's internal representation. If this is + * the last use of the ResolvedCmdName, it is freed. This in turn + * decrements the ref count of the Command structure pointed to by + * the ResolvedSymbol, which may free the Command structure. + * + *---------------------------------------------------------------------- + */ + +static void +FreeCmdNameInternalRep(objPtr) + register Tcl_Obj *objPtr; /* CmdName object with internal + * representation to free. */ +{ + register ResolvedCmdName *resPtr = + (ResolvedCmdName *) objPtr->internalRep.otherValuePtr; + + if (resPtr != NULL) { + /* + * Decrement the reference count of the ResolvedCmdName structure. + * If there are no more uses, free the ResolvedCmdName structure. + */ + + resPtr->refCount--; + if (resPtr->refCount == 0) { + /* + * Now free the cached command, unless it is still in its + * hash table or if there are other references to it + * from other cmdName objects. + */ + + Command *cmdPtr = resPtr->cmdPtr; + TclCleanupCommand(cmdPtr); + ckfree((char *) resPtr); + } + } +} + +/* + *---------------------------------------------------------------------- + * + * DupCmdNameInternalRep -- + * + * Initialize the internal representation of an cmdName Tcl_Obj to a + * copy of the internal representation of an existing cmdName object. + * + * Results: + * None. + * + * Side effects: + * "copyPtr"s internal rep is set to point to the ResolvedCmdName + * structure corresponding to "srcPtr"s internal rep. Increments the + * ref count of the ResolvedCmdName structure pointed to by the + * cmdName's internal representation. + * + *---------------------------------------------------------------------- + */ + +static void +DupCmdNameInternalRep(srcPtr, copyPtr) + Tcl_Obj *srcPtr; /* Object with internal rep to copy. */ + register Tcl_Obj *copyPtr; /* Object with internal rep to set. */ +{ + register ResolvedCmdName *resPtr = + (ResolvedCmdName *) srcPtr->internalRep.otherValuePtr; + + copyPtr->internalRep.twoPtrValue.ptr1 = (VOID *) resPtr; + copyPtr->internalRep.twoPtrValue.ptr2 = NULL; + if (resPtr != NULL) { + resPtr->refCount++; + } + copyPtr->typePtr = &tclCmdNameType; +} + +/* + *---------------------------------------------------------------------- + * + * SetCmdNameFromAny -- + * + * Generate an cmdName internal form for the Tcl object "objPtr". + * + * Results: + * The return value is a standard Tcl result. The conversion always + * succeeds and TCL_OK is returned. + * + * Side effects: + * A pointer to a ResolvedCmdName structure that holds a cached pointer + * to the command with a name that matches objPtr's string rep is + * stored as objPtr's internal representation. This ResolvedCmdName + * pointer will be NULL if no matching command was found. The ref count + * of the cached Command's structure (if any) is also incremented. + * + *---------------------------------------------------------------------- + */ + +static int +SetCmdNameFromAny(interp, objPtr) + Tcl_Interp *interp; /* Used for error reporting if not NULL. */ + register Tcl_Obj *objPtr; /* The object to convert. */ +{ + Interp *iPtr = (Interp *) interp; + char *name; + Tcl_Command cmd; + register Command *cmdPtr; + Namespace *currNsPtr; + register ResolvedCmdName *resPtr; + + /* + * Get "objPtr"s string representation. Make it up-to-date if necessary. + */ + + name = objPtr->bytes; + if (name == NULL) { + name = Tcl_GetString(objPtr); + } + + /* + * Find the Command structure, if any, that describes the command called + * "name". Build a ResolvedCmdName that holds a cached pointer to this + * Command, and bump the reference count in the referenced Command + * structure. A Command structure will not be deleted as long as it is + * referenced from a CmdName object. + */ + + cmd = Tcl_FindCommand(interp, name, (Tcl_Namespace *) NULL, + /*flags*/ 0); + cmdPtr = (Command *) cmd; + if (cmdPtr != NULL) { + /* + * Get the current namespace. + */ + + if (iPtr->varFramePtr != NULL) { + currNsPtr = iPtr->varFramePtr->nsPtr; + } else { + currNsPtr = iPtr->globalNsPtr; + } + + cmdPtr->refCount++; + resPtr = (ResolvedCmdName *) ckalloc(sizeof(ResolvedCmdName)); + resPtr->cmdPtr = cmdPtr; + resPtr->refNsPtr = currNsPtr; + resPtr->refNsId = currNsPtr->nsId; + resPtr->refNsCmdEpoch = currNsPtr->cmdRefEpoch; + resPtr->cmdEpoch = cmdPtr->cmdEpoch; + resPtr->refCount = 1; + } else { + resPtr = NULL; /* no command named "name" was found */ + } + + /* + * Free the old internalRep before setting the new one. We do this as + * late as possible to allow the conversion code, in particular + * GetStringFromObj, to use that old internalRep. If no Command + * structure was found, leave NULL as the cached value. + */ + + if ((objPtr->typePtr != NULL) + && (objPtr->typePtr->freeIntRepProc != NULL)) { + objPtr->typePtr->freeIntRepProc(objPtr); + } + + objPtr->internalRep.twoPtrValue.ptr1 = (VOID *) resPtr; + objPtr->internalRep.twoPtrValue.ptr2 = NULL; + objPtr->typePtr = &tclCmdNameType; + return TCL_OK; +} + +#ifdef TCL_COMPILE_DEBUG +/* + *---------------------------------------------------------------------- + * + * StringForResultCode -- + * + * Procedure that returns a human-readable string representing a + * Tcl result code such as TCL_ERROR. + * + * Results: + * If the result code is one of the standard Tcl return codes, the + * result is a string representing that code such as "TCL_ERROR". + * Otherwise, the result string is that code formatted as a + * sequence of decimal digit characters. Note that the resulting + * string must not be modified by the caller. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static char * +StringForResultCode(result) + int result; /* The Tcl result code for which to + * generate a string. */ +{ + static char buf[TCL_INTEGER_SPACE]; + + if ((result >= TCL_OK) && (result <= TCL_CONTINUE)) { + return resultStrings[result]; + } + TclFormatInt(buf, result); + return buf; +} +#endif /* TCL_COMPILE_DEBUG */ |