diff options
author | Miguel Sofer <miguel.sofer@gmail.com> | 2015-11-08 14:58:35 (GMT) |
---|---|---|
committer | Miguel Sofer <miguel.sofer@gmail.com> | 2015-11-08 14:58:35 (GMT) |
commit | ce9f44325016e5a430f94e10093885286ed86ecd (patch) | |
tree | 9803f811ca9e953a5a608a3ab31e1a5132fa3e52 | |
parent | 0cf78463d3239b3b19fd0f38622daed8c6bc3f4c (diff) | |
download | tcl-ce9f44325016e5a430f94e10093885286ed86ecd.zip tcl-ce9f44325016e5a430f94e10093885286ed86ecd.tar.gz tcl-ce9f44325016e5a430f94e10093885286ed86ecd.tar.bz2 |
drop in experimental optimizer; tmp branch as it is buggy
-rw-r--r-- | generic/tclOptimize.c | 1304 |
1 files changed, 910 insertions, 394 deletions
diff --git a/generic/tclOptimize.c b/generic/tclOptimize.c index 2b83b3f..c754472 100644 --- a/generic/tclOptimize.c +++ b/generic/tclOptimize.c @@ -3,40 +3,59 @@ * * This file contains the bytecode optimizer. * - * Copyright (c) 2013 by Donal Fellows. * Copyright (c) 2013 by Miguel Sofer. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ -#include "tclInt.h" #include "tclCompile.h" #include <assert.h> -/* - * Forward declarations. - */ - -static void AdvanceJumps(CompileEnv *envPtr); -static void ConvertZeroEffectToNOP(CompileEnv *envPtr); -static void LocateTargetAddresses(CompileEnv *envPtr, - Tcl_HashTable *tablePtr); -static void TrimUnreachable(CompileEnv *envPtr); -static void CompactCode(CompileEnv *envPtr); +typedef struct optPad { + int codeSize; + int cache; + int *npaths; + int *scratch; + Tcl_HashTable stack; + int first; +} optPad; + +static void markPath(CompileEnv *envPtr, int pc, optPad *padPtr, + int mark); +static int effectivePCInternal(CompileEnv *envPtr, int pc, + optPad *padPtr, int start); +static void ThreadJumps(CompileEnv *envPtr, optPad *padPtr); +static int UpdateJump(CompileEnv *envPtr,optPad *padPtr, int pc); +static int optimizePush_0(CompileEnv *envPtr,optPad *padPtr, int pc); +static int optimizePush_1(CompileEnv *envPtr,optPad *padPtr, int pc); +static void MoveUnreachable(CompileEnv *envPtr, optPad *padPtr); +static void CompactCode(CompileEnv *envPtr, optPad *padPtr, + int shrinkInst); +static void Optimize_0(CompileEnv *envPtr, optPad *padPtr); +static void Optimize_1(CompileEnv *envPtr, optPad *padPtr); + +#define INIT_SIZE \ + int codeSize = padPtr->codeSize + +#define INIT_PATHS \ + int *PATHS = padPtr->npaths + +#define INIT_STACK \ + Tcl_HashTable *stackPtr = &padPtr->stack /* * Helper macros. */ -#define DefineTargetAddress(tablePtr, address) \ - ((void) Tcl_CreateHashEntry((tablePtr), (void *) (address), &isNew)) -#define IsTargetAddress(tablePtr, address) \ - (Tcl_FindHashEntry((tablePtr), (void *) (address)) != NULL) #define AddrLength(address) \ (tclInstructionTable[*(unsigned char *)(address)].numBytes) #define InstLength(instruction) \ (tclInstructionTable[(unsigned char)(instruction)].numBytes) +#define InstEffect(instruction) \ + (tclInstructionTable[(unsigned char)(instruction)].stackEffect) +#define Op1Type(instruction) \ + (tclInstructionTable[(unsigned char)(instruction)].opTypes[0]) /* * Macros used in the code compactor. @@ -45,16 +64,16 @@ static void CompactCode(CompileEnv *envPtr); #define GET_INT1_AT_PC(pc) \ TclGetInt1AtPtr(envPtr->codeStart + (pc)) -#define GET_INT4_AT_PC(pc) \ +#define GET_INT4_AT_PC(pc) \ TclGetInt4AtPtr(envPtr->codeStart + (pc)) -#define GET_UINT1_AT_PC(pc) \ +#define GET_UINT1_AT_PC(pc) \ TclGetUInt1AtPtr(envPtr->codeStart + (pc)) #define GET_UINT4_AT_PC(pc) \ TclGetUInt4AtPtr(envPtr->codeStart + (pc)) -#define SET_INT1_AT_PC(i, pc) \ +#define SET_INT1_AT_PC(i, pc) \ TclStoreInt1AtPtr((i), envPtr->codeStart + (pc)) #define SET_INT4_AT_PC(i, pc) \ @@ -65,395 +84,296 @@ static void CompactCode(CompileEnv *envPtr); #define NEXT_PC(pc) \ pc + InstLength(INST_AT_PC(pc)) + +#define MARK(pc) \ + markPath(envPtr, (pc), padPtr, /* mark */ 1) + +#define UNMARK(pc) \ + markPath(envPtr, (pc), padPtr, /* mark */ 0) + +#define REPLACE(old, new) \ + MARK(new); UNMARK(old) + +#define UPDATE_JUMP(pc) \ + UpdateJump(envPtr, padPtr, (pc)) + +#define UNSHARED(pc) (PATHS[pc] == 1) + + +/* + * The code for following a path from a given PC. + */ + +#define FOLLOW(pc) \ + effectivePC(envPtr, (pc), padPtr) + +static inline int +effectivePC( + CompileEnv *envPtr, + int pc, + optPad *padPtr) +{ + unsigned char inst = INST_AT_PC(pc); + + if ((inst == INST_NOP) + || (inst == INST_JUMP1) + || (inst == INST_JUMP4)) { + /* do update the whole path forward */ + pc = effectivePCInternal(envPtr, pc, padPtr, pc); + } + return pc; +} + + +static void Initialize(CompileEnv *envPtr, optPad *padPtr); + /* * ---------------------------------------------------------------------- * - * LocateTargetAddresses -- + * OptimizeBytecode -- * - * Populate a hash table with places that we need to be careful around - * because they're the targets of various kinds of jumps and other - * non-local behavior. + * An optimizer for bytecode to replace TclOptimizeBytecode. * * ---------------------------------------------------------------------- */ -static void -LocateTargetAddresses( - CompileEnv *envPtr, - Tcl_HashTable *tablePtr) +void +TclOptimizeBytecode( + void *env1Ptr) { - unsigned char *currentInstPtr, *targetInstPtr; - int isNew, i; - Tcl_HashEntry *hPtr; - Tcl_HashSearch hSearch; + CompileEnv *envPtr = (CompileEnv *) env1Ptr; + int codeSize = (envPtr->codeNext - envPtr->codeStart); + int padSize = sizeof(optPad) + 4*codeSize*sizeof(int); + optPad *padPtr; + + padPtr = (optPad *) Tcl_AttemptAlloc(padSize); + if (!padPtr) { + /* Not enough memory to optimize this code */ + Tcl_Panic("** Not enough mem to optimize! **"); + return; + } + padPtr->codeSize = codeSize; + padPtr->cache = -1; + padPtr->npaths = &padPtr->first; + padPtr->scratch = &padPtr->npaths[2*codeSize + 1]; - Tcl_InitHashTable(tablePtr, TCL_ONE_WORD_KEYS); + Tcl_InitHashTable(&padPtr->stack, TCL_ONE_WORD_KEYS); - /* - * The starts of commands represent target addresses. - */ + /* Simplify the code as much as possible without knowing the paths */ - for (i=0 ; i<envPtr->numCommands ; i++) { - DefineTargetAddress(tablePtr, - envPtr->codeStart + envPtr->cmdMapPtr[i].codeOffset); - } + /* 1. Path-independent opts: push elimination, jump threading, etc. We + * could advance some jumps (to cond-jump, to done or return) */ - /* - * Find places where we should be careful about replacing instructions - * because they are the targets of various types of jumps. - */ + Optimize_0(envPtr, padPtr); + + /* 2. Initial path marking, move unreachable code to after INST_DONE and + * compress */ - for (currentInstPtr = envPtr->codeStart ; - currentInstPtr < envPtr->codeNext ; - currentInstPtr += AddrLength(currentInstPtr)) { - switch (*currentInstPtr) { - case INST_JUMP1: - case INST_JUMP_TRUE1: - case INST_JUMP_FALSE1: - targetInstPtr = currentInstPtr+TclGetInt1AtPtr(currentInstPtr+1); - goto storeTarget; - case INST_JUMP4: - case INST_JUMP_TRUE4: - case INST_JUMP_FALSE4: - case INST_START_CMD: - targetInstPtr = currentInstPtr+TclGetInt4AtPtr(currentInstPtr+1); - goto storeTarget; - case INST_BEGIN_CATCH4: - targetInstPtr = envPtr->codeStart + envPtr->exceptArrayPtr[ - TclGetUInt4AtPtr(currentInstPtr+1)].codeOffset; - storeTarget: - DefineTargetAddress(tablePtr, targetInstPtr); - break; - case INST_JUMP_TABLE: - hPtr = Tcl_FirstHashEntry( - &JUMPTABLEINFO(envPtr, currentInstPtr+1)->hashTable, - &hSearch); - for (; hPtr ; hPtr = Tcl_NextHashEntry(&hSearch)) { - targetInstPtr = currentInstPtr + - PTR2INT(Tcl_GetHashValue(hPtr)); - DefineTargetAddress(tablePtr, targetInstPtr); - } - break; - case INST_RETURN_CODE_BRANCH: - for (i=TCL_ERROR ; i<TCL_CONTINUE+1 ; i++) { - DefineTargetAddress(tablePtr, currentInstPtr + 2*i - 1); - } - break; - } - } + Initialize(envPtr, padPtr); + /* 3. Iterate path-dependent optimizations until all done; this can + * conceivably enable new path-independent opts, so try again. Note that + * paths need to be kept up-to-date for this to work properly - careful + * with backwards jumps (? think properly). Maybe separate jump-threading + * from path computations. */ + /* TODO: there MUST be a more efficient approach than relaxation */ + + Optimize_1(envPtr, padPtr); + + /* Finally remove all nops and unreachable code, reduce code size */ + CompactCode(envPtr, padPtr, 1); + + Tcl_DeleteHashTable(&padPtr->stack); + Tcl_Free((char *) padPtr); +} + +static void +Initialize( + CompileEnv *envPtr, + optPad *padPtr) +{ + INIT_PATHS; INIT_SIZE; + int i; + /* - * Add a marker *after* the last bytecode instruction. WARNING: points to - * one past the end! + * Initialize NEXT to identity, PATHS to 0. */ - DefineTargetAddress(tablePtr, currentInstPtr); + for (i=0; i < codeSize; i++) { + PATHS[i] = 0; + } /* - * Enter in the targets of exception ranges. + * Compute the paths to reachable code */ + MARK(0); + for (i=0 ; i<envPtr->exceptArrayNext ; i++) { ExceptionRange *rangePtr = &envPtr->exceptArrayPtr[i]; - if (rangePtr->type == CATCH_EXCEPTION_RANGE) { - targetInstPtr = envPtr->codeStart + rangePtr->catchOffset; - DefineTargetAddress(tablePtr, targetInstPtr); - } else { - targetInstPtr = envPtr->codeStart + rangePtr->breakOffset; - DefineTargetAddress(tablePtr, targetInstPtr); - if (rangePtr->continueOffset >= 0) { - targetInstPtr = envPtr->codeStart + rangePtr->continueOffset; - DefineTargetAddress(tablePtr, targetInstPtr); + if ((rangePtr->type == LOOP_EXCEPTION_RANGE) && + PATHS[rangePtr->codeOffset]) { + if (!PATHS[rangePtr->breakOffset]) { + MARK(rangePtr->breakOffset); + } + if ((rangePtr->continueOffset >= 0) + && !PATHS[rangePtr->continueOffset]){ + MARK(rangePtr->continueOffset); } } } + MoveUnreachable(envPtr, padPtr); + CompactCode(envPtr, padPtr, 0); } /* * ---------------------------------------------------------------------- * - * TrimUnreachable -- + * Optimize -- * - * Converts code that provably can't be executed into NOPs and reduces - * the overall reported length of the bytecode where that is possible. + * Replaces instructions and threads jumps in order to speed up the + * execution. It also marks unreachable code, replacing it with NOPS that + * can later be removed. * * ---------------------------------------------------------------------- */ -static void -TrimUnreachable( - CompileEnv *envPtr) -{ - unsigned char *currentInstPtr; - Tcl_HashTable targets; +#define MODIFIED \ + goto restart - LocateTargetAddresses(envPtr, &targets); +void +Optimize_0( + CompileEnv *envPtr, + optPad *padPtr) +{ + int codeSize = padPtr->codeSize; + int pc, nextpc; + unsigned char inst; - for (currentInstPtr = envPtr->codeStart ; - currentInstPtr < envPtr->codeNext-1 ; - currentInstPtr += AddrLength(currentInstPtr)) { - int clear = 0; + for (pc = 0; pc < codeSize; pc = nextpc) { + nextpc = NEXT_PC(pc); + inst = INST_AT_PC(pc); + + switch(inst) { + case INST_RETURN_CODE_BRANCH: + nextpc += 10; + continue; - if (*currentInstPtr != INST_DONE) { - continue; - } + case INST_PUSH4: + optimizePush_0(envPtr, padPtr, pc); - while (!IsTargetAddress(&targets, currentInstPtr + 1 + clear)) { - clear += AddrLength(currentInstPtr + 1 + clear); - } - if (currentInstPtr + 1 + clear == envPtr->codeNext) { - envPtr->codeNext -= clear; - } else { - while (clear --> 0) { - *(currentInstPtr + 1 + clear) = INST_NOP; - } + case INST_JUMP1: + case INST_JUMP_TRUE1: + case INST_JUMP_FALSE1: + case INST_JUMP_TRUE4: + case INST_JUMP_FALSE4: + case INST_JUMP4: + case INST_JUMP_TABLE: + UPDATE_JUMP(pc); + break; + } } - - Tcl_DeleteHashTable(&targets); } - -/* - * ---------------------------------------------------------------------- - * - * ConvertZeroEffectToNOP -- - * - * Replace PUSH/POP sequences (when non-hazardous) with NOPs. Also - * replace PUSH empty/STR_CONCAT and TRY_CVT_NUMERIC (when followed by an - * operation that guarantees the check for arithmeticity) and eliminate - * LNOT when we can invert the following JUMP condition. - * - * ---------------------------------------------------------------------- - */ -static void -ConvertZeroEffectToNOP( - CompileEnv *envPtr) +void +Optimize_1( + CompileEnv *envPtr, + optPad *padPtr) { - unsigned char *currentInstPtr; - int size; - Tcl_HashTable targets; - - LocateTargetAddresses(envPtr, &targets); - for (currentInstPtr = envPtr->codeStart ; - currentInstPtr < envPtr->codeNext ; currentInstPtr += size) { - int blank = 0, i, nextInst; - - size = AddrLength(currentInstPtr); - while ((currentInstPtr + size < envPtr->codeNext) - && *(currentInstPtr+size) == INST_NOP) { - if (IsTargetAddress(&targets, currentInstPtr + size)) { - break; - } - size += InstLength(INST_NOP); - } - if (IsTargetAddress(&targets, currentInstPtr + size)) { - continue; - } - nextInst = *(currentInstPtr + size); - switch (*currentInstPtr) { - case INST_PUSH1: - if (nextInst == INST_POP) { - blank = size + InstLength(nextInst); - } else if (nextInst == INST_STR_CONCAT1 - && TclGetUInt1AtPtr(currentInstPtr + size + 1) == 2) { - Tcl_Obj *litPtr = TclFetchLiteral(envPtr, - TclGetUInt1AtPtr(currentInstPtr + 1)); - int numBytes; - - (void) Tcl_GetStringFromObj(litPtr, &numBytes); - if (numBytes == 0) { - blank = size + InstLength(nextInst); - } - } - break; - case INST_PUSH4: - if (nextInst == INST_POP) { - blank = size + 1; - } else if (nextInst == INST_STR_CONCAT1 - && TclGetUInt1AtPtr(currentInstPtr + size + 1) == 2) { - Tcl_Obj *litPtr = TclFetchLiteral(envPtr, - TclGetUInt4AtPtr(currentInstPtr + 1)); - int numBytes; - - (void) Tcl_GetStringFromObj(litPtr, &numBytes); - if (numBytes == 0) { - blank = size + InstLength(nextInst); - } - } - break; + INIT_PATHS; + int codeSize = padPtr->codeSize; + int pc, nextpc, tmp; + unsigned char inst; - case INST_LNOT: - switch (nextInst) { + restart: + ThreadJumps(envPtr, padPtr); + for (pc = 0; pc < codeSize; pc = nextpc) { + nextpc = NEXT_PC(pc); + inst = INST_AT_PC(pc); + if ((inst == INST_NOP) || !PATHS[pc]) continue; + + switch(inst) { + case INST_RETURN_CODE_BRANCH: + nextpc += 10; + continue; + + case INST_JUMP1: case INST_JUMP_TRUE1: - blank = size; - *(currentInstPtr + size) = INST_JUMP_FALSE1; - break; case INST_JUMP_FALSE1: - blank = size; - *(currentInstPtr + size) = INST_JUMP_TRUE1; - break; - case INST_JUMP_TRUE4: - blank = size; - *(currentInstPtr + size) = INST_JUMP_FALSE4; + //Tcl_Panic("found a 1-jump!"); break; - case INST_JUMP_FALSE4: - blank = size; - *(currentInstPtr + size) = INST_JUMP_TRUE4; + + case INST_PUSH4: + if (optimizePush_1(envPtr, padPtr, pc)) { + if (!UNSHARED(pc)) MODIFIED; + } break; - } - break; - case INST_TRY_CVT_TO_NUMERIC: - switch (nextInst) { - case INST_JUMP_TRUE1: case INST_JUMP_TRUE4: - case INST_JUMP_FALSE1: - case INST_JUMP_FALSE4: - case INST_INCR_SCALAR1: - case INST_INCR_ARRAY1: - case INST_INCR_ARRAY_STK: - case INST_INCR_SCALAR_STK: - case INST_INCR_STK: - case INST_LOR: - case INST_LAND: - case INST_EQ: - case INST_NEQ: - case INST_LT: - case INST_LE: - case INST_GT: - case INST_GE: - case INST_MOD: - case INST_LSHIFT: - case INST_RSHIFT: - case INST_BITOR: - case INST_BITXOR: - case INST_BITAND: - case INST_EXPON: - case INST_ADD: - case INST_SUB: - case INST_DIV: - case INST_MULT: - case INST_LNOT: - case INST_BITNOT: - case INST_UMINUS: - case INST_UPLUS: - case INST_TRY_CVT_TO_NUMERIC: - blank = size; - break; - } - break; - } + case INST_JUMP_FALSE4: { + int t1, t, tgt, new; - if (blank > 0) { - for (i=0 ; i<blank ; i++) { - *(currentInstPtr + i) = INST_NOP; - } - size = blank; - } - } - Tcl_DeleteHashTable(&targets); -} - -/* - * ---------------------------------------------------------------------- - * - * AdvanceJumps -- - * - * Advance jumps past NOPs and chained JUMPs. After this runs, the only - * JUMPs that jump to a NOP or a JUMP will be length-1 ones that run out - * of room in their opcode to be targeted to where they really belong. - * - * ---------------------------------------------------------------------- - */ - -static void -AdvanceJumps( - CompileEnv *envPtr) -{ - unsigned char *currentInstPtr; - Tcl_HashTable jumps; - - for (currentInstPtr = envPtr->codeStart ; - currentInstPtr < envPtr->codeNext-1 ; - currentInstPtr += AddrLength(currentInstPtr)) { - int offset, delta, isNew; - - redo: - switch (*currentInstPtr) { - case INST_PUSH4: { - if (*(currentInstPtr + AddrLength(currentInstPtr)) == INST_POP) { - *currentInstPtr = INST_JUMP4; - TclStoreInt4AtPtr(6, currentInstPtr + 1); - goto redo; - } - continue; - } + /* + * detect stupid jump-around-jump, untangle + * + * <-- PC: CONDJMP->OLD !CONDJMP->TGT + * | T1: JMP->TGT <=> NOP + * --> OLD: FOO FOO + */ - case INST_JUMP1: - case INST_JUMP_TRUE1: - case INST_JUMP_FALSE1: - offset = TclGetInt1AtPtr(currentInstPtr + 1); - Tcl_InitHashTable(&jumps, TCL_ONE_WORD_KEYS); - for (delta=0 ; offset+delta != 0 ;) { - if (offset + delta < -128 || offset + delta > 127) { - break; - } - Tcl_CreateHashEntry(&jumps, INT2PTR(offset), &isNew); - if (!isNew) { - offset = TclGetInt1AtPtr(currentInstPtr + 1); - break; - } - offset += delta; - switch (*(currentInstPtr + offset)) { - case INST_NOP: - delta = InstLength(INST_NOP); - continue; - case INST_JUMP1: - delta = TclGetInt1AtPtr(currentInstPtr + offset + 1); - continue; - case INST_JUMP4: - delta = TclGetInt4AtPtr(currentInstPtr + offset + 1); - continue; + if (UPDATE_JUMP(pc)) MODIFIED; + new = pc + GET_INT4_AT_PC(pc+1); + for (t1 = NEXT_PC(pc); + (INST_AT_PC(t1) == INST_NOP) && UNSHARED(t1); + t1++) {}; + if ((INST_AT_PC(t1) == INST_JUMP4) && UNSHARED(t1)) { + if (new == FOLLOW(NEXT_PC(t1))) { + /* ENTANGLED! undo ... */ + tgt = t1 + GET_INT4_AT_PC(t1+1); + for (t = t1; t < t1 + 5; t++) { + INST_AT_PC(t) = INST_NOP; + PATHS[t] = 0; + } + t = pc + GET_INT4_AT_PC(pc+1); + REPLACE(t, t1); + + t = FOLLOW(tgt); + REPLACE(tgt, t); + INST_AT_PC(pc) ^= 2; + SET_INT4_AT_PC(t-pc, pc+1); + if (!UNSHARED(pc)) MODIFIED; + } } break; } - Tcl_DeleteHashTable(&jumps); - TclStoreInt1AtPtr(offset, currentInstPtr + 1); - continue; - case INST_JUMP4: - case INST_JUMP_TRUE4: - case INST_JUMP_FALSE4: - Tcl_InitHashTable(&jumps, TCL_ONE_WORD_KEYS); - Tcl_CreateHashEntry(&jumps, INT2PTR(0), &isNew); - for (offset = TclGetInt4AtPtr(currentInstPtr + 1); offset!=0 ;) { - Tcl_CreateHashEntry(&jumps, INT2PTR(offset), &isNew); - if (!isNew) { - offset = TclGetInt4AtPtr(currentInstPtr + 1); - break; - } - switch (*(currentInstPtr + offset)) { - case INST_NOP: - offset += InstLength(INST_NOP); - continue; - case INST_JUMP1: - offset += TclGetInt1AtPtr(currentInstPtr + offset + 1); - continue; - case INST_JUMP4: - offset += TclGetInt4AtPtr(currentInstPtr + offset + 1); - continue; + case INST_JUMP4: + if (UPDATE_JUMP(pc)) MODIFIED; + break; + + case INST_LNOT: + tmp = FOLLOW(nextpc); + if (UNSHARED(tmp)) { + inst = INST_AT_PC(tmp); + switch(inst) { + case INST_JUMP_TRUE1: + case INST_JUMP_FALSE1: + case INST_JUMP_TRUE4: + case INST_JUMP_FALSE4: + /* Trick: this transforms to the negation! */ + INST_AT_PC(tmp) ^= 2; + INST_AT_PC(pc) = INST_NOP; + if (!UNSHARED(pc)) MODIFIED; + } } break; - } - Tcl_DeleteHashTable(&jumps); - TclStoreInt4AtPtr(offset, currentInstPtr + 1); - continue; } } } +#undef MODIFIED /* * ---------------------------------------------------------------------- @@ -467,14 +387,17 @@ AdvanceJumps( * ---------------------------------------------------------------------- */ -static void +void CompactCode( - CompileEnv *envPtr) + CompileEnv *envPtr, + optPad *padPtr, + int shrinkInst) { - int pc, nops; - int inst, i, nextpc; - int codeSize = (envPtr->codeNext - envPtr->codeStart); - int *NEW = (int*) ckalloc(sizeof(int)*(codeSize + 1)); + int codeSize = padPtr->codeSize; + int *PATHS = padPtr->npaths; + int *NEW = padPtr->scratch; + int pc, nops, i, nextpc; + unsigned char inst; /* * First pass: shrink jumps and push, compute new positions. @@ -490,20 +413,24 @@ CompactCode( inst = INST_AT_PC(pc); NEW[pc] = pc - nops; /* new position */ + if ((inst == INST_NOP) || !PATHS[pc]) { + nops += nextpc - pc; + continue; + } + resize = 0; switch (inst) { - case INST_NOP: - nops++; - break; - case INST_RETURN_CODE_BRANCH: /* do not count NOPS in the special jump targets: skip them * altogether */ - for (i = 1; i < 11; i++) { - NEW[pc+i] = pc + i - nops; - } - nextpc += 10; + for (i = 0; i < 10; i++) { + NEW[nextpc] = nextpc - nops; + if (PATHS[nextpc] == 0) { + PATHS[nextpc] = 1; + } + nextpc++; + } break; case INST_PUSH4: @@ -523,7 +450,7 @@ CompactCode( break; } - if (resize) { + if (shrinkInst && resize) { /* * INST_XXX1 is always one less than INST_XXX4 */ @@ -538,7 +465,7 @@ CompactCode( } if (nops == 0) { - goto done; + return; } NEW[codeSize] = codeSize - nops; @@ -552,7 +479,7 @@ CompactCode( nextpc = NEXT_PC(pc); inst = INST_AT_PC(pc); - if (inst == INST_NOP) { + if ((inst == INST_NOP) || !PATHS[pc]) { continue; } @@ -565,16 +492,6 @@ CompactCode( Tcl_HashEntry *hPtr; Tcl_HashSearch hSearch; - case INST_RETURN_CODE_BRANCH: - /* - * Careful with NOPs in the next 10 bytes: they NEED to stay, - * and their jumps NEED to be updated. Currently only JUMP1 - * or RETURN_STK/DONE are compiled for those destinations so - * it is not a problem. - * - */ - break; - case INST_JUMP1: case INST_JUMP_TRUE1: case INST_JUMP_FALSE1: @@ -596,25 +513,28 @@ CompactCode( target = pc + GET_INT4_AT_PC(pc+1); offset = NEW[target]-NEW[pc]; SET_INT4_AT_PC(offset, pc+1); - if (inst == INST_START_CMD) break; - if ((offset < 127) && (offset > -128)) { + if (inst != INST_START_CMD) { if (offset == 5) { if (inst == INST_JUMP4) { INST_AT_PC(pc) = INST_NOP; INST_AT_PC(pc+1) = INST_NOP; nops += 2; - } else { + goto push3nops; + } else if (shrinkInst) { INST_AT_PC(pc) -= 1; SET_INT1_AT_PC(2, pc+1); + goto push3nops; } - } else if ((offset < 127) && (offset > -128)) { + } else if (shrinkInst && + (offset < 127) && (offset > -128)) { INST_AT_PC(pc) -= 1; SET_INT1_AT_PC(offset, pc+1); + push3nops: + INST_AT_PC(pc+2) = INST_NOP; + INST_AT_PC(pc+3) = INST_NOP; + INST_AT_PC(pc+4) = INST_NOP; + nops += 3; } - INST_AT_PC(pc+2) = INST_NOP; - INST_AT_PC(pc+3) = INST_NOP; - INST_AT_PC(pc+4) = INST_NOP; - nops += 3; } break; @@ -636,13 +556,16 @@ CompactCode( break; } - /* move up opcode and operands, eliminate */ + /* move up opcode and operands, en block */ for (i=0; pc+i < nextpc; i++) { - INST_AT_PC(NEW[pc]+i) = INST_AT_PC(pc+i); + int old = pc+i, new = NEW[pc] + i; + INST_AT_PC(new) = INST_AT_PC(old); + PATHS[new] = PATHS[old]; } } envPtr->codeNext = envPtr->codeStart + NEW[codeSize]; - + padPtr->codeSize = NEW[codeSize]; + /* * Update range targets */ @@ -695,43 +618,636 @@ CompactCode( /* * Restart until all done; should be rare. Other possible criteria: - * - restart if nops > x*codeSize + * - restart if (nops > x*codeSize), if new jumps, ... * - use back jumps as loop indicators, restart only if some backjmp is * reduced in size * - don't restart, bet that there's not much more to be done */ if (nops) { - codeSize = NEW[codeSize]; + codeSize = padPtr->codeSize; goto restart; } +} + +/* + * ---------------------------------------------------------------------- + * + * effectivePC, effectivePCinternal -- + * + * Utility functions. Find the effective newpc that will be executed when + * we get at pc, by following through jumps and nops. The results are + * cached in the NEXT array. + * + * Side effects: may update both the NEXT and PATHS arrays. + * + * Remark: both arrays need to be initialized on first call; NEXT to the + * identity and PATHS to 0 (as this may call MARK/UNMARK which + * require that). + * ---------------------------------------------------------------------- + */ + +int +effectivePCInternal( + CompileEnv *envPtr, + int pc, + optPad *padPtr, + int start) +{ + int old, new; + unsigned char inst; - done: - ckfree((char*) NEW); + /* recurse so that the whole path forward is updated and cached */ + + inst = INST_AT_PC(pc); + switch (inst) { + case INST_NOP: + old = pc + 1; + break; + + case INST_JUMP1: + old = pc + GET_INT1_AT_PC(pc+1); + break; + + case INST_JUMP4: + old = pc + GET_INT4_AT_PC(pc+1); + break; + + default: + return pc; + } + + if (start == old) { + /* + * INFINITE LOOP! TODO + * Eventually insert error generating code, possibly after INST_DONE? + */ + return pc; + } + + new = FOLLOW(old); + return new; +} + +int +UpdateJump( + CompileEnv *envPtr, + optPad *padPtr, + int pc) +{ + int old, new, inst; + Tcl_HashEntry *hPtr; + Tcl_HashSearch hSearch; + Tcl_HashTable *tPtr; + int result = 0; + + inst = INST_AT_PC(pc); + + switch(inst) { + default: + return 0; + + case INST_JUMP_TABLE: + tPtr = &JUMPTABLEINFO(envPtr, envPtr->codeStart+pc+1)->hashTable; + + hPtr = Tcl_FirstHashEntry(tPtr, &hSearch); + for (; hPtr ; hPtr = Tcl_NextHashEntry(&hSearch)) { + old = pc + PTR2INT(Tcl_GetHashValue(hPtr)); + new = FOLLOW(old); + if (new != old) { + //REPLACE(old, new); + Tcl_SetHashValue(hPtr, INT2PTR(new - pc)); + result = 1; + } + } + return result; + + case INST_JUMP_TRUE4: + case INST_JUMP_FALSE4: + case INST_JUMP4: + old = pc + GET_INT4_AT_PC(pc+1); + break; + } + + new = FOLLOW(old); + if (new == old) { + return 0; + } + SET_INT4_AT_PC(new - pc, pc+1); + //REPLACE(old, new); + return 1; +} + +void +ThreadJumps( + CompileEnv *envPtr, + optPad *padPtr) +{ + INIT_SIZE; + int pc, nextpc; + unsigned char inst; + + for (pc = 0; pc < codeSize; pc = nextpc) { + nextpc = NEXT_PC(pc); + inst = INST_AT_PC(pc); + + switch(inst) { + /* TODO: + * - condJmp around a non-target jmp -> invert the condition, + * remove the jump! + * - advance jump to unshared cond-jmp? error reporting problems, + * same as advancing done (when caught ... can it be?) + */ + + case INST_JUMP_TRUE4: + case INST_JUMP_FALSE4: + case INST_JUMP4: + case INST_JUMP_TABLE: + UPDATE_JUMP(pc); + break; + } + } } /* * ---------------------------------------------------------------------- * - * TclOptimizeBytecode -- + * markPath -- * - * A very simple peephole optimizer for bytecode. + * Count the number of paths reaching an instruction, following the jumps + * as possible. * * ---------------------------------------------------------------------- */ +#define PUSH(pc) \ + iPUSH((pc), padPtr, mark) + +#define POP(pc) \ + ((pc) = iPOP(padPtr)) + +static void +iPUSH( + int pc, + optPad *padPtr, + int mark) +{ + INIT_SIZE; INIT_PATHS; INIT_STACK; + int tmp; + int *cachePtr = &padPtr->cache; + int cached = *cachePtr; + + if (pc >= codeSize) return; + + tmp = ((!mark && (--PATHS[pc] == 0)) + || (mark && (++PATHS[pc] == 1))); + + if (tmp) { + if (cached != -1) { + Tcl_CreateHashEntry(stackPtr, INT2PTR(cached), &tmp); + } + *cachePtr = pc; + } +} + +static int +iPOP( + optPad *padPtr) +{ + INIT_STACK; + Tcl_HashEntry *hPtr; + Tcl_HashSearch hSearch; + int pc; + int cached = padPtr->cache; + + if (cached != -1) { + padPtr->cache = -1; + return cached; + } + + hPtr = Tcl_FirstHashEntry(stackPtr, &hSearch); + if (!hPtr) { + return -1; + } + + pc = PTR2INT(Tcl_GetHashKey(stackPtr, hPtr)); + Tcl_DeleteHashEntry(hPtr); + return pc; +} + void -TclOptimizeBytecode( - void *envPtr) +markPath( + CompileEnv *envPtr, + int pc, + optPad *padPtr, + int mark) +{ + INIT_PATHS; + unsigned char inst; + int nextpc, i, target; + Tcl_HashEntry *hPtr; + Tcl_HashSearch hSearch; + + /* + * Note that each pc will be followed at most once, so that only branch + * targets have a PATHS count > 1. + */ + + if (mark) { + if (PATHS[pc] > 0) { + PATHS[pc]++; + return; + } + } else { + if (PATHS[pc] > 1) { + PATHS[pc]--; + return; + } else if (PATHS[pc] <=0) { + PATHS[pc] = 0; + return; + } + } + + PUSH(pc); + while (POP(pc) != -1) { + inst = INST_AT_PC(pc); + nextpc = NEXT_PC(pc); + mark = (PATHS[pc] > 0); + switch(inst) { + case INST_DONE: + case INST_TAILCALL: + case INST_CONTINUE: + case INST_BREAK: + break; + + case INST_JUMP_TRUE1: + case INST_JUMP_FALSE1: + PUSH(nextpc); + case INST_JUMP1: + PUSH(pc + GET_INT1_AT_PC(pc+1)); + break; + + case INST_JUMP_TRUE4: + case INST_JUMP_FALSE4: + PUSH(nextpc); + case INST_JUMP4: + target = pc + GET_INT4_AT_PC(pc+1); + if (mark) { + target = FOLLOW(target); + SET_INT4_AT_PC(target-pc, pc+1); + } + PUSH(target); + break; + + case INST_JUMP_TABLE: + hPtr = Tcl_FirstHashEntry( + &JUMPTABLEINFO(envPtr, envPtr->codeStart+pc+1)->hashTable, + &hSearch); + for (; hPtr ; hPtr = Tcl_NextHashEntry(&hSearch)) { + target = pc + PTR2INT(Tcl_GetHashValue(hPtr)); + if (mark) { + target = FOLLOW(target); + Tcl_SetHashValue(hPtr, INT2PTR(target - pc)); + } + PUSH(target); + } + PUSH(nextpc); + break; + + case INST_RETURN_CODE_BRANCH: + /* + * uncommon jumps: handle all here. Convert possible unreachable + * NOP to DONE, marked as reachable so that they are not + * eliminated. This is followed by 2-bytes each for codes: error, + * return, break, continue, other. After that, the code for ok. + */ + + for (i = 0; i < 5; i++, nextpc += 2) { + PUSH(nextpc); + if (InstLength(INST_AT_PC(nextpc)) == 1) { + PUSH(nextpc + 1); + } + } + break; + + default: + PUSH(nextpc); + break; + } + } +#undef PUSH +#undef POP +} + +/* + * Move exception handling code to after INST_DONE. ASSUMES that there are no + * FORCED 1-jumps (as after INST_RETURN_CODE_BRANCH) from exception handling + * code to normal code. + */ + + +void +MoveUnreachable( + CompileEnv *envPtr, + optPad *padPtr) +{ + INIT_PATHS; + int *NEW = padPtr->scratch; + int pc, nextpc, target, inst; + int fixend, curr, i, imax; + ExceptionRange *rangePtr; + JumptableInfo *infoPtr; + Tcl_HashEntry *hPtr; + Tcl_HashSearch hSearch; + int codeSize = padPtr->codeSize; + + /* + * Move unreachable code out to after done, after checking there is enough + * room to do it - be generous. + */ + + if ((envPtr->codeEnd - envPtr->codeStart) < + 2 * (envPtr->codeNext - envPtr->codeStart)) { + TclExpandCodeArray(envPtr); + } + +#if 1 + for (pc = 0; pc < codeSize; pc = nextpc) { + nextpc = NEXT_PC(pc); + + if (PATHS[pc] > 0) { + NEW[pc] = pc; + continue; + } + + curr = (envPtr->codeNext - envPtr->codeStart); + + inst = INST_AT_PC(pc); + fixend = (nextpc >= codeSize)? 0 : PATHS[nextpc]; + NEW[pc] = curr; + INST_AT_PC(curr) = INST_AT_PC(pc); + + switch (inst) { + case INST_JUMP1: + case INST_JUMP_FALSE1: + case INST_JUMP_TRUE1: + /* + * They should be all gone by now! OK not to move this code? + * + * Used to Tcl_Panic, but that is not good if this loaded on + * a standard Tcl that produces lots of 1-jumps. + */ + break; + + + case INST_JUMP4: + fixend = 0; + case INST_JUMP_FALSE4: + case INST_JUMP_TRUE4: + target = pc + GET_INT4_AT_PC(pc+1); + SET_INT4_AT_PC(target, curr+1); + break; + + case INST_JUMP_TABLE: + i = GET_UINT4_AT_PC(pc+1); + SET_INT4_AT_PC(i, curr+1); + infoPtr = (JumptableInfo *) TclFetchAuxData(envPtr, i); + hPtr = Tcl_FirstHashEntry(&infoPtr->hashTable, &hSearch); + for (; hPtr ; hPtr = Tcl_NextHashEntry(&hSearch)) { + target = pc + PTR2INT(Tcl_GetHashValue(hPtr)); + Tcl_SetHashValue(hPtr, INT2PTR(target)); + } + break; + + case INST_RETURN_CODE_BRANCH: + /* let's hope there was no 1-jump to OK code! */ + nextpc += 10; + + default: + for (i=1; i < nextpc-pc; i++) { + INST_AT_PC(curr+i) = INST_AT_PC(pc+i); + NEW[pc+i] = curr+i; + } + break; + } + envPtr->codeNext += (nextpc-pc); + + if (fixend) { + /* add a jump back to ok code */ + curr = (envPtr->codeNext - envPtr->codeStart); + envPtr->codeNext += 5; + INST_AT_PC(curr) = INST_JUMP4; + SET_INT4_AT_PC(nextpc, curr+1); + } + } + + /* + * The code has been moved, jump targets are all absolute. Pass over the + * moved code, updating jump targets and setting. + */ + + for (pc = codeSize; + pc < envPtr->codeNext-envPtr->codeStart; + pc = nextpc) { + nextpc = NEXT_PC(pc); + inst = INST_AT_PC(pc); + NEW[pc] = pc; + PATHS[pc] = 0; + + switch (inst) { + case INST_JUMP4: + case INST_JUMP_FALSE4: + case INST_JUMP_TRUE4: + target = NEW[GET_INT4_AT_PC(pc+1)]; + SET_INT4_AT_PC(target-pc, pc+1); + break; + + case INST_JUMP_TABLE: + infoPtr = (JumptableInfo *) TclFetchAuxData(envPtr, GET_UINT4_AT_PC(pc+1)); + hPtr = Tcl_FirstHashEntry(&infoPtr->hashTable, &hSearch); + for (; hPtr ; hPtr = Tcl_NextHashEntry(&hSearch)) { + target = NEW[PTR2INT(Tcl_GetHashValue(hPtr))]; + Tcl_SetHashValue(hPtr, INT2PTR(target-pc)); + } + break; + } + } + + /* Reset things to normal */ + + padPtr->codeSize = envPtr->codeNext - envPtr->codeStart; + +#else + for (pc = 0; pc < codeSize; pc = nextpc) { + nextpc = NEXT_PC(pc); + NEW[pc] = pc; + } +#endif + + /* Restore all accessible ranges */ + + imax = envPtr->exceptArrayNext; + for (i = 0 ; i < imax; i++) { + rangePtr = &envPtr->exceptArrayPtr[i]; + if (!PATHS[NEW[rangePtr->codeOffset]]) continue; + + if (rangePtr->type == CATCH_EXCEPTION_RANGE) { + target = FOLLOW(NEW[rangePtr->catchOffset]); + rangePtr->catchOffset = target; + MARK(target); + } else { + rangePtr->breakOffset = FOLLOW(NEW[rangePtr->breakOffset]); + if (!PATHS[rangePtr->breakOffset]) { + MARK(rangePtr->breakOffset); + } + if (rangePtr->continueOffset >= 0) { + rangePtr->continueOffset = FOLLOW(NEW[rangePtr->continueOffset]); + if (!PATHS[rangePtr->continueOffset]){ + MARK(rangePtr->continueOffset); + } + } + } + } +} + +int +optimizePush_0( + CompileEnv *envPtr, + optPad *padPtr, + int pc) { - int i = 2; + int inst, nextpc, i, target, tmp; + Tcl_Obj *objPtr; + + nextpc = NEXT_PC(pc); + + tmp = FOLLOW(nextpc); + inst = INST_AT_PC(tmp); + objPtr = envPtr->literalArrayPtr[GET_UINT4_AT_PC(pc+1)].objPtr; + + switch (inst) { + case INST_POP: + tmp = FOLLOW(tmp + 1); + INST_AT_PC(pc) = INST_JUMP4; + SET_INT4_AT_PC(tmp - pc, pc+1); + //REPLACE(nextpc, tmp); + return 1; + + case INST_JUMP_TRUE1: + case INST_JUMP_FALSE1: + case INST_JUMP_TRUE4: + case INST_JUMP_FALSE4: + if (Tcl_GetBooleanFromObj(NULL, objPtr, &i) == TCL_ERROR) { + return 0; + } + /* let i indicate if we take the jump or not */ + if (i) { + i = ((inst == INST_JUMP_TRUE1) || (inst == INST_JUMP_TRUE4)); + } else { + i = ((inst == INST_JUMP_FALSE1) || (inst == INST_JUMP_FALSE4)); + } + if (i) { + switch (inst) { + case INST_JUMP_TRUE1: + case INST_JUMP_FALSE1: + target = tmp + GET_INT1_AT_PC(tmp+1); + break; + + case INST_JUMP_TRUE4: + case INST_JUMP_FALSE4: + target = tmp + GET_INT4_AT_PC(tmp+1); + break; + } + } else { + target = NEXT_PC(tmp); + } + target = FOLLOW(target); + //REPLACE(nextpc, target); + INST_AT_PC(pc) = INST_JUMP4; + SET_INT4_AT_PC(target-pc, pc+1); + return 1; + + case INST_LNOT: + break; + + /* FIXME: TODO, eval and jump past it? No room for the + * jump. If unshared target, change the literal if it + * is a number. */ + + case INST_UMINUS: + case INST_UPLUS: + case INST_TRY_CVT_TO_NUMERIC: + case INST_LSHIFT: + case INST_RSHIFT: + case INST_BITNOT: + break; + } + return 0; +} + +static inline int +optPush1( + CompileEnv *envPtr, + optPad *padPtr, + int pc) +{ + INIT_PATHS; + int inst, nextpc, i, tmp; + Tcl_Obj *objPtr; + + nextpc = NEXT_PC(pc); + + redoPush: + tmp = FOLLOW(nextpc); + if (!UNSHARED(tmp)) { + return 0; + } + + inst = INST_AT_PC(tmp); + objPtr = envPtr->literalArrayPtr[GET_UINT4_AT_PC(pc+1)].objPtr; + switch (inst) { + case INST_LNOT: + if (Tcl_GetBooleanFromObj(NULL, objPtr, &i) != TCL_ERROR) { + INST_AT_PC(tmp) = INST_NOP; + i = TclRegisterNewLiteral(envPtr, i? "0" : "1", 1); + SET_INT4_AT_PC(i, pc+1); + goto redoPush; + } + break; + + /* FIXME: TODO, eval and jump past it? No room for the + * jump. If unshared target, change the literal if it + * is a number. */ + + case INST_UMINUS: + case INST_UPLUS: + case INST_TRY_CVT_TO_NUMERIC: + case INST_LSHIFT: + case INST_RSHIFT: + case INST_BITNOT: + break; + } + return 0; +} + + +int +optimizePush_1( + CompileEnv *envPtr, + optPad *padPtr, + int pc) +{ + int res; + + if (INST_AT_PC(pc) != INST_PUSH4) { + return 0; + } - while (i--) { - ConvertZeroEffectToNOP(envPtr); - AdvanceJumps(envPtr); - TrimUnreachable(envPtr); - CompactCode(envPtr); + res = optimizePush_0(envPtr, padPtr, pc); + if (!res) { + res = optPush1(envPtr, padPtr, pc); } + return res; } /* |