Skip to content

Validate function imports #6315

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

Merged
merged 2 commits into from
Feb 20, 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
100 changes: 60 additions & 40 deletions src/wasm/wasm-validator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3305,11 +3305,6 @@ void FunctionValidator::visitResume(Resume* curr) {
}

void FunctionValidator::visitFunction(Function* curr) {
if (curr->getResults().isTuple()) {
shouldBeTrue(getModule()->features.hasMultivalue(),
curr->body,
"Multivalue function results (multivalue is not enabled)");
}
FeatureSet features;
// Check for things like having a rec group with GC enabled. The type we're
// checking is a reference type even if this an MVP function type, so ignore
Expand All @@ -3330,46 +3325,58 @@ void FunctionValidator::visitFunction(Function* curr) {
shouldBeTrue(features <= getModule()->features,
curr->name,
"all used types should be allowed");
if (curr->profile == IRProfile::Poppy) {
shouldBeTrue(
curr->body->is<Block>(), curr->body, "Function body must be a block");
}
// if function has no result, it is ignored
// if body is unreachable, it might be e.g. a return
shouldBeSubType(curr->body->type,
curr->getResults(),
curr->body,
"function body type must match, if function returns");
for (Type returnType : returnTypes) {
shouldBeSubType(returnType,
curr->getResults(),
curr->body,
"function result must match, if function has returns");
}

assert(breakTypes.empty());
assert(delegateTargetNames.empty());
assert(rethrowTargetNames.empty());
returnTypes.clear();
labelNames.clear();
// validate optional local names
std::unordered_set<Name> seen;
for (auto& pair : curr->localNames) {
Name name = pair.second;
shouldBeTrue(seen.insert(name).second, name, "local names must be unique");
}

if (getModule()->features.hasGC()) {
// If we have non-nullable locals, verify that local.get are valid.
LocalStructuralDominance info(curr, *getModule());
for (auto index : info.nonDominatingIndices) {
auto localType = curr->getLocalType(index);
for (auto type : localType) {
shouldBeTrue(!type.isNonNullable(),
index,
"non-nullable local's sets must dominate gets");
if (curr->body) {
if (curr->getResults().isTuple()) {
shouldBeTrue(getModule()->features.hasMultivalue(),
curr->body,
"Multivalue function results (multivalue is not enabled)");
}
if (curr->profile == IRProfile::Poppy) {
shouldBeTrue(
curr->body->is<Block>(), curr->body, "Function body must be a block");
}
// if function has no result, it is ignored
// if body is unreachable, it might be e.g. a return
shouldBeSubType(curr->body->type,
curr->getResults(),
curr->body,
"function body type must match, if function returns");
for (Type returnType : returnTypes) {
shouldBeSubType(returnType,
curr->getResults(),
curr->body,
"function result must match, if function has returns");
}

if (getModule()->features.hasGC()) {
// If we have non-nullable locals, verify that local.get are valid.
LocalStructuralDominance info(curr, *getModule());
for (auto index : info.nonDominatingIndices) {
auto localType = curr->getLocalType(index);
for (auto type : localType) {
shouldBeTrue(!type.isNonNullable(),
index,
"non-nullable local's sets must dominate gets");
}
}
}

// Assert that we finished with a clean state after processing the body's
// expressions, and reset the state for next time. Note that we use some of
// this state in the above validations, so this must appear last.
assert(breakTypes.empty());
assert(delegateTargetNames.empty());
assert(rethrowTargetNames.empty());
returnTypes.clear();
labelNames.clear();
}
}

Expand Down Expand Up @@ -3904,10 +3911,21 @@ bool WasmValidator::validate(Module& module, Flags flags) {
info.validateGlobally = (flags & Globally) != 0;
info.quiet = (flags & Quiet) != 0;
info.closedWorld = (flags & ClosedWorld) != 0;
// parallel wasm logic validation

// Parallel function validation.
PassRunner runner(&module);
FunctionValidator(module, &info).validate(&runner);
// validate globally
FunctionValidator functionValidator(module, &info);
functionValidator.validate(&runner);

// Also validate imports, which were not covered in the parallel traversal
// since it is a function-parallel operation.
for (auto& func : module.functions) {
if (func->imported()) {
functionValidator.visitFunction(func.get());
}
}

// Validate globally.
if (info.validateGlobally) {
validateImports(module, info);
validateExports(module, info);
Expand All @@ -3922,11 +3940,13 @@ bool WasmValidator::validate(Module& module, Flags flags) {
validateClosedWorldInterface(module, info);
}
}
// validate additional internal IR details when in pass-debug mode

// Validate additional internal IR details when in pass-debug mode.
if (PassRunner::getPassDebug()) {
validateBinaryenIR(module, info);
}
// print all the data

// Print all the data.
if (!info.valid.load() && !info.quiet) {
for (auto& func : module.functions) {
std::cerr << info.getStream(func.get()).str();
Expand Down
9 changes: 9 additions & 0 deletions test/lit/validation/imports.wast
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
;; Test that we validate imported functions.

;; RUN: not wasm-opt %s -all --disable-simd 2>&1 | filecheck %s

;; CHECK: all used types should be allowed

(module
(import "env" "imported-v128" (func $imported-v128 (result v128)))
)