-
Notifications
You must be signed in to change notification settings - Fork 54
Do some extra work to avoid making a full size copy of the buffer when base64 encoding #1372
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,162 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
using System; | ||
using System.IO; | ||
using System.Xml.Linq; | ||
|
||
#nullable enable | ||
|
||
namespace Microsoft.DotNet.XHarness.TestRunners.Xunit; | ||
|
||
internal class WasmXmlResultWriter | ||
{ | ||
public static void WriteOnSingleLine(XElement assembliesElement) | ||
{ | ||
using var ms = new MemoryStream(); | ||
assembliesElement.Save(ms); | ||
ms.TryGetBuffer(out var bytes); | ||
var base64 = Convert.ToBase64String(bytes, Base64FormattingOptions.None); | ||
Console.WriteLine($"STARTRESULTXML {bytes.Count} {base64} ENDRESULTXML"); | ||
Console.WriteLine($"Finished writing {bytes.Count} bytes of RESULTXML"); | ||
} | ||
} | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
using System; | ||
using System.IO; | ||
using System.Runtime.InteropServices; | ||
using System.Xml.Linq; | ||
using System.Text; | ||
using System.Security.Cryptography; | ||
|
||
#nullable enable | ||
|
||
namespace Microsoft.DotNet.XHarness.TestRunners.Xunit; | ||
|
||
internal class WasmXmlResultWriter | ||
{ | ||
#if DEBUG | ||
public static void WriteOnSingleLine(XElement assembliesElement) | ||
{ | ||
using var ms = new MemoryStream(); | ||
assembliesElement.Save(ms); | ||
ms.TryGetBuffer(out var bytes); | ||
var base64 = Convert.ToBase64String(bytes, Base64FormattingOptions.None); | ||
Console.WriteLine($"STARTRESULTXML {bytes.Count} {base64} ENDRESULTXML"); | ||
Console.WriteLine($"Finished writing {bytes.Count} bytes of RESULTXML"); | ||
} | ||
#else | ||
private class ToBase64CharTransform : ICryptoTransform | ||
{ | ||
private readonly ToBase64Transform _base64Transform = new ToBase64Transform(); | ||
private byte[] _intermediate = new byte[2]; | ||
|
||
public int InputBlockSize => _base64Transform.InputBlockSize; // 3 bytes of input | ||
public int OutputBlockSize => _base64Transform.OutputBlockSize * 2; // 4 bytes of base64 output * 2 for UTF-16 encoding | ||
|
||
public bool CanTransformMultipleBlocks => _base64Transform.CanTransformMultipleBlocks; | ||
public bool CanReuseTransform => _base64Transform.CanReuseTransform; | ||
|
||
public void Dispose() | ||
{ | ||
_base64Transform.Dispose(); | ||
} | ||
|
||
public int TransformBlock( | ||
byte[] inputBuffer, int inputOffset, int inputCount, | ||
byte[] outputBuffer, int outputOffset) | ||
{ | ||
int inputBlocks = Math.DivRem(inputCount, InputBlockSize, out int inputRemainder); | ||
|
||
if (inputRemainder != 0) | ||
{ | ||
throw new ArgumentException($"Input count must be a multiple of {InputBlockSize}.", nameof(inputCount)); | ||
} | ||
|
||
if (inputCount == 0) | ||
{ | ||
throw new ArgumentException("Input count must be greater than 0.", nameof(inputCount)); | ||
} | ||
|
||
/* | ||
Input Buffer ("hi mom"): | ||
+-----+-----+-----+-----+-----+-----+ | ||
| 'h' | 'i' | ' ' | 'm' | 'o' | 'm' | | ||
+-----+-----+-----+-----+-----+-----+ | ||
|104 |105 | 32 |109 |111 |109 | | ||
+-----+-----+-----+-----+-----+-----+ | ||
|
||
Base64 Encoding Process: | ||
- 'hi ' -> 'aGkg' | ||
- 'mom' -> 'bW9t' | ||
|
||
Base64 Encoded Output: | ||
| |base64Written | | base64Written | | ||
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ | ||
| \0 | \0 | \0 | \0 |'a' |'G' |'k' |'g' | \0 | \0 | \0 | \0 |'b' |'W' |'9' |'t' | | ||
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ | ||
| 0 | 0 | 0 | 0 | 97 | 71 |107 |103 | 0 | 0 | 0 | 0 | 98 | 87 | 57 |116 | | ||
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ | ||
|
||
Expanded Output Buffer (UTF-16 Encoding): | ||
| outputChars | outputChars | | ||
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ | ||
| \0 |'a' | \0 |'G' | \0 |'k' | \0 |'g' | \0 |'b' | \0 |'W' | \0 |'9' | \0 |'t' | | ||
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ | ||
| 0 | 97 | 0 | 71 | 0 |107 | 0 |103 | 0 | 98 | 0 | 87 | 0 | 57 | 0 |116 | | ||
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ | ||
|
||
*/ | ||
|
||
// Calculate positions in the output buffer | ||
int base64OutputStart = outputOffset + OutputBlockSize / 2; | ||
|
||
// write Base64 transformation directly to the second half of the output buffer | ||
int base64BytesWritten = _base64Transform.TransformBlock( | ||
inputBuffer, inputOffset, inputCount, | ||
outputBuffer, base64OutputStart); | ||
|
||
var base64Written = outputBuffer.AsSpan(base64OutputStart, base64BytesWritten); | ||
var outputChars = outputBuffer.AsSpan(outputOffset, OutputBlockSize); | ||
|
||
for (int i = 0; i < base64BytesWritten; i++) | ||
{ | ||
// Expand each ascii byte to a char write it in the same logical position | ||
// as a char in outputChars eventually filling the output buffer | ||
if (!BitConverter.TryWriteBytes(outputChars.Slice(i * 2), (char)base64Written[i])) | ||
{ | ||
BitConverter.TryWriteBytes(_intermediate, (char)base64Written[i]); | ||
_intermediate.CopyTo(outputChars.Slice(i * 2, 2)); | ||
} | ||
} | ||
|
||
return base64BytesWritten * 2; | ||
} | ||
|
||
public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount) | ||
{ | ||
// Apply Base64 transformation to the final block | ||
byte[] base64Buffer = _base64Transform.TransformFinalBlock(inputBuffer, inputOffset, inputCount); | ||
|
||
// Expand each Base64 byte to two bytes in the output buffer | ||
byte[] outputBuffer = new byte[base64Buffer.Length * 2]; | ||
for (int i = 0; i < base64Buffer.Length; i++) | ||
{ | ||
// Convert each ascii byte to a char | ||
BitConverter.TryWriteBytes(outputBuffer.AsSpan(i * 2), (char)base64Buffer[i]); | ||
} | ||
|
||
return outputBuffer; | ||
} | ||
} | ||
|
||
public static void WriteOnSingleLine(XElement assembliesElement) | ||
{ | ||
using var ms = new MemoryStream(); | ||
using var transform = new ToBase64CharTransform(); | ||
using var cryptoStream = new CryptoStream(ms, transform, CryptoStreamMode.Write); | ||
|
||
// Create a StreamWriter to write the XML content to the CryptoStream | ||
using var xmlWriter = new StreamWriter(cryptoStream, Encoding.UTF8); | ||
|
||
assembliesElement.Save(xmlWriter); | ||
|
||
// Ensure all data is flushed through the CryptoStream | ||
xmlWriter.Flush(); | ||
cryptoStream.FlushFinalBlock(); | ||
|
||
// guaranteed to succeed with the MemoryStream() constructor | ||
ms.TryGetBuffer(out var bytes); | ||
// we went to a lot of trouble to put characters in the final buffer | ||
// so that we can avoid a copy here and pass the span directly to the | ||
// string interpolation logic. | ||
Span<char> charData = MemoryMarshal.Cast<byte, char>(bytes.AsSpan()); | ||
lewing marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// Output the result and the the ascii length of the data | ||
Console.Write($"STARTRESULTXML {charData.Length} "); | ||
Console.Write(charData); | ||
Check failure on line 157 in src/Microsoft.DotNet.XHarness.TestRunners.Xunit/WasmXmlResultWriter.cs
|
||
Console.WriteLine(" ENDRESULTXML"); | ||
Console.WriteLine($"Finished writing {charData.Length} bytes of RESULTXML"); | ||
} | ||
#endif | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that base64 could contain
\n
in the middle right ? If so, you can insert some which would make the buffer in emscripten smaller. Becausestdout
emulator would build buffer until newline and only then write it into browserconsole.log
.@maraf noted that maybe we had some issues with message order WRT
WASM EXIT
... so I don't know if multi-line is good idea or not.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could in theory write each block as we transform it rather than create a single buffer. We should really review the emscripten line buffering.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternatively, this could be
fetch('POST', xml)
in browser and nodejs.Remaining is
v8
shell which doesn't have file write. So we may need to keep this contraption.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really think we should implement it as POST. Do we run V8 in CI at all ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, we noticed it in dotnet/runtime#102069 (comment). it'd be nice to fix that though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#1388