[wasm] Reverse count logic for the tiering of js-to-wasm wrappers
This CL reverses the count logic for the tiering strategy of the js-to-wasm wrappers. The initial approach was that calls to each function were counted up, until a threshold was reached and the function would tier up. With this CL, each function is assigned a budget of calls that can be handled through the generic wrapper. Calls are counted down until the budget is exhausted, which will trigger the tier-up for the function. This approach comes with two advantages. Firstly, determining whether a function's budget is exhausted is as simple as checking the flags set from the decrement of the budget. Secondly, the code generated by the generic wrapper does not depend on the specific value of the initial budget. Bug: v8:10982 Change-Id: I5e186c6cf836a9c197b41d0f7ad075b07c87a4da Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2532300 Reviewed-by: Jakob Kummerow <jkummerow@chromium.org> Reviewed-by: Andreas Haas <ahaas@chromium.org> Commit-Queue: Vicky Kontoura <vkont@google.com> Cr-Commit-Position: refs/heads/master@{#71153}
This commit is contained in:
parent
83a2f390f1
commit
eb0ef4d7c8
@ -3032,22 +3032,20 @@ void Builtins::Generate_GenericJSToWasmWrapper(MacroAssembler* masm) {
|
||||
WasmExportedFunctionData::kInstanceOffset - kHeapObjectTag));
|
||||
|
||||
// -------------------------------------------
|
||||
// Increment the call count in function data.
|
||||
// Decrement the budget of the generic wrapper in function data.
|
||||
// -------------------------------------------
|
||||
__ SmiAddConstant(
|
||||
MemOperand(function_data,
|
||||
WasmExportedFunctionData::kCallCountOffset - kHeapObjectTag),
|
||||
Smi::FromInt(1));
|
||||
MemOperand(function_data, WasmExportedFunctionData::kWrapperBudgetOffset -
|
||||
kHeapObjectTag),
|
||||
Smi::FromInt(-1));
|
||||
|
||||
// -------------------------------------------
|
||||
// Check if the call count reached the threshold.
|
||||
// Check if the budget of the generic wrapper reached 0 (zero).
|
||||
// -------------------------------------------
|
||||
// Instead of a specific comparison, we can directly use the flags set
|
||||
// from the previous addition.
|
||||
Label compile_wrapper, compile_wrapper_done;
|
||||
__ SmiCompare(
|
||||
MemOperand(function_data,
|
||||
WasmExportedFunctionData::kCallCountOffset - kHeapObjectTag),
|
||||
Smi::FromInt(wasm::kGenericWrapperThreshold));
|
||||
__ j(greater_equal, &compile_wrapper);
|
||||
__ j(less_equal, &compile_wrapper);
|
||||
__ bind(&compile_wrapper_done);
|
||||
|
||||
// -------------------------------------------
|
||||
|
@ -126,7 +126,7 @@ constexpr int kAnonymousFuncIndex = -1;
|
||||
// The number of calls to an exported wasm function that will be handled
|
||||
// by the generic wrapper. Once this threshold is reached, a specific wrapper
|
||||
// is to be compiled for the function's signature.
|
||||
constexpr uint32_t kGenericWrapperThreshold = 6;
|
||||
constexpr uint32_t kGenericWrapperBudget = 6;
|
||||
|
||||
} // namespace wasm
|
||||
} // namespace internal
|
||||
|
@ -333,7 +333,7 @@ SMI_ACCESSORS(WasmExportedFunctionData, jump_table_offset,
|
||||
kJumpTableOffsetOffset)
|
||||
SMI_ACCESSORS(WasmExportedFunctionData, function_index, kFunctionIndexOffset)
|
||||
ACCESSORS(WasmExportedFunctionData, signature, Foreign, kSignatureOffset)
|
||||
SMI_ACCESSORS(WasmExportedFunctionData, call_count, kCallCountOffset)
|
||||
SMI_ACCESSORS(WasmExportedFunctionData, wrapper_budget, kWrapperBudgetOffset)
|
||||
ACCESSORS(WasmExportedFunctionData, c_wrapper_code, Object, kCWrapperCodeOffset)
|
||||
ACCESSORS(WasmExportedFunctionData, wasm_call_target, Object,
|
||||
kWasmCallTargetOffset)
|
||||
|
@ -1891,7 +1891,7 @@ Handle<WasmExportedFunction> WasmExportedFunction::New(
|
||||
function_data->set_jump_table_offset(jump_table_offset);
|
||||
function_data->set_function_index(func_index);
|
||||
function_data->set_signature(*sig_foreign);
|
||||
function_data->set_call_count(0);
|
||||
function_data->set_wrapper_budget(wasm::kGenericWrapperBudget);
|
||||
function_data->set_c_wrapper_code(Smi::zero(), SKIP_WRITE_BARRIER);
|
||||
function_data->set_wasm_call_target(Smi::zero(), SKIP_WRITE_BARRIER);
|
||||
function_data->set_packed_args_size(0);
|
||||
|
@ -763,7 +763,7 @@ class WasmExportedFunctionData : public Struct {
|
||||
DECL_INT_ACCESSORS(jump_table_offset)
|
||||
DECL_INT_ACCESSORS(function_index)
|
||||
DECL_ACCESSORS(signature, Foreign)
|
||||
DECL_INT_ACCESSORS(call_count)
|
||||
DECL_INT_ACCESSORS(wrapper_budget)
|
||||
DECL_ACCESSORS(c_wrapper_code, Object)
|
||||
DECL_ACCESSORS(wasm_call_target, Object)
|
||||
DECL_INT_ACCESSORS(packed_args_size)
|
||||
|
@ -18,7 +18,7 @@ extern class WasmExportedFunctionData extends Struct {
|
||||
jump_table_offset: Smi;
|
||||
function_index: Smi;
|
||||
signature: Foreign;
|
||||
call_count: Smi;
|
||||
wrapper_budget: Smi;
|
||||
// The remaining fields are for fast calling from C++. The contract is
|
||||
// that they are lazily populated, and either all will be present or none.
|
||||
c_wrapper_code: Object;
|
||||
|
@ -29,7 +29,7 @@ void Cleanup() {
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(CallCounter) {
|
||||
TEST(WrapperBudget) {
|
||||
{
|
||||
// This test assumes use of the generic wrapper.
|
||||
FlagScope<bool> use_wasm_generic_wrapper(&FLAG_wasm_generic_wrapper, true);
|
||||
@ -61,9 +61,12 @@ TEST(CallCounter) {
|
||||
"main");
|
||||
Handle<WasmExportedFunction> main_export = maybe_export.ToHandleChecked();
|
||||
|
||||
// Check that the counter has initially a value of 0.
|
||||
CHECK_EQ(main_export->shared().wasm_exported_function_data().call_count(),
|
||||
0);
|
||||
// Check that the generic-wrapper budget has initially a value of
|
||||
// kGenericWrapperBudget.
|
||||
CHECK_EQ(
|
||||
main_export->shared().wasm_exported_function_data().wrapper_budget(),
|
||||
kGenericWrapperBudget);
|
||||
CHECK_GT(kGenericWrapperBudget, 0);
|
||||
|
||||
// Call the exported Wasm function and get the result.
|
||||
Handle<Object> params[2] = {Handle<Object>(Smi::FromInt(6), isolate),
|
||||
@ -74,9 +77,10 @@ TEST(CallCounter) {
|
||||
Execution::Call(isolate, main_export, receiver, 2, params);
|
||||
Handle<Object> result = maybe_result.ToHandleChecked();
|
||||
|
||||
// Check that the counter has now a value of 1.
|
||||
CHECK_EQ(main_export->shared().wasm_exported_function_data().call_count(),
|
||||
1);
|
||||
// Check that the budget has now a value of (kGenericWrapperBudget - 1).
|
||||
CHECK_EQ(
|
||||
main_export->shared().wasm_exported_function_data().wrapper_budget(),
|
||||
kGenericWrapperBudget - 1);
|
||||
|
||||
CHECK(result->IsSmi() && Smi::ToInt(*result) == kExpectedValue);
|
||||
}
|
||||
@ -115,15 +119,17 @@ TEST(WrapperReplacement) {
|
||||
"main");
|
||||
Handle<WasmExportedFunction> main_export = maybe_export.ToHandleChecked();
|
||||
|
||||
// Check that the counter has initially a value of 0.
|
||||
CHECK_EQ(main_export->shared().wasm_exported_function_data().call_count(),
|
||||
0);
|
||||
CHECK_GT(kGenericWrapperThreshold, 0);
|
||||
// Check that the generic-wrapper budget has initially a value of
|
||||
// kGenericWrapperBudget.
|
||||
CHECK_EQ(
|
||||
main_export->shared().wasm_exported_function_data().wrapper_budget(),
|
||||
kGenericWrapperBudget);
|
||||
CHECK_GT(kGenericWrapperBudget, 0);
|
||||
|
||||
// Call the exported Wasm function as many times as required to reach the
|
||||
// threshold for compiling the specific wrapper.
|
||||
const int threshold = static_cast<int>(kGenericWrapperThreshold);
|
||||
for (int i = 1; i < threshold; ++i) {
|
||||
// Call the exported Wasm function as many times as required to almost
|
||||
// exhaust the budget for using the generic wrapper.
|
||||
const int budget = static_cast<int>(kGenericWrapperBudget);
|
||||
for (int i = budget; i > 1; --i) {
|
||||
// Verify that the wrapper to be used is still the generic one.
|
||||
Code wrapper =
|
||||
main_export->shared().wasm_exported_function_data().wrapper_code();
|
||||
@ -137,10 +143,11 @@ TEST(WrapperReplacement) {
|
||||
MaybeHandle<Object> maybe_result =
|
||||
Execution::Call(isolate, main_export, receiver, 1, params);
|
||||
Handle<Object> result = maybe_result.ToHandleChecked();
|
||||
// Verify that the counter has now a value of i and the return value is
|
||||
// correct.
|
||||
CHECK_EQ(main_export->shared().wasm_exported_function_data().call_count(),
|
||||
i);
|
||||
// Verify that the budget has now a value of (i - 1) and the return value
|
||||
// is correct.
|
||||
CHECK_EQ(
|
||||
main_export->shared().wasm_exported_function_data().wrapper_budget(),
|
||||
i - 1);
|
||||
CHECK(result->IsSmi() && Smi::ToInt(*result) == expected_value);
|
||||
}
|
||||
|
||||
@ -162,9 +169,10 @@ TEST(WrapperReplacement) {
|
||||
MaybeHandle<Object> maybe_result =
|
||||
Execution::Call(isolate, main_export, receiver, 1, params);
|
||||
Handle<Object> result = maybe_result.ToHandleChecked();
|
||||
// Check that the counter has the threshold value and the result is correct.
|
||||
CHECK_EQ(main_export->shared().wasm_exported_function_data().call_count(),
|
||||
kGenericWrapperThreshold);
|
||||
// Check that the budget has been exhausted and the result is correct.
|
||||
CHECK_EQ(
|
||||
main_export->shared().wasm_exported_function_data().wrapper_budget(),
|
||||
0);
|
||||
CHECK(result->IsSmi() && Smi::ToInt(*result) == expected_value);
|
||||
|
||||
// Verify that the wrapper-code object has changed.
|
||||
@ -235,14 +243,14 @@ TEST(EagerWrapperReplacement) {
|
||||
WasmExportedFunctionData id_function_data =
|
||||
id_export->shared().wasm_exported_function_data();
|
||||
|
||||
// Set the call count for add to (threshold - 1),
|
||||
// Set the remaining generic-wrapper budget for add to 1,
|
||||
// so that the next call to it will cause the function to tier up.
|
||||
add_function_data.set_call_count(kGenericWrapperThreshold - 1);
|
||||
add_function_data.set_wrapper_budget(1);
|
||||
|
||||
// Verify that the call counts for all functions are correct.
|
||||
CHECK_EQ(add_function_data.call_count(), kGenericWrapperThreshold - 1);
|
||||
CHECK_EQ(mult_function_data.call_count(), 0);
|
||||
CHECK_EQ(id_function_data.call_count(), 0);
|
||||
// Verify that the generic-wrapper budgets for all functions are correct.
|
||||
CHECK_EQ(add_function_data.wrapper_budget(), 1);
|
||||
CHECK_EQ(mult_function_data.wrapper_budget(), kGenericWrapperBudget);
|
||||
CHECK_EQ(id_function_data.wrapper_budget(), kGenericWrapperBudget);
|
||||
|
||||
// Verify that all functions are set to use the generic wrapper.
|
||||
CHECK(add_function_data.wrapper_code().is_builtin() &&
|
||||
@ -267,10 +275,10 @@ TEST(EagerWrapperReplacement) {
|
||||
CHECK(result->IsSmi() && Smi::ToInt(*result) == expected_value);
|
||||
}
|
||||
|
||||
// Verify that the call counts for all functions are correct.
|
||||
CHECK_EQ(add_function_data.call_count(), kGenericWrapperThreshold);
|
||||
CHECK_EQ(mult_function_data.call_count(), 0);
|
||||
CHECK_EQ(id_function_data.call_count(), 0);
|
||||
// Verify that the generic-wrapper budgets for all functions are correct.
|
||||
CHECK_EQ(add_function_data.wrapper_budget(), 0);
|
||||
CHECK_EQ(mult_function_data.wrapper_budget(), kGenericWrapperBudget);
|
||||
CHECK_EQ(id_function_data.wrapper_budget(), kGenericWrapperBudget);
|
||||
|
||||
// Verify that the tier up of the add function replaced the wrapper
|
||||
// for both the add and the mult functions, but not the id function.
|
||||
@ -293,9 +301,9 @@ TEST(EagerWrapperReplacement) {
|
||||
Handle<Object> result = maybe_result.ToHandleChecked();
|
||||
CHECK(result->IsSmi() && Smi::ToInt(*result) == expected_value);
|
||||
}
|
||||
// Verify that mult's call count is still 0, which means that the call
|
||||
// Verify that mult's budget is still intact, which means that the call
|
||||
// didn't go through the generic wrapper.
|
||||
CHECK_EQ(mult_function_data.call_count(), 0);
|
||||
CHECK_EQ(mult_function_data.wrapper_budget(), kGenericWrapperBudget);
|
||||
|
||||
// Call the id function to verify that the generic wrapper is used.
|
||||
{
|
||||
@ -308,9 +316,9 @@ TEST(EagerWrapperReplacement) {
|
||||
Handle<Object> result = maybe_result.ToHandleChecked();
|
||||
CHECK(result->IsSmi() && Smi::ToInt(*result) == expected_value);
|
||||
}
|
||||
// Verify that id's call count increased to 1, which means that the call
|
||||
// Verify that id's budget decreased by 1, which means that the call
|
||||
// used the generic wrapper.
|
||||
CHECK_EQ(id_function_data.call_count(), 1);
|
||||
CHECK_EQ(id_function_data.wrapper_budget(), kGenericWrapperBudget - 1);
|
||||
}
|
||||
Cleanup();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user