• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #region Copyright notice and license
2 // Protocol Buffers - Google's data interchange format
3 // Copyright 2008 Google Inc.  All rights reserved.
4 //
5 // Use of this source code is governed by a BSD-style
6 // license that can be found in the LICENSE file or at
7 // https://developers.google.com/open-source/licenses/bsd
8 #endregion
9 
10 using System;
11 using System.Buffers.Binary;
12 using System.Runtime.CompilerServices;
13 using System.Runtime.InteropServices;
14 #if GOOGLE_PROTOBUF_SIMD
15 using System.Runtime.Intrinsics;
16 using System.Runtime.Intrinsics.Arm;
17 using System.Runtime.Intrinsics.X86;
18 #endif
19 using System.Security;
20 using System.Text;
21 
22 namespace Google.Protobuf
23 {
24     /// <summary>
25     /// Primitives for encoding protobuf wire format.
26     /// </summary>
27     [SecuritySafeCritical]
28     internal static class WritingPrimitives
29     {
30 #if NET5_0_OR_GREATER
31       internal static Encoding Utf8Encoding => Encoding.UTF8;  // allows JIT to devirtualize
32 #else
33       internal static readonly Encoding Utf8Encoding =
34           Encoding.UTF8;  // "Local" copy of Encoding.UTF8, for efficiency. (Yes, it makes a
35                           // difference.)
36 #endif
37 
38         #region Writing of values (not including tags)
39 
40         /// <summary>
41         /// Writes a double field value, without a tag, to the stream.
42         /// </summary>
WriteDouble(ref Span<byte> buffer, ref WriterInternalState state, double value)43         public static void WriteDouble(ref Span<byte> buffer, ref WriterInternalState state, double value)
44         {
45             WriteRawLittleEndian64(ref buffer, ref state, (ulong)BitConverter.DoubleToInt64Bits(value));
46         }
47 
48         /// <summary>
49         /// Writes a float field value, without a tag, to the stream.
50         /// </summary>
WriteFloat(ref Span<byte> buffer, ref WriterInternalState state, float value)51         public static unsafe void WriteFloat(ref Span<byte> buffer, ref WriterInternalState state, float value)
52         {
53             const int length = sizeof(float);
54             if (buffer.Length - state.position >= length)
55             {
56                 // if there's enough space in the buffer, write the float directly into the buffer
57                 var floatSpan = buffer.Slice(state.position, length);
58                 Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(floatSpan), value);
59 
60                 if (!BitConverter.IsLittleEndian)
61                 {
62                     floatSpan.Reverse();
63                 }
64                 state.position += length;
65             }
66             else
67             {
68                 WriteFloatSlowPath(ref buffer, ref state, value);
69             }
70         }
71 
72         [MethodImpl(MethodImplOptions.NoInlining)]
WriteFloatSlowPath(ref Span<byte> buffer, ref WriterInternalState state, float value)73         private static unsafe void WriteFloatSlowPath(ref Span<byte> buffer, ref WriterInternalState state, float value)
74         {
75             const int length = sizeof(float);
76 
77             // TODO: deduplicate the code. Populating the span is the same as for the fastpath.
78             Span<byte> floatSpan = stackalloc byte[length];
79             Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(floatSpan), value);
80             if (!BitConverter.IsLittleEndian)
81             {
82                 floatSpan.Reverse();
83             }
84 
85             WriteRawByte(ref buffer, ref state, floatSpan[0]);
86             WriteRawByte(ref buffer, ref state, floatSpan[1]);
87             WriteRawByte(ref buffer, ref state, floatSpan[2]);
88             WriteRawByte(ref buffer, ref state, floatSpan[3]);
89         }
90 
91         /// <summary>
92         /// Writes a uint64 field value, without a tag, to the stream.
93         /// </summary>
WriteUInt64(ref Span<byte> buffer, ref WriterInternalState state, ulong value)94         public static void WriteUInt64(ref Span<byte> buffer, ref WriterInternalState state, ulong value)
95         {
96             WriteRawVarint64(ref buffer, ref state, value);
97         }
98 
99         /// <summary>
100         /// Writes an int64 field value, without a tag, to the stream.
101         /// </summary>
WriteInt64(ref Span<byte> buffer, ref WriterInternalState state, long value)102         public static void WriteInt64(ref Span<byte> buffer, ref WriterInternalState state, long value)
103         {
104             WriteRawVarint64(ref buffer, ref state, (ulong)value);
105         }
106 
107         /// <summary>
108         /// Writes an int32 field value, without a tag, to the stream.
109         /// </summary>
WriteInt32(ref Span<byte> buffer, ref WriterInternalState state, int value)110         public static void WriteInt32(ref Span<byte> buffer, ref WriterInternalState state, int value)
111         {
112             if (value >= 0)
113             {
114                 WriteRawVarint32(ref buffer, ref state, (uint)value);
115             }
116             else
117             {
118                 // Must sign-extend.
119                 WriteRawVarint64(ref buffer, ref state, (ulong)value);
120             }
121         }
122 
123         /// <summary>
124         /// Writes a fixed64 field value, without a tag, to the stream.
125         /// </summary>
WriteFixed64(ref Span<byte> buffer, ref WriterInternalState state, ulong value)126         public static void WriteFixed64(ref Span<byte> buffer, ref WriterInternalState state, ulong value)
127         {
128             WriteRawLittleEndian64(ref buffer, ref state, value);
129         }
130 
131         /// <summary>
132         /// Writes a fixed32 field value, without a tag, to the stream.
133         /// </summary>
WriteFixed32(ref Span<byte> buffer, ref WriterInternalState state, uint value)134         public static void WriteFixed32(ref Span<byte> buffer, ref WriterInternalState state, uint value)
135         {
136             WriteRawLittleEndian32(ref buffer, ref state, value);
137         }
138 
139         /// <summary>
140         /// Writes a bool field value, without a tag, to the stream.
141         /// </summary>
WriteBool(ref Span<byte> buffer, ref WriterInternalState state, bool value)142         public static void WriteBool(ref Span<byte> buffer, ref WriterInternalState state, bool value)
143         {
144             WriteRawByte(ref buffer, ref state, value ? (byte)1 : (byte)0);
145         }
146 
147         /// <summary>
148         /// Writes a string field value, without a tag, to the stream.
149         /// The data is length-prefixed.
150         /// </summary>
WriteString(ref Span<byte> buffer, ref WriterInternalState state, string value)151         public static void WriteString(ref Span<byte> buffer, ref WriterInternalState state, string value)
152         {
153             const int MaxBytesPerChar = 3;
154             const int MaxSmallStringLength = 128 / MaxBytesPerChar;
155 
156             // The string is small enough that the length will always be a 1 byte varint.
157             // Also there is enough space to write length + bytes to buffer.
158             // Write string directly to the buffer, and then write length.
159             // This saves calling GetByteCount on the string. We get the string length from GetBytes.
160             if (value.Length <= MaxSmallStringLength && buffer.Length - state.position - 1 >= value.Length * MaxBytesPerChar)
161             {
162                 int indexOfLengthDelimiter = state.position++;
163                 buffer[indexOfLengthDelimiter] = (byte)WriteStringToBuffer(buffer, ref state, value);
164                 return;
165             }
166 
167             int length = Utf8Encoding.GetByteCount(value);
168             WriteLength(ref buffer, ref state, length);
169 
170             // Optimise the case where we have enough space to write
171             // the string directly to the buffer, which should be common.
172             if (buffer.Length - state.position >= length)
173             {
174                 if (length == value.Length) // Must be all ASCII...
175                 {
176                     WriteAsciiStringToBuffer(buffer, ref state, value, length);
177                 }
178                 else
179                 {
180                     WriteStringToBuffer(buffer, ref state, value);
181                 }
182             }
183             else
184             {
185                 // Opportunity for future optimization:
186                 // Large strings that don't fit into the current buffer segment
187                 // can probably be optimized by using Utf8Encoding.GetEncoder()
188                 // but more benchmarks would need to be added as evidence.
189                 byte[] bytes = Utf8Encoding.GetBytes(value);
190                 WriteRawBytes(ref buffer, ref state, bytes);
191             }
192         }
193 
194         // Calling this method with non-ASCII content will break.
195         // Content must be verified to be all ASCII before using this method.
WriteAsciiStringToBuffer(Span<byte> buffer, ref WriterInternalState state, string value, int length)196         private static void WriteAsciiStringToBuffer(Span<byte> buffer, ref WriterInternalState state, string value, int length)
197         {
198             ref char sourceChars = ref MemoryMarshal.GetReference(value.AsSpan());
199             ref byte destinationBytes = ref MemoryMarshal.GetReference(buffer.Slice(state.position));
200 
201             int currentIndex = 0;
202             // If 64bit, process 4 chars at a time.
203             // The logic inside this check will be elided by JIT in 32bit programs.
204             if (IntPtr.Size == 8)
205             {
206                 // Need at least 4 chars available to use this optimization.
207                 if (length >= 4)
208                 {
209                     ref byte sourceBytes = ref Unsafe.As<char, byte>(ref sourceChars);
210 
211                     // Process 4 chars at a time until there are less than 4 remaining.
212                     // We already know all characters are ASCII so there is no need to validate the source.
213                     int lastIndexWhereCanReadFourChars = value.Length - 4;
214                     do
215                     {
216                         NarrowFourUtf16CharsToAsciiAndWriteToBuffer(
217                             ref Unsafe.AddByteOffset(ref destinationBytes, (IntPtr)currentIndex),
218                             Unsafe.ReadUnaligned<ulong>(ref Unsafe.AddByteOffset(ref sourceBytes, (IntPtr)(currentIndex * 2))));
219 
220                     } while ((currentIndex += 4) <= lastIndexWhereCanReadFourChars);
221                 }
222             }
223 
224             // Process any remaining, 1 char at a time.
225             // Avoid bounds checking with ref + Unsafe
226             for (; currentIndex < length; currentIndex++)
227             {
228                 Unsafe.AddByteOffset(ref destinationBytes, (IntPtr)currentIndex) = (byte)Unsafe.AddByteOffset(ref sourceChars, (IntPtr)(currentIndex * 2));
229             }
230 
231             state.position += length;
232         }
233 
234         // Copied with permission from https://github.com/dotnet/runtime/blob/1cdafd27e4afd2c916af5df949c13f8b373c4335/src/libraries/System.Private.CoreLib/src/System/Text/ASCIIUtility.cs#L1119-L1171
235         //
236         /// <summary>
237         /// Given a QWORD which represents a buffer of 4 ASCII chars in machine-endian order,
238         /// narrows each WORD to a BYTE, then writes the 4-byte result to the output buffer
239         /// also in machine-endian order.
240         /// </summary>
241         [MethodImpl(MethodImplOptions.AggressiveInlining)]
NarrowFourUtf16CharsToAsciiAndWriteToBuffer(ref byte outputBuffer, ulong value)242         private static void NarrowFourUtf16CharsToAsciiAndWriteToBuffer(ref byte outputBuffer, ulong value)
243         {
244 #if GOOGLE_PROTOBUF_SIMD
245             if (Sse2.X64.IsSupported)
246             {
247                 // Narrows a vector of words [ w0 w1 w2 w3 ] to a vector of bytes
248                 // [ b0 b1 b2 b3 b0 b1 b2 b3 ], then writes 4 bytes (32 bits) to the destination.
249 
250                 Vector128<short> vecWide = Sse2.X64.ConvertScalarToVector128UInt64(value).AsInt16();
251                 Vector128<uint> vecNarrow = Sse2.PackUnsignedSaturate(vecWide, vecWide).AsUInt32();
252                 Unsafe.WriteUnaligned<uint>(ref outputBuffer, Sse2.ConvertToUInt32(vecNarrow));
253             }
254             else if (AdvSimd.IsSupported)
255             {
256                 // Narrows a vector of words [ w0 w1 w2 w3 ] to a vector of bytes
257                 // [ b0 b1 b2 b3 * * * * ], then writes 4 bytes (32 bits) to the destination.
258 
259                 Vector128<short> vecWide = Vector128.CreateScalarUnsafe(value).AsInt16();
260                 Vector64<byte> lower = AdvSimd.ExtractNarrowingSaturateUnsignedLower(vecWide);
261                 Unsafe.WriteUnaligned<uint>(ref outputBuffer, lower.AsUInt32().ToScalar());
262             }
263             else
264 #endif
265             {
266                 // Fallback to non-SIMD approach when SIMD is not available.
267                 // This could happen either because the APIs are not available, or hardware doesn't support it.
268                 // Processing 4 chars at a time in this fallback is still faster than casting one char at a time.
269                 if (BitConverter.IsLittleEndian)
270                 {
271                     outputBuffer = (byte)value;
272                     value >>= 16;
273                     Unsafe.Add(ref outputBuffer, 1) = (byte)value;
274                     value >>= 16;
275                     Unsafe.Add(ref outputBuffer, 2) = (byte)value;
276                     value >>= 16;
277                     Unsafe.Add(ref outputBuffer, 3) = (byte)value;
278                 }
279                 else
280                 {
281                     Unsafe.Add(ref outputBuffer, 3) = (byte)value;
282                     value >>= 16;
283                     Unsafe.Add(ref outputBuffer, 2) = (byte)value;
284                     value >>= 16;
285                     Unsafe.Add(ref outputBuffer, 1) = (byte)value;
286                     value >>= 16;
287                     outputBuffer = (byte)value;
288                 }
289             }
290         }
291 
WriteStringToBuffer(Span<byte> buffer, ref WriterInternalState state, string value)292         private static int WriteStringToBuffer(Span<byte> buffer, ref WriterInternalState state, string value)
293         {
294 #if NETSTANDARD1_1
295             // slowpath when Encoding.GetBytes(Char*, Int32, Byte*, Int32) is not available
296             byte[] bytes = Utf8Encoding.GetBytes(value);
297             WriteRawBytes(ref buffer, ref state, bytes);
298             return bytes.Length;
299 #else
300             ReadOnlySpan<char> source = value.AsSpan();
301             int bytesUsed;
302             unsafe
303             {
304                 fixed (char* sourceChars = &MemoryMarshal.GetReference(source))
305                 fixed (byte* destinationBytes = &MemoryMarshal.GetReference(buffer))
306                 {
307                     bytesUsed = Utf8Encoding.GetBytes(
308                         sourceChars,
309                         source.Length,
310                         destinationBytes + state.position,
311                         buffer.Length - state.position);
312                 }
313             }
314             state.position += bytesUsed;
315             return bytesUsed;
316 #endif
317         }
318 
319         /// <summary>
320         /// Write a byte string, without a tag, to the stream.
321         /// The data is length-prefixed.
322         /// </summary>
WriteBytes(ref Span<byte> buffer, ref WriterInternalState state, ByteString value)323         public static void WriteBytes(ref Span<byte> buffer, ref WriterInternalState state, ByteString value)
324         {
325             WriteLength(ref buffer, ref state, value.Length);
326             WriteRawBytes(ref buffer, ref state, value.Span);
327         }
328 
329         /// <summary>
330         /// Writes a uint32 value, without a tag, to the stream.
331         /// </summary>
WriteUInt32(ref Span<byte> buffer, ref WriterInternalState state, uint value)332         public static void WriteUInt32(ref Span<byte> buffer, ref WriterInternalState state, uint value)
333         {
334             WriteRawVarint32(ref buffer, ref state, value);
335         }
336 
337         /// <summary>
338         /// Writes an enum value, without a tag, to the stream.
339         /// </summary>
WriteEnum(ref Span<byte> buffer, ref WriterInternalState state, int value)340         public static void WriteEnum(ref Span<byte> buffer, ref WriterInternalState state, int value)
341         {
342             WriteInt32(ref buffer, ref state, value);
343         }
344 
345         /// <summary>
346         /// Writes an sfixed32 value, without a tag, to the stream.
347         /// </summary>
WriteSFixed32(ref Span<byte> buffer, ref WriterInternalState state, int value)348         public static void WriteSFixed32(ref Span<byte> buffer, ref WriterInternalState state, int value)
349         {
350             WriteRawLittleEndian32(ref buffer, ref state, (uint)value);
351         }
352 
353         /// <summary>
354         /// Writes an sfixed64 value, without a tag, to the stream.
355         /// </summary>
WriteSFixed64(ref Span<byte> buffer, ref WriterInternalState state, long value)356         public static void WriteSFixed64(ref Span<byte> buffer, ref WriterInternalState state, long value)
357         {
358             WriteRawLittleEndian64(ref buffer, ref state, (ulong)value);
359         }
360 
361         /// <summary>
362         /// Writes an sint32 value, without a tag, to the stream.
363         /// </summary>
WriteSInt32(ref Span<byte> buffer, ref WriterInternalState state, int value)364         public static void WriteSInt32(ref Span<byte> buffer, ref WriterInternalState state, int value)
365         {
366             WriteRawVarint32(ref buffer, ref state, EncodeZigZag32(value));
367         }
368 
369         /// <summary>
370         /// Writes an sint64 value, without a tag, to the stream.
371         /// </summary>
WriteSInt64(ref Span<byte> buffer, ref WriterInternalState state, long value)372         public static void WriteSInt64(ref Span<byte> buffer, ref WriterInternalState state, long value)
373         {
374             WriteRawVarint64(ref buffer, ref state, EncodeZigZag64(value));
375         }
376 
377         /// <summary>
378         /// Writes a length (in bytes) for length-delimited data.
379         /// </summary>
380         /// <remarks>
381         /// This method simply writes a rawint, but exists for clarity in calling code.
382         /// </remarks>
WriteLength(ref Span<byte> buffer, ref WriterInternalState state, int length)383         public static void WriteLength(ref Span<byte> buffer, ref WriterInternalState state, int length)
384         {
385             WriteRawVarint32(ref buffer, ref state, (uint)length);
386         }
387 
388         #endregion
389 
390         #region Writing primitives
391         /// <summary>
392         /// Writes a 32 bit value as a varint. The fast route is taken when
393         /// there's enough buffer space left to whizz through without checking
394         /// for each byte; otherwise, we resort to calling WriteRawByte each time.
395         /// </summary>
WriteRawVarint32(ref Span<byte> buffer, ref WriterInternalState state, uint value)396         public static void WriteRawVarint32(ref Span<byte> buffer, ref WriterInternalState state, uint value)
397         {
398             // Optimize for the common case of a single byte value
399             if (value < 128 && state.position < buffer.Length)
400             {
401                 buffer[state.position++] = (byte)value;
402                 return;
403             }
404 
405             // Fast path when capacity is available
406             while (state.position < buffer.Length)
407             {
408                 if (value > 127)
409                 {
410                     buffer[state.position++] = (byte)((value & 0x7F) | 0x80);
411                     value >>= 7;
412                 }
413                 else
414                 {
415                     buffer[state.position++] = (byte)value;
416                     return;
417                 }
418             }
419 
420             while (value > 127)
421             {
422                 WriteRawByte(ref buffer, ref state, (byte)((value & 0x7F) | 0x80));
423                 value >>= 7;
424             }
425 
426             WriteRawByte(ref buffer, ref state, (byte)value);
427         }
428 
WriteRawVarint64(ref Span<byte> buffer, ref WriterInternalState state, ulong value)429         public static void WriteRawVarint64(ref Span<byte> buffer, ref WriterInternalState state, ulong value)
430         {
431             // Optimize for the common case of a single byte value
432             if (value < 128 && state.position < buffer.Length)
433             {
434                 buffer[state.position++] = (byte)value;
435                 return;
436             }
437 
438             // Fast path when capacity is available
439             while (state.position < buffer.Length)
440             {
441                 if (value > 127)
442                 {
443                     buffer[state.position++] = (byte)((value & 0x7F) | 0x80);
444                     value >>= 7;
445                 }
446                 else
447                 {
448                     buffer[state.position++] = (byte)value;
449                     return;
450                 }
451             }
452 
453             while (value > 127)
454             {
455                 WriteRawByte(ref buffer, ref state, (byte)((value & 0x7F) | 0x80));
456                 value >>= 7;
457             }
458 
459             WriteRawByte(ref buffer, ref state, (byte)value);
460         }
461 
WriteRawLittleEndian32(ref Span<byte> buffer, ref WriterInternalState state, uint value)462         public static void WriteRawLittleEndian32(ref Span<byte> buffer, ref WriterInternalState state, uint value)
463         {
464             const int length = sizeof(uint);
465             if (state.position + length > buffer.Length)
466             {
467                 WriteRawLittleEndian32SlowPath(ref buffer, ref state, value);
468             }
469             else
470             {
471                 BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(state.position), value);
472                 state.position += length;
473             }
474         }
475 
476         [MethodImpl(MethodImplOptions.NoInlining)]
WriteRawLittleEndian32SlowPath(ref Span<byte> buffer, ref WriterInternalState state, uint value)477         private static void WriteRawLittleEndian32SlowPath(ref Span<byte> buffer, ref WriterInternalState state, uint value)
478         {
479             WriteRawByte(ref buffer, ref state, (byte)value);
480             WriteRawByte(ref buffer, ref state, (byte)(value >> 8));
481             WriteRawByte(ref buffer, ref state, (byte)(value >> 16));
482             WriteRawByte(ref buffer, ref state, (byte)(value >> 24));
483         }
484 
WriteRawLittleEndian64(ref Span<byte> buffer, ref WriterInternalState state, ulong value)485         public static void WriteRawLittleEndian64(ref Span<byte> buffer, ref WriterInternalState state, ulong value)
486         {
487             const int length = sizeof(ulong);
488             if (state.position + length > buffer.Length)
489             {
490                 WriteRawLittleEndian64SlowPath(ref buffer, ref state, value);
491             }
492             else
493             {
494                 BinaryPrimitives.WriteUInt64LittleEndian(buffer.Slice(state.position), value);
495                 state.position += length;
496             }
497         }
498 
499         [MethodImpl(MethodImplOptions.NoInlining)]
WriteRawLittleEndian64SlowPath(ref Span<byte> buffer, ref WriterInternalState state, ulong value)500         public static void WriteRawLittleEndian64SlowPath(ref Span<byte> buffer, ref WriterInternalState state, ulong value)
501         {
502             WriteRawByte(ref buffer, ref state, (byte)value);
503             WriteRawByte(ref buffer, ref state, (byte)(value >> 8));
504             WriteRawByte(ref buffer, ref state, (byte)(value >> 16));
505             WriteRawByte(ref buffer, ref state, (byte)(value >> 24));
506             WriteRawByte(ref buffer, ref state, (byte)(value >> 32));
507             WriteRawByte(ref buffer, ref state, (byte)(value >> 40));
508             WriteRawByte(ref buffer, ref state, (byte)(value >> 48));
509             WriteRawByte(ref buffer, ref state, (byte)(value >> 56));
510         }
511 
WriteRawByte(ref Span<byte> buffer, ref WriterInternalState state, byte value)512         private static void WriteRawByte(ref Span<byte> buffer, ref WriterInternalState state, byte value)
513         {
514             if (state.position == buffer.Length)
515             {
516                 WriteBufferHelper.RefreshBuffer(ref buffer, ref state);
517             }
518 
519             buffer[state.position++] = value;
520         }
521 
522         /// <summary>
523         /// Writes out an array of bytes.
524         /// </summary>
WriteRawBytes(ref Span<byte> buffer, ref WriterInternalState state, byte[] value)525         public static void WriteRawBytes(ref Span<byte> buffer, ref WriterInternalState state, byte[] value)
526         {
527             WriteRawBytes(ref buffer, ref state, new ReadOnlySpan<byte>(value));
528         }
529 
530         /// <summary>
531         /// Writes out part of an array of bytes.
532         /// </summary>
WriteRawBytes(ref Span<byte> buffer, ref WriterInternalState state, byte[] value, int offset, int length)533         public static void WriteRawBytes(ref Span<byte> buffer, ref WriterInternalState state, byte[] value, int offset, int length)
534         {
535             WriteRawBytes(ref buffer, ref state, new ReadOnlySpan<byte>(value, offset, length));
536         }
537 
538         /// <summary>
539         /// Writes out part of an array of bytes.
540         /// </summary>
WriteRawBytes(ref Span<byte> buffer, ref WriterInternalState state, ReadOnlySpan<byte> value)541         public static void WriteRawBytes(ref Span<byte> buffer, ref WriterInternalState state, ReadOnlySpan<byte> value)
542         {
543             if (buffer.Length - state.position >= value.Length)
544             {
545                 // We have room in the current buffer.
546                 value.CopyTo(buffer.Slice(state.position, value.Length));
547                 state.position += value.Length;
548             }
549             else
550             {
551                 // When writing to a CodedOutputStream backed by a Stream, we could avoid
552                 // copying the data twice (first copying to the current buffer and
553                 // and later writing from the current buffer to the underlying Stream)
554                 // in some circumstances by writing the data directly to the underlying Stream.
555                 // Current this is not being done to avoid specialcasing the code for
556                 // CodedOutputStream vs IBufferWriter<byte>.
557                 int bytesWritten = 0;
558                 while (buffer.Length - state.position < value.Length - bytesWritten)
559                 {
560                     int length = buffer.Length - state.position;
561                     value.Slice(bytesWritten, length).CopyTo(buffer.Slice(state.position, length));
562                     bytesWritten += length;
563                     state.position += length;
564                     WriteBufferHelper.RefreshBuffer(ref buffer, ref state);
565                 }
566 
567                 // copy the remaining data
568                 int remainderLength = value.Length - bytesWritten;
569                 value.Slice(bytesWritten, remainderLength).CopyTo(buffer.Slice(state.position, remainderLength));
570                 state.position += remainderLength;
571             }
572         }
573         #endregion
574 
575         #region Raw tag writing
576         /// <summary>
577         /// Encodes and writes a tag.
578         /// </summary>
WriteTag(ref Span<byte> buffer, ref WriterInternalState state, int fieldNumber, WireFormat.WireType type)579         public static void WriteTag(ref Span<byte> buffer, ref WriterInternalState state, int fieldNumber, WireFormat.WireType type)
580         {
581             WriteRawVarint32(ref buffer, ref state, WireFormat.MakeTag(fieldNumber, type));
582         }
583 
584         /// <summary>
585         /// Writes an already-encoded tag.
586         /// </summary>
WriteTag(ref Span<byte> buffer, ref WriterInternalState state, uint tag)587         public static void WriteTag(ref Span<byte> buffer, ref WriterInternalState state, uint tag)
588         {
589             WriteRawVarint32(ref buffer, ref state, tag);
590         }
591 
592         /// <summary>
593         /// Writes the given single-byte tag directly to the stream.
594         /// </summary>
WriteRawTag(ref Span<byte> buffer, ref WriterInternalState state, byte b1)595         public static void WriteRawTag(ref Span<byte> buffer, ref WriterInternalState state, byte b1)
596         {
597             WriteRawByte(ref buffer, ref state, b1);
598         }
599 
600         /// <summary>
601         /// Writes the given two-byte tag directly to the stream.
602         /// </summary>
WriteRawTag(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2)603         public static void WriteRawTag(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2)
604         {
605             if (state.position + 2 > buffer.Length)
606             {
607                 WriteRawTagSlowPath(ref buffer, ref state, b1, b2);
608             }
609             else
610             {
611                 buffer[state.position++] = b1;
612                 buffer[state.position++] = b2;
613             }
614         }
615 
616         [MethodImpl(MethodImplOptions.NoInlining)]
WriteRawTagSlowPath(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2)617         private static void WriteRawTagSlowPath(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2)
618         {
619             WriteRawByte(ref buffer, ref state, b1);
620             WriteRawByte(ref buffer, ref state, b2);
621         }
622 
623         /// <summary>
624         /// Writes the given three-byte tag directly to the stream.
625         /// </summary>
WriteRawTag(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2, byte b3)626         public static void WriteRawTag(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2, byte b3)
627         {
628             if (state.position + 3 > buffer.Length)
629             {
630                 WriteRawTagSlowPath(ref buffer, ref state, b1, b2, b3);
631             }
632             else
633             {
634                 buffer[state.position++] = b1;
635                 buffer[state.position++] = b2;
636                 buffer[state.position++] = b3;
637             }
638         }
639 
640         [MethodImpl(MethodImplOptions.NoInlining)]
WriteRawTagSlowPath(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2, byte b3)641         private static void WriteRawTagSlowPath(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2, byte b3)
642         {
643             WriteRawByte(ref buffer, ref state, b1);
644             WriteRawByte(ref buffer, ref state, b2);
645             WriteRawByte(ref buffer, ref state, b3);
646         }
647 
648         /// <summary>
649         /// Writes the given four-byte tag directly to the stream.
650         /// </summary>
WriteRawTag(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2, byte b3, byte b4)651         public static void WriteRawTag(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2, byte b3, byte b4)
652         {
653             if (state.position + 4 > buffer.Length)
654             {
655                 WriteRawTagSlowPath(ref buffer, ref state, b1, b2, b3, b4);
656             }
657             else
658             {
659                 buffer[state.position++] = b1;
660                 buffer[state.position++] = b2;
661                 buffer[state.position++] = b3;
662                 buffer[state.position++] = b4;
663             }
664         }
665 
666         [MethodImpl(MethodImplOptions.NoInlining)]
667 
WriteRawTagSlowPath(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2, byte b3, byte b4)668         private static void WriteRawTagSlowPath(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2, byte b3, byte b4)
669         {
670             WriteRawByte(ref buffer, ref state, b1);
671             WriteRawByte(ref buffer, ref state, b2);
672             WriteRawByte(ref buffer, ref state, b3);
673             WriteRawByte(ref buffer, ref state, b4);
674         }
675 
676         /// <summary>
677         /// Writes the given five-byte tag directly to the stream.
678         /// </summary>
WriteRawTag(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2, byte b3, byte b4, byte b5)679         public static void WriteRawTag(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2, byte b3, byte b4, byte b5)
680         {
681             if (state.position + 5 > buffer.Length)
682             {
683                 WriteRawTagSlowPath(ref buffer, ref state, b1, b2, b3, b4, b5);
684             }
685             else
686             {
687                 buffer[state.position++] = b1;
688                 buffer[state.position++] = b2;
689                 buffer[state.position++] = b3;
690                 buffer[state.position++] = b4;
691                 buffer[state.position++] = b5;
692             }
693         }
694 
695         [MethodImpl(MethodImplOptions.NoInlining)]
WriteRawTagSlowPath(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2, byte b3, byte b4, byte b5)696         private static void WriteRawTagSlowPath(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2, byte b3, byte b4, byte b5)
697         {
698             WriteRawByte(ref buffer, ref state, b1);
699             WriteRawByte(ref buffer, ref state, b2);
700             WriteRawByte(ref buffer, ref state, b3);
701             WriteRawByte(ref buffer, ref state, b4);
702             WriteRawByte(ref buffer, ref state, b5);
703         }
704         #endregion
705 
706         /// <summary>
707         /// Encode a 32-bit value with ZigZag encoding.
708         /// </summary>
709         /// <remarks>
710         /// ZigZag encodes signed integers into values that can be efficiently
711         /// encoded with varint.  (Otherwise, negative values must be
712         /// sign-extended to 64 bits to be varint encoded, thus always taking
713         /// 10 bytes on the wire.)
714         /// </remarks>
EncodeZigZag32(int n)715         public static uint EncodeZigZag32(int n)
716         {
717             // Note:  the right-shift must be arithmetic
718             return (uint)((n << 1) ^ (n >> 31));
719         }
720 
721         /// <summary>
722         /// Encode a 64-bit value with ZigZag encoding.
723         /// </summary>
724         /// <remarks>
725         /// ZigZag encodes signed integers into values that can be efficiently
726         /// encoded with varint.  (Otherwise, negative values must be
727         /// sign-extended to 64 bits to be varint encoded, thus always taking
728         /// 10 bytes on the wire.)
729         /// </remarks>
EncodeZigZag64(long n)730         public static ulong EncodeZigZag64(long n)
731         {
732             return (ulong)((n << 1) ^ (n >> 63));
733         }
734     }
735 }
736