diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp index dafc06d15a14..e330b2c22913 100644 --- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -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()); + 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" diff --git a/js/src/jit-test/tests/wasm/gc/speculative-inlining.js b/js/src/jit-test/tests/wasm/gc/speculative-inlining.js index 2c350e200600..71ea3c9d45b5 100644 --- a/js/src/jit-test/tests/wasm/gc/speculative-inlining.js +++ b/js/src/jit-test/tests/wasm/gc/speculative-inlining.js @@ -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); + } } diff --git a/js/src/wasm/WasmCode.h b/js/src/wasm/WasmCode.h index 0873523c907a..917e6183af96 100644 --- a/js/src/wasm/WasmCode.h +++ b/js/src/wasm/WasmCode.h @@ -1128,6 +1128,9 @@ class Code : public ShareableBase { 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 { } 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 diff --git a/js/src/wasm/WasmCompileArgs.h b/js/src/wasm/WasmCompileArgs.h index f5b439a5716a..54db1cddb914 100644 --- a/js/src/wasm/WasmCompileArgs.h +++ b/js/src/wasm/WasmCompileArgs.h @@ -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 { diff --git a/js/src/wasm/WasmInstance.cpp b/js/src/wasm/WasmInstance.cpp index c5f4f5dc4471..3e42a3c221d7 100644 --- a/js/src/wasm/WasmInstance.cpp +++ b/js/src/wasm/WasmInstance.cpp @@ -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, diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml index c2d4929af472..a6649a245cb4 100644 --- a/modules/libpref/init/StaticPrefList.yaml +++ b/modules/libpref/init/StaticPrefList.yaml @@ -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