| /** |
| * The atomic module provides basic support for lock-free |
| * concurrent programming. |
| * |
| * Copyright: Copyright Sean Kelly 2005 - 2016. |
| * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) |
| * Authors: Sean Kelly, Alex Rønne Petersen |
| * Source: $(DRUNTIMESRC core/_atomic.d) |
| */ |
| |
| |
| /* NOTE: This file has been patched from the original DMD distribution to |
| * work with the GDC compiler. |
| */ |
| module core.atomic; |
| |
| version (D_InlineAsm_X86) |
| { |
| version = AsmX86; |
| version = AsmX86_32; |
| enum has64BitCAS = true; |
| enum has128BitCAS = false; |
| } |
| else version (D_InlineAsm_X86_64) |
| { |
| version = AsmX86; |
| version = AsmX86_64; |
| enum has64BitCAS = true; |
| enum has128BitCAS = true; |
| } |
| else version (GNU) |
| { |
| import gcc.config; |
| enum has64BitCAS = GNU_Have_64Bit_Atomics; |
| enum has128BitCAS = GNU_Have_LibAtomic; |
| } |
| else |
| { |
| enum has64BitCAS = false; |
| enum has128BitCAS = false; |
| } |
| |
| private |
| { |
| template HeadUnshared(T) |
| { |
| static if ( is( T U : shared(U*) ) ) |
| alias shared(U)* HeadUnshared; |
| else |
| alias T HeadUnshared; |
| } |
| } |
| |
| |
| version (AsmX86) |
| { |
| // NOTE: Strictly speaking, the x86 supports atomic operations on |
| // unaligned values. However, this is far slower than the |
| // common case, so such behavior should be prohibited. |
| private bool atomicValueIsProperlyAligned(T)( ref T val ) pure nothrow @nogc @trusted |
| { |
| return atomicPtrIsProperlyAligned(&val); |
| } |
| |
| private bool atomicPtrIsProperlyAligned(T)( T* ptr ) pure nothrow @nogc @safe |
| { |
| // NOTE: 32 bit x86 systems support 8 byte CAS, which only requires |
| // 4 byte alignment, so use size_t as the align type here. |
| static if ( T.sizeof > size_t.sizeof ) |
| return cast(size_t)ptr % size_t.sizeof == 0; |
| else |
| return cast(size_t)ptr % T.sizeof == 0; |
| } |
| } |
| |
| |
| version (CoreDdoc) |
| { |
| /** |
| * Performs the binary operation 'op' on val using 'mod' as the modifier. |
| * |
| * Params: |
| * val = The target variable. |
| * mod = The modifier to apply. |
| * |
| * Returns: |
| * The result of the operation. |
| */ |
| HeadUnshared!(T) atomicOp(string op, T, V1)( ref shared T val, V1 mod ) pure nothrow @nogc @safe |
| if ( __traits( compiles, mixin( "*cast(T*)&val" ~ op ~ "mod" ) ) ) |
| { |
| return HeadUnshared!(T).init; |
| } |
| |
| |
| /** |
| * Stores 'writeThis' to the memory referenced by 'here' if the value |
| * referenced by 'here' is equal to 'ifThis'. This operation is both |
| * lock-free and atomic. |
| * |
| * Params: |
| * here = The address of the destination variable. |
| * writeThis = The value to store. |
| * ifThis = The comparison value. |
| * |
| * Returns: |
| * true if the store occurred, false if not. |
| */ |
| bool cas(T,V1,V2)( shared(T)* here, const V1 ifThis, V2 writeThis ) pure nothrow @nogc @safe |
| if ( !is(T == class) && !is(T U : U*) && __traits( compiles, { *here = writeThis; } ) ); |
| |
| /// Ditto |
| bool cas(T,V1,V2)( shared(T)* here, const shared(V1) ifThis, shared(V2) writeThis ) pure nothrow @nogc @safe |
| if ( is(T == class) && __traits( compiles, { *here = writeThis; } ) ); |
| |
| /// Ditto |
| bool cas(T,V1,V2)( shared(T)* here, const shared(V1)* ifThis, shared(V2)* writeThis ) pure nothrow @nogc @safe |
| if ( is(T U : U*) && __traits( compiles, { *here = writeThis; } ) ); |
| |
| /** |
| * Loads 'val' from memory and returns it. The memory barrier specified |
| * by 'ms' is applied to the operation, which is fully sequenced by |
| * default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq, |
| * and MemoryOrder.seq. |
| * |
| * Params: |
| * val = The target variable. |
| * |
| * Returns: |
| * The value of 'val'. |
| */ |
| HeadUnshared!(T) atomicLoad(MemoryOrder ms = MemoryOrder.seq,T)( ref const shared T val ) pure nothrow @nogc @safe |
| { |
| return HeadUnshared!(T).init; |
| } |
| |
| |
| /** |
| * Writes 'newval' into 'val'. The memory barrier specified by 'ms' is |
| * applied to the operation, which is fully sequenced by default. |
| * Valid memory orders are MemoryOrder.raw, MemoryOrder.rel, and |
| * MemoryOrder.seq. |
| * |
| * Params: |
| * val = The target variable. |
| * newval = The value to store. |
| */ |
| void atomicStore(MemoryOrder ms = MemoryOrder.seq,T,V1)( ref shared T val, V1 newval ) pure nothrow @nogc @safe |
| if ( __traits( compiles, { val = newval; } ) ) |
| { |
| |
| } |
| |
| |
| /** |
| * Specifies the memory ordering semantics of an atomic operation. |
| */ |
| enum MemoryOrder |
| { |
| raw, /// Not sequenced. |
| acq, /// Hoist-load + hoist-store barrier. |
| rel, /// Sink-load + sink-store barrier. |
| seq, /// Fully sequenced (acquire + release). |
| } |
| |
| deprecated("Please use MemoryOrder instead.") |
| alias MemoryOrder msync; |
| |
| /** |
| * Inserts a full load/store memory fence (on platforms that need it). This ensures |
| * that all loads and stores before a call to this function are executed before any |
| * loads and stores after the call. |
| */ |
| void atomicFence() nothrow @nogc; |
| } |
| else version (AsmX86_32) |
| { |
| // Uses specialized asm for fast fetch and add operations |
| private HeadUnshared!(T) atomicFetchAdd(T)( ref shared T val, size_t mod ) pure nothrow @nogc @safe |
| if ( T.sizeof <= 4 ) |
| { |
| size_t tmp = mod; |
| asm pure nothrow @nogc @trusted |
| { |
| mov EAX, tmp; |
| mov EDX, val; |
| } |
| static if (T.sizeof == 1) asm pure nothrow @nogc @trusted { lock; xadd[EDX], AL; } |
| else static if (T.sizeof == 2) asm pure nothrow @nogc @trusted { lock; xadd[EDX], AX; } |
| else static if (T.sizeof == 4) asm pure nothrow @nogc @trusted { lock; xadd[EDX], EAX; } |
| |
| asm pure nothrow @nogc @trusted |
| { |
| mov tmp, EAX; |
| } |
| |
| return cast(T)tmp; |
| } |
| |
| private HeadUnshared!(T) atomicFetchSub(T)( ref shared T val, size_t mod ) pure nothrow @nogc @safe |
| if ( T.sizeof <= 4) |
| { |
| return atomicFetchAdd(val, -mod); |
| } |
| |
| HeadUnshared!(T) atomicOp(string op, T, V1)( ref shared T val, V1 mod ) pure nothrow @nogc |
| if ( __traits( compiles, mixin( "*cast(T*)&val" ~ op ~ "mod" ) ) ) |
| in |
| { |
| assert(atomicValueIsProperlyAligned(val)); |
| } |
| body |
| { |
| // binary operators |
| // |
| // + - * / % ^^ & |
| // | ^ << >> >>> ~ in |
| // == != < <= > >= |
| static if ( op == "+" || op == "-" || op == "*" || op == "/" || |
| op == "%" || op == "^^" || op == "&" || op == "|" || |
| op == "^" || op == "<<" || op == ">>" || op == ">>>" || |
| op == "~" || // skip "in" |
| op == "==" || op == "!=" || op == "<" || op == "<=" || |
| op == ">" || op == ">=" ) |
| { |
| HeadUnshared!(T) get = atomicLoad!(MemoryOrder.raw)( val ); |
| mixin( "return get " ~ op ~ " mod;" ); |
| } |
| else |
| // assignment operators |
| // |
| // += -= *= /= %= ^^= &= |
| // |= ^= <<= >>= >>>= ~= |
| static if ( op == "+=" && __traits(isIntegral, T) && T.sizeof <= 4 && V1.sizeof <= 4) |
| { |
| return cast(T)(atomicFetchAdd!(T)(val, mod) + mod); |
| } |
| else static if ( op == "-=" && __traits(isIntegral, T) && T.sizeof <= 4 && V1.sizeof <= 4) |
| { |
| return cast(T)(atomicFetchSub!(T)(val, mod) - mod); |
| } |
| else static if ( op == "+=" || op == "-=" || op == "*=" || op == "/=" || |
| op == "%=" || op == "^^=" || op == "&=" || op == "|=" || |
| op == "^=" || op == "<<=" || op == ">>=" || op == ">>>=" ) // skip "~=" |
| { |
| HeadUnshared!(T) get, set; |
| |
| do |
| { |
| get = set = atomicLoad!(MemoryOrder.raw)( val ); |
| mixin( "set " ~ op ~ " mod;" ); |
| } while ( !casByRef( val, get, set ) ); |
| return set; |
| } |
| else |
| { |
| static assert( false, "Operation not supported." ); |
| } |
| } |
| |
| bool casByRef(T,V1,V2)( ref T value, V1 ifThis, V2 writeThis ) pure nothrow @nogc @trusted |
| { |
| return cas(&value, ifThis, writeThis); |
| } |
| |
| bool cas(T,V1,V2)( shared(T)* here, const V1 ifThis, V2 writeThis ) pure nothrow @nogc @safe |
| if ( !is(T == class) && !is(T U : U*) && __traits( compiles, { *here = writeThis; } ) ) |
| { |
| return casImpl(here, ifThis, writeThis); |
| } |
| |
| bool cas(T,V1,V2)( shared(T)* here, const shared(V1) ifThis, shared(V2) writeThis ) pure nothrow @nogc @safe |
| if ( is(T == class) && __traits( compiles, { *here = writeThis; } ) ) |
| { |
| return casImpl(here, ifThis, writeThis); |
| } |
| |
| bool cas(T,V1,V2)( shared(T)* here, const shared(V1)* ifThis, shared(V2)* writeThis ) pure nothrow @nogc @safe |
| if ( is(T U : U*) && __traits( compiles, { *here = writeThis; } ) ) |
| { |
| return casImpl(here, ifThis, writeThis); |
| } |
| |
| private bool casImpl(T,V1,V2)( shared(T)* here, V1 ifThis, V2 writeThis ) pure nothrow @nogc @safe |
| in |
| { |
| assert( atomicPtrIsProperlyAligned( here ) ); |
| } |
| body |
| { |
| static if ( T.sizeof == byte.sizeof ) |
| { |
| ////////////////////////////////////////////////////////////////// |
| // 1 Byte CAS |
| ////////////////////////////////////////////////////////////////// |
| |
| asm pure nothrow @nogc @trusted |
| { |
| mov DL, writeThis; |
| mov AL, ifThis; |
| mov ECX, here; |
| lock; // lock always needed to make this op atomic |
| cmpxchg [ECX], DL; |
| setz AL; |
| } |
| } |
| else static if ( T.sizeof == short.sizeof ) |
| { |
| ////////////////////////////////////////////////////////////////// |
| // 2 Byte CAS |
| ////////////////////////////////////////////////////////////////// |
| |
| asm pure nothrow @nogc @trusted |
| { |
| mov DX, writeThis; |
| mov AX, ifThis; |
| mov ECX, here; |
| lock; // lock always needed to make this op atomic |
| cmpxchg [ECX], DX; |
| setz AL; |
| } |
| } |
| else static if ( T.sizeof == int.sizeof ) |
| { |
| ////////////////////////////////////////////////////////////////// |
| // 4 Byte CAS |
| ////////////////////////////////////////////////////////////////// |
| |
| asm pure nothrow @nogc @trusted |
| { |
| mov EDX, writeThis; |
| mov EAX, ifThis; |
| mov ECX, here; |
| lock; // lock always needed to make this op atomic |
| cmpxchg [ECX], EDX; |
| setz AL; |
| } |
| } |
| else static if ( T.sizeof == long.sizeof && has64BitCAS ) |
| { |
| |
| ////////////////////////////////////////////////////////////////// |
| // 8 Byte CAS on a 32-Bit Processor |
| ////////////////////////////////////////////////////////////////// |
| |
| asm pure nothrow @nogc @trusted |
| { |
| push EDI; |
| push EBX; |
| lea EDI, writeThis; |
| mov EBX, [EDI]; |
| mov ECX, 4[EDI]; |
| lea EDI, ifThis; |
| mov EAX, [EDI]; |
| mov EDX, 4[EDI]; |
| mov EDI, here; |
| lock; // lock always needed to make this op atomic |
| cmpxchg8b [EDI]; |
| setz AL; |
| pop EBX; |
| pop EDI; |
| |
| } |
| |
| } |
| else |
| { |
| static assert( false, "Invalid template type specified." ); |
| } |
| } |
| |
| |
| enum MemoryOrder |
| { |
| raw, |
| acq, |
| rel, |
| seq, |
| } |
| |
| deprecated("Please use MemoryOrder instead.") |
| alias MemoryOrder msync; |
| |
| |
| private |
| { |
| // NOTE: x86 loads implicitly have acquire semantics so a memory |
| // barrier is only necessary on releases. |
| template needsLoadBarrier( MemoryOrder ms ) |
| { |
| enum bool needsLoadBarrier = ms == MemoryOrder.seq; |
| } |
| |
| |
| // NOTE: x86 stores implicitly have release semantics so a memory |
| // barrier is only necessary on acquires. |
| template needsStoreBarrier( MemoryOrder ms ) |
| { |
| enum bool needsStoreBarrier = ms == MemoryOrder.seq; |
| } |
| } |
| |
| |
| HeadUnshared!(T) atomicLoad(MemoryOrder ms = MemoryOrder.seq, T)( ref const shared T val ) pure nothrow @nogc @safe |
| if (!__traits(isFloating, T)) |
| { |
| static assert( ms != MemoryOrder.rel, "invalid MemoryOrder for atomicLoad()" ); |
| static assert( __traits(isPOD, T), "argument to atomicLoad() must be POD" ); |
| |
| static if ( T.sizeof == byte.sizeof ) |
| { |
| ////////////////////////////////////////////////////////////////// |
| // 1 Byte Load |
| ////////////////////////////////////////////////////////////////// |
| |
| static if ( needsLoadBarrier!(ms) ) |
| { |
| asm pure nothrow @nogc @trusted |
| { |
| mov DL, 0; |
| mov AL, 0; |
| mov ECX, val; |
| lock; // lock always needed to make this op atomic |
| cmpxchg [ECX], DL; |
| } |
| } |
| else |
| { |
| asm pure nothrow @nogc @trusted |
| { |
| mov EAX, val; |
| mov AL, [EAX]; |
| } |
| } |
| } |
| else static if ( T.sizeof == short.sizeof ) |
| { |
| ////////////////////////////////////////////////////////////////// |
| // 2 Byte Load |
| ////////////////////////////////////////////////////////////////// |
| |
| static if ( needsLoadBarrier!(ms) ) |
| { |
| asm pure nothrow @nogc @trusted |
| { |
| mov DX, 0; |
| mov AX, 0; |
| mov ECX, val; |
| lock; // lock always needed to make this op atomic |
| cmpxchg [ECX], DX; |
| } |
| } |
| else |
| { |
| asm pure nothrow @nogc @trusted |
| { |
| mov EAX, val; |
| mov AX, [EAX]; |
| } |
| } |
| } |
| else static if ( T.sizeof == int.sizeof ) |
| { |
| ////////////////////////////////////////////////////////////////// |
| // 4 Byte Load |
| ////////////////////////////////////////////////////////////////// |
| |
| static if ( needsLoadBarrier!(ms) ) |
| { |
| asm pure nothrow @nogc @trusted |
| { |
| mov EDX, 0; |
| mov EAX, 0; |
| mov ECX, val; |
| lock; // lock always needed to make this op atomic |
| cmpxchg [ECX], EDX; |
| } |
| } |
| else |
| { |
| asm pure nothrow @nogc @trusted |
| { |
| mov EAX, val; |
| mov EAX, [EAX]; |
| } |
| } |
| } |
| else static if ( T.sizeof == long.sizeof && has64BitCAS ) |
| { |
| ////////////////////////////////////////////////////////////////// |
| // 8 Byte Load on a 32-Bit Processor |
| ////////////////////////////////////////////////////////////////// |
| |
| asm pure nothrow @nogc @trusted |
| { |
| push EDI; |
| push EBX; |
| mov EBX, 0; |
| mov ECX, 0; |
| mov EAX, 0; |
| mov EDX, 0; |
| mov EDI, val; |
| lock; // lock always needed to make this op atomic |
| cmpxchg8b [EDI]; |
| pop EBX; |
| pop EDI; |
| } |
| } |
| else |
| { |
| static assert( false, "Invalid template type specified." ); |
| } |
| } |
| |
| void atomicStore(MemoryOrder ms = MemoryOrder.seq, T, V1)( ref shared T val, V1 newval ) pure nothrow @nogc @safe |
| if ( __traits( compiles, { val = newval; } ) ) |
| { |
| static assert( ms != MemoryOrder.acq, "invalid MemoryOrder for atomicStore()" ); |
| static assert( __traits(isPOD, T), "argument to atomicStore() must be POD" ); |
| |
| static if ( T.sizeof == byte.sizeof ) |
| { |
| ////////////////////////////////////////////////////////////////// |
| // 1 Byte Store |
| ////////////////////////////////////////////////////////////////// |
| |
| static if ( needsStoreBarrier!(ms) ) |
| { |
| asm pure nothrow @nogc @trusted |
| { |
| mov EAX, val; |
| mov DL, newval; |
| lock; |
| xchg [EAX], DL; |
| } |
| } |
| else |
| { |
| asm pure nothrow @nogc @trusted |
| { |
| mov EAX, val; |
| mov DL, newval; |
| mov [EAX], DL; |
| } |
| } |
| } |
| else static if ( T.sizeof == short.sizeof ) |
| { |
| ////////////////////////////////////////////////////////////////// |
| // 2 Byte Store |
| ////////////////////////////////////////////////////////////////// |
| |
| static if ( needsStoreBarrier!(ms) ) |
| { |
| asm pure nothrow @nogc @trusted |
| { |
| mov EAX, val; |
| mov DX, newval; |
| lock; |
| xchg [EAX], DX; |
| } |
| } |
| else |
| { |
| asm pure nothrow @nogc @trusted |
| { |
| mov EAX, val; |
| mov DX, newval; |
| mov [EAX], DX; |
| } |
| } |
| } |
| else static if ( T.sizeof == int.sizeof ) |
| { |
| ////////////////////////////////////////////////////////////////// |
| // 4 Byte Store |
| ////////////////////////////////////////////////////////////////// |
| |
| static if ( needsStoreBarrier!(ms) ) |
| { |
| asm pure nothrow @nogc @trusted |
| { |
| mov EAX, val; |
| mov EDX, newval; |
| lock; |
| xchg [EAX], EDX; |
| } |
| } |
| else |
| { |
| asm pure nothrow @nogc @trusted |
| { |
| mov EAX, val; |
| mov EDX, newval; |
| mov [EAX], EDX; |
| } |
| } |
| } |
| else static if ( T.sizeof == long.sizeof && has64BitCAS ) |
| { |
| ////////////////////////////////////////////////////////////////// |
| // 8 Byte Store on a 32-Bit Processor |
| ////////////////////////////////////////////////////////////////// |
| |
| asm pure nothrow @nogc @trusted |
| { |
| push EDI; |
| push EBX; |
| lea EDI, newval; |
| mov EBX, [EDI]; |
| mov ECX, 4[EDI]; |
| mov EDI, val; |
| mov EAX, [EDI]; |
| mov EDX, 4[EDI]; |
| L1: lock; // lock always needed to make this op atomic |
| cmpxchg8b [EDI]; |
| jne L1; |
| pop EBX; |
| pop EDI; |
| } |
| } |
| else |
| { |
| static assert( false, "Invalid template type specified." ); |
| } |
| } |
| |
| |
| void atomicFence() nothrow @nogc @safe |
| { |
| import core.cpuid; |
| |
| asm pure nothrow @nogc @trusted |
| { |
| naked; |
| |
| call sse2; |
| test AL, AL; |
| jne Lcpuid; |
| |
| // Fast path: We have SSE2, so just use mfence. |
| mfence; |
| jmp Lend; |
| |
| Lcpuid: |
| |
| // Slow path: We use cpuid to serialize. This is |
| // significantly slower than mfence, but is the |
| // only serialization facility we have available |
| // on older non-SSE2 chips. |
| push EBX; |
| |
| mov EAX, 0; |
| cpuid; |
| |
| pop EBX; |
| |
| Lend: |
| |
| ret; |
| } |
| } |
| } |
| else version (AsmX86_64) |
| { |
| // Uses specialized asm for fast fetch and add operations |
| private HeadUnshared!(T) atomicFetchAdd(T)( ref shared T val, size_t mod ) pure nothrow @nogc @trusted |
| if ( __traits(isIntegral, T) ) |
| in |
| { |
| assert( atomicValueIsProperlyAligned(val)); |
| } |
| body |
| { |
| size_t tmp = mod; |
| asm pure nothrow @nogc @trusted |
| { |
| mov RAX, tmp; |
| mov RDX, val; |
| } |
| static if (T.sizeof == 1) asm pure nothrow @nogc @trusted { lock; xadd[RDX], AL; } |
| else static if (T.sizeof == 2) asm pure nothrow @nogc @trusted { lock; xadd[RDX], AX; } |
| else static if (T.sizeof == 4) asm pure nothrow @nogc @trusted { lock; xadd[RDX], EAX; } |
| else static if (T.sizeof == 8) asm pure nothrow @nogc @trusted { lock; xadd[RDX], RAX; } |
| |
| asm pure nothrow @nogc @trusted |
| { |
| mov tmp, RAX; |
| } |
| |
| return cast(T)tmp; |
| } |
| |
| private HeadUnshared!(T) atomicFetchSub(T)( ref shared T val, size_t mod ) pure nothrow @nogc @safe |
| if ( __traits(isIntegral, T) ) |
| { |
| return atomicFetchAdd(val, -mod); |
| } |
| |
| HeadUnshared!(T) atomicOp(string op, T, V1)( ref shared T val, V1 mod ) pure nothrow @nogc |
| if ( __traits( compiles, mixin( "*cast(T*)&val" ~ op ~ "mod" ) ) ) |
| in |
| { |
| assert( atomicValueIsProperlyAligned(val)); |
| } |
| body |
| { |
| // binary operators |
| // |
| // + - * / % ^^ & |
| // | ^ << >> >>> ~ in |
| // == != < <= > >= |
| static if ( op == "+" || op == "-" || op == "*" || op == "/" || |
| op == "%" || op == "^^" || op == "&" || op == "|" || |
| op == "^" || op == "<<" || op == ">>" || op == ">>>" || |
| op == "~" || // skip "in" |
| op == "==" || op == "!=" || op == "<" || op == "<=" || |
| op == ">" || op == ">=" ) |
| { |
| HeadUnshared!(T) get = atomicLoad!(MemoryOrder.raw)( val ); |
| mixin( "return get " ~ op ~ " mod;" ); |
| } |
| else |
| // assignment operators |
| // |
| // += -= *= /= %= ^^= &= |
| // |= ^= <<= >>= >>>= ~= |
| static if ( op == "+=" && __traits(isIntegral, T) && __traits(isIntegral, V1)) |
| { |
| return cast(T)(atomicFetchAdd!(T)(val, mod) + mod); |
| } |
| else static if ( op == "-=" && __traits(isIntegral, T) && __traits(isIntegral, V1)) |
| { |
| return cast(T)(atomicFetchSub!(T)(val, mod) - mod); |
| } |
| else static if ( op == "+=" || op == "-=" || op == "*=" || op == "/=" || |
| op == "%=" || op == "^^=" || op == "&=" || op == "|=" || |
| op == "^=" || op == "<<=" || op == ">>=" || op == ">>>=" ) // skip "~=" |
| { |
| HeadUnshared!(T) get, set; |
| |
| do |
| { |
| get = set = atomicLoad!(MemoryOrder.raw)( val ); |
| mixin( "set " ~ op ~ " mod;" ); |
| } while ( !casByRef( val, get, set ) ); |
| return set; |
| } |
| else |
| { |
| static assert( false, "Operation not supported." ); |
| } |
| } |
| |
| |
| bool casByRef(T,V1,V2)( ref T value, V1 ifThis, V2 writeThis ) pure nothrow @nogc @trusted |
| { |
| return cas(&value, ifThis, writeThis); |
| } |
| |
| bool cas(T,V1,V2)( shared(T)* here, const V1 ifThis, V2 writeThis ) pure nothrow @nogc @safe |
| if ( !is(T == class) && !is(T U : U*) && __traits( compiles, { *here = writeThis; } ) ) |
| { |
| return casImpl(here, ifThis, writeThis); |
| } |
| |
| bool cas(T,V1,V2)( shared(T)* here, const shared(V1) ifThis, shared(V2) writeThis ) pure nothrow @nogc @safe |
| if ( is(T == class) && __traits( compiles, { *here = writeThis; } ) ) |
| { |
| return casImpl(here, ifThis, writeThis); |
| } |
| |
| bool cas(T,V1,V2)( shared(T)* here, const shared(V1)* ifThis, shared(V2)* writeThis ) pure nothrow @nogc @safe |
| if ( is(T U : U*) && __traits( compiles, { *here = writeThis; } ) ) |
| { |
| return casImpl(here, ifThis, writeThis); |
| } |
| |
| private bool casImpl(T,V1,V2)( shared(T)* here, V1 ifThis, V2 writeThis ) pure nothrow @nogc @safe |
| in |
| { |
| assert( atomicPtrIsProperlyAligned( here ) ); |
| } |
| body |
| { |
| static if ( T.sizeof == byte.sizeof ) |
| { |
| ////////////////////////////////////////////////////////////////// |
| // 1 Byte CAS |
| ////////////////////////////////////////////////////////////////// |
| |
| asm pure nothrow @nogc @trusted |
| { |
| mov DL, writeThis; |
| mov AL, ifThis; |
| mov RCX, here; |
| lock; // lock always needed to make this op atomic |
| cmpxchg [RCX], DL; |
| setz AL; |
| } |
| } |
| else static if ( T.sizeof == short.sizeof ) |
| { |
| ////////////////////////////////////////////////////////////////// |
| // 2 Byte CAS |
| ////////////////////////////////////////////////////////////////// |
| |
| asm pure nothrow @nogc @trusted |
| { |
| mov DX, writeThis; |
| mov AX, ifThis; |
| mov RCX, here; |
| lock; // lock always needed to make this op atomic |
| cmpxchg [RCX], DX; |
| setz AL; |
| } |
| } |
| else static if ( T.sizeof == int.sizeof ) |
| { |
| ////////////////////////////////////////////////////////////////// |
| // 4 Byte CAS |
| ////////////////////////////////////////////////////////////////// |
| |
| asm pure nothrow @nogc @trusted |
| { |
| mov EDX, writeThis; |
| mov EAX, ifThis; |
| mov RCX, here; |
| lock; // lock always needed to make this op atomic |
| cmpxchg [RCX], EDX; |
| setz AL; |
| } |
| } |
| else static if ( T.sizeof == long.sizeof ) |
| { |
| ////////////////////////////////////////////////////////////////// |
| // 8 Byte CAS on a 64-Bit Processor |
| ////////////////////////////////////////////////////////////////// |
| |
| asm pure nothrow @nogc @trusted |
| { |
| mov RDX, writeThis; |
| mov RAX, ifThis; |
| mov RCX, here; |
| lock; // lock always needed to make this op atomic |
| cmpxchg [RCX], RDX; |
| setz AL; |
| } |
| } |
| else static if ( T.sizeof == long.sizeof*2 && has128BitCAS) |
| { |
| ////////////////////////////////////////////////////////////////// |
| // 16 Byte CAS on a 64-Bit Processor |
| ////////////////////////////////////////////////////////////////// |
| version (Win64){ |
| //Windows 64 calling convention uses different registers. |
| //DMD appears to reverse the register order. |
| asm pure nothrow @nogc @trusted |
| { |
| push RDI; |
| push RBX; |
| mov R9, writeThis; |
| mov R10, ifThis; |
| mov R11, here; |
| |
| mov RDI, R9; |
| mov RBX, [RDI]; |
| mov RCX, 8[RDI]; |
| |
| mov RDI, R10; |
| mov RAX, [RDI]; |
| mov RDX, 8[RDI]; |
| |
| mov RDI, R11; |
| lock; |
| cmpxchg16b [RDI]; |
| setz AL; |
| pop RBX; |
| pop RDI; |
| } |
| |
| }else{ |
| |
| asm pure nothrow @nogc @trusted |
| { |
| push RDI; |
| push RBX; |
| lea RDI, writeThis; |
| mov RBX, [RDI]; |
| mov RCX, 8[RDI]; |
| lea RDI, ifThis; |
| mov RAX, [RDI]; |
| mov RDX, 8[RDI]; |
| mov RDI, here; |
| lock; // lock always needed to make this op atomic |
| cmpxchg16b [RDI]; |
| setz AL; |
| pop RBX; |
| pop RDI; |
| } |
| } |
| } |
| else |
| { |
| static assert( false, "Invalid template type specified." ); |
| } |
| } |
| |
| |
| enum MemoryOrder |
| { |
| raw, |
| acq, |
| rel, |
| seq, |
| } |
| |
| deprecated("Please use MemoryOrder instead.") |
| alias MemoryOrder msync; |
| |
| |
| private |
| { |
| // NOTE: x86 loads implicitly have acquire semantics so a memory |
| // barrier is only necessary on releases. |
| template needsLoadBarrier( MemoryOrder ms ) |
| { |
| enum bool needsLoadBarrier = ms == MemoryOrder.seq; |
| } |
| |
| |
| // NOTE: x86 stores implicitly have release semantics so a memory |
| // barrier is only necessary on acquires. |
| template needsStoreBarrier( MemoryOrder ms ) |
| { |
| enum bool needsStoreBarrier = ms == MemoryOrder.seq; |
| } |
| } |
| |
| |
| HeadUnshared!(T) atomicLoad(MemoryOrder ms = MemoryOrder.seq, T)( ref const shared T val ) pure nothrow @nogc @safe |
| if (!__traits(isFloating, T)) |
| { |
| static assert( ms != MemoryOrder.rel, "invalid MemoryOrder for atomicLoad()" ); |
| static assert( __traits(isPOD, T), "argument to atomicLoad() must be POD" ); |
| |
| static if ( T.sizeof == byte.sizeof ) |
| { |
| ////////////////////////////////////////////////////////////////// |
| // 1 Byte Load |
| ////////////////////////////////////////////////////////////////// |
| |
| static if ( needsLoadBarrier!(ms) ) |
| { |
| asm pure nothrow @nogc @trusted |
| { |
| mov DL, 0; |
| mov AL, 0; |
| mov RCX, val; |
| lock; // lock always needed to make this op atomic |
| cmpxchg [RCX], DL; |
| } |
| } |
| else |
| { |
| asm pure nothrow @nogc @trusted |
| { |
| mov RAX, val; |
| mov AL, [RAX]; |
| } |
| } |
| } |
| else static if ( T.sizeof == short.sizeof ) |
| { |
| ////////////////////////////////////////////////////////////////// |
| // 2 Byte Load |
| ////////////////////////////////////////////////////////////////// |
| |
| static if ( needsLoadBarrier!(ms) ) |
| { |
| asm pure nothrow @nogc @trusted |
| { |
| mov DX, 0; |
| mov AX, 0; |
| mov RCX, val; |
| lock; // lock always needed to make this op atomic |
| cmpxchg [RCX], DX; |
| } |
| } |
| else |
| { |
| asm pure nothrow @nogc @trusted |
| { |
| mov RAX, val; |
| mov AX, [RAX]; |
| } |
| } |
| } |
| else static if ( T.sizeof == int.sizeof ) |
| { |
| ////////////////////////////////////////////////////////////////// |
| // 4 Byte Load |
| ////////////////////////////////////////////////////////////////// |
| |
| static if ( needsLoadBarrier!(ms) ) |
| { |
| asm pure nothrow @nogc @trusted |
| { |
| mov EDX, 0; |
| mov EAX, 0; |
| mov RCX, val; |
| lock; // lock always needed to make this op atomic |
| cmpxchg [RCX], EDX; |
| } |
| } |
| else |
| { |
| asm pure nothrow @nogc @trusted |
| { |
| mov RAX, val; |
| mov EAX, [RAX]; |
| } |
| } |
| } |
| else static if ( T.sizeof == long.sizeof ) |
| { |
| ////////////////////////////////////////////////////////////////// |
| // 8 Byte Load |
| ////////////////////////////////////////////////////////////////// |
| |
| static if ( needsLoadBarrier!(ms) ) |
| { |
| asm pure nothrow @nogc @trusted |
| { |
| mov RDX, 0; |
| mov RAX, 0; |
| mov RCX, val; |
| lock; // lock always needed to make this op atomic |
| cmpxchg [RCX], RDX; |
| } |
| } |
| else |
| { |
| asm pure nothrow @nogc @trusted |
| { |
| mov RAX, val; |
| mov RAX, [RAX]; |
| } |
| } |
| } |
| else static if ( T.sizeof == long.sizeof*2 && has128BitCAS ) |
| { |
| ////////////////////////////////////////////////////////////////// |
| // 16 Byte Load on a 64-Bit Processor |
| ////////////////////////////////////////////////////////////////// |
| version (Win64){ |
| size_t[2] retVal; |
| asm pure nothrow @nogc @trusted |
| { |
| push RDI; |
| push RBX; |
| mov RDI, val; |
| mov RBX, 0; |
| mov RCX, 0; |
| mov RAX, 0; |
| mov RDX, 0; |
| lock; // lock always needed to make this op atomic |
| cmpxchg16b [RDI]; |
| lea RDI, retVal; |
| mov [RDI], RAX; |
| mov 8[RDI], RDX; |
| pop RBX; |
| pop RDI; |
| } |
| |
| static if (is(T:U[], U)) |
| { |
| pragma(inline, true) |
| static typeof(return) toTrusted(size_t[2] retVal) @trusted |
| { |
| return *(cast(typeof(return)*) retVal.ptr); |
| } |
| |
| return toTrusted(retVal); |
| } |
| else |
| { |
| return cast(typeof(return)) retVal; |
| } |
| }else{ |
| asm pure nothrow @nogc @trusted |
| { |
| push RDI; |
| push RBX; |
| mov RBX, 0; |
| mov RCX, 0; |
| mov RAX, 0; |
| mov RDX, 0; |
| mov RDI, val; |
| lock; // lock always needed to make this op atomic |
| cmpxchg16b [RDI]; |
| pop RBX; |
| pop RDI; |
| } |
| } |
| } |
| else |
| { |
| static assert( false, "Invalid template type specified." ); |
| } |
| } |
| |
| |
| void atomicStore(MemoryOrder ms = MemoryOrder.seq, T, V1)( ref shared T val, V1 newval ) pure nothrow @nogc @safe |
| if ( __traits( compiles, { val = newval; } ) ) |
| { |
| static assert( ms != MemoryOrder.acq, "invalid MemoryOrder for atomicStore()" ); |
| static assert( __traits(isPOD, T), "argument to atomicStore() must be POD" ); |
| |
| static if ( T.sizeof == byte.sizeof ) |
| { |
| ////////////////////////////////////////////////////////////////// |
| // 1 Byte Store |
| ////////////////////////////////////////////////////////////////// |
| |
| static if ( needsStoreBarrier!(ms) ) |
| { |
| asm pure nothrow @nogc @trusted |
| { |
| mov RAX, val; |
| mov DL, newval; |
| lock; |
| xchg [RAX], DL; |
| } |
| } |
| else |
| { |
| asm pure nothrow @nogc @trusted |
| { |
| mov RAX, val; |
| mov DL, newval; |
| mov [RAX], DL; |
| } |
| } |
| } |
| else static if ( T.sizeof == short.sizeof ) |
| { |
| ////////////////////////////////////////////////////////////////// |
| // 2 Byte Store |
| ////////////////////////////////////////////////////////////////// |
| |
| static if ( needsStoreBarrier!(ms) ) |
| { |
| asm pure nothrow @nogc @trusted |
| { |
| mov RAX, val; |
| mov DX, newval; |
| lock; |
| xchg [RAX], DX; |
| } |
| } |
| else |
| { |
| asm pure nothrow @nogc @trusted |
| { |
| mov RAX, val; |
| mov DX, newval; |
| mov [RAX], DX; |
| } |
| } |
| } |
| else static if ( T.sizeof == int.sizeof ) |
| { |
| ////////////////////////////////////////////////////////////////// |
| // 4 Byte Store |
| ////////////////////////////////////////////////////////////////// |
| |
| static if ( needsStoreBarrier!(ms) ) |
| { |
| asm pure nothrow @nogc @trusted |
| { |
| mov RAX, val; |
| mov EDX, newval; |
| lock; |
| xchg [RAX], EDX; |
| } |
| } |
| else |
| { |
| asm pure nothrow @nogc @trusted |
| { |
| mov RAX, val; |
| mov EDX, newval; |
| mov [RAX], EDX; |
| } |
| } |
| } |
| else static if ( T.sizeof == long.sizeof && has64BitCAS ) |
| { |
| ////////////////////////////////////////////////////////////////// |
| // 8 Byte Store on a 64-Bit Processor |
| ////////////////////////////////////////////////////////////////// |
| |
| static if ( needsStoreBarrier!(ms) ) |
| { |
| asm pure nothrow @nogc @trusted |
| { |
| mov RAX, val; |
| mov RDX, newval; |
| lock; |
| xchg [RAX], RDX; |
| } |
| } |
| else |
| { |
| asm pure nothrow @nogc @trusted |
| { |
| mov RAX, val; |
| mov RDX, newval; |
| mov [RAX], RDX; |
| } |
| } |
| } |
| else static if ( T.sizeof == long.sizeof*2 && has128BitCAS ) |
| { |
| ////////////////////////////////////////////////////////////////// |
| // 16 Byte Store on a 64-Bit Processor |
| ////////////////////////////////////////////////////////////////// |
| version (Win64){ |
| asm pure nothrow @nogc @trusted |
| { |
| push RDI; |
| push RBX; |
| mov R9, val; |
| mov R10, newval; |
| |
| mov RDI, R10; |
| mov RBX, [RDI]; |
| mov RCX, 8[RDI]; |
| |
| mov RDI, R9; |
| mov RAX, [RDI]; |
| mov RDX, 8[RDI]; |
| |
| L1: lock; // lock always needed to make this op atomic |
| cmpxchg16b [RDI]; |
| jne L1; |
| pop RBX; |
| pop RDI; |
| } |
| }else{ |
| asm pure nothrow @nogc @trusted |
| { |
| push RDI; |
| push RBX; |
| lea RDI, newval; |
| mov RBX, [RDI]; |
| mov RCX, 8[RDI]; |
| mov RDI, val; |
| mov RAX, [RDI]; |
| mov RDX, 8[RDI]; |
| L1: lock; // lock always needed to make this op atomic |
| cmpxchg16b [RDI]; |
| jne L1; |
| pop RBX; |
| pop RDI; |
| } |
| } |
| } |
| else |
| { |
| static assert( false, "Invalid template type specified." ); |
| } |
| } |
| |
| |
| void atomicFence() nothrow @nogc @safe |
| { |
| // SSE2 is always present in 64-bit x86 chips. |
| asm nothrow @nogc @trusted |
| { |
| naked; |
| |
| mfence; |
| ret; |
| } |
| } |
| } |
| else version (GNU) |
| { |
| import gcc.builtins; |
| |
| HeadUnshared!(T) atomicOp(string op, T, V1)( ref shared T val, V1 mod ) pure nothrow @nogc @trusted |
| if ( __traits( compiles, mixin( "*cast(T*)&val" ~ op ~ "mod" ) ) ) |
| { |
| // binary operators |
| // |
| // + - * / % ^^ & |
| // | ^ << >> >>> ~ in |
| // == != < <= > >= |
| static if ( op == "+" || op == "-" || op == "*" || op == "/" || |
| op == "%" || op == "^^" || op == "&" || op == "|" || |
| op == "^" || op == "<<" || op == ">>" || op == ">>>" || |
| op == "~" || // skip "in" |
| op == "==" || op == "!=" || op == "<" || op == "<=" || |
| op == ">" || op == ">=" ) |
| { |
| HeadUnshared!(T) get = atomicLoad!(MemoryOrder.raw)( val ); |
| mixin( "return get " ~ op ~ " mod;" ); |
| } |
| else |
| // assignment operators |
| // |
| // += -= *= /= %= ^^= &= |
| // |= ^= <<= >>= >>>= ~= |
| static if ( op == "+=" || op == "-=" || op == "*=" || op == "/=" || |
| op == "%=" || op == "^^=" || op == "&=" || op == "|=" || |
| op == "^=" || op == "<<=" || op == ">>=" || op == ">>>=" ) // skip "~=" |
| { |
| HeadUnshared!(T) get, set; |
| |
| do |
| { |
| get = set = atomicLoad!(MemoryOrder.raw)( val ); |
| mixin( "set " ~ op ~ " mod;" ); |
| } while ( !cas( &val, get, set ) ); |
| return set; |
| } |
| else |
| { |
| static assert( false, "Operation not supported." ); |
| } |
| } |
| |
| |
| bool cas(T,V1,V2)( shared(T)* here, const V1 ifThis, V2 writeThis ) pure nothrow @nogc @safe |
| if ( !is(T == class) && !is(T U : U*) && __traits( compiles, { *here = writeThis; } ) ) |
| { |
| return casImpl(here, ifThis, writeThis); |
| } |
| |
| bool cas(T,V1,V2)( shared(T)* here, const shared(V1) ifThis, shared(V2) writeThis ) pure nothrow @nogc @safe |
| if ( is(T == class) && __traits( compiles, { *here = writeThis; } ) ) |
| { |
| return casImpl(here, ifThis, writeThis); |
| } |
| |
| bool cas(T,V1,V2)( shared(T)* here, const shared(V1)* ifThis, shared(V2)* writeThis ) pure nothrow @nogc @safe |
| if ( is(T U : U*) && __traits( compiles, { *here = writeThis; } ) ) |
| { |
| return casImpl(here, ifThis, writeThis); |
| } |
| |
| private bool casImpl(T,V1,V2)( shared(T)* here, V1 ifThis, V2 writeThis ) pure nothrow @nogc @trusted |
| { |
| bool res = void; |
| |
| static if (GNU_Have_Atomics || GNU_Have_LibAtomic) |
| { |
| static if (T.sizeof == byte.sizeof) |
| { |
| res = __atomic_compare_exchange_1(here, cast(void*) &ifThis, *cast(ubyte*) &writeThis, |
| false, MemoryOrder.seq, MemoryOrder.seq); |
| } |
| else static if (T.sizeof == short.sizeof) |
| { |
| res = __atomic_compare_exchange_2(here, cast(void*) &ifThis, *cast(ushort*) &writeThis, |
| false, MemoryOrder.seq, MemoryOrder.seq); |
| } |
| else static if (T.sizeof == int.sizeof) |
| { |
| res = __atomic_compare_exchange_4(here, cast(void*) &ifThis, *cast(uint*) &writeThis, |
| false, MemoryOrder.seq, MemoryOrder.seq); |
| } |
| else static if (T.sizeof == long.sizeof && GNU_Have_64Bit_Atomics) |
| { |
| res = __atomic_compare_exchange_8(here, cast(void*) &ifThis, *cast(ulong*) &writeThis, |
| false, MemoryOrder.seq, MemoryOrder.seq); |
| } |
| else static if (GNU_Have_LibAtomic) |
| { |
| res = __atomic_compare_exchange(T.sizeof, here, cast(void*) &ifThis, cast(void*) &writeThis, |
| MemoryOrder.seq, MemoryOrder.seq); |
| } |
| else |
| static assert(0, "Invalid template type specified."); |
| } |
| else |
| { |
| static if (T.sizeof == byte.sizeof) |
| alias U = byte; |
| else static if (T.sizeof == short.sizeof) |
| alias U = short; |
| else static if (T.sizeof == int.sizeof) |
| alias U = int; |
| else static if (T.sizeof == long.sizeof) |
| alias U = long; |
| else |
| static assert(0, "Invalid template type specified."); |
| |
| getAtomicMutex.lock(); |
| scope(exit) getAtomicMutex.unlock(); |
| |
| if (*cast(U*)here == *cast(U*)&ifThis) |
| { |
| *here = writeThis; |
| res = true; |
| } |
| else |
| res = false; |
| } |
| |
| return res; |
| } |
| |
| |
| // Memory model types for the __atomic* builtins. |
| enum MemoryOrder |
| { |
| raw = 0, |
| acq = 2, |
| rel = 3, |
| seq = 5, |
| } |
| |
| deprecated("Please use MemoryOrder instead.") |
| alias MemoryOrder msync; |
| |
| |
| HeadUnshared!(T) atomicLoad(MemoryOrder ms = MemoryOrder.seq, T)( ref const shared T val ) pure nothrow @nogc @trusted |
| if (!__traits(isFloating, T)) |
| { |
| static assert(ms != MemoryOrder.rel, "Invalid MemoryOrder for atomicLoad"); |
| static assert(__traits(isPOD, T), "argument to atomicLoad() must be POD"); |
| |
| static if (GNU_Have_Atomics || GNU_Have_LibAtomic) |
| { |
| static if (T.sizeof == ubyte.sizeof) |
| { |
| ubyte value = __atomic_load_1(&val, ms); |
| return *cast(HeadUnshared!T*) &value; |
| } |
| else static if (T.sizeof == ushort.sizeof) |
| { |
| ushort value = __atomic_load_2(&val, ms); |
| return *cast(HeadUnshared!T*) &value; |
| } |
| else static if (T.sizeof == uint.sizeof) |
| { |
| uint value = __atomic_load_4(&val, ms); |
| return *cast(HeadUnshared!T*) &value; |
| } |
| else static if (T.sizeof == ulong.sizeof && GNU_Have_64Bit_Atomics) |
| { |
| ulong value = __atomic_load_8(&val, ms); |
| return *cast(HeadUnshared!T*) &value; |
| } |
| else static if (GNU_Have_LibAtomic) |
| { |
| T value; |
| __atomic_load(T.sizeof, &val, cast(void*)&value, ms); |
| return *cast(HeadUnshared!T*) &value; |
| } |
| else |
| static assert(0, "Invalid template type specified."); |
| } |
| else |
| { |
| getAtomicMutex.lock(); |
| scope(exit) getAtomicMutex.unlock(); |
| return *cast(HeadUnshared!T*)&val; |
| } |
| } |
| |
| |
| void atomicStore(MemoryOrder ms = MemoryOrder.seq, T, V1)( ref shared T val, V1 newval ) pure nothrow @nogc @trusted |
| if ( __traits( compiles, { val = newval; } ) ) |
| { |
| static assert(ms != MemoryOrder.acq, "Invalid MemoryOrder for atomicStore"); |
| static assert(__traits(isPOD, T), "argument to atomicLoad() must be POD"); |
| |
| static if (GNU_Have_Atomics || GNU_Have_LibAtomic) |
| { |
| static if (T.sizeof == ubyte.sizeof) |
| { |
| __atomic_store_1(&val, *cast(ubyte*) &newval, ms); |
| } |
| else static if (T.sizeof == ushort.sizeof) |
| { |
| __atomic_store_2(&val, *cast(ushort*) &newval, ms); |
| } |
| else static if (T.sizeof == uint.sizeof) |
| { |
| __atomic_store_4(&val, *cast(uint*) &newval, ms); |
| } |
| else static if (T.sizeof == ulong.sizeof && GNU_Have_64Bit_Atomics) |
| { |
| __atomic_store_8(&val, *cast(ulong*) &newval, ms); |
| } |
| else static if (GNU_Have_LibAtomic) |
| { |
| __atomic_store(T.sizeof, &val, cast(void*)&newval, ms); |
| } |
| else |
| static assert(0, "Invalid template type specified."); |
| } |
| else |
| { |
| getAtomicMutex.lock(); |
| val = newval; |
| getAtomicMutex.unlock(); |
| } |
| } |
| |
| |
| void atomicFence() nothrow @nogc |
| { |
| static if (GNU_Have_Atomics || GNU_Have_LibAtomic) |
| __atomic_thread_fence(MemoryOrder.seq); |
| else |
| { |
| getAtomicMutex.lock(); |
| getAtomicMutex.unlock(); |
| } |
| } |
| |
| static if (!GNU_Have_Atomics && !GNU_Have_LibAtomic) |
| { |
| // Use system mutex for atomics, faking the purity of the functions so |
| // that they can be used in pure/nothrow/@safe code. |
| extern (C) private pure @trusted @nogc nothrow |
| { |
| static if (GNU_Thread_Model == ThreadModel.Posix) |
| { |
| import core.sys.posix.pthread; |
| alias atomicMutexHandle = pthread_mutex_t; |
| |
| pragma(mangle, "pthread_mutex_init") int fakePureMutexInit(pthread_mutex_t*, pthread_mutexattr_t*); |
| pragma(mangle, "pthread_mutex_lock") int fakePureMutexLock(pthread_mutex_t*); |
| pragma(mangle, "pthread_mutex_unlock") int fakePureMutexUnlock(pthread_mutex_t*); |
| } |
| else static if (GNU_Thread_Model == ThreadModel.Win32) |
| { |
| import core.sys.windows.winbase; |
| alias atomicMutexHandle = CRITICAL_SECTION; |
| |
| pragma(mangle, "InitializeCriticalSection") int fakePureMutexInit(CRITICAL_SECTION*); |
| pragma(mangle, "EnterCriticalSection") void fakePureMutexLock(CRITICAL_SECTION*); |
| pragma(mangle, "LeaveCriticalSection") int fakePureMutexUnlock(CRITICAL_SECTION*); |
| } |
| else |
| { |
| alias atomicMutexHandle = int; |
| } |
| } |
| |
| // Implements lock/unlock operations. |
| private struct AtomicMutex |
| { |
| int lock() pure @trusted @nogc nothrow |
| { |
| static if (GNU_Thread_Model == ThreadModel.Posix) |
| { |
| if (!_inited) |
| { |
| fakePureMutexInit(&_handle, null); |
| _inited = true; |
| } |
| return fakePureMutexLock(&_handle); |
| } |
| else |
| { |
| static if (GNU_Thread_Model == ThreadModel.Win32) |
| { |
| if (!_inited) |
| { |
| fakePureMutexInit(&_handle); |
| _inited = true; |
| } |
| fakePureMutexLock(&_handle); |
| } |
| return 0; |
| } |
| } |
| |
| int unlock() pure @trusted @nogc nothrow |
| { |
| static if (GNU_Thread_Model == ThreadModel.Posix) |
| return fakePureMutexUnlock(&_handle); |
| else |
| { |
| static if (GNU_Thread_Model == ThreadModel.Win32) |
| fakePureMutexUnlock(&_handle); |
| return 0; |
| } |
| } |
| |
| private: |
| atomicMutexHandle _handle; |
| bool _inited; |
| } |
| |
| // Internal static mutex reference. |
| private AtomicMutex* _getAtomicMutex() @trusted @nogc nothrow |
| { |
| __gshared static AtomicMutex mutex; |
| return &mutex; |
| } |
| |
| // Pure alias for _getAtomicMutex. |
| pragma(mangle, _getAtomicMutex.mangleof) |
| private AtomicMutex* getAtomicMutex() pure @trusted @nogc nothrow @property; |
| } |
| } |
| |
| // This is an ABI adapter that works on all architectures. It type puns |
| // floats and doubles to ints and longs, atomically loads them, then puns |
| // them back. This is necessary so that they get returned in floating |
| // point instead of integer registers. |
| HeadUnshared!(T) atomicLoad(MemoryOrder ms = MemoryOrder.seq, T)( ref const shared T val ) pure nothrow @nogc @trusted |
| if (__traits(isFloating, T)) |
| { |
| static if (T.sizeof == int.sizeof) |
| { |
| static assert(is(T : float)); |
| auto ptr = cast(const shared int*) &val; |
| auto asInt = atomicLoad!(ms)(*ptr); |
| return *(cast(typeof(return)*) &asInt); |
| } |
| else static if (T.sizeof == long.sizeof) |
| { |
| static assert(is(T : double)); |
| auto ptr = cast(const shared long*) &val; |
| auto asLong = atomicLoad!(ms)(*ptr); |
| return *(cast(typeof(return)*) &asLong); |
| } |
| else |
| { |
| static assert(0, "Cannot atomically load 80-bit reals."); |
| } |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // Unit Tests |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| |
| version (unittest) |
| { |
| void testCAS(T)( T val ) pure nothrow @nogc @trusted |
| in |
| { |
| assert(val !is T.init); |
| } |
| body |
| { |
| T base = cast(T)null; |
| shared(T) atom = cast(shared(T))null; |
| |
| assert( base !is val, T.stringof ); |
| assert( atom is base, T.stringof ); |
| |
| assert( cas( &atom, base, val ), T.stringof ); |
| assert( atom is val, T.stringof ); |
| assert( !cas( &atom, base, base ), T.stringof ); |
| assert( atom is val, T.stringof ); |
| } |
| |
| void testLoadStore(MemoryOrder ms = MemoryOrder.seq, T)( T val = T.init + 1 ) pure nothrow @nogc @trusted |
| { |
| T base = cast(T) 0; |
| shared(T) atom = cast(T) 0; |
| |
| assert( base !is val ); |
| assert( atom is base ); |
| atomicStore!(ms)( atom, val ); |
| base = atomicLoad!(ms)( atom ); |
| |
| assert( base is val, T.stringof ); |
| assert( atom is val ); |
| } |
| |
| |
| void testType(T)( T val = T.init + 1 ) pure nothrow @nogc @safe |
| { |
| testCAS!(T)( val ); |
| testLoadStore!(MemoryOrder.seq, T)( val ); |
| testLoadStore!(MemoryOrder.raw, T)( val ); |
| } |
| |
| @safe pure nothrow unittest |
| { |
| testType!(bool)(); |
| |
| testType!(byte)(); |
| testType!(ubyte)(); |
| |
| testType!(short)(); |
| testType!(ushort)(); |
| |
| testType!(int)(); |
| testType!(uint)(); |
| |
| testType!(shared int*)(); |
| |
| static class Klass {} |
| testCAS!(shared Klass)( new shared(Klass) ); |
| |
| testType!(float)(1.0f); |
| |
| static if ( has64BitCAS ) |
| { |
| testType!(double)(1.0); |
| testType!(long)(); |
| testType!(ulong)(); |
| } |
| |
| shared(size_t) i; |
| |
| atomicOp!"+="( i, cast(size_t) 1 ); |
| assert( i == 1 ); |
| |
| atomicOp!"-="( i, cast(size_t) 1 ); |
| assert( i == 0 ); |
| |
| shared float f = 0; |
| atomicOp!"+="( f, 1 ); |
| assert( f == 1 ); |
| |
| static if ( has64BitCAS ) |
| { |
| shared double d = 0; |
| atomicOp!"+="( d, 1 ); |
| assert( d == 1 ); |
| } |
| } |
| |
| pure nothrow unittest |
| { |
| static if (has128BitCAS) |
| { |
| struct DoubleValue |
| { |
| long value1; |
| long value2; |
| } |
| |
| align(16) shared DoubleValue a; |
| atomicStore(a, DoubleValue(1,2)); |
| assert(a.value1 == 1 && a.value2 ==2); |
| |
| while (!cas(&a, DoubleValue(1,2), DoubleValue(3,4))){} |
| assert(a.value1 == 3 && a.value2 ==4); |
| |
| align(16) DoubleValue b = atomicLoad(a); |
| assert(b.value1 == 3 && b.value2 ==4); |
| } |
| |
| version (D_LP64) |
| { |
| enum hasDWCAS = has128BitCAS; |
| } |
| else |
| { |
| enum hasDWCAS = has64BitCAS; |
| } |
| |
| static if (hasDWCAS) |
| { |
| static struct List { size_t gen; List* next; } |
| shared(List) head; |
| assert(cas(&head, shared(List)(0, null), shared(List)(1, cast(List*)1))); |
| assert(head.gen == 1); |
| assert(cast(size_t)head.next == 1); |
| } |
| } |
| |
| pure nothrow unittest |
| { |
| static struct S { int val; } |
| auto s = shared(S)(1); |
| |
| shared(S*) ptr; |
| |
| // head unshared |
| shared(S)* ifThis = null; |
| shared(S)* writeThis = &s; |
| assert(ptr is null); |
| assert(cas(&ptr, ifThis, writeThis)); |
| assert(ptr is writeThis); |
| |
| // head shared |
| shared(S*) ifThis2 = writeThis; |
| shared(S*) writeThis2 = null; |
| assert(cas(&ptr, ifThis2, writeThis2)); |
| assert(ptr is null); |
| |
| // head unshared target doesn't want atomic CAS |
| shared(S)* ptr2; |
| static assert(!__traits(compiles, cas(&ptr2, ifThis, writeThis))); |
| static assert(!__traits(compiles, cas(&ptr2, ifThis2, writeThis2))); |
| } |
| |
| unittest |
| { |
| import core.thread; |
| |
| // Use heap memory to ensure an optimizing |
| // compiler doesn't put things in registers. |
| uint* x = new uint(); |
| bool* f = new bool(); |
| uint* r = new uint(); |
| |
| auto thr = new Thread(() |
| { |
| while (!*f) |
| { |
| } |
| |
| atomicFence(); |
| |
| *r = *x; |
| }); |
| |
| thr.start(); |
| |
| *x = 42; |
| |
| atomicFence(); |
| |
| *f = true; |
| |
| atomicFence(); |
| |
| thr.join(); |
| |
| assert(*r == 42); |
| } |
| |
| // === atomicFetchAdd and atomicFetchSub operations ==== |
| pure nothrow @nogc @safe unittest |
| { |
| shared ubyte u8 = 1; |
| shared ushort u16 = 2; |
| shared uint u32 = 3; |
| shared byte i8 = 5; |
| shared short i16 = 6; |
| shared int i32 = 7; |
| |
| assert(atomicOp!"+="(u8, 8) == 9); |
| assert(atomicOp!"+="(u16, 8) == 10); |
| assert(atomicOp!"+="(u32, 8) == 11); |
| assert(atomicOp!"+="(i8, 8) == 13); |
| assert(atomicOp!"+="(i16, 8) == 14); |
| assert(atomicOp!"+="(i32, 8) == 15); |
| version (AsmX86_64) |
| { |
| shared ulong u64 = 4; |
| shared long i64 = 8; |
| assert(atomicOp!"+="(u64, 8) == 12); |
| assert(atomicOp!"+="(i64, 8) == 16); |
| } |
| } |
| |
| pure nothrow @nogc @safe unittest |
| { |
| shared ubyte u8 = 1; |
| shared ushort u16 = 2; |
| shared uint u32 = 3; |
| shared byte i8 = 5; |
| shared short i16 = 6; |
| shared int i32 = 7; |
| |
| assert(atomicOp!"-="(u8, 1) == 0); |
| assert(atomicOp!"-="(u16, 1) == 1); |
| assert(atomicOp!"-="(u32, 1) == 2); |
| assert(atomicOp!"-="(i8, 1) == 4); |
| assert(atomicOp!"-="(i16, 1) == 5); |
| assert(atomicOp!"-="(i32, 1) == 6); |
| version (AsmX86_64) |
| { |
| shared ulong u64 = 4; |
| shared long i64 = 8; |
| assert(atomicOp!"-="(u64, 1) == 3); |
| assert(atomicOp!"-="(i64, 1) == 7); |
| } |
| } |
| |
| pure nothrow @nogc @safe unittest // issue 16651 |
| { |
| shared ulong a = 2; |
| uint b = 1; |
| atomicOp!"-="( a, b ); |
| assert(a == 1); |
| |
| shared uint c = 2; |
| ubyte d = 1; |
| atomicOp!"-="( c, d ); |
| assert(c == 1); |
| } |
| } |