Bug 645416, part 9 - Implement the symbol registry and Symbol.for(). r=terrence,r=efaust.
authorJason Orendorff <jorendorff@mozilla.com>
Mon, 23 Jun 2014 10:56:49 -0500
changeset 190276 82afa573b28538462fef8d78c06e3fbd39033d5a
parent 190275 0513197d722ecddd0b7a3f3a8a4caa7b6bc9f39f
child 190277 d8e2600e9aa3abe063ef443781f13586f023687f
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, efaust
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 9 - Implement the symbol registry and Symbol.for(). r=terrence,r=efaust.
js/src/builtin/SymbolObject.cpp
js/src/builtin/SymbolObject.h
js/src/gc/Statistics.cpp
js/src/gc/Statistics.h
js/src/jsapi-tests/testSymbol.cpp
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jscntxt.h
js/src/jsgc.cpp
js/src/tests/ecma_6/Map/symbols.js
js/src/tests/ecma_6/Set/symbols.js
js/src/tests/ecma_6/Symbol/equality.js
js/src/tests/ecma_6/Symbol/for.js
js/src/tests/ecma_6/Symbol/realms.js
js/src/tests/ecma_6/Symbol/surfaces.js
js/src/tests/ecma_6/Symbol/typeof.js
js/src/vm/Runtime.cpp
js/src/vm/Runtime.h
js/src/vm/Symbol.cpp
js/src/vm/Symbol.h
--- a/js/src/builtin/SymbolObject.cpp
+++ b/js/src/builtin/SymbolObject.cpp
@@ -39,16 +39,17 @@ const JSPropertySpec SymbolObject::prope
     JS_PS_END
 };
 
 const JSFunctionSpec SymbolObject::methods[] = {
     JS_FS_END
 };
 
 const JSFunctionSpec SymbolObject::staticMethods[] = {
+    JS_FN("for", for_, 1, 0),
     JS_FS_END
 };
 
 JSObject *
 SymbolObject::initClass(JSContext *cx, HandleObject obj)
 {
     Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
 
@@ -90,17 +91,36 @@ SymbolObject::construct(JSContext *cx, u
     RootedString desc(cx);
     if (!args.get(0).isUndefined()) {
         desc = ToString(cx, args.get(0));
         if (!desc)
             return false;
     }
 
     // step 4
-    RootedSymbol symbol(cx, JS::Symbol::new_(cx, desc));
+    RootedSymbol symbol(cx, JS::Symbol::new_(cx, false, desc));
+    if (!symbol)
+        return false;
+    args.rval().setSymbol(symbol);
+    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);
+
+    // steps 1-2
+    RootedString stringKey(cx, ToString(cx, args.get(0)));
+    if (!stringKey)
+        return false;
+
+    // steps 3-7
+    JS::Symbol *symbol = JS::Symbol::for_(cx, stringKey);
     if (!symbol)
         return false;
     args.rval().setSymbol(symbol);
     return true;
 }
 
 JSObject *
 js_InitSymbolClass(JSContext *cx, HandleObject obj)
--- a/js/src/builtin/SymbolObject.h
+++ b/js/src/builtin/SymbolObject.h
@@ -37,16 +37,19 @@ class SymbolObject : public JSObject
 
   private:
     inline void setPrimitiveValue(JS::Symbol *symbol) {
         setFixedSlot(PRIMITIVE_VALUE_SLOT, SymbolValue(symbol));
     }
 
     static bool construct(JSContext *cx, unsigned argc, Value *vp);
 
+    // Static methods.
+    static bool for_(JSContext *cx, unsigned argc, Value *vp);
+
     static const JSPropertySpec properties[];
     static const JSFunctionSpec methods[];
     static const JSFunctionSpec staticMethods[];
 };
 
 } /* namespace js */
 
 extern JSObject *
--- a/js/src/gc/Statistics.cpp
+++ b/js/src/gc/Statistics.cpp
@@ -283,16 +283,17 @@ static const PhaseInfo phases[] = {
     { PHASE_SWEEP_MARK_TYPES, "Mark Types During Sweeping", PHASE_SWEEP_MARK },
     { PHASE_SWEEP_MARK_INCOMING_BLACK, "Mark Incoming Black Pointers", PHASE_SWEEP_MARK },
     { PHASE_SWEEP_MARK_WEAK, "Mark Weak", PHASE_SWEEP_MARK },
     { PHASE_SWEEP_MARK_INCOMING_GRAY, "Mark Incoming Gray Pointers", PHASE_SWEEP_MARK },
     { PHASE_SWEEP_MARK_GRAY, "Mark Gray", PHASE_SWEEP_MARK },
     { PHASE_SWEEP_MARK_GRAY_WEAK, "Mark Gray and Weak", PHASE_SWEEP_MARK },
     { PHASE_FINALIZE_START, "Finalize Start Callback", PHASE_SWEEP },
     { PHASE_SWEEP_ATOMS, "Sweep Atoms", PHASE_SWEEP },
+    { PHASE_SWEEP_SYMBOL_REGISTRY, "Sweep Symbol Registry", PHASE_SWEEP },
     { PHASE_SWEEP_COMPARTMENTS, "Sweep Compartments", PHASE_SWEEP },
     { PHASE_SWEEP_DISCARD_CODE, "Sweep Discard Code", PHASE_SWEEP_COMPARTMENTS },
     { PHASE_SWEEP_TABLES, "Sweep Tables", PHASE_SWEEP_COMPARTMENTS },
     { PHASE_SWEEP_TABLES_WRAPPER, "Sweep Cross Compartment Wrappers", PHASE_SWEEP_TABLES },
     { PHASE_SWEEP_TABLES_BASE_SHAPE, "Sweep Base Shapes", PHASE_SWEEP_TABLES },
     { PHASE_SWEEP_TABLES_INITIAL_SHAPE, "Sweep Initial Shapes", PHASE_SWEEP_TABLES },
     { PHASE_SWEEP_TABLES_TYPE_OBJECT, "Sweep Type Objects", PHASE_SWEEP_TABLES },
     { PHASE_SWEEP_TABLES_BREAKPOINT, "Sweep Breakpoints", PHASE_SWEEP_TABLES },
--- a/js/src/gc/Statistics.h
+++ b/js/src/gc/Statistics.h
@@ -34,16 +34,17 @@ enum Phase {
     PHASE_SWEEP_MARK_TYPES,
     PHASE_SWEEP_MARK_INCOMING_BLACK,
     PHASE_SWEEP_MARK_WEAK,
     PHASE_SWEEP_MARK_INCOMING_GRAY,
     PHASE_SWEEP_MARK_GRAY,
     PHASE_SWEEP_MARK_GRAY_WEAK,
     PHASE_FINALIZE_START,
     PHASE_SWEEP_ATOMS,
+    PHASE_SWEEP_SYMBOL_REGISTRY,
     PHASE_SWEEP_COMPARTMENTS,
     PHASE_SWEEP_DISCARD_CODE,
     PHASE_SWEEP_TABLES,
     PHASE_SWEEP_TABLES_WRAPPER,
     PHASE_SWEEP_TABLES_BASE_SHAPE,
     PHASE_SWEEP_TABLES_INITIAL_SHAPE,
     PHASE_SWEEP_TABLES_TYPE_OBJECT,
     PHASE_SWEEP_TABLES_BREAKPOINT,
--- a/js/src/jsapi-tests/testSymbol.cpp
+++ b/js/src/jsapi-tests/testSymbol.cpp
@@ -21,8 +21,37 @@ BEGIN_TEST(testSymbol_New)
 
     CHECK(desc = JS_NewStringCopyZ(cx, "ponies"));
     CHECK(sym2 = NewSymbol(cx, desc));
     CHECK_SAME(StringValue(GetSymbolDescription(sym2)), StringValue(desc));
 
     return true;
 }
 END_TEST(testSymbol_New)
+
+BEGIN_TEST(testSymbol_GetSymbolFor)
+{
+    using namespace JS;
+
+    RootedString desc(cx, JS_NewStringCopyZ(cx, "ponies"));
+    CHECK(desc);
+    RootedSymbol sym1(cx);
+    CHECK(sym1 = GetSymbolFor(cx, desc));
+    CHECK_SAME(StringValue(GetSymbolDescription(sym1)), StringValue(desc));
+
+    // Calling JS::GetSymbolFor again with the same arguments produces the
+    // same Symbol.
+    RootedSymbol sym2(cx);
+    CHECK(sym2 = GetSymbolFor(cx, desc));
+    CHECK_EQUAL(sym1, sym2);
+
+    // Passing a new but equal string also produces the same Symbol.
+    CHECK(desc = JS_NewStringCopyZ(cx, "ponies"));
+    CHECK(sym2 = GetSymbolFor(cx, desc));
+    CHECK_EQUAL(sym1, sym2);
+
+    // But SymbolNew always produces a new distinct Symbol.
+    CHECK(sym2 = NewSymbol(cx, desc));
+    CHECK(sym2 != sym1);
+
+    return true;
+}
+END_TEST(testSymbol_GetSymbolFor)
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -5621,17 +5621,27 @@ JS_EncodeStringToBuffer(JSContext *cx, J
 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);
+    return Symbol::new_(cx, false, description);
+}
+
+JS_PUBLIC_API(JS::Symbol *)
+JS::GetSymbolFor(JSContext *cx, HandleString key)
+{
+    AssertHeapIsIdle(cx);
+    CHECK_REQUEST(cx);
+    assertSameCompartment(cx, key);
+
+    return Symbol::for_(cx, key);
 }
 
 JS_PUBLIC_API(JSString *)
 JS::GetSymbolDescription(HandleSymbol symbol)
 {
     return symbol->description();
 }
 
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -4364,16 +4364,26 @@ namespace JS {
  *
  * If description is null, the new Symbol's [[Description]] attribute is
  * undefined.
  */
 JS_PUBLIC_API(Symbol *)
 NewSymbol(JSContext *cx, HandleString description);
 
 /*
+ * Symbol.for as specified in ES6.
+ *
+ * Get a Symbol with the description 'key' from the Runtime-wide symbol registry.
+ * If there is not already a Symbol with that description in the registry, a new
+ * Symbol is created and registered. 'key' must not be null.
+ */
+JS_PUBLIC_API(Symbol *)
+GetSymbolFor(JSContext *cx, HandleString key);
+
+/*
  * 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);
 
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -382,16 +382,19 @@ class ExclusiveContext : public ThreadSa
         return runtime_->parseMapPool();
     }
     AtomSet &atoms() {
         return runtime_->atoms();
     }
     JSCompartment *atomsCompartment() {
         return runtime_->atomsCompartment();
     }
+    SymbolRegistry &symbolRegistry() {
+        return runtime_->symbolRegistry();
+    }
     ScriptDataTable &scriptDataTable() {
         return runtime_->scriptDataTable();
     }
 
     // Methods specific to any HelperThread for the context.
     frontend::CompileError &addPendingCompileError();
     void addPendingOverRecursed();
 };
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -4075,18 +4075,24 @@ GCRuntime::beginSweepingZoneGroup()
         for (Callback<JSFinalizeCallback> *p = rt->gc.finalizeCallbacks.begin();
              p < rt->gc.finalizeCallbacks.end(); p++)
         {
             p->op(&fop, JSFINALIZE_GROUP_START, !isFull /* unused */, p->data);
         }
     }
 
     if (sweepingAtoms) {
-        gcstats::AutoPhase ap(stats, gcstats::PHASE_SWEEP_ATOMS);
-        rt->sweepAtoms();
+        {
+            gcstats::AutoPhase ap(stats, gcstats::PHASE_SWEEP_ATOMS);
+            rt->sweepAtoms();
+        }
+        {
+            gcstats::AutoPhase ap(stats, gcstats::PHASE_SWEEP_SYMBOL_REGISTRY);
+            rt->symbolRegistry().sweep();
+        }
     }
 
     /* Prune out dead views from ArrayBuffer's view lists. */
     for (GCCompartmentGroupIter c(rt); !c.done(); c.next())
         ArrayBufferObject::sweep(c);
 
     /* Collect watch points associated with unreachable objects. */
     WatchpointMap::sweepAll(rt);
@@ -4998,22 +5004,25 @@ struct AutoDisableStoreBuffer
 
 } /* anonymous namespace */
 
 void
 GCRuntime::collect(bool incremental, int64_t budget, JSGCInvocationKind gckind,
                    JS::gcreason::Reason reason)
 {
     /* GC shouldn't be running in parallel execution mode */
-    JS_ASSERT(!InParallelSection());
+    MOZ_ASSERT(!InParallelSection());
 
     JS_AbortIfWrongThread(rt);
 
     /* If we attempt to invoke the GC while we are running in the GC, assert. */
-    JS_ASSERT(!rt->isHeapBusy());
+    MOZ_ASSERT(!rt->isHeapBusy());
+
+    /* The engine never locks across anything that could GC. */
+    MOZ_ASSERT(!rt->currentThreadHasExclusiveAccess());
 
     if (rt->mainThread.suppressGC)
         return;
 
     TraceLogger *logger = TraceLoggerForMainThread(rt);
     AutoTraceLog logGC(logger, TraceLogger::GC);
 
 #ifdef JS_GC_ZEAL
--- a/js/src/tests/ecma_6/Map/symbols.js
+++ b/js/src/tests/ecma_6/Map/symbols.js
@@ -12,10 +12,22 @@ assertEq(m.has(Symbol()), false);
 assertEq(m.get(Symbol()), undefined);
 assertEq([...m][0][0], sym);
 m.set(sym, "replaced");
 assertEq(m.get(sym), "replaced");
 m.delete(sym);
 assertEq(m.has(sym), false);
 assertEq(m.size, 0);
 
+// Symbols returned by Symbol.for() can be Map keys.
+for (var word of "that that is is that that is not is not is that not it".split(' ')) {
+    sym = Symbol.for(word);
+    m.set(sym, (m.get(sym) || 0) + 1);
+}
+assertDeepEq([...m], [
+    [Symbol.for("that"), 5],
+    [Symbol.for("is"), 5],
+    [Symbol.for("not"), 3],
+    [Symbol.for("it"), 1]
+]);
+
 if (typeof reportCompare === "function")
   reportCompare(0, 0);
--- a/js/src/tests/ecma_6/Set/symbols.js
+++ b/js/src/tests/ecma_6/Set/symbols.js
@@ -11,10 +11,17 @@ assertEq(s.has(Symbol()), false);
 assertEq([...s][0], sym);
 s.add(sym);
 assertEq(s.has(sym), true);
 assertEq(s.size, 1);
 s.delete(sym);
 assertEq(s.has(sym), false);
 assertEq(s.size, 0);
 
+// Symbols returned by Symbol.for() can be in Sets.
+var str = "how much wood would a woodchuck chuck if a woodchuck could chuck wood";
+var s2  = "how much wood would a woodchuck chuck if could";
+var arr = [for (word of str.split(" ")) Symbol.for(word)];
+s = new Set(arr);
+assertDeepEq([...s], [for (word of s2.split(" ")) Symbol.for(word)]);
+
 if (typeof reportCompare === "function")
   reportCompare(0, 0);
--- a/js/src/tests/ecma_6/Symbol/equality.js
+++ b/js/src/tests/ecma_6/Symbol/equality.js
@@ -1,16 +1,20 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/licenses/publicdomain/ */
 
+// Symbol.for returns the same symbol whenever the same argument is passed.
+assertEq(Symbol.for("q") === Symbol.for("q"), true);
+
 // Several distinct Symbol values.
 var symbols = [
     Symbol(),
     Symbol("Symbol.iterator"),
-    Symbol("Symbol.iterator")   // distinct new symbol with the same description
+    Symbol("Symbol.iterator"),  // distinct new symbol with the same description
+    Symbol.for("Symbol.iterator")
 ];
 
 // Distinct symbols are never equal to each other, even if they have the same
 // description.
 for (var i = 0; i < symbols.length; i++) {
     for (var j = i; j < symbols.length; j++) {
         var expected = (i === j);
         assertEq(symbols[i] == symbols[j], expected);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Symbol/for.js
@@ -0,0 +1,31 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/ */
+
+// Symbol.for called twice with the same argument returns the same symbol.
+assertEq(Symbol.for("ponies"), Symbol.for("ponies"));
+
+// Called twice with equal strings: still the same result.
+var one = Array(64+1).join("x");
+var two = Array(8+1).join(Array(8+1).join("x"));
+assertEq(Symbol.for(one), Symbol.for(two));
+
+// Symbols created by calling Symbol() are not in the symbol registry.
+var sym = Symbol("123");
+assertEq(Symbol.for("123") !== sym, true);
+
+// Empty string is fine.
+assertEq(typeof Symbol.for(""), "symbol");
+
+// Primitive arguments.
+assertEq(Symbol.for(3), Symbol.for("3"));
+assertEq(Symbol.for(null), Symbol.for("null"));
+assertEq(Symbol.for(undefined), Symbol.for("undefined"));
+assertEq(Symbol.for(), Symbol.for("undefined"));
+
+// Symbol.for ignores the 'this' value.
+var foo = Symbol.for("foo")
+assertEq(Symbol.for.call(String, "foo"), foo);
+assertEq(Symbol.for.call(3.14, "foo"), foo);
+
+if (typeof reportCompare === "function")
+    reportCompare(0, 0);
--- a/js/src/tests/ecma_6/Symbol/realms.js
+++ b/js/src/tests/ecma_6/Symbol/realms.js
@@ -15,12 +15,16 @@ if (typeof newGlobal === "function") {
     assertEq(gj, g.jones);
     assertEq(gj !== Symbol("jones"), true);
 
     // A symbol can be round-tripped to another realm and back;
     // the result is the original symbol.
     var smith = Symbol("smith");
     g.smith = smith;  // put smith into the realm
     assertEq(g.smith, smith);  // pull it back out
+
+    // Symbol.for functions share a symbol registry across all realms.
+    assertEq(g.Symbol.for("ponies"), Symbol.for("ponies"));
+    assertEq(g.eval("Symbol.for('rainbows')"), Symbol.for("rainbows"));
 }
 
 if (typeof reportCompare === "function")
     reportCompare(0, 0);
--- a/js/src/tests/ecma_6/Symbol/surfaces.js
+++ b/js/src/tests/ecma_6/Symbol/surfaces.js
@@ -16,10 +16,12 @@ assertEq(desc.enumerable, false);
 assertEq(desc.writable, false);
 
 assertEq(Symbol.prototype.constructor, Symbol);
 desc = Object.getOwnPropertyDescriptor(Symbol.prototype, "constructor");
 assertEq(desc.configurable, true);
 assertEq(desc.enumerable, false);
 assertEq(desc.writable, true);
 
+assertEq(Symbol.for.length, 1);
+
 if (typeof reportCompare === "function")
     reportCompare(0, 0);
--- a/js/src/tests/ecma_6/Symbol/typeof.js
+++ b/js/src/tests/ecma_6/Symbol/typeof.js
@@ -1,8 +1,9 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/licenses/publicdomain/ */
 
 assertEq(typeof Symbol(), "symbol");
 assertEq(typeof Symbol("ponies"), "symbol");
+assertEq(typeof Symbol.for("ponies"), "symbol");
 
 if (typeof reportCompare === "function")
     reportCompare(0, 0);
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -301,16 +301,19 @@ JSRuntime::init(uint32_t maxbytes)
 
     atomsCompartment->isSystem = true;
     atomsZone->isSystem = true;
     atomsZone->setGCLastBytes(8192, GC_NORMAL);
 
     atomsZone.forget();
     this->atomsCompartment_ = atomsCompartment.forget();
 
+    if (!symbolRegistry_.init())
+        return false;
+
     if (!scriptDataTable_.init())
         return false;
 
     if (!evalCache.init())
         return false;
 
     if (!compressedSourceSet.init())
         return false;
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -34,16 +34,17 @@
 #endif
 #include "js/HashTable.h"
 #include "js/Vector.h"
 #include "vm/CommonPropertyNames.h"
 #include "vm/DateTime.h"
 #include "vm/MallocProvider.h"
 #include "vm/SPSProfiler.h"
 #include "vm/Stack.h"
+#include "vm/Symbol.h"
 #include "vm/ThreadPool.h"
 
 #ifdef _MSC_VER
 #pragma warning(push)
 #pragma warning(disable:4100) /* Silence unreferenced formal parameter warnings */
 #endif
 
 namespace js {
@@ -1149,25 +1150,32 @@ struct JSRuntime : public JS::shadow::Ru
     bool beingDestroyed_;
   public:
     bool isBeingDestroyed() const {
         return beingDestroyed_;
     }
 
   private:
     // Set of all atoms other than those in permanentAtoms and staticStrings.
-    // This may be modified by threads with an ExclusiveContext and requires
-    // a lock.
+    // Reading or writing this set requires the calling thread to have an
+    // ExclusiveContext and hold a lock. Use AutoLockForExclusiveAccess.
     js::AtomSet *atoms_;
 
-    // Compartment and associated zone containing all atoms in the runtime,
-    // as well as runtime wide IonCode stubs. The contents of this compartment
-    // may be modified by threads with an ExclusiveContext and requires a lock.
+    // Compartment and associated zone containing all atoms in the runtime, as
+    // well as runtime wide IonCode stubs. Modifying the contents of this
+    // compartment requires the calling thread to have an ExclusiveContext and
+    // hold a lock. Use AutoLockForExclusiveAccess.
     JSCompartment *atomsCompartment_;
 
+    // Set of all live symbols produced by Symbol.for(). All such symbols are
+    // allocated in the atomsCompartment. Reading or writing the symbol
+    // registry requires the calling thread to have an ExclusiveContext and
+    // hold a lock. Use AutoLockForExclusiveAccess.
+    js::SymbolRegistry symbolRegistry_;
+
   public:
     bool initializeAtoms(JSContext *cx);
     void finishAtoms();
 
     void sweepAtoms();
 
     js::AtomSet &atoms() {
         JS_ASSERT(currentThreadHasExclusiveAccess());
@@ -1182,16 +1190,21 @@ struct JSRuntime : public JS::shadow::Ru
         return comp == atomsCompartment_;
     }
 
     // The atoms compartment is the only one in its zone.
     inline bool isAtomsZone(JS::Zone *zone);
 
     bool activeGCInAtomsZone();
 
+    js::SymbolRegistry &symbolRegistry() {
+        JS_ASSERT(currentThreadHasExclusiveAccess());
+        return symbolRegistry_;
+    }
+
     // Permanent atoms are fixed during initialization of the runtime and are
     // not modified or collected until the runtime is destroyed. These may be
     // shared with another, longer living runtime through |parentRuntime| and
     // can be freely accessed with no locking necessary.
 
     // Permanent atoms pre-allocated for general use.
     js::StaticStrings *staticStrings;
 
--- a/js/src/vm/Symbol.cpp
+++ b/js/src/vm/Symbol.cpp
@@ -13,30 +13,77 @@
 
 #include "jscompartmentinlines.h"
 #include "jsgcinlines.h"
 
 using JS::Symbol;
 using namespace js;
 
 Symbol *
-Symbol::new_(ExclusiveContext *cx, JSString *description)
+Symbol::newInternal(ExclusiveContext *cx, bool inRegistry, JSAtom *description)
+{
+    MOZ_ASSERT(cx->compartment() == cx->atomsCompartment());
+    MOZ_ASSERT(cx->atomsCompartment()->runtimeFromAnyThread()->currentThreadHasExclusiveAccess());
+
+    // Following js::AtomizeString, 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(inRegistry, description);
+}
+
+Symbol *
+Symbol::new_(ExclusiveContext *cx, bool inRegistry, 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());
+    return newInternal(cx, inRegistry, atom);
+}
 
-    // Following AtomizeAndCopyChars, we grudgingly forgo last-ditch GC here.
-    Symbol *p = gc::AllocateNonObject<Symbol, NoGC>(cx);
-    if (!p) {
+Symbol *
+Symbol::for_(js::ExclusiveContext *cx, HandleString description)
+{
+    JSAtom *atom = AtomizeString(cx, description);
+    if (!atom)
+        return nullptr;
+
+    AutoLockForExclusiveAccess lock(cx);
+
+    SymbolRegistry &registry = cx->symbolRegistry();
+    SymbolRegistry::AddPtr p = registry.lookupForAdd(atom);
+    if (p)
+        return *p;
+
+    AutoCompartment ac(cx, cx->atomsCompartment());
+    Symbol *sym = newInternal(cx, true, atom);
+    if (!sym)
+        return nullptr;
+
+    // p is still valid here because we have held the lock since the
+    // lookupForAdd call, and newInternal can't GC.
+    if (!registry.add(p, sym)) {
+        // SystemAllocPolicy does not report OOM.
         js_ReportOutOfMemory(cx);
         return nullptr;
     }
-    return new (p) Symbol(atom);
+    return sym;
 }
+
+void
+SymbolRegistry::sweep()
+{
+    for (Enum e(*this); !e.empty(); e.popFront()) {
+        Symbol *sym = e.front();
+        if (IsSymbolAboutToBeFinalized(&sym))
+            e.removeFront();
+    }
+}
--- a/js/src/vm/Symbol.h
+++ b/js/src/vm/Symbol.h
@@ -4,43 +4,102 @@
  * 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 "jsalloc.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.
+    // This is only a boolean for now, but a later patch in the stack changes
+    // it to an enum.
+    uint32_t inSymbolRegistry_;
     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(bool inRegistry, JSAtom *desc)
+        : inSymbolRegistry_(inRegistry), description_(desc) {}
+
     Symbol(const Symbol &) MOZ_DELETE;
     void operator=(const Symbol &) MOZ_DELETE;
 
+    static Symbol *
+    newInternal(js::ExclusiveContext *cx, bool inRegistry, JSAtom *description);
+
   public:
-    static Symbol *new_(js::ExclusiveContext *cx, JSString *description);
+    static Symbol *new_(js::ExclusiveContext *cx, bool inRegistry, JSString *description);
+    static Symbol *for_(js::ExclusiveContext *cx, js::HandleString description);
 
     JSAtom *description() const { return description_; }
+    bool isInSymbolRegistry() const { return inSymbolRegistry_; }
 
     static inline js::ThingRootKind rootKind() { return js::THING_ROOT_SYMBOL; }
     inline void markChildren(JSTracer *trc);
     inline void finalize(js::FreeOp *) {}
 };
 
 } /* namespace JS */
 
+namespace js {
+
+/* Hash policy used by the SymbolRegistry. */
+struct HashSymbolsByDescription
+{
+    typedef JS::Symbol *Key;
+    typedef JSAtom *Lookup;
+
+    static HashNumber hash(Lookup l) {
+        return HashNumber(reinterpret_cast<uintptr_t>(l));
+    }
+    static bool match(Key sym, Lookup l) {
+        return sym->description() == l;
+    }
+};
+
+/*
+ * Hash table that implements the symbol registry.
+ *
+ * This must be a typedef for the benefit of GCC 4.4.6 (used to build B2G for Ice
+ * Cream Sandwich).
+ */
+typedef HashSet<JS::Symbol *, HashSymbolsByDescription, SystemAllocPolicy> SymbolHashSet;
+
+/*
+ * The runtime-wide symbol registry, used to implement Symbol.for().
+ *
+ * ES6 draft rev 25 (2014 May 22) calls this the GlobalSymbolRegistry List. In
+ * our implementation, it is not global. There is one per JSRuntime. The
+ * symbols in the symbol registry, like all symbols, are allocated in the atoms
+ * compartment and can be directly referenced from any compartment. They are
+ * never shared across runtimes.
+ *
+ * The memory management strategy here is modeled after js::AtomSet. It's like
+ * a WeakSet. The registry itself does not keep any symbols alive; when a
+ * symbol in the registry is collected, the registry entry is removed. No GC
+ * nondeterminism is exposed to scripts, because there is no API for
+ * enumerating the symbol registry, querying its size, etc.
+ */
+class SymbolRegistry : public SymbolHashSet
+{
+  public:
+    SymbolRegistry() : SymbolHashSet() {}
+    void sweep();
+};
+
+} /* namespace js */
+
 #endif /* vm_Symbol_h */