Files
zeekayandHanzo Dev 3e5f509c69 One plugin runtime, four languages, one interface
The contract was written down twice — zip declares Module and Loader, and the
loader beside base's runners declared its own copy. Structural typing was the
only thing keeping them equal, which is a property nobody can see and a compiler
will not check.

This implements zip's. The loader, the manifest and the runners live here so
cloud can stop carrying a second goja runtime of its own — a shim, a bundler and
a third registry that answered the same question this one does.

native, goja, wazero and pyvm. wazero is the reason this is not a list of four
languages: anything compiled to WebAssembly ahead of time arrives without a new
runner, and Rust walks through that door rather than getting one built for it.
pyvm is real CPython through cgo with PEP 684 sub-interpreters, behind a build
tag, because embedding the language beats reimplementing a subset of it.

Its one dependency on base was a single call for an unguessable string, which
crypto/rand says directly.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-16 12:33:40 -07:00

51 lines
2.0 KiB
C

// Package pyvm — C bridge declarations shared across pyvm cgo files.
// Implementation lives in pyvm_bridge.c so multiple .go files can call
// the same helpers without cgo dropping duplicate-symbol errors.
#ifndef PYVM_BRIDGE_H
#define PYVM_BRIDGE_H
#include <Python.h>
// pyvm_init initializes CPython (idempotent). Returns the saved main
// thread state, leaves the GIL released. Sets the global GIL-disabled
// flag retrievable via pyvm_gil_disabled.
PyThreadState* pyvm_init(void);
// pyvm_gil_disabled returns 1 if CPython was built with --disable-gil
// (free-threading PEP 703), 0 otherwise.
int pyvm_gil_disabled(void);
// pyvm_new_sub creates an OWN_GIL sub-interpreter (PEP 684). On
// success returns its thread state and leaves the caller thread
// without holding any GIL. On failure returns NULL and writes a heap-
// allocated error string to *err_out (caller frees).
PyThreadState* pyvm_new_sub(char **err_out);
// pyvm_end_sub destroys a sub-interpreter. Caller must NOT hold GIL.
// Safe to pass NULL.
void pyvm_end_sub(PyThreadState *ts);
// pyvm_enter / pyvm_leave swap the calling OS thread to/from the
// given sub-interpreter. Must be paired and called from the same OS
// thread.
void pyvm_enter(PyThreadState *ts);
void pyvm_leave(void);
// pyvm_load_source runs source as __main__ of the current
// sub-interpreter. Caller must hold the GIL. Returns 0 on success;
// on failure writes a heap-allocated error string to *err_out.
int pyvm_load_source(const char *src, char **err_out);
// pyvm_invoke calls __main__.fn(json.loads(payload_json)) and returns
// json.dumps(result) as a heap-allocated C string (caller frees).
// Caller must hold the GIL of the target sub-interpreter.
//
// On error returns NULL and writes the error to *err_out. If the
// failure is "function not found / not callable", *was_unknown is
// set to 1 — the Go layer maps that to ErrUnknownFn.
char* pyvm_invoke(const char *fn, const char *payload_json,
int *was_unknown, char **err_out);
#endif // PYVM_BRIDGE_H