# # # The Nim Compiler # (c) Copyright 2013 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. # ## This module contains the type definitions for the new evaluation engine. ## An instruction is 1-3 int32s in memory, it is a register based VM. import tables import ast, idents, options, modulegraphs, lineinfos type TInstrType* = uint64 const regOBits = 8 # Opcode regABits = 16 regBBits = 16 regCBits = 16 regBxBits = 24 byteExcess* = 128 # we use excess-K for immediates # Calculate register shifts, masks and ranges const regOShift* = 0.TInstrType regAShift* = (regOShift + regOBits) regBShift* = (regAShift + regABits) regCShift* = (regBShift + regBBits) regBxShift* = (regAShift + regABits) regOMask* = ((1.TInstrType shl regOBits) - 1) regAMask* = ((1.TInstrType shl regABits) - 1) regBMask* = ((1.TInstrType shl regBBits) - 1) regCMask* = ((1.TInstrType shl regCBits) - 1) regBxMask* = ((1.TInstrType shl regBxBits) - 1) wordExcess* = 1 shl (regBxBits-1) regBxMin* = -wordExcess+1 regBxMax* = wordExcess-1 type TRegister* = range[0..regAMask.int] TDest* = range[-1..regAMask.int] TInstr* = distinct TInstrType TOpcode* = enum opcEof, # end of code opcRet, # return opcYldYoid, # yield with no value opcYldVal, # yield with a value opcAsgnInt, opcAsgnFloat, opcAsgnRef, opcAsgnComplex, opcCastIntToFloat32, # int and float must be of the same byte size opcCastIntToFloat64, # int and float must be of the same byte size opcCastFloatToInt32, # int and float must be of the same byte size opcCastFloatToInt64, # int and float must be of the same byte size opcCastPtrToInt, opcCastIntToPtr, opcFastAsgnComplex, opcNodeToReg, opcLdArr, # a = b[c] opcLdArrAddr, # a = addr(b[c]) opcWrArr, # a[b] = c opcLdObj, # a = b.c opcLdObjAddr, # a = addr(b.c) opcWrObj, # a.b = c opcAddrReg, opcAddrNode, opcLdDeref, opcWrDeref, opcWrStrIdx, opcLdStrIdx, # a = b[c] opcLdStrIdxAddr, # a = addr(b[c]) opcAddInt, opcAddImmInt, opcSubInt, opcSubImmInt, opcLenSeq, opcLenStr, opcLenCstring, opcIncl, opcInclRange, opcExcl, opcCard, opcMulInt, opcDivInt, opcModInt, opcAddFloat, opcSubFloat, opcMulFloat, opcDivFloat, opcShrInt, opcShlInt, opcAshrInt, opcBitandInt, opcBitorInt, opcBitxorInt, opcAddu, opcSubu, opcMulu, opcDivu, opcModu, opcEqInt, opcLeInt, opcLtInt, opcEqFloat, opcLeFloat, opcLtFloat, opcLeu, opcLtu, opcEqRef, opcEqNimNode, opcSameNodeType, opcXor, opcNot, opcUnaryMinusInt, opcUnaryMinusFloat, opcBitnotInt, opcEqStr, opcLeStr, opcLtStr, opcEqSet, opcLeSet, opcLtSet, opcMulSet, opcPlusSet, opcMinusSet, opcConcatStr, opcContainsSet, opcRepr, opcSetLenStr, opcSetLenSeq, opcIsNil, opcOf, opcIs, opcSubStr, opcParseFloat, opcConv, opcCast, opcQuit, opcInvalidField, opcNarrowS, opcNarrowU, opcSignExtend, opcAddStrCh, opcAddStrStr, opcAddSeqElem, opcRangeChck, opcNAdd, opcNAddMultiple, opcNKind, opcNSymKind, opcNIntVal, opcNFloatVal, opcNSymbol, opcNIdent, opcNGetType, opcNStrVal, opcNSigHash, opcNGetSize, opcNSetIntVal, opcNSetFloatVal, opcNSetSymbol, opcNSetIdent, opcNSetType, opcNSetStrVal, opcNNewNimNode, opcNCopyNimNode, opcNCopyNimTree, opcNDel, opcGenSym, opcNccValue, opcNccInc, opcNcsAdd, opcNcsIncl, opcNcsLen, opcNcsAt, opcNctPut, opcNctLen, opcNctGet, opcNctHasNext, opcNctNext, opcNodeId, opcSlurp, opcGorge, opcParseExprToAst, opcParseStmtToAst, opcQueryErrorFlag, opcNError, opcNWarning, opcNHint, opcNGetLineInfo, opcNSetLineInfo, opcEqIdent, opcStrToIdent, opcGetImpl, opcGetImplTransf opcEcho, opcIndCall, # dest = call regStart, n; where regStart = fn, arg1, ... opcIndCallAsgn, # dest = call regStart, n; where regStart = fn, arg1, ... opcRaise, opcNChild, opcNSetChild, opcCallSite, opcNewStr, opcTJmp, # jump Bx if A != 0 opcFJmp, # jump Bx if A == 0 opcJmp, # jump Bx opcJmpBack, # jump Bx; resulting from a while loop opcBranch, # branch for 'case' opcTry, opcExcept, opcFinally, opcFinallyEnd, opcNew, opcNewSeq, opcLdNull, # dest = nullvalue(types[Bx]) opcLdNullReg, opcLdConst, # dest = constants[Bx] opcAsgnConst, # dest = copy(constants[Bx]) opcLdGlobal, # dest = globals[Bx] opcLdGlobalAddr, # dest = addr(globals[Bx]) opcLdGlobalDerefFFI, # dest = globals[Bx][] opcLdGlobalAddrDerefFFI, # globals[Bx][] = ... opcLdImmInt, # dest = immediate value opcNBindSym, opcNDynBindSym, opcSetType, # dest.typ = types[Bx] opcTypeTrait, opcMarshalLoad, opcMarshalStore, opcSymOwner, opcSymIsInstantiationOf TBlock* = object label*: PSym fixups*: seq[TPosition] TEvalMode* = enum ## reason for evaluation emRepl, ## evaluate because in REPL mode emConst, ## evaluate for 'const' according to spec emOptimize, ## evaluate for optimization purposes (same as ## emConst?) emStaticExpr, ## evaluate for enforced compile time eval ## ('static' context) emStaticStmt ## 'static' as an expression TSandboxFlag* = enum ## what the evaluation engine should allow allowCast, ## allow unsafe language feature: 'cast' allowInfiniteLoops ## allow endless loops TSandboxFlags* = set[TSandboxFlag] TSlotKind* = enum # We try to re-use slots in a smart way to # minimize allocations; however the VM supports arbitrary # temporary slot usage. This is required for the parameter # passing implementation. slotEmpty, # slot is unused slotFixedVar, # slot is used for a fixed var/result (requires copy then) slotFixedLet, # slot is used for a fixed param/let slotTempUnknown, # slot but type unknown (argument of proc call) slotTempInt, # some temporary int slotTempFloat, # some temporary float slotTempStr, # some temporary string slotTempComplex, # some complex temporary (s.node field is used) slotTempPerm # slot is temporary but permanent (hack) TRegisterKind* = enum rkNone, rkNode, rkInt, rkFloat, rkRegisterAddr, rkNodeAddr TFullReg* = object # with a custom mark proc, we could use the same # data representation as LuaJit (tagged NaNs). case kind*: TRegisterKind of rkNone: nil of rkInt: intVal*: BiggestInt of rkFloat: floatVal*: BiggestFloat of rkNode: node*: PNode of rkRegisterAddr: regAddr*: ptr TFullReg of rkNodeAddr: nodeAddr*: ptr PNode PProc* = ref object blocks*: seq[TBlock] # blocks; temp data structure sym*: PSym regInfo*: seq[tuple[inUse: bool, kind: TSlotKind]] VmArgs* = object ra*, rb*, rc*: Natural slots*: ptr UncheckedArray[TFullReg] currentException*: PNode currentLineInfo*: TLineInfo VmCallback* = proc (args: VmArgs) {.closure.} PCtx* = ref TCtx TCtx* = object of TPassContext # code gen context code*: seq[TInstr] debug*: seq[TLineInfo] # line info for every instruction; kept separate # to not slow down interpretation globals*: PNode # constants*: PNode # constant data types*: seq[PType] # some instructions reference types (e.g. 'except') currentExceptionA*, currentExceptionB*: PNode exceptionInstr*: int # index of instruction that raised the exception prc*: PProc module*: PSym callsite*: PNode mode*: TEvalMode features*: TSandboxFlags traceActive*: bool loopIterations*: int comesFromHeuristic*: TLineInfo # Heuristic for better macro stack traces callbacks*: seq[tuple[key: string, value: VmCallback]] errorFlag*: string cache*: IdentCache config*: ConfigRef graph*: ModuleGraph oldErrorCount*: int profiler*: Profiler templInstCounter*: ref int # gives every template instantiation a unique ID, needed here for getAst vmstateDiff*: seq[(PSym, PNode)] # we remember the "diff" to global state here (feature for IC) procToCodePos*: Table[int, int] PStackFrame* = ref TStackFrame TStackFrame* {.acyclic.} = object prc*: PSym # current prc; proc that is evaluated slots*: seq[TFullReg] # parameters passed to the proc + locals; # parameters come first next*: PStackFrame # for stacking comesFrom*: int safePoints*: seq[int] # used for exception handling # XXX 'break' should perform cleanup actions # What does the C backend do for it? Profiler* = object tEnter*: float tos*: PStackFrame TPosition* = distinct int PEvalContext* = PCtx proc newCtx*(module: PSym; cache: IdentCache; g: ModuleGraph; idgen: IdGenerator): PCtx = PCtx(code: @[], debug: @[], globals: newNode(nkStmtListExpr), constants: newNode(nkStmtList), types: @[], prc: PProc(blocks: @[]), module: module, loopIterations: g.config.maxLoopIterationsVM, comesFromHeuristic: unknownLineInfo, callbacks: @[], errorFlag: "", cache: cache, config: g.config, graph: g, idgen: idgen) proc refresh*(c: PCtx, module: PSym; idgen: IdGenerator) = c.module = module c.prc = PProc(blocks: @[]) c.loopIterations = c.config.maxLoopIterationsVM c.idgen = idgen proc registerCallback*(c: PCtx; name: string; callback: VmCallback): int {.discardable.} = result = c.callbacks.len c.callbacks.add((name, callback)) const firstABxInstr* = opcTJmp largeInstrs* = { # instructions which use 2 int32s instead of 1: opcSubStr, opcConv, opcCast, opcNewSeq, opcOf, opcMarshalLoad, opcMarshalStore} slotSomeTemp* = slotTempUnknown relativeJumps* = {opcTJmp, opcFJmp, opcJmp, opcJmpBack} # flag is used to signal opcSeqLen if node is NimNode. const nimNodeFlag* = 16 template opcode*(x: TInstr): TOpcode = TOpcode(x.TInstrType shr regOShift and regOMask) template regA*(x: TInstr): TRegister = TRegister(x.TInstrType shr regAShift and regAMask) template regB*(x: TInstr): TRegister = TRegister(x.TInstrType shr regBShift and regBMask) template regC*(x: TInstr): TRegister = TRegister(x.TInstrType shr regCShift and regCMask) template regBx*(x: TInstr): int = (x.TInstrType shr regBxShift and regBxMask).int template jmpDiff*(x: TInstr): int = regBx(x) - wordExcess