Faster JS-to-Wasm calls

This replaces https://chromium-review.googlesource.com/c/v8/v8/+/2376165/.

Currently JS-to-Wasm calls go through a wrapper/trampoline, built on
the basis of the signature of a Wasm function to call, and whose task
is to:
- set "thread_in_wasm_flag" to true
- convert the arguments from tagged types into Wasm native types
- calculate the address of the Wasm function to call and call it
- convert back the result from Wasm native types into tagged types
- reset "thread_in_wasm_flag" to false.

This CL tries to improve the performance of JS-to-Wasm calls by
inlining the code of the JS-to-Wasm wrappers in the call site.

It introduces a new IR operand, JSWasmCall, which replaces JSCall for
this kind of calls. A 'JSWasmCall' node is associated to
WasmCallParameters, which contain information about the signature of
the Wasm function to call.

WasmWrapperGraphBuilder::BuildJSToWasmWrapper is modified to avoid generating code to convert the types for the arguments
of the Wasm function, when the conversion is not necessary.
The actual inlining of the graph generated for this wrapper happens in
the simplified-lowering phase.

A new builtin, JSToWasmLazyDeoptContinuation, is introduced to manage
lazy deoptimizations that can happen if the Wasm function callee calls
back some JS code that invalidates the compiled JS caller function.

Bug: v8:11092
Change-Id: I3174c1c1f59b39107b333d1929ecc0584486b8ad
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2557538
Reviewed-by: Igor Sheludko <ishell@chromium.org>
Reviewed-by: Nico Hartmann <nicohartmann@chromium.org>
Reviewed-by: Georg Neis (ooo until January 5) <neis@chromium.org>
Reviewed-by: Tobias Tebbi <tebbi@chromium.org>
Reviewed-by: Maya Lekova <mslekova@chromium.org>
Reviewed-by: Andreas Haas <ahaas@chromium.org>
Commit-Queue: Paolo Severini <paolosev@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#71824}
This commit is contained in:
Paolo Severini 2020-12-16 05:22:32 -08:00 committed by Commit Bot
parent f475e99021
commit 860fcb1bd2
45 changed files with 2131 additions and 255 deletions

View File

@ -3424,6 +3424,7 @@ v8_source_set("v8_base_without_compiler") {
"src/wasm/streaming-decoder.h",
"src/wasm/struct-types.h",
"src/wasm/sync-streaming-decoder.cc",
"src/wasm/value-type.cc",
"src/wasm/value-type.h",
"src/wasm/wasm-arguments.h",
"src/wasm/wasm-code-manager.cc",

View File

@ -202,7 +202,7 @@ namespace internal {
TFC(I32PairToBigInt, I32PairToBigInt) \
\
/* Type conversions continuations */ \
TFC(ToBooleanLazyDeoptContinuation, TypeConversionStackParameter) \
TFC(ToBooleanLazyDeoptContinuation, SingleParameterOnStack) \
\
/* Handlers */ \
TFH(KeyedLoadIC_PolymorphicName, LoadWithVector) \
@ -817,6 +817,7 @@ namespace internal {
TFC(WasmI32AtomicWait32, WasmI32AtomicWait32) \
TFC(WasmI64AtomicWait32, WasmI64AtomicWait32) \
TFS(WasmAllocatePair, kValue1, kValue2) \
TFC(JSToWasmLazyDeoptContinuation, SingleParameterOnStack) \
\
/* WeakMap */ \
TFJ(WeakMapConstructor, kDontAdaptArgumentsSentinel) \

View File

@ -129,5 +129,20 @@ TF_BUILTIN(WasmAllocatePair, WasmBuiltinsAssembler) {
Return(result);
}
TF_BUILTIN(JSToWasmLazyDeoptContinuation, WasmBuiltinsAssembler) {
// Reset thread_in_wasm_flag.
TNode<ExternalReference> thread_in_wasm_flag_address_address =
ExternalConstant(
ExternalReference::thread_in_wasm_flag_address_address(isolate()));
auto thread_in_wasm_flag_address =
Load<RawPtrT>(thread_in_wasm_flag_address_address);
StoreNoWriteBarrier(MachineRepresentation::kWord32,
thread_in_wasm_flag_address, Int32Constant(0));
// Return the argument.
auto value = Parameter<Object>(Descriptor::kArgument);
Return(value);
}
} // namespace internal
} // namespace v8

View File

@ -485,6 +485,11 @@ ExternalReference::address_of_enable_experimental_regexp_engine() {
return ExternalReference(&FLAG_enable_experimental_regexp_engine);
}
ExternalReference ExternalReference::thread_in_wasm_flag_address_address(
Isolate* isolate) {
return ExternalReference(isolate->thread_in_wasm_flag_address_address());
}
ExternalReference ExternalReference::is_profiling_address(Isolate* isolate) {
return ExternalReference(isolate->is_profiling_address());
}

View File

@ -78,6 +78,8 @@ class StatsCounter;
V(address_of_regexp_stack_memory_top_address, \
"RegExpStack::memory_top_address_address()") \
V(address_of_static_offsets_vector, "OffsetsVector::static_offsets_vector") \
V(thread_in_wasm_flag_address_address, \
"Isolate::thread_in_wasm_flag_address_address") \
V(re_case_insensitive_compare_unicode, \
"NativeRegExpMacroAssembler::CaseInsensitiveCompareUnicode()") \
V(re_case_insensitive_compare_non_unicode, \

View File

@ -293,7 +293,7 @@ void TypeConversionNoContextDescriptor::InitializePlatformSpecific(
data->InitializePlatformSpecific(arraysize(registers), registers);
}
void TypeConversionStackParameterDescriptor::InitializePlatformSpecific(
void SingleParameterOnStackDescriptor::InitializePlatformSpecific(
CallInterfaceDescriptorData* data) {
data->InitializePlatformSpecific(0, nullptr);
}

View File

@ -81,6 +81,7 @@ namespace internal {
V(LoadWithVector) \
V(LoadWithReceiverAndVector) \
V(NoContext) \
V(SingleParameterOnStack) \
V(RecordWrite) \
V(ResumeGenerator) \
V(RunMicrotasks) \
@ -95,7 +96,6 @@ namespace internal {
V(StringSubstring) \
V(TypeConversion) \
V(TypeConversionNoContext) \
V(TypeConversionStackParameter) \
V(Typeof) \
V(UnaryOp_WithFeedback) \
V(Void) \
@ -935,13 +935,11 @@ class TypeConversionNoContextDescriptor final : public CallInterfaceDescriptor {
DECLARE_DESCRIPTOR(TypeConversionNoContextDescriptor, CallInterfaceDescriptor)
};
class TypeConversionStackParameterDescriptor final
: public CallInterfaceDescriptor {
class SingleParameterOnStackDescriptor final : public CallInterfaceDescriptor {
public:
DEFINE_PARAMETERS(kArgument)
DEFINE_PARAMETER_TYPES(MachineType::AnyTagged())
DECLARE_DESCRIPTOR(TypeConversionStackParameterDescriptor,
CallInterfaceDescriptor)
DECLARE_DESCRIPTOR(SingleParameterOnStackDescriptor, CallInterfaceDescriptor)
};
class AsyncFunctionStackParameterDescriptor final

View File

@ -1188,6 +1188,14 @@ void CodeGenerator::BuildTranslationForFrameStateDescriptor(
height);
break;
}
case FrameStateType::kJSToWasmBuiltinContinuation: {
const JSToWasmFrameStateDescriptor* js_to_wasm_descriptor =
static_cast<const JSToWasmFrameStateDescriptor*>(descriptor);
translation->BeginJSToWasmBuiltinContinuationFrame(
bailout_id, shared_info_id, height,
js_to_wasm_descriptor->return_type());
break;
}
case FrameStateType::kJavaScriptBuiltinContinuation: {
translation->BeginJavaScriptBuiltinContinuationFrame(
bailout_id, shared_info_id, height);

View File

@ -3339,6 +3339,15 @@ FrameStateDescriptor* GetFrameStateDescriptorInternal(Zone* zone, Node* state) {
outer_state = GetFrameStateDescriptorInternal(zone, outer_node);
}
if (state_info.type() == FrameStateType::kJSToWasmBuiltinContinuation) {
auto function_info = static_cast<const JSToWasmFrameStateFunctionInfo*>(
state_info.function_info());
return zone->New<JSToWasmFrameStateDescriptor>(
zone, state_info.type(), state_info.bailout_id(),
state_info.state_combine(), parameters, locals, stack,
state_info.shared_info(), outer_state, function_info->signature());
}
return zone->New<FrameStateDescriptor>(
zone, state_info.type(), state_info.bailout_id(),
state_info.state_combine(), parameters, locals, stack,

View File

@ -14,8 +14,10 @@
#include "src/compiler/graph.h"
#include "src/compiler/node.h"
#include "src/compiler/schedule.h"
#include "src/deoptimizer/deoptimizer.h"
#include "src/execution/frames.h"
#include "src/utils/ostreams.h"
#include "src/wasm/value-type.h"
namespace v8 {
namespace internal {
@ -1017,6 +1019,7 @@ size_t GetConservativeFrameSizeInBytes(FrameStateType type,
return info.frame_size_in_bytes();
}
case FrameStateType::kBuiltinContinuation:
case FrameStateType::kJSToWasmBuiltinContinuation:
case FrameStateType::kJavaScriptBuiltinContinuation:
case FrameStateType::kJavaScriptBuiltinContinuationWithCatch: {
const RegisterConfiguration* config = RegisterConfiguration::Default();
@ -1071,6 +1074,7 @@ size_t FrameStateDescriptor::GetHeight() const {
case FrameStateType::kInterpretedFunction:
return locals_count(); // The accumulator is *not* included.
case FrameStateType::kBuiltinContinuation:
case FrameStateType::kJSToWasmBuiltinContinuation:
// Custom, non-JS calling convention (that does not have a notion of
// a receiver or context).
return parameters_count();
@ -1122,6 +1126,17 @@ size_t FrameStateDescriptor::GetJSFrameCount() const {
return count;
}
JSToWasmFrameStateDescriptor::JSToWasmFrameStateDescriptor(
Zone* zone, FrameStateType type, BailoutId bailout_id,
OutputFrameStateCombine state_combine, size_t parameters_count,
size_t locals_count, size_t stack_count,
MaybeHandle<SharedFunctionInfo> shared_info,
FrameStateDescriptor* outer_state, const wasm::FunctionSig* wasm_signature)
: FrameStateDescriptor(zone, type, bailout_id, state_combine,
parameters_count, locals_count, stack_count,
shared_info, outer_state),
return_type_(wasm::WasmReturnTypeFromSignature(wasm_signature)) {}
std::ostream& operator<<(std::ostream& os, const RpoNumber& rpo) {
return os << rpo.ToSize();
}

View File

@ -1318,6 +1318,7 @@ class FrameStateDescriptor : public ZoneObject {
bool HasContext() const {
return FrameStateFunctionInfo::IsJSFunctionType(type_) ||
type_ == FrameStateType::kBuiltinContinuation ||
type_ == FrameStateType::kJSToWasmBuiltinContinuation ||
type_ == FrameStateType::kConstructStub;
}
@ -1357,6 +1358,25 @@ class FrameStateDescriptor : public ZoneObject {
FrameStateDescriptor* const outer_state_;
};
class JSToWasmFrameStateDescriptor : public FrameStateDescriptor {
public:
JSToWasmFrameStateDescriptor(Zone* zone, FrameStateType type,
BailoutId bailout_id,
OutputFrameStateCombine state_combine,
size_t parameters_count, size_t locals_count,
size_t stack_count,
MaybeHandle<SharedFunctionInfo> shared_info,
FrameStateDescriptor* outer_state,
const wasm::FunctionSig* wasm_signature);
base::Optional<wasm::ValueType::Kind> return_type() const {
return return_type_;
}
private:
base::Optional<wasm::ValueType::Kind> return_type_;
};
// A deoptimization entry is a pair of the reason why we deoptimize and the
// frame state descriptor that we have to go back to.
class DeoptimizationEntry final {

View File

@ -1620,9 +1620,15 @@ const Operator* CommonOperatorBuilder::ResizeMergeOrPhi(const Operator* op,
const FrameStateFunctionInfo*
CommonOperatorBuilder::CreateFrameStateFunctionInfo(
FrameStateType type, int parameter_count, int local_count,
Handle<SharedFunctionInfo> shared_info) {
return zone()->New<FrameStateFunctionInfo>(type, parameter_count, local_count,
shared_info);
Handle<SharedFunctionInfo> shared_info,
const wasm::FunctionSig* signature) {
DCHECK_EQ(type == FrameStateType::kJSToWasmBuiltinContinuation,
signature != nullptr);
return type == FrameStateType::kJSToWasmBuiltinContinuation
? zone()->New<JSToWasmFrameStateFunctionInfo>(
type, parameter_count, local_count, shared_info, signature)
: zone()->New<FrameStateFunctionInfo>(type, parameter_count,
local_count, shared_info);
}
const Operator* CommonOperatorBuilder::DeadValue(MachineRepresentation rep) {

View File

@ -560,7 +560,8 @@ class V8_EXPORT_PRIVATE CommonOperatorBuilder final
// Constructs function info for frame state construction.
const FrameStateFunctionInfo* CreateFrameStateFunctionInfo(
FrameStateType type, int parameter_count, int local_count,
Handle<SharedFunctionInfo> shared_info);
Handle<SharedFunctionInfo> shared_info,
const wasm::FunctionSig* signature = nullptr);
const Operator* MarkAsSafetyCheck(const Operator* op,
IsSafetyCheck safety_check);

View File

@ -11,6 +11,7 @@
#include "src/compiler/node.h"
#include "src/handles/handles-inl.h"
#include "src/objects/objects-inl.h"
#include "src/wasm/value-type.h"
namespace v8 {
namespace internal {
@ -65,6 +66,9 @@ std::ostream& operator<<(std::ostream& os, FrameStateType type) {
case FrameStateType::kBuiltinContinuation:
os << "BUILTIN_CONTINUATION_FRAME";
break;
case FrameStateType::kJSToWasmBuiltinContinuation:
os << "JS_TO_WASM_BUILTIN_CONTINUATION_FRAME";
break;
case FrameStateType::kJavaScriptBuiltinContinuation:
os << "JAVA_SCRIPT_BUILTIN_CONTINUATION_FRAME";
break;
@ -88,7 +92,7 @@ std::ostream& operator<<(std::ostream& os, FrameStateInfo const& info) {
namespace {
// Lazy deopt points where the frame state is assocated with a call get an
// Lazy deopt points where the frame state is associated with a call get an
// additional parameter for the return result from the call. The return result
// is added by the deoptimizer and not explicitly specified in the frame state.
// Lazy deopt points which can catch exceptions further get an additional
@ -110,7 +114,8 @@ FrameState CreateBuiltinContinuationFrameStateCommon(
JSGraph* jsgraph, FrameStateType frame_type, Builtins::Name name,
Node* closure, Node* context, Node** parameters, int parameter_count,
Node* outer_frame_state,
Handle<SharedFunctionInfo> shared = Handle<SharedFunctionInfo>()) {
Handle<SharedFunctionInfo> shared = Handle<SharedFunctionInfo>(),
const wasm::FunctionSig* signature = nullptr) {
Graph* const graph = jsgraph->graph();
CommonOperatorBuilder* const common = jsgraph->common();
@ -121,7 +126,7 @@ FrameState CreateBuiltinContinuationFrameStateCommon(
BailoutId bailout_id = Builtins::GetContinuationBailoutId(name);
const FrameStateFunctionInfo* state_info =
common->CreateFrameStateFunctionInfo(frame_type, parameter_count, 0,
shared);
shared, signature);
const Operator* op = common->FrameState(
bailout_id, OutputFrameStateCombine::Ignore(), state_info);
return FrameState(graph->NewNode(op, params_node, jsgraph->EmptyStateValues(),
@ -134,7 +139,7 @@ FrameState CreateBuiltinContinuationFrameStateCommon(
FrameState CreateStubBuiltinContinuationFrameState(
JSGraph* jsgraph, Builtins::Name name, Node* context,
Node* const* parameters, int parameter_count, Node* outer_frame_state,
ContinuationFrameStateMode mode) {
ContinuationFrameStateMode mode, const wasm::FunctionSig* signature) {
Callable callable = Builtins::CallableFor(jsgraph->isolate(), name);
CallInterfaceDescriptor descriptor = callable.descriptor();
@ -163,10 +168,29 @@ FrameState CreateStubBuiltinContinuationFrameState(
actual_parameters.push_back(parameters[i]);
}
FrameStateType frame_state_type = FrameStateType::kBuiltinContinuation;
if (name == Builtins::kJSToWasmLazyDeoptContinuation) {
CHECK_NOT_NULL(signature);
frame_state_type = FrameStateType::kJSToWasmBuiltinContinuation;
}
return CreateBuiltinContinuationFrameStateCommon(
jsgraph, FrameStateType::kBuiltinContinuation, name,
jsgraph->UndefinedConstant(), context, actual_parameters.data(),
static_cast<int>(actual_parameters.size()), outer_frame_state);
jsgraph, frame_state_type, name, jsgraph->UndefinedConstant(), context,
actual_parameters.data(), static_cast<int>(actual_parameters.size()),
outer_frame_state, Handle<SharedFunctionInfo>(), signature);
}
FrameState CreateJSWasmCallBuiltinContinuationFrameState(
JSGraph* jsgraph, Node* context, Node* outer_frame_state,
const wasm::FunctionSig* signature) {
base::Optional<wasm::ValueType::Kind> wasm_return_type =
wasm::WasmReturnTypeFromSignature(signature);
Node* node_return_type =
jsgraph->SmiConstant(wasm_return_type ? wasm_return_type.value() : -1);
Node* lazy_deopt_parameters[] = {node_return_type};
return CreateStubBuiltinContinuationFrameState(
jsgraph, Builtins::kJSToWasmLazyDeoptContinuation, context,
lazy_deopt_parameters, arraysize(lazy_deopt_parameters),
outer_frame_state, ContinuationFrameStateMode::LAZY, signature);
}
FrameState CreateJavaScriptBuiltinContinuationFrameState(

View File

@ -66,6 +66,8 @@ enum class FrameStateType {
kArgumentsAdaptor, // Represents an ArgumentsAdaptorFrame.
kConstructStub, // Represents a ConstructStubFrame.
kBuiltinContinuation, // Represents a continuation to a stub.
kJSToWasmBuiltinContinuation, // Represents a lazy deopt continuation for a
// JS to Wasm call.
kJavaScriptBuiltinContinuation, // Represents a continuation to a JavaScipt
// builtin.
kJavaScriptBuiltinContinuationWithCatch // Represents a continuation to a
@ -101,6 +103,22 @@ class FrameStateFunctionInfo {
Handle<SharedFunctionInfo> const shared_info_;
};
class JSToWasmFrameStateFunctionInfo : public FrameStateFunctionInfo {
public:
JSToWasmFrameStateFunctionInfo(FrameStateType type, int parameter_count,
int local_count,
Handle<SharedFunctionInfo> shared_info,
const wasm::FunctionSig* signature)
: FrameStateFunctionInfo(type, parameter_count, local_count, shared_info),
signature_(signature) {
DCHECK_NOT_NULL(signature);
}
const wasm::FunctionSig* signature() const { return signature_; }
private:
const wasm::FunctionSig* const signature_;
};
class FrameStateInfo final {
public:
@ -154,7 +172,12 @@ enum class ContinuationFrameStateMode { EAGER, LAZY, LAZY_WITH_CATCH };
FrameState CreateStubBuiltinContinuationFrameState(
JSGraph* graph, Builtins::Name name, Node* context, Node* const* parameters,
int parameter_count, Node* outer_frame_state,
ContinuationFrameStateMode mode);
ContinuationFrameStateMode mode,
const wasm::FunctionSig* signature = nullptr);
FrameState CreateJSWasmCallBuiltinContinuationFrameState(
JSGraph* jsgraph, Node* context, Node* outer_frame_state,
const wasm::FunctionSig* signature);
FrameState CreateJavaScriptBuiltinContinuationFrameState(
JSGraph* graph, const SharedFunctionInfoRef& shared, Builtins::Name name,

View File

@ -230,6 +230,8 @@ std::unique_ptr<char[]> GetVisualizerLogFileName(OptimizedCompilationInfo* info,
}
std::replace(filename.begin(), filename.begin() + filename.length(), ' ',
'_');
std::replace(filename.begin(), filename.begin() + filename.length(), ':',
'-');
EmbeddedVector<char, 256> base_dir;
if (optional_base_dir != nullptr) {

View File

@ -31,6 +31,13 @@ class JSRegExp;
class JSTypedArray;
class NativeContext;
class ScriptContextTable;
template <typename>
class Signature;
namespace wasm {
class ValueType;
struct WasmModule;
} // namespace wasm
namespace compiler {
@ -787,20 +794,22 @@ class ScopeInfoRef : public HeapObjectRef {
void SerializeScopeInfoChain();
};
#define BROKER_SFI_FIELDS(V) \
V(int, internal_formal_parameter_count) \
V(bool, has_duplicate_parameters) \
V(int, function_map_index) \
V(FunctionKind, kind) \
V(LanguageMode, language_mode) \
V(bool, native) \
V(bool, HasBreakInfo) \
V(bool, HasBuiltinId) \
V(bool, construct_as_builtin) \
V(bool, HasBytecodeArray) \
V(int, StartPosition) \
V(bool, is_compiled) \
V(bool, IsUserJavaScript)
#define BROKER_SFI_FIELDS(V) \
V(int, internal_formal_parameter_count) \
V(bool, has_duplicate_parameters) \
V(int, function_map_index) \
V(FunctionKind, kind) \
V(LanguageMode, language_mode) \
V(bool, native) \
V(bool, HasBreakInfo) \
V(bool, HasBuiltinId) \
V(bool, construct_as_builtin) \
V(bool, HasBytecodeArray) \
V(int, StartPosition) \
V(bool, is_compiled) \
V(bool, IsUserJavaScript) \
V(const wasm::WasmModule*, wasm_module) \
V(const wasm::FunctionSig*, wasm_function_signature)
class V8_EXPORT_PRIVATE SharedFunctionInfoRef : public HeapObjectRef {
public:

View File

@ -3429,6 +3429,82 @@ Reduction JSCallReducer::ReduceArraySome(Node* node,
return ReplaceWithSubgraph(&a, subgraph);
}
namespace {
bool CanInlineJSToWasmCall(const wasm::FunctionSig* wasm_signature) {
if (!FLAG_turbo_inline_js_wasm_calls || wasm_signature->return_count() > 1) {
return false;
}
for (auto type : wasm_signature->all()) {
#if defined(V8_TARGET_ARCH_32_BIT)
if (type == wasm::kWasmI64) return false;
#endif
if (type != wasm::kWasmI32 && type != wasm::kWasmI64 &&
type != wasm::kWasmF32 && type != wasm::kWasmF64) {
return false;
}
}
return true;
}
} // namespace
Reduction JSCallReducer::ReduceCallWasmFunction(
Node* node, const SharedFunctionInfoRef& shared) {
JSCallNode n(node);
const CallParameters& p = n.Parameters();
// Avoid deoptimization loops
if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) {
return NoChange();
}
// Don't inline if we are in a try/catch.
// WasmWrapperGraphBuilder::BuildJSToWasmWrapper generates Call nodes that are
// not part of the control chain and this breaks the inlining of IfSuccess
// nodes.
if (NodeProperties::IsExceptionalCall(node)) {
return NoChange();
}
const wasm::FunctionSig* wasm_signature = shared.wasm_function_signature();
if (!CanInlineJSToWasmCall(wasm_signature)) {
return NoChange();
}
const wasm::WasmModule* wasm_module = shared.wasm_module();
const Operator* op =
javascript()->CallWasm(wasm_module, wasm_signature, p.feedback());
// Remove additional inputs
size_t actual_arity = n.ArgumentCount();
DCHECK(JSCallNode::kFeedbackVectorIsLastInput);
DCHECK_EQ(actual_arity + JSWasmCallNode::kExtraInputCount - 1,
n.FeedbackVectorIndex());
size_t expected_arity = wasm_signature->parameter_count();
while (actual_arity > expected_arity) {
int removal_index =
static_cast<int>(n.FirstArgumentIndex() + expected_arity);
DCHECK_LT(removal_index, static_cast<int>(node->InputCount()));
node->RemoveInput(removal_index);
actual_arity--;
}
// Add missing inputs
while (actual_arity < expected_arity) {
int insertion_index = n.ArgumentIndex(n.ArgumentCount());
node->InsertInput(graph()->zone(), insertion_index,
jsgraph()->UndefinedConstant());
actual_arity++;
}
NodeProperties::ChangeOp(node, op);
return Changed(node);
}
#ifndef V8_ENABLE_FP_PARAMS_IN_C_LINKAGE
namespace {
bool HasFPParamsInSignature(const CFunctionInfo* c_signature) {
@ -4533,6 +4609,11 @@ Reduction JSCallReducer::ReduceJSCall(Node* node,
if (shared.function_template_info().has_value()) {
return ReduceCallApiFunction(node, shared);
}
if (shared.wasm_function_signature()) {
return ReduceCallWasmFunction(node, shared);
}
return NoChange();
}

View File

@ -73,6 +73,8 @@ class V8_EXPORT_PRIVATE JSCallReducer final : public AdvancedReducer {
Reduction ReduceBooleanConstructor(Node* node);
Reduction ReduceCallApiFunction(Node* node,
const SharedFunctionInfoRef& shared);
Reduction ReduceCallWasmFunction(Node* node,
const SharedFunctionInfoRef& shared);
Reduction ReduceFunctionPrototypeApply(Node* node);
Reduction ReduceFunctionPrototypeBind(Node* node);
Reduction ReduceFunctionPrototypeCall(Node* node);

View File

@ -1251,6 +1251,9 @@ void JSGenericLowering::LowerJSCallRuntime(Node* node) {
ReplaceWithRuntimeCall(node, p.id(), static_cast<int>(p.arity()));
}
// Will be lowered in SimplifiedLowering.
void JSGenericLowering::LowerJSWasmCall(Node* node) {}
void JSGenericLowering::LowerJSForInPrepare(Node* node) {
JSForInPrepareNode n(node);
Effect effect(node); // {node} is kept in the effect chain.

View File

@ -676,6 +676,50 @@ ForInParameters const& ForInParametersOf(const Operator* op) {
return OpParameter<ForInParameters>(op);
}
JSWasmCallParameters const& JSWasmCallParametersOf(const Operator* op) {
DCHECK_EQ(IrOpcode::kJSWasmCall, op->opcode());
return OpParameter<JSWasmCallParameters>(op);
}
std::ostream& operator<<(std::ostream& os, JSWasmCallParameters const& p) {
return os << p.module() << ", " << p.signature() << ", " << p.feedback();
}
size_t hash_value(JSWasmCallParameters const& p) {
return base::hash_combine(p.module(), p.signature(),
FeedbackSource::Hash()(p.feedback()));
}
bool operator==(JSWasmCallParameters const& lhs,
JSWasmCallParameters const& rhs) {
return lhs.module() == rhs.module() && lhs.signature() == rhs.signature() &&
lhs.feedback() == rhs.feedback();
}
int JSWasmCallParameters::arity_without_implicit_args() const {
return static_cast<int>(signature_->parameter_count());
}
int JSWasmCallParameters::input_count() const {
return static_cast<int>(signature_->parameter_count()) +
JSWasmCallNode::kExtraInputCount;
}
// static
Type JSWasmCallNode::TypeForWasmReturnType(const wasm::ValueType& type) {
switch (type.kind()) {
case wasm::ValueType::kI32:
return Type::Signed32();
case wasm::ValueType::kI64:
return Type::BigInt();
case wasm::ValueType::kF32:
case wasm::ValueType::kF64:
return Type::Number();
default:
UNREACHABLE();
}
}
#define CACHED_OP_LIST(V) \
V(ToLength, Operator::kNoProperties, 1, 1) \
V(ToName, Operator::kNoProperties, 1, 1) \
@ -874,6 +918,17 @@ const Operator* JSOperatorBuilder::CallRuntime(const Runtime::Function* f,
parameters); // parameter
}
const Operator* JSOperatorBuilder::CallWasm(
const wasm::WasmModule* wasm_module,
const wasm::FunctionSig* wasm_signature, FeedbackSource const& feedback) {
JSWasmCallParameters parameters(wasm_module, wasm_signature, feedback);
return zone()->New<Operator1<JSWasmCallParameters>>(
IrOpcode::kJSWasmCall, Operator::kNoProperties, // opcode
"JSWasmCall", // name
parameters.input_count(), 1, 1, 1, 1, 2, // inputs/outputs
parameters); // parameter
}
const Operator* JSOperatorBuilder::ConstructForwardVarargs(
size_t arity, uint32_t start_index) {
ConstructForwardVarargsParameters parameters(arity, start_index);

View File

@ -25,6 +25,10 @@ class ArrayBoilerplateDescription;
class FeedbackCell;
class SharedFunctionInfo;
namespace wasm {
class ValueType;
}
namespace compiler {
// Forward declarations.
@ -816,6 +820,35 @@ size_t hash_value(ForInParameters const&);
std::ostream& operator<<(std::ostream&, ForInParameters const&);
const ForInParameters& ForInParametersOf(const Operator* op);
class JSWasmCallParameters {
public:
explicit JSWasmCallParameters(const wasm::WasmModule* module,
const wasm::FunctionSig* signature,
FeedbackSource const& feedback)
: module_(module), signature_(signature), feedback_(feedback) {
DCHECK_NOT_NULL(module);
DCHECK_NOT_NULL(signature);
}
const wasm::WasmModule* module() const { return module_; }
const wasm::FunctionSig* signature() const { return signature_; }
FeedbackSource const& feedback() const { return feedback_; }
int input_count() const;
int arity_without_implicit_args() const;
private:
const wasm::WasmModule* const module_;
const wasm::FunctionSig* const signature_;
const FeedbackSource feedback_;
};
JSWasmCallParameters const& JSWasmCallParametersOf(const Operator* op)
V8_WARN_UNUSED_RESULT;
V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream&,
JSWasmCallParameters const&);
size_t hash_value(JSWasmCallParameters const&);
bool operator==(JSWasmCallParameters const&, JSWasmCallParameters const&);
int RegisterCountOf(Operator const* op) V8_WARN_UNUSED_RESULT;
int GeneratorStoreValueCountOf(const Operator* op) V8_WARN_UNUSED_RESULT;
@ -925,6 +958,10 @@ class V8_EXPORT_PRIVATE JSOperatorBuilder final
const Operator* CallRuntime(Runtime::FunctionId id, size_t arity);
const Operator* CallRuntime(const Runtime::Function* function, size_t arity);
const Operator* CallWasm(const wasm::WasmModule* wasm_module,
const wasm::FunctionSig* wasm_signature,
FeedbackSource const& feedback);
const Operator* ConstructForwardVarargs(size_t arity, uint32_t start_index);
const Operator* Construct(uint32_t arity,
CallFrequency const& frequency = CallFrequency(),
@ -1247,7 +1284,8 @@ class JSCallOrConstructNode : public JSNodeWrapperBase {
node->opcode() == IrOpcode::kJSCallWithSpread ||
node->opcode() == IrOpcode::kJSConstruct ||
node->opcode() == IrOpcode::kJSConstructWithArrayLike ||
node->opcode() == IrOpcode::kJSConstructWithSpread);
node->opcode() == IrOpcode::kJSConstructWithSpread ||
node->opcode() == IrOpcode::kJSWasmCall);
}
#define INPUTS(V) \
@ -1259,8 +1297,8 @@ class JSCallOrConstructNode : public JSNodeWrapperBase {
// Besides actual arguments, JSCall nodes (and variants) also take the
// following. Note that we rely on the fact that all variants (JSCall,
// JSCallWithArrayLike, JSCallWithSpread, JSConstruct,
// JSConstructWithArrayLike, JSConstructWithSpread) have the same underlying
// node layout.
// JSConstructWithArrayLike, JSConstructWithSpread, JSWasmCall) have the same
// underlying node layout.
static constexpr int kTargetInputCount = 1;
static constexpr int kReceiverOrNewTargetInputCount = 1;
static constexpr int kFeedbackVectorInputCount = 1;
@ -1355,6 +1393,35 @@ using JSCallNode = JSCallNodeBase<IrOpcode::kJSCall>;
using JSCallWithSpreadNode = JSCallNodeBase<IrOpcode::kJSCallWithSpread>;
using JSCallWithArrayLikeNode = JSCallNodeBase<IrOpcode::kJSCallWithArrayLike>;
class JSWasmCallNode final : public JSCallOrConstructNode {
public:
explicit constexpr JSWasmCallNode(Node* node) : JSCallOrConstructNode(node) {
CONSTEXPR_DCHECK(node->opcode() == IrOpcode::kJSWasmCall);
}
const JSWasmCallParameters& Parameters() const {
return OpParameter<JSWasmCallParameters>(node()->op());
}
#define INPUTS(V) \
V(Target, target, 0, Object) \
V(Receiver, receiver, 1, Object)
INPUTS(DEFINE_INPUT_ACCESSORS)
#undef INPUTS
static constexpr int kReceiverInputCount = 1;
STATIC_ASSERT(kReceiverInputCount ==
JSCallOrConstructNode::kReceiverOrNewTargetInputCount);
int ArgumentCount() const override {
// Note: The count reported by this function depends only on the parameter
// count, thus adding/removing inputs will not affect it.
return Parameters().arity_without_implicit_args();
}
static Type TypeForWasmReturnType(const wasm::ValueType& type);
};
template <int kOpcode>
class JSConstructNodeBase final : public JSCallOrConstructNode {
public:

View File

@ -196,7 +196,8 @@
V(JSCall) \
V(JSCallForwardVarargs) \
V(JSCallWithArrayLike) \
V(JSCallWithSpread)
V(JSCallWithSpread) \
V(JSWasmCall)
#define JS_CONSTRUCT_OP_LIST(V) \
V(JSConstructForwardVarargs) \

View File

@ -223,6 +223,7 @@ bool OperatorProperties::HasFrameStateInput(const Operator* op) {
case IrOpcode::kJSCall:
case IrOpcode::kJSCallWithArrayLike:
case IrOpcode::kJSCallWithSpread:
case IrOpcode::kJSWasmCall:
// Misc operations
case IrOpcode::kJSAsyncFunctionEnter:

View File

@ -25,9 +25,11 @@
#include "src/compiler/representation-change.h"
#include "src/compiler/simplified-operator.h"
#include "src/compiler/type-cache.h"
#include "src/compiler/wasm-compiler.h"
#include "src/numbers/conversions-inl.h"
#include "src/objects/objects.h"
#include "src/utils/address-map.h"
#include "src/wasm/value-type.h"
namespace v8 {
namespace internal {
@ -1807,6 +1809,120 @@ class RepresentationSelector {
SetOutput<T>(node, return_type.representation());
}
static MachineType MachineTypeForWasmReturnType(wasm::ValueType type) {
switch (type.kind()) {
case wasm::ValueType::kI32:
return MachineType::Int32();
case wasm::ValueType::kF32:
return MachineType::Float32();
case wasm::ValueType::kF64:
return MachineType::Float64();
case wasm::ValueType::kI64:
// Not used for i64, see VisitJSWasmCall().
default:
UNREACHABLE();
}
}
UseInfo UseInfoForJSWasmCallArgument(Node* input, wasm::ValueType type,
FeedbackSource const& feedback) {
// If the input type is a Number or Oddball, we can directly convert the
// input into the Wasm native type of the argument. If not, we return
// UseInfo::AnyTagged to signal that WasmWrapperGraphBuilder will need to
// add Nodes to perform the conversion (in WasmWrapperGraphBuilder::FromJS).
switch (type.kind()) {
case wasm::ValueType::kI32:
return UseInfo::CheckedNumberOrOddballAsWord32(feedback);
case wasm::ValueType::kI64:
return UseInfo::AnyTagged();
case wasm::ValueType::kF32:
case wasm::ValueType::kF64:
// For Float32, TruncateFloat64ToFloat32 will be inserted later in
// WasmWrapperGraphBuilder::BuildJSToWasmWrapper.
return UseInfo::CheckedNumberOrOddballAsFloat64(kDistinguishZeros,
feedback);
default:
UNREACHABLE();
}
}
template <Phase T>
void VisitJSWasmCall(Node* node, SimplifiedLowering* lowering) {
DCHECK_EQ(JSWasmCallNode::TargetIndex(), 0);
DCHECK_EQ(JSWasmCallNode::ReceiverIndex(), 1);
DCHECK_EQ(JSWasmCallNode::FirstArgumentIndex(), 2);
JSWasmCallNode n(node);
JSWasmCallParameters const& params = n.Parameters();
const wasm::FunctionSig* wasm_signature = params.signature();
int wasm_arg_count = static_cast<int>(wasm_signature->parameter_count());
DCHECK_EQ(wasm_arg_count, n.ArgumentCount());
base::SmallVector<UseInfo, kInitialArgumentsCount> arg_use_info(
wasm_arg_count);
// Visit JSFunction and Receiver nodes.
ProcessInput<T>(node, JSWasmCallNode::TargetIndex(), UseInfo::Any());
ProcessInput<T>(node, JSWasmCallNode::ReceiverIndex(), UseInfo::Any());
JSWasmCallData js_wasm_call_data;
// Propagate representation information from TypeInfo.
for (int i = 0; i < wasm_arg_count; i++) {
TNode<Object> input = n.Argument(i);
DCHECK_NOT_NULL(input);
wasm::ValueType value = wasm_signature->GetParam(i);
arg_use_info[i] =
UseInfoForJSWasmCallArgument(input, value, params.feedback());
bool need_type_conversion =
arg_use_info[i].representation() == MachineRepresentation::kTagged;
// UseInfo should be AnyTagged
DCHECK(!need_type_conversion ||
(arg_use_info[i].truncation() == Truncation::Any() &&
arg_use_info[i].type_check() == TypeCheckKind::kNone));
js_wasm_call_data.set_arg_needs_conversion(i, need_type_conversion);
ProcessInput<T>(node, JSWasmCallNode::ArgumentIndex(i), arg_use_info[i]);
}
// Visit value, context and frame state inputs as tagged.
int first_effect_index = NodeProperties::FirstEffectIndex(node);
DCHECK(first_effect_index >
JSWasmCallNode::FirstArgumentIndex() + wasm_arg_count);
for (int i = JSWasmCallNode::FirstArgumentIndex() + wasm_arg_count;
i < first_effect_index; i++) {
ProcessInput<T>(node, i, UseInfo::AnyTagged());
}
// Effect and Control.
ProcessRemainingInputs<T>(node, NodeProperties::FirstEffectIndex(node));
if (wasm_signature->return_count() == 1) {
if (wasm_signature->GetReturn().kind() == wasm::ValueType::kI64) {
// Conversion between negative int64 and BigInt not supported yet.
// Do not bypass the type conversion when the result type is i64.
js_wasm_call_data.set_result_needs_conversion(true);
SetOutput<T>(node, MachineRepresentation::kTagged);
} else {
MachineType return_type =
MachineTypeForWasmReturnType(wasm_signature->GetReturn());
SetOutput<T>(
node, return_type.representation(),
JSWasmCallNode::TypeForWasmReturnType(wasm_signature->GetReturn()));
}
} else {
DCHECK_EQ(wasm_signature->return_count(), 0);
SetOutput<T>(node, MachineRepresentation::kTagged);
}
if (lower<T>()) {
LowerJSWasmCall(jsgraph_, node, js_wasm_call_data);
}
}
// Dispatching routine for visiting the node {node} with the usage {use}.
// Depending on the operator, propagate new usage info to the inputs.
template <Phase T>
@ -3732,7 +3848,8 @@ class RepresentationSelector {
case IrOpcode::kArgumentsLengthState:
case IrOpcode::kUnreachable:
case IrOpcode::kRuntimeAbort:
// All JavaScript operators except JSToNumber have uniform handling.
// All JavaScript operators except JSToNumber, JSToNumberConvertBigInt,
// kJSToNumeric and JSWasmCall have uniform handling.
#define OPCODE_CASE(name, ...) case IrOpcode::k##name:
JS_SIMPLE_BINOP_LIST(OPCODE_CASE)
JS_OBJECT_OP_LIST(OPCODE_CASE)
@ -3748,6 +3865,9 @@ class RepresentationSelector {
case IrOpcode::kJSToObject:
case IrOpcode::kJSToString:
case IrOpcode::kJSParseInt:
if (node->opcode() == IrOpcode::kJSWasmCall) {
return VisitJSWasmCall<T>(node, lowering);
}
VisitInputs<T>(node);
// Assume the output is tagged.
return SetOutput<T>(node, MachineRepresentation::kTagged);
@ -3809,6 +3929,12 @@ class RepresentationSelector {
node->NullAllInputs(); // The {node} is now dead.
}
// Used for kJSWasmCall
void InlineWasmCall(Node* call, const wasm::FunctionSig* sig, Node* start,
Node* end);
void LowerJSWasmCall(JSGraph* jsgraph, Node* node,
const JSWasmCallData& js_wasm_call_data);
private:
JSGraph* jsgraph_;
Zone* zone_; // Temporary zone.
@ -3850,6 +3976,154 @@ class RepresentationSelector {
Linkage* linkage() { return linkage_; }
};
void RepresentationSelector::InlineWasmCall(Node* call,
const wasm::FunctionSig* sig,
Node* start, Node* end) {
DCHECK(FLAG_turbo_inline_js_wasm_calls);
DCHECK_NOT_NULL(start);
DCHECK_NOT_NULL(end);
Node* context = NodeProperties::GetContextInput(call);
Node* frame_state = NodeProperties::GetFrameStateInput(call);
// The scheduler is smart enough to place our code; we just ensure {control}
// becomes the control input of the start of the inlinee, and {effect} becomes
// the effect input of the start of the inlinee.
Node* control = NodeProperties::GetControlInput(call);
Node* effect = NodeProperties::GetEffectInput(call);
DCHECK_GE(start->op()->ValueOutputCount(), 3);
int const inlinee_new_target_index =
static_cast<int>(start->op()->ValueOutputCount()) - 3;
int const inlinee_arity_index =
static_cast<int>(start->op()->ValueOutputCount()) - 2;
int const inlinee_context_index =
static_cast<int>(start->op()->ValueOutputCount()) - 1;
// Counts the target, receiver/new_target, and arguments; but
// not feedback vector, context, effect, frame state or control.
DCHECK_EQ(static_cast<int>(sig->parameter_count()) +
JSCallOrConstructNode::kExtraInputCount -
JSCallOrConstructNode::kFeedbackVectorInputCount,
inlinee_new_target_index);
// Iterate over all uses of the start node.
for (Edge edge : start->use_edges()) {
Node* use = edge.from();
if (use->opcode() == IrOpcode::kParameter) {
int index = 1 + ParameterIndexOf(use->op());
DCHECK_LE(index, inlinee_context_index);
if (index < inlinee_new_target_index) {
// There is an input from the call, and the index is a value
// projection but not the context, so rewire the input.
DeferReplacement(use, call->InputAt(index));
} else if (index == inlinee_new_target_index) {
// The projection is requesting the new target value.
// This shouldn't happen, the call to a JSToWasm wrapper doesn't have
// a receiver or new_target.
UNREACHABLE();
} else if (index == inlinee_arity_index) {
// The projection is requesting the number of arguments.
// This should not happen in a JSToWasm subgraph
UNREACHABLE();
} else if (index == inlinee_context_index) {
// The projection is requesting the inlinee function context.
DeferReplacement(use, context);
} else {
// A JSWasmCall should have all the required arguments.
UNREACHABLE();
}
} else {
if (NodeProperties::IsEffectEdge(edge)) {
edge.UpdateTo(effect);
} else if (NodeProperties::IsControlEdge(edge)) {
edge.UpdateTo(control);
} else if (NodeProperties::IsFrameStateEdge(edge)) {
edge.UpdateTo(frame_state);
} else {
UNREACHABLE();
}
}
}
using NodeVector = ZoneVector<Node*>;
Zone* const local_zone = graph()->zone();
NodeVector values(local_zone);
NodeVector effects(local_zone);
NodeVector controls(local_zone);
for (Node* const input : end->inputs()) {
switch (input->opcode()) {
case IrOpcode::kReturn:
values.push_back(NodeProperties::GetValueInput(input, 1));
effects.push_back(NodeProperties::GetEffectInput(input));
controls.push_back(NodeProperties::GetControlInput(input));
break;
case IrOpcode::kDeoptimize:
case IrOpcode::kTerminate:
case IrOpcode::kThrow:
NodeProperties::MergeControlToEnd(graph(), common(), input);
break;
default:
UNREACHABLE();
break;
}
}
DCHECK_EQ(values.size(), effects.size());
DCHECK_EQ(values.size(), controls.size());
// The JSWasmCall always produces one value, Undefined if the Wasm function
// has no return value.
DCHECK_GT(values.size(), 0);
int const input_count = static_cast<int>(controls.size());
Node* control_output = graph()->NewNode(common()->Merge(input_count),
input_count, &controls.front());
values.push_back(control_output);
effects.push_back(control_output);
Node* value_output = graph()->NewNode(
common()->Phi(MachineRepresentation::kTagged, input_count),
static_cast<int>(values.size()), &values.front());
Node* effect_output =
graph()->NewNode(common()->EffectPhi(input_count),
static_cast<int>(effects.size()), &effects.front());
ReplaceEffectControlUses(call, effect_output, control_output);
DeferReplacement(call, value_output);
}
void RepresentationSelector::LowerJSWasmCall(
JSGraph* jsgraph, Node* node, const JSWasmCallData& js_wasm_call_data) {
DCHECK(FLAG_turbo_inline_js_wasm_calls);
Graph* graph = this->graph();
JSWasmCallNode n(node);
const JSWasmCallParameters& wasm_call_params = n.Parameters();
// Create a nested frame state inside the frame state attached to the call;
// this will ensure that lazy deoptimizations at this point will still return
// the result of the Wasm function call.
Node* continuation_frame_state =
CreateJSWasmCallBuiltinContinuationFrameState(
jsgraph, n.context(), n.frame_state(), wasm_call_params.signature());
Node* start;
Node* end;
{
Graph::SubgraphScope scope(graph);
graph->SetEnd(nullptr);
BuildInlinedJSToWasmWrapper(
graph->zone(), jsgraph, wasm_call_params.signature(),
wasm_call_params.module(), source_positions_,
StubCallMode::kCallBuiltinPointer, wasm::WasmFeatures::FromFlags(),
&js_wasm_call_data, continuation_frame_state);
// Extract the inlinee start/end nodes.
start = graph->start();
end = graph->end();
}
InlineWasmCall(node, wasm_call_params.signature(), start, end);
}
// Template specializations
// Enqueue {use_node}'s {index} input if the {use_info} contains new information

View File

@ -992,6 +992,15 @@ Type Typer::Visitor::TypeCall(Node* node) { return Type::Any(); }
Type Typer::Visitor::TypeFastApiCall(Node* node) { return Type::Any(); }
Type Typer::Visitor::TypeJSWasmCall(Node* node) {
const JSWasmCallParameters& op_params = JSWasmCallParametersOf(node->op());
const wasm::FunctionSig* wasm_signature = op_params.signature();
if (wasm_signature->return_count() > 0) {
return JSWasmCallNode::TypeForWasmReturnType(wasm_signature->GetReturn());
}
return Type::Any();
}
Type Typer::Visitor::TypeProjection(Node* node) {
Type const type = Operand(node, 0);
if (type.Is(Type::None())) return Type::None();

View File

@ -1627,6 +1627,11 @@ void Verifier::Visitor::Check(Node* node, const AllNodes& all) {
CheckValueInputIs(node, 0, Type::ExternalPointer()); // callee
CheckValueInputIs(node, 1, Type::Any()); // receiver
break;
case IrOpcode::kJSWasmCall:
CHECK_GE(value_count, 3);
CheckTypeIs(node, Type::Any());
CheckValueInputIs(node, 0, Type::Any()); // callee
break;
// Machine operators
// -----------------------

View File

@ -164,15 +164,17 @@ bool ContainsInt64(const wasm::FunctionSig* sig) {
template <typename BuiltinDescriptor>
CallDescriptor* GetBuiltinCallDescriptor(WasmGraphBuilder* builder,
StubCallMode stub_mode) {
StubCallMode stub_mode,
bool needs_frame_state = false) {
BuiltinDescriptor interface_descriptor;
return Linkage::GetStubCallDescriptor(
builder->mcgraph()->zone(), // zone
interface_descriptor, // descriptor
interface_descriptor.GetStackParameterCount(), // stack parameter count
CallDescriptor::kNoFlags, // flags
Operator::kNoProperties, // properties
stub_mode); // stub call mode
needs_frame_state ? CallDescriptor::kNeedsFrameState
: CallDescriptor::kNoFlags, // flags
Operator::kNoProperties, // properties
stub_mode); // stub call mode
}
} // namespace
@ -2801,15 +2803,17 @@ Node* WasmGraphBuilder::BuildCCall(MachineSignature* sig, Node* function,
Node* WasmGraphBuilder::BuildCallNode(const wasm::FunctionSig* sig,
Vector<Node*> args,
wasm::WasmCodePosition position,
Node* instance_node, const Operator* op) {
Node* instance_node, const Operator* op,
Node* frame_state) {
if (instance_node == nullptr) {
DCHECK_NOT_NULL(instance_node_);
instance_node = instance_node_.get();
}
needs_stack_check_ = true;
const size_t params = sig->parameter_count();
const size_t has_frame_state = frame_state != nullptr ? 1 : 0;
const size_t extra = 3; // instance_node, effect, and control.
const size_t count = 1 + params + extra;
const size_t count = 1 + params + extra + has_frame_state;
// Reallocate the buffer to make space for extra inputs.
base::SmallVector<Node*, 16 + extra> inputs(count);
@ -2821,8 +2825,9 @@ Node* WasmGraphBuilder::BuildCallNode(const wasm::FunctionSig* sig,
if (params > 0) base::Memcpy(&inputs[2], &args[1], params * sizeof(Node*));
// Add effect and control inputs.
inputs[params + 2] = effect();
inputs[params + 3] = control();
if (has_frame_state != 0) inputs[params + 2] = frame_state;
inputs[params + has_frame_state + 2] = effect();
inputs[params + has_frame_state + 3] = control();
Node* call = graph()->NewNode(op, static_cast<int>(count), inputs.begin());
// Return calls have no effect output. Other calls are the new effect node.
@ -2837,11 +2842,14 @@ Node* WasmGraphBuilder::BuildWasmCall(const wasm::FunctionSig* sig,
Vector<Node*> args, Vector<Node*> rets,
wasm::WasmCodePosition position,
Node* instance_node,
UseRetpoline use_retpoline) {
UseRetpoline use_retpoline,
Node* frame_state) {
CallDescriptor* call_descriptor =
GetWasmCallDescriptor(mcgraph()->zone(), sig, use_retpoline);
GetWasmCallDescriptor(mcgraph()->zone(), sig, use_retpoline,
kWasmFunction, frame_state != nullptr);
const Operator* op = mcgraph()->common()->Call(call_descriptor);
Node* call = BuildCallNode(sig, args, position, instance_node, op);
Node* call =
BuildCallNode(sig, args, position, instance_node, op, frame_state);
size_t ret_count = sig->return_count();
if (ret_count == 0) return call; // No return value.
@ -6059,11 +6067,11 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
return i64_to_bigint_descriptor_;
}
CallDescriptor* GetBigIntToI64CallDescriptor() {
CallDescriptor* GetBigIntToI64CallDescriptor(bool needs_frame_state) {
if (bigint_to_i64_descriptor_) return bigint_to_i64_descriptor_;
bigint_to_i64_descriptor_ =
GetBuiltinCallDescriptor<BigIntToI64Descriptor>(this, stub_mode_);
bigint_to_i64_descriptor_ = GetBuiltinCallDescriptor<BigIntToI64Descriptor>(
this, stub_mode_, needs_frame_state);
AddInt64LoweringReplacement(
bigint_to_i64_descriptor_,
@ -6130,7 +6138,8 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
return done.PhiAt(0);
}
Node* BuildChangeTaggedToInt32(Node* value, Node* context) {
Node* BuildChangeTaggedToInt32(Node* value, Node* context,
Node* frame_state) {
// We expect most integers at runtime to be Smis, so it is important for
// wrapper performance that Smi conversion be inlined.
auto builtin = gasm_->MakeDeferredLabel();
@ -6151,11 +6160,16 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
if (!tagged_non_smi_to_int32_operator_.is_set()) {
auto call_descriptor = Linkage::GetStubCallDescriptor(
mcgraph()->zone(), WasmTaggedNonSmiToInt32Descriptor(), 0,
CallDescriptor::kNoFlags, Operator::kNoProperties, stub_mode_);
frame_state ? CallDescriptor::kNeedsFrameState
: CallDescriptor::kNoFlags,
Operator::kNoProperties, stub_mode_);
tagged_non_smi_to_int32_operator_.set(common->Call(call_descriptor));
}
Node* call = gasm_->Call(tagged_non_smi_to_int32_operator_.get(), target,
value, context);
Node* call = frame_state
? gasm_->Call(tagged_non_smi_to_int32_operator_.get(),
target, value, context, frame_state)
: gasm_->Call(tagged_non_smi_to_int32_operator_.get(),
target, value, context);
SetSourcePosition(call, 1);
gasm_->Goto(&done, call);
gasm_->Bind(&done);
@ -6188,18 +6202,25 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
return gasm_->Call(float64_to_number_operator_.get(), target, value);
}
Node* BuildChangeTaggedToFloat64(Node* value, Node* context) {
Node* BuildChangeTaggedToFloat64(Node* value, Node* context,
Node* frame_state) {
CommonOperatorBuilder* common = mcgraph()->common();
Node* target = GetTargetForBuiltinCall(wasm::WasmCode::kWasmTaggedToFloat64,
Builtins::kWasmTaggedToFloat64);
bool needs_frame_state = frame_state != nullptr;
if (!tagged_to_float64_operator_.is_set()) {
auto call_descriptor = Linkage::GetStubCallDescriptor(
mcgraph()->zone(), WasmTaggedToFloat64Descriptor(), 0,
CallDescriptor::kNoFlags, Operator::kNoProperties, stub_mode_);
frame_state ? CallDescriptor::kNeedsFrameState
: CallDescriptor::kNoFlags,
Operator::kNoProperties, stub_mode_);
tagged_to_float64_operator_.set(common->Call(call_descriptor));
}
Node* call =
gasm_->Call(tagged_to_float64_operator_.get(), target, value, context);
Node* call = needs_frame_state
? gasm_->Call(tagged_to_float64_operator_.get(), target,
value, context, frame_state)
: gasm_->Call(tagged_to_float64_operator_.get(), target,
value, context);
SetSourcePosition(call, 1);
return call;
}
@ -6322,9 +6343,10 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
graph()->NewNode(call, target, input, effect(), control()));
}
Node* BuildChangeBigIntToInt64(Node* input, Node* context) {
const Operator* call =
mcgraph()->common()->Call(GetBigIntToI64CallDescriptor());
Node* BuildChangeBigIntToInt64(Node* input, Node* context,
Node* frame_state) {
const Operator* call = mcgraph()->common()->Call(
GetBigIntToI64CallDescriptor(frame_state != nullptr));
Node* target;
if (mcgraph()->machine()->Is64()) {
@ -6339,6 +6361,9 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
Builtins::kBigIntToI32Pair);
}
if (frame_state)
return SetEffectControl(graph()->NewNode(
call, target, input, context, frame_state, effect(), control()));
return SetEffectControl(
graph()->NewNode(call, target, input, context, effect(), control()));
}
@ -6366,7 +6391,8 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
type_check.merge);
}
Node* FromJS(Node* input, Node* js_context, wasm::ValueType type) {
Node* FromJS(Node* input, Node* js_context, wasm::ValueType type,
Node* frame_state = nullptr) {
switch (type.kind()) {
case wasm::ValueType::kRef:
case wasm::ValueType::kOptRef: {
@ -6402,18 +6428,18 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
case wasm::ValueType::kF32:
return graph()->NewNode(
mcgraph()->machine()->TruncateFloat64ToFloat32(),
BuildChangeTaggedToFloat64(input, js_context));
BuildChangeTaggedToFloat64(input, js_context, frame_state));
case wasm::ValueType::kF64:
return BuildChangeTaggedToFloat64(input, js_context);
return BuildChangeTaggedToFloat64(input, js_context, frame_state);
case wasm::ValueType::kI32:
return BuildChangeTaggedToInt32(input, js_context);
return BuildChangeTaggedToInt32(input, js_context, frame_state);
case wasm::ValueType::kI64:
// i64 values can only come from BigInt.
DCHECK(enabled_features_.has_bigint());
return BuildChangeBigIntToInt64(input, js_context);
return BuildChangeBigIntToInt64(input, js_context, frame_state);
case wasm::ValueType::kRtt: // TODO(7748): Implement.
case wasm::ValueType::kS128:
@ -6482,14 +6508,8 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
}
}
void BuildModifyThreadInWasmFlag(bool new_value) {
if (!trap_handler::IsTrapHandlerEnabled()) return;
Node* isolate_root = BuildLoadIsolateRoot();
Node* thread_in_wasm_flag_address =
gasm_->Load(MachineType::Pointer(), isolate_root,
Isolate::thread_in_wasm_flag_address_offset());
void BuildModifyThreadInWasmFlagHelper(Node* thread_in_wasm_flag_address,
bool new_value) {
if (FLAG_debug_code) {
Node* flag_value = SetEffect(
graph()->NewNode(mcgraph()->machine()->Load(MachineType::Pointer()),
@ -6523,6 +6543,46 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
mcgraph()->Int32Constant(new_value ? 1 : 0), effect(), control()));
}
void BuildModifyThreadInWasmFlag(bool new_value) {
if (!trap_handler::IsTrapHandlerEnabled()) return;
Node* isolate_root = BuildLoadIsolateRoot();
Node* thread_in_wasm_flag_address =
gasm_->Load(MachineType::Pointer(), isolate_root,
Isolate::thread_in_wasm_flag_address_offset());
BuildModifyThreadInWasmFlagHelper(thread_in_wasm_flag_address, new_value);
}
class ModifyThreadInWasmFlagScope {
public:
ModifyThreadInWasmFlagScope(
WasmWrapperGraphBuilder* wasm_wrapper_graph_builder,
WasmGraphAssembler* gasm)
: wasm_wrapper_graph_builder_(wasm_wrapper_graph_builder) {
if (!trap_handler::IsTrapHandlerEnabled()) return;
Node* isolate_root = wasm_wrapper_graph_builder_->BuildLoadIsolateRoot();
thread_in_wasm_flag_address_ =
gasm->Load(MachineType::Pointer(), isolate_root,
Isolate::thread_in_wasm_flag_address_offset());
wasm_wrapper_graph_builder_->BuildModifyThreadInWasmFlagHelper(
thread_in_wasm_flag_address_, true);
}
~ModifyThreadInWasmFlagScope() {
if (!trap_handler::IsTrapHandlerEnabled()) return;
wasm_wrapper_graph_builder_->BuildModifyThreadInWasmFlagHelper(
thread_in_wasm_flag_address_, false);
}
private:
WasmWrapperGraphBuilder* wasm_wrapper_graph_builder_;
Node* thread_in_wasm_flag_address_;
};
Node* BuildMultiReturnFixedArrayFromIterable(const wasm::FunctionSig* sig,
Node* iterable, Node* context) {
Node* length = BuildChangeUint31ToSmi(
@ -6541,38 +6601,42 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
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);
base::SmallVector<Node*, 16> args,
const JSWasmCallData* js_wasm_call_data,
Node* frame_state) {
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}.
Node* function_index = BuildChangeSmiToInt32(
gasm_->LoadExportedFunctionIndexAsSmi(function_data));
BuildImportCall(sig_, VectorOf(args), VectorOf(rets),
wasm::kNoCodePosition, function_index, kCallContinues);
} else {
// Call to a wasm function defined in this module.
// The call target is the jump table slot for that function.
Node* jump_table_start =
LOAD_INSTANCE_FIELD(JumpTableStart, MachineType::Pointer());
Node* jump_table_offset =
BuildLoadJumpTableOffsetFromExportedFunctionData(function_data);
Node* jump_table_slot = graph()->NewNode(
mcgraph()->machine()->IntAdd(), jump_table_start, jump_table_offset);
args[0] = jump_table_slot;
// Set the ThreadInWasm flag before we do the actual call.
{
ModifyThreadInWasmFlagScope modify_thread_in_wasm_flag_builder(
this, gasm_.get());
BuildWasmCall(sig_, VectorOf(args), VectorOf(rets), wasm::kNoCodePosition,
nullptr, kNoRetpoline);
if (is_import) {
// Call to an imported function.
// Load function index from {WasmExportedFunctionData}.
Node* function_index = BuildChangeSmiToInt32(
gasm_->LoadExportedFunctionIndexAsSmi(function_data));
BuildImportCall(sig_, VectorOf(args), VectorOf(rets),
wasm::kNoCodePosition, function_index, kCallContinues);
} else {
// Call to a wasm function defined in this module.
// The call target is the jump table slot for that function.
Node* jump_table_start =
LOAD_INSTANCE_FIELD(JumpTableStart, MachineType::Pointer());
Node* jump_table_offset =
BuildLoadJumpTableOffsetFromExportedFunctionData(function_data);
Node* jump_table_slot =
graph()->NewNode(mcgraph()->machine()->IntAdd(), jump_table_start,
jump_table_offset);
args[0] = jump_table_slot;
BuildWasmCall(sig_, VectorOf(args), VectorOf(rets),
wasm::kNoCodePosition, nullptr, kNoRetpoline,
frame_state);
}
}
// Clear the ThreadInWasm flag.
BuildModifyThreadInWasmFlag(false);
Node* jsval;
if (sig_->return_count() == 0) {
// We do not use {BuildLoadUndefinedValueFromInstance} here because it
@ -6584,7 +6648,9 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
mcgraph()->Int32Constant(
IsolateData::root_slot_offset(RootIndex::kUndefinedValue)));
} else if (sig_->return_count() == 1) {
jsval = ToJS(rets[0], sig_->GetReturn());
jsval = js_wasm_call_data && !js_wasm_call_data->result_needs_conversion()
? rets[0]
: ToJS(rets[0], sig_->GetReturn());
} else {
int32_t return_count = static_cast<int32_t>(sig_->return_count());
Node* size =
@ -6671,11 +6737,13 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
}
}
void BuildJSToWasmWrapper(bool is_import) {
const int wasm_count = static_cast<int>(sig_->parameter_count());
void BuildJSToWasmWrapper(bool is_import,
const JSWasmCallData* js_wasm_call_data = nullptr,
Node* frame_state = nullptr) {
const int wasm_param_count = static_cast<int>(sig_->parameter_count());
// Build the start and the JS parameter nodes.
SetEffectControl(Start(wasm_count + 5));
SetEffectControl(Start(wasm_param_count + 5));
// Create the js_closure and js_context parameters.
Node* js_closure =
@ -6684,7 +6752,8 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
graph()->start());
Node* js_context = graph()->NewNode(
mcgraph()->common()->Parameter(
Linkage::GetJSCallContextParamIndex(wasm_count + 1), "%context"),
Linkage::GetJSCallContextParamIndex(wasm_param_count + 1),
"%context"),
graph()->start());
// Create the instance_node node to pass as parameter. It is loaded from
@ -6704,17 +6773,18 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
return;
}
const int args_count = wasm_count + 1; // +1 for wasm_code.
const int args_count = wasm_param_count + 1; // +1 for wasm_code.
// Check whether the signature of the function allows for a fast
// transformation (if any params exist that need transformation).
// Create a fast transformation path, only if it does.
bool include_fast_path = wasm_count && QualifiesForFastTransform(sig_);
bool include_fast_path = !js_wasm_call_data && wasm_param_count > 0 &&
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);
for (int i = 0; i < wasm_param_count; ++i) params[i + 1] = Param(i + 1);
auto done = gasm_->MakeLabel(MachineRepresentation::kTagged);
if (include_fast_path) {
@ -6723,30 +6793,47 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
// using the fast transformation. When a param that cannot be transformed
// fast is encountered, skip checking the rest and fall back to the slow
// path.
for (int i = 0; i < wasm_count; ++i) {
for (int i = 0; i < wasm_param_count; ++i) {
CanTransformFast(params[i + 1], sig_->GetParam(i), &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) {
for (int i = 0; i < wasm_param_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);
Node* jsval = BuildCallAndReturn(is_import, js_context, function_data,
args, js_wasm_call_data, frame_state);
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;
for (int i = 0; i < wasm_param_count; ++i) {
bool do_conversion =
!js_wasm_call_data || js_wasm_call_data->arg_needs_conversion(i);
if (do_conversion) {
args[i + 1] =
FromJS(params[i + 1], js_context, sig_->GetParam(i), frame_state);
} else {
Node* wasm_param = params[i + 1];
// For Float32 parameters
// we set UseInfo::CheckedNumberOrOddballAsFloat64 in
// simplified-lowering and we need to add here a conversion from Float64
// to Float32.
if (sig_->GetParam(i).kind() == wasm::ValueType::kF32) {
wasm_param = graph()->NewNode(
mcgraph()->machine()->TruncateFloat64ToFloat32(), wasm_param);
}
args[i + 1] = wasm_param;
}
}
Node* jsval =
BuildCallAndReturn(is_import, js_context, function_data, args);
Node* jsval = BuildCallAndReturn(is_import, js_context, function_data, args,
js_wasm_call_data, frame_state);
// If both the default and a fast transformation paths are present,
// get the return value based on the path used.
if (include_fast_path) {
@ -7273,6 +7360,16 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
} // namespace
void BuildInlinedJSToWasmWrapper(
Zone* zone, MachineGraph* mcgraph, const wasm::FunctionSig* signature,
const wasm::WasmModule* module, compiler::SourcePositionTable* spt,
StubCallMode stub_mode, wasm::WasmFeatures features,
const JSWasmCallData* js_wasm_call_data, Node* frame_state) {
WasmWrapperGraphBuilder builder(zone, mcgraph, signature, module, spt,
stub_mode, features);
builder.BuildJSToWasmWrapper(false, js_wasm_call_data, frame_state);
}
std::unique_ptr<OptimizedCompilationJob> NewJSToWasmCompilationJob(
Isolate* isolate, wasm::WasmEngine* wasm_engine,
const wasm::FunctionSig* sig, const wasm::WasmModule* module,
@ -7991,7 +8088,8 @@ class LinkageLocationAllocator {
// General code uses the above configuration data.
CallDescriptor* GetWasmCallDescriptor(
Zone* zone, const wasm::FunctionSig* fsig,
WasmGraphBuilder::UseRetpoline use_retpoline, WasmCallKind call_kind) {
WasmGraphBuilder::UseRetpoline use_retpoline, WasmCallKind call_kind,
bool need_frame_state) {
// The extra here is to accomodate the instance object as first parameter
// and, when specified, the additional callable.
bool extra_callable_param =
@ -8068,7 +8166,9 @@ CallDescriptor* GetWasmCallDescriptor(
}
CallDescriptor::Flags flags =
use_retpoline ? CallDescriptor::kRetpoline : CallDescriptor::kNoFlags;
use_retpoline ? CallDescriptor::kRetpoline
: need_frame_state ? CallDescriptor::kNeedsFrameState
: CallDescriptor::kNoFlags;
return zone->New<CallDescriptor>( // --
descriptor_kind, // kind
target_type, // target MachineType

View File

@ -148,6 +148,28 @@ enum CWasmEntryParameters {
V8_EXPORT_PRIVATE Handle<Code> CompileCWasmEntry(
Isolate*, const wasm::FunctionSig*, const wasm::WasmModule* module);
class JSWasmCallData {
public:
void set_arg_needs_conversion(size_t index, bool value) {
if (index >= arg_needs_conversion_.size())
arg_needs_conversion_.resize(index + 1);
arg_needs_conversion_[index] = value;
}
bool arg_needs_conversion(size_t index) const {
DCHECK_LT(index, arg_needs_conversion_.size());
return arg_needs_conversion_[index];
}
void set_result_needs_conversion(bool value) {
result_needs_conversion_ = value;
}
bool result_needs_conversion() const { return result_needs_conversion_; }
private:
bool result_needs_conversion_ = false;
std::vector<bool> arg_needs_conversion_;
};
// Values from the instance object are cached between Wasm-level function calls.
// This struct allows the SSA environment handling this cache to be defined
// and manipulated in wasm-compiler.{h,cc} instead of inside the Wasm decoder.
@ -487,7 +509,7 @@ class WasmGraphBuilder {
Node* BuildCCall(MachineSignature* sig, Node* function, Args... args);
Node* BuildCallNode(const wasm::FunctionSig* sig, Vector<Node*> args,
wasm::WasmCodePosition position, Node* instance_node,
const Operator* op);
const Operator* op, Node* frame_state = nullptr);
// Helper function for {BuildIndirectCall}.
void LoadIndirectFunctionTable(uint32_t table_index, Node** ift_size,
Node** ift_sig_ids, Node** ift_targets,
@ -498,7 +520,8 @@ class WasmGraphBuilder {
IsReturnCall continuation);
Node* BuildWasmCall(const wasm::FunctionSig* sig, Vector<Node*> args,
Vector<Node*> rets, wasm::WasmCodePosition position,
Node* instance_node, UseRetpoline use_retpoline);
Node* instance_node, UseRetpoline use_retpoline,
Node* frame_state = nullptr);
Node* BuildWasmReturnCall(const wasm::FunctionSig* sig, Vector<Node*> args,
wasm::WasmCodePosition position,
Node* instance_node, UseRetpoline use_retpoline);
@ -663,11 +686,17 @@ class WasmGraphBuilder {
enum WasmCallKind { kWasmFunction, kWasmImportWrapper, kWasmCapiFunction };
V8_EXPORT_PRIVATE void BuildInlinedJSToWasmWrapper(
Zone* zone, MachineGraph* mcgraph, const wasm::FunctionSig* signature,
const wasm::WasmModule* module, compiler::SourcePositionTable* spt,
StubCallMode stub_mode, wasm::WasmFeatures features,
const JSWasmCallData* js_wasm_call_data, Node* frame_state);
V8_EXPORT_PRIVATE CallDescriptor* GetWasmCallDescriptor(
Zone* zone, const wasm::FunctionSig* signature,
WasmGraphBuilder::UseRetpoline use_retpoline =
WasmGraphBuilder::kNoRetpoline,
WasmCallKind kind = kWasmFunction);
WasmCallKind kind = kWasmFunction, bool need_frame_state = false);
V8_EXPORT_PRIVATE CallDescriptor* GetI32WasmCallDescriptor(
Zone* zone, const CallDescriptor* call_descriptor);

View File

@ -29,6 +29,7 @@
#include "src/objects/smi.h"
#include "src/snapshot/embedded/embedded-data.h"
#include "src/tracing/trace-event.h"
#include "src/wasm/wasm-linkage.h"
// Has to be the last include (doesn't have include guards)
#include "src/objects/object-macros.h"
@ -484,6 +485,24 @@ uint16_t InternalFormalParameterCountWithReceiver(SharedFunctionInfo sfi) {
} // namespace
namespace {
// Encodes/decodes the return type of a Wasm function as the integer value of
// wasm::ValueType::Kind, or -1 if the function returns void.
int EncodeWasmReturnType(base::Optional<wasm::ValueType::Kind> return_type) {
return return_type ? static_cast<int>(return_type.value()) : -1;
}
base::Optional<wasm::ValueType::Kind> DecodeWasmReturnType(int code) {
if (code >= 0) {
return {static_cast<wasm::ValueType::Kind>(code)};
}
return {};
}
} // namespace
Deoptimizer::Deoptimizer(Isolate* isolate, JSFunction function,
DeoptimizeKind kind, unsigned bailout_id, Address from,
int fp_to_sp_delta)
@ -937,6 +956,7 @@ void Deoptimizer::DoComputeOutputFrames() {
DoComputeConstructStubFrame(translated_frame, frame_index);
break;
case TranslatedFrame::kBuiltinContinuation:
case TranslatedFrame::kJSToWasmBuiltinContinuation:
DoComputeBuiltinContinuation(translated_frame, frame_index,
BuiltinContinuationMode::STUB);
break;
@ -1656,6 +1676,36 @@ Builtins::Name Deoptimizer::TrampolineForBuiltinContinuation(
UNREACHABLE();
}
TranslatedValue Deoptimizer::TranslatedValueForWasmReturnType(
base::Optional<wasm::ValueType::Kind> wasm_call_return_type) {
if (wasm_call_return_type) {
switch (wasm_call_return_type.value()) {
case wasm::ValueType::kI32:
return TranslatedValue::NewInt32(
&translated_state_,
(int32_t)input_->GetRegister(kReturnRegister0.code()));
case wasm::ValueType::kI64:
return TranslatedValue::NewInt64ToBigInt(
&translated_state_,
(int64_t)input_->GetRegister(kReturnRegister0.code()));
case wasm::ValueType::kF32:
return TranslatedValue::NewFloat(
&translated_state_,
Float32(*reinterpret_cast<float*>(
input_->GetDoubleRegister(wasm::kFpReturnRegisters[0].code())
.get_bits_address())));
case wasm::ValueType::kF64:
return TranslatedValue::NewDouble(
&translated_state_,
input_->GetDoubleRegister(wasm::kFpReturnRegisters[0].code()));
default:
UNREACHABLE();
}
}
return TranslatedValue::NewTagged(&translated_state_,
ReadOnlyRoots(isolate()).undefined_value());
}
// BuiltinContinuationFrames capture the machine state that is expected as input
// to a builtin, including both input register values and stack parameters. When
// the frame is reactivated (i.e. the frame below it returns), a
@ -1717,6 +1767,21 @@ Builtins::Name Deoptimizer::TrampolineForBuiltinContinuation(
void Deoptimizer::DoComputeBuiltinContinuation(
TranslatedFrame* translated_frame, int frame_index,
BuiltinContinuationMode mode) {
TranslatedFrame::iterator result_iterator = translated_frame->end();
bool is_js_to_wasm_builtin_continuation =
translated_frame->kind() == TranslatedFrame::kJSToWasmBuiltinContinuation;
if (is_js_to_wasm_builtin_continuation) {
// For JSToWasmBuiltinContinuations, add a TranslatedValue with the result
// of the Wasm call, extracted from the input FrameDescription.
// This TranslatedValue will be written in the output frame in place of the
// hole and we'll use ContinueToCodeStubBuiltin in place of
// ContinueToCodeStubBuiltinWithResult.
TranslatedValue result = TranslatedValueForWasmReturnType(
translated_frame->wasm_call_return_type());
translated_frame->Add(result);
}
TranslatedFrame::iterator value_iterator = translated_frame->begin();
const BailoutId bailout_id = translated_frame->node_id();
@ -1800,9 +1865,15 @@ void Deoptimizer::DoComputeBuiltinContinuation(
frame_writer.PushTranslatedValue(value_iterator, "stack parameter");
}
if (frame_info.frame_has_result_stack_slot()) {
frame_writer.PushRawObject(
roots.the_hole_value(),
"placeholder for return result on lazy deopt\n");
if (is_js_to_wasm_builtin_continuation) {
frame_writer.PushTranslatedValue(result_iterator,
"return result on lazy deopt\n");
} else {
DCHECK_EQ(result_iterator, translated_frame->end());
frame_writer.PushRawObject(
roots.the_hole_value(),
"placeholder for return result on lazy deopt\n");
}
}
} else {
// JavaScript builtin.
@ -1897,7 +1968,7 @@ void Deoptimizer::DoComputeBuiltinContinuation(
frame_writer.PushRawObject(Smi::FromInt(output_frame_size_above_fp),
"frame height at deoptimization\n");
// The context even if this is a stub contininuation frame. We can't use the
// The context even if this is a stub continuation frame. We can't use the
// usual context slot, because we must store the frame marker there.
frame_writer.PushTranslatedValue(context_register_value,
"builtin JavaScript context\n");
@ -1950,7 +2021,7 @@ void Deoptimizer::DoComputeBuiltinContinuation(
}
}
CHECK_EQ(translated_frame->end(), value_iterator);
CHECK_EQ(result_iterator, value_iterator);
CHECK_EQ(0u, frame_writer.top_offset());
// Clear the context register. The context might be a de-materialized object
@ -1966,10 +2037,13 @@ void Deoptimizer::DoComputeBuiltinContinuation(
// will build its own frame once we continue to it.
Register fp_reg = JavaScriptFrame::fp_register();
output_frame->SetRegister(fp_reg.code(), fp_value);
// For JSToWasmBuiltinContinuations use ContinueToCodeStubBuiltin, and not
// ContinueToCodeStubBuiltinWithResult because we don't want to overwrite the
// return value that we have already set.
Code continue_to_builtin =
isolate()->builtins()->builtin(TrampolineForBuiltinContinuation(
mode, frame_info.frame_has_result_stack_slot()));
mode, frame_info.frame_has_result_stack_slot() &&
!is_js_to_wasm_builtin_continuation));
if (is_topmost) {
// Only the pc of the topmost frame needs to be signed since it is
// authenticated at the end of the DeoptimizationEntry builtin.
@ -2156,6 +2230,16 @@ void Translation::BeginBuiltinContinuationFrame(BailoutId bailout_id,
buffer_->Add(height);
}
void Translation::BeginJSToWasmBuiltinContinuationFrame(
BailoutId bailout_id, int literal_id, unsigned height,
base::Optional<wasm::ValueType::Kind> return_type) {
buffer_->Add(JS_TO_WASM_BUILTIN_CONTINUATION_FRAME);
buffer_->Add(bailout_id.ToInt());
buffer_->Add(literal_id);
buffer_->Add(height);
buffer_->Add(EncodeWasmReturnType(return_type));
}
void Translation::BeginJavaScriptBuiltinContinuationFrame(BailoutId bailout_id,
int literal_id,
unsigned height) {
@ -2332,6 +2416,7 @@ int Translation::NumberOfOperandsFor(Opcode opcode) {
case BEGIN:
case CONSTRUCT_STUB_FRAME:
case BUILTIN_CONTINUATION_FRAME:
case JS_TO_WASM_BUILTIN_CONTINUATION_FRAME:
case JAVA_SCRIPT_BUILTIN_CONTINUATION_FRAME:
case JAVA_SCRIPT_BUILTIN_CONTINUATION_WITH_CATCH_FRAME:
return 3;
@ -2577,6 +2662,14 @@ TranslatedValue TranslatedValue::NewInt64(TranslatedState* container,
return slot;
}
// static
TranslatedValue TranslatedValue::NewInt64ToBigInt(TranslatedState* container,
int64_t value) {
TranslatedValue slot(container, kInt64ToBigInt);
slot.int64_value_ = value;
return slot;
}
// static
TranslatedValue TranslatedValue::NewUInt32(TranslatedState* container,
uint32_t value) {
@ -2619,7 +2712,7 @@ int32_t TranslatedValue::int32_value() const {
}
int64_t TranslatedValue::int64_value() const {
DCHECK_EQ(kInt64, kind());
DCHECK(kInt64 == kind() || kInt64ToBigInt == kind());
return int64_value_;
}
@ -2681,6 +2774,10 @@ Object TranslatedValue::GetRawValue() const {
break;
}
case kInt64ToBigInt:
// Return the arguments marker.
break;
case kUInt32: {
bool is_smi = (uint32_value() <= static_cast<uintptr_t>(Smi::kMaxValue));
if (is_smi) {
@ -2770,28 +2867,37 @@ Handle<Object> TranslatedValue::GetValue() {
return container_->InitializeObjectAt(this);
}
double number;
double number = 0;
Handle<HeapObject> heap_object;
switch (kind()) {
case TranslatedValue::kInt32:
number = int32_value();
heap_object = isolate()->factory()->NewHeapNumber(number);
break;
case TranslatedValue::kInt64:
number = int64_value();
heap_object = isolate()->factory()->NewHeapNumber(number);
break;
case TranslatedValue::kInt64ToBigInt:
heap_object = BigInt::FromInt64(isolate(), int64_value());
break;
case TranslatedValue::kUInt32:
number = uint32_value();
heap_object = isolate()->factory()->NewHeapNumber(number);
break;
case TranslatedValue::kFloat:
number = float_value().get_scalar();
heap_object = isolate()->factory()->NewHeapNumber(number);
break;
case TranslatedValue::kDouble:
number = double_value().get_scalar();
heap_object = isolate()->factory()->NewHeapNumber(number);
break;
default:
UNREACHABLE();
}
DCHECK(!IsSmiDouble(number));
set_initialized_storage(isolate()->factory()->NewHeapNumber(number));
DCHECK(!IsSmiDouble(number) || kind() == TranslatedValue::kInt64ToBigInt);
set_initialized_storage(heap_object);
return storage_;
}
@ -2883,6 +2989,15 @@ TranslatedFrame TranslatedFrame::BuiltinContinuationFrame(
return frame;
}
TranslatedFrame TranslatedFrame::JSToWasmBuiltinContinuationFrame(
BailoutId bailout_id, SharedFunctionInfo shared_info, int height,
base::Optional<wasm::ValueType::Kind> return_type) {
TranslatedFrame frame(kJSToWasmBuiltinContinuation, shared_info, height);
frame.node_id_ = bailout_id;
frame.return_type_ = return_type;
return frame;
}
TranslatedFrame TranslatedFrame::JavaScriptBuiltinContinuationFrame(
BailoutId bailout_id, SharedFunctionInfo shared_info, int height) {
TranslatedFrame frame(kJavaScriptBuiltinContinuation, shared_info, height);
@ -2918,6 +3033,7 @@ int TranslatedFrame::GetValueCount() {
case kConstructStub:
case kBuiltinContinuation:
case kJSToWasmBuiltinContinuation:
case kJavaScriptBuiltinContinuation:
case kJavaScriptBuiltinContinuationWithCatch: {
static constexpr int kTheContext = 1;
@ -3012,6 +3128,26 @@ TranslatedFrame TranslatedState::CreateNextTranslatedFrame(
height);
}
case Translation::JS_TO_WASM_BUILTIN_CONTINUATION_FRAME: {
BailoutId bailout_id = BailoutId(iterator->Next());
SharedFunctionInfo shared_info =
SharedFunctionInfo::cast(literal_array.get(iterator->Next()));
int height = iterator->Next();
base::Optional<wasm::ValueType::Kind> return_type =
DecodeWasmReturnType(iterator->Next());
if (trace_file != nullptr) {
std::unique_ptr<char[]> name = shared_info.DebugNameCStr();
PrintF(trace_file, " reading JS to Wasm builtin continuation frame %s",
name.get());
PrintF(trace_file,
" => bailout_id=%d, height=%d return_type=%d; inputs:\n",
bailout_id.ToInt(), height,
return_type.has_value() ? return_type.value() : -1);
}
return TranslatedFrame::JSToWasmBuiltinContinuationFrame(
bailout_id, shared_info, height, return_type);
}
case Translation::JAVA_SCRIPT_BUILTIN_CONTINUATION_FRAME: {
BailoutId bailout_id = BailoutId(iterator->Next());
SharedFunctionInfo shared_info =
@ -3201,6 +3337,7 @@ int TranslatedState::CreateNextTranslatedValue(
case Translation::JAVA_SCRIPT_BUILTIN_CONTINUATION_FRAME:
case Translation::JAVA_SCRIPT_BUILTIN_CONTINUATION_WITH_CATCH_FRAME:
case Translation::BUILTIN_CONTINUATION_FRAME:
case Translation::JS_TO_WASM_BUILTIN_CONTINUATION_FRAME:
case Translation::UPDATE_FEEDBACK:
// Peeled off before getting here.
break;

View File

@ -63,12 +63,14 @@ class TranslatedValue {
private:
friend class TranslatedState;
friend class TranslatedFrame;
friend class Deoptimizer;
enum Kind : uint8_t {
kInvalid,
kTagged,
kInt32,
kInt64,
kInt64ToBigInt,
kUInt32,
kBoolBit,
kFloat,
@ -105,6 +107,8 @@ class TranslatedValue {
static TranslatedValue NewDouble(TranslatedState* container, Float64 value);
static TranslatedValue NewInt32(TranslatedState* container, int32_t value);
static TranslatedValue NewInt64(TranslatedState* container, int64_t value);
static TranslatedValue NewInt64ToBigInt(TranslatedState* container,
int64_t value);
static TranslatedValue NewUInt32(TranslatedState* container, uint32_t value);
static TranslatedValue NewBool(TranslatedState* container, uint32_t value);
static TranslatedValue NewTagged(TranslatedState* container, Object literal);
@ -172,6 +176,7 @@ class TranslatedFrame {
kArgumentsAdaptor,
kConstructStub,
kBuiltinContinuation,
kJSToWasmBuiltinContinuation,
kJavaScriptBuiltinContinuation,
kJavaScriptBuiltinContinuationWithCatch,
kInvalid
@ -246,8 +251,15 @@ class TranslatedFrame {
reference front() { return values_.front(); }
const_reference front() const { return values_.front(); }
// Only for Kind == kJSToWasmBuiltinContinuation
base::Optional<wasm::ValueType::Kind> wasm_call_return_type() const {
DCHECK_EQ(kind(), kJSToWasmBuiltinContinuation);
return return_type_;
}
private:
friend class TranslatedState;
friend class Deoptimizer;
// Constructor static methods.
static TranslatedFrame InterpretedFrame(BailoutId bytecode_offset,
@ -263,6 +275,9 @@ class TranslatedFrame {
int height);
static TranslatedFrame BuiltinContinuationFrame(
BailoutId bailout_id, SharedFunctionInfo shared_info, int height);
static TranslatedFrame JSToWasmBuiltinContinuationFrame(
BailoutId bailout_id, SharedFunctionInfo shared_info, int height,
base::Optional<wasm::ValueType::Kind> return_type);
static TranslatedFrame JavaScriptBuiltinContinuationFrame(
BailoutId bailout_id, SharedFunctionInfo shared_info, int height);
static TranslatedFrame JavaScriptBuiltinContinuationWithCatchFrame(
@ -299,6 +314,9 @@ class TranslatedFrame {
using ValuesContainer = std::deque<TranslatedValue>;
ValuesContainer values_;
// Only for Kind == kJSToWasmBuiltinContinuation
base::Optional<wasm::ValueType::Kind> return_type_;
};
// Auxiliary class for translating deoptimization values.
@ -578,6 +596,9 @@ class Deoptimizer : public Malloced {
static Builtins::Name TrampolineForBuiltinContinuation(
BuiltinContinuationMode mode, bool must_handle_result);
TranslatedValue TranslatedValueForWasmReturnType(
base::Optional<wasm::ValueType::Kind> wasm_call_return_type);
void DoComputeBuiltinContinuation(TranslatedFrame* translated_frame,
int frame_index,
BuiltinContinuationMode mode);
@ -876,6 +897,7 @@ class TranslationIterator {
V(BEGIN) \
V(INTERPRETED_FRAME) \
V(BUILTIN_CONTINUATION_FRAME) \
V(JS_TO_WASM_BUILTIN_CONTINUATION_FRAME) \
V(JAVA_SCRIPT_BUILTIN_CONTINUATION_FRAME) \
V(JAVA_SCRIPT_BUILTIN_CONTINUATION_WITH_CATCH_FRAME) \
V(CONSTRUCT_STUB_FRAME) \
@ -929,6 +951,9 @@ class Translation {
unsigned height);
void BeginBuiltinContinuationFrame(BailoutId bailout_id, int literal_id,
unsigned height);
void BeginJSToWasmBuiltinContinuationFrame(
BailoutId bailout_id, int literal_id, unsigned height,
base::Optional<wasm::ValueType::Kind> return_type);
void BeginJavaScriptBuiltinContinuationFrame(BailoutId bailout_id,
int literal_id, unsigned height);
void BeginJavaScriptBuiltinContinuationWithCatchFrame(BailoutId bailout_id,

View File

@ -1088,6 +1088,8 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory {
isolate_root_bias());
}
THREAD_LOCAL_TOP_ADDRESS(Address, thread_in_wasm_flag_address)
MaterializedObjectStore* materialized_object_store() {
return materialized_object_store_;
}

View File

@ -424,6 +424,7 @@ DEFINE_BOOL(future, FUTURE_BOOL,
DEFINE_WEAK_IMPLICATION(future, write_protect_code_memory)
DEFINE_WEAK_IMPLICATION(future, finalize_streaming_on_background)
DEFINE_WEAK_IMPLICATION(future, super_ic)
DEFINE_WEAK_IMPLICATION(future, turbo_inline_js_wasm_calls)
// Flags for jitless
DEFINE_BOOL(jitless, V8_LITE_BOOL,
@ -735,6 +736,7 @@ DEFINE_INT(reuse_opt_code_count, 0,
DEFINE_BOOL(turbo_dynamic_map_checks, true,
"use dynamic map checks when generating code for property accesses "
"if all handlers in an IC are the same for turboprop and NCI")
DEFINE_BOOL(turbo_inline_js_wasm_calls, false, "inline JS->Wasm calls")
// Native context independent (NCI) code.
DEFINE_BOOL(turbo_nci, false,

View File

@ -526,6 +526,19 @@ void DeoptimizationData::DeoptimizationDataPrint(std::ostream& os) { // NOLINT
break;
}
case Translation::JS_TO_WASM_BUILTIN_CONTINUATION_FRAME: {
int bailout_id = iterator.Next();
int shared_info_id = iterator.Next();
Object shared_info = LiteralArray().get(shared_info_id);
unsigned height = iterator.Next();
int wasm_return_type = iterator.Next();
os << "{bailout_id=" << bailout_id << ", function="
<< SharedFunctionInfo::cast(shared_info).DebugNameCStr().get()
<< ", height=" << height
<< ", wasm_return_type=" << wasm_return_type << "}";
break;
}
case Translation::ARGUMENTS_ADAPTOR_FRAME: {
int shared_info_id = iterator.Next();
Object shared_info = LiteralArray().get(shared_info_id);

View File

@ -15,6 +15,7 @@
#include "src/objects/scope-info.h"
#include "src/objects/shared-function-info.h"
#include "src/objects/templates.h"
#include "src/wasm/wasm-module.h"
#include "src/wasm/wasm-objects-inl.h"
// Has to be the last include (doesn't have include guards):
@ -656,6 +657,22 @@ bool SharedFunctionInfo::HasWasmJSFunctionData() const {
return function_data(kAcquireLoad).IsWasmJSFunctionData();
}
const wasm::WasmModule* SharedFunctionInfo::wasm_module() const {
if (!HasWasmExportedFunctionData()) return nullptr;
const WasmExportedFunctionData& function_data = wasm_exported_function_data();
const WasmInstanceObject& wasm_instance = function_data.instance();
const WasmModuleObject& wasm_module_object = wasm_instance.module_object();
return wasm_module_object.module();
}
const wasm::FunctionSig* SharedFunctionInfo::wasm_function_signature() const {
const wasm::WasmModule* module = wasm_module();
if (!module) return nullptr;
const WasmExportedFunctionData& function_data = wasm_exported_function_data();
DCHECK_LT(function_data.function_index(), module->functions.size());
return module->functions[function_data.function_index()].sig;
}
bool SharedFunctionInfo::HasWasmCapiFunctionData() const {
return function_data(kAcquireLoad).IsWasmCapiFunctionData();
}

View File

@ -18,6 +18,7 @@
#include "src/objects/smi.h"
#include "src/objects/struct.h"
#include "src/roots/roots.h"
#include "src/wasm/value-type.h"
#include "testing/gtest/include/gtest/gtest_prod.h"
#include "torque-generated/bit-fields.h"
#include "torque-generated/field-offsets.h"
@ -34,10 +35,16 @@ class BytecodeArray;
class CoverageInfo;
class DebugInfo;
class IsCompiledScope;
template <typename>
class Signature;
class WasmCapiFunctionData;
class WasmExportedFunctionData;
class WasmJSFunctionData;
namespace wasm {
struct WasmModule;
} // namespace wasm
#include "torque-generated/src/objects/shared-function-info-tq.inc"
// Data collected by the pre-parser storing information about scopes and inner
@ -317,6 +324,9 @@ class SharedFunctionInfo
inline bool HasWasmCapiFunctionData() const;
WasmCapiFunctionData wasm_capi_function_data() const;
inline const wasm::WasmModule* wasm_module() const;
inline const wasm::FunctionSig* wasm_function_signature() const;
// Clear out pre-parsed scope data from UncompiledDataWithPreparseData,
// turning it into UncompiledDataWithoutPreparseData.
inline void ClearPreparseData();

34
src/wasm/value-type.cc Normal file
View File

@ -0,0 +1,34 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/wasm/value-type.h"
#include "src/codegen/signature.h"
namespace v8 {
namespace internal {
namespace wasm {
base::Optional<wasm::ValueType::Kind> WasmReturnTypeFromSignature(
const FunctionSig* wasm_signature) {
if (wasm_signature->return_count() == 0) {
return {};
} else {
DCHECK_EQ(wasm_signature->return_count(), 1);
ValueType return_type = wasm_signature->GetReturn(0);
switch (return_type.kind()) {
case ValueType::kI32:
case ValueType::kI64:
case ValueType::kF32:
case ValueType::kF64:
return {return_type.kind()};
default:
UNREACHABLE();
}
}
}
} // namespace wasm
} // namespace internal
} // namespace v8

View File

@ -6,6 +6,7 @@
#define V8_WASM_VALUE_TYPE_H_
#include "src/base/bit-field.h"
#include "src/base/optional.h"
#include "src/codegen/machine-type.h"
#include "src/wasm/wasm-constants.h"
@ -679,6 +680,9 @@ class StoreType {
};
};
base::Optional<wasm::ValueType::Kind> WasmReturnTypeFromSignature(
const FunctionSig* wasm_signature);
} // namespace wasm
} // namespace internal
} // namespace v8

View File

@ -247,6 +247,7 @@ v8_source_set("cctest_sources") {
"test-inobject-slack-tracking.cc",
"test-inspector.cc",
"test-intl.cc",
"test-js-to-wasm.cc",
"test-js-weak-refs.cc",
"test-liveedit.cc",
"test-local-handles.cc",

View File

@ -365,6 +365,10 @@ static inline v8::Local<v8::Integer> v8_int(int32_t x) {
return v8::Integer::New(v8::Isolate::GetCurrent(), x);
}
static inline v8::Local<v8::BigInt> v8_bigint(int64_t x) {
return v8::BigInt::New(v8::Isolate::GetCurrent(), x);
}
static inline v8::Local<v8::String> v8_str(const char* x) {
return v8::String::NewFromUtf8(v8::Isolate::GetCurrent(), x).ToLocalChecked();
}

View File

@ -603,6 +603,7 @@
'test-api/TurboAsmDisablesDetach': [SKIP],
'test-cpu-profiler/TickLinesOptimized': [SKIP],
'test-heap/TestOptimizeAfterBytecodeFlushingCandidate': [SKIP],
'test-js-to-wasm/*': [SKIP],
'test-run-wasm-exceptions/RunWasmInterpreter_TryCatchCallDirect': [SKIP],
'test-run-wasm-exceptions/RunWasmInterpreter_TryCatchCallExternal': [SKIP],
'test-run-wasm-exceptions/RunWasmInterpreter_TryCatchCallIndirect': [SKIP],
@ -646,6 +647,7 @@
'test-cpu-profiler/DetailedSourcePositionAPI_Inlining': [SKIP],
'serializer-tester/BoundFunctionArguments': [SKIP],
'serializer-tester/BoundFunctionTarget': [SKIP],
'test-js-to-wasm/*': [SKIP],
}], # variant == turboprop
##############################################################################

View File

@ -27492,126 +27492,6 @@ UNINITIALIZED_TEST(NestedIsolates) {
#ifndef V8_LITE_MODE
namespace {
template <typename T>
struct ConvertJSValue {
static Maybe<T> Get(v8::Local<v8::Value> value,
v8::Local<v8::Context> context);
};
template <>
struct ConvertJSValue<int32_t> {
static Maybe<int32_t> Get(v8::Local<v8::Value> value,
v8::Local<v8::Context> context) {
return value->Int32Value(context);
}
};
template <>
struct ConvertJSValue<uint32_t> {
static Maybe<uint32_t> Get(v8::Local<v8::Value> value,
v8::Local<v8::Context> context) {
return value->Uint32Value(context);
}
};
// NaNs and +/-Infinity should be 0, otherwise (modulo 2^64) - 2^63.
// Step 8 - 12 of https://heycam.github.io/webidl/#abstract-opdef-converttoint
// The int64_t and uint64_t implementations below are copied from Blink:
// https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h;l=249?q=doubletointeger&sq=&ss=chromium%2Fchromium%2Fsrc
template <>
struct ConvertJSValue<int64_t> {
static Maybe<int64_t> Get(v8::Local<v8::Value> value,
v8::Local<v8::Context> context) {
Maybe<double> double_value = value->NumberValue(context);
if (!double_value.IsJust()) {
return v8::Nothing<int64_t>();
}
double result = double_value.ToChecked();
if (std::isinf(result) || std::isnan(result)) {
return v8::Just(int64_t(0));
}
result = trunc(result);
constexpr uint64_t kMaxULL = std::numeric_limits<uint64_t>::max();
// -2^{64} < fmod_value < 2^{64}.
double fmod_value = fmod(result, kMaxULL + 1.0);
if (fmod_value >= 0) {
if (fmod_value < pow(2, 63)) {
// 0 <= fmod_value < 2^{63}.
// 0 <= value < 2^{63}. This cast causes no loss.
return v8::Just(static_cast<int64_t>(fmod_value));
} else {
// 2^{63} <= fmod_value < 2^{64}.
// 2^{63} <= value < 2^{64}. This cast causes no loss.
return v8::Just(static_cast<int64_t>(fmod_value - pow(2, 64)));
}
}
// -2^{64} < fmod_value < 0.
// 0 < fmod_value_uint64 < 2^{64}. This cast causes no loss.
uint64_t fmod_value_uint64 = static_cast<uint64_t>(-fmod_value);
// -1 < (kMaxULL - fmod_value_uint64) < 2^{64} - 1.
// 0 < value < 2^{64}.
return v8::Just(static_cast<int64_t>(kMaxULL - fmod_value_uint64 + 1));
}
};
template <>
struct ConvertJSValue<uint64_t> {
static Maybe<uint64_t> Get(v8::Local<v8::Value> value,
v8::Local<v8::Context> context) {
Maybe<double> double_value = value->NumberValue(context);
if (!double_value.IsJust()) {
return v8::Nothing<uint64_t>();
}
double result = double_value.ToChecked();
if (std::isinf(result) || std::isnan(result)) {
return v8::Just(uint64_t(0));
}
result = trunc(result);
constexpr uint64_t kMaxULL = std::numeric_limits<uint64_t>::max();
// -2^{64} < fmod_value < 2^{64}.
double fmod_value = fmod(result, kMaxULL + 1.0);
if (fmod_value >= 0) {
return v8::Just(static_cast<uint64_t>(fmod_value));
}
// -2^{64} < fmod_value < 0.
// 0 < fmod_value_uint64 < 2^{64}. This cast causes no loss.
uint64_t fmod_value_uint64 = static_cast<uint64_t>(-fmod_value);
// -1 < (kMaxULL - fmod_value_uint64) < 2^{64} - 1.
// 0 < value < 2^{64}.
return v8::Just(static_cast<uint64_t>(kMaxULL - fmod_value_uint64 + 1));
}
};
template <>
struct ConvertJSValue<float> {
static Maybe<float> Get(v8::Local<v8::Value> value,
v8::Local<v8::Context> context) {
Maybe<double> val = value->NumberValue(context);
if (val.IsNothing()) return v8::Nothing<float>();
return v8::Just(static_cast<float>(val.ToChecked()));
}
};
template <>
struct ConvertJSValue<double> {
static Maybe<double> Get(v8::Local<v8::Value> value,
v8::Local<v8::Context> context) {
return value->NumberValue(context);
}
};
template <>
struct ConvertJSValue<bool> {
static Maybe<bool> Get(v8::Local<v8::Value> value,
v8::Local<v8::Context> context) {
return v8::Just<bool>(value->BooleanValue(CcTest::isolate()));
}
};
template <typename Value, typename Impl>
struct BasicApiChecker {
static void FastCallback(v8::ApiObject receiver, Value argument,

View File

@ -52,4 +52,135 @@ static void CheckInternalFieldsAreZero(v8::Local<T> value) {
}
}
template <typename T>
struct ConvertJSValue {
static v8::Maybe<T> Get(v8::Local<v8::Value> value,
v8::Local<v8::Context> context);
};
template <>
struct ConvertJSValue<int32_t> {
static v8::Maybe<int32_t> Get(v8::Local<v8::Value> value,
v8::Local<v8::Context> context) {
return value->Int32Value(context);
}
};
template <>
struct ConvertJSValue<uint32_t> {
static v8::Maybe<uint32_t> Get(v8::Local<v8::Value> value,
v8::Local<v8::Context> context) {
return value->Uint32Value(context);
}
};
// NaNs and +/-Infinity should be 0, otherwise (modulo 2^64) - 2^63.
// Step 8 - 12 of https://heycam.github.io/webidl/#abstract-opdef-converttoint
// The int64_t and uint64_t implementations below are copied from Blink:
// https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h;l=249?q=doubletointeger&sq=&ss=chromium%2Fchromium%2Fsrc
template <>
struct ConvertJSValue<int64_t> {
static v8::Maybe<int64_t> Get(v8::Local<v8::Value> value,
v8::Local<v8::Context> context) {
v8::Maybe<double> double_value = value->NumberValue(context);
if (!double_value.IsJust()) {
return v8::Nothing<int64_t>();
}
double result = double_value.ToChecked();
if (std::isinf(result) || std::isnan(result)) {
return v8::Just(int64_t(0));
}
result = trunc(result);
constexpr uint64_t kMaxULL = std::numeric_limits<uint64_t>::max();
// -2^{64} < fmod_value < 2^{64}.
double fmod_value = fmod(result, kMaxULL + 1.0);
if (fmod_value >= 0) {
if (fmod_value < pow(2, 63)) {
// 0 <= fmod_value < 2^{63}.
// 0 <= value < 2^{63}. This cast causes no loss.
return v8::Just(static_cast<int64_t>(fmod_value));
} else {
// 2^{63} <= fmod_value < 2^{64}.
// 2^{63} <= value < 2^{64}. This cast causes no loss.
return v8::Just(static_cast<int64_t>(fmod_value - pow(2, 64)));
}
}
// -2^{64} < fmod_value < 0.
// 0 < fmod_value_uint64 < 2^{64}. This cast causes no loss.
uint64_t fmod_value_uint64 = static_cast<uint64_t>(-fmod_value);
// -1 < (kMaxULL - fmod_value_uint64) < 2^{64} - 1.
// 0 < value < 2^{64}.
return v8::Just(static_cast<int64_t>(kMaxULL - fmod_value_uint64 + 1));
}
};
template <>
struct ConvertJSValue<uint64_t> {
static v8::Maybe<uint64_t> Get(v8::Local<v8::Value> value,
v8::Local<v8::Context> context) {
v8::Maybe<double> double_value = value->NumberValue(context);
if (!double_value.IsJust()) {
return v8::Nothing<uint64_t>();
}
double result = double_value.ToChecked();
if (std::isinf(result) || std::isnan(result)) {
return v8::Just(uint64_t(0));
}
result = trunc(result);
constexpr uint64_t kMaxULL = std::numeric_limits<uint64_t>::max();
// -2^{64} < fmod_value < 2^{64}.
double fmod_value = fmod(result, kMaxULL + 1.0);
if (fmod_value >= 0) {
return v8::Just(static_cast<uint64_t>(fmod_value));
}
// -2^{64} < fmod_value < 0.
// 0 < fmod_value_uint64 < 2^{64}. This cast causes no loss.
uint64_t fmod_value_uint64 = static_cast<uint64_t>(-fmod_value);
// -1 < (kMaxULL - fmod_value_uint64) < 2^{64} - 1.
// 0 < value < 2^{64}.
return v8::Just(static_cast<uint64_t>(kMaxULL - fmod_value_uint64 + 1));
}
};
template <>
struct ConvertJSValue<v8::BigInt> {
static v8::Maybe<v8::Local<v8::BigInt>> Get(v8::Local<v8::Value> value,
v8::Local<v8::Context> context) {
if (value->IsBigInt()) {
return v8::Just(value.As<v8::BigInt>());
}
return v8::Nothing<v8::Local<v8::BigInt>>();
}
};
template <>
struct ConvertJSValue<float> {
static v8::Maybe<float> Get(v8::Local<v8::Value> value,
v8::Local<v8::Context> context) {
v8::Maybe<double> val = value->NumberValue(context);
if (val.IsNothing()) return v8::Nothing<float>();
return v8::Just(static_cast<float>(val.ToChecked()));
}
};
template <>
struct ConvertJSValue<double> {
static v8::Maybe<double> Get(v8::Local<v8::Value> value,
v8::Local<v8::Context> context) {
return value->NumberValue(context);
}
};
template <>
struct ConvertJSValue<bool> {
static v8::Maybe<bool> Get(v8::Local<v8::Value> value,
v8::Local<v8::Context> context) {
return v8::Just<bool>(value->BooleanValue(CcTest::isolate()));
}
};
#endif // V8_TEST_CCTEST_TEST_API_H_

View File

@ -0,0 +1,845 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <iomanip>
#include "include/v8.h"
#include "src/api/api.h"
#include "src/wasm/wasm-module-builder.h"
#include "test/cctest/cctest.h"
#include "test/cctest/test-api.h"
#include "test/common/wasm/flag-utils.h"
#include "test/common/wasm/test-signatures.h"
#include "test/common/wasm/wasm-macro-gen.h"
namespace v8 {
namespace internal {
namespace wasm {
static const int kDeoptLoopCount = 1e4;
// Validates the type of the result returned by a test function.
template <typename T>
bool CheckType(v8::Local<v8::Value> result) {
return result->IsNumber();
}
template <>
bool CheckType<void>(v8::Local<v8::Value> result) {
return result->IsUndefined();
}
template <>
bool CheckType<int>(v8::Local<v8::Value> result) {
return result->IsInt32();
}
template <>
bool CheckType<int64_t>(v8::Local<v8::Value> result) {
return result->IsBigInt();
}
template <>
bool CheckType<v8::Local<v8::BigInt>>(v8::Local<v8::Value> result) {
return result->IsBigInt();
}
static TestSignatures sigs;
struct ExportedFunction {
std::string name;
FunctionSig* signature;
std::vector<ValueType> locals;
std::vector<uint8_t> code;
};
#define WASM_CODE(...) __VA_ARGS__
#define DECLARE_EXPORTED_FUNCTION(name, sig, code) \
static ExportedFunction k_##name = {#name, sig, {}, code};
#define DECLARE_EXPORTED_FUNCTION_WITH_LOCALS(name, sig, locals, code) \
static ExportedFunction k_##name = {#name, sig, locals, code};
DECLARE_EXPORTED_FUNCTION(nop, sigs.v_v(), WASM_CODE({WASM_NOP}))
DECLARE_EXPORTED_FUNCTION(i32_square, sigs.i_i(),
WASM_CODE({WASM_GET_LOCAL(0), WASM_GET_LOCAL(0),
kExprI32Mul}))
DECLARE_EXPORTED_FUNCTION(i64_square, sigs.l_l(),
WASM_CODE({WASM_GET_LOCAL(0), WASM_GET_LOCAL(0),
kExprI64Mul}))
DECLARE_EXPORTED_FUNCTION(f32_square, sigs.f_f(),
WASM_CODE({WASM_GET_LOCAL(0), WASM_GET_LOCAL(0),
kExprF32Mul}))
DECLARE_EXPORTED_FUNCTION(f64_square, sigs.d_d(),
WASM_CODE({WASM_GET_LOCAL(0), WASM_GET_LOCAL(0),
kExprF64Mul}))
DECLARE_EXPORTED_FUNCTION(void_square, sigs.v_i(),
WASM_CODE({WASM_GET_LOCAL(0), WASM_GET_LOCAL(0),
kExprI32Mul, kExprDrop}))
DECLARE_EXPORTED_FUNCTION(add, sigs.i_ii(),
WASM_CODE({WASM_GET_LOCAL(0), WASM_GET_LOCAL(1),
kExprI32Add}))
DECLARE_EXPORTED_FUNCTION(i64_add, sigs.l_ll(),
WASM_CODE({WASM_GET_LOCAL(0), WASM_GET_LOCAL(1),
kExprI64Add}))
DECLARE_EXPORTED_FUNCTION(sum3, sigs.i_iii(),
WASM_CODE({WASM_GET_LOCAL(0), WASM_GET_LOCAL(1),
WASM_GET_LOCAL(2), kExprI32Add,
kExprI32Add}))
DECLARE_EXPORTED_FUNCTION(no_args, sigs.i_v(), WASM_CODE({WASM_I32V(42)}))
DECLARE_EXPORTED_FUNCTION(load, sigs.i_i(),
WASM_CODE({WASM_LOAD_MEM(MachineType::Int32(),
WASM_GET_LOCAL(0))}))
// int32_t test(int32_t v0, int32_t v1, int32_t v2, int32_t v3, int32_t v4,
// int32_t v5, int32_t v6, int32_t v7, int32_t v8, int32_t v9) {
// return v0 + v1 + v2 + v3 + v4 + v5 + v6 + v7 + v8 + v9;
// }
static const ValueType kIntTypes11[11] = {
kWasmI32, kWasmI32, kWasmI32, kWasmI32, kWasmI32, kWasmI32,
kWasmI32, kWasmI32, kWasmI32, kWasmI32, kWasmI32};
static FunctionSig i_iiiiiiiiii(1, 10, kIntTypes11);
DECLARE_EXPORTED_FUNCTION(
sum10, &i_iiiiiiiiii,
WASM_CODE({WASM_GET_LOCAL(0), WASM_GET_LOCAL(1), WASM_GET_LOCAL(2),
WASM_GET_LOCAL(3), WASM_GET_LOCAL(4), WASM_GET_LOCAL(5),
WASM_GET_LOCAL(6), WASM_GET_LOCAL(7), WASM_GET_LOCAL(8),
WASM_GET_LOCAL(9), kExprI32Add, kExprI32Add, kExprI32Add,
kExprI32Add, kExprI32Add, kExprI32Add, kExprI32Add, kExprI32Add,
kExprI32Add}))
// double test(int32_t i32, int64_t i64, float f32, double f64) {
// return i32 + i64 + f32 + f64;
// }
static const ValueType kMixedTypes5[5] = {kWasmF64, kWasmI32, kWasmI64,
kWasmF32, kWasmF64};
static FunctionSig d_ilfd(1, 4, kMixedTypes5);
DECLARE_EXPORTED_FUNCTION(
sum_mixed, &d_ilfd,
WASM_CODE({WASM_GET_LOCAL(2), kExprF64ConvertF32, WASM_GET_LOCAL(3),
kExprF64Add, WASM_GET_LOCAL(0), kExprF64UConvertI32, kExprF64Add,
WASM_GET_LOCAL(1), kExprF64UConvertI64, kExprF64Add}))
// float f32_square_deopt(float f32) {
// static int count = 0;
// if (++count == kDeoptLoopCount) {
// callback(f32);
// }
// return f32 * f32;
// }
DECLARE_EXPORTED_FUNCTION_WITH_LOCALS(
f32_square_deopt, sigs.f_f(), {kWasmI32},
WASM_CODE(
{WASM_STORE_MEM(
MachineType::Int32(), WASM_I32V(1024),
WASM_TEE_LOCAL(1, WASM_I32_ADD(WASM_LOAD_MEM(MachineType::Int32(),
WASM_I32V(1024)),
WASM_ONE))),
WASM_BLOCK(
WASM_BR_IF(0, WASM_I32_NE(WASM_GET_LOCAL(1),
WASM_I32V(kDeoptLoopCount))),
WASM_CALL_FUNCTION(0, WASM_F64_CONVERT_F32(WASM_GET_LOCAL(0)))),
WASM_F32_MUL(WASM_GET_LOCAL(0), WASM_GET_LOCAL(0))}))
// double f64_square_deopt(double f64) {
// static int count = 0;
// if (++count == kDeoptLoopCount) {
// callback(f64);
// }
// return f64 * f64;
// }
DECLARE_EXPORTED_FUNCTION_WITH_LOCALS(
f64_square_deopt, sigs.d_d(), {kWasmI32},
WASM_CODE(
{WASM_STORE_MEM(
MachineType::Int32(), WASM_I32V(1028),
WASM_TEE_LOCAL(1, WASM_I32_ADD(WASM_LOAD_MEM(MachineType::Int32(),
WASM_I32V(1028)),
WASM_ONE))),
WASM_BLOCK(WASM_BR_IF(0, WASM_I32_NE(WASM_GET_LOCAL(1),
WASM_I32V(kDeoptLoopCount))),
WASM_CALL_FUNCTION(0, WASM_GET_LOCAL(0))),
WASM_F64_MUL(WASM_GET_LOCAL(0), WASM_GET_LOCAL(0))}))
// int32_t i32_square_deopt(int32_t i32) {
// static int count = 0;
// if (++count == kDeoptLoopCount) {
// callback(i32);
// }
// return i32 * i32;
// }
DECLARE_EXPORTED_FUNCTION_WITH_LOCALS(
i32_square_deopt, sigs.i_i(), {kWasmI32},
WASM_CODE(
{WASM_STORE_MEM(
MachineType::Int32(), WASM_I32V(1032),
WASM_TEE_LOCAL(1, WASM_I32_ADD(WASM_LOAD_MEM(MachineType::Int32(),
WASM_I32V(1032)),
WASM_ONE))),
WASM_BLOCK(
WASM_BR_IF(0, WASM_I32_NE(WASM_GET_LOCAL(1),
WASM_I32V(kDeoptLoopCount))),
WASM_CALL_FUNCTION(0, WASM_F64_SCONVERT_I32(WASM_GET_LOCAL(0)))),
WASM_I32_MUL(WASM_GET_LOCAL(0), WASM_GET_LOCAL(0))}))
// int64_t i64_square_deopt(int64_t i64) {
// static int count = 0;
// if (++count == kDeoptLoopCount) {
// callback(i64);
// }
// return i64 * i64;
// }
DECLARE_EXPORTED_FUNCTION_WITH_LOCALS(
i64_square_deopt, sigs.l_l(), {kWasmI32},
WASM_CODE(
{WASM_STORE_MEM(
MachineType::Int32(), WASM_I32V(1036),
WASM_TEE_LOCAL(1, WASM_I32_ADD(WASM_LOAD_MEM(MachineType::Int32(),
WASM_I32V(1036)),
WASM_ONE))),
WASM_BLOCK(
WASM_BR_IF(0, WASM_I32_NE(WASM_GET_LOCAL(1),
WASM_I32V(kDeoptLoopCount))),
WASM_CALL_FUNCTION(0, WASM_F64_SCONVERT_I64(WASM_GET_LOCAL(0)))),
WASM_I64_MUL(WASM_GET_LOCAL(0), WASM_GET_LOCAL(0))}))
// void void_square_deopt(int32_t i32) {
// static int count = 0;
// if (++count == kDeoptLoopCount) {
// callback(i32);
// }
// }
DECLARE_EXPORTED_FUNCTION_WITH_LOCALS(
void_square_deopt, sigs.v_i(), {kWasmI32},
WASM_CODE(
{WASM_STORE_MEM(
MachineType::Int32(), WASM_I32V(1040),
WASM_TEE_LOCAL(1, WASM_I32_ADD(WASM_LOAD_MEM(MachineType::Int32(),
WASM_I32V(1040)),
WASM_ONE))),
WASM_BLOCK(
WASM_BR_IF(0, WASM_I32_NE(WASM_GET_LOCAL(1),
WASM_I32V(kDeoptLoopCount))),
WASM_CALL_FUNCTION(0, WASM_F64_SCONVERT_I32(WASM_GET_LOCAL(0))))}))
class FastJSWasmCallTester {
public:
FastJSWasmCallTester()
: allow_natives_syntax_(&i::FLAG_allow_natives_syntax, true),
inline_js_wasm_calls_(&i::FLAG_turbo_inline_js_wasm_calls, true),
stress_background_compile_(&i::FLAG_stress_background_compile, false),
allocator_(),
zone_(&allocator_, ZONE_NAME),
builder_(zone_.New<WasmModuleBuilder>(&zone_)) {}
void DeclareCallback(const char* name, FunctionSig* signature,
const char* module) {
builder_->AddImport(CStrVector(name), signature, CStrVector(module));
}
void AddExportedFunction(const ExportedFunction& exported_func) {
WasmFunctionBuilder* func = builder_->AddFunction(exported_func.signature);
for (auto& wasm_type : exported_func.locals) func->AddLocal(wasm_type);
func->EmitCode(exported_func.code.data(),
static_cast<uint32_t>(exported_func.code.size()));
func->Emit(kExprEnd);
builder_->AddExport(CStrVector(exported_func.name.c_str()),
kExternalFunction, func->func_index());
}
// Executes a test function that returns a value of type T.
template <typename T>
void CallAndCheckWasmFunction(const std::string& exported_function_name,
const std::vector<v8::Local<v8::Value>>& args,
const T& expected_result,
bool test_lazy_deopt = false) {
LocalContext env;
v8::Local<v8::Value> result_value = DoCallAndCheckWasmFunction(
env, exported_function_name, args, test_lazy_deopt);
CHECK(CheckType<T>(result_value));
T result = ConvertJSValue<T>::Get(result_value, env.local()).ToChecked();
CHECK_EQ(result, expected_result);
}
// Executes a test function that returns NaN.
void CallAndCheckWasmFunctionNaN(
const std::string& exported_function_name,
const std::vector<v8::Local<v8::Value>>& args,
bool test_lazy_deopt = false) {
LocalContext env;
v8::Local<v8::Value> result_value = DoCallAndCheckWasmFunction(
env, exported_function_name, args, test_lazy_deopt);
CHECK(CheckType<double>(result_value));
double result =
ConvertJSValue<double>::Get(result_value, env.local()).ToChecked();
CHECK(std::isnan(result));
}
// Executes a test function that returns a BigInt.
void CallAndCheckWasmFunctionBigInt(
const std::string& exported_function_name,
const std::vector<v8::Local<v8::Value>>& args,
const v8::Local<v8::BigInt> expected_result,
bool test_lazy_deopt = false) {
LocalContext env;
v8::Local<v8::Value> result_value = DoCallAndCheckWasmFunction(
env, exported_function_name, args, test_lazy_deopt);
CHECK(CheckType<v8::Local<v8::BigInt>>(result_value));
auto result =
ConvertJSValue<v8::BigInt>::Get(result_value, env.local()).ToChecked();
CHECK_EQ(result->Int64Value(), expected_result->Int64Value());
}
// Executes a test function that returns void.
void CallAndCheckWasmFunction(const std::string& exported_function_name,
const std::vector<v8::Local<v8::Value>>& args,
bool test_lazy_deopt = false) {
LocalContext env;
v8::Local<v8::Value> result_value = DoCallAndCheckWasmFunction(
env, exported_function_name, args, test_lazy_deopt);
CHECK(test_lazy_deopt ? result_value->IsNumber() /* NaN */
: result_value->IsUndefined());
}
// Executes a test function that triggers eager deoptimization.
template <typename T>
T CallAndCheckWasmFunctionWithEagerDeopt(
const std::string& exported_function_name, const std::string& arg,
const T& expected_result, const std::string& deopt_arg) {
LocalContext env;
v8::Isolate* isolate = CcTest::isolate();
v8::TryCatch try_catch(isolate);
std::string js_code =
"const importObj = {"
" env: {"
" callback : function(num) {}"
" }"
"};"
"let buf = new Uint8Array(" +
WasmModuleAsJSArray() +
");"
"let module = new WebAssembly.Module(buf);"
"let instance = new WebAssembly.Instance(module, importObj);"
"function test(value) {"
" return instance.exports." +
exported_function_name +
"(value);"
"}"
"%PrepareFunctionForOptimization(test);"
"test(" +
arg +
");"
"%OptimizeFunctionOnNextCall(test);"
"test(" +
arg + ");";
v8::Local<v8::Value> result_value = CompileRun(js_code.c_str());
CHECK(CheckType<T>(result_value));
T result = ConvertJSValue<T>::Get(result_value, env.local()).ToChecked();
CHECK_EQ(result, expected_result);
std::string deopt_code = "test(" + deopt_arg + ");";
result_value = CompileRun(deopt_code.c_str());
CHECK(CheckType<T>(result_value));
return ConvertJSValue<T>::Get(result_value, env.local()).ToChecked();
}
// Executes a test function that throws an exception.
void CallAndCheckExceptionCaught(const std::string& exported_function_name,
const v8::Local<v8::Value> arg) {
LocalContext env;
CHECK((*env)->Global()->Set(env.local(), v8_str("arg"), arg).FromJust());
v8::Isolate* isolate = CcTest::isolate();
v8::TryCatch try_catch(isolate);
std::string js_code =
"const importObj = {"
" env: {"
" callback : function(num) {}"
" }"
"};"
"let buf = new Uint8Array(" +
WasmModuleAsJSArray() +
");"
"let module = new WebAssembly.Module(buf);"
"let instance = new WebAssembly.Instance(module, importObj);"
"let " +
exported_function_name + " = instance.exports." +
exported_function_name +
";"
"function test() {"
" return " +
exported_function_name +
"(arg);"
"}"
"%PrepareFunctionForOptimization(test);"
"test();";
CompileRun(js_code.c_str());
CHECK(try_catch.HasCaught());
try_catch.Reset();
CompileRun("%OptimizeFunctionOnNextCall(test); test();");
CHECK(try_catch.HasCaught());
}
// Executes a test function with a try/catch.
template <typename T>
void CallAndCheckWithTryCatch(const std::string& exported_function_name,
const v8::Local<v8::Value> arg) {
LocalContext env;
CHECK((*env)->Global()->Set(env.local(), v8_str("arg"), arg).FromJust());
std::string js_code =
"const importObj = {"
" env: {"
" callback : function(num) {}"
" }"
"};"
"let buf = new Uint8Array(" +
WasmModuleAsJSArray() +
");"
"let module = new WebAssembly.Module(buf);"
"let instance = new WebAssembly.Instance(module, importObj);"
"let " +
exported_function_name + " = instance.exports." +
exported_function_name +
";"
"function test() {"
" try {"
" return " +
exported_function_name +
"(arg);"
" } catch (e) {"
" return 0;"
" }"
"}"
"%PrepareFunctionForOptimization(test);"
"test();";
v8::Local<v8::Value> result_value_interpreted = CompileRun(js_code.c_str());
CHECK(CheckType<T>(result_value_interpreted));
T result_interpreted =
ConvertJSValue<T>::Get(result_value_interpreted, env.local())
.ToChecked();
v8::Local<v8::Value> result_value_compiled = CompileRun(
"%OptimizeFunctionOnNextCall(test);"
"test();");
CHECK(CheckType<T>(result_value_compiled));
T result_compiled =
ConvertJSValue<T>::Get(result_value_compiled, env.local()).ToChecked();
CHECK_EQ(result_interpreted, result_compiled);
}
private:
// Convert the code of a Wasm module into a string that represents the content
// of a JavaScript Uint8Array, that can be loaded with
// WebAssembly.Module(buf).
std::string WasmModuleAsJSArray() {
ZoneBuffer buffer(&zone_);
builder_->WriteTo(&buffer);
std::stringstream string_stream;
string_stream << "[";
auto it = buffer.begin();
if (it != buffer.end()) {
string_stream << "0x" << std::setfill('0') << std::setw(2) << std::hex
<< static_cast<int>(*it++);
}
while (it != buffer.end()) {
string_stream << ", 0x" << std::setfill('0') << std::setw(2) << std::hex
<< static_cast<int>(*it++);
}
string_stream << "]";
return string_stream.str();
}
v8::Local<v8::Value> DoCallAndCheckWasmFunction(
LocalContext& env, const std::string& exported_function_name,
const std::vector<v8::Local<v8::Value>>& args,
bool test_lazy_deopt = false) {
for (size_t i = 0; i < args.size(); i++) {
CHECK((*env)
->Global()
->Set(env.local(), v8_str(("arg" + std::to_string(i)).c_str()),
args[i])
.FromJust());
}
std::string js_code =
test_lazy_deopt
? GetJSTestCodeWithLazyDeopt(env, WasmModuleAsJSArray(),
exported_function_name, args.size())
: GetJSTestCode(WasmModuleAsJSArray(), exported_function_name,
args.size());
return CompileRun(js_code.c_str());
}
// Format the JS test code that loads and instantiates a Wasm module and
// calls a Wasm exported function, making sure that it is compiled by
// TurboFan:
//
// function test() {"
// let result = exported_func(arg0, arg1, ..., argN-1);
// return result;"
// }
std::string GetJSTestCode(const std::string& wasm_module,
const std::string& wasm_exported_function_name,
size_t arity) {
std::string js_args = ArgsToString(arity);
return "const importObj = {"
" env: { callback : function(num) {} }"
"};"
"let buf = new Uint8Array(" +
wasm_module +
");"
"let module = new WebAssembly.Module(buf);"
"let instance = new WebAssembly.Instance(module, importObj);"
"let " +
wasm_exported_function_name + " = instance.exports." +
wasm_exported_function_name +
";"
"function test() {"
" let result = " +
wasm_exported_function_name + "(" + js_args +
");"
" return result;"
"}"
"%PrepareFunctionForOptimization(test);"
"test(" +
js_args +
");"
"%OptimizeFunctionOnNextCall(test);"
"test(" +
js_args + ");";
}
// Format the JS test code that loads and instantiates a Wasm module and
// calls a Wasm exported function in a loop, and it's compiled with TurboFan:
//
// var b = 0;"
// var n = 0;"
// function test() {"
// let result = 0;
// for(var i = 0; i < 1e5; i++) {
// result = exported_func(arg0 + b) + n;
// }
// return result;"
// }
//
// Here the Wasm function calls back into a JavaScript function that modifies
// the values of 'b' and 'n', triggering the lazy deoptimization of the 'test'
// function.
std::string GetJSTestCodeWithLazyDeopt(
LocalContext& env, const std::string& wasm_module,
const std::string& wasm_exported_function_name, size_t arity) {
DCHECK_LE(arity, 1);
bool bigint_arg = false;
if (arity == 1) {
v8::Local<v8::Value> arg0 =
(*env)->Global()->Get(env.local(), v8_str("arg0")).ToLocalChecked();
bigint_arg = arg0->IsBigInt();
}
std::string js_args = ArgsToString(arity);
std::string code =
"const importObj = {"
" env: {"
" callback : function(num) {"
" n = 1; b = 1;"
" }"
" }"
"};"
"let buf = new Uint8Array(" +
wasm_module +
");"
"let module = new WebAssembly.Module(buf);"
"let instance = new WebAssembly.Instance(module, importObj);"
"let " +
wasm_exported_function_name + " = instance.exports." +
wasm_exported_function_name +
";"
"var b = 0;"
"var n = 0;"
"function test(" +
js_args +
") {"
" var result = 0;"
" for (let i = 0; i < " +
std::to_string(kDeoptLoopCount) + " + 5; i++) {";
code += bigint_arg ? " result = " + wasm_exported_function_name + "(" +
js_args + "+ BigInt(b)) + BigInt(n);"
: " result = " + wasm_exported_function_name + "(" +
js_args + "+ b) + n;";
code +=
" }"
" return result;"
"}"
"test(" +
js_args + ");";
return code;
}
// Format a string that represents the set of arguments passed to a test
// function, in the form 'arg0, arg1, ..., argN-1'.
// The value of these args is set by GetJSTestCodeWithLazyDeopt.
std::string ArgsToString(size_t arity) {
std::stringstream string_stream;
for (size_t i = 0; i < arity; i++) {
if (i > 0) string_stream << ", ";
string_stream << "arg" << i;
}
return string_stream.str();
}
i::FlagScope<bool> allow_natives_syntax_;
i::FlagScope<bool> inline_js_wasm_calls_;
i::FlagScope<bool> stress_background_compile_;
AccountingAllocator allocator_;
Zone zone_;
WasmModuleBuilder* builder_;
};
TEST(TestFastJSWasmCall_Nop) {
v8::HandleScope scope(CcTest::isolate());
FastJSWasmCallTester tester;
tester.AddExportedFunction(k_nop);
tester.CallAndCheckWasmFunction("nop", {});
}
TEST(TestFastJSWasmCall_I32Arg) {
v8::HandleScope scope(CcTest::isolate());
FastJSWasmCallTester tester;
tester.AddExportedFunction(k_i32_square);
tester.CallAndCheckWasmFunction<int32_t>("i32_square", {v8_num(42)}, 42 * 42);
}
TEST(TestFastJSWasmCall_I32ArgNotSmi) {
v8::HandleScope scope(CcTest::isolate());
FastJSWasmCallTester tester;
tester.AddExportedFunction(k_add);
tester.CallAndCheckWasmFunction<int32_t>(
"add", {v8_num(0x7fffffff), v8_int(1)}, 0x80000000);
}
TEST(TestFastJSWasmCall_F32Arg) {
v8::HandleScope scope(CcTest::isolate());
FastJSWasmCallTester tester;
tester.AddExportedFunction(k_f32_square);
tester.CallAndCheckWasmFunction<float>("f32_square", {v8_num(42.0)},
42.0 * 42.0);
}
TEST(TestFastJSWasmCall_F64Arg) {
v8::HandleScope scope(CcTest::isolate());
FastJSWasmCallTester tester;
tester.AddExportedFunction(k_f64_square);
tester.CallAndCheckWasmFunction<double>("f64_square", {v8_num(42.0)},
42.0 * 42.0);
}
TEST(TestFastJSWasmCall_I64Arg) {
v8::HandleScope scope(CcTest::isolate());
FastJSWasmCallTester tester;
tester.AddExportedFunction(k_i64_square);
tester.CallAndCheckWasmFunctionBigInt("i64_square", {v8_bigint(1234567890ll)},
v8_bigint(1234567890ll * 1234567890ll));
}
TEST(TestFastJSWasmCall_I64NegativeResult) {
v8::HandleScope scope(CcTest::isolate());
FastJSWasmCallTester tester;
tester.AddExportedFunction(k_i64_add);
tester.CallAndCheckWasmFunctionBigInt(
"i64_add", {v8_bigint(1ll), v8_bigint(-2ll)}, v8_bigint(-1ll));
}
TEST(TestFastJSWasmCall_MultipleArgs) {
v8::HandleScope scope(CcTest::isolate());
FastJSWasmCallTester tester;
tester.AddExportedFunction(k_sum10);
tester.CallAndCheckWasmFunction<int32_t>(
"sum10",
{v8_num(1), v8_num(2), v8_num(3), v8_num(4), v8_num(5), v8_num(6),
v8_num(7), v8_num(8), v8_num(9), v8_num(10)},
55);
}
TEST(TestFastJSWasmCall_MixedArgs) {
v8::HandleScope scope(CcTest::isolate());
FastJSWasmCallTester tester;
tester.AddExportedFunction(k_sum_mixed);
tester.CallAndCheckWasmFunction<double>(
"sum_mixed", {v8_num(1), v8_bigint(0x80000000), v8_num(42.0), v8_num(.5)},
1 + 0x80000000 + 42 + .5);
}
TEST(TestFastJSWasmCall_MistypedArgs) {
v8::HandleScope scope(CcTest::isolate());
FastJSWasmCallTester tester;
tester.AddExportedFunction(k_i32_square);
tester.CallAndCheckWasmFunction<int32_t>("i32_square", {v8_str("test")}, 0);
}
TEST(TestFastJSWasmCall_MixedMistypedArgs) {
v8::HandleScope scope(CcTest::isolate());
FastJSWasmCallTester tester;
tester.AddExportedFunction(k_sum_mixed);
tester.CallAndCheckWasmFunctionNaN(
"sum_mixed", {v8_str("alpha"), v8_bigint(0x80000000), v8_str("beta"),
v8_str("gamma")});
}
TEST(TestFastJSWasmCall_NoArgs) {
v8::HandleScope scope(CcTest::isolate());
FastJSWasmCallTester tester;
tester.AddExportedFunction(k_no_args);
tester.CallAndCheckWasmFunction<int32_t>("no_args", {}, 42);
}
TEST(TestFastJSWasmCall_NoReturnTypes) {
v8::HandleScope scope(CcTest::isolate());
FastJSWasmCallTester tester;
tester.AddExportedFunction(k_void_square);
tester.CallAndCheckWasmFunction("void_square", {v8_num(42)});
}
TEST(TestFastJSWasmCall_MismatchedArity) {
v8::HandleScope scope(CcTest::isolate());
FastJSWasmCallTester tester;
tester.AddExportedFunction(k_sum3);
tester.CallAndCheckWasmFunction<int32_t>("sum3", {v8_num(1), v8_num(2)}, 3);
tester.CallAndCheckWasmFunction<int32_t>(
"sum3",
{v8_num(1), v8_num(2), v8_num(3), v8_num(4), v8_num(5), v8_num(6)}, 6);
tester.CallAndCheckWasmFunction<int32_t>("sum3", {}, 0);
}
// Lazy deoptimization tests
TEST(TestFastJSWasmCall_LazyDeopt_I32Result) {
v8::HandleScope scope(CcTest::isolate());
FastJSWasmCallTester tester;
tester.DeclareCallback("callback", sigs.v_d(), "env");
tester.AddExportedFunction(k_i32_square_deopt);
tester.CallAndCheckWasmFunction<int32_t>("i32_square_deopt", {v8_num(42)},
43 * 43 + 1, true);
}
TEST(TestFastJSWasmCall_LazyDeopt_I64Result) {
v8::HandleScope scope(CcTest::isolate());
FastJSWasmCallTester tester;
tester.DeclareCallback("callback", sigs.v_d(), "env");
tester.AddExportedFunction(k_i64_square_deopt);
tester.CallAndCheckWasmFunctionBigInt("i64_square_deopt", {v8_bigint(42)},
v8_bigint(43 * 43 + 1), true);
// This test would fail if the result was converted into a HeapNumber through
// a double, losing precision.
tester.CallAndCheckWasmFunctionBigInt(
"i64_square_deopt", {v8_bigint(1234567890ll)},
v8_bigint(1524157877488187882ll), // (1234567890 + 1)*(1234567890 + 1)+1
true);
}
TEST(TestFastJSWasmCall_LazyDeopt_F32Result) {
v8::HandleScope scope(CcTest::isolate());
FastJSWasmCallTester tester;
tester.DeclareCallback("callback", sigs.v_d(), "env");
tester.AddExportedFunction(k_f32_square_deopt);
tester.CallAndCheckWasmFunction<float>("f32_square_deopt", {v8_num(42.0)},
43 * 43 + 1, true);
}
TEST(TestFastJSWasmCall_LazyDeopt_F64Result) {
v8::HandleScope scope(CcTest::isolate());
FastJSWasmCallTester tester;
tester.DeclareCallback("callback", sigs.v_d(), "env");
tester.AddExportedFunction(k_f64_square_deopt);
tester.CallAndCheckWasmFunction<float>("f64_square_deopt", {v8_num(42.0)},
43 * 43 + 1, true);
}
TEST(TestFastJSWasmCall_LazyDeopt_VoidResult) {
v8::HandleScope scope(CcTest::isolate());
FastJSWasmCallTester tester;
tester.DeclareCallback("callback", sigs.v_d(), "env");
tester.AddExportedFunction(k_void_square_deopt);
tester.CallAndCheckWasmFunction("void_square_deopt", {v8_num(42.0)}, true);
}
// Eager deoptimization tests
TEST(TestFastJSWasmCall_EagerDeopt) {
v8::HandleScope scope(CcTest::isolate());
FastJSWasmCallTester tester;
tester.AddExportedFunction(k_f32_square);
float result_after_deopt =
tester.CallAndCheckWasmFunctionWithEagerDeopt<float>(
"f32_square", "42", 42.0 * 42.0, "{x:1,y:2}");
CHECK(std::isnan(result_after_deopt));
}
// Exception handling tests
TEST(TestFastJSWasmCall_Trap) {
v8::HandleScope scope(CcTest::isolate());
FastJSWasmCallTester tester;
tester.AddExportedFunction(k_load);
tester.CallAndCheckWithTryCatch<int>("load", {v8_int(0x7fffffff)});
}
TEST(TestFastJSWasmCall_I64ArgExpectsBigInt) {
v8::HandleScope scope(CcTest::isolate());
FastJSWasmCallTester tester;
tester.AddExportedFunction(k_i64_square);
tester.CallAndCheckExceptionCaught("i64_square", v8_int(42));
}
TEST(TestFastJSWasmCall_F32ArgDoesntExpectBigInt) {
v8::HandleScope scope(CcTest::isolate());
FastJSWasmCallTester tester;
tester.AddExportedFunction(k_f32_square);
tester.CallAndCheckExceptionCaught("f32_square", v8_bigint(42ll));
}
TEST(TestFastJSWasmCall_F64ArgDoesntExpectBigInt) {
v8::HandleScope scope(CcTest::isolate());
FastJSWasmCallTester tester;
tester.AddExportedFunction(k_f64_square);
tester.CallAndCheckExceptionCaught("f64_square", v8_bigint(42ll));
}
TEST(TestFastJSWasmCall_I32ArgDoesntExpectBigInt) {
v8::HandleScope scope(CcTest::isolate());
FastJSWasmCallTester tester;
tester.AddExportedFunction(k_i32_square);
tester.CallAndCheckExceptionCaught("i32_square", v8_bigint(42ll));
}
} // namespace wasm
} // namespace internal
} // namespace v8

View File

@ -47,6 +47,7 @@ class TestSignatures {
sig_v_iii(0, 3, kIntTypes4),
sig_v_e(0, 1, kExternRefTypes4),
sig_v_c(0, 1, kFuncTypes4),
sig_v_d(0, 1, kDoubleTypes4),
sig_s_i(1, 1, kSimd128IntTypes4),
sig_s_s(1, 1, kSimd128Types4),
sig_s_ss(1, 2, kSimd128Types4),
@ -111,6 +112,7 @@ class TestSignatures {
FunctionSig* v_iii() { return &sig_v_iii; }
FunctionSig* v_e() { return &sig_v_e; }
FunctionSig* v_c() { return &sig_v_c; }
FunctionSig* v_d() { return &sig_v_d; }
FunctionSig* s_i() { return &sig_s_i; }
FunctionSig* s_s() { return &sig_s_s; }
FunctionSig* s_ss() { return &sig_s_ss; }
@ -178,6 +180,7 @@ class TestSignatures {
FunctionSig sig_v_iii;
FunctionSig sig_v_e;
FunctionSig sig_v_c;
FunctionSig sig_v_d;
FunctionSig sig_s_i;
FunctionSig sig_s_s;
FunctionSig sig_s_ss;