current commit
261015.17 · deps: update V8 to 15.6.75.13
// Copyright 2011 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/scope-info.h"

#include <stdlib.h>

#include <optional>
#include <set>

#include "src/ast/scopes.h"
#include "src/ast/variables.h"
#include "src/init/bootstrapper.h"
#include "src/objects/module-inl.h"
#include "src/objects/objects-inl.h"
#include "src/objects/scope-info-inl.h"
#include "src/objects/string-set-inl.h"
#include "src/roots/roots.h"

// Has to be the last include (doesn't have include guards):
#include "src/objects/object-macros.h"

namespace v8 {
namespace internal {

namespace {
bool NameToIndexHashTableEquals(Tagged<NameToIndexHashTable> a,
                                Tagged<NameToIndexHashTable> b) {
  if (a->Capacity() != b->Capacity()) return false;
  if (a->NumberOfElements() != b->NumberOfElements()) return false;

  InternalIndex max(a->Capacity());
  InternalIndex entry(0);

  while (entry < max) {
    Tagged<Object> key_a = a->KeyAt(entry);
    Tagged<Object> key_b = b->KeyAt(entry);
    if (key_a != key_b) return false;

    Tagged<Object> value_a = a->ValueAt(entry);
    Tagged<Object> value_b = b->ValueAt(entry);
    if (value_a != value_b) return false;
  }
  return true;
}
}  // namespace

// TODO(crbug.com/401059828): make it DEBUG only, once investigation is over.
bool ScopeInfo::Equals(Tagged<ScopeInfo> other,
                       int* out_last_checked_field) const {
  if (length() != other->length()) return false;
  // Fixed header fields.
  if (Flags() != other->Flags()) return false;
  if (out_last_checked_field) *out_last_checked_field = kFlags;
  if (parameter_count() != other->parameter_count()) return false;
  if (out_last_checked_field) *out_last_checked_field = kParameterCount;
  if (context_local_count() != other->context_local_count()) return false;
  if (out_last_checked_field) *out_last_checked_field = kContextLocalCount;
  if (position_info_start() != other->position_info_start()) return false;
  if (out_last_checked_field) *out_last_checked_field = kPositionInfoStart;
  if (position_info_end() != other->position_info_end()) return false;
  if (out_last_checked_field) *out_last_checked_field = kPositionInfoEnd;
  // Variable-part tail.
  for (int index = 0; index < length(); ++index) {
    if (out_last_checked_field) {
      *out_last_checked_field = kVariablePartStart + index;
    }
    Tagged<Object> entry = get(index);
    Tagged<Object> other_entry = other->get(index);
    if (IsSmi(entry)) {
      if (entry != other_entry) return false;
    } else {
      if (Cast<HeapObject>(entry)->map()->instance_type() !=
          Cast<HeapObject>(other_entry)->map()->instance_type()) {
        return false;
      }
      if (IsString(entry)) {
        if (!Cast<String>(entry)->Equals(Cast<String>(other_entry))) {
          return false;
        }
      } else if (IsScopeInfo(entry)) {
        if (!Cast<ScopeInfo>(entry)->Equals(Cast<ScopeInfo>(other_entry))) {
          return false;
        }
      } else if (IsSourceTextModuleInfo(entry)) {
        if (!Cast<SourceTextModuleInfo>(entry)->Equals(
                Cast<SourceTextModuleInfo>(other_entry))) {
          return false;
        }
      } else if (IsOddball(entry)) {
        if (Cast<Oddball>(entry)->kind() !=
            Cast<Oddball>(other_entry)->kind()) {
          return false;
        }
      } else if (IsDependentCode(entry)) {
        DCHECK(IsDependentCode(other_entry));
      } else if (IsNameToIndexHashTable(entry)) {
        if (!NameToIndexHashTableEquals(
                Cast<NameToIndexHashTable>(entry),
                Cast<NameToIndexHashTable>(other_entry))) {
          return false;
        }
      } else {
        UNREACHABLE();
      }
    }
  }
  return true;
}

// static
template <typename IsolateT>
Handle<ScopeInfo> ScopeInfo::Create(IsolateT* isolate, Zone* zone, Scope* scope,
                                    MaybeDirectHandle<ScopeInfo> outer_scope) {
  // Collect variables.
  int context_local_count = 0;
  int module_vars_count = 0;
  // Stack allocated block scope variables are allocated in the parent
  // declaration scope, but are recorded in the block scope's scope info. First
  // slot index indicates at which offset a particular scope starts in the
  // parent declaration scope.
  for (Variable* var : *scope->locals()) {
    switch (var->location()) {
      case VariableLocation::CONTEXT:
      case VariableLocation::REPL_GLOBAL:
        context_local_count++;
        break;
      case VariableLocation::MODULE:
        module_vars_count++;
        break;
      default:
        break;
    }
  }
  // Determine use and location of the "this" binding if it is present.
  VariableAllocationInfo receiver_info;
  if (scope->is_declaration_scope() &&
      scope->AsDeclarationScope()->has_this_declaration()) {
    Variable* var = scope->AsDeclarationScope()->receiver();
    if (!var->is_used()) {
      receiver_info = VariableAllocationInfo::UNUSED;
    } else if (var->IsContextSlot()) {
      receiver_info = VariableAllocationInfo::CONTEXT;
    } else {
      DCHECK(var->IsParameter());
      receiver_info = VariableAllocationInfo::STACK;
    }
  } else {
    receiver_info = VariableAllocationInfo::NONE;
  }

  DCHECK(module_vars_count == 0 || scope->is_module_scope());

  // Make sure we allocate the correct amount.
  DCHECK_EQ(scope->ContextLocalCount(), context_local_count);

  // If the number of locals is small, we inline directly
  // in the scope info object.
  bool has_inlined_local_names =
      context_local_count < kScopeInfoMaxInlinedLocalNamesSize;

  const bool allocates_arguments =
      scope->is_function_scope() &&
      scope->AsDeclarationScope()->arguments() != nullptr;
  // TODO(cbruni): Don't always waste a field for the inferred name.
  const bool has_inferred_function_name = scope->is_function_scope();

  // Determine use and location of the function variable if it is present.
  VariableAllocationInfo function_name_info;
  if (scope->is_function_scope()) {
    if (scope->AsDeclarationScope()->function_var() != nullptr) {
      Variable* var = scope->AsDeclarationScope()->function_var();
      if (!var->is_used()) {
        function_name_info = VariableAllocationInfo::UNUSED;
      } else if (var->IsContextSlot()) {
        function_name_info = VariableAllocationInfo::CONTEXT;
      } else {
        DCHECK(var->IsStackLocal());
        function_name_info = VariableAllocationInfo::STACK;
      }
    } else {
      // Always reserve space for the debug name in the scope info.
      function_name_info = VariableAllocationInfo::UNUSED;
    }
  } else if (scope->is_module_scope() || scope->is_script_scope() ||
             scope->is_eval_scope()) {
    // Always reserve space for the debug name in the scope info.
    function_name_info = VariableAllocationInfo::UNUSED;
  } else {
    function_name_info = VariableAllocationInfo::NONE;
  }

  const bool has_brand =
      scope->is_class_scope()
          ? scope->AsClassScope()->brand() != nullptr
          : scope->IsConstructorScope() &&
                scope->AsDeclarationScope()->class_scope_has_private_brand();
  const bool should_save_class_variable =
      scope->is_class_scope()
          ? scope->AsClassScope()->should_save_class_variable()
          : false;
  const bool has_function_name =
      function_name_info != VariableAllocationInfo::NONE;
  const int parameter_count =
      scope->is_declaration_scope()
          ? scope->AsDeclarationScope()->num_parameters()
          : 0;
  const bool has_outer_scope_info = !outer_scope.is_null();

  DirectHandle<SourceTextModuleInfo> module_info;
  if (scope->is_module_scope()) {
    module_info = SourceTextModuleInfo::New(isolate, zone,
                                            scope->AsModuleScope()->module());
  }

  FunctionKind function_kind = FunctionKind::kNormalFunction;
  bool sloppy_eval_can_extend_vars = false;
  if (scope->is_declaration_scope()) {
    function_kind = scope->AsDeclarationScope()->function_kind();
    sloppy_eval_can_extend_vars =
        scope->AsDeclarationScope()->sloppy_eval_can_extend_vars();
  }
  DCHECK_IMPLIES(sloppy_eval_can_extend_vars, scope->HasContextExtensionSlot());

  const int local_names_container_size =
      has_inlined_local_names ? context_local_count : 1;

  const int has_dependent_code = sloppy_eval_can_extend_vars;
  // Module scopes with many module variables also carry a name -> entry
  // hash table so ModuleIndex() need not scan the entries linearly (same
  // threshold as the context-local names hashtable).
  const bool has_module_vars_hashtable =
      scope->is_module_scope() &&
      module_vars_count >= kScopeInfoMaxInlinedLocalNamesSize;
  const int length =
      local_names_container_size + context_local_count +
      (should_save_class_variable ? 1 : 0) +
      (has_function_name ? kFunctionNameEntries : 0) +
      (has_inferred_function_name ? 1 : 0) + (has_outer_scope_info ? 1 : 0) +
      (scope->is_module_scope()
           ? 2 + kModuleVariableEntryLength * module_vars_count
           : 0) +
      (has_module_vars_hashtable ? 1 : 0) + (has_dependent_code ? 1 : 0) +
      (scope->is_function_scope() ? 1 : 0);

  // Create hash table if local names are not inlined.
  Handle<NameToIndexHashTable> local_names_hashtable;
  if (!has_inlined_local_names) {
    local_names_hashtable = NameToIndexHashTable::New(
        isolate, context_local_count, AllocationType::kOld);
  }
  Handle<NameToIndexHashTable> module_vars_hashtable;
  if (has_module_vars_hashtable) {
    module_vars_hashtable = NameToIndexHashTable::New(
        isolate, module_vars_count, AllocationType::kOld);
  }

  Handle<ScopeInfo> scope_info_handle =
      isolate->factory()->NewScopeInfo(length);
  int index = 0;
  {
    DisallowGarbageCollection no_gc;
    Tagged<ScopeInfo> scope_info = *scope_info_handle;
    WriteBarrierModeScope mode = scope_info->GetWriteBarrierMode(no_gc);

    bool has_simple_parameters = false;
    if (scope->is_function_scope()) {
      DeclarationScope* function_scope = scope->AsDeclarationScope();
      has_simple_parameters = function_scope->has_simple_parameters();
    }

    // Encode the flags.
    uint32_t flags =
        ScopeTypeBits::encode(scope->scope_type()) |
        SloppyEvalCanExtendVarsBit::encode(sloppy_eval_can_extend_vars) |
        LanguageModeBit::encode(scope->language_mode()) |
        DeclarationScopeBit::encode(scope->is_declaration_scope()) |
        ReceiverVariableBits::encode(receiver_info) |
        ClassScopeHasPrivateBrandBit::encode(has_brand) |
        HasSavedClassVariableBit::encode(should_save_class_variable) |
        AllocatesArgumentsBit::encode(allocates_arguments) |
        FunctionVariableBits::encode(function_name_info) |
        HasInferredFunctionNameBit::encode(has_inferred_function_name) |
        HasSimpleParametersBit::encode(has_simple_parameters) |
        FunctionKindBits::encode(function_kind) |
        HasOuterScopeInfoBit::encode(has_outer_scope_info) |
        IsDebugEvaluateScopeBit::encode(false) |
        ForceContextAllocationBit::encode(
            scope->ForceContextForLanguageMode()) |
        PrivateNameLookupSkipsOuterClassBit::encode(
            scope->private_name_lookup_skips_outer_class()) |
        HasContextExtensionSlotBit::encode(scope->HasContextExtensionSlot()) |
        IsHiddenBit::encode(scope->is_hidden()) |
        IsWrappedFunctionBit::encode(scope->is_wrapped_function()) |
        HasContextCellsBit::encode(scope->has_context_cells()) |
        IsHoistedInContextBit::encode(scope->is_hoisted_in_context());
    scope_info->set_flags(flags, kRelaxedStore);

    scope_info->set_parameter_count(parameter_count);
    scope_info->set_context_local_count(context_local_count);

    scope_info->set_position_info_start(scope->start_position());
    scope_info->set_position_info_end(scope->end_position());

    if (scope->is_module_scope()) {
      scope_info->set_module_variable_count(module_vars_count);
      ++index;
    }
    if (!has_inlined_local_names) {
      scope_info->set_context_local_names_hashtable(*local_names_hashtable);
    }

    // Add context locals' names and info, module variables' names and info.
    // Context locals are added using their index.
    int context_local_base = index;
    int context_local_info_base =
        context_local_base + local_names_container_size;
    int module_var_entry = scope_info->ModuleVariablesIndex();
    int module_var_number = 0;

    for (Variable* var : *scope->locals()) {
      switch (var->location()) {
        case VariableLocation::CONTEXT:
        case VariableLocation::REPL_GLOBAL: {
          // Due to duplicate parameters, context locals aren't guaranteed to
          // come in order.
          int local_index = var->index() - scope->ContextHeaderLength();
          DCHECK_LE(0, local_index);
          DCHECK_LT(local_index, context_local_count);
          uint32_t info = VariableModeBits::encode(var->mode()) |
                          InitFlagBit::encode(var->initialization_flag()) |
                          MaybeAssignedFlagBit::encode(var->maybe_assigned()) |
                          IsParameterBit::encode(false) |
                          IsStaticFlagBit::encode(var->is_static_flag());
          int position = var->initializer_position();
          if (position != kNoSourcePosition) {
            int relative_position =
                std::max(0, position - scope->start_position());
            if (relative_position >=
                static_cast<int>(ParameterNumberOrPositionBits::kMax)) {
              relative_position = ParameterNumberOrPositionBits::kMax;
            }
            info =
                ParameterNumberOrPositionBits::update(info, relative_position);
          } else {
            info = ParameterNumberOrPositionBits::update(
                info, ParameterNumberOrPositionBits::kMax);
          }
          if (has_inlined_local_names) {
            scope_info->set(context_local_base + local_index, *var->name(),
                            *mode);
          } else {
            Handle<NameToIndexHashTable> new_table = NameToIndexHashTable::Add(
                isolate, local_names_hashtable, var->name(), local_index);
            DCHECK_EQ(*new_table, *local_names_hashtable);
            USE(new_table);
          }
          scope_info->set(context_local_info_base + local_index,
                          Smi::FromInt(info));
          break;
        }
        case VariableLocation::MODULE: {
          scope_info->set(module_var_entry +
                              offsetof(ModuleVariableInfo, name) / kTaggedSize,
                          *var->name(), *mode);
          scope_info->set(module_var_entry +
                              offsetof(ModuleVariableInfo, index) / kTaggedSize,
                          Smi::FromInt(var->index()));
          uint32_t properties =
              VariableModeBits::encode(var->mode()) |
              InitFlagBit::encode(var->initialization_flag()) |
              MaybeAssignedFlagBit::encode(var->maybe_assigned()) |
              IsParameterBit::encode(false) |
              IsStaticFlagBit::encode(var->is_static_flag());
          int position = var->initializer_position();
          if (position != kNoSourcePosition) {
            int relative_position =
                std::max(0, position - scope->start_position());
            if (relative_position >=
                static_cast<int>(ParameterNumberOrPositionBits::kMax)) {
              relative_position = ParameterNumberOrPositionBits::kMax;
            }
            properties = ParameterNumberOrPositionBits::update(
                properties, relative_position);
          } else {
            properties = ParameterNumberOrPositionBits::update(
                properties, ParameterNumberOrPositionBits::kMax);
          }
          scope_info->set(
              module_var_entry +
                  offsetof(ModuleVariableInfo, properties) / kTaggedSize,
              Smi::FromInt(properties));
          if (has_module_vars_hashtable) {
            // The table was sized for module_vars_count entries, so Add()
            // never reallocates (and thus never allocates under no_gc).
            Handle<NameToIndexHashTable> new_table = NameToIndexHashTable::Add(
                isolate, module_vars_hashtable, var->name(), module_var_number);
            DCHECK_EQ(*new_table, *module_vars_hashtable);
            USE(new_table);
          }
          module_var_entry += kModuleVariableEntryLength;
          ++module_var_number;
          break;
        }
        default:
          break;
      }
    }

    if (scope->is_declaration_scope()) {
      // Mark contexts slots with the parameter number they represent. We walk
      // the list of parameters. That can include duplicate entries if a
      // parameter name is repeated. By walking upwards, we'll automatically
      // mark the context slot with the highest parameter number that uses this
      // variable. That will be the parameter number that is represented by the
      // context slot. All lower parameters will only be available on the stack
      // through the arguments object.
      for (int i = 0; i < parameter_count; i++) {
        Variable* parameter = scope->AsDeclarationScope()->parameter(i);
        if (parameter->location() != VariableLocation::CONTEXT) continue;
        int param_index = parameter->index() - scope->ContextHeaderLength();
        int info_index = context_local_info_base + param_index;
        int info = Smi::ToInt(scope_info->get(info_index));
        info = IsParameterBit::update(info, true);
        info = ParameterNumberOrPositionBits::update(info, i);
        scope_info->set(info_index, Smi::FromInt(info));
      }
    }

    // Advance past local names and local names info.
    index += local_names_container_size + context_local_count;

    DCHECK_EQ(index, scope_info->SavedClassVariableInfoIndex());
    // If the scope is a class scope and has used static private methods,
    // save context slot index if locals are inlined, otherwise save the name.
    if (should_save_class_variable) {
      Variable* class_variable = scope->AsClassScope()->class_variable();
      DCHECK_EQ(class_variable->location(), VariableLocation::CONTEXT);
      if (has_inlined_local_names) {
        scope_info->set(index++, Smi::FromInt(class_variable->index()));
      } else {
        scope_info->set(index++, *class_variable->name());
      }
    }

    // If present, add the function variable name and its index.
    DCHECK_EQ(index, scope_info->FunctionVariableInfoIndex());
    if (has_function_name) {
      Variable* var = scope->AsDeclarationScope()->function_var();
      int var_index = -1;
      Tagged<Object> name = Smi::zero();
      if (var != nullptr) {
        var_index = var->index();
        name = *var->name();
      }
      scope_info->set(index++, name, *mode);
      scope_info->set(index++, Smi::FromInt(var_index));
      DCHECK(function_name_info != VariableAllocationInfo::CONTEXT ||
             var_index == scope_info->ContextLength() - 1);
    }

    DCHECK_EQ(index, scope_info->InferredFunctionNameIndex());
    if (has_inferred_function_name) {
      // The inferred function name is taken from the SFI.
      index++;
    }

    // If present, add the outer scope info.
    DCHECK_EQ(index, scope_info->OuterScopeInfoIndex());
    if (has_outer_scope_info) {
      scope_info->set(index++, *outer_scope.ToHandleChecked(), *mode);
    }

    // Module-specific information (only for module scopes).
    if (scope->is_module_scope()) {
      DCHECK_EQ(index, scope_info->ModuleInfoIndex());
      scope_info->set(index++, *module_info);
      DCHECK_EQ(index, scope_info->ModuleVariablesIndex());
      // The variable entries themselves have already been written above.
      index += kModuleVariableEntryLength * module_vars_count;
      DCHECK_EQ(index, scope_info->ModuleVariablesHashtableIndex());
      DCHECK_EQ(has_module_vars_hashtable,
                scope_info->HasModuleVariablesHashtable());
      if (has_module_vars_hashtable) {
        DCHECK_EQ(module_var_number, module_vars_count);
        scope_info->set(index++, *module_vars_hashtable);
      }
    }

    DCHECK_EQ(index, scope_info->DependentCodeIndex());
    if (has_dependent_code) {
      ReadOnlyRoots roots(isolate);
      scope_info->set(index++, DependentCode::empty_dependent_code(roots));
    }
    DCHECK_EQ(index, scope_info->UnusedParameterBitsIndex());
    if (scope->is_function_scope()) {
      uint32_t unused_parameter_bits = 0;
      auto func_scope = scope->AsDeclarationScope();
      int count = std::min(31, func_scope->num_parameters());
      for (int i = 0; i < count; ++i) {
        bool unused = !func_scope->parameter(i)->is_used();
        unused_parameter_bits |= static_cast<uint32_t>(unused) << i;
      }
      scope_info->set(index++, Smi::From31BitPattern(unused_parameter_bits));
    }
  }

  DCHECK_EQ(index, scope_info_handle->length());
  DCHECK_EQ(length, scope_info_handle->length());
  DCHECK_EQ(parameter_count, scope_info_handle->ParameterCount());
  DCHECK_EQ(scope->num_heap_slots(), scope_info_handle->ContextLength());

  return scope_info_handle;
}

template EXPORT_TEMPLATE_DEFINE(V8_EXPORT_PRIVATE)
    Handle<ScopeInfo> ScopeInfo::Create(
        Isolate* isolate, Zone* zone, Scope* scope,
        MaybeDirectHandle<ScopeInfo> outer_scope);
template EXPORT_TEMPLATE_DEFINE(V8_EXPORT_PRIVATE)
    Handle<ScopeInfo> ScopeInfo::Create(
        LocalIsolate* isolate, Zone* zone, Scope* scope,
        MaybeDirectHandle<ScopeInfo> outer_scope);

// static
DirectHandle<ScopeInfo> ScopeInfo::CreateForWithScope(
    Isolate* isolate, MaybeDirectHandle<ScopeInfo> outer_scope) {
  const bool has_outer_scope_info = !outer_scope.is_null();
  const int length = (has_outer_scope_info ? 1 : 0);

  Factory* factory = isolate->factory();
  DirectHandle<ScopeInfo> scope_info = factory->NewScopeInfo(length);

  // Encode the flags.
  uint32_t flags = ScopeTypeBits::encode(WITH_SCOPE) |
                   SloppyEvalCanExtendVarsBit::encode(false) |
                   LanguageModeBit::encode(LanguageMode::kSloppy) |
                   DeclarationScopeBit::encode(false) |
                   ReceiverVariableBits::encode(VariableAllocationInfo::NONE) |
                   ClassScopeHasPrivateBrandBit::encode(false) |
                   HasSavedClassVariableBit::encode(false) |
                   AllocatesArgumentsBit::encode(false) |
                   FunctionVariableBits::encode(VariableAllocationInfo::NONE) |
                   HasSimpleParametersBit::encode(true) |
                   FunctionKindBits::encode(FunctionKind::kNormalFunction) |
                   HasOuterScopeInfoBit::encode(has_outer_scope_info) |
                   IsDebugEvaluateScopeBit::encode(false) |
                   ForceContextAllocationBit::encode(false) |
                   PrivateNameLookupSkipsOuterClassBit::encode(false) |
                   HasContextExtensionSlotBit::encode(true) |
                   IsHiddenBit::encode(false) |
                   IsWrappedFunctionBit::encode(false) |
                   IsHoistedInContextBit::encode(false);
  scope_info->set_flags(flags, kRelaxedStore);

  scope_info->set_parameter_count(0);
  scope_info->set_context_local_count(0);

  scope_info->set_position_info_start(0);
  scope_info->set_position_info_end(0);

  int index = 0;
  DCHECK_EQ(index, scope_info->FunctionVariableInfoIndex());
  DCHECK_EQ(index, scope_info->InferredFunctionNameIndex());
  DCHECK_EQ(index, scope_info->OuterScopeInfoIndex());
  if (has_outer_scope_info) {
    Tagged<ScopeInfo> outer = *outer_scope.ToHandleChecked();
    scope_info->set(index++, outer);
  }
  DCHECK_EQ(index, scope_info->DependentCodeIndex());
  DCHECK_EQ(index, scope_info->UnusedParameterBitsIndex());
  DCHECK_EQ(index, scope_info->length());
  DCHECK_EQ(length, scope_info->length());
  DCHECK_EQ(0, scope_info->ParameterCount());
  DCHECK_EQ(scope_info->ContextHeaderLength(), scope_info->ContextLength());
  return scope_info;
}

// static
DirectHandle<ScopeInfo> ScopeInfo::CreateGlobalThisBinding(Isolate* isolate) {
  return CreateForBootstrapping(isolate, BootstrappingType::kScript);
}

// static
DirectHandle<ScopeInfo> ScopeInfo::CreateForEmptyFunction(Isolate* isolate) {
  DirectHandle<ScopeInfo> scope_info =
      CreateForBootstrapping(isolate, BootstrappingType::kFunction);
  if (v8_flags.function_context_cells) {
    // An empty function scope will set the has_context_cells_ flag, since it is
    // set to true for all function scopes with the number of context locals (in
    // this case zero) below the threshold
    // v8_flags.function_context_cells_max_size.
    scope_info->set_flags(
        scope_info->Flags() | HasContextCellsBit::encode(true), kRelaxedStore);
  }
  return scope_info;
}

// static
DirectHandle<ScopeInfo> ScopeInfo::CreateForNativeContext(Isolate* isolate) {
  return CreateForBootstrapping(isolate, BootstrappingType::kNative);
}

// static
DirectHandle<ScopeInfo> ScopeInfo::CreateForShadowRealmNativeContext(
    Isolate* isolate) {
  return CreateForBootstrapping(isolate, BootstrappingType::kShadowRealm);
}

// static
DirectHandle<ScopeInfo> ScopeInfo::CreateForBootstrapping(
    Isolate* isolate, BootstrappingType type) {
  const int parameter_count = 0;
  const bool is_empty_function = type == BootstrappingType::kFunction;
  const bool is_native_context = (type == BootstrappingType::kNative) ||
                                 (type == BootstrappingType::kShadowRealm);
  const bool is_script = type == BootstrappingType::kScript;
  const bool is_shadow_realm = type == BootstrappingType::kShadowRealm;
  const int context_local_count =
      is_empty_function || is_native_context ? 0 : 1;
  const bool has_inferred_function_name = is_empty_function;
  // NOTE: Local names are always inlined here, since context_local_count < 2.
  DCHECK_LT(context_local_count, kScopeInfoMaxInlinedLocalNamesSize);
  const int length =
      2 * context_local_count + (is_empty_function ? kFunctionNameEntries : 0) +
      (has_inferred_function_name ? 1 : 0) + (is_empty_function ? 1 : 0);

  Factory* factory = isolate->factory();
  DirectHandle<ScopeInfo> scope_info =
      factory->NewScopeInfo(length, AllocationType::kReadOnly);
  DisallowGarbageCollection _nogc;
  // Encode the flags.
  DCHECK_IMPLIES(is_shadow_realm || is_script, !is_empty_function);
  uint32_t flags =
      ScopeTypeBits::encode(
          is_empty_function
              ? FUNCTION_SCOPE
              : (is_shadow_realm ? SHADOW_REALM_SCOPE : SCRIPT_SCOPE)) |
      SloppyEvalCanExtendVarsBit::encode(false) |
      LanguageModeBit::encode(LanguageMode::kSloppy) |
      DeclarationScopeBit::encode(true) |
      ReceiverVariableBits::encode(is_script ? VariableAllocationInfo::CONTEXT
                                             : VariableAllocationInfo::UNUSED) |
      ClassScopeHasPrivateBrandBit::encode(false) |
      HasSavedClassVariableBit::encode(false) |
      // We don't know the value of this flag, so set defensively.
      AllocatesArgumentsBit::encode(type == BootstrappingType::kFunction &&
                                    !is_empty_function) |
      FunctionVariableBits::encode(is_empty_function
                                       ? VariableAllocationInfo::UNUSED
                                       : VariableAllocationInfo::NONE) |
      HasInferredFunctionNameBit::encode(has_inferred_function_name) |
      HasSimpleParametersBit::encode(true) |
      FunctionKindBits::encode(FunctionKind::kNormalFunction) |
      HasOuterScopeInfoBit::encode(false) |
      IsDebugEvaluateScopeBit::encode(false) |
      ForceContextAllocationBit::encode(false) |
      PrivateNameLookupSkipsOuterClassBit::encode(false) |
      HasContextExtensionSlotBit::encode(is_native_context) |
      IsHiddenBit::encode(false) | IsWrappedFunctionBit::encode(false) |
      HasContextCellsBit::encode(false) | IsHoistedInContextBit::encode(false);
  Tagged<ScopeInfo> raw_scope_info = *scope_info;
  raw_scope_info->set_flags(flags, kRelaxedStore);
  raw_scope_info->set_parameter_count(parameter_count);
  raw_scope_info->set_context_local_count(context_local_count);
  raw_scope_info->set_position_info_start(0);
  raw_scope_info->set_position_info_end(0);

  int index = 0;

  // Here we add info for context-allocated "this".
  DCHECK_EQ(index, raw_scope_info->ContextLocalNamesIndex());
  ReadOnlyRoots roots(isolate);
  if (context_local_count) {
    raw_scope_info->set(index++, roots.this_string());
  }
  DCHECK_EQ(index, raw_scope_info->ContextLocalInfosIndex());
  if (context_local_count > 0) {
    const uint32_t value = VariableModeBits::encode(VariableMode::kConst) |
                           InitFlagBit::encode(kCreatedInitialized) |
                           MaybeAssignedFlagBit::encode(kNotAssigned) |
                           IsParameterBit::encode(false) |
                           ParameterNumberOrPositionBits::encode(
                               ParameterNumberOrPositionBits::kMax) |
                           IsStaticFlagBit::encode(IsStaticFlag::kNotStatic);
    raw_scope_info->set(index++, Smi::FromInt(value));
  }

  DCHECK_EQ(index, raw_scope_info->FunctionVariableInfoIndex());
  if (is_empty_function) {
    raw_scope_info->set(index++, roots.empty_string());
    raw_scope_info->set(index++, Smi::zero());
  }
  DCHECK_EQ(index, raw_scope_info->InferredFunctionNameIndex());
  if (has_inferred_function_name) {
    raw_scope_info->set(index++, roots.empty_string());
  }
  DCHECK_EQ(index, raw_scope_info->OuterScopeInfoIndex());
  DCHECK_EQ(index, raw_scope_info->DependentCodeIndex());
  DCHECK_EQ(index, scope_info->UnusedParameterBitsIndex());
  if (is_empty_function) {
    // unused parameters
    raw_scope_info->set(index++, Smi::zero());
  }
  DCHECK_EQ(index, raw_scope_info->length());
  DCHECK_EQ(length, raw_scope_info->length());
  DCHECK_EQ(raw_scope_info->ParameterCount(), parameter_count);
  if (is_empty_function || is_native_context) {
    DCHECK_EQ(raw_scope_info->ContextLength(), 0);
  } else {
    DCHECK_EQ(raw_scope_info->ContextLength(),
              raw_scope_info->ContextHeaderLength() + 1);
  }

  return scope_info;
}

Tagged<Object> ScopeInfo::get(int index) const {
  DCHECK_LT(static_cast<unsigned>(index), static_cast<unsigned>(length()));
  return data()[index].Relaxed_Load();
}

void ScopeInfo::set(int index, Tagged<Smi> value) {
  DCHECK_LT(static_cast<unsigned>(index), static_cast<unsigned>(length()));
  DCHECK(IsSmi(Tagged<Object>(value)));
  data()[index].Relaxed_Store(this, value, SKIP_WRITE_BARRIER);
}

void ScopeInfo::set(int index, Tagged<Object> value, WriteBarrierMode mode) {
  DCHECK_LT(static_cast<unsigned>(index), static_cast<unsigned>(length()));
  data()[index].Relaxed_Store(this, value, mode);
}

void ScopeInfo::CopyElements(Isolate* isolate, int dst_index,
                             Tagged<ScopeInfo> src, int src_index, int len,
                             WriteBarrierMode mode) {
  if (len == 0) return;
  DCHECK_LE(src_index + len, src->length());
  DisallowGarbageCollection no_gc;

  ObjectSlot dst_slot(RawFieldOfElementAt(dst_index));
  ObjectSlot src_slot(src->RawFieldOfElementAt(src_index));
  isolate->heap()->CopyRange(this, dst_slot, src_slot, len, mode);
}

ObjectSlot ScopeInfo::RawFieldOfElementAt(int index) {
  return ObjectSlot(reinterpret_cast<Address>(this) + OffsetOfElementAt(index));
}

int ScopeInfo::length() const {
  // AllocatedSize() represents the total size in bytes of the object, as
  // computed from flags, context_local_count, and possibly
  // module_variable_count. Subtract the fixed header and convert the
  // remaining bytes into variable-part slots.
  return (AllocatedSize() - OFFSET_OF_DATA_START(ScopeInfo)) / kTaggedSize;
}

Tagged<ScopeInfo> ScopeInfo::Empty(Isolate* isolate) {
  return ReadOnlyRoots(isolate).empty_scope_info();
}

bool ScopeInfo::IsEmpty() const {
  return this == ReadOnlyHeap::EarlyGetReadOnlyRoots(this).empty_scope_info();
}

ScopeType ScopeInfo::scope_type() const {
  DCHECK(!this->IsEmpty());
  return ScopeTypeBits::decode(Flags());
}

bool ScopeInfo::is_script_scope() const {
  return scope_type() == SCRIPT_SCOPE || scope_type() == REPL_MODE_SCOPE;
}

bool ScopeInfo::SloppyEvalCanExtendVars() const {
  bool sloppy_eval_can_extend_vars =
      SloppyEvalCanExtendVarsBit::decode(Flags());
  DCHECK_IMPLIES(sloppy_eval_can_extend_vars, is_sloppy(language_mode()));
  DCHECK_IMPLIES(sloppy_eval_can_extend_vars, is_declaration_scope());
  return sloppy_eval_can_extend_vars;
}

LanguageMode ScopeInfo::language_mode() const {
  return LanguageModeBit::decode(Flags());
}

bool ScopeInfo::is_declaration_scope() const {
  return DeclarationScopeBit::decode(Flags());
}

int ScopeInfo::ContextLength() const {
  int context_locals = ContextLocalCount();
  bool function_name_context_slot = HasContextAllocatedFunctionName();
  bool force_context = ForceContextAllocationBit::decode(Flags());
  bool has_context =
      context_locals > 0 || force_context || function_name_context_slot ||
      scope_type() == WITH_SCOPE || scope_type() == CLASS_SCOPE ||
      (scope_type() == BLOCK_SCOPE && SloppyEvalCanExtendVars() &&
       is_declaration_scope()) ||
      (scope_type() == FUNCTION_SCOPE && SloppyEvalCanExtendVars()) ||
      scope_type() == MODULE_SCOPE;

  if (!has_context) return 0;
  return ContextHeaderLength() + context_locals +
         (function_name_context_slot ? 1 : 0);
}

// Needs to be kept in sync with Scope::UniqueIdInScript and
// SharedFunctionInfo::UniqueIdInScript.
int ScopeInfo::UniqueIdInScript() const {
  // Script scopes start "before" the script to avoid clashing with a scope that
  // starts on character 0.
  if (is_script_scope() || scope_type() == EVAL_SCOPE ||
      scope_type() == MODULE_SCOPE) {
    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 (IsWrappedFunctionScope()) {
    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(function_kind());
}

bool ScopeInfo::HasContextExtensionSlot() const {
  return HasContextExtensionSlotBit::decode(Flags());
}

bool ScopeInfo::SomeContextHasExtension() const {
  return SomeContextHasExtensionBit::decode(Flags());
}

void ScopeInfo::mark_some_context_has_extension() {
  set_flags(SomeContextHasExtensionBit::update(Flags(), true), kRelaxedStore);
}

int ScopeInfo::ContextHeaderLength() const {
  return HasContextExtensionSlot() ? Context::MIN_CONTEXT_EXTENDED_SLOTS
                                   : Context::MIN_CONTEXT_SLOTS;
}

bool ScopeInfo::HasReceiver() const {
  return VariableAllocationInfo::NONE != ReceiverVariableBits::decode(Flags());
}

bool ScopeInfo::HasAllocatedReceiver() const {
  // The receiver is allocated and needs to be deserialized during reparsing
  // when:
  // 1. During the initial parsing, it's been observed that the inner
  //    scopes are accessing this, so the receiver should be allocated
  //    again. This can be inferred when the receiver variable is
  //    recorded as being allocated on the stack or context.
  // 2. The scope is created as a debug evaluate scope, so this is not
  //    an actual reparse, we are not sure if the inner scope will access
  //    this, but the receiver should be allocated just in case.
  VariableAllocationInfo allocation = ReceiverVariableBits::decode(Flags());
  return allocation == VariableAllocationInfo::STACK ||
         allocation == VariableAllocationInfo::CONTEXT ||
         IsDebugEvaluateScope();
}

bool ScopeInfo::ClassScopeHasPrivateBrand() const {
  return ClassScopeHasPrivateBrandBit::decode(Flags());
}

bool ScopeInfo::HasSavedClassVariable() const {
  return HasSavedClassVariableBit::decode(Flags());
}

bool ScopeInfo::IsSloppyNormalJSFunction() const {
  return function_kind() == FunctionKind::kNormalFunction &&
         is_sloppy(language_mode());
}

bool ScopeInfo::CanOnlyAccessFixedFormalParameters() const {
  DCHECK(!IsEmpty());
  FunctionKind function_kind = this->function_kind();
  return
      // Can't be a SloppyNormalJSFunction.
      !IsSloppyNormalJSFunction() &&
      // TODO(dcarney): Make this function kind filter exact. It's currently
      //                fine if it's not as this results in conservation
      //                optimizations.
      (function_kind == FunctionKind::kNormalFunction ||
       function_kind == FunctionKind::kArrowFunction) &&
      // Can't have arguments allocated in the frame for any reason since this
      // indicates potential reachability.
      !AllocatesArgumentsBit::decode(Flags()) &&
      // Can't have rest parameters.
      HasSimpleParameters();
}

bool ScopeInfo::HasFunctionName() const {
  return VariableAllocationInfo::NONE != FunctionVariableBits::decode(Flags());
}

bool ScopeInfo::HasContextAllocatedFunctionName() const {
  return VariableAllocationInfo::CONTEXT ==
         FunctionVariableBits::decode(Flags());
}

bool ScopeInfo::HasInferredFunctionName() const {
  return HasInferredFunctionNameBit::decode(Flags());
}

bool ScopeInfo::HasSharedFunctionName() const {
  return FunctionName() != SharedFunctionInfo::kNoSharedNameSentinel;
}

void ScopeInfo::SetFunctionName(Tagged<UnionOf<Smi, String>> name) {
  DCHECK(HasFunctionName());
  DCHECK(IsString(name) || name == SharedFunctionInfo::kNoSharedNameSentinel);
  DCHECK_IMPLIES(HasContextAllocatedFunctionName(), IsInternalizedString(name));
  set_function_variable_info_name(name);
}

void ScopeInfo::SetInferredFunctionName(Tagged<String> name) {
  DCHECK(HasInferredFunctionName());
  set_inferred_function_name(name);
}

bool ScopeInfo::HasOuterScopeInfo() const {
  return HasOuterScopeInfoBit::decode(Flags());
}

bool ScopeInfo::IsDebugEvaluateScope() const {
  return IsDebugEvaluateScopeBit::decode(Flags());
}

void ScopeInfo::SetIsDebugEvaluateScope() {
  CHECK(!this->IsEmpty());
  DCHECK_EQ(scope_type(), WITH_SCOPE);
  set_flags(Flags() | IsDebugEvaluateScopeBit::encode(true), kRelaxedStore);
}

bool ScopeInfo::PrivateNameLookupSkipsOuterClass() const {
  return PrivateNameLookupSkipsOuterClassBit::decode(Flags());
}

bool ScopeInfo::IsReplModeScope() const {
  return scope_type() == REPL_MODE_SCOPE;
}

bool ScopeInfo::IsWrappedFunctionScope() const {
  DCHECK_IMPLIES(IsWrappedFunctionBit::decode(Flags()),
                 scope_type() == FUNCTION_SCOPE);
  return IsWrappedFunctionBit::decode(Flags());
}

bool ScopeInfo::HasContext() const { return ContextLength() > 0; }

Tagged<UnionOf<Smi, String>> ScopeInfo::FunctionName() const {
  DCHECK(HasFunctionName());
  return function_variable_info_name();
}

Tagged<Object> ScopeInfo::InferredFunctionName() const {
  DCHECK(HasInferredFunctionName());
  return inferred_function_name();
}

Tagged<String> ScopeInfo::FunctionDebugName() const {
  if (!HasFunctionName()) return GetReadOnlyRoots().empty_string();
  Tagged<Object> name = FunctionName();
  if (IsString(name) && Cast<String>(name)->length() > 0) {
    return Cast<String>(name);
  }
  if (HasInferredFunctionName()) {
    name = InferredFunctionName();
    if (IsString(name)) return Cast<String>(name);
  }
  return GetReadOnlyRoots().empty_string();
}

int ScopeInfo::StartPosition() const {
  DCHECK(!this->IsEmpty());
  return position_info_start();
}

int ScopeInfo::EndPosition() const {
  DCHECK(!this->IsEmpty());
  return position_info_end();
}

void ScopeInfo::SetPositionInfo(int start, int end) {
  DCHECK(!this->IsEmpty());
  DCHECK_LE(start, end);
  set_position_info_start(start);
  set_position_info_end(end);
}

Tagged<ScopeInfo> ScopeInfo::OuterScopeInfo() const {
  DCHECK(HasOuterScopeInfo());
  return Cast<ScopeInfo>(outer_scope_info());
}

Tagged<SourceTextModuleInfo> ScopeInfo::ModuleDescriptorInfo() const {
  DCHECK(scope_type() == MODULE_SCOPE);
  return Cast<SourceTextModuleInfo>(module_info());
}

Tagged<String> ScopeInfo::ContextInlinedLocalName(int var) const {
  DCHECK(HasInlinedLocalNames());
  return context_local_names(var);
}

VariableMode ScopeInfo::ContextLocalMode(int var) const {
  int value = context_local_infos(var);
  return VariableModeBits::decode(value);
}

IsStaticFlag ScopeInfo::ContextLocalIsStaticFlag(int var) const {
  int value = context_local_infos(var);
  return IsStaticFlagBit::decode(value);
}

InitializationFlag ScopeInfo::ContextLocalInitFlag(int var) const {
  int value = context_local_infos(var);
  return InitFlagBit::decode(value);
}

bool ScopeInfo::ContextLocalIsParameter(int var) const {
  int value = context_local_infos(var);
  return IsParameterBit::decode(value);
}

uint32_t ScopeInfo::ContextLocalParameterNumber(int var) const {
  DCHECK(ContextLocalIsParameter(var));
  int value = context_local_infos(var);
  return ParameterNumberOrPositionBits::decode(value);
}

int ScopeInfo::ContextLocalInitializerPosition(int var) const {
  DCHECK(!ContextLocalIsParameter(var));
  int value = context_local_infos(var);
  int position = ParameterNumberOrPositionBits::decode(value);
  if (position == static_cast<int>(ParameterNumberOrPositionBits::kMax)) {
    return kMaxInt;
  }
  return StartPosition() + position;
}

MaybeAssignedFlag ScopeInfo::ContextLocalMaybeAssignedFlag(int var) const {
  int value = context_local_infos(var);
  return MaybeAssignedFlagBit::decode(value);
}

// LINT.IfChange(VariableIsSynthetic)
// static
bool ScopeInfo::VariableIsSynthetic(Tagged<String> name) {
  // There's currently no flag stored on the ScopeInfo to indicate that a
  // variable is a compiler-introduced temporary. However, to avoid conflict
  // with user declarations, the current temporaries like .generator_object and
  // .result start with a dot, so we can use that as a flag. It's a hack!
  return name->length() == 0 || name->Get(0) == '.' || name->Get(0) == '#' ||
         name->Equals(GetReadOnlyRoots().this_string());
}
// LINT.ThenChange(/src/debug/debug-scope-info.cc:VariableIsSynthetic)

int ScopeInfo::ModuleVariableCount() const {
  DCHECK_EQ(scope_type(), MODULE_SCOPE);
  return module_variable_count();
}

int ScopeInfo::ModuleIndex(Tagged<String> name, VariableMode* mode,
                           InitializationFlag* init_flag,
                           MaybeAssignedFlag* maybe_assigned_flag,
                           int* initializer_position) {
  DisallowGarbageCollection no_gc;
  DCHECK(IsInternalizedString(name));
  DCHECK_EQ(scope_type(), MODULE_SCOPE);
  DCHECK_NOT_NULL(mode);
  DCHECK_NOT_NULL(init_flag);
  DCHECK_NOT_NULL(maybe_assigned_flag);

  if (HasModuleVariablesHashtable()) {
    int i = module_variables_hashtable()->Lookup(name);
    if (i == -1) return 0;
    DCHECK_LT(i, module_variable_count());
    DCHECK(name->Equals(module_variables_name(i)));
    int index;
    ModuleVariable(i, nullptr, &index, mode, init_flag, maybe_assigned_flag,
                   initializer_position);
    return index;
  }

  int module_vars_count = module_variable_count();
  for (int i = 0; i < module_vars_count; ++i) {
    Tagged<String> var_name = module_variables_name(i);
    if (name->Equals(var_name)) {
      int index;
      ModuleVariable(i, nullptr, &index, mode, init_flag, maybe_assigned_flag,
                     initializer_position);
      return index;
    }
  }

  return 0;
}

int ScopeInfo::InlinedLocalNamesLookup(Tagged<String> name) {
  DisallowGarbageCollection no_gc;
  int local_count = context_local_count();
  for (int i = 0; i < local_count; ++i) {
    if (name == ContextInlinedLocalName(i)) {
      return i;
    }
  }
  return -1;
}

int ScopeInfo::ContextSlotIndex(Tagged<String> name,
                                VariableLookupResult* lookup_result) {
  DisallowGarbageCollection no_gc;
  DCHECK(IsInternalizedString(name));
  DCHECK_NOT_NULL(lookup_result);
  DCHECK_IMPLIES(this->IsEmpty(), ContextLocalCount() == 0);

  int index = HasInlinedLocalNames()
                  ? InlinedLocalNamesLookup(name)
                  : context_local_names_hashtable()->Lookup(name);

  if (index != -1) {
    lookup_result->mode = ContextLocalMode(index);
    lookup_result->is_static_flag = ContextLocalIsStaticFlag(index);
    lookup_result->init_flag = ContextLocalInitFlag(index);
    lookup_result->maybe_assigned_flag = ContextLocalMaybeAssignedFlag(index);
    lookup_result->is_repl_mode = IsReplModeScope();
    lookup_result->initializer_position =
        ContextLocalIsParameter(index) ? kNoSourcePosition
                                       : ContextLocalInitializerPosition(index);
    int context_slot = ContextHeaderLength() + index;
    DCHECK_LT(context_slot, ContextLength());
    return context_slot;
  }

  return -1;
}

int ScopeInfo::ContextSlotIndex(Tagged<String> name) {
  VariableLookupResult lookup_result;
  return ContextSlotIndex(name, &lookup_result);
}

std::pair<Tagged<String>, int> ScopeInfo::SavedClassVariable() const {
  DCHECK(HasSavedClassVariableBit::decode(Flags()));
  auto class_variable_info = saved_class_variable_info();
  if (HasInlinedLocalNames()) {
    // The saved class variable info corresponds to the context slot index.
    DCHECK(class_variable_info.IsSmi());
    int index =
        class_variable_info.ToSmi().value() - Context::MIN_CONTEXT_SLOTS;
    DCHECK_GE(index, 0);
    DCHECK_LT(index, ContextLocalCount());
    Tagged<String> name = ContextInlinedLocalName(index);
    return std::make_pair(name, index);
  } else {
    // The saved class variable info corresponds to the name.
    Tagged<Name> name = Cast<Name>(class_variable_info);
    Tagged<NameToIndexHashTable> table = context_local_names_hashtable();
    int index = table->Lookup(name);
    DCHECK_GE(index, 0);
    DCHECK(IsString(name));
    return std::make_pair(Cast<String>(name), index);
  }
}

int ScopeInfo::ReceiverContextSlotIndex() const {
  if (ReceiverVariableBits::decode(Flags()) ==
      VariableAllocationInfo::CONTEXT) {
    return ContextHeaderLength();
  }
  return -1;
}

int ScopeInfo::ParametersStartIndex() const {
  if (ReceiverVariableBits::decode(Flags()) ==
      VariableAllocationInfo::CONTEXT) {
    return ContextHeaderLength() + 1;
  }
  return ContextHeaderLength();
}

int ScopeInfo::FunctionContextSlotIndex() const {
  if (HasContextAllocatedFunctionName()) {
    return function_variable_info_context_or_stack_slot_index();
  }
  return -1;
}

int ScopeInfo::FunctionContextSlotIndex(Tagged<String> name) const {
  DCHECK(IsInternalizedString(name));
  if (HasContextAllocatedFunctionName()) {
    DCHECK_IMPLIES(HasFunctionName(), IsInternalizedString(FunctionName()));
    if (FunctionName() == name) {
      return function_variable_info_context_or_stack_slot_index();
    }
  }
  return -1;
}

FunctionKind ScopeInfo::function_kind() const {
  return FunctionKindBits::decode(Flags());
}

int ScopeInfo::ContextLocalNamesIndex() const {
  return ConvertOffsetToIndex(ContextLocalNamesOffset());
}

int ScopeInfo::ContextLocalInfosIndex() const {
  return ConvertOffsetToIndex(ContextLocalInfosOffset());
}

int ScopeInfo::SavedClassVariableInfoIndex() const {
  return ConvertOffsetToIndex(SavedClassVariableInfoOffset());
}

int ScopeInfo::FunctionVariableInfoIndex() const {
  return ConvertOffsetToIndex(FunctionVariableInfoOffset());
}

int ScopeInfo::InferredFunctionNameIndex() const {
  return ConvertOffsetToIndex(InferredFunctionNameOffset());
}

int ScopeInfo::OuterScopeInfoIndex() const {
  return ConvertOffsetToIndex(OuterScopeInfoOffset());
}

int ScopeInfo::ModuleInfoIndex() const {
  return ConvertOffsetToIndex(ModuleInfoOffset());
}

int ScopeInfo::ModuleVariableCountIndex() const {
  return ConvertOffsetToIndex(kModuleVariableCountOffset);
}

int ScopeInfo::ModuleVariablesIndex() const {
  return ConvertOffsetToIndex(ModuleVariablesOffset());
}

int ScopeInfo::ModuleVariablesHashtableIndex() const {
  return ConvertOffsetToIndex(ModuleVariablesHashtableOffset());
}

void ScopeInfo::ModuleVariable(int i, Tagged<String>* name, int* index,
                               VariableMode* mode,
                               InitializationFlag* init_flag,
                               MaybeAssignedFlag* maybe_assigned_flag,
                               int* initializer_position) {
  int properties = module_variables_properties(i);

  if (name != nullptr) {
    *name = module_variables_name(i);
  }
  if (index != nullptr) {
    *index = module_variables_index(i);
    DCHECK_NE(*index, 0);
  }
  if (mode != nullptr) {
    *mode = VariableModeBits::decode(properties);
  }
  if (init_flag != nullptr) {
    *init_flag = InitFlagBit::decode(properties);
  }
  if (maybe_assigned_flag != nullptr) {
    *maybe_assigned_flag = MaybeAssignedFlagBit::decode(properties);
  }
  if (initializer_position != nullptr) {
    int position = ParameterNumberOrPositionBits::decode(properties);
    if (position == static_cast<int>(ParameterNumberOrPositionBits::kMax)) {
      *initializer_position = kMaxInt;
    } else {
      *initializer_position = StartPosition() + position;
    }
  }
}

int ScopeInfo::DependentCodeIndex() const {
  return ConvertOffsetToIndex(DependentCodeOffset());
}

int ScopeInfo::UnusedParameterBitsIndex() const {
  return ConvertOffsetToIndex(UnusedParameterBitsOffset());
}

uint32_t ScopeInfo::Hash() {
  // Hash ScopeInfo based on its start and end position.
  // Note: Ideally we'd also have the script ID. But since we only use the
  // hash in a debug-evaluate cache, we don't worry too much about collisions.
  DCHECK(!this->IsEmpty());
  return static_cast<uint32_t>(
      base::hash_combine(flags(kRelaxedLoad), StartPosition(), EndPosition()));
}

std::ostream& operator<<(std::ostream& os, VariableAllocationInfo var_info) {
  switch (var_info) {
    case VariableAllocationInfo::NONE:
      return os << "NONE";
    case VariableAllocationInfo::STACK:
      return os << "STACK";
    case VariableAllocationInfo::CONTEXT:
      return os << "CONTEXT";
    case VariableAllocationInfo::UNUSED:
      return os << "UNUSED";
  }
  UNREACHABLE();
}

template <typename IsolateT>
Handle<ModuleRequest> ModuleRequest::New(
    IsolateT* isolate, DirectHandle<String> specifier, ModuleImportPhase phase,
    DirectHandle<FixedArray> import_attributes, int position) {
  auto result = Cast<ModuleRequest>(
      isolate->factory()->NewStruct(MODULE_REQUEST_TYPE, AllocationType::kOld));
  DisallowGarbageCollection no_gc;
  Tagged<ModuleRequest> raw = *result;
  raw->set_specifier(*specifier);
  raw->set_import_attributes(*import_attributes);
  raw->set_flags(0);

  raw->set_phase(phase);
  DCHECK_GE(position, 0);
  raw->set_position(position);
  return result;
}

template EXPORT_TEMPLATE_DEFINE(V8_EXPORT_PRIVATE)
    Handle<ModuleRequest> ModuleRequest::New(
        Isolate* isolate, DirectHandle<String> specifier,
        ModuleImportPhase phase, DirectHandle<FixedArray> import_attributes,
        int position);
template EXPORT_TEMPLATE_DEFINE(V8_EXPORT_PRIVATE)
    Handle<ModuleRequest> ModuleRequest::New(
        LocalIsolate* isolate, DirectHandle<String> specifier,
        ModuleImportPhase phase, DirectHandle<FixedArray> import_attributes,
        int position);

template <typename IsolateT>
Handle<SourceTextModuleInfoEntry> SourceTextModuleInfoEntry::New(
    IsolateT* isolate, DirectHandle<UnionOf<String, Undefined>> export_name,
    DirectHandle<UnionOf<String, Undefined>> local_name,
    DirectHandle<UnionOf<String, Undefined>> import_name, int module_request,
    int cell_index, int beg_pos, int end_pos) {
  auto result = Cast<SourceTextModuleInfoEntry>(isolate->factory()->NewStruct(
      SOURCE_TEXT_MODULE_INFO_ENTRY_TYPE, AllocationType::kOld));
  DisallowGarbageCollection no_gc;
  Tagged<SourceTextModuleInfoEntry> raw = *result;
  raw->set_export_name(*export_name);
  raw->set_local_name(*local_name);
  raw->set_import_name(*import_name);
  raw->set_module_request(module_request);
  raw->set_cell_index(cell_index);
  raw->set_beg_pos(beg_pos);
  raw->set_end_pos(end_pos);
  return result;
}

template EXPORT_TEMPLATE_DEFINE(V8_EXPORT_PRIVATE)
    Handle<SourceTextModuleInfoEntry> SourceTextModuleInfoEntry::New(
        Isolate* isolate, DirectHandle<UnionOf<String, Undefined>> export_name,
        DirectHandle<UnionOf<String, Undefined>> local_name,
        DirectHandle<UnionOf<String, Undefined>> import_name,
        int module_request, int cell_index, int beg_pos, int end_pos);
template EXPORT_TEMPLATE_DEFINE(V8_EXPORT_PRIVATE)
    Handle<SourceTextModuleInfoEntry> SourceTextModuleInfoEntry::New(
        LocalIsolate* isolate,
        DirectHandle<UnionOf<String, Undefined>> export_name,
        DirectHandle<UnionOf<String, Undefined>> local_name,
        DirectHandle<UnionOf<String, Undefined>> import_name,
        int module_request, int cell_index, int beg_pos, int end_pos);

template <typename IsolateT>
DirectHandle<SourceTextModuleInfo> SourceTextModuleInfo::New(
    IsolateT* isolate, Zone* zone, SourceTextModuleDescriptor* descr) {
  // Serialize module requests.
  int size = static_cast<int>(descr->module_requests().size());
  DirectHandle<FixedArray> module_requests =
      isolate->factory()->NewFixedArray(size, AllocationType::kOld);
  for (const auto& elem : descr->module_requests()) {
    DirectHandle<ModuleRequest> serialized_module_request =
        elem->Serialize(isolate);
    module_requests->set(elem->index(), *serialized_module_request);
  }

  // Serialize special exports.
  DirectHandle<FixedArray> special_exports = isolate->factory()->NewFixedArray(
      static_cast<int>(descr->special_exports().size()), AllocationType::kOld);
  {
    int i = 0;
    for (auto entry : descr->special_exports()) {
      DirectHandle<SourceTextModuleInfoEntry> serialized_entry =
          entry->Serialize(isolate);
      special_exports->set(i++, *serialized_entry);
    }
  }

  // Serialize namespace imports.
  DirectHandle<FixedArray> namespace_imports =
      isolate->factory()->NewFixedArray(
          static_cast<int>(descr->namespace_imports().size()),
          AllocationType::kOld);
  {
    int i = 0;
    for (auto entry : descr->namespace_imports()) {
      DirectHandle<SourceTextModuleInfoEntry> serialized_entry =
          entry.second->Serialize(isolate);
      namespace_imports->set(i++, *serialized_entry);
    }
  }

  // Serialize regular exports.
  DirectHandle<FixedArray> regular_exports =
      descr->SerializeRegularExports(isolate, zone);

  // Serialize regular imports.
  DirectHandle<FixedArray> regular_imports = isolate->factory()->NewFixedArray(
      static_cast<int>(descr->regular_imports().size()), AllocationType::kOld);
  {
    int i = 0;
    for (const auto& elem : descr->regular_imports()) {
      DirectHandle<SourceTextModuleInfoEntry> serialized_entry =
          elem.second->Serialize(isolate);
      regular_imports->set(i++, *serialized_entry);
    }
  }

  DirectHandle<SourceTextModuleInfo> result =
      isolate->factory()->NewSourceTextModuleInfo();
  result->set(kModuleRequestsIndex, *module_requests);
  result->set(kSpecialExportsIndex, *special_exports);
  result->set(kRegularExportsIndex, *regular_exports);
  result->set(kNamespaceImportsIndex, *namespace_imports);
  result->set(kRegularImportsIndex, *regular_imports);
  return result;
}

template EXPORT_TEMPLATE_DEFINE(V8_EXPORT_PRIVATE)
    DirectHandle<SourceTextModuleInfo> SourceTextModuleInfo::New(
        Isolate* isolate, Zone* zone, SourceTextModuleDescriptor* descr);
template EXPORT_TEMPLATE_DEFINE(V8_EXPORT_PRIVATE)
    DirectHandle<SourceTextModuleInfo> SourceTextModuleInfo::New(
        LocalIsolate* isolate, Zone* zone, SourceTextModuleDescriptor* descr);

uint32_t SourceTextModuleInfo::RegularExportCount() const {
  uint32_t len = regular_exports()->ulength().value();
  DCHECK_EQ(len % kRegularExportLength, 0);
  return len / kRegularExportLength;
}

Tagged<String> SourceTextModuleInfo::RegularExportLocalName(int i) const {
  return Cast<String>(regular_exports()->get(i * kRegularExportLength +
                                             kRegularExportLocalNameOffset));
}

int SourceTextModuleInfo::RegularExportCellIndex(int i) const {
  return Smi::ToInt(regular_exports()->get(i * kRegularExportLength +
                                           kRegularExportCellIndexOffset));
}

Tagged<FixedArray> SourceTextModuleInfo::RegularExportExportNames(int i) const {
  return Cast<FixedArray>(regular_exports()->get(
      i * kRegularExportLength + kRegularExportExportNamesOffset));
}

bool SourceTextModuleInfo::HasStarExports() const {
  DisallowGarbageCollection no_gc;
  Tagged<FixedArray> exports = special_exports();
  const uint32_t length = exports->ulength().value();
  for (uint32_t i = 0; i < length; ++i) {
    // Star exports are the only special exports without a name.
    if (IsUndefined(
            Cast<SourceTextModuleInfoEntry>(exports->get(i))->export_name())) {
      return true;
    }
  }
  return false;
}

const char* ToString(ScopeType type) {
  switch (type) {
    case ScopeType::EVAL_SCOPE:
      return "EVAL_SCOPE";
    case ScopeType::FUNCTION_SCOPE:
      return "FUNCTION_SCOPE";
    case ScopeType::MODULE_SCOPE:
      return "MODULE_SCOPE";
    case ScopeType::SCRIPT_SCOPE:
      return "SCRIPT_SCOPE";
    case ScopeType::CATCH_SCOPE:
      return "CATCH_SCOPE";
    case ScopeType::BLOCK_SCOPE:
      return "BLOCK_SCOPE";
    case ScopeType::CLASS_SCOPE:
      return "CLASS_SCOPE";
    case ScopeType::WITH_SCOPE:
      return "WITH_SCOPE";
    case ScopeType::SHADOW_REALM_SCOPE:
      return "SHADOW_REALM_SCOPE";
    case ScopeType::REPL_MODE_SCOPE:
      return "REPL_MODE_SCOPE";
  }
  UNREACHABLE();
}

}  // namespace internal
}  // namespace v8

#include "src/objects/object-macros-undef.h"