Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -906,71 +906,67 @@ 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);
// String blobs are emitted as a single LLVM IR constant byte string (`c"..."`) instead of one
// `i8 u0xNN` element per byte. A hello world MAUI app has ~3.6 million bytes of type map
// strings, so the array form produces a >50MB `.ll` file which is slow both to write here and
// to consume in `llc`. The `c"..."` form is roughly ten times smaller.
//
// A consequence of this is that we can no longer annotate individual strings with comments,
// because a constant string literal must fit on a single line and `;` comments extend to the
// end of the line.

// `Rent()` may return a larger array than requested, so use its actual length as the capacity.
char [] chunk = ArrayPool<char>.Shared.Rent (4096);
int chunkCapacity = chunk.Length;
int chunkUsed = 0;

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 ();
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<char>.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;
// `"` and `\` must always be escaped, as must anything outside of the printable ASCII range.
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 + 3 > chunkCapacity) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 💡 Code organization — The + 3 here is the maximum width of a single emitted escape (\ + two hex digits), and 4096 on line 919 is the buffer size. Both are magic numbers whose relationship is implicit. Consider a named const int MaxEscapeWidth = 3; (and a const int ChunkSize = 4096;) so the flush-threshold intent is self-documenting.

(non-blocking maintainability nit)

Flush ();
}
WriteByteTypeAndValue (0); // Terminating NUL is counted for each string, but not included in its bytes

chunk [chunkUsed++] = '\\';
HexUtilities.WriteHex (chunk.AsSpan (chunkUsed, 2), b, upperCase: true);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 💡 Documentation — Nice catch reusing the span-based WriteHex overload here for the zero-allocation hot path. Note this switches the emitted hex to uppercase (\FF) where the old array form used lowercase (u0xff); that's cosmetic-only for the .ll and llc accepts both, so no action needed — just flagging it since it's the one visible textual difference a reader diffing .ll files might notice.

(informational; no change required)

chunkUsed += 2;
Comment thread
Copilot marked this conversation as resolved.
}

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;
}
}

Expand Down