Splinter Bindings For Other Languages
Splinter is a very "dlopen()-friendly" library; it doesn't require complex
aggregation or construction, it doesn't implement callbacks and it doesn't rely
on type-punning. Because of this, it tends to work without heroics on modern
runtimes that can read Linux DSOs.
If you benefit from Splinter commercially, please consider a small donation to the author to help fund independent research that drives Splinter's development and unrelenting standards for integration. If you could cover a book, or some lab supplies, hosting or just some pizza - I'm always grateful!
A Note On "Far" Pointers
Looking at a region of memory through FFI is one point where that old information superhighway cliche actually bears meaning. When you can travel between borders freely, without a checkpoint, there's no barrier to speed.
If you can drive very fast but have to slow down just a little for your license tag to be captured, or for a RFID fob to be read, then it's a slight choke point.
If you have to completely stop, show your paperwork, declare everything in your possession and what you intend to do with it, and then get back on your way, it's a blocking clog.
Foreign function interfaces each treat "unsafe" pointers in their own way. Those that essentially let data flow to -> from them freely do exceptionally well with splinter. Those that don't still achieve massive speedup, but anywhere the client has to wait for a list of things (keys, embeddings, modules) will be slower than "single-shot" I/O.
Now, the supported languages:
TypeScript (Deno FFI / Bun FFI)
There's an included class in bindings/ts/splinter.ts that you can import and
just start using (Bun or Deno, it uses a factory to provide the correct
bindings).
You will likely see better performance with Bun's FFI for things like listing keys and tandem reads (due to pointer diligence), otherwise they're pretty much equal.
Here's an example:
import { Splinter } from "./splinter.ts";
// open the bus
const store = Splinter.connect("nomic_text");
// write a value
store.set("ts_key", "Hello from TypeScript!");
// read a value
const val = store.getString("ts_key");
console.log(`Value: ${val}`);
// check an epoch (seqlock verification)
const epoch = store.getEpoch("ts_key");
console.log(`Current Epoch: ${epoch}`);
// not absolutely necessary unless you want to
// open another store
store.close();
The class contains the basics to get you started. If you want more of the
library exposed in the class, just upload the class itself, splinter.c and
splinter.h to any decent code LLM and tell it what additional methods you want
to include.
The bare minimum is just sort of how we do things in the main distribution.
Rust (Identical To C Build)
Rust is (aside from C/C++) absolutely the easiest, most straight-forward and efficient way to use Splinter:
use std::ffi::{CString, CStr};
use std::slice;
use std::str;
// the build/ directory has the crates
// they need a maintainer
// wink wink
use splinter_memory::*;
fn main() {
// Rust strings must be converted to C-compatible null-terminated strings
let bus_name = CString::new("nomic_text").expect("CString::new failed");
let key_name = CString::new("rust_test_key").expect("CString::new failed");
// the test manifold payload
let payload = b"Hello from Rust on the bare metal!";
// any interaction with C FFI requires an unsafe block
unsafe {
// 1. Connect to the Splinter bus
if splinter_open(bus_name.as_ptr()) != 0 {
panic!("Failed to connect to Splinter bus. Is it initialized?");
}
println!("Connected to Splinter substrate!");
// publish
// we cast the Rust byte array pointer to a C void pointer
let rc = splinter_set(
key_name.as_ptr(),
payload.as_ptr() as *const std::ffi::c_void,
payload.len()
);
if rc != 0 {
eprintln!("Failed to write to Splinter slot.");
}
// the zero-copy read
let mut out_sz: usize = 0;
let mut out_epoch: u64 = 0;
let raw_ptr = splinter_get_raw_ptr(
key_name.as_ptr(),
&mut out_sz,
&mut out_epoch
);
if !raw_ptr.is_null() {
// cast the C pointer directly into a Rust slice.
// the Rust compiler now treats the L3 cache memory as a standard
// byte slice, tracking its lifetime safely.
// NO memcpy()!
let data_slice = slice::from_raw_parts(raw_ptr as *const u8, out_sz);
// convert the raw bytes back to a UTF-8 string for printing
match str::from_utf8(data_slice) {
Ok(msg) => println!("Epoch {} -> Read: {}", out_epoch, msg),
Err(e) => eprintln!("Invalid UTF-8 sequence: {}", e),
}
} else {
println!("Key not found or empty.");
}
// politely disconnect
splinter_close();
}
}
Rust is not Splinter's primary dev's primary language, but something like that
wrapped in a struct with sync and send could work? A better example would be
appreciated.
Python3x (Native Ctypes)
Splinter abhors unnecessary data duplication. It's actually a really good
GIL-bypass for vector data. Because Splinter uses mmap() to act as a passive
substrate, Python can use its built-in ctypes library to cast a Splinter slot
directly into a NumPy array or a native Python memory view:
import ctypes
import os
# load the shared library
lib_path = os.path.abspath("./libsplinter.so")
splinter = ctypes.CDLL(lib_path)
# define argument and return types for the FFI boundary
splinter.splinter_open.argtypes = [ctypes.c_char_p]
splinter.splinter_open.restype = ctypes.c_int
splinter.splinter_set.argtypes = [ctypes.c_char_p, ctypes.c_void_p, ctypes.c_size_t]
splinter.splinter_set.restype = ctypes.c_int
# splinter_get_raw_ptr(const char *key, size_t *out_sz, uint64_t *out_epoch)
splinter.splinter_get_raw_ptr.argtypes = [
ctypes.c_char_p,
ctypes.POINTER(ctypes.c_size_t),
ctypes.POINTER(ctypes.c_uint64)
]
splinter.splinter_get_raw_ptr.restype = ctypes.c_void_p
class SplinterBus:
def __init__(self, bus_name: str):
if splinter.splinter_open(bus_name.encode('utf-8')) != 0:
raise RuntimeError(f"Failed to connect to Splinter bus: {bus_name}")
def set_string(self, key: str, value: str):
data = value.encode('utf-8')
splinter.splinter_set(key.encode('utf-8'), data, len(data))
def get_embeddings_zero_copy(self, key: str, dimensions=768):
"""
Reads 768 floats directly from the L3 cache without copying bytes!
"""
out_sz = ctypes.c_size_t(0)
out_epoch = ctypes.c_uint64(0)
# get the raw memory pointer from the Splinter manifold
raw_ptr = splinter.splinter_get_raw_ptr(
key.encode('utf-8'),
ctypes.byref(out_sz),
ctypes.byref(out_epoch)
)
if not raw_ptr:
return None
# cast the raw memory address directly into a Python Float Array.
# this is true zero-copy. If the C daemon updates the slot,
# this array updates instantly because it points to the same RAM.
FloatArrayType = ctypes.c_float * dimensions
return ctypes.cast(raw_ptr, ctypes.POINTER(FloatArrayType)).contents
# Usage:
# bus = SplinterBus("nomic_text")
# vec = bus.get_embeddings_zero_copy("npc_dialogue_vector")
# print(f"Vector Head: {vec[0]}, {vec[1]}, {vec[2]}")
In fact, slow Python scrapers can trickle data in while the very tiny
splinference Nomic Text inference engine (included) deposits vectors silently
(no memcpy() in the background either) as it chugs along.
Information Physics (from social data) is often done this way (GDELT, RSS, Etc) and Splinter was written specifically to handle that.
Java (Panama)
Because Splinter utilizes a static memory geometry mapped directly via the OS, you can use Java's new MemorySegment and Arena classes to read and write directly to Splinter:
import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;
public class SplinterWire {
private static final Linker linker = Linker.nativeLinker();
private static final SymbolLookup lib = SymbolLookup.libraryLookup("./libsplinter.so", Arena.global());
// Bindings to the C functions
private static final MethodHandle splinterOpen = linker.downcallHandle(
lib.find("splinter_open").get(),
FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS)
);
private static final MethodHandle splinterGetRawPtr = linker.downcallHandle(
lib.find("splinter_get_raw_ptr").get(),
FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.ADDRESS)
);
public static void main(String[] args) throws Throwable {
// open the bus (Off-heap string allocation)
try (Arena arena = Arena.ofConfined()) {
MemorySegment busName = arena.allocateFrom("nomic_text");
int rc = (int) splinterOpen.invokeExact(busName);
if (rc != 0) {
System.out.println("Failed to open Splinter bus.");
return;
}
System.out.println("Connected to Splinter substrate!");
// fetch the Raw Pointer (Zero-Copy)
MemorySegment key = arena.allocateFrom("nomic_test_key");
MemorySegment outSz = arena.allocate(ValueLayout.JAVA_LONG);
MemorySegment outEpoch = arena.allocate(ValueLayout.JAVA_LONG);
// This returns a MemorySegment pointing directly into /dev/shm
MemorySegment rawDataPtr = (MemorySegment) splinterGetRawPtr.invokeExact(key, outSz, outEpoch);
if (!rawDataPtr.equals(MemorySegment.NULL)) {
long size = outSz.get(ValueLayout.JAVA_LONG, 0);
long epoch = outEpoch.get(ValueLayout.JAVA_LONG, 0);
// read the memory DIRECTLY from the C bus.
// the JVM Garbage Collector never sees this data, so it can never cause a lag spike.
// we reinterpret the zero-length pointer to the actual size of the data.
MemorySegment readableData = rawDataPtr.reinterpret(size);
// if a string:
String val = readableData.getString(0);
System.out.println("Epoch " + epoch + " -> Read: " + val);
// if a vector, then it would be:
// float firstDimension = readableData.get(ValueLayout.JAVA_FLOAT, 0);
}
}
}
}
Because the data lives in Splinter's static slots and not the JVM heap, you can cache gigabytes of Rank-2 tensors without ever triggering a single Garbage Collection pause.
Splinter also allows atomic INC, DEC, AND, and OR bitwise operations on BIGUINT flags directly in shared memory, allowing thousands of Java threads to coordinate state without relying on slow Java-level synchronized locks.
And, you still have the per-slot feature flags. It's made for IPC without tick rate impacts, and we count inference as IPC.
Lua
Just like you'd expect, mostly:
local result = bus.set("test_multi", multi) or -1
print(result)
bus.set("test_integer", 1)
bus.math("test_integer", "inc", 65535)
See splinter_cli_cmd_lua.c for the actual coupling and breakout box to add
more definitions and functionality.
If someone wants to expose the whole store as a table in Lua, I'd love a patch!
Bash / Shell
Just use splinterctl or splinterpctl respectively. There's also
splinter_cli and splinterp_cli for interactive use.
See the CLI page for more information.