[fastcall] Extend the fast API interface with sequences
This CL enhances the interface of the fast C API with constants and structs necessary for supporting JSArrays, TypedArrays and ArrayBuffers. It also adds checks for incompatible combinations of argument type/flags. Bug: chromium:1052746 Change-Id: I032167d0739d33f8151f78574c89d565cb9bd821 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2903147 Reviewed-by: Camillo Bruni <cbruni@chromium.org> Reviewed-by: Georg Neis <neis@chromium.org> Commit-Queue: Maya Lekova <mslekova@chromium.org> Cr-Commit-Position: refs/heads/master@{#74857}
This commit is contained in:
parent
0e6263ec22
commit
7261bf01d7
@ -252,22 +252,73 @@ class CTypeInfo {
|
||||
// than any valid Type enum.
|
||||
static constexpr Type kCallbackOptionsType = Type(255);
|
||||
|
||||
enum class Flags : uint8_t {
|
||||
kNone = 0,
|
||||
enum class SequenceType : uint8_t {
|
||||
kScalar,
|
||||
kIsSequence, // sequence<T>
|
||||
kIsTypedArray, // TypedArray of T or any ArrayBufferView if T
|
||||
// is void
|
||||
kIsArrayBuffer // ArrayBuffer
|
||||
};
|
||||
|
||||
explicit constexpr CTypeInfo(Type type, Flags flags = Flags::kNone)
|
||||
: type_(type), flags_(flags) {}
|
||||
enum class Flags : uint8_t {
|
||||
kNone = 0,
|
||||
kAllowSharedBit = 1 << 0, // Must be an ArrayBuffer or TypedArray
|
||||
kEnforceRangeBit = 1 << 1, // T must be integral
|
||||
kClampBit = 1 << 2, // T must be integral
|
||||
kIsRestrictedBit = 1 << 3, // T must be float or double
|
||||
};
|
||||
|
||||
explicit constexpr CTypeInfo(
|
||||
Type type, SequenceType sequence_type = SequenceType::kScalar,
|
||||
Flags flags = Flags::kNone)
|
||||
: type_(type), sequence_type_(sequence_type), flags_(flags) {}
|
||||
|
||||
constexpr Type GetType() const { return type_; }
|
||||
|
||||
constexpr SequenceType GetSequenceType() const { return sequence_type_; }
|
||||
constexpr Flags GetFlags() const { return flags_; }
|
||||
|
||||
static constexpr bool IsIntegralType(Type type) {
|
||||
return type == Type::kInt32 || type == Type::kUint32 ||
|
||||
type == Type::kInt64 || type == Type::kUint64;
|
||||
}
|
||||
|
||||
static constexpr bool IsFloatingPointType(Type type) {
|
||||
return type == Type::kFloat32 || type == Type::kFloat64;
|
||||
}
|
||||
|
||||
static constexpr bool IsPrimitive(Type type) {
|
||||
return IsIntegralType(type) || IsFloatingPointType(type) ||
|
||||
type == Type::kBool;
|
||||
}
|
||||
|
||||
private:
|
||||
Type type_;
|
||||
SequenceType sequence_type_;
|
||||
Flags flags_;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct FastApiTypedArray {
|
||||
T* data; // should include the typed array offset applied
|
||||
size_t length; // length in number of elements
|
||||
};
|
||||
|
||||
// Any TypedArray. It uses kTypedArrayBit with base type void
|
||||
// Overloaded args of ArrayBufferView and TypedArray are not supported
|
||||
// (for now) because the generic “any” ArrayBufferView doesn’t have its
|
||||
// own instance type. It could be supported if we specify that
|
||||
// TypedArray<T> always has precedence over the generic ArrayBufferView,
|
||||
// but this complicates overload resolution.
|
||||
struct FastApiArrayBufferView {
|
||||
void* data;
|
||||
size_t byte_length;
|
||||
};
|
||||
|
||||
struct FastApiArrayBuffer {
|
||||
void* data;
|
||||
size_t byte_length;
|
||||
};
|
||||
|
||||
class V8_EXPORT CFunctionInfo {
|
||||
public:
|
||||
// Construct a struct to hold a CFunction's type information.
|
||||
@ -319,6 +370,42 @@ class V8_EXPORT CFunction {
|
||||
const void* GetAddress() const { return address_; }
|
||||
const CFunctionInfo* GetTypeInfo() const { return type_info_; }
|
||||
|
||||
enum class OverloadResolution { kImpossible, kAtRuntime, kAtCompileTime };
|
||||
|
||||
// Returns whether an overload between this and the given CFunction can
|
||||
// be resolved at runtime by the RTTI available for the arguments or at
|
||||
// compile time for functions with different number of arguments.
|
||||
OverloadResolution GetOverloadResolution(const CFunction* other) {
|
||||
// Runtime overload resolution can only deal with functions with the
|
||||
// same number of arguments. Functions with different arity are handled
|
||||
// by compile time overload resolution though.
|
||||
if (ArgumentCount() != other->ArgumentCount()) {
|
||||
return OverloadResolution::kAtCompileTime;
|
||||
}
|
||||
|
||||
// The functions can only differ by a single argument position.
|
||||
int diff_index = -1;
|
||||
for (unsigned int i = 0; i < ArgumentCount(); ++i) {
|
||||
if (ArgumentInfo(i).GetSequenceType() !=
|
||||
other->ArgumentInfo(i).GetSequenceType()) {
|
||||
if (diff_index >= 0) {
|
||||
return OverloadResolution::kImpossible;
|
||||
}
|
||||
diff_index = i;
|
||||
|
||||
// We only support overload resolution between sequence types.
|
||||
if (ArgumentInfo(i).GetSequenceType() ==
|
||||
CTypeInfo::SequenceType::kScalar ||
|
||||
other->ArgumentInfo(i).GetSequenceType() ==
|
||||
CTypeInfo::SequenceType::kScalar) {
|
||||
return OverloadResolution::kImpossible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return OverloadResolution::kAtRuntime;
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
static CFunction Make(F* func) {
|
||||
return ArgUnwrap<F*>::Make(func);
|
||||
@ -447,6 +534,9 @@ struct TypeInfoHelper {
|
||||
} \
|
||||
\
|
||||
static constexpr CTypeInfo::Type Type() { return CTypeInfo::Type::Enum; } \
|
||||
static constexpr CTypeInfo::SequenceType SequenceType() { \
|
||||
return CTypeInfo::SequenceType::kScalar; \
|
||||
} \
|
||||
};
|
||||
|
||||
#define BASIC_C_TYPES(V) \
|
||||
@ -470,6 +560,41 @@ BASIC_C_TYPES(SPECIALIZE_GET_TYPE_INFO_HELPER_FOR)
|
||||
|
||||
#undef BASIC_C_TYPES
|
||||
|
||||
#define SPECIALIZE_GET_TYPE_INFO_HELPER_FOR_TA(T, Enum) \
|
||||
template <> \
|
||||
struct TypeInfoHelper<FastApiTypedArray<T>> { \
|
||||
static constexpr CTypeInfo::Flags Flags() { \
|
||||
return CTypeInfo::Flags::kNone; \
|
||||
} \
|
||||
\
|
||||
static constexpr CTypeInfo::Type Type() { return CTypeInfo::Type::Enum; } \
|
||||
static constexpr CTypeInfo::SequenceType SequenceType() { \
|
||||
return CTypeInfo::SequenceType::kIsTypedArray; \
|
||||
} \
|
||||
};
|
||||
|
||||
#define TYPED_ARRAY_C_TYPES(V) \
|
||||
V(int32_t, kInt32) \
|
||||
V(uint32_t, kUint32) \
|
||||
V(int64_t, kInt64) \
|
||||
V(uint64_t, kUint64) \
|
||||
V(float, kFloat32) \
|
||||
V(double, kFloat64)
|
||||
|
||||
TYPED_ARRAY_C_TYPES(SPECIALIZE_GET_TYPE_INFO_HELPER_FOR_TA)
|
||||
|
||||
#undef TYPED_ARRAY_C_TYPES
|
||||
|
||||
template <>
|
||||
struct TypeInfoHelper<v8::Local<v8::Array>> {
|
||||
static constexpr CTypeInfo::Flags Flags() { return CTypeInfo::Flags::kNone; }
|
||||
|
||||
static constexpr CTypeInfo::Type Type() { return CTypeInfo::Type::kVoid; }
|
||||
static constexpr CTypeInfo::SequenceType SequenceType() {
|
||||
return CTypeInfo::SequenceType::kIsSequence;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct TypeInfoHelper<FastApiCallbackOptions&> {
|
||||
static constexpr CTypeInfo::Flags Flags() { return CTypeInfo::Flags::kNone; }
|
||||
@ -477,26 +602,63 @@ struct TypeInfoHelper<FastApiCallbackOptions&> {
|
||||
static constexpr CTypeInfo::Type Type() {
|
||||
return CTypeInfo::kCallbackOptionsType;
|
||||
}
|
||||
static constexpr CTypeInfo::SequenceType SequenceType() {
|
||||
return CTypeInfo::SequenceType::kScalar;
|
||||
}
|
||||
};
|
||||
|
||||
#define STATIC_ASSERT_IMPLIES(COND, ASSERTION, MSG) \
|
||||
static_assert(((COND) == 0) || (ASSERTION), MSG)
|
||||
|
||||
template <typename T, CTypeInfo::Flags... Flags>
|
||||
class CTypeInfoBuilder {
|
||||
public:
|
||||
using BaseType = T;
|
||||
|
||||
static constexpr CTypeInfo Build() {
|
||||
// Get the flags and merge in any additional flags.
|
||||
uint8_t flags = uint8_t(TypeInfoHelper<T>::Flags());
|
||||
int unused[] = {0, (flags |= uint8_t(Flags), 0)...};
|
||||
// With C++17, we could use a "..." fold expression over a parameter pack.
|
||||
// Since we're still using C++14, we have to evaluate an OR expresion while
|
||||
// constructing an unused list of 0's. This applies the binary operator
|
||||
// for each value in Flags.
|
||||
(void)unused;
|
||||
constexpr CTypeInfo::Flags kFlags =
|
||||
MergeFlags(TypeInfoHelper<T>::Flags(), Flags...);
|
||||
constexpr CTypeInfo::Type kType = TypeInfoHelper<T>::Type();
|
||||
constexpr CTypeInfo::SequenceType kSequenceType =
|
||||
TypeInfoHelper<T>::SequenceType();
|
||||
|
||||
STATIC_ASSERT_IMPLIES(
|
||||
uint8_t(kFlags) & uint8_t(CTypeInfo::Flags::kAllowSharedBit),
|
||||
(kSequenceType == CTypeInfo::SequenceType::kIsTypedArray ||
|
||||
kSequenceType == CTypeInfo::SequenceType::kIsArrayBuffer),
|
||||
"kAllowSharedBit is only allowed for TypedArrays and ArrayBuffers.");
|
||||
STATIC_ASSERT_IMPLIES(
|
||||
uint8_t(kFlags) & uint8_t(CTypeInfo::Flags::kEnforceRangeBit),
|
||||
CTypeInfo::IsIntegralType(kType),
|
||||
"kEnforceRangeBit is only allowed for integral types.");
|
||||
STATIC_ASSERT_IMPLIES(
|
||||
uint8_t(kFlags) & uint8_t(CTypeInfo::Flags::kClampBit),
|
||||
CTypeInfo::IsIntegralType(kType),
|
||||
"kClampBit is only allowed for integral types.");
|
||||
STATIC_ASSERT_IMPLIES(
|
||||
uint8_t(kFlags) & uint8_t(CTypeInfo::Flags::kIsRestrictedBit),
|
||||
CTypeInfo::IsFloatingPointType(kType),
|
||||
"kIsRestrictedBit is only allowed for floating point types.");
|
||||
STATIC_ASSERT_IMPLIES(kSequenceType == CTypeInfo::SequenceType::kIsSequence,
|
||||
kType == CTypeInfo::Type::kVoid,
|
||||
"Sequences are only supported from void type.");
|
||||
STATIC_ASSERT_IMPLIES(
|
||||
kSequenceType == CTypeInfo::SequenceType::kIsTypedArray,
|
||||
CTypeInfo::IsPrimitive(kType) || kType == CTypeInfo::Type::kVoid,
|
||||
"TypedArrays are only supported from primitive types or void.");
|
||||
|
||||
// Return the same type with the merged flags.
|
||||
return CTypeInfo(TypeInfoHelper<T>::Type(), CTypeInfo::Flags(flags));
|
||||
return CTypeInfo(TypeInfoHelper<T>::Type(),
|
||||
TypeInfoHelper<T>::SequenceType(), kFlags);
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename... Rest>
|
||||
static constexpr CTypeInfo::Flags MergeFlags(CTypeInfo::Flags flags,
|
||||
Rest... rest) {
|
||||
return CTypeInfo::Flags(uint8_t(flags) | uint8_t(MergeFlags(rest...)));
|
||||
}
|
||||
static constexpr CTypeInfo::Flags MergeFlags() { return CTypeInfo::Flags(0); }
|
||||
};
|
||||
|
||||
template <typename RetBuilder, typename... ArgBuilders>
|
||||
@ -548,8 +710,9 @@ class CFunctionBuilderWithFunction {
|
||||
Flags...>;
|
||||
};
|
||||
|
||||
// Return a copy of the CFunctionBuilder, but merges the Flags on ArgBuilder
|
||||
// index N with the new Flags passed in the template parameter pack.
|
||||
// Return a copy of the CFunctionBuilder, but merges the Flags on
|
||||
// ArgBuilder index N with the new Flags passed in the template parameter
|
||||
// pack.
|
||||
template <unsigned int N, CTypeInfo::Flags... Flags, size_t... I>
|
||||
constexpr auto ArgImpl(std::index_sequence<I...>) {
|
||||
return CFunctionBuilderWithFunction<
|
||||
|
@ -1269,6 +1269,15 @@ Local<FunctionTemplate> FunctionTemplate::NewWithCFunctionOverloads(
|
||||
v8::Local<Signature> signature, int length, ConstructorBehavior behavior,
|
||||
SideEffectType side_effect_type,
|
||||
const MemorySpan<const CFunction>& c_function_overloads) {
|
||||
// TODO(mslekova): Once runtime overload resolution between sequences is
|
||||
// supported, check that if (c_function_overloads.size() == 2), then
|
||||
// c_function_overloads.data()[0].
|
||||
// CanResolveOverload(c_function_overloads.data()[1]). We won't support
|
||||
// the case where the size is greater than 2 for runtime resolution, until
|
||||
// we've added support for ArrayBuffers and ArrayBufferViews. OTOH the
|
||||
// overloads list might contain more than 2 functions with different arity,
|
||||
// the resolution between which is available at compile time.
|
||||
|
||||
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
|
||||
LOG_API(i_isolate, FunctionTemplate, New);
|
||||
ENTER_V8_NO_SCRIPT_NO_EXCEPTION(i_isolate);
|
||||
|
@ -28216,6 +28216,7 @@ TEST(FastApiStackSlot) {
|
||||
// Disable --always_opt, otherwise we haven't generated the necessary
|
||||
// feedback to go down the "best optimization" path for the fast call.
|
||||
v8::internal::FLAG_always_opt = false;
|
||||
v8::internal::FlagList::EnforceFlagImplications();
|
||||
|
||||
v8::Isolate* isolate = CcTest::isolate();
|
||||
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
|
||||
@ -28267,6 +28268,7 @@ TEST(FastApiCalls) {
|
||||
// Disable --always_opt, otherwise we haven't generated the necessary
|
||||
// feedback to go down the "best optimization" path for the fast call.
|
||||
v8::internal::FLAG_always_opt = false;
|
||||
v8::internal::FlagList::EnforceFlagImplications();
|
||||
|
||||
v8::Isolate* isolate = CcTest::isolate();
|
||||
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
|
||||
@ -28725,6 +28727,108 @@ TEST(FastApiCalls) {
|
||||
#endif // V8_LITE_MODE
|
||||
}
|
||||
|
||||
namespace {
|
||||
void FastCallback1TypedArray(v8::Local<v8::Object> receiver, int arg0,
|
||||
v8::FastApiTypedArray<double> arg1) {
|
||||
// TODO(mslekova): Use the TypedArray parameter
|
||||
}
|
||||
|
||||
void FastCallback2JSArray(v8::Local<v8::Object> receiver, int arg0,
|
||||
v8::Local<v8::Array> arg1) {
|
||||
// TODO(mslekova): Use the JSArray parameter
|
||||
}
|
||||
|
||||
void FastCallback3SwappedParams(v8::Local<v8::Object> receiver,
|
||||
v8::Local<v8::Array> arg0, int arg1) {}
|
||||
|
||||
void FastCallback4Scalar(v8::Local<v8::Object> receiver, int arg0, float arg1) {
|
||||
}
|
||||
|
||||
void FastCallback5DifferentArity(v8::Local<v8::Object> receiver, int arg0,
|
||||
v8::Local<v8::Array> arg1, float arg2) {}
|
||||
} // namespace
|
||||
|
||||
TEST(FastApiSequenceOverloads) {
|
||||
#ifndef V8_LITE_MODE
|
||||
if (i::FLAG_jitless) return;
|
||||
|
||||
v8::internal::FLAG_opt = true;
|
||||
v8::internal::FLAG_turbo_fast_api_calls = true;
|
||||
v8::internal::FLAG_allow_natives_syntax = true;
|
||||
// Disable --always_opt, otherwise we haven't generated the necessary
|
||||
// feedback to go down the "best optimization" path for the fast call.
|
||||
v8::internal::FLAG_always_opt = false;
|
||||
v8::internal::FlagList::EnforceFlagImplications();
|
||||
|
||||
v8::CFunction typed_array_callback =
|
||||
v8::CFunctionBuilder()
|
||||
.Fn(FastCallback1TypedArray)
|
||||
.Arg<0, v8::CTypeInfo::Flags::kNone>()
|
||||
.Arg<1, v8::CTypeInfo::Flags::kNone>()
|
||||
.Arg<2, v8::CTypeInfo::Flags::kAllowSharedBit>()
|
||||
.Build();
|
||||
v8::CFunction js_array_callback = v8::CFunctionBuilder()
|
||||
.Fn(FastCallback2JSArray)
|
||||
.Arg<0, v8::CTypeInfo::Flags::kNone>()
|
||||
.Arg<1, v8::CTypeInfo::Flags::kNone>()
|
||||
.Arg<2, v8::CTypeInfo::Flags::kNone>()
|
||||
.Build();
|
||||
|
||||
// TODO(mslekova): Create a FunctionTemplate with the 2 overloads.
|
||||
USE(typed_array_callback);
|
||||
USE(js_array_callback);
|
||||
|
||||
#endif // V8_LITE_MODE
|
||||
}
|
||||
|
||||
TEST(FastApiOverloadResolution) {
|
||||
#ifndef V8_LITE_MODE
|
||||
if (i::FLAG_jitless) return;
|
||||
|
||||
v8::internal::FLAG_opt = true;
|
||||
v8::internal::FLAG_turbo_fast_api_calls = true;
|
||||
v8::internal::FLAG_allow_natives_syntax = true;
|
||||
// Disable --always_opt, otherwise we haven't generated the necessary
|
||||
// feedback to go down the "best optimization" path for the fast call.
|
||||
v8::internal::FLAG_always_opt = false;
|
||||
v8::internal::FlagList::EnforceFlagImplications();
|
||||
|
||||
v8::CFunction typed_array_callback =
|
||||
v8::CFunctionBuilder().Fn(FastCallback1TypedArray).Build();
|
||||
v8::CFunction js_array_callback =
|
||||
v8::CFunctionBuilder().Fn(FastCallback2JSArray).Build();
|
||||
|
||||
// Check that a general runtime overload resolution is possible.
|
||||
CHECK_EQ(v8::CFunction::OverloadResolution::kAtRuntime,
|
||||
typed_array_callback.GetOverloadResolution(&js_array_callback));
|
||||
|
||||
v8::CFunction swapped_params_callback =
|
||||
v8::CFunctionBuilder().Fn(FastCallback3SwappedParams).Build();
|
||||
|
||||
// Check that difference in > 1 position is not possible.
|
||||
CHECK_EQ(
|
||||
v8::CFunction::OverloadResolution::kImpossible,
|
||||
typed_array_callback.GetOverloadResolution(&swapped_params_callback));
|
||||
|
||||
v8::CFunction scalar_callback =
|
||||
v8::CFunctionBuilder().Fn(FastCallback4Scalar).Build();
|
||||
|
||||
// Check that resolving when there is a scalar at the difference position
|
||||
// is not possible.
|
||||
CHECK_EQ(v8::CFunction::OverloadResolution::kImpossible,
|
||||
typed_array_callback.GetOverloadResolution(&scalar_callback));
|
||||
|
||||
v8::CFunction diff_arity_callback =
|
||||
v8::CFunctionBuilder().Fn(FastCallback5DifferentArity).Build();
|
||||
|
||||
// Check that overload resolution between different number of arguments
|
||||
// is possible.
|
||||
CHECK_EQ(v8::CFunction::OverloadResolution::kAtCompileTime,
|
||||
typed_array_callback.GetOverloadResolution(&diff_arity_callback));
|
||||
|
||||
#endif // V8_LITE_MODE
|
||||
}
|
||||
|
||||
THREADED_TEST(Recorder_GetContext) {
|
||||
using v8::Context;
|
||||
using v8::Local;
|
||||
|
Loading…
Reference in New Issue
Block a user