summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ports/webassembly/proxy_c.c53
-rw-r--r--ports/webassembly/proxy_js.js32
-rw-r--r--tests/ports/webassembly/heap_expand.mjs.exp42
-rw-r--r--tests/ports/webassembly/py_proxy_identity.mjs26
-rw-r--r--tests/ports/webassembly/py_proxy_identity.mjs.exp3
5 files changed, 129 insertions, 27 deletions
diff --git a/ports/webassembly/proxy_c.c b/ports/webassembly/proxy_c.c
index a8c444faa..00abc43bf 100644
--- a/ports/webassembly/proxy_c.c
+++ b/ports/webassembly/proxy_c.c
@@ -49,6 +49,7 @@ enum {
PROXY_KIND_MP_GENERATOR = 7,
PROXY_KIND_MP_OBJECT = 8,
PROXY_KIND_MP_JSPROXY = 9,
+ PROXY_KIND_MP_EXISTING = 10,
};
enum {
@@ -79,40 +80,76 @@ static size_t proxy_c_ref_next;
void proxy_c_init(void) {
MP_STATE_PORT(proxy_c_ref) = mp_obj_new_list(0, NULL);
+ MP_STATE_PORT(proxy_c_dict) = mp_obj_new_dict(0);
mp_obj_list_append(MP_STATE_PORT(proxy_c_ref), MP_OBJ_NULL);
proxy_c_ref_next = PROXY_C_REF_NUM_STATIC;
}
MP_REGISTER_ROOT_POINTER(mp_obj_t proxy_c_ref);
+MP_REGISTER_ROOT_POINTER(mp_obj_t proxy_c_dict);
// obj cannot be MP_OBJ_NULL.
static inline size_t proxy_c_add_obj(mp_obj_t obj) {
// Search for the first free slot in proxy_c_ref.
+ size_t id = 0;
mp_obj_list_t *l = (mp_obj_list_t *)MP_OBJ_TO_PTR(MP_STATE_PORT(proxy_c_ref));
while (proxy_c_ref_next < l->len) {
if (l->items[proxy_c_ref_next] == MP_OBJ_NULL) {
// Free slot found, reuse it.
- size_t id = proxy_c_ref_next;
+ id = proxy_c_ref_next;
++proxy_c_ref_next;
l->items[id] = obj;
- return id;
+ break;
}
++proxy_c_ref_next;
}
- // No free slots, so grow proxy_c_ref by one (append at the end of the list).
- size_t id = l->len;
- mp_obj_list_append(MP_STATE_PORT(proxy_c_ref), obj);
- proxy_c_ref_next = l->len;
+ if (id == 0) {
+ // No free slots, so grow proxy_c_ref by one (append at the end of the list).
+ id = l->len;
+ mp_obj_list_append(MP_STATE_PORT(proxy_c_ref), obj);
+ proxy_c_ref_next = l->len;
+ }
+
+ // Add the object to proxy_c_dict, keyed by the object pointer, with value the object id.
+ mp_obj_t obj_key = mp_obj_new_int_from_uint((uintptr_t)obj);
+ 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);
+ elem->value = mp_obj_new_int_from_uint(id);
+
return id;
}
+EM_JS(int, js_check_existing, (int c_ref), {
+ return proxy_js_check_existing(c_ref);
+});
+
+// obj cannot be MP_OBJ_NULL.
+static inline int proxy_c_check_existing(mp_obj_t obj) {
+ mp_obj_t obj_key = mp_obj_new_int_from_uint((uintptr_t)obj);
+ mp_map_elem_t *elem = mp_map_lookup(mp_obj_dict_get_map(MP_STATE_PORT(proxy_c_dict)), obj_key, MP_MAP_LOOKUP);
+ if (elem == NULL) {
+ return -1;
+ }
+ uint32_t c_ref = mp_obj_int_get_truncated(elem->value);
+ return js_check_existing(c_ref);
+}
+
static inline mp_obj_t proxy_c_get_obj(uint32_t c_ref) {
return ((mp_obj_list_t *)MP_OBJ_TO_PTR(MP_STATE_PORT(proxy_c_ref)))->items[c_ref];
}
void proxy_c_free_obj(uint32_t c_ref) {
if (c_ref >= PROXY_C_REF_NUM_STATIC) {
+ // Remove the object from proxy_c_dict if the c_ref in that dict corresponds to this object.
+ // (It may be that this object exists in the dict but with a different c_ref from a more
+ // recent proxy of this object.)
+ mp_obj_t obj_key = mp_obj_new_int_from_uint((uintptr_t)proxy_c_get_obj(c_ref));
+ mp_map_elem_t *elem = mp_map_lookup(mp_obj_dict_get_map(MP_STATE_PORT(proxy_c_dict)), obj_key, MP_MAP_LOOKUP);
+ if (elem != NULL && mp_obj_int_get_truncated(elem->value) == c_ref) {
+ mp_map_lookup(mp_obj_dict_get_map(MP_STATE_PORT(proxy_c_dict)), obj_key, MP_MAP_LOOKUP_REMOVE_IF_FOUND);
+ }
+
+ // Clear the slot in proxy_c_ref used by this object, so the GC can reclaim the object.
((mp_obj_list_t *)MP_OBJ_TO_PTR(MP_STATE_PORT(proxy_c_ref)))->items[c_ref] = MP_OBJ_NULL;
proxy_c_ref_next = MIN(proxy_c_ref_next, c_ref);
}
@@ -143,6 +180,7 @@ mp_obj_t proxy_convert_js_to_mp_obj_cside(uint32_t *value) {
void proxy_convert_mp_to_js_obj_cside(mp_obj_t obj, uint32_t *out) {
uint32_t kind;
+ int js_ref;
if (obj == MP_OBJ_NULL) {
kind = PROXY_KIND_MP_NULL;
} 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) {
} else if (mp_obj_is_jsproxy(obj)) {
kind = PROXY_KIND_MP_JSPROXY;
out[1] = mp_obj_jsproxy_get_ref(obj);
+ } else if ((js_ref = proxy_c_check_existing(obj)) >= 0) {
+ kind = PROXY_KIND_MP_EXISTING;
+ out[1] = js_ref;
} else if (mp_obj_get_type(obj) == &mp_type_JsException) {
mp_obj_exception_t *exc = MP_OBJ_TO_PTR(obj);
if (exc->args->len > 0 && mp_obj_is_jsproxy(exc->args->items[0])) {
diff --git a/ports/webassembly/proxy_js.js b/ports/webassembly/proxy_js.js
index fe92b5725..9e7c233e3 100644
--- a/ports/webassembly/proxy_js.js
+++ b/ports/webassembly/proxy_js.js
@@ -40,6 +40,7 @@ const PROXY_KIND_MP_CALLABLE = 6;
const PROXY_KIND_MP_GENERATOR = 7;
const PROXY_KIND_MP_OBJECT = 8;
const PROXY_KIND_MP_JSPROXY = 9;
+const PROXY_KIND_MP_EXISTING = 10;
const PROXY_KIND_JS_UNDEFINED = 0;
const PROXY_KIND_JS_NULL = 1;
@@ -61,13 +62,39 @@ class PythonError extends Error {
function proxy_js_init() {
globalThis.proxy_js_ref = [globalThis, undefined];
globalThis.proxy_js_ref_next = PROXY_JS_REF_NUM_STATIC;
+ globalThis.proxy_js_map = new Map();
+ globalThis.proxy_js_existing = [undefined];
globalThis.pyProxyFinalizationRegistry = new FinalizationRegistry(
(cRef) => {
+ globalThis.proxy_js_map.delete(cRef);
Module.ccall("proxy_c_free_obj", "null", ["number"], [cRef]);
},
);
}
+// Check if the c_ref (Python proxy index) has a corresponding JavaScript-side PyProxy
+// associated with it. If so, take a concrete reference to this PyProxy from the WeakRef
+// and put it in proxy_js_existing, to be referenced and reused by PROXY_KIND_MP_EXISTING.
+function proxy_js_check_existing(c_ref) {
+ const existing_obj = globalThis.proxy_js_map.get(c_ref)?.deref();
+ if (existing_obj === undefined) {
+ return -1;
+ }
+
+ // Search for a free slot in proxy_js_existing.
+ for (let i = 0; i < globalThis.proxy_js_existing.length; ++i) {
+ if (globalThis.proxy_js_existing[i] === undefined) {
+ // Free slot found, put existing_obj here and return the index.
+ globalThis.proxy_js_existing[i] = existing_obj;
+ return i;
+ }
+ }
+
+ // No free slot, so append to proxy_js_existing and return the new index.
+ globalThis.proxy_js_existing.push(existing_obj);
+ return globalThis.proxy_js_existing.length - 1;
+}
+
// js_obj cannot be undefined
function proxy_js_add_obj(js_obj) {
// Search for the first free slot in proxy_js_ref.
@@ -241,6 +268,10 @@ function proxy_convert_mp_to_js_obj_jsside(value) {
// js proxy
const id = Module.getValue(value + 4, "i32");
obj = proxy_js_ref[id];
+ } else if (kind === PROXY_KIND_MP_EXISTING) {
+ const id = Module.getValue(value + 4, "i32");
+ obj = globalThis.proxy_js_existing[id];
+ globalThis.proxy_js_existing[id] = undefined;
} else {
// obj
const id = Module.getValue(value + 4, "i32");
@@ -257,6 +288,7 @@ function proxy_convert_mp_to_js_obj_jsside(value) {
obj = new Proxy(target, py_proxy_handler);
}
globalThis.pyProxyFinalizationRegistry.register(obj, id);
+ globalThis.proxy_js_map.set(id, new WeakRef(obj));
}
return obj;
}
diff --git a/tests/ports/webassembly/heap_expand.mjs.exp b/tests/ports/webassembly/heap_expand.mjs.exp
index 5efa8567f..563413514 100644
--- a/tests/ports/webassembly/heap_expand.mjs.exp
+++ b/tests/ports/webassembly/heap_expand.mjs.exp
@@ -1,27 +1,27 @@
-135241360
135241328
135241296
135241264
-135241216
-135241168
-135241088
-135240944
-135240640
-135240112
-135239072
-135237008
-135232896
-135224688
-135208288
-135175504
-135109888
-134978800
-134716640
-135216784
-136217152
-138217840
-142219296
-150222224
+135241232
+135241184
+135241136
+135241056
+135240912
+135240608
+135240080
+135239040
+135236976
+135232864
+135224656
+135208256
+135175472
+135109856
+134978768
+134716608
+135216752
+136217120
+138217808
+142219264
+150222192
1
2
4
diff --git a/tests/ports/webassembly/py_proxy_identity.mjs b/tests/ports/webassembly/py_proxy_identity.mjs
new file mode 100644
index 000000000..d4a720b73
--- /dev/null
+++ b/tests/ports/webassembly/py_proxy_identity.mjs
@@ -0,0 +1,26 @@
+// Test identity of PyProxy when they are the same Python object.
+
+const mp = await (await import(process.argv[2])).loadMicroPython();
+
+mp.runPython(`
+l = []
+`);
+
+const l1 = mp.globals.get("l");
+const l2 = mp.globals.get("l");
+console.log(l1, l2);
+console.log(l1 === l2);
+
+globalThis.eventTarget = new EventTarget();
+globalThis.event = new Event("event");
+
+mp.runPython(`
+import js
+
+def callback(ev):
+ print("callback", ev)
+js.eventTarget.addEventListener("event", callback)
+js.eventTarget.dispatchEvent(js.event)
+js.eventTarget.removeEventListener("event", callback)
+js.eventTarget.dispatchEvent(js.event)
+`);
diff --git a/tests/ports/webassembly/py_proxy_identity.mjs.exp b/tests/ports/webassembly/py_proxy_identity.mjs.exp
new file mode 100644
index 000000000..01ccf0d89
--- /dev/null
+++ b/tests/ports/webassembly/py_proxy_identity.mjs.exp
@@ -0,0 +1,3 @@
+PyProxy { _ref: 3 } PyProxy { _ref: 3 }
+true
+callback <JsProxy 7>