4351 lines
132 KiB
C
4351 lines
132 KiB
C
|
/*
|
|||
|
* tclAssembly.c --
|
|||
|
*
|
|||
|
* Assembler for Tcl bytecodes.
|
|||
|
*
|
|||
|
* This file contains the procedures that convert Tcl Assembly Language (TAL)
|
|||
|
* to a sequence of bytecode instructions for the Tcl execution engine.
|
|||
|
*
|
|||
|
* Copyright (c) 2010 by Ozgur Dogan Ugurlu.
|
|||
|
* Copyright (c) 2010 by Kevin B. Kenny.
|
|||
|
*
|
|||
|
* See the file "license.terms" for information on usage and redistribution of
|
|||
|
* this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
|||
|
*/
|
|||
|
|
|||
|
/*-
|
|||
|
*- THINGS TO DO:
|
|||
|
*- More instructions:
|
|||
|
*- done - alternate exit point (affects stack and exception range checking)
|
|||
|
*- break and continue - if exception ranges can be sorted out.
|
|||
|
*- foreach_start4, foreach_step4
|
|||
|
*- returnImm, returnStk
|
|||
|
*- expandStart, expandStkTop, invokeExpanded, expandDrop
|
|||
|
*- dictFirst, dictNext, dictDone
|
|||
|
*- dictUpdateStart, dictUpdateEnd
|
|||
|
*- jumpTable testing
|
|||
|
*- syntax (?)
|
|||
|
*- returnCodeBranch
|
|||
|
*- tclooNext, tclooNextClass
|
|||
|
*/
|
|||
|
|
|||
|
#include "tclInt.h"
|
|||
|
#include "tclCompile.h"
|
|||
|
#include "tclOOInt.h"
|
|||
|
|
|||
|
/*
|
|||
|
* Structure that represents a range of instructions in the bytecode.
|
|||
|
*/
|
|||
|
|
|||
|
typedef struct CodeRange {
|
|||
|
int startOffset; /* Start offset in the bytecode array */
|
|||
|
int endOffset; /* End offset in the bytecode array */
|
|||
|
} CodeRange;
|
|||
|
|
|||
|
/*
|
|||
|
* State identified for a basic block's catch context.
|
|||
|
*/
|
|||
|
|
|||
|
typedef enum BasicBlockCatchState {
|
|||
|
BBCS_UNKNOWN = 0, /* Catch context has not yet been identified */
|
|||
|
BBCS_NONE, /* Block is outside of any catch */
|
|||
|
BBCS_INCATCH, /* Block is within a catch context */
|
|||
|
BBCS_CAUGHT /* Block is within a catch context and
|
|||
|
* may be executed after an exception fires */
|
|||
|
} BasicBlockCatchState;
|
|||
|
|
|||
|
/*
|
|||
|
* Structure that defines a basic block - a linear sequence of bytecode
|
|||
|
* instructions with no jumps in or out (including not changing the
|
|||
|
* state of any exception range).
|
|||
|
*/
|
|||
|
|
|||
|
typedef struct BasicBlock {
|
|||
|
int originalStartOffset; /* Instruction offset before JUMP1s were
|
|||
|
* substituted with JUMP4's */
|
|||
|
int startOffset; /* Instruction offset of the start of the
|
|||
|
* block */
|
|||
|
int startLine; /* Line number in the input script of the
|
|||
|
* instruction at the start of the block */
|
|||
|
int jumpOffset; /* Bytecode offset of the 'jump' instruction
|
|||
|
* that ends the block, or -1 if there is no
|
|||
|
* jump. */
|
|||
|
int jumpLine; /* Line number in the input script of the
|
|||
|
* 'jump' instruction that ends the block, or
|
|||
|
* -1 if there is no jump */
|
|||
|
struct BasicBlock* prevPtr; /* Immediate predecessor of this block */
|
|||
|
struct BasicBlock* predecessor;
|
|||
|
/* Predecessor of this block in the spanning
|
|||
|
* tree */
|
|||
|
struct BasicBlock* successor1;
|
|||
|
/* BasicBlock structure of the following
|
|||
|
* block: NULL at the end of the bytecode
|
|||
|
* sequence. */
|
|||
|
Tcl_Obj* jumpTarget; /* Jump target label if the jump target is
|
|||
|
* unresolved */
|
|||
|
int initialStackDepth; /* Absolute stack depth on entry */
|
|||
|
int minStackDepth; /* Low-water relative stack depth */
|
|||
|
int maxStackDepth; /* High-water relative stack depth */
|
|||
|
int finalStackDepth; /* Relative stack depth on exit */
|
|||
|
enum BasicBlockCatchState catchState;
|
|||
|
/* State of the block for 'catch' analysis */
|
|||
|
int catchDepth; /* Number of nested catches in which the basic
|
|||
|
* block appears */
|
|||
|
struct BasicBlock* enclosingCatch;
|
|||
|
/* BasicBlock structure of the last startCatch
|
|||
|
* executed on a path to this block, or NULL
|
|||
|
* if there is no enclosing catch */
|
|||
|
int foreignExceptionBase; /* Base index of foreign exceptions */
|
|||
|
int foreignExceptionCount; /* Count of foreign exceptions */
|
|||
|
ExceptionRange* foreignExceptions;
|
|||
|
/* ExceptionRange structures for exception
|
|||
|
* ranges belonging to embedded scripts and
|
|||
|
* expressions in this block */
|
|||
|
JumptableInfo* jtPtr; /* Jump table at the end of this basic block */
|
|||
|
int flags; /* Boolean flags */
|
|||
|
} BasicBlock;
|
|||
|
|
|||
|
/*
|
|||
|
* Flags that pertain to a basic block.
|
|||
|
*/
|
|||
|
|
|||
|
enum BasicBlockFlags {
|
|||
|
BB_VISITED = (1 << 0), /* Block has been visited in the current
|
|||
|
* traversal */
|
|||
|
BB_FALLTHRU = (1 << 1), /* Control may pass from this block to a
|
|||
|
* successor */
|
|||
|
BB_JUMP1 = (1 << 2), /* Basic block ends with a 1-byte-offset jump
|
|||
|
* and may need expansion */
|
|||
|
BB_JUMPTABLE = (1 << 3), /* Basic block ends with a jump table */
|
|||
|
BB_BEGINCATCH = (1 << 4), /* Block ends with a 'beginCatch' instruction,
|
|||
|
* marking it as the start of a 'catch'
|
|||
|
* sequence. The 'jumpTarget' is the exception
|
|||
|
* exit from the catch block. */
|
|||
|
BB_ENDCATCH = (1 << 5) /* Block ends with an 'endCatch' instruction,
|
|||
|
* unwinding the catch from the exception
|
|||
|
* stack. */
|
|||
|
};
|
|||
|
|
|||
|
/*
|
|||
|
* Source instruction type recognized by the assembler.
|
|||
|
*/
|
|||
|
|
|||
|
typedef enum TalInstType {
|
|||
|
ASSEM_1BYTE, /* Fixed arity, 1-byte instruction */
|
|||
|
ASSEM_BEGIN_CATCH, /* Begin catch: one 4-byte jump offset to be
|
|||
|
* converted to appropriate exception
|
|||
|
* ranges */
|
|||
|
ASSEM_BOOL, /* One Boolean operand */
|
|||
|
ASSEM_BOOL_LVT4, /* One Boolean, one 4-byte LVT ref. */
|
|||
|
ASSEM_CLOCK_READ, /* 1-byte unsigned-integer case number, in the
|
|||
|
* range 0-3 */
|
|||
|
ASSEM_CONCAT1, /* 1-byte unsigned-integer operand count, must
|
|||
|
* be strictly positive, consumes N, produces
|
|||
|
* 1 */
|
|||
|
ASSEM_DICT_GET, /* 'dict get' and related - consumes N+1
|
|||
|
* operands, produces 1, N > 0 */
|
|||
|
ASSEM_DICT_SET, /* specifies key count and LVT index, consumes
|
|||
|
* N+1 operands, produces 1, N > 0 */
|
|||
|
ASSEM_DICT_UNSET, /* specifies key count and LVT index, consumes
|
|||
|
* N operands, produces 1, N > 0 */
|
|||
|
ASSEM_END_CATCH, /* End catch. No args. Exception range popped
|
|||
|
* from stack and stack pointer restored. */
|
|||
|
ASSEM_EVAL, /* 'eval' - evaluate a constant script (by
|
|||
|
* compiling it in line with the assembly
|
|||
|
* code! I love Tcl!) */
|
|||
|
ASSEM_INDEX, /* 4 byte operand, integer or end-integer */
|
|||
|
ASSEM_INVOKE, /* 1- or 4-byte operand count, must be
|
|||
|
* strictly positive, consumes N, produces
|
|||
|
* 1. */
|
|||
|
ASSEM_JUMP, /* Jump instructions */
|
|||
|
ASSEM_JUMP4, /* Jump instructions forcing a 4-byte offset */
|
|||
|
ASSEM_JUMPTABLE, /* Jumptable (switch -exact) */
|
|||
|
ASSEM_LABEL, /* The assembly directive that defines a
|
|||
|
* label */
|
|||
|
ASSEM_LINDEX_MULTI, /* 4-byte operand count, must be strictly
|
|||
|
* positive, consumes N, produces 1 */
|
|||
|
ASSEM_LIST, /* 4-byte operand count, must be nonnegative,
|
|||
|
* consumses N, produces 1 */
|
|||
|
ASSEM_LSET_FLAT, /* 4-byte operand count, must be >= 3,
|
|||
|
* consumes N, produces 1 */
|
|||
|
ASSEM_LVT, /* One operand that references a local
|
|||
|
* variable */
|
|||
|
ASSEM_LVT1, /* One 1-byte operand that references a local
|
|||
|
* variable */
|
|||
|
ASSEM_LVT1_SINT1, /* One 1-byte operand that references a local
|
|||
|
* variable, one signed-integer 1-byte
|
|||
|
* operand */
|
|||
|
ASSEM_LVT4, /* One 4-byte operand that references a local
|
|||
|
* variable */
|
|||
|
ASSEM_OVER, /* OVER: 4-byte operand count, consumes N+1,
|
|||
|
* produces N+2 */
|
|||
|
ASSEM_PUSH, /* one literal operand */
|
|||
|
ASSEM_REGEXP, /* One Boolean operand, but weird mapping to
|
|||
|
* call flags */
|
|||
|
ASSEM_REVERSE, /* REVERSE: 4-byte operand count, consumes N,
|
|||
|
* produces N */
|
|||
|
ASSEM_SINT1, /* One 1-byte signed-integer operand
|
|||
|
* (INCR_STK_IMM) */
|
|||
|
ASSEM_SINT4_LVT4 /* Signed 4-byte integer operand followed by
|
|||
|
* LVT entry. Fixed arity */
|
|||
|
} TalInstType;
|
|||
|
|
|||
|
/*
|
|||
|
* Description of an instruction recognized by the assembler.
|
|||
|
*/
|
|||
|
|
|||
|
typedef struct TalInstDesc {
|
|||
|
const char *name; /* Name of instruction. */
|
|||
|
TalInstType instType; /* The type of instruction */
|
|||
|
int tclInstCode; /* Instruction code. For instructions having
|
|||
|
* 1- and 4-byte variables, tclInstCode is
|
|||
|
* ((1byte)<<8) || (4byte) */
|
|||
|
int operandsConsumed; /* Number of operands consumed by the
|
|||
|
* operation, or INT_MIN if the operation is
|
|||
|
* variadic */
|
|||
|
int operandsProduced; /* Number of operands produced by the
|
|||
|
* operation. If negative, the operation has a
|
|||
|
* net stack effect of -1-operandsProduced */
|
|||
|
} TalInstDesc;
|
|||
|
|
|||
|
/*
|
|||
|
* Structure that holds the state of the assembler while generating code.
|
|||
|
*/
|
|||
|
|
|||
|
typedef struct AssemblyEnv {
|
|||
|
CompileEnv* envPtr; /* Compilation environment being used for code
|
|||
|
* generation */
|
|||
|
Tcl_Parse* parsePtr; /* Parse of the current line of source */
|
|||
|
Tcl_HashTable labelHash; /* Hash table whose keys are labels and whose
|
|||
|
* values are 'label' objects storing the code
|
|||
|
* offsets of the labels. */
|
|||
|
int cmdLine; /* Current line number within the assembly
|
|||
|
* code */
|
|||
|
int* clNext; /* Invisible continuation line for
|
|||
|
* [info frame] */
|
|||
|
BasicBlock* head_bb; /* First basic block in the code */
|
|||
|
BasicBlock* curr_bb; /* Current basic block */
|
|||
|
int maxDepth; /* Maximum stack depth encountered */
|
|||
|
int curCatchDepth; /* Current depth of catches */
|
|||
|
int maxCatchDepth; /* Maximum depth of catches encountered */
|
|||
|
int flags; /* Compilation flags (TCL_EVAL_DIRECT) */
|
|||
|
} AssemblyEnv;
|
|||
|
|
|||
|
/*
|
|||
|
* Static functions defined in this file.
|
|||
|
*/
|
|||
|
|
|||
|
static void AddBasicBlockRangeToErrorInfo(AssemblyEnv*,
|
|||
|
BasicBlock*);
|
|||
|
static BasicBlock * AllocBB(AssemblyEnv*);
|
|||
|
static int AssembleOneLine(AssemblyEnv* envPtr);
|
|||
|
static void BBAdjustStackDepth(BasicBlock* bbPtr, int consumed,
|
|||
|
int produced);
|
|||
|
static void BBUpdateStackReqs(BasicBlock* bbPtr, int tblIdx,
|
|||
|
int count);
|
|||
|
static void BBEmitInstInt1(AssemblyEnv* assemEnvPtr, int tblIdx,
|
|||
|
int opnd, int count);
|
|||
|
static void BBEmitInstInt4(AssemblyEnv* assemEnvPtr, int tblIdx,
|
|||
|
int opnd, int count);
|
|||
|
static void BBEmitInst1or4(AssemblyEnv* assemEnvPtr, int tblIdx,
|
|||
|
int param, int count);
|
|||
|
static void BBEmitOpcode(AssemblyEnv* assemEnvPtr, int tblIdx,
|
|||
|
int count);
|
|||
|
static int BuildExceptionRanges(AssemblyEnv* assemEnvPtr);
|
|||
|
static int CalculateJumpRelocations(AssemblyEnv*, int*);
|
|||
|
static int CheckForUnclosedCatches(AssemblyEnv*);
|
|||
|
static int CheckForThrowInWrongContext(AssemblyEnv*);
|
|||
|
static int CheckNonThrowingBlock(AssemblyEnv*, BasicBlock*);
|
|||
|
static int BytecodeMightThrow(unsigned char);
|
|||
|
static int CheckJumpTableLabels(AssemblyEnv*, BasicBlock*);
|
|||
|
static int CheckNamespaceQualifiers(Tcl_Interp*, const char*,
|
|||
|
int);
|
|||
|
static int CheckNonNegative(Tcl_Interp*, int);
|
|||
|
static int CheckOneByte(Tcl_Interp*, int);
|
|||
|
static int CheckSignedOneByte(Tcl_Interp*, int);
|
|||
|
static int CheckStack(AssemblyEnv*);
|
|||
|
static int CheckStrictlyPositive(Tcl_Interp*, int);
|
|||
|
static ByteCode * CompileAssembleObj(Tcl_Interp *interp,
|
|||
|
Tcl_Obj *objPtr);
|
|||
|
static void CompileEmbeddedScript(AssemblyEnv*, Tcl_Token*,
|
|||
|
const TalInstDesc*);
|
|||
|
static int DefineLabel(AssemblyEnv* envPtr, const char* label);
|
|||
|
static void DeleteMirrorJumpTable(JumptableInfo* jtPtr);
|
|||
|
static void DupAssembleCodeInternalRep(Tcl_Obj* src,
|
|||
|
Tcl_Obj* dest);
|
|||
|
static void FillInJumpOffsets(AssemblyEnv*);
|
|||
|
static int CreateMirrorJumpTable(AssemblyEnv* assemEnvPtr,
|
|||
|
Tcl_Obj* jumpTable);
|
|||
|
static int FindLocalVar(AssemblyEnv* envPtr,
|
|||
|
Tcl_Token** tokenPtrPtr);
|
|||
|
static int FinishAssembly(AssemblyEnv*);
|
|||
|
static void FreeAssembleCodeInternalRep(Tcl_Obj *objPtr);
|
|||
|
static void FreeAssemblyEnv(AssemblyEnv*);
|
|||
|
static int GetBooleanOperand(AssemblyEnv*, Tcl_Token**, int*);
|
|||
|
static int GetListIndexOperand(AssemblyEnv*, Tcl_Token**, int*);
|
|||
|
static int GetIntegerOperand(AssemblyEnv*, Tcl_Token**, int*);
|
|||
|
static int GetNextOperand(AssemblyEnv*, Tcl_Token**, Tcl_Obj**);
|
|||
|
static void LookForFreshCatches(BasicBlock*, BasicBlock**);
|
|||
|
static void MoveCodeForJumps(AssemblyEnv*, int);
|
|||
|
static void MoveExceptionRangesToBasicBlock(AssemblyEnv*, int);
|
|||
|
static AssemblyEnv* NewAssemblyEnv(CompileEnv*, int);
|
|||
|
static int ProcessCatches(AssemblyEnv*);
|
|||
|
static int ProcessCatchesInBasicBlock(AssemblyEnv*, BasicBlock*,
|
|||
|
BasicBlock*, enum BasicBlockCatchState, int);
|
|||
|
static void ResetVisitedBasicBlocks(AssemblyEnv*);
|
|||
|
static void ResolveJumpTableTargets(AssemblyEnv*, BasicBlock*);
|
|||
|
static void ReportUndefinedLabel(AssemblyEnv*, BasicBlock*,
|
|||
|
Tcl_Obj*);
|
|||
|
static void RestoreEmbeddedExceptionRanges(AssemblyEnv*);
|
|||
|
static int StackCheckBasicBlock(AssemblyEnv*, BasicBlock *,
|
|||
|
BasicBlock *, int);
|
|||
|
static BasicBlock* StartBasicBlock(AssemblyEnv*, int fallthrough,
|
|||
|
Tcl_Obj* jumpLabel);
|
|||
|
/* static int AdvanceIp(const unsigned char *pc); */
|
|||
|
static int StackCheckBasicBlock(AssemblyEnv*, BasicBlock *,
|
|||
|
BasicBlock *, int);
|
|||
|
static int StackCheckExit(AssemblyEnv*);
|
|||
|
static void StackFreshCatches(AssemblyEnv*, BasicBlock*, int,
|
|||
|
BasicBlock**, int*);
|
|||
|
static void SyncStackDepth(AssemblyEnv*);
|
|||
|
static int TclAssembleCode(CompileEnv* envPtr, const char* code,
|
|||
|
int codeLen, int flags);
|
|||
|
static void UnstackExpiredCatches(CompileEnv*, BasicBlock*, int,
|
|||
|
BasicBlock**, int*);
|
|||
|
|
|||
|
/*
|
|||
|
* Tcl_ObjType that describes bytecode emitted by the assembler.
|
|||
|
*/
|
|||
|
|
|||
|
static const Tcl_ObjType assembleCodeType = {
|
|||
|
"assemblecode",
|
|||
|
FreeAssembleCodeInternalRep, /* freeIntRepProc */
|
|||
|
DupAssembleCodeInternalRep, /* dupIntRepProc */
|
|||
|
NULL, /* updateStringProc */
|
|||
|
NULL /* setFromAnyProc */
|
|||
|
};
|
|||
|
|
|||
|
/*
|
|||
|
* Source instructions recognized in the Tcl Assembly Language (TAL)
|
|||
|
*/
|
|||
|
|
|||
|
static const TalInstDesc TalInstructionTable[] = {
|
|||
|
/* PUSH must be first, see the code near the end of TclAssembleCode */
|
|||
|
{"push", ASSEM_PUSH, (INST_PUSH1<<8
|
|||
|
| INST_PUSH4), 0, 1},
|
|||
|
|
|||
|
{"add", ASSEM_1BYTE, INST_ADD, 2, 1},
|
|||
|
{"append", ASSEM_LVT, (INST_APPEND_SCALAR1<<8
|
|||
|
| INST_APPEND_SCALAR4),1, 1},
|
|||
|
{"appendArray", ASSEM_LVT, (INST_APPEND_ARRAY1<<8
|
|||
|
| INST_APPEND_ARRAY4), 2, 1},
|
|||
|
{"appendArrayStk", ASSEM_1BYTE, INST_APPEND_ARRAY_STK, 3, 1},
|
|||
|
{"appendStk", ASSEM_1BYTE, INST_APPEND_STK, 2, 1},
|
|||
|
{"arrayExistsImm", ASSEM_LVT4, INST_ARRAY_EXISTS_IMM, 0, 1},
|
|||
|
{"arrayExistsStk", ASSEM_1BYTE, INST_ARRAY_EXISTS_STK, 1, 1},
|
|||
|
{"arrayMakeImm", ASSEM_LVT4, INST_ARRAY_MAKE_IMM, 0, 0},
|
|||
|
{"arrayMakeStk", ASSEM_1BYTE, INST_ARRAY_MAKE_STK, 1, 0},
|
|||
|
{"beginCatch", ASSEM_BEGIN_CATCH,
|
|||
|
INST_BEGIN_CATCH4, 0, 0},
|
|||
|
{"bitand", ASSEM_1BYTE, INST_BITAND, 2, 1},
|
|||
|
{"bitnot", ASSEM_1BYTE, INST_BITNOT, 1, 1},
|
|||
|
{"bitor", ASSEM_1BYTE, INST_BITOR, 2, 1},
|
|||
|
{"bitxor", ASSEM_1BYTE, INST_BITXOR, 2, 1},
|
|||
|
{"clockRead", ASSEM_CLOCK_READ, INST_CLOCK_READ, 0, 1},
|
|||
|
{"concat", ASSEM_CONCAT1, INST_STR_CONCAT1, INT_MIN,1},
|
|||
|
{"concatStk", ASSEM_LIST, INST_CONCAT_STK, INT_MIN,1},
|
|||
|
{"coroName", ASSEM_1BYTE, INST_COROUTINE_NAME, 0, 1},
|
|||
|
{"currentNamespace",ASSEM_1BYTE, INST_NS_CURRENT, 0, 1},
|
|||
|
{"dictAppend", ASSEM_LVT4, INST_DICT_APPEND, 2, 1},
|
|||
|
{"dictExists", ASSEM_DICT_GET, INST_DICT_EXISTS, INT_MIN,1},
|
|||
|
{"dictExpand", ASSEM_1BYTE, INST_DICT_EXPAND, 3, 1},
|
|||
|
{"dictGet", ASSEM_DICT_GET, INST_DICT_GET, INT_MIN,1},
|
|||
|
{"dictIncrImm", ASSEM_SINT4_LVT4,
|
|||
|
INST_DICT_INCR_IMM, 1, 1},
|
|||
|
{"dictLappend", ASSEM_LVT4, INST_DICT_LAPPEND, 2, 1},
|
|||
|
{"dictRecombineStk",ASSEM_1BYTE, INST_DICT_RECOMBINE_STK,3, 0},
|
|||
|
{"dictRecombineImm",ASSEM_LVT4, INST_DICT_RECOMBINE_IMM,2, 0},
|
|||
|
{"dictSet", ASSEM_DICT_SET, INST_DICT_SET, INT_MIN,1},
|
|||
|
{"dictUnset", ASSEM_DICT_UNSET,
|
|||
|
INST_DICT_UNSET, INT_MIN,1},
|
|||
|
{"div", ASSEM_1BYTE, INST_DIV, 2, 1},
|
|||
|
{"dup", ASSEM_1BYTE, INST_DUP, 1, 2},
|
|||
|
{"endCatch", ASSEM_END_CATCH,INST_END_CATCH, 0, 0},
|
|||
|
{"eq", ASSEM_1BYTE, INST_EQ, 2, 1},
|
|||
|
{"eval", ASSEM_EVAL, INST_EVAL_STK, 1, 1},
|
|||
|
{"evalStk", ASSEM_1BYTE, INST_EVAL_STK, 1, 1},
|
|||
|
{"exist", ASSEM_LVT4, INST_EXIST_SCALAR, 0, 1},
|
|||
|
{"existArray", ASSEM_LVT4, INST_EXIST_ARRAY, 1, 1},
|
|||
|
{"existArrayStk", ASSEM_1BYTE, INST_EXIST_ARRAY_STK, 2, 1},
|
|||
|
{"existStk", ASSEM_1BYTE, INST_EXIST_STK, 1, 1},
|
|||
|
{"expon", ASSEM_1BYTE, INST_EXPON, 2, 1},
|
|||
|
{"expr", ASSEM_EVAL, INST_EXPR_STK, 1, 1},
|
|||
|
{"exprStk", ASSEM_1BYTE, INST_EXPR_STK, 1, 1},
|
|||
|
{"ge", ASSEM_1BYTE, INST_GE, 2, 1},
|
|||
|
{"gt", ASSEM_1BYTE, INST_GT, 2, 1},
|
|||
|
{"incr", ASSEM_LVT1, INST_INCR_SCALAR1, 1, 1},
|
|||
|
{"incrArray", ASSEM_LVT1, INST_INCR_ARRAY1, 2, 1},
|
|||
|
{"incrArrayImm", ASSEM_LVT1_SINT1,
|
|||
|
INST_INCR_ARRAY1_IMM, 1, 1},
|
|||
|
{"incrArrayStk", ASSEM_1BYTE, INST_INCR_ARRAY_STK, 3, 1},
|
|||
|
{"incrArrayStkImm", ASSEM_SINT1, INST_INCR_ARRAY_STK_IMM,2, 1},
|
|||
|
{"incrImm", ASSEM_LVT1_SINT1,
|
|||
|
INST_INCR_SCALAR1_IMM, 0, 1},
|
|||
|
{"incrStk", ASSEM_1BYTE, INST_INCR_STK, 2, 1},
|
|||
|
{"incrStkImm", ASSEM_SINT1, INST_INCR_STK_IMM, 1, 1},
|
|||
|
{"infoLevelArgs", ASSEM_1BYTE, INST_INFO_LEVEL_ARGS, 1, 1},
|
|||
|
{"infoLevelNumber", ASSEM_1BYTE, INST_INFO_LEVEL_NUM, 0, 1},
|
|||
|
{"invokeStk", ASSEM_INVOKE, (INST_INVOKE_STK1 << 8
|
|||
|
| INST_INVOKE_STK4), INT_MIN,1},
|
|||
|
{"jump", ASSEM_JUMP, INST_JUMP1, 0, 0},
|
|||
|
{"jump4", ASSEM_JUMP4, INST_JUMP4, 0, 0},
|
|||
|
{"jumpFalse", ASSEM_JUMP, INST_JUMP_FALSE1, 1, 0},
|
|||
|
{"jumpFalse4", ASSEM_JUMP4, INST_JUMP_FALSE4, 1, 0},
|
|||
|
{"jumpTable", ASSEM_JUMPTABLE,INST_JUMP_TABLE, 1, 0},
|
|||
|
{"jumpTrue", ASSEM_JUMP, INST_JUMP_TRUE1, 1, 0},
|
|||
|
{"jumpTrue4", ASSEM_JUMP4, INST_JUMP_TRUE4, 1, 0},
|
|||
|
{"label", ASSEM_LABEL, 0, 0, 0},
|
|||
|
{"land", ASSEM_1BYTE, INST_LAND, 2, 1},
|
|||
|
{"lappend", ASSEM_LVT, (INST_LAPPEND_SCALAR1<<8
|
|||
|
| INST_LAPPEND_SCALAR4),
|
|||
|
1, 1},
|
|||
|
{"lappendArray", ASSEM_LVT, (INST_LAPPEND_ARRAY1<<8
|
|||
|
| INST_LAPPEND_ARRAY4),2, 1},
|
|||
|
{"lappendArrayStk", ASSEM_1BYTE, INST_LAPPEND_ARRAY_STK, 3, 1},
|
|||
|
{"lappendList", ASSEM_LVT4, INST_LAPPEND_LIST, 1, 1},
|
|||
|
{"lappendListArray",ASSEM_LVT4, INST_LAPPEND_LIST_ARRAY,2, 1},
|
|||
|
{"lappendListArrayStk", ASSEM_1BYTE,INST_LAPPEND_LIST_ARRAY_STK, 3, 1},
|
|||
|
{"lappendListStk", ASSEM_1BYTE, INST_LAPPEND_LIST_STK, 2, 1},
|
|||
|
{"lappendStk", ASSEM_1BYTE, INST_LAPPEND_STK, 2, 1},
|
|||
|
{"le", ASSEM_1BYTE, INST_LE, 2, 1},
|
|||
|
{"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},
|
|||
|
{"listLength", ASSEM_1BYTE, INST_LIST_LENGTH, 1, 1},
|
|||
|
{"listNotIn", ASSEM_1BYTE, INST_LIST_NOT_IN, 2, 1},
|
|||
|
{"load", ASSEM_LVT, (INST_LOAD_SCALAR1 << 8
|
|||
|
| INST_LOAD_SCALAR4), 0, 1},
|
|||
|
{"loadArray", ASSEM_LVT, (INST_LOAD_ARRAY1<<8
|
|||
|
| INST_LOAD_ARRAY4), 1, 1},
|
|||
|
{"loadArrayStk", ASSEM_1BYTE, INST_LOAD_ARRAY_STK, 2, 1},
|
|||
|
{"loadStk", ASSEM_1BYTE, INST_LOAD_STK, 1, 1},
|
|||
|
{"lor", ASSEM_1BYTE, INST_LOR, 2, 1},
|
|||
|
{"lsetFlat", ASSEM_LSET_FLAT,INST_LSET_FLAT, INT_MIN,1},
|
|||
|
{"lsetList", ASSEM_1BYTE, INST_LSET_LIST, 3, 1},
|
|||
|
{"lshift", ASSEM_1BYTE, INST_LSHIFT, 2, 1},
|
|||
|
{"lt", ASSEM_1BYTE, INST_LT, 2, 1},
|
|||
|
{"mod", ASSEM_1BYTE, INST_MOD, 2, 1},
|
|||
|
{"mult", ASSEM_1BYTE, INST_MULT, 2, 1},
|
|||
|
{"neq", ASSEM_1BYTE, INST_NEQ, 2, 1},
|
|||
|
{"nop", ASSEM_1BYTE, INST_NOP, 0, 0},
|
|||
|
{"not", ASSEM_1BYTE, INST_LNOT, 1, 1},
|
|||
|
{"nsupvar", ASSEM_LVT4, INST_NSUPVAR, 2, 1},
|
|||
|
{"numericType", ASSEM_1BYTE, INST_NUM_TYPE, 1, 1},
|
|||
|
{"originCmd", ASSEM_1BYTE, INST_ORIGIN_COMMAND, 1, 1},
|
|||
|
{"over", ASSEM_OVER, INST_OVER, INT_MIN,-1-1},
|
|||
|
{"pop", ASSEM_1BYTE, INST_POP, 1, 0},
|
|||
|
{"pushReturnCode", ASSEM_1BYTE, INST_PUSH_RETURN_CODE, 0, 1},
|
|||
|
{"pushReturnOpts", ASSEM_1BYTE, INST_PUSH_RETURN_OPTIONS,
|
|||
|
0, 1},
|
|||
|
{"pushResult", ASSEM_1BYTE, INST_PUSH_RESULT, 0, 1},
|
|||
|
{"regexp", ASSEM_REGEXP, INST_REGEXP, 2, 1},
|
|||
|
{"resolveCmd", ASSEM_1BYTE, INST_RESOLVE_COMMAND, 1, 1},
|
|||
|
{"reverse", ASSEM_REVERSE, INST_REVERSE, INT_MIN,-1-0},
|
|||
|
{"rshift", ASSEM_1BYTE, INST_RSHIFT, 2, 1},
|
|||
|
{"store", ASSEM_LVT, (INST_STORE_SCALAR1<<8
|
|||
|
| INST_STORE_SCALAR4), 1, 1},
|
|||
|
{"storeArray", ASSEM_LVT, (INST_STORE_ARRAY1<<8
|
|||
|
| INST_STORE_ARRAY4), 2, 1},
|
|||
|
{"storeArrayStk", ASSEM_1BYTE, INST_STORE_ARRAY_STK, 3, 1},
|
|||
|
{"storeStk", ASSEM_1BYTE, INST_STORE_STK, 2, 1},
|
|||
|
{"strcaseLower", ASSEM_1BYTE, INST_STR_LOWER, 1, 1},
|
|||
|
{"strcaseTitle", ASSEM_1BYTE, INST_STR_TITLE, 1, 1},
|
|||
|
{"strcaseUpper", ASSEM_1BYTE, INST_STR_UPPER, 1, 1},
|
|||
|
{"strcmp", ASSEM_1BYTE, INST_STR_CMP, 2, 1},
|
|||
|
{"strcat", ASSEM_CONCAT1, INST_STR_CONCAT1, INT_MIN,1},
|
|||
|
{"streq", ASSEM_1BYTE, INST_STR_EQ, 2, 1},
|
|||
|
{"strfind", ASSEM_1BYTE, INST_STR_FIND, 2, 1},
|
|||
|
{"strindex", ASSEM_1BYTE, INST_STR_INDEX, 2, 1},
|
|||
|
{"strlen", ASSEM_1BYTE, INST_STR_LEN, 1, 1},
|
|||
|
{"strmap", ASSEM_1BYTE, INST_STR_MAP, 3, 1},
|
|||
|
{"strmatch", ASSEM_BOOL, INST_STR_MATCH, 2, 1},
|
|||
|
{"strneq", ASSEM_1BYTE, INST_STR_NEQ, 2, 1},
|
|||
|
{"strrange", ASSEM_1BYTE, INST_STR_RANGE, 3, 1},
|
|||
|
{"strreplace", ASSEM_1BYTE, INST_STR_REPLACE, 4, 1},
|
|||
|
{"strrfind", ASSEM_1BYTE, INST_STR_FIND_LAST, 2, 1},
|
|||
|
{"strtrim", ASSEM_1BYTE, INST_STR_TRIM, 2, 1},
|
|||
|
{"strtrimLeft", ASSEM_1BYTE, INST_STR_TRIM_LEFT, 2, 1},
|
|||
|
{"strtrimRight", ASSEM_1BYTE, INST_STR_TRIM_RIGHT, 2, 1},
|
|||
|
{"sub", ASSEM_1BYTE, INST_SUB, 2, 1},
|
|||
|
{"tclooClass", ASSEM_1BYTE, INST_TCLOO_CLASS, 1, 1},
|
|||
|
{"tclooIsObject", ASSEM_1BYTE, INST_TCLOO_IS_OBJECT, 1, 1},
|
|||
|
{"tclooNamespace", ASSEM_1BYTE, INST_TCLOO_NS, 1, 1},
|
|||
|
{"tclooSelf", ASSEM_1BYTE, INST_TCLOO_SELF, 0, 1},
|
|||
|
{"tryCvtToBoolean", ASSEM_1BYTE, INST_TRY_CVT_TO_BOOLEAN,1, 2},
|
|||
|
{"tryCvtToNumeric", ASSEM_1BYTE, INST_TRY_CVT_TO_NUMERIC,1, 1},
|
|||
|
{"uminus", ASSEM_1BYTE, INST_UMINUS, 1, 1},
|
|||
|
{"unset", ASSEM_BOOL_LVT4,INST_UNSET_SCALAR, 0, 0},
|
|||
|
{"unsetArray", ASSEM_BOOL_LVT4,INST_UNSET_ARRAY, 1, 0},
|
|||
|
{"unsetArrayStk", ASSEM_BOOL, INST_UNSET_ARRAY_STK, 2, 0},
|
|||
|
{"unsetStk", ASSEM_BOOL, INST_UNSET_STK, 1, 0},
|
|||
|
{"uplus", ASSEM_1BYTE, INST_UPLUS, 1, 1},
|
|||
|
{"upvar", ASSEM_LVT4, INST_UPVAR, 2, 1},
|
|||
|
{"variable", ASSEM_LVT4, INST_VARIABLE, 1, 0},
|
|||
|
{"verifyDict", ASSEM_1BYTE, INST_DICT_VERIFY, 1, 0},
|
|||
|
{"yield", ASSEM_1BYTE, INST_YIELD, 1, 1},
|
|||
|
{NULL, ASSEM_1BYTE, 0, 0, 0}
|
|||
|
};
|
|||
|
|
|||
|
/*
|
|||
|
* List of instructions that cannot throw an exception under any
|
|||
|
* circumstances. These instructions are the ones that are permissible after
|
|||
|
* an exception is caught but before the corresponding exception range is
|
|||
|
* popped from the stack.
|
|||
|
* The instructions must be in ascending order by numeric operation code.
|
|||
|
*/
|
|||
|
|
|||
|
static const unsigned char NonThrowingByteCodes[] = {
|
|||
|
INST_PUSH1, INST_PUSH4, INST_POP, INST_DUP, /* 1-4 */
|
|||
|
INST_JUMP1, INST_JUMP4, /* 34-35 */
|
|||
|
INST_END_CATCH, INST_PUSH_RESULT, INST_PUSH_RETURN_CODE, /* 70-72 */
|
|||
|
INST_STR_EQ, INST_STR_NEQ, INST_STR_CMP, INST_STR_LEN, /* 73-76 */
|
|||
|
INST_LIST, /* 79 */
|
|||
|
INST_OVER, /* 95 */
|
|||
|
INST_PUSH_RETURN_OPTIONS, /* 108 */
|
|||
|
INST_REVERSE, /* 126 */
|
|||
|
INST_NOP, /* 132 */
|
|||
|
INST_STR_MAP, /* 143 */
|
|||
|
INST_STR_FIND, /* 144 */
|
|||
|
INST_COROUTINE_NAME, /* 149 */
|
|||
|
INST_NS_CURRENT, /* 151 */
|
|||
|
INST_INFO_LEVEL_NUM, /* 152 */
|
|||
|
INST_RESOLVE_COMMAND, /* 154 */
|
|||
|
INST_STR_TRIM, INST_STR_TRIM_LEFT, INST_STR_TRIM_RIGHT, /* 166-168 */
|
|||
|
INST_CONCAT_STK, /* 169 */
|
|||
|
INST_STR_UPPER, INST_STR_LOWER, INST_STR_TITLE, /* 170-172 */
|
|||
|
INST_NUM_TYPE /* 180 */
|
|||
|
};
|
|||
|
|
|||
|
/*
|
|||
|
* Helper macros.
|
|||
|
*/
|
|||
|
|
|||
|
#if defined(TCL_DEBUG_ASSEMBLY) && defined(__GNUC__) && __GNUC__ > 2
|
|||
|
#define DEBUG_PRINT(...) fprintf(stderr, ##__VA_ARGS__);fflush(stderr)
|
|||
|
#elif defined(__GNUC__) && __GNUC__ > 2
|
|||
|
#define DEBUG_PRINT(...) /* nothing */
|
|||
|
#else
|
|||
|
#define DEBUG_PRINT /* nothing */
|
|||
|
#endif
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* BBAdjustStackDepth --
|
|||
|
*
|
|||
|
* When an opcode is emitted, adjusts the stack information in the basic
|
|||
|
* block to reflect the number of operands produced and consumed.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* None.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* Updates minimum, maximum and final stack requirements in the basic
|
|||
|
* block.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static void
|
|||
|
BBAdjustStackDepth(
|
|||
|
BasicBlock *bbPtr, /* Structure describing the basic block */
|
|||
|
int consumed, /* Count of operands consumed by the
|
|||
|
* operation */
|
|||
|
int produced) /* Count of operands produced by the
|
|||
|
* operation */
|
|||
|
{
|
|||
|
int depth = bbPtr->finalStackDepth;
|
|||
|
|
|||
|
depth -= consumed;
|
|||
|
if (depth < bbPtr->minStackDepth) {
|
|||
|
bbPtr->minStackDepth = depth;
|
|||
|
}
|
|||
|
depth += produced;
|
|||
|
if (depth > bbPtr->maxStackDepth) {
|
|||
|
bbPtr->maxStackDepth = depth;
|
|||
|
}
|
|||
|
bbPtr->finalStackDepth = depth;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* BBUpdateStackReqs --
|
|||
|
*
|
|||
|
* Updates the stack requirements of a basic block, given the opcode
|
|||
|
* being emitted and an operand count.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* None.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* Updates min, max and final stack requirements in the basic block.
|
|||
|
*
|
|||
|
* Notes:
|
|||
|
* This function must not be called for instructions such as REVERSE and
|
|||
|
* OVER that are variadic but do not consume all their operands. Instead,
|
|||
|
* BBAdjustStackDepth should be called directly.
|
|||
|
*
|
|||
|
* count should be provided only for variadic operations. For operations
|
|||
|
* with known arity, count should be 0.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static void
|
|||
|
BBUpdateStackReqs(
|
|||
|
BasicBlock* bbPtr, /* Structure describing the basic block */
|
|||
|
int tblIdx, /* Index in TalInstructionTable of the
|
|||
|
* operation being assembled */
|
|||
|
int count) /* Count of operands for variadic insts */
|
|||
|
{
|
|||
|
int consumed = TalInstructionTable[tblIdx].operandsConsumed;
|
|||
|
int produced = TalInstructionTable[tblIdx].operandsProduced;
|
|||
|
|
|||
|
if (consumed == INT_MIN) {
|
|||
|
/*
|
|||
|
* The instruction is variadic; it consumes 'count' operands.
|
|||
|
*/
|
|||
|
|
|||
|
consumed = count;
|
|||
|
}
|
|||
|
if (produced < 0) {
|
|||
|
/*
|
|||
|
* The instruction leaves some of its variadic operands on the stack,
|
|||
|
* with net stack effect of '-1-produced'
|
|||
|
*/
|
|||
|
|
|||
|
produced = consumed - produced - 1;
|
|||
|
}
|
|||
|
BBAdjustStackDepth(bbPtr, consumed, produced);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* BBEmitOpcode, BBEmitInstInt1, BBEmitInstInt4 --
|
|||
|
*
|
|||
|
* Emit the opcode part of an instruction, or the entirety of an
|
|||
|
* instruction with a 1- or 4-byte operand, and adjust stack
|
|||
|
* requirements.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* None.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* Stores instruction and operand in the operand stream, and adjusts the
|
|||
|
* stack.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static void
|
|||
|
BBEmitOpcode(
|
|||
|
AssemblyEnv* assemEnvPtr, /* Assembly environment */
|
|||
|
int tblIdx, /* Table index in TalInstructionTable of op */
|
|||
|
int count) /* Operand count for variadic ops */
|
|||
|
{
|
|||
|
CompileEnv* envPtr = assemEnvPtr->envPtr;
|
|||
|
/* Compilation environment */
|
|||
|
BasicBlock* bbPtr = assemEnvPtr->curr_bb;
|
|||
|
/* Current basic block */
|
|||
|
int op = TalInstructionTable[tblIdx].tclInstCode & 0xFF;
|
|||
|
|
|||
|
/*
|
|||
|
* If this is the first instruction in a basic block, record its line
|
|||
|
* number.
|
|||
|
*/
|
|||
|
|
|||
|
if (bbPtr->startOffset == envPtr->codeNext - envPtr->codeStart) {
|
|||
|
bbPtr->startLine = assemEnvPtr->cmdLine;
|
|||
|
}
|
|||
|
|
|||
|
TclEmitInt1(op, envPtr);
|
|||
|
TclUpdateAtCmdStart(op, envPtr);
|
|||
|
BBUpdateStackReqs(bbPtr, tblIdx, count);
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
BBEmitInstInt1(
|
|||
|
AssemblyEnv* assemEnvPtr, /* Assembly environment */
|
|||
|
int tblIdx, /* Index in TalInstructionTable of op */
|
|||
|
int opnd, /* 1-byte operand */
|
|||
|
int count) /* Operand count for variadic ops */
|
|||
|
{
|
|||
|
BBEmitOpcode(assemEnvPtr, tblIdx, count);
|
|||
|
TclEmitInt1(opnd, assemEnvPtr->envPtr);
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
BBEmitInstInt4(
|
|||
|
AssemblyEnv* assemEnvPtr, /* Assembly environment */
|
|||
|
int tblIdx, /* Index in TalInstructionTable of op */
|
|||
|
int opnd, /* 4-byte operand */
|
|||
|
int count) /* Operand count for variadic ops */
|
|||
|
{
|
|||
|
BBEmitOpcode(assemEnvPtr, tblIdx, count);
|
|||
|
TclEmitInt4(opnd, assemEnvPtr->envPtr);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* BBEmitInst1or4 --
|
|||
|
*
|
|||
|
* Emits a 1- or 4-byte operation according to the magnitude of the
|
|||
|
* operand.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static void
|
|||
|
BBEmitInst1or4(
|
|||
|
AssemblyEnv* assemEnvPtr, /* Assembly environment */
|
|||
|
int tblIdx, /* Index in TalInstructionTable of op */
|
|||
|
int param, /* Variable-length parameter */
|
|||
|
int count) /* Arity if variadic */
|
|||
|
{
|
|||
|
CompileEnv* envPtr = assemEnvPtr->envPtr;
|
|||
|
/* Compilation environment */
|
|||
|
BasicBlock* bbPtr = assemEnvPtr->curr_bb;
|
|||
|
/* Current basic block */
|
|||
|
int op = TalInstructionTable[tblIdx].tclInstCode;
|
|||
|
|
|||
|
if (param <= 0xFF) {
|
|||
|
op >>= 8;
|
|||
|
} else {
|
|||
|
op &= 0xFF;
|
|||
|
}
|
|||
|
TclEmitInt1(op, envPtr);
|
|||
|
if (param <= 0xFF) {
|
|||
|
TclEmitInt1(param, envPtr);
|
|||
|
} else {
|
|||
|
TclEmitInt4(param, envPtr);
|
|||
|
}
|
|||
|
TclUpdateAtCmdStart(op, envPtr);
|
|||
|
BBUpdateStackReqs(bbPtr, tblIdx, count);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Tcl_AssembleObjCmd, TclNRAssembleObjCmd --
|
|||
|
*
|
|||
|
* Direct evaluation path for tcl::unsupported::assemble
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Returns a standard Tcl result.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* Assembles the code in objv[1], and executes it, so side effects
|
|||
|
* include whatever the code does.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
int
|
|||
|
Tcl_AssembleObjCmd(
|
|||
|
ClientData dummy, /* Not used. */
|
|||
|
Tcl_Interp *interp, /* Current interpreter. */
|
|||
|
int objc, /* Number of arguments. */
|
|||
|
Tcl_Obj *const objv[]) /* Argument objects. */
|
|||
|
{
|
|||
|
/*
|
|||
|
* Boilerplate - make sure that there is an NRE trampoline on the C stack
|
|||
|
* because there needs to be one in place to execute bytecode.
|
|||
|
*/
|
|||
|
|
|||
|
return Tcl_NRCallObjProc(interp, TclNRAssembleObjCmd, dummy, objc, objv);
|
|||
|
}
|
|||
|
|
|||
|
int
|
|||
|
TclNRAssembleObjCmd(
|
|||
|
ClientData dummy, /* Not used. */
|
|||
|
Tcl_Interp *interp, /* Current interpreter. */
|
|||
|
int objc, /* Number of arguments. */
|
|||
|
Tcl_Obj *const objv[]) /* Argument objects. */
|
|||
|
{
|
|||
|
ByteCode *codePtr; /* Pointer to the bytecode to execute */
|
|||
|
Tcl_Obj* backtrace; /* Object where extra error information is
|
|||
|
* constructed. */
|
|||
|
|
|||
|
(void)dummy;
|
|||
|
if (objc != 2) {
|
|||
|
Tcl_WrongNumArgs(interp, 1, objv, "bytecodeList");
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Assemble the source to bytecode.
|
|||
|
*/
|
|||
|
|
|||
|
codePtr = CompileAssembleObj(interp, objv[1]);
|
|||
|
|
|||
|
/*
|
|||
|
* On failure, report error line.
|
|||
|
*/
|
|||
|
|
|||
|
if (codePtr == NULL) {
|
|||
|
Tcl_AddErrorInfo(interp, "\n (\"");
|
|||
|
Tcl_AppendObjToErrorInfo(interp, objv[0]);
|
|||
|
Tcl_AddErrorInfo(interp, "\" body, line ");
|
|||
|
TclNewIntObj(backtrace, Tcl_GetErrorLine(interp));
|
|||
|
Tcl_AppendObjToErrorInfo(interp, backtrace);
|
|||
|
Tcl_AddErrorInfo(interp, ")");
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Use NRE to evaluate the bytecode from the trampoline.
|
|||
|
*/
|
|||
|
|
|||
|
return TclNRExecuteByteCode(interp, codePtr);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* CompileAssembleObj --
|
|||
|
*
|
|||
|
* Sets up and assembles Tcl bytecode for the direct-execution path in
|
|||
|
* the Tcl bytecode assembler.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Returns a pointer to the assembled code. Returns NULL if the assembly
|
|||
|
* fails for any reason, with an appropriate error message in the
|
|||
|
* interpreter.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static ByteCode *
|
|||
|
CompileAssembleObj(
|
|||
|
Tcl_Interp *interp, /* Tcl interpreter */
|
|||
|
Tcl_Obj *objPtr) /* Source code to assemble */
|
|||
|
{
|
|||
|
Interp *iPtr = (Interp *) interp;
|
|||
|
/* Internals of the interpreter */
|
|||
|
CompileEnv compEnv; /* Compilation environment structure */
|
|||
|
ByteCode *codePtr = NULL;
|
|||
|
/* Bytecode resulting from the assembly */
|
|||
|
Namespace* namespacePtr; /* Namespace in which variable and command
|
|||
|
* names in the bytecode resolve */
|
|||
|
int status; /* Status return from Tcl_AssembleCode */
|
|||
|
const char* source; /* String representation of the source code */
|
|||
|
int sourceLen; /* Length of the source code in bytes */
|
|||
|
|
|||
|
/*
|
|||
|
* Get the expression ByteCode from the object. If it exists, make sure it
|
|||
|
* is valid in the current context.
|
|||
|
*/
|
|||
|
|
|||
|
if (objPtr->typePtr == &assembleCodeType) {
|
|||
|
namespacePtr = iPtr->varFramePtr->nsPtr;
|
|||
|
codePtr = (ByteCode *)objPtr->internalRep.twoPtrValue.ptr1;
|
|||
|
if (((Interp *) *codePtr->interpHandle == iPtr)
|
|||
|
&& (codePtr->compileEpoch == iPtr->compileEpoch)
|
|||
|
&& (codePtr->nsPtr == namespacePtr)
|
|||
|
&& (codePtr->nsEpoch == namespacePtr->resolverEpoch)
|
|||
|
&& (codePtr->localCachePtr
|
|||
|
== iPtr->varFramePtr->localCachePtr)) {
|
|||
|
return codePtr;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Not valid, so free it and regenerate.
|
|||
|
*/
|
|||
|
|
|||
|
FreeAssembleCodeInternalRep(objPtr);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Set up the compilation environment, and assemble the code.
|
|||
|
*/
|
|||
|
|
|||
|
source = TclGetStringFromObj(objPtr, &sourceLen);
|
|||
|
TclInitCompileEnv(interp, &compEnv, source, sourceLen, NULL, 0);
|
|||
|
status = TclAssembleCode(&compEnv, source, sourceLen, TCL_EVAL_DIRECT);
|
|||
|
if (status != TCL_OK) {
|
|||
|
/*
|
|||
|
* Assembly failed. Clean up and report the error.
|
|||
|
*/
|
|||
|
TclFreeCompileEnv(&compEnv);
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Add a "done" instruction as the last instruction and change the object
|
|||
|
* into a ByteCode object. Ownership of the literal objects and aux data
|
|||
|
* items is given to the ByteCode object.
|
|||
|
*/
|
|||
|
|
|||
|
TclEmitOpcode(INST_DONE, &compEnv);
|
|||
|
TclInitByteCodeObj(objPtr, &compEnv);
|
|||
|
objPtr->typePtr = &assembleCodeType;
|
|||
|
TclFreeCompileEnv(&compEnv);
|
|||
|
|
|||
|
/*
|
|||
|
* Record the local variable context to which the bytecode pertains
|
|||
|
*/
|
|||
|
|
|||
|
codePtr = (ByteCode *)objPtr->internalRep.twoPtrValue.ptr1;
|
|||
|
if (iPtr->varFramePtr->localCachePtr) {
|
|||
|
codePtr->localCachePtr = iPtr->varFramePtr->localCachePtr;
|
|||
|
codePtr->localCachePtr->refCount++;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Report on what the assembler did.
|
|||
|
*/
|
|||
|
|
|||
|
#ifdef TCL_COMPILE_DEBUG
|
|||
|
if (tclTraceCompile >= 2) {
|
|||
|
TclPrintByteCodeObj(interp, objPtr);
|
|||
|
fflush(stdout);
|
|||
|
}
|
|||
|
#endif /* TCL_COMPILE_DEBUG */
|
|||
|
|
|||
|
return codePtr;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* TclCompileAssembleCmd --
|
|||
|
*
|
|||
|
* Compilation procedure for the '::tcl::unsupported::assemble' command.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Returns a standard Tcl result.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* Puts the result of assembling the code into the bytecode stream in
|
|||
|
* 'compileEnv'.
|
|||
|
*
|
|||
|
* This procedure makes sure that the command has a single arg, which is
|
|||
|
* constant. If that condition is met, the procedure calls TclAssembleCode to
|
|||
|
* produce bytecode for the given assembly code, and returns any error
|
|||
|
* resulting from the assembly.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
int
|
|||
|
TclCompileAssembleCmd(
|
|||
|
Tcl_Interp *interp, /* Used for error reporting. */
|
|||
|
Tcl_Parse *parsePtr, /* Points to a parse structure for the command
|
|||
|
* created by Tcl_ParseCommand. */
|
|||
|
Command *cmdPtr, /* Points to defintion of command being
|
|||
|
* compiled. */
|
|||
|
CompileEnv *envPtr) /* Holds resulting instructions. */
|
|||
|
{
|
|||
|
Tcl_Token *tokenPtr; /* Token in the input script */
|
|||
|
|
|||
|
int numCommands = envPtr->numCommands;
|
|||
|
int offset = envPtr->codeNext - envPtr->codeStart;
|
|||
|
int depth = envPtr->currStackDepth;
|
|||
|
(void)cmdPtr;
|
|||
|
/*
|
|||
|
* Make sure that the command has a single arg that is a simple word.
|
|||
|
*/
|
|||
|
|
|||
|
if (parsePtr->numWords != 2) {
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
tokenPtr = TokenAfter(parsePtr->tokenPtr);
|
|||
|
if (tokenPtr->type != TCL_TOKEN_SIMPLE_WORD) {
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Compile the code and convert any error from the compilation into
|
|||
|
* bytecode reporting the error;
|
|||
|
*/
|
|||
|
|
|||
|
if (TCL_ERROR == TclAssembleCode(envPtr, tokenPtr[1].start,
|
|||
|
tokenPtr[1].size, TCL_EVAL_DIRECT)) {
|
|||
|
|
|||
|
Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf(
|
|||
|
"\n (\"%.*s\" body, line %d)",
|
|||
|
parsePtr->tokenPtr->size, parsePtr->tokenPtr->start,
|
|||
|
Tcl_GetErrorLine(interp)));
|
|||
|
envPtr->numCommands = numCommands;
|
|||
|
envPtr->codeNext = envPtr->codeStart + offset;
|
|||
|
envPtr->currStackDepth = depth;
|
|||
|
TclCompileSyntaxError(interp, envPtr);
|
|||
|
}
|
|||
|
return TCL_OK;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* TclAssembleCode --
|
|||
|
*
|
|||
|
* Take a list of instructions in a Tcl_Obj, and assemble them to Tcl
|
|||
|
* bytecodes
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Returns TCL_OK on success, TCL_ERROR on failure. If 'flags' includes
|
|||
|
* TCL_EVAL_DIRECT, places an error message in the interpreter result.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* Adds byte codes to the compile environment, and updates the
|
|||
|
* environment's stack depth.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static int
|
|||
|
TclAssembleCode(
|
|||
|
CompileEnv *envPtr, /* Compilation environment that is to receive
|
|||
|
* the generated bytecode */
|
|||
|
const char* codePtr, /* Assembly-language code to be processed */
|
|||
|
int codeLen, /* Length of the code */
|
|||
|
int flags) /* OR'ed combination of flags */
|
|||
|
{
|
|||
|
Tcl_Interp* interp = (Tcl_Interp*) envPtr->iPtr;
|
|||
|
/* Tcl interpreter */
|
|||
|
/*
|
|||
|
* Walk through the assembly script using the Tcl parser. Each 'command'
|
|||
|
* will be an instruction or assembly directive.
|
|||
|
*/
|
|||
|
|
|||
|
const char* instPtr = codePtr;
|
|||
|
/* Where to start looking for a line of code */
|
|||
|
const char* nextPtr; /* Pointer to the end of the line of code */
|
|||
|
int bytesLeft = codeLen; /* Number of bytes of source code remaining to
|
|||
|
* be parsed */
|
|||
|
int status; /* Tcl status return */
|
|||
|
AssemblyEnv* assemEnvPtr = NewAssemblyEnv(envPtr, flags);
|
|||
|
Tcl_Parse* parsePtr = assemEnvPtr->parsePtr;
|
|||
|
|
|||
|
do {
|
|||
|
/*
|
|||
|
* Parse out one command line from the assembly script.
|
|||
|
*/
|
|||
|
|
|||
|
status = Tcl_ParseCommand(interp, instPtr, bytesLeft, 0, parsePtr);
|
|||
|
|
|||
|
/*
|
|||
|
* Report errors in the parse.
|
|||
|
*/
|
|||
|
|
|||
|
if (status != TCL_OK) {
|
|||
|
if (flags & TCL_EVAL_DIRECT) {
|
|||
|
Tcl_LogCommandInfo(interp, codePtr, parsePtr->commandStart,
|
|||
|
parsePtr->term + 1 - parsePtr->commandStart);
|
|||
|
}
|
|||
|
FreeAssemblyEnv(assemEnvPtr);
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Advance the pointers around any leading commentary.
|
|||
|
*/
|
|||
|
|
|||
|
TclAdvanceLines(&assemEnvPtr->cmdLine, instPtr,
|
|||
|
parsePtr->commandStart);
|
|||
|
TclAdvanceContinuations(&assemEnvPtr->cmdLine, &assemEnvPtr->clNext,
|
|||
|
parsePtr->commandStart - envPtr->source);
|
|||
|
|
|||
|
/*
|
|||
|
* Process the line of code.
|
|||
|
*/
|
|||
|
|
|||
|
if (parsePtr->numWords > 0) {
|
|||
|
int instLen = parsePtr->commandSize;
|
|||
|
/* Length in bytes of the current command */
|
|||
|
|
|||
|
if (parsePtr->term == parsePtr->commandStart + instLen - 1) {
|
|||
|
--instLen;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* If tracing, show each line assembled as it happens.
|
|||
|
*/
|
|||
|
|
|||
|
#ifdef TCL_COMPILE_DEBUG
|
|||
|
if ((tclTraceCompile >= 2) && (envPtr->procPtr == NULL)) {
|
|||
|
printf(" %4ld Assembling: ",
|
|||
|
(long)(envPtr->codeNext - envPtr->codeStart));
|
|||
|
TclPrintSource(stdout, parsePtr->commandStart,
|
|||
|
TclMin(instLen, 55));
|
|||
|
printf("\n");
|
|||
|
}
|
|||
|
#endif
|
|||
|
if (AssembleOneLine(assemEnvPtr) != TCL_OK) {
|
|||
|
if (flags & TCL_EVAL_DIRECT) {
|
|||
|
Tcl_LogCommandInfo(interp, codePtr,
|
|||
|
parsePtr->commandStart, instLen);
|
|||
|
}
|
|||
|
Tcl_FreeParse(parsePtr);
|
|||
|
FreeAssemblyEnv(assemEnvPtr);
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Advance to the next line of code.
|
|||
|
*/
|
|||
|
|
|||
|
nextPtr = parsePtr->commandStart + parsePtr->commandSize;
|
|||
|
bytesLeft -= (nextPtr - instPtr);
|
|||
|
instPtr = nextPtr;
|
|||
|
TclAdvanceLines(&assemEnvPtr->cmdLine, parsePtr->commandStart,
|
|||
|
instPtr);
|
|||
|
TclAdvanceContinuations(&assemEnvPtr->cmdLine, &assemEnvPtr->clNext,
|
|||
|
instPtr - envPtr->source);
|
|||
|
Tcl_FreeParse(parsePtr);
|
|||
|
} while (bytesLeft > 0);
|
|||
|
|
|||
|
/*
|
|||
|
* Done with parsing the code.
|
|||
|
*/
|
|||
|
|
|||
|
status = FinishAssembly(assemEnvPtr);
|
|||
|
FreeAssemblyEnv(assemEnvPtr);
|
|||
|
return status;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* NewAssemblyEnv --
|
|||
|
*
|
|||
|
* Creates an environment for the assembler to run in.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Allocates, initialises and returns an assembler environment
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static AssemblyEnv*
|
|||
|
NewAssemblyEnv(
|
|||
|
CompileEnv* envPtr, /* Compilation environment being used for code
|
|||
|
* generation*/
|
|||
|
int flags) /* Compilation flags (TCL_EVAL_DIRECT) */
|
|||
|
{
|
|||
|
Tcl_Interp* interp = (Tcl_Interp*) envPtr->iPtr;
|
|||
|
/* Tcl interpreter */
|
|||
|
AssemblyEnv* assemEnvPtr = (AssemblyEnv*)TclStackAlloc(interp, sizeof(AssemblyEnv));
|
|||
|
/* Assembler environment under construction */
|
|||
|
Tcl_Parse* parsePtr = (Tcl_Parse*)TclStackAlloc(interp, sizeof(Tcl_Parse));
|
|||
|
/* Parse of one line of assembly code */
|
|||
|
|
|||
|
assemEnvPtr->envPtr = envPtr;
|
|||
|
assemEnvPtr->parsePtr = parsePtr;
|
|||
|
assemEnvPtr->cmdLine = 1;
|
|||
|
assemEnvPtr->clNext = envPtr->clNext;
|
|||
|
|
|||
|
/*
|
|||
|
* Make the hashtables that store symbol resolution.
|
|||
|
*/
|
|||
|
|
|||
|
Tcl_InitHashTable(&assemEnvPtr->labelHash, TCL_STRING_KEYS);
|
|||
|
|
|||
|
/*
|
|||
|
* Start the first basic block.
|
|||
|
*/
|
|||
|
|
|||
|
assemEnvPtr->curr_bb = NULL;
|
|||
|
assemEnvPtr->head_bb = AllocBB(assemEnvPtr);
|
|||
|
assemEnvPtr->curr_bb = assemEnvPtr->head_bb;
|
|||
|
assemEnvPtr->head_bb->startLine = 1;
|
|||
|
|
|||
|
/*
|
|||
|
* Stash compilation flags.
|
|||
|
*/
|
|||
|
|
|||
|
assemEnvPtr->flags = flags;
|
|||
|
return assemEnvPtr;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* FreeAssemblyEnv --
|
|||
|
*
|
|||
|
* Cleans up the assembler environment when assembly is complete.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static void
|
|||
|
FreeAssemblyEnv(
|
|||
|
AssemblyEnv* assemEnvPtr) /* Environment to free */
|
|||
|
{
|
|||
|
CompileEnv* envPtr = assemEnvPtr->envPtr;
|
|||
|
/* Compilation environment being used for code
|
|||
|
* generation */
|
|||
|
Tcl_Interp* interp = (Tcl_Interp*) envPtr->iPtr;
|
|||
|
/* Tcl interpreter */
|
|||
|
BasicBlock* thisBB; /* Pointer to a basic block being deleted */
|
|||
|
BasicBlock* nextBB; /* Pointer to a deleted basic block's
|
|||
|
* successor */
|
|||
|
|
|||
|
/*
|
|||
|
* Free all the basic block structures.
|
|||
|
*/
|
|||
|
|
|||
|
for (thisBB = assemEnvPtr->head_bb; thisBB != NULL; thisBB = nextBB) {
|
|||
|
if (thisBB->jumpTarget != NULL) {
|
|||
|
Tcl_DecrRefCount(thisBB->jumpTarget);
|
|||
|
}
|
|||
|
if (thisBB->foreignExceptions != NULL) {
|
|||
|
ckfree(thisBB->foreignExceptions);
|
|||
|
}
|
|||
|
nextBB = thisBB->successor1;
|
|||
|
if (thisBB->jtPtr != NULL) {
|
|||
|
DeleteMirrorJumpTable(thisBB->jtPtr);
|
|||
|
thisBB->jtPtr = NULL;
|
|||
|
}
|
|||
|
ckfree(thisBB);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Dispose what's left.
|
|||
|
*/
|
|||
|
|
|||
|
Tcl_DeleteHashTable(&assemEnvPtr->labelHash);
|
|||
|
TclStackFree(interp, assemEnvPtr->parsePtr);
|
|||
|
TclStackFree(interp, assemEnvPtr);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* AssembleOneLine --
|
|||
|
*
|
|||
|
* Assembles a single command from an assembly language source.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Returns TCL_ERROR with an appropriate error message if the assembly
|
|||
|
* fails. Returns TCL_OK if the assembly succeeds. Updates the assembly
|
|||
|
* environment with the state of the assembly.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static int
|
|||
|
AssembleOneLine(
|
|||
|
AssemblyEnv* assemEnvPtr) /* State of the assembly */
|
|||
|
{
|
|||
|
CompileEnv* envPtr = assemEnvPtr->envPtr;
|
|||
|
/* Compilation environment being used for code
|
|||
|
* gen */
|
|||
|
Tcl_Interp* interp = (Tcl_Interp*) envPtr->iPtr;
|
|||
|
/* Tcl interpreter */
|
|||
|
Tcl_Parse* parsePtr = assemEnvPtr->parsePtr;
|
|||
|
/* Parse of the line of code */
|
|||
|
Tcl_Token* tokenPtr; /* Current token within the line of code */
|
|||
|
Tcl_Obj* instNameObj; /* Name of the instruction */
|
|||
|
int tblIdx; /* Index in TalInstructionTable of the
|
|||
|
* instruction */
|
|||
|
enum TalInstType instType; /* Type of the instruction */
|
|||
|
Tcl_Obj* operand1Obj = NULL;
|
|||
|
/* First operand to the instruction */
|
|||
|
const char* operand1; /* String rep of the operand */
|
|||
|
int operand1Len; /* String length of the operand */
|
|||
|
int opnd; /* Integer representation of an operand */
|
|||
|
int litIndex; /* Literal pool index of a constant */
|
|||
|
int localVar; /* LVT index of a local variable */
|
|||
|
int flags; /* Flags for a basic block */
|
|||
|
JumptableInfo* jtPtr; /* Pointer to a jumptable */
|
|||
|
int infoIndex; /* Index of the jumptable in auxdata */
|
|||
|
int status = TCL_ERROR; /* Return value from this function */
|
|||
|
|
|||
|
/*
|
|||
|
* Make sure that the instruction name is known at compile time.
|
|||
|
*/
|
|||
|
|
|||
|
tokenPtr = parsePtr->tokenPtr;
|
|||
|
if (GetNextOperand(assemEnvPtr, &tokenPtr, &instNameObj) != TCL_OK) {
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Look up the instruction name.
|
|||
|
*/
|
|||
|
|
|||
|
if (Tcl_GetIndexFromObjStruct(interp, instNameObj,
|
|||
|
&TalInstructionTable[0].name, sizeof(TalInstDesc), "instruction",
|
|||
|
TCL_EXACT, &tblIdx) != TCL_OK) {
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Vector on the type of instruction being processed.
|
|||
|
*/
|
|||
|
|
|||
|
instType = TalInstructionTable[tblIdx].instType;
|
|||
|
switch (instType) {
|
|||
|
|
|||
|
case ASSEM_PUSH:
|
|||
|
if (parsePtr->numWords != 2) {
|
|||
|
Tcl_WrongNumArgs(interp, 1, &instNameObj, "value");
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
if (GetNextOperand(assemEnvPtr, &tokenPtr, &operand1Obj) != TCL_OK) {
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
operand1 = Tcl_GetStringFromObj(operand1Obj, &operand1Len);
|
|||
|
litIndex = TclRegisterNewLiteral(envPtr, operand1, operand1Len);
|
|||
|
BBEmitInst1or4(assemEnvPtr, tblIdx, litIndex, 0);
|
|||
|
break;
|
|||
|
|
|||
|
case ASSEM_1BYTE:
|
|||
|
if (parsePtr->numWords != 1) {
|
|||
|
Tcl_WrongNumArgs(interp, 1, &instNameObj, "");
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
BBEmitOpcode(assemEnvPtr, tblIdx, 0);
|
|||
|
break;
|
|||
|
|
|||
|
case ASSEM_BEGIN_CATCH:
|
|||
|
/*
|
|||
|
* Emit the BEGIN_CATCH instruction with the code offset of the
|
|||
|
* exception branch target instead of the exception range index. The
|
|||
|
* correct index will be generated and inserted later, when catches
|
|||
|
* are being resolved.
|
|||
|
*/
|
|||
|
|
|||
|
if (parsePtr->numWords != 2) {
|
|||
|
Tcl_WrongNumArgs(interp, 1, &instNameObj, "label");
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
if (GetNextOperand(assemEnvPtr, &tokenPtr, &operand1Obj) != TCL_OK) {
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
assemEnvPtr->curr_bb->jumpLine = assemEnvPtr->cmdLine;
|
|||
|
assemEnvPtr->curr_bb->jumpOffset = envPtr->codeNext-envPtr->codeStart;
|
|||
|
BBEmitInstInt4(assemEnvPtr, tblIdx, 0, 0);
|
|||
|
assemEnvPtr->curr_bb->flags |= BB_BEGINCATCH;
|
|||
|
StartBasicBlock(assemEnvPtr, BB_FALLTHRU, operand1Obj);
|
|||
|
break;
|
|||
|
|
|||
|
case ASSEM_BOOL:
|
|||
|
if (parsePtr->numWords != 2) {
|
|||
|
Tcl_WrongNumArgs(interp, 1, &instNameObj, "boolean");
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
if (GetBooleanOperand(assemEnvPtr, &tokenPtr, &opnd) != TCL_OK) {
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
BBEmitInstInt1(assemEnvPtr, tblIdx, opnd, 0);
|
|||
|
break;
|
|||
|
|
|||
|
case ASSEM_BOOL_LVT4:
|
|||
|
if (parsePtr->numWords != 3) {
|
|||
|
Tcl_WrongNumArgs(interp, 1, &instNameObj, "boolean varName");
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
if (GetBooleanOperand(assemEnvPtr, &tokenPtr, &opnd) != TCL_OK) {
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
localVar = FindLocalVar(assemEnvPtr, &tokenPtr);
|
|||
|
if (localVar < 0) {
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
BBEmitInstInt1(assemEnvPtr, tblIdx, opnd, 0);
|
|||
|
TclEmitInt4(localVar, envPtr);
|
|||
|
break;
|
|||
|
|
|||
|
case ASSEM_CLOCK_READ:
|
|||
|
if (parsePtr->numWords != 2) {
|
|||
|
Tcl_WrongNumArgs(interp, 1, &instNameObj, "imm8");
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
if (GetIntegerOperand(assemEnvPtr, &tokenPtr, &opnd) != TCL_OK) {
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
if (opnd < 0 || opnd > 3) {
|
|||
|
Tcl_SetObjResult(interp,
|
|||
|
Tcl_NewStringObj("operand must be [0..3]", -1));
|
|||
|
Tcl_SetErrorCode(interp, "TCL", "ASSEM", "OPERAND<0,>3", NULL);
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
BBEmitInstInt1(assemEnvPtr, tblIdx, opnd, opnd);
|
|||
|
break;
|
|||
|
|
|||
|
case ASSEM_CONCAT1:
|
|||
|
if (parsePtr->numWords != 2) {
|
|||
|
Tcl_WrongNumArgs(interp, 1, &instNameObj, "imm8");
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
if (GetIntegerOperand(assemEnvPtr, &tokenPtr, &opnd) != TCL_OK
|
|||
|
|| CheckOneByte(interp, opnd) != TCL_OK
|
|||
|
|| CheckStrictlyPositive(interp, opnd) != TCL_OK) {
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
BBEmitInstInt1(assemEnvPtr, tblIdx, opnd, opnd);
|
|||
|
break;
|
|||
|
|
|||
|
case ASSEM_DICT_GET:
|
|||
|
if (parsePtr->numWords != 2) {
|
|||
|
Tcl_WrongNumArgs(interp, 1, &instNameObj, "count");
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
if (GetIntegerOperand(assemEnvPtr, &tokenPtr, &opnd) != TCL_OK
|
|||
|
|| CheckStrictlyPositive(interp, opnd) != TCL_OK) {
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
BBEmitInstInt4(assemEnvPtr, tblIdx, opnd, opnd+1);
|
|||
|
break;
|
|||
|
|
|||
|
case ASSEM_DICT_SET:
|
|||
|
if (parsePtr->numWords != 3) {
|
|||
|
Tcl_WrongNumArgs(interp, 1, &instNameObj, "count varName");
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
if (GetIntegerOperand(assemEnvPtr, &tokenPtr, &opnd) != TCL_OK
|
|||
|
|| CheckStrictlyPositive(interp, opnd) != TCL_OK) {
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
localVar = FindLocalVar(assemEnvPtr, &tokenPtr);
|
|||
|
if (localVar < 0) {
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
BBEmitInstInt4(assemEnvPtr, tblIdx, opnd, opnd+1);
|
|||
|
TclEmitInt4(localVar, envPtr);
|
|||
|
break;
|
|||
|
|
|||
|
case ASSEM_DICT_UNSET:
|
|||
|
if (parsePtr->numWords != 3) {
|
|||
|
Tcl_WrongNumArgs(interp, 1, &instNameObj, "count varName");
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
if (GetIntegerOperand(assemEnvPtr, &tokenPtr, &opnd) != TCL_OK
|
|||
|
|| CheckStrictlyPositive(interp, opnd) != TCL_OK) {
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
localVar = FindLocalVar(assemEnvPtr, &tokenPtr);
|
|||
|
if (localVar < 0) {
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
BBEmitInstInt4(assemEnvPtr, tblIdx, opnd, opnd);
|
|||
|
TclEmitInt4(localVar, envPtr);
|
|||
|
break;
|
|||
|
|
|||
|
case ASSEM_END_CATCH:
|
|||
|
if (parsePtr->numWords != 1) {
|
|||
|
Tcl_WrongNumArgs(interp, 1, &instNameObj, "");
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
assemEnvPtr->curr_bb->flags |= BB_ENDCATCH;
|
|||
|
BBEmitOpcode(assemEnvPtr, tblIdx, 0);
|
|||
|
StartBasicBlock(assemEnvPtr, BB_FALLTHRU, NULL);
|
|||
|
break;
|
|||
|
|
|||
|
case ASSEM_EVAL:
|
|||
|
/* TODO - Refactor this stuff into a subroutine that takes the inst
|
|||
|
* code, the message ("script" or "expression") and an evaluator
|
|||
|
* callback that calls TclCompileScript or TclCompileExpr. */
|
|||
|
|
|||
|
if (parsePtr->numWords != 2) {
|
|||
|
Tcl_WrongNumArgs(interp, 1, &instNameObj,
|
|||
|
((TalInstructionTable[tblIdx].tclInstCode
|
|||
|
== INST_EVAL_STK) ? "script" : "expression"));
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
if (tokenPtr->type == TCL_TOKEN_SIMPLE_WORD) {
|
|||
|
CompileEmbeddedScript(assemEnvPtr, tokenPtr+1,
|
|||
|
TalInstructionTable+tblIdx);
|
|||
|
} else if (GetNextOperand(assemEnvPtr, &tokenPtr,
|
|||
|
&operand1Obj) != TCL_OK) {
|
|||
|
goto cleanup;
|
|||
|
} else {
|
|||
|
operand1 = Tcl_GetStringFromObj(operand1Obj, &operand1Len);
|
|||
|
litIndex = TclRegisterNewLiteral(envPtr, operand1, operand1Len);
|
|||
|
|
|||
|
/*
|
|||
|
* Assumes that PUSH is the first slot!
|
|||
|
*/
|
|||
|
|
|||
|
BBEmitInst1or4(assemEnvPtr, 0, litIndex, 0);
|
|||
|
BBEmitOpcode(assemEnvPtr, tblIdx, 0);
|
|||
|
}
|
|||
|
break;
|
|||
|
|
|||
|
case ASSEM_INVOKE:
|
|||
|
if (parsePtr->numWords != 2) {
|
|||
|
Tcl_WrongNumArgs(interp, 1, &instNameObj, "count");
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
if (GetIntegerOperand(assemEnvPtr, &tokenPtr, &opnd) != TCL_OK
|
|||
|
|| CheckStrictlyPositive(interp, opnd) != TCL_OK) {
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
|
|||
|
BBEmitInst1or4(assemEnvPtr, tblIdx, opnd, opnd);
|
|||
|
break;
|
|||
|
|
|||
|
case ASSEM_JUMP:
|
|||
|
case ASSEM_JUMP4:
|
|||
|
if (parsePtr->numWords != 2) {
|
|||
|
Tcl_WrongNumArgs(interp, 1, &instNameObj, "label");
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
if (GetNextOperand(assemEnvPtr, &tokenPtr, &operand1Obj) != TCL_OK) {
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
assemEnvPtr->curr_bb->jumpOffset = envPtr->codeNext-envPtr->codeStart;
|
|||
|
if (instType == ASSEM_JUMP) {
|
|||
|
flags = BB_JUMP1;
|
|||
|
BBEmitInstInt1(assemEnvPtr, tblIdx, 0, 0);
|
|||
|
} else {
|
|||
|
flags = 0;
|
|||
|
BBEmitInstInt4(assemEnvPtr, tblIdx, 0, 0);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Start a new basic block at the instruction following the jump.
|
|||
|
*/
|
|||
|
|
|||
|
assemEnvPtr->curr_bb->jumpLine = assemEnvPtr->cmdLine;
|
|||
|
if (TalInstructionTable[tblIdx].operandsConsumed != 0) {
|
|||
|
flags |= BB_FALLTHRU;
|
|||
|
}
|
|||
|
StartBasicBlock(assemEnvPtr, flags, operand1Obj);
|
|||
|
break;
|
|||
|
|
|||
|
case ASSEM_JUMPTABLE:
|
|||
|
if (parsePtr->numWords != 2) {
|
|||
|
Tcl_WrongNumArgs(interp, 1, &instNameObj, "table");
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
if (GetNextOperand(assemEnvPtr, &tokenPtr, &operand1Obj) != TCL_OK) {
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
|
|||
|
jtPtr = (JumptableInfo*)ckalloc(sizeof(JumptableInfo));
|
|||
|
|
|||
|
Tcl_InitHashTable(&jtPtr->hashTable, TCL_STRING_KEYS);
|
|||
|
assemEnvPtr->curr_bb->jumpLine = assemEnvPtr->cmdLine;
|
|||
|
assemEnvPtr->curr_bb->jumpOffset = envPtr->codeNext-envPtr->codeStart;
|
|||
|
DEBUG_PRINT("bb %p jumpLine %d jumpOffset %d\n",
|
|||
|
assemEnvPtr->curr_bb, assemEnvPtr->cmdLine,
|
|||
|
envPtr->codeNext - envPtr->codeStart);
|
|||
|
|
|||
|
infoIndex = TclCreateAuxData(jtPtr, &tclJumptableInfoType, envPtr);
|
|||
|
DEBUG_PRINT("auxdata index=%d\n", infoIndex);
|
|||
|
|
|||
|
BBEmitInstInt4(assemEnvPtr, tblIdx, infoIndex, 0);
|
|||
|
if (CreateMirrorJumpTable(assemEnvPtr, operand1Obj) != TCL_OK) {
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
StartBasicBlock(assemEnvPtr, BB_JUMPTABLE|BB_FALLTHRU, NULL);
|
|||
|
break;
|
|||
|
|
|||
|
case ASSEM_LABEL:
|
|||
|
if (parsePtr->numWords != 2) {
|
|||
|
Tcl_WrongNumArgs(interp, 1, &instNameObj, "name");
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
if (GetNextOperand(assemEnvPtr, &tokenPtr, &operand1Obj) != TCL_OK) {
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Add the (label_name, address) pair to the hash table.
|
|||
|
*/
|
|||
|
|
|||
|
if (DefineLabel(assemEnvPtr, Tcl_GetString(operand1Obj)) != TCL_OK) {
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
break;
|
|||
|
|
|||
|
case ASSEM_LINDEX_MULTI:
|
|||
|
if (parsePtr->numWords != 2) {
|
|||
|
Tcl_WrongNumArgs(interp, 1, &instNameObj, "count");
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
if (GetIntegerOperand(assemEnvPtr, &tokenPtr, &opnd) != TCL_OK
|
|||
|
|| CheckStrictlyPositive(interp, opnd) != TCL_OK) {
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
BBEmitInstInt4(assemEnvPtr, tblIdx, opnd, opnd);
|
|||
|
break;
|
|||
|
|
|||
|
case ASSEM_LIST:
|
|||
|
if (parsePtr->numWords != 2) {
|
|||
|
Tcl_WrongNumArgs(interp, 1, &instNameObj, "count");
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
if (GetIntegerOperand(assemEnvPtr, &tokenPtr, &opnd) != TCL_OK
|
|||
|
|| CheckNonNegative(interp, opnd) != TCL_OK) {
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
BBEmitInstInt4(assemEnvPtr, tblIdx, opnd, opnd);
|
|||
|
break;
|
|||
|
|
|||
|
case ASSEM_INDEX:
|
|||
|
if (parsePtr->numWords != 2) {
|
|||
|
Tcl_WrongNumArgs(interp, 1, &instNameObj, "count");
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
if (GetListIndexOperand(assemEnvPtr, &tokenPtr, &opnd) != TCL_OK) {
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
BBEmitInstInt4(assemEnvPtr, tblIdx, opnd, opnd);
|
|||
|
break;
|
|||
|
|
|||
|
case ASSEM_LSET_FLAT:
|
|||
|
if (parsePtr->numWords != 2) {
|
|||
|
Tcl_WrongNumArgs(interp, 1, &instNameObj, "count");
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
if (GetIntegerOperand(assemEnvPtr, &tokenPtr, &opnd) != TCL_OK) {
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
if (opnd < 2) {
|
|||
|
if (assemEnvPtr->flags & TCL_EVAL_DIRECT) {
|
|||
|
Tcl_SetObjResult(interp,
|
|||
|
Tcl_NewStringObj("operand must be >=2", -1));
|
|||
|
Tcl_SetErrorCode(interp, "TCL", "ASSEM", "OPERAND>=2", NULL);
|
|||
|
}
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
BBEmitInstInt4(assemEnvPtr, tblIdx, opnd, opnd);
|
|||
|
break;
|
|||
|
|
|||
|
case ASSEM_LVT:
|
|||
|
if (parsePtr->numWords != 2) {
|
|||
|
Tcl_WrongNumArgs(interp, 1, &instNameObj, "varname");
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
localVar = FindLocalVar(assemEnvPtr, &tokenPtr);
|
|||
|
if (localVar < 0) {
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
BBEmitInst1or4(assemEnvPtr, tblIdx, localVar, 0);
|
|||
|
break;
|
|||
|
|
|||
|
case ASSEM_LVT1:
|
|||
|
if (parsePtr->numWords != 2) {
|
|||
|
Tcl_WrongNumArgs(interp, 1, &instNameObj, "varname");
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
localVar = FindLocalVar(assemEnvPtr, &tokenPtr);
|
|||
|
if (localVar < 0 || CheckOneByte(interp, localVar)) {
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
BBEmitInstInt1(assemEnvPtr, tblIdx, localVar, 0);
|
|||
|
break;
|
|||
|
|
|||
|
case ASSEM_LVT1_SINT1:
|
|||
|
if (parsePtr->numWords != 3) {
|
|||
|
Tcl_WrongNumArgs(interp, 1, &instNameObj, "varName imm8");
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
localVar = FindLocalVar(assemEnvPtr, &tokenPtr);
|
|||
|
if (localVar < 0 || CheckOneByte(interp, localVar)
|
|||
|
|| GetIntegerOperand(assemEnvPtr, &tokenPtr, &opnd) != TCL_OK
|
|||
|
|| CheckSignedOneByte(interp, opnd)) {
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
BBEmitInstInt1(assemEnvPtr, tblIdx, localVar, 0);
|
|||
|
TclEmitInt1(opnd, envPtr);
|
|||
|
break;
|
|||
|
|
|||
|
case ASSEM_LVT4:
|
|||
|
if (parsePtr->numWords != 2) {
|
|||
|
Tcl_WrongNumArgs(interp, 1, &instNameObj, "varname");
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
localVar = FindLocalVar(assemEnvPtr, &tokenPtr);
|
|||
|
if (localVar < 0) {
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
BBEmitInstInt4(assemEnvPtr, tblIdx, localVar, 0);
|
|||
|
break;
|
|||
|
|
|||
|
case ASSEM_OVER:
|
|||
|
if (parsePtr->numWords != 2) {
|
|||
|
Tcl_WrongNumArgs(interp, 1, &instNameObj, "count");
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
if (GetIntegerOperand(assemEnvPtr, &tokenPtr, &opnd) != TCL_OK
|
|||
|
|| CheckNonNegative(interp, opnd) != TCL_OK) {
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
BBEmitInstInt4(assemEnvPtr, tblIdx, opnd, opnd+1);
|
|||
|
break;
|
|||
|
|
|||
|
case ASSEM_REGEXP:
|
|||
|
if (parsePtr->numWords != 2) {
|
|||
|
Tcl_WrongNumArgs(interp, 1, &instNameObj, "boolean");
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
if (GetBooleanOperand(assemEnvPtr, &tokenPtr, &opnd) != TCL_OK) {
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
{
|
|||
|
BBEmitInstInt1(assemEnvPtr, tblIdx, TCL_REG_ADVANCED | (opnd ? TCL_REG_NOCASE : 0), 0);
|
|||
|
}
|
|||
|
break;
|
|||
|
|
|||
|
case ASSEM_REVERSE:
|
|||
|
if (parsePtr->numWords != 2) {
|
|||
|
Tcl_WrongNumArgs(interp, 1, &instNameObj, "count");
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
if (GetIntegerOperand(assemEnvPtr, &tokenPtr, &opnd) != TCL_OK
|
|||
|
|| CheckNonNegative(interp, opnd) != TCL_OK) {
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
BBEmitInstInt4(assemEnvPtr, tblIdx, opnd, opnd);
|
|||
|
break;
|
|||
|
|
|||
|
case ASSEM_SINT1:
|
|||
|
if (parsePtr->numWords != 2) {
|
|||
|
Tcl_WrongNumArgs(interp, 1, &instNameObj, "imm8");
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
if (GetIntegerOperand(assemEnvPtr, &tokenPtr, &opnd) != TCL_OK
|
|||
|
|| CheckSignedOneByte(interp, opnd) != TCL_OK) {
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
BBEmitInstInt1(assemEnvPtr, tblIdx, opnd, 0);
|
|||
|
break;
|
|||
|
|
|||
|
case ASSEM_SINT4_LVT4:
|
|||
|
if (parsePtr->numWords != 3) {
|
|||
|
Tcl_WrongNumArgs(interp, 1, &instNameObj, "count varName");
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
if (GetIntegerOperand(assemEnvPtr, &tokenPtr, &opnd) != TCL_OK) {
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
localVar = FindLocalVar(assemEnvPtr, &tokenPtr);
|
|||
|
if (localVar < 0) {
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
BBEmitInstInt4(assemEnvPtr, tblIdx, opnd, 0);
|
|||
|
TclEmitInt4(localVar, envPtr);
|
|||
|
break;
|
|||
|
|
|||
|
default:
|
|||
|
Tcl_Panic("Instruction \"%s\" could not be found, can't happen\n",
|
|||
|
Tcl_GetString(instNameObj));
|
|||
|
}
|
|||
|
|
|||
|
status = TCL_OK;
|
|||
|
cleanup:
|
|||
|
Tcl_DecrRefCount(instNameObj);
|
|||
|
if (operand1Obj) {
|
|||
|
Tcl_DecrRefCount(operand1Obj);
|
|||
|
}
|
|||
|
return status;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* CompileEmbeddedScript --
|
|||
|
*
|
|||
|
* Compile an embedded 'eval' or 'expr' that appears in assembly code.
|
|||
|
*
|
|||
|
* This procedure is called when the 'eval' or 'expr' assembly directive is
|
|||
|
* encountered, and the argument to the directive is a simple word that
|
|||
|
* requires no substitution. The appropriate compiler (TclCompileScript or
|
|||
|
* TclCompileExpr) is invoked recursively, and emits bytecode.
|
|||
|
*
|
|||
|
* Before the compiler is invoked, the compilation environment's stack
|
|||
|
* consumption is reset to zero. Upon return from the compilation, the net
|
|||
|
* stack effect of the compilation is in the compiler env, and this stack
|
|||
|
* effect is posted to the assembler environment. The compile environment's
|
|||
|
* stack consumption is then restored to what it was before (which is actually
|
|||
|
* the state of the stack on entry to the block of assembly code).
|
|||
|
*
|
|||
|
* Any exception ranges pushed by the compilation are copied to the basic
|
|||
|
* block and removed from the compiler environment. They will be rebuilt at
|
|||
|
* the end of assembly, when the exception stack depth is actually known.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static void
|
|||
|
CompileEmbeddedScript(
|
|||
|
AssemblyEnv* assemEnvPtr, /* Assembly environment */
|
|||
|
Tcl_Token* tokenPtr, /* Tcl_Token containing the script */
|
|||
|
const TalInstDesc* instPtr) /* Instruction that determines whether
|
|||
|
* the script is 'expr' or 'eval' */
|
|||
|
{
|
|||
|
CompileEnv* envPtr = assemEnvPtr->envPtr;
|
|||
|
/* Compilation environment */
|
|||
|
Tcl_Interp* interp = (Tcl_Interp*) envPtr->iPtr;
|
|||
|
/* Tcl interpreter */
|
|||
|
|
|||
|
/*
|
|||
|
* The expression or script is not only known at compile time, but
|
|||
|
* actually a "simple word". It can be compiled inline by invoking the
|
|||
|
* compiler recursively.
|
|||
|
*
|
|||
|
* Save away the stack depth and reset it before compiling the script.
|
|||
|
* We'll record the stack usage of the script in the BasicBlock, and
|
|||
|
* accumulate it together with the stack usage of the enclosing assembly
|
|||
|
* code.
|
|||
|
*/
|
|||
|
|
|||
|
int savedStackDepth = envPtr->currStackDepth;
|
|||
|
int savedMaxStackDepth = envPtr->maxStackDepth;
|
|||
|
int savedExceptArrayNext = envPtr->exceptArrayNext;
|
|||
|
|
|||
|
envPtr->currStackDepth = 0;
|
|||
|
envPtr->maxStackDepth = 0;
|
|||
|
|
|||
|
StartBasicBlock(assemEnvPtr, BB_FALLTHRU, NULL);
|
|||
|
switch(instPtr->tclInstCode) {
|
|||
|
case INST_EVAL_STK:
|
|||
|
TclCompileScript(interp, tokenPtr->start, tokenPtr->size, envPtr);
|
|||
|
break;
|
|||
|
case INST_EXPR_STK:
|
|||
|
TclCompileExpr(interp, tokenPtr->start, tokenPtr->size, envPtr, 1);
|
|||
|
break;
|
|||
|
default:
|
|||
|
Tcl_Panic("no ASSEM_EVAL case for %s (%d), can't happen",
|
|||
|
instPtr->name, instPtr->tclInstCode);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Roll up the stack usage of the embedded block into the assembler
|
|||
|
* environment.
|
|||
|
*/
|
|||
|
|
|||
|
SyncStackDepth(assemEnvPtr);
|
|||
|
envPtr->currStackDepth = savedStackDepth;
|
|||
|
envPtr->maxStackDepth = savedMaxStackDepth;
|
|||
|
|
|||
|
/*
|
|||
|
* Save any exception ranges that were pushed by the compiler; they will
|
|||
|
* need to be fixed up once the stack depth is known.
|
|||
|
*/
|
|||
|
|
|||
|
MoveExceptionRangesToBasicBlock(assemEnvPtr, savedExceptArrayNext);
|
|||
|
|
|||
|
/*
|
|||
|
* Flush the current basic block.
|
|||
|
*/
|
|||
|
|
|||
|
StartBasicBlock(assemEnvPtr, BB_FALLTHRU, NULL);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* SyncStackDepth --
|
|||
|
*
|
|||
|
* Copies the stack depth from the compile environment to a basic block.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* Current and max stack depth in the current basic block are adjusted.
|
|||
|
*
|
|||
|
* This procedure is called on return from invoking the compiler for the
|
|||
|
* 'eval' and 'expr' operations. It adjusts the stack depth of the current
|
|||
|
* basic block to reflect the stack required by the just-compiled code.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static void
|
|||
|
SyncStackDepth(
|
|||
|
AssemblyEnv* assemEnvPtr) /* Assembly environment */
|
|||
|
{
|
|||
|
CompileEnv* envPtr = assemEnvPtr->envPtr;
|
|||
|
/* Compilation environment */
|
|||
|
BasicBlock* curr_bb = assemEnvPtr->curr_bb;
|
|||
|
/* Current basic block */
|
|||
|
int maxStackDepth = curr_bb->finalStackDepth + envPtr->maxStackDepth;
|
|||
|
/* Max stack depth in the basic block */
|
|||
|
|
|||
|
if (maxStackDepth > curr_bb->maxStackDepth) {
|
|||
|
curr_bb->maxStackDepth = maxStackDepth;
|
|||
|
}
|
|||
|
curr_bb->finalStackDepth += envPtr->currStackDepth;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* MoveExceptionRangesToBasicBlock --
|
|||
|
*
|
|||
|
* Removes exception ranges that were created by compiling an embedded
|
|||
|
* script from the CompileEnv, and stores them in the BasicBlock. They
|
|||
|
* will be reinstalled, at the correct stack depth, after control flow
|
|||
|
* analysis is complete on the assembly code.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static void
|
|||
|
MoveExceptionRangesToBasicBlock(
|
|||
|
AssemblyEnv* assemEnvPtr, /* Assembly environment */
|
|||
|
int savedExceptArrayNext) /* Saved index of the end of the exception
|
|||
|
* range array */
|
|||
|
{
|
|||
|
CompileEnv* envPtr = assemEnvPtr->envPtr;
|
|||
|
/* Compilation environment */
|
|||
|
BasicBlock* curr_bb = assemEnvPtr->curr_bb;
|
|||
|
/* Current basic block */
|
|||
|
int exceptionCount = envPtr->exceptArrayNext - savedExceptArrayNext;
|
|||
|
/* Number of ranges that must be moved */
|
|||
|
int i;
|
|||
|
|
|||
|
if (exceptionCount == 0) {
|
|||
|
/* Nothing to do */
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Save the exception ranges in the basic block. They will be re-added at
|
|||
|
* the conclusion of assembly; at this time, the INST_BEGIN_CATCH
|
|||
|
* instructions in the block will be adjusted from whatever range indices
|
|||
|
* they have [savedExceptArrayNext .. envPtr->exceptArrayNext) to the
|
|||
|
* indices that the exceptions acquire. The saved exception ranges are
|
|||
|
* converted to a relative nesting depth. The depth will be recomputed
|
|||
|
* once flow analysis has determined the actual stack depth of the block.
|
|||
|
*/
|
|||
|
|
|||
|
DEBUG_PRINT("basic block %p has %d exceptions starting at %d\n",
|
|||
|
curr_bb, exceptionCount, savedExceptArrayNext);
|
|||
|
curr_bb->foreignExceptionBase = savedExceptArrayNext;
|
|||
|
curr_bb->foreignExceptionCount = exceptionCount;
|
|||
|
curr_bb->foreignExceptions =
|
|||
|
(ExceptionRange*)ckalloc(exceptionCount * sizeof(ExceptionRange));
|
|||
|
memcpy(curr_bb->foreignExceptions,
|
|||
|
envPtr->exceptArrayPtr + savedExceptArrayNext,
|
|||
|
exceptionCount * sizeof(ExceptionRange));
|
|||
|
for (i = 0; i < exceptionCount; ++i) {
|
|||
|
curr_bb->foreignExceptions[i].nestingLevel -= envPtr->exceptDepth;
|
|||
|
}
|
|||
|
envPtr->exceptArrayNext = savedExceptArrayNext;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* CreateMirrorJumpTable --
|
|||
|
*
|
|||
|
* Makes a jump table with comparison values and assembly code labels.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Returns a standard Tcl status, with an error message in the
|
|||
|
* interpreter on error.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* Initializes the jump table pointer in the current basic block to a
|
|||
|
* JumptableInfo. The keys in the JumptableInfo are the comparison
|
|||
|
* strings. The values, instead of being jump displacements, are
|
|||
|
* Tcl_Obj's with the code labels.
|
|||
|
*/
|
|||
|
|
|||
|
static int
|
|||
|
CreateMirrorJumpTable(
|
|||
|
AssemblyEnv* assemEnvPtr, /* Assembly environment */
|
|||
|
Tcl_Obj* jumps) /* List of alternating keywords and labels */
|
|||
|
{
|
|||
|
int objc; /* Number of elements in the 'jumps' list */
|
|||
|
Tcl_Obj** objv; /* Pointers to the elements in the list */
|
|||
|
CompileEnv* envPtr = assemEnvPtr->envPtr;
|
|||
|
/* Compilation environment */
|
|||
|
Tcl_Interp* interp = (Tcl_Interp*) envPtr->iPtr;
|
|||
|
/* Tcl interpreter */
|
|||
|
BasicBlock* bbPtr = assemEnvPtr->curr_bb;
|
|||
|
/* Current basic block */
|
|||
|
JumptableInfo* jtPtr;
|
|||
|
Tcl_HashTable* jtHashPtr; /* Hashtable in the JumptableInfo */
|
|||
|
Tcl_HashEntry* hashEntry; /* Entry for a key in the hashtable */
|
|||
|
int isNew; /* Flag==1 if the key is not yet in the
|
|||
|
* table. */
|
|||
|
int i;
|
|||
|
|
|||
|
if (Tcl_ListObjGetElements(interp, jumps, &objc, &objv) != TCL_OK) {
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
if (objc % 2 != 0) {
|
|||
|
if (assemEnvPtr->flags & TCL_EVAL_DIRECT) {
|
|||
|
Tcl_SetObjResult(interp, Tcl_NewStringObj(
|
|||
|
"jump table must have an even number of list elements",
|
|||
|
-1));
|
|||
|
Tcl_SetErrorCode(interp, "TCL", "ASSEM", "BADJUMPTABLE", NULL);
|
|||
|
}
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Allocate the jumptable.
|
|||
|
*/
|
|||
|
|
|||
|
jtPtr = (JumptableInfo*)ckalloc(sizeof(JumptableInfo));
|
|||
|
jtHashPtr = &jtPtr->hashTable;
|
|||
|
Tcl_InitHashTable(jtHashPtr, TCL_STRING_KEYS);
|
|||
|
|
|||
|
/*
|
|||
|
* Fill the keys and labels into the table.
|
|||
|
*/
|
|||
|
|
|||
|
DEBUG_PRINT("jump table {\n");
|
|||
|
for (i = 0; i < objc; i+=2) {
|
|||
|
DEBUG_PRINT(" %s -> %s\n", Tcl_GetString(objv[i]),
|
|||
|
Tcl_GetString(objv[i+1]));
|
|||
|
hashEntry = Tcl_CreateHashEntry(jtHashPtr, Tcl_GetString(objv[i]),
|
|||
|
&isNew);
|
|||
|
if (!isNew) {
|
|||
|
if (assemEnvPtr->flags & TCL_EVAL_DIRECT) {
|
|||
|
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
|
|||
|
"duplicate entry in jump table for \"%s\"",
|
|||
|
Tcl_GetString(objv[i])));
|
|||
|
Tcl_SetErrorCode(interp, "TCL", "ASSEM", "DUPJUMPTABLEENTRY");
|
|||
|
DeleteMirrorJumpTable(jtPtr);
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
}
|
|||
|
Tcl_SetHashValue(hashEntry, objv[i+1]);
|
|||
|
Tcl_IncrRefCount(objv[i+1]);
|
|||
|
}
|
|||
|
DEBUG_PRINT("}\n");
|
|||
|
|
|||
|
/*
|
|||
|
* Put the mirror jumptable in the basic block struct.
|
|||
|
*/
|
|||
|
|
|||
|
bbPtr->jtPtr = jtPtr;
|
|||
|
return TCL_OK;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* DeleteMirrorJumpTable --
|
|||
|
*
|
|||
|
* Cleans up a jump table when the basic block is deleted.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static void
|
|||
|
DeleteMirrorJumpTable(
|
|||
|
JumptableInfo* jtPtr)
|
|||
|
{
|
|||
|
Tcl_HashTable* jtHashPtr = &jtPtr->hashTable;
|
|||
|
/* Hash table pointer */
|
|||
|
Tcl_HashSearch search; /* Hash search control */
|
|||
|
Tcl_HashEntry* entry; /* Hash table entry containing a jump label */
|
|||
|
Tcl_Obj* label; /* Jump label from the hash table */
|
|||
|
|
|||
|
for (entry = Tcl_FirstHashEntry(jtHashPtr, &search);
|
|||
|
entry != NULL;
|
|||
|
entry = Tcl_NextHashEntry(&search)) {
|
|||
|
label = (Tcl_Obj*)Tcl_GetHashValue(entry);
|
|||
|
Tcl_DecrRefCount(label);
|
|||
|
Tcl_SetHashValue(entry, NULL);
|
|||
|
}
|
|||
|
Tcl_DeleteHashTable(jtHashPtr);
|
|||
|
ckfree(jtPtr);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* GetNextOperand --
|
|||
|
*
|
|||
|
* Retrieves the next operand in sequence from an assembly instruction,
|
|||
|
* and makes sure that its value is known at compile time.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* If successful, returns TCL_OK and leaves a Tcl_Obj with the operand
|
|||
|
* text in *operandObjPtr. In case of failure, returns TCL_ERROR and
|
|||
|
* leaves *operandObjPtr untouched.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* Advances *tokenPtrPtr around the token just processed.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static int
|
|||
|
GetNextOperand(
|
|||
|
AssemblyEnv* assemEnvPtr, /* Assembly environment */
|
|||
|
Tcl_Token** tokenPtrPtr, /* INPUT/OUTPUT: Pointer to the token holding
|
|||
|
* the operand */
|
|||
|
Tcl_Obj** operandObjPtr) /* OUTPUT: Tcl object holding the operand text
|
|||
|
* with \-substitutions done. */
|
|||
|
{
|
|||
|
Tcl_Interp* interp = (Tcl_Interp*) assemEnvPtr->envPtr->iPtr;
|
|||
|
Tcl_Obj* operandObj;
|
|||
|
|
|||
|
TclNewObj(operandObj);
|
|||
|
if (!TclWordKnownAtCompileTime(*tokenPtrPtr, operandObj)) {
|
|||
|
Tcl_DecrRefCount(operandObj);
|
|||
|
if (assemEnvPtr->flags & TCL_EVAL_DIRECT) {
|
|||
|
Tcl_SetObjResult(interp, Tcl_NewStringObj(
|
|||
|
"assembly code may not contain substitutions", -1));
|
|||
|
Tcl_SetErrorCode(interp, "TCL", "ASSEM", "NOSUBST", NULL);
|
|||
|
}
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
*tokenPtrPtr = TokenAfter(*tokenPtrPtr);
|
|||
|
Tcl_IncrRefCount(operandObj);
|
|||
|
*operandObjPtr = operandObj;
|
|||
|
return TCL_OK;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* GetBooleanOperand --
|
|||
|
*
|
|||
|
* Retrieves a Boolean operand from the input stream and advances
|
|||
|
* the token pointer.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Returns a standard Tcl result (with an error message in the
|
|||
|
* interpreter on failure).
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* Stores the Boolean value in (*result) and advances (*tokenPtrPtr)
|
|||
|
* to the next token.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static int
|
|||
|
GetBooleanOperand(
|
|||
|
AssemblyEnv* assemEnvPtr, /* Assembly environment */
|
|||
|
Tcl_Token** tokenPtrPtr, /* Current token from the parser */
|
|||
|
int* result) /* OUTPUT: Integer extracted from the token */
|
|||
|
{
|
|||
|
CompileEnv* envPtr = assemEnvPtr->envPtr;
|
|||
|
/* Compilation environment */
|
|||
|
Tcl_Interp* interp = (Tcl_Interp*) envPtr->iPtr;
|
|||
|
/* Tcl interpreter */
|
|||
|
Tcl_Token* tokenPtr = *tokenPtrPtr;
|
|||
|
/* INOUT: Pointer to the next token in the
|
|||
|
* source code */
|
|||
|
Tcl_Obj* intObj; /* Integer from the source code */
|
|||
|
int status; /* Tcl status return */
|
|||
|
|
|||
|
/*
|
|||
|
* Extract the next token as a string.
|
|||
|
*/
|
|||
|
|
|||
|
if (GetNextOperand(assemEnvPtr, tokenPtrPtr, &intObj) != TCL_OK) {
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Convert to an integer, advance to the next token and return.
|
|||
|
*/
|
|||
|
|
|||
|
status = Tcl_GetBooleanFromObj(interp, intObj, result);
|
|||
|
Tcl_DecrRefCount(intObj);
|
|||
|
*tokenPtrPtr = TokenAfter(tokenPtr);
|
|||
|
return status;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* GetIntegerOperand --
|
|||
|
*
|
|||
|
* Retrieves an integer operand from the input stream and advances the
|
|||
|
* token pointer.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Returns a standard Tcl result (with an error message in the
|
|||
|
* interpreter on failure).
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* Stores the integer value in (*result) and advances (*tokenPtrPtr) to
|
|||
|
* the next token.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static int
|
|||
|
GetIntegerOperand(
|
|||
|
AssemblyEnv* assemEnvPtr, /* Assembly environment */
|
|||
|
Tcl_Token** tokenPtrPtr, /* Current token from the parser */
|
|||
|
int* result) /* OUTPUT: Integer extracted from the token */
|
|||
|
{
|
|||
|
CompileEnv* envPtr = assemEnvPtr->envPtr;
|
|||
|
/* Compilation environment */
|
|||
|
Tcl_Interp* interp = (Tcl_Interp*) envPtr->iPtr;
|
|||
|
/* Tcl interpreter */
|
|||
|
Tcl_Token* tokenPtr = *tokenPtrPtr;
|
|||
|
/* INOUT: Pointer to the next token in the
|
|||
|
* source code */
|
|||
|
Tcl_Obj* intObj; /* Integer from the source code */
|
|||
|
int status; /* Tcl status return */
|
|||
|
|
|||
|
/*
|
|||
|
* Extract the next token as a string.
|
|||
|
*/
|
|||
|
|
|||
|
if (GetNextOperand(assemEnvPtr, tokenPtrPtr, &intObj) != TCL_OK) {
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Convert to an integer, advance to the next token and return.
|
|||
|
*/
|
|||
|
|
|||
|
status = Tcl_GetIntFromObj(interp, intObj, result);
|
|||
|
Tcl_DecrRefCount(intObj);
|
|||
|
*tokenPtrPtr = TokenAfter(tokenPtr);
|
|||
|
return status;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* GetListIndexOperand --
|
|||
|
*
|
|||
|
* Gets the value of an operand intended to serve as a list index.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Returns a standard Tcl result: TCL_OK if the parse is successful and
|
|||
|
* TCL_ERROR (with an appropriate error message) if the parse fails.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* Stores the list index at '*index'. Values between -1 and 0x7FFFFFFF
|
|||
|
* have their natural meaning; values between -2 and -0x80000000
|
|||
|
* represent 'end-2-N'.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static int
|
|||
|
GetListIndexOperand(
|
|||
|
AssemblyEnv* assemEnvPtr, /* Assembly environment */
|
|||
|
Tcl_Token** tokenPtrPtr, /* Current token from the parser */
|
|||
|
int* result) /* OUTPUT: Integer extracted from the token */
|
|||
|
{
|
|||
|
CompileEnv* envPtr = assemEnvPtr->envPtr;
|
|||
|
/* Compilation environment */
|
|||
|
Tcl_Interp* interp = (Tcl_Interp*) envPtr->iPtr;
|
|||
|
/* Tcl interpreter */
|
|||
|
Tcl_Token* tokenPtr = *tokenPtrPtr;
|
|||
|
/* INOUT: Pointer to the next token in the
|
|||
|
* source code */
|
|||
|
Tcl_Obj *value;
|
|||
|
int status;
|
|||
|
|
|||
|
/* General operand validity check */
|
|||
|
if (GetNextOperand(assemEnvPtr, tokenPtrPtr, &value) != TCL_OK) {
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
|
|||
|
/* Convert to an integer, advance to the next token and return. */
|
|||
|
/*
|
|||
|
* NOTE: Indexing a list with an index before it yields the
|
|||
|
* same result as indexing after it, and might be more easily portable
|
|||
|
* when list size limits grow.
|
|||
|
*/
|
|||
|
status = TclIndexEncode(interp, value,
|
|||
|
TCL_INDEX_BEFORE,TCL_INDEX_BEFORE, result);
|
|||
|
|
|||
|
Tcl_DecrRefCount(value);
|
|||
|
*tokenPtrPtr = TokenAfter(tokenPtr);
|
|||
|
return status;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* FindLocalVar --
|
|||
|
*
|
|||
|
* Gets the name of a local variable from the input stream and advances
|
|||
|
* the token pointer.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Returns the LVT index of the local variable. Returns -1 if the
|
|||
|
* variable is non-local, not known at compile time, or cannot be
|
|||
|
* installed in the LVT (leaving an error message in the interpreter
|
|||
|
* result if necessary).
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* Advances the token pointer. May define a new LVT slot if the variable
|
|||
|
* has not yet been seen and the execution context allows for it.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static int
|
|||
|
FindLocalVar(
|
|||
|
AssemblyEnv* assemEnvPtr, /* Assembly environment */
|
|||
|
Tcl_Token** tokenPtrPtr)
|
|||
|
{
|
|||
|
CompileEnv* envPtr = assemEnvPtr->envPtr;
|
|||
|
/* Compilation environment */
|
|||
|
Tcl_Interp* interp = (Tcl_Interp*) envPtr->iPtr;
|
|||
|
/* Tcl interpreter */
|
|||
|
Tcl_Token* tokenPtr = *tokenPtrPtr;
|
|||
|
/* INOUT: Pointer to the next token in the
|
|||
|
* source code. */
|
|||
|
Tcl_Obj* varNameObj; /* Name of the variable */
|
|||
|
const char* varNameStr;
|
|||
|
int varNameLen;
|
|||
|
int localVar; /* Index of the variable in the LVT */
|
|||
|
|
|||
|
if (GetNextOperand(assemEnvPtr, tokenPtrPtr, &varNameObj) != TCL_OK) {
|
|||
|
return -1;
|
|||
|
}
|
|||
|
varNameStr = Tcl_GetStringFromObj(varNameObj, &varNameLen);
|
|||
|
if (CheckNamespaceQualifiers(interp, varNameStr, varNameLen)) {
|
|||
|
Tcl_DecrRefCount(varNameObj);
|
|||
|
return -1;
|
|||
|
}
|
|||
|
localVar = TclFindCompiledLocal(varNameStr, varNameLen, 1, envPtr);
|
|||
|
Tcl_DecrRefCount(varNameObj);
|
|||
|
if (localVar == -1) {
|
|||
|
if (assemEnvPtr->flags & TCL_EVAL_DIRECT) {
|
|||
|
Tcl_SetObjResult(interp, Tcl_NewStringObj(
|
|||
|
"cannot use this instruction to create a variable"
|
|||
|
" in a non-proc context", -1));
|
|||
|
Tcl_SetErrorCode(interp, "TCL", "ASSEM", "LVT", NULL);
|
|||
|
}
|
|||
|
return -1;
|
|||
|
}
|
|||
|
*tokenPtrPtr = TokenAfter(tokenPtr);
|
|||
|
return localVar;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* CheckNamespaceQualifiers --
|
|||
|
*
|
|||
|
* Verify that a variable name has no namespace qualifiers before
|
|||
|
* attempting to install it in the LVT.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* On success, returns TCL_OK. On failure, returns TCL_ERROR and stores
|
|||
|
* an error message in the interpreter result.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static int
|
|||
|
CheckNamespaceQualifiers(
|
|||
|
Tcl_Interp* interp, /* Tcl interpreter for error reporting */
|
|||
|
const char* name, /* Variable name to check */
|
|||
|
int nameLen) /* Length of the variable */
|
|||
|
{
|
|||
|
const char* p;
|
|||
|
|
|||
|
for (p = name; p+2 < name+nameLen; p++) {
|
|||
|
if ((*p == ':') && (p[1] == ':')) {
|
|||
|
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
|
|||
|
"variable \"%s\" is not local", name));
|
|||
|
Tcl_SetErrorCode(interp, "TCL", "ASSEM", "NONLOCAL", name, NULL);
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
}
|
|||
|
return TCL_OK;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* CheckOneByte --
|
|||
|
*
|
|||
|
* Verify that a constant fits in a single byte in the instruction
|
|||
|
* stream.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* On success, returns TCL_OK. On failure, returns TCL_ERROR and stores
|
|||
|
* an error message in the interpreter result.
|
|||
|
*
|
|||
|
* This code is here primarily to verify that instructions like INCR_SCALAR1
|
|||
|
* are possible on a given local variable. The fact that there is no
|
|||
|
* INCR_SCALAR4 is puzzling.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static int
|
|||
|
CheckOneByte(
|
|||
|
Tcl_Interp* interp, /* Tcl interpreter for error reporting */
|
|||
|
int value) /* Value to check */
|
|||
|
{
|
|||
|
Tcl_Obj* result; /* Error message */
|
|||
|
|
|||
|
if (value < 0 || value > 0xFF) {
|
|||
|
result = Tcl_NewStringObj("operand does not fit in one byte", -1);
|
|||
|
Tcl_SetObjResult(interp, result);
|
|||
|
Tcl_SetErrorCode(interp, "TCL", "ASSEM", "1BYTE", NULL);
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
return TCL_OK;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* CheckSignedOneByte --
|
|||
|
*
|
|||
|
* Verify that a constant fits in a single signed byte in the instruction
|
|||
|
* stream.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* On success, returns TCL_OK. On failure, returns TCL_ERROR and stores
|
|||
|
* an error message in the interpreter result.
|
|||
|
*
|
|||
|
* This code is here primarily to verify that instructions like INCR_SCALAR1
|
|||
|
* are possible on a given local variable. The fact that there is no
|
|||
|
* INCR_SCALAR4 is puzzling.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static int
|
|||
|
CheckSignedOneByte(
|
|||
|
Tcl_Interp* interp, /* Tcl interpreter for error reporting */
|
|||
|
int value) /* Value to check */
|
|||
|
{
|
|||
|
Tcl_Obj* result; /* Error message */
|
|||
|
|
|||
|
if (value > 0x7F || value < -0x80) {
|
|||
|
result = Tcl_NewStringObj("operand does not fit in one byte", -1);
|
|||
|
Tcl_SetObjResult(interp, result);
|
|||
|
Tcl_SetErrorCode(interp, "TCL", "ASSEM", "1BYTE", NULL);
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
return TCL_OK;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* CheckNonNegative --
|
|||
|
*
|
|||
|
* Verify that a constant is nonnegative
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* On success, returns TCL_OK. On failure, returns TCL_ERROR and stores
|
|||
|
* an error message in the interpreter result.
|
|||
|
*
|
|||
|
* This code is here primarily to verify that instructions like INCR_INVOKE
|
|||
|
* are consuming a positive number of operands
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static int
|
|||
|
CheckNonNegative(
|
|||
|
Tcl_Interp* interp, /* Tcl interpreter for error reporting */
|
|||
|
int value) /* Value to check */
|
|||
|
{
|
|||
|
Tcl_Obj* result; /* Error message */
|
|||
|
|
|||
|
if (value < 0) {
|
|||
|
result = Tcl_NewStringObj("operand must be nonnegative", -1);
|
|||
|
Tcl_SetObjResult(interp, result);
|
|||
|
Tcl_SetErrorCode(interp, "TCL", "ASSEM", "NONNEGATIVE", NULL);
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
return TCL_OK;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* CheckStrictlyPositive --
|
|||
|
*
|
|||
|
* Verify that a constant is positive
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* On success, returns TCL_OK. On failure, returns TCL_ERROR and
|
|||
|
* stores an error message in the interpreter result.
|
|||
|
*
|
|||
|
* This code is here primarily to verify that instructions like INCR_INVOKE
|
|||
|
* are consuming a positive number of operands
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static int
|
|||
|
CheckStrictlyPositive(
|
|||
|
Tcl_Interp* interp, /* Tcl interpreter for error reporting */
|
|||
|
int value) /* Value to check */
|
|||
|
{
|
|||
|
Tcl_Obj* result; /* Error message */
|
|||
|
|
|||
|
if (value <= 0) {
|
|||
|
result = Tcl_NewStringObj("operand must be positive", -1);
|
|||
|
Tcl_SetObjResult(interp, result);
|
|||
|
Tcl_SetErrorCode(interp, "TCL", "ASSEM", "POSITIVE", NULL);
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
return TCL_OK;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* DefineLabel --
|
|||
|
*
|
|||
|
* Defines a label appearing in the assembly sequence.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Returns a standard Tcl result. Returns TCL_OK and an empty result if
|
|||
|
* the definition succeeds; returns TCL_ERROR and an appropriate message
|
|||
|
* if a duplicate definition is found.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static int
|
|||
|
DefineLabel(
|
|||
|
AssemblyEnv* assemEnvPtr, /* Assembly environment */
|
|||
|
const char* labelName) /* Label being defined */
|
|||
|
{
|
|||
|
CompileEnv* envPtr = assemEnvPtr->envPtr;
|
|||
|
/* Compilation environment */
|
|||
|
Tcl_Interp* interp = (Tcl_Interp*) envPtr->iPtr;
|
|||
|
/* Tcl interpreter */
|
|||
|
Tcl_HashEntry* entry; /* Label's entry in the symbol table */
|
|||
|
int isNew; /* Flag == 1 iff the label was previously
|
|||
|
* undefined */
|
|||
|
|
|||
|
/* TODO - This can now be simplified! */
|
|||
|
|
|||
|
StartBasicBlock(assemEnvPtr, BB_FALLTHRU, NULL);
|
|||
|
|
|||
|
/*
|
|||
|
* Look up the newly-defined label in the symbol table.
|
|||
|
*/
|
|||
|
|
|||
|
entry = Tcl_CreateHashEntry(&assemEnvPtr->labelHash, labelName, &isNew);
|
|||
|
if (!isNew) {
|
|||
|
/*
|
|||
|
* This is a duplicate label.
|
|||
|
*/
|
|||
|
|
|||
|
if (assemEnvPtr->flags & TCL_EVAL_DIRECT) {
|
|||
|
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
|
|||
|
"duplicate definition of label \"%s\"", labelName));
|
|||
|
Tcl_SetErrorCode(interp, "TCL", "ASSEM", "DUPLABEL", labelName,
|
|||
|
NULL);
|
|||
|
}
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* This is the first appearance of the label in the code.
|
|||
|
*/
|
|||
|
|
|||
|
Tcl_SetHashValue(entry, assemEnvPtr->curr_bb);
|
|||
|
return TCL_OK;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* StartBasicBlock --
|
|||
|
*
|
|||
|
* Starts a new basic block when a label or jump is encountered.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Returns a pointer to the BasicBlock structure of the new
|
|||
|
* basic block.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static BasicBlock*
|
|||
|
StartBasicBlock(
|
|||
|
AssemblyEnv* assemEnvPtr, /* Assembly environment */
|
|||
|
int flags, /* Flags to apply to the basic block being
|
|||
|
* closed, if there is one. */
|
|||
|
Tcl_Obj* jumpLabel) /* Label of the location that the block jumps
|
|||
|
* to, or NULL if the block does not jump */
|
|||
|
{
|
|||
|
CompileEnv* envPtr = assemEnvPtr->envPtr;
|
|||
|
/* Compilation environment */
|
|||
|
BasicBlock* newBB; /* BasicBlock structure for the new block */
|
|||
|
BasicBlock* currBB = assemEnvPtr->curr_bb;
|
|||
|
|
|||
|
/*
|
|||
|
* Coalesce zero-length blocks.
|
|||
|
*/
|
|||
|
|
|||
|
if (currBB->startOffset == envPtr->codeNext - envPtr->codeStart) {
|
|||
|
currBB->startLine = assemEnvPtr->cmdLine;
|
|||
|
return currBB;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Make the new basic block.
|
|||
|
*/
|
|||
|
|
|||
|
newBB = AllocBB(assemEnvPtr);
|
|||
|
|
|||
|
/*
|
|||
|
* Record the jump target if there is one.
|
|||
|
*/
|
|||
|
|
|||
|
currBB->jumpTarget = jumpLabel;
|
|||
|
if (jumpLabel != NULL) {
|
|||
|
Tcl_IncrRefCount(currBB->jumpTarget);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Record the fallthrough if there is one.
|
|||
|
*/
|
|||
|
|
|||
|
currBB->flags |= flags;
|
|||
|
|
|||
|
/*
|
|||
|
* Record the successor block.
|
|||
|
*/
|
|||
|
|
|||
|
currBB->successor1 = newBB;
|
|||
|
assemEnvPtr->curr_bb = newBB;
|
|||
|
return newBB;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* AllocBB --
|
|||
|
*
|
|||
|
* Allocates a new basic block
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Returns a pointer to the newly allocated block, which is initialized
|
|||
|
* to contain no code and begin at the current instruction pointer.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static BasicBlock *
|
|||
|
AllocBB(
|
|||
|
AssemblyEnv* assemEnvPtr) /* Assembly environment */
|
|||
|
{
|
|||
|
CompileEnv* envPtr = assemEnvPtr->envPtr;
|
|||
|
BasicBlock *bb = (BasicBlock*)ckalloc(sizeof(BasicBlock));
|
|||
|
|
|||
|
bb->originalStartOffset =
|
|||
|
bb->startOffset = envPtr->codeNext - envPtr->codeStart;
|
|||
|
bb->startLine = assemEnvPtr->cmdLine + 1;
|
|||
|
bb->jumpOffset = -1;
|
|||
|
bb->jumpLine = -1;
|
|||
|
bb->prevPtr = assemEnvPtr->curr_bb;
|
|||
|
bb->predecessor = NULL;
|
|||
|
bb->successor1 = NULL;
|
|||
|
bb->jumpTarget = NULL;
|
|||
|
bb->initialStackDepth = 0;
|
|||
|
bb->minStackDepth = 0;
|
|||
|
bb->maxStackDepth = 0;
|
|||
|
bb->finalStackDepth = 0;
|
|||
|
bb->catchDepth = 0;
|
|||
|
bb->enclosingCatch = NULL;
|
|||
|
bb->foreignExceptionBase = -1;
|
|||
|
bb->foreignExceptionCount = 0;
|
|||
|
bb->foreignExceptions = NULL;
|
|||
|
bb->jtPtr = NULL;
|
|||
|
bb->flags = 0;
|
|||
|
|
|||
|
return bb;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* FinishAssembly --
|
|||
|
*
|
|||
|
* Postprocessing after all bytecode has been generated for a block of
|
|||
|
* assembly code.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Returns a standard Tcl result, with an error message left in the
|
|||
|
* interpreter if appropriate.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* The program is checked to see if any undefined labels remain. The
|
|||
|
* initial stack depth of all the basic blocks in the flow graph is
|
|||
|
* calculated and saved. The stack balance on exit is computed, checked
|
|||
|
* and saved.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static int
|
|||
|
FinishAssembly(
|
|||
|
AssemblyEnv* assemEnvPtr) /* Assembly environment */
|
|||
|
{
|
|||
|
int mustMove; /* Amount by which the code needs to be grown
|
|||
|
* because of expanding jumps */
|
|||
|
|
|||
|
/*
|
|||
|
* Resolve the targets of all jumps and determine whether code needs to be
|
|||
|
* moved around.
|
|||
|
*/
|
|||
|
|
|||
|
if (CalculateJumpRelocations(assemEnvPtr, &mustMove)) {
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Move the code if necessary.
|
|||
|
*/
|
|||
|
|
|||
|
if (mustMove) {
|
|||
|
MoveCodeForJumps(assemEnvPtr, mustMove);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Resolve jump target labels to bytecode offsets.
|
|||
|
*/
|
|||
|
|
|||
|
FillInJumpOffsets(assemEnvPtr);
|
|||
|
|
|||
|
/*
|
|||
|
* Label each basic block with its catch context. Quit on inconsistency.
|
|||
|
*/
|
|||
|
|
|||
|
if (ProcessCatches(assemEnvPtr) != TCL_OK) {
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Make sure that no block accessible from a catch's error exit that hasn't
|
|||
|
* popped the exception stack can throw an exception.
|
|||
|
*/
|
|||
|
|
|||
|
if (CheckForThrowInWrongContext(assemEnvPtr) != TCL_OK) {
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Compute stack balance throughout the program.
|
|||
|
*/
|
|||
|
|
|||
|
if (CheckStack(assemEnvPtr) != TCL_OK) {
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* TODO - Check for unreachable code. Or maybe not; unreachable code is
|
|||
|
* Mostly Harmless.
|
|||
|
*/
|
|||
|
|
|||
|
return TCL_OK;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* CalculateJumpRelocations --
|
|||
|
*
|
|||
|
* Calculate any movement that has to be done in the assembly code to
|
|||
|
* expand JUMP1 instructions to JUMP4 (because they jump more than a
|
|||
|
* 1-byte range).
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Returns a standard Tcl result, with an appropriate error message if
|
|||
|
* anything fails.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* Sets the 'startOffset' pointer in every basic block to the new origin
|
|||
|
* of the block, and turns off JUMP1 flags on instructions that must be
|
|||
|
* expanded (and adjusts them to the corresponding JUMP4's). Does *not*
|
|||
|
* store the jump offsets at this point.
|
|||
|
*
|
|||
|
* Sets *mustMove to 1 if and only if at least one instruction changed
|
|||
|
* size so the code must be moved.
|
|||
|
*
|
|||
|
* As a side effect, also checks for undefined labels and reports them.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static int
|
|||
|
CalculateJumpRelocations(
|
|||
|
AssemblyEnv* assemEnvPtr, /* Assembly environment */
|
|||
|
int* mustMove) /* OUTPUT: Number of bytes that have been
|
|||
|
* added to the code */
|
|||
|
{
|
|||
|
CompileEnv* envPtr = assemEnvPtr->envPtr;
|
|||
|
/* Compilation environment */
|
|||
|
BasicBlock* bbPtr; /* Pointer to a basic block being checked */
|
|||
|
Tcl_HashEntry* entry; /* Exit label's entry in the symbol table */
|
|||
|
BasicBlock* jumpTarget; /* Basic block where the jump goes */
|
|||
|
int motion; /* Amount by which the code has expanded */
|
|||
|
int offset; /* Offset in the bytecode from a jump
|
|||
|
* instruction to its target */
|
|||
|
unsigned opcode; /* Opcode in the bytecode being adjusted */
|
|||
|
|
|||
|
/*
|
|||
|
* Iterate through basic blocks as long as a change results in code
|
|||
|
* expansion.
|
|||
|
*/
|
|||
|
|
|||
|
*mustMove = 0;
|
|||
|
do {
|
|||
|
motion = 0;
|
|||
|
for (bbPtr = assemEnvPtr->head_bb;
|
|||
|
bbPtr != NULL;
|
|||
|
bbPtr = bbPtr->successor1) {
|
|||
|
/*
|
|||
|
* Advance the basic block start offset by however many bytes we
|
|||
|
* have inserted in the code up to this point
|
|||
|
*/
|
|||
|
|
|||
|
bbPtr->startOffset += motion;
|
|||
|
|
|||
|
/*
|
|||
|
* If the basic block references a label (and hence performs a
|
|||
|
* jump), find the location of the label. Report an error if the
|
|||
|
* label is missing.
|
|||
|
*/
|
|||
|
|
|||
|
if (bbPtr->jumpTarget != NULL) {
|
|||
|
entry = Tcl_FindHashEntry(&assemEnvPtr->labelHash,
|
|||
|
Tcl_GetString(bbPtr->jumpTarget));
|
|||
|
if (entry == NULL) {
|
|||
|
ReportUndefinedLabel(assemEnvPtr, bbPtr,
|
|||
|
bbPtr->jumpTarget);
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* If the instruction is a JUMP1, turn it into a JUMP4 if its
|
|||
|
* target is out of range.
|
|||
|
*/
|
|||
|
|
|||
|
jumpTarget = (BasicBlock*)Tcl_GetHashValue(entry);
|
|||
|
if (bbPtr->flags & BB_JUMP1) {
|
|||
|
offset = jumpTarget->startOffset
|
|||
|
- (bbPtr->jumpOffset + motion);
|
|||
|
if (offset < -0x80 || offset > 0x7F) {
|
|||
|
opcode = TclGetUInt1AtPtr(envPtr->codeStart
|
|||
|
+ bbPtr->jumpOffset);
|
|||
|
++opcode;
|
|||
|
TclStoreInt1AtPtr(opcode,
|
|||
|
envPtr->codeStart + bbPtr->jumpOffset);
|
|||
|
motion += 3;
|
|||
|
bbPtr->flags &= ~BB_JUMP1;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* If the basic block references a jump table, that doesn't affect
|
|||
|
* the code locations, but resolve the labels now, and store basic
|
|||
|
* block pointers in the jumptable hash.
|
|||
|
*/
|
|||
|
|
|||
|
if (bbPtr->flags & BB_JUMPTABLE) {
|
|||
|
if (CheckJumpTableLabels(assemEnvPtr, bbPtr) != TCL_OK) {
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
*mustMove += motion;
|
|||
|
} while (motion != 0);
|
|||
|
|
|||
|
return TCL_OK;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* CheckJumpTableLabels --
|
|||
|
*
|
|||
|
* Make sure that all the labels in a jump table are defined.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Returns TCL_OK if they are, TCL_ERROR if they aren't.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static int
|
|||
|
CheckJumpTableLabels(
|
|||
|
AssemblyEnv* assemEnvPtr, /* Assembly environment */
|
|||
|
BasicBlock* bbPtr) /* Basic block that ends in a jump table */
|
|||
|
{
|
|||
|
Tcl_HashTable* symHash = &bbPtr->jtPtr->hashTable;
|
|||
|
/* Hash table with the symbols */
|
|||
|
Tcl_HashSearch search; /* Hash table iterator */
|
|||
|
Tcl_HashEntry* symEntryPtr; /* Hash entry for the symbols */
|
|||
|
Tcl_Obj* symbolObj; /* Jump target */
|
|||
|
Tcl_HashEntry* valEntryPtr; /* Hash entry for the resolutions */
|
|||
|
|
|||
|
/*
|
|||
|
* Look up every jump target in the jump hash.
|
|||
|
*/
|
|||
|
|
|||
|
DEBUG_PRINT("check jump table labels %p {\n", bbPtr);
|
|||
|
for (symEntryPtr = Tcl_FirstHashEntry(symHash, &search);
|
|||
|
symEntryPtr != NULL;
|
|||
|
symEntryPtr = Tcl_NextHashEntry(&search)) {
|
|||
|
symbolObj = (Tcl_Obj*)Tcl_GetHashValue(symEntryPtr);
|
|||
|
valEntryPtr = Tcl_FindHashEntry(&assemEnvPtr->labelHash,
|
|||
|
Tcl_GetString(symbolObj));
|
|||
|
DEBUG_PRINT(" %s -> %s (%d)\n",
|
|||
|
(char*) Tcl_GetHashKey(symHash, symEntryPtr),
|
|||
|
Tcl_GetString(symbolObj), (valEntryPtr != NULL));
|
|||
|
if (valEntryPtr == NULL) {
|
|||
|
ReportUndefinedLabel(assemEnvPtr, bbPtr, symbolObj);
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
}
|
|||
|
DEBUG_PRINT("}\n");
|
|||
|
return TCL_OK;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* ReportUndefinedLabel --
|
|||
|
*
|
|||
|
* Report that a basic block refers to an undefined jump label
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* Stores an error message, error code, and line number information in
|
|||
|
* the assembler's Tcl interpreter.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static void
|
|||
|
ReportUndefinedLabel(
|
|||
|
AssemblyEnv* assemEnvPtr, /* Assembly environment */
|
|||
|
BasicBlock* bbPtr, /* Basic block that contains the undefined
|
|||
|
* label */
|
|||
|
Tcl_Obj* jumpTarget) /* Label of a jump target */
|
|||
|
{
|
|||
|
CompileEnv* envPtr = assemEnvPtr->envPtr;
|
|||
|
/* Compilation environment */
|
|||
|
Tcl_Interp* interp = (Tcl_Interp*) envPtr->iPtr;
|
|||
|
/* Tcl interpreter */
|
|||
|
|
|||
|
if (assemEnvPtr->flags & TCL_EVAL_DIRECT) {
|
|||
|
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
|
|||
|
"undefined label \"%s\"", Tcl_GetString(jumpTarget)));
|
|||
|
Tcl_SetErrorCode(interp, "TCL", "ASSEM", "NOLABEL",
|
|||
|
Tcl_GetString(jumpTarget), NULL);
|
|||
|
Tcl_SetErrorLine(interp, bbPtr->jumpLine);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* MoveCodeForJumps --
|
|||
|
*
|
|||
|
* Move bytecodes in memory to accommodate JUMP1 instructions that have
|
|||
|
* expanded to become JUMP4's.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static void
|
|||
|
MoveCodeForJumps(
|
|||
|
AssemblyEnv* assemEnvPtr, /* Assembler environment */
|
|||
|
int mustMove) /* Number of bytes of added code */
|
|||
|
{
|
|||
|
CompileEnv* envPtr = assemEnvPtr->envPtr;
|
|||
|
/* Compilation environment */
|
|||
|
BasicBlock* bbPtr; /* Pointer to a basic block being checked */
|
|||
|
int topOffset; /* Bytecode offset of the following basic
|
|||
|
* block before code motion */
|
|||
|
|
|||
|
/*
|
|||
|
* Make sure that there is enough space in the bytecode array to
|
|||
|
* accommodate the expanded code.
|
|||
|
*/
|
|||
|
|
|||
|
while (envPtr->codeEnd < envPtr->codeNext + mustMove) {
|
|||
|
TclExpandCodeArray(envPtr);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Iterate through the bytecodes in reverse order, and move them upward to
|
|||
|
* their new homes.
|
|||
|
*/
|
|||
|
|
|||
|
topOffset = envPtr->codeNext - envPtr->codeStart;
|
|||
|
for (bbPtr = assemEnvPtr->curr_bb; bbPtr != NULL; bbPtr = bbPtr->prevPtr) {
|
|||
|
DEBUG_PRINT("move code from %d to %d\n",
|
|||
|
bbPtr->originalStartOffset, bbPtr->startOffset);
|
|||
|
memmove(envPtr->codeStart + bbPtr->startOffset,
|
|||
|
envPtr->codeStart + bbPtr->originalStartOffset,
|
|||
|
topOffset - bbPtr->originalStartOffset);
|
|||
|
topOffset = bbPtr->originalStartOffset;
|
|||
|
bbPtr->jumpOffset += (bbPtr->startOffset - bbPtr->originalStartOffset);
|
|||
|
}
|
|||
|
envPtr->codeNext += mustMove;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* FillInJumpOffsets --
|
|||
|
*
|
|||
|
* Fill in the final offsets of all jump instructions once bytecode
|
|||
|
* locations have been completely determined.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static void
|
|||
|
FillInJumpOffsets(
|
|||
|
AssemblyEnv* assemEnvPtr) /* Assembly environment */
|
|||
|
{
|
|||
|
CompileEnv* envPtr = assemEnvPtr->envPtr;
|
|||
|
/* Compilation environment */
|
|||
|
BasicBlock* bbPtr; /* Pointer to a basic block being checked */
|
|||
|
Tcl_HashEntry* entry; /* Hashtable entry for a jump target label */
|
|||
|
BasicBlock* jumpTarget; /* Basic block where a jump goes */
|
|||
|
int fromOffset; /* Bytecode location of a jump instruction */
|
|||
|
int targetOffset; /* Bytecode location of a jump instruction's
|
|||
|
* target */
|
|||
|
|
|||
|
for (bbPtr = assemEnvPtr->head_bb;
|
|||
|
bbPtr != NULL;
|
|||
|
bbPtr = bbPtr->successor1) {
|
|||
|
if (bbPtr->jumpTarget != NULL) {
|
|||
|
entry = Tcl_FindHashEntry(&assemEnvPtr->labelHash,
|
|||
|
Tcl_GetString(bbPtr->jumpTarget));
|
|||
|
jumpTarget = (BasicBlock*)Tcl_GetHashValue(entry);
|
|||
|
fromOffset = bbPtr->jumpOffset;
|
|||
|
targetOffset = jumpTarget->startOffset;
|
|||
|
if (bbPtr->flags & BB_JUMP1) {
|
|||
|
TclStoreInt1AtPtr(targetOffset - fromOffset,
|
|||
|
envPtr->codeStart + fromOffset + 1);
|
|||
|
} else {
|
|||
|
TclStoreInt4AtPtr(targetOffset - fromOffset,
|
|||
|
envPtr->codeStart + fromOffset + 1);
|
|||
|
}
|
|||
|
}
|
|||
|
if (bbPtr->flags & BB_JUMPTABLE) {
|
|||
|
ResolveJumpTableTargets(assemEnvPtr, bbPtr);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* ResolveJumpTableTargets --
|
|||
|
*
|
|||
|
* Puts bytecode addresses for the targets of a jumptable into the
|
|||
|
* table
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Returns TCL_OK if they are, TCL_ERROR if they aren't.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static void
|
|||
|
ResolveJumpTableTargets(
|
|||
|
AssemblyEnv* assemEnvPtr, /* Assembly environment */
|
|||
|
BasicBlock* bbPtr) /* Basic block that ends in a jump table */
|
|||
|
{
|
|||
|
CompileEnv* envPtr = assemEnvPtr->envPtr;
|
|||
|
/* Compilation environment */
|
|||
|
Tcl_HashTable* symHash = &bbPtr->jtPtr->hashTable;
|
|||
|
/* Hash table with the symbols */
|
|||
|
Tcl_HashSearch search; /* Hash table iterator */
|
|||
|
Tcl_HashEntry* symEntryPtr; /* Hash entry for the symbols */
|
|||
|
Tcl_Obj* symbolObj; /* Jump target */
|
|||
|
Tcl_HashEntry* valEntryPtr; /* Hash entry for the resolutions */
|
|||
|
int auxDataIndex; /* Index of the auxdata */
|
|||
|
JumptableInfo* realJumpTablePtr;
|
|||
|
/* Jump table in the actual code */
|
|||
|
Tcl_HashTable* realJumpHashPtr;
|
|||
|
/* Jump table hash in the actual code */
|
|||
|
Tcl_HashEntry* realJumpEntryPtr;
|
|||
|
/* Entry in the jump table hash in
|
|||
|
* the actual code */
|
|||
|
BasicBlock* jumpTargetBBPtr;
|
|||
|
/* Basic block that the jump proceeds to */
|
|||
|
int junk;
|
|||
|
|
|||
|
auxDataIndex = TclGetInt4AtPtr(envPtr->codeStart + bbPtr->jumpOffset + 1);
|
|||
|
DEBUG_PRINT("bbPtr = %p jumpOffset = %d auxDataIndex = %d\n",
|
|||
|
bbPtr, bbPtr->jumpOffset, auxDataIndex);
|
|||
|
realJumpTablePtr = (JumptableInfo*)TclFetchAuxData(envPtr, auxDataIndex);
|
|||
|
realJumpHashPtr = &realJumpTablePtr->hashTable;
|
|||
|
|
|||
|
/*
|
|||
|
* Look up every jump target in the jump hash.
|
|||
|
*/
|
|||
|
|
|||
|
DEBUG_PRINT("resolve jump table {\n");
|
|||
|
for (symEntryPtr = Tcl_FirstHashEntry(symHash, &search);
|
|||
|
symEntryPtr != NULL;
|
|||
|
symEntryPtr = Tcl_NextHashEntry(&search)) {
|
|||
|
symbolObj = (Tcl_Obj*)Tcl_GetHashValue(symEntryPtr);
|
|||
|
DEBUG_PRINT(" symbol %s\n", Tcl_GetString(symbolObj));
|
|||
|
|
|||
|
valEntryPtr = Tcl_FindHashEntry(&assemEnvPtr->labelHash,
|
|||
|
Tcl_GetString(symbolObj));
|
|||
|
jumpTargetBBPtr = (BasicBlock*)Tcl_GetHashValue(valEntryPtr);
|
|||
|
|
|||
|
realJumpEntryPtr = Tcl_CreateHashEntry(realJumpHashPtr,
|
|||
|
Tcl_GetHashKey(symHash, symEntryPtr), &junk);
|
|||
|
DEBUG_PRINT(" %s -> %s -> bb %p (pc %d) hash entry %p\n",
|
|||
|
(char*) Tcl_GetHashKey(symHash, symEntryPtr),
|
|||
|
Tcl_GetString(symbolObj), jumpTargetBBPtr,
|
|||
|
jumpTargetBBPtr->startOffset, realJumpEntryPtr);
|
|||
|
|
|||
|
Tcl_SetHashValue(realJumpEntryPtr,
|
|||
|
INT2PTR(jumpTargetBBPtr->startOffset - bbPtr->jumpOffset));
|
|||
|
}
|
|||
|
DEBUG_PRINT("}\n");
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* CheckForThrowInWrongContext --
|
|||
|
*
|
|||
|
* Verify that no beginCatch/endCatch sequence can throw an exception
|
|||
|
* after an original exception is caught and before its exception context
|
|||
|
* is removed from the stack.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Returns a standard Tcl result.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* Stores an appropriate error message in the interpreter as needed.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static int
|
|||
|
CheckForThrowInWrongContext(
|
|||
|
AssemblyEnv* assemEnvPtr) /* Assembly environment */
|
|||
|
{
|
|||
|
BasicBlock* blockPtr; /* Current basic block */
|
|||
|
|
|||
|
/*
|
|||
|
* Walk through the basic blocks in turn, checking all the ones that have
|
|||
|
* caught an exception and not disposed of it properly.
|
|||
|
*/
|
|||
|
|
|||
|
for (blockPtr = assemEnvPtr->head_bb;
|
|||
|
blockPtr != NULL;
|
|||
|
blockPtr = blockPtr->successor1) {
|
|||
|
if (blockPtr->catchState == BBCS_CAUGHT) {
|
|||
|
/*
|
|||
|
* Walk through the instructions in the basic block.
|
|||
|
*/
|
|||
|
|
|||
|
if (CheckNonThrowingBlock(assemEnvPtr, blockPtr) != TCL_OK) {
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
return TCL_OK;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* CheckNonThrowingBlock --
|
|||
|
*
|
|||
|
* Check that a basic block cannot throw an exception.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Returns TCL_ERROR if the block cannot be proven to be nonthrowing.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* Stashes an error message in the interpreter result.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static int
|
|||
|
CheckNonThrowingBlock(
|
|||
|
AssemblyEnv* assemEnvPtr, /* Assembly environment */
|
|||
|
BasicBlock* blockPtr) /* Basic block where exceptions are not
|
|||
|
* allowed */
|
|||
|
{
|
|||
|
CompileEnv* envPtr = assemEnvPtr->envPtr;
|
|||
|
/* Compilation environment */
|
|||
|
Tcl_Interp* interp = (Tcl_Interp*) envPtr->iPtr;
|
|||
|
/* Tcl interpreter */
|
|||
|
BasicBlock* nextPtr; /* Pointer to the succeeding basic block */
|
|||
|
int offset; /* Bytecode offset of the current
|
|||
|
* instruction */
|
|||
|
int bound; /* Bytecode offset following the last
|
|||
|
* instruction of the block. */
|
|||
|
unsigned char opcode; /* Current bytecode instruction */
|
|||
|
|
|||
|
/*
|
|||
|
* Determine where in the code array the basic block ends.
|
|||
|
*/
|
|||
|
|
|||
|
nextPtr = blockPtr->successor1;
|
|||
|
if (nextPtr == NULL) {
|
|||
|
bound = envPtr->codeNext - envPtr->codeStart;
|
|||
|
} else {
|
|||
|
bound = nextPtr->startOffset;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Walk through the instructions of the block.
|
|||
|
*/
|
|||
|
|
|||
|
offset = blockPtr->startOffset;
|
|||
|
while (offset < bound) {
|
|||
|
/*
|
|||
|
* Determine whether an instruction is nonthrowing.
|
|||
|
*/
|
|||
|
|
|||
|
opcode = (envPtr->codeStart)[offset];
|
|||
|
if (BytecodeMightThrow(opcode)) {
|
|||
|
/*
|
|||
|
* Report an error for a throw in the wrong context.
|
|||
|
*/
|
|||
|
|
|||
|
if (assemEnvPtr->flags & TCL_EVAL_DIRECT) {
|
|||
|
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
|
|||
|
"\"%s\" instruction may not appear in "
|
|||
|
"a context where an exception has been "
|
|||
|
"caught and not disposed of.",
|
|||
|
tclInstructionTable[opcode].name));
|
|||
|
Tcl_SetErrorCode(interp, "TCL", "ASSEM", "BADTHROW", NULL);
|
|||
|
AddBasicBlockRangeToErrorInfo(assemEnvPtr, blockPtr);
|
|||
|
}
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
offset += tclInstructionTable[opcode].numBytes;
|
|||
|
}
|
|||
|
return TCL_OK;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* BytecodeMightThrow --
|
|||
|
*
|
|||
|
* Tests if a given bytecode instruction might throw an exception.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Returns 1 if the bytecode might throw an exception, 0 if the
|
|||
|
* instruction is known never to throw.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static int
|
|||
|
BytecodeMightThrow(
|
|||
|
unsigned char opcode)
|
|||
|
{
|
|||
|
/*
|
|||
|
* Binary search on the non-throwing bytecode list.
|
|||
|
*/
|
|||
|
|
|||
|
int min = 0;
|
|||
|
int max = sizeof(NonThrowingByteCodes) - 1;
|
|||
|
int mid;
|
|||
|
unsigned char c;
|
|||
|
|
|||
|
while (max >= min) {
|
|||
|
mid = (min + max) / 2;
|
|||
|
c = NonThrowingByteCodes[mid];
|
|||
|
if (opcode < c) {
|
|||
|
max = mid-1;
|
|||
|
} else if (opcode > c) {
|
|||
|
min = mid+1;
|
|||
|
} else {
|
|||
|
/*
|
|||
|
* Opcode is nonthrowing.
|
|||
|
*/
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return 1;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* CheckStack --
|
|||
|
*
|
|||
|
* Audit stack usage in a block of assembly code.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Returns a standard Tcl result.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* Updates stack depth on entry for all basic blocks in the flowgraph.
|
|||
|
* Calculates the max stack depth used in the program, and updates the
|
|||
|
* compilation environment to reflect it.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static int
|
|||
|
CheckStack(
|
|||
|
AssemblyEnv* assemEnvPtr) /* Assembly environment */
|
|||
|
{
|
|||
|
CompileEnv* envPtr = assemEnvPtr->envPtr;
|
|||
|
/* Compilation environment */
|
|||
|
int maxDepth; /* Maximum stack depth overall */
|
|||
|
|
|||
|
/*
|
|||
|
* Checking the head block will check all the other blocks recursively.
|
|||
|
*/
|
|||
|
|
|||
|
assemEnvPtr->maxDepth = 0;
|
|||
|
if (StackCheckBasicBlock(assemEnvPtr, assemEnvPtr->head_bb, NULL,
|
|||
|
0) == TCL_ERROR) {
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Post the max stack depth back to the compilation environment.
|
|||
|
*/
|
|||
|
|
|||
|
maxDepth = assemEnvPtr->maxDepth + envPtr->currStackDepth;
|
|||
|
if (maxDepth > envPtr->maxStackDepth) {
|
|||
|
envPtr->maxStackDepth = maxDepth;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* If the exit is reachable, make sure that the program exits with 1
|
|||
|
* operand on the stack.
|
|||
|
*/
|
|||
|
|
|||
|
if (StackCheckExit(assemEnvPtr) != TCL_OK) {
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Reset the visited state on all basic blocks.
|
|||
|
*/
|
|||
|
|
|||
|
ResetVisitedBasicBlocks(assemEnvPtr);
|
|||
|
return TCL_OK;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* StackCheckBasicBlock --
|
|||
|
*
|
|||
|
* Checks stack consumption for a basic block (and recursively for its
|
|||
|
* successors).
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Returns a standard Tcl result.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* Updates initial stack depth for the basic block and its successors.
|
|||
|
* (Final and maximum stack depth are relative to initial, and are not
|
|||
|
* touched).
|
|||
|
*
|
|||
|
* This procedure eventually checks, for the entire flow graph, whether stack
|
|||
|
* balance is consistent. It is an error for a given basic block to be
|
|||
|
* reachable along multiple flow paths with different stack depths.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static int
|
|||
|
StackCheckBasicBlock(
|
|||
|
AssemblyEnv* assemEnvPtr, /* Assembly environment */
|
|||
|
BasicBlock* blockPtr, /* Pointer to the basic block being checked */
|
|||
|
BasicBlock* predecessor, /* Pointer to the block that passed control to
|
|||
|
* this one. */
|
|||
|
int initialStackDepth) /* Stack depth on entry to the block */
|
|||
|
{
|
|||
|
CompileEnv* envPtr = assemEnvPtr->envPtr;
|
|||
|
/* Compilation environment */
|
|||
|
Tcl_Interp* interp = (Tcl_Interp*) envPtr->iPtr;
|
|||
|
/* Tcl interpreter */
|
|||
|
BasicBlock* jumpTarget; /* Basic block where a jump goes */
|
|||
|
int stackDepth; /* Current stack depth */
|
|||
|
int maxDepth; /* Maximum stack depth so far */
|
|||
|
int result; /* Tcl status return */
|
|||
|
Tcl_HashSearch jtSearch; /* Search structure for the jump table */
|
|||
|
Tcl_HashEntry* jtEntry; /* Hash entry in the jump table */
|
|||
|
Tcl_Obj* targetLabel; /* Target label from the jump table */
|
|||
|
Tcl_HashEntry* entry; /* Hash entry in the label table */
|
|||
|
|
|||
|
if (blockPtr->flags & BB_VISITED) {
|
|||
|
/*
|
|||
|
* If the block is already visited, check stack depth for consistency
|
|||
|
* among the paths that reach it.
|
|||
|
*/
|
|||
|
|
|||
|
if (blockPtr->initialStackDepth == initialStackDepth) {
|
|||
|
return TCL_OK;
|
|||
|
}
|
|||
|
if (assemEnvPtr->flags & TCL_EVAL_DIRECT) {
|
|||
|
Tcl_SetObjResult(interp, Tcl_NewStringObj(
|
|||
|
"inconsistent stack depths on two execution paths", -1));
|
|||
|
|
|||
|
/*
|
|||
|
* TODO - add execution trace of both paths
|
|||
|
*/
|
|||
|
|
|||
|
Tcl_SetErrorLine(interp, blockPtr->startLine);
|
|||
|
Tcl_SetErrorCode(interp, "TCL", "ASSEM", "BADSTACK", NULL);
|
|||
|
}
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* If the block is not already visited, set the 'predecessor' link to
|
|||
|
* indicate how control got to it. Set the initial stack depth to the
|
|||
|
* current stack depth in the flow of control.
|
|||
|
*/
|
|||
|
|
|||
|
blockPtr->flags |= BB_VISITED;
|
|||
|
blockPtr->predecessor = predecessor;
|
|||
|
blockPtr->initialStackDepth = initialStackDepth;
|
|||
|
|
|||
|
/*
|
|||
|
* Calculate minimum stack depth, and flag an error if the block
|
|||
|
* underflows the stack.
|
|||
|
*/
|
|||
|
|
|||
|
if (initialStackDepth + blockPtr->minStackDepth < 0) {
|
|||
|
if (assemEnvPtr->flags & TCL_EVAL_DIRECT) {
|
|||
|
Tcl_SetObjResult(interp, Tcl_NewStringObj("stack underflow", -1));
|
|||
|
Tcl_SetErrorCode(interp, "TCL", "ASSEM", "BADSTACK", NULL);
|
|||
|
AddBasicBlockRangeToErrorInfo(assemEnvPtr, blockPtr);
|
|||
|
Tcl_SetErrorLine(interp, blockPtr->startLine);
|
|||
|
}
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Make sure that the block doesn't try to pop below the stack level of an
|
|||
|
* enclosing catch.
|
|||
|
*/
|
|||
|
|
|||
|
if (blockPtr->enclosingCatch != 0 &&
|
|||
|
initialStackDepth + blockPtr->minStackDepth
|
|||
|
< (blockPtr->enclosingCatch->initialStackDepth
|
|||
|
+ blockPtr->enclosingCatch->finalStackDepth)) {
|
|||
|
if (assemEnvPtr->flags & TCL_EVAL_DIRECT) {
|
|||
|
Tcl_SetObjResult(interp, Tcl_NewStringObj(
|
|||
|
"code pops stack below level of enclosing catch", -1));
|
|||
|
Tcl_SetErrorCode(interp, "TCL", "ASSEM", "BADSTACKINCATCH", -1);
|
|||
|
AddBasicBlockRangeToErrorInfo(assemEnvPtr, blockPtr);
|
|||
|
Tcl_SetErrorLine(interp, blockPtr->startLine);
|
|||
|
}
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Update maximum stgack depth.
|
|||
|
*/
|
|||
|
|
|||
|
maxDepth = initialStackDepth + blockPtr->maxStackDepth;
|
|||
|
if (maxDepth > assemEnvPtr->maxDepth) {
|
|||
|
assemEnvPtr->maxDepth = maxDepth;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Calculate stack depth on exit from the block, and invoke this procedure
|
|||
|
* recursively to check successor blocks.
|
|||
|
*/
|
|||
|
|
|||
|
stackDepth = initialStackDepth + blockPtr->finalStackDepth;
|
|||
|
result = TCL_OK;
|
|||
|
if (blockPtr->flags & BB_FALLTHRU) {
|
|||
|
result = StackCheckBasicBlock(assemEnvPtr, blockPtr->successor1,
|
|||
|
blockPtr, stackDepth);
|
|||
|
}
|
|||
|
|
|||
|
if (result == TCL_OK && blockPtr->jumpTarget != NULL) {
|
|||
|
entry = Tcl_FindHashEntry(&assemEnvPtr->labelHash,
|
|||
|
Tcl_GetString(blockPtr->jumpTarget));
|
|||
|
jumpTarget = (BasicBlock*)Tcl_GetHashValue(entry);
|
|||
|
result = StackCheckBasicBlock(assemEnvPtr, jumpTarget, blockPtr,
|
|||
|
stackDepth);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* All blocks referenced in a jump table are successors.
|
|||
|
*/
|
|||
|
|
|||
|
if (blockPtr->flags & BB_JUMPTABLE) {
|
|||
|
for (jtEntry = Tcl_FirstHashEntry(&blockPtr->jtPtr->hashTable,
|
|||
|
&jtSearch);
|
|||
|
result == TCL_OK && jtEntry != NULL;
|
|||
|
jtEntry = Tcl_NextHashEntry(&jtSearch)) {
|
|||
|
targetLabel = (Tcl_Obj*)Tcl_GetHashValue(jtEntry);
|
|||
|
entry = Tcl_FindHashEntry(&assemEnvPtr->labelHash,
|
|||
|
Tcl_GetString(targetLabel));
|
|||
|
jumpTarget = (BasicBlock*)Tcl_GetHashValue(entry);
|
|||
|
result = StackCheckBasicBlock(assemEnvPtr, jumpTarget,
|
|||
|
blockPtr, stackDepth);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return result;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* StackCheckExit --
|
|||
|
*
|
|||
|
* Makes sure that the net stack effect of an entire assembly language
|
|||
|
* script is to push 1 result.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Returns a standard Tcl result, with an error message in the
|
|||
|
* interpreter result if the stack is wrong.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* If the assembly code had a net stack effect of zero, emits code to the
|
|||
|
* concluding block to push a null result. In any case, updates the stack
|
|||
|
* depth in the compile environment to reflect the net effect of the
|
|||
|
* assembly code.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static int
|
|||
|
StackCheckExit(
|
|||
|
AssemblyEnv* assemEnvPtr) /* Assembly environment */
|
|||
|
{
|
|||
|
CompileEnv* envPtr = assemEnvPtr->envPtr;
|
|||
|
/* Compilation environment */
|
|||
|
Tcl_Interp* interp = (Tcl_Interp*) envPtr->iPtr;
|
|||
|
/* Tcl interpreter */
|
|||
|
int depth; /* Net stack effect */
|
|||
|
int litIndex; /* Index in the literal pool of the empty
|
|||
|
* string */
|
|||
|
BasicBlock* curr_bb = assemEnvPtr->curr_bb;
|
|||
|
/* Final basic block in the assembly */
|
|||
|
|
|||
|
/*
|
|||
|
* Don't perform these checks if execution doesn't reach the exit (either
|
|||
|
* because of an infinite loop or because the only return is from the
|
|||
|
* middle.
|
|||
|
*/
|
|||
|
|
|||
|
if (curr_bb->flags & BB_VISITED) {
|
|||
|
/*
|
|||
|
* Exit with no operands; push an empty one.
|
|||
|
*/
|
|||
|
|
|||
|
depth = curr_bb->finalStackDepth + curr_bb->initialStackDepth;
|
|||
|
if (depth == 0) {
|
|||
|
/*
|
|||
|
* Emit a 'push' of the empty literal.
|
|||
|
*/
|
|||
|
|
|||
|
litIndex = TclRegisterNewLiteral(envPtr, "", 0);
|
|||
|
|
|||
|
/*
|
|||
|
* Assumes that 'push' is at slot 0 in TalInstructionTable.
|
|||
|
*/
|
|||
|
|
|||
|
BBEmitInst1or4(assemEnvPtr, 0, litIndex, 0);
|
|||
|
++depth;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Exit with unbalanced stack.
|
|||
|
*/
|
|||
|
|
|||
|
if (depth != 1) {
|
|||
|
if (assemEnvPtr->flags & TCL_EVAL_DIRECT) {
|
|||
|
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
|
|||
|
"stack is unbalanced on exit from the code (depth=%d)",
|
|||
|
depth));
|
|||
|
Tcl_SetErrorCode(interp, "TCL", "ASSEM", "BADSTACK", NULL);
|
|||
|
}
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Record stack usage.
|
|||
|
*/
|
|||
|
|
|||
|
envPtr->currStackDepth += depth;
|
|||
|
}
|
|||
|
|
|||
|
return TCL_OK;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* ProcessCatches --
|
|||
|
*
|
|||
|
* First pass of 'catch' processing.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Returns a standard Tcl result, with an appropriate error message if
|
|||
|
* the result is TCL_ERROR.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* Labels all basic blocks with their enclosing catches.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static int
|
|||
|
ProcessCatches(
|
|||
|
AssemblyEnv* assemEnvPtr) /* Assembly environment */
|
|||
|
{
|
|||
|
BasicBlock* blockPtr; /* Pointer to a basic block */
|
|||
|
|
|||
|
/*
|
|||
|
* Clear the catch state of all basic blocks.
|
|||
|
*/
|
|||
|
|
|||
|
for (blockPtr = assemEnvPtr->head_bb;
|
|||
|
blockPtr != NULL;
|
|||
|
blockPtr = blockPtr->successor1) {
|
|||
|
blockPtr->catchState = BBCS_UNKNOWN;
|
|||
|
blockPtr->enclosingCatch = NULL;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Start the check recursively from the first basic block, which is
|
|||
|
* outside any exception context
|
|||
|
*/
|
|||
|
|
|||
|
if (ProcessCatchesInBasicBlock(assemEnvPtr, assemEnvPtr->head_bb,
|
|||
|
NULL, BBCS_NONE, 0) != TCL_OK) {
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Check for unclosed catch on exit.
|
|||
|
*/
|
|||
|
|
|||
|
if (CheckForUnclosedCatches(assemEnvPtr) != TCL_OK) {
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Now there's enough information to build the exception ranges.
|
|||
|
*/
|
|||
|
|
|||
|
if (BuildExceptionRanges(assemEnvPtr) != TCL_OK) {
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Finally, restore any exception ranges from embedded scripts.
|
|||
|
*/
|
|||
|
|
|||
|
RestoreEmbeddedExceptionRanges(assemEnvPtr);
|
|||
|
return TCL_OK;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* ProcessCatchesInBasicBlock --
|
|||
|
*
|
|||
|
* First-pass catch processing for one basic block.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Returns a standard Tcl result, with error message in the interpreter
|
|||
|
* result if an error occurs.
|
|||
|
*
|
|||
|
* This procedure checks consistency of the exception context through the
|
|||
|
* assembler program, and records the enclosing 'catch' for every basic block.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static int
|
|||
|
ProcessCatchesInBasicBlock(
|
|||
|
AssemblyEnv* assemEnvPtr, /* Assembly environment */
|
|||
|
BasicBlock* bbPtr, /* Basic block being processed */
|
|||
|
BasicBlock* enclosing, /* Start basic block of the enclosing catch */
|
|||
|
enum BasicBlockCatchState state,
|
|||
|
/* BBCS_NONE, BBCS_INCATCH, or BBCS_CAUGHT */
|
|||
|
int catchDepth) /* Depth of nesting of catches */
|
|||
|
{
|
|||
|
CompileEnv* envPtr = assemEnvPtr->envPtr;
|
|||
|
/* Compilation environment */
|
|||
|
Tcl_Interp* interp = (Tcl_Interp*) envPtr->iPtr;
|
|||
|
/* Tcl interpreter */
|
|||
|
int result; /* Return value from this procedure */
|
|||
|
BasicBlock* fallThruEnclosing;
|
|||
|
/* Enclosing catch if execution falls thru */
|
|||
|
enum BasicBlockCatchState fallThruState;
|
|||
|
/* Catch state of the successor block */
|
|||
|
BasicBlock* jumpEnclosing; /* Enclosing catch if execution goes to jump
|
|||
|
* target */
|
|||
|
enum BasicBlockCatchState jumpState;
|
|||
|
/* Catch state of the jump target */
|
|||
|
int changed = 0; /* Flag == 1 iff successor blocks need to be
|
|||
|
* checked because the state of this block has
|
|||
|
* changed. */
|
|||
|
BasicBlock* jumpTarget; /* Basic block where a jump goes */
|
|||
|
Tcl_HashSearch jtSearch; /* Hash search control for a jumptable */
|
|||
|
Tcl_HashEntry* jtEntry; /* Entry in a jumptable */
|
|||
|
Tcl_Obj* targetLabel; /* Target label from a jumptable */
|
|||
|
Tcl_HashEntry* entry; /* Entry from the label table */
|
|||
|
|
|||
|
/*
|
|||
|
* Update the state of the current block, checking for consistency. Set
|
|||
|
* 'changed' to 1 if the state changes and successor blocks need to be
|
|||
|
* rechecked.
|
|||
|
*/
|
|||
|
|
|||
|
if (bbPtr->catchState == BBCS_UNKNOWN) {
|
|||
|
bbPtr->enclosingCatch = enclosing;
|
|||
|
} else if (bbPtr->enclosingCatch != enclosing) {
|
|||
|
if (assemEnvPtr->flags & TCL_EVAL_DIRECT) {
|
|||
|
Tcl_SetObjResult(interp, Tcl_NewStringObj(
|
|||
|
"execution reaches an instruction in inconsistent "
|
|||
|
"exception contexts", -1));
|
|||
|
Tcl_SetErrorLine(interp, bbPtr->startLine);
|
|||
|
Tcl_SetErrorCode(interp, "TCL", "ASSEM", "BADCATCH", NULL);
|
|||
|
}
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
if (state > bbPtr->catchState) {
|
|||
|
bbPtr->catchState = state;
|
|||
|
changed = 1;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* If this block has been visited before, and its state hasn't changed,
|
|||
|
* we're done with it for now.
|
|||
|
*/
|
|||
|
|
|||
|
if (!changed) {
|
|||
|
return TCL_OK;
|
|||
|
}
|
|||
|
bbPtr->catchDepth = catchDepth;
|
|||
|
|
|||
|
/*
|
|||
|
* Determine enclosing catch and 'caught' state for the fallthrough and
|
|||
|
* the jump target. Default for both is the state of the current block.
|
|||
|
*/
|
|||
|
|
|||
|
fallThruEnclosing = enclosing;
|
|||
|
fallThruState = state;
|
|||
|
jumpEnclosing = enclosing;
|
|||
|
jumpState = state;
|
|||
|
|
|||
|
/*
|
|||
|
* TODO: Make sure that the test cases include validating that a natural
|
|||
|
* loop can't include 'beginCatch' or 'endCatch'
|
|||
|
*/
|
|||
|
|
|||
|
if (bbPtr->flags & BB_BEGINCATCH) {
|
|||
|
/*
|
|||
|
* If the block begins a catch, the state for the successor is 'in
|
|||
|
* catch'. The jump target is the exception exit, and the state of the
|
|||
|
* jump target is 'caught.'
|
|||
|
*/
|
|||
|
|
|||
|
fallThruEnclosing = bbPtr;
|
|||
|
fallThruState = BBCS_INCATCH;
|
|||
|
jumpEnclosing = bbPtr;
|
|||
|
jumpState = BBCS_CAUGHT;
|
|||
|
++catchDepth;
|
|||
|
}
|
|||
|
|
|||
|
if (bbPtr->flags & BB_ENDCATCH) {
|
|||
|
/*
|
|||
|
* If the block ends a catch, the state for the successor is whatever
|
|||
|
* the state was on entry to the catch.
|
|||
|
*/
|
|||
|
|
|||
|
if (enclosing == NULL) {
|
|||
|
if (assemEnvPtr->flags & TCL_EVAL_DIRECT) {
|
|||
|
Tcl_SetObjResult(interp, Tcl_NewStringObj(
|
|||
|
"endCatch without a corresponding beginCatch", -1));
|
|||
|
Tcl_SetErrorLine(interp, bbPtr->startLine);
|
|||
|
Tcl_SetErrorCode(interp, "TCL", "ASSEM", "BADENDCATCH", NULL);
|
|||
|
}
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
fallThruEnclosing = enclosing->enclosingCatch;
|
|||
|
fallThruState = enclosing->catchState;
|
|||
|
--catchDepth;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Visit any successor blocks with the appropriate exception context
|
|||
|
*/
|
|||
|
|
|||
|
result = TCL_OK;
|
|||
|
if (bbPtr->flags & BB_FALLTHRU) {
|
|||
|
result = ProcessCatchesInBasicBlock(assemEnvPtr, bbPtr->successor1,
|
|||
|
fallThruEnclosing, fallThruState, catchDepth);
|
|||
|
}
|
|||
|
if (result == TCL_OK && bbPtr->jumpTarget != NULL) {
|
|||
|
entry = Tcl_FindHashEntry(&assemEnvPtr->labelHash,
|
|||
|
Tcl_GetString(bbPtr->jumpTarget));
|
|||
|
jumpTarget = (BasicBlock*)Tcl_GetHashValue(entry);
|
|||
|
result = ProcessCatchesInBasicBlock(assemEnvPtr, jumpTarget,
|
|||
|
jumpEnclosing, jumpState, catchDepth);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* All blocks referenced in a jump table are successors.
|
|||
|
*/
|
|||
|
|
|||
|
if (bbPtr->flags & BB_JUMPTABLE) {
|
|||
|
for (jtEntry = Tcl_FirstHashEntry(&bbPtr->jtPtr->hashTable,&jtSearch);
|
|||
|
result == TCL_OK && jtEntry != NULL;
|
|||
|
jtEntry = Tcl_NextHashEntry(&jtSearch)) {
|
|||
|
targetLabel = (Tcl_Obj*)Tcl_GetHashValue(jtEntry);
|
|||
|
entry = Tcl_FindHashEntry(&assemEnvPtr->labelHash,
|
|||
|
Tcl_GetString(targetLabel));
|
|||
|
jumpTarget = (BasicBlock*)Tcl_GetHashValue(entry);
|
|||
|
result = ProcessCatchesInBasicBlock(assemEnvPtr, jumpTarget,
|
|||
|
jumpEnclosing, jumpState, catchDepth);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return result;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* CheckForUnclosedCatches --
|
|||
|
*
|
|||
|
* Checks that a sequence of assembly code has no unclosed catches on
|
|||
|
* exit.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Returns a standard Tcl result, with an error message for unclosed
|
|||
|
* catches.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static int
|
|||
|
CheckForUnclosedCatches(
|
|||
|
AssemblyEnv* assemEnvPtr) /* Assembly environment */
|
|||
|
{
|
|||
|
CompileEnv* envPtr = assemEnvPtr->envPtr;
|
|||
|
/* Compilation environment */
|
|||
|
Tcl_Interp* interp = (Tcl_Interp*) envPtr->iPtr;
|
|||
|
/* Tcl interpreter */
|
|||
|
|
|||
|
if (assemEnvPtr->curr_bb->catchState >= BBCS_INCATCH) {
|
|||
|
if (assemEnvPtr->flags & TCL_EVAL_DIRECT) {
|
|||
|
Tcl_SetObjResult(interp, Tcl_NewStringObj(
|
|||
|
"catch still active on exit from assembly code", -1));
|
|||
|
Tcl_SetErrorLine(interp,
|
|||
|
assemEnvPtr->curr_bb->enclosingCatch->startLine);
|
|||
|
Tcl_SetErrorCode(interp, "TCL", "ASSEM", "UNCLOSEDCATCH", NULL);
|
|||
|
}
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
return TCL_OK;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* BuildExceptionRanges --
|
|||
|
*
|
|||
|
* Walks through the assembly code and builds exception ranges for the
|
|||
|
* catches embedded therein.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Returns a standard Tcl result with an error message in the interpreter
|
|||
|
* if anything is unsuccessful.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* Each contiguous block of code with a given catch exit is assigned an
|
|||
|
* exception range at the appropriate level.
|
|||
|
* Exception ranges in embedded blocks have their levels corrected and
|
|||
|
* collated into the table.
|
|||
|
* Blocks that end with 'beginCatch' are associated with the innermost
|
|||
|
* exception range of the following block.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static int
|
|||
|
BuildExceptionRanges(
|
|||
|
AssemblyEnv* assemEnvPtr) /* Assembly environment */
|
|||
|
{
|
|||
|
CompileEnv* envPtr = assemEnvPtr->envPtr;
|
|||
|
/* Compilation environment */
|
|||
|
BasicBlock* bbPtr; /* Current basic block */
|
|||
|
BasicBlock* prevPtr = NULL; /* Previous basic block */
|
|||
|
int catchDepth = 0; /* Current catch depth */
|
|||
|
int maxCatchDepth = 0; /* Maximum catch depth in the program */
|
|||
|
BasicBlock** catches; /* Stack of catches in progress */
|
|||
|
int* catchIndices; /* Indices of the exception ranges of catches
|
|||
|
* in progress */
|
|||
|
int i;
|
|||
|
|
|||
|
/*
|
|||
|
* Determine the max catch depth for the entire assembly script
|
|||
|
* (excluding embedded eval's and expr's, which will be handled later).
|
|||
|
*/
|
|||
|
|
|||
|
for (bbPtr=assemEnvPtr->head_bb; bbPtr != NULL; bbPtr=bbPtr->successor1) {
|
|||
|
if (bbPtr->catchDepth > maxCatchDepth) {
|
|||
|
maxCatchDepth = bbPtr->catchDepth;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Allocate memory for a stack of active catches.
|
|||
|
*/
|
|||
|
|
|||
|
catches = (BasicBlock**)ckalloc(maxCatchDepth * sizeof(BasicBlock*));
|
|||
|
catchIndices = (int *)ckalloc(maxCatchDepth * sizeof(int));
|
|||
|
for (i = 0; i < maxCatchDepth; ++i) {
|
|||
|
catches[i] = NULL;
|
|||
|
catchIndices[i] = -1;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Walk through the basic blocks and manage exception ranges.
|
|||
|
*/
|
|||
|
|
|||
|
for (bbPtr=assemEnvPtr->head_bb; bbPtr != NULL; bbPtr=bbPtr->successor1) {
|
|||
|
UnstackExpiredCatches(envPtr, bbPtr, catchDepth, catches,
|
|||
|
catchIndices);
|
|||
|
LookForFreshCatches(bbPtr, catches);
|
|||
|
StackFreshCatches(assemEnvPtr, bbPtr, catchDepth, catches,
|
|||
|
catchIndices);
|
|||
|
|
|||
|
/*
|
|||
|
* If the last block was a 'begin catch', fill in the exception range.
|
|||
|
*/
|
|||
|
|
|||
|
catchDepth = bbPtr->catchDepth;
|
|||
|
if (prevPtr != NULL && (prevPtr->flags & BB_BEGINCATCH)) {
|
|||
|
TclStoreInt4AtPtr(catchIndices[catchDepth-1],
|
|||
|
envPtr->codeStart + bbPtr->startOffset - 4);
|
|||
|
}
|
|||
|
|
|||
|
prevPtr = bbPtr;
|
|||
|
}
|
|||
|
|
|||
|
/* Make sure that all catches are closed */
|
|||
|
|
|||
|
if (catchDepth != 0) {
|
|||
|
Tcl_Panic("unclosed catch at end of code in "
|
|||
|
"tclAssembly.c:BuildExceptionRanges, can't happen");
|
|||
|
}
|
|||
|
|
|||
|
/* Free temp storage */
|
|||
|
|
|||
|
ckfree(catchIndices);
|
|||
|
ckfree(catches);
|
|||
|
|
|||
|
return TCL_OK;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* UnstackExpiredCatches --
|
|||
|
*
|
|||
|
* Unstacks and closes the exception ranges for any catch contexts that
|
|||
|
* were active in the previous basic block but are inactive in the
|
|||
|
* current one.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static void
|
|||
|
UnstackExpiredCatches(
|
|||
|
CompileEnv* envPtr, /* Compilation environment */
|
|||
|
BasicBlock* bbPtr, /* Basic block being processed */
|
|||
|
int catchDepth, /* Depth of nesting of catches prior to entry
|
|||
|
* to this block */
|
|||
|
BasicBlock** catches, /* Array of catch contexts */
|
|||
|
int* catchIndices) /* Indices of the exception ranges
|
|||
|
* corresponding to the catch contexts */
|
|||
|
{
|
|||
|
ExceptionRange* range; /* Exception range for a specific catch */
|
|||
|
BasicBlock* block; /* Catch block being examined */
|
|||
|
BasicBlockCatchState catchState;
|
|||
|
/* State of the code relative to the catch
|
|||
|
* block being examined ("in catch" or
|
|||
|
* "caught"). */
|
|||
|
|
|||
|
/*
|
|||
|
* Unstack any catches that are deeper than the nesting level of the basic
|
|||
|
* block being entered.
|
|||
|
*/
|
|||
|
|
|||
|
while (catchDepth > bbPtr->catchDepth) {
|
|||
|
--catchDepth;
|
|||
|
if (catches[catchDepth] != NULL) {
|
|||
|
range = envPtr->exceptArrayPtr + catchIndices[catchDepth];
|
|||
|
range->numCodeBytes = bbPtr->startOffset - range->codeOffset;
|
|||
|
catches[catchDepth] = NULL;
|
|||
|
catchIndices[catchDepth] = -1;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Unstack any catches that don't match the basic block being entered,
|
|||
|
* either because they are no longer part of the context, or because the
|
|||
|
* context has changed from INCATCH to CAUGHT.
|
|||
|
*/
|
|||
|
|
|||
|
catchState = bbPtr->catchState;
|
|||
|
block = bbPtr->enclosingCatch;
|
|||
|
while (catchDepth > 0) {
|
|||
|
--catchDepth;
|
|||
|
if (catches[catchDepth] != NULL) {
|
|||
|
if (catches[catchDepth] != block || catchState >= BBCS_CAUGHT) {
|
|||
|
range = envPtr->exceptArrayPtr + catchIndices[catchDepth];
|
|||
|
range->numCodeBytes = bbPtr->startOffset - range->codeOffset;
|
|||
|
catches[catchDepth] = NULL;
|
|||
|
catchIndices[catchDepth] = -1;
|
|||
|
}
|
|||
|
catchState = block->catchState;
|
|||
|
block = block->enclosingCatch;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* LookForFreshCatches --
|
|||
|
*
|
|||
|
* Determines whether a basic block being entered needs any exception
|
|||
|
* ranges that are not already stacked.
|
|||
|
*
|
|||
|
* Does not create the ranges: this procedure iterates from the innermost
|
|||
|
* catch outward, but exception ranges must be created from the outermost
|
|||
|
* catch inward.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static void
|
|||
|
LookForFreshCatches(
|
|||
|
BasicBlock* bbPtr, /* Basic block being entered */
|
|||
|
BasicBlock** catches) /* Array of catch contexts that are already
|
|||
|
* entered */
|
|||
|
{
|
|||
|
BasicBlockCatchState catchState;
|
|||
|
/* State ("in catch" or "caught") of the
|
|||
|
* current catch. */
|
|||
|
BasicBlock* block; /* Current enclosing catch */
|
|||
|
int catchDepth; /* Nesting depth of the current catch */
|
|||
|
|
|||
|
catchState = bbPtr->catchState;
|
|||
|
block = bbPtr->enclosingCatch;
|
|||
|
catchDepth = bbPtr->catchDepth;
|
|||
|
while (catchDepth > 0) {
|
|||
|
--catchDepth;
|
|||
|
if (catches[catchDepth] != block && catchState < BBCS_CAUGHT) {
|
|||
|
catches[catchDepth] = block;
|
|||
|
}
|
|||
|
catchState = block->catchState;
|
|||
|
block = block->enclosingCatch;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* StackFreshCatches --
|
|||
|
*
|
|||
|
* Make ExceptionRange records for any catches that are in the basic
|
|||
|
* block being entered and were not in the previous basic block.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static void
|
|||
|
StackFreshCatches(
|
|||
|
AssemblyEnv* assemEnvPtr, /* Assembly environment */
|
|||
|
BasicBlock* bbPtr, /* Basic block being processed */
|
|||
|
int catchDepth, /* Depth of nesting of catches prior to entry
|
|||
|
* to this block */
|
|||
|
BasicBlock** catches, /* Array of catch contexts */
|
|||
|
int* catchIndices) /* Indices of the exception ranges
|
|||
|
* corresponding to the catch contexts */
|
|||
|
{
|
|||
|
CompileEnv* envPtr = assemEnvPtr->envPtr;
|
|||
|
/* Compilation environment */
|
|||
|
ExceptionRange* range; /* Exception range for a specific catch */
|
|||
|
BasicBlock* block; /* Catch block being examined */
|
|||
|
BasicBlock* errorExit; /* Error exit from the catch block */
|
|||
|
Tcl_HashEntry* entryPtr;
|
|||
|
|
|||
|
catchDepth = 0;
|
|||
|
|
|||
|
/*
|
|||
|
* Iterate through the enclosing catch blocks from the outside in,
|
|||
|
* looking for ones that don't have exception ranges (and are uncaught)
|
|||
|
*/
|
|||
|
|
|||
|
for (catchDepth = 0; catchDepth < bbPtr->catchDepth; ++catchDepth) {
|
|||
|
if (catchIndices[catchDepth] == -1 && catches[catchDepth] != NULL) {
|
|||
|
/*
|
|||
|
* Create an exception range for a block that needs one.
|
|||
|
*/
|
|||
|
|
|||
|
block = catches[catchDepth];
|
|||
|
catchIndices[catchDepth] =
|
|||
|
TclCreateExceptRange(CATCH_EXCEPTION_RANGE, envPtr);
|
|||
|
range = envPtr->exceptArrayPtr + catchIndices[catchDepth];
|
|||
|
range->nestingLevel = envPtr->exceptDepth + catchDepth;
|
|||
|
envPtr->maxExceptDepth =
|
|||
|
TclMax(range->nestingLevel + 1, envPtr->maxExceptDepth);
|
|||
|
range->codeOffset = bbPtr->startOffset;
|
|||
|
|
|||
|
entryPtr = Tcl_FindHashEntry(&assemEnvPtr->labelHash,
|
|||
|
Tcl_GetString(block->jumpTarget));
|
|||
|
if (entryPtr == NULL) {
|
|||
|
Tcl_Panic("undefined label in tclAssembly.c:"
|
|||
|
"BuildExceptionRanges, can't happen");
|
|||
|
}
|
|||
|
|
|||
|
errorExit = (BasicBlock*)Tcl_GetHashValue(entryPtr);
|
|||
|
range->catchOffset = errorExit->startOffset;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* RestoreEmbeddedExceptionRanges --
|
|||
|
*
|
|||
|
* Processes an assembly script, replacing any exception ranges that
|
|||
|
* were present in embedded code.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static void
|
|||
|
RestoreEmbeddedExceptionRanges(
|
|||
|
AssemblyEnv* assemEnvPtr) /* Assembly environment */
|
|||
|
{
|
|||
|
CompileEnv* envPtr = assemEnvPtr->envPtr;
|
|||
|
/* Compilation environment */
|
|||
|
BasicBlock* bbPtr; /* Current basic block */
|
|||
|
int rangeBase; /* Base of the foreign exception ranges when
|
|||
|
* they are reinstalled */
|
|||
|
int rangeIndex; /* Index of the current foreign exception
|
|||
|
* range as reinstalled */
|
|||
|
ExceptionRange* range; /* Current foreign exception range */
|
|||
|
unsigned char opcode; /* Current instruction's opcode */
|
|||
|
int catchIndex; /* Index of the exception range to which the
|
|||
|
* current instruction refers */
|
|||
|
int i;
|
|||
|
|
|||
|
/*
|
|||
|
* Walk the basic blocks looking for exceptions in embedded scripts.
|
|||
|
*/
|
|||
|
|
|||
|
for (bbPtr = assemEnvPtr->head_bb;
|
|||
|
bbPtr != NULL;
|
|||
|
bbPtr = bbPtr->successor1) {
|
|||
|
if (bbPtr->foreignExceptionCount != 0) {
|
|||
|
/*
|
|||
|
* Reinstall the embedded exceptions and track their nesting level
|
|||
|
*/
|
|||
|
|
|||
|
rangeBase = envPtr->exceptArrayNext;
|
|||
|
for (i = 0; i < bbPtr->foreignExceptionCount; ++i) {
|
|||
|
range = bbPtr->foreignExceptions + i;
|
|||
|
rangeIndex = TclCreateExceptRange(range->type, envPtr);
|
|||
|
range->nestingLevel += envPtr->exceptDepth + bbPtr->catchDepth;
|
|||
|
memcpy(envPtr->exceptArrayPtr + rangeIndex, range,
|
|||
|
sizeof(ExceptionRange));
|
|||
|
if (range->nestingLevel >= envPtr->maxExceptDepth) {
|
|||
|
envPtr->maxExceptDepth = range->nestingLevel + 1;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Walk through the bytecode of the basic block, and relocate
|
|||
|
* INST_BEGIN_CATCH4 instructions to the new locations
|
|||
|
*/
|
|||
|
|
|||
|
i = bbPtr->startOffset;
|
|||
|
while (i < bbPtr->successor1->startOffset) {
|
|||
|
opcode = envPtr->codeStart[i];
|
|||
|
if (opcode == INST_BEGIN_CATCH4) {
|
|||
|
catchIndex = TclGetUInt4AtPtr(envPtr->codeStart + i + 1);
|
|||
|
if (catchIndex >= bbPtr->foreignExceptionBase
|
|||
|
&& catchIndex < (bbPtr->foreignExceptionBase +
|
|||
|
bbPtr->foreignExceptionCount)) {
|
|||
|
catchIndex -= bbPtr->foreignExceptionBase;
|
|||
|
catchIndex += rangeBase;
|
|||
|
TclStoreInt4AtPtr(catchIndex, envPtr->codeStart+i+1);
|
|||
|
}
|
|||
|
}
|
|||
|
i += tclInstructionTable[opcode].numBytes;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* ResetVisitedBasicBlocks --
|
|||
|
*
|
|||
|
* Turns off the 'visited' flag in all basic blocks at the conclusion
|
|||
|
* of a pass.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static void
|
|||
|
ResetVisitedBasicBlocks(
|
|||
|
AssemblyEnv* assemEnvPtr) /* Assembly environment */
|
|||
|
{
|
|||
|
BasicBlock* block;
|
|||
|
|
|||
|
for (block = assemEnvPtr->head_bb; block != NULL;
|
|||
|
block = block->successor1) {
|
|||
|
block->flags &= ~BB_VISITED;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* AddBasicBlockRangeToErrorInfo --
|
|||
|
*
|
|||
|
* Updates the error info of the Tcl interpreter to show a given basic
|
|||
|
* block in the code.
|
|||
|
*
|
|||
|
* This procedure is used to label the callstack with source location
|
|||
|
* information when reporting an error in stack checking.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static void
|
|||
|
AddBasicBlockRangeToErrorInfo(
|
|||
|
AssemblyEnv* assemEnvPtr, /* Assembly environment */
|
|||
|
BasicBlock* bbPtr) /* Basic block in which the error is found */
|
|||
|
{
|
|||
|
CompileEnv* envPtr = assemEnvPtr->envPtr;
|
|||
|
/* Compilation environment */
|
|||
|
Tcl_Interp* interp = (Tcl_Interp*) envPtr->iPtr;
|
|||
|
/* Tcl interpreter */
|
|||
|
Tcl_Obj* lineNo; /* Line number in the source */
|
|||
|
|
|||
|
Tcl_AddErrorInfo(interp, "\n in assembly code between lines ");
|
|||
|
TclNewIntObj(lineNo, bbPtr->startLine);
|
|||
|
Tcl_IncrRefCount(lineNo);
|
|||
|
Tcl_AppendObjToErrorInfo(interp, lineNo);
|
|||
|
Tcl_AddErrorInfo(interp, " and ");
|
|||
|
if (bbPtr->successor1 != NULL) {
|
|||
|
Tcl_SetIntObj(lineNo, bbPtr->successor1->startLine);
|
|||
|
Tcl_AppendObjToErrorInfo(interp, lineNo);
|
|||
|
} else {
|
|||
|
Tcl_AddErrorInfo(interp, "end of assembly code");
|
|||
|
}
|
|||
|
Tcl_DecrRefCount(lineNo);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* DupAssembleCodeInternalRep --
|
|||
|
*
|
|||
|
* Part of the Tcl object type implementation for Tcl assembly language
|
|||
|
* bytecode. We do not copy the bytecode internalrep. Instead, we return
|
|||
|
* without setting copyPtr->typePtr, so the copy is a plain string copy
|
|||
|
* of the assembly source, and if it is to be used as a compiled
|
|||
|
* expression, it will need to be reprocessed.
|
|||
|
*
|
|||
|
* This makes sense, because with Tcl's copy-on-write practices, the
|
|||
|
* usual (only?) time Tcl_DuplicateObj() will be called is when the copy
|
|||
|
* is about to be modified, which would invalidate any copied bytecode
|
|||
|
* anyway. The only reason it might make sense to copy the bytecode is if
|
|||
|
* we had some modifying routines that operated directly on the internalrep,
|
|||
|
* as we do for lists and dicts.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* None.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* None.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static void
|
|||
|
DupAssembleCodeInternalRep(
|
|||
|
Tcl_Obj *srcPtr,
|
|||
|
Tcl_Obj *copyPtr)
|
|||
|
{
|
|||
|
(void)srcPtr;
|
|||
|
(void)copyPtr;
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* FreeAssembleCodeInternalRep --
|
|||
|
*
|
|||
|
* Part of the Tcl object type implementation for Tcl expression
|
|||
|
* bytecode. Frees the storage allocated to hold the internal rep, unless
|
|||
|
* ref counts indicate bytecode execution is still in progress.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* None.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* May free allocated memory. Leaves objPtr untyped.
|
|||
|
*
|
|||
|
*-----------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static void
|
|||
|
FreeAssembleCodeInternalRep(
|
|||
|
Tcl_Obj *objPtr)
|
|||
|
{
|
|||
|
ByteCode *codePtr = (ByteCode *)objPtr->internalRep.twoPtrValue.ptr1;
|
|||
|
|
|||
|
if (codePtr->refCount-- <= 1) {
|
|||
|
TclCleanupByteCode(codePtr);
|
|||
|
}
|
|||
|
objPtr->typePtr = NULL;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Local Variables:
|
|||
|
* mode: c
|
|||
|
* c-basic-offset: 4
|
|||
|
* fill-column: 78
|
|||
|
* End:
|
|||
|
*/
|