/* 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/. */

#include "jit/FlushICache.h"

#include "mozilla/Atomics.h"

#include <stdint.h>

#ifdef JS_SIMULATOR_ARM64
#  include "jit/arm64/vixl/MozCachingDecoder.h"
#  include "jit/arm64/vixl/Simulator-vixl.h"
#endif

#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64)

#  ifdef __linux__
#    include <linux/version.h>
#    define LINUX_HAS_MEMBARRIER (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 3, 0))
#  else
#    define LINUX_HAS_MEMBARRIER 0
#  endif

#  if LINUX_HAS_MEMBARRIER || defined(__android__)
#    include <string.h>

#    if LINUX_HAS_MEMBARRIER
#      include <linux/membarrier.h>
#      include <sys/syscall.h>
#      include <sys/utsname.h>
#      include <unistd.h>
#    elif defined(__android__)
#      include <sys/syscall.h>
#      include <unistd.h>
#    else
#      error "Missing platform-specific declarations for membarrier syscall!"
#    endif  // __linux__ / ANDROID

static int membarrier(int cmd, int flags) {
  return syscall(__NR_membarrier, cmd, flags);
}

// These definitions come from the Linux kernel source, for kernels before 4.16
// which didn't have access to these membarrier commands.
#    ifndef MEMBARRIER_CMD_PRIVATE_EXPEDITED_SYNC_CORE
#      define MEMBARRIER_CMD_PRIVATE_EXPEDITED_SYNC_CORE (1 << 5)
#    endif

#    ifndef MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_SYNC_CORE
#      define MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_SYNC_CORE (1 << 6)
#    endif
#  endif  // LINUX_HAS_MEMBARRIER || defined(__android__)

using namespace js;
using namespace js::jit;

namespace js {
namespace jit {

bool CanFlushExecutionContextForAllThreads() {
#  if (LINUX_HAS_MEMBARRIER || defined(__android__))
  // We only need to decide this once, but we don't know which thread will ask
  // first.  Hence the atomic.
  enum class MemBarrierAvailable : uint32_t { Unset, No, Yes };

  // The only allowable transitions are Unset->No and Unset->Yes.
  static mozilla::Atomic<MemBarrierAvailable> state(MemBarrierAvailable::Unset);

  MemBarrierAvailable localState = state;
  if (MOZ_LIKELY(localState != MemBarrierAvailable::Unset)) {
    return localState == MemBarrierAvailable::Yes;
  }

  // Otherwise we'll have to make enquiries.  This needs to produce the same
  // result if performed more than once.

  // On linux, check the kernel supports membarrier(2).  A prerequisite is that
  // the kernel version is 4.16 or later.
  //
  // Note: this code has been extracted (August 2020) from
  // https://android.googlesource.com/platform/art/+/58520dfba31d6eeef75f5babff15e09aa28e5db8/libartbase/base/membarrier.cc#50
  static constexpr int kRequiredMajor = 4;
  static constexpr int kRequiredMinor = 16;

  struct utsname uts;
  int major, minor;
  bool memBarrierAvailable =
      uname(&uts) == 0 && strcmp(uts.sysname, "Linux") == 0 &&
      sscanf(uts.release, "%d.%d", &major, &minor) == 2 &&
      major >= kRequiredMajor &&
      (major != kRequiredMajor || minor >= kRequiredMinor);

  // Try to run the syscall with the command registering the intent to use the
  // actual membarrier we'll want to carry out later.
  //
  // IMPORTANT: This is needs to succeed, since otherwise running the
  // membarrier later won't actually interrupt the threads in this process.
  // Per `man 2 membarrier`, "RETURN VALUE", we are assured that multiple calls
  // to `membarrier` with the same commands always produce the same results.
  if (memBarrierAvailable &&
      membarrier(MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_SYNC_CORE, 0) != 0) {
    memBarrierAvailable = false;
  }

  bool ok = state.compareExchange(
      MemBarrierAvailable::Unset,
      memBarrierAvailable ? MemBarrierAvailable::Yes : MemBarrierAvailable::No);
  if (ok) {
    return memBarrierAvailable;
  }

  // We got out-raced.  Return the info that's already there.
  MOZ_ASSERT(state != MemBarrierAvailable::Unset);
  return state == MemBarrierAvailable::Yes;

#  else
  // On other platforms, we assume that the syscall for flushing the icache
  // will flush the execution context for other cores.
  return true;
#  endif
}

void FlushExecutionContextForAllThreads() {
  // Callers must check that this operation is available.
  MOZ_RELEASE_ASSERT(CanFlushExecutionContextForAllThreads());

#  if defined(JS_SIMULATOR_ARM64) && defined(JS_CACHE_SIMULATOR_ARM64)
  // Emulate what the real hardware would do by emitting a membarrier that'll
  // interrupt and flush the execution context of all threads.
  using js::jit::SimulatorProcess;
  js::jit::AutoLockSimulatorCache alsc;
  SimulatorProcess::membarrier();
#  elif (LINUX_HAS_MEMBARRIER || defined(__android__))
  // The caller has checked this can be performed, which will have registered
  // this process to receive the membarrier. See above.
  //
  // membarrier will trigger an inter-processor-interrupt on any active threads
  // of this process. This is an execution context synchronization event
  // equivalent to running an `isb` instruction.
  if (membarrier(MEMBARRIER_CMD_PRIVATE_EXPEDITED_SYNC_CORE, 0) != 0) {
    // Better safe than sorry.
    MOZ_CRASH("membarrier can't be executed");
  }
#  else
  // On other platforms, we assume that the syscall for flushing the icache
  // will flush the execution context for other cores.
#  endif
}

}  // namespace jit
}  // namespace js

#endif
