| /* -*- 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; |
| |
| using mozilla::UniquePtr; |
| |
| // 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 JSClass pm_class = { |
| "PerfMeasurement", JSCLASS_HAS_PRIVATE, |
| nullptr, nullptr, nullptr, nullptr, |
| nullptr, nullptr, nullptr, pm_finalize |
| }; |
| |
| // 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()) { |
| UniquePtr<char[], JS::FreePolicy> bytes = |
| DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, value, nullptr); |
| if (!bytes) |
| return nullptr; |
| JS_ReportErrorNumber(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_ReportErrorNumber(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(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 |