[wasm][anyref] Cache export wrappers per signature
Up until now, we cached export wrappers per export index. With the anyref proposal potentially many more functions will need export wrappers, e.g. any function that is stored in a table, and any function accessed by the new ref.func instruction. With this CL, we change the caching scheme an do the caching per signature. Thereby we can guarantee that any export wrapper which potentially exists can be stored in the cache. For cctests which use wasm-run-utils, we don't know the size of the cache anymore ahead of time. However, we assume that no more than 5 signatures will be used in any cctest. If this assumption is not true, we can just adjust the number. The cache is now accessed in all code paths where we need an export wrapper. Bug: chromium:962850 Change-Id: I32df60dfa7801d1e71f7d837da091f388198af1f Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1615247 Commit-Queue: Andreas Haas <ahaas@chromium.org> Reviewed-by: Clemens Hammacher <clemensh@chromium.org> Cr-Commit-Position: refs/heads/master@{#61752}
This commit is contained in:
parent
4088c521f6
commit
d56ee2e3df
@ -1076,8 +1076,6 @@ std::shared_ptr<NativeModule> CompileToNativeModule(
|
||||
if (wasm_module->has_shared_memory) {
|
||||
isolate->CountUsage(v8::Isolate::UseCounterFeature::kWasmSharedMemory);
|
||||
}
|
||||
int export_wrapper_size = static_cast<int>(module->num_exported_functions);
|
||||
|
||||
// TODO(wasm): only save the sections necessary to deserialize a
|
||||
// {WasmModule}. E.g. function bodies could be omitted.
|
||||
OwnedVector<uint8_t> wire_bytes_copy =
|
||||
@ -1098,8 +1096,9 @@ std::shared_ptr<NativeModule> CompileToNativeModule(
|
||||
if (thrower->error()) return {};
|
||||
|
||||
// Compile JS->wasm wrappers for exported functions.
|
||||
*export_wrappers_out = isolate->factory()->NewFixedArray(
|
||||
export_wrapper_size, AllocationType::kOld);
|
||||
int num_wrappers = MaxNumExportWrappers(native_module->module());
|
||||
*export_wrappers_out =
|
||||
isolate->factory()->NewFixedArray(num_wrappers, AllocationType::kOld);
|
||||
CompileJsToWasmWrappers(isolate, native_module->module(),
|
||||
*export_wrappers_out);
|
||||
|
||||
@ -2186,7 +2185,6 @@ void CompilationStateImpl::SetError() {
|
||||
void CompileJsToWasmWrappers(Isolate* isolate, const WasmModule* module,
|
||||
Handle<FixedArray> export_wrappers) {
|
||||
JSToWasmWrapperCache js_to_wasm_cache;
|
||||
int wrapper_index = 0;
|
||||
|
||||
// TODO(6792): Wrappers below are allocated with {Factory::NewCode}. As an
|
||||
// optimization we keep the code space unlocked to avoid repeated unlocking
|
||||
@ -2197,9 +2195,11 @@ void CompileJsToWasmWrappers(Isolate* isolate, const WasmModule* module,
|
||||
auto& function = module->functions[exp.index];
|
||||
Handle<Code> wrapper_code = js_to_wasm_cache.GetOrCompileJSToWasmWrapper(
|
||||
isolate, function.sig, function.imported);
|
||||
int wrapper_index =
|
||||
GetExportWrapperIndex(module, function.sig, function.imported);
|
||||
|
||||
export_wrappers->set(wrapper_index, *wrapper_code);
|
||||
RecordStats(*wrapper_code, isolate->counters());
|
||||
++wrapper_index;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1316,8 +1316,6 @@ bool InstanceBuilder::NeedsWrappers() const {
|
||||
// Process the exports, creating wrappers for functions, tables, memories,
|
||||
// globals, and exceptions.
|
||||
void InstanceBuilder::ProcessExports(Handle<WasmInstanceObject> instance) {
|
||||
Handle<FixedArray> export_wrappers(module_object_->export_wrappers(),
|
||||
isolate_);
|
||||
if (NeedsWrappers()) {
|
||||
// If an imported WebAssembly function gets exported, the exported function
|
||||
// has to be identical to to imported function. Therefore we cache all
|
||||
@ -1365,7 +1363,6 @@ void InstanceBuilder::ProcessExports(Handle<WasmInstanceObject> instance) {
|
||||
desc.set_configurable(is_asm_js);
|
||||
|
||||
// Process each export in the export table.
|
||||
int export_index = 0; // Index into {export_wrappers}.
|
||||
for (const WasmExport& exp : module_->export_table) {
|
||||
Handle<String> name = WasmModuleObject::ExtractUtf8StringFromModuleBytes(
|
||||
isolate_, module_object_, exp.name)
|
||||
@ -1387,7 +1384,6 @@ void InstanceBuilder::ProcessExports(Handle<WasmInstanceObject> instance) {
|
||||
isolate_, instance, exp.index);
|
||||
|
||||
desc.set_value(wasm_exported_function.ToHandleChecked());
|
||||
export_index++;
|
||||
break;
|
||||
}
|
||||
case kExternalTable: {
|
||||
@ -1483,7 +1479,6 @@ void InstanceBuilder::ProcessExports(Handle<WasmInstanceObject> instance) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
DCHECK_EQ(export_index, export_wrappers->length());
|
||||
|
||||
if (module_->origin == kWasmOrigin) {
|
||||
v8::Maybe<bool> success =
|
||||
|
@ -34,6 +34,10 @@ class V8_EXPORT_PRIVATE SignatureMap {
|
||||
// Disallows further insertions to this signature map.
|
||||
void Freeze() { frozen_ = true; }
|
||||
|
||||
size_t size() const { return map_.size(); }
|
||||
|
||||
bool is_frozen() const { return frozen_; }
|
||||
|
||||
private:
|
||||
bool frozen_ = false;
|
||||
std::unordered_map<FunctionSig, uint32_t, base::hash<FunctionSig>> map_;
|
||||
|
@ -42,6 +42,22 @@ WireBytesRef WasmModule::LookupFunctionName(const ModuleWireBytes& wire_bytes,
|
||||
return it->second;
|
||||
}
|
||||
|
||||
// static
|
||||
int MaxNumExportWrappers(const WasmModule* module) {
|
||||
// For each signature there may exist a wrapper, both for imported and
|
||||
// internal functions.
|
||||
return static_cast<int>(module->signature_map.size()) * 2;
|
||||
}
|
||||
|
||||
// static
|
||||
int GetExportWrapperIndex(const WasmModule* module, const FunctionSig* sig,
|
||||
bool is_import) {
|
||||
int result = module->signature_map.Find(*sig);
|
||||
CHECK_GE(result, 0);
|
||||
result += is_import ? module->signature_map.size() : 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
void WasmModule::AddFunctionNameForTesting(int function_index,
|
||||
WireBytesRef name) {
|
||||
if (!function_names) {
|
||||
|
@ -223,6 +223,14 @@ struct V8_EXPORT_PRIVATE WasmModule {
|
||||
|
||||
size_t EstimateStoredSize(const WasmModule* module);
|
||||
|
||||
// Returns the number of possible export wrappers for a given module.
|
||||
V8_EXPORT_PRIVATE int MaxNumExportWrappers(const WasmModule* module);
|
||||
|
||||
// Returns the wrapper index for a function in {module} with signature {sig}
|
||||
// and origin defined by {is_import}.
|
||||
int GetExportWrapperIndex(const WasmModule* module, const FunctionSig* sig,
|
||||
bool is_import);
|
||||
|
||||
// Interface to the storage (wire bytes) of a wasm module.
|
||||
// It is illegal for anyone receiving a ModuleWireBytes to store pointers based
|
||||
// on module_bytes, as this storage is only guaranteed to be alive as long as
|
||||
|
@ -232,9 +232,9 @@ Handle<WasmModuleObject> WasmModuleObject::New(
|
||||
Isolate* isolate, std::shared_ptr<wasm::NativeModule> native_module,
|
||||
Handle<Script> script, size_t code_size_estimate) {
|
||||
const WasmModule* module = native_module->module();
|
||||
int export_wrapper_size = static_cast<int>(module->num_exported_functions);
|
||||
Handle<FixedArray> export_wrappers = isolate->factory()->NewFixedArray(
|
||||
export_wrapper_size, AllocationType::kOld);
|
||||
int num_wrappers = MaxNumExportWrappers(module);
|
||||
Handle<FixedArray> export_wrappers =
|
||||
isolate->factory()->NewFixedArray(num_wrappers, AllocationType::kOld);
|
||||
return New(isolate, std::move(native_module), script, export_wrappers,
|
||||
code_size_estimate);
|
||||
}
|
||||
@ -1814,15 +1814,30 @@ WasmInstanceObject::GetOrCreateWasmExportedFunction(
|
||||
return result;
|
||||
}
|
||||
|
||||
const WasmModule* module = instance->module_object()->module();
|
||||
Handle<WasmModuleObject> module_object(instance->module_object(), isolate);
|
||||
const WasmModule* module = module_object->module();
|
||||
const WasmFunction& function = module->functions[function_index];
|
||||
Handle<Code> wrapper_code =
|
||||
compiler::CompileJSToWasmWrapper(isolate, function.sig, function.imported)
|
||||
.ToHandleChecked();
|
||||
int wrapper_index =
|
||||
GetExportWrapperIndex(module, function.sig, function.imported);
|
||||
|
||||
Handle<Object> entry =
|
||||
FixedArray::get(module_object->export_wrappers(), wrapper_index, isolate);
|
||||
|
||||
Handle<Code> wrapper;
|
||||
if (entry->IsCode()) {
|
||||
wrapper = Handle<Code>::cast(entry);
|
||||
} else {
|
||||
// The wrapper may not exist yet if no function in the exports section has
|
||||
// this signature. We compile it and store the wrapper in the module for
|
||||
// later use.
|
||||
wrapper = compiler::CompileJSToWasmWrapper(isolate, function.sig,
|
||||
function.imported)
|
||||
.ToHandleChecked();
|
||||
module_object->export_wrappers()->set(wrapper_index, *wrapper);
|
||||
}
|
||||
result = WasmExportedFunction::New(
|
||||
isolate, instance, function_index,
|
||||
static_cast<int>(function.sig->parameter_count()), wrapper_code);
|
||||
static_cast<int>(function.sig->parameter_count()), wrapper);
|
||||
|
||||
WasmInstanceObject::SetWasmExportedFunction(isolate, instance, function_index,
|
||||
result);
|
||||
|
@ -587,9 +587,10 @@ class WasmInstanceObject : public JSObject {
|
||||
// cache of the given {instance}, or creates a new {WasmExportedFunction} if
|
||||
// it does not exist yet. The new {WasmExportedFunction} is added to the
|
||||
// cache of the {instance} immediately.
|
||||
static Handle<WasmExportedFunction> GetOrCreateWasmExportedFunction(
|
||||
Isolate* isolate, Handle<WasmInstanceObject> instance,
|
||||
int function_index);
|
||||
V8_EXPORT_PRIVATE static Handle<WasmExportedFunction>
|
||||
GetOrCreateWasmExportedFunction(Isolate* isolate,
|
||||
Handle<WasmInstanceObject> instance,
|
||||
int function_index);
|
||||
|
||||
static void SetWasmExportedFunction(Isolate* isolate,
|
||||
Handle<WasmInstanceObject> instance,
|
||||
|
@ -543,6 +543,8 @@ WASM_EXEC_TEST(TableCopyElems) {
|
||||
WASM_TABLE_COPY(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1), WASM_GET_LOCAL(2)),
|
||||
kExprI32Const, 0);
|
||||
|
||||
r.builder().FreezeSignatureMapAndInitializeWrapperCache();
|
||||
|
||||
auto table = handle(
|
||||
WasmTableObject::cast(r.builder().instance_object()->tables().get(0)),
|
||||
isolate);
|
||||
@ -628,6 +630,8 @@ WASM_EXEC_TEST(TableCopyOobWrites) {
|
||||
WASM_TABLE_COPY(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1), WASM_GET_LOCAL(2)),
|
||||
kExprI32Const, 0);
|
||||
|
||||
r.builder().FreezeSignatureMapAndInitializeWrapperCache();
|
||||
|
||||
auto table = handle(
|
||||
WasmTableObject::cast(r.builder().instance_object()->tables().get(0)),
|
||||
isolate);
|
||||
|
@ -71,7 +71,7 @@ WASM_EXEC_TEST(TryCatchCallIndirect) {
|
||||
// Build a throwing helper function.
|
||||
WasmFunctionCompiler& throw_func = r.NewFunction(sigs.i_ii());
|
||||
BUILD(throw_func, WASM_THROW(except));
|
||||
r.builder().AddSignature(sigs.i_ii());
|
||||
byte sig_index = r.builder().AddSignature(sigs.i_ii());
|
||||
throw_func.SetSigIndex(0);
|
||||
|
||||
// Add an indirect function table.
|
||||
@ -86,7 +86,7 @@ WASM_EXEC_TEST(TryCatchCallIndirect) {
|
||||
WASM_STMTS(WASM_I32V(kResult1),
|
||||
WASM_IF(WASM_I32_EQZ(WASM_GET_LOCAL(0)),
|
||||
WASM_STMTS(WASM_CALL_INDIRECT2(
|
||||
0, WASM_GET_LOCAL(0),
|
||||
sig_index, WASM_GET_LOCAL(0),
|
||||
WASM_I32V(7), WASM_I32V(9)),
|
||||
WASM_DROP))),
|
||||
WASM_STMTS(WASM_DROP, WASM_I32V(kResult0))));
|
||||
|
@ -220,7 +220,7 @@ TEST(Run_Wasm_returnCallIndirectFactorial) {
|
||||
WasmFunctionCompiler& fact_aux_fn = r.NewFunction(sigs.i_ii(), "fact_aux");
|
||||
fact_aux_fn.SetSigIndex(0);
|
||||
|
||||
r.builder().AddSignature(sigs.i_ii());
|
||||
byte sig_index = r.builder().AddSignature(sigs.i_ii());
|
||||
|
||||
// Function table.
|
||||
uint16_t indirect_function_table[] = {
|
||||
@ -229,14 +229,15 @@ TEST(Run_Wasm_returnCallIndirectFactorial) {
|
||||
r.builder().AddIndirectFunctionTable(indirect_function_table,
|
||||
arraysize(indirect_function_table));
|
||||
|
||||
BUILD(r, WASM_RETURN_CALL_INDIRECT(0, WASM_I32V(0), WASM_GET_LOCAL(0),
|
||||
BUILD(r, WASM_RETURN_CALL_INDIRECT(sig_index, WASM_I32V(0), WASM_GET_LOCAL(0),
|
||||
WASM_I32V(1)));
|
||||
|
||||
BUILD(fact_aux_fn,
|
||||
WASM_IF_ELSE_I(
|
||||
WASM_I32_EQ(WASM_I32V(1), WASM_GET_LOCAL(0)), WASM_GET_LOCAL(1),
|
||||
WASM_RETURN_CALL_INDIRECT(
|
||||
0, WASM_I32V(0), WASM_I32_SUB(WASM_GET_LOCAL(0), WASM_I32V(1)),
|
||||
sig_index, WASM_I32V(0),
|
||||
WASM_I32_SUB(WASM_GET_LOCAL(0), WASM_I32V(1)),
|
||||
WASM_I32_MUL(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1)))));
|
||||
|
||||
uint32_t test_values[] = {1, 2, 5, 10, 20};
|
||||
|
@ -141,14 +141,15 @@ WASM_EXEC_TEST(Run_IndirectCallJSFunction) {
|
||||
|
||||
WasmFunctionCompiler& rc_fn = r.NewFunction(sigs.i_i(), "rc");
|
||||
|
||||
r.builder().AddSignature(sigs.i_iii());
|
||||
byte sig_index = r.builder().AddSignature(sigs.i_iii());
|
||||
uint16_t indirect_function_table[] = {static_cast<uint16_t>(js_index)};
|
||||
|
||||
r.builder().AddIndirectFunctionTable(indirect_function_table,
|
||||
arraysize(indirect_function_table));
|
||||
|
||||
BUILD(rc_fn, WASM_CALL_INDIRECT3(0, WASM_I32V(js_index), WASM_I32V(left),
|
||||
WASM_I32V(right), WASM_GET_LOCAL(0)));
|
||||
BUILD(rc_fn,
|
||||
WASM_CALL_INDIRECT3(sig_index, WASM_I32V(js_index), WASM_I32V(left),
|
||||
WASM_I32V(right), WASM_GET_LOCAL(0)));
|
||||
|
||||
Handle<Object> args_left[] = {isolate->factory()->NewNumber(1)};
|
||||
r.CheckCallApplyViaJS(left, rc_fn.function_index(), args_left, 1);
|
||||
@ -538,15 +539,15 @@ void RunPickerTest(ExecutionTier tier, bool indirect) {
|
||||
WasmFunctionCompiler& rc_fn = r.NewFunction(sigs.i_i(), "rc");
|
||||
|
||||
if (indirect) {
|
||||
r.builder().AddSignature(sigs.i_iii());
|
||||
byte sig_index = r.builder().AddSignature(sigs.i_iii());
|
||||
uint16_t indirect_function_table[] = {static_cast<uint16_t>(js_index)};
|
||||
|
||||
r.builder().AddIndirectFunctionTable(indirect_function_table,
|
||||
arraysize(indirect_function_table));
|
||||
|
||||
BUILD(rc_fn,
|
||||
WASM_RETURN_CALL_INDIRECT(0, WASM_I32V(js_index), WASM_I32V(left),
|
||||
WASM_I32V(right), WASM_GET_LOCAL(0)));
|
||||
BUILD(rc_fn, WASM_RETURN_CALL_INDIRECT(sig_index, WASM_I32V(js_index),
|
||||
WASM_I32V(left), WASM_I32V(right),
|
||||
WASM_GET_LOCAL(0)));
|
||||
} else {
|
||||
BUILD(rc_fn,
|
||||
WASM_RETURN_CALL_FUNCTION(js_index, WASM_I32V(left), WASM_I32V(right),
|
||||
|
@ -122,15 +122,14 @@ WASM_EXEC_TEST(Int32Add_P2) {
|
||||
WASM_EXEC_TEST(Int32Add_block1) {
|
||||
EXPERIMENTAL_FLAG_SCOPE(mv);
|
||||
static const byte code[] = {
|
||||
WASM_BLOCK_X(0, WASM_GET_LOCAL(0), WASM_GET_LOCAL(1)),
|
||||
kExprI32Add};
|
||||
WASM_BLOCK_X(1, WASM_GET_LOCAL(0), WASM_GET_LOCAL(1)), kExprI32Add};
|
||||
RunInt32AddTest(execution_tier, code, sizeof(code));
|
||||
}
|
||||
|
||||
WASM_EXEC_TEST(Int32Add_block2) {
|
||||
EXPERIMENTAL_FLAG_SCOPE(mv);
|
||||
static const byte code[] = {
|
||||
WASM_BLOCK_X(0, WASM_GET_LOCAL(0), WASM_GET_LOCAL(1), kExprBr, DEPTH_0),
|
||||
WASM_BLOCK_X(1, WASM_GET_LOCAL(0), WASM_GET_LOCAL(1), kExprBr, DEPTH_0),
|
||||
kExprI32Add};
|
||||
RunInt32AddTest(execution_tier, code, sizeof(code));
|
||||
}
|
||||
@ -138,7 +137,7 @@ WASM_EXEC_TEST(Int32Add_block2) {
|
||||
WASM_EXEC_TEST(Int32Add_multi_if) {
|
||||
EXPERIMENTAL_FLAG_SCOPE(mv);
|
||||
static const byte code[] = {
|
||||
WASM_IF_ELSE_X(0, WASM_GET_LOCAL(0),
|
||||
WASM_IF_ELSE_X(1, WASM_GET_LOCAL(0),
|
||||
WASM_SEQ(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1)),
|
||||
WASM_SEQ(WASM_GET_LOCAL(1), WASM_GET_LOCAL(0))),
|
||||
kExprI32Add};
|
||||
|
@ -137,26 +137,20 @@ uint32_t TestingModuleBuilder::AddFunction(FunctionSig* sig, const char* name,
|
||||
return index;
|
||||
}
|
||||
|
||||
Handle<JSFunction> TestingModuleBuilder::WrapCode(uint32_t index) {
|
||||
SetExecutable();
|
||||
FunctionSig* sig = test_module_->functions[index].sig;
|
||||
MaybeHandle<Code> maybe_ret_code =
|
||||
compiler::CompileJSToWasmWrapper(isolate_, sig, false);
|
||||
Handle<Code> ret_code = maybe_ret_code.ToHandleChecked();
|
||||
Handle<JSFunction> ret = WasmExportedFunction::New(
|
||||
isolate_, instance_object(), static_cast<int>(index),
|
||||
static_cast<int>(sig->parameter_count()), ret_code);
|
||||
void TestingModuleBuilder::FreezeSignatureMapAndInitializeWrapperCache() {
|
||||
if (test_module_->signature_map.is_frozen()) return;
|
||||
test_module_->signature_map.Freeze();
|
||||
size_t max_num_sigs = MaxNumExportWrappers(test_module_.get());
|
||||
Handle<FixedArray> export_wrappers =
|
||||
isolate_->factory()->NewFixedArray(static_cast<int>(max_num_sigs));
|
||||
instance_object_->module_object()->set_export_wrappers(*export_wrappers);
|
||||
}
|
||||
|
||||
// Add reference to the exported wrapper code.
|
||||
Handle<WasmModuleObject> module_object(instance_object()->module_object(),
|
||||
isolate_);
|
||||
Handle<FixedArray> old_arr(module_object->export_wrappers(), isolate_);
|
||||
Handle<FixedArray> new_arr =
|
||||
isolate_->factory()->NewFixedArray(old_arr->length() + 1);
|
||||
old_arr->CopyTo(0, *new_arr, 0, old_arr->length());
|
||||
new_arr->set(old_arr->length(), *ret_code);
|
||||
module_object->set_export_wrappers(*new_arr);
|
||||
return ret;
|
||||
Handle<JSFunction> TestingModuleBuilder::WrapCode(uint32_t index) {
|
||||
FreezeSignatureMapAndInitializeWrapperCache();
|
||||
SetExecutable();
|
||||
return WasmInstanceObject::GetOrCreateWasmExportedFunction(
|
||||
isolate_, instance_object(), index);
|
||||
}
|
||||
|
||||
void TestingModuleBuilder::AddIndirectFunctionTable(
|
||||
|
@ -179,6 +179,10 @@ class TestingModuleBuilder {
|
||||
enum FunctionType { kImport, kWasm };
|
||||
uint32_t AddFunction(FunctionSig* sig, const char* name, FunctionType type);
|
||||
|
||||
// Freezes the signature map of the module and allocates the storage for
|
||||
// export wrappers.
|
||||
void FreezeSignatureMapAndInitializeWrapperCache();
|
||||
|
||||
// Wrap the code so it can be called as a JS function.
|
||||
Handle<JSFunction> WrapCode(uint32_t index);
|
||||
|
||||
@ -380,6 +384,7 @@ class WasmRunnerBase : public HandleAndZoneScope {
|
||||
const char* name = nullptr) {
|
||||
functions_.emplace_back(
|
||||
new WasmFunctionCompiler(&zone_, sig, &builder_, name));
|
||||
builder().AddSignature(sig);
|
||||
return *functions_.back();
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user