# # # The Nimrod Compiler # (c) Copyright 2014 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. # ## This file implements the new evaluation engine for Nimrod code. ## An instruction is 1-3 int32s in memory, it is a register based VM. const debugEchoCode = false import ast except getstr import strutils, astalgo, msgs, vmdef, vmgen, nimsets, types, passes, unsigned, parser, vmdeps, idents, trees, renderer, options, transf from semfold import leValueConv, ordinalValToString from evaltempl import evalTemplate when hasFFI: import evalffi type 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 PStackFrame* = ref TStackFrame TStackFrame* = 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? proc stackTraceAux(c: PCtx; x: PStackFrame; pc: int; recursionLimit=100) = if x != nil: if recursionLimit == 0: var calls = 0 var x = x while x != nil: inc calls x = x.next msgWriteln($calls & " calls omitted\n") return stackTraceAux(c, x.next, x.comesFrom, recursionLimit-1) var info = c.debug[pc] # we now use the same format as in system/except.nim var s = toFilename(info) var line = toLinenumber(info) if line > 0: add(s, '(') add(s, $line) add(s, ')') if x.prc != nil: for k in 1..max(1, 25-s.len): add(s, ' ') add(s, x.prc.name.s) msgWriteln(s) proc stackTrace(c: PCtx, tos: PStackFrame, pc: int, msg: TMsgKind, arg = "") = msgWriteln("stack trace: (most recent call last)") stackTraceAux(c, tos, pc) # XXX test if we want 'globalError' for every mode if c.mode == emRepl: globalError(c.debug[pc], msg, arg) else: localError(c.debug[pc], msg, arg) proc bailOut(c: PCtx; tos: PStackFrame) = stackTrace(c, tos, c.exceptionInstr, errUnhandledExceptionX, c.currentExceptionA.sons[2].strVal) when not defined(nimComputedGoto): {.pragma: computedGoto.} proc myreset(n: var TFullReg) = when defined(system.reset): reset(n) template ensureKind(k: expr) {.immediate, dirty.} = if regs[ra].kind != k: myreset(regs[ra]) regs[ra].kind = k template decodeB(k: expr) {.immediate, dirty.} = let rb = instr.regB ensureKind(k) template decodeBC(k: expr) {.immediate, dirty.} = let rb = instr.regB let rc = instr.regC ensureKind(k) template declBC() {.immediate, dirty.} = let rb = instr.regB let rc = instr.regC template decodeBImm(k: expr) {.immediate, dirty.} = let rb = instr.regB let imm = instr.regC - byteExcess ensureKind(k) template decodeBx(k: expr) {.immediate, dirty.} = let rbx = instr.regBx - wordExcess ensureKind(k) template move(a, b: expr) {.immediate, dirty.} = system.shallowCopy(a, b) # XXX fix minor 'shallowCopy' overloading bug in compiler proc createStrKeepNode(x: var TFullReg) = if x.node.isNil: x.node = newNode(nkStrLit) elif x.node.kind == nkNilLit: system.reset(x.node[]) x.node.kind = nkStrLit elif x.node.kind notin {nkStrLit..nkTripleStrLit}: # XXX this is hacky; tests/txmlgen triggers it: x.node = newNode(nkStrLit) # debug x.node #assert x.node.kind in {nkStrLit..nkTripleStrLit} template createStr(x) = x.node = newNode(nkStrLit) proc moveConst(x: var TFullReg, y: TFullReg) = if x.kind != y.kind: myreset(x) x.kind = y.kind case x.kind of rkNone: discard of rkInt: x.intVal = y.intVal of rkFloat: x.floatVal = y.floatVal of rkNode: x.node = y.node of rkRegisterAddr: x.regAddr = y.regAddr of rkNodeAddr: x.nodeAddr = y.nodeAddr # this seems to be the best way to model the reference semantics # of PNimrodNode: template asgnRef(x, y: expr) = moveConst(x, y) proc copyValue(src: PNode): PNode = if src == nil or nfIsRef in src.flags: return src result = newNode(src.kind) result.info = src.info result.typ = src.typ result.flags = src.flags * PersistentNodeFlags when defined(useNodeIds): if result.id == nodeIdToDebug: echo "COMES FROM ", src.id case src.kind of nkCharLit..nkUInt64Lit: result.intVal = src.intVal of nkFloatLit..nkFloat128Lit: result.floatVal = src.floatVal of nkSym: result.sym = src.sym of nkIdent: result.ident = src.ident of nkStrLit..nkTripleStrLit: result.strVal = src.strVal else: newSeq(result.sons, sonsLen(src)) for i in countup(0, sonsLen(src) - 1): result.sons[i] = copyValue(src.sons[i]) proc asgnComplex(x: var TFullReg, y: TFullReg) = if x.kind != y.kind: myreset(x) x.kind = y.kind case x.kind of rkNone: discard of rkInt: x.intVal = y.intVal of rkFloat: x.floatVal = y.floatVal of rkNode: x.node = copyValue(y.node) of rkRegisterAddr: x.regAddr = y.regAddr of rkNodeAddr: x.nodeAddr = y.nodeAddr proc putIntoNode(n: var PNode; x: TFullReg) = case x.kind of rkNone: discard of rkInt: n.intVal = x.intVal of rkFloat: n.floatVal = x.floatVal of rkNode: if nfIsRef in x.node.flags: n = x.node else: n[] = x.node[] of rkRegisterAddr: putIntoNode(n, x.regAddr[]) of rkNodeAddr: n[] = x.nodeAddr[][] proc putIntoReg(dest: var TFullReg; n: PNode) = case n.kind of nkStrLit..nkTripleStrLit: dest.kind = rkNode createStr(dest) dest.node.strVal = n.strVal of nkCharLit..nkUInt64Lit: dest.kind = rkInt dest.intVal = n.intVal of nkFloatLit..nkFloat128Lit: dest.kind = rkFloat dest.floatVal = n.floatVal else: dest.kind = rkNode dest.node = n proc regToNode(x: TFullReg): PNode = case x.kind of rkNone: result = newNode(nkEmpty) of rkInt: result = newNode(nkIntLit); result.intVal = x.intVal of rkFloat: result = newNode(nkFloatLit); result.floatVal = x.floatVal of rkNode: result = x.node of rkRegisterAddr: result = regToNode(x.regAddr[]) of rkNodeAddr: result = x.nodeAddr[] template getstr(a: expr): expr = (if a.kind == rkNode: a.node.strVal else: $chr(int(a.intVal))) proc pushSafePoint(f: PStackFrame; pc: int) = if f.safePoints.isNil: f.safePoints = @[] f.safePoints.add(pc) proc popSafePoint(f: PStackFrame) = discard f.safePoints.pop() proc cleanUpOnException(c: PCtx; tos: PStackFrame): tuple[pc: int, f: PStackFrame] = let raisedType = c.currentExceptionA.typ.skipTypes(abstractPtrs) var f = tos while true: while f.safePoints.isNil or f.safePoints.len == 0: f = f.next if f.isNil: return (-1, nil) var pc2 = f.safePoints[f.safePoints.high] var nextExceptOrFinally = -1 if c.code[pc2].opcode == opcExcept: nextExceptOrFinally = pc2 + c.code[pc2].regBx - wordExcess inc pc2 while c.code[pc2].opcode == opcExcept: let exceptType = c.types[c.code[pc2].regBx-wordExcess].skipTypes( abstractPtrs) if inheritanceDiff(exceptType, raisedType) <= 0: # mark exception as handled but keep it in B for # the getCurrentException() builtin: c.currentExceptionB = c.currentExceptionA c.currentExceptionA = nil # execute the corresponding handler: return (pc2, f) inc pc2 if nextExceptOrFinally >= 0: pc2 = nextExceptOrFinally if c.code[pc2].opcode == opcFinally: # execute the corresponding handler, but don't quit walking the stack: return (pc2, f) # not the right one: discard f.safePoints.pop proc cleanUpOnReturn(c: PCtx; f: PStackFrame): int = if f.safePoints.isNil: return -1 for s in f.safePoints: var pc = s while c.code[pc].opcode == opcExcept: pc = pc + c.code[pc].regBx - wordExcess if c.code[pc].opcode == opcFinally: return pc return -1 proc opConv*(dest: var TFullReg, src: TFullReg, desttyp, srctyp: PType): bool = if desttyp.kind == tyString: if dest.kind != rkNode: myreset(dest) dest.kind = rkNode dest.node = newNode(nkStrLit) let styp = srctyp.skipTypes(abstractRange) case styp.kind of tyEnum: let n = styp.n let x = src.intVal.int if x <% n.len and (let f = n.sons[x].sym; f.position == x): dest.node.strVal = if f.ast.isNil: f.name.s else: f.ast.strVal else: for i in 0.. lastOrd(desttyp): return true of tyUInt..tyUInt64: if dest.kind != rkInt: myreset(dest); dest.kind = rkInt case skipTypes(srctyp, abstractRange).kind of tyFloat..tyFloat64: dest.intVal = system.toInt(src.floatVal) else: dest.intVal = src.intVal and ((1 shl (desttyp.size*8))-1) of tyFloat..tyFloat64: if dest.kind != rkFloat: myreset(dest); dest.kind = rkFloat case skipTypes(srctyp, abstractRange).kind of tyInt..tyInt64, tyUInt..tyUInt64, tyEnum, tyBool, tyChar: dest.floatVal = toFloat(src.intVal.int) else: dest.floatVal = src.floatVal else: asgnComplex(dest, src) proc compile(c: PCtx, s: PSym): int = result = vmgen.genProc(c, s) when debugEchoCode: c.echoCode result #c.echoCode template handleJmpBack() {.dirty.} = if c.loopIterations <= 0: if allowInfiniteLoops in c.features: c.loopIterations = MaxLoopIterations else: msgWriteln("stack trace: (most recent call last)") stackTraceAux(c, tos, pc) globalError(c.debug[pc], errTooManyIterations) dec(c.loopIterations) proc skipColon(n: PNode): PNode = result = n if n.kind == nkExprColonExpr: result = n.sons[1] proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = var pc = start var tos = tos var regs: seq[TFullReg] # alias to tos.slots for performance move(regs, tos.slots) #echo "NEW RUN ------------------------" while true: #{.computedGoto.} let instr = c.code[pc] let ra = instr.regA #if c.traceActive: # echo "PC ", pc, " ", c.code[pc].opcode, " ra ", ra # message(c.debug[pc], warnUser, "Trace") case instr.opcode of opcEof: return regs[ra] of opcRet: # XXX perform any cleanup actions pc = tos.comesFrom tos = tos.next let retVal = regs[0] if tos.isNil: #echo "RET ", retVal.rendertree return retVal move(regs, tos.slots) assert c.code[pc].opcode in {opcIndCall, opcIndCallAsgn} if c.code[pc].opcode == opcIndCallAsgn: regs[c.code[pc].regA] = retVal #echo "RET2 ", retVal.rendertree, " ", c.code[pc].regA of opcYldYoid: assert false of opcYldVal: assert false of opcAsgnInt: decodeB(rkInt) regs[ra].intVal = regs[rb].intVal of opcAsgnStr: decodeB(rkNode) createStrKeepNode regs[ra] regs[ra].node.strVal = regs[rb].node.strVal of opcAsgnFloat: decodeB(rkFloat) regs[ra].floatVal = regs[rb].floatVal of opcAsgnComplex: asgnComplex(regs[ra], regs[instr.regB]) of opcAsgnRef: asgnRef(regs[ra], regs[instr.regB]) of opcRegToNode: decodeB(rkNode) putIntoNode(regs[ra].node, regs[rb]) of opcNodeToReg: let ra = instr.regA let rb = instr.regB # opcDeref might already have loaded it into a register. XXX Let's hope # this is still correct this way: if regs[rb].kind != rkNode: regs[ra] = regs[rb] else: assert regs[rb].kind == rkNode let nb = regs[rb].node case nb.kind of nkCharLit..nkInt64Lit: ensureKind(rkInt) regs[ra].intVal = nb.intVal of nkFloatLit..nkFloat64Lit: ensureKind(rkFloat) regs[ra].floatVal = nb.floatVal else: ensureKind(rkNode) regs[ra].node = nb of opcLdArr: # a = b[c] decodeBC(rkNode) if regs[rc].intVal > high(int): stackTrace(c, tos, pc, errIndexOutOfBounds) let idx = regs[rc].intVal.int # XXX what if the array is not 0-based? -> codegen should insert a sub let src = regs[rb].node if src.kind notin {nkEmpty..nkNilLit} and idx <% src.len: regs[ra].node = src.sons[idx] else: stackTrace(c, tos, pc, errIndexOutOfBounds) of opcLdStrIdx: decodeBC(rkInt) let idx = regs[rc].intVal.int if idx <=% regs[rb].node.strVal.len: regs[ra].intVal = regs[rb].node.strVal[idx].ord else: stackTrace(c, tos, pc, errIndexOutOfBounds) of opcWrArr: # a[b] = c decodeBC(rkNode) let idx = regs[rb].intVal.int if idx <% regs[ra].node.len: putIntoNode(regs[ra].node.sons[idx], regs[rc]) else: stackTrace(c, tos, pc, errIndexOutOfBounds) of opcLdObj: # a = b.c decodeBC(rkNode) let src = regs[rb].node if src.kind notin {nkEmpty..nkNilLit}: let n = src.sons[rc].skipColon regs[ra].node = n else: stackTrace(c, tos, pc, errNilAccess) of opcWrObj: # a.b = c decodeBC(rkNode) putIntoNode(regs[ra].node.sons[rb], regs[rc]) of opcWrStrIdx: decodeBC(rkNode) let idx = regs[rb].intVal.int if idx <% regs[ra].node.strVal.len: regs[ra].node.strVal[idx] = chr(regs[rc].intVal) else: stackTrace(c, tos, pc, errIndexOutOfBounds) of opcAddrReg: decodeB(rkRegisterAddr) regs[ra].regAddr = addr(regs[rb]) of opcAddrNode: decodeB(rkNodeAddr) regs[ra].nodeAddr = addr(regs[rb].node) of opcLdDeref: # a = b[] let ra = instr.regA let rb = instr.regB case regs[rb].kind of rkNodeAddr: ensureKind(rkNode) regs[ra].node = regs[rb].nodeAddr[] of rkRegisterAddr: ensureKind(regs[rb].regAddr.kind) regs[ra] = regs[rb].regAddr[] of rkNode: if regs[rb].node.kind == nkNilLit: stackTrace(c, tos, pc, errNilAccess) assert regs[rb].node.kind == nkRefTy regs[ra].node = regs[rb].node.sons[0] else: stackTrace(c, tos, pc, errNilAccess) of opcWrDeref: # a[] = b let ra = instr.regA let rb = instr.regB case regs[ra].kind of rkNodeAddr: putIntoNode(regs[ra].nodeAddr[], regs[rb]) of rkRegisterAddr: regs[ra].regAddr[] = regs[rb] of rkNode: putIntoNode(regs[ra].node, regs[rb]) else: stackTrace(c, tos, pc, errNilAccess) of opcAddInt: decodeBC(rkInt) let bVal = regs[rb].intVal cVal = regs[rc].intVal sum = bVal +% cVal if (sum xor bVal) >= 0 or (sum xor cVal) >= 0: regs[ra].intVal = sum else: stackTrace(c, tos, pc, errOverOrUnderflow) of opcAddImmInt: decodeBImm(rkInt) #message(c.debug[pc], warnUser, "came here") #debug regs[rb].node let bVal = regs[rb].intVal cVal = imm sum = bVal +% cVal if (sum xor bVal) >= 0 or (sum xor cVal) >= 0: regs[ra].intVal = sum else: stackTrace(c, tos, pc, errOverOrUnderflow) of opcSubInt: decodeBC(rkInt) let bVal = regs[rb].intVal cVal = regs[rc].intVal diff = bVal -% cVal if (diff xor bVal) >= 0 or (diff xor not cVal) >= 0: regs[ra].intVal = diff else: stackTrace(c, tos, pc, errOverOrUnderflow) of opcSubImmInt: decodeBImm(rkInt) let bVal = regs[rb].intVal cVal = imm diff = bVal -% cVal if (diff xor bVal) >= 0 or (diff xor not cVal) >= 0: regs[ra].intVal = diff else: stackTrace(c, tos, pc, errOverOrUnderflow) of opcLenSeq: decodeBImm(rkInt) #assert regs[rb].kind == nkBracket # also used by mNLen: regs[ra].intVal = regs[rb].node.safeLen - imm of opcLenStr: decodeBImm(rkInt) assert regs[rb].kind == rkNode regs[ra].intVal = regs[rb].node.strVal.len - imm of opcIncl: decodeB(rkNode) let b = regs[rb].regToNode if not inSet(regs[ra].node, b): addSon(regs[ra].node, copyTree(b)) of opcInclRange: decodeBC(rkNode) var r = newNode(nkRange) r.add regs[rb].regToNode r.add regs[rc].regToNode addSon(regs[ra].node, r.copyTree) of opcExcl: decodeB(rkNode) var b = newNodeIT(nkCurly, regs[rb].node.info, regs[rb].node.typ) addSon(b, regs[rb].regToNode) var r = diffSets(regs[ra].node, b) discardSons(regs[ra].node) for i in countup(0, sonsLen(r) - 1): addSon(regs[ra].node, r.sons[i]) of opcCard: decodeB(rkInt) regs[ra].intVal = nimsets.cardSet(regs[rb].node) of opcMulInt: decodeBC(rkInt) let bVal = regs[rb].intVal cVal = regs[rc].intVal product = bVal *% cVal floatProd = toBiggestFloat(bVal) * toBiggestFloat(cVal) resAsFloat = toBiggestFloat(product) if resAsFloat == floatProd: regs[ra].intVal = product elif 32.0 * abs(resAsFloat - floatProd) <= abs(floatProd): regs[ra].intVal = product else: stackTrace(c, tos, pc, errOverOrUnderflow) of opcDivInt: decodeBC(rkInt) if regs[rc].intVal == 0: stackTrace(c, tos, pc, errConstantDivisionByZero) else: regs[ra].intVal = regs[rb].intVal div regs[rc].intVal of opcModInt: decodeBC(rkInt) if regs[rc].intVal == 0: stackTrace(c, tos, pc, errConstantDivisionByZero) else: regs[ra].intVal = regs[rb].intVal mod regs[rc].intVal of opcAddFloat: decodeBC(rkFloat) regs[ra].floatVal = regs[rb].floatVal + regs[rc].floatVal of opcSubFloat: decodeBC(rkFloat) regs[ra].floatVal = regs[rb].floatVal - regs[rc].floatVal of opcMulFloat: decodeBC(rkFloat) regs[ra].floatVal = regs[rb].floatVal * regs[rc].floatVal of opcDivFloat: decodeBC(rkFloat) regs[ra].floatVal = regs[rb].floatVal / regs[rc].floatVal of opcShrInt: decodeBC(rkInt) regs[ra].intVal = regs[rb].intVal shr regs[rc].intVal of opcShlInt: decodeBC(rkInt) regs[ra].intVal = regs[rb].intVal shl regs[rc].intVal of opcBitandInt: decodeBC(rkInt) regs[ra].intVal = regs[rb].intVal and regs[rc].intVal of opcBitorInt: decodeBC(rkInt) regs[ra].intVal = regs[rb].intVal or regs[rc].intVal of opcBitxorInt: decodeBC(rkInt) regs[ra].intVal = regs[rb].intVal xor regs[rc].intVal of opcAddu: decodeBC(rkInt) regs[ra].intVal = regs[rb].intVal +% regs[rc].intVal of opcSubu: decodeBC(rkInt) regs[ra].intVal = regs[rb].intVal -% regs[rc].intVal of opcMulu: decodeBC(rkInt) regs[ra].intVal = regs[rb].intVal *% regs[rc].intVal of opcDivu: decodeBC(rkInt) regs[ra].intVal = regs[rb].intVal /% regs[rc].intVal of opcModu: decodeBC(rkInt) regs[ra].intVal = regs[rb].intVal %% regs[rc].intVal of opcEqInt: decodeBC(rkInt) regs[ra].intVal = ord(regs[rb].intVal == regs[rc].intVal) of opcLeInt: decodeBC(rkInt) regs[ra].intVal = ord(regs[rb].intVal <= regs[rc].intVal) of opcLtInt: decodeBC(rkInt) regs[ra].intVal = ord(regs[rb].intVal < regs[rc].intVal) of opcEqFloat: decodeBC(rkInt) regs[ra].intVal = ord(regs[rb].floatVal == regs[rc].floatVal) of opcLeFloat: decodeBC(rkInt) regs[ra].intVal = ord(regs[rb].floatVal <= regs[rc].floatVal) of opcLtFloat: decodeBC(rkInt) regs[ra].intVal = ord(regs[rb].floatVal < regs[rc].floatVal) of opcLeu: decodeBC(rkInt) regs[ra].intVal = ord(regs[rb].intVal <=% regs[rc].intVal) of opcLtu: decodeBC(rkInt) regs[ra].intVal = ord(regs[rb].intVal <% regs[rc].intVal) of opcEqRef: decodeBC(rkInt) regs[ra].intVal = ord((regs[rb].node.kind == nkNilLit and regs[rc].node.kind == nkNilLit) or regs[rb].node == regs[rc].node) of opcEqNimrodNode: decodeBC(rkInt) regs[ra].intVal = ord(regs[rb].node == regs[rc].node) of opcXor: decodeBC(rkInt) regs[ra].intVal = ord(regs[rb].intVal != regs[rc].intVal) of opcNot: decodeB(rkInt) assert regs[rb].kind == rkInt regs[ra].intVal = 1 - regs[rb].intVal of opcUnaryMinusInt: decodeB(rkInt) assert regs[rb].kind == rkInt let val = regs[rb].intVal if val != int64.low: regs[ra].intVal = -val else: stackTrace(c, tos, pc, errOverOrUnderflow) of opcUnaryMinusFloat: decodeB(rkFloat) assert regs[rb].kind == rkFloat regs[ra].floatVal = -regs[rb].floatVal of opcBitnotInt: decodeB(rkInt) assert regs[rb].kind == rkInt regs[ra].intVal = not regs[rb].intVal of opcEqStr: decodeBC(rkInt) regs[ra].intVal = ord(regs[rb].node.strVal == regs[rc].node.strVal) of opcLeStr: decodeBC(rkInt) regs[ra].intVal = ord(regs[rb].node.strVal <= regs[rc].node.strVal) of opcLtStr: decodeBC(rkInt) regs[ra].intVal = ord(regs[rb].node.strVal < regs[rc].node.strVal) of opcLeSet: decodeBC(rkInt) regs[ra].intVal = ord(containsSets(regs[rb].node, regs[rc].node)) of opcEqSet: decodeBC(rkInt) regs[ra].intVal = ord(equalSets(regs[rb].node, regs[rc].node)) of opcLtSet: decodeBC(rkInt) let a = regs[rb].node let b = regs[rc].node regs[ra].intVal = ord(containsSets(a, b) and not equalSets(a, b)) of opcMulSet: decodeBC(rkNode) move(regs[ra].node.sons, nimsets.intersectSets(regs[rb].node, regs[rc].node).sons) of opcPlusSet: decodeBC(rkNode) move(regs[ra].node.sons, nimsets.unionSets(regs[rb].node, regs[rc].node).sons) of opcMinusSet: decodeBC(rkNode) move(regs[ra].node.sons, nimsets.diffSets(regs[rb].node, regs[rc].node).sons) of opcSymdiffSet: decodeBC(rkNode) move(regs[ra].node.sons, nimsets.symdiffSets(regs[rb].node, regs[rc].node).sons) of opcConcatStr: decodeBC(rkNode) createStr regs[ra] regs[ra].node.strVal = getstr(regs[rb]) for i in rb+1..rb+rc-1: regs[ra].node.strVal.add getstr(regs[i]) of opcAddStrCh: decodeB(rkNode) createStrKeepNode regs[ra] regs[ra].node.strVal.add(regs[rb].intVal.chr) of opcAddStrStr: decodeB(rkNode) createStrKeepNode regs[ra] regs[ra].node.strVal.add(regs[rb].node.strVal) of opcAddSeqElem: decodeB(rkNode) if regs[ra].node.kind == nkBracket: regs[ra].node.add(copyTree(regs[rb].regToNode)) else: stackTrace(c, tos, pc, errNilAccess) of opcEcho: let rb = instr.regB for i in ra..ra+rb-1: #if regs[i].kind != rkNode: debug regs[i] write(stdout, regs[i].node.strVal) writeln(stdout, "") of opcContainsSet: decodeBC(rkInt) regs[ra].intVal = ord(inSet(regs[rb].node, regs[rc].regToNode)) of opcSubStr: decodeBC(rkNode) inc pc assert c.code[pc].opcode == opcSubStr let rd = c.code[pc].regA createStr regs[ra] regs[ra].node.strVal = substr(regs[rb].node.strVal, regs[rc].intVal.int, regs[rd].intVal.int) of opcRangeChck: let rb = instr.regB let rc = instr.regC if not (leValueConv(regs[rb].regToNode, regs[ra].regToNode) and leValueConv(regs[ra].regToNode, regs[rc].regToNode)): stackTrace(c, tos, pc, errGenerated, msgKindToString(errIllegalConvFromXtoY) % [ "unknown type" , "unknown type"]) of opcIndCall, opcIndCallAsgn: # dest = call regStart, n; where regStart = fn, arg1, ... let rb = instr.regB let rc = instr.regC let bb = regs[rb].node let isClosure = bb.kind == nkPar let prc = if not isClosure: bb.sym else: bb.sons[0].sym if sfImportc in prc.flags: if allowFFI notin c.features: globalError(c.debug[pc], errGenerated, "VM not allowed to do FFI") # we pass 'tos.slots' instead of 'regs' so that the compiler can keep # 'regs' in a register: when hasFFI: let prcValue = c.globals.sons[prc.position-1] if prcValue.kind == nkEmpty: globalError(c.debug[pc], errGenerated, "canot run " & prc.name.s) let newValue = callForeignFunction(prcValue, prc.typ, tos.slots, rb+1, rc-1, c.debug[pc]) if newValue.kind != nkEmpty: assert instr.opcode == opcIndCallAsgn putIntoReg(regs[ra], newValue) else: globalError(c.debug[pc], errGenerated, "VM not built with FFI support") elif prc.kind != skTemplate: let newPc = compile(c, prc) # tricky: a recursion is also a jump back, so we use the same # logic as for loops: if newPc < pc: handleJmpBack() #echo "new pc ", newPc, " calling: ", prc.name.s var newFrame = PStackFrame(prc: prc, comesFrom: pc, next: tos) newSeq(newFrame.slots, prc.offset) if not isEmptyType(prc.typ.sons[0]) or prc.kind == skMacro: putIntoReg(newFrame.slots[0], getNullValue(prc.typ.sons[0], prc.info)) for i in 1 .. rc-1: newFrame.slots[i] = regs[rb+i] if isClosure: newFrame.slots[rc].kind = rkNode newFrame.slots[rc].node = regs[rb].node.sons[1] # allocate the temporaries: #for i in rc+ord(isClosure) .. The codegen has to deal with it # via 'genAsgnPatch'. of opcLdNullReg: let typ = c.types[instr.regBx - wordExcess] if typ.skipTypes(abstractInst+{tyRange}-{tyTypeDesc}).kind in { tyFloat..tyFloat128}: ensureKind(rkFloat) regs[ra].floatVal = 0.0 else: ensureKind(rkInt) regs[ra].intVal = 0 of opcLdConst: let rb = instr.regBx - wordExcess let cnst = c.constants.sons[rb] if fitsRegister(cnst.typ): putIntoReg(regs[ra], cnst) else: ensureKind(rkNode) regs[ra].node = cnst of opcAsgnConst: let rb = instr.regBx - wordExcess let cnst = c.constants.sons[rb] if fitsRegister(cnst.typ): putIntoReg(regs[ra], cnst) else: ensureKind(rkNode) regs[ra].node = cnst.copyTree of opcLdGlobal: let rb = instr.regBx - wordExcess - 1 ensureKind(rkNode) regs[ra].node = c.globals.sons[rb] of opcLdGlobalAddr: let rb = instr.regBx - wordExcess - 1 ensureKind(rkNodeAddr) regs[ra].nodeAddr = addr(c.globals.sons[rb]) of opcRepr: decodeB(rkNode) createStr regs[ra] regs[ra].node.strVal = renderTree(regs[rb].regToNode, {renderNoComments}) of opcQuit: if c.mode in {emRepl, emStaticExpr, emStaticStmt}: message(c.debug[pc], hintQuitCalled) quit(int(getOrdValue(regs[ra].regToNode))) else: return TFullReg(kind: rkNone) of opcSetLenStr: decodeB(rkNode) createStrKeepNode regs[ra] regs[ra].node.strVal.setLen(regs[rb].intVal.int) of opcOf: decodeBC(rkInt) let typ = c.types[regs[rc].intVal.int] regs[ra].intVal = ord(inheritanceDiff(regs[rb].node.typ, typ) >= 0) of opcIs: decodeBC(rkInt) let t1 = regs[rb].node.typ.skipTypes({tyTypeDesc}) let t2 = c.types[regs[rc].intVal.int] # XXX: This should use the standard isOpImpl let match = if t2.kind == tyUserTypeClass: true else: sameType(t1, t2) regs[ra].intVal = ord(match) of opcSetLenSeq: decodeB(rkNode) let newLen = regs[rb].intVal.int if regs[ra].node.isNil: stackTrace(c, tos, pc, errNilAccess) else: setLen(regs[ra].node.sons, newLen) of opcSwap: let rb = instr.regB if regs[ra].kind == regs[rb].kind: case regs[ra].kind of rkNone: discard of rkInt: swap regs[ra].intVal, regs[rb].intVal of rkFloat: swap regs[ra].floatVal, regs[rb].floatVal of rkNode: swap regs[ra].node, regs[rb].node of rkRegisterAddr: swap regs[ra].regAddr, regs[rb].regAddr of rkNodeAddr: swap regs[ra].nodeAddr, regs[rb].nodeAddr else: internalError(c.debug[pc], "cannot swap operands") of opcReset: internalError(c.debug[pc], "too implement") of opcNarrowS: decodeB(rkInt) let min = -(1 shl (rb-1)) let max = (1 shl (rb-1))-1 if regs[ra].intVal < min or regs[ra].intVal > max: stackTrace(c, tos, pc, errGenerated, msgKindToString(errUnhandledExceptionX) % "value out of range") of opcNarrowU: decodeB(rkInt) regs[ra].intVal = regs[ra].intVal and ((1'i64 shl rb)-1) of opcIsNil: decodeB(rkInt) regs[ra].intVal = ord(regs[rb].node.kind == nkNilLit) of opcNBindSym: decodeBx(rkNode) regs[ra].node = copyTree(c.constants.sons[rbx]) of opcNChild: decodeBC(rkNode) let idx = regs[rc].intVal.int let src = regs[rb].node if src.kind notin {nkEmpty..nkNilLit} and idx <% src.len: regs[ra].node = src.sons[idx] else: stackTrace(c, tos, pc, errIndexOutOfBounds) of opcNSetChild: decodeBC(rkNode) let idx = regs[rb].intVal.int var dest = regs[ra].node if dest.kind notin {nkEmpty..nkNilLit} and idx <% dest.len: dest.sons[idx] = regs[rc].node else: stackTrace(c, tos, pc, errIndexOutOfBounds) of opcNAdd: decodeBC(rkNode) var u = regs[rb].node u.add(regs[rc].node) regs[ra].node = u of opcNAddMultiple: decodeBC(rkNode) let x = regs[rc].node var u = regs[rb].node # XXX can be optimized: for i in 0.. ord(high(TNodeKind)): internalError(c.debug[pc], "request to create a NimNode of invalid kind") let cc = regs[rc].node regs[ra].node = newNodeI(TNodeKind(int(k)), if cc.kind == nkNilLit: c.debug[pc] else: cc.info) regs[ra].node.flags.incl nfIsRef of opcNCopyNimNode: decodeB(rkNode) regs[ra].node = copyNode(regs[rb].node) of opcNCopyNimTree: decodeB(rkNode) regs[ra].node = copyTree(regs[rb].node) of opcNDel: decodeBC(rkNode) let bb = regs[rb].intVal.int for i in countup(0, regs[rc].intVal.int-1): delSon(regs[ra].node, bb) of opcGenSym: decodeBC(rkNode) let k = regs[rb].intVal let name = if regs[rc].node.strVal.len == 0: ":tmp" else: regs[rc].node.strVal if k < 0 or k > ord(high(TSymKind)): internalError(c.debug[pc], "request to create symbol of invalid kind") var sym = newSym(k.TSymKind, name.getIdent, c.module, c.debug[pc]) incl(sym.flags, sfGenSym) regs[ra].node = newSymNode(sym) of opcTypeTrait: # XXX only supports 'name' for now; we can use regC to encode the # type trait operation decodeB(rkNode) var typ = regs[rb].node.typ internalAssert typ != nil while typ.kind == tyTypeDesc and typ.len > 0: typ = typ.sons[0] createStr regs[ra] regs[ra].node.strVal = typ.typeToString(preferExported) inc pc proc execute(c: PCtx, start: int): PNode = var tos = PStackFrame(prc: nil, comesFrom: 0, next: nil) newSeq(tos.slots, c.prc.maxSlots) result = rawExecute(c, start, tos).regToNode proc evalStmt*(c: PCtx, n: PNode) = let n = transformExpr(c.module, n) let start = genStmt(c, n) # execute new instructions; this redundant opcEof check saves us lots # of allocations in 'execute': if c.code[start].opcode != opcEof: discard execute(c, start) proc evalExpr*(c: PCtx, n: PNode): PNode = let n = transformExpr(c.module, n) let start = genExpr(c, n) assert c.code[start].opcode != opcEof result = execute(c, start) # for now we share the 'globals' environment. XXX Coming soon: An API for # storing&loading the 'globals' environment to get what a component system # requires. var globalCtx: PCtx proc setupGlobalCtx(module: PSym) = if globalCtx.isNil: globalCtx = newCtx(module) else: refresh(globalCtx, module) proc myOpen(module: PSym): PPassContext = #var c = newEvalContext(module, emRepl) #c.features = {allowCast, allowFFI, allowInfiniteLoops} #pushStackFrame(c, newStackFrame()) # XXX produce a new 'globals' environment here: setupGlobalCtx(module) result = globalCtx when hasFFI: globalCtx.features = {allowFFI, allowCast} var oldErrorCount: int proc myProcess(c: PPassContext, n: PNode): PNode = # don't eval errornous code: if oldErrorCount == msgs.gErrorCounter: evalStmt(PCtx(c), n) result = emptyNode else: result = n oldErrorCount = msgs.gErrorCounter const evalPass* = makePass(myOpen, nil, myProcess, myProcess) proc evalConstExprAux(module, prc: PSym, n: PNode, mode: TEvalMode): PNode = let n = transformExpr(module, n) setupGlobalCtx(module) var c = globalCtx c.mode = mode let start = genExpr(c, n, requiresValue = mode!=emStaticStmt) if c.code[start].opcode == opcEof: return emptyNode assert c.code[start].opcode != opcEof when debugEchoCode: c.echoCode start var tos = PStackFrame(prc: prc, comesFrom: 0, next: nil) newSeq(tos.slots, c.prc.maxSlots) #for i in 0 .. 100: globalError(n.info, errTemplateInstantiationTooNested) setupGlobalCtx(module) var c = globalCtx c.callsite = nOrig let start = genProc(c, sym) var tos = PStackFrame(prc: sym, comesFrom: 0, next: nil) let maxSlots = sym.offset newSeq(tos.slots, maxSlots) # setup arguments: var L = n.safeLen if L == 0: L = 1 # This is wrong for tests/reject/tind1.nim where the passed 'else' part # doesn't end up in the parameter: #InternalAssert tos.slots.len >= L # return value: tos.slots[0].kind = rkNode tos.slots[0].node = newNodeIT(nkEmpty, n.info, sym.typ.sons[0]) # setup parameters: for i in 1 .. < min(tos.slots.len, L): tos.slots[i].kind = rkNode tos.slots[i].node = setupMacroParam(n.sons[i]) # temporary storage: #for i in L ..