Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VM: support importc var, ptr/pointer types, cast int <=> ptr/pointer #12877

Merged
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion compiler/ast.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion compiler/cgen.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
13 changes: 3 additions & 10 deletions compiler/evalffi.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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: "<errno.h>".}: 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")
Expand All @@ -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 =
Expand Down
2 changes: 2 additions & 0 deletions compiler/pragmas.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
132 changes: 123 additions & 9 deletions compiler/vm.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -242,6 +276,15 @@ proc writeField(n: var PNode, x: TFullReg) =
of rkNodeAddr: n = x.nodeAddr[]

proc putIntoReg(dest: var TFullReg; n: PNode) =
if n.kind == nkIntLit:
timotheecour marked this conversation as resolved.
Show resolved Hide resolved
# use `nkPtrLit` once this is added
if dest.kind == rkNode:
dest.node = n
return
elif n.typ != nil and n.typ.kind == tyPtr:
dest = TFullReg(kind: rkNode, node: n)
return

case n.kind
of nkStrLit..nkTripleStrLit:
dest.kind = rkNode
Expand Down Expand Up @@ -567,6 +610,24 @@ 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:
let rb = instr.regB
ensureKind(rkInt)
if regs[rb].kind != rkNode:
stackTrace(c, tos, pc, "opcCastPtrToInt: regs[rb].kind: " & $regs[rb].kind)
regs[ra].intVal = cast[int](regs[rb].node.intVal)
of opcCastIntToPtr:
let rb = instr.regB
let typ = regs[ra].node.typ
timotheecour marked this conversation as resolved.
Show resolved Hide resolved
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:
Expand Down Expand Up @@ -651,7 +712,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
Expand Down Expand Up @@ -718,10 +782,22 @@ 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
timotheecour marked this conversation as resolved.
Show resolved Hide resolved
# see also `nfIsPtr`
if node.kind == nkIntLit:
var typ2 = typ
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
Expand All @@ -742,8 +818,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
timotheecour marked this conversation as resolved.
Show resolved Hide resolved
if nfIsPtr in node.flags or 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)
Expand Down Expand Up @@ -931,9 +1017,14 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
# we know these cannot be equal
regs[ra].intVal = ord(false)
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
template getTyp(n): untyped =
n.typ.skipTypes({tyAlias})
timotheecour marked this conversation as resolved.
Show resolved Hide resolved
regs[ra].intVal = ord(
(nb.kind == nkNilLit and nc.kind == nkNilLit) or
(nb.getTyp.kind in PtrLikeKinds and nb.getTyp.kind == nc.getTyp.kind and nb.intVal == nc.intVal) or
nb == nc)
of opcEqNimNode:
decodeBC(rkInt)
regs[ra].intVal =
Expand Down Expand Up @@ -1338,6 +1429,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 opcLdGlobalDeref:
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
timotheecour marked this conversation as resolved.
Show resolved Hide resolved
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 opcLdGlobalAddrDeref:
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)
Expand Down
4 changes: 4 additions & 0 deletions compiler/vmdef.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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,

Expand Down Expand Up @@ -169,6 +171,8 @@ type
opcAsgnConst, # dest = copy(constants[Bx])
opcLdGlobal, # dest = globals[Bx]
opcLdGlobalAddr, # dest = addr(globals[Bx])
opcLdGlobalDeref, # dest = globals[Bx][]
timotheecour marked this conversation as resolved.
Show resolved Hide resolved
opcLdGlobalAddrDeref, # globals[Bx][] = ...

opcLdImmInt, # dest = immediate value
opcNBindSym, opcNDynBindSym,
Expand Down
32 changes: 28 additions & 4 deletions compiler/vmgen.nim
Original file line number Diff line number Diff line change
Expand Up @@ -885,8 +885,19 @@ 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 and dst.kind == tyInt:
let tmp = c.genx(n[1])
if dest < 0: dest = c.getTemp(n[0].typ)
c.gABC(n, opcCastPtrToInt, dest, tmp)
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")
globalError(c.config, n.info, "VM is only allowed to 'cast' between integers and/or floats of same size or PtrLikeKinds <=> PtrLikeKinds / int " & $(src.kind, dst.kind))

proc genVoidABC(c: PCtx, n: PNode, dest: TDest, opcode: TOpcode) =
unused(c, n, dest)
Expand Down Expand Up @@ -1474,11 +1485,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
Expand Down Expand Up @@ -1618,17 +1635,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, opcLdGlobalAddrDeref, dest, s.position)
else:
c.gABx(n, opcLdGlobalAddr, dest, s.position)
elif isImportcVar:
c.gABx(n, opcLdGlobalDeref, dest, s.position)
elif fitsRegister(s.typ) and gfNode notin flags:
var cc = c.getTemp(n.typ)
c.gABx(n, opcLdGlobal, cc, s.position)
Expand Down