Bug 1479465 - ref.eq on all wasm reference types. r=luke
authorLars T Hansen <lhansen@mozilla.com>
Mon, 30 Jul 2018 17:31:08 +0200
changeset 437664 71ded3ad0ed4fa86b939958dd86bd09c2cbf55d1
parent 437663 95985c7c160bd17f1d6c6e850f3304adeceb6135
child 437665 03d7d05c12a2371b5a486086411e40e4f2d7ab13
push id34690
push usercbrindusan@mozilla.com
push dateFri, 21 Sep 2018 17:30:01 +0000
treeherdermozilla-central@ce4e883f7642 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke
bugs1479465
milestone64.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 1479465 - ref.eq on all wasm reference types. r=luke Implement ref.eq on all wasm pointer types without worrying about implementing eqref yet, with standard prefix-based upcast semantics. See https://github.com/lars-t-hansen/moz-gc-experiments for a detailed description of these (intermediate) semantics.
js/src/jit-test/tests/wasm/gc/anyref.js
js/src/jit-test/tests/wasm/gc/gc-feature-opt-in.js
js/src/jit-test/tests/wasm/gc/ref.js
js/src/wasm/WasmBaselineCompile.cpp
js/src/wasm/WasmConstants.h
js/src/wasm/WasmIonCompile.cpp
js/src/wasm/WasmOpIter.cpp
js/src/wasm/WasmTextToBinary.cpp
js/src/wasm/WasmValidate.cpp
--- a/js/src/jit-test/tests/wasm/gc/anyref.js
+++ b/js/src/jit-test/tests/wasm/gc/anyref.js
@@ -88,21 +88,35 @@ let { exports } = wasmEvalText(`(module
         ref.null anyref
         set_local 0
         i32.const 58
         call $sum
         drop
         get_local 0
         ref.is_null
     )
-)`);
+
+    (func (export "ref_eq") (param $a anyref) (param $b anyref) (result i32)
+	  (ref.eq (get_local $a) (get_local $b)))
+
+    (func (export "ref_eq_for_control") (param $a anyref) (param $b anyref) (result f64)
+	  (if f64 (ref.eq (get_local $a) (get_local $b))
+	      (f64.const 5.0)
+	      (f64.const 3.0)))
+    )`);
 
 assertEq(exports.is_null(), 1);
 assertEq(exports.is_null_spill(), 1);
 assertEq(exports.is_null_local(), 1);
+assertEq(exports.ref_eq(null, null), 1);
+assertEq(exports.ref_eq(null, {}), 0);
+assertEq(exports.ref_eq(this, this), 1);
+assertEq(exports.ref_eq_for_control(null, null), 5);
+assertEq(exports.ref_eq_for_control(null, {}), 3);
+assertEq(exports.ref_eq_for_control(this, this), 5);
 
 // Anyref param and result in wasm functions.
 
 exports = wasmEvalText(`(module
     (gc_feature_opt_in 1)
     (func (export "is_null") (result i32) (param $ref anyref)
         get_local $ref
         ref.is_null
--- a/js/src/jit-test/tests/wasm/gc/gc-feature-opt-in.js
+++ b/js/src/jit-test/tests/wasm/gc/gc-feature-opt-in.js
@@ -113,8 +113,14 @@ assertErrorMessage(() => new WebAssembly
                    WebAssembly.CompileError,
                    /unrecognized opcode/);
 
 assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
     `(module
       (func ref.is_null))`)),
                    WebAssembly.CompileError,
                    /unrecognized opcode/);
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+    `(module
+      (func ref.eq))`)),
+                   WebAssembly.CompileError,
+                   /unrecognized opcode/);
--- a/js/src/jit-test/tests/wasm/gc/ref.js
+++ b/js/src/jit-test/tests/wasm/gc/ref.js
@@ -53,16 +53,21 @@ var bin = wasmTextToBinary(
        (ref.null (ref $even)))
 
       (func (param (ref $cons))
        (call $cdr (get_local 0))
        drop
        (call $imp (get_local 0))
        drop)
 
+      (func (param (ref $cons))
+       (drop (ref.eq (get_local 0) (ref.null (ref $cons))))
+       (drop (ref.eq (ref.null (ref $cons)) (get_local 0)))
+       (drop (ref.eq (get_local 0) (ref.null anyref)))
+       (drop (ref.eq (ref.null anyref) (get_local 0))))
      )`);
 
 // Validation
 
 assertEq(WebAssembly.validate(bin), true);
 
 // ref.is_null should work on any reference type
 
--- a/js/src/wasm/WasmBaselineCompile.cpp
+++ b/js/src/wasm/WasmBaselineCompile.cpp
@@ -6018,16 +6018,17 @@ class BaseCompiler final : public BaseCo
 
     void doReturn(ExprType returnType, bool popStack);
     void pushReturnedIfNonVoid(const FunctionCall& call, ExprType type);
 
     void emitCompareI32(Assembler::Condition compareOp, ValType compareType);
     void emitCompareI64(Assembler::Condition compareOp, ValType compareType);
     void emitCompareF32(Assembler::DoubleCondition compareOp, ValType compareType);
     void emitCompareF64(Assembler::DoubleCondition compareOp, ValType compareType);
+    void emitCompareRef(Assembler::Condition compareOp, ValType compareType);
 
     void emitAddI32();
     void emitAddI64();
     void emitAddF64();
     void emitAddF32();
     void emitSubtractI32();
     void emitSubtractI64();
     void emitSubtractF32();
@@ -7379,16 +7380,21 @@ BaseCompiler::sniffConditionalControlCmp
     // On x86, latent i64 binary comparisons use too many registers: the
     // reserved join register and the lhs and rhs operands require six, but we
     // only have five.
     if (operandType == ValType::I64) {
         return false;
     }
 #endif
 
+    // No optimization for pointer compares yet.
+    if (operandType.isRefOrAnyRef()) {
+        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):
         setLatentCompare(compareOp, operandType);
         return true;
@@ -9165,16 +9171,30 @@ BaseCompiler::emitCompareF64(Assembler::
     moveImm32(0, rd);
     masm.bind(&across);
     freeF64(rs0);
     freeF64(rs1);
     pushI32(rd);
 }
 
 void
+BaseCompiler::emitCompareRef(Assembler::Condition compareOp, ValType compareType)
+{
+    MOZ_ASSERT(!sniffConditionalControlCmp(compareOp, compareType));
+
+    RegPtr rs1, rs2;
+    pop2xRef(&rs1, &rs2);
+    RegI32 rd = needI32();
+    masm.cmpPtrSet(compareOp, rs1, rs2, rd);
+    freeRef(rs1);
+    freeRef(rs2);
+    pushI32(rd);
+}
+
+void
 BaseCompiler::emitInstanceCall(uint32_t lineOrBytecode, const MIRTypeVector& sig,
                                ExprType retType, SymbolicAddress builtin)
 {
     MOZ_ASSERT(sig[0] == MIRType::Pointer);
 
     sync();
 
     uint32_t numArgs = sig.length() - 1 /* instance */;
@@ -9634,21 +9654,23 @@ BaseCompiler::emitMemOrTableCopy(bool is
 }
 
 bool
 BaseCompiler::emitMemOrTableDrop(bool isMem)
 {
     uint32_t lineOrBytecode = readCallSiteLineOrBytecode();
 
     uint32_t segIndex = 0;
-    if (!iter_.readMemOrTableDrop(isMem, &segIndex))
+    if (!iter_.readMemOrTableDrop(isMem, &segIndex)) {
         return false;
-
-    if (deadCode_)
+    }
+
+    if (deadCode_) {
         return true;
+    }
 
     // Despite the cast to int32_t, the callee regards the value as unsigned.
     pushI32(int32_t(segIndex));
     SymbolicAddress callee = isMem ? SymbolicAddress::MemDrop
                                    : SymbolicAddress::TableDrop;
     emitInstanceCall(lineOrBytecode, SigPI_, ExprType::Void, callee);
 
     Label ok;
@@ -9685,21 +9707,23 @@ BaseCompiler::emitMemFill()
 
 bool
 BaseCompiler::emitMemOrTableInit(bool isMem)
 {
     uint32_t lineOrBytecode = readCallSiteLineOrBytecode();
 
     uint32_t segIndex = 0;
     Nothing nothing;
-    if (!iter_.readMemOrTableInit(isMem, &segIndex, &nothing, &nothing, &nothing))
+    if (!iter_.readMemOrTableInit(isMem, &segIndex, &nothing, &nothing, &nothing)) {
         return false;
-
-    if (deadCode_)
+    }
+
+    if (deadCode_) {
         return true;
+    }
 
     pushI32(int32_t(segIndex));
     SymbolicAddress callee = isMem ? SymbolicAddress::MemInit
                                    : SymbolicAddress::TableInit;
     emitInstanceCall(lineOrBytecode, SigPIIII_, ExprType::Void, callee);
 
     Label ok;
     masm.branchTest32(Assembler::NotSigned, ReturnReg, ReturnReg, &ok);
@@ -10270,16 +10294,21 @@ BaseCompiler::emitBody()
 
           // Memory Related
           case uint16_t(Op::GrowMemory):
             CHECK_NEXT(emitGrowMemory());
           case uint16_t(Op::CurrentMemory):
             CHECK_NEXT(emitCurrentMemory());
 
 #ifdef ENABLE_WASM_GC
+          case uint16_t(Op::RefEq):
+            if (env_.gcTypesEnabled() == HasGcTypes::False) {
+                return iter_.unrecognizedOpcode(&op);
+            }
+            CHECK_NEXT(emitComparison(emitCompareRef, ValType::AnyRef, Assembler::Equal));
           case uint16_t(Op::RefNull):
             if (env_.gcTypesEnabled() == HasGcTypes::False) {
                 return iter_.unrecognizedOpcode(&op);
             }
             CHECK_NEXT(emitRefNull());
             break;
           case uint16_t(Op::RefIsNull):
             if (env_.gcTypesEnabled() == HasGcTypes::False) {
--- a/js/src/wasm/WasmConstants.h
+++ b/js/src/wasm/WasmConstants.h
@@ -355,16 +355,17 @@ enum class Op
     I32Extend16S                         = 0xc1,
     I64Extend8S                          = 0xc2,
     I64Extend16S                         = 0xc3,
     I64Extend32S                         = 0xc4,
 
     // GC ops
     RefNull                              = 0xd0,
     RefIsNull                            = 0xd1,
+    RefEq                                = 0xd2, // Unofficial
 
     FirstPrefix                          = 0xfc,
     MiscPrefix                           = 0xfc,
     ThreadPrefix                         = 0xfe,
     MozPrefix                            = 0xff,
 
     Limit                                = 0x100
 };
--- a/js/src/wasm/WasmIonCompile.cpp
+++ b/js/src/wasm/WasmIonCompile.cpp
@@ -3554,16 +3554,17 @@ EmitBodyExprs(FunctionCompiler& f)
           case uint16_t(Op::I64ReinterpretF64):
             CHECK(EmitReinterpret(f, ValType::I64, ValType::F64, MIRType::Int64));
           case uint16_t(Op::F32ReinterpretI32):
             CHECK(EmitReinterpret(f, ValType::F32, ValType::I32, MIRType::Float32));
           case uint16_t(Op::F64ReinterpretI64):
             CHECK(EmitReinterpret(f, ValType::F64, ValType::I64, MIRType::Double));
 
           // GC types are NYI in Ion.
+          case uint16_t(Op::RefEq):
           case uint16_t(Op::RefNull):
           case uint16_t(Op::RefIsNull):
             return f.iter().unrecognizedOpcode(&op);
 
           // Sign extensions
           case uint16_t(Op::I32Extend8S):
             CHECK(EmitSignExtend(f, 1, 4));
           case uint16_t(Op::I32Extend16S):
--- a/js/src/wasm/WasmOpIter.cpp
+++ b/js/src/wasm/WasmOpIter.cpp
@@ -144,16 +144,17 @@ wasm::Classify(OpBytes op)
       case Op::F32Gt:
       case Op::F32Ge:
       case Op::F64Eq:
       case Op::F64Ne:
       case Op::F64Lt:
       case Op::F64Le:
       case Op::F64Gt:
       case Op::F64Ge:
+      case Op::RefEq:
         return OpKind::Comparison;
       case Op::I32Eqz:
       case Op::I32WrapI64:
       case Op::I32TruncSF32:
       case Op::I32TruncUF32:
       case Op::I32ReinterpretF32:
       case Op::I32TruncSF64:
       case Op::I32TruncUF64:
--- a/js/src/wasm/WasmTextToBinary.cpp
+++ b/js/src/wasm/WasmTextToBinary.cpp
@@ -2049,16 +2049,19 @@ WasmTokenStream::next()
       case 'r':
         if (consume(u"result")) {
             return WasmToken(WasmToken::Result, begin, cur_);
         }
         if (consume(u"return")) {
             return WasmToken(WasmToken::Return, begin, cur_);
         }
         if (consume(u"ref")) {
+            if (consume(u".eq")) {
+                return WasmToken(WasmToken::ComparisonOpcode, Op::RefEq, begin, cur_);
+            }
             if (consume(u".null")) {
                 return WasmToken(WasmToken::RefNull, begin, cur_);
             }
             if (consume(u".is_null")) {
                 return WasmToken(WasmToken::UnaryOpcode, Op::RefIsNull, begin, cur_);
             }
             return WasmToken(WasmToken::Ref, begin, cur_);
         }
--- a/js/src/wasm/WasmValidate.cpp
+++ b/js/src/wasm/WasmValidate.cpp
@@ -932,16 +932,23 @@ DecodeFunctionBodyExprs(const ModuleEnvi
               }
 #endif
               default:
                 return iter.unrecognizedOpcode(&op);
             }
             break;
           }
 #ifdef ENABLE_WASM_GC
+          case uint16_t(Op::RefEq): {
+            if (env.gcTypesEnabled() == HasGcTypes::False) {
+                return iter.unrecognizedOpcode(&op);
+            }
+            CHECK(iter.readComparison(ValType::AnyRef, &nothing, &nothing));
+            break;
+          }
           case uint16_t(Op::RefNull): {
             if (env.gcTypesEnabled() == HasGcTypes::False) {
                 return iter.unrecognizedOpcode(&op);
             }
             ValType unusedType;
             CHECK(iter.readRefNull(&unusedType));
             break;
           }