@@ -12,6 +12,7 @@ namespace Thinktecture.Internal;
1212/// any release. You should only use it directly in your code with extreme caution and knowing that
1313/// doing so can result in application failures when updating to a new Thinktecture.Runtime.Extensions release.
1414/// </summary>
15+ [ SkipLocalsInit ]
1516public static class Utf8JsonReaderHelper
1617{
1718 // Aligned with System.Text.Json's JsonConstants.StackallocCharThreshold (= StackallocByteThreshold / 2 = 128)
@@ -32,34 +33,39 @@ public static class Utf8JsonReaderHelper
3233 where T : IObjectFactory < T , ReadOnlySpan < char > , TValidationError >
3334 where TValidationError : class , IValidationError < TValidationError >
3435 {
35- // Fast path: contiguous, unescaped value (most common case)
36- if ( ! reader . HasValueSequence && ! reader . ValueIsEscaped )
36+ // Escaped values (rarest case): CopyString handles unescaping and reassembly
37+ if ( reader . ValueIsEscaped )
38+ return ValidateEscaped < T , TValidationError > ( ref reader , provider , out result ) ;
39+
40+ // Fragmented but not escaped: assemble bytes, then transcode
41+ if ( reader . HasValueSequence )
42+ return ValidateFragmentedUnescaped < T , TValidationError > ( ref reader , provider , out result ) ;
43+
44+ // Fast path: contiguous, unescaped, short value (most common case)
45+ var utf8Bytes = reader . ValueSpan ;
46+
47+ if ( utf8Bytes . Length <= _STACKALLOC_CHAR_THRESHOLD )
3748 {
38- return ValidateFastPath < T , TValidationError > ( reader . ValueSpan , provider , out result ) ;
49+ // Constant size enables JIT to emit a simple stack bump instead of localloc
50+ Span < char > charBuf = stackalloc char [ _STACKALLOC_CHAR_THRESHOLD ] ;
51+ var charsWritten = Encoding . UTF8 . GetChars ( utf8Bytes , charBuf ) ;
52+ return T . Validate ( charBuf [ ..charsWritten ] , provider , out result ) ;
3953 }
4054
41- // Slow path: escaped or fragmented value
42- return ValidateSlowPath < T , TValidationError > ( ref reader , provider , out result ) ;
55+ // Large contiguous unescaped value
56+ return ValidateLargeContiguousValue < T , TValidationError > ( utf8Bytes , provider , out result ) ;
4357 }
4458
45- [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
46- private static TValidationError ? ValidateFastPath < T , TValidationError > (
59+ // NoInlining: keeps the try/finally and ArrayPool machinery out of ValidateFromUtf8's inlined body,
60+ // ensuring the JIT emits compact native code for the hot stackalloc path.
61+ [ MethodImpl ( MethodImplOptions . NoInlining ) ]
62+ private static TValidationError ? ValidateLargeContiguousValue < T , TValidationError > (
4763 ReadOnlySpan < byte > utf8Bytes ,
4864 IFormatProvider ? provider ,
4965 out T ? result )
5066 where T : IObjectFactory < T , ReadOnlySpan < char > , TValidationError >
5167 where TValidationError : class , IValidationError < TValidationError >
5268 {
53- // UTF-16 char count is always <= UTF-8 byte count, so this comparison is safe
54- if ( utf8Bytes . Length <= _STACKALLOC_CHAR_THRESHOLD )
55- {
56- // Constant size enables JIT to emit a simple stack bump instead of localloc
57- Span < char > charBuf = stackalloc char [ _STACKALLOC_CHAR_THRESHOLD ] ;
58- var charsWritten = Encoding . UTF8 . GetChars ( utf8Bytes , charBuf ) ;
59- return T . Validate ( charBuf [ ..charsWritten ] , provider , out result ) ;
60- }
61-
62- // Large values: use array pool
6369 var rentedChars = ArrayPool < char > . Shared . Rent ( utf8Bytes . Length ) ;
6470
6571 try
@@ -73,19 +79,66 @@ public static class Utf8JsonReaderHelper
7379 }
7480 }
7581
76- private static TValidationError ? ValidateSlowPath < T , TValidationError > (
82+ // NoInlining: fragmented values are rare; isolating this keeps ValidateFromUtf8's
83+ // inlined body compact and avoids polluting the caller with ArrayPool<byte> machinery.
84+ [ MethodImpl ( MethodImplOptions . NoInlining ) ]
85+ private static TValidationError ? ValidateFragmentedUnescaped < T , TValidationError > (
86+ ref Utf8JsonReader reader ,
87+ IFormatProvider ? provider ,
88+ out T ? result )
89+ where T : IObjectFactory < T , ReadOnlySpan < char > , TValidationError >
90+ where TValidationError : class , IValidationError < TValidationError >
91+ {
92+ var sequence = reader . ValueSequence ;
93+ var byteLength = checked ( ( int ) sequence . Length ) ;
94+
95+ var rentedBytes = ArrayPool < byte > . Shared . Rent ( byteLength ) ;
96+
97+ try
98+ {
99+ sequence . CopyTo ( rentedBytes ) ;
100+ var utf8Bytes = rentedBytes . AsSpan ( 0 , byteLength ) ;
101+
102+ // Now we have contiguous bytes — same transcoding logic as the contiguous unescaped path
103+ if ( byteLength <= _STACKALLOC_CHAR_THRESHOLD )
104+ {
105+ Span < char > charBuf = stackalloc char [ _STACKALLOC_CHAR_THRESHOLD ] ;
106+ var charsWritten = Encoding . UTF8 . GetChars ( utf8Bytes , charBuf ) ;
107+ return T . Validate ( charBuf [ ..charsWritten ] , provider , out result ) ;
108+ }
109+
110+ var rentedChars = ArrayPool < char > . Shared . Rent ( byteLength ) ;
111+
112+ try
113+ {
114+ var charsWritten = Encoding . UTF8 . GetChars ( utf8Bytes , rentedChars ) ;
115+ return T . Validate ( rentedChars . AsSpan ( 0 , charsWritten ) , provider , out result ) ;
116+ }
117+ finally
118+ {
119+ ArrayPool < char > . Shared . Return ( rentedChars ) ;
120+ }
121+ }
122+ finally
123+ {
124+ ArrayPool < byte > . Shared . Return ( rentedBytes ) ;
125+ }
126+ }
127+
128+ // NoInlining: escaped values are the rarest case; isolating this keeps the dispatch compact.
129+ [ MethodImpl ( MethodImplOptions . NoInlining ) ]
130+ private static TValidationError ? ValidateEscaped < T , TValidationError > (
77131 ref Utf8JsonReader reader ,
78132 IFormatProvider ? provider ,
79133 out T ? result )
80134 where T : IObjectFactory < T , ReadOnlySpan < char > , TValidationError >
81135 where TValidationError : class , IValidationError < TValidationError >
82136 {
83- // Get the byte length for buffer sizing
137+ // CopyString handles both unescaping and reassembly of fragmented sequences
84138 var byteLength = reader . HasValueSequence
85139 ? checked ( ( int ) reader . ValueSequence . Length )
86140 : reader . ValueSpan . Length ;
87141
88- // Use CopyString for escaped/fragmented values
89142 if ( byteLength <= _STACKALLOC_CHAR_THRESHOLD )
90143 {
91144 Span < char > charBuf = stackalloc char [ _STACKALLOC_CHAR_THRESHOLD ] ;
0 commit comments