Skip to content
Open
15 changes: 5 additions & 10 deletions scripts/test/shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -439,15 +439,14 @@ def get_tests(test_dir, extensions=[], recursive=False):
'func.wast', # Duplicate parameter names not properly rejected
'global.wast', # Fail to parse table
'if.wast', # Requires more precise unreachable validation
'imports.wast', # Missing validation of missing function on instantiation
'imports.wast', # Requires fixing handling of mutation to imported globals
'proposals/threads/imports.wast', # Missing memory type validation on instantiation
'linking.wast', # Missing function type validation on instantiation
'proposals/threads/memory.wast', # Missing memory type validation on instantiation
'memory64-imports.wast', # Missing validation on instantiation
'annotations.wast', # String annotations IDs should be allowed
'id.wast', # Empty IDs should be disallowed
# Requires correct handling of tag imports from different instances of the same module,
# ref.null wast constants, and splitting for module instances
# Requires correct handling of tag imports from different instances of the same module
# and splitting for module instances
'instance.wast',
'table64.wast', # Requires validations for table size
'table_grow.wast', # Incorrect table linking semantics in interpreter
Expand All @@ -472,12 +471,8 @@ def get_tests(test_dir, extensions=[], recursive=False):
'type-rec.wast', # Missing function type validation on instantiation
'type-subtyping.wast', # ShellExternalInterface::callTable does not handle subtyping
'call_indirect.wast', # Bug with 64-bit inline element segment parsing
'memory64.wast', # Requires validations for memory size
'imports0.wast', # Missing memory type validation on instantiation
'imports2.wast', # Missing memory type validation on instantiation
'imports3.wast', # Missing memory type validation on instantiation
'linking0.wast', # Missing memory type validation on instantiation
'linking3.wast', # Fatal error on missing table.
'memory64.wast', # Requires validations on the max memory size
'imports3.wast', # Requires better checking of exports from the special "spectest" module
'i16x8_relaxed_q15mulr_s.wast', # Requires wast `either` support
'i32x4_relaxed_trunc.wast', # Requires wast `either` support
'i8x16_relaxed_swizzle.wast', # Requires wast `either` support
Expand Down
5 changes: 5 additions & 0 deletions src/ir/memory-utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@

namespace wasm::MemoryUtils {

bool isSubType(const Memory& a, const Memory& b) {
return a.shared == b.shared && a.addressType == b.addressType &&
a.initial >= b.initial && a.max <= b.max;
}

bool flatten(Module& wasm) {
// If there are no memories then they are already flat, in the empty sense.
if (wasm.memories.empty()) {
Expand Down
2 changes: 2 additions & 0 deletions src/ir/memory-utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@

namespace wasm::MemoryUtils {

bool isSubType(const Memory& a, const Memory& b);

// Flattens memory into a single data segment, or no segment. If there is
// a segment, it starts at 0.
// Returns true if successful (e.g. relocatable segments cannot be flattened).
Expand Down
20 changes: 20 additions & 0 deletions src/ir/table-utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,26 @@

namespace wasm::TableUtils {

bool isSubType(const Table& a, const Table& b) {
if (a.addressType != b.addressType) {
return false;
}

if (!Type::isSubType(a.type, b.type)) {
return false;
}

if (a.initial > b.initial) {
return false;
}

if (a.max < b.max) {
return false;
}

return true;
}

std::set<Name> getFunctionsNeedingElemDeclare(Module& wasm) {
// Without reference types there are no ref.funcs or elem declare.
if (!wasm.features.hasReferenceTypes()) {
Expand Down
2 changes: 2 additions & 0 deletions src/ir/table-utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@

namespace wasm::TableUtils {

bool isSubType(const Table& a, const Table& b);

struct FlatTable {
std::vector<Name> names;
bool valid;
Expand Down
7 changes: 5 additions & 2 deletions src/shell-interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,11 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface {
auto inst = getImportInstance(import);
auto* exportedGlobal = inst->wasm.getExportOrNull(import->base);
if (!exportedGlobal || exportedGlobal->kind != ExternalKind::Global) {
Fatal() << "importGlobals: unknown import: " << import->module.str
<< "." << import->name.str;
trap((std::stringstream()
<< "importGlobals: unknown import: " << import->module.str << "."
<< import->name.str)
.str()
.c_str());
}
globals[import->name] = inst->globals[*exportedGlobal->getInternalName()];
});
Expand Down
2 changes: 2 additions & 0 deletions src/tools/wasm-shell.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@ struct Shell {
// SIMD instructions.
instance->setRelaxedBehavior(ModuleRunner::RelaxedBehavior::Execute);
instance->instantiate();
} catch (const std::exception& e) {
return Err{std::string("failed to instantiate module: ") + e.what()};
} catch (...) {
return Err{"failed to instantiate module"};
}
Expand Down
108 changes: 100 additions & 8 deletions src/wasm-interpreter.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,18 @@
#define wasm_wasm_interpreter_h

#include <cmath>
#include <iomanip>
#include <limits.h>
#include <sstream>
#include <variant>

#include "fp16.h"
#include "ir/intrinsics.h"
#include "ir/iteration.h"
#include "ir/memory-utils.h"
#include "ir/module-utils.h"
#include "ir/properties.h"
#include "ir/table-utils.h"
#include "support/bits.h"
#include "support/safe_integer.h"
#include "support/stdckdint.h"
Expand Down Expand Up @@ -86,7 +89,7 @@ class Flow {
}

Literals values;
Name breakTo; // if non-null, a break is going on
Name breakTo; // if non-null, a break is going on
Tag* suspendTag = nullptr; // if non-null, breakTo must be SUSPEND_FLOW, and
// this is the tag being suspended

Expand Down Expand Up @@ -3211,6 +3214,8 @@ class ModuleRunnerBase : public ExpressionRunner<SubType> {
// initialize the rest of the external interface
externalInterface->init(wasm, *self());

validateImports();

initializeTableContents();
initializeMemoryContents();

Expand Down Expand Up @@ -3302,6 +3307,84 @@ class ModuleRunnerBase : public ExpressionRunner<SubType> {
Name name;
};

// Validates that the export that provides `importable` exists and has the
// same kind that the import expects (`kind`).
void validateImportKindMatches(ExternalKind kind,
const Importable& importable) {
auto it = linkedInstances.find(importable.module);
if (it == linkedInstances.end()) {
trap((std::stringstream()
<< "Import module " << std::quoted(importable.module.toString())
<< " doesn't exist.")
.str()
.c_str());
}
SubType* importedInstance = it->second.get();

Export* export_ = importedInstance->wasm.getExportOrNull(importable.base);

if (!export_) {
trap((std::stringstream()
<< "Export " << importable.base << " doesn't exist.")
.str()
.c_str());
}
if (export_->kind != kind) {
trap((std::stringstream() << "Exported kind: " << export_->kind
<< " doesn't match expected kind: " << kind)
.str()
.c_str());
}
}

// Trap if types don't match between all imports and their corresponding
// exports. Imported memories and tables must also be a subtype of their
// export.
void validateImports() {
ModuleUtils::iterImportable(
wasm,
[this](ExternalKind kind,
std::variant<Function*, Memory*, Tag*, Global*, Table*> import) {
Importable* importable = std::visit(
[](const auto& import) -> Importable* { return import; }, import);

// These two modules are injected implicitly to tests. We won't find any
// import information for them.
if (importable->module == "binaryen-intrinsics" ||
(importable->module == "spectest" &&
importable->base.startsWith("print")) ||
importable->module == "fuzzing-support") {
return;
}

validateImportKindMatches(kind, *importable);

SubType* importedInstance =
linkedInstances.at(importable->module).get();
Export* export_ =
importedInstance->wasm.getExportOrNull(importable->base);

if (auto** memory = std::get_if<Memory*>(&import)) {
Memory exportedMemory =
*importedInstance->wasm.getMemory(*export_->getInternalName());
exportedMemory.initial =
importedInstance->getMemorySize(*export_->getInternalName());

if (!MemoryUtils::isSubType(exportedMemory, **memory)) {
trap("Imported memory isn't compatible.");
}
}

if (auto** table = std::get_if<Table*>(&import)) {
Table* exportedTable =
importedInstance->wasm.getTable(*export_->getInternalName());
if (!TableUtils::isSubType(**table, *exportedTable)) {
trap("Imported table isn't compatible");
}
}
});
}

TableInstanceInfo getTableInstanceInfo(Name name) {
auto* table = wasm.getTable(name);
if (table->imported()) {
Expand Down Expand Up @@ -3359,12 +3442,16 @@ class ModuleRunnerBase : public ExpressionRunner<SubType> {
};

MemoryInstanceInfo getMemoryInstanceInfo(Name name) {
auto* memory = wasm.getMemory(name);
if (memory->imported()) {
auto& importedInstance = linkedInstances.at(memory->module);
auto* memoryExport = importedInstance->wasm.getExport(memory->base);
return importedInstance->getMemoryInstanceInfo(
*memoryExport->getInternalName());
auto* instance = self();
Export* memoryExport = nullptr;
for (auto* memory = instance->wasm.getMemory(name); memory->imported();
memory = instance->wasm.getMemory(*memoryExport->getInternalName())) {
instance = instance->linkedInstances.at(memory->module).get();
memoryExport = instance->wasm.getExport(memory->base);
}

if (memoryExport) {
return instance->getMemoryInstanceInfo(*memoryExport->getInternalName());
}

return MemoryInstanceInfo{self(), name};
Expand Down Expand Up @@ -3418,6 +3505,11 @@ class ModuleRunnerBase : public ExpressionRunner<SubType> {
}

Address getMemorySize(Name memory) {
auto info = getMemoryInstanceInfo(memory);
if (info.instance != self()) {
return info.instance->getMemorySize(info.name);
}

auto iter = memorySizes.find(memory);
if (iter == memorySizes.end()) {
externalInterface->trap("getMemorySize called on non-existing memory");
Expand All @@ -3430,7 +3522,7 @@ class ModuleRunnerBase : public ExpressionRunner<SubType> {
if (iter == memorySizes.end()) {
externalInterface->trap("setMemorySize called on non-existing memory");
}
memorySizes[memory] = size;
iter->second = size;
}

public:
Expand Down
2 changes: 1 addition & 1 deletion test/lit/exec/exported-import.wast
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
;; no other output is expected (as the import does nothing and is ignored).

(module
(import "spectest" "nothing" (func $import))
(import "spectest" "print" (func $import))
(export "__wasm_call_ctors" (func $import))
)

Expand Down
7 changes: 2 additions & 5 deletions test/reduce/imports.wast.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
(module
(type $0 (func (result i32)))
(export "x" (func $0))
(func $0 (result i32)
(i32.const 5678)
)
(type $0 (func))
(import "env" "func" (func $fimport$0))
Copy link
Member

Choose a reason for hiding this comment

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

Why did this change?

Copy link
Member Author

Choose a reason for hiding this comment

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

Good point, this looks wrong. I might have added this while I tested it and forgot to revert. I'm guessing something traps during instantiation in this test because of the import which doesn't exist as far as wasm-reduce knows. Taking a look now.

)

40 changes: 20 additions & 20 deletions test/spec/exact-func-import.wast
Original file line number Diff line number Diff line change
@@ -1,29 +1,27 @@
;; TODO: Use the upstream version from the custom descriptors proposal.

(module
(module definition
(type $f (func))
(import "" "" (func $1 (exact (type 0))))
(import "" "" (func $2 (exact (type $f) (param) (result))))
(import "" "" (func $3 (exact (type $f))))
(import "" "" (func $4 (exact (type 1)))) ;; Implicitly defined next
(import "" "" (func $5 (exact (param i32) (result i64))))
;; (import "" "" (func $2 (exact (type $f) (param) (result)))) ;; TODO: parser support
(import "" "" (func $2 (exact (type $f))))
(import "" "" (func $3 (exact (type 1)))) ;; Implicitly defined next
(import "" "" (func $4 (exact (param i32) (result i64))))

(func $6 (import "" "") (exact (type 0)))
(func $7 (import "" "") (exact (type $f) (param) (result)))
(func $8 (import "" "") (exact (type $f)))
(func $9 (import "" "") (exact (type 2))) ;; Implicitly defined next
(func $10 (import "" "") (exact (param i64) (result i32)))
(func $5 (import "" "") (exact (type 0)))
;; (func $6 (import "" "") (exact (type $f) (param) (result))) ;; TODO: parser support
(func $6 (import "" "") (exact (type $f)))
;; (func $7 (import "" "") (exact (type 2))) ;; Implicitly defined next
;; (func $8 (import "" "") (exact (param i64) (result i32))) ;; TODO: parser support

(global (ref (exact $f)) (ref.func $1))
(global (ref (exact $f)) (ref.func $2))
(global (ref (exact $f)) (ref.func $3))
(global (ref (exact 1)) (ref.func $3))
(global (ref (exact 1)) (ref.func $4))
(global (ref (exact 1)) (ref.func $5))
(global (ref (exact $f)) (ref.func $5))
(global (ref (exact $f)) (ref.func $6))
(global (ref (exact $f)) (ref.func $7))
(global (ref (exact $f)) (ref.func $8))
(global (ref (exact 2)) (ref.func $9))
(global (ref (exact 2)) (ref.func $10))
;; (global (ref (exact 2)) (ref.func $7))
;; (global (ref (exact 2)) (ref.func $8))
)

;; References to inexact imports are not exact.
Expand Down Expand Up @@ -51,7 +49,7 @@

;; Inexact imports can still be referenced inexactly, though.

(module
(module definition
(type $f (func))
(import "" "" (func $1 (type $f)))
(global (ref $f) (ref.func $1))
Expand All @@ -70,7 +68,9 @@
;; Import and re-export inexactly.
(module $B
(type $f (func))
(func (export "f") (import "A" "f") (type $f))
;; (func (import "A" "f") (export "f") (type $f))
(func (import "A" "f") (type $f))
(export "f" (func 0))
)
(register "B")

Expand Down Expand Up @@ -220,7 +220,7 @@
;; Test the binary format

;; Exact function imports use 0x20.
(module binary
(module definition binary
"\00asm" "\01\00\00\00"
"\01" ;; Type section id
"\04" ;; Type section length
Expand Down Expand Up @@ -265,4 +265,4 @@
"\0b" ;; End
)
"malformed export kind"
)
)
3 changes: 3 additions & 0 deletions test/spec/ref_func.wast
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
;; TODO: use test/spec/testsuite/ref_func.wast once it's fixed.

(module
(func (export "f") (param $x i32) (result i32) (local.get $x))
)
(register "M")
(module
(func $f (import "M" "f") (param i32) (result i32))
(func $g (param $x i32) (result i32) (i32.add (local.get $x) (i32.const 1)))
Expand Down
Loading
Loading