# # # The Nim Compiler # (c) Copyright 2015 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. # ## This file implements the new evaluation engine for Nim code. ## An instruction is 1-3 int32s in memory, it is a register based VM. import std/[strutils, tables, parseutils], msgs, vmdef, vmgen, nimsets, types, passes, parser, vmdeps, idents, trees, renderer, options, transf, vmmarshal, gorgeimpl, lineinfos, btrees, macrocacheimpl, modulegraphs, sighashes, int128, vmprofiler import ast except getstr from semfold import leValueConv, ordinalValToString from evaltempl import evalTemplate from magicsys import getSysType const traceCode = defined(nimVMDebug) when hasFFI: import evalffi 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(c.config, $calls & " calls omitted\n", {msgNoUnitSep}) return stackTraceAux(c, x.next, x.comesFrom, recursionLimit-1) var info = c.debug[pc] # we now use a format similar to the one in lib/system/excpt.nim var s = "" # todo: factor with quotedFilename if optExcessiveStackTrace in c.config.globalOptions: s = toFullPath(c.config, info) else: s = toFilename(c.config, info) var line = toLinenumber(info) var col = toColumn(info) if line > 0: s.add('(') s.add($line) s.add(", ") s.add($(col + ColOffset)) s.add(')') if x.prc != nil: for k in 1..max(1, 25-s.len): s.add(' ') s.add(x.prc.name.s) msgWriteln(c.config, s, {msgNoUnitSep}) proc stackTraceImpl(c: PCtx, tos: PStackFrame, pc: int, msg: string, lineInfo: TLineInfo, infoOrigin: InstantiationInfo) {.noinline.} = # noinline to avoid code bloat msgWriteln(c.config, "stack trace: (most recent call last)", {msgNoUnitSep}) stackTraceAux(c, tos, pc) let action = if c.mode == emRepl: doRaise else: doNothing # XXX test if we want 'globalError' for every mode let lineInfo = if lineInfo == TLineInfo.default: c.debug[pc] else: lineInfo liMessage(c.config, lineInfo, errGenerated, msg, action, infoOrigin) template stackTrace(c: PCtx, tos: PStackFrame, pc: int, msg: string, lineInfo: TLineInfo = TLineInfo.default) = stackTraceImpl(c, tos, pc, msg, lineInfo, instantiationInfo(-2, fullPaths = true)) return proc bailOut(c: PCtx; tos: PStackFrame) = stackTrace(c, tos, c.exceptionInstr, "unhandled exception: " & c.currentExceptionA[3].skipColon.strVal & " [" & c.currentExceptionA[2].skipColon.strVal & "]") when not defined(nimComputedGoto): {.pragma: computedGoto.} proc ensureKind(n: var TFullReg, kind: TRegisterKind) = if n.kind != kind: n = TFullReg(kind: kind) template ensureKind(k: untyped) {.dirty.} = ensureKind(regs[ra], k) template decodeB(k: untyped) {.dirty.} = let rb = instr.regB ensureKind(k) template decodeBC(k: untyped) {.dirty.} = let rb = instr.regB let rc = instr.regC ensureKind(k) template declBC() {.dirty.} = let rb = instr.regB let rc = instr.regC template decodeBImm(k: untyped) {.dirty.} = let rb = instr.regB let imm = instr.regC - byteExcess ensureKind(k) template decodeBx(k: untyped) {.dirty.} = let rbx = instr.regBx - wordExcess ensureKind(k) template move(a, b: untyped) {.dirty.} = system.shallowCopy(a, b) # XXX fix minor 'shallowCopy' overloading bug in compiler proc derefPtrToReg(address: BiggestInt, typ: PType, r: var TFullReg, isAssign: bool): bool = # nim bug: `isAssign: static bool` doesn't work, giving odd compiler error template fun(field, T, rkind) = if isAssign: cast[ptr T](address)[] = T(r.field) else: r.ensureKind(rkind) let val = cast[ptr T](address)[] when T is SomeInteger | char: r.field = BiggestInt(val) else: r.field = val return true ## see also typeinfo.getBiggestInt case typ.kind of tyChar: fun(intVal, char, rkInt) of tyInt: fun(intVal, int, rkInt) of tyInt8: fun(intVal, int8, rkInt) of tyInt16: fun(intVal, int16, rkInt) of tyInt32: fun(intVal, int32, rkInt) of tyInt64: fun(intVal, int64, rkInt) of tyUInt: fun(intVal, uint, rkInt) of tyUInt8: fun(intVal, uint8, rkInt) of tyUInt16: fun(intVal, uint16, rkInt) of tyUInt32: fun(intVal, uint32, rkInt) of tyUInt64: fun(intVal, uint64, rkInt) # note: differs from typeinfo.getBiggestInt of tyFloat: fun(floatVal, float, rkFloat) of tyFloat32: fun(floatVal, float32, rkFloat) of tyFloat64: fun(floatVal, float64, rkFloat) else: return false proc createStrKeepNode(x: var TFullReg; keepNode=true) = if x.node.isNil or not keepNode: x.node = newNode(nkStrLit) elif x.node.kind == nkNilLit and keepNode: when defined(useNodeIds): let id = x.node.id x.node[] = TNode(kind: nkStrLit) when defined(useNodeIds): x.node.id = id elif x.node.kind notin {nkStrLit..nkTripleStrLit} or nfAllConst in x.node.flags: # XXX this is hacky; tests/txmlgen triggers it: x.node = newNode(nkStrLit) # It not only hackey, it is also wrong for tgentemplate. The primary # cause of bugs like these is that the VM does not properly distinguish # between variable definitions (var foo = e) and variable updates (foo = e). include vmhooks template createStr(x) = x.node = newNode(nkStrLit) template createSet(x) = x.node = newNode(nkCurly) proc moveConst(x: var TFullReg, y: TFullReg) = x.ensureKind(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 system.NimNode: template asgnRef(x, y: untyped) = 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 result.comment = src.comment 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, src.len) for i in 0.. 0: var pc = f.safePoints.pop() var matched = false var pcEndExcept = pc # Scan the chain of exceptions starting at pc. # The structure is the following: # pc - opcExcept, # - opcExcept, # - opcExcept, # ... # - opcExcept, # - Exception handler body # - ... more opcExcept blocks may follow # - ... an optional opcFinally block may follow # # Note that the exception handler body already contains a jump to the # finally block or, if that's not present, to the point where the execution # should continue. # Also note that opcFinally blocks are the last in the chain. while c.code[pc].opcode == opcExcept: # Where this Except block ends pcEndExcept = pc + c.code[pc].regBx - wordExcess inc pc # A series of opcExcept follows for each exception type matched while c.code[pc].opcode == opcExcept: let excIndex = c.code[pc].regBx - wordExcess let exceptType = if excIndex > 0: c.types[excIndex].skipTypes(abstractPtrs) else: nil # echo typeToString(exceptType), " ", typeToString(raisedType) # Determine if the exception type matches the pattern if exceptType.isNil or inheritanceDiff(raisedType, exceptType) <= 0: matched = true break inc pc # Skip any further ``except`` pattern and find the first instruction of # the handler body while c.code[pc].opcode == opcExcept: inc pc if matched: break # If no handler in this chain is able to catch this exception we check if # the "parent" chains are able to. If this chain ends with a `finally` # block we must execute it before continuing. pc = pcEndExcept # Where the handler body starts let pcBody = pc if matched: return (ExceptionGotoHandler, pcBody) elif c.code[pc].opcode == opcFinally: # The +1 here is here because we don't want to execute it since we've # already pop'd this statepoint from the stack. return (ExceptionGotoFinally, pc + 1) return (ExceptionGotoUnhandled, 0) proc cleanUpOnReturn(c: PCtx; f: PStackFrame): int = # Walk up the chain of safepoints and return the PC of the first `finally` # block we find or -1 if no such block is found. # Note that the safepoint is removed once the function returns! result = -1 # Traverse the stack starting from the end in order to execute the blocks in # the intended order for i in 1..f.safePoints.len: var pc = f.safePoints[^i] # Skip the `except` blocks while c.code[pc].opcode == opcExcept: pc += c.code[pc].regBx - wordExcess if c.code[pc].opcode == opcFinally: discard f.safePoints.pop return pc + 1 proc opConv(c: PCtx; dest: var TFullReg, src: TFullReg, desttyp, srctyp: PType): bool = if desttyp.kind == tyString: dest.ensureKind(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[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(c.config, desttyp): return true of tyUInt..tyUInt64: dest.ensureKind(rkInt) let styp = srctyp.skipTypes(abstractRange) # skip distinct types(dest type could do this too if needed) case styp.kind of tyFloat..tyFloat64: dest.intVal = int(src.floatVal) else: let srcDist = (sizeof(src.intVal) - styp.size) * 8 let destDist = (sizeof(dest.intVal) - desttyp.size) * 8 var value = cast[BiggestUInt](src.intVal) value = (value shl srcDist) shr srcDist value = (value shl destDist) shr destDist dest.intVal = cast[BiggestInt](value) of tyBool: dest.ensureKind(rkInt) dest.intVal = case skipTypes(srctyp, abstractRange).kind of tyFloat..tyFloat64: int(src.floatVal != 0.0) else: int(src.intVal != 0) of tyFloat..tyFloat64: dest.ensureKind(rkFloat) case skipTypes(srctyp, abstractRange).kind of tyInt..tyInt64, tyUInt..tyUInt64, tyEnum, tyBool, tyChar: dest.floatVal = toBiggestFloat(src.intVal) else: dest.floatVal = src.floatVal of tyObject: if srctyp.skipTypes(abstractVarRange).kind != tyObject: internalError(c.config, "invalid object-to-object conversion") # A object-to-object conversion is essentially a no-op moveConst(dest, src) 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 = c.config.maxLoopIterationsVM else: msgWriteln(c.config, "stack trace: (most recent call last)", {msgNoUnitSep}) stackTraceAux(c, tos, pc) globalError(c.config, c.debug[pc], errTooManyIterations % $c.config.maxLoopIterationsVM) dec(c.loopIterations) proc recSetFlagIsRef(arg: PNode) = if arg.kind notin {nkStrLit..nkTripleStrLit}: arg.flags.incl(nfIsRef) for i in 0.. high(int): stackTrace(c, tos, pc, formatErrorIndexBound(regs[rc].intVal, high(int))) let idx = regs[rc].intVal.int let src = regs[rb].node if src.kind in {nkStrLit..nkTripleStrLit}: if idx <% src.strVal.len: regs[ra].node = newNodeI(nkCharLit, c.debug[pc]) regs[ra].node.intVal = src.strVal[idx].ord else: stackTrace(c, tos, pc, formatErrorIndexBound(idx, src.strVal.len-1)) elif src.kind notin {nkEmpty..nkFloat128Lit} and idx <% src.len: regs[ra].node = src[idx] else: stackTrace(c, tos, pc, formatErrorIndexBound(idx, src.safeLen-1)) of opcLdArrAddr: # a = addr(b[c]) decodeBC(rkNodeAddr) if regs[rc].intVal > high(int): stackTrace(c, tos, pc, formatErrorIndexBound(regs[rc].intVal, high(int))) let idx = regs[rc].intVal.int let src = if regs[rb].kind == rkNode: regs[rb].node else: regs[rb].nodeAddr[] if src.kind notin {nkEmpty..nkTripleStrLit} and idx <% src.len: regs[ra].nodeAddr = addr src.sons[idx] else: stackTrace(c, tos, pc, formatErrorIndexBound(idx, src.safeLen-1)) of opcLdStrIdx: decodeBC(rkInt) let idx = regs[rc].intVal.int let s = regs[rb].node.strVal if idx <% s.len: regs[ra].intVal = s[idx].ord else: stackTrace(c, tos, pc, formatErrorIndexBound(idx, s.len-1)) of opcLdStrIdxAddr: # a = addr(b[c]); similar to opcLdArrAddr decodeBC(rkNode) if regs[rc].intVal > high(int): stackTrace(c, tos, pc, formatErrorIndexBound(regs[rc].intVal, high(int))) let idx = regs[rc].intVal.int let s = regs[rb].node.strVal.addr # or `byaddr` if idx <% s[].len: # `makePtrType` not accessible from vm.nim let typ = newType(tyPtr, nextTypeId c.idgen, c.module.owner) typ.add getSysType(c.graph, c.debug[pc], tyChar) let node = newNodeIT(nkIntLit, c.debug[pc], typ) # xxx nkPtrLit node.intVal = cast[int](s[][idx].addr) node.flags.incl nfIsPtr regs[ra].node = node else: stackTrace(c, tos, pc, formatErrorIndexBound(idx, s[].len-1)) of opcWrArr: # a[b] = c decodeBC(rkNode) let idx = regs[rb].intVal.int let arr = regs[ra].node if arr.kind in {nkStrLit..nkTripleStrLit}: if idx <% arr.strVal.len: arr.strVal[idx] = chr(regs[rc].intVal) else: stackTrace(c, tos, pc, formatErrorIndexBound(idx, arr.strVal.len-1)) elif idx <% arr.len: writeField(arr[idx], regs[rc]) else: stackTrace(c, tos, pc, formatErrorIndexBound(idx, arr.safeLen-1)) of opcLdObj: # a = b.c decodeBC(rkNode) let src = if regs[rb].kind == rkNode: regs[rb].node else: regs[rb].nodeAddr[] case src.kind of nkEmpty..nkNilLit: # for nkPtrLit, this could be supported in the future, use something like: # derefPtrToReg(src.intVal + offsetof(src.typ, rc), typ_field, regs[ra], isAssign = false) # where we compute the offset in bytes for field rc stackTrace(c, tos, pc, errNilAccess & " " & $("kind", src.kind, "typ", typeToString(src.typ), "rc", rc)) of nkObjConstr: let n = src[rc + 1].skipColon regs[ra].node = n else: let n = src[rc] regs[ra].node = n of opcLdObjAddr: # a = addr(b.c) decodeBC(rkNodeAddr) let src = if regs[rb].kind == rkNode: regs[rb].node else: regs[rb].nodeAddr[] case src.kind of nkEmpty..nkNilLit: stackTrace(c, tos, pc, errNilAccess) of nkObjConstr: let n = src.sons[rc + 1] if n.kind == nkExprColonExpr: regs[ra].nodeAddr = addr n.sons[1] else: regs[ra].nodeAddr = addr src.sons[rc + 1] else: regs[ra].nodeAddr = addr src.sons[rc] of opcWrObj: # a.b = c decodeBC(rkNode) assert regs[ra].node != nil let shiftedRb = rb + ord(regs[ra].node.kind == nkObjConstr) let dest = regs[ra].node if dest.kind == nkNilLit: stackTrace(c, tos, pc, errNilAccess) elif dest[shiftedRb].kind == nkExprColonExpr: writeField(dest[shiftedRb][1], regs[rc]) else: writeField(dest[shiftedRb], 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, formatErrorIndexBound(idx, regs[ra].node.strVal.len-1)) of opcAddrReg: decodeB(rkRegisterAddr) regs[ra].regAddr = addr(regs[rb]) of opcAddrNode: decodeB(rkNodeAddr) case regs[rb].kind of rkNode: regs[ra].nodeAddr = addr(regs[rb].node) of rkNodeAddr: # bug #14339 regs[ra].nodeAddr = regs[rb].nodeAddr else: stackTrace(c, tos, pc, "limited VM support for 'addr', got kind: " & $regs[rb].kind) 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 == nkRefTy: regs[ra].node = regs[rb].node[0] elif not maybeHandlePtr(regs[rb].node, regs[ra], false): ## e.g.: typ.kind = tyObject ensureKind(rkNode) regs[ra].node = regs[rb].node else: stackTrace(c, tos, pc, errNilAccess & " kind: " & $regs[rb].kind) of opcWrDeref: # a[] = c; b unused let ra = instr.regA let rc = instr.regC case regs[ra].kind of rkNodeAddr: let n = regs[rc].regToNode # `var object` parameters are sent as rkNodeAddr. When they are mutated # vmgen generates opcWrDeref, which means that we must dereference # twice. # TODO: This should likely be handled differently in vmgen. let nAddr = regs[ra].nodeAddr if nAddr[] == nil: stackTrace(c, tos, pc, "opcWrDeref internal error") # refs bug #16613 if (nfIsRef notin nAddr[].flags and nfIsRef notin n.flags): nAddr[][] = n[] else: nAddr[] = n of rkRegisterAddr: regs[ra].regAddr[] = regs[rc] of rkNode: # xxx: also check for nkRefTy as in opcLdDeref? if not maybeHandlePtr(regs[ra].node, regs[rc], true): regs[ra].node[] = regs[rc].regToNode[] regs[ra].node.flags.incl nfIsRef 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.config, 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 let high = (imm and 1) # discard flags if (imm and nimNodeFlag) != 0: # used by mNLen (NimNode.len) regs[ra].intVal = regs[rb].node.safeLen - high else: # safeArrLen also return string node len # used when string is passed as openArray in VM regs[ra].intVal = regs[rb].node.safeArrLen - high of opcLenStr: decodeBImm(rkInt) assert regs[rb].kind == rkNode regs[ra].intVal = regs[rb].node.strVal.len - imm of opcLenCstring: decodeBImm(rkInt) assert regs[rb].kind == rkNode regs[ra].intVal = regs[rb].node.strVal.cstring.len - imm of opcIncl: decodeB(rkNode) let b = regs[rb].regToNode if not inSet(regs[ra].node, b): regs[ra].node.add copyTree(b) of opcInclRange: decodeBC(rkNode) var r = newNode(nkRange) r.add regs[rb].regToNode r.add regs[rc].regToNode regs[ra].node.add r.copyTree of opcExcl: decodeB(rkNode) var b = newNodeIT(nkCurly, regs[ra].node.info, regs[ra].node.typ) b.add regs[rb].regToNode var r = diffSets(c.config, regs[ra].node, b) discardSons(regs[ra].node) for i in 0..= 0: pc = savedPC - 1 savedPC = -1 if tos != savedFrame: tos = savedFrame updateRegsAlias of opcRaise: let raised = # Empty `raise` statement - reraise current exception if regs[ra].kind == rkNone: c.currentExceptionA else: regs[ra].node c.currentExceptionA = raised # Set the `name` field of the exception c.currentExceptionA[2].skipColon.strVal = c.currentExceptionA.typ.sym.name.s c.exceptionInstr = pc var frame = tos var jumpTo = findExceptionHandler(c, frame, raised) while jumpTo.why == ExceptionGotoUnhandled and not frame.next.isNil: frame = frame.next jumpTo = findExceptionHandler(c, frame, raised) case jumpTo.why: of ExceptionGotoHandler: # Jump to the handler, do nothing when the `finally` block ends. savedPC = -1 pc = jumpTo.where - 1 if tos != frame: tos = frame updateRegsAlias of ExceptionGotoFinally: # Jump to the `finally` block first then re-jump here to continue the # traversal of the exception chain savedPC = pc savedFrame = tos pc = jumpTo.where - 1 if tos != frame: tos = frame updateRegsAlias of ExceptionGotoUnhandled: # Nobody handled this exception, error out. bailOut(c, tos) of opcNew: ensureKind(rkNode) let typ = c.types[instr.regBx - wordExcess] regs[ra].node = getNullValue(typ, c.debug[pc], c.config) regs[ra].node.flags.incl nfIsRef of opcNewSeq: let typ = c.types[instr.regBx - wordExcess] inc pc ensureKind(rkNode) let instr2 = c.code[pc] let count = regs[instr2.regA].intVal.int regs[ra].node = newNodeI(nkBracket, c.debug[pc]) regs[ra].node.typ = typ newSeq(regs[ra].node.sons, count) for i in 0.. 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[rb] if fitsRegister(cnst.typ): reset(regs[ra]) putIntoReg(regs[ra], cnst) else: ensureKind(rkNode) regs[ra].node = cnst of opcAsgnConst: let rb = instr.regBx - wordExcess let cnst = c.constants[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[rb] of opcLdGlobalDerefFFI: let rb = instr.regBx - wordExcess - 1 let node = c.globals[rb] let typ = node.typ doAssert node.kind == nkIntLit, $(node.kind) if typ.kind == tyPtr: ensureKind(rkNode) # use nkPtrLit once this is added let node2 = newNodeIT(nkIntLit, c.debug[pc], typ) node2.intVal = cast[ptr int](node.intVal)[] node2.flags.incl nfIsPtr regs[ra].node = node2 elif not derefPtrToReg(node.intVal, typ, regs[ra], isAssign = false): stackTrace(c, tos, pc, "opcLdDeref unsupported type: " & $(typeToString(typ), typ[0].kind)) of opcLdGlobalAddrDerefFFI: let rb = instr.regBx - wordExcess - 1 let node = c.globals[rb] let typ = node.typ var node2 = newNodeIT(nkIntLit, node.info, typ) node2.intVal = node.intVal node2.flags.incl nfIsPtr ensureKind(rkNode) regs[ra].node = node2 of opcLdGlobalAddr: let rb = instr.regBx - wordExcess - 1 ensureKind(rkNodeAddr) regs[ra].nodeAddr = addr(c.globals[rb]) of opcRepr: decodeB(rkNode) createStr regs[ra] regs[ra].node.strVal = renderTree(regs[rb].regToNode, {renderNoComments, renderDocComments}) of opcQuit: if c.mode in {emRepl, emStaticExpr, emStaticStmt}: message(c.config, c.debug[pc], hintQuitCalled) msgQuit(int8(toInt(getOrdValue(regs[ra].regToNode, onError = toInt128(1))))) else: return TFullReg(kind: rkNone) of opcInvalidField: let msg = regs[ra].node.strVal let disc = regs[instr.regB].regToNode let msg2 = formatFieldDefect(msg, $disc) stackTrace(c, tos, pc, msg2) 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: c.setLenSeq(regs[ra].node, newLen, c.debug[pc]) of opcNarrowS: decodeB(rkInt) let min = -(1.BiggestInt shl (rb-1)) let max = (1.BiggestInt shl (rb-1))-1 if regs[ra].intVal < min or regs[ra].intVal > max: stackTrace(c, tos, pc, "unhandled exception: value out of range") of opcNarrowU: decodeB(rkInt) regs[ra].intVal = regs[ra].intVal and ((1'i64 shl rb)-1) of opcSignExtend: # like opcNarrowS, but no out of range possible decodeB(rkInt) let imm = 64 - rb regs[ra].intVal = ashr(regs[ra].intVal shl imm, imm) of opcIsNil: decodeB(rkInt) let node = regs[rb].node regs[ra].intVal = ord( # Note that `nfIsRef` + `nkNilLit` represents an allocated # reference with the value `nil`, so `isNil` should be false! (node.kind == nkNilLit and nfIsRef notin node.flags) or (not node.typ.isNil and node.typ.kind == tyProc and node.typ.callConv == ccClosure and node.safeLen > 0 and node[0].kind == nkNilLit and node[1].kind == nkNilLit)) of opcNBindSym: # cannot use this simple check # if dynamicBindSym notin c.config.features: # bindSym with static input decodeBx(rkNode) regs[ra].node = copyTree(c.constants[rbx]) regs[ra].node.flags.incl nfIsRef of opcNDynBindSym: # experimental bindSym let rb = instr.regB rc = instr.regC idx = int(regs[rb+rc-1].intVal) callback = c.callbacks[idx].value args = VmArgs(ra: ra, rb: rb, rc: rc, slots: cast[ptr UncheckedArray[TFullReg]](addr regs[0]), currentException: c.currentExceptionA, currentLineInfo: c.debug[pc]) callback(args) regs[ra].node.flags.incl nfIsRef of opcNChild: decodeBC(rkNode) let idx = regs[rc].intVal.int let src = regs[rb].node if src.kind in {nkEmpty..nkNilLit}: stackTrace(c, tos, pc, "cannot get child of node kind: n" & $src.kind) elif idx >=% src.len: stackTrace(c, tos, pc, formatErrorIndexBound(idx, src.len-1)) else: regs[ra].node = src[idx] of opcNSetChild: decodeBC(rkNode) let idx = regs[rb].intVal.int var dest = regs[ra].node if nfSem in dest.flags and allowSemcheckedAstModification notin c.config.legacyFeatures: stackTrace(c, tos, pc, "typechecked nodes may not be modified") elif dest.kind in {nkEmpty..nkNilLit}: stackTrace(c, tos, pc, "cannot set child of node kind: n" & $dest.kind) elif idx >=% dest.len: stackTrace(c, tos, pc, formatErrorIndexBound(idx, dest.len-1)) else: dest[idx] = regs[rc].node of opcNAdd: decodeBC(rkNode) var u = regs[rb].node if nfSem in u.flags and allowSemcheckedAstModification notin c.config.legacyFeatures: stackTrace(c, tos, pc, "typechecked nodes may not be modified") elif u.kind in {nkEmpty..nkNilLit}: stackTrace(c, tos, pc, "cannot add to node kind: n" & $u.kind) else: u.add(regs[rc].node) regs[ra].node = u of opcNAddMultiple: decodeBC(rkNode) let x = regs[rc].node var u = regs[rb].node if nfSem in u.flags and allowSemcheckedAstModification notin c.config.legacyFeatures: stackTrace(c, tos, pc, "typechecked nodes may not be modified") elif u.kind in {nkEmpty..nkNilLit}: stackTrace(c, tos, pc, "cannot add to node kind: n" & $u.kind) else: for i in 0.. 0: c.errorFlag = error elif ast.len != 1: c.errorFlag = formatMsg(c.config, c.debug[pc], errGenerated, "expected expression, but got multiple statements") else: regs[ra].node = ast[0] of opcParseStmtToAst: decodeB(rkNode) var error: string let ast = parseString(regs[rb].node.strVal, c.cache, c.config, toFullPath(c.config, c.debug[pc]), c.debug[pc].line.int, proc (conf: ConfigRef; info: TLineInfo; msg: TMsgKind; arg: string) {.nosinks.} = if error.len == 0 and msg <= errMax: error = formatMsg(conf, info, msg, arg)) if error.len > 0: c.errorFlag = error else: regs[ra].node = ast of opcQueryErrorFlag: createStr regs[ra] regs[ra].node.strVal = c.errorFlag c.errorFlag.setLen 0 of opcCallSite: ensureKind(rkNode) if c.callsite != nil: regs[ra].node = c.callsite else: stackTrace(c, tos, pc, errFieldXNotFound & "callsite") of opcNGetLineInfo: decodeBImm(rkNode) let n = regs[rb].node case imm of 0: # getFile regs[ra].node = newStrNode(nkStrLit, toFullPath(c.config, n.info)) of 1: # getLine regs[ra].node = newIntNode(nkIntLit, n.info.line.int) of 2: # getColumn regs[ra].node = newIntNode(nkIntLit, n.info.col) else: internalAssert c.config, false regs[ra].node.info = n.info regs[ra].node.typ = n.typ of opcNSetLineInfo: decodeB(rkNode) regs[ra].node.info = regs[rb].node.info of opcEqIdent: decodeBC(rkInt) # aliases for shorter and easier to understand code below var aNode = regs[rb].node var bNode = regs[rc].node # Skipping both, `nkPostfix` and `nkAccQuoted` for both # arguments. `nkPostfix` exists only to tag exported symbols # and therefor it can be safely skipped. Nim has no postfix # operator. `nkAccQuoted` is used to quote an identifier that # wouldn't be allowed to use in an unquoted context. if aNode.kind == nkPostfix: aNode = aNode[1] if aNode.kind == nkAccQuoted: aNode = aNode[0] if bNode.kind == nkPostfix: bNode = bNode[1] if bNode.kind == nkAccQuoted: bNode = bNode[0] # These vars are of type `cstring` to prevent unnecessary string copy. var aStrVal: cstring = nil var bStrVal: cstring = nil # extract strVal from argument ``a`` case aNode.kind of nkStrLit..nkTripleStrLit: aStrVal = aNode.strVal.cstring of nkIdent: aStrVal = aNode.ident.s.cstring of nkSym: aStrVal = aNode.sym.name.s.cstring of nkOpenSymChoice, nkClosedSymChoice: aStrVal = aNode[0].sym.name.s.cstring else: discard # extract strVal from argument ``b`` case bNode.kind of nkStrLit..nkTripleStrLit: bStrVal = bNode.strVal.cstring of nkIdent: bStrVal = bNode.ident.s.cstring of nkSym: bStrVal = bNode.sym.name.s.cstring of nkOpenSymChoice, nkClosedSymChoice: bStrVal = bNode[0].sym.name.s.cstring else: discard regs[ra].intVal = if aStrVal != nil and bStrVal != nil: ord(idents.cmpIgnoreStyle(aStrVal, bStrVal, high(int)) == 0) else: 0 of opcStrToIdent: decodeB(rkNode) if regs[rb].node.kind notin {nkStrLit..nkTripleStrLit}: stackTrace(c, tos, pc, errFieldXNotFound & "strVal") else: regs[ra].node = newNodeI(nkIdent, c.debug[pc]) regs[ra].node.ident = getIdent(c.cache, regs[rb].node.strVal) regs[ra].node.flags.incl nfIsRef of opcSetType: let typ = c.types[instr.regBx - wordExcess] if regs[ra].kind != rkNode: let temp = regToNode(regs[ra]) ensureKind(rkNode) regs[ra].node = temp regs[ra].node.info = c.debug[pc] regs[ra].node.typ = typ of opcConv: let rb = instr.regB inc pc let desttyp = c.types[c.code[pc].regBx - wordExcess] inc pc let srctyp = c.types[c.code[pc].regBx - wordExcess] if opConv(c, regs[ra], regs[rb], desttyp, srctyp): stackTrace(c, tos, pc, errIllegalConvFromXtoY % [ typeToString(srctyp), typeToString(desttyp)]) of opcCast: let rb = instr.regB inc pc let desttyp = c.types[c.code[pc].regBx - wordExcess] inc pc let srctyp = c.types[c.code[pc].regBx - wordExcess] when hasFFI: let dest = fficast(c.config, regs[rb].node, desttyp) # todo: check whether this is correct # asgnRef(regs[ra], dest) putIntoReg(regs[ra], dest) else: globalError(c.config, c.debug[pc], "cannot evaluate cast") of opcNSetIntVal: decodeB(rkNode) var dest = regs[ra].node if dest.kind in {nkCharLit..nkUInt64Lit} and regs[rb].kind in {rkInt}: dest.intVal = regs[rb].intVal elif dest.kind == nkSym and dest.sym.kind == skEnumField: stackTrace(c, tos, pc, "`intVal` cannot be changed for an enum symbol.") else: stackTrace(c, tos, pc, errFieldXNotFound & "intVal") of opcNSetFloatVal: decodeB(rkNode) var dest = regs[ra].node if dest.kind in {nkFloatLit..nkFloat64Lit} and regs[rb].kind in {rkFloat}: dest.floatVal = regs[rb].floatVal else: stackTrace(c, tos, pc, errFieldXNotFound & "floatVal") of opcNSetSymbol: decodeB(rkNode) var dest = regs[ra].node if dest.kind == nkSym and regs[rb].node.kind == nkSym: dest.sym = regs[rb].node.sym else: stackTrace(c, tos, pc, errFieldXNotFound & "symbol") of opcNSetIdent: decodeB(rkNode) var dest = regs[ra].node if dest.kind == nkIdent and regs[rb].node.kind == nkIdent: dest.ident = regs[rb].node.ident else: stackTrace(c, tos, pc, errFieldXNotFound & "ident") of opcNSetType: decodeB(rkNode) let b = regs[rb].node internalAssert c.config, b.kind == nkSym and b.sym.kind == skType internalAssert c.config, regs[ra].node != nil regs[ra].node.typ = b.sym.typ of opcNSetStrVal: decodeB(rkNode) var dest = regs[ra].node if dest.kind in {nkStrLit..nkTripleStrLit} and regs[rb].kind in {rkNode}: dest.strVal = regs[rb].node.strVal elif dest.kind == nkCommentStmt and regs[rb].kind in {rkNode}: dest.comment = regs[rb].node.strVal else: stackTrace(c, tos, pc, errFieldXNotFound & "strVal") of opcNNewNimNode: decodeBC(rkNode) var k = regs[rb].intVal if k < 0 or k > ord(high(TNodeKind)): internalError(c.config, c.debug[pc], "request to create a NimNode of invalid kind") let cc = regs[rc].node let x = newNodeI(TNodeKind(int(k)), if cc.kind != nkNilLit: cc.info elif c.comesFromHeuristic.line != 0'u16: c.comesFromHeuristic elif c.callsite != nil and c.callsite.safeLen > 1: c.callsite[1].info else: c.debug[pc]) x.flags.incl nfIsRef # prevent crashes in the compiler resulting from wrong macros: if x.kind == nkIdent: x.ident = c.cache.emptyIdent regs[ra].node = x 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 0.. ord(high(TSymKind)): internalError(c.config, c.debug[pc], "request to create symbol of invalid kind") var sym = newSym(k.TSymKind, getIdent(c.cache, name), nextSymId c.idgen, c.module.owner, c.debug[pc]) incl(sym.flags, sfGenSym) regs[ra].node = newSymNode(sym) regs[ra].node.flags.incl nfIsRef of opcNccValue: decodeB(rkInt) let destKey = regs[rb].node.strVal regs[ra].intVal = getOrDefault(c.graph.cacheCounters, destKey) of opcNccInc: let g = c.graph declBC() let destKey = regs[rb].node.strVal let by = regs[rc].intVal let v = getOrDefault(g.cacheCounters, destKey) g.cacheCounters[destKey] = v+by recordInc(c, c.debug[pc], destKey, by) of opcNcsAdd: let g = c.graph declBC() let destKey = regs[rb].node.strVal let val = regs[rc].node if not contains(g.cacheSeqs, destKey): g.cacheSeqs[destKey] = newTree(nkStmtList, val) else: g.cacheSeqs[destKey].add val recordAdd(c, c.debug[pc], destKey, val) of opcNcsIncl: let g = c.graph declBC() let destKey = regs[rb].node.strVal let val = regs[rc].node if not contains(g.cacheSeqs, destKey): g.cacheSeqs[destKey] = newTree(nkStmtList, val) else: block search: for existing in g.cacheSeqs[destKey]: if exprStructuralEquivalent(existing, val, strictSymEquality=true): break search g.cacheSeqs[destKey].add val recordIncl(c, c.debug[pc], destKey, val) of opcNcsLen: let g = c.graph decodeB(rkInt) let destKey = regs[rb].node.strVal regs[ra].intVal = if contains(g.cacheSeqs, destKey): g.cacheSeqs[destKey].len else: 0 of opcNcsAt: let g = c.graph decodeBC(rkNode) let idx = regs[rc].intVal let destKey = regs[rb].node.strVal if contains(g.cacheSeqs, destKey) and idx <% g.cacheSeqs[destKey].len: regs[ra].node = g.cacheSeqs[destKey][idx.int] else: stackTrace(c, tos, pc, formatErrorIndexBound(idx, g.cacheSeqs[destKey].len-1)) of opcNctPut: let g = c.graph let destKey = regs[ra].node.strVal let key = regs[instr.regB].node.strVal let val = regs[instr.regC].node if not contains(g.cacheTables, destKey): g.cacheTables[destKey] = initBTree[string, PNode]() if not contains(g.cacheTables[destKey], key): g.cacheTables[destKey].add(key, val) recordPut(c, c.debug[pc], destKey, key, val) else: stackTrace(c, tos, pc, "key already exists: " & key) of opcNctLen: let g = c.graph decodeB(rkInt) let destKey = regs[rb].node.strVal regs[ra].intVal = if contains(g.cacheTables, destKey): g.cacheTables[destKey].len else: 0 of opcNctGet: let g = c.graph decodeBC(rkNode) let destKey = regs[rb].node.strVal let key = regs[rc].node.strVal if contains(g.cacheTables, destKey): if contains(g.cacheTables[destKey], key): regs[ra].node = getOrDefault(g.cacheTables[destKey], key) else: stackTrace(c, tos, pc, "key does not exist: " & key) else: stackTrace(c, tos, pc, "key does not exist: " & destKey) of opcNctHasNext: let g = c.graph decodeBC(rkInt) let destKey = regs[rb].node.strVal regs[ra].intVal = if g.cacheTables.contains(destKey): ord(btrees.hasNext(g.cacheTables[destKey], regs[rc].intVal.int)) else: 0 of opcNctNext: let g = c.graph decodeBC(rkNode) let destKey = regs[rb].node.strVal let index = regs[rc].intVal if contains(g.cacheTables, destKey): let (k, v, nextIndex) = btrees.next(g.cacheTables[destKey], index.int) regs[ra].node = newTree(nkTupleConstr, newStrNode(k, c.debug[pc]), v, newIntNode(nkIntLit, nextIndex)) else: stackTrace(c, tos, pc, "key does not exist: " & destKey) 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 c.config, typ != nil while typ.kind == tyTypeDesc and typ.len > 0: typ = typ[0] createStr regs[ra] regs[ra].node.strVal = typ.typeToString(preferExported) of opcMarshalLoad: let ra = instr.regA let rb = instr.regB inc pc let typ = c.types[c.code[pc].regBx - wordExcess] putIntoReg(regs[ra], loadAny(regs[rb].node.strVal, typ, c.cache, c.config, c.idgen)) of opcMarshalStore: decodeB(rkNode) inc pc let typ = c.types[c.code[pc].regBx - wordExcess] createStrKeepNode(regs[ra]) storeAny(regs[ra].node.strVal, typ, regs[rb].regToNode, c.config) c.profiler.leave(c) inc pc proc execute(c: PCtx, start: int): PNode = var tos = PStackFrame(prc: nil, comesFrom: 0, next: nil) newSeq(tos.slots, c.prc.regInfo.len) result = rawExecute(c, start, tos).regToNode proc execProc*(c: PCtx; sym: PSym; args: openArray[PNode]): PNode = c.loopIterations = c.config.maxLoopIterationsVM if sym.kind in routineKinds: if sym.typ.len-1 != args.len: localError(c.config, sym.info, "NimScript: expected $# arguments, but got $#" % [ $(sym.typ.len-1), $args.len]) else: let start = genProc(c, sym) var tos = PStackFrame(prc: sym, comesFrom: 0, next: nil) let maxSlots = sym.offset newSeq(tos.slots, maxSlots) # setup parameters: if not isEmptyType(sym.typ[0]) or sym.kind == skMacro: putIntoReg(tos.slots[0], getNullValue(sym.typ[0], sym.info, c.config)) # XXX We could perform some type checking here. for i in 1.. 0: return n let n = transformExpr(g, idgen, module, n) setupGlobalCtx(module, g, idgen) var c = PCtx g.vm let oldMode = c.mode c.mode = mode let start = genExpr(c, n, requiresValue = mode!=emStaticStmt) if c.code[start].opcode == opcEof: return newNodeI(nkEmpty, n.info) 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.regInfo.len) #for i in 0.. 0: return errorNode(idgen, module, n) # XXX globalError() is ugly here, but I don't know a better solution for now inc(g.config.evalMacroCounter) if g.config.evalMacroCounter > evalMacroLimit: globalError(g.config, n.info, "macro instantiation too nested") # immediate macros can bypass any type and arity checking so we check the # arity here too: if sym.typ.len > n.safeLen and sym.typ.len > 1: globalError(g.config, n.info, "in call '$#' got $#, but expected $# argument(s)" % [ n.renderTree, $(n.safeLen-1), $(sym.typ.len-1)]) setupGlobalCtx(module, g, idgen) var c = PCtx g.vm let oldMode = c.mode c.mode = emStaticStmt c.comesFromHeuristic.line = 0'u16 c.callsite = nOrig c.templInstCounter = templInstCounter 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] = TFullReg(kind: rkNode, node: newNodeI(nkEmpty, n.info)) # setup parameters: for i in 1..