diff options
author | Araq <rumpf_a@web.de> | 2013-07-24 21:39:39 +0200 |
---|---|---|
committer | Araq <rumpf_a@web.de> | 2013-07-24 21:39:39 +0200 |
commit | b30e237a3746b4c4c421a87277a6458642c9f13a (patch) | |
tree | aa695bb65169ed47c0e1f30e46a83b38b98c7e94 | |
parent | 84534ce4b8d2717f672e870921cab46dc2397c28 (diff) | |
download | Nim-b30e237a3746b4c4c421a87277a6458642c9f13a.tar.gz |
first steps to a new evaluation engine
-rw-r--r-- | compiler/nimeval.nim | 33 | ||||
-rw-r--r-- | compiler/vm.nim | 584 | ||||
-rw-r--r-- | compiler/vmdef.nim | 149 | ||||
-rw-r--r-- | compiler/vmgen.nim | 1002 |
4 files changed, 1768 insertions, 0 deletions
diff --git a/compiler/nimeval.nim b/compiler/nimeval.nim new file mode 100644 index 000000000..a239d4ef2 --- /dev/null +++ b/compiler/nimeval.nim @@ -0,0 +1,33 @@ +# +# +# The Nimrod Compiler +# (c) Copyright 2013 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## exposes the Nimrod VM to clients. + +import + ast, modules, passes, passaux, condsyms, + options, nimconf, lists, sem, semdata, llstream, vm + +proc execute*(program: string) = + passes.gIncludeFile = includeModule + passes.gImportModule = importModule + initDefines() + LoadConfigs(DefaultConfig) + + initDefines() + DefineSymbol("nimrodvm") + when hasFFI: DefineSymbol("nimffi") + registerPass(verbosePass) + registerPass(semPass) + registerPass(vmPass) + + appendStr(searchPaths, options.libpath) + compileSystemModule() + var m = makeStdinModule() + incl(m.flags, sfMainModule) + processModule(m, LLStreamOpen(program), nil) diff --git a/compiler/vm.nim b/compiler/vm.nim new file mode 100644 index 000000000..72e0cb5b1 --- /dev/null +++ b/compiler/vm.nim @@ -0,0 +1,584 @@ +# +# +# The Nimrod Compiler +# (c) Copyright 2013 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-2 int32s in memory, it is a register based VM. + +import ast, astalgo, msgs, vmdef, vmgen, nimsets, types, passes, unsigned + +type + PStackFrame* = ref TStackFrame + TStackFrame* = object + prc: PSym # current prc; proc that is evaluated + slots: TNodeSeq # 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) = + if x != nil: + stackTraceAux(c, x.next, x.comesFrom) + 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) + 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(nimHasInterpreterLoop): + {.pragma: interpreterLoop.} + +template inc(pc: ptr TInstr, diff = 1) = + inc cast[TAddress](pc), TInstr.sizeof * diff + +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 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) + +proc compile(c: PCtx, s: PSym): int = vmgen.genProc(c, s) + +proc myreset(n: PNode) = + when defined(system.reset): + var oldInfo = n.info + reset(n[]) + n.info = oldInfo + +template move(a, b: expr) = system.shallowCopy(a, b) +# XXX fix minor 'shallowCopy' overloading bug in compiler + +proc asgnRef(x, y: PNode) = + myreset(x) + x.kind = y.kind + x.typ = y.typ + case x.kind + of nkCharLit..nkInt64Lit: x.intVal = y.intVal + of nkFloatLit..nkFloat64Lit: x.floatVal = y.floatVal + of nkStrLit..nkTripleStrLit: x.strVal = y.strVal + of nkIdent: x.ident = y.ident + of nkSym: x.sym = y.sym + else: + if x.kind notin {nkEmpty..nkNilLit}: + move(x.sons, y.sons) + +proc asgnComplex(x, y: PNode) = + myreset(x) + x.kind = y.kind + x.typ = y.typ + case x.kind + of nkCharLit..nkInt64Lit: x.intVal = y.intVal + of nkFloatLit..nkFloat64Lit: x.floatVal = y.floatVal + of nkStrLit..nkTripleStrLit: x.strVal = y.strVal + of nkIdent: x.ident = y.ident + of nkSym: x.sym = y.sym + else: + if x.kind notin {nkEmpty..nkNilLit}: + let y = y.copyTree + for i in countup(0, sonsLen(y) - 1): addSon(x, y.sons[i]) + +template getstr(a: expr): expr = + (if a.kind == nkStrLit: a.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 nextSafePoint(f: PStackFrame): int = + var f = f + while f.safePoints.isNil or f.safePoints.len == 0: + f = f.next + if f.isNil: return -1 + result = f.safePoints.pop + +proc cleanUpOnException(c: PCtx; tos: PStackFrame; regs: TNodeSeq): int = + let raisedType = c.currentExceptionA.typ.skipTypes(abstractPtrs) + while true: + var pc2 = tos.nextSafePoint + if pc2 == -1: return -1 + + 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 + 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 + +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 execute(c: PCtx, start: int) = + var pc = start + var regs: TNodeSeq # alias to tos.slots for performance + var tos: PStackFrame + newSeq(regs, c.prc.maxSlots) + while true: + {.interpreterLoop.} + let instr = c.code[pc] + let ra = instr.regA + echo "PC ", pc, " ", c.code[pc].opcode, " ra ", ra + case instr.opcode + of opcEof: break + of opcRet: + # XXX perform any cleanup actions + tos = tos.next + if tos.isNil: return + let retVal = regs[0] + move(regs, tos.slots) + pc = tos.comesFrom + assert c.code[pc].opcode in {opcIndCall, opcIndCallAsgn} + if c.code[pc].opcode == opcIndCallAsgn: + regs[c.code[pc].regA] = retVal + of opcYldYoid: assert false + of opcYldVal: assert false + of opcAsgnInt: + echo ra, " ", instr.regB, " ", regs.len, tos.prc.name.s + decodeB(nkIntLit) + regs[ra].intVal = regs[rb].intVal + of opcAsgnStr: + decodeB(nkStrLit) + debug regs[rb] + echo rb + Message(c.debug[pc], warnUser, " here") + regs[ra].strVal = regs[rb].strVal + of opcAsgnFloat: + decodeB(nkFloatLit) + regs[ra].floatVal = regs[rb].floatVal + of opcAsgnComplex: + asgnComplex(regs[ra], regs[instr.regB]) + of opcAsgnRef: + asgnRef(regs[ra], regs[instr.regB]) + of opcWrGlobalRef: + asgnRef(c.globals[instr.regBx-wordExcess-1], regs[ra]) + of opcWrGlobal: + asgnComplex(c.globals.sons[instr.regBx-wordExcess-1], regs[ra]) + of opcLdArr: + # a = b[c] + let rb = instr.regB + let rc = instr.regC + let idx = regs[rc].intVal + # XXX what if the array is not 0-based? -> codegen should insert a sub + regs[ra] = regs[rb].sons[idx.int] + of opcWrArr: + # a[b] = c + let rb = instr.regB + let rc = instr.regC + let idx = regs[rb].intVal + asgnComplex(regs[ra].sons[idx.int], regs[rc]) + of opcWrArrRef: + let rb = instr.regB + let rc = instr.regC + let idx = regs[rb].intVal + asgnRef(regs[ra].sons[idx.int], regs[rc]) + of opcLdObj: + # a = b.c + let rb = instr.regB + let rc = instr.regC + # XXX this creates a wrong alias + asgnComplex(regs[ra], regs[rb].sons[rc]) + of opcWrObj: + # a.b = c + let rb = instr.regB + let rc = instr.regC + asgnComplex(regs[ra].sons[rb], regs[rc]) + of opcWrObjRef: + let rb = instr.regB + let rc = instr.regC + asgnRef(regs[ra].sons[rb], regs[rc]) + of opcWrStrIdx: + decodeBC(nkStrLit) + let idx = regs[rb].intVal.int + regs[ra].strVal[idx] = chr(regs[rc].intVal) + of opcAddr: + decodeB(nkRefTy) + if regs[ra].len == 0: regs[ra].add regs[rb] + else: regs[ra].sons[0] = regs[rb] + of opcDeref: + # a = b[] + let rb = instr.regB + if regs[rb].kind == nkNilLit: + stackTrace(c, tos, pc, errNilAccess) + assert regs[rb].kind == nkRefTy + regs[ra] = regs[rb].sons[0] + of opcAddInt: + decodeBC(nkIntLit) + regs[ra].intVal = regs[rb].intVal + regs[rc].intVal + of opcAddImmInt: + decodeBImm(nkIntLit) + regs[ra].intVal = regs[rb].intVal + imm + of opcSubInt: + decodeBC(nkIntLit) + regs[ra].intVal = regs[rb].intVal - regs[rc].intVal + of opcSubImmInt: + decodeBImm(nkIntLit) + regs[ra].intVal = regs[rb].intVal - imm + of opcLenSeq: + decodeBImm(nkIntLit) + assert regs[rb].kind == nkBracket + regs[ra].intVal = regs[rb].len - imm + of opcLenStr: + decodeBImm(nkIntLit) + assert regs[rb].kind == nkStrLit + regs[ra].intVal = regs[rb].strVal.len - imm + of opcIncl: + decodeB(nkCurly) + if not inSet(regs[ra], regs[rb]): addSon(regs[ra], copyTree(regs[rb])) + of opcExcl: + decodeB(nkCurly) + # XXX arg we need types here :-( + var b = newNodeIT(nkCurly, regs[rb].info, regs[rb].typ) + addSon(b, regs[rb]) + var r = diffSets(regs[ra], b) + discardSons(regs[ra]) + for i in countup(0, sonsLen(r) - 1): addSon(regs[ra], r.sons[i]) + of opcCard: + decodeB(nkIntLit) + regs[ra].intVal = nimsets.cardSet(regs[rb]) + of opcMulInt: + decodeBC(nkIntLit) + regs[ra].intVal = regs[rb].intVal * regs[rc].intVal + of opcDivInt: + decodeBC(nkIntLit) + regs[ra].intVal = regs[rb].intVal div regs[rc].intVal + of opcModInt: + decodeBC(nkIntLit) + regs[ra].intVal = regs[rb].intVal mod regs[rc].intVal + of opcAddFloat: + decodeBC(nkFloatLit) + regs[ra].floatVal = regs[rb].floatVal + regs[rc].floatVal + of opcSubFloat: + decodeBC(nkFloatLit) + regs[ra].floatVal = regs[rb].floatVal - regs[rc].floatVal + of opcMulFloat: + decodeBC(nkFloatLit) + regs[ra].floatVal = regs[rb].floatVal * regs[rc].floatVal + of opcDivFloat: + decodeBC(nkFloatLit) + regs[ra].floatVal = regs[rb].floatVal / regs[rc].floatVal + of opcShrInt: + decodeBC(nkIntLit) + regs[ra].intVal = regs[rb].intVal shr regs[rc].intVal + of opcShlInt: + decodeBC(nkIntLit) + regs[ra].intVal = regs[rb].intVal shl regs[rc].intVal + of opcBitandInt: + decodeBC(nkIntLit) + regs[ra].intVal = regs[rb].intVal and regs[rc].intVal + of opcBitorInt: + decodeBC(nkIntLit) + regs[ra].intVal = regs[rb].intVal or regs[rc].intVal + of opcBitxorInt: + decodeBC(nkIntLit) + regs[ra].intVal = regs[rb].intVal xor regs[rc].intVal + of opcAddu: + decodeBC(nkIntLit) + regs[ra].intVal = regs[rb].intVal +% regs[rc].intVal + of opcSubu: + decodeBC(nkIntLit) + regs[ra].intVal = regs[rb].intVal -% regs[rc].intVal + of opcMulu: + decodeBC(nkIntLit) + regs[ra].intVal = regs[rb].intVal *% regs[rc].intVal + of opcDivu: + decodeBC(nkIntLit) + regs[ra].intVal = regs[rb].intVal /% regs[rc].intVal + of opcModu: + decodeBC(nkIntLit) + regs[ra].intVal = regs[rb].intVal %% regs[rc].intVal + of opcEqInt: + decodeBC(nkIntLit) + regs[ra].intVal = ord(regs[rb].intVal == regs[rc].intVal) + of opcLeInt: + decodeBC(nkIntLit) + regs[ra].intVal = ord(regs[rb].intVal <= regs[rc].intVal) + of opcLtInt: + decodeBC(nkIntLit) + regs[ra].intVal = ord(regs[rb].intVal < regs[rc].intVal) + of opcEqFloat: + decodeBC(nkIntLit) + regs[ra].intVal = ord(regs[rb].floatVal == regs[rc].floatVal) + of opcLeFloat: + decodeBC(nkIntLit) + regs[ra].intVal = ord(regs[rb].floatVal <= regs[rc].floatVal) + of opcLtFloat: + decodeBC(nkIntLit) + regs[ra].intVal = ord(regs[rb].floatVal < regs[rc].floatVal) + of opcLeu: + decodeBC(nkIntLit) + regs[ra].intVal = ord(regs[rb].intVal <=% regs[rc].intVal) + of opcLtu: + decodeBC(nkIntLit) + regs[ra].intVal = ord(regs[rb].intVal <% regs[rc].intVal) + of opcEqRef: + decodeBC(nkIntLit) + regs[ra].intVal = ord(regs[rb] == regs[rc]) + # XXX is this correct? nope ... + of opcXor: + decodeBC(nkIntLit) + regs[ra].intVal = ord(regs[rb].intVal != regs[rc].intVal) + of opcNot: + decodeB(nkIntLit) + assert regs[rb].kind == nkIntLit + regs[ra].intVal = 1 - regs[rb].intVal + of opcUnaryMinusInt: + decodeB(nkIntLit) + assert regs[rb].kind == nkIntLit + regs[ra].intVal = -regs[rb].intVal + of opcUnaryMinusFloat: + decodeB(nkFloatLit) + assert regs[rb].kind == nkFloatLit + regs[ra].floatVal = -regs[rb].floatVal + of opcBitnotInt: + decodeB(nkIntLit) + assert regs[rb].kind == nkIntLit + regs[ra].intVal = not regs[rb].intVal + of opcEqStr: + decodeBC(nkIntLit) + regs[ra].intVal = Ord(regs[rb].strVal == regs[rc].strVal) + of opcLeStr: + decodeBC(nkIntLit) + regs[ra].intVal = Ord(regs[rb].strVal <= regs[rc].strVal) + of opcLtStr: + decodeBC(nkIntLit) + regs[ra].intVal = Ord(regs[rb].strVal < regs[rc].strVal) + of opcLeSet: + decodeBC(nkIntLit) + regs[ra].intVal = Ord(containsSets(regs[rb], regs[rc])) + of opcEqSet: + decodeBC(nkIntLit) + regs[ra].intVal = Ord(equalSets(regs[rb], regs[rc])) + of opcLtSet: + decodeBC(nkIntLit) + let a = regs[rb] + let b = regs[rc] + regs[ra].intVal = Ord(containsSets(a, b) and not equalSets(a, b)) + of opcMulSet: + decodeBC(nkCurly) + move(regs[ra].sons, nimsets.intersectSets(regs[rb], regs[rc]).sons) + of opcPlusSet: + decodeBC(nkCurly) + move(regs[ra].sons, nimsets.unionSets(regs[rb], regs[rc]).sons) + of opcMinusSet: + decodeBC(nkCurly) + move(regs[ra].sons, nimsets.diffSets(regs[rb], regs[rc]).sons) + of opcSymDiffSet: + decodeBC(nkCurly) + move(regs[ra].sons, nimsets.symdiffSets(regs[rb], regs[rc]).sons) + of opcConcatStr: + decodeBC(nkStrLit) + regs[ra].strVal = getstr(regs[rb]) + for i in rb+1..rb+rc-1: + regs[ra].strVal.add getstr(regs[i]) + of opcEcho: + echo regs[ra].strVal + of opcContainsSet: + decodeBC(nkIntLit) + regs[ra].intVal = Ord(inSet(regs[rb], regs[rc])) + of opcSubStr: + decodeBC(nkStrLit) + inc pc + assert c.code[pc].opcode == opcSubStr + let rd = c.code[pc].regA + regs[ra].strVal = substr(regs[rb].strVal, regs[rc].intVal.int, + regs[rd].intVal.int) + of opcIndCall, opcIndCallAsgn: + # dest = call regStart, n; where regStart = fn, arg1, ... + let rb = instr.regB + let rc = instr.regC + let prc = regs[rb].sym + let newPc = compile(c, prc) + var newFrame = PStackFrame(prc: prc, comesFrom: pc, next: tos) + newSeq(newFrame.slots, prc.position) + if not isEmptyType(prc.typ.sons[0]): + newFrame.slots[0] = getNullValue(prc.typ.sons[0], prc.info) + # pass every parameter by var (the language definition allows this): + for i in 1 .. rc-1: + newFrame.slots[i] = regs[rb+i] + # allocate the temporaries: + for i in rc .. <prc.position: + newFrame.slots[i] = newNode(nkEmpty) + tos = newFrame + move(regs, newFrame.slots) + pc = newPc + of opcTJmp: + # jump Bx if A != 0 + let rbx = instr.regBx - wordExcess - 1 # -1 for the following 'inc pc' + if regs[ra].intVal != 0: + inc pc, rbx + of opcFJmp: + # jump Bx if A == 0 + let rbx = instr.regBx - wordExcess - 1 # -1 for the following 'inc pc' + if regs[ra].intVal == 0: + inc pc, rbx + of opcJmp: + # jump Bx + let rbx = instr.regBx - wordExcess - 1 # -1 for the following 'inc pc' + inc pc, rbx + of opcBranch: + # we know the next instruction is a 'jmp': + let branch = c.constants[instr.regBx-wordExcess] + var cond = false + for j in countup(0, sonsLen(branch) - 2): + if overlap(regs[ra], branch.sons[j]): + cond = true + break + assert c.code[pc+1].opcode == opcJmp + inc pc + # we skip this instruction so that the final 'inc(pc)' skips + # the following jump + if cond: + let instr2 = c.code[pc] + let rbx = instr2.regBx - wordExcess - 1 # -1 for the following 'inc pc' + inc pc, rbx + of opcTry: + let rbx = instr.regBx - wordExcess + tos.pushSafePoint(pc + rbx) + of opcExcept: + # just skip it; it's followed by a jump; + # we'll execute in the 'raise' handler + discard + of opcFinally: + # just skip it; it's followed by the code we need to execute anyway + tos.popSafePoint() + of opcFinallyEnd: + if c.currentExceptionA != nil: + # we are in a cleanup run: + pc = cleanupOnException(c, tos, regs)-1 + if pc < 0: + bailOut(c, tos) + return + of opcRaise: + let raised = regs[ra] + c.currentExceptionA = raised + c.exceptionInstr = pc + # -1 because of the following 'inc' + pc = cleanupOnException(c, tos, regs) - 1 + if pc < 0: + bailOut(c, tos) + return + of opcNew: + let typ = c.types[instr.regBx - wordExcess] + regs[ra] = getNullValue(typ, regs[ra].info) + of opcNewSeq: + let typ = c.types[instr.regBx - wordExcess] + inc pc + ensureKind(nkBracket) + let instr2 = c.code[pc] + let rb = instr2.regA + regs[ra].typ = typ + newSeq(regs[ra].sons, rb) + for i in 0 .. <rb: + regs[ra].sons[i] = getNullValue(typ, regs[ra].info) + of opcNewStr: + decodeB(nkStrLit) + regs[ra].strVal = newString(regs[rb].intVal.int) + of opcLdImmInt: + # dest = immediate value + decodeBx(nkIntLit) + regs[ra].intVal = rbx + of opcLdNull: + let typ = c.types[instr.regBx - wordExcess] + regs[ra] = getNullValue(typ, c.debug[pc]) + of opcLdConst: + regs[ra] = c.constants.sons[instr.regBx - wordExcess] + of opcNBindSym: + # trivial implementation: + let rb = instr.regB + regs[ra] = regs[rb].sons[1] + else: + InternalError(c.debug[pc], "unknown opcode " & $instr.opcode) + inc pc + +proc eval*(c: PCtx, n: PNode): PNode = + ## eval never returns nil! This simplifies the code a lot and + ## makes it faster too. + 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: + execute(c, start) + result = emptyNode + +proc myOpen(module: PSym): PPassContext = + #var c = newEvalContext(module, emRepl) + #c.features = {allowCast, allowFFI, allowInfiniteLoops} + #pushStackFrame(c, newStackFrame()) + result = newCtx() + +var oldErrorCount: int + +proc myProcess(c: PPassContext, n: PNode): PNode = + # don't eval errornous code: + if oldErrorCount == msgs.gErrorCounter: + result = eval(PCtx(c), n) + else: + result = n + oldErrorCount = msgs.gErrorCounter + +const vmPass* = makePass(myOpen, nil, myProcess, myProcess) + diff --git a/compiler/vmdef.nim b/compiler/vmdef.nim new file mode 100644 index 000000000..cbad16516 --- /dev/null +++ b/compiler/vmdef.nim @@ -0,0 +1,149 @@ +# +# +# The Nimrod 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-2 int32s in memory, it is a register based VM. + +import ast, passes, msgs, intsets + +const + byteExcess* = 128 # we use excess-K for immediates + wordExcess* = 32768 + +type + TRegister* = range[0..255] + TDest* = range[-1 .. 255] + TInstr* = distinct uint32 + + TInstrFormat = enum + ifABC, # three registers + ifABx, # A + extended B + + TOpcode* = enum + opcEof, # end of code + opcRet, # return + opcYldYoid, # yield with no value + opcYldVal, # yield with a value + + opcAsgnInt, + opcAsgnStr, + opcAsgnFloat, + opcAsgnRef, + opcAsgnComplex, + + opcLdArr, # a = b[c] + opcWrArr, # a[b] = c + opcWrArrRef, + opcLdObj, # a = b.c + opcWrObj, # a.b = c + opcWrObjRef, + opcAddr, + opcDeref, + opcWrStrIdx, + + opcAddInt, + opcAddImmInt, + opcSubInt, + opcSubImmInt, + opcLenSeq, + opcLenStr, + + opcIncl, opcExcl, opcCard, opcMulInt, opcDivInt, opcModInt, + opcAddFloat, opcSubFloat, opcMulFloat, opcDivFloat, opcShrInt, opcShlInt, + opcBitandInt, opcBitorInt, opcBitxorInt, opcAddu, opcSubu, opcMulu, + opcDivu, opcModu, opcEqInt, opcLeInt, opcLtInt, opcEqFloat, + opcLeFloat, opcLtFloat, opcLeu, opcLtu, opcEqRef, opcXor, + opcNot, opcUnaryMinusInt, opcUnaryMinusFloat, opcBitnotInt, + opcEqStr, opcLeStr, opcLtStr, opcEqSet, opcLeSet, opcLtSet, + opcMulSet, opcPlusSet, opcMinusSet, opcSymdiffSet, opcConcatStr, + opcContainsSet, opcRepr, opcSetLenStr, opcSetLenSeq, + opcSwap, opcIsNil, opcOf, + opcSubStr, opcConv, opcCast, opcQuit, opcReset, + + opcEcho, + opcIndCall, # dest = call regStart, n; where regStart = fn, arg1, ... + opcIndCallAsgn, # dest = call regStart, n; where regStart = fn, arg1, ... + + opcRaise, + opcNBindSym, # opcodes for the AST manipulation following + + opcTJmp, # jump Bx if A != 0 + opcFJmp, # jump Bx if A == 0 + opcJmp, # jump Bx + opcBranch, # branch for 'case' + opcTry, + opcExcept, + opcFinally, + opcFinallyEnd, + opcNew, + opcNewSeq, + opcNewStr, + opcLdNull, # dest = nullvalue(types[Bx]) + opcLdConst, # dest = constants[Bx] + opcLdGlobal, # dest = globals[Bx] + opcLdImmInt, # dest = immediate value + opcWrGlobal, + opcWrGlobalRef + + TBlock* = object + label*: PSym + fixups*: seq[TPosition] + + 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 + slotFixed, # slot is used for a fixed var/param/result + 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 (n.sons field is used) + + PProc* = ref object + blocks*: seq[TBlock] # blocks; temp data structure + slots*: array[TRegister, tuple[inUse: bool, kind: TSlotKind]] + maxSlots*: int + + PCtx* = ref TCtx + TCtx* = object of passes.TPassContext # code gen context + code*: seq[TInstr] + debug*: seq[TLineInfo] # line info for every instruction; kept separate + # to not slow down interpretation + jumpTargets*: TIntSet # we need to mark instructions that are + # jump targets; + # we must not optimize over a jump target and we + # need to generate a label for a jump target when + # producing a VM listing + 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 + + TPosition* = distinct int + +proc newCtx*(): PCtx = + PCtx(code: @[], debug: @[], jumpTargets: initIntSet(), + globals: newNode(nkStmtList), constants: newNode(nkStmtList), types: @[], + prc: PProc(blocks: @[])) + +const + firstABxInstr* = opcTJmp + largeInstrs* = { # instructions which use 2 int32s instead of 1: + opcSubstr, opcConv, opcCast, opcNewSeq, opcOf} + slotSomeTemp* = slotTempUnknown + +template opcode*(x: TInstr): TOpcode {.immediate.} = TOpcode(x.uint32 and 0xff'u32) +template regA*(x: TInstr): TRegister {.immediate.} = TRegister(x.uint32 shr 8'u32 and 0xff'u32) +template regB*(x: TInstr): TRegister {.immediate.} = TRegister(x.uint32 shr 16'u32 and 0xff'u32) +template regC*(x: TInstr): TRegister {.immediate.} = TRegister(x.uint32 shr 24'u32) +template regBx*(x: TInstr): int {.immediate.} = (x.uint32 shr 16'u32).int diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim new file mode 100644 index 000000000..048a89b8c --- /dev/null +++ b/compiler/vmgen.nim @@ -0,0 +1,1002 @@ +# +# +# The Nimrod Compiler +# (c) Copyright 2013 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## This module implements the code generator for the VM. + +import + unsigned, strutils, ast, astalgo, types, msgs, renderer, vmdef, + trees, intsets, rodread + +proc codeListing(c: PCtx, result: var string) = + # for debugging purposes + var i = 0 + while i < c.code.len: + if i in c.jumpTargets: result.addf("L$1:\n", i) + let x = c.code[i] + + let opc = opcode(x) + if opc < firstABxInstr: + result.addf("\t$#\tr$#, r$#, r$#", ($opc).substr(3), x.regA, + x.regB, x.regC) + else: + result.addf("\t$#\tr$#, r$#", ($opc).substr(3), x.regA, x.regBx) + result.add(" #") + result.add(toFileLine(c.debug[i])) + result.add("\n") + inc i + +proc gABC(ctx: PCtx; n: PNode; opc: TOpcode; a, b, c: TRegister = 0) = + assert opc.ord < 255 + let ins = (opc.uint32 or (a.uint32 shl 8'u32) or + (b.uint32 shl 16'u32) or + (c.uint32 shl 24'u32)).TInstr + ctx.code.add(ins) + ctx.debug.add(n.info) + +proc gABI(c: PCtx; n: PNode; opc: TOpcode; a, b: TRegister; imm: biggestInt) = + let ins = (opc.ord or a shl 8 or b shl 16 or (imm+byteExcess) shl 24).TInstr + c.code.add(ins) + c.debug.add(n.info) + +proc gABx(c: PCtx; n: PNode; opc: TOpcode; a: TRegister = 0; bx: int) = + let ins = (opc.ord or a shl 8 or (bx+wordExcess) shl 16).TInstr + c.code.add(ins) + c.debug.add(n.info) + +proc xjmp(c: PCtx; n: PNode; opc: TOpcode; a: TRegister = 0): TPosition = + #assert opc in {opcJmp, opcFJmp, opcTJmp} + gABx(c, n, opc, a, 0) + result = TPosition(c.code.len) + +proc genLabel(c: PCtx): TPosition = + result = TPosition(c.code.len) + c.jumpTargets.incl(c.code.len) + +proc jmp(c: PCtx, n: PNode, opc: TOpcode, p = TPosition(0)) = + let dist = p.int - c.code.len + InternalAssert(-0x7fff < dist and dist < 0x7fff) + gABx(c, n, opc, 0, dist) + +proc patch(c: PCtx, p: TPosition) = + # patch with current index + let p = p.int + let diff = c.code.len - p + c.jumpTargets.incl(c.code.len) + InternalAssert(-0x7fff < diff and diff < 0x7fff) + let oldInstr = c.code[p] + # opcode and regA stay the same: + c.code[p] = ((oldInstr.int and 0xffff) or (diff+wordExcess)).TInstr + +proc getSlotKind(t: PType): TSlotKind = + case t.skipTypes(abstractRange).kind + of tyBool, tyChar, tyEnum, tyOrdinal, tyInt..tyInt64, tyUInt..tyUInt64: + slotTempInt + of tyString, tyCString: + slotTempStr + of tyFloat..tyFloat128: + slotTempFloat + else: + slotTempComplex + +const + HighRegisterPressure = 40 + +proc getTemp(c: PCtx; typ: PType): TRegister = + let c = c.prc + # we prefer the same slot kind here for efficiency: + let k = typ.getSlotKind + for i in 0 .. c.maxSlots-1: + if c.slots[i].kind == k and not c.slots[i].inUse: + c.slots[i].inUse = true + return TRegister(i) + + # if register pressure is high, we re-use more aggressively: + if c.maxSlots >= HighRegisterPressure: + for i in 0 .. c.maxSlots-1: + if not c.slots[i].inUse: + c.slots[i] = (inUse: true, kind: k) + return TRegister(i) + result = TRegister(c.maxSlots) + c.slots[c.maxSlots] = (inUse: true, kind: k) + inc c.maxSlots + +proc freeTemp(c: PCtx; r: TRegister) = + let c = c.prc + if c.slots[r].kind >= slotSomeTemp: c.slots[r].inUse = false + +proc getTempRange(c: PCtx; n: int; kind: TSlotKind): TRegister = + # if register pressure is high, we re-use more aggressively: + let c = c.prc + if c.maxSlots >= HighRegisterPressure or c.maxSlots+n >= high(TRegister): + for i in 0 .. c.maxSlots-1: + block search: + if not c.slots[i].inUse: + for j in i+1 .. i+n-1: + if c.slots[j].inUse: break search + result = TRegister(i) + for k in result .. result+n-1: c.slots[k] = (inUse: true, kind: kind) + return + if c.maxSlots+n >= high(TRegister): + InternalError("cannot generate code; too many registers required") + result = TRegister(c.maxSlots) + inc c.maxSlots, n + for k in result .. result+n-1: c.slots[k] = (inUse: true, kind: kind) + +proc freeTempRange(c: PCtx; start: TRegister, n: int) = + for i in start .. start+n-1: c.freeTemp(TRegister(i)) + +template withTemp(tmp, typ: expr, body: stmt) {.immediate, dirty.} = + var tmp = getTemp(c, typ) + body + c.freeTemp(tmp) + +proc popBlock(c: PCtx; oldLen: int) = + for f in c.prc.blocks[oldLen].fixups: + c.patch(f) + c.prc.blocks.setLen(oldLen) + +template withBlock(labl: PSym; body: stmt) {.immediate, dirty.} = + var oldLen {.gensym.} = c.prc.blocks.len + c.prc.blocks.add TBlock(label: labl, fixups: @[]) + body + popBlock(c, oldLen) + +proc gen(c: PCtx; n: PNode; dest: var TDest) +proc gen(c: PCtx; n: PNode; dest: TRegister) = + var d: TDest = dest + gen(c, n, d) + InternalAssert d == dest + +proc gen(c: PCtx; n: PNode) = + var tmp: TDest = -1 + gen(c, n, tmp) + InternalAssert tmp < 0 + +proc genx(c: PCtx; n: PNode): TRegister = + var tmp: TDest = -1 + gen(c, n, tmp) + result = TRegister(tmp) + +proc genWhile(c: PCtx; n: PNode) = + # L1: + # cond, tmp + # fjmp tmp, L2 + # body + # jmp L1 + # L2: + let L1 = c.genLabel + withBlock(nil): + var tmp = c.genx(n.sons[0]) + let L2 = c.xjmp(n, opcFJmp, tmp) + c.freeTemp(tmp) + c.gen(n.sons[1]) + c.jmp(n, opcJmp, L1) + c.patch(L2) + +proc genBlock(c: PCtx; n: PNode; dest: var TDest) = + withBlock(n.sons[0].sym): + c.gen(n.sons[1], dest) + +proc genBreak(c: PCtx; n: PNode) = + let L1 = c.xjmp(n, opcJmp) + if n.sons[0].kind == nkSym: + for i in countdown(c.prc.blocks.len-1, 0): + if c.prc.blocks[i].label == n.sons[0].sym: + c.prc.blocks[i].fixups.add L1 + break + InternalError(n.info, "cannot find 'break' target") + else: + c.prc.blocks[c.prc.blocks.high].fixups.add L1 + +proc genIf(c: PCtx, n: PNode; dest: var TDest) = + # if (!expr1) goto L1; + # thenPart + # goto LEnd + # L1: + # if (!expr2) goto L2; + # thenPart2 + # goto LEnd + # L2: + # elsePart + # Lend: + if dest < 0 and not isEmptyType(n.typ): dest = getTemp(c, n.typ) + var endings: seq[TPosition] = @[] + for i in countup(0, len(n) - 1): + var it = n.sons[i] + if it.len == 2: + withTemp(tmp, it.sons[0].typ): + c.gen(it.sons[0], tmp) + let elsePos = c.xjmp(it.sons[0], opcFJmp, tmp) # if false + c.gen(n.sons[1], dest) # then part + if i < sonsLen(n)-1: + endings.add(c.xjmp(it.sons[1], opcJmp, 0)) + c.patch(elsePos) + else: + c.gen(it.sons[0], dest) + for endPos in endings: c.patch(endPos) + +proc genAndOr(c: PCtx; n: PNode; opc: TOpcode; dest: var TDest) = + # asgn dest, a + # tjmp|fjmp L1 + # asgn dest, b + # L1: + if dest < 0: dest = getTemp(c, n.typ) + c.gen(n.sons[0], dest) + let L1 = c.xjmp(n, opc) + c.gen(n.sons[1], dest) + c.patch(L1) + +proc rawGenLiteral(c: PCtx; n: PNode): int = + result = c.constants.len + c.constants.add n + InternalAssert result < 0x7fff + +proc sameConstant*(a, b: PNode): bool = + result = false + if a == b: + result = true + elif a != nil and b != nil and a.kind == b.kind: + case a.kind + of nkSym: result = a.sym == b.sym + of nkIdent: result = a.ident.id == b.ident.id + of nkCharLit..nkInt64Lit: result = a.intVal == b.intVal + of nkFloatLit..nkFloat64Lit: result = a.floatVal == b.floatVal + of nkStrLit..nkTripleStrLit: result = a.strVal == b.strVal + of nkEmpty, nkNilLit, nkType: result = true + else: + if sonsLen(a) == sonsLen(b): + for i in countup(0, sonsLen(a) - 1): + if not sameConstant(a.sons[i], b.sons[i]): return + result = true + +proc genLiteral(c: PCtx; n: PNode): int = + # types do not matter here: + for i in 0 .. <c.constants.len: + if sameConstant(c.constants[i], n): return i + result = rawGenLiteral(c, n) + +proc genCase(c: PCtx; n: PNode; dest: var TDest) = + # if (!expr1) goto L1; + # thenPart + # goto LEnd + # L1: + # if (!expr2) goto L2; + # thenPart2 + # goto LEnd + # L2: + # elsePart + # Lend: + if dest < 0 and not isEmptyType(n.typ): dest = getTemp(c, n.typ) + var endings: seq[TPosition] = @[] + withTemp(tmp, n.sons[0].typ): + c.gen(n.sons[0], tmp) + # branch tmp, codeIdx + # fjmp elseLabel + for i in 1 .. <n.len: + let it = n.sons[i] + if it.len == 1: + # else stmt: + c.gen(it.sons[0], dest) + else: + let b = rawGenLiteral(c, it) + c.gABx(it, opcBranch, tmp, b) + let elsePos = c.xjmp(it.lastSon, opcFJmp, tmp) + c.gen(it.lastSon, dest) + if i < sonsLen(n)-1: + endings.add(c.xjmp(it.lastSon, opcJmp, 0)) + c.patch(elsePos) + for endPos in endings: c.patch(endPos) + +proc genType(c: PCtx; typ: PType): int = + for i, t in c.types: + if sameType(t, typ): return i + result = c.types.len + c.types.add(typ) + internalAssert(result <= 0x7fff) + +proc genTry(c: PCtx; n: PNode; dest: var TDest) = + if dest < 0 and not isEmptyType(n.typ): dest = getTemp(c, n.typ) + var endings: seq[TPosition] = @[] + let elsePos = c.xjmp(n, opcTry, 0) + c.gen(n.sons[0], dest) + c.patch(elsePos) + for i in 1 .. <n.len: + let it = n.sons[i] + if it.kind != nkFinally: + var blen = len(it) + # first opcExcept contains the end label of the 'except' block: + let endExcept = c.xjmp(it, opcExcept, 0) + for j in countup(0, blen - 2): + assert(it.sons[j].kind == nkType) + let typ = it.sons[j].typ.skipTypes(abstractPtrs) + c.gABx(it, opcExcept, 0, c.genType(typ)) + if blen == 1: + # general except section: + c.gABx(it, opcExcept, 0, 0) + c.gen(it.lastSon, dest) + if i < sonsLen(n)-1: + endings.add(c.xjmp(it, opcJmp, 0)) + c.patch(endExcept) + for endPos in endings: c.patch(endPos) + let fin = lastSon(n) + # we always generate an 'opcFinally' as that pops the safepoint + # from the stack + c.gABx(fin, opcFinally, 0, 0) + if fin.kind == nkFinally: + c.gen(fin.sons[0], dest) + c.gABx(fin, opcFinallyEnd, 0, 0) + +proc genRaise(c: PCtx; n: PNode) = + let dest = genx(c, n.sons[0]) + c.gABC(n, opcRaise, dest) + c.freeTemp(dest) + +proc genReturn(c: PCtx; n: PNode) = + if n.sons[0].kind != nkEmpty: + gen(c, n.sons[0]) + c.gABC(n, opcRet) + +proc genCall(c: PCtx; n: PNode; dest: var TDest) = + if dest < 0 and not isEmptyType(n.typ): dest = getTemp(c, n.typ) + let x = c.getTempRange(n.len, slotTempUnknown) + for i in 0.. <n.len: + var r: TRegister = x+i + c.gen(n.sons[i], r) + if dest < 0: + c.gABC(n, opcIndCall, 0, x, n.len) + else: + c.gABC(n, opcIndCallAsgn, dest, x, n.len) + c.freeTempRange(x, n.len) + +proc genNew(c: PCtx; n: PNode) = + let dest = c.genx(n.sons[1]) + c.gABx(n, opcNew, dest, c.genType(n.sons[1].typ.skipTypes(abstractVar))) + c.freeTemp(dest) + +proc genNewSeq(c: PCtx; n: PNode) = + let dest = c.genx(n.sons[1]) + c.gABx(n, opcNewSeq, dest, c.genType(n.sons[1].typ.skipTypes(abstractVar))) + let tmp = c.genx(n.sons[2]) + c.gABx(n, opcNewSeq, tmp, 0) + c.freeTemp(dest) + c.freeTemp(tmp) + +proc genUnaryABC(c: PCtx; n: PNode; dest: var TDest; opc: TOpcode) = + let tmp = c.genx(n.sons[1]) + if dest < 0: dest = c.getTemp(n.typ) + c.gABC(n, opc, dest, tmp) + c.freeTemp(tmp) + +proc genBinaryABC(c: PCtx; n: PNode; dest: var TDest; opc: TOpcode) = + let + tmp = c.genx(n.sons[1]) + tmp2 = c.genx(n.sons[2]) + if dest < 0: dest = c.getTemp(n.typ) + c.gABC(n, opc, dest, tmp, tmp2) + c.freeTemp(tmp) + c.freeTemp(tmp2) + +proc genVarargsABC(c: PCtx; n: PNode; dest: var TDest; opc: TOpcode) = + if dest < 0: dest = getTemp(c, n.typ) + var x = c.getTempRange(n.len-1, slotTempStr) + for i in 1..n.len-1: + var r: TRegister = x+i-1 + c.gen(n.sons[i], r) + c.gABC(n, opc, dest, x, n.len-1) + c.freeTempRange(x, n.len) + +proc isInt8Lit(n: PNode): bool = + if n.kind in {nkCharLit..nkUInt64Lit}: + result = n.intVal >= low(int8) and n.intVal <= high(int8) + +proc isInt16Lit(n: PNode): bool = + if n.kind in {nkCharLit..nkUInt64Lit}: + result = n.intVal >= low(int16) and n.intVal <= high(int16) + +proc genAddSubInt(c: PCtx; n: PNode; dest: var TDest; opc: TOpcode) = + if n.sons[2].isInt8Lit: + let tmp = c.genx(n.sons[1]) + if dest < 0: dest = c.getTemp(n.typ) + c.gABI(n, succ(opc), dest, tmp, n.sons[2].intVal) + c.freeTemp(tmp) + else: + genBinaryABC(c, n, dest, opc) + +proc unused(n: PNode; x: TDest) {.inline.} = + if x >= 0: InternalError(n.info, "not unused") + +proc genConv(c: PCtx; n, arg: PNode; dest: var TDest; opc=opcConv) = + let tmp = c.genx(arg) + let t = genType(c, n.typ) + if dest < 0: dest = c.getTemp(n.typ) + c.gABC(n, opc, dest, tmp) + c.gABx(n, opc, 0, t) + c.freeTemp(tmp) + +proc genMagic(c: PCtx; n: PNode; dest: var TDest) = + let m = n.sons[0].sym.magic + case m + of mAnd: c.genAndOr(n, opcFJmp, dest) + of mOr: c.genAndOr(n, opcTJmp, dest) + of mUnaryLt: + let tmp = c.genx(n.sons[1]) + if dest < 0: dest = c.getTemp(n.typ) + c.gABI(n, opcSubImmInt, dest, tmp, 1) + c.freeTemp(tmp) + of mPred, mSubI, mSubI64: + c.genAddSubInt(n, dest, opcSubInt) + of mSucc, mAddI, mAddI64: + c.genAddSubInt(n, dest, opcAddInt) + of mInc, mDec: + unused(n, dest) + var d = c.genx(n.sons[1]).TDest + c.genAddSubInt(n, d, if m == mInc: opcAddInt else: opcSubInt) + c.freeTemp(d.TRegister) + of mOrd, mChr, mArrToSeq: c.gen(n.sons[1], dest) + of mNew, mNewFinalize: + unused(n, dest) + c.genNew(n) + of mNewSeq: + unused(n, dest) + c.genNewSeq(n) + of mNewString: + genUnaryABC(c, n, dest, opcNewStr) + of mNewStringOfCap: + # we ignore the 'cap' argument and translate it as 'newString(0)'. + # eval n.sons[1] for possible side effects: + var tmp = c.genx(n.sons[1]) + c.gABx(n, opcLdImmInt, tmp, 0) + if dest < 0: dest = c.getTemp(n.typ) + c.gABC(n, opcNewStr, dest, tmp) + c.freeTemp(tmp) + of mLengthOpenArray, mLengthArray, mLengthSeq: + genUnaryABC(c, n, dest, opcLenSeq) + of mLengthStr: + genUnaryABC(c, n, dest, opcLenStr) + of mIncl, mExcl: + unused(n, dest) + var d = c.genx(n.sons[1]) + var tmp = c.genx(n.sons[2]) + c.gABC(n, if m == mIncl: opcIncl else: opcExcl, d, tmp) + c.freeTemp(d) + c.freeTemp(tmp) + of mCard: genUnaryABC(c, n, dest, opcCard) + of mMulI, mMulI64: genBinaryABC(c, n, dest, opcMulInt) + of mDivI, mDivI64: genBinaryABC(c, n, dest, opcDivInt) + of mModI, mModI64: genBinaryABC(c, n, dest, opcModInt) + of mAddF64: genBinaryABC(c, n, dest, opcAddFloat) + of mSubF64: genBinaryABC(c, n, dest, opcSubFloat) + of mMulF64: genBinaryABC(c, n, dest, opcMulFloat) + of mDivF64: genBinaryABC(c, n, dest, opcDivFloat) + of mShrI, mShrI64: genBinaryABC(c, n, dest, opcShrInt) + of mShlI, mShlI64: genBinaryABC(c, n, dest, opcShlInt) + of mBitandI, mBitandI64: genBinaryABC(c, n, dest, opcBitandInt) + of mBitorI, mBitorI64: genBinaryABC(c, n, dest, opcBitorInt) + of mBitxorI, mBitxorI64: genBinaryABC(c, n, dest, opcBitxorInt) + of mAddU: genBinaryABC(c, n, dest, opcAddu) + of mSubU: genBinaryABC(c, n, dest, opcSubu) + of mMulU: genBinaryABC(c, n, dest, opcMulu) + of mDivU: genBinaryABC(c, n, dest, opcDivu) + of mModU: genBinaryABC(c, n, dest, opcModu) + of mEqI, mEqI64, mEqB, mEqEnum, mEqCh: + genBinaryABC(c, n, dest, opcEqInt) + of mLeI, mLeI64, mLeEnum, mLeCh, mLeB: + genBinaryABC(c, n, dest, opcLeInt) + of mLtI, mLtI64, mLtEnum, mLtCh, mLtB: + genBinaryABC(c, n, dest, opcLtInt) + of mEqF64: genBinaryABC(c, n, dest, opcEqFloat) + of mLeF64: genBinaryABC(c, n, dest, opcLeFloat) + of mLtF64: genBinaryABC(c, n, dest, opcLtFloat) + of mLePtr, mLeU, mLeU64: genBinaryABC(c, n, dest, opcLeu) + of mLtPtr, mLtU, mLtU64: genBinaryABC(c, n, dest, opcLtu) + of mEqProc, mEqRef, mEqUntracedRef: genBinaryABC(c, n, dest, opcEqRef) + of mXor: genBinaryABC(c, n, dest, opcXor) + of mNot: genUnaryABC(c, n, dest, opcNot) + of mUnaryMinusI, mUnaryMinusI64: genUnaryABC(c, n, dest, opcUnaryMinusInt) + of mUnaryMinusF64: genUnaryABC(c, n, dest, opcUnaryMinusFloat) + of mUnaryPlusI, mUnaryPlusI64, mUnaryPlusF64: gen(c, n.sons[1], dest) + of mBitnotI, mBitnotI64: genUnaryABC(c, n, dest, opcBitnotInt) + of mZe8ToI, mZe8ToI64, mZe16ToI, mZe16ToI64, mZe32ToI64, mZeIToI64, + mToU8, mToU16, mToU32, mToFloat, mToBiggestFloat, mToInt, + mToBiggestInt, mCharToStr, mBoolToStr, mIntToStr, mInt64ToStr, + mFloatToStr, mCStrToStr, mStrToStr, mEnumToStr: + genConv(c, n, n.sons[1], dest) + of mEqStr: genBinaryABC(c, n, dest, opcEqStr) + of mLeStr: genBinaryABC(c, n, dest, opcLeStr) + of mLtStr: genBinaryABC(c, n, dest, opcLtStr) + of mEqSet: genBinaryABC(c, n, dest, opcEqSet) + of mLeSet: genBinaryABC(c, n, dest, opcLeSet) + of mLtSet: genBinaryABC(c, n, dest, opcLtSet) + of mMulSet: genBinaryABC(c, n, dest, opcMulSet) + of mPlusSet: genBinaryABC(c, n, dest, opcPlusSet) + of mMinusSet: genBinaryABC(c, n, dest, opcMinusSet) + of mSymDiffSet: genBinaryABC(c, n, dest, opcSymdiffSet) + of mConStrStr: genVarargsABC(c, n, dest, opcConcatStr) + of mInSet: genBinaryABC(c, n, dest, opcContainsSet) + of mRepr: genUnaryABC(c, n, dest, opcRepr) + of mExit: + unused(n, dest) + var tmp = c.genx(n.sons[1]) + c.gABC(n, opcQuit, tmp) + c.freeTemp(tmp) + of mSetLengthStr, mSetLengthSeq: + unused(n, dest) + var d = c.genx(n.sons[1]) + var tmp = c.genx(n.sons[2]) + c.gABC(n, if m == mSetLengthStr: opcSetLenStr else: opcSetLenSeq, d, tmp) + c.freeTemp(tmp) + of mSwap: + unused(n, dest) + var d = c.genx(n.sons[1]) + var tmp = c.genx(n.sons[2]) + c.gABC(n, opcSwap, d, tmp) + c.freeTemp(tmp) + of mIsNil: genUnaryABC(c, n, dest, opcIsNil) + of mCopyStr: + if dest < 0: dest = c.getTemp(n.typ) + var + tmp1 = c.genx(n.sons[1]) + tmp2 = c.genx(n.sons[2]) + tmp3 = c.getTemp(n.sons[2].typ) + c.gABC(n, opcLenStr, tmp3, tmp1) + c.gABC(n, opcSubstr, dest, tmp1, tmp2) + c.gABC(n, opcSubstr, tmp3) + c.freeTemp(tmp1) + c.freeTemp(tmp2) + c.freeTemp(tmp3) + of mCopyStrLast: + if dest < 0: dest = c.getTemp(n.typ) + var + tmp1 = c.genx(n.sons[1]) + tmp2 = c.genx(n.sons[2]) + tmp3 = c.genx(n.sons[3]) + c.gABC(n, opcSubstr, dest, tmp1, tmp2) + c.gABC(n, opcSubstr, tmp3) + c.freeTemp(tmp1) + c.freeTemp(tmp2) + c.freeTemp(tmp3) + of mReset: + unused(n, dest) + var d = c.genx(n.sons[1]) + c.gABC(n, opcReset, d) + of mOf: + if dest < 0: dest = c.getTemp(n.typ) + var tmp = c.genx(n.sons[1]) + c.gABC(n, opcOf, dest, tmp) + c.gABx(n, opcOf, 0, c.genType(n.sons[2].typ.skipTypes(abstractPtrs))) + c.freeTemp(tmp) + of mSizeOf: + GlobalError(n.info, errCannotInterpretNodeX, renderTree(n)) + of mHigh: + if dest < 0: dest = c.getTemp(n.typ) + let tmp = c.genx(n.sons[1]) + if n.sons[1].typ.skipTypes(abstractVar).kind == tyString: + c.gABI(n, opcLenStr, dest, tmp, 1) + else: + c.gABI(n, opcLenSeq, dest, tmp, 1) + c.freeTemp(tmp) + of mEcho: + unused(n, dest) + for i in 1.. <n.len: + var d = c.genx(n.sons[i]) + c.gABC(n, opcEcho, d) + c.freeTemp(d) + of mAppendStrCh: InternalError(n.info, "cannot generate code for: " & $m) + of mAppendStrStr: InternalError(n.info, "cannot generate code for: " & $m) + of mAppendSeqElem: InternalError(n.info, "cannot generate code for: " & $m) + of mParseExprToAst: InternalError(n.info, "cannot generate code for: " & $m) + of mParseStmtToAst: InternalError(n.info, "cannot generate code for: " & $m) + of mExpandToAst: InternalError(n.info, "cannot generate code for: " & $m) + of mTypeTrait: InternalError(n.info, "cannot generate code for: " & $m) + of mIs: InternalError(n.info, "cannot generate code for: " & $m) + of mSlurp: InternalError(n.info, "cannot generate code for: " & $m) + of mStaticExec: InternalError(n.info, "cannot generate code for: " & $m) + of mNLen: InternalError(n.info, "cannot generate code for: " & $m) + of mNChild: InternalError(n.info, "cannot generate code for: " & $m) + of mNSetChild: InternalError(n.info, "cannot generate code for: " & $m) + of mNAdd: InternalError(n.info, "cannot generate code for: " & $m) + of mNAddMultiple: InternalError(n.info, "cannot generate code for: " & $m) + of mNDel: InternalError(n.info, "cannot generate code for: " & $m) + of mNKind: InternalError(n.info, "cannot generate code for: " & $m) + of mNIntVal: InternalError(n.info, "cannot generate code for: " & $m) + of mNFloatVal: InternalError(n.info, "cannot generate code for: " & $m) + of mNSymbol: InternalError(n.info, "cannot generate code for: " & $m) + of mNIdent: InternalError(n.info, "cannot generate code for: " & $m) + of mNGetType: InternalError(n.info, "cannot generate code for: " & $m) + of mNStrVal: InternalError(n.info, "cannot generate code for: " & $m) + of mNSetIntVal: InternalError(n.info, "cannot generate code for: " & $m) + of mNSetFloatVal: InternalError(n.info, "cannot generate code for: " & $m) + of mNSetSymbol: InternalError(n.info, "cannot generate code for: " & $m) + of mNSetIdent: InternalError(n.info, "cannot generate code for: " & $m) + of mNSetType: InternalError(n.info, "cannot generate code for: " & $m) + of mNSetStrVal: InternalError(n.info, "cannot generate code for: " & $m) + of mNNewNimNode: InternalError(n.info, "cannot generate code for: " & $m) + of mNCopyNimNode: InternalError(n.info, "cannot generate code for: " & $m) + of mNCopyNimTree: InternalError(n.info, "cannot generate code for: " & $m) + of mNBindSym: InternalError(n.info, "cannot generate code for: " & $m) + of mStrToIdent: InternalError(n.info, "cannot generate code for: " & $m) + of mIdentToStr: InternalError(n.info, "cannot generate code for: " & $m) + of mEqIdent: InternalError(n.info, "cannot generate code for: " & $m) + of mEqNimrodNode: InternalError(n.info, "cannot generate code for: " & $m) + of mNLineInfo: InternalError(n.info, "cannot generate code for: " & $m) + of mNHint: InternalError(n.info, "cannot generate code for: " & $m) + of mNWarning: InternalError(n.info, "cannot generate code for: " & $m) + of mNError: InternalError(n.info, "cannot generate code for: " & $m) + of mNCallSite: InternalError(n.info, "cannot generate code for: " & $m) + else: + # XXX get rid of these: mMinI, mMaxI, mMinI64, mMaxI64, mMinF64, mMaxF64 + # mGCref, mGCunref, mEqCString, mAbsI, mAbsI64, mAbsF64 + InternalError(n.info, "cannot generate code for: " & $m) + +const + atomicTypes = {tyBool, tyChar, + tyExpr, tyStmt, tyTypeDesc, + tyEnum, + tyOrdinal, + tyRange, + tyProc, + tyPointer, tyOpenArray, + tyString, tyCString, + tyInt, tyInt8, tyInt16, tyInt32, tyInt64, + tyFloat, tyFloat32, tyFloat64, tyFloat128, + tyUInt, tyUInt8, tyUInt16, tyUInt32, tyUInt64} + +proc requiresCopy(n: PNode): bool = + if n.typ.skipTypes(abstractInst).kind in atomicTypes: + result = false + elif n.kind in ({nkCurly, nkBracket, nkPar, nkObjConstr}+nkCallKinds): + result = false + else: + result = true + +proc unnecessaryIndirection(n: PNode): bool = + n.typ.skipTypes(abstractInst).kind == tyRef + +proc skipDeref(n: PNode): PNode = + if n.kind in {nkDerefExpr, nkHiddenDeref} and unnecessaryIndirection(n): + result = n.sons[0] + else: + result = n + +proc genAddrDeref(c: PCtx; n: PNode; dest: var TDest; opc: TOpcode) = + # a nop for certain types + if unnecessaryIndirection(n): + gen(c, n.sons[0], dest) + else: + let tmp = c.genx(n.sons[0]) + if dest < 0: dest = c.getTemp(n.typ) + gABC(c, n, opc, dest, tmp) + c.freeTemp(tmp) + +proc whichAsgnOpc(n: PNode): TOpcode = + case n.typ.skipTypes(abstractRange).kind + of tyBool, tyChar, tyEnum, tyOrdinal, tyInt..tyInt64, tyUInt..tyUInt64: + opcAsgnInt + of tyString, tyCString: + opcAsgnStr + of tyFloat..tyFloat128: + opcAsgnFloat + of tyRef, tyNil: + opcAsgnRef + else: + opcAsgnComplex + +proc isRef(t: PType): bool = t.skipTypes(abstractRange).kind == tyRef + +proc whichAsgnOpc(n: PNode; opc: TOpcode): TOpcode = + if isRef(n.typ): succ(opc) else: opc + +proc genAsgn(c: PCtx; dest: TDest; ri: PNode; requiresCopy: bool) = + let tmp = c.genx(ri) + assert dest >= 0 + gABC(c, ri, whichAsgnOpc(ri), dest, tmp) + c.freeTemp(tmp) + +proc genAsgn(c: PCtx; le, ri: PNode; requiresCopy: bool) = + case le.kind + of nkBracketExpr: + let dest = c.genx(le.sons[0]) + let idx = c.genx(le.sons[1]) + let tmp = c.genx(ri) + if le.typ.skipTypes(abstractVarRange).kind in {tyString, tyCString}: + c.gABC(le, opcWrStrIdx, dest, idx, tmp) + else: + c.gABC(le, whichAsgnOpc(le, opcWrArr), dest, idx, tmp) + c.freeTemp(tmp) + of nkDotExpr: + let dest = c.genx(le.sons[0]) + let idx = c.genx(le.sons[1]) + let tmp = c.genx(ri) + c.gABC(le, whichAsgnOpc(le, opcWrObj), dest, idx, tmp) + c.freeTemp(tmp) + of nkSym: + let s = le.sym + if sfGlobal in s.flags: + withTemp(tmp, le.typ): + gen(c, ri, tmp) + c.gABx(le, whichAsgnOpc(le, opcWrGlobal), tmp, s.position) + else: + InternalAssert s.position > 0 or (s.position == 0 and + s.kind in {skParam, skResult}) + var dest: TRegister = s.position + ord(s.kind == skParam) + gen(c, ri, dest) + else: + let dest = c.genx(le) + genAsgn(c, dest, ri, requiresCopy) + +proc genLit(c: PCtx; n: PNode; dest: var TDest) = + if dest < 0: dest = c.getTemp(n.typ) + let lit = genLiteral(c, n) + c.gABx(n, opcLdConst, dest, lit) + +proc genRdVar(c: PCtx; n: PNode; dest: var TDest) = + let s = n.sym + if sfGlobal in s.flags: + if dest < 0: dest = c.getTemp(s.typ) + if s.position == 0: + c.globals.add(s.ast) + s.position = c.globals.len + # XXX var g = codeHere() ? + c.gABx(n, opcLdGlobal, dest, s.position) + else: + if s.position > 0 or (s.position == 0 and s.kind in {skParam, skResult}): + if dest < 0: + dest = s.position + ord(s.kind == skParam) + else: + # we need to generate an assignment: + genAsgn(c, dest, n, c.prc.slots[dest].kind >= slotSomeTemp) + else: + InternalError(n.info, s.name.s & " " & $s.position) + +proc genAccess(c: PCtx; n: PNode; dest: var TDest; opc: TOpcode) = + let a = c.genx(n.sons[0]) + let b = c.genx(n.sons[1]) + if dest < 0: dest = c.getTemp(n.typ) + c.gABC(n, opc, dest, a, b) + c.freeTemp(a) + c.freeTemp(b) + +proc genObjAccess(c: PCtx; n: PNode; dest: var TDest) = + genAccess(c, n, dest, opcLdObj) + +proc genArrAccess(c: PCtx; n: PNode; dest: var TDest) = + genAccess(c, n, dest, opcLdArr) + +proc getNullValue*(typ: PType, info: TLineInfo): PNode +proc getNullValueAux(obj: PNode, result: PNode) = + case obj.kind + of nkRecList: + for i in countup(0, sonsLen(obj) - 1): getNullValueAux(obj.sons[i], result) + of nkRecCase: + getNullValueAux(obj.sons[0], result) + for i in countup(1, sonsLen(obj) - 1): + getNullValueAux(lastSon(obj.sons[i]), result) + of nkSym: + addSon(result, getNullValue(obj.sym.typ, result.info)) + else: InternalError(result.info, "getNullValueAux") + +proc getNullValue(typ: PType, info: TLineInfo): PNode = + var t = skipTypes(typ, abstractRange-{tyTypeDesc}) + result = emptyNode + case t.kind + of tyBool, tyEnum, tyChar, tyInt..tyInt64: + result = newNodeIT(nkIntLit, info, t) + of tyUInt..tyUInt64: + result = newNodeIT(nkUIntLit, info, t) + of tyFloat..tyFloat128: + result = newNodeIt(nkFloatLit, info, t) + of tyVar, tyPointer, tyPtr, tyCString, tySequence, tyString, tyExpr, + tyStmt, tyTypeDesc, tyProc, tyRef: + result = newNodeIT(nkNilLit, info, t) + of tyObject: + result = newNodeIT(nkPar, info, t) + getNullValueAux(t.n, result) + # initialize inherited fields: + var base = t.sons[0] + while base != nil: + getNullValueAux(skipTypes(base, skipPtrs).n, result) + base = base.sons[0] + of tyArray, tyArrayConstr: + result = newNodeIT(nkBracket, info, t) + for i in countup(0, int(lengthOrd(t)) - 1): + addSon(result, getNullValue(elemType(t), info)) + of tyTuple: + result = newNodeIT(nkPar, info, t) + for i in countup(0, sonsLen(t) - 1): + addSon(result, getNullValue(t.sons[i], info)) + of tySet: + result = newNodeIT(nkCurly, info, t) + else: InternalError("getNullValue: " & $t.kind) + +proc setSlot(c: PCtx; v: PSym) = + # XXX generate type initialization here? + if v.position == 0: + v.position = c.prc.maxSlots + c.prc.slots[v.position] = (inUse: true, kind: slotFixed) + inc c.prc.maxSlots + echo v.name.s, " has position ", v.position + +proc genVarSection(c: PCtx; n: PNode) = + for a in n: + if a.kind == nkCommentStmt: continue + #assert(a.sons[0].kind == nkSym) can happen for transformed vars + if a.kind == nkVarTuple: + let tmp = c.genx(a.lastSon) + for i in 0 .. a.len-3: + setSlot(c, a[i].sym) + # v = t[i] + var v: TDest = -1 + genRdVar(c, a[i], v) + c.gABC(n, opcLdObj, v, tmp, i) + # XXX globals? + c.freeTemp(tmp) + elif a.sons[0].kind == nkSym: + let s = a.sons[0].sym + if sfGlobal in s.flags: + if s.position == 0: + let sa = if s.ast.isNil: getNullValue(s.typ, a.info) else: s.ast + c.globals.add(sa) + s.position = c.globals.len + if a.sons[2].kind == nkEmpty: + when false: + withTemp(tmp, s.typ): + c.gABx(a, opcLdNull, tmp, c.genType(s.typ)) + c.gABx(a, whichAsgnOpc(a.sons[0], opcWrGlobal), tmp, s.position) + else: + let tmp = genx(c, a.sons[2]) + c.gABx(a, whichAsgnOpc(a.sons[0], opcWrGlobal), tmp, s.position) + c.freeTemp(tmp) + else: + setSlot(c, s) + if a.sons[2].kind == nkEmpty: + c.gABx(a, opcLdNull, s.position, c.genType(s.typ)) + else: + gen(c, a.sons[2], s.position.TRegister) + else: + # assign to a.sons[0]; happens for closures + if a.sons[2].kind == nkEmpty: + let tmp = genx(c, a.sons[0]) + c.gABx(a, opcLdNull, tmp, c.genType(a.sons[0].typ)) + c.freeTemp(tmp) + else: + genAsgn(c, a.sons[0], a.sons[2], true) + +proc genProc*(c: PCtx; s: PSym): int + +proc gen(c: PCtx; n: PNode; dest: var TDest) = + case n.kind + of nkSym: + let s = n.sym + case s.kind + of skVar, skForVar, skTemp, skLet, skParam, skResult: + genRdVar(c, n, dest) + of skProc, skConverter, skMacro, skMethod, skIterator: + genLit(c, n, dest) + of skConst: + gen(c, s.ast, dest) + of skEnumField: + if dest < 0: dest = c.getTemp(n.typ) + if s.position >= low(int16) and s.position <= high(int16): + c.gABx(n, opcLdImmInt, dest, s.position) + else: + var lit = genLiteral(c, newIntNode(nkIntLit, s.position)) + c.gABx(n, opcLdConst, dest, lit) + of skField: + InternalAssert dest < 0 + if s.position > high(dest): + InternalError(n.info, + "too large offset! cannot generate code for: " & s.name.s) + dest = s.position + else: + InternalError(n.info, "cannot generate code for: " & s.name.s) + of nkCallKinds: + if n.sons[0].kind == nkSym and n.sons[0].sym.magic != mNone: + genMagic(c, n, dest) + else: + genCall(c, n, dest) + of nkCharLit..nkInt64Lit: + if isInt16Lit(n): + if dest < 0: dest = c.getTemp(n.typ) + c.gABx(n, opcLdImmInt, dest, n.intVal.int) + else: + genLit(c, n, dest) + of nkUIntLit..nkNilLit: genLit(c, n, dest) + of nkAsgn, nkFastAsgn: + unused(n, dest) + genAsgn(c, n.sons[0], n.sons[1], n.kind == nkAsgn) + of nkDotExpr: genObjAccess(c, n, dest) + of nkBracketExpr: genArrAccess(c, n, dest) + of nkDerefExpr, nkHiddenDeref: genAddrDeref(c, n, dest, opcDeref) + of nkAddr, nkHiddenAddr: genAddrDeref(c, n, dest, opcAddr) + of nkWhenStmt, nkIfStmt, nkIfExpr: genIf(c, n, dest) + of nkCaseStmt: genCase(c, n, dest) + of nkWhileStmt: + unused(n, dest) + genWhile(c, n) + of nkBlockExpr, nkBlockStmt: genBlock(c, n, dest) + of nkReturnStmt: + unused(n, dest) + genReturn(c, n) + of nkRaiseStmt: + unused(n, dest) + genRaise(c, n) + of nkBreakStmt: + unused(n, dest) + genBreak(c, n) + of nkTryStmt: genTry(c, n, dest) + of nkStmtList: + unused(n, dest) + for x in n: gen(c, x) + of nkStmtListExpr: + let L = n.len-1 + for i in 0 .. <L: gen(c, n.sons[i]) + gen(c, n.sons[L], dest) + of nkDiscardStmt: + unused(n, dest) + gen(c, n.sons[0]) + of nkHiddenStdConv, nkHiddenSubConv, nkConv: + genConv(c, n, n.sons[1], dest) + of nkVarSection, nkLetSection: + unused(n, dest) + genVarSection(c, n) + of declarativeDefs: + unused(n, dest) + of nkLambdaKinds: + let s = n.sons[namePos].sym + discard genProc(c, s) + genLit(c, n.sons[namePos], dest) + of nkEmpty, nkCommentStmt, nkTypeSection, nkConstSection, nkPragma, + nkTemplateDef, nkIncludeStmt, nkImportStmt: + unused(n, dest) + else: + #of nkCurly, nkBracket, nkPar: + InternalError n.info, "too implement " & $n.kind + +proc genStmt*(c: PCtx; n: PNode): int = + result = c.code.len + var d: TDest = -1 + gen(c, n, d) + let last = c.code.len-1 + if last >= 0 and c.code[last].opcode == opcEof: + # since we can re-use the EOF nothing happened: + result = last + else: + gABC(c, n, opcEof) + InternalAssert d < 0 + +proc genParams(c: PCtx; params: PNode) = + # res.sym.position is already 0 + c.prc.slots[0] = (inUse: true, kind: slotFixed) + for i in 1.. <params.len: + let param = params.sons[i].sym + c.prc.slots[i] = (inUse: true, kind: slotFixed) + c.prc.maxSlots = max(params.len, 1) + +proc genProc(c: PCtx; s: PSym): int = + let x = s.ast.sons[optimizedCodePos] + if x.kind == nkEmpty: + result = c.code.len+1 # skip the jump instruction + s.ast.sons[optimizedCodePos] = newIntNode(nkIntLit, result) + # thanks to the jmp we can add top level statements easily and also nest + # procs easily: + let body = s.getBody + let procStart = c.xjmp(body, opcJmp, 0) + var p = PProc(blocks: @[]) + let oldPrc = c.prc + c.prc = p + # iterate over the parameters and allocate space for them: + genParams(c, s.typ.n) + gen(c, body) + # generate final 'return' statement: + c.gABC(body, opcRet) + c.patch(procStart) + s.position = c.prc.maxSlots + c.prc = oldPrc + else: + result = x.intVal.int |