|
91 | 91 | // merged, and at the end we traverse the entire merged module once to fuse |
92 | 92 | // imports and exports. |
93 | 93 | // |
| 94 | +// Debugging: Set BINARYEN_PASS_DEBUG=1 in the env to get validation after each |
| 95 | +// merging of a module (like pass-debug mode for the pass runner, this does |
| 96 | +// expensive work after each incremental operation). This can take quadratic |
| 97 | +// time, so we do not do it by default. |
| 98 | +// |
94 | 99 |
|
95 | 100 | #include "ir/module-utils.h" |
96 | 101 | #include "ir/names.h" |
@@ -418,7 +423,7 @@ void checkLimit(bool& valid, const char* kind, T* export_, T* import) { |
418 | 423 |
|
419 | 424 | // Find pairs of matching imports and exports, and make uses of the import refer |
420 | 425 | // to the exported item (which has been merged into the module). |
421 | | -void fuseImportsAndExports() { |
| 426 | +void fuseImportsAndExports(const PassOptions& options) { |
422 | 427 | // First, scan the exports and build a map. We build a map of [module name] to |
423 | 428 | // [export name => internal name]. For example, consider this module: |
424 | 429 | // |
@@ -466,80 +471,86 @@ void fuseImportsAndExports() { |
466 | 471 | } |
467 | 472 | }); |
468 | 473 |
|
469 | | - // Make sure that the export types match the import types. |
470 | | - bool valid = true; |
471 | | - ModuleUtils::iterImportedFunctions(merged, [&](Function* import) { |
472 | | - auto internalName = kindModuleExportMaps[ExternalKind::Function] |
473 | | - [import->module][import->base]; |
474 | | - if (internalName.is()) { |
475 | | - auto* export_ = merged.getFunction(internalName); |
476 | | - if (!HeapType::isSubType(export_->type, import->type)) { |
477 | | - reportTypeMismatch(valid, "function", import); |
478 | | - std::cerr << "type " << export_->type << " is not a subtype of " |
479 | | - << import->type << ".\n"; |
480 | | - } |
481 | | - } |
482 | | - }); |
483 | | - ModuleUtils::iterImportedTables(merged, [&](Table* import) { |
484 | | - auto internalName = |
485 | | - kindModuleExportMaps[ExternalKind::Table][import->module][import->base]; |
486 | | - if (internalName.is()) { |
487 | | - auto* export_ = merged.getTable(internalName); |
488 | | - checkLimit(valid, "table", export_, import); |
489 | | - if (export_->type != import->type) { |
490 | | - reportTypeMismatch(valid, "table", import); |
491 | | - std::cerr << "export type " << export_->type |
492 | | - << " is different from import type " << import->type << ".\n"; |
493 | | - } |
494 | | - } |
495 | | - }); |
496 | | - ModuleUtils::iterImportedMemories(merged, [&](Memory* import) { |
497 | | - auto internalName = |
498 | | - kindModuleExportMaps[ExternalKind::Memory][import->module][import->base]; |
499 | | - if (internalName.is()) { |
500 | | - auto* export_ = merged.getMemory(internalName); |
501 | | - if (export_->is64() != import->is64()) { |
502 | | - reportTypeMismatch(valid, "memory", import); |
503 | | - std::cerr << "index type should match.\n"; |
| 474 | + if (options.validate) { |
| 475 | + // Make sure that the export types match the import types. |
| 476 | + bool valid = true; |
| 477 | + ModuleUtils::iterImportedFunctions(merged, [&](Function* import) { |
| 478 | + auto internalName = kindModuleExportMaps[ExternalKind::Function] |
| 479 | + [import->module][import->base]; |
| 480 | + if (internalName.is()) { |
| 481 | + auto* export_ = merged.getFunction(internalName); |
| 482 | + if (!HeapType::isSubType(export_->type, import->type)) { |
| 483 | + reportTypeMismatch(valid, "function", import); |
| 484 | + std::cerr << "type " << export_->type << " is not a subtype of " |
| 485 | + << import->type << ".\n"; |
| 486 | + } |
504 | 487 | } |
505 | | - checkLimit(valid, "memory", export_, import); |
506 | | - } |
507 | | - }); |
508 | | - ModuleUtils::iterImportedGlobals(merged, [&](Global* import) { |
509 | | - auto internalName = |
510 | | - kindModuleExportMaps[ExternalKind::Global][import->module][import->base]; |
511 | | - if (internalName.is()) { |
512 | | - auto* export_ = merged.getGlobal(internalName); |
513 | | - if (export_->mutable_ != import->mutable_) { |
514 | | - reportTypeMismatch(valid, "global", import); |
515 | | - std::cerr << "mutability should match.\n"; |
| 488 | + }); |
| 489 | + ModuleUtils::iterImportedTables(merged, [&](Table* import) { |
| 490 | + auto internalName = |
| 491 | + kindModuleExportMaps[ExternalKind::Table][import->module][import->base]; |
| 492 | + if (internalName.is()) { |
| 493 | + auto* export_ = merged.getTable(internalName); |
| 494 | + checkLimit(valid, "table", export_, import); |
| 495 | + if (export_->type != import->type) { |
| 496 | + reportTypeMismatch(valid, "table", import); |
| 497 | + std::cerr << "export type " << export_->type |
| 498 | + << " is different from import type " << import->type |
| 499 | + << ".\n"; |
| 500 | + } |
516 | 501 | } |
517 | | - if (export_->mutable_ && export_->type != import->type) { |
518 | | - reportTypeMismatch(valid, "global", import); |
519 | | - std::cerr << "export type " << export_->type |
520 | | - << " is different from import type " << import->type << ".\n"; |
| 502 | + }); |
| 503 | + ModuleUtils::iterImportedMemories(merged, [&](Memory* import) { |
| 504 | + auto internalName = kindModuleExportMaps[ExternalKind::Memory] |
| 505 | + [import->module][import->base]; |
| 506 | + if (internalName.is()) { |
| 507 | + auto* export_ = merged.getMemory(internalName); |
| 508 | + if (export_->is64() != import->is64()) { |
| 509 | + reportTypeMismatch(valid, "memory", import); |
| 510 | + std::cerr << "index type should match.\n"; |
| 511 | + } |
| 512 | + checkLimit(valid, "memory", export_, import); |
521 | 513 | } |
522 | | - if (!export_->mutable_ && !Type::isSubType(export_->type, import->type)) { |
523 | | - reportTypeMismatch(valid, "global", import); |
524 | | - std::cerr << "type " << export_->type << " is not a subtype of " |
525 | | - << import->type << ".\n"; |
| 514 | + }); |
| 515 | + ModuleUtils::iterImportedGlobals(merged, [&](Global* import) { |
| 516 | + auto internalName = kindModuleExportMaps[ExternalKind::Global] |
| 517 | + [import->module][import->base]; |
| 518 | + if (internalName.is()) { |
| 519 | + auto* export_ = merged.getGlobal(internalName); |
| 520 | + if (export_->mutable_ != import->mutable_) { |
| 521 | + reportTypeMismatch(valid, "global", import); |
| 522 | + std::cerr << "mutability should match.\n"; |
| 523 | + } |
| 524 | + if (export_->mutable_ && export_->type != import->type) { |
| 525 | + reportTypeMismatch(valid, "global", import); |
| 526 | + std::cerr << "export type " << export_->type |
| 527 | + << " is different from import type " << import->type |
| 528 | + << ".\n"; |
| 529 | + } |
| 530 | + if (!export_->mutable_ && |
| 531 | + !Type::isSubType(export_->type, import->type)) { |
| 532 | + reportTypeMismatch(valid, "global", import); |
| 533 | + std::cerr << "type " << export_->type << " is not a subtype of " |
| 534 | + << import->type << ".\n"; |
| 535 | + } |
526 | 536 | } |
527 | | - } |
528 | | - }); |
529 | | - ModuleUtils::iterImportedTags(merged, [&](Tag* import) { |
530 | | - auto internalName = |
531 | | - kindModuleExportMaps[ExternalKind::Tag][import->module][import->base]; |
532 | | - if (internalName.is()) { |
533 | | - auto* export_ = merged.getTag(internalName); |
534 | | - if (export_->type != import->type) { |
535 | | - reportTypeMismatch(valid, "tag", import); |
536 | | - std::cerr << "export type " << export_->type |
537 | | - << " is different from import type " << import->type << ".\n"; |
| 537 | + }); |
| 538 | + ModuleUtils::iterImportedTags(merged, [&](Tag* import) { |
| 539 | + auto internalName = |
| 540 | + kindModuleExportMaps[ExternalKind::Tag][import->module][import->base]; |
| 541 | + if (internalName.is()) { |
| 542 | + auto* export_ = merged.getTag(internalName); |
| 543 | + if (export_->type != import->type) { |
| 544 | + reportTypeMismatch(valid, "tag", import); |
| 545 | + std::cerr << "export type " << export_->type |
| 546 | + << " is different from import type " << import->type |
| 547 | + << ".\n"; |
| 548 | + } |
538 | 549 | } |
| 550 | + }); |
| 551 | + if (!valid) { |
| 552 | + Fatal() << "import/export mismatches"; |
539 | 553 | } |
540 | | - }); |
541 | | - if (!valid) { |
542 | | - Fatal() << "import/export mismatches"; |
543 | 554 | } |
544 | 555 |
|
545 | 556 | // Update the things we found. |
@@ -730,18 +741,29 @@ Input source maps can be specified by adding an -ism option right after the modu |
730 | 741 | // This is a later module: do a full merge. |
731 | 742 | mergeInto(*currModule, inputFileName); |
732 | 743 |
|
733 | | - if (options.passOptions.validate) { |
734 | | - if (!WasmValidator().validate(merged)) { |
| 744 | + // Validate after each merged module, when we are in pass-debug mode |
| 745 | + // (this can be quadratic time). |
| 746 | + if (PassRunner::getPassDebug()) { |
| 747 | + std::cerr << "[WasmMerge] merged : " << inputFile << '\n'; |
| 748 | + if (options.passOptions.validate && !WasmValidator().validate(merged)) { |
735 | 749 | std::cout << merged << '\n'; |
736 | | - Fatal() << "error in validating merged after: " << inputFile; |
| 750 | + Fatal() << "error in validating after: " << inputFile; |
737 | 751 | } |
738 | 752 | } |
739 | 753 | } |
740 | 754 | } |
741 | 755 |
|
| 756 | + // If we didn't validate after each merged module, validate once at the very |
| 757 | + // end. This won't catch problems at the earliest point, but is still useful. |
| 758 | + if (!PassRunner::getPassDebug() && options.passOptions.validate && |
| 759 | + !WasmValidator().validate(merged)) { |
| 760 | + std::cout << merged << '\n'; |
| 761 | + Fatal() << "error in validating final merged"; |
| 762 | + } |
| 763 | + |
742 | 764 | // Fuse imports and exports now that everything is all together in the merged |
743 | 765 | // module. |
744 | | - fuseImportsAndExports(); |
| 766 | + fuseImportsAndExports(options.passOptions); |
745 | 767 |
|
746 | 768 | { |
747 | 769 | PassRunner passRunner(&merged); |
|
0 commit comments