jack2/common/JackAtomicArrayState.h

262 lines
8.3 KiB
C++

/*
Copyright (C) 2004-2008 Grame
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifndef __JackAtomicArrayState__
#define __JackAtomicArrayState__
#include "JackAtomic.h"
#include "JackCompilerDeps.h"
#include <string.h> // for memcpy
#include <cstddef>
namespace Jack
{
/*!
\brief Counter for CAS
*/
PRE_PACKED_STRUCTURE
struct AtomicArrayCounter
{
union {
struct {
unsigned char fByteVal[4];
}
scounter;
UInt32 fLongVal;
}info;
AtomicArrayCounter()
{
info.fLongVal = 0;
}
AtomicArrayCounter(volatile const AtomicArrayCounter& obj)
{
info.fLongVal = obj.info.fLongVal;
}
AtomicArrayCounter(volatile AtomicArrayCounter& obj)
{
info.fLongVal = obj.info.fLongVal;
}
AtomicArrayCounter& operator=(volatile AtomicArrayCounter& obj)
{
info.fLongVal = obj.info.fLongVal;
return *this;
}
AtomicArrayCounter& operator=(AtomicArrayCounter& obj)
{
info.fLongVal = obj.info.fLongVal;
return *this;
}
} POST_PACKED_STRUCTURE;
#define Counter1(e) (e).info.fLongVal
#define GetIndex1(e, state) ((e).info.scounter.fByteVal[state])
#define SetIndex1(e, state, val) ((e).info.scounter.fByteVal[state] = val)
#define IncIndex1(e, state) ((e).info.scounter.fByteVal[state]++)
#define SwapIndex1(e, state) (((e).info.scounter.fByteVal[0] == state) ? 0 : state)
/*!
\brief A class to handle several states in a lock-free manner
Requirement:
- a "current" state
- several possible "pending" state
- an TrySwitchState(int state) operation to atomically switch a "pending" to the "current" state (the pending becomes the current).
The TrySwitchState operation returns a "current" state (either the same if switch fails or the new one, one can know if the switch has succeeded)
- a WriteNextStartState(int state) returns a "pending" state to be written into
- a WriteNextStartStop(int state) make the written "pending" state become "switchable"
Different pending states can be written independently and concurrently.
GetCurrentIndex() *must* return an increasing value to be able to check reading current state coherency
The fCounter is an array of indexes to access the current and 3 different "pending" states.
WriteNextStateStart(int index) must return a valid state to be written into, and must invalidate state "index" ==> cur state switch.
WriteNextStateStop(int index) makes the "index" state become "switchable" with the current state.
TrySwitchState(int index) must detect that pending state is a new state, and does the switch
ReadCurrentState() must return the state
GetCurrentIndex() must return an index increased each new switch.
WriteNextStateStart(int index1) and WriteNextStateStart(int index2) can be interleaved
[switch counter][index state][index state][cur index]
*/
// CHECK livelock
PRE_PACKED_STRUCTURE
template <class T>
class JackAtomicArrayState
{
protected:
// fState[0] ==> current
// fState[1] ==> pending
// fState[2] ==> request
T fState[3];
alignas(UInt32) alignas(AtomicArrayCounter) volatile AtomicArrayCounter fCounter;
UInt32 WriteNextStateStartAux(int state, bool* result)
{
AtomicArrayCounter old_val;
AtomicArrayCounter new_val;
UInt32 cur_index;
UInt32 next_index;
bool need_copy;
do {
old_val = fCounter;
new_val = old_val;
*result = GetIndex1(new_val, state);
cur_index = GetIndex1(new_val, 0);
next_index = SwapIndex1(fCounter, state);
need_copy = (GetIndex1(new_val, state) == 0); // Written = false, switch just occurred
SetIndex1(new_val, state, 0); // Written = false, invalidate state
} while (!CAS(Counter1(old_val), Counter1(new_val), (UInt32*)&fCounter));
if (need_copy)
memcpy(&fState[next_index], &fState[cur_index], sizeof(T));
return next_index;
}
void WriteNextStateStopAux(int state)
{
AtomicArrayCounter old_val;
AtomicArrayCounter new_val;
do {
old_val = fCounter;
new_val = old_val;
SetIndex1(new_val, state, 1); // Written = true, state becomes "switchable"
} while (!CAS(Counter1(old_val), Counter1(new_val), (UInt32*)&fCounter));
}
public:
JackAtomicArrayState()
{
static_assert(offsetof(JackAtomicArrayState, fCounter) % sizeof(fCounter) == 0,
"fCounter must be aligned within JackAtomicArrayState");
Counter1(fCounter) = 0;
}
~JackAtomicArrayState() // Not virtual ??
{}
/*!
\brief Returns the current state : only valid in the RT reader thread
*/
T* ReadCurrentState()
{
return &fState[GetIndex1(fCounter, 0)];
}
/*!
\brief Returns the current switch counter
*/
UInt16 GetCurrentIndex()
{
return GetIndex1(fCounter, 3);
}
/*!
\brief Tries to switch to the next state and returns the new current state (either the same as before if case of switch failure or the new one)
*/
T* TrySwitchState(int state)
{
AtomicArrayCounter old_val;
AtomicArrayCounter new_val;
do {
old_val = fCounter;
new_val = old_val;
if (GetIndex1(new_val, state)) { // If state has been written
SetIndex1(new_val, 0, SwapIndex1(new_val, state)); // Prepare switch
SetIndex1(new_val, state, 0); // Invalidate the state "state"
IncIndex1(new_val, 3); // Inc switch
}
} while (!CAS(Counter1(old_val), Counter1(new_val), (UInt32*)&fCounter));
return &fState[GetIndex1(fCounter, 0)]; // Read the counter again
}
/*!
\brief Tries to switch to the next state and returns the new current state (either the same as before if case of switch failure or the new one)
*/
T* TrySwitchState(int state, bool* result)
{
AtomicArrayCounter old_val;
AtomicArrayCounter new_val;
do {
old_val = fCounter;
new_val = old_val;
if ((*result = GetIndex1(new_val, state))) { // If state has been written
SetIndex1(new_val, 0, SwapIndex1(new_val, state)); // Prepare switch
SetIndex1(new_val, state, 0); // Invalidate the state "state"
IncIndex1(new_val, 3); // Inc switch
}
} while (!CAS(Counter1(old_val), Counter1(new_val), (UInt32*)&fCounter));
return &fState[GetIndex1(fCounter, 0)]; // Read the counter again
}
/*!
\brief Start write operation : setup and returns the next state to update, check for recursive write calls.
*/
T* WriteNextStateStart(int state)
{
bool tmp;
UInt32 index = WriteNextStateStartAux(state, &tmp);
return &fState[index];
}
T* WriteNextStateStart(int state, bool* result)
{
UInt32 index = WriteNextStateStartAux(state, result);
return &fState[index];
}
/*!
\brief Stop write operation : make the next state ready to be used by the RT thread
*/
void WriteNextStateStop(int state)
{
WriteNextStateStopAux(state);
}
} POST_PACKED_STRUCTURE;
} // end of namespace
#endif