From 0d9814d99349d905021e3f3bbce9b8ad75997b64 Mon Sep 17 00:00:00 2001 From: Andy McFadden Date: Wed, 2 Oct 2019 16:26:05 -0700 Subject: [PATCH] Allow explicit widths in project/platform symbols, part 3 Implement multi-byte project/platform symbols by filling out a table of addresses. Each symbol is "painted" into the table, replacing an existing entry if the new entry has higher priority. This allows us to handle overlapping entries, giving boosted priority to platform symbols that are defined in .sym65 files loaded later. The bounds on project/platform symbols are now rigidly defined. If the "nearby" feature is enabled, references to SYM-1 will be picked up, but we won't go hunting for SYM+1 unless the symbol is at least two bytes wide. The cost of adding a symbol to the symbol table is about the same, but we don't have a quick way to remove a symbol. Previously, if two platform symbols had the same value, the symbol with the alphabetically lowest label would win. Now, the symbol defined in the most-recently-loaded file wins. (If you define two symbols with the same value in the same file, it's still resolved alphabetically.) This allows the user to pick the winner by arranging the load order of the platform symbol files. Platform symbols now keep a reference to the file ident of the symbol file that defined them, so we can show the symbols's source in the Info panel. These changes altered the behavior of test 2008-address-changes, which includes some tests on external addresses that are close to labeled internal addresses. The previous behavior essentially treated user labels as being 3 bytes wide and extending outside the file bounds, which was mildly convenient on occasion but felt a little skanky. (We could do with a way to define external symbols relative to internal symbols, for things like the source address of code that gets relocated.) Also, re-enabled some unit tests. Also, added a bit of identifying stuff to CrashLog.txt. --- CommonUtil/Misc.cs | 10 + CommonUtil/Version.cs | 10 +- SourceGen/DataAnalysis.cs | 5 +- SourceGen/DefSymbol.cs | 40 ++- SourceGen/DisasmProject.cs | 40 ++- SourceGen/MainController.cs | 9 +- SourceGen/PlatformSymbols.cs | 12 +- SourceGen/SGTestData/2021-external-symbols | Bin 0 -> 205 bytes .../SGTestData/2021-external-symbols-1.sym65 | 49 ++++ .../SGTestData/2021-external-symbols-2.sym65 | 13 + .../SGTestData/2021-external-symbols-3.sym65 | 10 + .../SGTestData/2021-external-symbols.dis65 | 36 +++ .../Expected/2008-address-changes_64tass.S | 8 +- .../Expected/2008-address-changes_Merlin32.S | 8 +- .../Expected/2008-address-changes_acme.S | 8 +- .../Expected/2008-address-changes_cc65.S | 8 +- .../Expected/2021-external-symbols_64tass.S | 100 +++++++ .../Expected/2021-external-symbols_Merlin32.S | 99 +++++++ .../Expected/2021-external-symbols_acme.S | 101 +++++++ .../Expected/2021-external-symbols_cc65.S | 101 +++++++ .../Expected/2021-external-symbols_cc65.cfg | 11 + .../SGTestData/Source/2021-external-symbols.S | 112 ++++++++ SourceGen/Symbol.cs | 3 +- SourceGen/SymbolTable.cs | 259 +++++++++++------- SourceGen/WpfGui/MainWindow.xaml.cs | 4 +- 25 files changed, 894 insertions(+), 162 deletions(-) create mode 100644 SourceGen/SGTestData/2021-external-symbols create mode 100644 SourceGen/SGTestData/2021-external-symbols-1.sym65 create mode 100644 SourceGen/SGTestData/2021-external-symbols-2.sym65 create mode 100644 SourceGen/SGTestData/2021-external-symbols-3.sym65 create mode 100644 SourceGen/SGTestData/2021-external-symbols.dis65 create mode 100644 SourceGen/SGTestData/Expected/2021-external-symbols_64tass.S create mode 100644 SourceGen/SGTestData/Expected/2021-external-symbols_Merlin32.S create mode 100644 SourceGen/SGTestData/Expected/2021-external-symbols_acme.S create mode 100644 SourceGen/SGTestData/Expected/2021-external-symbols_cc65.S create mode 100644 SourceGen/SGTestData/Expected/2021-external-symbols_cc65.cfg create mode 100644 SourceGen/SGTestData/Source/2021-external-symbols.S diff --git a/CommonUtil/Misc.cs b/CommonUtil/Misc.cs index d441a6a..4180ee2 100644 --- a/CommonUtil/Misc.cs +++ b/CommonUtil/Misc.cs @@ -20,6 +20,12 @@ using System.Linq; namespace CommonUtil { public static class Misc { + /// + /// Application identifier. This is an arbitrary string set by the application. It + /// will be included in the crash dump. + /// + public static string AppIdent { get; set; } = "(app ident unset)"; + // Given a type, dump all namespaces found in the same assembly. // https://stackoverflow.com/a/1549216/294248 public static void DumpNamespacesInAssembly(Type type) { @@ -50,6 +56,10 @@ namespace CommonUtil { try { using (StreamWriter writer = new StreamWriter(CRASH_PATH, true)) { writer.WriteLine("*** " + DateTime.Now.ToLocalTime() + " ***"); + writer.WriteLine(" App: " + AppIdent); + writer.WriteLine(" OS: " + + System.Runtime.InteropServices.RuntimeInformation.OSDescription); + writer.WriteLine(string.Empty); while (ex != null) { writer.WriteLine(ex.GetType().FullName + ": " + ex.Message); writer.WriteLine("Trace:"); diff --git a/CommonUtil/Version.cs b/CommonUtil/Version.cs index 4d9655d..2822e8e 100644 --- a/CommonUtil/Version.cs +++ b/CommonUtil/Version.cs @@ -19,9 +19,10 @@ using System.Diagnostics; namespace CommonUtil { /// /// Version number container. Instances are immutable. - /// - /// See https://semver.org/ for explanation of system. /// + /// + /// See https://semver.org/ for explanation of system. + /// public struct Version { // Must be in ascending order, e.g. Alpha release comes before Beta. public enum PreRelType { Dev, Alpha, Beta, Final }; @@ -47,7 +48,7 @@ namespace CommonUtil { public PreRelType PreReleaseType { get; private set; } /// - /// Pre-release version. + /// Pre-release version. Always zero when PreReleaseType is Final. /// public int PreRelease { get; private set; } @@ -109,6 +110,7 @@ namespace CommonUtil { } + // this is a struct, so no need for null checks public static bool operator ==(Version a, Version b) { return a.Major == b.Major && a.Minor == b.Minor && a.Patch == b.Patch && a.PreReleaseType == b.PreReleaseType && a.PreRelease == b.PreRelease; @@ -185,7 +187,7 @@ namespace CommonUtil { ok &= (checkVers > new Version(1, 2, 2)); ok &= (checkVers < new Version(1, 3, 1)); - Debug.WriteLine("Version struct test complete (ok=" + ok + ")"); + Debug.WriteLine("Version struct: test complete (ok=" + ok + ")"); return ok; } } diff --git a/SourceGen/DataAnalysis.cs b/SourceGen/DataAnalysis.cs index d00c059..174edd3 100644 --- a/SourceGen/DataAnalysis.cs +++ b/SourceGen/DataAnalysis.cs @@ -393,7 +393,10 @@ namespace SourceGen { if (addrDiff == targetOffset - probeOffset) { targetOffset = probeOffset; } else { - Debug.WriteLine("NOT probing past address boundary change"); + Debug.WriteLine("NOT probing past address boundary change (src=+" + + srcOffset.ToString("x6") + + " targ=+" + targetOffset.ToString("x6") + + " probe=+" + probeOffset.ToString("x6") + ")"); } break; } diff --git a/SourceGen/DefSymbol.cs b/SourceGen/DefSymbol.cs index c172bfb..9af52ff 100644 --- a/SourceGen/DefSymbol.cs +++ b/SourceGen/DefSymbol.cs @@ -59,14 +59,27 @@ namespace SourceGen { /// /// Platform symbols only: tag used to organize symbols into groups. Used by /// extension scripts. + /// + /// Not serialized. /// - /// - /// This is not included in DefSymbol serialization because symbols with tags are - /// not stored in the project file. It's only set when symbols are parsed out of - /// platform symbol files. - /// public string Tag { get; private set; } + /// + /// Platform symbols only: this indicates the position of the defining platform symbol + /// file in the set of symbol files. Higher numbers mean higher priority. + /// + /// Not serialized. + /// + public int LoadOrdinal { get; private set; } + + /// + /// Platform symbols only: external file identifier for the platform symbol file that + /// defined this symbol. Can be displayed to the user in the Info panel. + /// + /// Not serialized. + /// + public string FileIdentifier { get; private set; } + /// /// Cross-reference data, generated by the analyzer. /// @@ -146,6 +159,23 @@ namespace SourceGen { Tag = tag; } + /// + /// Constructor. Used for platform symbol files. + /// + /// Indicates the order in which the defining platform + /// symbol file was loaded. Higher numbers indicate later loading, which translates + /// to higher priority. + /// Platform symbol file identifier, for the Info panel. + public DefSymbol(string label, int value, Source source, Type type, + FormatDescriptor.SubType formatSubType, string comment, string tag, int width, + bool widthSpecified, int loadOrdinal, string fileIdent) + : this(label, value, source, type, formatSubType, comment, tag, width, + widthSpecified) { + LoadOrdinal = loadOrdinal; + FileIdentifier = fileIdent; + } + + /// /// Constructor. Used for deserialization, when we have a FormatDescriptor and a Symbol. /// diff --git a/SourceGen/DisasmProject.cs b/SourceGen/DisasmProject.cs index 8cfe7a8..000d610 100644 --- a/SourceGen/DisasmProject.cs +++ b/SourceGen/DisasmProject.cs @@ -533,7 +533,7 @@ namespace SourceGen { /// /// Failures here will be reported to the user but aren't fatal. /// - /// String with all warnings from load process. + /// Multi-line string with all warnings from load process. public string LoadExternalFiles() { TaskTimer timer = new TaskTimer(); timer.StartTask("Total"); @@ -548,15 +548,18 @@ namespace SourceGen { // Load the platform symbols first. timer.StartTask("Platform Symbols"); PlatformSyms.Clear(); + int loadOrdinal = 0; foreach (string fileIdent in ProjectProps.PlatformSymbolFileIdentifiers) { PlatformSymbols ps = new PlatformSymbols(); - bool ok = ps.LoadFromFile(fileIdent, projectDir, out FileLoadReport report); + bool ok = ps.LoadFromFile(fileIdent, projectDir, loadOrdinal, + out FileLoadReport report); if (ok) { PlatformSyms.Add(ps); } if (report.Count > 0) { sb.Append(report.Format()); } + loadOrdinal++; } timer.EndTask("Platform Symbols"); @@ -664,6 +667,7 @@ namespace SourceGen { reanalysisTimer.StartTask("SymbolTable init"); SymbolTable.Clear(); MergePlatformProjectSymbols(); + // Merge user labels into the symbol table, overwriting platform/project symbols // where they conflict. Labels whose values are out of sync (because of a change // to the address map) are updated as part of this. @@ -911,7 +915,8 @@ namespace SourceGen { /// so that ordering of platform files behaves in an intuitive fashion. /// private void MergePlatformProjectSymbols() { - // Start by pulling in the platform symbols. + // Start by pulling in the platform symbols. The list in PlatformSymbols is in + // order, so we can just overwrite earlier symbols with matching labels. foreach (PlatformSymbols ps in PlatformSyms) { foreach (Symbol sym in ps) { SymbolTable[sym.Label] = sym; @@ -1080,7 +1085,8 @@ namespace SourceGen { /// This works pretty well for addresses, but is a little rough for constants. /// /// Call this after the code and data analysis passes have completed. This doesn't - /// interact with labels, so the ordering there doesn't matter. + /// interact with labels, so the ordering there doesn't matter. This should come after + /// local variable resolution, so that those have priority. /// private void GeneratePlatformSymbolRefs() { bool checkNearby = ProjectProps.AnalysisParams.SeekNearbyTargets; @@ -1132,25 +1138,11 @@ namespace SourceGen { } if (address >= 0) { - // If we didn't find it, check addr-1. This is very helpful when working - // with pointers, because it gets us references to "PTR+1" when "PTR" is - // defined. (It's potentially helpful in labeling the "near side" of an - // address map split as well, since the first byte past is an external - // address, and a label at the end of the current region will be offset - // from by this.) - if (sym == null && (address & 0xffff) > 0 && checkNearby) { - sym = SymbolTable.FindNonVariableByAddress(address - 1); - } - // If that didn't work, try addr-2. Good for 24-bit addresses and jump - // vectors that start with a JMP instruction. - if (sym == null && (address & 0xffff) > 1 && checkNearby) { - sym = SymbolTable.FindNonVariableByAddress(address - 2); - } - // Still nothing, try addr+1. Sometimes indexed addressing will use - // "STA addr-1,y". This will also catch "STA addr-1" when addr is the - // very start of a segment, which means we're actually finding a label - // reference rather than project/platform symbol; only works if the - // location already has a label. + // If we didn't find it, see if addr+1 has a label. Sometimes indexed + // addressing will use "STA addr-1,y". This will also catch "STA addr-1" + // when addr is the very start of a segment, which means we're actually + // finding a label reference rather than project/platform symbol; only + // works if the location already has a label. if (sym == null && (address & 0xffff) < 0xffff && checkNearby) { sym = SymbolTable.FindNonVariableByAddress(address + 1); if (sym != null && sym.SymbolSource != Symbol.Source.Project && @@ -1790,7 +1782,7 @@ namespace SourceGen { if (newValue == null) { // We're removing a user label. UserLabels.Remove(offset); - SymbolTable.Remove((Symbol)oldValue); // unnecessary? + SymbolTable.Remove((Symbol)oldValue); // unnecessary? will regen Debug.Assert(uc.ReanalysisRequired == UndoableChange.ReanalysisScope.DataOnly); } else { diff --git a/SourceGen/MainController.cs b/SourceGen/MainController.cs index 530dee3..6733a4b 100644 --- a/SourceGen/MainController.cs +++ b/SourceGen/MainController.cs @@ -229,6 +229,10 @@ namespace SourceGen { /// to this point so we can report fatal errors directly to the user. /// public void WindowLoaded() { + Debug.Assert(CommonUtil.RangeSet.Test()); + Debug.Assert(CommonUtil.TypedRangeSet.Test()); + Debug.Assert(CommonUtil.Version.Test()); + if (RuntimeDataAccess.GetDirectory() == null) { MessageBox.Show(Res.Strings.RUNTIME_DIR_NOT_FOUND, Res.Strings.RUNTIME_DIR_NOT_FOUND_CAPTION, @@ -3309,7 +3313,8 @@ namespace SourceGen { if (defSym.SymbolSource == Symbol.Source.Project) { sourceStr = "project symbol definition"; } else if (defSym.SymbolSource == Symbol.Source.Platform) { - sourceStr = "platform symbol file"; + sourceStr = "platform symbol file (#" + defSym.LoadOrdinal + + ":" + defSym.FileIdentifier + ")"; } else { sourceStr = "???"; } @@ -3602,6 +3607,6 @@ namespace SourceGen { ScriptManager.UseKeepAliveHack = !ScriptManager.UseKeepAliveHack; } - #endregion + #endregion Debug features } } diff --git a/SourceGen/PlatformSymbols.cs b/SourceGen/PlatformSymbols.cs index f6a2469..3600f37 100644 --- a/SourceGen/PlatformSymbols.cs +++ b/SourceGen/PlatformSymbols.cs @@ -71,12 +71,13 @@ namespace SourceGen { /// /// Loads platform symbols. /// - /// Relative pathname of file to open. + /// External file identifier of symbol file. /// Full path to project directory. + /// Platform file load order. /// Report of warnings and errors. /// True on success (no errors), false on failure. - public bool LoadFromFile(string fileIdent, string projectDir, out FileLoadReport report) { - // These files shouldn't be enormous. Do it the easy way. + public bool LoadFromFile(string fileIdent, string projectDir, int loadOrdinal, + out FileLoadReport report) { report = new FileLoadReport(fileIdent); ExternalFile ef = ExternalFile.CreateFromIdent(fileIdent); @@ -85,13 +86,14 @@ namespace SourceGen { CommonUtil.Properties.Resources.ERR_FILE_NOT_FOUND + ": " + fileIdent); return false; } - string pathName = ef.GetPathName(projectDir); if (pathName == null) { report.Add(FileLoadItem.Type.Error, Res.Strings.ERR_BAD_IDENT + ": " + fileIdent); return false; } + + // These files shouldn't be enormous. Just read the entire thing into a string array. string[] lines; try { lines = File.ReadAllLines(pathName); @@ -168,7 +170,7 @@ namespace SourceGen { FormatDescriptor.GetSubTypeForBase(numBase); DefSymbol symDef = new DefSymbol(label, value, Symbol.Source.Platform, isConst ? Symbol.Type.Constant : Symbol.Type.ExternalAddr, - subType, comment, tag, width, width > 0); + subType, comment, tag, width, width > 0, loadOrdinal, fileIdent); if (mSymbols.ContainsKey(label)) { // This is very easy to do -- just define the same symbol twice // in the same file. We don't really need to do anything about diff --git a/SourceGen/SGTestData/2021-external-symbols b/SourceGen/SGTestData/2021-external-symbols new file mode 100644 index 0000000000000000000000000000000000000000..582f59262548873bf216677e0004effde04f18c7 GIT binary patch literal 205 zcmWN|%R$316h%SFzkI=>0zbV5a@7EFfI_HFtDF?(A`srs(B@Z2&zLs;7TQ8EW+NFN z#$uR}4Y!SuNg2yK%(P5!qSHCOGdQC&IS1$HoSd_B@txjiH-dPXCf>y?lJ+T@2!&Ya3VmTHjD@Le^|RjVS|9bRe%GIkb| - /// Symbol origin, e.g. auto-generated or entered by user. + /// Symbol origin, e.g. auto-generated or entered by user. Enum values are in + /// priority order. /// public Source SymbolSource { get; private set; } diff --git a/SourceGen/SymbolTable.cs b/SourceGen/SymbolTable.cs index 2c03f39..da5830b 100644 --- a/SourceGen/SymbolTable.cs +++ b/SourceGen/SymbolTable.cs @@ -31,42 +31,18 @@ namespace SourceGen { new SortedList(Asm65.Label.LABEL_COMPARER); /// - /// Same content, but ordered by value. Note the key and the value are the same object. - /// - private SortedList mSymbolsByValue = - new SortedList(new CompareByValue()); - - /// - /// Compare two symbols, primarily by value, secondarily by source, and tertiarily - /// by label. The primary SortedList guarantees that the label is unique, so we - /// should never have two equal Symbols in the list. + /// 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. /// - /// The type comparison ensures that project symbols appear before platform symbols, - /// so that you can "overwrite" a platform symbol with the same value. - /// - /// TODO(someday): sort by symbol file load order, so you can choose which set of - /// symbols gets to represent a given address. Mostly useful for zero-page variables. + /// This does not contain constants or local variables. /// - private class CompareByValue : IComparer { - public int Compare(Symbol a, Symbol b) { - if (a.Value < b.Value) { - return -1; - } else if (a.Value > b.Value) { - return 1; - } - - if ((int)a.SymbolSource < (int)b.SymbolSource) { - return -1; - } else if ((int)a.SymbolSource > (int)b.SymbolSource) { - return 1; - } - - // Equal values, check string. We'll get a match on Remove or when - // replacing an entry with itself, but no two Symbols in the list - // should have the same label. - return Asm65.Label.LABEL_COMPARER.Compare(a.Label, b.Label); - } - } + /// + /// 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 @@ -75,7 +51,7 @@ namespace SourceGen { /// /// We could theoretically miss something at the 2^32 rollover. Not worried. /// - public int ChangeSerial { get; private set; } + //public int ChangeSerial { get; private set; } public SymbolTable() { } @@ -96,15 +72,14 @@ namespace SourceGen { /// public void Clear() { mSymbols.Clear(); - mSymbolsByValue.Clear(); - ChangeSerial++; + mSymbolsByAddress.Clear(); + //ChangeSerial++; } /// /// Returns the number of symbols in the table. /// public int Count() { - Debug.Assert(mSymbolsByValue.Count == mSymbols.Count); return mSymbols.Count; } @@ -116,86 +91,32 @@ namespace SourceGen { // 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); - mSymbolsByValue.Add(sym, sym); - ChangeSerial++; + AddAddressTableEntry(sym); + //ChangeSerial++; } /// - /// Finds the specified symbol by label. Throws an exception if it's not found. + /// get: Finds the specified symbol by label. Throws an exception if it's not found. /// - /// Adds the specified symbol to the list, or replaces it if it's already present. + /// set: Adds the specified symbol to the list, or replaces it if it's already present. /// public Symbol this[string key] { get { - Debug.Assert(mSymbolsByValue.Count == mSymbols.Count); return mSymbols[key]; } set { - // Replacing {"foo", 1} with ("foo", 2} works correctly for mSymbols, because - // the label is the unique key. For mSymbolsByValue we have to explicitly - // remove it, because the entire Symbol is used as the key. mSymbols.TryGetValue(key, out Symbol oldValue); - if (oldValue != null) { - mSymbolsByValue.Remove(oldValue); - } mSymbols[key] = value; - mSymbolsByValue[value] = value; - ChangeSerial++; - } - } - - /// - /// Searches the table for symbols with matching address values. Ignores constants and - /// variables. - /// - /// Value to find. - /// First matching symbol found, or null if nothing matched. - public Symbol FindNonVariableByAddress(int value) { - // Get sorted list of values. This is documented as efficient. - IList values = mSymbolsByValue.Values; - - //for (int i = 0; i < values.Count; i++) { - // if (values[i].Value == value && values[i].SymbolType != Symbol.Type.Constant) { - // return values[i]; - // } - //} - - int low = 0; - int high = values.Count - 1; - while (low <= high) { - int mid = (low + high) / 2; - Symbol midValue = values[mid]; - - if (midValue.Value == value) { - // found a match, walk back to find first match - while (mid > 0 && values[mid - 1].Value == value) { - mid--; - } - // now skip past constants and variables - while (mid < values.Count && (values[mid].SymbolType == Symbol.Type.Constant || - values[mid].SymbolSource == Symbol.Source.Variable)) { - //Debug.WriteLine("disregarding " + values[mid]); - mid++; - } - if (mid < values.Count && values[mid].Value == value) { - return values[mid]; - } - //Debug.WriteLine("Found value " + value + " but only constants"); - return null; - } else if (midValue.Value < value) { - // move the low end in - low = mid + 1; + if (oldValue != null) { + ReplaceAddressTableEntry(oldValue, value); } else { - // move the high end in - Debug.Assert(midValue.Value > value); - high = mid - 1; + AddAddressTableEntry(value); } + //ChangeSerial++; } - - // not found - return null; } + /// /// Gets the value associated with the key. /// @@ -226,8 +147,140 @@ namespace SourceGen { /// public void Remove(Symbol sym) { mSymbols.Remove(sym.Label); - mSymbolsByValue.Remove(sym); - ChangeSerial++; + 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 + mSymbolsByAddress.TryGetValue(sym.Value + i, out Symbol curSym); + mSymbolsByAddress[sym.Value + i] = (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. + 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. + mSymbolsByAddress.Remove(sym.Value); + } + 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; } } } diff --git a/SourceGen/WpfGui/MainWindow.xaml.cs b/SourceGen/WpfGui/MainWindow.xaml.cs index 6ab6a59..acd3253 100644 --- a/SourceGen/WpfGui/MainWindow.xaml.cs +++ b/SourceGen/WpfGui/MainWindow.xaml.cs @@ -162,8 +162,10 @@ namespace SourceGen.WpfGui { Debug.WriteLine("START at " + DateTime.Now.ToLocalTime()); InitializeComponent(); + // Prep the crash handler. + Misc.AppIdent = "6502bench SourceGen v" + App.ProgramVersion.ToString(); AppDomain.CurrentDomain.UnhandledException += - new UnhandledExceptionEventHandler(CommonUtil.Misc.CrashReporter); + new UnhandledExceptionEventHandler(Misc.CrashReporter); listViewSetSelectedItems = codeListView.GetType().GetMethod("SetSelectedItems", BindingFlags.NonPublic | BindingFlags.Instance);