Content-Length: 800701 | pFad | http://github.com/micropython/micropython/commit/77bd8fe5b80b0e7e02cdb6b4272c401ae3dca638

C6 webassembly: Reuse PyProxy objects when they are the same Python object. · micropython/micropython@77bd8fe · GitHub
Skip to content

Commit 77bd8fe

Browse files
committed
webassembly: Reuse PyProxy objects when they are the same Python object.
This commit makes it so that PyProxy objects are reused (on the JavaScript side) when they correspond to an existing Python object that is the same object. For example, proxying the same Python function to JavaScript, the same PyProxy instance is now used. This means that if `foo` is a Python function then accessing it on the JavaScript side such as `api.globals().get("foo")` has the property that: api.globals().get("foo") === api.globals().get("foo") Prior to this commit the above was not true because new PyProxy instances were created each time `foo` was accessed. Signed-off-by: Damien George <damien@micropython.org>
1 parent 5147dc5 commit 77bd8fe

File tree

5 files changed

+129
-27
lines changed

5 files changed

+129
-27
lines changed

Diff for: ports/webassembly/proxy_c.c

+47-6
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ enum {
4949
PROXY_KIND_MP_GENERATOR = 7,
5050
PROXY_KIND_MP_OBJECT = 8,
5151
PROXY_KIND_MP_JSPROXY = 9,
52+
PROXY_KIND_MP_EXISTING = 10,
5253
};
5354

5455
enum {
@@ -79,40 +80,76 @@ static size_t proxy_c_ref_next;
7980

8081
void proxy_c_init(void) {
8182
MP_STATE_PORT(proxy_c_ref) = mp_obj_new_list(0, NULL);
83+
MP_STATE_PORT(proxy_c_dict) = mp_obj_new_dict(0);
8284
mp_obj_list_append(MP_STATE_PORT(proxy_c_ref), MP_OBJ_NULL);
8385
proxy_c_ref_next = PROXY_C_REF_NUM_STATIC;
8486
}
8587

8688
MP_REGISTER_ROOT_POINTER(mp_obj_t proxy_c_ref);
89+
MP_REGISTER_ROOT_POINTER(mp_obj_t proxy_c_dict);
8790

8891
// obj cannot be MP_OBJ_NULL.
8992
static inline size_t proxy_c_add_obj(mp_obj_t obj) {
9093
// Search for the first free slot in proxy_c_ref.
94+
size_t id = 0;
9195
mp_obj_list_t *l = (mp_obj_list_t *)MP_OBJ_TO_PTR(MP_STATE_PORT(proxy_c_ref));
9296
while (proxy_c_ref_next < l->len) {
9397
if (l->items[proxy_c_ref_next] == MP_OBJ_NULL) {
9498
// Free slot found, reuse it.
95-
size_t id = proxy_c_ref_next;
99+
id = proxy_c_ref_next;
96100
++proxy_c_ref_next;
97101
l->items[id] = obj;
98-
return id;
102+
break;
99103
}
100104
++proxy_c_ref_next;
101105
}
102106

103-
// No free slots, so grow proxy_c_ref by one (append at the end of the list).
104-
size_t id = l->len;
105-
mp_obj_list_append(MP_STATE_PORT(proxy_c_ref), obj);
106-
proxy_c_ref_next = l->len;
107+
if (id == 0) {
108+
// No free slots, so grow proxy_c_ref by one (append at the end of the list).
109+
id = l->len;
110+
mp_obj_list_append(MP_STATE_PORT(proxy_c_ref), obj);
111+
proxy_c_ref_next = l->len;
112+
}
113+
114+
// Add the object to proxy_c_dict, keyed by the object pointer, with value the object id.
115+
mp_obj_t obj_key = mp_obj_new_int_from_uint((uintptr_t)obj);
116+
mp_map_elem_t *elem = mp_map_lookup(mp_obj_dict_get_map(MP_STATE_PORT(proxy_c_dict)), obj_key, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND);
117+
elem->value = mp_obj_new_int_from_uint(id);
118+
107119
return id;
108120
}
109121

122+
EM_JS(int, js_check_existing, (int c_ref), {
123+
return proxy_js_check_existing(c_ref);
124+
});
125+
126+
// obj cannot be MP_OBJ_NULL.
127+
static inline int proxy_c_check_existing(mp_obj_t obj) {
128+
mp_obj_t obj_key = mp_obj_new_int_from_uint((uintptr_t)obj);
129+
mp_map_elem_t *elem = mp_map_lookup(mp_obj_dict_get_map(MP_STATE_PORT(proxy_c_dict)), obj_key, MP_MAP_LOOKUP);
130+
if (elem == NULL) {
131+
return -1;
132+
}
133+
uint32_t c_ref = mp_obj_int_get_truncated(elem->value);
134+
return js_check_existing(c_ref);
135+
}
136+
110137
static inline mp_obj_t proxy_c_get_obj(uint32_t c_ref) {
111138
return ((mp_obj_list_t *)MP_OBJ_TO_PTR(MP_STATE_PORT(proxy_c_ref)))->items[c_ref];
112139
}
113140

114141
void proxy_c_free_obj(uint32_t c_ref) {
115142
if (c_ref >= PROXY_C_REF_NUM_STATIC) {
143+
// Remove the object from proxy_c_dict if the c_ref in that dict corresponds to this object.
144+
// (It may be that this object exists in the dict but with a different c_ref from a more
145+
// recent proxy of this object.)
146+
mp_obj_t obj_key = mp_obj_new_int_from_uint((uintptr_t)proxy_c_get_obj(c_ref));
147+
mp_map_elem_t *elem = mp_map_lookup(mp_obj_dict_get_map(MP_STATE_PORT(proxy_c_dict)), obj_key, MP_MAP_LOOKUP);
148+
if (elem != NULL && mp_obj_int_get_truncated(elem->value) == c_ref) {
149+
mp_map_lookup(mp_obj_dict_get_map(MP_STATE_PORT(proxy_c_dict)), obj_key, MP_MAP_LOOKUP_REMOVE_IF_FOUND);
150+
}
151+
152+
// Clear the slot in proxy_c_ref used by this object, so the GC can reclaim the object.
116153
((mp_obj_list_t *)MP_OBJ_TO_PTR(MP_STATE_PORT(proxy_c_ref)))->items[c_ref] = MP_OBJ_NULL;
117154
proxy_c_ref_next = MIN(proxy_c_ref_next, c_ref);
118155
}
@@ -143,6 +180,7 @@ mp_obj_t proxy_convert_js_to_mp_obj_cside(uint32_t *value) {
143180

144181
void proxy_convert_mp_to_js_obj_cside(mp_obj_t obj, uint32_t *out) {
145182
uint32_t kind;
183+
int js_ref;
146184
if (obj == MP_OBJ_NULL) {
147185
kind = PROXY_KIND_MP_NULL;
148186
} else if (obj == mp_const_none) {
@@ -168,6 +206,9 @@ void proxy_convert_mp_to_js_obj_cside(mp_obj_t obj, uint32_t *out) {
168206
} else if (mp_obj_is_jsproxy(obj)) {
169207
kind = PROXY_KIND_MP_JSPROXY;
170208
out[1] = mp_obj_jsproxy_get_ref(obj);
209+
} else if ((js_ref = proxy_c_check_existing(obj)) >= 0) {
210+
kind = PROXY_KIND_MP_EXISTING;
211+
out[1] = js_ref;
171212
} else if (mp_obj_get_type(obj) == &mp_type_JsException) {
172213
mp_obj_exception_t *exc = MP_OBJ_TO_PTR(obj);
173214
if (exc->args->len > 0 && mp_obj_is_jsproxy(exc->args->items[0])) {

Diff for: ports/webassembly/proxy_js.js

+32
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ const PROXY_KIND_MP_CALLABLE = 6;
4040
const PROXY_KIND_MP_GENERATOR = 7;
4141
const PROXY_KIND_MP_OBJECT = 8;
4242
const PROXY_KIND_MP_JSPROXY = 9;
43+
const PROXY_KIND_MP_EXISTING = 10;
4344

4445
const PROXY_KIND_JS_UNDEFINED = 0;
4546
const PROXY_KIND_JS_NULL = 1;
@@ -61,13 +62,39 @@ class PythonError extends Error {
6162
function proxy_js_init() {
6263
globalThis.proxy_js_ref = [globalThis, undefined];
6364
globalThis.proxy_js_ref_next = PROXY_JS_REF_NUM_STATIC;
65+
globalThis.proxy_js_map = new Map();
66+
globalThis.proxy_js_existing = [undefined];
6467
globalThis.pyProxyFinalizationRegistry = new FinalizationRegistry(
6568
(cRef) => {
69+
globalThis.proxy_js_map.delete(cRef);
6670
Module.ccall("proxy_c_free_obj", "null", ["number"], [cRef]);
6771
},
6872
);
6973
}
7074

75+
// Check if the c_ref (Python proxy index) has a corresponding JavaScript-side PyProxy
76+
// associated with it. If so, take a concrete reference to this PyProxy from the WeakRef
77+
// and put it in proxy_js_existing, to be referenced and reused by PROXY_KIND_MP_EXISTING.
78+
function proxy_js_check_existing(c_ref) {
79+
const existing_obj = globalThis.proxy_js_map.get(c_ref)?.deref();
80+
if (existing_obj === undefined) {
81+
return -1;
82+
}
83+
84+
// Search for a free slot in proxy_js_existing.
85+
for (let i = 0; i < globalThis.proxy_js_existing.length; ++i) {
86+
if (globalThis.proxy_js_existing[i] === undefined) {
87+
// Free slot found, put existing_obj here and return the index.
88+
globalThis.proxy_js_existing[i] = existing_obj;
89+
return i;
90+
}
91+
}
92+
93+
// No free slot, so append to proxy_js_existing and return the new index.
94+
globalThis.proxy_js_existing.push(existing_obj);
95+
return globalThis.proxy_js_existing.length - 1;
96+
}
97+
7198
// js_obj cannot be undefined
7299
function proxy_js_add_obj(js_obj) {
73100
// Search for the first free slot in proxy_js_ref.
@@ -241,6 +268,10 @@ function proxy_convert_mp_to_js_obj_jsside(value) {
241268
// js proxy
242269
const id = Module.getValue(value + 4, "i32");
243270
obj = proxy_js_ref[id];
271+
} else if (kind === PROXY_KIND_MP_EXISTING) {
272+
const id = Module.getValue(value + 4, "i32");
273+
obj = globalThis.proxy_js_existing[id];
274+
globalThis.proxy_js_existing[id] = undefined;
244275
} else {
245276
// obj
246277
const id = Module.getValue(value + 4, "i32");
@@ -257,6 +288,7 @@ function proxy_convert_mp_to_js_obj_jsside(value) {
257288
obj = new Proxy(target, py_proxy_handler);
258289
}
259290
globalThis.pyProxyFinalizationRegistry.register(obj, id);
291+
globalThis.proxy_js_map.set(id, new WeakRef(obj));
260292
}
261293
return obj;
262294
}

Diff for: tests/ports/webassembly/heap_expand.mjs.exp

+21-21
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
1-
135241360
21
135241328
32
135241296
43
135241264
5-
135241216
6-
135241168
7-
135241088
8-
135240944
9-
135240640
10-
135240112
11-
135239072
12-
135237008
13-
135232896
14-
135224688
15-
135208288
16-
135175504
17-
135109888
18-
134978800
19-
134716640
20-
135216784
21-
136217152
22-
138217840
23-
142219296
24-
150222224
4+
135241232
5+
135241184
6+
135241136
7+
135241056
8+
135240912
9+
135240608
10+
135240080
11+
135239040
12+
135236976
13+
135232864
14+
135224656
15+
135208256
16+
135175472
17+
135109856
18+
134978768
19+
134716608
20+
135216752
21+
136217120
22+
138217808
23+
142219264
24+
150222192
2525
1
2626
2
2727
4

Diff for: tests/ports/webassembly/py_proxy_identity.mjs

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Test identity of PyProxy when they are the same Python object.
2+
3+
const mp = await (await import(process.argv[2])).loadMicroPython();
4+
5+
mp.runPython(`
6+
l = []
7+
`);
8+
9+
const l1 = mp.globals.get("l");
10+
const l2 = mp.globals.get("l");
11+
console.log(l1, l2);
12+
console.log(l1 === l2);
13+
14+
globalThis.eventTarget = new EventTarget();
15+
globalThis.event = new Event("event");
16+
17+
mp.runPython(`
18+
import js
19+
20+
def callback(ev):
21+
print("callback", ev)
22+
js.eventTarget.addEventListener("event", callback)
23+
js.eventTarget.dispatchEvent(js.event)
24+
js.eventTarget.removeEventListener("event", callback)
25+
js.eventTarget.dispatchEvent(js.event)
26+
`);

Diff for: tests/ports/webassembly/py_proxy_identity.mjs.exp

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
PyProxy { _ref: 3 } PyProxy { _ref: 3 }
2+
true
3+
callback <JsProxy 7>

0 commit comments

Comments
 (0)








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: http://github.com/micropython/micropython/commit/77bd8fe5b80b0e7e02cdb6b4272c401ae3dca638

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy