Fix crash in ctor-eval when a tag is imported (#8284)

Fix for [fuzzer-detected crash when ctor-eval runs on a module that
imports a
tag](https://github.com/WebAssembly/binaryen/pull/8254#issuecomment-3880081644).
Prior to #8254, ctor-eval would
[crash](https://github.com/WebAssembly/binaryen/blob/23d218d0bd469a399ff17b26fdd71164beeb63fa/src/tools/wasm-ctor-eval.cpp#L396)
when an imported tag was evaluated, but not when imported. Change the
code to allow imported tags even during evaluation.

Note that we can't reason about the identity of imported tags. In the
following code, $t1 and $t2 may be the same or different tags:
```wasm
(import "foo" "bar" (tag $t1))
(import "foo" "bar2" (tag $t2))
```

In this PR, we assume that $t1 and $t2 are different tags, and that
they're the same tag if the import name is the same (this is also not
true in general, the hosting environment may provide two different
values for the same exact import name). This may cause some correctness
issues. As a followup, we can make equality comparison of two imported
tags throw FailToEvalException to make evaluation correct.

Part of #8180.
diff --git a/src/ir/import-name.h b/src/ir/import-name.h
index 81fd0a3..04e141c 100644
--- a/src/ir/import-name.h
+++ b/src/ir/import-name.h
@@ -19,6 +19,7 @@
 
 #include <ostream>
 
+#include "support/hash.h"
 #include "support/name.h"
 
 namespace wasm {
@@ -26,8 +27,24 @@
 struct ImportNames {
   Name module;
   Name name;
+
+  bool operator==(const ImportNames& other) const {
+    return module == other.module && name == other.name;
+  }
 };
 
 } // namespace wasm
 
+namespace std {
+
+template<> struct hash<wasm::ImportNames> {
+  std::size_t operator()(const wasm::ImportNames& importNames) const noexcept {
+    size_t val = hash<wasm::Name>{}(importNames.module);
+    wasm::rehash(val, importNames.name);
+    return val;
+  }
+};
+
+} // namespace std
+
 #endif // wasm_ir_import_name_h
diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp
index ac8045e..1c8f0c5 100644
--- a/src/tools/wasm-ctor-eval.cpp
+++ b/src/tools/wasm-ctor-eval.cpp
@@ -84,14 +84,22 @@
     throw FailToEvalException{"Imported table access."};
   }
 
+  // We assume that each tag import is distinct. This is wrong if the same tag
+  // instantiation is imported twice with different import names.
   Tag* getTagOrNull(ImportNames name,
                     const Signature& signature) const override {
-    Fatal() << "getTagOrNull not implemented in ctor-eval.";
-    return nullptr;
+    auto [it, inserted] = importedTags.try_emplace(name, Tag{});
+    if (inserted) {
+      auto& tag = it->second;
+      tag.type = HeapType(signature);
+    }
+
+    return &it->second;
   }
 
 private:
   mutable Literals stubLiteral;
+  mutable std::unordered_map<ImportNames, Tag> importedTags;
 };
 
 class EvallingRuntimeTable : public RuntimeTable {
diff --git a/test/ctor-eval/tag-import.wast b/test/ctor-eval/tag-import.wast
new file mode 100644
index 0000000..dca27d7
--- /dev/null
+++ b/test/ctor-eval/tag-import.wast
@@ -0,0 +1,16 @@
+(module
+  ;; an imported tag that isn't accessed doesn't stop us from optimizing
+  (import "import" "tag" (tag $imported))
+  (global $g (mut i32) (i32.const 0))
+  (func $setg (export "setg")
+    (drop (i32.const 1))
+    (global.set $g
+      (i32.add (i32.const 1) (i32.const 2))
+    )
+  )
+
+  (func $keepalive (export "keepalive") (result i32)
+    ;; Keep the global alive so we can see its value.
+    (global.get $g)
+  )
+)
diff --git a/test/ctor-eval/tag-import.wast.ctors b/test/ctor-eval/tag-import.wast.ctors
new file mode 100644
index 0000000..9b1821d
--- /dev/null
+++ b/test/ctor-eval/tag-import.wast.ctors
@@ -0,0 +1 @@
+setg
diff --git a/test/ctor-eval/tag-import.wast.out b/test/ctor-eval/tag-import.wast.out
new file mode 100644
index 0000000..89271ee
--- /dev/null
+++ b/test/ctor-eval/tag-import.wast.out
@@ -0,0 +1,8 @@
+(module
+ (type $0 (func (result i32)))
+ (global $g (mut i32) (i32.const 3))
+ (export "keepalive" (func $keepalive))
+ (func $keepalive (type $0) (result i32)
+  (global.get $g)
+ )
+)