Add profiling of basic block hit counts for IonMonkey and expose to addons, bug 811349. r=pierron
authorBrian Hackett <bhackett1024@gmail.com>
Fri, 16 Nov 2012 09:14:06 -0800
changeset 113509 ef6530d96b63062a4d16ca0e8758fff868ff7142
parent 113508 d86aa72c7dbf6ff76bea32db9079de93de53cf8d
child 113510 54dcaf34c6ccb9374e38281129b512d022ada918
push id18196
push userbhackett@mozilla.com
push dateFri, 16 Nov 2012 17:14:27 +0000
treeherdermozilla-inbound@ef6530d96b63 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspierron
bugs811349
milestone19.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
Add profiling of basic block hit counts for IonMonkey and expose to addons, bug 811349. r=pierron
js/src/ion/CodeGenerator.cpp
js/src/ion/CodeGenerator.h
js/src/ion/IonCode.h
js/src/ion/arm/MacroAssembler-arm.cpp
js/src/ion/arm/MacroAssembler-arm.h
js/src/ion/shared/Assembler-shared.h
js/src/ion/x64/MacroAssembler-x64.h
js/src/ion/x86/MacroAssembler-x86.h
js/src/jsopcode.cpp
js/src/jsscript.cpp
js/src/jsscript.h
js/src/jsscriptinlines.h
--- a/js/src/ion/CodeGenerator.cpp
+++ b/js/src/ion/CodeGenerator.cpp
@@ -1379,33 +1379,109 @@ CodeGenerator::visitCheckOverRecursedFai
     if (!callVM(CheckOverRecursedInfo, ool->lir()))
         return false;
 
     restoreLive(ool->lir());
     masm.jump(ool->rejoin());
     return true;
 }
 
+IonScriptCounts *
+CodeGenerator::maybeCreateScriptCounts()
+{
+    // If scripts are being profiled, create a new IonScriptCounts and attach
+    // it to the script. This must be done on the main thread.
+    JSContext *cx = GetIonContext()->cx;
+    if (!cx)
+        return NULL;
+
+    IonScriptCounts *counts = NULL;
+
+    CompileInfo *outerInfo = &gen->info();
+    RawScript script = outerInfo->script();
+
+    if (cx->runtime->profilingScripts && !script->hasScriptCounts) {
+        if (!script->initScriptCounts(cx))
+            return NULL;
+    }
+
+    if (!script->hasScriptCounts)
+        return NULL;
+
+    counts = js_new<IonScriptCounts>();
+    if (!counts || !counts->init(graph.numBlocks())) {
+        js_delete(counts);
+        return NULL;
+    }
+
+    script->addIonCounts(counts);
+
+    for (size_t i = 0; i < graph.numBlocks(); i++) {
+        MBasicBlock *block = graph.getBlock(i)->mir();
+
+        // Find a PC offset in the outermost script to use. If this block is
+        // from an inlined script, find a location in the outer script to
+        // associate information about the inling with.
+        MResumePoint *resume = block->entryResumePoint();
+        while (resume->caller())
+            resume = resume->caller();
+        uint32 offset = resume->pc() - script->code;
+        JS_ASSERT(offset < script->length);
+
+        if (!counts->block(i).init(block->id(), offset, block->numSuccessors()))
+            return NULL;
+        for (size_t j = 0; j < block->numSuccessors(); j++)
+            counts->block(i).setSuccessor(j, block->getSuccessor(j)->id());
+    }
+
+    return counts;
+}
+
 bool
 CodeGenerator::generateBody()
 {
+    IonScriptCounts *counts = maybeCreateScriptCounts();
+
     for (size_t i = 0; i < graph.numBlocks(); i++) {
         current = graph.getBlock(i);
-        for (LInstructionIterator iter = current->begin(); iter != current->end(); iter++) {
+
+        LInstructionIterator iter = current->begin();
+
+        // Separately visit the label at the start of every block, so that
+        // count instrumentation is inserted after the block label is bound.
+        if (!iter->accept(this))
+            return false;
+        iter++;
+
+        mozilla::Maybe<Sprinter> printer;
+        if (counts) {
+            masm.inc64(AbsoluteAddress(counts->block(i).addressOfHitCount()));
+            printer.construct(GetIonContext()->cx);
+            if (!printer.ref().init())
+                return false;
+        }
+
+        for (; iter != current->end(); iter++) {
             IonSpew(IonSpew_Codegen, "instruction %s", iter->opName());
+            if (counts)
+                printer.ref().printf("[%s]\n", iter->opName());
+
             if (iter->safepoint() && pushedArgumentSlots_.length()) {
                 if (!markArgumentSlots(iter->safepoint()))
                     return false;
             }
 
             if (!iter->accept(this))
                 return false;
         }
         if (masm.oom())
             return false;
+
+        if (counts)
+            counts->block(i).setCode(printer.ref().string());
     }
 
     JS_ASSERT(pushedArgumentSlots_.empty());
     return true;
 }
 
 // Out-of-line object allocation for LNewArray.
 class OutOfLineNewArray : public OutOfLineCodeBase<CodeGenerator>
--- a/js/src/ion/CodeGenerator.h
+++ b/js/src/ion/CodeGenerator.h
@@ -214,15 +214,17 @@ class CodeGenerator : public CodeGenerat
     }
 
   private:
     bool visitCache(LInstruction *load);
     bool visitCallSetProperty(LInstruction *ins);
 
     ConstantOrRegister getSetPropertyValue(LInstruction *ins);
     bool generateBranchV(const ValueOperand &value, Label *ifTrue, Label *ifFalse, FloatRegister fr);
+
+    IonScriptCounts *maybeCreateScriptCounts();
 };
 
 } // namespace ion
 } // namespace js
 
 #endif // jsion_codegen_h__
 
--- a/js/src/ion/IonCode.h
+++ b/js/src/ion/IonCode.h
@@ -397,16 +397,151 @@ struct IonScript
         if (!refcount_)
             Destroy(fop, this);
     }
     const types::RecompileInfo& recompileInfo() const {
         return recompileInfo_;
     }
 };
 
+// Execution information for a basic block which may persist after the
+// accompanying IonScript is destroyed, for use during profiling.
+struct IonBlockCounts
+{
+  private:
+    uint32 id_;
+
+    // Approximate bytecode in the outer (not inlined) script this block
+    // was generated from.
+    uint32 offset_;
+
+    // ids for successors of this block.
+    uint32 numSuccessors_;
+    uint32 *successors_;
+
+    // Hit count for this block.
+    uint64 hitCount_;
+
+    // Information about the code generated for this block.
+    char *code_;
+
+  public:
+
+    bool init(uint32 id, uint32 offset, uint32 numSuccessors) {
+        id_ = id;
+        offset_ = offset;
+        numSuccessors_ = numSuccessors;
+        if (numSuccessors) {
+            successors_ = (uint32 *) js_calloc(numSuccessors * sizeof(uint32));
+            if (!successors_)
+                return false;
+        }
+        return true;
+    }
+
+    void destroy() {
+        if (successors_)
+            js_free(successors_);
+        if (code_)
+            js_free(code_);
+    }
+
+    uint32 id() const {
+        return id_;
+    }
+
+    uint32 offset() const {
+        return offset_;
+    }
+
+    size_t numSuccessors() const {
+        return numSuccessors_;
+    }
+
+    void setSuccessor(size_t i, uint32 id) {
+        JS_ASSERT(i < numSuccessors_);
+        successors_[i] = id;
+    }
+
+    uint32 successor(size_t i) const {
+        JS_ASSERT(i < numSuccessors_);
+        return successors_[i];
+    }
+
+    uint64 *addressOfHitCount() {
+        return &hitCount_;
+    }
+
+    uint64 hitCount() const {
+        return hitCount_;
+    }
+
+    void setCode(const char *code) {
+        char *ncode = (char *) js_malloc(strlen(code) + 1);
+        if (ncode) {
+            strcpy(ncode, code);
+            code_ = ncode;
+        }
+    }
+
+    const char *code() const {
+        return code_;
+    }
+};
+
+// Execution information for a compiled script which may persist after the
+// IonScript is destroyed, for use during profiling.
+struct IonScriptCounts
+{
+  private:
+    // Any previous invalidated compilation(s) for the script.
+    IonScriptCounts *previous_;
+
+    // Information about basic blocks in this script.
+    size_t numBlocks_;
+    IonBlockCounts *blocks_;
+
+  public:
+
+    IonScriptCounts() {
+        PodZero(this);
+    }
+
+    ~IonScriptCounts() {
+        for (size_t i = 0; i < numBlocks_; i++)
+            blocks_[i].destroy();
+        js_free(blocks_);
+        if (previous_)
+            js_delete(previous_);
+    }
+
+    bool init(size_t numBlocks) {
+        numBlocks_ = numBlocks;
+        blocks_ = (IonBlockCounts *) js_calloc(numBlocks * sizeof(IonBlockCounts));
+        return blocks_ != NULL;
+    }
+
+    size_t numBlocks() const {
+        return numBlocks_;
+    }
+
+    IonBlockCounts &block(size_t i) {
+        JS_ASSERT(i < numBlocks_);
+        return blocks_[i];
+    }
+
+    void setPrevious(IonScriptCounts *previous) {
+        previous_ = previous;
+    }
+
+    IonScriptCounts *previous() const {
+        return previous_;
+    }
+};
+
 struct VMFunction;
 
 class IonCompartment;
 
 struct AutoFlushCache {
 
   private:
     uintptr_t start_;
--- a/js/src/ion/arm/MacroAssembler-arm.cpp
+++ b/js/src/ion/arm/MacroAssembler-arm.cpp
@@ -60,16 +60,22 @@ MacroAssemblerARM::branchTruncateDouble(
 {
     ma_vcvt_F64_I32(src, ScratchFloatReg);
     ma_vxfer(ScratchFloatReg, dest);
     ma_cmp(dest, Imm32(0x7fffffff));
     ma_cmp(dest, Imm32(0x80000000), Assembler::NotEqual);
     ma_b(fail, Assembler::Equal);
 }
 
+void
+MacroAssemblerARM::inc64(AbsoluteAddress dest)
+{
+    JS_NOT_REACHED("NYI");
+}
+
 bool
 MacroAssemblerARM::alu_dbl(Register src1, Imm32 imm, Register dest, ALUOp op,
                            SetCond_ sc, Condition c)
 {
     if ((sc == SetCond && ! condsAreSafe(op)) || !can_dbl(op))
         return false;
     ALUOp interop = getDestVariant(op);
     Imm8::TwoImm8mData both = Imm8::encodeTwoImms(imm.value);
--- a/js/src/ion/arm/MacroAssembler-arm.h
+++ b/js/src/ion/arm/MacroAssembler-arm.h
@@ -30,16 +30,18 @@ class MacroAssemblerARM : public Assembl
     MacroAssemblerARM()
     { }
 
     void convertInt32ToDouble(const Register &src, const FloatRegister &dest);
     void convertUInt32ToDouble(const Register &src, const FloatRegister &dest);
     void convertDoubleToFloat(const FloatRegister &src, const FloatRegister &dest);
     void branchTruncateDouble(const FloatRegister &src, const Register &dest, Label *fail);
 
+    void inc64(AbsoluteAddress dest);
+
     // somewhat direct wrappers for the low-level assembler funcitons
     // bitops
     // attempt to encode a virtual alu instruction using
     // two real instructions.
   private:
     bool alu_dbl(Register src1, Imm32 imm, Register dest, ALUOp op,
                  SetCond_ sc, Condition c);
   public:
--- a/js/src/ion/shared/Assembler-shared.h
+++ b/js/src/ion/shared/Assembler-shared.h
@@ -102,16 +102,20 @@ struct ImmGCPtr
 
 // Specifies a hardcoded, absolute address.
 struct AbsoluteAddress {
     void *addr;
 
     explicit AbsoluteAddress(void *addr)
       : addr(addr)
     { }
+
+    AbsoluteAddress offset(ptrdiff_t delta) {
+        return AbsoluteAddress(((uint8 *) addr) + delta);
+    }
 };
 
 // Specifies an address computed in the form of a register base and a constant,
 // 32-bit offset.
 struct Address
 {
     Register base;
     int32 offset;
--- a/js/src/ion/x64/MacroAssembler-x64.h
+++ b/js/src/ion/x64/MacroAssembler-x64.h
@@ -821,16 +821,21 @@ class MacroAssemblerX64 : public MacroAs
     void loadInstructionPointerAfterCall(const Register &dest) {
         movq(Operand(StackPointer, 0x0), dest);
     }
 
     void convertUInt32ToDouble(const Register &src, const FloatRegister &dest) {
         cvtsq2sd(src, dest);
     }
 
+    void inc64(AbsoluteAddress dest) {
+        mov(ImmWord(dest.addr), ScratchReg);
+        addPtr(Imm32(1), Address(ScratchReg, 0));
+    }
+
     // Setup a call to C/C++ code, given the number of general arguments it
     // takes. Note that this only supports cdecl.
     //
     // In order for alignment to work correctly, the MacroAssembler must have a
     // consistent view of the stack displacement. It is okay to call "push"
     // manually, however, if the stack alignment were to change, the macro
     // assembler should be notified before starting a call.
     void setupAlignedABICall(uint32 args);
--- a/js/src/ion/x86/MacroAssembler-x86.h
+++ b/js/src/ion/x86/MacroAssembler-x86.h
@@ -678,16 +678,24 @@ class MacroAssemblerX86 : public MacroAs
         cvtsi2sd(src, dest);
 
         // dest is now a double with the int range.
         // correct the double value by adding 0x80000000.
         static const double NegativeOne = 2147483648.0;
         addsd(Operand(&NegativeOne), dest);
     }
 
+    void inc64(AbsoluteAddress dest) {
+        addl(Imm32(1), Operand(dest));
+        Label noOverflow;
+        j(NonZero, &noOverflow);
+        addl(Imm32(1), Operand(dest.offset(4)));
+        bind(&noOverflow);
+    }
+
     // Setup a call to C/C++ code, given the number of general arguments it
     // takes. Note that this only supports cdecl.
     //
     // In order for alignment to work correctly, the MacroAssembler must have a
     // consistent view of the stack displacement. It is okay to call "push"
     // manually, however, if the stack alignment were to change, the macro
     // assembler should be notified before starting a call.
     void setupAlignedABICall(uint32 args);
--- a/js/src/jsopcode.cpp
+++ b/js/src/jsopcode.cpp
@@ -313,16 +313,31 @@ js_DumpPCCounts(JSContext *cx, HandleScr
                 Sprint(sp, "\"%s\": %.0f", PCCounts::countName(op, i), val);
                 printed = true;
             }
         }
         Sprint(sp, "}\n");
 
         pc = next;
     }
+
+    ion::IonScriptCounts *ionCounts = script->getIonCounts();
+
+    while (ionCounts) {
+        Sprint(sp, "IonScript [%lu blocks]:\n", ionCounts->numBlocks());
+        for (size_t i = 0; i < ionCounts->numBlocks(); i++) {
+            const ion::IonBlockCounts &block = ionCounts->block(i);
+            Sprint(sp, "BB #%lu [%05u]", block.id(), block.offset());
+            for (size_t j = 0; j < block.numSuccessors(); j++)
+                Sprint(sp, " -> #%lu", block.successor(j));
+            Sprint(sp, " :: %llu hits\n", block.hitCount());
+            Sprint(sp, "%s\n", block.code());
+        }
+        ionCounts = ionCounts->previous();
+    }
 }
 
 /*
  * If pc != NULL, include a prefix indicating whether the PC is at the current line.
  * If counts != NULL, include a counter of the number of times each op was executed.
  */
 JS_FRIEND_API(JSBool)
 js_DisassembleAtPC(JSContext *cx, JSScript *scriptArg, JSBool lines, jsbytecode *pc, Sprinter *sp)
@@ -6697,16 +6712,28 @@ GetPCCountScriptSummary(JSContext *cx, s
                               JS_ARRAY_LENGTH(accessTotals), comma);
     AppendArrayJSONProperties(cx, buf, elementTotals, countElementNames,
                               JS_ARRAY_LENGTH(elementTotals), comma);
     AppendArrayJSONProperties(cx, buf, propertyTotals, countPropertyNames,
                               JS_ARRAY_LENGTH(propertyTotals), comma);
     AppendArrayJSONProperties(cx, buf, arithTotals, countArithNames,
                               JS_ARRAY_LENGTH(arithTotals), comma);
 
+    uint64_t ionActivity = 0;
+    ion::IonScriptCounts *ionCounts = sac.getIonCounts();
+    while (ionCounts) {
+        for (size_t i = 0; i < ionCounts->numBlocks(); i++)
+            ionActivity += ionCounts->block(i).hitCount();
+        ionCounts = ionCounts->previous();
+    }
+    if (ionActivity) {
+        AppendJSONProperty(buf, "ion", comma);
+        NumberValueToStringBuffer(cx, DoubleValue(ionActivity), buf);
+    }
+
     buf.append('}');
     buf.append('}');
 
     if (cx->isExceptionPending())
         return NULL;
 
     return buf.finishString();
 }
@@ -6833,16 +6860,64 @@ GetPCCountJSON(JSContext *cx, const Scri
             }
         }
 
         buf.append('}');
         buf.append('}');
     }
 
     buf.append(']');
+
+    ion::IonScriptCounts *ionCounts = sac.getIonCounts();
+    if (ionCounts) {
+        AppendJSONProperty(buf, "ion");
+        buf.append('[');
+        bool comma = false;
+        while (ionCounts) {
+            if (comma)
+                buf.append(',');
+            comma = true;
+
+            buf.append('[');
+            for (size_t i = 0; i < ionCounts->numBlocks(); i++) {
+                if (i)
+                    buf.append(',');
+                const ion::IonBlockCounts &block = ionCounts->block(i);
+
+                buf.append('{');
+                AppendJSONProperty(buf, "id", NO_COMMA);
+                NumberValueToStringBuffer(cx, Int32Value(block.id()), buf);
+                AppendJSONProperty(buf, "offset");
+                NumberValueToStringBuffer(cx, Int32Value(block.offset()), buf);
+                AppendJSONProperty(buf, "successors");
+                buf.append('[');
+                for (size_t j = 0; j < block.numSuccessors(); j++) {
+                    if (j)
+                        buf.append(',');
+                    NumberValueToStringBuffer(cx, Int32Value(block.successor(j)), buf);
+                }
+                buf.append(']');
+                AppendJSONProperty(buf, "hits");
+                NumberValueToStringBuffer(cx, DoubleValue(block.hitCount()), buf);
+
+                AppendJSONProperty(buf, "code");
+                JSString *str = JS_NewStringCopyZ(cx, block.code());
+                if (!str || !(str = JS_ValueToSource(cx, StringValue(str))))
+                    return false;
+                buf.append(str);
+
+                buf.append('}');
+            }
+            buf.append(']');
+
+            ionCounts = ionCounts->previous();
+        }
+        buf.append(']');
+    }
+
     buf.append('}');
 
     return !cx->isExceptionPending();
 }
 
 JS_FRIEND_API(JSString *)
 GetPCCountScriptContents(JSContext *cx, size_t index)
 {
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -837,47 +837,64 @@ JSScript::initScriptCounts(JSContext *cx
     /* Enable interrupts in any interpreter frames running on this script. */
     InterpreterFrames *frames;
     for (frames = cx->runtime->interpreterFrames; frames; frames = frames->older)
         frames->enableInterruptsIfRunning(this);
 
     return true;
 }
 
+static inline ScriptCountsMap::Ptr GetScriptCountsMapEntry(JSScript *script)
+{
+    JS_ASSERT(script->hasScriptCounts);
+    ScriptCountsMap *map = script->compartment()->scriptCountsMap;
+    ScriptCountsMap::Ptr p = map->lookup(script);
+    JS_ASSERT(p);
+    return p;
+}
+
 js::PCCounts
 JSScript::getPCCounts(jsbytecode *pc) {
-    JS_ASSERT(hasScriptCounts);
     JS_ASSERT(size_t(pc - code) < length);
-    ScriptCountsMap *map = compartment()->scriptCountsMap;
-    JS_ASSERT(map);
-    ScriptCountsMap::Ptr p = map->lookup(this);
-    JS_ASSERT(p);
+    ScriptCountsMap::Ptr p = GetScriptCountsMapEntry(this);
     return p->value.pcCountsVector[pc - code];
 }
 
+void
+JSScript::addIonCounts(ion::IonScriptCounts *ionCounts)
+{
+    ScriptCountsMap::Ptr p = GetScriptCountsMapEntry(this);
+    if (p->value.ionCounts)
+        ionCounts->setPrevious(p->value.ionCounts);
+    p->value.ionCounts = ionCounts;
+}
+
+ion::IonScriptCounts *
+JSScript::getIonCounts()
+{
+    ScriptCountsMap::Ptr p = GetScriptCountsMapEntry(this);
+    return p->value.ionCounts;
+}
+
 ScriptCounts
 JSScript::releaseScriptCounts()
 {
-    JS_ASSERT(hasScriptCounts);
-    ScriptCountsMap *map = compartment()->scriptCountsMap;
-    JS_ASSERT(map);
-    ScriptCountsMap::Ptr p = map->lookup(this);
-    JS_ASSERT(p);
+    ScriptCountsMap::Ptr p = GetScriptCountsMapEntry(this);
     ScriptCounts counts = p->value;
-    map->remove(p);
+    compartment()->scriptCountsMap->remove(p);
     hasScriptCounts = false;
     return counts;
 }
 
 void
 JSScript::destroyScriptCounts(FreeOp *fop)
 {
     if (hasScriptCounts) {
         ScriptCounts scriptCounts = releaseScriptCounts();
-        fop->free_(scriptCounts.pcCountsVector);
+        scriptCounts.destroy(fop);
     }
 }
 
 #ifdef JS_THREADSAFE
 void
 SourceCompressorThread::compressorThread(void *arg)
 {
     PR_SetCurrentThreadName("JS Source Compressing Thread");
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -18,16 +18,17 @@
 #include "jsscope.h"
 
 #include "gc/Barrier.h"
 
 namespace js {
 
 namespace ion {
     struct IonScript;
+    struct IonScriptCounts;
 }
 
 # define ION_DISABLED_SCRIPT ((js::ion::IonScript *)0x1)
 # define ION_COMPILING_SCRIPT ((js::ion::IonScript *)0x2)
 
 struct Shape;
 
 class BindingIter;
@@ -211,30 +212,35 @@ struct RootMethods<Bindings> {
         return IsPoisonedPtr(bindings.callObjShape());
     }
 };
 
 class ScriptCounts
 {
     friend struct ::JSScript;
     friend struct ScriptAndCounts;
+
     /*
      * This points to a single block that holds an array of PCCounts followed
      * by an array of doubles.  Each element in the PCCounts array has a
      * pointer into the array of doubles.
      */
     PCCounts *pcCountsVector;
 
+    /* Information about any Ion compilations for the script. */
+    ion::IonScriptCounts *ionCounts;
+
  public:
-    ScriptCounts() : pcCountsVector(NULL) { }
+    ScriptCounts() : pcCountsVector(NULL), ionCounts(NULL) { }
 
     inline void destroy(FreeOp *fop);
 
     void set(js::ScriptCounts counts) {
         pcCountsVector = counts.pcCountsVector;
+        ionCounts = counts.ionCounts;
     }
 };
 
 typedef HashMap<JSScript *,
                 ScriptCounts,
                 DefaultHasher<JSScript *>,
                 SystemAllocPolicy> ScriptCountsMap;
 
@@ -729,16 +735,18 @@ struct JSScript : public js::gc::Cell
      * MethodJIT.cpp.)
      */
     size_t sizeOfJitScripts(JSMallocSizeOfFun mallocSizeOf);
 #endif
 
   public:
     bool initScriptCounts(JSContext *cx);
     js::PCCounts getPCCounts(jsbytecode *pc);
+    void addIonCounts(js::ion::IonScriptCounts *ionCounts);
+    js::ion::IonScriptCounts *getIonCounts();
     js::ScriptCounts releaseScriptCounts();
     void destroyScriptCounts(js::FreeOp *fop);
 
     jsbytecode *main() {
         return code + mainOffset;
     }
 
     /*
@@ -1238,16 +1246,20 @@ struct ScriptAndCounts
 {
     JSScript *script;
     ScriptCounts scriptCounts;
 
     PCCounts &getPCCounts(jsbytecode *pc) const {
         JS_ASSERT(unsigned(pc - script->code) < script->length);
         return scriptCounts.pcCountsVector[pc - script->code];
     }
+
+    ion::IonScriptCounts *getIonCounts() const {
+        return scriptCounts.ionCounts;
+    }
 };
 
 } /* namespace js */
 
 extern jssrcnote *
 js_GetSrcNote(JSContext *cx, js::RawScript script, jsbytecode *pc);
 
 extern jsbytecode *
--- a/js/src/jsscriptinlines.h
+++ b/js/src/jsscriptinlines.h
@@ -57,16 +57,17 @@ CurrentScriptFileLineOrigin(JSContext *c
 
     CurrentScriptFileLineOriginSlow(cx, file, linenop, origin);
 }
 
 inline void
 ScriptCounts::destroy(FreeOp *fop)
 {
     fop->free_(pcCountsVector);
+    fop->delete_(ionCounts);
 }
 
 inline void
 MarkScriptFilename(JSRuntime *rt, const char *filename)
 {
     /*
      * As an invariant, a ScriptFilenameEntry should not be 'marked' outside of
      * a GC. Since SweepScriptFilenames is only called during a full gc,