This chapter describes how a Mica program calls C and how a C program calls Mica: the imports from the C library, POSIX, Linux and the contract of a library, the types that cross and how C spells them, who releases a cell that crosses, the exports of a library with the contract and the header its build writes, the callbacks a C library calls at the slots its contract names, and what a C program sees of a Mica library. Each construct names its rule in the grammar, says what it means, and quotes what the compiler answers when a source breaks the rule. The C boundary states every detail of the calling convention, and library contracts the file that describes a library; Mica calls C and C calls Mica teach the same ground from the start.
Every example is a complete program the website’s gate compiles with the compiler as it is written here, and the output shown beneath it is the output the program prints. The library example is built as a library, which the text beneath it says how, and the header shown is the one its build wrote.
Importing from C
A C function reaches a program by imp, as every name does
(import-group), from
one of two kinds of surface. The C library, POSIX and Linux are the namespaces
cstd, posix and linux, whose surfaces travel inside the compiler, and a C
library of one’s own is described by its contract, a mica.external beside the
sources or named by the project file
(chapter 2).
Either surface states, for each function, the name a program calls it by, the
C symbol behind it, and the type and passing mode of every parameter and of its
result: Labs of cstd is C’s labs, which takes and answers an int64.
Every call is checked against that statement as a call of a Mica routine is,
and what the compiler emits for it is the call the C ABI defines, of the
symbol the surface names, with the arguments in the registers and stack slots
a C caller uses. A string that a C function takes as text reaches it as text C
reads, a zero code unit behind its last character, and in a program built for
UTF-32 the call’s text is first written as UTF-8 into a scratch block the
runtime releases after the call. A value that C answers into an enumeration, a
subrange, a bool or a unicode is checked where it enters, and a value
outside its type stops the program with ordinal_out_of_domain.
A function whose surface states how it reports an error fails on the failure
channel instead of answering a minus one. The POSIX functions fail with a code
of OsError, the domain the C library’s error numbers arrive in, so a call of
Close takes a consumption clause as every call of a routine that fails does
(chapter 9).
| Written | Code | The compiler’s words |
|---|---|---|
n := Labs("x"); | 5180 | incompatible parameter and argument data types at position 1 in call to Labs: expected int64, got string |
imp Nope : cstd;, a name the surface lacks | 5021 | no declarations found in namespace 'cstd' for identifier: Nope — a library exports only its 'exp'-marked declarations, so a name that exists in its source may still be private |
Close(99); without a clause | 5304 | a call to the fallible function 'Close' must consume its failure channel: propagate with 'on fail leave', default with 'on fail use <expression>', handle with 'on fail e do <statement>', or discard a procedure's failure with 'on fail continue' — an unconsumed failure cannot exist |
{
Calls - three functions of the C library and POSIX, called by the names their surfaces give them.
'Labs' and 'Atoi' are C's 'labs' and 'atoi', each call the plain C call with its arguments checked against the
signature the surface declares; the string reaches 'atoi' as the text C reads. 'Close' is POSIX's 'close', whose
failure arrives on the failure channel as a code of 'OsError' instead of a minus one and an error number.
}
program Calls;
imp
WriteLn, Ord : std;
Labs, Atoi : cstd;
Close, OsError : posix;
const
Negative = -42;
Digits = "1234";
MissingDescriptor = 99;
MagnitudeLine = "Labs(-42) answers %lld";
NumberLine = "Atoi of the digits 1234 answers %d";
CloseLine = "Close(99) fails with code %lld of OsError";
var
cause : OsError;
begin
WriteLn(MagnitudeLine, Labs(Negative));
WriteLn(NumberLine, Atoi(Digits));
Close(MissingDescriptor) on fail cause do
WriteLn(CloseLine, Ord(cause));
end.It prints:
Labs(-42) answers 42
Atoi of the digits 1234 answers 1234
Close(99) fails with code 7 of OsErrorThe types that cross
A value crosses in its C spelling, and a record crosses as the struct of the same fields in the same order:
| Mica | C |
|---|---|
int8 to int64, uint8 to uint64 | int8_t to int64_t, uint8_t to uint64_t |
float32, float64 | float, double |
bool | bool |
unicode | char32_t |
| an enumeration | a type over its base integer, its literals as constants |
| a subrange | its base integer |
| a record; a packed record | a struct of the same fields in the same order; a __attribute__((packed)) struct |
| a fixed array | an array type, which a routine takes and answers through a pointer |
pointer T; a const parameter of that type | T *; const T * |
string | the two-word descriptor MicaStringView, the address of the text and its length in characters |
A parameter receives its argument in one of the three modes of
chapter 7: a
copy, an address the callee writes through, written address at the call, or
a const pointer the callee only reads.
What crosses, and how states
where the C ABI puts each of them, and
strings and the encodings
the descriptor under each encoding.
Who releases a cell that crosses
A cell crosses the boundary with its owner, and the owner releases it. A cell
a C function answers is the C library’s, which releases it with a routine of
its own, so a dispose of it is refused; a cell a Mica library hands a C
program is the library’s, which exports the routine that releases it; and a
pointer a contract states as borrowed_pointer belongs to the other side and
asks Mica for no release.
Who owns a cell that crosses
states each rule.
| Written | Code | The compiler’s words |
|---|---|---|
dispose Malloc(64 as uint64); | 5191 | dispose statement cannot release a foreign pointer of data type 'pointer uint8': a pointer allocated by the C side is released by C, never disposed in Mica |
Exports and a library’s contract
exp before a declaration at a library’s root exports it: a constant, a type,
a routine
(routine-declaration,
constant-entry,
type-declaration).
An exported routine links under the library’s name, two underscores and its
own name, with __utf8 behind it in a library built for UTF-8:
exp function Clamped of the library Gauges is Gauges__Clamped. A program
has no importers, so its body’s declarations carry no exp; an object unit of
a program’s build exports its routines the same way, and a C library the
program calls may call them back by those names while the program runs.
A library’s build writes what other builds read: --emit-contract writes its
contract, mica.external, beside the archive, so a Mica unit imports the
library by its exports, and --emit-header writes its C header,
<Library>.h, which declares every export that has a C form under its linkage
name (the command line). An export without a C form is left
out of the header, and the header’s opening comment names it with its reason:
an error domain and a routine that fails, whose failures travel on a channel C
does not read (chapter 9),
and a fixed array handed over by value, which C passes only by its address.
Every library has one more entry, its name and __init, which the header
declares too: a C program calls it once, before its first call into the
library, and it runs the library’s body.
| Written | Code | The compiler’s words |
|---|---|---|
exp function Twice(x : int64) : int64; in a program | 5397 | the 'exp' mark exports a library's root declaration, so 'Twice' cannot carry it here: a program has no importers, and a nested declaration is invisible outside its enclosing body |
{
Gauges - a library whose exports a C program calls through the header the compiler writes beside it.
'Clamped' and 'Fill' have C forms, so the header declares both under their linkage names, with the record 'Reading'
and the constant 'Limit'. 'Checked' fails on Mica's failure channel and 'Total' takes a fixed array by value, which
C cannot express, so the header leaves them out and its opening comment says why.
}
library Gauges;
const
exp Limit : int64 = 100;
type
{ The way a checked reading fails. }
exp GaugeError = error (Disconnected);
{ One reading of a gauge, and whether it holds a level. }
exp Reading = record
level : int64;
valid : bool;
end;
{ Four levels, which a routine receives whole. }
exp Quad = array[0..3] of int64;
{ A level held to the gauge's limit. }
exp function Clamped(level : int64) : int64;
begin
Clamped := level;
if level > Limit then
Clamped := Limit;
end;
{ The caller's reading, filled with a level held to the limit. }
exp procedure Fill(target : pointer Reading, level : int64);
begin
target.level := Clamped(level);
target.valid := True;
end;
{ A level that fails where it lies below zero. }
exp function Checked(level : int64) : int64 fails GaugeError;
begin
if level < 0 then
fail Disconnected;
Checked := level;
end;
{ The sum of four levels. }
exp function Total(values : Quad) : int64;
begin
Total := values[0] + values[1] + values[2] + values[3];
end;
begin
end.Built as a library, with a project file naming its version and its source and
mica --compile --link --output-modifier archive --project mica.project --build build --emit-header --emit-contract, it writes
Gauges.a, mica.external and Gauges.h. The header’s opening comment ends
with what it leaves out:
- GaugeError: an error domain, whose codes travel on Mica's failure channel
- Checked: it can fail, and its failure travels on Mica's failure channel, which C does not read
- Total: its parameter 'values' is the fixed array 'Quad' by value, and C passes an array only by its addressand its declarations, their comments left out here, read:
typedef struct Reading Reading;
struct Reading
{
int64_t level;
bool valid;
};
typedef int64_t Quad[4];
#define Gauges__Limit ((int64_t)100)
int64_t Gauges__Clamped(int64_t level);
void Gauges__Fill(Reading *target, int64_t level);
void Gauges__init(void);Callbacks
A C library that takes a function pointer states that parameter in its
contract as a callback slot: the class of the slot, the C shape of the
function it calls, and the parameter that carries the caller’s context to it
(callback slots). A callback is the
routine that stands at such a slot
(callback-heading):
it is declared with the slot’s C shape, handed to the imported function by its
name, and called by the C library, never by Mica. At a during_call slot the
library calls it only while the imported call runs, and its answer steers the C
side as a C callback’s would. Its whole environment is the context record its
context parameter points at and the unit’s own variables.
Callbacks teaches one that counts the rows a database
answers.
| Written | Code | The compiler’s words |
|---|---|---|
n := K(1);, K a callback | 5505 | the callback 'K' cannot be called: only the foreign library behind a contract's callback slot fires a callback — pass 'K' to an imported function whose contract declares such a slot |
a callback nested in a routine, reading the routine’s variable local | 5513 | the callback 'K' reaches 'local' in an enclosing routine's frame: a callback's whole environment is its explicit context parameter, because the foreign caller carries no static link — move the state into the record the context parameter points at |
exp callback K(x : int64) : int32; in a library | 5511 | a callback cannot be exported: 'K' is named at contract slots inside its own unit, and no importer could bind it at a foreign library's slot for you — declare the callback in the unit that passes it |
a callback that states fails E | 5512 | a callback cannot declare 'fails': the foreign library that fires 'K' runs outside the failure channel and could neither read nor consume it — report failure through the callback's C-shaped return value |
A slot of the class registered keeps the callback and calls it after the
imported call has returned, possibly from threads of the C library’s own, as
stored callbacks
describes.
Nothing between C and a routine
Mica puts no wrapper and no glue between C and a routine. An imported function is called by the C call of its own symbol, and an exported routine is the routine itself, under its linkage name, keeping the C convention on its side: 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. A callback is an ordinary C function pointer at the slot. What crosses is described once, in the contract or the header, and checked at every call.
What a C program sees
A C program that calls a Mica library includes the header the library’s build
wrote, calls the library’s __init entry once, and links the archive with the
runtime floor the build names in its last note, the floor of the memory class,
the tasking flavor and the tier the archive was built for, as
C calls Mica walks through. A trap inside the library
ends the process as it ends a Mica program, with its report on standard error
and exit status 1
(chapter 9), and an outcome the C program
must read travels in a routine’s result or through a pointer parameter, since
a routine that fails is not in the header.
On arm64, a record of bfloat16 values crosses between Mica and C that GCC
compiled in other registers than Mica uses, which is a
known limitation
with the two roads that work.