Bug 1561515 - Wasm: Add typed select instruction. r=lth
authorRyan Hunt <rhunt@eqrion.net>
Wed, 25 Sep 2019 21:27:14 +0000
changeset 494987 b01aab238aa0293e21f204be90eea391e35c71ec
parent 494986 7f894549b2877b0c44393a1957eafe092d92f727
child 494992 88ed3923442a5527d87df3a05e31c7732239b953
push id114131
push userdluca@mozilla.com
push dateThu, 26 Sep 2019 09:47:34 +0000
treeherdermozilla-inbound@1dc1a755079a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerslth
bugs1561515
milestone71.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1561515 - Wasm: Add typed select instruction. r=lth Issue: https://github.com/WebAssembly/reference-types/issues/42 Text: https://webassembly.github.io/reference-types/core/text/instructions.html#parametric-instructions Binary: https://webassembly.github.io/reference-types/core/binary/instructions.html#parametric-instructions This commit adds 'select t*'. The instruction is allowed to encode/decode multiple result types for compatibility with multi-value, but only one is currently supported. Differential Revision: https://phabricator.services.mozilla.com/D45866
js/src/jit-test/lib/wasm-binary.js
js/src/jit-test/tests/wasm/gc/anyref.js
js/src/jit-test/tests/wasm/gc/funcref.js
js/src/wasm/WasmAST.h
js/src/wasm/WasmBaselineCompile.cpp
js/src/wasm/WasmConstants.h
js/src/wasm/WasmIonCompile.cpp
js/src/wasm/WasmOpIter.cpp
js/src/wasm/WasmOpIter.h
js/src/wasm/WasmTextToBinary.cpp
js/src/wasm/WasmValidate.cpp
--- a/js/src/jit-test/lib/wasm-binary.js
+++ b/js/src/jit-test/lib/wasm-binary.js
@@ -111,17 +111,17 @@ const MozPrefix = 0xff;
 
 // See WasmConstants.h for documentation.
 // Limit this to a group of 8 per line.
 
 const definedOpcodes =
     [0x00, 0x01, 0x02, 0x03, 0x04, 0x05,
      0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
      0x10, 0x11,
-     0x1a, 0x1b,
+     0x1a, 0x1b, 0x1c,
      0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
      0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
      0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
      0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
      0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
      0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
      0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
      0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
--- a/js/src/jit-test/tests/wasm/gc/anyref.js
+++ b/js/src/jit-test/tests/wasm/gc/anyref.js
@@ -15,19 +15,19 @@ assertErrorMessage(() => wasmEvalText(`(
     )
 )`), CompileError, mismatchError('i32', 'anyref'));
 
 assertErrorMessage(() => wasmEvalText(`(module
     (func (result anyref)
         i32.const 0
         ref.null
         i32.const 42
-        select
+        select (result anyref)
     )
-)`), CompileError, /select operand types/);
+)`), CompileError, /type mismatch/);
 
 assertErrorMessage(() => wasmEvalText(`(module
     (func (result i32)
         ref.null
         if
             i32.const 42
         end
     )
@@ -98,17 +98,17 @@ exports = wasmEvalText(`(module
         local.get $ref
         ref.is_null
     )
 
     (func (export "ref_or_null") (result anyref) (param $ref anyref) (param $selector i32)
         local.get $ref
         ref.null
         local.get $selector
-        select
+        select (result anyref)
     )
 
     (func $recursive (export "nested") (result anyref) (param $ref anyref) (param $i i32)
         ;; i == 10 => ret $ref
         local.get $i
         i32.const 10
         i32.eq
         if
@@ -231,17 +231,17 @@ assertJoin(`(block $out anyref (block $u
 `);
 
 let x = { i: 42 }, y = { f: 53 };
 exports = wasmEvalText(`(module
     (func (export "test") (param $lhs anyref) (param $rhs anyref) (param $i i32) (result anyref)
         local.get $lhs
         local.get $rhs
         local.get $i
-        select
+        select (result anyref)
     )
 )`).exports;
 
 let result = exports.test(x, y, 0);
 assertEq(result, y);
 assertEq(result.i, undefined);
 assertEq(result.f, 53);
 assertEq(x.i, 42);
--- a/js/src/jit-test/tests/wasm/gc/funcref.js
+++ b/js/src/jit-test/tests/wasm/gc/funcref.js
@@ -16,37 +16,37 @@ wasmEvalText(`(module (global (mut funcr
 wasmEvalText(`(module (global (mut anyref) (ref.null)) (func (param funcref) (global.set 0 (local.get 0))))`);
 wasmFailValidateText(`(module (global (mut funcref) (ref.null)) (func (param anyref) (global.set 0 (local.get 0))))`, typeErr);
 wasmEvalText(`(module (func (param funcref)) (func (param funcref) (call 0 (local.get 0))))`);
 wasmEvalText(`(module (func (param anyref)) (func (param funcref) (call 0 (local.get 0))))`);
 wasmFailValidateText(`(module (func (param funcref)) (func (param anyref) (call 0 (local.get 0))))`, typeErr);
 wasmEvalText(`(module (func (param funcref) (result funcref) (block funcref (local.get 0) (br 0))))`);
 wasmEvalText(`(module (func (param funcref) (result anyref) (block anyref (local.get 0) (br 0))))`);
 wasmFailValidateText(`(module (func (param anyref) (result anyref) (block funcref (local.get 0) (br 0))))`, typeErr);
-wasmEvalText(`(module (func (param funcref funcref) (result funcref) (select (local.get 0) (local.get 1) (i32.const 0))))`);
-wasmEvalText(`(module (func (param anyref funcref) (result anyref) (select (local.get 0) (local.get 1) (i32.const 0))))`);
-wasmEvalText(`(module (func (param funcref anyref) (result anyref) (select (local.get 0) (local.get 1) (i32.const 0))))`);
-wasmFailValidateText(`(module (func (param anyref funcref) (result funcref) (select (local.get 0) (local.get 1) (i32.const 0))))`, typeErr);
-wasmFailValidateText(`(module (func (param funcref anyref) (result funcref) (select (local.get 0) (local.get 1) (i32.const 0))))`, typeErr);
+wasmEvalText(`(module (func (param funcref funcref) (result funcref) (select (result funcref) (local.get 0) (local.get 1) (i32.const 0))))`);
+wasmEvalText(`(module (func (param anyref funcref) (result anyref) (select (result anyref) (local.get 0) (local.get 1) (i32.const 0))))`);
+wasmEvalText(`(module (func (param funcref anyref) (result anyref) (select (result anyref)(local.get 0) (local.get 1) (i32.const 0))))`);
+wasmFailValidateText(`(module (func (param anyref funcref) (result funcref) (select (result funcref) (local.get 0) (local.get 1) (i32.const 0))))`, typeErr);
+wasmFailValidateText(`(module (func (param funcref anyref) (result funcref) (select (result funcref) (local.get 0) (local.get 1) (i32.const 0))))`, typeErr);
 
 
 // Runtime:
 
 var m = new Module(wasmTextToBinary(`(module (func (export "wasmFun")))`));
 const wasmFun1 = new Instance(m).exports.wasmFun;
 const wasmFun2 = new Instance(m).exports.wasmFun;
 const wasmFun3 = new Instance(m).exports.wasmFun;
 
 var run = wasmEvalText(`(module
     (global (mut funcref) (ref.null))
     (func (param $x funcref) (param $test i32) (result funcref)
       local.get $x
       global.get 0
       local.get $test
-      select
+      select (result funcref)
     )
     (func (export "run") (param $a funcref) (param $b funcref) (param $c funcref) (param $test1 i32) (param $test2 i32) (result funcref)
       local.get $a
       global.set 0
       block funcref
         local.get $b
         local.get $test1
         br_if 0
--- a/js/src/wasm/WasmAST.h
+++ b/js/src/wasm/WasmAST.h
@@ -487,28 +487,33 @@ class AstDrop : public AstExpr {
       : AstExpr(AstExprKind::Drop, ExprType::Void), value_(value) {}
   AstExpr& value() const { return value_; }
 };
 
 class AstSelect : public AstExpr {
   AstExpr* condition_;
   AstExpr* op1_;
   AstExpr* op2_;
+  AstValTypeVector result_;
 
  public:
   static const AstExprKind Kind = AstExprKind::Select;
-  AstSelect(AstExpr* condition, AstExpr* op1, AstExpr* op2)
+  AstSelect(AstExpr* condition, AstExpr* op1, AstExpr* op2,
+            AstValTypeVector&& result)
       : AstExpr(Kind, ExprType::Limit),
         condition_(condition),
         op1_(op1),
-        op2_(op2) {}
+        op2_(op2),
+        result_(std::move(result)) {}
 
   AstExpr* condition() const { return condition_; }
   AstExpr* op1() const { return op1_; }
   AstExpr* op2() const { return op2_; }
+  AstValTypeVector& result() { return result_; }
+  const AstValTypeVector& result() const { return result_; }
 };
 
 class AstConst : public AstExpr {
   const LitVal val_;
 
  public:
   static const AstExprKind Kind = AstExprKind::Const;
   explicit AstConst(LitVal val) : AstExpr(Kind, ExprType::Limit), val_(val) {}
--- a/js/src/wasm/WasmBaselineCompile.cpp
+++ b/js/src/wasm/WasmBaselineCompile.cpp
@@ -6702,17 +6702,17 @@ class BaseCompiler final : public BaseCo
   MOZ_MUST_USE bool emitSetGlobal();
   MOZ_MUST_USE RegI32 maybeLoadTlsForAccess(const AccessCheck& check);
   MOZ_MUST_USE RegI32 maybeLoadTlsForAccess(const AccessCheck& check,
                                             RegI32 specific);
   MOZ_MUST_USE bool emitLoad(ValType type, Scalar::Type viewType);
   MOZ_MUST_USE bool loadCommon(MemoryAccessDesc* access, ValType type);
   MOZ_MUST_USE bool emitStore(ValType resultType, Scalar::Type viewType);
   MOZ_MUST_USE bool storeCommon(MemoryAccessDesc* access, ValType resultType);
-  MOZ_MUST_USE bool emitSelect();
+  MOZ_MUST_USE bool emitSelect(bool typed);
 
   template <bool isSetLocal>
   MOZ_MUST_USE bool emitSetOrTeeLocal(uint32_t slot);
 
   void endBlock(ExprType type);
   void endIfThen();
   void endIfThenElse(ExprType type);
 
@@ -7954,33 +7954,35 @@ bool BaseCompiler::sniffConditionalContr
     return false;
   }
 
   OpBytes op;
   iter_.peekOp(&op);
   switch (op.b0) {
     case uint16_t(Op::BrIf):
     case uint16_t(Op::If):
-    case uint16_t(Op::Select):
+    case uint16_t(Op::SelectNumeric):
+    case uint16_t(Op::SelectTyped):
       setLatentCompare(compareOp, operandType);
       return true;
     default:
       return false;
   }
 }
 
 bool BaseCompiler::sniffConditionalControlEqz(ValType operandType) {
   MOZ_ASSERT(latentOp_ == LatentOp::None,
              "Latent comparison state not properly reset");
 
   OpBytes op;
   iter_.peekOp(&op);
   switch (op.b0) {
     case uint16_t(Op::BrIf):
-    case uint16_t(Op::Select):
+    case uint16_t(Op::SelectNumeric):
+    case uint16_t(Op::SelectTyped):
     case uint16_t(Op::If):
       setLatentEqz(operandType);
       return true;
     default:
       return false;
   }
 }
 
@@ -9530,22 +9532,22 @@ bool BaseCompiler::emitStore(ValType res
   if (deadCode_) {
     return true;
   }
 
   MemoryAccessDesc access(viewType, addr.align, addr.offset, bytecodeOffset());
   return storeCommon(&access, resultType);
 }
 
-bool BaseCompiler::emitSelect() {
+bool BaseCompiler::emitSelect(bool typed) {
   StackType type;
   Nothing unused_trueValue;
   Nothing unused_falseValue;
   Nothing unused_condition;
-  if (!iter_.readSelect(&type, &unused_trueValue, &unused_falseValue,
+  if (!iter_.readSelect(typed, &type, &unused_trueValue, &unused_falseValue,
                         &unused_condition)) {
     return false;
   }
 
   if (deadCode_) {
     resetLatentOp();
     return true;
   }
@@ -10967,18 +10969,23 @@ bool BaseCompiler::emitBody() {
 #ifdef ENABLE_WASM_REFTYPES
       case uint16_t(Op::TableGet):
         CHECK_NEXT(emitTableGet());
       case uint16_t(Op::TableSet):
         CHECK_NEXT(emitTableSet());
 #endif
 
       // Select
-      case uint16_t(Op::Select):
-        CHECK_NEXT(emitSelect());
+      case uint16_t(Op::SelectNumeric):
+        CHECK_NEXT(emitSelect(/*typed*/ false));
+      case uint16_t(Op::SelectTyped):
+        if (!env_.refTypesEnabled()) {
+          return iter_.unrecognizedOpcode(&op);
+        }
+        CHECK_NEXT(emitSelect(/*typed*/ true));
 
       // I32
       case uint16_t(Op::I32Const): {
         int32_t i32;
         CHECK(iter_.readI32Const(&i32));
         if (!deadCode_) {
           pushI32(i32);
         }
--- a/js/src/wasm/WasmConstants.h
+++ b/js/src/wasm/WasmConstants.h
@@ -169,17 +169,18 @@ enum class Op {
   Return = 0x0f,
 
   // Call operators
   Call = 0x10,
   CallIndirect = 0x11,
 
   // Parametric operators
   Drop = 0x1a,
-  Select = 0x1b,
+  SelectNumeric = 0x1b,
+  SelectTyped = 0x1c,
 
   // Variable access
   GetLocal = 0x20,
   SetLocal = 0x21,
   TeeLocal = 0x22,
   GetGlobal = 0x23,
   SetGlobal = 0x24,
   TableGet = 0x25,  // Reftypes,
--- a/js/src/wasm/WasmIonCompile.cpp
+++ b/js/src/wasm/WasmIonCompile.cpp
@@ -2443,22 +2443,22 @@ static bool EmitComparison(FunctionCompi
   if (!f.iter().readComparison(operandType, &lhs, &rhs)) {
     return false;
   }
 
   f.iter().setResult(f.compare(lhs, rhs, compareOp, compareType));
   return true;
 }
 
-static bool EmitSelect(FunctionCompiler& f) {
+static bool EmitSelect(FunctionCompiler& f, bool typed) {
   StackType type;
   MDefinition* trueValue;
   MDefinition* falseValue;
   MDefinition* condition;
-  if (!f.iter().readSelect(&type, &trueValue, &falseValue, &condition)) {
+  if (!f.iter().readSelect(typed, &type, &trueValue, &falseValue, &condition)) {
     return false;
   }
 
   f.iter().setResult(f.select(trueValue, falseValue, condition));
   return true;
 }
 
 static bool EmitLoad(FunctionCompiler& f, ValType type, Scalar::Type viewType) {
@@ -3441,18 +3441,23 @@ static bool EmitBodyExprs(FunctionCompil
       case uint16_t(Op::Call):
         CHECK(EmitCall(f, /* asmJSFuncDef = */ false));
       case uint16_t(Op::CallIndirect):
         CHECK(EmitCallIndirect(f, /* oldStyle = */ false));
 
       // Parametric operators
       case uint16_t(Op::Drop):
         CHECK(f.iter().readDrop());
-      case uint16_t(Op::Select):
-        CHECK(EmitSelect(f));
+      case uint16_t(Op::SelectNumeric):
+        CHECK(EmitSelect(f, /*typed*/ false));
+      case uint16_t(Op::SelectTyped):
+        if (!f.env().refTypesEnabled()) {
+          return f.iter().unrecognizedOpcode(&op);
+        }
+        CHECK(EmitSelect(f, /*typed*/ true));
 
       // Locals and globals
       case uint16_t(Op::GetLocal):
         CHECK(EmitGetLocal(f));
       case uint16_t(Op::SetLocal):
         CHECK(EmitSetLocal(f));
       case uint16_t(Op::TeeLocal):
         CHECK(EmitTeeLocal(f));
--- a/js/src/wasm/WasmOpIter.cpp
+++ b/js/src/wasm/WasmOpIter.cpp
@@ -219,17 +219,18 @@ OpKind wasm::Classify(OpBytes op) {
     case Op::I64Store8:
     case Op::I64Store16:
     case Op::I64Store32:
     case Op::I32Store:
     case Op::I64Store:
     case Op::F32Store:
     case Op::F64Store:
       return OpKind::Store;
-    case Op::Select:
+    case Op::SelectNumeric:
+    case Op::SelectTyped:
       return OpKind::Select;
     case Op::GetLocal:
       return OpKind::GetLocal;
     case Op::SetLocal:
       return OpKind::SetLocal;
     case Op::TeeLocal:
       return OpKind::TeeLocal;
     case Op::GetGlobal:
--- a/js/src/wasm/WasmOpIter.h
+++ b/js/src/wasm/WasmOpIter.h
@@ -430,17 +430,17 @@ class MOZ_STACK_CLASS OpIter : private P
   MOZ_MUST_USE bool readStore(ValType resultType, uint32_t byteSize,
                               LinearMemoryAddress<Value>* addr, Value* value);
   MOZ_MUST_USE bool readTeeStore(ValType resultType, uint32_t byteSize,
                                  LinearMemoryAddress<Value>* addr,
                                  Value* value);
   MOZ_MUST_USE bool readNop();
   MOZ_MUST_USE bool readMemorySize();
   MOZ_MUST_USE bool readMemoryGrow(Value* input);
-  MOZ_MUST_USE bool readSelect(StackType* type, Value* trueValue,
+  MOZ_MUST_USE bool readSelect(bool typed, StackType* type, Value* trueValue,
                                Value* falseValue, Value* condition);
   MOZ_MUST_USE bool readGetLocal(const ValTypeVector& locals, uint32_t* id);
   MOZ_MUST_USE bool readSetLocal(const ValTypeVector& locals, uint32_t* id,
                                  Value* value);
   MOZ_MUST_USE bool readTeeLocal(const ValTypeVector& locals, uint32_t* id,
                                  Value* value);
   MOZ_MUST_USE bool readGetGlobal(uint32_t* id);
   MOZ_MUST_USE bool readSetGlobal(uint32_t* id, Value* value);
@@ -1390,20 +1390,49 @@ inline bool OpIter<Policy>::readMemoryGr
   }
 
   infalliblePush(ValType::I32);
 
   return true;
 }
 
 template <typename Policy>
-inline bool OpIter<Policy>::readSelect(StackType* type, Value* trueValue,
-                                       Value* falseValue, Value* condition) {
+inline bool OpIter<Policy>::readSelect(bool typed, StackType* type,
+                                       Value* trueValue, Value* falseValue,
+                                       Value* condition) {
   MOZ_ASSERT(Classify(op_) == OpKind::Select);
 
+  if (typed) {
+    uint32_t length;
+    if (!readVarU32(&length)) {
+      return fail("unable to read select result length");
+    }
+    if (length != 1) {
+      return fail("bad number of results");
+    }
+    ValType result;
+    if (!readValType(&result)) {
+      return fail("invalid result type for select");
+    }
+
+    if (!popWithType(ValType::I32, condition)) {
+      return false;
+    }
+    if (!popWithType(result, falseValue)) {
+      return false;
+    }
+    if (!popWithType(result, trueValue)) {
+      return false;
+    }
+
+    *type = StackType(result);
+    infalliblePush(*type);
+    return true;
+  }
+
   if (!popWithType(ValType::I32, condition)) {
     return false;
   }
 
   StackType falseType;
   if (!popStackType(&falseType, falseValue)) {
     return false;
   }
--- a/js/src/wasm/WasmTextToBinary.cpp
+++ b/js/src/wasm/WasmTextToBinary.cpp
@@ -2421,16 +2421,32 @@ static bool ParseValType(WasmParseContex
   if (!type->isValid()) {
     c.ts.generateError(c.ts.peek(), "expected value type", c.error);
     return false;
   }
 
   return true;
 }
 
+static bool ParseValueTypeList(WasmParseContext& c, AstValTypeVector* vec) {
+  for (;;) {
+    AstValType vt;
+    if (!MaybeParseValType(c, &vt)) {
+      return false;
+    }
+    if (!vt.isValid()) {
+      break;
+    }
+    if (!vec->append(vt)) {
+      return false;
+    }
+  }
+  return true;
+}
+
 static bool ParseBlockSignature(WasmParseContext& c, AstExprType* type) {
   WasmToken token;
   AstValType vt;
 
   if (c.ts.getIf(WasmToken::OpenParen, &token)) {
     if (c.ts.getIf(WasmToken::Result)) {
       if (!ParseValType(c, &vt)) {
         return false;
@@ -3120,32 +3136,59 @@ static AstDrop* ParseDrop(WasmParseConte
   if (!value) {
     return nullptr;
   }
 
   return new (c.lifo) AstDrop(*value);
 }
 
 static AstSelect* ParseSelect(WasmParseContext& c, bool inParens) {
-  AstExpr* condition = ParseExpr(c, inParens);
+  AstValTypeVector result(c.lifo);
+  AstExpr* condition = nullptr;
+
+  while (!condition && c.ts.getIf(WasmToken::OpenParen)) {
+    WasmToken token = c.ts.get();
+    switch (token.kind()) {
+      case WasmToken::Result:
+        if (!ParseValueTypeList(c, &result)) {
+          return nullptr;
+        }
+        break;
+      default:
+        c.ts.unget(token);
+        AstExpr* expr = ParseExprInsideParens(c);
+        if (!expr) {
+          return nullptr;
+        }
+        condition = expr;
+        break;
+    }
+    if (!c.ts.match(WasmToken::CloseParen, c.error)) {
+      return nullptr;
+    }
+  }
+
   if (!condition) {
-    return nullptr;
+    condition = ParseExpr(c, inParens);
+    if (!condition) {
+      return nullptr;
+    }
   }
 
   AstExpr* op1 = ParseExpr(c, inParens);
   if (!op1) {
     return nullptr;
   }
 
   AstExpr* op2 = ParseExpr(c, inParens);
   if (!op2) {
     return nullptr;
   }
 
-  return new (c.lifo) AstSelect(condition, op1, op2);
+  return new (c.lifo) AstSelect(condition, op1, op2, std::move(result));
 }
 
 static AstIf* ParseIf(WasmParseContext& c, bool inParens) {
   AstName name = c.ts.getIfName();
 
   AstExprType type(ExprType::Limit);
   if (!ParseBlockSignature(c, &type)) {
     return nullptr;
@@ -4112,32 +4155,16 @@ static AstExpr* ParseExprBody(WasmParseC
 }
 
 static AstExpr* ParseExprInsideParens(WasmParseContext& c) {
   WasmToken token = c.ts.get();
 
   return ParseExprBody(c, token, true);
 }
 
-static bool ParseValueTypeList(WasmParseContext& c, AstValTypeVector* vec) {
-  for (;;) {
-    AstValType vt;
-    if (!MaybeParseValType(c, &vt)) {
-      return false;
-    }
-    if (!vt.isValid()) {
-      break;
-    }
-    if (!vec->append(vt)) {
-      return false;
-    }
-  }
-  return true;
-}
-
 static bool ParseResult(WasmParseContext& c, AstExprType* result) {
   if (!result->isVoid()) {
     c.ts.generateError(c.ts.peek(), c.error);
     return false;
   }
 
   AstValType type;
   if (!ParseValType(c, &type)) {
@@ -6265,18 +6292,36 @@ static bool EncodeConst(Encoder& e, AstC
   MOZ_CRASH("Bad value type");
 }
 
 static bool EncodeDrop(Encoder& e, AstDrop& drop) {
   return EncodeExpr(e, drop.value()) && e.writeOp(Op::Drop);
 }
 
 static bool EncodeSelect(Encoder& e, AstSelect& b) {
-  return EncodeExpr(e, *b.condition()) && EncodeExpr(e, *b.op1()) &&
-         EncodeExpr(e, *b.op2()) && e.writeOp(Op::Select);
+  if (!EncodeExpr(e, *b.condition()) || !EncodeExpr(e, *b.op1()) ||
+      !EncodeExpr(e, *b.op2())) {
+    return false;
+  }
+
+  if (b.result().empty()) {
+    return e.writeOp(Op::SelectNumeric);
+  }
+
+  if (!e.writeOp(Op::SelectTyped) || !e.writeVarU32(b.result().length())) {
+    return false;
+  }
+
+  for (AstValType vt : b.result()) {
+    if (!e.writeValType(vt.type())) {
+      return false;
+    }
+  }
+
+  return true;
 }
 
 static bool EncodeGetLocal(Encoder& e, AstGetLocal& gl) {
   return e.writeOp(Op::GetLocal) && e.writeVarU32(gl.local().index());
 }
 
 static bool EncodeSetLocal(Encoder& e, AstSetLocal& sl) {
   return EncodeExpr(e, sl.value()) && e.writeOp(Op::SetLocal) &&
--- a/js/src/wasm/WasmValidate.cpp
+++ b/js/src/wasm/WasmValidate.cpp
@@ -548,19 +548,28 @@ static bool DecodeFunctionBodyExprs(cons
       case uint16_t(Op::TableSet): {
         if (!env.refTypesEnabled()) {
           return iter.unrecognizedOpcode(&op);
         }
         uint32_t unusedTableIndex;
         CHECK(iter.readTableSet(&unusedTableIndex, &nothing, &nothing));
       }
 #endif
-      case uint16_t(Op::Select): {
+      case uint16_t(Op::SelectNumeric): {
         StackType unused;
-        CHECK(iter.readSelect(&unused, &nothing, &nothing, &nothing));
+        CHECK(iter.readSelect(/*typed*/ false, &unused, &nothing, &nothing,
+                              &nothing));
+      }
+      case uint16_t(Op::SelectTyped): {
+        if (!env.refTypesEnabled()) {
+          return iter.unrecognizedOpcode(&op);
+        }
+        StackType unused;
+        CHECK(iter.readSelect(/*typed*/ true, &unused, &nothing, &nothing,
+                              &nothing));
       }
       case uint16_t(Op::Block):
         CHECK(iter.readBlock());
       case uint16_t(Op::Loop):
         CHECK(iter.readLoop());
       case uint16_t(Op::If):
         CHECK(iter.readIf(&nothing));
       case uint16_t(Op::Else): {