public IEnumerable <Tuple <CodeSequence, SpriteGeneratorState> > Successors(SpriteGeneratorState state) { if (state.LongA || !state.S.IsScreenOffset) { yield break; } var openList = state.RemainingBytes(); // Get the first byte with a non-zero mask that is within 255 bytes of the current location var nextByte = openList.Where(sb => (sb.Offset - state.S.Value) <= 255).Cast <SpriteByte?>().FirstOrDefault(); if (nextByte != null) { yield return(state.Apply(new MOVE_STACK(nextByte.Value.Offset - state.S.Value))); } // Check for the next run of solid bytes and move to the end var run = StateHelpers.FirstSolidRun(openList); if (run != null) { yield return(state.Apply(new MOVE_STACK(run.Last.Offset - state.S.Value))); } yield break; }
public IEnumerable <Tuple <CodeSequence, SpriteGeneratorState> > Successors(SpriteGeneratorState state) { // Get the list of remaining bytes by removing the closed list from the global sprite dataset var open = state.RemainingBytes(); // If the open list is empty, there can be only one reason -- we're in 8-bit // mode. if (!open.Any()) { yield return(state.Apply(new LONG_M())); yield break; } // Get the first byte -- if we are expanding a state we can safely assume there // is still data to expand. var firstByte = open.First(); // Identify the first run of solid bytes var firstSolid = StateHelpers.FirstSolidRun(open); // In an initial state, the stack has not been set, but the accumulator contains a screen // offset address. There are three possible options // // 1. Set the stack to the accumulator value. This is optimal when there are very few // data bytes to set and the overhead of moving the stack negates any benefit from // using stack push instructions // // 2. Set the stack to the first offset of the open set. This can be optimal if there are a few // bytes and moving the stack forward a bit can allow the code to reach them without needing // a second stack adjustment. // // 3. Set the stack to the first, right-most offset that ends a sequence of solid bytes if (!state.S.IsScreenOffset && state.A.IsScreenOffset && state.LongA) { // If the first byte is within 255 bytes of the accumulator, propose setting // the stack to the accumulator value, which is the fastest var delta = firstByte.Offset - state.A.Value; if (delta >= 0 && delta < 256) { yield return(state.Apply(new MOVE_STACK(0))); } // If the first byte offset is not equal to the accumulator value, propose that if (delta > 0) { yield return(state.Apply(new MOVE_STACK(delta))); } // Find the first edge of a solid run....TODO if (firstSolid != null && firstSolid.Count >= 2) { yield return(state.Apply(new MOVE_STACK(firstSolid.Last.Offset - state.A.Value))); } yield break; } // If the first byte is 256 bytes or more ahead of the current stack location, // then we need to advance var firstByteDistance = firstByte.Offset - state.S.Value; if (state.S.IsScreenOffset && firstByteDistance >= 256 && state.LongA) { // If the accumulator is not set to the stack if (!state.A.IsScreenOffset) { yield return(state.Apply(new TSC())); } // Go to the next byte, or the first solid edge else { yield return(state.Apply(new MOVE_STACK(firstByteDistance))); } yield break; } var bytes = open.ToDictionary(x => x.Offset, x => x); // Get the current byte and current word that exist at the current stack location var topByte = state.TryGetStackByte(bytes); var topWord = state.TryGetStackWord(bytes); // If the top of the stack is a solid work, we can always emit a PEA regardless of 8/16-bit mode if (topWord.HasValue && topWord.Value.Mask == 0x000) { yield return(state.Apply(new PEA(topWord.Value.Data))); } // First set of operations for when the accumulator is in 8-bit mode. We are basically limited // to either // // 1. Switching to 16-bit mode // 3. Using a PHA to push an 8-bit solid or masked value on the stack // 4. Using a STA 0,s to store an 8-bit solid or masked value on the stack if (!state.LongA) { // The byte to be stored at the top of the stack has some special methods available if (topByte.HasValue) { var datum = topByte.Value; // Solid byte if (datum.Mask == 0x00) { if (state.A.IsLiteral && ((state.A.Value & 0xFF) == datum.Data)) { yield return(state.Apply(new PHA_8())); } else { yield return(state.Apply(new LOAD_8_BIT_IMMEDIATE_AND_PUSH(datum.Data))); yield return(state.Apply(new STACK_REL_8_BIT_IMMEDIATE_STORE(datum.Data, 0))); } } // Masked byte else { yield return(state.Apply(new STACK_REL_8_BIT_READ_MODIFY_PUSH(datum.Data, datum.Mask))); } } // Otherwise, just store the next byte closest to the stack if (state.S.IsScreenOffset) { var addr = state.S.Value; // We can LDA #$XX / STA X,s for any values within 256 bytes of the current address foreach (var datum in open.Where(WithinRangeOf(addr, 256))) { var offset = (byte)(datum.Offset - addr); // Easy case when mask is empty if (datum.Mask == 0x00) { if (datum.Data == (state.A.Value & 0xFF)) { yield return(state.Apply(new STACK_REL_8_BIT_STORE(offset))); } else { yield return(state.Apply(new STACK_REL_8_BIT_IMMEDIATE_STORE(datum.Data, offset))); } } // Otherwise there is really only one choice LDA / AND / ORA / STA sequence else { yield return(state.Apply(new STACK_REL_8_BIT_READ_MODIFY_WRITE(datum.Data, datum.Mask, offset))); } } } if (state.AllowModeChange) { yield return(state.Apply(new LONG_M())); } } // Now consider what can be done when the accumulator is 16-bit. All of the stack // manipulation happens here, too. if (state.LongA) { // Handle the special case of a value sitting right on top of the stack if (topWord.HasValue) { var datum = topWord.Value; // First, the simple case -- the data has no mask if (datum.Mask == 0x000) { if (state.A.IsLiteral && state.A.Value == datum.Data) { yield return(state.Apply(new PHA_16())); } else { // Only consider this if the value appear in the sprite more than once if (SpriteGeneratorState.DATASET_SOLID_WORDS[topWord.Value.Data] > 1) { yield return(state.Apply(new LOAD_16_BIT_IMMEDIATE_AND_PUSH(topWord.Value.Data))); } } } } // If the stack is set, find the next word to store (just one to reduce branching factor) if (state.S.IsScreenOffset) { var addr = state.S.Value; var local = open.Where(WithinRangeOf(addr, 256)).ToList(); var words = local .Where(x => SpriteGeneratorState.DATASET_BY_OFFSET.ContainsKey(x.Offset + 1)) .Select(x => new { Low = x, High = SpriteGeneratorState.DATASET_BY_OFFSET[x.Offset + 1] }) .ToList(); foreach (var word in words) { 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)); // Easy case when mask is empty if (mask == 0x0000) { if (data == state.A.Value) { yield return(state.Apply(new STACK_REL_16_BIT_STORE(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_READ_MODIFY_WRITE(data, mask, offset))); } } } if (state.AllowModeChange) { yield return(state.Apply(new SHORT_M())); } } }