Bug 1910194 - wasm: Add testing functions and improve speculative inlining test. r=jseward

Adds testing function for getting the best tier of a function.
Adds prefs for configuring our inlining heuristics.
Improves test of speculative inlining.

Differential Revision: https://phabricator.services.mozilla.com/D217881
This commit is contained in:
Ryan Hunt 2024-08-11 15:20:11 +00:00
parent a9f56370e0
commit 5bde5d48e3
6 changed files with 130 additions and 21 deletions

View File

@ -2051,6 +2051,38 @@ static bool WasmDisassemble(JSContext* cx, unsigned argc, Value* vp) {
return false;
}
static bool WasmFunctionTier(JSContext* cx, unsigned argc, Value* vp) {
if (!wasm::HasSupport(cx)) {
JS_ReportErrorASCII(cx, "wasm support unavailable");
return false;
}
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().set(UndefinedValue());
if (!args.get(0).isObject()) {
JS_ReportErrorASCII(cx, "argument is not an object");
return false;
}
RootedFunction func(cx, args[0].toObject().maybeUnwrapIf<JSFunction>());
if (func && wasm::IsWasmExportedFunction(func)) {
uint32_t funcIndex = wasm::ExportedFunctionToFuncIndex(func);
wasm::Instance& instance = wasm::ExportedFunctionToInstance(func);
wasm::Tier tier = instance.code().funcTier(funcIndex);
RootedString tierString(cx, JS_NewStringCopyZ(cx, wasm::ToString(tier)));
if (!tierString) {
ReportOutOfMemory(cx);
return false;
}
args.rval().set(StringValue(tierString));
return true;
}
JS_ReportErrorASCII(cx, "argument is not an exported wasm function");
return false;
}
static bool ToIonDumpContents(JSContext* cx, HandleValue value,
wasm::IonDumpContents* contents) {
RootedString option(cx, JS::ToString(cx, value));
@ -10057,6 +10089,10 @@ JS_FOR_WASM_FEATURES(WASM_FEATURE)
" ImportJitExit - wasm-to-jitted-JS stubs\n"
" all - all kinds, including obscure ones\n"),
JS_FN_HELP("wasmFunctionTier", WasmFunctionTier, 1, 0,
"wasmFunctionTier(wasmFunc)\n",
" Returns the best compiled tier for a function. Either 'baseline' or 'optimized'."),
JS_FN_HELP("wasmHasTier2CompilationCompleted", WasmHasTier2CompilationCompleted, 1, 0,
"wasmHasTier2CompilationCompleted(module)",
" Returns a boolean indicating whether a given module has finished compiled code for tier2. \n"

View File

@ -1,19 +1,66 @@
// |jit-test| skip-if: !wasmGcEnabled(); test-also=-P wasm_experimental_compile_pipeline;
// |jit-test| skip-if: !wasmGcEnabled() || !wasmExperimentalCompilePipelineEnabled(); test-also=-P wasm_experimental_compile_pipeline;
// Basic test that call_ref profiling information works. Will be expanded in
// a future commit.
let {test} = wasmEvalText(`
(module
(type $refType (func (result i32)))
(func $ref (export "ref") (result i32)
i32.const 1
)
(func (export "test") (result i32)
ref.func $ref
call_ref $refType
)
)`).exports;
const tierUpThreshold = 1;
let {importFunc} = wasmEvalText(`
(module (func (export "importFunc") (result i32) i32.const 2))
`).exports;
let testFuncs = [
[importFunc, 2],
["trueFunc", 1],
["falseFunc", 0],
["trapFunc", WebAssembly.RuntimeError],
[null, WebAssembly.RuntimeError],
];
function invokeTestWith(exports, exportThing, expected) {
let targetFunc;
if (exportThing instanceof WebAssembly.Function) {
targetFunc = exportThing;
} else if (exportThing === null) {
targetFunc = null;
} else {
targetFunc = exports[exportThing];
}
for (let i = 0; i < 10; i++) {
assertEq(test(), 1);
if (expected === WebAssembly.RuntimeError) {
assertErrorMessage(() => exports.test(targetFunc), WebAssembly.RuntimeError, /./);
} else {
assertEq(exports.test(targetFunc), expected);
}
}
for ([funcToInline, funcToInlineExpected] of testFuncs) {
let exports = wasmEvalText(`
(module
(type $booleanFunc (func (result i32)))
(func $importFunc (import "" "importFunc") (result i32))
(func $trueFunc (export "trueFunc") (result i32)
i32.const 1
)
(func $falseFunc (export "falseFunc") (result i32)
i32.const 0
)
(func $trapFunc (export "trapFunc") (result i32)
unreachable
)
(func (export "test") (param (ref null $booleanFunc)) (result i32)
local.get 0
call_ref $booleanFunc
)
)`, {"": {importFunc}}).exports;
let test = exports["test"];
// Force a tier-up of the function, calling the same function every time
assertEq(wasmFunctionTier(test), "baseline");
for (let i = 0; i <= tierUpThreshold; i++) {
invokeTestWith(exports, funcToInline, funcToInlineExpected);
}
assertEq(wasmFunctionTier(test), "optimized");
// Now that we've inlined something, try calling it with every test function
// and double check we get the expected behavior
for ([testFunc, testFuncExpected] of testFuncs) {
invokeTestWith(exports, testFunc, testFuncExpected);
}
}

View File

@ -1128,6 +1128,9 @@ class Code : public ShareableBase<Code> {
bool funcHasTier(uint32_t funcIndex, Tier tier) const {
return funcCodeBlock(funcIndex).tier() == tier;
}
Tier funcTier(uint32_t funcIndex) const {
return funcCodeBlock(funcIndex).tier();
}
const LinkData* codeBlockLinkData(const CodeBlock& block) const;
void clearLinkData() const;
@ -1179,6 +1182,8 @@ class Code : public ShareableBase<Code> {
}
return block->lookupUnwindInfo(pc);
}
// Search through this code to find which tier a code range is from. Returns
// false if this code range was not found.
bool lookupFunctionTier(const CodeRange* codeRange, Tier* tier) const;
// To save memory, profilingLabels_ are generated lazily when profiling mode

View File

@ -46,6 +46,17 @@ enum class Tier {
Serialized = Optimized
};
static constexpr const char* ToString(Tier tier) {
switch (tier) {
case wasm::Tier::Baseline:
return "baseline";
case wasm::Tier::Optimized:
return "optimized";
default:
return "unknown";
}
}
// Iterator over tiers present in a tiered data structure.
class Tiers {

View File

@ -2309,9 +2309,11 @@ bool Instance::init(JSContext* cx, const JSObjectVector& funcImports,
// Initialize the hotness counters, if relevant
if (code().mode() == CompileMode::LazyTiering) {
// Set every function so that the first time it executes, it will trip the
// hotness counter to negative and force a tier-up.
for (uint32_t funcIndex = codeMeta().numFuncImports;
funcIndex < codeMeta().numFuncs(); funcIndex++) {
funcDefInstanceData(funcIndex)->hotnessCounter = 1;
funcDefInstanceData(funcIndex)->hotnessCounter = 0;
}
}
@ -2700,12 +2702,14 @@ void Instance::resetHotnessCounter(uint32_t funcIndex) {
}
void Instance::submitCallRefHints(uint32_t funcIndex) {
CallRefMetricsRange funcRange = codeMeta().getFuncDefCallRefs(funcIndex);
for (uint32_t callRefIndex = funcRange.begin;
callRefIndex < funcRange.begin + funcRange.length; callRefIndex++) {
uint32_t callCountThreshold =
JS::Prefs::wasm_experimental_inline_call_ref_threshold();
CallRefMetricsRange range = codeMeta().getFuncDefCallRefs(funcIndex);
for (uint32_t callRefIndex = range.begin;
callRefIndex < range.begin + range.length; callRefIndex++) {
CallRefMetrics& metrics = callRefMetrics_[callRefIndex];
if (metrics.state == CallRefMetrics::State::Monomorphic &&
metrics.callCount >= 1) {
metrics.callCount >= callCountThreshold) {
uint32_t funcIndex =
wasm::ExportedFunctionToFuncIndex(metrics.monomorphicTarget);
codeMeta().setCallRefHint(callRefIndex,

View File

@ -8270,6 +8270,12 @@
mirror: always
set_spidermonkey_pref: always
- name: javascript.options.wasm_experimental_inline_call_ref_threshold
type: uint32_t
value: 1
mirror: always
set_spidermonkey_pref: always
# Support for pretenuring allocations based on their allocation site.
- name: javascript.options.site_based_pretenuring
type: bool