From 73fed692a4d620dcd284c04f303877a98c137bcb Mon Sep 17 00:00:00 2001 From: Dubzer Date: Fri, 27 Sep 2024 00:36:05 +0300 Subject: [PATCH] Reduce heap allocations when parsing a host --- src/Dubzer.WhatwgUrl/HostParser.cs | 2 +- src/Dubzer.WhatwgUrl/Ipv6Parser.cs | 2 ++ src/Dubzer.WhatwgUrl/Uts46/Idna.cs | 45 ++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/Dubzer.WhatwgUrl/HostParser.cs b/src/Dubzer.WhatwgUrl/HostParser.cs index 39ff8d5..130c721 100644 --- a/src/Dubzer.WhatwgUrl/HostParser.cs +++ b/src/Dubzer.WhatwgUrl/HostParser.cs @@ -70,7 +70,7 @@ public static Result Parse(string input, bool isOpaque) // only when serializing the host. var ipv6Result = Ipv6Parser.Parse(input[1..^1]); return ipv6Result - ? Result.Success($"[{ipv6Result.Value}]") + ? Result.Success(ipv6Result.Value!) : ipv6Result; } diff --git a/src/Dubzer.WhatwgUrl/Ipv6Parser.cs b/src/Dubzer.WhatwgUrl/Ipv6Parser.cs index cbef955..b86ca6b 100644 --- a/src/Dubzer.WhatwgUrl/Ipv6Parser.cs +++ b/src/Dubzer.WhatwgUrl/Ipv6Parser.cs @@ -169,6 +169,7 @@ internal static Result Parse(string input) private static string SerializeIpv6(ReadOnlySpan address) { var sb = new StringBuilder(); + sb.Append('['); // 2. Let compress be an index // to the first IPv6 piece in the first longest sequences of address’s IPv6 pieces that are 0. // 3. If there is no sequence of address’s IPv6 pieces that are 0 that is longer than 1, @@ -202,6 +203,7 @@ private static string SerializeIpv6(ReadOnlySpan address) sb.Append(':'); } + sb.Append(']'); return sb.ToString(); } diff --git a/src/Dubzer.WhatwgUrl/Uts46/Idna.cs b/src/Dubzer.WhatwgUrl/Uts46/Idna.cs index 93353a1..10186da 100644 --- a/src/Dubzer.WhatwgUrl/Uts46/Idna.cs +++ b/src/Dubzer.WhatwgUrl/Uts46/Idna.cs @@ -100,6 +100,51 @@ private static MappingTableRow FindMapping(uint val) private static string Map(string input) { + // 13 is the max mapping length. Presumably that's the worst case scenario + if (input.Length < Consts.MaxLengthOnStack.Char / 13) + { + Span chars = stackalloc char[Consts.MaxLengthOnStack.Char]; + + var nextCharI = 0; + foreach (var rune in input.EnumerateRunes()) + { + var mapping = FindMapping((uint)rune.Value); + switch (mapping.Status) + { + case IdnaStatus.Deviation: + case IdnaStatus.Valid: + case IdnaStatus.DisallowedSTD3Valid: + case IdnaStatus.Disallowed: + var codepoint = rune.Value; + + // Inlined Rune.IsBmp + if (codepoint <= ushort.MaxValue) + { + chars[nextCharI] = (char) codepoint; + nextCharI++; + } + else + { + // Inlined Rune.EncodeToUtf16 => UnicodeUtility.GetUtf16SurrogatesFromSupplementaryPlaneScalar + chars[nextCharI] = (char) (codepoint + 56557568U >> 10); + chars[nextCharI + 1] = (char) ((codepoint & 1023) + 56320); + nextCharI += 2; + } + + break; + case IdnaStatus.DisallowedSTD3Mapped: + case IdnaStatus.Mapped: + mapping.Mapping.CopyTo(chars[nextCharI..]); + nextCharI += mapping.Mapping.Length; + break; + case IdnaStatus.Ignored: + break; + } + } + + return new string(chars[..nextCharI]); + } + var result = new StringBuilder(input.Length); foreach (var rune in input.EnumerateRunes()) {