Skip to content

Transpiler: support multi-byte static fields (int/ushort)#238

Merged
jonathanpeppers merged 3 commits intomainfrom
copilot/support-multi-byte-static-fields
Mar 17, 2026
Merged

Transpiler: support multi-byte static fields (int/ushort)#238
jonathanpeppers merged 3 commits intomainfrom
copilot/support-multi-byte-static-fields

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 16, 2026

PreAllocateStaticFields allocated 1 byte per static field regardless of type, so int/ushort fields overlapped adjacent fields in zero-page memory.

Changes

  • Transpiler.csBuildStaticFieldSizes() reads field types from metadata signatures via DecodeFieldSize(). PreAllocateStaticFields() now allocates 2 bytes for ushort/short/int/uint fields and returns a WordStaticFields set alongside addresses. LocalCount initialized from total byte count instead of field count.
  • LocalVariableManager.csWordStaticFields HashSet (paralleling WordLocals). StaticFieldAddresses setter uses per-field size when computing LocalCount.
  • IL2NESWriter.StructFields.csHandleLdsfld emits LDA+LDX for word fields, sets _ushortInAX. HandleStsfld emits STA+STX from A:X, or zero-extends byte values into the high byte.
  • IL2NESWriter.csWordStaticFields forwarding property.

Example

static class G
{
    public static ushort word_val;  // now gets 2 bytes at $0328-$0329
    public static byte byte_val;   // 1 byte at $0325
    public static int int_val;     // 2 bytes at $0326-$0327 (capped to 16-bit)
}

G.word_val = 300;        // → LDA #$2C, STA $0328, STX $0329 (hi=$01)
G.int_val = G.byte_val;  // → LDA $0325, STA $0326, LDA #$00, STA $0327

Test

Added wordfield sample with ushort and int static fields validating correct allocation, 16-bit load/store, and zero-extension.

Original prompt

This section details on the original issue you should resolve

<issue_title>Transpiler: support multi-byte static fields (int/ushort)</issue_title>
<issue_description>Follow-up from PR #211 (crypto.c sample port) review.

PreAllocateStaticFields in Transpiler.cs allocates exactly 1 byte per static field regardless of type. When a sample uses int or ushort static fields:

static class G
{
    public static int int_temp0;  // needs 2-4 bytes, only gets 1
}

The field overlaps with adjacent fields in zero-page memory, causing corruption.

Use case

The crypto sample needs int for screen coordinate math where byte would overflow:

G.int_temp0 = G.enemies_x[G.i];
G.int_temp0 <<= 1;
G.int_temp0 -= G.scr_x;
if (G.int_temp0 < 0 || G.int_temp0 > 255) { G.i++; continue; }
G.temp0 = (byte)G.int_temp0;

Current workaround

Use pairs of byte fields and manual hi/lo splitting, or restructure math to stay within byte range.

Implementation

  1. PreAllocateStaticFields needs to detect field types from metadata (similar to DetectWordLocals for local variables)
  2. Allocate 2 bytes for ushort/short/int fields (NES is 8-bit, so 16-bit is sufficient)
  3. HandleLdsfld/HandleStsfld need to emit 16-bit load/store sequences for word-sized fields
  4. LocalVariableManager.GetOrAllocateStaticField needs a size parameter

Context

Blocking the crypto sample (PR #211). See review: #211 (review)</issue_description>

Comments on the Issue (you are @copilot in this section)


📱 Kick off Copilot coding agent tasks wherever you are with GitHub Mobile, available on iOS and Android.

- BuildStaticFieldSizes() reads field types from metadata signatures
- PreAllocateStaticFields() allocates 2 bytes for ushort/short/int fields
- HandleLdsfld emits LDA+LDX for word fields (16-bit load to A:X)
- HandleStsfld emits STA+STX for word fields (16-bit store from A:X)
- WordStaticFields tracked on LocalVariableManager and IL2NESWriter
- StaticFieldAddresses setter accounts for word field sizes
- Added wordfield test sample with ushort and int static fields

Co-authored-by: jonathanpeppers <840039+jonathanpeppers@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix multi-byte static fields allocation in transpiler Transpiler: support multi-byte static fields (int/ushort) Mar 16, 2026
Copilot AI requested a review from jonathanpeppers March 16, 2026 01:36
Copy link
Copy Markdown
Owner

@jonathanpeppers jonathanpeppers left a comment

Choose a reason for hiding this comment

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

🤖 Reviewed and verified — this looks great.

What I checked:

  • Field size detection: BuildStaticFieldSizes() correctly reads field signatures via DecodeFieldSize() and caps at 2 bytes. Skips non-static and RVA fields.
  • Address allocation: PreAllocateStaticFields allocates sequential addresses with per-field sizes. The tuple return (addresses, wordFields, totalBytes) keeps all three pieces synchronized.
  • 16-bit emission: HandleLdsfld emits LDA addr / LDX addr+1 and sets _ushortInAX. HandleStsfld handles all four state combinations correctly (_ushortInAX, _runtimeValueInA, _immediateInA, fallback).
  • Zero-extension: Storing a byte into a word field correctly zeros the high byte.
  • LocalCount: Main writer uses explicit staticFieldBytes; method writers get correct offsets via methodFrameOffsets (which starts from mainLocalCount).
  • ROM disassembly: Verified the wordfield ROM — ldc_i4 300LDX #$01, LDA #$2C, STA $0328, STX $0329 ✓, byte→word zero-extend STA $0326, LDA #$00, STA $0327 ✓, conv_u1 correctly discards X ✓.
  • All 509 tests pass locally.

@jonathanpeppers jonathanpeppers marked this pull request as ready for review March 17, 2026 23:40
Copilot AI review requested due to automatic review settings March 17, 2026 23:40
@jonathanpeppers
Copy link
Copy Markdown
Owner

🤖 CI is green. Ready for human review.

Remove the opaque wordfield.debug.dll, wordfield.release.dll, and
verified.bin in favor of four inline Roslyn tests that compile the
C# source at test time:

- WordStaticField_StoreImmediate: 16-bit immediate store
- WordStaticField_LoadZeroExtend: byte-to-word zero extension
- WordStaticField_LoadWord: 16-bit LDA+LDX load
- WordStaticField_TwoByteAllocation: no address overlap

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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

Adds proper 16-bit (word) support for user-defined static fields in the dotnes transpiler so ushort/short/int/uint statics don’t overlap adjacent RAM allocations and can be loaded/stored as 16-bit values.

Changes:

  • Allocate static fields by byte-size derived from metadata signatures (with int/uint capped to 16-bit for NES use).
  • Track which static fields are word-sized and emit 16-bit ldsfld/stsfld sequences (A:low, X:high) including zero-extension when storing byte→word.
  • Add a wordfield test sample (debug+release inputs) and snapshot to validate correct behavior.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/dotnes.tasks/Utilities/Transpiler.cs Computes per-static-field sizes from metadata and pre-allocates shared addresses + WordStaticFields set.
src/dotnes.tasks/Utilities/LocalVariableManager.cs Adds WordStaticFields tracking and uses it when advancing LocalCount for preallocated statics.
src/dotnes.tasks/Utilities/IL2NESWriter.StructFields.cs Emits 16-bit load/store for word static fields and handles byte→word zero-extension on stores.
src/dotnes.tasks/Utilities/IL2NESWriter.cs Forwards WordStaticFields through the writer API.
src/dotnes.tests/TranspilerTests.cs Adds wordfield to snapshot test matrix (debug + release).
src/dotnes.tests/TranspilerTests.Write.wordfield.verified.bin New ROM snapshot for the wordfield sample.
src/dotnes.tests/Data/wordfield.debug.dll New debug input DLL for wordfield transpilation test.
src/dotnes.tests/Data/wordfield.release.dll New release input DLL for wordfield transpilation test.

@jonathanpeppers jonathanpeppers merged commit 827b720 into main Mar 17, 2026
1 check passed
@jonathanpeppers jonathanpeppers deleted the copilot/support-multi-byte-static-fields branch March 17, 2026 23:46
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 multi-byte static fields (int/ushort)

3 participants