bend-python 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bend_python/__init__.py +24 -0
- bend_python/__main__.py +26 -0
- bend_python/build.py +368 -0
- bend_python/lib/LAWS.bend +85 -0
- bend_python/lib/PROOF.bend +141 -0
- bend_python/lib/THREAD_LAWS.bend +199 -0
- bend_python/lib/THREAD_PROOF.bend +362 -0
- bend_python/lib/python.bend +218 -0
- bend_python/lib/python.c +817 -0
- bend_python/lib/thread_state.bend +225 -0
- bend_python/library.py +52 -0
- bend_python-0.1.0.dist-info/METADATA +262 -0
- bend_python-0.1.0.dist-info/RECORD +16 -0
- bend_python-0.1.0.dist-info/WHEEL +5 -0
- bend_python-0.1.0.dist-info/licenses/LICENSE +21 -0
- bend_python-0.1.0.dist-info/top_level.txt +1 -0
bend_python/lib/python.c
ADDED
|
@@ -0,0 +1,817 @@
|
|
|
1
|
+
// CPython and private Bend 2.0.28 runtime boundary. Every invocation exclusively
|
|
2
|
+
// owns a runtime instance; the generated runtime selects it through bp_current.
|
|
3
|
+
// The build relocates this block before the pinned runtime's first state use.
|
|
4
|
+
// BENDPY_RUNTIME_CONTEXT_BEGIN
|
|
5
|
+
#include <setjmp.h>
|
|
6
|
+
typedef struct BpRuntime {
|
|
7
|
+
Corpus heap;
|
|
8
|
+
u64 allocator[CUBE_T + 1][3 * ALC_WORDS] __attribute__((aligned(128)));
|
|
9
|
+
u32 keep_words, cube_log, bank, workers;
|
|
10
|
+
bool gpu;
|
|
11
|
+
Stk stack;
|
|
12
|
+
u64 heap_size;
|
|
13
|
+
jmp_buf escape;
|
|
14
|
+
bool poisoned;
|
|
15
|
+
char error[256];
|
|
16
|
+
struct BpRuntime* next;
|
|
17
|
+
} BpRuntime;
|
|
18
|
+
static _Thread_local BpRuntime* bp_current;
|
|
19
|
+
#define CORPUS (bp_current->heap)
|
|
20
|
+
#define ALC (bp_current->allocator)
|
|
21
|
+
#define KEEP_WORDS (bp_current->keep_words)
|
|
22
|
+
#define CUBE_LOG (bp_current->cube_log)
|
|
23
|
+
#define bank_lock (bp_current->bank)
|
|
24
|
+
#define pool_size (bp_current->workers)
|
|
25
|
+
#define io_gpu (bp_current->gpu)
|
|
26
|
+
#define io_stk (bp_current->stack)
|
|
27
|
+
#define corpus_size (bp_current->heap_size)
|
|
28
|
+
// BENDPY_RUNTIME_CONTEXT_END
|
|
29
|
+
// The build includes Python.h before the runtime's standard headers.
|
|
30
|
+
#include <Python.h>
|
|
31
|
+
#include <stdatomic.h>
|
|
32
|
+
#include <sys/random.h>
|
|
33
|
+
#include <time.h>
|
|
34
|
+
|
|
35
|
+
#if BANGS
|
|
36
|
+
#error "GPU entry points are not supported by this embedding."
|
|
37
|
+
#endif
|
|
38
|
+
#ifndef __STDC_IEC_559__
|
|
39
|
+
#error "F32 narrowing relies on IEEE 754 double-to-float conversion."
|
|
40
|
+
#endif
|
|
41
|
+
|
|
42
|
+
// An exported Bend function. Its fields are immutable after export, and its
|
|
43
|
+
// captureless Term holds no heap reference, so any runtime instance may run it.
|
|
44
|
+
typedef struct {
|
|
45
|
+
PyObject_HEAD
|
|
46
|
+
Term function;
|
|
47
|
+
bool release_gil;
|
|
48
|
+
PyObject* name;
|
|
49
|
+
PyObject* module;
|
|
50
|
+
} BpFunction;
|
|
51
|
+
|
|
52
|
+
typedef enum { BP_UNIT, BP_OBJECT, BP_U32, BP_F32, BP_BOOL, BP_STRING, BP_NAT, BP_BYTES } BpValue;
|
|
53
|
+
// A decoded Bend list: object handles, String codepoints, or raw U32 words.
|
|
54
|
+
typedef enum { BP_READ_OBJECTS, BP_READ_TEXT, BP_READ_WORDS } BpRead;
|
|
55
|
+
typedef enum { BP_INIT, BP_START, BP_RESUME, BP_DROP } BpOperation;
|
|
56
|
+
|
|
57
|
+
typedef struct {
|
|
58
|
+
BpRuntime* runtime;
|
|
59
|
+
PyObject** objects;
|
|
60
|
+
size_t count, capacity, nargs;
|
|
61
|
+
PyObject* module;
|
|
62
|
+
Term continuation, argument, function;
|
|
63
|
+
Term fields[5];
|
|
64
|
+
u32 effect;
|
|
65
|
+
u32* buffer;
|
|
66
|
+
size_t length;
|
|
67
|
+
BpValue result_kind;
|
|
68
|
+
u64 result;
|
|
69
|
+
u64 key;
|
|
70
|
+
bool release_gil, pending, done;
|
|
71
|
+
char error[256];
|
|
72
|
+
} BpCall;
|
|
73
|
+
|
|
74
|
+
static pthread_mutex_t bp_pool_mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
75
|
+
static BpRuntime* bp_idle;
|
|
76
|
+
static size_t bp_idle_count;
|
|
77
|
+
static pthread_once_t bp_secret_once = PTHREAD_ONCE_INIT;
|
|
78
|
+
static u64 bp_secret;
|
|
79
|
+
static _Atomic u64 bp_call_counter;
|
|
80
|
+
#define BP_IDLE_LIMIT 8
|
|
81
|
+
#define BP_STACK_BYTES (1ull << 31)
|
|
82
|
+
#define BP_STACK_GUARD 16384
|
|
83
|
+
// Instances whose heap grew past this are unmapped rather than cached, so idle
|
|
84
|
+
// instances cannot pin a large call's resident pages until process exit.
|
|
85
|
+
#define BP_IDLE_HEAP_BYTES (32ull << 20)
|
|
86
|
+
|
|
87
|
+
static void bp_assert_attached(void) {
|
|
88
|
+
// PyThreadState_Get fails fatally if this thread is detached, including on
|
|
89
|
+
// free-threaded CPython. Traditional builds must also own the GIL.
|
|
90
|
+
(void)PyThreadState_Get();
|
|
91
|
+
if (bp_current != NULL) Py_FatalError("Python API reached inside Bend evaluation");
|
|
92
|
+
#ifndef Py_GIL_DISABLED
|
|
93
|
+
if (!PyGILState_Check()) Py_FatalError("Bend bridge entered Python without the GIL");
|
|
94
|
+
#endif
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
static void bendpy_panic(const char* message) {
|
|
98
|
+
bp_current->poisoned = true;
|
|
99
|
+
snprintf(bp_current->error, sizeof(bp_current->error), "%s", message);
|
|
100
|
+
longjmp(bp_current->escape, 1);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
static BpRuntime* bp_runtime_acquire(void) {
|
|
104
|
+
bp_assert_attached();
|
|
105
|
+
PyThreadState* thread = PyEval_SaveThread();
|
|
106
|
+
pthread_mutex_lock(&bp_pool_mutex);
|
|
107
|
+
BpRuntime* runtime = bp_idle;
|
|
108
|
+
if (runtime) {
|
|
109
|
+
bp_idle = runtime->next;
|
|
110
|
+
--bp_idle_count;
|
|
111
|
+
runtime->next = NULL;
|
|
112
|
+
}
|
|
113
|
+
pthread_mutex_unlock(&bp_pool_mutex);
|
|
114
|
+
if (!runtime) {
|
|
115
|
+
// The allocator cache has 128-byte alignment in the pinned runtime.
|
|
116
|
+
void* memory = NULL;
|
|
117
|
+
if (posix_memalign(&memory, _Alignof(BpRuntime), sizeof(BpRuntime)) == 0) {
|
|
118
|
+
runtime = memory;
|
|
119
|
+
memset(runtime, 0, sizeof(*runtime));
|
|
120
|
+
runtime->cube_log = 7;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
PyEval_RestoreThread(thread);
|
|
124
|
+
if (!runtime) PyErr_NoMemory();
|
|
125
|
+
return runtime;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
static bool bp_runtime_reusable(BpRuntime* runtime) {
|
|
129
|
+
if (runtime->poisoned) return false;
|
|
130
|
+
if (runtime->heap == NULL) return true;
|
|
131
|
+
u64 pages = a32_load(a32_at(runtime->heap, H_BUMP));
|
|
132
|
+
return (HEAP_OFF + (pages << PAGE_BITS)) * sizeof(u64) <= BP_IDLE_HEAP_BYTES;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
static void bp_runtime_release(BpRuntime* runtime) {
|
|
136
|
+
bp_assert_attached();
|
|
137
|
+
PyThreadState* thread = PyEval_SaveThread();
|
|
138
|
+
bool retained = false, reusable = bp_runtime_reusable(runtime);
|
|
139
|
+
pthread_mutex_lock(&bp_pool_mutex);
|
|
140
|
+
if (reusable && bp_idle_count < BP_IDLE_LIMIT) {
|
|
141
|
+
runtime->next = bp_idle;
|
|
142
|
+
bp_idle = runtime;
|
|
143
|
+
++bp_idle_count;
|
|
144
|
+
retained = true;
|
|
145
|
+
}
|
|
146
|
+
pthread_mutex_unlock(&bp_pool_mutex);
|
|
147
|
+
if (!retained) {
|
|
148
|
+
if (runtime->heap) munmap(runtime->heap, runtime->heap_size);
|
|
149
|
+
if (runtime->stack) munmap(runtime->stack, BP_STACK_BYTES + BP_STACK_GUARD);
|
|
150
|
+
free(runtime);
|
|
151
|
+
}
|
|
152
|
+
PyEval_RestoreThread(thread);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
static void* bp_alloc(size_t size) {
|
|
156
|
+
void* p = malloc(size ? size : 1);
|
|
157
|
+
if (p == NULL) bendpy_panic("unable to allocate Bend bridge buffer");
|
|
158
|
+
return p;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
static Term bp_apply(Env e, Term function, Term argument) {
|
|
162
|
+
Loc at = task_node(e, FID_CLO_APPLY, TERM_HOLE, 0, 0);
|
|
163
|
+
e.mem[at] = function;
|
|
164
|
+
e.mem[at + 1] = argument;
|
|
165
|
+
return corpus_eval(e.mem, term_tsk(FID_CLO_APPLY, at));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Object wrappers may be packed or boxed on the heap. Anything else means the
|
|
169
|
+
// private runtime representation changed, so fail loudly rather than guess.
|
|
170
|
+
static u32 bp_unbox(Env e, Term object) {
|
|
171
|
+
if (term_aux(object) != CID(PyObject)) bendpy_panic("expected a Python object handle");
|
|
172
|
+
if (term_tag(object) == TAG_PAK) return (u32)term_loc(object);
|
|
173
|
+
if (term_tag(object) != TAG_CTR) bendpy_panic("unexpected Python object representation");
|
|
174
|
+
Term field;
|
|
175
|
+
spare_free(e, 0, ctr_take(e, object, 1, &field));
|
|
176
|
+
return (u32)field;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
static void bp_read_list(Env e, Term list, BpCall* call, BpRead mode) {
|
|
180
|
+
bool string = mode == BP_READ_TEXT;
|
|
181
|
+
size_t length = 0, capacity = 16;
|
|
182
|
+
u32* data = bp_alloc(capacity * sizeof(u32));
|
|
183
|
+
// Store before traversal so a runtime failure still frees the host buffer.
|
|
184
|
+
call->buffer = data;
|
|
185
|
+
u32 cons = string ? CID(SCon) : CID(Con), nil = string ? CID(SNil) : CID(Nil);
|
|
186
|
+
while (term_tag(list) == TAG_CTR && term_aux(list) == cons) {
|
|
187
|
+
Term fields[2];
|
|
188
|
+
spare_free(e, 1, ctr_take(e, list, 2, fields));
|
|
189
|
+
if (length == capacity) {
|
|
190
|
+
capacity *= 2;
|
|
191
|
+
u32* grown = realloc(data, capacity * sizeof(u32));
|
|
192
|
+
if (grown == NULL) bendpy_panic("unable to grow Bend bridge buffer");
|
|
193
|
+
data = grown;
|
|
194
|
+
call->buffer = data;
|
|
195
|
+
}
|
|
196
|
+
if (mode == BP_READ_WORDS && (u64)fields[0] > UINT32_MAX)
|
|
197
|
+
bendpy_panic("unexpected Bend U32 representation");
|
|
198
|
+
data[length++] = mode == BP_READ_OBJECTS ? bp_unbox(e, fields[0]) : (u32)fields[0];
|
|
199
|
+
list = fields[1];
|
|
200
|
+
}
|
|
201
|
+
if ((term_tag(list) != TAG_PAK && term_tag(list) != TAG_CTR) || term_aux(list) != nil)
|
|
202
|
+
bendpy_panic("unexpected Bend list representation");
|
|
203
|
+
term_drop(e, list);
|
|
204
|
+
call->length = length;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Handles are sealed: a Bend Object holds its arena index encrypted by a
|
|
208
|
+
// 32-bit Feistel permutation under a fresh per-invocation key. Bend code has
|
|
209
|
+
// no private constructors, so it could otherwise build PyObject{n} or do
|
|
210
|
+
// arithmetic on an id and silently alias another object of the same call.
|
|
211
|
+
// Accidental fabrication is usually detected, but 32-bit collisions remain
|
|
212
|
+
// possible. This is not authentication; the first rejected handle aborts the call.
|
|
213
|
+
static u64 bp_mix(u64 x) {
|
|
214
|
+
x ^= x >> 30; x *= 0xbf58476d1ce4e5b9ull;
|
|
215
|
+
x ^= x >> 27; x *= 0x94d049bb133111ebull;
|
|
216
|
+
return x ^ (x >> 31);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
static void bp_seed_secret(void) {
|
|
220
|
+
if (getrandom(&bp_secret, sizeof(bp_secret), 0) != sizeof(bp_secret))
|
|
221
|
+
bp_secret = bp_mix((u64)time(NULL) ^ (u64)(uintptr_t)&bp_secret);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
static u64 bp_call_key(void) {
|
|
225
|
+
pthread_once(&bp_secret_once, bp_seed_secret);
|
|
226
|
+
return bp_mix(bp_secret + bp_mix(atomic_fetch_add(&bp_call_counter, 1) + 1));
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
static u32 bp_round(u64 key, u32 round, u32 half) {
|
|
230
|
+
return (u32)bp_mix(key ^ ((u64)round << 16 | half)) & 0xffff;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
static u32 bp_seal(u64 key, u32 index) {
|
|
234
|
+
u32 l = index >> 16, r = index & 0xffff;
|
|
235
|
+
for (u32 i = 0; i < 4; ++i) { u32 t = l ^ bp_round(key, i, r); l = r; r = t; }
|
|
236
|
+
return l << 16 | r;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
static u32 bp_open(u64 key, u32 handle) {
|
|
240
|
+
u32 l = handle >> 16, r = handle & 0xffff;
|
|
241
|
+
for (u32 i = 4; i-- > 0;) { u32 t = r ^ bp_round(key, i, l); r = l; l = t; }
|
|
242
|
+
return l << 16 | r;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
static Term bp_object(BpCall* call, u64 index) {
|
|
246
|
+
return term_pak(CID(PyObject), bp_seal(call->key, (u32)index));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
static Term bp_pack(Env e, BpCall* call) {
|
|
250
|
+
switch (call->result_kind) {
|
|
251
|
+
case BP_UNIT: return term_pak(CID(Unit), 0);
|
|
252
|
+
case BP_OBJECT: return bp_object(call, call->result);
|
|
253
|
+
case BP_BOOL: return term_pak(call->result ? CID(True) : CID(False), 0);
|
|
254
|
+
case BP_STRING:
|
|
255
|
+
case BP_BYTES: {
|
|
256
|
+
bool bytes = call->result_kind == BP_BYTES;
|
|
257
|
+
Term text = term_pak(bytes ? CID(Nil) : CID(SNil), 0);
|
|
258
|
+
for (size_t i = call->length; i > 0; --i)
|
|
259
|
+
text = io_node(e, bytes ? CID(Con) : CID(SCon), call->buffer[i - 1], text);
|
|
260
|
+
return text;
|
|
261
|
+
}
|
|
262
|
+
default: return (Term)call->result;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Decode arguments in the exclusively leased runtime. The resulting handles
|
|
267
|
+
// and codepoints are plain C data; Python callbacks run outside evaluation.
|
|
268
|
+
static void bp_decode(Env e, BpCall* call) {
|
|
269
|
+
Term* f = call->fields;
|
|
270
|
+
switch (call->effect) {
|
|
271
|
+
#ifdef CID(export)
|
|
272
|
+
case CID(export):
|
|
273
|
+
bp_read_list(e, f[0], call, BP_READ_TEXT);
|
|
274
|
+
if (term_tag(f[1]) != TAG_CLO || term_loc(f[1]) != 0)
|
|
275
|
+
bendpy_panic("exports require a captureless function; use a top-level wrapper");
|
|
276
|
+
f[2] = term_aux(f[2]) == CID(True);
|
|
277
|
+
break;
|
|
278
|
+
#endif
|
|
279
|
+
#ifdef CID(require_no_kwargs)
|
|
280
|
+
case CID(require_no_kwargs):
|
|
281
|
+
#endif
|
|
282
|
+
#ifdef CID(to_u32)
|
|
283
|
+
case CID(to_u32):
|
|
284
|
+
#endif
|
|
285
|
+
#ifdef CID(to_f32)
|
|
286
|
+
case CID(to_f32):
|
|
287
|
+
#endif
|
|
288
|
+
#ifdef CID(to_bool)
|
|
289
|
+
case CID(to_bool):
|
|
290
|
+
#endif
|
|
291
|
+
#ifdef CID(to_string)
|
|
292
|
+
case CID(to_string):
|
|
293
|
+
#endif
|
|
294
|
+
#ifdef CID(len)
|
|
295
|
+
case CID(len):
|
|
296
|
+
#endif
|
|
297
|
+
#ifdef CID(truthy)
|
|
298
|
+
case CID(truthy):
|
|
299
|
+
#endif
|
|
300
|
+
#ifdef CID(to_bytes)
|
|
301
|
+
case CID(to_bytes):
|
|
302
|
+
#endif
|
|
303
|
+
f[0] = bp_unbox(e, f[0]); break;
|
|
304
|
+
#ifdef CID(get_item)
|
|
305
|
+
case CID(get_item):
|
|
306
|
+
f[0] = bp_unbox(e, f[0]); f[1] = bp_unbox(e, f[1]); break;
|
|
307
|
+
#endif
|
|
308
|
+
#ifdef CID(set_item)
|
|
309
|
+
case CID(set_item):
|
|
310
|
+
#endif
|
|
311
|
+
#ifdef CID(call)
|
|
312
|
+
case CID(call):
|
|
313
|
+
#endif
|
|
314
|
+
for (int i = 0; i < 3; ++i) f[i] = bp_unbox(e, f[i]); break;
|
|
315
|
+
#ifdef CID(getattr)
|
|
316
|
+
case CID(getattr):
|
|
317
|
+
f[0] = bp_unbox(e, f[0]); bp_read_list(e, f[1], call, BP_READ_TEXT); break;
|
|
318
|
+
#endif
|
|
319
|
+
#ifdef CID(builtins)
|
|
320
|
+
case CID(builtins):
|
|
321
|
+
#endif
|
|
322
|
+
#ifdef CID(type_error)
|
|
323
|
+
case CID(type_error):
|
|
324
|
+
#endif
|
|
325
|
+
#ifdef CID(from_string)
|
|
326
|
+
case CID(from_string):
|
|
327
|
+
#endif
|
|
328
|
+
#ifdef CID(import_module)
|
|
329
|
+
case CID(import_module):
|
|
330
|
+
#endif
|
|
331
|
+
bp_read_list(e, f[0], call, BP_READ_TEXT); break;
|
|
332
|
+
#ifdef CID(tuple)
|
|
333
|
+
case CID(tuple):
|
|
334
|
+
#endif
|
|
335
|
+
#ifdef CID(list)
|
|
336
|
+
case CID(list):
|
|
337
|
+
#endif
|
|
338
|
+
bp_read_list(e, f[0], call, BP_READ_OBJECTS); break;
|
|
339
|
+
#ifdef CID(from_bytes)
|
|
340
|
+
case CID(from_bytes):
|
|
341
|
+
bp_read_list(e, f[0], call, BP_READ_WORDS); break;
|
|
342
|
+
#endif
|
|
343
|
+
#ifdef CID(from_bool)
|
|
344
|
+
case CID(from_bool): f[0] = term_aux(f[0]) == CID(True); break;
|
|
345
|
+
#endif
|
|
346
|
+
#ifdef CID(from_u32)
|
|
347
|
+
case CID(from_u32): break;
|
|
348
|
+
#endif
|
|
349
|
+
#ifdef CID(from_f32)
|
|
350
|
+
case CID(from_f32): break;
|
|
351
|
+
#endif
|
|
352
|
+
#ifdef CID(from_nat)
|
|
353
|
+
case CID(from_nat):
|
|
354
|
+
if ((u64)f[0] > NAT_IMM) bendpy_panic("unexpected Bend Nat representation");
|
|
355
|
+
break;
|
|
356
|
+
#endif
|
|
357
|
+
#ifdef CID(none)
|
|
358
|
+
case CID(none): break;
|
|
359
|
+
#endif
|
|
360
|
+
#ifdef CID(empty_dict)
|
|
361
|
+
case CID(empty_dict): break;
|
|
362
|
+
#endif
|
|
363
|
+
default: bendpy_panic("unsupported foreign effect in Python extension");
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
static bool bp_native(BpCall* call, BpOperation operation) {
|
|
368
|
+
bp_assert_attached();
|
|
369
|
+
#ifdef Py_GIL_DISABLED
|
|
370
|
+
// Without a GIL, staying attached only delays stop-the-world pauses.
|
|
371
|
+
PyThreadState* thread = PyEval_SaveThread();
|
|
372
|
+
#else
|
|
373
|
+
PyThreadState* thread = call->release_gil ? PyEval_SaveThread() : NULL;
|
|
374
|
+
#endif
|
|
375
|
+
bp_current = call->runtime;
|
|
376
|
+
// Poisoned instances are never cached, so every lease starts healthy.
|
|
377
|
+
if (setjmp(bp_current->escape)) {
|
|
378
|
+
snprintf(call->error, sizeof(call->error), "%s", bp_current->error);
|
|
379
|
+
} else {
|
|
380
|
+
if (CORPUS == NULL) {
|
|
381
|
+
corpus_setup(false, 1, 0);
|
|
382
|
+
io_stk = pool_mmap(BP_STACK_BYTES + BP_STACK_GUARD);
|
|
383
|
+
if (mprotect((char*)io_stk + BP_STACK_BYTES, BP_STACK_GUARD, PROT_NONE))
|
|
384
|
+
bendpy_panic("unable to guard Bend stack");
|
|
385
|
+
}
|
|
386
|
+
Env e = { CORPUS, ALC[0] };
|
|
387
|
+
switch (operation) {
|
|
388
|
+
case BP_INIT:
|
|
389
|
+
call->continuation = corpus_eval(CORPUS,
|
|
390
|
+
term_tsk(MAIN_FID, task_node(e, MAIN_FID, TERM_HOLE, 0, 0)));
|
|
391
|
+
call->argument = term_clo(FID_IO_EMIT, 0);
|
|
392
|
+
break;
|
|
393
|
+
case BP_START: {
|
|
394
|
+
Term args = term_pak(CID(Nil), 0);
|
|
395
|
+
for (size_t i = call->nargs; i > 0; --i)
|
|
396
|
+
args = io_node(e, CID(Con), bp_object(call, i - 1), args);
|
|
397
|
+
Term input = io_node(e, CID(PyCall), args, bp_object(call, call->nargs));
|
|
398
|
+
call->continuation = bp_apply(e, call->function, input);
|
|
399
|
+
call->argument = term_clo(FID_IO_EMIT, 0);
|
|
400
|
+
break;
|
|
401
|
+
}
|
|
402
|
+
case BP_RESUME: {
|
|
403
|
+
if (call->pending) {
|
|
404
|
+
call->argument = bp_pack(e, call);
|
|
405
|
+
call->pending = false;
|
|
406
|
+
free(call->buffer); call->buffer = NULL;
|
|
407
|
+
}
|
|
408
|
+
Term request = bp_apply(e, call->continuation, call->argument);
|
|
409
|
+
call->continuation = 0;
|
|
410
|
+
call->effect = (u32)term_aux(request);
|
|
411
|
+
if (call->effect == CID(Emit)) {
|
|
412
|
+
Term value;
|
|
413
|
+
spare_free(e, 0, ctr_take(e, request, 1, &value));
|
|
414
|
+
if (call->module) term_drop(e, value);
|
|
415
|
+
else call->result = bp_unbox(e, value);
|
|
416
|
+
call->done = true;
|
|
417
|
+
break;
|
|
418
|
+
}
|
|
419
|
+
u32 n = cid_arity(call->effect);
|
|
420
|
+
if (n == 0 || n > 5) bendpy_panic("unsupported foreign effect arity");
|
|
421
|
+
spare_free(e, cls_fit(n), ctr_take(e, request, n, call->fields));
|
|
422
|
+
call->continuation = call->fields[n - 1];
|
|
423
|
+
bp_decode(e, call);
|
|
424
|
+
break;
|
|
425
|
+
}
|
|
426
|
+
case BP_DROP:
|
|
427
|
+
if (call->continuation) term_drop(e, call->continuation);
|
|
428
|
+
break;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
bp_current = NULL;
|
|
432
|
+
if (thread) PyEval_RestoreThread(thread);
|
|
433
|
+
bp_assert_attached();
|
|
434
|
+
return call->error[0] == 0;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Each invocation owns a reference arena. Handles never contain PyObject*
|
|
438
|
+
// addresses. Open and check them before access, and release the arena outside
|
|
439
|
+
// evaluation.
|
|
440
|
+
static PyObject* bp_get(BpCall* call, u64 handle) {
|
|
441
|
+
bp_assert_attached();
|
|
442
|
+
u32 index = bp_open(call->key, (u32)handle);
|
|
443
|
+
if (index >= call->count) {
|
|
444
|
+
PyErr_SetString(PyExc_ValueError,
|
|
445
|
+
"invalid Python object handle: handles must come from the bridge in this call");
|
|
446
|
+
return NULL;
|
|
447
|
+
}
|
|
448
|
+
return call->objects[index];
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
static bool bp_add(BpCall* call, PyObject* object) {
|
|
452
|
+
bp_assert_attached();
|
|
453
|
+
if (object == NULL) return false;
|
|
454
|
+
if (call->count == UINT32_MAX) {
|
|
455
|
+
Py_DECREF(object); PyErr_SetString(PyExc_OverflowError, "too many object handles"); return false;
|
|
456
|
+
}
|
|
457
|
+
if (call->count == call->capacity) {
|
|
458
|
+
size_t capacity = call->capacity ? call->capacity * 2 : 16;
|
|
459
|
+
PyObject** objects = PyMem_Realloc(call->objects, capacity * sizeof(PyObject*));
|
|
460
|
+
if (objects == NULL) { Py_DECREF(object); PyErr_NoMemory(); return false; }
|
|
461
|
+
call->objects = objects; call->capacity = capacity;
|
|
462
|
+
}
|
|
463
|
+
call->result_kind = BP_OBJECT;
|
|
464
|
+
call->result = call->count;
|
|
465
|
+
call->objects[call->count++] = object;
|
|
466
|
+
return true;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
static PyObject* bp_text(BpCall* call) {
|
|
470
|
+
bp_assert_attached();
|
|
471
|
+
for (size_t i = 0; i < call->length; ++i) {
|
|
472
|
+
if (call->buffer[i] > 0x10ffff) {
|
|
473
|
+
PyErr_SetString(PyExc_ValueError, "Bend String contains an invalid Unicode codepoint");
|
|
474
|
+
return NULL;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
return PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, call->buffer, call->length);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
static PyObject* bp_call_python(PyObject*, PyObject*, PyObject*);
|
|
481
|
+
|
|
482
|
+
static void bp_function_dealloc(PyObject* self) {
|
|
483
|
+
BpFunction* function = (BpFunction*)self;
|
|
484
|
+
Py_XDECREF(function->name); Py_XDECREF(function->module);
|
|
485
|
+
PyObject_Free(self);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
static PyObject* bp_function_name(PyObject* self, void* closure) {
|
|
489
|
+
return Py_NewRef(((BpFunction*)self)->name);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
static PyObject* bp_function_module(PyObject* self, void* closure) {
|
|
493
|
+
return Py_NewRef(((BpFunction*)self)->module);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
static PyObject* bp_function_repr(PyObject* self) {
|
|
497
|
+
BpFunction* function = (BpFunction*)self;
|
|
498
|
+
return PyUnicode_FromFormat("<bend function %U.%U>", function->module, function->name);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Pickle by reference, like a module-level function: module.name.
|
|
502
|
+
static PyObject* bp_function_reduce(PyObject* self, PyObject* unused) {
|
|
503
|
+
return Py_NewRef(((BpFunction*)self)->name);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
static PyGetSetDef bp_function_getset[] = {
|
|
507
|
+
{"__name__", bp_function_name, NULL, NULL, NULL},
|
|
508
|
+
{"__qualname__", bp_function_name, NULL, NULL, NULL},
|
|
509
|
+
{"__module__", bp_function_module, NULL, NULL, NULL},
|
|
510
|
+
{NULL},
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
static PyMethodDef bp_function_methods[] = {
|
|
514
|
+
{"__reduce__", bp_function_reduce, METH_NOARGS, NULL},
|
|
515
|
+
{NULL},
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
static PyTypeObject bp_function_type = {
|
|
519
|
+
PyVarObject_HEAD_INIT(NULL, 0)
|
|
520
|
+
.tp_name = "bend_python.function",
|
|
521
|
+
.tp_basicsize = sizeof(BpFunction),
|
|
522
|
+
.tp_dealloc = bp_function_dealloc,
|
|
523
|
+
.tp_repr = bp_function_repr,
|
|
524
|
+
.tp_call = bp_call_python,
|
|
525
|
+
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE | Py_TPFLAGS_DISALLOW_INSTANTIATION,
|
|
526
|
+
.tp_doc = "A native Bend function.",
|
|
527
|
+
.tp_methods = bp_function_methods,
|
|
528
|
+
.tp_getset = bp_function_getset,
|
|
529
|
+
};
|
|
530
|
+
|
|
531
|
+
static bool bp_export(BpCall* call) {
|
|
532
|
+
if (!call->module) { PyErr_SetString(PyExc_RuntimeError, "exports are only allowed during initialization"); return false; }
|
|
533
|
+
PyObject* name = bp_text(call);
|
|
534
|
+
if (name == NULL) return false;
|
|
535
|
+
Py_ssize_t size;
|
|
536
|
+
const char* text = PyUnicode_AsUTF8AndSize(name, &size);
|
|
537
|
+
if (!text) { Py_DECREF(name); return false; }
|
|
538
|
+
if (size == 0 || strlen(text) != (size_t)size || PyObject_HasAttr(call->module, name)) {
|
|
539
|
+
Py_DECREF(name); PyErr_SetString(PyExc_ValueError, "empty, duplicate, or NUL-containing export name"); return false;
|
|
540
|
+
}
|
|
541
|
+
PyObject* module_name = PyModule_GetNameObject(call->module);
|
|
542
|
+
BpFunction* function = module_name ? PyObject_New(BpFunction, &bp_function_type) : NULL;
|
|
543
|
+
if (!function) { Py_DECREF(name); Py_XDECREF(module_name); return false; }
|
|
544
|
+
function->function = call->fields[1]; function->release_gil = call->fields[2];
|
|
545
|
+
function->name = name; function->module = module_name;
|
|
546
|
+
int added = PyModule_AddObjectRef(call->module, text, (PyObject*)function);
|
|
547
|
+
Py_DECREF(function);
|
|
548
|
+
return added == 0;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
static bool bp_effect(BpCall* call) {
|
|
552
|
+
bp_assert_attached();
|
|
553
|
+
Term* f = call->fields;
|
|
554
|
+
PyObject *a = NULL, *b = NULL, *c = NULL, *result = NULL;
|
|
555
|
+
call->result_kind = BP_UNIT;
|
|
556
|
+
switch (call->effect) {
|
|
557
|
+
#ifdef CID(export)
|
|
558
|
+
case CID(export): return bp_export(call);
|
|
559
|
+
#endif
|
|
560
|
+
#ifdef CID(require_no_kwargs)
|
|
561
|
+
case CID(require_no_kwargs):
|
|
562
|
+
a = bp_get(call, f[0]);
|
|
563
|
+
if (!a) return false;
|
|
564
|
+
if (!PyDict_Check(a) || PyDict_Size(a) != 0) {
|
|
565
|
+
PyErr_SetString(PyExc_TypeError, "this function accepts positional arguments only"); return false;
|
|
566
|
+
}
|
|
567
|
+
return true;
|
|
568
|
+
#endif
|
|
569
|
+
#ifdef CID(to_u32)
|
|
570
|
+
case CID(to_u32): {
|
|
571
|
+
a = bp_get(call, f[0]); if (!a) return false;
|
|
572
|
+
if (!PyLong_Check(a) || PyBool_Check(a)) {
|
|
573
|
+
PyErr_SetString(PyExc_TypeError, "expected an integer in the U32 range"); return false;
|
|
574
|
+
}
|
|
575
|
+
unsigned long x = PyLong_AsUnsignedLong(a);
|
|
576
|
+
if (PyErr_Occurred()) return false;
|
|
577
|
+
if (x > UINT32_MAX) { PyErr_SetString(PyExc_OverflowError, "integer does not fit U32"); return false; }
|
|
578
|
+
call->result_kind = BP_U32; call->result = x; return true;
|
|
579
|
+
}
|
|
580
|
+
#endif
|
|
581
|
+
#ifdef CID(from_u32)
|
|
582
|
+
case CID(from_u32): return bp_add(call, PyLong_FromUnsignedLong((u32)f[0]));
|
|
583
|
+
#endif
|
|
584
|
+
#ifdef CID(from_nat)
|
|
585
|
+
case CID(from_nat): return bp_add(call, PyLong_FromUnsignedLongLong((u64)f[0]));
|
|
586
|
+
#endif
|
|
587
|
+
#ifdef CID(to_f32)
|
|
588
|
+
case CID(to_f32): {
|
|
589
|
+
a = bp_get(call, f[0]); if (!a) return false;
|
|
590
|
+
if (!PyFloat_Check(a)) { PyErr_SetString(PyExc_TypeError, "expected a float"); return false; }
|
|
591
|
+
// IEEE 754 narrowing (Annex F): round to nearest, overflowing to infinity.
|
|
592
|
+
union { float f; u32 u; } x = { .f = (float)PyFloat_AsDouble(a) };
|
|
593
|
+
call->result_kind = BP_F32; call->result = x.u; return true;
|
|
594
|
+
}
|
|
595
|
+
#endif
|
|
596
|
+
#ifdef CID(from_f32)
|
|
597
|
+
case CID(from_f32): { union { u32 u; float f; } x = { .u = (u32)f[0] }; return bp_add(call, PyFloat_FromDouble(x.f)); }
|
|
598
|
+
#endif
|
|
599
|
+
#ifdef CID(to_bool)
|
|
600
|
+
case CID(to_bool):
|
|
601
|
+
a = bp_get(call, f[0]); if (!a) return false;
|
|
602
|
+
if (!PyBool_Check(a)) { PyErr_SetString(PyExc_TypeError, "expected a bool"); return false; }
|
|
603
|
+
call->result_kind = BP_BOOL; call->result = a == Py_True; return true;
|
|
604
|
+
#endif
|
|
605
|
+
#ifdef CID(from_bool)
|
|
606
|
+
case CID(from_bool): return bp_add(call, PyBool_FromLong(f[0] != 0));
|
|
607
|
+
#endif
|
|
608
|
+
#ifdef CID(to_string)
|
|
609
|
+
case CID(to_string): {
|
|
610
|
+
a = bp_get(call, f[0]); if (!a) return false;
|
|
611
|
+
if (!PyUnicode_Check(a)) { PyErr_SetString(PyExc_TypeError, "expected a string"); return false; }
|
|
612
|
+
Py_ssize_t n = PyUnicode_GetLength(a);
|
|
613
|
+
call->buffer = malloc((n ? n : 1) * sizeof(u32));
|
|
614
|
+
if (!call->buffer) { PyErr_NoMemory(); return false; }
|
|
615
|
+
for (Py_ssize_t i = 0; i < n; ++i) call->buffer[i] = PyUnicode_ReadChar(a, i);
|
|
616
|
+
call->length = n; call->result_kind = BP_STRING; return true;
|
|
617
|
+
}
|
|
618
|
+
#endif
|
|
619
|
+
#ifdef CID(from_string)
|
|
620
|
+
case CID(from_string): return bp_add(call, bp_text(call));
|
|
621
|
+
#endif
|
|
622
|
+
#ifdef CID(get_item)
|
|
623
|
+
case CID(get_item):
|
|
624
|
+
a = bp_get(call, f[0]); b = bp_get(call, f[1]); if (!a || !b) return false;
|
|
625
|
+
return bp_add(call, PyObject_GetItem(a, b));
|
|
626
|
+
#endif
|
|
627
|
+
#ifdef CID(set_item)
|
|
628
|
+
case CID(set_item):
|
|
629
|
+
a = bp_get(call, f[0]); b = bp_get(call, f[1]); c = bp_get(call, f[2]);
|
|
630
|
+
return a && b && c && PyObject_SetItem(a, b, c) == 0;
|
|
631
|
+
#endif
|
|
632
|
+
#ifdef CID(getattr)
|
|
633
|
+
case CID(getattr):
|
|
634
|
+
a = bp_get(call, f[0]); b = bp_text(call); if (!a || !b) { Py_XDECREF(b); return false; }
|
|
635
|
+
result = PyObject_GetAttr(a, b); Py_DECREF(b); return bp_add(call, result);
|
|
636
|
+
#endif
|
|
637
|
+
#ifdef CID(call)
|
|
638
|
+
case CID(call):
|
|
639
|
+
a = bp_get(call, f[0]); b = bp_get(call, f[1]); c = bp_get(call, f[2]);
|
|
640
|
+
if (!a || !b || !c) return false;
|
|
641
|
+
if (!PyTuple_Check(b) || !PyDict_Check(c)) {
|
|
642
|
+
PyErr_SetString(PyExc_TypeError, "call requires a tuple of arguments and a dict of keywords"); return false;
|
|
643
|
+
}
|
|
644
|
+
return bp_add(call, PyObject_Call(a, b, c));
|
|
645
|
+
#endif
|
|
646
|
+
#ifdef CID(builtins)
|
|
647
|
+
case CID(builtins):
|
|
648
|
+
a = PyImport_ImportModule("builtins"); b = bp_text(call);
|
|
649
|
+
if (!a || !b) { Py_XDECREF(a); Py_XDECREF(b); return false; }
|
|
650
|
+
result = PyObject_GetAttr(a, b); Py_DECREF(a); Py_DECREF(b); return bp_add(call, result);
|
|
651
|
+
#endif
|
|
652
|
+
#ifdef CID(none)
|
|
653
|
+
case CID(none): return bp_add(call, Py_NewRef(Py_None));
|
|
654
|
+
#endif
|
|
655
|
+
#ifdef CID(empty_dict)
|
|
656
|
+
case CID(empty_dict): return bp_add(call, PyDict_New());
|
|
657
|
+
#endif
|
|
658
|
+
#ifdef CID(tuple)
|
|
659
|
+
case CID(tuple):
|
|
660
|
+
#endif
|
|
661
|
+
#ifdef CID(list)
|
|
662
|
+
case CID(list):
|
|
663
|
+
#endif
|
|
664
|
+
result = PyTuple_New(call->length);
|
|
665
|
+
if (!result) return false;
|
|
666
|
+
for (size_t i = 0; i < call->length; ++i) {
|
|
667
|
+
a = bp_get(call, call->buffer[i]);
|
|
668
|
+
if (!a) { Py_DECREF(result); return false; }
|
|
669
|
+
PyTuple_SET_ITEM(result, i, Py_NewRef(a));
|
|
670
|
+
}
|
|
671
|
+
#ifdef CID(list)
|
|
672
|
+
if (call->effect == CID(list)) { a = PySequence_List(result); Py_DECREF(result); result = a; }
|
|
673
|
+
#endif
|
|
674
|
+
return bp_add(call, result);
|
|
675
|
+
#ifdef CID(truthy)
|
|
676
|
+
case CID(truthy): {
|
|
677
|
+
a = bp_get(call, f[0]); if (!a) return false;
|
|
678
|
+
int truth = PyObject_IsTrue(a); if (truth < 0) return false;
|
|
679
|
+
call->result_kind = BP_BOOL; call->result = truth; return true;
|
|
680
|
+
}
|
|
681
|
+
#endif
|
|
682
|
+
#ifdef CID(to_bytes)
|
|
683
|
+
case CID(to_bytes): {
|
|
684
|
+
a = bp_get(call, f[0]); if (!a) return false;
|
|
685
|
+
// Any C-contiguous buffer: bytes, bytearray, memoryview, array.array, ...
|
|
686
|
+
Py_buffer view;
|
|
687
|
+
if (PyObject_GetBuffer(a, &view, PyBUF_C_CONTIGUOUS) < 0) return false;
|
|
688
|
+
call->buffer = malloc((view.len ? view.len : 1) * sizeof(u32));
|
|
689
|
+
if (!call->buffer) { PyBuffer_Release(&view); PyErr_NoMemory(); return false; }
|
|
690
|
+
const unsigned char* bytes = view.buf;
|
|
691
|
+
#ifdef Py_GIL_DISABLED
|
|
692
|
+
// Buffer exports pin storage, but do not stop bytearray slice writes.
|
|
693
|
+
// Other exporters/views require caller synchronization against alias writes.
|
|
694
|
+
Py_BEGIN_CRITICAL_SECTION(a);
|
|
695
|
+
#endif
|
|
696
|
+
for (Py_ssize_t i = 0; i < view.len; ++i) call->buffer[i] = bytes[i];
|
|
697
|
+
#ifdef Py_GIL_DISABLED
|
|
698
|
+
Py_END_CRITICAL_SECTION();
|
|
699
|
+
#endif
|
|
700
|
+
call->length = view.len; call->result_kind = BP_BYTES;
|
|
701
|
+
PyBuffer_Release(&view);
|
|
702
|
+
return true;
|
|
703
|
+
}
|
|
704
|
+
#endif
|
|
705
|
+
#ifdef CID(from_bytes)
|
|
706
|
+
case CID(from_bytes): {
|
|
707
|
+
result = PyBytes_FromStringAndSize(NULL, call->length);
|
|
708
|
+
if (!result) return false;
|
|
709
|
+
char* out = PyBytes_AS_STRING(result);
|
|
710
|
+
for (size_t i = 0; i < call->length; ++i) {
|
|
711
|
+
if (call->buffer[i] > 255) {
|
|
712
|
+
Py_DECREF(result);
|
|
713
|
+
PyErr_Format(PyExc_ValueError, "byte %zu is %u, outside range(256)", i, call->buffer[i]);
|
|
714
|
+
return false;
|
|
715
|
+
}
|
|
716
|
+
out[i] = (char)call->buffer[i];
|
|
717
|
+
}
|
|
718
|
+
return bp_add(call, result);
|
|
719
|
+
}
|
|
720
|
+
#endif
|
|
721
|
+
#ifdef CID(import_module)
|
|
722
|
+
case CID(import_module):
|
|
723
|
+
a = bp_text(call); if (!a) return false;
|
|
724
|
+
result = PyImport_Import(a); Py_DECREF(a); return bp_add(call, result);
|
|
725
|
+
#endif
|
|
726
|
+
#ifdef CID(len)
|
|
727
|
+
case CID(len): {
|
|
728
|
+
a = bp_get(call, f[0]); if (!a) return false;
|
|
729
|
+
Py_ssize_t n = PyObject_Length(a); if (n < 0) return false;
|
|
730
|
+
if ((u64)n > NAT_IMM) { PyErr_SetString(PyExc_OverflowError, "length exceeds Bend Nat range"); return false; }
|
|
731
|
+
call->result_kind = BP_NAT; call->result = n; return true;
|
|
732
|
+
}
|
|
733
|
+
#endif
|
|
734
|
+
#ifdef CID(type_error)
|
|
735
|
+
case CID(type_error):
|
|
736
|
+
a = bp_text(call); if (!a) return false;
|
|
737
|
+
PyErr_SetObject(PyExc_TypeError, a); Py_DECREF(a); return false;
|
|
738
|
+
#endif
|
|
739
|
+
default: PyErr_SetString(PyExc_RuntimeError, "unsupported Python effect"); return false;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
static PyObject* bp_run(BpCall* call, BpOperation start) {
|
|
744
|
+
call->key = bp_call_key();
|
|
745
|
+
call->runtime = bp_runtime_acquire();
|
|
746
|
+
bool ok = call->runtime != NULL && bp_native(call, start);
|
|
747
|
+
while (ok && !call->done) {
|
|
748
|
+
ok = bp_native(call, BP_RESUME);
|
|
749
|
+
if (!ok || call->done) break;
|
|
750
|
+
ok = bp_effect(call);
|
|
751
|
+
// Conversions returning native data retain the buffer until BP_RESUME packs it.
|
|
752
|
+
if (call->result_kind != BP_STRING && call->result_kind != BP_BYTES) { free(call->buffer); call->buffer = NULL; }
|
|
753
|
+
call->pending = ok;
|
|
754
|
+
}
|
|
755
|
+
PyObject* result = NULL;
|
|
756
|
+
if (ok) {
|
|
757
|
+
PyObject* object = call->module ? call->module : bp_get(call, call->result);
|
|
758
|
+
if (object) result = Py_NewRef(object);
|
|
759
|
+
} else {
|
|
760
|
+
if (!PyErr_Occurred()) PyErr_SetString(PyExc_RuntimeError, call->error);
|
|
761
|
+
// A Python exception only aborts its invocation. Native failures discard
|
|
762
|
+
// the damaged instance rather than affecting other callers.
|
|
763
|
+
if (call->runtime && !call->error[0]) bp_native(call, BP_DROP);
|
|
764
|
+
}
|
|
765
|
+
// Return the lease before decref: finalizers can invoke this extension too.
|
|
766
|
+
if (call->runtime) bp_runtime_release(call->runtime);
|
|
767
|
+
call->runtime = NULL;
|
|
768
|
+
free(call->buffer);
|
|
769
|
+
for (size_t i = 0; i < call->count; ++i) Py_DECREF(call->objects[i]);
|
|
770
|
+
PyMem_Free(call->objects);
|
|
771
|
+
return result;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
static PyObject* bp_call_python(PyObject* self, PyObject* args, PyObject* kwargs) {
|
|
775
|
+
bp_assert_attached();
|
|
776
|
+
// Older CPython can reuse a single-phase module in another interpreter
|
|
777
|
+
// without invoking PyInit again; enforce the boundary on every call too.
|
|
778
|
+
if (PyInterpreterState_GetID(PyInterpreterState_Get()) != 0) {
|
|
779
|
+
PyErr_SetString(PyExc_RuntimeError, "Bend calls require the main Python interpreter"); return NULL;
|
|
780
|
+
}
|
|
781
|
+
BpFunction* entry = (BpFunction*)self;
|
|
782
|
+
BpCall call = { .function = entry->function, .release_gil = entry->release_gil,
|
|
783
|
+
.nargs = (size_t)PyTuple_Size(args) };
|
|
784
|
+
for (size_t i = 0; i < call.nargs; ++i) {
|
|
785
|
+
if (!bp_add(&call, Py_NewRef(PyTuple_GetItem(args, i)))) goto fail;
|
|
786
|
+
}
|
|
787
|
+
// C callers (PyObject_Call) may pass their own dict; like a Python **kwargs
|
|
788
|
+
// function, Bend gets a fresh one it may mutate or return.
|
|
789
|
+
if (!bp_add(&call, kwargs ? PyDict_Copy(kwargs) : PyDict_New())) goto fail;
|
|
790
|
+
return bp_run(&call, BP_START);
|
|
791
|
+
fail:
|
|
792
|
+
for (size_t i = 0; i < call.count; ++i) Py_DECREF(call.objects[i]);
|
|
793
|
+
PyMem_Free(call.objects);
|
|
794
|
+
return NULL;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
static struct PyModuleDef bp_definition = {
|
|
798
|
+
PyModuleDef_HEAD_INIT, BENDPY_MODULE_NAME,
|
|
799
|
+
"Native Python bindings written in Bend.", -1, NULL
|
|
800
|
+
};
|
|
801
|
+
|
|
802
|
+
PyMODINIT_FUNC BENDPY_INIT(void) {
|
|
803
|
+
bp_assert_attached();
|
|
804
|
+
if (PyInterpreterState_GetID(PyInterpreterState_Get()) != 0) {
|
|
805
|
+
PyErr_SetString(PyExc_ImportError, "Bend currently supports only the main Python interpreter"); return NULL;
|
|
806
|
+
}
|
|
807
|
+
if (PyType_Ready(&bp_function_type) < 0) return NULL;
|
|
808
|
+
PyObject* module = PyModule_Create(&bp_definition);
|
|
809
|
+
if (!module) return NULL;
|
|
810
|
+
#ifdef Py_GIL_DISABLED
|
|
811
|
+
PyUnstable_Module_SetGIL(module, Py_MOD_GIL_NOT_USED);
|
|
812
|
+
#endif
|
|
813
|
+
BpCall call = { .module = module };
|
|
814
|
+
PyObject* result = bp_run(&call, BP_INIT);
|
|
815
|
+
Py_DECREF(module);
|
|
816
|
+
return result;
|
|
817
|
+
}
|