[wasm] Share code of js-to-wasm wrappers

Instead of creating a separate code object per exported function, we
can share the code per signature, and load the function index from the
{WasmExportedFunction} object which is being passed as an argument
anyway.
This greatly reduces instantiation time for modules with a lot of
exports.
As a next step, we could even share the code across instances, or (with
more work) across isolates.

R=mstarzinger@chromium.org

Bug: chromium:860491
Change-Id: I6438065b2de0df59dce980fb52408a0f475144b3
Reviewed-on: https://chromium-review.googlesource.com/1127660
Commit-Queue: Clemens Hammacher <clemensh@chromium.org>
Reviewed-by: Michael Starzinger <mstarzinger@chromium.org>
Cr-Commit-Position: refs/heads/master@{#54299}
This commit is contained in:
Clemens Hammacher 2018-07-06 16:27:20 +02:00 committed by Commit Bot
parent fa067fb94c
commit 815485244f
6 changed files with 135 additions and 85 deletions

View File

@ -39,6 +39,7 @@
#include "src/trap-handler/trap-handler.h"
#include "src/wasm/function-body-decoder.h"
#include "src/wasm/function-compiler.h"
#include "src/wasm/jump-table-assembler.h"
#include "src/wasm/memory-tracing.h"
#include "src/wasm/wasm-code-manager.h"
#include "src/wasm/wasm-limits.h"
@ -2590,13 +2591,45 @@ Node* WasmGraphBuilder::BuildImportWasmCall(wasm::FunctionSig* sig, Node** args,
LOAD_INSTANCE_FIELD(ImportedFunctionTargets, MachineType::Pointer());
Node* target_node = graph()->NewNode(
mcgraph()->machine()->Load(MachineType::Pointer()), imported_targets,
mcgraph()->Int32Constant(func_index * sizeof(Address)),
mcgraph()->Int32Constant(func_index * kPointerSize),
mcgraph()->graph()->start(), mcgraph()->graph()->start());
args[0] = target_node;
return BuildWasmCall(sig, args, rets, position, instance_node,
untrusted_code_mitigations_ ? kRetpoline : kNoRetpoline);
}
Node* WasmGraphBuilder::BuildImportWasmCall(wasm::FunctionSig* sig, Node** args,
Node*** rets,
wasm::WasmCodePosition position,
Node* func_index) {
// Load the instance from the imported_instances array.
Node* imported_instances = LOAD_INSTANCE_FIELD(ImportedFunctionInstances,
MachineType::TaggedPointer());
// Access fixed array at {header_size - tag + func_index * kPointerSize}.
Node* imported_instances_data =
graph()->NewNode(mcgraph()->machine()->IntAdd(), imported_instances,
mcgraph()->IntPtrConstant(FixedArrayOffsetMinusTag(0)));
Node* func_index_times_pointersize = graph()->NewNode(
mcgraph()->machine()->IntMul(), Uint32ToUintptr(func_index),
mcgraph()->Int32Constant(kPointerSize));
Node* instance_node =
graph()->NewNode(mcgraph()->machine()->Load(MachineType::TaggedPointer()),
imported_instances_data, func_index_times_pointersize,
*effect_, *control_);
// Load the target from the imported_targets array at the offset of
// {func_index}.
Node* imported_targets =
LOAD_INSTANCE_FIELD(ImportedFunctionTargets, MachineType::Pointer());
Node* target_node = graph()->NewNode(
mcgraph()->machine()->Load(MachineType::Pointer()), imported_targets,
func_index_times_pointersize, mcgraph()->graph()->start(),
mcgraph()->graph()->start());
args[0] = target_node;
return BuildWasmCall(sig, args, rets, position, instance_node,
untrusted_code_mitigations_ ? kRetpoline : kNoRetpoline);
}
Node* WasmGraphBuilder::CallDirect(uint32_t index, Node** args, Node*** rets,
wasm::WasmCodePosition position) {
DCHECK_NULL(args[0]);
@ -2929,10 +2962,8 @@ void WasmGraphBuilder::GetGlobalBaseAndOffset(MachineType mem_type,
if (mem_type == MachineType::Simd128() && global.offset != 0) {
// TODO(titzer,bbudge): code generation for SIMD memory offsets is broken.
*base_node =
graph()->NewNode(kPointerSize == 4 ? mcgraph()->machine()->Int32Add()
: mcgraph()->machine()->Int64Add(),
*base_node, *offset_node);
*base_node = graph()->NewNode(mcgraph()->machine()->IntAdd(), *base_node,
*offset_node);
*offset_node = mcgraph()->Int32Constant(0);
}
}
@ -4326,7 +4357,7 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
&sig, graph()->NewNode(mcgraph()->common()->ExternalConstant(ref)));
}
Node* BuildLoadInstanceFromExportedFunction(Node* closure) {
Node* BuildLoadFunctionDataFromExportedFunction(Node* closure) {
Node* shared = *effect_ = graph()->NewNode(
jsgraph()->machine()->Load(MachineType::AnyTagged()), closure,
jsgraph()->Int32Constant(JSFunction::kSharedFunctionInfoOffset -
@ -4337,6 +4368,10 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
jsgraph()->Int32Constant(SharedFunctionInfo::kFunctionDataOffset -
kHeapObjectTag),
*effect_, *control_);
return function_data;
}
Node* BuildLoadInstanceFromExportedFunctionData(Node* function_data) {
Node* instance = *effect_ = graph()->NewNode(
jsgraph()->machine()->Load(MachineType::AnyTagged()), function_data,
jsgraph()->Int32Constant(WasmExportedFunctionData::kInstanceOffset -
@ -4345,7 +4380,18 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
return instance;
}
void BuildJSToWasmWrapper(uint32_t wasm_func_index, Address call_target) {
Node* BuildLoadFunctionIndexFromExportedFunctionData(Node* function_data) {
Node* function_index_smi = *effect_ = graph()->NewNode(
jsgraph()->machine()->Load(MachineType::AnyTagged()), function_data,
jsgraph()->Int32Constant(
WasmExportedFunctionData::kFunctionIndexOffset - kHeapObjectTag),
*effect_, *control_);
Node* function_index = BuildChangeSmiToInt32(function_index_smi);
return function_index;
}
void BuildJSToWasmWrapper(Address jump_table_start, bool is_import,
uint32_t num_imported_functions) {
const int wasm_count = static_cast<int>(sig_->parameter_count());
// Build the start and the JS parameter nodes.
@ -4363,12 +4409,13 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
Linkage::GetJSCallContextParamIndex(wasm_count + 1), "%context"),
graph()->start());
// Create the instance_node node to pass as parameter. It is loaded from the
// Create the instance_node node to pass as parameter. It is loaded from
// an actual reference to an instance or a placeholder reference,
// called {WasmExportedFunction} via the {WasmExportedFunctionData}
// structure. since JSToWasm wrappers can be compiled at module compile time
// and patched at instance build time.
instance_node_.set(BuildLoadInstanceFromExportedFunction(js_closure));
// structure.
Node* function_data = BuildLoadFunctionDataFromExportedFunction(js_closure);
instance_node_.set(
BuildLoadInstanceFromExportedFunctionData(function_data));
if (!wasm::IsJSCompatibleSignature(sig_)) {
// Throw a TypeError. Use the js_context of the calling javascript
@ -4394,16 +4441,32 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
// Set the ThreadInWasm flag before we do the actual call.
BuildModifyThreadInWasmFlag(true);
if (env_ && wasm_func_index < env_->module->num_imported_functions) {
// Load function index from {WasmExportedFunctionData}.
Node* function_index =
BuildLoadFunctionIndexFromExportedFunctionData(function_data);
if (is_import) {
// Call to an imported function.
DCHECK_EQ(kNullAddress, call_target);
BuildImportWasmCall(sig_, args, &rets, wasm::kNoCodePosition,
wasm_func_index);
function_index);
} else {
// Call to a wasm function defined in this module.
DCHECK_NE(kNullAddress, call_target);
args[0] = mcgraph()->RelocatableIntPtrConstant(
call_target, RelocInfo::JS_TO_WASM_CALL);
// The call target is the jump table slot for that function. This is
// {jump_table + (func_index - num_imports) * kJumpTableSlotSize}.
// Compute as
// jump_table_adjusted (static) := jump_table - num_imports * kJTSS.
// call_target := jump_table_adjusted + func_index * kJTSS.
Node* jump_table_adjusted = mcgraph()->IntPtrConstant(
jump_table_start - num_imported_functions *
wasm::JumpTableAssembler::kJumpTableSlotSize);
Node* jump_table_offset = graph()->NewNode(
mcgraph()->machine()->IntMul(), Uint32ToUintptr(function_index),
mcgraph()->IntPtrConstant(
wasm::JumpTableAssembler::kJumpTableSlotSize));
Node* jump_table_slot =
graph()->NewNode(mcgraph()->machine()->IntAdd(), jump_table_adjusted,
jump_table_offset);
args[0] = jump_table_slot;
BuildWasmCall(sig_, args, &rets, wasm::kNoCodePosition, nullptr,
kNoRetpoline);
@ -4726,12 +4789,13 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
} // namespace
MaybeHandle<Code> CompileJSToWasmWrapper(
Isolate* isolate, const wasm::WasmModule* module, Address call_target,
uint32_t index, wasm::UseTrapHandler use_trap_handler) {
const wasm::WasmFunction* func = &module->functions[index];
Isolate* isolate, const wasm::NativeModule* native_module,
wasm::FunctionSig* sig, bool is_import,
wasm::UseTrapHandler use_trap_handler) {
const wasm::WasmModule* module = native_module->module();
//----------------------------------------------------------------------------
// Create the Graph
// Create the Graph.
//----------------------------------------------------------------------------
Zone zone(isolate->allocator(), ZONE_NAME);
Graph graph(&zone);
@ -4746,11 +4810,12 @@ MaybeHandle<Code> CompileJSToWasmWrapper(
Node* effect = nullptr;
wasm::ModuleEnv env(module, use_trap_handler, wasm::kRuntimeExceptionSupport);
WasmWrapperGraphBuilder builder(&zone, &env, &jsgraph, func->sig, nullptr,
WasmWrapperGraphBuilder builder(&zone, &env, &jsgraph, sig, nullptr,
StubCallMode::kCallOnHeapBuiltin);
builder.set_control_ptr(&control);
builder.set_effect_ptr(&effect);
builder.BuildJSToWasmWrapper(index, call_target);
builder.BuildJSToWasmWrapper(native_module->jump_table_start(), is_import,
module->num_imported_functions);
//----------------------------------------------------------------------------
// Run the compilation pipeline.
@ -4771,8 +4836,7 @@ MaybeHandle<Code> CompileJSToWasmWrapper(
}
// Schedule and compile to machine code.
int params =
static_cast<int>(module->functions[index].sig->parameter_count());
int params = static_cast<int>(sig->parameter_count());
CallDescriptor* incoming = Linkage::GetJSCallDescriptor(
&zone, false, params + 1, CallDescriptor::kNoFlags);

View File

@ -114,10 +114,11 @@ MaybeHandle<Code> CompileWasmToJSWrapper(Isolate*, Handle<JSReceiver> target,
wasm::ModuleOrigin,
wasm::UseTrapHandler);
// Wraps a given wasm code object, producing a code object.
// Creates a code object calling a wasm function with the given signature,
// callable from JS.
V8_EXPORT_PRIVATE MaybeHandle<Code> CompileJSToWasmWrapper(
Isolate*, const wasm::WasmModule*, Address call_target, uint32_t index,
wasm::UseTrapHandler);
Isolate*, const wasm::NativeModule*, wasm::FunctionSig*, bool is_import,
wasm::UseTrapHandler use_trap_handler);
// Compiles a stub that redirects a call to a wasm function to the wasm
// interpreter. It's ABI compatible with the compiled wasm function.
@ -393,6 +394,8 @@ class WasmGraphBuilder {
UseRetpoline use_retpoline);
Node* BuildImportWasmCall(wasm::FunctionSig* sig, Node** args, Node*** rets,
wasm::WasmCodePosition position, int func_index);
Node* BuildImportWasmCall(wasm::FunctionSig* sig, Node** args, Node*** rets,
wasm::WasmCodePosition position, Node* func_index);
Node* BuildF32CopySign(Node* left, Node* right);
Node* BuildF64CopySign(Node* left, Node* right);

View File

@ -171,46 +171,35 @@ namespace {
class JSToWasmWrapperCache {
public:
Handle<Code> CloneOrCompileJSToWasmWrapper(
Isolate* isolate, const wasm::WasmModule* module, Address call_target,
uint32_t index, wasm::UseTrapHandler use_trap_handler) {
const bool is_import = index < module->num_imported_functions;
DCHECK_EQ(is_import, call_target == kNullAddress);
const wasm::WasmFunction* func = &module->functions[index];
// We cannot cache js-to-wasm wrappers for imports, as they hard-code the
// function index.
if (!is_import) {
int cached_idx = sig_map_.Find(func->sig);
if (cached_idx >= 0) {
Handle<Code> code =
isolate->factory()->CopyCode(code_cache_[cached_idx]);
// Now patch the call to wasm code.
RelocIterator it(*code,
RelocInfo::ModeMask(RelocInfo::JS_TO_WASM_CALL));
// If there is no reloc info, then it's an incompatible signature or
// calls an import.
if (!it.done()) it.rinfo()->set_js_to_wasm_address(call_target);
return code;
}
}
Handle<Code> GetOrCompileJSToWasmWrapper(
Isolate* isolate, const wasm::NativeModule* native_module,
uint32_t func_index, wasm::UseTrapHandler use_trap_handler) {
const wasm::WasmModule* module = native_module->module();
const wasm::WasmFunction* func = &module->functions[func_index];
bool is_import = func_index < module->num_imported_functions;
auto& cache = cache_[is_import ? 1 : 0];
int cached_idx = cache.sig_map.Find(func->sig);
if (cached_idx >= 0) return cache.code_cache[cached_idx];
Handle<Code> code =
compiler::CompileJSToWasmWrapper(isolate, module, call_target, index,
use_trap_handler)
compiler::CompileJSToWasmWrapper(isolate, native_module, func->sig,
is_import, use_trap_handler)
.ToHandleChecked();
if (!is_import) {
uint32_t new_cache_idx = sig_map_.FindOrInsert(func->sig);
DCHECK_EQ(code_cache_.size(), new_cache_idx);
USE(new_cache_idx);
code_cache_.push_back(code);
}
uint32_t new_cache_idx = cache.sig_map.FindOrInsert(func->sig);
DCHECK_EQ(cache.code_cache.size(), new_cache_idx);
USE(new_cache_idx);
cache.code_cache.push_back(code);
return code;
}
private:
// sig_map_ maps signatures to an index in code_cache_.
wasm::SignatureMap sig_map_;
std::vector<Handle<Code>> code_cache_;
// We generate different code for calling imports than calling wasm functions
// in this module. Both are cached separately.
// [0] for non-imports, [1] for imports.
struct Cache {
wasm::SignatureMap sig_map;
std::vector<Handle<Code>> code_cache;
} cache_[2];
};
// A helper class to simplify instantiating a module from a module object.
@ -1254,13 +1243,9 @@ MaybeHandle<WasmInstanceObject> InstanceBuilder::Build() {
//--------------------------------------------------------------------------
if (module_->start_function_index >= 0) {
int start_index = module_->start_function_index;
Address start_call_address =
static_cast<uint32_t>(start_index) < module_->num_imported_functions
? kNullAddress
: native_module->GetCallTargetForFunction(start_index);
FunctionSig* sig = module_->functions[start_index].sig;
Handle<Code> wrapper_code = js_to_wasm_cache_.CloneOrCompileJSToWasmWrapper(
isolate_, module_, start_call_address, start_index, use_trap_handler());
Handle<Code> wrapper_code = js_to_wasm_cache_.GetOrCompileJSToWasmWrapper(
isolate_, native_module, start_index, use_trap_handler());
// TODO(clemensh): Don't generate an exported function for the start
// function. Use CWasmEntry instead.
start_function_ = WasmExportedFunction::New(
@ -2135,9 +2120,8 @@ void InstanceBuilder::LoadTableSegments(Handle<WasmInstanceObject> instance) {
// at module compile time and cached instead.
Handle<Code> wrapper_code =
js_to_wasm_cache_.CloneOrCompileJSToWasmWrapper(
isolate_, module_, is_import ? kNullAddress : call_target,
func_index, use_trap_handler());
js_to_wasm_cache_.GetOrCompileJSToWasmWrapper(
isolate_, native_module, func_index, use_trap_handler());
MaybeHandle<String> func_name;
if (module_->origin == kAsmJsOrigin) {
// For modules arising from asm.js, honor the names section.
@ -3050,15 +3034,11 @@ void CompileJsToWasmWrappers(Isolate* isolate,
NativeModule* native_module = module_object->native_module();
wasm::UseTrapHandler use_trap_handler =
native_module->use_trap_handler() ? kUseTrapHandler : kNoTrapHandler;
const WasmModule* module = module_object->module();
const WasmModule* module = native_module->module();
for (auto exp : module->export_table) {
if (exp.kind != kExternalFunction) continue;
Address call_target =
exp.index < module->num_imported_functions
? kNullAddress
: native_module->GetCallTargetForFunction(exp.index);
Handle<Code> wrapper_code = js_to_wasm_cache.CloneOrCompileJSToWasmWrapper(
isolate, module, call_target, exp.index, use_trap_handler);
Handle<Code> wrapper_code = js_to_wasm_cache.GetOrCompileJSToWasmWrapper(
isolate, native_module, exp.index, use_trap_handler);
export_wrappers->set(wrapper_index, *wrapper_code);
RecordStats(*wrapper_code, isolate->counters());
++wrapper_index;

View File

@ -279,6 +279,10 @@ class V8_EXPORT_PRIVATE NativeModule final {
return code;
}
Address jump_table_start() const {
return jump_table_ ? jump_table_->instruction_start() : kNullAddress;
}
bool is_jump_table_slot(Address address) const {
return jump_table_->contains(address);
}

View File

@ -501,10 +501,10 @@ class WasmExportedFunctionData : public Struct {
DECL_VERIFIER(WasmExportedFunctionData)
// Layout description.
#define WASM_EXPORTED_FUNCTION_DATA_FIELDS(V) \
V(kWrapperCodeOffset, kPointerSize) \
V(kInstanceOffset, kPointerSize) \
V(kFunctionIndexOffset, kPointerSize) \
#define WASM_EXPORTED_FUNCTION_DATA_FIELDS(V) \
V(kWrapperCodeOffset, kPointerSize) \
V(kInstanceOffset, kPointerSize) \
V(kFunctionIndexOffset, kPointerSize) /* Smi */ \
V(kSize, 0)
DEFINE_FIELD_OFFSET_CONSTANTS(HeapObject::kHeaderSize,

View File

@ -122,15 +122,14 @@ uint32_t TestingModuleBuilder::AddFunction(FunctionSig* sig, const char* name,
Handle<JSFunction> TestingModuleBuilder::WrapCode(uint32_t index) {
// Wrap the code so it can be called as a JS function.
Link();
Address target = native_module_->GetCallTargetForFunction(index);
FunctionSig* sig = test_module_->functions[index].sig;
MaybeHandle<Code> maybe_ret_code = compiler::CompileJSToWasmWrapper(
isolate_, test_module_ptr_, target, index,
isolate_, native_module_, sig, false,
trap_handler::IsTrapHandlerEnabled() ? kUseTrapHandler : kNoTrapHandler);
Handle<Code> ret_code = maybe_ret_code.ToHandleChecked();
Handle<JSFunction> ret = WasmExportedFunction::New(
isolate_, instance_object(), MaybeHandle<String>(),
static_cast<int>(index),
static_cast<int>(test_module_->functions[index].sig->parameter_count()),
static_cast<int>(index), static_cast<int>(sig->parameter_count()),
ret_code);
// Add reference to the exported wrapper code.