/* * 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 { /// /// 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 could map to multiple offsets. For this reason /// it's useful to know the offset of the referring code when evaluating a /// reference, so that a "local" match can take priority. /// /// /// This was part of the main SourceGen application, but I want to share it with /// script extensions. /// public class AddressMap : IEnumerable { public const int NO_ENTRY_ADDR = -1; // address value indicating no entry /// /// 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. /// /// Entries are mutable, but must only be altered by AddressMap. Don't retain /// instances of this across other activity. /// /// /// TODO: make this immutable. That should allow us to eliminate the copy constructor, /// since we won't need to make copies of things. /// [Serializable] public class AddressMapEntry { public int Offset { get; set; } public int Addr { get; set; } public int Length { get; set; } public AddressMapEntry(int offset, int addr, int len) { Offset = offset; Addr = addr; Length = len; } // Copy constructor. public AddressMapEntry(AddressMapEntry src) { Offset = src.Offset; Addr = src.Addr; Length = src.Length; } } /// /// Total length, in bytes, spanned by this map. /// private int mTotalLength; /// /// List of definitions, in sorted order. /// private List mAddrList = new List(); /// /// Constructor. /// /// Total length, in bytes, spanned by this map. 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)); } /// /// Constructor. /// /// List of AddressMapEntry. public AddressMap(List entries) { mTotalLength = entries[entries.Count - 1].Offset + entries[entries.Count - 1].Length; foreach (AddressMapEntry ent in entries) { mAddrList.Add(new AddressMapEntry(ent)); } DebugValidate(); } /// /// Returns a copy of the list of entries. /// /// public List GetEntryList() { List newList = new List(mAddrList.Count); foreach (AddressMapEntry ent in mAddrList) { newList.Add(new AddressMapEntry(ent)); } return newList; } // IEnumerable public IEnumerator GetEnumerator() { return ((IEnumerable)mAddrList).GetEnumerator(); } // IEnumerable IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)mAddrList).GetEnumerator(); } /// /// Returns the Nth entry in the address map. /// public AddressMapEntry this[int i] { get { return mAddrList[i]; } } /// /// Number of entries in the address map. /// public int Count { get { return mAddrList.Count; } } /// /// 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. /// public int Get(int offset) { foreach (AddressMapEntry ad in mAddrList) { if (ad.Offset == offset) { return ad.Addr; } } return NO_ENTRY_ADDR; } /// /// Returns the index of the address map entry that contains the given offset. /// We assume the offset is valid. /// 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; } /// /// Adds, updates, or removes a map entry. /// /// File offset at which the address changes. /// 24-bit address. 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 ad.Addr = addr; mAddrList[i] = ad; 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; prev.Length = prevNewLen; mAddrList[i - 1] = prev; mAddrList.Insert(i, new AddressMapEntry(offset, addr, prevOldLen - prevNewLen)); DebugValidate(); } /// /// Removes an entry from the set. /// /// The initial offset of the mapping to remove. This /// must be the initial value, not a mid-range value. /// True if something was removed. 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]; prev.Length += mAddrList[i].Length; mAddrList[i - 1] = prev; mAddrList.RemoveAt(i); DebugValidate(); return true; } } return false; } /// /// Returns true if the given address falls into the range spanned by the /// address map entry. /// /// Address map entry index. /// Address to check. /// private bool IndexContainsAddress(int index, int addr) { return addr >= mAddrList[index].Addr && addr < mAddrList[index].Addr + mAddrList[index].Length; } /// /// Determines the file offset that best contains the specified target address. /// /// Offset of the address reference. /// Address to look up. /// The file offset, or -1 if the address falls outside the file. 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; } /// /// Converts a file offset to an address. /// /// File offset. /// 24-bit address. public int OffsetToAddress(int offset) { int srcOffIndex = IndexForOffset(offset); return mAddrList[srcOffIndex].Addr + (offset - mAddrList[srcOffIndex].Offset); } /// /// Checks to see if the specified range of offsets is in a contiguous range of /// addresses. Use this to see if something crosses an address-change boundary. /// /// Start offset. /// Length of region. /// True if the data area is unbroken. public bool IsContiguous(int offset, int length) { Debug.Assert(offset >= 0 && offset < mTotalLength); Debug.Assert(length > 0 && offset + length <= mTotalLength); return (IndexForOffset(offset) == IndexForOffset(offset + length - 1)); } /// /// Internal consistency checks. /// 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]"; } } }