Implement startup string and block copy opcodes
This commit is contained in:
@@ -10,11 +10,17 @@ public class NaturalBootIntegrationTests
|
||||
private sealed class StopAtSc0000Sink : ITraceSink
|
||||
{
|
||||
public readonly List<string> Entered = new();
|
||||
public bool SawStringEqualsStub;
|
||||
public bool SawUnitDataCopyStub;
|
||||
public Action<string>? OnEnter;
|
||||
public bool TracingSteps => false;
|
||||
public bool TracingSteps => true;
|
||||
|
||||
public void Emit(in TraceEvent e)
|
||||
{
|
||||
if (e.Kind == TraceEventKind.Stub && e.Opcode == 0x194)
|
||||
SawStringEqualsStub = true;
|
||||
if (e.Kind == TraceEventKind.Stub && e.Opcode is 0x63 or 0x1b0)
|
||||
SawUnitDataCopyStub = true;
|
||||
if (e.Kind == TraceEventKind.FrameEnter && e.Name != null)
|
||||
{
|
||||
Entered.Add(e.Name);
|
||||
@@ -114,5 +120,7 @@ public class NaturalBootIntegrationTests
|
||||
Assert.Equal(1, vm.Globals.GetValueOrDefault(0));
|
||||
Assert.Equal(0x22, vm.Globals.GetValueOrDefault(0x699));
|
||||
Assert.Equal(1, vm.Globals.GetValueOrDefault(0x6c1));
|
||||
Assert.False(sink.SawStringEqualsStub);
|
||||
Assert.False(sink.SawUnitDataCopyStub);
|
||||
}
|
||||
}
|
||||
|
||||
87
engine/Age.Engine.Tests/PointerAndBlockCopyOpsTests.cs
Normal file
87
engine/Age.Engine.Tests/PointerAndBlockCopyOpsTests.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Age.Engine.Model;
|
||||
using Age.Engine.Sys4;
|
||||
using Age.Engine.Vm;
|
||||
using Xunit;
|
||||
|
||||
public class PointerAndBlockCopyOpsTests
|
||||
{
|
||||
private const int Immediate = 0, InlineString = 2, GlobalInt = 3, GlobalString = 5,
|
||||
LocalInt = 9, LocalPointer = 12, LocalStringPointer = 14;
|
||||
|
||||
[Fact]
|
||||
public void TakeAddressAndCopyDwords_PreserveLocalAndGlobalAddressDomains()
|
||||
{
|
||||
var table = OpcodeTableJson.Load(Paths.OpcodesJson);
|
||||
int move = table.ByLabel("mov")!.Value;
|
||||
int lookup = table.ByLabel("lookup-array")!.Value;
|
||||
var script = ScriptAssembler.Assemble(table, "POINTER_BLOCK_COPY", new List<(int, Operand[])>
|
||||
{
|
||||
(move, new[] { new Operand(LocalInt, 0), new Operand(Immediate, 11) }),
|
||||
(move, new[] { new Operand(LocalInt, 1), new Operand(Immediate, 22) }),
|
||||
(move, new[] { new Operand(LocalInt, 2), new Operand(Immediate, 33) }),
|
||||
(0x63, new[] { new Operand(LocalPointer, 0), new Operand(LocalInt, 0) }),
|
||||
(0x63, new[] { new Operand(LocalPointer, 1), new Operand(GlobalInt, 0x700) }),
|
||||
(0x1b0, new[]
|
||||
{
|
||||
new Operand(LocalPointer, 0), new Operand(LocalPointer, 1), new Operand(Immediate, 3),
|
||||
}),
|
||||
|
||||
// A pointer source is aliased to its target, rather than to the pointer slot itself.
|
||||
(lookup, new[]
|
||||
{
|
||||
new Operand(LocalPointer, 2), new Operand(GlobalInt, 0x710), new Operand(Immediate, 2),
|
||||
}),
|
||||
(0x63, new[] { new Operand(LocalPointer, 3), new Operand(LocalPointer, 2) }),
|
||||
(0x1b0, new[]
|
||||
{
|
||||
new Operand(LocalPointer, 3), new Operand(GlobalInt, 0x720), new Operand(Immediate, 2),
|
||||
}),
|
||||
|
||||
// Direct operands resolve as the starts of consecutive cell spans.
|
||||
(0x1b0, new[]
|
||||
{
|
||||
new Operand(GlobalInt, 0x700), new Operand(LocalInt, 10), new Operand(Immediate, 3),
|
||||
}),
|
||||
(move, new[] { new Operand(GlobalInt, 0x730), new Operand(LocalInt, 10) }),
|
||||
(move, new[] { new Operand(GlobalInt, 0x731), new Operand(LocalInt, 11) }),
|
||||
(move, new[] { new Operand(GlobalInt, 0x732), new Operand(LocalInt, 12) }),
|
||||
(0x2, Array.Empty<Operand>()),
|
||||
}, Array.Empty<string>());
|
||||
var vm = new VirtualMachine(script, table, new RecordingHost());
|
||||
vm.Globals[0x712] = 44;
|
||||
vm.Globals[0x713] = 55;
|
||||
|
||||
vm.Run();
|
||||
|
||||
Assert.Equal(new long[] { 11, 22, 33 }, new[]
|
||||
{
|
||||
vm.Globals[0x700], vm.Globals[0x701], vm.Globals[0x702],
|
||||
});
|
||||
Assert.Equal(new long[] { 44, 55 }, new[] { vm.Globals[0x720], vm.Globals[0x721] });
|
||||
Assert.Equal(new long[] { 11, 22, 33 }, new[]
|
||||
{
|
||||
vm.Globals[0x730], vm.Globals[0x731], vm.Globals[0x732],
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TakeAddress_SupportsTheNativeStringPointerDestination()
|
||||
{
|
||||
var table = OpcodeTableJson.Load(Paths.OpcodesJson);
|
||||
int move = table.ByLabel("mov")!.Value;
|
||||
var script = ScriptAssembler.Assemble(table, "STRING_ADDRESS", new List<(int, Operand[])>
|
||||
{
|
||||
(0x63, new[] { new Operand(LocalStringPointer, 0), new Operand(GlobalString, 0x740) }),
|
||||
(move, new[] { new Operand(LocalStringPointer, 0), new Operand(InlineString, 0) }),
|
||||
(0x2, Array.Empty<Operand>()),
|
||||
}, new[] { "aliased" });
|
||||
var vm = new VirtualMachine(script, table, new RecordingHost());
|
||||
vm.GlobalStrings[0x740] = "before";
|
||||
|
||||
vm.Run();
|
||||
|
||||
Assert.Equal("aliased", vm.GlobalStrings[0x740]);
|
||||
}
|
||||
}
|
||||
97
engine/Age.Engine.Tests/StringComparisonOpsTests.cs
Normal file
97
engine/Age.Engine.Tests/StringComparisonOpsTests.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Age.Engine.Model;
|
||||
using Age.Engine.Sys4;
|
||||
using Age.Engine.Vm;
|
||||
using Xunit;
|
||||
|
||||
public class StringComparisonOpsTests
|
||||
{
|
||||
private const int Immediate = 0, InlineString = 2, GlobalInt = 3, GlobalString = 5,
|
||||
GlobalStringPointer = 8, LocalInt = 9, LocalString = 11,
|
||||
LocalStringPointer = 14;
|
||||
|
||||
[Fact]
|
||||
public void StringEquals_HandlesLiteralGlobalLocalAndPointerOperands()
|
||||
{
|
||||
var table = OpcodeTableJson.Load(Paths.OpcodesJson);
|
||||
int lookup = table.ByLabel("lookup-array")!.Value;
|
||||
int move = table.ByLabel("mov")!.Value;
|
||||
var script = ScriptAssembler.Assemble(table, "STRING_EQUALS", new List<(int, Operand[])>
|
||||
{
|
||||
(move, new[] { new Operand(LocalString, 0), new Operand(InlineString, 0) }),
|
||||
(lookup, new[]
|
||||
{
|
||||
new Operand(LocalStringPointer, 0), new Operand(GlobalString, 0x300),
|
||||
new Operand(Immediate, 2),
|
||||
}),
|
||||
(0x194, new[]
|
||||
{
|
||||
new Operand(LocalInt, 0), new Operand(InlineString, 0), new Operand(InlineString, 1),
|
||||
}),
|
||||
(0x194, new[]
|
||||
{
|
||||
new Operand(LocalInt, 1), new Operand(GlobalString, 0x301), new Operand(InlineString, 0),
|
||||
}),
|
||||
(0x194, new[]
|
||||
{
|
||||
new Operand(LocalInt, 2), new Operand(LocalString, 0), new Operand(InlineString, 0),
|
||||
}),
|
||||
(0x194, new[]
|
||||
{
|
||||
new Operand(LocalInt, 3), new Operand(LocalStringPointer, 0), new Operand(InlineString, 0),
|
||||
}),
|
||||
(0x194, new[]
|
||||
{
|
||||
new Operand(LocalInt, 4), new Operand(GlobalStringPointer, 0x400),
|
||||
new Operand(InlineString, 0),
|
||||
}),
|
||||
(move, new[] { new Operand(GlobalInt, 0x500), new Operand(LocalInt, 0) }),
|
||||
(move, new[] { new Operand(GlobalInt, 0x501), new Operand(LocalInt, 1) }),
|
||||
(move, new[] { new Operand(GlobalInt, 0x502), new Operand(LocalInt, 2) }),
|
||||
(move, new[] { new Operand(GlobalInt, 0x503), new Operand(LocalInt, 3) }),
|
||||
(move, new[] { new Operand(GlobalInt, 0x504), new Operand(LocalInt, 4) }),
|
||||
(0x2, Array.Empty<Operand>()),
|
||||
}, new[] { "姫狩り", "姫狩り", "姫狩り違い" });
|
||||
var vm = new VirtualMachine(script, table, new RecordingHost());
|
||||
vm.GlobalStrings[0x301] = "姫狩り違い";
|
||||
vm.GlobalStrings[0x302] = "姫狩り";
|
||||
vm.Globals[0x400] = 0x302;
|
||||
|
||||
vm.Run();
|
||||
|
||||
Assert.Equal(new long[] { 1, 0, 1, 1, 1 }, new[]
|
||||
{
|
||||
vm.Globals[0x500], vm.Globals[0x501], vm.Globals[0x502],
|
||||
vm.Globals[0x503], vm.Globals[0x504],
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StringEquals_DrivesAGameStartShapedConditionalBranch()
|
||||
{
|
||||
var table = OpcodeTableJson.Load(Paths.OpcodesJson);
|
||||
var script = ScriptAssembler.Assemble(table, "GAMESTART_STRING_BRANCH", new List<(int, Operand[])>
|
||||
{
|
||||
// These offsets mirror the release idiom: compare INPUTNAME with a literal, then jcc.
|
||||
(0x194, new[]
|
||||
{
|
||||
new Operand(LocalInt, 0), new Operand(GlobalString, 0x52d), new Operand(InlineString, 0),
|
||||
}),
|
||||
(0xa0, new[]
|
||||
{
|
||||
new Operand(LocalInt, 0), new Operand(Immediate, 19),
|
||||
new Operand(Immediate, unchecked((long)0xffffffff)),
|
||||
}),
|
||||
(0x55, new[] { new Operand(GlobalInt, 0x600), new Operand(Immediate, 99) }),
|
||||
(0x2, Array.Empty<Operand>()),
|
||||
}, new[] { "?" });
|
||||
var vm = new VirtualMachine(script, table, new RecordingHost());
|
||||
vm.GlobalStrings[0x52d] = "?";
|
||||
vm.Globals[0x600] = 1;
|
||||
|
||||
vm.Run();
|
||||
|
||||
Assert.Equal(1, vm.Globals[0x600]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user