Skip to content
This repository has been archived by the owner on Aug 2, 2019. It is now read-only.

Latest commit

 

History

History
1048 lines (753 loc) · 42 KB

uvm-client-interface.rest

File metadata and controls

1048 lines (753 loc) · 42 KB

Mu Client Interface

A C header is available here: muapi.h

Overview

This chapter defines the Mu Client Interface, or "the API", the interfaces for the client to load bundles and manipulate the state of Mu, including creating stacks and threads, allocating objects, accessing memory and manipulating stacks and frames.

It is defined in the C programming language. A C header is available here: muapi.h. Clients can also be written in other languages via language bindings, which is beyond this specification.

Tools available: A Python script muapiparser.py is available. It parses the muapi.h header and generates a JSON-like tree. Language binding developers can use this script to automatically generate interfaces to higher-level languages.

Most API functions are also available in the form of instructions (such as refcast and REFCAST) or common instructions (such as new_stack and @uvm.new_stack). See their respective chapters.

Starting Up and Shutting Down

How to start a Mu micro VM instance or a client is implementation-specific.

The Mu specification defines some types for using with common instructions, such as @uvm.meta.byteref. These types are always available. Whether other types, signatures, constants, global cells or functions are already defined, declared or exposed, or whether any Mu objects, stacks or Mu threads already created is implementation-specific.

How to stop a Mu micro VM and/or a client is implementation-specific. Stopping the micro VM implies stopping all Mu threads in it.

The Mu Micro VM and Client Contexts

Mu IDs and names are represented as:

typedef uint32_t MuID;
typedef char *MuName;

A Mu instance is represented as a pointer to the struct MuVM:

typedef struct MuVM MuVM;

struct MuVM {
    void   *header;   // Refer to internal stuff

    MuCtx* (*new_context     )(MuVM *mvm);
    MuID   (*id_of           )(MuVM *mvm, MuName name);
    MuName (*name_of         )(MuVM *mvm, MuID id);
    void   (*set_trap_handler)(MuVM *mvm, MuTrapHandler trap_handler, MuCPtr userdata);
};

The client interacts with Mu for almost all tasks through client contexts, or simply context when unambiguous.

NOTE: In older versions of the spec, it was called "client agent".

A client context is represented as a pointer to the struct MuCtx:

typedef struct MuCtx MuCtx;

struct MuCtx {
    void   *header;   // Refer to internal stuff

    MuID   (*id_of        )(MuCtx *ctx, MuName name);
    MuName (*name_of      )(MuCtx *ctx, MuID id);
    void   (*close_context)(MuCtx *ctx);
    void   (*load_bundle  )(MuCtx *ctx, char *buf, MuArraySize sz);
    void   (*load_hail    )(MuCtx *ctx, char *buf, MuArraySize sz);
    ...
};

The full list of member function pointers is listed in the header muapi.h.

Both the MuVM and the MuCtx struct contain many function pointer fields. Each function pointer must be called with the MuVM or MuCtx pointer as its first argument.

NOTE: This design is inspired by JNI. Exposing API functions as struct members rather than as global C functions has two advantages:

  1. A client can use multiple Mu implementations at the same time. Each implementation should provide its own structures.
  2. The client does not refer to any symbol in other dynamic libraries. So the client does not need to link against any binary libraries at compile time. This makes the client less coupled with a particular Mu implementation. It also allows the Mu micro VM to provide its API functions at the run time (JIT compile its own API functions, or even implementing the API in Mu IR itself).

The Mu instance is shared by all client threads, and its member functions can be called by any client thread without additional synchronisation. A client context can only be used by one client thread at a time.

NOTE: Client contexts are supposed to hold thread-local states similar to what a Mu thread holds. For example, implementations may reserve blocks of heap memory in client contexts so that memory allocation will not always require global locks. This trick is used by many garbage collectors that support parallel mutators, for example, Immix.

It holds Mu values for the client. The values are referred by the client via opaque handles. Those handles are defined as:

// Top value type.
typedef void *MuValue;                // Any Mu value

// Abstract value type.
typedef MuValue MuSeqValue;           // array or vector
typedef MuValue MuGenRefValue;        // ref, iref, funcref, threadref, stackref, framecursorref, irnoderef

// concrete value types
typedef MuValue MuIntValue;           // int<n>
typedef MuValue MuFloatValue;         // float
typedef MuValue MuDoubleValue;        // double
typedef MuValue MuUPtrValue;          // uptr
typedef MuValue MuUFPValue;           // ufuncptr

typedef MuSeqValue MuStructValue;     // struct<...>
typedef MuSeqValue MuArrayValue;      // array<T l>
typedef MuSeqValue MuVectorValue;     // vector<T l>

typedef MuGenRefValue MuRefValue;           // ref<T>
typedef MuGenRefValue MuIRefValue;          // iref<T>
typedef MuGenRefValue MuTagRef64Value;      // tagref64
typedef MuGenRefValue MuFuncRefValue;       // funcref<sig>
typedef MuGenRefValue MuThreadRefValue;     // threadref
typedef MuGenRefValue MuStackRefValue;      // stackref
typedef MuGenRefValue MuFCRefValue;         // framecursorref
typedef MuGenRefValue MuIRNodeRefValue;     // irnoderef

Each handle can only hold a Mu value of the type shown in the comments above. Since the C programming language does not support user-defined type hierarchies, they are not checked by the C compiler, but the users must only pass appropriate handles to the appropriate API functions.

Handles have reference semantics: a handle refers to a value the client context holds. Handles can be copied as if they are pointers in C, and the resulting handles refer to the same value held by a context.

A handle is only valid within a client context. They must only be accessed with API functions that operates on the same context the handle is in. Handles are valid until either the value held by the context is explicitly deleted, or the client context is closed (explained later). Specifically, like SSA variables in the IR, a handle of general reference types always refer to the same object, location or other entities. This means a handle can keep Mu heap objects alive.

NOTE: The decision of using opaque handles serves two purposes. Both purposes are inspired by the Lua C API and JNI.

  1. To segregate the type system of Mu and the client. The type system of C is very different from Mu. For example, C does not have portable vector types. It does not support int<n> for n other than 8, 16, 32, 64 or 128. General reference types ref, iref, funcref, threadref and stackref have no C counterparts, either, and their representations are specific to Mu implementations.
  2. To simplify garbage collection. By recording all references held by the client, Mu can easily identify all roots. Making the representation of handles implementation-specific gives the client many choices, for instance:
    1. Handles can be raw pointers and all client-held references are pinned.
    2. Handles are indices in an indirection table. Since all memory accesses are done via one indirection, the Mu GC can still move objects.

Lua 4.0, introduced a stack-based API to pass values to/from the C program. That stack is unnecessary for Mu because Mu is not a stack-based VM. The API is also not designed to frequently exchange data frequently with the client (see the (unsafe) Native Interface for a more efficient (and less safe) interface).

For Lua users: The client context is the counterpart of lua_State. Both maintain a registry of values in order to segregate Lua or Mu types and garbage collection from the C or client world. However, Mu client context can also access the Mu memory (heap, stack or global) which is shared between multiple client context as well as multiple Mu Threads running concurrently in the same Mu instance. In Lua, lua_State instances are isolated.
For JNI users: The client context is the counterpart of the context of a JNI invocation or an "attached" native thread, where there is a registry of Java references. Mu handles are like local references, and the Mu API does not have global references. For that need, consider using global cells (.global in the IR) to store shared values and use the appropriate atomic access and memory order.

Convention

In this document, parameters of type MuVM*, MuCtx* and all subtypes of MuValue must no be the C NULL pointer, or a handle to a Mu NULL general reference value, unless explicitly stated otherwise. API functions will not return C NULL pointers unless explicitly stated otherwise.

NOTE: This is the billion dollar mistake. Language bindings of high-level languages are encouraged to use the Option[T] type or equivalent, if available.

MuVM Functions

In the following functions, the first argument mvm must be the same MuVM* pointer to the MuVM structure that contains the function pointer.

MuCtx* (*new_context)(MuVM *mvm);

The newcontext function creates a new client context.

For Lua users: This is similar to lua_newstate, but multiple client contexts share the Mu memory and can be used concurrently.
MuID    (*id_of  )(MuVM *mvm, MuName name);
MuName  (*name_of)(MuVM *mvm, MuID id);

The id_of function looks up the corresponding ID by a name. The name_of function looks up the name by an ID. Looking up names or IDs that do not exist has undefined behaviour.

void    (*set_trap_handler      )(MuVM *mvm, MuTrapHandler trap_handler, MuCPtr userdata);

The set_trap_handler function sets the handler for traps. This overrides the trap handler registered by the @uvm.meta.set_trap_handler instruction. See the Trap Handling section below for more information.

userdata will be passed to the handlers as the last element when they are called.

MuCtx Functions

In the following functions, the first argument ctx must be the same MuCtx* pointer to the MuCtx structure that contains the function pointer.

Basic functions

MuID        (*id_of  )(MuCtx *ctx, MuName name);
MuName      (*name_of)(MuCtx *ctx, MuID id);

They are the same as the MuVM functions. More often than not (such as trap handling), a client function only has access to a MuCtx* pointer rather than a MuVM* pointer.

void        (*close_context)(MuCtx *ctx);

The close_context function closes the current client context. After calling this function, this MuCtx* may no longer point to a valid MuCtx structure and, thus, must not be used again. All values held by this context are released. Specifically, all values of reference type are cleared so they no longer strongly refer to objects and keep them alive. All handles created from this context become invalid.

Implementations may release additional resources, such as context-specific (thread-local) memory allocation pools.

Bundle and HAIL loading

typedef uintptr_t MuArraySize;

void        (*load_bundle)(MuCtx *ctx, char *buf, MuArraySize sz);
void        (*load_hail  )(MuCtx *ctx, char *buf, MuArraySize sz);

The load_bundle function loads a Mu IR bundle, and the load_hail function loads a HAIL script. The content is held in the memory pointed to by buf, and sz is the length of the content in bytes.

Concurrency: The content of the bundle or the effect of the HAIL script is fully visible to other evaluations in the client that happen after this call.

TODO: These two functions should be made optional or lifted to the higher-level API which should be beyond this spec. The text-form bundle needs a parser, and the HAIL script is also not the most efficient way to load data into Mu at run time.

For Lua users: This is similar to lua_load, but a Mu bundle itself is not an executable thing like a Lua top-level function.

For JVM users: This is similar to class loading. load_bundle loads the code and some Mu-level constants, but some Java-level constants, such as Strings, may be mapped to heap objects in Mu, and can be created and initialised using HAIL.

Converting values to/from handles

// Convert from C values to Mu values
MuIntValue      (*handle_from_sint8  )(MuCtx *ctx, int8_t     num, int len);
MuIntValue      (*handle_from_uint8  )(MuCtx *ctx, uint8_t    num, int len);
MuIntValue      (*handle_from_sint16 )(MuCtx *ctx, int16_t    num, int len);
MuIntValue      (*handle_from_uint16 )(MuCtx *ctx, uint16_t   num, int len);
MuIntValue      (*handle_from_sint32 )(MuCtx *ctx, int32_t    num, int len);
MuIntValue      (*handle_from_uint32 )(MuCtx *ctx, uint32_t   num, int len);
MuIntValue      (*handle_from_sint64 )(MuCtx *ctx, int64_t    num, int len);
MuIntValue      (*handle_from_uint64 )(MuCtx *ctx, uint64_t   num, int len);
MuIntValue      (*handle_from_uint64s)(MuCtx *ctx, uint64_t *nums, MuArraySize nnums, int len);
MuFloatValue    (*handle_from_float  )(MuCtx *ctx, float      num);
MuDoubleValue   (*handle_from_double )(MuCtx *ctx, double     num);
MuUPtrValue     (*handle_from_ptr    )(MuCtx *ctx, MuID mu_type, MuCPtr ptr);
MuUFPValue      (*handle_from_fp     )(MuCtx *ctx, MuID mu_type, MuCFP fp);

where MuCPtr and MuCFP are convenient definitions for C types:

typedef void *MuCPtr;
typedef void _MuCFP_Func();
typedef _MuCFP_Func* MuCFP;     // it is void(*)()

The handle_from_XintYY functions convert C integers to Mu int values. X can be s or u, means the C value is signed or unsigned, respectively. YY is the number of bits in the C value.

The handle_from_uint64s function converts an array of uint64_t values into a single Mu int value. nums points to the array and nnums is the length of the array. Lower 64-bit words are stored in lower indices (little endian).

The len parameter is the length of the Mu int value, i.e. the n in int<n>. If the length of the C value is different from the Mu type, it will be truncated or extended. It is sign-extended for signed C types or zero-extended for unsigned C types.

Other functions are:

  • handle_from_float: Convert a C float value to a Mu float value.
  • handle_from_double: Convert a C double value to a Mu double value.
  • handle_from_ptr: Convert a C object pointer to a Mu pointer value. The Mu type is mu_type which must be uptr<T> for some type T.
  • handle_from_fp: Convert a C function pointer to a Mu function pointer value. The Mu type is mu_type which must be ufuncptr<sig> for some signature sig.

For Lua users: These are the counterpart of lua_pushnumber, lua_pushboolean, lua_pushinteger, lua_pushstring and so on. But Mu has much fewer primitive data types. Strings usually cannot be created this way since they are not primitive values.

For JNI users: These are similar to the NewLocalReference function, but Mu handles can refer to not just reference types.

int8_t      (*handle_to_sint8 )(MuCtx *ctx, MuIntValue    opnd);
uint8_t     (*handle_to_uint8 )(MuCtx *ctx, MuIntValue    opnd);
int16_t     (*handle_to_sint16)(MuCtx *ctx, MuIntValue    opnd);
uint16_t    (*handle_to_uint16)(MuCtx *ctx, MuIntValue    opnd);
int32_t     (*handle_to_sint32)(MuCtx *ctx, MuIntValue    opnd);
uint32_t    (*handle_to_uint32)(MuCtx *ctx, MuIntValue    opnd);
int64_t     (*handle_to_sint64)(MuCtx *ctx, MuIntValue    opnd);
uint64_t    (*handle_to_uint64)(MuCtx *ctx, MuIntValue    opnd);
float       (*handle_to_float )(MuCtx *ctx, MuFloatValue  opnd);
double      (*handle_to_double)(MuCtx *ctx, MuDoubleValue opnd);
MuCPtr      (*handle_to_ptr   )(MuCtx *ctx, MuUPtrValue   opnd);
MuCFP       (*handle_to_fp    )(MuCtx *ctx, MuUFPValue    opnd);

The handle_to_XXX functions convert Mu values to C values. Specifically:

The handle_to_XintYY functions convert Mu int values to C integers. X can be s or u, means the C value is signed or unsigned, respectively. YY is the number of bits in the C value. If the length of the Mu value is different from the C type, it will be truncated or extended. It is sign-extended for signed C types or zero-extended for unsigned C types.

Other functions are:

  • handle_to_float: Convert a Mu float value to a C float value.
  • handle_to_double: Convert a Mu double value to a C double value.
  • handle_to_ptr: Convert a Mu uptr<T> value to a C void* value.
  • handle_to_fp: Convert a Mu ufuncptr<sig> value to a C void(*)() value.

For Lua users: These are the counterpart of lua_tonumber, lua_toboolean, lua_tostring and so on. But Mu has much fewer primitive data types.

For JNI users: Primitive types in JNI do not need to be converted. They are not held by handles and they are already compatible with C data types.

Create handles from Mu global variables

MuValue         (*handle_from_const )(MuCtx *ctx, MuID id);
MuIRefValue     (*handle_from_global)(MuCtx *ctx, MuID id);
MuFuncRefValue  (*handle_from_func  )(MuCtx *ctx, MuID id);
MuValue         (*handle_from_expose)(MuCtx *ctx, MuID id);

These instructions create handles which has the same value as the Mu global variables whose ID is id. Specifically,

  • handle_from_const: Creates a handle from a Mu constant.
  • handle_from_global: Creates a handle from a Mu global cell.
  • handle_from_func: Creates a handle from a Mu function.
  • handle_from_expose: Creates a handle from an exposed function.

For Lua users: In Lua, global variables are top-level and they may refer to values or functions. These API functions are similar to the lua_getglobal function.

For JNI users: This is similar to the GetStaticObjectField, GetStaticFieldID or GetStaticMethodID which allows the C program to access fields (including final fields) or methods. Mu does not support OOP directly, so methods may be just Mu functions that take objects as arguments.

Deleting values held by the context

void        (*delete_value)(MuCtx *ctx, MuValue opnd);

Delete a value held by the context. After calling this function, the handle opnd cannot be used. If it was copied in C by assignment, all copies refer to the same value held by the context, and they all become invalid.

For Lua users: This is similar to the lua_pop function.

For JNI users: This is similar to the DeleteLocalRef routine.

Reference type comparison

typedef int MuBool;

MuBool      (*ref_eq )(MuCtx *ctx, MuGenRefValue lhs, MuGenRefValue rhs);
MuBool      (*ref_ult)(MuCtx *ctx, MuIRefValue lhs,   MuIRefValue rhs);

The ref_eq function compares two values of general reference type for equality according to the semantics of the EQ instruction. Both lhs and rhs must have the same general reference type.

The ref_ult function compares two internal reference lhs and rhs for "less-than" according to the semantics of the ULT instruction. Both lhs and rhs must have the same internal reference type.

The return value is 1 for true and 0 for false.

For Lua users: ref_eq is equivalent to the lua_rawequal function for reference types.

For JNI users: ref_eq is equivalent to the IsSameObject function.

Aggregate type operations

MuValue     (*extract_value)(MuCtx *ctx, MuStructValue str, int index);
MuValue     (*insert_value )(MuCtx *ctx, MuStructValue str, int index, MuValue newval);
MuValue     (*extract_element)(MuCtx *ctx, MuSeqValue str, MuIntValue index);
MuSeqValue  (*insert_element )(MuCtx *ctx, MuSeqValue str, MuIntValue index, MuValue newval);

These functions manipulates values of composite types. Specifically,

  • extract_value returns the value of a field at index in a struct value str.
  • insert_value returns a new struct value which is the same as str except the field at index is replaced with newval.
  • extract_element returns the value of an element at index in an array or vector value seq.
  • insert_element returns a new array or vector value which is the same as seq except the element at index is replaced with newval.

str must have struct type. seq must have array or vector type. newval must have the type of the field at index, or the element type. index can be any integer type and is considered unsigned.

For Lua users: extract_value and insert_value are similar to the lua_getfield and the lua_setfield functions, but Mu struct fields are statically typed. extract_element and insert_element are similar to lua_gettable and lua_settable with numerical indices, but Mu has arrays and vectors as value types.

For JNI users: extract_value and insert_value are similar Get<type>Field and Set<type>Field. extract_element and insert_element are similar to the GetObjectArrayElement and SetObjectArrayElement if accessing arrays of references. These Mu functions work with value types. The pin and unpin Mu API function are similar to Get<PrimitiveType>ArrayElemets and Release<PrimitiveType>ArrayElements when the array is in the heap.

Heap memory allocation

MuRefValue  (*new_fixed )(MuCtx *ctx, MuID mu_type);
MuRefValue  (*new_hybrid)(MuCtx *ctx, MuID mu_type, MuIntValue length);

new_fixed allocates an object in the heap which has mu_type type. mu_type must be a fixed-length type.

new_hybrid allocates an object in the heap which has mu_type type. my_type must be a hybrid. length is the length of the variable part.

The return value is the object reference of the allocated object, or NULL (A NULL pointer in C, which means it does not return a handle. It is not a handle to a NULL Mu reference.) when the allocation failed.

For Lua users: They are similar to the lua_newtable or lua_createtable function, but Mu can allocate many different types on the heap. In this sense, it is also similar to lua_newuserdata, but the allocated Mu object has associated metadata to identify references in the object.

For JNI users: They are similar to the AllocObject and the New<xxx>Array routines. Mu is not aware of "initialiser".

Cast between general reference types

MuValue     (*refcast)(MuCtx *ctx, MuValue opnd, MuID new_type);

refcast casts a value of general reference types to a new type new_type. The rules on the old type and the new type and the resulting value follow the same rules as the REFCAST instruction.

Memory addressing

MuIRefValue     (*get_iref           )(MuCtx *ctx, MuRefValue opnd);
MuIRefValue     (*get_field_iref     )(MuCtx *ctx, MuIRefValue opnd, int field);
MuIRefValue     (*get_elem_iref      )(MuCtx *ctx, MuIRefValue opnd, MuIntValue index);
MuIRefValue     (*shift_iref         )(MuCtx *ctx, MuIRefValue opnd, MuIntValue offset);
MuIRefValue     (*get_var_part_iref  )(MuCtx *ctx, MuIRefValue opnd);
  • get_iref converts an object reference into an internal reference to the memory location of the whole object.
  • get_field_iref gets an iref to the field-th field of the struct or of the fixed part of a hybrid referred by the iref opnd.
  • get_elem_iref gets an iref to the index-th element of the array or vector referred by the iref opnd.
  • shift_iref assumes opnd refers to an element in a memory array (including arrays, vectors and the variable part of hybrids in the memory, see Mu and the Memory). It returns the iref of opnd moved by offset (forward if positive, backward if negative).
  • get_var_part_iref gets an iref to the 0-th element of the variable part of the hybrid referred by iref opnd.

index and offset can be any integer types, and are considered signed.

Memory accessing

MuValue     (*load     )(MuCtx *ctx, MuMemOrd ord, MuIRefValue loc);
void        (*store    )(MuCtx *ctx, MuMemOrd ord, MuIRefValue loc, MuValue newval);
MuValue     (*cmpxchg  )(MuCtx *ctx, MuMemOrd ord_succ, MuMemOrd ord_fail,
                    MuBool weak, MuIRefValue loc, MuValue expected, MuValue desired,
                    MuBool *is_succ);
MuValue     (*atomicrmw)(MuCtx *ctx, MuMemOrd ord, MuAtomicRMWOptr op,
                    MuIRefValue loc, MuValue opnd);
void        (*fence    )(MuCtx *ctx, MuMemOrd ord);

where:

typedef uint32_t MuFlag;

// Memory orders
typedef MuFlag MuMemOrd;
#define MU_ORD_NOT_ATOMIC   ((MuMemOrd)0x00)
#define MU_ORD_RELAXED      ((MuMemOrd)0x01)
#define MU_ORD_CONSUME      ((MuMemOrd)0x02)
#define MU_ORD_ACQUIRE      ((MuMemOrd)0x03)
#define MU_ORD_RELEASE      ((MuMemOrd)0x04)
#define MU_ORD_ACQ_REL      ((MuMemOrd)0x05)
#define MU_ORD_SEQ_CST      ((MuMemOrd)0x06)

// Operations for the atomicrmw API function
typedef MuFlag MuAtomicRMWOptr;
#define MU_ARMW_XCHG    ((MuAtomicRMWOptr)0x00)
#define MU_ARMW_ADD     ((MuAtomicRMWOptr)0x01)
#define MU_ARMW_SUB     ((MuAtomicRMWOptr)0x02)
#define MU_ARMW_AND     ((MuAtomicRMWOptr)0x03)
#define MU_ARMW_NAND    ((MuAtomicRMWOptr)0x04)
#define MU_ARMW_OR      ((MuAtomicRMWOptr)0x05)
#define MU_ARMW_XOR     ((MuAtomicRMWOptr)0x06)
#define MU_ARMW_MAX     ((MuAtomicRMWOptr)0x07)
#define MU_ARMW_MIN     ((MuAtomicRMWOptr)0x08)
#define MU_ARMW_UMAX    ((MuAtomicRMWOptr)0x09)
#define MU_ARMW_UMIN    ((MuAtomicRMWOptr)0x0A)
  • load performs a load operation with arguments (ord, T, loc).
  • store performs a store operation with arguments (ord, T, loc, newval).
  • cmpxchg performs a compare exchange operation with arguments (weak, ord_sicc, ord_fail, T, loc, expected, desired). weak is Boolean encoded as int, where 1 means true and 0 means false. *is_succ is set to 1 if successful, or 0 if failed.
  • atomicrmw performs an atomic-x operation with argument (ord, T, loc, opnd), where the x in atomic-x is op.
  • fence is a fence of memory order ord.

In the above operations, T is the type of the referent type of internal reference loc. The type of newval, expected, desired, opnd and the return values of store, cmpxchg and atomicrmw is the strong variant of T (weakref<T> becomes ref<T>).

In the case where T is a general reference type, newval, expected, desired, opnd and the return values may be handles to the Mu NULL value.

The semantics of these instructions are defined by the Mu memory model.

For C users: Functions in stdatomic.h may be implemented in a way incompatible with Mu. This is implementation-defined.

For JNI users: load and store are similar to Get<type>Field and Set<type>Field.

Stack and thread operations

// Thread and stack creation and stack destruction
MuStackRefValue     (*new_stack )(MuCtx *ctx, MuFuncRefValue func);
MuThreadRefValue    (*new_thread_nor)(MuCtx *ctx, MuStackRefValue stack,
                        MuRefValue threadlocal,
                        MuValue *vals, MuBool nvals);
MuThreadRefValue    (*new_thread_exc)(MuCtx *ctx, MuStackRefValue stack,
                        MuRefValue threadlocal,
                        MuRefValue exc);
void                (*kill_stack)(MuCtx *ctx, MuStackRefValue stack);

// Thread-local object reference
void        (*set_threadlocal)(MuCtx *ctx, MuThreadRefValue thread,
                MuRefValue threadlocal);
MuRefValue  (*get_threadlocal)(MuCtx *ctx, MuThreadRefValue thread);

new_stack creates a new Mu stack with func as the bottom function. Returns a stackref to the new stack. The stack-bottom frame will resume from the very beginning of func when resumed, and its state is READY<Ts>, where Ts is the parameter types of func.

new_thread_nor and new_thread_exc create a new Mu thread and binds it to stack. Returns a threadref to the new thread. stack must be in the READY<Ts> state for some types Ts.

  • new_thread_nor passes values to the stack. vals points to the array of values, and nvals is the length of the array. The types of the values must match the Ts types of the stack state.
  • new_thread_exc throws the exception exc to the stack.

The threadlocal parameter is the initial thread-local reference of the new thread. If it is a C NULL pointer, it is equivalent to a Mu NULL object reference.

kill_stack kills the stack. The stack must be in the READY<Ts> state for some Ts. The stack enters the DEAD state. If the stack contains native frames, the behaviour is implementation-defined.

For Lua users: new_stack is similar to coroutine.create. new_thread is similar to coroutine.resume, but actually creates a new thread. The real counterpart of lua_resume is the SWAPSTACK instruction, which is in the Mu IR but not available in the API. kill_stack is similar to lua_close.

For JVM users: The JVM does not distinguish stacks and threads. The closest counterpart is the JVM TI function StopThread, but the Mu kill_stack message does not unwind the stack. It simply marks the whole stack for deletion.

set_threadlocal sets the thread-local object reference of the thread argument to threadlocal.

get_threadlocal returns the current thread-local object reference of thread.

Both get_threadlocal and set_threadlocal can only be used in the trap handler and only on the thread that caused the trap.

Stack introspection

MuFCRefValue    (*new_cursor  )(MuCtx *ctx, MuStackRefValue stack);
void            (*next_frame  )(MuCtx *ctx, MuFCRefValue cursor);
MuFCRefValue    (*copy_cursor )(MuCtx *ctx, MuFCRefValue cursor);
void            (*close_cursor)(MuCtx *ctx, MuFCRefValue cursor);
  • new_cursor allocates a frame cursor, referring to the top frame of stack. Returns the frame cursor reference.
  • next_frame moves the frame cursor so that it refers to the frame below its current frame.
  • copy_cursor allocates a frame cursor which refers to the same frame as cursor. Returns the frame cursor reference.
  • close_cursor deallocates the cursor.
MuID        (*cur_func       )(MuCtx *ctx, MuFCRefValue cursor);
MuID        (*cur_func_ver   )(MuCtx *ctx, MuFCRefValue cursor);
MuID        (*cur_inst       )(MuCtx *ctx, MuFCRefValue cursor);
void        (*dump_keepalives)(MuCtx *ctx, MuFCRefValue cursor, MuValue *results);

These functions operate on the frame referred by cursor.

  • cur_func returns the ID of the frame. Returns 0 if the frame is native.
  • cur_func_ver returns the ID of the current function version of the frame. Returns 0 if the frame is native, or the function of the frame is undefined.
  • cur_inst returns the ID of the current instruction of the frame. Returns 0 if the frame is just created, its function is undefined, or the frame is native.
  • dump_keepalives dumps the values of the keep-alive variables of the current instruction of the frame. If the function is undefined, the arguments are the keep-alive variables. Cannot be used on native frames. As many handles as the keep-alive variables are written in the results array. Values returned in this array may be handles to NULL values if the respective local variable is NULL.
NOTE: The current instruction ID uniquely decides the list of keep-alive variables, so the client can always know how long the results array should be allocated.

For Lua users: The debug interface provides many similar introspection functions.

For JVM users: The JVM TI function GetFrameLocation gets both the method ID and the location of instruction. It is like a combination of cur_func_ver and cur_inst. JVM TI has GetLocalVariable which gets any local variable, but Mu only preserves some user-specified local variables for introspection. The user, however, can write the KEEPALIVE clause to request all local variables to be available.

On-stack replacement

void        (*pop_frames_to)(MuCtx *ctx, MuFCRefValue cursor);
void        (*push_frame   )(MuCtx *ctx, MuStackRefValue stack, MuFuncRefValue func);
  • pop_frames_to pops all frames above cursor.
  • push_frame creates a new frame on the top of stack for the current version of function func.
For JVM users: pop_frames_to is similar to the PopFrame JVM TI function, but the resumption point is immediately after the call site, not immediately before.

Tagged reference operations

int             (*tr64_is_fp   )(MuCtx *ctx, MuTagRef64Value value);
int             (*tr64_is_int  )(MuCtx *ctx, MuTagRef64Value value);
int             (*tr64_is_ref  )(MuCtx *ctx, MuTagRef64Value value);
  • tr64_is_fp checks if value holds an FP number.
  • tr64_is_int checks if value holds an integer.
  • tr64_is_ref checks if value holds a reference.

Return 1 or 0 for true or false.

MuDoubleValue   (*tr64_to_fp   )(MuCtx *ctx, MuTagRef64Value value);
MuIntValue      (*tr64_to_int  )(MuCtx *ctx, MuTagRef64Value value);
MuRefValue      (*tr64_to_ref  )(MuCtx *ctx, MuTagRef64Value value);
MuIntValue      (*tr64_to_tag  )(MuCtx *ctx, MuTagRef64Value value);
  • tr64_to_fp returns the FP number held by value.
  • tr64_to_int returns the integer held by value.
  • tr64_to_ref returns the reference held by value, may be a handle to the NULL value.
  • tr64_to_tag returns the integer tag held by value that accompanies the reference.

They have undefined behaviours if %tr does not hold the value of the expected type.

MuTagRef64Value (*tr64_from_fp )(MuCtx *ctx, MuDoubleValue value);
MuTagRef64Value (*tr64_from_int)(MuCtx *ctx, MuIntValue value);
MuTagRef64Value (*tr64_from_ref)(MuCtx *ctx, MuRefValue ref, MuIntValue tag);
  • tr64_from_fp creates a tagref64 value from an FP number value.
  • tr64_from_int creates a tagref64 value from an integer value.
  • tr64_from_ref creates a tagref64 value from a reference ref and the integer tag tag. ref may be a handle to the NULL value.

Return the created tagref64 value.

Watchpoint operations

typedef uint32_t MuWPID;

void        (*enable_watchpoint )(MuCtx *ctx, MuWPID wpid);
void        (*disable_watchpoint)(MuCtx *ctx, MuWPID wpid);
  • enable_watchpoint enables all watchpoints of ID wpid.
  • disable_watchpoint disables all watchpoints of ID wpid.

Enabling or disabling any non-existing wpid has undefined behaviour.

Object pinning

MuUPtrValue (*pin  )(MuCtx *ctx, MuValue loc);      // loc is either MuRefValue or MuIRefValue
void        (*unpin)(MuCtx *ctx, MuValue loc);      // loc is either MuRefValue or MuIRefValue
  • pin adds one instance of memory location loc to the pinning set local to the current client context ctx. Returns a pointer which has the resulting address. The pointer has the same referent type as loc.
  • unpin removes one instance of memory location loc from the pinning set local to the current client context ctx. Attempting to unpin locations that has not yet been added has undefined behaviour.

loc can be either ref<T> or iref<T>. If it is ref<T>, it pins the underlying memory location as obtained by get_iref. In both cases, the result is uptr<T>.

For Lua users: Lua userdata are always available as pointers. This assumes userdata are always pinned.

For JNI users: The Get<PrimitiveType>ArrayElements and Release<PrimitiveType>ArrayElements> has similar semantics as pinning and unpinning. Also note that the argument of unpin is the same reference as what is pinned, not the resulting (untraced) pointer.

Exposing Mu functions for native callback

MuValue     (*expose  )(MuCtx *ctx, MuFuncRefValue func, MuCallConv call_conv, MuIntValue cookie);
void        (*unexpose)(MuCtx *ctx, MuCallConv call_conv, MuValue value);

where:

typedef MuFlag MuCallConv;
#define MU_CC_DEFAULT   ((MuCallConv)0x00)
  • expose exposes a Mu function func according to the calling convention call_conv. cookie is the attached cookie. The return value is specific to the calling convention.
  • unexpose invalidates an exposed value value created by the @uvm.native.expose instruction or the expose API function with calling convention call_conv.

See the Native Interface chapter for details of expose and unexpose. See the platform-specific native interface for the return value type.

Implementations may define other calling conventions in addition to MU_CC_DEFAULT.

Trap Handling

A trap handler is called when a TRAP or WATCHPOINT instruction is executed. It is also implicitly called when executing an undefined function.

It is unspecified on which stack the trap handler is run.

Mu is allowed to call these handlers concurrently. Mu does not guarantee the order in which these handlers are called, but guarantees the happen-before relation between the trap and the handler. See Memory Model.

The Mu thread that triggers the trap is temporarily unbound from the stack.

NOTE: This unbinding is only conceptual. It is a valid implementation to use the same Mu thread to execute the trap handler.

The signature of trap handlers is:

// Signature of the trap handler
typedef void _MuTrapHandler_Func(
        // input parameters
        MuCtx *ctx,
        MuThreadRefValue thread,
        MuStackRefValue stack,
        MuWPID wpid,
        // output parameters
        MuTrapHandlerResult *result,
        MuStackRefValue *new_stack,
        MuValue **values,
        MuArraySize *nvalues,
        MuValuesFreer *freer,
        MuCPtr *freerdata,
        MuRefValue *exception,
        // input parameter (userdata)
        MuCPtr userdata);

where:

typedef MuFlag MuTrapHandlerResult;

#define MU_THREAD_EXIT          ((MuTrapHandlerResult)0x00)
#define MU_REBIND_PASS_VALUES   ((MuTrapHandlerResult)0x01)
#define MU_REBIND_THROW_EXC     ((MuTrapHandlerResult)0x02)

typedef void _MuValuesFreer_Func(MuValue *values, MuCPtr freerdata);
typedef _MuValuesFreer_Func* MuValuesFreer;

ctx is a new client context created for this particular trap event. thread is a threadref to the thread that causes the trap. stack is stackref to the stack the thread was bound to when trap happened. Both thread and stack are held by ctx. wpid is the watch point ID if it is caused by a WATCHPOINT instruction, or 0 if by TRAP. userdata is the pointer provided when registering the handler.

ctx will be closed when the trap handler returns, so the client must not close it manually.

Before returning, the trap handler should set *result:

  • MU_THREAD_EXIT: The thread thread terminates.

  • Mu_REBIND_PASS_VALUES: The thread thread will be rebound to a stack *new_stack. *nvalues values at *values are passed to *new_stack. The types of *values must match the type expected by *new_stack. Mu will copy the values from the *values array. Then if *freer is not NULL, Mu will call (*freer)(*values, *freerdata).

    NOTE: Effectively, the client is returning an array to Mu. Mu does not know what stack the client is going to rebind to before calling, therefore the client must allocate the array for the values. The client is also responsible for freeing the array, but it loses control after returning to Mu. So Mu will call the "freer" function on behalf of the client. Usually the client can use malloc, and provide a thin wrapper over free as the freer.

  • Mu_REBIND_THROW_EXC: The thread thread will be rebound to a stack *new_stack. It throws exception *exception to the stack.

In all cases, if *new_stack, any value in *values, and/or *exception are used, they must be set and must be held by ctx.

Building Mu IR Bundles

See IR Builder for this part of the API.

Signal Handling

Signal handling is implementation-dependent. Mu may register signal handlers.