diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs index 2ea987a2ef6..e6b8c6a51fd 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs @@ -906,71 +906,62 @@ void WriteSectionedArrayValue (GeneratorWriteContext context, LlvmIrSectionedArr void WriteStringBlobArray (GeneratorWriteContext context, LlvmIrStringBlob blob) { - // The stride determines how many elements are written on a single line before a newline is added. - const uint stride = 16; - Type elementType = typeof(byte); + // Emitted as a single `c"..."` literal rather than one `i8` element per byte, which shrinks + // the `.ll` ~10x. No per-string comments are possible, as the literal must be on one line. + const int HexDigits = 2; + const int MaxEscapeWidth = 1 + HexDigits; + const int ChunkSize = 4096; - LlvmIrVariableNumberFormat oldNumberFormat = context.NumberFormat; - context.NumberFormat = LlvmIrVariableNumberFormat.Hexadecimal; - WriteArrayValueStart (context); - foreach (LlvmIrStringBlob.StringInfo si in blob.GetSegments ()) { - if (si.Offset > 0) { - context.Output.Write (','); - context.Output.WriteLine (); - context.Output.WriteLine (); + char [] chunk = ArrayPool.Shared.Rent (ChunkSize); + int chunkCapacity = chunk.Length; + int chunkUsed = 0; + + try { + context.Output.Write ('c'); + context.Output.Write ('"'); + + foreach (LlvmIrStringBlob.StringInfo si in blob.GetSegments ()) { + foreach (byte b in si.Bytes) { + WriteByte (b); + } + + // Terminating NUL is counted for each string, but not included in its bytes + WriteByte (0); } - context.Output.Write (context.CurrentIndent); - WriteCommentLine (context, $" '{si.Value}' @ {si.Offset}"); - WriteBytes (si.Bytes); + Flush (); + context.Output.Write ('"'); + } finally { + ArrayPool.Shared.Return (chunk); } - context.Output.WriteLine (); - WriteArrayValueEnd (context); - context.NumberFormat = oldNumberFormat; - void WriteBytes (byte[] bytes) + void WriteByte (byte b) { - ulong counter = 0; - bool first = true; - foreach (byte b in bytes) { - if (!first) { - WriteCommaWithStride (counter); - } else { - context.Output.Write (context.CurrentIndent); - first = false; + if (b != (byte) '"' && b != (byte) '\\' && b >= 32 && b < 127) { + if (chunkUsed == chunkCapacity) { + Flush (); } - - counter++; - WriteByteTypeAndValue (b); + chunk [chunkUsed++] = (char) b; + return; } - if (bytes.Length > 0) { - WriteCommaWithStride (counter); - } else { - context.Output.Write (context.CurrentIndent); + if (chunkUsed + MaxEscapeWidth > chunkCapacity) { + Flush (); } - WriteByteTypeAndValue (0); // Terminating NUL is counted for each string, but not included in its bytes + + chunk [chunkUsed++] = '\\'; + HexUtilities.WriteHex (chunk.AsSpan (chunkUsed, HexDigits), b, upperCase: true); + chunkUsed += HexDigits; } - void WriteCommaWithStride (ulong counter) + void Flush () { - context.Output.Write (','); - if (stride == 1 || counter % stride == 0) { - context.Output.WriteLine (); - context.Output.Write (context.CurrentIndent); - } else { - context.Output.Write (' '); + if (chunkUsed == 0) { + return; } - } - void WriteByteTypeAndValue (byte v) - { - // This is by far the hottest path in the generator: a hello world MAUI app writes - // ~3.6 million bytes here. WriteType()/WriteValue() would box the byte and allocate a - // handful of strings per element, so write the (always identical) type and the two hex - // digits directly. - context.Output.Write ("i8 u0x"); - HexUtilities.WriteHex (context.Output, v, upperCase: false); + context.Output.Write (chunk, 0, chunkUsed); + chunkUsed = 0; } }