Add nametable helpers: nt_put_tile, nt_put_row, nt_write, nt_set_palette#471
Add nametable helpers: nt_put_tile, nt_put_row, nt_write, nt_set_palette#471
Conversation
There was a problem hiding this comment.
Pull request overview
Adds four high-level nametable helper APIs to NESLib and corresponding intrinsic handling in the IL→6502 transpiler, intended to eliminate manual VRAM address math and attribute-table bit packing for samples and games.
Changes:
- Added
nt_put_tile,nt_put_row,nt_write, andnt_set_palettetoNESLib(API surface + XML docs). - Added intrinsic dispatch/codegen for the four helpers in
IL2NESWriter.ILDispatch.cs. - Added Roslyn-based codegen tests for the new intrinsics.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 12 comments.
| File | Description |
|---|---|
| src/neslib/NESLib.cs | Adds new public helper method stubs with documentation. |
| src/neslib/PublicAPI.Shipped.txt | Ships the new public API entries. |
| src/dotnes.tasks/Utilities/IL2NESWriter.ILDispatch.cs | Implements intrinsic lowering/inline codegen for the new helpers. |
| src/dotnes.tests/RoslynTests.cs | Adds tests asserting emitted bytes for the new helpers. |
| // PHA (48) to save value | ||
| Assert.Contains("48", hex); | ||
| // PLA (68) to restore | ||
| Assert.Contains("68", hex); | ||
| // STA $2007 (PPU_DATA) | ||
| Assert.Contains("8D0720", hex); |
There was a problem hiding this comment.
Fixed. Replaced the single-byte Assert.Contains("48") / Assert.Contains("68") with a full PHA→PPU_ADDR→PLA→STA PPU_DATA sequence assertion: Assert.Contains("48A9238D0620A9C98D062068", hex). This matches the complete write-back sequence and cannot match unrelated bytes.
| // Remove ldloc buf (LDA $arrayAddr) + ldc len (LDA #len) + 3 constant args | ||
| RemoveLastInstructions(5); | ||
|
|
There was a problem hiding this comment.
Fixed. Replaced the fixed count with a backward walk that counts consecutive LDA/LDX/JSR(pusha|pushax) instructions from the end of the block. This correctly handles both RAM arrays (9 instructions) and ROM/label arrays (13 instructions).
| "nt_put_row requires a byte array argument", MethodName); | ||
| } | ||
|
|
||
| // A = length |
There was a problem hiding this comment.
len is always a compile-time constant in practice because it comes from a literal in the C# source. The existing checked((byte)len) catches overflow. Supporting runtime len would require fundamentally different codegen (preserving A through the address setup), which is out of scope for this helper. The current behavior is consistent with how the nametable/col/row args are enforced as compile-time.
| // Remove: 3 constant args (nametable, col, row) + Ldstr pattern (5 instrs) | ||
| RemoveLastInstructions(8); | ||
|
|
There was a problem hiding this comment.
Fixed. Changed to nametable > byte.MaxValue ? 13 : 12, which accounts for: ushort nametable (LDX+LDA+pushax=3) + col (LDA+pusha=2) + row (LDA+pusha=2) + pusha before Ldstr (1) + Ldstr pattern (5) = 13. Also added a fallback backward walk for robustness when the block is smaller than expected.
| // Detect byte array local via _lastLoadedLocalIndex | ||
| Local? arrayLocal = null; | ||
| bool isByteArrayLocal = _lastLoadedLocalIndex.HasValue && | ||
| Locals.TryGetValue(_lastLoadedLocalIndex.Value, out arrayLocal) && | ||
| arrayLocal.ArraySize > 0; |
There was a problem hiding this comment.
Fixed. Removed the dead isByteArrayLocal variable. The array local detection now just uses _lastLoadedLocalIndex with Locals.TryGetValue directly.
| // Check if tile is runtime (last LDA is absolute, not immediate) | ||
| var lastInstr = block[block.Count - 1]; | ||
| bool tileIsRuntime = lastInstr.Opcode == Opcode.LDA && lastInstr.Mode == AddressMode.Absolute; | ||
|
|
There was a problem hiding this comment.
Fixed. Now checks _lastLoadedLocalIndex and _lastStaticFieldAddress in addition to the LDA Absolute pattern. Throws TranspileException for unsupported computed runtime patterns (_runtimeValueInA without LDA Absolute). Added NtPutTileRuntime test.
|
/review |
|
✅ dotnes PR Reviewer completed successfully! |
There was a problem hiding this comment.
Review: Nametable Helpers
Overall: Well-designed compile-time intrinsics that follow existing transpiler patterns. The API surface is clean, the NESLib stubs follow the throw null! convention, and the RoslynTests follow the preferred testing approach. CI is green.
What looks good
- Correct PPU read-modify-write sequence in
nt_set_palette(dummy read for non-palette VRAM) vram_adr/vram_put/vram_writedon't needUsedMethods?.Add(always-included built-ins);vrambuf_putcorrectly added since it's optionalchecked((byte)len)catches overflow at transpile time- Safety guards in commit 3 (bounds check before indexing, pattern-match cast)
- PublicAPI.Shipped.txt correctly updated
Issues to address
- Missing test coverage for runtime codegen paths —
tileIsRuntime(nt_put_tile) andpaletteIsRuntime(nt_set_palette) have no tests. These are the most complex branches. - Unguarded
Stack.Pop()innt_put_row— inconsistent with all other pops in the PR. - No col/row bounds validation — since these must be compile-time constants, range checks would catch bugs at build time rather than silently emitting wrong VRAM addresses.
Generated by dotnes PR Reviewer for issue #471 · ● 4.5M
Agent-Logs-Url: https://github.com/jonathanpeppers/dotnes/sessions/ff4c9338-98e8-4800-9405-26972afff726 Co-authored-by: jonathanpeppers <840039+jonathanpeppers@users.noreply.github.com>
Agent-Logs-Url: https://github.com/jonathanpeppers/dotnes/sessions/ff4c9338-98e8-4800-9405-26972afff726 Co-authored-by: jonathanpeppers <840039+jonathanpeppers@users.noreply.github.com>
- Fix RemoveLastInstructions counts for all nt_* handlers to account for JSR pushax/pusha between arg loads (was 4, now 8 for ushort nametable args; nt_put_row uses backward walk; nt_write uses 13) - Add compile-time bounds validation for col (0-31) and row (0-29) - Fix unguarded Stack.Pop() in nt_put_row - Remove dead code (isByteArrayLocal computed but never used) - Broaden runtime tile/palette detection to also check _lastLoadedLocalIndex and _lastStaticFieldAddress, throw TranspileException for unsupported computed runtime patterns - Reset _lastLoadedLocalIndex/_lastStaticFieldAddress after use - Update XML docs to document compile-time constant constraint - Strengthen NtSetPalette test assertions with full PHA/PLA sequence - Add NtPutTileRuntime and NtSetPaletteRuntime tests for runtime codegen paths Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
f8f5bb5 to
1bdab89
Compare
Manual VRAM address math and attribute-table bit-packing are the most error-prone parts of NES development in this codebase. This adds four high-level nametable helpers to NESLib so samples can replace dozens of lines of address arithmetic and read-modify-write code with one call.
Changes
src/neslib/NESLib.cs— four new method stubs (throw null!reference-only, per the existing convention) with XML docs.src/neslib/PublicAPI.Shipped.txt— four new entries.src/dotnes.tasks/Utilities/IL2NESWriter.ILDispatch.cs— four new intrinsic dispatch cases (~225 lines):nt_put_tile,nt_put_row,nt_writerewrite the call into the equivalentvram_adr/vram_put/vrambuf_put/vram_writeemission with the address baked in at compile time.nt_set_paletteis emitted fully inline (no new built-in subroutine) — the attribute address(nt + 0x3C0) + (row/4)*8 + (col/4), quadrant shift(row & 2) | ((col & 2) >> 1), and clear-mask are all resolved at compile time, leaving a small PPU read-modify-write sequence at runtime. Constant-palettecollapses the OR to a single immediate; runtimepaletteemits a short ASL chain.src/dotnes.tests/RoslynTests.cs— fiveRoslynTests(NtPutTile,NtPutRow,NtWrite,NtSetPalette,NtSetPaletteQuadrant3) asserting on the emitted hex.Constraints
nametable,col, androwmust be compile-time constants (matches every example in the issue and keeps the dispatch logic tractable). A clearTranspileExceptionis raised otherwise.tile,palette,buf,len, andstrmay be runtime values, inheriting whatever the underlying low-level API supports..verified.binfiles are touched — the adoption work called out at the bottom of the issue (climber's ~60 lines of attribute packing, hello, attributetable, etc.) is intentionally deferred to a follow-up PR so this one stays scoped to the API surface.