Bug 688891: C++ power-armor for Sprinter. (r=cdleary)
authorAdam <adam@sigterm.info>
Fri, 20 Jan 2012 15:09:56 -0800
changeset 87575 24dac171a324a343cebc273bc6275e4016dabb0d
parent 87574 d17842ea50f24fc677eb0372fbcfaa9d524372b0
child 87576 7026011a83df40a996b40afaa413d8def439791f
push idunknown
push userunknown
push dateunknown
reviewerscdleary
bugs688891
milestone12.0a1
Bug 688891: C++ power-armor for Sprinter. (r=cdleary)
js/src/jsanalyze.cpp
js/src/jsapi.cpp
js/src/jsdbgapi.cpp
js/src/jsopcode.cpp
js/src/jsopcode.h
js/src/methodjit/Compiler.cpp
js/src/shell/js.cpp
--- a/js/src/jsanalyze.cpp
+++ b/js/src/jsanalyze.cpp
@@ -52,21 +52,21 @@ namespace analyze {
 // Bytecode
 /////////////////////////////////////////////////////////////////////
 
 #ifdef DEBUG
 void
 PrintBytecode(JSContext *cx, JSScript *script, jsbytecode *pc)
 {
     printf("#%u:", script->id());
-    LifoAlloc lifoAlloc(1024);
-    Sprinter sprinter;
-    INIT_SPRINTER(cx, &sprinter, &lifoAlloc, 0);
+    Sprinter sprinter(cx);
+    if (!sprinter.init())
+        return;
     js_Disassemble1(cx, script, pc, pc - script->code, true, &sprinter);
-    fprintf(stdout, "%s", sprinter.base);
+    fprintf(stdout, "%s", sprinter.string());
 }
 #endif
 
 /////////////////////////////////////////////////////////////////////
 // Bytecode Analysis
 /////////////////////////////////////////////////////////////////////
 
 inline bool
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -5595,17 +5595,17 @@ JS_PUBLIC_API(JSString *)
 JS_NewStringCopyZ(JSContext *cx, const char *s)
 {
     size_t n;
     jschar *js;
     JSString *str;
 
     AssertNoGC(cx);
     CHECK_REQUEST(cx);
-    if (!s)
+    if (!s || !*s)
         return cx->runtime->emptyString;
     n = strlen(s);
     js = InflateString(cx, s, &n);
     if (!js)
         return NULL;
     str = js_NewString(cx, js, n);
     if (!str)
         cx->free_(js);
--- a/js/src/jsdbgapi.cpp
+++ b/js/src/jsdbgapi.cpp
@@ -1615,40 +1615,40 @@ js_ResumeVtune()
 }
 
 #endif /* MOZ_VTUNE */
 
 JS_PUBLIC_API(void)
 JS_DumpBytecode(JSContext *cx, JSScript *script)
 {
 #if defined(DEBUG)
-    LifoAlloc lifoAlloc(1024);
-    Sprinter sprinter;
-    INIT_SPRINTER(cx, &sprinter, &lifoAlloc, 0);
+    Sprinter sprinter(cx);
+    if (!sprinter.init())
+        return;
 
     fprintf(stdout, "--- SCRIPT %s:%d ---\n", script->filename, script->lineno);
     js_Disassemble(cx, script, true, &sprinter);
-    fputs(sprinter.base, stdout);
+    fputs(sprinter.string(), stdout);
     fprintf(stdout, "--- END SCRIPT %s:%d ---\n", script->filename, script->lineno);
 #endif
 }
 
 extern JS_PUBLIC_API(void)
 JS_DumpPCCounts(JSContext *cx, JSScript *script)
 {
 #if defined(DEBUG)
     JS_ASSERT(script->pcCounters);
 
-    LifoAlloc lifoAlloc(1024);
-    Sprinter sprinter;
-    INIT_SPRINTER(cx, &sprinter, &lifoAlloc, 0);
+    Sprinter sprinter(cx);
+    if (!sprinter.init())
+        return;
 
     fprintf(stdout, "--- SCRIPT %s:%d ---\n", script->filename, script->lineno);
     js_DumpPCCounts(cx, script, &sprinter);
-    fputs(sprinter.base, stdout);
+    fputs(sprinter.string(), stdout);
     fprintf(stdout, "--- END SCRIPT %s:%d ---\n", script->filename, script->lineno);
 #endif
 }
 
 static void
 DumpBytecodeScriptCallback(JSContext *cx, void *data, void *thing,
                            JSGCTraceKind traceKind, size_t thingSize)
 {
--- a/js/src/jsopcode.cpp
+++ b/js/src/jsopcode.cpp
@@ -418,46 +418,45 @@ JS_FRIEND_API(JSBool)
 js_Disassemble(JSContext *cx, JSScript *script, JSBool lines, Sprinter *sp)
 {
     return js_DisassembleAtPC(cx, script, lines, NULL, sp);
 }
 
 JS_FRIEND_API(JSBool)
 js_DumpPC(JSContext *cx)
 {
-    LifoAllocScope las(&cx->tempLifoAlloc());
-    Sprinter sprinter;
-    INIT_SPRINTER(cx, &sprinter, &cx->tempLifoAlloc(), 0);
+    Sprinter sprinter(cx);
+    if (!sprinter.init())
+        return JS_FALSE;
     JSBool ok = js_DisassembleAtPC(cx, cx->fp()->script(), true, cx->regs().pc, &sprinter);
-    fprintf(stdout, "%s", sprinter.base);
+    fprintf(stdout, "%s", sprinter.string());
     return ok;
 }
 
 JSBool
 js_DumpScript(JSContext *cx, JSScript *script)
 {
-    LifoAllocScope las(&cx->tempLifoAlloc());
-    Sprinter sprinter;
-    INIT_SPRINTER(cx, &sprinter, &cx->tempLifoAlloc(), 0);
+    Sprinter sprinter(cx);
+    if (!sprinter.init())
+        return JS_FALSE;
     JSBool ok = js_Disassemble(cx, script, true, &sprinter);
-    fprintf(stdout, "%s", sprinter.base);
+    fprintf(stdout, "%s", sprinter.string());
     return ok;
 }
 
 static char *
 QuoteString(Sprinter *sp, JSString *str, uint32_t quote);
 
 static bool
 ToDisassemblySource(JSContext *cx, jsval v, JSAutoByteString *bytes)
 {
     if (JSVAL_IS_STRING(v)) {
-        Sprinter sprinter;
-        LifoAlloc &tla = cx->tempLifoAlloc();
-        LifoAllocScope las(&tla);
-        INIT_SPRINTER(cx, &sprinter, &tla, 0);
+        Sprinter sprinter(cx);
+        if (!sprinter.init())
+            return false;
         char *nbytes = QuoteString(&sprinter, JSVAL_TO_STRING(v), '"');
         if (!nbytes)
             return false;
         nbytes = JS_sprintf_append(NULL, "%s", nbytes);
         if (!nbytes)
             return false;
         bytes->initBytes(nbytes);
         return true;
@@ -722,107 +721,247 @@ js_Disassemble1(JSContext *cx, JSScript 
     SprintCString(sp, "\n");
     return len;
 }
 
 #endif /* DEBUG */
 
 /************************************************************************/
 
-#define OFF2STR(sp,off) ((sp)->base + (off))
-#define STR2OFF(sp,str) ((str) - (sp)->base)
-#define RETRACT(sp,str) ((sp)->offset = STR2OFF(sp, str))
-
-static JSBool
-SprintEnsureBuffer(Sprinter *sp, size_t len)
+const size_t Sprinter::DefaultSize = 64;
+
+bool 
+Sprinter::realloc_(size_t newSize)
 {
-    if (sp->offset + len < sp->size)
-        return JS_TRUE;
-
-    ptrdiff_t nb = Max(Max((size_t)1024, sp->size * 2), sp->offset + len + 1);
-    char *base = sp->base;
+    JS_ASSERT(newSize > (size_t) offset);
+    char *newBuf = (char *) context->realloc_(base, newSize);
+    if (!newBuf)
+        return false;
+    base = newBuf;
+    size = newSize;
+    base[size - 1] = 0;
+    return true;
+}
+
+Sprinter::Sprinter(JSContext *cx)
+  : context(cx),
+#ifdef DEBUG
+    initialized(false),
+#endif
+    base(NULL), size(0), offset(0)
+{ }
+
+Sprinter::~Sprinter()
+{
+#ifdef DEBUG
+    if (initialized)
+        checkInvariants();
+#endif
+    context->free_(base);
+}
+
+bool
+Sprinter::init()
+{
+    JS_ASSERT(!initialized);
+    base = (char *) context->malloc_(DefaultSize);
     if (!base)
-        base = static_cast<char *>(sp->pool->allocUnaligned(nb));
-    else
-        base = static_cast<char *>(sp->pool->reallocUnaligned(base, sp->size, nb - sp->size));
-    if (!base) {
-        js_ReportOutOfMemory(sp->context);
-        return JS_FALSE;
-    }
-    sp->base = base;
-    sp->size = nb;
-    return JS_TRUE;
+        return false;
+#ifdef DEBUG
+    initialized = true;
+#endif
+    *base = 0;
+    size = DefaultSize;
+    base[size - 1] = 0;
+    return true;
 }
 
-namespace js {
+void
+Sprinter::checkInvariants() const
+{
+    JS_ASSERT(initialized);
+    JS_ASSERT((size_t) offset < size);
+    JS_ASSERT(base[size - 1] == 0);
+}
+
+const char *
+Sprinter::string() const
+{
+    return base;
+}
+
+const char *
+Sprinter::stringEnd() const
+{
+    return base + offset;
+}
 
 char *
-SprintReserveAmount(Sprinter *sp, size_t len)
+Sprinter::stringAt(ptrdiff_t off) const
+{
+    JS_ASSERT(off >= 0 && (size_t) off < size);
+    return base + off;
+}
+
+char &
+Sprinter::operator[](size_t off)
+{
+    JS_ASSERT(off >= 0 && (size_t) off < size);
+    return *(base + off);
+}
+
+bool
+Sprinter::empty() const
 {
-    /* Allocate space for s, including the '\0' at the end. */
-    if (!SprintEnsureBuffer(sp, len))
-        return NULL;
-
-    /* Advance offset and return the previous offset for caller to fill. */
-    ptrdiff_t offset = sp->offset;
-    sp->offset += len;
-    return sp->base + offset;
+    return *base == 0;
+}
+
+char *
+Sprinter::reserve(size_t len)
+{
+    InvariantChecker ic(this);
+
+    while (len + 1 > size - offset) { /* Include trailing \0 */
+        if (!realloc_(size * 2))
+            return NULL;
+    }
+
+    char *sb = base + offset;
+    offset += len;
+    return sb;
+}
+
+char *
+Sprinter::reserveAndClear(size_t len)
+{
+    char *sb = reserve(len);
+    if (sb)
+        memset(sb, 0, len);
+    return sb;
 }
 
 ptrdiff_t
-SprintPut(Sprinter *sp, const char *s, size_t len)
+Sprinter::put(const char *s, size_t len)
 {
-    ptrdiff_t offset = sp->size; /* save old size */
-    char *bp = sp->base;         /* save old base */
-
-    /* Allocate space for s, including the '\0' at the end. */
-    if (!SprintEnsureBuffer(sp, len))
+    InvariantChecker ic(this);
+
+    const char *oldBase = base;
+    const char *oldEnd = base + size;
+
+    ptrdiff_t oldOffset = offset;
+    char *bp = reserve(len);
+    if (!bp)
+        return -1;
+
+    /* s is within the buffer already */
+    if (s >= oldBase && s < oldEnd) {
+        /* buffer was realloc'ed */
+        if (base != oldBase)
+            s = stringAt(s - oldBase);  /* this is where it lives now */
+        memmove(bp, s, len);
+    } else {
+        JS_ASSERT(s < base || s >= base + size);
+        memcpy(bp, s, len);
+    }
+
+    bp[len] = 0;
+    return oldOffset;
+}
+
+ptrdiff_t
+Sprinter::putString(JSString *s)
+{
+    InvariantChecker ic(this);
+
+    size_t length = s->length();
+    const jschar *chars = s->getChars(context);
+    if (!chars)
+        return -1;
+
+    size_t size = GetDeflatedStringLength(context, chars, length);
+    if (size == (size_t) -1)
         return -1;
 
-    if (sp->base != bp &&               /* buffer was realloc'ed */
-        s >= bp && s < bp + offset) {   /* s was within the buffer */
-        s = sp->base + (s - bp);        /* this is where it lives now */
-    }
-
-    /* Advance offset and copy s into sp's buffer. */
-    offset = sp->offset;
-    sp->offset += len;
-    bp = sp->base + offset;
-    memmove(bp, s, len);
-    bp[len] = 0;
+    ptrdiff_t oldOffset = offset;
+    char *buffer = reserve(size);
+    if (!buffer)
+        return -1;
+    DeflateStringToBuffer(context, chars, length, buffer, &size);
+    buffer[size] = 0;
+
+    return oldOffset;
+}
+
+int
+Sprinter::printf(const char *fmt, ...)
+{
+    InvariantChecker ic(this);
+
+    do {
+        va_list va;
+        va_start(va, fmt);
+        int i = vsnprintf(base + offset, size - offset, fmt, va);
+        va_end(va);
+
+        if (i > -1 && (size_t) i < size - offset) {
+            offset += i;
+            return i;
+        }
+    } while (realloc_(size * 2));
+
+    return -1;
+}
+
+void
+Sprinter::setOffset(const char *end)
+{
+    JS_ASSERT(end >= base && end < base + size);
+    offset = end - base;
+}
+
+void
+Sprinter::setOffset(ptrdiff_t off)
+{
+    JS_ASSERT(off >= 0 && (size_t) off < size);
+    offset = off;
+}
+
+ptrdiff_t
+Sprinter::getOffset() const
+{
     return offset;
 }
 
 ptrdiff_t
-SprintCString(Sprinter *sp, const char *s)
+Sprinter::getOffsetOf(const char *string) const
+{
+    JS_ASSERT(string >= base && string < base + size);
+    return string - base;
+}
+
+ptrdiff_t
+js::SprintPut(Sprinter *sp, const char *s, size_t len)
+{
+    return sp->put(s, len);
+}
+
+ptrdiff_t
+js::SprintCString(Sprinter *sp, const char *s)
 {
     return SprintPut(sp, s, strlen(s));
 }
 
 ptrdiff_t
-SprintString(Sprinter *sp, JSString *str)
+js::SprintString(Sprinter *sp, JSString *str)
 {
-    size_t length = str->length();
-    const jschar *chars = str->getChars(sp->context);
-    if (!chars)
-        return -1;
-
-    size_t size = GetDeflatedStringLength(sp->context, chars, length);
-    if (size == (size_t)-1 || !SprintEnsureBuffer(sp, size))
-        return -1;
-
-    ptrdiff_t offset = sp->offset;
-    sp->offset += size;
-    DeflateStringToBuffer(sp->context, chars, length, sp->base + offset, &size);
-    sp->base[sp->offset] = 0;
-    return offset;
+    return sp->putString(str);
 }
 
 ptrdiff_t
-Sprint(Sprinter *sp, const char *format, ...)
+js::Sprint(Sprinter *sp, const char *format, ...)
 {
     va_list ap;
     char *bp;
     ptrdiff_t offset;
 
     va_start(ap, format);
     bp = JS_vsmprintf(format, ap);      /* XXX vsaprintf */
     va_end(ap);
@@ -830,18 +969,16 @@ Sprint(Sprinter *sp, const char *format,
         JS_ReportOutOfMemory(sp->context);
         return -1;
     }
     offset = SprintCString(sp, bp);
     sp->context->free_(bp);
     return offset;
 }
 
-} // namespace js
-
 const char js_EscapeMap[] = {
     '\b', 'b',
     '\f', 'f',
     '\n', 'n',
     '\r', 'r',
     '\t', 't',
     '\v', 'v',
     '"',  '"',
@@ -853,17 +990,17 @@ const char js_EscapeMap[] = {
 #define DONT_ESCAPE     0x10000
 
 static char *
 QuoteString(Sprinter *sp, JSString *str, uint32_t quote)
 {
     /* Sample off first for later return value pointer computation. */
     JSBool dontEscape = (quote & DONT_ESCAPE) != 0;
     jschar qc = (jschar) quote;
-    ptrdiff_t off = sp->offset;
+    ptrdiff_t offset = sp->getOffset();
     if (qc && Sprint(sp, "%c", (char)qc) < 0)
         return NULL;
 
     const jschar *s = str->getChars(sp->context);
     if (!s)
         return NULL;
     const jschar *z = s + str->length();
 
@@ -871,28 +1008,28 @@ QuoteString(Sprinter *sp, JSString *str,
     for (const jschar *t = s; t < z; s = ++t) {
         /* Move t forward from s past un-quote-worthy characters. */
         jschar c = *t;
         while (c < 127 && isprint(c) && c != qc && c != '\\' && c != '\t') {
             c = *++t;
             if (t == z)
                 break;
         }
-        ptrdiff_t len = t - s;
-
-        /* Allocate space for s, including the '\0' at the end. */
-        if (!SprintEnsureBuffer(sp, len))
-            return NULL;
-
-        /* Advance sp->offset and copy s into sp's buffer. */
-        char *bp = sp->base + sp->offset;
-        sp->offset += len;
-        while (--len >= 0)
-            *bp++ = (char) *s++;
-        *bp = '\0';
+
+        {
+            ptrdiff_t len = t - s;
+            ptrdiff_t base = sp->getOffset();
+            char *bp = sp->reserve(len);
+            if (!bp)
+                return NULL;
+
+            for (ptrdiff_t i = 0; i < len; ++i)
+                (*sp)[base + i] = (char) *s++;
+            (*sp)[base + len] = 0;
+        }
 
         if (t == z)
             break;
 
         /* Use js_EscapeMap, \u, or \x only if necessary. */
         bool ok;
         const char *e;
         if (!(c >> 8) && c != 0 && (e = strchr(js_EscapeMap, (int)c)) != NULL) {
@@ -912,29 +1049,30 @@ QuoteString(Sprinter *sp, JSString *str,
     }
 
     /* Sprint the closing quote and return the quoted string. */
     if (qc && Sprint(sp, "%c", (char)qc) < 0)
         return NULL;
 
     /*
      * If we haven't Sprint'd anything yet, Sprint an empty string so that
-     * the OFF2STR below gives a valid result.
+     * the return below gives a valid result.
      */
-    if (off == sp->offset && Sprint(sp, "") < 0)
+    if (offset == sp->getOffset() && Sprint(sp, "") < 0)
         return NULL;
-    return OFF2STR(sp, off);
+
+    return sp->stringAt(offset);
 }
 
 JSString *
 js_QuoteString(JSContext *cx, JSString *str, jschar quote)
 {
-    LifoAllocScope las(&cx->tempLifoAlloc());
-    Sprinter sprinter;
-    INIT_SPRINTER(cx, &sprinter, &cx->tempLifoAlloc(), 0);
+    Sprinter sprinter(cx);
+    if (!sprinter.init())
+        return NULL;
     char *bytes = QuoteString(&sprinter, str, quote);
     JSString *escstr = bytes ? JS_NewStringCopyZ(cx, bytes) : NULL;
     return escstr;
 }
 
 /************************************************************************/
 
 /*
@@ -991,17 +1129,19 @@ struct JSPrinter
 
 JSPrinter *
 js_NewPrinter(JSContext *cx, const char *name, JSFunction *fun,
               uintN indent, JSBool pretty, JSBool grouped, JSBool strict)
 {
     JSPrinter *jp = (JSPrinter *) cx->malloc_(sizeof(JSPrinter));
     if (!jp)
         return NULL;
-    INIT_SPRINTER(cx, &jp->sprinter, &jp->pool, 0);
+    new (&jp->sprinter) Sprinter(cx);
+    if (!jp->sprinter.init())
+        return NULL;
     new (&jp->pool) LifoAlloc(1024);
     jp->indent = indent;
     jp->pretty = !!pretty;
     jp->grouped = !!grouped;
     jp->strict = !!strict;
     jp->script = NULL;
     jp->dvgfence = NULL;
     jp->pcstack = NULL;
@@ -1025,19 +1165,17 @@ js_DestroyPrinter(JSPrinter *jp)
     Foreground::delete_(jp->localNames);
     jp->sprinter.context->free_(jp);
 }
 
 JSString *
 js_GetPrinterOutput(JSPrinter *jp)
 {
     JSContext *cx = jp->sprinter.context;
-    if (!jp->sprinter.base)
-        return cx->runtime->emptyString;
-    return JS_NewStringCopyZ(cx, jp->sprinter.base);
+    return JS_NewStringCopyZ(cx, jp->sprinter.string());
 }
 
 /* Mark the parent and offset into the parent's text for a printed opcode. */
 static inline void
 UpdateDecompiledParent(JSPrinter *jp, jsbytecode *pc, jsbytecode *parent, size_t offset)
 {
     if (jp->decompiledOpcodes && pc) {
         jp->decompiled(pc).parent = parent;
@@ -1128,29 +1266,35 @@ struct SprintStack
     Sprinter    sprinter;       /* sprinter for postfix to infix buffering */
     ptrdiff_t   *offsets;       /* stack of postfix string offsets */
     jsbytecode  *opcodes;       /* parallel stack of JS opcodes */
     jsbytecode  **bytecodes;    /* actual script bytecode pushing the value */
     uintN       top;            /* top of stack index */
     uintN       inArrayInit;    /* array initialiser/comprehension level */
     JSBool      inGenExp;       /* in generator expression */
     JSPrinter   *printer;       /* permanent output goes here */
+
+    explicit SprintStack(JSContext *cx)
+      : sprinter(cx), offsets(NULL),
+        opcodes(NULL), bytecodes(NULL), top(0), inArrayInit(0),
+        inGenExp(JS_FALSE), printer(NULL)
+    { }
 };
 
 /*
  * Set the decompiled text of an opcode, according to an offset into the
  * print stack's sprinter buffer.
  */
 static inline bool
 UpdateDecompiledText(SprintStack *ss, jsbytecode *pc, ptrdiff_t todo)
 {
     JSPrinter *jp = ss->printer;
 
     if (jp->decompiledOpcodes && jp->decompiled(pc).text == NULL) {
-        const char *text = OFF2STR(&ss->sprinter, todo);
+        const char *text = ss->sprinter.stringAt(todo);
         size_t len = strlen(text) + 1;
 
         char *ntext = ss->printer->pool.newArrayUninitialized<char>(len);
         if (!ntext) {
             js_ReportOutOfMemory(ss->sprinter.context);
             return false;
         }
 
@@ -1176,34 +1320,36 @@ SprintDupeStr(SprintStack *ss, const cha
 
     return nstr;
 }
 
 /* Place an opcode's decompiled text into a printer's permanent output. */
 static inline void
 SprintOpcodePermanent(JSPrinter *jp, const char *str, jsbytecode *pc)
 {
-    UpdateDecompiledParent(jp, pc, NULL, jp->sprinter.offset);
+    ptrdiff_t offset = jp->sprinter.getOffset();
+    UpdateDecompiledParent(jp, pc, NULL, offset);
     js_printf(jp, "%s", str);
 }
 
 /*
  * Place an opcode's decompiled text into the printed output for another
  * opcode parentpc, where startOffset indicates the printer offset for the
  * start of parentpc.
  */
 static inline void
 SprintOpcode(SprintStack *ss, const char *str, jsbytecode *pc,
              jsbytecode *parentpc, ptrdiff_t startOffset)
 {
     if (startOffset < 0) {
         JS_ASSERT(ss->sprinter.context->isExceptionPending());
         return;
     }
-    UpdateDecompiledParent(ss->printer, pc, parentpc, ss->sprinter.offset - startOffset);
+    ptrdiff_t offset = ss->sprinter.getOffset();
+    UpdateDecompiledParent(ss->printer, pc, parentpc, offset - startOffset);
     SprintCString(&ss->sprinter, str);
 }
 
 /*
  * Copy the decompiled text for an opcode to all other ops which it was
  * decomposed into.
  */
 static inline void
@@ -1284,76 +1430,68 @@ GetOff(SprintStack *ss, uintN i)
         if (bytes != FAILED_EXPRESSION_DECOMPILER) {
             off = SprintCString(&ss->sprinter, bytes);
             if (off < 0)
                 off = 0;
             ss->offsets[i] = off;
             ss->sprinter.context->free_(bytes);
             return off;
         }
-        if (!ss->sprinter.base && SprintPut(&ss->sprinter, "", 0) >= 0) {
-            memset(ss->sprinter.base, 0, ss->sprinter.offset);
+
+        if (!*ss->sprinter.string()) {
+            memset(ss->sprinter.stringAt(0), 0, ss->sprinter.getOffset());
             ss->offsets[i] = -1;
         }
     }
     return 0;
 }
 
 static const char *
 GetStr(SprintStack *ss, uintN i)
 {
-    ptrdiff_t off;
-
-    /*
-     * Must call GetOff before using ss->sprinter.base, since it may be null
-     * until bootstrapped by GetOff.
-     */
-    off = GetOff(ss, i);
-    return OFF2STR(&ss->sprinter, off);
+    ptrdiff_t off = GetOff(ss, i);
+    return ss->sprinter.stringAt(off);
 }
 
 /*
  * Gap between stacked strings to allow for insertion of parens and commas
  * when auto-parenthesizing expressions and decompiling array initialisers.
  */
 #define PAREN_SLOP      (2 + 1)
 
 /* Fake opcodes (see jsopcode.h) must not overflow unsigned 8-bit space. */
 JS_STATIC_ASSERT(JSOP_FAKE_LIMIT <= 255);
 
-static void
+static inline void
 AddParenSlop(SprintStack *ss)
 {
-    memset(OFF2STR(&ss->sprinter, ss->sprinter.offset), 0, PAREN_SLOP);
-    ss->sprinter.offset += PAREN_SLOP;
+    ss->sprinter.reserveAndClear(PAREN_SLOP);
 }
 
 static JSBool
 PushOff(SprintStack *ss, ptrdiff_t off, JSOp op, jsbytecode *pc = NULL)
 {
     uintN top;
 
-    if (!SprintEnsureBuffer(&ss->sprinter, PAREN_SLOP))
-        return JS_FALSE;
-
     /* ss->top points to the next free slot; be paranoid about overflow. */
     top = ss->top;
     JS_ASSERT(top < StackDepth(ss->printer->script));
     if (top >= StackDepth(ss->printer->script)) {
         JS_ReportOutOfMemory(ss->sprinter.context);
         return JS_FALSE;
     }
 
     /* The opcodes stack must contain real bytecodes that index js_CodeSpec. */
     ss->offsets[top] = off;
     ss->opcodes[top] = jsbytecode((op == JSOP_GETPROP2) ? JSOP_GETPROP
                                 : (op == JSOP_GETELEM2) ? JSOP_GETELEM
                                 : op);
     ss->bytecodes[top] = pc;
     ss->top = ++top;
+
     AddParenSlop(ss);
     return JS_TRUE;
 }
 
 static bool
 PushStr(SprintStack *ss, const char *str, JSOp op)
 {
     ptrdiff_t off = SprintCString(&ss->sprinter, str);
@@ -1382,33 +1520,34 @@ PopOffPrec(SprintStack *ss, uint8_t prec
     off = GetOff(ss, top);
     topcs = &js_CodeSpec[ss->opcodes[top]];
 
     jsbytecode *pc = ss->bytecodes[top];
     if (ppc)
         *ppc = pc;
 
     if (topcs->prec != 0 && topcs->prec < prec) {
-        ss->sprinter.offset = ss->offsets[top] = off - 2;
-        off = Sprint(&ss->sprinter, "(%s)", OFF2STR(&ss->sprinter, off));
+        ss->offsets[top] = off - 2;
+        ss->sprinter.setOffset(off - 2);
+        off = Sprint(&ss->sprinter, "(%s)", ss->sprinter.stringAt(off));
         if (ss->printer->decompiledOpcodes && pc)
             ss->printer->decompiled(pc).parenthesized = true;
     } else {
-        ss->sprinter.offset = off;
+        ss->sprinter.setOffset(off);
     }
     return off;
 }
 
 static const char *
 PopStrPrec(SprintStack *ss, uint8_t prec, jsbytecode **ppc = NULL)
 {
     ptrdiff_t off;
 
     off = PopOffPrec(ss, prec, ppc);
-    return OFF2STR(&ss->sprinter, off);
+    return ss->sprinter.stringAt(off);
 }
 
 /*
  * As for PopStrPrec, but duplicates the string into the printer's arena.
  * Strings returned by PopStrPrec are otherwise invalidated if any new text
  * is printed into ss.
  */
 static const char *
@@ -1512,17 +1651,17 @@ Decompile(SprintStack *ss, jsbytecode *p
 static JSBool
 DecompileSwitch(SprintStack *ss, TableEntry *table, uintN tableLength,
                 jsbytecode *pc, ptrdiff_t switchLength,
                 ptrdiff_t defaultOffset, JSBool isCondSwitch)
 {
     JSContext *cx;
     JSPrinter *jp;
     ptrdiff_t off, off2, diff, caseExprOff, todo;
-    char *rval;
+    const char *rval;
     uintN i;
     jsval key;
     JSString *str;
 
     cx = ss->sprinter.context;
     jp = ss->printer;
 
     jsbytecode *lvalpc;
@@ -1592,24 +1731,24 @@ DecompileSwitch(SprintStack *ss, TableEn
                         return JS_FALSE;
                     str = NULL;
                 } else {
                     str = ToString(cx, key);
                     if (!str)
                         return JS_FALSE;
                 }
                 if (todo >= 0) {
-                    rval = OFF2STR(&ss->sprinter, todo);
+                    rval = ss->sprinter.stringAt(todo);
                 } else {
                     rval = QuoteString(&ss->sprinter, str, (jschar)
                                        (JSVAL_IS_STRING(key) ? '"' : 0));
                     if (!rval)
                         return JS_FALSE;
                 }
-                RETRACT(&ss->sprinter, rval);
+                ss->sprinter.setOffset(rval);
                 jp->indent += 2;
                 js_printf(jp, "\tcase %s:\n", rval);
             }
 
             jp->indent += 2;
             if (off <= defaultOffset && defaultOffset < off2) {
                 diff = defaultOffset - off;
                 if (diff != 0) {
@@ -1678,30 +1817,30 @@ GetLocalInSlot(SprintStack *ss, jsint i,
             if (!JSID_IS_ATOM(shape.propid()))
                 continue;
 
             JSAtom *atom = JSID_TO_ATOM(shape.propid());
             const char *rval = QuoteString(&ss->sprinter, atom, 0);
             if (!rval)
                 return NULL;
 
-            RETRACT(&ss->sprinter, rval);
+            ss->sprinter.setOffset(rval);
             return rval;
         }
     }
 
     return GetStr(ss, i);
 }
 
 const char *
 GetLocal(SprintStack *ss, jsint i)
 {
     ptrdiff_t off = ss->offsets[i];
     if (off >= 0)
-        return OFF2STR(&ss->sprinter, off);
+        return ss->sprinter.stringAt(off);
 
     /*
      * We must be called from js_DecompileValueGenerator (via Decompile) when
      * dereferencing a local that's undefined or null. Search script->objects
      * for the block containing this local by its stack index, i.
      *
      * In case of destructuring's use of JSOP_GETLOCAL, however, there may be
      * no such local. This could mean no blocks (no script objects at all, or
@@ -1917,28 +2056,28 @@ DecompileDestructuringLHS(SprintStack *s
         LOCAL_ASSERT(!letNames);
         /*
          * We may need to auto-parenthesize the left-most value decompiled
          * here, so add back PAREN_SLOP temporarily.  Then decompile until the
          * opcode that would reduce the stack depth to (ss->top-1), which we
          * pass to Decompile encoded as -(ss->top-1) - 1 or just -ss->top for
          * the nb parameter.
          */
-        ptrdiff_t todo = ss->sprinter.offset;
-        ss->sprinter.offset = todo + PAREN_SLOP;
+        ptrdiff_t todo = ss->sprinter.getOffset();
+        ss->sprinter.setOffset(todo + PAREN_SLOP);
         pc = Decompile(ss, pc, -((intN)ss->top));
         if (!pc)
             return NULL;
         if (pc == endpc)
             return pc;
         LOAD_OP_DATA(pc);
         LOCAL_ASSERT(op == JSOP_ENUMELEM || op == JSOP_ENUMCONSTELEM);
         xval = PopStr(ss, JSOP_NOP);
         lval = PopStr(ss, JSOP_GETPROP);
-        ss->sprinter.offset = todo;
+        ss->sprinter.setOffset(todo);
         if (*lval == '\0') {
             /* lval is from JSOP_BINDNAME, so just print xval. */
             todo = SprintCString(&ss->sprinter, xval);
         } else if (*xval == '\0') {
             /* xval is from JSOP_SETCALL or JSOP_BINDXMLNAME, print lval. */
             todo = SprintCString(&ss->sprinter, lval);
         } else {
             todo = Sprint(&ss->sprinter,
@@ -1980,19 +2119,19 @@ DecompileDestructuring(SprintStack *ss, 
     /*
      * Set head so we can rewrite '[' to '{' as needed.  Back up PAREN_SLOP
      * chars so the destructuring decompilation accumulates contiguously in
      * ss->sprinter starting with "[".
      */
     ptrdiff_t head = SprintPut(&ss->sprinter, "[", 1);
     if (head < 0 || !PushOff(ss, head, JSOP_NOP))
         return NULL;
-    ss->sprinter.offset -= PAREN_SLOP;
-    LOCAL_ASSERT(head == ss->sprinter.offset - 1);
-    LOCAL_ASSERT(*OFF2STR(&ss->sprinter, head) == '[');
+    ss->sprinter.setOffset(ss->sprinter.getOffset() - PAREN_SLOP);
+    LOCAL_ASSERT(head == ss->sprinter.getOffset() - 1);
+    LOCAL_ASSERT(ss->sprinter[head] == '[');
 
     int lasti = -1;
 
     while (pc < endpc) {
 #if JS_HAS_DESTRUCTURING_SHORTHAND
         ptrdiff_t nameoff = -1;
 #endif
 
@@ -2029,17 +2168,17 @@ DecompileDestructuring(SprintStack *ss, 
             pc += oplen;
             if (pc == endpc)
                 return pc;
             LOAD_OP_DATA(pc);
             LOCAL_ASSERT(op == JSOP_GETELEM);
 
             /* Distinguish object from array by opcode or source note. */
             if (sn && SN_TYPE(sn) == SRC_INITPROP) {
-                *OFF2STR(&ss->sprinter, head) = '{';
+                ss->sprinter[head] = '{';
                 if (Sprint(&ss->sprinter, "%g: ", d) < 0)
                     return NULL;
             } else {
                 /* Sanity check for the gnarly control flow above. */
                 LOCAL_ASSERT(i == d);
 
                 /* Fill in any holes (holes at the end don't matter). */
                 while (++lasti < i) {
@@ -2050,19 +2189,19 @@ DecompileDestructuring(SprintStack *ss, 
             break;
           }
 
           case JSOP_GETPROP:
           case JSOP_LENGTH:
           {
             JSAtom *atom;
             LOAD_ATOM(0);
-            *OFF2STR(&ss->sprinter, head) = '{';
+            ss->sprinter[head] = '{';
 #if JS_HAS_DESTRUCTURING_SHORTHAND
-            nameoff = ss->sprinter.offset;
+            nameoff = ss->sprinter.getOffset();
 #endif
             if (!QuoteString(&ss->sprinter, atom, IsIdentifier(atom) ? 0 : (jschar)'\''))
                 return NULL;
             if (SprintPut(&ss->sprinter, ": ", 2) < 0)
                 return NULL;
             break;
           }
 
@@ -2083,37 +2222,37 @@ DecompileDestructuring(SprintStack *ss, 
         pc = DecompileDestructuringLHS(ss, pc, endpc, &hole, letNames);
         if (!pc)
             return NULL;
 
 #if JS_HAS_DESTRUCTURING_SHORTHAND
         if (nameoff >= 0) {
             ptrdiff_t offset, initlen;
 
-            offset = ss->sprinter.offset;
-            LOCAL_ASSERT(*OFF2STR(&ss->sprinter, offset) == '\0');
+            offset = ss->sprinter.getOffset();
+            LOCAL_ASSERT(ss->sprinter[offset] == '\0');
             initlen = offset - nameoff;
             LOCAL_ASSERT(initlen >= 4);
 
             /* Early check to rule out odd "name: lval" length. */
             if (((size_t)initlen & 1) == 0) {
                 size_t namelen;
                 const char *name;
 
                 /*
                  * Even "name: lval" string length: check for "x: x" and the
                  * like, and apply the shorthand if we can.
                  */
                 namelen = (size_t)(initlen - 2) >> 1;
-                name = OFF2STR(&ss->sprinter, nameoff);
+                name = ss->sprinter.stringAt(nameoff);
                 if (!strncmp(name + namelen, ": ", 2) &&
                     !strncmp(name, name + namelen + 2, namelen)) {
                     offset -= namelen + 2;
-                    *OFF2STR(&ss->sprinter, offset) = '\0';
-                    ss->sprinter.offset = offset;
+                    ss->sprinter[offset] = '\0';
+                    ss->sprinter.setOffset(offset);
                 }
             }
         }
 #endif
 
         if (pc == endpc || *pc != JSOP_DUP)
             break;
 
@@ -2135,17 +2274,17 @@ DecompileDestructuring(SprintStack *ss, 
 
         if (!hole && SprintPut(&ss->sprinter, ", ", 2) < 0)
             return NULL;
 
         pc += JSOP_DUP_LENGTH;
     }
 
 out:
-    const char *lval = OFF2STR(&ss->sprinter, head);
+    const char *lval = ss->sprinter.stringAt(head);
     if (SprintPut(&ss->sprinter, (*lval == '[') ? "]" : "}", 1) < 0)
         return NULL;
     return pc;
 }
 
 static jsbytecode *
 DecompileGroupAssignment(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc,
                          jssrcnote *sn, ptrdiff_t *todop)
@@ -2158,17 +2297,17 @@ DecompileGroupAssignment(SprintStack *ss
     const char *rval;
 
     LOAD_OP_DATA(pc);
     LOCAL_ASSERT(op == JSOP_GETLOCAL);
 
     todo = Sprint(&ss->sprinter, "%s[", VarPrefix(sn));
     if (todo < 0 || !PushOff(ss, todo, JSOP_NOP))
         return NULL;
-    ss->sprinter.offset -= PAREN_SLOP;
+    ss->sprinter.setOffset(ss->sprinter.getOffset() - PAREN_SLOP);
 
     for (;;) {
         pc += oplen;
         if (pc == endpc)
             return pc;
         pc = DecompileDestructuringLHS(ss, pc, endpc, &hole);
         if (!pc)
             return NULL;
@@ -2193,17 +2332,17 @@ DecompileGroupAssignment(SprintStack *ss
                    (i == start) ? "%s" : ", %s",
                    (i == end - 1 && *rval == '\0') ? ", " : rval) < 0) {
             return NULL;
         }
     }
 
     if (SprintPut(&ss->sprinter, "]", 1) < 0)
         return NULL;
-    ss->sprinter.offset = ss->offsets[i];
+    ss->sprinter.setOffset(ss->offsets[i]);
     ss->top = start;
     *todop = todo;
     return pc;
 }
 
 #undef LOCAL_ASSERT
 #undef LOAD_OP_DATA
 
@@ -2240,17 +2379,17 @@ GetBlockNames(JSContext *cx, StaticBlock
     return true;
 }
 
 static bool
 PushBlockNames(JSContext *cx, SprintStack *ss, const AtomVector &atoms)
 {
     for (size_t i = 0; i < atoms.length(); i++) {
         const char *name = QuoteString(&ss->sprinter, atoms[i], 0);
-        if (!name || !PushOff(ss, STR2OFF(&ss->sprinter, name), JSOP_ENTERBLOCK))
+        if (!name || !PushOff(ss, ss->sprinter.getOffsetOf(name), JSOP_ENTERBLOCK))
             return false;
     }
     return true;
 }
 
 /*
  * In the scope of a let, the variables' (decompiler) stack slots must contain
  * the corresponding variable's name. This function updates the N top slots
@@ -2385,17 +2524,17 @@ SprintNormalFor(JSContext *cx, JSPrinter
          */
         uintN saveTop = ss->top;
 
         if (!Decompile(ss, pc + next, cond - next - JSOP_POP_LENGTH))
             return -1;
         LOCAL_ASSERT(ss->top - saveTop <= 1U);
         jsbytecode *updatepc = NULL;
         const char *update = (ss->top == saveTop)
-                             ? ss->sprinter.base + ss->sprinter.offset
+                             ? ss->sprinter.stringEnd()
                              : PopStr(ss, JSOP_NOP, &updatepc);
         js_printf(jp, " ");
         SprintOpcodePermanent(jp, update, updatepc);
     }
 
     /* Do the loop body. */
     js_printf(jp, ") {\n");
     jp->indent += 4;
@@ -2411,17 +2550,19 @@ SprintNormalFor(JSContext *cx, JSPrinter
     return -2;
 }
 
 #undef LOCAL_ASSERT
 
 static JSBool
 InitSprintStack(JSContext *cx, SprintStack *ss, JSPrinter *jp, uintN depth)
 {
-    INIT_SPRINTER(cx, &ss->sprinter, &cx->tempLifoAlloc(), PAREN_SLOP);
+    if (!ss->sprinter.init())
+        return JS_FALSE;
+    ss->sprinter.setOffset(PAREN_SLOP);
 
     /* Allocate the parallel (to avoid padding) offset, opcode and bytecode stacks. */
     size_t offsetsz = depth * sizeof(ptrdiff_t);
     size_t opcodesz = depth * sizeof(jsbytecode);
     size_t bytecodesz = depth * sizeof(jsbytecode *);
     void *space = cx->tempLifoAlloc().alloc(offsetsz + opcodesz + bytecodesz);
     if (!space) {
         js_ReportOutOfMemory(cx);
@@ -2738,17 +2879,17 @@ Decompile(SprintStack *ss, jsbytecode *p
                     rval = PopStr(ss, op, &lastrvalpc);
                     (void)PopStr(ss, op, &lastlvalpc);
 
                     /* Print only the right operand of the assignment-op. */
                     todo = SprintCString(&ss->sprinter, rval);
                 } else if (!inXML) {
                     rval = PopStrPrecDupe(ss, cs->prec + !!(cs->format & JOF_LEFTASSOC), &rvalpc);
                     lval = PopStrPrec(ss, cs->prec + !(cs->format & JOF_LEFTASSOC), &lvalpc);
-                    todo = ss->sprinter.offset;
+                    todo = ss->sprinter.getOffset();
                     SprintOpcode(ss, lval, lvalpc, pc, todo);
                     Sprint(&ss->sprinter, " %s ", token);
                     SprintOpcode(ss, rval, rvalpc, pc, todo);
                 } else {
                     /* In XML, just concatenate the two operands. */
                     LOCAL_ASSERT(op == JSOP_ADD);
                     rval = POP_STR();
                     lval = POP_STR();
@@ -2822,18 +2963,18 @@ Decompile(SprintStack *ss, jsbytecode *p
                   do_function:
                     js_puts(jp, "\n");
                     jp2 = js_NewPrinter(cx, "nested_function", fun,
                                         jp->indent, jp->pretty, jp->grouped,
                                         jp->strict);
                     if (!jp2)
                         return NULL;
                     ok = js_DecompileFunction(jp2);
-                    if (ok && jp2->sprinter.base)
-                        js_puts(jp, jp2->sprinter.base);
+                    if (ok && !jp2->sprinter.empty())
+                        js_puts(jp, jp2->sprinter.string());
                     js_DestroyPrinter(jp2);
                     if (!ok)
                         return NULL;
                     js_puts(jp, "\n\n");
                     break;
 
                   case SRC_BRACE:
                     js_printf(jp, "\t{\n");
@@ -2853,27 +2994,27 @@ Decompile(SprintStack *ss, jsbytecode *p
                 todo = -2;
                 switch (sn ? SN_TYPE(sn) : SRC_NULL) {
                   case SRC_LABEL:
                     GET_SOURCE_NOTE_ATOM(sn, atom);
                     jp->indent -= 4;
                     rval = QuoteString(&ss->sprinter, atom, 0);
                     if (!rval)
                         return NULL;
-                    RETRACT(&ss->sprinter, rval);
+                    ss->sprinter.setOffset(rval);
                     js_printf(jp, "\t%s:\n", rval);
                     jp->indent += 4;
                     break;
 
                   case SRC_LABELBRACE:
                     GET_SOURCE_NOTE_ATOM(sn, atom);
                     rval = QuoteString(&ss->sprinter, atom, 0);
                     if (!rval)
                         return NULL;
-                    RETRACT(&ss->sprinter, rval);
+                    ss->sprinter.setOffset(rval);
                     js_printf(jp, "\t%s: {\n", rval);
                     jp->indent += 4;
                     break;
 
                   default:
                     JS_NOT_REACHED("JSOP_LABEL without source note");
                     break;
                 }
@@ -2943,17 +3084,17 @@ Decompile(SprintStack *ss, jsbytecode *p
                     break;
 #if JS_HAS_DESTRUCTURING
                 if (sn && SN_TYPE(sn) == SRC_GROUPASSIGN) {
                     todo = Sprint(&ss->sprinter, "%s[] = [",
                                   VarPrefix(sn));
                     if (todo < 0)
                         return NULL;
                     for (uintN i = newtop; i < oldtop; i++) {
-                        rval = OFF2STR(&ss->sprinter, ss->offsets[i]);
+                        rval = ss->sprinter.stringAt(ss->offsets[i]);
                         if (Sprint(&ss->sprinter, ss_format,
                                    (i == newtop) ? "" : ", ",
                                    (i == oldtop - 1 && *rval == '\0')
                                    ? ", " : rval) < 0) {
                             return NULL;
                         }
                     }
                     if (SprintPut(&ss->sprinter, "]", 1) < 0)
@@ -2961,24 +3102,24 @@ Decompile(SprintStack *ss, jsbytecode *p
 
                     /*
                      * If this is an empty group assignment, we have no stack
                      * budget into which we can push our result string. Adjust
                      * ss->sprinter.offset so that our consumer can find the
                      * empty group assignment decompilation.
                      */
                     if (newtop == oldtop) {
-                        ss->sprinter.offset = todo;
+                        ss->sprinter.setOffset(todo);
                     } else {
                         /*
                          * Kill newtop before the end_groupassignment: label by
                          * retracting/popping early.
                          */
                         LOCAL_ASSERT(newtop < oldtop);
-                        ss->sprinter.offset = GetOff(ss, newtop);
+                        ss->sprinter.setOffset(GetOff(ss, newtop));
                         ss->top = newtop;
                     }
 
                   end_groupassignment:
                     LOCAL_ASSERT(*pc == JSOP_POPN);
 
                     /*
                      * Thread directly to the next opcode if we can, to handle
@@ -2986,17 +3127,17 @@ Decompile(SprintStack *ss, jsbytecode *p
                      * last part of a for(;;) loop head, or in a let block or
                      * expression head.
                      *
                      * NB: todo at this point indexes space in ss->sprinter
                      * that is liable to be overwritten.  The code below knows
                      * exactly how long rval lives, or else copies it down via
                      * SprintCString.
                      */
-                    rval = OFF2STR(&ss->sprinter, todo);
+                    rval = ss->sprinter.stringAt(todo);
                     rvalpc = NULL;
                     todo = -2;
                     pc2 = pc + oplen;
 
                     if (*pc2 == JSOP_NOP) {
                         sn = js_GetSrcNote(jp->script, pc2);
                         if (sn) {
                             if (SN_TYPE(sn) == SRC_FOR) {
@@ -3026,17 +3167,17 @@ Decompile(SprintStack *ss, jsbytecode *p
                      * just print rval as an expression statement.
                      */
                     if (todo == -2)
                         js_printf(jp, "\t%s;\n", rval);
                     break;
                 }
 #endif
                 if (newtop < oldtop) {
-                    ss->sprinter.offset = GetOff(ss, newtop);
+                    ss->sprinter.setOffset(GetOff(ss, newtop));
                     ss->top = newtop;
                 }
                 break;
               }
 
               case JSOP_EXCEPTION:
                 /* The catch decompiler handles this op itself. */
                 LOCAL_ASSERT(JS_FALSE);
@@ -3079,17 +3220,17 @@ Decompile(SprintStack *ss, jsbytecode *p
                     pc += js_GetSrcNoteOffset(sn, 0);
                     len = 0;
 
                     if (!Decompile(ss, done, pc - done))
                         return NULL;
 
                     /* Pop Decompile result and print comma expression. */
                     rval = PopStrDupe(ss, op, &rvalpc);
-                    todo = ss->sprinter.offset;
+                    todo = ss->sprinter.getOffset();
                     SprintOpcode(ss, lval, lvalpc, pushpc, todo);
                     SprintCString(&ss->sprinter, ", ");
                     SprintOpcode(ss, rval, rvalpc, pushpc, todo);
                     break;
 
                   case SRC_HIDDEN:
                     /* Hide this pop, it's from a goto in a with or for/in. */
                     todo = -2;
@@ -3288,17 +3429,17 @@ Decompile(SprintStack *ss, jsbytecode *p
                         LOCAL_ASSERT(strcmp(rval, exception_cookie) == 0);
                     }
                 }
                 top = ss->top;
                 depth = GET_UINT16(pc);
                 LOCAL_ASSERT(top >= depth);
                 top -= depth;
                 ss->top = top;
-                ss->sprinter.offset = GetOff(ss, top);
+                ss->sprinter.setOffset(GetOff(ss, top));
                 if (op == JSOP_LEAVEBLOCKEXPR)
                     todo = SprintCString(&ss->sprinter, rval);
                 break;
               }
 
               case JSOP_ENTERLET0:
               {
                 LOAD_OBJECT(0);
@@ -3319,24 +3460,21 @@ Decompile(SprintStack *ss, jsbytecode *p
                 /*
                  * Build the list of decompiled rhs expressions. Do this before
                  * sprinting the let-head since GetStr can inject stuff on top
                  * of the stack (in case js_DecompileValueGenerator).
                  */
                 Vector<const char *> rhsExprs(cx);
                 if (!rhsExprs.resize(atoms.length()))
                     return NULL;
-                for (size_t i = 0; i < rhsExprs.length(); ++i) {
-                    rhsExprs[i] = GetStr(ss, letDepth + i);
-                    if (!rhsExprs[i])
-                        return NULL;
-                }
+                for (size_t i = 0; i < rhsExprs.length(); ++i)
+                    rhsExprs[i] = SprintDupeStr(ss, GetStr(ss, letDepth + i));
 
                 /* Build the let head starting at headBegin. */
-                ptrdiff_t headBegin = ss->sprinter.offset;
+                ptrdiff_t headBegin = ss->sprinter.getOffset();
 
                 /*
                  * For group assignment, prepend the '[lhs-vars] = [' here,
                  * append rhsExprs in the next loop and append ']' after.
                  */
                 if (groupAssign) {
                     if (Sprint(&ss->sprinter, "[") < 0)
                         return NULL;
@@ -3379,17 +3517,17 @@ Decompile(SprintStack *ss, jsbytecode *p
                     }
                 }
 
                 if (groupAssign && Sprint(&ss->sprinter, "]") < 0)
                     return NULL;
 
                 /* Clone the let head chars before clobbering the stack. */
                 DupBuffer head(cx);
-                if (!Dup(OFF2STR(&ss->sprinter, headBegin), &head))
+                if (!Dup(ss->sprinter.stringAt(headBegin), &head))
                     return NULL;
                 if (!AssignBlockNamesToPushedSlots(cx, ss, atoms))
                     return NULL;
 
                 /* Detect 'for (let ...)' desugared into 'let (...) {for}'. */
                 jsbytecode *nextpc = pc + JSOP_ENTERLET0_LENGTH;
                 if (*nextpc == JSOP_NOP) {
                     jssrcnote *nextsn = js_GetSrcNote(jp->script, nextpc);
@@ -3674,18 +3812,18 @@ Decompile(SprintStack *ss, jsbytecode *p
                      * Generator expression: decompile just rval followed by
                      * the string starting at forpos. Leave the result string
                      * in ss->offsets[0] so it can be recovered by our caller
                      * (the JSOP_ANONFUNOBJ with SRC_GENEXP case). Bump the
                      * top of stack to balance yield, which is an expression
                      * (so has neutral stack balance).
                      */
                     LOCAL_ASSERT(pos == 0);
-                    xval = OFF2STR(&ss->sprinter, ss->offsets[forpos]);
-                    ss->sprinter.offset = PAREN_SLOP;
+                    xval = ss->sprinter.stringAt(ss->offsets[forpos]);
+                    ss->sprinter.setOffset(PAREN_SLOP);
                     todo = Sprint(&ss->sprinter, ss_format, rval, xval);
                     if (todo < 0)
                         return NULL;
                     ss->offsets[0] = todo;
                     ++ss->top;
                     return pc;
                 }
 #endif /* JS_HAS_GENERATOR_EXPRS */
@@ -3693,22 +3831,22 @@ Decompile(SprintStack *ss, jsbytecode *p
                 /*
                  * Array comprehension: retract the sprinter to the beginning
                  * of the array initialiser and decompile "[<rval> for ...]".
                  */
                 JS_ASSERT(jp->script->nfixed + pos == GET_UINT16(pc));
                 LOCAL_ASSERT(ss->opcodes[pos] == JSOP_NEWINIT);
 
                 ptrdiff_t start = ss->offsets[pos];
-                LOCAL_ASSERT(ss->sprinter.base[start] == '[' ||
-                             ss->sprinter.base[start] == '#');
+                LOCAL_ASSERT(ss->sprinter[start] == '[' ||
+                             ss->sprinter[start] == '#');
                 LOCAL_ASSERT(forpos < ss->top);
-                xval = OFF2STR(&ss->sprinter, ss->offsets[forpos]);
-                lval = OFF2STR(&ss->sprinter, start);
-                RETRACT(&ss->sprinter, lval);
+                xval = ss->sprinter.stringAt(ss->offsets[forpos]);
+                lval = ss->sprinter.stringAt(start);
+                ss->sprinter.setOffset(lval);
 
                 todo = Sprint(&ss->sprinter, sss_format, lval, rval, xval);
                 if (todo < 0)
                     return NULL;
                 ss->offsets[pos] = todo;
                 todo = -2;
                 break;
               }
@@ -3793,17 +3931,17 @@ Decompile(SprintStack *ss, jsbytecode *p
                      */
                     JS_ASSERT(strcmp(lval + strlen(lval) - 9, " = <next>") == 0);
                     const_cast<char *>(lval)[strlen(lval) - 9] = '\0';
                     LOCAL_ASSERT(ss->top >= 1);
 
                     if (ss->inArrayInit || ss->inGenExp) {
                         rval = POP_STR();
                         if (ss->top >= 1 && ss->opcodes[ss->top - 1] == JSOP_FORLOCAL) {
-                            ss->sprinter.offset = ss->offsets[ss->top] - PAREN_SLOP;
+                            ss->sprinter.setOffset(ss->offsets[ss->top] - PAREN_SLOP);
                             if (Sprint(&ss->sprinter, " %s (%s in %s)",
                                        foreach ? js_for_each_str : js_for_str,
                                        lval, rval) < 0) {
                                 return NULL;
                             }
 
                             /*
                              * Do not AddParenSlop here, as we will push the
@@ -3859,30 +3997,30 @@ Decompile(SprintStack *ss, jsbytecode *p
                     todo = -2;
                     break;
 
                   case SRC_CONT2LABEL:
                     GET_SOURCE_NOTE_ATOM(sn, atom);
                     rval = QuoteString(&ss->sprinter, atom, 0);
                     if (!rval)
                         return NULL;
-                    RETRACT(&ss->sprinter, rval);
+                    ss->sprinter.setOffset(rval);
                     js_printf(jp, "\tcontinue %s;\n", rval);
                     break;
 
                   case SRC_CONTINUE:
                     js_printf(jp, "\tcontinue;\n");
                     break;
 
                   case SRC_BREAK2LABEL:
                     GET_SOURCE_NOTE_ATOM(sn, atom);
                     rval = QuoteString(&ss->sprinter, atom, 0);
                     if (!rval)
                         return NULL;
-                    RETRACT(&ss->sprinter, rval);
+                    ss->sprinter.setOffset(rval);
                     js_printf(jp, "\tbreak %s;\n", rval);
                     break;
 
                   case SRC_HIDDEN:
                     break;
 
                   default:
                     js_printf(jp, "\tbreak;\n");
@@ -3900,17 +4038,17 @@ Decompile(SprintStack *ss, jsbytecode *p
                 sn = js_GetSrcNote(jp->script, pc);
 
                 switch (sn ? SN_TYPE(sn) : SRC_NULL) {
                   case SRC_IF:
                   case SRC_IF_ELSE:
                     rval = PopCondStr(ss, &rvalpc);
                     if (ss->inArrayInit || ss->inGenExp) {
                         LOCAL_ASSERT(SN_TYPE(sn) == SRC_IF);
-                        ss->sprinter.offset -= PAREN_SLOP;
+                        ss->sprinter.setOffset(ss->sprinter.getOffset() - PAREN_SLOP);
                         if (Sprint(&ss->sprinter, " if (%s)", rval) < 0)
                             return NULL;
                         AddParenSlop(ss);
                     } else {
                         js_printf(jp, elseif ? " if (" : "\tif (");
                         SprintOpcodePermanent(jp, rval, rvalpc);
                         js_printf(jp, ") {\n");
                         jp->indent += 4;
@@ -3966,17 +4104,17 @@ Decompile(SprintStack *ss, jsbytecode *p
                     lval = PopStrDupe(ss, op, &lvalpc);
                     pushpc = pc;
                     pc += len;
                     LOCAL_ASSERT(*pc == JSOP_GOTO);
                     oplen = js_CodeSpec[*pc].length;
                     len = GET_JUMP_OFFSET(pc);
                     DECOMPILE_CODE(pc + oplen, len - oplen);
                     rval = PopStrDupe(ss, op, &rvalpc);
-                    todo = ss->sprinter.offset;
+                    todo = ss->sprinter.getOffset();
                     SprintOpcode(ss, xval, xvalpc, pushpc, todo);
                     SprintCString(&ss->sprinter, " ? ");
                     SprintOpcode(ss, lval, lvalpc, pushpc, todo);
                     SprintCString(&ss->sprinter, " : ");
                     SprintOpcode(ss, rval, rvalpc, pushpc, todo);
                     break;
 
                   default:
@@ -4001,17 +4139,17 @@ Decompile(SprintStack *ss, jsbytecode *p
                 JS_ASSERT(*pc == JSOP_POP);
                 pc += JSOP_POP_LENGTH;
                 len = done - pc;
                 if (!Decompile(ss, pc, len))
                     return NULL;
                 rval = PopStrDupe(ss, op, &rvalpc);
                 if (!rval)
                     return NULL;
-                todo = ss->sprinter.offset;
+                todo = ss->sprinter.getOffset();
                 SprintOpcode(ss, lval, lvalpc, pushpc, todo);
                 if (jp->pretty &&
                     jp->indent + 4 + strlen(lval) + 4 + strlen(rval) > 75) {
                     Sprint(&ss->sprinter, " %s\n", xval);
                     Sprint(&ss->sprinter, "%*s", jp->indent + 4, "");
                 } else {
                     Sprint(&ss->sprinter, " %s ", xval);
                 }
@@ -4201,17 +4339,17 @@ Decompile(SprintStack *ss, jsbytecode *p
                 if (!lval)
                     return NULL;
                 rval = PopStrDupe(ss, op, &rvalpc);
                 if (op == JSOP_SETNAME || op == JSOP_SETGNAME)
                     (void) PopOff(ss, op);
 
               do_setlval:
                 sn = js_GetSrcNote(jp->script, pc - 1);
-                todo = ss->sprinter.offset;
+                todo = ss->sprinter.getOffset();
                 if (sn && SN_TYPE(sn) == SRC_ASSIGNOP) {
                     const char *token =
                         GetTokenForAssignment(jp, sn, lastop, pc, rvalpc,
                                               &lastlvalpc, &lastrvalpc);
                     Sprint(&ss->sprinter, "%s %s= ", lval, token);
                     SprintOpcode(ss, rval, rvalpc, pc, todo);
                 } else {
                     sn = js_GetSrcNote(jp->script, pc);
@@ -4270,17 +4408,17 @@ Decompile(SprintStack *ss, jsbytecode *p
                                        op == JSOP_CALLPROP ||
                                        op == JSOP_CALLELEM))
                                      ? JSOP_NAME
                                      : saveop,
                                      &lvalpc);
                 op = saveop;
 
                 lval = "(", rval = ")";
-                todo = ss->sprinter.offset;
+                todo = ss->sprinter.getOffset();
                 if (op == JSOP_NEW) {
                     if (argc == 0)
                         lval = rval = "";
                     Sprint(&ss->sprinter, "%s ", js_new_str);
                 }
                 SprintOpcode(ss, argv[0], lvalpc, pc, todo);
                 SprintCString(&ss->sprinter, lval);
 
@@ -4301,17 +4439,17 @@ Decompile(SprintStack *ss, jsbytecode *p
                 todo = Sprint(&ss->sprinter, "");
                 break;
 
               case JSOP_DELNAME:
                 LOAD_ATOM(0);
                 lval = QuoteString(&ss->sprinter, atom, 0);
                 if (!lval)
                     return NULL;
-                RETRACT(&ss->sprinter, lval);
+                ss->sprinter.setOffset(lval);
               do_delete_lval:
                 todo = Sprint(&ss->sprinter, "%s %s", js_delete_str, lval);
                 break;
 
               case JSOP_DELPROP:
                 GET_ATOM_QUOTE_AND_FMT("%s %s[%s]", "%s %s.%s", rval);
                 op = JSOP_GETPROP;
                 lval = POP_STR();
@@ -4343,17 +4481,17 @@ Decompile(SprintStack *ss, jsbytecode *p
 #endif
 
               case JSOP_TYPEOFEXPR:
               case JSOP_TYPEOF:
               case JSOP_VOID:
               {
                 const char *prefix = (op == JSOP_VOID) ? js_void_str : js_typeof_str;
                 rval = PopStrDupe(ss, op, &rvalpc);
-                todo = ss->sprinter.offset;
+                todo = ss->sprinter.getOffset();
                 Sprint(&ss->sprinter, "%s ", prefix);
                 SprintOpcode(ss, rval, rvalpc, pc, todo);
                 break;
               }
 
               case JSOP_INCARG:
               case JSOP_DECARG:
                 atom = GetArgOrVarAtom(jp, GET_ARGNO(pc));
@@ -4364,17 +4502,17 @@ Decompile(SprintStack *ss, jsbytecode *p
               case JSOP_DECNAME:
               case JSOP_INCGNAME:
               case JSOP_DECGNAME:
                 LOAD_ATOM(0);
               do_incatom:
                 lval = QuoteString(&ss->sprinter, atom, 0);
                 if (!lval)
                     return NULL;
-                RETRACT(&ss->sprinter, lval);
+                ss->sprinter.setOffset(lval);
               do_inclval:
                 todo = Sprint(&ss->sprinter, ss_format,
                               js_incop_strs[!(cs->format & JOF_INC)], lval);
                 if (js_CodeSpec[*pc].format & JOF_DECOMPOSE)
                     len += GetDecomposeLength(pc, js_CodeSpec[*pc].length);
                 break;
 
               case JSOP_INCPROP:
@@ -4424,17 +4562,17 @@ Decompile(SprintStack *ss, jsbytecode *p
               case JSOP_NAMEDEC:
               case JSOP_GNAMEINC:
               case JSOP_GNAMEDEC:
                 LOAD_ATOM(0);
               do_atominc:
                 lval = QuoteString(&ss->sprinter, atom, 0);
                 if (!lval)
                     return NULL;
-                RETRACT(&ss->sprinter, lval);
+                ss->sprinter.setOffset(lval);
               do_lvalinc:
                 todo = Sprint(&ss->sprinter, ss_format,
                               lval, js_incop_strs[!(cs->format & JOF_INC)]);
                 if (js_CodeSpec[*pc].format & JOF_DECOMPOSE)
                     len += GetDecomposeLength(pc, js_CodeSpec[*pc].length);
                 break;
 
               case JSOP_PROPINC:
@@ -4482,17 +4620,17 @@ Decompile(SprintStack *ss, jsbytecode *p
               case JSOP_GETPROP:
               case JSOP_GETXPROP:
               case JSOP_LENGTH:
                 LOAD_ATOM(0);
 
                 GET_QUOTE_AND_FMT("[%s]", ".%s", rval);
                 PROPAGATE_CALLNESS();
                 lval = PopStr(ss, op, &lvalpc);
-                todo = ss->sprinter.offset;
+                todo = ss->sprinter.getOffset();
                 SprintOpcode(ss, lval, lvalpc, pc, todo);
                 Sprint(&ss->sprinter, fmt, rval);
                 break;
 
               case JSOP_SETPROP:
               case JSOP_SETMETHOD:
               {
                 LOAD_ATOM(0);
@@ -4505,34 +4643,34 @@ Decompile(SprintStack *ss, jsbytecode *p
                  * around the left-hand side of dot.
                  */
                 op = JSOP_GETPROP;
                 lval = PopStr(ss, op, &lvalpc);
                 sn = js_GetSrcNote(jp->script, pc - 1);
                 const char *token =
                     GetTokenForAssignment(jp, sn, lastop, pc, rvalpc,
                                           &lastlvalpc, &lastrvalpc);
-                todo = ss->sprinter.offset;
+                todo = ss->sprinter.getOffset();
                 SprintOpcode(ss, lval, lvalpc, pc, todo);
                 Sprint(&ss->sprinter, fmt, xval, token);
                 SprintOpcode(ss, rval, rvalpc, pc, todo);
                 break;
               }
 
               case JSOP_GETELEM2:
                 (void) PopOff(ss, lastop);
                 /* FALL THROUGH */
               case JSOP_CALLELEM:
               case JSOP_GETELEM:
                 op = JSOP_NOP;          /* turn off parens */
                 xval = PopStrDupe(ss, op, &xvalpc);
                 op = saveop;
                 PROPAGATE_CALLNESS();
                 lval = PopStr(ss, op, &lvalpc);
-                todo = ss->sprinter.offset;
+                todo = ss->sprinter.getOffset();
                 SprintOpcode(ss, lval, lvalpc, pc, todo);
                 if (*xval != '\0') {
                     bool xml = (JOF_OPMODE(lastop) == JOF_XMLNAME);
                     SprintCString(&ss->sprinter, xml ? "." : "[");
                     SprintOpcode(ss, xval, xvalpc, pc, todo);
                     SprintCString(&ss->sprinter, xml ? "" : "]");
                 }
                 break;
@@ -4548,17 +4686,17 @@ Decompile(SprintStack *ss, jsbytecode *p
                 op = saveop;
                 if (*xval == '\0')
                     goto do_setlval;
                 sn = js_GetSrcNote(jp->script, pc - 1);
                 bool xml = (JOF_MODE(cs->format) == JOF_XMLNAME);
                 const char *token =
                     GetTokenForAssignment(jp, sn, lastop, pc, rvalpc,
                                           &lastlvalpc, &lastrvalpc);
-                todo = ss->sprinter.offset;
+                todo = ss->sprinter.getOffset();
                 SprintOpcode(ss, lval, lvalpc, pc, todo);
                 SprintCString(&ss->sprinter, xml ? "." : "[");
                 SprintOpcode(ss, xval, xvalpc, pc, todo);
                 SprintCString(&ss->sprinter, xml ? "" : "]");
                 Sprint(&ss->sprinter, " %s= ", token);
                 SprintOpcode(ss, rval, rvalpc, pc, todo);
                 break;
               }
@@ -4586,17 +4724,17 @@ Decompile(SprintStack *ss, jsbytecode *p
                 lval = "";
 #if JS_HAS_XML_SUPPORT
               do_qname:
 #endif
                 sn = js_GetSrcNote(jp->script, pc);
                 rval = QuoteString(&ss->sprinter, atom, inXML ? DONT_ESCAPE : 0);
                 if (!rval)
                     return NULL;
-                RETRACT(&ss->sprinter, rval);
+                ss->sprinter.setOffset(rval);
                 todo = Sprint(&ss->sprinter, sss_format,
                               VarPrefix(sn), lval, rval);
                 break;
 
               case JSOP_UINT16:
                 i = (jsint) GET_UINT16(pc);
                 goto do_sprint_int;
 
@@ -4623,29 +4761,29 @@ Decompile(SprintStack *ss, jsbytecode *p
                 break;
               }
 
               case JSOP_STRING:
                 LOAD_ATOM(0);
                 rval = QuoteString(&ss->sprinter, atom, inXML ? DONT_ESCAPE : '"');
                 if (!rval)
                     return NULL;
-                todo = STR2OFF(&ss->sprinter, rval);
+                todo = ss->sprinter.getOffsetOf(rval);
                 break;
 
               case JSOP_LAMBDA:
               case JSOP_LAMBDA_FC:
 #if JS_HAS_GENERATOR_EXPRS
                 sn = js_GetSrcNote(jp->script, pc);
                 if (sn && SN_TYPE(sn) == SRC_GENEXP) {
                     Vector<JSAtom *> *innerLocalNames;
                     Vector<JSAtom *> *outerLocalNames;
                     JSScript *inner, *outer;
                     Vector<DecompiledOpcode> *decompiledOpcodes;
-                    SprintStack ss2;
+                    SprintStack ss2(cx);
                     JSFunction *outerfun;
 
                     LOAD_FUNCTION(0);
 
                     /*
                      * All allocation when decompiling is LIFO, using malloc or,
                      * more commonly, arena-allocating from cx->tempLifoAlloc
                      * Therefore after InitSprintStack succeeds, we must release
@@ -5008,17 +5146,17 @@ Decompile(SprintStack *ss, jsbytecode *p
                 todo = SprintPut(&ss->sprinter, "", 0);
                 break;
 
               case JSOP_NEWINIT:
               {
                 i = pc[1];
                 LOCAL_ASSERT(i == JSProto_Array || i == JSProto_Object);
 
-                todo = ss->sprinter.offset;
+                todo = ss->sprinter.getOffset();
 #if JS_HAS_SHARP_VARS
                 op = (JSOp)pc[len];
                 if (op == JSOP_SHARPINIT)
                     op = (JSOp)pc[len += JSOP_SHARPINIT_LENGTH];
                 if (op == JSOP_DEFSHARP) {
                     pc += len;
                     cs = &js_CodeSpec[op];
                     len = cs->length;
@@ -5037,26 +5175,26 @@ Decompile(SprintStack *ss, jsbytecode *p
                     if (SprintCString(&ss->sprinter, "{") < 0)
                         return NULL;
                 }
                 break;
               }
 
               case JSOP_NEWARRAY:
               {
-                todo = ss->sprinter.offset;
+                todo = ss->sprinter.getOffset();
                 ++ss->inArrayInit;
                 if (SprintCString(&ss->sprinter, "[") < 0)
                     return NULL;
                 break;
               }
 
               case JSOP_NEWOBJECT:
               {
-                todo = ss->sprinter.offset;
+                todo = ss->sprinter.getOffset();
                 if (SprintCString(&ss->sprinter, "{") < 0)
                     return NULL;
                 break;
               }
 
               case JSOP_ENDINIT:
               {
                 JSBool inArray;
@@ -5066,17 +5204,17 @@ Decompile(SprintStack *ss, jsbytecode *p
                 sn = js_GetSrcNote(jp->script, pc);
 
                 /* Skip any #n= prefix to find the opening bracket. */
                 for (xval = rval; *xval != '[' && *xval != '{'; xval++)
                     continue;
                 inArray = (*xval == '[');
                 if (inArray)
                     --ss->inArrayInit;
-                todo = ss->sprinter.offset;
+                todo = ss->sprinter.getOffset();
                 SprintOpcode(ss, rval, rvalpc, pc, todo);
                 Sprint(&ss->sprinter, "%s%c",
                        (sn && SN_TYPE(sn) == SRC_CONTINUE) ? ", " : "",
                        inArray ? ']' : '}');
                 break;
               }
 
               {
@@ -5110,17 +5248,17 @@ Decompile(SprintStack *ss, jsbytecode *p
                 if (!xval)
                     return NULL;
                 isFirst = IsInitializerOp(ss->opcodes[ss->top - 2]);
                 rval = PopStrDupe(ss, op, &rvalpc);
                 lval = PopStr(ss, op, &lvalpc);
                 /* fall through */
 
               do_initprop:
-                todo = ss->sprinter.offset;
+                todo = ss->sprinter.getOffset();
                 SprintOpcode(ss, lval, lvalpc, pc, todo);
                 maybeComma = isFirst ? "" : ", ";
                 if (lastop == JSOP_GETTER || lastop == JSOP_SETTER) {
                     const char *end = rval + strlen(rval);
 
                     if (*rval == '(')
                         ++rval, --end;
                     LOCAL_ASSERT(strncmp(rval, js_function_str, 8) == 0);
@@ -5192,17 +5330,17 @@ Decompile(SprintStack *ss, jsbytecode *p
                 }
                 goto do_name;
 
               case JSOP_QNAMECONST:
                 LOAD_ATOM(0);
                 rval = QuoteString(&ss->sprinter, atom, 0);
                 if (!rval)
                     return NULL;
-                RETRACT(&ss->sprinter, rval);
+                ss->sprinter.setOffset(rval);
                 lval = POP_STR();
                 todo = Sprint(&ss->sprinter, "%s::%s", lval, rval);
                 break;
 
               case JSOP_QNAME:
                 rval = POP_STR();
                 lval = POP_STR();
                 todo = Sprint(&ss->sprinter, "%s::[%s]", lval, rval);
@@ -5376,17 +5514,17 @@ DecompileCode(JSPrinter *jp, JSScript *s
 {
     JSContext *cx = jp->sprinter.context;
 
     uintN depth = StackDepth(script);
     JS_ASSERT(pcdepth <= depth);
 
     /* Initialize a sprinter for use with the offset stack. */
     LifoAllocScope las(&cx->tempLifoAlloc());
-    SprintStack ss;
+    SprintStack ss(cx);
     if (!InitSprintStack(cx, &ss, jp, depth))
         return false;
 
     /*
      * If we are called from js_DecompileValueGenerator with a portion of
      * script's bytecode that starts with a non-zero model stack depth given
      * by pcdepth, attempt to initialize the missing string offsets in ss to
      * |spindex| negative indexes from fp->sp for the activation fp in which
@@ -5407,19 +5545,19 @@ DecompileCode(JSPrinter *jp, JSScript *s
     /* Call recursive subroutine to do the hard work. */
     JSScript *oldscript = jp->script;
     jp->script = script;
     bool ok = Decompile(&ss, pc, len) != NULL;
     jp->script = oldscript;
 
     /* If the given code didn't empty the stack, do it now. */
     if (ok && ss.top) {
-        char *last;
+        const char *last;
         do {
-            last = OFF2STR(&ss.sprinter, PopOff(&ss, JSOP_POP));
+            last = ss.sprinter.stringAt(PopOff(&ss, JSOP_POP));
         } while (ss.top > pcdepth);
         js_printf(jp, "%s", last);
     }
 
     return ok;
 }
 
 /*
@@ -5519,28 +5657,27 @@ js_DecompileFunction(JSPrinter *jp)
         js_printf(jp, ") {\n");
         jp->indent += 4;
         js_printf(jp, native_code_str);
         jp->indent -= 4;
         js_printf(jp, "\t}");
     } else {
         JSScript *script = fun->script();
 #if JS_HAS_DESTRUCTURING
-        SprintStack ss;
+        SprintStack ss(jp->sprinter.context);
 #endif
 
         /* Print the parameters. */
         jsbytecode *pc = script->main();
         jsbytecode *endpc = pc + script->length;
         JSBool ok = JS_TRUE;
 
 #if JS_HAS_DESTRUCTURING
         ss.printer = NULL;
         jp->script = script;
-        LifoAllocScope las(&jp->sprinter.context->tempLifoAlloc());
 #endif
 
         for (uintN i = 0; i < fun->nargs; i++) {
             if (i > 0)
                 js_puts(jp, ", ");
 
             JSAtom *param = GetArgOrVarAtom(jp, i);
 
@@ -5581,17 +5718,16 @@ js_DecompileFunction(JSPrinter *jp)
             if (!QuoteString(&jp->sprinter, param, 0)) {
                 ok = JS_FALSE;
                 break;
             }
         }
 
 #if JS_HAS_DESTRUCTURING
         jp->script = NULL;
-        las.releaseEarly();
 #endif
         if (!ok)
             return JS_FALSE;
         js_printf(jp, ") ");
         if (!(fun->flags & JSFUN_EXPR_CLOSURE)) {
             js_printf(jp, "{\n");
             jp->indent += 4;
         }
@@ -5813,18 +5949,17 @@ DecompileExpression(JSContext *cx, JSScr
     if (!g.printer)
         return NULL;
 
     g.printer->dvgfence = end;
     g.printer->pcstack = g.pcstack;
     if (!DecompileCode(g.printer, script, begin, (uintN) len, (uintN) pcdepth))
         return NULL;
 
-    const char *name = (g.printer->sprinter.base) ? g.printer->sprinter.base : "";
-    return JS_strdup(cx, name);
+    return JS_strdup(cx, g.printer->sprinter.string());
 }
 
 uintN
 js_ReconstructStackDepth(JSContext *cx, JSScript *script, jsbytecode *pc)
 {
     return ReconstructPCStack(cx, script, pc, NULL, NULL);
 }
 
--- a/js/src/jsopcode.h
+++ b/js/src/jsopcode.h
@@ -435,36 +435,90 @@ DecompileValueGenerator(JSContext *cx, i
                         JSString *fallback)
 {
     return js_DecompileValueGenerator(cx, spindex, v, fallback);
 }
 
 /*
  * Sprintf, but with unlimited and automatically allocated buffering.
  */
-struct Sprinter {
-    JSContext       *context;       /* context executing the decompiler */
-    LifoAlloc       *pool;          /* string allocation pool */
-    char            *base;          /* base address of buffer in pool */
-    size_t          size;           /* size of buffer allocated at base */
-    ptrdiff_t       offset;         /* offset of next free char in buffer */
-};
+class Sprinter
+{
+  public:
+    struct InvariantChecker
+    {
+        const Sprinter *parent;
+
+        explicit InvariantChecker(const Sprinter *p) : parent(p) {
+            parent->checkInvariants();
+        }
+
+        ~InvariantChecker() {
+            parent->checkInvariants();
+        }
+    };
+
+    JSContext               *context;       /* context executing the decompiler */
+
+  private:
+    static const size_t     DefaultSize;
+#ifdef DEBUG
+    bool                    initialized;    /* true if this is initialized, use for debug builds */
+#endif
+    char                    *base;          /* malloc'd buffer address */
+    size_t                  size;           /* size of buffer allocated at base */
+    ptrdiff_t               offset;         /* offset of next free char in buffer */
+
+    bool realloc_(size_t newSize);
+
+  public:
+    explicit Sprinter(JSContext *cx);
+    ~Sprinter();
+
+    /* Initialize this sprinter, returns false on error */
+    bool init();
 
-#define INIT_SPRINTER(cx, sp, ap, off) \
-    ((sp)->context = cx, (sp)->pool = ap, (sp)->base = NULL, (sp)->size = 0,  \
-     (sp)->offset = off)
+    void checkInvariants() const;
+
+    const char *string() const;
+    const char *stringEnd() const;
+    /* Returns the string at offset |off| */
+    char *stringAt(ptrdiff_t off) const;
+    /* Returns the char at offset |off| */
+    char &operator[](size_t off);
+    /* Test if this Sprinter is empty */
+    bool empty() const;
 
-/*
- * Attempt to reserve len space in sp (including a trailing NULL byte). If the
- * attempt succeeds, return a pointer to the start of that space and adjust the
- * length of sp's contents. The caller *must* completely fill this space
- * (including the space for the trailing NULL byte) on success.
- */
-extern char *
-SprintReserveAmount(Sprinter *sp, size_t len);
+    /*
+     * Attempt to reserve len + 1 space (for a trailing NULL byte). If the
+     * attempt succeeds, return a pointer to the start of that space and adjust the
+     * internal content. The caller *must* completely fill this space on success.
+     */
+    char *reserve(size_t len);
+    /* Like reserve, but memory is initialized to 0 */
+    char *reserveAndClear(size_t len);
+
+    /*
+     * Puts |len| characters from |s| at the current position and return an offset to
+     * the beginning of this new data
+     */
+    ptrdiff_t put(const char *s, size_t len);
+    ptrdiff_t putString(JSString *str);
+
+    /* Prints a formatted string into the buffer */
+    int printf(const char *fmt, ...);
+
+    /* Change the offset */
+    void setOffset(const char *end);
+    void setOffset(ptrdiff_t off);
+
+    /* Get the offset */
+    ptrdiff_t getOffset() const;
+    ptrdiff_t getOffsetOf(const char *string) const;
+};
 
 extern ptrdiff_t
 SprintPut(Sprinter *sp, const char *s, size_t len);
 
 extern ptrdiff_t
 SprintCString(Sprinter *sp, const char *s);
 
 extern ptrdiff_t
--- a/js/src/methodjit/Compiler.cpp
+++ b/js/src/methodjit/Compiler.cpp
@@ -1835,23 +1835,22 @@ mjit::Compiler::finishThisUp()
 
     return Compile_Okay;
 }
 
 #ifdef DEBUG
 #define SPEW_OPCODE()                                                         \
     JS_BEGIN_MACRO                                                            \
         if (IsJaegerSpewChannelActive(JSpew_JSOps)) {                         \
-            LifoAllocScope las(&cx->tempLifoAlloc());                         \
-            Sprinter sprinter;                                                \
-            INIT_SPRINTER(cx, &sprinter, &cx->tempLifoAlloc(), 0);            \
+            Sprinter sprinter(cx);                                            \
+            sprinter.init();                                                  \
             js_Disassemble1(cx, script, PC, PC - script->code,                \
                             JS_TRUE, &sprinter);                              \
             JaegerSpew(JSpew_JSOps, "    %2d %s",                             \
-                       frame.stackDepth(), sprinter.base);                    \
+                       frame.stackDepth(), sprinter.string());                \
         }                                                                     \
     JS_END_MACRO;
 #else
 #define SPEW_OPCODE()
 #endif /* DEBUG */
 
 #define BEGIN_CASE(name)        case name:
 #define END_CASE(name)                      \
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -1911,19 +1911,19 @@ SrcNotes(JSContext *cx, JSScript *script
           case SRC_LABEL:
           case SRC_LABELBRACE:
           case SRC_BREAK2LABEL:
           case SRC_CONT2LABEL: {
             uint32_t index = js_GetSrcNoteOffset(sn, 0);
             JSAtom *atom = script->getAtom(index);
             Sprint(sp, " atom %u (", index);
             size_t len = PutEscapedString(NULL, 0, atom, '\0');
-            if (char *buf = SprintReserveAmount(sp, len)) {
+            if (char *buf = sp->reserve(len)) {
                 PutEscapedString(buf, len, atom, 0);
-                buf[len] = '\0';
+                buf[len] = 0;
             }
             Sprint(sp, ")");
             break;
           }
           case SRC_FUNCDEF: {
             uint32_t index = js_GetSrcNoteOffset(sn, 0);
             JSObject *obj = script->getObject(index);
             JSFunction *fun = obj->toFunction();
@@ -1959,30 +1959,30 @@ SrcNotes(JSContext *cx, JSScript *script
         }
         Sprint(sp, "\n");
     }
 }
 
 static JSBool
 Notes(JSContext *cx, uintN argc, jsval *vp)
 {
-    LifoAllocScope las(&cx->tempLifoAlloc());
-    Sprinter sprinter;
-    INIT_SPRINTER(cx, &sprinter, &cx->tempLifoAlloc(), 0);
+    Sprinter sprinter(cx);
+    if (!sprinter.init())
+        return JS_FALSE;
 
     jsval *argv = JS_ARGV(cx, vp);
     for (uintN i = 0; i < argc; i++) {
         JSScript *script = ValueToScript(cx, argv[i]);
         if (!script)
             continue;
 
         SrcNotes(cx, script, &sprinter);
     }
 
-    JSString *str = JS_NewStringCopyZ(cx, sprinter.base);
+    JSString *str = JS_NewStringCopyZ(cx, sprinter.string());
     if (!str)
         return JS_FALSE;
     JS_SET_RVAL(cx, vp, STRING_TO_JSVAL(str));
     return JS_TRUE;
 }
 
 JS_STATIC_ASSERT(JSTRY_CATCH == 0);
 JS_STATIC_ASSERT(JSTRY_FINALLY == 1);
@@ -2112,82 +2112,80 @@ struct DisassembleOptionParser {
 
 static JSBool
 DisassembleToString(JSContext *cx, uintN argc, jsval *vp)
 {
     DisassembleOptionParser p(argc, JS_ARGV(cx, vp));
     if (!p.parse(cx))
         return false;
 
-    LifoAllocScope las(&cx->tempLifoAlloc());
-    Sprinter sprinter;
-    INIT_SPRINTER(cx, &sprinter, &cx->tempLifoAlloc(), 0);
-    Sprinter *sp = &sprinter;
+    Sprinter sprinter(cx);
+    if (!sprinter.init())
+        return false;
 
     bool ok = true;
     if (p.argc == 0) {
         /* Without arguments, disassemble the current script. */
         if (JSStackFrame *frame = JS_GetScriptedCaller(cx, NULL)) {
             JSScript *script = JS_GetFrameScript(cx, frame);
-            if (js_Disassemble(cx, script, p.lines, sp)) {
-                SrcNotes(cx, script, sp);
-                TryNotes(cx, script, sp);
+            if (js_Disassemble(cx, script, p.lines, &sprinter)) {
+                SrcNotes(cx, script, &sprinter);
+                TryNotes(cx, script, &sprinter);
             } else {
                 ok = false;
             }
         }
     } else {
         for (uintN i = 0; i < p.argc; i++) {
             JSFunction *fun;
             JSScript *script = ValueToScript(cx, p.argv[i], &fun);
-            ok = ok && script && DisassembleScript(cx, script, fun, p.lines, p.recursive, sp);
+            ok = ok && script && DisassembleScript(cx, script, fun, p.lines, p.recursive, &sprinter);
         }
     }
 
-    JSString *str = ok ? JS_NewStringCopyZ(cx, sprinter.base) : NULL;
+    JSString *str = ok ? JS_NewStringCopyZ(cx, sprinter.string()) : NULL;
     if (!str)
         return false;
     JS_SET_RVAL(cx, vp, STRING_TO_JSVAL(str));
     return true;
 }
 
 static JSBool
 Disassemble(JSContext *cx, uintN argc, jsval *vp)
 {
     DisassembleOptionParser p(argc, JS_ARGV(cx, vp));
     if (!p.parse(cx))
         return false;
 
-    LifoAllocScope las(&cx->tempLifoAlloc());
-    Sprinter sprinter;
-    INIT_SPRINTER(cx, &sprinter, &cx->tempLifoAlloc(), 0);
-    Sprinter *sp = &sprinter;
+    Sprinter sprinter(cx);
+    if (!sprinter.init())
+        return false;
 
     bool ok = true;
     if (p.argc == 0) {
         /* Without arguments, disassemble the current script. */
         if (JSStackFrame *frame = JS_GetScriptedCaller(cx, NULL)) {
             JSScript *script = JS_GetFrameScript(cx, frame);
-            if (js_Disassemble(cx, script, p.lines, sp)) {
-                SrcNotes(cx, script, sp);
-                TryNotes(cx, script, sp);
+            if (js_Disassemble(cx, script, p.lines, &sprinter)) {
+                SrcNotes(cx, script, &sprinter);
+                TryNotes(cx, script, &sprinter);
             } else {
                 ok = false;
             }
         }
     } else {
         for (uintN i = 0; i < p.argc; i++) {
             JSFunction *fun;
             JSScript *script = ValueToScript(cx, p.argv[i], &fun);
-            ok = ok && script && DisassembleScript(cx, script, fun, p.lines, p.recursive, sp);
+            ok = ok && script && DisassembleScript(cx, script, fun, p.lines, p.recursive, &sprinter);
         }
     }
 
     if (ok)
-        fprintf(stdout, "%s\n", sprinter.base);
+        fprintf(stdout, "%s\n", sprinter.string());
     JS_SET_RVAL(cx, vp, JSVAL_VOID);
     return ok;
 }
 
 static JSBool
 DisassFile(JSContext *cx, uintN argc, jsval *vp)
 {
     /* Support extra options at the start, just like Dissassemble. */
@@ -2213,22 +2211,22 @@ DisassFile(JSContext *cx, uintN argc, js
 
     uint32_t oldopts = JS_GetOptions(cx);
     JS_SetOptions(cx, oldopts | JSOPTION_COMPILE_N_GO | JSOPTION_NO_SCRIPT_RVAL);
     JSScript *script = JS_CompileUTF8File(cx, thisobj, filename.ptr());
     JS_SetOptions(cx, oldopts);
     if (!script)
         return false;
 
-    LifoAllocScope las(&cx->tempLifoAlloc());
-    Sprinter sprinter;
-    INIT_SPRINTER(cx, &sprinter, &cx->tempLifoAlloc(), 0);
+    Sprinter sprinter(cx);
+    if (!sprinter.init())
+        return false;
     bool ok = DisassembleScript(cx, script, NULL, p.lines, p.recursive, &sprinter);
     if (ok)
-        fprintf(stdout, "%s\n", sprinter.base);
+        fprintf(stdout, "%s\n", sprinter.string());
     if (!ok)
         return false;
 
     JS_SET_RVAL(cx, vp, JSVAL_VOID);
     return true;
 }
 
 static JSBool
@@ -2262,20 +2260,21 @@ DisassWithSrc(JSContext *cx, uintN argc,
                                  JSSMSG_CANT_OPEN, script->filename,
                                  strerror(errno));
             return JS_FALSE;
         }
 
         pc = script->code;
         end = pc + script->length;
 
-        LifoAllocScope las(&cx->tempLifoAlloc());
-        Sprinter sprinter;
-        Sprinter *sp = &sprinter;
-        INIT_SPRINTER(cx, sp, &cx->tempLifoAlloc(), 0);
+        Sprinter sprinter(cx);
+        if (!sprinter.init()) {
+            ok = JS_FALSE;
+            goto bail;
+        }
 
         /* burn the leading lines */
         line2 = JS_PCToLineNumber(cx, script, pc);
         for (line1 = 0; line1 < line2 - 1; line1++) {
             char *tmp = fgets(linebuf, LINE_BUF_LEN, file);
             if (!tmp) {
                 JS_ReportError(cx, "failed to read %s fully", script->filename);
                 ok = JS_FALSE;
@@ -2285,36 +2284,36 @@ DisassWithSrc(JSContext *cx, uintN argc,
 
         bupline = 0;
         while (pc < end) {
             line2 = JS_PCToLineNumber(cx, script, pc);
 
             if (line2 < line1) {
                 if (bupline != line2) {
                     bupline = line2;
-                    Sprint(sp, "%s %3u: BACKUP\n", sep, line2);
+                    Sprint(&sprinter, "%s %3u: BACKUP\n", sep, line2);
                 }
             } else {
                 if (bupline && line1 == line2)
-                    Sprint(sp, "%s %3u: RESTORE\n", sep, line2);
+                    Sprint(&sprinter, "%s %3u: RESTORE\n", sep, line2);
                 bupline = 0;
                 while (line1 < line2) {
                     if (!fgets(linebuf, LINE_BUF_LEN, file)) {
                         JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL,
                                              JSSMSG_UNEXPECTED_EOF,
                                              script->filename);
                         ok = JS_FALSE;
                         goto bail;
                     }
                     line1++;
-                    Sprint(sp, "%s %3u: %s", sep, line1, linebuf);
+                    Sprint(&sprinter, "%s %3u: %s", sep, line1, linebuf);
                 }
             }
 
-            len = js_Disassemble1(cx, script, pc, pc - script->code, JS_TRUE, sp);
+            len = js_Disassemble1(cx, script, pc, pc - script->code, JS_TRUE, &sprinter);
             if (!len) {
                 ok = JS_FALSE;
                 goto bail;
             }
             pc += len;
         }
 
       bail: