* enable FFI at CT
* rename useFFI=>nimHasLibFFI; improve formatting rawExecute traceCode
* disable libffi on windows (works for win32, not yet win64)
This commit is contained in:
Timothee Cour 2019-02-23 02:31:01 -08:00 • committed by Andreas Rumpf
commit adbabf145c
9 changed files with 250 additions and 136 deletions

View file

@ -9,43 +9,47 @@
## This file implements the FFI part of the evaluator for Nim code.
import ast, astalgo, ropes, types, options, tables, dynlib, libffi, msgs, os
import ast, astalgo, ropes, types, options, tables, dynlib, msgs, os, lineinfos
import pkg/libffi
when defined(windows):
const libcDll = "msvcrt.dll"
else:
elif defined(linux):
const libcDll = "libc.so(.6|.5|)"
elif defined(osx):
const libcDll = "/usr/lib/libSystem.dylib"
else:
{.error: "`libcDll` not implemented on this platform".}
type
TDllCache = tables.TTable[string, TLibHandle]
TDllCache = tables.Table[string, LibHandle]
var
gDllCache = initTable[string, TLibHandle]()
gDllCache = initTable[string, LibHandle]()
when defined(windows):
var gExeHandle = loadLib(os.getAppFilename())
else:
var gExeHandle = loadLib()
proc getDll(cache: var TDllCache; dll: string; info: TLineInfo): pointer =
result = cache[dll]
proc getDll(conf: ConfigRef, cache: var TDllCache; dll: string; info: TLineInfo): pointer =
if dll in cache:
return cache[dll]
var libs: seq[string]
libCandidates(dll, libs)
for c in libs:
result = loadLib(c)
if not result.isNil: break
if result.isNil:
var libs: seq[string] = @[]
libCandidates(dll, libs)
for c in libs:
result = loadLib(c)
if not result.isNil: break
if result.isNil:
globalError(info, "cannot load: " & dll)
cache[dll] = result
globalError(conf, info, "cannot load: " & dll)
cache[dll] = result
const
nkPtrLit = nkIntLit # hopefully we can get rid of this hack soon
var myerrno {.importc: "errno", header: "<errno.h>".}: cint ## error variable
proc importcSymbol*(sym: PSym): PNode =
let name = ropeToStr(sym.loc.r)
proc importcSymbol*(conf: ConfigRef, sym: PSym): PNode =
let name = $sym.loc.r
# 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)
@ -57,28 +61,28 @@ proc importcSymbol*(sym: PSym): PNode =
else:
let lib = sym.annex
if lib != nil and lib.path.kind notin {nkStrLit..nkTripleStrLit}:
globalError(sym.info, "dynlib needs to be a string lit for the REPL")
globalError(conf, sym.info, "dynlib needs to be a string lit")
var theAddr: pointer
if lib.isNil and not gExehandle.isNil:
if (lib.isNil or lib.kind == libHeader) and not gExehandle.isNil:
# first try this exe itself:
theAddr = gExehandle.symAddr(name)
# then try libc:
if theAddr.isNil:
let dllhandle = gDllCache.getDll(libcDll, sym.info)
let dllhandle = getDll(conf, gDllCache, libcDll, sym.info)
theAddr = dllhandle.symAddr(name)
elif not lib.isNil:
let dllhandle = gDllCache.getDll(if lib.kind == libHeader: libcDll
else: lib.path.strVal, sym.info)
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(sym.info, "cannot import: " & sym.name.s)
if theAddr.isNil: globalError(conf, sym.info, "cannot import: " & sym.name.s)
result.intVal = cast[ByteAddress](theAddr)
proc mapType(t: ast.PType): ptr libffi.TType =
proc mapType(conf: ConfigRef, t: ast.PType): ptr libffi.TType =
if t == nil: return addr libffi.type_void
case t.kind
of tyBool, tyEnum, tyChar, tyInt..tyInt64, tyUInt..tyUInt64, tySet:
case t.getSize
case getSize(conf, t)
of 1: result = addr libffi.type_uint8
of 2: result = addr libffi.type_sint16
of 4: result = addr libffi.type_sint32
@ -90,87 +94,87 @@ proc mapType(t: ast.PType): ptr libffi.TType =
tyStmt, tyTypeDesc, tyProc, tyArray, tyStatic, tyNil:
result = addr libffi.type_pointer
of tyDistinct, tyAlias, tySink:
result = mapType(t.sons[0])
result = mapType(conf, t.sons[0])
else:
result = nil
# too risky:
#of tyFloat128: result = addr libffi.type_longdouble
proc mapCallConv(cc: TCallingConvention, info: TLineInfo): TABI =
proc mapCallConv(conf: ConfigRef, cc: TCallingConvention, info: TLineInfo): TABI =
case cc
of ccDefault: result = DEFAULT_ABI
of ccStdCall: result = when defined(windows): STDCALL else: DEFAULT_ABI
of ccStdCall: result = when defined(windows) and defined(x86): STDCALL else: DEFAULT_ABI
of ccCDecl: result = DEFAULT_ABI
else:
globalError(info, "cannot map calling convention to FFI")
globalError(conf, info, "cannot map calling convention to FFI")
template rd(T, p: untyped): untyped = (cast[ptr T](p))[]
template wr(T, p, v: untyped): untyped = (cast[ptr T](p))[] = v
template `+!`(x, y: untyped): untyped =
cast[pointer](cast[ByteAddress](x) + y)
proc packSize(v: PNode, typ: PType): int =
proc packSize(conf: ConfigRef, v: PNode, typ: PType): int =
## computes the size of the blob
case typ.kind
of tyPtr, tyRef, tyVar, tyLent:
if v.kind in {nkNilLit, nkPtrLit}:
result = sizeof(pointer)
else:
result = sizeof(pointer) + packSize(v.sons[0], typ.lastSon)
result = sizeof(pointer) + packSize(conf, v.sons[0], typ.lastSon)
of tyDistinct, tyGenericInst, tyAlias, tySink:
result = packSize(v, typ.sons[0])
result = packSize(conf, v, typ.sons[0])
of tyArray:
# consider: ptr array[0..1000_000, int] which is common for interfacing;
# we use the real length here instead
if v.kind in {nkNilLit, nkPtrLit}:
result = sizeof(pointer)
elif v.len != 0:
result = v.len * packSize(v.sons[0], typ.sons[1])
result = v.len * packSize(conf, v.sons[0], typ.sons[1])
else:
result = typ.getSize.int
result = getSize(conf, typ).int
proc pack(v: PNode, typ: PType, res: pointer)
proc pack(conf: ConfigRef, v: PNode, typ: PType, res: pointer)
proc getField(n: PNode; position: int): PSym =
proc getField(conf: ConfigRef, n: PNode; position: int): PSym =
case n.kind
of nkRecList:
for i in countup(0, sonsLen(n) - 1):
result = getField(n.sons[i], position)
result = getField(conf, n.sons[i], position)
if result != nil: return
of nkRecCase:
result = getField(n.sons[0], position)
result = getField(conf, n.sons[0], position)
if result != nil: return
for i in countup(1, sonsLen(n) - 1):
case n.sons[i].kind
of nkOfBranch, nkElse:
result = getField(lastSon(n.sons[i]), position)
result = getField(conf, lastSon(n.sons[i]), position)
if result != nil: return
else: internalError(n.info, "getField(record case branch)")
else: internalError(conf, n.info, "getField(record case branch)")
of nkSym:
if n.sym.position == position: result = n.sym
else: discard
proc packObject(x: PNode, typ: PType, res: pointer) =
internalAssert x.kind in {nkObjConstr, nkPar, nkTupleConstr}
proc packObject(conf: ConfigRef, x: PNode, typ: PType, res: pointer) =
internalAssert conf, x.kind in {nkObjConstr, nkPar, nkTupleConstr}
# compute the field's offsets:
discard typ.getSize
discard getSize(conf, typ)
for i in countup(ord(x.kind == nkObjConstr), sonsLen(x) - 1):
var it = x.sons[i]
if it.kind == nkExprColonExpr:
internalAssert it.sons[0].kind == nkSym
internalAssert conf, it.sons[0].kind == nkSym
let field = it.sons[0].sym
pack(it.sons[1], field.typ, res +! field.offset)
pack(conf, it.sons[1], field.typ, res +! field.offset)
elif typ.n != nil:
let field = getField(typ.n, i)
pack(it, field.typ, res +! field.offset)
let field = getField(conf, typ.n, i)
pack(conf, it, field.typ, res +! field.offset)
else:
# XXX: todo
globalError(x.info, "cannot pack unnamed tuple")
globalError(conf, x.info, "cannot pack unnamed tuple")
const maxPackDepth = 20
var packRecCheck = 0
proc pack(v: PNode, typ: PType, res: pointer) =
proc pack(conf: ConfigRef, v: PNode, typ: PType, res: pointer) =
template awr(T, v: untyped): untyped =
wr(T, res, v)
@ -188,13 +192,13 @@ proc pack(v: PNode, typ: PType, res: pointer) =
of tyUInt32: awr(uint32, v.intVal.uint32)
of tyUInt64: awr(uint64, v.intVal.uint64)
of tyEnum, tySet:
case v.typ.getSize
case getSize(conf, v.typ)
of 1: awr(uint8, v.intVal.uint8)
of 2: awr(uint16, v.intVal.uint16)
of 4: awr(int32, v.intVal.int32)
of 8: awr(int64, v.intVal.int64)
else:
globalError(v.info, "cannot map value to FFI (tyEnum, tySet)")
globalError(conf, v.info, "cannot map value to FFI (tyEnum, tySet)")
of tyFloat: awr(float, v.floatVal)
of tyFloat32: awr(float32, v.floatVal)
of tyFloat64: awr(float64, v.floatVal)
@ -208,7 +212,7 @@ proc pack(v: PNode, typ: PType, res: pointer) =
elif v.kind in {nkStrLit..nkTripleStrLit}:
awr(cstring, cstring(v.strVal))
else:
globalError(v.info, "cannot map pointer/proc value to FFI")
globalError(conf, v.info, "cannot map pointer/proc value to FFI")
of tyPtr, tyRef, tyVar, tyLent:
if v.kind == nkNilLit:
# nothing to do since the memory is 0 initialized anyway
@ -218,44 +222,44 @@ proc pack(v: PNode, typ: PType, res: pointer) =
else:
if packRecCheck > maxPackDepth:
packRecCheck = 0
globalError(v.info, "cannot map value to FFI " & typeToString(v.typ))
globalError(conf, v.info, "cannot map value to FFI " & typeToString(v.typ))
inc packRecCheck
pack(v.sons[0], typ.lastSon, res +! sizeof(pointer))
pack(conf, v.sons[0], typ.lastSon, res +! sizeof(pointer))
dec packRecCheck
awr(pointer, res +! sizeof(pointer))
of tyArray:
let baseSize = typ.sons[1].getSize
let baseSize = getSize(conf, typ.sons[1])
for i in 0 ..< v.len:
pack(v.sons[i], typ.sons[1], res +! i * baseSize)
pack(conf, v.sons[i], typ.sons[1], res +! i * baseSize)
of tyObject, tyTuple:
packObject(v, typ, res)
packObject(conf, v, typ, res)
of tyNil:
discard
of tyDistinct, tyGenericInst, tyAlias, tySink:
pack(v, typ.sons[0], res)
pack(conf, v, typ.sons[0], res)
else:
globalError(v.info, "cannot map value to FFI " & typeToString(v.typ))
globalError(conf, v.info, "cannot map value to FFI " & typeToString(v.typ))
proc unpack(x: pointer, typ: PType, n: PNode): PNode
proc unpack(conf: ConfigRef, x: pointer, typ: PType, n: PNode): PNode
proc unpackObjectAdd(x: pointer, n, result: PNode) =
proc unpackObjectAdd(conf: ConfigRef, x: pointer, n, result: PNode) =
case n.kind
of nkRecList:
for i in countup(0, sonsLen(n) - 1):
unpackObjectAdd(x, n.sons[i], result)
unpackObjectAdd(conf, x, n.sons[i], result)
of nkRecCase:
globalError(result.info, "case objects cannot be unpacked")
globalError(conf, result.info, "case objects cannot be unpacked")
of nkSym:
var pair = newNodeI(nkExprColonExpr, result.info, 2)
pair.sons[0] = n
pair.sons[1] = unpack(x +! n.sym.offset, n.sym.typ, nil)
pair.sons[1] = unpack(conf, x +! n.sym.offset, n.sym.typ, nil)
#echo "offset: ", n.sym.name.s, " ", n.sym.offset
result.add pair
else: discard
proc unpackObject(x: pointer, typ: PType, n: PNode): PNode =
proc unpackObject(conf: ConfigRef, x: pointer, typ: PType, n: PNode): PNode =
# compute the field's offsets:
discard typ.getSize
discard getSize(conf, typ)
# iterate over any actual field of 'n' ... if n is nil we need to create
# the nkPar node:
@ -263,36 +267,36 @@ proc unpackObject(x: pointer, typ: PType, n: PNode): PNode =
result = newNode(nkTupleConstr)
result.typ = typ
if typ.n.isNil:
internalError("cannot unpack unnamed tuple")
unpackObjectAdd(x, typ.n, result)
internalError(conf, "cannot unpack unnamed tuple")
unpackObjectAdd(conf, x, typ.n, result)
else:
result = n
if result.kind notin {nkObjConstr, nkPar, nkTupleConstr}:
globalError(n.info, "cannot map value from FFI")
globalError(conf, n.info, "cannot map value from FFI")
if typ.n.isNil:
globalError(n.info, "cannot unpack unnamed tuple")
globalError(conf, n.info, "cannot unpack unnamed tuple")
for i in countup(ord(n.kind == nkObjConstr), sonsLen(n) - 1):
var it = n.sons[i]
if it.kind == nkExprColonExpr:
internalAssert it.sons[0].kind == nkSym
internalAssert conf, it.sons[0].kind == nkSym
let field = it.sons[0].sym
it.sons[1] = unpack(x +! field.offset, field.typ, it.sons[1])
it.sons[1] = unpack(conf, x +! field.offset, field.typ, it.sons[1])
else:
let field = getField(typ.n, i)
n.sons[i] = unpack(x +! field.offset, field.typ, it)
let field = getField(conf, typ.n, i)
n.sons[i] = unpack(conf, x +! field.offset, field.typ, it)
proc unpackArray(x: pointer, typ: PType, n: PNode): PNode =
proc unpackArray(conf: ConfigRef, x: pointer, typ: PType, n: PNode): PNode =
if n.isNil:
result = newNode(nkBracket)
result.typ = typ
newSeq(result.sons, lengthOrd(typ).int)
newSeq(result.sons, lengthOrd(conf, typ).int)
else:
result = n
if result.kind != nkBracket:
globalError(n.info, "cannot map value from FFI")
let baseSize = typ.sons[1].getSize
globalError(conf, n.info, "cannot map value from FFI")
let baseSize = getSize(conf, typ.sons[1])
for i in 0 ..< result.len:
result.sons[i] = unpack(x +! i * baseSize, typ.sons[1], result.sons[i])
result.sons[i] = unpack(conf, x +! i * baseSize, typ.sons[1], result.sons[i])
proc canonNodeKind(k: TNodeKind): TNodeKind =
case k
@ -301,7 +305,7 @@ proc canonNodeKind(k: TNodeKind): TNodeKind =
of nkStrLit..nkTripleStrLit: result = nkStrLit
else: result = k
proc unpack(x: pointer, typ: PType, n: PNode): PNode =
proc unpack(conf: ConfigRef, x: pointer, typ: PType, n: PNode): PNode =
template aw(k, v, field: untyped): untyped =
if n.isNil:
result = newNode(k)
@ -313,7 +317,7 @@ proc unpack(x: pointer, typ: PType, n: PNode): PNode =
#echo "expected ", k, " but got ", result.kind
#debug result
return newNodeI(nkExceptBranch, n.info)
#globalError(n.info, "cannot map value from FFI")
#globalError(conf, n.info, "cannot map value from FFI")
result.field = v
template setNil() =
@ -344,13 +348,13 @@ proc unpack(x: pointer, typ: PType, n: PNode): PNode =
of tyUInt32: awi(nkUInt32Lit, rd(uint32, x).BiggestInt)
of tyUInt64: awi(nkUInt64Lit, rd(uint64, x).BiggestInt)
of tyEnum:
case typ.getSize
case getSize(conf, typ)
of 1: awi(nkIntLit, rd(uint8, x).BiggestInt)
of 2: awi(nkIntLit, rd(uint16, x).BiggestInt)
of 4: awi(nkIntLit, rd(int32, x).BiggestInt)
of 8: awi(nkIntLit, rd(int64, x).BiggestInt)
else:
globalError(n.info, "cannot map value from FFI (tyEnum, tySet)")
globalError(conf, n.info, "cannot map value from FFI (tyEnum, tySet)")
of tyFloat: awf(nkFloatLit, rd(float, x))
of tyFloat32: awf(nkFloat32Lit, rd(float32, x))
of tyFloat64: awf(nkFloat64Lit, rd(float64, x))
@ -371,15 +375,15 @@ proc unpack(x: pointer, typ: PType, n: PNode): PNode =
elif n == nil or n.kind == nkPtrLit:
awi(nkPtrLit, cast[ByteAddress](p))
elif n != nil and n.len == 1:
internalAssert n.kind == nkRefTy
n.sons[0] = unpack(p, typ.lastSon, n.sons[0])
internalAssert(conf, n.kind == nkRefTy)
n.sons[0] = unpack(conf, p, typ.lastSon, n.sons[0])
result = n
else:
globalError(n.info, "cannot map value from FFI " & typeToString(typ))
globalError(conf, n.info, "cannot map value from FFI " & typeToString(typ))
of tyObject, tyTuple:
result = unpackObject(x, typ, n)
result = unpackObject(conf, x, typ, n)
of tyArray:
result = unpackArray(x, typ, n)
result = unpackArray(conf, x, typ, n)
of tyCString, tyString:
let p = rd(cstring, x)
if p.isNil:
@ -389,12 +393,12 @@ proc unpack(x: pointer, typ: PType, n: PNode): PNode =
of tyNil:
setNil()
of tyDistinct, tyGenericInst, tyAlias, tySink:
result = unpack(x, typ.lastSon, n)
result = unpack(conf, x, typ.lastSon, n)
else:
# XXX what to do with 'array' here?
globalError(n.info, "cannot map value from FFI " & typeToString(typ))
globalError(conf, n.info, "cannot map value from FFI " & typeToString(typ))
proc fficast*(x: PNode, destTyp: PType): PNode =
proc fficast*(conf: ConfigRef, x: PNode, destTyp: PType): PNode =
if x.kind == nkPtrLit and x.typ.kind in {tyPtr, tyRef, tyVar, tyLent, tyPointer,
tyProc, tyCString, tyString,
tySequence}:
@ -404,93 +408,94 @@ proc fficast*(x: PNode, destTyp: PType): PNode =
result = newNodeIT(x.kind, x.info, destTyp)
else:
# we play safe here and allocate the max possible size:
let size = max(packSize(x, x.typ), packSize(x, destTyp))
let size = max(packSize(conf, x, x.typ), packSize(conf, x, destTyp))
var a = alloc0(size)
pack(x, x.typ, a)
pack(conf, x, x.typ, a)
# cast through a pointer needs a new inner object:
let y = if x.kind == nkRefTy: newNodeI(nkRefTy, x.info, 1)
else: x.copyTree
y.typ = x.typ
result = unpack(a, destTyp, y)
result = unpack(conf, a, destTyp, y)
dealloc a
proc callForeignFunction*(call: PNode): PNode =
internalAssert call.sons[0].kind == nkPtrLit
proc callForeignFunction*(conf: ConfigRef, call: PNode): PNode =
internalAssert conf, call.sons[0].kind == nkPtrLit
var cif: TCif
var sig: TParamList
# use the arguments' types for varargs support:
for i in 1..call.len-1:
sig[i-1] = mapType(call.sons[i].typ)
sig[i-1] = mapType(conf, call.sons[i].typ)
if sig[i-1].isNil:
globalError(call.info, "cannot map FFI type")
globalError(conf, call.info, "cannot map FFI type")
let typ = call.sons[0].typ
if prep_cif(cif, mapCallConv(typ.callConv, call.info), cuint(call.len-1),
mapType(typ.sons[0]), sig) != OK:
globalError(call.info, "error in FFI call")
if prep_cif(cif, mapCallConv(conf, typ.callConv, call.info), cuint(call.len-1),
mapType(conf, typ.sons[0]), sig) != OK:
globalError(conf, call.info, "error in FFI call")
var args: TArgList
let fn = cast[pointer](call.sons[0].intVal)
for i in 1 .. call.len-1:
var t = call.sons[i].typ
args[i-1] = alloc0(packSize(call.sons[i], t))
pack(call.sons[i], t, args[i-1])
args[i-1] = alloc0(packSize(conf, call.sons[i], t))
pack(conf, call.sons[i], t, args[i-1])
let retVal = if isEmptyType(typ.sons[0]): pointer(nil)
else: alloc(typ.sons[0].getSize.int)
else: alloc(getSize(conf, typ.sons[0]).int)
libffi.call(cif, fn, retVal, args)
if retVal.isNil:
result = newNode(nkEmpty)
else:
result = unpack(retVal, typ.sons[0], nil)
result = unpack(conf, retVal, typ.sons[0], nil)
result.info = call.info
if retVal != nil: dealloc retVal
for i in 1 .. call.len-1:
call.sons[i] = unpack(args[i-1], typ.sons[i], call[i])
call.sons[i] = unpack(conf, args[i-1], typ.sons[i], call[i])
dealloc args[i-1]
proc callForeignFunction*(fn: PNode, fntyp: PType,
proc callForeignFunction*(conf: ConfigRef, fn: PNode, fntyp: PType,
args: var TNodeSeq, start, len: int,
info: TLineInfo): PNode =
internalAssert fn.kind == nkPtrLit
internalAssert conf, fn.kind == nkPtrLit
var cif: TCif
var sig: TParamList
for i in 0..len-1:
var aTyp = args[i+start].typ
if aTyp.isNil:
internalAssert i+1 < fntyp.len
internalAssert conf, i+1 < fntyp.len
aTyp = fntyp.sons[i+1]
args[i+start].typ = aTyp
sig[i] = mapType(aTyp)
if sig[i].isNil: globalError(info, "cannot map FFI type")
sig[i] = mapType(conf, aTyp)
if sig[i].isNil: globalError(conf, info, "cannot map FFI type")
if prep_cif(cif, mapCallConv(fntyp.callConv, info), cuint(len),
mapType(fntyp.sons[0]), sig) != OK:
globalError(info, "error in FFI call")
if prep_cif(cif, mapCallConv(conf, fntyp.callConv, info), cuint(len),
mapType(conf, fntyp.sons[0]), sig) != OK:
globalError(conf, info, "error in FFI call")
var cargs: TArgList
let fn = cast[pointer](fn.intVal)
for i in 0 .. len-1:
let t = args[i+start].typ
cargs[i] = alloc0(packSize(args[i+start], t))
pack(args[i+start], t, cargs[i])
cargs[i] = alloc0(packSize(conf, args[i+start], t))
pack(conf, args[i+start], t, cargs[i])
let retVal = if isEmptyType(fntyp.sons[0]): pointer(nil)
else: alloc(fntyp.sons[0].getSize.int)
else: alloc(getSize(conf, fntyp.sons[0]).int)
libffi.call(cif, fn, retVal, cargs)
if retVal.isNil:
result = newNode(nkEmpty)
else:
result = unpack(retVal, fntyp.sons[0], nil)
result = unpack(conf, retVal, fntyp.sons[0], nil)
result.info = info
if retVal != nil: dealloc retVal
for i in 0 .. len-1:
let t = args[i+start].typ
args[i+start] = unpack(cargs[i], t, args[i+start])
args[i+start] = unpack(conf, cargs[i], t, args[i+start])
dealloc cargs[i]