Skip to content

webassembly: reuse JavaScript proxy references if possible, to improve identity/equality relationships #17758

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 31, 2025

Conversation

dpgeorge
Copy link
Member

Summary

In 77bd8fe PyProxy's were reused when the same Python object was proxied across to JavaScript.

This PR 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 keep down the number of alive objects, and, more importantly, improves equality relationships. Eg we now get, on the Python side:

import js

print(js.Object == js.Object)

that prints True. Previously it was False.

Testing

Tests have been added to CI. They pass locally.

Trade-offs and Alternatives

This does not make identity work with is, eg js.Object is js.Object is actually False. With more work that could be made True but for now we leave that as-is. This matches Pyodide semantics (only == is True, while is is False).

This needs to maintain a separate Map on the JS side, of all the proxied JS objects. But that's (a) necessary and (b) worth it because of the reduced churn of JS objects when proxying them across to Python.

@dpgeorge
Copy link
Member Author

@WebReflection FYI. This improved object equality as we discussed. See the tests for what improvements to expect. Note that is (on the Python side) is still False, per Pyodide.

Copy link

codecov bot commented Jul 24, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.38%. Comparing base (fdbd232) to head (ffa98cb).
⚠️ Report is 3 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master   #17758   +/-   ##
=======================================
  Coverage   98.38%   98.38%           
=======================================
  Files         171      171           
  Lines       22257    22257           
=======================================
  Hits        21898    21898           
  Misses        359      359           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@@ -97,13 +98,19 @@ function proxy_js_check_existing(c_ref) {

// js_obj cannot be undefined
function proxy_js_add_obj(js_obj) {
// See if there is an existing JsProxy reference, and use that if there is.
if (proxy_js_ref_map.has(js_obj)) {
Copy link
Contributor

@WebReflection WebReflection Jul 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know it's an ugly workaround but AFAIK there are no optimizations for has immediately followed by get in the JS Map world (runtimes) so that when performance is meant to be "best" and/or on the critical path, JS devs tend to abuse undefined returned when no entry is found.

That is:

const known = proxy_js_ref_map.get(js_obj);
if (known) return known;

We're still talking O(1) with JS Map lookup for the key but while O(1) + O(1) is still O(1) in the theoretical side of BigO notation, you can skip that "O(2)" by reaching directly the value, if available, via .get(key).

Not a blocker, of course, but years of perf tuning makes me always use that ugly fast-path whenever it's crucial.


edit for correctness and history sake ... the suggested improvements fails when:

  • a key in a Map is meant to be falsy or even undefined ... for whatever reason; IIRC this would never be the case in here
  • if you prefer strict known !== undefined in that if statement, that's OK, the mentioned perf boost won't be affected

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review.

I was wondering if there was a more efficient way to do this, and I've now changed it to what you suggested.

Note that proxy_js_ref_map contains integers and could potentially return 0, so we do need the known !== undefined check.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, that's the falsy value I was referring to ... if 0 could be returned, you definitively need that !== undefined check 👍

@dpgeorge dpgeorge force-pushed the webassembly-improve-identity-v2 branch from 06ef5d4 to d6351ee Compare July 25, 2025 01:08
Copy link

github-actions bot commented Jul 25, 2025

Code size report:

   bare-arm:    +0 +0.000% 
minimal x86:    +0 +0.000% 
   unix x64:    +0 +0.000% standard
      stm32:    +0 +0.000% PYBV10
     mimxrt:    +0 +0.000% TEENSY40
        rp2:    +0 +0.000% RPI_PICO_W
       samd:    +0 +0.000% ADAFRUIT_ITSYBITSY_M4_EXPRESS
  qemu rv32:    +0 +0.000% VIRT_RV32

dpgeorge added 3 commits July 31, 2025 11:38
This option is needed for ports such as webassembly where objects are
proxied and can be identical without being the same C pointer.

Signed-off-by: Damien George <damien@micropython.org>
Signed-off-by: Damien George <damien@micropython.org>
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 <damien@micropython.org>
@dpgeorge dpgeorge force-pushed the webassembly-improve-identity-v2 branch from d6351ee to ffa98cb Compare July 31, 2025 01:44
@dpgeorge dpgeorge merged commit ffa98cb into micropython:master Jul 31, 2025
88 of 90 checks passed
@dpgeorge dpgeorge deleted the webassembly-improve-identity-v2 branch July 31, 2025 03:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy