[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:
Maya Lekova 2021-06-23 15:08:06 +02:00 committed by V8 LUCI CQ
parent 70dd5f89e3
commit d0aebc06e0
13 changed files with 519 additions and 52 deletions

View File

@ -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_

View File

@ -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);

View File

@ -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() {

View File

@ -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

View File

@ -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

View File

@ -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};

View File

@ -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);

View File

@ -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]);
}

View File

@ -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 =

View File

@ -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);

View File

@ -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 {

View 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());

View File

@ -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