Skip to content

Commit

Permalink
Fuzzer: Add a pass to prune illegal imports and exports for JS (WebAs…
Browse files Browse the repository at this point in the history
…sembly#6312)

We already have passes to legalize i64 imports and exports, which the fuzzer will
run so that we can run wasm files in JS VMs. SIMD and multivalue also pose a
problem as they trap on the boundary. In principle we could legalize them as well,
but that is substantial effort, so instead just prune them: given a wasm module,
remove any imports or exports that use SIMD or multivalue (or anything else that
is not legal for JS).

Running this in the fuzzer will allow us to not skip running v8 on any testcase we
enable SIMD and multivalue for.

(Multivalue is allowed in newer VMs, so that part of this PR could be removed
eventually.)

Also remove the limitation on running v8 with multimemory (v8 now supports
that).
  • Loading branch information
kripken authored Feb 21, 2024
1 parent 0ecea77 commit 1441bcb
Show file tree
Hide file tree
Showing 8 changed files with 316 additions and 3 deletions.
6 changes: 3 additions & 3 deletions scripts/fuzz_opt.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ def randomize_fuzz_settings():
FUZZ_OPTS += ['--no-fuzz-oob']
if random.random() < 0.5:
LEGALIZE = True
FUZZ_OPTS += ['--legalize-js-interface']
FUZZ_OPTS += ['--legalize-and-prune-js-interface']
else:
LEGALIZE = False

Expand Down Expand Up @@ -926,7 +926,7 @@ def compare_before_and_after(self, before, after):
compare(before[vm], after[vm], 'CompareVMs between before and after: ' + vm.name)

def can_run_on_feature_opts(self, feature_opts):
return all_disallowed(['simd', 'multivalue', 'multimemory'])
return True


# Check for determinism - the same command must have the same output.
Expand Down Expand Up @@ -957,7 +957,7 @@ def handle_pair(self, input, before_wasm, after_wasm, opts):
# later make sense (if we don't do this, the wasm may have i64 exports).
# after applying other necessary fixes, we'll recreate the after wasm
# from scratch.
run([in_bin('wasm-opt'), before_wasm, '--legalize-js-interface', '-o', before_wasm_temp] + FEATURE_OPTS)
run([in_bin('wasm-opt'), before_wasm, '--legalize-and-prune-js-interface', '-o', before_wasm_temp] + FEATURE_OPTS)
compare_before_to_after = random.random() < 0.5
compare_to_interpreter = compare_before_to_after and random.random() < 0.5
if compare_before_to_after:
Expand Down
5 changes: 5 additions & 0 deletions scripts/fuzz_shell.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ var imports = {
'log-i64': logValue,
'log-f32': logValue,
'log-f64': logValue,
// JS cannot log v128 values (we trap on the boundary), but we must still
// provide an import so that we do not trap during linking. (Alternatively,
// we could avoid running JS on code with SIMD in it, but it is useful to
// fuzz such code as much as we can.)
'log-v128': logValue,
},
'env': {
'setTempRet0': function(x) { tempRet0 = x },
Expand Down
86 changes: 86 additions & 0 deletions src/passes/LegalizeJSInterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@
// across modules, we still want to legalize dynCalls so JS can call into the
// tables even to a signature that is not legal.
//
// Another variation also "prunes" imports and exports that we cannot yet
// legalize, like exports and imports with SIMD or multivalue. Until we write
// the logic to legalize them, removing those imports/exports still allows us to
// fuzz all the legal imports/exports. (Note that multivalue is supported in
// exports in newer VMs - node 16+ - so that part is only needed for older VMs.)
//

#include "asmjs/shared-constants.h"
#include "ir/element-utils.h"
Expand All @@ -43,6 +49,8 @@

namespace wasm {

namespace {

// These are aliases for getTempRet0/setTempRet0 which emscripten defines in
// compiler-rt and exports under these names.
static Name GET_TEMP_RET_EXPORT("__get_temp_ret");
Expand Down Expand Up @@ -358,10 +366,88 @@ struct LegalizeJSInterface : public Pass {
}
};

struct LegalizeAndPruneJSInterface : public LegalizeJSInterface {
// Legalize fully (true) and add pruning on top.
LegalizeAndPruneJSInterface() : LegalizeJSInterface(true) {}

void run(Module* module) override {
LegalizeJSInterface::run(module);

prune(module);
}

void prune(Module* module) {
// For each function name, the exported id it is exported with. For
// example,
//
// (func $foo (export "bar")
//
// Would have exportedFunctions["foo"] = "bar";
std::unordered_map<Name, Name> exportedFunctions;
for (auto& exp : module->exports) {
if (exp->kind == ExternalKind::Function) {
exportedFunctions[exp->value] = exp->name;
}
}

for (auto& func : module->functions) {
// If the function is neither exported nor imported, no problem.
auto imported = func->imported();
auto exported = exportedFunctions.count(func->name);
if (!imported && !exported) {
continue;
}

// The params are allowed to be multivalue, but not the results. Otherwise
// look for SIMD.
auto sig = func->type.getSignature();
auto illegal = isIllegal(sig.results);
illegal =
illegal || std::any_of(sig.params.begin(),
sig.params.end(),
[&](const Type& t) { return isIllegal(t); });
if (!illegal) {
continue;
}

// Prune an import by implementing it in a trivial manner.
if (imported) {
func->module = func->base = Name();

Builder builder(*module);
if (sig.results == Type::none) {
func->body = builder.makeNop();
} else {
func->body =
builder.makeConstantExpression(Literal::makeZeros(sig.results));
}
}

// Prune an export by just removing it.
if (exported) {
module->removeExport(exportedFunctions[func->name]);
}
}

// TODO: globals etc.
}

bool isIllegal(Type type) {
auto features = type.getFeatures();
return features.hasSIMD() || features.hasMultivalue();
}
};

} // anonymous namespace

Pass* createLegalizeJSInterfacePass() { return new LegalizeJSInterface(true); }

Pass* createLegalizeJSInterfaceMinimallyPass() {
return new LegalizeJSInterface(false);
}

Pass* createLegalizeAndPruneJSInterfacePass() {
return new LegalizeAndPruneJSInterface();
}

} // namespace wasm
3 changes: 3 additions & 0 deletions src/passes/pass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,9 @@ void PassRegistry::registerPasses() {
"legalizes i64 types on the import/export boundary in a minimal "
"manner, only on things only JS will call",
createLegalizeJSInterfaceMinimallyPass);
registerPass("legalize-and-prune-js-interface",
"legalizes the import/export boundary and prunes when needed",
createLegalizeAndPruneJSInterfacePass);
registerPass("local-cse",
"common subexpression elimination inside basic blocks",
createLocalCSEPass);
Expand Down
1 change: 1 addition & 0 deletions src/passes/passes.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ Pass* createInliningPass();
Pass* createInliningOptimizingPass();
Pass* createJSPIPass();
Pass* createJ2CLOptsPass();
Pass* createLegalizeAndPruneJSInterfacePass();
Pass* createLegalizeJSInterfacePass();
Pass* createLegalizeJSInterfaceMinimallyPass();
Pass* createLimitSegmentsPass();
Expand Down
3 changes: 3 additions & 0 deletions test/lit/help/wasm-opt.test
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,9 @@
;; CHECK-NEXT: --jspi wrap imports and exports for
;; CHECK-NEXT: JavaScript promise integration
;; CHECK-NEXT:
;; CHECK-NEXT: --legalize-and-prune-js-interface legalizes the import/export
;; CHECK-NEXT: boundary and prunes when needed
;; CHECK-NEXT:
;; CHECK-NEXT: --legalize-js-interface legalizes i64 types on the
;; CHECK-NEXT: import/export boundary
;; CHECK-NEXT:
Expand Down
3 changes: 3 additions & 0 deletions test/lit/help/wasm2js.test
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,9 @@
;; CHECK-NEXT: --jspi wrap imports and exports for
;; CHECK-NEXT: JavaScript promise integration
;; CHECK-NEXT:
;; CHECK-NEXT: --legalize-and-prune-js-interface legalizes the import/export
;; CHECK-NEXT: boundary and prunes when needed
;; CHECK-NEXT:
;; CHECK-NEXT: --legalize-js-interface legalizes i64 types on the
;; CHECK-NEXT: import/export boundary
;; CHECK-NEXT:
Expand Down
Loading

0 comments on commit 1441bcb

Please sign in to comment.