[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:
parent
fa067fb94c
commit
815485244f
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user