Skip to content

ElverseProjects/lina

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

alt text

Lina

Lina is a tiny shared-memory ABI translator for embedded interpreters.

The core idea is deliberately small:

  • C owns one raw shared arena.
  • Lina publishes named descriptors for regions inside that arena.
  • Python, Julia, JS, Lua, Java, etc. build their own native views over the same bytes.
  • signal/wait is the control plane; descriptors + raw memory are the data plane.

Lina should not become a new NumPy, a new Julia Array implementation, or a universal object runtime. It should translate enough information for each language runtime to do the right native thing.

Current prototype

This prototype supports:

  • shared arena allocation/registration;
  • named descriptors;
  • dtypes:
    • u8
    • i64
    • f64
  • ndim <= 8;
  • shape and byte strides;
  • C-order and Fortran-order stride computation;
  • zero-copy Python memoryview;
  • Julia unsafe_wrap demo code;
  • existing signal/wait/try_wait/clear synchronization.

Memory model

A Lina descriptor describes bytes, not language objects.

process address space

┌─────────────────────────────────────────────┐
│ C host                                      │
│                                             │
│ shared arena                                │
│ base = 0x10000000                           │
│                                             │
│  + offset 0x0000: input payload             │◄──── Python memoryview / NumPy view
│  + offset 0x0080: result payload            │◄──── Julia unsafe_wrap Array
│                                             │
│ Python heap: small wrapper object           │
│ Julia heap:  small wrapper object           │
└─────────────────────────────────────────────┘

The payload is not copied. Python and Julia have different wrapper objects, but their data pointer can refer to the same C-owned memory.

Descriptor ABI

The core descriptor is:

#define LINA_MAX_NAME 64
#define LINA_MAX_DIMS 8

typedef struct lina_desc_t {
    char     name[LINA_MAX_NAME];
    uint64_t offset;
    uint64_t nbytes;
    uint64_t capacity_bytes;
    uint32_t dtype;
    uint32_t ndim;
    uint64_t shape[LINA_MAX_DIMS];
    int64_t  strides[LINA_MAX_DIMS];
    uint32_t layout;
    uint32_t generation;
    uint32_t flags;
    uint32_t reserved;
} lina_desc_t;

Important fields:

  • name: stable cross-language name, like "input" or "result".
  • offset: byte offset from lina_shared_base().
  • nbytes: logical visible payload size.
  • capacity_bytes: reserved payload size.
  • dtype: LINA_DTYPE_U8, LINA_DTYPE_I64, LINA_DTYPE_F64.
  • shape: dimensions.
  • strides: byte strides.
  • generation: descriptor version; useful when a region is replaced.

Core C API

void lina_init(void);
void lina_shutdown(void);

void lina_register_shared_memory(void* ptr, size_t bytes);
int  lina_alloc_shared_memory(size_t bytes);
void* lina_shared_base(void);
size_t lina_shared_size(void);

void lina_signal(const char* name);
void lina_wait(const char* name);
int  lina_try_wait(const char* name);
void lina_clear(const char* name);

int lina_create_array(const char* name, uint32_t dtype,
                      uint32_t ndim, const uint64_t* shape,
                      uint32_t layout, lina_desc_t* out);

int lina_open(const char* name, lina_desc_t* out);
int lina_resolve_ptr(const lina_desc_t* desc, void** out_ptr);
int lina_list(lina_desc_t* out, size_t cap, size_t* written);

Python usage

The Python binding intentionally returns a byte-level memoryview. Higher-level code can cast it or wrap it with NumPy.

import lina

lina.init_arena(1024 * 1024)
lina.create_array('input', 'f64', (16,))

x = lina.buffer('input').cast('d')
for i in range(len(x)):
    x[i] = float(i + 1)

print(lina.open_desc('input'))

With NumPy, the same memoryview can become an ndarray without copying:

import numpy as np

mv = lina.buffer('input')
desc = lina.open_desc('input')
x = np.ndarray(shape=desc['shape'], dtype=np.float64, buffer=mv, strides=desc['strides'])

Julia usage

In Julia, the natural bridge is unsafe_wrap:

base = ccall((:lina_shared_base, "liblina_core"), Ptr{UInt8}, ())
desc = lina_open_desc("input")
ptr = base + UInt(desc.offset)
len = Int(desc.nbytes ÷ 8)
A = unsafe_wrap(Array, Ptr{Float64}(ptr), len; own=false)

own=false is essential: Julia does not own or free Lina's memory.

JS, Lua, and Java adapters

All future language adapters follow the same recipe:

  1. lina_open(name, &desc)
  2. lina_resolve_ptr(&desc, &ptr)
  3. create the language-native external view:
    • JS: external ArrayBuffer + TypedArray
    • Lua: userdata holding pointer + descriptor
    • Java: JNI NewDirectByteBuffer or Foreign Memory API

Example Java shape:

ByteBuffer bb = Lina.openBuffer("input");
bb.order(ByteOrder.nativeOrder());
DoubleBuffer db = bb.asDoubleBuffer();

Example JS shape:

const x = lina.typedArray("input"); // Float64Array over external backing store
x[0] = 42.0;

Build

Install Meson and Ninja, then:

meson setup build
meson compile -C build

For the full embedded Python + Julia demo, Julia development headers and libjulia must be visible to the compiler/linker.

Some Julia installations do not provide julia.pc. In that case, pass flags manually, for example:

meson setup build \
  -Djulia=enabled \
  -Djulia_include_dir="/path/to/julia/include/julia" \
  -Djulia_lib_dir="/path/to/julia/lib"
meson compile -C build

If you only want the core library, C smoke test, and Python extension:

meson setup build -Djulia=disabled
meson compile -C build
./build/lina_c_smoke
PYTHONPATH=build python examples/standalone_python.py

Demo flow

The full host demo does this:

  1. C host allocates a Lina arena.
  2. C creates two shared arrays: input and result.
  3. Python opens input, writes Float64 values, and signals data_ready.
  4. Julia waits for data_ready, opens the same memory, doubles the values into result, and signals math_done.
  5. Python waits for math_done and prints result.

No payload copy is required between Python and Julia.

Safety rules

These rules keep the ABI sane:

  1. A published region must not be moved while another runtime may hold a view.
  2. Resize should be implemented as republish/new generation, not raw realloc() under live views.
  3. Language wrappers must not free Lina-owned memory.
  4. signal/wait should be used as ownership handoff or phase barrier.
  5. Multidimensional layout must be explicit via strides, because Python and Julia disagree by default: NumPy commonly uses C-order, Julia uses column-major order.

Design stance

Lina is the translator, not the owner of language semantics.

It answers:

  • where are the bytes?
  • what are they called?
  • how should a runtime interpret them?
  • when is it safe to read/write?

Then each interpreter builds the most natural local object over those bytes.

About

Lina interpreter orchestrator.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors