alcove can call C functions from any shared library via libffi.
The core builtin is (ffi-fn LIB FN-NAME RETURN-TYPE ARG-TYPES…), which
returns a callable; you store it like any value and call it like any
alcove function. (ffi-callback RET (ARG-TYPES…) FN) goes the other way —
it hands an alcove function to C as a function pointer (see 05-callbacks.alc).
Security: FFI runs C code with the full privileges of the alcove process. A user script can
(ffi-fn "libc.so.6" "system" "int" "string")and execute arbitrary shell commands. Only run scripts you trust — there is no sandbox.
(= sqrt (ffi-fn "libm.so.6" "sqrt" "double" "double"))
(prn (sqrt 16.0)) ; → 4.000000
(= pow (ffi-fn "libm.so.6" "pow" "double" "double" "double"))
(prn (pow 2.0 10.0)) ; → 1024.000000
(= strlen (ffi-fn "libc.so.6" "strlen" "long" "string"))
(prn (strlen "alcove")) ; → 6
The C ABI types alcove understands. Pass each as a string literal.
| name | C type | alcove type |
|---|---|---|
"void" |
void |
nil (return only) |
"int" |
int32_t |
fixnum |
"long", "int64" |
int64_t |
fixnum |
"double" |
double |
float |
"string", "char*" |
const char* |
string |
"ptr", "void*" |
void* |
fixnum (the address) |
(ffi-fn "lib" "name" "RETURN-TYPE" "ARG-TYPE-1" "ARG-TYPE-2" …) —
omit arg types for zero-arg functions.
Pass the runtime .so (not the dev .so symlink). Common ones on Linux:
| library | typical SONAME |
|---|---|
| C standard | libc.so.6 |
| math | libm.so.6 |
| pthreads | libpthread.so.0 |
| dynamic loader | libdl.so.2 |
| readline | libreadline.so.8 |
For your own libraries, use a path with a slash (./mylib.so) or
install on LD_LIBRARY_PATH / standard search paths.
An empty library name ("") resolves symbols already linked into the
running alcove process — libc, libm, and alcove's own exported functions —
without naming a platform-specific shared object:
(= slen (ffi-fn "" "strlen" "long" "string")) ; libc, portable
(prn (slen "hello")) ; → 5
This is how the test suite exercises FFI with no external .so. Use
(ffi?) to check at runtime whether the build has FFI support.
- 01-libm-math.alc — sqrt, sin, cos, pow, log, exp.
- 02-libc-strings.alc — strlen, strcmp, toupper, tolower, getpid.
- 03-time-syscalls.alc — time(2), usleep(3).
- 04-custom-mylib.c / .alc — build your own .so and call into it (add, fib, strlen, stateful counter, returned C string).
- 05-callbacks.alc — pass an alcove function to C as a function
pointer via
(ffi-callback …)(C calling back into alcove, incl. in a loop, withdoubleand closures). - 06-structs.alc — by-value structs via
(ffi-struct …)/(ffi-pack …)/(ffi-unpack …): struct args, struct returns, and both. - 07-varargs.alc — variadic C functions (printf-style) via
(ffi-vfn …): a fixed-arg prefix plus a variadic tail whose types are inferred from the values.
Run them all (auto-builds libmylib.so):
make run
Build with -shared -fPIC:
cc -shared -fPIC -O2 -o libmylib.so mylib.cThen from alcove:
(= my-add (ffi-fn "./libmylib.so" "mylib_add" "long" "long" "long"))
(prn (my-add 3 4)) ; → 7
State across calls works fine — the library's static variables are preserved between alcove invocations because alcove keeps the dlopen handle for the lifetime of the process.
- Max 8 args per FFI fn (compile-time constant, easy to bump).
- Callbacks are supported via
(ffi-callback RET (ARG-TYPES…) FN)— see 05-callbacks.alc. Pass the result where aptrarg is expected. Callback ret/arg types: void int long double ptr (no string return). - Struct-by-value is supported via
(ffi-struct …)+(ffi-pack …)/(ffi-unpack …)— see 06-structs.alc. Fields: int long double ptr, or a nested struct descriptor (struct-in-struct; unpack yields the inner struct as a blob). Works as ffi-fn arg and return types. (No string fields yet — use ptr.) - Variadic functions are supported via
(ffi-vfn …)— see 07-varargs.alc. The fixed args are declared; the variadic tail is inferred per call (fixnum→long, float→double, char→int, string/nil→pointer). Works on Linux and macOS incl. Apple silicon (libffi 3.3+). - Returned
char*is copied into a fresh alcove string. If your function returns a pointer the caller is supposed tofree(), alcove leaks it (nofreebuiltin yet — wrap it via FFI:(= cfree (ffi-fn "libc.so.6" "free" "void" "ptr"))). - Reserved names can't be shadowed at top level. alcove has a
builtin
time, so(= time (ffi-fn "libc.so.6" "time" …))does bindtimein the global env butlookupchecks the reserved symbol table first and never finds your binding. Use a different name (e.g.ctime). - NULL pointer arg. Pass
0(a fixnum) wherever the C function expectsNULLfor a pointer arg. - No bignum — return values bigger than 2^61 silently overflow alcove's tagged fixnum encoding.
The FFI is libffi-backed (-lffi). Each (ffi-fn …) call:
- dlopens the library (cached per-name in a process-wide table).
- dlsyms the function.
- Builds an
ffi_ciffrom the type signature. - Wraps the fn pointer + cif in an
EXP_FFIexp_t.
When invoked, alcove's evaluate detects EXP_FFI ahead of the
islambda/isinternal paths, evaluates each arg, marshals to the
right C type, calls ffi_call, and marshals the return back.
See alcove.c's alc_ffi_t struct, ffifncmd, and alc_ffi_call
for the implementation (~150 lines).