Mica has no FFI, in either direction:

  • Every call from Mica to C is a direct C ABI call.
  • Every call from C to Mica is a direct C ABI call.

No wrapper, no glue and no marshaling layer stands between the two languages. A Mica value is laid out the way the C ABI lays out its C counterpart, and a Mica routine is called the way a C function is called, so the two link and call each other as two C files would. The rules that keep the boundary safe live inside the Mica routine or in the runtime, never between the two, and this page states every one a C developer meets there. The tutorials walk the two directions with a running example each: Mica calls C and C calls Mica.

Mica calls C

A C library’s surface is declared once, in its contract, and a C function is then an imported name whose every call the compiler checks against the declared signature. The call it emits is the plain native call the C ABI defines, with the arguments in the registers and stack slots a C caller would use.

A value that a C function answers or hands to a callback, typed in the contract as an enumeration, a subrange, bool or unicode, is checked against its type where it enters Mica, and a value outside it stops the program with ordinal_out_of_domain.

C calls Mica

An exported routine of a Mica library links under the build target’s name, two underscores, and the exported name, with __utf8 behind it in a library built for UTF-8: exp function Fibonacci of a library Utilities is Utilities__Fibonacci. --emit-header writes the C header that declares every export under that name, with every type the exports use.

An exported routine keeps the C convention on its side of the call: a record parameter it only reads arrives by value, as C passes it, and an eight- or sixteen-bit argument is read at its own width, whatever the C caller left in the bits above it.

A Mica program may export routines too, and a C library it calls may call them back through their linkage names while the program runs.

What crosses, and how

MicaC
int8 to int64, uint8 to uint64int8_t to int64_t, uint8_t to uint64_t
float32, float64float, double
boolbool
unicodechar32_t
an enumerationa type name over its base integer, its literals as constants
a subrangeits base integer
a recorda struct of the same fields in the same order
a packed recorda __attribute__((packed)) struct
a fixed array behind a pointeran array type
pointer T; a const parameter of that typeT *; const T *
stringthe descriptor MicaStringView

A value crosses in one of three modes, Mica’s own: by value, where the callee gets a copy; by pointer, address at the call, where the callee may write through it; and by const pointer, where the callee may only read, and the compiler holds a Mica callee to that. A record answered by value travels in registers or through the hidden result address, exactly where the C ABI puts it.

An export C cannot express, such as a fixed array passed or answered by value, a task, or a generic routine, is left out of the generated header, and the header’s opening comment names it with the reason.

Strings and the encodings

A Mica string crosses as two machine words, the address of its first code unit and its length in characters:

typedef struct MicaStringView
{
    const mica_char_t *Data;
    uint64_t Length;
} MicaStringView;

A program or library built for UTF-32 has wchar_t as its code unit, and one built for UTF-8 has char, one byte of its text. The length counts characters under both encodings. A zero code unit follows the last character, so C’s text functions read the text where it stands, and the empty string may carry no address at all.

A C function whose contract takes UTF-8 text is called with the text as it stands from a program built for UTF-8. A program built for UTF-32 writes the text as UTF-8 into a scratch block of the runtime just before the call and releases it after the call, and the call itself is the same direct C ABI call.

A C function’s text result, declared c_text in its contract, is copied into a string of the program’s own at every call, decoded for the program’s encoding: each ill-formed byte sequence becomes one replacement character, and a null pointer becomes the empty string. The C side keeps its own buffer.

Who owns a cell that crosses

Memory crosses the boundary with its owner, and the owner releases it:

  • What a Mica library hands your C code, the library releases. Give it an exported routine that releases what it handed out, and call that routine, never free: free knows only the blocks malloc made.
  • Memory your C code allocated stays yours to release. Hand it to a Mica library to read or to fill, never to release: the compiler refuses a Dispose of a pointer a C function answered, also when another unit receives it, and a checked build stops at such a release with foreign_pointer_release where no rule can see it ahead of time.
  • A borrowed_pointer in a contract carries no ownership: the cell belongs to the other side, and Mica demands no release for it.
  • A string or a record an exported routine answers is released by its caller when the caller is Mica, and a string answered to a C caller still reads as it did after the same thread’s next call.

In the fixed-arena memory class every Mica cell comes from one block of static storage, which a C program may define itself, and the rules above are the same.

Callbacks

A Mica routine handed to C is an ordinary C function pointer, which C calls at the place its contract names. A callback routine is declared with the C shape of its slot and bound by name at the call: C calls it while the imported call runs, at a during_call slot, and its answer steers the C side as a C callback’s would. The contract’s callback slots state the class, the C signature and the userdata parameter.

The compiler refuses, each in its own words: a generic callback, which has no single C shape; a callback that can fail, since C does not read Mica’s failure channel; a callback that reaches into an enclosing routine’s frame; a callback called as a routine or used as a value; a plain routine at a callback slot; a shape that disagrees with the slot’s C signature; and an exported callback.

A C routine a library calls may call the library’s own exports before it returns. The compiler reads every such call as a call of each export, and a routine whose contract states "effects": {"never_calls_back": true} is read as returning only by returning.

Stored callbacks and callbacks from C’s own threads

A slot whose contract class is registered stores the callback and fires it after the call returns, possibly from threads the C library started. A library built with --tasking multicore binds a callback there: each thread that fires it becomes a Mica task of its own, as every C thread that calls into Mica does (see threads in C), so the callback guards what it shares with synchronized. Here a C library keeps Tick and fires it from four threads of its own after Start returned, and the count keeps every firing:

{ A tick adds one to the count under its own mark, whichever thread fires it. }
callback Tick(context : pointer int64);
begin
    synchronized ticks := ticks + 1;
end;

{ A start hands the tick to the C library, which keeps it and fires it later. }
exp procedure Start();
begin
    StartTicker(Tick, address ticks);
end;

The context the C library hands back to every firing outlives the call, so a context that points into the calling routine’s frame, the address of one of its locals, is refused by name: hand it a unit variable’s address or memory the program keeps alive. A library built for one thread refuses the binding by name, because it keeps C’s rule of one thread in Mica code at a time.

A during_call callback may be fired from threads the C function starts while the call runs, and on the multicore floor each firing thread is a task of its own as well, so the callback’s synchronized statement keeps every firing.

Heapless routines

A routine declared heapless promises to use no heap, and the compiler holds it to the promise: every construct that allocates is refused at its own site, and a call of a C function is admitted only where the function’s contract states heapless, the attribute a Mica library’s emitted contract writes for its own heapless routines. A heapless callback handed to such a function is checked like any heapless routine.

A checked build keeps the promise while the program runs, through every road the compiler cannot see: the first allocation or release a heapless routine reaches, inside its C callees too, stops the program and names the routine and what it reached, a C allocator entry such as malloc, or the program’s fixed arena. A C program that links a checked library is watched over the C allocator’s entries in the same way; its own fixed arena is not watched.

Threads in C

A C thread calls a Mica library the way it calls any C library: directly, with no call to make first. The program calls the library’s body entry once, before its first call into the library (see the library’s entries), and no thread makes a call of its own beforehand. Build the library with --tasking multicore, link it with the multicore runtime floor mica-dragon-rt-mc-<arch>.a, and each C thread becomes a Mica task of its own the first time its call reaches the runtime, with its own failure record, its own region for the strings a routine returns and its own heap partition. The task goes back when its thread ends, as the thread leaves its start routine or calls pthread_exit.

No lock is taken for the call, and calls from several threads run at the same time.

A thread’s first call allocates nothing, a heapless routine’s call among them. The library keeps sixteen task records ready from the moment it loads, and a thread of its own maps sixteen more whenever the threads find the reserve low, so a library serves any number of threads; a thread that finds the reserve empty sleeps until a record is free. The library also makes, as it loads, the thread key that gives a thread’s task back, so the key stays among the first thirty-two, which the GNU C Library keeps inside every thread. A shared object a program loads with dlopen after the program made thirty-two keys of its own is the exception: the C library then allocates once in each new thread’s first call.

A signal handler the library installs may run on a thread that has no task, a thread of the host that never called the library among them. It runs on a record lent for the handler’s duration, and the thread leaves the handler with no task again.

Such a library is exactly as thread-safe as its own code, as a C library is. State it keeps between calls, a unit variable among it, is guarded with synchronized, and in a library built for several threads a synchronized statement always takes its lock, so it keeps the host’s threads apart. This library counts every addition of eight C threads, and the host takes no lock:

library Tally;

var
    count : int64;

{ A bump adds one to the count round after round, under the routine's own mark. }
exp procedure Bump(rounds : int64);
var
    round : int64;
begin
    for round := 1 to rounds do
        synchronized count := count + 1;
end;

{ The count is read after the host joined its threads. }
exp function Count() : int64;
begin
    Count := count;
end;

begin
    count := 0;
end.
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>

extern void Tally__init(void);
extern void Tally__Bump(int64_t rounds);
extern int64_t Tally__Count(void);

static void *worker(void *unused) {
    (void)unused;
    Tally__Bump(100000);
    return NULL;
}

int main(void) {
    pthread_t threads[8];
    Tally__init();
    for (int i = 0; i < 8; i++) pthread_create(&threads[i], NULL, worker, NULL);
    for (int i = 0; i < 8; i++) pthread_join(threads[i], NULL);
    printf("%lld of 800000\n", (long long)Tally__Count());
    return 0;
}

The library is built for UTF-32, so its names carry no __utf8, from a mica.project that states its version. The host, main.c, runs the library’s body once, links it with the standard library and the multicore floor, and prints 800000 of 800000:

mica --compile --link --output-modifier archive --optimize O2 \
  --platform linux,arm64,utf-32 --tasking multicore --project mica.project \
  --source Tally.mica --build build
cc -pthread -o tally main.c build/Tally.a /usr/lib/mica-stdlib-arm64.a /usr/lib/mica-dragon-rt-mc-arm64.a -lm
./tally

A routine that places tasks, a concurrent block among them, runs them on the library’s pool of carriers while the calling thread sleeps, and returns only after every task it placed has ended. A thread that enters the library again through a C function the library called stays the same task, and the inner call’s tasks end before the outer call goes on.

A library built with --tasking single keeps C’s rule for a library that is not thread-safe: one thread runs Mica code at a time, and the host makes its threads take turns with a mutex of its own:

static pthread_mutex_t tally_turn = PTHREAD_MUTEX_INITIALIZER;

pthread_mutex_lock(&tally_turn);
Tally__Bump(100000);
pthread_mutex_unlock(&tally_turn);

Such a library links with the single-carrier floor mica-dragon-rt-<arch>.a, and a library built with --tasking multicore links with the multicore floor; a link that pairs a library with the other floor is refused by name, on either road. A Mica program whose --tasking differs from a library it imports stops before the linker runs:

Failed to link: library 'Tally' was compiled for one carrier: a program built with '--tasking multicore' imports it built the same way
Failed to link: library 'Tally' was compiled for '--tasking multicore': a program that imports it is built with the same '--tasking'

A C program is refused by its own linker, because a library’s objects name a marker that only the floor of their tasking defines. A multicore library linked with the single-carrier floor names the first marker, and a single-carrier library linked with the multicore floor the second:

...: hidden symbol `__mica_floor_tasking_multicore' isn't defined
...: hidden symbol `__mica_floor_tasking_single' isn't defined

The generated header states each exported routine’s thread safety on the multicore floor in the words of the GNU C Library’s manual: MT-Safe where the routine touches no state of the library outside a synchronized statement, and MT-Unsafe with a race word for each state it touches without one, as in MT-Unsafe race:count. A routine that trains reads race:tape, since the process has one gradient tape, so two C threads train one at a time. The header states as well which runtime the library was built for. A multicore library’s header says that each calling thread becomes a task of its own on its first call and that several threads may call the library at once; a single-carrier library’s header says that a program calling the library from several threads makes them take turns, holding one lock of its own around every call.

A failure that leaves an exported routine from inside a synchronized statement gives the statement’s lock back on its way out, so the next thread’s call proceeds.

A thread that ends while it is inside a Mica call, by pthread_exit from a callback or by cancellation, leaves the program undefined, as the GNU C Library’s manual says of a function it marks AC-Unsafe. Let a callback return to its caller, and keep cancellation disabled across a call into Mica with pthread_setcancelstate.

How a failure reaches C

A trap inside a library ends the process the way it ends a Mica program: one Mica runtime failure line on standard error naming the reason, the file, line and column of the library’s source where it happened, and exit status 1. A trap in the library’s initialization stops the process there, before the body of a Mica program that imports the library runs.

A routine declared with fails reports its failure on Mica’s failure channel, which a Mica caller reads and C does not: the generated header leaves such a routine out and says why, and a call through a declaration of the C program’s own returns to C after a failure, with the lock of every synchronized statement it left given back. A library meant for C reports an outcome C must read in its result or through a pointer parameter.

The library’s entries

Every library has one more entry, the target’s name and __init (Learner__init__utf8 for a library Learner built for UTF-8), which the generated header declares. A C program calls it once, before its first call into the library: it runs the body’s statements and gives each variable that starts at a value other than zero its first value. A library whose body is empty and whose variables start at zero returns from it at once, so a program that calls it keeps working when the body later gains a statement. A Mica program that imports the library calls the entry before its own first statement wherever the entry has work to do.

A library that places work on processes declares one more entry in its header, the target’s name and __init_processes. The C program calls it once, early in main and before it starts threads of its own, and the library’s placements then run on the process server it starts; a second call returns at once. A call while a second thread runs, or a placement without the call, ends the program with words that name the entry.

What an archive and a shared object export

A static archive publishes the library’s exp-marked declarations and nothing else: no private routine, no generic template and none of the release machinery the compiler writes.

A shared object carries a copy of the runtime floor, and one process holds one floor whatever it loads: a second Mica shared object, or a program, binds to the floor already there, so one scheduler serves them all. Loading leaves the process as the C program made it, its locale and its signal handlers among it, and dlclose gives back what the object holds.

A library that exports a task, or that places work on processes, is refused as a shared object by name before any tool runs, and links as an archive.