Commit 6124a534 authored by Maya Lekova's avatar Maya Lekova Committed by Commit Bot

[fastcall] Add support for leaf interface type checks

This CL adds an IsTemplateForApiObject method to FunctionTemplate
allowing the embedder to check whether a given API object was
instantiated by this template without including parent templates
in the search. It also replaces the v8::ApiObject in the fast API
with a raw v8::Value pointer to allow use of standard C++ casts.

Bug: chromium:1052746
Change-Id: I0812ec8b4daaa5f5005aabf10b63e1e84e0b8f03
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2595310
Commit-Queue: Maya Lekova <mslekova@chromium.org>
Reviewed-by: 's avatarGeorg Neis <neis@chromium.org>
Reviewed-by: 's avatarCamillo Bruni <cbruni@chromium.org>
Reviewed-by: 's avatarSathya Gunasekaran  <gsathya@chromium.org>
Cr-Commit-Position: refs/heads/master@{#73999}
parent 76d83daa
......@@ -70,8 +70,8 @@
* return GetInternalField<CustomEmbedderType,
* kV8EmbedderWrapperObjectIndex>(wrapper);
* }
* static void FastMethod(v8::ApiObject receiver_obj, int param) {
* v8::Object* v8_object = reinterpret_cast<v8::Object*>(&api_object);
* static void FastMethod(v8::Value* receiver_obj, int param) {
* v8::Object* v8_object = v8::Object::Cast(receiver_obj);
* CustomEmbedderType* receiver = static_cast<CustomEmbedderType*>(
* receiver_obj->GetAlignedPointerFromInternalField(
* kV8EmbedderWrapperObjectIndex));
......@@ -190,6 +190,7 @@
#include <tuple>
#include <type_traits>
#include "v8.h" // NOLINT(build/include_directory)
#include "v8config.h" // NOLINT(build/include_directory)
namespace v8 {
......@@ -312,7 +313,7 @@ class V8_EXPORT CFunction {
};
};
struct ApiObject {
struct V8_DEPRECATE_SOON("Use v8::Value* instead.") ApiObject {
uintptr_t address;
};
......@@ -346,8 +347,12 @@ struct FastApiCallbackOptions {
/**
* The `data` passed to the FunctionTemplate constructor, or `undefined`.
* `data_ptr` allows for default constructing FastApiCallbackOptions.
*/
const ApiObject data;
union {
uintptr_t data_ptr;
v8::Value data;
};
};
namespace internal {
......@@ -417,7 +422,11 @@ struct TypeInfoHelper {
V(uint64_t, kUint64) \
V(float, kFloat32) \
V(double, kFloat64) \
V(ApiObject, kV8Value)
V(ApiObject, kV8Value) \
V(v8::Value*, kV8Value)
// ApiObject was a temporary solution to wrap the pointer to the v8::Value.
// Please use v8::Value* in new code, as ApiObject will be deprecated soon.
BASIC_C_TYPES(SPECIALIZE_GET_TYPE_INFO_HELPER_FOR)
......
......@@ -6590,6 +6590,15 @@ class V8_EXPORT FunctionTemplate : public Template {
*/
bool HasInstance(Local<Value> object);
/**
* Returns true if the given value is an API object that was constructed by an
* instance of this function template (without checking for inheriting
* function templates).
*
* This is an experimental feature and may still change significantly.
*/
bool IsLeafTemplateForApiObject(Value* value) const;
V8_INLINE static FunctionTemplate* Cast(Data* data);
private:
......
......@@ -6399,6 +6399,15 @@ bool FunctionTemplate::HasInstance(v8::Local<v8::Value> value) {
return false;
}
bool FunctionTemplate::IsLeafTemplateForApiObject(v8::Value* value) const {
i::DisallowGarbageCollection no_gc;
i::Object object = *Utils::OpenHandle(value);
auto self = Utils::OpenHandle(this);
return self->IsLeafTemplateForApiObject(object);
}
Local<External> v8::External::New(Isolate* isolate, void* value) {
STATIC_ASSERT(sizeof(value) == sizeof(i::Address));
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
......
......@@ -193,6 +193,7 @@ class EffectControlLinearizer {
void LowerTransitionElementsKind(Node* node);
Node* LowerLoadFieldByIndex(Node* node);
Node* LowerLoadMessage(Node* node);
Node* AdaptFastCallArgument(Node* node, CTypeInfo::Type arg_type);
Node* LowerFastApiCall(Node* node);
Node* LowerLoadTypedElement(Node* node);
Node* LowerLoadDataViewElement(Node* node);
......@@ -4975,7 +4976,8 @@ void EffectControlLinearizer::LowerStoreMessage(Node* node) {
__ StoreField(AccessBuilder::ForExternalIntPtr(), offset, object_pattern);
}
static MachineType MachineTypeFor(CTypeInfo::Type type) {
namespace {
MachineType MachineTypeFor(CTypeInfo::Type type) {
switch (type) {
case CTypeInfo::Type::kVoid:
return MachineType::AnyTagged();
......@@ -4997,6 +4999,30 @@ static MachineType MachineTypeFor(CTypeInfo::Type type) {
return MachineType::AnyTagged();
}
}
} // namespace
Node* EffectControlLinearizer::AdaptFastCallArgument(Node* node,
CTypeInfo::Type arg_type) {
switch (arg_type) {
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;
}
}
}
Node* EffectControlLinearizer::LowerFastApiCall(Node* node) {
FastApiCallNode n(node);
......@@ -5061,13 +5087,9 @@ Node* EffectControlLinearizer::LowerFastApiCall(Node* node) {
inputs[0] = n.target();
for (int i = FastApiCallNode::kFastTargetInputCount;
i < c_arg_count + FastApiCallNode::kFastTargetInputCount; ++i) {
if (c_signature->ArgumentInfo(i - 1).GetType() ==
CTypeInfo::Type::kFloat32) {
inputs[i] =
__ TruncateFloat64ToFloat32(NodeProperties::GetValueInput(node, i));
} else {
inputs[i] = NodeProperties::GetValueInput(node, i);
}
AdaptFastCallArgument(NodeProperties::GetValueInput(node, i),
c_signature->ArgumentInfo(i - 1).GetType());
}
if (c_signature->HasOptions()) {
inputs[c_arg_count + 1] = stack_slot;
......
......@@ -21,16 +21,18 @@
namespace v8 {
namespace {
thread_local Persistent<FunctionTemplate> api_obj_ctor;
class FastCApiObject {
public:
static double AddAllFastCallback(ApiObject receiver, bool should_fallback,
static double AddAllFastCallback(v8::Value* receiver, bool should_fallback,
int32_t arg_i32, uint32_t arg_u32,
int64_t arg_i64, uint64_t arg_u64,
float arg_f32, double arg_f64,
FastApiCallbackOptions& options) {
Value* receiver_value = reinterpret_cast<Value*>(&receiver);
CHECK(receiver_value->IsObject());
FastCApiObject* self = UnwrapObject(Object::Cast(receiver_value));
CHECK(receiver->IsObject());
FastCApiObject* self = UnwrapObject(Object::Cast(receiver));
self->fast_call_count_++;
if (should_fallback) {
......@@ -77,12 +79,11 @@ class FastCApiObject {
args.GetReturnValue().Set(Number::New(isolate, sum));
}
static int Add32BitIntFastCallback(ApiObject receiver, bool should_fallback,
static int Add32BitIntFastCallback(v8::Value* receiver, bool should_fallback,
int32_t arg_i32, uint32_t arg_u32,
FastApiCallbackOptions& options) {
Value* receiver_value = reinterpret_cast<Value*>(&receiver);
CHECK(receiver_value->IsObject());
FastCApiObject* self = UnwrapObject(Object::Cast(receiver_value));
CHECK(receiver->IsObject());
FastCApiObject* self = UnwrapObject(Object::Cast(receiver));
self->fast_call_count_++;
if (should_fallback) {
......@@ -111,6 +112,55 @@ class FastCApiObject {
args.GetReturnValue().Set(Number::New(isolate, sum));
}
static bool IsFastCApiObjectFastCallback(v8::Value* receiver,
bool should_fallback, v8::Value* arg,
FastApiCallbackOptions& options) {
CHECK(receiver->IsObject());
FastCApiObject* self = UnwrapObject(Object::Cast(receiver));
self->fast_call_count_++;
if (should_fallback) {
options.fallback = 1;
return false;
}
Object* object = Object::Cast(arg);
if (!IsValidApiObject(object)) return false;
internal::Isolate* i_isolate =
internal::IsolateFromNeverReadOnlySpaceObject(
*reinterpret_cast<internal::Address*>(object));
CHECK_NOT_NULL(i_isolate);
Isolate* isolate = reinterpret_cast<Isolate*>(i_isolate);
HandleScope handle_scope(isolate);
return api_obj_ctor.Get(isolate)->IsLeafTemplateForApiObject(object);
}
static void IsFastCApiObjectSlowCallback(
const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
FastCApiObject* self = UnwrapObject(*args.This());
self->slow_call_count_++;
HandleScope handle_scope(isolate);
bool result = false;
if (args.Length() < 2) {
args.GetIsolate()->ThrowError(
"is_valid_api_object should be called with 2 arguments");
return;
}
Object* object = Object::Cast(*args[1]);
if (!IsValidApiObject(object)) {
result = false;
} else {
result = api_obj_ctor.Get(args.GetIsolate())
->IsLeafTemplateForApiObject(object);
}
args.GetReturnValue().Set(Boolean::New(isolate, result));
}
static void FastCallCount(const FunctionCallbackInfo<Value>& args) {
FastCApiObject* self = UnwrapObject(*args.This());
args.GetReturnValue().Set(
......@@ -141,12 +191,14 @@ class FastCApiObject {
static const int kV8WrapperObjectIndex = 1;
private:
static FastCApiObject* UnwrapObject(Object* object) {
static bool IsValidApiObject(Object* object) {
i::Address addr = *reinterpret_cast<i::Address*>(object);
auto instance_type = i::Internals::GetInstanceType(addr);
if (instance_type != i::Internals::kJSObjectType &&
instance_type != i::Internals::kJSApiObjectType &&
instance_type != i::Internals::kJSSpecialApiObjectType) {
return (instance_type == i::Internals::kJSApiObjectType ||
instance_type == i::Internals::kJSSpecialApiObjectType);
}
static FastCApiObject* UnwrapObject(Object* object) {
if (!IsValidApiObject(object)) {
return nullptr;
}
FastCApiObject* wrapped = reinterpret_cast<FastCApiObject*>(
......@@ -167,8 +219,7 @@ class FastCApiObject {
thread_local FastCApiObject kFastCApiObject;
} // namespace
// TODO(mslekova): Rename the fast_c_api helper to FastCAPI.
void CreateObject(const FunctionCallbackInfo<Value>& info) {
void CreateFastCAPIObject(const FunctionCallbackInfo<Value>& info) {
if (!info.IsConstructCall()) {
info.GetIsolate()->ThrowError(
"FastCAPI helper must be constructed with new.");
......@@ -186,13 +237,14 @@ void CreateObject(const FunctionCallbackInfo<Value>& info) {
}
Local<FunctionTemplate> Shell::CreateTestFastCApiTemplate(Isolate* isolate) {
Local<FunctionTemplate> api_obj_ctor =
FunctionTemplate::New(isolate, CreateObject);
Local<Signature> signature = Signature::New(isolate, api_obj_ctor);
api_obj_ctor.Reset(isolate,
FunctionTemplate::New(isolate, CreateFastCAPIObject));
Local<Signature> signature =
Signature::New(isolate, api_obj_ctor.Get(isolate));
{
CFunction add_all_c_func =
CFunction::Make(FastCApiObject::AddAllFastCallback);
api_obj_ctor->PrototypeTemplate()->Set(
api_obj_ctor.Get(isolate)->PrototypeTemplate()->Set(
isolate, "add_all",
FunctionTemplate::New(isolate, FastCApiObject::AddAllSlowCallback,
Local<Value>(), signature, 1,
......@@ -200,29 +252,53 @@ Local<FunctionTemplate> Shell::CreateTestFastCApiTemplate(Isolate* isolate) {
SideEffectType::kHasSideEffect, &add_all_c_func));
CFunction add_32bit_int_c_func =
CFunction::Make(FastCApiObject::Add32BitIntFastCallback);
api_obj_ctor->PrototypeTemplate()->Set(
api_obj_ctor.Get(isolate)->PrototypeTemplate()->Set(
isolate, "add_32bit_int",
FunctionTemplate::New(
isolate, FastCApiObject::Add32BitIntSlowCallback, Local<Value>(),
signature, 1, ConstructorBehavior::kThrow,
SideEffectType::kHasSideEffect, &add_32bit_int_c_func));
api_obj_ctor->PrototypeTemplate()->Set(
CFunction is_valid_api_object_c_func =
CFunction::Make(FastCApiObject::IsFastCApiObjectFastCallback);
api_obj_ctor.Get(isolate)->PrototypeTemplate()->Set(
isolate, "is_fast_c_api_object",
FunctionTemplate::New(
isolate, FastCApiObject::IsFastCApiObjectSlowCallback,
Local<Value>(), signature, 1, ConstructorBehavior::kThrow,
SideEffectType::kHasSideEffect, &is_valid_api_object_c_func));
api_obj_ctor.Get(isolate)->PrototypeTemplate()->Set(
isolate, "fast_call_count",
FunctionTemplate::New(isolate, FastCApiObject::FastCallCount,
Local<Value>(), signature));
api_obj_ctor->PrototypeTemplate()->Set(
api_obj_ctor.Get(isolate)->PrototypeTemplate()->Set(
isolate, "slow_call_count",
FunctionTemplate::New(isolate, FastCApiObject::SlowCallCount,
Local<Value>(), signature));
api_obj_ctor->PrototypeTemplate()->Set(
api_obj_ctor.Get(isolate)->PrototypeTemplate()->Set(
isolate, "reset_counts",
FunctionTemplate::New(isolate, FastCApiObject::ResetCounts,
Local<Value>(), signature));
}
api_obj_ctor->InstanceTemplate()->SetInternalFieldCount(
api_obj_ctor.Get(isolate)->InstanceTemplate()->SetInternalFieldCount(
FastCApiObject::kV8WrapperObjectIndex + 1);
return api_obj_ctor;
return api_obj_ctor.Get(isolate);
}
void CreateLeafInterfaceObject(const FunctionCallbackInfo<Value>& info) {
if (!info.IsConstructCall()) {
info.GetIsolate()->ThrowError(
"LeafInterfaceType helper must be constructed with new.");
}
}
Local<FunctionTemplate> Shell::CreateLeafInterfaceTypeTemplate(
Isolate* isolate) {
Local<FunctionTemplate> leaf_object_ctor =
FunctionTemplate::New(isolate, CreateLeafInterfaceObject);
leaf_object_ctor->SetClassName(
String::NewFromUtf8Literal(isolate, "LeafInterfaceType"));
return leaf_object_ctor;
}
} // namespace v8
......@@ -2733,8 +2733,10 @@ Local<ObjectTemplate> Shell::CreateD8Template(Isolate* isolate) {
// constructor when --correctness_fuzzer_suppressions is on.
if (i::FLAG_turbo_fast_api_calls &&
!i::FLAG_correctness_fuzzer_suppressions) {
test_template->Set(isolate, "fast_c_api",
test_template->Set(isolate, "FastCAPI",
Shell::CreateTestFastCApiTemplate(isolate));
test_template->Set(isolate, "LeafInterfaceType",
Shell::CreateLeafInterfaceTypeTemplate(isolate));
}
d8_template->Set(isolate, "test", test_template);
......
......@@ -635,6 +635,8 @@ class Shell : public i::AllStatic {
static Local<ObjectTemplate> CreateRealmTemplate(Isolate* isolate);
static Local<ObjectTemplate> CreateD8Template(Isolate* isolate);
static Local<FunctionTemplate> CreateTestFastCApiTemplate(Isolate* isolate);
static Local<FunctionTemplate> CreateLeafInterfaceTypeTemplate(
Isolate* isolate);
static MaybeLocal<Context> CreateRealm(
const v8::FunctionCallbackInfo<v8::Value>& args, int index,
......
......@@ -1319,7 +1319,7 @@ Handle<SharedFunctionInfo> FunctionTemplateInfo::GetOrCreateSharedFunctionInfo(
return result;
}
bool FunctionTemplateInfo::IsTemplateFor(Map map) {
bool FunctionTemplateInfo::IsTemplateFor(Map map) const {
RCS_SCOPE(
LocalHeap::Current() == nullptr
? GetIsolate()->counters()->runtime_call_stats()
......@@ -1349,6 +1349,26 @@ bool FunctionTemplateInfo::IsTemplateFor(Map map) {
return false;
}
bool FunctionTemplateInfo::IsLeafTemplateForApiObject(Object object) const {
i::DisallowGarbageCollection no_gc;
if (!object.IsJSApiObject()) {
return false;
}
bool result = false;
Map map = HeapObject::cast(object).map();
Object constructor_obj = map.GetConstructor();
if (constructor_obj.IsJSFunction()) {
JSFunction fun = JSFunction::cast(constructor_obj);
result = (*this == fun.shared().function_data(kAcquireLoad));
} else if (constructor_obj.IsFunctionTemplateInfo()) {
result = (*this == constructor_obj);
}
DCHECK_IMPLIES(result, IsTemplateFor(map));
return result;
}
// static
FunctionTemplateRareData FunctionTemplateInfo::AllocateFunctionTemplateRareData(
Isolate* isolate, Handle<FunctionTemplateInfo> function_template_info) {
......
......@@ -150,7 +150,11 @@ class FunctionTemplateInfo
inline FunctionTemplateInfo GetParent(Isolate* isolate);
// Returns true if |object| is an instance of this function template.
inline bool IsTemplateFor(JSObject object);
bool IsTemplateFor(Map map);
bool IsTemplateFor(Map map) const;
// Returns true if |object| is an API object and is constructed by this
// particular function template (skips walking up the chain of inheriting
// functions that is done by IsTemplateFor).
bool IsLeafTemplateForApiObject(Object object) const;
inline bool instantiated();
inline bool BreakAtEntry();
......
......@@ -842,11 +842,11 @@ DEFINE_OPERATORS_FOR_FLAGS(ApiCheckerResultFlags)
bool IsValidUnwrapObject(v8::Object* object);
template <typename T, int offset>
template <typename T>
T* GetInternalField(v8::Object* wrapper) {
assert(offset < wrapper->InternalFieldCount());
assert(kV8WrapperObjectIndex < wrapper->InternalFieldCount());
return reinterpret_cast<T*>(
wrapper->GetAlignedPointerFromInternalField(offset));
wrapper->GetAlignedPointerFromInternalField(kV8WrapperObjectIndex));
}
#endif // ifndef CCTEST_H_
This diff is collapsed.
......@@ -3934,17 +3934,17 @@ UNINITIALIZED_TEST(DetailedSourcePositionAPI_Inlining) {
namespace {
struct FastApiReceiver {
static void FastCallback(v8::ApiObject receiver, int argument,
static void FastCallback(v8::Value* receiver, int argument,
v8::FastApiCallbackOptions& options) {
// TODO(mslekova): The fallback is not used by the test. Replace this
// with a CHECK.
v8::Object* receiver_obj = reinterpret_cast<v8::Object*>(&receiver);
v8::Object* receiver_obj = v8::Object::Cast(receiver);
if (!IsValidUnwrapObject(receiver_obj)) {
options.fallback = 1;
return;
}
FastApiReceiver* receiver_ptr =
GetInternalField<FastApiReceiver, kV8WrapperObjectIndex>(receiver_obj);
GetInternalField<FastApiReceiver>(receiver_obj);
receiver_ptr->result_ |= ApiCheckerResult::kFastCalled;
......@@ -3960,8 +3960,7 @@ struct FastApiReceiver {
info.GetIsolate()->ThrowError("Called with a non-object.");
return;
}
FastApiReceiver* receiver =
GetInternalField<FastApiReceiver, kV8WrapperObjectIndex>(receiver_obj);
FastApiReceiver* receiver = GetInternalField<FastApiReceiver>(receiver_obj);
receiver->result_ |= ApiCheckerResult::kSlowCalled;
}
......
......@@ -13,8 +13,8 @@
// it's not suitable for deoptimization fuzzing.
// Flags: --deopt-every-n-times=0
assertThrows(() => d8.test.fast_c_api());
const fast_c_api = new d8.test.fast_c_api();
assertThrows(() => d8.test.FastCAPI());
const fast_c_api = new d8.test.FastCAPI();
// ----------- add_all -----------
// `add_all` has the following signature:
......
// 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 interface types used with fast API calls.
// Flags: --turbo-fast-api-calls --allow-natives-syntax --opt
// Flags: --no-always-opt
// Flags: --deopt-every-n-times=0
const fast_c_api = new d8.test.FastCAPI();
// We create another API object to avoid migrating the map of fast_c_api
// when using it as a prototype.
const another_fast_c_api = new d8.test.FastCAPI();
function is_fast_c_api_object(obj, should_fallback = false) {
return fast_c_api.is_fast_c_api_object(should_fallback, obj);
}
%PrepareFunctionForOptimization(is_fast_c_api_object);
assertTrue(is_fast_c_api_object(another_fast_c_api));
%OptimizeFunctionOnNextCall(is_fast_c_api_object);
// Test that fast_c_api is an API object wrapping a C++ one.
fast_c_api.reset_counts();
assertTrue(is_fast_c_api_object(another_fast_c_api));
assertOptimized(is_fast_c_api_object);
assertEquals(1, fast_c_api.fast_call_count());
assertEquals(0, fast_c_api.slow_call_count());
// Same test but taking the fallback to the slow path.
fast_c_api.reset_counts();
assertTrue(is_fast_c_api_object(another_fast_c_api, true));
assertOptimized(is_fast_c_api_object);
assertEquals(1, fast_c_api.fast_call_count());
assertEquals(1, fast_c_api.slow_call_count());
// Test that a regular JS object returns false.
fast_c_api.reset_counts();
assertFalse(is_fast_c_api_object({}));
assertOptimized(is_fast_c_api_object);
assertEquals(1, fast_c_api.fast_call_count());
assertEquals(0, fast_c_api.slow_call_count());
// Test that a subclassed object returns false.
const child = Object.create(another_fast_c_api);
fast_c_api.reset_counts();
assertFalse(is_fast_c_api_object(child));
assertOptimized(is_fast_c_api_object);
assertEquals(1, fast_c_api.fast_call_count());
assertEquals(0, fast_c_api.slow_call_count());
// Test that passing an API object of an unrelated (leaf) type
// causes the function to return false, because an object of
// FastCAPI type is expected as an argument.
const leaf_interface_obj = new d8.test.LeafInterfaceType();
fast_c_api.reset_counts();
assertFalse(is_fast_c_api_object(leaf_interface_obj));
assertOptimized(is_fast_c_api_object);
assertEquals(1, fast_c_api.fast_call_count());
assertEquals(0, fast_c_api.slow_call_count());
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