/* * 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 SourceGen { /// /// List of all symbols, arranged primarily by label, but also accessible by value. All /// symbols have a unique label. /// public class SymbolTable : IEnumerable { /// /// Primary storage. Provides fast lookup by label. The StringComparer we choose /// determines how case sensitivity and Culture is handled. private SortedList mSymbols = new SortedList(Asm65.Label.LABEL_COMPARER); /// /// By-address lookup table. Because symbols can span more than one byte, there may /// be more than one entry per symbol here. If two symbols cover the same address, /// only the highest-priority symbol is kept, so not all symbols are represented here. /// /// This does not contain constants or local variables. /// /// /// For efficiency on larger data files, we may want to break this up by bank. That /// way we can do a partial update. /// private Dictionary mSymbolsByAddress = new Dictionary(); /// /// This is incremented whenever the contents of the symbol table change. External /// code can compare this against a previous value to see if anything has changed /// since the last visit. /// /// We could theoretically miss something at the 2^32 rollover. Not worried. /// //public int ChangeSerial { get; private set; } public SymbolTable() { } // IEnumerable public IEnumerator GetEnumerator() { // .Values is documented as O(1) return mSymbols.Values.GetEnumerator(); } // IEnumerable IEnumerator IEnumerable.GetEnumerator() { return mSymbols.Values.GetEnumerator(); } /// /// Clears the symbol table. /// public void Clear() { mSymbols.Clear(); mSymbolsByAddress.Clear(); //ChangeSerial++; } /// /// Returns the number of symbols in the table. /// public int Count() { return mSymbols.Count; } /// /// Adds the specified symbol to the list. Throws an exception if the symbol is /// already present. /// public void Add(Symbol sym) { // If Symbol with matching label is in list, this will throw an exception, // and the by-value add won't happen. mSymbols.Add(sym.Label, sym); AddAddressTableEntry(sym); //ChangeSerial++; } /// /// get: Finds the specified symbol by label. Throws an exception if it's not found. /// /// set: Adds the specified symbol to the list, or replaces it if it's already present. /// public Symbol this[string key] { get { return mSymbols[key]; } set { mSymbols.TryGetValue(key, out Symbol oldValue); mSymbols[key] = value; if (oldValue != null) { ReplaceAddressTableEntry(oldValue, value); } else { AddAddressTableEntry(value); } //ChangeSerial++; } } /// /// Gets the value associated with the key. /// /// Label to look up. /// Symbol, or null if not found. /// True if the key is present, false otherwise. public bool TryGetValue(string key, out Symbol sym) { return mSymbols.TryGetValue(key, out sym); } /// /// Gets the value associated with the key, unless it's a variable. /// /// Label to look up. /// Symbol, or null if not found, or found but it's a variable. /// True if the key is present, false otherwise. public bool TryGetNonVariableValue(string key, out Symbol sym) { bool found = mSymbols.TryGetValue(key, out sym); if (found && sym.IsVariable) { sym = null; found = false; } return found; } /// /// Removes the specified symbol. /// public void Remove(Symbol sym) { mSymbols.Remove(sym.Label); RemoveAddressTableEntry(sym); //ChangeSerial++; } /// /// Adds a symbol to the address table. All affected addresses are updated. If an /// existing symbol is already present at an address, the new or old symbol will be /// selected in priority order. /// /// Symbol to add. private void AddAddressTableEntry(Symbol sym) { if (sym.SymbolType == Symbol.Type.Constant) { return; } if (sym.SymbolSource == Symbol.Source.Variable) { return; } int width = 1; if (sym is DefSymbol) { width = ((DefSymbol)sym).DataDescriptor.Length; } // we could restore some older behavior by giving user labels a width of 3, but // we'd have to make sure that they didn't win for addresses outside the file for (int i = 0; i < width; i++) { // See if there's already something here. If we reach the end of the // bank, wrap around. int addr = (sym.Value & 0xff0000) + ((sym.Value + i) & 0xffff); mSymbolsByAddress.TryGetValue(addr, out Symbol curSym); mSymbolsByAddress[addr] = (curSym == null) ? sym : HighestPriority(sym, curSym); } } private Symbol HighestPriority(Symbol sym1, Symbol sym2) { // First determinant is symbol source. User labels have highest priority, then // project symbols, then platform symbols, then auto labels. if ((int)sym1.SymbolSource < (int)sym2.SymbolSource) { return sym1; } else if ((int)sym1.SymbolSource > (int)sym2.SymbolSource) { return sym2; } // Same source. Are they platform symbols? if (sym1.SymbolSource == Symbol.Source.Platform) { // Sort by file load order. Symbols from files loaded later, which will have // a higher ordinal, have priority. int lo1 = ((DefSymbol)sym1).LoadOrdinal; int lo2 = ((DefSymbol)sym2).LoadOrdinal; if (lo1 > lo2) { return sym1; } else if (lo1 < lo2) { return sym2; } } // Same source, so this is e.g. two project symbol definitions that overlap. We // handle this by selecting whichever one was defined closer to the target address, // i.e. whichever one has the higher value. // TODO(someday): this mishandles bank wrap... do we care? if (sym1.Value > sym2.Value) { return sym1; } else if (sym1.Value < sym2.Value) { return sym2; } // In the absence of anything better, we select them alphabetically. (If they have // the same name, value, and source, there's not much to distinguish them anyway.) if (Asm65.Label.LABEL_COMPARER.Compare(sym1.Label, sym2.Label) < 0) { return sym1; } else { return sym2; } } /// /// Replaces an entry in the address table. Must be called AFTER the by-label list /// has been updated. /// /// Symbol being replaced. /// New symbol. private void ReplaceAddressTableEntry(Symbol oldSym, Symbol newSym) { RemoveAddressTableEntry(oldSym); AddAddressTableEntry(newSym); } /// /// Removes an entry from the address table. Must be called AFTER the by-label list /// has been updated. /// /// Symbol to remove. private void RemoveAddressTableEntry(Symbol sym) { // Easiest thing to do is just regenerate the table. Since we don't track // constants or variables, we can just ignore those. if (sym.SymbolType == Symbol.Type.Constant) { return; } if (sym.SymbolSource == Symbol.Source.Variable) { return; } if (sym.SymbolSource == Symbol.Source.User || sym.SymbolSource == Symbol.Source.Auto) { // These have a width of 1 and can't overlap with anything meaningful... even // if there's a project symbol for the address, it won't be used, because it's // an in-file address. So we can just remove the entry. // // Note we do this *a lot* when the fancier auto labels are enabled, because we // generate plain labels and then replace them with annotated labels. mSymbolsByAddress.Remove(sym.Value); return; } // Removing a project/platform symbol requires re-evaluating the by-address table. RegenerateAddressTable(); } /// /// Regenerates the entire by-address table, from the contents of the by-label list. /// /// /// This is a little painful, but if a symbol gets removed we don't have a way to /// restore lower-priority items. If this becomes a performance issue we can create /// an ordered list of symbols at each address, but with a few hundred symbols this /// should take very little time. /// private void RegenerateAddressTable() { Debug.WriteLine("SymbolTable: regenerating address table"); mSymbolsByAddress.Clear(); foreach (KeyValuePair kvp in mSymbols) { AddAddressTableEntry(kvp.Value); } } /// /// Searches the table for symbols with matching address values. Ignores constants and /// variables. /// /// Address to find. /// First matching symbol found, or null if nothing matched. public Symbol FindNonVariableByAddress(int addr) { mSymbolsByAddress.TryGetValue(addr, out Symbol sym); return sym; } } }