GitLab will be upgraded to the 12.10.14-ce.0 on 28 Sept 2020 at 2.00pm (AEDT) to 2.30pm (AEDT). During the update, GitLab and Mattermost services will not be available. If you have any concerns with this, please talk to us at N110 (b) CSIT building.

native-interface.rest 15.1 KB
Newer Older
Kunshan Wang's avatar
Kunshan Wang committed
1 2 3 4 5 6
================
Native Interface
================

This chapter defines the Mu native interface.

7 8 9 10 11 12 13 14 15 16 17 18 19
    NOTE: The term **foreign function interface** may have been used in many
    other places by experienced VM engineers to mean a heavy-weighted complex
    interface with another language. JikesRVM users use **foreign function
    interface** to refer to JNI and use **syscall** to refer to a light-weight
    unsafe mechanism to call arbitrary C functions with minimal overhead.
    
    The ``CCALL`` instruction is more similar to the latter. It has minimum
    overhead, but provides no protection to malicious code. So it must be used
    with care. 

    To reduce confusion, we use the term **unsafe native interface** or just
    **native interface** instead of *foreign function interface*.

Kunshan Wang's avatar
Kunshan Wang committed
20 21 22
The **native interface** is a *light-weight* *unsafe* interface through which
*Mu IR programs* communicate with *native programs*.

Kunshan Wang's avatar
Kunshan Wang committed
23 24 25 26 27 28 29 30 31 32 33 34 35
    NOTE: This has no direct relationship with the Mu client interface.
    
    * Native programs are usually written in C, C++ or other low-level languages
      and usually does not run on VMs.

    * A Mu client is not necessary a native program.  The client can be written
      in a managed language, running in a VM, running in the same Mu VM as
      user-level programs (i.e. a "metacircular" client), or living in a
      different process or even a different computer, communicating with Mu
      using sockets.

    However, it does not rule out the possibility to implement the Mu client
    interface *for* native programs *via* this native interface.
Kunshan Wang's avatar
Kunshan Wang committed
36 37 38 39 40 41 42 43 44 45 46

The main purpose of the native interface is

1. to interoperate with the operating system by invoking system libraries
   (including system calls), and
   
2. to interoperate with libraries written in other programming languages.

..

    NOTE: The purpose of the Mu client interface is to let the client control
Kunshan Wang's avatar
Kunshan Wang committed
47 48
    the Mu micro VM and handle events. The native interface is not about
    "controlling Mu".
Kunshan Wang's avatar
Kunshan Wang committed
49

Kunshan Wang's avatar
Kunshan Wang committed
50 51 52 53 54 55
It is not a purpose to interface with *arbitrary* native libraries. This
interface should be minimal but just enough to handle most *common* system calls
(e.g. ``open``, ``read``, ``write``, ``close``, ...) and *common* native
libraries.  Complex data types and functions (e.g. those with unusual
size/alignment requirements or calling conventions) may require wrapper code
provided by the language implementer.
Kunshan Wang's avatar
Kunshan Wang committed
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77

The native interface is not required to be *safe*. The overhead of this
interface should be as low as possible. It is the client's responsibility to
implement things like JNI on top of this interface.

    For JikesRVM users: The native interface includes raw memory access which is
    similar to "vmmagic" and the ``CCALL`` instruction is more like the
    "syscall" mechanism. They are not safe, but highly efficient and should be
    used with care.

..

    NOTE: Directly making system calls from Mu and bypassing the C library
    (libc) is theoretically possible, but is not a mainstream way to do so. It
    has a lower priority in the design.

Outline
=======

This interface has several aspects:

1. **Raw memory access**: This interface provides pointer types and directly
Kunshan Wang's avatar
Kunshan Wang committed
78
   access the memory via pointers.
Kunshan Wang's avatar
Kunshan Wang committed
79

Kunshan Wang's avatar
Kunshan Wang committed
80 81 82 83
2. **Exposing Mu memory to the native world**: This allows native programs to
   access Mu memory in a limited fashion.

3. **Native function call**: This interface provides a mechanism to call a
Kunshan Wang's avatar
Kunshan Wang committed
84 85
   native function using a native calling convention.

Kunshan Wang's avatar
Kunshan Wang committed
86
4. **Callback from native programs**: This interface will enable calling back
Kunshan Wang's avatar
Kunshan Wang committed
87 88
   from the native program.

Kunshan Wang's avatar
Kunshan Wang committed
89
5. **Inline assembly**: Directly inserting machine-dependent instructions into
Kunshan Wang's avatar
Kunshan Wang committed
90 91 92 93 94
   a Mu IR function.

Raw Memory Access
=================

95 96 97
This section defines mechanisms for raw memory access. *Pointers* give Mu
programs access to the native (raw) memory, while *pinning* gives native
programs access to the Mu memory.
Kunshan Wang's avatar
Kunshan Wang committed
98 99 100 101

Pointers
--------

Kunshan Wang's avatar
Kunshan Wang committed
102
A **pointer** is an address in the memory space of the current process.  A
Kunshan Wang's avatar
Kunshan Wang committed
103 104
pointer can be a **data pointer** (type ``uptr<T>``) or **function pointer**
(type ``ufuncptr<sig>``). The former assumes a data value is stored in a region
Kunshan Wang's avatar
Kunshan Wang committed
105 106 107
beginning with the address. The latter assumes a piece of executable machine
code is located at the address.

Kunshan Wang's avatar
Kunshan Wang committed
108
``uptr<T>``, ``ufuncptr<sig>`` and ``int<n>``, where ``T`` is a type, ``sig`` is a
109 110 111
function signature, can be cast to each other using the ``PTRCAST`` instruction.
The address is preserved and the ``int<n>`` type has the numerical value of the
address. Type checking is not performed.
Kunshan Wang's avatar
Kunshan Wang committed
112 113 114 115 116 117 118 119 120

    Potential problem: There may be machines where data pointers have a
    different size from function pointers, but I have never seen one.

    For C users: C spec never defined pointers as addresses. C pointers can
    point to either objects (region of storage) or functions. Casting between
    object pointers, function pointers and integers has implementation-defined
    behaviours.

Kunshan Wang's avatar
Kunshan Wang committed
121 122 123 124
    There are segmented architectures, including x86, whose "pointers" are
    segments + offsets. However, apparently the trend is to move to a "flat"
    memory space.

Kunshan Wang's avatar
Kunshan Wang committed
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
Pinning
-------

A **pinning** operations takes either a ``ref<T>`` value or an ``iref<T>`` value
as parameter. The result is a data pointer. If it is an ``iref``, the data
pointer can be used to access the memory location referred by the ``iref``.
Pinning a ``NULL`` ``iref`` returns a ``NULL`` pointer whose address is 0. If it
is a ``ref``, it is equivalent to pin the ``iref`` of the memory location of
the object itself, or 0 if the ``ref`` itself is ``NULL``.

An **unpinning** operation also takes either a ``ref<T>`` value or an
``iref<T>`` value as parameter, but returns ``void``.

In each thread, there is a conceptual "pinning multi-set" (may contain repeated
elements). A pinning operation adds a ``ref`` or ``iref`` into this multi-set,
and an unpinning operation removes one instance of the ``ref`` or ``iref`` from
141 142
his multi-set. A memory location is pinned as long as there is at least one
``iref`` to that memory location in the pinning multi-set of any thread.
Kunshan Wang's avatar
Kunshan Wang committed
143 144 145 146 147 148 149 150

    NOTE: This requires the Mu micro VM to perform somewhat complex
    book-keeping, but this gives Mu the opportunity for performance improvement
    over global Boolean pinning, where a pinned object can be unpinned instantly
    by an unpinning operation in any thread. The "pinning multi-thread" can be
    implemented as a thread-local buffer. In this case, if GC never happens, no
    expensive atomic memory access or inter-thread synchronisation is performed.

Kunshan Wang's avatar
Kunshan Wang committed
151 152 153
Calling between Mu and Native Functions
=======================================

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
Calling Conventions
-------------------

The calling conventions involving native programs are platform-dependent and
implementation-dependent. It should be defined by platform-specific binary
interfaces (ABI) as supplements to this Mu specification. Mu implementations
should advertise what ABI it implements.

Calling conventions are identified by flags (``#XXXXXX``) in the IR. Mu defines
the flag ``#DEFAULT`` and its numerical value 0x00 for the default calling
convention of platforms. This flag is always available. Other calling
conventions can be defined by implementations.

The calling convention determines the type of value that are callable by the
``CCALL`` instruction (described below), and the type of the exposed value for
Mu functions (described below). The type is usually a ``ufuncptr<sig>`` for C
functions, which are called via their addresses. Other examples are:

* If it is desired to make system calls directly from Mu, then the type can be
  an integer, i.e. the system call number.
  
* If it is something like `a SWAP-STACK operation implemented as a calling
  convention <http://dl.acm.org/citation.cfm?id=2400695>`__, then the callee can
  be a stack pointer in the form of ``uptr<void>``.

Mu Functions Calling Native Functions
Kunshan Wang's avatar
Kunshan Wang committed
180 181 182 183 184 185 186
-------------------------------------

The ``CCALL`` instruction calls a native function. Determined by calling
conventions, the native function may be represented in different ways, and the
arguments are passed in different ways. The return value of the call will be the
return value of the ``CCALL`` instruction, which is a Mu SSA variable.

187
Native Functions Calling Mu Functions
Kunshan Wang's avatar
Kunshan Wang committed
188 189 190 191 192 193 194 195
-------------------------------------

A Mu function can be **exposed** as a native function pointer in three ways:

1. Statically, an ``.expose`` top-level definition exposes a Mu function as a
   native value according to the desired calling convention. For the default
   calling convention, the result is usually a function pointer.

196 197 198
2. Dynamically, the ``@uvm.native.expose`` common instructions can expose a Mu
   function, and the ``@uvm.native.unexpose`` common instruction deletes the
   exposed value.
Kunshan Wang's avatar
Kunshan Wang committed
199

200
3. Dynamically, the ``expose`` and ``unexpose`` API function do the same thing
Kunshan Wang's avatar
Kunshan Wang committed
201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275
   as the above instructions.

A "cookie", which is a 64-bit integer value, can be attached to each exposed
value. When a Mu function is called via one of its exposed value, the attached
cookie can be retrieved by the ``@uvm.native.get_cookie`` common instruction in
the callee, or 0 if called directly from Mu.

    NOTE: The purpose for the cookie is to support "closures". In some
    high-level languages, the programmer-accessible "functions" are actually
    closures, i.e. codes with attached data. Implemented on Mu, multiple
    different closures may share the same Mu function as their codes, but has
    different attached data. For example, in Lua::

        function make_adder(y)
            return function(x)
                return x + y
            end
        end

        plus_one = make_adder(1)
        plus_two = make_adder(2)

        print(plus_one(3), plus_two(3))     -- 4 5

    ``plus_one`` and ``plus_two`` may probably share the same underlying Mu
    function as their common implementations, and they only differ by the
    different "up-value" ``y``.

    In C, any sane C programs that use call-backs should also have a ``void *`` 
    as the "user data". For example, the ``pthread_create`` routine takes an
    extra ``void *arg`` parameter which will be passed to its ``start_routine``
    as the argument. If the call-back is supposed to be a wrapper of a
    high-level language closure, the user data will be its context.
    
    However, different C programs support user data in different ways (if at
    all). For example, the UNIX signal handler function takes exactly one
    parameter which is the signal number: ``typedef void (*sig_t) (int)``. If a
    closure is supposed to handle UNIX signals, it must be able to identify its
    context by merely the exposed function pointer.

    One way to work around this problem is to generate a trampoline function
    which sets the cookie and jumps to the real callee. Many different
    trampolines can be made for a single Mu function, each of which supplies a
    different cookie. In this case, the cookie can identify the context for the
    closure.

    The simplest kind of cookie is an integer, but an object reference may also
    be a candidate.

Since Mu programs need special contexts to execute (such as the thread-local
memory allocation pool for the garbage collector, and the notion of the "current
stack" for the SWAP-STACK operation), a native thread needs to attach itself to
the Mu instance before calling any Mu functions. If a Mu thread calls native
code from Mu, then it is already attached and can freely call back to Mu again.
How to attach a thread to Mu is implementation-defined.

    For JVM users: The JNI invocation API function ``AttachCurrentThread()`` and
    ``DetachCurrentThread()`` are the counterpart of this requirement.

Stack Sharing and Stack Introspection
-------------------------------------

The callee may share the stack with the caller. 

When a Mu function "A" calls a native function which then calls back to another
Mu function "B", Mu sees one single native frame between the frames for "A" and
"B". When a Mu function is called from a native function without other Mu
functions below, Mu consider the Mu function sitting on top of a native frame.

Stack introspection can skip native frames and introspect other Mu frames below.

    NOTE: The requirement to "see through" native frames is partially required
    by exact garbage collection, in which case all references in the stack must
    be identified.

276 277 278 279 280 281 282 283 284 285 286 287 288 289 290
However, throwing Mu exceptions into native frames has implementation-defined
behaviour. Attempting to pop native frames via the API also has
implementation-defined behaviour.

    NOTE: In general, it is not safe to force unwind native frames because
    native programs may need to clean up their own resources. Existing
    approaches, including JNI, models high-level (such as Java-level) exceptions
    as a query-able state rather than actual stack unwinding through native
    programs.

Native exceptions thrown into Mu frames also have implementation-defined
behaviours.

    NOTE: Similar to native frames, Mu programs may have even more necessary
    clean-up operations, such as GC barriers.
Kunshan Wang's avatar
Kunshan Wang committed
291

292 293
Changes in the Mu IR and the API introduced by the native interface
===================================================================
Kunshan Wang's avatar
Kunshan Wang committed
294

295
**New types**:
Kunshan Wang's avatar
Kunshan Wang committed
296

Kunshan Wang's avatar
Kunshan Wang committed
297 298
* ``uptr < T >``
* ``ufuncptr < sig >``
Kunshan Wang's avatar
Kunshan Wang committed
299

Kunshan Wang's avatar
Kunshan Wang committed
300
See `Type System <type-system.rest>`__
Kunshan Wang's avatar
Kunshan Wang committed
301

302
**New top-level definitions**:
Kunshan Wang's avatar
Kunshan Wang committed
303

304
* function exposing definition
Kunshan Wang's avatar
Kunshan Wang committed
305

Kunshan Wang's avatar
Kunshan Wang committed
306
See `Mu IR <uvm-ir.rest>`__.
Kunshan Wang's avatar
Kunshan Wang committed
307

308
**New instructions**:
Kunshan Wang's avatar
Kunshan Wang committed
309

310 311 312 313 314 315
* ``PTRCAST``
* ``@uvm.native.pin``
* ``@uvm.native.unpin``
* ``@uvm.native.expose``
* ``@uvm.native.unexpose``
* ``@uvm.native.get_cookie``
Kunshan Wang's avatar
Kunshan Wang committed
316

Kunshan Wang's avatar
Kunshan Wang committed
317 318
See `Instruction Set <instruction-set.rest>`__ and `Common Instructions
<common-insts.rest>`__.
Kunshan Wang's avatar
Kunshan Wang committed
319

320
**Modified instructions**:
Kunshan Wang's avatar
Kunshan Wang committed
321

322
* Memory addressing:
Kunshan Wang's avatar
Kunshan Wang committed
323

324 325 326 327 328 329 330 331
  * ``GETFIELDIREF``
  * ``GETELEMIREF``
  * ``SHIFTIREF``
  * ``GETVARPARTIREF``
  * ``LOAD``
  * ``STORE``
  * ``CMPXCHG``
  * ``ATOMICRMW``
Kunshan Wang's avatar
Kunshan Wang committed
332

333
* ``CCALL``
Kunshan Wang's avatar
Kunshan Wang committed
334

335
Memory addressing instructions take an additional ``PTR`` flag. If this flag is
Kunshan Wang's avatar
Kunshan Wang committed
336
present, the location operand must be ``uptr<T>`` rather than ``iref<T>``. For
337
example:
Kunshan Wang's avatar
Kunshan Wang committed
338 339 340 341 342 343 344 345 346 347

* ``%new_ptr = GETFIELDIREF PTR <@some_struct 3>   %ptr_to_some_struct``
* ``%new_ptr = GETELEMIREF  PTR <@some_array @i64> %ptr_to_some_array @const1``
* ``%new_ptr = SHIFTIREF    PTR <@some_elem  @i64> %ptr_to_some_elem  @const2``
* ``%new_ptr = GETVARPARTIREF   PTR <@some_hybrid> %ptr_to_some_hybrid``
* ``%old_val = LOAD          PTR SEQ_CST         <@T> %ptr_to_T``
* ``%void    = STORE         PTR SEQ_CST         <@T> %ptr_to_T %newval``
* ``%result  = CMPXCHG       PTR ACQ_REL ACQUIRE <@T> %ptr_to_T %expected %desired``
* ``%old_val = ATOMICRMW ADD PTR SEQ_CST         <@T> %ptr_to_T %rhs``

Kunshan Wang's avatar
Kunshan Wang committed
348
See `Instruction Set <instruction-set.rest>`__.
Kunshan Wang's avatar
Kunshan Wang committed
349

350
**New API functions**:
Kunshan Wang's avatar
Kunshan Wang committed
351

352 353 354 355 356
* ``ptrcast``
* ``pin``
* ``unpin``
* ``expose``
* ``unexpose``
Kunshan Wang's avatar
Kunshan Wang committed
357

358
**Modified API functions**:
Kunshan Wang's avatar
Kunshan Wang committed
359

360
The ``cur_func_ver`` function, in addition to returning the function version
361 362 363
ID, it may also return 0 if the selected frame is a native frame. (Multiple
native frames are counted as one between two Mu frames.)

364 365
The ``pop_frames_to`` function has implementation-defined behaviours when
popping native frames.
366 367 368 369

When rebinding a thread to a stack with a value, and the top frame is on a call
site (native or Mu), the value associated with the rebinding is the return value
of the call.
Kunshan Wang's avatar
Kunshan Wang committed
370 371 372
  
Future Works
============
Kunshan Wang's avatar
Kunshan Wang committed
373

Kunshan Wang's avatar
Kunshan Wang committed
374
    TODO: Inline assembly
Kunshan Wang's avatar
Kunshan Wang committed
375 376

.. vim: tw=80