Skip to content

Commit 2089525

Browse files
committed
[wasm-split] Do multi-split at once
This does multi-splitting of modules at once, rather than splitting them one by one by doing 2-way split n times. Previously when we did multi-splitting we split the 1st module as the "secondary" module assuming all other functions belonging to 2nd~nth modules as "primary" module. And then we repeat the same task for the 2nd module, assuming 3rd~nth module functions belong to the "primary" module. This unnecessarily repeated some tasks that could have been done once. This reduces the running time on a reproducer provided by @biggs0125 before (to fix #7725) from 236s to 88s, reducing it by around 63%. Some side-products of this PR are: - Now we only create a single table to host placeholders (or `ref.null`s in case of `--no-placeholders`) even when reference-types is enabled. Previously we created a table per secondary module, resulting in n tables. - The names of trampoline functions have been changed in the tests, but semantically they are the same. (e.g. in `test/lit/wasm-split/multi-split.wast`) The reason for the change is, previously we split modules one by one, by the time we split the first module, it assumed functions belonging to other secondary modules were primary functions, but they later changed to trampolines as well. Now they are all named as trampolines, arguably enhacing readability. --- Some detailed analysis run using the reproducer of #7725, a case where we split a module into 301 (1 primary + 300 secondary) modules: - Before this PR: Time: 236.8s Task breakdown: ``` Task Total Time (ms) Percentage --------------------------------------------------------------------------- shareImportableItems 62661.1860 28.24% classifyFunctions 42366.7451 19.09% removeUnusedSecondaryElements 33083.6602 14.91% indirectReferencesToSecondaryFunctions 27852.3143 12.55% indirectCallsToSecondaryFunctions 25091.4263 11.31% moveSecondaryFunctions 14159.9166 6.38% writeModule_secondary 9331.1667 4.20% setupTablePatching 3099.9597 1.40% initExportedPrimaryFuncs 1657.0465 0.75% writeModule_primary 901.6800 0.41% exportImportCalledPrimaryFunctions 892.0132 0.40% thunkExportedSecondaryFunctions 826.8599 0.37% initSecondary 0.2241 0.00% --------------------------------------------------------------------------- Overall Total 221924.1985 100.00% ``` - After this PR: Time : 88.40207334437098 Task breakdown: ``` Task Total Time (ms) Percentage --------------------------------------------------------------------------- shareImportableItems 40176.7000 50.38% removeUnusedSecondaryElements 28635.2000 35.91% moveSecondaryFunctions 5998.9600 7.52% writeModule_secondary 2611.0099 3.27% writeModule_primary 935.7750 1.17% exportImportCalledPrimaryFunctions 646.9860 0.81% indirectReferencesToSecondaryFunctions 318.2980 0.40% classifyFunctions 238.5780 0.30% indirectCallsToSecondaryFunctions 139.1730 0.17% setupTablePatching 44.1466 0.06% thunkExportedSecondaryFunctions 3.9405 0.00% initExportedPrimaryFuncs 0.6870 0.00% --------------------------------------------------------------------------- Overall Total 79749.4539 100.00% ``` We can see time taken in `classifyFunctions`, `indirectReferencesToSecondaryFunctions`, and `indirectCallsToSecondaryFunctions` has reduced basically to nothing. This is because now we can all functions only once in those functions, where we used to scan the functions n times or similar. Now `shareImportableItems` and `moveSecondaryFunctions` take up around 85% of the execution time. The reason `shareImportableItems` takes so long is the reproducer has 90k globals. ``` Analysis of shareImportableItems: Sub-Task Total Time (ms) Percentage --------------------------------------------------------------------------- globals 41166.4904 98.35% tables 535.5134 1.28% tags 10.3355 0.02% memories 7.5937 0.02% exports 1.5482 0.00% --------------------------------------------------------------------------- Total 41857.1000 100.00% ``` ('exports' meaning processing existing exports) We can probably improve this by selectively importing module items, as already noted by the existing TODO. `moveSecondaryFunctions` basically just runs RemoveUnusedModuleElements on each module. We can also consider parallelizing `moveSecondaryFunctions` by modules but not sure how much improvements it can bring given that the pass is already parallized in function granularity. But if we export only used items in `shareImportableItems`, running this pass may become unnecessary after all.
1 parent 959d522 commit 2089525

8 files changed

Lines changed: 419 additions & 378 deletions

File tree

src/ir/module-splitting.cpp

Lines changed: 271 additions & 193 deletions
Large diffs are not rendered by default.

src/ir/module-splitting.h

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,8 @@ namespace wasm::ModuleSplitting {
4747
static const Name LOAD_SECONDARY_MODULE("__load_secondary_module");
4848

4949
struct Config {
50-
// The set of functions to split into the secondary module. All others are
51-
// kept in the primary module. Must not include the start function if it
52-
// exists. May or may not include imported functions, which are always kept in
53-
// the primary module regardless.
54-
std::set<Name> secondaryFuncs;
50+
// Module names to the functions that should be in the modules.
51+
std::map<Name, std::set<Name>> moduleToFuncs;
5552
// Whether to import placeholder functions into the primary module that will
5653
// be called when a secondary function is called before the secondary module
5754
// has been loaded.
@@ -76,7 +73,7 @@ struct Config {
7673
};
7774

7875
struct Results {
79-
std::unique_ptr<Module> secondary;
76+
std::map<Name, std::unique_ptr<Module>> secondaryPtrMap;
8077
std::unordered_map<Name, std::map<size_t, Name>> placeholderMap;
8178
};
8279

src/tools/wasm-split/wasm-split.cpp

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ void splitModule(const WasmSplitOptions& options) {
329329

330330
// Actually perform the splitting
331331
ModuleSplitting::Config config;
332-
config.secondaryFuncs = std::move(splitFuncs);
332+
config.moduleToFuncs["secondary"] = std::move(splitFuncs);
333333
if (options.importNamespace.size()) {
334334
config.importNamespace = options.importNamespace;
335335
}
@@ -343,7 +343,7 @@ void splitModule(const WasmSplitOptions& options) {
343343
config.minimizeNewExportNames = !options.passOptions.debugInfo;
344344
config.jspi = options.jspi;
345345
auto splitResults = ModuleSplitting::splitFunctions(wasm, config);
346-
auto& secondary = splitResults.secondary;
346+
auto& secondary = splitResults.secondaryPtrMap["secondary"];
347347

348348
adjustTableSize(wasm, options.initialTableSize);
349349
adjustTableSize(*secondary, options.initialTableSize, /*secondary=*/true);
@@ -389,27 +389,29 @@ void multiSplitModule(const WasmSplitOptions& options) {
389389
Module wasm;
390390
parseInput(wasm, options);
391391

392-
// Map module names to the functions that should be in the modules.
393-
std::map<Name, std::unordered_set<Name>> moduleFuncs;
394392
// The module for which we are currently parsing a set of functions.
395393
Name currModule;
396394
// The set of functions we are currently inserting into.
397-
std::unordered_set<Name>* currFuncs = nullptr;
395+
std::set<Name>* currFuncs = nullptr;
398396
// Map functions to their modules to ensure no function is assigned to
399397
// multiple modules.
400398
std::unordered_map<Name, Name> funcModules;
401399

400+
ModuleSplitting::Config config;
402401
std::string line;
403402
bool newSection = true;
403+
std::unordered_set<Name> moduleNames;
404404
while (std::getline(manifest, line)) {
405405
if (line.empty()) {
406406
newSection = true;
407407
continue;
408408
}
409409
Name name = WasmBinaryReader::escape(line);
410410
if (newSection) {
411-
currModule = name;
412-
currFuncs = &moduleFuncs[name];
411+
currModule = Names::getValidName(
412+
name, [&](Name n) { return moduleNames.find(n) == moduleNames.end(); });
413+
moduleNames.insert(currModule);
414+
currFuncs = &config.moduleToFuncs[currModule];
413415
newSection = false;
414416
continue;
415417
}
@@ -426,42 +428,38 @@ void multiSplitModule(const WasmSplitOptions& options) {
426428
}
427429
}
428430

429-
ModuleSplitting::Config config;
430431
config.usePlaceholders = options.usePlaceholders;
431432
config.importNamespace = options.importNamespace;
432433
config.minimizeNewExportNames = !options.passOptions.debugInfo;
433434
if (options.emitModuleNames && !wasm.name) {
434435
wasm.name = Path::getBaseName(options.output);
435436
}
436437

437-
std::unordered_map<Name, std::map<size_t, Name>> placeholderMap;
438-
for (auto& [mod, funcs] : moduleFuncs) {
439-
if (options.verbose) {
440-
std::cerr << "Splitting module " << mod << '\n';
441-
}
438+
for (auto& [mod, funcs] : config.moduleToFuncs) {
442439
if (!options.quiet && funcs.empty()) {
443440
std::cerr << "warning: Module " << mod << " will be empty\n";
444441
}
445-
config.secondaryFuncs = std::set<Name>(funcs.begin(), funcs.end());
446-
auto splitResults = ModuleSplitting::splitFunctions(wasm, config);
442+
}
443+
auto splitResults = ModuleSplitting::splitFunctions(wasm, config);
444+
for (auto& [mod, funcs] : config.moduleToFuncs) {
445+
Module& secondary = *splitResults.secondaryPtrMap[mod];
447446
auto moduleName = options.outPrefix + mod.toString() +
448447
(options.emitBinary ? ".wasm" : ".wast");
449448
if (options.symbolMap) {
450-
writeSymbolMap(*splitResults.secondary, moduleName + ".symbols");
451-
}
452-
if (options.placeholderMap) {
453-
placeholderMap.merge(splitResults.placeholderMap);
449+
writeSymbolMap(*splitResults.secondaryPtrMap[mod],
450+
moduleName + ".symbols");
454451
}
455452
if (options.emitModuleNames) {
456-
splitResults.secondary->name = Path::getBaseName(moduleName);
453+
secondary.name = Path::getBaseName(moduleName);
454+
}
455+
writeModule(secondary, moduleName, options);
456+
if (options.symbolMap) {
457+
writeSymbolMap(wasm, options.output + ".symbols");
458+
}
459+
if (options.placeholderMap) {
460+
writePlaceholderMap(
461+
wasm, splitResults.placeholderMap, options.output + ".placeholders");
457462
}
458-
writeModule(*splitResults.secondary, moduleName, options);
459-
}
460-
if (options.symbolMap) {
461-
writeSymbolMap(wasm, options.output + ".symbols");
462-
}
463-
if (options.placeholderMap) {
464-
writePlaceholderMap(wasm, placeholderMap, options.output + ".placeholders");
465463
}
466464
writeModule(wasm, options.output, options);
467465
}

test/example/module-splitting.cpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,10 @@ void do_test(const std::set<Name>& keptFuncs, std::string&& module) {
5151
std::cout << "\n";
5252

5353
ModuleSplitting::Config config;
54-
config.secondaryFuncs = std::move(splitFuncs);
54+
config.moduleToFuncs["secondary"] = std::move(splitFuncs);
5555
config.newExportPrefix = "%";
56-
auto secondary = splitFunctions(*primary, config).secondary;
56+
auto results = splitFunctions(*primary, config);
57+
auto& secondary = results.secondaryPtrMap["secondary"];
5758

5859
std::cout << "After:\n";
5960
std::cout << *primary.get();
@@ -475,11 +476,12 @@ void test_minimized_exports() {
475476
primary.addFunction(Builder::makeFunction("call", funcType, {}, callBody));
476477

477478
ModuleSplitting::Config config;
478-
config.secondaryFuncs = {"call"};
479+
config.moduleToFuncs["secondary"] = {"call"};
479480
config.newExportPrefix = "%";
480481
config.minimizeNewExportNames = true;
481482

482-
auto secondary = splitFunctions(primary, config).secondary;
483+
auto results = splitFunctions(primary, config);
484+
auto& secondary = results.secondaryPtrMap["secondary"];
483485
std::cout << "Minimized names primary:\n";
484486
std::cout << primary << "\n";
485487
std::cout << "Minimized names secondary:\n";

test/lit/wasm-split/multi-split-escape-names.wast

Lines changed: 32 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@
77
;; RUN: wasm-dis %t3.wasm | filecheck %s --check-prefix=MOD3
88

99
(module
10+
;; PRIMARY: (type $ret-i64 (func (result i64)))
11+
12+
;; PRIMARY: (type $ret-f32 (func (result f32)))
13+
1014
;; PRIMARY: (type $ret-i32 (func (result i32)))
1115
(type $ret-i32 (func (result i32)))
12-
;; PRIMARY: (type $ret-i64 (func (result i64)))
1316
(type $ret-i64 (func (result i64)))
14-
;; PRIMARY: (type $ret-f32 (func (result f32)))
1517
(type $ret-f32 (func (result f32)))
1618

1719
;; MOD1: (type $0 (func (result f32)))
@@ -20,13 +22,13 @@
2022

2123
;; MOD1: (type $2 (func (result i32)))
2224

23-
;; MOD1: (import "" "table" (table $timport$0 1 funcref))
25+
;; MOD1: (import "" "table" (table $timport$0 3 funcref))
2426

25-
;; MOD1: (import "" "std::operator<<\\28std::__2::basic_ostream<char\\2c\\20std::__2::char_traits<char>>&\\2c\\20wasm::Module&\\29" (func $std::operator<<\28std::__2::basic_ostream<char\2c\20std::__2::char_traits<char>>&\2c\20wasm::Module&\29 (result f32)))
27+
;; MOD1: (import "" "trampoline_std::operator<<\\28std::__2::basic_ostream<char\\2c\\20std::__2::char_traits<char>>&\\2c\\20wasm::Module&\\29" (func $trampoline_std::operator<<\28std::__2::basic_ostream<char\2c\20std::__2::char_traits<char>>&\2c\20wasm::Module&\29 (result f32)))
2628

27-
;; MOD1: (import "" "wasm::Literal::Literal\\28std::__2::array<wasm::Literal\\2c\\204ul>\\20const&\\29" (func $wasm::Literal::Literal\28std::__2::array<wasm::Literal\2c\204ul>\20const&\29 (result i64)))
29+
;; MOD1: (import "" "trampoline_wasm::Literal::Literal\\28std::__2::array<wasm::Literal\\2c\\204ul>\\20const&\\29" (func $trampoline_wasm::Literal::Literal\28std::__2::array<wasm::Literal\2c\204ul>\20const&\29 (result i64)))
2830

29-
;; MOD1: (elem $0 (i32.const 0) $wasm::Type::getFeatures\28\29\20const)
31+
;; MOD1: (elem $0 (i32.const 2) $wasm::Type::getFeatures\28\29\20const)
3032

3133
;; MOD1: (func $wasm::Type::getFeatures\28\29\20const (result i32)
3234
;; MOD1-NEXT: (drop
@@ -36,12 +38,12 @@
3638
;; MOD1-NEXT: )
3739
;; MOD1-NEXT: (drop
3840
;; MOD1-NEXT: (call_ref $1
39-
;; MOD1-NEXT: (ref.func $wasm::Literal::Literal\28std::__2::array<wasm::Literal\2c\204ul>\20const&\29)
41+
;; MOD1-NEXT: (ref.func $trampoline_wasm::Literal::Literal\28std::__2::array<wasm::Literal\2c\204ul>\20const&\29)
4042
;; MOD1-NEXT: )
4143
;; MOD1-NEXT: )
4244
;; MOD1-NEXT: (drop
4345
;; MOD1-NEXT: (call_ref $0
44-
;; MOD1-NEXT: (ref.func $std::operator<<\28std::__2::basic_ostream<char\2c\20std::__2::char_traits<char>>&\2c\20wasm::Module&\29)
46+
;; MOD1-NEXT: (ref.func $trampoline_std::operator<<\28std::__2::basic_ostream<char\2c\20std::__2::char_traits<char>>&\2c\20wasm::Module&\29)
4547
;; MOD1-NEXT: )
4648
;; MOD1-NEXT: )
4749
;; MOD1-NEXT: (i32.const 0)
@@ -71,9 +73,9 @@
7173

7274
;; MOD2: (type $2 (func (result i64)))
7375

74-
;; MOD2: (import "" "table_4" (table $timport$0 1 funcref))
76+
;; MOD2: (import "" "table" (table $timport$0 3 funcref))
7577

76-
;; MOD2: (import "" "std::operator<<\\28std::__2::basic_ostream<char\\2c\\20std::__2::char_traits<char>>&\\2c\\20wasm::Module&\\29" (func $std::operator<<\28std::__2::basic_ostream<char\2c\20std::__2::char_traits<char>>&\2c\20wasm::Module&\29 (result f32)))
78+
;; MOD2: (import "" "trampoline_std::operator<<\\28std::__2::basic_ostream<char\\2c\\20std::__2::char_traits<char>>&\\2c\\20wasm::Module&\\29" (func $trampoline_std::operator<<\28std::__2::basic_ostream<char\2c\20std::__2::char_traits<char>>&\2c\20wasm::Module&\29 (result f32)))
7779

7880
;; MOD2: (import "" "trampoline_wasm::Type::getFeatures\\28\\29\\20const" (func $trampoline_wasm::Type::getFeatures\28\29\20const (result i32)))
7981

@@ -92,7 +94,7 @@
9294
;; MOD2-NEXT: )
9395
;; MOD2-NEXT: (drop
9496
;; MOD2-NEXT: (call_ref $0
95-
;; MOD2-NEXT: (ref.func $std::operator<<\28std::__2::basic_ostream<char\2c\20std::__2::char_traits<char>>&\2c\20wasm::Module&\29)
97+
;; MOD2-NEXT: (ref.func $trampoline_std::operator<<\28std::__2::basic_ostream<char\2c\20std::__2::char_traits<char>>&\2c\20wasm::Module&\29)
9698
;; MOD2-NEXT: )
9799
;; MOD2-NEXT: )
98100
;; MOD2-NEXT: (i64.const 0)
@@ -122,13 +124,13 @@
122124

123125
;; MOD3: (type $2 (func (result f32)))
124126

125-
;; MOD3: (import "" "table_5" (table $timport$0 1 funcref))
127+
;; MOD3: (import "" "table" (table $timport$0 3 funcref))
126128

127-
;; MOD3: (import "" "wasm::Literal::Literal\\28std::__2::array<wasm::Literal\\2c\\204ul>\\20const&\\29" (func $trampoline_wasm::Literal::Literal\28std::__2::array<wasm::Literal\2c\204ul>\20const&\29 (result i64)))
129+
;; MOD3: (import "" "trampoline_wasm::Literal::Literal\\28std::__2::array<wasm::Literal\\2c\\204ul>\\20const&\\29" (func $trampoline_wasm::Literal::Literal\28std::__2::array<wasm::Literal\2c\204ul>\20const&\29 (result i64)))
128130

129131
;; MOD3: (import "" "trampoline_wasm::Type::getFeatures\\28\\29\\20const" (func $trampoline_wasm::Type::getFeatures\28\29\20const (result i32)))
130132

131-
;; MOD3: (elem $0 (i32.const 0) $std::operator<<\28std::__2::basic_ostream<char\2c\20std::__2::char_traits<char>>&\2c\20wasm::Module&\29)
133+
;; MOD3: (elem $0 (i32.const 1) $std::operator<<\28std::__2::basic_ostream<char\2c\20std::__2::char_traits<char>>&\2c\20wasm::Module&\29)
132134

133135
;; MOD3: (func $std::operator<<\28std::__2::basic_ostream<char\2c\20std::__2::char_traits<char>>&\2c\20wasm::Module&\29 (result f32)
134136
;; MOD3-NEXT: (drop
@@ -167,50 +169,38 @@
167169
(f32.const 0)
168170
)
169171
)
170-
;; PRIMARY: (import "placeholder" "0" (func $placeholder_0 (result i32)))
172+
;; PRIMARY: (import "placeholder" "0" (func $placeholder_0 (result i64)))
171173

172-
;; PRIMARY: (import "placeholder" "0" (func $placeholder_0_4 (result i64)))
174+
;; PRIMARY: (import "placeholder" "1" (func $placeholder_1 (result f32)))
173175

174-
;; PRIMARY: (import "placeholder" "0" (func $placeholder_0_5 (result f32)))
176+
;; PRIMARY: (import "placeholder" "2" (func $placeholder_2 (result i32)))
175177

176-
;; PRIMARY: (table $0 1 funcref)
178+
;; PRIMARY: (table $0 3 funcref)
177179

178-
;; PRIMARY: (table $1 1 funcref)
180+
;; PRIMARY: (elem $0 (i32.const 0) $placeholder_0 $placeholder_1 $placeholder_2)
179181

180-
;; PRIMARY: (table $2 1 funcref)
182+
;; PRIMARY: (export "trampoline_std::operator<<\\28std::__2::basic_ostream<char\\2c\\20std::__2::char_traits<char>>&\\2c\\20wasm::Module&\\29" (func $trampoline_std::operator<<\28std::__2::basic_ostream<char\2c\20std::__2::char_traits<char>>&\2c\20wasm::Module&\29))
181183

182-
;; PRIMARY: (elem $0 (table $0) (i32.const 0) func $placeholder_0)
183-
184-
;; PRIMARY: (elem $1 (table $1) (i32.const 0) func $placeholder_0_4)
185-
186-
;; PRIMARY: (elem $2 (table $2) (i32.const 0) func $placeholder_0_5)
187-
188-
;; PRIMARY: (export "std::operator<<\\28std::__2::basic_ostream<char\\2c\\20std::__2::char_traits<char>>&\\2c\\20wasm::Module&\\29" (func $trampoline_std::operator<<\28std::__2::basic_ostream<char\2c\20std::__2::char_traits<char>>&\2c\20wasm::Module&\29))
189-
190-
;; PRIMARY: (export "wasm::Literal::Literal\\28std::__2::array<wasm::Literal\\2c\\204ul>\\20const&\\29" (func $trampoline_wasm::Literal::Literal\28std::__2::array<wasm::Literal\2c\204ul>\20const&\29))
191-
192-
;; PRIMARY: (export "table" (table $0))
184+
;; PRIMARY: (export "trampoline_wasm::Literal::Literal\\28std::__2::array<wasm::Literal\\2c\\204ul>\\20const&\\29" (func $trampoline_wasm::Literal::Literal\28std::__2::array<wasm::Literal\2c\204ul>\20const&\29))
193185

194186
;; PRIMARY: (export "trampoline_wasm::Type::getFeatures\\28\\29\\20const" (func $trampoline_wasm::Type::getFeatures\28\29\20const))
195187

196-
;; PRIMARY: (export "table_4" (table $1))
197-
198-
;; PRIMARY: (export "table_5" (table $2))
188+
;; PRIMARY: (export "table" (table $0))
199189

200-
;; PRIMARY: (func $trampoline_wasm::Type::getFeatures\28\29\20const (result i32)
201-
;; PRIMARY-NEXT: (call_indirect (type $ret-i32)
190+
;; PRIMARY: (func $trampoline_wasm::Literal::Literal\28std::__2::array<wasm::Literal\2c\204ul>\20const&\29 (result i64)
191+
;; PRIMARY-NEXT: (call_indirect (type $ret-i64)
202192
;; PRIMARY-NEXT: (i32.const 0)
203193
;; PRIMARY-NEXT: )
204194
;; PRIMARY-NEXT: )
205195

206-
;; PRIMARY: (func $trampoline_wasm::Literal::Literal\28std::__2::array<wasm::Literal\2c\204ul>\20const&\29 (result i64)
207-
;; PRIMARY-NEXT: (call_indirect $1 (type $ret-i64)
208-
;; PRIMARY-NEXT: (i32.const 0)
196+
;; PRIMARY: (func $trampoline_std::operator<<\28std::__2::basic_ostream<char\2c\20std::__2::char_traits<char>>&\2c\20wasm::Module&\29 (result f32)
197+
;; PRIMARY-NEXT: (call_indirect (type $ret-f32)
198+
;; PRIMARY-NEXT: (i32.const 1)
209199
;; PRIMARY-NEXT: )
210200
;; PRIMARY-NEXT: )
211201

212-
;; PRIMARY: (func $trampoline_std::operator<<\28std::__2::basic_ostream<char\2c\20std::__2::char_traits<char>>&\2c\20wasm::Module&\29 (result f32)
213-
;; PRIMARY-NEXT: (call_indirect $2 (type $ret-f32)
214-
;; PRIMARY-NEXT: (i32.const 0)
202+
;; PRIMARY: (func $trampoline_wasm::Type::getFeatures\28\29\20const (result i32)
203+
;; PRIMARY-NEXT: (call_indirect (type $ret-i32)
204+
;; PRIMARY-NEXT: (i32.const 2)
215205
;; PRIMARY-NEXT: )
216206
;; PRIMARY-NEXT: )

0 commit comments

Comments
 (0)