/* -*- 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_GetterSetter_h
#define vm_GetterSetter_h

#include "gc/Barrier.h"  // js::GCPtr<JSObject*>
#include "gc/Cell.h"     // js::gc::TenuredCellWithGCPointer

#include "js/TypeDecls.h"  // JS::HandleObject
#include "js/UbiNode.h"    // JS::ubi::TracerConcrete

namespace js {

// [SMDOC] Getter/Setter Properties
//
// Getter/setter properties are implemented similar to plain data properties:
// the shape contains the property's key, attributes, and slot number, but the
// getter/setter objects are stored separately as part of the object.
//
// To simplify the NativeObject and Shape code, a single slot is allocated for
// each getter/setter property (again similar to data properties). This slot
// contains a PrivateGCThingValue pointing to a js::GetterSetter instance.
//
// js::GetterSetter
// ================
// js::GetterSetter is an immutable type that stores the getter/setter objects.
// Because accessor properties can be defined with only a getter or only a
// setter, a GetterSetter's objects can be nullptr.
//
// JIT/IC Guards
// =============
// An object's shape implies a certain property is an accessor, but it does not
// imply the identity of the getter/setter objects. This means IC code needs to
// guard on the slot value (the GetterSetter*) when optimizing a call to a
// particular getter/setter function.
//
// See EmitGuardGetterSetterSlot in jit/CacheIR.cpp.
//
// HadGetterSetterChange Optimization
// ==================================
// Some getters and setters defined on the prototype chain are very hot, for
// example the 'length' getter for typed arrays. To avoid the GetterSetter guard
// in the common case, when attaching a stub for a known 'holder' object, we
// use the HadGetterSetterChange object flag.
//
// When this flag is not set, the object is guaranteed to get a different shape
// when an accessor property is either deleted or mutated, because when that
// happens the HadGetterSetterChange will be set which triggers a shape change.
//
// This means CacheIR does not have to guard on the GetterSetter slot for
// accessors on the prototype chain until the first time an accessor property is
// mutated or deleted.
class GetterSetter : public gc::TenuredCellWithGCPointer<JSObject> {
  friend class gc::CellAllocator;

 public:
  // Getter object, stored in the cell header.
  JSObject* getter() const { return headerPtr(); }

  GCPtr<JSObject*> setter_;

#ifndef JS_64BIT
  // Ensure size >= MinCellSize on 32-bit platforms.
  uint64_t padding_ = 0;
#endif

 private:
  GetterSetter(HandleObject getter, HandleObject setter);

 public:
  static GetterSetter* create(JSContext* cx, HandleObject getter,
                              HandleObject setter);

  JSObject* setter() const { return setter_; }

  static const JS::TraceKind TraceKind = JS::TraceKind::GetterSetter;

  static constexpr size_t offsetOfGetter() { return offsetOfHeaderPtr(); }

  void traceChildren(JSTracer* trc);
};

}  // namespace js

// JS::ubi::Nodes can point to GetterSetters; they're js::gc::Cell instances
// with no associated compartment.
namespace JS {
namespace ubi {

template <>
class Concrete<js::GetterSetter> : TracerConcrete<js::GetterSetter> {
 protected:
  explicit Concrete(js::GetterSetter* ptr)
      : TracerConcrete<js::GetterSetter>(ptr) {}

 public:
  static void construct(void* storage, js::GetterSetter* ptr) {
    new (storage) Concrete(ptr);
  }

  Size size(mozilla::MallocSizeOf mallocSizeOf) const override;

  const char16_t* typeName() const override { return concreteTypeName; }
  static const char16_t concreteTypeName[];
};

}  // namespace ubi
}  // namespace JS

#endif  // vm_GetterSetter_h
