Repository navigation
Make dlsym O(1) in the number of exports - #28125
Open
basvandijk wants to merge 8 commits into
Open
basvandijk wants to merge 8 commits into
basvandijk wants to merge 8 commits into
Conversation
`_dlsym_js` enumerated every export of the library on each call, `Object.keys(lib.exports).indexOf(symbol)`, before doing anything else. For `RTLD_DEFAULT` on a main module that is `wasmImports`, which holds every export of the main module, so each `dlsym` call cost O(N log N) in the number of exported symbols, whether the symbol exists or not. The index is only needed when `addFunction` allocates a new table slot and the other threads replay the insertion by index (`_dlsym_catchup_js`). Test membership with `hasOwnProperty`, which gives the same answer as `Object.keys(...).indexOf(symbol) != -1` for these plain objects, and compute the enumeration index only in the branch that stores it into `symbolIndex`. A main module with 250,000 exported functions goes from 80.9 ms to 0.009 ms per `dlsym` call. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request optimizes dlsym in src/lib/libdylink.js by avoiding the enumeration of all library exports on every lookup. Instead, it uses hasOwnProperty for the existence check and only computes the symbol's index when a table slot is actually allocated. The reviewer suggested a further optimization to cache the keys array on the library object to prevent repeated enumeration during multiple allocations.
sbc100
approved these changes
Nov 1, 2026
sbc100
left a comment
Collaborator
There was a problem hiding this comment.
I imagine you will need to rebaseline to codesize tests here.
Review feedback: compute the enumeration index into `newSymIndex` so the `makeSetValue` line stays simple. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This is an automatic change generated by tools/maint/rebaseline_tests.py. The following (1) test expectation files were updated by running the tests with `--rebaseline`: ``` codesize/test_codesize_hello_dylink_all.json: 862336 => 862390 [+54 bytes / +0.01%] Average change: +0.01% (+0.01% - +0.01%) ``` Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Author
basvandijk
commented
Nov 1, 2026
basvandijk
commented
Nov 2, 2026
sbc100
added a commit
to sbc100/emscripten
that referenced
this pull request
Nov 2, 2026
In compile-time JS code (`src/`, `tools/`, and compile-time helpers in `libwebgl.js`), use `Object.hasOwn` directly instead of `hasOwnProperty`. In runtime JS library code, introduce an internal `$hasOwn` helper in `libcore.js` that aliases `Object.hasOwn` when targeting modern browser versions (`MIN_FIREFOX_VERSION >= 92`, `MIN_CHROME_VERSION >= 93`, and `MIN_SAFARI_VERSION >= 150400`) and falls back to `Object.prototype.hasOwnProperty.call` otherwise. Note that this change comes with minor codesize change, in some cases an improvement and in some a regression. Note that if we were to bump our default min browser versions we would always see an improvement (perhaps we should always target modern browser in our codesize tests?) See emscripten-core#28125
sbc100
reviewed
Nov 2, 2026
sbc100
approved these changes
Nov 2, 2026
sbc100
added a commit
to sbc100/emscripten
that referenced
this pull request
Nov 2, 2026
In compile-time JS code (`src/`, `tools/`, and compile-time helpers in `libwebgl.js`), use `Object.hasOwn` directly instead of `hasOwnProperty`. In runtime JS library code, introduce an internal `$hasOwn` helper in `libcore.js` that aliases `Object.hasOwn` when targeting modern browser versions (`MIN_FIREFOX_VERSION >= 92`, `MIN_CHROME_VERSION >= 93`, and `MIN_SAFARI_VERSION >= 150400`) and falls back to `Object.prototype.hasOwnProperty.call` otherwise. Note that this change comes with minor codesize change, in some cases an improvement and in some a regression. Note that if we were to bump our default min browser versions we would always see an improvement (perhaps we should always target modern browser in our codesize tests?) See emscripten-core#28125
basvandijk
commented
Nov 2, 2026
…ration Resolve the conflict in test/codesize/test_codesize_hello_dylink_all.json by taking main's version; the next commit rebaselines it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This is an automatic change generated by tools/maint/rebaseline_tests.py. The following (1) test expectation files were updated by running the tests with `--rebaseline`: ``` codesize/test_codesize_hello_dylink_all.json: 862405 => 862459 [+54 bytes / +0.01%] Average change: +0.01% (+0.01% - +0.01%) ``` Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sbc100
added a commit
to sbc100/emscripten
that referenced
this pull request
Nov 2, 2026
In compile-time JS code (`src/`, `tools/`, and compile-time helpers in `libwebgl.js`), use `Object.hasOwn` directly instead of `hasOwnProperty`. In runtime JS library code, introduce an internal `$hasOwn` helper in `libcore.js` that aliases `Object.hasOwn` when targeting modern browser versions (`MIN_FIREFOX_VERSION >= 92`, `MIN_CHROME_VERSION >= 93`, and `MIN_SAFARI_VERSION >= 150400`) and falls back to `Object.prototype.hasOwnProperty.call` otherwise. Note that this change comes with minor codesize change, in some cases an improvement and in some a regression. Note that if we were to bump our default min browser versions we would always see an improvement (perhaps we should always target modern browser in our codesize tests?) See emscripten-core#28125
sbc100
added a commit
that referenced
this pull request
Nov 2, 2026
In compile-time JS code (`src/`, `tools/`, and compile-time helpers in `libwebgl.js`), use `Object.hasOwn` directly instead of `hasOwnProperty`. In runtime JS library code, introduce an internal `$hasOwn` helper in `libcore.js` that aliases `Object.hasOwn` when targeting modern browser versions (`MIN_FIREFOX_VERSION >= 92`, `MIN_CHROME_VERSION >= 93`, and `MIN_SAFARI_VERSION >= 150400`) and falls back to `Object.prototype.hasOwnProperty.call` otherwise. Note that this change comes with minor codesize change, in some cases an improvement and in some a regression. Note that if we were to bump our default min browser versions we would always see an improvement (perhaps we should always target modern browser in our codesize tests?) See #28125
Collaborator
|
I just landed #28129, so maybe you can rebase and use the |
Author
|
Will do! |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
_dlsym_jsenumerated every export of the library on each call,Object.keys(lib.exports).indexOf(symbol), before doing anything else. ForRTLD_DEFAULTon a main module that iswasmImports, which holds every export of the main module, so eachdlsymcall cost O(N log N) in the number of exported symbols (V8 sorts the keys of a dictionary-mode object on everyObject.keys), whether the symbol exists or not. The index is only needed whenaddFunctionallocates a new table slot and the other threads replay the insertion by index (_dlsym_catchup_js, #18590).This change tests membership with
Object.prototype.hasOwnProperty.call, which gives the same answer asObject.keys(...).indexOf(symbol) != -1for these plain objects, and computes the enumeration index only in the branch that stores it intosymbolIndex. The thread catch-up protocol is unchanged.Measured with a main module of N exported functions calling
dlsym(RTLD_DEFAULT, name)(emcc -O2 -pthread -sMAIN_MODULE=1 -sEXIT_RUNTIME=1, Node 26.13.0, ms per call, hits):Lean 4's
lean.js(247,834 exports) makes 2,630dlsymcalls at startup, which took 210 s of a 236 s run and take 17 ms with this change;Fixes #28124.
Tests:
test/runner core0.test_dlfcn_self core0.test_dylink_basics core0.test_pthread_dlsym core0.test_pthread_dylink core0.test_pthread_dylink_basics core0.test_pthread_dylink_main_module_1 other.test_dlsym_rtld_default other.test_dlsym_rtld_default_js_symbol other.test_dylink_pthread_static_datapass.🤖 Generated with Claude Code