Bug 716647 - Part 1: Introduce JS_OPTIMIZED_OUT magic for optimized out slots and teach Debugger about them. (r=jandem)
authorShu-yu Guo <shu@rfrn.org>
Thu, 24 Apr 2014 01:59:36 -0700
changeset 198439 d34458e80bcbfb39b6b04214ca6dcd47c0be3027
parent 198438 25dd3d17a19aa712bb749278aafcacdd52e2bf70
child 198440 b30afb9de404554b547a9adba6bc483fd045a584
push id3624
push userasasaki@mozilla.com
push dateMon, 09 Jun 2014 21:49:01 +0000
treeherdermozilla-beta@b1a5da15899a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs716647
milestone31.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 716647 - Part 1: Introduce JS_OPTIMIZED_OUT magic for optimized out slots and teach Debugger about them. (r=jandem)
js/public/Value.h
js/src/jit/BaselineBailouts.cpp
js/src/jit/IonAnalysis.cpp
js/src/jit/IonBuilder.cpp
js/src/jit/IonTypes.h
js/src/jit/MIR.h
js/src/jit/shared/CodeGenerator-shared.cpp
js/src/jsinfer.h
js/src/jsinferinlines.h
js/src/jsobj.cpp
js/src/jsscript.cpp
js/src/vm/CommonPropertyNames.h
js/src/vm/Debugger.cpp
js/src/vm/Debugger.h
js/src/vm/ScopeObject.cpp
--- a/js/public/Value.h
+++ b/js/public/Value.h
@@ -232,16 +232,17 @@ typedef enum JSWhyMagic
     JS_LAZY_ARGUMENTS,           /* lazy arguments value on the stack */
     JS_OPTIMIZED_ARGUMENTS,      /* optimized-away 'arguments' value */
     JS_IS_CONSTRUCTING,          /* magic value passed to natives to indicate construction */
     JS_OVERWRITTEN_CALLEE,       /* arguments.callee has been overwritten */
     JS_BLOCK_NEEDS_CLONE,        /* value of static block object slot */
     JS_HASH_KEY_EMPTY,           /* see class js::HashableValue */
     JS_ION_ERROR,                /* error while running Ion code */
     JS_ION_BAILOUT,              /* status code to signal EnterIon will OSR into Interpret */
+    JS_OPTIMIZED_OUT,            /* optimized out slot */
     JS_GENERIC_MAGIC             /* for local use */
 } JSWhyMagic;
 
 #if defined(IS_LITTLE_ENDIAN)
 # if JS_BITS_PER_WORD == 32
 typedef union jsval_layout
 {
     uint64_t asBits;
@@ -1273,16 +1274,26 @@ IsPoisonedValue(const Value &v)
 {
     if (v.isString())
         return IsPoisonedPtr(v.toString());
     if (v.isObject())
         return IsPoisonedPtr(&v.toObject());
     return false;
 }
 
+inline bool
+IsOptimizedPlaceholderMagicValue(const Value &v)
+{
+    if (v.isMagic()) {
+        MOZ_ASSERT(v.whyMagic() == JS_OPTIMIZED_ARGUMENTS || v.whyMagic() == JS_OPTIMIZED_OUT);
+        return true;
+    }
+    return false;
+}
+
 /************************************************************************/
 
 static inline Value
 NullValue()
 {
     Value v;
     v.setNull();
     return v;
--- a/js/src/jit/BaselineBailouts.cpp
+++ b/js/src/jit/BaselineBailouts.cpp
@@ -583,17 +583,17 @@ InitFromBailout(JSContext *cx, HandleScr
         }
     } else {
         Value v = iter.read();
         if (v.isObject()) {
             scopeChain = &v.toObject();
             if (fun && fun->isHeavyweight())
                 flags |= BaselineFrame::HAS_CALL_OBJ;
         } else {
-            JS_ASSERT(v.isUndefined());
+            JS_ASSERT(v.isUndefined() || v.isMagic(JS_OPTIMIZED_OUT));
 
             // Get scope chain from function or script.
             if (fun) {
                 // If pcOffset == 0, we may have to push a new call object, so
                 // we leave scopeChain nullptr and enter baseline code before
                 // the prologue.
                 if (iter.pcOffset() != 0 || iter.resumeAfter())
                     scopeChain = fun->environment();
@@ -612,17 +612,17 @@ InitFromBailout(JSContext *cx, HandleScr
         // Make sure to add HAS_RVAL to flags here because setFlags() below
         // will clobber it.
         returnValue = iter.read();
         flags |= BaselineFrame::HAS_RVAL;
 
         // If script maybe has an arguments object, the third slot will hold it.
         if (script->argumentsHasVarBinding()) {
             v = iter.read();
-            JS_ASSERT(v.isObject() || v.isUndefined());
+            JS_ASSERT(v.isObject() || v.isUndefined() || v.isMagic(JS_OPTIMIZED_OUT));
             if (v.isObject())
                 argsObj = &v.toObject().as<ArgumentsObject>();
         }
     }
     IonSpew(IonSpew_BaselineBailouts, "      ScopeChain=%p", scopeChain);
     blFrame->setScopeChain(scopeChain);
     IonSpew(IonSpew_BaselineBailouts, "      ReturnValue=%016llx", *((uint64_t *) &returnValue));
     blFrame->setReturnValue(returnValue);
--- a/js/src/jit/IonAnalysis.cpp
+++ b/js/src/jit/IonAnalysis.cpp
@@ -132,25 +132,25 @@ jit::EliminateDeadResumePointOperands(MI
                     !mrp->instruction() ||
                     mrp->instruction() == *ins ||
                     mrp->instruction()->id() <= maxDefinition)
                 {
                     uses++;
                     continue;
                 }
 
-                // Store an undefined value in place of all dead resume point
-                // operands. Making any such substitution can in general alter
-                // the interpreter's behavior, even though the code is dead, as
-                // the interpreter will still execute opcodes whose effects
-                // cannot be observed. If the undefined value were to flow to,
-                // say, a dead property access the interpreter could throw an
-                // exception; we avoid this problem by removing dead operands
-                // before removing dead code.
-                MConstant *constant = MConstant::New(graph.alloc(), UndefinedValue());
+                // Store an optimized out magic value in place of all dead
+                // resume point operands. Making any such substitution can in
+                // general alter the interpreter's behavior, even though the
+                // code is dead, as the interpreter will still execute opcodes
+                // whose effects cannot be observed. If the undefined value
+                // were to flow to, say, a dead property access the
+                // interpreter could throw an exception; we avoid this problem
+                // by removing dead operands before removing dead code.
+                MConstant *constant = MConstant::New(graph.alloc(), MagicValue(JS_OPTIMIZED_OUT));
                 block->insertBefore(*(block->begin()), constant);
                 uses = mrp->replaceOperand(uses, constant);
             }
         }
     }
 
     return true;
 }
@@ -438,17 +438,19 @@ class TypeAnalyzer
 
 } /* anonymous namespace */
 
 // Try to specialize this phi based on its non-cyclic inputs.
 static MIRType
 GuessPhiType(MPhi *phi, bool *hasInputsWithEmptyTypes)
 {
 #ifdef DEBUG
-    // Check that different magic constants aren't flowing together.
+    // Check that different magic constants aren't flowing together. Ignore
+    // JS_OPTIMIZED_OUT, since an operand could be legitimately optimized
+    // away.
     MIRType magicType = MIRType_None;
     for (size_t i = 0; i < phi->numOperands(); i++) {
         MDefinition *in = phi->getOperand(i);
         if (in->type() == MIRType_MagicOptimizedArguments ||
             in->type() == MIRType_MagicHole ||
             in->type() == MIRType_MagicIsConstructing)
         {
             if (magicType == MIRType_None)
@@ -728,16 +730,19 @@ TypeAnalyzer::replaceRedundantPhi(MPhi *
         v = UndefinedValue();
         break;
       case MIRType_Null:
         v = NullValue();
         break;
       case MIRType_MagicOptimizedArguments:
         v = MagicValue(JS_OPTIMIZED_ARGUMENTS);
         break;
+      case MIRType_MagicOptimizedOut:
+        v = MagicValue(JS_OPTIMIZED_OUT);
+        break;
       default:
         MOZ_ASSUME_UNREACHABLE("unexpected type");
     }
     MConstant *c = MConstant::New(alloc(), v);
     // The instruction pass will insert the box
     block->insertBefore(*(block->begin()), c);
     phi->replaceAllUsesWith(c);
 }
@@ -750,17 +755,18 @@ TypeAnalyzer::insertConversions()
     // inputs of uses) does not conflict with input adjustment.
     for (ReversePostorderIterator block(graph.rpoBegin()); block != graph.rpoEnd(); block++) {
         if (mir->shouldCancel("Insert Conversions"))
             return false;
 
         for (MPhiIterator phi(block->phisBegin()); phi != block->phisEnd();) {
             if (phi->type() == MIRType_Undefined ||
                 phi->type() == MIRType_Null ||
-                phi->type() == MIRType_MagicOptimizedArguments)
+                phi->type() == MIRType_MagicOptimizedArguments ||
+                phi->type() == MIRType_MagicOptimizedOut)
             {
                 replaceRedundantPhi(*phi);
                 phi = block->discardPhiAt(phi);
             } else {
                 adjustPhiInputs(*phi);
                 phi++;
             }
         }
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -61,45 +61,50 @@ jit::NewBaselineFrameInspector(TempAlloc
     BaselineFrameInspector *inspector = temp->lifoAlloc()->new_<BaselineFrameInspector>(temp);
     if (!inspector)
         return nullptr;
 
     // Note: copying the actual values into a temporary structure for use
     // during compilation could capture nursery pointers, so the values' types
     // are recorded instead.
 
-    inspector->thisType = types::GetValueType(frame->thisValue());
+    inspector->thisType = types::GetMaybeOptimizedOutValueType(frame->thisValue());
 
     if (frame->scopeChain()->hasSingletonType())
         inspector->singletonScopeChain = frame->scopeChain();
 
     JSScript *script = frame->script();
 
     if (script->functionNonDelazifying()) {
         if (!inspector->argTypes.reserve(frame->numFormalArgs()))
             return nullptr;
         for (size_t i = 0; i < frame->numFormalArgs(); i++) {
-            if (script->formalIsAliased(i))
+            if (script->formalIsAliased(i)) {
                 inspector->argTypes.infallibleAppend(types::Type::UndefinedType());
-            else if (!script->argsObjAliasesFormals())
-                inspector->argTypes.infallibleAppend(types::GetValueType(frame->unaliasedFormal(i)));
-            else if (frame->hasArgsObj())
-                inspector->argTypes.infallibleAppend(types::GetValueType(frame->argsObj().arg(i)));
-            else
+            } else if (!script->argsObjAliasesFormals()) {
+                types::Type type = types::GetMaybeOptimizedOutValueType(frame->unaliasedFormal(i));
+                inspector->argTypes.infallibleAppend(type);
+            } else if (frame->hasArgsObj()) {
+                types::Type type = types::GetMaybeOptimizedOutValueType(frame->argsObj().arg(i));
+                inspector->argTypes.infallibleAppend(type);
+            } else {
                 inspector->argTypes.infallibleAppend(types::Type::UndefinedType());
+            }
         }
     }
 
     if (!inspector->varTypes.reserve(frame->script()->nfixed()))
         return nullptr;
     for (size_t i = 0; i < frame->script()->nfixed(); i++) {
-        if (info->isSlotAliasedAtOsr(i + info->firstLocalSlot()))
+        if (info->isSlotAliasedAtOsr(i + info->firstLocalSlot())) {
             inspector->varTypes.infallibleAppend(types::Type::UndefinedType());
-        else
-            inspector->varTypes.infallibleAppend(types::GetValueType(frame->unaliasedLocal(i)));
+        } else {
+            types::Type type = types::GetMaybeOptimizedOutValueType(frame->unaliasedLocal(i));
+            inspector->varTypes.infallibleAppend(type);
+        }
     }
 
     return inspector;
 }
 
 IonBuilder::IonBuilder(JSContext *analysisContext, CompileCompartment *comp,
                        const JitCompileOptions &options, TempAllocator *temp,
                        MIRGraph *graph, types::CompilerConstraintList *constraints,
--- a/js/src/jit/IonTypes.h
+++ b/js/src/jit/IonTypes.h
@@ -86,16 +86,17 @@ enum MIRType
     MIRType_Null,
     MIRType_Boolean,
     MIRType_Int32,
     MIRType_Double,
     MIRType_Float32,
     MIRType_String,
     MIRType_Object,
     MIRType_MagicOptimizedArguments, // JS_OPTIMIZED_ARGUMENTS magic value.
+    MIRType_MagicOptimizedOut,       // JS_OPTIMIZED_OUT magic value.
     MIRType_MagicHole,               // JS_ELEMENTS_HOLE magic value.
     MIRType_MagicIsConstructing,     // JS_IS_CONSTRUCTING magic value.
     MIRType_Value,
     MIRType_None,                    // Invalid, used as a placeholder.
     MIRType_Slots,                   // A slots vector
     MIRType_Elements,                // An elements vector
     MIRType_Pointer,                 // An opaque pointer that receives no special treatment
     MIRType_Shape,                   // A Shape pointer.
@@ -159,16 +160,17 @@ ValueTypeFromMIRType(MIRType type)
     case MIRType_Int32:
       return JSVAL_TYPE_INT32;
     case MIRType_Float32: // Fall through, there's no JSVAL for Float32
     case MIRType_Double:
       return JSVAL_TYPE_DOUBLE;
     case MIRType_String:
       return JSVAL_TYPE_STRING;
     case MIRType_MagicOptimizedArguments:
+    case MIRType_MagicOptimizedOut:
     case MIRType_MagicHole:
     case MIRType_MagicIsConstructing:
       return JSVAL_TYPE_MAGIC;
     default:
       JS_ASSERT(type == MIRType_Object);
       return JSVAL_TYPE_OBJECT;
   }
 }
@@ -196,16 +198,18 @@ StringFromMIRType(MIRType type)
     case MIRType_Float32:
       return "Float32";
     case MIRType_String:
       return "String";
     case MIRType_Object:
       return "Object";
     case MIRType_MagicOptimizedArguments:
       return "MagicOptimizedArguments";
+    case MIRType_MagicOptimizedOut:
+      return "MagicOptimizedOut";
     case MIRType_MagicHole:
       return "MagicHole";
     case MIRType_MagicIsConstructing:
       return "MagicIsConstructing";
     case MIRType_Value:
       return "Value";
     case MIRType_None:
       return "None";
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -41,16 +41,18 @@ static const inline
 MIRType MIRTypeFromValue(const js::Value &vp)
 {
     if (vp.isDouble())
         return MIRType_Double;
     if (vp.isMagic()) {
         switch (vp.whyMagic()) {
           case JS_OPTIMIZED_ARGUMENTS:
             return MIRType_MagicOptimizedArguments;
+          case JS_OPTIMIZED_OUT:
+            return MIRType_MagicOptimizedOut;
           case JS_ELEMENTS_HOLE:
             return MIRType_MagicHole;
           case JS_IS_CONSTRUCTING:
             return MIRType_MagicIsConstructing;
           default:
             MOZ_ASSERT(!"Unexpected magic constant");
         }
     }
--- a/js/src/jit/shared/CodeGenerator-shared.cpp
+++ b/js/src/jit/shared/CodeGenerator-shared.cpp
@@ -145,17 +145,17 @@ CodeGeneratorShared::encodeAllocations(L
     for (uint32_t allocno = 0, e = resumePoint->numOperands(); allocno < e; allocno++) {
         uint32_t i = allocno + *startIndex;
         MDefinition *mir = resumePoint->getOperand(allocno);
 
         if (mir->isBox())
             mir = mir->toBox()->getOperand(0);
 
         MIRType type = mir->isUnused()
-                       ? MIRType_Undefined
+                       ? MIRType_MagicOptimizedOut
                        : mir->type();
 
         RValueAllocation alloc;
 
         switch (type) {
           case MIRType_Undefined:
             alloc = RValueAllocation::Undefined();
             break;
@@ -189,19 +189,24 @@ CodeGeneratorShared::encodeAllocations(L
                 uint32_t index;
                 if (!graph.addConstantToPool(constant->value(), &index))
                     return false;
                 alloc = RValueAllocation::ConstantPool(index);
             }
             break;
           }
           case MIRType_MagicOptimizedArguments:
+          case MIRType_MagicOptimizedOut:
           {
             uint32_t index;
-            if (!graph.addConstantToPool(MagicValue(JS_OPTIMIZED_ARGUMENTS), &index))
+            JSWhyMagic why = (type == MIRType_MagicOptimizedArguments
+                              ? JS_OPTIMIZED_ARGUMENTS
+                              : JS_OPTIMIZED_OUT);
+            Value v = MagicValue(why);
+            if (!graph.addConstantToPool(v, &index))
                 return false;
             alloc = RValueAllocation::ConstantPool(index);
             break;
           }
           default:
           {
             JS_ASSERT(mir->type() == MIRType_Value);
             LAllocation *payload = snapshot->payloadOfSlot(i);
--- a/js/src/jsinfer.h
+++ b/js/src/jsinfer.h
@@ -291,16 +291,23 @@ class Type
     static inline Type ObjectType(TypeObject *obj);
     static inline Type ObjectType(TypeObjectKey *obj);
 };
 
 /* Get the type of a jsval, or zero for an unknown special value. */
 inline Type GetValueType(const Value &val);
 
 /*
+ * Get the type of a possibly optimized out value. This generally only
+ * happens on unconditional type monitors on bailing out of Ion, such as
+ * for argument and local types.
+ */
+inline Type GetMaybeOptimizedOutValueType(const Value &val);
+
+/*
  * Type inference memory management overview.
  *
  * Type information about the values observed within scripts and about the
  * contents of the heap is accumulated as the program executes. Compilation
  * accumulates constraints relating type information on the heap with the
  * compilations that should be invalidated when those types change. Type
  * information and constraints are allocated in the zone's typeLifoAlloc,
  * and on GC all data referring to live things is copied into a new allocator.
--- a/js/src/jsinferinlines.h
+++ b/js/src/jsinferinlines.h
@@ -105,16 +105,24 @@ GetValueType(const Value &val)
 {
     if (val.isDouble())
         return Type::DoubleType();
     if (val.isObject())
         return Type::ObjectType(&val.toObject());
     return Type::PrimitiveType(val.extractNonDoubleType());
 }
 
+inline Type
+GetMaybeOptimizedOutValueType(const Value &val)
+{
+    if (val.isMagic() && val.whyMagic() == JS_OPTIMIZED_OUT)
+        return Type::UndefinedType();
+    return GetValueType(val);
+}
+
 inline TypeFlags
 PrimitiveTypeFlag(JSValueType type)
 {
     switch (type) {
       case JSVAL_TYPE_UNDEFINED:
         return TYPE_FLAG_UNDEFINED;
       case JSVAL_TYPE_NULL:
         return TYPE_FLAG_NULL;
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -5657,16 +5657,17 @@ dumpValue(const Value &v)
     } else if (v.isMagic()) {
         fprintf(stderr, "<invalid");
 #ifdef DEBUG
         switch (v.whyMagic()) {
           case JS_ELEMENTS_HOLE:     fprintf(stderr, " elements hole");      break;
           case JS_NATIVE_ENUMERATE:  fprintf(stderr, " native enumeration"); break;
           case JS_NO_ITER_VALUE:     fprintf(stderr, " no iter value");      break;
           case JS_GENERATOR_CLOSING: fprintf(stderr, " generator closing");  break;
+          case JS_OPTIMIZED_OUT:     fprintf(stderr, " optimized out");      break;
           default:                   fprintf(stderr, " ?!");                 break;
         }
 #endif
         fprintf(stderr, ">");
     } else {
         fprintf(stderr, "unexpected value");
     }
 }
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -3476,20 +3476,23 @@ js::SetFrameArgumentsObject(JSContext *c
          * is assigned to.
          */
         jsbytecode *pc = script->code();
         while (*pc != JSOP_ARGUMENTS)
             pc += GetBytecodeLength(pc);
         pc += JSOP_ARGUMENTS_LENGTH;
         JS_ASSERT(*pc == JSOP_SETALIASEDVAR);
 
-        if (frame.callObj().as<ScopeObject>().aliasedVar(pc).isMagic(JS_OPTIMIZED_ARGUMENTS))
+        // Note that here and below, it is insufficient to only check for
+        // JS_OPTIMIZED_ARGUMENTS, as Ion could have optimized out the
+        // arguments slot.
+        if (IsOptimizedPlaceholderMagicValue(frame.callObj().as<ScopeObject>().aliasedVar(pc)))
             frame.callObj().as<ScopeObject>().setAliasedVar(cx, pc, cx->names().arguments, ObjectValue(*argsobj));
     } else {
-        if (frame.unaliasedLocal(var).isMagic(JS_OPTIMIZED_ARGUMENTS))
+        if (IsOptimizedPlaceholderMagicValue(frame.unaliasedLocal(var)))
             frame.unaliasedLocal(var) = ObjectValue(*argsobj);
     }
 }
 
 /* static */ bool
 JSScript::argumentsOptimizationFailed(JSContext *cx, HandleScript script)
 {
     JS_ASSERT(script->functionNonDelazifying());
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -141,16 +141,17 @@
     macro(objectNull, objectNull, "[object Null]") \
     macro(objectNumber, objectNumber, "[object Number]") \
     macro(objectObject, objectObject, "[object Object]") \
     macro(objectString, objectString, "[object String]") \
     macro(objectUndefined, objectUndefined, "[object Undefined]") \
     macro(objectWindow, objectWindow, "[object Window]") \
     macro(of, of, "of") \
     macro(offset, offset, "offset") \
+    macro(optimizedOut, optimizedOut, "optimizedOut") \
     macro(outOfMemory, outOfMemory, "out of memory") \
     macro(parseFloat, parseFloat, "parseFloat") \
     macro(parseInt, parseInt, "parseInt") \
     macro(pattern, pattern, "pattern") \
     macro(preventExtensions, preventExtensions, "preventExtensions") \
     macro(propertyIsEnumerable, propertyIsEnumerable, "propertyIsEnumerable") \
     macro(proto, proto, "__proto__") \
     macro(prototype, prototype, "prototype") \
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -751,16 +751,29 @@ Debugger::wrapDebuggeeValue(JSContext *c
                     objects.remove(obj);
                     js_ReportOutOfMemory(cx);
                     return false;
                 }
             }
 
             vp.setObject(*dobj);
         }
+    } else if (vp.isMagic()) {
+        // Other magic values should not have escaped.
+        MOZ_ASSERT(vp.whyMagic() == JS_OPTIMIZED_OUT);
+
+        RootedObject optObj(cx, NewBuiltinClassInstance(cx, &JSObject::class_));
+        if (!optObj)
+            return false;
+
+        RootedValue trueVal(cx, BooleanValue(true));
+        if (!JSObject::defineProperty(cx, optObj, cx->names().optimizedOut, trueVal))
+            return false;
+
+        vp.setObject(*optObj);
     } else if (!cx->compartment()->wrap(cx, vp)) {
         vp.setUndefined();
         return false;
     }
 
     return true;
 }
 
@@ -5951,19 +5964,19 @@ JS_DefineDebuggerObject(JSContext *cx, H
 
     objectProto = js_InitClass(cx, debugCtor, objProto, &DebuggerObject_class,
                                DebuggerObject_construct, 0,
                                DebuggerObject_properties, DebuggerObject_methods,
                                nullptr, nullptr);
     if (!objectProto)
         return false;
     envProto = js_InitClass(cx, debugCtor, objProto, &DebuggerEnv_class,
-                                      DebuggerEnv_construct, 0,
-                                      DebuggerEnv_properties, DebuggerEnv_methods,
-                                      nullptr, nullptr);
+                            DebuggerEnv_construct, 0,
+                            DebuggerEnv_properties, DebuggerEnv_methods,
+                            nullptr, nullptr);
     if (!envProto)
         return false;
     memoryProto = js_InitClass(cx, debugCtor, objProto, &DebuggerMemory::class_,
                                DebuggerMemory::construct, 0, DebuggerMemory::properties,
                                DebuggerMemory::methods, nullptr, nullptr);
     if (!memoryProto)
         return false;
 
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -448,16 +448,19 @@ class Debugger : private mozilla::Linked
     /*
      * Like cx->compartment()->wrap(cx, vp), but for the debugger compartment.
      *
      * Preconditions: *vp is a value from a debuggee compartment; cx is in the
      * debugger's compartment.
      *
      * If *vp is an object, this produces a (new or existing) Debugger.Object
      * wrapper for it. Otherwise this is the same as JSCompartment::wrap.
+     *
+     * If *vp is a magic JS_OPTIMIZED_OUT value, this produces a plain object
+     * of the form { optimizedOut: true }.
      */
     bool wrapDebuggeeValue(JSContext *cx, MutableHandleValue vp);
 
     /*
      * Unwrap a Debug.Object, without rewrapping it for any particular debuggee
      * compartment.
      *
      * Preconditions: cx is in the debugger compartment. *vp is a value in that
--- a/js/src/vm/ScopeObject.cpp
+++ b/js/src/vm/ScopeObject.cpp
@@ -1174,17 +1174,17 @@ class DebugScopeProxy : public BaseProxy
                 } else if (JSObject *snapshot = debugScope->maybeSnapshot()) {
                     if (action == GET)
                         vp.set(snapshot->getDenseElement(bindings.numArgs() + i));
                     else
                         snapshot->setDenseElement(bindings.numArgs() + i, vp);
                 } else {
                     /* The unaliased value has been lost to the debugger. */
                     if (action == GET)
-                        vp.set(UndefinedValue());
+                        vp.set(MagicValue(JS_OPTIMIZED_OUT));
                 }
             } else {
                 JS_ASSERT(bi->kind() == Binding::ARGUMENT);
                 unsigned i = bi.frameIndex();
                 if (script->formalIsAliased(i))
                     return false;
 
                 if (maybeLiveScope) {
@@ -1203,17 +1203,17 @@ class DebugScopeProxy : public BaseProxy
                 } else if (JSObject *snapshot = debugScope->maybeSnapshot()) {
                     if (action == GET)
                         vp.set(snapshot->getDenseElement(i));
                     else
                         snapshot->setDenseElement(i, vp);
                 } else {
                     /* The unaliased value has been lost to the debugger. */
                     if (action == GET)
-                        vp.set(UndefinedValue());
+                        vp.set(MagicValue(JS_OPTIMIZED_OUT));
                 }
 
                 if (action == SET)
                     TypeScript::SetArgument(cx, script, i, vp);
             }
 
             return true;
         }