From acacf1f3b8d2bfe0cee7f954f4bb0d55a925d114 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Fri, 20 Sep 2024 11:09:53 -0500 Subject: [PATCH] Implemented `Bne_un_s` instruction (#54) This is very rudimentary branching, programs such as: pal_all(PALETTE); byte x = 10; if (x == 10) oam_spr(x, 40, 0x10, 3, 0); ppu_on_all(); while (true) ; This works if I change to: byte x = 40; if (x == 40) oam_spr(x, 40, 0x10, 3, 0); --- src/dotnes.tasks/Utilities/IL2NESWriter.cs | 64 ++++++++++++++++++---- src/dotnes.tasks/Utilities/Transpiler.cs | 37 +++++++++---- src/dotnes.tests/RoslynTests.cs | 58 +++++++++++++++++++- 3 files changed, 133 insertions(+), 26 deletions(-) diff --git a/src/dotnes.tasks/Utilities/IL2NESWriter.cs b/src/dotnes.tasks/Utilities/IL2NESWriter.cs index a1f012c..52fb068 100644 --- a/src/dotnes.tasks/Utilities/IL2NESWriter.cs +++ b/src/dotnes.tasks/Utilities/IL2NESWriter.cs @@ -95,7 +95,6 @@ public void Write(ILInstruction instruction, ushort sizeOfMain) case ILOpCode.Stloc_0: if (previous == ILOpCode.Ldtoken) { - SeekBack(4); Locals[0] = new Local(Stack.Pop()); } else @@ -106,7 +105,6 @@ public void Write(ILInstruction instruction, ushort sizeOfMain) case ILOpCode.Stloc_1: if (previous == ILOpCode.Ldtoken) { - SeekBack(4); Locals[1] = new Local(Stack.Pop()); } else @@ -117,7 +115,6 @@ public void Write(ILInstruction instruction, ushort sizeOfMain) case ILOpCode.Stloc_2: if (previous == ILOpCode.Ldtoken) { - SeekBack(4); Locals[2] = new Local(Stack.Pop()); } else @@ -128,7 +125,6 @@ public void Write(ILInstruction instruction, ushort sizeOfMain) case ILOpCode.Stloc_3: if (previous == ILOpCode.Ldtoken) { - SeekBack(4); Locals[3] = new Local(Stack.Pop()); } else @@ -213,21 +209,61 @@ public void Write(ILInstruction instruction, int operand, ushort sizeOfMain) } break; case ILOpCode.Stloc_s: - if (previous == ILOpCode.Ldtoken) - { - SeekBack(4); - } Locals[operand] = new Local(Stack.Pop()); break; case ILOpCode.Ldloc_s: WriteLdloc(Locals[operand], sizeOfMain); break; + case ILOpCode.Bne_un_s: + SeekBack(5); + Write(NESInstruction.CMP, checked((byte)Stack.Pop())); + Write(NESInstruction.BNE_rel, NumberOfInstructionsForBranch(instruction.Offset + operand + 2, sizeOfMain)); + break; default: throw new NotImplementedException($"OpCode {instruction.OpCode} with Int32 operand is not implemented!"); } previous = instruction.OpCode; } + public ILInstruction[]? Instructions { get; set; } + + public int Index { get; set; } + + byte NumberOfInstructionsForBranch(int stopAt, ushort sizeOfMain) + { + _logger.WriteLine($"Reading forward until IL_{stopAt:x4}..."); + + if (Instructions is null) + throw new ArgumentNullException(nameof(Instructions)); + + long nesPosition = _writer.BaseStream.Position; + for (int i = Index + 1; ; i++) + { + var instruction = Instructions[i]; + if (instruction.Integer != null) + { + Write(instruction, instruction.Integer.Value, sizeOfMain); + } + else if (instruction.String != null) + { + Write(instruction, instruction.String, sizeOfMain); + } + else if (instruction.Bytes != null) + { + Write(instruction, instruction.Bytes.Value, sizeOfMain); + } + else + { + Write(instruction, sizeOfMain); + } + if (instruction.Offset >= stopAt) + break; + } + byte numberOfInstructions = checked((byte)(_writer.BaseStream.Position - nesPosition)); + SeekBack(numberOfInstructions); + return numberOfInstructions; + } + public void Write(ILInstruction instruction, string operand, ushort sizeOfMain) { switch (instruction.OpCode) @@ -314,8 +350,12 @@ public void Write(ILInstruction instruction, ImmutableArray operand, ushor ByteArrayOffset += 44; } } - Write(NESInstruction.LDA, (byte)(ByteArrayOffset & 0xff)); - Write(NESInstruction.LDX, (byte)(ByteArrayOffset >> 8)); + // HACK: write these if next instruction is Call + if (Instructions is not null && Instructions[Index + 1].OpCode == ILOpCode.Call) + { + Write(NESInstruction.LDA, (byte)(ByteArrayOffset & 0xff)); + Write(NESInstruction.LDX, (byte)(ByteArrayOffset >> 8)); + } Stack.Push(ByteArrayOffset); ByteArrayOffset = (ushort)(ByteArrayOffset + operand.Length); ByteArrays.Add(operand); @@ -472,7 +512,7 @@ void WriteStloc(Local local) if (local.Value < byte.MaxValue) { LocalCount += 1; - SeekBack(6); + SeekBack(2); Write(NESInstruction.LDA, (byte)local.Value); Write(NESInstruction.STA_abs, (ushort)local.Address); Write(NESInstruction.LDA, 0x22); @@ -481,7 +521,7 @@ void WriteStloc(Local local) else if (local.Value < ushort.MaxValue) { LocalCount += 2; - SeekBack(8); + SeekBack(4); Write(NESInstruction.LDX, 0x03); Write(NESInstruction.LDA, 0xC0); Write(NESInstruction.STA_abs, (ushort)local.Address); diff --git a/src/dotnes.tasks/Utilities/Transpiler.cs b/src/dotnes.tasks/Utilities/Transpiler.cs index 76e794a..c157b71 100644 --- a/src/dotnes.tasks/Utilities/Transpiler.cs +++ b/src/dotnes.tasks/Utilities/Transpiler.cs @@ -33,35 +33,38 @@ public Transpiler(Stream stream, IList assemblyFiles, ILogger? l /// /// /// - private Dictionary CalculateAddressLabels(ushort sizeOfMain) + private Dictionary CalculateAddressLabels(ILInstruction[] instructions) { using var ms = new MemoryStream(); using var writer = new IL2NESWriter(ms, logger: _logger) { UsedMethods = UsedMethods, + Instructions = instructions, }; // Write built-in functions - writer.WriteBuiltIns(sizeOfMain); + writer.WriteBuiltIns(sizeOfMain: 0); // Write main program - foreach (var instruction in ReadStaticVoidMain()) + for (int i = 0; i < writer.Instructions.Length; i++) { + writer.Index = i; + var instruction = writer.Instructions[i]; if (instruction.Integer != null) { - writer.Write(instruction, instruction.Integer.Value, sizeOfMain); + writer.Write(instruction, instruction.Integer.Value, sizeOfMain: 0); } else if (instruction.String != null) { - writer.Write(instruction, instruction.String, sizeOfMain); + writer.Write(instruction, instruction.String, sizeOfMain: 0); } else if (instruction.Bytes != null) { - writer.Write(instruction, instruction.Bytes.Value, sizeOfMain); + writer.Write(instruction, instruction.Bytes.Value, sizeOfMain: 0); } else { - writer.Write(instruction, sizeOfMain); + writer.Write(instruction, sizeOfMain: 0); } } @@ -82,15 +85,17 @@ public void Write(Stream stream) _logger.WriteLine($"First pass..."); - var labels = CalculateAddressLabels(0); + var instructions = ReadStaticVoidMain().ToArray(); + var labels = CalculateAddressLabels(instructions); // Generate static void main in a first pass, so we know the size of the program - FirstPass(labels, out ushort sizeOfMain, out byte locals); + FirstPass(labels, instructions, out ushort sizeOfMain, out byte locals); _logger.WriteLine($"Size of main: {sizeOfMain}"); using var writer = new IL2NESWriter(stream, logger: _logger) { + Instructions = instructions, UsedMethods = UsedMethods, }; writer.SetLabels(labels); @@ -178,15 +183,18 @@ public void Write(Stream stream) /// /// Generate static void main in a first pass, so we know the size of the program /// - protected virtual void FirstPass(Dictionary labels, out ushort sizeOfMain, out byte locals) + protected virtual void FirstPass(Dictionary labels, ILInstruction[] instructions, out ushort sizeOfMain, out byte locals) { using var writer = new IL2NESWriter(new MemoryStream(), logger: _logger) { UsedMethods = UsedMethods, + Instructions = instructions, }; writer.SetLabels(labels); - foreach (var instruction in ReadStaticVoidMain()) + for (int i = 0; i < writer.Instructions.Length; i++) { + writer.Index = i; + var instruction = writer.Instructions[i]; _logger.WriteLine($"{instruction}"); if (instruction.Integer != null) { @@ -216,8 +224,13 @@ protected virtual void FirstPass(Dictionary labels, out ushort s /// protected virtual void SecondPass(ushort sizeOfMain, IL2NESWriter writer) { - foreach (var instruction in ReadStaticVoidMain()) + if (writer.Instructions is null) + throw new ArgumentNullException(nameof(writer.Instructions)); + + for (int i = 0; i < writer.Instructions.Length; i++) { + writer.Index = i; + var instruction = writer.Instructions[i]; if (instruction.Integer != null) { writer.Write(instruction, instruction.Integer.Value, sizeOfMain); diff --git a/src/dotnes.tests/RoslynTests.cs b/src/dotnes.tests/RoslynTests.cs index 26062c1..a98a832 100644 --- a/src/dotnes.tests/RoslynTests.cs +++ b/src/dotnes.tests/RoslynTests.cs @@ -72,8 +72,11 @@ protected override void SecondPass(ushort sizeOfMain, IL2NESWriter parent) base.SecondPass(sizeOfMain, parent); _writer.SetLabels(parent.Labels); - foreach (var instruction in ReadStaticVoidMain()) + _writer.Instructions = parent.Instructions!; + for (int i = 0; i < _writer.Instructions.Length; i++) { + _writer.Index = i; + var instruction = _writer.Instructions[i]; _logger.WriteLine($"{instruction}"); if (instruction.Integer != null) @@ -294,7 +297,7 @@ public void OneLocalByte() expectedAssembly: """ A916 - 8D2503 + 8D2503 ; STA M0001 A922 A286 202B82 ; JSR pal_bg @@ -358,4 +361,55 @@ public void StaticSprite() 4C2385 ; JMP $8527 """); } + + [Fact] + public void Branch() + { + AssertProgram( + csharpSource: + """ + byte[] PALETTE = new byte[32] { + 0x01, + 0x11,0x30,0x27,0x0, + 0x1c,0x20,0x2c,0x0, + 0x00,0x10,0x20,0x0, + 0x06,0x16,0x26,0x0, + 0x16,0x35,0x24,0x0, + 0x00,0x37,0x25,0x0, + 0x0d,0x2d,0x3a,0x0, + 0x0d,0x27,0x2a + }; + pal_all(PALETTE); + byte x = 40; + if (x == 40) + oam_spr(x, 40, 0x10, 3, 0); + ppu_on_all(); + while (true) ; + """, + expectedAssembly: + """ + A9E5 + A285 + 201182 ; JSR pal_all + A928 + 8D2403 ; STA M0001 + A922 + A286 + AD2403 ; LDA M0001 + C928 ; CMP #$28 + D01D ; BNE $8530 + AD2403 ; LDA M0001 + 209685 ; JSR popa + A928 + 209685 ; JSR popa + A910 + 209685 ; JSR popa + A903 + 209685 ; JSR popa + A900 + 20E585 ; JSR oam_spr + 208982 ; JSR ppu_on_all + 4C3485 + """); + } }