/*
 * Copyright (c) 2017, 2018, Red Hat, Inc. All rights reserved.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 *
 */

#ifndef SHARE_VM_GC_SHENANDOAH_SHENANDOAHSHAREDFLAG_HPP
#define SHARE_VM_GC_SHENANDOAH_SHENANDOAHSHAREDFLAG_HPP

#include "gc_implementation/shenandoah/shenandoahPadding.hpp"
#include "memory/allocation.hpp"
#include "runtime/orderAccess.hpp"

typedef jbyte ShenandoahSharedValue;

typedef struct ShenandoahSharedFlag {
  enum {
    UNSET = 0,
    SET = 1
  };

  shenandoah_padding(0);
  volatile ShenandoahSharedValue value;
  shenandoah_padding(1);

  ShenandoahSharedFlag() {
    // Needed for cooperation with generated code.
    STATIC_ASSERT(sizeof(ShenandoahSharedValue) == 1);

    unset();
  }

  void set() {
    OrderAccess::release_store_fence(&value, (ShenandoahSharedValue)SET);
  }

  void unset() {
    OrderAccess::release_store_fence(&value, (ShenandoahSharedValue)UNSET);
  }

  bool is_set() const {
    return OrderAccess::load_acquire((volatile ShenandoahSharedValue*) &value) == SET;
  }

  bool is_unset() const {
    return OrderAccess::load_acquire((volatile ShenandoahSharedValue*) &value) == UNSET;
  }

  void set_cond(bool val) {
    if (val) {
      set();
    } else {
      unset();
    }
  }

  bool try_set() {
    if (is_set()) {
      return false;
    }
    ShenandoahSharedValue old = Atomic::cmpxchg((ShenandoahSharedValue)SET, &value, (ShenandoahSharedValue)UNSET);
    return old == UNSET; // success
  }

  bool try_unset() {
    if (!is_set()) {
      return false;
    }
    ShenandoahSharedValue old = Atomic::cmpxchg((ShenandoahSharedValue)UNSET, &value, (ShenandoahSharedValue)SET);
    return old == SET; // success
  }

  volatile ShenandoahSharedValue* addr_of() {
    return &value;
  }

private:
  volatile ShenandoahSharedValue* operator&() {
    fatal("Use addr_of() instead");
    return NULL;
  }

  bool operator==(ShenandoahSharedFlag& other) { fatal("Use is_set() instead"); return false; }
  bool operator!=(ShenandoahSharedFlag& other) { fatal("Use is_set() instead"); return false; }
  bool operator> (ShenandoahSharedFlag& other) { fatal("Use is_set() instead"); return false; }
  bool operator>=(ShenandoahSharedFlag& other) { fatal("Use is_set() instead"); return false; }
  bool operator< (ShenandoahSharedFlag& other) { fatal("Use is_set() instead"); return false; }
  bool operator<=(ShenandoahSharedFlag& other) { fatal("Use is_set() instead"); return false; }

} ShenandoahSharedFlag;

typedef struct ShenandoahSharedBitmap {
  shenandoah_padding(0);
  volatile ShenandoahSharedValue value;
  shenandoah_padding(1);

  ShenandoahSharedBitmap() {
    clear();
  }

  void set(uint mask) {
    assert (mask < (sizeof(ShenandoahSharedValue) * CHAR_MAX), "sanity");
    ShenandoahSharedValue mask_val = (ShenandoahSharedValue) mask;
    while (true) {
      ShenandoahSharedValue ov = OrderAccess::load_acquire(&value);
      if ((ov & mask_val) != 0) {
        // already set
        return;
      }

      ShenandoahSharedValue nv = ov | mask_val;
      if (Atomic::cmpxchg(nv, &value, ov) == ov) {
        // successfully set
        return;
      }
    }
  }

  void unset(uint mask) {
    assert (mask < (sizeof(ShenandoahSharedValue) * CHAR_MAX), "sanity");
    ShenandoahSharedValue mask_val = (ShenandoahSharedValue) mask;
    while (true) {
      ShenandoahSharedValue ov = OrderAccess::load_acquire(&value);
      if ((ov & mask_val) == 0) {
        // already unset
        return;
      }

      ShenandoahSharedValue nv = ov & ~mask_val;
      if (Atomic::cmpxchg(nv, &value, ov) == ov) {
        // successfully unset
        return;
      }
    }
  }

  void clear() {
    OrderAccess::release_store_fence(&value, (ShenandoahSharedValue)0);
  }

  bool is_set(uint mask) const {
    return !is_unset(mask);
  }

  bool is_unset(uint mask) const {
    assert (mask < (sizeof(ShenandoahSharedValue) * CHAR_MAX), "sanity");
    return (OrderAccess::load_acquire((volatile ShenandoahSharedValue*)&value) & (ShenandoahSharedValue) mask) == 0;
  }

  bool is_clear() const {
    return (OrderAccess::load_acquire((volatile ShenandoahSharedValue*)&value)) == 0;
  }

  void set_cond(uint mask, bool val) {
    if (val) {
      set(mask);
    } else {
      unset(mask);
    }
  }

  volatile ShenandoahSharedValue* addr_of() {
    return &value;
  }

  ShenandoahSharedValue raw_value() {
    return value;
  }

private:
  volatile ShenandoahSharedValue* operator&() {
    fatal("Use addr_of() instead");
    return NULL;
  }

  bool operator==(ShenandoahSharedFlag& other) { fatal("Use is_set() instead"); return false; }
  bool operator!=(ShenandoahSharedFlag& other) { fatal("Use is_set() instead"); return false; }
  bool operator> (ShenandoahSharedFlag& other) { fatal("Use is_set() instead"); return false; }
  bool operator>=(ShenandoahSharedFlag& other) { fatal("Use is_set() instead"); return false; }
  bool operator< (ShenandoahSharedFlag& other) { fatal("Use is_set() instead"); return false; }
  bool operator<=(ShenandoahSharedFlag& other) { fatal("Use is_set() instead"); return false; }

} ShenandoahSharedBitmap;

template<class T>
struct ShenandoahSharedEnumFlag {
  shenandoah_padding(0);
  volatile ShenandoahSharedValue value;
  shenandoah_padding(1);

  ShenandoahSharedEnumFlag() {
    value = 0;
  }

  void set(T v) {
    assert (v >= 0, "sanity");
    assert (v < (sizeof(ShenandoahSharedValue) * CHAR_MAX), "sanity");
    OrderAccess::release_store_fence(&value, (ShenandoahSharedValue)v);
  }

  T get() const {
    return (T)OrderAccess::load_acquire((volatile ShenandoahSharedValue*) &value);
  }

  T cmpxchg(T new_value, T expected) {
    assert (new_value >= 0, "sanity");
    assert (new_value < (sizeof(ShenandoahSharedValue) * CHAR_MAX), "sanity");
    return (T)Atomic::cmpxchg((ShenandoahSharedValue)new_value, &value, (ShenandoahSharedValue)expected);
  }

  volatile ShenandoahSharedValue* addr_of() {
    return &value;
  }

private:
  volatile T* operator&() {
    fatal("Use addr_of() instead");
    return NULL;
  }

  bool operator==(ShenandoahSharedEnumFlag& other) { fatal("Use get() instead"); return false; }
  bool operator!=(ShenandoahSharedEnumFlag& other) { fatal("Use get() instead"); return false; }
  bool operator> (ShenandoahSharedEnumFlag& other) { fatal("Use get() instead"); return false; }
  bool operator>=(ShenandoahSharedEnumFlag& other) { fatal("Use get() instead"); return false; }
  bool operator< (ShenandoahSharedEnumFlag& other) { fatal("Use get() instead"); return false; }
  bool operator<=(ShenandoahSharedEnumFlag& other) { fatal("Use get() instead"); return false; }

};

#endif // SHARE_VM_GC_SHENANDOAH_SHENANDOAHSHAREDFLAG_HPP
