Section 11: Foreign Function Interface (FFI)
Goal: Enable Ori to call C libraries, system APIs, and JavaScript APIs (WASM target)
Criticality: CRITICAL — Without FFI, Ori cannot integrate with the software ecosystem
Proposal: proposals/approved/platform-ffi-proposal.md
Dependencies: Section 6 (Capabilities — uses FFI capability system must be in place)
Sync Points: CPtr and C ABI Types (multi-crate sync required)
Adding CPtr and C ABI types requires updates across these crates:
ori_ir— AddCPtrtype variant, C type aliases (c_int,c_long, etc.)ori_types— Register CPtr type, FFI-safety validation,Option<CPtr>nullability handlingori_eval— CPtr runtime value representation, pointer operations in unsafe blocksori_llvm— CPtr maps to LLVM opaqueptrtype, C calling convention codegenlibrary/std/ffi.ori— CPtr type definition, FfiError type (for Deep FFI)
Design Decisions (Approved)
| Question | Decision | Rationale |
|---|---|---|
| Should FFI require capability? | Yes, FFI capability | Consistent with effect tracking |
| Single or multiple capabilities? | Single FFI | Platform-agnostic user code |
| Support C++ directly? | No | C ABI only; C++ via extern “C” |
| Support callbacks? | Yes (native) | Required for many C APIs |
| Memory management? | Manual in unsafe blocks | C doesn’t know about ARC |
| Async WASM handling? | Implicit JsPromise resolution | Preserves Ori’s “no await” philosophy |
| Unsafe operations? | unsafe { ... } blocks | Explicit marking for unverifiable ops |
11.1 Extern Block Syntax
Spec section: spec/26-ffi.md § Extern Blocks
Native (C ABI)
extern "c" from "m" {
@_sin (x: float) -> float as "sin"
@_sqrt (x: float) -> float as "sqrt"
}
JavaScript (WASM)
extern "js" {
@_sin (x: float) -> float as "Math.sin"
@_now () -> float as "Date.now"
}
extern "js" from "./utils.js" {
@_formatDate (timestamp: int) -> str as "formatDate"
}
Implementation
-
Spec: Add
spec/26-ffi.mdwith extern block syntax- Define extern block grammar
- Define calling conventions (“c”, “js”)
- Define linkage semantics
-
Lexer: Add tokens
-
externkeyword - String literals for ABI (“c”, “js”)
-
-
Parser: Parse extern blocks
-
parse_extern_block()in parser - Add
ExternBlockto AST - Add
ExternItemvariants -
from "lib"library specification -
as "name"name mapping
-
-
Type checker: Validate extern declarations
- Ensure types are FFI-safe
- Check for
uses FFIin callers
-
Codegen: Generate external references
- Emit LLVM
declarefor C functions - Handle calling convention
- Link external symbols
- Emit LLVM
-
LLVM Support: LLVM codegen for extern blocks
-
LLVM Rust Tests:
ori_llvm/tests/ffi_tests.rs— extern blocks codegen -
AOT Tests: No AOT coverage yet
-
Test:
tests/spec/ffi/extern_blocks.ori- Basic extern function declaration
- Multiple functions in one block
- Name mapping with
as - Library specification with
from
11.2 C ABI Types
Spec section: spec/26-ffi.md § C Types
Primitive Mappings
| Ori Type | C Type | Size |
|---|---|---|
c_char | char | 1 byte |
c_short | short | 2 bytes |
c_int | int | 4 bytes |
c_long | long | platform |
c_longlong | long long | 8 bytes |
c_float | float | 4 bytes |
c_double | double | 8 bytes |
c_size | size_t | platform |
CPtr Type
type CPtr // Opaque pointer - cannot be dereferenced in Ori
extern "c" from "sqlite3" {
@sqlite3_open (filename: str, db: CPtr) -> int
@sqlite3_close (db: CPtr) -> int
}
// Nullable pointers
extern "c" from "foo" {
@get_resource (id: int) -> Option<CPtr>
}
Implementation
-
Spec: Add C types section
- Primitive type mappings
-
CPtropaque pointer type -
Option<CPtr>for nullable pointers
-
Types: Add C primitive types
- Add
CPtrto type system - Add C type aliases (
c_int,c_long, etc.) - Size/alignment handling
- Platform-dependent sizes
- Add
-
Type checker: FFI type validation
- Warn on non-FFI-safe types
- Validate CPtr usage
-
LLVM Support: LLVM codegen for C ABI types
-
LLVM Rust Tests:
ori_llvm/tests/ffi_tests.rs— C ABI types codegen -
AOT Tests: No AOT coverage yet
-
Test:
tests/spec/ffi/c_types.ori- All primitive C types
- CPtr operations
- Option
for nullable
11.3 #repr Attribute
Spec section: spec/26-ffi.md § C Structs
Proposal: proposals/approved/repr-extensions-proposal.md
Syntax
| Attribute | Effect |
|---|---|
#repr("c") | C-compatible field layout and alignment |
#repr("packed") | No padding between fields |
#repr("transparent") | Same layout as single field |
#repr("aligned", N) | Minimum N-byte alignment (N must be power of two) |
#repr("c")
type CTimeSpec = { tv_sec: c_long, tv_nsec: c_long }
#repr("packed")
type PacketHeader = { version: byte, flags: byte, length: c_short }
#repr("transparent")
type FileHandle = { fd: c_int }
#repr("aligned", 64)
type CacheAligned = { value: int }
Combining: #repr("c") may combine with #repr("aligned", N). Other combinations are invalid.
Newtypes: Newtypes (type T = U) are implicitly transparent.
Implementation
-
IR: Add
ReprKindenum to struct type definitions-
Default,C,Packed,Transparent,Aligned(u32),CAligned(u32)
-
-
Parser: Parse
#reprattribute variants-
#repr("c")— existing -
#repr("packed")— new -
#repr("transparent")— new -
#repr("aligned", N)— new, validate power of two
-
-
Type checker: Validate #repr usage
- Only valid on struct types (not sum types)
-
transparentrequires exactly one field -
alignedN must be power of two - Reject
packed+alignedcombination
-
Codegen: Generate appropriate LLVM layout
-
#repr("c")— default struct, no packed -
#repr("packed")— LLVM packed struct type -
#repr("transparent")— same type as inner field -
#repr("aligned", N)— align N on allocations
-
-
LLVM Support: LLVM codegen for #repr structs
-
LLVM Rust Tests:
ori_llvm/tests/ffi_tests.rs— #repr struct codegen -
AOT Tests: No AOT coverage yet
-
Test:
tests/spec/ffi/repr.ori-
#repr("c")struct -
#repr("packed")struct -
#repr("transparent")single-field struct -
#repr("aligned", 16)struct -
#repr("c")+#repr("aligned", N)combination - Invalid:
#repron sum type (compile error) - Invalid:
#repr("transparent")with multiple fields (compile error) - Invalid:
#repr("aligned", 7)non-power-of-two (compile error) - Invalid:
#repr("packed")+#repr("aligned")(compile error)
-
11.4 Unsafe Expressions
Spec section: spec/26-ffi.md § Unsafe Expressions
Syntax
@raw_memory_access (ptr: CPtr, offset: int) -> byte uses FFI =
// Direct pointer arithmetic - Ori cannot verify safety
unsafe { ptr_read_byte(ptr: ptr, offset: offset) }
Semantics
Inside unsafe:
- Dereference raw pointers
- Pointer arithmetic
- Access mutable statics
- Transmute types
Implementation
-
Spec: Define unsafe block semantics
- List of unsafe operations
- Scoping rules
- Interaction with FFI capability
-
Parser: Parse unsafe blocks
-
unsafekeyword - Block expression
-
-
Type checker: Track unsafe context
- Set
in_unsafeflag - Allow unsafe operations only in context
- Set
-
Evaluator: Execute unsafe operations
- Pointer dereference
- Raw memory access
-
LLVM Support: LLVM codegen for unsafe blocks
-
LLVM Rust Tests:
ori_llvm/tests/ffi_tests.rs— unsafe blocks codegen -
AOT Tests: No AOT coverage yet
-
Test:
tests/spec/ffi/unsafe_blocks.ori- Basic unsafe block
- Nested unsafe
- Unsafe operations outside block (compile error)
-
Test:
tests/compile-fail/ffi/unsafe_required.ori- Pointer deref outside unsafe
- Unsafe op without unsafe block
11.5 FFI Capability
Spec section: spec/26-ffi.md § FFI Capability
Design
// FFI functions require FFI capability
@call_c_function () -> int uses FFI = some_c_function()
// Callers must have capability
@main () -> void uses FFI = {
let result = call_c_function()
print(msg: `Result: {result}`)
}
// Or provide it explicitly in tests
@test_c_call tests @call_c_function () -> void = {
with FFI = AllowFFI in
assert_eq(actual: call_c_function(), expected: 42)
}
Implementation
-
Spec: FFI capability definition
- As a marker capability (like Async)
- Propagation rules
- Testing patterns
-
Capability system: Add
FFIcapability- Define in prelude
- Track in function signatures
- Propagate to callers
-
Type checker: Enforce capability requirement
- Require
uses FFIfor extern calls - Require
uses FFIfor unsafe blocks
- Require
-
LLVM Support: LLVM codegen for FFI capability
-
LLVM Rust Tests:
ori_llvm/tests/ffi_tests.rs— FFI capability codegen -
AOT Tests: No AOT coverage yet
-
Test:
tests/spec/ffi/ffi_capability.ori- Function requiring FFI
- Providing FFI in tests
- Missing capability error
11.6 Callbacks (Native)
Spec section: spec/26-ffi.md § Callbacks
Syntax
extern "c" from "libc" {
@qsort (
base: [byte],
nmemb: int,
size: int,
compar: (CPtr, CPtr) -> int
) -> void
}
@compare_ints (a: CPtr, b: CPtr) -> int uses FFI = ...
qsort(base: data, nmemb: len, size: 4, compar: compare_ints)
Implementation
-
Spec: Callback semantics
- Function pointer type syntax
- Conversion from Ori functions
- Lifetime considerations
-
Types: Function pointer type
-
(CPtr, CPtr) -> intin type system - ABI specification
-
-
Codegen: Generate callback wrappers
- Trampoline functions
- ABI adaptation
-
LLVM Support: LLVM codegen for callbacks
-
LLVM Rust Tests:
ori_llvm/tests/ffi_tests.rs— callbacks codegen -
AOT Tests: No AOT coverage yet
-
Test:
tests/spec/ffi/callbacks.ori- Simple callback
- qsort example
- Callback with userdata
11.7 Build System Integration
Spec section: spec/26-ffi.md § Linking
ori.toml Configuration
[native]
libraries = ["m", "z", "pthread"]
library_paths = ["/usr/local/lib", "./native/lib"]
[native.linux]
libraries = ["m", "rt"]
[native.macos]
libraries = ["m"]
frameworks = ["Security", "Foundation"]
[native.windows]
libraries = ["msvcrt"]
Implementation
-
Spec: Link specification
- ori.toml native section
- Library kinds (static, dylib, framework)
- Search paths
-
Codegen: Emit link directives
- LLVM link metadata
- Library search
-
Build system: Handle native dependencies
- ori.toml parsing
- pkg-config integration
-
LLVM Support: LLVM codegen for link directives
-
LLVM Rust Tests:
ori_llvm/tests/ffi_tests.rs— linking codegen -
AOT Tests: No AOT coverage yet
-
Test:
tests/spec/ffi/linking.ori- Link to libc
- Link to libm
- Custom library
11.8 compile_error Built-in
CROSS-REFERENCE: Section 13.10 also covers
compile_errorin the context of conditional compilation. Implementation should be done once and shared; avoid duplicate work.
Spec section: spec/annex-c-built-in-functions.md § Compile-Time Functions
Syntax
#target(arch: "wasm32")
compile_error("std.fs is not available for WASM.")
Implementation
-
Spec: Define compile_error semantics
- Compile-time error with custom message
- Works with conditional compilation
-
Parser: Parse compile_error
- Built-in function syntax
- String literal argument
-
Type checker: Trigger compile error
- Evaluate during type checking
- Only if code path is active
-
Test:
tests/compile-fail/compile_error.ori- Basic compile_error
- With conditional compilation
11.9 WASM Target (Section 2)
JS FFI
extern "js" {
@_sin (x: float) -> float as "Math.sin"
@_fetch (url: str) -> JsPromise<JsValue> as "fetch"
}
Implementation
-
Codegen: WASM code generation
- WASM binary output
- Import generation
-
Glue generation: Generate JS glue code
- String marshalling (TextEncoder/TextDecoder)
- Object heap slab
-
Test:
tests/spec/ffi/js_ffi.ori- Basic JS function call
- String marshalling
- Object handles
11.10 JsValue and Async (Section 3-4)
JsValue Type
type JsValue = { _handle: int }
extern "js" {
@_document_query (selector: str) -> JsValue
@_drop_js_value (handle: JsValue) -> void
}
JsPromise with Implicit Resolution
extern "js" {
@_fetch (url: str) -> JsPromise<JsValue> as "fetch"
}
// JsPromise auto-resolved at binding sites
@fetch_text (url: str) -> str uses Async, FFI =
{
let response = _fetch(url: url), // auto-resolved
text
}
Implementation
-
Types: JsValue opaque handle type
- Define in stdlib
- Handle tracking
-
Types: JsPromise
type - Compiler-recognized generic
- Implicit resolution rules
-
Codegen: JSPI/Asyncify integration
- Stack switching for async
- Promise resolution glue
-
Test:
tests/spec/ffi/js_async.ori- JsPromise implicit resolution
- Async function with FFI
11.11 Deep FFI — Higher-Level FFI Abstractions
Proposal: proposals/approved/deep-ffi-proposal.md
Deep FFI layers five opt-in abstractions on top of the base FFI syntax: error protocols, ownership annotations, declarative marshalling, capability-gated testability, and const-generic safety. Each is independently useful and backward-compatible.
11.11.1 Error Protocols + out Parameters (Phase 1)
- Implement:
#error(errno | nonzero | null | negative | success: N | none)block/function attributes- Parser: Parse
#error(...)on extern blocks and extern items - IR: Represent error protocol in extern block IR
- Type checker: Transform return types when error protocol is active
- Codegen: Generate error check +
Resultwrapping code after C calls - Rust Tests:
ori_parse/src/tests/— error protocol parsing - Ori Tests:
tests/spec/ffi/error_protocol.ori
- Parser: Parse
- Implement:
FfiErrortype instd.ffi- Library: Define
FfiErrortype inlibrary/std/ffi.ori
- Library: Define
- Implement:
outparameter modifier (parser → IR → codegen)- Parser: Parse
outmodifier on extern params - IR: Represent
outparams in extern item IR - Type checker: Fold
outparams into return type - Codegen: Allocate stack slot, pass address, extract value
- Rust Tests:
ori_parse/src/tests/—outparam parsing - Ori Tests:
tests/spec/ffi/out_params.ori
- Parser: Parse
- Implement: Errno reading infrastructure
- Runtime:
get_errno()as compiler intrinsic - Codegen: Read errno after C calls when
#error(errno)active - LLVM Support: LLVM codegen for errno reading
- LLVM Rust Tests:
ori_llvm/tests/ffi_tests.rs— errno codegen - AOT Tests: No AOT coverage yet
- Runtime:
11.11.2 Ownership Annotations (Phase 2)
- Implement:
owned/borrowedannotations- Parser: Parse ownership annotations on extern params and return types
- IR: Represent ownership in extern item IR
- Type checker: Validate ownership combinations
- Rust Tests:
ori_parse/src/tests/— ownership annotation parsing - Ori Tests:
tests/spec/ffi/ownership.ori
- Implement:
#free(fn)attribute (block-level and per-function)- Parser: Parse
#free(...)on extern blocks and items - Codegen: Wire up cleanup function
- Parser: Parse
- Implement: Auto-generated Drop impls for
owned CPtrwith#free- Type checker: Generate synthetic Drop impl for owned CPtr types
- Codegen: Emit cleanup call on scope exit
- LLVM Support: LLVM codegen for auto-Drop
- LLVM Rust Tests:
ori_llvm/tests/ffi_tests.rs— ownership codegen - AOT Tests: No AOT coverage yet
- Implement:
strreturn defaults to borrowed (copy, don’t free)- Ori Tests:
tests/spec/ffi/str_return_borrowed.ori
- Ori Tests:
- Implement: Compiler warnings for unannotated CPtr returns
11.11.3 Declarative Marshalling Extensions (Phase 3)
- Implement:
[byte]length elision — adjacent(ptr, len)pair insertion- Type checker: Detect
[byte]params and expand to two C args - Codegen: Insert length argument at call site
- Ori Tests:
tests/spec/ffi/byte_length_elision.ori
- Type checker: Detect
- Implement:
mut [byte]parameter handling — adjacent(ptr, &len)pair - Implement:
int↔c_intautomatic narrowing/widening with bounds checks - Implement:
bool↔c_intconversion- LLVM Support: LLVM codegen for marshalling extensions
- LLVM Rust Tests:
ori_llvm/tests/ffi_tests.rs— marshalling codegen - AOT Tests: No AOT coverage yet
11.11.4 Capability-Gated Testability (Phase 4)
- Implement: Compiler generates internal traits from extern blocks
- Type checker: Auto-generate trait from each extern block’s
fromclause
- Type checker: Auto-generate trait from each extern block’s
- Implement: Parametric
FFI("lib")capability- Type checker: Parse and validate parametric FFI capability
- Backward compat: Unparameterized
uses FFIremains shorthand for all
- Implement:
with FFI("lib") = handler { ... } indispatch routing- Handler signature validation against generated traits
- Stateless
handler { ... }as sugar forhandler(state: ()) { ... } - Fall-through to real C implementation for unmocked functions
- Ori Tests:
tests/spec/ffi/mock_handler.ori
11.11.5 Const-Generic Safety (Phase 5 — Future)
- Design: Where clauses on extern items with const expressions
- Depends on Section 18 (Const Generics) being complete
- Implement: Buffer size validation at compile time
- Implement: Fixed-capacity list
[T, max N]at FFI boundary
Section Completion Checklist
- All items above have checkboxes marked
[ ] - Spec file
spec/26-ffi.mdcomplete - CLAUDE.md updated with FFI syntax
- grammar.ebnf updated with extern blocks
- Can call libc functions (strlen, malloc, free)
- Can call libm functions (sin, cos, sqrt)
- Can create and use SQLite binding
- All tests pass:
./test-all.sh -
uses FFIproperly enforced -
unsafeblocks working
Exit Criteria: Can write a program that opens and queries a SQLite database
Example: SQLite Binding
Target capability demonstration:
extern "c" from "sqlite3" {
@_sqlite3_open (filename: str, ppDb: CPtr) -> int as "sqlite3_open"
@_sqlite3_close (db: CPtr) -> int as "sqlite3_close"
@_sqlite3_exec (
db: CPtr,
sql: str,
callback: (CPtr, int, CPtr, CPtr) -> int,
userdata: CPtr,
errmsg: CPtr
) -> int as "sqlite3_exec"
}
type SqliteDb = { handle: CPtr }
impl SqliteDb {
pub @open (path: str) -> Result<SqliteDb, str> uses FFI =
{
let handle = CPtr.null()
let result = _sqlite3_open(filename: path, ppDb: handle)
if result == 0 then
Ok(SqliteDb { handle: handle })
else
Err("Failed to open database")
}
pub @close (self) -> void uses FFI =
_sqlite3_close(db: self.handle)
}