RAII Wrappers and C API
JustJIT provides a comprehensive C API for use within inline_c code. These functions enable safe Python-C interop, GIL management, and zero-copy buffer access.
GIL Management
When calling Python from C code, you must hold the Global Interpreter Lock (GIL). When doing compute-intensive C work, releasing the GIL allows Python threads to run.
Acquire/Release Cycle:
void* guard = jit_gil_acquire();
// ... call Python APIs safely ...
jit_gil_release(guard);
Release for Parallel Work:
void* save = jit_gil_release_begin();
// ... compute-intensive work without GIL ...
// Python threads can run during this time
jit_gil_release_end(save);
Functions:
-
void *jit_gil_acquire(void)
Acquire the Python GIL. Returns a handle that must be passed to
jit_gil_release.
-
void jit_gil_release(void *guard)
Release a GIL acquired with
jit_gil_acquire.
-
void *jit_gil_release_begin(void)
Release the GIL to allow parallel execution. Returns a handle for
jit_gil_release_end.
-
void jit_gil_release_end(void *save)
Reacquire the GIL after parallel work.
Type Conversion
Convert between Python objects and C types:
Python to C:
long long val = jit_py_to_long(py_obj);
double d = jit_py_to_double(py_obj);
const char* s = jit_py_to_string(py_obj);
C to Python:
PyObject* py_int = jit_long_to_py(42);
PyObject* py_float = jit_double_to_py(3.14);
PyObject* py_str = jit_string_to_py("hello");
// Remember to call jit_decref when done
Functions:
Buffer Access
Access NumPy array data directly without copying:
void* buf = jit_buffer_new(numpy_array);
if (buf) {
double* data = (double*)jit_buffer_data(buf);
long long size = jit_buffer_size(buf);
for (long long i = 0; i < size; i++) {
data[i] *= 2.0; // Modify in place
}
jit_buffer_free(buf); // Release buffer
}
Functions:
-
void *jit_buffer_new(PyObject *arr)
Create buffer handle from NumPy array. Returns NULL if invalid.
-
void jit_buffer_free(void *buf)
Release buffer handle.
-
void *jit_buffer_data(void *buf)
Get raw data pointer from buffer.
-
Py_ssize_t jit_buffer_size(void *buf)
Get buffer size in bytes.
Reference Counting
Manage Python object lifetimes from C:
PyObject* obj = jit_long_to_py(42);
jit_incref(obj); // Add reference
// ... use obj ...
jit_decref(obj); // Remove added reference
jit_decref(obj); // Remove original reference
Functions:
Calling Python Functions
Call Python functions from C:
// Call with 1 argument
PyObject* result = jit_call1(py_func, arg1);
// Call with 2 arguments
PyObject* result = jit_call2(py_func, arg1, arg2);
// Call method
PyObject* result = jit_call_method1(obj, "method_name", arg);
Functions:
-
PyObject *jit_call1(PyObject *func, PyObject *arg)
Call Python callable with 1 argument. Returns new reference or NULL on error.
-
PyObject *jit_call2(PyObject *func, PyObject *arg1, PyObject *arg2)
Call Python callable with 2 arguments.
Collection Operations
Work with Python lists, dicts, and tuples:
Lists:
PyObject* list = jit_list_new(5);
jit_list_set(list, 0, jit_long_to_py(42));
jit_list_append(list, jit_long_to_py(100));
PyObject* item = jit_list_get(list, 0);
Dicts:
PyObject* dict = jit_dict_new();
jit_dict_set(dict, "key", jit_long_to_py(42));
PyObject* val = jit_dict_get(dict, "key");
Tuples:
PyObject* tuple = jit_tuple_new(2);
jit_tuple_set(tuple, 0, arg1);
jit_tuple_set(tuple, 1, arg2);
Type Checking
Check Python object types:
if (jit_is_int(obj)) { /* ... */ }
if (jit_is_float(obj)) { /* ... */ }
if (jit_is_str(obj)) { /* ... */ }
if (jit_is_list(obj)) { /* ... */ }
if (jit_is_dict(obj)) { /* ... */ }
if (jit_is_none(obj)) { /* ... */ }
if (jit_is_callable(obj)) { /* ... */ }
Constants
Access Python singletons:
PyObject* none = jit_none(); // Py_None
PyObject* t = jit_true(); // Py_True
PyObject* f = jit_false(); // Py_False
Error Handling
Check and clear Python errors:
PyObject* result = jit_call1(func, arg);
if (jit_error_occurred()) {
jit_error_print();
jit_error_clear();
}
Complete Example
A complete example using GIL, buffers, and callbacks:
double process_array(void* py_array, void* py_callback) {
// Access NumPy array
void* buf = jit_buffer_new(py_array);
if (!buf) return -1.0;
double* data = (double*)jit_buffer_data(buf);
long long size = jit_buffer_size(buf) / sizeof(double);
// Release GIL for parallel work
void* save = jit_gil_release_begin();
double sum = 0.0;
for (long long i = 0; i < size; i++) {
sum += data[i];
}
// Reacquire GIL before Python callback
jit_gil_release_end(save);
// Call Python callback with result
PyObject* py_sum = jit_double_to_py(sum);
PyObject* result = jit_call1(py_callback, py_sum);
jit_decref(py_sum);
double final = jit_py_to_double(result);
jit_decref(result);
jit_buffer_free(buf);
return final;
}