Bug 1073700 - Move getter/setter data out of BaseShape into a new AccessorShape type. r=bhackett
authorJan de Mooij <jdemooij@mozilla.com>
Fri, 10 Oct 2014 11:32:14 +0200
changeset 233060 48ef078126bff7d189691d3d3bea46e3f666747a
parent 233059 5c6980f9caff1c87e94cce5e67d0ef28f7e7d49c
child 233061 3389b00c83bc0d5f53737f4a20230c00d46afd92
push id4187
push userbhearsum@mozilla.com
push dateFri, 28 Nov 2014 15:29:12 +0000
treeherdermozilla-beta@f23cc6a30c11 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbhackett
bugs1073700
milestone35.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 1073700 - Move getter/setter data out of BaseShape into a new AccessorShape type. r=bhackett
js/src/gc/Heap.h
js/src/gc/Marking.cpp
js/src/gc/RootMarking.cpp
js/src/jsgc.cpp
js/src/jsgc.h
js/src/jsgcinlines.h
js/src/jspropertytree.cpp
js/src/jspropertytree.h
js/src/vm/NativeObject.h
js/src/vm/Shape-inl.h
js/src/vm/Shape.cpp
js/src/vm/Shape.h
--- a/js/src/gc/Heap.h
+++ b/js/src/gc/Heap.h
@@ -82,16 +82,17 @@ enum AllocKind {
     FINALIZE_OBJECT12,
     FINALIZE_OBJECT12_BACKGROUND,
     FINALIZE_OBJECT16,
     FINALIZE_OBJECT16_BACKGROUND,
     FINALIZE_OBJECT_LAST = FINALIZE_OBJECT16_BACKGROUND,
     FINALIZE_SCRIPT,
     FINALIZE_LAZY_SCRIPT,
     FINALIZE_SHAPE,
+    FINALIZE_ACCESSOR_SHAPE,
     FINALIZE_BASE_SHAPE,
     FINALIZE_TYPE_OBJECT,
     FINALIZE_FAT_INLINE_STRING,
     FINALIZE_STRING,
     FINALIZE_EXTERNAL_STRING,
     FINALIZE_SYMBOL,
     FINALIZE_JITCODE,
     FINALIZE_LAST = FINALIZE_JITCODE
@@ -114,16 +115,17 @@ MapAllocToTraceKind(AllocKind kind)
         JSTRACE_OBJECT,     /* FINALIZE_OBJECT8_BACKGROUND */
         JSTRACE_OBJECT,     /* FINALIZE_OBJECT12 */
         JSTRACE_OBJECT,     /* FINALIZE_OBJECT12_BACKGROUND */
         JSTRACE_OBJECT,     /* FINALIZE_OBJECT16 */
         JSTRACE_OBJECT,     /* FINALIZE_OBJECT16_BACKGROUND */
         JSTRACE_SCRIPT,     /* FINALIZE_SCRIPT */
         JSTRACE_LAZY_SCRIPT,/* FINALIZE_LAZY_SCRIPT */
         JSTRACE_SHAPE,      /* FINALIZE_SHAPE */
+        JSTRACE_SHAPE,      /* FINALIZE_ACCESSOR_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 */
     };
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -1209,34 +1209,34 @@ ScanShape(GCMarker *gcmarker, Shape *sha
     PushMarkStack(gcmarker, shape->base());
 
     const BarrieredBase<jsid> &id = shape->propidRef();
     if (JSID_IS_STRING(id))
         PushMarkStack(gcmarker, JSID_TO_STRING(id));
     else if (JSID_IS_SYMBOL(id))
         PushMarkStack(gcmarker, JSID_TO_SYMBOL(id));
 
+    if (shape->hasGetterObject())
+        MaybePushMarkStackBetweenSlices(gcmarker, shape->getterObject());
+
+    if (shape->hasSetterObject())
+        MaybePushMarkStackBetweenSlices(gcmarker, shape->setterObject());
+
     shape = shape->previous();
     if (shape && shape->markIfUnmarked(gcmarker->getMarkColor()))
         goto restart;
 }
 
 static inline void
 ScanBaseShape(GCMarker *gcmarker, BaseShape *base)
 {
     base->assertConsistency();
 
     base->compartment()->mark();
 
-    if (base->hasGetterObject())
-        MaybePushMarkStackBetweenSlices(gcmarker, base->getterObject());
-
-    if (base->hasSetterObject())
-        MaybePushMarkStackBetweenSlices(gcmarker, base->setterObject());
-
     if (JSObject *parent = base->getObjectParent()) {
         MaybePushMarkStackBetweenSlices(gcmarker, parent);
     } else if (GlobalObject *global = base->compartment()->unsafeUnbarrieredMaybeGlobal()) {
         PushMarkStack(gcmarker, global);
     }
 
     if (JSObject *metadata = base->getObjectMetadata())
         MaybePushMarkStackBetweenSlices(gcmarker, metadata);
@@ -1422,44 +1422,30 @@ gc::MarkChildren(JSTracer *trc, BaseShap
 {
     base->markChildren(trc);
 }
 
 /*
  * This function is used by the cycle collector to trace through the
  * children of a BaseShape (and its baseUnowned(), if any). The cycle
  * collector does not directly care about BaseShapes, so only the
- * getter, setter, and parent are marked. Furthermore, the parent is
- * marked only if it isn't the same as prevParent, which will be
- * updated to the current shape's parent.
+ * parent is marked. Furthermore, the parent is marked only if it isn't the
+ * same as prevParent, which will be updated to the current shape's parent.
  */
 static inline void
 MarkCycleCollectorChildren(JSTracer *trc, BaseShape *base, JSObject **prevParent)
 {
     MOZ_ASSERT(base);
 
     /*
      * The cycle collector does not need to trace unowned base shapes,
-     * as they have the same getter, setter and parent as the original
-     * base shape.
+     * as they have the same parent as the original base shape.
      */
     base->assertConsistency();
 
-    if (base->hasGetterObject()) {
-        JSObject *tmp = base->getterObject();
-        MarkObjectUnbarriered(trc, &tmp, "getter");
-        MOZ_ASSERT(tmp == base->getterObject());
-    }
-
-    if (base->hasSetterObject()) {
-        JSObject *tmp = base->setterObject();
-        MarkObjectUnbarriered(trc, &tmp, "setter");
-        MOZ_ASSERT(tmp == base->setterObject());
-    }
-
     JSObject *parent = base->getObjectParent();
     if (parent && parent != *prevParent) {
         MarkObjectUnbarriered(trc, &parent, "parent");
         MOZ_ASSERT(parent == base->getObjectParent());
         *prevParent = parent;
     }
 }
 
@@ -1473,16 +1459,29 @@ MarkCycleCollectorChildren(JSTracer *trc
  */
 void
 gc::MarkCycleCollectorChildren(JSTracer *trc, Shape *shape)
 {
     JSObject *prevParent = nullptr;
     do {
         MarkCycleCollectorChildren(trc, shape->base(), &prevParent);
         MarkId(trc, &shape->propidRef(), "propid");
+
+        if (shape->hasGetterObject()) {
+            JSObject *tmp = shape->getterObject();
+            MarkObjectUnbarriered(trc, &tmp, "getter");
+            MOZ_ASSERT(tmp == shape->getterObject());
+        }
+
+        if (shape->hasSetterObject()) {
+            JSObject *tmp = shape->setterObject();
+            MarkObjectUnbarriered(trc, &tmp, "setter");
+            MOZ_ASSERT(tmp == shape->setterObject());
+        }
+
         shape = shape->previous();
     } while (shape);
 }
 
 static void
 ScanTypeObject(GCMarker *gcmarker, types::TypeObject *type)
 {
     unsigned count = type->getPropertyCount();
--- a/js/src/gc/RootMarking.cpp
+++ b/js/src/gc/RootMarking.cpp
@@ -352,17 +352,24 @@ AutoHashableValueRooter::trace(JSTracer 
     MarkValueRoot(trc, reinterpret_cast<Value*>(&value), "AutoHashableValueRooter");
 }
 
 void
 StackShape::trace(JSTracer *trc)
 {
     if (base)
         MarkBaseShapeRoot(trc, (BaseShape**) &base, "StackShape base");
+
     MarkIdRoot(trc, (jsid*) &propid, "StackShape id");
+
+    if ((attrs & JSPROP_GETTER) && rawGetter)
+        MarkObjectRoot(trc, (JSObject**)&rawGetter, "StackShape getter");
+
+    if ((attrs & JSPROP_SETTER) && rawSetter)
+        MarkObjectRoot(trc, (JSObject**)&rawSetter, "StackShape setter");
 }
 
 void
 JSPropertyDescriptor::trace(JSTracer *trc)
 {
     if (obj)
         MarkObjectRoot(trc, &obj, "Descriptor::obj");
     MarkValueRoot(trc, &value, "Descriptor::value");
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -277,16 +277,17 @@ const uint32_t Arena::ThingSizes[] = CHE
     sizeof(JSObject_Slots8),    /* FINALIZE_OBJECT8_BACKGROUND  */
     sizeof(JSObject_Slots12),   /* FINALIZE_OBJECT12            */
     sizeof(JSObject_Slots12),   /* FINALIZE_OBJECT12_BACKGROUND */
     sizeof(JSObject_Slots16),   /* FINALIZE_OBJECT16            */
     sizeof(JSObject_Slots16),   /* FINALIZE_OBJECT16_BACKGROUND */
     sizeof(JSScript),           /* FINALIZE_SCRIPT              */
     sizeof(LazyScript),         /* FINALIZE_LAZY_SCRIPT         */
     sizeof(Shape),              /* FINALIZE_SHAPE               */
+    sizeof(AccessorShape),      /* FINALIZE_ACCESSOR_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             */
 );
@@ -307,16 +308,17 @@ const uint32_t Arena::FirstThingOffsets[
     OFFSET(JSObject_Slots8),    /* FINALIZE_OBJECT8_BACKGROUND  */
     OFFSET(JSObject_Slots12),   /* FINALIZE_OBJECT12            */
     OFFSET(JSObject_Slots12),   /* FINALIZE_OBJECT12_BACKGROUND */
     OFFSET(JSObject_Slots16),   /* FINALIZE_OBJECT16            */
     OFFSET(JSObject_Slots16),   /* FINALIZE_OBJECT16_BACKGROUND */
     OFFSET(JSScript),           /* FINALIZE_SCRIPT              */
     OFFSET(LazyScript),         /* FINALIZE_LAZY_SCRIPT         */
     OFFSET(Shape),              /* FINALIZE_SHAPE               */
+    OFFSET(AccessorShape),      /* FINALIZE_ACCESSOR_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             */
 };
@@ -392,16 +394,17 @@ static const AllocKind BackgroundPhaseOb
 static const AllocKind BackgroundPhaseStringsAndSymbols[] = {
     FINALIZE_FAT_INLINE_STRING,
     FINALIZE_STRING,
     FINALIZE_SYMBOL
 };
 
 static const AllocKind BackgroundPhaseShapes[] = {
     FINALIZE_SHAPE,
+    FINALIZE_ACCESSOR_SHAPE,
     FINALIZE_BASE_SHAPE,
     FINALIZE_TYPE_OBJECT
 };
 
 static const AllocKind * const BackgroundPhases[] = {
     BackgroundPhaseObjects,
     BackgroundPhaseStringsAndSymbols,
     BackgroundPhaseShapes
@@ -618,16 +621,18 @@ FinalizeArenas(FreeOp *fop,
       case FINALIZE_OBJECT16_BACKGROUND:
         return FinalizeTypedArenas<JSObject>(fop, src, dest, thingKind, budget);
       case FINALIZE_SCRIPT:
         return FinalizeTypedArenas<JSScript>(fop, src, dest, thingKind, budget);
       case FINALIZE_LAZY_SCRIPT:
         return FinalizeTypedArenas<LazyScript>(fop, src, dest, thingKind, budget);
       case FINALIZE_SHAPE:
         return FinalizeTypedArenas<Shape>(fop, src, dest, thingKind, budget);
+      case FINALIZE_ACCESSOR_SHAPE:
+        return FinalizeTypedArenas<AccessorShape>(fop, src, dest, thingKind, budget);
       case FINALIZE_BASE_SHAPE:
         return FinalizeTypedArenas<BaseShape>(fop, src, dest, thingKind, budget);
       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);
@@ -2679,16 +2684,17 @@ ArenaLists::queueJitCodeForSweep(FreeOp 
 }
 
 void
 ArenaLists::queueShapesForSweep(FreeOp *fop)
 {
     gcstats::AutoPhase ap(fop->runtime()->gc.stats, gcstats::PHASE_SWEEP_SHAPE);
 
     queueForBackgroundSweep(fop, FINALIZE_SHAPE);
+    queueForBackgroundSweep(fop, FINALIZE_ACCESSOR_SHAPE);
     queueForBackgroundSweep(fop, FINALIZE_BASE_SHAPE);
     queueForBackgroundSweep(fop, FINALIZE_TYPE_OBJECT);
 }
 
 static void *
 RunLastDitchGC(JSContext *cx, JS::Zone *zone, AllocKind thingKind)
 {
     /*
@@ -4876,16 +4882,18 @@ GCRuntime::beginSweepingZoneGroup()
         gcstats::AutoSCC scc(stats, zoneGroupIndex);
         zone->allocator.arenas.queueJitCodeForSweep(&fop);
     }
     for (GCZoneGroupIter zone(rt); !zone.done(); zone.next()) {
         gcstats::AutoSCC scc(stats, zoneGroupIndex);
         zone->allocator.arenas.queueShapesForSweep(&fop);
         zone->allocator.arenas.gcShapeArenasToSweep =
             zone->allocator.arenas.arenaListsToSweep[FINALIZE_SHAPE];
+        zone->allocator.arenas.gcAccessorShapeArenasToSweep =
+            zone->allocator.arenas.arenaListsToSweep[FINALIZE_ACCESSOR_SHAPE];
     }
 
     finalizePhase = 0;
     sweepZone = currentZoneGroup;
     sweepKindIndex = 0;
 
     {
         gcstats::AutoPhase ap(stats, gcstats::PHASE_FINALIZE_END);
@@ -4973,16 +4981,35 @@ ArenaLists::foregroundFinalize(FreeOp *f
 bool
 GCRuntime::drainMarkStack(SliceBudget &sliceBudget, gcstats::Phase phase)
 {
     /* Run a marking slice and return whether the stack is now empty. */
     gcstats::AutoPhase ap(stats, phase);
     return marker.drainMarkStack(sliceBudget);
 }
 
+static bool
+SweepShapes(ArenaHeader **arenasToSweep, size_t thingsPerArena, SliceBudget &sliceBudget)
+{
+    while (ArenaHeader *arena = *arenasToSweep) {
+        for (ArenaCellIterUnderGC i(arena); !i.done(); i.next()) {
+            Shape *shape = i.get<Shape>();
+            if (!shape->isMarked())
+                shape->sweep();
+        }
+
+        *arenasToSweep = arena->next;
+        sliceBudget.step(thingsPerArena);
+        if (sliceBudget.isOverBudget())
+            return false;  /* Yield to the mutator. */
+    }
+
+    return true;
+}
+
 bool
 GCRuntime::sweepPhase(SliceBudget &sliceBudget)
 {
     gcstats::AutoPhase ap(stats, gcstats::PHASE_SWEEP);
     FreeOp fop(rt);
 
     bool finished = drainMarkStack(sliceBudget, gcstats::PHASE_SWEEP_MARK);
     if (!finished)
@@ -5018,27 +5045,27 @@ GCRuntime::sweepPhase(SliceBudget &slice
         }
 
         /* Remove dead shapes from the shape tree, but don't finalize them yet. */
         {
             gcstats::AutoPhase ap(stats, gcstats::PHASE_SWEEP_SHAPE);
 
             for (; sweepZone; sweepZone = sweepZone->nextNodeInGroup()) {
                 Zone *zone = sweepZone;
-                while (ArenaHeader *arena = zone->allocator.arenas.gcShapeArenasToSweep) {
-                    for (ArenaCellIterUnderGC i(arena); !i.done(); i.next()) {
-                        Shape *shape = i.get<Shape>();
-                        if (!shape->isMarked())
-                            shape->sweep();
-                    }
-
-                    zone->allocator.arenas.gcShapeArenasToSweep = arena->next;
-                    sliceBudget.step(Arena::thingsPerArena(Arena::thingSize(FINALIZE_SHAPE)));
-                    if (sliceBudget.isOverBudget())
-                        return false;  /* Yield to the mutator. */
+                if (!SweepShapes(&zone->allocator.arenas.gcShapeArenasToSweep,
+                                 Arena::thingsPerArena(Arena::thingSize(FINALIZE_SHAPE)),
+                                 sliceBudget))
+                {
+                    return false;  /* Yield to the mutator. */
+                }
+                if (!SweepShapes(&zone->allocator.arenas.gcAccessorShapeArenasToSweep,
+                                 Arena::thingsPerArena(Arena::thingSize(FINALIZE_ACCESSOR_SHAPE)),
+                                 sliceBudget))
+                {
+                    return false;  /* Yield to the mutator. */
                 }
             }
         }
 
         endSweepingZoneGroup();
         getNextZoneGroup();
         if (!currentZoneGroup)
             return true;  /* We're finished. */
--- a/js/src/jsgc.h
+++ b/js/src/jsgc.h
@@ -57,16 +57,17 @@ enum State {
 const char *
 TraceKindAsAscii(JSGCTraceKind kind);
 
 /* Map from C++ type to alloc kind. JSObject does not have a 1:1 mapping, so must use Arena::thingSize. */
 template <typename T> struct MapTypeToFinalizeKind {};
 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<AccessorShape>     { static const AllocKind kind = FINALIZE_ACCESSOR_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; };
 
@@ -86,16 +87,17 @@ IsNurseryAllocable(AllocKind kind)
         true,      /* FINALIZE_OBJECT8_BACKGROUND */
         false,     /* FINALIZE_OBJECT12 */
         true,      /* FINALIZE_OBJECT12_BACKGROUND */
         false,     /* FINALIZE_OBJECT16 */
         true,      /* FINALIZE_OBJECT16_BACKGROUND */
         false,     /* FINALIZE_SCRIPT */
         false,     /* FINALIZE_LAZY_SCRIPT */
         false,     /* FINALIZE_SHAPE */
+        false,     /* FINALIZE_ACCESSOR_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 */
     };
@@ -123,16 +125,17 @@ IsFJNurseryAllocable(AllocKind kind)
         true,      /* FINALIZE_OBJECT8_BACKGROUND */
         false,     /* FINALIZE_OBJECT12 */
         true,      /* FINALIZE_OBJECT12_BACKGROUND */
         false,     /* FINALIZE_OBJECT16 */
         true,      /* FINALIZE_OBJECT16_BACKGROUND */
         false,     /* FINALIZE_SCRIPT */
         false,     /* FINALIZE_LAZY_SCRIPT */
         false,     /* FINALIZE_SHAPE */
+        false,     /* FINALIZE_ACCESSOR_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 */
     };
@@ -156,16 +159,17 @@ IsBackgroundFinalized(AllocKind kind)
         true,      /* FINALIZE_OBJECT8_BACKGROUND */
         false,     /* FINALIZE_OBJECT12 */
         true,      /* FINALIZE_OBJECT12_BACKGROUND */
         false,     /* FINALIZE_OBJECT16 */
         true,      /* FINALIZE_OBJECT16_BACKGROUND */
         false,     /* FINALIZE_SCRIPT */
         false,     /* FINALIZE_LAZY_SCRIPT */
         true,      /* FINALIZE_SHAPE */
+        true,      /* FINALIZE_ACCESSOR_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 */
     };
@@ -610,27 +614,29 @@ class ArenaLists
     ArenaHeader *arenaListsToSweep[FINALIZE_LIMIT];
 
     /* During incremental sweeping, a list of the arenas already swept. */
     unsigned incrementalSweptArenaKind;
     ArenaList incrementalSweptArenas;
 
     /* Shape arenas to be swept in the foreground. */
     ArenaHeader *gcShapeArenasToSweep;
+    ArenaHeader *gcAccessorShapeArenasToSweep;
 
   public:
     ArenaLists() {
         for (size_t i = 0; i != FINALIZE_LIMIT; ++i)
             freeLists[i].initAsEmpty();
         for (size_t i = 0; i != FINALIZE_LIMIT; ++i)
             backgroundFinalizeState[i] = BFS_DONE;
         for (size_t i = 0; i != FINALIZE_LIMIT; ++i)
             arenaListsToSweep[i] = nullptr;
         incrementalSweptArenaKind = FINALIZE_LIMIT;
         gcShapeArenasToSweep = nullptr;
+        gcAccessorShapeArenasToSweep = nullptr;
     }
 
     ~ArenaLists() {
         for (size_t i = 0; i != FINALIZE_LIMIT; ++i) {
             /*
              * We can only call this during the shutdown after the last GC when
              * the background finalization is disabled.
              */
--- a/js/src/jsgcinlines.h
+++ b/js/src/jsgcinlines.h
@@ -733,36 +733,42 @@ NewGCFatInlineString(js::ThreadSafeConte
 }
 
 inline JSExternalString *
 NewGCExternalString(js::ThreadSafeContext *cx)
 {
     return js::gc::AllocateNonObject<JSExternalString, js::CanGC>(cx);
 }
 
+inline Shape *
+NewGCShape(ThreadSafeContext *cx)
+{
+    return gc::AllocateNonObject<Shape, CanGC>(cx);
+}
+
+inline Shape *
+NewGCAccessorShape(ThreadSafeContext *cx)
+{
+    return gc::AllocateNonObject<AccessorShape, CanGC>(cx);
+}
+
 } /* namespace js */
 
 inline JSScript *
 js_NewGCScript(js::ThreadSafeContext *cx)
 {
     return js::gc::AllocateNonObject<JSScript, js::CanGC>(cx);
 }
 
 inline js::LazyScript *
 js_NewGCLazyScript(js::ThreadSafeContext *cx)
 {
     return js::gc::AllocateNonObject<js::LazyScript, js::CanGC>(cx);
 }
 
-inline js::Shape *
-js_NewGCShape(js::ThreadSafeContext *cx)
-{
-    return js::gc::AllocateNonObject<js::Shape, js::CanGC>(cx);
-}
-
 template <js::AllowGC allowGC>
 inline js::BaseShape *
 js_NewGCBaseShape(js::ThreadSafeContext *cx)
 {
     return js::gc::AllocateNonObject<js::BaseShape, allowGC>(cx);
 }
 
 #endif /* jsgcinlines_h */
--- a/js/src/jspropertytree.cpp
+++ b/js/src/jspropertytree.cpp
@@ -26,25 +26,16 @@ ShapeHasher::hash(const Lookup &l)
 }
 
 inline bool
 ShapeHasher::match(const Key k, const Lookup &l)
 {
     return k->matches(l);
 }
 
-Shape *
-PropertyTree::newShape(ExclusiveContext *cx)
-{
-    Shape *shape = js_NewGCShape(cx);
-    if (!shape)
-        js_ReportOutOfMemory(cx);
-    return shape;
-}
-
 static KidsHash *
 HashChildren(Shape *kid1, Shape *kid2)
 {
     KidsHash *hash = js_new<KidsHash>();
     if (!hash || !hash->init(2)) {
         js_delete(hash);
         return nullptr;
     }
@@ -108,19 +99,25 @@ Shape::removeChild(Shape *child)
         kidp->setNull();
         child->parent = nullptr;
         return;
     }
 
     KidsHash *hash = kidp->toHash();
     MOZ_ASSERT(hash->count() >= 2);      /* otherwise kidp->isShape() should be true */
 
+#ifdef DEBUG
+    size_t oldCount = hash->count();
+#endif
+
     hash->remove(StackShape(child));
     child->parent = nullptr;
 
+    MOZ_ASSERT(hash->count() == oldCount - 1);
+
     if (hash->count() == 1) {
         /* Convert from HASH form back to SHAPE form. */
         KidsHash::Range r = hash->all();
         Shape *otherChild = r.front();
         MOZ_ASSERT((r.popFront(), r.empty()));    /* No more elements! */
         kidp->setShape(otherChild);
         js_delete(hash);
     }
@@ -179,24 +176,20 @@ PropertyTree::getChild(ExclusiveContext 
             JS::UnmarkGrayGCThingRecursively(existingShape, JSTRACE_SHAPE);
         }
     }
 #endif
 
     if (existingShape)
         return existingShape;
 
-    RootedGeneric<StackShape*> child(cx, &unrootedChild);
-
-    Shape *shape = newShape(cx);
+    Shape *shape = Shape::new_(cx, unrootedChild, parent->numFixedSlots());
     if (!shape)
         return nullptr;
 
-    new (shape) Shape(*child, parent->numFixedSlots());
-
     if (!insertChild(cx, parent, shape))
         return nullptr;
 
     return shape;
 }
 
 Shape *
 PropertyTree::lookupChild(ThreadSafeContext *cx, Shape *parent, const StackShape &child)
@@ -282,18 +275,20 @@ Shape::fixupDictionaryShapeAfterMovingGC
     // touch it again.
     if (IsInsideNursery(reinterpret_cast<Cell *>(listp))) {
         listp = nullptr;
         return;
     }
 
     MOZ_ASSERT(!IsInsideNursery(reinterpret_cast<Cell *>(listp)));
     AllocKind kind = TenuredCell::fromPointer(listp)->getAllocKind();
-    MOZ_ASSERT(kind == FINALIZE_SHAPE || kind <= FINALIZE_OBJECT_LAST);
-    if (kind == FINALIZE_SHAPE) {
+    MOZ_ASSERT(kind == FINALIZE_SHAPE ||
+               kind == FINALIZE_ACCESSOR_SHAPE ||
+               kind <= FINALIZE_OBJECT_LAST);
+    if (kind == FINALIZE_SHAPE || kind == FINALIZE_ACCESSOR_SHAPE) {
         // listp points to the parent field of the next shape.
         Shape *next = reinterpret_cast<Shape *>(uintptr_t(listp) -
                                                 offsetof(Shape, parent));
         listp = &gc::MaybeForwarded(next)->parent;
     } else {
         // listp points to the shape_ field of an object.
         JSObject *last = reinterpret_cast<JSObject *>(uintptr_t(listp) -
                                                       offsetof(JSObject, shape_));
@@ -312,46 +307,83 @@ Shape::fixupShapeTreeAfterMovingGC()
             kids.setShape(gc::Forwarded(kids.toShape()));
         return;
     }
 
     MOZ_ASSERT(kids.isHash());
     KidsHash *kh = kids.toHash();
     for (KidsHash::Enum e(*kh); !e.empty(); e.popFront()) {
         Shape *key = e.front();
-        if (!IsForwarded(key))
-            continue;
+        if (IsForwarded(key))
+            key = Forwarded(key);
 
-        key = Forwarded(key);
         BaseShape *base = key->base();
         if (IsForwarded(base))
             base = Forwarded(base);
         UnownedBaseShape *unowned = base->unowned();
         if (IsForwarded(unowned))
             unowned = Forwarded(unowned);
+
+        PropertyOp getter = key->getter();
+        if (key->hasGetterObject() && IsForwarded(key->getterObject()))
+            getter = PropertyOp(Forwarded(key->getterObject()));
+
+        StrictPropertyOp setter = key->setter();
+        if (key->hasSetterObject() && IsForwarded(key->setterObject()))
+            setter = StrictPropertyOp(Forwarded(key->setterObject()));
+
         StackShape lookup(unowned,
                           const_cast<Shape *>(key)->propidRef(),
                           key->slotInfo & Shape::SLOT_MASK,
                           key->attrs,
                           key->flags);
+        lookup.updateGetterSetter(getter, setter);
         e.rekeyFront(lookup, key);
     }
 }
 
 void
 Shape::fixupAfterMovingGC()
 {
     if (inDictionary())
         fixupDictionaryShapeAfterMovingGC();
     else
         fixupShapeTreeAfterMovingGC();
 }
 
 #endif // JSGC_COMPACTING
 
+#ifdef JSGC_GENERATIONAL
+void
+ShapeGetterSetterRef::mark(JSTracer *trc)
+{
+    // Update the current shape's entry in the parent KidsHash table if needed.
+    // This is necessary as the computed hash includes the getter/setter
+    // pointers.
+
+    JSObject *obj = *objp;
+    JSObject *prior = obj;
+    trc->setTracingLocation(&*prior);
+    gc::Mark(trc, &obj, "AccessorShape getter or setter");
+    if (obj == *objp)
+        return;
+
+    Shape *parent = shape->parent;
+    if (shape->inDictionary() || !parent->kids.isHash()) {
+        *objp = obj;
+        return;
+    }
+
+    KidsHash *kh = parent->kids.toHash();
+    kh->remove(StackShape(shape));
+    *objp = obj;
+    MOZ_ALWAYS_TRUE(kh->putNew(StackShape(shape), shape));
+}
+#endif
+
 #ifdef DEBUG
 
 void
 KidsPointer::checkConsistency(Shape *aKid) const
 {
     if (isShape()) {
         MOZ_ASSERT(toShape() == aKid);
     } else {
@@ -380,18 +412,18 @@ Shape::dump(JSContext *cx, FILE *fp) con
         else
             fputs("<error>", fp);
     } else {
         MOZ_ASSERT(JSID_IS_SYMBOL(propid));
         JSID_TO_SYMBOL(propid)->dump(fp);
     }
 
     fprintf(fp, " g/s %p/%p slot %d attrs %x ",
-            JS_FUNC_TO_DATA_PTR(void *, base()->rawGetter),
-            JS_FUNC_TO_DATA_PTR(void *, base()->rawSetter),
+            JS_FUNC_TO_DATA_PTR(void *, getter()),
+            JS_FUNC_TO_DATA_PTR(void *, setter()),
             hasSlot() ? slot() : -1, attrs);
 
     if (attrs) {
         int first = 1;
         fputs("(", fp);
 #define DUMP_ATTR(name, display) if (attrs & JSPROP_##name) fputs(&(" " #display)[first], fp), first = 0
         DUMP_ATTR(ENUMERATE, enumerate);
         DUMP_ATTR(READONLY, readonly);
--- a/js/src/jspropertytree.h
+++ b/js/src/jspropertytree.h
@@ -91,16 +91,15 @@ class PropertyTree
 
     explicit PropertyTree(JSCompartment *comp)
         : compartment_(comp)
     {
     }
 
     JSCompartment *compartment() { return compartment_; }
 
-    Shape *newShape(ExclusiveContext *cx);
     Shape *getChild(ExclusiveContext *cx, Shape *parent, StackShape &child);
     Shape *lookupChild(ThreadSafeContext *cx, Shape *parent, const StackShape &child);
 };
 
 } /* namespace js */
 
 #endif /* jspropertytree_h */
--- a/js/src/vm/NativeObject.h
+++ b/js/src/vm/NativeObject.h
@@ -402,17 +402,18 @@ class NativeObject : public JSObject
 #ifdef DEBUG
     void checkShapeConsistency();
 #else
     void checkShapeConsistency() { }
 #endif
 
     Shape *
     replaceWithNewEquivalentShape(ThreadSafeContext *cx,
-                                  Shape *existingShape, Shape *newShape = nullptr);
+                                  Shape *existingShape, Shape *newShape = nullptr,
+                                  bool accessorShape = false);
 
     /*
      * Remove the last property of an object, provided that it is safe to do so
      * (the shape and previous shape do not carry conflicting information about
      * the object itself).
      */
     inline void removeLastProperty(ExclusiveContext *cx);
     inline bool canRemoveLastProperty();
--- a/js/src/vm/Shape-inl.h
+++ b/js/src/vm/Shape-inl.h
@@ -25,18 +25,16 @@ namespace js {
 
 inline
 StackBaseShape::StackBaseShape(ThreadSafeContext *cx, const Class *clasp,
                                JSObject *parent, JSObject *metadata, uint32_t objectFlags)
   : flags(objectFlags),
     clasp(clasp),
     parent(parent),
     metadata(metadata),
-    rawGetter(nullptr),
-    rawSetter(nullptr),
     compartment(cx->compartment_)
 {}
 
 inline bool
 Shape::get(JSContext* cx, HandleObject receiver, JSObject* obj, JSObject *pobj,
            MutableHandleValue vp)
 {
     MOZ_ASSERT(!hasDefaultGetter());
@@ -148,16 +146,34 @@ Shape::search(ExclusiveContext *cx, Shap
     for (Shape *shape = start; shape; shape = shape->parent) {
         if (shape->propidRef() == id)
             return shape;
     }
 
     return nullptr;
 }
 
+inline Shape *
+Shape::new_(ExclusiveContext *cx, StackShape &unrootedOther, uint32_t nfixed)
+{
+    RootedGeneric<StackShape*> other(cx, &unrootedOther);
+    Shape *shape = other->isAccessorShape() ? NewGCAccessorShape(cx) : NewGCShape(cx);
+    if (!shape) {
+        js_ReportOutOfMemory(cx);
+        return nullptr;
+    }
+
+    if (other->isAccessorShape())
+        new (shape) AccessorShape(*other, nfixed);
+    else
+        new (shape) Shape(*other, nfixed);
+
+    return shape;
+}
+
 template<class ObjectSubclass>
 /* static */ inline bool
 EmptyShape::ensureInitialCustomShape(ExclusiveContext *cx, Handle<ObjectSubclass*> obj)
 {
     static_assert(mozilla::IsBaseOf<JSObject, ObjectSubclass>::value,
                   "ObjectSubclass must be a subclass of JSObject");
 
     // If the provided object has a non-empty shape, it was given the cached
--- a/js/src/vm/Shape.cpp
+++ b/js/src/vm/Shape.cpp
@@ -380,17 +380,17 @@ NativeObject::getChildPropertyOnDictiona
         }
     }
 
     RootedShape shape(cx);
 
     if (obj->inDictionaryMode()) {
         MOZ_ASSERT(parent == obj->lastProperty());
         RootedGeneric<StackShape*> childRoot(cx, &child);
-        shape = js_NewGCShape(cx);
+        shape = childRoot->isAccessorShape() ? NewGCAccessorShape(cx) : NewGCShape(cx);
         if (!shape)
             return nullptr;
         if (childRoot->hasSlot() && childRoot->slot() >= obj->lastProperty()->base()->slotSpan()) {
             if (!setSlotSpan(cx, obj, childRoot->slot() + 1))
                 return nullptr;
         }
         shape->initDictionaryShape(*childRoot, obj->numFixedSlots(), &obj->shape_);
     }
@@ -462,17 +462,17 @@ js::NativeObject::toDictionaryMode(Threa
     // creating the dictionary will get the wrong slot span for this object.
     RootedShape root(cx);
     RootedShape dictionaryShape(cx);
 
     RootedShape shape(cx, lastProperty());
     while (shape) {
         MOZ_ASSERT(!shape->inDictionary());
 
-        Shape *dprop = js_NewGCShape(cx);
+        Shape *dprop = shape->isAccessorShape() ? NewGCAccessorShape(cx) : NewGCShape(cx);
         if (!dprop) {
             js_ReportOutOfMemory(cx);
             return false;
         }
 
         HeapPtrShape *listp = dictionaryShape ? &dictionaryShape->parent : nullptr;
         StackShape child(shape);
         dprop->initDictionaryShape(child, self->numFixedSlots(), listp);
@@ -621,29 +621,28 @@ NativeObject::addPropertyInternal(typena
     RootedShape shape(cx);
     {
         RootedShape last(cx, obj->lastProperty());
 
         uint32_t index;
         bool indexed = js_IdIsIndex(id, &index);
 
         Rooted<UnownedBaseShape*> nbase(cx);
-        if (last->base()->matchesGetterSetter(getter, setter) && !indexed) {
+        if (!indexed) {
             nbase = last->base()->unowned();
         } else {
             StackBaseShape base(last->base());
-            base.updateGetterSetter(attrs, getter, setter);
-            if (indexed)
-                base.flags |= BaseShape::INDEXED;
+            base.flags |= BaseShape::INDEXED;
             nbase = GetOrLookupUnownedBaseShape<mode>(cx, base);
             if (!nbase)
                 return nullptr;
         }
 
         StackShape child(nbase, id, slot, attrs, flags);
+        child.updateGetterSetter(getter, setter);
         shape = getOrLookupChildProperty<mode>(cx, obj, last, child);
     }
 
     if (shape) {
         MOZ_ASSERT(shape == obj->lastProperty());
 
         if (table) {
             /* Store the tree node pointer in the table entry for id. */
@@ -840,29 +839,28 @@ NativeObject::putProperty(typename Execu
     if (!(attrs & JSPROP_SHARED) && slot == SHAPE_INVALID_SLOT && hadSlot)
         slot = oldSlot;
 
     Rooted<UnownedBaseShape*> nbase(cx);
     {
         uint32_t index;
         bool indexed = js_IdIsIndex(id, &index);
         StackBaseShape base(obj->lastProperty()->base());
-        base.updateGetterSetter(attrs, getter, setter);
         if (indexed)
             base.flags |= BaseShape::INDEXED;
         nbase = GetOrLookupUnownedBaseShape<mode>(cx, base);
         if (!nbase)
             return nullptr;
     }
 
     /*
      * Now that we've possibly preserved slot, check whether all members match.
      * If so, this is a redundant "put" and we can return without more work.
      */
-    if (shape->matchesParamsAfterId(nbase, slot, attrs, flags))
+    if (shape->matchesParamsAfterId(nbase, slot, attrs, flags, getter, setter))
         return shape;
 
     /*
      * Overwriting a non-last property requires switching to dictionary mode.
      * The shape tree is shared immutable, and we can't removeProperty and then
      * addPropertyInternal because a failure under add would lose data.
      */
     if (shape != obj->lastProperty() && !obj->inDictionaryMode()) {
@@ -877,17 +875,18 @@ NativeObject::putProperty(typename Execu
     if (obj->inDictionaryMode()) {
         /*
          * Updating some property in a dictionary-mode object. Create a new
          * shape for the existing property, and also generate a new shape for
          * the last property of the dictionary (unless the modified property
          * is also the last property).
          */
         bool updateLast = (shape == obj->lastProperty());
-        shape = obj->replaceWithNewEquivalentShape(cx, shape);
+        bool accessorShape = getter || setter || (attrs & (JSPROP_GETTER | JSPROP_SETTER));
+        shape = obj->replaceWithNewEquivalentShape(cx, shape, nullptr, accessorShape);
         if (!shape)
             return nullptr;
         if (!updateLast && !obj->generateOwnShape(cx))
             return nullptr;
 
         /* FIXME bug 593129 -- slot allocation and JSObject *this must move out of here! */
         if (slot == SHAPE_INVALID_SLOT && !(attrs & JSPROP_SHARED)) {
             if (!allocSlot(cx, obj, &slot))
@@ -898,33 +897,45 @@ NativeObject::putProperty(typename Execu
             shape->base()->adoptUnowned(nbase);
         else
             shape->base_ = nbase;
 
         MOZ_ASSERT_IF(attrs & (JSPROP_GETTER | JSPROP_SETTER), attrs & JSPROP_SHARED);
 
         shape->setSlot(slot);
         shape->attrs = uint8_t(attrs);
-        shape->flags = flags | Shape::IN_DICTIONARY;
+        shape->flags = flags | Shape::IN_DICTIONARY | (accessorShape ? Shape::ACCESSOR_SHAPE : 0);
+        if (shape->isAccessorShape()) {
+            AccessorShape &accShape = shape->asAccessorShape();
+            accShape.rawGetter = getter;
+            if (accShape.hasGetterObject())
+                GetterSetterWriteBarrierPost(&accShape, &accShape.getterObj);
+            accShape.rawSetter = setter;
+            if (accShape.hasSetterObject())
+                GetterSetterWriteBarrierPost(&accShape, &accShape.setterObj);
+        } else {
+            MOZ_ASSERT(!getter);
+            MOZ_ASSERT(!setter);
+        }
     } else {
         /*
          * Updating the last property in a non-dictionary-mode object. Find an
          * alternate shared child of the last property's previous shape.
          */
         StackBaseShape base(obj->lastProperty()->base());
-        base.updateGetterSetter(attrs, getter, setter);
 
         UnownedBaseShape *nbase = GetOrLookupUnownedBaseShape<mode>(cx, base);
         if (!nbase)
             return nullptr;
 
         MOZ_ASSERT(shape == obj->lastProperty());
 
         /* Find or create a property tree node labeled by our arguments. */
         StackShape child(nbase, id, slot, attrs, flags);
+        child.updateGetterSetter(getter, setter);
         RootedShape parent(cx, shape->parent);
         Shape *newShape = getOrLookupChildProperty<mode>(cx, obj, parent, child);
 
         if (!newShape) {
             obj->checkShapeConsistency();
             return nullptr;
         }
 
@@ -1048,30 +1059,30 @@ NativeObject::removeProperty(ExclusiveCo
      * If in dictionary mode, get a new shape for the last property after the
      * removal. We need a fresh shape for all dictionary deletions, even of
      * the last property. Otherwise, a shape could replay and caches might
      * return deleted DictionaryShapes! See bug 595365. Do this before changing
      * the object or table, so the remaining removal is infallible.
      */
     RootedShape spare(cx);
     if (self->inDictionaryMode()) {
-        spare = js_NewGCShape(cx);
+        /* For simplicity, always allocate an accessor shape for now. */
+        spare = NewGCAccessorShape(cx);
         if (!spare)
             return false;
         new (spare) Shape(shape->base()->unowned(), 0);
         if (shape == self->lastProperty()) {
             /*
              * Get an up to date unowned base shape for the new last property
              * when removing the dictionary's last property. Information in
              * base shapes for non-last properties may be out of sync with the
              * object's state.
              */
             RootedShape previous(cx, self->lastProperty()->parent);
             StackBaseShape base(self->lastProperty()->base());
-            base.updateGetterSetter(previous->attrs, previous->getter(), previous->setter());
             BaseShape *nbase = BaseShape::getUnowned(cx, base);
             if (!nbase)
                 return false;
             previous->base_ = nbase;
         }
     }
 
     /* If shape has a slot, free its slot number. */
@@ -1183,17 +1194,18 @@ NativeObject::rollbackProperties(Exclusi
         if (!obj->removeProperty(cx, obj->lastProperty()->propid()))
             return false;
     }
 
     return true;
 }
 
 Shape *
-NativeObject::replaceWithNewEquivalentShape(ThreadSafeContext *cx, Shape *oldShape, Shape *newShape)
+NativeObject::replaceWithNewEquivalentShape(ThreadSafeContext *cx, Shape *oldShape, Shape *newShape,
+                                            bool accessorShape)
 {
     MOZ_ASSERT(cx->isThreadLocal(this));
     MOZ_ASSERT(cx->isThreadLocal(oldShape));
     MOZ_ASSERT(cx->isInsideCurrentCompartment(oldShape));
     MOZ_ASSERT_IF(oldShape != lastProperty(),
                   inDictionaryMode() &&
                   ((cx->isExclusiveContext()
                     ? lookup(cx->asExclusiveContext(), oldShape->propidRef())
@@ -1209,17 +1221,19 @@ NativeObject::replaceWithNewEquivalentSh
         oldShape = selfRoot->lastProperty();
         self = selfRoot;
         newShape = newRoot;
     }
 
     if (!newShape) {
         RootedNativeObject selfRoot(cx, self);
         RootedShape oldRoot(cx, oldShape);
-        newShape = js_NewGCShape(cx);
+        newShape = (oldShape->isAccessorShape() || accessorShape)
+                   ? NewGCAccessorShape(cx)
+                   : NewGCShape(cx);
         if (!newShape)
             return nullptr;
         new (newShape) Shape(oldRoot->base()->unowned(), 0);
         self = selfRoot;
         oldShape = oldRoot;
     }
 
     ShapeTable &table = self->lastProperty()->table();
@@ -1425,51 +1439,36 @@ Shape::setObjectFlag(ExclusiveContext *c
 
 /* static */ inline HashNumber
 StackBaseShape::hash(const StackBaseShape *base)
 {
     HashNumber hash = base->flags;
     hash = RotateLeft(hash, 4) ^ (uintptr_t(base->clasp) >> 3);
     hash = RotateLeft(hash, 4) ^ (uintptr_t(base->parent) >> 3);
     hash = RotateLeft(hash, 4) ^ (uintptr_t(base->metadata) >> 3);
-    hash = RotateLeft(hash, 4) ^ uintptr_t(base->rawGetter);
-    hash = RotateLeft(hash, 4) ^ uintptr_t(base->rawSetter);
     return hash;
 }
 
 /* static */ inline bool
 StackBaseShape::match(UnownedBaseShape *key, const StackBaseShape *lookup)
 {
     return key->flags == lookup->flags
         && key->clasp_ == lookup->clasp
         && key->parent == lookup->parent
-        && key->metadata == lookup->metadata
-        && key->rawGetter == lookup->rawGetter
-        && key->rawSetter == lookup->rawSetter;
+        && key->metadata == lookup->metadata;
 }
 
 void
 StackBaseShape::trace(JSTracer *trc)
 {
-    if (parent) {
-        gc::MarkObjectRoot(trc, (JSObject**)&parent,
-                           "StackBaseShape parent");
-    }
-    if (metadata) {
-        gc::MarkObjectRoot(trc, (JSObject**)&metadata,
-                           "StackBaseShape metadata");
-    }
-    if ((flags & BaseShape::HAS_GETTER_OBJECT) && rawGetter) {
-        gc::MarkObjectRoot(trc, (JSObject**)&rawGetter,
-                           "StackBaseShape getter");
-    }
-    if ((flags & BaseShape::HAS_SETTER_OBJECT) && rawSetter) {
-        gc::MarkObjectRoot(trc, (JSObject**)&rawSetter,
-                           "StackBaseShape setter");
-    }
+    if (parent)
+        gc::MarkObjectRoot(trc, (JSObject**)&parent, "StackBaseShape parent");
+
+    if (metadata)
+        gc::MarkObjectRoot(trc, (JSObject**)&metadata, "StackBaseShape metadata");
 }
 
 /* static */ UnownedBaseShape*
 BaseShape::getUnowned(ExclusiveContext *cx, StackBaseShape &base)
 {
     BaseShapeSet &table = cx->compartment()->baseShapes;
 
     if (!table.initialized() && !table.init())
@@ -1508,20 +1507,16 @@ BaseShape::lookupUnowned(ThreadSafeConte
 }
 
 void
 BaseShape::assertConsistency()
 {
 #ifdef DEBUG
     if (isOwned()) {
         UnownedBaseShape *unowned = baseUnowned();
-        MOZ_ASSERT(hasGetterObject() == unowned->hasGetterObject());
-        MOZ_ASSERT(hasSetterObject() == unowned->hasSetterObject());
-        MOZ_ASSERT_IF(hasGetterObject(), getterObject() == unowned->getterObject());
-        MOZ_ASSERT_IF(hasSetterObject(), setterObject() == unowned->setterObject());
         MOZ_ASSERT(getObjectParent() == unowned->getObjectParent());
         MOZ_ASSERT(getObjectMetadata() == unowned->getObjectMetadata());
         MOZ_ASSERT(getObjectFlags() == unowned->getObjectFlags());
     }
 #endif
 }
 
 void
@@ -1693,16 +1688,29 @@ JSCompartment::checkInitialShapesTableAf
                                          shape->getObjectFlags());
         InitialShapeSet::Ptr ptr = initialShapes.lookup(lookup);
         MOZ_ASSERT(ptr.found() && &*ptr == &e.front());
     }
 }
 
 #endif // JSGC_HASH_TABLE_CHECKS
 
+Shape *
+EmptyShape::new_(ExclusiveContext *cx, Handle<UnownedBaseShape *> base, uint32_t nfixed)
+{
+    Shape *shape = NewGCShape(cx);
+    if (!shape) {
+        js_ReportOutOfMemory(cx);
+        return nullptr;
+    }
+
+    new (shape) EmptyShape(base, nfixed);
+    return shape;
+}
+
 /* static */ Shape *
 EmptyShape::getInitialShape(ExclusiveContext *cx, const Class *clasp, TaggedProto proto,
                             JSObject *parent, JSObject *metadata,
                             size_t nfixed, uint32_t objectFlags)
 {
     MOZ_ASSERT_IF(proto.isObject(), cx->isInsideCurrentCompartment(proto.toObject()));
     MOZ_ASSERT_IF(parent, cx->isInsideCurrentCompartment(parent));
 
@@ -1721,20 +1729,19 @@ EmptyShape::getInitialShape(ExclusiveCon
     RootedObject parentRoot(cx, parent);
     RootedObject metadataRoot(cx, metadata);
 
     StackBaseShape base(cx, clasp, parent, metadata, objectFlags);
     Rooted<UnownedBaseShape*> nbase(cx, BaseShape::getUnowned(cx, base));
     if (!nbase)
         return nullptr;
 
-    Shape *shape = cx->compartment()->propertyTree.newShape(cx);
+    Shape *shape = EmptyShape::new_(cx, nbase, nfixed);
     if (!shape)
         return nullptr;
-    new (shape) EmptyShape(nbase, nfixed);
 
     Lookup lookup(clasp, protoRoot, parentRoot, metadataRoot, nfixed, objectFlags);
     if (!p.add(cx, table, lookup, InitialShapeEntry(ReadBarrieredShape(shape), protoRoot)))
         return nullptr;
 
 #ifdef JSGC_GENERATIONAL
     if (cx->isJSContext()) {
         if ((protoRoot.isObject() && IsInsideNursery(protoRoot.toObject())) ||
--- a/js/src/vm/Shape.h
+++ b/js/src/vm/Shape.h
@@ -93,16 +93,19 @@
  * and it is attached to the last Shape in the lineage. Shape tables for
  * property tree Shapes never change, but shape tables for dictionary mode
  * Shapes can grow and shrink.
  *
  * There used to be a long, math-heavy comment here explaining why property
  * trees are more space-efficient than alternatives.  This was removed in bug
  * 631138; see that bug for the full details.
  *
+ * For getters/setters, an AccessorShape is allocated. This is a slightly fatter
+ * type with extra fields for the getter/setter data.
+ *
  * Because many Shapes have similar data, there is actually a secondary type
  * called a BaseShape that holds some of a Shape's data.  Many shapes can share
  * a single BaseShape.
  */
 
 #define JSSLOT_FREE(clasp)  JSCLASS_RESERVED_SLOTS(clasp)
 
 namespace js {
@@ -246,34 +249,52 @@ struct ShapeTable {
  * may change when the object has an established property lineage. On such
  * changes the entire property lineage is not updated, but rather only the
  * last property (and its base shape). This works because only the object's
  * last property is used to query information about the object. Care must be
  * taken to call JSObject::canRemoveLastProperty when unwinding an object to
  * an earlier property, however.
  */
 
+class AccessorShape;
 class Shape;
 class UnownedBaseShape;
 struct StackBaseShape;
 
 namespace gc {
 void MergeCompartments(JSCompartment *source, JSCompartment *target);
 }
 
+#ifdef JSGC_GENERATIONAL
+// This class is used to add a post barrier on the AccessorShape's getter/setter
+// objects. It updates the shape's entry in the parent's KidsHash table.
+class ShapeGetterSetterRef : public gc::BufferableRef
+{
+    AccessorShape *shape;
+    JSObject **objp;
+
+  public:
+    ShapeGetterSetterRef(AccessorShape *shape, JSObject **objp)
+      : shape(shape), objp(objp)
+    {}
+
+    void mark(JSTracer *trc);
+};
+#endif
+
 static inline void
-GetterSetterWriteBarrierPost(JSRuntime *rt, JSObject **objp)
+GetterSetterWriteBarrierPost(AccessorShape *shape, JSObject **objp)
 {
 #ifdef JSGC_GENERATIONAL
+    MOZ_ASSERT(shape);
     MOZ_ASSERT(objp);
     MOZ_ASSERT(*objp);
     gc::Cell **cellp = reinterpret_cast<gc::Cell **>(objp);
-    gc::StoreBuffer *storeBuffer = (*cellp)->storeBuffer();
-    if (storeBuffer)
-        storeBuffer->putRelocatableCellFromAnyThread(cellp);
+    if (gc::StoreBuffer *sb = (*cellp)->storeBuffer())
+        sb->putGeneric(ShapeGetterSetterRef(shape, objp));
 #endif
 }
 
 static inline void
 GetterSetterWriteBarrierPostRemove(JSRuntime *rt, JSObject **objp)
 {
 #ifdef JSGC_GENERATIONAL
     JS::shadow::Runtime *shadowRuntime = JS::shadow::Runtime::asShadowRuntime(rt);
@@ -288,19 +309,17 @@ class BaseShape : public gc::TenuredCell
     friend struct StackBaseShape;
     friend struct StackShape;
     friend void gc::MergeCompartments(JSCompartment *source, JSCompartment *target);
 
     enum Flag {
         /* Owned by the referring shape. */
         OWNED_SHAPE        = 0x1,
 
-        /* getterObj/setterObj are active in unions below. */
-        HAS_GETTER_OBJECT  = 0x2,
-        HAS_SETTER_OBJECT  = 0x4,
+        /* (0x2 and 0x4 are unused) */
 
         /*
          * Flags set which describe the referring object. Once set these cannot
          * be unset (except during object densification of sparse indexes), and
          * are transferred from shape to shape as the object's last property
          * changes.
          *
          * If you add a new flag here, please add appropriate code to
@@ -336,28 +355,16 @@ class BaseShape : public gc::TenuredCell
     HeapPtrObject       parent;         /* Parent of referring object. */
     HeapPtrObject       metadata;       /* Optional holder of metadata about
                                          * the referring object. */
     JSCompartment       *compartment_;  /* Compartment shape belongs to. */
     uint32_t            flags;          /* Vector of above flags. */
     uint32_t            slotSpan_;      /* Object slot span for BaseShapes at
                                          * dictionary last properties. */
 
-    union {
-        PropertyOp      rawGetter;      /* getter hook for shape */
-        JSObject        *getterObj;     /* user-defined callable "get" object or
-                                           null if shape->hasGetterValue() */
-    };
-
-    union {
-        StrictPropertyOp rawSetter;     /* setter hook for shape */
-        JSObject        *setterObj;     /* user-defined callable "set" object or
-                                           null if shape->hasSetterValue() */
-    };
-
     /* For owned BaseShapes, the canonical unowned BaseShape. */
     HeapPtrUnownedBaseShape unowned_;
 
     /* For owned BaseShapes, the shape's shape table. */
     ShapeTable       *table_;
 
     BaseShape(const BaseShape &base) MOZ_DELETE;
 
@@ -372,94 +379,57 @@ class BaseShape : public gc::TenuredCell
         this->clasp_ = clasp;
         this->parent = parent;
         this->metadata = metadata;
         this->flags = objectFlags;
         this->compartment_ = comp;
     }
 
     BaseShape(JSCompartment *comp, const Class *clasp, JSObject *parent, JSObject *metadata,
-              uint32_t objectFlags, uint8_t attrs,
-              PropertyOp rawGetter, StrictPropertyOp rawSetter)
+              uint32_t objectFlags, uint8_t attrs)
     {
         MOZ_ASSERT(!(objectFlags & ~OBJECT_FLAG_MASK));
         mozilla::PodZero(this);
         this->clasp_ = clasp;
         this->parent = parent;
         this->metadata = metadata;
         this->flags = objectFlags;
-        this->rawGetter = rawGetter;
-        this->rawSetter = rawSetter;
-        if ((attrs & JSPROP_GETTER) && rawGetter) {
-            this->flags |= HAS_GETTER_OBJECT;
-            GetterSetterWriteBarrierPost(runtimeFromMainThread(), &this->getterObj);
-        }
-        if ((attrs & JSPROP_SETTER) && rawSetter) {
-            this->flags |= HAS_SETTER_OBJECT;
-            GetterSetterWriteBarrierPost(runtimeFromMainThread(), &this->setterObj);
-        }
         this->compartment_ = comp;
     }
 
     explicit inline BaseShape(const StackBaseShape &base);
 
     /* Not defined: BaseShapes must not be stack allocated. */
     ~BaseShape();
 
     BaseShape &operator=(const BaseShape &other) {
         clasp_ = other.clasp_;
         parent = other.parent;
         metadata = other.metadata;
         flags = other.flags;
         slotSpan_ = other.slotSpan_;
-        if (flags & HAS_GETTER_OBJECT) {
-            getterObj = other.getterObj;
-            GetterSetterWriteBarrierPost(runtimeFromMainThread(), &getterObj);
-        } else {
-            if (rawGetter)
-                GetterSetterWriteBarrierPostRemove(runtimeFromMainThread(), &getterObj);
-            rawGetter = other.rawGetter;
-        }
-        if (flags & HAS_SETTER_OBJECT) {
-            setterObj = other.setterObj;
-            GetterSetterWriteBarrierPost(runtimeFromMainThread(), &setterObj);
-        } else {
-            if (rawSetter)
-                GetterSetterWriteBarrierPostRemove(runtimeFromMainThread(), &setterObj);
-            rawSetter = other.rawSetter;
-        }
         compartment_ = other.compartment_;
         return *this;
     }
 
     const Class *clasp() const { return clasp_; }
 
     bool isOwned() const { return !!(flags & OWNED_SHAPE); }
 
-    bool matchesGetterSetter(PropertyOp rawGetter, StrictPropertyOp rawSetter) const {
-        return rawGetter == this->rawGetter && rawSetter == this->rawSetter;
-    }
-
     inline void adoptUnowned(UnownedBaseShape *other);
 
     void setOwned(UnownedBaseShape *unowned) {
         flags |= OWNED_SHAPE;
         this->unowned_ = unowned;
     }
 
     JSObject *getObjectParent() const { return parent; }
     JSObject *getObjectMetadata() const { return metadata; }
     uint32_t getObjectFlags() const { return flags & OBJECT_FLAG_MASK; }
 
-    bool hasGetterObject() const { return !!(flags & HAS_GETTER_OBJECT); }
-    JSObject *getterObject() const { MOZ_ASSERT(hasGetterObject()); return getterObj; }
-
-    bool hasSetterObject() const { return !!(flags & HAS_SETTER_OBJECT); }
-    JSObject *setterObject() const { MOZ_ASSERT(hasSetterObject()); return setterObj; }
-
     bool hasTable() const { MOZ_ASSERT_IF(table_, isOwned()); return table_ != nullptr; }
     ShapeTable &table() const { MOZ_ASSERT(table_ && isOwned()); return *table_; }
     void setTable(ShapeTable *table) { MOZ_ASSERT(isOwned()); table_ = table; }
 
     uint32_t slotSpan() const { MOZ_ASSERT(isOwned()); return slotSpan_; }
     void setSlotSpan(uint32_t slotSpan) { MOZ_ASSERT(isOwned()); slotSpan_ = slotSpan; }
 
     JSCompartment *compartment() const { return compartment_; }
@@ -490,22 +460,16 @@ class BaseShape : public gc::TenuredCell
 
     /* For JIT usage */
     static inline size_t offsetOfParent() { return offsetof(BaseShape, parent); }
     static inline size_t offsetOfFlags() { return offsetof(BaseShape, flags); }
 
     static inline ThingRootKind rootKind() { return THING_ROOT_BASE_SHAPE; }
 
     void markChildren(JSTracer *trc) {
-        if (hasGetterObject())
-            gc::MarkObjectUnbarriered(trc, &getterObj, "getter");
-
-        if (hasSetterObject())
-            gc::MarkObjectUnbarriered(trc, &setterObj, "setter");
-
         if (isOwned())
             gc::MarkBaseShape(trc, &unowned_, "base");
 
         if (parent)
             gc::MarkObject(trc, &parent, "parent");
 
         if (metadata)
             gc::MarkObject(trc, &metadata, "metadata");
@@ -563,70 +527,45 @@ BaseShape::baseUnowned()
 struct StackBaseShape : public DefaultHasher<ReadBarrieredUnownedBaseShape>
 {
     typedef const StackBaseShape *Lookup;
 
     uint32_t flags;
     const Class *clasp;
     JSObject *parent;
     JSObject *metadata;
-    PropertyOp rawGetter;
-    StrictPropertyOp rawSetter;
     JSCompartment *compartment;
 
     explicit StackBaseShape(BaseShape *base)
       : flags(base->flags & BaseShape::OBJECT_FLAG_MASK),
         clasp(base->clasp_),
         parent(base->parent),
         metadata(base->metadata),
-        rawGetter(nullptr),
-        rawSetter(nullptr),
         compartment(base->compartment())
     {}
 
     inline StackBaseShape(ThreadSafeContext *cx, const Class *clasp,
                           JSObject *parent, JSObject *metadata, uint32_t objectFlags);
     explicit inline StackBaseShape(Shape *shape);
 
-    void updateGetterSetter(uint8_t attrs, PropertyOp rawGetter, StrictPropertyOp rawSetter) {
-        flags &= ~(BaseShape::HAS_GETTER_OBJECT | BaseShape::HAS_SETTER_OBJECT);
-        if ((attrs & JSPROP_GETTER) && rawGetter) {
-            MOZ_ASSERT(!IsPoisonedPtr(rawGetter));
-            flags |= BaseShape::HAS_GETTER_OBJECT;
-        }
-        if ((attrs & JSPROP_SETTER) && rawSetter) {
-            MOZ_ASSERT(!IsPoisonedPtr(rawSetter));
-            flags |= BaseShape::HAS_SETTER_OBJECT;
-        }
-
-        this->rawGetter = rawGetter;
-        this->rawSetter = rawSetter;
-    }
-
     static inline HashNumber hash(const StackBaseShape *lookup);
     static inline bool match(UnownedBaseShape *key, const StackBaseShape *lookup);
 
     // For RootedGeneric<StackBaseShape*>
     void trace(JSTracer *trc);
 };
 
 inline
 BaseShape::BaseShape(const StackBaseShape &base)
 {
     mozilla::PodZero(this);
     this->clasp_ = base.clasp;
     this->parent = base.parent;
     this->metadata = base.metadata;
     this->flags = base.flags;
-    this->rawGetter = base.rawGetter;
-    this->rawSetter = base.rawSetter;
-    if ((base.flags & HAS_GETTER_OBJECT) && base.rawGetter)
-        GetterSetterWriteBarrierPost(runtimeFromMainThread(), &this->getterObj);
-    if ((base.flags & HAS_SETTER_OBJECT) && base.rawSetter)
-        GetterSetterWriteBarrierPost(runtimeFromMainThread(), &this->setterObj);
     this->compartment_ = base.compartment;
 }
 
 typedef HashSet<ReadBarrieredUnownedBaseShape,
                 StackBaseShape,
                 SystemAllocPolicy> BaseShapeSet;
 
 
@@ -635,16 +574,17 @@ class Shape : public gc::TenuredCell
     friend class ::JSObject;
     friend class ::JSFunction;
     friend class js::Bindings;
     friend class js::Nursery;
     friend class js::gc::ForkJoinNursery;
     friend class js::NativeObject;
     friend class js::PropertyTree;
     friend class js::StaticBlockObject;
+    friend class js::ShapeGetterSetterRef;
     friend struct js::StackShape;
     friend struct js::StackBaseShape;
 
   protected:
     HeapPtrBaseShape    base_;
     PreBarrieredId      propid_;
 
     JS_ENUM_HEADER(SlotInfo, uint32_t)
@@ -693,24 +633,17 @@ class Shape : public gc::TenuredCell
                                 Shape ***pspp, bool adding = false);
     static inline Shape *searchThreadLocal(ThreadSafeContext *cx, Shape *start, jsid id,
                                            Shape ***pspp, bool adding = false);
     static inline Shape *searchNoHashify(Shape *start, jsid id);
 
     void removeFromDictionary(NativeObject *obj);
     void insertIntoDictionary(HeapPtrShape *dictp);
 
-    void initDictionaryShape(const StackShape &child, uint32_t nfixed, HeapPtrShape *dictp) {
-        new (this) Shape(child, nfixed);
-        this->flags |= IN_DICTIONARY;
-
-        this->listp = nullptr;
-        if (dictp)
-            insertIntoDictionary(dictp);
-    }
+    inline void initDictionaryShape(const StackShape &child, uint32_t nfixed, HeapPtrShape *dictp);
 
     /* Replace the base shape of the last shape in a non-dictionary lineage with base. */
     static Shape *replaceLastProperty(ExclusiveContext *cx, StackBaseShape &base,
                                       TaggedProto proto, HandleShape shape);
 
     /*
      * This function is thread safe if every shape in the lineage of |shape|
      * is thread local, which is the case when we clone the entire shape
@@ -753,16 +686,25 @@ class Shape : public gc::TenuredCell
             info->shapesMallocHeapTreeKids += kids.toHash()->sizeOfIncludingThis(mallocSizeOf);
     }
 
     bool isNative() const {
         MOZ_ASSERT(!(flags & NON_NATIVE) == getObjectClass()->isNative());
         return !(flags & NON_NATIVE);
     }
 
+    bool isAccessorShape() const {
+        MOZ_ASSERT_IF(flags & ACCESSOR_SHAPE, getAllocKind() == gc::FINALIZE_ACCESSOR_SHAPE);
+        return flags & ACCESSOR_SHAPE;
+    }
+    AccessorShape &asAccessorShape() const {
+        MOZ_ASSERT(isAccessorShape());
+        return *(AccessorShape *)this;
+    }
+
     const HeapPtrShape &previous() const { return parent; }
     JSCompartment *compartment() const { return base()->compartment(); }
 
     template <AllowGC allowGC>
     class Range {
       protected:
         friend class Shape;
 
@@ -825,96 +767,111 @@ class Shape : public gc::TenuredCell
         IN_DICTIONARY   = 0x02,
 
         /*
          * Slotful property was stored to more than once. This is used as a
          * hint for type inference.
          */
         OVERWRITTEN     = 0x04,
 
-        UNUSED_BITS     = 0x38
+        /*
+         * This shape is an AccessorShape, a fat Shape that can store
+         * getter/setter information.
+         */
+        ACCESSOR_SHAPE  = 0x08,
+
+        UNUSED_BITS     = 0x3C
     };
 
     /* Get a shape identical to this one, without parent/kids information. */
     inline Shape(const StackShape &other, uint32_t nfixed);
 
     /* Used by EmptyShape (see jsscopeinlines.h). */
     inline Shape(UnownedBaseShape *base, uint32_t nfixed);
 
     /* Copy constructor disabled, to avoid misuse of the above form. */
     Shape(const Shape &other) MOZ_DELETE;
 
+    /* Allocate a new shape based on the given StackShape. */
+    static inline Shape *new_(ExclusiveContext *cx, StackShape &unrootedOther, uint32_t nfixed);
+
     /*
      * Whether this shape has a valid slot value. This may be true even if
      * !hasSlot() (see SlotInfo comment above), and may be false even if
      * hasSlot() if the shape is being constructed and has not had a slot
      * assigned yet. After construction, hasSlot() implies !hasMissingSlot().
      */
     bool hasMissingSlot() const { return maybeSlot() == SHAPE_INVALID_SLOT; }
 
   public:
     bool inDictionary() const {
         return (flags & IN_DICTIONARY) != 0;
     }
 
-    PropertyOp getter() const { return base()->rawGetter; }
-    bool hasDefaultGetter() const {return !base()->rawGetter; }
-    PropertyOp getterOp() const { MOZ_ASSERT(!hasGetterValue()); return base()->rawGetter; }
-    JSObject *getterObject() const { MOZ_ASSERT(hasGetterValue()); return base()->getterObj; }
+    inline PropertyOp getter() const;
+    bool hasDefaultGetter() const { return !getter(); }
+    PropertyOp getterOp() const { MOZ_ASSERT(!hasGetterValue()); return getter(); }
+    inline JSObject *getterObject() const;
+    bool hasGetterObject() const { return hasGetterValue() && getterObject(); }
 
     // Per ES5, decode null getterObj as the undefined value, which encodes as null.
     Value getterValue() const {
         MOZ_ASSERT(hasGetterValue());
-        return base()->getterObj ? ObjectValue(*base()->getterObj) : UndefinedValue();
+        if (JSObject *getterObj = getterObject())
+            return ObjectValue(*getterObj);
+        return UndefinedValue();
     }
 
     Value getterOrUndefined() const {
-        return (hasGetterValue() && base()->getterObj)
-               ? ObjectValue(*base()->getterObj)
-               : UndefinedValue();
+        return hasGetterValue() ? getterValue() : UndefinedValue();
     }
 
-    StrictPropertyOp setter() const { return base()->rawSetter; }
-    bool hasDefaultSetter() const  { return !base()->rawSetter; }
-    StrictPropertyOp setterOp() const { MOZ_ASSERT(!hasSetterValue()); return base()->rawSetter; }
-    JSObject *setterObject() const { MOZ_ASSERT(hasSetterValue()); return base()->setterObj; }
+    inline StrictPropertyOp setter() const;
+    bool hasDefaultSetter() const { return !setter(); }
+    StrictPropertyOp setterOp() const { MOZ_ASSERT(!hasSetterValue()); return setter(); }
+    inline JSObject *setterObject() const;
+    bool hasSetterObject() const { return hasSetterValue() && setterObject(); }
 
     // Per ES5, decode null setterObj as the undefined value, which encodes as null.
     Value setterValue() const {
         MOZ_ASSERT(hasSetterValue());
-        return base()->setterObj ? ObjectValue(*base()->setterObj) : UndefinedValue();
+        if (JSObject *setterObj = setterObject())
+            return ObjectValue(*setterObj);
+        return UndefinedValue();
     }
 
     Value setterOrUndefined() const {
-        return (hasSetterValue() && base()->setterObj)
-               ? ObjectValue(*base()->setterObj)
-               : UndefinedValue();
+        return hasSetterValue() ? setterValue() : UndefinedValue();
     }
 
     void setOverwritten() {
         flags |= OVERWRITTEN;
     }
     bool hadOverwrite() const {
         return flags & OVERWRITTEN;
     }
 
     void update(PropertyOp getter, StrictPropertyOp setter, uint8_t attrs);
 
     bool matches(const Shape *other) const {
         return propid_.get() == other->propid_.get() &&
-               matchesParamsAfterId(other->base(), other->maybeSlot(), other->attrs, other->flags);
+               matchesParamsAfterId(other->base(), other->maybeSlot(), other->attrs, other->flags,
+                                    other->getter(), other->setter());
     }
 
     inline bool matches(const StackShape &other) const;
 
-    bool matchesParamsAfterId(BaseShape *base, uint32_t aslot, unsigned aattrs, unsigned aflags) const
+    bool matchesParamsAfterId(BaseShape *base, uint32_t aslot, unsigned aattrs, unsigned aflags,
+                              PropertyOp rawGetter, StrictPropertyOp rawSetter) const
     {
         return base->unowned() == this->base()->unowned() &&
                maybeSlot() == aslot &&
-               attrs == aattrs;
+               attrs == aattrs &&
+               getter() == rawGetter &&
+               setter() == rawSetter;
     }
 
     bool get(JSContext* cx, HandleObject receiver, JSObject *obj, JSObject *pobj, MutableHandleValue vp);
     bool set(JSContext* cx, HandleObject obj, HandleObject receiver, bool strict, MutableHandleValue vp);
 
     BaseShape *base() const { return base_.get(); }
 
     bool hasSlot() const {
@@ -1042,22 +999,17 @@ class Shape : public gc::TenuredCell
 #endif
 
     void sweep();
     void finalize(FreeOp *fop);
     void removeChild(Shape *child);
 
     static inline ThingRootKind rootKind() { return THING_ROOT_SHAPE; }
 
-    void markChildren(JSTracer *trc) {
-        MarkBaseShape(trc, &base_, "base");
-        gc::MarkId(trc, &propidRef(), "propid");
-        if (parent)
-            MarkShape(trc, &parent, "parent");
-    }
+    inline void markChildren(JSTracer *trc);
 
     inline Shape *search(ExclusiveContext *cx, jsid id);
     inline Shape *searchLinear(jsid id);
 
 #ifdef JSGC_COMPACTING
     void fixupAfterMovingGC();
 #endif
 
@@ -1074,26 +1026,47 @@ class Shape : public gc::TenuredCell
         JS_STATIC_ASSERT(offsetof(Shape, base_) == offsetof(js::shadow::Shape, base));
         JS_STATIC_ASSERT(offsetof(Shape, slotInfo) == offsetof(js::shadow::Shape, slotInfo));
         JS_STATIC_ASSERT(FIXED_SLOTS_SHIFT == js::shadow::Shape::FIXED_SLOTS_SHIFT);
         static_assert(js::shadow::Object::MAX_FIXED_SLOTS <= FIXED_SLOTS_MAX,
                       "verify numFixedSlots() bitfield is big enough");
     }
 };
 
+/* Fat Shape used for accessor properties. */
+class AccessorShape : public Shape
+{
+    friend class Shape;
+    friend class ShapeGetterSetterRef;
+    friend class NativeObject;
+
+    union {
+        PropertyOp      rawGetter;      /* getter hook for shape */
+        JSObject        *getterObj;     /* user-defined callable "get" object or
+                                           null if shape->hasGetterValue() */
+    };
+    union {
+        StrictPropertyOp rawSetter;     /* setter hook for shape */
+        JSObject        *setterObj;     /* user-defined callable "set" object or
+                                           null if shape->hasSetterValue() */
+    };
+
+  public:
+    /* Get a shape identical to this one, without parent/kids information. */
+    inline AccessorShape(const StackShape &other, uint32_t nfixed);
+};
+
 inline
 StackBaseShape::StackBaseShape(Shape *shape)
   : flags(shape->getObjectFlags()),
     clasp(shape->getObjectClass()),
     parent(shape->getObjectParent()),
     metadata(shape->getObjectMetadata()),
     compartment(shape->compartment())
-{
-    updateGetterSetter(shape->attrs, shape->getter(), shape->setter());
-}
+{}
 
 class AutoRooterGetterSetter
 {
     class Inner : private JS::CustomAutoRooter
     {
       public:
         inline Inner(ThreadSafeContext *cx, uint8_t attrs,
                      PropertyOp *pgetter_, StrictPropertyOp *psetter_);
@@ -1121,16 +1094,18 @@ struct EmptyShape : public js::Shape
     EmptyShape(UnownedBaseShape *base, uint32_t nfixed)
       : js::Shape(base, nfixed)
     {
         // Only empty shapes can be NON_NATIVE.
         if (!getObjectClass()->isNative())
             flags |= NON_NATIVE;
     }
 
+    static Shape *new_(ExclusiveContext *cx, Handle<UnownedBaseShape *> base, uint32_t nfixed);
+
     /*
      * Lookup an initial shape matching the given parameters, creating an empty
      * shape if none was found.
      */
     static Shape *getInitialShape(ExclusiveContext *cx, const Class *clasp,
                                   TaggedProto proto, JSObject *metadata,
                                   JSObject *parent, size_t nfixed, uint32_t objectFlags = 0);
     static Shape *getInitialShape(ExclusiveContext *cx, const Class *clasp,
@@ -1228,65 +1203,90 @@ struct InitialShapeEntry
 
 typedef HashSet<InitialShapeEntry, InitialShapeEntry, SystemAllocPolicy> InitialShapeSet;
 
 struct StackShape
 {
     /* For performance, StackShape only roots when absolutely necessary. */
     UnownedBaseShape *base;
     jsid             propid;
+    PropertyOp       rawGetter;
+    StrictPropertyOp rawSetter;
     uint32_t         slot_;
     uint8_t          attrs;
     uint8_t          flags;
 
     explicit StackShape(UnownedBaseShape *base, jsid propid, uint32_t slot,
                         unsigned attrs, unsigned flags)
       : base(base),
         propid(propid),
+        rawGetter(nullptr),
+        rawSetter(nullptr),
         slot_(slot),
         attrs(uint8_t(attrs)),
         flags(uint8_t(flags))
     {
         MOZ_ASSERT(base);
         MOZ_ASSERT(!JSID_IS_VOID(propid));
         MOZ_ASSERT(slot <= SHAPE_INVALID_SLOT);
         MOZ_ASSERT_IF(attrs & (JSPROP_GETTER | JSPROP_SETTER), attrs & JSPROP_SHARED);
     }
 
     explicit StackShape(Shape *shape)
       : base(shape->base()->unowned()),
         propid(shape->propidRef()),
+        rawGetter(shape->getter()),
+        rawSetter(shape->setter()),
         slot_(shape->maybeSlot()),
         attrs(shape->attrs),
         flags(shape->flags)
     {}
 
+    void updateGetterSetter(PropertyOp rawGetter, StrictPropertyOp rawSetter) {
+        MOZ_ASSERT_IF((attrs & JSPROP_GETTER) && rawGetter, !IsPoisonedPtr(rawGetter));
+        MOZ_ASSERT_IF((attrs & JSPROP_SETTER) && rawSetter, !IsPoisonedPtr(rawSetter));
+
+        if (rawGetter || rawSetter || (attrs & (JSPROP_GETTER|JSPROP_SETTER)))
+            flags |= Shape::ACCESSOR_SHAPE;
+        else
+            flags &= ~Shape::ACCESSOR_SHAPE;
+
+        this->rawGetter = rawGetter;
+        this->rawSetter = rawSetter;
+    }
+
     bool hasSlot() const { return (attrs & JSPROP_SHARED) == 0; }
     bool hasMissingSlot() const { return maybeSlot() == SHAPE_INVALID_SLOT; }
 
     uint32_t slot() const { MOZ_ASSERT(hasSlot() && !hasMissingSlot()); return slot_; }
     uint32_t maybeSlot() const { return slot_; }
 
     uint32_t slotSpan() const {
         uint32_t free = JSSLOT_FREE(base->clasp_);
         return hasMissingSlot() ? free : (maybeSlot() + 1);
     }
 
     void setSlot(uint32_t slot) {
         MOZ_ASSERT(slot <= SHAPE_INVALID_SLOT);
         slot_ = slot;
     }
 
+    bool isAccessorShape() const {
+        return flags & Shape::ACCESSOR_SHAPE;
+    }
+
     HashNumber hash() const {
         HashNumber hash = uintptr_t(base);
 
         /* Accumulate from least to most random so the low bits are most random. */
         hash = mozilla::RotateLeft(hash, 4) ^ attrs;
         hash = mozilla::RotateLeft(hash, 4) ^ slot_;
         hash = mozilla::RotateLeft(hash, 4) ^ JSID_BITS(propid);
+        hash = mozilla::RotateLeft(hash, 4) ^ uintptr_t(rawGetter);
+        hash = mozilla::RotateLeft(hash, 4) ^ uintptr_t(rawSetter);
         return hash;
     }
 
     // For RootedGeneric<StackShape*>
     void trace(JSTracer *trc);
 };
 
 } /* namespace js */
@@ -1352,33 +1352,93 @@ inline
 Shape::Shape(const StackShape &other, uint32_t nfixed)
   : base_(other.base),
     propid_(other.propid),
     slotInfo(other.maybeSlot() | (nfixed << FIXED_SLOTS_SHIFT)),
     attrs(other.attrs),
     flags(other.flags),
     parent(nullptr)
 {
+#ifdef DEBUG
+    gc::AllocKind allocKind = getAllocKind();
+    MOZ_ASSERT_IF(other.isAccessorShape(), allocKind == gc::FINALIZE_ACCESSOR_SHAPE);
+    MOZ_ASSERT_IF(allocKind == gc::FINALIZE_SHAPE, !other.isAccessorShape());
+#endif
+
     MOZ_ASSERT_IF(attrs & (JSPROP_GETTER | JSPROP_SETTER), attrs & JSPROP_SHARED);
     kids.setNull();
 }
 
 inline
+AccessorShape::AccessorShape(const StackShape &other, uint32_t nfixed)
+  : Shape(other, nfixed),
+    rawGetter(other.rawGetter),
+    rawSetter(other.rawSetter)
+{
+    MOZ_ASSERT(getAllocKind() == gc::FINALIZE_ACCESSOR_SHAPE);
+
+    if ((attrs & JSPROP_GETTER) && rawGetter)
+        GetterSetterWriteBarrierPost(this, &this->getterObj);
+    if ((attrs & JSPROP_SETTER) && rawSetter)
+        GetterSetterWriteBarrierPost(this, &this->setterObj);
+}
+
+inline
 Shape::Shape(UnownedBaseShape *base, uint32_t nfixed)
   : base_(base),
     propid_(JSID_EMPTY),
     slotInfo(SHAPE_INVALID_SLOT | (nfixed << FIXED_SLOTS_SHIFT)),
     attrs(JSPROP_SHARED),
     flags(0),
     parent(nullptr)
 {
     MOZ_ASSERT(base);
     kids.setNull();
 }
 
+inline PropertyOp
+Shape::getter() const
+{
+    return isAccessorShape() ? asAccessorShape().rawGetter : nullptr;
+}
+
+inline StrictPropertyOp
+Shape::setter() const
+{
+    return isAccessorShape() ? asAccessorShape().rawSetter : nullptr;
+}
+
+inline JSObject *
+Shape::getterObject() const
+{
+    MOZ_ASSERT(hasGetterValue());
+    return asAccessorShape().getterObj;
+}
+
+inline JSObject *
+Shape::setterObject() const
+{
+    MOZ_ASSERT(hasSetterValue());
+    return asAccessorShape().setterObj;
+}
+
+inline void
+Shape::initDictionaryShape(const StackShape &child, uint32_t nfixed, HeapPtrShape *dictp)
+{
+    if (child.isAccessorShape())
+        new (this) AccessorShape(child, nfixed);
+    else
+        new (this) Shape(child, nfixed);
+    this->flags |= IN_DICTIONARY;
+
+    this->listp = nullptr;
+    if (dictp)
+        insertIntoDictionary(dictp);
+}
+
 inline Shape *
 Shape::searchLinear(jsid id)
 {
     /*
      * Non-dictionary shapes can acquire a table at any point the main thread
      * is operating on it, so other threads inspecting such shapes can't use
      * their table without racing. This function can be called from any thread
      * on any non-dictionary shape.
@@ -1389,16 +1449,31 @@ Shape::searchLinear(jsid id)
         if (shape->propidRef() == id)
             return shape;
         shape = shape->parent;
     }
 
     return nullptr;
 }
 
+inline void
+Shape::markChildren(JSTracer *trc)
+{
+    MarkBaseShape(trc, &base_, "base");
+    gc::MarkId(trc, &propidRef(), "propid");
+    if (parent)
+        MarkShape(trc, &parent, "parent");
+
+    if (hasGetterObject())
+        gc::MarkObjectUnbarriered(trc, &asAccessorShape().getterObj, "getter");
+
+    if (hasSetterObject())
+        gc::MarkObjectUnbarriered(trc, &asAccessorShape().setterObj, "setter");
+}
+
 /*
  * Keep this function in sync with search. It neither hashifies the start
  * shape nor increments linear search count.
  */
 inline Shape *
 Shape::searchNoHashify(Shape *start, jsid id)
 {
     /*
@@ -1412,17 +1487,18 @@ Shape::searchNoHashify(Shape *start, jsi
 
     return start->searchLinear(id);
 }
 
 inline bool
 Shape::matches(const StackShape &other) const
 {
     return propid_.get() == other.propid &&
-           matchesParamsAfterId(other.base, other.slot_, other.attrs, other.flags);
+           matchesParamsAfterId(other.base, other.slot_, other.attrs, other.flags,
+                                other.rawGetter, other.rawSetter);
 }
 
 template<> struct RootKind<Shape *> : SpecificRootKind<Shape *, THING_ROOT_SHAPE> {};
 template<> struct RootKind<BaseShape *> : SpecificRootKind<BaseShape *, THING_ROOT_BASE_SHAPE> {};
 
 // Property lookup hooks on objects are required to return a non-nullptr shape
 // to signify that the property has been found. For cases where the property is
 // not actually represented by a Shape, use a dummy value. This includes all