445 lines
11 KiB
C
445 lines
11 KiB
C
|
/*
|
|||
|
* tclOptimize.c --
|
|||
|
*
|
|||
|
* This file contains the bytecode optimizer.
|
|||
|
*
|
|||
|
* Copyright (c) 2013 by Donal Fellows.
|
|||
|
*
|
|||
|
* 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);
|
|||
|
|
|||
|
/*
|
|||
|
* 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[UCHAR(instruction)].numBytes)
|
|||
|
|
|||
|
/*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* LocateTargetAddresses --
|
|||
|
*
|
|||
|
* 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.
|
|||
|
*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static void
|
|||
|
LocateTargetAddresses(
|
|||
|
CompileEnv *envPtr,
|
|||
|
Tcl_HashTable *tablePtr)
|
|||
|
{
|
|||
|
unsigned char *currentInstPtr, *targetInstPtr;
|
|||
|
int isNew, i;
|
|||
|
Tcl_HashEntry *hPtr;
|
|||
|
Tcl_HashSearch hSearch;
|
|||
|
|
|||
|
Tcl_InitHashTable(tablePtr, TCL_ONE_WORD_KEYS);
|
|||
|
|
|||
|
/*
|
|||
|
* The starts of commands represent target addresses.
|
|||
|
*/
|
|||
|
|
|||
|
for (i=0 ; i<envPtr->numCommands ; i++) {
|
|||
|
DefineTargetAddress(tablePtr,
|
|||
|
envPtr->codeStart + envPtr->cmdMapPtr[i].codeOffset);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Find places where we should be careful about replacing instructions
|
|||
|
* because they are the targets of various types of jumps.
|
|||
|
*/
|
|||
|
|
|||
|
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;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Add a marker *after* the last bytecode instruction. WARNING: points to
|
|||
|
* one past the end!
|
|||
|
*/
|
|||
|
|
|||
|
DefineTargetAddress(tablePtr, currentInstPtr);
|
|||
|
|
|||
|
/*
|
|||
|
* Enter in the targets of exception ranges.
|
|||
|
*/
|
|||
|
|
|||
|
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);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* TrimUnreachable --
|
|||
|
*
|
|||
|
* Converts code that provably can't be executed into NOPs and reduces
|
|||
|
* the overall reported length of the bytecode where that is possible.
|
|||
|
*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static void
|
|||
|
TrimUnreachable(
|
|||
|
CompileEnv *envPtr)
|
|||
|
{
|
|||
|
unsigned char *currentInstPtr;
|
|||
|
Tcl_HashTable targets;
|
|||
|
|
|||
|
LocateTargetAddresses(envPtr, &targets);
|
|||
|
|
|||
|
for (currentInstPtr = envPtr->codeStart ;
|
|||
|
currentInstPtr < envPtr->codeNext-1 ;
|
|||
|
currentInstPtr += AddrLength(currentInstPtr)) {
|
|||
|
int clear = 0;
|
|||
|
|
|||
|
if (*currentInstPtr != INST_DONE) {
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
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;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
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)
|
|||
|
{
|
|||
|
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;
|
|||
|
|
|||
|
case INST_LNOT:
|
|||
|
switch (nextInst) {
|
|||
|
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;
|
|||
|
break;
|
|||
|
case INST_JUMP_FALSE4:
|
|||
|
blank = size;
|
|||
|
*(currentInstPtr + size) = INST_JUMP_TRUE4;
|
|||
|
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;
|
|||
|
}
|
|||
|
|
|||
|
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;
|
|||
|
|
|||
|
switch (*currentInstPtr) {
|
|||
|
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;
|
|||
|
}
|
|||
|
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;
|
|||
|
}
|
|||
|
break;
|
|||
|
}
|
|||
|
Tcl_DeleteHashTable(&jumps);
|
|||
|
TclStoreInt4AtPtr(offset, currentInstPtr + 1);
|
|||
|
continue;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* TclOptimizeBytecode --
|
|||
|
*
|
|||
|
* A very simple peephole optimizer for bytecode.
|
|||
|
*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
void
|
|||
|
TclOptimizeBytecode(
|
|||
|
void *envPtr)
|
|||
|
{
|
|||
|
ConvertZeroEffectToNOP(envPtr);
|
|||
|
AdvanceJumps(envPtr);
|
|||
|
TrimUnreachable(envPtr);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Local Variables:
|
|||
|
* mode: c
|
|||
|
* c-basic-offset: 4
|
|||
|
* fill-column: 78
|
|||
|
* tab-width: 8
|
|||
|
* End:
|
|||
|
*/
|