webassembly: Improve identity and fix bug with lost JsProxy refs.

Commit ffa98cb014 improved equality for
`JsProxy` objects so that, eg, `js.Object == js.Object` is true.

As mentioned in #17758, a further optimisation is to make identity work in
that case, eg `js.Object is js.Object` should be true (on the Python side).

This commit implements that, by keeping track of all `JsProxy` Python
objects and reusing them where possible: where the underlying JS ref is
equal, ie they point to the same JS object.  That reduces memory churn and
gives better identity behaviour of JS objects proxied over to Python.

As part of this, a bug is fixed where JS objects can be freed while there's
still a `JsProxy` referring to that JS object.  A test is added for that
exact scenario, and the test now passes.

Signed-off-by: Damien George <damien@micropython.org>
This commit is contained in:
Damien George
2025-09-01 13:56:58 +10:00
parent 9eddbb32f3
commit a319ddefa6
9 changed files with 155 additions and 52 deletions

View File

@@ -1,27 +1,27 @@
135241328
135241296
135241264
135241232
135241184
135241136
135241056
135240912
135240608
135240080
135239040
135236976
135232864
135224656
135208256
135175472
135109856
134978768
134716608
135216752
136217120
138217808
142219264
150222192
135241312
135241280
135241248
135241216
135241168
135241120
135241040
135240896
135240592
135240064
135239024
135236960
135232848
135224640
135208240
135175456
135109840
134978752
134716592
135216800
136217168
138217984
142219568
150222816
1
2
4

View File

@@ -11,8 +11,15 @@ print("Object equality")
print(js.Object == js.Object)
print(js.Object.assign == js.Object.assign)
print("Object identity")
print(js.Object is js.Object)
print("Array equality")
print(js.Array == js.Array)
print(js.Array.prototype == js.Array.prototype)
print(js.Array.prototype.push == js.Array.prototype.push)
print("Array identity")
print(js.Array is js.Array)
print(js.Array.prototype is js.Array.prototype)
`);

View File

@@ -2,7 +2,12 @@
Object equality
True
True
Object identity
True
Array equality
True
True
True
Array identity
True
True

View File

@@ -0,0 +1,48 @@
// Test reuse of JsProxy references and freeing of JsProxy objects.
// This ensures that a Python-side JsProxy that refers to a JavaScript object retains
// the correct JavaScript object in the case that another JsProxy that refers to the
// same JavaScript object is freed.
const mp = await (await import(process.argv[2])).loadMicroPython();
globalThis.obj = [1, 2];
globalThis.obj2 = [3, 4];
console.log("JS obj:", globalThis.obj);
mp.runPython(`
import gc
import js
# Create 2 proxies of the same JS object.
# They should refer to the same underlying JS-side reference.
obj = js.obj
obj_copy = js.obj
print(obj, obj_copy, obj == obj_copy)
# Print out the object.
js.console.log("Py obj:", obj)
# Forget obj_copy and trigger a GC when the Python code finishes.
obj_copy = None
gc.collect()
`);
console.log("JS obj:", globalThis.obj);
mp.runPython(`
# Create a new proxy of a different object.
# It should not clobber the existing obj proxy reference.
obj2 = js.obj2
# Create a copy of the existing obj proxy.
obj_copy = js.obj
# Print the JS proxy, it should be the same reference as before.
print(obj, obj_copy, obj == obj_copy)
# Print out the object.
js.console.log("Py obj:", obj)
`);
console.log("JS obj:", globalThis.obj);

View File

@@ -0,0 +1,7 @@
JS obj: [ 1, 2 ]
<JsProxy 2> <JsProxy 2> True
Py obj: [ 1, 2 ]
JS obj: [ 1, 2 ]
<JsProxy 2> <JsProxy 2> True
Py obj: [ 1, 2 ]
JS obj: [ 1, 2 ]