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

#ifndef V8_SANDBOX_INDIRECT_POINTER_INL_H_
#define V8_SANDBOX_INDIRECT_POINTER_INL_H_

#include "include/v8-internal.h"
#include "src/base/atomic-utils.h"
#include "src/sandbox/code-pointer-table-inl.h"
#include "src/sandbox/indirect-pointer.h"
#include "src/sandbox/isolate-inl.h"
#include "src/sandbox/trusted-pointer-table-inl.h"

namespace v8 {
namespace internal {

V8_INLINE void InitSelfIndirectPointerField(Address field_address,
                                            IsolateForSandbox isolate,
                                            Tagged<HeapObject> host,
                                            IndirectPointerTag tag) {
#ifdef V8_ENABLE_SANDBOX
  DCHECK_NE(tag, kUnknownIndirectPointerTag);
  // TODO(saelo): in the future, we might want to CHECK here or in
  // AllocateAndInitializeEntry that the host lives in trusted space.

  IndirectPointerHandle handle;
  if (tag == kCodeIndirectPointerTag) {
    CodePointerTable::Space* space =
        isolate.GetCodePointerTableSpaceFor(field_address);
    handle = GetProcessWideCodePointerTable()->AllocateAndInitializeEntry(
        space, host.address(), kNullAddress, kDefaultCodeEntrypointTag);
  } else {
    TrustedPointerTable::Space* space = isolate.GetTrustedPointerTableSpace();
    handle = isolate.GetTrustedPointerTable().AllocateAndInitializeEntry(
        space, host.address(), tag);
  }

  // Use a Release_Store to ensure that the store of the pointer into the table
  // is not reordered after the store of the handle. Otherwise, other threads
  // may access an uninitialized table entry and crash.
  auto location = reinterpret_cast<IndirectPointerHandle*>(field_address);
  base::AsAtomic32::Release_Store(location, handle);
#else
  UNREACHABLE();
#endif
}

namespace {
#ifdef V8_ENABLE_SANDBOX
template <IndirectPointerTag tag>
V8_INLINE Tagged<Object> ResolveTrustedPointerHandle(
    IndirectPointerHandle handle, IsolateForSandbox isolate) {
  const TrustedPointerTable& table = isolate.GetTrustedPointerTable();
  return Tagged<Object>(table.Get(handle));
}

V8_INLINE Tagged<Object> ResolveCodePointerHandle(
    IndirectPointerHandle handle) {
  CodePointerTable* table = GetProcessWideCodePointerTable();
  return Tagged<Object>(table->GetCodeObject(handle));
}
#endif  // V8_ENABLE_SANDBOX
}  // namespace

template <IndirectPointerTag tag>
V8_INLINE Tagged<Object> ReadIndirectPointerField(Address field_address,
                                                  IsolateForSandbox isolate) {
#ifdef V8_ENABLE_SANDBOX
  // Load the indirect pointer handle from the object.
  auto location = reinterpret_cast<IndirectPointerHandle*>(field_address);
  IndirectPointerHandle handle = base::AsAtomic32::Relaxed_Load(location);
  DCHECK_NE(handle, kNullIndirectPointerHandle);

  // Resolve the handle. The tag implies the pointer table to use.
  // Here we generally assume that the load from the table cannot be reordered
  // before the load of the code object pointer due to the data dependency
  // between the two loads and therefore use relaxed memory ordering, but
  // technically we should use memory_order_consume here.
  if constexpr (tag == kUnknownIndirectPointerTag) {
    // In this case we need to check if the handle is a code pointer handle and
    // select the appropriate table based on that.
    if (handle & kCodePointerHandleMarker) {
      return ResolveCodePointerHandle(handle);
    } else {
      // TODO(saelo): once we have type tagging for entries in the trusted
      // pointer table, we could ASSUME that the top bits of the tag match the
      // instance type, which might allow the compiler to optimize subsequent
      // instance type checks.
      return ResolveTrustedPointerHandle<tag>(handle, isolate);
    }
  } else if constexpr (tag == kCodeIndirectPointerTag) {
    return ResolveCodePointerHandle(handle);
  } else {
    return ResolveTrustedPointerHandle<tag>(handle, isolate);
  }
#else
  UNREACHABLE();
#endif
}

template <IndirectPointerTag tag>
V8_INLINE void WriteIndirectPointerField(Address field_address,
                                         Tagged<ExposedTrustedObject> value) {
#ifdef V8_ENABLE_SANDBOX
  static_assert(tag != kIndirectPointerNullTag);
  IndirectPointerHandle handle = value->self_indirect_pointer_handle();
  DCHECK_NE(handle, kNullIndirectPointerHandle);
  auto location = reinterpret_cast<IndirectPointerHandle*>(field_address);
  base::AsAtomic32::Release_Store(location, handle);
#else
  UNREACHABLE();
#endif
}

}  // namespace internal
}  // namespace v8

#endif  // V8_SANDBOX_INDIRECT_POINTER_INL_H_
