# # # The Nimrod Compiler # (c) Copyright 2012 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 Nimrod code. import ast, astalgo, ropes, types, options, tables, dynlib, libffi, msgs when defined(windows): const libcDll = "msvcrt.dll" else: const libcDll = "libc.so(.6|.5|)" type TDllCache = tables.TTable[string, TLibHandle] var gDllCache = initTable[string, TLibHandle]() gExeHandle = LoadLib() proc getDll(cache: var TDllCache; dll: string; info: TLineInfo): pointer = result = cache[dll] 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, errGenerated, "cannot load: " & dll) cache[dll] = result const nkPtrLit = nkIntLit # hopefully we can get rid of this hack soon proc importcSymbol*(sym: PSym): PNode = let name = ropeToStr(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) case name of "stdin": result.intVal = cast[TAddress](system.stdin) of "stdout": result.intVal = cast[TAddress](system.stdout) of "stderr": result.intVal = cast[TAddress](system.stderr) else: let lib = sym.annex if lib != nil and lib.path.kind notin {nkStrLit..nkTripleStrLit}: GlobalError(sym.info, errGenerated, "dynlib needs to be a string lit for the REPL") var theAddr: pointer if lib.isNil and not gExehandle.isNil: # first try this exe itself: theAddr = gExehandle.symAddr(name) # then try libc: if theAddr.isNil: let dllhandle = gDllCache.getDll(libcDll) theAddr = dllhandle.checkedSymAddr(name) else: let dllhandle = gDllCache.getDll(lib.path.strVal, sym.info) theAddr = dllhandle.checkedSymAddr(name) result.intVal = cast[TAddress](theAddr) proc mapType(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 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: InternalError("cannot map type to FFI") of tyFloat, tyFloat64: result = addr libffi.type_double of tyFloat32: result = addr libffi.type_float of tyVar, tyPointer, tyPtr, tyRef, tyCString, tySequence, tyString, tyExpr, tyStmt, tyTypeDesc, tyProc, tyArray, tyArrayConstr, tyNil: result = addr libffi.type_pointer of tyDistinct: result = mapType(t.sons[0]) else: InternalError("cannot map type to FFI") # too risky: #of tyFloat128: result = addr libffi.type_longdouble proc mapCallConv(cc: TCallingConvention): TABI = case cc of ccDefault: result = DEFAULT_ABI of ccStdCall: result = when defined(windows): STDCALL else: DEFAULT_ABI of ccCDecl: result = DEFAULT_ABI else: InternalError("cannot map calling convention to FFI") template rd(T, p: expr): expr {.immediate.} = (cast[ptr T](p))[] template wr(T, p, v: expr) {.immediate.} = (cast[ptr T](p))[] = v template `+!`(x, y: expr): expr {.immediate.} = cast[pointer](cast[TAddress](x) + y) proc pack(v: PNode, typ: PType, res: pointer) = template awr(T, v: expr) {.immediate, dirty.} = wr(T, 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 v.typ.getSize 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: InternalError("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: if v.kind == nkNilLit: # nothing to do since the memory is 0 initialized anyway nil elif v.kind == nkPtrLit: awr(pointer, cast[pointer](v.intVal)) else: InternalError("cannot map pointer/proc value to FFI") of tyPtr, tyRef, tyVar: if v.kind == nkNilLit: # nothing to do since the memory is 0 initialized anyway nil elif v.kind == nkPtrLit: awr(pointer, cast[pointer](v.intVal)) else: # XXX this is pretty hard: we need to allocate a new buffer and store it # somewhere to be freed; we also need to write back any changes to the # data! InternalError("cannot map pointer/proc value to FFI") of tyCString, tyString: if v.kind == nkNilLit: nil else: awr(cstring, cstring(v.strVal)) of tyArray, tyArrayConstr: let baseSize = typ.sons[1].getSize assert(v.len == lengthOrd(typ.sons[0])) for i in 0 ..