js/src/vm/SPSProfiler.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/js/src/vm/SPSProfiler.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,362 @@
     1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
     1.5 + * vim: set ts=8 sts=4 et sw=4 tw=99:
     1.6 + * This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +#include "vm/SPSProfiler.h"
    1.11 +
    1.12 +#include "mozilla/DebugOnly.h"
    1.13 +
    1.14 +#include "jsnum.h"
    1.15 +#include "jsprf.h"
    1.16 +#include "jsscript.h"
    1.17 +
    1.18 +#include "jit/BaselineJIT.h"
    1.19 +#include "vm/StringBuffer.h"
    1.20 +
    1.21 +using namespace js;
    1.22 +
    1.23 +using mozilla::DebugOnly;
    1.24 +
    1.25 +SPSProfiler::SPSProfiler(JSRuntime *rt)
    1.26 +  : rt(rt),
    1.27 +    stack_(nullptr),
    1.28 +    size_(nullptr),
    1.29 +    max_(0),
    1.30 +    slowAssertions(false),
    1.31 +    enabled_(false),
    1.32 +    lock_(nullptr),
    1.33 +    eventMarker_(nullptr)
    1.34 +{
    1.35 +    JS_ASSERT(rt != nullptr);
    1.36 +}
    1.37 +
    1.38 +bool
    1.39 +SPSProfiler::init()
    1.40 +{
    1.41 +#ifdef JS_THREADSAFE
    1.42 +    lock_ = PR_NewLock();
    1.43 +    if (lock_ == nullptr)
    1.44 +        return false;
    1.45 +#endif
    1.46 +    return true;
    1.47 +}
    1.48 +
    1.49 +SPSProfiler::~SPSProfiler()
    1.50 +{
    1.51 +    if (strings.initialized()) {
    1.52 +        for (ProfileStringMap::Enum e(strings); !e.empty(); e.popFront())
    1.53 +            js_free(const_cast<char *>(e.front().value()));
    1.54 +    }
    1.55 +#ifdef JS_THREADSAFE
    1.56 +    if (lock_)
    1.57 +        PR_DestroyLock(lock_);
    1.58 +#endif
    1.59 +}
    1.60 +
    1.61 +void
    1.62 +SPSProfiler::setProfilingStack(ProfileEntry *stack, uint32_t *size, uint32_t max)
    1.63 +{
    1.64 +    AutoSPSLock lock(lock_);
    1.65 +    JS_ASSERT_IF(size_ && *size_ != 0, !enabled());
    1.66 +    if (!strings.initialized())
    1.67 +        strings.init();
    1.68 +    stack_ = stack;
    1.69 +    size_  = size;
    1.70 +    max_   = max;
    1.71 +}
    1.72 +
    1.73 +void
    1.74 +SPSProfiler::setEventMarker(void (*fn)(const char *))
    1.75 +{
    1.76 +    eventMarker_ = fn;
    1.77 +}
    1.78 +
    1.79 +void
    1.80 +SPSProfiler::enable(bool enabled)
    1.81 +{
    1.82 +    JS_ASSERT(installed());
    1.83 +
    1.84 +    if (enabled_ == enabled)
    1.85 +        return;
    1.86 +
    1.87 +    /*
    1.88 +     * Ensure all future generated code will be instrumented, or that all
    1.89 +     * currently instrumented code is discarded
    1.90 +     */
    1.91 +    ReleaseAllJITCode(rt->defaultFreeOp());
    1.92 +
    1.93 +    enabled_ = enabled;
    1.94 +
    1.95 +#ifdef JS_ION
    1.96 +    /* Toggle SPS-related jumps on baseline jitcode.
    1.97 +     * The call to |ReleaseAllJITCode| above will release most baseline jitcode, but not
    1.98 +     * jitcode for scripts with active frames on the stack.  These scripts need to have
    1.99 +     * their profiler state toggled so they behave properly.
   1.100 +     */
   1.101 +    jit::ToggleBaselineSPS(rt, enabled);
   1.102 +#endif
   1.103 +}
   1.104 +
   1.105 +/* Lookup the string for the function/script, creating one if necessary */
   1.106 +const char*
   1.107 +SPSProfiler::profileString(JSScript *script, JSFunction *maybeFun)
   1.108 +{
   1.109 +    AutoSPSLock lock(lock_);
   1.110 +    JS_ASSERT(strings.initialized());
   1.111 +    ProfileStringMap::AddPtr s = strings.lookupForAdd(script);
   1.112 +    if (s)
   1.113 +        return s->value();
   1.114 +    const char *str = allocProfileString(script, maybeFun);
   1.115 +    if (str == nullptr)
   1.116 +        return nullptr;
   1.117 +    if (!strings.add(s, script, str)) {
   1.118 +        js_free(const_cast<char *>(str));
   1.119 +        return nullptr;
   1.120 +    }
   1.121 +    return str;
   1.122 +}
   1.123 +
   1.124 +void
   1.125 +SPSProfiler::onScriptFinalized(JSScript *script)
   1.126 +{
   1.127 +    /*
   1.128 +     * This function is called whenever a script is destroyed, regardless of
   1.129 +     * whether profiling has been turned on, so don't invoke a function on an
   1.130 +     * invalid hash set. Also, even if profiling was enabled but then turned
   1.131 +     * off, we still want to remove the string, so no check of enabled() is
   1.132 +     * done.
   1.133 +     */
   1.134 +    AutoSPSLock lock(lock_);
   1.135 +    if (!strings.initialized())
   1.136 +        return;
   1.137 +    if (ProfileStringMap::Ptr entry = strings.lookup(script)) {
   1.138 +        const char *tofree = entry->value();
   1.139 +        strings.remove(entry);
   1.140 +        js_free(const_cast<char *>(tofree));
   1.141 +    }
   1.142 +}
   1.143 +
   1.144 +void
   1.145 +SPSProfiler::markEvent(const char *event)
   1.146 +{
   1.147 +    JS_ASSERT(enabled());
   1.148 +    if (eventMarker_) {
   1.149 +        JS::AutoAssertNoGC nogc;
   1.150 +        eventMarker_(event);
   1.151 +    }
   1.152 +}
   1.153 +
   1.154 +bool
   1.155 +SPSProfiler::enter(JSScript *script, JSFunction *maybeFun)
   1.156 +{
   1.157 +    const char *str = profileString(script, maybeFun);
   1.158 +    if (str == nullptr)
   1.159 +        return false;
   1.160 +
   1.161 +#ifdef DEBUG
   1.162 +    // In debug builds, assert the JS pseudo frames already on the stack
   1.163 +    // have a non-null pc. Only look at the top frames to avoid quadratic
   1.164 +    // behavior.
   1.165 +    if (*size_ > 0 && *size_ - 1 < max_) {
   1.166 +        size_t start = (*size_ > 4) ? *size_ - 4 : 0;
   1.167 +        for (size_t i = start; i < *size_ - 1; i++)
   1.168 +            MOZ_ASSERT_IF(stack_[i].js(), stack_[i].pc() != nullptr);
   1.169 +    }
   1.170 +#endif
   1.171 +
   1.172 +    push(str, nullptr, script, script->code());
   1.173 +    return true;
   1.174 +}
   1.175 +
   1.176 +void
   1.177 +SPSProfiler::exit(JSScript *script, JSFunction *maybeFun)
   1.178 +{
   1.179 +    pop();
   1.180 +
   1.181 +#ifdef DEBUG
   1.182 +    /* Sanity check to make sure push/pop balanced */
   1.183 +    if (*size_ < max_) {
   1.184 +        const char *str = profileString(script, maybeFun);
   1.185 +        /* Can't fail lookup because we should already be in the set */
   1.186 +        JS_ASSERT(str != nullptr);
   1.187 +
   1.188 +        // Bug 822041
   1.189 +        if (!stack_[*size_].js()) {
   1.190 +            fprintf(stderr, "--- ABOUT TO FAIL ASSERTION ---\n");
   1.191 +            fprintf(stderr, " stack=%p size=%d/%d\n", (void*) stack_, *size_, max_);
   1.192 +            for (int32_t i = *size_; i >= 0; i--) {
   1.193 +                if (stack_[i].js())
   1.194 +                    fprintf(stderr, "  [%d] JS %s\n", i, stack_[i].label());
   1.195 +                else
   1.196 +                    fprintf(stderr, "  [%d] C line %d %s\n", i, stack_[i].line(), stack_[i].label());
   1.197 +            }
   1.198 +        }
   1.199 +
   1.200 +        JS_ASSERT(stack_[*size_].js());
   1.201 +        JS_ASSERT(stack_[*size_].script() == script);
   1.202 +        JS_ASSERT(strcmp((const char*) stack_[*size_].label(), str) == 0);
   1.203 +        stack_[*size_].setLabel(nullptr);
   1.204 +        stack_[*size_].setPC(nullptr);
   1.205 +    }
   1.206 +#endif
   1.207 +}
   1.208 +
   1.209 +void
   1.210 +SPSProfiler::enterNative(const char *string, void *sp)
   1.211 +{
   1.212 +    /* these operations cannot be re-ordered, so volatile-ize operations */
   1.213 +    volatile ProfileEntry *stack = stack_;
   1.214 +    volatile uint32_t *size = size_;
   1.215 +    uint32_t current = *size;
   1.216 +
   1.217 +    JS_ASSERT(enabled());
   1.218 +    if (current < max_) {
   1.219 +        stack[current].setLabel(string);
   1.220 +        stack[current].setStackAddress(sp);
   1.221 +        stack[current].setScript(nullptr);
   1.222 +        stack[current].setLine(0);
   1.223 +    }
   1.224 +    *size = current + 1;
   1.225 +}
   1.226 +
   1.227 +void
   1.228 +SPSProfiler::push(const char *string, void *sp, JSScript *script, jsbytecode *pc)
   1.229 +{
   1.230 +    /* these operations cannot be re-ordered, so volatile-ize operations */
   1.231 +    volatile ProfileEntry *stack = stack_;
   1.232 +    volatile uint32_t *size = size_;
   1.233 +    uint32_t current = *size;
   1.234 +
   1.235 +    JS_ASSERT(installed());
   1.236 +    if (current < max_) {
   1.237 +        stack[current].setLabel(string);
   1.238 +        stack[current].setStackAddress(sp);
   1.239 +        stack[current].setScript(script);
   1.240 +        stack[current].setPC(pc);
   1.241 +    }
   1.242 +    *size = current + 1;
   1.243 +}
   1.244 +
   1.245 +void
   1.246 +SPSProfiler::pop()
   1.247 +{
   1.248 +    JS_ASSERT(installed());
   1.249 +    (*size_)--;
   1.250 +    JS_ASSERT(*(int*)size_ >= 0);
   1.251 +}
   1.252 +
   1.253 +/*
   1.254 + * Serializes the script/function pair into a "descriptive string" which is
   1.255 + * allowed to fail. This function cannot trigger a GC because it could finalize
   1.256 + * some scripts, resize the hash table of profile strings, and invalidate the
   1.257 + * AddPtr held while invoking allocProfileString.
   1.258 + */
   1.259 +const char *
   1.260 +SPSProfiler::allocProfileString(JSScript *script, JSFunction *maybeFun)
   1.261 +{
   1.262 +    // Note: this profiler string is regexp-matched by
   1.263 +    // browser/devtools/profiler/cleopatra/js/parserWorker.js.
   1.264 +
   1.265 +    // Determine if the function (if any) has an explicit or guessed name.
   1.266 +    bool hasAtom = maybeFun && maybeFun->displayAtom();
   1.267 +
   1.268 +    // Get the function name, if any, and its length.
   1.269 +    const jschar *atom = nullptr;
   1.270 +    size_t lenAtom = 0;
   1.271 +    if (hasAtom) {
   1.272 +        atom = maybeFun->displayAtom()->charsZ();
   1.273 +        lenAtom = maybeFun->displayAtom()->length();
   1.274 +    }
   1.275 +
   1.276 +    // Get the script filename, if any, and its length.
   1.277 +    const char *filename = script->filename();
   1.278 +    if (filename == nullptr)
   1.279 +        filename = "<unknown>";
   1.280 +    size_t lenFilename = strlen(filename);
   1.281 +
   1.282 +    // Get the line number and its length as a string.
   1.283 +    uint64_t lineno = script->lineno();
   1.284 +    size_t lenLineno = 1;
   1.285 +    for (uint64_t i = lineno; i /= 10; lenLineno++);
   1.286 +
   1.287 +    // Determine the required buffer size.
   1.288 +    size_t len = lenFilename + lenLineno + 1; // +1 for the ":" separating them.
   1.289 +    if (hasAtom)
   1.290 +        len += lenAtom + 3; // +3 for the " (" and ")" it adds.
   1.291 +
   1.292 +    // Allocate the buffer.
   1.293 +    char *cstr = js_pod_malloc<char>(len + 1);
   1.294 +    if (cstr == nullptr)
   1.295 +        return nullptr;
   1.296 +
   1.297 +    // Construct the descriptive string.
   1.298 +    DebugOnly<size_t> ret;
   1.299 +    if (hasAtom)
   1.300 +        ret = JS_snprintf(cstr, len + 1, "%hs (%s:%llu)", atom, filename, lineno);
   1.301 +    else
   1.302 +        ret = JS_snprintf(cstr, len + 1, "%s:%llu", filename, lineno);
   1.303 +
   1.304 +    MOZ_ASSERT(ret == len, "Computed length should match actual length!");
   1.305 +
   1.306 +    return cstr;
   1.307 +}
   1.308 +
   1.309 +SPSEntryMarker::SPSEntryMarker(JSRuntime *rt
   1.310 +                               MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
   1.311 +    : profiler(&rt->spsProfiler)
   1.312 +{
   1.313 +    MOZ_GUARD_OBJECT_NOTIFIER_INIT;
   1.314 +    if (!profiler->installed()) {
   1.315 +        profiler = nullptr;
   1.316 +        return;
   1.317 +    }
   1.318 +    size_before = *profiler->size_;
   1.319 +    profiler->pushNoCopy("js::RunScript", this, nullptr, nullptr);
   1.320 +}
   1.321 +
   1.322 +SPSEntryMarker::~SPSEntryMarker()
   1.323 +{
   1.324 +    if (profiler != nullptr) {
   1.325 +        profiler->pop();
   1.326 +        JS_ASSERT(size_before == *profiler->size_);
   1.327 +    }
   1.328 +}
   1.329 +
   1.330 +JS_FRIEND_API(jsbytecode*)
   1.331 +ProfileEntry::pc() const volatile
   1.332 +{
   1.333 +    return idx == NullPCIndex ? nullptr : script()->offsetToPC(idx);
   1.334 +}
   1.335 +
   1.336 +JS_FRIEND_API(void)
   1.337 +ProfileEntry::setPC(jsbytecode *pc) volatile
   1.338 +{
   1.339 +    idx = pc == nullptr ? NullPCIndex : script()->pcToOffset(pc);
   1.340 +}
   1.341 +
   1.342 +JS_FRIEND_API(void)
   1.343 +js::SetRuntimeProfilingStack(JSRuntime *rt, ProfileEntry *stack, uint32_t *size, uint32_t max)
   1.344 +{
   1.345 +    rt->spsProfiler.setProfilingStack(stack, size, max);
   1.346 +}
   1.347 +
   1.348 +JS_FRIEND_API(void)
   1.349 +js::EnableRuntimeProfilingStack(JSRuntime *rt, bool enabled)
   1.350 +{
   1.351 +    rt->spsProfiler.enable(enabled);
   1.352 +}
   1.353 +
   1.354 +JS_FRIEND_API(void)
   1.355 +js::RegisterRuntimeProfilingEventMarker(JSRuntime *rt, void (*fn)(const char *))
   1.356 +{
   1.357 +    JS_ASSERT(rt->spsProfiler.enabled());
   1.358 +    rt->spsProfiler.setEventMarker(fn);
   1.359 +}
   1.360 +
   1.361 +JS_FRIEND_API(jsbytecode*)
   1.362 +js::ProfilingGetPC(JSRuntime *rt, JSScript *script, void *ip)
   1.363 +{
   1.364 +    return rt->spsProfiler.ipToPC(script, size_t(ip));
   1.365 +}

mercurial