[turbofan] Remove object unwrapping for fast C calls

The object is passed now as an v8::ApiObject instead of unwrapped
C++ pointer and the embedder should do the unwrapping.

Bug: chromium:1052746
Change-Id: If5671c5fdbbe8d58435c7bd9aceccf5e17f8ea21
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2304571
Reviewed-by: Toon Verwaest <verwaest@chromium.org>
Reviewed-by: Georg Neis <neis@chromium.org>
Commit-Queue: Maya Lekova <mslekova@chromium.org>
Cr-Commit-Position: refs/heads/master@{#68991}
This commit is contained in:
Maya Lekova 2020-07-21 11:29:25 +02:00 committed by Commit Bot
parent b64cede5d8
commit 470b614608
5 changed files with 148 additions and 128 deletions

View File

@ -23,13 +23,7 @@
*
* \code
*
* // Represents the way this type system maps C++ and JS values.
* struct WrapperTypeInfo {
* // Store e.g. a method to map from exposed C++ types to the already
* // created v8::FunctionTemplate's for instantiating them.
* };
*
* // Helper method with a sanity check.
* // Helper method with a check for field count.
* template <typename T, int offset>
* inline T* GetInternalField(v8::Local<v8::Object> wrapper) {
* assert(offset < wrapper->InternalFieldCount());
@ -37,25 +31,19 @@
* wrapper->GetAlignedPointerFromInternalField(offset));
* }
*
* // Returns the type info from a wrapper JS object.
* inline const WrapperTypeInfo* ToWrapperTypeInfo(
* v8::Local<v8::Object> wrapper) {
* return GetInternalField<WrapperTypeInfo,
* kV8EmbedderWrapperTypeIndex>(wrapper);
* }
*
* class CustomEmbedderType {
* public:
* static constexpr const WrapperTypeInfo* GetWrapperTypeInfo() {
* return &custom_type_wrapper_type_info;
* }
* // Returns the raw C object from a wrapper JS object.
* static CustomEmbedderType* Unwrap(v8::Local<v8::Object> wrapper) {
* return GetInternalField<CustomEmbedderType,
* kV8EmbedderWrapperObjectIndex>(wrapper);
* }
* static void FastMethod(CustomEmbedderType* receiver, int param) {
* assert(receiver != nullptr);
* static void FastMethod(v8::ApiObject receiver_obj, int param) {
* v8::Object* v8_object = reinterpret_cast<v8::Object*>(&api_object);
* CustomEmbedderType* receiver = static_cast<CustomEmbedderType*>(
* receiver_obj->GetAlignedPointerFromInternalField(
* kV8EmbedderWrapperObjectIndex));
*
* // Type checks are already done by the optimized code.
* // Then call some performance-critical method like:
* // receiver->Method(param);
@ -67,25 +55,10 @@
* v8::Local<v8::Object>::Cast(info.Holder());
* CustomEmbedderType* receiver = Unwrap(instance);
* // TODO: Do type checks and extract {param}.
* FastMethod(receiver, param);
* receiver->Method(param);
* }
*
* private:
* static const WrapperTypeInfo custom_type_wrapper_type_info;
* };
*
* // Support for custom embedder types via specialization of WrapperTraits.
* namespace v8 {
* template <>
* class WrapperTraits<CustomEmbedderType> {
* public:
* static const void* GetTypeInfo() {
* // We use the already defined machinery for the custom type.
* return CustomEmbedderType::GetWrapperTypeInfo();
* }
* };
* } // namespace v8
*
* // The constants kV8EmbedderWrapperTypeIndex and
* // kV8EmbedderWrapperObjectIndex describe the offsets for the type info
* // struct (the one returned by WrapperTraits::GetTypeInfo) and the
@ -179,7 +152,7 @@ class CTypeInfo {
kUint64,
kFloat32,
kFloat64,
kUnwrappedApiObject,
kV8Value,
};
enum class ArgFlags : uint8_t {
@ -187,6 +160,7 @@ class CTypeInfo {
kIsArrayBit = 1 << 0, // This argument is first in an array of values.
};
// TODO(mslekova): Clean this up once V8's version in Node.js is updated
static CTypeInfo FromWrapperType(const void* wrapper_type_info,
ArgFlags flags = ArgFlags::kNone) {
uintptr_t wrapper_type_info_ptr =
@ -196,15 +170,15 @@ class CTypeInfo {
wrapper_type_info_ptr & ~(static_cast<uintptr_t>(~0)
<< static_cast<uintptr_t>(kIsWrapperTypeBit)),
0u);
// TODO(mslekova): Refactor the manual bit manipulations to use
// PointerWithPayload instead.
return CTypeInfo(wrapper_type_info_ptr | static_cast<int>(flags) |
kIsWrapperTypeBit);
}
static constexpr CTypeInfo FromCType(Type ctype,
ArgFlags flags = ArgFlags::kNone) {
// ctype cannot be Type::kUnwrappedApiObject.
// TODO(mslekova): Refactor the manual bit manipulations to use
// PointerWithPayload instead.
// ctype cannot be Type::kV8Value.
return CTypeInfo(
((static_cast<uintptr_t>(ctype) << kTypeOffset) & kTypeMask) |
static_cast<int>(flags));
@ -214,7 +188,7 @@ class CTypeInfo {
constexpr Type GetType() const {
if (payload_ & kIsWrapperTypeBit) {
return Type::kUnwrappedApiObject;
return Type::kV8Value;
}
return static_cast<Type>((payload_ & kTypeMask) >> kTypeOffset);
}
@ -246,21 +220,27 @@ class CFunctionInfo {
virtual const CTypeInfo& ArgumentInfo(unsigned int index) const = 0;
};
// TODO(mslekova): Clean this up once V8's version in Node.js is updated
template <typename T>
class WrapperTraits {
public:
static const void* GetTypeInfo() {
static_assert(sizeof(T) != sizeof(T),
"WrapperTraits must be specialized for this type.");
return nullptr;
static const int tag = 0;
return reinterpret_cast<const void*>(&tag);
}
};
struct ApiObject {
uintptr_t address;
};
namespace internal {
template <typename T>
struct GetCType {
static_assert(sizeof(T) != sizeof(T), "Unsupported CType");
static constexpr CTypeInfo Get() {
return CTypeInfo::FromCType(CTypeInfo::Type::kV8Value);
}
};
#define SPECIALIZE_GET_C_TYPE_FOR(ctype, ctypeinfo) \
@ -279,19 +259,11 @@ struct GetCType {
V(int64_t, kInt64) \
V(uint64_t, kUint64) \
V(float, kFloat32) \
V(double, kFloat64)
V(double, kFloat64) \
V(ApiObject, kV8Value)
SUPPORTED_C_TYPES(SPECIALIZE_GET_C_TYPE_FOR)
template <typename T, typename = void>
struct EnableIfHasWrapperTypeInfo {};
template <typename T>
struct EnableIfHasWrapperTypeInfo<T, decltype(WrapperTraits<T>::GetTypeInfo(),
void())> {
typedef void type;
};
// T* where T is a primitive (array of primitives).
template <typename T, typename = void>
struct GetCTypePointerImpl {
@ -303,7 +275,7 @@ struct GetCTypePointerImpl {
// T* where T is an API object.
template <typename T>
struct GetCTypePointerImpl<T, typename EnableIfHasWrapperTypeInfo<T>::type> {
struct GetCTypePointerImpl<T, void> {
static constexpr CTypeInfo Get() {
return CTypeInfo::FromWrapperType(WrapperTraits<T>::GetTypeInfo());
}
@ -317,8 +289,7 @@ struct GetCTypePointerPointerImpl {
// T** where T is an API object (array of API objects).
template <typename T>
struct GetCTypePointerPointerImpl<
T, typename EnableIfHasWrapperTypeInfo<T>::type> {
struct GetCTypePointerPointerImpl<T, void> {
static constexpr CTypeInfo Get() {
return CTypeInfo::FromWrapperType(WrapperTraits<T>::GetTypeInfo(),
CTypeInfo::ArgFlags::kIsArrayBit);

View File

@ -4938,8 +4938,8 @@ static MachineType MachineTypeFor(CTypeInfo::Type type) {
return MachineType::Float32();
case CTypeInfo::Type::kFloat64:
return MachineType::Float64();
case CTypeInfo::Type::kUnwrappedApiObject:
return MachineType::Pointer();
case CTypeInfo::Type::kV8Value:
return MachineType::AnyTagged();
}
}

View File

@ -908,17 +908,14 @@ class FastApiCallReducerAssembler : public JSCallReducerAssembler {
kExtraInputsCount);
inputs[cursor++] = ExternalConstant(ExternalReference::Create(c_function_));
inputs[cursor++] = MaybeUnwrapApiObject(
c_signature_->ArgumentInfo(0).GetType(), n.receiver());
inputs[cursor++] = n.receiver();
// TODO(turbofan): Consider refactoring CFunctionInfo to distinguish
// between receiver and arguments, simplifying this (and related) spots.
int js_args_count = c_argument_count - kReceiver;
for (int i = 0; i < js_args_count; ++i) {
if (i < n.ArgumentCount()) {
CTypeInfo::Type type =
c_signature_->ArgumentInfo(i + kReceiver).GetType();
inputs[cursor++] = MaybeUnwrapApiObject(type, n.Argument(i));
inputs[cursor++] = n.Argument(i);
} else {
inputs[cursor++] = UndefinedConstant();
}
@ -985,32 +982,6 @@ class FastApiCallReducerAssembler : public JSCallReducerAssembler {
static_cast<int>(inputs_size), inputs));
}
TNode<RawPtrT> UnwrapApiObject(TNode<JSObject> node) {
CHECK_GE(isolate()->embedder_wrapper_object_index(), 0);
const int offset = Internals::kJSObjectHeaderSize +
(Internals::kEmbedderDataSlotSize *
isolate()->embedder_wrapper_object_index());
FieldAccess access(
kTaggedBase, offset, MaybeHandle<Name>(), MaybeHandle<Map>(),
V8_HEAP_SANDBOX_BOOL ? Type::SandboxedExternalPointer() : Type::Any(),
MachineType::Pointer(), WriteBarrierKind::kNoWriteBarrier);
return LoadField<RawPtrT>(access, node);
}
Node* MaybeUnwrapApiObject(CTypeInfo::Type type, TNode<Object> node) {
switch (type) {
case CTypeInfo::Type::kUnwrappedApiObject:
// This call assumes that {node} is a JSObject with an internal field
// set to a C pointer. It should fail in all other cases.
// TODO(mslekova): Implement instanceOf check for the C pointer type.
// TODO(mslekova): Introduce a GraphAssembler primitive for safe cast.
return UnwrapApiObject(TNode<JSObject>::UncheckedCast(node));
default:
return node;
}
}
const Address c_function_;
const CFunctionInfo* const c_signature_;
const FunctionTemplateInfoRef function_template_info_;

View File

@ -1724,8 +1724,8 @@ class RepresentationSelector {
return MachineType::Float32();
case CTypeInfo::Type::kFloat64:
return MachineType::Float64();
case CTypeInfo::Type::kUnwrappedApiObject:
return MachineType::Pointer();
case CTypeInfo::Type::kV8Value:
return MachineType::AnyTagged();
}
}
@ -1751,8 +1751,8 @@ class RepresentationSelector {
// fall into that pit, but future changes may break this here.
case CTypeInfo::Type::kUint64:
return UseInfo::Word64();
case CTypeInfo::Type::kUnwrappedApiObject:
return UseInfo::Word();
case CTypeInfo::Type::kV8Value:
return UseInfo::AnyTagged();
}
}

View File

@ -27364,9 +27364,9 @@ DEFINE_OPERATORS_FOR_FLAGS(ApiCheckerResultFlags)
template <typename Value, typename Impl>
struct BasicApiChecker {
static void FastCallback(BasicApiChecker<Value, Impl>* receiver,
Value argument, int* fallback) {
Impl::FastCallback(static_cast<Impl*>(receiver), argument, fallback);
static void FastCallback(v8::ApiObject receiver, Value argument,
int* fallback) {
Impl::FastCallback(receiver, argument, fallback);
}
static void SlowCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
Impl::SlowCallback(info);
@ -27378,25 +27378,52 @@ struct BasicApiChecker {
ApiCheckerResultFlags result_ = ApiCheckerResult::kNotCalled;
};
bool IsValidUnwrapObject(v8::Object* object) {
v8::internal::Address addr =
*reinterpret_cast<v8::internal::Address*>(object);
auto instance_type = v8::internal::Internals::GetInstanceType(addr);
return (instance_type == v8::internal::Internals::kJSObjectType ||
instance_type == v8::internal::Internals::kJSApiObjectType ||
instance_type == v8::internal::Internals::kJSSpecialApiObjectType);
}
template <typename T, int offset>
T* GetInternalField(v8::Object* wrapper) {
assert(offset < wrapper->InternalFieldCount());
return reinterpret_cast<T*>(
wrapper->GetAlignedPointerFromInternalField(offset));
}
template <typename T>
struct ApiNumberChecker : BasicApiChecker<T, ApiNumberChecker<T>> {
explicit ApiNumberChecker(T value, bool raise_exception = false,
int args_count = 1)
: raise_exception_(raise_exception), args_count_(args_count) {}
static void FastCallback(ApiNumberChecker<T>* receiver, T argument,
int* fallback) {
receiver->result_ |= ApiCheckerResult::kFastCalled;
receiver->fast_value_ = argument;
if (receiver->raise_exception_) {
static void FastCallback(v8::ApiObject receiver, T argument, int* fallback) {
v8::Object* receiver_obj = reinterpret_cast<v8::Object*>(&receiver);
if (!IsValidUnwrapObject(receiver_obj)) {
*fallback = 1;
return;
}
ApiNumberChecker<T>* receiver_ptr =
GetInternalField<ApiNumberChecker<T>, kV8WrapperObjectIndex>(
receiver_obj);
receiver_ptr->result_ |= ApiCheckerResult::kFastCalled;
receiver_ptr->fast_value_ = argument;
if (receiver_ptr->raise_exception_) {
*fallback = 1;
}
}
static void SlowCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
v8::Object* receiver = v8::Object::Cast(*info.Holder());
ApiNumberChecker<T>* checker = static_cast<ApiNumberChecker<T>*>(
receiver->GetAlignedPointerFromInternalField(kV8WrapperObjectIndex));
if (!IsValidUnwrapObject(receiver)) {
info.GetIsolate()->ThrowException(v8_str("Called with a non-object."));
return;
}
ApiNumberChecker<T>* checker =
GetInternalField<ApiNumberChecker<T>, kV8WrapperObjectIndex>(receiver);
CHECK_EQ(info.Length(), checker->args_count_);
checker->result_ |= ApiCheckerResult::kSlowCalled;
@ -27415,6 +27442,35 @@ struct ApiNumberChecker : BasicApiChecker<T, ApiNumberChecker<T>> {
int args_count_ = 1;
};
struct UnexpectedObjectChecker
: BasicApiChecker<v8::ApiObject, UnexpectedObjectChecker> {
static void FastCallback(v8::ApiObject receiver, v8::ApiObject argument,
int* fallback) {
v8::Object* receiver_obj = reinterpret_cast<v8::Object*>(&receiver);
UnexpectedObjectChecker* receiver_ptr =
GetInternalField<UnexpectedObjectChecker, kV8WrapperObjectIndex>(
receiver_obj);
receiver_ptr->result_ |= ApiCheckerResult::kFastCalled;
v8::Value* argument_value = reinterpret_cast<v8::Value*>(&argument);
if (argument_value->IsObject()) {
v8::Object* argument_obj = reinterpret_cast<v8::Object*>(&argument);
CHECK(!IsValidUnwrapObject(argument_obj));
}
}
static void SlowCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
v8::Object* receiver_obj = v8::Object::Cast(*info.Holder());
UnexpectedObjectChecker* receiver_ptr =
GetInternalField<UnexpectedObjectChecker, kV8WrapperObjectIndex>(
receiver_obj);
receiver_ptr->result_ |= ApiCheckerResult::kSlowCalled;
if (info[0]->IsObject()) {
v8::Object* argument_obj = v8::Object::Cast(*info[0]);
CHECK(!IsValidUnwrapObject(argument_obj));
}
}
};
enum class Behavior {
kNoException,
kException, // An exception should be thrown by the callback function.
@ -27428,8 +27484,7 @@ bool SetupTest(v8::Local<v8::Value> initial_value, LocalContext* env,
v8::CFunction c_func = v8::CFunction::MakeRaisesException(
BasicApiChecker<Value, Impl>::FastCallback);
CHECK_EQ(c_func.ArgumentInfo(0).GetType(),
v8::CTypeInfo::Type::kUnwrappedApiObject);
CHECK_EQ(c_func.ArgumentInfo(0).GetType(), v8::CTypeInfo::Type::kV8Value);
Local<v8::FunctionTemplate> checker_templ = v8::FunctionTemplate::New(
isolate, BasicApiChecker<Value, Impl>::SlowCallback,
@ -27554,6 +27609,35 @@ void CallWithMoreArguments() {
CHECK(checker.DidCallFast());
}
void CallWithUnexpectedReceiverType(v8::Local<v8::Value> receiver) {
LocalContext env;
ApiNumberChecker<int32_t> checker(42);
bool has_caught =
SetupTest(receiver, &env, &checker,
"function func(arg) { receiver.api_func.apply(value, arg); }"
"%PrepareFunctionForOptimization(func);"
"func(value);"
"%OptimizeFunctionOnNextCall(func);"
"func(value);");
CHECK(has_caught);
// The slow and fast callbacks were called actually, but aborted early.
CHECK(!checker.DidCallSlow());
CHECK(!checker.DidCallFast());
}
void CallWithUnexpectedObjectType(v8::Local<v8::Value> receiver) {
LocalContext env;
UnexpectedObjectChecker checker;
SetupTest(receiver, &env, &checker,
"function func(arg) { receiver.api_func(arg); }"
"%PrepareFunctionForOptimization(func);"
"func(value);"
"%OptimizeFunctionOnNextCall(func);"
"func(value);");
CHECK(checker.DidCallFast());
CHECK(checker.DidCallSlow());
}
class TestCFunctionInfo : public v8::CFunctionInfo {
const v8::CTypeInfo& ReturnInfo() const override {
static v8::CTypeInfo return_info =
@ -27565,7 +27649,7 @@ class TestCFunctionInfo : public v8::CFunctionInfo {
const v8::CTypeInfo& ArgumentInfo(unsigned int index) const override {
static v8::CTypeInfo type_info0 =
v8::CTypeInfo::FromCType(v8::CTypeInfo::Type::kUnwrappedApiObject);
v8::CTypeInfo::FromCType(v8::CTypeInfo::Type::kV8Value);
static v8::CTypeInfo type_info1 =
v8::CTypeInfo::FromCType(v8::CTypeInfo::Type::kBool);
switch (index) {
@ -27586,31 +27670,11 @@ void CheckDynamicTypeInfo() {
v8::CFunction c_func =
v8::CFunction::Make(ApiNumberChecker<bool>::FastCallback, &type_info);
CHECK_EQ(c_func.ArgumentCount(), 2);
CHECK_EQ(c_func.ArgumentInfo(0).GetType(),
v8::CTypeInfo::Type::kUnwrappedApiObject);
CHECK_EQ(c_func.ArgumentInfo(0).GetType(), v8::CTypeInfo::Type::kV8Value);
CHECK_EQ(c_func.ArgumentInfo(1).GetType(), v8::CTypeInfo::Type::kBool);
CHECK_EQ(c_func.ReturnInfo().GetType(), v8::CTypeInfo::Type::kVoid);
}
} // namespace
namespace v8 {
template <typename T>
class WrapperTraits<BasicApiChecker<T, ApiNumberChecker<T>>> {
public:
static const void* GetTypeInfo() {
static const int tag = 0;
return reinterpret_cast<const void*>(&tag);
}
};
template <>
class WrapperTraits<int> {
public:
static const void* GetTypeInfo() {
static const int tag = 0;
return reinterpret_cast<const void*>(&tag);
}
};
} // namespace v8
#endif // V8_LITE_MODE
TEST(FastApiCalls) {
@ -27663,6 +27727,9 @@ TEST(FastApiCalls) {
v8_num(std::numeric_limits<double>::infinity()));
CallAndCheck<int32_t>(0, Behavior::kNoException,
ApiCheckerResult::kSlowCalled, v8_str("some_string"));
CallAndCheck<int32_t>(0, Behavior::kNoException,
ApiCheckerResult::kSlowCalled,
CompileRun("new Proxy({}, {});"));
CallAndCheck<int32_t>(0, Behavior::kNoException,
ApiCheckerResult::kSlowCalled,
v8::Object::New(isolate));
@ -27749,9 +27816,20 @@ TEST(FastApiCalls) {
CallWithLessArguments();
CallWithMoreArguments();
// Wrong types of receiver
CallWithUnexpectedReceiverType(v8_num(123));
CallWithUnexpectedReceiverType(v8_str("str"));
CallWithUnexpectedReceiverType(CompileRun("new Proxy({}, {});"));
// Wrong types for argument of type object
CallWithUnexpectedObjectType(v8_num(123));
CallWithUnexpectedObjectType(v8_str("str"));
CallWithUnexpectedObjectType(CompileRun("new Proxy({}, {});"));
// TODO(mslekova): Add corner cases for 64-bit values.
// TODO(mslekova): Add main cases for float and double.
// TODO(mslekova): Restructure the tests so that the fast optimized calls
// are compared against the slow optimized calls.
// TODO(mslekova): Add tests for FTI that requires access check.
#endif // V8_LITE_MODE
}