Skip to content

[wasm-split] Move exclusive items to secondary modules#8441

Open
aheejin wants to merge 1 commit intomainfrom
wasm_split_global
Open

[wasm-split] Move exclusive items to secondary modules#8441
aheejin wants to merge 1 commit intomainfrom
wasm_split_global

Conversation

@aheejin
Copy link
Member

@aheejin aheejin commented Mar 10, 2026

When splitting a module, if non-function items (memories, tables, globals, tags) are exclusively used by a single secondary module, this moves them directly to that secondary module rather than exporting them from the primary module.

When a global is moved, its initializer can contain global.get or ref.funcs, creating dependences on other globals and functions. For now, this PR just exports all the dependences from the primary module to the secondary module. This will be improved by follow-up PRs.

This PR reduces the size of the primary module for acx_gallery by 12.5%. Follow-up PRs will reduce it further.

This also sadly increases wasm-split's running time on acx_gallery from 16.5s -> 24.7s, by 49%, due to more computations in shareImportableItems.


The below is wasm-objdump -h result of the primary modules:

  • Before
     Type start=0x0000000c end=0x00035e09 (size=0x00035dfd) count: 11192
   Import start=0x00035e0e end=0x004bd669 (size=0x0048785b) count: 65720
 Function start=0x004bd66d end=0x004d0519 (size=0x00012eac) count: 62890
    Table start=0x004d051c end=0x004d4059 (size=0x00003b3d) count: 2921
      Tag start=0x004d405b end=0x004d405f (size=0x00000004) count: 1
   Global start=0x004d4063 end=0x00689ff8 (size=0x001b5f95) count: 80766
   Export start=0x00689ffc end=0x0071b16c (size=0x00091170) count: 60877
    Start start=0x0071b16e end=0x0071b170 (size=0x00000002) start: 828
     Elem start=0x0071b174 end=0x00784fb9 (size=0x00069e45) count: 12303
DataCount start=0x00784fbb end=0x00784fbc (size=0x00000001) count: 1
     Code start=0x00784fc1 end=0x009b4958 (size=0x0022f997) count: 62890
     Data start=0x009b495c end=0x009d44e9 (size=0x0001fb8d) count: 1
  • After (This PR)
     Type start=0x0000000c end=0x00035d44 (size=0x00035d38) count: 11185
   Import start=0x00035d49 end=0x003faf6f (size=0x003c5226) count: 56805
 Function start=0x003faf73 end=0x0040de1f (size=0x00012eac) count: 62890
    Table start=0x0040de22 end=0x0041195d (size=0x00003b3b) count: 2921
      Tag start=0x0041195f end=0x00411963 (size=0x00000004) count: 1
   Global start=0x00411967 end=0x005541c5 (size=0x0014285e) count: 47771
   Export start=0x005541c9 end=0x005dfc2c (size=0x0008ba63) count: 59077
    Start start=0x005dfc2e end=0x005dfc30 (size=0x00000002) start: 828
     Elem start=0x005dfc34 end=0x00649a77 (size=0x00069e43) count: 12303
DataCount start=0x00649a79 end=0x00649a7a (size=0x00000001) count: 1
     Code start=0x00649a7f end=0x00879385 (size=0x0022f906) count: 62890
     Data start=0x00879389 end=0x00898f16 (size=0x0001fb8d) count: 1

Follow-ups: #8442 and #8443

When splitting a module, if non-function items (memories, tables,
globals, tags) are exclusively used by a single secondary module, this
moves them directly to that secondary module rather than exporting them
from the primary module.

When a global is moved, its initializer can contain `global.get` or
`ref.func`s, creating dependences on other globals and functions. For
now, this PR just exports all the dependences from the primary module to
the secondary module. This will be improved by follow-up PRs.

This PR does not reduce the size of the primary module for acx_gallery
test case; it actually increases it by 2.6%. But this PR is mostly a
preparation for the follow-up PRs, which will reduce the size.

This also sadly increases wasm-split's running time on acx_gallery from
15.7s -> 24.2s, by 54%, due to more computations in
`shareImportableItems`.

---

The below is `wasm-objdump -h` result of the primary modules:

- Before
```
     Type start=0x0000000c end=0x00035e09 (size=0x00035dfd) count: 11192
   Import start=0x00035e0e end=0x004bd669 (size=0x0048785b) count: 65720
 Function start=0x004bd66d end=0x004d0519 (size=0x00012eac) count: 62890
    Table start=0x004d051c end=0x004d4059 (size=0x00003b3d) count: 2921
      Tag start=0x004d405b end=0x004d405f (size=0x00000004) count: 1
   Global start=0x004d4063 end=0x00689ff8 (size=0x001b5f95) count: 80766
   Export start=0x00689ffc end=0x0077aafe (size=0x000f0b02) count: 60877
    Start start=0x0077ab00 end=0x0077ab02 (size=0x00000002) start: 828
     Elem start=0x0077ab06 end=0x007e494b (size=0x00069e45) count: 12303
DataCount start=0x007e494d end=0x007e494e (size=0x00000001) count: 1
     Code start=0x007e4953 end=0x00a142ea (size=0x0022f997) count: 62890
     Data start=0x00a142ee end=0x00a33e7b (size=0x0001fb8d) count: 1
```

- After (This PR)
```
     Type start=0x0000000c end=0x00035d44 (size=0x00035d38) count: 11185
   Import start=0x00035d49 end=0x003faf6f (size=0x003c5226) count: 56805
 Function start=0x003faf73 end=0x0040de1f (size=0x00012eac) count: 62890
    Table start=0x0040de22 end=0x0041195d (size=0x00003b3b) count: 2921
      Tag start=0x0041195f end=0x00411963 (size=0x00000004) count: 1
   Global start=0x00411967 end=0x005541c5 (size=0x0014285e) count: 47771
   Export start=0x005541ca end=0x007c0a00 (size=0x0026c836) count: 59077
    Start start=0x007c0a02 end=0x007c0a04 (size=0x00000002) start: 828
     Elem start=0x007c0a08 end=0x0082a84b (size=0x00069e43) count: 12303
DataCount start=0x0082a84d end=0x0082a84e (size=0x00000001) count: 1
     Code start=0x0082a853 end=0x00a5a159 (size=0x0022f906) count: 62890
     Data start=0x00a5a15d end=0x00a79cea (size=0x0001fb8d) count: 1
```

Note that even though the size of the global section has decreased by
27% (the number of global by 41%), the size of the export section
increased by 157%, while the number of exports has actually decreased.
The reason is, while we shed some exports for globals, we gained exports
for functions (due to globals `ref.func` dependences), which has a lot
longer names.

Follow-ups:
@aheejin aheejin requested a review from tlively March 10, 2026 09:31
aheejin added a commit that referenced this pull request Mar 10, 2026
When a global is exclusively used by a secondary module, we can move it
to the secondary module. If its initializer contains a `global.get` of
another global, we exported it from the primary module to the secondary
module, even if it may not be used anywhere else.

When we split a global out to a secondary module, this PR computes the
transitive dependency of the split global, and if those globals in the
dependency are not used anywhere else in other modules, we move them to
the secondary module as well.

by 29%.

The running time of `wasm-split` hasn't really changed with this PR,
compared to #8441.

---

`wasm-objdump -h` result:

- Before (#8441)
     Type start=0x0000000c end=0x00035d44 (size=0x00035d38) count: 11185
   Import start=0x00035d49 end=0x003faf6f (size=0x003c5226) count: 56805
 Function start=0x003faf73 end=0x0040de1f (size=0x00012eac) count: 62890
    Table start=0x0040de22 end=0x0041195d (size=0x00003b3b) count: 2921
      Tag start=0x0041195f end=0x00411963 (size=0x00000004) count: 1
   Global start=0x00411967 end=0x005541c5 (size=0x0014285e) count: 47771
   Export start=0x005541ca end=0x007c0a00 (size=0x0026c836) count: 59077
    Start start=0x007c0a02 end=0x007c0a04 (size=0x00000002) start: 828
     Elem start=0x007c0a08 end=0x0082a84b (size=0x00069e43) count: 12303
DataCount start=0x0082a84d end=0x0082a84e (size=0x00000001) count: 1
     Code start=0x0082a853 end=0x00a5a159 (size=0x0022f906) count: 62890
     Data start=0x00a5a15d end=0x00a79cea (size=0x0001fb8d) count: 1
```

- After (This PR)
```
     Type start=0x0000000c end=0x00035d44 (size=0x00035d38) count: 11185
   Import start=0x00035d48 end=0x00132efc (size=0x000fd1b4) count: 32642
 Function start=0x00132f00 end=0x00145dac (size=0x00012eac) count: 62890
    Table start=0x00145daf end=0x001498ea (size=0x00003b3b) count: 2921
      Tag start=0x001498ec end=0x001498f0 (size=0x00000004) count: 1
   Global start=0x001498f4 end=0x00289e60 (size=0x0014056c) count: 47728
   Export start=0x00289e65 end=0x004977fe (size=0x0020d999) count: 35861
    Start start=0x00497800 end=0x00497802 (size=0x00000002) start: 828
     Elem start=0x00497806 end=0x00501649 (size=0x00069e43) count: 12303
DataCount start=0x0050164b end=0x0050164c (size=0x00000001) count: 1
     Code start=0x00501651 end=0x00730f22 (size=0x0022f8d1) count: 62890
     Data start=0x00730f26 end=0x00750ab3 (size=0x0001fb8d) count: 1
```

Note that while the decrease in the global section is small, we have a
significant size decrease in the import and the export sections, because
we used to import globals and export them just to relay those globals to
the secondary modules.
aheejin added a commit that referenced this pull request Mar 10, 2026
When a global is exclusively used by a secondary module, we can move it
to the secondary module. If its initializer contains a `global.get` of
another global, we exported it from the primary module to the secondary
module, even if it may not be used anywhere else.

When we split a global out to a secondary module, this PR computes the
transitive dependency of the split global, and if those globals in the
dependency are not used anywhere else in other modules, we move them to
the secondary module as well.

 #8441 and this PR combined reduce the size of the primary module by
29%.

The running time of `wasm-split` hasn't really changed with this PR,
compared to #8441.

---

`wasm-objdump -h` result:

- Before (#8441)
     Type start=0x0000000c end=0x00035d44 (size=0x00035d38) count: 11185
   Import start=0x00035d49 end=0x003faf6f (size=0x003c5226) count: 56805
 Function start=0x003faf73 end=0x0040de1f (size=0x00012eac) count: 62890
    Table start=0x0040de22 end=0x0041195d (size=0x00003b3b) count: 2921
      Tag start=0x0041195f end=0x00411963 (size=0x00000004) count: 1
   Global start=0x00411967 end=0x005541c5 (size=0x0014285e) count: 47771
   Export start=0x005541ca end=0x007c0a00 (size=0x0026c836) count: 59077
    Start start=0x007c0a02 end=0x007c0a04 (size=0x00000002) start: 828
     Elem start=0x007c0a08 end=0x0082a84b (size=0x00069e43) count: 12303
DataCount start=0x0082a84d end=0x0082a84e (size=0x00000001) count: 1
     Code start=0x0082a853 end=0x00a5a159 (size=0x0022f906) count: 62890
     Data start=0x00a5a15d end=0x00a79cea (size=0x0001fb8d) count: 1
```

- After (This PR)
```
     Type start=0x0000000c end=0x00035d44 (size=0x00035d38) count: 11185
   Import start=0x00035d48 end=0x00132efc (size=0x000fd1b4) count: 32642
 Function start=0x00132f00 end=0x00145dac (size=0x00012eac) count: 62890
    Table start=0x00145daf end=0x001498ea (size=0x00003b3b) count: 2921
      Tag start=0x001498ec end=0x001498f0 (size=0x00000004) count: 1
   Global start=0x001498f4 end=0x00289e60 (size=0x0014056c) count: 47728
   Export start=0x00289e65 end=0x004977fe (size=0x0020d999) count: 35861
    Start start=0x00497800 end=0x00497802 (size=0x00000002) start: 828
     Elem start=0x00497806 end=0x00501649 (size=0x00069e43) count: 12303
DataCount start=0x0050164b end=0x0050164c (size=0x00000001) count: 1
     Code start=0x00501651 end=0x00730f22 (size=0x0022f8d1) count: 62890
     Data start=0x00730f26 end=0x00750ab3 (size=0x0001fb8d) count: 1
```

Note that while the decrease in the global section is small, we have a
significant size decrease in the import and the export sections, because
we used to import globals and export them just to relay those globals to
the secondary modules.
aheejin added a commit that referenced this pull request Mar 10, 2026
When a global is exclusively used by a secondary module, we can move it
to the secondary module. If its initializer contains a `global.get` of
another global, we exported it from the primary module to the secondary
module, even if it may not be used anywhere else.

When we split a global out to a secondary module, this PR computes the
transitive dependency of the split global, and if those globals in the
dependency are not used anywhere else in other modules, we move them to
the secondary module as well.

 #8441 and this PR combined reduce the size of the primary module by
29%.

The running time of `wasm-split` hasn't really changed with this PR,
compared to #8441.

---

`wasm-objdump -h` result:

- Before (#8441)
```
     Type start=0x0000000c end=0x00035d44 (size=0x00035d38) count: 11185
   Import start=0x00035d49 end=0x003faf6f (size=0x003c5226) count: 56805
 Function start=0x003faf73 end=0x0040de1f (size=0x00012eac) count: 62890
    Table start=0x0040de22 end=0x0041195d (size=0x00003b3b) count: 2921
      Tag start=0x0041195f end=0x00411963 (size=0x00000004) count: 1
   Global start=0x00411967 end=0x005541c5 (size=0x0014285e) count: 47771
   Export start=0x005541ca end=0x007c0a00 (size=0x0026c836) count: 59077
    Start start=0x007c0a02 end=0x007c0a04 (size=0x00000002) start: 828
     Elem start=0x007c0a08 end=0x0082a84b (size=0x00069e43) count: 12303
DataCount start=0x0082a84d end=0x0082a84e (size=0x00000001) count: 1
     Code start=0x0082a853 end=0x00a5a159 (size=0x0022f906) count: 62890
     Data start=0x00a5a15d end=0x00a79cea (size=0x0001fb8d) count: 1
```

- After (This PR)
```
     Type start=0x0000000c end=0x00035d44 (size=0x00035d38) count: 11185
   Import start=0x00035d48 end=0x00132efc (size=0x000fd1b4) count: 32642
 Function start=0x00132f00 end=0x00145dac (size=0x00012eac) count: 62890
    Table start=0x00145daf end=0x001498ea (size=0x00003b3b) count: 2921
      Tag start=0x001498ec end=0x001498f0 (size=0x00000004) count: 1
   Global start=0x001498f4 end=0x00289e60 (size=0x0014056c) count: 47728
   Export start=0x00289e65 end=0x004977fe (size=0x0020d999) count: 35861
    Start start=0x00497800 end=0x00497802 (size=0x00000002) start: 828
     Elem start=0x00497806 end=0x00501649 (size=0x00069e43) count: 12303
DataCount start=0x0050164b end=0x0050164c (size=0x00000001) count: 1
     Code start=0x00501651 end=0x00730f22 (size=0x0022f8d1) count: 62890
     Data start=0x00730f26 end=0x00750ab3 (size=0x0001fb8d) count: 1
```

Note that while the decrease in the global section is small, we have a
significant size decrease in the import and the export sections, because
we used to import globals and export them just to relay those globals to
the secondary modules.
aheejin added a commit that referenced this pull request Mar 10, 2026
When a global is exclusively used by a secondary module and thus moved
to that module, and its initializer has a `(ref.func $func)`, we used to
create a trampoline and export it from the primary module in all cases,
even in the case that the function is in the same secondary module. This
now moves those functions referred to by `ref.func`s to the secondary
module, as long as they don't have uses anywhere else.

To do this, we now skip scanning global initializers in
`indirectReferencesToSecondaryFunctions`, and selectively create
trampolines only when needed in `shareImportableItems`.

The running time of `wasm-split` hasn't really changed with this PR,
compared to the previous PR #8442 (~25s range in acx_gallery).

 #8441, #8442, and this PR combined reduce the size of the primary
module by 46.6%.

---

`wasm-objdump -h` result:

- Before (#8442)
```
     Type start=0x0000000c end=0x00035d44 (size=0x00035d38) count: 11185
   Import start=0x00035d48 end=0x00132efc (size=0x000fd1b4) count: 32642
 Function start=0x00132f00 end=0x00145dac (size=0x00012eac) count: 62890
    Table start=0x00145daf end=0x001498ea (size=0x00003b3b) count: 2921
      Tag start=0x001498ec end=0x001498f0 (size=0x00000004) count: 1
   Global start=0x001498f4 end=0x00289e60 (size=0x0014056c) count: 47728
   Export start=0x00289e65 end=0x004977fe (size=0x0020d999) count: 35861
    Start start=0x00497800 end=0x00497802 (size=0x00000002) start: 828
     Elem start=0x00497806 end=0x00501649 (size=0x00069e43) count: 12303
DataCount start=0x0050164b end=0x0050164c (size=0x00000001) count: 1
     Code start=0x00501651 end=0x00730f22 (size=0x0022f8d1) count: 62890
     Data start=0x00730f26 end=0x00750ab3 (size=0x0001fb8d) count: 1
```

- After (This PR)
```
     Type start=0x0000000c end=0x00035d38 (size=0x00035d2c) count: 11185
   Import start=0x00035d3c end=0x00132ef0 (size=0x000fd1b4) count: 32642
 Function start=0x00132ef4 end=0x001436cc (size=0x000107d8) count: 53001
    Table start=0x001436cf end=0x0014720a (size=0x00003b3b) count: 2921
      Tag start=0x0014720c end=0x00147210 (size=0x00000004) count: 1
   Global start=0x00147214 end=0x00287b75 (size=0x00140961) count: 47728
   Export start=0x00287b79 end=0x002e703f (size=0x0005f4c6) count: 25972
    Start start=0x002e7041 end=0x002e7043 (size=0x00000002) start: 828
     Elem start=0x002e7047 end=0x00349aa7 (size=0x00062a60) count: 12303
DataCount start=0x00349aa9 end=0x00349aaa (size=0x00000001) count: 1
     Code start=0x00349aaf end=0x00550a4e (size=0x00206f9f) count: 53001
     Data start=0x00550a52 end=0x005705df (size=0x0001fb8d) count: 1
```

We can see while the size of the function and the code sections have
decreased, the big gains come from the decrease of the export section,
which can contain long function names.
@aheejin
Copy link
Member Author

aheejin commented Mar 10, 2026

cc @biggs0125

@tlively
Copy link
Member

tlively commented Mar 10, 2026

The reason is, while we shed some exports for globals, we gained exports for functions (due to globals ref.func dependences), which had a lot longer names.

I'm surprised the length of names factors into this. I thought we used minimized names for internal exports. Am I misremembering?

@aheejin
Copy link
Member Author

aheejin commented Mar 10, 2026

Sorry, you're right, I was running the tests with -g option. (I was confused and I thought I needed that option to 'output' the name section, which was not true) This PR does not increase the size of the primary module.

I reran all tests for this and the two follow-up PRs and updated the PR description. The primary module size is

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