blob: 87a5b463b253804d27385774c5b1a2e50625024e [file]
// 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.
#include "src/objects/templates.h"
#include <stdint.h>
#include <algorithm>
#include <cstdint>
#include <limits>
#include <optional>
#include <span>
#include "include/v8-fast-api-calls.h"
#include "src/api/api-inl.h"
#include "src/base/macros.h"
#include "src/common/assert-scope.h"
#include "src/common/globals.h"
#include "src/execution/isolate.h"
#include "src/heap/factory.h"
#include "src/heap/heap-inl.h"
#include "src/objects/contexts-inl.h"
#include "src/objects/function-kind.h"
#include "src/objects/hash-table-inl.h"
#include "src/objects/instance-type-inl.h"
#include "src/objects/js-function-inl.h"
#include "src/objects/managed-inl.h"
#include "src/objects/map-inl.h"
#include "src/objects/name-inl.h"
#include "src/objects/object-conversions-inl.h"
#include "src/objects/objects-inl.h"
#include "src/objects/shared-function-info-inl.h"
#include "src/objects/string-inl.h"
namespace v8::internal {
bool FunctionTemplateInfo::HasInstanceType() {
return instance_type() != kNoJSApiObjectType;
}
Handle<SharedFunctionInfo> FunctionTemplateInfo::GetOrCreateSharedFunctionInfo(
Isolate* isolate, DirectHandle<FunctionTemplateInfo> info,
MaybeDirectHandle<Name> maybe_name) {
Tagged<Object> current_info = info->shared_function_info();
if (IsSharedFunctionInfo(current_info)) {
return handle(Cast<SharedFunctionInfo>(current_info), isolate);
}
DirectHandle<Name> name;
DirectHandle<String> name_string;
if (maybe_name.ToHandle(&name) && IsString(*name)) {
name_string = Cast<String>(name);
} else if (IsString(info->class_name())) {
name_string = direct_handle(Cast<String>(info->class_name()), isolate);
} else {
name_string = isolate->factory()->empty_string();
}
FunctionKind function_kind;
if (info->remove_prototype()) {
function_kind = FunctionKind::kConciseMethod;
} else {
function_kind = FunctionKind::kNormalFunction;
}
Handle<SharedFunctionInfo> sfi =
isolate->factory()->NewSharedFunctionInfoForApiFunction(name_string, info,
function_kind);
DCHECK(sfi->IsApiFunction());
info->set_shared_function_info(*sfi);
return sfi;
}
bool FunctionTemplateInfo::IsTemplateFor(Tagged<Map> map) const {
#ifdef V8_RUNTIME_CALL_STATS
LocalHeap* local_heap = LocalHeap::Current();
RCS_SCOPE(local_heap->is_main_thread()
? Isolate::Current()->counters()->runtime_call_stats()
: LocalIsolate::FromHeap(local_heap)->runtime_call_stats(),
RuntimeCallCounterId::kIsTemplateFor);
#endif // V8_RUNTIME_CALL_STATS
// There is a constraint on the object; check.
if (!IsJSObjectMap(map)) return false;
// Fetch the constructor function of the object.
Tagged<Object> cons_obj = map->GetConstructor();
Tagged<Object> type;
if (IsJSFunction(cons_obj)) {
Tagged<JSFunction> fun = Cast<JSFunction>(cons_obj);
if (!fun->shared()->IsApiFunction()) return false;
type = fun->shared()->api_func_data();
} else if (IsFunctionTemplateInfo(cons_obj)) {
type = Cast<FunctionTemplateInfo>(cons_obj);
} else {
return false;
}
DCHECK(IsFunctionTemplateInfo(type));
// Iterate through the chain of inheriting function templates to
// see if the required one occurs.
while (IsFunctionTemplateInfo(type)) {
if (type == this) return true;
type = Cast<FunctionTemplateInfo>(type)->GetParentTemplate();
}
// Didn't find the required type in the inheritance chain.
return false;
}
bool FunctionTemplateInfo::IsLeafTemplateForApiObject(
Tagged<Object> object) const {
i::DisallowGarbageCollection no_gc;
if (!IsJSApiObject(object)) {
return false;
}
bool result = false;
Tagged<Map> map = Cast<HeapObject>(object)->map();
Tagged<Object> constructor_obj = map->GetConstructor();
if (IsJSFunction(constructor_obj)) {
Tagged<JSFunction> fun = Cast<JSFunction>(constructor_obj);
result = (this == fun->shared()->api_func_data());
} else if (IsFunctionTemplateInfo(constructor_obj)) {
result = (this == constructor_obj);
}
DCHECK_IMPLIES(result, IsTemplateFor(map));
return result;
}
// static
void FunctionTemplateInfo::SealAndPrepareForPromotionToReadOnly(
Isolate* isolate, DirectHandle<FunctionTemplateInfo> info) {
if (info->should_promote_to_read_only()) return;
CHECK(!HeapLayout::InReadOnlySpace(*info));
info->EnsureHasSerialNumber(isolate);
GetOrCreateSharedFunctionInfo(isolate, info,
isolate->factory()->empty_string());
info->set_should_promote_to_read_only(true);
info->set_published(true);
}
// static
Tagged<FunctionTemplateRareData>
FunctionTemplateInfo::AllocateFunctionTemplateRareData(
Isolate* isolate,
DirectHandle<FunctionTemplateInfo> function_template_info) {
DCHECK(IsUndefined(function_template_info->rare_data(kAcquireLoad)));
DirectHandle<FunctionTemplateRareData> rare_data =
isolate->factory()->NewFunctionTemplateRareData();
function_template_info->set_rare_data(*rare_data, kReleaseStore);
return *rare_data;
}
std::optional<Tagged<Name>> FunctionTemplateInfo::TryGetCachedPropertyName(
Isolate* isolate, Tagged<Object> getter) {
DisallowGarbageCollection no_gc;
if (!IsFunctionTemplateInfo(getter)) {
if (!IsJSFunction(getter)) return {};
Tagged<SharedFunctionInfo> info = Cast<JSFunction>(getter)->shared();
if (!info->IsApiFunction()) return {};
getter = info->api_func_data();
}
// Check if the accessor uses a cached property.
Tagged<Object> maybe_name =
Cast<FunctionTemplateInfo>(getter)->cached_property_name();
if (IsTheHole(maybe_name)) return {};
return Cast<Name>(maybe_name);
}
uint32_t FunctionTemplateInfo::GetCFunctionsCount() const {
i::DisallowHeapAllocation no_gc;
return Cast<FixedArray>(GetCFunctionOverloads())->ulength().value();
}
CFunctionWithSignature FunctionTemplateInfo::GetCFunction(
uint32_t index) const {
i::DisallowGarbageCollection no_gc;
const CFunction* c_function = reinterpret_cast<const CFunction*>(
Cast<Foreign>(Cast<FixedArray>(GetCFunctionOverloads())->get(index))
->template foreign_address<kCFunctionTag>());
return CFunctionWithSignature(
reinterpret_cast<Address>(c_function->GetAddress()),
c_function->GetTypeInfo());
}
// static
void ObjectTemplateInfo::SealAndPrepareForPromotionToReadOnly(
Isolate* isolate, DirectHandle<ObjectTemplateInfo> info) {
if (info->should_promote_to_read_only()) return;
CHECK(!HeapLayout::InReadOnlySpace(*info));
info->EnsureHasSerialNumber(isolate);
info->set_should_promote_to_read_only(true);
}
// static
DirectHandle<DictionaryTemplateInfo> DictionaryTemplateInfo::Create(
Isolate* isolate, const std::span<const std::string_view>& names) {
DirectHandle<FixedArray> property_names = isolate->factory()->NewFixedArray(
static_cast<int>(names.size()), AllocationType::kOld);
int index = 0;
uint32_t unused_array_index;
for (const std::string_view& name : names) {
DirectHandle<String> internalized_name =
isolate->factory()->InternalizeString(
base::Vector<const char>(name.data(), name.length()));
// Check that property name cannot be used as index.
CHECK(!internalized_name->AsArrayIndex(&unused_array_index));
property_names->set(index, *internalized_name);
++index;
}
return isolate->factory()->NewDictionaryTemplateInfo(property_names);
}
namespace {
DirectHandle<JSObject> CreateSlowJSObjectWithProperties(
Isolate* isolate, DirectHandle<FixedArray> property_names,
const std::span<MaybeLocal<Value>>& property_values,
int num_properties_set) {
DirectHandle<JSObject> object = isolate->factory()->NewSlowJSObjectFromMap(
isolate->slow_object_with_object_prototype_map(), num_properties_set,
AllocationType::kYoung);
Handle<PropertyDictionary> properties = handle(
Cast<PropertyDictionary>(object->raw_properties_or_hash()), isolate);
for (int i = 0; i < static_cast<int>(property_values.size()); ++i) {
Local<Value> property_value;
if (!property_values[i].ToLocal(&property_value)) {
continue;
}
properties =
PropertyDictionary::Add(
isolate, properties,
Cast<String>(handle(property_names->get(i), isolate)),
Utils::OpenDirectHandle(*property_value), PropertyDetails::Empty())
.ToHandleChecked();
}
object->set_raw_properties_or_hash(*properties);
return object;
}
} // namespace
// static
DirectHandle<JSObject> DictionaryTemplateInfo::NewInstance(
DirectHandle<NativeContext> context,
DirectHandle<DictionaryTemplateInfo> self,
const std::span<MaybeLocal<Value>>& property_values) {
Isolate* isolate = Isolate::Current();
DirectHandle<FixedArray> property_names(self->property_names(), isolate);
const uint32_t property_names_len = property_names->ulength().value();
CHECK_EQ(property_names_len, property_values.size());
const uint32_t num_properties_set = static_cast<uint32_t>(std::count_if(
property_values.begin(), property_values.end(),
[](const auto& maybe_value) { return !maybe_value.IsEmpty(); }));
if (V8_UNLIKELY(num_properties_set > JSObject::kMaxInObjectProperties)) {
return CreateSlowJSObjectWithProperties(
isolate, property_names, property_values, num_properties_set);
}
const bool can_use_map_cache = num_properties_set == property_names_len;
MaybeDirectHandle<Map> maybe_cached_map;
if (V8_LIKELY(can_use_map_cache)) {
maybe_cached_map = TemplateInfo::ProbeInstantiationsCache<Map>(
isolate, context, self, TemplateInfo::CachingMode::kUnlimited);
}
DirectHandle<Map> cached_map;
if (V8_LIKELY(can_use_map_cache && maybe_cached_map.ToHandle(&cached_map))) {
DCHECK(!cached_map->is_dictionary_map());
bool can_use_cached_map = !cached_map->is_deprecated();
if (V8_LIKELY(can_use_cached_map)) {
// Verify that the cached map can be reused.
auto descriptors = handle(cached_map->instance_descriptors(), isolate);
for (int i = 0; i < static_cast<int>(property_values.size()); ++i) {
DirectHandle<Object> value =
Utils::OpenDirectHandle(*property_values[i].ToLocalChecked());
InternalIndex descriptor{static_cast<size_t>(i)};
const auto details = descriptors->GetDetails(descriptor);
if (!Object::FitsRepresentation(*value, details.representation()) ||
!FieldType::NowContains(descriptors->GetFieldType(descriptor),
value)) {
can_use_cached_map = false;
break;
}
// Double representation means mutable heap number. In this case we need
// to allocate a new heap number to put in the dictionary.
if (details.representation().Equals(Representation::Double())) {
// We allowed coercion in `FitsRepresentation` above which means that
// we may deal with a Smi here.
double value_as_double = Object::NumberValue(Cast<Number>(*value));
if (std::isnan(value_as_double)) {
value_as_double = std::numeric_limits<double>::quiet_NaN();
}
property_values[i] = ToApiHandle<v8::Object>(
isolate->factory()->NewHeapNumber(value_as_double));
}
}
if (V8_LIKELY(can_use_cached_map)) {
// Create the object from the cached map.
CHECK(!cached_map->is_deprecated());
CHECK_EQ(context->object_function_prototype(), cached_map->prototype());
auto object = isolate->factory()->NewJSObjectFromMap(
cached_map, AllocationType::kYoung);
DisallowGarbageCollection no_gc;
for (int i = 0; i < static_cast<int>(property_values.size()); ++i) {
Local<Value> property_value = property_values[i].ToLocalChecked();
DirectHandle<Object> value = Utils::OpenDirectHandle(*property_value);
const FieldIndex index = FieldIndex::ForPropertyIndex(
*cached_map, i, Representation::Tagged());
object->FastPropertyAtPut(index, *value,
WriteBarrierMode::SKIP_WRITE_BARRIER);
}
return object;
}
}
// A cached map was either deprecated or the descriptors changed in
// incompatible ways. We clear the cached map and continue with the generic
// path.
TemplateInfo::UncacheTemplateInstantiation(
isolate, context, self, TemplateInfo::CachingMode::kUnlimited);
}
// General case: We either don't have a cached map, or it is unusable for the
// values provided.
DirectHandle<Map> current_map = isolate->factory()->ObjectLiteralMapFromCache(
context, num_properties_set);
DirectHandle<JSObject> object =
isolate->factory()->NewJSObjectFromMap(current_map);
int current_property_index = 0;
for (int i = 0; i < static_cast<int>(property_values.size()); ++i) {
Local<Value> property_value;
if (!property_values[i].ToLocal(&property_value)) {
continue;
}
auto name = Cast<String>(handle(property_names->get(i), isolate));
DirectHandle<Object> value = Utils::OpenDirectHandle(*property_value);
constexpr PropertyAttributes attributes = PropertyAttributes::NONE;
constexpr PropertyConstness constness = PropertyConstness::kConst;
current_map = Map::TransitionToDataProperty(isolate, current_map, name,
value, attributes, constness,
StoreOrigin::kNamed);
if (current_map->is_dictionary_map()) {
return CreateSlowJSObjectWithProperties(
isolate, property_names, property_values, num_properties_set);
}
JSObject::MigrateToMap(isolate, object, current_map);
PropertyDetails details = current_map->GetLastDescriptorDetails();
object->WriteToField(InternalIndex(current_property_index), details,
*value);
current_property_index++;
}
if (V8_LIKELY(can_use_map_cache)) {
TemplateInfo::CacheTemplateInstantiation(
isolate, context, self, TemplateInfo::CachingMode::kUnlimited,
direct_handle(object->map(), isolate));
}
return object;
}
// static
MaybeHandle<Object> TemplateInfo::ProbeInstantiationsCache(
Isolate* isolate, DirectHandle<NativeContext> native_context,
DirectHandle<TemplateInfo> info, CachingMode caching_mode) {
DCHECK(info->is_cacheable());
uint32_t serial_number = info->serial_number();
if (serial_number == kUninitializedSerialNumber) {
return {};
}
if (serial_number < kFastTemplateInstantiationsCacheSize) {
Tagged<FixedArray> fast_cache =
native_context->fast_template_instantiations_cache();
Tagged<Object> object = fast_cache->get(serial_number);
if (IsTheHole(object)) {
return {};
}
return handle(object, isolate);
}
Tagged<EphemeronHashTable> cache =
native_context->slow_template_instantiations_cache();
ReadOnlyRoots roots(isolate);
// Instead of detouring via Object::GetHash() load the hash directly.
uint32_t hash = info->GetHash();
InternalIndex entry = cache->FindEntry(roots, info, hash);
if (entry.is_found()) {
return handle(cache->ValueAt(entry), isolate);
}
return {};
}
// static
void TemplateInfo::CacheTemplateInstantiation(
Isolate* isolate, DirectHandle<NativeContext> native_context,
DirectHandle<TemplateInfo> info, CachingMode caching_mode,
DirectHandle<Object> object) {
DCHECK(info->is_cacheable());
uint32_t serial_number = info->EnsureHasSerialNumber(isolate);
if (serial_number < kFastTemplateInstantiationsCacheSize) {
Handle<FixedArray> fast_cache =
handle(native_context->fast_template_instantiations_cache(), isolate);
fast_cache->set(serial_number, *object);
return;
}
Handle<EphemeronHashTable> cache =
handle(native_context->slow_template_instantiations_cache(), isolate);
if (caching_mode == CachingMode::kUnlimited ||
(cache->NumberOfElements() < kMaxTemplateInstantiationsCacheSize)) {
ReadOnlyRoots roots(isolate);
// Instead of detouring via Object::GetHash() load the hash directly.
uint32_t hash = info->GetHash();
auto new_cache =
EphemeronHashTable::Put(isolate, cache, info, object, hash);
if (*new_cache != *cache) {
native_context->set_slow_template_instantiations_cache(*new_cache);
}
}
}
// static
void TemplateInfo::UncacheTemplateInstantiation(
Isolate* isolate, DirectHandle<NativeContext> native_context,
DirectHandle<TemplateInfo> info, CachingMode caching_mode) {
int serial_number = info->serial_number();
if (serial_number == kUninitializedSerialNumber) return;
if (serial_number < kFastTemplateInstantiationsCacheSize) {
Tagged<FixedArray> fast_cache =
native_context->fast_template_instantiations_cache();
DCHECK(!IsUndefined(fast_cache->get(serial_number)));
fast_cache->set(serial_number, ReadOnlyRoots{isolate}.the_hole_value(),
SKIP_WRITE_BARRIER);
return;
}
Handle<EphemeronHashTable> cache =
handle(native_context->slow_template_instantiations_cache(), isolate);
// Instead of detouring via Object::GetHash() load the hash directly.
uint32_t hash = info->GetHash();
bool was_present = false;
auto new_cache =
EphemeronHashTable::Remove(isolate, cache, info, &was_present, hash);
DCHECK(was_present);
if (!new_cache.is_identical_to(cache)) {
native_context->set_slow_template_instantiations_cache(*new_cache);
}
}
bool FunctionTemplateInfo::BreakAtEntry(Isolate* isolate) {
Tagged<Object> maybe_shared = shared_function_info();
if (IsSharedFunctionInfo(maybe_shared)) {
Tagged<SharedFunctionInfo> shared = Cast<SharedFunctionInfo>(maybe_shared);
return shared->BreakAtEntry(isolate);
}
return false;
}
uint32_t TemplateInfo::EnsureHasSerialNumber(Isolate* isolate) {
uint32_t serial_number = this->serial_number();
if (serial_number == kUninitializedSerialNumber) {
CHECK(!HeapLayout::InReadOnlySpace(this));
serial_number = isolate->heap()->GetNextTemplateSerialNumber();
set_serial_number(serial_number);
}
return serial_number;
}
} // namespace v8::internal