diff --git a/compiler/evalffi.nim b/compiler/evalffi.nim new file mode 100644 index 000000000..b1a23802d --- /dev/null +++ b/compiler/evalffi.nim @@ -0,0 +1,496 @@ +# +# +# 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, astalgo, ropes, types, options, tables, dynlib, libffi, msgs, os + +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]() + +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] + 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 + +const + nkPtrLit = nkIntLit # hopefully we can get rid of this hack soon + +var myerrno {.importc: "errno", header: "".}: cint ## error variable + +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) + of "vmErrnoWrapper": result.intVal = cast[TAddress](myerrno) + 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") + 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, 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) + theAddr = dllhandle.symAddr(name) + if theAddr.isNil: globalError(sym.info, "cannot import: " & sym.name.s) + 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: result = nil + 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, tyStatic, tyNil: + result = addr libffi.type_pointer + of tyDistinct: + result = mapType(t.sons[0]) + else: + result = nil + # too risky: + #of tyFloat128: result = addr libffi.type_longdouble + +proc mapCallConv(cc: TCallingConvention, info: TLineInfo): TABI = + case cc + of ccDefault: result = DEFAULT_ABI + of ccStdCall: result = when defined(windows): STDCALL else: DEFAULT_ABI + of ccCDecl: result = DEFAULT_ABI + else: + globalError(info, "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 packSize(v: PNode, typ: PType): int = + ## computes the size of the blob + case typ.kind + of tyPtr, tyRef, tyVar: + if v.kind in {nkNilLit, nkPtrLit}: + result = sizeof(pointer) + else: + result = sizeof(pointer) + packSize(v.sons[0], typ.lastSon) + of tyDistinct, tyGenericInst: + result = packSize(v, typ.sons[0]) + of tyArray, tyArrayConstr: + # 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]) + else: + result = typ.getSize.int + +proc pack(v: PNode, typ: PType, res: pointer) + +proc getField(n: PNode; position: int): PSym = + case n.kind + of nkRecList: + for i in countup(0, sonsLen(n) - 1): + result = getField(n.sons[i], position) + if result != nil: return + of nkRecCase: + result = getField(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) + if result != nil: return + else: internalError(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} + # compute the field's offsets: + discard typ.getSize + 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 + let field = it.sons[0].sym + pack(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) + else: + # XXX: todo + globalError(x.info, "cannot pack unnamed tuple") + +const maxPackDepth = 20 +var packRecCheck = 0 + +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: + globalError(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(v.info, "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 + discard + elif v.kind == nkPtrLit: + awr(pointer, cast[pointer](v.intVal)) + else: + if packRecCheck > maxPackDepth: + packRecCheck = 0 + globalError(v.info, "cannot map value to FFI " & typeToString(v.typ)) + inc packRecCheck + pack(v.sons[0], typ.lastSon, res +! sizeof(pointer)) + dec packRecCheck + awr(pointer, res +! sizeof(pointer)) + of tyArray, tyArrayConstr: + let baseSize = typ.sons[1].getSize + for i in 0 ..