/**
 * Implementation of support routines for synchronized blocks.
 *
 * Copyright: Copyright Digital Mars 2000 - 2011.
 * License:   $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
 * Authors:   Walter Bright, Sean Kelly
 * Source: $(DRUNTIMESRC rt/_critical_.d)
 */

/*          Copyright Digital Mars 2000 - 2011.
 * Distributed under the Boost Software License, Version 1.0.
 *    (See accompanying file LICENSE or copy at
 *          http://www.boost.org/LICENSE_1_0.txt)
 */
module rt.critical_;

nothrow:

import rt.monitor_, core.atomic;

extern (C) void _d_critical_init() @nogc nothrow
{
    initMutex(cast(Mutex*)&gcs.mtx);
    atomicStore(head, &gcs);
}

extern (C) void _d_critical_term() @nogc nothrow
{
    // This function is only ever called by the runtime shutdown code
    // and therefore is single threaded so the following cast is fine.
    auto h = cast(D_CRITICAL_SECTION*) atomicLoad(head);
    for (auto p = h; p; p = p.next)
        destroyMutex(cast(Mutex*)&p.mtx);
}

extern (C) void _d_criticalenter(D_CRITICAL_SECTION* cs)
{
    assert(cs !is null);
    ensureMutex(cast(shared(D_CRITICAL_SECTION*)) cs);
    lockMutex(&cs.mtx);
}

extern (C) void _d_criticalenter2(D_CRITICAL_SECTION** pcs)
{
    if (atomicLoad!(MemoryOrder.acq)(*cast(shared) pcs) is null)
    {
        lockMutex(cast(Mutex*)&gcs.mtx);
        if (atomicLoad!(MemoryOrder.raw)(*cast(shared) pcs) is null)
        {
            auto cs = new shared D_CRITICAL_SECTION;
            initMutex(cast(Mutex*)&cs.mtx);
            atomicStore!(MemoryOrder.rel)(*cast(shared) pcs, cs);
        }
        unlockMutex(cast(Mutex*)&gcs.mtx);
    }
    lockMutex(&(*pcs).mtx);
}

extern (C) void _d_criticalexit(D_CRITICAL_SECTION* cs)
{
    assert(cs !is null);
    unlockMutex(&cs.mtx);
}

private:

shared D_CRITICAL_SECTION* head;
shared D_CRITICAL_SECTION gcs;

struct D_CRITICAL_SECTION
{
    D_CRITICAL_SECTION* next;
    Mutex mtx;
}

void ensureMutex(shared(D_CRITICAL_SECTION)* cs)
{
    if (atomicLoad!(MemoryOrder.acq)(cs.next) is null)
    {
        lockMutex(cast(Mutex*)&gcs.mtx);
        if (atomicLoad!(MemoryOrder.raw)(cs.next) is null)
        {
            initMutex(cast(Mutex*)&cs.mtx);
            auto ohead = atomicLoad(head);
            atomicStore(head, cs);
            atomicStore!(MemoryOrder.rel)(cs.next, ohead);
        }
        unlockMutex(cast(Mutex*)&gcs.mtx);
    }
}
