[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:
Andreas Haas 2019-05-22 16:04:36 +02:00 committed by Commit Bot
parent 4088c521f6
commit d56ee2e3df
14 changed files with 100 additions and 57 deletions

View File

@ -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;
}
}

View File

@ -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 =

View File

@ -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_;

View File

@ -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) {

View File

@ -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

View File

@ -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);

View File

@ -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,

View File

@ -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);

View File

@ -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))));

View File

@ -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};

View File

@ -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),

View File

@ -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};

View File

@ -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(

View File

@ -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();
}