///////////////////////////////////////////////////////////////////////////////
//
// Microsoft Research Singularity
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// File: BlockCache.sg
//
// Todo: Look at giving up blocks when there is system memory pressure.
// This needs a channel to the shared heap or another entity to
// provide the necessary hints.
//
using Microsoft.Singularity.Channels;
using Microsoft.SingSharp;
using System.Threading;
namespace Microsoft.Singularity.Services.Fat.Fs
{
/// States associated with BlockCacheNodes.
internal enum NodeState : uint {
Unused = 0, // Node is unused. It has a buffer, but it does
// not correspond to data on the disk.
// Transitions :=
Clean = 1, // Node has data and it's clean.
// Transitions :=
CleanBorrowed = 2, // There is clean data associated with node,
// but it is temporarily in use elsewhere in
// fs code.
// Transitions :=
Dirty = 3, // Node has data and it's dirty. If a user
// requests the node's data we have to return
// a copy as the data is enqueued to be written
// written to disk.
// Transitions :=
DirtyBorrowed = 4, // There is dirty data associated with node,
// but it is temporarily in use elsewhere in
// fs code.
// Transitions :=
WritePending = 5, // The disk has the data associated with the
// node. The disk will return the data when
// the write is complete. The node state will
// transition to Clean when done.
// Transitions :=
ReadPending = 6, // The node is tagged with sector id, but data
// from disk has yet to be transferred.
// Transitions :=
}
internal struct BlockCacheNode
{
NodeState nodeState;
ulong sectorId;
int lruTicks;
VContainer blockData; // VContainer has mutex for acquire/release
// but we don't use it and would prefer
// not to have it.
bool acquired; // For sanity check
internal BlockCacheNode(NodeState ns,
ulong sectorId,
[Claims] byte[]! in ExHeap blockData,
int lruTicks)
{
this.nodeState = ns;
this.sectorId = sectorId;
this.blockData = new VContainer (blockData);
this.lruTicks = lruTicks;
this.acquired = false;
}
private static NodeState[,] Transitions = {
// From State To State
{ NodeState.Unused, NodeState.ReadPending },
{ NodeState.Unused, NodeState.Dirty },
{ NodeState.ReadPending, NodeState.Clean },
{ NodeState.ReadPending, NodeState.CleanBorrowed },
{ NodeState.Unused, NodeState.Dirty },
{ NodeState.Clean, NodeState.CleanBorrowed },
{ NodeState.Clean, NodeState.Dirty },
{ NodeState.Clean, NodeState.ReadPending },
{ NodeState.CleanBorrowed, NodeState.Clean },
{ NodeState.CleanBorrowed, NodeState.Dirty },
{ NodeState.Dirty, NodeState.Dirty },
{ NodeState.Dirty, NodeState.DirtyBorrowed },
{ NodeState.Dirty, NodeState.WritePending },
{ NodeState.DirtyBorrowed, NodeState.Dirty },
{ NodeState.WritePending, NodeState.Clean }
};
[ Microsoft.Contracts.Pure ]
static bool TransitionValid(NodeState oldState, NodeState newState)
{
for (int i = 0; i < Transitions.Length; i += Transitions.Rank) {
if (Transitions[i/2, 0] == oldState &&
Transitions[i/2, 1] == newState) {
return true;
}
}
return false;
}
internal byte[]! in ExHeap Acquire()
{
byte []! in ExHeap bytes = blockData.Acquire();
assert (State == NodeState.Unused ||
State == NodeState.Clean ||
State == NodeState.Dirty);
this.acquired = true;
return bytes;
}
internal void AcquiredTransition(NodeState newNodeState, int lruTicks)
{
assert TransitionValid(nodeState, newNodeState);
this.nodeState = newNodeState;
this.lruTicks = lruTicks;
}
internal void AcquiredSetSectorId(ulong newSectorId)
{
assert acquired == true;
sectorId = newSectorId;
}
internal void Release([Claims] byte[]! in ExHeap bytes,
NodeState newNodeState,
int lruTicks)
{
assert TransitionValid(nodeState, newNodeState);
assert (newNodeState == NodeState.Unused ||
newNodeState == NodeState.Clean ||
newNodeState == NodeState.Dirty);
this.nodeState = newNodeState;
this.lruTicks = lruTicks;
this.acquired = false;
this.blockData.Release(bytes);
}
internal NodeState State
{
get { return nodeState; }
}
internal ulong SectorId
{
get { return sectorId; }
}
internal int LruTicks
{
get { return lruTicks; }
}
internal bool Acquired
{
get { return acquired; }
}
}
internal pointerfree struct BlockCacheConfiguration
{
ulong startSectorId; // SectorId of first block in cache range
uint bytesPerSector;
uint sectorsPerBlock;
uint totalBlocks; // Number of blocks in cache range
uint maxCacheKB; // Maximum block data cached
internal BlockCacheConfiguration(ulong startSectorId,
uint bytesPerSector,
uint sectorsPerBlock,
uint totalBlocks,
uint maxCacheKB)
requires (bytesPerSector != 0 &&
(bytesPerSector & (bytesPerSector - 1)) == 0)
/* otherwise ArgumentException */;
requires (sectorsPerBlock != 0 &&
(sectorsPerBlock & (sectorsPerBlock - 1)) == 0)
/* otherwise ArgumentException */;
requires (totalBlocks != 0)
/* otherwise ArgumentException */;
{
this.startSectorId = startSectorId;
this.bytesPerSector = bytesPerSector;
this.sectorsPerBlock = sectorsPerBlock;
this.totalBlocks = totalBlocks;
this.maxCacheKB = maxCacheKB;
}
internal ulong StartSectorId { get { return startSectorId; } }
internal uint BytesPerSector { get { return sectorsPerBlock; } }
internal uint SectorsPerBlock { get { return sectorsPerBlock; } }
internal uint BytesPerBlock
{
get { return sectorsPerBlock * bytesPerSector; }
}
internal uint TotalBlocks { get { return totalBlocks; } }
internal uint MaxCacheKB { get { return maxCacheKB; } }
}
///
/// The BlockCache is an n-way associative cache of a set of blocks on
/// a disk. All of the blocks are assumed to have the same size.
///
internal sealed class BlockCache : IBlockWriterUser
{
///////////////////////////////////////////////////////////////////////
// Fields
private BlockCacheConfiguration config;
private uint cacheStride; // bucketCount
private uint cacheAssociativity; // bucketDepth
private BlockCacheNode [][] cache;
private int [] cacheTicks; // lru clock for
// each cache line
private Disk! disk;
private BlockWriter! blockWriter;
///////////////////////////////////////////////////////////////////////
// Methods
[ Microsoft.Contracts.NotDelayed ]
internal BlockCache(BlockCacheConfiguration configuration,
Disk! disk,
BlockWriter! blockWriter)
{
this.config = configuration;
this.disk = disk;
this.blockWriter = blockWriter;
base();
SizeCache(ref config, out cacheStride, out cacheAssociativity);
ulong usedKB = ((ulong)cacheStride) * cacheAssociativity * config.BytesPerBlock / 1024;
DebugStub.Print(
"BlockCache suggested size = {0} K, actual size = {1} K ({2} x {3} x {4})\n",
__arglist(
config.MaxCacheKB,
usedKB,
cacheStride,
cacheAssociativity,
config.BytesPerBlock));
cache = new BlockCacheNode [cacheStride][];
cacheTicks = new int[cacheStride];
ulong allocated = 0;
for (uint i = 0; i < cacheStride; i++) {
BlockCacheNode [] cacheLine = new BlockCacheNode[cacheAssociativity];
cache[i] = cacheLine;
cacheTicks[i] = 0;
for (uint j = 0; j < cacheAssociativity; j++) {
cacheLine[j] = new BlockCacheNode(
NodeState.Unused,
0 /* no sector id */,
new [ExHeap] byte [config.BytesPerBlock],
cacheTicks[i]);
allocated += config.BytesPerBlock;
}
}
DebugStub.Print("Cache allocated {0}\n",
__arglist(allocated));
}
internal ulong StartSectorId
{
get { return config.StartSectorId; }
}
internal uint TotalBlocks
{
get { return config.TotalBlocks; }
}
internal uint BytesPerBlock
{
get { return config.BytesPerBlock; }
}
private uint GetCacheLine(uint blockId)
requires blockId < TotalBlocks /* otherwise ArgumentException */;
{
return blockId % cacheStride;
}
private ulong BlockToSectorId(uint blockId)
requires blockId < TotalBlocks /* otherwise ArgumentException */;
{
return config.StartSectorId + blockId * (ulong)config.SectorsPerBlock;
}
private uint SectorToBlockId(ulong sectorId)
requires (long)sectorId >= (long)StartSectorId;
{
return ((uint)(sectorId - config.StartSectorId) / config.SectorsPerBlock);
}
enum CandidateResult : uint
{
FoundExact = 1, // Found exact block
FoundVictim = 2, // Found candidate for replacement
NodeBlocked = 0x80000001, // Found block but in use
CacheLineBlocked = 0x80000002 // No blocks found or available
}
/// Search for identified node or a victim for
/// replacement if not present.
///
/// Looks for cache node matching sector id or
/// most suitable node to use for sector id. If it is
/// not in the cache, a suitable candidate for
/// replacement may be returned.
///
/// If the node is in the cache, but in use, then
/// NodeBlocked is returned.
///
/// If the node is not present and there are no
/// available nodes in cache line for replacement, then
/// CacheLineBlocked is returned.
///
private static CandidateResult
GetCandidateNode(BlockCacheNode[]! cacheLine,
int clockTicks,
ulong sectorId,
out int nodeIndex)
{
// requires Monitor.Entered(cacheLine) == true
int freeIndex = -1;
int lruIndex = -1;
uint lastLruAge = 0;
for (int i = 0; i < cacheLine.Length; i++) {
if (cacheLine[i].State == NodeState.Unused) {
freeIndex = i;
}
else if (cacheLine[i].SectorId == sectorId) {
if (cacheLine[i].State == NodeState.Clean ||
cacheLine[i].State == NodeState.Dirty) {
nodeIndex = i;
#if RECORD_BLOCK_CACHE_STATISTICS
DebugStub.AddToPerfCounter(0, 1); // Block found
#endif
return CandidateResult.FoundExact;
}
else {
nodeIndex = i;
// Node exists in cache but is in use
#if RECORD_BLOCK_CACHE_STATISTICS
DebugStub.AddToPerfCounter(1, 1); // Block in use, block.
#endif
return CandidateResult.NodeBlocked;
}
}
else if (cacheLine[i].State == NodeState.Clean) {
// Candidate for eviction from cache. Compute age
// and note if eldest clean node to date.
if (lruIndex < -0) {
lruIndex = i;
lastLruAge = (uint) unchecked(clockTicks - cacheLine[i].LruTicks);
}
else {
uint lruAge = (uint) unchecked(clockTicks - cacheLine[i].LruTicks);
if (lruAge > lastLruAge) {
lruIndex = i;
lastLruAge = lruAge;
}
}
}
}
if (freeIndex >= 0) {
nodeIndex = freeIndex;
#if RECORD_BLOCK_CACHE_STATISTICS
DebugStub.AddToPerfCounter(2, 1); // Will fetch.
#endif
return CandidateResult.FoundVictim;
}
else if (lruIndex >= 0) {
nodeIndex = lruIndex;
#if RECORD_BLOCK_CACHE_STATISTICS
DebugStub.AddToPerfCounter(3, 1); // Will fetch.
#endif
return CandidateResult.FoundVictim;
}
nodeIndex = -1;
#if RECORD_BLOCK_CACHE_STATISTICS
DebugStub.AddToPerfCounter(3, 1); // Wait to evict cache.
#endif
return CandidateResult.CacheLineBlocked;
}
///
/// Claims an unused or clean block in the cache and tags it
/// with the requested block id. It is assumed the caller wants
/// to write block as new without reading it first. The block
/// is optionally initialized with zeros and always marked as dirty.
/// The caller must pair this method with EndQuickBlockOperation.
/// Typical usage would be creating a new directory or first block
/// for a file.
///
internal byte[]! in ExHeap
CreateBlockAndBeginQuickOperation(uint blockId, bool zeroBuffer)
{
uint line = GetCacheLine(blockId);
BlockCacheNode[]! cacheLine = (!) cache[line];
ulong sectorId = BlockToSectorId(blockId);
bool enqueue = false;
// Take cache line lock
Monitor.Enter(cacheLine);
retry:
int nodeIndex;
CandidateResult cr =
GetCandidateNode(cacheLine, cacheTicks[line], sectorId,
out nodeIndex);
if (cr == CandidateResult.NodeBlocked) {
Monitor.Wait(cacheLine);
goto retry;
}
else if (cr == CandidateResult.CacheLineBlocked) {
// Wake up thread that flushes blocks to disk and wait
// for notification that block state has changed.
blockWriter.WakeUp();
Monitor.Wait(cacheLine);
goto retry;
}
byte []! in ExHeap bytes = cacheLine[nodeIndex].Acquire();
if (cr == CandidateResult.FoundExact) {
// Block exists in cache
assert cacheLine[nodeIndex].SectorId == sectorId;
assert (cacheLine[nodeIndex].State == NodeState.Clean ||
cacheLine[nodeIndex].State == NodeState.Dirty);
if (cacheLine[nodeIndex].State == NodeState.Clean) {
cacheLine[nodeIndex].AcquiredTransition(
NodeState.Dirty, cacheTicks[line]++);
enqueue = true;
}
if (zeroBuffer) {
Bitter.Zero(bytes, 0, bytes.Length);
}
cacheLine[nodeIndex].AcquiredTransition(
NodeState.DirtyBorrowed, cacheTicks[line]++);
Monitor.Exit(cacheLine);
}
else {
// Block was not in cache, relabel block and mark as dirty.
assert cr == CandidateResult.FoundVictim;
assert (cacheLine[nodeIndex].State == NodeState.Clean ||
cacheLine[nodeIndex].State == NodeState.Unused);
cacheLine[nodeIndex].AcquiredSetSectorId(sectorId);
cacheLine[nodeIndex].AcquiredTransition(
NodeState.Dirty, cacheTicks[line]++);
if (zeroBuffer) {
Bitter.Zero(bytes, 0, bytes.Length);
}
cacheLine[nodeIndex].AcquiredTransition(
NodeState.DirtyBorrowed, cacheTicks[line]++);
enqueue = true;
Monitor.Exit(cacheLine);
}
if (enqueue) {
blockWriter.Enqueue(this, sectorId, nodeIndex);
}
return bytes;
}
///
/// Fetches and locks a data block in the cache. Only
/// used by non-blocking internal FS operations. This
/// method may block if a fetch from from the disk is
/// required to get the data or the data block is
/// already locked.
///
internal byte[]! in ExHeap
BeginQuickBlockOperation(uint blockId)
{
uint line = GetCacheLine(blockId);
BlockCacheNode[]! cacheLine = (!) cache[line];
ulong sectorId = BlockToSectorId(blockId);
// Take cache line lock
Monitor.Enter(cacheLine);
retry:
int nodeIndex;
CandidateResult cr =
GetCandidateNode(cacheLine, cacheTicks[line], sectorId,
out nodeIndex);
if (cr == CandidateResult.NodeBlocked) {
Monitor.Wait(cacheLine);
goto retry;
}
else if (cr == CandidateResult.CacheLineBlocked) {
// Wake up thread that flushes blocks to disk and wait
// for notification that block state has changed.
blockWriter.WakeUp();
Monitor.Wait(cacheLine);
goto retry;
}
// Get buffer
byte []! in ExHeap bytes = cacheLine[nodeIndex].Acquire();
if (cr == CandidateResult.FoundExact) {
// Block exists in cache
assert cacheLine[nodeIndex].SectorId == sectorId;
assert (cacheLine[nodeIndex].State == NodeState.Clean ||
cacheLine[nodeIndex].State == NodeState.Dirty);
if (cacheLine[nodeIndex].State == NodeState.Clean) {
cacheLine[nodeIndex].AcquiredTransition(
NodeState.CleanBorrowed, cacheTicks[line]++);
}
else if (cacheLine[nodeIndex].State == NodeState.Dirty) {
cacheLine[nodeIndex].AcquiredTransition(
NodeState.DirtyBorrowed, cacheTicks[line]++);
}
// Drop cache line lock
Monitor.Exit(cacheLine);
return bytes;
}
else {
assert cr == CandidateResult.FoundVictim;
assert (cacheLine[nodeIndex].State == NodeState.Clean ||
cacheLine[nodeIndex].State == NodeState.Unused);
// Block needs to be fetched from disk.
// Change sector id on node about to be replaced
cacheLine[nodeIndex].AcquiredSetSectorId(sectorId);
// Mark State
cacheLine[nodeIndex].AcquiredTransition(
NodeState.ReadPending, cacheTicks[line]++);
// Drop lock for duration of read
Monitor.Exit(cacheLine);
byte[] in ExHeap inData = disk.Read(sectorId, bytes);
assert inData != null;
Monitor.Enter(cacheLine);
cacheLine[nodeIndex].AcquiredTransition(
NodeState.CleanBorrowed, cacheTicks[line]++);
Monitor.Exit(cacheLine);
return inData;
}
}
///
/// Returns block to cache and unlocks it. Must be paired with
/// BeginQuickBlockOperation.
///
internal void
EndQuickBlockOperation(uint blockId,
[Claims] byte[]! in ExHeap blockData,
bool dirtiedBuffer)
{
uint line = GetCacheLine(blockId);
BlockCacheNode[]! cacheLine = (!) cache[line];
// Lock cache line
Monitor.Enter(cacheLine);
// Get node
int nodeIndex;
CandidateResult cr = GetCandidateNode(cacheLine,
cacheTicks[line],
BlockToSectorId(blockId),
out nodeIndex);
// BeginQuickBlockOperation should have blocked node
assert cr == CandidateResult.NodeBlocked;
assert nodeIndex >= 0;
assert cacheLine[nodeIndex].Acquired == true;
assert (cacheLine[nodeIndex].State == NodeState.CleanBorrowed ||
cacheLine[nodeIndex].State == NodeState.DirtyBorrowed);
bool enqueueBlock = false;
NodeState newState;
// Return data to cache node and update state
if (cacheLine[nodeIndex].State == NodeState.CleanBorrowed) {
if (dirtiedBuffer) {
enqueueBlock = true;
newState = NodeState.Dirty;
}
else {
newState = NodeState.Clean;
}
}
else {
assert cacheLine[nodeIndex].State == NodeState.DirtyBorrowed;
newState = NodeState.Dirty;
}
cacheLine[nodeIndex].Release(blockData, newState,
cacheTicks[line]++);
// Enqueue request *after* returning node to cache.
// This is a must to maintain the correct state associated with
// the node because writer may work synchronously or
// asynchronously and the enqueue operation may trigger a write
// which will update the state of the block both when it
// starts and completes.
if (enqueueBlock) {
Monitor.Exit(cacheLine);
// NB cookie is index
// in cache line of node.
blockWriter.Enqueue(this,
cacheLine[nodeIndex].SectorId,
nodeIndex);
}
else {
// Wakeup anyone waiting for this block or this cache-line
// NB Threads may be sleeping because node has data transiently
// unavailable (CleanBorrowed,DirtyBorrowed,ReadPending, or
// WritePending), or because it's waiting for a victim to replace
// and this node might now be eligible.
Monitor.Pulse(cacheLine);
Monitor.Exit(cacheLine);
}
}
internal void Read(uint blockId,
int blockOffset,
byte[]! in ExHeap dstBuffer,
int dstOffset,
int readBytes)
requires blockId < TotalBlocks;
requires blockOffset >= 0;
requires dstOffset >= 0;
requires readBytes >= 0;
requires blockOffset + readBytes <= this.BytesPerBlock;
requires dstOffset + readBytes <= dstBuffer.Length;
{
byte[]! in ExHeap blockData = BeginQuickBlockOperation(blockId);
Bitter.Copy(dstBuffer, dstOffset, readBytes,
blockData, blockOffset);
EndQuickBlockOperation(blockId, blockData, false);
}
internal void WriteEntireBlock(uint blockId,
byte[]! in ExHeap srcBuffer,
int srcOffset)
requires blockId < TotalBlocks;
requires srcOffset >= 0;
requires srcOffset + BytesPerBlock <= srcBuffer.Length;
{
uint line = GetCacheLine(blockId);
BlockCacheNode[]! cacheLine = (!) cache[line];
ulong sectorId = BlockToSectorId(blockId);
// Take cache line lock
Monitor.Enter(cacheLine);
retry:
int nodeIndex;
CandidateResult cr = GetCandidateNode(cacheLine,
cacheTicks[line],
sectorId,
out nodeIndex);
if (cr == CandidateResult.NodeBlocked) {
Monitor.Wait(cacheLine);
goto retry;
}
else if (cr == CandidateResult.CacheLineBlocked) {
// Wake up thread that flushes blocks to disk and wait
// for notification that block state has changed.
blockWriter.WakeUp();
Monitor.Wait(cacheLine);
goto retry;
}
assert (cr == CandidateResult.FoundExact ||
cr == CandidateResult.FoundVictim);
assert ((cacheLine[nodeIndex].SectorId == sectorId &&
// Node has correct sector id, so is dirty/clean/unused
(cacheLine[nodeIndex].State == NodeState.Dirty ||
cacheLine[nodeIndex].State == NodeState.Clean ||
cacheLine[nodeIndex].State == NodeState.Unused)) ||
// Node is about to be reassigned, so is clean/unused
(cacheLine[nodeIndex].State == NodeState.Clean ||
cacheLine[nodeIndex].State == NodeState.Unused));
byte []! in ExHeap blockData = cacheLine[nodeIndex].Acquire();
NodeState oldState = cacheLine[nodeIndex].State;
cacheLine[nodeIndex].AcquiredSetSectorId(sectorId);
Bitter.Copy(blockData, 0, (int)BytesPerBlock,
srcBuffer, (int)srcOffset);
cacheLine[nodeIndex].Release(blockData, NodeState.Dirty,
cacheTicks[line]++);
if (cacheLine[nodeIndex].State != oldState) {
// NB cookie is index
// in cache line of node.
blockWriter.Enqueue(this,
cacheLine[nodeIndex].SectorId,
nodeIndex);
}
// Drop cache line lock
Monitor.Exit(cacheLine);
return;
}
/// Creates a zero-filled block in the cache.
internal void ZeroBlock(uint blockId)
{
uint line = GetCacheLine(blockId);
BlockCacheNode[]! cacheLine = (!) cache[line];
ulong sectorId = BlockToSectorId(blockId);
// Take cache line lock
Monitor.Enter(cacheLine);
retry:
int nodeIndex;
CandidateResult cr = GetCandidateNode(cacheLine,
cacheTicks[line],
sectorId,
out nodeIndex);
if (cr == CandidateResult.NodeBlocked) {
Monitor.Wait(cacheLine);
goto retry;
}
else if (cr == CandidateResult.CacheLineBlocked) {
// Wake up thread that flushes blocks to disk and wait
// for notification that block state has changed.
blockWriter.WakeUp();
Monitor.Wait(cacheLine);
goto retry;
}
assert (cr == CandidateResult.FoundExact ||
cr == CandidateResult.FoundVictim);
assert ((cacheLine[nodeIndex].SectorId == sectorId &&
// Node has correct sector id, so is dirty/clean/unused
(cacheLine[nodeIndex].State == NodeState.Dirty ||
cacheLine[nodeIndex].State == NodeState.Clean ||
cacheLine[nodeIndex].State == NodeState.Unused)) ||
// Node is about to be reassigned, so is clean/unused
(cacheLine[nodeIndex].State == NodeState.Clean ||
cacheLine[nodeIndex].State == NodeState.Unused));
byte []! in ExHeap blockData = cacheLine[nodeIndex].Acquire();
NodeState oldState = cacheLine[nodeIndex].State;
cacheLine[nodeIndex].AcquiredSetSectorId(sectorId);
Bitter.Zero(blockData, 0, (int)BytesPerBlock);
cacheLine[nodeIndex].Release(blockData, NodeState.Dirty,
cacheTicks[line]++);
if (cacheLine[nodeIndex].State != oldState) {
// NB cookie is index
// in cache line of node.
blockWriter.Enqueue(this,
cacheLine[nodeIndex].SectorId,
nodeIndex);
}
// Drop cache line lock
Monitor.Exit(cacheLine);
return;
}
internal void Write(uint blockId,
int blockOffset,
byte[]! in ExHeap srcBuffer,
int srcOffset,
int writeBytes)
requires blockId < TotalBlocks;
requires blockOffset >= 0;
requires srcOffset >= 0;
requires writeBytes >= 0;
requires blockOffset + writeBytes <= this.BytesPerBlock;
requires srcOffset + writeBytes <= srcBuffer.Length;
{
if (writeBytes < BytesPerBlock) {
byte[]! in ExHeap blockData = BeginQuickBlockOperation(blockId);
Bitter.Copy(blockData, blockOffset, writeBytes,
srcBuffer, srcOffset);
EndQuickBlockOperation(blockId, blockData, true);
}
else {
WriteEntireBlock(blockId, srcBuffer, srcOffset);
}
}
byte[]! in ExHeap
IBlockWriterUser.GetDataForWrite(ulong sectorId, int cookie)
{
uint line = GetCacheLine(SectorToBlockId(sectorId));
BlockCacheNode[]! cacheLine = (!) cache[line];
Monitor.Enter(cacheLine);
// Cookie is index in cache line of node.
int nodeIndex = cookie;
// Node should not have moved
// and should be dirty.
assert nodeIndex >= 0 && nodeIndex <= cacheLine.Length;
assert cacheLine[nodeIndex].SectorId == sectorId;
assert (cacheLine[nodeIndex].State == NodeState.Dirty ||
cacheLine[nodeIndex].State == NodeState.DirtyBorrowed);
while (cacheLine[nodeIndex].State == NodeState.DirtyBorrowed) {
Monitor.Wait(cacheLine);
}
byte[]! in ExHeap blockData = cacheLine[nodeIndex].Acquire();
cacheLine[nodeIndex].AcquiredTransition(NodeState.WritePending,
cacheTicks[line]++);
Monitor.Exit(cacheLine);
return blockData;
}
void
IBlockWriterUser.WriteComplete(ulong sectorId,
[Claims] byte[]! in ExHeap blockData,
int cookie)
{
uint line = GetCacheLine(SectorToBlockId(sectorId));
BlockCacheNode[]! cacheLine = (!) cache[line];
Monitor.Enter(cacheLine);
// Cookie is index in cache line of node.
int nodeIndex = cookie;
// Node should not have moved
// and should be pending write completion.
assert nodeIndex >= 0 && nodeIndex <= cacheLine.Length;
assert cacheLine[nodeIndex].SectorId == sectorId;
assert cacheLine[nodeIndex].State == NodeState.WritePending;
cacheLine[nodeIndex].Release(blockData,
NodeState.Clean,
cacheTicks[line]++);
// Notify any waiters of clean node presence
Monitor.Pulse(cacheLine);
Monitor.Exit(cacheLine);
}
internal void ValidateAllClean()
{
assert this.cache != null;
bool failed = false;
for (int line = 0; line < this.cache.Length; line++) {
BlockCacheNode []! bcns = (!)cache[line];
for (int i = 0; i < bcns.Length; i++) {
if (bcns[i].State == NodeState.Unused &&
bcns[i].State == NodeState.Clean) {
DebugStub.Print("Failed on node[{0}][{1}] (sectorId {2}) -> {3}\n",
__arglist(line, i,
bcns[i].SectorId,
bcns[i].State));
failed = true;
}
}
}
assert !failed;
}
///////////////////////////////////////////////////////////////////////
// Static helper methods
private static int Log2(uint value)
{
int l = 0;
if ((value & 0xffff0000u) != 0) {
l += 16;
value >>= 16;
}
if ((value & 0xff00ff00u) != 0) {
l += 8;
value >>= 8;
}
if ((value & 0xf0f0f0f0) != 0) {
l += 4;
value >>= 4;
}
if ((value & 0xcccccccc) != 0) {
l += 2;
value >>= 2;
}
if ((value & 0xaaaaaaaa) != 0) {
l += 1;
value >>= 1;
}
return l;
}
///
/// Computes cache dimensions from cache configuration.
/// Output values are both powers of 2.
///
private static void SizeCache(ref BlockCacheConfiguration config,
out uint stride,
out uint associativity)
{
uint maxBlocks = config.MaxCacheKB * 1024 / config.BytesPerBlock;
if (maxBlocks > config.TotalBlocks) {
maxBlocks = config.TotalBlocks;
}
if (maxBlocks == 0) {
stride = 1;
associativity = 1;
}
else {
stride = 0;
associativity = 64;
while (stride == 0) {
associativity /= 2;
stride = maxBlocks / associativity;
}
}
}
}
}