Skip to content

Commit 6c97a76

Browse files
authored
Add a wasm-embed tool (#9127)
The tool embeds a given sequence of Wasm modules in place of the Wasm modules already present in a given JS file. Alternatively, with the --extract option, it extracts the embedded modules from the JS file and writes them as standalone Wasm files. This replaces the embed_wasms.py and extract_wasms.py scripts, reusing the C++ embedded module parsing logic added for wasm-reduce --js. The differences in behavior from the replaced scripts are that the new tool 1) can accept .wat files as input, assembling them before embedding, and 2) does not modify the original source when extracting modules and does not rely on a sentinel placeholder to determine where to embed new modules.
1 parent 5611af0 commit 6c97a76

14 files changed

Lines changed: 264 additions & 230 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ full changeset diff at the end of each section.
1515
Current Trunk
1616
-------------
1717

18+
- Add a new wasm-embed tool for extracting Wasm modules embedded as byte arrays
19+
in JS source files or replacing such modules in the JS source (#9127)
20+
1821
v133
1922
----
2023

‎scripts/clusterfuzz/embed_wasms.py‎

Lines changed: 0 additions & 76 deletions
This file was deleted.

‎scripts/clusterfuzz/extract_wasms.py‎

Lines changed: 0 additions & 94 deletions
This file was deleted.

‎scripts/fuzz_opt.py‎

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1863,10 +1863,7 @@ def handle_pair(self, input, before_wasm, after_wasm, opts):
18631863
if output != IGNORE and INSTANTIATE_ERROR not in output:
18641864
# Do the work to find if there were function exports: extract the
18651865
# wasm from the JS, and process it.
1866-
run([sys.executable,
1867-
in_binaryen('scripts', 'clusterfuzz', 'extract_wasms.py'),
1868-
fuzz_file,
1869-
'extracted'])
1866+
run([in_bin('wasm-embed'), '--extract', fuzz_file, 'extracted'])
18701867
if get_exports('extracted.0.wasm', ['func']):
18711868
assert FUZZ_EXEC_EXPORT_PREFIX in output
18721869

‎scripts/test/shared.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,7 @@ def is_exe(fpath):
284284
WASM_CTOR_EVAL = [os.path.join(options.binaryen_bin, 'wasm-ctor-eval')]
285285
WASM_SHELL = [os.path.join(options.binaryen_bin, 'wasm-shell')]
286286
WASM_REDUCE = [os.path.join(options.binaryen_bin, 'wasm-reduce')]
287+
WASM_EMBED = [os.path.join(options.binaryen_bin, 'wasm-embed')]
287288
WASM_METADCE = [os.path.join(options.binaryen_bin, 'wasm-metadce')]
288289
WASM_EMSCRIPTEN_FINALIZE = [os.path.join(options.binaryen_bin,
289290
'wasm-emscripten-finalize')]

‎scripts/update_help_checks.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626

2727
TOOLS = ['wasm-opt', 'wasm-as', 'wasm-dis', 'wasm2js', 'wasm-ctor-eval',
2828
'wasm-shell', 'wasm-reduce', 'wasm-metadce', 'wasm-split',
29-
'wasm-fuzz-types', 'wasm-emscripten-finalize', 'wasm-merge']
29+
'wasm-fuzz-types', 'wasm-emscripten-finalize', 'wasm-merge',
30+
'wasm-embed']
3031

3132

3233
def main():

‎src/tools/CMakeLists.txt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ if(NOT BUILD_EMSCRIPTEN_TOOLS_ONLY)
2121
binaryen_add_executable(wasm-merge wasm-merge.cpp)
2222
binaryen_add_executable(wasm-fuzz-types "${fuzzing_SOURCES};wasm-fuzz-types.cpp")
2323
binaryen_add_executable(wasm-fuzz-lattices "${fuzzing_SOURCES};wasm-fuzz-lattices.cpp")
24+
binaryen_add_executable(wasm-embed wasm-embed.cpp)
2425
add_subdirectory(wasm-reduce)
2526
endif()
2627

‎src/tools/wasm-embed.cpp‎

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
/*
2+
* Copyright 2026 WebAssembly Community Group participants
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
//
18+
// Embed Wasm binaries into a JS file, or extract embedded Wasm binaries from a
19+
// JS file.
20+
//
21+
22+
#include <iostream>
23+
#include <string>
24+
#include <vector>
25+
26+
#include "parsing.h"
27+
#include "pass.h"
28+
#include "support/command-line.h"
29+
#include "support/file.h"
30+
#include "support/js-embedded-module.h"
31+
#include "support/utilities.h"
32+
#include "wasm-io.h"
33+
#include "wasm-validator.h"
34+
35+
using namespace wasm;
36+
37+
int main(int argc, const char* argv[]) {
38+
const std::string WasmEmbedOption = "wasm-embed options";
39+
40+
bool extract = false;
41+
std::string output;
42+
std::vector<std::string> positionals;
43+
44+
Options options(
45+
"wasm-embed",
46+
"Embed Wasm binaries into a JS file, or extract embedded Wasm binaries "
47+
"from a JS file");
48+
options
49+
.add("--extract",
50+
"-e",
51+
"Extract embedded Wasm modules from the input JS file into "
52+
"<output>.0.wasm, <output>.1.wasm, ...",
53+
WasmEmbedOption,
54+
Options::Arguments::Zero,
55+
[&](Options* o, const std::string& argument) { extract = true; })
56+
.add("--output",
57+
"-o",
58+
"Output JS file (or output prefix when --extract is used)",
59+
WasmEmbedOption,
60+
Options::Arguments::One,
61+
[&](Options* o, const std::string& argument) { output = argument; })
62+
.add_positional("INFILE.js [WASM_FILES...] [OUTFILE]",
63+
Options::Arguments::N,
64+
[&](Options* o, const std::string& argument) {
65+
positionals.push_back(argument);
66+
});
67+
options.parse(argc, argv);
68+
69+
if (extract) {
70+
std::string inJsFile;
71+
if (output.empty()) {
72+
if (positionals.size() != 2) {
73+
Fatal() << "Expected input JS file and output prefix";
74+
}
75+
inJsFile = positionals[0];
76+
output = positionals[1];
77+
} else {
78+
if (positionals.size() != 1) {
79+
Fatal() << "Expected a single input JS file when --output is specified";
80+
}
81+
inJsFile = positionals[0];
82+
}
83+
84+
auto js = read_file<std::string>(inJsFile, Flags::Text);
85+
auto modules = findEmbeddedModules(js);
86+
for (size_t i = 0; i < modules.size(); ++i) {
87+
write_file(output + "." + std::to_string(i) + ".wasm", modules[i].bytes);
88+
}
89+
flush_and_quick_exit(0);
90+
}
91+
92+
std::string inJsFile;
93+
std::vector<std::string> wasmFiles;
94+
if (output.empty()) {
95+
if (positionals.size() < 2) {
96+
Fatal() << "Expected input JS file, wasm files, and output JS file";
97+
}
98+
inJsFile = positionals.front();
99+
output = positionals.back();
100+
wasmFiles.assign(positionals.begin() + 1, positionals.end() - 1);
101+
} else {
102+
if (positionals.empty()) {
103+
Fatal() << "Expected input JS file";
104+
}
105+
inJsFile = positionals.front();
106+
wasmFiles.assign(positionals.begin() + 1, positionals.end());
107+
}
108+
109+
auto js = read_file<std::string>(inJsFile, Flags::Text);
110+
auto existing = findEmbeddedModules(js);
111+
if (existing.size() != wasmFiles.size()) {
112+
Fatal() << "Number of embedded wasm modules in " << inJsFile << " ("
113+
<< existing.size()
114+
<< ") does not match number of provided wasm files ("
115+
<< wasmFiles.size() << ")";
116+
}
117+
118+
for (size_t i = existing.size(); i > 0; --i) {
119+
const auto& mod = existing[i - 1];
120+
const auto& wasmFile = wasmFiles[i - 1];
121+
auto bytes = read_file<std::vector<char>>(wasmFile, Flags::Binary);
122+
bool isBinary = bytes.size() >= 4 && bytes[0] == '\0' && bytes[1] == 'a' &&
123+
bytes[2] == 's' && bytes[3] == 'm';
124+
125+
Module wasm;
126+
wasm.features = FeatureSet::All;
127+
try {
128+
ModuleReader().readData(bytes, wasm);
129+
} catch (ParseException& p) {
130+
p.dump(std::cerr);
131+
std::cerr << '\n';
132+
Fatal() << "error parsing wasm (" << wasmFile << ")";
133+
}
134+
135+
if (!WasmValidator().validate(wasm)) {
136+
Fatal() << "error validating module (" << wasmFile << ")";
137+
}
138+
139+
if (!isBinary) {
140+
PassOptions passOptions;
141+
ModuleWriter writer(passOptions);
142+
writer.setBinary(true);
143+
writer.write(wasm, bytes);
144+
}
145+
146+
js.replace(mod.start, mod.end - mod.start, formatByteArray(bytes));
147+
}
148+
149+
write_file(output, js);
150+
flush_and_quick_exit(0);
151+
}

‎test/lit/help/wasm-embed.test‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
;; RUN: wasm-embed --help | filecheck %s
2+
;; CHECK: ================================================================================
3+
;; CHECK-NEXT: wasm-embed INFILE.js [WASM_FILES...] [OUTFILE]
4+
;; CHECK-EMPTY:
5+
;; CHECK-NEXT: Embed Wasm binaries into a JS file, or extract embedded Wasm binaries from a JS
6+
;; CHECK-NEXT: file
7+
;; CHECK-NEXT: ================================================================================
8+
;; CHECK-EMPTY:
9+
;; CHECK-EMPTY:
10+
;; CHECK-NEXT: wasm-embed options:
11+
;; CHECK-NEXT: -------------------
12+
;; CHECK-EMPTY:
13+
;; CHECK-NEXT: --extract,-e Extract embedded Wasm modules from the input JS file into
14+
;; CHECK-NEXT: <output>.0.wasm, <output>.1.wasm, ...
15+
;; CHECK-EMPTY:
16+
;; CHECK-NEXT: --output,-o Output JS file (or output prefix when --extract is used)
17+
;; CHECK-EMPTY:
18+
;; CHECK-EMPTY:
19+
;; CHECK-NEXT: General options:
20+
;; CHECK-NEXT: ----------------
21+
;; CHECK-EMPTY:
22+
;; CHECK-NEXT: --version Output version information and exit
23+
;; CHECK-EMPTY:
24+
;; CHECK-NEXT: --help,-h Show this help message and exit
25+
;; CHECK-EMPTY:
26+
;; CHECK-NEXT: --debug,-d Print debug information to stderr
27+
;; CHECK-EMPTY:

0 commit comments

Comments
 (0)