/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: set ts=8 sts=2 et sw=2 tw=80:
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef vm_FunctionFlags_h
#define vm_FunctionFlags_h

#include "mozilla/Assertions.h"  // MOZ_ASSERT, MOZ_ASSERT_IF
#include "mozilla/Attributes.h"  // MOZ_IMPLICIT

#include <stdint.h>  // uint8_t, uint16_t

#include "jstypes.h"  // JS_PUBLIC_API

class JS_PUBLIC_API JSAtom;

namespace js {

class FunctionFlags {
 public:
  // Syntactic characteristics of a function.
  enum FunctionKind : uint8_t {
    // Regular function that doesn't match any of the other kinds.
    //
    // This kind is used by the following scipted functions:
    //   * FunctionDeclaration
    //   * FunctionExpression
    //   * Function created from Function() call or its variants
    //
    // Also all native functions excluding AsmJS and Wasm use this kind.
    NormalFunction = 0,

    // ES6 '(args) => body' syntax.
    // This kind is used only by scripted function.
    Arrow,

    // ES6 MethodDefinition syntax.
    // This kind is used only by scripted function.
    Method,

    // Class constructor syntax, or default constructor.
    // This kind is used only by scripted function and default constructor.
    //
    // WARNING: This is independent from Flags::CONSTRUCTOR.
    ClassConstructor,

    // Getter and setter syntax in objects or classes, or
    // native getter and setter created from JSPropertySpec.
    // This kind is used both by scripted functions and native functions.
    Getter,
    Setter,

    // An asm.js module or exported function.
    //
    // This kind is used only by scripted function, and used only when the
    // asm.js module is created.
    //
    // "use asm" directive itself doesn't necessarily imply this kind.
    // e.g. arrow function with "use asm" becomes Arrow kind,
    //
    // See EstablishPreconditions in js/src/wasm/AsmJS.cpp
    AsmJS,

    // An exported WebAssembly function.
    Wasm,

    FunctionKindLimit
  };

  enum Flags : uint16_t {
    // FunctionKind enum value.
    FUNCTION_KIND_SHIFT = 0,
    FUNCTION_KIND_MASK = 0x0007,

    // The AllocKind used was FunctionExtended and extra slots were allocated.
    // These slots may be used by the engine or the embedding so care must be
    // taken to avoid conflicts.
    //
    // This flag is used both by scripted functions and native functions.
    EXTENDED = 1 << 3,

    // Set if function is a self-hosted builtin or intrinsic. An 'intrinsic'
    // here means a native function used inside self-hosted code. In general, a
    // self-hosted function should appear to script as though it were a native
    // builtin.
    SELF_HOSTED = 1 << 4,

    // An interpreted function has or may-have bytecode and an environment. Only
    // one of these flags may be used at a time. As a memory optimization, the
    // SELFHOSTLAZY flag indicates there is no js::BaseScript at all and we must
    // clone from the self-hosted realm in order to get bytecode.
    BASESCRIPT = 1 << 5,
    SELFHOSTLAZY = 1 << 6,

    // Function may be called as a constructor. This corresponds in the spec as
    // having a [[Construct]] internal method.
    //
    // e.g. FunctionDeclaration has this flag, but GeneratorDeclaration doesn't
    //      have this flag.
    //
    // This flag is used both by scripted functions and native functions.
    //
    // WARNING: This is independent from FunctionKind::ClassConstructor.
    CONSTRUCTOR = 1 << 7,

    // Function is either getter or setter, with "get " or "set " prefix,
    // but JSFunction::AtomSlot contains unprefixed name, and the function name
    // is lazily constructed on the first access.
    LAZY_ACCESSOR_NAME = 1 << 8,

    // Function comes from a FunctionExpression, ArrowFunction, or Function()
    // call (not a FunctionDeclaration).
    //
    // This flag is used only by scripted functions and AsmJS.
    LAMBDA = 1 << 9,

    // The WASM function has a JIT entry which emulates the
    // js::BaseScript::jitCodeRaw mechanism.
    WASM_JIT_ENTRY = 1 << 10,

    // Function had no explicit name, but a name was set by SetFunctionName at
    // compile time or SetFunctionName at runtime.
    //
    // This flag can be used both by scripted functions and native functions.
    HAS_INFERRED_NAME = 1 << 11,

    // Function had no explicit name, but a name was guessed for it anyway.
    //
    // This flag is used only by scripted function.
    HAS_GUESSED_ATOM = 1 << 12,

    // The 'length' or 'name property has been resolved. See fun_resolve.
    //
    // These flags are used both by scripted functions and native functions.
    RESOLVED_NAME = 1 << 13,
    RESOLVED_LENGTH = 1 << 14,

    // This function is kept only for skipping it over during delazification.
    //
    // This function is inside arrow function's parameter expression, and
    // parsed twice, once before finding "=>" token, and once after finding
    // "=>" and rewinding to the beginning of the parameters.
    // ScriptStencil is created for both case, and the first one is kept only
    // for delazification, to make sure delazification sees the same sequence
    // of inner function to skip over.
    //
    // We call the first one "ghost".
    // It should be kept lazy, and shouldn't be exposed to debugger.
    //
    // This flag is used only by scripted functions.
    GHOST_FUNCTION = 1 << 15,

    // Shifted form of FunctionKinds.
    NORMAL_KIND = NormalFunction << FUNCTION_KIND_SHIFT,
    ASMJS_KIND = AsmJS << FUNCTION_KIND_SHIFT,
    WASM_KIND = Wasm << FUNCTION_KIND_SHIFT,
    ARROW_KIND = Arrow << FUNCTION_KIND_SHIFT,
    METHOD_KIND = Method << FUNCTION_KIND_SHIFT,
    CLASSCONSTRUCTOR_KIND = ClassConstructor << FUNCTION_KIND_SHIFT,
    GETTER_KIND = Getter << FUNCTION_KIND_SHIFT,
    SETTER_KIND = Setter << FUNCTION_KIND_SHIFT,

    // Derived Flags combinations to use when creating functions.
    NATIVE_FUN = NORMAL_KIND,
    NATIVE_CTOR = CONSTRUCTOR | NORMAL_KIND,
    NATIVE_GETTER_WITH_LAZY_NAME = LAZY_ACCESSOR_NAME | GETTER_KIND,
    NATIVE_SETTER_WITH_LAZY_NAME = LAZY_ACCESSOR_NAME | SETTER_KIND,
    ASMJS_CTOR = CONSTRUCTOR | ASMJS_KIND,
    ASMJS_LAMBDA_CTOR = CONSTRUCTOR | LAMBDA | ASMJS_KIND,
    WASM = WASM_KIND,
    INTERPRETED_NORMAL = BASESCRIPT | CONSTRUCTOR | NORMAL_KIND,
    INTERPRETED_CLASS_CTOR = BASESCRIPT | CONSTRUCTOR | CLASSCONSTRUCTOR_KIND,
    INTERPRETED_GENERATOR_OR_ASYNC = BASESCRIPT | NORMAL_KIND,
    INTERPRETED_LAMBDA = BASESCRIPT | LAMBDA | CONSTRUCTOR | NORMAL_KIND,
    INTERPRETED_LAMBDA_ARROW = BASESCRIPT | LAMBDA | ARROW_KIND,
    INTERPRETED_LAMBDA_GENERATOR_OR_ASYNC = BASESCRIPT | LAMBDA | NORMAL_KIND,
    INTERPRETED_GETTER = BASESCRIPT | GETTER_KIND,
    INTERPRETED_SETTER = BASESCRIPT | SETTER_KIND,
    INTERPRETED_METHOD = BASESCRIPT | METHOD_KIND,

    // Flags that XDR ignores. See also: js::BaseScript::MutableFlags.
    MUTABLE_FLAGS = RESOLVED_NAME | RESOLVED_LENGTH,

    // Flags preserved when cloning a function.
    STABLE_ACROSS_CLONES =
        CONSTRUCTOR | LAMBDA | SELF_HOSTED | FUNCTION_KIND_MASK | GHOST_FUNCTION
  };

  uint16_t flags_;

 public:
  FunctionFlags() : flags_() {
    static_assert(sizeof(FunctionFlags) == sizeof(flags_),
                  "No extra members allowed is it'll grow JSFunction");
    static_assert(offsetof(FunctionFlags, flags_) == 0,
                  "Required for JIT flag access");
  }

  explicit FunctionFlags(uint16_t flags) : flags_(flags) {}
  MOZ_IMPLICIT FunctionFlags(Flags f) : flags_(f) {}

  static_assert(((FunctionKindLimit - 1) << FUNCTION_KIND_SHIFT) <=
                    FUNCTION_KIND_MASK,
                "FunctionKind doesn't fit into flags_");

  uint16_t toRaw() const { return flags_; }

  uint16_t stableAcrossClones() const { return flags_ & STABLE_ACROSS_CLONES; }

  // For flag combinations the type is int.
  bool hasFlags(uint16_t flags) const { return flags_ & flags; }
  FunctionFlags& setFlags(uint16_t flags) {
    flags_ |= flags;
    return *this;
  }
  FunctionFlags& clearFlags(uint16_t flags) {
    flags_ &= ~flags;
    return *this;
  }
  FunctionFlags& setFlags(uint16_t flags, bool set) {
    if (set) {
      setFlags(flags);
    } else {
      clearFlags(flags);
    }
    return *this;
  }

  FunctionKind kind() const {
    return static_cast<FunctionKind>((flags_ & FUNCTION_KIND_MASK) >>
                                     FUNCTION_KIND_SHIFT);
  }

#ifdef DEBUG
  void assertFunctionKindIntegrity() {
    switch (kind()) {
      case FunctionKind::NormalFunction:
        MOZ_ASSERT(!hasFlags(LAZY_ACCESSOR_NAME));
        MOZ_ASSERT(!hasFlags(WASM_JIT_ENTRY));
        break;

      case FunctionKind::Arrow:
        MOZ_ASSERT(hasFlags(BASESCRIPT) || hasFlags(SELFHOSTLAZY));
        MOZ_ASSERT(!hasFlags(CONSTRUCTOR));
        MOZ_ASSERT(!hasFlags(LAZY_ACCESSOR_NAME));
        MOZ_ASSERT(hasFlags(LAMBDA));
        MOZ_ASSERT(!hasFlags(WASM_JIT_ENTRY));
        break;
      case FunctionKind::Method:
        MOZ_ASSERT(hasFlags(BASESCRIPT) || hasFlags(SELFHOSTLAZY));
        MOZ_ASSERT(!hasFlags(CONSTRUCTOR));
        MOZ_ASSERT(!hasFlags(LAZY_ACCESSOR_NAME));
        MOZ_ASSERT(!hasFlags(LAMBDA));
        MOZ_ASSERT(!hasFlags(WASM_JIT_ENTRY));
        break;
      case FunctionKind::ClassConstructor:
        MOZ_ASSERT(hasFlags(BASESCRIPT) || hasFlags(SELFHOSTLAZY));
        MOZ_ASSERT(hasFlags(CONSTRUCTOR));
        MOZ_ASSERT(!hasFlags(LAZY_ACCESSOR_NAME));
        MOZ_ASSERT(!hasFlags(LAMBDA));
        MOZ_ASSERT(!hasFlags(WASM_JIT_ENTRY));
        break;
      case FunctionKind::Getter:
        MOZ_ASSERT(!hasFlags(CONSTRUCTOR));
        MOZ_ASSERT(!hasFlags(LAMBDA));
        MOZ_ASSERT(!hasFlags(WASM_JIT_ENTRY));
        break;
      case FunctionKind::Setter:
        MOZ_ASSERT(!hasFlags(CONSTRUCTOR));
        MOZ_ASSERT(!hasFlags(LAMBDA));
        MOZ_ASSERT(!hasFlags(WASM_JIT_ENTRY));
        break;

      case FunctionKind::AsmJS:
        MOZ_ASSERT(!hasFlags(BASESCRIPT));
        MOZ_ASSERT(!hasFlags(SELFHOSTLAZY));
        MOZ_ASSERT(!hasFlags(LAZY_ACCESSOR_NAME));
        MOZ_ASSERT(!hasFlags(WASM_JIT_ENTRY));
        break;
      case FunctionKind::Wasm:
        MOZ_ASSERT(!hasFlags(BASESCRIPT));
        MOZ_ASSERT(!hasFlags(SELFHOSTLAZY));
        MOZ_ASSERT(!hasFlags(CONSTRUCTOR));
        MOZ_ASSERT(!hasFlags(LAZY_ACCESSOR_NAME));
        MOZ_ASSERT(!hasFlags(LAMBDA));
        break;
      default:
        break;
    }
  }
#endif

  /* A function can be classified as either native (C++) or interpreted (JS): */
  bool isInterpreted() const {
    return hasFlags(BASESCRIPT) || hasFlags(SELFHOSTLAZY);
  }
  bool isNativeFun() const { return !isInterpreted(); }

  bool isConstructor() const { return hasFlags(CONSTRUCTOR); }

  bool isNonBuiltinConstructor() const {
    // Note: keep this in sync with branchIfNotFunctionIsNonBuiltinCtor in
    // MacroAssembler.cpp.
    return hasFlags(BASESCRIPT) && hasFlags(CONSTRUCTOR) &&
           !hasFlags(SELF_HOSTED);
  }

  /* Possible attributes of a native function: */
  bool isAsmJSNative() const {
    MOZ_ASSERT_IF(kind() == AsmJS, isNativeFun());
    return kind() == AsmJS;
  }
  bool isWasm() const {
    MOZ_ASSERT_IF(kind() == Wasm, isNativeFun());
    return kind() == Wasm;
  }
  bool isWasmWithJitEntry() const {
    MOZ_ASSERT_IF(hasFlags(WASM_JIT_ENTRY), isWasm());
    return hasFlags(WASM_JIT_ENTRY);
  }
  bool isNativeWithoutJitEntry() const {
    MOZ_ASSERT_IF(!hasJitEntry(), isNativeFun());
    return !hasJitEntry();
  }
  bool isBuiltinNative() const {
    return isNativeFun() && !isAsmJSNative() && !isWasm();
  }
  bool hasJitEntry() const {
    return hasBaseScript() || hasSelfHostedLazyScript() || isWasmWithJitEntry();
  }

  /* Possible attributes of an interpreted function: */
  bool hasInferredName() const { return hasFlags(HAS_INFERRED_NAME); }
  bool hasGuessedAtom() const { return hasFlags(HAS_GUESSED_ATOM); }
  bool isLambda() const { return hasFlags(LAMBDA); }

  bool isNamedLambda(bool hasName) const {
    return hasName && isLambda() && !hasInferredName() && !hasGuessedAtom();
  }

  // These methods determine which of the u.scripted.s union arms are active.
  // For live JSFunctions the pointer values will always be non-null, but due
  // to partial initialization the GC (and other features that scan the heap
  // directly) may still return a null pointer.
  bool hasBaseScript() const { return hasFlags(BASESCRIPT); }
  bool hasSelfHostedLazyScript() const { return hasFlags(SELFHOSTLAZY); }

  // Arrow functions store their lexical new.target in the first extended slot.
  bool isArrow() const { return kind() == Arrow; }
  // Every class-constructor is also a method.
  bool isMethod() const {
    return kind() == Method || kind() == ClassConstructor;
  }
  bool isClassConstructor() const { return kind() == ClassConstructor; }

  bool isGetter() const { return kind() == Getter; }
  bool isSetter() const { return kind() == Setter; }

  bool isAccessorWithLazyName() const { return hasFlags(LAZY_ACCESSOR_NAME); }

  bool allowSuperProperty() const {
    return isMethod() || isGetter() || isSetter();
  }

  bool hasResolvedLength() const { return hasFlags(RESOLVED_LENGTH); }
  bool hasResolvedName() const { return hasFlags(RESOLVED_NAME); }

  bool isSelfHostedOrIntrinsic() const { return hasFlags(SELF_HOSTED); }
  bool isSelfHostedBuiltin() const {
    return isSelfHostedOrIntrinsic() && !isNativeFun();
  }
  bool isIntrinsic() const {
    return isSelfHostedOrIntrinsic() && isNativeFun();
  }

  FunctionFlags& setKind(FunctionKind kind) {
    this->flags_ &= ~FUNCTION_KIND_MASK;
    this->flags_ |= static_cast<uint16_t>(kind) << FUNCTION_KIND_SHIFT;
    return *this;
  }

  // Make the function constructible.
  FunctionFlags& setIsConstructor() {
    MOZ_ASSERT(!isConstructor());
    MOZ_ASSERT(isSelfHostedBuiltin());
    return setFlags(CONSTRUCTOR);
  }

  FunctionFlags& setIsSelfHostedBuiltin() {
    MOZ_ASSERT(isInterpreted());
    MOZ_ASSERT(!isSelfHostedBuiltin());
    setFlags(SELF_HOSTED);
    // Self-hosted functions should not be constructable.
    return clearFlags(CONSTRUCTOR);
  }
  FunctionFlags& setIsIntrinsic() {
    MOZ_ASSERT(isNativeFun());
    MOZ_ASSERT(!isIntrinsic());
    return setFlags(SELF_HOSTED);
  }

  FunctionFlags& setResolvedLength() { return setFlags(RESOLVED_LENGTH); }
  FunctionFlags& setResolvedName() { return setFlags(RESOLVED_NAME); }

  FunctionFlags& setInferredName() { return setFlags(HAS_INFERRED_NAME); }

  FunctionFlags& setGuessedAtom() { return setFlags(HAS_GUESSED_ATOM); }

  FunctionFlags& setSelfHostedLazy() { return setFlags(SELFHOSTLAZY); }
  FunctionFlags& clearSelfHostedLazy() { return clearFlags(SELFHOSTLAZY); }
  FunctionFlags& setBaseScript() { return setFlags(BASESCRIPT); }
  FunctionFlags& clearBaseScript() { return clearFlags(BASESCRIPT); }

  FunctionFlags& clearLazyAccessorName() {
    return clearFlags(LAZY_ACCESSOR_NAME);
  }

  FunctionFlags& setWasmJitEntry() { return setFlags(WASM_JIT_ENTRY); }

  bool isExtended() const { return hasFlags(EXTENDED); }
  FunctionFlags& setIsExtended() { return setFlags(EXTENDED); }

  bool isNativeConstructor() const { return hasFlags(NATIVE_CTOR); }

  FunctionFlags& setIsGhost() { return setFlags(GHOST_FUNCTION); }
  bool isGhost() const { return hasFlags(GHOST_FUNCTION); }

  static uint16_t HasJitEntryFlags(bool isConstructing) {
    uint16_t flags = BASESCRIPT | SELFHOSTLAZY;
    if (!isConstructing) {
      flags |= WASM_JIT_ENTRY;
    }
    return flags;
  }

  static FunctionFlags clearMutableflags(FunctionFlags flags) {
    return FunctionFlags(flags.toRaw() & ~FunctionFlags::MUTABLE_FLAGS);
  }
};

} /* namespace js */

#endif /* vm_FunctionFlags_h */