Skip to content

Transpiler: constant-fold arr.Length and sizeof(T)#540

Open
Copilot wants to merge 2 commits into
mainfrom
copilot/add-constant-folding-support
Open

Transpiler: constant-fold arr.Length and sizeof(T)#540
Copilot wants to merge 2 commits into
mainfrom
copilot/add-constant-folding-support

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 22, 2026

Roslyn emits arr.Length as ldlen and the transpiler rejected it, forcing samples to maintain a parallel const byte LEN = N. This blocks the From Below port (#378), which uses array-length loop bounds 26 times.

Changes

  • IL2NESWriterLdlen handler. When the preceding Ldloc.X/Ldsfld references a known array Local, suppress the load and emit LDA #count (or LDX/LDA pair for >255 elements) at Ldlen. Uses Local.Value, which is element count for byte, ushort, and struct arrays alike.
  • Release-mode local elision. Roslyn drops the array local for patterns like byte n = (byte)new byte[8].Length, emitting ldc; newarr; ldlen directly. Ldlen falls back to popping the size off the IL stack left by Newarr.
  • Transpiler.ScanCctorArraySizes. Replaced the prevOp is Newarr or Dup check at Stsfld with an inArrayInit flag so the standard newarr; dup; ldtoken; call InitializeArray; stsfld shape is recognized. Without this, static readonly byte[] fields were sized as 0 and .Length folded to the wrong value.
  • sizeof(T). No code change required for primitives — Roslyn folds these to Ldc before IL emission. Regression test added.
  • Error message + docs. Refined the Ldlen unsupported-opcode message to describe which forms are now supported; dropped the sizeof() caveat from transtable.c in docs/8bitworkshop-samples.md.

Now transpiles

static readonly byte[] palette_bg = new byte[] { 0x0f, /* … 16 entries … */ };

for (byte i = 0; i < palette_bg.Length; i++)   // CMP #$10 folded at transpile time
    pal_col(i, palette_bg[i]);

Tests

Five new RoslynTests in ArraysTests cover: local byte[] loop bound (byte-identical to a hard-coded N), static readonly byte[] loop bound, standalone .Length assignment under Release elision, ushort[].Length returning element count (not byte count), and sizeof(byte)/sizeof(ushort) Roslyn-folding. Existing GetUnsupportedOpcodeMessage row for Ldlen updated to match the new wording.

Agent-Logs-Url: https://github.com/jonathanpeppers/dotnes/sessions/a97fcdbc-cc6f-40e0-b6e3-c1b88a0941b7

Co-authored-by: jonathanpeppers <840039+jonathanpeppers@users.noreply.github.com>
Copilot AI changed the title [WIP] Add transpiler support for arr.Length and sizeof(T) Transpiler: constant-fold arr.Length and sizeof(T) May 22, 2026
Copilot AI requested a review from jonathanpeppers May 22, 2026 23:00
@jonathanpeppers jonathanpeppers marked this pull request as ready for review May 22, 2026 23:17
Copilot AI review requested due to automatic review settings May 22, 2026 23:17
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR extends the dotnes IL→6502 pipeline to constant-fold array length (ldlen / .Length) and verifies Roslyn’s compile-time folding of sizeof(T) for primitive types, unblocking ports that rely heavily on array-length loop bounds.

Changes:

  • Add ldlen support in IL2NESWriter by folding .Length to an immediate constant when the array size is known at transpile time (locals, static field tables, and new T[N].Length Release elision).
  • Fix .cctor scanning for static array sizes by tracking array-initialization state across dup/ldtoken/call InitializeArray patterns.
  • Add Roslyn-based regression tests for .Length folding and primitive sizeof() folding; update related unsupported-opcode guidance/docs.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/dotnes.tests/IL2NESWriterTests.cs Updates the Ldlen unsupported-opcode message expectation to match new guidance.
src/dotnes.tests/ArraysTests.cs Adds Roslyn tests covering .Length folding patterns and sizeof(byte/ushort) folding.
src/dotnes.tasks/Utilities/Transpiler.StructAnalysis.cs Improves .cctor scanning so static array sizes are discovered for .Length folding.
src/dotnes.tasks/Utilities/IL2NESWriter.ILDispatch.cs Implements Ldlen folding in IL dispatch and adds/duplicates Sizeof handling.
src/dotnes.tasks/Utilities/IL2NESWriter.Helpers.cs Adds helper lookahead logic to detect ldlen and suppress redundant array loads.
src/dotnes.tasks/Utilities/IL2NESWriter.cs Adds _pendingLdlenSource state used to fold .Length at ldlen.
docs/8bitworkshop-samples.md Removes an outdated sizeof() limitation note.

Comment on lines 171 to 175
int lastLdc = 0;
ILOpCode prevOp = ILOpCode.Nop;
bool inArrayInit = false; // True after Newarr until Stsfld/Stloc/etc. consumes the array

while (il.Offset < il.Length)
Comment on lines +1168 to +1176
else if (previous == ILOpCode.Newarr && Stack.Count > 0)
{
// Roslyn (Release) elides the local for `new T[N].Length`, emitting
// ldc; newarr; ldlen directly. The element count is on the Stack
// (the Newarr handler left it there after removing the size LDA).
// For byte[] the count IS the length; for ushort[] it was already
// popped during newarr processing, so this path only handles byte[].
count = Stack.Pop();
}
Comment on lines +1187 to +1193
case ILOpCode.Sizeof:
// Native integer size (nint/IntPtr) on the 6502 is 1 byte (8-bit CPU).
// Fixed-size primitive types (byte, ushort, int, etc.) are folded by Roslyn at compile time
// and typically never reach this opcode; this implementation only handles platform-dependent
// native integer sizeof values and will always push 1 here.
WriteLdc(1);
break;
Comment on lines +1200 to +1201
// Use a local copy of the array for indexing to focus the test on
// the .Length constant-folding for the static field.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Transpiler: support arr.Length and sizeof(T) constant-folding

3 participants