Bug 980263 - Part 1: Disable Ion OSR for loops nested in expressions r=jandem
authorAndy Wingo <wingo@igalia.com>
Fri, 07 Mar 2014 08:44:38 +0100
changeset 189607 9f0e946c9674c8141f8548711e873c9f9e1d1e91
parent 189606 6a294c3292c3460fa6becb7825701a8f2df3cade
child 189608 b9fc2eb18bd17de27d2271cf57fae3a386d2de3a
push id3503
push userraliiev@mozilla.com
push dateMon, 28 Apr 2014 18:51:11 +0000
treeherdermozilla-beta@c95ac01e332e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs980263
milestone30.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 980263 - Part 1: Disable Ion OSR for loops nested in expressions r=jandem
js/src/frontend/BytecodeEmitter.cpp
js/src/jit/BaselineCompiler.cpp
js/src/jit/BaselineCompiler.h
js/src/jit/BaselineIC.cpp
js/src/jit/Ion.cpp
js/src/jit/IonBuilder.cpp
js/src/jit/IonOptimizationLevels.cpp
js/src/jsopcode.h
js/src/vm/Opcodes.h
js/src/vm/Xdr.h
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -77,16 +77,37 @@ struct frontend::StmtInfoBCE : public St
     }
 
     ptrdiff_t &guardJump() {
         JS_ASSERT(type == STMT_TRY || type == STMT_FINALLY);
         return continues;
     }
 };
 
+
+namespace {
+
+struct LoopStmtInfo : public StmtInfoBCE
+{
+    int32_t         stackDepth;     // Stack depth when this loop was pushed.
+    uint32_t        loopDepth;      // Loop depth.
+
+    // Can we OSR into Ion from here?  True unless there is non-loop state on the stack.
+    bool            canIonOsr;
+
+    LoopStmtInfo(ExclusiveContext *cx) : StmtInfoBCE(cx) {}
+
+    static LoopStmtInfo* fromStmtInfo(StmtInfoBCE *stmt) {
+        JS_ASSERT(stmt->isLoop());
+        return static_cast<LoopStmtInfo*>(stmt);
+    }
+};
+
+} // anonymous namespace
+
 BytecodeEmitter::BytecodeEmitter(BytecodeEmitter *parent,
                                  Parser<FullParseHandler> *parser, SharedContext *sc,
                                  HandleScript script, bool insideEval, HandleScript evalCaller,
                                  bool hasGlobalScope, uint32_t lineNum, EmitterMode emitterMode)
   : sc(sc),
     parent(parent),
     script(sc->context, script),
     prolog(sc->context, lineNum),
@@ -435,33 +456,21 @@ EmitLoopEntry(ExclusiveContext *cx, Byte
         /* Update the line number, as for LOOPHEAD. */
         JS_ASSERT_IF(nextpn->isKind(PNK_STATEMENTLIST), nextpn->isArity(PN_LIST));
         if (nextpn->isKind(PNK_STATEMENTLIST) && nextpn->pn_head)
             nextpn = nextpn->pn_head;
         if (!UpdateSourceCoordNotes(cx, bce, nextpn->pn_pos.begin))
             return false;
     }
 
-    /*
-     * Calculate loop depth. Note that this value is just a hint, so
-     * give up for deeply nested loops.
-     */
-    uint32_t loopDepth = 0;
-    StmtInfoBCE *stmt = bce->topStmt;
-    while (stmt) {
-        if (stmt->isLoop()) {
-            loopDepth++;
-            if (loopDepth >= 5)
-                break;
-        }
-        stmt = stmt->down;
-    }
-
-    JS_ASSERT(loopDepth > 0);
-    return Emit2(cx, bce, JSOP_LOOPENTRY, uint8_t(loopDepth)) >= 0;
+    LoopStmtInfo *loop = LoopStmtInfo::fromStmtInfo(bce->topStmt);
+    JS_ASSERT(loop->loopDepth > 0);
+
+    uint8_t loopDepthAndFlags = PackLoopEntryDepthHintAndFlags(loop->loopDepth, loop->canIonOsr);
+    return Emit2(cx, bce, JSOP_LOOPENTRY, loopDepthAndFlags) >= 0;
 }
 
 /*
  * If op is JOF_TYPESET (see the type barriers comment in jsinfer.h), reserve
  * a type set to store its result.
  */
 static inline void
 CheckTypeSet(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp op)
@@ -656,22 +665,61 @@ BackPatch(ExclusiveContext *cx, Bytecode
     }
     return true;
 }
 
 #define SET_STATEMENT_TOP(stmt, top)                                          \
     ((stmt)->update = (top), (stmt)->breaks = (stmt)->continues = (-1))
 
 static void
-PushStatementBCE(BytecodeEmitter *bce, StmtInfoBCE *stmt, StmtType type, ptrdiff_t top)
+PushStatementInner(BytecodeEmitter *bce, StmtInfoBCE *stmt, StmtType type, ptrdiff_t top)
 {
     SET_STATEMENT_TOP(stmt, top);
     PushStatement(bce, stmt, type);
 }
 
+static void
+PushStatementBCE(BytecodeEmitter *bce, StmtInfoBCE *stmt, StmtType type, ptrdiff_t top)
+{
+    PushStatementInner(bce, stmt, type, top);
+    JS_ASSERT(!stmt->isLoop());
+}
+
+static void
+PushLoopStatement(BytecodeEmitter *bce, LoopStmtInfo *stmt, StmtType type, ptrdiff_t top)
+{
+    PushStatementInner(bce, stmt, type, top);
+    JS_ASSERT(stmt->isLoop());
+
+    LoopStmtInfo *downLoop = nullptr;
+    for (StmtInfoBCE *outer = stmt->down; outer; outer = outer->down) {
+        if (outer->isLoop()) {
+            downLoop = LoopStmtInfo::fromStmtInfo(outer);
+            break;
+        }
+    }
+
+    stmt->stackDepth = bce->stackDepth;
+    stmt->loopDepth = downLoop ? downLoop->loopDepth + 1 : 1;
+
+    int loopSlots;
+    if (type == STMT_FOR_OF_LOOP)
+        loopSlots = 2;
+    else if (type == STMT_FOR_IN_LOOP)
+        loopSlots = 1;
+    else
+        loopSlots = 0;
+
+    if (downLoop)
+        stmt->canIonOsr = (downLoop->canIonOsr &&
+                           stmt->stackDepth == downLoop->stackDepth + loopSlots);
+    else
+        stmt->canIonOsr = stmt->stackDepth == loopSlots;
+}
+
 /*
  * Return the enclosing lexical scope, which is the innermost enclosing static
  * block object or compiler created function.
  */
 static JSObject *
 EnclosingStaticScope(BytecodeEmitter *bce)
 {
     if (bce->staticScope)
@@ -4404,18 +4452,18 @@ EmitForOf(ExclusiveContext *cx, Bytecode
 
     // Enter the block before the loop body, after evaluating the obj.
     StmtInfoBCE letStmt(cx);
     if (letDecl) {
         if (!EnterBlockScope(cx, bce, &letStmt, pn1->pn_objbox, 0))
             return false;
     }
 
-    StmtInfoBCE stmtInfo(cx);
-    PushStatementBCE(bce, &stmtInfo, STMT_FOR_OF_LOOP, top);
+    LoopStmtInfo stmtInfo(cx);
+    PushLoopStatement(bce, &stmtInfo, STMT_FOR_OF_LOOP, top);
 
     // Jump down to the loop condition to minimize overhead assuming at least
     // one iteration, as the other loop forms do.  Annotate so IonMonkey can
     // find the loop-closing jump.
     int noteIndex = NewSrcNote(cx, bce, SRC_FOR_OF);
     if (noteIndex < 0)
         return false;
     ptrdiff_t jmp = EmitJump(cx, bce, JSOP_GOTO, 0);
@@ -4544,18 +4592,18 @@ EmitForIn(ExclusiveContext *cx, Bytecode
 
     /* Enter the block before the loop body, after evaluating the obj. */
     StmtInfoBCE letStmt(cx);
     if (letDecl) {
         if (!EnterBlockScope(cx, bce, &letStmt, pn1->pn_objbox, 0))
             return false;
     }
 
-    StmtInfoBCE stmtInfo(cx);
-    PushStatementBCE(bce, &stmtInfo, STMT_FOR_IN_LOOP, top);
+    LoopStmtInfo stmtInfo(cx);
+    PushLoopStatement(bce, &stmtInfo, STMT_FOR_IN_LOOP, top);
 
     /* Annotate so IonMonkey can find the loop-closing jump. */
     int noteIndex = NewSrcNote(cx, bce, SRC_FOR_IN);
     if (noteIndex < 0)
         return false;
 
     /*
      * Jump down to the loop condition to minimize overhead assuming at
@@ -4630,18 +4678,18 @@ EmitForIn(ExclusiveContext *cx, Bytecode
     }
 
     return true;
 }
 
 static bool
 EmitNormalFor(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t top)
 {
-    StmtInfoBCE stmtInfo(cx);
-    PushStatementBCE(bce, &stmtInfo, STMT_FOR_LOOP, top);
+    LoopStmtInfo stmtInfo(cx);
+    PushLoopStatement(bce, &stmtInfo, STMT_FOR_LOOP, top);
 
     ParseNode *forHead = pn->pn_left;
     ParseNode *forBody = pn->pn_right;
 
     /* C-style for (init; cond; update) ... loop. */
     JSOp op = JSOP_POP;
     ParseNode *pn3 = forHead->pn_kid1;
     if (!pn3) {
@@ -4947,18 +4995,18 @@ EmitDo(ExclusiveContext *cx, BytecodeEmi
     if (noteIndex2 < 0)
         return false;
 
     /* Compile the loop body. */
     ptrdiff_t top = EmitLoopHead(cx, bce, pn->pn_left);
     if (top < 0)
         return false;
 
-    StmtInfoBCE stmtInfo(cx);
-    PushStatementBCE(bce, &stmtInfo, STMT_DO_LOOP, top);
+    LoopStmtInfo stmtInfo(cx);
+    PushLoopStatement(bce, &stmtInfo, STMT_DO_LOOP, top);
 
     if (!EmitLoopEntry(cx, bce, nullptr))
         return false;
 
     if (!EmitTree(cx, bce, pn->pn_left))
         return false;
 
     /* Set loop and enclosing label update offsets, for continue. */
@@ -5005,18 +5053,18 @@ EmitWhile(ExclusiveContext *cx, Bytecode
      *  i    test at the top                 test at the bottom
      *  =    ===============                 ==================
      *  0    ifeq-pass                       goto; ifne-fail
      *  1    ifeq-fail; goto; ifne-pass      goto; ifne-pass; ifne-fail
      *  2    2*(ifeq-fail; goto); ifeq-pass  goto; 2*ifne-pass; ifne-fail
      *  . . .
      *  N    N*(ifeq-fail; goto); ifeq-pass  goto; N*ifne-pass; ifne-fail
      */
-    StmtInfoBCE stmtInfo(cx);
-    PushStatementBCE(bce, &stmtInfo, STMT_WHILE_LOOP, top);
+    LoopStmtInfo stmtInfo(cx);
+    PushLoopStatement(bce, &stmtInfo, STMT_WHILE_LOOP, top);
 
     ptrdiff_t noteIndex = NewSrcNote(cx, bce, SRC_WHILE);
     if (noteIndex < 0)
         return false;
 
     ptrdiff_t jmp = EmitJump(cx, bce, JSOP_GOTO, 0);
     if (jmp < 0)
         return false;
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -607,17 +607,17 @@ BaselineCompiler::emitInterruptCheck()
     if (!callVM(InterruptCheckInfo))
         return false;
 
     masm.bind(&done);
     return true;
 }
 
 bool
-BaselineCompiler::emitUseCountIncrement()
+BaselineCompiler::emitUseCountIncrement(bool allowOsr)
 {
     // Emit no use count increments or bailouts if Ion is not
     // enabled, or if the script will never be Ion-compileable
 
     if (!ionCompileable_ && !ionOSRCompileable_)
         return true;
 
     Register scriptReg = R2.scratchReg();
@@ -631,16 +631,22 @@ BaselineCompiler::emitUseCountIncrement(
 
     // If this is a loop inside a catch or finally block, increment the use
     // count but don't attempt OSR (Ion only compiles the try block).
     if (analysis_.info(pc).loopEntryInCatchOrFinally) {
         JS_ASSERT(JSOp(*pc) == JSOP_LOOPENTRY);
         return true;
     }
 
+    // OSR not possible at this loop entry.
+    if (!allowOsr) {
+        JS_ASSERT(JSOp(*pc) == JSOP_LOOPENTRY);
+        return true;
+    }
+
     Label skipCall;
 
     const OptimizationInfo *info = js_IonOptimizations.get(js_IonOptimizations.firstLevel());
     uint32_t minUses = info->usesBeforeCompile(script, pc);
     masm.branch32(Assembler::LessThan, countReg, Imm32(minUses), &skipCall);
 
     masm.branchPtr(Assembler::Equal,
                    Address(scriptReg, JSScript::offsetOfIonScript()),
@@ -1059,17 +1065,17 @@ BaselineCompiler::emit_JSOP_LOOPHEAD()
 {
     return emitInterruptCheck();
 }
 
 bool
 BaselineCompiler::emit_JSOP_LOOPENTRY()
 {
     frame.syncStack(0);
-    return emitUseCountIncrement();
+    return emitUseCountIncrement(LoopEntryCanIonOsr(pc));
 }
 
 bool
 BaselineCompiler::emit_JSOP_VOID()
 {
     frame.pop();
     frame.push(UndefinedValue());
     return true;
--- a/js/src/jit/BaselineCompiler.h
+++ b/js/src/jit/BaselineCompiler.h
@@ -210,17 +210,17 @@ class BaselineCompiler : public Baseline
         return emitIC(stub, true);
     }
     bool emitNonOpIC(ICStub *stub) {
         return emitIC(stub, false);
     }
 
     bool emitStackCheck(bool earlyCheck=false);
     bool emitInterruptCheck();
-    bool emitUseCountIncrement();
+    bool emitUseCountIncrement(bool allowOsr=true);
     bool emitArgumentTypeChecks();
     bool emitDebugPrologue();
     bool emitDebugTrap();
     bool emitSPSPush();
     void emitSPSPop();
 
     bool initScopeChain();
 
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -760,16 +760,17 @@ EnsureCanEnterIon(JSContext *cx, ICUseCo
     JS_ASSERT(jitcodePtr);
     JS_ASSERT(!*jitcodePtr);
 
     bool isLoopEntry = (JSOp(*pc) == JSOP_LOOPENTRY);
 
     bool isConstructing = IsTopFrameConstructing(cx);
     MethodStatus stat;
     if (isLoopEntry) {
+        JS_ASSERT(LoopEntryCanIonOsr(pc));
         IonSpew(IonSpew_BaselineOSR, "  Compile at loop entry!");
         stat = CanEnterAtBranch(cx, script, frame, pc, isConstructing);
     } else if (frame->isFunctionFrame()) {
         IonSpew(IonSpew_BaselineOSR, "  Compile function from top for later entry!");
         stat = CompileFunctionForBaseline(cx, script, frame, isConstructing);
     } else {
         return true;
     }
@@ -903,16 +904,18 @@ DoUseCountFallback(JSContext *cx, ICUseC
     // A TI OOM will disable TI and Ion.
     if (!jit::IsIonEnabled(cx))
         return true;
 
     RootedScript script(cx, frame->script());
     jsbytecode *pc = stub->icEntry()->pc(script);
     bool isLoopEntry = JSOp(*pc) == JSOP_LOOPENTRY;
 
+    JS_ASSERT(!isLoopEntry || LoopEntryCanIonOsr(pc));
+
     FallbackICSpew(cx, stub, "UseCount(%d)", isLoopEntry ? int(script->pcToOffset(pc)) : int(-1));
 
     if (!script->canIonCompile()) {
         // TODO: ASSERT that ion-compilation-disabled checker stub doesn't exist.
         // TODO: Clear all optimized stubs.
         // TODO: Add a ion-compilation-disabled checker IC stub
         script->resetUseCount();
         return true;
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -1896,17 +1896,17 @@ GetOptimizationLevel(HandleScript script
 }
 
 static MethodStatus
 Compile(JSContext *cx, HandleScript script, BaselineFrame *osrFrame, jsbytecode *osrPc,
         bool constructing, ExecutionMode executionMode)
 {
     JS_ASSERT(jit::IsIonEnabled(cx));
     JS_ASSERT(jit::IsBaselineEnabled(cx));
-    JS_ASSERT_IF(osrPc != nullptr, (JSOp)*osrPc == JSOP_LOOPENTRY);
+    JS_ASSERT_IF(osrPc != nullptr, LoopEntryCanIonOsr(osrPc));
     JS_ASSERT_IF(executionMode == ParallelExecution, !osrFrame && !osrPc);
     JS_ASSERT_IF(executionMode == ParallelExecution, !HasIonScript(script, executionMode));
 
     if (!script->hasBaselineScript())
         return Method_Skipped;
 
     if (cx->compartment()->debugMode()) {
         IonSpew(IonSpew_Abort, "debugging");
@@ -1995,16 +1995,17 @@ Compile(JSContext *cx, HandleScript scri
 // Decide if a transition from interpreter execution to Ion code should occur.
 // May compile or recompile the target JSScript.
 MethodStatus
 jit::CanEnterAtBranch(JSContext *cx, JSScript *script, BaselineFrame *osrFrame,
                       jsbytecode *pc, bool isConstructing)
 {
     JS_ASSERT(jit::IsIonEnabled(cx));
     JS_ASSERT((JSOp)*pc == JSOP_LOOPENTRY);
+    JS_ASSERT(LoopEntryCanIonOsr(pc));
 
     // Skip if the script has been disabled.
     if (!script->canIonCompile())
         return Method_Skipped;
 
     // Skip if the script is being compiled off thread.
     if (script->isIonCompilingOffThread())
         return Method_Skipped;
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -5624,17 +5624,17 @@ IonBuilder::newBlock(MBasicBlock *predec
     MBasicBlock *block = MBasicBlock::New(graph(), &analysis(), info(),
                                           predecessor, pc, MBasicBlock::NORMAL);
     return addBlock(block, loopDepth);
 }
 
 MBasicBlock *
 IonBuilder::newOsrPreheader(MBasicBlock *predecessor, jsbytecode *loopEntry)
 {
-    JS_ASSERT((JSOp)*loopEntry == JSOP_LOOPENTRY);
+    JS_ASSERT(LoopEntryCanIonOsr(loopEntry));
     JS_ASSERT(loopEntry == info().osrPc());
 
     // Create two blocks: one for the OSR entry with no predecessors, one for
     // the preheader, which has the OSR entry block as a predecessor. The
     // OSR block is always the second block (with id 1).
     MBasicBlock *osrBlock  = newBlockAfter(*graph().begin(), loopEntry);
     MBasicBlock *preheader = newBlock(predecessor, loopEntry);
     if (!osrBlock || !preheader)
--- a/js/src/jit/IonOptimizationLevels.cpp
+++ b/js/src/jit/IonOptimizationLevels.cpp
@@ -81,17 +81,17 @@ OptimizationInfo::usesBeforeCompile(JSSc
         minUses = minUses * (numLocalsAndArgs / (double) MAX_MAIN_THREAD_LOCALS_AND_ARGS);
 
     if (!pc || js_JitOptions.eagerCompilation)
         return minUses;
 
     // It's more efficient to enter outer loops, rather than inner loops, via OSR.
     // To accomplish this, we use a slightly higher threshold for inner loops.
     // Note that the loop depth is always > 0 so we will prefer non-OSR over OSR.
-    uint32_t loopDepth = GET_UINT8(pc);
+    uint32_t loopDepth = LoopEntryDepthHint(pc);
     JS_ASSERT(loopDepth > 0);
     return minUses + loopDepth * 100;
 }
 
 OptimizationInfos::OptimizationInfos()
 {
     infos_[Optimization_Normal - 1].initNormalOptimizationInfo();
     infos_[Optimization_AsmJS - 1].initAsmjsOptimizationInfo();
--- a/js/src/jsopcode.h
+++ b/js/src/jsopcode.h
@@ -203,16 +203,34 @@ SET_UINT32_INDEX(jsbytecode *pc, uint32_
 #define ARGNO_LIMIT             UINT16_LIMIT
 
 #define GET_LOCALNO(pc)         GET_UINT24(pc)
 #define SET_LOCALNO(pc,varno)   SET_UINT24(pc,varno)
 #define LOCALNO_LEN             3
 #define LOCALNO_BITS            24
 #define LOCALNO_LIMIT           (1 << LOCALNO_BITS)
 
+static inline unsigned
+LoopEntryDepthHint(jsbytecode *pc)
+{
+    JS_ASSERT(*pc == JSOP_LOOPENTRY);
+    return GET_UINT8(pc) & 0x7f;
+}
+static inline bool
+LoopEntryCanIonOsr(jsbytecode *pc)
+{
+    JS_ASSERT(*pc == JSOP_LOOPENTRY);
+    return GET_UINT8(pc) & 0x80;
+}
+static inline uint8_t
+PackLoopEntryDepthHintAndFlags(unsigned loopDepth, bool canIonOsr)
+{
+    return (loopDepth < 0x80 ? uint8_t(loopDepth) : 0x7f) | (canIonOsr ? 0x80 : 0);
+}
+
 /*
  * Describes the 'hops' component of a JOF_SCOPECOORD opcode.
  *
  * Note: this component is only 8 bits wide, limiting the maximum number of
  * scopes between a use and def to roughly 255. This is a pretty small limit but
  * note that SpiderMonkey's recursive descent parser can only parse about this
  * many functions before hitting the C-stack recursion limit so this shouldn't
  * be a significant limitation in practice.
--- a/js/src/vm/Opcodes.h
+++ b/js/src/vm/Opcodes.h
@@ -472,19 +472,22 @@ 1234567890123456789012345678901234567890
     \
     /* Pop the stack, convert to a jsid (int or string), and push back. */ \
     macro(JSOP_TOID,          225, "toid",         NULL,  1,  1,  1,  JOF_BYTE) \
     \
     /* Push the implicit 'this' value for calls to the associated name. */ \
     macro(JSOP_IMPLICITTHIS,  226, "implicitthis", "",    5,  0,  1,  JOF_ATOM) \
     \
     /*
-     * This opcode is the target of the entry jump for some loop. The uint8 argument
-     * is the loop depth. This value starts at 1 and is just a hint: deeply
-     * nested loops all have the same value.
+     * This opcode is the target of the entry jump for some loop. The uint8
+     * argument is a bitfield. The lower 7 bits of the argument indicate the
+     * loop depth. This value starts at 1 and is just a hint: deeply nested
+     * loops all have the same value.  The upper bit is set if Ion should be
+     * able to OSR at this point, which is true unless there is non-loop state
+     * on the stack.
      */ \
     macro(JSOP_LOOPENTRY,     227, "loopentry",    NULL,  2,  0,  0,  JOF_UINT8)
 
 /*
  * In certain circumstances it may be useful to "pad out" the opcode space to
  * a power of two.  Use this macro to do so.
  */
 #define FOR_EACH_TRAILING_UNUSED_OPCODE(macro) \
--- a/js/src/vm/Xdr.h
+++ b/js/src/vm/Xdr.h
@@ -18,17 +18,17 @@ namespace js {
  * Bytecode version number. Increment the subtrahend whenever JS bytecode
  * changes incompatibly.
  *
  * This version number is XDR'd near the front of xdr bytecode and
  * aborts deserialization if there is a mismatch between the current
  * and saved versions. If deserialization fails, the data should be
  * invalidated if possible.
  */
-static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - 168);
+static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - 169);
 
 class XDRBuffer {
   public:
     XDRBuffer(JSContext *cx)
       : context(cx), base(nullptr), cursor(nullptr), limit(nullptr) { }
 
     JSContext *cx() const {
         return context;