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

Mu Client Interface as C Binding #40

Open
wks opened this issue Aug 21, 2015 · 0 comments
Open

Mu Client Interface as C Binding #40

wks opened this issue Aug 21, 2015 · 0 comments

Comments

@wks
Copy link
Member

wks commented Aug 21, 2015

The current API is expressed in a language-neutral form, and it is the implementation that decides how to implement such an interface. Programmers still need to resort to implementation-specific interfaces to actually use a particular Mu implementation.

Since C is so widely used as a system programming language, the Mu client interface (a.k.a the API) should be expressed as data types and function calls in the C programming language. If the client is not in C, it usually still has a C FFI.

The API in C

Resources in Mu are exposed in opaque types which have reference semantics: they can be copied and still refers to the same resource.

  • mu_micro_vm_t: a reference to a Mu micro VM instance.
  • mu_client_agent_t: a reference to a client agent.
  • mu_handle_t: a handle to a value in the Mu type system exposed to the client.

Messages are C functions. Like JNI, they are contained in a struct: typedef struct mu_api_msgs {...} mu_api_msgs_t. In this way, the client in C does not need to link against any libraries when compiling. The reason is, for a Mu micro VM implemented in a higher-level language (like the reference implementation in Scala), the binding of the callable C function is generated very late, later than even the loading time, and has no access to the native loader.

For example, assume there is a mu_api_msgs_t* msgs defined:

mu_client_agent_t ca = ...

char buf[999999];
int sz;
// load file into buf
msgs->load_bundle(ca, buf, sz); // Load a bundle

// Putting C values into Mu
mu_handle_t h1 = msgs->put_schar(ca, 127);
mu_handle_t h2 = msgs->put_sshort(ca, 32767);
mu_handle_t h3 = msgs->put_sint(ca, 42);
mu_handle_t h4 = msgs->put_slong(ca, 42);
mu_handle_t h5 = msgs->put_slonglong(ca, 999999999999999);

// Converting Mu values to C
int v3 = msgs->to_sint(ca, h3);
unsigned long v4 = msgs->to_ulong(ca, h4); // just treat the int as unsigned

Mu-level flags are C preprocessor macros. They have type int.

msgs->store(SEQ_CST, hLoc, hNewVal); // SEQ_CST is a macro

Callbacks, including the trap handler and undefined function handler, have defined signatures:

typedef mu_trap_return_status_t (*mu_trap_handler_t)(
    mu_client_agent_t ca,
    mu_handle_t stack,
    mu_handle_t thread,
    int watchpoint_id,
    mu_handle_t &new_stack,
    mu_handle_t &data_passed,
    mu_handle_t &new_exception,
    mu_api_msgs *msgs,
    void *user_data);

typedef void (*mu_undefined_function_handler_t)(
    mu_micro_vm_t microvm,
    int funciton_id,
    mu_api_msgs *msgs,
    void *user_data);

These functions are registered via the msgs->register_trap_handler and msgs->register_undefined_function_handler API messages. In their parameters, the user_data is an arbitrary pointer provided by the client in an implementation-specific manner (see below).

Implementation-defined behaviours

Some aspects of the C binding are implementation-specified. They include:

  • How to create a Mu micro VM? Options are:
    1. The C executable creates the Mu instance.
    2. Mu loads the C dynamic library.
    3. Mu starts separately and C connects to the existing instance in the same process.
    4. C connects to a Mu instance in a different process, or a different machine.
  • Options in creating Mu instances. Options are:
    1. Heap size. Giving a heap size means the Client determines the heap size rather than Mu automatically decide its own storage.
    2. Global data space size. Setting this value means the global data may have their own storage. Actual implementation could use the heap space, too.
    3. Stack size. Similarly, this is too implementation-specific.
  • What happens during initialisation?
    1. Mu calls a C function to initialise the client, and the client provides a void* to Mu for the client's own context. (note: in this case, it is Mu loading C rather than C creating Mu.)
    2. C creates a Mu instance, and sets its void* user data in a proprietary API message.

Open questions

  • Should we allow each Mu implementation have its own "namespace"? The opaque types (mu_micro_vm_t and so on) are opaque, but different implementations may have different representations. The current C binding design forbids one C program working with more than one Mu implementations (though it is okay to work with more than one instances of the same implementation).
    • JNI does not solve this problem, either.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant