// Copyright 2019 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.

extern macro EmptyScopeInfoConstant(): ScopeInfo;
const kEmptyScopeInfo: ScopeInfo = EmptyScopeInfoConstant();

extern enum ScopeType extends uint32 {
  // The empty scope info for builtins and NativeContexts is allocated
  // in a way that it gets the first scope type in line, see
  // Heap::CreateInitialMaps(). It's always guarded with the IsEmpty
  // bit, so it doesn't matter what scope type it gets.
  CLASS_SCOPE,
  EVAL_SCOPE,
  FUNCTION_SCOPE,
  MODULE_SCOPE,
  SCRIPT_SCOPE,
  CATCH_SCOPE,
  BLOCK_SCOPE,
  WITH_SCOPE
}

extern enum VariableAllocationInfo extends uint32 {
  NONE,
  STACK,
  CONTEXT,
  UNUSED
}

extern enum VariableMode extends uint32 {
  kLet,
  kConst,
  kVar,
  kTemporary,
  kDynamic,
  kDynamicGlobal,
  kDynamicLocal,
  kPrivateMethod,
  kPrivateSetterOnly,
  kPrivateGetterOnly,
  kPrivateGetterAndSetter
}

extern enum InitializationFlag extends uint32 {
  kNeedsInitialization,
  kCreatedInitialized
}

extern enum IsStaticFlag extends uint32 { kNotStatic, kStatic }

extern enum MaybeAssignedFlag extends uint32 { kNotAssigned, kMaybeAssigned }

// Properties of scopes.
bitfield struct ScopeFlags extends uint31 {
  scope_type: ScopeType: 4 bit;
  sloppy_eval_can_extend_vars: bool: 1 bit;
  language_mode: LanguageMode: 1 bit;
  declaration_scope: bool: 1 bit;
  receiver_variable: VariableAllocationInfo: 2 bit;
  // In class scope, this indicates whether the class has a private brand.
  // In constructor scope, this indicates whether the constructor needs
  // private brand initialization.
  class_scope_has_private_brand: bool: 1 bit;
  has_saved_class_variable: bool: 1 bit;
  has_new_target: bool: 1 bit;
  // TODO(cbruni): Combine with function variable field when only storing the
  // function name.
  function_variable: VariableAllocationInfo: 2 bit;
  has_inferred_function_name: bool: 1 bit;
  is_asm_module: bool: 1 bit;
  has_simple_parameters: bool: 1 bit;
  function_kind: FunctionKind: 5 bit;
  has_outer_scope_info: bool: 1 bit;
  is_debug_evaluate_scope: bool: 1 bit;
  force_context_allocation: bool: 1 bit;
  private_name_lookup_skips_outer_class: bool: 1 bit;
  has_context_extension_slot: bool: 1 bit;
  is_repl_mode_scope: bool: 1 bit;
  has_locals_block_list: bool: 1 bit;
  is_empty: bool: 1 bit;
}

struct PositionInfo {
  start: Smi;
  end: Smi;
}

struct FunctionVariableInfo {
  name: String|Zero;
  context_or_stack_slot_index: Smi;
}

bitfield struct VariableProperties extends uint31 {
  variable_mode: VariableMode: 4 bit;
  init_flag: InitializationFlag: 1 bit;
  maybe_assigned_flag: MaybeAssignedFlag: 1 bit;
  parameter_number: uint32: 16 bit;
  is_static_flag: IsStaticFlag: 1 bit;
}

struct ModuleVariable {
  name: String;
  index: Smi;
  properties: SmiTagged<VariableProperties>;
}

const kMaxInlinedLocalNamesSize:
    constexpr int32 generates 'kScopeInfoMaxInlinedLocalNamesSize';

@generateBodyDescriptor
extern class ScopeInfo extends HeapObject {
  const flags: SmiTagged<ScopeFlags>;

  // The number of parameters. For non-function scopes this is 0.
  parameter_count: Smi;

  // The number of non-parameter and parameter variables allocated in the
  // context.
  const context_local_count: Smi;

  // Contains the names of inlined local variables and parameters that are
  // allocated in the context. They are stored in increasing order of the
  // context slot index starting with Context::MIN_CONTEXT_SLOTS.
  context_local_names[Convert<intptr>(context_local_count) < kMaxInlinedLocalNamesSize ? context_local_count : 0]:
      String;

  // Contains a hash_map from local names to context slot index.
  // This is only used when local names are not inlined in the scope info.
  context_local_names_hashtable?
      [kMaxInlinedLocalNamesSize <= Convert<intptr>(context_local_count)]:
          NameToIndexHashTable;

  // Contains the variable modes and initialization flags corresponding to
  // the context locals in ContextLocalNames.
  context_local_infos[context_local_count]: SmiTagged<VariableProperties>;

  // If the scope is a class scope and it has static private methods that
  // may be accessed directly or through eval, one slot is reserved to hold
  // the offset in the field storage of the hash table (or the slot index if
  // local names are inlined) for the class variable.
  saved_class_variable_info?[flags.has_saved_class_variable]: Smi;

  // If the scope belongs to a named function expression this part contains
  // information about the function variable. It always occupies two array
  // slots:  a. The name of the function variable.
  //         b. The context or stack slot index for the variable.
  function_variable_info?
      [flags.function_variable !=
       FromConstexpr<VariableAllocationInfo>(VariableAllocationInfo::NONE)]:
          FunctionVariableInfo;

  inferred_function_name?[flags.has_inferred_function_name]: String|Undefined;

  // Contains two slots with a) the startPosition and b) the endPosition if
  // the scope belongs to a function or script.
  position_info?
      [flags.scope_type == ScopeType::FUNCTION_SCOPE ||
       flags.scope_type == ScopeType::SCRIPT_SCOPE ||
       flags.scope_type == ScopeType::EVAL_SCOPE ||
       flags.scope_type == ScopeType::MODULE_SCOPE ||
       (flags.is_empty ? false : flags.scope_type == ScopeType::CLASS_SCOPE)]:
          PositionInfo;

  outer_scope_info?[flags.has_outer_scope_info]: ScopeInfo|TheHole;

  // List of stack allocated local variables. Used by debug evaluate to properly
  // abort variable lookup when a name clashes with a stack allocated local that
  // can't be materialized.
  locals_block_list?[flags.has_locals_block_list]: HashTable;

  // For a module scope, this part contains the SourceTextModuleInfo, the
  // number of MODULE-allocated variables, and the metadata of those
  // variables.  For non-module scopes it is empty.
  module_info?
      [flags.scope_type == ScopeType::MODULE_SCOPE]: SourceTextModuleInfo;
  const module_variable_count?
      [flags.scope_type == ScopeType::MODULE_SCOPE]: Smi;
  module_variables[flags.scope_type == ScopeType::MODULE_SCOPE ? module_variable_count : 0]:
      ModuleVariable;
}

extern macro NameToIndexHashTableLookup(
    NameToIndexHashTable, Name): intptr labels NotFound;

macro IndexOfInlinedLocalName(
    scopeInfo: ScopeInfo, name: Name): intptr labels NotFound {
  const count: intptr = Convert<intptr>(scopeInfo.context_local_count);
  for (let i: intptr = 0; i < count; ++i) {
    if (TaggedEqual(name, scopeInfo.context_local_names[i])) {
      return i;
    }
  }
  goto NotFound;
}

// Returns the index of the named local in a ScopeInfo.
// Assumes that the given name is internalized; uses pointer comparisons.
@export
macro IndexOfLocalName(scopeInfo: ScopeInfo, name: Name):
    intptr labels NotFound {
  const count: intptr = Convert<intptr>(scopeInfo.context_local_count);
  if (count < kMaxInlinedLocalNamesSize) {
    return IndexOfInlinedLocalName(scopeInfo, name) otherwise goto NotFound;
  } else {
    return NameToIndexHashTableLookup(
        scopeInfo.context_local_names_hashtable, name) otherwise goto NotFound;
  }
}