Include heap views only when needed#27269
Conversation
Previously, all standard heap views (HEAP8 through HEAPF64) were always included in the JS bundle and initialized, even when unused. This increased bundle size and memory overhead. Remove heap view symbols from default library inclusions. Only include and initialize heap views that are required by runtime features, exported, or referenced in user code and snippets.
This is an automatic change generated by tools/maint/rebaseline_tests.py. The following (62) test expectation files were updated by running the tests with `--rebaseline`: ``` test/codesize/audio_worklet_wasm.expected.js updated test/codesize/hello_wasm_worker_wasm.expected.js updated test/codesize/hello_world_wasm.expected.js updated test/codesize/hello_world_wasm2js.expected.js updated codesize/test_codesize_cxx_ctors1.json: 151786 => 151686 [-100 bytes / -0.07%] codesize/test_codesize_cxx_ctors2.json: 151189 => 151089 [-100 bytes / -0.07%] codesize/test_codesize_cxx_except.json: 195681 => 195582 [-99 bytes / -0.05%] codesize/test_codesize_cxx_except_wasm.json: 166910 => 166810 [-100 bytes / -0.06%] codesize/test_codesize_cxx_except_wasm_legacy.json: 164794 => 164694 [-100 bytes / -0.06%] codesize/test_codesize_cxx_lto.json: 120616 => 120516 [-100 bytes / -0.08%] codesize/test_codesize_cxx_mangle.json: 262160 => 262061 [-99 bytes / -0.04%] codesize/test_codesize_cxx_noexcept.json: 153786 => 153686 [-100 bytes / -0.07%] codesize/test_codesize_cxx_wasmfs.json: 179554 => 179433 [-121 bytes / -0.07%] test/codesize/test_codesize_file_preload.expected.js updated codesize/test_codesize_file_preload.json: 23905 => 23806 [-99 bytes / -0.41%] codesize/test_codesize_files_js_fs.json: 18212 => 18112 [-100 bytes / -0.55%] codesize/test_codesize_files_wasmfs.json: 63764 => 63585 [-179 bytes / -0.28%] codesize/test_codesize_hello_O0.json: 38701 => 38554 [-147 bytes / -0.38%] codesize/test_codesize_hello_O1.json: 8261 => 8066 [-195 bytes / -2.36%] codesize/test_codesize_hello_O2.json: 5927 => 5771 [-156 bytes / -2.63%] codesize/test_codesize_hello_O3.json: 5622 => 5467 [-155 bytes / -2.76%] codesize/test_codesize_hello_Os.json: 5610 => 5455 [-155 bytes / -2.76%] codesize/test_codesize_hello_Oz.json: 4781 => 4626 [-155 bytes / -3.24%] codesize/test_codesize_hello_dylink.json: 44051 => 44052 [+1 bytes / +0.00%] codesize/test_codesize_hello_export_nothing.json: 2899 => 2685 [-214 bytes / -7.38%] codesize/test_codesize_hello_single_file.json: 5008 => 4853 [-155 bytes / -3.10%] codesize/test_codesize_hello_wasmfs.json: 5622 => 5467 [-155 bytes / -2.76%] codesize/test_codesize_libcxxabi_message_O3.json: 3312 => 3099 [-213 bytes / -6.43%] codesize/test_codesize_libcxxabi_message_O3_standalone.json: 3493 => 3380 [-113 bytes / -3.24%] codesize/test_codesize_mem_O3.json: 9290 => 9196 [-94 bytes / -1.01%] codesize/test_codesize_mem_O3_grow.json: 9608 => 9539 [-69 bytes / -0.72%] codesize/test_codesize_mem_O3_grow_standalone.json: 9454 => 9382 [-72 bytes / -0.76%] codesize/test_codesize_mem_O3_standalone.json: 9289 => 9193 [-96 bytes / -1.03%] codesize/test_codesize_mem_O3_standalone_lib.json: 8503 => 8390 [-113 bytes / -1.33%] codesize/test_codesize_mem_O3_standalone_narg.json: 8625 => 8512 [-113 bytes / -1.31%] codesize/test_codesize_mem_O3_standalone_narg_flto.json: 7556 => 7443 [-113 bytes / -1.50%] codesize/test_codesize_minimal_64.json: 2600 => 2388 [-212 bytes / -8.15%] test/codesize/test_codesize_minimal_O0.expected.js updated codesize/test_codesize_minimal_O0.json: 19805 => 19650 [-155 bytes / -0.78%] codesize/test_codesize_minimal_O1.json: 3376 => 3119 [-257 bytes / -7.61%] codesize/test_codesize_minimal_O2.json: 2549 => 2332 [-217 bytes / -8.51%] codesize/test_codesize_minimal_O3.json: 2285 => 2073 [-212 bytes / -9.28%] codesize/test_codesize_minimal_Os.json: 2285 => 2073 [-212 bytes / -9.28%] codesize/test_codesize_minimal_Os_mr.json: 577 => 353 [-224 bytes / -38.82%] codesize/test_codesize_minimal_Oz-ctors.json: 2256 => 2044 [-212 bytes / -9.40%] codesize/test_codesize_minimal_Oz.json: 2285 => 2073 [-212 bytes / -9.28%] codesize/test_codesize_minimal_esm.json: 2423 => 2212 [-211 bytes / -8.71%] codesize/test_codesize_minimal_pthreads.json: 26008 => 25912 [-96 bytes / -0.37%] codesize/test_codesize_minimal_pthreads_memgrowth.json: 26443 => 26402 [-41 bytes / -0.16%] codesize/test_codesize_minimal_wasmfs.json: 2285 => 2073 [-212 bytes / -9.28%] codesize/test_minimal_runtime_code_size_hello_embind.json: 14896 => 14896 [+0 bytes / +0.00%] codesize/test_minimal_runtime_code_size_hello_embind_val.json: 11635 => 11635 [+0 bytes / +0.00%] codesize/test_minimal_runtime_code_size_hello_wasm_worker.json: 4121 => 4099 [-22 bytes / -0.53%] codesize/test_minimal_runtime_code_size_hello_webgl2_wasm2js.json: 18559 => 18559 [+0 bytes / +0.00%] codesize/test_minimal_runtime_code_size_hello_webgl2_wasm_singlefile.json: 15040 => 15040 [+0 bytes / +0.00%] codesize/test_minimal_runtime_code_size_hello_webgl_wasm.json: 12724 => 12724 [+0 bytes / +0.00%] codesize/test_minimal_runtime_code_size_hello_world_wasm.json: 927 => 927 [+0 bytes / +0.00%] codesize/test_minimal_runtime_code_size_random_printf_wasm.json: 11054 => 11054 [+0 bytes / +0.00%] codesize/test_minimal_runtime_code_size_random_printf_wasm2js.json: 17417 => 17417 [+0 bytes / +0.00%] test/codesize/test_small_js_flags.expected.js updated codesize/test_small_js_flags.json: 3888 => 3732 [-156 bytes / -4.01%] codesize/test_unoptimized_code_size.json: 177452 => 172027 [-5425 bytes / -3.06%] Average change: -3.16% (-38.82% - +0.00%) ```
sbc100
left a comment
There was a problem hiding this comment.
Nice!
I guess the reason this has an impact on codesize even closure build is that somehow these are not been detected and removed as dead code by closure?
| return `${maybeExportHeap(x)}${x} = new ${type}(b);`; | ||
| } | ||
| return ''; | ||
| }; |
There was a problem hiding this comment.
I think this is almost enough complexity to just move the whole updateMemoryViews into a generator / helper. But I guess it makes sense for this incremental change to do it this way for now.
| feature_matrix.auto_enable_features() | ||
|
|
||
| if settings.WASM_BIGINT: | ||
| settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$HEAP64', '$HEAPU64'] |
There was a problem hiding this comment.
Have you basically moved the encoding of this information from here to hardcoding it in getRequiredHeapSymbols? Why is that better?
We discussed in person, but for posterity... Closure Compiler cannot eliminate those remaining |
| 'runtimeKeepalivePop', | ||
| 'UTF8ToString', | ||
| 'getValue', | ||
| 'setValue', |
There was a problem hiding this comment.
I wonder if these are actually used much. Maybe add a TODO to remove them from this list?
| } | ||
| } | ||
| // If the snippet contains eval(), it may dynamically evaluate strings that reference any | ||
| // heap view (e.g., eval('HEAP8[0]')). Since static string matching cannot detect which |
There was a problem hiding this comment.
But wouldn't eval('HEAP8[0]') also work for check below? i.e. we would still see the HEAP8 reference below wouldn't we?
There was a problem hiding this comment.
Not always. This was for fixing emscripten_run_script which loads a string from memory to eval.
There was a problem hiding this comment.
I should update that comment.
There was a problem hiding this comment.
I think if anyone is doing this it should be on them to explictly add these deps.
If you don't want to break such folks with this PR, then you can maybe include this code now and then remove it later (i.e. delay the breakage)?
Can you include this in the commit message in come form? |
| // Because addImplicitDeps only scans library functions and user code (pre-js/post-js/EM_JS), | ||
| // runtime scaffolding files that access linear memory directly must have their heap views | ||
| // explicitly declared here so they are emitted in the final JS bundle. | ||
| function getRequiredHeapSymbols() { |
There was a problem hiding this comment.
I'm still not sure why we would prefer to implement this function rather than in link.py (where these symbols would be added to DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.
For other cases there a build config implies a library symbol we do it in link.py, why should this be different?
There was a problem hiding this comment.
Not sure what's happening, my comment didn't post....
I was trying to put all the heap handling code in one place. I've added new commit with it moved to python.
It's called twice because it's not fully known if the HEAP symbols are needed until after link. We can figure out most pre link (e.g. if MAIN_MODULE, SAFE_HEAP, etc are set), but we don't know if (settings.HAS_MAIN and settings.MAIN_READS_PARAMS) until later. I suppose we could call it in compile_javascript. It will still be called twice, but from one place.
There was a problem hiding this comment.
That is kind of sad.. perhaps just the settings.HAS_MAIN and settings.MAIN_READS_PARAMS could be done in emscripten.py and the rest can stay in link.py
There was a problem hiding this comment.
i.e. we do the things we can do in phase_linker_setup.py and we then update the list once we know about settings.HAS_MAIN and settings.MAIN_READS_PARAMS?
There was a problem hiding this comment.
I'm not sure if its even good idea to group all this stuff together either.. i.e. sometimes is nice to see the side effects of a given setting all in one place (i.e. to see the effects of MEMORY64), but its also nice to group things like by the effect (i.e. to see all the things the effect HEAP requirments). Is not clear to me the that latter grouping is any better than the former grouping.
| @@ -806,6 +806,49 @@ def setup_cross_origin_storage(): | |||
| exit_with_error(f"CROSS_ORIGIN_STORAGE_ORIGINS: {o!r} is not a valid HTTPS origin (expected 'https://host' or 'https://host:port')") | |||
|
|
|||
|
|
|||
| def add_required_heap_symbols(): | |||
There was a problem hiding this comment.
Why can't this be called from phase_linker_setup below (where most of it lived before) instead of moving to emcc.py
| @@ -155,6 +156,8 @@ def update_settings_glue(wasm_file, metadata, base_metadata): | |||
| for deps in metadata.js_deps: | |||
| settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.extend(deps.split(',')) | |||
|
|
|||
| link.add_required_heap_symbols() | |||
There was a problem hiding this comment.
This seems rather odd that we could need to call it twice.
Previously, all standard heap views (HEAP8 through HEAPF64) were
always included in the JS bundle and initialized, even when unused.
Closure Compiler could not eliminate the
new TypedArray(a)expressions because in JavaScript and Closure Compiler's standard
externs, TypedArray constructors are not considered pure (side-effect-free).
Remove heap view symbols from default library inclusions. Only
include and initialize heap views that are required by runtime
features, exported, or referenced in user code and snippets.