# # # The Nim Compiler # (c) Copyright 2015 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. # Important things to remember: # - The VM does not distinguish between definitions ('var x = y') and # assignments ('x = y'). For simple data types that fit into a register # this doesn't matter. However it matters for strings and other complex # types that use the 'node' field; the reason is that slots are # re-used in a register based VM. Example: # #.. code-block:: nim # let s = a & b # no matter what, create fresh node # s = a & b # no matter what, keep the node # # Also *stores* into non-temporary memory need to perform deep copies: # a.b = x.y # We used to generate opcAsgn for the *load* of 'x.y' but this is clearly # wrong! We need to produce opcAsgn (the copy) for the *store*. This also # solves the opcLdConst vs opcAsgnConst issue. Of course whether we need # this copy depends on the involved types. import tables import strutils, ast, types, msgs, renderer, vmdef, intsets, magicsys, options, lowerings, lineinfos, transf, astmsgs from modulegraphs import getBody when defined(nimCompilerStacktraceHints): import std/stackframes const debugEchoCode* = defined(nimVMDebug) when debugEchoCode: import std/private/asciitables when hasFFI: import evalffi type TGenFlag = enum gfNode # Affects how variables are loaded - always loads as rkNode gfNodeAddr # Affects how variables are loaded - always loads as rkNodeAddr gfIsParam # do not deepcopy parameters, they are immutable TGenFlags = set[TGenFlag] proc debugInfo(c: PCtx; info: TLineInfo): string = result = toFileLineCol(c.config, info) proc codeListing(c: PCtx, result: var string, start=0; last = -1) = ## for debugging purposes # first iteration: compute all necessary labels: var jumpTargets = initIntSet() let last = if last < 0: c.code.len-1 else: min(last, c.code.len-1) for i in start..last: let x = c.code[i] if x.opcode in relativeJumps: jumpTargets.incl(i+x.regBx-wordExcess) template toStr(opc: TOpcode): string = ($opc).substr(3) result.add "code listing:\n" var i = start while i <= last: if i in jumpTargets: result.addf("L$1:\n", i) let x = c.code[i] result.add($i) let opc = opcode(x) if opc in {opcIndCall, opcIndCallAsgn}: result.addf("\t$#\tr$#, r$#, nargs:$#", opc.toStr, x.regA, x.regB, x.regC) elif opc in {opcConv, opcCast}: let y = c.code[i+1] let z = c.code[i+2] result.addf("\t$#\tr$#, r$#, $#, $#", opc.toStr, x.regA, x.regB, c.types[y.regBx-wordExcess].typeToString, c.types[z.regBx-wordExcess].typeToString) inc i, 2 elif opc < firstABxInstr: result.addf("\t$#\tr$#, r$#, r$#", opc.toStr, x.regA, x.regB, x.regC) elif opc in relativeJumps + {opcTry}: result.addf("\t$#\tr$#, L$#", opc.toStr, x.regA, i+x.regBx-wordExcess) elif opc in {opcExcept}: let idx = x.regBx-wordExcess result.addf("\t$#\t$#, $#", opc.toStr, x.regA, $idx) elif opc in {opcLdConst, opcAsgnConst}: let idx = x.regBx-wordExcess result.addf("\t$#\tr$#, $# ($#)", opc.toStr, x.regA, c.constants[idx].renderTree, $idx) elif opc in {opcMarshalLoad, opcMarshalStore}: let y = c.code[i+1] result.addf("\t$#\tr$#, r$#, $#", opc.toStr, x.regA, x.regB, c.types[y.regBx-wordExcess].typeToString) inc i else: result.addf("\t$#\tr$#, $#", opc.toStr, x.regA, x.regBx-wordExcess) result.add("\t# ") result.add(debugInfo(c, c.debug[i])) result.add("\n") inc i when debugEchoCode: result = result.alignTable proc echoCode*(c: PCtx; start=0; last = -1) {.deprecated.} = var buf = "" codeListing(c, buf, start, last) echo buf proc gABC(ctx: PCtx; n: PNode; opc: TOpcode; a, b, c: TRegister = 0) = ## Takes the registers `b` and `c`, applies the operation `opc` to them, and ## stores the result into register `a` ## The node is needed for debug information assert opc.ord < 255 let ins = (opc.TInstrType or (a.TInstrType shl regAShift) or (b.TInstrType shl regBShift) or (c.TInstrType shl regCShift)).TInstr when false: if ctx.code.len == 43: writeStackTrace() echo "generating ", opc ctx.code.add(ins) ctx.debug.add(n.info) proc gABI(c: PCtx; n: PNode; opc: TOpcode; a, b: TRegister; imm: BiggestInt) = # Takes the `b` register and the immediate `imm`, applies the operation `opc`, # and stores the output value into `a`. # `imm` is signed and must be within [-128, 127] if imm >= -128 and imm <= 127: let ins = (opc.TInstrType or (a.TInstrType shl regAShift) or (b.TInstrType shl regBShift) or (imm+byteExcess).TInstrType shl regCShift).TInstr c.code.add(ins) c.debug.add(n.info) else: localError(c.config, n.info, "VM: immediate value does not fit into an int8") proc gABx(c: PCtx; n: PNode; opc: TOpcode; a: TRegister = 0; bx: int) = # Applies `opc` to `bx` and stores it into register `a` # `bx` must be signed and in the range [regBxMin, regBxMax] when false: if c.code.len == 43: writeStackTrace() echo "generating ", opc if bx >= regBxMin-1 and bx <= regBxMax: let ins = (opc.TInstrType or a.TInstrType shl regAShift or (bx+wordExcess).TInstrType shl regBxShift).TInstr c.code.add(ins) c.debug.add(n.info) else: localError(c.config, n.info, "VM: immediate value does not fit into regBx") 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, p = TPosition(0)) = let dist = p.int - c.code.len internalAssert(c.config, regBxMin < dist and dist < regBxMax) gABx(c, n, opcJmpBack, 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(c.config, regBxMin < diff and diff < regBxMax) let oldInstr = c.code[p] # opcode and regA stay the same: c.code[p] = ((oldInstr.TInstrType and regBxMask).TInstrType or TInstrType(diff+wordExcess) shl regBxShift).TInstr proc getSlotKind(t: PType): TSlotKind = case t.skipTypes(abstractRange-{tyTypeDesc}).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 bestEffort(c: PCtx): TLineInfo = if c.prc != nil and c.prc.sym != nil: c.prc.sym.info else: c.module.info proc getFreeRegister(cc: PCtx; k: TSlotKind; start: int): TRegister = let c = cc.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]: for i in start..c.regInfo.len-1: if c.regInfo[i].kind == k and not c.regInfo[i].inUse: c.regInfo[i].inUse = true return TRegister(i) # if register pressure is high, we re-use more aggressively: if c.regInfo.len >= high(TRegister): for i in start..c.regInfo.len-1: if not c.regInfo[i].inUse: c.regInfo[i] = (inUse: true, kind: k) return TRegister(i) if c.regInfo.len >= high(TRegister): globalError(cc.config, cc.bestEffort, "VM problem: too many registers required") result = TRegister(max(c.regInfo.len, start)) c.regInfo.setLen int(result)+1 c.regInfo[result] = (inUse: true, kind: k) proc getTemp(cc: PCtx; tt: PType): TRegister = let typ = tt.skipTypesOrNil({tyStatic}) # 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 result = getFreeRegister(cc, k, start = 0) when false: # enable this to find "register" leaks: if result == 4: echo "begin ---------------" writeStackTrace() echo "end ----------------" proc freeTemp(c: PCtx; r: TRegister) = let c = c.prc if c.regInfo[r].kind in {slotSomeTemp..slotTempComplex}: # this seems to cause https://github.com/nim-lang/Nim/issues/10647 c.regInfo[r].inUse = false proc getTempRange(cc: PCtx; n: int; kind: TSlotKind): TRegister = # if register pressure is high, we re-use more aggressively: let c = cc.prc # we could also customize via the following (with proper caching in ConfigRef): # let highRegisterPressure = cc.config.getConfigVar("vm.highRegisterPressure", "40").parseInt if c.regInfo.len >= HighRegisterPressure or c.regInfo.len+n >= high(TRegister): for i in 0..c.regInfo.len-n: if not c.regInfo[i].inUse: block search: for j in i+1..i+n-1: if c.regInfo[j].inUse: break search result = TRegister(i) for k in result..result+n-1: c.regInfo[k] = (inUse: true, kind: kind) return if c.regInfo.len+n >= high(TRegister): globalError(cc.config, cc.bestEffort, "VM problem: too many registers required") result = TRegister(c.regInfo.len) setLen c.regInfo, c.regInfo.len+n for k in result..result+n-1: c.regInfo[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, body: untyped) {.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: untyped) {.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; flags: TGenFlags = {}) proc gen(c: PCtx; n: PNode; dest: TRegister; flags: TGenFlags = {}) = var d: TDest = dest gen(c, n, d, flags) #internalAssert c.config, d == dest # issue #7407 proc gen(c: PCtx; n: PNode; flags: TGenFlags = {}) = var tmp: TDest = -1 gen(c, n, tmp, flags) if tmp >= 0: freeTemp(c, tmp) #if n.typ.isEmptyType: internalAssert tmp < 0 proc genx(c: PCtx; n: PNode; flags: TGenFlags = {}): TRegister = var tmp: TDest = -1 gen(c, n, tmp, flags) #internalAssert c.config, tmp >= 0 # 'nim check' does not like this internalAssert. if tmp >= 0: result = TRegister(tmp) proc clearDest(c: PCtx; n: PNode; dest: var TDest) {.inline.} = # stmt is different from 'void' in meta programming contexts. # So we only set dest to -1 if 'void': if dest >= 0 and (n.typ.isNil or n.typ.kind == tyVoid): c.freeTemp(dest) dest = -1 proc isNotOpr(n: PNode): bool = n.kind in nkCallKinds and n[0].kind == nkSym and n[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) = # lab1: # cond, tmp # fjmp tmp, lab2 # body # jmp lab1 # lab2: let lab1 = c.genLabel withBlock(nil): if isTrue(n[0]): c.gen(n[1]) c.jmpBack(n, lab1) elif isNotOpr(n[0]): var tmp = c.genx(n[0][1]) let lab2 = c.xjmp(n, opcTJmp, tmp) c.freeTemp(tmp) c.gen(n[1]) c.jmpBack(n, lab1) c.patch(lab2) else: var tmp = c.genx(n[0]) let lab2 = c.xjmp(n, opcFJmp, tmp) c.freeTemp(tmp) c.gen(n[1]) c.jmpBack(n, lab1) c.patch(lab2) proc genBlock(c: PCtx; n: PNode; dest: var TDest) = let oldRegisterCount = c.prc.regInfo.len withBlock(n[0].sym): c.gen(n[1], dest) for i in oldRegisterCount..= 0 and c.prc.regInfo[dest].kind >= slotTempUnknown proc genAndOr(c: PCtx; n: PNode; opc: TOpcode; dest: var TDest) = # asgn dest, a # tjmp|fjmp lab1 # asgn dest, b # lab1: let copyBack = dest < 0 or not isTemp(c, dest) let tmp = if copyBack: getTemp(c, n.typ) else: TRegister dest c.gen(n[1], tmp) let lab1 = c.xjmp(n, opc, tmp) c.gen(n[2], tmp) c.patch(lab1) if dest < 0: dest = tmp elif copyBack: c.gABC(n, opcAsgnInt, dest, tmp) freeTemp(c, tmp) proc canonValue*(n: PNode): PNode = result = n proc rawGenLiteral(c: PCtx; n: PNode): int = result = c.constants.len #assert(n.kind != nkCall) n.flags.incl nfAllConst c.constants.add n.canonValue internalAssert c.config, result < regBxMax 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..nkUInt64Lit: result = a.intVal == b.intVal of nkFloatLit..nkFloat64Lit: result = cast[uint64](a.floatVal) == cast[uint64](b.floatVal) # refs bug #16469 # if we wanted to only distinguish 0.0 vs -0.0: # if a.floatVal == 0.0: result = cast[uint64](a.floatVal) == cast[uint64](b.floatVal) # else: result = a.floatVal == b.floatVal of nkStrLit..nkTripleStrLit: result = a.strVal == b.strVal of nkType, nkNilLit: result = a.typ == b.typ of nkEmpty: result = true else: if a.len == b.len: for i in 0..= 0: #debug(n) globalError(c.config, n.info, "not unused") proc genCase(c: PCtx; n: PNode; dest: var TDest) = # if (!expr1) goto lab1; # thenPart # goto LEnd # lab1: # if (!expr2) goto lab2; # thenPart2 # goto LEnd # lab2: # elsePart # Lend: if not isEmptyType(n.typ): if dest < 0: dest = getTemp(c, n.typ) else: unused(c, n, dest) var endings: seq[TPosition] = @[] withTemp(tmp, n[0].typ): c.gen(n[0], tmp) # branch tmp, codeIdx # fjmp elseLabel for i in 1..= fntyp.len: internalAssert c.config, tfVarargs in fntyp.flags c.gABx(n, opcSetType, r, c.genType(n[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) template isGlobal(s: PSym): bool = sfGlobal in s.flags and s.kind != skForVar proc isGlobal(n: PNode): bool = n.kind == nkSym and isGlobal(n.sym) proc needsAsgnPatch(n: PNode): bool = n.kind in {nkBracketExpr, nkDotExpr, nkCheckedFieldExpr, nkDerefExpr, nkHiddenDeref} or (n.kind == nkSym and n.sym.isGlobal) proc genField(c: PCtx; n: PNode): TRegister = if n.kind != nkSym or n.sym.kind != skField: globalError(c.config, n.info, "no field symbol") let s = n.sym if s.position > high(typeof(result)): globalError(c.config, n.info, "too large offset! cannot generate code for: " & s.name.s) result = s.position proc genIndex(c: PCtx; n: PNode; arr: PType): TRegister = if arr.skipTypes(abstractInst).kind == tyArray and (let x = firstOrd(c.config, arr); x != Zero): let tmp = c.genx(n) # freeing the temporary here means we can produce: regA = regA - Imm c.freeTemp(tmp) result = c.getTemp(n.typ) c.gABI(n, opcSubImmInt, result, tmp, toInt(x)) else: result = c.genx(n) proc genCheckedObjAccessAux(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags) proc genAsgnPatch(c: PCtx; le: PNode, value: TRegister) = case le.kind of nkBracketExpr: let dest = c.genx(le[0], {gfNode}) let idx = c.genIndex(le[1], le[0].typ) c.gABC(le, opcWrArr, dest, idx, value) c.freeTemp(dest) c.freeTemp(idx) of nkCheckedFieldExpr: var objR: TDest = -1 genCheckedObjAccessAux(c, le, objR, {gfNode}) let idx = genField(c, le[0][1]) c.gABC(le[0], opcWrObj, objR, idx, value) c.freeTemp(objR) of nkDotExpr: let dest = c.genx(le[0], {gfNode}) let idx = genField(c, le[1]) c.gABC(le, opcWrObj, dest, idx, value) c.freeTemp(dest) of nkDerefExpr, nkHiddenDeref: let dest = c.genx(le[0], {gfNode}) c.gABC(le, opcWrDeref, dest, 0, value) c.freeTemp(dest) of nkSym: if le.sym.isGlobal: let dest = c.genx(le, {gfNodeAddr}) c.gABC(le, opcWrDeref, dest, 0, value) c.freeTemp(dest) else: discard proc genNew(c: PCtx; n: PNode) = let dest = if needsAsgnPatch(n[1]): c.getTemp(n[1].typ) else: c.genx(n[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[1].typ.skipTypes(abstractVar-{tyTypeDesc})[0])) c.genAsgnPatch(n[1], dest) c.freeTemp(dest) proc genNewSeq(c: PCtx; n: PNode) = let t = n[1].typ let dest = if needsAsgnPatch(n[1]): c.getTemp(t) else: c.genx(n[1]) let tmp = c.genx(n[2]) c.gABx(n, opcNewSeq, dest, c.genType(t.skipTypes( abstractVar-{tyTypeDesc}))) c.gABx(n, opcNewSeq, tmp, 0) c.freeTemp(tmp) c.genAsgnPatch(n[1], dest) c.freeTemp(dest) proc genNewSeqOfCap(c: PCtx; n: PNode; dest: var TDest) = let t = n.typ if dest < 0: dest = c.getTemp(n.typ) let tmp = c.getTemp(n[1].typ) c.gABx(n, opcLdNull, dest, c.genType(t)) c.gABx(n, opcLdImmInt, tmp, 0) c.gABx(n, opcNewSeq, dest, c.genType(t.skipTypes( abstractVar-{tyTypeDesc}))) c.gABx(n, opcNewSeq, tmp, 0) c.freeTemp(tmp) proc genUnaryABC(c: PCtx; n: PNode; dest: var TDest; opc: TOpcode) = let tmp = c.genx(n[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; imm: BiggestInt=0) = let tmp = c.genx(n[1]) if dest < 0: dest = c.getTemp(n.typ) c.gABI(n, opc, dest, tmp, imm) c.freeTemp(tmp) proc genBinaryABC(c: PCtx; n: PNode; dest: var TDest; opc: TOpcode) = let tmp = c.genx(n[1]) tmp2 = c.genx(n[2]) if dest < 0: dest = c.getTemp(n.typ) c.gABC(n, opc, dest, tmp, tmp2) c.freeTemp(tmp) c.freeTemp(tmp2) proc genBinaryABCD(c: PCtx; n: PNode; dest: var TDest; opc: TOpcode) = let tmp = c.genx(n[1]) tmp2 = c.genx(n[2]) tmp3 = c.genx(n[3]) if dest < 0: dest = c.getTemp(n.typ) c.gABC(n, opc, dest, tmp, tmp2) c.gABC(n, opc, tmp3) c.freeTemp(tmp) c.freeTemp(tmp2) c.freeTemp(tmp3) template sizeOfLikeMsg(name): string = "'$1' requires '.importc' types to be '.completeStruct'" % [name] proc genNarrow(c: PCtx; n: PNode; dest: TDest) = let t = skipTypes(n.typ, abstractVar-{tyTypeDesc}) # uint is uint64 in the VM, we we only need to mask the result for # other unsigned types: if t.kind in {tyUInt8..tyUInt32} or (t.kind == tyUInt and t.size < 8): c.gABC(n, opcNarrowU, dest, TRegister(t.size*8)) elif t.kind in {tyInt8..tyInt32} or (t.kind == tyInt and t.size < 8): c.gABC(n, opcNarrowS, dest, TRegister(t.size*8)) proc genNarrowU(c: PCtx; n: PNode; dest: TDest) = let t = skipTypes(n.typ, abstractVar-{tyTypeDesc}) # uint is uint64 in the VM, we we only need to mask the result for # other unsigned types: if t.kind in {tyUInt8..tyUInt32, tyInt8..tyInt32} or (t.kind in {tyUInt, tyInt} and t.size < 8): c.gABC(n, opcNarrowU, dest, TRegister(t.size*8)) proc genBinaryABCnarrow(c: PCtx; n: PNode; dest: var TDest; opc: TOpcode) = genBinaryABC(c, n, dest, opc) genNarrow(c, n, dest) proc genBinaryABCnarrowU(c: PCtx; n: PNode; dest: var TDest; opc: TOpcode) = genBinaryABC(c, n, dest, opc) genNarrowU(c, n, dest) proc genSetType(c: PCtx; n: PNode; dest: TRegister) = let t = skipTypes(n.typ, abstractInst-{tyTypeDesc}) 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[1]) tmp2 = c.genx(n[2]) if dest < 0: dest = c.getTemp(n.typ) c.genSetType(n[1], tmp) c.genSetType(n[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[1]) tmp = c.genx(n[2]) c.gABC(n, opc, dest, tmp, 0) c.freeTemp(tmp) c.freeTemp(dest) proc genBinaryStmtVar(c: PCtx; n: PNode; opc: TOpcode) = var x = n[1] if x.kind in {nkAddr, nkHiddenAddr}: x = x[0] let dest = c.genx(x) tmp = c.genx(n[2]) c.gABC(n, opc, dest, tmp, 0) #c.genAsgnPatch(n[1], dest) c.freeTemp(tmp) c.freeTemp(dest) proc genUnaryStmt(c: PCtx; n: PNode; opc: TOpcode) = let tmp = c.genx(n[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..= 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[2].isInt8Lit: let tmp = c.genx(n[1]) if dest < 0: dest = c.getTemp(n.typ) c.gABI(n, succ(opc), dest, tmp, n[2].intVal) c.freeTemp(tmp) else: genBinaryABC(c, n, dest, opc) c.genNarrow(n, dest) proc genConv(c: PCtx; n, arg: PNode; dest: var TDest; opc=opcConv) = let t2 = n.typ.skipTypes({tyDistinct}) let targ2 = arg.typ.skipTypes({tyDistinct}) proc implicitConv(): bool = if sameType(t2, targ2): return true # xxx consider whether to use t2 and targ2 here if n.typ.kind == arg.typ.kind and arg.typ.kind == tyProc: # don't do anything for lambda lifting conversions: return true if implicitConv(): gen(c, arg, dest) return let tmp = c.genx(arg) if dest < 0: dest = c.getTemp(n.typ) c.gABC(n, opc, dest, tmp) c.gABx(n, opc, 0, genType(c, n.typ.skipTypes({tyStatic}))) c.gABx(n, opc, 0, genType(c, arg.typ.skipTypes({tyStatic}))) c.freeTemp(tmp) proc genCard(c: PCtx; n: PNode; dest: var TDest) = let tmp = c.genx(n[1]) if dest < 0: dest = c.getTemp(n.typ) c.genSetType(n[1], tmp) c.gABC(n, opcCard, dest, tmp) c.freeTemp(tmp) proc genCastIntFloat(c: PCtx; n: PNode; dest: var TDest) = const allowedIntegers = {tyInt..tyInt64, tyUInt..tyUInt64, tyChar} var signedIntegers = {tyInt..tyInt64} var unsignedIntegers = {tyUInt..tyUInt64, tyChar} let src = n[1].typ.skipTypes(abstractRange)#.kind let dst = n[0].typ.skipTypes(abstractRange)#.kind let srcSize = getSize(c.config, src) let dstSize = getSize(c.config, dst) if src.kind in allowedIntegers and dst.kind in allowedIntegers: let tmp = c.genx(n[1]) if dest < 0: dest = c.getTemp(n[0].typ) c.gABC(n, opcAsgnInt, dest, tmp) if dstSize != sizeof(BiggestInt): # don't do anything on biggest int types if dst.kind in signedIntegers: # we need to do sign extensions if dstSize <= srcSize: # Sign extension can be omitted when the size increases. c.gABC(n, opcSignExtend, dest, TRegister(dstSize*8)) elif dst.kind in unsignedIntegers: if src.kind in signedIntegers or dstSize < srcSize: # Cast from signed to unsigned always needs narrowing. Cast # from unsigned to unsigned only needs narrowing when target # is smaller than source. c.gABC(n, opcNarrowU, dest, TRegister(dstSize*8)) c.freeTemp(tmp) elif srcSize == dstSize and src.kind in allowedIntegers and dst.kind in {tyFloat, tyFloat32, tyFloat64}: let tmp = c.genx(n[1]) if dest < 0: dest = c.getTemp(n[0].typ) if dst.kind == tyFloat32: c.gABC(n, opcCastIntToFloat32, dest, tmp) else: c.gABC(n, opcCastIntToFloat64, dest, tmp) c.freeTemp(tmp) elif srcSize == dstSize and src.kind in {tyFloat, tyFloat32, tyFloat64} and dst.kind in allowedIntegers: let tmp = c.genx(n[1]) if dest < 0: dest = c.getTemp(n[0].typ) if src.kind == tyFloat32: c.gABC(n, opcCastFloatToInt32, dest, tmp) if dst.kind in unsignedIntegers: # integers are sign extended by default. # since there is no opcCastFloatToUInt32, narrowing should do the trick. c.gABC(n, opcNarrowU, dest, TRegister(32)) else: c.gABC(n, opcCastFloatToInt64, dest, tmp) # narrowing for 64 bits not needed (no extended sign bits available). c.freeTemp(tmp) elif src.kind in PtrLikeKinds + {tyRef} and dst.kind == tyInt: let tmp = c.genx(n[1]) if dest < 0: dest = c.getTemp(n[0].typ) var imm: BiggestInt = if src.kind in PtrLikeKinds: 1 else: 2 c.gABI(n, opcCastPtrToInt, dest, tmp, imm) c.freeTemp(tmp) elif src.kind in PtrLikeKinds + {tyInt} and dst.kind in PtrLikeKinds: let tmp = c.genx(n[1]) if dest < 0: dest = c.getTemp(n[0].typ) c.gABx(n, opcSetType, dest, c.genType(dst)) c.gABC(n, opcCastIntToPtr, dest, tmp) c.freeTemp(tmp) elif src.kind == tyNil and dst.kind in NilableTypes: # supports casting nil literals to NilableTypes in VM # see #16024 if dest < 0: dest = c.getTemp(n[0].typ) genLit(c, n[1], dest) else: # todo: support cast from tyInt to tyRef globalError(c.config, n.info, "VM does not support 'cast' from " & $src.kind & " to " & $dst.kind) proc genVoidABC(c: PCtx, n: PNode, dest: TDest, opcode: TOpcode) = unused(c, n, dest) var tmp1 = c.genx(n[1]) tmp2 = c.genx(n[2]) tmp3 = c.genx(n[3]) c.gABC(n, opcode, tmp1, tmp2, tmp3) c.freeTemp(tmp1) c.freeTemp(tmp2) c.freeTemp(tmp3) proc genBindSym(c: PCtx; n: PNode; dest: var TDest) = # nah, cannot use c.config.features because sempass context # can have local experimental switch # if dynamicBindSym notin c.config.features: if n.len == 2: # hmm, reliable? # bindSym with static input if n[1].kind in {nkClosedSymChoice, nkOpenSymChoice, nkSym}: let idx = c.genLiteral(n[1]) if dest < 0: dest = c.getTemp(n.typ) c.gABx(n, opcNBindSym, dest, idx) else: localError(c.config, n.info, "invalid bindSym usage") else: # experimental bindSym if dest < 0: dest = c.getTemp(n.typ) let x = c.getTempRange(n.len, slotTempUnknown) # callee symbol var tmp0 = TDest(x) c.genLit(n[0], tmp0) # original parameters for i in 1.. nkConv(x) result = copyNode(n[0]) result.add m[0] of nkHiddenStdConv, nkHiddenSubConv, nkConv: var m = n[0][1] if m.kind in {nkDerefExpr, nkHiddenDeref}: # addr ( nkConv ( deref ( x ) ) ) --> nkConv(x) result = copyNode(n[0]) result.add m[0] else: if n[0].kind in {nkDerefExpr, nkHiddenDeref}: # addr ( deref ( x )) --> x result = n[0][0] proc genAddr(c: PCtx, n: PNode, dest: var TDest, flags: TGenFlags) = if (let m = canElimAddr(n); m != nil): gen(c, m, dest, flags) return let newflags = flags-{gfNode}+{gfNodeAddr} if isGlobal(n[0]) or n[0].kind in {nkDotExpr, nkCheckedFieldExpr, nkBracketExpr}: # checking for this pattern: addr(obj.field) / addr(array[i]) gen(c, n[0], dest, newflags) else: let tmp = c.genx(n[0], newflags) if dest < 0: dest = c.getTemp(n.typ) if c.prc.regInfo[tmp].kind >= slotTempUnknown: gABC(c, n, opcAddrNode, dest, tmp) # hack ahead; in order to fix bug #1781 we mark the temporary as # permanent, so that it's not used for anything else: c.prc.regInfo[tmp].kind = slotTempPerm # XXX this is still a hack #message(c.congig, n.info, warnUser, "suspicious opcode used") else: gABC(c, n, opcAddrReg, dest, tmp) c.freeTemp(tmp) proc genDeref(c: PCtx, n: PNode, dest: var TDest, flags: TGenFlags) = if unneededIndirection(n[0]): gen(c, n[0], dest, flags) if {gfNodeAddr, gfNode} * flags == {} and fitsRegister(n.typ): c.gABC(n, opcNodeToReg, dest, dest) else: let tmp = c.genx(n[0], flags) if dest < 0: dest = c.getTemp(n.typ) gABC(c, n, opcLdDeref, dest, tmp) assert n.typ != nil if {gfNodeAddr, gfNode} * flags == {} and fitsRegister(n.typ): c.gABC(n, opcNodeToReg, dest, dest) c.freeTemp(tmp) proc genAsgn(c: PCtx; dest: TDest; ri: PNode; requiresCopy: bool) = let tmp = c.genx(ri) assert dest >= 0 gABC(c, ri, whichAsgnOpc(ri, requiresCopy), dest, tmp) c.freeTemp(tmp) proc setSlot(c: PCtx; v: PSym) = # XXX generate type initialization here? if v.position == 0: v.position = getFreeRegister(c, if v.kind == skLet: slotFixedLet else: slotFixedVar, start = 1) proc cannotEval(c: PCtx; n: PNode) {.noinline.} = globalError(c.config, n.info, "cannot evaluate at compile time: " & n.renderTree) proc isOwnedBy(a, b: PSym): bool = var a = a.owner while a != nil and a.kind != skModule: if a == b: return true a = a.owner proc getOwner(c: PCtx): PSym = result = c.prc.sym if result.isNil: result = c.module proc importcCondVar*(s: PSym): bool {.inline.} = # see also importcCond if sfImportc in s.flags: return s.kind in {skVar, skLet, skConst} proc checkCanEval(c: PCtx; n: PNode) = # we need to ensure that we don't evaluate 'x' here: # proc foo() = var x ... let s = n.sym if {sfCompileTime, sfGlobal} <= s.flags: return if s.importcCondVar: return if s.kind in {skVar, skTemp, skLet, skParam, skResult} and not s.isOwnedBy(c.prc.sym) and s.owner != c.module and c.mode != emRepl: # little hack ahead for bug #12612: assume gensym'ed variables # are in the right scope: if sfGenSym in s.flags and c.prc.sym == nil: discard else: cannotEval(c, n) elif s.kind in {skProc, skFunc, skConverter, skMethod, skIterator} and sfForward in s.flags: cannotEval(c, n) template needsAdditionalCopy(n): untyped = not c.isTemp(dest) and not fitsRegister(n.typ) proc genAdditionalCopy(c: PCtx; n: PNode; opc: TOpcode; dest, idx, value: TRegister) = var cc = c.getTemp(n.typ) c.gABC(n, whichAsgnOpc(n), cc, value) c.gABC(n, opc, dest, idx, cc) c.freeTemp(cc) proc preventFalseAlias(c: PCtx; n: PNode; opc: TOpcode; dest, idx, value: TRegister) = # opcLdObj et al really means "load address". We sometimes have to create a # copy in order to not introduce false aliasing: # mylocal = a.b # needs a copy of the data! assert n.typ != nil if needsAdditionalCopy(n): genAdditionalCopy(c, n, opc, dest, idx, value) else: c.gABC(n, opc, dest, idx, value) proc genAsgn(c: PCtx; le, ri: PNode; requiresCopy: bool) = case le.kind of nkBracketExpr: let dest = c.genx(le[0], {gfNode}) let idx = c.genIndex(le[1], le[0].typ) let tmp = c.genx(ri) if le[0].typ.skipTypes(abstractVarRange-{tyTypeDesc}).kind in { tyString, tyCstring}: c.preventFalseAlias(le, opcWrStrIdx, dest, idx, tmp) else: c.preventFalseAlias(le, opcWrArr, dest, idx, tmp) c.freeTemp(tmp) c.freeTemp(idx) c.freeTemp(dest) of nkCheckedFieldExpr: var objR: TDest = -1 genCheckedObjAccessAux(c, le, objR, {gfNode}) let idx = genField(c, le[0][1]) let tmp = c.genx(ri) c.preventFalseAlias(le[0], opcWrObj, objR, idx, tmp) c.freeTemp(tmp) # c.freeTemp(idx) # BUGFIX, see nkDotExpr c.freeTemp(objR) of nkDotExpr: let dest = c.genx(le[0], {gfNode}) let idx = genField(c, le[1]) let tmp = c.genx(ri) c.preventFalseAlias(le, opcWrObj, dest, idx, tmp) # c.freeTemp(idx) # BUGFIX: idx is an immediate (field position), not a register c.freeTemp(tmp) c.freeTemp(dest) of nkDerefExpr, nkHiddenDeref: let dest = c.genx(le[0], {gfNode}) let tmp = c.genx(ri) c.preventFalseAlias(le, opcWrDeref, dest, 0, tmp) c.freeTemp(dest) c.freeTemp(tmp) of nkSym: let s = le.sym checkCanEval(c, le) if s.isGlobal: withTemp(tmp, le.typ): c.gen(le, tmp, {gfNodeAddr}) let val = c.genx(ri) c.preventFalseAlias(le, opcWrDeref, tmp, 0, val) c.freeTemp(val) else: if s.kind == skForVar: c.setSlot s internalAssert c.config, s.position > 0 or (s.position == 0 and s.kind in {skParam, skResult}) var dest: TRegister = s.position + ord(s.kind == skParam) assert le.typ != nil if needsAdditionalCopy(le) and s.kind in {skResult, skVar, skParam}: var cc = c.getTemp(le.typ) gen(c, ri, cc) c.gABC(le, whichAsgnOpc(le), dest, cc) c.freeTemp(cc) else: gen(c, ri, dest) else: let dest = c.genx(le, {gfNodeAddr}) genAsgn(c, dest, ri, requiresCopy) c.freeTemp(dest) proc genTypeLit(c: PCtx; t: PType; dest: var TDest) = var n = newNode(nkType) n.typ = t genLit(c, n, dest) proc importcCond*(c: PCtx; s: PSym): bool {.inline.} = ## return true to importc `s`, false to execute its body instead (refs #8405) if sfImportc in s.flags: if s.kind in routineKinds: return getBody(c.graph, s).kind == nkEmpty proc importcSym(c: PCtx; info: TLineInfo; s: PSym) = when hasFFI: if compiletimeFFI in c.config.features: c.globals.add(importcSymbol(c.config, s)) s.position = c.globals.len else: localError(c.config, info, "VM is not allowed to 'importc' without --experimental:compiletimeFFI") else: localError(c.config, info, "cannot 'importc' variable at compile time; " & s.name.s) proc getNullValue*(typ: PType, info: TLineInfo; conf: ConfigRef): PNode proc genGlobalInit(c: PCtx; n: PNode; s: PSym) = c.globals.add(getNullValue(s.typ, n.info, c.config)) 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 necessary: # var decls{.compileTime.}: seq[NimNode] = @[] let dest = c.getTemp(s.typ) c.gABx(n, opcLdGlobal, dest, s.position) if s.astdef != nil: let tmp = c.genx(s.astdef) c.genAdditionalCopy(n, opcWrDeref, dest, 0, tmp) c.freeTemp(dest) c.freeTemp(tmp) proc genRdVar(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags) = # gfNodeAddr and gfNode are mutually exclusive assert card(flags * {gfNodeAddr, gfNode}) < 2 let s = n.sym if s.isGlobal: let isImportcVar = importcCondVar(s) if sfCompileTime in s.flags or c.mode == emRepl or isImportcVar: discard elif s.position == 0: cannotEval(c, n) if s.position == 0: if importcCond(c, s) or isImportcVar: c.importcSym(n.info, s) else: genGlobalInit(c, n, s) if dest < 0: dest = c.getTemp(n.typ) assert s.typ != nil if gfNodeAddr in flags: if isImportcVar: c.gABx(n, opcLdGlobalAddrDerefFFI, dest, s.position) else: c.gABx(n, opcLdGlobalAddr, dest, s.position) elif isImportcVar: c.gABx(n, opcLdGlobalDerefFFI, dest, s.position) elif fitsRegister(s.typ) and gfNode notin flags: var cc = c.getTemp(n.typ) c.gABx(n, opcLdGlobal, cc, s.position) c.gABC(n, opcNodeToReg, dest, cc) c.freeTemp(cc) else: c.gABx(n, opcLdGlobal, dest, s.position) else: if s.kind == skForVar and c.mode == emRepl: c.setSlot(s) if s.position > 0 or (s.position == 0 and s.kind in {skParam, skResult}): if dest < 0: dest = s.position + ord(s.kind == skParam) internalAssert(c.config, c.prc.regInfo[dest].kind < slotSomeTemp) else: # we need to generate an assignment: let requiresCopy = c.prc.regInfo[dest].kind >= slotSomeTemp and gfIsParam notin flags genAsgn(c, dest, n, requiresCopy) else: # see tests/t99bott for an example that triggers it: cannotEval(c, n) template needsRegLoad(): untyped = {gfNode, gfNodeAddr} * flags == {} and fitsRegister(n.typ.skipTypes({tyVar, tyLent, tyStatic})) proc genArrAccessOpcode(c: PCtx; n: PNode; dest: var TDest; opc: TOpcode; flags: TGenFlags) = let a = c.genx(n[0], flags) let b = c.genIndex(n[1], n[0].typ) if dest < 0: dest = c.getTemp(n.typ) if opc in {opcLdArrAddr, opcLdStrIdxAddr} and gfNodeAddr in flags: c.gABC(n, opc, dest, a, b) elif needsRegLoad(): var cc = c.getTemp(n.typ) c.gABC(n, opc, cc, a, b) c.gABC(n, opcNodeToReg, dest, cc) c.freeTemp(cc) else: #message(c.config, n.info, warnUser, "argh") #echo "FLAGS ", flags, " ", fitsRegister(n.typ), " ", typeToString(n.typ) c.gABC(n, opc, dest, a, b) c.freeTemp(a) c.freeTemp(b) proc genObjAccess(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags) = let a = c.genx(n[0], flags) let b = genField(c, n[1]) if dest < 0: dest = c.getTemp(n.typ) if {gfNodeAddr} * flags != {}: c.gABC(n, opcLdObjAddr, dest, a, b) elif needsRegLoad(): var cc = c.getTemp(n.typ) c.gABC(n, opcLdObj, cc, a, b) c.gABC(n, opcNodeToReg, dest, cc) c.freeTemp(cc) else: c.gABC(n, opcLdObj, dest, a, b) c.freeTemp(a) proc genCheckedObjAccessAux(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags) = internalAssert c.config, n.kind == nkCheckedFieldExpr # nkDotExpr to access the requested field let accessExpr = n[0] # nkCall to check if the discriminant is valid var checkExpr = n[1] let negCheck = checkExpr[0].sym.magic == mNot if negCheck: checkExpr = checkExpr[^1] # Discriminant symbol let disc = checkExpr[2] internalAssert c.config, disc.sym.kind == skField # Load the object in `dest` c.gen(accessExpr[0], dest, flags) # Load the discriminant var discVal = c.getTemp(disc.typ) c.gABC(n, opcLdObj, discVal, dest, genField(c, disc)) # Check if its value is contained in the supplied set let setLit = c.genx(checkExpr[1]) var rs = c.getTemp(getSysType(c.graph, n.info, tyBool)) c.gABC(n, opcContainsSet, rs, setLit, discVal) c.freeTemp(discVal) c.freeTemp(setLit) # If the check fails let the user know let lab1 = c.xjmp(n, if negCheck: opcFJmp else: opcTJmp, rs) c.freeTemp(rs) let strType = getSysType(c.graph, n.info, tyString) var msgReg: TDest = c.getTemp(strType) let fieldName = $accessExpr[1] let msg = genFieldDefect(c.config, fieldName, disc.sym) let strLit = newStrNode(msg, accessExpr[1].info) strLit.typ = strType c.genLit(strLit, msgReg) c.gABC(n, opcInvalidField, msgReg, discVal) c.freeTemp(msgReg) c.patch(lab1) proc genCheckedObjAccess(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags) = var objR: TDest = -1 genCheckedObjAccessAux(c, n, objR, flags) let accessExpr = n[0] # Field symbol var field = accessExpr[1] internalAssert c.config, field.sym.kind == skField # Load the content now if dest < 0: dest = c.getTemp(n.typ) let fieldPos = genField(c, field) if {gfNodeAddr} * flags != {}: c.gABC(n, opcLdObjAddr, dest, objR, fieldPos) elif needsRegLoad(): var cc = c.getTemp(accessExpr.typ) c.gABC(n, opcLdObj, cc, objR, fieldPos) c.gABC(n, opcNodeToReg, dest, cc) c.freeTemp(cc) else: c.gABC(n, opcLdObj, dest, objR, fieldPos) c.freeTemp(objR) proc genArrAccess(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags) = let arrayType = n[0].typ.skipTypes(abstractVarRange-{tyTypeDesc}).kind if arrayType in {tyString, tyCstring}: let opc = if gfNodeAddr in flags: opcLdStrIdxAddr else: opcLdStrIdx genArrAccessOpcode(c, n, dest, opc, flags) elif arrayType == tyTypeDesc: c.genTypeLit(n.typ, dest) else: let opc = if gfNodeAddr in flags: opcLdArrAddr else: opcLdArr genArrAccessOpcode(c, n, dest, opc, flags) proc getNullValueAux(t: PType; obj: PNode, result: PNode; conf: ConfigRef; currPosition: var int) = if t != nil and t.len > 0 and t[0] != nil: let b = skipTypes(t[0], skipPtrs) getNullValueAux(b, b.n, result, conf, currPosition) case obj.kind of nkRecList: for i in 0.. 0: var tmp = getTemp(c, intType) c.gABx(n, opcLdNullReg, tmp, c.genType(intType)) for x in n: let a = c.genx(x) c.preventFalseAlias(n, 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[0]) let b = c.genx(x[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+{tyOwned}-{tyTypeDesc}) if t.kind == tyRef: c.gABx(n, opcNew, dest, c.genType(t[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 skType: genTypeLit(c, s.typ, dest) of skGenericParam: if c.prc.sym != nil and c.prc.sym.kind == skMacro: genRdVar(c, n, dest, flags) else: globalError(c.config, n.info, "cannot generate code for: " & s.name.s) else: globalError(c.config, n.info, "cannot generate code for: " & s.name.s) of nkCallKinds: if n[0].kind == nkSym: let s = n[0].sym if s.magic != mNone: genMagic(c, n, dest, s.magic) elif s.kind == skMethod: localError(c.config, n.info, "cannot call method " & s.name.s & " at compile time") elif matches(s, "stdlib.marshal.to"): # XXX marshal load&store should not be opcodes, but use the # general callback mechanisms. genMarshalLoad(c, n, dest) elif matches(s, "stdlib.marshal.$$"): genMarshalStore(c, n, dest) else: genCall(c, n, dest) clearDest(c, n, dest) else: genCall(c, n, dest) clearDest(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, getNullValue(n.typ, n.info, c.config), dest) else: unused(c, n, dest) of nkAsgn, nkFastAsgn: unused(c, n, dest) genAsgn(c, n[0], n[1], n.kind == nkAsgn) of nkDotExpr: genObjAccess(c, n, dest, flags) of nkCheckedFieldExpr: genCheckedObjAccess(c, n, dest, flags) of nkBracketExpr: genArrAccess(c, n, dest, flags) of nkDerefExpr, nkHiddenDeref: genDeref(c, n, dest, flags) of nkAddr, nkHiddenAddr: genAddr(c, n, dest, flags) of nkIfStmt, nkIfExpr: genIf(c, n, dest) of nkWhenStmt: # This is "when nimvm" node. Chose the first branch. gen(c, n[0][1], dest) of nkCaseStmt: genCase(c, n, dest) of nkWhileStmt: unused(c, n, dest) genWhile(c, n) of nkBlockExpr, nkBlockStmt: genBlock(c, n, dest) of nkReturnStmt: genReturn(c, n) of nkRaiseStmt: genRaise(c, n) of nkBreakStmt: genBreak(c, n) of nkTryStmt, nkHiddenTryStmt: genTry(c, n, dest) of nkStmtList: #unused(c, n, dest) # XXX Fix this bug properly, lexim triggers it for x in n: gen(c, x) of nkStmtListExpr: 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, nkExportStmt, nkMixinStmt, nkBindStmt: unused(c, n, dest) of nkStringToCString, nkCStringToString: gen(c, n[0], dest) of nkBracket: genArrayConstr(c, n, dest) of nkCurly: genSetConstr(c, n, dest) of nkObjConstr: genObjConstr(c, n, dest) of nkPar, nkClosure, nkTupleConstr: genTupleConstr(c, n, dest) of nkCast: if allowCast in c.features: genConv(c, n, n[1], dest, opcCast) else: genCastIntFloat(c, n, dest) of nkTypeOfExpr: genTypeLit(c, n.typ, dest) of nkComesFrom: discard "XXX to implement for better stack traces" else: if n.typ != nil and n.typ.isCompileTimeOnly: genTypeLit(c, n.typ, dest) else: globalError(c.config, n.info, "cannot generate VM code for " & $n) 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: globalError(c.config, n.info, "VM problem: dest register is 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: globalError(c.config, n.info, "VM problem: dest register is not set") d = 0 c.gABC(n, opcEof, d) #echo renderTree(n) #c.echoCode(result) proc genParams(c: PCtx; params: PNode) = # res.sym.position is already 0 setLen(c.prc.regInfo, max(params.len, 1)) c.prc.regInfo[0] = (inUse: true, kind: slotFixedVar) for i in 1.. 0: 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 pos = c.procToCodePos.getOrDefault(s.id) wasNotGenProcBefore = pos == 0 noRegistersAllocated = s.offset == -1 if wasNotGenProcBefore or noRegistersAllocated: # xxx: the noRegisterAllocated check is required in order to avoid issues # where nimsuggest can crash due as a macro with pos will be loaded # but it doesn't have offsets for register allocations see: # https://github.com/nim-lang/Nim/issues/18385 # Improvements and further use of IC should remove the need for this. #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 c.procToCodePos[s.id] = result # thanks to the jmp we can add top level statements easily and also nest # procs easily: let body = transformBody(c.graph, c.idgen, s, cache = not isCompileTimeProc(s)) let procStart = c.xjmp(body, opcJmp, 0) var p = PProc(blocks: @[], sym: s) let oldPrc = c.prc c.prc = p # iterate over the parameters and allocate space for them: genParams(c, s.typ.n) # allocate additional space for any generically bound parameters if s.kind == skMacro and s.isGenericRoutineStrict: genGenericParams(c, s.ast[genericParamsPos]) if tfCapturesEnv in s.typ.flags: #let env = s.ast[paramsPos].lastSon.sym #assert env.position == 2 c.prc.regInfo.add (inUse: true, kind: slotFixedLet) 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.regInfo.len #if s.name.s == "main" or s.name.s == "[]": # echo renderTree(body) # c.echoCode(result) c.prc = oldPrc else: c.prc.regInfo.setLen s.offset result = pos