IKVM11  11
Java SE 11 Virtual Machine for .NET
Loading...
Searching...
No Matches
HexConverter.cs
Go to the documentation of this file.
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
4using System.Buffers;
5using System.Diagnostics;
7
8namespace System
9{
10
11 internal static class HexConverter
12 {
13
14 public enum Casing : uint
15 {
16 // Output [ '0' .. '9' ] and [ 'A' .. 'F' ].
17 Upper = 0,
18
19 // Output [ '0' .. '9' ] and [ 'a' .. 'f' ].
20 // This works because values in the range [ 0x30 .. 0x39 ] ([ '0' .. '9' ])
21 // already have the 0x20 bit set, so ORing them with 0x20 is a no-op,
22 // while outputs in the range [ 0x41 .. 0x46 ] ([ 'A' .. 'F' ])
23 // don't have the 0x20 bit set, so ORing them maps to
24 // [ 0x61 .. 0x66 ] ([ 'a' .. 'f' ]), which is what we want.
25 Lower = 0x2020U,
26 }
27
28 // We want to pack the incoming byte into a single integer [ 0000 HHHH 0000 LLLL ],
29 // where HHHH and LLLL are the high and low nibbles of the incoming byte. Then
30 // subtract this integer from a constant minuend as shown below.
31 //
32 // [ 1000 1001 1000 1001 ]
33 // - [ 0000 HHHH 0000 LLLL ]
34 // =========================
35 // [ *YYY **** *ZZZ **** ]
36 //
37 // The end result of this is that YYY is 0b000 if HHHH <= 9, and YYY is 0b111 if HHHH >= 10.
38 // Similarly, ZZZ is 0b000 if LLLL <= 9, and ZZZ is 0b111 if LLLL >= 10.
39 // (We don't care about the value of asterisked bits.)
40 //
41 // To turn a nibble in the range [ 0 .. 9 ] into hex, we calculate hex := nibble + 48 (ascii '0').
42 // To turn a nibble in the range [ 10 .. 15 ] into hex, we calculate hex := nibble - 10 + 65 (ascii 'A').
43 // => hex := nibble + 55.
44 // The difference in the starting ASCII offset is (55 - 48) = 7, depending on whether the nibble is <= 9 or >= 10.
45 // Since 7 is 0b111, this conveniently matches the YYY or ZZZ value computed during the earlier subtraction.
46
47 // The commented out code below is code that directly implements the logic described above.
48
49 // uint packedOriginalValues = (((uint)value & 0xF0U) << 4) + ((uint)value & 0x0FU);
50 // uint difference = 0x8989U - packedOriginalValues;
51 // uint add7Mask = (difference & 0x7070U) >> 4; // line YYY and ZZZ back up with the packed values
52 // uint packedResult = packedOriginalValues + add7Mask + 0x3030U /* ascii '0' */;
53
54 // The code below is equivalent to the commented out code above but has been tweaked
55 // to allow codegen to make some extra optimizations.
56
57 // The low byte of the packed result contains the hex representation of the incoming byte's low nibble.
58 // The adjacent byte of the packed result contains the hex representation of the incoming byte's high nibble.
59
60 // Finally, write to the output buffer starting with the *highest* index so that codegen can
61 // elide all but the first bounds check. (This only works if 'startingIndex' is a compile-time constant.)
62
63 // The JIT can elide bounds checks if 'startingIndex' is constant and if the caller is
64 // writing to a span of known length (or the caller has already checked the bounds of the
65 // furthest access).
66 [MethodImpl(MethodImplOptions.AggressiveInlining)]
67 public static void ToBytesBuffer(byte value, Span<byte> buffer, int startingIndex = 0, Casing casing = Casing.Upper)
68 {
69 uint difference = ((value & 0xF0U) << 4) + (value & 0x0FU) - 0x8989U;
70 uint packedResult = (((uint)-(int)difference & 0x7070U) >> 4) + difference + 0xB9B9U | (uint)casing;
71
72 buffer[startingIndex + 1] = (byte)packedResult;
73 buffer[startingIndex] = (byte)(packedResult >> 8);
74 }
75
76 [MethodImpl(MethodImplOptions.AggressiveInlining)]
77 public static void ToCharsBuffer(byte value, Span<char> buffer, int startingIndex = 0, Casing casing = Casing.Upper)
78 {
79 uint difference = ((value & 0xF0U) << 4) + (value & 0x0FU) - 0x8989U;
80 uint packedResult = (((uint)-(int)difference & 0x7070U) >> 4) + difference + 0xB9B9U | (uint)casing;
81
82 buffer[startingIndex + 1] = (char)(packedResult & 0xFF);
83 buffer[startingIndex] = (char)(packedResult >> 8);
84 }
85
86#if SYSTEM_PRIVATE_CORELIB
87 // Converts Vector128<byte> into 2xVector128<byte> ASCII Hex representation
88 [MethodImpl(MethodImplOptions.AggressiveInlining)]
89 [CompExactlyDependsOn(typeof(Ssse3))]
90 [CompExactlyDependsOn(typeof(AdvSimd.Arm64))]
91 internal static (Vector128<byte>, Vector128<byte>) AsciiToHexVector128(Vector128<byte> src, Vector128<byte> hexMap)
92 {
93 Debug.Assert(Ssse3.IsSupported || AdvSimd.Arm64.IsSupported);
94 // The algorithm is simple: a single srcVec (contains the whole 16b Guid) is converted
95 // into nibbles and then, via hexMap, converted into a HEX representation via
96 // Shuffle(nibbles, srcVec). ASCII is then expanded to UTF-16.
97 Vector128<byte> shiftedSrc = Vector128.ShiftRightLogical(src.AsUInt64(), 4).AsByte();
98 Vector128<byte> lowNibbles = Vector128.UnpackLow(shiftedSrc, src);
99 Vector128<byte> highNibbles = Vector128.UnpackHigh(shiftedSrc, src);
100
101 return (Vector128.ShuffleUnsafe(hexMap, lowNibbles & Vector128.Create((byte)0xF)),
102 Vector128.ShuffleUnsafe(hexMap, highNibbles & Vector128.Create((byte)0xF)));
103 }
104
105 [CompExactlyDependsOn(typeof(Ssse3))]
106 [CompExactlyDependsOn(typeof(AdvSimd.Arm64))]
107 private static void EncodeToUtf16_Vector128(ReadOnlySpan<byte> bytes, Span<char> chars, Casing casing)
108 {
109 Debug.Assert(bytes.Length >= Vector128<int>.Count);
110
111 ref byte srcRef = ref MemoryMarshal.GetReference(bytes);
112 ref ushort destRef = ref Unsafe.As<char, ushort>(ref MemoryMarshal.GetReference(chars));
113
114 Vector128<byte> hexMap = casing == Casing.Upper ?
115 Vector128.Create((byte)'0', (byte)'1', (byte)'2', (byte)'3',
116 (byte)'4', (byte)'5', (byte)'6', (byte)'7',
117 (byte)'8', (byte)'9', (byte)'A', (byte)'B',
118 (byte)'C', (byte)'D', (byte)'E', (byte)'F') :
119 Vector128.Create((byte)'0', (byte)'1', (byte)'2', (byte)'3',
120 (byte)'4', (byte)'5', (byte)'6', (byte)'7',
121 (byte)'8', (byte)'9', (byte)'a', (byte)'b',
122 (byte)'c', (byte)'d', (byte)'e', (byte)'f');
123
124 nuint pos = 0;
125 nuint lengthSubVector128 = (nuint)bytes.Length - (nuint)Vector128<int>.Count;
126 do
127 {
128 // This implementation processes 4 bytes of input at once, it can be easily modified
129 // to support 16 bytes at once, but that didn't demonstrate noticeable wins
130 // for Converter.ToHexString (around 8% faster for large inputs) so
131 // it focuses on small inputs instead.
132
133 uint i32 = Unsafe.ReadUnaligned<uint>(ref Unsafe.Add(ref srcRef, pos));
134 Vector128<byte> vec = Vector128.CreateScalar(i32).AsByte();
135
136 // JIT is expected to eliminate all unused calculations
137 (Vector128<byte> hexLow, _) = AsciiToHexVector128(vec, hexMap);
138 (Vector128<ushort> v0, _) = Vector128.Widen(hexLow);
139
140 v0.StoreUnsafe(ref destRef, pos * 2);
141
142 pos += (nuint)Vector128<int>.Count;
143 if (pos == (nuint)bytes.Length)
144 {
145 return;
146 }
147
148 // Overlap with the current chunk for trailing elements
149 if (pos > lengthSubVector128)
150 {
151 pos = lengthSubVector128;
152 }
153
154 } while (true);
155 }
156#endif
157
158 public static void EncodeToUtf16(ReadOnlySpan<byte> bytes, Span<char> chars, Casing casing = Casing.Upper)
159 {
160 Debug.Assert(chars.Length >= bytes.Length * 2);
161
162 for (int pos = 0; pos < bytes.Length; pos++)
163 {
164 ToCharsBuffer(bytes[pos], chars, pos * 2, casing);
165 }
166 }
167
168#if NET
169
173 static readonly unsafe SpanAction<char, (nint ptr, int len, Casing cas)> EncodeToUtf16Action = (Span<char> chars, (nint ptr, int len, Casing cas) args) =>
174 {
175 EncodeToUtf16(new ReadOnlySpan<byte>((void*)args.ptr, args.len), chars, args.cas);
176 };
177
178#endif
179
180 public static unsafe string ToString(ReadOnlySpan<byte> bytes, Casing casing = Casing.Upper)
181 {
182#if NETFRAMEWORK || NETSTANDARD2_0
183 var result = bytes.Length > 16 ? new char[bytes.Length * 2].AsSpan() : stackalloc char[bytes.Length * 2];
184
185 int pos = 0;
186 foreach (byte b in bytes)
187 {
188 ToCharsBuffer(b, result, pos, casing);
189 pos += 2;
190 }
191
192 return result.ToString();
193#else
194 fixed (byte* b = bytes)
195 return string.Create(bytes.Length * 2, ((nint)b, bytes.Length, casing), EncodeToUtf16Action);
196#endif
197 }
198
199 [MethodImpl(MethodImplOptions.AggressiveInlining)]
200 public static char ToCharUpper(int value)
201 {
202 value &= 0xF;
203 value += '0';
204
205 if (value > '9')
206 value += 'A' - ('9' + 1);
207
208 return (char)value;
209 }
210
211 [MethodImpl(MethodImplOptions.AggressiveInlining)]
212 public static char ToCharLower(int value)
213 {
214 value &= 0xF;
215 value += '0';
216
217 if (value > '9')
218 value += 'a' - ('9' + 1);
219
220 return (char)value;
221 }
222
223 public static bool TryDecodeFromUtf16(ReadOnlySpan<char> chars, Span<byte> bytes, out int charsProcessed)
224 {
225 return TryDecodeFromUtf16_Scalar(chars, bytes, out charsProcessed);
226 }
227
228 static bool TryDecodeFromUtf16_Scalar(ReadOnlySpan<char> chars, Span<byte> bytes, out int charsProcessed)
229 {
230 Debug.Assert(chars.Length % 2 == 0, "Un-even number of characters provided");
231 Debug.Assert(chars.Length / 2 == bytes.Length, "Target buffer not right-sized for provided characters");
232
233 int i = 0;
234 int j = 0;
235 int byteLo = 0;
236 int byteHi = 0;
237 while (j < bytes.Length)
238 {
239 byteLo = FromChar(chars[i + 1]);
240 byteHi = FromChar(chars[i]);
241
242 // byteHi hasn't been shifted to the high half yet, so the only way the bitwise or produces this pattern
243 // is if either byteHi or byteLo was not a hex character.
244 if ((byteLo | byteHi) == 0xFF)
245 break;
246
247 bytes[j++] = (byte)(byteHi << 4 | byteLo);
248 i += 2;
249 }
250
251 if (byteLo == 0xFF)
252 i++;
253
254 charsProcessed = i;
255 return (byteLo | byteHi) != 0xFF;
256 }
257
258 [MethodImpl(MethodImplOptions.AggressiveInlining)]
259 public static int FromChar(int c)
260 {
261 return c >= CharToHexLookup.Length ? 0xFF : CharToHexLookup[c];
262 }
263
264 [MethodImpl(MethodImplOptions.AggressiveInlining)]
265 public static int FromUpperChar(int c)
266 {
267 return c > 71 ? 0xFF : CharToHexLookup[c];
268 }
269
270 [MethodImpl(MethodImplOptions.AggressiveInlining)]
271 public static int FromLowerChar(int c)
272 {
273 if ((uint)(c - '0') <= '9' - '0')
274 return c - '0';
275
276 if ((uint)(c - 'a') <= 'f' - 'a')
277 return c - 'a' + 10;
278
279 return 0xFF;
280 }
281
282 [MethodImpl(MethodImplOptions.AggressiveInlining)]
283 public static bool IsHexChar(int c)
284 {
285 if (IntPtr.Size == 8)
286 {
287 // This code path, when used, has no branches and doesn't depend on cache hits,
288 // so it's faster and does not vary in speed depending on input data distribution.
289 // We only use this logic on 64-bit systems, as using 64 bit values would otherwise
290 // be much slower than just using the lookup table anyway (no hardware support).
291 // The magic constant 18428868213665201664 is a 64 bit value containing 1s at the
292 // indices corresponding to all the valid hex characters (ie. "0123456789ABCDEFabcdef")
293 // minus 48 (ie. '0'), and backwards (so from the most significant bit and downwards).
294 // The offset of 48 for each bit is necessary so that the entire range fits in 64 bits.
295 // First, we subtract '0' to the input digit (after casting to uint to account for any
296 // negative inputs). Note that even if this subtraction underflows, this happens before
297 // the result is zero-extended to ulong, meaning that `i` will always have upper 32 bits
298 // equal to 0. We then left shift the constant with this offset, and apply a bitmask that
299 // has the highest bit set (the sign bit) if and only if `c` is in the ['0', '0' + 64) range.
300 // Then we only need to check whether this final result is less than 0: this will only be
301 // the case if both `i` was in fact the index of a set bit in the magic constant, and also
302 // `c` was in the allowed range (this ensures that false positive bit shifts are ignored).
303 ulong i = (uint)c - '0';
304 ulong shift = 18428868213665201664UL << (int)i;
305 ulong mask = i - 64;
306
307 return (long)(shift & mask) < 0 ? true : false;
308 }
309
310 return FromChar(c) != 0xFF;
311 }
312
313 [MethodImpl(MethodImplOptions.AggressiveInlining)]
314 public static bool IsHexUpperChar(int c)
315 {
316 return (uint)(c - '0') <= 9 || (uint)(c - 'A') <= 'F' - 'A';
317 }
318
319 [MethodImpl(MethodImplOptions.AggressiveInlining)]
320 public static bool IsHexLowerChar(int c)
321 {
322 return (uint)(c - '0') <= 9 || (uint)(c - 'a') <= 'f' - 'a';
323 }
324
326 public static ReadOnlySpan<byte> CharToHexLookup =>
327 [
328 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 15
329 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 31
330 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 47
331 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 63
332 0xFF, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 79
333 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 95
334 0xFF, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 111
335 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 127
336 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 143
337 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 159
338 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 175
339 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 191
340 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 207
341 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 223
342 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 239
343 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF // 255
344 ];
345
346 }
347
348}