/* -*- 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_SymbolType_h
#define vm_SymbolType_h

#include <stdio.h>

#include "gc/Barrier.h"
#include "gc/Tracer.h"
#include "js/AllocPolicy.h"
#include "js/GCHashTable.h"
#include "js/RootingAPI.h"
#include "js/shadow/Symbol.h"  // JS::shadow::Symbol
#include "js/Symbol.h"
#include "js/TypeDecls.h"
#include "vm/StringType.h"

namespace js {
class JS_PUBLIC_API GenericPrinter;
class JSONPrinter;
} /* namespace js */

namespace JS {

class Symbol
    : public js::gc::CellWithTenuredGCPointer<js::gc::TenuredCell, JSAtom> {
  friend class js::gc::CellAllocator;

 public:
  // User description of symbol, stored in the cell header.
  JSAtom* description() const { return headerPtr(); }

 private:
  SymbolCode code_;

  // Each Symbol gets its own hash code so that we don't have to use
  // addresses as hash codes (a security hazard).
  js::HashNumber hash_;

  Symbol(SymbolCode code, js::HashNumber hash, Handle<JSAtom*> desc)
      : CellWithTenuredGCPointer(desc), code_(code), hash_(hash) {}

  Symbol(const Symbol&) = delete;
  void operator=(const Symbol&) = delete;

  static Symbol* newInternal(JSContext* cx, SymbolCode code,
                             js::HashNumber hash, Handle<JSAtom*> description);

  static void staticAsserts() {
    static_assert(uint32_t(SymbolCode::WellKnownAPILimit) ==
                      JS::shadow::Symbol::WellKnownAPILimit,
                  "JS::shadow::Symbol::WellKnownAPILimit must match "
                  "SymbolCode::WellKnownAPILimit");
    static_assert(
        offsetof(Symbol, code_) == offsetof(JS::shadow::Symbol, code_),
        "JS::shadow::Symbol::code_ offset must match SymbolCode::code_");
  }

 public:
  static Symbol* new_(JSContext* cx, SymbolCode code,
                      js::HandleString description);
  static Symbol* newWellKnown(JSContext* cx, SymbolCode code,
                              Handle<js::PropertyName*> description);
  static Symbol* for_(JSContext* cx, js::HandleString description);

  SymbolCode code() const { return code_; }
  js::HashNumber hash() const { return hash_; }

  bool isWellKnownSymbol() const {
    return uint32_t(code_) < WellKnownSymbolLimit;
  }

  // An "interesting symbol" is a well-known symbol, like @@toStringTag,
  // that's often looked up on random objects but is usually not present. We
  // optimize this by setting a flag on the object's BaseShape when such
  // symbol properties are added, so we can optimize lookups on objects that
  // don't have the BaseShape flag.
  bool isInterestingSymbol() const {
    return code_ == SymbolCode::toStringTag ||
           code_ == SymbolCode::toPrimitive ||
           code_ == SymbolCode::isConcatSpreadable;
  }

  // Symbol created for the #PrivateName syntax.
  bool isPrivateName() const { return code_ == SymbolCode::PrivateNameSymbol; }

  static const JS::TraceKind TraceKind = JS::TraceKind::Symbol;

  void traceChildren(JSTracer* trc);

  // Override base class implementation to tell GC about well-known symbols.
  bool isPermanentAndMayBeShared() const { return isWellKnownSymbol(); }

  size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
    return mallocSizeOf(this);
  }

#if defined(DEBUG) || defined(JS_JITSPEW)
  void dump() const;  // Debugger-friendly stderr dump.
  void dump(js::GenericPrinter& out) const;
  void dump(js::JSONPrinter& json) const;

  void dumpFields(js::JSONPrinter& json) const;
  void dumpStringContent(js::GenericPrinter& out) const;
  void dumpPropertyName(js::GenericPrinter& out) const;
#endif

  static constexpr size_t offsetOfHash() { return offsetof(Symbol, hash_); }
};

} /* namespace JS */

namespace js {

/* Hash policy used by the SymbolRegistry. */
struct HashSymbolsByDescription {
  using Key = JS::Symbol*;
  using Lookup = JSAtom*;

  static HashNumber hash(Lookup l) { return HashNumber(l->hash()); }
  static bool match(Key sym, Lookup l) { return sym->description() == l; }
};

/*
 * [SMDOC] Symbol.for() registry (ES6 GlobalSymbolRegistry)
 *
 * The runtime-wide symbol registry, used to implement Symbol.for().
 *
 * ES6 draft rev 25 (2014 May 22) calls this the GlobalSymbolRegistry List. In
 * our implementation, it is not global. There is one per JSRuntime. The
 * symbols in the symbol registry, like all symbols, are allocated in the atoms
 * compartment and can be directly referenced from any compartment. They are
 * never shared across runtimes.
 *
 * The memory management strategy here is modeled after js::AtomSet. It's like
 * a WeakSet. The registry itself does not keep any symbols alive; when a
 * symbol in the registry is collected, the registry entry is removed. No GC
 * nondeterminism is exposed to scripts, because there is no API for
 * enumerating the symbol registry, querying its size, etc.
 */
class SymbolRegistry
    : public GCHashSet<WeakHeapPtr<JS::Symbol*>, HashSymbolsByDescription,
                       SystemAllocPolicy> {
 public:
  SymbolRegistry() = default;
};

// ES6 rev 27 (2014 Aug 24) 19.4.3.3
bool SymbolDescriptiveString(JSContext* cx, JS::Symbol* sym,
                             JS::MutableHandleValue result);

} /* namespace js */

#endif /* vm_SymbolType_h */
