From 06fa1bedac6be63815e710b23619c78ff4b1db50 Mon Sep 17 00:00:00 2001 From: Lucas Scharenbroich Date: Thu, 8 Dec 2016 23:15:59 -0600 Subject: [PATCH] Add support for masks --- SpriteCompiler.Test/Tests.cs | 56 +++++++- SpriteCompiler/Problem/CodeSequence.cs | 66 ++++++++++ SpriteCompiler/Problem/SpriteByte.cs | 30 +++++ .../Problem/SpriteGeneratorState.cs | 11 +- .../SpriteGeneratorSuccessorFunction.cs | 121 ++++++++++++++---- SpriteCompiler/Program.cs | 17 ++- 6 files changed, 266 insertions(+), 35 deletions(-) diff --git a/SpriteCompiler.Test/Tests.cs b/SpriteCompiler.Test/Tests.cs index f7feb96..2c83f4e 100644 --- a/SpriteCompiler.Test/Tests.cs +++ b/SpriteCompiler.Test/Tests.cs @@ -19,7 +19,7 @@ namespace SpriteCompiler.Test var search = SpriteGeneratorSearchProblem.Create(); // Act : solve the problem - var solution = search.Search(problem, new SpriteGeneratorState()); + var solution = search.Search(problem, SpriteGeneratorState.Init(Enumerable.Empty())); // Assert : The initial state IS the goal state Assert.AreEqual(1, solution.Count()); @@ -105,6 +105,60 @@ namespace SpriteCompiler.Test Assert.AreEqual(18, (int)solution.Last().PathCost); } + [TestMethod] + public void TestSingleByteWithMask() + { + // Arrange + var problem = SpriteGeneratorSearchProblem.CreateSearchProblem(); + var search = SpriteGeneratorSearchProblem.Create(); + + // Act : solve the problem + var data = new byte[] { 0x00, 0x11 }; + var mask = new byte[] { 0xFF, 0x00 }; + + var solution = search.Search(problem, SpriteGeneratorState.Init(data, mask)); + + // Assert + // + // Solution should be same as regular single-byte sprite, except + // the store happens at offset 1, instead of 0. + + // Write out the solution + WriteOutSolution(solution); + + Assert.AreEqual(5, solution.Count()); + Assert.AreEqual(14, (int)solution.Last().PathCost); + } + + [TestMethod] + public void TestSinglePixelMask() + { + // Arrange + var problem = SpriteGeneratorSearchProblem.CreateSearchProblem(); + var search = SpriteGeneratorSearchProblem.Create(); + + // Act : solve the problem + var data = new byte[] { 0x01, 0x11 }; + var mask = new byte[] { 0xF0, 0x00 }; + + var solution = search.Search(problem, SpriteGeneratorState.Init(data, mask)); + + // Assert + // + // Solution should be a single 16-bit RMW + // + // TCS + // LDA 0,s + // AND #$00F0 + // ORA #$1101 + // STA 0,s = 18 cycles + + // Write out the solution + WriteOutSolution(solution); + + Assert.AreEqual(18, (int)solution.Last().PathCost); + } + [TestMethod] public void TestConsecutiveWordSprite() { diff --git a/SpriteCompiler/Problem/CodeSequence.cs b/SpriteCompiler/Problem/CodeSequence.cs index 873ecb2..14875cc 100644 --- a/SpriteCompiler/Problem/CodeSequence.cs +++ b/SpriteCompiler/Problem/CodeSequence.cs @@ -142,6 +142,39 @@ namespace SpriteCompiler.Problem } } + public sealed class STACK_REL_8_BIT_READ_MODIFY_WRITE : CodeSequence + { + private readonly byte value; + private readonly byte mask; + private readonly byte offset; + + public STACK_REL_8_BIT_READ_MODIFY_WRITE(byte value, byte mask, byte offset) : base(12) { this.value = value; this.mask = mask; this.offset = offset; } + + public override SpriteGeneratorState Apply(SpriteGeneratorState state) + { + return state.Clone(_ => + { + _.A = Register.UNINITIALIZED; + _.RemoveByte((ushort)(offset + _.S.Value)); + }); + } + + public override string ToString() + { + return "LDA " + offset.ToString("X2") + ",s / AND #$" + mask.ToString("X2") + " / ORA #$" + value.ToString("X2") + " / STA " + offset.ToString("X2") + ",s"; + } + + public override string Emit() + { + return String.Join("\n", + FormatLine("", "LDA", offset.ToString("X2") + ",s", "4 cycles"), + FormatLine("", "AND", "#$" + mask.ToString("X2"), "2 cycles"), + FormatLine("", "ORA", "#$" + value.ToString("X2"), "2 cycles"), + FormatLine("", "STA", offset.ToString("X2") + ",s", "4 cycles") + ); + } + } + public sealed class STACK_REL_16_BIT_STORE : CodeSequence { private readonly ushort value; @@ -168,6 +201,39 @@ namespace SpriteCompiler.Problem } } + public sealed class STACK_REL_16_BIT_READ_MODIFY_WRITE : CodeSequence + { + private readonly ushort value; + private readonly ushort mask; + private readonly byte offset; + + public STACK_REL_16_BIT_READ_MODIFY_WRITE(ushort value, ushort mask, byte offset) : base(16) { this.value = value; this.mask = mask; this.offset = offset; } + + public override SpriteGeneratorState Apply(SpriteGeneratorState state) + { + return state.Clone(_ => + { + _.A = Register.UNINITIALIZED; + _.RemoveWord((ushort)(offset + _.S.Value)); + }); + } + + public override string ToString() + { + return "LDA " + offset.ToString("X2") + ",s / AND #$" + mask.ToString("X4") + " / ORA #$" + value.ToString("X4") + " / STA " + offset.ToString("X2") + ",s"; + } + + public override string Emit() + { + return String.Join("\n", + FormatLine("", "LDA", offset.ToString("X2") + ",s", "5 cycles"), + FormatLine("", "AND", "#$" + mask.ToString("X4"), "3 cycles"), + FormatLine("", "ORA", "#$" + value.ToString("X4"), "3 cycles"), + FormatLine("", "STA", offset.ToString("X2") + ",s", "5 cycles") + ); + } + } + public sealed class STACK_REL_16_BIT_IMMEDIATE_STORE : CodeSequence { private readonly ushort value; diff --git a/SpriteCompiler/Problem/SpriteByte.cs b/SpriteCompiler/Problem/SpriteByte.cs index 1e6abdb..ffc600f 100644 --- a/SpriteCompiler/Problem/SpriteByte.cs +++ b/SpriteCompiler/Problem/SpriteByte.cs @@ -30,4 +30,34 @@ namespace SpriteCompiler.Problem public byte Mask { get; private set; } public ushort Offset { get; private set; } } + + public struct SpriteWord + { + private static ushort DataToMask(ushort data) + { + return (ushort)( + (((data & 0xF000) == 0x00) ? 0xF000 : 0x0000) | + (((data & 0x0F00) == 0x00) ? 0x0F00 : 0x0000) | + (((data & 0x00F0) == 0x00) ? 0x00F0 : 0x0000) | + (((data & 0x000F) == 0x00) ? 0x000F : 0x0000) + ); + } + + public SpriteWord(ushort data, ushort offset) + : this(data, DataToMask(data), offset) + { + } + + public SpriteWord(ushort data, ushort mask, ushort offset) + : this() + { + Data = data; + Mask = mask; + Offset = offset; + } + + public ushort Data { get; private set; } + public ushort Mask { get; private set; } + public ushort Offset { get; private set; } + } } diff --git a/SpriteCompiler/Problem/SpriteGeneratorState.cs b/SpriteCompiler/Problem/SpriteGeneratorState.cs index caf4eb8..18cf727 100644 --- a/SpriteCompiler/Problem/SpriteGeneratorState.cs +++ b/SpriteCompiler/Problem/SpriteGeneratorState.cs @@ -18,9 +18,16 @@ return new SpriteGeneratorState(); } - public static SpriteGeneratorState Init(IEnumerable bytes) + public static SpriteGeneratorState Init(IEnumerable data) { - return Init(bytes.Select((x, i) => new SpriteByte(x, (ushort)i))); + return Init(data.Select((x, i) => new SpriteByte(x, (ushort)i))); + } + + public static SpriteGeneratorState Init(IEnumerable data, IEnumerable mask) + { + return Init(data.Zip(mask, (x, y) => new { Data = x, Mask = y }) + .Select((_, i) => new SpriteByte(_.Data, _.Mask, (ushort)i)) + .Where(_ => _.Mask != 0xFF)); } public SpriteGeneratorState() diff --git a/SpriteCompiler/Problem/SpriteGeneratorSuccessorFunction.cs b/SpriteCompiler/Problem/SpriteGeneratorSuccessorFunction.cs index eeb26d5..108c05e 100644 --- a/SpriteCompiler/Problem/SpriteGeneratorSuccessorFunction.cs +++ b/SpriteCompiler/Problem/SpriteGeneratorSuccessorFunction.cs @@ -7,29 +7,61 @@ public static class StateHelpers { - public static byte? TryGetStackByte(this SpriteGeneratorState state, IDictionary data) + public static SpriteByte? TryGetStackByte(this SpriteGeneratorState state, IDictionary data) { SpriteByte top; if (state.S.IsScreenOffset && data.TryGetValue((ushort)state.S.Value, out top)) { - return top.Data; + return top; } return null; } - public static ushort? TryGetStackWord(this SpriteGeneratorState state, IDictionary data) + public static SpriteWord? TryGetStackWord(this SpriteGeneratorState state, IDictionary data) { return TryGetStackWord(state, data, 0); } - public static ushort? TryGetStackWord(this SpriteGeneratorState state, IDictionary data, int offset) + public static SpriteWord? TryGetStackWord(this SpriteGeneratorState state, IDictionary data, int offset) { + // When we get a word, it's permissible for either the high or low byte to not exist, but not both SpriteByte high; SpriteByte low; - if (state.S.IsScreenOffset && (state.S.Value + offset) > 0 && data.TryGetValue((ushort)(state.S.Value + offset), out high) && data.TryGetValue((ushort)(state.S.Value + offset - 1), out low)) + + // Make sure the range is within bounds + if (state.S.IsScreenOffset && (state.S.Value + offset) > 0) { - return (ushort)(low.Data + (high.Data << 8)); + // Try to get the high byte + byte high_data = 0x00; + byte high_mask = 0xFF; + ushort high_offset = (ushort)(state.S.Value + offset); + + if (data.TryGetValue(high_offset, out high)) + { + high_data = high.Data; + high_mask = high.Mask; + } + + // Try to get the low byte + byte low_data = 0x00; + byte low_mask = 0xFF; + ushort low_offset = (ushort)(state.S.Value + offset - 1); + + if (data.TryGetValue(low_offset, out low)) + { + low_data = low.Data; + low_mask = low.Mask; + } + + // At least some data need to be visible + if (high_mask != 0xFF || low_mask != 0xFF) + { + var word_data = (ushort)(low_data + (high_data << 8)); + var word_mask = (ushort)(low_mask + (high_mask << 8)); + + return new SpriteWord(word_data, word_mask, low_offset); + } } return null; @@ -74,45 +106,62 @@ var topWord = state.TryGetStackWord(bytes); var nextWord = state.TryGetStackWord(bytes, -2); // Also get the next value below the current word - // We can always perform a PEA regardless of the register widths + // If there is some data at the top of the stack, see what we can do if (topWord.HasValue) { - yield return state.Apply(new PEA(topWord.Value)); - - // If any of the registers happen to match the value, we can do an optimized PHA/X/Y/D operations. Any one - // PHx is as good as another and cannot affect the state, so just pick the first one. - if (state.LongA) + // First, the simple case -- the data has no mask + if (topWord.Value.Mask == 0x000) { - if (state.A.IsLiteral && state.A.Value == topWord.Value) + // If any of the registers has the exact value we need, then it is always fastest to just push the value + if (state.LongA) { - yield return state.Apply(new PHA()); + if (state.A.IsLiteral && state.A.Value == topWord.Value.Data) + { + yield return state.Apply(new PHA()); + } + else + { + yield return state.Apply(new PEA(topWord.Value.Data)); + yield return state.Apply(new LOAD_16_BIT_IMMEDIATE_AND_PUSH(topWord.Value.Data)); + } } + // Otherwise, the only alternative is a PEA instruction else { - yield return state.Apply(new LOAD_16_BIT_IMMEDIATE_AND_PUSH(topWord.Value)); + yield return state.Apply(new PEA(topWord.Value.Data)); } - - //else if (state.X.IsLiteral && state.X.Value == topWord.Value) { } - //else if (state.Y.IsLiteral && state.Y.Value == topWord.Value) { } - //else if (state.D.IsLiteral && state.D.Value == topWord.Value) { } - - // If the top two workd match, it might be worthwhile to load the accumulator to start immediate PHAs } } // If there is a valid byte, then we can look for an 8-bit push, or an immediate mode LDA #XX/STA 0,s if (topByte.HasValue) { - if (!state.LongA) + if (topByte.Value.Mask == 0x00) { - yield return state.Apply(new STACK_REL_8_BIT_IMMEDIATE_STORE(topByte.Value, 0)); + if (!state.LongA) + { + yield return state.Apply(new STACK_REL_8_BIT_IMMEDIATE_STORE(topByte.Value.Data, 0)); + } } } // If the accumulator holds an offset then we could move to any byte position, but it is only beneficial to + // either + // + // 1. Set the stack to the current accumulator value + // 2. Set the stack to the start of a contiguous segment + // 3. Set the stack to the end of a contiguous segment + // move to the first or last byte of each span. So , take the first byte and then look for any if (state.A.IsScreenOffset && !state.S.IsScreenOffset && state.LongA) { + // If any of the open bytes are within 255 bytes of the accumulator, consider just + // setting the stack to the accumulator value + if (open.Any(x => (x.Offset - state.A.Value) >= 0 && (x.Offset - state.A.Value) < 256)) + { + yield return state.Apply(new MOVE_STACK(0)); + } + for (var i = 0; i < open.Count; i++) { if (i == 0) @@ -155,14 +204,25 @@ { var offset = (byte)(word.Low.Offset - addr); var data = (ushort)(word.Low.Data + (word.High.Data << 8)); + var mask = (ushort)(word.Low.Mask + (word.High.Mask << 8)); - if (data == state.A.Value) + // Easy case when mask is empty + if (mask == 0x0000) { - yield return state.Apply(new STACK_REL_16_BIT_STORE(data, offset)); + if (data == state.A.Value) + { + yield return state.Apply(new STACK_REL_16_BIT_STORE(data, offset)); + } + else + { + yield return state.Apply(new STACK_REL_16_BIT_IMMEDIATE_STORE(data, offset)); + } } + + // Otherwise there is really only one choice LDA / AND / ORA / STA sequence else { - yield return state.Apply(new STACK_REL_16_BIT_IMMEDIATE_STORE(data, offset)); + yield return state.Apply(new STACK_REL_16_BIT_READ_MODIFY_WRITE(data, mask, offset)); } } } @@ -180,7 +240,14 @@ foreach (var datum in open.Where(WithinRangeOf(addr, 256))) { var offset = datum.Offset - addr; - yield return state.Apply(new STACK_REL_8_BIT_IMMEDIATE_STORE(datum.Data, (byte)offset)); + if (datum.Mask == 0x00) + { + yield return state.Apply(new STACK_REL_8_BIT_IMMEDIATE_STORE(datum.Data, (byte)offset)); + } + else + { + yield return state.Apply(new STACK_REL_8_BIT_READ_MODIFY_WRITE(datum.Data, datum.Mask, (byte)offset)); + } } } } diff --git a/SpriteCompiler/Program.cs b/SpriteCompiler/Program.cs index cd9baf5..74fb8d4 100644 --- a/SpriteCompiler/Program.cs +++ b/SpriteCompiler/Program.cs @@ -57,7 +57,7 @@ .Callback(_ => data = _.Select(s => Convert.ToByte(s, 16)).ToList()); p.Setup>('m', "mask") - .Callback(_ => data = _.Select(s => Convert.ToByte(s, 16)).ToList()); + .Callback(_ => mask = _.Select(s => Convert.ToByte(s, 16)).ToList()); p.Setup('i', "image") .Callback(_ => filename = _); @@ -155,10 +155,17 @@ // Set the global state var problem = SpriteGeneratorSearchProblem.CreateSearchProblem(); var search = SpriteGeneratorSearchProblem.Create(); - - var solution = search.Search(problem, SpriteGeneratorState.Init(data)); - - WriteOutSolution(solution); + + if (data.Count == mask.Count) + { + var solution = search.Search(problem, SpriteGeneratorState.Init(data, mask)); + WriteOutSolution(solution); + } + else + { + var solution = search.Search(problem, SpriteGeneratorState.Init(data)); + WriteOutSolution(solution); + } } } }