[fastcall] Support JSArray as arguments
This CL adds support in TurboFan for passing JSArrays as arguments to fast API callbacks. It also extends the v8::Array class with a CopyAndConvertArrayToCppBuffer method to allow the embedder to perform quick conversions of their JSArrays to a C++ buffer. The CL also adds tests in d8. Design doc: https://docs.google.com/document/d/1BNKKZNgrGYafx8kqSfNEQqQYY5n4A6mGufss_Vz-h-4/edit#heading=h.c0kgf82jnlpp Bug: chromium:1052746, chromium:715122 Change-Id: If47ac60d9ebe6462bbf3adff002e2da8e14e8fc8 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2940900 Commit-Queue: Maya Lekova <mslekova@chromium.org> Reviewed-by: Camillo Bruni <cbruni@chromium.org> Reviewed-by: Georg Neis <neis@chromium.org> Reviewed-by: Jakob Kummerow <jkummerow@chromium.org> Cr-Commit-Position: refs/heads/master@{#75333}
This commit is contained in:
parent
70dd5f89e3
commit
d0aebc06e0
@ -156,6 +156,7 @@
|
||||
* - float64_t
|
||||
* Currently supported argument types:
|
||||
* - pointer to an embedder type
|
||||
* - JavaScript array of primitive types
|
||||
* - bool
|
||||
* - int32_t
|
||||
* - uint32_t
|
||||
@ -176,7 +177,7 @@
|
||||
* passes NaN values as-is, i.e. doesn't normalize them.
|
||||
*
|
||||
* To be supported types:
|
||||
* - arrays of C types
|
||||
* - TypedArrays and ArrayBuffers
|
||||
* - arrays of embedder types
|
||||
*
|
||||
*
|
||||
@ -539,26 +540,41 @@ struct TypeInfoHelper {
|
||||
} \
|
||||
};
|
||||
|
||||
#define BASIC_C_TYPES(V) \
|
||||
V(void, kVoid) \
|
||||
V(bool, kBool) \
|
||||
V(int32_t, kInt32) \
|
||||
V(uint32_t, kUint32) \
|
||||
V(int64_t, kInt64) \
|
||||
V(uint64_t, kUint64) \
|
||||
V(float, kFloat32) \
|
||||
V(double, kFloat64) \
|
||||
V(ApiObject, kApiObject) \
|
||||
V(v8::Local<v8::Value>, kV8Value) \
|
||||
V(v8::Local<v8::Object>, kV8Value)
|
||||
template <CTypeInfo::Type type>
|
||||
struct CTypeInfoTraits {};
|
||||
|
||||
#define DEFINE_TYPE_INFO_TRAITS(CType, Enum) \
|
||||
template <> \
|
||||
struct CTypeInfoTraits<CTypeInfo::Type::Enum> { \
|
||||
using ctype = CType; \
|
||||
};
|
||||
|
||||
#define PRIMITIVE_C_TYPES(V) \
|
||||
V(bool, kBool) \
|
||||
V(int32_t, kInt32) \
|
||||
V(uint32_t, kUint32) \
|
||||
V(int64_t, kInt64) \
|
||||
V(uint64_t, kUint64) \
|
||||
V(float, kFloat32) \
|
||||
V(double, kFloat64)
|
||||
|
||||
// Same as above, but includes deprecated types for compatibility.
|
||||
#define ALL_C_TYPES(V) \
|
||||
PRIMITIVE_C_TYPES(V) \
|
||||
V(void, kVoid) \
|
||||
V(v8::Local<v8::Value>, kV8Value) \
|
||||
V(v8::Local<v8::Object>, kV8Value) \
|
||||
V(ApiObject, kApiObject)
|
||||
|
||||
// ApiObject was a temporary solution to wrap the pointer to the v8::Value.
|
||||
// Please use v8::Local<v8::Value> in new code for the arguments and
|
||||
// v8::Local<v8::Object> for the receiver, as ApiObject will be deprecated.
|
||||
|
||||
BASIC_C_TYPES(SPECIALIZE_GET_TYPE_INFO_HELPER_FOR)
|
||||
ALL_C_TYPES(SPECIALIZE_GET_TYPE_INFO_HELPER_FOR)
|
||||
PRIMITIVE_C_TYPES(DEFINE_TYPE_INFO_TRAITS)
|
||||
|
||||
#undef BASIC_C_TYPES
|
||||
#undef PRIMITIVE_C_TYPES
|
||||
#undef ALL_C_TYPES
|
||||
|
||||
#define SPECIALIZE_GET_TYPE_INFO_HELPER_FOR_TA(T, Enum) \
|
||||
template <> \
|
||||
@ -744,6 +760,22 @@ CFunction CFunction::ArgUnwrap<R (*)(Args...)>::Make(R (*func)(Args...)) {
|
||||
|
||||
using CFunctionBuilder = internal::CFunctionBuilder;
|
||||
|
||||
/**
|
||||
* Copies the contents of this JavaScript array to a C++ buffer with
|
||||
* a given max_length. A CTypeInfo is passed as an argument,
|
||||
* instructing different rules for conversion (e.g. restricted float/double).
|
||||
* The element type T of the destination array must match the C type
|
||||
* corresponding to the CTypeInfo (specified by CTypeInfoTraits).
|
||||
* If the array length is larger than max_length or the array is of
|
||||
* unsupported type, the operation will fail, returning false. Generally, an
|
||||
* array which contains objects, undefined, null or anything not convertible
|
||||
* to the requested destination type, is considered unsupported. The operation
|
||||
* returns true on success. `type_info` will be used for conversions.
|
||||
*/
|
||||
template <typename T, const CTypeInfo* type_info>
|
||||
bool CopyAndConvertArrayToCppBuffer(Local<Array> src, T* dst,
|
||||
uint32_t max_length);
|
||||
|
||||
} // namespace v8
|
||||
|
||||
#endif // INCLUDE_V8_FAST_API_CALLS_H_
|
||||
|
@ -50,6 +50,7 @@ class CFunction;
|
||||
class CallHandlerHelper;
|
||||
class Context;
|
||||
class CppHeap;
|
||||
class CTypeInfo;
|
||||
class Data;
|
||||
class Date;
|
||||
class EscapableHandleScope;
|
||||
@ -4431,6 +4432,7 @@ class V8_EXPORT Array : public Object {
|
||||
static Local<Array> New(Isolate* isolate, Local<Value>* elements,
|
||||
size_t length);
|
||||
V8_INLINE static Array* Cast(Value* obj);
|
||||
|
||||
private:
|
||||
Array();
|
||||
static void CheckCast(Value* obj);
|
||||
|
@ -5,6 +5,7 @@
|
||||
#ifndef V8_API_API_INL_H_
|
||||
#define V8_API_API_INL_H_
|
||||
|
||||
#include "include/v8-fast-api-calls.h"
|
||||
#include "src/api/api.h"
|
||||
#include "src/execution/interrupts-scope.h"
|
||||
#include "src/execution/microtask-queue.h"
|
||||
@ -240,6 +241,58 @@ inline bool IsExecutionTerminatingCheck(i::Isolate* isolate) {
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void CopySmiElementsToTypedBuffer(T* dst, uint32_t length,
|
||||
i::FixedArray elements) {
|
||||
for (uint32_t i = 0; i < length; ++i) {
|
||||
double value = elements.get(static_cast<int>(i)).Number();
|
||||
// TODO(mslekova): Avoid converting back-and-forth when possible, e.g
|
||||
// avoid int->double->int conversions to boost performance.
|
||||
dst[i] = i::ConvertDouble<T>(value);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void CopyDoubleElementsToTypedBuffer(T* dst, uint32_t length,
|
||||
i::FixedDoubleArray elements) {
|
||||
for (uint32_t i = 0; i < length; ++i) {
|
||||
double value = elements.get_scalar(static_cast<int>(i));
|
||||
// TODO(mslekova): There are certain cases, e.g. double->double, in which
|
||||
// we could do a memcpy directly.
|
||||
dst[i] = i::ConvertDouble<T>(value);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, const CTypeInfo* type_info>
|
||||
bool CopyAndConvertArrayToCppBuffer(Local<Array> src, T* dst,
|
||||
uint32_t max_length) {
|
||||
static_assert(
|
||||
std::is_same<
|
||||
T, typename i::CTypeInfoTraits<type_info->GetType()>::ctype>::value,
|
||||
"Type mismatch between the expected CTypeInfo::Type and the destination "
|
||||
"array");
|
||||
|
||||
uint32_t length = src->Length();
|
||||
if (length > max_length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
i::DisallowGarbageCollection no_gc;
|
||||
i::JSArray obj = *reinterpret_cast<i::JSArray*>(*src);
|
||||
|
||||
i::FixedArrayBase elements = obj.elements();
|
||||
if (obj.HasSmiElements()) {
|
||||
CopySmiElementsToTypedBuffer(dst, length, i::FixedArray::cast(elements));
|
||||
return true;
|
||||
} else if (obj.HasDoubleElements()) {
|
||||
CopyDoubleElementsToTypedBuffer(dst, length,
|
||||
i::FixedDoubleArray::cast(elements));
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
namespace internal {
|
||||
|
||||
Handle<Context> HandleScopeImplementer::LastEnteredContext() {
|
||||
|
@ -10266,6 +10266,49 @@ void InvokeFinalizationRegistryCleanupFromTask(
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
EXPORT_TEMPLATE_DEFINE(V8_EXPORT_PRIVATE)
|
||||
int32_t ConvertDouble(double d) {
|
||||
return internal::DoubleToInt32(d);
|
||||
}
|
||||
|
||||
template <>
|
||||
EXPORT_TEMPLATE_DEFINE(V8_EXPORT_PRIVATE)
|
||||
uint32_t ConvertDouble(double d) {
|
||||
return internal::DoubleToUint32(d);
|
||||
}
|
||||
|
||||
template <>
|
||||
EXPORT_TEMPLATE_DEFINE(V8_EXPORT_PRIVATE)
|
||||
float ConvertDouble(double d) {
|
||||
return internal::DoubleToFloat32(d);
|
||||
}
|
||||
|
||||
template <>
|
||||
EXPORT_TEMPLATE_DEFINE(V8_EXPORT_PRIVATE)
|
||||
double ConvertDouble(double d) {
|
||||
return d;
|
||||
}
|
||||
|
||||
template <>
|
||||
EXPORT_TEMPLATE_DEFINE(V8_EXPORT_PRIVATE)
|
||||
int64_t ConvertDouble(double d) {
|
||||
return internal::DoubleToWebIDLInt64(d);
|
||||
}
|
||||
|
||||
template <>
|
||||
EXPORT_TEMPLATE_DEFINE(V8_EXPORT_PRIVATE)
|
||||
uint64_t ConvertDouble(double d) {
|
||||
return internal::DoubleToWebIDLUint64(d);
|
||||
}
|
||||
|
||||
template <>
|
||||
EXPORT_TEMPLATE_DEFINE(V8_EXPORT_PRIVATE)
|
||||
bool ConvertDouble(double d) {
|
||||
// Implements https://tc39.es/ecma262/#sec-toboolean.
|
||||
return !std::isnan(d) && d != 0;
|
||||
}
|
||||
|
||||
// Undefine macros for jumbo build.
|
||||
#undef SET_FIELD_WRAPPED
|
||||
#undef NEW_STRING
|
||||
|
@ -536,6 +536,10 @@ void InvokeFinalizationRegistryCleanupFromTask(
|
||||
Handle<JSFinalizationRegistry> finalization_registry,
|
||||
Handle<Object> callback);
|
||||
|
||||
template <typename T>
|
||||
EXPORT_TEMPLATE_DECLARE(V8_EXPORT_PRIVATE)
|
||||
T ConvertDouble(double d);
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
||||
|
@ -1142,6 +1142,8 @@ constexpr uint64_t kHoleNanInt64 =
|
||||
// ES6 section 20.1.2.6 Number.MAX_SAFE_INTEGER
|
||||
constexpr uint64_t kMaxSafeIntegerUint64 = 9007199254740991; // 2^53-1
|
||||
constexpr double kMaxSafeInteger = static_cast<double>(kMaxSafeIntegerUint64);
|
||||
// ES6 section 21.1.2.8 Number.MIN_SAFE_INTEGER
|
||||
constexpr double kMinSafeInteger = -kMaxSafeInteger;
|
||||
|
||||
constexpr double kMaxUInt32Double = double{kMaxUInt32};
|
||||
|
||||
|
@ -196,7 +196,8 @@ class EffectControlLinearizer {
|
||||
void LowerTransitionElementsKind(Node* node);
|
||||
Node* LowerLoadFieldByIndex(Node* node);
|
||||
Node* LowerLoadMessage(Node* node);
|
||||
Node* AdaptFastCallArgument(Node* node, CTypeInfo::Type arg_type);
|
||||
Node* AdaptFastCallArgument(Node* node, CTypeInfo arg_type,
|
||||
GraphAssemblerLabel<0>* if_error);
|
||||
Node* LowerFastApiCall(Node* node);
|
||||
Node* LowerLoadTypedElement(Node* node);
|
||||
Node* LowerLoadDataViewElement(Node* node);
|
||||
@ -4983,10 +4984,37 @@ MachineType MachineTypeFor(CTypeInfo::Type type) {
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Node* EffectControlLinearizer::AdaptFastCallArgument(Node* node,
|
||||
CTypeInfo::Type arg_type) {
|
||||
switch (arg_type) {
|
||||
case CTypeInfo::Type::kV8Value: {
|
||||
Node* EffectControlLinearizer::AdaptFastCallArgument(
|
||||
Node* node, CTypeInfo arg_type, GraphAssemblerLabel<0>* if_error) {
|
||||
switch (arg_type.GetSequenceType()) {
|
||||
case CTypeInfo::SequenceType::kScalar: {
|
||||
switch (arg_type.GetType()) {
|
||||
case CTypeInfo::Type::kV8Value: {
|
||||
int kAlign = alignof(uintptr_t);
|
||||
int kSize = sizeof(uintptr_t);
|
||||
Node* stack_slot = __ StackSlot(kSize, kAlign);
|
||||
|
||||
__ Store(StoreRepresentation(MachineType::PointerRepresentation(),
|
||||
kNoWriteBarrier),
|
||||
stack_slot, 0, node);
|
||||
|
||||
return stack_slot;
|
||||
}
|
||||
case CTypeInfo::Type::kFloat32: {
|
||||
return __ TruncateFloat64ToFloat32(node);
|
||||
}
|
||||
default: {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
}
|
||||
case CTypeInfo::SequenceType::kIsSequence: {
|
||||
CHECK_EQ(arg_type.GetType(), CTypeInfo::Type::kVoid);
|
||||
|
||||
// Check that the value is a HeapObject.
|
||||
Node* value_is_smi = ObjectIsSmi(node);
|
||||
__ GotoIf(value_is_smi, if_error);
|
||||
|
||||
int kAlign = alignof(uintptr_t);
|
||||
int kSize = sizeof(uintptr_t);
|
||||
Node* stack_slot = __ StackSlot(kSize, kAlign);
|
||||
@ -4995,13 +5023,18 @@ Node* EffectControlLinearizer::AdaptFastCallArgument(Node* node,
|
||||
kNoWriteBarrier),
|
||||
stack_slot, 0, node);
|
||||
|
||||
// Check that the value is a JSArray.
|
||||
Node* value_map = __ LoadField(AccessBuilder::ForMap(), node);
|
||||
Node* value_instance_type =
|
||||
__ LoadField(AccessBuilder::ForMapInstanceType(), value_map);
|
||||
Node* value_is_js_array =
|
||||
__ Word32Equal(value_instance_type, __ Int32Constant(JS_ARRAY_TYPE));
|
||||
__ GotoIfNot(value_is_js_array, if_error);
|
||||
|
||||
return stack_slot;
|
||||
}
|
||||
case CTypeInfo::Type::kFloat32: {
|
||||
return __ TruncateFloat64ToFloat32(node);
|
||||
}
|
||||
default: {
|
||||
return node;
|
||||
UNREACHABLE(); // TODO(mslekova): Implement typed arrays.
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -5067,11 +5100,16 @@ Node* EffectControlLinearizer::LowerFastApiCall(Node* node) {
|
||||
Node** const inputs = graph()->zone()->NewArray<Node*>(
|
||||
c_arg_count + n.FastCallExtraInputCount());
|
||||
inputs[0] = n.target();
|
||||
|
||||
// Hint to fast path.
|
||||
auto if_success = __ MakeLabel();
|
||||
auto if_error = __ MakeDeferredLabel();
|
||||
|
||||
for (int i = FastApiCallNode::kFastTargetInputCount;
|
||||
i < c_arg_count + FastApiCallNode::kFastTargetInputCount; ++i) {
|
||||
inputs[i] =
|
||||
AdaptFastCallArgument(NodeProperties::GetValueInput(node, i),
|
||||
c_signature->ArgumentInfo(i - 1).GetType());
|
||||
Node* value = NodeProperties::GetValueInput(node, i);
|
||||
CTypeInfo type = c_signature->ArgumentInfo(i - 1);
|
||||
inputs[i] = AdaptFastCallArgument(value, type, &if_error);
|
||||
}
|
||||
if (c_signature->HasOptions()) {
|
||||
inputs[c_arg_count + 1] = stack_slot;
|
||||
@ -5130,9 +5168,6 @@ Node* EffectControlLinearizer::LowerFastApiCall(Node* node) {
|
||||
static_cast<int>(offsetof(v8::FastApiCallbackOptions, fallback)));
|
||||
|
||||
Node* is_zero = __ Word32Equal(load, __ Int32Constant(0));
|
||||
// Hint to true.
|
||||
auto if_success = __ MakeLabel();
|
||||
auto if_error = __ MakeDeferredLabel();
|
||||
auto merge = __ MakeLabel(MachineRepresentation::kTagged);
|
||||
__ Branch(is_zero, &if_success, &if_error);
|
||||
|
||||
|
@ -1786,27 +1786,39 @@ class RepresentationSelector {
|
||||
}
|
||||
}
|
||||
|
||||
UseInfo UseInfoForFastApiCallArgument(CTypeInfo::Type type,
|
||||
UseInfo UseInfoForFastApiCallArgument(CTypeInfo type,
|
||||
FeedbackSource const& feedback) {
|
||||
switch (type) {
|
||||
case CTypeInfo::Type::kVoid:
|
||||
UNREACHABLE();
|
||||
case CTypeInfo::Type::kBool:
|
||||
return UseInfo::Bool();
|
||||
case CTypeInfo::Type::kInt32:
|
||||
case CTypeInfo::Type::kUint32:
|
||||
return UseInfo::CheckedNumberAsWord32(feedback);
|
||||
// TODO(mslekova): We deopt for unsafe integers, but ultimately we want
|
||||
// to make this less restrictive in order to stay on the fast path.
|
||||
case CTypeInfo::Type::kInt64:
|
||||
case CTypeInfo::Type::kUint64:
|
||||
return UseInfo::CheckedSigned64AsWord64(kIdentifyZeros, feedback);
|
||||
case CTypeInfo::Type::kFloat32:
|
||||
case CTypeInfo::Type::kFloat64:
|
||||
return UseInfo::CheckedNumberAsFloat64(kDistinguishZeros, feedback);
|
||||
case CTypeInfo::Type::kV8Value:
|
||||
case CTypeInfo::Type::kApiObject:
|
||||
switch (type.GetSequenceType()) {
|
||||
case CTypeInfo::SequenceType::kScalar: {
|
||||
switch (type.GetType()) {
|
||||
case CTypeInfo::Type::kVoid:
|
||||
UNREACHABLE();
|
||||
case CTypeInfo::Type::kBool:
|
||||
return UseInfo::Bool();
|
||||
case CTypeInfo::Type::kInt32:
|
||||
case CTypeInfo::Type::kUint32:
|
||||
return UseInfo::CheckedNumberAsWord32(feedback);
|
||||
// TODO(mslekova): We deopt for unsafe integers, but ultimately we
|
||||
// want to make this less restrictive in order to stay on the fast
|
||||
// path.
|
||||
case CTypeInfo::Type::kInt64:
|
||||
case CTypeInfo::Type::kUint64:
|
||||
return UseInfo::CheckedSigned64AsWord64(kIdentifyZeros, feedback);
|
||||
case CTypeInfo::Type::kFloat32:
|
||||
case CTypeInfo::Type::kFloat64:
|
||||
return UseInfo::CheckedNumberAsFloat64(kDistinguishZeros, feedback);
|
||||
case CTypeInfo::Type::kV8Value:
|
||||
case CTypeInfo::Type::kApiObject:
|
||||
return UseInfo::AnyTagged();
|
||||
}
|
||||
}
|
||||
case CTypeInfo::SequenceType::kIsSequence: {
|
||||
CHECK_EQ(type.GetType(), CTypeInfo::Type::kVoid);
|
||||
return UseInfo::AnyTagged();
|
||||
}
|
||||
default: {
|
||||
UNREACHABLE(); // TODO(mslekova): Implement typed arrays.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1831,7 +1843,7 @@ class RepresentationSelector {
|
||||
// Propagate representation information from TypeInfo.
|
||||
for (int i = 0; i < c_arg_count; i++) {
|
||||
arg_use_info[i] = UseInfoForFastApiCallArgument(
|
||||
c_signature->ArgumentInfo(i).GetType(), op_params.feedback());
|
||||
c_signature->ArgumentInfo(i), op_params.feedback());
|
||||
ProcessInput<T>(node, i + FastApiCallNode::kFastTargetInputCount,
|
||||
arg_use_info[i]);
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include "src/d8/d8.h"
|
||||
|
||||
#include "include/v8-fast-api-calls.h"
|
||||
#include "src/api/api-inl.h"
|
||||
|
||||
// This file exposes a d8.test.fast_c_api object, which adds testing facility
|
||||
// for writing mjsunit tests that exercise fast API calls. The fast_c_api object
|
||||
@ -91,6 +92,87 @@ class FastCApiObject {
|
||||
args.GetReturnValue().Set(Number::New(isolate, sum));
|
||||
}
|
||||
|
||||
#ifdef V8_ENABLE_FP_PARAMS_IN_C_LINKAGE
|
||||
typedef double Type;
|
||||
static constexpr CTypeInfo type_info = CTypeInfo(CTypeInfo::Type::kFloat64);
|
||||
#else
|
||||
typedef int32_t Type;
|
||||
static constexpr CTypeInfo type_info = CTypeInfo(CTypeInfo::Type::kInt32);
|
||||
#endif // V8_ENABLE_FP_PARAMS_IN_C_LINKAGE
|
||||
static Type AddAllSequenceFastCallback(Local<Object> receiver,
|
||||
bool should_fallback,
|
||||
Local<Array> seq_arg,
|
||||
FastApiCallbackOptions& options) {
|
||||
FastCApiObject* self = UnwrapObject(receiver);
|
||||
CHECK_SELF_OR_FALLBACK(0);
|
||||
self->fast_call_count_++;
|
||||
|
||||
if (should_fallback) {
|
||||
options.fallback = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t length = seq_arg->Length();
|
||||
if (length > 1024) {
|
||||
options.fallback = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
Type buffer[1024];
|
||||
bool result =
|
||||
CopyAndConvertArrayToCppBuffer<Type, &type_info>(seq_arg, buffer, 1024);
|
||||
if (!result) {
|
||||
options.fallback = 1;
|
||||
return 0;
|
||||
}
|
||||
DCHECK_EQ(seq_arg->Length(), length);
|
||||
|
||||
Type sum = 0;
|
||||
for (uint32_t i = 0; i < length; ++i) {
|
||||
sum += buffer[i];
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
static void AddAllSequenceSlowCallback(
|
||||
const FunctionCallbackInfo<Value>& args) {
|
||||
Isolate* isolate = args.GetIsolate();
|
||||
|
||||
FastCApiObject* self = UnwrapObject(args.This());
|
||||
CHECK_SELF_OR_THROW();
|
||||
self->slow_call_count_++;
|
||||
|
||||
HandleScope handle_scope(isolate);
|
||||
|
||||
CHECK_EQ(args.Length(), 2);
|
||||
if (!args[1]->IsArray()) {
|
||||
isolate->ThrowError("This method expects an array as a second argument.");
|
||||
return;
|
||||
}
|
||||
|
||||
Local<Array> seq_arg = args[1].As<Array>();
|
||||
uint32_t length = seq_arg->Length();
|
||||
if (length > 1024) {
|
||||
isolate->ThrowError(
|
||||
"Invalid length of array, must be between 0 and 1024.");
|
||||
return;
|
||||
}
|
||||
Type buffer[1024];
|
||||
bool result =
|
||||
CopyAndConvertArrayToCppBuffer<Type, &type_info>(seq_arg, buffer, 1024);
|
||||
if (!result) {
|
||||
isolate->ThrowError("Array conversion unsuccessful.");
|
||||
return;
|
||||
}
|
||||
DCHECK_EQ(seq_arg->Length(), length);
|
||||
|
||||
Type sum = 0;
|
||||
for (uint32_t i = 0; i < length; ++i) {
|
||||
sum += buffer[i];
|
||||
}
|
||||
args.GetReturnValue().Set(Number::New(isolate, sum));
|
||||
}
|
||||
|
||||
static int Add32BitIntFastCallback(v8::Local<v8::Object> receiver,
|
||||
bool should_fallback, int32_t arg_i32,
|
||||
uint32_t arg_u32,
|
||||
@ -338,6 +420,15 @@ Local<FunctionTemplate> Shell::CreateTestFastCApiTemplate(Isolate* isolate) {
|
||||
ConstructorBehavior::kThrow,
|
||||
SideEffectType::kHasSideEffect, &add_all_c_func));
|
||||
|
||||
CFunction add_all_seq_c_func =
|
||||
CFunction::Make(FastCApiObject::AddAllSequenceFastCallback);
|
||||
api_obj_ctor->PrototypeTemplate()->Set(
|
||||
isolate, "add_all_sequence",
|
||||
FunctionTemplate::New(
|
||||
isolate, FastCApiObject::AddAllSequenceSlowCallback, Local<Value>(),
|
||||
signature, 1, ConstructorBehavior::kThrow,
|
||||
SideEffectType::kHasSideEffect, &add_all_seq_c_func));
|
||||
|
||||
CFunction add_all_32bit_int_6args_c_func =
|
||||
CFunction::Make(FastCApiObject::AddAll32BitIntFastCallback_6Args);
|
||||
CFunction add_all_32bit_int_5args_c_func =
|
||||
|
@ -91,8 +91,8 @@ inline double DoubleToInteger(double x) {
|
||||
// Implements most of https://tc39.github.io/ecma262/#sec-toint32.
|
||||
int32_t DoubleToInt32(double x) {
|
||||
if ((std::isfinite(x)) && (x <= INT_MAX) && (x >= INT_MIN)) {
|
||||
int32_t i = static_cast<int32_t>(x);
|
||||
if (FastI2D(i) == x) return i;
|
||||
// All doubles within these limits are trivially convertable to an int.
|
||||
return static_cast<int32_t>(x);
|
||||
}
|
||||
Double d(x);
|
||||
int exponent = d.Exponent();
|
||||
@ -110,6 +110,35 @@ int32_t DoubleToInt32(double x) {
|
||||
return static_cast<int32_t>(d.Sign() * static_cast<int64_t>(bits));
|
||||
}
|
||||
|
||||
// Implements https://heycam.github.io/webidl/#abstract-opdef-converttoint for
|
||||
// the general case (step 1 and steps 8 to 12). Support for Clamp and
|
||||
// EnforceRange will come in the future.
|
||||
inline int64_t DoubleToWebIDLInt64(double x) {
|
||||
if ((std::isfinite(x)) && (x <= kMaxSafeInteger) && (x >= kMinSafeInteger)) {
|
||||
// All doubles within these limits are trivially convertable to an int.
|
||||
return static_cast<int64_t>(x);
|
||||
}
|
||||
Double d(x);
|
||||
int exponent = d.Exponent();
|
||||
uint64_t bits;
|
||||
if (exponent < 0) {
|
||||
if (exponent <= -Double::kSignificandSize) return 0;
|
||||
bits = d.Significand() >> -exponent;
|
||||
} else {
|
||||
if (exponent > 63) return 0;
|
||||
bits = (d.Significand() << exponent);
|
||||
int64_t bits_int64 = static_cast<int64_t>(bits);
|
||||
if (bits_int64 == std::numeric_limits<int64_t>::min()) {
|
||||
return bits_int64;
|
||||
}
|
||||
}
|
||||
return static_cast<int64_t>(d.Sign() * static_cast<int64_t>(bits));
|
||||
}
|
||||
|
||||
inline uint64_t DoubleToWebIDLUint64(double x) {
|
||||
return static_cast<uint64_t>(DoubleToWebIDLInt64(x));
|
||||
}
|
||||
|
||||
bool DoubleToSmiInteger(double value, int* smi_int_value) {
|
||||
if (!IsSmiDouble(value)) return false;
|
||||
*smi_int_value = FastD2I(value);
|
||||
|
@ -70,6 +70,11 @@ inline int32_t DoubleToInt32(double x);
|
||||
// This function should match the exact semantics of ECMA-262 9.6.
|
||||
inline uint32_t DoubleToUint32(double x);
|
||||
|
||||
// These functions have similar semantics as the ones above, but are
|
||||
// added for 64-bit integer types.
|
||||
inline int64_t DoubleToInt64(double x);
|
||||
inline uint64_t DoubleToUint64(double x);
|
||||
|
||||
// Enumeration for allowing octals and ignoring junk when converting
|
||||
// strings to numbers.
|
||||
enum ConversionFlags {
|
||||
|
97
test/mjsunit/compiler/fast-api-sequences.js
Normal file
97
test/mjsunit/compiler/fast-api-sequences.js
Normal file
@ -0,0 +1,97 @@
|
||||
// Copyright 2021 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.
|
||||
|
||||
// This file excercises sequences support for fast API calls.
|
||||
|
||||
// Flags: --turbo-fast-api-calls --allow-natives-syntax --opt
|
||||
// --always-opt is disabled because we rely on particular feedback for
|
||||
// optimizing to the fastest path.
|
||||
// Flags: --no-always-opt
|
||||
// The test relies on optimizing/deoptimizing at predictable moments, so
|
||||
// it's not suitable for deoptimization fuzzing.
|
||||
// Flags: --deopt-every-n-times=0
|
||||
|
||||
const fast_c_api = new d8.test.FastCAPI();
|
||||
|
||||
// ----------- add_all_sequence -----------
|
||||
// `add_all_sequence` has the following signature:
|
||||
// double add_all_sequence(bool /*should_fallback*/, Local<Array>)
|
||||
|
||||
const max_safe_float = 2**24 - 1;
|
||||
const add_all_result_full = -42 + 45 +
|
||||
Number.MIN_SAFE_INTEGER + Number.MAX_SAFE_INTEGER +
|
||||
max_safe_float * 0.5 + Math.PI;
|
||||
const full_array = [-42, 45,
|
||||
Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER,
|
||||
max_safe_float * 0.5, Math.PI];
|
||||
|
||||
function add_all_sequence_smi(arg) {
|
||||
return fast_c_api.add_all_sequence(false /* should_fallback */, arg);
|
||||
}
|
||||
|
||||
%PrepareFunctionForOptimization(add_all_sequence_smi);
|
||||
assertEquals(3, add_all_sequence_smi([-42, 45]));
|
||||
%OptimizeFunctionOnNextCall(add_all_sequence_smi);
|
||||
|
||||
function add_all_sequence_full(arg) {
|
||||
return fast_c_api.add_all_sequence(false /* should_fallback */, arg);
|
||||
}
|
||||
|
||||
%PrepareFunctionForOptimization(add_all_sequence_full);
|
||||
if (fast_c_api.supports_fp_params) {
|
||||
assertEquals(add_all_result_full, add_all_sequence_full(full_array));
|
||||
} else {
|
||||
assertEquals(3, add_all_sequence_smi([-42, 45]));
|
||||
}
|
||||
%OptimizeFunctionOnNextCall(add_all_sequence_full);
|
||||
|
||||
if (fast_c_api.supports_fp_params) {
|
||||
// Test that regular call hits the fast path.
|
||||
fast_c_api.reset_counts();
|
||||
assertEquals(add_all_result_full, add_all_sequence_full(full_array));
|
||||
assertOptimized(add_all_sequence_full);
|
||||
assertEquals(1, fast_c_api.fast_call_count());
|
||||
assertEquals(0, fast_c_api.slow_call_count());
|
||||
} else {
|
||||
// Smi only test - regular call hits the fast path.
|
||||
fast_c_api.reset_counts();
|
||||
assertEquals(3, add_all_sequence_smi([-42, 45]));
|
||||
assertOptimized(add_all_sequence_smi);
|
||||
assertEquals(1, fast_c_api.fast_call_count());
|
||||
assertEquals(0, fast_c_api.slow_call_count());
|
||||
}
|
||||
|
||||
function add_all_sequence_mismatch(arg) {
|
||||
return fast_c_api.add_all_sequence(false /*should_fallback*/,
|
||||
arg);
|
||||
}
|
||||
|
||||
%PrepareFunctionForOptimization(add_all_sequence_mismatch);
|
||||
assertThrows(() => add_all_sequence_mismatch());
|
||||
%OptimizeFunctionOnNextCall(add_all_sequence_mismatch);
|
||||
|
||||
// Test that passing non-array arguments falls down the slow path.
|
||||
fast_c_api.reset_counts();
|
||||
assertThrows(() => add_all_sequence_mismatch(42));
|
||||
assertOptimized(add_all_sequence_mismatch);
|
||||
assertEquals(0, fast_c_api.fast_call_count());
|
||||
assertEquals(1, fast_c_api.slow_call_count());
|
||||
|
||||
fast_c_api.reset_counts();
|
||||
assertThrows(() => add_all_sequence_mismatch({}));
|
||||
assertOptimized(add_all_sequence_mismatch);
|
||||
assertEquals(0, fast_c_api.fast_call_count());
|
||||
assertEquals(1, fast_c_api.slow_call_count());
|
||||
|
||||
fast_c_api.reset_counts();
|
||||
assertThrows(() => add_all_sequence_mismatch('string'));
|
||||
assertOptimized(add_all_sequence_mismatch);
|
||||
assertEquals(0, fast_c_api.fast_call_count());
|
||||
assertEquals(1, fast_c_api.slow_call_count());
|
||||
|
||||
fast_c_api.reset_counts();
|
||||
assertThrows(() => add_all_sequence_mismatch(Symbol()));
|
||||
assertOptimized(add_all_sequence_mismatch);
|
||||
assertEquals(0, fast_c_api.fast_call_count());
|
||||
assertEquals(1, fast_c_api.slow_call_count());
|
@ -72,6 +72,68 @@ TEST_F(ConversionsTest, DoubleToCString) {
|
||||
}
|
||||
}
|
||||
|
||||
struct DoubleInt32Pair {
|
||||
double number;
|
||||
int integer;
|
||||
};
|
||||
|
||||
static DoubleInt32Pair double_int32_pairs[] = {
|
||||
{0.0, 0},
|
||||
{-0.0, 0},
|
||||
{std::numeric_limits<double>::quiet_NaN(), 0},
|
||||
{std::numeric_limits<double>::infinity(), 0},
|
||||
{-std::numeric_limits<double>::infinity(), 0},
|
||||
{3.14, 3},
|
||||
{1.99, 1},
|
||||
{-1.99, -1},
|
||||
{static_cast<double>(kMinInt), kMinInt},
|
||||
{static_cast<double>(kMaxInt), kMaxInt},
|
||||
{kMaxSafeInteger, -1},
|
||||
{kMinSafeInteger, 1},
|
||||
{kMaxSafeInteger + 1, 0},
|
||||
{kMinSafeInteger - 1, 0},
|
||||
};
|
||||
|
||||
TEST_F(ConversionsTest, DoubleToInt32) {
|
||||
for (size_t i = 0; i < arraysize(double_int32_pairs); i++) {
|
||||
ASSERT_EQ(DoubleToInt32(double_int32_pairs[i].number),
|
||||
double_int32_pairs[i].integer);
|
||||
}
|
||||
}
|
||||
|
||||
struct DoubleInt64Pair {
|
||||
double number;
|
||||
int64_t integer;
|
||||
};
|
||||
|
||||
static DoubleInt64Pair double_int64_pairs[] = {
|
||||
{0.0, 0},
|
||||
{-0.0, 0},
|
||||
{std::numeric_limits<double>::quiet_NaN(), 0},
|
||||
{std::numeric_limits<double>::infinity(), 0},
|
||||
{-std::numeric_limits<double>::infinity(), 0},
|
||||
{3.14, 3},
|
||||
{1.99, 1},
|
||||
{-1.99, -1},
|
||||
{kMinSafeInteger, static_cast<int64_t>(kMinSafeInteger)},
|
||||
{kMaxSafeInteger, static_cast<int64_t>(kMaxSafeIntegerUint64)},
|
||||
{kMinSafeInteger - 1, static_cast<int64_t>(kMinSafeInteger) - 1},
|
||||
{kMaxSafeInteger + 1, static_cast<int64_t>(kMaxSafeIntegerUint64) + 1},
|
||||
{static_cast<double>(std::numeric_limits<int64_t>::min()),
|
||||
std::numeric_limits<int64_t>::min()},
|
||||
// Max int64_t is not representable as a double, the closest is -2^63.
|
||||
{static_cast<double>(std::numeric_limits<int64_t>::max()),
|
||||
std::numeric_limits<int64_t>::min()},
|
||||
// So we test for a smaller number, representable as a double.
|
||||
{static_cast<double>((1ull << 63) - 1024), (1ull << 63) - 1024}};
|
||||
|
||||
TEST_F(ConversionsTest, DoubleToWebIDLInt64) {
|
||||
for (size_t i = 0; i < arraysize(double_int64_pairs); i++) {
|
||||
ASSERT_EQ(DoubleToWebIDLInt64(double_int64_pairs[i].number),
|
||||
double_int64_pairs[i].integer);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace interpreter
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
Loading…
Reference in New Issue
Block a user