# # # 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, magicsys, options when hasFFI: import evalffi proc codeListing(c: PCtx, result: var string, start=0) = # first iteration: compute all necessary labels: var jumpTargets = initIntSet() for i in start.. < c.code.len: let x = c.code[i] if x.opcode in relativeJumps: jumpTargets.incl(i+x.regBx-wordExcess) # for debugging purposes var i = start while i < c.code.len: if i in 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) elif opc in relativeJumps: result.addf("\t$#\tr$#, L$#", ($opc).substr(3), x.regA, i+x.regBx-wordExcess) elif opc in {opcLdConst, opcAsgnConst}: result.addf("\t$#\tr$#, $#", ($opc).substr(3), x.regA, c.constants[x.regBx-wordExcess].renderTree) else: result.addf("\t$#\tr$#, $#", ($opc).substr(3), x.regA, x.regBx-wordExcess) result.add("\t#") result.add(toFileLine(c.debug[i])) result.add("\n") inc i proc echoCode*(c: PCtx, start=0) {.deprecated.} = var buf = "" codeListing(c, buf, start) echo buf 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.uint32 or (a.uint32 shl 8'u32) or (b.uint32 shl 16'u32) or (imm+byteExcess).uint32 shl 24'u32).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.uint32 or a.uint32 shl 8'u32 or (bx+wordExcess).uint32 shl 16'u32).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} result = TPosition(c.code.len) gABx(c, n, opc, a, 0) proc genLabel(c: PCtx): TPosition = result = TPosition(c.code.len) #c.jumpTargets.incl(c.code.len) proc jmpBack(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.uint32 and 0xffff'u32).uint32 or uint32(diff+wordExcess) shl 16'u32).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. Unfortunately for # discardable return types we may not know the desired type. This can happen # for e.g. mNAdd[Multiple]: let k = if typ.isNil: slotTempComplex else: 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 getGlobalSlot(c: PCtx; n: PNode; s: PSym): TRegister = let p = c.prc for i in 0 .. p.maxSlots-1: if p.globals[i] == s.id: return TRegister(i) result = TRegister(p.maxSlots) p.slots[p.maxSlots] = (inUse: true, kind: slotFixedVar) p.globals[p.maxSlots] = s.id inc p.maxSlots # XXX this is still not correct! We need to load the global in a proc init # section, otherwise control flow could lead to a usage before it's been # loaded. c.gABx(n, opcGlobalAlias, result, s.position) # XXX add some internal asserts here 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-n: if not c.slots[i].inUse: block search: 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) #if n.typ.isEmptyType: InternalAssert tmp < 0 proc genx(c: PCtx; n: PNode): TRegister = var tmp: TDest = -1 gen(c, n, tmp) result = TRegister(tmp) proc clearDest(n: PNode; dest: var TDest) {.inline.} = if isEmptyType(n.typ): dest = -1 proc isNotOpr(n: PNode): bool = n.kind in nkCallKinds and n.sons[0].kind == nkSym and n.sons[0].sym.magic == mNot proc isTrue(n: PNode): bool = n.kind == nkSym and n.sym.kind == skEnumField and n.sym.position != 0 or n.kind == nkIntLit and n.intVal != 0 proc genWhile(c: PCtx; n: PNode) = # L1: # cond, tmp # fjmp tmp, L2 # body # jmp L1 # L2: let L1 = c.genLabel withBlock(nil): if isTrue(n.sons[0]): c.gen(n.sons[1]) c.jmpBack(n, opcJmp, L1) elif isNotOpr(n.sons[0]): var tmp = c.genx(n.sons[0].sons[1]) let L2 = c.xjmp(n, opcTJmp, tmp) c.freeTemp(tmp) c.gen(n.sons[1]) c.jmpBack(n, opcJmp, L1) c.patch(L2) else: var tmp = c.genx(n.sons[0]) let L2 = c.xjmp(n, opcFJmp, tmp) c.freeTemp(tmp) c.gen(n.sons[1]) c.jmpBack(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) clearDest(n, dest) proc genBreak(c: PCtx; n: PNode) = let L1 = c.xjmp(n, opcJmp) if n.sons[0].kind == nkSym: #echo cast[int](n.sons[0].sym) 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 return 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): var elsePos: TPosition if isNotOpr(it.sons[0]): c.gen(it.sons[0].sons[1], tmp) elsePos = c.xjmp(it.sons[0].sons[1], opcTJmp, tmp) # if true else: c.gen(it.sons[0], tmp) elsePos = c.xjmp(it.sons[0], opcFJmp, tmp) # if false c.gen(it.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) clearDest(n, dest) 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[1], dest) let L1 = c.xjmp(n, opc, dest) c.gen(n.sons[2], 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 nkType: result = a.typ == b.typ of nkEmpty, nkNilLit: 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 .. = 0: #debug(n) internalError(n.info, "not unused") 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 not isEmptyType(n.typ): if dest < 0: dest = getTemp(c, n.typ) else: unused(n, dest) 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 .. = fntyp.len: internalAssert tfVarargs in fntyp.flags c.gABx(n, opcSetType, r, c.genType(n.sons[i].typ)) 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) clearDest(n, dest) proc needsAsgnPatch(n: PNode): bool = n.kind in {nkBracketExpr, nkDotExpr, nkCheckedFieldExpr} proc genAsgnPatch(c: PCtx; le: PNode, value: TRegister) = case le.kind of nkBracketExpr: let dest = c.genx(le.sons[0]) let idx = c.genx(le.sons[1]) c.gABC(le, opcWrArrRef, dest, idx, value) of nkDotExpr, nkCheckedFieldExpr: # XXX field checks here let left = if le.kind == nkDotExpr: le else: le.sons[0] let dest = c.genx(left.sons[0]) let idx = c.genx(left.sons[1]) c.gABC(left, opcWrObjRef, dest, idx, value) else: discard proc genNew(c: PCtx; n: PNode) = let dest = if needsAsgnPatch(n.sons[1]): c.getTemp(n.sons[1].typ) else: c.genx(n.sons[1]) # we use the ref's base type here as the VM conflates 'ref object' # and 'object' since internally we already have a pointer. c.gABx(n, opcNew, dest, c.genType(n.sons[1].typ.skipTypes(abstractVar).sons[0])) c.genAsgnPatch(n.sons[1], dest) c.freeTemp(dest) proc genNewSeq(c: PCtx; n: PNode) = let dest = if needsAsgnPatch(n.sons[1]): c.getTemp(n.sons[1].typ) else: 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(tmp) c.genAsgnPatch(n.sons[1], dest) c.freeTemp(dest) 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 genUnaryABI(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.gABI(n, opc, dest, tmp, 0) 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 genSetType(c: PCtx; n: PNode; dest: TRegister) = let t = skipTypes(n.typ, abstractInst) if t.kind == tySet: c.gABx(n, opcSetType, dest, c.genType(t)) proc genBinarySet(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.genSetType(n.sons[1], tmp) c.genSetType(n.sons[2], tmp2) c.gABC(n, opc, dest, tmp, tmp2) c.freeTemp(tmp) c.freeTemp(tmp2) proc genBinaryStmt(c: PCtx; n: PNode; opc: TOpcode) = let dest = c.genx(n.sons[1]) tmp = c.genx(n.sons[2]) c.gABC(n, opc, dest, tmp, 0) c.freeTemp(tmp) proc genUnaryStmt(c: PCtx; n: PNode; opc: TOpcode) = let tmp = c.genx(n.sons[1]) c.gABC(n, opc, tmp, 0, 0) c.freeTemp(tmp) 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 genConv(c: PCtx; n, arg: PNode; dest: var TDest; opc=opcConv) = let tmp = c.genx(arg) c.gABx(n, opcSetType, tmp, genType(c, arg.typ)) if dest < 0: dest = c.getTemp(n.typ) c.gABC(n, opc, dest, tmp) c.gABx(n, opc, 0, genType(c, n.typ)) c.freeTemp(tmp) proc genCard(c: PCtx; n: PNode; dest: var TDest) = let tmp = c.genx(n.sons[1]) if dest < 0: dest = c.getTemp(n.typ) c.genSetType(n.sons[1], tmp) c.gABC(n, opcCard, dest, tmp) 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: genUnaryABI(c, n, dest, opcLenSeq) of mLengthStr: genUnaryABI(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.genSetType(n.sons[1], d) c.gABC(n, if m == mIncl: opcIncl else: opcExcl, d, tmp) c.freeTemp(d) c.freeTemp(tmp) of mCard: genCard(c, n, dest) 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, mEqCString: 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: genBinarySet(c, n, dest, opcEqSet) of mLeSet: genBinarySet(c, n, dest, opcLeSet) of mLtSet: genBinarySet(c, n, dest, opcLtSet) of mMulSet: genBinarySet(c, n, dest, opcMulSet) of mPlusSet: genBinarySet(c, n, dest, opcPlusSet) of mMinusSet: genBinarySet(c, n, dest, opcMinusSet) of mSymDiffSet: genBinarySet(c, n, dest, opcSymdiffSet) of mConStrStr: genVarargsABC(c, n, dest, opcConcatStr) of mInSet: genBinarySet(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, mIs: if dest < 0: dest = c.getTemp(n.typ) var tmp = c.genx(n.sons[1]) var idx = c.getTemp(getSysType(tyInt)) var typ = n.sons[2].typ if m == mOf: typ = typ.skipTypes(abstractPtrs) c.gABx(n, opcLdImmInt, idx, c.genType(typ)) c.gABC(n, if m == mOf: opcOf else: opcIs, dest, tmp, idx) c.freeTemp(tmp) c.freeTemp(idx) 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) let x = c.getTempRange(n.len-1, slotTempUnknown) for i in 1.. = 0 gABC(c, ri, whichAsgnOpc(ri), dest, tmp) c.freeTemp(tmp) template isGlobal(s: PSym): bool = sfGlobal in s.flags and s.kind != skForVar 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.sons[0].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, nkCheckedFieldExpr: # XXX field checks here let left = if le.kind == nkDotExpr: le else: le.sons[0] let dest = c.genx(left.sons[0]) let idx = c.genx(left.sons[1]) let tmp = c.genx(ri) c.gABC(left, whichAsgnOpc(left, opcWrObj), dest, idx, tmp) c.freeTemp(tmp) of nkSym: let s = le.sym if s.isGlobal: 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) = var opc = opcLdConst if dest < 0: dest = c.getTemp(n.typ) elif c.prc.slots[dest].kind == slotFixedVar: opc = opcAsgnConst let lit = genLiteral(c, n) c.gABx(n, opc, dest, lit) proc genTypeLit(c: PCtx; t: PType; dest: var TDest) = var n = newNode(nkType) n.typ = t genLit(c, n, dest) proc importcSym(c: PCtx; info: TLineInfo; s: PSym) = when hasFFI: if allowFFI in c.features: c.globals.add(importcSymbol(s)) s.position = c.globals.len else: localError(info, errGenerated, "VM is not allowed to 'importc'") else: localError(info, errGenerated, "cannot 'importc' variable at compile time") proc cannotEval(n: PNode) {.noinline.} = globalError(n.info, errGenerated, "cannot evaluate at compile time: " & n.renderTree) proc genGlobalInit(c: PCtx; n: PNode; s: PSym) = c.globals.add(emptyNode.copyNode) s.position = c.globals.len # This is rather hard to support, due to the laziness of the VM code # generator. See tests/compile/tmacro2 for why this is necesary: # var decls{.compileTime.}: seq[PNimrodNode] = @[] c.gABx(n, opcGlobalOnce, 0, s.position) let tmp = c.genx(s.ast) c.gABx(n, whichAsgnOpc(n, opcWrGlobal), tmp, s.position) c.freeTemp(tmp) proc genRdVar(c: PCtx; n: PNode; dest: var TDest) = let s = n.sym if s.isGlobal: if sfCompileTime in s.flags or c.mode == emRepl: discard else: cannotEval(n) if s.position == 0: if sfImportc in s.flags: c.importcSym(n.info, s) else: genGlobalInit(c, n, s) if dest < 0: dest = c.getGlobalSlot(n, s) #c.gABx(n, opcAliasGlobal, dest, s.position) else: 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: # see tests/t99bott for an example that triggers it: cannotEval(n) #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 genCheckedObjAccess(c: PCtx; n: PNode; dest: var TDest) = # XXX implement field checks! genAccess(c, n.sons[0], dest, opcLdObj) proc genArrAccess(c: PCtx; n: PNode; dest: var TDest) = if n.sons[0].typ.skipTypes(abstractVarRange).kind in {tyString, tyCString}: genAccess(c, n, dest, opcLdStrIdx) else: 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, tyStatic, tyRef: result = newNodeIT(nkNilLit, info, t) of tyProc: if t.callConv != ccClosure: result = newNodeIT(nkNilLit, info, t) else: result = newNodeIT(nkPar, info, t) result.add(newNodeIT(nkNilLit, info, t)) result.add(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: if v.kind == skLet: slotFixedLet else: slotFixedVar) inc c.prc.maxSlots 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 s.isGlobal: if s.position == 0: if sfImportc in s.flags: c.importcSym(a.info, s) else: let sa = if s.ast.isNil: getNullValue(s.typ, a.info) else: s.ast c.globals.add(sa) s.position = c.globals.len # "Once support" is unnecessary here 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 genArrayConstr(c: PCtx, n: PNode, dest: var TDest) = if dest < 0: dest = c.getTemp(n.typ) c.gABx(n, opcLdNull, dest, c.genType(n.typ)) if n.len > 0: let intType = getSysType(tyInt) var tmp = getTemp(c, intType) c.gABx(n, opcLdNull, tmp, c.genType(intType)) for x in n: let a = c.genx(x) c.gABC(n, whichAsgnOpc(x, opcWrArr), dest, tmp, a) c.gABI(n, opcAddImmInt, tmp, tmp, 1) c.freeTemp(a) c.freeTemp(tmp) proc genSetConstr(c: PCtx, n: PNode, dest: var TDest) = if dest < 0: dest = c.getTemp(n.typ) c.gABx(n, opcLdNull, dest, c.genType(n.typ)) for x in n: if x.kind == nkRange: let a = c.genx(x.sons[0]) let b = c.genx(x.sons[1]) c.gABC(n, opcInclRange, dest, a, b) c.freeTemp(b) c.freeTemp(a) else: let a = c.genx(x) c.gABC(n, opcIncl, dest, a) c.freeTemp(a) proc genObjConstr(c: PCtx, n: PNode, dest: var TDest) = if dest < 0: dest = c.getTemp(n.typ) let t = n.typ.skipTypes(abstractRange) if t.kind == tyRef: c.gABx(n, opcNew, dest, c.genType(t.sons[0])) else: c.gABx(n, opcLdNull, dest, c.genType(n.typ)) for i in 1.. = 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 of skType: genTypeLit(c, s.typ, dest) 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..pred(nkNilLit): genLit(c, n, dest) of nkNilLit: if not n.typ.isEmptyType: genLit(c, n, dest) else: unused(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 nkCheckedFieldExpr: genCheckedObjAccess(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 .. = 0: gABC(c, n, whichAsgnOpc(n), dest, tmp0) c.freeTemp(tmp0) else: dest = tmp0 of nkEmpty, nkCommentStmt, nkTypeSection, nkConstSection, nkPragma, nkTemplateDef, nkIncludeStmt, nkImportStmt, nkFromStmt: unused(n, dest) of nkStringToCString, nkCStringToString: gen(c, n.sons[0], dest) of nkBracket: genArrayConstr(c, n, dest) of nkCurly: genSetConstr(c, n, dest) of nkObjConstr: genObjConstr(c, n, dest) of nkPar, nkClosure: genTupleConstr(c, n, dest) of nkCast: if allowCast in c.features: genConv(c, n, n.sons[1], dest, opcCast) else: localError(n.info, errGenerated, "VM is not allowed to 'cast'") else: internalError n.info, "too implement " & $n.kind proc removeLastEof(c: PCtx) = let last = c.code.len-1 if last >= 0 and c.code[last].opcode == opcEof: # overwrite last EOF: assert c.code.len == c.debug.len c.code.setLen(last) c.debug.setLen(last) proc genStmt*(c: PCtx; n: PNode): int = c.removeLastEof result = c.code.len var d: TDest = -1 c.gen(n, d) c.gABC(n, opcEof) if d >= 0: internalError(n.info, "some destination set") proc genExpr*(c: PCtx; n: PNode, requiresValue = true): int = c.removeLastEof result = c.code.len var d: TDest = -1 c.gen(n, d) if d < 0: if requiresValue: internalError(n.info, "no destination set") d = 0 c.gABC(n, opcEof, d) proc genParams(c: PCtx; params: PNode) = # res.sym.position is already 0 c.prc.slots[0] = (inUse: true, kind: slotFixedVar) for i in 1.. 0: d = d + c.code[d].jmpDiff dec iters if c.code[d].opcode == opcRet: # optimize 'jmp to ret' to 'ret' here c.code[i] = c.code[d] elif d != i + c.code[i].jmpDiff: c.finalJumpTarget(i, d - i) else: discard proc genProc(c: PCtx; s: PSym): int = let x = s.ast.sons[optimizedCodePos] if x.kind == nkEmpty: #if s.name.s == "outterMacro" or s.name.s == "innerProc": # echo "GENERATING CODE FOR ", s.name.s let last = c.code.len-1 var eofInstr: TInstr if last >= 0 and c.code[last].opcode == opcEof: eofInstr = c.code[last] c.code.setLen(last) c.debug.setLen(last) #c.removeLastEof 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) if tfCapturesEnv in s.typ.flags: #let env = s.ast.sons[paramsPos].lastSon.sym #assert env.position == 2 c.prc.slots[c.prc.maxSlots] = (inUse: true, kind: slotFixedLet) inc c.prc.maxSlots gen(c, body) # generate final 'return' statement: c.gABC(body, opcRet) c.patch(procStart) c.gABC(body, opcEof, eofInstr.regA) c.optimizeJumps(result) s.offset = c.prc.maxSlots #if s.name.s == "rawGet": # c.echoCode(result) # echo renderTree(body) c.prc = oldPrc else: c.prc.maxSlots = s.offset result = x.intVal.int