// -*-C++-*-
// This file is a part of the IncludeOS unikernel - www.includeos.org
//
// Copyright 2015 Oslo and Akershus University College of Applied Sciences
// and Alfred Bratterud
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#pragma once
#ifndef API_SMP_UTILS_HEADER
#define API_SMP_UTILS_HEADER

#include <chrono>
#include <arch.hpp>

/* TTAS[1] spinlock implementation.
 *
 * Implements C++ named requirements BasicLockable, Lockable and TimedLockable
 * for use with e.g. std::lock_guard.
 *
 * Loosely follows C++ spinlock implementation by Erik Rigtorp[2].
 *
 * References:
 * 1. https://en.wikipedia.org/wiki/Test_and_test-and-set
 * 2. https://rigtorp.se/spinlock/
 * */
class Spinlock {
  public:
    void lock() noexcept {
      // See https://rigtorp.se/spinlock/ for details on memory ordering
      //
      // Optimised for uncontended locks as it starts with exchange, which
      // requires a write.
      while (lock_.exchange(true, std::memory_order_acquire)) {
        while (lock_.load(std::memory_order_relaxed)) {
          os::Arch::cpu_relax();
        }
      }
    }

    void unlock() noexcept {
      lock_.store(false, std::memory_order_release);
    }

    bool is_locked() noexcept {
      return lock_.load(std::memory_order_relaxed);
    }

    // try_lock() returns true on success, false on failure. Needed for Lockable.
    bool try_lock() noexcept {
      // Fast path first. Return false if the lock is taken
      if (lock_.load(std::memory_order_relaxed)) {
        return false;
      }
      // Try to take the lock, .exchange returns false if the lock was available
      return !lock_.exchange(true, std::memory_order_acquire);
    }

    // Try to lock for duration. Needed for TimedLockable.
    bool try_lock_for(std::chrono::nanoseconds t) noexcept {
      using namespace std::chrono;

      // Try to take lock first
      if (try_lock()) {
        return true;
      }

      // Try to take again until deadline has passed
      auto deadline = high_resolution_clock::now() + t;

      do {
        os::Arch::cpu_relax();
        if (try_lock()) {
          return true;
        }
      } while (deadline > high_resolution_clock::now());

      return false;
    }

    // Try to lock until timestamp. Needed for TimedLockable.
    template<class T>
      bool try_lock_until(typename std::chrono::time_point<T> t) noexcept {
        // Convert to duration
        auto d = t - T::now();
        return try_lock_for(d);
      }

  private:
    std::atomic<bool> lock_ = {0};

};

struct minimal_barrier_t
{
  void inc()
  {
    __sync_fetch_and_add(&val, 1);
  }

  void spin_wait(int max)
  {
    asm("mfence");
    while (this->val < max) {
      asm("pause; nop;");
    }
  }

  void reset(int val)
  {
    asm volatile("mfence");
    this->val = val;
  }

private:
  volatile int val = 0;
};

#endif // hdr
