Skip to content

Commit

Permalink
wasm-merge: Check that the types of imports and exports match (#6437)
Browse files Browse the repository at this point in the history
  • Loading branch information
vouillon authored Mar 27, 2024
1 parent 61877e9 commit eae2638
Show file tree
Hide file tree
Showing 2 changed files with 192 additions and 0 deletions.
108 changes: 108 additions & 0 deletions src/tools/wasm-merge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,38 @@ void copyModuleContents(Module& input, Name inputName) {
// TODO: type names, features, debug info, custom sections, dylink info, etc.
}

void reportTypeMismatch(bool& valid, const char* kind, Importable* import) {
valid = false;
std::cerr << "Type mismatch when importing " << kind << " " << import->base
<< " from module " << import->module << " ($" << import->name
<< "): ";
}

// Check that the export and import limits match.
template<typename T>
void checkLimit(bool& valid, const char* kind, T* export_, T* import) {
if (export_->initial < import->initial) {
reportTypeMismatch(valid, kind, import);
std::cerr << "minimal size " << export_->initial
<< " is smaller than expected minimal size " << import->initial
<< ".\n";
}
if (import->hasMax()) {
if (!export_->hasMax()) {
reportTypeMismatch(valid, kind, import);
std::cerr << "expecting a bounded " << kind
<< " but the "
"imported "
<< kind << " is unbounded.\n";
} else if (export_->max > import->max) {
reportTypeMismatch(valid, kind, import);
std::cerr << "maximal size " << export_->max
<< " is larger than expected maximal size " << import->max
<< ".\n";
}
}
}

// Find pairs of matching imports and exports, and make uses of the import refer
// to the exported item (which has been merged into the module).
void fuseImportsAndExports() {
Expand Down Expand Up @@ -428,6 +460,82 @@ void fuseImportsAndExports() {
}
});

// Make sure that the export types match the import types.
bool valid = true;
ModuleUtils::iterImportedFunctions(merged, [&](Function* import) {
auto internalName = kindModuleExportMaps[ExternalKind::Function]
[import->module][import->base];
if (internalName.is()) {
auto* export_ = merged.getFunction(internalName);
if (!HeapType::isSubType(export_->type, import->type)) {
reportTypeMismatch(valid, "function", import);
std::cerr << "type " << export_->type << " is not a subtype of "
<< import->type << ".\n";
}
}
});
ModuleUtils::iterImportedTables(merged, [&](Table* import) {
auto internalName =
kindModuleExportMaps[ExternalKind::Table][import->module][import->base];
if (internalName.is()) {
auto* export_ = merged.getTable(internalName);
checkLimit(valid, "table", export_, import);
if (export_->type != import->type) {
reportTypeMismatch(valid, "table", import);
std::cerr << "export type " << export_->type
<< " is different from import type " << import->type << ".\n";
}
}
});
ModuleUtils::iterImportedMemories(merged, [&](Memory* import) {
auto internalName =
kindModuleExportMaps[ExternalKind::Memory][import->module][import->base];
if (internalName.is()) {
auto* export_ = merged.getMemory(internalName);
if (export_->is64() != import->is64()) {
reportTypeMismatch(valid, "memory", import);
std::cerr << "index type should match.\n";
}
checkLimit(valid, "memory", export_, import);
}
});
ModuleUtils::iterImportedGlobals(merged, [&](Global* import) {
auto internalName =
kindModuleExportMaps[ExternalKind::Global][import->module][import->base];
if (internalName.is()) {
auto* export_ = merged.getGlobal(internalName);
if (export_->mutable_ != import->mutable_) {
reportTypeMismatch(valid, "global", import);
std::cerr << "mutability should match.\n";
}
if (export_->mutable_ && export_->type != import->type) {
reportTypeMismatch(valid, "global", import);
std::cerr << "export type " << export_->type
<< " is different from import type " << import->type << ".\n";
}
if (!export_->mutable_ && !Type::isSubType(export_->type, import->type)) {
reportTypeMismatch(valid, "global", import);
std::cerr << "type " << export_->type << " is not a subtype of "
<< import->type << ".\n";
}
}
});
ModuleUtils::iterImportedTags(merged, [&](Tag* import) {
auto internalName =
kindModuleExportMaps[ExternalKind::Tag][import->module][import->base];
if (internalName.is()) {
auto* export_ = merged.getTag(internalName);
if (HeapType(export_->sig) != HeapType(import->sig)) {
reportTypeMismatch(valid, "tag", import);
std::cerr << "export type " << export_->sig
<< " is different from import type " << import->sig << ".\n";
}
}
});
if (!valid) {
Fatal() << "import/export mismatches";
}

// Update the things we found.
updateNames(merged, kindNameUpdates);
}
Expand Down
84 changes: 84 additions & 0 deletions test/lit/merge/types.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
;; RUN: not wasm-merge %s env -all 2>&1 | filecheck %s

;; Test of exports / imports type matching

;; CHECK: Type mismatch when importing function f1 from module env ($bad1): type (type $func.0 (func)) is not a subtype of (type $func.0 (func (param (ref eq)))).
;; CHECK-NEXT: Type mismatch when importing function f3 from module env ($bad2): type (type $func.0 (sub (func (result anyref)))) is not a subtype of (type $func.0 (sub $func.1 (func (result eqref)))).
;; CHECK-NEXT: Type mismatch when importing table t1 from module env ($bad1): minimal size 10 is smaller than expected minimal size 11.
;; CHECK-NEXT: Type mismatch when importing table t1 from module env ($bad2): maximal size 100 is larger than expected maximal size 99.
;; CHECK-NEXT: Type mismatch when importing table t2 from module env ($bad3): expecting a bounded table but the imported table is unbounded.
;; CHECK-NEXT: Type mismatch when importing table t3 from module env ($bad4): export type anyref is different from import type funcref.
;; CHECK-NEXT: Type mismatch when importing memory m1 from module env ($bad1): minimal size 10 is smaller than expected minimal size 11.
;; CHECK-NEXT: Type mismatch when importing memory m1 from module env ($bad2): maximal size 100 is larger than expected maximal size 99.
;; CHECK-NEXT: Type mismatch when importing memory m2 from module env ($bad3): expecting a bounded memory but the imported memory is unbounded.
;; CHECK-NEXT: Type mismatch when importing memory m3 from module env ($bad4): index type should match.
;; CHECK-NEXT: Type mismatch when importing global g1 from module env ($bad1): mutability should match.
;; CHECK-NEXT: Type mismatch when importing global g2 from module env ($bad2): mutability should match.
;; CHECK-NEXT: Type mismatch when importing global g2 from module env ($bad3): export type eqref is different from import type i31ref.
;; CHECK-NEXT: Type mismatch when importing global g2 from module env ($bad4): export type eqref is different from import type anyref.
;; CHECK-NEXT: Type mismatch when importing global g1 from module env ($bad5): type eqref is not a subtype of i31ref.
;; CHECK-NEXT: Type mismatch when importing tag t from module env ($bad1): export type (func (param eqref)) is different from import type (func (param anyref)).
;; CHECK-NEXT: Type mismatch when importing tag t from module env ($bad2): export type (func (param eqref)) is different from import type (func (param i31ref)).
;; CHECK-NEXT: Fatal: import/export mismatches

(module
(type $f (sub (func (result anyref))))
(type $g (sub $f (func (result eqref))))

(func (export "f1"))
(func (export "f2") (type $g) (ref.null eq))
(func (export "f3") (type $f) (ref.null eq))

(import "env" "f1" (func $good1))
(import "env" "f2" (func $good2 (type $f)))

(import "env" "f1" (func $bad1 (param (ref eq))))
(import "env" "f3" (func $bad2 (type $g)))

(table (export "t1") 10 100 funcref)
(table (export "t2") 10 funcref)
(table (export "t3") 10 anyref)

(import "env" "t1" (table $good1 10 funcref))
(import "env" "t1" (table $good2 10 100 funcref))
(import "env" "t2" (table $good3 10 funcref))
(import "env" "t3" (table $good4 10 anyref))

(import "env" "t1" (table $bad1 11 funcref))
(import "env" "t1" (table $bad2 10 99 funcref))
(import "env" "t2" (table $bad3 10 100 funcref))
(import "env" "t3" (table $bad4 10 funcref))

(memory (export "m1") 10 100)
(memory (export "m2") 10)
(memory (export "m3") i64 10)

(import "env" "m1" (memory $good1 10))
(import "env" "m1" (memory $good2 10 100))
(import "env" "m2" (memory $good3 10))
(import "env" "m3" (memory $good4 i64 10))

(import "env" "m1" (memory $bad1 11))
(import "env" "m1" (memory $bad2 10 99))
(import "env" "m2" (memory $bad3 10 100))
(import "env" "m3" (memory $bad4 10))

(global (export "g1") eqref (ref.null eq))
(global (export "g2") (mut eqref) (ref.null eq))

(import "env" "g1" (global $good1 anyref))
(import "env" "g2" (global $good2 (mut eqref)))

(import "env" "g1" (global $bad1 (mut eqref)))
(import "env" "g2" (global $bad2 eqref))
(import "env" "g2" (global $bad3 (mut i31ref)))
(import "env" "g2" (global $bad4 (mut anyref)))
(import "env" "g1" (global $bad5 i31ref))

(tag (export "t") (param eqref))

(import "env" "t" (tag $good1 (param eqref)))

(import "env" "t" (tag $bad1 (param anyref)))
(import "env" "t" (tag $bad2 (param i31ref)))
)

0 comments on commit eae2638

Please sign in to comment.