/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to you 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
 *
 * https://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.
 */

#ifndef AVRO_REFCOUNT_H
#define AVRO_REFCOUNT_H

#if defined(_WIN32) && defined(__cplusplus)
/* Include the C++ file <intrin.h> outside the scope of extern "C" */
#include <intrin.h>
#endif

#ifdef __cplusplus
extern "C" {
#define CLOSE_EXTERN }
#else
#define CLOSE_EXTERN
#endif

/**
 * Atomically sets the value of a reference count.
 */

static inline void
avro_refcount_set(volatile int *refcount, int value);

/**
 * Increments a reference count, ensuring that its value doesn't
 * overflow.
 */

static inline void
avro_refcount_inc(volatile int *refcount);

/**
 * Decrements a reference count, and returns whether the resulting
 * (decremented) value is 0.
 */

static inline int
avro_refcount_dec(volatile int *refcount);


/*-----------------------------------------------------------------------
 * Non-Atomic Reference Count
 */
#if defined(AVRO_NON_ATOMIC_REFCOUNT)
static inline void
avro_refcount_set(volatile int *refcount, int value)
{
	*refcount = value;
}

static inline void
avro_refcount_inc(volatile int *refcount)
{
	if (*refcount != (int) -1) {
		*refcount += 1;
	}
}

static inline int
avro_refcount_dec(volatile int *refcount)
{
	if (*refcount != (int) -1) {
		*refcount -= 1;
		return (*refcount == 0);
	}
	return 0;
}

/*-----------------------------------------------------------------------
 * Mac OS X
 */

#elif __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 1050

#include <libkern/OSAtomic.h>

static inline void
avro_refcount_set(volatile int *refcount, int value)
{
	*refcount = value;
}

static inline void
avro_refcount_inc(volatile int *refcount)
{
	if (*refcount != (int) -1) {
		OSAtomicIncrement32(refcount);
	}
}

static inline int
avro_refcount_dec(volatile int *refcount)
{
	if (*refcount != (int) -1) {
		return (OSAtomicDecrement32(refcount) == 0);
	}
	return 0;
}


/*-----------------------------------------------------------------------
 * GCC intrinsics
 */

#elif (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) > 40500 \
|| defined(__clang__)

static inline void
avro_refcount_set(volatile int *refcount, int value)
{
	*refcount = value;
}

static inline void
avro_refcount_inc(volatile int *refcount)
{
	if (*refcount != (int) -1) {
		__sync_add_and_fetch(refcount, 1);
	}
}

static inline int
avro_refcount_dec(volatile int *refcount)
{
	if (*refcount != (int) -1) {
		return (__sync_sub_and_fetch(refcount, 1) == 0);
	}
	return 0;
}


/*-----------------------------------------------------------------------
 * Raw x86 assembly
 */

#elif defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))

/* determine the size of int */

#include <limits.h>
#include <avro/platform.h>
#if INT_MAX == INT32_MAX
#define REFCOUNT_SS "l"
#elif INT_MAX == INT64_MAX
#define REFCOUNT_SS "q"
#else
#error "Unknown int size"
#endif

static inline void
avro_refcount_set(volatile int *refcount, int value)
{
	*refcount = value;
}

static inline void
avro_refcount_inc(volatile int *refcount)
{
	if (*refcount != (int) -1) {
		__asm__ __volatile__ ("lock ; inc"REFCOUNT_SS" %0"
				      :"=m" (*refcount)
				      :"m" (*refcount));
	}
}

static inline int
avro_refcount_dec(volatile int *refcount)
{
	if (*refcount != (int) -1) {
		char result;
		__asm__ __volatile__ ("lock ; dec"REFCOUNT_SS" %0; setz %1"
				      :"=m" (*refcount), "=q" (result)
				      :"m" (*refcount));
		return result;
	}
	return 0;
}

#undef REFCOUNT_SS


/*-----------------------------------------------------------------------
 * Raw PPC assembly
 */

#elif defined(__GNUC__) && defined(__ppc__)

static inline int
avro_refcount_LL_int(volatile int *ptr)
{
	int val;
	__asm__ __volatile__ ("lwarx %[val],0,%[ptr]"
			      : [val] "=r" (val)
			      : [ptr] "r" (&ptr)
			      : "cc");

	return val;
}

/* Returns non-zero if the store was successful, zero otherwise. */
static inline int
avro_refcount_SC_int(volatile int *ptr, int val)
{
	int ret = 1; /* init to non-zero, will be reset to 0 if SC was successful */
	__asm__ __volatile__ ("stwcx. %[val],0,%[ptr];\n"
			      "beq 1f;\n"
			      "li %[ret], 0;\n"
			      "1: ;\n"
			      : [ret] "=r" (ret)
			      : [ptr] "r" (&ptr), [val] "r" (val), "0" (ret)
			      : "cc", "memory");
	return ret;
}

static inline void
avro_refcount_set(volatile int *refcount, int value)
{
	*refcount = value;
}

static inline void
avro_refcount_inc(volatile int *refcount)
{
	int prev;
	do {
		prev = avro_refcount_LL_int(refcount);
		if (prev == (int) -1) {
			return;
		}
	} while (!avro_refcount_SC_int(refcount, prev + 1));
}

static inline int
avro_refcount_dec(volatile int *refcount)
{
	int prev;
	do {
		prev = avro_refcount_LL_int(refcount);
		if (prev == (int) -1) {
			return 0;
		}
	} while (!avro_refcount_SC_int(refcount, prev - 1));
	return prev == 1;
}


/*-----------------------------------------------------------------------
 * Windows intrinsics
 */
#elif defined(_WIN32)

#ifdef __cplusplus
// Note: <intrin.h> included outside the extern "C" wrappers above
#else
#include <windows.h>
#include <intrin.h>
#endif // __cplusplus

static inline void
avro_refcount_set(volatile int *refcount, int value)
{
	*refcount = value;
}

static inline void
avro_refcount_inc(volatile int *refcount)
{
	if (*refcount != (int) -1) {
		_InterlockedIncrement((volatile long *) refcount);
	}
}

static inline int
avro_refcount_dec(volatile int *refcount)
{
	if (*refcount != (int) -1) {
		return (_InterlockedDecrement((volatile long *) refcount) == 0);
	}
	return 0;
}

/*-----------------------------------------------------------------------
 * Fallback
 */
#else
#error "No atomic implementation!"
#endif

CLOSE_EXTERN
#endif
