Bug 645416, part 3 - Symbol layout and GC support for allocating them. r=terrence.
authorJason Orendorff <jorendorff@mozilla.com>
Mon, 23 Jun 2014 10:55:51 -0500
changeset 190270 537d97cbf684826ee4153e15d2287fa31fd6e761
parent 190269 192a1527e6f1dc845a542fa9f7ee8e4e5ded0e14
child 190271 df1552da0b8f6209de02980dafa715740ad4d07c
push id27004
push useremorley@mozilla.com
push dateTue, 24 Jun 2014 15:52:34 +0000
treeherdermozilla-central@7b174d47f3cc [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersterrence
bugs645416
milestone33.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 645416, part 3 - Symbol layout and GC support for allocating them. r=terrence. Layout: js/src/vm/Symbol.h defines the new class JS::Symbol. JS::Symbol is the same size as JSString on all platforms, because the allocator does not support smaller allocations. Allocation: Since the purpose of symbols is to serve as property keys, they are always allocated in the atoms compartment. We take a lock when allocating. This could probably be replaced with a main-thread-only assertion. However, if atom allocation is not already a bottleneck, symbol allocation probably never will be. Symbols are given their own finalize-class in the GC. This means we allocate a page per zone for symbols, even though they are only ever allocated in the atoms zone. Terrence thought this could be easily fixed later. It should be; we never touch the page, but a 32-bit virtual address space does not just have infinite pages to spare. A jsapi-test exercises the new symbol allocation code. A few oddities in jsapi-tests are fixed in passing. Discussion after review led to some new assertions about minimum object size in AllocateObject and AllocateNonObject.
js/public/HeapAPI.h
js/public/MemoryMetrics.h
js/public/TypeDecls.h
js/public/Value.h
js/src/NamespaceImports.h
js/src/builtin/TestingFunctions.cpp
js/src/gc/Barrier.cpp
js/src/gc/Barrier.h
js/src/gc/Heap.h
js/src/gc/Marking.cpp
js/src/gc/Marking.h
js/src/gc/RootMarking.cpp
js/src/gc/Tracer.cpp
js/src/jsapi-tests/README
js/src/jsapi-tests/moz.build
js/src/jsapi-tests/testResolveRecursion.cpp
js/src/jsapi-tests/testSymbol.cpp
js/src/jsapi-tests/testWeakMap.cpp
js/src/jsapi-tests/tests.h
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jsatom.cpp
js/src/jscntxtinlines.h
js/src/jscompartmentinlines.h
js/src/jsfriendapi.cpp
js/src/jsgc.cpp
js/src/jsgc.h
js/src/jsgcinlines.h
js/src/jspubtd.h
js/src/moz.build
js/src/vm/CommonPropertyNames.h
js/src/vm/Interpreter.cpp
js/src/vm/MemoryMetrics.cpp
js/src/vm/Symbol-inl.h
js/src/vm/Symbol.cpp
js/src/vm/Symbol.h
js/xpconnect/src/XPCJSRuntime.cpp
xpcom/base/CycleCollectedJSRuntime.cpp
--- a/js/public/HeapAPI.h
+++ b/js/public/HeapAPI.h
@@ -120,16 +120,24 @@ static MOZ_ALWAYS_INLINE js::gc::Cell *
 AsCell(JSFlatString *flat)
 {
     js::gc::Cell *cell = reinterpret_cast<js::gc::Cell *>(flat);
     AssertGCThingHasType(cell, JSTRACE_STRING);
     return cell;
 }
 
 static MOZ_ALWAYS_INLINE js::gc::Cell *
+AsCell(JS::Symbol *sym)
+{
+    js::gc::Cell *cell = reinterpret_cast<js::gc::Cell *>(sym);
+    AssertGCThingHasType(cell, JSTRACE_SYMBOL);
+    return cell;
+}
+
+static MOZ_ALWAYS_INLINE js::gc::Cell *
 AsCell(JSScript *script)
 {
     js::gc::Cell *cell = reinterpret_cast<js::gc::Cell *>(script);
     AssertGCThingHasType(cell, JSTRACE_SCRIPT);
     return cell;
 }
 
 namespace shadow {
--- a/js/public/MemoryMetrics.h
+++ b/js/public/MemoryMetrics.h
@@ -395,16 +395,17 @@ struct RuntimeSizes
     js::Vector<NotableScriptSourceInfo, 0, js::SystemAllocPolicy> notableScriptSources;
 
 #undef FOR_EACH_SIZE
 };
 
 struct ZoneStats
 {
 #define FOR_EACH_SIZE(macro) \
+    macro(Other,   IsLiveGCThing,  symbolsGCHeap) \
     macro(Other,   NotLiveGCThing, gcHeapArenaAdmin) \
     macro(Other,   NotLiveGCThing, unusedGCThings) \
     macro(Other,   IsLiveGCThing,  lazyScriptsGCHeap) \
     macro(Other,   NotLiveGCThing, lazyScriptsMallocHeap) \
     macro(Other,   IsLiveGCThing,  jitCodesGCHeap) \
     macro(Other,   IsLiveGCThing,  typeObjectsGCHeap) \
     macro(Other,   NotLiveGCThing, typeObjectsMallocHeap) \
     macro(Other,   NotLiveGCThing, typePool) \
--- a/js/public/TypeDecls.h
+++ b/js/public/TypeDecls.h
@@ -44,34 +44,38 @@ template <typename T> class MutableHandl
 template <typename T> class Rooted;
 template <typename T> class PersistentRooted;
 
 typedef Handle<JSFunction*> HandleFunction;
 typedef Handle<jsid>        HandleId;
 typedef Handle<JSObject*>   HandleObject;
 typedef Handle<JSScript*>   HandleScript;
 typedef Handle<JSString*>   HandleString;
+typedef Handle<JS::Symbol*> HandleSymbol;
 typedef Handle<Value>       HandleValue;
 
 typedef MutableHandle<JSFunction*> MutableHandleFunction;
 typedef MutableHandle<jsid>        MutableHandleId;
 typedef MutableHandle<JSObject*>   MutableHandleObject;
 typedef MutableHandle<JSScript*>   MutableHandleScript;
 typedef MutableHandle<JSString*>   MutableHandleString;
+typedef MutableHandle<JS::Symbol*> MutableHandleSymbol;
 typedef MutableHandle<Value>       MutableHandleValue;
 
 typedef Rooted<JSObject*>       RootedObject;
 typedef Rooted<JSFunction*>     RootedFunction;
 typedef Rooted<JSScript*>       RootedScript;
 typedef Rooted<JSString*>       RootedString;
+typedef Rooted<JS::Symbol*>     RootedSymbol;
 typedef Rooted<jsid>            RootedId;
 typedef Rooted<JS::Value>       RootedValue;
 
 typedef PersistentRooted<JSFunction*> PersistentRootedFunction;
 typedef PersistentRooted<jsid>        PersistentRootedId;
 typedef PersistentRooted<JSObject*>   PersistentRootedObject;
 typedef PersistentRooted<JSScript*>   PersistentRootedScript;
 typedef PersistentRooted<JSString*>   PersistentRootedString;
+typedef PersistentRooted<JS::Symbol*> PersistentRootedSymbol;
 typedef PersistentRooted<Value>       PersistentRootedValue;
 
 } // namespace JS
 
 #endif /* js_TypeDecls_h */
--- a/js/public/Value.h
+++ b/js/public/Value.h
@@ -594,17 +594,23 @@ static inline js::gc::Cell *
 JSVAL_TO_GCTHING_IMPL(jsval_layout l)
 {
     return l.s.payload.cell;
 }
 
 static inline uint32_t
 JSVAL_TRACE_KIND_IMPL(jsval_layout l)
 {
-    return (uint32_t)(bool)JSVAL_IS_STRING_IMPL(l);
+    static_assert((JSVAL_TAG_STRING & 0x03) == JSTRACE_STRING,
+                  "Value type tags must correspond with JSGCTraceKinds.");
+    static_assert((JSVAL_TAG_SYMBOL & 0x03) == JSTRACE_SYMBOL,
+                  "Value type tags must correspond with JSGCTraceKinds.");
+    static_assert((JSVAL_TAG_OBJECT & 0x03) == JSTRACE_OBJECT,
+                  "Value type tags must correspond with JSGCTraceKinds.");
+    return l.s.tag & 0x03;
 }
 
 static inline bool
 JSVAL_IS_SPECIFIC_INT32_IMPL(jsval_layout l, int32_t i32)
 {
     return l.s.tag == JSVAL_TAG_INT32 && l.s.payload.i32 == i32;
 }
 
@@ -829,17 +835,23 @@ JSVAL_TO_GCTHING_IMPL(jsval_layout l)
     uint64_t ptrBits = l.asBits & JSVAL_PAYLOAD_MASK;
     MOZ_ASSERT((ptrBits & 0x7) == 0);
     return reinterpret_cast<js::gc::Cell *>(ptrBits);
 }
 
 static inline uint32_t
 JSVAL_TRACE_KIND_IMPL(jsval_layout l)
 {
-    return (uint32_t)(bool)!(JSVAL_IS_OBJECT_IMPL(l));
+    static_assert((JSVAL_TAG_STRING & 0x03) == JSTRACE_STRING,
+                  "Value type tags must correspond with JSGCTraceKinds.");
+    static_assert((JSVAL_TAG_SYMBOL & 0x03) == JSTRACE_SYMBOL,
+                  "Value type tags must correspond with JSGCTraceKinds.");
+    static_assert((JSVAL_TAG_OBJECT & 0x03) == JSTRACE_OBJECT,
+                  "Value type tags must correspond with JSGCTraceKinds.");
+    return (uint32_t)(l.asBits >> JSVAL_TAG_SHIFT) & 0x03;
 }
 
 static inline jsval_layout
 PRIVATE_PTR_TO_JSVAL_IMPL(void *ptr)
 {
     jsval_layout l;
     uint64_t ptrBits = (uint64_t)ptr;
     MOZ_ASSERT((ptrBits & 1) == 0);
--- a/js/src/NamespaceImports.h
+++ b/js/src/NamespaceImports.h
@@ -95,40 +95,44 @@ using JS::ReadOnlyCompileOptions;
 using JS::SourceBufferHolder;
 
 using JS::Rooted;
 using JS::RootedFunction;
 using JS::RootedId;
 using JS::RootedObject;
 using JS::RootedScript;
 using JS::RootedString;
+using JS::RootedSymbol;
 using JS::RootedValue;
 
 using JS::PersistentRooted;
 using JS::PersistentRootedFunction;
 using JS::PersistentRootedId;
 using JS::PersistentRootedObject;
 using JS::PersistentRootedScript;
 using JS::PersistentRootedString;
+using JS::PersistentRootedSymbol;
 using JS::PersistentRootedValue;
 
 using JS::Handle;
 using JS::HandleFunction;
 using JS::HandleId;
 using JS::HandleObject;
 using JS::HandleScript;
 using JS::HandleString;
+using JS::HandleSymbol;
 using JS::HandleValue;
 
 using JS::MutableHandle;
 using JS::MutableHandleFunction;
 using JS::MutableHandleId;
 using JS::MutableHandleObject;
 using JS::MutableHandleScript;
 using JS::MutableHandleString;
+using JS::MutableHandleSymbol;
 using JS::MutableHandleValue;
 
 using JS::NullHandleValue;
 using JS::UndefinedHandleValue;
 
 using JS::HandleValueArray;
 
 using JS::Zone;
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -759,16 +759,17 @@ CountHeapNotify(JSTracer *trc, void **th
 
 static const struct TraceKindPair {
     const char       *name;
     int32_t           kind;
 } traceKindNames[] = {
     { "all",        -1                  },
     { "object",     JSTRACE_OBJECT      },
     { "string",     JSTRACE_STRING      },
+    { "symbol",     JSTRACE_SYMBOL      },
 };
 
 static bool
 CountHeap(JSContext *cx, unsigned argc, jsval *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     RootedValue startValue(cx, UndefinedValue());
--- a/js/src/gc/Barrier.cpp
+++ b/js/src/gc/Barrier.cpp
@@ -6,26 +6,30 @@
 
 #include "gc/Barrier.h"
 
 #include "jscompartment.h"
 #include "jsobj.h"
 
 #include "gc/Zone.h"
 
+#include "vm/Symbol.h"
+
 namespace js {
 
 void
 ValueReadBarrier(const Value &value)
 {
     JS_ASSERT(!CurrentThreadIsIonCompiling());
     if (value.isObject())
         JSObject::readBarrier(&value.toObject());
     else if (value.isString())
         JSString::readBarrier(value.toString());
+    else if (value.isSymbol())
+        JS::Symbol::readBarrier(value.toSymbol());
     else
         JS_ASSERT(!value.isMarkable());
 }
 
 #ifdef DEBUG
 bool
 HeapSlot::preconditionForSet(JSObject *owner, Kind kind, uint32_t slot)
 {
--- a/js/src/gc/Barrier.h
+++ b/js/src/gc/Barrier.h
@@ -267,16 +267,23 @@ ShadowZoneOfObjectFromAnyThread(JSObject
 
 static inline JS::shadow::Zone *
 ShadowZoneOfStringFromAnyThread(JSString *str)
 {
     return JS::shadow::Zone::asShadowZone(
         reinterpret_cast<const js::gc::Cell *>(str)->tenuredZoneFromAnyThread());
 }
 
+static inline JS::shadow::Zone *
+ShadowZoneOfSymbolFromAnyThread(JS::Symbol *sym)
+{
+    return JS::shadow::Zone::asShadowZone(
+        reinterpret_cast<const js::gc::Cell *>(sym)->tenuredZoneFromAnyThread());
+}
+
 MOZ_ALWAYS_INLINE JS::Zone *
 ZoneOfValueFromAnyThread(const JS::Value &value)
 {
     JS_ASSERT(value.isMarkable());
     if (value.isObject())
         return ZoneOfObjectFromAnyThread(value.toObject());
     return static_cast<js::gc::Cell *>(value.toGCThing())->tenuredZoneFromAnyThread();
 }
--- a/js/src/gc/Heap.h
+++ b/js/src/gc/Heap.h
@@ -70,16 +70,17 @@ enum AllocKind {
     FINALIZE_SCRIPT,
     FINALIZE_LAZY_SCRIPT,
     FINALIZE_SHAPE,
     FINALIZE_BASE_SHAPE,
     FINALIZE_TYPE_OBJECT,
     FINALIZE_FAT_INLINE_STRING,
     FINALIZE_STRING,
     FINALIZE_EXTERNAL_STRING,
+    FINALIZE_SYMBOL,
     FINALIZE_JITCODE,
     FINALIZE_LAST = FINALIZE_JITCODE
 };
 
 static const unsigned FINALIZE_LIMIT = FINALIZE_LAST + 1;
 static const unsigned FINALIZE_OBJECT_LIMIT = FINALIZE_OBJECT_LAST + 1;
 
 /*
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -10,26 +10,28 @@
 
 #include "jsprf.h"
 
 #include "jit/IonCode.h"
 #include "js/SliceBudget.h"
 #include "vm/ArgumentsObject.h"
 #include "vm/ScopeObject.h"
 #include "vm/Shape.h"
+#include "vm/Symbol.h"
 #include "vm/TypedArrayObject.h"
 
 #include "jscompartmentinlines.h"
 #include "jsinferinlines.h"
 #include "jsobjinlines.h"
 
 #ifdef JSGC_GENERATIONAL
 # include "gc/Nursery-inl.h"
 #endif
 #include "vm/String-inl.h"
+#include "vm/Symbol-inl.h"
 
 using namespace js;
 using namespace js::gc;
 
 using mozilla::DebugOnly;
 
 void * const js::NullPtr::constNullValue = nullptr;
 
@@ -73,25 +75,29 @@ PushMarkStack(GCMarker *gcmarker, JSFunc
 
 static inline void
 PushMarkStack(GCMarker *gcmarker, JSScript *thing);
 
 static inline void
 PushMarkStack(GCMarker *gcmarker, Shape *thing);
 
 static inline void
-PushMarkStack(GCMarker *gcmarker, JSString *thing);
+PushMarkStack(GCMarker *gcmarker, JSString *str);
+
+static inline void
+PushMarkStack(GCMarker *gcmarker, JS::Symbol *sym);
 
 static inline void
 PushMarkStack(GCMarker *gcmarker, types::TypeObject *thing);
 
 namespace js {
 namespace gc {
 
 static void MarkChildren(JSTracer *trc, JSString *str);
+static void MarkChildren(JSTracer *trc, JS::Symbol *sym);
 static void MarkChildren(JSTracer *trc, JSScript *script);
 static void MarkChildren(JSTracer *trc, LazyScript *lazy);
 static void MarkChildren(JSTracer *trc, Shape *shape);
 static void MarkChildren(JSTracer *trc, BaseShape *base);
 static void MarkChildren(JSTracer *trc, types::TypeObject *type);
 static void MarkChildren(JSTracer *trc, jit::JitCode *code);
 
 } /* namespace gc */
@@ -564,16 +570,17 @@ DeclMarkerImpl(Object, ScopeObject)
 DeclMarkerImpl(Script, JSScript)
 DeclMarkerImpl(LazyScript, LazyScript)
 DeclMarkerImpl(Shape, Shape)
 DeclMarkerImpl(String, JSAtom)
 DeclMarkerImpl(String, JSString)
 DeclMarkerImpl(String, JSFlatString)
 DeclMarkerImpl(String, JSLinearString)
 DeclMarkerImpl(String, PropertyName)
+DeclMarkerImpl(Symbol, JS::Symbol)
 DeclMarkerImpl(TypeObject, js::types::TypeObject)
 
 } /* namespace gc */
 } /* namespace js */
 
 /*** Externally Typed Marking ***/
 
 void
@@ -585,16 +592,19 @@ gc::MarkKind(JSTracer *trc, void **thing
     JS_ASSERT_IF(cell->isTenured(), kind == MapAllocToTraceKind(cell->tenuredGetAllocKind()));
     switch (kind) {
       case JSTRACE_OBJECT:
         MarkInternal(trc, reinterpret_cast<JSObject **>(thingp));
         break;
       case JSTRACE_STRING:
         MarkInternal(trc, reinterpret_cast<JSString **>(thingp));
         break;
+      case JSTRACE_SYMBOL:
+        MarkInternal(trc, reinterpret_cast<JS::Symbol **>(thingp));
+        break;
       case JSTRACE_SCRIPT:
         MarkInternal(trc, reinterpret_cast<JSScript **>(thingp));
         break;
       case JSTRACE_LAZY_SCRIPT:
         MarkInternal(trc, reinterpret_cast<LazyScript **>(thingp));
         break;
       case JSTRACE_SHAPE:
         MarkInternal(trc, reinterpret_cast<Shape **>(thingp));
@@ -703,16 +713,18 @@ MarkValueInternal(JSTracer *trc, Value *
 {
     if (v->isMarkable()) {
         JS_ASSERT(v->toGCThing());
         void *thing = v->toGCThing();
         trc->setTracingLocation((void *)v);
         MarkKind(trc, &thing, v->gcKind());
         if (v->isString())
             v->setString((JSString *)thing);
+        else if (v->isSymbol())
+            v->setSymbol((JS::Symbol *)thing);
         else
             v->setObjectOrNull((JSObject *)thing);
     } else {
         /* Unset realLocation manually if we do not call MarkInternal. */
         trc->unsetTracingLocation();
     }
 }
 
@@ -924,16 +936,20 @@ gc::IsCellAboutToBeFinalized(Cell **thin
 
 #define JS_COMPARTMENT_ASSERT(rt, thing)                                \
     JS_ASSERT((thing)->zone()->isGCMarking())
 
 #define JS_COMPARTMENT_ASSERT_STR(rt, thing)                            \
     JS_ASSERT((thing)->zone()->isGCMarking() ||                         \
               (rt)->isAtomsZone((thing)->zone()));
 
+// Symbols can also be in the atoms zone.
+#define JS_COMPARTMENT_ASSERT_SYM(rt, sym)                              \
+    JS_COMPARTMENT_ASSERT_STR(rt, sym)
+
 static void
 PushMarkStack(GCMarker *gcmarker, ObjectImpl *thing)
 {
     JS_COMPARTMENT_ASSERT(gcmarker->runtime(), thing);
     JS_ASSERT(!IsInsideNursery(thing));
 
     if (thing->markIfUnmarked(gcmarker->getMarkColor()))
         gcmarker->pushObject(thing);
@@ -1194,32 +1210,48 @@ PushMarkStack(GCMarker *gcmarker, JSStri
      * As string can only refer to other strings we fully scan its GC graph
      * using the explicit stack when navigating the rope tree to avoid
      * dealing with strings on the stack in drainMarkStack.
      */
     if (str->markIfUnmarked())
         ScanString(gcmarker, str);
 }
 
+static inline void
+PushMarkStack(GCMarker *gcmarker, JS::Symbol *sym)
+{
+    JS_COMPARTMENT_ASSERT_SYM(gcmarker->runtime(), sym);
+    if (sym->markIfUnmarked()) {
+        if (JSString *desc = sym->description())
+            ScanString(gcmarker, desc);
+    }
+}
+
 void
 gc::MarkChildren(JSTracer *trc, JSObject *obj)
 {
     obj->markChildren(trc);
 }
 
 static void
 gc::MarkChildren(JSTracer *trc, JSString *str)
 {
     if (str->hasBase())
         str->markBase(trc);
     else if (str->isRope())
         str->asRope().markChildren(trc);
 }
 
 static void
+gc::MarkChildren(JSTracer *trc, JS::Symbol *sym)
+{
+    sym->markChildren(trc);
+}
+
+static void
 gc::MarkChildren(JSTracer *trc, JSScript *script)
 {
     script->markChildren(trc);
 }
 
 static void
 gc::MarkChildren(JSTracer *trc, LazyScript *lazy)
 {
@@ -1370,16 +1402,20 @@ gc::PushArena(GCMarker *gcmarker, ArenaH
       case JSTRACE_OBJECT:
         PushArenaTyped<JSObject>(gcmarker, aheader);
         break;
 
       case JSTRACE_STRING:
         PushArenaTyped<JSString>(gcmarker, aheader);
         break;
 
+      case JSTRACE_SYMBOL:
+        PushArenaTyped<JS::Symbol>(gcmarker, aheader);
+        break;
+
       case JSTRACE_SCRIPT:
         PushArenaTyped<JSScript>(gcmarker, aheader);
         break;
 
       case JSTRACE_LAZY_SCRIPT:
         PushArenaTyped<LazyScript>(gcmarker, aheader);
         break;
 
@@ -1700,16 +1736,20 @@ js::TraceChildren(JSTracer *trc, void *t
       case JSTRACE_OBJECT:
         MarkChildren(trc, static_cast<JSObject *>(thing));
         break;
 
       case JSTRACE_STRING:
         MarkChildren(trc, static_cast<JSString *>(thing));
         break;
 
+      case JSTRACE_SYMBOL:
+        MarkChildren(trc, static_cast<JS::Symbol *>(thing));
+        break;
+
       case JSTRACE_SCRIPT:
         MarkChildren(trc, static_cast<JSScript *>(thing));
         break;
 
       case JSTRACE_LAZY_SCRIPT:
         MarkChildren(trc, static_cast<LazyScript *>(thing));
         break;
 
--- a/js/src/gc/Marking.h
+++ b/js/src/gc/Marking.h
@@ -115,16 +115,17 @@ DeclMarker(Object, ScopeObject)
 DeclMarker(Script, JSScript)
 DeclMarker(LazyScript, LazyScript)
 DeclMarker(Shape, Shape)
 DeclMarker(String, JSAtom)
 DeclMarker(String, JSString)
 DeclMarker(String, JSFlatString)
 DeclMarker(String, JSLinearString)
 DeclMarker(String, PropertyName)
+DeclMarker(Symbol, JS::Symbol)
 DeclMarker(TypeObject, types::TypeObject)
 
 #undef DeclMarker
 
 void
 MarkPermanentAtom(JSTracer *trc, JSAtom *atom, const char *name);
 
 /* Return true if the pointer is nullptr, or if it is a tagged pointer to
@@ -383,17 +384,20 @@ ToMarkable(Cell *cell)
 }
 
 inline JSGCTraceKind
 TraceKind(const Value &v)
 {
     JS_ASSERT(v.isMarkable());
     if (v.isObject())
         return JSTRACE_OBJECT;
-    return JSTRACE_STRING;
+    if (v.isString())
+        return JSTRACE_STRING;
+    JS_ASSERT(v.isSymbol());
+    return JSTRACE_SYMBOL;
 }
 
 inline JSGCTraceKind
 TraceKind(JSObject *obj)
 {
     return JSTRACE_OBJECT;
 }
 
--- a/js/src/gc/RootMarking.cpp
+++ b/js/src/gc/RootMarking.cpp
@@ -121,16 +121,17 @@ template <class T>
 static void
 MarkExactStackRoots(T context, JSTracer *trc)
 {
     MarkExactStackRootsForType<JSObject *, MarkObjectRoot>(context, trc, "exact-object");
     MarkExactStackRootsForType<Shape *, MarkShapeRoot>(context, trc, "exact-shape");
     MarkExactStackRootsForType<BaseShape *, MarkBaseShapeRoot>(context, trc, "exact-baseshape");
     MarkExactStackRootsForType<types::TypeObject *, MarkTypeObjectRoot>(context, trc, "exact-typeobject");
     MarkExactStackRootsForType<JSString *, MarkStringRoot>(context, trc, "exact-string");
+    MarkExactStackRootsForType<JS::Symbol *, MarkSymbolRoot>(context, trc, "exact-symbol");
     MarkExactStackRootsForType<jit::JitCode *, MarkJitCodeRoot>(context, trc, "exact-jitcode");
     MarkExactStackRootsForType<JSScript *, MarkScriptRoot>(context, trc, "exact-script");
     MarkExactStackRootsForType<LazyScript *, MarkLazyScriptRoot>(context, trc, "exact-lazy-script");
     MarkExactStackRootsForType<jsid, MarkIdRoot>(context, trc, "exact-id");
     MarkExactStackRootsForType<Value, MarkValueRoot>(context, trc, "exact-value");
     MarkExactStackRootsForType<types::Type, MarkTypeRoot>(context, trc, "exact-type");
     MarkExactStackRootsForType<Bindings, MarkBindingsRoot>(context, trc);
     MarkExactStackRootsForType<JSPropertyDescriptor, MarkPropertyDescriptorRoot>(context, trc);
--- a/js/src/gc/Tracer.cpp
+++ b/js/src/gc/Tracer.cpp
@@ -14,16 +14,18 @@
 #include "jsprf.h"
 #include "jsscript.h"
 #include "jsutil.h"
 #include "NamespaceImports.h"
 
 #include "gc/GCInternals.h"
 #include "gc/Marking.h"
 
+#include "vm/Symbol.h"
+
 #include "jsgcinlines.h"
 
 using namespace js;
 using namespace js::gc;
 using mozilla::DebugOnly;
 
 JS_PUBLIC_API(void)
 JS_CallValueTracer(JSTracer *trc, Value *valuep, const char *name)
@@ -147,16 +149,20 @@ JS_GetTraceThingInfo(char *buf, size_t b
       }
 
       case JSTRACE_STRING:
         name = ((JSString *)thing)->isDependent()
                ? "substring"
                : "string";
         break;
 
+      case JSTRACE_SYMBOL:
+        name = "symbol";
+        break;
+
       case JSTRACE_SCRIPT:
         name = "script";
         break;
 
       case JSTRACE_LAZY_SCRIPT:
         name = "lazyscript";
         break;
 
@@ -217,19 +223,36 @@ JS_GetTraceThingInfo(char *buf, size_t b
 
                 n = JS_snprintf(buf, bufsize, "<length %d%s> ",
                                 (int)str->length(),
                                 willFit ? "" : " (truncated)");
                 buf += n;
                 bufsize -= n;
 
                 PutEscapedString(buf, bufsize, &str->asLinear(), 0);
+            } else {
+                JS_snprintf(buf, bufsize, "<rope: length %d>", (int)str->length());
             }
-            else
-                JS_snprintf(buf, bufsize, "<rope: length %d>", (int)str->length());
+            break;
+          }
+
+          case JSTRACE_SYMBOL:
+          {
+            JS::Symbol *sym = static_cast<JS::Symbol *>(thing);
+            if (JSString *desc = sym->description()) {
+                if (desc->isLinear()) {
+                    *buf++ = ' ';
+                    bufsize--;
+                    PutEscapedString(buf, bufsize, &desc->asLinear(), 0);
+                } else {
+                    JS_snprintf(buf, bufsize, "<nonlinear desc>");
+                }
+            } else {
+                JS_snprintf(buf, bufsize, "<null>");
+            }
             break;
           }
 
           case JSTRACE_SCRIPT:
           {
             JSScript *script = static_cast<JSScript *>(thing);
             JS_snprintf(buf, bufsize, " %s:%u", script->filename(), unsigned(script->lineno()));
             break;
--- a/js/src/jsapi-tests/README
+++ b/js/src/jsapi-tests/README
@@ -84,17 +84,29 @@ tests.h:
         failing the test.
 
     CHECK_SAME(jsval a, jsval b);
 
         If a and b are different values, print an error message and return
         false, failing the test.
 
         This is like CHECK(sameValue(a, b)) but with a more detailed error
-        message. See sameValue below.
+        message on failure. See sameValue below.
+
+    CHECK_EQUAL(const T &a, const U &b);
+
+        CHECK(a == b), but with a more detailed error message.
+
+    CHECK_NULL(const T *ptr);
+
+        CHECK(ptr == nullptr), but with a more detailed error message.
+
+        (This is here because CHECK_EQUAL(ptr, nullptr) fails to compile on GCC
+        2.5 and before.)
+
 
     bool knownFail;
 
         Set this to true if your test is known to fail. The test runner will
         print a TEST-KNOWN-FAIL line rather than a TEST-UNEXPECTED-FAIL
         line. This way you can check in a test illustrating a bug ahead of the
         fix.
 
--- a/js/src/jsapi-tests/moz.build
+++ b/js/src/jsapi-tests/moz.build
@@ -63,16 +63,17 @@ UNIFIED_SOURCES += [
     'tests.cpp',
     'testSameValue.cpp',
     'testScriptInfo.cpp',
     'testScriptObject.cpp',
     'testSetProperty.cpp',
     'testSourcePolicy.cpp',
     'testStringBuffer.cpp',
     'testStructuredClone.cpp',
+    'testSymbol.cpp',
     'testToIntWidth.cpp',
     'testTrap.cpp',
     'testTypedArrays.cpp',
     'testUncaughtError.cpp',
     'testUTF8.cpp',
     'testWeakMap.cpp',
     'testXDR.cpp',
 ]
--- a/js/src/jsapi-tests/testResolveRecursion.cpp
+++ b/js/src/jsapi-tests/testResolveRecursion.cpp
@@ -78,17 +78,17 @@ struct AutoIncrCounters {
     cls_testResolveRecursion *t;
 };
 
 bool
 doResolve(JS::HandleObject obj, JS::HandleId id, JS::MutableHandleObject objp)
 {
     CHECK_EQUAL(resolveExitCount, 0);
     AutoIncrCounters incr(this);
-    CHECK_EQUAL(obj, obj1 || obj == obj2);
+    CHECK(obj == obj1 || obj == obj2);
 
     CHECK(JSID_IS_STRING(id));
 
     JSFlatString *str = JS_FlattenString(cx, JSID_TO_STRING(id));
     CHECK(str);
     JS::RootedValue v(cx);
     if (JS_FlatStringEqualsAscii(str, "x")) {
         if (obj == obj1) {
new file mode 100644
--- /dev/null
+++ b/js/src/jsapi-tests/testSymbol.cpp
@@ -0,0 +1,28 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "jsapi-tests/tests.h"
+
+BEGIN_TEST(testSymbol_New)
+{
+    using namespace JS;
+
+    RootedString desc(cx, nullptr);
+    RootedSymbol sym1(cx);
+    CHECK(sym1 = NewSymbol(cx, desc));
+    CHECK_NULL(GetSymbolDescription(sym1));
+    RootedValue v(cx, SymbolValue(sym1));
+    CHECK_EQUAL(JS_TypeOfValue(cx, v), JSTYPE_SYMBOL);
+
+    RootedSymbol sym2(cx);
+    CHECK(sym2 = NewSymbol(cx, desc));
+    CHECK(sym1 != sym2);
+
+    CHECK(desc = JS_NewStringCopyZ(cx, "ponies"));
+    CHECK(sym2 = NewSymbol(cx, desc));
+    CHECK_SAME(StringValue(GetSymbolDescription(sym2)), StringValue(desc));
+
+    return true;
+}
+END_TEST(testSymbol_New)
--- a/js/src/jsapi-tests/testWeakMap.cpp
+++ b/js/src/jsapi-tests/testWeakMap.cpp
@@ -177,17 +177,17 @@ JSObject *newCCW(HandleObject sourceZone
     /*
      * Now ensure that this zone will be swept first by adding a cross
      * compartment wrapper to a new objct in the same zone as the
      * delegate obejct.
      */
     RootedObject object(cx);
     {
         JSAutoCompartment ac(cx, destZone);
-        object = JS_NewObject(cx, nullptr, NullPtr(), NullPtr());
+        object = JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr());
         if (!object)
             return nullptr;
     }
     {
         JSAutoCompartment ac(cx, sourceZone);
         if (!JS_WrapObject(cx, &object))
             return nullptr;
     }
--- a/js/src/jsapi-tests/tests.h
+++ b/js/src/jsapi-tests/tests.h
@@ -150,44 +150,50 @@ class JSAPITest
         JS::RootedValue val(cx, JS::StringValue((JSString *)v));
         return jsvalToSource(val);
     }
 
     JSAPITestString toSource(JSVersion v) {
         return JSAPITestString(JS_VersionToString(v));
     }
 
-    template<typename T>
-    bool checkEqual(const T &actual, const T &expected,
+    // Note that in some still-supported GCC versions (we think anything before
+    // GCC 4.6), this template does not work when the second argument is
+    // nullptr. It infers type U = long int. Use CHECK_NULL instead.
+    template <typename T, typename U>
+    bool checkEqual(const T &actual, const U &expected,
                     const char *actualExpr, const char *expectedExpr,
                     const char *filename, int lineno) {
         return (actual == expected) ||
             fail(JSAPITestString("CHECK_EQUAL failed: expected (") +
                  expectedExpr + ") = " + toSource(expected) +
                  ", got (" + actualExpr + ") = " + toSource(actual), filename, lineno);
     }
 
-    // There are many cases where the static types of 'actual' and 'expected'
-    // are not identical, and C++ is understandably cautious about automatic
-    // coercions. So catch those cases and forcibly coerce, then use the
-    // identical-type specialization. This may do bad things if the types are
-    // actually *not* compatible.
-    template<typename T, typename U>
-    bool checkEqual(const T &actual, const U &expected,
-                   const char *actualExpr, const char *expectedExpr,
-                   const char *filename, int lineno) {
-        return checkEqual(U(actual), expected, actualExpr, expectedExpr, filename, lineno);
-    }
-
 #define CHECK_EQUAL(actual, expected) \
     do { \
         if (!checkEqual(actual, expected, #actual, #expected, __FILE__, __LINE__)) \
             return false; \
     } while (false)
 
+    template <typename T>
+    bool checkNull(const T *actual, const char *actualExpr,
+                   const char *filename, int lineno) {
+        return (actual == nullptr) ||
+            fail(JSAPITestString("CHECK_NULL failed: expected nullptr, got (") +
+                 actualExpr + ") = " + toSource(actual),
+                 filename, lineno);
+    }
+
+#define CHECK_NULL(actual) \
+    do { \
+        if (!checkNull(actual, #actual, __FILE__, __LINE__)) \
+            return false; \
+    } while (false)
+
     bool checkSame(jsval actualArg, jsval expectedArg,
                    const char *actualExpr, const char *expectedExpr,
                    const char *filename, int lineno) {
         bool same;
         JS::RootedValue actual(cx, actualArg), expected(cx, expectedArg);
         return (JS_SameValue(cx, actual, expected, &same) && same) ||
                fail(JSAPITestString("CHECK_SAME failed: expected JS_SameValue(cx, ") +
                     actualExpr + ", " + expectedExpr + "), got !JS_SameValue(cx, " +
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -71,16 +71,17 @@
 #include "vm/Interpreter.h"
 #include "vm/NumericConversions.h"
 #include "vm/RegExpStatics.h"
 #include "vm/Runtime.h"
 #include "vm/Shape.h"
 #include "vm/SharedArrayObject.h"
 #include "vm/StopIterationObject.h"
 #include "vm/StringBuffer.h"
+#include "vm/Symbol.h"
 #include "vm/TypedArrayObject.h"
 #include "vm/WeakMapObject.h"
 #include "vm/WrapperObject.h"
 #include "vm/Xdr.h"
 
 #include "jsatominlines.h"
 #include "jsfuninlines.h"
 #include "jsinferinlines.h"
@@ -5611,16 +5612,33 @@ JS_EncodeStringToBuffer(JSContext *cx, J
     JS_ASSERT(writtenLength <= length);
     size_t necessaryLength = str->length();
     if (necessaryLength == size_t(-1))
         return size_t(-1);
     JS_ASSERT(writtenLength == length); // C strings are NOT encoded.
     return necessaryLength;
 }
 
+JS_PUBLIC_API(JS::Symbol *)
+JS::NewSymbol(JSContext *cx, HandleString description)
+{
+    AssertHeapIsIdle(cx);
+    CHECK_REQUEST(cx);
+    if (description)
+        assertSameCompartment(cx, description);
+
+    return Symbol::new_(cx, description);
+}
+
+JS_PUBLIC_API(JSString *)
+JS::GetSymbolDescription(HandleSymbol symbol)
+{
+    return symbol->description();
+}
+
 JS_PUBLIC_API(bool)
 JS_Stringify(JSContext *cx, MutableHandleValue vp, HandleObject replacer,
              HandleValue space, JSONWriteCallback callback, void *data)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, replacer, space);
     StringBuffer sb(cx);
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -4348,16 +4348,44 @@ StringOfAddonId(JSAddonId *id);
 
 extern JS_PUBLIC_API(JSAddonId *)
 AddonIdOfObject(JSObject *obj);
 
 } // namespace JS
 
 /************************************************************************/
 /*
+ * Symbols
+ */
+
+namespace JS {
+
+/*
+ * Create a new Symbol with the given description. This function never returns
+ * a Symbol that is in the Runtime-wide symbol registry.
+ *
+ * If description is null, the new Symbol's [[Description]] attribute is
+ * undefined.
+ */
+JS_PUBLIC_API(Symbol *)
+NewSymbol(JSContext *cx, HandleString description);
+
+/*
+ * Get the [[Description]] attribute of the given symbol.
+ *
+ * This function is infallible. If it returns null, that means the symbol's
+ * [[Description]] is undefined.
+ */
+JS_PUBLIC_API(JSString *)
+GetSymbolDescription(HandleSymbol symbol);
+
+} /* namespace JS */
+
+/************************************************************************/
+/*
  * JSON functions
  */
 typedef bool (* JSONWriteCallback)(const jschar *buf, uint32_t len, void *data);
 
 /*
  * JSON.stringify as specified by ES5.
  */
 JS_PUBLIC_API(bool)
--- a/js/src/jsatom.cpp
+++ b/js/src/jsatom.cpp
@@ -22,16 +22,17 @@
 #include "gc/Marking.h"
 #include "vm/Xdr.h"
 
 #include "jscntxtinlines.h"
 #include "jscompartmentinlines.h"
 #include "jsobjinlines.h"
 
 #include "vm/String-inl.h"
+#include "vm/Symbol-inl.h"
 
 using namespace js;
 using namespace js::gc;
 
 using mozilla::ArrayEnd;
 using mozilla::ArrayLength;
 using mozilla::RangedPtr;
 
@@ -375,16 +376,19 @@ AtomizeAndCopyChars(ExclusiveContext *cx
         p->setTagged(bool(ib));
         return atom;
     }
 
     AutoCompartment ac(cx, cx->atomsCompartment());
 
     JSFlatString *flat = js_NewStringCopyN<NoGC>(cx, tbchars, length);
     if (!flat) {
+        // Grudgingly forgo last-ditch GC. The alternative would be to release
+        // the lock, manually GC here, and retry from the top. If you fix this,
+        // please also fix or comment the similar case in Symbol::new_.
         js_ReportOutOfMemory(cx);
         return nullptr;
     }
 
     JSAtom *atom = flat->morphAtomizedStringIntoAtom();
 
     // We have held the lock since looking up p, and the operations we've done
     // since then can't GC; therefore the atoms table has not been modified and
--- a/js/src/jscntxtinlines.h
+++ b/js/src/jscntxtinlines.h
@@ -13,16 +13,17 @@
 #include "jsiter.h"
 
 #include "builtin/Object.h"
 #include "jit/IonFrames.h"
 #include "vm/ForkJoin.h"
 #include "vm/HelperThreads.h"
 #include "vm/Interpreter.h"
 #include "vm/ProxyObject.h"
+#include "vm/Symbol.h"
 
 #include "gc/ForkJoinNursery-inl.h"
 
 namespace js {
 
 #ifdef JS_CRASH_DIAGNOSTICS
 class CompartmentChecker
 {
--- a/js/src/jscompartmentinlines.h
+++ b/js/src/jscompartmentinlines.h
@@ -6,16 +6,18 @@
 
 #ifndef jscompartmentinlines_h
 #define jscompartmentinlines_h
 
 #include "jscompartment.h"
 
 #include "gc/Barrier.h"
 
+#include "jscntxtinlines.h"
+
 inline void
 JSCompartment::initGlobal(js::GlobalObject &global)
 {
     JS_ASSERT(global.compartment() == this);
     JS_ASSERT(!global_);
     global_.set(&global);
 }
 
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -975,16 +975,18 @@ JS::IncrementalReferenceBarrier(void *pt
     JS_ASSERT(!zone->runtimeFromMainThread()->isHeapMajorCollecting());
 
     AutoMarkInDeadZone amn(zone);
 
     if (kind == JSTRACE_OBJECT)
         JSObject::writeBarrierPre(static_cast<JSObject*>(cell));
     else if (kind == JSTRACE_STRING)
         JSString::writeBarrierPre(static_cast<JSString*>(cell));
+    else if (kind == JSTRACE_SYMBOL)
+        JS::Symbol::writeBarrierPre(static_cast<JS::Symbol*>(cell));
     else if (kind == JSTRACE_SCRIPT)
         JSScript::writeBarrierPre(static_cast<JSScript*>(cell));
     else if (kind == JSTRACE_LAZY_SCRIPT)
         LazyScript::writeBarrierPre(static_cast<LazyScript*>(cell));
     else if (kind == JSTRACE_SHAPE)
         Shape::writeBarrierPre(static_cast<Shape*>(cell));
     else if (kind == JSTRACE_BASE_SHAPE)
         BaseShape::writeBarrierPre(static_cast<BaseShape*>(cell));
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -208,16 +208,17 @@
 #endif
 #include "jit/IonCode.h"
 #include "js/SliceBudget.h"
 #include "vm/Debugger.h"
 #include "vm/ForkJoin.h"
 #include "vm/ProxyObject.h"
 #include "vm/Shape.h"
 #include "vm/String.h"
+#include "vm/Symbol.h"
 #include "vm/TraceLogging.h"
 #include "vm/WrapperObject.h"
 
 #include "jsobjinlines.h"
 #include "jsscriptinlines.h"
 
 #include "vm/Stack-inl.h"
 #include "vm/String-inl.h"
@@ -272,16 +273,17 @@ const uint32_t Arena::ThingSizes[] = {
     sizeof(JSScript),           /* FINALIZE_SCRIPT              */
     sizeof(LazyScript),         /* FINALIZE_LAZY_SCRIPT         */
     sizeof(Shape),              /* FINALIZE_SHAPE               */
     sizeof(BaseShape),          /* FINALIZE_BASE_SHAPE          */
     sizeof(types::TypeObject),  /* FINALIZE_TYPE_OBJECT         */
     sizeof(JSFatInlineString),  /* FINALIZE_FAT_INLINE_STRING   */
     sizeof(JSString),           /* FINALIZE_STRING              */
     sizeof(JSExternalString),   /* FINALIZE_EXTERNAL_STRING     */
+    sizeof(JS::Symbol),         /* FINALIZE_SYMBOL              */
     sizeof(jit::JitCode),       /* FINALIZE_JITCODE             */
 };
 
 #define OFFSET(type) uint32_t(sizeof(ArenaHeader) + (ArenaSize - sizeof(ArenaHeader)) % sizeof(type))
 
 const uint32_t Arena::FirstThingOffsets[] = {
     OFFSET(JSObject),           /* FINALIZE_OBJECT0             */
     OFFSET(JSObject),           /* FINALIZE_OBJECT0_BACKGROUND  */
@@ -298,27 +300,29 @@ const uint32_t Arena::FirstThingOffsets[
     OFFSET(JSScript),           /* FINALIZE_SCRIPT              */
     OFFSET(LazyScript),         /* FINALIZE_LAZY_SCRIPT         */
     OFFSET(Shape),              /* FINALIZE_SHAPE               */
     OFFSET(BaseShape),          /* FINALIZE_BASE_SHAPE          */
     OFFSET(types::TypeObject),  /* FINALIZE_TYPE_OBJECT         */
     OFFSET(JSFatInlineString),  /* FINALIZE_FAT_INLINE_STRING   */
     OFFSET(JSString),           /* FINALIZE_STRING              */
     OFFSET(JSExternalString),   /* FINALIZE_EXTERNAL_STRING     */
+    OFFSET(JS::Symbol),         /* FINALIZE_SYMBOL              */
     OFFSET(jit::JitCode),       /* FINALIZE_JITCODE             */
 };
 
 #undef OFFSET
 
 const char *
 js::gc::TraceKindAsAscii(JSGCTraceKind kind)
 {
     switch(kind) {
       case JSTRACE_OBJECT: return "JSTRACE_OBJECT";
       case JSTRACE_STRING: return "JSTRACE_STRING";
+      case JSTRACE_SYMBOL: return "JSTRACE_SYMBOL";
       case JSTRACE_SCRIPT: return "JSTRACE_SCRIPT";
       case JSTRACE_LAZY_SCRIPT: return "JSTRACE_SCRIPT";
       case JSTRACE_JITCODE: return "JSTRACE_JITCODE";
       case JSTRACE_SHAPE: return "JSTRACE_SHAPE";
       case JSTRACE_BASE_SHAPE: return "JSTRACE_BASE_SHAPE";
       case JSTRACE_TYPE_OBJECT: return "JSTRACE_TYPE_OBJECT";
       default: return "INVALID";
     }
@@ -368,37 +372,38 @@ static const AllocKind BackgroundPhaseOb
     FINALIZE_OBJECT0_BACKGROUND,
     FINALIZE_OBJECT2_BACKGROUND,
     FINALIZE_OBJECT4_BACKGROUND,
     FINALIZE_OBJECT8_BACKGROUND,
     FINALIZE_OBJECT12_BACKGROUND,
     FINALIZE_OBJECT16_BACKGROUND
 };
 
-static const AllocKind BackgroundPhaseStrings[] = {
+static const AllocKind BackgroundPhaseStringsAndSymbols[] = {
     FINALIZE_FAT_INLINE_STRING,
-    FINALIZE_STRING
+    FINALIZE_STRING,
+    FINALIZE_SYMBOL
 };
 
 static const AllocKind BackgroundPhaseShapes[] = {
     FINALIZE_SHAPE,
     FINALIZE_BASE_SHAPE,
     FINALIZE_TYPE_OBJECT
 };
 
 static const AllocKind * const BackgroundPhases[] = {
     BackgroundPhaseObjects,
-    BackgroundPhaseStrings,
+    BackgroundPhaseStringsAndSymbols,
     BackgroundPhaseShapes
 };
 static const int BackgroundPhaseCount = sizeof(BackgroundPhases) / sizeof(AllocKind*);
 
 static const int BackgroundPhaseLength[] = {
     sizeof(BackgroundPhaseObjects) / sizeof(AllocKind),
-    sizeof(BackgroundPhaseStrings) / sizeof(AllocKind),
+    sizeof(BackgroundPhaseStringsAndSymbols) / sizeof(AllocKind),
     sizeof(BackgroundPhaseShapes) / sizeof(AllocKind)
 };
 
 #ifdef DEBUG
 void
 ArenaHeader::checkSynchronizedWithFreeList() const
 {
     /*
@@ -593,16 +598,18 @@ FinalizeArenas(FreeOp *fop,
       case FINALIZE_TYPE_OBJECT:
         return FinalizeTypedArenas<types::TypeObject>(fop, src, dest, thingKind, budget);
       case FINALIZE_STRING:
         return FinalizeTypedArenas<JSString>(fop, src, dest, thingKind, budget);
       case FINALIZE_FAT_INLINE_STRING:
         return FinalizeTypedArenas<JSFatInlineString>(fop, src, dest, thingKind, budget);
       case FINALIZE_EXTERNAL_STRING:
         return FinalizeTypedArenas<JSExternalString>(fop, src, dest, thingKind, budget);
+      case FINALIZE_SYMBOL:
+        return FinalizeTypedArenas<JS::Symbol>(fop, src, dest, thingKind, budget);
       case FINALIZE_JITCODE:
 #ifdef JS_ION
       {
         // JitCode finalization may release references on an executable
         // allocator that is accessed when requesting interrupts.
         JSRuntime::AutoLockForInterrupt lock(fop->runtime());
         return FinalizeTypedArenas<jit::JitCode>(fop, src, dest, thingKind, budget);
       }
@@ -2002,22 +2009,23 @@ ArenaLists::queueObjectsForSweep(FreeOp 
     queueForBackgroundSweep(fop, FINALIZE_OBJECT2_BACKGROUND);
     queueForBackgroundSweep(fop, FINALIZE_OBJECT4_BACKGROUND);
     queueForBackgroundSweep(fop, FINALIZE_OBJECT8_BACKGROUND);
     queueForBackgroundSweep(fop, FINALIZE_OBJECT12_BACKGROUND);
     queueForBackgroundSweep(fop, FINALIZE_OBJECT16_BACKGROUND);
 }
 
 void
-ArenaLists::queueStringsForSweep(FreeOp *fop)
+ArenaLists::queueStringsAndSymbolsForSweep(FreeOp *fop)
 {
     gcstats::AutoPhase ap(fop->runtime()->gc.stats, gcstats::PHASE_SWEEP_STRING);
 
     queueForBackgroundSweep(fop, FINALIZE_FAT_INLINE_STRING);
     queueForBackgroundSweep(fop, FINALIZE_STRING);
+    queueForBackgroundSweep(fop, FINALIZE_SYMBOL);
 
     queueForForegroundSweep(fop, FINALIZE_EXTERNAL_STRING);
 }
 
 void
 ArenaLists::queueScriptsForSweep(FreeOp *fop)
 {
     gcstats::AutoPhase ap(fop->runtime()->gc.stats, gcstats::PHASE_SWEEP_SCRIPT);
@@ -4130,17 +4138,17 @@ GCRuntime::beginSweepingZoneGroup()
      * Objects are finalized immediately but this may change in the future.
      */
     for (GCZoneGroupIter zone(rt); !zone.done(); zone.next()) {
         gcstats::AutoSCC scc(stats, zoneGroupIndex);
         zone->allocator.arenas.queueObjectsForSweep(&fop);
     }
     for (GCZoneGroupIter zone(rt); !zone.done(); zone.next()) {
         gcstats::AutoSCC scc(stats, zoneGroupIndex);
-        zone->allocator.arenas.queueStringsForSweep(&fop);
+        zone->allocator.arenas.queueStringsAndSymbolsForSweep(&fop);
     }
     for (GCZoneGroupIter zone(rt); !zone.done(); zone.next()) {
         gcstats::AutoSCC scc(stats, zoneGroupIndex);
         zone->allocator.arenas.queueScriptsForSweep(&fop);
     }
 #ifdef JS_ION
     for (GCZoneGroupIter zone(rt); !zone.done(); zone.next()) {
         gcstats::AutoSCC scc(stats, zoneGroupIndex);
--- a/js/src/jsgc.h
+++ b/js/src/jsgc.h
@@ -20,16 +20,20 @@
 #include "js/SliceBudget.h"
 #include "js/Vector.h"
 
 class JSAtom;
 struct JSCompartment;
 class JSFlatString;
 class JSLinearString;
 
+namespace JS {
+class Symbol;
+} /* namespace JS */
+
 namespace js {
 
 class ArgumentsObject;
 class ArrayBufferObject;
 class ArrayBufferViewObject;
 class SharedArrayBufferObject;
 class BaseShape;
 class DebugScopeObject;
@@ -116,16 +120,17 @@ MapAllocToTraceKind(AllocKind kind)
         JSTRACE_SCRIPT,     /* FINALIZE_SCRIPT */
         JSTRACE_LAZY_SCRIPT,/* FINALIZE_LAZY_SCRIPT */
         JSTRACE_SHAPE,      /* FINALIZE_SHAPE */
         JSTRACE_BASE_SHAPE, /* FINALIZE_BASE_SHAPE */
         JSTRACE_TYPE_OBJECT,/* FINALIZE_TYPE_OBJECT */
         JSTRACE_STRING,     /* FINALIZE_FAT_INLINE_STRING */
         JSTRACE_STRING,     /* FINALIZE_STRING */
         JSTRACE_STRING,     /* FINALIZE_EXTERNAL_STRING */
+        JSTRACE_SYMBOL,     /* FINALIZE_SYMBOL */
         JSTRACE_JITCODE,    /* FINALIZE_JITCODE */
     };
     JS_STATIC_ASSERT(JS_ARRAY_LENGTH(map) == FINALIZE_LIMIT);
     return map[kind];
 }
 
 template <typename T> struct MapTypeToTraceKind {};
 template <> struct MapTypeToTraceKind<ObjectImpl>       { static const JSGCTraceKind kind = JSTRACE_OBJECT; };
@@ -143,16 +148,17 @@ template <> struct MapTypeToTraceKind<La
 template <> struct MapTypeToTraceKind<Shape>            { static const JSGCTraceKind kind = JSTRACE_SHAPE; };
 template <> struct MapTypeToTraceKind<BaseShape>        { static const JSGCTraceKind kind = JSTRACE_BASE_SHAPE; };
 template <> struct MapTypeToTraceKind<UnownedBaseShape> { static const JSGCTraceKind kind = JSTRACE_BASE_SHAPE; };
 template <> struct MapTypeToTraceKind<types::TypeObject>{ static const JSGCTraceKind kind = JSTRACE_TYPE_OBJECT; };
 template <> struct MapTypeToTraceKind<JSAtom>           { static const JSGCTraceKind kind = JSTRACE_STRING; };
 template <> struct MapTypeToTraceKind<JSString>         { static const JSGCTraceKind kind = JSTRACE_STRING; };
 template <> struct MapTypeToTraceKind<JSFlatString>     { static const JSGCTraceKind kind = JSTRACE_STRING; };
 template <> struct MapTypeToTraceKind<JSLinearString>   { static const JSGCTraceKind kind = JSTRACE_STRING; };
+template <> struct MapTypeToTraceKind<JS::Symbol>       { static const JSGCTraceKind kind = JSTRACE_SYMBOL; }; 
 template <> struct MapTypeToTraceKind<PropertyName>     { static const JSGCTraceKind kind = JSTRACE_STRING; };
 template <> struct MapTypeToTraceKind<jit::JitCode>     { static const JSGCTraceKind kind = JSTRACE_JITCODE; };
 
 /* Return a printable string for the given kind, for diagnostic purposes. */
 const char *
 TraceKindAsAscii(JSGCTraceKind kind);
 
 /* Map from C++ type to finalize kind. JSObject does not have a 1:1 mapping, so must use Arena::thingSize. */
@@ -160,16 +166,17 @@ template <typename T> struct MapTypeToFi
 template <> struct MapTypeToFinalizeKind<JSScript>          { static const AllocKind kind = FINALIZE_SCRIPT; };
 template <> struct MapTypeToFinalizeKind<LazyScript>        { static const AllocKind kind = FINALIZE_LAZY_SCRIPT; };
 template <> struct MapTypeToFinalizeKind<Shape>             { static const AllocKind kind = FINALIZE_SHAPE; };
 template <> struct MapTypeToFinalizeKind<BaseShape>         { static const AllocKind kind = FINALIZE_BASE_SHAPE; };
 template <> struct MapTypeToFinalizeKind<types::TypeObject> { static const AllocKind kind = FINALIZE_TYPE_OBJECT; };
 template <> struct MapTypeToFinalizeKind<JSFatInlineString> { static const AllocKind kind = FINALIZE_FAT_INLINE_STRING; };
 template <> struct MapTypeToFinalizeKind<JSString>          { static const AllocKind kind = FINALIZE_STRING; };
 template <> struct MapTypeToFinalizeKind<JSExternalString>  { static const AllocKind kind = FINALIZE_EXTERNAL_STRING; };
+template <> struct MapTypeToFinalizeKind<JS::Symbol>        { static const AllocKind kind = FINALIZE_SYMBOL; };
 template <> struct MapTypeToFinalizeKind<jit::JitCode>      { static const AllocKind kind = FINALIZE_JITCODE; };
 
 #if defined(JSGC_GENERATIONAL) || defined(DEBUG)
 static inline bool
 IsNurseryAllocable(AllocKind kind)
 {
     JS_ASSERT(kind >= 0 && unsigned(kind) < FINALIZE_LIMIT);
     static const bool map[] = {
@@ -188,16 +195,17 @@ IsNurseryAllocable(AllocKind kind)
         false,     /* FINALIZE_SCRIPT */
         false,     /* FINALIZE_LAZY_SCRIPT */
         false,     /* FINALIZE_SHAPE */
         false,     /* FINALIZE_BASE_SHAPE */
         false,     /* FINALIZE_TYPE_OBJECT */
         false,     /* FINALIZE_FAT_INLINE_STRING */
         false,     /* FINALIZE_STRING */
         false,     /* FINALIZE_EXTERNAL_STRING */
+        false,     /* FINALIZE_SYMBOL */
         false,     /* FINALIZE_JITCODE */
     };
     JS_STATIC_ASSERT(JS_ARRAY_LENGTH(map) == FINALIZE_LIMIT);
     return map[kind];
 }
 #endif
 
 #if defined(JSGC_FJGENERATIONAL)
@@ -224,16 +232,17 @@ IsFJNurseryAllocable(AllocKind kind)
         false,     /* FINALIZE_SCRIPT */
         false,     /* FINALIZE_LAZY_SCRIPT */
         false,     /* FINALIZE_SHAPE */
         false,     /* FINALIZE_BASE_SHAPE */
         false,     /* FINALIZE_TYPE_OBJECT */
         false,     /* FINALIZE_FAT_INLINE_STRING */
         false,     /* FINALIZE_STRING */
         false,     /* FINALIZE_EXTERNAL_STRING */
+        false,     /* FINALIZE_SYMBOL */
         false,     /* FINALIZE_JITCODE */
     };
     JS_STATIC_ASSERT(JS_ARRAY_LENGTH(map) == FINALIZE_LIMIT);
     return map[kind];
 }
 #endif
 
 static inline bool
@@ -256,16 +265,17 @@ IsBackgroundFinalized(AllocKind kind)
         false,     /* FINALIZE_SCRIPT */
         false,     /* FINALIZE_LAZY_SCRIPT */
         true,      /* FINALIZE_SHAPE */
         true,      /* FINALIZE_BASE_SHAPE */
         true,      /* FINALIZE_TYPE_OBJECT */
         true,      /* FINALIZE_FAT_INLINE_STRING */
         true,      /* FINALIZE_STRING */
         false,     /* FINALIZE_EXTERNAL_STRING */
+        true,      /* FINALIZE_SYMBOL */
         false,     /* FINALIZE_JITCODE */
     };
     JS_STATIC_ASSERT(JS_ARRAY_LENGTH(map) == FINALIZE_LIMIT);
     return map[kind];
 }
 
 static inline bool
 CanBeFinalizedInBackground(gc::AllocKind kind, const Class *clasp)
@@ -794,17 +804,17 @@ class ArenaLists
 #endif
     }
 
     void checkEmptyFreeList(AllocKind kind) {
         JS_ASSERT(freeLists[kind].isEmpty());
     }
 
     void queueObjectsForSweep(FreeOp *fop);
-    void queueStringsForSweep(FreeOp *fop);
+    void queueStringsAndSymbolsForSweep(FreeOp *fop);
     void queueShapesForSweep(FreeOp *fop);
     void queueScriptsForSweep(FreeOp *fop);
     void queueJitCodeForSweep(FreeOp *fop);
 
     bool foregroundFinalize(FreeOp *fop, AllocKind thingKind, SliceBudget &sliceBudget);
     static void backgroundFinalize(FreeOp *fop, ArenaHeader *listHead, bool onBackgroundThread);
 
     void wipeDuringParallelExecution(JSRuntime *rt);
--- a/js/src/jsgcinlines.h
+++ b/js/src/jsgcinlines.h
@@ -542,16 +542,17 @@ CheckAllocatorState(ThreadSafeContext *c
         return true;
 
     JSContext *ncx = cx->asJSContext();
     JSRuntime *rt = ncx->runtime();
 #if defined(JS_GC_ZEAL) || defined(DEBUG)
     JS_ASSERT_IF(rt->isAtomsCompartment(ncx->compartment()),
                  kind == FINALIZE_STRING ||
                  kind == FINALIZE_FAT_INLINE_STRING ||
+                 kind == FINALIZE_SYMBOL ||
                  kind == FINALIZE_JITCODE);
     JS_ASSERT(!rt->isHeapBusy());
     JS_ASSERT(rt->gc.isAllocAllowed());
 #endif
 
     // Crash if we perform a GC action when it is not safe.
     if (allowGC && !rt->mainThread.suppressGC)
         JS::AutoAssertOnGC::VerifyIsSafeToGC(rt);
@@ -601,16 +602,20 @@ CheckIncrementalZoneState(ThreadSafeCont
 
 template <AllowGC allowGC>
 inline JSObject *
 AllocateObject(ThreadSafeContext *cx, AllocKind kind, size_t nDynamicSlots, InitialHeap heap)
 {
     size_t thingSize = Arena::thingSize(kind);
 
     JS_ASSERT(thingSize == Arena::thingSize(kind));
+    JS_ASSERT(thingSize >= sizeof(JSObject));
+    static_assert(sizeof(JSObject) >= CellSize,
+                  "All allocations must be at least the allocator-imposed minimum size.");
+
     if (!CheckAllocatorState<allowGC>(cx, kind))
         return nullptr;
 
 #ifdef JSGC_GENERATIONAL
     if (cx->hasNursery() && ShouldNurseryAllocate(cx->nursery(), kind, heap)) {
         JSObject *obj = TryNewNurseryObject<allowGC>(cx, thingSize, nDynamicSlots);
         if (obj)
             return obj;
@@ -647,16 +652,19 @@ AllocateObject(ThreadSafeContext *cx, Al
     CheckIncrementalZoneState(cx, obj);
     return obj;
 }
 
 template <typename T, AllowGC allowGC>
 inline T *
 AllocateNonObject(ThreadSafeContext *cx)
 {
+    static_assert(sizeof(T) >= CellSize,
+                  "All allocations must be at least the allocator-imposed minimum size.");
+
     AllocKind kind = MapTypeToFinalizeKind<T>::kind;
     size_t thingSize = sizeof(T);
 
     JS_ASSERT(thingSize == Arena::thingSize(kind));
     if (!CheckAllocatorState<allowGC>(cx, kind))
         return nullptr;
 
     T *t = static_cast<T *>(cx->allocator()->arenas.allocateFromFreeList(kind, thingSize));
--- a/js/src/jspubtd.h
+++ b/js/src/jspubtd.h
@@ -66,16 +66,17 @@ enum JSVersion {
 enum JSType {
     JSTYPE_VOID,                /* undefined */
     JSTYPE_OBJECT,              /* object */
     JSTYPE_FUNCTION,            /* function */
     JSTYPE_STRING,              /* string */
     JSTYPE_NUMBER,              /* number */
     JSTYPE_BOOLEAN,             /* boolean */
     JSTYPE_NULL,                /* null */
+    JSTYPE_SYMBOL,              /* symbol */
     JSTYPE_LIMIT
 };
 
 /* Dense index into cached prototypes and class atoms for standard objects. */
 enum JSProtoKey {
 #define PROTOKEY_AND_INITIALIZER(name,code,init,clasp) JSProto_##name = code,
     JS_FOR_EACH_PROTOTYPE(PROTOKEY_AND_INITIALIZER)
 #undef PROTOKEY_AND_INITIALIZER
@@ -99,16 +100,17 @@ enum JSIterateOp {
     /* Destroy iterator state. */
     JSENUMERATE_DESTROY
 };
 
 /* See Value::gcKind() and JSTraceCallback in Tracer.h. */
 enum JSGCTraceKind {
     JSTRACE_OBJECT,
     JSTRACE_STRING,
+    JSTRACE_SYMBOL,
     JSTRACE_SCRIPT,
 
     /*
      * Trace kinds internal to the engine. The embedding can only see them if
      * it implements JSTraceCallback.
      */
     JSTRACE_LAZY_SCRIPT,
     JSTRACE_JITCODE,
@@ -338,16 +340,17 @@ class Allocator;
 
 enum ThingRootKind
 {
     THING_ROOT_OBJECT,
     THING_ROOT_SHAPE,
     THING_ROOT_BASE_SHAPE,
     THING_ROOT_TYPE_OBJECT,
     THING_ROOT_STRING,
+    THING_ROOT_SYMBOL,
     THING_ROOT_JIT_CODE,
     THING_ROOT_SCRIPT,
     THING_ROOT_LAZY_SCRIPT,
     THING_ROOT_ID,
     THING_ROOT_VALUE,
     THING_ROOT_TYPE,
     THING_ROOT_BINDINGS,
     THING_ROOT_PROPERTY_DESCRIPTOR,
@@ -381,16 +384,17 @@ struct SpecificRootKind
 {
     static ThingRootKind rootKind() { return Kind; }
 };
 
 template <> struct RootKind<JSObject *> : SpecificRootKind<JSObject *, THING_ROOT_OBJECT> {};
 template <> struct RootKind<JSFlatString *> : SpecificRootKind<JSFlatString *, THING_ROOT_STRING> {};
 template <> struct RootKind<JSFunction *> : SpecificRootKind<JSFunction *, THING_ROOT_OBJECT> {};
 template <> struct RootKind<JSString *> : SpecificRootKind<JSString *, THING_ROOT_STRING> {};
+template <> struct RootKind<JS::Symbol *> : SpecificRootKind<JS::Symbol *, THING_ROOT_SYMBOL> {};
 template <> struct RootKind<JSScript *> : SpecificRootKind<JSScript *, THING_ROOT_SCRIPT> {};
 template <> struct RootKind<jsid> : SpecificRootKind<jsid, THING_ROOT_ID> {};
 template <> struct RootKind<JS::Value> : SpecificRootKind<JS::Value, THING_ROOT_VALUE> {};
 
 struct ContextFriendFields
 {
   protected:
     JSRuntime *const     runtime_;
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -192,16 +192,17 @@ UNIFIED_SOURCES += [
     'vm/SelfHosting.cpp',
     'vm/Shape.cpp',
     'vm/SharedArrayObject.cpp',
     'vm/SPSProfiler.cpp',
     'vm/Stack.cpp',
     'vm/String.cpp',
     'vm/StringBuffer.cpp',
     'vm/StructuredClone.cpp',
+    'vm/Symbol.cpp',
     'vm/ThreadPool.cpp',
     'vm/TypedArrayObject.cpp',
     'vm/UbiNode.cpp',
     'vm/Unicode.cpp',
     'vm/Value.cpp',
     'vm/WeakMapPtr.cpp',
     'vm/Xdr.cpp'
 ]
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -204,11 +204,12 @@
     macro(z, z, "z") \
     /* Type names must be contiguous and ordered; see js::TypeName. */ \
     macro(undefined, undefined, "undefined") \
     macro(object, object, "object") \
     macro(function, function, "function") \
     macro(string, string, "string") \
     macro(number, number, "number") \
     macro(boolean, boolean, "boolean") \
-    macro(null, null, "null")
+    macro(null, null, "null") \
+    macro(symbol, symbol, "symbol")
 
 #endif /* vm_CommonPropertyNames_h */
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -808,18 +808,20 @@ js::TypeOfValue(const Value &v)
     if (v.isString())
         return JSTYPE_STRING;
     if (v.isNull())
         return JSTYPE_OBJECT;
     if (v.isUndefined())
         return JSTYPE_VOID;
     if (v.isObject())
         return TypeOfObject(&v.toObject());
-    JS_ASSERT(v.isBoolean());
-    return JSTYPE_BOOLEAN;
+    if (v.isBoolean())
+        return JSTYPE_BOOLEAN;
+    JS_ASSERT(v.isSymbol());
+    return JSTYPE_SYMBOL;
 }
 
 /*
  * Enter the new with scope using an object at sp[-1] and associate the depth
  * of the with block with sp + stackIndex.
  */
 bool
 js::EnterWithOperation(JSContext *cx, AbstractFramePtr frame, HandleValue val,
--- a/js/src/vm/MemoryMetrics.cpp
+++ b/js/src/vm/MemoryMetrics.cpp
@@ -15,16 +15,17 @@
 #include "jsscript.h"
 
 #include "jit/BaselineJIT.h"
 #include "jit/Ion.h"
 #include "vm/ArrayObject.h"
 #include "vm/Runtime.h"
 #include "vm/Shape.h"
 #include "vm/String.h"
+#include "vm/Symbol.h"
 #include "vm/WrapperObject.h"
 
 using mozilla::DebugOnly;
 using mozilla::MallocSizeOf;
 using mozilla::Move;
 using mozilla::PodCopy;
 using mozilla::PodEqual;
 
@@ -395,16 +396,20 @@ StatsCellCallback(JSRuntime *rt, void *d
                 (void)zStats->allStrings->add(p, str, info);
             } else {
                 p->value().add(info);
             }
         }
         break;
       }
 
+      case JSTRACE_SYMBOL:
+        zStats->symbolsGCHeap += thingSize;
+        break;
+
       case JSTRACE_SHAPE: {
         Shape *shape = static_cast<Shape *>(thing);
         CompartmentStats *cStats = GetCompartmentStats(shape->compartment());
         if (shape->inDictionary()) {
             cStats->shapesGCHeapDict += thingSize;
 
             // nullptr because kidsSize shouldn't be incremented in this case.
             shape->addSizeOfExcludingThis(rtStats->mallocSizeOf_,
new file mode 100644
--- /dev/null
+++ b/js/src/vm/Symbol-inl.h
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef vm_Symbol_inl_h
+#define vm_Symbol_inl_h
+
+#include "vm/Symbol.h"
+
+#include "gc/Marking.h"
+
+#include "js/RootingAPI.h"
+
+#include "jsgcinlines.h"
+
+inline void
+JS::Symbol::markChildren(JSTracer *trc)
+{
+    if (description_)
+        MarkStringUnbarriered(trc, &description_, "description");
+}
+
+#endif /* vm_Symbol_inl_h */
new file mode 100644
--- /dev/null
+++ b/js/src/vm/Symbol.cpp
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "vm/Symbol.h"
+
+#include "jscntxt.h"
+#include "jscompartment.h"
+
+#include "gc/Rooting.h"
+
+#include "jscompartmentinlines.h"
+#include "jsgcinlines.h"
+
+using JS::Symbol;
+using namespace js;
+
+Symbol *
+Symbol::new_(ExclusiveContext *cx, JSString *description)
+{
+    RootedAtom atom(cx);
+    if (description) {
+        atom = AtomizeString(cx, description);
+        if (!atom)
+            return nullptr;
+    }
+
+    // Lock to allocate. If symbol allocation becomes a bottleneck, this can
+    // probably be replaced with an assertion that we're on the main thread.
+    AutoLockForExclusiveAccess lock(cx);
+    AutoCompartment ac(cx, cx->atomsCompartment());
+
+    // Following AtomizeAndCopyChars, we grudgingly forgo last-ditch GC here.
+    Symbol *p = gc::AllocateNonObject<Symbol, NoGC>(cx);
+    if (!p) {
+        js_ReportOutOfMemory(cx);
+        return nullptr;
+    }
+    return new (p) Symbol(atom);
+}
new file mode 100644
--- /dev/null
+++ b/js/src/vm/Symbol.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef vm_Symbol_h
+#define vm_Symbol_h
+
+#include "mozilla/Attributes.h"
+
+#include "gc/Barrier.h"
+
+#include "js/RootingAPI.h"
+#include "js/TypeDecls.h"
+
+namespace JS {
+
+class Symbol : public js::gc::BarrieredCell<Symbol>
+{
+  private:
+    uint32_t unused1_;  // This field will be used before long.
+    JSAtom *description_;
+
+    // The minimum allocation size is sizeof(JSString): 16 bytes on 32-bit
+    // architectures and 24 bytes on 64-bit.  8 bytes of padding makes Symbol
+    // the minimum size on both.
+    uint64_t unused2_;
+
+    explicit Symbol(JSAtom *desc) : description_(desc) {}
+    Symbol(const Symbol &) MOZ_DELETE;
+    void operator=(const Symbol &) MOZ_DELETE;
+
+  public:
+    static Symbol *new_(js::ExclusiveContext *cx, JSString *description);
+
+    JSAtom *description() const { return description_; }
+
+    static inline js::ThingRootKind rootKind() { return js::THING_ROOT_SYMBOL; }
+    inline void markChildren(JSTracer *trc);
+    inline void finalize(js::FreeOp *) {}
+};
+
+} /* namespace JS */
+
+#endif /* vm_Symbol_h */
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -1904,16 +1904,20 @@ ReportZoneStats(const JS::ZoneStats &zSt
                 bool anonymize,
                 size_t *gcTotalOut = nullptr)
 {
     const nsAutoCString& pathPrefix = extras.pathPrefix;
     size_t gcTotal = 0, sundriesGCHeap = 0, sundriesMallocHeap = 0;
 
     MOZ_ASSERT(!gcTotalOut == zStats.isTotals);
 
+    ZCREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("symbols/gc-heap"),
+        zStats.symbolsGCHeap,
+        "Symbols.");
+
     ZCREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("gc-heap-arena-admin"),
         zStats.gcHeapArenaAdmin,
         "Bookkeeping information and alignment padding within GC arenas.");
 
     ZCREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("unused-gc-things"),
         zStats.unusedGCThings,
         "Empty GC thing cells within non-empty arenas.");
 
--- a/xpcom/base/CycleCollectedJSRuntime.cpp
+++ b/xpcom/base/CycleCollectedJSRuntime.cpp
@@ -569,16 +569,17 @@ CycleCollectedJSRuntime::DescribeGCThing
     } else {
       JS_snprintf(name, sizeof(name), "JS Object (%s)",
                   clasp->name);
     }
   } else {
     static const char trace_types[][11] = {
       "Object",
       "String",
+      "Symbol",
       "Script",
       "LazyScript",
       "IonCode",
       "Shape",
       "BaseShape",
       "TypeObject",
     };
     static_assert(MOZ_ARRAY_LENGTH(trace_types) == JSTRACE_LAST + 1,