Hi,
Is there somewhere that documents what goes into the precompiled artifacts made by Engine::precompile_module. The difference in sizes between before and after precompilation can be pretty big, some examples I have go from 35KiB to 100 KiB and I'm wondering if there are ways to reduce this gap.
For reference here's the config I'm using for precompilation
let mut config = Config::new();
// Tell how the memory is expected to behave when instantiated
config.memory_reservation(0);
config.wasm_custom_page_sizes(true);
config.memory_may_move(false);
config.memory_init_cow(false);
// Optimize for Code size in the .cwasm
config.cranelift_opt_level(wasmtime::OptLevel::SpeedAndSize);
config.target("pulley32")?;
One other big one is likely disabling address map support
https://github.com/google/wasefire/issues/458#issuecomment-3189916340 is some dissection of the current size if you're curious for a similar/related project where binary sizes were important
there's also https://github.com/bytecodealliance/wasmtime/issues/10286 which is an open issue for us
FWIW, you should be able to see a breakdown of section sizes with a tool like bloaty; e.g. here's a run on a test.cwasm compiled from a test.wat with one tiny function:
% bloaty test.cwasm
FILE SIZE VM SIZE
-------------- --------------
62.6% 30.8Ki 0.0% 0 [Unmapped]
32.6% 16.1Ki 93.0% 16.0Ki .text
2.0% 1016 5.4% 952 .wasmtime.engine
0.5% 228 0.9% 164 .eh_frame
0.4% 221 0.0% 0 .shstrtab
0.4% 203 0.4% 75 .wasmtime.info
0.3% 163 0.0% 0 .strtab
0.3% 160 0.0% 0 .symtab
0.3% 128 0.0% 0 [ELF Headers]
0.2% 92 0.2% 28 .wasmtime.addrmap
0.1% 72 0.0% 8 .wasmtime.exceptions
0.1% 68 0.0% 4 .wasmtime.traps
0.1% 65 0.0% 1 .wasmtime.bti
100.0% 49.2Ki 100.0% 17.2Ki TOTAL
I have looked deeper into optimizing this stuff. This is the configuration I ended up with
config.memory_init_cow(false);
config.generate_address_map(false);
config.table_lazy_init(false);
config.cranelift_opt_level(OptLevel::Speed);
config.memory_may_move(false);
config.memory_reservation(0);
config.wasm_custom_page_sizes(true);
compared to an "unoptimized" precompilation this reduced the elf size by about 5, most of coming from generate_adress_map(false). Now I have ELFs that have the following sections :
llvm-readelf -S testing.cwasm
There are 11 section headers, starting at offset 0x2f28:
Section Headers:
[Nr] Name Type Address Off Size ES Flg Lk Inf Al
[ 0] NULL 0000000000000000 000000 000000 00 0 0 0
[ 1] .wasmtime.engine PROGBITS 0000000000000000 000040 000315 00 A 0 0 1
[ 2] .wasmtime.bti PROGBITS 0000000000000000 000355 000001 00 A 0 0 1
[ 3] .text PROGBITS 0000000000000000 000356 001d93 00 W 0 0 1
[ 4] .wasmtime.traps PROGBITS 0000000000000000 0020e9 000027 00 A 0 0 1
[ 5] .wasmtime.exceptions PROGBITS 0000000000000000 002110 0000e0 00 A 0 0 1
[ 6] .rodata.wasm PROGBITS 0000000000000000 0021f0 000034 00 A 0 0 1
[ 7] .wasmtime.info PROGBITS 0000000000000000 002224 000596 00 A 0 0 1
[ 8] .symtab SYMTAB 0000000000000000 0027c0 000300 18 9 32 8
[ 9] .strtab STRTAB 0000000000000000 002ac0 0003e1 00 0 0 1
[10] .shstrtab STRTAB 0000000000000000 002ea1 000081 00 0 0 1
For some reasons, llvm-size -A and objdump --section-headers don't show the last 3 sections in their output.
From what I understand from this issue .symtab, .strtab and .shstrtab can be stripped and everything should work. What about the .wasmtime sections, especially traps and exceptions and info ? Can they be removed ?
For a sense of scale, in this example, the sum of all 6 sections, accounts for 3853( 28.4 %) out of 12776 bytes (total measured with wc -c). In another bigger example I have lying around it accounts for 7339(19.6 %) out of 37448 bytes. So it's impact is decreasing but even constant gains in code size are desirable in the constrained environments I'm working in
The traps section is necessary for wasmtime to know which locations are expected to trap and thus can be handled by returning a wasm trap to the host code rather than aborting the entire process. The exceptions section is necessary for wasm exceptions to function. The info section contains things like function types and the names and locations of all exports and imports, which is essential information for getting to run any wasm code at all.
.shstrtab is necessary as it contains the names of all ELF sections, which are necessary to locate the sections in the first place.
it's theoretically possible to strip .wasmtime.traps, .wasmtime.exceptions, .wasmtime.engine, and .wasmtime.bti.
.wasmtime.traps - as bjorn3 mentioned this is used for verifying that a faulting instruction can indeed fault. It's also used to report the trap code when a fault happens. We could in theory add an unsafe method to strip it which would report less information on trap and possibly catch bugs in Wasmtime as "this is a wasm trap". For functional correctness though it's not necessarily required..wasmtime.exceptions - we should just strip this, it's an empty section anyway.wasmtime.engine / .wasmtime.bti - this has compile-time metadata which is double-checked when loading the module to ensure that compilation/runtime settings all match. This could, like .wasmtime.traps, in theory be stripped away so long as you opt-in to the unsafety of doing so.Otherwise, yeah, .symtab and .strtab are definitely strippable and aren't needed by Wasmtime at all.
Would you be interested in sending some PRs to help out here? For example stripping .wasmtime.exceptions shouldn't be too hard, and the next-easiest thing would probably be figuring out how to strip the sybols
.wamstime.engine in my examples is a flat 0x355 bytes and .wasmtime.bti is 0x1 byte regardless of the .textor .rodata.wasm section sizes so it's really not worth the removal I think. .symtab and .strtab are more interesting to remove. Regarding PRs, I'm not necessarily against it but I can't say that I'll find the time to contribute this. Also this kind of thing is not my domain of expertise. I'm fine llvm-striping the unnecessary sections for the time being. Regarding stripping the symbols, in the issue linked above, you mention [the need to request features to upstream projects like object] so I don't know how much I could do in that direction
it's true yeah I don't know how we'd strip the sections in Rust itself
in the meantime llvm-strip should work ok
It's been awhile, but I do think there would need to be updates to a few dependencies (gimli, IIRC) to be able to strip additional sections without external tooling. It would certainly be convenient to be able to do this without external tooling.
Last updated: Dec 06 2025 at 06:05 UTC