packages feed

Box2D-0.1.0.0: upstream/src/platform.h

// SPDX-FileCopyrightText: 2023 Erin Catto
// SPDX-License-Identifier: MIT

#pragma once

#include "core.h"

#include <stdbool.h>
#include <stdint.h>

#if defined( _MSC_VER ) && ( defined( _M_ARM ) || defined( _M_ARM64 ) || defined( _M_ARM64EC ) )
#include <intrin.h>
#elif defined( _MSC_VER )
#include <intrin0.h>
#endif

#if defined( _M_X64 ) || defined( __x86_64__ ) || defined( _M_IX86 ) || defined( __i386__ )
#include <xmmintrin.h>
#endif

#if defined( _MSC_VER )
#if defined( _M_X64 ) || defined( __x86_64__ ) || defined( _M_IX86 ) || defined( __i386__ )
#define b2Prefetch( addr ) _mm_prefetch( (const char*)( addr ), _MM_HINT_T0 )
#else
#define b2Prefetch( addr ) __prefetch( (const void*)( addr ) )
#endif
#elif defined( __GNUC__ ) || defined( __clang__ )
#define b2Prefetch( addr ) __builtin_prefetch( (const void*)( addr ), 0, 3 )
#else
#define b2Prefetch( addr ) ( (void)( addr ) )
#endif

#if ( defined( __GNUC__ ) || defined( __clang__ ) ) && ( defined( __i386__ ) || defined( __x86_64__ ) )
static inline void b2Pause( void )
{
	__asm__ __volatile__( "pause\n" );
}
#elif ( defined( __arm__ ) && defined( __ARM_ARCH ) && __ARM_ARCH >= 7 ) || defined( __aarch64__ )
static inline void b2Pause( void )
{
	__asm__ __volatile__( "yield" ::: "memory" );
}
#elif defined( _MSC_VER ) && ( defined( _M_IX86 ) || defined( _M_X64 ) )
static inline void b2Pause( void )
{
	_mm_pause();
}
#elif defined( _MSC_VER ) && ( defined( _M_ARM ) || defined( _M_ARM64 ) )
static inline void b2Pause( void )
{
	__yield();
}
#else
static inline void b2Pause( void )
{
}
#endif


static inline void b2AtomicStoreInt( b2AtomicInt* a, int value )
{
#if defined( _MSC_VER )
	(void)_InterlockedExchange( (long*)&a->value, value );
#elif defined( __GNUC__ ) || defined( __clang__ )
	__atomic_store_n( &a->value, value, __ATOMIC_SEQ_CST );
#else
#error "Unsupported platform"
#endif
}

static inline int b2AtomicLoadInt( b2AtomicInt* a )
{
#if defined( _MSC_VER ) && !defined( __clang__ )
	int value = __iso_volatile_load32( (volatile __int32*)&a->value );
#if defined( _M_ARM ) || defined( _M_ARM64 ) || defined( _M_ARM64EC )
	__dmb( 0xB );
#else
	_ReadWriteBarrier();
#endif
	return value;
#elif defined( __GNUC__ ) || defined( __clang__ )
	return __atomic_load_n( &a->value, __ATOMIC_SEQ_CST );
#else
#error "Unsupported platform"
#endif
}

static inline int b2AtomicFetchAddInt( b2AtomicInt* a, int increment )
{
#if defined( _MSC_VER )
	return _InterlockedExchangeAdd( (long*)&a->value, (long)increment );
#elif defined( __GNUC__ ) || defined( __clang__ )
	return __atomic_fetch_add( &a->value, increment, __ATOMIC_SEQ_CST );
#else
#error "Unsupported platform"
#endif
}

static inline uint16_t b2AtomicFetchOrU16( uint16_t* a, uint16_t mask )
{
#if defined( _MSC_VER )
	return (uint16_t)_InterlockedOr16( (short*)a, (short)mask );
#elif defined( __GNUC__ ) || defined( __clang__ )
	return __atomic_fetch_or( a, mask, __ATOMIC_SEQ_CST );
#else
#error "Unsupported platform"
#endif
}

static inline bool b2AtomicCompareExchangeInt( b2AtomicInt* a, int expected, int desired )
{
#if defined( _MSC_VER )
	return _InterlockedCompareExchange( (long*)&a->value, (long)desired, (long)expected ) == expected;
#elif defined( __GNUC__ ) || defined( __clang__ )
	// The value written to expected is ignored
	return __atomic_compare_exchange_n( &a->value, &expected, desired, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST );
#else
#error "Unsupported platform"
#endif
}

static inline void b2AtomicStoreU32( b2AtomicU32* a, uint32_t value )
{
#if defined( _MSC_VER )
	(void)_InterlockedExchange( (long*)&a->value, value );
#elif defined( __GNUC__ ) || defined( __clang__ )
	__atomic_store_n( &a->value, value, __ATOMIC_SEQ_CST );
#else
#error "Unsupported platform"
#endif
}

static inline uint16_t b2AtomicLoadU16( const uint16_t* a )
{
#if defined( _MSC_VER ) && !defined( __clang__ )
	return (uint16_t)__iso_volatile_load16( (const volatile __int16*)a );
#elif defined( __GNUC__ ) || defined( __clang__ )
	return __atomic_load_n( a, __ATOMIC_RELAXED );
#else
#error "Unsupported platform"
#endif
}

static inline uint32_t b2AtomicLoadU32( b2AtomicU32* a )
{
#if defined( _MSC_VER ) && !defined( __clang__ )
	uint32_t value = (uint32_t)__iso_volatile_load32( (volatile __int32*)&a->value );
#if defined( _M_ARM ) || defined( _M_ARM64 ) || defined( _M_ARM64EC )
	__dmb( 0xB );
#else
	_ReadWriteBarrier();
#endif
	return value;
#elif defined( __GNUC__ ) || defined( __clang__ )
	return __atomic_load_n( &a->value, __ATOMIC_SEQ_CST );
#else
#error "Unsupported platform"
#endif
}

static inline uint32_t b2AtomicLoadU32Raw( uint32_t* a )
{
#if defined( _MSC_VER ) && !defined( __clang__ )
	return (uint32_t)__iso_volatile_load32( (volatile __int32*)a );
#elif defined( __GNUC__ ) || defined( __clang__ )
	return __atomic_load_n( a, __ATOMIC_RELAXED );
#else
#error "Unsupported platform"
#endif
}

static inline uint32_t b2AtomicFetchOrU32( uint32_t* a, uint32_t mask )
{
#if defined( _MSC_VER )
	return (uint32_t)_InterlockedOr( (long*)a, (long)mask );
#elif defined( __GNUC__ ) || defined( __clang__ )
	return __atomic_fetch_or( a, mask, __ATOMIC_SEQ_CST );
#else
#error "Unsupported platform"
#endif
}

static inline int64_t b2AtomicFetchAddI64( b2AtomicI64* a, int64_t increment )
{
#if defined( _MSC_VER )
	return (int64_t)_InterlockedExchangeAdd64( (__int64*)&a->value, (__int64)increment );
#elif defined( __GNUC__ ) || defined( __clang__ )
	return __atomic_fetch_add( &a->value, increment, __ATOMIC_SEQ_CST );
#else
#error "Unsupported platform"
#endif
}

static inline int64_t b2AtomicLoadI64( b2AtomicI64* a )
{
#if defined( _MSC_VER ) && !defined( __clang__ ) && !defined( _M_ARM )
	int64_t value = __iso_volatile_load64( (volatile __int64*)&a->value );
#if defined( _M_ARM64 ) || defined( _M_ARM64EC )
	__dmb( 0xB );
#else
	_ReadWriteBarrier();
#endif
	return value;
#elif defined( _MSC_VER ) && !defined( __clang__ )
	// 32-bit ARM has no plain atomic 64-bit load
	return _InterlockedOr64( (__int64*)&a->value, 0 );
#elif defined( __GNUC__ ) || defined( __clang__ )
	return __atomic_load_n( &a->value, __ATOMIC_SEQ_CST );
#else
#error "Unsupported platform"
#endif
}

// Denormal flushing is a per thread mode the host can turn on, usually by linking a module
// built with fast math. This breaks determinism.
static inline bool b2IsDenormalFlushEnabled( void )
{
#if defined( _M_X64 ) || defined( __x86_64__ ) || defined( _M_IX86 ) || defined( __i386__ )
	// FTZ is bit 15, DAZ is bit 6
	uint32_t csr = _mm_getcsr();
	return ( csr & 0x8040 ) != 0;
#elif defined( _M_ARM64 ) || defined( __aarch64__ )
	// FZ is bit 24 and flushes denormal inputs and outputs together
	uint64_t fpcr;
#if defined( _MSC_VER )
	fpcr = _ReadStatusReg( ARM64_FPCR );
#else
	__asm__ __volatile__( "mrs %0, fpcr" : "=r"( fpcr ) );
#endif
	return ( fpcr & ( 1ull << 24 ) ) != 0;
#else
	// wasm has no flush mode
	return false;
#endif
}

#if defined( _MSC_VER ) && !defined( __clang__ )

#include <intrin0.h>

// https://en.wikipedia.org/wiki/Find_first_set

static inline uint32_t b2CTZ32( uint32_t block )
{
	unsigned long index;
	_BitScanForward( &index, block );
	return index;
}

// This function doesn't need to be fast, so using the Ivy Bridge fallback.
static inline uint32_t b2CLZ32( uint32_t value )
{
	#if 1

	// Use BSR (Bit Scan Reverse) which is available on Ivy Bridge
	unsigned long index;
	if ( _BitScanReverse( &index, value ) )
	{
		// BSR gives the index of the most significant 1-bit
		// We need to invert this to get the number of leading zeros
		return 31 - index;
	}
	else
	{
		// If x is 0, BSR sets the zero flag and doesn't modify index
		// LZCNT should return 32 for an input of 0
		return 32;
	}

	#else

	return __lzcnt( value );

	#endif
}

static inline uint32_t b2CTZ64( uint64_t block )
{
	unsigned long index;

	#ifdef _WIN64
	_BitScanForward64( &index, block );
	#else
	// 32-bit fall back
	if ( (uint32_t)block != 0 )
	{
		_BitScanForward( &index, (uint32_t)block );
	}
	else
	{
		_BitScanForward( &index, (uint32_t)( block >> 32 ) );
		index += 32;
	}
	#endif

	return index;
}

static inline int b2PopCount64( uint64_t block )
{
	return (int)__popcnt64( block );
}

#else

static inline uint32_t b2CTZ32( uint32_t block )
{
	return __builtin_ctz( block );
}

static inline uint32_t b2CLZ32( uint32_t value )
{
	return __builtin_clz( value );
}

static inline uint32_t b2CTZ64( uint64_t block )
{
	return __builtin_ctzll( block );
}

static inline int b2PopCount64( uint64_t block )
{
	return __builtin_popcountll( block );
}
#endif

static inline bool b2IsPowerOf2( int x )
{
	return ( x & ( x - 1 ) ) == 0;
}

static inline int b2BoundingPowerOf2( int x )
{
	if ( x <= 1 )
	{
		return 1;
	}

	return 32 - (int)b2CLZ32( (uint32_t)x - 1 );
}

static inline int b2RoundUpPowerOf2( int x )
{
	if ( x <= 1 )
	{
		return 1;
	}

	return 1 << ( 32 - (int)b2CLZ32( (uint32_t)x - 1 ) );
}