summary refs log tree commit diff stats
path: root/compiler/evalffi.nim
blob: b98679ac60e54e5ff1800cb713be5c93bc11e098 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
#
#
#            Nim's Runtime Library
#        (c) Copyright 2015 Andreas Rumpf
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

## This module provides the standard Nim command line parser.
## It supports one convenience iterator over all command line options and some
## lower-level features.
##
## Supported syntax:
##
## 1. short options - ``-abcd``, where a, b, c, d are names
## 2. long option - ``--foo:bar``, ``--foo=bar`` or ``--foo``
## 3. argument - everything else

{.push debugger: off.}

include "system/inclrtl"

import
  os, strutils

type
  CmdLineKind* = enum         ## the detected command line token
    cmdEnd,                   ## end of command line reached
    cmdArgument,              ## argument detected
    cmdLongOption,            ## a long option ``--option`` detected
    cmdShortOption            ## a short option ``-c`` detected
  OptParser* =
      object of RootObj ## this object implements the command line parser
    cmd: string
    pos: int
    inShortState: bool
    kind*: CmdLineKind        ## the dected command line token
    key*, val*: TaintedString ## key and value pair; ``key`` is the option
                              ## or the argument, ``value`` is not "" if
                              ## the option was given a value

{.deprecated: [TCmdLineKind: CmdLineKind, TOptParser: OptParser].}

proc parseWord(s: string, i: int, w: var string,
               delim: set[char] = {'\x09', ' ', '\0'}): int =
  result = i
  if s[result] == '\"':
    inc(result)
    while not (s[result] in pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
#
#
#           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: "<errno.h>".}: 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:
      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 .. <v.len:
      pack(v.sons[i], typ.sons[1], res +! i * baseSize)
  of tyObject, tyTuple:
    packObject(v, typ, res)
  of tyNil:
    discard
  of tyDistinct, tyGenericInst:
    pack(v, typ.sons[0], res)
  else:
    globalError(v.info, "cannot map value to FFI " & typeToString(v.typ))

proc unpack(x: pointer, typ: PType, n: PNode): PNode

proc unpackObjectAdd(x: pointer, n, result: PNode) =
  case n.kind
  of nkRecList:
    for i in countup(0, sonsLen(n) - 1):
      unpackObjectAdd(x, n.sons[i], result)
  of nkRecCase:
    globalError(result.info, "case objects cannot be unpacked")
  of nkSym:
    var pair = newNodeI(nkExprColonExpr, result.info, 2)
    pair.sons[0] = n
    pair.sons[1] = unpack(x +! n.sym.offset, n.sym.typ, nil)
    #echo "offset: ", n.sym.name.s, " ", n.sym.offset
    result.add pair
  else: discard

proc unpackObject(x: pointer, typ: PType, n: PNode): PNode =
  # compute the field's offsets:
  discard typ.getSize
  
  # iterate over any actual field of 'n' ... if n is nil we need to create
  # the nkPar node:
  if n.isNil:
    result = newNode(nkPar)
    result.typ = typ
    if typ.n.isNil:
      internalError("cannot unpack unnamed tuple")
    unpackObjectAdd(x, typ.n, result)
  else:
    result = n
    if result.kind notin {nkObjConstr, nkPar}:
      globalError(n.info, "cannot map value from FFI")
    if typ.n.isNil:
      globalError(n.info, "cannot unpack unnamed tuple")
    for i in countup(ord(n.kind == nkObjConstr), sonsLen(n) - 1):
      var it = n.sons[i]
      if it.kind == nkExprColonExpr:
        internalAssert it.sons[0].kind == nkSym
        let field = it.sons[0].sym
        it.sons[1] = unpack(x +! field.offset, field.typ, it.sons[1])
      else:
        let field = getField(typ.n, i)
        n.sons[i] = unpack(x +! field.offset, field.typ, it)

proc unpackArray(x: pointer, typ: PType, n: PNode): PNode =
  if n.isNil:
    result = newNode(nkBracket)
    result.typ = typ
    newSeq(result.sons, lengthOrd(typ).int)
  else:
    result = n
    if result.kind != nkBracket:
      globalError(n.info, "cannot map value from FFI")
  let baseSize = typ.sons[1].getSize
  for i in 0 .. < result.len:
    result.sons[i] = unpack(x +! i * baseSize, typ.sons[1], result.sons[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(x: pointer, typ: PType, n: PNode): PNode =
  template aw(k, v, field: expr) {.immediate, dirty.} =
    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(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.kind = nkNilLit
      result.typ = typ

  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(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 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:
      globalError(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[TAddress](p))
  of tyPtr, tyRef, tyVar:
    let p = rd(pointer, x)
    if p.isNil:
      setNil()
    elif n == nil or n.kind == nkPtrLit:
      awi(nkPtrLit, cast[TAddress](p))
    elif n != nil and n.len == 1:
      internalAssert n.kind == nkRefTy
      n.sons[0] = unpack(p, typ.lastSon, n.sons[0])
      result = n
    else:
      globalError(n.info, "cannot map value from FFI " & typeToString(typ))
  of tyObject, tyTuple:
    result = unpackObject(x, typ, n)
  of tyArray, tyArrayConstr:
    result = unpackArray(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:
    result = unpack(x, typ.sons[0], n)
  else:
    # XXX what to do with 'array' here?
    globalError(n.info, "cannot map value from FFI " & typeToString(typ))

proc fficast*(x: PNode, destTyp: PType): PNode =
  if x.kind == nkPtrLit and x.typ.kind in {tyPtr, tyRef, tyVar, 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(x, x.typ), packSize(x, destTyp))
    var a = alloc0(size)
    pack(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(a, destTyp, y)
    dealloc a

proc callForeignFunction*(call: PNode): PNode =
  internalAssert call.sons[0].kind == nkPtrLit
  
  var cif: TCif
  var sig: TParamList
  # use the arguments' types for varargs support:
  for i in 1..call.len-1:
    sig[i-1] = mapType(call.sons[i].typ)
    if sig[i-1].isNil:
      globalError(call.info, "cannot map FFI type")
  
  let typ = call.sons[0].typ
  if prep_cif(cif, mapCallConv(typ.callConv, call.info), cuint(call.len-1),
              mapType(typ.sons[0]), sig) != OK:
    globalError(call.info, "error in FFI call")
  
  var args: TArgList
  let fn = cast[pointer](call.sons[0].intVal)
  for i in 1 .. call.len-1:
    var t = call.sons[i].typ
    args[i-1] = alloc0(packSize(call.sons[i], t))
    pack(call.sons[i], t, args[i-1])
  let retVal = if isEmptyType(typ.sons[0]): pointer(nil)
               else: alloc(typ.sons[0].getSize.int)

  libffi.call(cif, fn, retVal, args)
  
  if retVal.isNil: 
    result = emptyNode
  else:
    result = unpack(retVal, typ.sons[0], nil)
    result.info = call.info

  if retVal != nil: dealloc retVal
  for i in 1 .. call.len-1:
    call.sons[i] = unpack(args[i-1], typ.sons[i], call[i])
    dealloc args[i-1]

proc callForeignFunction*(fn: PNode, fntyp: PType,
                          args: var TNodeSeq, start, len: int,
                          info: TLineInfo): PNode =
  internalAssert fn.kind == nkPtrLit
  
  var cif: TCif
  var sig: TParamList
  for i in 0..len-1:
    var aTyp = args[i+start].typ
    if aTyp.isNil:
      internalAssert i+1 < fntyp.len
      aTyp = fntyp.sons[i+1]
      args[i+start].typ = aTyp
    sig[i] = mapType(aTyp)
    if sig[i].isNil: globalError(info, "cannot map FFI type")
  
  if prep_cif(cif, mapCallConv(fntyp.callConv, info), cuint(len),
              mapType(fntyp.sons[0]), sig) != OK:
    globalError(info, "error in FFI call")
  
  var cargs: TArgList
  let fn = cast[pointer](fn.intVal)
  for i in 0 .. len-1:
    let t = args[i+start].typ
    cargs[i] = alloc0(packSize(args[i+start], t))
    pack(args[i+start], t, cargs[i])
  let retVal = if isEmptyType(fntyp.sons[0]): pointer(nil)
               else: alloc(fntyp.sons[0].getSize.int)

  libffi.call(cif, fn, retVal, cargs)
  
  if retVal.isNil: 
    result = emptyNode
  else:
    result = unpack(retVal, fntyp.sons[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(cargs[i], t, args[i+start])
    dealloc cargs[i]