Skip to content

Commit ffa98cb

Browse files
committed
webassembly/proxy_js: Reuse JsProxy ref if object matches.
This reduces memory use by reusing objects, and improves identity/equality relationships of JavaScript objects on the Python side. In 77bd8fe PyProxy's were reused when the same Python object was proxied across to JavaScript. This commit does the same thing but for JsProxy's going from JS to Python. If an existing JsProxy reference exists for the JS object about to be proxied across, then it's reused. This helps reduce the number of alive objects (memory use), and, more importantly, improves equality relationships of JavaScript objects on the Python side. Eg we now get, on the Python side: import js print(js.Object == js.Object) that prints True. Previously it was False. Note that this change does not make identity work with `is`, for example `js.Object is js.Object` is actually False. With more work that could be made True but for now we leave that as-is. The behaviour with this commit matches Pyodide semantics. Signed-off-by: Damien George <[email protected]>
1 parent 813f0c1 commit ffa98cb

File tree

5 files changed

+32
-5
lines changed

5 files changed

+32
-5
lines changed

ports/webassembly/objjsproxy.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ static mp_obj_t jsproxy_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t r
285285

286286
EM_JS(void, proxy_js_free_obj, (int js_ref), {
287287
if (js_ref >= PROXY_JS_REF_NUM_STATIC) {
288+
proxy_js_ref_map.delete(proxy_js_ref[js_ref]);
288289
proxy_js_ref[js_ref] = undefined;
289290
if (js_ref < proxy_js_ref_next) {
290291
proxy_js_ref_next = js_ref;

ports/webassembly/proxy_js.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ class PythonError extends Error {
6262
function proxy_js_init() {
6363
globalThis.proxy_js_ref = [globalThis, undefined];
6464
globalThis.proxy_js_ref_next = PROXY_JS_REF_NUM_STATIC;
65+
globalThis.proxy_js_ref_map = new Map();
6566
globalThis.proxy_js_map = new Map();
6667
globalThis.proxy_js_existing = [undefined];
6768
globalThis.pyProxyFinalizationRegistry = new FinalizationRegistry(
@@ -95,15 +96,23 @@ function proxy_js_check_existing(c_ref) {
9596
return globalThis.proxy_js_existing.length - 1;
9697
}
9798

98-
// js_obj cannot be undefined
99+
// The `js_obj` argument cannot be `undefined`.
100+
// Returns an integer reference to the given `js_obj`.
99101
function proxy_js_add_obj(js_obj) {
102+
// See if there is an existing JsProxy reference, and use that if there is.
103+
const existing_ref = proxy_js_ref_map.get(js_obj);
104+
if (existing_ref !== undefined) {
105+
return existing_ref;
106+
}
107+
100108
// Search for the first free slot in proxy_js_ref.
101109
while (proxy_js_ref_next < proxy_js_ref.length) {
102110
if (proxy_js_ref[proxy_js_ref_next] === undefined) {
103111
// Free slot found, reuse it.
104112
const id = proxy_js_ref_next;
105113
++proxy_js_ref_next;
106114
proxy_js_ref[id] = js_obj;
115+
proxy_js_ref_map.set(js_obj, id);
107116
return id;
108117
}
109118
++proxy_js_ref_next;
@@ -113,6 +122,7 @@ function proxy_js_add_obj(js_obj) {
113122
const id = proxy_js_ref.length;
114123
proxy_js_ref[id] = js_obj;
115124
proxy_js_ref_next = proxy_js_ref.length;
125+
proxy_js_ref_map.set(js_obj, id);
116126
return id;
117127
}
118128

tests/ports/webassembly/py_proxy_identity.mjs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,13 @@ js.eventTarget.addEventListener("event", callback)
2323
js.eventTarget.dispatchEvent(js.event)
2424
js.eventTarget.removeEventListener("event", callback)
2525
js.eventTarget.dispatchEvent(js.event)
26+
27+
print("Object equality")
28+
print(js.Object == js.Object)
29+
print(js.Object.assign == js.Object.assign)
30+
31+
print("Array equality")
32+
print(js.Array == js.Array)
33+
print(js.Array.prototype == js.Array.prototype)
34+
print(js.Array.prototype.push == js.Array.prototype.push)
2635
`);
Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
11
PyProxy { _ref: 3 } PyProxy { _ref: 3 }
22
true
3-
callback <JsProxy 7>
3+
callback <JsProxy 5>
4+
Object equality
5+
True
6+
True
7+
Array equality
8+
True
9+
True
10+
True

tests/ports/webassembly/run_python_async.mjs.exp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@
22
1
33
<JsProxy 2>
44
py 1
5-
<JsProxy 5>
5+
<JsProxy 4>
66
py 2
77
2
88
resolved 123
99
3
1010
= TEST 2 ==========
1111
1
12-
<JsProxy 6>
12+
<JsProxy 5>
1313
py 1
14-
<JsProxy 9>
14+
<JsProxy 6>
1515
py 2
1616
2
1717
setTimeout resolved

0 commit comments

Comments
 (0)