Bug 1255691: Implement Select; r=sunfish
authorBenjamin Bouvier <benj@benj.me>
Mon, 14 Mar 2016 11:27:42 +0100
changeset 329650 32f3f27d070c203f3c4869b9fbb44d9c7857a88d
parent 329649 88a8fe526ba3f7bc7aec06bac5b0e88b968baec1
child 329651 efed456bdb89af3eff3221afc2ac5d47aebfc469
push id1146
push userCallek@gmail.com
push dateMon, 25 Jul 2016 16:35:44 +0000
treeherdermozilla-release@a55778f9cd5a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssunfish
bugs1255691
milestone48.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 1255691: Implement Select; r=sunfish MozReview-Commit-ID: 4Pb2z4fTyYG
js/src/asmjs/Wasm.cpp
js/src/asmjs/WasmIonCompile.cpp
js/src/asmjs/WasmTextToBinary.cpp
js/src/jit-test/tests/wasm/basic-control-flow.js
js/src/jit-test/tests/wasm/basic.js
js/src/jit/MIR.h
js/src/jit/MOpcodes.h
js/src/jit/arm/CodeGenerator-arm.cpp
js/src/jit/arm/CodeGenerator-arm.h
js/src/jit/arm/Lowering-arm.cpp
js/src/jit/arm/Lowering-arm.h
js/src/jit/arm/MacroAssembler-arm.h
js/src/jit/arm64/Lowering-arm64.cpp
js/src/jit/arm64/Lowering-arm64.h
js/src/jit/none/Lowering-none.h
js/src/jit/shared/LIR-shared.h
js/src/jit/shared/LOpcodes-shared.h
js/src/jit/x64/Assembler-x64.h
js/src/jit/x64/BaseAssembler-x64.h
js/src/jit/x64/CodeGenerator-x64.cpp
js/src/jit/x64/CodeGenerator-x64.h
js/src/jit/x86-shared/Assembler-x86-shared.h
js/src/jit/x86-shared/BaseAssembler-x86-shared.h
js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp
js/src/jit/x86-shared/CodeGenerator-x86-shared.h
js/src/jit/x86-shared/Encoding-x86-shared.h
js/src/jit/x86-shared/Lowering-x86-shared.cpp
js/src/jit/x86-shared/Lowering-x86-shared.h
--- a/js/src/asmjs/Wasm.cpp
+++ b/js/src/asmjs/Wasm.cpp
@@ -445,16 +445,44 @@ DecodeConversionOperator(FunctionDecoder
     if (!CheckType(f, actual, argType))
         return false;
 
     *type = ToExprType(to);
     return true;
 }
 
 static bool
+DecodeSelectOperator(FunctionDecoder& f, ExprType* type)
+{
+    ExprType trueType;
+    if (!DecodeExpr(f, &trueType))
+        return false;
+
+    if (trueType == ExprType::I64 && !f.checkI64Support())
+        return false;
+
+    ExprType falseType;
+    if (!DecodeExpr(f, &falseType))
+        return false;
+
+    if (!CheckType(f, falseType, trueType))
+        return false;
+
+    ExprType condType;
+    if (!DecodeExpr(f, &condType))
+        return false;
+
+    if (!CheckType(f, condType, ValType::I32))
+        return false;
+
+    *type = trueType;
+    return true;
+}
+
+static bool
 DecodeIfElse(FunctionDecoder& f, bool hasElse, ExprType* type)
 {
     ExprType condType;
     if (!DecodeExpr(f, &condType))
         return false;
 
     if (!CheckType(f, condType, ValType::I32))
         return false;
@@ -638,16 +666,18 @@ DecodeExpr(FunctionDecoder& f, ExprType*
       case Expr::F32Const:
         return DecodeConstF32(f, type);
       case Expr::F64Const:
         return DecodeConstF64(f, type);
       case Expr::GetLocal:
         return DecodeGetLocal(f, type);
       case Expr::SetLocal:
         return DecodeSetLocal(f, type);
+      case Expr::Select:
+        return DecodeSelectOperator(f, type);
       case Expr::Block:
         return DecodeBlock(f, /* isLoop */ false, type);
       case Expr::Loop:
         return DecodeBlock(f, /* isLoop */ true, type);
       case Expr::If:
         return DecodeIfElse(f, /* hasElse */ false, type);
       case Expr::IfElse:
         return DecodeIfElse(f, /* hasElse */ true, type);
--- a/js/src/asmjs/WasmIonCompile.cpp
+++ b/js/src/asmjs/WasmIonCompile.cpp
@@ -453,16 +453,25 @@ class FunctionCompiler
     {
         if (inDeadCode())
             return nullptr;
         T* ins = T::NewAsmJS(alloc(), op);
         curBlock_->add(ins);
         return ins;
     }
 
+    MDefinition* select(MDefinition* trueExpr, MDefinition* falseExpr, MDefinition* condExpr)
+    {
+        if (inDeadCode())
+            return nullptr;
+        auto* ins = MAsmSelect::New(alloc(), trueExpr, falseExpr, condExpr);
+        curBlock_->add(ins);
+        return ins;
+    }
+
     MDefinition* extendI32(MDefinition* op, bool isUnsigned)
     {
         if (inDeadCode())
             return nullptr;
         MExtendInt32ToInt64* ins = MExtendInt32ToInt64::NewAsmJS(alloc(), op, isUnsigned);
         curBlock_->add(ins);
         return ins;
     }
@@ -2180,16 +2189,35 @@ EmitMultiply(FunctionCompiler& f, ValTyp
     MDefinition* rhs;
     if (!EmitExpr(f, &rhs))
         return false;
     MIRType mirType = ToMIRType(type);
     *def = f.mul(lhs, rhs, mirType, mirType == MIRType_Int32 ? MMul::Integer : MMul::Normal);
     return true;
 }
 
+static bool
+EmitSelect(FunctionCompiler& f, MDefinition** def)
+{
+    MDefinition* trueExpr;
+    if (!EmitExpr(f, &trueExpr))
+        return false;
+
+    MDefinition* falseExpr;
+    if (!EmitExpr(f, &falseExpr))
+        return false;
+
+    MDefinition* condExpr;
+    if (!EmitExpr(f, &condExpr))
+        return false;
+
+    *def = f.select(trueExpr, falseExpr, condExpr);
+    return true;
+}
+
 typedef bool IsAdd;
 
 static bool
 EmitAddOrSub(FunctionCompiler& f, ValType type, bool isAdd, MDefinition** def)
 {
     MDefinition* lhs;
     if (!EmitExpr(f, &lhs))
         return false;
@@ -2747,16 +2775,20 @@ EmitExpr(FunctionCompiler& f, MDefinitio
         return EmitGetLocal(f, def);
       case Expr::SetLocal:
         return EmitSetLocal(f, def);
       case Expr::LoadGlobal:
         return EmitLoadGlobal(f, def);
       case Expr::StoreGlobal:
         return EmitStoreGlobal(f, def);
 
+      // Select
+      case Expr::Select:
+        return EmitSelect(f, def);
+
       // I32
       case Expr::I32Const:
         return EmitLiteral(f, ValType::I32, def);
       case Expr::I32Add:
         return EmitAddOrSub(f, ValType::I32, IsAdd(true), def);
       case Expr::I32Sub:
         return EmitAddOrSub(f, ValType::I32, IsAdd(false), def);
       case Expr::I32Mul:
@@ -3042,17 +3074,16 @@ EmitExpr(FunctionCompiler& f, MDefinitio
       case Expr::I32AtomicsLoad:
         return EmitAtomicsLoad(f, def);
       case Expr::I32AtomicsStore:
         return EmitAtomicsStore(f, def);
       case Expr::I32AtomicsBinOp:
         return EmitAtomicsBinOp(f, def);
 
       // Future opcodes
-      case Expr::Select:
       case Expr::F32CopySign:
       case Expr::F32Trunc:
       case Expr::F32Nearest:
       case Expr::F64CopySign:
       case Expr::F64Nearest:
       case Expr::F64Trunc:
       case Expr::I64ReinterpretF64:
       case Expr::F64ReinterpretI64:
--- a/js/src/asmjs/WasmTextToBinary.cpp
+++ b/js/src/asmjs/WasmTextToBinary.cpp
@@ -205,16 +205,17 @@ enum class WasmAstExprKind
     ConversionOperator,
     GetLocal,
     If,
     Load,
     Nop,
     Return,
     SetLocal,
     Store,
+    TernaryOperator,
     Trap,
     UnaryOperator,
 };
 
 class WasmAstExpr : public WasmAstNode
 {
     const WasmAstExprKind kind_;
 
@@ -703,16 +704,36 @@ class WasmAstBinaryOperator final : publ
         expr_(expr), lhs_(lhs), rhs_(rhs)
     {}
 
     Expr expr() const { return expr_; }
     WasmAstExpr* lhs() const { return lhs_; }
     WasmAstExpr* rhs() const { return rhs_; }
 };
 
+class WasmAstTernaryOperator : public WasmAstExpr
+{
+    Expr expr_;
+    WasmAstExpr* op0_;
+    WasmAstExpr* op1_;
+    WasmAstExpr* op2_;
+
+  public:
+    static const WasmAstExprKind Kind = WasmAstExprKind::TernaryOperator;
+    WasmAstTernaryOperator(Expr expr, WasmAstExpr* op0, WasmAstExpr* op1, WasmAstExpr* op2)
+      : WasmAstExpr(Kind),
+        expr_(expr), op0_(op0), op1_(op1), op2_(op2)
+    {}
+
+    Expr expr() const { return expr_; }
+    WasmAstExpr* op0() const { return op0_; }
+    WasmAstExpr* op1() const { return op1_; }
+    WasmAstExpr* op2() const { return op2_; }
+};
+
 class WasmAstComparisonOperator final : public WasmAstExpr
 {
     Expr expr_;
     WasmAstExpr* lhs_;
     WasmAstExpr* rhs_;
 
   public:
     static const WasmAstExprKind Kind = WasmAstExprKind::ComparisonOperator;
@@ -799,16 +820,17 @@ class WasmToken
         OpenParen,
         Param,
         Result,
         Return,
         Segment,
         SetLocal,
         Store,
         Table,
+        TernaryOpcode,
         Text,
         Trap,
         Type,
         UnaryOpcode,
         ValueType
     };
   private:
     Kind kind_;
@@ -875,18 +897,19 @@ class WasmToken
         u.valueType_ = valueType;
     }
     explicit WasmToken(Kind kind, Expr expr, const char16_t* begin, const char16_t* end)
       : kind_(kind),
         begin_(begin),
         end_(end)
     {
         MOZ_ASSERT(begin != end);
-        MOZ_ASSERT(kind_ == UnaryOpcode || kind_ == BinaryOpcode || kind_ == ComparisonOpcode ||
-                   kind_ == ConversionOpcode || kind_ == Load || kind_ == Store);
+        MOZ_ASSERT(kind_ == UnaryOpcode || kind_ == BinaryOpcode || kind_ == TernaryOpcode ||
+                   kind_ == ComparisonOpcode || kind_ == ConversionOpcode ||
+                   kind_ == Load || kind_ == Store);
         u.expr_ = expr;
     }
     explicit WasmToken(const char16_t* begin)
       : kind_(Error),
         begin_(begin),
         end_(begin)
     {}
     Kind kind() const {
@@ -924,18 +947,19 @@ class WasmToken
         MOZ_ASSERT(kind_ == Float);
         return u.floatLiteralKind_;
     }
     ValType valueType() const {
         MOZ_ASSERT(kind_ == ValueType || kind_ == Const);
         return u.valueType_;
     }
     Expr expr() const {
-        MOZ_ASSERT(kind_ == UnaryOpcode || kind_ == BinaryOpcode || kind_ == ComparisonOpcode ||
-                   kind_ == ConversionOpcode || kind_ == Load || kind_ == Store);
+        MOZ_ASSERT(kind_ == UnaryOpcode || kind_ == BinaryOpcode || kind_ == TernaryOpcode ||
+                   kind_ == ComparisonOpcode || kind_ == ConversionOpcode ||
+                   kind_ == Load || kind_ == Store);
         return u.expr_;
     }
 };
 
 } // end anonymous namespace
 
 static bool
 IsWasmNewLine(char16_t c)
@@ -1945,16 +1969,18 @@ WasmTokenStream::next()
       case 'r':
         if (consume(MOZ_UTF16("result")))
             return WasmToken(WasmToken::Result, begin, cur_);
         if (consume(MOZ_UTF16("return")))
             return WasmToken(WasmToken::Return, begin, cur_);
         break;
 
       case 's':
+        if (consume(MOZ_UTF16("select")))
+            return WasmToken(WasmToken::TernaryOpcode, Expr::Select, begin, cur_);
         if (consume(MOZ_UTF16("set_local")))
             return WasmToken(WasmToken::SetLocal, begin, cur_);
         if (consume(MOZ_UTF16("segment")))
             return WasmToken(WasmToken::Segment, begin, cur_);
         break;
 
       case 't':
         if (consume(MOZ_UTF16("table")))
@@ -2503,16 +2529,34 @@ ParseComparisonOperator(WasmParseContext
 
     WasmAstExpr* rhs = ParseExpr(c);
     if (!rhs)
         return nullptr;
 
     return new(c.lifo) WasmAstComparisonOperator(expr, lhs, rhs);
 }
 
+static WasmAstTernaryOperator*
+ParseTernaryOperator(WasmParseContext& c, Expr expr)
+{
+    WasmAstExpr* op0 = ParseExpr(c);
+    if (!op0)
+        return nullptr;
+
+    WasmAstExpr* op1 = ParseExpr(c);
+    if (!op1)
+        return nullptr;
+
+    WasmAstExpr* op2 = ParseExpr(c);
+    if (!op2)
+        return nullptr;
+
+    return new(c.lifo) WasmAstTernaryOperator(expr, op0, op1, op2);
+}
+
 static WasmAstConversionOperator*
 ParseConversionOperator(WasmParseContext& c, Expr expr)
 {
     WasmAstExpr* op = ParseExpr(c);
     if (!op)
         return nullptr;
 
     return new(c.lifo) WasmAstConversionOperator(expr, op);
@@ -2736,16 +2780,18 @@ ParseExprInsideParens(WasmParseContext& 
       case WasmToken::Loop:
         return ParseBlock(c, Expr::Loop);
       case WasmToken::Return:
         return ParseReturn(c);
       case WasmToken::SetLocal:
         return ParseSetLocal(c);
       case WasmToken::Store:
         return ParseStore(c, token.expr());
+      case WasmToken::TernaryOpcode:
+        return ParseTernaryOperator(c, token.expr());
       case WasmToken::UnaryOpcode:
         return ParseUnaryOperator(c, token.expr());
       default:
         c.ts.generateError(token, c.error);
         return nullptr;
     }
 }
 
@@ -3340,16 +3386,24 @@ ResolveUnaryOperator(Resolver& r, WasmAs
 static bool
 ResolveBinaryOperator(Resolver& r, WasmAstBinaryOperator& b)
 {
     return ResolveExpr(r, *b.lhs()) &&
            ResolveExpr(r, *b.rhs());
 }
 
 static bool
+ResolveTernaryOperator(Resolver& r, WasmAstTernaryOperator& b)
+{
+    return ResolveExpr(r, *b.op0()) &&
+           ResolveExpr(r, *b.op1()) &&
+           ResolveExpr(r, *b.op2());
+}
+
+static bool
 ResolveComparisonOperator(Resolver& r, WasmAstComparisonOperator& b)
 {
     return ResolveExpr(r, *b.lhs()) &&
            ResolveExpr(r, *b.rhs());
 }
 
 static bool
 ResolveConversionOperator(Resolver& r, WasmAstConversionOperator& b)
@@ -3436,16 +3490,18 @@ ResolveExpr(Resolver& r, WasmAstExpr& ex
       case WasmAstExprKind::Return:
         return ResolveReturn(r, expr.as<WasmAstReturn>());
       case WasmAstExprKind::SetLocal:
         return ResolveSetLocal(r, expr.as<WasmAstSetLocal>());
       case WasmAstExprKind::Store:
         return ResolveStore(r, expr.as<WasmAstStore>());
       case WasmAstExprKind::BranchTable:
         return ResolveBranchTable(r, expr.as<WasmAstBranchTable>());
+      case WasmAstExprKind::TernaryOperator:
+        return ResolveTernaryOperator(r, expr.as<WasmAstTernaryOperator>());
       case WasmAstExprKind::UnaryOperator:
         return ResolveUnaryOperator(r, expr.as<WasmAstUnaryOperator>());
     }
     MOZ_CRASH("Bad expr kind");
 }
 
 static bool
 ResolveFunc(Resolver& r, WasmAstFunc& func)
@@ -3656,16 +3712,25 @@ static bool
 EncodeBinaryOperator(Encoder& e, WasmAstBinaryOperator& b)
 {
     return e.writeExpr(b.expr()) &&
            EncodeExpr(e, *b.lhs()) &&
            EncodeExpr(e, *b.rhs());
 }
 
 static bool
+EncodeTernaryOperator(Encoder& e, WasmAstTernaryOperator& b)
+{
+    return e.writeExpr(b.expr()) &&
+           EncodeExpr(e, *b.op0()) &&
+           EncodeExpr(e, *b.op1()) &&
+           EncodeExpr(e, *b.op2());
+}
+
+static bool
 EncodeComparisonOperator(Encoder& e, WasmAstComparisonOperator& b)
 {
     return e.writeExpr(b.expr()) &&
            EncodeExpr(e, *b.lhs()) &&
            EncodeExpr(e, *b.rhs());
 }
 
 static bool
@@ -3767,16 +3832,18 @@ EncodeExpr(Encoder& e, WasmAstExpr& expr
       case WasmAstExprKind::Return:
         return EncodeReturn(e, expr.as<WasmAstReturn>());
       case WasmAstExprKind::SetLocal:
         return EncodeSetLocal(e, expr.as<WasmAstSetLocal>());
       case WasmAstExprKind::Store:
         return EncodeStore(e, expr.as<WasmAstStore>());
       case WasmAstExprKind::BranchTable:
         return EncodeBranchTable(e, expr.as<WasmAstBranchTable>());
+      case WasmAstExprKind::TernaryOperator:
+        return EncodeTernaryOperator(e, expr.as<WasmAstTernaryOperator>());
       case WasmAstExprKind::UnaryOperator:
         return EncodeUnaryOperator(e, expr.as<WasmAstUnaryOperator>());
     }
     MOZ_CRASH("Bad expr kind");
 }
 
 /*****************************************************************************/
 // wasm AST binary serialization
--- a/js/src/jit-test/tests/wasm/basic-control-flow.js
+++ b/js/src/jit-test/tests/wasm/basic-control-flow.js
@@ -410,9 +410,8 @@ assertEq(f(3), -1);
 // unreachable
 
 const UNREACHABLE = /unreachable/;
 assertErrorMessage(wasmEvalText(`(module (func (trap)) (export "" 0))`), Error, UNREACHABLE);
 assertErrorMessage(wasmEvalText(`(module (func (if (trap) (i32.const 0))) (export "" 0))`), Error, UNREACHABLE);
 assertErrorMessage(wasmEvalText(`(module (func (block (br_if 0 (trap)))) (export "" 0))`), Error, UNREACHABLE);
 assertErrorMessage(wasmEvalText(`(module (func (block (br_table 0 (trap)))) (export "" 0))`), Error, UNREACHABLE);
 assertErrorMessage(wasmEvalText(`(module (func (result i32) (i32.add (i32.const 0) (trap))) (export "" 0))`), Error, UNREACHABLE);
-
--- a/js/src/jit-test/tests/wasm/basic.js
+++ b/js/src/jit-test/tests/wasm/basic.js
@@ -432,8 +432,170 @@ var {v2i, i2i, i2v} = wasmEvalText(`(mod
     (export "i2i" 7)
     (export "i2v" 8)
 )`);
 
 wasmEvalText('(module (func $foo (nop)) (func (call $foo)))');
 wasmEvalText('(module (func (call $foo)) (func $foo (nop)))');
 wasmEvalText('(module (import $bar "a" "") (func (call_import $bar)) (func $foo (nop)))', {a:()=>{}});
 
+
+// ----------------------------------------------------------------------------
+// select
+
+assertErrorMessage(() => wasmEvalText('(module (func (select (i32.const 0) (f32.const 0) (i32.const 0))))'), TypeError, mismatchError("f32", "i32"));
+assertErrorMessage(() => wasmEvalText('(module (func (select (i32.const 0) (i32.const 0) (f32.const 0))))'), TypeError, mismatchError("f32", "i32"));
+
+(function testSideEffects() {
+
+var numT = 0;
+var numF = 0;
+
+var imports = {
+    ifTrue: () => 1 + numT++,
+    ifFalse: () => -1 + numF++,
+}
+
+// Test that side-effects are applied on both branches.
+var f = wasmEvalText(`
+(module
+ (import "ifTrue" "" (result i32))
+ (import "ifFalse" "" (result i32))
+ (func (result i32) (param i32)
+  (select
+   (call_import 0)
+   (call_import 1)
+   (get_local 0)
+  )
+ )
+ (export "" 0)
+)
+`, imports);
+
+assertEq(f(-1), numT);
+assertEq(numT, 1);
+assertEq(numF, 1);
+
+assertEq(f(0), numF - 2);
+assertEq(numT, 2);
+assertEq(numF, 2);
+
+assertEq(f(1), numT);
+assertEq(numT, 3);
+assertEq(numF, 3);
+
+assertEq(f(0), numF - 2);
+assertEq(numT, 4);
+assertEq(numF, 4);
+
+assertEq(f(0), numF - 2);
+assertEq(numT, 5);
+assertEq(numF, 5);
+
+assertEq(f(1), numT);
+assertEq(numT, 6);
+assertEq(numF, 6);
+
+})();
+
+function testSelect(type, trueVal, falseVal) {
+
+    function toJS(val) {
+        switch (val) {
+            case "infinity": return Infinity;
+            case "nan": return NaN;
+            case "-0": return -0;
+            default: return val;
+        }
+    }
+
+    var trueJS = toJS(trueVal);
+    var falseJS = toJS(falseVal);
+
+    // Always true condition
+    var alwaysTrue = wasmEvalText(`
+    (module
+     (func (result ${type}) (param i32)
+      (select
+       (${type}.const ${trueVal})
+       (${type}.const ${falseVal})
+       (i32.const 1)
+      )
+     )
+     (export "" 0)
+    )
+    `, imports);
+
+    assertEq(alwaysTrue(0), trueJS);
+    assertEq(alwaysTrue(1), trueJS);
+    assertEq(alwaysTrue(-1), trueJS);
+
+    // Always false condition
+    var alwaysFalse = wasmEvalText(`
+    (module
+     (func (result ${type}) (param i32)
+      (select
+       (${type}.const ${trueVal})
+       (${type}.const ${falseVal})
+       (i32.const 0)
+      )
+     )
+     (export "" 0)
+    )
+    `, imports);
+
+    assertEq(alwaysFalse(0), falseJS);
+    assertEq(alwaysFalse(1), falseJS);
+    assertEq(alwaysFalse(-1), falseJS);
+
+    // Variable condition
+    var f = wasmEvalText(`
+    (module
+     (func (result ${type}) (param i32)
+      (select
+       (${type}.const ${trueVal})
+       (${type}.const ${falseVal})
+       (get_local 0)
+      )
+     )
+     (export "" 0)
+    )
+    `, imports);
+
+    assertEq(f(0), falseJS);
+    assertEq(f(1), trueJS);
+    assertEq(f(-1), trueJS);
+}
+
+testSelect('i32', 13, 37);
+testSelect('i32', Math.pow(2, 31) - 1, -Math.pow(2, 31));
+
+testSelect('f32', Math.fround(13.37), Math.fround(19.89));
+testSelect('f32', 'infinity', '-0');
+testSelect('f32', 'nan', Math.pow(2, -31));
+
+testSelect('f64', 13.37, 19.89);
+testSelect('f64', 'infinity', '-0');
+testSelect('f64', 'nan', Math.pow(2, -31));
+
+if (!hasI64) {
+    assertErrorMessage(() => wasmEvalText('(module (func (select (i64.const 0) (i64.const 1) (i32.const 0))))'), TypeError, /NYI/);
+} else {
+    var f = wasmEvalText(`
+    (module
+     (func (result i32) (param i32)
+      (i64.gt_s
+       (select
+        (i64.const ${Math.pow(2, 31) + 1})
+        (i64.const ${-Math.pow(2, 31) - 1})
+        (get_local 0)
+       )
+       (i64.const ${Math.pow(2, 31)})
+      )
+     )
+     (export "" 0)
+    )
+    `, imports);
+
+    assertEq(f(0), 0);
+    assertEq(f(1), 1);
+    assertEq(f(-1), 1);
+}
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -14478,16 +14478,59 @@ class MAsmJSCall final
         return spIncrement_;
     }
 
     bool possiblyCalls() const override {
         return true;
     }
 };
 
+class MAsmSelect
+  : public MTernaryInstruction,
+    public NoTypePolicy::Data
+{
+    MAsmSelect(MDefinition* trueExpr, MDefinition* falseExpr, MDefinition *condExpr)
+      : MTernaryInstruction(trueExpr, falseExpr, condExpr)
+    {
+        MOZ_ASSERT(condExpr->type() == MIRType_Int32);
+        MOZ_ASSERT(trueExpr->type() == falseExpr->type());
+        setResultType(trueExpr->type());
+        setMovable();
+    }
+
+  public:
+    INSTRUCTION_HEADER(AsmSelect)
+
+    static MAsmSelect* New(TempAllocator& alloc, MDefinition* trueExpr, MDefinition* falseExpr,
+                           MDefinition* condExpr)
+    {
+        return new(alloc) MAsmSelect(trueExpr, falseExpr, condExpr);
+    }
+
+    MDefinition* trueExpr() const {
+        return getOperand(0);
+    }
+    MDefinition* falseExpr() const {
+        return getOperand(1);
+    }
+    MDefinition* condExpr() const {
+        return getOperand(2);
+    }
+
+    AliasSet getAliasSet() const override {
+        return AliasSet::None();
+    }
+
+    bool congruentTo(const MDefinition* ins) const override {
+        return congruentIfOperandsEqual(ins);
+    }
+
+    ALLOW_CLONE(MAsmSelect)
+};
+
 class MUnknownValue : public MNullaryInstruction
 {
   protected:
     MUnknownValue() {
         setResultType(MIRType_Value);
     }
 
   public:
--- a/js/src/jit/MOpcodes.h
+++ b/js/src/jit/MOpcodes.h
@@ -271,16 +271,17 @@ namespace jit {
     _(AsmJSStoreGlobalVar)                                                  \
     _(AsmJSLoadFuncPtr)                                                     \
     _(AsmJSLoadFFIFunc)                                                     \
     _(AsmJSReturn)                                                          \
     _(AsmJSParameter)                                                       \
     _(AsmJSVoidReturn)                                                      \
     _(AsmJSPassStackArg)                                                    \
     _(AsmJSCall)                                                            \
+    _(AsmSelect)                                                            \
     _(NewDerivedTypedObject)                                                \
     _(RecompileCheck)                                                       \
     _(MemoryBarrier)                                                        \
     _(AsmJSCompareExchangeHeap)                                             \
     _(AsmJSAtomicExchangeHeap)                                              \
     _(AsmJSAtomicBinopHeap)                                                 \
     _(UnknownValue)                                                         \
     _(LexicalCheck)                                                         \
--- a/js/src/jit/arm/CodeGenerator-arm.cpp
+++ b/js/src/jit/arm/CodeGenerator-arm.cpp
@@ -2096,16 +2096,45 @@ CodeGeneratorARM::visitAtomicTypedArrayE
         AtomicBinopToTypedArray(this, lir->mir()->operation(), arrayType, value, mem, flagTemp);
     } else {
         BaseIndex mem(elements, ToRegister(lir->index()), ScaleFromElemWidth(width));
         AtomicBinopToTypedArray(this, lir->mir()->operation(), arrayType, value, mem, flagTemp);
     }
 }
 
 void
+CodeGeneratorARM::visitAsmSelect(LAsmSelect* ins)
+{
+    MIRType mirType = ins->mir()->type();
+
+    Register cond = ToRegister(ins->condExpr());
+    masm.ma_cmp(cond, Imm32(0));
+
+    if (mirType == MIRType_Int32) {
+        Register falseExpr = ToRegister(ins->falseExpr());
+        Register out = ToRegister(ins->output());
+        MOZ_ASSERT(ToRegister(ins->trueExpr()) == out, "true expr input is reused for output");
+        masm.ma_mov(falseExpr, out, LeaveCC, Assembler::Zero);
+        return;
+    }
+
+    FloatRegister out = ToFloatRegister(ins->output());
+    MOZ_ASSERT(ToFloatRegister(ins->trueExpr()) == out, "true expr input is reused for output");
+
+    FloatRegister falseExpr = ToFloatRegister(ins->falseExpr());
+
+    if (mirType == MIRType_Double)
+        masm.moveDouble(falseExpr, out, Assembler::Zero);
+    else if (mirType == MIRType_Float32)
+        masm.moveFloat32(falseExpr, out, Assembler::Zero);
+    else
+        MOZ_CRASH("unhandled type in visitAsmSelect!");
+}
+
+void
 CodeGeneratorARM::visitAsmJSCall(LAsmJSCall* ins)
 {
     MAsmJSCall* mir = ins->mir();
 
     if (UseHardFpABI() || mir->callee().which() != MAsmJSCall::Callee::Builtin) {
         emitAsmJSCall(ins);
         return;
     }
--- a/js/src/jit/arm/CodeGenerator-arm.h
+++ b/js/src/jit/arm/CodeGenerator-arm.h
@@ -195,16 +195,17 @@ class CodeGeneratorARM : public CodeGene
     void visitNegD(LNegD* lir);
     void visitNegF(LNegF* lir);
     void visitLoadTypedArrayElementStatic(LLoadTypedArrayElementStatic* ins);
     void visitStoreTypedArrayElementStatic(LStoreTypedArrayElementStatic* ins);
     void visitAtomicTypedArrayElementBinop(LAtomicTypedArrayElementBinop* lir);
     void visitAtomicTypedArrayElementBinopForEffect(LAtomicTypedArrayElementBinopForEffect* lir);
     void visitCompareExchangeTypedArrayElement(LCompareExchangeTypedArrayElement* lir);
     void visitAtomicExchangeTypedArrayElement(LAtomicExchangeTypedArrayElement* lir);
+    void visitAsmSelect(LAsmSelect* ins);
     void visitAsmJSCall(LAsmJSCall* ins);
     void visitAsmJSLoadHeap(LAsmJSLoadHeap* ins);
     void visitAsmJSStoreHeap(LAsmJSStoreHeap* ins);
     void visitAsmJSCompareExchangeHeap(LAsmJSCompareExchangeHeap* ins);
     void visitAsmJSCompareExchangeCallout(LAsmJSCompareExchangeCallout* ins);
     void visitAsmJSAtomicExchangeHeap(LAsmJSAtomicExchangeHeap* ins);
     void visitAsmJSAtomicExchangeCallout(LAsmJSAtomicExchangeCallout* ins);
     void visitAsmJSAtomicBinopHeap(LAsmJSAtomicBinopHeap* ins);
--- a/js/src/jit/arm/Lowering-arm.cpp
+++ b/js/src/jit/arm/Lowering-arm.cpp
@@ -406,16 +406,29 @@ LIRGeneratorARM::lowerUrshD(MUrsh* mir)
     MOZ_ASSERT(lhs->type() == MIRType_Int32);
     MOZ_ASSERT(rhs->type() == MIRType_Int32);
 
     LUrshD* lir = new(alloc()) LUrshD(useRegister(lhs), useRegisterOrConstant(rhs), temp());
     define(lir, mir);
 }
 
 void
+LIRGeneratorARM::visitAsmSelect(MAsmSelect* ins)
+{
+    MOZ_ASSERT(ins->type() != MIRType_Int64);
+
+    auto* lir = new(alloc()) LAsmSelect(useRegisterAtStart(ins->trueExpr()),
+                                        useRegister(ins->falseExpr()),
+                                        useRegister(ins->condExpr())
+                                       );
+
+    defineReuseInput(lir, ins, LAsmSelect::TrueExprIndex);
+}
+
+void
 LIRGeneratorARM::visitAsmJSNeg(MAsmJSNeg* ins)
 {
     if (ins->type() == MIRType_Int32) {
         define(new(alloc()) LNegI(useRegisterAtStart(ins->input())), ins);
     } else if (ins->type() == MIRType_Float32) {
         define(new(alloc()) LNegF(useRegisterAtStart(ins->input())), ins);
     } else {
         MOZ_ASSERT(ins->type() == MIRType_Double);
--- a/js/src/jit/arm/Lowering-arm.h
+++ b/js/src/jit/arm/Lowering-arm.h
@@ -89,16 +89,17 @@ class LIRGeneratorARM : public LIRGenera
 
   public:
     void visitBox(MBox* box);
     void visitUnbox(MUnbox* unbox);
     void visitReturn(MReturn* ret);
     void lowerPhi(MPhi* phi);
     void visitGuardShape(MGuardShape* ins);
     void visitGuardObjectGroup(MGuardObjectGroup* ins);
+    void visitAsmSelect(MAsmSelect* ins);
     void visitAsmJSUnsignedToDouble(MAsmJSUnsignedToDouble* ins);
     void visitAsmJSUnsignedToFloat32(MAsmJSUnsignedToFloat32* ins);
     void visitAsmJSLoadHeap(MAsmJSLoadHeap* ins);
     void visitAsmJSStoreHeap(MAsmJSStoreHeap* ins);
     void visitAsmJSLoadFuncPtr(MAsmJSLoadFuncPtr* ins);
     void visitAsmJSCompareExchangeHeap(MAsmJSCompareExchangeHeap* ins);
     void visitAsmJSAtomicExchangeHeap(MAsmJSAtomicExchangeHeap* ins);
     void visitAsmJSAtomicBinopHeap(MAsmJSAtomicBinopHeap* ins);
--- a/js/src/jit/arm/MacroAssembler-arm.h
+++ b/js/src/jit/arm/MacroAssembler-arm.h
@@ -998,18 +998,18 @@ class MacroAssemblerARMCompat : public M
     void storePtr(Register src, AbsoluteAddress dest);
     void storeDouble(FloatRegister src, Address addr) {
         ma_vstr(src, addr);
     }
     void storeDouble(FloatRegister src, BaseIndex addr) {
         uint32_t scale = Imm32::ShiftOf(addr.scale).value;
         ma_vstr(src, addr.base, addr.index, scale, addr.offset);
     }
-    void moveDouble(FloatRegister src, FloatRegister dest) {
-        ma_vmov(src, dest);
+    void moveDouble(FloatRegister src, FloatRegister dest, Condition cc = Always) {
+        ma_vmov(src, dest, cc);
     }
 
     void storeFloat32(FloatRegister src, const Address& addr) {
         ma_vstr(VFPRegister(src).singleOverlay(), addr);
     }
     void storeFloat32(FloatRegister src, const BaseIndex& addr) {
         uint32_t scale = Imm32::ShiftOf(addr.scale).value;
         ma_vstr(VFPRegister(src).singleOverlay(), addr.base, addr.index, scale, addr.offset);
@@ -1436,18 +1436,18 @@ class MacroAssemblerARMCompat : public M
     void ma_storeImm(Imm32 c, const Address& dest) {
         ma_mov(c, lr);
         ma_str(lr, dest);
     }
     BufferOffset ma_BoundsCheck(Register bounded) {
         return as_cmp(bounded, Imm8(0));
     }
 
-    void moveFloat32(FloatRegister src, FloatRegister dest) {
-        as_vmov(VFPRegister(dest).singleOverlay(), VFPRegister(src).singleOverlay());
+    void moveFloat32(FloatRegister src, FloatRegister dest, Condition cc = Always) {
+        as_vmov(VFPRegister(dest).singleOverlay(), VFPRegister(src).singleOverlay(), cc);
     }
 
     void loadWasmActivation(Register dest) {
         loadPtr(Address(GlobalReg, wasm::ActivationGlobalDataOffset - AsmJSGlobalRegBias), dest);
     }
     void loadAsmJSHeapRegisterFromGlobalData() {
         loadPtr(Address(GlobalReg, wasm::HeapGlobalDataOffset - AsmJSGlobalRegBias), HeapReg);
     }
--- a/js/src/jit/arm64/Lowering-arm64.cpp
+++ b/js/src/jit/arm64/Lowering-arm64.cpp
@@ -197,16 +197,22 @@ LIRGeneratorARM64::lowerUrshD(MUrsh* mir
 
 void
 LIRGeneratorARM64::visitAsmJSNeg(MAsmJSNeg* ins)
 {
     MOZ_CRASH("visitAsmJSNeg");
 }
 
 void
+LIRGeneratorARM64::visitAsmSelect(MAsmSelect* ins)
+{
+    MOZ_CRASH("visitAsmSelect");
+}
+
+void
 LIRGeneratorARM64::lowerUDiv(MDiv* div)
 {
     MOZ_CRASH("lowerUDiv");
 }
 
 void
 LIRGeneratorARM64::lowerUMod(MMod* mod)
 {
--- a/js/src/jit/arm64/Lowering-arm64.h
+++ b/js/src/jit/arm64/Lowering-arm64.h
@@ -79,16 +79,17 @@ class LIRGeneratorARM64 : public LIRGene
     void lowerModI(MMod* mod);
     void lowerDivI64(MDiv* div);
     void lowerModI64(MMod* mod);
     void lowerMulI(MMul* mul, MDefinition* lhs, MDefinition* rhs);
     void lowerUDiv(MDiv* div);
     void lowerUMod(MMod* mod);
     void visitPowHalf(MPowHalf* ins);
     void visitAsmJSNeg(MAsmJSNeg* ins);
+    void visitAsmSelect(MAsmSelect* ins);
 
     LTableSwitchV* newLTableSwitchV(MTableSwitch* ins);
     LTableSwitch* newLTableSwitch(const LAllocation& in,
                                   const LDefinition& inputCopy,
                                   MTableSwitch* ins);
 
   public:
     void visitBox(MBox* box);
--- a/js/src/jit/none/Lowering-none.h
+++ b/js/src/jit/none/Lowering-none.h
@@ -80,16 +80,17 @@ class LIRGeneratorNone : public LIRGener
     void visitAsmJSLoadFuncPtr(MAsmJSLoadFuncPtr* ins) { MOZ_CRASH(); }
     void visitStoreTypedArrayElementStatic(MStoreTypedArrayElementStatic* ins) { MOZ_CRASH(); }
     void visitAtomicTypedArrayElementBinop(MAtomicTypedArrayElementBinop* ins) { MOZ_CRASH(); }
     void visitCompareExchangeTypedArrayElement(MCompareExchangeTypedArrayElement* ins) { MOZ_CRASH(); }
     void visitAtomicExchangeTypedArrayElement(MAtomicExchangeTypedArrayElement* ins) { MOZ_CRASH(); }
     void visitAsmJSCompareExchangeHeap(MAsmJSCompareExchangeHeap* ins) { MOZ_CRASH(); }
     void visitAsmJSAtomicExchangeHeap(MAsmJSAtomicExchangeHeap* ins) { MOZ_CRASH(); }
     void visitAsmJSAtomicBinopHeap(MAsmJSAtomicBinopHeap* ins) { MOZ_CRASH(); }
+    void visitAsmSelect(MAsmSelect*) { MOZ_CRASH(); }
 
     LTableSwitch* newLTableSwitch(LAllocation, LDefinition, MTableSwitch*) { MOZ_CRASH(); }
     LTableSwitchV* newLTableSwitchV(MTableSwitch*) { MOZ_CRASH(); }
     void visitSimdSelect(MSimdSelect* ins) { MOZ_CRASH(); }
     void visitSimdSplatX4(MSimdSplatX4* ins) { MOZ_CRASH(); }
     void visitSimdValueX4(MSimdValueX4* lir) { MOZ_CRASH(); }
     void visitSubstr(MSubstr*) { MOZ_CRASH(); }
     void visitSimdBinaryArith(js::jit::MSimdBinaryArith*) { MOZ_CRASH(); }
--- a/js/src/jit/shared/LIR-shared.h
+++ b/js/src/jit/shared/LIR-shared.h
@@ -7250,16 +7250,67 @@ class LHasClass : public LInstructionHel
     const LAllocation* lhs() {
         return getOperand(0);
     }
     MHasClass* mir() const {
         return mir_->toHasClass();
     }
 };
 
+template<size_t Defs, size_t Ops>
+class LAsmSelectBase : public LInstructionHelper<Defs, Ops, 0>
+{
+    typedef LInstructionHelper<Defs, Ops, 0> Base;
+  public:
+    static const size_t TrueExprIndex = 0;
+
+    const LAllocation* trueExpr() {
+        static_assert(TrueExprIndex == 0, "trueExprIndex kept in sync");
+        return Base::getOperand(0);
+    }
+    const LAllocation* falseExpr() {
+        return Base::getOperand(1);
+    }
+    const LAllocation* condExpr() {
+        return Base::getOperand(2);
+    }
+
+    MAsmSelect* mir() const {
+        return Base::mir_->toAsmSelect();
+    }
+};
+
+class LAsmSelect : public LAsmSelectBase<1, 3>
+{
+  public:
+    LIR_HEADER(AsmSelect);
+
+    LAsmSelect(const LAllocation& trueExpr, const LAllocation& falseExpr, const LAllocation& cond) {
+        static_assert(TrueExprIndex == 0, "trueExprIndex kept in sync");
+        setOperand(0, trueExpr);
+        setOperand(1, falseExpr);
+        setOperand(2, cond);
+    }
+};
+
+class LAsmSelectI64 : public LAsmSelectBase<INT64_PIECES, 2 * INT64_PIECES + 1>
+{
+  public:
+    LIR_HEADER(AsmSelectI64);
+
+    LAsmSelectI64(const LInt64Allocation& trueExpr, const LInt64Allocation& falseExpr,
+                  const LAllocation& cond)
+    {
+        static_assert(TrueExprIndex == 0, "trueExprIndex kept in sync");
+        setInt64Operand(0, trueExpr);
+        setInt64Operand(1, falseExpr);
+        setOperand(2, cond);
+    }
+};
+
 class LAsmJSLoadHeap : public LInstructionHelper<1, 1, 0>
 {
   public:
     LIR_HEADER(AsmJSLoadHeap);
     explicit LAsmJSLoadHeap(const LAllocation& ptr) {
         setOperand(0, ptr);
     }
     MAsmJSLoadHeap* mir() const {
--- a/js/src/jit/shared/LOpcodes-shared.h
+++ b/js/src/jit/shared/LOpcodes-shared.h
@@ -350,16 +350,18 @@
     _(GetDOMMemberV)                \
     _(GetDOMMemberT)                \
     _(SetDOMProperty)               \
     _(CallDOMNative)                \
     _(IsCallable)                   \
     _(IsObject)                     \
     _(IsObjectAndBranch)            \
     _(HasClass)                     \
+    _(AsmSelect)                    \
+    _(AsmSelectI64)                 \
     _(AsmJSLoadHeap)                \
     _(AsmJSStoreHeap)               \
     _(AsmJSLoadFuncPtr)             \
     _(AsmJSLoadGlobalVar)           \
     _(AsmJSStoreGlobalVar)          \
     _(AsmJSLoadFFIFunc)             \
     _(AsmJSParameter)               \
     _(AsmJSReturn)                  \
--- a/js/src/jit/x64/Assembler-x64.h
+++ b/js/src/jit/x64/Assembler-x64.h
@@ -433,16 +433,32 @@ class Assembler : public AssemblerX86Sha
     }
     void vmovq(FloatRegister src, Register dest) {
         masm.vmovq_rr(src.encoding(), dest.encoding());
     }
     void movq(Register src, Register dest) {
         masm.movq_rr(src.encoding(), dest.encoding());
     }
 
+    void cmovzq(const Operand& src, Register dest) {
+        switch (src.kind()) {
+          case Operand::REG:
+            masm.cmovzq_rr(src.reg(), dest.encoding());
+            break;
+          case Operand::MEM_REG_DISP:
+            masm.cmovzq_mr(src.disp(), src.base(), dest.encoding());
+            break;
+          case Operand::MEM_SCALE:
+            masm.cmovzq_mr(src.disp(), src.base(), src.index(), src.scale(), dest.encoding());
+            break;
+          default:
+            MOZ_CRASH("unexpected operand kind");
+        }
+    }
+
     void xchgq(Register src, Register dest) {
         masm.xchgq_rr(src.encoding(), dest.encoding());
     }
 
     void movslq(Register src, Register dest) {
         masm.movslq_rr(src.encoding(), dest.encoding());
     }
     void movslq(const Operand& src, Register dest) {
--- a/js/src/jit/x64/BaseAssembler-x64.h
+++ b/js/src/jit/x64/BaseAssembler-x64.h
@@ -430,16 +430,32 @@ class BaseAssemblerX64 : public BaseAsse
     {
         spew("testq      $0x%4x, " MEM_obs, rhs, ADDR_obs(offset, base, index, scale));
         m_formatter.oneByteOp64(OP_GROUP3_EvIz, offset, base, index, scale, GROUP3_OP_TEST);
         m_formatter.immediate32(rhs);
     }
 
     // Various move ops:
 
+    void cmovzq_rr(RegisterID src, RegisterID dst)
+    {
+        spew("cmovz     %s, %s", GPReg16Name(src), GPReg32Name(dst));
+        m_formatter.twoByteOp64(OP2_CMOVZ_GvqpEvqp, src, dst);
+    }
+    void cmovzq_mr(int32_t offset, RegisterID base, RegisterID dst)
+    {
+        spew("cmovz     " MEM_ob ", %s", ADDR_ob(offset, base), GPReg32Name(dst));
+        m_formatter.twoByteOp64(OP2_CMOVZ_GvqpEvqp, offset, base, dst);
+    }
+    void cmovzq_mr(int32_t offset, RegisterID base, RegisterID index, int scale, RegisterID dst)
+    {
+        spew("cmovz     " MEM_obs ", %s", ADDR_obs(offset, base, index, scale), GPReg32Name(dst));
+        m_formatter.twoByteOp64(OP2_CMOVZ_GvqpEvqp, offset, base, index, scale, dst);
+    }
+
     void xchgq_rr(RegisterID src, RegisterID dst)
     {
         spew("xchgq      %s, %s", GPReg64Name(src), GPReg64Name(dst));
         m_formatter.oneByteOp64(OP_XCHG_GvEv, src, dst);
     }
     void xchgq_rm(RegisterID src, int32_t offset, RegisterID base)
     {
         spew("xchgq      %s, " MEM_ob, GPReg64Name(src), ADDR_ob(offset, base));
--- a/js/src/jit/x64/CodeGenerator-x64.cpp
+++ b/js/src/jit/x64/CodeGenerator-x64.cpp
@@ -473,16 +473,31 @@ CodeGeneratorX64::visitUDivOrMod64(LUDiv
     // Zero extend the lhs into rdx to make (rdx:rax).
     masm.xorl(rdx, rdx);
     masm.udivq(rhs);
 
     masm.bind(&done);
 }
 
 void
+CodeGeneratorX64::visitAsmSelectI64(LAsmSelectI64* lir)
+{
+    MOZ_ASSERT(lir->mir()->type() == MIRType_Int64);
+
+    Register cond = ToRegister(lir->condExpr());
+    Operand falseExpr = ToOperand(lir->falseExpr());
+
+    Register out = ToRegister(lir->output());
+    MOZ_ASSERT(ToRegister(lir->trueExpr()) == out, "true expr is reused for input");
+
+    masm.test32(cond, cond);
+    masm.cmovzq(falseExpr, out);
+}
+
+void
 CodeGeneratorX64::visitAsmJSUInt32ToDouble(LAsmJSUInt32ToDouble* lir)
 {
     masm.convertUInt32ToDouble(ToRegister(lir->input()), ToFloatRegister(lir->output()));
 }
 
 void
 CodeGeneratorX64::visitAsmJSUInt32ToFloat32(LAsmJSUInt32ToFloat32* lir)
 {
--- a/js/src/jit/x64/CodeGenerator-x64.h
+++ b/js/src/jit/x64/CodeGenerator-x64.h
@@ -54,16 +54,17 @@ class CodeGeneratorX64 : public CodeGene
     void visitTruncateDToInt32(LTruncateDToInt32* ins);
     void visitTruncateFToInt32(LTruncateFToInt32* ins);
     void visitWrapInt64ToInt32(LWrapInt64ToInt32* lir);
     void visitExtendInt32ToInt64(LExtendInt32ToInt64* lir);
     void visitTruncateToInt64(LTruncateToInt64* lir);
     void visitInt64ToFloatingPoint(LInt64ToFloatingPoint* lir);
     void visitLoadTypedArrayElementStatic(LLoadTypedArrayElementStatic* ins);
     void visitStoreTypedArrayElementStatic(LStoreTypedArrayElementStatic* ins);
+    void visitAsmSelectI64(LAsmSelectI64* ins);
     void visitAsmJSCall(LAsmJSCall* ins);
     void visitAsmJSLoadHeap(LAsmJSLoadHeap* ins);
     void visitAsmJSStoreHeap(LAsmJSStoreHeap* ins);
     void visitAsmJSCompareExchangeHeap(LAsmJSCompareExchangeHeap* ins);
     void visitAsmJSAtomicExchangeHeap(LAsmJSAtomicExchangeHeap* ins);
     void visitAsmJSAtomicBinopHeap(LAsmJSAtomicBinopHeap* ins);
     void visitAsmJSAtomicBinopHeapForEffect(LAsmJSAtomicBinopHeapForEffect* ins);
     void visitAsmJSLoadGlobalVar(LAsmJSLoadGlobalVar* ins);
--- a/js/src/jit/x86-shared/Assembler-x86-shared.h
+++ b/js/src/jit/x86-shared/Assembler-x86-shared.h
@@ -440,16 +440,31 @@ class AssemblerX86Shared : public Assemb
     void nopAlign(int alignment) {
         masm.nopAlign(alignment);
     }
     void writeCodePointer(CodeOffset* label) {
         // A CodeOffset only has one use, bake in the "end of list" value.
         masm.jumpTablePointer(LabelBase::INVALID_OFFSET);
         label->bind(masm.size());
     }
+    void cmovz(const Operand& src, Register dest) {
+        switch (src.kind()) {
+          case Operand::REG:
+            masm.cmovz_rr(src.reg(), dest.encoding());
+            break;
+          case Operand::MEM_REG_DISP:
+            masm.cmovz_mr(src.disp(), src.base(), dest.encoding());
+            break;
+          case Operand::MEM_SCALE:
+            masm.cmovz_mr(src.disp(), src.base(), src.index(), src.scale(), dest.encoding());
+            break;
+          default:
+            MOZ_CRASH("unexpected operand kind");
+        }
+    }
     void movl(Imm32 imm32, Register dest) {
         masm.movl_i32r(imm32.value, dest.encoding());
     }
     void movl(Register src, Register dest) {
         masm.movl_rr(src.encoding(), dest.encoding());
     }
     void movl(const Operand& src, Register dest) {
         switch (src.kind()) {
--- a/js/src/jit/x86-shared/BaseAssembler-x86-shared.h
+++ b/js/src/jit/x86-shared/BaseAssembler-x86-shared.h
@@ -1766,16 +1766,32 @@ public:
         m_formatter.oneByteOp(OP_XCHG_GvEv, offset, base, src);
     }
     void xchgl_rm(RegisterID src, int32_t offset, RegisterID base, RegisterID index, int scale)
     {
         spew("xchgl      %s, " MEM_obs, GPReg32Name(src), ADDR_obs(offset, base, index, scale));
         m_formatter.oneByteOp(OP_XCHG_GvEv, offset, base, index, scale, src);
     }
 
+    void cmovz_rr(RegisterID src, RegisterID dst)
+    {
+        spew("cmovz     %s, %s", GPReg16Name(src), GPReg32Name(dst));
+        m_formatter.twoByteOp(OP2_CMOVZ_GvqpEvqp, src, dst);
+    }
+    void cmovz_mr(int32_t offset, RegisterID base, RegisterID dst)
+    {
+        spew("cmovz     " MEM_ob ", %s", ADDR_ob(offset, base), GPReg32Name(dst));
+        m_formatter.twoByteOp(OP2_CMOVZ_GvqpEvqp, offset, base, dst);
+    }
+    void cmovz_mr(int32_t offset, RegisterID base, RegisterID index, int scale, RegisterID dst)
+    {
+        spew("cmovz     " MEM_obs ", %s", ADDR_obs(offset, base, index, scale), GPReg32Name(dst));
+        m_formatter.twoByteOp(OP2_CMOVZ_GvqpEvqp, offset, base, index, scale, dst);
+    }
+
     void movl_rr(RegisterID src, RegisterID dst)
     {
         spew("movl       %s, %s", GPReg32Name(src), GPReg32Name(dst));
         m_formatter.oneByteOp(OP_MOV_GvEv, src, dst);
     }
 
     void movw_rm(RegisterID src, int32_t offset, RegisterID base)
     {
--- a/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp
+++ b/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp
@@ -296,16 +296,57 @@ CodeGeneratorX86Shared::visitAsmJSPassSt
               default: break;
             }
             MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("unexpected mir type in AsmJSPassStackArg");
         }
     }
 }
 
 void
+CodeGeneratorX86Shared::visitAsmSelect(LAsmSelect* ins)
+{
+    MIRType mirType = ins->mir()->type();
+
+    Register cond = ToRegister(ins->condExpr());
+    Operand falseExpr = ToOperand(ins->falseExpr());
+
+    masm.test32(cond, cond);
+
+    if (mirType == MIRType_Int32) {
+        Register out = ToRegister(ins->output());
+        MOZ_ASSERT(ToRegister(ins->trueExpr()) == out, "true expr input is reused for output");
+        masm.cmovz(falseExpr, out);
+        return;
+    }
+
+    FloatRegister out = ToFloatRegister(ins->output());
+    MOZ_ASSERT(ToFloatRegister(ins->trueExpr()) == out, "true expr input is reused for output");
+
+    Label done;
+    masm.j(Assembler::NonZero, &done);
+
+    if (mirType == MIRType_Float32) {
+        if (falseExpr.kind() == Operand::FPREG)
+            masm.moveFloat32(ToFloatRegister(ins->falseExpr()), out);
+        else
+            masm.loadFloat32(falseExpr, out);
+    } else if (mirType == MIRType_Double) {
+        if (falseExpr.kind() == Operand::FPREG)
+            masm.moveDouble(ToFloatRegister(ins->falseExpr()), out);
+        else
+            masm.loadDouble(falseExpr, out);
+    } else {
+        MOZ_CRASH("unhandled type in visitAsmSelect!");
+    }
+
+    masm.bind(&done);
+    return;
+}
+
+void
 CodeGeneratorX86Shared::visitOutOfLineLoadTypedArrayOutOfBounds(OutOfLineLoadTypedArrayOutOfBounds* ool)
 {
     switch (ool->viewType()) {
       case Scalar::Float32x4:
       case Scalar::Int32x4:
       case Scalar::MaxTypedArrayViewType:
         MOZ_CRASH("unexpected array type");
       case Scalar::Float32:
--- a/js/src/jit/x86-shared/CodeGenerator-x86-shared.h
+++ b/js/src/jit/x86-shared/CodeGenerator-x86-shared.h
@@ -256,16 +256,17 @@ class CodeGeneratorX86Shared : public Co
     virtual void visitRoundF(LRoundF* lir);
     virtual void visitGuardShape(LGuardShape* guard);
     virtual void visitGuardObjectGroup(LGuardObjectGroup* guard);
     virtual void visitGuardClass(LGuardClass* guard);
     virtual void visitEffectiveAddress(LEffectiveAddress* ins);
     virtual void visitUDivOrMod(LUDivOrMod* ins);
     virtual void visitUDivOrModConstant(LUDivOrModConstant *ins);
     virtual void visitAsmJSPassStackArg(LAsmJSPassStackArg* ins);
+    virtual void visitAsmSelect(LAsmSelect* ins);
     virtual void visitMemoryBarrier(LMemoryBarrier* ins);
     virtual void visitAtomicTypedArrayElementBinop(LAtomicTypedArrayElementBinop* lir);
     virtual void visitAtomicTypedArrayElementBinopForEffect(LAtomicTypedArrayElementBinopForEffect* lir);
     virtual void visitCompareExchangeTypedArrayElement(LCompareExchangeTypedArrayElement* lir);
     virtual void visitAtomicExchangeTypedArrayElement(LAtomicExchangeTypedArrayElement* lir);
 
     void visitOutOfLineLoadTypedArrayOutOfBounds(OutOfLineLoadTypedArrayOutOfBounds* ool);
     void visitOffsetBoundsCheck(OffsetBoundsCheck* oolCheck);
--- a/js/src/jit/x86-shared/Encoding-x86-shared.h
+++ b/js/src/jit/x86-shared/Encoding-x86-shared.h
@@ -141,16 +141,17 @@ enum TwoByteOpcodeID {
     OP2_MOVLHPS_VqUq    = 0x16,
     OP2_MOVSHDUP_VpsWps = 0x16,
     OP2_MOVAPD_VsdWsd   = 0x28,
     OP2_MOVAPS_VsdWsd   = 0x28,
     OP2_MOVAPS_WsdVsd   = 0x29,
     OP2_CVTSI2SD_VsdEd  = 0x2A,
     OP2_CVTTSD2SI_GdWsd = 0x2C,
     OP2_UCOMISD_VsdWsd  = 0x2E,
+    OP2_CMOVZ_GvqpEvqp  = 0x44,
     OP2_MOVMSKPD_EdVd   = 0x50,
     OP2_ANDPS_VpsWps    = 0x54,
     OP2_ANDNPS_VpsWps   = 0x55,
     OP2_ORPS_VpsWps     = 0x56,
     OP2_XORPS_VpsWps    = 0x57,
     OP2_ADDSD_VsdWsd    = 0x58,
     OP2_ADDPS_VpsWps    = 0x58,
     OP2_MULSD_VsdWsd    = 0x59,
--- a/js/src/jit/x86-shared/Lowering-x86-shared.cpp
+++ b/js/src/jit/x86-shared/Lowering-x86-shared.cpp
@@ -284,16 +284,37 @@ LIRGeneratorX86Shared::lowerModI(MMod* m
                                     useRegister(mod->rhs()),
                                     tempFixed(eax));
     if (mod->fallible())
         assignSnapshot(lir, Bailout_DoubleOutput);
     defineFixed(lir, mod, LAllocation(AnyRegister(edx)));
 }
 
 void
+LIRGeneratorX86Shared::visitAsmSelect(MAsmSelect* ins)
+{
+    if (ins->type() == MIRType_Int64) {
+        auto* lir = new(alloc()) LAsmSelectI64(useInt64RegisterAtStart(ins->trueExpr()),
+                                               useInt64(ins->falseExpr()),
+                                               useRegister(ins->condExpr())
+                                              );
+
+        defineInt64ReuseInput(lir, ins, LAsmSelectI64::TrueExprIndex);
+        return;
+    }
+
+    auto* lir = new(alloc()) LAsmSelect(useRegisterAtStart(ins->trueExpr()),
+                                        use(ins->falseExpr()),
+                                        useRegister(ins->condExpr())
+                                       );
+
+    defineReuseInput(lir, ins, LAsmSelect::TrueExprIndex);
+}
+
+void
 LIRGeneratorX86Shared::visitAsmJSNeg(MAsmJSNeg* ins)
 {
     switch (ins->type()) {
       case MIRType_Int32:
         defineReuseInput(new(alloc()) LNegI(useRegisterAtStart(ins->input())), ins, 0);
         break;
       case MIRType_Float32:
         defineReuseInput(new(alloc()) LNegF(useRegisterAtStart(ins->input())), ins, 0);
--- a/js/src/jit/x86-shared/Lowering-x86-shared.h
+++ b/js/src/jit/x86-shared/Lowering-x86-shared.h
@@ -42,16 +42,17 @@ class LIRGeneratorX86Shared : public LIR
                      MDefinition* rhs);
     void lowerForCompIx4(LSimdBinaryCompIx4* ins, MSimdBinaryComp* mir,
                          MDefinition* lhs, MDefinition* rhs);
     void lowerForCompFx4(LSimdBinaryCompFx4* ins, MSimdBinaryComp* mir,
                          MDefinition* lhs, MDefinition* rhs);
     void lowerForBitAndAndBranch(LBitAndAndBranch* baab, MInstruction* mir,
                                  MDefinition* lhs, MDefinition* rhs);
     void visitAsmJSNeg(MAsmJSNeg* ins);
+    void visitAsmSelect(MAsmSelect* ins);
     void lowerMulI(MMul* mul, MDefinition* lhs, MDefinition* rhs);
     void lowerDivI(MDiv* div);
     void lowerModI(MMod* mod);
     void lowerUDiv(MDiv* div);
     void lowerUMod(MMod* mod);
     void lowerUrshD(MUrsh* mir);
     void lowerTruncateDToInt32(MTruncateToInt32* ins);
     void lowerTruncateFToInt32(MTruncateToInt32* ins);