1
0
mirror of https://github.com/fadden/6502bench.git synced 2024-06-27 03:29:28 +00:00
6502bench/CommonUtil/AddressMap.cs
Andy McFadden 0bbb307d4e Correct handling of no-op .ORG statements
These were being overlooked because they didn't actually cause
anything to happen (a no-op .ORG sets the address to what it would
already have been).  The assembly source generator works in a way
that causes them to be skipped, so everybody was happy.

This seemed like the sort of thing that was likely to cause problems
down the road, however, so we now split regions correctly when a
no-op .ORG is encountered.  This affects the uncategorized data
analyzer and selection grouping.

This changed the behavior of the 2004-numeric-types test, which was
visibly weird in the UI but generated correct output.

Added the 2024-ui-edge-cases test to provide a place to exercise
edge cases when testing the UI by hand.  It has some value for the
automated regression test, so it's included there.

Also, changed the AddressMapEntry objects to be immutable.  This
is handy when passing lists of them around.
2020-02-28 14:49:18 -08:00

340 lines
13 KiB
C#

/*
* Copyright 2019 faddenSoft
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
namespace CommonUtil {
/// <summary>
/// Map file offsets to 65xx addresses and vice-versa. Useful for sources with
/// multiple ORG directives.
///
/// It's possible to generate code that would overlap once relocated at run time,
/// which means a given address can map to multiple offsets (overlays, bank-switched
/// RAM, etc). For this reason it's useful to know the offset of the referring code
/// when evaluating a reference, so that "local" matches take priority.
/// </summary>
/// <remarks>
/// This was part of the main SourceGen application, but I want to share it with
/// the extension script mechanism.
/// </remarks>
public class AddressMap : IEnumerable<AddressMap.AddressMapEntry> {
public const int NO_ENTRY_ADDR = -1; // address value indicating no entry
/// <summary>
/// Code starting at the specified offset will have the specified address.
///
/// The entries are held in the list in order, sorted by offset, with no gaps.
/// This makes the "length" field redundant, as it can be computed by
/// (entry[N+1].mOffset - entry[N].mOffset), with a special case for the last
/// entry in the list. It's convenient to maintain it explicitly however, as
/// the list is read far more often than it is updated.
///
/// Instances are immutable.
/// </summary>
[Serializable]
public class AddressMapEntry {
public int Offset { get; private set; }
public int Addr { get; private set; }
public int Length { get; private set; }
public AddressMapEntry(int offset, int addr, int len) {
Offset = offset;
Addr = addr;
Length = len;
}
}
/// <summary>
/// Total length, in bytes, spanned by this map.
/// </summary>
private int mTotalLength;
/// <summary>
/// List of definitions, in sorted order.
/// </summary>
private List<AddressMapEntry> mAddrList = new List<AddressMapEntry>();
/// <summary>
/// Constructor.
/// </summary>
/// <param name="length">Total length, in bytes, spanned by this map.</param>
public AddressMap(int length) {
/// There must always be at least one entry, defining the target address
/// for file offset 0. This can be changed, but can't be removed.
mTotalLength = length;
mAddrList.Add(new AddressMapEntry(0, 0, length));
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="entries">List of AddressMapEntry.</param>
public AddressMap(List<AddressMapEntry> entries) {
mTotalLength = entries[entries.Count - 1].Offset + entries[entries.Count - 1].Length;
foreach (AddressMapEntry ent in entries) {
mAddrList.Add(ent);
}
DebugValidate();
}
/// <summary>
/// Returns a copy of the list of entries.
/// </summary>
/// <returns></returns>
public List<AddressMapEntry> GetEntryList() {
List<AddressMapEntry> newList = new List<AddressMapEntry>(mAddrList.Count);
foreach (AddressMapEntry ent in mAddrList) {
newList.Add(ent);
}
return newList;
}
// IEnumerable
public IEnumerator<AddressMapEntry> GetEnumerator() {
return ((IEnumerable<AddressMapEntry>)mAddrList).GetEnumerator();
}
// IEnumerable
IEnumerator IEnumerable.GetEnumerator() {
return ((IEnumerable<AddressMapEntry>)mAddrList).GetEnumerator();
}
/// <summary>
/// Returns the Nth entry in the address map.
/// </summary>
public AddressMapEntry this[int i] {
get { return mAddrList[i]; }
}
/// <summary>
/// Number of entries in the address map.
/// </summary>
public int Count { get { return mAddrList.Count; } }
/// <summary>
/// Returns the Address value of the address map entry associated with the specified
/// offset, or NO_ENTRY_ADDR if there is no address map entry there. The offset must
/// match exactly.
/// </summary>
public int Get(int offset) {
foreach (AddressMapEntry ad in mAddrList) {
if (ad.Offset == offset) {
return ad.Addr;
}
}
return NO_ENTRY_ADDR;
}
/// <summary>
/// Returns the index of the address map entry that contains the given offset.
/// We assume the offset is valid.
/// </summary>
private int IndexForOffset(int offset) {
for (int i = 1; i < mAddrList.Count; i++) {
if (mAddrList[i].Offset > offset) {
return i - 1;
}
}
return mAddrList.Count - 1;
}
/// <summary>
/// Adds, updates, or removes a map entry.
/// </summary>
/// <param name="offset">File offset at which the address changes.</param>
/// <param name="addr">24-bit address.</param>
public void Set(int offset, int addr) {
Debug.Assert(offset >= 0);
if (addr == NO_ENTRY_ADDR) {
if (offset != 0) { // ignore attempts to remove entry at offset zero
Remove(offset);
}
return;
}
Debug.Assert(addr >= 0 && addr < 0x01000000); // 24-bit address space
int i;
for (i = 0; i < mAddrList.Count; i++) {
AddressMapEntry ad = mAddrList[i];
if (ad.Offset == offset) {
// update existing
mAddrList[i] = new AddressMapEntry(ad.Offset, addr, ad.Length);
return;
} else if (ad.Offset > offset) {
// The i'th entry is one past the interesting part.
break;
}
}
// Carve a chunk out of the previous entry.
AddressMapEntry prev = mAddrList[i - 1];
int prevOldLen = prev.Length;
int prevNewLen = offset - prev.Offset;
mAddrList[i - 1] = new AddressMapEntry(prev.Offset, prev.Addr, prevNewLen);
mAddrList.Insert(i,
new AddressMapEntry(offset, addr, prevOldLen - prevNewLen));
DebugValidate();
}
/// <summary>
/// Removes an entry from the set.
/// </summary>
/// <param name="offset">The initial offset of the mapping to remove. This
/// must be the initial value, not a mid-range value.</param>
/// <returns>True if something was removed.</returns>
public bool Remove(int offset) {
if (offset == 0) {
throw new Exception("Not allowed to remove entry 0");
}
for (int i = 1; i < mAddrList.Count; i++) {
if (mAddrList[i].Offset == offset) {
// Add the length to the previous entry.
AddressMapEntry prev = mAddrList[i - 1];
mAddrList[i - 1] = new AddressMapEntry(prev.Offset, prev.Addr,
prev.Length + mAddrList[i].Length);
mAddrList.RemoveAt(i);
DebugValidate();
return true;
}
}
return false;
}
/// <summary>
/// Returns true if the given address falls into the range spanned by the
/// address map entry.
/// </summary>
/// <param name="index">Address map entry index.</param>
/// <param name="addr">Address to check.</param>
/// <returns></returns>
private bool IndexContainsAddress(int index, int addr) {
return addr >= mAddrList[index].Addr &&
addr < mAddrList[index].Addr + mAddrList[index].Length;
}
/// <summary>
/// Determines the file offset that best contains the specified target address.
/// </summary>
/// <param name="srcOffset">Offset of the address reference.</param>
/// <param name="targetAddr">Address to look up.</param>
/// <returns>The file offset, or -1 if the address falls outside the file.</returns>
public int AddressToOffset(int srcOffset, int targetAddr) {
if (mAddrList.Count == 1) {
// Trivial case.
if (IndexContainsAddress(0, targetAddr)) {
Debug.Assert(targetAddr >= mAddrList[0].Addr);
return targetAddr - mAddrList[0].Addr;
} else {
return -1;
}
}
// We have multiple, potentially overlapping address ranges. Start by
// looking for a match in the srcOffset range; if that fails, scan
// forward from the start.
int srcOffIndex = IndexForOffset(srcOffset);
if (IndexContainsAddress(srcOffIndex, targetAddr)) {
Debug.Assert(targetAddr >= mAddrList[srcOffIndex].Addr);
return (targetAddr - mAddrList[srcOffIndex].Addr) + mAddrList[srcOffIndex].Offset;
}
for (int i = 0; i < mAddrList.Count; i++) {
if (i == srcOffIndex) {
// optimization -- we already checked this one
continue;
}
if (IndexContainsAddress(i, targetAddr)) {
Debug.Assert(targetAddr >= mAddrList[i].Addr);
return (targetAddr - mAddrList[i].Addr) + mAddrList[i].Offset;
}
}
return -1;
}
/// <summary>
/// Converts a file offset to an address.
/// </summary>
/// <param name="offset">File offset.</param>
/// <returns>24-bit address.</returns>
public int OffsetToAddress(int offset) {
int srcOffIndex = IndexForOffset(offset);
return mAddrList[srcOffIndex].Addr + (offset - mAddrList[srcOffIndex].Offset);
}
/// <summary>
/// Checks to see if the specified range of offsets is in a single address range. Use
/// this to see if something crosses an address-change boundary. This does not
/// handle no-op address changes specially.
/// </summary>
/// <param name="offset">Start offset.</param>
/// <param name="length">Length of region.</param>
/// <returns>True if the data area is unbroken.</returns>
public bool IsSingleAddrRange(int offset, int length) {
Debug.Assert(offset >= 0 && offset < mTotalLength);
Debug.Assert(length > 0 && offset + length <= mTotalLength);
return (IndexForOffset(offset) == IndexForOffset(offset + length - 1));
}
/// <summary>
/// Internal consistency checks.
/// </summary>
private void DebugValidate() {
if (mAddrList.Count < 1) {
throw new Exception("AddressMap: empty");
}
if (mAddrList[0].Offset != 0) {
throw new Exception("AddressMap: bad offset 0");
}
if (mAddrList.Count == 1) {
if (mAddrList[0].Length != mTotalLength) {
throw new Exception("AddressMap: single entry len bad");
}
} else {
int totalLen = 0;
for (int i = 0; i < mAddrList.Count; i++) {
AddressMapEntry ent = mAddrList[i];
if (i != 0) {
if (ent.Offset != mAddrList[i - 1].Offset + mAddrList[i - 1].Length) {
throw new Exception("Bad offset step to " + i);
}
}
totalLen += ent.Length;
}
if (totalLen != mTotalLength) {
throw new Exception("AddressMap: bad length sum (" + totalLen + " vs " +
mTotalLength + ")");
}
}
}
public override string ToString() {
return "[AddressMap: " + mAddrList.Count + " entries]";
}
}
}