//===-- xray_fdr_logging.cpp -----------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file is a part of XRay, a dynamic runtime instrumentation system.
//
// Here we implement the Flight Data Recorder mode for XRay, where we use
// compact structures to store records in memory as well as when writing out the
// data to files.
//
//===----------------------------------------------------------------------===//
#include "xray_fdr_logging.h"
#include <cassert>
#include <cstddef>
#include <errno.h>
#include <limits>
#include <memory>
#include <pthread.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>

#include "sanitizer_common/sanitizer_allocator_internal.h"
#include "sanitizer_common/sanitizer_atomic.h"
#include "sanitizer_common/sanitizer_common.h"
#include "xray/xray_interface.h"
#include "xray/xray_records.h"
#include "xray_allocator.h"
#include "xray_buffer_queue.h"
#include "xray_defs.h"
#include "xray_fdr_controller.h"
#include "xray_fdr_flags.h"
#include "xray_fdr_log_writer.h"
#include "xray_flags.h"
#include "xray_recursion_guard.h"
#include "xray_tsc.h"
#include "xray_utils.h"

namespace __xray {

static atomic_sint32_t LoggingStatus = {
    XRayLogInitStatus::XRAY_LOG_UNINITIALIZED};

namespace {

// Group together thread-local-data in a struct, then hide it behind a function
// call so that it can be initialized on first use instead of as a global. We
// force the alignment to 64-bytes for x86 cache line alignment, as this
// structure is used in the hot path of implementation.
struct XRAY_TLS_ALIGNAS(64) ThreadLocalData {
  BufferQueue::Buffer Buffer{};
  BufferQueue *BQ = nullptr;

  using LogWriterStorage =
      typename std::aligned_storage<sizeof(FDRLogWriter),
                                    alignof(FDRLogWriter)>::type;

  LogWriterStorage LWStorage;
  FDRLogWriter *Writer = nullptr;

  using ControllerStorage =
      typename std::aligned_storage<sizeof(FDRController<>),
                                    alignof(FDRController<>)>::type;
  ControllerStorage CStorage;
  FDRController<> *Controller = nullptr;
};

} // namespace

static_assert(std::is_trivially_destructible<ThreadLocalData>::value,
              "ThreadLocalData must be trivially destructible");

// Use a global pthread key to identify thread-local data for logging.
static pthread_key_t Key;

// Global BufferQueue.
static std::aligned_storage<sizeof(BufferQueue)>::type BufferQueueStorage;
static BufferQueue *BQ = nullptr;

// Global thresholds for function durations.
static atomic_uint64_t ThresholdTicks{0};

// Global for ticks per second.
static atomic_uint64_t TicksPerSec{0};

static atomic_sint32_t LogFlushStatus = {
    XRayLogFlushStatus::XRAY_LOG_NOT_FLUSHING};

// This function will initialize the thread-local data structure used by the FDR
// logging implementation and return a reference to it. The implementation
// details require a bit of care to maintain.
//
// First, some requirements on the implementation in general:
//
//   - XRay handlers should not call any memory allocation routines that may
//     delegate to an instrumented implementation. This means functions like
//     malloc() and free() should not be called while instrumenting.
//
//   - We would like to use some thread-local data initialized on first-use of
//     the XRay instrumentation. These allow us to implement unsynchronized
//     routines that access resources associated with the thread.
//
// The implementation here uses a few mechanisms that allow us to provide both
// the requirements listed above. We do this by:
//
//   1. Using a thread-local aligned storage buffer for representing the
//      ThreadLocalData struct. This data will be uninitialized memory by
//      design.
//
//   2. Not requiring a thread exit handler/implementation, keeping the
//      thread-local as purely a collection of references/data that do not
//      require cleanup.
//
// We're doing this to avoid using a `thread_local` object that has a
// non-trivial destructor, because the C++ runtime might call std::malloc(...)
// to register calls to destructors. Deadlocks may arise when, for example, an
// externally provided malloc implementation is XRay instrumented, and
// initializing the thread-locals involves calling into malloc. A malloc
// implementation that does global synchronization might be holding a lock for a
// critical section, calling a function that might be XRay instrumented (and
// thus in turn calling into malloc by virtue of registration of the
// thread_local's destructor).
#if XRAY_HAS_TLS_ALIGNAS
static_assert(alignof(ThreadLocalData) >= 64,
              "ThreadLocalData must be cache line aligned.");
#endif
static ThreadLocalData &getThreadLocalData() {
  thread_local typename std::aligned_storage<
      sizeof(ThreadLocalData), alignof(ThreadLocalData)>::type TLDStorage{};

  if (pthread_getspecific(Key) == NULL) {
    new (reinterpret_cast<ThreadLocalData *>(&TLDStorage)) ThreadLocalData{};
    pthread_setspecific(Key, &TLDStorage);
  }

  return *reinterpret_cast<ThreadLocalData *>(&TLDStorage);
}

static XRayFileHeader &fdrCommonHeaderInfo() {
  alignas(XRayFileHeader) static std::byte HStorage[sizeof(XRayFileHeader)];
  static pthread_once_t OnceInit = PTHREAD_ONCE_INIT;
  static bool TSCSupported = true;
  static uint64_t CycleFrequency = NanosecondsPerSecond;
  pthread_once(
      &OnceInit, +[] {
        XRayFileHeader &H = reinterpret_cast<XRayFileHeader &>(HStorage);
        // Version 2 of the log writes the extents of the buffer, instead of
        // relying on an end-of-buffer record.
        // Version 3 includes PID metadata record.
        // Version 4 includes CPU data in the custom event records.
        // Version 5 uses relative deltas for custom and typed event records,
        // and removes the CPU data in custom event records (similar to how
        // function records use deltas instead of full TSCs and rely on other
        // metadata records for TSC wraparound and CPU migration).
        H.Version = 5;
        H.Type = FileTypes::FDR_LOG;

        // Test for required CPU features and cache the cycle frequency
        TSCSupported = probeRequiredCPUFeatures();
        if (TSCSupported)
          CycleFrequency = getTSCFrequency();
        H.CycleFrequency = CycleFrequency;

        // FIXME: Actually check whether we have 'constant_tsc' and
        // 'nonstop_tsc' before setting the values in the header.
        H.ConstantTSC = 1;
        H.NonstopTSC = 1;
      });
  return reinterpret_cast<XRayFileHeader &>(HStorage);
}

// This is the iterator implementation, which knows how to handle FDR-mode
// specific buffers. This is used as an implementation of the iterator function
// needed by __xray_set_buffer_iterator(...). It maintains a global state of the
// buffer iteration for the currently installed FDR mode buffers. In particular:
//
//   - If the argument represents the initial state of XRayBuffer ({nullptr, 0})
//     then the iterator returns the header information.
//   - If the argument represents the header information ({address of header
//     info, size of the header info}) then it returns the first FDR buffer's
//     address and extents.
//   - It will keep returning the next buffer and extents as there are more
//     buffers to process. When the input represents the last buffer, it will
//     return the initial state to signal completion ({nullptr, 0}).
//
// See xray/xray_log_interface.h for more details on the requirements for the
// implementations of __xray_set_buffer_iterator(...) and
// __xray_log_process_buffers(...).
XRayBuffer fdrIterator(const XRayBuffer B) {
  DCHECK(internal_strcmp(__xray_log_get_current_mode(), "xray-fdr") == 0);
  DCHECK(BQ->finalizing());

  if (BQ == nullptr || !BQ->finalizing()) {
    if (Verbosity())
      Report(
          "XRay FDR: Failed global buffer queue is null or not finalizing!\n");
    return {nullptr, 0};
  }

  // We use a global scratch-pad for the header information, which only gets
  // initialized the first time this function is called. We'll update one part
  // of this information with some relevant data (in particular the number of
  // buffers to expect).
  alignas(
      XRayFileHeader) static std::byte HeaderStorage[sizeof(XRayFileHeader)];
  static pthread_once_t HeaderOnce = PTHREAD_ONCE_INIT;
  pthread_once(
      &HeaderOnce, +[] {
        reinterpret_cast<XRayFileHeader &>(HeaderStorage) =
            fdrCommonHeaderInfo();
      });

  // We use a convenience alias for code referring to Header from here on out.
  auto &Header = reinterpret_cast<XRayFileHeader &>(HeaderStorage);
  if (B.Data == nullptr && B.Size == 0) {
    Header.FdrData = FdrAdditionalHeaderData{BQ->ConfiguredBufferSize()};
    return XRayBuffer{static_cast<void *>(&Header), sizeof(Header)};
  }

  static BufferQueue::const_iterator It{};
  static BufferQueue::const_iterator End{};
  static uint8_t *CurrentBuffer{nullptr};
  static size_t SerializedBufferSize = 0;
  if (B.Data == static_cast<void *>(&Header) && B.Size == sizeof(Header)) {
    // From this point on, we provide raw access to the raw buffer we're getting
    // from the BufferQueue. We're relying on the iterators from the current
    // Buffer queue.
    It = BQ->cbegin();
    End = BQ->cend();
  }

  if (CurrentBuffer != nullptr) {
    deallocateBuffer(CurrentBuffer, SerializedBufferSize);
    CurrentBuffer = nullptr;
  }

  if (It == End)
    return {nullptr, 0};

  // Set up the current buffer to contain the extents like we would when writing
  // out to disk. The difference here would be that we still write "empty"
  // buffers, or at least go through the iterators faithfully to let the
  // handlers see the empty buffers in the queue.
  //
  // We need this atomic fence here to ensure that writes happening to the
  // buffer have been committed before we load the extents atomically. Because
  // the buffer is not explicitly synchronised across threads, we rely on the
  // fence ordering to ensure that writes we expect to have been completed
  // before the fence are fully committed before we read the extents.
  atomic_thread_fence(memory_order_acquire);
  auto BufferSize = atomic_load(It->Extents, memory_order_acquire);
  SerializedBufferSize = BufferSize + sizeof(MetadataRecord);
  CurrentBuffer = allocateBuffer(SerializedBufferSize);
  if (CurrentBuffer == nullptr)
    return {nullptr, 0};

  // Write out the extents as a Metadata Record into the CurrentBuffer.
  MetadataRecord ExtentsRecord;
  ExtentsRecord.Type = uint8_t(RecordType::Metadata);
  ExtentsRecord.RecordKind =
      uint8_t(MetadataRecord::RecordKinds::BufferExtents);
  internal_memcpy(ExtentsRecord.Data, &BufferSize, sizeof(BufferSize));
  auto AfterExtents =
      static_cast<char *>(internal_memcpy(CurrentBuffer, &ExtentsRecord,
                                          sizeof(MetadataRecord))) +
      sizeof(MetadataRecord);
  internal_memcpy(AfterExtents, It->Data, BufferSize);

  XRayBuffer Result;
  Result.Data = CurrentBuffer;
  Result.Size = SerializedBufferSize;
  ++It;
  return Result;
}

// Must finalize before flushing.
XRayLogFlushStatus fdrLoggingFlush() XRAY_NEVER_INSTRUMENT {
  if (atomic_load(&LoggingStatus, memory_order_acquire) !=
      XRayLogInitStatus::XRAY_LOG_FINALIZED) {
    if (Verbosity())
      Report("Not flushing log, implementation is not finalized.\n");
    return XRayLogFlushStatus::XRAY_LOG_NOT_FLUSHING;
  }

  if (atomic_exchange(&LogFlushStatus, XRayLogFlushStatus::XRAY_LOG_FLUSHING,
                      memory_order_release) ==
      XRayLogFlushStatus::XRAY_LOG_FLUSHING) {
    if (Verbosity())
      Report("Not flushing log, implementation is still flushing.\n");
    return XRayLogFlushStatus::XRAY_LOG_NOT_FLUSHING;
  }

  if (BQ == nullptr) {
    if (Verbosity())
      Report("Cannot flush when global buffer queue is null.\n");
    return XRayLogFlushStatus::XRAY_LOG_NOT_FLUSHING;
  }

  // We wait a number of milliseconds to allow threads to see that we've
  // finalised before attempting to flush the log.
  SleepForMillis(fdrFlags()->grace_period_ms);

  // At this point, we're going to uninstall the iterator implementation, before
  // we decide to do anything further with the global buffer queue.
  __xray_log_remove_buffer_iterator();

  // Once flushed, we should set the global status of the logging implementation
  // to "uninitialized" to allow for FDR-logging multiple runs.
  auto ResetToUnitialized = at_scope_exit([] {
    atomic_store(&LoggingStatus, XRayLogInitStatus::XRAY_LOG_UNINITIALIZED,
                 memory_order_release);
  });

  auto CleanupBuffers = at_scope_exit([] {
    auto &TLD = getThreadLocalData();
    if (TLD.Controller != nullptr)
      TLD.Controller->flush();
  });

  if (fdrFlags()->no_file_flush) {
    if (Verbosity())
      Report("XRay FDR: Not flushing to file, 'no_file_flush=true'.\n");

    atomic_store(&LogFlushStatus, XRayLogFlushStatus::XRAY_LOG_FLUSHED,
                 memory_order_release);
    return XRayLogFlushStatus::XRAY_LOG_FLUSHED;
  }

  // We write out the file in the following format:
  //
  //   1) We write down the XRay file header with version 1, type FDR_LOG.
  //   2) Then we use the 'apply' member of the BufferQueue that's live, to
  //      ensure that at this point in time we write down the buffers that have
  //      been released (and marked "used") -- we dump the full buffer for now
  //      (fixed-sized) and let the tools reading the buffers deal with the data
  //      afterwards.
  //
  LogWriter *LW = LogWriter::Open();
  if (LW == nullptr) {
    auto Result = XRayLogFlushStatus::XRAY_LOG_NOT_FLUSHING;
    atomic_store(&LogFlushStatus, Result, memory_order_release);
    return Result;
  }

  XRayFileHeader Header = fdrCommonHeaderInfo();
  Header.FdrData = FdrAdditionalHeaderData{BQ->ConfiguredBufferSize()};
  LW->WriteAll(reinterpret_cast<char *>(&Header),
               reinterpret_cast<char *>(&Header) + sizeof(Header));

  // Release the current thread's buffer before we attempt to write out all the
  // buffers. This ensures that in case we had only a single thread going, that
  // we are able to capture the data nonetheless.
  auto &TLD = getThreadLocalData();
  if (TLD.Controller != nullptr)
    TLD.Controller->flush();

  BQ->apply([&](const BufferQueue::Buffer &B) {
    // Starting at version 2 of the FDR logging implementation, we only write
    // the records identified by the extents of the buffer. We use the Extents
    // from the Buffer and write that out as the first record in the buffer.  We
    // still use a Metadata record, but fill in the extents instead for the
    // data.
    MetadataRecord ExtentsRecord;
    auto BufferExtents = atomic_load(B.Extents, memory_order_acquire);
    DCHECK(BufferExtents <= B.Size);
    ExtentsRecord.Type = uint8_t(RecordType::Metadata);
    ExtentsRecord.RecordKind =
        uint8_t(MetadataRecord::RecordKinds::BufferExtents);
    internal_memcpy(ExtentsRecord.Data, &BufferExtents, sizeof(BufferExtents));
    if (BufferExtents > 0) {
      LW->WriteAll(reinterpret_cast<char *>(&ExtentsRecord),
                   reinterpret_cast<char *>(&ExtentsRecord) +
                       sizeof(MetadataRecord));
      LW->WriteAll(reinterpret_cast<char *>(B.Data),
                   reinterpret_cast<char *>(B.Data) + BufferExtents);
    }
  });

  atomic_store(&LogFlushStatus, XRayLogFlushStatus::XRAY_LOG_FLUSHED,
               memory_order_release);
  return XRayLogFlushStatus::XRAY_LOG_FLUSHED;
}

XRayLogInitStatus fdrLoggingFinalize() XRAY_NEVER_INSTRUMENT {
  s32 CurrentStatus = XRayLogInitStatus::XRAY_LOG_INITIALIZED;
  if (!atomic_compare_exchange_strong(&LoggingStatus, &CurrentStatus,
                                      XRayLogInitStatus::XRAY_LOG_FINALIZING,
                                      memory_order_release)) {
    if (Verbosity())
      Report("Cannot finalize log, implementation not initialized.\n");
    return static_cast<XRayLogInitStatus>(CurrentStatus);
  }

  // Do special things to make the log finalize itself, and not allow any more
  // operations to be performed until re-initialized.
  if (BQ == nullptr) {
    if (Verbosity())
      Report("Attempting to finalize an uninitialized global buffer!\n");
  } else {
    BQ->finalize();
  }

  atomic_store(&LoggingStatus, XRayLogInitStatus::XRAY_LOG_FINALIZED,
               memory_order_release);
  return XRayLogInitStatus::XRAY_LOG_FINALIZED;
}

struct TSCAndCPU {
  uint64_t TSC = 0;
  unsigned char CPU = 0;
};

static TSCAndCPU getTimestamp() XRAY_NEVER_INSTRUMENT {
  // We want to get the TSC as early as possible, so that we can check whether
  // we've seen this CPU before. We also do it before we load anything else,
  // to allow for forward progress with the scheduling.
  TSCAndCPU Result;

  // Test once for required CPU features
  static pthread_once_t OnceProbe = PTHREAD_ONCE_INIT;
  static bool TSCSupported = true;
  pthread_once(
      &OnceProbe, +[] { TSCSupported = probeRequiredCPUFeatures(); });

  if (TSCSupported) {
    Result.TSC = __xray::readTSC(Result.CPU);
  } else {
    // FIXME: This code needs refactoring as it appears in multiple locations
    timespec TS;
    int result = clock_gettime(CLOCK_REALTIME, &TS);
    if (result != 0) {
      Report("clock_gettime(2) return %d, errno=%d", result, int(errno));
      TS = {0, 0};
    }
    Result.CPU = 0;
    Result.TSC = TS.tv_sec * __xray::NanosecondsPerSecond + TS.tv_nsec;
  }
  return Result;
}

thread_local atomic_uint8_t Running{0};

static bool setupTLD(ThreadLocalData &TLD) XRAY_NEVER_INSTRUMENT {
  // Check if we're finalizing, before proceeding.
  {
    auto Status = atomic_load(&LoggingStatus, memory_order_acquire);
    if (Status == XRayLogInitStatus::XRAY_LOG_FINALIZING ||
        Status == XRayLogInitStatus::XRAY_LOG_FINALIZED) {
      if (TLD.Controller != nullptr) {
        TLD.Controller->flush();
        TLD.Controller = nullptr;
      }
      return false;
    }
  }

  if (UNLIKELY(TLD.Controller == nullptr)) {
    // Set up the TLD buffer queue.
    if (UNLIKELY(BQ == nullptr))
      return false;
    TLD.BQ = BQ;

    // Check that we have a valid buffer.
    if (TLD.Buffer.Generation != BQ->generation() &&
        TLD.BQ->releaseBuffer(TLD.Buffer) != BufferQueue::ErrorCode::Ok)
      return false;

    // Set up a buffer, before setting up the log writer. Bail out on failure.
    if (TLD.BQ->getBuffer(TLD.Buffer) != BufferQueue::ErrorCode::Ok)
      return false;

    // Set up the Log Writer for this thread.
    if (UNLIKELY(TLD.Writer == nullptr)) {
      auto *LWStorage = reinterpret_cast<FDRLogWriter *>(&TLD.LWStorage);
      new (LWStorage) FDRLogWriter(TLD.Buffer);
      TLD.Writer = LWStorage;
    } else {
      TLD.Writer->resetRecord();
    }

    auto *CStorage = reinterpret_cast<FDRController<> *>(&TLD.CStorage);
    new (CStorage)
        FDRController<>(TLD.BQ, TLD.Buffer, *TLD.Writer, clock_gettime,
                        atomic_load_relaxed(&ThresholdTicks));
    TLD.Controller = CStorage;
  }

  DCHECK_NE(TLD.Controller, nullptr);
  return true;
}

void fdrLoggingHandleArg0(int32_t FuncId,
                          XRayEntryType Entry) XRAY_NEVER_INSTRUMENT {
  auto TC = getTimestamp();
  auto &TSC = TC.TSC;
  auto &CPU = TC.CPU;
  RecursionGuard Guard{Running};
  if (!Guard)
    return;

  auto &TLD = getThreadLocalData();
  if (!setupTLD(TLD))
    return;

  switch (Entry) {
  case XRayEntryType::ENTRY:
  case XRayEntryType::LOG_ARGS_ENTRY:
    TLD.Controller->functionEnter(FuncId, TSC, CPU);
    return;
  case XRayEntryType::EXIT:
    TLD.Controller->functionExit(FuncId, TSC, CPU);
    return;
  case XRayEntryType::TAIL:
    TLD.Controller->functionTailExit(FuncId, TSC, CPU);
    return;
  case XRayEntryType::CUSTOM_EVENT:
  case XRayEntryType::TYPED_EVENT:
    break;
  }
}

void fdrLoggingHandleArg1(int32_t FuncId, XRayEntryType Entry,
                          uint64_t Arg) XRAY_NEVER_INSTRUMENT {
  auto TC = getTimestamp();
  auto &TSC = TC.TSC;
  auto &CPU = TC.CPU;
  RecursionGuard Guard{Running};
  if (!Guard)
    return;

  auto &TLD = getThreadLocalData();
  if (!setupTLD(TLD))
    return;

  switch (Entry) {
  case XRayEntryType::ENTRY:
  case XRayEntryType::LOG_ARGS_ENTRY:
    TLD.Controller->functionEnterArg(FuncId, TSC, CPU, Arg);
    return;
  case XRayEntryType::EXIT:
    TLD.Controller->functionExit(FuncId, TSC, CPU);
    return;
  case XRayEntryType::TAIL:
    TLD.Controller->functionTailExit(FuncId, TSC, CPU);
    return;
  case XRayEntryType::CUSTOM_EVENT:
  case XRayEntryType::TYPED_EVENT:
    break;
  }
}

void fdrLoggingHandleCustomEvent(void *Event,
                                 std::size_t EventSize) XRAY_NEVER_INSTRUMENT {
  auto TC = getTimestamp();
  auto &TSC = TC.TSC;
  auto &CPU = TC.CPU;
  RecursionGuard Guard{Running};
  if (!Guard)
    return;

  // Complain when we ever get at least one custom event that's larger than what
  // we can possibly support.
  if (EventSize >
      static_cast<std::size_t>(std::numeric_limits<int32_t>::max())) {
    static pthread_once_t Once = PTHREAD_ONCE_INIT;
    pthread_once(
        &Once, +[] {
          Report("Custom event size too large; truncating to %d.\n",
                 std::numeric_limits<int32_t>::max());
        });
  }

  auto &TLD = getThreadLocalData();
  if (!setupTLD(TLD))
    return;

  int32_t ReducedEventSize = static_cast<int32_t>(EventSize);
  TLD.Controller->customEvent(TSC, CPU, Event, ReducedEventSize);
}

void fdrLoggingHandleTypedEvent(size_t EventType, const void *Event,
                                size_t EventSize) noexcept
    XRAY_NEVER_INSTRUMENT {
  auto TC = getTimestamp();
  auto &TSC = TC.TSC;
  auto &CPU = TC.CPU;
  RecursionGuard Guard{Running};
  if (!Guard)
    return;

  // Complain when we ever get at least one typed event that's larger than what
  // we can possibly support.
  if (EventSize >
      static_cast<std::size_t>(std::numeric_limits<int32_t>::max())) {
    static pthread_once_t Once = PTHREAD_ONCE_INIT;
    pthread_once(
        &Once, +[] {
          Report("Typed event size too large; truncating to %d.\n",
                 std::numeric_limits<int32_t>::max());
        });
  }

  auto &TLD = getThreadLocalData();
  if (!setupTLD(TLD))
    return;

  int32_t ReducedEventSize = static_cast<int32_t>(EventSize);
  TLD.Controller->typedEvent(TSC, CPU, static_cast<uint16_t>(EventType), Event,
                             ReducedEventSize);
}

XRayLogInitStatus fdrLoggingInit(size_t, size_t, void *Options,
                                 size_t OptionsSize) XRAY_NEVER_INSTRUMENT {
  if (Options == nullptr)
    return XRayLogInitStatus::XRAY_LOG_UNINITIALIZED;

  s32 CurrentStatus = XRayLogInitStatus::XRAY_LOG_UNINITIALIZED;
  if (!atomic_compare_exchange_strong(&LoggingStatus, &CurrentStatus,
                                      XRayLogInitStatus::XRAY_LOG_INITIALIZING,
                                      memory_order_release)) {
    if (Verbosity())
      Report("Cannot initialize already initialized implementation.\n");
    return static_cast<XRayLogInitStatus>(CurrentStatus);
  }

  if (Verbosity())
    Report("Initializing FDR mode with options: %s\n",
           static_cast<const char *>(Options));

  // TODO: Factor out the flags specific to the FDR mode implementation. For
  // now, use the global/single definition of the flags, since the FDR mode
  // flags are already defined there.
  FlagParser FDRParser;
  FDRFlags FDRFlags;
  registerXRayFDRFlags(&FDRParser, &FDRFlags);
  FDRFlags.setDefaults();

  // Override first from the general XRAY_DEFAULT_OPTIONS compiler-provided
  // options until we migrate everyone to use the XRAY_FDR_OPTIONS
  // compiler-provided options.
  FDRParser.ParseString(useCompilerDefinedFlags());
  FDRParser.ParseString(useCompilerDefinedFDRFlags());
  auto *EnvOpts = GetEnv("XRAY_FDR_OPTIONS");
  if (EnvOpts == nullptr)
    EnvOpts = "";
  FDRParser.ParseString(EnvOpts);

  // FIXME: Remove this when we fully remove the deprecated flags.
  if (internal_strlen(EnvOpts) == 0) {
    FDRFlags.func_duration_threshold_us =
        flags()->xray_fdr_log_func_duration_threshold_us;
    FDRFlags.grace_period_ms = flags()->xray_fdr_log_grace_period_ms;
  }

  // The provided options should always override the compiler-provided and
  // environment-variable defined options.
  FDRParser.ParseString(static_cast<const char *>(Options));
  *fdrFlags() = FDRFlags;
  auto BufferSize = FDRFlags.buffer_size;
  auto BufferMax = FDRFlags.buffer_max;

  if (BQ == nullptr) {
    bool Success = false;
    BQ = reinterpret_cast<BufferQueue *>(&BufferQueueStorage);
    new (BQ) BufferQueue(BufferSize, BufferMax, Success);
    if (!Success) {
      Report("BufferQueue init failed.\n");
      return XRayLogInitStatus::XRAY_LOG_UNINITIALIZED;
    }
  } else {
    if (BQ->init(BufferSize, BufferMax) != BufferQueue::ErrorCode::Ok) {
      if (Verbosity())
        Report("Failed to re-initialize global buffer queue. Init failed.\n");
      return XRayLogInitStatus::XRAY_LOG_UNINITIALIZED;
    }
  }

  static pthread_once_t OnceInit = PTHREAD_ONCE_INIT;
  pthread_once(
      &OnceInit, +[] {
        atomic_store(&TicksPerSec,
                     probeRequiredCPUFeatures() ? getTSCFrequency()
                                                : __xray::NanosecondsPerSecond,
                     memory_order_release);
        pthread_key_create(
            &Key, +[](void *TLDPtr) {
              if (TLDPtr == nullptr)
                return;
              auto &TLD = *reinterpret_cast<ThreadLocalData *>(TLDPtr);
              if (TLD.BQ == nullptr)
                return;
              if (TLD.Buffer.Data == nullptr)
                return;
              auto EC = TLD.BQ->releaseBuffer(TLD.Buffer);
              if (EC != BufferQueue::ErrorCode::Ok)
                Report("At thread exit, failed to release buffer at %p; "
                       "error=%s\n",
                       TLD.Buffer.Data, BufferQueue::getErrorString(EC));
            });
      });

  atomic_store(&ThresholdTicks,
               atomic_load_relaxed(&TicksPerSec) *
                   fdrFlags()->func_duration_threshold_us / 1000000,
               memory_order_release);
  // Arg1 handler should go in first to avoid concurrent code accidentally
  // falling back to arg0 when it should have ran arg1.
  __xray_set_handler_arg1(fdrLoggingHandleArg1);
  // Install the actual handleArg0 handler after initialising the buffers.
  __xray_set_handler(fdrLoggingHandleArg0);
  __xray_set_customevent_handler(fdrLoggingHandleCustomEvent);
  __xray_set_typedevent_handler(fdrLoggingHandleTypedEvent);

  // Install the buffer iterator implementation.
  __xray_log_set_buffer_iterator(fdrIterator);

  atomic_store(&LoggingStatus, XRayLogInitStatus::XRAY_LOG_INITIALIZED,
               memory_order_release);

  if (Verbosity())
    Report("XRay FDR init successful.\n");
  return XRayLogInitStatus::XRAY_LOG_INITIALIZED;
}

bool fdrLogDynamicInitializer() XRAY_NEVER_INSTRUMENT {
  XRayLogImpl Impl{
      fdrLoggingInit,
      fdrLoggingFinalize,
      fdrLoggingHandleArg0,
      fdrLoggingFlush,
  };
  auto RegistrationResult = __xray_log_register_mode("xray-fdr", Impl);
  if (RegistrationResult != XRayLogRegisterStatus::XRAY_REGISTRATION_OK &&
      Verbosity()) {
    Report("Cannot register XRay FDR mode to 'xray-fdr'; error = %d\n",
           RegistrationResult);
    return false;
  }

  if (flags()->xray_fdr_log ||
      !internal_strcmp(flags()->xray_mode, "xray-fdr")) {
    auto SelectResult = __xray_log_select_mode("xray-fdr");
    if (SelectResult != XRayLogRegisterStatus::XRAY_REGISTRATION_OK &&
        Verbosity()) {
      Report("Cannot select XRay FDR mode as 'xray-fdr'; error = %d\n",
             SelectResult);
      return false;
    }
  }
  return true;
}

} // namespace __xray

static auto UNUSED Unused = __xray::fdrLogDynamicInitializer();
