Skip to content

Include heap views only when needed#27269

Open
brendandahl wants to merge 6 commits into
emscripten-core:mainfrom
brendandahl:heap-vars
Open

Include heap views only when needed#27269
brendandahl wants to merge 6 commits into
emscripten-core:mainfrom
brendandahl:heap-vars

Conversation

@brendandahl

@brendandahl brendandahl commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

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.

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 sbc100 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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?

Comment thread src/jsifier.mjs
Comment thread src/jsifier.mjs Outdated
Comment thread src/runtime_common.js Outdated
Comment thread src/runtime_common.js
return `${maybeExportHeap(x)}${x} = new ${type}(b);`;
}
return '';
};

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

Comment thread src/runtime_common.js Outdated
Comment thread src/runtime_common.js Outdated
Comment thread src/runtime_common.js Outdated
Comment thread src/settings.js Outdated
Comment thread tools/link.py
feature_matrix.auto_enable_features()

if settings.WASM_BIGINT:
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$HEAP64', '$HEAPU64']

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Have you basically moved the encoding of this information from here to hardcoding it in getRequiredHeapSymbols? Why is that better?

Comment thread tools/link.py Outdated
@brendandahl

Copy link
Copy Markdown
Collaborator Author

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?

We discussed in person, but for posterity...

Closure Compiler cannot eliminate those remaining new TypedArray(a) expressions because in JavaScript and Closure Compiler's standard externs, TypedArray constructors are not considered pure (side-effect-free) for three main reasons: 1) runtime exceptions, 2) property accessor traps, 3) no @nosideeffects in the externs defined by closure.

Comment thread tools/emscripten.py Outdated
Comment thread src/jsifier.mjs
'runtimeKeepalivePop',
'UTF8ToString',
'getValue',
'setValue',

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I wonder if these are actually used much. Maybe add a TODO to remove them from this list?

Comment thread src/jsifier.mjs Outdated
}
}
// 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

@sbc100 sbc100 Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

But wouldn't eval('HEAP8[0]') also work for check below? i.e. we would still see the HEAP8 reference below wouldn't we?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Not always. This was for fixing emscripten_run_script which loads a string from memory to eval.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I should update that comment.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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)?

@sbc100

sbc100 commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

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?

We discussed in person, but for posterity...

Closure Compiler cannot eliminate those remaining new TypedArray(a) expressions because in JavaScript and Closure Compiler's standard externs, TypedArray constructors are not considered pure (side-effect-free) for three main reasons: 1) runtime exceptions, 2) property accessor traps, 3) no @nosideeffects in the externs defined by closure.

Can you include this in the commit message in come form?

Comment thread src/jsifier.mjs Outdated
// 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() {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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?

@sbc100 sbc100 Jul 10, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

@sbc100 sbc100 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

lgtm % final comment

Comment thread tools/link.py
@@ -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():

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why can't this be called from phase_linker_setup below (where most of it lived before) instead of moving to emcc.py

Comment thread tools/emscripten.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()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This seems rather odd that we could need to call it twice.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants