/////////////////////////////////////////////////////////////////////////////// // // Microsoft Research Singularity // // Copyright (c) Microsoft Corporation. All rights reserved. // // File: Bitmap.sg // // Note: // // The Bitmap class provides a bitmap allocator for fixed size // spaces. It's used in the FAT filesystem to maintain // information on free FAT clusters and to keep track of // allocated entries in directories which helps in creating and // finding files. // // For Compilation with C#/Spec# of test program: // {csc,ssc} /D:DEBUG /DEBUG /D:TEST_BITMAP bitmap.sg // // TODO: // - Consider optimizing set/clear bit search based on summaries. It is // presently expensive for sparse cases. using System; using System.Diagnostics; #if TEST_BITMAP using System.Collections; #endif namespace Microsoft.Singularity.Services.Fat.Fs { internal sealed class Bitmap { #region tables // Upper 4-bits number free on left, lower 4bits number // free on right. private static readonly byte [] LRContiguousTable = { /* 0x00 */ 0x88, 0x70, 0x61, 0x60, 0x52, 0x50, 0x51, 0x50, /* 0x08 */ 0x43, 0x40, 0x41, 0x40, 0x42, 0x40, 0x41, 0x40, /* 0x10 */ 0x34, 0x30, 0x31, 0x30, 0x32, 0x30, 0x31, 0x30, /* 0x18 */ 0x33, 0x30, 0x31, 0x30, 0x32, 0x30, 0x31, 0x30, /* 0x20 */ 0x25, 0x20, 0x21, 0x20, 0x22, 0x20, 0x21, 0x20, /* 0x28 */ 0x23, 0x20, 0x21, 0x20, 0x22, 0x20, 0x21, 0x20, /* 0x30 */ 0x24, 0x20, 0x21, 0x20, 0x22, 0x20, 0x21, 0x20, /* 0x38 */ 0x23, 0x20, 0x21, 0x20, 0x22, 0x20, 0x21, 0x20, /* 0x40 */ 0x16, 0x10, 0x11, 0x10, 0x12, 0x10, 0x11, 0x10, /* 0x48 */ 0x13, 0x10, 0x11, 0x10, 0x12, 0x10, 0x11, 0x10, /* 0x50 */ 0x14, 0x10, 0x11, 0x10, 0x12, 0x10, 0x11, 0x10, /* 0x58 */ 0x13, 0x10, 0x11, 0x10, 0x12, 0x10, 0x11, 0x10, /* 0x60 */ 0x15, 0x10, 0x11, 0x10, 0x12, 0x10, 0x11, 0x10, /* 0x68 */ 0x13, 0x10, 0x11, 0x10, 0x12, 0x10, 0x11, 0x10, /* 0x70 */ 0x14, 0x10, 0x11, 0x10, 0x12, 0x10, 0x11, 0x10, /* 0x78 */ 0x13, 0x10, 0x11, 0x10, 0x12, 0x10, 0x11, 0x10, /* 0x80 */ 0x07, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, /* 0x88 */ 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, /* 0x90 */ 0x04, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, /* 0x98 */ 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, /* 0xa0 */ 0x05, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, /* 0xa8 */ 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, /* 0xb0 */ 0x04, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, /* 0xb8 */ 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, /* 0xc0 */ 0x06, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, /* 0xc8 */ 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, /* 0xd0 */ 0x04, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, /* 0xd8 */ 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, /* 0xe0 */ 0x05, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, /* 0xe8 */ 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, /* 0xf0 */ 0x04, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, /* 0xf8 */ 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00 }; // Upper for bits equals start index (0=msb,7=lsb). // Lower four bits are the number free. private static readonly byte [] MaxFreeTable = { /* 0x00 */ 0x08, 0x07, 0x06, 0x06, 0x05, 0x05, 0x05, 0x05, /* 0x08 */ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, /* 0x10 */ 0x44, 0x43, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, /* 0x18 */ 0x53, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, /* 0x20 */ 0x35, 0x34, 0x33, 0x33, 0x62, 0x32, 0x32, 0x32, /* 0x28 */ 0x53, 0x52, 0x02, 0x02, 0x62, 0x02, 0x02, 0x02, /* 0x30 */ 0x44, 0x43, 0x42, 0x42, 0x62, 0x02, 0x02, 0x02, /* 0x38 */ 0x53, 0x52, 0x02, 0x02, 0x62, 0x02, 0x02, 0x02, /* 0x40 */ 0x26, 0x25, 0x24, 0x24, 0x23, 0x23, 0x23, 0x23, /* 0x48 */ 0x53, 0x52, 0x22, 0x22, 0x62, 0x22, 0x22, 0x22, /* 0x50 */ 0x44, 0x43, 0x42, 0x42, 0x62, 0x61, 0x71, 0x41, /* 0x58 */ 0x53, 0x52, 0x71, 0x51, 0x62, 0x61, 0x71, 0x21, /* 0x60 */ 0x35, 0x34, 0x33, 0x33, 0x62, 0x32, 0x32, 0x32, /* 0x68 */ 0x53, 0x52, 0x71, 0x51, 0x62, 0x61, 0x71, 0x31, /* 0x70 */ 0x44, 0x43, 0x42, 0x42, 0x62, 0x61, 0x71, 0x41, /* 0x78 */ 0x53, 0x52, 0x71, 0x51, 0x62, 0x61, 0x71, 0x01, /* 0x80 */ 0x17, 0x16, 0x15, 0x15, 0x14, 0x14, 0x14, 0x14, /* 0x88 */ 0x53, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, /* 0x90 */ 0x44, 0x43, 0x42, 0x42, 0x62, 0x12, 0x12, 0x12, /* 0x98 */ 0x53, 0x52, 0x12, 0x12, 0x62, 0x12, 0x12, 0x12, /* 0xa0 */ 0x35, 0x34, 0x33, 0x33, 0x62, 0x32, 0x32, 0x32, /* 0xa8 */ 0x53, 0x52, 0x71, 0x51, 0x62, 0x61, 0x71, 0x31, /* 0xb0 */ 0x44, 0x43, 0x42, 0x42, 0x62, 0x61, 0x71, 0x41, /* 0xb8 */ 0x53, 0x52, 0x71, 0x51, 0x62, 0x61, 0x71, 0x11, /* 0xc0 */ 0x26, 0x25, 0x24, 0x24, 0x23, 0x23, 0x23, 0x23, /* 0xc8 */ 0x53, 0x52, 0x22, 0x22, 0x62, 0x22, 0x22, 0x22, /* 0xd0 */ 0x44, 0x43, 0x42, 0x42, 0x62, 0x61, 0x71, 0x41, /* 0xd8 */ 0x53, 0x52, 0x71, 0x51, 0x62, 0x61, 0x71, 0x21, /* 0xe0 */ 0x35, 0x34, 0x33, 0x33, 0x62, 0x32, 0x32, 0x32, /* 0xe8 */ 0x53, 0x52, 0x71, 0x51, 0x62, 0x61, 0x71, 0x31, /* 0xf0 */ 0x44, 0x43, 0x42, 0x42, 0x62, 0x61, 0x71, 0x41, /* 0xf8 */ 0x53, 0x52, 0x71, 0x51, 0x62, 0x61, 0x71, 0x80 }; // Left Edge Contiguous Table. // // Assumes left most bits are set if present. Table // entries represent the contiguous number free from the // first free left-bit. private static readonly byte [] LeftEdgeContiguousTable = { /* 0x00 */ 0x08, 0x07, 0x06, 0x06, 0x05, 0x05, 0x05, 0x05, /* 0x08 */ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, /* 0x10 */ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, /* 0x18 */ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, /* 0x20 */ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, /* 0x28 */ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, /* 0x30 */ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, /* 0x38 */ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, /* 0x40 */ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, /* 0x48 */ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, /* 0x50 */ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, /* 0x58 */ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, /* 0x60 */ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, /* 0x68 */ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, /* 0x70 */ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, /* 0x78 */ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, /* 0x80 */ 0x07, 0x06, 0x05, 0x05, 0x04, 0x04, 0x04, 0x04, /* 0x88 */ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, /* 0x90 */ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, /* 0x98 */ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, /* 0xa0 */ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, /* 0xa8 */ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, /* 0xb0 */ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, /* 0xb8 */ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, /* 0xc0 */ 0x06, 0x05, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03, /* 0xc8 */ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, /* 0xd0 */ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, /* 0xd8 */ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, /* 0xe0 */ 0x05, 0x04, 0x03, 0x03, 0x02, 0x02, 0x02, 0x02, /* 0xe8 */ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, /* 0xf0 */ 0x04, 0x03, 0x02, 0x02, 0x01, 0x01, 0x01, 0x01, /* 0xf8 */ 0x03, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x00, }; private static readonly byte [] BitCountTable = { /* 0x00 */ 0x00, 0x01, 0x01, 0x02, 0x01, 0x02, 0x02, 0x03, /* 0x08 */ 0x01, 0x02, 0x02, 0x03, 0x02, 0x03, 0x03, 0x04, /* 0x10 */ 0x01, 0x02, 0x02, 0x03, 0x02, 0x03, 0x03, 0x04, /* 0x18 */ 0x02, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x05, /* 0x20 */ 0x01, 0x02, 0x02, 0x03, 0x02, 0x03, 0x03, 0x04, /* 0x28 */ 0x02, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x05, /* 0x30 */ 0x02, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x05, /* 0x38 */ 0x03, 0x04, 0x04, 0x05, 0x04, 0x05, 0x05, 0x06, /* 0x40 */ 0x01, 0x02, 0x02, 0x03, 0x02, 0x03, 0x03, 0x04, /* 0x48 */ 0x02, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x05, /* 0x50 */ 0x02, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x05, /* 0x58 */ 0x03, 0x04, 0x04, 0x05, 0x04, 0x05, 0x05, 0x06, /* 0x60 */ 0x02, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x05, /* 0x68 */ 0x03, 0x04, 0x04, 0x05, 0x04, 0x05, 0x05, 0x06, /* 0x70 */ 0x03, 0x04, 0x04, 0x05, 0x04, 0x05, 0x05, 0x06, /* 0x78 */ 0x04, 0x05, 0x05, 0x06, 0x05, 0x06, 0x06, 0x07, /* 0x80 */ 0x01, 0x02, 0x02, 0x03, 0x02, 0x03, 0x03, 0x04, /* 0x88 */ 0x02, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x05, /* 0x90 */ 0x02, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x05, /* 0x98 */ 0x03, 0x04, 0x04, 0x05, 0x04, 0x05, 0x05, 0x06, /* 0xa0 */ 0x02, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x05, /* 0xa8 */ 0x03, 0x04, 0x04, 0x05, 0x04, 0x05, 0x05, 0x06, /* 0xb0 */ 0x03, 0x04, 0x04, 0x05, 0x04, 0x05, 0x05, 0x06, /* 0xb8 */ 0x04, 0x05, 0x05, 0x06, 0x05, 0x06, 0x06, 0x07, /* 0xc0 */ 0x02, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x05, /* 0xc8 */ 0x03, 0x04, 0x04, 0x05, 0x04, 0x05, 0x05, 0x06, /* 0xd0 */ 0x03, 0x04, 0x04, 0x05, 0x04, 0x05, 0x05, 0x06, /* 0xd8 */ 0x04, 0x05, 0x05, 0x06, 0x05, 0x06, 0x06, 0x07, /* 0xe0 */ 0x03, 0x04, 0x04, 0x05, 0x04, 0x05, 0x05, 0x06, /* 0xe8 */ 0x04, 0x05, 0x05, 0x06, 0x05, 0x06, 0x06, 0x07, /* 0xf0 */ 0x04, 0x05, 0x05, 0x06, 0x05, 0x06, 0x06, 0x07, /* 0xf8 */ 0x05, 0x06, 0x06, 0x07, 0x06, 0x07, 0x07, 0x08, }; #endregion tables // Constants for logarithmic summary totals private const int SummaryFanOut = 256; private const int Log2SummaryFanOut = 9; /// Maximum length of a single allocation. public const int MaxAllocationLength = 64; private const int SmallAllocationLength = 8; // Padding at end of array to simplify end cases private const int PadBytes = MaxAllocationLength / 8; internal readonly int totalBits; private int totalFreeBits; private byte[] map; private int [][] summary; // logarithmic summary totals [ Microsoft.Contracts.NotDelayed ] internal Bitmap(int numberOfBits) { totalBits = numberOfBits; totalFreeBits = 0; int mapLength = (numberOfBits + 7) / 8 + PadBytes; this.map = new byte[mapLength]; int logSummaryLevels = Log2Up(mapLength) / Log2SummaryFanOut; if (logSummaryLevels == 0) { logSummaryLevels = 1; } this.summary = new int [logSummaryLevels][]; int n = mapLength; for (int i = (logSummaryLevels - 1); i >= 0; i--) { n = (n + SummaryFanOut - 1) / SummaryFanOut; summary[i] = new int [n]; } this.Initialize(); } private void Initialize() { // This is a little gratuitous - we make // two passes over the map when initializing. This enables // getting the logarithmic summary table information correct in // a trivial manner, but is obviously sub-optimal. totalFreeBits = 0; for (int i = 0; i < map.Length; i++) { this.map[i] = 0xff; } for (int i = 0; i < totalBits / 8; i++) { ClearBits(i, 0, 8); } // Clean up trailing bits if ((totalBits & 0x07) != 0) { ClearBits((totalBits / 8), 0, totalBits & 0x07); } } private static int LeadingFree(byte value) { return ((int)LRContiguousTable[value]) >> 4; } private static int TrailingFree(byte value) { return ((int)LRContiguousTable[value]) & 0x0f; } private static int MaxFreeStart(byte value) { return ((int)MaxFreeTable[value]) >> 4; } private static int MaxFree(byte value) { return ((int)MaxFreeTable[value]) & 0xf; } private static int BitCount(byte value) { return (int)BitCountTable[value]; } private static int MakeMask(int startIndex, int length) /*^ requires startIndex >= 0 && startIndex + length <= 8; ^*/ /*^ requires length >= 0 && length <= 8; ^*/ { int a = 0xff >> startIndex; int b = 0xff << (8 - length - startIndex); return a & b; } private void SetBits(int byteIndex, int bitIndex, int length) { Debug.Assert(length >= 0 && length <= 8); Debug.Assert(bitIndex >= 0 && bitIndex + length <= 8); int tmp = (int)this.map[byteIndex]; int mask = MakeMask(bitIndex, length); tmp ^= mask; Debug.Assert((tmp & mask) == mask); this.map[byteIndex] = (byte) tmp; SummarizeAlloc(byteIndex, length); } private void ClearBits(int byteIndex, int bitIndex, int length) /*^ requires length <= SmallAllocationLength; ^*/ { Debug.Assert(length >= 0 && length <= 8); Debug.Assert(bitIndex >= 0 && bitIndex + length <= 8); int tmp = (int)this.map[byteIndex]; int mask = MakeMask(bitIndex, length); tmp ^= mask; Debug.Assert((tmp & mask) == 0); this.map[byteIndex] = (byte) tmp; SummarizeFree(byteIndex, length); } private void SetRegion(int byteIndex, int bitIndex, int length) /*^ requires byteIndex >= 0; ^*/ /*^ requires bitIndex >= 0 && bitIndex <= 7; ^*/ /*^ requires length > SmallAllocationLength; ^*/ { int lhs = 8 - bitIndex; SetBits(byteIndex, bitIndex, lhs); int fill8 = (length - lhs) / 8; for (int i = 1; i <= fill8; i++) { SetBits(byteIndex + i, 0, 8); } int rhs = length - (lhs + fill8 * 8); if (rhs != 0) { SetBits(byteIndex + fill8 + 1, 0, rhs); } } private void ClearRegion(int byteIndex, int bitIndex, int length) /*^ requires byteIndex >= 0; ^*/ /*^ requires bitIndex >= 0 && bitIndex <= 7; ^*/ /*^ requires length > SmallAllocationLength; ^*/ { int lhs = 8 - bitIndex; ClearBits(byteIndex, bitIndex, lhs); int fill8 = (length - lhs) / 8; for (int i = 1; i <= fill8; i++) { ClearBits(byteIndex + i, 0, 8); } int rhs = length - (lhs + fill8 * 8); if (rhs != 0) { ClearBits(byteIndex + fill8 + 1, 0, rhs); } } private bool IsAllocated(int bitIndex) { int mask = 0x80 >> (bitIndex & 0x7); int value = (int) this.map[bitIndex / 8]; return (value & mask) == mask; } private int GetLeadingClearBytes(int startByte, int maxLength) /*^ requires startByte >= 0; ^*/ /*^ requires maxLength >= 0; ^*/ /*^ requires startByte + maxLength <= this.map.Length; ^*/ { for (int i = 0; i < maxLength; i++) { if (this.map[startByte + i] != 0) { return i; } } return maxLength; } private bool HintAllocateSmall(int requestLength, int hintStart, out int allocStart, out int allocLength) { Debug.Assert(this.IsAllocated(hintStart) == false); Debug.Assert(requestLength <= SmallAllocationLength); int byteIndex = hintStart / 8; int bitIndex = hintStart - byteIndex * 8; int edgeMask = (0xff00 >> bitIndex) & 0xff; int lhs = LeftEdgeContiguousTable[ this.map[byteIndex] | edgeMask ]; if (lhs >= requestLength) { // Allocation confined to single byte in bitmap allocStart = hintStart; allocLength = requestLength; SetBits(byteIndex, bitIndex, allocLength); return true; } int rhs = LeadingFree( this.map[byteIndex + 1] ); if (lhs == (8 - bitIndex) && lhs + rhs >= requestLength) { // Allocation spans two bytes in bitmap allocStart = hintStart; allocLength = requestLength; SetBits(byteIndex, 8 - lhs, lhs); SetBits(byteIndex + 1, 0, requestLength - lhs); return true; } return ScanAllocateSmall(byteIndex + 1, requestLength, out allocStart, out allocLength); } private bool HintAllocateLarge(int requestLength, int hintStart, out int allocStart, out int allocLength) { // Set bit just before hint start if not already set so // ScanAllocateLarge starts from at least hintStart. bool didFiddle = false; int bitIndex = 0; int byteIndex = 0; if (hintStart > 0 && !IsAllocated(hintStart - 1)) { byteIndex = (hintStart - 1) / 8; bitIndex = (hintStart - 1) - byteIndex * 8; SetBits(byteIndex, bitIndex, 1); didFiddle = true; } try { return ScanAllocateLarge(hintStart / 8, requestLength, out allocStart, out allocLength); } finally { if (didFiddle) { ClearBits(byteIndex, bitIndex, 1); } } } private bool HintAllocate(int requestLength, int hintStart, out int allocStart, out int allocLength) { if (requestLength <= SmallAllocationLength) { return HintAllocateSmall(requestLength, hintStart, out allocStart, out allocLength); } else { return HintAllocateLarge(requestLength, hintStart, out allocStart, out allocLength); } } private bool ScanAllocateSmall(int startByte, int requestLength, out int allocStart, out int allocLength) /*^ requires requestLength <= SmallAllocationLength; ^*/ { if (startByte >= this.map.Length) { allocStart = 0; allocLength = 0; return false; } int endByte = startByte + SummaryFanOut; if (endByte >= map.Length) { endByte = map.Length; } for (int i = startByte; i < endByte; i++) { int maxFree = MaxFree(this.map[i]); if (maxFree == 0) { continue; } int startFree = MaxFreeStart(this.map[i]); if (maxFree >= requestLength) { allocStart = i * 8 + startFree; allocLength = requestLength; SetBits(i, MaxFreeStart(this.map[i]), requestLength); return true; } int lhs = TrailingFree(this.map[i]); int rhs = LeadingFree(this.map[i + 1]); if (lhs + rhs >= requestLength) { Debug.Assert(lhs != requestLength); allocStart = i * 8 + 8 - lhs; allocLength = requestLength; SetBits(i, 8 - lhs, lhs); SetBits(i + 1, 0, requestLength - lhs); return true; } } allocStart = 0; allocLength = 0; return false; } private bool ScanAllocateLarge(int startByte, int requestLength, out int allocStart, out int allocLength) /*^ requires requestLength > SmallAllocationLength; ^*/ /*^ requires requestLength <= MaxAllocationLength; ^*/ { if (startByte >= this.map.Length) { allocStart = 0; allocLength = 0; return false; } int maxRangeBytes = (requestLength + 14) / 8; int endByte = startByte + SummaryFanOut; if (endByte > this.map.Length - maxRangeBytes) { endByte = this.map.Length - maxRangeBytes; } for (int i = startByte; i < endByte; i++) { int lhs = TrailingFree(this.map[i]); if (lhs == 0) { continue; } int clearBytes = GetLeadingClearBytes(i + 1, maxRangeBytes - 1); if (lhs + clearBytes * 8 >= requestLength) { SetRegion(i, 8 - lhs, requestLength); allocStart = i * 8 + (8 - lhs); allocLength = requestLength; return true; } int rhs = LeadingFree(this.map[i + clearBytes + 1]); if (rhs + lhs + clearBytes * 8 >= requestLength) { SetRegion(i, 8 - lhs, requestLength); allocStart = i * 8 + (8 - lhs); allocLength = requestLength; return true; } else { i += maxRangeBytes - 2; } } allocStart = 0; allocLength = 0; return false; } private bool ScanAllocate(int startByte, int requestLength, out int allocStart, out int allocLength) { if (requestLength < SmallAllocationLength) { return ScanAllocateSmall(startByte, requestLength, out allocStart, out allocLength); } else { return ScanAllocateLarge(startByte, requestLength, out allocStart, out allocLength); } } /// Allocate contiguous block of specified length near /// requested start position. /// This method attempts to find contiguous block /// of specified length near the hinted start position. /// /// true on success, false if allocation fails. /// internal bool Allocate(int requestStart, int requestLength, out int allocStart, out int allocLength) /*^ requires requestLength > 0; ^*/ /*^ requires requestStart < this.totalBits; ^*/ { if (requestLength > MaxAllocationLength || totalFreeBits == 0) { allocStart = 0; allocLength = 0; return false; } if (requestStart <= totalBits && IsAllocated(requestStart) == false && HintAllocate(requestLength, requestStart, out allocStart, out allocLength)) { CheckInvariants(); return true; } return Allocate(requestLength, out allocStart, out allocLength); } // This is a recursive method that examines // the summary information held in the logarithmically // arranged summary data and explores regions that have // sufficient reserve to potentially fulfill the // allocation request. private bool DrillDownScan(int depth, int position, int requestLength, out int allocStart, out int allocLength) { if (depth == summary.Length) { return ScanAllocate(position, requestLength, out allocStart, out allocLength); } int [] /*!*/ totals = /*^ (!) ^*/ summary[depth]; int end = Math.Min(position + SummaryFanOut, totals.Length); for (int i = position; i < end; i++) { // Explore lower level if this cell has a total geq the // target length, or, this cell and its neighbor have // combined total geq the target length. if ((totals[i] >= requestLength) || (totals.Length > i + 1 && totals[i] + totals[i + 1] >= requestLength)) { if (DrillDownScan(depth + 1, i * SummaryFanOut, requestLength, out allocStart, out allocLength)) { return true; } } } allocStart = 0; allocLength = 0; return false; } /// Allocate contiguous block of specified length. /// This method attempts to allocate region of specified length. /// /// true on success, false if allocation fails. /// internal bool Allocate(int requestLength, out int allocStart, out int allocLength) /*^ requires requestLength > 0; ^*/ /*^ requires requestLength <= MaxAllocationLength; ^*/ { if (requestLength > MaxAllocationLength || totalFreeBits == 0) { allocStart = 0; allocLength = 0; return false; } CheckInvariants(); try { return DrillDownScan(0, 0, requestLength, out allocStart, out allocLength); } finally { CheckInvariants(); } } private void FreeSmall(int start, int length) { Debug.Assert(length <= SmallAllocationLength); int byteIndex = start / 8; int bitIndex = start - byteIndex * 8; if (bitIndex + length <= 8) { ClearBits(byteIndex, bitIndex, length); } else { ClearBits(byteIndex, bitIndex, 8 - bitIndex); ClearBits(byteIndex + 1, 0, length - (8 - bitIndex)); } } private void FreeLarge(int start, int length) { int byteIndex = start / 8; int bitIndex = start - byteIndex * 8; ClearRegion(byteIndex, bitIndex, length); } internal void Free(int start, int length) { if (length <= SmallAllocationLength) { FreeSmall(start, length); } else { FreeLarge(start, length); } } /// Scans bitmap for first set bit. Scan starts /// from supplied position. // value >= 0 on success, -1 on failure. /*^ [Microsoft.Contracts.Pure] ^*/ internal int GetFirstSetBitFrom(int startBit) { int byteIndex = startBit / 8; int bitIndex = startBit - byteIndex * 8; int m = MakeMask(bitIndex, 8 - bitIndex) & (int)this.map[byteIndex]; int skip = LeadingFree((byte)m); Debug.Assert(skip >= bitIndex); if (skip != 8) { return startBit - bitIndex + skip; } for (int i = byteIndex + 1; i < map.Length; i++) { skip = LeadingFree(this.map[i]); if (skip != 8) { return 8 * i + skip; } } return -1; } /// Scans bitmap for first clear bit. Scan /// starts from supplied position. // value >= 0 on success, -1 on failure. internal int GetFirstClearBitFrom(int startBit) { int byteIndex = startBit / 8; int bitIndex = startBit - byteIndex * 8; int m = MakeMask(bitIndex, 8 - bitIndex) & ~(int)this.map[byteIndex]; int skip = LeadingFree((byte)m); Debug.Assert(skip >= bitIndex); if (skip != 8) { return startBit - bitIndex + skip; } for (int i = byteIndex + 1; i < map.Length; i++) { int tmp = ~(int)this.map[i]; skip = LeadingFree((byte)tmp); if (skip != 8) { return 8 * i + skip; } } return -1; } /// Scans bitmap for first set bit. Scan /// starts from supplied position. // value >= 0 on success, -1 on failure. internal int GetFirstBitSetBefore(int startBit) requires (startBit < this.totalBits); { int byteIndex = startBit / 8; int bitIndex = startBit - byteIndex * 8; int m = MakeMask(0, bitIndex) & (int)this.map[byteIndex]; int r = TrailingFree((byte)m); if (r != 8) { return (byteIndex * 8) + (7 - r); } for (int i = byteIndex - 1; i >= 0; i--) { r = TrailingFree(this.map[i]); if (r != 8) { return 8 * i + (7 - r); } } return -1; } internal int Length { get { return totalBits; } } internal int FreeCount { get { return totalFreeBits; } } internal int UsedCount { get { return Length - FreeCount; } } private void SummarizeAlloc(int byteIndex, int numberOfBits) { totalFreeBits -= numberOfBits; int n = byteIndex; for (int i = summary.Length - 1; i >= 0; i--) { n = n / SummaryFanOut; (/*^ (!) ^*/summary[i])[n] -= numberOfBits; } CheckInvariants(); } private void SummarizeFree(int byteIndex, int numberOfBits) { totalFreeBits += numberOfBits; int n = byteIndex; for (int i = summary.Length - 1; i >= 0; i--) { n = n / SummaryFanOut; (/*^ (!) ^*/summary[i])[n] += numberOfBits; } CheckInvariants(); } [ Conditional("CHECK_INVARIANTS") ] private static void FastBitCount(byte [] /*!*/ data, out int sum) { const int GroupCount = 64; const int GroupRound = -64; // sgc croaks if GroupRound is assigned // using: // const int GroupRound = ~(GroupCount - 1); // const int GroupRound = ~63; int rounded = data.Length & GroupRound; int i = 0; sum = 0; while (i < rounded) { sum += (int) BitCountTable[ data[i + 0] ]; sum += (int) BitCountTable[ data[i + 1] ]; sum += (int) BitCountTable[ data[i + 2] ]; sum += (int) BitCountTable[ data[i + 3] ]; sum += (int) BitCountTable[ data[i + 4] ]; sum += (int) BitCountTable[ data[i + 5] ]; sum += (int) BitCountTable[ data[i + 6] ]; sum += (int) BitCountTable[ data[i + 7] ]; sum += (int) BitCountTable[ data[i + 8] ]; sum += (int) BitCountTable[ data[i + 9] ]; sum += (int) BitCountTable[ data[i + 10] ]; sum += (int) BitCountTable[ data[i + 11] ]; sum += (int) BitCountTable[ data[i + 12] ]; sum += (int) BitCountTable[ data[i + 13] ]; sum += (int) BitCountTable[ data[i + 14] ]; sum += (int) BitCountTable[ data[i + 15] ]; sum += (int) BitCountTable[ data[i + 16] ]; sum += (int) BitCountTable[ data[i + 17] ]; sum += (int) BitCountTable[ data[i + 18] ]; sum += (int) BitCountTable[ data[i + 19] ]; sum += (int) BitCountTable[ data[i + 20] ]; sum += (int) BitCountTable[ data[i + 21] ]; sum += (int) BitCountTable[ data[i + 22] ]; sum += (int) BitCountTable[ data[i + 23] ]; sum += (int) BitCountTable[ data[i + 24] ]; sum += (int) BitCountTable[ data[i + 25] ]; sum += (int) BitCountTable[ data[i + 26] ]; sum += (int) BitCountTable[ data[i + 27] ]; sum += (int) BitCountTable[ data[i + 28] ]; sum += (int) BitCountTable[ data[i + 29] ]; sum += (int) BitCountTable[ data[i + 30] ]; sum += (int) BitCountTable[ data[i + 31] ]; sum += (int) BitCountTable[ data[i + 32] ]; sum += (int) BitCountTable[ data[i + 33] ]; sum += (int) BitCountTable[ data[i + 34] ]; sum += (int) BitCountTable[ data[i + 35] ]; sum += (int) BitCountTable[ data[i + 36] ]; sum += (int) BitCountTable[ data[i + 37] ]; sum += (int) BitCountTable[ data[i + 38] ]; sum += (int) BitCountTable[ data[i + 39] ]; sum += (int) BitCountTable[ data[i + 40] ]; sum += (int) BitCountTable[ data[i + 41] ]; sum += (int) BitCountTable[ data[i + 42] ]; sum += (int) BitCountTable[ data[i + 43] ]; sum += (int) BitCountTable[ data[i + 44] ]; sum += (int) BitCountTable[ data[i + 45] ]; sum += (int) BitCountTable[ data[i + 46] ]; sum += (int) BitCountTable[ data[i + 47] ]; sum += (int) BitCountTable[ data[i + 48] ]; sum += (int) BitCountTable[ data[i + 49] ]; sum += (int) BitCountTable[ data[i + 50] ]; sum += (int) BitCountTable[ data[i + 51] ]; sum += (int) BitCountTable[ data[i + 52] ]; sum += (int) BitCountTable[ data[i + 53] ]; sum += (int) BitCountTable[ data[i + 54] ]; sum += (int) BitCountTable[ data[i + 55] ]; sum += (int) BitCountTable[ data[i + 56] ]; sum += (int) BitCountTable[ data[i + 57] ]; sum += (int) BitCountTable[ data[i + 58] ]; sum += (int) BitCountTable[ data[i + 59] ]; sum += (int) BitCountTable[ data[i + 60] ]; sum += (int) BitCountTable[ data[i + 61] ]; sum += (int) BitCountTable[ data[i + 62] ]; sum += (int) BitCountTable[ data[i + 63] ]; i += GroupCount; } while (i != data.Length) { sum += BitCount(data[i++]); } #if ALTERNATE_FAST_COUNT // Neither method here seems particularly efficient. The // unrolled loop still entails bounds checks on every access and // the cost of calling the bit converter appears to be moderately // high. sum = 0; int i = 0; int max = data.Length & 0x3; while (i < max) { uint v = BitConverter.ToUInt32(data, i); uint w = v - ((v >> 1) & 0x55555555); uint x = (w & 0x33333333) + ((w >> 2) & 0x33333333); uint c = ((x + (x >> 4) & 0xF0F0F0F) * 0x1010101) >> 24; sum += (int) c; i += 4; } while (i != data.Length) { sum += BitCount(data[i++]); } #endif } [ Conditional("CHECK_INVARIANTS") ] private void CheckInvariants() { for (int i = 0; i < summary.Length; i++) { int [] /*!*/ s = /*^ (!) ^*/ summary[i]; int totalSummary = 0; for (int j = 0; j < s.Length; j++) { totalSummary += s[j]; Debug.Assert(s[j] >= 0); } Debug.Assert(totalSummary == totalFreeBits); } int totalMap = 0; FastBitCount(this.map, out totalMap); Debug.Assert(totalMap == map.Length * 8 - totalFreeBits); } private static int Log2Up(int x) /*^ requires x >= 0 && x <= 0x40000000; ^*/ { // Calculate log2 of x rounded up to nearest power int i = 0; for (i = 0; i <= 30; i++) { if ((1 << i) >= x) { return i; } } // not reached Debug.Assert(false); return 0; } [ System.Diagnostics.Conditional("TEST_BITMAP") ] internal void Dump() { const int BPS = 64; for (int i = 0; i < Length; i += BPS) { Console.Write("\n{0:x6}: " , i); int todo = Math.Min(Length - i, BPS); for (int j = 0; j < todo; j++) { if ((j & 15) == 8) Console.Write(" "); Console.Write("{0}", IsAllocated(i + j) ? 1 : 0); } } Console.Write("\n"); } } /////////////////////////////////////////////////////////////////////////// // Test code #if TEST_BITMAP internal class BitmapTest { internal class AllocationRecord { internal int Start; internal int Length; internal AllocationRecord() {} internal AllocationRecord(int start, int length) { Start = start; Length = length; } } private static void TestSearch() { System.Console.WriteLine("TestSearch()"); const int BitCount = 1024; Random rng = new Random(0); bool failed = false; for (int i = 0; i < 1000 && !failed; i++) { Bitmap b = new Bitmap(BitCount); int start; int length; if (i == 0) { // This forces the initial byte checks in // GetFirst{Set,Clear}BitFrom(). start = 3; length = 2; } else if (i == 1) { // This forces code past initial byte checks in // GetFirst{Set,Clear}BitFrom(). start = 13; length = Bitmap.MaxAllocationLength; } else { start = rng.Next(BitCount); length = 1 + rng.Next(Math.Min(BitCount - start - 1, Bitmap.MaxAllocationLength - 1)); } int theStart, theLength; b.Allocate(start, length, out theStart, out theLength); Debug.Assert(start == theStart); Debug.Assert(length == theLength); Debug.Assert(b.GetFirstSetBitFrom(0) == theStart); for (int j = 0; j < length; j++) { Debug.Assert( b.GetFirstSetBitFrom(theStart + j) == theStart + j); } for (int j = theStart + theLength; j < b.Length; j++) { Debug.Assert(b.GetFirstClearBitFrom(j) == j); } } } private static void TestSearch2() { Console.WriteLine("TestSearch2()"); const int BitCount = 1024; Random rng = new Random(0); ArrayList allocations = new ArrayList(BitCount / 8); for (int r = 0; r < 1000; r++) { Bitmap b = new Bitmap(BitCount); int i = r % Bitmap.MaxAllocationLength; while (i < BitCount) { int length = 1 + Math.Min( b.Length - i - 1, rng.Next(Bitmap.MaxAllocationLength - 1) ); AllocationRecord a = new AllocationRecord(); b.Allocate(i, length, out a.Start, out a.Length); Debug.Assert(a.Start == i && a.Length == length); allocations.Add(a); i += 1 + length + rng.Next(Bitmap.MaxAllocationLength - 1); } i = 0; while (i < BitCount) { while (i < BitCount && b.GetFirstClearBitFrom(i) == i) { i++; } if (i >= BitCount) { break; } AllocationRecord /*!*/ a = /*^(!)^*/ (AllocationRecord)allocations[0]; allocations.RemoveAt(0); Debug.Assert(i == a.Start); int nextClear = b.GetFirstClearBitFrom(i); Debug.Assert(nextClear == i + a.Length || i + a.Length >= BitCount); int j = i; while (j < BitCount && b.GetFirstSetBitFrom(j) == j) { j++; } Debug.Assert(j == i + a.Length); i = j; } Debug.Assert(allocations.Count == 0); } } private static void TestFillThenEmpty(int bitmapSize, int allocationSize) { System.Console.WriteLine("TestFillThenEmpty({0}, {1})", bitmapSize, allocationSize); Debug.Assert((bitmapSize % allocationSize) == 0); Bitmap b = new Bitmap(bitmapSize); Debug.Assert(b.FreeCount == bitmapSize); // Allocate all bits in map for (int i = 0; i < bitmapSize; i += allocationSize) { int start; int length; if (! b.Allocate(allocationSize, out start, out length)) { b.Dump(); Console.WriteLine("Allocation failed (i = {0}, length = {1})", i, allocationSize); Debug.Assert(false); } Debug.Assert(start == i); Debug.Assert(length == allocationSize); Debug.Assert(b.FreeCount == b.Length - i - allocationSize); } // Try allocation when full Debug.Assert(b.FreeCount == 0); int dummyStart, dummyLength; Debug.Assert(! b.Allocate(1, out dummyStart, out dummyLength)); Debug.Assert(b.FreeCount == 0); // Free all bits in map for (int i = 0; i < bitmapSize; i += allocationSize) { b.Free(i, allocationSize); Debug.Assert(b.FreeCount == i + allocationSize); } Debug.Assert(b.FreeCount == b.Length); // Allocate all bits in specific positions for (int i = 0; i < bitmapSize; i += allocationSize) { int start; int length; b.Allocate(i, allocationSize, out start, out length); Debug.Assert(start == i); Debug.Assert(length == allocationSize); Debug.Assert(b.FreeCount == b.Length - i - allocationSize); } // Try allocation when full Debug.Assert(b.FreeCount == 0); Debug.Assert(! b.Allocate(1, out dummyStart, out dummyLength)); Debug.Assert(b.FreeCount == 0); // Free all bits in map for (int i = 0; i < bitmapSize; i += allocationSize) { b.Free(i, allocationSize); Debug.Assert(b.FreeCount == i + allocationSize); } Debug.Assert(b.FreeCount == b.Length); } private static void TestSkipFillThenEmpty(int bitmapSize, int allocationSize) { System.Console.WriteLine("TestSkipFillThenEmpty({0}, {1})", bitmapSize, allocationSize); for (int skip = 0; skip < allocationSize; skip++) { int size = bitmapSize - (bitmapSize % (allocationSize + skip)); Bitmap b = new Bitmap(size); int start = 0; int length; int canDo = bitmapSize / (allocationSize + skip); for (int i = 0; i < canDo; i++) { bool success = b.Allocate(i * (allocationSize + skip) + skip, allocationSize, out start, out length); Debug.Assert(success); Debug.Assert(length == allocationSize); } Debug.Assert(b.Allocate(allocationSize, out start, out length) == false); for (int i = 0; i < canDo; i++) { int oldFree = b.FreeCount; b.Free(i * (allocationSize + skip) + skip, allocationSize); Debug.Assert(oldFree == b.FreeCount - allocationSize); } } } private static void Hammer(int bitmapSize, double allocProbability) { Bitmap b = new Bitmap(bitmapSize); ArrayList allocations = new ArrayList(); Console.WriteLine("Hammer {0}, {1}", bitmapSize, allocProbability); Random rng = new Random(0); for (int i = 0; i < bitmapSize; i++) { if (rng.NextDouble() > (1.0 - allocProbability)) { int request = Math.Min(Bitmap.MaxAllocationLength, b.FreeCount); if (request == 0) continue; AllocationRecord a = new AllocationRecord(); int oldFree = b.FreeCount; if (b.Allocate(request, out a.Start, out a.Length) == true) { allocations.Add(a); Debug.Assert(a.Length == request); Debug.Assert(b.FreeCount + a.Length == oldFree); } } else { int count = allocations.Count; if (count > 0) { int index = rng.Next(count); AllocationRecord /*!*/ a = /*^(!)^*/ (AllocationRecord)allocations[index]; allocations.RemoveAt(index); int oldFree = b.FreeCount; b.Free(a.Start, a.Length); Debug.Assert(oldFree == b.FreeCount - a.Length); } } } } private static void HammerFixedHint(int bitmapSize, double allocProbability) { Bitmap b = new Bitmap(bitmapSize); ArrayList allocations = new ArrayList(); Console.WriteLine("HammerFixedHint {0}, {1}", bitmapSize, allocProbability); Random rng = new Random(0); for (int i = 0; i < bitmapSize; i++) { if (rng.NextDouble() > (1.0 - allocProbability)) { int request = 1 + Math.Min(Bitmap.MaxAllocationLength - 1, b.FreeCount - 1); if (request == 0) continue; int oldFree = b.FreeCount; AllocationRecord a = new AllocationRecord(); if (b.Allocate(bitmapSize / 3, request, out a.Start, out a.Length) == true) { allocations.Add(a); Debug.Assert(a.Length == request); Debug.Assert(oldFree == b.FreeCount + a.Length); } } else { int count = allocations.Count; if (count > 0) { int index = rng.Next(count); int oldFree = b.FreeCount; AllocationRecord /*!*/ a = /*^(!)^*/(AllocationRecord)allocations[index]; allocations.RemoveAt(index); b.Free(a.Start, a.Length); Debug.Assert(oldFree == b.FreeCount - a.Length); } } } } private static void HammerBadHint(int bitmapSize, double allocProbability) { Bitmap b = new Bitmap(bitmapSize); ArrayList allocations = new ArrayList(); Console.WriteLine("HammerBadHint {0}, {1}", bitmapSize, allocProbability); Random rng = new Random(0); for (int i = 0; i < bitmapSize; i++) { if (rng.NextDouble() > (1.0 - allocProbability)) { int length = Math.Min(Bitmap.MaxAllocationLength, b.FreeCount); if (length == 0) continue; AllocationRecord a = new AllocationRecord(); int start = rng.Next(bitmapSize); int oldFree = b.FreeCount; if (b.Allocate(start, length, out a.Start, out a.Length) == true) { allocations.Add(a); Debug.Assert(a.Length == length); Debug.Assert(oldFree == b.FreeCount + a.Length); } } else { int count = allocations.Count; if (count > 0) { int index = rng.Next(count); int oldFree = b.FreeCount; AllocationRecord /*!*/ a = /*^(!)^*/(AllocationRecord)allocations[index]; allocations.RemoveAt(index); b.Free(a.Start, a.Length); Debug.Assert(oldFree == b.FreeCount - a.Length); } } } } internal static void Main() { #if !DEBUG Console.WriteLine("Program needs to be compiles with /D:DEBUG /DEBUG flags"); Environment.Exit(-1); #endif TestSearch(); TestSearch2(); TestFillThenEmpty(333, 3); TestFillThenEmpty(28, 2); TestFillThenEmpty(256, 8); TestFillThenEmpty(13 * 17, 13); TestFillThenEmpty(17 * 17, 17); TestFillThenEmpty(1700, 17); TestFillThenEmpty(8 * 8 * 1024 * 2, 8); TestFillThenEmpty(8 * 8 * 1024 * 2, 32); TestFillThenEmpty(8 * 8 * 1024 * 2, 1); for (int i = 3; i < Bitmap.MaxAllocationLength; i++) { int s = 3 * 256 * 256 + Bitmap.MaxAllocationLength; if ((s % i) != 0) { s -= s % i; } TestFillThenEmpty(s, i); } for (int i = 1; i < Bitmap.MaxAllocationLength; i++) { TestSkipFillThenEmpty(13 * 1024 + i, i); } foreach (int s in new int [] { 172, 4247, 16384}) { foreach (double p in new double [] { 0.1, 0.5, 0.85, 0.99 }) { Hammer(s, p); HammerFixedHint(s, p); HammerBadHint(s, p); } } } } #endif // TEST_BITMAP }