/*
 * $Header: /p/sss/cvs/OpenVM/src/native/common/include/atomicops.h,v 1.14 2004/09/29 17:45:46 cunei Exp $
 */
#ifndef ATOMICOPS_H
#define ATOMICOPS_H

#include "config.h"
#include "jtypes.h"

/**
 * Defines the basic "atomic" operation of "compare-and-swap" (CAS).
 *
 * When running on a uniprocessor where preemption is limited to safe
 * points, ordinary C code is, in fact, atomic.
 *
 * On x86 this uses cmpxchg and forms the basis for all atomic operations 
 * that need to return the value being operated upon.
 *
 * On PPC the ll/sc primitives are used to construct a "compare-and-swap"
 * operation. There are two forms: the "weak" and the "strong". The "weak"
 * form can suffer from spurious failures due to unrelated accesses causing
 * a lost reservation. Because ll/sc is more primitive than compare-and-swap
 * you can build other atomic operations, like get_and_add, directly in
 * assembler
 * At present the higher-level code implements all other atomic operations in 
 * terms of CAS.
 * 
 */

#if defined(SAFE_POINTS) && defined(UNIPROCESSOR)
/*
 * Note that SAFE_POINTS and UNIPROCESSOR are only defined when
 * compiling the ovm itself.  Code in libnative.a will be compiled
 * with SMP-safe CAS primitives.
 */

/*
 * On the x86: gcc does a poor job of compiling either of these
 * functions for a branch.  It seems to do slightly better with the
 * first version: it uses the same number of instructions, but one
 * fewer registers.
 */
#if 1
static inline jboolean CAS32(void *_target, jint oldval, jint newval) {
  jint *target = (jint *) _target; /* C++ compatiblity */
  jboolean same = *target == oldval;
  if (same)
    *target = newval;
  return same;
}
#else
static inline jboolean CAS32(void *_target, jint oldval, jint newval) {
  jint *target = (jint *) _target; /* C++ compatibility */
  if (*target == oldval) {
    *target = newval;
    return 1;
  } else
    return 0;
}
#endif

static inline jboolean CAS64(void *_target, jlong oldval, jlong newval) {
  jlong *target = (jlong *) _target; /* C++ compatibility */
  jboolean same = *target == oldval;
  if (same)
    *target = newval;
  return same;
}
#elif defined(OVM_PPC)
#include <stdio.h>
#include <stdlib.h>
/*
 * Perform a "weak" atomic compare-and-swap of a 32-bit value.
 * This is a "weak" version because the store-conditional could fail
 * if another address in the reservation granule was written to.
 * @param target the target address
 * @param oldval the value to compare against
 * @param newval the value to set if the target has oldval
 *
 * @return 1 if the comparison succeeded and the newvalue was set
 *         0 if the comparison failed or the reservation was lost
 */
static inline
jboolean CAS32_WEAK(volatile jint *target,jint oldval, jint newval) {
    // %0 = result
    // %2 = expected old value 
    // %3 = target address 
    // %4 = new value
    // %5 = tmp
    jboolean result;
    int tmp;
    __asm__ __volatile__ (
        "lwarx  %5,0,%3\n"  // load *target with reservation
        "cmpw   %2,%5\n"    // compare with oldval
        "bne-   0f\n"       // goto result=0 if != oldval
        "stwcx. %4,0,%3\n"  // conditionally store newval
        "bne-   0f\n"       // goto result=0 if store failed
        "li     %0,1\n"     // result = 1
        "b      1f\n"       // goto exit
        "0:\n"
        "li     %0,0\n"     // result = 0
        "1:\n"
        /* outputs */
        : "=r"(result), "=r"(target)
        /* inputs */
        : "r"(oldval), "1"(target), "r"(newval), "r"(tmp)
        /* clobbers */
        : "cr0", "memory");
    return result;
}

/*
 * Perform an atomic compare-and-swap of a 32-bit value.
 * @param target the target address
 * @param oldval the value to compare against
 * @param newval the value to set if the target has oldval
 *
 * @return 1 if the comparison succeeded and the newvalue was set
 *         0 if the comparison failed.
 */
static inline
jboolean CAS32(volatile void *_target,jint oldval, jint newval) {
    // %0 = result
    // %2 = expected old value 
    // %3 = target address 
    // %4 = new value
    // %5 = tmp
    volatile jint *target = (volatile jint *) _target;
    jboolean result;
    int tmp = 0; // gcc warns about uninitialized use - though that should not be so
    __asm__ __volatile__ (
        "0:\n"
        "lwarx  %5,0,%3\n"  // load *target with reservation
        "cmpw   %2,%5\n"    // compare with oldval
        "bne-   1f\n"       // goto result=0 if != oldval
        "stwcx. %4,0,%3\n"  // conditionally store newval
        "bne-   0b\n"       // loop if lost reservation
        "li     %0,1\n"     // result = 1
        "b      2f\n"       // goto exit
        "1:\n"
        "li     %0,0\n"     // result = 0
        "2:\n"              // exit
        /* outputs */
        : "=r"(result), "=r"(target)
        /* inputs */
        : "r"(oldval), "1"(target), "r"(newval), "r"(tmp)
        /* clobbers */
        : "cr0", "memory");

    return result;
}


static inline
jboolean CAS64_WEAK(volatile jlong* target, jlong oldval, jlong newval) {
/* there is no extended CAS support on PPC. So CAS64 is only valid
   when on a 64-bit architecture
*/
#if SIZEOF_INT == 8
    // %0 = result
    // %2 = expected old value
    // %3 = target address
    // %4 = new value
    // %5 = temp
    jboolean result;
    int tmp;
    __asm__ __volatile__ (
        "ldarx  %5,0,%3\n"   // load dword *target with reservation
        "cmpd   %2,%5\n"     // compare with old value
        "bne-   0f\n"        // goto result=0 if != oldval
        "stdcx. %4,0,%3\n"   // conditionally store newval
        "bne-   0f\n"        // goto result=0 if lost reservation
        "li     %0,1\n"      // result = 1
        "b      1f\n"        // goto exit
        "0:\n"
        "li     %0,0\n"      // result = 0
        "1:\n"               // exit
        /* outputs */
        : "=r"(result), "=r"(target)
        /* inputs */
        : "r"(oldval),  "1"(target), "r"(newval), "r"(tmp)
        /* clobbers */
        : "cr0", "memory");

    return result;
#else
    fprintf(stderr, "ERROR: attempt to invoke CAS64_WEAK from a 32-bit PPC machine\n");
    exit(-1);
#endif
}

static inline
jboolean CAS64(volatile void* _target, jlong oldval, jlong newval) {
/* there is no extended CAS support on PPC. So CAS64 is only valid
   when on a 64-bit architecture
*/
    volatile jlong *target = (volatile jlong *) _target;  
#if SIZEOF_INT == 8
    // %0 = result
    // %2 = expected old value
    // %3 = target address
    // %4 = new value
    // %5 = temp
    jboolean result;
    int tmp;
    __asm__ __volatile__ (
        "0:\n"
        "ldarx  %5,0,%3\n"   // load dword *target with reservation
        "cmpd   %2,%5\n"     // compare with old value
        "bne-   1f\n"        // goto result=0 if != oldval
        "stdcx. %4,0,%3\n"   // conditionally store newval
        "bne-   0b\n"        // loop if lost reservation
        "li     %0,1\n"      // result = 1
        "b      2f\n"        // goto exit
        "1:\n"
        "li     %0,0\n"      // result = 0
        "2:\n"               // exit
        /* outputs */
        : "=r"(result), "=r"(target)
        /* inputs */
        : "r"(oldval),  "1"(target), "r"(newval), "r"(tmp)
        /* clobbers */
        : "cr0", "memory");

    return result;
#else
    fprintf(stderr, "ERROR: attempt to invoke CAS64 from a 32-bit PPC machine\n");
    exit(-1);
#endif
}



#elif defined(OVM_X86)

#ifdef UNIPROCESSOR
# define LOCK
#else 
# define LOCK "LOCK"
#endif

/*
 * Perform an atomic compare-and-swap of a 32-bit value
 * @param target the target address
 * @param oldval the value to compare against
 * @param newval the value to set if the target has oldval
 *
 * @return 1 if the comparison succeeded and the newvalue was set
 *         0 if the comparison failed
 */
static inline 
jboolean CAS32(volatile void *_target, jint oldval, jint newval) {
    volatile jint *target = (volatile jint *) _target;
    char ret;  // must be a single byte
    jint readval;
    
    __asm__ __volatile__ 
        ( LOCK" cmpxchgl %3, %1\n\t"  /* do the cas */
          "sete %b0\n\t"  /* set the return value */
          : /* outputs */
          "=q" (ret), "+m" (*target), "=a" (readval)
          : /* inputs */
          "r" (newval),  "a" (oldval)
          : /* clobbers */
          "memory", "cc");

    return (jboolean)ret;
}


/*
 * Perform an atomic compare-and-swap of a 64-bit value
 * @param retval the value at target when the comparison is made will be
 *               stored at this address
 * @param target the target address
 * @param cmp the value to compare against
 * @param xchg_1 the low 32-bits of the new value to set
 * @param xchg_2 the high 32-bits of the new value to set
 *
 * @return 1 if the comparison succeeded and the new value was set
 *         0 if the comparison failed
 */
/* 
This code came from Joe Seigh in a posting in comp.lang.asm.x86. 
The original code used 'and' to zero out the upper three bytes of ret
after sete wrote 0 or 1 to the first byte. However, Matt Young, reported
that the use of a byte-access op followed by a word-access op would cause
a stall unless the CPU knew the value was zero - hence the 'and' was changed
to a movzbl - move byte to word with zero extension.
Of course you could use a byte for ret instead of an int, but seemingly this
might restrict the compiler too much and the compiler will probably use it
as an int anyway ... note that we use a byte for CAS32 so it might suffer too.
Someone should profile this.
*/
static inline 
int cas64_(jlong *retval, 
           volatile jlong *target,
           jlong cmp,
           long xchg_1,
           long xchg_2) {
    int ret;

    __asm__ __volatile__
        (
            LOCK" cmpxchg8b %2\n\t"      /* do the CAS */
            "sete %b0\n\t"          /* set bit 0 of ret if it succeeded */
            "movzbl %b0, %0\n\t"    /* zero-extend result - avoiding stall */
            : /* output */
            "=q" (ret),
            "=A" (*retval)
            : /* input */
            "m"  (*target),  /* target */
            "A"  (cmp),      /* comparand */
            "b"  (xchg_1),   /* exchange-lo */
            "c"  (xchg_2)    /* exchange-hi */
            : /* clobber list */  
            "memory", "cc"
            );

    return ret;
}

/*
 * Perform an atomic compare-and-swap of a 64-bit value
 * @param target the target address
 * @param oldval the value to compare against
 * @param newval the value to set if the target has oldval
 *
 * @return 1 if the comparison succeeded and the new value was set
 *         0 if the comparison failed
 */
inline static jboolean CAS64(void* _target, jlong oldval, jlong newval) {
    jlong *target = (jlong *) _target;
    jlong retval; /* we don't use this */
    jlong tmp = newval;
    return cas64_(&retval, target, oldval, (&tmp)[0], (&tmp)[1]);
}

#if 0
/* original wrapper function used by Joe Seigh */
int CAS64_original(long long *target, long long *cmp, long *xchg) {
 long long retval;
 int rc;

 rc = cas64_(&retval, target, *cmp, xchg[0], xchg[1]);
 if (rc == 0)
  *cmp = retval;
 return rc;
}
#endif


#else
# error "OVM is only supported on IA32 and PPC architectures at present"
#endif


/* the other atomic functions */

/**
 * Atomically retrieve the current value of the target
 */
jint atomic_get_int(volatile jint* target);

/**
 * Atomically set the target to newVal
 */
void atomic_set_int(volatile jint* target, jint newVal);

/**
 * Atomically retrieve the current value of the target and set it to newVal
 */
jint atomic_get_and_set_int(volatile jint* target, jint newVal);

/**
 * Atomically increment by one, the target, and return the previous value.
 */
jint atomic_get_and_inc_int(volatile jint* target);


/**
 * Atomically decrement by one, the target, and return the previous value.
 */
jint atomic_get_and_dec_int(volatile jint* target);

/**
 * Atomically add the given val to the target, and return the previous value
 */
jint atomic_get_and_add_int(volatile jint* target, jint val);


/**
 * Atomically increment by one, the target, and return the new value.
 */
jint atomic_inc_and_get_int(volatile jint* target);


/**
 * Atomically decrement by one, the target, and return the new value.
 */
jint atomic_dec_and_get_int(volatile jint* target);

/**
 * Atomically add the given val to the target, and return the new value
 */
jint atomic_add_and_get_int(volatile jint* target, jint val);



#endif /* ATOMICOPS_H */




