Bug 1042602 - Symbol behavior changes in ES6 draft rev 26. r=h4writer.
author446240525@qq.com, Jason Orendorff <jorendorff@mozilla.com>
Thu, 31 Jul 2014 09:05:18 -0500
changeset 199012 8e4e04daf2a6
parent 199011 539e4e94e400
child 199013 a1d05feda793
push id47558
push userjorendorff@mozilla.com
push dateTue, 12 Aug 2014 15:01:11 +0000
treeherdermozilla-inbound@f497c1d55fd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersh4writer
bugs1042602
milestone34.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 1042602 - Symbol behavior changes in ES6 draft rev 26. r=h4writer.
js/src/builtin/SymbolObject.cpp
js/src/jit-test/tests/asm.js/testFFI.js
js/src/jit-test/tests/symbol/toNumber.js
js/src/jit-test/tests/symbol/typed-arrays.js
js/src/jit/IonMacroAssembler.cpp
js/src/jit/Lowering.cpp
js/src/jit/MIR.cpp
js/src/jit/MIR.h
js/src/jit/TypePolicy.cpp
js/src/js.msg
js/src/jsnum.cpp
js/src/tests/ecma_6/Symbol/comparisons.js
js/src/tests/ecma_6/Symbol/conversions.js
js/src/tests/ecma_6/Symbol/typed-arrays.js
js/src/vm/Interpreter.cpp
js/src/vm/TypedArrayObject.cpp
--- a/js/src/builtin/SymbolObject.cpp
+++ b/js/src/builtin/SymbolObject.cpp
@@ -117,22 +117,22 @@ SymbolObject::construct(JSContext *cx, u
     // step 4
     RootedSymbol symbol(cx, JS::Symbol::new_(cx, JS::SymbolCode::UniqueSymbol, desc));
     if (!symbol)
         return false;
     args.rval().setSymbol(symbol);
     return true;
 }
 
-// Stand-in for Symbol.prototype[@@toPrimitive], ES6 rev 25 (2014 May 22) 19.4.3.4
+// Stand-in for Symbol.prototype[@@toPrimitive], ES6 rev 26 (2014 Jul 18) 19.4.3.4
 bool
-SymbolObject::convert(JSContext *cx, HandleObject obj, JSType type, MutableHandleValue vp)
+SymbolObject::convert(JSContext *cx, HandleObject obj, JSType hint, MutableHandleValue vp)
 {
-    JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_SYMBOL_TO_PRIMITIVE);
-    return false;
+    vp.setSymbol(obj->as<SymbolObject>().unbox());
+    return true;
 }
 
 // ES6 rev 24 (2014 Apr 27) 19.4.2.2
 bool
 SymbolObject::for_(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
--- a/js/src/jit-test/tests/asm.js/testFFI.js
+++ b/js/src/jit-test/tests/asm.js/testFFI.js
@@ -136,19 +136,29 @@ try {
     throw 'assume unreachable';
 } catch (e) {
     assertEq(e, 'yolo');
     assertEq(i, 14);
 }
 
 // OOL conversion paths
 var INT32_MAX = Math.pow(2, 31) - 1;
-function ffiOOLConvertInt(n) { if (n == 40) return INT32_MAX + 1; return 42; }
+function ffiOOLConvertInt(n) { if (n == 40) return valueToConvert; return 42; }
 var f = asmLink(asmCompile('glob', 'imp', USE_ASM + 'var ffi=imp.ffi; function f(i) { i=i|0; return ffi(i >> 0) | 0; } return f'), null, {ffi:ffiOOLConvertInt});
 for (var i = 0; i < 40; i++)
     assertEq(f(i), 42);
+valueToConvert = INT32_MAX + 1;
 assertEq(f(40), INT32_MAX + 1 | 0);
+function testBadConversions(f) {
+    valueToConvert = {valueOf: function () { throw "FAIL"; }};
+    assertThrowsValue(() => f(40), "FAIL");
+    valueToConvert = Symbol();
+    assertThrowsInstanceOf(() => f(40), TypeError);
+}
+testBadConversions(f);
 
-function ffiOOLConvertDouble(n) { if (n == 40) return {valueOf: function() { return 13.37 }}; return 42.5; }
+function ffiOOLConvertDouble(n) { if (n == 40) return valueToConvert; return 42.5; }
 var f = asmLink(asmCompile('glob', 'imp', USE_ASM + 'var ffi=imp.ffi; function f(i) { i=i|0; return +ffi(i >> 0); } return f'), null, {ffi:ffiOOLConvertDouble});
 for (var i = 0; i < 40; i++)
     assertEq(f(i), 42.5);
+valueToConvert = {valueOf: function() { return 13.37 }};
 assertEq(f(40), 13.37);
+testBadConversions(f);
--- a/js/src/jit-test/tests/symbol/toNumber.js
+++ b/js/src/jit-test/tests/symbol/toNumber.js
@@ -1,52 +1,53 @@
 load(libdir + "asserts.js");
 
-function add() {
-    assertEq(Symbol() + 2, NaN);
+var sym = Symbol();
+
+function add2(x) {
+    return x + 2;
 }
 for (var i = 0; i < 9; i++)
-    add();
+    assertThrowsInstanceOf(() => add2(sym), TypeError);
 
-function mul() {
-    assertEq(Symbol() * Symbol(), NaN);
+function sqr(x) {
+    return x * x;
 }
 for (var i = 0; i < 9; i++)
-    mul();
+    assertThrowsInstanceOf(() => sqr(sym), TypeError);
 
-function bit_or() {
-    assertEq(Symbol() | Symbol(), 0);
+function bit_or(x) {
+    return x | x;
 }
 for (var i = 0; i < 9; i++)
-    bit_or();
+    assertThrowsInstanceOf(() => bit_or(sym), TypeError);
 
-function bit_not() {
-    assertEq(~Symbol(), -1);
+function bit_not(x) {
+    return ~x;
 }
 for (var i = 0; i < 9; i++)
-    bit_not();
+    assertThrowsInstanceOf(() => bit_not(sym), TypeError);
 
-function plus() {
-    assertEq(+Symbol(), NaN);
+function plus(x) {
+    return +x;
 }
 for (var i = 0; i < 9; i++)
-    plus();
-
+    assertThrowsInstanceOf(() => plus(sym), TypeError);
 
 function f(a, b) {
     return a + b;
 }
 
 function testPoly() {
     assertEq(f(20, 30), 50);
     assertEq(f("one", "two"), "onetwo");
-    assertEq(f(Symbol("one"), Symbol("two")), NaN);
-    assertEq(f(Symbol("14"), 14), NaN);
-    assertEq(f(Symbol("14"), 13.719), NaN);
-    assertEq(f(14, Symbol("14")), NaN);
-    assertEq(f(13.719, Symbol("14")), NaN);
+    assertThrowsInstanceOf(() => f(Symbol("one"), Symbol("two")), TypeError);
+    assertThrowsInstanceOf(() => f(Symbol("14"), 14), TypeError);
+    assertThrowsInstanceOf(() => f(Symbol("14"), 13.719), TypeError);
+    assertThrowsInstanceOf(() => f(14, Symbol("14")), TypeError);
+    assertThrowsInstanceOf(() => f(13.719, Symbol("14")), TypeError);
 }
 
 for (var i = 0; i < 9; i++)
     testPoly();
 
 for (var i = 0; i < 9; i++)
     assertThrowsInstanceOf(() => assertEq(f(Symbol("14"), "40"), NaN), TypeError);
--- a/js/src/jit-test/tests/symbol/typed-arrays.js
+++ b/js/src/jit-test/tests/symbol/typed-arrays.js
@@ -1,29 +1,29 @@
-var tests = [
-    {T: Uint8Array, result: 0},
-    {T: Uint8ClampedArray, result: 0},
-    {T: Int16Array, result: 0},
-    {T: Float32Array, result: NaN}
-];
+load(libdir + "asserts.js");
 
 var LENGTH = 1024, SYMBOL_INDEX = 999;
 
 var big = [];
 for (var i = 0; i < LENGTH; i++)
     big[i] = (i === SYMBOL_INDEX ? Symbol.for("comet") : i);
 
+var progress;
 function copy(arr, big) {
-    for (var i = 0; i < LENGTH; i++)
+    for (var i = 0; i < LENGTH; i++) {
         arr[i] = big[i];
+        progress = i;
+    }
 }
 
-for (var {T, result} of tests) {
-    // Typed array constructors convert symbols to NaN or 0.
-    arr = new T(big);
-    assertEq(arr[SYMBOL_INDEX], result);
+for (var T of [Uint8Array, Uint8ClampedArray, Int16Array, Float32Array]) {
+    // Typed array constructors convert symbols using ToNumber, which throws.
+    assertThrowsInstanceOf(() => new T(big), TypeError);
 
     // Element assignment does the same.
+    var arr = new T(big.length);
     for (var k = 0; k < 3; k++) {
-        copy(arr, big);
-        assertEq(arr[SYMBOL_INDEX], result);
+        progress = -1;
+        assertThrowsInstanceOf(() => copy(arr, big), TypeError);
+        assertEq(progress, SYMBOL_INDEX - 1);
+        assertEq(arr[SYMBOL_INDEX], 0);
     }
 }
--- a/js/src/jit/IonMacroAssembler.cpp
+++ b/js/src/jit/IonMacroAssembler.cpp
@@ -1769,17 +1769,17 @@ MacroAssembler::convertValueToInt(ValueO
             branchEqualTypeIfNeeded(MIRType_Object, maybeInput, tag, fail);
             branchTestUndefined(Assembler::NotEqual, tag, fail);
             break;
         }
     } else {
         jump(fail);
     }
 
-    // The value is null, undefined, or a symbol in truncation contexts - just emit 0.
+    // The value is null or undefined in truncation contexts - just emit 0.
     if (isNull.used())
         bind(&isNull);
     mov(ImmWord(0), output);
     jump(&done);
 
     // Try converting a string into a double, then jump to the double case.
     if (handleStrings) {
         bind(&isString);
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -1736,17 +1736,16 @@ LIRGenerator::visitToDouble(MToDouble *c
         return assignSnapshot(lir, Bailout_NonPrimitiveInput) && define(lir, convert);
       }
 
       case MIRType_Null:
         JS_ASSERT(conversion != MToDouble::NumbersOnly && conversion != MToDouble::NonNullNonStringPrimitives);
         return lowerConstantDouble(0, convert);
 
       case MIRType_Undefined:
-      case MIRType_Symbol:
         JS_ASSERT(conversion != MToDouble::NumbersOnly);
         return lowerConstantDouble(GenericNaN(), convert);
 
       case MIRType_Boolean:
         JS_ASSERT(conversion != MToDouble::NumbersOnly);
         /* FALLTHROUGH */
 
       case MIRType_Int32:
@@ -1760,17 +1759,17 @@ LIRGenerator::visitToDouble(MToDouble *c
         LFloat32ToDouble *lir = new(alloc()) LFloat32ToDouble(useRegisterAtStart(opd));
         return define(lir, convert);
       }
 
       case MIRType_Double:
         return redefine(convert, opd);
 
       default:
-        // Objects might be effectful.
+        // Objects might be effectful. Symbols will throw.
         // Strings are complicated - we don't handle them yet.
         MOZ_ASSUME_UNREACHABLE("unexpected type");
     }
 }
 
 bool
 LIRGenerator::visitToFloat32(MToFloat32 *convert)
 {
@@ -1786,17 +1785,16 @@ LIRGenerator::visitToFloat32(MToFloat32 
         return assignSnapshot(lir, Bailout_NonPrimitiveInput) && define(lir, convert);
       }
 
       case MIRType_Null:
         JS_ASSERT(conversion != MToFloat32::NonStringPrimitives);
         return lowerConstantFloat32(0, convert);
 
       case MIRType_Undefined:
-      case MIRType_Symbol:
         JS_ASSERT(conversion != MToFloat32::NumbersOnly);
         return lowerConstantFloat32(GenericNaN(), convert);
 
       case MIRType_Boolean:
         JS_ASSERT(conversion != MToFloat32::NumbersOnly);
         /* FALLTHROUGH */
 
       case MIRType_Int32:
@@ -1810,17 +1808,17 @@ LIRGenerator::visitToFloat32(MToFloat32 
         LDoubleToFloat32 *lir = new(alloc()) LDoubleToFloat32(useRegister(opd));
         return define(lir, convert);
       }
 
       case MIRType_Float32:
         return redefine(convert, opd);
 
       default:
-        // Objects might be effectful.
+        // Objects might be effectful. Symbols will throw.
         // Strings are complicated - we don't handle them yet.
         MOZ_ASSUME_UNREACHABLE("unexpected type");
         return false;
     }
 }
 
 bool
 LIRGenerator::visitToInt32(MToInt32 *convert)
@@ -1857,17 +1855,17 @@ LIRGenerator::visitToInt32(MToInt32 *con
         LDoubleToInt32 *lir = new(alloc()) LDoubleToInt32(useRegister(opd));
         return assignSnapshot(lir, Bailout_PrecisionLoss) && define(lir, convert);
       }
 
       case MIRType_String:
       case MIRType_Symbol:
       case MIRType_Object:
       case MIRType_Undefined:
-        // Objects might be effectful. Undefined and symbols coerce to NaN, not int32.
+        // Objects might be effectful. Symbols throw. Undefined coerces to NaN, not int32.
         MOZ_ASSUME_UNREACHABLE("ToInt32 invalid input type");
         return false;
 
       default:
         MOZ_ASSUME_UNREACHABLE("unexpected type");
     }
 }
 
@@ -1885,31 +1883,30 @@ LIRGenerator::visitTruncateToInt32(MTrun
             return false;
         return assignSnapshot(lir, Bailout_NonPrimitiveInput)
                && define(lir, truncate)
                && assignSafepoint(lir, truncate);
       }
 
       case MIRType_Null:
       case MIRType_Undefined:
-      case MIRType_Symbol:
         return define(new(alloc()) LInteger(0), truncate);
 
       case MIRType_Int32:
       case MIRType_Boolean:
         return redefine(truncate, opd);
 
       case MIRType_Double:
         return lowerTruncateDToInt32(truncate);
 
       case MIRType_Float32:
         return lowerTruncateFToInt32(truncate);
 
       default:
-        // Objects might be effectful.
+        // Objects might be effectful. Symbols throw.
         // Strings are complicated - we don't handle them yet.
         MOZ_ASSUME_UNREACHABLE("unexpected type");
     }
 }
 
 bool
 LIRGenerator::visitToString(MToString *ins)
 {
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -1318,20 +1318,23 @@ MBinaryBitwiseInstruction::foldUnnecessa
         return foldIfEqual();
 
     return this;
 }
 
 void
 MBinaryBitwiseInstruction::infer(BaselineInspector *, jsbytecode *)
 {
-    if (getOperand(0)->mightBeType(MIRType_Object) || getOperand(1)->mightBeType(MIRType_Object))
+    if (getOperand(0)->mightBeType(MIRType_Object) || getOperand(0)->mightBeType(MIRType_Symbol) ||
+        getOperand(1)->mightBeType(MIRType_Object) || getOperand(1)->mightBeType(MIRType_Symbol))
+    {
         specialization_ = MIRType_None;
-    else
+    } else {
         specializeAsInt32();
+    }
 }
 
 void
 MBinaryBitwiseInstruction::specializeAsInt32()
 {
     specialization_ = MIRType_Int32;
     JS_ASSERT(type() == MIRType_Int32);
 
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -3404,17 +3404,18 @@ class MToDouble
 
     explicit MToDouble(MDefinition *def, ConversionKind conversion = NonStringPrimitives)
       : MUnaryInstruction(def), conversion_(conversion), implicitTruncate_(NoTruncate)
     {
         setResultType(MIRType_Double);
         setMovable();
 
         // An object might have "valueOf", which means it is effectful.
-        if (def->mightBeType(MIRType_Object))
+        // ToNumber(symbol) throws.
+        if (def->mightBeType(MIRType_Object) || def->mightBeType(MIRType_Symbol))
             setGuard();
     }
 
   public:
     INSTRUCTION_HEADER(ToDouble)
     static MToDouble *New(TempAllocator &alloc, MDefinition *def,
                           ConversionKind conversion = NonStringPrimitives)
     {
@@ -3479,17 +3480,18 @@ class MToFloat32
 
     MToFloat32(MDefinition *def, ConversionKind conversion)
       : MUnaryInstruction(def), conversion_(conversion)
     {
         setResultType(MIRType_Float32);
         setMovable();
 
         // An object might have "valueOf", which means it is effectful.
-        if (def->mightBeType(MIRType_Object))
+        // ToNumber(symbol) throws.
+        if (def->mightBeType(MIRType_Object) || def->mightBeType(MIRType_Symbol))
             setGuard();
     }
 
   public:
     INSTRUCTION_HEADER(ToFloat32)
     static MToFloat32 *New(TempAllocator &alloc, MDefinition *def,
                            ConversionKind conversion = NonStringPrimitives)
     {
@@ -3593,17 +3595,18 @@ class MToInt32
       : MUnaryInstruction(def),
         canBeNegativeZero_(true),
         conversion_(conversion)
     {
         setResultType(MIRType_Int32);
         setMovable();
 
         // An object might have "valueOf", which means it is effectful.
-        if (def->mightBeType(MIRType_Object))
+        // ToNumber(symbol) throws.
+        if (def->mightBeType(MIRType_Object) || def->mightBeType(MIRType_Symbol))
             setGuard();
     }
 
   public:
     INSTRUCTION_HEADER(ToInt32)
     static MToInt32 *New(TempAllocator &alloc, MDefinition *def,
                          MacroAssembler::IntConversionInputKind conversion =
                              MacroAssembler::IntConversion_Any)
@@ -3654,17 +3657,20 @@ class MTruncateToInt32 : public MUnaryIn
 {
     explicit MTruncateToInt32(MDefinition *def)
       : MUnaryInstruction(def)
     {
         setResultType(MIRType_Int32);
         setMovable();
 
         // An object might have "valueOf", which means it is effectful.
-        if (def->mightBeType(MIRType_Object))
+        // ToInt32(symbol) throws.
+        MOZ_ASSERT(def->type() != MIRType_Object);
+        MOZ_ASSERT(def->type() != MIRType_Symbol);
+        if (def->mightBeType(MIRType_Object) || def->mightBeType(MIRType_Symbol))
             setGuard();
     }
 
   public:
     INSTRUCTION_HEADER(TruncateToInt32)
     static MTruncateToInt32 *New(TempAllocator &alloc, MDefinition *def) {
         return new(alloc) MTruncateToInt32(def);
     }
--- a/js/src/jit/TypePolicy.cpp
+++ b/js/src/jit/TypePolicy.cpp
@@ -71,21 +71,20 @@ ArithPolicy::adjustInputs(TempAllocator 
 
     for (size_t i = 0, e = ins->numOperands(); i < e; i++) {
         MDefinition *in = ins->getOperand(i);
         if (in->type() == ins->type())
             continue;
 
         MInstruction *replace;
 
-        // If the input is a string, symbol, or object, the conversion is not
+        // If the input is a string or object, the conversion is not
         // possible--at least, we can't specialize. So box the input.
         if (in->type() == MIRType_Object ||
             in->type() == MIRType_String ||
-            in->type() == MIRType_Symbol ||
             (in->type() == MIRType_Undefined && specialization_ == MIRType_Int32))
         {
             in = boxAt(alloc, ins, in);
         }
 
         if (ins->type() == MIRType_Double)
             replace = MToDouble::New(alloc, in);
         else if (ins->type() == MIRType_Float32)
@@ -190,17 +189,17 @@ ComparePolicy::adjustInputs(TempAllocato
               type == MIRType_Object || type == MIRType_String || type == MIRType_Float32);
     for (size_t i = 0; i < 2; i++) {
         MDefinition *in = def->getOperand(i);
         if (in->type() == type)
             continue;
 
         MInstruction *replace;
 
-        // See BinaryArithPolicy::adjustInputs for an explanation of the following
+        // See ArithPolicy::adjustInputs for an explanation of the following.
         if (in->type() == MIRType_Object || in->type() == MIRType_String ||
             in->type() == MIRType_Undefined)
         {
             in = boxAt(alloc, def, in);
         }
 
         switch (type) {
           case MIRType_Double: {
@@ -358,19 +357,23 @@ BitwisePolicy::adjustInputs(TempAllocato
     JS_ASSERT(specialization_ == MIRType_Int32 || specialization_ == MIRType_Double);
 
     // This policy works for both unary and binary bitwise operations.
     for (size_t i = 0, e = ins->numOperands(); i < e; i++) {
         MDefinition *in = ins->getOperand(i);
         if (in->type() == MIRType_Int32)
             continue;
 
-        // See BinaryArithPolicy::adjustInputs for an explanation of the following
-        if (in->type() == MIRType_Object || in->type() == MIRType_String)
+        // See ArithPolicy::adjustInputs for an explanation of the following.
+        // MTruncateToInt32 in particular does not support MIRType_Symbol input.
+        if (in->type() == MIRType_Object || in->type() == MIRType_String ||
+            in->type() == MIRType_Symbol)
+        {
             in = boxAt(alloc, ins, in);
+        }
 
         MInstruction *replace = MTruncateToInt32::New(alloc, in);
         ins->block()->insertBefore(ins, replace);
         ins->replaceOperand(i, replace);
     }
 
     return true;
 }
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -234,17 +234,17 @@ MSG_DEF(JSMSG_ESCAPE_AT_END_OF_REGEXP, 1
 MSG_DEF(JSMSG_NUMBERS_OUT_OF_ORDER,   181, 0, JSEXN_SYNTAXERR, "numbers out of order in {} quantifier.")
 MSG_DEF(JSMSG_BAD_GENERATOR_SEND,     182, 1, JSEXN_TYPEERR, "attempt to send {0} to newborn generator")
 MSG_DEF(JSMSG_SC_NOT_TRANSFERABLE,    183, 0, JSEXN_TYPEERR, "invalid transferable array for structured clone")
 MSG_DEF(JSMSG_SC_DUP_TRANSFERABLE,    184, 0, JSEXN_TYPEERR, "duplicate transferable for structured clone")
 MSG_DEF(JSMSG_CANT_REPORT_AS_NON_EXTENSIBLE, 185, 0, JSEXN_TYPEERR, "proxy can't report an extensible object as non-extensible")
 MSG_DEF(JSMSG_SYMBOL_TO_STRING,       186, 0, JSEXN_TYPEERR, "can't convert symbol to string")
 MSG_DEF(JSMSG_OBJECT_METADATA_CALLBACK_ALREADY_SET, 187, 0, JSEXN_ERR, "Cannot track object allocation, because other tools are already doing so")
 MSG_DEF(JSMSG_INCOMPATIBLE_METHOD,    188, 3, JSEXN_TYPEERR, "{0} {1} called on incompatible {2}")
-MSG_DEF(JSMSG_SYMBOL_TO_PRIMITIVE,    189, 0, JSEXN_TYPEERR, "can't convert symbol object to primitive")
+MSG_DEF(JSMSG_SYMBOL_TO_NUMBER,       189, 0, JSEXN_TYPEERR, "can't convert symbol to number")
 MSG_DEF(JSMSG_NOT_TRACKING_ALLOCATIONS, 190, 1, JSEXN_ERR, "Cannot call {0} without setting trackingAllocationSites to true")
 MSG_DEF(JSMSG_BAD_INDEX,              191, 0, JSEXN_RANGEERR, "invalid or out-of-range index")
 MSG_DEF(JSMSG_SELFHOSTED_TOP_LEVEL_LET,192,0, JSEXN_SYNTAXERR, "self-hosted code cannot contain top-level 'let' declarations")
 MSG_DEF(JSMSG_BAD_FOR_EACH_LOOP,      193, 0, JSEXN_SYNTAXERR, "invalid for each loop")
 MSG_DEF(JSMSG_OBJECT_WATCH_DEPRECATED,194, 0, JSEXN_NONE, "Object.prototype.watch and unwatch are very slow, non-standard, and deprecated; use a getter/setter instead")
 MSG_DEF(JSMSG_TYPE_ERR_BAD_ARGS,      195, 0, JSEXN_TYPEERR, "invalid arguments")
 MSG_DEF(JSMSG_REDECLARED_CATCH_IDENTIFIER, 196, 1, JSEXN_TYPEERR, "redeclaration of identifier '{0}' in catch")
 MSG_DEF(JSMSG_INTERNAL_INTL_ERROR,    197, 0, JSEXN_ERR, "internal error while computing Intl data")
--- a/js/src/jsnum.cpp
+++ b/js/src/jsnum.cpp
@@ -1553,18 +1553,22 @@ js::NonObjectToNumberSlow(ThreadSafeCont
     if (v.isBoolean()) {
         *out = v.toBoolean() ? 1.0 : 0.0;
         return true;
     }
     if (v.isNull()) {
         *out = 0.0;
         return true;
     }
+    if (v.isSymbol()) {
+        JS_ReportErrorNumber(cx->asJSContext(), js_GetErrorMessage, nullptr, JSMSG_SYMBOL_TO_NUMBER);
+        return false;
+    }
 
-    JS_ASSERT(v.isUndefined() || v.isSymbol());
+    JS_ASSERT(v.isUndefined());
     *out = GenericNaN();
     return true;
 }
 
 #if defined(_MSC_VER)
 # pragma optimize("g", off)
 #endif
 
--- a/js/src/tests/ecma_6/Symbol/comparisons.js
+++ b/js/src/tests/ecma_6/Symbol/comparisons.js
@@ -8,19 +8,27 @@ var symbols = [
     Symbol.iterator
 ];
 
 var values = [
     undefined, null, 0, 3.14, -0, NaN, "", "alphabet", Symbol("0"),
     {}, []
 ];
 
-for (var comparator of ["==", "!=", "===", "!==", "<", "<=", ">", ">="]) {
+for (var comparator of ["==", "!=", "===", "!=="]) {
     var f = Function("a, b", "return a " + comparator + " b;");
     var expected = (comparator[0] == '!');
     for (var a of symbols) {
         for (var b of values)
             assertEq(f(a, b), expected);
     }
 }
 
+for (var comparator of ["<", "<=", ">", ">="]) {
+    var f = Function("a, b", "return a " + comparator + " b;");
+    for (var a of symbols) {
+        for (var b of values)
+            assertThrowsInstanceOf(() => f(a, b), TypeError);
+    }
+}
+
 if (typeof reportCompare === "function")
     reportCompare(0, 0);
--- a/js/src/tests/ecma_6/Symbol/conversions.js
+++ b/js/src/tests/ecma_6/Symbol/conversions.js
@@ -16,30 +16,32 @@ if (Symbol.toPrimitive in Symbol.prototy
 
 for (var sym of symbols) {
     // 7.1.1 ToPrimitive
     var symobj = Object(sym);
     assertThrowsInstanceOf(() => Number(symobj), TypeError);
     assertThrowsInstanceOf(() => String(symobj), TypeError);
     assertThrowsInstanceOf(() => symobj < 0, TypeError);
     assertThrowsInstanceOf(() => 0 < symobj, TypeError);
-    assertThrowsInstanceOf(() => symobj == 0, TypeError);
-    assertThrowsInstanceOf(() => 0 != symobj, TypeError);
     assertThrowsInstanceOf(() => symobj + 1, TypeError);
     assertThrowsInstanceOf(() => "" + symobj, TypeError);
+    assertEq(sym == symobj, true);
+    assertEq(sym === symobj, false);
+    assertEq(symobj == 0, false);
+    assertEq(0 != symobj, true);
 
     // 7.1.2 ToBoolean
     assertEq(Boolean(sym), true);
     assertEq(!sym, false);
     assertEq(sym || 13, sym);
     assertEq(sym && 13, 13);
 
     // 7.1.3 ToNumber
-    assertEq(+sym, NaN);
-    assertEq(sym | 0, 0);
+    assertThrowsInstanceOf(() => +sym, TypeError);
+    assertThrowsInstanceOf(() => sym | 0, TypeError);
 
     // 7.1.12 ToString
     assertThrowsInstanceOf(() => String(sym), TypeError);
     assertThrowsInstanceOf(() => "" + sym, TypeError);
     assertThrowsInstanceOf(() => sym + "", TypeError);
     assertThrowsInstanceOf(() => "" + [1, 2, Symbol()], TypeError);
     assertThrowsInstanceOf(() => ["simple", "thimble", Symbol()].join(), TypeError);
 
--- a/js/src/tests/ecma_6/Symbol/typed-arrays.js
+++ b/js/src/tests/ecma_6/Symbol/typed-arrays.js
@@ -1,26 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/licenses/publicdomain/ */
 
 // Symbol-to-number type conversions involving typed arrays.
 
-var tests = [
-    {T: Uint8Array, result: 0},
-    {T: Uint8ClampedArray, result: 0},
-    {T: Int16Array, result: 0},
-    {T: Float32Array, result: NaN}
-];
+for (var T of [Uint8Array, Uint8ClampedArray, Int16Array, Float32Array]) {
+    // Typed array constructors convert symbols using ToNumber(), which throws.
+    assertThrowsInstanceOf(() => new T([Symbol("a")]), TypeError);
 
-for (var {T, result} of tests) {
-    // Typed array constructors convert symbols to NaN or 0.
-    var arr = new T([Symbol("a")]);
-    assertEq(arr.length, 1);
-    assertEq(arr[0], result);
-
-    // Assignment also converts symbols to NaN or 0.
-    arr[0] = 0;
-    assertEq(arr[0] = Symbol.iterator, Symbol.iterator);
-    assertEq(arr[0], result);
+    // Assignment does the same.
+    var arr = new T([1]);
+    assertThrowsInstanceOf(() => { arr[0] = Symbol.iterator; }, TypeError);
+    assertEq(arr[0], 1);
 }
 
 if (typeof reportCompare === "function")
     reportCompare(0, 0);
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -710,20 +710,22 @@ js::LooselyEqual(JSContext *cx, const Va
     RootedValue lvalue(cx, lval);
     RootedValue rvalue(cx, rval);
 
     if (!ToPrimitive(cx, &lvalue))
         return false;
     if (!ToPrimitive(cx, &rvalue))
         return false;
 
-    if (lvalue.get().isString() && rvalue.get().isString()) {
-        JSString *l = lvalue.get().toString();
-        JSString *r = rvalue.get().toString();
-        return EqualStrings(cx, l, r, result);
+    if (SameType(lvalue, rvalue))
+        return EqualGivenSameType(cx, lvalue, rvalue, result);
+
+    if (lvalue.isSymbol() || rvalue.isSymbol()) {
+        *result = false;
+        return true;
     }
 
     double l, r;
     if (!ToNumber(cx, lvalue, &l) || !ToNumber(cx, rvalue, &r))
         return false;
     *result = (l == r);
     return true;
 }
--- a/js/src/vm/TypedArrayObject.cpp
+++ b/js/src/vm/TypedArrayObject.cpp
@@ -878,47 +878,47 @@ class TypedArrayObjectTemplate : public 
         if (TypeIsUnsigned<NativeType>())
             return NativeType(ToUint32(d));
         return NativeType(ToInt32(d));
     }
 
     static bool
     canConvertInfallibly(const Value &v)
     {
-        return v.isNumber() || v.isBoolean() || v.isNull() || v.isUndefined() || v.isSymbol();
+        return v.isNumber() || v.isBoolean() || v.isNull() || v.isUndefined();
     }
 
     static NativeType
     infallibleValueToNative(const Value &v)
     {
         if (v.isInt32())
             return NativeType(v.toInt32());
         if (v.isDouble())
             return doubleToNative(v.toDouble());
         if (v.isBoolean())
             return NativeType(v.toBoolean());
         if (v.isNull())
             return NativeType(0);
 
-        MOZ_ASSERT(v.isUndefined() || v.isSymbol());
+        MOZ_ASSERT(v.isUndefined());
         return ArrayTypeIsFloatingPoint() ? NativeType(GenericNaN()) : NativeType(0);
     }
 
     static bool
     valueToNative(JSContext *cx, const Value &v, NativeType *result)
     {
         MOZ_ASSERT(!v.isMagic());
 
         if (MOZ_LIKELY(canConvertInfallibly(v))) {
             *result = infallibleValueToNative(v);
             return true;
         }
 
         double d;
-        MOZ_ASSERT(v.isString() || v.isObject());
+        MOZ_ASSERT(v.isString() || v.isObject() || v.isSymbol());
         if (!(v.isString() ? StringToNumber(cx, v.toString(), &d) : ToNumber(cx, v, &d)))
             return false;
 
         *result = doubleToNative(d);
         return true;
     }
 
     static bool