// Copyright 2020 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/heap/weak-object-worklists.h"

#include "src/heap/heap-inl.h"
#include "src/heap/heap.h"
#include "src/objects/hash-table.h"
#include "src/objects/heap-object.h"
#include "src/objects/js-function.h"
#include "src/objects/js-weak-refs-inl.h"
#include "src/objects/js-weak-refs.h"
#include "src/objects/shared-function-info.h"
#include "src/objects/transitions.h"

namespace v8 {

namespace internal {

WeakObjects::Local::Local(WeakObjects* weak_objects)
    : WeakObjects::UnusedBase()
#define INIT_LOCAL_WORKLIST(_, name, __) , name##_local(&weak_objects->name)
          WEAK_OBJECT_WORKLISTS(INIT_LOCAL_WORKLIST)
#undef INIT_LOCAL_WORKLIST
{
}

void WeakObjects::Local::Publish() {
#define INVOKE_PUBLISH(_, name, __) name##_local.Publish();
  WEAK_OBJECT_WORKLISTS(INVOKE_PUBLISH)
#undef INVOKE_PUBLISH
}

void WeakObjects::UpdateAfterScavenge() {
#define INVOKE_UPDATE(_, name, Name) Update##Name(name);
  WEAK_OBJECT_WORKLISTS(INVOKE_UPDATE)
#undef INVOKE_UPDATE
}

void WeakObjects::Clear() {
#define INVOKE_CLEAR(_, name, __) name.Clear();
  WEAK_OBJECT_WORKLISTS(INVOKE_CLEAR)
#undef INVOKE_CLEAR
}

// static
void WeakObjects::UpdateTransitionArrays(
    WeakObjectWorklist<TransitionArray>& transition_arrays) {
  DCHECK(!ContainsYoungObjects(transition_arrays));
}

// static
void WeakObjects::UpdateEphemeronHashTables(
    WeakObjectWorklist<EphemeronHashTable>& ephemeron_hash_tables) {
  ephemeron_hash_tables.Update(
      [](EphemeronHashTable slot_in, EphemeronHashTable* slot_out) -> bool {
        EphemeronHashTable forwarded = ForwardingAddress(slot_in);

        if (!forwarded.is_null()) {
          *slot_out = forwarded;
          return true;
        }

        return false;
      });
}

namespace {
bool EphemeronUpdater(Ephemeron slot_in, Ephemeron* slot_out) {
  HeapObject key = slot_in.key;
  HeapObject value = slot_in.value;
  HeapObject forwarded_key = ForwardingAddress(key);
  HeapObject forwarded_value = ForwardingAddress(value);

  if (!forwarded_key.is_null() && !forwarded_value.is_null()) {
    *slot_out = Ephemeron{forwarded_key, forwarded_value};
    return true;
  }

  return false;
}
}  // anonymous namespace

// static
void WeakObjects::UpdateCurrentEphemerons(
    WeakObjectWorklist<Ephemeron>& current_ephemerons) {
  current_ephemerons.Update(EphemeronUpdater);
}

// static
void WeakObjects::UpdateNextEphemerons(
    WeakObjectWorklist<Ephemeron>& next_ephemerons) {
  next_ephemerons.Update(EphemeronUpdater);
}

// static
void WeakObjects::UpdateDiscoveredEphemerons(
    WeakObjectWorklist<Ephemeron>& discovered_ephemerons) {
  discovered_ephemerons.Update(EphemeronUpdater);
}

// static
void WeakObjects::UpdateWeakReferences(
    WeakObjectWorklist<HeapObjectAndSlot>& weak_references) {
  weak_references.Update(
      [](HeapObjectAndSlot slot_in, HeapObjectAndSlot* slot_out) -> bool {
        HeapObject heap_obj = slot_in.first;
        HeapObject forwarded = ForwardingAddress(heap_obj);

        if (!forwarded.is_null()) {
          ptrdiff_t distance_to_slot =
              slot_in.second.address() - slot_in.first.ptr();
          Address new_slot = forwarded.ptr() + distance_to_slot;
          slot_out->first = forwarded;
          slot_out->second = HeapObjectSlot(new_slot);
          return true;
        }

        return false;
      });
}

// static
void WeakObjects::UpdateWeakObjectsInCode(
    WeakObjectWorklist<HeapObjectAndCode>& weak_objects_in_code) {
  weak_objects_in_code.Update(
      [](HeapObjectAndCode slot_in, HeapObjectAndCode* slot_out) -> bool {
        HeapObject heap_obj = slot_in.first;
        HeapObject forwarded = ForwardingAddress(heap_obj);

        if (!forwarded.is_null()) {
          slot_out->first = forwarded;
          slot_out->second = slot_in.second;
          return true;
        }

        return false;
      });
}

// static
void WeakObjects::UpdateJSWeakRefs(
    WeakObjectWorklist<JSWeakRef>& js_weak_refs) {
  js_weak_refs.Update(
      [](JSWeakRef js_weak_ref_in, JSWeakRef* js_weak_ref_out) -> bool {
        JSWeakRef forwarded = ForwardingAddress(js_weak_ref_in);

        if (!forwarded.is_null()) {
          *js_weak_ref_out = forwarded;
          return true;
        }

        return false;
      });
}

// static
void WeakObjects::UpdateWeakCells(WeakObjectWorklist<WeakCell>& weak_cells) {
  // TODO(syg, marja): Support WeakCells in the young generation.
  DCHECK(!ContainsYoungObjects(weak_cells));
}

// static
void WeakObjects::UpdateCodeFlushingCandidates(
    WeakObjectWorklist<SharedFunctionInfo>& code_flushing_candidates) {
  DCHECK(!ContainsYoungObjects(code_flushing_candidates));
}

// static
void WeakObjects::UpdateFlushedJSFunctions(
    WeakObjectWorklist<JSFunction>& flushed_js_functions) {
  flushed_js_functions.Update(
      [](JSFunction slot_in, JSFunction* slot_out) -> bool {
        JSFunction forwarded = ForwardingAddress(slot_in);

        if (!forwarded.is_null()) {
          *slot_out = forwarded;
          return true;
        }

        return false;
      });
}

// static
void WeakObjects::UpdateBaselineFlushingCandidates(
    WeakObjectWorklist<JSFunction>& baseline_flush_candidates) {
  baseline_flush_candidates.Update(
      [](JSFunction slot_in, JSFunction* slot_out) -> bool {
        JSFunction forwarded = ForwardingAddress(slot_in);

        if (!forwarded.is_null()) {
          *slot_out = forwarded;
          return true;
        }

        return false;
      });
}

#ifdef DEBUG
// static
template <typename Type>
bool WeakObjects::ContainsYoungObjects(WeakObjectWorklist<Type>& worklist) {
  bool result = false;
  worklist.Iterate([&result](Type candidate) {
    if (Heap::InYoungGeneration(candidate)) {
      result = true;
    }
  });
  return result;
}
#endif

}  // namespace internal
}  // namespace v8
