Binding C++ to Clef with Farscape
Binding the Clef language to C++ libraries has historically meant one of three compromises: accepting the limitations of C-style APIs, writing error-prone binding code by hand, or relying on runtime marshaling that imposes performance penalties. Farscape generates these bindings against the FFI boundary semantics the specification defines, where null handling and pointer validity are isolated to the interface with external code. Farscape’s design targets Plugify’s C++ ABI intelligence, extending the broader binding vision The Farscape Bridge introduces. Farscape generates type-safe Clef bindings automatically, and those bindings compile away to zero-cost abstractions through LLVM’s Link-Time Optimization.
Farscape will evolve from its current C-focused binding generation to comprehensive C++ support by building on Plugify’s accumulated knowledge of C++ ABIs. The result will be a tool that generates safe, idiomatic Clef bindings for any C++ library, with those bindings compiling through the Fidelity framework to native code that’s as efficient as hand-written C++.
Architectural Foundation
Plugify As Semantic Interpreter
Plugify encodes how C++ ABIs vary across platforms, knowledge built up over years of development. That model supplies Farscape’s C++ binding generation with the platform-specific facts it depends on:
graph LR
subgraph "Plugify ABI Intelligence"
VTABLE[Virtual Table Layout Analysis]
MANGLE[Name Mangling Resolution]
CALL[Calling Convention Detection]
EXCEPT[Exception Model Understanding]
RTTI[RTTI Processing]
end
subgraph "Farscape Integration"
PARSER[Clang Two-Pass Parser]
ANALYZER[ABI Analyzer Module]
GENERATOR[Clef Code Generator]
end
subgraph "Generated Output"
BINDINGS[Type-Safe Clef Bindings]
METADATA[LTO Optimization Hints]
LAYOUT[Memory Layout Descriptors]
end
VTABLE --> ANALYZER
MANGLE --> ANALYZER
CALL --> ANALYZER
EXCEPT --> ANALYZER
RTTI --> ANALYZER
PARSER --> ANALYZER
ANALYZER --> GENERATOR
GENERATOR --> BINDINGS
GENERATOR --> METADATA
GENERATOR --> LAYOUT
The integration architecture builds on Farscape’s existing clang two-pass parsing (JSON AST + macro extraction) with XParsec post-processing, augmenting the header parse with the runtime-proven ABI knowledge from Plugify.
Core Architectural Components
Four components transform C++ headers into optimizable Clef bindings:
ABI Analysis Engine: A new component that ingests clang’s parsed AST and enriches it with Plugify’s ABI knowledge. This engine models platform-specific variations, compiler quirks, and the differences between C++ standards that affect binary interfaces.
Type Mapping System: An extended type mapper that goes beyond primitive conversions to handle templates, inheritance hierarchies, and RAII patterns. It translates each construct into an idiomatic Clef representation.
Code Generation Pipeline: A generator that produces complete Clef modules built around [<FidelityExtern>] attributed binding declarations. The generated modules carry lifetime management and error handling, along with optimization metadata for LLVM LTO.
Metadata Preservation Layer: Mechanisms to preserve ABI information through the compilation pipeline, enabling LLVM to make informed optimization decisions during link-time optimization.
Virtual Method Handling
C++ Virtual Table Layouts
C++ virtual methods are among the harder interop surfaces because virtual table layouts differ across compilers and platforms. Plugify’s infrastructure records those layouts in detail, and Farscape will draw on that record to generate Clef bindings that call C++ virtual methods correctly.
The virtual method binding strategy follows a three-tier approach:
// Tier 1: Raw virtual table access (generated from Plugify's layout analysis)
[<Struct>]
type VTablePtr =
val mutable ptr: nativeint
member inline this.GetMethod(index: int) =
NativePtr.read (NativePtr.ofNativeInt (this.ptr + nativeint(index * sizeof<nativeint>)))
// Tier 2: Type-safe method wrappers (generated from C++ method signatures)
type IRenderer =
abstract member Render: vertices: nativeptr<float32> * count: int -> unit
abstract member Clear: color: uint32 -> unit
// Tier 3: Implementation bridge (connects Clef to C++ vtable)
type RendererWrapper(handle: nativeint) =
let vtable = NativePtr.read (NativePtr.ofNativeInt<VTablePtr> handle)
interface IRenderer with
member _.Render(vertices, count) =
let methodPtr = vtable.GetMethod(0) // Render is first virtual method
NativeInterop.callVirtualMethod methodPtr handle vertices count
member _.Clear(color) =
let methodPtr = vtable.GetMethod(1) // Clear is second virtual method
NativeInterop.callVirtualMethod methodPtr handle colorThis tiered approach provides type safety at the Clef level while maintaining binary compatibility with the C++ virtual table layout. During LLVM LTO, these indirections can often be optimized away entirely when the concrete type is known at compile time.
Platform-Specific Virtual Table Variations
Different platforms and compilers implement virtual tables differently. Farscape will incorporate Plugify’s platform detection to generate appropriate bindings:
- Windows MSVC: Virtual tables with specific __thiscall conventions
- Linux GCC/Clang: Itanium C++ ABI with different virtual table layouts
- Embedded Systems: Simplified virtual tables for space-constrained environments
The generator selects platform-specific code paths at build time based on the target triple.
Template Instantiation Strategy
Mapping C++ Templates to Clef Generics
Because C++ templates are instantiated at compile time with potentially infinite variations, Farscape will instantiate selectively, informed by actual usage patterns:
// C++ template: template<typename T> class Vector { ... }
// Farscape generates common instantiations
module Vector =
// Pre-generated for common types
type Float32Vector =
struct
val mutable data: nativeptr<float32>
val mutable size: int
val mutable capacity: int
end
type Int32Vector =
struct
val mutable data: nativeptr<int32>
val mutable size: int
val mutable capacity: int
end
// Generic wrapper for arbitrary types (using SRTPs)
type Vector< ^T when ^T: unmanaged>() =
let mutable data = NativePtr.nullPtr< ^T>
let mutable size = 0
let mutable capacity = 0
member _.Add(item: ^T) =
// Implementation using generic native operations
()The strategy balances compile-time efficiency with runtime flexibility, generating specialized versions for commonly used types while providing generic fallbacks for less common instantiations.
Complex Template Patterns
For more complex template patterns that don’t map cleanly to Clef generics, Farscape will generate specialized bindings with clear documentation about the limitations and workarounds:
- SFINAE patterns: Translated to Clef type constraints where possible
- Template metaprogramming: Pre-evaluated and specialized during binding generation
- Variadic templates: Mapped to Clef method overloads or array parameters
Exception Boundary Management
C++ Exception to Clef Result Types
Farscape will generate exception-safe wrappers that translate C++ exceptions into Clef Result types before they cross the language boundary:
// Generated exception-safe wrapper using Plugify's ABI-aware exception shim
let callCppFunction (args: nativeint) : Result<nativeint, CppException> =
let mutable exceptionPtr = nativeint 0
let mutable result = nativeint 0
// Call through exception boundary shim -- Plugify provides the ABI-correct
// try/catch wrapper that translates C++ exceptions to return codes
let success = Platform.callWithExceptionHandling args &&result &&exceptionPtr
if success then
Ok result
else
// Decode exception information from Plugify's exception model
let exInfo = Plugify.decodeException exceptionPtr
Error {
Type = exInfo.TypeName
Message = exInfo.What
}C++ exceptions therefore never propagate uncaught into Clef code, and the Result value preserves the exception’s type name and message.
RAII and Resource Management
C++ RAII patterns map naturally to Fidelity’s scope-based resource management. Farscape generates wrappers where resource lifetime is tied to lexical scope:
// C++ class with RAII: class FileHandle { ... }
// Layer 1: FidelityExtern binding declarations
module Platform =
[<FidelityExtern("mylib", "createFileHandle")>]
let createFileHandle (path: nativeint) : nativeint = Unchecked.defaultof<nativeint>
[<FidelityExtern("mylib", "destroyFileHandle")>]
let destroyFileHandle (handle: nativeint) : unit = Unchecked.defaultof<unit>
[<FidelityExtern("mylib", "readFile")>]
let readFile (handle: nativeint) (buffer: nativeint) (size: nativeint) : int32 =
Unchecked.defaultof<int32>
// Layer 2: Scope-safe wrapper with deterministic cleanup
let withFileHandle (path: NativeStr) (action: nativeint -> Result<'a, string>) : Result<'a, string> =
let handle = Platform.createFileHandle path.Pointer
if handle = nativeint 0 then
Error "Failed to open file"
else
let result = action handle
Platform.destroyFileHandle handle
resultMemory Layout Compatibility
Structure Padding and Alignment
C++ struct layouts vary based on compiler settings and platform ABI. Farscape will use Plugify’s layout analysis to generate Clef structs with correct padding and alignment:
// C++ struct with platform-specific layout
// struct Data { char a; int b; short c; };
// Generated Clef with BAREWire layout descriptor
[<Struct>]
type Data =
val mutable a: byte // offset 0
val mutable _pad0: byte // offset 1 (padding)
val mutable _pad1: byte // offset 2
val mutable _pad2: byte // offset 3
val mutable b: int32 // offset 4
val mutable c: int16 // offset 8
val mutable _pad3: byte // offset 10
val mutable _pad4: byte // offset 11
// Total size: 12 bytes -- matches C layout exactly
The layout analysis considers:
- Natural alignment requirements
- Compiler-specific packing directives
- Platform ABI specifications
- Cache line optimization
Union and Variant Types
C++ unions and std::variant require special handling to maintain type safety in Clef:
// C++ union: union Value { int i; float f; char str[16]; };
// Raw union representation -- 16 bytes at a single memory location
// Access is through typed views over the same buffer
[<Struct>]
type ValueUnion =
val mutable data: FixedBuffer16 // 16-byte aligned buffer
member this.AsInt () : int32 =
NativePtr.read (NativePtr.ofNativeInt<int32> (NativePtr.toNativeInt &&this.data))
member this.AsFloat () : float32 =
NativePtr.read (NativePtr.ofNativeInt<float32> (NativePtr.toNativeInt &&this.data))
// Safe wrapper using discriminated union
type Value =
| Integer of int32
| Float of float32
| String of NativeStr
static member FromNative(u: ValueUnion, discriminator: byte) =
match discriminator with
| 0uy -> Integer (u.AsInt())
| 1uy -> Float (u.AsFloat())
| 2uy -> String (NativeStr.fromFixedBuffer u.data 16)
| _ -> failwith "Invalid discriminator"Optimization Metadata Generation
LLVM LTO Hints
Farscape will generate metadata that guides LLVM’s link-time optimizer to make aggressive cross-language optimizations:
// Generated with LLVM optimization metadata
// The 'inline' keyword ensures Alex marks this for aggressive inlining in MLIR
let inline vectorAdd (a: nativeptr<float32>) (b: nativeptr<float32>)
(result: nativeptr<float32>) (count: int) =
// FidelityExtern call -- LLVM LTO can inline and vectorize across
// the Clef/C++ boundary when both sides are available as LLVM IR
Platform.simd_vector_add (NativePtr.toNativeInt a) (NativePtr.toNativeInt b)
(NativePtr.toNativeInt result) countThese declarations, combined with LLVM LTO, enable the optimizer to:
- Inline across language boundaries when static binding provides both sides as LLVM IR
- Vectorize loops that span Clef and C++
- Eliminate redundant checks and conversions
- Devirtualize method calls when types are known
Profile-Guided Optimization Support
The generated bindings carry metadata through MLIR attributes that LLVM’s profile-guided optimization consumes:
// Rendering hot path -- MLIR attributes guide LLVM PGO
let renderFrame (engine: nativeint) (scene: nativeint) : Result<unit, int> =
let result = Platform.renderScene engine scene
if result = 0l then Ok ()
else Error (int result)When compiled with PGO instrumentation enabled, LLVM automatically identifies hot call sites and applies aggressive optimization to the extern call paths that dominate execution time.
Integration with BAREWire
Zero-Copy Serialization
Farscape’s C++ bindings will integrate with BAREWire for zero-copy operations between Clef and C++:
// C++ struct that matches BAREWire schema -- layout verified at compile time
[<Struct>]
type NetworkPacket =
val mutable header: PacketHeader
val mutable payload: FixedBuffer<byte, 1024>
val mutable checksum: uint32
// Zero-copy: the Clef struct IS the C++ struct in memory
// BAREWire descriptor verifies layout compatibility at generation time
let processPacket (packetPtr: nativeptr<NetworkPacket>) =
let packet = NativePtr.read packetPtr
let checksum = computeChecksum packet.payload
NativePtr.set packetPtr 0 { packet with checksum = checksum }Testing and Validation Strategy
ABI Compatibility Testing
Farscape will generate tests that validate ABI compatibility:
// Generated test module (runs in Farscape's test harness)
module GeneratedTests =
[<Fact>]
let ``Virtual method calls match C++ behavior`` () =
let cppObject = CppTestHarness.createTestObject()
let fsharpResult = TestObjectWrapper.callVirtualMethod cppObject 42
let cppResult = CppTestHarness.callVirtualMethod cppObject 42
Assert.Equal(cppResult, fsharpResult)
CppTestHarness.destroyTestObject cppObject
[<Fact>]
let ``Structure layout matches C++ compiler`` () =
let fsharpSize = sizeof<GeneratedStruct>
let cppSize = CppTestHarness.getStructSize()
Assert.Equal(cppSize, fsharpSize)Cross-Language Debugging Support
The generated bindings emit DWARF debug information through MLIR/LLVM that enables native debuggers (GDB, LLDB) to inspect across language boundaries:
// Wrapper carries handle + debug metadata emitted as DWARF type info
type CppObjectWrapper = {
Handle: nativeint
VTableAddress: nativeint
TypeName: NativeStr
}
// Debug inspection -- native debuggers see these as struct fields
let inspectObject (wrapper: CppObjectWrapper) =
let vtable = NativePtr.read (NativePtr.ofNativeInt<nativeint> wrapper.Handle)
{ wrapper with VTableAddress = vtable }Because Fidelity compiles through MLIR to LLVM IR, the resulting native binaries carry standard DWARF debug information. Native debuggers can step across the Clef/C++ boundary, inspect struct layouts, and follow virtual method dispatch without any framework-specific tooling.
Evolutionary Path
Progressive Enhancement Strategy
Farscape’s C++ support will evolve through progressive enhancement phases:
Foundation Phase: Basic C++ class binding with simple inheritance and virtual methods, providing the core infrastructure for ABI analysis and code generation.
Template Support Phase: Common template patterns and STL container bindings, which cover the majority of real-world C++ library usage.
Advanced Features Phase: Complex inheritance hierarchies, multiple inheritance, and template metaprogramming, extending support to sophisticated C++ libraries.
Optimization Phase: Profile-guided binding generation and cross-language optimization hints to draw the most performance from static linking with LTO.
Extensibility Architecture
The architecture will support plugins for specialized binding scenarios:
// Extensibility point for custom generators
type IBindingGenerator =
abstract member CanGenerate: CppEntity -> bool
abstract member Generate: CppEntity -> GeneratorContext -> FSharpAST
// Plugin for Qt-style signal/slot
type QtSignalSlotGenerator() =
interface IBindingGenerator with
member _.CanGenerate(entity) =
entity.HasAnnotation("Q_SIGNAL") ||
entity.HasAnnotation("Q_SLOT")
member _.Generate(entity, context) =
// Generate Clef event handlers for Qt signals
generateQtBinding entity contextPerformance Validation Framework
Benchmark Generation
Eventually there could be automatic generation of performance benchmarks comparing different binding strategies. These benchmarks would validate choices in using the zero-cost abstraction promise, and ensure those promises are kept across different optimization levels and binding strategies.
Ecosystem Reach
Integrating Plugify’s C++ ABI intelligence gives Farscape a foundation of cross-language knowledge built over years of Plugify’s development. Much work remains before the ABI analysis and code generation reach the scope described here, and Plugify shortens that path for the Fidelity framework. We consider that integration one of the things Clef needs to serve as a systems programming language able to bind to any C++ library.
Each phase, from the current C-focused designs to full C++ support, holds to the same zero-cost abstraction principle. As these phases complete, Clef developers will be able to bind existing C++ libraries without giving up type safety at the boundary.
While we have many tracks of work that focus on new architectures and processor types, we’re also committed to ensuring that the many investments companies have made in existing technologies have the best performance available with the Fidelity framework.