| | | 1 | | // Licensed to the .NET Foundation under one or more agreements. |
| | | 2 | | // The .NET Foundation licenses this file to you under the MIT license. |
| | | 3 | | |
| | | 4 | | using System.Diagnostics; |
| | | 5 | | using System.Runtime.CompilerServices; |
| | | 6 | | |
| | | 7 | | namespace System.Text.Json |
| | | 8 | | { |
| | | 9 | | internal struct BitStack |
| | | 10 | | { |
| | | 11 | | // We are using a ulong to represent our nested state, so we can only |
| | | 12 | | // go 64 levels deep without having to allocate. |
| | | 13 | | private const int AllocationFreeMaxDepth = sizeof(ulong) * 8; |
| | | 14 | | |
| | | 15 | | private const int DefaultInitialArraySize = 2; |
| | | 16 | | |
| | | 17 | | // The backing array for the stack used when the depth exceeds AllocationFreeMaxDepth. |
| | | 18 | | private int[]? _array; |
| | | 19 | | |
| | | 20 | | // This ulong container represents a tiny stack to track the state during nested transitions. |
| | | 21 | | // The first bit represents the state of the current depth (1 == object, 0 == array). |
| | | 22 | | // Each subsequent bit is the parent / containing type (object or array). Since this |
| | | 23 | | // reader does a linear scan, we only need to keep a single path as we go through the data. |
| | | 24 | | // This is primarily used as an optimization to avoid having to allocate an object for |
| | | 25 | | // depths up to 64 (which is the default max depth). |
| | | 26 | | private ulong _allocationFreeContainer; |
| | | 27 | | |
| | | 28 | | private int _currentDepth; |
| | | 29 | | |
| | | 30 | | /// <summary> |
| | | 31 | | /// Gets the number of elements in the stack. |
| | | 32 | | /// </summary> |
| | 33952 | 33 | | public readonly int CurrentDepth => _currentDepth; |
| | | 34 | | |
| | | 35 | | /// <summary> |
| | | 36 | | /// Pushes <see langword="true"/> onto the stack. |
| | | 37 | | /// </summary> |
| | | 38 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 39 | | public void PushTrue() |
| | 82 | 40 | | { |
| | 82 | 41 | | if (_currentDepth < AllocationFreeMaxDepth) |
| | 82 | 42 | | { |
| | 82 | 43 | | _allocationFreeContainer = (_allocationFreeContainer << 1) | 1; |
| | 82 | 44 | | } |
| | | 45 | | else |
| | 0 | 46 | | { |
| | 0 | 47 | | PushToArray(true); |
| | 0 | 48 | | } |
| | 82 | 49 | | _currentDepth++; |
| | 82 | 50 | | } |
| | | 51 | | |
| | | 52 | | /// <summary> |
| | | 53 | | /// Pushes <see langword="false"/> onto the stack. |
| | | 54 | | /// </summary> |
| | | 55 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 56 | | public void PushFalse() |
| | 1354 | 57 | | { |
| | 1354 | 58 | | if (_currentDepth < AllocationFreeMaxDepth) |
| | 1354 | 59 | | { |
| | 1354 | 60 | | _allocationFreeContainer <<= 1; |
| | 1354 | 61 | | } |
| | | 62 | | else |
| | 0 | 63 | | { |
| | 0 | 64 | | PushToArray(false); |
| | 0 | 65 | | } |
| | 1354 | 66 | | _currentDepth++; |
| | 1354 | 67 | | } |
| | | 68 | | |
| | | 69 | | /// <summary> |
| | | 70 | | /// Pushes a bit onto the stack. Allocate the bit array lazily only when it is absolutely necessary. |
| | | 71 | | /// </summary> |
| | | 72 | | /// <param name="value">The bit to push onto the stack.</param> |
| | | 73 | | [MethodImpl(MethodImplOptions.NoInlining)] |
| | | 74 | | private void PushToArray(bool value) |
| | 0 | 75 | | { |
| | 0 | 76 | | _array ??= new int[DefaultInitialArraySize]; |
| | | 77 | | |
| | 0 | 78 | | int index = _currentDepth - AllocationFreeMaxDepth; |
| | | 79 | | |
| | 0 | 80 | | Debug.Assert(index >= 0, $"Set - Negative - index: {index}, arrayLength: {_array.Length}"); |
| | | 81 | | |
| | | 82 | | // Maximum possible array length if bitLength was int.MaxValue (i.e. 67_108_864) |
| | 0 | 83 | | Debug.Assert(_array.Length <= int.MaxValue / 32 + 1, $"index: {index}, arrayLength: {_array.Length}"); |
| | | 84 | | |
| | 0 | 85 | | int elementIndex = Div32Rem(index, out int extraBits); |
| | | 86 | | |
| | | 87 | | // Grow the array when setting a bit if it isn't big enough |
| | | 88 | | // This way the caller doesn't have to check. |
| | 0 | 89 | | if (elementIndex >= _array.Length) |
| | 0 | 90 | | { |
| | | 91 | | // This multiplication can overflow, so cast to uint first. |
| | 0 | 92 | | Debug.Assert(index >= 0 && index > (int)((uint)_array.Length * 32 - 1), $"Only grow when necessary - ind |
| | 0 | 93 | | DoubleArray(elementIndex); |
| | 0 | 94 | | } |
| | | 95 | | |
| | 0 | 96 | | Debug.Assert(elementIndex < _array.Length, $"Set - index: {index}, elementIndex: {elementIndex}, arrayLength |
| | | 97 | | |
| | 0 | 98 | | int newValue = _array[elementIndex]; |
| | 0 | 99 | | if (value) |
| | 0 | 100 | | { |
| | 0 | 101 | | newValue |= 1 << extraBits; |
| | 0 | 102 | | } |
| | | 103 | | else |
| | 0 | 104 | | { |
| | 0 | 105 | | newValue &= ~(1 << extraBits); |
| | 0 | 106 | | } |
| | 0 | 107 | | _array[elementIndex] = newValue; |
| | 0 | 108 | | } |
| | | 109 | | |
| | | 110 | | /// <summary> |
| | | 111 | | /// Pops the bit at the top of the stack and returns its value. |
| | | 112 | | /// </summary> |
| | | 113 | | /// <returns>The bit that was popped.</returns> |
| | | 114 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 115 | | public bool Pop() |
| | 332 | 116 | | { |
| | 332 | 117 | | _currentDepth--; |
| | | 118 | | bool inObject; |
| | 332 | 119 | | if (_currentDepth < AllocationFreeMaxDepth) |
| | 332 | 120 | | { |
| | 332 | 121 | | _allocationFreeContainer >>= 1; |
| | 332 | 122 | | inObject = (_allocationFreeContainer & 1) != 0; |
| | 332 | 123 | | } |
| | 0 | 124 | | else if (_currentDepth == AllocationFreeMaxDepth) |
| | 0 | 125 | | { |
| | 0 | 126 | | inObject = (_allocationFreeContainer & 1) != 0; |
| | 0 | 127 | | } |
| | | 128 | | else |
| | 0 | 129 | | { |
| | | 130 | | // Decrementing depth above effectively pops the last element in the array-backed case. |
| | 0 | 131 | | inObject = PeekInArray(); |
| | 0 | 132 | | } |
| | 332 | 133 | | return inObject; |
| | 332 | 134 | | } |
| | | 135 | | |
| | | 136 | | /// <summary> |
| | | 137 | | /// If the stack has a backing array allocated, this method will find the topmost bit in the array and return it |
| | | 138 | | /// This should only be called if the depth is greater than AllocationFreeMaxDepth and an array has been allocat |
| | | 139 | | /// </summary> |
| | | 140 | | [MethodImpl(MethodImplOptions.NoInlining)] |
| | | 141 | | private readonly bool PeekInArray() |
| | 0 | 142 | | { |
| | 0 | 143 | | int index = _currentDepth - AllocationFreeMaxDepth - 1; |
| | 0 | 144 | | Debug.Assert(_array != null); |
| | 0 | 145 | | Debug.Assert(index >= 0, $"Get - Negative - index: {index}, arrayLength: {_array.Length}"); |
| | | 146 | | |
| | 0 | 147 | | int elementIndex = Div32Rem(index, out int extraBits); |
| | | 148 | | |
| | 0 | 149 | | Debug.Assert(elementIndex < _array.Length, $"Get - index: {index}, elementIndex: {elementIndex}, arrayLength |
| | | 150 | | |
| | 0 | 151 | | return (_array[elementIndex] & (1 << extraBits)) != 0; |
| | 0 | 152 | | } |
| | | 153 | | |
| | | 154 | | /// <summary> |
| | | 155 | | /// Peeks at the bit at the top of the stack. |
| | | 156 | | /// </summary> |
| | | 157 | | /// <returns>The bit at the top of the stack.</returns> |
| | | 158 | | public readonly bool Peek() |
| | | 159 | | // If the stack is small enough, we can use the allocation-free container, otherwise check the allocated arr |
| | 0 | 160 | | => _currentDepth <= AllocationFreeMaxDepth ? (_allocationFreeContainer & 1) != 0 : PeekInArray(); |
| | | 161 | | |
| | | 162 | | private void DoubleArray(int minSize) |
| | 0 | 163 | | { |
| | 0 | 164 | | Debug.Assert(_array != null); |
| | 0 | 165 | | Debug.Assert(_array.Length < int.MaxValue / 2, $"Array too large - arrayLength: {_array.Length}"); |
| | 0 | 166 | | Debug.Assert(minSize >= 0 && minSize >= _array.Length); |
| | | 167 | | |
| | 0 | 168 | | int nextDouble = Math.Max(minSize + 1, _array.Length * 2); |
| | 0 | 169 | | Debug.Assert(nextDouble > minSize); |
| | | 170 | | |
| | 0 | 171 | | Array.Resize(ref _array, nextDouble); |
| | 0 | 172 | | } |
| | | 173 | | |
| | | 174 | | /// <summary> |
| | | 175 | | /// Optimization to push <see langword="true"/> as the first bit when the stack is empty. |
| | | 176 | | /// </summary> |
| | | 177 | | public void SetFirstBit() |
| | 1932 | 178 | | { |
| | 1932 | 179 | | Debug.Assert(_currentDepth == 0, "Only call SetFirstBit when depth is 0"); |
| | 1932 | 180 | | _currentDepth++; |
| | 1932 | 181 | | _allocationFreeContainer = 1; |
| | 1932 | 182 | | } |
| | | 183 | | |
| | | 184 | | /// <summary> |
| | | 185 | | /// Optimization to push <see langword="false"/> as the first bit when the stack is empty. |
| | | 186 | | /// </summary> |
| | | 187 | | public void ResetFirstBit() |
| | 7698 | 188 | | { |
| | 7698 | 189 | | Debug.Assert(_currentDepth == 0, "Only call ResetFirstBit when depth is 0"); |
| | 7698 | 190 | | _currentDepth++; |
| | 7698 | 191 | | _allocationFreeContainer = 0; |
| | 7698 | 192 | | } |
| | | 193 | | |
| | | 194 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 195 | | private static int Div32Rem(int number, out int remainder) |
| | 0 | 196 | | { |
| | 0 | 197 | | uint quotient = (uint)number / 32; |
| | 0 | 198 | | remainder = number & (32 - 1); // equivalent to number % 32, since 32 is a power of 2 |
| | 0 | 199 | | return (int)quotient; |
| | 0 | 200 | | } |
| | | 201 | | } |
| | | 202 | | } |