From 32fed2e24ee15f4b04c45c4b5b3bf3fd22a1e21b Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Wed, 17 Jul 2024 15:07:06 -0700 Subject: [PATCH 01/16] Add StringExtensions.CreateString(...) helper --- .../StringExtensions.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/StringExtensions.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/StringExtensions.cs index 9dde4e4b539..5741c8ea5d8 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/StringExtensions.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/StringExtensions.cs @@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis; #if !NET +using ArgHelper = Microsoft.AspNetCore.Razor.ArgHelper; using ThrowHelper = Microsoft.AspNetCore.Razor.Utilities.ThrowHelper; #endif @@ -552,6 +553,31 @@ public static bool EndsWith(this string text, char value) return text.EndsWith(value); #else return text.Length > 0 && text[^1] == value; +#endif + } + + public delegate void SpanAction(Span span, TArg arg); + + public unsafe static string CreateString(int length, TState state, SpanAction action) + { +#if NET + return string.Create(length, (action, state), (span, state) => state.action(span, state.state)); +#else + ArgHelper.ThrowIfNegative(length); + + if (length == 0) + { + return string.Empty; + } + + var result = new string('\0', length); + + fixed (char* ptr = result) + { + action(new Span(ptr, length), state); + } + + return result; #endif } } From 6f3fcc1a801b6b2d55eb6ad5e8662be8651effb8 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Wed, 17 Jul 2024 15:17:22 -0700 Subject: [PATCH 02/16] Use CreateString helper in CodeWriter --- .../src/Language/CodeGeneration/CodeWriter.cs | 49 +++++-------------- ...crosoft.CodeAnalysis.Razor.Compiler.csproj | 3 +- 2 files changed, 14 insertions(+), 38 deletions(-) diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/CodeGeneration/CodeWriter.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/CodeGeneration/CodeWriter.cs index b0d4dcae50a..e9f11be5f1c 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/CodeGeneration/CodeWriter.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/CodeGeneration/CodeWriter.cs @@ -8,6 +8,7 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using Microsoft.AspNetCore.Razor.PooledObjects; +using static System.StringExtensions; namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration; @@ -259,7 +260,7 @@ public CodeWriter Write([InterpolatedStringHandlerArgument("")] ref WriteInterpo => this; [MethodImpl(MethodImplOptions.AggressiveInlining)] - private unsafe CodeWriter WriteCore(ReadOnlyMemory value, bool allowIndent = true) + private CodeWriter WriteCore(ReadOnlyMemory value, bool allowIndent = true) { if (value.IsEmpty) { @@ -336,50 +337,26 @@ public CodeWriter WriteLine([InterpolatedStringHandlerArgument("")] ref WriteInt public string GenerateCode() { - unsafe + return CreateString(Length, _pages, static (span, pages) => { - // This might look a bit scary, but it's pretty simple. We allocate our string - // with the correct length up front and then use simple pointer math to copy - // the pages of ReadOnlyMemory directly into it. - - // Eventually, we need to remove this and not return a giant string, which can - // easily be allocated on the LOH. The work to remove this is tracked by - // https://github.com/dotnet/razor/issues/8076. - - var length = Length; - var result = new string('\0', length); - - fixed (char* stringPtr = result) + foreach (var page in pages) { - var destination = stringPtr; - - // destinationSize and sourceSize track the number of bytes (not chars). - var destinationSize = length * sizeof(char); - - foreach (var page in _pages) + foreach (var chars in page) { - foreach (var chars in page) + if (chars.IsEmpty) { - var source = chars.Span; - var sourceSize = source.Length * sizeof(char); - - fixed (char* srcPtr = source) - { - Buffer.MemoryCopy(srcPtr, destination, destinationSize, sourceSize); - } + return; + } - destination += source.Length; - destinationSize -= sourceSize; + chars.Span.CopyTo(span); + span = span[chars.Length..]; - Debug.Assert(destinationSize >= 0); - } + Debug.Assert(span.Length >= 0); } - - Debug.Assert(destinationSize == 0, "We didn't exhaust our destination pointer!"); } - return result; - } + Debug.Assert(span.Length == 0, "We didn't fill the whole span!"); + }); } public void Dispose() diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Microsoft.CodeAnalysis.Razor.Compiler.csproj b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Microsoft.CodeAnalysis.Razor.Compiler.csproj index c98fe2ddf61..deca9b6930d 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Microsoft.CodeAnalysis.Razor.Compiler.csproj +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Microsoft.CodeAnalysis.Razor.Compiler.csproj @@ -1,10 +1,9 @@ - + Razor is a markup syntax for adding server-side logic to web pages. This package contains the Razor compiler. $(DefaultNetCoreTargetFramework);netstandard2.0 false - true