Skip to content

Commit

Permalink
Implemented Bne_un_s instruction (#54)
Browse files Browse the repository at this point in the history
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);
  • Loading branch information
jonathanpeppers authored Sep 20, 2024
1 parent d89f32e commit acacf1f
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 26 deletions.
64 changes: 52 additions & 12 deletions src/dotnes.tasks/Utilities/IL2NESWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -314,8 +350,12 @@ public void Write(ILInstruction instruction, ImmutableArray<byte> 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);
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down
37 changes: 25 additions & 12 deletions src/dotnes.tasks/Utilities/Transpiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,35 +33,38 @@ public Transpiler(Stream stream, IList<AssemblyReader> assemblyFiles, ILogger? l
/// </summary>
/// <param name="sizeOfMain"></param>
/// <returns></returns>
private Dictionary<string, ushort> CalculateAddressLabels(ushort sizeOfMain)
private Dictionary<string, ushort> 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);
}
}

Expand All @@ -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);
Expand Down Expand Up @@ -178,15 +183,18 @@ public void Write(Stream stream)
/// <summary>
/// Generate static void main in a first pass, so we know the size of the program
/// </summary>
protected virtual void FirstPass(Dictionary<string, ushort> labels, out ushort sizeOfMain, out byte locals)
protected virtual void FirstPass(Dictionary<string, ushort> 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)
{
Expand Down Expand Up @@ -216,8 +224,13 @@ protected virtual void FirstPass(Dictionary<string, ushort> labels, out ushort s
/// </summary>
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);
Expand Down
58 changes: 56 additions & 2 deletions src/dotnes.tests/RoslynTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -294,7 +297,7 @@ public void OneLocalByte()
expectedAssembly:
"""
A916
8D2503
8D2503 ; STA M0001
A922
A286
202B82 ; JSR pal_bg
Expand Down Expand Up @@ -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
""");
}
}

0 comments on commit acacf1f

Please sign in to comment.