123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503 |
- #
- #
- # The Nim Compiler
- # (c) Copyright 2015 Andreas Rumpf
- #
- # See the file "copying.txt", included in this
- # distribution, for details about the copyright.
- #
- ## This file implements the FFI part of the evaluator for Nim code.
- import ast, types, options, tables, dynlib, msgs, lineinfos
- from os import getAppFilename
- import pkg/libffi
- when defined(windows):
- const libcDll = "msvcrt.dll"
- elif defined(linux):
- const libcDll = "libc.so(.6|.5|)"
- elif defined(openbsd):
- const libcDll = "/usr/lib/libc.so(.95.1|)"
- elif defined(bsd):
- const libcDll = "/lib/libc.so.7"
- elif defined(osx):
- const libcDll = "/usr/lib/libSystem.dylib"
- else:
- {.error: "`libcDll` not implemented on this platform".}
- type
- TDllCache = tables.Table[string, LibHandle]
- var
- gDllCache = initTable[string, LibHandle]()
- when defined(windows):
- var gExeHandle = loadLib(getAppFilename())
- else:
- var gExeHandle = loadLib()
- 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:
- globalError(conf, info, "cannot load: " & dll)
- cache[dll] = result
- const
- nkPtrLit = nkIntLit # hopefully we can get rid of this hack soon
- proc importcSymbol*(conf: ConfigRef, sym: PSym): PNode =
- 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)
- when true:
- var libPathMsg = ""
- 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")
- var theAddr: pointer
- if (lib.isNil or lib.kind == libHeader) and not gExeHandle.isNil:
- libPathMsg = "current exe: " & getAppFilename() & " nor libc: " & libcDll
- # first try this exe itself:
- theAddr = gExeHandle.symAddr(name.cstring)
- # then try libc:
- if theAddr.isNil:
- let dllhandle = getDll(conf, gDllCache, libcDll, sym.info)
- theAddr = dllhandle.symAddr(name.cstring)
- elif not lib.isNil:
- let dll = if lib.kind == libHeader: libcDll else: lib.path.strVal
- libPathMsg = dll
- let dllhandle = getDll(conf, gDllCache, dll, sym.info)
- theAddr = dllhandle.symAddr(name.cstring)
- if theAddr.isNil: globalError(conf, sym.info,
- "cannot import symbol: " & name & " from " & libPathMsg)
- result.intVal = cast[int](theAddr)
- proc mapType(conf: ConfigRef, t: ast.PType): ptr libffi.Type =
- if t == nil: return addr libffi.type_void
- case t.kind
- of tyBool, tyEnum, tyChar, tyInt..tyInt64, tyUInt..tyUInt64, tySet:
- 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
- of 8: result = addr libffi.type_sint64
- else: result = nil
- of tyFloat, tyFloat64: result = addr libffi.type_double
- of tyFloat32: result = addr libffi.type_float
- of tyVar, tyLent, tyPointer, tyPtr, tyRef, tyCstring, tySequence, tyString, tyUntyped,
- tyTyped, tyTypeDesc, tyProc, tyArray, tyStatic, tyNil:
- result = addr libffi.type_pointer
- of tyDistinct, tyAlias, tySink:
- result = mapType(conf, t[0])
- else:
- result = nil
- # too risky:
- #of tyFloat128: result = addr libffi.type_longdouble
- proc mapCallConv(conf: ConfigRef, cc: TCallingConvention, info: TLineInfo): TABI =
- case cc
- of ccNimCall: result = DEFAULT_ABI
- of ccStdCall: result = when defined(windows) and defined(x86): STDCALL else: DEFAULT_ABI
- of ccCDecl: result = DEFAULT_ABI
- else:
- globalError(conf, info, "cannot map calling convention to FFI")
- template rd(typ, p: untyped): untyped = (cast[ptr typ](p))[]
- template wr(typ, p, v: untyped): untyped = (cast[ptr typ](p))[] = v
- template `+!`(x, y: untyped): untyped =
- cast[pointer](cast[int](x) + y)
- 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(conf, v[0], typ.lastSon)
- of tyDistinct, tyGenericInst, tyAlias, tySink:
- result = packSize(conf, v, typ[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(conf, v[0], typ[1])
- else:
- result = getSize(conf, typ).int
- proc pack(conf: ConfigRef, v: PNode, typ: PType, res: pointer)
- proc getField(conf: ConfigRef, n: PNode; position: int): PSym =
- case n.kind
- of nkRecList:
- for i in 0..<n.len:
- result = getField(conf, n[i], position)
- if result != nil: return
- of nkRecCase:
- result = getField(conf, n[0], position)
- if result != nil: return
- for i in 1..<n.len:
- case n[i].kind
- of nkOfBranch, nkElse:
- result = getField(conf, lastSon(n[i]), position)
- if result != nil: return
- else: internalError(conf, n.info, "getField(record case branch)")
- of nkSym:
- if n.sym.position == position: result = n.sym
- else: discard
- proc packObject(conf: ConfigRef, x: PNode, typ: PType, res: pointer) =
- internalAssert conf, x.kind in {nkObjConstr, nkPar, nkTupleConstr}
- # compute the field's offsets:
- discard getSize(conf, typ)
- for i in ord(x.kind == nkObjConstr)..<x.len:
- var it = x[i]
- if it.kind == nkExprColonExpr:
- internalAssert conf, it[0].kind == nkSym
- let field = it[0].sym
- pack(conf, it[1], field.typ, res +! field.offset)
- elif typ.n != nil:
- let field = getField(conf, typ.n, i)
- pack(conf, it, field.typ, res +! field.offset)
- else:
- # XXX: todo
- globalError(conf, x.info, "cannot pack unnamed tuple")
- const maxPackDepth = 20
- var packRecCheck = 0
- proc pack(conf: ConfigRef, v: PNode, typ: PType, res: pointer) =
- template awr(typ, v: untyped): untyped =
- wr(typ, res, v)
- case typ.kind
- of tyBool: awr(bool, v.intVal != 0)
- of tyChar: awr(char, v.intVal.chr)
- of tyInt: awr(int, v.intVal.int)
- of tyInt8: awr(int8, v.intVal.int8)
- of tyInt16: awr(int16, v.intVal.int16)
- of tyInt32: awr(int32, v.intVal.int32)
- of tyInt64: awr(int64, v.intVal.int64)
- of tyUInt: awr(uint, v.intVal.uint)
- of tyUInt8: awr(uint8, v.intVal.uint8)
- of tyUInt16: awr(uint16, v.intVal.uint16)
- of tyUInt32: awr(uint32, v.intVal.uint32)
- of tyUInt64: awr(uint64, v.intVal.uint64)
- of tyEnum, tySet:
- 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(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)
- of tyPointer, tyProc, tyCstring, tyString:
- if v.kind == nkNilLit:
- # nothing to do since the memory is 0 initialized anyway
- discard
- elif v.kind == nkPtrLit:
- awr(pointer, cast[pointer](v.intVal))
- elif v.kind in {nkStrLit..nkTripleStrLit}:
- awr(cstring, cstring(v.strVal))
- else:
- 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
- discard
- elif v.kind == nkPtrLit:
- awr(pointer, cast[pointer](v.intVal))
- else:
- if packRecCheck > maxPackDepth:
- packRecCheck = 0
- globalError(conf, v.info, "cannot map value to FFI " & typeToString(v.typ))
- inc packRecCheck
- pack(conf, v[0], typ.lastSon, res +! sizeof(pointer))
- dec packRecCheck
- awr(pointer, res +! sizeof(pointer))
- of tyArray:
- let baseSize = getSize(conf, typ[1])
- for i in 0..<v.len:
- pack(conf, v[i], typ[1], res +! i * baseSize)
- of tyObject, tyTuple:
- packObject(conf, v, typ, res)
- of tyNil:
- discard
- of tyDistinct, tyGenericInst, tyAlias, tySink:
- pack(conf, v, typ[0], res)
- else:
- globalError(conf, v.info, "cannot map value to FFI " & typeToString(v.typ))
- proc unpack(conf: ConfigRef, x: pointer, typ: PType, n: PNode): PNode
- proc unpackObjectAdd(conf: ConfigRef, x: pointer, n, result: PNode) =
- case n.kind
- of nkRecList:
- for i in 0..<n.len:
- unpackObjectAdd(conf, x, n[i], result)
- of nkRecCase:
- globalError(conf, result.info, "case objects cannot be unpacked")
- of nkSym:
- var pair = newNodeI(nkExprColonExpr, result.info, 2)
- pair[0] = n
- pair[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(conf: ConfigRef, x: pointer, typ: PType, n: PNode): PNode =
- # compute the field's offsets:
- discard getSize(conf, typ)
- # iterate over any actual field of 'n' ... if n is nil we need to create
- # the nkPar node:
- if n.isNil:
- result = newNode(nkTupleConstr)
- result.typ = typ
- if typ.n.isNil:
- internalError(conf, "cannot unpack unnamed tuple")
- unpackObjectAdd(conf, x, typ.n, result)
- else:
- result = n
- if result.kind notin {nkObjConstr, nkPar, nkTupleConstr}:
- globalError(conf, n.info, "cannot map value from FFI")
- if typ.n.isNil:
- globalError(conf, n.info, "cannot unpack unnamed tuple")
- for i in ord(n.kind == nkObjConstr)..<n.len:
- var it = n[i]
- if it.kind == nkExprColonExpr:
- internalAssert conf, it[0].kind == nkSym
- let field = it[0].sym
- it[1] = unpack(conf, x +! field.offset, field.typ, it[1])
- else:
- let field = getField(conf, typ.n, i)
- n[i] = unpack(conf, x +! field.offset, field.typ, it)
- proc unpackArray(conf: ConfigRef, x: pointer, typ: PType, n: PNode): PNode =
- if n.isNil:
- result = newNode(nkBracket)
- result.typ = typ
- newSeq(result.sons, lengthOrd(conf, typ).toInt)
- else:
- result = n
- if result.kind != nkBracket:
- globalError(conf, n.info, "cannot map value from FFI")
- let baseSize = getSize(conf, typ[1])
- for i in 0..<result.len:
- result[i] = unpack(conf, x +! i * baseSize, typ[1], result[i])
- proc canonNodeKind(k: TNodeKind): TNodeKind =
- case k
- of nkCharLit..nkUInt64Lit: result = nkIntLit
- of nkFloatLit..nkFloat128Lit: result = nkFloatLit
- of nkStrLit..nkTripleStrLit: result = nkStrLit
- else: result = k
- proc unpack(conf: ConfigRef, x: pointer, typ: PType, n: PNode): PNode =
- template aw(k, v, field: untyped): untyped =
- if n.isNil:
- result = newNode(k)
- result.typ = typ
- else:
- # check we have the right field:
- result = n
- if result.kind.canonNodeKind != k.canonNodeKind:
- #echo "expected ", k, " but got ", result.kind
- #debug result
- return newNodeI(nkExceptBranch, n.info)
- #globalError(conf, n.info, "cannot map value from FFI")
- result.field = v
- template setNil() =
- if n.isNil:
- result = newNode(nkNilLit)
- result.typ = typ
- else:
- reset n[]
- result = n
- result[] = TNode(kind: nkNilLit)
- result.typ = typ
- template awi(kind, v: untyped): untyped = aw(kind, v, intVal)
- template awf(kind, v: untyped): untyped = aw(kind, v, floatVal)
- template aws(kind, v: untyped): untyped = aw(kind, v, strVal)
- case typ.kind
- of tyBool: awi(nkIntLit, rd(bool, x).ord)
- of tyChar: awi(nkCharLit, rd(char, x).ord)
- of tyInt: awi(nkIntLit, rd(int, x))
- of tyInt8: awi(nkInt8Lit, rd(int8, x))
- of tyInt16: awi(nkInt16Lit, rd(int16, x))
- of tyInt32: awi(nkInt32Lit, rd(int32, x))
- of tyInt64: awi(nkInt64Lit, rd(int64, x))
- of tyUInt: awi(nkUIntLit, rd(uint, x).BiggestInt)
- of tyUInt8: awi(nkUInt8Lit, rd(uint8, x).BiggestInt)
- of tyUInt16: awi(nkUInt16Lit, rd(uint16, x).BiggestInt)
- of tyUInt32: awi(nkUInt32Lit, rd(uint32, x).BiggestInt)
- of tyUInt64: awi(nkUInt64Lit, rd(uint64, x).BiggestInt)
- of tyEnum:
- 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(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))
- of tyPointer, tyProc:
- let p = rd(pointer, x)
- if p.isNil:
- setNil()
- elif n != nil and n.kind == nkStrLit:
- # we passed a string literal as a pointer; however strings are already
- # in their unboxed representation so nothing it to be unpacked:
- result = n
- else:
- awi(nkPtrLit, cast[int](p))
- of tyPtr, tyRef, tyVar, tyLent:
- let p = rd(pointer, x)
- if p.isNil:
- setNil()
- elif n == nil or n.kind == nkPtrLit:
- awi(nkPtrLit, cast[int](p))
- elif n != nil and n.len == 1:
- internalAssert(conf, n.kind == nkRefTy)
- n[0] = unpack(conf, p, typ.lastSon, n[0])
- result = n
- else:
- globalError(conf, n.info, "cannot map value from FFI " & typeToString(typ))
- of tyObject, tyTuple:
- result = unpackObject(conf, x, typ, n)
- of tyArray:
- result = unpackArray(conf, x, typ, n)
- of tyCstring, tyString:
- let p = rd(cstring, x)
- if p.isNil:
- setNil()
- else:
- aws(nkStrLit, $p)
- of tyNil:
- setNil()
- of tyDistinct, tyGenericInst, tyAlias, tySink:
- result = unpack(conf, x, typ.lastSon, n)
- else:
- # XXX what to do with 'array' here?
- globalError(conf, n.info, "cannot map value from FFI " & typeToString(typ))
- 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}:
- result = newNodeIT(x.kind, x.info, destTyp)
- result.intVal = x.intVal
- elif x.kind == nkNilLit:
- result = newNodeIT(x.kind, x.info, destTyp)
- else:
- # we play safe here and allocate the max possible size:
- let size = max(packSize(conf, x, x.typ), packSize(conf, x, destTyp))
- var a = alloc0(size)
- 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(conf, a, destTyp, y)
- dealloc a
- proc callForeignFunction*(conf: ConfigRef, call: PNode): PNode =
- internalAssert conf, call[0].kind == nkPtrLit
- var cif: TCif
- var sig: ParamList
- # use the arguments' types for varargs support:
- for i in 1..<call.len:
- sig[i-1] = mapType(conf, call[i].typ)
- if sig[i-1].isNil:
- globalError(conf, call.info, "cannot map FFI type")
- let typ = call[0].typ
- if prep_cif(cif, mapCallConv(conf, typ.callConv, call.info), cuint(call.len-1),
- mapType(conf, typ[0]), sig) != OK:
- globalError(conf, call.info, "error in FFI call")
- var args: ArgList
- let fn = cast[pointer](call[0].intVal)
- for i in 1..<call.len:
- var t = call[i].typ
- args[i-1] = alloc0(packSize(conf, call[i], t))
- pack(conf, call[i], t, args[i-1])
- let retVal = if isEmptyType(typ[0]): pointer(nil)
- else: alloc(getSize(conf, typ[0]).int)
- libffi.call(cif, fn, retVal, args)
- if retVal.isNil:
- result = newNode(nkEmpty)
- else:
- result = unpack(conf, retVal, typ[0], nil)
- result.info = call.info
- if retVal != nil: dealloc retVal
- for i in 1..<call.len:
- call[i] = unpack(conf, args[i-1], typ[i], call[i])
- dealloc args[i-1]
- proc callForeignFunction*(conf: ConfigRef, fn: PNode, fntyp: PType,
- args: var TNodeSeq, start, len: int,
- info: TLineInfo): PNode =
- internalAssert conf, fn.kind == nkPtrLit
- var cif: TCif
- var sig: ParamList
- for i in 0..len-1:
- var aTyp = args[i+start].typ
- if aTyp.isNil:
- internalAssert conf, i+1 < fntyp.len
- aTyp = fntyp[i+1]
- args[i+start].typ = aTyp
- sig[i] = mapType(conf, aTyp)
- if sig[i].isNil: globalError(conf, info, "cannot map FFI type")
- if prep_cif(cif, mapCallConv(conf, fntyp.callConv, info), cuint(len),
- mapType(conf, fntyp[0]), sig) != OK:
- globalError(conf, info, "error in FFI call")
- var cargs: ArgList
- let fn = cast[pointer](fn.intVal)
- for i in 0..len-1:
- let t = args[i+start].typ
- cargs[i] = alloc0(packSize(conf, args[i+start], t))
- pack(conf, args[i+start], t, cargs[i])
- let retVal = if isEmptyType(fntyp[0]): pointer(nil)
- else: alloc(getSize(conf, fntyp[0]).int)
- libffi.call(cif, fn, retVal, cargs)
- if retVal.isNil:
- result = newNode(nkEmpty)
- else:
- result = unpack(conf, retVal, fntyp[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(conf, cargs[i], t, args[i+start])
- dealloc cargs[i]
|