From 9571165813914daaf16bd8fb71b4f97e7affa1d6 Mon Sep 17 00:00:00 2001 From: dkf Date: Fri, 10 May 2013 12:57:38 +0000 Subject: Optimizations and general bytecode generation improvements. --- ChangeLog | 13 +++ generic/tclAssembly.c | 3 +- generic/tclCompCmds.c | 199 +++++++++++++++++++++++++++++----- generic/tclCompile.c | 288 +++++++++++++++++++++++++++++++++++++++++++++++--- generic/tclCompile.h | 36 ++++++- generic/tclExecute.c | 62 +++++++++-- 6 files changed, 548 insertions(+), 53 deletions(-) diff --git a/ChangeLog b/ChangeLog index 6330666..bcd089d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,16 @@ +2013-05-10 Donal K. Fellows + + Optimizations and general bytecode generation improvements. + * generic/tclCompCmds.c (TclCompileAppendCmd, TclCompileLappendCmd): + (TclCompileReturnCmd): Make these generate bytecode in more cases. + (TclCompileListCmd): Make this able to push a literal when it can. + * generic/tclCompile.c (TclSetByteCodeFromAny, PeepholeOptimize): + Added checks to see if we can apply some simple cross-command-boundary + optimizations, and defined a small number of such optimizations. + (TclCompileScript): Added the special ability to compile the list + command with expansion ([list {*}blah]) into bytecode that does not + call an external command. + 2013-05-06 Jan Nijtmans * generic/tclStubInit.c: Add support for Cygwin64, which has a 64-bit diff --git a/generic/tclAssembly.c b/generic/tclAssembly.c index 5786975..cd2ad13 100644 --- a/generic/tclAssembly.c +++ b/generic/tclAssembly.c @@ -20,7 +20,7 @@ *- break and continue - if exception ranges can be sorted out. *- foreach_start4, foreach_step4 *- returnImm, returnStk - *- expandStart, expandStkTop, invokeExpanded + *- expandStart, expandStkTop, invokeExpanded, listExpanded *- dictFirst, dictNext, dictDone *- dictUpdateStart, dictUpdateEnd *- jumpTable testing @@ -437,6 +437,7 @@ static const TalInstDesc TalInstructionTable[] = { {"lindexMulti", ASSEM_LINDEX_MULTI, INST_LIST_INDEX_MULTI, INT_MIN,1}, {"list", ASSEM_LIST, INST_LIST, INT_MIN,1}, + {"listConcat", ASSEM_1BYTE, INST_LIST_CONCAT, 2, 1}, {"listIn", ASSEM_1BYTE, INST_LIST_IN, 2, 1}, {"listIndex", ASSEM_1BYTE, INST_LIST_INDEX, 2, 1}, {"listIndexImm", ASSEM_INDEX, INST_LIST_INDEX_IMM, 1, 1}, diff --git a/generic/tclCompCmds.c b/generic/tclCompCmds.c index f6ca0e0..9ffdbc3 100644 --- a/generic/tclCompCmds.c +++ b/generic/tclCompCmds.c @@ -155,7 +155,7 @@ TclCompileAppendCmd( CompileEnv *envPtr) /* Holds resulting instructions. */ { Tcl_Token *varTokenPtr, *valueTokenPtr; - int simpleVarName, isScalar, localIndex, numWords; + int simpleVarName, isScalar, localIndex, numWords, i; DefineLineInformation; /* TIP #280 */ numWords = parsePtr->numWords; @@ -169,10 +169,11 @@ TclCompileAppendCmd( return TclCompileSetCmd(interp, parsePtr, cmdPtr, envPtr); } else if (numWords > 3) { /* - * APPEND instructions currently only handle one value. + * APPEND instructions currently only handle one value, but we can + * handle some multi-value cases by stringing them together. */ - return TCL_ERROR; + goto appendMultiple; } /* @@ -222,6 +223,42 @@ TclCompileAppendCmd( } return TCL_OK; + + appendMultiple: + /* + * Can only handle the case where we are appending to a local scalar when + * there are multiple values to append. Fortunately, this is common. + */ + + if (envPtr->procPtr == NULL) { + return TCL_ERROR; + } + varTokenPtr = TokenAfter(parsePtr->tokenPtr); + PushVarNameWord(interp, varTokenPtr, envPtr, TCL_NO_ELEMENT, + &localIndex, &simpleVarName, &isScalar, 1); + if (!isScalar || localIndex < 0) { + return TCL_ERROR; + } + + /* + * Definitely appending to a local scalar; generate the words and append + * them. + */ + + valueTokenPtr = TokenAfter(varTokenPtr); + for (i = 2 ; i < numWords ; i++) { + CompileWord(envPtr, valueTokenPtr, interp, i); + valueTokenPtr = TokenAfter(valueTokenPtr); + } + TclEmitInstInt4( INST_REVERSE, numWords-2, envPtr); + for (i = 2 ; i < numWords ;) { + Emit14Inst( INST_APPEND_SCALAR, localIndex, envPtr); + if (++i < numWords) { + TclEmitOpcode(INST_POP, envPtr); + } + } + + return TCL_OK; } /* @@ -4067,8 +4104,8 @@ TclCompileLappendCmd( * compiled. */ CompileEnv *envPtr) /* Holds resulting instructions. */ { - Tcl_Token *varTokenPtr; - int simpleVarName, isScalar, localIndex, numWords; + Tcl_Token *varTokenPtr, *valueTokenPtr; + int simpleVarName, isScalar, localIndex, numWords, i, fwd, offsetFwd; DefineLineInformation; /* TIP #280 */ /* @@ -4085,10 +4122,11 @@ TclCompileLappendCmd( } if (numWords != 3) { /* - * LAPPEND instructions currently only handle one value appends. + * LAPPEND instructions currently only handle one value, but we can + * handle some multi-value cases by stringing them together. */ - return TCL_ERROR; + goto lappendMultiple; } /* @@ -4141,6 +4179,45 @@ TclCompileLappendCmd( } return TCL_OK; + + lappendMultiple: + /* + * Can only handle the case where we are appending to a local scalar when + * there are multiple values to append. Fortunately, this is common. + */ + + if (envPtr->procPtr == NULL) { + return TCL_ERROR; + } + varTokenPtr = TokenAfter(parsePtr->tokenPtr); + PushVarNameWord(interp, varTokenPtr, envPtr, TCL_NO_ELEMENT, + &localIndex, &simpleVarName, &isScalar, 1); + if (!isScalar || localIndex < 0) { + return TCL_ERROR; + } + + /* + * Definitely appending to a local scalar; generate the words and append + * them. + */ + + valueTokenPtr = TokenAfter(varTokenPtr); + for (i = 2 ; i < numWords ; i++) { + CompileWord(envPtr, valueTokenPtr, interp, i); + valueTokenPtr = TokenAfter(valueTokenPtr); + } + TclEmitInstInt4( INST_LIST, numWords-2, envPtr); + TclEmitInstInt4( INST_EXIST_SCALAR, localIndex, envPtr); + offsetFwd = CurrentOffset(envPtr); + TclEmitInstInt1( INST_JUMP_FALSE1, 0, envPtr); + Emit14Inst( INST_LOAD_SCALAR, localIndex, envPtr); + TclEmitInstInt4( INST_REVERSE, 2, envPtr); + TclEmitOpcode( INST_LIST_CONCAT, envPtr); + fwd = CurrentOffset(envPtr) - offsetFwd; + TclStoreInt1AtPtr(fwd, envPtr->codeStart+offsetFwd+1); + Emit14Inst( INST_STORE_SCALAR, localIndex, envPtr); + + return TCL_OK; } /* @@ -4390,14 +4467,7 @@ TclCompileListCmd( DefineLineInformation; /* TIP #280 */ Tcl_Token *valueTokenPtr; int i, numWords; - - /* - * If we're not in a procedure, don't compile. - */ - - if (envPtr->procPtr == NULL) { - return TCL_ERROR; - } + Tcl_Obj *listObj, *objPtr; if (parsePtr->numWords == 1) { /* @@ -4405,20 +4475,57 @@ TclCompileListCmd( */ PushLiteral(envPtr, "", 0); - } else { - /* - * Push the all values onto the stack. - */ + return TCL_OK; + } + + /* + * Test if all arguments are compile-time known. If they are, we can + * implement with a simple push. + */ - numWords = parsePtr->numWords; - valueTokenPtr = TokenAfter(parsePtr->tokenPtr); - for (i = 1; i < numWords; i++) { - CompileWord(envPtr, valueTokenPtr, interp, i); - valueTokenPtr = TokenAfter(valueTokenPtr); + numWords = parsePtr->numWords; + valueTokenPtr = TokenAfter(parsePtr->tokenPtr); + listObj = Tcl_NewObj(); + for (i = 1; i < numWords && listObj != NULL; i++) { + objPtr = Tcl_NewObj(); + if (TclWordKnownAtCompileTime(valueTokenPtr, objPtr)) { + (void) Tcl_ListObjAppendElement(NULL, listObj, objPtr); + } else { + Tcl_DecrRefCount(objPtr); + Tcl_DecrRefCount(listObj); + listObj = NULL; } - TclEmitInstInt4( INST_LIST, numWords - 1, envPtr); + valueTokenPtr = TokenAfter(valueTokenPtr); } + if (listObj != NULL) { + int len; + const char *bytes = Tcl_GetStringFromObj(listObj, &len); + PushLiteral(envPtr, bytes, len); + Tcl_DecrRefCount(listObj); + if (len > 0) { + /* + * Force list interpretation! + */ + + TclEmitOpcode( INST_DUP, envPtr); + TclEmitOpcode( INST_LIST_LENGTH, envPtr); + TclEmitOpcode( INST_POP, envPtr); + } + return TCL_OK; + } + + /* + * Push the all values onto the stack. + */ + + numWords = parsePtr->numWords; + valueTokenPtr = TokenAfter(parsePtr->tokenPtr); + for (i = 1; i < numWords; i++) { + CompileWord(envPtr, valueTokenPtr, interp, i); + valueTokenPtr = TokenAfter(valueTokenPtr); + } + TclEmitInstInt4( INST_LIST, numWords - 1, envPtr); return TCL_OK; } @@ -5578,15 +5685,20 @@ TclCompileReturnCmd( objv[objc] = Tcl_NewObj(); Tcl_IncrRefCount(objv[objc]); if (!TclWordKnownAtCompileTime(wordTokenPtr, objv[objc])) { - objc++; - status = TCL_ERROR; - goto cleanup; + /* + * Non-literal, so punt to run-time. + */ + + for (; objc>=0 ; objc--) { + TclDecrRefCount(objv[objc]); + } + TclStackFree(interp, objv); + goto issueRuntimeReturn; } wordTokenPtr = TokenAfter(wordTokenPtr); } status = TclMergeReturnOptions(interp, objc, objv, &returnOpts, &code, &level); - cleanup: while (--objc >= 0) { TclDecrRefCount(objv[objc]); } @@ -5667,6 +5779,35 @@ TclCompileReturnCmd( CompileReturnInternal(envPtr, INST_RETURN_IMM, code, level, returnOpts); return TCL_OK; + + issueRuntimeReturn: + /* + * Assemble the option dictionary (as a list as that's good enough). + */ + + wordTokenPtr = TokenAfter(parsePtr->tokenPtr); + for (objc=1 ; objc<=numOptionWords ; objc++) { + CompileWord(envPtr, wordTokenPtr, interp, objc); + wordTokenPtr = TokenAfter(wordTokenPtr); + } + TclEmitInstInt4(INST_LIST, numOptionWords, envPtr); + + /* + * Push the result. + */ + + if (explicitResult) { + CompileWord(envPtr, wordTokenPtr, interp, numWords-1); + } else { + PushLiteral(envPtr, "", 0); + } + + /* + * Issue the RETURN itself. + */ + + TclEmitOpcode(INST_RETURN_STK, envPtr); + return TCL_OK; } static void diff --git a/generic/tclCompile.c b/generic/tclCompile.c index 0e98385..1da6e03 100644 --- a/generic/tclCompile.c +++ b/generic/tclCompile.c @@ -14,6 +14,7 @@ #include "tclInt.h" #include "tclCompile.h" +#include /* * Table of all AuxData types. @@ -50,7 +51,7 @@ static int traceInitialized = 0; * existence of a procedure call frame to distinguish these. */ -InstructionDesc const tclInstructionTable[] = { +const InstructionDesc const tclInstructionTable[] = { /* Name Bytes stackEffect #Opnds Operand types */ {"done", 1, -1, 0, {OPERAND_NONE}}, /* Finish ByteCode execution and return stktop (top stack item) */ @@ -279,12 +280,12 @@ InstructionDesc const tclInstructionTable[] = { /* Binary exponentiation operator: push (stknext ** stktop) */ /* - * NOTE: the stack effects of expandStkTop and invokeExpanded are wrong - - * but it cannot be done right at compile time, the stack effect is only - * known at run time. The value for invokeExpanded is estimated better at - * compile time. + * NOTE: the stack effects of expandStkTop, invokeExpanded and + * listExpanded are wrong - but it cannot be done right at compile time, + * the stack effect is only known at run time. The value for both + * invokeExpanded and listExpanded are estimated better at compile time. * See the comments further down in this file, where INST_INVOKE_EXPANDED - * is emitted. + * and INST_LIST_EXPANDED are emitted. */ {"expandStart", 1, 0, 0, {OPERAND_NONE}}, /* Start of command with {*} (expanded) arguments */ @@ -534,6 +535,13 @@ InstructionDesc const tclInstructionTable[] = { * the word at the top of the stack; * = */ + {"listConcat", 1, -1, 0, {OPERAND_NONE}}, + /* Concatenates the two lists at the top of the stack into a single + * list and pushes that resulting list onto the stack. + * Stack: ... list1 list2 => ... [lconcat list1 list2] */ + {"listExpanded", 1, 0, 0, {OPERAND_NONE}}, + /* Construct a list from the words marked by the last 'expandStart' */ + {NULL, 0, 0, 0, {OPERAND_NONE}} }; @@ -554,6 +562,9 @@ static void EnterCmdStartData(CompileEnv *envPtr, static void FreeByteCodeInternalRep(Tcl_Obj *objPtr); static void FreeSubstCodeInternalRep(Tcl_Obj *objPtr); static int GetCmdLocEncodingSize(CompileEnv *envPtr); +static int IsCompactibleCompileEnv(Tcl_Interp *interp, + CompileEnv *envPtr); +static void PeepholeOptimize(CompileEnv *envPtr); #ifdef TCL_COMPILE_STATS static void RecordByteCodeStats(ByteCode *codePtr); #endif /* TCL_COMPILE_STATS */ @@ -654,6 +665,7 @@ TclSetByteCodeFromAny( * in frame. */ int length, result = TCL_OK; const char *stringPtr; + Proc *procPtr = iPtr->compiledProcPtr; ContLineLoc *clLocPtr; #ifdef TCL_COMPILE_DEBUG @@ -705,6 +717,38 @@ TclSetByteCodeFromAny( TclEmitOpcode(INST_DONE, &compEnv); /* + * Check for optimizations! + * + * Test if the generated code is free of most hazards; if so, recompile + * but with generation of INST_START_CMD disabled. This produces somewhat + * faster code in some cases, and more compact code in more. + */ + + if (Tcl_GetMaster(interp) == NULL && + !Tcl_LimitTypeEnabled(interp, TCL_LIMIT_COMMANDS|TCL_LIMIT_TIME) + && IsCompactibleCompileEnv(interp, &compEnv)) { + TclFreeCompileEnv(&compEnv); + iPtr->compiledProcPtr = procPtr; + TclInitCompileEnv(interp, &compEnv, stringPtr, length, + iPtr->invokeCmdFramePtr, iPtr->invokeWord); + if (clLocPtr) { + compEnv.clLoc = clLocPtr; + compEnv.clNext = &compEnv.clLoc->loc[0]; + Tcl_Preserve(compEnv.clLoc); + } + compEnv.atCmdStart = 2; /* The disabling magic. */ + TclCompileScript(interp, stringPtr, length, &compEnv); + TclEmitOpcode(INST_DONE, &compEnv); + } + + /* + * Apply some peephole optimizations that can cross specific/generic + * instruction generator boundaries. + */ + + PeepholeOptimize(&compEnv); + + /* * Invoke the compilation hook procedure if one exists. */ @@ -973,6 +1017,202 @@ TclCleanupByteCode( } /* + * --------------------------------------------------------------------- + * + * IsCompactibleCompileEnv -- + * + * Checks to see if we may apply some basic compaction optimizations to a + * piece of bytecode. Idempotent. + * + * --------------------------------------------------------------------- + */ + +static int +IsCompactibleCompileEnv( + Tcl_Interp *interp, + CompileEnv *envPtr) +{ + unsigned char *pc; + int size; + + /* + * Special: procedures in the '::tcl' namespace (or its children) are + * considered to be well-behaved and so can have compaction applied even + * if it would otherwise be invalid. + */ + + if (envPtr->procPtr != NULL && envPtr->procPtr->cmdPtr != NULL + && envPtr->procPtr->cmdPtr->nsPtr != NULL) { + Namespace *nsPtr = envPtr->procPtr->cmdPtr->nsPtr; + + if (strcmp(nsPtr->fullName, "::tcl") == 0 + || strncmp(nsPtr->fullName, "::tcl::", 7) == 0) { + return 1; + } + } + + /* + * Go through and ensure that no operation involved can cause a desired + * change of bytecode sequence during running. This comes down to ensuring + * that there are no mapped variables (due to traces) or calls to external + * commands (traces, [uplevel] trickery). This is actually a very + * conservative check; it turns down a lot of code that is OK in practice. + */ + + for (pc = envPtr->codeStart ; pc < envPtr->codeNext ; pc += size) { + switch (*pc) { + /* Invokes */ + case INST_INVOKE_STK1: + case INST_INVOKE_STK4: + case INST_INVOKE_EXPANDED: + case INST_INVOKE_REPLACE: + return 0; + /* Runtime evals */ + case INST_EVAL_STK: + case INST_EXPR_STK: + case INST_YIELD: + return 0; + /* Upvars */ + case INST_UPVAR: + case INST_NSUPVAR: + case INST_VARIABLE: + return 0; + } + size = tclInstructionTable[*pc].numBytes; + assert (size > 0); + } + + return 1; +} + +/* + * ---------------------------------------------------------------------- + * + * PeepholeOptimize -- + * + * A very simple peephole optimizer for bytecode. + * + * ---------------------------------------------------------------------- + */ + +static void +PeepholeOptimize( + CompileEnv *envPtr) +{ + unsigned char *pc, *prev1 = NULL, *prev2 = NULL, *target; + int size, isNew; + Tcl_HashTable targets; + Tcl_HashEntry *hPtr; + Tcl_HashSearch hSearch; + + /* + * Find places where we should be careful about replacing instructions + * because they are the targets of various types of jumps. + */ + + Tcl_InitHashTable(&targets, TCL_ONE_WORD_KEYS); + for (pc = envPtr->codeStart ; pc < envPtr->codeNext ; pc += size) { + size = tclInstructionTable[*pc].numBytes; + switch (*pc) { + case INST_JUMP1: + case INST_JUMP_TRUE1: + case INST_JUMP_FALSE1: + target = pc + TclGetInt1AtPtr(pc+1); + goto storeTarget; + case INST_JUMP4: + case INST_JUMP_TRUE4: + case INST_JUMP_FALSE4: + target = pc + TclGetInt4AtPtr(pc+1); + goto storeTarget; + case INST_BEGIN_CATCH4: + target = envPtr->codeStart + envPtr->exceptArrayPtr[ + TclGetUInt4AtPtr(pc+1)].codeOffset; + storeTarget: + (void) Tcl_CreateHashEntry(&targets, (void *) target, &isNew); + break; + case INST_JUMP_TABLE: + hPtr = Tcl_FirstHashEntry( + &JUMPTABLEINFO(envPtr, pc+1)->hashTable, &hSearch); + for (; hPtr ; hPtr = Tcl_NextHashEntry(&hSearch)) { + target = pc + (int) Tcl_GetHashValue(hPtr); + (void) Tcl_CreateHashEntry(&targets, (void *) target, &isNew); + } + break; + } + } + + /* + * Replace PUSH/POP sequences (when non-hazardous) with NOPs. + */ + + (void) Tcl_CreateHashEntry(&targets, (void *) pc, &isNew); + for (pc = envPtr->codeStart ; pc < envPtr->codeNext ; pc += size) { + int blank = 0, i; + + size = tclInstructionTable[*pc].numBytes; + prev2 = prev1; + prev1 = pc; + if (Tcl_FindHashEntry(&targets, (void *) (pc + size))) { + continue; + } + switch (*pc) { + case INST_PUSH1: + while (*(pc+size) == INST_NOP) { + size++; + } + if (*(pc+size) == INST_POP) { + blank = size + 1; + } else if (*(pc+size) == INST_CONCAT1 + && TclGetUInt1AtPtr(pc + size + 1) == 2) { + Tcl_Obj *litPtr = TclFetchLiteral(envPtr, + TclGetUInt1AtPtr(pc + 1)); + int numBytes; + + (void) Tcl_GetStringFromObj(litPtr, &numBytes); + if (numBytes == 0) { + blank = size + 2; + } + } + break; + case INST_PUSH4: + while (*(pc+size) == INST_NOP) { + size++; + } + if (*(pc+size) == INST_POP) { + blank = size + 1; + } else if (*(pc+size) == INST_CONCAT1 + && TclGetUInt1AtPtr(pc + size + 1) == 2) { + Tcl_Obj *litPtr = TclFetchLiteral(envPtr, + TclGetUInt4AtPtr(pc + 1)); + int numBytes; + + (void) Tcl_GetStringFromObj(litPtr, &numBytes); + if (numBytes == 0) { + blank = size + 2; + } + } + break; + } + if (blank > 0) { + for (i=0 ; icodeNext--; + } + Tcl_DeleteHashTable(&targets); +} + +/* *---------------------------------------------------------------------- * * Tcl_SubstObj -- @@ -1194,6 +1434,8 @@ TclInitCompileEnv( { Interp *iPtr = (Interp *) interp; + assert(tclInstructionTable[LAST_INST_OPCODE].name == NULL); + envPtr->iPtr = iPtr; envPtr->source = stringPtr; envPtr->numSrcBytes = numBytes; @@ -1689,7 +1931,7 @@ TclCompileScript( wordIdx < parsePtr->numWords; wordIdx++, tokenPtr += tokenPtr->numComponents + 1) { if (tokenPtr->type == TCL_TOKEN_EXPAND_WORD) { - expand = 1; + expand = INST_INVOKE_EXPANDED; break; } } @@ -1802,7 +2044,7 @@ TclCompileScript( * command. */ - if (envPtr->atCmdStart) { + if (envPtr->atCmdStart == 1) { if (savedCodeNext != 0) { /* * Increase the number of commands being @@ -1816,7 +2058,7 @@ TclCompileScript( TclStoreInt4AtPtr(TclGetUInt4AtPtr(fixPtr)+1, fixPtr); } - } else { + } else if (envPtr->atCmdStart == 0) { TclEmitInstInt4(INST_START_CMD, 0, envPtr); TclEmitInt4(1, envPtr); update = 1; @@ -1860,7 +2102,7 @@ TclCompileScript( goto finishCommand; } - if (envPtr->atCmdStart && savedCodeNext != 0) { + if (envPtr->atCmdStart == 1 && savedCodeNext != 0) { /* * Decrease the number of commands being started * at the current point. Note that this depends on @@ -1899,6 +2141,25 @@ TclCompileScript( TclFetchLiteral(envPtr, objIndex), cmdPtr); } } else { + if (wordIdx == 0 && expand) { + TclDStringClear(&ds); + TclDStringAppendToken(&ds, &tokenPtr[1]); + cmdPtr = (Command *) Tcl_FindCommand(interp, + Tcl_DStringValue(&ds), + (Tcl_Namespace *) cmdNsPtr, /*flags*/ 0); + if ((cmdPtr != NULL) && + (cmdPtr->compileProc == TclCompileListCmd)) { + /* + * Special case! [list] command can be expanded + * directly provided the first word is not the + * expanded one. + */ + + expand = INST_LIST_EXPANDED; + continue; + } + } + /* * Simple argument word of a command. We reach this if and * only if the command word was not compiled for whatever @@ -1941,9 +2202,12 @@ TclCompileScript( * Note that the estimates are not correct while the command * is being prepared and run, INST_EXPAND_STKTOP is not * stack-neutral in general. + * + * The opcodes that may be issued here (both assumed to be + * non-zero) are INST_INVOKE_EXPANDED and INST_LIST_EXPANDED. */ - TclEmitOpcode(INST_INVOKE_EXPANDED, envPtr); + TclEmitOpcode(expand, envPtr); TclAdjustStackDepth((1-wordIdx), envPtr); } else if (wordIdx > 0) { /* @@ -3692,7 +3956,7 @@ TclInitAuxDataTypeTable(void) Tcl_InitHashTable(&auxDataTypeTable, TCL_STRING_KEYS); /* - * There are only two AuxData type at this time, so register them here. + * There are only three AuxData types at this time, so register them here. */ RegisterAuxDataType(&tclForeachInfoType); diff --git a/generic/tclCompile.h b/generic/tclCompile.h index 79497d2..c68d3ec 100644 --- a/generic/tclCompile.h +++ b/generic/tclCompile.h @@ -309,7 +309,9 @@ typedef struct CompileEnv { int atCmdStart; /* Flag to say whether an INST_START_CMD * should be issued; they should never be * issued repeatedly, as that is significantly - * inefficient. */ + * inefficient. If set to 2, that instruction + * should not be issued at all (by the generic + * part of the command compiler). */ ContLineLoc *clLoc; /* If not NULL, the table holding the * locations of the invisible continuation * lines in the input script, to adjust the @@ -713,8 +715,11 @@ typedef struct ByteCode { #define INST_INVOKE_REPLACE 163 +#define INST_LIST_CONCAT 164 +#define INST_LIST_EXPANDED 165 + /* The last opcode */ -#define LAST_INST_OPCODE 163 +#define LAST_INST_OPCODE 165 /* * Table describing the Tcl bytecode instructions: their name (for displaying @@ -848,6 +853,9 @@ typedef struct ForeachInfo { MODULE_SCOPE const AuxDataType tclForeachInfoType; +#define FOREACHINFO(envPtr, index) \ + ((ForeachInfo*)((envPtr)->auxDataArrayPtr[TclGetUInt4AtPtr(index)].clientData)) + /* * Structure used to hold information about a switch command that is needed * during program execution. These structures are stored in CompileEnv and @@ -861,6 +869,9 @@ typedef struct JumptableInfo { MODULE_SCOPE const AuxDataType tclJumptableInfoType; +#define JUMPTABLEINFO(envPtr, index) \ + ((JumptableInfo*)((envPtr)->auxDataArrayPtr[TclGetUInt4AtPtr(index)].clientData)) + /* * Structure used to hold information about a [dict update] command that is * needed during program execution. These structures are stored in CompileEnv @@ -879,6 +890,9 @@ typedef struct { MODULE_SCOPE const AuxDataType tclDictUpdateInfoType; +#define DICTUPDATEINFO(envPtr, index) \ + ((DictUpdateInfo*)((envPtr)->auxDataArrayPtr[TclGetUInt4AtPtr(index)].clientData)) + /* * ClientData type used by the math operator commands. */ @@ -1090,6 +1104,18 @@ MODULE_SCOPE Tcl_Obj *TclNewInstNameObj(unsigned char inst); } while (0) /* + * Macros used to update the flag that indicates if we are at the start of a + * command, based on whether the opcode is INST_START_COMMAND. + * + * void TclUpdateAtCmdStart(unsigned char op, CompileEnv *envPtr); + */ + +#define TclUpdateAtCmdStart(op, envPtr) \ + if ((envPtr)->atCmdStart < 2) { \ + (envPtr)->atCmdStart = ((op) == INST_START_CMD ? 1 : 0); \ + } + +/* * Macro to emit an opcode byte into a CompileEnv's code array. The ANSI C * "prototype" for this macro is: * @@ -1102,7 +1128,7 @@ MODULE_SCOPE Tcl_Obj *TclNewInstNameObj(unsigned char inst); TclExpandCodeArray(envPtr); \ } \ *(envPtr)->codeNext++ = (unsigned char) (op); \ - (envPtr)->atCmdStart = ((op) == INST_START_CMD); \ + TclUpdateAtCmdStart(op, envPtr); \ TclUpdateStackReqs(op, 0, envPtr); \ } while (0) @@ -1154,7 +1180,7 @@ MODULE_SCOPE Tcl_Obj *TclNewInstNameObj(unsigned char inst); } \ *(envPtr)->codeNext++ = (unsigned char) (op); \ *(envPtr)->codeNext++ = (unsigned char) ((unsigned int) (i)); \ - (envPtr)->atCmdStart = ((op) == INST_START_CMD); \ + TclUpdateAtCmdStart(op, envPtr); \ TclUpdateStackReqs(op, i, envPtr); \ } while (0) @@ -1172,7 +1198,7 @@ MODULE_SCOPE Tcl_Obj *TclNewInstNameObj(unsigned char inst); (unsigned char) ((unsigned int) (i) >> 8); \ *(envPtr)->codeNext++ = \ (unsigned char) ((unsigned int) (i) ); \ - (envPtr)->atCmdStart = ((op) == INST_START_CMD); \ + TclUpdateAtCmdStart(op, envPtr); \ TclUpdateStackReqs(op, i, envPtr); \ } while (0) diff --git a/generic/tclExecute.c b/generic/tclExecute.c index 029f402..afde900 100644 --- a/generic/tclExecute.c +++ b/generic/tclExecute.c @@ -2338,6 +2338,14 @@ TEBCresume( } inst = *(pc += 9); goto peepholeStart; + } else if (inst == INST_NOP) { +#ifndef TCL_COMPILE_DEBUG + while (inst == INST_NOP) +#endif + { + inst = *++pc; + } + goto peepholeStart; } switch (inst) { @@ -2369,14 +2377,28 @@ TEBCresume( TRACE(("=> ")); objResultPtr = POP_OBJECT(); result = Tcl_SetReturnOptions(interp, OBJ_AT_TOS); - Tcl_DecrRefCount(OBJ_AT_TOS); - OBJ_AT_TOS = objResultPtr; if (result == TCL_OK) { + Tcl_DecrRefCount(OBJ_AT_TOS); + OBJ_AT_TOS = objResultPtr; TRACE_APPEND(("continuing to next instruction (result=\"%.30s\")", O2S(objResultPtr))); NEXT_INST_F(1, 0, 0); + } else if (result == TCL_ERROR) { + /* + * BEWARE! Must do this in this order, because an error in the + * option dictionary overrides the result (and can be verified by + * test). + */ + + Tcl_SetObjResult(interp, objResultPtr); + Tcl_SetReturnOptions(interp, OBJ_AT_TOS); + Tcl_DecrRefCount(OBJ_AT_TOS); + OBJ_AT_TOS = objResultPtr; + } else { + Tcl_DecrRefCount(OBJ_AT_TOS); + OBJ_AT_TOS = objResultPtr; + Tcl_SetObjResult(interp, objResultPtr); } - Tcl_SetObjResult(interp, objResultPtr); cleanup = 1; goto processExceptionReturn; @@ -2501,9 +2523,6 @@ TEBCresume( TclDecrRefCount(objPtr); NEXT_INST_F(1, 0, 0); - case INST_NOP: - NEXT_INST_F(1, 0, 0); - case INST_DUP: objResultPtr = OBJ_AT_TOS; TRACE_WITH_OBJ(("=> "), objResultPtr); @@ -4418,6 +4437,14 @@ TEBCresume( TRACE_WITH_OBJ(("%u => ", opnd), objResultPtr); NEXT_INST_V(5, opnd, 1); + case INST_LIST_EXPANDED: + CLANG_ASSERT(auxObjList); + objc = CURR_DEPTH - auxObjList->internalRep.ptrAndLongRep.value; + POP_TAUX_OBJ(); + objResultPtr = Tcl_NewListObj(objc, &OBJ_AT_DEPTH(objc-1)); + TRACE_WITH_OBJ(("(%u) => ", objc), objResultPtr); + NEXT_INST_V(1, objc, 1); + case INST_LIST_LENGTH: valuePtr = OBJ_AT_TOS; if (TclListObjLength(interp, valuePtr, &length) != TCL_OK) { @@ -4763,6 +4790,29 @@ TEBCresume( objResultPtr = TCONST(match); NEXT_INST_F(0, 2, 1); + case INST_LIST_CONCAT: + value2Ptr = OBJ_AT_TOS; + valuePtr = OBJ_UNDER_TOS; + TRACE(("\"%.30s\" \"%.30s\" => ", O2S(valuePtr), O2S(value2Ptr))); + if (Tcl_IsShared(valuePtr)) { + objResultPtr = Tcl_DuplicateObj(valuePtr); + if (Tcl_ListObjAppendList(interp, objResultPtr, + value2Ptr) != TCL_OK) { + TRACE_APPEND(("ERROR: %.30s\n", O2S(Tcl_GetObjResult(interp)))); + TclDecrRefCount(objResultPtr); + goto gotError; + } + TRACE_APPEND(("\"%.30s\"\n", O2S(objResultPtr))); + NEXT_INST_F(1, 2, 1); + } else { + if (Tcl_ListObjAppendList(interp, valuePtr, value2Ptr) != TCL_OK){ + TRACE_APPEND(("ERROR: %.30s\n", O2S(Tcl_GetObjResult(interp)))); + goto gotError; + } + TRACE_APPEND(("\"%.30s\"\n", O2S(valuePtr))); + NEXT_INST_F(1, 1, 0); + } + /* * End of INST_LIST and related instructions. * ----------------------------------------------------------------- -- cgit v0.12 From b5dfface8d2bbb92709c3ce349d4f101a4354361 Mon Sep 17 00:00:00 2001 From: dkf Date: Sat, 11 May 2013 20:02:43 +0000 Subject: Partial fix: still ongoing --- generic/tclCompCmds.c | 3 +++ generic/tclCompile.c | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/generic/tclCompCmds.c b/generic/tclCompCmds.c index 9ffdbc3..c2495bd 100644 --- a/generic/tclCompCmds.c +++ b/generic/tclCompCmds.c @@ -5761,6 +5761,7 @@ TclCompileReturnCmd( Tcl_DecrRefCount(returnOpts); TclEmitOpcode(INST_DONE, envPtr); + envPtr->currStackDepth = savedStackDepth; return TCL_OK; } } @@ -5778,6 +5779,7 @@ TclCompileReturnCmd( */ CompileReturnInternal(envPtr, INST_RETURN_IMM, code, level, returnOpts); + envPtr->currStackDepth = savedStackDepth + 1; return TCL_OK; issueRuntimeReturn: @@ -5807,6 +5809,7 @@ TclCompileReturnCmd( */ TclEmitOpcode(INST_RETURN_STK, envPtr); + envPtr->currStackDepth = savedStackDepth + 1; return TCL_OK; } diff --git a/generic/tclCompile.c b/generic/tclCompile.c index 1da6e03..838d801 100644 --- a/generic/tclCompile.c +++ b/generic/tclCompile.c @@ -1434,7 +1434,7 @@ TclInitCompileEnv( { Interp *iPtr = (Interp *) interp; - assert(tclInstructionTable[LAST_INST_OPCODE].name == NULL); + assert(tclInstructionTable[LAST_INST_OPCODE+1].name == NULL); envPtr->iPtr = iPtr; envPtr->source = stringPtr; -- cgit v0.12 From 6b7dccabf9f9804049494bf42d0b72cf5a3f9a90 Mon Sep 17 00:00:00 2001 From: dkf Date: Sun, 12 May 2013 00:04:27 +0000 Subject: Fix implementation of INST_LIST_EXPANDED. --- generic/tclExecute.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/generic/tclExecute.c b/generic/tclExecute.c index afde900..f994ba5 100644 --- a/generic/tclExecute.c +++ b/generic/tclExecute.c @@ -4443,7 +4443,11 @@ TEBCresume( POP_TAUX_OBJ(); objResultPtr = Tcl_NewListObj(objc, &OBJ_AT_DEPTH(objc-1)); TRACE_WITH_OBJ(("(%u) => ", objc), objResultPtr); - NEXT_INST_V(1, objc, 1); + while (objc--) { + valuePtr = POP_OBJECT(); + TclDecrRefCount(valuePtr); + } + NEXT_INST_F(1, 0, 1); case INST_LIST_LENGTH: valuePtr = OBJ_AT_TOS; -- cgit v0.12 From d7477a9621b19997f770d8df75b8a071704973d0 Mon Sep 17 00:00:00 2001 From: dkf Date: Sun, 12 May 2013 00:42:53 +0000 Subject: Corrected the stack balancing in the special [list {*} ] compiler. --- generic/tclCompile.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/generic/tclCompile.c b/generic/tclCompile.c index 838d801..7f6b7d4 100644 --- a/generic/tclCompile.c +++ b/generic/tclCompile.c @@ -1879,6 +1879,13 @@ TclCompileScript( if (parsePtr->numWords > 0) { int expand = 0; /* Set if there are dynamic expansions to * handle */ + int expandIgnoredWords = 0; + /* The number of *apparent* words that we are + * generating code from directly during + * expansion processing. For [list {*}blah] + * expansion, we set this to one because we + * ignore the first word and generate code + * directly. */ /* * If not the first command, pop the previous command's result @@ -2156,6 +2163,7 @@ TclCompileScript( */ expand = INST_LIST_EXPANDED; + expandIgnoredWords = 1; continue; } } @@ -2208,7 +2216,7 @@ TclCompileScript( */ TclEmitOpcode(expand, envPtr); - TclAdjustStackDepth((1-wordIdx), envPtr); + TclAdjustStackDepth(1 + expandIgnoredWords - wordIdx, envPtr); } else if (wordIdx > 0) { /* * Save PC -> command map for the TclArgumentBC* functions. -- cgit v0.12