Visitar URL original
Phase 4.X-full X3a (Batch 95): NEW PhxPtrMap substrate + phx_threaded… · SonicField/cpython@e56b679 · GitHub
Skip to content

Commit e56b679

Browse files
committed
Phase 4.X-full X3a (Batch 95): NEW PhxPtrMap substrate + phx_threaded_ref bridges + verifier
Per supervisor 04:04:50Z X3 disposition (Q-X3-1..5) + theologian docs/tier7-phase4x-x3-preanalysis-2026-05-01.md §2-§6. X3a is substrate-only authoring (no migration this batch); X3b/X3c will consume PhxPtrMap + bridges for E-1+E-2+E-3 Environment + E-6 BlockCanonicalizer migrations. Pre-author substrate-grep per supervisor instruction: confirmed ZERO existing PhxPtrMap/phx_ptr_map tokens in Python/jit/hir/. ZERO existing phx_threaded_incref/decref bridges. ThreadedRef class lives at threaded_compile.h:224 with static incref/decref template methods at :307+:337. Pre-commit chat-recheck applied per discipline. NEW files (+340 LOC across 3 NEW + 1 modified): (1) phx_ptr_map.h (190 LOC): PhxPtrMap generic void*-keyed open-address hash map with void* values. - typedef struct PhxPtrMapEntry { void *key; void *value } - typedef struct PhxPtrMap { PhxPtrMapEntry *entries; size_t count; capacity } - 11 static-inline ops: init/destroy/clear/lookup/contains/insert/ insert_raw/resize + slot/size/capacity/at_key/at_value - Open-addressed hash with linear probing (mirrors PhxPtrSet) - Power-of-2 capacity; PHX_PTR_MAP_INITIAL_CAP=16u; load 0.7 triggers resize doubling - Knuth multiplicative hash on uintptr_t (64-bit constant) - Empty-slot sentinel: entries[i].key == NULL - NULL-key invariant (cannot store NULL as key); NULL VALUE permitted with contains-vs-NULL-value disambiguation via separate phx_ptr_map_contains accessor - Allocator: plain calloc/realloc/free; JIT_CHECK_C(new_entries != NULL) loud-fail post-calloc per forward stance python#4 (X1a 0bac3bc precedent) - Q-X3-2 void*-VALUE only (YAGNI-bounded); specialize at E-9 if typed-value variant needed - Q-X3-5 E-9 forward-prep tag in header block-comment (audit-trail amortization signal; no speculative E-9 surface added) (2) phx_threaded_ref.h (32 LOC): extern "C" declarations for phx_threaded_incref / phx_threaded_decref. Both NULL-safe and immortal-safe (mirror ThreadedRef::incref/decref short-circuits). Uses Python.h opaque PyObject for C ABI. Per Q-X-full-5 supervisor 03:17:18Z: substrates stay in Python/jit/hir/ namespace. (3) phx_threaded_ref.cpp (18 LOC): extern "C" implementations. Thin wrappers calling jit::ThreadedRef<>::incref(obj) and ::decref(obj) static template methods. Auto-globbed by Python/jit_build/CMakeLists.txt:123 GLOB_RECURSE pattern; no build config change needed. (4) hir_instr_c_verify.cpp (+98 LOC): batch95 7-section coverage: (a) empty: lookup NULL, contains 0, size 0 (b) single insert: returns 1 / size 1 / lookup correct value (c) duplicate-key update: insert returns 0 (overwrite), size unchanged, lookup returns NEW value (d) multi insert + lookup across 4 distinct keys (e) contains-vs-NULL-value disambiguation: NULL value permitted; contains true; lookup returns NULL; absent-key contains false (f) iter raw slots: walk capacity collecting all 5 inserted keys (g) clear + reuse: count resets, lookup NULL; post-clear insert works; destroy resets all fields Substrate ready for X3b E-1+E-2+E-3 Environment migration + X3c E-6 BlockCanonicalizer migration. Header-only zero-cost abstractions for PhxPtrMap; thin extern 'C' bridge layer for ThreadedRef. Falsifier (V5): batch95 7-section verifier exercises empty/single/ duplicate-update/multi/NULL-value/iteration/clear semantics. Compile proves bridge linkage; phx_threaded_incref/decref delegate to ThreadedRef::incref/decref which has its own GIL-disabled atomic-counter + standard refcount paths. Cumulative-drift watch counter: 15/5+. Mostly header-only static-inline + thin C/C++ bridge .cpp; release-codegen IDENTICAL modulo new symbols (PhxPtrMap header-only; bridge .cpp adds 2 entries). Phase 4.X-full progress: 2.1 / 2.7 / 2.4 / 2.2 — all 4 substrate classes initiated. X3 cluster will discharge 4-of-7 remaining stay-C++ exceptions on full close (X3b/X3c migrations). Q-X3-4 phase-stop accepted post-X3; E-7 residual + E-9 cluster → next session(s). Per supervisor M-slate dispatch D-1777572112 + Phase 4.X-full ENTRY + X3a dispatch (LEAD: generalist, STRUCT substrate-authoring tier per supervisor 04:04:50Z).
1 parent 287c8f8 commit e56b679

4 files changed

Lines changed: 349 additions & 0 deletions

File tree

‎Python/jit/hir/hir_instr_c_verify.cpp‎

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "cinderx/Jit/hir/phx_bc_offset_set.h" /* Phase 4.X-full X1a (Batch 89) */
1919
#include "cinderx/Jit/hir/phx_ptr_queue.h" /* Phase 4.X-full X2a (Batch 91) */
2020
#include "cinderx/Jit/hir/phx_ptr_set.h" /* Phase 4.X-full X2b (Batch 92) */
21+
#include "cinderx/Jit/hir/phx_ptr_map.h" /* Phase 4.X-full X3a (Batch 95) */
2122

2223
#include <cassert>
2324
#include <cstring>
@@ -4396,6 +4397,102 @@ static void verify_phase4x_full_batch93_ptr_set_at() {
43964397
phx_ptr_set_destroy(&s);
43974398
}
43984399

4400+
/* Phase 4.X-full X3a Batch 95 (supervisor 04:04:50Z): PhxPtrMap
4401+
* substrate falsifier. Generic void*-keyed open-address hash map with
4402+
* void* values + parallel keys/values entries. Discharges 4-of-7
4403+
* remaining stay-C++ exceptions when X3b/X3c migrations consume.
4404+
* Coverage: empty/single insert+lookup/duplicate-update/multi
4405+
* insert+lookup/contains-vs-NULL-value/iter raw slots/clear+reuse. */
4406+
static void verify_phase4x_full_batch95_ptr_map() {
4407+
PhxPtrMap m;
4408+
phx_ptr_map_init(&m);
4409+
4410+
/* (a) empty: lookup returns NULL, contains 0, capacity 0 */
4411+
int marker_a = 0xA;
4412+
int marker_b = 0xB;
4413+
assert(phx_ptr_map_size(&m) == 0 &&
4414+
"Phase 4.X-full Batch 95(a): empty size==0");
4415+
assert(phx_ptr_map_lookup(&m, &marker_a) == NULL &&
4416+
"Phase 4.X-full Batch 95(a): lookup on empty == NULL");
4417+
assert(phx_ptr_map_contains(&m, &marker_a) == 0 &&
4418+
"Phase 4.X-full Batch 95(a): contains on empty == 0");
4419+
4420+
/* (b) single insert: returns 1 (newly inserted); lookup returns value */
4421+
int r = phx_ptr_map_insert(&m, &marker_a, &marker_b);
4422+
assert(r == 1 &&
4423+
"Phase 4.X-full Batch 95(b): first insert returns 1");
4424+
assert(phx_ptr_map_size(&m) == 1 &&
4425+
"Phase 4.X-full Batch 95(b): size==1 after first insert");
4426+
assert(phx_ptr_map_lookup(&m, &marker_a) == &marker_b &&
4427+
"Phase 4.X-full Batch 95(b): lookup returns inserted value");
4428+
assert(phx_ptr_map_contains(&m, &marker_a) == 1 &&
4429+
"Phase 4.X-full Batch 95(b): contains after insert == 1");
4430+
4431+
/* (c) duplicate-key update: returns 0 (overwrite); size unchanged */
4432+
int marker_c = 0xC;
4433+
int r_upd = phx_ptr_map_insert(&m, &marker_a, &marker_c);
4434+
assert(r_upd == 0 &&
4435+
"Phase 4.X-full Batch 95(c): dup-key insert returns 0 (update)");
4436+
assert(phx_ptr_map_size(&m) == 1 &&
4437+
"Phase 4.X-full Batch 95(c): size unchanged on update");
4438+
assert(phx_ptr_map_lookup(&m, &marker_a) == &marker_c &&
4439+
"Phase 4.X-full Batch 95(c): lookup returns NEW value post-update");
4440+
4441+
/* (d) multi insert + lookup across distinct keys */
4442+
int marker_d = 0xD, marker_e = 0xE, marker_f = 0xF;
4443+
phx_ptr_map_insert(&m, &marker_b, &marker_d);
4444+
phx_ptr_map_insert(&m, &marker_c, &marker_e);
4445+
phx_ptr_map_insert(&m, &marker_d, &marker_f);
4446+
assert(phx_ptr_map_size(&m) == 4 &&
4447+
"Phase 4.X-full Batch 95(d): size==4 after multi-insert");
4448+
assert(phx_ptr_map_lookup(&m, &marker_a) == &marker_c &&
4449+
phx_ptr_map_lookup(&m, &marker_b) == &marker_d &&
4450+
phx_ptr_map_lookup(&m, &marker_c) == &marker_e &&
4451+
phx_ptr_map_lookup(&m, &marker_d) == &marker_f &&
4452+
"Phase 4.X-full Batch 95(d): all 4 keys lookup correct values");
4453+
4454+
/* (e) contains-vs-NULL-value disambiguation: insert key with NULL
4455+
* value; contains returns 1, lookup returns NULL */
4456+
int marker_g = 0xC0DE;
4457+
phx_ptr_map_insert(&m, &marker_g, NULL);
4458+
assert(phx_ptr_map_contains(&m, &marker_g) == 1 &&
4459+
"Phase 4.X-full Batch 95(e): contains true for NULL-value key");
4460+
assert(phx_ptr_map_lookup(&m, &marker_g) == NULL &&
4461+
"Phase 4.X-full Batch 95(e): lookup returns NULL for NULL-value key");
4462+
int marker_z = 0xDEAD;
4463+
assert(phx_ptr_map_contains(&m, &marker_z) == 0 &&
4464+
"Phase 4.X-full Batch 95(e): contains false for absent key");
4465+
4466+
/* (f) iter raw slots: walk capacity collecting non-NULL keys */
4467+
size_t cap = phx_ptr_map_capacity(&m);
4468+
int seen_a = 0, seen_b = 0, seen_c = 0, seen_d = 0, seen_g = 0;
4469+
for (size_t i = 0; i < cap; i++) {
4470+
void *key = phx_ptr_map_at_key(&m, i);
4471+
if (key == &marker_a) seen_a = 1;
4472+
else if (key == &marker_b) seen_b = 1;
4473+
else if (key == &marker_c) seen_c = 1;
4474+
else if (key == &marker_d) seen_d = 1;
4475+
else if (key == &marker_g) seen_g = 1;
4476+
}
4477+
assert(seen_a && seen_b && seen_c && seen_d && seen_g &&
4478+
"Phase 4.X-full Batch 95(f): iter visits all 5 inserted keys");
4479+
4480+
/* (g) clear + reuse: count resets, lookup returns NULL; subsequent
4481+
* insert works without re-init; destroy resets all fields */
4482+
phx_ptr_map_clear(&m);
4483+
assert(phx_ptr_map_size(&m) == 0 &&
4484+
"Phase 4.X-full Batch 95(g): clear sets size 0");
4485+
assert(phx_ptr_map_lookup(&m, &marker_a) == NULL &&
4486+
"Phase 4.X-full Batch 95(g): post-clear lookup returns NULL");
4487+
phx_ptr_map_insert(&m, &marker_a, &marker_b);
4488+
assert(phx_ptr_map_lookup(&m, &marker_a) == &marker_b &&
4489+
"Phase 4.X-full Batch 95(g): post-clear insert+lookup works");
4490+
4491+
phx_ptr_map_destroy(&m);
4492+
assert(m.entries == NULL && m.capacity == 0 && m.count == 0 &&
4493+
"Phase 4.X-full Batch 95(g): destroy resets all fields");
4494+
}
4495+
43994496
__attribute__((constructor))
44004497
static void hir_instr_runtime_check() {
44014498
verify_hir_instr_read_through_cast();
@@ -4469,4 +4566,5 @@ static void hir_instr_runtime_check() {
44694566
verify_phase4x_full_batch91_ptr_queue();
44704567
verify_phase4x_full_batch92_ptr_set();
44714568
verify_phase4x_full_batch93_ptr_set_at();
4569+
verify_phase4x_full_batch95_ptr_map();
44724570
}

‎Python/jit/hir/phx_ptr_map.h‎

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
/* Copyright (c) Meta Platforms, Inc. and affiliates.
2+
*
3+
* PhxPtrMap — generic void*-keyed open-addressed hash map with void* values.
4+
* Phase 4.X-full X3a substrate per supervisor 04:04:50Z (Q-X3-1/Q-X3-2)
5+
* + theologian docs/tier7-phase4x-x3-preanalysis-2026-05-01.md §2 + §6.2.
6+
*
7+
* Use-class: hash-based key→value map for pointer keys + pointer values
8+
* (e.g. Environment ThreadedRef-keyed sets where value carries auxiliary
9+
* data; BlockCanonicalizer copy-tracking maps). X3b/X3c migrations
10+
* consume for E-1+E-2+E-3 + E-6 discharge.
11+
*
12+
* Implementation: power-of-2 capacity, linear probing, load factor 0.7
13+
* triggers resize (double). Hash: Knuth multiplicative on uintptr_t cast
14+
* of key. Empty slot sentinel: key==NULL.
15+
*
16+
* Layout: parallel keys[] + values[] entries packed as PhxPtrMapEntry
17+
* (key, value) for cache-friendly access. Mirrors PhxBlockMap pattern at
18+
* builder_state_c.h:124-128.
19+
*
20+
* NULL-key invariant: PhxPtrMap cannot store NULL as a key — NULL is
21+
* reserved as the empty-slot sentinel. NULL VALUE is permitted.
22+
*
23+
* Allocator: plain calloc/realloc/free (matches PhxPtrSet sibling
24+
* pattern at phx_ptr_set.h). Forward stance #4 discharged via
25+
* JIT_CHECK_C(new_entries != NULL) loud-fail post-allocation per
26+
* X1a 0bac3bc31d precedent.
27+
*
28+
* Q-X3-2 (supervisor 04:04:50Z): void*-VALUE only per YAGNI bound.
29+
* Specialize at E-9 if typed-value variant needed.
30+
*
31+
* Q-X3-5 (supervisor 04:04:50Z): tagged as E-9 forward-prep audit
32+
* trail. Substrate is independently consumed by X3b/X3c (E-1/E-2/E-3/E-6
33+
* discharge); no speculative E-9 surface added.
34+
*/
35+
#pragma once
36+
37+
#include <stddef.h>
38+
#include <stdint.h>
39+
#include <stdlib.h>
40+
#include <string.h>
41+
42+
#include "cinderx/Common/jit_log_c.h" /* JIT_CHECK_C loud-fail on alloc OOM */
43+
44+
#ifdef __cplusplus
45+
extern "C" {
46+
#endif
47+
48+
typedef struct PhxPtrMapEntry {
49+
void *key; /* NULL = empty slot sentinel */
50+
void *value; /* NULL value PERMITTED (key absence checked via key==NULL) */
51+
} PhxPtrMapEntry;
52+
53+
typedef struct PhxPtrMap {
54+
PhxPtrMapEntry *entries;
55+
size_t count;
56+
size_t capacity; /* power-of-2; 0 ⇒ entries NULL (lazy init) */
57+
} PhxPtrMap;
58+
59+
#define PHX_PTR_MAP_INITIAL_CAP 16u
60+
#define PHX_PTR_MAP_LOAD_NUM 7u
61+
#define PHX_PTR_MAP_LOAD_DEN 10u
62+
63+
static inline void phx_ptr_map_init(PhxPtrMap *m) {
64+
m->entries = NULL;
65+
m->count = 0;
66+
m->capacity = 0;
67+
}
68+
69+
static inline void phx_ptr_map_destroy(PhxPtrMap *m) {
70+
if (m->entries) {
71+
free(m->entries);
72+
m->entries = NULL;
73+
}
74+
m->count = 0;
75+
m->capacity = 0;
76+
}
77+
78+
static inline void phx_ptr_map_clear(PhxPtrMap *m) {
79+
if (m->entries && m->capacity) {
80+
memset(m->entries, 0, m->capacity * sizeof(PhxPtrMapEntry));
81+
}
82+
m->count = 0;
83+
}
84+
85+
/* Knuth multiplicative hash on uintptr_t, mask to power-of-2 capacity. */
86+
static inline size_t phx_ptr_map_slot(size_t cap, const void *key) {
87+
uint64_t k = (uint64_t)(uintptr_t)key;
88+
uint32_t h = (uint32_t)((k * 11400714819323198485ULL) >> 32);
89+
return (size_t)h & (cap - 1u);
90+
}
91+
92+
/* Returns value associated with key, or NULL if not present. Note: NULL
93+
* value is also a valid stored value, so callers needing absence-vs-NULL
94+
* disambiguation must use phx_ptr_map_contains. */
95+
static inline void *phx_ptr_map_lookup(const PhxPtrMap *m, const void *key) {
96+
if (m->capacity == 0 || m->entries == NULL) return NULL;
97+
size_t i = phx_ptr_map_slot(m->capacity, key);
98+
while (m->entries[i].key != NULL) {
99+
if (m->entries[i].key == key) return m->entries[i].value;
100+
i = (i + 1u) & (m->capacity - 1u);
101+
}
102+
return NULL;
103+
}
104+
105+
static inline int phx_ptr_map_contains(const PhxPtrMap *m, const void *key) {
106+
if (m->capacity == 0 || m->entries == NULL) return 0;
107+
size_t i = phx_ptr_map_slot(m->capacity, key);
108+
while (m->entries[i].key != NULL) {
109+
if (m->entries[i].key == key) return 1;
110+
i = (i + 1u) & (m->capacity - 1u);
111+
}
112+
return 0;
113+
}
114+
115+
/* Insert raw (assumes capacity > 0 and load not exceeded). Returns 1 if
116+
* newly inserted, 0 if updated (overwrite). */
117+
static inline int phx_ptr_map_insert_raw(
118+
PhxPtrMap *m, void *key, void *value) {
119+
size_t i = phx_ptr_map_slot(m->capacity, key);
120+
while (m->entries[i].key != NULL) {
121+
if (m->entries[i].key == key) {
122+
m->entries[i].value = value; /* overwrite */
123+
return 0;
124+
}
125+
i = (i + 1u) & (m->capacity - 1u);
126+
}
127+
m->entries[i].key = key;
128+
m->entries[i].value = value;
129+
m->count++;
130+
return 1;
131+
}
132+
133+
static inline void phx_ptr_map_resize(PhxPtrMap *m, size_t new_cap) {
134+
PhxPtrMapEntry *old_entries = m->entries;
135+
size_t old_cap = m->capacity;
136+
PhxPtrMapEntry *new_entries =
137+
(PhxPtrMapEntry *)calloc(new_cap, sizeof(PhxPtrMapEntry));
138+
JIT_CHECK_C(new_entries != NULL,
139+
"phx_ptr_map_resize calloc failed (new_cap=%zu)", new_cap);
140+
m->entries = new_entries;
141+
m->capacity = new_cap;
142+
m->count = 0;
143+
if (old_entries) {
144+
for (size_t i = 0; i < old_cap; i++) {
145+
if (old_entries[i].key != NULL) {
146+
phx_ptr_map_insert_raw(
147+
m, old_entries[i].key, old_entries[i].value);
148+
}
149+
}
150+
free(old_entries);
151+
}
152+
}
153+
154+
/* Returns 1 if newly inserted, 0 if updated (overwrite). */
155+
static inline int phx_ptr_map_insert(PhxPtrMap *m, void *key, void *value) {
156+
if (m->capacity == 0) {
157+
phx_ptr_map_resize(m, PHX_PTR_MAP_INITIAL_CAP);
158+
} else if ((m->count + 1u) * PHX_PTR_MAP_LOAD_DEN
159+
> m->capacity * PHX_PTR_MAP_LOAD_NUM) {
160+
phx_ptr_map_resize(m, m->capacity * 2u);
161+
}
162+
return phx_ptr_map_insert_raw(m, key, value);
163+
}
164+
165+
static inline size_t phx_ptr_map_size(const PhxPtrMap *m) {
166+
return m->count;
167+
}
168+
169+
static inline size_t phx_ptr_map_capacity(const PhxPtrMap *m) {
170+
return m->capacity;
171+
}
172+
173+
/* Raw slot accessors for open-address-hash iteration. Caller iterates
174+
* 0..capacity-1 and skips slots where key==NULL:
175+
*
176+
* for (size_t i = 0; i < phx_ptr_map_capacity(&m); i++) {
177+
* void *key = phx_ptr_map_at_key(&m, i);
178+
* if (key != NULL) { use(key, phx_ptr_map_at_value(&m, i)); }
179+
* }
180+
*/
181+
static inline void *phx_ptr_map_at_key(const PhxPtrMap *m, size_t slot_idx) {
182+
if (m->entries == NULL || slot_idx >= m->capacity) return NULL;
183+
return m->entries[slot_idx].key;
184+
}
185+
186+
static inline void *phx_ptr_map_at_value(const PhxPtrMap *m, size_t slot_idx) {
187+
if (m->entries == NULL || slot_idx >= m->capacity) return NULL;
188+
return m->entries[slot_idx].value;
189+
}
190+
191+
#ifdef __cplusplus
192+
} /* extern "C" */
193+
#endif
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/* Copyright (c) Meta Platforms, Inc. and affiliates.
2+
*
3+
* phx_threaded_ref bridge implementation. Phase 4.X-full X3a per
4+
* supervisor 04:04:50Z (Q-X3-3) + theologian §6.3.
5+
*
6+
* Thin extern "C" wrappers over jit::ThreadedRef<>::incref/decref
7+
* (threaded_compile.h:307/337). NULL + immortal-object short-circuits
8+
* inherited from the C++ implementation.
9+
*/
10+
#include "cinderx/Jit/hir/phx_threaded_ref.h"
11+
#include "cinderx/Jit/threaded_compile.h"
12+
13+
extern "C" void phx_threaded_incref(PyObject *obj) {
14+
jit::ThreadedRef<>::incref(obj);
15+
}
16+
17+
extern "C" void phx_threaded_decref(PyObject *obj) {
18+
jit::ThreadedRef<>::decref(obj);
19+
}

‎Python/jit/hir/phx_threaded_ref.h‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/* Copyright (c) Meta Platforms, Inc. and affiliates.
2+
*
3+
* phx_threaded_incref / phx_threaded_decref — extern "C" bridges over
4+
* jit::ThreadedRef<>::incref/decref (threaded_compile.h:307/337). Phase
5+
* 4.X-full X3a per supervisor 04:04:50Z (Q-X3-3) + theologian §6.3.
6+
*
7+
* Use-class: C-side substrate consumers (PhxPtrSet/PhxPtrMap holding
8+
* Python object pointers) need GIL-correct incref/decref operations.
9+
* The C++ ThreadedRef class handles GIL-disabled atomic-counter path
10+
* + standard Py_INCREF/DECREF; bridges expose this to C without
11+
* forcing C consumers to depend on Python.h or threaded_compile.h.
12+
*
13+
* Both functions are NULL-safe and immortal-safe (mirror ThreadedRef::
14+
* incref/decref short-circuits at threaded_compile.h:308/338).
15+
*
16+
* Implementation: phx_threaded_ref.cpp (extern "C" wrappers around
17+
* static C++ template methods). Adds C/C++ seam similar to existing
18+
* extern "C" hir_builder_emit_*_c bridges in builder.cpp.
19+
*
20+
* Q-X3-3 (supervisor 04:04:50Z): thin extern "C" approach;
21+
* ThreadedCompileContext stays C++ (Phoenix concurrency-infra class,
22+
* out of stay-C++ exception inventory).
23+
*/
24+
#pragma once
25+
26+
#include "Python.h" /* PyObject opaque type for C ABI */
27+
28+
#ifdef __cplusplus
29+
extern "C" {
30+
#endif
31+
32+
/* GIL-correct refcount ops mirroring ThreadedRef::incref/decref. NULL +
33+
* immortal-object short-circuit per ThreadedRef contract. */
34+
void phx_threaded_incref(PyObject *obj);
35+
void phx_threaded_decref(PyObject *obj);
36+
37+
#ifdef __cplusplus
38+
} /* extern "C" */
39+
#endif

0 commit comments

Comments
 (0)