summary refs log tree commit diff stats
path: root/doc/manual/ffi.txt
blob: 4a4e0316fbeda721bb818061cdde06f837a44fee (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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
Foreign function interface
==========================

Nim's `FFI`:idx: (foreign function interface) is extensive and only the
parts that scale to other future backends (like the LLVM/JavaScript backends)
are documented here.


Importc pragma
--------------
The ``importc`` pragma provides a means to import a proc or a variable
from C. The optional argument is a string containing the C identifier. If
the argument is missing, the C name is the Nim identifier *exactly as
spelled*:

.. code-block::
  proc printf(formatstr: cstring) {.header: "<stdio.h>", importc: "printf", varargs.}

Note that this pragma is somewhat of a misnomer: Other backends will provide
the same feature under the same name. Also, if one is interfacing with C++
the `ImportCpp pragma <nimc.html#importcpp-pragma>`_ and
interfacing with Objective-C the `ImportObjC pragma
<nimc.html#importobjc-pragma>`_ can be used.


Exportc pragma
--------------
The ``exportc`` pragma provides a means to export a type, a variable, or a
procedure to C. Enums and constants can't be exported. The optional argument
is a string containing the C identifier.  If the argument is missing, the C
name is the Nim identifier *exactly as spelled*:

.. code-block:: Nim
  proc callme(formatstr: cstring) {.exportc: "callMe", varargs.}

Note that this pragma is somewhat of a misnomer: Other backends will provide
the same feature under the same name.


Extern pragma
-------------
Like ``exportc`` or ``importc``, the ``extern`` pragma affects name
mangling. The string literal passed to ``extern`` can be a format string:

.. code-block:: Nim
  proc p(s: string) {.extern: "prefix$1".} =
    echo s

In the example the external name of ``p`` is set to ``prefixp``.


Bycopy pragma
-------------

The ``bycopy`` pragma can be applied to an object or tuple type and
instructs the compiler to pass the type by value to procs:

.. code-block:: nim
  type
    Vector {.bycopy, pure.} = object
      x, y, z: float


Byref pragma
------------

The ``byref`` pragma can be applied to an object or tuple type and instructs
the compiler to pass the type by reference (hidden pointer) to procs.


Varargs pragma
--------------
The ``varargs`` pragma can be applied to procedures only (and procedure 
types). It tells Nim that the proc can take a variable number of parameters 
after the last specified parameter. Nim string values will be converted to C
strings automatically:

.. code-block:: Nim
  proc printf(formatstr: cstring) {.nodecl, varargs.}

  printf("hallo %s", "world") # "world" will be passed as C string


Union pragma
------------
The ``union`` pragma can be applied to any ``object`` type. It means all
of the object's fields are overlaid in memory. This produces a ``union``
instead of a ``struct`` in the generated C/C++ code. The object declaration
then must not use inheritance or any GC'ed memory but this is currently not
checked.

**Future directions**: GC'ed memory should be allowed in unions and the GC
should scan unions conservatively.

Packed pragma
-------------
The ``packed`` pragma can be applied to any ``object`` type. It ensures 
that the fields of an object are packed back-to-back in memory. It is useful 
to store packets or messages from/to network or hardware drivers, and for 
interoperability with C. Combining packed pragma with inheritance is not 
defined, and it should not be used with GC'ed memory (ref's).  

**Future directions**: Using GC'ed memory in packed pragma will result in 
compile-time error. Usage with inheritance should be defined and documented.

Unchecked pragma
----------------
The ``unchecked`` pragma can be used to mark a named array as ``unchecked``
meaning its bounds are not checked. This is often useful when one wishes to
implement his own flexibly sized arrays. Additionally an unchecked array is
translated into a C array of undetermined size:

.. code-block:: nim
  type
    ArrayPart{.unchecked.} = array[0..0, int]
    MySeq = object
      len, cap: int
      data: ArrayPart

Produces roughly this C code:

.. code-block:: C
  typedef struct {
    NI len;
    NI cap;
    NI data[];
  } MySeq;

The bounds checking done at compile time is not disabled for now, so to access
``s.data[C]`` (where ``C`` is a constant) the array's index needs needs to
include ``C``.

The base type of the unchecked array may not contain any GC'ed memory but this
is currently not checked.

**Future directions**: GC'ed memory should be allowed in unchecked arrays and
there should be an explicit annotation of how the GC is to determine the
runtime size of the array.


Dynlib pragma for import
------------------------
With the ``dynlib`` pragma a procedure or a variable can be imported from
a dynamic library (``.dll`` files for Windows, ``lib*.so`` files for UNIX). 
The non-optional argument has to be the name of the dynamic library:

.. code-block:: Nim
  proc gtk_image_new(): PGtkWidget
    {.cdecl, dynlib: "libgtk-x11-2.0.so", importc.}

In general, importing a dynamic library does not require any special linker
options or linking with import libraries. This also implies that no *devel*
packages need to be installed.

The ``dynlib`` import mechanism supports a versioning scheme: 

.. code-block:: nim 
  proc Tcl_Eval(interp: pTcl_Interp, script: cstring): int {.cdecl, 
    importc, dynlib: "libtcl(|8.5|8.4|8.3).so.(1|0)".}

At runtime the dynamic library is searched for (in this order)::
  
  libtcl.so.1
  libtcl.so.0
  libtcl8.5.so.1  
  libtcl8.5.so.0
  libtcl8.4.so.1
  libtcl8.4.so.0
  libtcl8.3.so.1
  libtcl8.3.so.0

The ``dynlib`` pragma supports not only constant strings as argument but also
string expressions in general:

.. code-block:: nim
  import os

  proc getDllName: string = 
    result = "mylib.dll"
    if existsFile(result): return
    result = "mylib2.dll"
    if existsFile(result): return
    quit("could not load dynamic library")
  
  proc myImport(s: cstring) {.cdecl, importc, dynlib: getDllName().}

**Note**: Patterns like ``libtcl(|8.5|8.4).so`` are only supported in constant
strings, because they are precompiled.

**Note**: Passing variables to the ``dynlib`` pragma will fail at runtime 
because of order of initialization problems.

**Note**: A ``dynlib`` import can be overriden with 
the ``--dynlibOverride:name`` command line option. The Compiler User Guide
contains further information.


Dynlib pragma for export
------------------------

With the ``dynlib`` pragma a procedure can also be exported to
a dynamic library. The pragma then has no argument and has to be used in
conjunction with the ``exportc`` pragma:

.. code-block:: Nim
  proc exportme(): int {.cdecl, exportc, dynlib.}

This is only useful if the program is compiled as a dynamic library via the
``--app:lib`` command line option.
/edx <- increment => "42/increment-edx" var/ebx <- increment => "43/increment-ebx" var/esi <- increment => "46/increment-esi" var/edi <- increment => "47/increment-edi" increment var => "ff 0/subop/increment *(ebp+" var.stack-offset ")" increment *var/reg => "ff 0/subop/increment *" reg var/eax <- decrement => "48/decrement-eax" var/ecx <- decrement => "49/decrement-ecx" var/edx <- decrement => "4a/decrement-edx" var/ebx <- decrement => "4b/decrement-ebx" var/esi <- decrement => "4e/decrement-esi" var/edi <- decrement => "4f/decrement-edi" decrement var => "ff 1/subop/decrement *(ebp+" var.stack-offset ")" decrement *var/reg => "ff 1/subop/decrement *" reg var/reg <- add var2/reg2 => "01/add-to %" reg " " reg2 "/r32" var/reg <- add var2 => "03/add *(ebp+" var2.stack-offset ") " reg "/r32" var/reg <- add *var2/reg2 => "03/add *" reg2 " " reg "/r32" add-to var1, var2/reg => "01/add-to *(ebp+" var1.stack-offset ") " reg "/r32" add-to *var1/reg1, var2/reg2 => "01/add-to *" reg1 " " reg2 "/r32" var/eax <- add n => "05/add-to-eax " n "/imm32" var/reg <- add n => "81 0/subop/add %" reg " " n "/imm32" add-to var, n => "81 0/subop/add *(ebp+" var.stack-offset ") " n "/imm32" add-to *var/reg, n => "81 0/subop/add *" reg " " n "/imm32" var/reg <- subtract var2/reg2 => "29/subtract-from %" reg " " reg2 "/r32" var/reg <- subtract var2 => "2b/subtract *(ebp+" var2.stack-offset ") " reg "/r32" var/reg <- subtract *var2/reg2 => "2b/subtract *" reg2 " " reg1 "/r32" subtract-from var1, var2/reg2 => "29/subtract-from *(ebp+" var1.stack-offset ") " reg2 "/r32" subtract-from *var1/reg1, var2/reg2 => "29/subtract-from *" reg1 " " reg2 "/r32" var/eax <- subtract n => "2d/subtract-from-eax " n "/imm32" var/reg <- subtract n => "81 5/subop/subtract %" reg " " n "/imm32" subtract-from var, n => "81 5/subop/subtract *(ebp+" var.stack-offset ") " n "/imm32" subtract-from *var/reg, n => "81 5/subop/subtract *" reg " " n "/imm32" var/reg <- and var2/reg2 => "21/and-with %" reg " " reg2 "/r32" var/reg <- and var2 => "23/and *(ebp+" var2.stack-offset " " reg "/r32" var/reg <- and *var2/reg2 => "23/and *" reg2 " " reg "/r32" and-with var1, var2/reg => "21/and-with *(ebp+" var1.stack-offset ") " reg "/r32" and-with *var1/reg1, var2/reg2 => "21/and-with *" reg1 " " reg2 "/r32" var/eax <- and n => "25/and-with-eax " n "/imm32" var/reg <- and n => "81 4/subop/and %" reg " " n "/imm32" and-with var, n => "81 4/subop/and *(ebp+" var.stack-offset ") " n "/imm32" and-with *var/reg, n => "81 4/subop/and *" reg " " n "/imm32" var/reg <- or var2/reg2 => "09/or-with %" reg " " reg2 "/r32" var/reg <- or var2 => "0b/or *(ebp+" var2.stack-offset ") " reg "/r32" var/reg <- or *var2/reg2 => "0b/or *" reg2 " " reg "/r32" or-with var1, var2/reg2 => "09/or-with *(ebp+" var1.stack-offset " " reg2 "/r32" or-with *var1/reg1, var2/reg2 => "09/or-with *" reg1 " " reg2 "/r32" var/eax <- or n => "0d/or-with-eax " n "/imm32" var/reg <- or n => "81 1/subop/or %" reg " " n "/imm32" or-with var, n => "81 1/subop/or *(ebp+" var.stack-offset ") " n "/imm32" or-with *var/reg, n => "81 1/subop/or *" reg " " n "/imm32" var/reg <- xor var2/reg2 => "31/xor-with %" reg " " reg2 "/r32" var/reg <- xor var2 => "33/xor *(ebp+" var2.stack-offset ") " reg "/r32" var/reg <- xor *var2/reg2 => "33/xor *" reg2 " " reg "/r32" xor-with var1, var2/reg => "31/xor-with *(ebp+" var1.stack-offset ") " reg "/r32" xor-with *var1/reg1, var2/reg2 => "31/xor-with *" reg1 " " reg2 "/r32" var/eax <- xor n => "35/xor-with-eax " n "/imm32" var/reg <- xor n => "81 6/subop/xor %" reg " " n "/imm32" xor-with var, n => "81 6/subop/xor *(ebp+" var.stack-offset ") " n "/imm32" xor-with *var/reg, n => "81 6/subop/xor *" reg " " n "/imm32" var/reg <- negate => "f7 3/subop/negate %" reg negate var => "f7 3/subop/negate *(ebp+" var.stack-offset ")" negate *var/reg => "f7 3/subop/negate *" reg var/reg <- shift-left n => "c1/shift 4/subop/left %" reg " " n "/imm32" var/reg <- shift-right n => "c1/shift 5/subop/right %" reg " " n "/imm32" var/reg <- shift-right-signed n => "c1/shift 7/subop/right-signed %" reg " " n "/imm32" shift-left var, n => "c1/shift 4/subop/left *(ebp+" var.stack-offset ") " n "/imm32" shift-left *var/reg, n => "c1/shift 4/subop/left *" reg " " n "/imm32" shift-right var, n => "c1/shift 5/subop/right *(ebp+" var.stack-offset ") " n "/imm32" shift-right *var/reg, n => "c1/shift 5/subop/right *" reg " " n "/imm32" shift-right-signed var, n => "c1/shift 7/subop/right-signed *(ebp+" var.stack-offset ") " n "/imm32" shift-right-signed *var/reg, n => "c1/shift 7/subop/right-signed *" reg " " n "/imm32" var/eax <- copy n => "b8/copy-to-eax " n "/imm32" var/ecx <- copy n => "b9/copy-to-ecx " n "/imm32" var/edx <- copy n => "ba/copy-to-edx " n "/imm32" var/ebx <- copy n => "bb/copy-to-ebx " n "/imm32" var/esi <- copy n => "be/copy-to-esi " n "/imm32" var/edi <- copy n => "bf/copy-to-edi " n "/imm32" var/reg <- copy var2/reg2 => "89/<- %" reg " " reg2 "/r32" copy-to var1, var2/reg => "89/<- *(ebp+" var1.stack-offset ") " reg "/r32" copy-to *var1/reg1, var2/reg2 => "89/<- *" reg1 " " reg2 "/r32" var/reg <- copy var2 => "8b/-> *(ebp+" var2.stack-offset ") " reg "/r32" var/reg <- copy *var2/reg2 => "8b/-> *" reg2 " " reg "/r32" var/reg <- copy n => "c7 0/subop/copy %" reg " " n "/imm32" copy-to var, n => "c7 0/subop/copy *(ebp+" var.stack-offset ") " n "/imm32" copy-to *var/reg, n => "c7 0/subop/copy *" reg " " n "/imm32" var/reg <- copy-byte var2/reg2 => "8a/byte-> %" reg2 " " reg "/r32" var/reg <- copy-byte *var2/reg2 => "8a/byte-> *" reg2 " " reg "/r32" copy-byte-to *var1/reg1, var2/reg2 => "88/byte<- *" reg1 " " reg2 "/r32" compare var1, var2/reg2 => "39/compare *(ebp+" var1.stack-offset ") " reg2 "/r32" compare *var1/reg1, var2/reg2 => "39/compare *" reg1 " " reg2 "/r32" compare var1/reg1, var2 => "3b/compare<- *(ebp+" var2.stack-offset ") " reg1 "/r32" compare var/reg, *var2/reg2 => "3b/compare<- *" reg " " n "/imm32" compare var/eax, n => "3d/compare-eax-with " n "/imm32" compare var/reg, n => "81 7/subop/compare %" reg " " n "/imm32" compare var, n => "81 7/subop/compare *(ebp+" var.stack-offset ") " n "/imm32" compare *var/reg, n => "81 7/subop/compare *" reg " " n "/imm32" var/reg <- multiply var2 => "0f af/multiply *(ebp+" var2.stack-offset ") " reg "/r32" var/reg <- multiply *var2/reg2 => "0f af/multiply *" reg2 " " reg "/r32" ## Floating-point operations These instructions operate on either floating-point registers (xreg) or general-purpose registers (reg) in indirect mode. var/xreg <- add var2/xreg2 => "f3 0f 58/add %" xreg2 " " xreg1 "/x32" var/xreg <- add var2 => "f3 0f 58/add *(ebp+" var2.stack-offset ") " xreg "/x32" var/xreg <- add *var2/reg2 => "f3 0f 58/add *" reg2 " " xreg "/x32" var/xreg <- subtract var2/xreg2 => "f3 0f 5c/subtract %" xreg2 " " xreg1 "/x32" var/xreg <- subtract var2 => "f3 0f 5c/subtract *(ebp+" var2.stack-offset ") " xreg "/x32" var/xreg <- subtract *var2/reg2 => "f3 0f 5c/subtract *" reg2 " " xreg "/x32" var/xreg <- multiply var2/xreg2 => "f3 0f 59/multiply %" xreg2 " " xreg1 "/x32" var/xreg <- multiply var2 => "f3 0f 59/multiply *(ebp+" var2.stack-offset ") " xreg "/x32" var/xreg <- multiply *var2/reg2 => "f3 0f 59/multiply *" reg2 " " xreg "/x32" var/xreg <- divide var2/xreg2 => "f3 0f 5e/divide %" xreg2 " " xreg1 "/x32" var/xreg <- divide var2 => "f3 0f 5e/divide *(ebp+" var2.stack-offset ") " xreg "/x32" var/xreg <- divide *var2/reg2 => "f3 0f 5e/divide *" reg2 " " xreg "/x32" There are also some exclusively floating-point instructions: var/xreg <- reciprocal var2/xreg2 => "f3 0f 53/reciprocal %" xreg2 " " xreg1 "/x32" var/xreg <- reciprocal var2 => "f3 0f 53/reciprocal *(ebp+" var2.stack-offset ") " xreg "/x32" var/xreg <- reciprocal *var2/reg2 => "f3 0f 53/reciprocal *" reg2 " " xreg "/x32" var/xreg <- square-root var2/xreg2 => "f3 0f 51/square-root %" xreg2 " " xreg1 "/x32" var/xreg <- square-root var2 => "f3 0f 51/square-root *(ebp+" var2.stack-offset ") " xreg "/x32" var/xreg <- square-root *var2/reg2 => "f3 0f 51/square-root *" reg2 " " xreg "/x32" var/xreg <- inverse-square-root var2/xreg2 => "f3 0f 52/inverse-square-root %" xreg2 " " xreg1 "/x32" var/xreg <- inverse-square-root var2 => "f3 0f 52/inverse-square-root *(ebp+" var2.stack-offset ") " xreg "/x32" var/xreg <- inverse-square-root *var2/reg2 => "f3 0f 52/inverse-square-root *" reg2 " " xreg "/x32" var/xreg <- min var2/xreg2 => "f3 0f 5d/min %" xreg2 " " xreg1 "/x32" var/xreg <- min var2 => "f3 0f 5d/min *(ebp+" var2.stack-offset ") " xreg "/x32" var/xreg <- min *var2/reg2 => "f3 0f 5d/min *" reg2 " " xreg "/x32" var/xreg <- max var2/xreg2 => "f3 0f 5f/max %" xreg2 " " xreg1 "/x32" var/xreg <- max var2 => "f3 0f 5f/max *(ebp+" var2.stack-offset ") " xreg "/x32" var/xreg <- max *var2/reg2 => "f3 0f 5f/max *" reg2 " " xreg "/x32" Remember, when these instructions use indirect mode, they still use an integer register. Floating-point registers can't hold addresses. Most instructions operate exclusively on integer or floating-point operands. The only exceptions are the instructions for converting between integers and floating-point numbers. var/xreg <- convert var2/reg2 => "f3 0f 2a/convert-to-float %" reg2 " " xreg "/x32" var/xreg <- convert var2 => "f3 0f 2a/convert-to-float *(ebp+" var2.stack-offset ") " xreg "/x32" var/xreg <- convert *var2/reg2 => "f3 0f 2a/convert-to-float *" reg2 " " xreg "/x32" Converting floats to ints performs rounding by default. (We don't mess with the MXCSR control register.) var/reg <- convert var2/xreg2 => "f3 0f 2d/convert-to-int %" xreg2 " " reg "/r32" var/reg <- convert var2 => "f3 0f 2d/convert-to-int *(ebp+" var2.stack-offset ") " reg "/r32" var/reg <- convert *var2/reg2 => "f3 0f 2d/convert-to-int *" reg2 " " reg "/r32" There's a separate instruction for truncating the fractional part. var/reg <- truncate var2/xreg2 => "f3 0f 2c/truncate-to-int %" xreg2 " " reg "/r32" var/reg <- truncate var2 => "f3 0f 2c/truncate-to-int *(ebp+" var2.stack-offset ") " reg "/r32" var/reg <- truncate *var2/reg2 => "f3 0f 2c/truncate-to-int *" reg2 " " reg "/r32" There are no instructions accepting floating-point literals. To obtain integer literals in floating-point registers, copy them to general-purpose registers and then convert them to floating-point. One pattern you may have noticed above is that the floating-point instructions above always write to registers. The only exceptions are `copy` instructions, which can write to memory locations. var/xreg <- copy var2/xreg2 => "f3 0f 11/<- %" xreg " " xreg2 "/x32" copy-to var1, var2/xreg => "f3 0f 11/<- *(ebp+" var1.stack-offset ") " xreg "/x32" var/xreg <- copy var2 => "f3 0f 10/-> *(ebp+" var2.stack-offset ") " xreg "/x32" var/xreg <- copy *var2/reg2 => "f3 0f 10/-> *" reg2 " " xreg "/x32" Comparisons must always start with a register: compare var1/xreg1, var2/xreg2 => "0f 2f/compare %" xreg2 " " xreg1 "/x32" compare var1/xreg1, var2 => "0f 2f/compare *(ebp+" var2.stack-offset ") " xreg1 "/x32" ## Blocks In themselves, blocks generate no instructions. However, if a block contains variable declarations, they must be cleaned up when the block ends. Clean up var on the stack => "81 0/subop/add %esp " size-of(var) "/imm32" Clean up var/reg => "8f 0/subop/pop %" reg Clean up var/xreg => "f3 0f 10/-> *esp " xreg "/x32" "81 0/subop/add %esp 4/imm32" ## Jumps Besides having to clean up any variable declarations (see above) between themselves and their target, jumps translate like this: break => "e9/jump break/disp32" break label => "e9/jump " label ":break/disp32" loop => "e9/jump loop/disp32" loop label => "e9/jump " label ":loop/disp32" break-if-= => "0f 84/jump-if-= break/disp32" break-if-= label => "0f 84/jump-if-= " label ":break/disp32" loop-if-= => "0f 84/jump-if-= loop/disp32" loop-if-= label => "0f 84/jump-if-= " label ":loop/disp32" break-if-!= => "0f 85/jump-if-!= break/disp32" break-if-!= label => "0f 85/jump-if-!= " label ":break/disp32" loop-if-!= => "0f 85/jump-if-!= loop/disp32" loop-if-!= label => "0f 85/jump-if-!= " label ":loop/disp32" break-if-< => "0f 8c/jump-if-< break/disp32" break-if-< label => "0f 8c/jump-if-< " label ":break/disp32" loop-if-< => "0f 8c/jump-if-< loop/disp32" loop-if-< label => "0f 8c/jump-if-< " label ":loop/disp32" break-if-> => "0f 8f/jump-if-> break/disp32" break-if-> label => "0f 8f/jump-if-> " label ":break/disp32" loop-if-> => "0f 8f/jump-if-> loop/disp32" loop-if-> label => "0f 8f/jump-if-> " label ":loop/disp32" break-if-<= => "0f 8e/jump-if-<= break/disp32" break-if-<= label => "0f 8e/jump-if-<= " label ":break/disp32" loop-if-<= => "0f 8e/jump-if-<= loop/disp32" loop-if-<= label => "0f 8e/jump-if-<= " label ":loop/disp32" break-if->= => "0f 8d/jump-if->= break/disp32" break-if->= label => "0f 8d/jump-if->= " label ":break/disp32" loop-if->= => "0f 8d/jump-if->= loop/disp32" loop-if->= label => "0f 8d/jump-if->= " label ":loop/disp32" break-if-addr< => "0f 82/jump-if-addr< break/disp32" break-if-addr< label => "0f 82/jump-if-addr< " label ":break/disp32" loop-if-addr< => "0f 82/jump-if-addr< loop/disp32" loop-if-addr< label => "0f 82/jump-if-addr< " label ":loop/disp32" break-if-addr> => "0f 87/jump-if-addr> break/disp32" break-if-addr> label => "0f 87/jump-if-addr> " label ":break/disp32" loop-if-addr> => "0f 87/jump-if-addr> loop/disp32" loop-if-addr> label => "0f 87/jump-if-addr> " label ":loop/disp32" break-if-addr<= => "0f 86/jump-if-addr<= break/disp32" break-if-addr<= label => "0f 86/jump-if-addr<= " label ":break/disp32" loop-if-addr<= => "0f 86/jump-if-addr<= loop/disp32" loop-if-addr<= label => "0f 86/jump-if-addr<= " label ":loop/disp32" break-if-addr>= => "0f 83/jump-if-addr>= break/disp32" break-if-addr>= label => "0f 83/jump-if-addr>= " label ":break/disp32" loop-if-addr>= => "0f 83/jump-if-addr>= loop/disp32" loop-if-addr>= label => "0f 83/jump-if-addr>= " label ":loop/disp32" Similar float variants like `break-if-float<` are aliases for the corresponding `addr` equivalents. The x86 instruction set stupidly has floating-point operations only update a subset of flags. ## Returns The `return` instruction cleans up variable declarations just like an unconditional `jump` to end of function, but also emits a series of copies before the final `jump`, copying each argument of `return` to the register appropriate to the respective function output. This doesn't work if a function output register contains a later `return` argument (e.g. if the registers for two outputs are swapped in `return`), so you can't do that. return => "c3/return" --- In the following instructions types are provided for clarity even if they must be provided in an earlier 'var' declaration. # Address operations var/reg: (addr T) <- address var2: T => "8d/copy-address *(ebp+" var2.stack-offset ") " reg "/r32" # Array operations (TODO: bounds-checking) var/reg <- index arr/rega: (addr array T), idx/regi: int | if size-of(T) is 4 or 8 => "8d/copy-address *(" rega "+" regi "<<" log2(size-of(T)) "+4) " reg "/r32" var/reg <- index arr: (array T sz), idx/regi: int => "8d/copy-address *(ebp+" regi "<<" log2(size-of(T)) "+" (arr.stack-offset + 4) ") " reg "/r32" var/reg <- index arr/rega: (addr array T), n => "8d/copy-address *(" rega "+" (n*size-of(T)+4) ") " reg "/r32" var/reg <- index arr: (array T sz), n => "8d/copy-address *(ebp+" (arr.stack-offset+4+n*size-of(T)) ") " reg "/r32" var/reg: (offset T) <- compute-offset arr: (addr array T), idx/regi: int # arr can be in reg or mem => "69/multiply %" regi " " size-of(T) "/imm32 " reg "/r32" var/reg: (offset T) <- compute-offset arr: (addr array T), idx: int # arr can be in reg or mem => "69/multiply *(ebp+" idx.stack-offset ") " size-of(T) "/imm32 " reg "/r32" var/reg <- index arr/rega: (addr array T), o/rego: offset => "8d/copy-address *(" rega "+" rego "+4) " reg "/r32" Computing the length of an array is complex. var/reg <- length arr/reg2: (addr array T) | if T is byte (TODO) => "8b/-> *" reg2 " " reg "/r32" | if size-of(T) is 4 or 8 or 16 or 32 or 64 or 128 => "8b/-> *" reg2 " " reg "/r32" "c1/shift 5/subop/logic-right %" reg " " log2(size-of(T)) "/imm8" | otherwise x86 has no instruction to divide by a literal, so we need up to 3 extra registers! eax/edx for division and say ecx => if reg is not eax "50/push-eax" if reg is not ecx "51/push-ecx" if reg is not edx "52/push-edx" "8b/-> *" reg2 " eax/r32" "31/xor %edx 2/r32/edx" # sign-extend, but array size can't be negative "b9/copy-to-ecx " size-of(T) "/imm32" "f7 7/subop/idiv-eax-edx-by %ecx" if reg is not eax "89/<- %" reg " 0/r32/eax" if reg is not edx "5a/pop-to-edx" if reg is not ecx "59/pop-to-ecx" if reg is not eax "58/pop-to-eax" # User-defined types If a record (product) type T was defined to have elements a, b, c, ... of types T_a, T_b, T_c, ..., then accessing one of those elements f of type T_f: var/reg: (addr T_f) <- get var2/reg2: (addr T), f => "8d/copy-address *(" reg2 "+" offset(f) ") " reg "/r32" var/reg: (addr T_f) <- get var2: T, f => "8d/copy-address *(ebp+" var2.stack-offset "+" offset(f) ") " reg "/r32" # Allocating memory allocate in: (addr handle T) => "(allocate Heap " size-of(T) " " in ")" populate in: (addr handle array T), num # can be literal or variable on stack or register => "(allocate-array2 Heap " size-of(T) " " num " " in ")" populate-stream in: (addr handle stream T), num # can be literal or variable on stack or register => "(new-stream Heap " size-of(T) " " num " " in ")" read-from-stream s: (addr stream T), out: (addr T) => "(read-from-stream " s " " out " " size-of(T) ")" write-to-stream s: (addr stream T), in: (addr T) => "(write-to-stream " s " " in " " size-of(T) ")" vim:ft=mu:nowrap:textwidth=0