diff --git a/compiler/ccgstmts.nim b/compiler/ccgstmts.nim index d6220afbf4..9eedd175aa 100644 --- a/compiler/ccgstmts.nim +++ b/compiler/ccgstmts.nim @@ -1446,7 +1446,8 @@ proc genTrySetjmp(p: BProc, t: PNode, d: var TLoc) = let memberName = if p.module.compileToCpp: "m_type" else: "Sup.m_type" if optTinyRtti in p.config.globalOptions: let checkFor = $getObjDepth(t[i][j].typ) - appcg(p.module, orExpr, "#isObjDisplayCheck(#nimBorrowCurrentException()->$1, $2, $3)", [memberName, checkFor, $genDisplayElem(MD5Digest(hashType(t[i][j].typ, p.config)))]) + appcg(p.module, orExpr, "#isObjDisplayCheck(#nimBorrowCurrentException()->$1, $2, $3)", + [memberName, checkFor, $genDisplayElem(MD5Digest(hashType(t[i][j].typ, p.config)))]) else: let checkFor = genTypeInfoV1(p.module, t[i][j].typ, t[i][j].info) appcg(p.module, orExpr, "#isObj(#nimBorrowCurrentException()->$1, $2)", [memberName, checkFor]) diff --git a/compiler/nir/ast2ir.nim b/compiler/nir/ast2ir.nim index ef4d4d9a8f..3ebf8f97c3 100644 --- a/compiler/nir/ast2ir.nim +++ b/compiler/nir/ast2ir.nim @@ -8,7 +8,7 @@ # import std / [assertions, tables, sets] -import ".." / [ast, types, options, lineinfos, msgs] +import ".." / [ast, astalgo, types, options, lineinfos, msgs] import .. / ic / bitabs import nirtypes, nirinsts, nirlineinfos, nirslots, types2ir @@ -19,80 +19,2030 @@ type man: LineInfoManager types: TypesCon slotGenerator: ref int + module: PSym LocInfo = object inUse: bool typ: TypeId ProcCon = object - conf: ConfigRef + config: ConfigRef lastFileKey: FileIndex lastFileVal: LitId labelGen: int - scopes: seq[LabelId] + exitLabel: LabelId + code: Tree + blocks: seq[(PSym, LabelId)] sm: SlotManager locGen: int m: ModuleCon + prc: PSym -proc initModuleCon*(conf: ConfigRef): ModuleCon = - ModuleCon(types: initTypesCon(conf), slotGenerator: new(int)) +proc initModuleCon*(config: ConfigRef; module: PSym): ModuleCon = + ModuleCon(types: initTypesCon(config), slotGenerator: new(int), module: module) -proc initProcCon*(m: ModuleCon; ): ProcCon = - ProcCon(m: m, sm: initSlotManager({}, m.slotGenerator)) +proc initProcCon*(m: ModuleCon; prc: PSym): ProcCon = + ProcCon(m: m, sm: initSlotManager({}, m.slotGenerator), prc: prc) proc toLineInfo(c: var ProcCon; i: TLineInfo): PackedLineInfo = var val: LitId if c.lastFileKey == i.fileIndex: val = c.lastFileVal else: - val = c.m.strings.getOrIncl(toFullPath(c.conf, i.fileIndex)) + val = c.m.strings.getOrIncl(toFullPath(c.config, i.fileIndex)) # remember the entry: c.lastFileKey = i.fileIndex c.lastFileVal = val result = pack(c.m.man, val, int32 i.line, int32 i.col) -proc gen*(c: var ProcCon; dest: var Tree; n: PNode) -proc genv*(c: var ProcCon; dest: var Tree; v: var Value; n: PNode) +when false: + proc gen*(c: var ProcCon; dest: var Tree; n: PNode) + proc genv*(c: var ProcCon; dest: var Tree; v: var Value; n: PNode) -proc genx*(c: var ProcCon; dest: var Tree; n: PNode): SymId = + proc genx*(c: var ProcCon; dest: var Tree; n: PNode): SymId = + let info = toLineInfo(c, n.info) + let t = typeToIr(c.m.types, n.typ) + result = allocTemp(c.sm, t) + addSummon dest, info, result, t + var ex = localToValue(info, result) + genv(c, dest, ex, n) + template withBlock(lab: LabelId; body: untyped) = + body + dest.addInstr(info, Label, lab) + + proc genWhile(c: var ProcCon; dest: var Tree; n: PNode) = + # LoopLabel lab1: + # cond, tmp + # select cond + # of false: goto lab2 + # body + # GotoLoop lab1 + # Label lab2: + let info = toLineInfo(c, n.info) + let loopLab = dest.addLabel(c.labelGen, info, LoopLabel) + let theEnd = newLabel(c.labelGen) + withBlock(theEnd): + if isTrue(n[0]): + c.gen(dest, n[1]) + dest.gotoLabel info, GotoLoop, loopLab + else: + let x = c.genx(dest, n[0]) + #dest.addSelect toLineInfo(c, n[0].kind), x + c.gen(dest, n[1]) + dest.gotoLabel info, GotoLoop, loopLab + + proc genv*(c: var ProcCon; dest: var Tree; v: var Value; n: PNode) = + quit "too implement" + + proc gen*(c: var ProcCon; dest: var Tree; n: PNode) = + case n.kind + of nkWhileStmt: + genWhile c, dest, n + else: + discard + +proc bestEffort(c: ProcCon): TLineInfo = + if c.prc != nil: + c.prc.info + else: + c.m.module.info + +proc popBlock(c: var ProcCon; oldLen: int) = + c.blocks.setLen(oldLen) + +template withBlock(labl: PSym; info: PackedLineInfo; asmLabl: LabelId; body: untyped) {.dirty.} = + var oldLen {.gensym.} = c.blocks.len + c.blocks.add (labl, asmLabl) + body + popBlock(c, oldLen) + +type + GenFlag = enum + gfAddrOf # Affects how variables are loaded - always loads as rkNodeAddr + gfToOutParam + GenFlags = set[GenFlag] + +proc gen(c: var ProcCon; n: PNode; dest: var Value; flags: GenFlags = {}) + +proc genScope(c: var ProcCon; n: PNode; dest: var Value; flags: GenFlags = {}) = + openScope c.sm + gen c, n, dest, flags + closeScope c.sm + +proc freeTemp(c: var ProcCon; tmp: Value) = + let s = extractTemp(tmp) + if s != SymId(-1): + freeLoc(c.sm, s) + +proc getTemp(c: var ProcCon; n: PNode): Value = let info = toLineInfo(c, n.info) let t = typeToIr(c.m.types, n.typ) - result = allocTemp(c.sm, t) - addSummon dest, info, result, t - var ex = localToValue(info, result) - genv(c, dest, ex, n) + let tmp = allocTemp(c.sm, t) + c.code.addSummon info, tmp, t + result = localToValue(info, tmp) -template withBlock(lab: LabelId; body: untyped) = +template withTemp(tmp, n, body: untyped) {.dirty.} = + var tmp = getTemp(c, n) body - dest.addInstr(info, Label, lab) + c.freeTemp(tmp) -proc genWhile(c: var ProcCon; dest: var Tree; n: PNode) = - # LoopLabel lab1: - # cond, tmp - # select cond - # of false: goto lab2 - # body - # GotoLoop lab1 - # Label lab2: +proc gen(c: var ProcCon; n: PNode; flags: GenFlags = {}) = + var tmp = default(Value) + gen(c, n, tmp, flags) + freeTemp c, tmp + +proc genScope(c: var ProcCon; n: PNode; flags: GenFlags = {}) = + openScope c.sm + gen c, n, flags + closeScope c.sm + +proc genx(c: var ProcCon; n: PNode; flags: GenFlags = {}): Value = + result = default(Value) + gen(c, n, result, flags) + +proc clearDest(c: var ProcCon; n: PNode; dest: var Value) {.inline.} = + if n.typ.isNil or n.typ.kind == tyVoid: + let s = extractTemp(dest) + if s != SymId(-1): + freeLoc(c.sm, s) + +proc isNotOpr(n: PNode): bool = + n.kind in nkCallKinds and n[0].kind == nkSym and n[0].sym.magic == mNot + +proc jmpBack(c: var ProcCon; n: PNode; lab: LabelId) = + c.code.gotoLabel toLineInfo(c, n.info), GotoLoop, lab + +type + JmpKind = enum opcFJmp, opcTJmp + +proc xjmp(c: var ProcCon; n: PNode; jk: JmpKind; v: Value): LabelId = + result = newLabel(c.labelGen) let info = toLineInfo(c, n.info) - let loopLab = dest.addLabel(c.labelGen, info, LoopLabel) - let theEnd = newLabel(c.labelGen) - withBlock(theEnd): + build c.code, info, Select: + c.code.addTyped info, Bool8Id + c.code.copyTree Tree(v) + build c.code, info, SelectPair: + build c.code, info, SelectValue: + c.code.boolVal(info, jk == opcTJmp) + c.code.gotoLabel info, Goto, result + +proc patch(c: var ProcCon; n: PNode; L: LabelId) = + addLabel c.code, toLineInfo(c, n.info), Label, L + +proc genWhile(c: var ProcCon; n: PNode) = + # lab1: + # cond, tmp + # fjmp tmp, lab2 + # body + # jmp lab1 + # lab2: + let info = toLineInfo(c, n.info) + let lab1 = c.code.addNewLabel(c.labelGen, info, LoopLabel) + withBlock(nil, info, lab1): if isTrue(n[0]): - c.gen(dest, n[1]) - dest.gotoLabel info, GotoLoop, loopLab + 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(n, lab2) else: - let x = c.genx(dest, n[0]) - #dest.addSelect toLineInfo(c, n[0].kind), x - c.gen(dest, n[1]) - dest.gotoLabel info, GotoLoop, loopLab + 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(n, lab2) -proc genv*(c: var ProcCon; dest: var Tree; v: var Value; n: PNode) = - quit "too implement" +proc genBlock(c: var ProcCon; n: PNode; dest: var Value) = + openScope c.sm + let info = toLineInfo(c, n.info) + let lab1 = newLabel(c.labelGen) -proc gen*(c: var ProcCon; dest: var Tree; n: PNode) = - case n.kind - of nkWhileStmt: - genWhile c, dest, n + withBlock(n[0].sym, info, lab1): + c.gen(n[1], dest) + + c.code.addLabel(info, Label, lab1) + closeScope c.sm + c.clearDest(n, dest) + +proc jumpTo(c: var ProcCon; n: PNode; L: LabelId) = + c.code.addLabel(toLineInfo(c, n.info), Goto, L) + +proc genBreak(c: var ProcCon; n: PNode) = + if n[0].kind == nkSym: + for i in countdown(c.blocks.len-1, 0): + if c.blocks[i][0] == n[0].sym: + c.jumpTo n, c.blocks[i][1] + return + localError(c.config, n.info, "NIR problem: cannot find 'break' target") else: - discard + c.jumpTo n, c.blocks[c.blocks.high][1] + +proc genIf(c: var ProcCon; n: PNode; dest: var Value) = + # if (!expr1) goto lab1; + # thenPart + # goto LEnd + # lab1: + # if (!expr2) goto lab2; + # thenPart2 + # goto LEnd + # lab2: + # elsePart + # Lend: + if isEmpty(dest) and not isEmptyType(n.typ): dest = getTemp(c, n) + var ending = newLabel(c.labelGen) + for i in 0..= low(int8) and n.intVal <= high(int8) + else: + result = false + +proc isInt16Lit(n: PNode): bool = + if n.kind in {nkCharLit..nkUInt64Lit}: + result = n.intVal >= low(int16) and n.intVal <= high(int16) + else: + result = false + +proc genAddSubInt(c: var ProcCon; n: PNode; dest: var Value; opc: TOpcode) = + if n[2].isInt8Lit: + let tmp = c.genx(n[1]) + if isEmpty(dest): dest = c.getTemp(n) + 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: var ProcCon; n, arg: PNode; dest: var Value; opc=opcConv) = + let t2 = n.typ.skipTypes({tyDistinct}) + let targ2 = arg.typ.skipTypes({tyDistinct}) + + proc implicitConv(): bool = + if sameBackendType(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: + result = true + else: + result = false + + if implicitConv(): + gen(c, arg, dest) + return + + let tmp = c.genx(arg) + if isEmpty(dest): dest = c.getTemp(n) + 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: var ProcCon; n: PNode; dest: var Value) = + let tmp = c.genx(n[1]) + if isEmpty(dest): dest = c.getTemp(n) + c.genSetType(n[1], tmp) + c.gABC(n, opcCard, dest, tmp) + c.freeTemp(tmp) + +proc genCastIntFloat(c: var ProcCon; n: PNode; dest: var Value) = + 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) + const unsupportedCastDifferentSize = + "VM does not support 'cast' from $1 with size $2 to $3 with size $4 due to different sizes" + if src.kind in allowedIntegers and dst.kind in allowedIntegers: + let tmp = c.genx(n[1]) + if isEmpty(dest): dest = c.getTemp(n[0]) + 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 src.kind in allowedIntegers and + dst.kind in {tyFloat, tyFloat32, tyFloat64}: + if srcSize != dstSize: + globalError(c.config, n.info, unsupportedCastDifferentSize % + [$src.kind, $srcSize, $dst.kind, $dstSize]) + let tmp = c.genx(n[1]) + if isEmpty(dest): dest = c.getTemp(n[0]) + if dst.kind == tyFloat32: + c.gABC(n, opcCastIntToFloat32, dest, tmp) + else: + c.gABC(n, opcCastIntToFloat64, dest, tmp) + c.freeTemp(tmp) + + elif src.kind in {tyFloat, tyFloat32, tyFloat64} and + dst.kind in allowedIntegers: + if srcSize != dstSize: + globalError(c.config, n.info, unsupportedCastDifferentSize % + [$src.kind, $srcSize, $dst.kind, $dstSize]) + let tmp = c.genx(n[1]) + if isEmpty(dest): dest = c.getTemp(n[0]) + 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 isEmpty(dest): dest = c.getTemp(n[0]) + 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 isEmpty(dest): dest = c.getTemp(n[0]) + 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 isEmpty(dest): dest = c.getTemp(n[0]) + 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 genBindSym(c: var ProcCon; n: PNode; dest: var Value) = + # 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 isEmpty(dest): dest = c.getTemp(n) + c.gABx(n, opcNBindSym, dest, idx) + else: + localError(c.config, n.info, "invalid bindSym usage") + else: + # experimental bindSym + if isEmpty(dest): dest = c.getTemp(n) + let x = c.getTempRange(n.len, slotTempUnknown) + + # callee symbol + var tmp0 = Value(x) + c.genLit(n[0], tmp0) + + # original parameters + for i in 1.. nkConv(x) + result = copyNode(n[0]) + result.add m[0] + if n.typ.skipTypes(abstractVar).kind != tyOpenArray: + result.typ = n.typ + elif n.typ.skipTypes(abstractInst).kind in {tyVar}: + result.typ = toVar(result.typ, n.typ.skipTypes(abstractInst).kind, idgen) + 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 n[0][0] + result.add m[0] + if n.typ.skipTypes(abstractVar).kind != tyOpenArray: + result.typ = n.typ + elif n.typ.skipTypes(abstractInst).kind in {tyVar}: + result.typ = toVar(result.typ, n.typ.skipTypes(abstractInst).kind, idgen) + else: + if n[0].kind in {nkDerefExpr, nkHiddenDeref}: + # addr ( deref ( x )) --> x + result = n[0][0] + +proc genAddr(c: var ProcCon; n: PNode, dest: var Value, flags: GenFlags) = + if (let m = canElimAddr(n, c.idgen); 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 isEmpty(dest): dest = c.getTemp(n) + if c.prc.regInfo[tmp].kind >= slotTempUnknown: + gABC(c, n, opcAddrNode, dest, tmp) + else: + gABC(c, n, opcAddrReg, dest, tmp) + c.freeTemp(tmp) + +proc genDeref(c: var ProcCon; n: PNode, dest: var Value, flags: GenFlags) = + 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 isEmpty(dest): dest = c.getTemp(n) + 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: var ProcCon; dest: Value; 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: var ProcCon; 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: var ProcCon; n: PNode) {.noinline.} = + globalError(c.config, n.info, "cannot evaluate at compile time: " & + n.renderTree) + +proc isOwnedBy(a, b: PSym): bool = + result = false + var a = a.owner + while a != nil and a.kind != skModule: + if a == b: return true + a = a.owner + +proc getOwner(c: ProcCon): 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: + result = s.kind in {skVar, skLet, skConst} + else: + result = false + +proc checkCanEval(c: var ProcCon; 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 compiletimeFFI in c.config.features and 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 + elif s.kind == skParam and s.typ.kind == tyTypeDesc: 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: var ProcCon; n: PNode; opc: TOpcode; + dest, idx, value: TRegister) = + var cc = c.getTemp(n) + c.gABC(n, whichAsgnOpc(n), cc, value) + c.gABC(n, opc, dest, idx, cc) + c.freeTemp(cc) + +proc preventFalseAlias(c: var ProcCon; 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: var ProcCon; le, ri: PNode; requiresCopy: bool) = + case le.kind + of nkBracketExpr: + let + dest = c.genx(le[0], {gfNode}) + idx = c.genIndex(le[1], le[0].typ) + tmp = c.genx(ri) + collTyp = le[0].typ.skipTypes(abstractVarRange-{tyTypeDesc}) + case collTyp.kind + of tyString, tyCstring: + c.preventFalseAlias(le, opcWrStrIdx, dest, idx, tmp) + of tyTuple: + c.preventFalseAlias(le, opcWrObj, dest, int le[1].intVal, tmp) + else: + c.preventFalseAlias(le, opcWrArr, dest, idx, tmp) + c.freeTemp(tmp) + c.freeTemp(idx) + c.freeTemp(dest) + of nkCheckedFieldExpr: + var objR: Value = -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) + 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: var ProcCon; t: PType; dest: var Value) = + var n = newNode(nkType) + n.typ = t + genLit(c, n, dest) + +proc isEmptyBody(n: PNode): bool = + case n.kind + of nkStmtList: + for i in 0.. 0 or (s.position == 0 and + s.kind in {skParam, skResult}): + if isEmpty(dest): + 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: var ProcCon; n: PNode; dest: var Value; opc: TOpcode; + flags: GenFlags) = + let a = c.genx(n[0], flags) + let b = c.genIndex(n[1], n[0].typ) + if isEmpty(dest): dest = c.getTemp(n) + if opc in {opcLdArrAddr, opcLdStrIdxAddr} and gfNodeAddr in flags: + c.gABC(n, opc, dest, a, b) + elif needsRegLoad(): + var cc = c.getTemp(n) + 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 genObjAccessAux(c: var ProcCon; n: PNode; a, b: int, dest: var Value; flags: GenFlags) = + if isEmpty(dest): dest = c.getTemp(n) + if {gfNodeAddr} * flags != {}: + c.gABC(n, opcLdObjAddr, dest, a, b) + elif needsRegLoad(): + var cc = c.getTemp(n) + 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 genObjAccess(c: var ProcCon; n: PNode; dest: var Value; flags: GenFlags) = + genObjAccessAux(c, n, c.genx(n[0], flags), genField(c, n[1]), dest, flags) + +proc genCheckedObjAccessAux(c: var ProcCon; n: PNode; dest: var Value; flags: GenFlags) = + 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) + 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(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: Value = 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(discVal) + c.freeTemp(msgReg) + c.patch(lab1) + +proc genCheckedObjAccess(c: var ProcCon; n: PNode; dest: var Value; flags: GenFlags) = + var objR: Value = -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 isEmpty(dest): dest = c.getTemp(n) + let fieldPos = genField(c, field) + + if {gfNodeAddr} * flags != {}: + c.gABC(n, opcLdObjAddr, dest, objR, fieldPos) + elif needsRegLoad(): + var cc = c.getTemp(accessExpr) + 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: var ProcCon; n: PNode; dest: var Value; flags: GenFlags) = + let arrayType = n[0].typ.skipTypes(abstractVarRange-{tyTypeDesc}).kind + case arrayType + of tyString, tyCstring: + let opc = if gfNodeAddr in flags: opcLdStrIdxAddr else: opcLdStrIdx + genArrAccessOpcode(c, n, dest, opc, flags) + of tyTuple: + c.genObjAccessAux(n, c.genx(n[0], flags), int n[1].intVal, dest, flags) + of 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; config: 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, config, currPosition) + case obj.kind + of nkRecList: + for i in 0.. 0 + if s.position == 0: + if importcCond(c, s): c.importcSym(a.info, s) + else: + let sa = getNullValue(s.typ, a.info, c.config) + #if s.ast.isNil: getNullValue(s.typ, a.info) + #else: s.ast + assert sa.kind != nkCall + c.globals.add(sa) + s.position = c.globals.len + if runtimeAccessToCompileTime: + discard + elif a[2].kind != nkEmpty: + let tmp = c.genx(a[0], {gfNodeAddr}) + let val = c.genx(a[2]) + c.genAdditionalCopy(a[2], opcWrDeref, tmp, 0, val) + c.freeTemp(val) + c.freeTemp(tmp) + elif not importcCondVar(s) and not (s.typ.kind == tyProc and s.typ.callConv == ccClosure) and + sfPure notin s.flags: # fixes #10938 + # there is a pre-existing issue with closure types in VM + # if `(var s: proc () = default(proc ()); doAssert s == nil)` works for you; + # you might remove the second condition. + # the problem is that closure types are tuples in VM, but the types of its children + # shouldn't have the same type as closure types. + let tmp = c.genx(a[0], {gfNodeAddr}) + let sa = getNullValue(s.typ, a.info, c.config) + let val = c.genx(sa) + c.genAdditionalCopy(sa, opcWrDeref, tmp, 0, val) + c.freeTemp(val) + c.freeTemp(tmp) + else: + setSlot(c, s) + if a[2].kind == nkEmpty: + c.gABx(a, ldNullOpcode(s.typ), s.position, c.genType(s.typ)) + else: + assert s.typ != nil + if not fitsRegister(s.typ): + c.gABx(a, ldNullOpcode(s.typ), s.position, c.genType(s.typ)) + let le = a[0] + assert le.typ != nil + if not fitsRegister(le.typ) and s.kind in {skResult, skVar, skParam}: + var cc = c.getTemp(le) + gen(c, a[2], cc) + c.gABC(le, whichAsgnOpc(le), s.position.TRegister, cc) + c.freeTemp(cc) + else: + gen(c, a[2], s.position.TRegister) + else: + # assign to a[0]; happens for closures + if a[2].kind == nkEmpty: + let tmp = genx(c, a[0]) + c.gABx(a, ldNullOpcode(a[0].typ), tmp, c.genType(a[0].typ)) + c.freeTemp(tmp) + else: + genAsgn(c, a[0], a[2], true) + +proc genArrayConstr(c: var ProcCon; n: PNode, dest: var Value) = + if isEmpty(dest): dest = c.getTemp(n) + c.gABx(n, opcLdNull, dest, c.genType(n.typ)) + + let intType = getSysType(c.graph, n.info, tyInt) + let seqType = n.typ.skipTypes(abstractVar-{tyTypeDesc}) + if seqType.kind == tySequence: + var tmp = c.getTemp(intType) + c.gABx(n, opcLdImmInt, tmp, n.len) + c.gABx(n, opcNewSeq, dest, c.genType(seqType)) + c.gABx(n, opcNewSeq, tmp, 0) + c.freeTemp(tmp) + + if n.len > 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: var ProcCon; n: PNode, dest: var Value) = + if isEmpty(dest): dest = c.getTemp(n) + 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: var ProcCon; n: PNode, dest: var Value) = + if isEmpty(dest): dest = c.getTemp(n) + 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") + 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 isEmpty(dest): dest = c.getTemp(n) + 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, nkSinkAsgn: + 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, declarativeDefs, nkMacroDef: + 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: + localError(c.config, n.info, "cannot generate IR code for " & $n) + +proc genStmt*(c: var ProcCon; n: PNode): int = + result = c.code.len + var d = default(Value) + c.gen(n, d) + unused c, n, d + +proc genExpr*(c: var ProcCon; n: PNode, requiresValue = true): int = + result = c.code.len + var d = default(Value) + c.gen(n, d) + if isEmpty d: + if requiresValue: + globalError(c.config, n.info, "VM problem: dest register is not set") + +proc genParams(c: var ProcCon; 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..; x begins to live @@ -55,6 +61,8 @@ type Load, # a[] Store, # a[] = b Asgn, # a = b + SetExc, + TestExc, Call, IndirectCall, @@ -83,6 +91,7 @@ type NumberConv, CheckedObjConv, ObjConv, + TestOf, Emit, ProcDecl @@ -92,13 +101,15 @@ const OpcodeBits = 8'u32 OpcodeMask = (1'u32 shl OpcodeBits) - 1'u32 + ValueProducingAtoms = {ImmediateVal, IntVal, StrVal, SymUse, NilVal} + ValueProducing* = { ImmediateVal, IntVal, StrVal, SymUse, - ModuleSymUse, NilVal, + ModuleSymUse, ArrayConstr, ObjConstr, CheckedAdd, @@ -127,7 +138,8 @@ const AddrOf, Load, ArrayAt, - FieldAt + FieldAt, + TestOf } type @@ -176,6 +188,11 @@ proc patch*(tree: var Tree; pos: PatchPos) = assert distance > 0 tree.nodes[pos].x = toX(k, cast[uint32](distance)) +template build*(tree: var Tree; info: PackedLineInfo; kind: Opcode; body: untyped) = + let pos = prepare(tree, info, kind) + body + patch(tree, pos) + proc len*(tree: Tree): int {.inline.} = tree.nodes.len template rawSpan(n: Instr): int = int(operand(n)) @@ -217,23 +234,29 @@ proc newLabel*(labelGen: var int): LabelId {.inline.} = result = LabelId labelGen inc labelGen -proc addLabel*(t: var Tree; labelGen: var int; info: PackedLineInfo; k: Opcode): LabelId = +proc addNewLabel*(t: var Tree; labelGen: var int; info: PackedLineInfo; k: Opcode): LabelId = assert k in {Label, LoopLabel} result = LabelId labelGen t.nodes.add Instr(x: toX(k, uint32(result)), info: info) inc labelGen +proc boolVal*(t: var Tree; info: PackedLineInfo; b: bool) = + t.nodes.add Instr(x: toX(ImmediateVal, uint32(b)), info: info) + proc gotoLabel*(t: var Tree; info: PackedLineInfo; k: Opcode; L: LabelId) = - assert k in {Goto, GotoLoop} + assert k in {Goto, GotoLoop, CheckedGoto} t.nodes.add Instr(x: toX(k, uint32(L)), info: info) -proc addInstr*(t: var Tree; info: PackedLineInfo; k: Opcode; L: LabelId) {.inline.} = - assert k in {Label, LoopLabel, Goto, GotoLoop} +proc addLabel*(t: var Tree; info: PackedLineInfo; k: Opcode; L: LabelId) {.inline.} = + assert k in {Label, LoopLabel, Goto, GotoLoop, CheckedGoto} t.nodes.add Instr(x: toX(k, uint32(L)), info: info) proc addSymUse*(t: var Tree; info: PackedLineInfo; s: SymId) {.inline.} = t.nodes.add Instr(x: toX(SymUse, uint32(s)), info: info) +proc addTyped*(t: var Tree; info: PackedLineInfo; typ: TypeId) {.inline.} = + t.nodes.add Instr(x: toX(Typed, uint32(typ)), info: info) + proc addSummon*(t: var Tree; info: PackedLineInfo; s: SymId; typ: TypeId) {.inline.} = let x = prepare(t, info, Summon) t.nodes.add Instr(x: toX(SymDef, uint32(s)), info: info) @@ -244,7 +267,7 @@ type Value* = distinct Tree proc prepare*(dest: var Value; info: PackedLineInfo; k: Opcode): PatchPos {.inline.} = - assert k in ValueProducing + assert k in ValueProducing - ValueProducingAtoms result = prepare(Tree(dest), info, k) proc patch*(dest: var Value; pos: PatchPos) {.inline.} = @@ -253,3 +276,27 @@ proc patch*(dest: var Value; pos: PatchPos) {.inline.} = proc localToValue*(info: PackedLineInfo; s: SymId): Value = result = Value(Tree()) Tree(result).addSymUse info, s + +proc hasValue*(v: Value): bool {.inline.} = Tree(v).len > 0 + +proc isEmpty*(v: Value): bool {.inline.} = Tree(v).len == 0 + +proc extractTemp*(v: Value): SymId = + if hasValue(v) and Tree(v)[NodePos 0].kind == SymUse: + result = SymId(Tree(v)[NodePos 0].operand) + else: + result = SymId(-1) + +proc copyTree*(dest: var Tree; src: Value) = copyTree dest, Tree(src) + +proc addImmediateVal*(t: var Value; info: PackedLineInfo; x: int) = + assert x >= 0 and x < ((1 shl 32) - OpcodeBits.int) + Tree(t).nodes.add Instr(x: toX(ImmediateVal, uint32(x)), info: info) + +template build*(tree: var Value; info: PackedLineInfo; kind: Opcode; body: untyped) = + let pos = prepare(Tree(tree), info, kind) + body + patch(tree, pos) + +proc addTyped*(t: var Value; info: PackedLineInfo; typ: TypeId) {.inline.} = + addTyped(Tree(t), info, typ) diff --git a/compiler/nir/nirslots.nim b/compiler/nir/nirslots.nim index a5c0e93f4a..b1bba4ae7a 100644 --- a/compiler/nir/nirslots.nim +++ b/compiler/nir/nirslots.nim @@ -21,6 +21,7 @@ type live: Table[SymId, TypeId] dead: Table[TypeId, seq[SymId]] flags: set[SlotManagerFlag] + inScope: seq[SymId] locGen: ref int proc initSlotManager*(flags: set[SlotManagerFlag]; generator: ref int): SlotManager {.inline.} = @@ -32,6 +33,7 @@ proc allocRaw(m: var SlotManager; t: TypeId; f: SlotManagerFlag): SymId {.inline else: result = SymId(m.locGen[]) inc m.locGen[] + m.inScope.add result m.live[result] = t proc allocTemp*(m: var SlotManager; t: TypeId): SymId {.inline.} = @@ -52,6 +54,17 @@ iterator stillAlive*(m: SlotManager): (SymId, TypeId) = proc getType*(m: SlotManager; s: SymId): TypeId {.inline.} = m.live[s] +proc openScope*(m: var SlotManager) = + m.inScope.add SymId(-1) # add marker + +proc closeScope*(m: var SlotManager) = + var i = m.inScope.len - 1 + while i >= 0: + if m.inScope[i] == SymId(-1): + m.inScope.setLen i-1 + break + dec i + when isMainModule: var m = initSlotManager({ReuseTemps}, new(int)) diff --git a/compiler/vm.nim b/compiler/vm.nim index 579bab600c..85c1305e94 100644 --- a/compiler/vm.nim +++ b/compiler/vm.nim @@ -1521,7 +1521,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = frame = frame.next jumpTo = findExceptionHandler(c, frame, raised) - case jumpTo.why: + case jumpTo.why of ExceptionGotoHandler: # Jump to the handler, do nothing when the `finally` block ends. savedPC = -1