diff --git a/compiler/ast.nim b/compiler/ast.nim index 0daa1f11f29d..3b9726a13ba3 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -469,6 +469,7 @@ type nfExplicitCall # x.y() was used instead of x.y nfExprCall # this is an attempt to call a regular expression nfIsRef # this node is a 'ref' node; used for the VM + nfIsPtr # this node is a 'ptr' node; used for the VM nfPreventCg # this node should be ignored by the codegen nfBlockArg # this a stmtlist appearing in a call (e.g. a do block) nfFromTemplate # a top-level node returned from a template @@ -856,6 +857,9 @@ type loc*: TLoc annex*: PLib # additional fields (seldom used, so we use a # reference to another object to save space) + when hasFFI: + cname*: string # resolved C declaration name in importc decl, eg: + # proc fun() {.importc: "$1aux".} => cname = funaux constraint*: PNode # additional constraints like 'lit|result'; also # misused for the codegenDecl pragma in the hope # it won't cause problems @@ -978,12 +982,13 @@ const tyTuple, tySequence} NilableTypes*: TTypeKinds = {tyPointer, tyCString, tyRef, tyPtr, tyProc, tyError} + PtrLikeKinds*: TTypeKinds = {tyPointer, tyPtr} # for VM ExportableSymKinds* = {skVar, skConst, skProc, skFunc, skMethod, skType, skIterator, skMacro, skTemplate, skConverter, skEnumField, skLet, skStub, skAlias} PersistentNodeFlags*: TNodeFlags = {nfBase2, nfBase8, nfBase16, nfDotSetter, nfDotField, - nfIsRef, nfPreventCg, nfLL, + nfIsRef, nfIsPtr, nfPreventCg, nfLL, nfFromTemplate, nfDefaultRefsParam, nfExecuteOnReload} namePos* = 0 diff --git a/compiler/cgen.nim b/compiler/cgen.nim index da041bf148a4..ee4634aad024 100644 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -700,7 +700,7 @@ proc loadDynamicLib(m: BModule, lib: PLib) = proc mangleDynLibProc(sym: PSym): Rope = # we have to build this as a single rope in order not to trip the - # optimization in genInfixCall + # optimization in genInfixCall, see test tests/cpp/t8241.nim if sfCompilerProc in sym.flags: # NOTE: sym.loc.r is the external name! result = rope(sym.name.s) diff --git a/compiler/evalffi.nim b/compiler/evalffi.nim index 5438108866e9..cb383a393be7 100644 --- a/compiler/evalffi.nim +++ b/compiler/evalffi.nim @@ -46,19 +46,12 @@ proc getDll(conf: ConfigRef, cache: var TDllCache; dll: string; info: TLineInfo) const nkPtrLit = nkIntLit # hopefully we can get rid of this hack soon -var myerrno {.importc: "errno", header: "".}: cint ## error variable - proc importcSymbol*(conf: ConfigRef, sym: PSym): PNode = - let name = $sym.loc.r + let name = sym.cname # $sym.loc.r would point to internal name # the AST does not support untyped pointers directly, so we use an nkIntLit # that contains the address instead: result = newNodeIT(nkPtrLit, sym.info, sym.typ) - case name - of "stdin": result.intVal = cast[ByteAddress](system.stdin) - of "stdout": result.intVal = cast[ByteAddress](system.stdout) - of "stderr": result.intVal = cast[ByteAddress](system.stderr) - of "vmErrnoWrapper": result.intVal = cast[ByteAddress](myerrno) - else: + when true: let lib = sym.annex if lib != nil and lib.path.kind notin {nkStrLit..nkTripleStrLit}: globalError(conf, sym.info, "dynlib needs to be a string lit") @@ -74,7 +67,7 @@ proc importcSymbol*(conf: ConfigRef, sym: PSym): PNode = let dll = if lib.kind == libHeader: libcDll else: lib.path.strVal let dllhandle = getDll(conf, gDllCache, dll, sym.info) theAddr = dllhandle.symAddr(name) - if theAddr.isNil: globalError(conf, sym.info, "cannot import: " & sym.name.s) + if theAddr.isNil: globalError(conf, sym.info, "cannot import: " & name) result.intVal = cast[ByteAddress](theAddr) proc mapType(conf: ConfigRef, t: ast.PType): ptr libffi.TType = diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index 726402bb4103..1802493f4115 100644 --- a/compiler/pragmas.nim +++ b/compiler/pragmas.nim @@ -129,6 +129,8 @@ proc setExternName(c: PContext; s: PSym, extname: string, info: TLineInfo) = s.loc.r = rope(extname % s.name.s) except ValueError: localError(c.config, info, "invalid extern name: '" & extname & "'. (Forgot to escape '$'?)") + when hasFFI: + s.cname = $s.loc.r if c.config.cmd == cmdPretty and '$' notin extname: # note that '{.importc.}' is transformed into '{.importc: "$1".}' s.loc.flags.incl(lfFullExternalName) diff --git a/compiler/vm.nim b/compiler/vm.nim index 4403a385d7c2..7d3d608ffffd 100644 --- a/compiler/vm.nim +++ b/compiler/vm.nim @@ -140,6 +140,40 @@ template decodeBx(k: untyped) {.dirty.} = template move(a, b: untyped) {.dirty.} = system.shallowCopy(a, b) # XXX fix minor 'shallowCopy' overloading bug in compiler +proc derefPtrToReg(address: BiggestInt, typ: PType, r: var TFullReg, isAssign: bool): bool = + # nim bug: `isAssign: static bool` doesn't work, giving odd compiler error + template fun(field, T, rkind) = + if isAssign: + cast[ptr T](address)[] = T(r.field) + else: + # ensureKind(rkind) + if r.kind != rkind: + myreset(r) + r.kind = rkind + let val = cast[ptr T](address)[] + when T is SomeInteger: + r.field = BiggestInt(val) + else: + r.field = val + return true + + ## see also typeinfo.getBiggestInt + case typ.kind + of tyInt: fun(intVal, int, rkInt) + of tyInt8: fun(intVal, int8, rkInt) + of tyInt16: fun(intVal, int16, rkInt) + of tyInt32: fun(intVal, int32, rkInt) + of tyInt64: fun(intVal, int64, rkInt) + of tyUInt: fun(intVal, uint, rkInt) + of tyUInt8: fun(intVal, uint8, rkInt) + of tyUInt16: fun(intVal, uint16, rkInt) + of tyUInt32: fun(intVal, uint32, rkInt) + of tyUInt64: fun(intVal, uint64, rkInt) # note: differs from typeinfo.getBiggestInt + of tyFloat: fun(floatVal, float, rkFloat) + of tyFloat32: fun(floatVal, float32, rkFloat) + of tyFloat64: fun(floatVal, float64, rkFloat) + else: return false + proc createStrKeepNode(x: var TFullReg; keepNode=true) = if x.node.isNil or not keepNode: x.node = newNode(nkStrLit) @@ -242,14 +276,20 @@ proc writeField(n: var PNode, x: TFullReg) = of rkNodeAddr: n = x.nodeAddr[] proc putIntoReg(dest: var TFullReg; n: PNode) = + template funInt() = + dest.kind = rkInt + dest.intVal = n.intVal case n.kind of nkStrLit..nkTripleStrLit: dest.kind = rkNode createStr(dest) dest.node.strVal = n.strVal - of nkCharLit..nkUInt64Lit: - dest.kind = rkInt - dest.intVal = n.intVal + of nkIntLit: # use `nkPtrLit` once this is added + if dest.kind == rkNode: dest.node = n + elif n.typ != nil and n.typ.kind in PtrLikeKinds: + dest = TFullReg(kind: rkNode, node: n) + else: funInt() + of {nkCharLit..nkUInt64Lit} - {nkIntLit}: funInt() of nkFloatLit..nkFloat128Lit: dest.kind = rkFloat dest.floatVal = n.floatVal @@ -567,6 +607,33 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = let rb = instr.regB ensureKind(rkFloat) regs[ra].floatVal = cast[float64](int64(regs[rb].intVal)) + + of opcCastPtrToInt: # RENAME opcCastPtrOrRefToInt + decodeBImm(rkInt) + case imm + of 1: # PtrLikeKinds + case regs[rb].kind + of rkNode: + regs[ra].intVal = cast[int](regs[rb].node.intVal) + of rkNodeAddr: + regs[ra].intVal = cast[int](regs[rb].nodeAddr) + else: + stackTrace(c, tos, pc, "opcCastPtrToInt: got " & $regs[rb].kind) + of 2: # tyRef + regs[ra].intVal = cast[int](regs[rb].node) + else: assert false, $imm + of opcCastIntToPtr: + let rb = instr.regB + let typ = regs[ra].node.typ + let node2 = newNodeIT(nkIntLit, c.debug[pc], typ) + case regs[rb].kind + of rkInt: node2.intVal = regs[rb].intVal + of rkNode: + if regs[rb].node.typ.kind notin PtrLikeKinds: + stackTrace(c, tos, pc, "opcCastIntToPtr: regs[rb].node.typ: " & $regs[rb].node.typ.kind) + node2.intVal = regs[rb].node.intVal + else: stackTrace(c, tos, pc, "opcCastIntToPtr: regs[rb].kind: " & $regs[rb].kind) + regs[ra].node = node2 of opcAsgnComplex: asgnComplex(regs[ra], regs[instr.regB]) of opcFastAsgnComplex: @@ -651,7 +718,10 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = let src = regs[rb].node case src.kind of nkEmpty..nkNilLit: - stackTrace(c, tos, pc, errNilAccess) + # for nkPtrLit, this could be supported in the future, use something like: + # derefPtrToReg(src.intVal + offsetof(src.typ, rc), typ_field, regs[ra], isAssign = false) + # where we compute the offset in bytes for field rc + stackTrace(c, tos, pc, errNilAccess & " " & $("kind", src.kind, "typ", typeToString(src.typ), "rc", rc)) of nkObjConstr: let n = src[rc + 1].skipColon regs[ra].node = n @@ -718,10 +788,23 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = if regs[rb].node.kind == nkRefTy: regs[ra].node = regs[rb].node[0] else: - ensureKind(rkNode) - regs[ra].node = regs[rb].node + let node = regs[rb].node + let typ = node.typ + # see also `nfIsPtr` + if node.kind == nkIntLit: + var typ2 = typ + doAssert typ != nil + if typ.kind == tyPtr: + typ2 = typ2[0] + if not derefPtrToReg(node.intVal, typ2, regs[ra], isAssign = false): + # tyObject not supported in this context + stackTrace(c, tos, pc, "opcLdDeref unsupported ptr type: " & $(typeToString(typ), typ.kind)) + else: + ## eg: typ.kind = tyObject + ensureKind(rkNode) + regs[ra].node = regs[rb].node else: - stackTrace(c, tos, pc, errNilAccess) + stackTrace(c, tos, pc, errNilAccess & " kind: " & $regs[rb].kind) of opcWrDeref: # a[] = c; b unused let ra = instr.regA @@ -742,8 +825,18 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = of rkNode: if regs[ra].node.kind == nkNilLit: stackTrace(c, tos, pc, errNilAccess) - regs[ra].node[] = regs[rc].regToNode[] - regs[ra].node.flags.incl nfIsRef + let node = regs[ra].node + let typ = node.typ + if nfIsPtr in node.flags or (typ != nil and typ.kind == tyPtr): + assert node.kind == nkIntLit, $(node.kind) + var typ2 = typ + if typ.kind == tyPtr: + typ2 = typ2[0] + if not derefPtrToReg(node.intVal, typ2, regs[rc], isAssign = true): + stackTrace(c, tos, pc, "opcWrDeref unsupported ptr type: " & $(typeToString(typ), typ.kind)) + else: + regs[ra].node[] = regs[rc].regToNode[] + regs[ra].node.flags.incl nfIsRef else: stackTrace(c, tos, pc, errNilAccess) of opcAddInt: decodeBC(rkInt) @@ -918,22 +1011,40 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = decodeBC(rkInt) regs[ra].intVal = ord(regs[rb].intVal <% regs[rc].intVal) of opcEqRef: + var ret = false decodeBC(rkInt) + template getTyp(n): untyped = + n.typ.skipTypes(abstractInst) + proc ptrEquality(n1: ptr PNode, n2: PNode): bool = + ## true if n2.intVal represents a ptr equal to n1 + let p1 = cast[int](n1) + case n2.kind + of nkNilLit: return p1 == 0 + of nkIntLit: # TODO: nkPtrLit + # for example, n1.kind == nkFloatLit (ptr float) + # the problem is that n1.typ == nil so we can't compare n1.typ and n2.typ + # this is the best we can do (pending making sure we assign a valid n1.typ to nodeAddr's) + let t2 = n2.getTyp + return t2.kind in PtrLikeKinds and n2.intVal == p1 + else: return false + if regs[rb].kind == rkNodeAddr: if regs[rc].kind == rkNodeAddr: - regs[ra].intVal = ord(regs[rb].nodeAddr == regs[rc].nodeAddr) + ret = regs[rb].nodeAddr == regs[rc].nodeAddr else: - assert regs[rc].kind == rkNode - # we know these cannot be equal - regs[ra].intVal = ord(false) + ret = ptrEquality(regs[rb].nodeAddr, regs[rc].node) elif regs[rc].kind == rkNodeAddr: - assert regs[rb].kind == rkNode - # we know these cannot be equal - regs[ra].intVal = ord(false) + ret = ptrEquality(regs[rc].nodeAddr, regs[rb].node) else: - regs[ra].intVal = ord((regs[rb].node.kind == nkNilLit and - regs[rc].node.kind == nkNilLit) or - regs[rb].node == regs[rc].node) + let nb = regs[rb].node + let nc = regs[rc].node + if nb.kind != nc.kind: discard + elif (nb == nc) or (nb.kind == nkNilLit): ret = true + elif nb.kind == nkIntLit and nb.intVal == nc.intVal: # TODO: nkPtrLit + let tb = nb.getTyp + let tc = nc.getTyp + ret = tb.kind in PtrLikeKinds and tc.kind == tb.kind + regs[ra].intVal = ord(ret) of opcEqNimNode: decodeBC(rkInt) regs[ra].intVal = @@ -1338,6 +1449,29 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = let rb = instr.regBx - wordExcess - 1 ensureKind(rkNode) regs[ra].node = c.globals[rb] + of opcLdGlobalDerefFFI: + let rb = instr.regBx - wordExcess - 1 + let node = c.globals[rb] + let typ = node.typ + doAssert node.kind == nkIntLit, $(node.kind) + if typ.kind == tyPtr: + ensureKind(rkNode) + # use nkPtrLit once this is added + let node2 = newNodeIT(nkIntLit, c.debug[pc], typ) + node2.intVal = cast[ptr int](node.intVal)[] + node2.flags.incl nfIsPtr + regs[ra].node = node2 + elif not derefPtrToReg(node.intVal, typ, regs[ra], isAssign = false): + stackTrace(c, tos, pc, "opcLdDeref unsupported type: " & $(typeToString(typ), typ[0].kind)) + of opcLdGlobalAddrDerefFFI: + let rb = instr.regBx - wordExcess - 1 + let node = c.globals[rb] + let typ = node.typ + var node2 = newNodeIT(nkIntLit, node.info, typ) + node2.intVal = node.intVal + node2.flags.incl nfIsPtr + ensureKind(rkNode) + regs[ra].node = node2 of opcLdGlobalAddr: let rb = instr.regBx - wordExcess - 1 ensureKind(rkNodeAddr) diff --git a/compiler/vmdef.nim b/compiler/vmdef.nim index a5df87ca8398..b46d8a293896 100644 --- a/compiler/vmdef.nim +++ b/compiler/vmdef.nim @@ -63,6 +63,8 @@ type opcCastIntToFloat64, # int and float must be of the same byte size opcCastFloatToInt32, # int and float must be of the same byte size opcCastFloatToInt64, # int and float must be of the same byte size + opcCastPtrToInt, + opcCastIntToPtr, opcFastAsgnComplex, opcNodeToReg, @@ -169,6 +171,8 @@ type opcAsgnConst, # dest = copy(constants[Bx]) opcLdGlobal, # dest = globals[Bx] opcLdGlobalAddr, # dest = addr(globals[Bx]) + opcLdGlobalDerefFFI, # dest = globals[Bx][] + opcLdGlobalAddrDerefFFI, # globals[Bx][] = ... opcLdImmInt, # dest = immediate value opcNBindSym, opcNDynBindSym, diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim index 987fc7864aae..a34ef5d095fc 100644 --- a/compiler/vmgen.nim +++ b/compiler/vmgen.nim @@ -885,8 +885,21 @@ proc genCastIntFloat(c: PCtx; n: PNode; dest: var TDest) = 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) else: - globalError(c.config, n.info, "VM is only allowed to 'cast' between integers and/or floats of same size") + # 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) @@ -1474,11 +1487,17 @@ 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 @@ -1618,17 +1637,24 @@ proc genRdVar(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags) = assert card(flags * {gfNodeAddr, gfNode}) < 2 let s = n.sym if s.isGlobal: - if sfCompileTime in s.flags or c.mode == emRepl: + 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(s): c.importcSym(n.info, s) + if importcCond(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: - c.gABx(n, opcLdGlobalAddr, dest, s.position) + 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)