Add support for either in wast
diff --git a/scripts/test/shared.py b/scripts/test/shared.py
index 18ca85d..0f14ad6 100644
--- a/scripts/test/shared.py
+++ b/scripts/test/shared.py
@@ -396,7 +396,7 @@
     # Test invalid
     'elem.wast',
 
-    # Requires wast `either` support
+    # Requires scoping of `register` statements within `thread` blocks
     'threads/thread.wast',
 
     # Requires better support for multi-threaded tests
@@ -453,12 +453,9 @@
     'type-subtyping.wast',  # ShellExternalInterface::callTable does not handle subtyping
     '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
-    'i8x16_relaxed_swizzle.wast',    # Requires wast `either` support
-    'relaxed_dot_product.wast',   # Requires wast `either` support
-    'relaxed_laneselect.wast',    # Requires wast `either` support
-    'relaxed_madd_nmadd.wast',    # Requires wast `either` support
-    'relaxed_min_max.wast',       # Requires wast `either` support
+    'relaxed_dot_product.wast',   # i16x8.relaxed_dot_i8x16_i7x16_s instruction not supported
+    'relaxed_laneselect.wast',    # i8x16.relaxed_laneselect instruction not supported
+    'relaxed_min_max.wast',       # Non-canonical NaN from f32x4.relaxed_min
     'simd_const.wast',            # Hex float constant not recognized as out of range
     'simd_conversions.wast',      # Promoted NaN should be canonical
     'simd_f32x4.wast',            # Min of 0 and NaN should give a canonical NaN
diff --git a/src/parser/wast-parser.cpp b/src/parser/wast-parser.cpp
index c5f5f91..b98bef0 100644
--- a/src/parser/wast-parser.cpp
+++ b/src/parser/wast-parser.cpp
@@ -324,12 +324,30 @@
   return in.err("unrecognized result");
 }
 
+Result<ResultAlternatives> eitherResult(Lexer& in) {
+  if (in.takeSExprStart("either"sv)) {
+    ResultAlternatives alternatives;
+    do {
+      auto r = result(in);
+      CHECK_ERR(r);
+
+      alternatives.push_back(*std::move(r));
+    } while (!in.takeRParen());
+
+    return alternatives;
+  }
+
+  auto r = result(in);
+  CHECK_ERR(r);
+  return ResultAlternatives{*std::move(r)};
+}
+
 Result<ExpectedResults> results(Lexer& in) {
   ExpectedResults res;
   while (!in.peekRParen()) {
-    auto r = result(in);
+    auto r = eitherResult(in);
     CHECK_ERR(r);
-    res.emplace_back(std::move(*r));
+    res.emplace_back(*std::move(r));
   }
   return res;
 }
@@ -648,7 +666,7 @@
       return cmds;
     }
     CHECK_ERR(cmd);
-    cmds.push_back(ScriptEntry{std::move(*cmd), line});
+    cmds.push_back(ScriptEntry{*std::move(cmd), line});
   }
   return cmds;
 }
diff --git a/src/parser/wat-parser.h b/src/parser/wat-parser.h
index 4eac32b..f833fc8 100644
--- a/src/parser/wat-parser.h
+++ b/src/parser/wat-parser.h
@@ -87,7 +87,13 @@
 using ExpectedResult =
   std::variant<Literal, NullRefResult, RefResult, NaNResult, LaneResults>;
 
-using ExpectedResults = std::vector<ExpectedResult>;
+using ResultAlternatives = std::vector<ExpectedResult>;
+
+// The WAST spec states that `either`s maybe be nested arbitrarily e.g.
+// (either (either "a" "b") (either "a" "c"))
+// but we store this flattened since there's no way to tell the difference
+// anyway.
+using ExpectedResults = std::vector<ResultAlternatives>;
 
 struct AssertReturn {
   Action action;
diff --git a/src/support/result.h b/src/support/result.h
index 7cd360d..f34f630 100644
--- a/src/support/result.h
+++ b/src/support/result.h
@@ -36,21 +36,22 @@
 // Check a Result or MaybeResult for error and return the error if it exists.
 #define CHECK_ERR(val)                                                         \
   if (auto _val = (val); auto err = _val.getErr()) {                           \
-    return Err{*err};                                                          \
+    return (typename decltype(_val)::ErrorType)(*err);                         \
   }
 
 // Represent a result of type T or an error message.
-template<typename T = Ok> struct [[nodiscard]] Result {
-  std::variant<T, Err> val;
+template<typename T = Ok, typename E = Err> struct [[nodiscard]] Result {
+  using ErrorType = E;
+  std::variant<T, E> val;
 
-  Result(Result<T>& other) = default;
-  Result(Result<T>&& other) = default;
-  Result(const Err& e) : val(std::in_place_type<Err>, e) {}
-  Result(Err&& e) : val(std::in_place_type<Err>, std::move(e)) {}
+  Result(Result<T, E>& other) = default;
+  Result(Result<T, E>&& other) = default;
+  Result(const E& e) : val(std::in_place_type<E>, e) {}
+  Result(E&& e) : val(std::in_place_type<E>, std::move(e)) {}
   template<typename U = T>
   Result(U&& u) : val(std::in_place_type<T>, std::forward<U>(u)) {}
 
-  Err* getErr() { return std::get_if<Err>(&val); }
+  E* getErr() { return std::get_if<E>(&val); }
   T& operator*() { return *std::get_if<T>(&val); }
   T* operator->() { return std::get_if<T>(&val); }
 };
@@ -58,6 +59,7 @@
 // Represent an optional result of type T or an error message.
 template<typename T = Ok> struct [[nodiscard]] MaybeResult {
   std::variant<T, None, Err> val;
+  using ErrorType = Err;
 
   MaybeResult() : val(None{}) {}
   MaybeResult(MaybeResult<T>& other) = default;
diff --git a/src/tools/wasm-shell.cpp b/src/tools/wasm-shell.cpp
index 4eb6f52..59d1e29 100644
--- a/src/tools/wasm-shell.cpp
+++ b/src/tools/wasm-shell.cpp
@@ -319,13 +319,13 @@
     switch (nan.kind) {
       case NaNKind::Canonical:
         if (val.type != nan.type || !val.isCanonicalNaN()) {
-          err << "expected canonical " << nan.type << " NaN, got " << val;
+          err << "canonical " << nan.type;
           return Err{err.str()};
         }
         break;
       case NaNKind::Arithmetic:
         if (val.type != nan.type || !val.isArithmeticNaN()) {
-          err << "expected arithmetic " << nan.type << " NaN, got " << val;
+          err << "arithmetic " << nan.type;
           return Err{err.str()};
         }
         break;
@@ -333,17 +333,17 @@
     return Ok{};
   }
 
-  Result<> checkLane(Literal val, LaneResult expected, Index index) {
+  Result<> checkLane(Literal val, LaneResult expected) {
     std::stringstream err;
     if (auto* e = std::get_if<Literal>(&expected)) {
       if (*e != val) {
-        err << "expected " << *e << ", got " << val << " at lane " << index;
+        err << *e;
         return Err{err.str()};
       }
     } else if (auto* nan = std::get_if<NaNResult>(&expected)) {
       auto check = checkNaN(val, *nan);
       if (auto* e = check.getErr()) {
-        err << e->msg << " at lane " << index;
+        err << e->msg;
         return Err{err.str()};
       }
     } else {
@@ -352,6 +352,99 @@
     return Ok{};
   }
 
+  struct AlternativeErr {
+    std::string expected;
+    int lane = -1;
+  };
+
+  Result<Ok, AlternativeErr> matchAlternative(const Literal& val,
+                                              const ExpectedResult& expected,
+                                              bool isAlternative) {
+    std::stringstream err;
+
+    if (auto* v = std::get_if<Literal>(&expected)) {
+      if (val != *v) {
+        if (val.type.isVector() && v->type.isVector() && isAlternative) {
+          auto valLanes = val.getLanesI32x4();
+          auto expLanes = v->getLanesI32x4();
+          for (int i = 0; i < 4; ++i) {
+            if (valLanes[i] != expLanes[i]) {
+              err << "0x" << std::setfill('0') << std::setw(8) << std::hex
+                  << expLanes[i] << std::dec;
+              return AlternativeErr{err.str(), i};
+            }
+          }
+        }
+        err << *v;
+        return AlternativeErr{err.str()};
+      }
+    } else if (auto* ref = std::get_if<RefResult>(&expected)) {
+      if (!val.type.isRef() ||
+          !HeapType::isSubType(val.type.getHeapType(), ref->type)) {
+        err << ref->type;
+        return AlternativeErr{err.str()};
+      }
+    } else if ([[maybe_unused]] auto* nullRef =
+                 std::get_if<NullRefResult>(&expected)) {
+      if (!val.isNull()) {
+        err << "ref.null";
+        return AlternativeErr{err.str()};
+      }
+    } else if (auto* nan = std::get_if<NaNResult>(&expected)) {
+      auto check = checkNaN(val, *nan);
+      if (auto* e = check.getErr()) {
+        err << e->msg;
+        return AlternativeErr{err.str()};
+      }
+    } else if (auto* l = std::get_if<LaneResults>(&expected)) {
+        auto* lanes = &l->lanes;
+
+        auto check = [&](int size, const auto& vals) -> Result<> {
+          for (int i = 0; i < size; ++i) {
+            auto check = checkLane(vals[i], (*lanes)[i], i);
+            if (auto* e = check.getErr()) {
+              err << e->msg << atIndex();
+              return Err{err.str()};
+            }
+          }
+          return Ok{};
+        };
+
+        bool isFloat = l->type == WATParser::LaneResults::LaneType::Float;
+         switch (lanes->size()) {
+          // Use unsigned values for the smaller types here to avoid sign
+          // extension when storing 8/16-bit values in 32-bit ints. This isn't
+          // needed for i32 and i64.
+          case 16: {
+            // There is no f8.
+            CHECK_ERR(check(16, val.getLanesUI8x16()));
+            break;
+          }
+          case 8: {
+            CHECK_ERR(
+              check(8, isFloat ? val.getLanesF16x8() : val.getLanesUI16x8()));
+            break;
+          }
+           case 4: {
+            CHECK_ERR(
+              check(4, isFloat ? val.getLanesF32x4() : val.getLanesI32x4()));
+             break;
+           }
+           case 2: {
+            CHECK_ERR(
+              check(2, isFloat ? val.getLanesF64x2() : val.getLanesI64x2()));
+             break;
+           }
+           default:
+             WASM_UNREACHABLE("unexpected number of lanes");
+         }
+
+    } else {
+      WASM_UNREACHABLE("unexpected expectation");
+    }
+    return Ok{};
+  }
+
   Result<> assertReturn(AssertReturn& assn) {
     std::stringstream err;
     auto result = doAction(assn.action);
@@ -374,78 +467,55 @@
         return ss.str();
       };
 
-      Literal val = (*values)[i];
-      auto& expected = assn.expected[i];
-      if (auto* v = std::get_if<Literal>(&expected)) {
-        if (val != *v) {
-          err << "expected " << *v << ", got " << val << atIndex();
-          return Err{err.str()};
-        }
-      } else if (auto* ref = std::get_if<RefResult>(&expected)) {
-        if (!val.type.isRef() ||
-            !HeapType::isSubType(val.type.getHeapType(), ref->type)) {
-          err << "expected " << ref->type << " reference, got " << val
-              << atIndex();
-          return Err{err.str()};
-        }
-      } else if ([[maybe_unused]] auto* nullRef =
-                   std::get_if<NullRefResult>(&expected)) {
-        if (!val.isNull()) {
-          err << "expected ref.null, got " << val << atIndex();
-          return Err{err.str()};
-        }
-      } else if (auto* nan = std::get_if<NaNResult>(&expected)) {
-        auto check = checkNaN(val, *nan);
-        if (auto* e = check.getErr()) {
-          err << e->msg << atIndex();
-          return Err{err.str()};
-        }
-      } else if (auto* l = std::get_if<LaneResults>(&expected)) {
-        auto* lanes = &l->lanes;
-
-        auto check = [&](int size, const auto& vals) -> Result<> {
-          for (int i = 0; i < size; ++i) {
-            auto check = checkLane(vals[i], (*lanes)[i], i);
-            if (auto* e = check.getErr()) {
-              err << e->msg << atIndex();
-              return Err{err.str()};
-            }
+      // non-either case
+      if (assn.expected[i].size() == 1) {
+        auto result = matchAlternative(
+          (*values)[i], assn.expected[i][0], /*isAlternative=*/false);
+        if (auto* e = result.getErr()) {
+          std::stringstream ss;
+          ss << "expected " << e->expected << ", got " << (*values)[i];
+          if (e->lane != -1) {
+            ss << " at lane " << e->lane;
           }
-          return Ok{};
-        };
-
-        bool isFloat = l->type == WATParser::LaneResults::LaneType::Float;
-        switch (lanes->size()) {
-          // Use unsigned values for the smaller types here to avoid sign
-          // extension when storing 8/16-bit values in 32-bit ints. This isn't
-          // needed for i32 and i64.
-          case 16: {
-            // There is no f8.
-            CHECK_ERR(check(16, val.getLanesUI8x16()));
-            break;
-          }
-          case 8: {
-            CHECK_ERR(
-              check(8, isFloat ? val.getLanesF16x8() : val.getLanesUI16x8()));
-            break;
-          }
-          case 4: {
-            CHECK_ERR(
-              check(4, isFloat ? val.getLanesF32x4() : val.getLanesI32x4()));
-            break;
-          }
-          case 2: {
-            CHECK_ERR(
-              check(2, isFloat ? val.getLanesF64x2() : val.getLanesI64x2()));
-            break;
-          }
-          default:
-            WASM_UNREACHABLE("unexpected number of lanes");
+          ss << atIndex();
+          return Err{ss.str()};
         }
-      } else {
-        WASM_UNREACHABLE("unexpected expectation");
+        continue;
       }
+
+      // either case
+      bool success = false;
+      std::vector<std::string> expecteds;
+      int failedLane = -1;
+      for (const auto& alternative : assn.expected[i]) {
+        auto result =
+          matchAlternative((*values)[i], alternative, /*isAlternative=*/true);
+        if (!result.getErr()) {
+          success = true;
+          break;
+        }
+
+        auto* e = result.getErr();
+        expecteds.push_back(e->expected);
+        if (failedLane == -1 && e->lane != -1) {
+          failedLane = e->lane;
+        }
+      }
+      if (success) {
+        continue;
+      }
+      std::stringstream ss;
+      ss << "Expected one of (" << String::join(expecteds, " | ") << ")";
+      if (failedLane != -1) {
+        ss << " at lane " << failedLane;
+      }
+      ss << " but got " << (*values)[i];
+
+      ss << atIndex();
+
+      return Err{ss.str()};
     }
+
     return Ok{};
   }
 
diff --git a/src/tools/wasm2js.cpp b/src/tools/wasm2js.cpp
index 4821070..03be0f7 100644
--- a/src/tools/wasm2js.cpp
+++ b/src/tools/wasm2js.cpp
@@ -605,7 +605,15 @@
                                            Name asmModule) {
   if (assn.expected.size() > 1) {
     Fatal() << "multivalue assert_return not supported";
+    return {};
   }
+  for (const auto& alternatives : assn.expected) {
+    if (alternatives.size() > 1) {
+      Fatal() << "(either ...) not supported";
+      return {};
+    }
+  }
+
   auto* invoke = std::get_if<InvokeAction>(&assn.action);
   if (!invoke) {
     Fatal() << "only invoke actions are supported in assert_return";
@@ -619,7 +627,7 @@
     } else {
       body = actual;
     }
-  } else if (auto* expectedVal = std::get_if<Literal>(&assn.expected[0])) {
+  } else if (auto* expectedVal = std::get_if<Literal>(&assn.expected[0][0])) {
     if (!expectedVal->type.isBasic()) {
       Fatal() << "unsupported type in assert_return: " << expectedVal->type;
     }
@@ -648,7 +656,7 @@
         Fatal() << "Unhandled type in assert: " << expected->type;
       }
     }
-  } else if (std::get_if<NaNResult>(&assn.expected[0])) {
+  } else if (std::get_if<NaNResult>(&assn.expected[0][0])) {
     body = builder.makeCall("isNaN", {actual}, Type::i32);
   }
   std::unique_ptr<Function> testFunc(