diff options
author | Araq <rumpf_a@web.de> | 2012-12-19 02:22:39 +0100 |
---|---|---|
committer | Araq <rumpf_a@web.de> | 2012-12-19 02:22:39 +0100 |
commit | 7148812524d26b89dab7efde2e86d318677b502b (patch) | |
tree | aefb4fe12e0aa08e27cfaeceec2c7594c0564f52 | |
parent | 3be576222a2e2a774570eb408d43c35ab94c5f15 (diff) | |
download | Nim-7148812524d26b89dab7efde2e86d318677b502b.tar.gz |
first steps for FFI support at compile time
-rwxr-xr-x | compiler/cgen.nim | 11 | ||||
-rwxr-xr-x | compiler/condsyms.nim | 2 | ||||
-rw-r--r-- | compiler/evalffi.nim | 198 | ||||
-rwxr-xr-x | compiler/evals.nim | 96 | ||||
-rwxr-xr-x | compiler/options.nim | 12 | ||||
-rwxr-xr-x | koch.nim | 1 | ||||
-rw-r--r-- | lib/wrappers/libffi.nim | 149 |
7 files changed, 420 insertions, 49 deletions
diff --git a/compiler/cgen.nim b/compiler/cgen.nim index d024f3479..001a6fbee 100755 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -469,17 +469,6 @@ include "ccgexprs.nim", "ccgstmts.nim" # ----------------------------- dynamic library handling ----------------- # We don't finalize dynamic libs as this does the OS for us. -proc libCandidates(s: string, dest: var TStringSeq) = - var le = strutils.find(s, '(') - var ri = strutils.find(s, ')', le+1) - if le >= 0 and ri > le: - var prefix = substr(s, 0, le - 1) - var suffix = substr(s, ri + 1) - for middle in split(substr(s, le + 1, ri - 1), '|'): - libCandidates(prefix & middle & suffix, dest) - else: - add(dest, s) - proc isGetProcAddr(lib: PLib): bool = let n = lib.path result = n.kind in nkCallKinds and n.typ != nil and diff --git a/compiler/condsyms.nim b/compiler/condsyms.nim index 17366f6e9..4cac7c847 100755 --- a/compiler/condsyms.nim +++ b/compiler/condsyms.nim @@ -61,6 +61,8 @@ proc InitDefines*() = DefineSymbol("nimmixin") DefineSymbol("nimeffects") DefineSymbol("nimbabel") + when defined(useFFI): + DefineSymbol("nimffi") # add platform specific symbols: case targetCPU diff --git a/compiler/evalffi.nim b/compiler/evalffi.nim new file mode 100644 index 000000000..66ed3a87e --- /dev/null +++ b/compiler/evalffi.nim @@ -0,0 +1,198 @@ +# +# +# 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]() + +proc getDll(cache: var TDllCache; dll: string): 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: + InternalError("cannot load: " & dll) + cache[dll] = result + +proc importcSymbol*(sym: PSym): PNode = + let lib = sym.annex + if lib != nil and lib.path.kind notin {nkStrLit..nkTripleStrLit}: + InternalError("dynlib needs to be a string literal for the REPL") + + let dllpath = if lib.isNil: libcDll else: lib.path.strVal + let dllhandle = gDllCache.getDll(dllpath) + let name = ropeToStr(sym.loc.r) + let theAddr = dllhandle.checkedSymAddr(name) + + # the AST does not support untyped pointers directly, so we use an nkIntLit + # that contains the address instead: + result = newNodeIT(nkIntLit, sym.info, sym.typ) + 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: + result = addr libffi.type_pointer + 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 + +proc pack(v: PNode): pointer = + template awr(T, v: expr) {.immediate, dirty.} = + result = alloc0(sizeof(T)) + wr(T, result, v) + + case v.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, tyPtr, tyRef: + if v.kind == nkNilLit: + result = alloc0(sizeof(pointer)) + else: + awr(pointer, cast[pointer](v.intVal)) + of tyCString, tyString: + if v.kind == nkNilLit: + result = alloc0(sizeof(pointer)) + else: + awr(cstring, cstring(v.strVal)) + else: + InternalError("cannot map value to FFI " & typeToString(v.typ)) + +proc unpack(x: pointer, typ: PType, info: TLineInfo): PNode = + template aw(kind, v, field: expr) {.immediate, dirty.} = + result = newNodeIT(kind, info, typ) + result.field = v + + template awi(kind, v: expr) {.immediate, dirty.} = aw(kind, v, intVal) + template awf(kind, v: expr) {.immediate, dirty.} = aw(kind, v, floatVal) + template aws(kind, v: expr) {.immediate, dirty.} = aw(kind, v, strVal) + + case typ.kind + of tyBool: awi(nkIntLit, rd(bool, x).ord) + of tyChar: awi(nkIntLit, rd(char, x).ord) + of tyInt: awi(nkIntLit, rd(int, x)) + of tyInt8: awi(nkIntLit, rd(int8, x)) + of tyInt16: awi(nkIntLit, rd(int16, x)) + of tyInt32: awi(nkIntLit, rd(int32, x)) + of tyInt64: awi(nkIntLit, rd(int64, x)) + of tyUInt: awi(nkIntLit, rd(uint, x).biggestInt) + of tyUInt8: awi(nkIntLit, rd(uint8, x).biggestInt) + of tyUInt16: awi(nkIntLit, rd(uint16, x).biggestInt) + of tyUInt32: awi(nkIntLit, rd(uint32, x).biggestInt) + of tyUInt64: awi(nkIntLit, rd(uint64, x).biggestInt) + of tyEnum: + case typ.getSize + 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: + InternalError("cannot map value from FFI (tyEnum, tySet)") + of tyFloat: awf(nkFloatLit, rd(float, x)) + of tyFloat32: awf(nkFloatLit, rd(float32, x)) + of tyFloat64: awf(nkFloatLit, rd(float64, x)) + of tyPointer, tyProc, tyPtr: + let p = rd(pointer, x) + if p.isNil: + result = newNodeIT(nkNilLit, info, typ) + else: + awi(nkIntLit, cast[TAddress](p)) + of tyCString, tyString: + let p = rd(cstring, x) + if p.isNil: + result = newNodeIT(nkNilLit, info, typ) + else: + aws(nkStrLit, $p) + else: + InternalError("cannot map value from FFI " & typeToString(typ)) + +proc callForeignFunction*(call: PNode): PNode = + InternalAssert call.sons[0].kind == nkIntLit + let typ = call.sons[0].typ + + var cif: TCif + var sig: TParamList + for i in 1..typ.len-1: sig[i-1] = mapType(typ.sons[i]) + + if prep_cif(cif, mapCallConv(typ.callConv), cuint(typ.len-1), + mapType(typ.sons[0]), sig) != OK: + InternalError(call.info, "error in FFI call") + + var args: TArgList + let fn = cast[pointer](call.sons[0].intVal) + for i in 0 .. call.len-1: + args[i] = pack(call.sons[i+1]) + let retVal = alloc(typ.sons[0].getSize.int) + + libffi.call(cif, fn, retVal, args) + + if isEmptyType(typ.sons[0]): result = emptyNode + else: result = unpack(retVal, typ.sons[0], call.info) + + dealloc retVal + for i in countdown(call.len-1, 0): dealloc args[i] diff --git a/compiler/evals.nim b/compiler/evals.nim index 20d354aaf..535d9aebd 100755 --- a/compiler/evals.nim +++ b/compiler/evals.nim @@ -18,6 +18,9 @@ import msgs, os, condsyms, idents, renderer, types, passes, semfold, transf, parser, ropes, rodread, idgen, osproc, streams, evaltempl +when hasFFI: + import evalffi + type PStackFrame* = ref TStackFrame TStackFrame*{.final.} = object @@ -307,42 +310,6 @@ proc evalVar(c: PEvalContext, n: PNode): PNode = for i in countup(0, sonsLen(result) - 1): addSon(x, result.sons[i]) result = emptyNode -proc evalCall(c: PEvalContext, n: PNode): PNode = - var d = newStackFrame() - d.call = n - var prc = n.sons[0] - let isClosure = prc.kind == nkClosure - setlen(d.params, sonsLen(n) + ord(isClosure)) - if isClosure: - #debug prc - result = evalAux(c, prc.sons[1], {efLValue}) - if isSpecial(result): return - d.params[sonsLen(n)] = result - result = evalAux(c, prc.sons[0], {}) - else: - result = evalAux(c, prc, {}) - - if isSpecial(result): return - prc = result - # bind the actual params to the local parameter of a new binding - if prc.kind != nkSym: - InternalError(n.info, "evalCall " & n.renderTree) - return - d.prc = prc.sym - if prc.sym.kind notin {skProc, skConverter, skMacro}: - InternalError(n.info, "evalCall") - return - for i in countup(1, sonsLen(n) - 1): - result = evalAux(c, n.sons[i], {}) - if isSpecial(result): return - d.params[i] = result - if n.typ != nil: d.params[0] = getNullValue(n.typ, n.info) - pushStackFrame(c, d) - result = evalAux(c, prc.sym.getBody, {}) - if result.kind == nkExceptBranch: return - if n.typ != nil: result = d.params[0] - popStackFrame(c) - proc aliasNeeded(n: PNode, flags: TEvalFlags): bool = result = efLValue in flags or n.typ == nil or n.typ.kind in {tyExpr, tyStmt, tyTypeDesc} @@ -374,7 +341,14 @@ proc evalGlobalVar(c: PEvalContext, s: PSym, flags: TEvalFlags): PNode = else: result = s.ast if result == nil or result.kind == nkEmpty: - result = getNullValue(s.typ, s.info) + when hasFFI: + # for 'stdin' etc. we need to support 'importc' for variables: + if sfImportc in s.flags: + result = importcSymbol(s) + else: + result = getNullValue(s.typ, s.info) + else: + result = getNullValue(s.typ, s.info) else: result = evalAux(c, result, {}) if isSpecial(result): return @@ -382,6 +356,51 @@ proc evalGlobalVar(c: PEvalContext, s: PSym, flags: TEvalFlags): PNode = else: result = raiseCannotEval(nil, s.info) +proc evalCall(c: PEvalContext, n: PNode): PNode = + var d = newStackFrame() + d.call = n + var prc = n.sons[0] + let isClosure = prc.kind == nkClosure + setlen(d.params, sonsLen(n) + ord(isClosure)) + if isClosure: + #debug prc + result = evalAux(c, prc.sons[1], {efLValue}) + if isSpecial(result): return + d.params[sonsLen(n)] = result + result = evalAux(c, prc.sons[0], {}) + else: + result = evalAux(c, prc, {}) + + if isSpecial(result): return + prc = result + # bind the actual params to the local parameter of a new binding + if prc.kind != nkSym: + InternalError(n.info, "evalCall " & n.renderTree) + return + d.prc = prc.sym + if prc.sym.kind notin {skProc, skConverter, skMacro}: + InternalError(n.info, "evalCall") + return + for i in countup(1, sonsLen(n) - 1): + result = evalAux(c, n.sons[i], {}) + if isSpecial(result): return + d.params[i] = result + if n.typ != nil: d.params[0] = getNullValue(n.typ, n.info) + + when hasFFI: + if sfImportc in prc.sym.flags: + var newCall = newNodeI(nkCall, n.info, n.len) + newCall.sons[0] = evalGlobalVar(c, prc.sym, {}) + for i in 1 .. <n.len: + newCall.sons[i] = d.params[i-1] + return callForeignFunction(newCall) + + pushStackFrame(c, d) + result = evalAux(c, prc.sym.getBody, {}) + if result.kind == nkExceptBranch: return + if n.typ != nil: result = d.params[0] + popStackFrame(c) + proc evalArrayAccess(c: PEvalContext, n: PNode, flags: TEvalFlags): PNode = result = evalAux(c, n.sons[0], flags) if isSpecial(result): return @@ -520,7 +539,8 @@ proc evalSym(c: PEvalContext, n: PNode, flags: TEvalFlags): PNode = of skConst: result = s.ast of skEnumField: result = newIntNodeT(s.position, n) else: result = nil - if result == nil or {sfImportc, sfForward} * s.flags != {}: + const mask = when hasFFI: {sfForward} else: {sfImportc, sfForward} + if result == nil or mask * s.flags != {}: result = raiseCannotEval(c, n.info) proc evalIncDec(c: PEvalContext, n: PNode, sign: biggestInt): PNode = diff --git a/compiler/options.nim b/compiler/options.nim index b8e65f8e3..6df29d85f 100755 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -13,6 +13,7 @@ import const hasTinyCBackend* = defined(tinyc) useEffectSystem* = true + hasFFI* = defined(useFFI) type # please make sure we have under 32 options # (improves code efficiency a lot!) @@ -230,6 +231,17 @@ proc findModule*(modulename: string): string {.inline.} = # returns path to module result = FindFile(AddFileExt(modulename, nimExt)) +proc libCandidates*(s: string, dest: var seq[string]) = + var le = strutils.find(s, '(') + var ri = strutils.find(s, ')', le+1) + if le >= 0 and ri > le: + var prefix = substr(s, 0, le - 1) + var suffix = substr(s, ri + 1) + for middle in split(substr(s, le + 1, ri - 1), '|'): + libCandidates(prefix & middle & suffix, dest) + else: + add(dest, s) + proc binaryStrSearch*(x: openarray[string], y: string): int = var a = 0 var b = len(x) - 1 diff --git a/koch.nim b/koch.nim index 6bc63a6ce..029e8a733 100755 --- a/koch.nim +++ b/koch.nim @@ -49,6 +49,7 @@ Boot options: -d:tinyc include the Tiny C backend (not supported on Windows) -d:useGnuReadline use the GNU readline library for interactive mode (not needed on Windows) + -d:useFFI build Nimrod with FFI support at compile time -d:nativeStacktrace use native stack traces (only for Mac OS X or Linux) """ diff --git a/lib/wrappers/libffi.nim b/lib/wrappers/libffi.nim new file mode 100644 index 000000000..514ce024f --- /dev/null +++ b/lib/wrappers/libffi.nim @@ -0,0 +1,149 @@ +# -----------------------------------------------------------------*-C-*- +# libffi 3.0.10 - Copyright (c) 2011 Anthony Green +# - Copyright (c) 1996-2003, 2007, 2008 Red Hat, Inc. +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the ``Software''), to deal in the Software without +# restriction, including without limitation the rights to use, copy, +# modify, merge, publish, distribute, sublicense, and/or sell copies +# of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. +# +# ----------------------------------------------------------------------- + +{.deadCodeElim: on.} + +when defined(windows): + const libffidll* = "libffi.dll" +elif defined(macosx): + const libffidll* = "libffi.dylib" +else: + const libffidll* = "libffi.so" + +type + TArg* = int + TSArg* = int + +when defined(windows) and defined(x86): + type + TABI* {.size: sizeof(cint).} = enum + FIRST_ABI, SYSV, STDCALL + + const DEFAULT_ABI* = SYSV +elif defined(amd64) and defined(windows): + type + TABI* {.size: sizeof(cint).} = enum + FIRST_ABI, WIN64 + const DEFAULT_ABI* = WIN64 +else: + type + TABI* {.size: sizeof(cint).} = enum + FIRST_ABI, SYSV, UNIX64 + + when defined(i386): + const DEFAULT_ABI* = SYSV + else: + const DEFAULT_ABI* = UNIX64 + +const + tkVOID* = 0 + tkINT* = 1 + tkFLOAT* = 2 + tkDOUBLE* = 3 + tkLONGDOUBLE* = 4 + tkUINT8* = 5 + tkSINT8* = 6 + tkUINT16* = 7 + tkSINT16* = 8 + tkUINT32* = 9 + tkSINT32* = 10 + tkUINT64* = 11 + tkSINT64* = 12 + tkSTRUCT* = 13 + tkPOINTER* = 14 + + tkLAST = tkPOINTER + tkSMALL_STRUCT_1B* = (tkLAST + 1) + tkSMALL_STRUCT_2B* = (tkLAST + 2) + tkSMALL_STRUCT_4B* = (tkLAST + 3) + +type + TType* = object + size*: int + alignment*: uint16 + typ*: uint16 + elements*: ptr ptr TType + +var + type_void* {.importc: "ffi_type_void", dynlib: libffidll.}: TType + type_uint8* {.importc: "ffi_type_uint8", dynlib: libffidll.}: TType + type_sint8* {.importc: "ffi_type_sint8", dynlib: libffidll.}: TType + type_uint16* {.importc: "ffi_type_uint16", dynlib: libffidll.}: TType + type_sint16* {.importc: "ffi_type_sint16", dynlib: libffidll.}: TType + type_uint32* {.importc: "ffi_type_uint32", dynlib: libffidll.}: TType + type_sint32* {.importc: "ffi_type_sint32", dynlib: libffidll.}: TType + type_uint64* {.importc: "ffi_type_uint64", dynlib: libffidll.}: TType + type_sint64* {.importc: "ffi_type_sint64", dynlib: libffidll.}: TType + type_float* {.importc: "ffi_type_float", dynlib: libffidll.}: TType + type_double* {.importc: "ffi_type_double", dynlib: libffidll.}: TType + type_pointer* {.importc: "ffi_type_pointer", dynlib: libffidll.}: TType + type_longdouble* {.importc: "ffi_type_longdouble", dynlib: libffidll.}: TType + +type + Tstatus* {.size: sizeof(cint).} = enum + OK, BAD_TYPEDEF, BAD_ABI + TTypeKind* = cuint + TCif* {.pure, final.} = object + abi*: TABI + nargs*: cuint + arg_types*: ptr ptr TType + rtype*: ptr TType + bytes*: cuint + flags*: cuint + +type + TRaw* = object + sint*: TSArg + +proc raw_call*(cif: var Tcif; fn: proc () {.cdecl.}; rvalue: pointer; + avalue: ptr TRaw) {.cdecl, importc: "ffi_raw_call", + dynlib: libffidll.} +proc ptrarray_to_raw*(cif: var Tcif; args: ptr pointer; raw: ptr TRaw) {.cdecl, + importc: "ffi_ptrarray_to_raw", dynlib: libffidll.} +proc raw_to_ptrarray*(cif: var Tcif; raw: ptr TRaw; args: ptr pointer) {.cdecl, + importc: "ffi_raw_to_ptrarray", dynlib: libffidll.} +proc raw_size*(cif: var Tcif): int {.cdecl, importc: "ffi_raw_size", + dynlib: libffidll.} + +proc prep_cif*(cif: var Tcif; abi: TABI; nargs: cuint; rtype: ptr TType; + atypes: ptr ptr TType): TStatus {.cdecl, importc: "ffi_prep_cif", + dynlib: libffidll.} +proc call*(cif: var Tcif; fn: proc () {.cdecl.}; rvalue: pointer; + avalue: ptr pointer) {.cdecl, importc: "ffi_call", dynlib: libffidll.} + +# the same with an easier interface: +type + TParamList* = array[0..100, ptr TType] + TArgList* = array[0..100, pointer] + +proc prep_cif*(cif: var Tcif; abi: TABI; nargs: cuint; rtype: ptr TType; + atypes: TParamList): TStatus {.cdecl, importc: "ffi_prep_cif", + dynlib: libffidll.} +proc call*(cif: var Tcif; fn, rvalue: pointer; + avalue: TArgList) {.cdecl, importc: "ffi_call", dynlib: libffidll.} + +# Useful for eliminating compiler warnings +##define FFI_FN(f) ((void (*)(void))f) |