blob: b24371402604b2d3b57cda790becf4e4378aa381 [file]
// Copyright 2020 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/shared-function-info.h"
#include <optional>
#include "src/ast/ast.h"
#include "src/ast/scopes.h"
#include "src/base/strong-alias.h"
#include "src/codegen/compiler.h"
#include "src/codegen/optimized-compilation-info.h"
#include "src/common/globals.h"
#include "src/debug/debug.h"
#include "src/diagnostics/code-tracer.h"
#include "src/heap/combined-heap.h"
#include "src/objects/abstract-code-inl.h"
#include "src/objects/shared-function-info-inl.h"
#include "src/strings/string-builder-inl.h"
#if V8_ENABLE_WEBASSEMBLY
#include "src/wasm/wasm-objects-inl.h"
#endif // V8_ENABLE_WEBASSEMBLY
namespace v8::internal {
V8_EXPORT_PRIVATE constexpr Tagged<Smi>
SharedFunctionInfo::kNoSharedNameSentinel;
Tagged<Union<Smi, TrustedObject>> SharedFunctionInfo::GetTrustedData(
IsolateForSandbox isolate) const {
if (HasUnpublishedTrustedData(isolate)) return Smi::zero();
return TrustedPointerField::ReadMaybeEmptyTrustedPointerField<
kTrustedDataIndirectPointerRange>(
Tagged<HeapObject>(this),
offsetof(SharedFunctionInfo, trusted_function_data_), isolate,
kAcquireLoad);
}
uint32_t SharedFunctionInfo::Hash() {
// Hash SharedFunctionInfo based on its start position and script id. Note: we
// don't use the function's literal id since getting that is slow for compiled
// functions.
int start_pos = StartPosition();
int script_id = IsScript(script()) ? Cast<Script>(script())->id() : 0;
return static_cast<uint32_t>(base::hash_combine(start_pos, script_id));
}
void SharedFunctionInfo::Init(ReadOnlyRoots ro_roots, int unique_id) {
DisallowGarbageCollection no_gc;
// Set the function data to the "illegal" builtin. Ideally we'd use some sort
// of "uninitialized" marker here, but it's cheaper to use a valid builtin and
// avoid having to do uninitialized checks elsewhere.
set_builtin_id(Builtin::kIllegal);
// Set the name to the no-name sentinel, this can be updated later.
set_name_or_scope_info(SharedFunctionInfo::kNoSharedNameSentinel,
kReleaseStore, SKIP_WRITE_BARRIER);
// Generally functions won't have feedback, unless they have been created
// from a FunctionLiteral. Those can just reset this field to keep the
// SharedFunctionInfo in a consistent state.
set_raw_outer_scope_info_or_feedback_metadata(ro_roots.the_hole_value(),
SKIP_WRITE_BARRIER);
set_script(ro_roots.undefined_value(), kReleaseStore, SKIP_WRITE_BARRIER);
set_function_literal_id(kInvalidInfoId, kRelaxedStore);
set_unique_id(unique_id);
// Set integer fields (smi or int, depending on the architecture).
set_length(0);
set_internal_formal_parameter_count(JSParameterCount(0));
set_expected_nof_properties(0);
set_raw_function_token_offset(0);
// All flags default to false or 0, except ConstructAsBuiltinBit just because
// we're using the kIllegal builtin.
set_flags(ConstructAsBuiltinBit::encode(true), kRelaxedStore);
set_flags2(0);
UpdateFunctionMapIndex();
set_age(0);
set_feedback_slot(0);
}
// LINT.IfChange(GetSharedFunctionInfoCode)
Tagged<Code> SharedFunctionInfo::GetCode(Isolate* isolate) const {
if (HasTrustedData()) {
Tagged<Union<Smi, TrustedObject>> trusted_data = GetTrustedData(isolate);
DCHECK(trusted_data != Smi::zero());
if (IsBytecodeArray(trusted_data)) {
// Having a bytecode array means we are a compiled, interpreted function.
DCHECK(HasBytecodeArray());
return isolate->builtins()->code(Builtin::kInterpreterEntryTrampoline);
}
if (Tagged<Code> code; TryCast(trusted_data, &code)) {
// Having baseline Code means we are a compiled, baseline function.
DCHECK(HasBaselineCode());
SBXCHECK_EQ(code->kind(), CodeKind::BASELINE);
return code;
}
if (IsInterpreterData(trusted_data)) {
Tagged<Code> code = InterpreterTrampoline(isolate);
DCHECK(IsCode(code));
DCHECK(code->is_interpreter_trampoline_builtin());
return code;
}
if (IsUncompiledData(trusted_data)) {
// Having uncompiled data (with or without scope) means we need to
// compile.
DCHECK(HasUncompiledData(isolate));
return isolate->builtins()->code(Builtin::kCompileLazy);
}
#if V8_ENABLE_WEBASSEMBLY
if (IsWasmExportedFunctionData(trusted_data)) {
// Having a WasmExportedFunctionData means the code is in there.
DCHECK(HasWasmExportedFunctionData(isolate));
return wasm_exported_function_data()->wrapper_code(isolate);
}
if (IsWasmCapiFunctionData(trusted_data)) {
return wasm_capi_function_data()->wrapper_code(isolate);
}
#endif // V8_ENABLE_WEBASSEMBLY
} else {
DCHECK(HasUntrustedData());
Tagged<Object> untrusted_data = GetUntrustedData();
if (IsSmi(untrusted_data)) {
// Holding a Smi means we are a builtin.
DCHECK(HasBuiltinId());
return isolate->builtins()->code(builtin_id());
}
if (IsFunctionTemplateInfo(untrusted_data)) {
// Having a function template info means we are an API function.
DCHECK(IsApiFunction());
return isolate->builtins()->code(Builtin::kHandleApiCallOrConstruct);
}
#if V8_ENABLE_WEBASSEMBLY
if (IsWasmResumeData(untrusted_data)) {
if (static_cast<wasm::OnResume>(
Cast<WasmResumeData>(untrusted_data)->on_resume()) ==
wasm::OnResume::kContinue) {
return isolate->builtins()->code(Builtin::kWasmResume);
} else {
return isolate->builtins()->code(Builtin::kWasmReject);
}
}
#endif // V8_ENABLE_WEBASSEMBLY
}
UNREACHABLE();
}
// LINT.ThenChange(/src/codegen/code-stub-assembler.cc:GetSharedFunctionInfoCode)
SharedFunctionInfo::ScriptIterator::ScriptIterator(Isolate* isolate,
Tagged<Script> script)
: ScriptIterator(handle(script->infos(), isolate)) {}
SharedFunctionInfo::ScriptIterator::ScriptIterator(Handle<WeakFixedArray> infos)
: infos_(infos), index_(0) {}
Tagged<SharedFunctionInfo> SharedFunctionInfo::ScriptIterator::Next() {
const uint32_t infos_len = infos_->ulength().value();
while (index_ < infos_len) {
Tagged<MaybeObject> raw = infos_->get(index_++);
Tagged<HeapObject> heap_object;
if (!raw.GetHeapObject(&heap_object) ||
!IsSharedFunctionInfo(heap_object)) {
continue;
}
return Cast<SharedFunctionInfo>(heap_object);
}
return {};
}
void SharedFunctionInfo::ScriptIterator::Reset(Isolate* isolate,
Tagged<Script> script) {
infos_ = handle(script->infos(), isolate);
index_ = 0;
}
void SharedFunctionInfo::SetScript(IsolateForSandbox isolate,
ReadOnlyRoots roots,
Tagged<HeapObject> script_object,
int function_literal_id,
bool reset_preparsed_scope_data) {
DisallowGarbageCollection no_gc;
if (script() == script_object) return;
if (reset_preparsed_scope_data &&
HasUncompiledDataWithPreparseData(isolate)) {
ClearPreparseData(isolate);
}
// Add shared function info to new script's list. If a collection occurs,
// the shared function info may be temporarily in two lists.
// This is okay because the gc-time processing of these lists can tolerate
// duplicates.
if (IsScript(script_object)) {
DCHECK(!IsScript(script()));
Tagged<Script> script = Cast<Script>(script_object);
Tagged<WeakFixedArray> list = script->infos();
#ifdef DEBUG
DCHECK_LT(static_cast<uint32_t>(function_literal_id),
list->ulength().value());
Tagged<MaybeObject> maybe_object = list->get(function_literal_id);
Tagged<HeapObject> heap_object;
if (maybe_object.GetHeapObjectIfWeak(&heap_object)) {
DCHECK_EQ(heap_object, this);
}
#endif
// Release-store: this SFI was just allocated and initialised; the infos()
// array is read concurrently by
// BackgroundMergeTask::BeginMergeInBackground.
list->set(function_literal_id, MakeWeak(Tagged<SharedFunctionInfo>(this)),
kReleaseStore);
} else {
DCHECK(IsScript(script()));
// Remove shared function info from old script's list.
Tagged<Script> old_script = Cast<Script>(script());
Tagged<WeakFixedArray> list = old_script->infos();
#ifdef DEBUG
DCHECK_LT(static_cast<uint32_t>(function_literal_id),
list->ulength().value());
Tagged<MaybeObject> maybe_object = list->get(function_literal_id);
Tagged<HeapObject> heap_object;
if (maybe_object.GetHeapObjectIfWeak(&heap_object)) {
DCHECK_EQ(heap_object, this);
}
#endif
list->set(function_literal_id, roots.undefined_value());
}
// Finally set new script.
set_script(script_object, kReleaseStore);
}
void SharedFunctionInfo::CopyFrom(Tagged<SharedFunctionInfo> other,
IsolateForSandbox isolate) {
set_name_or_scope_info(other->name_or_scope_info(kAcquireLoad),
kReleaseStore);
set_outer_scope_info_or_feedback_metadata(
other->outer_scope_info_or_feedback_metadata());
set_script(other->script(kAcquireLoad), kReleaseStore);
set_length(other->length());
set_formal_parameter_count(other->formal_parameter_count());
set_function_token_offset(other->function_token_offset());
set_expected_nof_properties(other->expected_nof_properties());
set_flags2(other->flags2());
set_flags(other->flags(kRelaxedLoad), kRelaxedStore);
set_function_literal_id(other->function_literal_id(kRelaxedLoad),
kRelaxedStore);
set_unique_id(other->unique_id());
set_age(0);
// Install code last to ensure that the entire SFI is properly initialized if
// it's compiled.
if (other->HasTrustedData()) {
SetTrustedData(
TrustedCast<ExposedTrustedObject>(other->GetTrustedData(isolate)));
} else {
SetUntrustedData(other->GetUntrustedData());
}
set_feedback_slot(other->feedback_slot());
#if DEBUG
// This should now be byte-for-byte identical to the input except for the age
// field (could be reset concurrently). Compare content before age field now:
DCHECK_EQ(memcmp(reinterpret_cast<void*>(address()),
reinterpret_cast<void*>(other.address()),
offsetof(SharedFunctionInfo, age_)),
0);
// Compare content after age field.
constexpr Address kPastAgeOffset =
offsetof(SharedFunctionInfo, age_) + SharedFunctionInfo::kAgeSize;
DCHECK_EQ(memcmp(reinterpret_cast<void*>(address() + kPastAgeOffset),
reinterpret_cast<void*>(other.address() + kPastAgeOffset),
SharedFunctionInfo::kSize - kPastAgeOffset),
0);
#endif
}
bool SharedFunctionInfo::HasDebugInfo(Isolate* isolate) const {
return isolate->debug()->HasDebugInfo(this);
}
// Needs to be kept in sync with Scope::UniqueIdInScript and
// ScopeInfo::UniqueIdInScript.
int SharedFunctionInfo::UniqueIdInScript() const {
// Script scopes start "before" the script to avoid clashing with a scope that
// starts on character 0.
if (function_literal_id(kRelaxedLoad) == kFunctionLiteralIdTopLevel) {
return -2;
}
// Wrapped functions start before the function body, but after the script
// start, to avoid clashing with a scope starting on character 0.
if (syntax_kind() == FunctionSyntaxKind::kWrapped) return -1;
// Default constructors have the same start position as their parent class
// scope. Use the next char position to distinguish this scope.
return StartPosition() + IsDefaultConstructor(kind());
}
Tagged<DebugInfo> SharedFunctionInfo::GetDebugInfo(Isolate* isolate) const {
return isolate->debug()->TryGetDebugInfo(this).value();
}
std::optional<Tagged<DebugInfo>> SharedFunctionInfo::TryGetDebugInfo(
Isolate* isolate) const {
return isolate->debug()->TryGetDebugInfo(this);
}
bool SharedFunctionInfo::HasBreakInfo(Isolate* isolate) const {
return isolate->debug()->HasBreakInfo(this);
}
bool SharedFunctionInfo::BreakAtEntry(Isolate* isolate) const {
return isolate->debug()->BreakAtEntry(this);
}
bool SharedFunctionInfo::HasCoverageInfo(Isolate* isolate) const {
return isolate->debug()->HasCoverageInfo(this);
}
Tagged<CoverageInfo> SharedFunctionInfo::GetCoverageInfo(
Isolate* isolate) const {
DCHECK(HasCoverageInfo(isolate));
return Cast<CoverageInfo>(GetDebugInfo(isolate)->coverage_info());
}
std::unique_ptr<char[]> SharedFunctionInfo::DebugNameCStr() const {
#if V8_ENABLE_WEBASSEMBLY
if (HasWasmExportedFunctionData(GetCurrentIsolateForSandbox())) {
return WasmExportedFunction::GetDebugName(
wasm_exported_function_data()->internal()->sig());
}
#endif // V8_ENABLE_WEBASSEMBLY
DisallowGarbageCollection no_gc;
Tagged<String> function_name = Name();
if (function_name->length() == 0) function_name = inferred_name();
return function_name->ToCString();
}
// static
Handle<String> SharedFunctionInfo::DebugName(
Isolate* isolate, DirectHandle<SharedFunctionInfo> shared,
AllowAllocation allow_allocation) {
#if V8_ENABLE_WEBASSEMBLY
if (shared->HasWasmExportedFunctionData(isolate) && allow_allocation) {
return isolate->factory()
->NewStringFromUtf8(base::CStrVector(shared->DebugNameCStr().get()))
.ToHandleChecked();
}
#endif // V8_ENABLE_WEBASSEMBLY
FunctionKind function_kind = shared->kind();
if (IsClassInitializerFunction(function_kind)) {
return IsClassInstanceInitializerFunction(function_kind)
? isolate->factory()->instance_members_initializer_string()
: isolate->factory()->static_initializer_string();
}
DisallowHeapAllocation no_gc;
Tagged<String> function_name = shared->Name();
if (function_name->length() == 0) function_name = shared->inferred_name();
return handle(function_name, isolate);
}
bool SharedFunctionInfo::PassesFilter(const char* raw_filter) {
// Filters are almost always "*", so check for that and exit quickly.
if (V8_LIKELY(raw_filter[0] == '*' && raw_filter[1] == '\0')) {
return true;
}
base::Vector<const char> filter = base::CStrVector(raw_filter);
return v8::internal::PassesFilter(base::CStrVector(DebugNameCStr().get()),
filter);
}
bool SharedFunctionInfo::HasSourceCode() const {
return !IsUndefined(script()) &&
!IsUndefined(Cast<Script>(script())->source()) &&
Cast<String>(Cast<Script>(script())->source())->length() > 0;
}
void SharedFunctionInfo::DiscardCompiledMetadata(
Isolate* isolate,
std::function<void(Tagged<HeapObject> object, ObjectSlot slot,
Tagged<HeapObject> target)>
gc_notify_updated_slot) {
DisallowGarbageCollection no_gc;
if (HasFeedbackMetadata()) {
if (v8_flags.trace_flush_code) {
CodeTracer::Scope scope(isolate->GetCodeTracer());
PrintF(scope.file(), "[discarding compiled metadata for ");
ShortPrint(this, scope.file());
PrintF(scope.file(), "]\n");
}
if (scope_info()->HasOuterScopeInfo()) {
Tagged<ScopeInfo> outer_scope_info = scope_info()->OuterScopeInfo();
// Raw setter to avoid validity checks, since we're performing the unusual
// task of decompiling.
set_raw_outer_scope_info_or_feedback_metadata(outer_scope_info);
gc_notify_updated_slot(
this,
RawField(offsetof(SharedFunctionInfo,
outer_scope_info_or_feedback_metadata_)),
outer_scope_info);
} else {
// Raw setter to avoid validity checks, since we're performing the unusual
// task of decompiling.
set_raw_outer_scope_info_or_feedback_metadata(
ReadOnlyRoots(isolate).the_hole_value());
}
} else {
DCHECK(IsTheHole(outer_scope_info()) || IsScopeInfo(outer_scope_info()));
}
// TODO(rmcilroy): Possibly discard ScopeInfo here as well.
}
// static
void SharedFunctionInfo::DiscardCompiled(
Isolate* isolate, DirectHandle<SharedFunctionInfo> shared_info) {
DCHECK(shared_info->CanDiscardCompiled());
Handle<String> inferred_name_val(shared_info->inferred_name(), isolate);
int start_position = shared_info->StartPosition();
int end_position = shared_info->EndPosition();
MaybeDirectHandle<UncompiledData> data;
if (!shared_info->HasUncompiledDataWithPreparseData(isolate)) {
// Create a new UncompiledData, without pre-parsed scope.
data = isolate->factory()->NewUncompiledDataWithoutPreparseData(
inferred_name_val, start_position, end_position);
}
// If the GC runs after changing one but not both fields below, it could see
// the SharedFunctionInfo in an unexpected state.
DisallowGarbageCollection no_gc;
shared_info->DiscardCompiledMetadata(isolate);
// Replace compiled data with a new UncompiledData object.
if (shared_info->HasUncompiledDataWithPreparseData(isolate)) {
// If this is uncompiled data with a pre-parsed scope data, we can just
// clear out the scope data and keep the uncompiled data.
shared_info->ClearPreparseData(isolate);
DCHECK(data.is_null());
} else {
// Update the function data to point to the UncompiledData without preparse
// data created above. Use the raw function data setter to avoid validity
// checks, since we're performing the unusual task of decompiling.
shared_info->SetTrustedData(*data.ToHandleChecked());
}
}
// static
DirectHandle<Object> SharedFunctionInfo::GetSourceCode(
Isolate* isolate, DirectHandle<SharedFunctionInfo> shared) {
if (!shared->HasSourceCode()) return isolate->factory()->undefined_value();
Handle<String> source(Cast<String>(Cast<Script>(shared->script())->source()),
isolate);
return isolate->factory()->NewSubString(source, shared->StartPosition(),
shared->EndPosition());
}
// static
Handle<Object> SharedFunctionInfo::GetSourceCodeHarmony(
Isolate* isolate, DirectHandle<SharedFunctionInfo> shared) {
if (!shared->HasSourceCode()) return isolate->factory()->undefined_value();
Handle<String> script_source(
Cast<String>(Cast<Script>(shared->script())->source()), isolate);
int start_pos = shared->function_token_position();
DCHECK_NE(start_pos, kNoSourcePosition);
Handle<String> source = isolate->factory()->NewSubString(
script_source, start_pos, shared->EndPosition());
if (!shared->is_wrapped()) return source;
DCHECK(!shared->name_should_print_as_anonymous());
IncrementalStringBuilder builder(isolate);
builder.AppendCStringLiteral("function ");
builder.AppendString(DirectHandle<String>(shared->Name(), isolate));
builder.AppendCharacter('(');
DirectHandle<FixedArray> args(
Cast<Script>(shared->script())->wrapped_arguments(), isolate);
const uint32_t argc = args->ulength().value();
for (uint32_t i = 0; i < argc; i++) {
if (i > 0) builder.AppendCStringLiteral(", ");
builder.AppendString(
DirectHandle<String>(Cast<String>(args->get(i)), isolate));
}
builder.AppendCStringLiteral(") {\n");
builder.AppendString(source);
builder.AppendCStringLiteral("\n}");
DirectHandle<String> result;
if (builder.Finish().To(&result)) {
return indirect_handle(result, isolate);
}
// This should be extremely rare (only when {source} is close to
// String::kMaxLength), but it is reachable. Finish() threw an
// invalid-string-length error; clear it, since we return a valid fallback
// string rather than propagating the exception.
DCHECK(isolate->has_exception());
isolate->clear_exception();
return isolate->factory()->NewStringFromAsciiChecked("<too long to print>");
}
int SharedFunctionInfo::SourceSize() { return EndPosition() - StartPosition(); }
// Output the source code without any allocation in the heap.
std::ostream& operator<<(std::ostream& os, const SourceCodeOf& v) {
const Tagged<SharedFunctionInfo> s = v.value;
// For some native functions there is no source.
if (!s->HasSourceCode()) return os << "<No Source>";
// Get the source for the script which this function came from.
// Don't use Cast<String> because we don't want more assertion errors while
// we are already creating a stack dump.
Tagged<String> script_source =
UncheckedCast<String>(Cast<Script>(s->script())->source());
if (!s->is_toplevel()) {
os << "function ";
Tagged<String> name = s->Name();
if (name->length() > 0) {
name->PrintUC16(os);
}
}
int len = s->EndPosition() - s->StartPosition();
if (len <= v.max_length || v.max_length < 0) {
script_source->PrintUC16(os, s->StartPosition(), s->EndPosition());
return os;
} else {
script_source->PrintUC16(os, s->StartPosition(),
s->StartPosition() + v.max_length);
return os << "...\n";
}
}
void SharedFunctionInfo::DisableOptimization(Isolate* isolate,
BailoutReason reason) {
DCHECK_NE(reason, BailoutReason::kNoReason);
set_flags(DisabledOptimizationReasonBits::update(flags(kRelaxedLoad), reason),
kRelaxedStore);
// Code should be the lazy compilation stub or else interpreted.
if constexpr (DEBUG_BOOL) {
CodeKind kind = abstract_code(isolate)->kind();
CHECK(kind == CodeKind::INTERPRETED_FUNCTION || kind == CodeKind::BUILTIN);
}
PROFILE(isolate,
CodeDisableOptEvent(
direct_handle(abstract_code(isolate), isolate),
direct_handle(Tagged<SharedFunctionInfo>(this), isolate)));
if (v8_flags.trace_opt) {
CodeTracer::Scope scope(isolate->GetCodeTracer());
PrintF(scope.file(), "[disabled optimization for ");
ShortPrint(this, scope.file());
PrintF(scope.file(), ", reason: %s]\n", GetBailoutReason(reason));
}
}
// static
template <typename IsolateT>
void SharedFunctionInfo::InitFromFunctionLiteral(IsolateT* isolate,
FunctionLiteral* lit,
bool is_toplevel) {
DCHECK(!IsScopeInfo(
lit->shared_function_info()->name_or_scope_info(kAcquireLoad)));
{
DisallowGarbageCollection no_gc;
Tagged<SharedFunctionInfo> raw_sfi = *lit->shared_function_info();
DCHECK_EQ(raw_sfi->function_literal_id(kRelaxedLoad),
lit->function_literal_id());
// When adding fields here, make sure DeclarationScope::AnalyzePartially is
// updated accordingly.
raw_sfi->set_internal_formal_parameter_count(
JSParameterCount(lit->parameter_count()));
raw_sfi->SetFunctionTokenPosition(lit->function_token_position(),
lit->start_position());
raw_sfi->set_syntax_kind(lit->syntax_kind());
raw_sfi->set_allows_lazy_compilation(lit->AllowsLazyCompilation());
raw_sfi->set_language_mode(lit->language_mode());
// FunctionKind must have already been set.
DCHECK(lit->kind() == raw_sfi->kind());
DCHECK_IMPLIES(lit->requires_instance_members_initializer(),
IsClassConstructor(lit->kind()));
raw_sfi->set_requires_instance_members_initializer(
lit->requires_instance_members_initializer());
DCHECK_IMPLIES(lit->class_scope_has_private_brand(),
IsClassConstructor(lit->kind()));
raw_sfi->set_class_scope_has_private_brand(
lit->class_scope_has_private_brand());
DCHECK_IMPLIES(lit->has_static_private_methods_or_accessors(),
IsClassConstructor(lit->kind()));
raw_sfi->set_has_static_private_methods_or_accessors(
lit->has_static_private_methods_or_accessors());
raw_sfi->set_is_hoisted_in_context(lit->scope()->is_hoisted_in_context());
raw_sfi->set_is_toplevel(is_toplevel);
DCHECK(IsTheHole(raw_sfi->outer_scope_info()));
Scope* outer_scope = lit->scope()->GetOuterScopeWithContext();
if (outer_scope) {
raw_sfi->set_outer_scope_info(*outer_scope->scope_info());
raw_sfi->set_private_name_lookup_skips_outer_class(
lit->scope()->private_name_lookup_skips_outer_class());
}
if (lit->scope()->from_scope_info()) {
raw_sfi->SetScopeInfo(*lit->scope()->scope_info());
}
raw_sfi->set_length(lit->function_length());
// For lazy parsed functions, the following flags will be inaccurate since
// we don't have the information yet. They're set later in
// UpdateSharedFunctionFlagsAfterCompilation (compiler.cc), when the
// function is really parsed and compiled.
if (lit->ShouldEagerCompile()) {
raw_sfi->set_has_duplicate_parameters(lit->has_duplicate_parameters());
raw_sfi->UpdateAndFinalizeExpectedNofPropertiesFromEstimate(lit);
DCHECK_NULL(lit->produced_preparse_data());
// If we're about to eager compile, we'll have the function literal
// available, so there's no need to wastefully allocate an uncompiled
// data.
return;
}
raw_sfi->UpdateExpectedNofPropertiesFromEstimate(lit);
}
CreateAndSetUncompiledData(isolate, lit);
}
template <typename IsolateT>
void SharedFunctionInfo::CreateAndSetUncompiledData(IsolateT* isolate,
FunctionLiteral* lit) {
DCHECK(!lit->shared_function_info()->HasUncompiledData(isolate));
DirectHandle<UncompiledData> data;
ProducedPreparseData* scope_data = lit->produced_preparse_data();
if (scope_data != nullptr) {
Handle<PreparseData> preparse_data = scope_data->Serialize(isolate);
if (lit->should_parallel_compile()) {
data = isolate->factory()->NewUncompiledDataWithPreparseDataAndJob(
lit->GetInferredName(isolate), lit->start_position(),
lit->end_position(), preparse_data);
} else {
data = isolate->factory()->NewUncompiledDataWithPreparseData(
lit->GetInferredName(isolate), lit->start_position(),
lit->end_position(), preparse_data);
}
} else {
if (lit->should_parallel_compile()) {
data = isolate->factory()->NewUncompiledDataWithoutPreparseDataWithJob(
lit->GetInferredName(isolate), lit->start_position(),
lit->end_position());
} else {
data = isolate->factory()->NewUncompiledDataWithoutPreparseData(
lit->GetInferredName(isolate), lit->start_position(),
lit->end_position());
}
}
lit->shared_function_info()->set_uncompiled_data(*data);
}
template EXPORT_TEMPLATE_DEFINE(V8_EXPORT_PRIVATE) void SharedFunctionInfo::
InitFromFunctionLiteral<Isolate>(Isolate* isolate,
FunctionLiteral* lit, bool is_toplevel);
template EXPORT_TEMPLATE_DEFINE(V8_EXPORT_PRIVATE) void SharedFunctionInfo::
InitFromFunctionLiteral<LocalIsolate>(LocalIsolate* isolate,
FunctionLiteral* lit,
bool is_toplevel);
template EXPORT_TEMPLATE_DEFINE(V8_EXPORT_PRIVATE) void SharedFunctionInfo::
CreateAndSetUncompiledData<Isolate>(Isolate* isolate, FunctionLiteral* lit);
template EXPORT_TEMPLATE_DEFINE(V8_EXPORT_PRIVATE) void SharedFunctionInfo::
CreateAndSetUncompiledData<LocalIsolate>(LocalIsolate* isolate,
FunctionLiteral* lit);
uint16_t SharedFunctionInfo::get_property_estimate_from_literal(
FunctionLiteral* literal) {
int estimate = literal->expected_property_count();
// If this is a class constructor, we may have already parsed fields.
if (is_class_constructor()) {
estimate += expected_nof_properties();
}
return estimate;
}
void SharedFunctionInfo::UpdateExpectedNofPropertiesFromEstimate(
FunctionLiteral* literal) {
// Limit actual estimate to fit in a 8 bit field, we will never allocate
// more than this in any case.
static_assert(JSObject::kMaxInObjectProperties <= kMaxUInt8);
int estimate = get_property_estimate_from_literal(literal);
set_expected_nof_properties(std::min(estimate, kMaxUInt8));
}
void SharedFunctionInfo::UpdateAndFinalizeExpectedNofPropertiesFromEstimate(
FunctionLiteral* literal) {
DCHECK(literal->ShouldEagerCompile());
if (are_properties_final()) {
return;
}
int estimate = get_property_estimate_from_literal(literal);
// If no properties are added in the constructor, they are more likely
// to be added later.
if (estimate == 0) estimate = 2;
// Limit actual estimate to fit in a 8 bit field, we will never allocate
// more than this in any case.
static_assert(JSObject::kMaxInObjectProperties <= kMaxUInt8);
estimate = std::min(estimate, kMaxUInt8);
set_expected_nof_properties(estimate);
set_are_properties_final(true);
}
void SharedFunctionInfo::SetFunctionTokenPosition(int function_token_position,
int start_position) {
int offset;
if (function_token_position == kNoSourcePosition) {
offset = 0;
} else {
offset = start_position - function_token_position;
}
if (offset > kMaximumFunctionTokenOffset) {
offset = kFunctionTokenOutOfRange;
}
set_raw_function_token_offset(offset);
}
int SharedFunctionInfo::StartPosition() const {
if (Tagged<ScopeInfo> info;
TryCast(name_or_scope_info(kAcquireLoad), &info)) {
return info->StartPosition();
}
IsolateForSandbox isolate = GetCurrentIsolateForSandbox();
if (HasUncompiledData(isolate)) {
// Works with or without scope.
return uncompiled_data(isolate)->start_position();
}
if (IsApiFunction()) {
return 0;
}
if (HasBuiltinId()) {
// A newly allocated SharedFunctionInfo without UncompiledData attached
// holds the builtin ID kCompileLazy. Since the source positions are not
// known at this point, we return kNoSourcePosition. This allows the heap
// snapshot generator to safely iterate over such an SFI in this transient
// state (e.g. when a GC is triggered during its creation).
if (builtin_id() == Builtin::kCompileLazy) {
return kNoSourcePosition;
}
return 0;
}
#if V8_ENABLE_WEBASSEMBLY
if (HasWasmExportedFunctionData(isolate)) {
Tagged<WasmTrustedInstanceData> instance_data =
wasm_exported_function_data()->instance_data();
int func_index = wasm_exported_function_data()->function_index();
auto& function = instance_data->module()->functions[func_index];
return static_cast<int>(function.code.offset());
}
#endif // V8_ENABLE_WEBASSEMBLY
return kNoSourcePosition;
}
int SharedFunctionInfo::EndPosition() const {
if (Tagged<ScopeInfo> info;
TryCast(name_or_scope_info(kAcquireLoad), &info)) {
return info->EndPosition();
}
IsolateForSandbox isolate = GetCurrentIsolateForSandbox();
if (HasUncompiledData(isolate)) {
// Works with or without scope.
return uncompiled_data(isolate)->end_position();
}
if (IsApiFunction()) {
return 0;
}
if (HasBuiltinId()) {
// A newly allocated SharedFunctionInfo without UncompiledData attached
// holds the builtin ID kCompileLazy. Since the source positions are not
// known at this point, we return kNoSourcePosition. This allows the heap
// snapshot generator to safely iterate over such an SFI in this transient
// state (e.g. when a GC is triggered during its creation).
if (builtin_id() == Builtin::kCompileLazy) {
return kNoSourcePosition;
}
return 0;
}
#if V8_ENABLE_WEBASSEMBLY
if (HasWasmExportedFunctionData(isolate)) {
Tagged<WasmTrustedInstanceData> instance_data =
wasm_exported_function_data()->instance_data();
int func_index = wasm_exported_function_data()->function_index();
auto& function = instance_data->module()->functions[func_index];
return static_cast<int>(function.code.end_offset());
}
#endif // V8_ENABLE_WEBASSEMBLY
return kNoSourcePosition;
}
CachedTieringDecision SharedFunctionInfo::cached_tiering_decision() {
return CachedTieringDecisionBits::decode(flags2());
}
void SharedFunctionInfo::set_cached_tiering_decision(
CachedTieringDecision decision) {
set_flags2(CachedTieringDecisionBits::update(flags2(), decision));
}
// static
void SharedFunctionInfo::EnsureBytecodeArrayAvailable(
Isolate* isolate, Handle<SharedFunctionInfo> shared_info,
IsCompiledScope* is_compiled_scope, CreateSourcePositions flag) {
if (!shared_info->HasBytecodeArray()) {
if (!Compiler::Compile(isolate, shared_info, Compiler::CLEAR_EXCEPTION,
is_compiled_scope, flag)) {
FATAL("Failed to compile shared info that was already compiled before");
}
DCHECK(shared_info->GetBytecodeArray(isolate)->HasSourcePositionTable());
} else {
*is_compiled_scope = shared_info->is_compiled_scope(isolate);
}
}
// static
void SharedFunctionInfo::EnsureSourcePositionsAvailable(
Isolate* isolate, DirectHandle<SharedFunctionInfo> shared_info) {
if (shared_info->CanCollectSourcePosition(isolate)) {
std::optional<Isolate::ExceptionScope> exception_scope;
if (isolate->has_exception()) {
exception_scope.emplace(isolate);
}
Compiler::CollectSourcePositions(isolate, shared_info);
}
}
// static
void SharedFunctionInfo::InstallDebugBytecode(
DirectHandle<SharedFunctionInfo> shared, Isolate* isolate) {
DCHECK(shared->HasBytecodeArray());
DirectHandle<BytecodeArray> original_bytecode_array(
shared->GetBytecodeArray(isolate), isolate);
DirectHandle<BytecodeArray> debug_bytecode_array =
isolate->factory()->CopyBytecodeArray(original_bytecode_array);
{
DisallowGarbageCollection no_gc;
base::MutexGuard guard(isolate->shared_function_info_access());
Tagged<DebugInfo> debug_info = shared->GetDebugInfo(isolate);
debug_info->set_original_bytecode_array(*original_bytecode_array,
kReleaseStore);
debug_info->set_debug_bytecode_array(*debug_bytecode_array, kReleaseStore);
shared->SetActiveBytecodeArray(*debug_bytecode_array, isolate);
}
}
// static
void SharedFunctionInfo::UninstallDebugBytecode(
Tagged<SharedFunctionInfo> shared, Isolate* isolate) {
DisallowGarbageCollection no_gc;
base::MutexGuard guard(isolate->shared_function_info_access());
Tagged<DebugInfo> debug_info = shared->GetDebugInfo(isolate);
Tagged<BytecodeArray> original_bytecode_array =
debug_info->OriginalBytecodeArray(isolate);
DCHECK(!shared->HasBaselineCode());
shared->SetActiveBytecodeArray(original_bytecode_array, isolate);
debug_info->clear_original_bytecode_array();
debug_info->clear_debug_bytecode_array();
}
// static
void SharedFunctionInfo::EnsureOldForTesting(Tagged<SharedFunctionInfo> sfi) {
if (v8_flags.flush_code_based_on_time ||
v8_flags.flush_code_based_on_tab_visibility) {
sfi->set_age(kMaxAge);
} else {
sfi->set_age(v8_flags.bytecode_old_age);
}
}
#ifdef DEBUG
// static
bool SharedFunctionInfo::UniqueIdsAreUnique(Isolate* isolate) {
std::unordered_set<uint32_t> ids({isolate->next_unique_sfi_id()});
CombinedHeapObjectIterator it(isolate->heap());
for (Tagged<HeapObject> o = it.Next(); !o.is_null(); o = it.Next()) {
if (IsInaccessible(o)) continue;
if (!IsSharedFunctionInfo(o)) continue;
auto result = ids.emplace(Cast<SharedFunctionInfo>(o)->unique_id());
// If previously inserted...
if (!result.second) return false;
}
return true;
}
#endif // DEBUG
std::ostream& operator<<(std::ostream& os,
SharedFunctionInfo::Inlineability i) {
switch (i) {
case SharedFunctionInfo::Inlineability::kHasNoScript:
os << "has no script";
break;
case SharedFunctionInfo::Inlineability::kNeedsBinaryCoverage:
os << "needs binary coverage";
break;
case SharedFunctionInfo::Inlineability::kIsBuiltin:
os << "is builtin";
break;
case SharedFunctionInfo::Inlineability::kIsNotUserCode:
os << "is not user code";
break;
case SharedFunctionInfo::Inlineability::kHasNoBytecode:
os << "has no bytecode";
break;
case SharedFunctionInfo::Inlineability::kExceedsBytecodeLimit:
os << "exceeds bytecode limit";
break;
case SharedFunctionInfo::Inlineability::kMayContainBreakPoints:
os << "may contain breakpoints";
break;
case SharedFunctionInfo::Inlineability::kHasOptimizationDisabled:
os << "has optimization disabled";
break;
case SharedFunctionInfo::Inlineability::kIsInlineable:
os << "is inlineable (!)";
break;
}
return os;
}
} // namespace v8::internal