* 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
parent 30ab7e6bdd
commit adbabf145c
9 changed files with 250 additions and 136 deletions

View File

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

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]

View File

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

View File

@@ -18,7 +18,7 @@ const
hasTinyCBackend* = defined(tinyc)
useEffectSystem* = true
useWriteTracking* = false
hasFFI* = defined(useFFI)
hasFFI* = defined(nimHasLibFFI)
copyrightYear* = "2018"
type # please make sure we have under 32 options
@@ -128,6 +128,10 @@ type
forLoopMacros,
caseStmtMacros,
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
disabledSf, writeOnlySf, readOnlySf, v2Sf

View File

@@ -498,7 +498,14 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
let ra = instr.regA
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
of opcEof: return regs[ra]
@@ -1072,15 +1079,19 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
currentException: c.currentExceptionA,
currentLineInfo: c.debug[pc]))
elif sfImportc in prc.flags:
if allowFFI notin c.features:
globalError(c.config, c.debug[pc], "VM not allowed to do FFI")
if compiletimeFFI notin c.config.features:
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
# 'regs' in a register:
when hasFFI:
let prcValue = c.globals.sons[prc.position-1]
if prcValue.kind == nkEmpty:
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])
if newValue.kind != nkEmpty:
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.flags.incl nfIsRef
of opcSetType:
let typ = c.types[instr.regBx - wordExcess]
if regs[ra].kind != rkNode:
internalError(c.config, c.debug[pc], "cannot set type")
regs[ra].node.typ = c.types[instr.regBx - wordExcess]
let temp = regToNode(regs[ra])
ensureKind(rkNode)
regs[ra].node = temp
regs[ra].node.info = c.debug[pc]
regs[ra].node.typ = typ
of opcConv:
let rb = instr.regB
inc pc
@@ -1633,8 +1648,10 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
let srctyp = c.types[c.code[pc].regBx - wordExcess]
when hasFFI:
let dest = fficast(regs[rb], desttyp)
asgnRef(regs[ra], dest)
let dest = fficast(c.config, regs[rb].node, desttyp)
# todo: check whether this is correct
# asgnRef(regs[ra], dest)
putIntoReg(regs[ra], dest)
else:
globalError(c.config, c.debug[pc], "cannot evaluate cast")
of opcNSetIntVal:
@@ -1921,14 +1938,12 @@ proc setupGlobalCtx*(module: PSym; graph: ModuleGraph) =
proc myOpen(graph: ModuleGraph; module: PSym): PPassContext =
#var c = newEvalContext(module, emRepl)
#c.features = {allowCast, allowFFI, allowInfiniteLoops}
#c.features = {allowCast, allowInfiniteLoops}
#pushStackFrame(c, newStackFrame())
# XXX produce a new 'globals' environment here:
setupGlobalCtx(module, graph)
result = PCtx graph.vm
when hasFFI:
PCtx(graph.vm).features = {allowFFI, allowCast}
proc myProcess(c: PPassContext, n: PNode): PNode =
let c = PCtx(c)

View File

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

View File

@@ -229,7 +229,9 @@ proc getTemp(cc: PCtx; tt: PType): TRegister =
proc freeTemp(c: PCtx; r: TRegister) =
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 =
# 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) =
when hasFFI:
if allowFFI in c.features:
c.globals.add(importcSymbol(s))
if compiletimeFFI in c.config.features:
c.globals.add(importcSymbol(c.config, s))
s.position = c.globals.len
else:
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.
when defined(posix): # appveyor (on windows) didn't run this
kochExecFold("Boot", "boot")
# boot without -d:nimHasLibFFI to make sure this still works
kochExecFold("Boot in release mode", "boot -d:release")
## build nimble early on to enable remainder to depend on it if needed
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":
execFold("Test selected Nimble packages", "nim c -r testament/tester cat nimble-extra")
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()