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