# # # 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[ByteAddress](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[ByteAddress](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.. 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..