[wasm] Introduce a faster param transformation in the js-to-wasm wrapper

This CL introduces a fast path for transforming JavaScript
parameters to WebAssembly when calling an exported WebAssembly
function from JavaScript.

A transformation is considered fast when it does not require a call
to a built-in function, thus spilling registers for the call can be
avoided.

Bug: v8:10943
Change-Id: I5563bfdf77a68bef7ab8afc6d0f4ab2d2ea67bd4
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2418857
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@{#70033}
This commit is contained in:
Vicky Kontoura 2020-09-21 14:51:29 +00:00 committed by Commit Bot
parent 69ca751bc8
commit 66300a0422

View File

@ -6226,6 +6226,26 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
}
}
Node* FromJSFast(Node* input, wasm::ValueType type) {
switch (type.kind()) {
case wasm::ValueType::kI32:
return BuildChangeSmiToInt32(input);
case wasm::ValueType::kRef:
case wasm::ValueType::kOptRef:
case wasm::ValueType::kF32:
case wasm::ValueType::kF64:
case wasm::ValueType::kI64:
case wasm::ValueType::kRtt:
case wasm::ValueType::kS128:
case wasm::ValueType::kI8:
case wasm::ValueType::kI16:
case wasm::ValueType::kBottom:
case wasm::ValueType::kStmt:
UNREACHABLE();
break;
}
}
void BuildModifyThreadInWasmFlag(bool new_value) {
if (!trap_handler::IsTrapHandlerEnabled()) return;
Node* isolate_root = BuildLoadIsolateRoot();
@ -6296,55 +6316,15 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
return SetControl(CALL_BUILTIN(WasmAllocateJSArray, array_length, context));
}
void BuildJSToWasmWrapper(bool is_import) {
const int wasm_count = static_cast<int>(sig_->parameter_count());
const int rets_count = static_cast<int>(sig_->return_count());
// Build the start and the JS parameter nodes.
SetEffectControl(Start(wasm_count + 5));
// Create the js_closure and js_context parameters.
Node* js_closure =
graph()->NewNode(mcgraph()->common()->Parameter(
Linkage::kJSCallClosureParamIndex, "%closure"),
graph()->start());
Node* js_context = graph()->NewNode(
mcgraph()->common()->Parameter(
Linkage::GetJSCallContextParamIndex(wasm_count + 1), "%context"),
graph()->start());
// 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.
Node* function_data = BuildLoadFunctionDataFromExportedFunction(js_closure);
instance_node_.set(
BuildLoadInstanceFromExportedFunctionData(function_data));
if (!wasm::IsJSCompatibleSignature(sig_, module_, enabled_features_)) {
// Throw a TypeError. Use the js_context of the calling javascript
// function (passed as a parameter), such that the generated code is
// js_context independent.
BuildCallToRuntimeWithContext(Runtime::kWasmThrowTypeError, js_context,
nullptr, 0);
TerminateThrow(effect(), control());
return;
}
const int args_count = wasm_count + 1; // +1 for wasm_code.
base::SmallVector<Node*, 16> args(args_count);
base::SmallVector<Node*, 1> rets(rets_count);
// Convert JS parameters to wasm numbers.
for (int i = 0; i < wasm_count; ++i) {
Node* param = Param(i + 1);
Node* wasm_param = FromJS(param, js_context, sig_->GetParam(i));
args[i + 1] = wasm_param;
}
Node* BuildCallAndReturn(bool is_import, Node* js_context,
Node* function_data,
base::SmallVector<Node*, 16> args) {
// Set the ThreadInWasm flag before we do the actual call.
BuildModifyThreadInWasmFlag(true);
const int rets_count = static_cast<int>(sig_->return_count());
base::SmallVector<Node*, 1> rets(rets_count);
if (is_import) {
// Call to an imported function.
// Load function index from {WasmExportedFunctionData}.
@ -6389,7 +6369,145 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
STORE_FIXED_ARRAY_SLOT_ANY(fixed_array, i, value);
}
}
Return(jsval);
return jsval;
}
bool QualifiesForFastTransform(const wasm::FunctionSig*) {
const int wasm_count = static_cast<int>(sig_->parameter_count());
for (int i = 0; i < wasm_count; ++i) {
wasm::ValueType type = sig_->GetParam(i);
switch (type.kind()) {
case wasm::ValueType::kRef:
case wasm::ValueType::kOptRef:
case wasm::ValueType::kF32:
case wasm::ValueType::kF64:
case wasm::ValueType::kI64:
case wasm::ValueType::kRtt:
case wasm::ValueType::kS128:
case wasm::ValueType::kI8:
case wasm::ValueType::kI16:
case wasm::ValueType::kBottom:
case wasm::ValueType::kStmt:
return false;
case wasm::ValueType::kI32:
break;
}
}
return true;
}
Node* CanTransformFast(Node* input, wasm::ValueType type) {
switch (type.kind()) {
case wasm::ValueType::kI32: {
Node* is_smi = gasm_->Word32Equal(
gasm_->Word32And(BuildTruncateIntPtrToInt32(input),
gasm_->Int32Constant(kSmiTagMask)),
gasm_->Int32Constant(0));
return is_smi;
}
case wasm::ValueType::kRef:
case wasm::ValueType::kOptRef:
case wasm::ValueType::kF32:
case wasm::ValueType::kF64:
case wasm::ValueType::kI64:
case wasm::ValueType::kRtt:
case wasm::ValueType::kS128:
case wasm::ValueType::kI8:
case wasm::ValueType::kI16:
case wasm::ValueType::kBottom:
case wasm::ValueType::kStmt:
UNREACHABLE();
break;
}
}
void BuildJSToWasmWrapper(bool is_import) {
const int wasm_count = static_cast<int>(sig_->parameter_count());
// Build the start and the JS parameter nodes.
SetEffectControl(Start(wasm_count + 5));
// Create the js_closure and js_context parameters.
Node* js_closure =
graph()->NewNode(mcgraph()->common()->Parameter(
Linkage::kJSCallClosureParamIndex, "%closure"),
graph()->start());
Node* js_context = graph()->NewNode(
mcgraph()->common()->Parameter(
Linkage::GetJSCallContextParamIndex(wasm_count + 1), "%context"),
graph()->start());
// 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.
Node* function_data = BuildLoadFunctionDataFromExportedFunction(js_closure);
instance_node_.set(
BuildLoadInstanceFromExportedFunctionData(function_data));
if (!wasm::IsJSCompatibleSignature(sig_, module_, enabled_features_)) {
// Throw a TypeError. Use the js_context of the calling javascript
// function (passed as a parameter), such that the generated code is
// js_context independent.
BuildCallToRuntimeWithContext(Runtime::kWasmThrowTypeError, js_context,
nullptr, 0);
TerminateThrow(effect(), control());
return;
}
const int args_count = wasm_count + 1; // +1 for wasm_code.
// Check whether the signature of the function allows for a fast
// transformation. Create a fast transformation path, only if it does.
bool include_fast_path = QualifiesForFastTransform(sig_);
// Prepare Param() nodes. Param() nodes can only be created once,
// so we need to use the same nodes along all possible transformation paths.
base::SmallVector<Node*, 16> params(args_count);
for (int i = 0; i < wasm_count; ++i) params[i + 1] = Param(i + 1);
auto done = gasm_->MakeLabel(MachineRepresentation::kTagged);
if (include_fast_path) {
auto slow_path = gasm_->MakeDeferredLabel();
// Create a condition to determine the transformation path to be used
// on runtime.
Node* use_fast_path = gasm_->Int32Constant(1);
for (int i = 0; i < wasm_count; ++i) {
Node* can_transform_fast =
CanTransformFast(params[i + 1], sig_->GetParam(i));
use_fast_path = gasm_->Word32And(can_transform_fast, use_fast_path);
}
gasm_->GotoIfNot(use_fast_path, &slow_path);
// Convert JS parameters to wasm numbers using the fast transformation
// and build the call.
base::SmallVector<Node*, 16> args(args_count);
for (int i = 0; i < wasm_count; ++i) {
Node* wasm_param = FromJSFast(params[i + 1], sig_->GetParam(i));
args[i + 1] = wasm_param;
}
Node* jsval =
BuildCallAndReturn(is_import, js_context, function_data, args);
gasm_->Goto(&done, jsval);
gasm_->Bind(&slow_path);
}
// Convert JS parameters to wasm numbers using the default transformation
// and build the call.
base::SmallVector<Node*, 16> args(args_count);
for (int i = 0; i < wasm_count; ++i) {
Node* wasm_param = FromJS(params[i + 1], js_context, sig_->GetParam(i));
args[i + 1] = wasm_param;
}
Node* jsval =
BuildCallAndReturn(is_import, js_context, function_data, args);
// If both the default and a fast transformation paths are present,
// get the return value based on the path used.
if (include_fast_path) {
gasm_->Goto(&done, jsval);
gasm_->Bind(&done);
Return(done.PhiAt(0));
} else {
Return(jsval);
}
if (ContainsInt64(sig_)) LowerInt64(kCalledFromJS);
}