Bug 1541404 part 27 - Implement emitInterpreterLoop. r=djvj
authorJan de Mooij <jdemooij@mozilla.com>
Wed, 15 May 2019 07:42:50 +0000
changeset 532726 ad2eae2743d4ffc3c0771efea055041e13b3954c
parent 532725 6138ca044e5c74d47923dac3accd8cafba1cc1a0
child 532727 e00d479b6a92650637b9347d9ae18bd8da3b9493
push id11272
push userapavel@mozilla.com
push dateThu, 16 May 2019 15:28:22 +0000
treeherdermozilla-beta@2265bfc5920d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdjvj
bugs1541404
milestone68.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 1541404 part 27 - Implement emitInterpreterLoop. r=djvj This is a basic threaded interpreter design. Performance is pretty good but we can optimize it more in the future when everything else is in place. Differential Revision: https://phabricator.services.mozilla.com/D30370
js/src/jit/BaselineCompiler.cpp
js/src/jit/BaselineCompiler.h
js/src/jit/arm/MacroAssembler-arm-inl.h
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -6625,17 +6625,121 @@ bool BaselineInterpreterGenerator::emitD
     ReportOutOfMemory(cx);
     return false;
   }
 
   return true;
 }
 
 bool BaselineInterpreterGenerator::emitInterpreterLoop() {
-  MOZ_CRASH("NYI: interpreter emitInterpreterLoop");
+  Register scratch1 = R0.scratchReg();
+  Register scratch2 = R1.scratchReg();
+
+  Address pcAddr = frame.addressOfInterpreterPC();
+
+  // Entry point for interpreting a bytecode op. No registers are live. PC is
+  // loaded from frame->interpreterPC.
+  masm.bind(handler.interpretOpLabel());
+  interpretOpOffset_ = masm.currentOffset();
+
+  // Emit a patchable call for debugger breakpoints/stepping.
+  if (!emitDebugTrap()) {
+    return false;
+  }
+
+  // Load pc, bytecode op.
+  masm.loadPtr(pcAddr, PCRegAtStart);
+  masm.load8ZeroExtend(Address(PCRegAtStart, 0), scratch1);
+
+  // Jump to table[op].
+  {
+    CodeOffset label = masm.movWithPatch(ImmWord(uintptr_t(-1)), scratch2);
+    if (!tableLabels_.append(label)) {
+      return false;
+    }
+    BaseIndex pointer(scratch2, scratch1, ScalePointer);
+    masm.branchToComputedAddress(pointer);
+  }
+
+  // At the end of each op, emit code to bump the pc and jump to the
+  // next op (this is also known as a threaded interpreter).
+  auto opEpilogue = [&](JSOp op, size_t opLength) -> bool {
+    MOZ_ASSERT(masm.framePushed() == 0);
+
+    if (!BytecodeFallsThrough(op)) {
+      // Nothing to do.
+      masm.assumeUnreachable("unexpected fall through");
+      return true;
+    }
+
+    // Bump frame->interpreterICEntry if needed.
+    if (BytecodeOpHasIC(op)) {
+      masm.addPtr(Imm32(sizeof(ICEntry)), frame.addressOfInterpreterICEntry());
+    }
+
+    // Bump frame->interpreterPC, keep pc in PCRegAtStart.
+    masm.loadPtr(pcAddr, PCRegAtStart);
+    masm.addPtr(Imm32(opLength), PCRegAtStart);
+    masm.storePtr(PCRegAtStart, pcAddr);
+
+    if (!emitDebugTrap()) {
+      return false;
+    }
+
+    // Load the opcode, jump to table[op].
+    masm.load8ZeroExtend(Address(PCRegAtStart, 0), scratch1);
+    CodeOffset label = masm.movWithPatch(ImmWord(uintptr_t(-1)), scratch2);
+    if (!tableLabels_.append(label)) {
+      return false;
+    }
+    BaseIndex pointer(scratch2, scratch1, ScalePointer);
+    masm.branchToComputedAddress(pointer);
+    return true;
+  };
+
+  // Emit code for each bytecode op.
+  Label opLabels[JSOP_LIMIT];
+#define EMIT_OP(OP)                     \
+  {                                     \
+    masm.bind(&opLabels[OP]);           \
+    if (!this->emit_##OP()) {           \
+      return false;                     \
+    }                                   \
+    if (!opEpilogue(OP, OP##_LENGTH)) { \
+      return false;                     \
+    }                                   \
+  }
+  OPCODE_LIST(EMIT_OP)
+#undef EMIT_OP
+
+  // Emit code for JSOP_UNUSED* ops.
+  Label invalidOp;
+  masm.bind(&invalidOp);
+  masm.assumeUnreachable("Invalid op");
+
+  // Emit the table.
+  masm.haltingAlign(sizeof(void*));
+
+#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64)
+  size_t numInstructions = JSOP_LIMIT * (sizeof(uintptr_t) / sizeof(uint32_t));
+  AutoForbidPoolsAndNops afp(&masm, numInstructions);
+#endif
+
+  tableOffset_ = masm.currentOffset();
+
+  for (size_t i = 0; i < JSOP_LIMIT; i++) {
+    // Store a pointer to the code for the current op. If the op's label is not
+    // bound it must be a JSOP_UNUSED* op and we use |invalidOp| instead.
+    const Label& opLabel = opLabels[i];
+    uint32_t opOffset = opLabel.bound() ? opLabel.offset() : invalidOp.offset();
+    CodeLabel cl;
+    masm.writeCodePointer(&cl);
+    cl.target()->bind(opOffset);
+    masm.addCodeLabel(cl);
+  }
 
   return true;
 }
 
 bool BaselineInterpreterGenerator::generate(BaselineInterpreter& interpreter) {
   if (!emitPrologue()) {
     return false;
   }
@@ -6658,16 +6762,23 @@ bool BaselineInterpreterGenerator::gener
     return false;
   }
 
   JitCode* code = linker.newCode(cx, CodeKind::Other);
   if (!code) {
     return false;
   }
 
+  // Patch loads now that we know the tableswitch base address.
+  for (CodeOffset off : tableLabels_) {
+    Assembler::PatchDataWithValueCheck(CodeLocationLabel(code, off),
+                                       ImmPtr(code->raw() + tableOffset_),
+                                       ImmPtr((void*)-1));
+  }
+
 #ifdef JS_ION_PERF
   writePerfSpewerJitCodeProfile(code, "BaselineInterpreter");
 #endif
 
 #ifdef MOZ_VTUNE
   vtune::MarkStub(code, "BaselineInterpreter");
 #endif
 
--- a/js/src/jit/BaselineCompiler.h
+++ b/js/src/jit/BaselineCompiler.h
@@ -692,17 +692,23 @@ class BaselineInterpreterHandler {
 
   JSObject* maybeNoCloneSingletonObject() { return nullptr; }
 };
 
 using BaselineInterpreterCodeGen = BaselineCodeGen<BaselineInterpreterHandler>;
 
 class BaselineInterpreterGenerator final : private BaselineInterpreterCodeGen {
   // Offsets of patchable call instructions for debugger breakpoints/stepping.
-  js::Vector<uint32_t, 0, SystemAllocPolicy> debugTrapOffsets_;
+  Vector<uint32_t, 0, SystemAllocPolicy> debugTrapOffsets_;
+
+  // Offsets of move instructions for tableswitch base address.
+  Vector<CodeOffset, 0, SystemAllocPolicy> tableLabels_;
+
+  // Offset of the first tableswitch entry.
+  uint32_t tableOffset_ = 0;
 
   // Offset of the code to start interpreting a bytecode op.
   uint32_t interpretOpOffset_ = 0;
 
  public:
   explicit BaselineInterpreterGenerator(JSContext* cx);
 
   MOZ_MUST_USE bool generate(BaselineInterpreter& interpreter);
--- a/js/src/jit/arm/MacroAssembler-arm-inl.h
+++ b/js/src/jit/arm/MacroAssembler-arm-inl.h
@@ -1861,28 +1861,30 @@ void MacroAssembler::branchTestMagic(Con
     branchTestMagic(Assembler::NotEqual, valaddr, label);
   }
 
   branch32(cond, ToPayload(valaddr), Imm32(why), label);
   bind(&notMagic);
 }
 
 void MacroAssembler::branchToComputedAddress(const BaseIndex& addr) {
-  MOZ_ASSERT(addr.base == pc, "Unsupported jump from any other addresses.");
   MOZ_ASSERT(
       addr.offset == 0,
       "NYI: offsets from pc should be shifted by the number of instructions.");
 
   Register base = addr.base;
   uint32_t scale = Imm32::ShiftOf(addr.scale).value;
 
   ma_ldr(DTRAddr(base, DtrRegImmShift(addr.index, LSL, scale)), pc);
-  // When loading from pc, the pc is shifted to the next instruction, we
-  // add one extra instruction to accomodate for this shifted offset.
-  breakpoint();
+
+  if (base == pc) {
+    // When loading from pc, the pc is shifted to the next instruction, we
+    // add one extra instruction to accomodate for this shifted offset.
+    breakpoint();
+  }
 }
 
 void MacroAssembler::cmp32Move32(Condition cond, Register lhs, Register rhs,
                                  Register src, Register dest) {
   cmp32(lhs, rhs);
   ma_mov(src, dest, LeaveCC, cond);
 }