Skip to content

Foreign callbacks

AIHC supports foreign import ccall "wrapper" and foreign import ccall "dynamic" on its compiled backends. These backends include wasm32-wasip3, Apple ARM64, Linux AMD64, and LLVM. The GRIN interpreter does not support these imports.

A wrapper converts a Haskell function or IO action to a C function pointer. A dynamic import calls a C function pointer. The callback type and the function pointer type must agree.

import Foreign.C.Types (CInt)
import Foreign.Ptr (FunPtr, freeHaskellFunPtr)

type Callback = CInt -> IO CInt

foreign import ccall "wrapper"
  makeCallback :: Callback -> IO (FunPtr Callback)

foreign import ccall safe "dynamic"
  callCallback :: FunPtr Callback -> Callback

Callbacks can capture Haskell values. The runtime protects those values from garbage collection until freeHaskellFunPtr releases the callback. The runtime also protects the suspended Haskell caller during a callback. Callbacks can allocate, use mutable references, and call other callbacks.

Each compiled module has 64 callback slots for each wrapper import that it uses. Each call to a wrapper takes one slot. A full pool causes a runtime error. freeHaskellFunPtr releases the slot for reuse.

Release each callback after C no longer needs it. Do not call a released function pointer.

Types

Callbacks support the scalar C ABI types that ordinary foreign imports support. These types include integers, floating point values, and pointers. The compiler converts supported boxed types and newtypes to their C representations. A callback can return a unit value through a C void result. An IO callback can have no C arguments. Callbacks cannot use byte arrays as argument or result values.

Runtime limits

A C call that enters Haskell must use safe or interruptible. A foreign import without a safety mark uses safe. An unsafe C call that enters Haskell causes a runtime error.

A callback must complete synchronously on the runtime thread. A callback that requires a scheduler switch causes a runtime error. This limit includes an empty MVar, yield, and host I/O that must wait. An uncaught callback exception causes a runtime error. External C threads cannot call these callbacks.

For a safe C call, use pinned byte arrays for any byte array arguments. Keep all other C memory valid for the complete call.

WASM

The compiler generates fixed callback functions and stores their function-table indices in FunPtr values. Each callback function selects its own protected Haskell closure. This method needs no JavaScript and no runtime code generation.

The C library must be part of the same linked WASM module. A function-table index is not a host function pointer or a component interface. Host callbacks need a separate host interface.

Callback support alone does not make terminfo portable to WASI. That package also needs its C library and terminal database. Its output callback can require I/O suspension, which this implementation does not support.