mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-17 20:34:34 +00:00
Bug 1896505 - Add initial reporting machinery for subclassing. r=jandem
This patch will report the Type II Array subclassing. A previous version of this patch had CacheIR support for this; however this has proved to have less performance impact and more complexity challenges than forseen. As a result, this support has been removed until proven necessary. Differential Revision: https://phabricator.services.mozilla.com/D210535
This commit is contained in:
parent
062031b254
commit
9f2510928b
@ -70,6 +70,7 @@ custom onunderflow sets an element onunderflow event listener
|
||||
custom JS_asmjs uses asm.js
|
||||
custom JS_wasm uses WebAssembly
|
||||
custom JS_wasm_legacy_exceptions uses WebAssembly legacy exception-handling
|
||||
custom JS_subclassing_array_type_2 Array is Type II subclassed
|
||||
|
||||
// Console API
|
||||
method console.assert
|
||||
|
@ -107,8 +107,8 @@ use.counter:
|
||||
send_in_pings:
|
||||
- use-counters
|
||||
|
||||
# Total of 2309 use counter metrics (excludes denominators).
|
||||
# Total of 354 'page' use counters.
|
||||
# Total of 2311 use counter metrics (excludes denominators).
|
||||
# Total of 355 'page' use counters.
|
||||
use.counter.page:
|
||||
svgsvgelement_getelementbyid:
|
||||
type: counter
|
||||
@ -535,6 +535,23 @@ use.counter.page:
|
||||
send_in_pings:
|
||||
- use-counters
|
||||
|
||||
js_subclassing_array_type_2:
|
||||
type: counter
|
||||
description: >
|
||||
Whether a page Array is Type II subclassed.
|
||||
Compare against `use.counter.top_level_content_documents_destroyed`
|
||||
to calculate the rate.
|
||||
bugs:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1852098
|
||||
data_reviews:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1852098
|
||||
notification_emails:
|
||||
- dom-core@mozilla.com
|
||||
- emilio@mozilla.com
|
||||
expires: never
|
||||
send_in_pings:
|
||||
- use-counters
|
||||
|
||||
console_assert:
|
||||
type: counter
|
||||
description: >
|
||||
@ -6128,7 +6145,7 @@ use.counter.page:
|
||||
send_in_pings:
|
||||
- use-counters
|
||||
|
||||
# Total of 354 'document' use counters.
|
||||
# Total of 355 'document' use counters.
|
||||
use.counter.doc:
|
||||
svgsvgelement_getelementbyid:
|
||||
type: counter
|
||||
@ -6555,6 +6572,23 @@ use.counter.doc:
|
||||
send_in_pings:
|
||||
- use-counters
|
||||
|
||||
js_subclassing_array_type_2:
|
||||
type: counter
|
||||
description: >
|
||||
Whether a document Array is Type II subclassed.
|
||||
Compare against `use.counter.content_documents_destroyed`
|
||||
to calculate the rate.
|
||||
bugs:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1852098
|
||||
data_reviews:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1852098
|
||||
notification_emails:
|
||||
- dom-core@mozilla.com
|
||||
- emilio@mozilla.com
|
||||
expires: never
|
||||
send_in_pings:
|
||||
- use-counters
|
||||
|
||||
console_assert:
|
||||
type: counter
|
||||
description: >
|
||||
@ -39378,3 +39412,4 @@ use.counter.css.doc:
|
||||
expires: never
|
||||
send_in_pings:
|
||||
- use-counters
|
||||
|
||||
|
@ -84,10 +84,11 @@ using JSAccumulateTelemetryDataCallback = void (*)(JSMetric, uint32_t);
|
||||
extern JS_PUBLIC_API void JS_SetAccumulateTelemetryCallback(
|
||||
JSContext* cx, JSAccumulateTelemetryDataCallback callback);
|
||||
|
||||
#define FOR_EACH_JS_USE_COUNTER(_) \
|
||||
_(ASMJS, AsmJS) \
|
||||
_(WASM, Wasm) \
|
||||
_(WASM_LEGACY_EXCEPTIONS, WasmLegacyExceptions)
|
||||
#define FOR_EACH_JS_USE_COUNTER(_) \
|
||||
_(ASMJS, AsmJS) \
|
||||
_(WASM, Wasm) \
|
||||
_(WASM_LEGACY_EXCEPTIONS, WasmLegacyExceptions) \
|
||||
_(SUBCLASSING_ARRAY_TYPE_II, SubclassingArrayTypeII)
|
||||
|
||||
/*
|
||||
* Use counter names passed to the accumulate use counter callback.
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include "jsnum.h"
|
||||
#include "jstypes.h"
|
||||
|
||||
#include "builtin/SelfHostingDefines.h"
|
||||
#include "ds/Sort.h"
|
||||
#include "jit/InlinableNatives.h"
|
||||
#include "jit/TrampolineNatives.h"
|
||||
@ -958,7 +959,7 @@ static SharedShape* AddLengthProperty(JSContext* cx,
|
||||
map, mapLength, objectFlags);
|
||||
}
|
||||
|
||||
static bool IsArrayConstructor(const JSObject* obj) {
|
||||
bool js::IsArrayConstructor(const JSObject* obj) {
|
||||
// Note: this also returns true for cross-realm Array constructors in the
|
||||
// same compartment.
|
||||
return IsNativeFunction(obj, ArrayConstructor);
|
||||
@ -4249,6 +4250,11 @@ static bool array_of(JSContext* cx, unsigned argc, Value* vp) {
|
||||
return ArrayFromCallArgs(cx, args);
|
||||
}
|
||||
|
||||
if (!ReportUsageCounter(cx, nullptr, SUBCLASSING_ARRAY,
|
||||
SUBCLASSING_TYPE_II)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 4.
|
||||
RootedObject obj(cx);
|
||||
{
|
||||
|
@ -257,6 +257,8 @@ class MOZ_NON_TEMPORARY_CLASS ArraySpeciesLookup final {
|
||||
}
|
||||
};
|
||||
|
||||
bool IsArrayConstructor(const JSObject* obj);
|
||||
|
||||
} /* namespace js */
|
||||
|
||||
#endif /* builtin_Array_h */
|
||||
|
@ -681,7 +681,8 @@ function ArrayFromAsync(asyncItems, mapfn = undefined, thisArg = undefined) {
|
||||
// Step 3.e.i. Let A be ? Construct(C).
|
||||
// Step 3.f. Else,
|
||||
// Step 3.f.i. Let A be ! ArrayCreate(0).
|
||||
var A = IsConstructor(C) ? constructContentFunction(C, C) : [];
|
||||
var A = IsConstructor(C) ?
|
||||
(ReportUsageCounter(C, SUBCLASS_ARRAY_TYPE_II), constructContentFunction(C, C)) : [];
|
||||
|
||||
|
||||
// Step 3.j.i. Let k be 0.
|
||||
@ -750,7 +751,7 @@ function ArrayFromAsync(asyncItems, mapfn = undefined, thisArg = undefined) {
|
||||
// Step 3.k.iv.1. Let A be ? Construct(C, « 𝔽(len) »).
|
||||
// Step 3.k.v. Else,
|
||||
// Step 3.k.v.1. Let A be ? ArrayCreate(len).
|
||||
var A = IsConstructor(C) ? constructContentFunction(C, C, len) : std_Array(len);
|
||||
var A = IsConstructor(C) ? (ReportUsageCounter(C, SUBCLASS_ARRAY_TYPE_II), constructContentFunction(C, C, len)) : std_Array(len);
|
||||
|
||||
// Step 3.k.vi. Let k be 0.
|
||||
var k = 0;
|
||||
@ -814,7 +815,7 @@ function ArrayFrom(items, mapfn = undefined, thisArg = undefined) {
|
||||
}
|
||||
|
||||
// Steps 5.a-b.
|
||||
var A = IsConstructor(C) ? constructContentFunction(C, C) : [];
|
||||
var A = IsConstructor(C) ? (ReportUsageCounter(C, SUBCLASS_ARRAY_TYPE_II), constructContentFunction(C, C)) : [];
|
||||
|
||||
// Step 5.d.
|
||||
var k = 0;
|
||||
@ -857,7 +858,7 @@ function ArrayFrom(items, mapfn = undefined, thisArg = undefined) {
|
||||
|
||||
// Steps 12-14.
|
||||
var A = IsConstructor(C)
|
||||
? constructContentFunction(C, C, len)
|
||||
? (ReportUsageCounter(C, SUBCLASS_ARRAY_TYPE_II), constructContentFunction(C, C, len))
|
||||
: std_Array(len);
|
||||
|
||||
// Steps 15-16.
|
||||
@ -921,7 +922,7 @@ function ArrayToLocaleString(locales, options) {
|
||||
if (IsNullOrUndefined(firstElement)) {
|
||||
R = "";
|
||||
} else {
|
||||
#if JS_HAS_INTL_API
|
||||
#if JS_HAS_INTL_API
|
||||
R = ToString(
|
||||
callContentFunction(
|
||||
firstElement.toLocaleString,
|
||||
@ -930,11 +931,11 @@ function ArrayToLocaleString(locales, options) {
|
||||
options
|
||||
)
|
||||
);
|
||||
#else
|
||||
#else
|
||||
R = ToString(
|
||||
callContentFunction(firstElement.toLocaleString, firstElement)
|
||||
);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
// Step 3 (reordered).
|
||||
@ -949,7 +950,7 @@ function ArrayToLocaleString(locales, options) {
|
||||
// Steps 9.a, 9.c-e.
|
||||
R += separator;
|
||||
if (!IsNullOrUndefined(nextElement)) {
|
||||
#if JS_HAS_INTL_API
|
||||
#if JS_HAS_INTL_API
|
||||
R += ToString(
|
||||
callContentFunction(
|
||||
nextElement.toLocaleString,
|
||||
@ -958,11 +959,11 @@ function ArrayToLocaleString(locales, options) {
|
||||
options
|
||||
)
|
||||
);
|
||||
#else
|
||||
#else
|
||||
R += ToString(
|
||||
callContentFunction(nextElement.toLocaleString, nextElement)
|
||||
);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -128,4 +128,18 @@
|
||||
|
||||
#define ASYNC_ITERATOR_HELPER_GENERATOR_SLOT 0
|
||||
|
||||
// Support for usage counters around subclassing:
|
||||
#define SUBCLASSING_ARRAY 1
|
||||
#define SUBCLASSING_LAST_BUILTIN 2
|
||||
|
||||
#define SUBCLASSING_TYPE_II 2
|
||||
#define SUBCLASSING_TYPE_III 3
|
||||
#define SUBCLASSING_TYPE_IV 4
|
||||
|
||||
#define SUBCLASSING_TYPE_MASK 0xf
|
||||
|
||||
#define SUBCLASSING_BUILTIN_SHIFT 16
|
||||
#define SUBCLASS_ARRAY_TYPE_II \
|
||||
((SUBCLASSING_ARRAY << SUBCLASSING_BUILTIN_SHIFT) | SUBCLASSING_TYPE_II)
|
||||
|
||||
#endif
|
||||
|
57
js/src/jit-test/tests/use-counters/array-counters.js
Normal file
57
js/src/jit-test/tests/use-counters/array-counters.js
Normal file
@ -0,0 +1,57 @@
|
||||
function test_function_for_use_counter_integration(fn, counter, expected_growth = true) {
|
||||
let before = getUseCounterResults();
|
||||
assertEq(counter in before, true);
|
||||
|
||||
fn();
|
||||
|
||||
let after = getUseCounterResults();
|
||||
if (expected_growth) {
|
||||
console.log("Yes Increase: Before ", before[counter], " After", after[counter]);
|
||||
assertEq(after[counter] > before[counter], true);
|
||||
} else {
|
||||
console.log("No Increase: Before ", before[counter], " After", after[counter]);
|
||||
assertEq(after[counter] == before[counter], true);
|
||||
}
|
||||
}
|
||||
|
||||
class MyArray extends Array { }
|
||||
|
||||
function array_from() {
|
||||
let r = Array.from([1, 2, 3]);
|
||||
assertEq(r instanceof Array, true);
|
||||
}
|
||||
|
||||
function array_from_subclassing_type_ii() {
|
||||
assertEq(MyArray.from([1, 2, 3]) instanceof MyArray, true);
|
||||
}
|
||||
|
||||
test_function_for_use_counter_integration(array_from, "SubclassingArrayTypeII", /* expected_growth = */ false);
|
||||
test_function_for_use_counter_integration(array_from_subclassing_type_ii, "SubclassingArrayTypeII", /* expected_growth = */ true);
|
||||
|
||||
function array_of() {
|
||||
let r = Array.of([1, 2, 3]);
|
||||
assertEq(r instanceof Array, true);
|
||||
}
|
||||
|
||||
function array_of_subclassing_type_ii() {
|
||||
assertEq(MyArray.of([1, 2, 3]) instanceof MyArray, true);
|
||||
}
|
||||
|
||||
test_function_for_use_counter_integration(array_of, "SubclassingArrayTypeII", /* expected_growth = */ false);
|
||||
test_function_for_use_counter_integration(array_of_subclassing_type_ii, "SubclassingArrayTypeII", /* expected_growth = */ true);
|
||||
|
||||
|
||||
// Array.fromAsync
|
||||
function array_fromAsync() {
|
||||
let r = Array.fromAsync([1, 2, 3]);
|
||||
r.then((x) => assertEq(x instanceof Array, true));
|
||||
}
|
||||
|
||||
function array_fromAsync_subclassing_type_ii() {
|
||||
MyArray.fromAsync([1, 2, 3]).then((x) => assertEq(x instanceof MyArray, true));
|
||||
}
|
||||
|
||||
test_function_for_use_counter_integration(array_fromAsync, "SubclassingArrayTypeII", /* expected_growth = */ false);
|
||||
test_function_for_use_counter_integration(array_fromAsync_subclassing_type_ii, "SubclassingArrayTypeII", /* expected_growth = */ true);
|
||||
|
||||
// Array.of
|
20
js/src/jit-test/tests/use-counters/basic-use-counter.js
Normal file
20
js/src/jit-test/tests/use-counters/basic-use-counter.js
Normal file
@ -0,0 +1,20 @@
|
||||
let countersBefore = getUseCounterResults();
|
||||
|
||||
class MyArray extends Array { }
|
||||
|
||||
function f() { return MyArray.from([1, 2, 3]) };
|
||||
assertEq(f() instanceof MyArray, true);
|
||||
for (var i = 0; i < 100; i++) { f(); }
|
||||
|
||||
let countersAfter = getUseCounterResults();
|
||||
|
||||
// The above code should have tripped the subclassing detection.
|
||||
assertEq(countersAfter.SubclassingArrayTypeII > countersBefore.SubclassingArrayTypeII, true);
|
||||
|
||||
function f2() {
|
||||
return Array.from([1, 2, 3]);
|
||||
}
|
||||
f2();
|
||||
|
||||
let countersAfterNoChange = getUseCounterResults();
|
||||
assertEq(countersAfter.SubclassingArrayTypeII, countersAfterNoChange.SubclassingArrayTypeII);
|
@ -2161,6 +2161,7 @@ static const JSFunctionSpec intrinsic_functions[] = {
|
||||
JS_INLINABLE_FN("RegExpSearcher", RegExpSearcher, 3, 0, RegExpSearcher),
|
||||
JS_INLINABLE_FN("RegExpSearcherLastLimit", RegExpSearcherLastLimit, 0, 0,
|
||||
RegExpSearcherLastLimit),
|
||||
JS_FN("ReportUsageCounter", intrinsic_ReportUsageCounter, 2, 0),
|
||||
JS_INLINABLE_FN("SameValue", js::obj_is, 2, 0, ObjectIs),
|
||||
JS_FN("SetCopy", SetObject::copy, 1, 0),
|
||||
JS_FN("SharedArrayBufferByteLength",
|
||||
@ -2960,6 +2961,82 @@ bool js::IsSelfHostedFunctionWithName(const Value& v, JSAtom* name) {
|
||||
return IsSelfHostedFunctionWithName(fun, name);
|
||||
}
|
||||
|
||||
bool js::ReportUsageCounter(JSContext* cx, HandleObject constructor,
|
||||
int32_t builtin, int32_t type) {
|
||||
switch (builtin) {
|
||||
case SUBCLASSING_ARRAY: {
|
||||
// Check if the provided function is actually the array constructor
|
||||
// anyhow; We're interested in if the object is in the current realm; CCW
|
||||
// realm is OK because even if this is a CCW it'll fail the
|
||||
// IsArrayConstructor check.
|
||||
//
|
||||
// Constructor may be nullptr if check has already been done.
|
||||
if (constructor && IsArrayConstructor(constructor) &&
|
||||
constructor->maybeCCWRealm() == cx->realm()) {
|
||||
return true;
|
||||
}
|
||||
switch (type) {
|
||||
case SUBCLASSING_TYPE_II:
|
||||
cx->runtime()->setUseCounter(cx->global(),
|
||||
JSUseCounter::SUBCLASSING_ARRAY_TYPE_II);
|
||||
return true;
|
||||
default:
|
||||
MOZ_CRASH("Unexpected Subclassing Type");
|
||||
}
|
||||
}
|
||||
default:
|
||||
MOZ_CRASH("Unexpected builtin");
|
||||
};
|
||||
}
|
||||
|
||||
// In the interests of efficiency and simplicity, we would like this function
|
||||
// to have to do as little as possible, and take as few parameters as possible.
|
||||
//
|
||||
// Nevethreless, we also wish to be able to report correctly for interesting
|
||||
// cases like Array.from.call(Map, ...) -- essentially, catching the case where
|
||||
// someone using a subclassing-elgible class as the 'this' value, thereby
|
||||
// executing a sub classing.
|
||||
//
|
||||
// To handle this with just two parameters, we treat our integer parameter as a
|
||||
// packed integer; This introduces some magic, but allows us to communicate all
|
||||
// call-site constant data in a single int32.
|
||||
//
|
||||
// The packing is as follows:
|
||||
//
|
||||
// SubclassingElgibleBuiltins << 16 | SubclassingType
|
||||
//
|
||||
// This produces the following magic constant values:
|
||||
//
|
||||
// Array Subclassing Type II: (1 << 16) | 2 == 0x010002
|
||||
// Array Subclassing Type III: (1 << 16) | 3 == 0x010003
|
||||
// Array Subclassing Type IV: (1 << 16) | 4 == 0x010004
|
||||
//
|
||||
// Subclassing is reported iff the constructor provided doesn't match
|
||||
// the existing prototype.
|
||||
//
|
||||
bool js::intrinsic_ReportUsageCounter(JSContext* cx, unsigned int argc,
|
||||
JS::Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
// Currently usage counter is only used for reporting subclassing
|
||||
// as a result we hyper-specialize the following
|
||||
MOZ_ASSERT(args.length() == 2);
|
||||
MOZ_ASSERT(args.get(0).isObject());
|
||||
MOZ_ASSERT(args.get(1).isInt32());
|
||||
|
||||
HandleValue arg0 = args.get(0);
|
||||
RootedObject constructor(cx, &arg0.toObject());
|
||||
|
||||
int32_t packedTypeAndBuiltin = args.get(1).toInt32();
|
||||
|
||||
int32_t type = packedTypeAndBuiltin & SUBCLASSING_TYPE_MASK;
|
||||
MOZ_ASSERT(type >= SUBCLASSING_TYPE_II && type <= SUBCLASSING_TYPE_IV);
|
||||
|
||||
int32_t builtin = packedTypeAndBuiltin >> SUBCLASSING_BUILTIN_SHIFT;
|
||||
MOZ_ASSERT(builtin > 0 && builtin < SUBCLASSING_LAST_BUILTIN);
|
||||
|
||||
return ReportUsageCounter(cx, constructor, builtin, type);
|
||||
}
|
||||
|
||||
static_assert(
|
||||
JSString::MAX_LENGTH <= INT32_MAX,
|
||||
"StringIteratorNext in builtin/String.js assumes the stored index "
|
||||
|
@ -292,6 +292,12 @@ bool IsTupleUnchecked(JSContext* cx, const CallArgs& args);
|
||||
bool intrinsic_IsTuple(JSContext* cx, unsigned argc, JS::Value* vp);
|
||||
#endif
|
||||
|
||||
bool intrinsic_ReportUsageCounter(JSContext* cx, unsigned argc, JS::Value* vp);
|
||||
|
||||
// The arguments to this are defined in SelfHostingDefines.h
|
||||
bool ReportUsageCounter(JSContext* cx, HandleObject constructor,
|
||||
int32_t builtin, int32_t type);
|
||||
|
||||
} /* namespace js */
|
||||
|
||||
#endif /* vm_SelfHosting_h_ */
|
||||
|
@ -2628,16 +2628,21 @@ static void SetUseCounterCallback(JSObject* obj, JSUseCounter counter) {
|
||||
switch (counter) {
|
||||
case JSUseCounter::ASMJS:
|
||||
SetUseCounter(obj, eUseCounter_custom_JS_asmjs);
|
||||
break;
|
||||
return;
|
||||
case JSUseCounter::WASM:
|
||||
SetUseCounter(obj, eUseCounter_custom_JS_wasm);
|
||||
break;
|
||||
return;
|
||||
case JSUseCounter::WASM_LEGACY_EXCEPTIONS:
|
||||
SetUseCounter(obj, eUseCounter_custom_JS_wasm_legacy_exceptions);
|
||||
return;
|
||||
case JSUseCounter::SUBCLASSING_ARRAY_TYPE_II:
|
||||
SetUseCounter(obj,
|
||||
mozilla::eUseCounter_custom_JS_subclassing_array_type_2);
|
||||
return;
|
||||
case JSUseCounter::COUNT:
|
||||
break;
|
||||
default:
|
||||
MOZ_ASSERT_UNREACHABLE("Unexpected JSUseCounter id");
|
||||
}
|
||||
MOZ_ASSERT_UNREACHABLE("Unexpected JSUseCounter id");
|
||||
}
|
||||
|
||||
static void GetRealmNameCallback(JSContext* cx, Realm* realm, char* buf,
|
||||
|
Loading…
Reference in New Issue
Block a user