Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fuzzer: Add a pass to prune illegal imports and exports for JS #6312

Merged
merged 12 commits into from
Feb 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -924,7 +924,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 @@ -955,7 +955,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 @@ -54,6 +54,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,
Comment on lines +57 to +61
Copy link
Member

Choose a reason for hiding this comment

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

Then what does it print for v128?

Copy link
Member Author

Choose a reason for hiding this comment

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

It will trap atm if it is called. But this is forward-looking in that if the JS-wasm spec some day allows this without trapping - maybe it becomes a JS array of 4 elements? - then it would print that JS value.

},
'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
Copy link
Member

Choose a reason for hiding this comment

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

Why would we need to legalize or prune out multivalue? We should only be testing on JS engines that fully support it, right?

// 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));
Comment on lines +421 to +422
Copy link
Member

Choose a reason for hiding this comment

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

What if the result type is not defaultable?

Copy link
Member Author

Choose a reason for hiding this comment

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

Then we'd be in trouble, but we already avoid this situation in the fuzzer because it is a problem in general: We can't send a non-null reference from JS. So the fuzzer fixes that up.

}
}

// 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 @@ -229,6 +229,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
Loading