* 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

@ -34,7 +34,7 @@ bootSwitch(usedTinyC, hasTinyCBackend, "-d:tinyc")
bootSwitch(usedNativeStacktrace, bootSwitch(usedNativeStacktrace,
defined(nativeStackTrace) and nativeStackTraceSupported, defined(nativeStackTrace) and nativeStackTraceSupported,
"-d:nativeStackTrace") "-d:nativeStackTrace")
bootSwitch(usedFFI, hasFFI, "-d:useFFI") bootSwitch(usedFFI, hasFFI, "-d:nimHasLibFFI")
type type
TCmdLinePass* = enum TCmdLinePass* = enum

View file

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

View file

@ -107,6 +107,7 @@ when not defined(leanCompiler):
proc interactivePasses(graph: ModuleGraph) = proc interactivePasses(graph: ModuleGraph) =
initDefines(graph.config.symbols) initDefines(graph.config.symbols)
defineSymbol(graph.config.symbols, "nimscript") defineSymbol(graph.config.symbols, "nimscript")
# note: seems redundant with -d:nimHasLibFFI
when hasFFI: defineSymbol(graph.config.symbols, "nimffi") when hasFFI: defineSymbol(graph.config.symbols, "nimffi")
registerPass(graph, verbosePass) registerPass(graph, verbosePass)
registerPass(graph, semPass) registerPass(graph, semPass)

View file

@ -18,7 +18,7 @@ const
hasTinyCBackend* = defined(tinyc) hasTinyCBackend* = defined(tinyc)
useEffectSystem* = true useEffectSystem* = true
useWriteTracking* = false useWriteTracking* = false
hasFFI* = defined(useFFI) hasFFI* = defined(nimHasLibFFI)
copyrightYear* = "2018" copyrightYear* = "2018"
type # please make sure we have under 32 options type # please make sure we have under 32 options
@ -128,6 +128,10 @@ type
forLoopMacros, forLoopMacros,
caseStmtMacros, caseStmtMacros,
codeReordering, codeReordering,
compiletimeFFI,
## This requires building nim with `-d:nimHasLibFFI`
## which itself requires `nimble install libffi`, see #10150
## Note: this feature can't be localized with {.push.}
SymbolFilesOption* = enum SymbolFilesOption* = enum
disabledSf, writeOnlySf, readOnlySf, v2Sf disabledSf, writeOnlySf, readOnlySf, v2Sf

View file

@ -498,7 +498,14 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
let ra = instr.regA let ra = instr.regA
when traceCode: when traceCode:
echo "PC ", pc, " ", c.code[pc].opcode, " ra ", ra, " rb ", instr.regB, " rc ", instr.regC template regDescr(name, r): string =
let kind = if r < regs.len: $regs[r].kind else: ""
let ret = name & ": " & $r & " " & $kind
alignLeft(ret, 15)
echo "PC:$pc $opcode $ra $rb $rc" % [
"pc", $pc, "opcode", alignLeft($c.code[pc].opcode, 15),
"ra", regDescr("ra", ra), "rb", regDescr("rb", instr.regB),
"rc", regDescr("rc", instr.regC)]
case instr.opcode case instr.opcode
of opcEof: return regs[ra] of opcEof: return regs[ra]
@ -1072,15 +1079,19 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
currentException: c.currentExceptionA, currentException: c.currentExceptionA,
currentLineInfo: c.debug[pc])) currentLineInfo: c.debug[pc]))
elif sfImportc in prc.flags: elif sfImportc in prc.flags:
if allowFFI notin c.features: if compiletimeFFI notin c.config.features:
globalError(c.config, c.debug[pc], "VM not allowed to do FFI") globalError(c.config, c.debug[pc], "VM not allowed to do FFI, see `compiletimeFFI`")
# we pass 'tos.slots' instead of 'regs' so that the compiler can keep # we pass 'tos.slots' instead of 'regs' so that the compiler can keep
# 'regs' in a register: # 'regs' in a register:
when hasFFI: when hasFFI:
let prcValue = c.globals.sons[prc.position-1] let prcValue = c.globals.sons[prc.position-1]
if prcValue.kind == nkEmpty: if prcValue.kind == nkEmpty:
globalError(c.config, c.debug[pc], "cannot run " & prc.name.s) globalError(c.config, c.debug[pc], "cannot run " & prc.name.s)
let newValue = callForeignFunction(prcValue, prc.typ, tos.slots, var slots2: TNodeSeq
slots2.setLen(tos.slots.len)
for i in 0..<tos.slots.len:
slots2[i] = regToNode(tos.slots[i])
let newValue = callForeignFunction(c.config, prcValue, prc.typ, slots2,
rb+1, rc-1, c.debug[pc]) rb+1, rc-1, c.debug[pc])
if newValue.kind != nkEmpty: if newValue.kind != nkEmpty:
assert instr.opcode == opcIndCallAsgn assert instr.opcode == opcIndCallAsgn
@ -1611,9 +1622,13 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
regs[ra].node.ident = getIdent(c.cache, regs[rb].node.strVal) regs[ra].node.ident = getIdent(c.cache, regs[rb].node.strVal)
regs[ra].node.flags.incl nfIsRef regs[ra].node.flags.incl nfIsRef
of opcSetType: of opcSetType:
let typ = c.types[instr.regBx - wordExcess]
if regs[ra].kind != rkNode: if regs[ra].kind != rkNode:
internalError(c.config, c.debug[pc], "cannot set type") let temp = regToNode(regs[ra])
regs[ra].node.typ = c.types[instr.regBx - wordExcess] ensureKind(rkNode)
regs[ra].node = temp
regs[ra].node.info = c.debug[pc]
regs[ra].node.typ = typ
of opcConv: of opcConv:
let rb = instr.regB let rb = instr.regB
inc pc inc pc
@ -1633,8 +1648,10 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
let srctyp = c.types[c.code[pc].regBx - wordExcess] let srctyp = c.types[c.code[pc].regBx - wordExcess]
when hasFFI: when hasFFI:
let dest = fficast(regs[rb], desttyp) let dest = fficast(c.config, regs[rb].node, desttyp)
asgnRef(regs[ra], dest) # todo: check whether this is correct
# asgnRef(regs[ra], dest)
putIntoReg(regs[ra], dest)
else: else:
globalError(c.config, c.debug[pc], "cannot evaluate cast") globalError(c.config, c.debug[pc], "cannot evaluate cast")
of opcNSetIntVal: of opcNSetIntVal:
@ -1921,14 +1938,12 @@ proc setupGlobalCtx*(module: PSym; graph: ModuleGraph) =
proc myOpen(graph: ModuleGraph; module: PSym): PPassContext = proc myOpen(graph: ModuleGraph; module: PSym): PPassContext =
#var c = newEvalContext(module, emRepl) #var c = newEvalContext(module, emRepl)
#c.features = {allowCast, allowFFI, allowInfiniteLoops} #c.features = {allowCast, allowInfiniteLoops}
#pushStackFrame(c, newStackFrame()) #pushStackFrame(c, newStackFrame())
# XXX produce a new 'globals' environment here: # XXX produce a new 'globals' environment here:
setupGlobalCtx(module, graph) setupGlobalCtx(module, graph)
result = PCtx graph.vm result = PCtx graph.vm
when hasFFI:
PCtx(graph.vm).features = {allowFFI, allowCast}
proc myProcess(c: PPassContext, n: PNode): PNode = proc myProcess(c: PPassContext, n: PNode): PNode =
let c = PCtx(c) let c = PCtx(c)

View file

@ -166,7 +166,6 @@ type
TSandboxFlag* = enum ## what the evaluation engine should allow TSandboxFlag* = enum ## what the evaluation engine should allow
allowCast, ## allow unsafe language feature: 'cast' allowCast, ## allow unsafe language feature: 'cast'
allowFFI, ## allow the FFI
allowInfiniteLoops ## allow endless loops allowInfiniteLoops ## allow endless loops
TSandboxFlags* = set[TSandboxFlag] TSandboxFlags* = set[TSandboxFlag]

View file

@ -229,7 +229,9 @@ proc getTemp(cc: PCtx; tt: PType): TRegister =
proc freeTemp(c: PCtx; r: TRegister) = proc freeTemp(c: PCtx; r: TRegister) =
let c = c.prc let c = c.prc
if c.slots[r].kind in {slotSomeTemp..slotTempComplex}: c.slots[r].inUse = false if c.slots[r].kind in {slotSomeTemp..slotTempComplex}:
# this seems to cause https://github.com/nim-lang/Nim/issues/10647
c.slots[r].inUse = false
proc getTempRange(cc: PCtx; n: int; kind: TSlotKind): TRegister = proc getTempRange(cc: PCtx; n: int; kind: TSlotKind): TRegister =
# if register pressure is high, we re-use more aggressively: # if register pressure is high, we re-use more aggressively:
@ -1540,8 +1542,8 @@ proc genTypeLit(c: PCtx; t: PType; dest: var TDest) =
proc importcSym(c: PCtx; info: TLineInfo; s: PSym) = proc importcSym(c: PCtx; info: TLineInfo; s: PSym) =
when hasFFI: when hasFFI:
if allowFFI in c.features: if compiletimeFFI in c.config.features:
c.globals.add(importcSymbol(s)) c.globals.add(importcSymbol(c.config, s))
s.position = c.globals.len s.position = c.globals.len
else: else:
localError(c.config, info, "VM is not allowed to 'importc'") localError(c.config, info, "VM is not allowed to 'importc'")

View file

@ -463,11 +463,18 @@ proc runCI(cmd: string) =
# as that would weaken our testing efforts. # as that would weaken our testing efforts.
when defined(posix): # appveyor (on windows) didn't run this when defined(posix): # appveyor (on windows) didn't run this
kochExecFold("Boot", "boot") kochExecFold("Boot", "boot")
# boot without -d:nimHasLibFFI to make sure this still works
kochExecFold("Boot in release mode", "boot -d:release") kochExecFold("Boot in release mode", "boot -d:release")
## build nimble early on to enable remainder to depend on it if needed ## build nimble early on to enable remainder to depend on it if needed
kochExecFold("Build Nimble", "nimble") kochExecFold("Build Nimble", "nimble")
when not defined(windows):
# pending https://github.com/Araq/libffi/pull/2
# also, that PR works on win32 but not yet win64
execFold("nimble install -y libffi", "nimble install -y libffi")
kochExecFold("boot -d:release -d:nimHasLibFFI", "boot -d:release -d:nimHasLibFFI")
if getEnv("NIM_TEST_PACKAGES", "false") == "true": if getEnv("NIM_TEST_PACKAGES", "false") == "true":
execFold("Test selected Nimble packages", "nim c -r testament/tester cat nimble-extra") execFold("Test selected Nimble packages", "nim c -r testament/tester cat nimble-extra")
else: else:

81
tests/vm/tevalffi.nim Normal file
View file

@ -0,0 +1,81 @@
discard """
cmd: "nim c --experimental:compiletimeFFI $file"
nimout: '''
foo
foo:100
foo:101
foo:102:103
foo:102:103:104
foo:0.03:asdf:103:105
ret={s1:foobar s2:foobar age:25 pi:3.14}
'''
output: '''
foo
foo:100
foo:101
foo:102:103
foo:102:103:104
foo:0.03:asdf:103:105
ret={s1:foobar s2:foobar age:25 pi:3.14}
'''
disabled: "windows"
"""
# re-enable for windows once libffi can be installed in koch.nim
# With win32 (not yet win64), libffi on windows works and this test passes.
when defined(linux):
{.passL: "-lm".} # for exp
proc c_exp(a: float64): float64 {.importc: "exp", header: "<math.h>".}
proc c_printf(frmt: cstring): cint {.importc: "printf", header: "<stdio.h>", varargs, discardable.}
const snprintfName = when defined(windows): "_snprintf" else: "snprintf"
proc c_snprintf*(buffer: pointer, buf_size: uint, format: cstring): cint {.importc: snprintfName, header: "<stdio.h>", varargs .}
proc c_malloc(size:uint):pointer {.importc:"malloc", header: "<stdlib.h>".}
proc c_free(p: pointer) {.importc:"free", header: "<stdlib.h>".}
proc fun() =
block: # c_exp
var x = 0.3
let b = c_exp(x)
let b2 = int(b*1_000_000) # avoids floating point equality
doAssert b2 == 1349858
doAssert c_exp(0.3) == c_exp(x)
const x2 = 0.3
doAssert c_exp(x2) == c_exp(x)
block: # c_printf
c_printf("foo\n")
c_printf("foo:%d\n", 100)
c_printf("foo:%d\n", 101.cint)
c_printf("foo:%d:%d\n", 102.cint, 103.cint)
let temp = 104.cint
c_printf("foo:%d:%d:%d\n", 102.cint, 103.cint, temp)
var temp2 = 105.cint
c_printf("foo:%g:%s:%d:%d\n", 0.03, "asdf", 103.cint, temp2)
block: # c_snprintf, c_malloc, c_free
let n: uint = 50
var buffer2: pointer = c_malloc(n)
var s: cstring = "foobar"
var age: cint = 25
let j = c_snprintf(buffer2, n, "s1:%s s2:%s age:%d pi:%g", s, s, age, 3.14)
c_printf("ret={%s}\n", buffer2)
c_free(buffer2) # not sure it has an effect
block: # c_printf bug
var a = 123
var a2 = a.addr
#[
bug: different behavior between CT RT in this case:
at CT, shows foo2:a=123
at RT, shows foo2:a=<address as int>
]#
if false:
c_printf("foo2:a=%d\n", a2)
static:
fun()
fun()