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.
This commit is contained in:
Andy McFadden 2019-10-02 16:26:05 -07:00
parent 6c9b8fd0e6
commit 0d9814d993
25 changed files with 894 additions and 162 deletions

View File

@ -20,6 +20,12 @@ using System.Linq;
namespace CommonUtil {
public static class Misc {
/// <summary>
/// Application identifier. This is an arbitrary string set by the application. It
/// will be included in the crash dump.
/// </summary>
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:");

View File

@ -19,9 +19,10 @@ using System.Diagnostics;
namespace CommonUtil {
/// <summary>
/// Version number container. Instances are immutable.
///
/// See https://semver.org/ for explanation of system.
/// </summary>
/// <remarks>
/// See https://semver.org/ for explanation of system.
/// </remarks>
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; }
/// <summary>
/// Pre-release version.
/// Pre-release version. Always zero when PreReleaseType is Final.
/// </summary>
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;
}
}

View File

@ -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;
}

View File

@ -59,14 +59,27 @@ namespace SourceGen {
/// <summary>
/// Platform symbols only: tag used to organize symbols into groups. Used by
/// extension scripts.
///
/// Not serialized.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public string Tag { get; private set; }
/// <summary>
/// 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.
/// </summary>
public int LoadOrdinal { get; private set; }
/// <summary>
/// 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.
/// </summary>
public string FileIdentifier { get; private set; }
/// <summary>
/// Cross-reference data, generated by the analyzer.
/// </summary>
@ -146,6 +159,23 @@ namespace SourceGen {
Tag = tag;
}
/// <summary>
/// Constructor. Used for platform symbol files.
/// </summary>
/// <param name="loadOrdinal">Indicates the order in which the defining platform
/// symbol file was loaded. Higher numbers indicate later loading, which translates
/// to higher priority.</param>
/// <param name="fileIdent">Platform symbol file identifier, for the Info panel.</param>
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;
}
/// <summary>
/// Constructor. Used for deserialization, when we have a FormatDescriptor and a Symbol.
/// </summary>

View File

@ -533,7 +533,7 @@ namespace SourceGen {
///
/// Failures here will be reported to the user but aren't fatal.
/// </summary>
/// <returns>String with all warnings from load process.</returns>
/// <returns>Multi-line string with all warnings from load process.</returns>
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.
/// </summary>
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.
/// </summary>
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 {

View File

@ -229,6 +229,10 @@ namespace SourceGen {
/// to this point so we can report fatal errors directly to the user.
/// </summary>
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
}
}

View File

@ -71,12 +71,13 @@ namespace SourceGen {
/// <summary>
/// Loads platform symbols.
/// </summary>
/// <param name="fileIdent">Relative pathname of file to open.</param>
/// <param name="fileIdent">External file identifier of symbol file.</param>
/// <param name="projectDir">Full path to project directory.</param>
/// <param name="loadOrdinal">Platform file load order.</param>
/// <param name="report">Report of warnings and errors.</param>
/// <returns>True on success (no errors), false on failure.</returns>
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

Binary file not shown.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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}}}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 {}

View File

@ -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

View File

@ -82,7 +82,8 @@ namespace SourceGen {
public int Value { get; private set; }
/// <summary>
/// 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.
/// </summary>
public Source SymbolSource { get; private set; }

View File

@ -31,42 +31,18 @@ namespace SourceGen {
new SortedList<string, Symbol>(Asm65.Label.LABEL_COMPARER);
/// <summary>
/// Same content, but ordered by value. Note the key and the value are the same object.
/// </summary>
private SortedList<Symbol, Symbol> mSymbolsByValue =
new SortedList<Symbol, Symbol>(new CompareByValue());
/// <summary>
/// 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.
/// </summary>
private class CompareByValue : IComparer<Symbol> {
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);
}
}
/// <remarks>
/// For efficiency on larger data files, we may want to break this up by bank. That
/// way we can do a partial update.
/// </remarks>
private Dictionary<int, Symbol> mSymbolsByAddress =
new Dictionary<int, Symbol>();
/// <summary>
/// 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.
/// </summary>
public int ChangeSerial { get; private set; }
//public int ChangeSerial { get; private set; }
public SymbolTable() { }
@ -96,15 +72,14 @@ namespace SourceGen {
/// </summary>
public void Clear() {
mSymbols.Clear();
mSymbolsByValue.Clear();
ChangeSerial++;
mSymbolsByAddress.Clear();
//ChangeSerial++;
}
/// <summary>
/// Returns the number of symbols in the table.
/// </summary>
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++;
}
/// <summary>
/// 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.
/// </summary>
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++;
}
}
/// <summary>
/// Searches the table for symbols with matching address values. Ignores constants and
/// variables.
/// </summary>
/// <param name="value">Value to find.</param>
/// <returns>First matching symbol found, or null if nothing matched.</returns>
public Symbol FindNonVariableByAddress(int value) {
// Get sorted list of values. This is documented as efficient.
IList<Symbol> 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;
}
/// <summary>
/// Gets the value associated with the key.
/// </summary>
@ -226,8 +147,140 @@ namespace SourceGen {
/// </summary>
public void Remove(Symbol sym) {
mSymbols.Remove(sym.Label);
mSymbolsByValue.Remove(sym);
ChangeSerial++;
RemoveAddressTableEntry(sym);
//ChangeSerial++;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="sym">Symbol to add.</param>
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;
}
}
/// <summary>
/// Replaces an entry in the address table. Must be called AFTER the by-label list
/// has been updated.
/// </summary>
/// <param name="oldSym">Symbol being replaced.</param>
/// <param name="newSym">New symbol.</param>
private void ReplaceAddressTableEntry(Symbol oldSym, Symbol newSym) {
RemoveAddressTableEntry(oldSym);
AddAddressTableEntry(newSym);
}
/// <summary>
/// Removes an entry from the address table. Must be called AFTER the by-label list
/// has been updated.
/// </summary>
/// <param name="sym">Symbol to remove.</param>
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();
}
/// <summary>
/// Regenerates the entire by-address table, from the contents of the by-label list.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
private void RegenerateAddressTable() {
Debug.WriteLine("SymbolTable: regenerating address table");
mSymbolsByAddress.Clear();
foreach (KeyValuePair<string, Symbol> kvp in mSymbols) {
AddAddressTableEntry(kvp.Value);
}
}
/// <summary>
/// Searches the table for symbols with matching address values. Ignores constants and
/// variables.
/// </summary>
/// <param name="addr">Address to find.</param>
/// <returns>First matching symbol found, or null if nothing matched.</returns>
public Symbol FindNonVariableByAddress(int addr) {
mSymbolsByAddress.TryGetValue(addr, out Symbol sym);
return sym;
}
}
}

View File

@ -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);