Bug 1204554 part 2 - Split LCov functions to make the aggregation of results incremental. r=terrence,bhackett
authorNicolas B. Pierron <nicolas.b.pierron@mozilla.com>
Thu, 01 Oct 2015 12:41:40 +0200
changeset 265600 6d0e0a5cfb4ba045de4205bfdd19e1b5887909f0
parent 265599 aa410adc30c058eb2cc30ccc2237b015b939076a
child 265601 673f622280ed4fb926e12fb012ba0054af4ece14
push id15472
push usercbook@mozilla.com
push dateFri, 02 Oct 2015 11:51:34 +0000
treeherderfx-team@2c33ef6b27e0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersterrence, bhackett
bugs1204554
milestone44.0a1
Bug 1204554 part 2 - Split LCov functions to make the aggregation of results incremental. r=terrence,bhackett
js/src/jsopcode.cpp
js/src/jsscript.h
js/src/moz.build
js/src/vm/CodeCoverage.cpp
js/src/vm/CodeCoverage.h
--- a/js/src/jsopcode.cpp
+++ b/js/src/jsopcode.cpp
@@ -32,16 +32,17 @@
 #include "jsstr.h"
 #include "jstypes.h"
 #include "jsutil.h"
 
 #include "asmjs/AsmJSModule.h"
 #include "frontend/BytecodeCompiler.h"
 #include "frontend/SourceNotes.h"
 #include "js/CharacterEncoding.h"
+#include "vm/CodeCoverage.h"
 #include "vm/Opcodes.h"
 #include "vm/ScopeObject.h"
 #include "vm/Shape.h"
 #include "vm/StringBuffer.h"
 
 #include "jscntxtinlines.h"
 #include "jscompartmentinlines.h"
 #include "jsobjinlines.h"
@@ -1940,256 +1941,58 @@ js::GetPCCountScriptContents(JSContext* 
         if (!GetPCCountJSON(cx, sac, buf))
             return nullptr;
     }
 
     return buf.finishString();
 }
 
 static bool
-LcovWriteScriptName(GenericPrinter& out, JSScript* script)
-{
-    JSFunction* fun = script->functionNonDelazifying();
-    if (fun && fun->displayAtom())
-        return EscapedStringPrinter(out, fun->displayAtom(), 0);
-    out.printf("top-level");
-    return true;
-}
-
-struct LcovSourceFile
-{
-    const char* filename;
-
-    LSprinter outFN;
-    LSprinter outFNDA;
-    size_t numFunctionsFound;
-    size_t numFunctionsHit;
-
-    LSprinter outBRDA;
-    size_t numBranchesFound;
-    size_t numBranchesHit;
-
-    LSprinter outDA;
-    size_t numLinesInstrumented;
-    size_t numLinesHit;
-
-    LcovSourceFile(LifoAlloc* alloc, JSScript *script)
-      : filename(script->filename()),
-        outFN(alloc),
-        outFNDA(alloc),
-        numFunctionsFound(0),
-        numFunctionsHit(0),
-        outBRDA(alloc),
-        numBranchesFound(0),
-        numBranchesHit(0),
-        outDA(alloc),
-        numLinesInstrumented(0),
-        numLinesHit(0)
-    { }
-};
-
-static bool
-LcovWriteScript(LcovSourceFile& lsf, JSScript* script)
-{
-    lsf.numFunctionsFound++;
-    lsf.outFN.printf("FN:%d,", script->lineno());
-    if (!LcovWriteScriptName(lsf.outFN, script))
-        return false;
-    lsf.outFN.put("\n", 1);
-
-    uint64_t hits = 0;
-    ScriptCounts* sc = nullptr;
-    if (script->hasScriptCounts()) {
-        sc = &script->getScriptCounts();
-        lsf.numFunctionsHit++;
-        const PCCounts* counts = sc->maybeGetPCCounts(script->pcToOffset(script->main()));
-        lsf.outFNDA.printf("FNDA:%" PRIu64 ",", counts->numExec());
-        if (!LcovWriteScriptName(lsf.outFNDA, script))
-            return false;
-        lsf.outFNDA.put("\n", 1);
-
-        // Set the hit count of the pre-main code to 1, if the function ever got
-        // visited.
-        hits = 1;
-    }
-
-    jsbytecode* snpc = script->code();
-    jssrcnote* sn = script->notes();
-    if (!SN_IS_TERMINATOR(sn))
-        snpc += SN_DELTA(sn);
-
-    size_t lineno = script->lineno();
-    jsbytecode* end = script->codeEnd();
-    size_t blockId = 0;
-    for (jsbytecode* pc = script->code(); pc != end; pc = GetNextPc(pc)) {
-        JSOp op = JSOp(*pc);
-        bool jump = IsJumpOpcode(op);
-        bool fallsthrough = BytecodeFallsThrough(op);
-
-        // If the current script & pc has a hit-count report, then update the
-        // current number of hits.
-        if (sc) {
-            const PCCounts* counts = sc->maybeGetPCCounts(script->pcToOffset(pc));
-            if (counts)
-                hits = counts->numExec();
-        }
-
-        // If we have additional source notes, walk all the source notes of the
-        // current pc.
-        if (snpc <= pc) {
-            size_t oldLine = lineno;
-            while (!SN_IS_TERMINATOR(sn) && snpc <= pc) {
-                SrcNoteType type = (SrcNoteType) SN_TYPE(sn);
-                if (type == SRC_SETLINE)
-                    lineno = size_t(GetSrcNoteOffset(sn, 0));
-                else if (type == SRC_NEWLINE)
-                    lineno++;
-
-                sn = SN_NEXT(sn);
-                snpc += SN_DELTA(sn);
-            }
-
-            if (oldLine != lineno && fallsthrough) {
-                lsf.outDA.printf("DA:%d,%" PRIu64 "\n", lineno, hits);
-
-                // Count the number of lines instrumented & hit.
-                lsf.numLinesInstrumented++;
-                if (hits)
-                    lsf.numLinesHit++;
-            }
-        }
-
-        // If the current instruction has thrown, then decrement the hit counts
-        // with the number of throws.
-        if (sc) {
-            const PCCounts* counts = sc->maybeGetThrowCounts(script->pcToOffset(pc));
-            if (counts)
-                hits -= counts->numExec();
-        }
-
-        // If the current pc corresponds to a conditional jump instruction, then reports
-        // branch hits.
-        if (jump && fallsthrough) {
-            jsbytecode* target = pc + GET_JUMP_OFFSET(pc);
-            jsbytecode* fallthroughTarget = GetNextPc(pc);
-            uint64_t fallthroughHits = 0;
-            if (sc) {
-                const PCCounts* counts = sc->maybeGetPCCounts(script->pcToOffset(fallthroughTarget));
-                if (counts)
-                    fallthroughHits = counts->numExec();
-            }
-
-            size_t targetId = script->pcToOffset(target);
-            uint64_t taken = hits - fallthroughHits;
-            lsf.outBRDA.printf("BRDA:%d,%d,%d,", lineno, blockId, targetId);
-            if (hits)
-                lsf.outBRDA.printf("%d\n", taken);
-            else
-                lsf.outBRDA.put("-\n", 2);
-
-            // Count the number of branches, and the number of branches hit.
-            lsf.numBranchesFound++;
-            if (hits)
-                lsf.numBranchesHit++;
-
-            // Update the blockId when there is a discontinuity.
-            blockId = script->pcToOffset(fallthroughTarget);
-        }
-    }
-
-    return true;
-}
-
-static bool
 GenerateLcovInfo(JSContext* cx, JSCompartment* comp, GenericPrinter& out)
 {
     JSRuntime* rt = cx->runtime();
 
     // Collect the list of scripts which are part of the current compartment.
     Rooted<ScriptVector> topScripts(cx, ScriptVector(cx));
     for (ZonesIter zone(rt, SkipAtoms); !zone.done(); zone.next()) {
         for (ZoneCellIter i(zone, AllocKind::SCRIPT); !i.done(); i.next()) {
             JSScript* script = i.get<JSScript>();
-            if (script->compartment() != comp)
-                continue;
-
-            // If we evaluate some code which contains a syntax error, then we
-            // might produce a JSScript which has no associated bytecode. This
-            // line filters out this kind of scripts.
-            if (!script->code())
-                continue;
-
-            // Filter out any JSScript which is not the top-level of a file.
-            if (script->functionNonDelazifying())
+            if (script->compartment() != comp || !script->isTopLevel())
                 continue;
 
             if (!topScripts.append(script))
                 return false;
         }
     }
 
     if (topScripts.length() == 0)
         return true;
 
     // Sort the information to avoid generating multiple file entries, and to
     // generate functions in the right order.
     auto lessFun = [](const JSScript* lhs, const JSScript* rhs) -> bool {
-        int d = strcmp(lhs->filename(), rhs->filename());
-        /*
-          This should not be necessary as we are supposed to have only the
-          top-level script.
-
-          d = (d != 0) ? d : lhs->lineno() - rhs->lineno();
-          d = (d != 0) ? d : lhs->column() - rhs->column();
-        */
-        return d < 0;
+        return strcmp(lhs->filename(), rhs->filename()) < 0;
     };
     std::sort(topScripts.begin(), topScripts.end(), lessFun);
 
-    // lcov trace files are starting with an optional test case name, that we
-    // recycle to be a compartment name.
-    out.put("TN:");
-    if (rt->compartmentNameCallback) {
-        char name[1024];
-        (*rt->compartmentNameCallback)(rt, comp, name, sizeof(name));
-        for (char *s = name; s < name + sizeof(name) && *s; s++) {
-            if (('a' <= *s && *s <= 'z') ||
-                ('A' <= *s && *s <= 'Z') ||
-                ('0' <= *s && *s <= '9'))
-            {
-                out.put(s, 1);
-                continue;
-            }
-            out.printf("_%p", (void*) size_t(*s));
-        }
-        out.put("\n", 1);
-    } else {
-        out.printf("Compartment_%p%p\n", (void*) size_t('_'), comp);
-    }
-
-    // For each source file
-    LifoAlloc printerAlloc(4096);
+    // Collect code coverage info for one compartment.
+    coverage::LCovCompartment compCover;
     for (JSScript* topLevel: topScripts) {
-        LifoAllocScope printerScope(&printerAlloc);
-        LcovSourceFile lsf(&printerAlloc, topLevel);
+        RootedScript topScript(cx, topLevel);
 
         // We found the top-level script, visit all the functions reachable
-        // from the top-level function.
+        // from the top-level function, and delazify them.
         Rooted<ScriptVector> queue(cx, ScriptVector(cx));
         if (!queue.append(topLevel))
             return false;
 
         RootedScript script(cx);
         do {
             script = queue.popCopy();
 
-            // Code the current script before pushing.
-            if (!LcovWriteScript(lsf, script))
-                return false;
-
             // Iterate from the last to the first object in order to have
             // the functions them visited in the opposite order when popping
             // elements from the stack of remaining scripts, such that the
             // functions are more-less listed with increasing line numbers.
             if (!script->hasObjects())
                 continue;
             size_t idx = script->objects()->length;
             while (idx--) {
@@ -2206,43 +2009,20 @@ GenerateLcovInfo(JSContext* cx, JSCompar
 
                 // Queue the script in the list of script associated to the
                 // current source.
                 if (!queue.append(fun.getOrCreateScript(cx)))
                     return false;
             }
         } while (!queue.empty());
 
-        if (lsf.outFN.hadOutOfMemory() ||
-            lsf.outFNDA.hadOutOfMemory() ||
-            lsf.outBRDA.hadOutOfMemory() ||
-            lsf.outDA.hadOutOfMemory())
-        {
-            out.reportOutOfMemory();
-            return false;
-        }
-
-        out.printf("SF:%s\n", lsf.filename);
-
-        lsf.outFN.exportInto(out);
-        lsf.outFNDA.exportInto(out);
-        out.printf("FNF:%d\n", lsf.numFunctionsFound);
-        out.printf("FNH:%d\n", lsf.numFunctionsHit);
-
-        lsf.outBRDA.exportInto(out);
-        out.printf("BRF:%d\n", lsf.numBranchesFound);
-        out.printf("BRH:%d\n", lsf.numBranchesHit);
-
-        lsf.outDA.exportInto(out);
-        out.printf("LF:%d\n", lsf.numLinesInstrumented);
-        out.printf("LH:%d\n", lsf.numLinesHit);
-
-        out.put("end_of_record\n");
+        compCover.collectCodeCoverageInfo(comp, topScript);
     }
 
+    compCover.exportInto(out);
     if (out.hadOutOfMemory())
         return false;
     return true;
 }
 
 JS_FRIEND_API(char*)
 js::GetCodeCoverageSummary(JSContext* cx, size_t* length)
 {
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -1607,16 +1607,28 @@ class JSScript : public js::gc::TenuredC
     const char* filename() const { return scriptSource()->filename(); }
     const char* maybeForwardedFilename() const { return maybeForwardedScriptSource()->filename(); }
 
   public:
 
     /* Return whether this script was compiled for 'eval' */
     bool isForEval() { return isCachedEval() || isActiveEval(); }
 
+    /*
+     * Return whether this script is a top-level script.
+     *
+     * If we evaluate some code which contains a syntax error, then we might
+     * produce a JSScript which has no associated bytecode. Testing with
+     * |code()| filters out this kind of scripts.
+     *
+     * If this script has a function associated to it, then it is not the
+     * top-level of a file.
+     */
+    bool isTopLevel() { return code() && !functionNonDelazifying(); }
+
     /* Ensure the script has a TypeScript. */
     inline bool ensureHasTypes(JSContext* cx);
 
     inline js::TypeScript* types();
 
     void maybeSweepTypes(js::AutoClearTypeInferenceStateOnOOM* oom);
 
     inline js::GlobalObject& global() const;
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -283,16 +283,17 @@ UNIFIED_SOURCES += [
     'proxy/ScriptedDirectProxyHandler.cpp',
     'proxy/ScriptedIndirectProxyHandler.cpp',
     'proxy/SecurityWrapper.cpp',
     'proxy/Wrapper.cpp',
     'vm/ArgumentsObject.cpp',
     'vm/ArrayBufferObject.cpp',
     'vm/CallNonGenericMethod.cpp',
     'vm/CharacterEncoding.cpp',
+    'vm/CodeCoverage.cpp',
     'vm/Compression.cpp',
     'vm/DateTime.cpp',
     'vm/Debugger.cpp',
     'vm/DebuggerMemory.cpp',
     'vm/ErrorObject.cpp',
     'vm/ForOfIterator.cpp',
     'vm/GeneratorObject.cpp',
     'vm/GlobalObject.cpp',
new file mode 100644
--- /dev/null
+++ b/js/src/vm/CodeCoverage.cpp
@@ -0,0 +1,365 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "vm/CodeCoverage.h"
+
+#include "mozilla/IntegerPrintfMacros.h"
+
+#include "jscompartment.h"
+#include "jsopcode.h"
+#include "jsscript.h"
+
+#include "vm/Runtime.h"
+
+// This file contains a few functions which are used to produce files understood
+// by lcov tools. A detailed description of the format is available in the man
+// page for "geninfo" [1].  To make it short, the following paraphrases what is
+// commented in the man page by using curly braces prefixed by for-each to
+// express repeated patterns.
+//
+//   TN:<compartment name>
+//   for-each <source file> {
+//     SN:<filename>
+//     for-each <script> {
+//       FN:<line>,<name>
+//     }
+//     for-each <script> {
+//       FNDA:<hits>,<name>
+//     }
+//     FNF:<number of scripts>
+//     FNH:<sum of scripts hits>
+//     for-each <script> {
+//       for-each <branch> {
+//         BRDA:<line>,<block id>,<target id>,<taken>
+//       }
+//     }
+//     BRF:<number of branches>
+//     BRH:<sum of branches hits>
+//     for-each <script> {
+//       for-each <line> {
+//         DA:<line>,<hits>
+//       }
+//     }
+//     LF:<number of lines>
+//     LH:<sum of lines hits>
+//   }
+//
+// [1] http://ltp.sourceforge.net/coverage/lcov/geninfo.1.php
+//
+namespace js {
+namespace coverage {
+
+LCovSource::LCovSource(LifoAlloc* alloc)
+  : outSF_(alloc),
+    outFN_(alloc),
+    outFNDA_(alloc),
+    numFunctionsFound_(0),
+    numFunctionsHit_(0),
+    outBRDA_(alloc),
+    numBranchesFound_(0),
+    numBranchesHit_(0),
+    outDA_(alloc),
+    numLinesInstrumented_(0),
+    numLinesHit_(0)
+{
+}
+
+void
+LCovSource::exportInto(GenericPrinter& out) const
+{
+    outSF_.exportInto(out);
+
+    outFN_.exportInto(out);
+    outFNDA_.exportInto(out);
+    out.printf("FNF:%d\n", numFunctionsFound_);
+    out.printf("FNH:%d\n", numFunctionsHit_);
+
+    outBRDA_.exportInto(out);
+    out.printf("BRF:%d\n", numBranchesFound_);
+    out.printf("BRH:%d\n", numBranchesHit_);
+
+    outDA_.exportInto(out);
+    out.printf("LF:%d\n", numLinesInstrumented_);
+    out.printf("LH:%d\n", numLinesHit_);
+
+    out.put("end_of_record\n");
+}
+
+bool
+LCovSource::writeTopLevelScript(JSScript* script)
+{
+    MOZ_ASSERT(script->isTopLevel());
+
+    if (!writeSourceFilename(outSF_, script))
+        return false;
+
+    Vector<JSScript*, 8, SystemAllocPolicy> queue;
+    if (!queue.append(script))
+        return false;
+
+    do {
+        script = queue.popCopy();
+
+        // Save the lcov output of the current script.
+        if (!writeScript(script))
+            return false;
+
+        // Iterate from the last to the first object in order to have
+        // the functions them visited in the opposite order when popping
+        // elements from the stack of remaining scripts, such that the
+        // functions are listed with increasing line numbers.
+        if (!script->hasObjects())
+            continue;
+        size_t idx = script->objects()->length;
+        while (idx--) {
+            JSObject* obj = script->getObject(idx);
+
+            // Only continue on JSFunction objects.
+            if (!obj->is<JSFunction>())
+                continue;
+            JSFunction& fun = obj->as<JSFunction>();
+
+            // Let's skip asm.js for now.
+            if (!fun.isInterpreted())
+                continue;
+            MOZ_ASSERT(!fun.isInterpretedLazy());
+
+            // Queue the script in the list of script associated to the
+            // current source.
+            if (!queue.append(fun.nonLazyScript()))
+                return false;
+        }
+    } while (!queue.empty());
+
+    return !(outFN_.hadOutOfMemory() ||
+             outFNDA_.hadOutOfMemory() ||
+             outBRDA_.hadOutOfMemory() ||
+             outDA_.hadOutOfMemory());
+}
+
+bool
+LCovSource::writeSourceFilename(LSprinter& out, JSScript* script)
+{
+    out.printf("SF:%s\n", script->filename());
+    return !out.hadOutOfMemory();
+}
+
+bool
+LCovSource::writeScriptName(LSprinter& out, JSScript* script)
+{
+    JSFunction* fun = script->functionNonDelazifying();
+    if (fun && fun->displayAtom())
+        return EscapedStringPrinter(out, fun->displayAtom(), 0);
+    out.printf("top-level");
+    return true;
+}
+
+bool
+LCovSource::writeScript(JSScript* script)
+{
+    numFunctionsFound_++;
+    outFN_.printf("FN:%d,", script->lineno());
+    if (!writeScriptName(outFN_, script))
+        return false;
+    outFN_.put("\n", 1);
+
+    uint64_t hits = 0;
+    ScriptCounts* sc = nullptr;
+    if (script->hasScriptCounts()) {
+        sc = &script->getScriptCounts();
+        numFunctionsHit_++;
+        const PCCounts* counts = sc->maybeGetPCCounts(script->pcToOffset(script->main()));
+        outFNDA_.printf("FNDA:%" PRIu64 ",", counts->numExec());
+        if (!writeScriptName(outFNDA_, script))
+            return false;
+        outFNDA_.put("\n", 1);
+
+        // Set the hit count of the pre-main code to 1, if the function ever got
+        // visited.
+        hits = 1;
+    }
+
+    jsbytecode* snpc = script->code();
+    jssrcnote* sn = script->notes();
+    if (!SN_IS_TERMINATOR(sn))
+        snpc += SN_DELTA(sn);
+
+    size_t lineno = script->lineno();
+    jsbytecode* end = script->codeEnd();
+    size_t blockId = 0;
+    for (jsbytecode* pc = script->code(); pc != end; pc = GetNextPc(pc)) {
+        JSOp op = JSOp(*pc);
+        bool jump = IsJumpOpcode(op);
+        bool fallsthrough = BytecodeFallsThrough(op);
+
+        // If the current script & pc has a hit-count report, then update the
+        // current number of hits.
+        if (sc) {
+            const PCCounts* counts = sc->maybeGetPCCounts(script->pcToOffset(pc));
+            if (counts)
+                hits = counts->numExec();
+        }
+
+        // If we have additional source notes, walk all the source notes of the
+        // current pc.
+        if (snpc <= pc) {
+            size_t oldLine = lineno;
+            while (!SN_IS_TERMINATOR(sn) && snpc <= pc) {
+                SrcNoteType type = (SrcNoteType) SN_TYPE(sn);
+                if (type == SRC_SETLINE)
+                    lineno = size_t(GetSrcNoteOffset(sn, 0));
+                else if (type == SRC_NEWLINE)
+                    lineno++;
+
+                sn = SN_NEXT(sn);
+                snpc += SN_DELTA(sn);
+            }
+
+            if (oldLine != lineno && fallsthrough) {
+                outDA_.printf("DA:%d,%" PRIu64 "\n", lineno, hits);
+
+                // Count the number of lines instrumented & hit.
+                numLinesInstrumented_++;
+                if (hits)
+                    numLinesHit_++;
+            }
+        }
+
+        // If the current instruction has thrown, then decrement the hit counts
+        // with the number of throws.
+        if (sc) {
+            const PCCounts* counts = sc->maybeGetThrowCounts(script->pcToOffset(pc));
+            if (counts)
+                hits -= counts->numExec();
+        }
+
+        // If the current pc corresponds to a conditional jump instruction, then reports
+        // branch hits.
+        if (jump && fallsthrough) {
+            jsbytecode* target = pc + GET_JUMP_OFFSET(pc);
+            jsbytecode* fallthroughTarget = GetNextPc(pc);
+            uint64_t fallthroughHits = 0;
+            if (sc) {
+                const PCCounts* counts = sc->maybeGetPCCounts(script->pcToOffset(fallthroughTarget));
+                if (counts)
+                    fallthroughHits = counts->numExec();
+            }
+
+            size_t targetId = script->pcToOffset(target);
+            uint64_t taken = hits - fallthroughHits;
+            outBRDA_.printf("BRDA:%d,%d,%d,", lineno, blockId, targetId);
+            if (hits)
+                outBRDA_.printf("%d\n", taken);
+            else
+                outBRDA_.put("-\n", 2);
+
+            // Count the number of branches, and the number of branches hit.
+            numBranchesFound_++;
+            if (hits)
+                numBranchesHit_++;
+
+            // Update the blockId when there is a discontinuity.
+            blockId = script->pcToOffset(fallthroughTarget);
+        }
+    }
+
+    return true;
+}
+
+LCovCompartment::LCovCompartment()
+  : alloc_(4096),
+    outTN_(&alloc_),
+    sources_(nullptr)
+{
+    MOZ_ASSERT(alloc_.isEmpty());
+}
+
+void
+LCovCompartment::collectCodeCoverageInfo(JSCompartment* comp, JSScript* topLevel)
+{
+    // Skip any operation if we already some out-of memory issues.
+    if (outTN_.hadOutOfMemory())
+        return;
+
+    // On the first call, write the compartment name, and allocate a LCovSource
+    // vector in the LifoAlloc.
+    if (!sources_) {
+        if (!writeCompartmentName(comp))
+            return;
+
+        LCovSourceVector* raw = alloc_.pod_malloc<LCovSourceVector>();
+        if (!raw) {
+            outTN_.reportOutOfMemory();
+            return;
+        }
+
+        sources_ = new(raw) LCovSourceVector(alloc_);
+    }
+
+    // Allocate a new LCovSource for the current top-level.
+    if (!sources_->append(Move(LCovSource(&alloc_)))) {
+        outTN_.reportOutOfMemory();
+        return;
+    }
+
+    // Write code coverage data into the allocated LCovSource.
+    if (!sources_->back().writeTopLevelScript(topLevel)) {
+        outTN_.reportOutOfMemory();
+        return;
+    }
+}
+
+void
+LCovCompartment::exportInto(GenericPrinter& out) const
+{
+    if (!sources_ || outTN_.hadOutOfMemory())
+        return;
+
+    outTN_.exportInto(out);
+    for (const LCovSource& sc : *sources_)
+        sc.exportInto(out);
+}
+
+bool
+LCovCompartment::writeCompartmentName(JSCompartment* comp)
+{
+    JSRuntime* rt = comp->runtimeFromMainThread();
+
+    // lcov trace files are starting with an optional test case name, that we
+    // recycle to be a compartment name.
+    //
+    // Note: The test case name has some constraint in terms of valid character,
+    // thus we escape invalid chracters with a "_" symbol in front of its
+    // hexadecimal code.
+    outTN_.put("TN:");
+    if (rt->compartmentNameCallback) {
+        char name[1024];
+        {
+            // Hazard analysis cannot tell that the callback does not GC.
+            JS::AutoSuppressGCAnalysis nogc;
+            (*rt->compartmentNameCallback)(rt, comp, name, sizeof(name));
+        }
+        for (char *s = name; s < name + sizeof(name) && *s; s++) {
+            if (('a' <= *s && *s <= 'z') ||
+                ('A' <= *s && *s <= 'Z') ||
+                ('0' <= *s && *s <= '9'))
+            {
+                outTN_.put(s, 1);
+                continue;
+            }
+            outTN_.printf("_%p", (void*) size_t(*s));
+        }
+        outTN_.put("\n", 1);
+    } else {
+        outTN_.printf("Compartment_%p%p\n", (void*) size_t('_'), comp);
+    }
+
+    return !outTN_.hadOutOfMemory();
+}
+
+
+} // namespace coverage
+} // namespace js
new file mode 100644
--- /dev/null
+++ b/js/src/vm/CodeCoverage.h
@@ -0,0 +1,108 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef vm_CodeCoverage_h
+#define vm_CodeCoverage_h
+
+#include "mozilla/Vector.h"
+
+#include "ds/LifoAlloc.h"
+
+#include "vm/Printer.h"
+
+struct JSCompartment;
+class JSScript;
+
+namespace js {
+namespace coverage {
+
+class LCovCompartment;
+
+class LCovSource
+{
+  public:
+    explicit LCovSource(LifoAlloc* alloc);
+
+    // Visit all JSScript in pre-order, and collect the lcov output based on
+    // the ScriptCounts counters.
+    //
+    // In case of the where this function is called during the finalization,
+    // this assumes that all of the children scripts are still alive, and
+    // not finalized yet.
+    bool writeTopLevelScript(JSScript* script);
+
+    // Write the Lcov output in a buffer, such as the one associated with
+    // the runtime code coverage trace file.
+    void exportInto(GenericPrinter& out) const;
+
+  private:
+    // Write the script name in out.
+    bool writeSourceFilename(LSprinter& out, JSScript* script);
+
+    // Write the script name in out.
+    bool writeScriptName(LSprinter& out, JSScript* script);
+
+    // Iterate over the bytecode and collect the lcov output based on the
+    // ScriptCounts counters.
+    bool writeScript(JSScript* script);
+
+  private:
+    // LifoAlloc string which hold the filename of the source.
+    LSprinter outSF_;
+
+    // LifoAlloc strings which hold the filename of each function as
+    // well as the number of hits for each function.
+    LSprinter outFN_;
+    LSprinter outFNDA_;
+    size_t numFunctionsFound_;
+    size_t numFunctionsHit_;
+
+    // LifoAlloc string which hold branches statistics.
+    LSprinter outBRDA_;
+    size_t numBranchesFound_;
+    size_t numBranchesHit_;
+
+    // LifoAlloc string which hold lines statistics.
+    LSprinter outDA_;
+    size_t numLinesInstrumented_;
+    size_t numLinesHit_;
+};
+
+class LCovCompartment
+{
+  public:
+    LCovCompartment();
+
+    // Collect code coverage information
+    void collectCodeCoverageInfo(JSCompartment* comp, JSScript* topLevel);
+
+    // Write the Lcov output in a buffer, such as the one associated with
+    // the runtime code coverage trace file.
+    void exportInto(GenericPrinter& out) const;
+
+  private:
+    // Write the script name in out.
+    bool writeCompartmentName(JSCompartment* comp);
+
+  private:
+    typedef Vector<LCovSource, 16, LifoAllocPolicy<Fallible>> LCovSourceVector;
+
+    // LifoAlloc backend for all temporary allocations needed to stash the
+    // strings to be written in the file.
+    LifoAlloc alloc_;
+
+    // LifoAlloc string which hold the name of the compartment.
+    LSprinter outTN_;
+
+    // Vector of all sources which are used in this compartment.
+    LCovSourceVector* sources_;
+};
+
+} // namespace coverage
+} // namespace js
+
+#endif // vm_Printer_h
+