Преглед изворни кода

Further optimize Dtoa and string building (#1698)

* make DtoaBuilder ref struct
* simplify JsonSerializer
* tweak default stackalloc sizes
Marko Lahma пре 1 година
родитељ
комит
177bdd49f5

+ 1 - 1
Jint/Native/Array/ArrayPrototype.cs

@@ -1265,7 +1265,7 @@ namespace Jint.Native.Array
                 return s;
             }
 
-            var sb = new ValueStringBuilder(stackalloc char[128]);
+            var sb = new ValueStringBuilder(stackalloc char[256]);
             sb.Append(s);
             for (uint k = 1; k < len; k++)
             {

+ 8 - 12
Jint/Native/BigInt/BigIntPrototype.cs

@@ -124,18 +124,14 @@ internal sealed class BigIntPrototype : Prototype
             sb.Append(Digits[d]);
         }
 
-#if NET6_0_OR_GREATER
-        var charArray = sb.Length < 512 ? stackalloc char[sb.Length] : new char[sb.Length];
-        sb.AsSpan().CopyTo(charArray);
-        charArray.Reverse();
-#else
-        var charArray = new char[sb.Length];
-        sb.AsSpan().CopyTo(charArray);
-        System.Array.Reverse(charArray);
-#endif
-
-        var s = new string(charArray);
-        return negative ? '-' + s : s;
+        if (negative)
+        {
+            sb.Append('-');
+        }
+
+        sb.Reverse();
+
+        return sb.ToString();
     }
 
     /// <summary>

+ 3 - 3
Jint/Native/Json/JsonParser.cs

@@ -172,7 +172,7 @@ namespace Jint.Native.Json
 
         private Token ScanNumericLiteral(ref State state)
         {
-            var sb = new ValueStringBuilder(stackalloc char[128]);
+            var sb = new ValueStringBuilder(stackalloc char[64]);
             var start = _index;
             var ch = _source.CharCodeAt(_index);
             var canBeInteger = true;
@@ -309,7 +309,7 @@ namespace Jint.Native.Json
             int start = _index;
             ++_index;
 
-            var sb = new ValueStringBuilder(stackalloc char[128]);
+            var sb = new ValueStringBuilder(stackalloc char[64]);
             while (_index < _length)
             {
                 char ch = _source[_index++];
@@ -379,7 +379,7 @@ namespace Jint.Native.Json
                 ThrowError(_index, Messages.UnexpectedEOS);
             }
 
-            string value = sb.ToString();
+            var value = sb.ToString();
             return CreateToken(Tokens.String, value, '\"', new JsString(value), new TextRange(start, _index));
         }
 

+ 65 - 84
Jint/Native/Json/JsonSerializer.cs

@@ -5,7 +5,6 @@ using Jint.Collections;
 using Jint.Native.BigInt;
 using Jint.Native.Boolean;
 using Jint.Native.Number;
-using Jint.Native.Number.Dtoa;
 using Jint.Native.Object;
 using Jint.Native.Proxy;
 using Jint.Native.String;
@@ -53,15 +52,13 @@ namespace Jint.Native.Json
             var wrapper = _engine.Realm.Intrinsics.Object.Construct(Arguments.Empty);
             wrapper.DefineOwnProperty(JsString.Empty, new PropertyDescriptor(value, PropertyFlag.ConfigurableEnumerableWritable));
 
-            var jsonBuilder = new StringBuilder();
-
-            var target = new SerializerState(jsonBuilder);
-            if (SerializeJSONProperty(JsString.Empty, wrapper, ref target) == SerializeResult.Undefined)
+            var jsonBuilder = new ValueStringBuilder();
+            if (SerializeJSONProperty(JsString.Empty, wrapper, ref jsonBuilder) == SerializeResult.Undefined)
             {
                 return JsValue.Undefined;
             }
 
-            return new JsString(target.Json.ToString());
+            return new JsString(jsonBuilder.ToString());
         }
 
         private void SetupReplacer(JsValue replacer)
@@ -153,25 +150,25 @@ namespace Jint.Native.Json
         /// <summary>
         /// https://tc39.es/ecma262/#sec-serializejsonproperty
         /// </summary>
-        private SerializeResult SerializeJSONProperty(JsValue key, JsValue holder, ref SerializerState target)
+        private SerializeResult SerializeJSONProperty(JsValue key, JsValue holder, ref ValueStringBuilder json)
         {
             var value = ReadUnwrappedValue(key, holder);
 
             if (ReferenceEquals(value, JsValue.Null))
             {
-                target.Json.Append("null");
+                json.Append("null");
                 return SerializeResult.NotUndefined;
             }
 
             if (value.IsBoolean())
             {
-                target.Json.Append(((JsBoolean) value)._value ? "true" : "false");
+                json.Append(((JsBoolean) value)._value ? "true" : "false");
                 return SerializeResult.NotUndefined;
             }
 
             if (value.IsString())
             {
-                QuoteJSONString(value.ToString(), target.Json);
+                QuoteJSONString(value.ToString(), ref json);
                 return SerializeResult.NotUndefined;
             }
 
@@ -181,7 +178,7 @@ namespace Jint.Native.Json
 
                 if (value.IsInteger())
                 {
-                    target.Json.Append((long) doubleValue);
+                    json.Append(((long) doubleValue).ToString(CultureInfo.InvariantCulture));
                     return SerializeResult.NotUndefined;
                 }
 
@@ -190,19 +187,15 @@ namespace Jint.Native.Json
                 {
                     if (TypeConverter.CanBeStringifiedAsLong(doubleValue))
                     {
-                        target.Json.Append((long) doubleValue);
+                        json.Append(((long) doubleValue).ToString(CultureInfo.InvariantCulture));
                         return SerializeResult.NotUndefined;
                     }
 
-                    target.DtoaBuilder.Reset();
-                    var sb = new ValueStringBuilder(stackalloc char[128]);
-                    NumberPrototype.NumberToString(doubleValue, target.DtoaBuilder, ref sb);
-                    target.Json.Append(sb.ToString());
-
+                    json.Append(NumberPrototype.ToNumberString(doubleValue));
                     return SerializeResult.NotUndefined;
                 }
 
-                target.Json.Append(JsString.NullString);
+                json.Append("null");
                 return SerializeResult.NotUndefined;
             }
 
@@ -215,18 +208,18 @@ namespace Jint.Native.Json
             {
                 if (CanSerializesAsArray(objectInstance))
                 {
-                    SerializeJSONArray(objectInstance, ref target);
+                    SerializeJSONArray(objectInstance, ref json);
                     return SerializeResult.NotUndefined;
                 }
 
                 if (objectInstance is IObjectWrapper wrapper
                     && _engine.Options.Interop.SerializeToJson is { } serialize)
                 {
-                    target.Json.Append(serialize(wrapper.Target));
+                    json.Append(serialize(wrapper.Target));
                     return SerializeResult.NotUndefined;
                 }
 
-                SerializeJSONObject(objectInstance, ref target);
+                SerializeJSONObject(objectInstance, ref json);
                 return SerializeResult.NotUndefined;
             }
 
@@ -307,16 +300,16 @@ namespace Jint.Native.Json
         /// <remarks>
         /// MethodImplOptions.AggressiveOptimization = 512 which is only exposed in .NET Core.
         /// </remarks>
-        [MethodImpl(MethodImplOptions.AggressiveInlining | (MethodImplOptions)512)]
-        private static unsafe void QuoteJSONString(string value, StringBuilder target)
+        [MethodImpl(MethodImplOptions.AggressiveInlining | (MethodImplOptions) 512)]
+        private static unsafe void QuoteJSONString(string value, ref ValueStringBuilder json)
         {
             if (value.Length == 0)
             {
-                target.Append("\"\"");
+                json.Append("\"\"");
                 return;
             }
 
-            target.Append('"');
+            json.Append('"');
 
 #if NETCOREAPP1_0_OR_GREATER
             fixed (char* ptr = value)
@@ -329,7 +322,7 @@ namespace Jint.Native.Json
                     if (index < 0)
                     {
                         // append the remaining text which doesn't need any encoding.
-                        target.Append(value.AsSpan(offset));
+                        json.Append(value.AsSpan(offset));
                         break;
                     }
 
@@ -337,10 +330,10 @@ namespace Jint.Native.Json
                     if (index - offset > 0)
                     {
                         // append everything which does not need any encoding until the found index.
-                        target.Append(value.AsSpan(offset, index - offset));
+                        json.Append(value.AsSpan(offset, index - offset));
                     }
 
-                    AppendJsonStringCharacter(value, ref index, target);
+                    AppendJsonStringCharacter(value, ref index, ref json);
 
                     offset = index + 1;
                     remainingLength = value.Length - offset;
@@ -353,59 +346,59 @@ namespace Jint.Native.Json
 #else
             for (var i = 0; i < value.Length; i++)
             {
-                AppendJsonStringCharacter(value, ref i, target);
+                AppendJsonStringCharacter(value, ref i, ref json);
             }
 #endif
-            target.Append('"');
+            json.Append('"');
         }
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private static void AppendJsonStringCharacter(string value, ref int index, StringBuilder target)
+        private static void AppendJsonStringCharacter(string value, ref int index, ref ValueStringBuilder json)
         {
             var c = value[index];
             switch (c)
             {
                 case '\"':
-                    target.Append("\\\"");
+                    json.Append("\\\"");
                     break;
                 case '\\':
-                    target.Append("\\\\");
+                    json.Append("\\\\");
                     break;
                 case '\b':
-                    target.Append("\\b");
+                    json.Append("\\b");
                     break;
                 case '\f':
-                    target.Append("\\f");
+                    json.Append("\\f");
                     break;
                 case '\n':
-                    target.Append("\\n");
+                    json.Append("\\n");
                     break;
                 case '\r':
-                    target.Append("\\r");
+                    json.Append("\\r");
                     break;
                 case '\t':
-                    target.Append("\\t");
+                    json.Append("\\t");
                     break;
                 default:
                     if (char.IsSurrogatePair(value, index))
                     {
 #if NETCOREAPP1_0_OR_GREATER
-                        target.Append(value.AsSpan(index, 2));
+                        json.Append(value.AsSpan(index, 2));
                         index++;
 #else
-                        target.Append(c);
+                        json.Append(c);
                         index++;
-                        target.Append(value[index]);
+                        json.Append(value[index]);
 #endif
                     }
                     else if (c < 0x20 || char.IsSurrogate(c))
                     {
-                        target.Append("\\u");
-                        target.Append(((int) c).ToString("x4", CultureInfo.InvariantCulture));
+                        json.Append("\\u");
+                        json.Append(((int) c).ToString("x4", CultureInfo.InvariantCulture));
                     }
                     else
                     {
-                        target.Append(c);
+                        json.Append(c);
                     }
                     break;
             }
@@ -414,12 +407,12 @@ namespace Jint.Native.Json
         /// <summary>
         /// https://tc39.es/ecma262/#sec-serializejsonarray
         /// </summary>
-        private void SerializeJSONArray(ObjectInstance value, ref SerializerState target)
+        private void SerializeJSONArray(ObjectInstance value, ref ValueStringBuilder json)
         {
             var len = TypeConverter.ToUint32(value.Get(CommonProperties.Length));
             if (len == 0)
             {
-                target.Json.Append("[]");
+                json.Append("[]");
                 return;
             }
 
@@ -437,22 +430,22 @@ namespace Jint.Native.Json
             {
                 if (hasPrevious)
                 {
-                    target.Json.Append(separator);
+                    json.Append(separator);
                 }
                 else
                 {
-                    target.Json.Append('[');
+                    json.Append('[');
                 }
 
                 if (_gap.Length > 0)
                 {
-                    target.Json.Append('\n');
-                    target.Json.Append(_indent);
+                    json.Append('\n');
+                    json.Append(_indent);
                 }
 
-                if (SerializeJSONProperty(i, value, ref target) == SerializeResult.Undefined)
+                if (SerializeJSONProperty(i, value, ref json) == SerializeResult.Undefined)
                 {
-                    target.Json.Append(JsString.NullString);
+                    json.Append("null");
                 }
 
                 hasPrevious = true;
@@ -462,16 +455,16 @@ namespace Jint.Native.Json
             {
                 _stack.Exit();
                 _indent = stepback;
-                target.Json.Append("[]");
+                json.Append("[]");
                 return;
             }
 
             if (_gap.Length > 0)
             {
-                target.Json.Append('\n');
-                target.Json.Append(stepback);
+                json.Append('\n');
+                json.Append(stepback);
             }
-            target.Json.Append(']');
+            json.Append(']');
 
             _stack.Exit();
             _indent = stepback;
@@ -480,14 +473,14 @@ namespace Jint.Native.Json
         /// <summary>
         /// https://tc39.es/ecma262/#sec-serializejsonobject
         /// </summary>
-        private void SerializeJSONObject(ObjectInstance value, ref SerializerState target)
+        private void SerializeJSONObject(ObjectInstance value, ref ValueStringBuilder json)
         {
             var enumeration = _propertyList is null
                 ? PropertyEnumeration.FromObjectInstance(value)
                 : PropertyEnumeration.FromList(_propertyList);
             if (enumeration.IsEmpty)
             {
-                target.Json.Append("{}");
+                json.Append("{}");
                 return;
             }
 
@@ -503,33 +496,33 @@ namespace Jint.Native.Json
             for (var i = 0; i < enumeration.Keys.Count; i++)
             {
                 var p = enumeration.Keys[i];
-                int position = target.Json.Length;
+                int position = json.Length;
 
                 if (hasPrevious)
                 {
-                    target.Json.Append(separator);
+                    json.Append(separator);
                 }
                 else
                 {
-                    target.Json.Append('{');
+                    json.Append('{');
                 }
 
                 if (_gap.Length > 0)
                 {
-                    target.Json.Append('\n');
-                    target.Json.Append(_indent);
+                    json.Append('\n');
+                    json.Append(_indent);
                 }
 
-                QuoteJSONString(p.ToString(), target.Json);
-                target.Json.Append(':');
+                QuoteJSONString(p.ToString(), ref json);
+                json.Append(':');
                 if (_gap.Length > 0)
                 {
-                    target.Json.Append(' ');
+                    json.Append(' ');
                 }
 
-                if (SerializeJSONProperty(p, value, ref target) == SerializeResult.Undefined)
+                if (SerializeJSONProperty(p, value, ref json) == SerializeResult.Undefined)
                 {
-                    target.Json.Length = position;
+                    json.Length = position;
                 }
                 else
                 {
@@ -541,33 +534,21 @@ namespace Jint.Native.Json
             {
                 _stack.Exit();
                 _indent = stepback;
-                target.Json.Append("{}");
+                json.Append("{}");
                 return;
             }
 
             if (_gap.Length > 0)
             {
-                target.Json.Append('\n');
-                target.Json.Append(stepback);
+                json.Append('\n');
+                json.Append(stepback);
             }
-            target.Json.Append('}');
+            json.Append('}');
 
             _stack.Exit();
             _indent = stepback;
         }
 
-        private readonly ref struct SerializerState
-        {
-            public SerializerState(StringBuilder jsonBuilder)
-            {
-                Json = jsonBuilder;
-                DtoaBuilder = TypeConverter.CreateDtoaBuilderForDouble();
-            }
-
-            public readonly StringBuilder Json;
-            public readonly DtoaBuilder DtoaBuilder;
-        }
-
         private enum SerializeResult
         {
             NotUndefined,

+ 8 - 8
Jint/Native/Number/Dtoa/BignumDtoa.cs

@@ -11,7 +11,7 @@ namespace Jint.Native.Number.Dtoa
             double v,
             DtoaMode mode,
             int requested_digits,
-            DtoaBuilder builder,
+            ref  DtoaBuilder builder,
             out int decimal_point)
         {
             var bits = (ulong) BitConverter.DoubleToInt64Bits(v);
@@ -73,7 +73,7 @@ namespace Jint.Native.Number.Dtoa
                         delta_minus,
                         delta_plus,
                         is_even,
-                        builder);
+                        ref builder);
                     break;
                 case DtoaMode.Fixed:
                     BignumToFixed(
@@ -81,7 +81,7 @@ namespace Jint.Native.Number.Dtoa
                         ref decimal_point,
                         numerator,
                         denominator,
-                        builder);
+                        ref builder);
                     break;
                 case DtoaMode.Precision:
                     GenerateCountedDigits(
@@ -89,7 +89,7 @@ namespace Jint.Native.Number.Dtoa
                         ref decimal_point,
                         numerator,
                         denominator,
-                        builder);
+                        ref builder);
                     break;
                 default:
                     ExceptionHelper.ThrowArgumentOutOfRangeException();
@@ -117,7 +117,7 @@ namespace Jint.Native.Number.Dtoa
             Bignum delta_minus,
             Bignum delta_plus,
             bool is_even,
-            DtoaBuilder buffer)
+            ref  DtoaBuilder buffer)
         {
             // Small optimization: if delta_minus and delta_plus are the same just reuse
             // one of the two bignums.
@@ -239,7 +239,7 @@ namespace Jint.Native.Number.Dtoa
             ref int decimal_point,
             Bignum numerator,
             Bignum denominator,
-            DtoaBuilder buffer)
+            ref  DtoaBuilder buffer)
         {
             Debug.Assert(count >= 0);
             for (int i = 0; i < count - 1; ++i)
@@ -286,7 +286,7 @@ namespace Jint.Native.Number.Dtoa
             ref int decimal_point,
             Bignum numerator,
             Bignum denominator,
-            DtoaBuilder buffer)
+            ref  DtoaBuilder buffer)
         {
             // Note that we have to look at more than just the requested_digits, since
             // a number could be rounded up. Example: v=0.5 with requested_digits=0.
@@ -329,7 +329,7 @@ namespace Jint.Native.Number.Dtoa
                 // The requested digits correspond to the digits after the point.
                 // The variable 'needed_digits' includes the digits before the point.
                 int needed_digits = (decimal_point) + requested_digits;
-                GenerateCountedDigits(needed_digits, ref decimal_point, numerator, denominator, buffer);
+                GenerateCountedDigits(needed_digits, ref decimal_point, numerator, denominator, ref buffer);
             }
         }
 

+ 39 - 41
Jint/Native/Number/Dtoa/DtoaBuilder.cs

@@ -1,55 +1,53 @@
-#nullable disable
-
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-namespace Jint.Native.Number.Dtoa
-{
-    internal sealed class DtoaBuilder
-    {
-        // allocate buffer for generated digits + extra notation + padding zeroes
-        internal readonly char[] _chars;
-        internal int Length;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
 
-        public DtoaBuilder(int size)
-        {
-            _chars = new char[size];
-        }
+namespace Jint.Native.Number.Dtoa;
 
-        public DtoaBuilder() : this(FastDtoa.KFastDtoaMaximalLength + 8)
-        {
-        }
+[StructLayout(LayoutKind.Auto)]
+internal ref struct DtoaBuilder
+{
+    // allocate buffer for generated digits + extra notation + padding zeroes
+    internal readonly Span<char> _chars;
+    internal int Length;
 
-        internal void Append(char c)
-        {
-            _chars[Length++] = c;
-        }
+    public DtoaBuilder(Span<char> initialBuffer)
+    {
+        _chars = initialBuffer;
+    }
 
-        internal void DecreaseLast()
-        {
-            _chars[Length - 1]--;
-        }
+    internal void Append(char c)
+    {
+        _chars[Length++] = c;
+    }
 
-        public void Reset()
-        {
-            Length = 0;
-            System.Array.Clear(_chars, 0, _chars.Length);
-        }
+    internal void DecreaseLast()
+    {
+        _chars[Length - 1]--;
+    }
 
-        public char this[int i]
-        {
-            get => _chars[i];
-            set
-            {
-                _chars[i] = value;
-                Length = System.Math.Max(Length, i + 1);
-            }
-        }
+    public void Reset()
+    {
+        Length = 0;
+        _chars.Clear();
+    }
 
-        public override string ToString()
+    public char this[int i]
+    {
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        get => _chars[i];
+        set
         {
-            return "[chars:" + new string(_chars, 0, Length) + "]";
+            _chars[i] = value;
+            Length = System.Math.Max(Length, i + 1);
         }
     }
-}
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public ReadOnlySpan<char> Slice(int start, int length) => _chars.Slice(start, length);
+
+    public override string ToString() => "[chars:" + _chars.Slice(0, Length).ToString() + "]";
+}

+ 5 - 5
Jint/Native/Number/Dtoa/DtoaNumberFormatter.cs

@@ -8,7 +8,7 @@ namespace Jint.Native.Number.Dtoa
     internal static class DtoaNumberFormatter
     {
         public static void DoubleToAscii(
-            DtoaBuilder buffer,
+            ref  DtoaBuilder buffer,
             double v,
             DtoaMode mode,
             int requested_digits,
@@ -44,14 +44,14 @@ namespace Jint.Native.Number.Dtoa
             bool fast_worked = false;
             switch (mode) {
                 case DtoaMode.Shortest:
-                    fast_worked = FastDtoa.NumberToString(v, DtoaMode.Shortest, 0, out point, buffer);
+                    fast_worked = FastDtoa.NumberToString(v, DtoaMode.Shortest, 0, out point, ref buffer);
                     break;
                 case DtoaMode.Fixed:
                     //fast_worked = FastFixedDtoa(v, requested_digits, buffer, length, point);
                     ExceptionHelper.ThrowNotImplementedException();
                     break;
                 case DtoaMode.Precision:
-                    fast_worked = FastDtoa.NumberToString(v, DtoaMode.Precision, requested_digits, out point, buffer);
+                    fast_worked = FastDtoa.NumberToString(v, DtoaMode.Precision, requested_digits, out point, ref buffer);
                     break;
                 default:
                     ExceptionHelper.ThrowArgumentOutOfRangeException();
@@ -65,7 +65,7 @@ namespace Jint.Native.Number.Dtoa
 
             // If the fast dtoa didn't succeed use the slower bignum version.
             buffer.Reset();
-            BignumDtoa.NumberToString(v, mode, requested_digits, buffer, out point);
+            BignumDtoa.NumberToString(v, mode, requested_digits, ref buffer, out point);
        }
     }
-}
+}

+ 15 - 15
Jint/Native/Number/Dtoa/FastDtoa.cs

@@ -64,7 +64,7 @@ namespace Jint.Native.Number.Dtoa
         //    representable number to the input.
         //  Modifies the generated digits in the buffer to approach (round towards) w.
         private static bool RoundWeed(
-            DtoaBuilder buffer,
+            ref  DtoaBuilder buffer,
             ulong distanceTooHighW,
             ulong unsafeInterval,
             ulong rest,
@@ -183,7 +183,7 @@ namespace Jint.Native.Number.Dtoa
         //
         // Precondition: rest < ten_kappa.
         static bool RoundWeedCounted(
-            DtoaBuilder buffer,
+            ref  DtoaBuilder buffer,
             ulong rest,
             ulong ten_kappa,
             ulong unit,
@@ -413,7 +413,7 @@ namespace Jint.Native.Number.Dtoa
             in DiyFp low,
             in DiyFp w,
             in DiyFp high,
-            DtoaBuilder buffer,
+            ref  DtoaBuilder buffer,
             int mk,
             out int kappa)
         {
@@ -473,7 +473,7 @@ namespace Jint.Native.Number.Dtoa
                     // Rounding down (by not emitting the remaining digits) yields a number
                     // that lies within the unsafe interval.
                     return RoundWeed(
-                        buffer,
+                        ref buffer,
                         DiyFp.Minus(tooHigh, w).F,
                         unsafeInterval.F,
                         rest,
@@ -511,7 +511,7 @@ namespace Jint.Native.Number.Dtoa
                 if (fractionals < unsafeInterval.F)
                 {
                     return RoundWeed(
-                        buffer,
+                        ref buffer,
                         DiyFp.Minus(tooHigh, w).F*unit,
                         unsafeInterval.F,
                         fractionals,
@@ -552,7 +552,7 @@ namespace Jint.Native.Number.Dtoa
         static bool DigitGenCounted(
             in DiyFp w,
             int requested_digits,
-            DtoaBuilder buffer,
+            ref  DtoaBuilder buffer,
             out int kappa)
         {
             Debug.Assert(MinimalTargetExponent <= w.E && w.E <= MaximalTargetExponent);
@@ -592,7 +592,7 @@ namespace Jint.Native.Number.Dtoa
             if (requested_digits == 0)
             {
                 ulong rest = (((ulong) integrals) << -one.E) + fractionals;
-                return RoundWeedCounted(buffer, rest,(ulong) divisor << -one.E, w_error, ref kappa);
+                return RoundWeedCounted(ref buffer, rest,(ulong) divisor << -one.E, w_error, ref kappa);
             }
 
           // The integrals have been generated. We are at the point of the decimal
@@ -615,7 +615,7 @@ namespace Jint.Native.Number.Dtoa
             (kappa)--;
           }
           if (requested_digits != 0) return false;
-          return RoundWeedCounted(buffer, fractionals, one.F, w_error, ref kappa);
+          return RoundWeedCounted(ref buffer, fractionals, one.F, w_error, ref kappa);
         }
 
         // Provides a decimal representation of v.
@@ -629,7 +629,7 @@ namespace Jint.Native.Number.Dtoa
         // The last digit will be closest to the actual v. That is, even if several
         // digits might correctly yield 'v' when read again, the closest will be
         // computed.
-        private static bool Grisu3(double v, DtoaBuilder buffer, out int decimal_exponent)
+        private static bool Grisu3(double v, ref  DtoaBuilder buffer, out int decimal_exponent)
         {
             ulong bits = (ulong) BitConverter.DoubleToInt64Bits(v);
             DiyFp w = DoubleHelper.AsNormalizedDiyFp(bits);
@@ -681,7 +681,7 @@ namespace Jint.Native.Number.Dtoa
             // the buffer will be filled with "123" und the decimal_exponent will be
             // decreased by 2.
             int kappa;
-            var digitGen = DigitGen(scaledBoundaryMinus, scaledW, scaledBoundaryPlus, buffer, mk, out kappa);
+            var digitGen = DigitGen(scaledBoundaryMinus, scaledW, scaledBoundaryPlus, ref buffer, mk, out kappa);
             decimal_exponent = -mk + kappa;
             return digitGen;
         }
@@ -695,7 +695,7 @@ namespace Jint.Native.Number.Dtoa
         static bool Grisu3Counted(
             double v,
             int requested_digits,
-            DtoaBuilder buffer,
+            ref  DtoaBuilder buffer,
             out int decimal_exponent)
         {
             ulong bits = (ulong) BitConverter.DoubleToInt64Bits(v);
@@ -725,7 +725,7 @@ namespace Jint.Native.Number.Dtoa
             // return together with a kappa such that scaled_w ~= buffer * 10^kappa. (It
             // will not always be exactly the same since DigitGenCounted only produces a
             // limited number of digits.)
-            bool result = DigitGenCounted(scaled_w, requested_digits, buffer, out var kappa);
+            bool result = DigitGenCounted(scaled_w, requested_digits, ref buffer, out var kappa);
             decimal_exponent = -mk + kappa;
             return result;
         }
@@ -735,7 +735,7 @@ namespace Jint.Native.Number.Dtoa
             DtoaMode mode,
             int requested_digits,
             out int decimal_point,
-            DtoaBuilder buffer)
+            ref  DtoaBuilder buffer)
         {
             Debug.Assert(v > 0);
             Debug.Assert(!double.IsNaN(v));
@@ -746,10 +746,10 @@ namespace Jint.Native.Number.Dtoa
             switch (mode)
             {
                 case DtoaMode.Shortest:
-                    result = Grisu3(v, buffer, out decimal_exponent);
+                    result = Grisu3(v, ref buffer, out decimal_exponent);
                     break;
                 case DtoaMode.Precision:
-                    result = Grisu3Counted(v, requested_digits, buffer, out decimal_exponent);
+                    result = Grisu3Counted(v, requested_digits, ref buffer, out decimal_exponent);
                     break;
                 default:
                     ExceptionHelper.ThrowArgumentOutOfRangeException();

+ 38 - 52
Jint/Native/Number/NumberPrototype.cs

@@ -15,6 +15,9 @@ namespace Jint.Native.Number
     /// </summary>
     internal sealed class NumberPrototype : NumberInstance
     {
+        private const int SmallDtoaLength = FastDtoa.KFastDtoaMaximalLength + 8;
+        private const int LargeDtoaLength = 101;
+
         private readonly Realm _realm;
         private readonly NumberConstructor _constructor;
 
@@ -200,12 +203,12 @@ namespace Jint.Native.Number
             }
 
             int decimalPoint;
-            DtoaBuilder dtoaBuilder;
+            var dtoaBuilder = new DtoaBuilder(stackalloc char[f == -1 ? SmallDtoaLength : LargeDtoaLength]);
+
             if (f == -1)
             {
-                dtoaBuilder = new DtoaBuilder();
                 DtoaNumberFormatter.DoubleToAscii(
-                    dtoaBuilder,
+                    ref dtoaBuilder,
                     x,
                     DtoaMode.Shortest,
                     requested_digits: 0,
@@ -215,9 +218,8 @@ namespace Jint.Native.Number
             }
             else
             {
-                dtoaBuilder = new DtoaBuilder(101);
                 DtoaNumberFormatter.DoubleToAscii(
-                    dtoaBuilder,
+                    ref dtoaBuilder,
                     x,
                     DtoaMode.Precision,
                     requested_digits: f + 1,
@@ -229,7 +231,7 @@ namespace Jint.Native.Number
             Debug.Assert(dtoaBuilder.Length <= f + 1);
 
             int exponent = decimalPoint - 1;
-            var result = CreateExponentialRepresentation(dtoaBuilder, exponent, negative, f+1);
+            var result = CreateExponentialRepresentation(ref dtoaBuilder, exponent, negative, f+1);
             return result;
         }
 
@@ -265,9 +267,9 @@ namespace Jint.Native.Number
                 ExceptionHelper.ThrowRangeError(_realm, "precision must be between 1 and 100");
             }
 
-            var dtoaBuilder = new DtoaBuilder(101);
+            var dtoaBuilder = new DtoaBuilder(stackalloc char[LargeDtoaLength]);
             DtoaNumberFormatter.DoubleToAscii(
-                dtoaBuilder,
+                ref dtoaBuilder,
                 x,
                 DtoaMode.Precision,
                 p,
@@ -278,10 +280,10 @@ namespace Jint.Native.Number
             int exponent = decimalPoint - 1;
             if (exponent < -6 || exponent >= p)
             {
-                return CreateExponentialRepresentation(dtoaBuilder, exponent, negative, p);
+                return CreateExponentialRepresentation(ref dtoaBuilder, exponent, negative, p);
             }
 
-            var sb = new ValueStringBuilder(stackalloc char[64]);
+            var sb = new ValueStringBuilder(stackalloc char[128]);
 
             // Use fixed notation.
             if (negative)
@@ -293,13 +295,13 @@ namespace Jint.Native.Number
             {
                 sb.Append("0.");
                 sb.Append('0', -decimalPoint);
-                sb.Append(dtoaBuilder._chars.AsSpan(0, dtoaBuilder.Length));
+                sb.Append(dtoaBuilder._chars.Slice(0, dtoaBuilder.Length));
                 sb.Append('0', p - dtoaBuilder.Length);
             }
             else
             {
                 int m = System.Math.Min(dtoaBuilder.Length, decimalPoint);
-                sb.Append(dtoaBuilder._chars.AsSpan(0, m));
+                sb.Append(dtoaBuilder._chars.Slice(0, m));
                 sb.Append('0', System.Math.Max(0, decimalPoint - dtoaBuilder.Length));
                 if (decimalPoint < p)
                 {
@@ -309,7 +311,7 @@ namespace Jint.Native.Number
                     {
                         int len = dtoaBuilder.Length - decimalPoint;
                         int n = System.Math.Min(len, p - (sb.Length - extra));
-                        sb.Append(dtoaBuilder._chars.AsSpan(decimalPoint, n));
+                        sb.Append(dtoaBuilder._chars.Slice(decimalPoint, n));
                     }
 
                     sb.Append('0', System.Math.Max(0, extra + (p - sb.Length)));
@@ -320,7 +322,7 @@ namespace Jint.Native.Number
         }
 
         private static string CreateExponentialRepresentation(
-            DtoaBuilder buffer,
+            ref  DtoaBuilder buffer,
             int exponent,
             bool negative,
             int significantDigits)
@@ -332,16 +334,16 @@ namespace Jint.Native.Number
                 exponent = -exponent;
             }
 
-            var sb = new ValueStringBuilder(stackalloc char[64]);
+            var sb = new ValueStringBuilder(stackalloc char[128]);
             if (negative)
             {
                 sb.Append('-');
             }
-            sb.Append(buffer._chars[0]);
+            sb.Append(buffer[0]);
             if (significantDigits != 1)
             {
                 sb.Append('.');
-                sb.Append(buffer._chars.AsSpan(1, buffer.Length - 1));
+                sb.Append(buffer.Slice(1, buffer.Length - 1));
                 int length = buffer.Length;
                 sb.Append('0', significantDigits - length);
             }
@@ -349,6 +351,7 @@ namespace Jint.Native.Number
             sb.Append('e');
             sb.Append(negativeExponent ? '-' : '+');
             sb.Append(exponent.ToString(CultureInfo.InvariantCulture));
+
             return sb.ToString();
         }
 
@@ -422,18 +425,8 @@ namespace Jint.Native.Number
                 n /= radix;
                 sb.Append(Digits[digit]);
             }
-
-#if NET6_0_OR_GREATER
-            var charArray = sb.Length < 512 ? stackalloc char[sb.Length] : new char[sb.Length];
-            sb.AsSpan().CopyTo(charArray);
-            charArray.Reverse();
-#else
-            var charArray = new char[sb.Length];
-            sb.AsSpan().CopyTo(charArray);
-            System.Array.Reverse(charArray);
-#endif
-
-            return new string(charArray);
+            sb.Reverse();
+            return sb.ToString();
         }
 
         internal static string ToFractionBase(double n, int radix)
@@ -460,44 +453,35 @@ namespace Jint.Native.Number
             return result.ToString();
         }
 
-        private static string ToNumberString(double m)
-        {
-            var stringBuilder = new ValueStringBuilder(stackalloc char[128]);
-            NumberToString(m, new DtoaBuilder(), ref stringBuilder);
-            return stringBuilder.ToString();
-        }
-
-        internal static void NumberToString(
-            double m,
-            DtoaBuilder builder,
-            ref ValueStringBuilder stringBuilder)
+        internal static string ToNumberString(double m)
         {
             if (double.IsNaN(m))
             {
-                stringBuilder.Append("NaN");
-                return;
+                return "NaN";
             }
 
             if (m == 0)
             {
-                stringBuilder.Append('0');
-                return;
+                return "0";
             }
 
             if (double.IsInfinity(m))
             {
-                stringBuilder.Append(double.IsNegativeInfinity(m) ? "-Infinity" : "Infinity");
-                return;
+                return double.IsNegativeInfinity(m) ? "-Infinity" : "Infinity";
             }
 
+            var builder = new DtoaBuilder(stackalloc char[SmallDtoaLength]);
+
             DtoaNumberFormatter.DoubleToAscii(
-                builder,
+                ref builder,
                 m,
                 DtoaMode.Shortest,
                 0,
                 out var negative,
                 out var decimal_point);
 
+
+            var stringBuilder = new ValueStringBuilder(stackalloc char[64]);
             if (negative)
             {
                 stringBuilder.Append('-');
@@ -506,22 +490,22 @@ namespace Jint.Native.Number
             if (builder.Length <= decimal_point && decimal_point <= 21)
             {
                 // ECMA-262 section 9.8.1 step 6.
-                stringBuilder.Append(builder._chars.AsSpan(0, builder.Length));
+                stringBuilder.Append(builder._chars.Slice(0, builder.Length));
                 stringBuilder.Append('0', decimal_point - builder.Length);
             }
             else if (0 < decimal_point && decimal_point <= 21)
             {
                 // ECMA-262 section 9.8.1 step 7.
-                stringBuilder.Append(builder._chars.AsSpan(0, decimal_point));
+                stringBuilder.Append(builder._chars.Slice(0, decimal_point));
                 stringBuilder.Append('.');
-                stringBuilder.Append(builder._chars.AsSpan(decimal_point, builder.Length - decimal_point));
+                stringBuilder.Append(builder._chars.Slice(decimal_point, builder.Length - decimal_point));
             }
             else if (decimal_point <= 0 && decimal_point > -6)
             {
                 // ECMA-262 section 9.8.1 step 8.
                 stringBuilder.Append("0.");
                 stringBuilder.Append('0', -decimal_point);
-                stringBuilder.Append(builder._chars.AsSpan(0, builder.Length));
+                stringBuilder.Append(builder._chars.Slice(0, builder.Length));
             }
             else
             {
@@ -530,7 +514,7 @@ namespace Jint.Native.Number
                 if (builder.Length != 1)
                 {
                     stringBuilder.Append('.');
-                    stringBuilder.Append(builder._chars.AsSpan(1, builder.Length - 1));
+                    stringBuilder.Append(builder._chars.Slice(1, builder.Length - 1));
                 }
 
                 stringBuilder.Append('e');
@@ -543,6 +527,8 @@ namespace Jint.Native.Number
 
                 stringBuilder.Append(exponent.ToString(CultureInfo.InvariantCulture));
             }
+
+            return stringBuilder.ToString();
         }
     }
 }

+ 1 - 1
Jint/Native/RegExp/RegExpPrototype.cs

@@ -337,7 +337,7 @@ namespace Jint.Native.RegExp
             // $`	Inserts the portion of the string that precedes the matched substring.
             // $'	Inserts the portion of the string that follows the matched substring.
             // $n or $nn	Where n or nn are decimal digits, inserts the nth parenthesized submatch string, provided the first argument was a RegExp object.
-            var sb = new ValueStringBuilder();
+            var sb = new ValueStringBuilder(stackalloc char[128]);
             for (var i = 0; i < replacement.Length; i++)
             {
                 char c = replacement[i];

+ 1 - 1
Jint/Native/String/StringConstructor.cs

@@ -82,7 +82,7 @@ namespace Jint.Native.String
         private JsValue FromCodePoint(JsValue thisObject, JsValue[] arguments)
         {
             JsNumber codePoint;
-            var result = new ValueStringBuilder(stackalloc char[10]);
+            var result = new ValueStringBuilder(stackalloc char[128]);
             foreach (var a in arguments)
             {
                 int point;

+ 5 - 0
Jint/Pooling/ValueStringBuilder.cs

@@ -114,6 +114,11 @@ internal ref struct ValueStringBuilder
     public ReadOnlySpan<char> AsSpan(int start) => _chars.Slice(start, _pos - start);
     public ReadOnlySpan<char> AsSpan(int start, int length) => _chars.Slice(start, length);
 
+    public void Reverse()
+    {
+        _chars.Slice(0, _pos).Reverse();
+    }
+
     public bool TryCopyTo(Span<char> destination, out int charsWritten)
     {
         if (_chars.Slice(0, _pos).TryCopyTo(destination))

+ 5 - 1
Jint/Runtime/CallStack/JintCallStack.cs

@@ -181,7 +181,11 @@ namespace Jint.Runtime.CallStack
                 index--;
             }
 
-            return builder.ToString().TrimEnd();
+            var result = builder.AsSpan().TrimEnd().ToString();
+
+            builder.Dispose();
+
+            return result;
         }
 
         /// <summary>

+ 1 - 14
Jint/Runtime/TypeConverter.cs

@@ -896,20 +896,7 @@ namespace Jint.Runtime
                 return ToString((long) d);
             }
 
-            var stringBuilder = new ValueStringBuilder(stackalloc char[128]);
-            // we can create smaller array as we know the format to be short
-            NumberPrototype.NumberToString(d, CreateDtoaBuilderForDouble(), ref stringBuilder);
-            return stringBuilder.ToString();
-        }
-
-        /// <summary>
-        /// Creates a new <see cref="DtoaBuilder"/> with the default buffer
-        /// size for <see cref="ToString(double)"/>
-        /// </summary>
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        internal static DtoaBuilder CreateDtoaBuilderForDouble()
-        {
-            return new DtoaBuilder(17);
+            return NumberPrototype.ToNumberString(d);
         }
 
         /// <summary>