Merge autoland to mozilla-central. a=merge
authorGurzau Raul <rgurzau@mozilla.com>
Mon, 11 Mar 2019 11:33:57 +0200
changeset 521307 06e2967e09efb1907d37319a3c4a2be9ca44e6a0
parent 521296 1072bbe0b8c4623557a5baf44e5b01e12c624054 (current diff)
parent 521306 2883b91c8f6f0fe8ecfc762787d0ebdb06beef75 (diff)
child 521309 cdb2110b85f32263e6213ee39e6cc9dc37945a03
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone67.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
Merge autoland to mozilla-central. a=merge
js/src/jit/BaselineCompiler.cpp
--- a/dom/media/test/crashtests/crashtests.list
+++ b/dom/media/test/crashtests/crashtests.list
@@ -110,9 +110,9 @@ skip-if(verify&&isDebugBuild&&gtkWidget)
 load oscillator-ended-1.html
 load oscillator-ended-2.html
 skip-if(Android&&AndroidVersion=='22') load video-replay-after-audio-end.html # bug 1315125, bug 1358876
 # This needs to run at the end to avoid leaking busted state into other tests.
 skip-if(Android) load 691096-1.html # Bug 1365451
 load 1236639.html
 test-pref(media.navigator.permission.disabled,true) load 1388372.html
 load 1494073.html
-load 1526044.html
+skip-if(Android) load 1526044.html # Bug 1528391
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -41,16 +41,23 @@ using namespace js;
 using namespace js::jit;
 
 using mozilla::AssertedCast;
 using mozilla::Maybe;
 
 namespace js {
 namespace jit {
 
+// When generating the Baseline Interpreter, this register is guaranteed to hold
+// the bytecode pc at the start of a bytecode instruction. Instructions are free
+// to clobber this register: the frame's interpreterPC is the canonical
+// location for the pc. This register is useful to avoid loading the pc when
+// compiling simple ops like JSOP_INT8 or JSOP_GETLOCAL.
+static constexpr Register PCRegAtStart = R2.scratchReg();
+
 BaselineCompilerHandler::BaselineCompilerHandler(JSContext* cx,
                                                  MacroAssembler& masm,
                                                  TempAllocator& alloc,
                                                  JSScript* script)
     : frame_(script, masm),
       alloc_(alloc),
       analysis_(alloc, script),
       script_(script),
@@ -366,44 +373,53 @@ MethodStatus BaselineCompiler::compile()
 
 #ifdef MOZ_VTUNE
   vtune::MarkScript(code, script, "baseline");
 #endif
 
   return Method_Compiled;
 }
 
+static void LoadInt8Operand(MacroAssembler& masm, Register pc, Register dest) {
+  masm.load8SignExtend(Address(pc, sizeof(jsbytecode)), dest);
+}
+
+static void LoadUint8Operand(MacroAssembler& masm, Register pc, Register dest) {
+  masm.load8ZeroExtend(Address(pc, sizeof(jsbytecode)), dest);
+}
+
+static void LoadUint16Operand(MacroAssembler& masm, Register pc,
+                              Register dest) {
+  masm.load16ZeroExtend(Address(pc, sizeof(jsbytecode)), dest);
+}
+
+static void LoadInt32Operand(MacroAssembler& masm, Register pc, Register dest) {
+  masm.load32(Address(pc, sizeof(jsbytecode)), dest);
+}
+
+static void LoadInt32OperandSignExtendToPtr(MacroAssembler& masm, Register pc,
+                                            Register dest) {
+  masm.load32SignExtendToPtr(Address(pc, sizeof(jsbytecode)), dest);
+}
+
+static void LoadUint24Operand(MacroAssembler& masm, Register pc, size_t offset,
+                              Register dest) {
+  // Load the opcode and operand, then left shift to discard the opcode.
+  masm.load32(Address(pc, offset), dest);
+  masm.rshift32(Imm32(8), dest);
+}
+
 template <>
 void BaselineCompilerCodeGen::loadScript(Register dest) {
   masm.movePtr(ImmGCPtr(handler.script()), dest);
 }
 
 template <>
 void BaselineInterpreterCodeGen::loadScript(Register dest) {
-  // TODO(bug 1522394): consider adding interpreterScript to BaselineFrame once
-  // we are able to run benchmarks.
-
-  masm.loadPtr(frame.addressOfCalleeToken(), dest);
-
-  Label notFunction, done;
-  masm.branchTestPtr(Assembler::NonZero, dest, Imm32(CalleeTokenScriptBit),
-                     &notFunction);
-  {
-    // CalleeToken_Function or CalleeToken_FunctionConstructing.
-    masm.andPtr(Imm32(uint32_t(CalleeTokenMask)), dest);
-    masm.loadPtr(Address(dest, JSFunction::offsetOfScript()), dest);
-    masm.jump(&done);
-  }
-  masm.bind(&notFunction);
-  {
-    // CalleeToken_Script.
-    masm.andPtr(Imm32(uint32_t(CalleeTokenMask)), dest);
-  }
-
-  masm.bind(&done);
+  masm.loadPtr(frame.addressOfInterpreterScript(), dest);
 }
 
 template <>
 void BaselineCompilerCodeGen::emitInitializeLocals() {
   // Initialize all locals to |undefined|. Lexical bindings are temporal
   // dead zoned in bytecode.
 
   size_t n = frame.nlocals();
@@ -528,17 +544,20 @@ bool BaselineCompilerCodeGen::emitNextIC
     return false;
   }
 
   return true;
 }
 
 template <>
 bool BaselineInterpreterCodeGen::emitNextIC() {
-  MOZ_CRASH("NYI: interpreter emitNextIC");
+  masm.loadPtr(frame.addressOfInterpreterICEntry(), ICStubReg);
+  masm.loadPtr(Address(ICStubReg, ICEntry::offsetOfFirstStub()), ICStubReg);
+  masm.call(Address(ICStubReg, ICStub::offsetOfStubCode()));
+  return true;
 }
 
 template <typename Handler>
 void BaselineCodeGen<Handler>::prepareVMCall() {
   pushedBeforeCall_ = masm.framePushed();
 #ifdef DEBUG
   inCall_ = true;
 #endif
@@ -795,35 +814,33 @@ void BaselineInterpreterCodeGen::loadGlo
   Register scratch = dest.scratchReg();
   loadGlobalLexicalEnvironment(scratch);
   static constexpr size_t SlotOffset =
       LexicalEnvironmentObject::offsetOfThisValueOrScopeSlot();
   masm.loadValue(Address(scratch, SlotOffset), dest);
 }
 
 template <>
-void BaselineCompilerCodeGen::pushScriptArg(Register scratch) {
+void BaselineCompilerCodeGen::pushScriptArg() {
   pushArg(ImmGCPtr(handler.script()));
 }
 
 template <>
-void BaselineInterpreterCodeGen::pushScriptArg(Register scratch) {
-  loadScript(scratch);
-  pushArg(scratch);
+void BaselineInterpreterCodeGen::pushScriptArg() {
+  pushArg(frame.addressOfInterpreterScript());
 }
 
 template <>
 void BaselineCompilerCodeGen::pushBytecodePCArg() {
   pushArg(ImmPtr(handler.pc()));
 }
 
 template <>
 void BaselineInterpreterCodeGen::pushBytecodePCArg() {
-  // This will be something like pushArg(Address(...));
-  MOZ_CRASH("NYI: interpreter pushBytecodePCArg");
+  pushArg(frame.addressOfInterpreterPC());
 }
 
 template <>
 void BaselineCompilerCodeGen::pushScriptNameArg() {
   pushArg(ImmGCPtr(handler.script()->getName(handler.pc())));
 }
 
 template <>
@@ -887,16 +904,49 @@ void BaselineCompilerCodeGen::loadResume
   masm.move32(Imm32(GET_RESUMEINDEX(handler.pc())), dest);
 }
 
 template <>
 void BaselineInterpreterCodeGen::loadResumeIndexBytecodeOperand(Register dest) {
   MOZ_CRASH("NYI: interpreter loadResumeIndexBytecodeOperand");
 }
 
+template <>
+void BaselineCompilerCodeGen::loadInt32LengthBytecodeOperand(Register dest) {
+  uint32_t length = GET_UINT32(handler.pc());
+  MOZ_ASSERT(length <= INT32_MAX,
+             "the bytecode emitter must fail to compile code that would "
+             "produce a length exceeding int32_t range");
+  masm.move32(Imm32(AssertedCast<int32_t>(length)), dest);
+}
+
+template <>
+void BaselineInterpreterCodeGen::loadInt32LengthBytecodeOperand(Register dest) {
+  masm.loadPtr(frame.addressOfInterpreterPC(), dest);
+  LoadInt32Operand(masm, dest, dest);
+}
+
+template <>
+void BaselineCompilerCodeGen::loadInt32IndexBytecodeOperand(ValueOperand dest) {
+  uint32_t index = GET_UINT32(handler.pc());
+  MOZ_ASSERT(index <= INT32_MAX,
+             "the bytecode emitter must fail to compile code that would "
+             "produce an index exceeding int32_t range");
+  masm.moveValue(Int32Value(AssertedCast<int32_t>(index)), dest);
+}
+
+template <>
+void BaselineInterpreterCodeGen::loadInt32IndexBytecodeOperand(
+    ValueOperand dest) {
+  Register scratch = dest.scratchReg();
+  masm.loadPtr(frame.addressOfInterpreterPC(), scratch);
+  LoadInt32Operand(masm, scratch, scratch);
+  masm.tagValue(JSVAL_TYPE_INT32, scratch, dest);
+}
+
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emitDebugPrologue() {
   auto ifDebuggee = [this]() {
     // Load pointer to BaselineFrame in R0.
     masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg());
 
     prepareVMCall();
     pushBytecodePCArg();
@@ -978,17 +1028,17 @@ bool BaselineCompilerCodeGen::initEnviro
     }
   } else if (!handler.module()) {
     // EnvironmentChain pointer in BaselineFrame has already been initialized
     // in prologue, but we need to check for redeclaration errors in global and
     // eval scripts.
 
     prepareVMCall();
 
-    pushScriptArg(R2.scratchReg());
+    pushScriptArg();
     masm.loadPtr(frame.addressOfEnvironmentChain(), R0.scratchReg());
     pushArg(R0.scratchReg());
 
     using Fn = bool (*)(JSContext*, HandleObject, HandleScript);
     if (!callVMNonOp<Fn, js::CheckGlobalOrEvalDeclarationConflicts>(phase)) {
       return false;
     }
   }
@@ -1323,17 +1373,19 @@ bool BaselineCodeGen<Handler>::emit_JSOP
 template <>
 bool BaselineCompilerCodeGen::emit_JSOP_POPN() {
   frame.popn(GET_UINT16(handler.pc()));
   return true;
 }
 
 template <>
 bool BaselineInterpreterCodeGen::emit_JSOP_POPN() {
-  MOZ_CRASH("NYI: interpreter JSOP_POPN");
+  LoadUint16Operand(masm, PCRegAtStart, R0.scratchReg());
+  frame.popn(R0.scratchReg());
+  return true;
 }
 
 template <>
 bool BaselineCompilerCodeGen::emit_JSOP_DUPAT() {
   frame.syncStack(0);
 
   // DUPAT takes a value on the stack and re-pushes it on top.  It's like
   // GETLOCAL but it addresses from the top of the stack instead of from the
@@ -1342,17 +1394,20 @@ bool BaselineCompilerCodeGen::emit_JSOP_
   int depth = -(GET_UINT24(handler.pc()) + 1);
   masm.loadValue(frame.addressOfStackValue(depth), R0);
   frame.push(R0);
   return true;
 }
 
 template <>
 bool BaselineInterpreterCodeGen::emit_JSOP_DUPAT() {
-  MOZ_CRASH("NYI: interpreter JSOP_DUPAT");
+  LoadUint24Operand(masm, PCRegAtStart, 0, R0.scratchReg());
+  masm.loadValue(frame.addressOfStackValue(R0.scratchReg()), R0);
+  frame.push(R0);
+  return true;
 }
 
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_DUP() {
   // Keep top stack value in R0, sync the rest so that we can use R1. We use
   // separate registers because every register can be used by at most one
   // StackValue.
   frame.popRegsAndSync(1);
@@ -1411,31 +1466,53 @@ bool BaselineCompilerCodeGen::emit_JSOP_
   // Push R0.
   frame.pop();
   frame.push(R0);
   return true;
 }
 
 template <>
 bool BaselineInterpreterCodeGen::emit_JSOP_PICK() {
-  MOZ_CRASH("NYI: interpreter JSOP_PICK");
+  // First, move the value to move up into R0.
+  LoadUint8Operand(masm, PCRegAtStart, PCRegAtStart);
+  masm.loadValue(frame.addressOfStackValue(PCRegAtStart), R0);
+
+  // Move the other values down.
+  Label top, done;
+  masm.bind(&top);
+  masm.sub32(Imm32(1), PCRegAtStart);
+  masm.branchTest32(Assembler::Signed, PCRegAtStart, PCRegAtStart, &done);
+  {
+    masm.loadValue(frame.addressOfStackValue(PCRegAtStart), R1);
+    masm.storeValue(R1, frame.addressOfStackValue(PCRegAtStart, sizeof(Value)));
+    masm.jump(&top);
+  }
+
+  masm.bind(&done);
+
+  // Replace value on top of the stack with R0.
+  masm.storeValue(R0, frame.addressOfStackValue(-1));
+  return true;
 }
 
 template <>
 bool BaselineCompilerCodeGen::emit_JSOP_UNPICK() {
   frame.syncStack(0);
 
   // Pick takes the top of the stack value and moves it under the nth value.
   // For instance, unpick 2:
   //     before: A B C D E
   //     after : A B E C D
 
   // First, move value at -1 into R0.
   masm.loadValue(frame.addressOfStackValue(-1), R0);
 
+  MOZ_ASSERT(GET_INT8(handler.pc()) > 0,
+             "Interpreter code assumes JSOP_UNPICK operand > 0");
+
   // Move the other values up.
   int32_t depth = -(GET_INT8(handler.pc()) + 1);
   for (int32_t i = -1; i > depth; i--) {
     Address source = frame.addressOfStackValue(i - 1);
     Address dest = frame.addressOfStackValue(i);
     masm.loadValue(source, R1);
     masm.storeValue(R1, dest);
   }
@@ -1443,33 +1520,82 @@ bool BaselineCompilerCodeGen::emit_JSOP_
   // Store R0 under the nth value.
   Address dest = frame.addressOfStackValue(depth);
   masm.storeValue(R0, dest);
   return true;
 }
 
 template <>
 bool BaselineInterpreterCodeGen::emit_JSOP_UNPICK() {
-  MOZ_CRASH("NYI: interpreter JSOP_UNPICK");
+  LoadUint8Operand(masm, PCRegAtStart, PCRegAtStart);
+
+  // Move the top value into R0.
+  masm.loadValue(frame.addressOfStackValue(-1), R0);
+
+  // Overwrite the nth stack value with R0 but first save the old value in R1.
+  masm.loadValue(frame.addressOfStackValue(PCRegAtStart), R1);
+  masm.storeValue(R0, frame.addressOfStackValue(PCRegAtStart));
+
+  // Now for each slot x in [n-1, 1] do the following:
+  //
+  // * Store the value in slot x in R0.
+  // * Store the value in the previous slot (now in R1) in slot x.
+  // * Move R0 to R1.
+
+#ifdef DEBUG
+  // Assert the operand > 0 so the sub32 below doesn't "underflow" to negative
+  // values.
+  {
+    Label ok;
+    masm.branch32(Assembler::GreaterThan, PCRegAtStart, Imm32(0), &ok);
+    masm.assumeUnreachable("JSOP_UNPICK with operand <= 0?");
+    masm.bind(&ok);
+  }
+#endif
+
+  Label top, done;
+  masm.bind(&top);
+  masm.sub32(Imm32(1), PCRegAtStart);
+  masm.branchTest32(Assembler::Zero, PCRegAtStart, PCRegAtStart, &done);
+  {
+    // Overwrite stack slot x with slot x + 1, saving the old value in R1.
+    masm.loadValue(frame.addressOfStackValue(PCRegAtStart), R0);
+    masm.storeValue(R1, frame.addressOfStackValue(PCRegAtStart));
+    masm.moveValue(R0, R1);
+    masm.jump(&top);
+  }
+
+  // Finally, replace the value on top of the stack (slot 0) with R1. This is
+  // the value that used to be in slot 1.
+  masm.bind(&done);
+  masm.storeValue(R1, frame.addressOfStackValue(-1));
+  return true;
 }
 
 template <>
 void BaselineCompilerCodeGen::emitJump() {
   jsbytecode* pc = handler.pc();
   MOZ_ASSERT(IsJumpOpcode(JSOp(*pc)));
   frame.assertSyncedStack();
 
   jsbytecode* target = pc + GET_JUMP_OFFSET(pc);
   masm.jump(handler.labelOf(target));
 }
 
 template <>
 void BaselineInterpreterCodeGen::emitJump() {
-  // We have to add the current pc's jump offset to the frame's pc.
-  MOZ_CRASH("NYI: interpreter emitJump");
+  // We have to add the current pc's jump offset to the frame's pc. We can use
+  // R0 as scratch because we jump to the "next op" label and that assumes a
+  // synced stack.
+  Register scratch = R0.scratchReg();
+  masm.loadPtr(frame.addressOfInterpreterPC(), scratch);
+  LoadInt32OperandSignExtendToPtr(masm, scratch, scratch);
+  masm.addPtr(frame.addressOfInterpreterPC(), scratch);
+  masm.storePtr(scratch, frame.addressOfInterpreterPC());
+  masm.jump(handler.interpretOpLabel());
 }
 
 template <>
 void BaselineCompilerCodeGen::emitTestBooleanTruthy(bool branchIfTrue,
                                                     ValueOperand val) {
   jsbytecode* pc = handler.pc();
   MOZ_ASSERT(IsJumpOpcode(JSOp(*pc)));
   frame.assertSyncedStack();
@@ -1979,50 +2105,62 @@ bool BaselineCodeGen<Handler>::emit_JSOP
 template <>
 bool BaselineCompilerCodeGen::emit_JSOP_INT8() {
   frame.push(Int32Value(GET_INT8(handler.pc())));
   return true;
 }
 
 template <>
 bool BaselineInterpreterCodeGen::emit_JSOP_INT8() {
-  MOZ_CRASH("NYI: interpreter JSOP_INT8");
+  LoadInt8Operand(masm, PCRegAtStart, R0.scratchReg());
+  masm.tagValue(JSVAL_TYPE_INT32, R0.scratchReg(), R0);
+  frame.push(R0);
+  return true;
 }
 
 template <>
 bool BaselineCompilerCodeGen::emit_JSOP_INT32() {
   frame.push(Int32Value(GET_INT32(handler.pc())));
   return true;
 }
 
 template <>
 bool BaselineInterpreterCodeGen::emit_JSOP_INT32() {
-  MOZ_CRASH("NYI: interpreter JSOP_INT32");
+  LoadInt32Operand(masm, PCRegAtStart, R0.scratchReg());
+  masm.tagValue(JSVAL_TYPE_INT32, R0.scratchReg(), R0);
+  frame.push(R0);
+  return true;
 }
 
 template <>
 bool BaselineCompilerCodeGen::emit_JSOP_UINT16() {
   frame.push(Int32Value(GET_UINT16(handler.pc())));
   return true;
 }
 
 template <>
 bool BaselineInterpreterCodeGen::emit_JSOP_UINT16() {
-  MOZ_CRASH("NYI: interpreter JSOP_UINT16");
+  LoadUint16Operand(masm, PCRegAtStart, R0.scratchReg());
+  masm.tagValue(JSVAL_TYPE_INT32, R0.scratchReg(), R0);
+  frame.push(R0);
+  return true;
 }
 
 template <>
 bool BaselineCompilerCodeGen::emit_JSOP_UINT24() {
   frame.push(Int32Value(GET_UINT24(handler.pc())));
   return true;
 }
 
 template <>
 bool BaselineInterpreterCodeGen::emit_JSOP_UINT24() {
-  MOZ_CRASH("NYI: interpreter JSOP_UINT24");
+  LoadUint24Operand(masm, PCRegAtStart, 0, R0.scratchReg());
+  masm.tagValue(JSVAL_TYPE_INT32, R0.scratchReg(), R0);
+  frame.push(R0);
+  return true;
 }
 
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_RESUMEINDEX() {
   return emit_JSOP_UINT24();
 }
 
 template <>
@@ -2083,17 +2221,17 @@ bool BaselineCodeGen<Handler>::emit_JSOP
   if (JSObject* obj = handler.maybeNoCloneSingletonObject()) {
     frame.push(ObjectValue(*obj));
     return true;
   }
 
   prepareVMCall();
 
   pushBytecodePCArg();
-  pushScriptArg(R2.scratchReg());
+  pushScriptArg();
 
   using Fn = JSObject* (*)(JSContext*, HandleScript, jsbytecode*);
   if (!callVM<Fn, SingletonObjectLiteralOperation>()) {
     return false;
   }
 
   // Box and push return value.
   masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0);
@@ -2113,17 +2251,17 @@ bool BaselineCompilerCodeGen::emit_JSOP_
   return true;
 }
 
 template <>
 bool BaselineInterpreterCodeGen::emit_JSOP_CALLSITEOBJ() {
   prepareVMCall();
 
   pushBytecodePCArg();
-  pushScriptArg(R2.scratchReg());
+  pushScriptArg();
 
   using Fn = ArrayObject* (*)(JSContext*, HandleScript, jsbytecode*);
   if (!callVM<Fn, ProcessCallSiteObjOperation>()) {
     return false;
   }
 
   // Box and push return value.
   masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0);
@@ -2403,42 +2541,32 @@ bool BaselineCodeGen<Handler>::emit_JSOP
   return emit_JSOP_GOTO();
 }
 
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_LINENO() {
   return true;
 }
 
-template <>
-bool BaselineCompilerCodeGen::emit_JSOP_NEWARRAY() {
+template <typename Handler>
+bool BaselineCodeGen<Handler>::emit_JSOP_NEWARRAY() {
   frame.syncStack(0);
 
-  uint32_t length = GET_UINT32(handler.pc());
-  MOZ_ASSERT(length <= INT32_MAX,
-             "the bytecode emitter must fail to compile code that would "
-             "produce JSOP_NEWARRAY with a length exceeding int32_t range");
-
   // Pass length in R0.
-  masm.move32(Imm32(AssertedCast<int32_t>(length)), R0.scratchReg());
+  loadInt32LengthBytecodeOperand(R0.scratchReg());
 
   if (!emitNextIC()) {
     return false;
   }
 
   frame.push(R0);
   return true;
 }
 
 template <>
-bool BaselineInterpreterCodeGen::emit_JSOP_NEWARRAY() {
-  MOZ_CRASH("NYI: interpreter JSOP_NEWARRAY");
-}
-
-template <>
 bool BaselineCompilerCodeGen::emit_JSOP_NEWARRAY_COPYONWRITE() {
   // This is like the interpreter implementation, but we can call
   // getOrFixupCopyOnWriteObject at compile-time.
 
   RootedScript scriptRoot(cx, handler.script());
   JSObject* obj =
       ObjectGroup::getOrFixupCopyOnWriteObject(cx, scriptRoot, handler.pc());
   if (!obj) {
@@ -2460,58 +2588,48 @@ bool BaselineCompilerCodeGen::emit_JSOP_
   return true;
 }
 
 template <>
 bool BaselineInterpreterCodeGen::emit_JSOP_NEWARRAY_COPYONWRITE() {
   prepareVMCall();
 
   pushBytecodePCArg();
-  pushScriptArg(R2.scratchReg());
+  pushScriptArg();
 
   using Fn = ArrayObject* (*)(JSContext*, HandleScript, jsbytecode*);
   if (!callVM<Fn, NewArrayCopyOnWriteOperation>()) {
     return false;
   }
 
   // Box and push return value.
   masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0);
   frame.push(R0);
   return true;
 }
 
-template <>
-bool BaselineCompilerCodeGen::emit_JSOP_INITELEM_ARRAY() {
+template <typename Handler>
+bool BaselineCodeGen<Handler>::emit_JSOP_INITELEM_ARRAY() {
   // Keep the object and rhs on the stack.
   frame.syncStack(0);
 
   // Load object in R0, index in R1.
   masm.loadValue(frame.addressOfStackValue(-2), R0);
-  uint32_t index = GET_UINT32(handler.pc());
-  MOZ_ASSERT(index <= INT32_MAX,
-             "the bytecode emitter must fail to compile code that would "
-             "produce JSOP_INITELEM_ARRAY with a length exceeding "
-             "int32_t range");
-  masm.moveValue(Int32Value(AssertedCast<int32_t>(index)), R1);
+  loadInt32IndexBytecodeOperand(R1);
 
   // Call IC.
   if (!emitNextIC()) {
     return false;
   }
 
   // Pop the rhs, so that the object is on the top of the stack.
   frame.pop();
   return true;
 }
 
-template <>
-bool BaselineInterpreterCodeGen::emit_JSOP_INITELEM_ARRAY() {
-  MOZ_CRASH("NYI: interpreter JSOP_INITELEM_ARRAY");
-}
-
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_NEWOBJECT() {
   frame.syncStack(0);
 
   if (!emitNextIC()) {
     return false;
   }
 
@@ -3288,17 +3406,17 @@ template <>
 bool BaselineInterpreterCodeGen::emit_JSOP_GETIMPORT() {
   frame.syncStack(0);
 
   masm.loadPtr(frame.addressOfEnvironmentChain(), R0.scratchReg());
 
   prepareVMCall();
 
   pushBytecodePCArg();
-  pushScriptArg(R2.scratchReg());
+  pushScriptArg();
   pushArg(R0.scratchReg());
 
   using Fn = bool (*)(JSContext*, HandleObject, HandleScript, jsbytecode*,
                       MutableHandleValue);
   if (!callVM<Fn, GetImportOperation>()) {
     return false;
   }
 
@@ -3327,32 +3445,32 @@ template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_SETINTRINSIC() {
   frame.syncStack(0);
   masm.loadValue(frame.addressOfStackValue(-1), R0);
 
   prepareVMCall();
 
   pushArg(R0);
   pushBytecodePCArg();
-  pushScriptArg(R2.scratchReg());
+  pushScriptArg();
 
   using Fn = bool (*)(JSContext*, JSScript*, jsbytecode*, HandleValue);
   return callVM<Fn, SetIntrinsicOperation>();
 }
 
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_DEFVAR() {
   frame.syncStack(0);
 
   masm.loadPtr(frame.addressOfEnvironmentChain(), R0.scratchReg());
 
   prepareVMCall();
 
   pushBytecodePCArg();
-  pushScriptArg(R2.scratchReg());
+  pushScriptArg();
   pushArg(R0.scratchReg());
 
   using Fn = bool (*)(JSContext*, HandleObject, HandleScript, jsbytecode*);
   return callVM<Fn, DefVarOperation>();
 }
 
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emitDefLexical(JSOp op) {
@@ -3360,17 +3478,17 @@ bool BaselineCodeGen<Handler>::emitDefLe
 
   frame.syncStack(0);
 
   masm.loadPtr(frame.addressOfEnvironmentChain(), R0.scratchReg());
 
   prepareVMCall();
 
   pushBytecodePCArg();
-  pushScriptArg(R2.scratchReg());
+  pushScriptArg();
   pushArg(R0.scratchReg());
 
   using Fn = bool (*)(JSContext*, HandleObject, HandleScript, jsbytecode*);
   return callVM<Fn, DefLexicalOperation>();
 }
 
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_DEFCONST() {
@@ -3387,17 +3505,17 @@ bool BaselineCodeGen<Handler>::emit_JSOP
   frame.popRegsAndSync(1);
   masm.unboxObject(R0, R0.scratchReg());
   masm.loadPtr(frame.addressOfEnvironmentChain(), R1.scratchReg());
 
   prepareVMCall();
 
   pushArg(R0.scratchReg());
   pushArg(R1.scratchReg());
-  pushScriptArg(R2.scratchReg());
+  pushScriptArg();
 
   using Fn = bool (*)(JSContext*, HandleScript, HandleObject, HandleFunction);
   return callVM<Fn, DefFunOperation>();
 }
 
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emitInitPropGetterSetter() {
   // Keep values on the stack for the decompiler.
@@ -3519,35 +3637,54 @@ bool BaselineCodeGen<Handler>::emit_JSOP
 }
 
 template <>
 bool BaselineCompilerCodeGen::emit_JSOP_GETLOCAL() {
   frame.pushLocal(GET_LOCALNO(handler.pc()));
   return true;
 }
 
+static BaseValueIndex ComputeAddressOfLocal(MacroAssembler& masm,
+                                            Register indexScratch) {
+  // Locals are stored in memory at a negative offset from the frame pointer. We
+  // negate the index first to effectively subtract it.
+  masm.negPtr(indexScratch);
+  return BaseValueIndex(BaselineFrameReg, indexScratch,
+                        BaselineFrame::reverseOffsetOfLocal(0));
+}
+
 template <>
 bool BaselineInterpreterCodeGen::emit_JSOP_GETLOCAL() {
-  MOZ_CRASH("NYI: interpreter JSOP_GETLOCAL");
+  Register scratch = R0.scratchReg();
+  LoadUint24Operand(masm, PCRegAtStart, 0, scratch);
+  BaseValueIndex addr = ComputeAddressOfLocal(masm, scratch);
+  masm.loadValue(addr, R0);
+  frame.push(R0);
+  return true;
 }
 
 template <>
 bool BaselineCompilerCodeGen::emit_JSOP_SETLOCAL() {
   // Ensure no other StackValue refers to the old value, for instance i + (i =
   // 3). This also allows us to use R0 as scratch below.
   frame.syncStack(1);
 
   uint32_t local = GET_LOCALNO(handler.pc());
   frame.storeStackValue(-1, frame.addressOfLocal(local), R0);
   return true;
 }
 
 template <>
 bool BaselineInterpreterCodeGen::emit_JSOP_SETLOCAL() {
-  MOZ_CRASH("NYI: interpreter JSOP_SETLOCAL");
+  Register scratch = R0.scratchReg();
+  LoadUint24Operand(masm, PCRegAtStart, 0, scratch);
+  BaseValueIndex addr = ComputeAddressOfLocal(masm, scratch);
+  masm.loadValue(frame.addressOfStackValue(-1), R1);
+  masm.storeValue(R1, addr);
+  return true;
 }
 
 template <>
 bool BaselineCompilerCodeGen::emitFormalArgAccess(JSOp op) {
   MOZ_ASSERT(op == JSOP_GETARG || op == JSOP_SETARG);
 
   uint32_t arg = GET_ARGNO(handler.pc());
 
@@ -3582,38 +3719,35 @@ bool BaselineCompilerCodeGen::emitFormal
       frame.storeStackValue(-1, frame.addressOfArg(arg), R0);
     }
     masm.jump(&done);
     masm.bind(&hasArgsObj);
   }
 
   // Load the arguments object data vector.
   Register reg = R2.scratchReg();
-  masm.loadPtr(
-      Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfArgsObj()), reg);
+  masm.loadPtr(frame.addressOfArgsObj(), reg);
   masm.loadPrivate(Address(reg, ArgumentsObject::getDataSlotOffset()), reg);
 
   // Load/store the argument.
   Address argAddr(reg, ArgumentsData::offsetOfArgs() + arg * sizeof(Value));
   if (op == JSOP_GETARG) {
     masm.loadValue(argAddr, R0);
     frame.push(R0);
   } else {
     Register temp = R1.scratchReg();
     masm.guardedCallPreBarrierAnyZone(argAddr, MIRType::Value, temp);
     masm.loadValue(frame.addressOfStackValue(-1), R0);
     masm.storeValue(R0, argAddr);
 
     MOZ_ASSERT(frame.numUnsyncedSlots() == 0);
 
-    // Reload the arguments object
+    // Reload the arguments object.
     Register reg = R2.scratchReg();
-    masm.loadPtr(
-        Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfArgsObj()),
-        reg);
+    masm.loadPtr(frame.addressOfArgsObj(), reg);
 
     Label skipBarrier;
 
     masm.branchPtrInNurseryChunk(Assembler::Equal, reg, temp, &skipBarrier);
     masm.branchValueIsNurseryCell(Assembler::NotEqual, R0, temp, &skipBarrier);
 
     masm.call(&postBarrierSlot_);
 
@@ -3621,17 +3755,77 @@ bool BaselineCompilerCodeGen::emitFormal
   }
 
   masm.bind(&done);
   return true;
 }
 
 template <>
 bool BaselineInterpreterCodeGen::emitFormalArgAccess(JSOp op) {
-  MOZ_CRASH("NYI: interpreter emitFormalArgAccess");
+  MOZ_ASSERT(op == JSOP_GETARG || op == JSOP_SETARG);
+
+  // Load the index.
+  Register argReg = R1.scratchReg();
+  LoadUint16Operand(masm, PCRegAtStart, argReg);
+
+  // If the frame has no arguments object, this must be an unaliased access.
+  Label isUnaliased, done;
+  masm.branchTest32(Assembler::Zero, frame.addressOfFlags(),
+                    Imm32(BaselineFrame::HAS_ARGS_OBJ), &isUnaliased);
+  {
+    Register reg = R2.scratchReg();
+
+    // If it's an unmapped arguments object, this is an unaliased access.
+    loadScript(reg);
+    masm.branchTest32(
+        Assembler::Zero, Address(reg, JSScript::offsetOfImmutableFlags()),
+        Imm32(uint32_t(JSScript::ImmutableFlags::HasMappedArgsObj)),
+        &isUnaliased);
+
+    // Load the arguments object data vector.
+    masm.loadPtr(frame.addressOfArgsObj(), reg);
+    masm.loadPrivate(Address(reg, ArgumentsObject::getDataSlotOffset()), reg);
+
+    // Load/store the argument.
+    BaseValueIndex argAddr(reg, argReg, ArgumentsData::offsetOfArgs());
+    if (op == JSOP_GETARG) {
+      masm.loadValue(argAddr, R0);
+      frame.push(R0);
+    } else {
+      masm.guardedCallPreBarrierAnyZone(argAddr, MIRType::Value,
+                                        R0.scratchReg());
+      masm.loadValue(frame.addressOfStackValue(-1), R0);
+      masm.storeValue(R0, argAddr);
+
+      // Reload the arguments object.
+      masm.loadPtr(frame.addressOfArgsObj(), reg);
+
+      Register temp = R1.scratchReg();
+      masm.branchPtrInNurseryChunk(Assembler::Equal, reg, temp, &done);
+      masm.branchValueIsNurseryCell(Assembler::NotEqual, R0, temp, &done);
+
+      masm.call(&postBarrierSlot_);
+    }
+    masm.jump(&done);
+  }
+  masm.bind(&isUnaliased);
+  {
+    BaseValueIndex addr(BaselineFrameReg, argReg,
+                        BaselineFrame::offsetOfArg(0));
+    if (op == JSOP_GETARG) {
+      masm.loadValue(addr, R0);
+      frame.push(R0);
+    } else {
+      masm.loadValue(frame.addressOfStackValue(-1), R0);
+      masm.storeValue(R0, addr);
+    }
+  }
+
+  masm.bind(&done);
+  return true;
 }
 
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_GETARG() {
   return emitFormalArgAccess(JSOP_GETARG);
 }
 
 template <typename Handler>
@@ -3811,25 +4005,43 @@ bool BaselineCompilerCodeGen::emitCall(J
   masm.move32(Imm32(argc), R0.scratchReg());
 
   // Call IC
   if (!emitNextIC()) {
     return false;
   }
 
   // Update FrameInfo.
-  bool construct = op == JSOP_NEW || op == JSOP_SUPERCALL;
+  bool construct = IsConstructorCallOp(op);
   frame.popn(2 + argc + construct);
   frame.push(R0);
   return true;
 }
 
 template <>
 bool BaselineInterpreterCodeGen::emitCall(JSOp op) {
-  MOZ_CRASH("NYI: interpreter emitCall");
+  MOZ_ASSERT(IsCallOp(op));
+
+  // The IC expects argc in R0.
+  LoadUint16Operand(masm, PCRegAtStart, R0.scratchReg());
+  if (!emitNextIC()) {
+    return false;
+  }
+
+  // Pop the arguments. We have to reload pc/argc because the IC clobbers them.
+  // The return value is in R0 so we can't use that.
+  Register scratch = R1.scratchReg();
+  uint32_t extraValuesToPop = IsConstructorCallOp(op) ? 3 : 2;
+  Register spReg = AsRegister(masm.getStackPointer());
+  masm.loadPtr(frame.addressOfInterpreterPC(), scratch);
+  LoadUint16Operand(masm, scratch, scratch);
+  masm.computeEffectiveAddress(
+      BaseValueIndex(spReg, scratch, extraValuesToPop * sizeof(Value)), spReg);
+  frame.push(R0);
+  return true;
 }
 
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emitSpreadCall(JSOp op) {
   MOZ_ASSERT(IsCallOp(op));
 
   frame.syncStack(0);
   masm.move32(Imm32(1), R0.scratchReg());
@@ -5528,17 +5740,17 @@ bool BaselineCodeGen<Handler>::emit_JSOP
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_CLASSCONSTRUCTOR() {
   frame.syncStack(0);
 
   // Pass nullptr as prototype to MakeDefaultConstructor
   prepareVMCall();
   pushArg(ImmPtr(nullptr));
   pushBytecodePCArg();
-  pushScriptArg(R2.scratchReg());
+  pushScriptArg();
 
   using Fn =
       JSFunction* (*)(JSContext*, HandleScript, jsbytecode*, HandleObject);
   if (!callVM<Fn, js::MakeDefaultConstructor>()) {
     return false;
   }
 
   masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0);
@@ -5550,17 +5762,17 @@ template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_DERIVEDCONSTRUCTOR() {
   frame.popRegsAndSync(1);
 
   masm.unboxObject(R0, R0.scratchReg());
 
   prepareVMCall();
   pushArg(R0.scratchReg());
   pushBytecodePCArg();
-  pushScriptArg(R2.scratchReg());
+  pushScriptArg();
 
   using Fn =
       JSFunction* (*)(JSContext*, HandleScript, jsbytecode*, HandleObject);
   if (!callVM<Fn, js::MakeDefaultConstructor>()) {
     return false;
   }
 
   masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0);
@@ -5590,17 +5802,17 @@ bool BaselineCompilerCodeGen::emit_JSOP_
   frame.push(R0);
   return true;
 }
 
 template <>
 bool BaselineInterpreterCodeGen::emit_JSOP_IMPORTMETA() {
   prepareVMCall();
 
-  pushScriptArg(R2.scratchReg());
+  pushScriptArg();
 
   using Fn = JSObject* (*)(JSContext*, HandleScript);
   if (!callVM<Fn, ImportMetaOperation>()) {
     return false;
   }
 
   masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0);
   frame.push(R0);
@@ -5609,17 +5821,17 @@ bool BaselineInterpreterCodeGen::emit_JS
 
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_DYNAMIC_IMPORT() {
   // Put specifier value in R0.
   frame.popRegsAndSync(1);
 
   prepareVMCall();
   pushArg(R0);
-  pushScriptArg(R2.scratchReg());
+  pushScriptArg();
 
   using Fn = JSObject* (*)(JSContext*, HandleScript, HandleValue);
   if (!callVM<Fn, js::StartDynamicModuleImport>()) {
     return false;
   }
 
   masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0);
   frame.push(R0);
--- a/js/src/jit/BaselineCompiler.h
+++ b/js/src/jit/BaselineCompiler.h
@@ -299,33 +299,35 @@ class BaselineCodeGen {
   explicit BaselineCodeGen(JSContext* cx, HandlerArgs&&... args);
 
   template <typename T>
   void pushArg(const T& t) {
     masm.Push(t);
   }
 
   // Pushes the current script as argument for a VM function.
-  void pushScriptArg(Register scratch);
+  void pushScriptArg();
 
   // Pushes the bytecode pc as argument for a VM function.
   void pushBytecodePCArg();
 
   // Pushes a name/object/scope associated with the current bytecode op (and
   // stored in the script) as argument for a VM function.
   enum class ScriptObjectType { RegExp, Function };
   void pushScriptObjectArg(ScriptObjectType type);
   void pushScriptNameArg();
   void pushScriptScopeArg();
 
   // Pushes a bytecode operand as argument for a VM function.
   void pushUint8BytecodeOperandArg();
   void pushUint16BytecodeOperandArg();
 
   void loadResumeIndexBytecodeOperand(Register dest);
+  void loadInt32LengthBytecodeOperand(Register dest);
+  void loadInt32IndexBytecodeOperand(ValueOperand dest);
 
   // Loads the current JSScript* in dest.
   void loadScript(Register dest);
 
   // Subtracts |script->nslots() * sizeof(Value)| from reg.
   void subtractScriptSlotsSize(Register reg, Register scratch);
 
   // Jump to the script's resume entry indicated by resumeIndex.
@@ -623,24 +625,27 @@ class BaselineCompiler final : private B
   MOZ_MUST_USE bool emitDebugTrap();
 
   MOZ_MUST_USE bool addPCMappingEntry(bool addIndexEntry);
 };
 
 // Interface used by BaselineCodeGen for BaselineInterpreterGenerator.
 class BaselineInterpreterHandler {
   InterpreterFrameInfo frame_;
+  Label interpretOp_;
 
  public:
   using FrameInfoT = InterpreterFrameInfo;
 
   explicit BaselineInterpreterHandler(JSContext* cx, MacroAssembler& masm);
 
   InterpreterFrameInfo& frame() { return frame_; }
 
+  Label* interpretOpLabel() { return &interpretOp_; }
+
   // Interpreter doesn't know the script and pc statically.
   jsbytecode* maybePC() const { return nullptr; }
   bool isDefinitelyLastOp() const { return false; }
   JSScript* maybeScript() const { return nullptr; }
   JSFunction* maybeFunction() const { return nullptr; }
 
   // Interpreter doesn't need to keep track of RetAddrEntries, so these methods
   // are no-ops.
--- a/js/src/jit/BaselineFrame.cpp
+++ b/js/src/jit/BaselineFrame.cpp
@@ -50,16 +50,20 @@ void BaselineFrame::trace(JSTracer* trc,
   if (isEvalFrame() && script()->isDirectEvalInFunction()) {
     TraceRoot(trc, evalNewTargetAddress(), "baseline-evalNewTarget");
   }
 
   if (hasArgsObj()) {
     TraceRoot(trc, &argsObj_, "baseline-args-obj");
   }
 
+  if (runningInInterpreter()) {
+    TraceRoot(trc, &interpreterScript_, "baseline-interpreterScript");
+  }
+
   // Trace locals and stack values.
   JSScript* script = this->script();
   size_t nfixed = script->nfixed();
   jsbytecode* pc;
   frameIterator.baselineScriptAndPc(nullptr, &pc);
   size_t nlivefixed = script->calculateLiveFixed(pc);
 
   // NB: It is possible that numValueSlots() could be zero, even if nfixed is
--- a/js/src/jit/BaselineFrame.h
+++ b/js/src/jit/BaselineFrame.h
@@ -9,32 +9,36 @@
 
 #include "jit/JitFrames.h"
 #include "vm/Stack.h"
 
 namespace js {
 namespace jit {
 
 struct BaselineDebugModeOSRInfo;
+class ICEntry;
 
 // The stack looks like this, fp is the frame pointer:
 //
 // fp+y   arguments
 // fp+x   JitFrameLayout (frame header)
 // fp  => saved frame pointer
 // fp-x   BaselineFrame
 //        locals
 //        stack values
 
 class BaselineFrame {
  public:
   enum Flags : uint32_t {
     // The frame has a valid return value. See also InterpreterFrame::HAS_RVAL.
     HAS_RVAL = 1 << 0,
 
+    // The frame is running in the Baseline interpreter instead of JIT.
+    RUNNING_IN_INTERPRETER = 1 << 1,
+
     // An initial environment has been pushed on the environment chain for
     // function frames that need a CallObject or eval frames that need a
     // VarEnvironmentObject.
     HAS_INITIAL_ENV = 1 << 2,
 
     // Frame has an arguments object, argsObj_.
     HAS_ARGS_OBJ = 1 << 4,
 
@@ -72,33 +76,43 @@ class BaselineFrame {
 
     // If set, we're handling an exception for this frame. This is set for
     // debug mode OSR sanity checking when it handles corner cases which
     // only arise during exception handling.
     HANDLING_EXCEPTION = 1 << 12,
   };
 
  protected:  // Silence Clang warning about unused private fields.
+  // The fields below are only valid if RUNNING_IN_INTERPRETER.
+  JSScript* interpreterScript_;
+  jsbytecode* interpreterPC_;
+  ICEntry* interpreterICEntry_;
+
+  JSObject* envChain_;        // Environment chain (always initialized).
+  ArgumentsObject* argsObj_;  // If HAS_ARGS_OBJ, the arguments object.
+
   // We need to split the Value into 2 fields of 32 bits, otherwise the C++
   // compiler may add some padding between the fields.
-
   union {
     struct {
       uint32_t loScratchValue_;
       uint32_t hiScratchValue_;
     };
     BaselineDebugModeOSRInfo* debugModeOSRInfo_;
   };
+
+  uint32_t flags_;
+  uint32_t frameSize_;
   uint32_t loReturnValue_;  // If HAS_RVAL, the frame's return value.
   uint32_t hiReturnValue_;
-  uint32_t frameSize_;
-  JSObject* envChain_;        // Environment chain (always initialized).
-  ArgumentsObject* argsObj_;  // If HAS_ARGS_OBJ, the arguments object.
   uint32_t overrideOffset_;   // If HAS_OVERRIDE_PC, the bytecode offset.
-  uint32_t flags_;
+#if JS_BITS_PER_WORD == 32
+  // Ensure frame is 8-byte aligned, see static_assert below.
+  uint32_t padding_;
+#endif
 
  public:
   // Distance between the frame pointer and the frame header (return address).
   // This is the old frame pointer saved in the prologue.
   static const uint32_t FramePointerOffset = sizeof(void*);
 
   MOZ_MUST_USE bool initForOsr(InterpreterFrame* fp, uint32_t numStackValues);
 
@@ -207,16 +221,28 @@ class BaselineFrame {
     if (isConstructing()) {
       return *(Value*)(reinterpret_cast<const uint8_t*>(this) +
                        BaselineFrame::Size() +
                        offsetOfArg(Max(numFormalArgs(), numActualArgs())));
     }
     return UndefinedValue();
   }
 
+  bool runningInInterpreter() const { return flags_ & RUNNING_IN_INTERPRETER; }
+
+  JSScript* interpreterScript() const {
+    MOZ_ASSERT(runningInInterpreter());
+    return interpreterScript_;
+  }
+
+  jsbytecode* interpreterPC() const {
+    MOZ_ASSERT(runningInInterpreter());
+    return interpreterPC_;
+  }
+
   bool hasReturnValue() const { return flags_ & HAS_RVAL; }
   MutableHandleValue returnValue() {
     if (!hasReturnValue()) {
       addressOfReturnValue()->setUndefined();
     }
     return MutableHandleValue::fromMarkedLocation(addressOfReturnValue());
   }
   void setReturnValue(const Value& v) {
@@ -372,21 +398,31 @@ class BaselineFrame {
     return -int(Size()) + offsetof(BaselineFrame, argsObj_);
   }
   static int reverseOffsetOfFlags() {
     return -int(Size()) + offsetof(BaselineFrame, flags_);
   }
   static int reverseOffsetOfReturnValue() {
     return -int(Size()) + offsetof(BaselineFrame, loReturnValue_);
   }
+  static int reverseOffsetOfInterpreterScript() {
+    return -int(Size()) + offsetof(BaselineFrame, interpreterScript_);
+  }
+  static int reverseOffsetOfInterpreterPC() {
+    return -int(Size()) + offsetof(BaselineFrame, interpreterPC_);
+  }
+  static int reverseOffsetOfInterpreterICEntry() {
+    return -int(Size()) + offsetof(BaselineFrame, interpreterICEntry_);
+  }
   static int reverseOffsetOfLocal(size_t index) {
     return -int(Size()) - (index + 1) * sizeof(Value);
   }
 };
 
 // Ensure the frame is 8-byte aligned (required on ARM).
-JS_STATIC_ASSERT(((sizeof(BaselineFrame) + BaselineFrame::FramePointerOffset) %
-                  8) == 0);
+static_assert(((sizeof(BaselineFrame) + BaselineFrame::FramePointerOffset) %
+               8) == 0,
+              "frame (including frame pointer) must be 8-byte aligned");
 
 }  // namespace jit
 }  // namespace js
 
 #endif /* jit_BaselineFrame_h */
--- a/js/src/jit/BaselineFrameInfo.h
+++ b/js/src/jit/BaselineFrameInfo.h
@@ -357,22 +357,28 @@ class InterpreterFrameInfo : public Fram
   }
 
   Address addressOfStackValue(int depth) const {
     MOZ_ASSERT(depth < 0);
     return Address(masm.getStackPointer(),
                    masm.framePushed() + size_t(-(depth + 1)) * sizeof(Value));
   }
 
+  BaseIndex addressOfStackValue(Register index, int32_t offset = 0) const {
+    return BaseIndex(masm.getStackPointer(), index, ValueScale, offset);
+  }
+
   void popRegsAndSync(uint32_t uses);
 
   void pop() { popn(1); }
 
   void popn(uint32_t n) { masm.addToStackPtr(Imm32(n * sizeof(Value))); }
 
+  void popn(Register reg) { masm.addToStackPtr(reg); }
+
   void popValue(ValueOperand dest) { masm.popValue(dest); }
 
   void push(const ValueOperand& val,
             JSValueType knownType = JSVAL_TYPE_UNKNOWN) {
     masm.pushValue(val);
   }
   void push(const Value& val) { masm.pushValue(val); }
 
@@ -380,14 +386,27 @@ class InterpreterFrameInfo : public Fram
   void pushEvalNewTarget() { masm.pushValue(addressOfEvalNewTarget()); }
   void pushScratchValue() { masm.pushValue(addressOfScratchValue()); }
 
   void storeStackValue(int32_t depth, const Address& dest,
                        const ValueOperand& scratch) {
     masm.loadValue(addressOfStackValue(depth), scratch);
     masm.storeValue(scratch, dest);
   }
+
+  Address addressOfInterpreterScript() const {
+    return Address(BaselineFrameReg,
+                   BaselineFrame::reverseOffsetOfInterpreterScript());
+  }
+  Address addressOfInterpreterPC() const {
+    return Address(BaselineFrameReg,
+                   BaselineFrame::reverseOffsetOfInterpreterPC());
+  }
+  Address addressOfInterpreterICEntry() const {
+    return Address(BaselineFrameReg,
+                   BaselineFrame::reverseOffsetOfInterpreterICEntry());
+  }
 };
 
 }  // namespace jit
 }  // namespace js
 
 #endif /* jit_BaselineFrameInfo_h */
--- a/js/src/jit/JSJitFrameIter.cpp
+++ b/js/src/jit/JSJitFrameIter.cpp
@@ -116,16 +116,22 @@ void JSJitFrameIter::baselineScriptAndPc
   MOZ_ASSERT(isBaselineJS());
   JSScript* script = this->script();
   if (scriptRes) {
     *scriptRes = script;
   }
 
   MOZ_ASSERT(pcRes);
 
+  if (baselineFrame()->runningInInterpreter()) {
+    MOZ_ASSERT(baselineFrame()->interpreterScript() == script);
+    *pcRes = baselineFrame()->interpreterPC();
+    return;
+  }
+
   // Use the frame's override pc, if we have one. This should only happen
   // when we're in FinishBailoutToBaseline, handling an exception or toggling
   // debug mode.
   if (jsbytecode* overridePc = baselineFrame()->maybeOverridePc()) {
     *pcRes = overridePc;
     return;
   }
 
--- a/js/src/jit/MacroAssembler.h
+++ b/js/src/jit/MacroAssembler.h
@@ -956,16 +956,17 @@ class MacroAssembler : public MacroAssem
 
   inline void divFloat32(FloatRegister src, FloatRegister dest) PER_SHARED_ARCH;
   inline void divDouble(FloatRegister src, FloatRegister dest) PER_SHARED_ARCH;
 
   inline void inc64(AbsoluteAddress dest) PER_ARCH;
 
   inline void neg32(Register reg) PER_SHARED_ARCH;
   inline void neg64(Register64 reg) DEFINED_ON(x86, x64, arm, mips32, mips64);
+  inline void negPtr(Register reg) PER_ARCH;
 
   inline void negateFloat(FloatRegister reg) PER_SHARED_ARCH;
 
   inline void negateDouble(FloatRegister reg) PER_SHARED_ARCH;
 
   inline void absFloat32(FloatRegister src, FloatRegister dest) PER_SHARED_ARCH;
   inline void absDouble(FloatRegister src, FloatRegister dest) PER_SHARED_ARCH;
 
--- a/js/src/jit/arm/MacroAssembler-arm-inl.h
+++ b/js/src/jit/arm/MacroAssembler-arm-inl.h
@@ -493,16 +493,18 @@ void MacroAssembler::inc64(AbsoluteAddre
 
 void MacroAssembler::neg32(Register reg) { ma_neg(reg, reg, SetCC); }
 
 void MacroAssembler::neg64(Register64 reg) {
   as_rsb(reg.low, reg.low, Imm8(0), SetCC);
   as_rsc(reg.high, reg.high, Imm8(0));
 }
 
+void MacroAssembler::negPtr(Register reg) { neg32(reg); }
+
 void MacroAssembler::negateDouble(FloatRegister reg) { ma_vneg(reg, reg); }
 
 void MacroAssembler::negateFloat(FloatRegister reg) { ma_vneg_f32(reg, reg); }
 
 void MacroAssembler::absFloat32(FloatRegister src, FloatRegister dest) {
   if (src != dest) {
     ma_vmov_f32(src, dest);
   }
--- a/js/src/jit/arm64/MacroAssembler-arm64-inl.h
+++ b/js/src/jit/arm64/MacroAssembler-arm64-inl.h
@@ -478,16 +478,20 @@ void MacroAssembler::inc64(AbsoluteAddre
   Add(scratch64, scratch64, Operand(1));
   Str(scratch64, MemOperand(scratchAddr64, 0));
 }
 
 void MacroAssembler::neg32(Register reg) {
   Negs(ARMRegister(reg, 32), Operand(ARMRegister(reg, 32)));
 }
 
+void MacroAssembler::negPtr(Register reg) {
+  Negs(ARMRegister(reg, 64), Operand(ARMRegister(reg, 64)));
+}
+
 void MacroAssembler::negateFloat(FloatRegister reg) {
   fneg(ARMFPRegister(reg, 32), ARMFPRegister(reg, 32));
 }
 
 void MacroAssembler::negateDouble(FloatRegister reg) {
   fneg(ARMFPRegister(reg, 64), ARMFPRegister(reg, 64));
 }
 
--- a/js/src/jit/x64/MacroAssembler-x64-inl.h
+++ b/js/src/jit/x64/MacroAssembler-x64-inl.h
@@ -259,16 +259,18 @@ void MacroAssembler::inc64(AbsoluteAddre
     ScratchRegisterScope scratch(*this);
     mov(ImmPtr(dest.addr), scratch);
     addPtr(Imm32(1), Address(scratch, 0));
   }
 }
 
 void MacroAssembler::neg64(Register64 reg) { negq(reg.reg); }
 
+void MacroAssembler::negPtr(Register reg) { negq(reg); }
+
 // ===============================================================
 // Shift functions
 
 void MacroAssembler::lshiftPtr(Imm32 imm, Register dest) {
   MOZ_ASSERT(0 <= imm.value && imm.value < 64);
   shlq(imm, dest);
 }
 
--- a/js/src/jit/x86/MacroAssembler-x86-inl.h
+++ b/js/src/jit/x86/MacroAssembler-x86-inl.h
@@ -338,16 +338,18 @@ void MacroAssembler::inc64(AbsoluteAddre
 }
 
 void MacroAssembler::neg64(Register64 reg) {
   negl(reg.low);
   adcl(Imm32(0), reg.high);
   negl(reg.high);
 }
 
+void MacroAssembler::negPtr(Register reg) { negl(reg); }
+
 // ===============================================================
 // Shift functions
 
 void MacroAssembler::lshiftPtr(Imm32 imm, Register dest) {
   MOZ_ASSERT(0 <= imm.value && imm.value < 32);
   shll(imm, dest);
 }
 
--- a/js/src/vm/BytecodeUtil.h
+++ b/js/src/vm/BytecodeUtil.h
@@ -587,21 +587,23 @@ inline bool IsCallOp(JSOp op) { return C
 
 inline bool IsCallPC(jsbytecode* pc) { return IsCallOp(JSOp(*pc)); }
 
 inline bool IsStrictEvalPC(jsbytecode* pc) {
   JSOp op = JSOp(*pc);
   return op == JSOP_STRICTEVAL || op == JSOP_STRICTSPREADEVAL;
 }
 
-inline bool IsConstructorCallPC(jsbytecode* pc) {
-  JSOp op = JSOp(*pc);
+inline bool IsConstructorCallOp(JSOp op) {
   return op == JSOP_NEW || op == JSOP_SUPERCALL || op == JSOP_SPREADNEW ||
          op == JSOP_SPREADSUPERCALL;
 }
+inline bool IsConstructorCallPC(const jsbytecode* pc) {
+  return IsConstructorCallOp(JSOp(*pc));
+}
 
 inline bool IsSpreadCallPC(jsbytecode* pc) {
   JSOp op = JSOp(*pc);
   return op == JSOP_SPREADCALL || op == JSOP_SPREADNEW ||
          op == JSOP_SPREADSUPERCALL || op == JSOP_SPREADEVAL ||
          op == JSOP_STRICTSPREADEVAL;
 }
 
--- a/js/src/vm/Opcodes.h
+++ b/js/src/vm/Opcodes.h
@@ -1974,16 +1974,17 @@
      *   Category: Statements
      *   Type: Function
      *   Operands: uint8_t prefixKind
      *   Stack: fun, name => fun
      */ \
     MACRO(JSOP_SETFUNNAME, 182, "setfunname", NULL, 2, 2, 1, JOF_UINT8) \
     /*
      * Moves the top of the stack value under the nth element of the stack.
+     * Note: n must NOT be 0.
      *
      *   Category: Operators
      *   Type: Stack Operations
      *   Operands: uint8_t n
      *   Stack: v[n], v[n-1], ..., v[1], v[0] => v[0], v[n], v[n-1], ..., v[1]
      */ \
     MACRO(JSOP_UNPICK, 183, "unpick", NULL, 2, 0, 0, JOF_UINT8) \
     /*
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -7681,76 +7681,18 @@ nsresult PresShell::EventHandler::Handle
       PopupBlocker::GetEventPopupControlState(aEvent));
 
   // FIXME. If the event was reused, we need to clear the old target,
   // bug 329430
   aEvent->mTarget = nullptr;
 
   HandlingTimeAccumulator handlingTimeAccumulator(*this, aEvent);
 
-  // 1. Give event to event manager for pre event state changes and
-  //    generation of synthetic events.
-  nsresult rv = manager->PreHandleEvent(
-      GetPresContext(), aEvent, mPresShell->mCurrentEventFrame,
-      mPresShell->mCurrentEventContent, aEventStatus, aOverrideClickTarget);
-
-  // 2. Give event to the DOM for third party and JS use.
-  if (NS_SUCCEEDED(rv)) {
-    bool wasHandlingKeyBoardEvent = nsContentUtils::IsHandlingKeyBoardEvent();
-    if (aEvent->mClass == eKeyboardEventClass) {
-      nsContentUtils::SetIsHandlingKeyBoardEvent(true);
-    }
-    // If EventStateManager or something wants reply from remote process and
-    // needs to win any other event listeners in chrome, the event is both
-    // stopped its propagation and marked as "waiting reply from remote
-    // process".  In this case, PresShell shouldn't dispatch the event into
-    // the DOM tree because they don't have a chance to stop propagation in
-    // the system event group.  On the other hand, if its propagation is not
-    // stopped, that means that the event may be reserved by chrome.  If it's
-    // reserved by chrome, the event shouldn't be sent to any remote
-    // processes.  In this case, PresShell needs to dispatch the event to
-    // the DOM tree for checking if it's reserved.
-    if (aEvent->IsAllowedToDispatchDOMEvent() &&
-        !(aEvent->PropagationStopped() &&
-          aEvent->IsWaitingReplyFromRemoteProcess())) {
-      MOZ_ASSERT(nsContentUtils::IsSafeToRunScript(),
-                 "Somebody changed aEvent to cause a DOM event!");
-      nsPresShellEventCB eventCB(mPresShell);
-      if (nsIFrame* target = mPresShell->GetCurrentEventFrame()) {
-        if (target->OnlySystemGroupDispatch(aEvent->mMessage)) {
-          aEvent->StopPropagation();
-        }
-      }
-      if (aEvent->mClass == eTouchEventClass) {
-        DispatchTouchEventToDOM(aEvent, aEventStatus, &eventCB, touchIsNew);
-      } else {
-        DispatchEventToDOM(aEvent, aEventStatus, &eventCB);
-      }
-    }
-
-    nsContentUtils::SetIsHandlingKeyBoardEvent(wasHandlingKeyBoardEvent);
-
-    if (aEvent->mMessage == ePointerUp || aEvent->mMessage == ePointerCancel) {
-      // Implicitly releasing capture for given pointer.
-      // ePointerLostCapture should be send after ePointerUp or
-      // ePointerCancel.
-      WidgetPointerEvent* pointerEvent = aEvent->AsPointerEvent();
-      MOZ_ASSERT(pointerEvent);
-      PointerEventHandler::ReleasePointerCaptureById(pointerEvent->pointerId);
-      PointerEventHandler::CheckPointerCaptureState(pointerEvent);
-    }
-
-    // 3. Give event to event manager for post event state changes and
-    //    generation of synthetic events.
-    if (!mPresShell->IsDestroying() && NS_SUCCEEDED(rv)) {
-      rv = manager->PostHandleEvent(GetPresContext(), aEvent,
-                                    mPresShell->GetCurrentEventFrame(),
-                                    aEventStatus, aOverrideClickTarget);
-    }
-  }
+  nsresult rv = DispatchEvent(manager, aEvent, touchIsNew, aEventStatus,
+                              aOverrideClickTarget);
 
   if (!mPresShell->IsDestroying() && aIsHandlingNativeEvent) {
     // Ensure that notifications to IME should be sent before getting next
     // native event from the event queue.
     // XXX Should we check the event message or event class instead of
     //     using aIsHandlingNativeEvent?
     manager->TryToFlushPendingNotificationsToIME();
   }
@@ -7800,16 +7742,87 @@ nsresult PresShell::EventHandler::Handle
     }
     default:
       break;
   }
   RecordEventHandlingResponsePerformance(aEvent);
   return rv;
 }
 
+nsresult PresShell::EventHandler::DispatchEvent(
+    EventStateManager* aEventStateManager, WidgetEvent* aEvent,
+    bool aTouchIsNew, nsEventStatus* aEventStatus,
+    nsIContent* aOverrideClickTarget) {
+  MOZ_ASSERT(aEventStateManager);
+  MOZ_ASSERT(aEvent);
+  MOZ_ASSERT(aEventStatus);
+
+  // 1. Give event to event manager for pre event state changes and
+  //    generation of synthetic events.
+  nsresult rv = aEventStateManager->PreHandleEvent(
+      GetPresContext(), aEvent, mPresShell->mCurrentEventFrame,
+      mPresShell->mCurrentEventContent, aEventStatus, aOverrideClickTarget);
+
+  // 2. Give event to the DOM for third party and JS use.
+  if (NS_SUCCEEDED(rv)) {
+    bool wasHandlingKeyBoardEvent = nsContentUtils::IsHandlingKeyBoardEvent();
+    if (aEvent->mClass == eKeyboardEventClass) {
+      nsContentUtils::SetIsHandlingKeyBoardEvent(true);
+    }
+    // If EventStateManager or something wants reply from remote process and
+    // needs to win any other event listeners in chrome, the event is both
+    // stopped its propagation and marked as "waiting reply from remote
+    // process".  In this case, PresShell shouldn't dispatch the event into
+    // the DOM tree because they don't have a chance to stop propagation in
+    // the system event group.  On the other hand, if its propagation is not
+    // stopped, that means that the event may be reserved by chrome.  If it's
+    // reserved by chrome, the event shouldn't be sent to any remote
+    // processes.  In this case, PresShell needs to dispatch the event to
+    // the DOM tree for checking if it's reserved.
+    if (aEvent->IsAllowedToDispatchDOMEvent() &&
+        !(aEvent->PropagationStopped() &&
+          aEvent->IsWaitingReplyFromRemoteProcess())) {
+      MOZ_ASSERT(nsContentUtils::IsSafeToRunScript(),
+                 "Somebody changed aEvent to cause a DOM event!");
+      nsPresShellEventCB eventCB(mPresShell);
+      if (nsIFrame* target = mPresShell->GetCurrentEventFrame()) {
+        if (target->OnlySystemGroupDispatch(aEvent->mMessage)) {
+          aEvent->StopPropagation();
+        }
+      }
+      if (aEvent->mClass == eTouchEventClass) {
+        DispatchTouchEventToDOM(aEvent, aEventStatus, &eventCB, aTouchIsNew);
+      } else {
+        DispatchEventToDOM(aEvent, aEventStatus, &eventCB);
+      }
+    }
+
+    nsContentUtils::SetIsHandlingKeyBoardEvent(wasHandlingKeyBoardEvent);
+
+    if (aEvent->mMessage == ePointerUp || aEvent->mMessage == ePointerCancel) {
+      // Implicitly releasing capture for given pointer.
+      // ePointerLostCapture should be send after ePointerUp or
+      // ePointerCancel.
+      WidgetPointerEvent* pointerEvent = aEvent->AsPointerEvent();
+      MOZ_ASSERT(pointerEvent);
+      PointerEventHandler::ReleasePointerCaptureById(pointerEvent->pointerId);
+      PointerEventHandler::CheckPointerCaptureState(pointerEvent);
+    }
+
+    // 3. Give event to event manager for post event state changes and
+    //    generation of synthetic events.
+    if (!mPresShell->IsDestroying() && NS_SUCCEEDED(rv)) {
+      rv = aEventStateManager->PostHandleEvent(
+          GetPresContext(), aEvent, mPresShell->GetCurrentEventFrame(),
+          aEventStatus, aOverrideClickTarget);
+    }
+  }
+  return rv;
+}
+
 bool PresShell::EventHandler::PrepareToDispatchEvent(WidgetEvent* aEvent) {
   if (!aEvent->IsTrusted()) {
     return false;
   }
 
   if (aEvent->IsUserAction()) {
     mPresShell->mHasHandledUserInput = true;
   }
--- a/layout/base/PresShell.h
+++ b/layout/base/PresShell.h
@@ -1144,16 +1144,37 @@ class PresShell final : public nsIPresSh
                                              nsIContent** aTargetToUse,
                                              LayoutDeviceIntPoint& aTargetPt,
                                              nsIWidget* aRootWidget);
 
     nsIContent* GetOverrideClickTarget(WidgetGUIEvent* aGUIEvent,
                                        nsIFrame* aFrame);
 
     /**
+     * DispatchEvent() tries to dispatch aEvent and notifies aEventStateManager
+     * of doing it.
+     *
+     * @param aEventStateManager        EventStateManager which should handle
+     *                                  the event before/after dispatching
+     *                                  aEvent into the DOM.
+     * @param aEvent                    The handling event.
+     * @param aTouchIsNew               Set this to true when the message is
+     *                                  eTouchMove and it's newly touched.
+     *                                  Then, the "touchmove" event becomes
+     *                                  cancelable.
+     * @param aEventStatus              [in/out] The status of aEvent.
+     * @param aOverrideClickTarget      Override click event target.
+     */
+    MOZ_CAN_RUN_SCRIPT
+    nsresult DispatchEvent(EventStateManager* aEventStateManager,
+                           WidgetEvent* aEvent, bool aTouchIsNew,
+                           nsEventStatus* aEventStatus,
+                           nsIContent* aOverrideClickTarget);
+
+    /**
      * DispatchEventToDOM() actually dispatches aEvent into the DOM tree.
      *
      * @param aEvent            Event to be dispatched into the DOM tree.
      * @param aEventStatus      [in/out] EventStatus of aEvent.
      * @param aEventCB          The callback kicked when the event moves
      *                          from the default group to the system group.
      */
     nsresult DispatchEventToDOM(WidgetEvent* aEvent,
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -914,16 +914,27 @@ bool nsIFrame::HasDisplayItem(uint32_t a
   for (nsDisplayItem* i : *items) {
     if (i->GetPerFrameKey() == aKey) {
       return true;
     }
   }
   return false;
 }
 
+void nsIFrame::DiscardOldItems() {
+  DisplayItemArray* items = GetProperty(DisplayItems());
+  if (!items) {
+    return;
+  }
+
+  for (nsDisplayItem* i : *items) {
+    i->DiscardIfOldItem();
+  }
+}
+
 void nsIFrame::RemoveDisplayItemDataForDeletion() {
   // Destroying a WebRenderUserDataTable can cause destruction of other objects
   // which can remove frame properties in their destructor. If we delete a frame
   // property it runs the destructor of the stored object in the middle of
   // updating the frame property table, so if the destruction of that object
   // causes another update to the frame property table it would leave the frame
   // property table in an inconsistent state. So we remove it from the table and
   // then destroy it. (bug 1530657)
@@ -3419,47 +3430,52 @@ static nsDisplayItem* WrapInWrapList(nsD
                                      const ActiveScrolledRoot* aContainerASR,
                                      bool aBuiltContainerItem = false) {
   nsDisplayItem* item = aList->GetBottom();
   if (!item) {
     return nullptr;
   }
 
   // We need a wrap list if there are multiple items, or if the single
-  // item has a different frame.
-  bool needsWrapList = item->GetAbove() || item->Frame() != aFrame;
-
-  // If we don't need a wrap list, and we're doing a full build, or if
-  // we have an explicit container item that guarantees to wrap all other
-  // items then we can skip.
-  if (!needsWrapList && (!aBuilder->IsPartialUpdate() || aBuiltContainerItem)) {
+  // item has a different frame. This can change in a partial build depending
+  // on which items we build, so we need to ensure that we don't transition
+  // to/from a wrap list without invalidating correctly.
+  bool needsWrapList =
+      item->GetAbove() || item->Frame() != aFrame || item->GetChildren();
+
+  // If we have an explicit container item (that can't change without an
+  // invalidation) or we're doing a full build and don't need a wrap list, then
+  // we can skip adding one.
+  if (aBuiltContainerItem || (!aBuilder->IsPartialUpdate() && !needsWrapList)) {
     aList->RemoveBottom();
     return item;
   }
 
   // If we're doing a partial build and we didn't need a wrap list
   // previously then we can try to work from there.
   if (aBuilder->IsPartialUpdate() &&
       !aFrame->HasDisplayItem(uint32_t(DisplayItemType::TYPE_WRAP_LIST))) {
-    // If we now need a wrap list, mark this frame as modified so that merging
-    // removes the unwrapped item. We don't need to add to the dirty rect since
-    // not needing a wrap list means that we must have only had a display item
-    // from this frame, which we're already building items for. All new items
-    // will have already been included in the dirty area.
+    // If we now need a wrap list, we must previously have had no display items
+    // or a single one belonging to this frame. Mark the item itself as
+    // discarded so that RetainedDisplayListBuilder uses the ones we just built.
+    // We don't want to mark the frame as modified as that would invalidate
+    // positioned descendants that might be outside of this list, and might not
+    // have been rebuilt this time.
     if (needsWrapList) {
-      aBuilder->MarkFrameModifiedDuringBuilding(aFrame);
+      aFrame->DiscardOldItems();
     } else {
       aList->RemoveBottom();
       return item;
     }
   }
 
-  // The last case is when we previously had a wrap list, but no longer
-  // need it. Unfortunately we can't differentiate this case from a partial
-  // build where other children exist but we just didn't build them.
+  // The last case we could try to handle is when we previously had a wrap list,
+  // but no longer need it. Unfortunately we can't differentiate this case from
+  // a partial build where other children exist but we just didn't build them
+  // this time.
   // TODO:RetainedDisplayListBuilder's merge phase has the full list and
   // could strip them out.
 
   // Clear clip rect for the construction of the items below. Since we're
   // clipping all their contents, they themselves don't need to be clipped.
   return MakeDisplayItem<nsDisplayWrapList>(aBuilder, aFrame, aList,
                                             aContainerASR, true);
 }
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -3737,17 +3737,18 @@ void ScrollFrameHelper::MaybeAddTopLayer
   if (mIsRoot) {
     if (ViewportFrame* viewportFrame = do_QueryFrame(mOuter->GetParent())) {
       nsDisplayList topLayerList;
       viewportFrame->BuildDisplayListForTopLayer(aBuilder, &topLayerList);
       if (!topLayerList.IsEmpty()) {
         // Wrap the whole top layer in a single item with maximum z-index,
         // and append it at the very end, so that it stays at the topmost.
         nsDisplayWrapList* wrapList = MakeDisplayItem<nsDisplayWrapList>(
-            aBuilder, viewportFrame, &topLayerList);
+            aBuilder, viewportFrame, &topLayerList,
+            aBuilder->CurrentActiveScrolledRoot(), false, 2);
         wrapList->SetOverrideZIndex(
             std::numeric_limits<decltype(wrapList->ZIndex())>::max());
         aLists.PositionedDescendants()->AppendToTop(wrapList);
       }
     }
   }
 }
 
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -4133,16 +4133,17 @@ class nsIFrame : public nsQueryFrame {
   }
 
   void AddDisplayItem(nsDisplayItem* aItem);
   bool RemoveDisplayItem(nsDisplayItem* aItem);
   void RemoveDisplayItemDataForDeletion();
   bool HasDisplayItems();
   bool HasDisplayItem(nsDisplayItem* aItem);
   bool HasDisplayItem(uint32_t aKey);
+  void DiscardOldItems();
 
   bool ForceDescendIntoIfVisible() const { return mForceDescendIntoIfVisible; }
   void SetForceDescendIntoIfVisible(bool aForce) {
     mForceDescendIntoIfVisible = aForce;
   }
 
   bool BuiltDisplayList() { return mBuiltDisplayList; }
   void SetBuiltDisplayList(bool aBuilt) { mBuiltDisplayList = aBuilt; }
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -3098,17 +3098,18 @@ nsDisplayItem::nsDisplayItem(nsDisplayLi
                              bool aAnonymous)
     : mFrame(aFrame),
       mActiveScrolledRoot(aActiveScrolledRoot),
       mAnimatedGeometryRoot(nullptr),
       mForceNotVisible(aBuilder->IsBuildingInvisibleItems()),
       mDisableSubpixelAA(false),
       mReusedItem(false),
       mBackfaceHidden(mFrame->In3DContextAndBackfaceIsHidden()),
-      mPaintRectValid(false)
+      mPaintRectValid(false),
+      mCanBeReused(true)
 #ifdef MOZ_DUMP_PAINTING
       ,
       mPainted(false)
 #endif
 {
   MOZ_COUNT_CTOR(nsDisplayItem);
   if (aBuilder->IsRetainingDisplayList() && !aAnonymous) {
     mFrame->AddDisplayItem(this);
--- a/layout/painting/nsDisplayList.h
+++ b/layout/painting/nsDisplayList.h
@@ -2096,17 +2096,18 @@ class nsDisplayItem : public nsDisplayIt
         mClip(nullptr),
         mActiveScrolledRoot(nullptr),
         mReferenceFrame(nullptr),
         mAnimatedGeometryRoot(nullptr),
         mForceNotVisible(false),
         mDisableSubpixelAA(false),
         mReusedItem(false),
         mBackfaceHidden(mFrame->In3DContextAndBackfaceIsHidden()),
-        mPaintRectValid(false)
+        mPaintRectValid(false),
+        mCanBeReused(true)
 #ifdef MOZ_DUMP_PAINTING
         ,
         mPainted(false)
 #endif
   {
     MOZ_COUNT_CTOR(nsDisplayItem);
   }
 
@@ -2170,17 +2171,18 @@ class nsDisplayItem : public nsDisplayIt
         mAnimatedGeometryRoot(aOther.mAnimatedGeometryRoot),
         mToReferenceFrame(aOther.mToReferenceFrame),
         mBuildingRect(aOther.mBuildingRect),
         mPaintRect(aOther.mPaintRect),
         mForceNotVisible(aOther.mForceNotVisible),
         mDisableSubpixelAA(aOther.mDisableSubpixelAA),
         mReusedItem(false),
         mBackfaceHidden(mFrame->In3DContextAndBackfaceIsHidden()),
-        mPaintRectValid(false)
+        mPaintRectValid(false),
+        mCanBeReused(true)
 #ifdef MOZ_DUMP_PAINTING
         ,
         mPainted(false)
 #endif
   {
     MOZ_COUNT_CTOR(nsDisplayItem);
   }
 
@@ -2839,17 +2841,22 @@ class nsDisplayItem : public nsDisplayIt
   bool HasSameContent(const nsDisplayItem* aOther) const {
     return mFrame->GetContent() == aOther->Frame()->GetContent();
   }
 
   bool IsReused() const { return mReusedItem; }
 
   void SetReused(bool aReused) { mReusedItem = aReused; }
 
-  virtual bool CanBeReused() const { return true; }
+  bool CanBeReused() const { return mCanBeReused; }
+  void DiscardIfOldItem() {
+    if (mOldList) {
+      mCanBeReused = false;
+    }
+  }
 
   virtual nsIFrame* GetDependentFrame() { return nullptr; }
 
   virtual mozilla::Maybe<nsRect> GetClipWithRespectToASR(
       nsDisplayListBuilder* aBuilder, const ActiveScrolledRoot* aASR) const;
 
   void SetDisplayItemData(mozilla::DisplayItemData* aDID,
                           mozilla::layers::LayerManager* aLayerManager) {
@@ -2953,16 +2960,17 @@ class nsDisplayItem : public nsDisplayIt
   OldListIndex mOldListIndex;
   uintptr_t mOldList = 0;
 
   bool mForceNotVisible;
   bool mDisableSubpixelAA;
   bool mReusedItem;
   bool mBackfaceHidden;
   bool mPaintRectValid;
+  bool mCanBeReused;
 #ifdef MOZ_DUMP_PAINTING
   // True if this frame has been painted.
   bool mPainted;
 #endif
 
   struct {
     RefPtr<const DisplayItemClipChain> mClipChain;
     const DisplayItemClip* mClip;
@@ -4050,22 +4058,21 @@ class nsDisplaySolidColorBase : public n
   nscolor mColor;
 };
 
 class nsDisplaySolidColor : public nsDisplaySolidColorBase {
  public:
   nsDisplaySolidColor(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
                       const nsRect& aBounds, nscolor aColor,
                       bool aCanBeReused = true)
-      : nsDisplaySolidColorBase(aBuilder, aFrame, aColor),
-        mBounds(aBounds),
-        mCanBeReused(aCanBeReused) {
+      : nsDisplaySolidColorBase(aBuilder, aFrame, aColor), mBounds(aBounds) {
     NS_ASSERTION(NS_GET_A(aColor) > 0,
                  "Don't create invisible nsDisplaySolidColors!");
     MOZ_COUNT_CTOR(nsDisplaySolidColor);
+    mCanBeReused = aCanBeReused;
   }
 
 #ifdef NS_BUILD_REFCNT_LOGGING
   ~nsDisplaySolidColor() override { MOZ_COUNT_DTOR(nsDisplaySolidColor); }
 #endif
 
   NS_DISPLAY_DECL_NAME("SolidColor", TYPE_SOLID_COLOR)
 
@@ -4079,32 +4086,30 @@ class nsDisplaySolidColor : public nsDis
   void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
   void WriteDebugInfo(std::stringstream& aStream) override;
   bool CreateWebRenderCommands(
       mozilla::wr::DisplayListBuilder& aBuilder,
       mozilla::wr::IpcResourceUpdateQueue& aResources,
       const StackingContextHelper& aSc,
       mozilla::layers::RenderRootStateManager* aManager,
       nsDisplayListBuilder* aDisplayListBuilder) override;
-  bool CanBeReused() const override { return mCanBeReused; }
 
   int32_t ZIndex() const override {
     if (mOverrideZIndex) {
       return mOverrideZIndex.value();
     }
     return nsDisplaySolidColorBase::ZIndex();
   }
 
   void SetOverrideZIndex(int32_t aZIndex) {
     mOverrideZIndex = mozilla::Some(aZIndex);
   }
 
  private:
   nsRect mBounds;
-  bool mCanBeReused;
   mozilla::Maybe<int32_t> mOverrideZIndex;
 };
 
 /**
  * A display item that renders a solid color over a region. This is not
  * exposed through CSS, its only purpose is efficient invalidation of
  * the find bar highlighter dimmer.
  */
new file mode 100644
--- /dev/null
+++ b/layout/reftests/display-list/1533317-1-ref.html
@@ -0,0 +1,15 @@
+<html>
+  <head>
+    <style>
+    div {
+      width: 400px;
+      height: 200px;
+      background-color: green;
+    }
+
+    </style>
+  </head>
+  <body>
+      <div></div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/display-list/1533317-1.html
@@ -0,0 +1,32 @@
+<html class="reftest-wait">
+  <head>
+    <style>
+    div {
+      width: 200px;
+      height: 200px;
+    }
+    .positioned {
+      position: absolute;
+      top: 0px;
+      left: 200px;
+      background-color: green;
+    }
+    .outer {
+      position: relative;
+    }
+    </style>
+  </head>
+  <body>
+    <div class="outer">
+      <div id="test"></div>
+      <div class="positioned"></div>
+    </div>
+    <script>
+      function doTest() {
+        document.getElementById("test").style.backgroundColor = "green";
+        document.documentElement.removeAttribute("class");
+      }
+      document.addEventListener("MozReftestInvalidate", doTest);
+    </script>
+  </body>
+</html>
--- a/layout/reftests/display-list/reftest.list
+++ b/layout/reftests/display-list/reftest.list
@@ -33,8 +33,9 @@ skip-if(!asyncPan) == 1437374-1.html 143
 == 1451971-1.html 1451971-ref.html
 == 1453541-1.html 1453541-ref.html
 == 1453541-2.html 1453541-ref.html
 == 1452805-1.html 1452805-ref.html
 == 1461231-1.html about:blank
 fuzzy(0-2,0-40000) skip-if(!asyncPan) == 1464288-1.html 1464288-ref.html
 == 1482403-1.html 1482403-1-ref.html
 == 1504233-1.html 1504233-1-ref.html
+== 1533317-1.html 1533317-1-ref.html
--- a/layout/xul/tree/nsTreeUtils.cpp
+++ b/layout/xul/tree/nsTreeUtils.cpp
@@ -50,16 +50,23 @@ nsresult nsTreeUtils::TokenizeProperties
 nsIContent* nsTreeUtils::GetImmediateChild(nsIContent* aContainer,
                                            nsAtom* aTag) {
   dom::FlattenedChildIterator iter(aContainer);
   for (nsIContent* child = iter.GetNextChild(); child;
        child = iter.GetNextChild()) {
     if (child->IsXULElement(aTag)) {
       return child;
     }
+    // <slot> is in the flattened tree, but <tree> code is used to work with
+    // <xbl:children> which is not, so recurse in <slot> here.
+    if (child->IsHTMLElement(nsGkAtoms::slot)) {
+      if (nsIContent* c = GetImmediateChild(child, aTag)) {
+        return c;
+      }
+    }
   }
 
   return nullptr;
 }
 
 nsIContent* nsTreeUtils::GetDescendantChild(nsIContent* aContainer,
                                             nsAtom* aTag) {
   dom::FlattenedChildIterator iter(aContainer);