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 0000000..582f592 Binary files /dev/null and b/SourceGen/SGTestData/2021-external-symbols differ diff --git a/SourceGen/SGTestData/2021-external-symbols-1.sym65 b/SourceGen/SGTestData/2021-external-symbols-1.sym65 new file mode 100644 index 0000000..8228744 --- /dev/null +++ b/SourceGen/SGTestData/2021-external-symbols-1.sym65 @@ -0,0 +1,49 @@ +; Copyright 2019 faddenSoft. All Rights Reserved. +; See the LICENSE.txt file for distribution terms (Apache 2.0). + +*SYNOPSIS Symbol set 1 for test 2021-external-symbols + +; Platform symbols aren't applied to file data. +PlatStart @ $1000 + +; SameName2 and SameName3 are replaced by later file +SameName1 @ $2000 +SameName2 @ $2010 +SameName3 @ $2020 + +; Symbols with the same values but different names are defined +; in later files. Names are chosen to not provide a strict +; alphabetical progression. +SameValA_C @ $2100 +SameValB_B @ $2110 +SameValC_A @ $2120 + +; Test overlap with project symbol. Declare at $2202(4b), and $220a(1b). +ChkProj1 @ $2200 4 +ChkProj2 @ $2204 4 + +; Overlapping regions, defined within a single platform file. We +; should always use the most-recently-defined symbol. When all +; else is equal, alphabetical. +Over1 @ $3000 16 ;$3000-300f, inclusive +Over2 @ $3002 8 ;$3002-3009 +Over3 @ $3006 7 ;$3006-300c +Over2a @ $3006 1 ;$3006 + +; Expected result: +; $3000-3001: Over1 +; $3002-3005: Over2 +; $3006 : Over4 +; $3007-300c: Over3 +; $300d-300f: Over1 + +; Overlapping regions defined in multiple files. The later definition +; takes priority. So while SepOver1 would normally end at $3102, +; instead it steps on the first two bytes of SepOver2. +SepOver2 @ $3102 4 ;$3102-3105, inclusive + +; Test overlap with local variable. Declare at $41(2b). +OverVar @ $40 4 + +; Width specifiers on constants should be ignored. +FatConst = $4000 8 diff --git a/SourceGen/SGTestData/2021-external-symbols-2.sym65 b/SourceGen/SGTestData/2021-external-symbols-2.sym65 new file mode 100644 index 0000000..b9407f5 --- /dev/null +++ b/SourceGen/SGTestData/2021-external-symbols-2.sym65 @@ -0,0 +1,13 @@ +; Copyright 2019 faddenSoft. All Rights Reserved. +; See the LICENSE.txt file for distribution terms (Apache 2.0). + +*SYNOPSIS Symbol set 2 for test 2021-external-symbols + +; override 2nd and 3rd +SameName2 @ $2011 +SameName3 @ $2021 + +SameValB_A @ $2110 +SameValC_B @ $2120 + +SepOver1 @ $3100 4 ;$3100-3103, inclusive diff --git a/SourceGen/SGTestData/2021-external-symbols-3.sym65 b/SourceGen/SGTestData/2021-external-symbols-3.sym65 new file mode 100644 index 0000000..c149562 --- /dev/null +++ b/SourceGen/SGTestData/2021-external-symbols-3.sym65 @@ -0,0 +1,10 @@ +; Copyright 2019 faddenSoft. All Rights Reserved. +; See the LICENSE.txt file for distribution terms (Apache 2.0). + +*SYNOPSIS Symbol set 3 for test 2021-external-symbols + +; override 3rd +SameName3 @ $2022 + +SameValA_A @ $2100 +SameValC_C @ $2120 diff --git a/SourceGen/SGTestData/2021-external-symbols.dis65 b/SourceGen/SGTestData/2021-external-symbols.dis65 new file mode 100644 index 0000000..00c62a5 --- /dev/null +++ b/SourceGen/SGTestData/2021-external-symbols.dis65 @@ -0,0 +1,36 @@ +### 6502bench SourceGen dis65 v1.0 ### +{ +"_ContentVersion":2,"FileDataLength":205,"FileDataCrc32":178586750,"ProjectProps":{ +"CpuName":"6502","IncludeUndocumentedInstr":false,"EntryFlags":32702671,"AutoLabelStyle":"Simple","AnalysisParams":{ +"AnalyzeUncategorizedData":true,"DefaultTextScanMode":"LowHighAscii","MinCharsForString":4,"SeekNearbyTargets":true,"SmartPlpHandling":true}, +"PlatformSymbolFileIdentifiers":["PROJ:2021-external-symbols-1.sym65","PROJ:2021-external-symbols-2.sym65","PROJ:2021-external-symbols-3.sym65"],"ExtensionScriptFileIdentifiers":[],"ProjectSyms":{ +"ProjSim1":{ +"DataDescriptor":{ +"Length":4,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"Comment":"","HasWidth":true,"Label":"ProjSim1","Value":8706,"Source":"Project","Type":"ExternalAddr"}, +"ProjSim2":{ +"DataDescriptor":{ +"Length":1,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"Comment":"","HasWidth":true,"Label":"ProjSim2","Value":8714,"Source":"Project","Type":"ExternalAddr"}}}, +"AddressMap":[{ +"Offset":0,"Addr":4096}],"TypeHints":[{ +"Low":0,"High":0,"Hint":"Code"}],"StatusFlagOverrides":{ +}, +"Comments":{ +}, +"LongComments":{ +}, +"Notes":{ +}, +"UserLabels":{ +}, +"OperandFormats":{ +"195":{ +"Length":3,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"FatConst","Part":"Low"}}}, +"LvTables":{ +"180":{ +"Variables":[{ +"DataDescriptor":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"Comment":"","HasWidth":true,"Label":"LocalVar","Value":65,"Source":"Variable","Type":"ExternalAddr"}],"ClearPrevious":false}}} diff --git a/SourceGen/SGTestData/Expected/2008-address-changes_64tass.S b/SourceGen/SGTestData/Expected/2008-address-changes_64tass.S index 99e9878..7719e78 100644 --- a/SourceGen/SGTestData/Expected/2008-address-changes_64tass.S +++ b/SourceGen/SGTestData/Expected/2008-address-changes_64tass.S @@ -44,7 +44,7 @@ L2000 bit L2000 .here .logical $2020 L2020 bit L2020 - beq offend+1 + beq $2029 brl L2080 offend nop @@ -53,8 +53,8 @@ offend nop L2080 bit L2080 lda offend jsr offend - lda offend+1 - jsr offend+1 + lda $2029 + jsr $2029 lda L2080-1 jsr L2080-1 lda L2080 @@ -95,7 +95,7 @@ L3100 .byte $02 fwd bit fwd lda ulabel lda ulabel+1 - lda ulabel+2 + lda $300e lda $300f lda L3100 beq L3182 diff --git a/SourceGen/SGTestData/Expected/2008-address-changes_Merlin32.S b/SourceGen/SGTestData/Expected/2008-address-changes_Merlin32.S index a31ce7c..72fbb73 100644 --- a/SourceGen/SGTestData/Expected/2008-address-changes_Merlin32.S +++ b/SourceGen/SGTestData/Expected/2008-address-changes_Merlin32.S @@ -37,7 +37,7 @@ L2000 bit L2000 org $2020 L2020 bit L2020 - beq offend+1 + beq $2029 brl L2080 offend nop @@ -45,8 +45,8 @@ offend nop L2080 bit L2080 lda offend jsr offend - lda offend+1 - jsr offend+1 + lda $2029 + jsr $2029 lda L2080-1 jsr L2080-1 lda L2080 @@ -82,7 +82,7 @@ L3100 dfb $02 fwd bit fwd lda ulabel lda ulabel+1 - lda ulabel+2 + lda $300e lda $300f lda L3100 beq L3182 diff --git a/SourceGen/SGTestData/Expected/2008-address-changes_acme.S b/SourceGen/SGTestData/Expected/2008-address-changes_acme.S index 4c0d479..dd73c74 100644 --- a/SourceGen/SGTestData/Expected/2008-address-changes_acme.S +++ b/SourceGen/SGTestData/Expected/2008-address-changes_acme.S @@ -44,7 +44,7 @@ L2000 bit L2000 } ;!pseudopc !pseudopc $2020 { L2020 bit L2020 - beq offend+1 + beq $2029 brl L2080 offend nop @@ -53,8 +53,8 @@ offend nop L2080 bit L2080 lda offend jsr offend - lda offend+1 - jsr offend+1 + lda $2029 + jsr $2029 lda L2080-1 jsr L2080-1 lda L2080 @@ -95,7 +95,7 @@ L3100 !byte $02 fwd bit fwd lda ulabel lda ulabel+1 - lda ulabel+2 + lda $300e lda $300f lda L3100 beq L3182 diff --git a/SourceGen/SGTestData/Expected/2008-address-changes_cc65.S b/SourceGen/SGTestData/Expected/2008-address-changes_cc65.S index 6c68a60..38f0596 100644 --- a/SourceGen/SGTestData/Expected/2008-address-changes_cc65.S +++ b/SourceGen/SGTestData/Expected/2008-address-changes_cc65.S @@ -46,7 +46,7 @@ L2000: bit L2000 ; .segment "SEG005" .org $2020 L2020: bit L2020 - beq offend+1 + beq $2029 brl L2080 offend: nop @@ -55,8 +55,8 @@ offend: nop L2080: bit L2080 lda offend jsr offend - lda offend+1 - jsr offend+1 + lda $2029 + jsr $2029 lda L2080-1 jsr L2080-1 lda L2080 @@ -97,7 +97,7 @@ L3100: .byte $02 fwd: bit fwd lda ulabel lda ulabel+1 - lda ulabel+2 + lda $300e lda $300f lda L3100 beq L3182 diff --git a/SourceGen/SGTestData/Expected/2021-external-symbols_64tass.S b/SourceGen/SGTestData/Expected/2021-external-symbols_64tass.S new file mode 100644 index 0000000..be32668 --- /dev/null +++ b/SourceGen/SGTestData/Expected/2021-external-symbols_64tass.S @@ -0,0 +1,100 @@ + .cpu "6502" +OverVar = $40 +PlatStart = $1000 +SameName1 = $2000 +SameName2 = $2011 +SameName3 = $2022 +SameValA_A = $2100 +SameValB_A = $2110 +SameValC_C = $2120 +ChkProj1 = $2200 +ProjSim1 = $2202 +ChkProj2 = $2204 +ProjSim2 = $220a +Over1 = $3000 ;$3000-300f, inclusive +Over2 = $3002 ;$3002-3009 +Over2a = $3006 ;$3006 +Over3 = $3006 ;$3006-300c +SepOver1 = $3100 ;$3100-3103, inclusive +SepOver2 = $3102 ;$3102-3105, inclusive +FatConst = $4000 + +* = $1000 +L1000 lda PlatStart-1 + ldx L1000 + ldy L1000+1 + nop + lda $1ffe + lda SameName1-1 + lda SameName1 + lda $200f + lda SameName2-1 + lda SameName2 + lda $201f + lda $2020 + lda SameName3-1 + lda SameName3 + nop + lda SameValA_A + lda SameValB_A + lda SameValC_C + nop + lda $21fe + lda ChkProj1-1 + lda ChkProj1 + lda ChkProj1+1 + lda ProjSim1 + lda ProjSim1+1 + lda ProjSim1+2 + lda ProjSim1+3 + lda ChkProj2+2 + lda ChkProj2+3 + lda $2208 + lda ProjSim2-1 + lda ProjSim2 + lda $220b + nop + lda $2ffe + lda Over1-1 + lda Over1 + lda Over1+1 + lda Over2 + lda Over2+1 + lda Over2+2 + lda Over2+3 + lda Over2a + lda Over3+1 + lda Over3+2 + lda Over3+3 + lda Over3+4 + lda Over3+5 + lda Over3+6 + lda Over1+13 + lda Over1+14 + lda Over1+15 + lda $3010 + nop + lda $30fe + lda SepOver1-1 + lda SepOver1 + lda SepOver1+1 + lda SepOver1+2 + lda SepOver1+3 + lda SepOver2+2 + lda SepOver2+3 + lda $3106 + nop +LocalVar .var $41 + ldx $3e + ldx OverVar-1 + ldx OverVar + ldx LocalVar + ldx LocalVar+1 + ldx OverVar+3 + ldx $44 + nop + lda FatConst-1 + lda $4000 + lda $4001 + rts + diff --git a/SourceGen/SGTestData/Expected/2021-external-symbols_Merlin32.S b/SourceGen/SGTestData/Expected/2021-external-symbols_Merlin32.S new file mode 100644 index 0000000..696ea3d --- /dev/null +++ b/SourceGen/SGTestData/Expected/2021-external-symbols_Merlin32.S @@ -0,0 +1,99 @@ +OverVar equ $40 +PlatStart equ $1000 +SameName1 equ $2000 +SameName2 equ $2011 +SameName3 equ $2022 +SameValA_A equ $2100 +SameValB_A equ $2110 +SameValC_C equ $2120 +ChkProj1 equ $2200 +ProjSim1 equ $2202 +ChkProj2 equ $2204 +ProjSim2 equ $220a +Over1 equ $3000 ;$3000-300f, inclusive +Over2 equ $3002 ;$3002-3009 +Over2a equ $3006 ;$3006 +Over3 equ $3006 ;$3006-300c +SepOver1 equ $3100 ;$3100-3103, inclusive +SepOver2 equ $3102 ;$3102-3105, inclusive +FatConst equ $4000 + + org $1000 +L1000 lda PlatStart-1 + ldx L1000 + ldy L1000+1 + nop + lda $1ffe + lda SameName1-1 + lda SameName1 + lda $200f + lda SameName2-1 + lda SameName2 + lda $201f + lda $2020 + lda SameName3-1 + lda SameName3 + nop + lda SameValA_A + lda SameValB_A + lda SameValC_C + nop + lda $21fe + lda ChkProj1-1 + lda ChkProj1 + lda ChkProj1+1 + lda ProjSim1 + lda ProjSim1+1 + lda ProjSim1+2 + lda ProjSim1+3 + lda ChkProj2+2 + lda ChkProj2+3 + lda $2208 + lda ProjSim2-1 + lda ProjSim2 + lda $220b + nop + lda $2ffe + lda Over1-1 + lda Over1 + lda Over1+1 + lda Over2 + lda Over2+1 + lda Over2+2 + lda Over2+3 + lda Over2a + lda Over3+1 + lda Over3+2 + lda Over3+3 + lda Over3+4 + lda Over3+5 + lda Over3+6 + lda Over1+13 + lda Over1+14 + lda Over1+15 + lda $3010 + nop + lda $30fe + lda SepOver1-1 + lda SepOver1 + lda SepOver1+1 + lda SepOver1+2 + lda SepOver1+3 + lda SepOver2+2 + lda SepOver2+3 + lda $3106 + nop +]LocalVar equ $41 + ldx $3e + ldx OverVar-1 + ldx OverVar + ldx ]LocalVar + ldx ]LocalVar+1 + ldx OverVar+3 + ldx $44 + nop + lda FatConst-1 + lda $4000 + lda $4001 + rts + diff --git a/SourceGen/SGTestData/Expected/2021-external-symbols_acme.S b/SourceGen/SGTestData/Expected/2021-external-symbols_acme.S new file mode 100644 index 0000000..99aa920 --- /dev/null +++ b/SourceGen/SGTestData/Expected/2021-external-symbols_acme.S @@ -0,0 +1,101 @@ + !cpu 6502 +OverVar = $40 +PlatStart = $1000 +SameName1 = $2000 +SameName2 = $2011 +SameName3 = $2022 +SameValA_A = $2100 +SameValB_A = $2110 +SameValC_C = $2120 +ChkProj1 = $2200 +ProjSim1 = $2202 +ChkProj2 = $2204 +ProjSim2 = $220a +Over1 = $3000 ;$3000-300f, inclusive +Over2 = $3002 ;$3002-3009 +Over2a = $3006 ;$3006 +Over3 = $3006 ;$3006-300c +SepOver1 = $3100 ;$3100-3103, inclusive +SepOver2 = $3102 ;$3102-3105, inclusive +FatConst = $4000 + +* = $1000 +L1000 lda PlatStart-1 + ldx L1000 + ldy L1000+1 + nop + lda $1ffe + lda SameName1-1 + lda SameName1 + lda $200f + lda SameName2-1 + lda SameName2 + lda $201f + lda $2020 + lda SameName3-1 + lda SameName3 + nop + lda SameValA_A + lda SameValB_A + lda SameValC_C + nop + lda $21fe + lda ChkProj1-1 + lda ChkProj1 + lda ChkProj1+1 + lda ProjSim1 + lda ProjSim1+1 + lda ProjSim1+2 + lda ProjSim1+3 + lda ChkProj2+2 + lda ChkProj2+3 + lda $2208 + lda ProjSim2-1 + lda ProjSim2 + lda $220b + nop + lda $2ffe + lda Over1-1 + lda Over1 + lda Over1+1 + lda Over2 + lda Over2+1 + lda Over2+2 + lda Over2+3 + lda Over2a + lda Over3+1 + lda Over3+2 + lda Over3+3 + lda Over3+4 + lda Over3+5 + lda Over3+6 + lda Over1+13 + lda Over1+14 + lda Over1+15 + lda $3010 + nop + lda $30fe + lda SepOver1-1 + lda SepOver1 + lda SepOver1+1 + lda SepOver1+2 + lda SepOver1+3 + lda SepOver2+2 + lda SepOver2+3 + lda $3106 + nop + !zone Z0000b4 +.LocalVar = $41 + ldx $3e + ldx OverVar-1 + ldx OverVar + ldx .LocalVar + ldx .LocalVar+1 + ldx OverVar+3 + ldx $44 + nop + lda FatConst-1 + lda $4000 + lda $4001 + rts + diff --git a/SourceGen/SGTestData/Expected/2021-external-symbols_cc65.S b/SourceGen/SGTestData/Expected/2021-external-symbols_cc65.S new file mode 100644 index 0000000..490975a --- /dev/null +++ b/SourceGen/SGTestData/Expected/2021-external-symbols_cc65.S @@ -0,0 +1,101 @@ + .setcpu "6502" +OverVar = $40 +PlatStart = $1000 +SameName1 = $2000 +SameName2 = $2011 +SameName3 = $2022 +SameValA_A = $2100 +SameValB_A = $2110 +SameValC_C = $2120 +ChkProj1 = $2200 +ProjSim1 = $2202 +ChkProj2 = $2204 +ProjSim2 = $220a +Over1 = $3000 ;$3000-300f, inclusive +Over2 = $3002 ;$3002-3009 +Over2a = $3006 ;$3006 +Over3 = $3006 ;$3006-300c +SepOver1 = $3100 ;$3100-3103, inclusive +SepOver2 = $3102 ;$3102-3105, inclusive +FatConst = $4000 + +; .segment "SEG000" + .org $1000 +L1000: lda PlatStart-1 + ldx L1000 + ldy L1000+1 + nop + lda $1ffe + lda SameName1-1 + lda SameName1 + lda $200f + lda SameName2-1 + lda SameName2 + lda $201f + lda $2020 + lda SameName3-1 + lda SameName3 + nop + lda SameValA_A + lda SameValB_A + lda SameValC_C + nop + lda $21fe + lda ChkProj1-1 + lda ChkProj1 + lda ChkProj1+1 + lda ProjSim1 + lda ProjSim1+1 + lda ProjSim1+2 + lda ProjSim1+3 + lda ChkProj2+2 + lda ChkProj2+3 + lda $2208 + lda ProjSim2-1 + lda ProjSim2 + lda $220b + nop + lda $2ffe + lda Over1-1 + lda Over1 + lda Over1+1 + lda Over2 + lda Over2+1 + lda Over2+2 + lda Over2+3 + lda Over2a + lda Over3+1 + lda Over3+2 + lda Over3+3 + lda Over3+4 + lda Over3+5 + lda Over3+6 + lda Over1+13 + lda Over1+14 + lda Over1+15 + lda $3010 + nop + lda $30fe + lda SepOver1-1 + lda SepOver1 + lda SepOver1+1 + lda SepOver1+2 + lda SepOver1+3 + lda SepOver2+2 + lda SepOver2+3 + lda $3106 + nop +LocalVar .set $41 + ldx $3e + ldx OverVar-1 + ldx OverVar + ldx LocalVar + ldx LocalVar+1 + ldx OverVar+3 + ldx $44 + nop + lda FatConst-1 + lda $4000 + lda $4001 + rts + diff --git a/SourceGen/SGTestData/Expected/2021-external-symbols_cc65.cfg b/SourceGen/SGTestData/Expected/2021-external-symbols_cc65.cfg new file mode 100644 index 0000000..3e2eea4 --- /dev/null +++ b/SourceGen/SGTestData/Expected/2021-external-symbols_cc65.cfg @@ -0,0 +1,11 @@ +# 6502bench SourceGen generated linker script for 2021-external-symbols +MEMORY { + MAIN: file=%O, start=%S, size=65536; +# MEM000: file=%O, start=$1000, size=205; +} +SEGMENTS { + CODE: load=MAIN, type=rw; +# SEG000: load=MEM000, type=rw; +} +FEATURES {} +SYMBOLS {} diff --git a/SourceGen/SGTestData/Source/2021-external-symbols.S b/SourceGen/SGTestData/Source/2021-external-symbols.S new file mode 100644 index 0000000..7d7104b --- /dev/null +++ b/SourceGen/SGTestData/Source/2021-external-symbols.S @@ -0,0 +1,112 @@ +; Copyright 2019 faddenSoft. All Rights Reserved. +; See the LICENSE.txt file for distribution terms (Apache 2.0). +; +; Assembler: Merlin 32 + +; EDIT: the project must include the three platform symbol files. + + org $1000 + +; make sure platform symbols don't get set for file data +; DO NOT set a user label here +Start lda Start-1 ;PlatStart-1 + ldx Start ;auto + ldy Start+1 ;auto+1 + + nop + +; test overlapping labels (multiple sym files) + lda $1ffe ;should have no symbol + lda $1fff ;should be SameName1-1 + lda $2000 ;should be SameName1 + + lda $200f ;should have no symbol + lda $2010 ;should be SameName2-1 + lda $2011 ;should be SameName2 + + lda $201f ;should have no symbol + lda $2020 ;should have no symbol + lda $2021 ;should be sym-1 + lda $2022 ;should be SameName3 + + nop + +; test overlapping values (multiple sym files) + lda $2100 ;should be SameValA_A + lda $2110 ;should be SameValB_A + lda $2120 ;should be SameValC_C + + nop + +; test overlap with project symbol +; EDIT: define project symbols ProjSym1 at $2202(4b) and ProjSim2 at $220a(1b) + lda $21fe ;should have no symbol + lda $21ff ;SYM-1 + lda $2200 ;ChkProj1 + lda $2201 ;ChkProj1+1 + lda $2202 ;ProjSym + lda $2203 ;ProjSym1+1 + lda $2204 ;ProjSym1+2 + lda $2205 ;ProjSym1+3 + lda $2206 ;ChkProj2+2 + lda $2207 ;ChkProj2+3 + lda $2208 ;should have no symbol + lda $2209 ;ProjSym2-1 + lda $220a ;ProjSym2 + lda $220b ;should have no symbol + + nop + +; test overlapping regions, single file + lda $2ffe ;should have no symbol + lda $2fff ;Over1-1 + lda $3000 ;Over1 + lda $3001 ;Over1+1 + lda $3002 ;Over2 + lda $3003 ;Over2+1 + lda $3004 ;Over2+2 + lda $3005 ;Over2+3 + lda $3006 ;Over2a + lda $3007 ;Over3+1 + lda $3008 ;Over3+2 + lda $3009 ;Over3+3 + lda $300a ;Over3+4 + lda $300b ;Over3+5 + lda $300c ;Over3+6 + lda $300d ;Over1+13 + lda $300e ;Over1+14 + lda $300f ;Over1+15 + lda $3010 ;should have no symbol + + nop + +; test overlapping regions, multiple platform files + lda $30fe ;should have no symbol + lda $30ff ;SepOver1-1 + lda $3100 ;SepOver1 + lda $3101 ;SepOver1+1 + lda $3102 ;SepOver1+2 + lda $3103 ;SepOver1+3 + lda $3104 ;SepOver2+2 + lda $3105 ;SepOver2+3 + lda $3106 ;should have no symbol + + nop + +; test overlap with local variable +; EDIT: create variable LocalVar at $41(2b) + ldx $3e ;should have no symbol + ldx $3f ;should be OverVar-1 + ldx $40 ;should be OverVar + ldx $41 ;should be LocalVar + ldx $42 ;should be LocalVar+1 + ldx $43 ;should be OverVar+3 + ldx $44 ;should have no symbol + + nop + + lda $3fff ;EDIT: change to "FatConst" + lda $4000 ;should have no symbol + lda $4001 ;should have no symbol + + rts diff --git a/SourceGen/Symbol.cs b/SourceGen/Symbol.cs index 26737e5..70c6c57 100644 --- a/SourceGen/Symbol.cs +++ b/SourceGen/Symbol.cs @@ -82,7 +82,8 @@ namespace SourceGen { public int Value { get; private set; } /// - /// 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);