/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* 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 "perf/jsperf.h"

#include "jscntxt.h" /* for error messages */
#include "jsobj.h" /* for unwrapping without a context */

using namespace js;
using JS::PerfMeasurement;

// You cannot forward-declare a static object in C++, so instead
// we have to forward-declare the helper function that refers to it.
static PerfMeasurement* GetPM(JSContext* cx, JS::HandleValue value, const char* fname);

// Property access

#define GETTER(name)                                                    \
    static bool                                                         \
    pm_get_##name(JSContext* cx, unsigned argc, Value* vp)              \
    {                                                                   \
        CallArgs args = CallArgsFromVp(argc, vp);                       \
        PerfMeasurement* p = GetPM(cx, args.thisv(), #name);            \
        if (!p)                                                         \
            return false;                                               \
        args.rval().setNumber(double(p->name));                         \
        return true;                                                    \
    }

GETTER(cpu_cycles)
GETTER(instructions)
GETTER(cache_references)
GETTER(cache_misses)
GETTER(branch_instructions)
GETTER(branch_misses)
GETTER(bus_cycles)
GETTER(page_faults)
GETTER(major_page_faults)
GETTER(context_switches)
GETTER(cpu_migrations)
GETTER(eventsMeasured)

#undef GETTER

// Calls

static bool
pm_start(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);
    PerfMeasurement* p = GetPM(cx, args.thisv(), "start");
    if (!p)
        return false;

    p->start();
    args.rval().setUndefined();
    return true;
}

static bool
pm_stop(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);
    PerfMeasurement* p = GetPM(cx, args.thisv(), "stop");
    if (!p)
        return false;

    p->stop();
    args.rval().setUndefined();
    return true;
}

static bool
pm_reset(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);
    PerfMeasurement* p = GetPM(cx, args.thisv(), "reset");
    if (!p)
        return false;

    p->reset();
    args.rval().setUndefined();
    return true;
}

static bool
pm_canMeasureSomething(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);
    PerfMeasurement* p = GetPM(cx, args.thisv(), "canMeasureSomething");
    if (!p)
        return false;

    args.rval().setBoolean(p->canMeasureSomething());
    return true;
}

static const uint8_t PM_FATTRS = JSPROP_READONLY | JSPROP_PERMANENT;
static const JSFunctionSpec pm_fns[] = {
    JS_FN("start",               pm_start,               0, PM_FATTRS),
    JS_FN("stop",                pm_stop,                0, PM_FATTRS),
    JS_FN("reset",               pm_reset,               0, PM_FATTRS),
    JS_FN("canMeasureSomething", pm_canMeasureSomething, 0, PM_FATTRS),
    JS_FS_END
};

static const uint8_t PM_PATTRS =
    JSPROP_ENUMERATE | JSPROP_PERMANENT;

#define GETTER(name)                            \
    JS_PSG(#name, pm_get_##name, PM_PATTRS)

static const JSPropertySpec pm_props[] = {
    GETTER(cpu_cycles),
    GETTER(instructions),
    GETTER(cache_references),
    GETTER(cache_misses),
    GETTER(branch_instructions),
    GETTER(branch_misses),
    GETTER(bus_cycles),
    GETTER(page_faults),
    GETTER(major_page_faults),
    GETTER(context_switches),
    GETTER(cpu_migrations),
    GETTER(eventsMeasured),
    JS_PS_END
};

#undef GETTER

// If this were C++ these would be "static const" members.

#define CONSTANT(name) { #name, PerfMeasurement::name }

static const struct pm_const {
    const char* name;
    PerfMeasurement::EventMask value;
} pm_consts[] = {
    CONSTANT(CPU_CYCLES),
    CONSTANT(INSTRUCTIONS),
    CONSTANT(CACHE_REFERENCES),
    CONSTANT(CACHE_MISSES),
    CONSTANT(BRANCH_INSTRUCTIONS),
    CONSTANT(BRANCH_MISSES),
    CONSTANT(BUS_CYCLES),
    CONSTANT(PAGE_FAULTS),
    CONSTANT(MAJOR_PAGE_FAULTS),
    CONSTANT(CONTEXT_SWITCHES),
    CONSTANT(CPU_MIGRATIONS),
    CONSTANT(ALL),
    CONSTANT(NUM_MEASURABLE_EVENTS),
    { 0, PerfMeasurement::EventMask(0) }
};

#undef CONSTANT

static bool pm_construct(JSContext* cx, unsigned argc, Value* vp);
static void pm_finalize(JSFreeOp* fop, JSObject* obj);

static const JSClassOps pm_classOps = {
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    pm_finalize
};

static const JSClass pm_class = {
    "PerfMeasurement",
    JSCLASS_HAS_PRIVATE |
    JSCLASS_FOREGROUND_FINALIZE,
    &pm_classOps
};

// Constructor and destructor

static bool
pm_construct(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);

    uint32_t mask;
    if (!args.hasDefined(0)) {
        ReportMissingArg(cx, args.calleev(), 0);
        return false;
    }
    if (!JS::ToUint32(cx, args[0], &mask))
        return false;

    JS::RootedObject obj(cx, JS_NewObjectForConstructor(cx, &pm_class, args));
    if (!obj)
        return false;

    if (!JS_FreezeObject(cx, obj))
        return false;

    PerfMeasurement* p = cx->new_<PerfMeasurement>(PerfMeasurement::EventMask(mask));
    if (!p) {
        JS_ReportOutOfMemory(cx);
        return false;
    }

    JS_SetPrivate(obj, p);
    args.rval().setObject(*obj);
    return true;
}

static void
pm_finalize(JSFreeOp* fop, JSObject* obj)
{
    js::FreeOp::get(fop)->delete_(static_cast<PerfMeasurement*>(JS_GetPrivate(obj)));
}

// Helpers (declared above)

static PerfMeasurement*
GetPM(JSContext* cx, JS::HandleValue value, const char* fname)
{
    if (!value.isObject()) {
        UniqueChars bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, value, nullptr);
        if (!bytes)
            return nullptr;
        JS_ReportErrorNumberLatin1(cx, GetErrorMessage, 0, JSMSG_NOT_NONNULL_OBJECT, bytes.get());
        return nullptr;
    }
    RootedObject obj(cx, &value.toObject());
    PerfMeasurement* p = (PerfMeasurement*)
        JS_GetInstancePrivate(cx, obj, &pm_class, nullptr);
    if (p)
        return p;

    // JS_GetInstancePrivate only sets an exception if its last argument
    // is nonzero, so we have to do it by hand.
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, 0, JSMSG_INCOMPATIBLE_PROTO,
                              pm_class.name, fname, JS_GetClass(obj)->name);
    return nullptr;
}

namespace JS {

JSObject*
RegisterPerfMeasurement(JSContext* cx, HandleObject globalArg)
{
    static const uint8_t PM_CATTRS = JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT;

    RootedObject global(cx, globalArg);
    RootedObject prototype(cx);
    prototype = JS_InitClass(cx, global, nullptr /* parent */,
                             &pm_class, pm_construct, 1,
                             pm_props, pm_fns, 0, 0);
    if (!prototype)
        return 0;

    RootedObject ctor(cx);
    ctor = JS_GetConstructor(cx, prototype);
    if (!ctor)
        return 0;

    for (const pm_const* c = pm_consts; c->name; c++) {
        if (!JS_DefineProperty(cx, ctor, c->name, c->value, PM_CATTRS,
                               JS_STUBGETTER, JS_STUBSETTER))
            return 0;
    }

    if (!JS_FreezeObject(cx, prototype) ||
        !JS_FreezeObject(cx, ctor)) {
        return 0;
    }

    return prototype;
}

PerfMeasurement*
ExtractPerfMeasurement(const Value& wrapper)
{
    if (wrapper.isPrimitive())
        return 0;

    // This is what JS_GetInstancePrivate does internally.  We can't
    // call JS_anything from here, because we don't have a JSContext.
    JSObject* obj = wrapper.toObjectOrNull();
    if (obj->getClass() != js::Valueify(&pm_class))
        return 0;

    return (PerfMeasurement*) obj->as<js::NativeObject>().getPrivate();
}

} // namespace JS
