Commit 7261bf01 authored by Maya Lekova's avatar Maya Lekova Committed by V8 LUCI CQ

[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/+/2903147Reviewed-by: 's avatarCamillo Bruni <cbruni@chromium.org>
Reviewed-by: 's avatarGeorg Neis <neis@chromium.org>
Commit-Queue: Maya Lekova <mslekova@chromium.org>
Cr-Commit-Position: refs/heads/master@{#74857}
parent 0e6263ec
......@@ -252,22 +252,73 @@ class CTypeInfo {
// than any valid Type enum.
static constexpr Type kCallbackOptionsType = Type(255);
enum class SequenceType : uint8_t {
kScalar,
kIsSequence, // sequence<T>
kIsTypedArray, // TypedArray of T or any ArrayBufferView if T
// is void
kIsArrayBuffer // ArrayBuffer
};
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, Flags flags = Flags::kNone)
: type_(type), flags_(flags) {}
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;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment