Skip to content

Opportunisticly emit compact imports#8926

Open
tlively wants to merge 9 commits into
compact-imports-textfrom
opportunistic-compact-imports
Open

Opportunisticly emit compact imports#8926
tlively wants to merge 9 commits into
compact-imports-textfrom
opportunistic-compact-imports

Conversation

@tlively

@tlively tlively commented Jul 22, 2026

Copy link
Copy Markdown
Member

In the binary writer when compact imports are enabled, opportunistically look for pairs of adjacent imports that share both their module names and import types, or alternatively just their module names. When a pair of matching imports is found, create a run of compact imports that includes as many subsequent imports as possible. Adding a pass that will reorder imports to purposefully put similar imports together is left as future work.

@tlively tlively changed the title [WIP SLOP] Opportunisticly emit compact imports Opportunisticly emit compact imports Jul 23, 2026
@tlively
tlively marked this pull request as ready for review July 23, 2026 01:56
@tlively
tlively requested a review from a team as a code owner July 23, 2026 01:56
@tlively
tlively requested review from kripken and removed request for a team July 23, 2026 01:56
Comment thread src/wasm/wasm-binary.cpp Outdated
if (const auto* fa = std::get_if<Function*>(&a)) {
auto* fb = std::get<Function*>(b);
return (*fa)->type.isExact() == fb->type.isExact() &&
(*fa)->type.getHeapType() == fb->type.getHeapType();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why isn't this just (*fa)->type == fb->type?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

No reason, that would be a good simplification.

Comment thread src/wasm/wasm-binary.cpp Outdated
return (*ma)->initial == mb->initial && (*ma)->max == mb->max &&
(*ma)->hasMax() == mb->hasMax() && (*ma)->shared == mb->shared &&
(*ma)->is64() == mb->is64() &&
(*ma)->pageSizeLog2 == mb->pageSizeLog2;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We have MemoryUtils::isSubType - we can check a <= b && b <= a here? (maybe adding a helper for it in MemoryUtils?)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Sure, or if that method isn't appropriate due to the runtime interaction Steven mentioned, I can create another helper.

Comment thread src/wasm/wasm-binary.cpp Outdated
auto* tb = std::get<Table*>(b);
return (*ta)->type == tb->type && (*ta)->initial == tb->initial &&
(*ta)->max == tb->max && (*ta)->hasMax() == tb->hasMax() &&
(*ta)->is64() == tb->is64();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We have RuntimeTable::isSubType, which we could use like above? Though perhaps first with moving isSubType to TableUtils?)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(in these two comments I am trying to avoid duplicating this logic across the codebase, which would make later refactors harder/error prone)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

FWIW isSubType is in RuntimeTable because it's not possible to statically determine whether one table is a subtype of another. table.grow will affect the subtyping relationship and it would be wrong to try to compare two table definitions for subtyping (this is a bug we previously had in the interpreter).

This is checking if two table definitions are equivalent statically, so I think we could keep this logic separate or introduce a new helper function for it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Oh, good point. Perhaps we can share some of the logic between those, though?

Comment thread test/lit/d8/fuzz_shell_exceptions.wast Outdated
;; Build to a binary wasm.
;;
;; RUN: wasm-opt %s -o %t.wasm -q -all
;; RUN: wasm-opt %s -o %t.wasm -q -all --disable-compact-imports

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why is this needed?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

-all makes the output Wasm contain compact imports, but the V8 run later in this file does not enable compact imports. We could alternatively have fixed this by enabled compact imports in V8, which I suppose would have the benefit of showing that V8 parses them correctly. I'll change this.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

is --wasm-staging not enough, btw, for v8 to support compact imports? That would be more general if so.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Turns out --wasm-staging is not enough. I'll specifically enable --wasm-compact-imports.

wasm_bytes = self.get_binary(wat, ['--enable-compact-imports'])
self.assertIn(b'\x03env\x00\x7e', wasm_bytes)
self.assertIn(b'\x04math\x00\x7f', wasm_bytes)
self.assertIn(b'\x06single\x02m1', wasm_bytes)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Maybe it would be good to add a code size test here, something like 1,000 procedurally-generated imports with the same module, and seeing how much smaller the binary size is with the feature enabled?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Sure, I guess that would catch accidental regressions.

Comment thread src/wasm/wasm-binary.cpp Outdated
Comment on lines +334 to +338
template<class... Ts> struct Overloaded : Ts... {
using Ts::operator()...;
};
template<class... Ts> Overloaded(Ts...) -> Overloaded<Ts...>;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This already exists in utilities.h.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Ah, thanks! I thought I had seen it before.

Comment thread src/wasm/wasm-binary.cpp Outdated
Comment on lines +350 to +359
ModuleUtils::iterImportedFunctions(
*wasm, [&](Function* func) { imports.push_back(func); });
ModuleUtils::iterImportedGlobals(
*wasm, [&](Global* global) { imports.push_back(global); });
ModuleUtils::iterImportedTags(*wasm,
[&](Tag* tag) { imports.push_back(tag); });
ModuleUtils::iterImportedMemories(
*wasm, [&](Memory* memory) { imports.push_back(memory); });
ModuleUtils::iterImportedTables(
*wasm, [&](Table* table) { imports.push_back(table); });

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This can be simplified to

ModuleUtils::iterImports(*wasm, [&](ImportItem import) { imports.push_back(import); }

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.

3 participants