diff --git a/SourceGen/AsmGen/AsmAcme.cs b/SourceGen/AsmGen/AsmAcme.cs
index d1e8c4f..4ae85fe 100644
--- a/SourceGen/AsmGen/AsmAcme.cs
+++ b/SourceGen/AsmGen/AsmAcme.cs
@@ -224,8 +224,8 @@ namespace SourceGen.AsmGen {
// While '.' labels are limited to the current zone, '@' labels are visible
// between global labels. (This is poorly documented.)
mLocalizer.LocalPrefix = "@";
+ mLocalizer.QuirkNoOpcodeMnemonics = true;
mLocalizer.Analyze();
- mLocalizer.FixOpcodeLabels();
// Use UTF-8 encoding, without a byte-order mark.
using (StreamWriter sw = new StreamWriter(pathName, false, new UTF8Encoding(false))) {
diff --git a/SourceGen/AsmGen/AsmCc65.cs b/SourceGen/AsmGen/AsmCc65.cs
index 7458694..9b2f5ee 100644
--- a/SourceGen/AsmGen/AsmCc65.cs
+++ b/SourceGen/AsmGen/AsmCc65.cs
@@ -218,9 +218,9 @@ namespace SourceGen.AsmGen {
mLocalizer = new LabelLocalizer(Project);
mLocalizer.LocalPrefix = "@";
- mLocalizer.QuirkVariablesEndScope = true;
+ mLocalizer.QuirkVariablesEndScope = true; // https://github.com/cc65/cc65/issues/938
+ mLocalizer.QuirkNoOpcodeMnemonics = true;
mLocalizer.Analyze();
- mLocalizer.FixOpcodeLabels();
// Use UTF-8 encoding, without a byte-order mark.
using (StreamWriter sw = new StreamWriter(cfgName, false, new UTF8Encoding(false))) {
diff --git a/SourceGen/AsmGen/AsmMerlin32.cs b/SourceGen/AsmGen/AsmMerlin32.cs
index 2f149c1..62273d8 100644
--- a/SourceGen/AsmGen/AsmMerlin32.cs
+++ b/SourceGen/AsmGen/AsmMerlin32.cs
@@ -191,8 +191,8 @@ namespace SourceGen.AsmGen {
mLocalizer = new LabelLocalizer(Project);
mLocalizer.LocalPrefix = ":";
+ // don't need to set QuirkNoOpcodeMnemonics
mLocalizer.Analyze();
- //mLocalizer.FixOpcodeLabels();
// Use UTF-8 encoding, without a byte-order mark.
using (StreamWriter sw = new StreamWriter(pathName, false, new UTF8Encoding(false))) {
diff --git a/SourceGen/AsmGen/AsmTass64.cs b/SourceGen/AsmGen/AsmTass64.cs
index d9395b9..a634135 100644
--- a/SourceGen/AsmGen/AsmTass64.cs
+++ b/SourceGen/AsmGen/AsmTass64.cs
@@ -230,9 +230,8 @@ namespace SourceGen.AsmGen {
mLocalizer = new LabelLocalizer(Project);
mLocalizer.LocalPrefix = "_";
+ mLocalizer.QuirkNoOpcodeMnemonics = true;
mLocalizer.Analyze();
- mLocalizer.MaskLeadingUnderscores();
- mLocalizer.FixOpcodeLabels();
// Use UTF-8 encoding, without a byte-order mark.
using (StreamWriter sw = new StreamWriter(pathName, false, new UTF8Encoding(false))) {
diff --git a/SourceGen/AsmGen/LabelLocalizer.cs b/SourceGen/AsmGen/LabelLocalizer.cs
index c1a598d..69f97b0 100644
--- a/SourceGen/AsmGen/LabelLocalizer.cs
+++ b/SourceGen/AsmGen/LabelLocalizer.cs
@@ -40,7 +40,7 @@ but this would cause an error:
because the local symbol table is cleared when a global symbol is encountered.
Another common form allows backward references to labels that don't go out of scope until
-they're re-used. This is useful for short loops.
+they're re-used. This is useful for short loops. (We use this for variables.)
As a further limitation, assemblers seem to want the first label encountered in a program
to be global.
@@ -78,6 +78,10 @@ name. This must be applied to both labels and operands.
namespace SourceGen.AsmGen {
public class LabelLocalizer {
+ // Prefix string to use for labels that start with '_' when generating code for
+ // assemblers that assign a special meaning to leading underscores.
+ private const string NO_UNDER_PFX = "X";
+
///
/// A pairing of an offset with a label string. (Essentially mAnattribs[n].Symbol
/// with all the fluff trimmed away.)
@@ -85,8 +89,8 @@ namespace SourceGen.AsmGen {
///
/// The label string isn't actually all that useful, since we can pull it back out
/// of anattrib, but it makes life a little easier during debugging. These get
- /// put into a List, so switching to a plain int offset doesn't necessarily help us
- /// much because the ints get boxed.
+ /// put into a List, so simply storing a plain int offset it's much better (in terms
+ /// of memory and allocations) because the ints get boxed.
///
private class OffsetLabel {
public int Offset { get; private set; }
@@ -136,6 +140,12 @@ namespace SourceGen.AsmGen {
///
public bool QuirkVariablesEndScope { get; set; }
+ ///
+ /// Set this if global variables are not allowed to have the same name as an opcode
+ /// mnemonic.
+ ///
+ public bool QuirkNoOpcodeMnemonics { get; set; }
+
///
/// Project reference.
///
@@ -175,6 +185,8 @@ namespace SourceGen.AsmGen {
public void Analyze() {
Debug.Assert(LocalPrefix.Length > 0);
+ // Init global flags list. An entry is set if the associated offset has a global
+ // label. It will be false if the entry has a local label, or no label.
mGlobalFlags.SetAll(false);
// Currently we only support the "local labels have scope that ends at a global
@@ -191,34 +203,142 @@ namespace SourceGen.AsmGen {
//
// The current algorithm uses a straightforward O(n^2) approach.
+ //
// Step 1: generate source/target pairs and global label list
+ //
GenerateLists();
+ //
// Step 2: walk through the list of global symbols, identifying source/target
// pairs that cross them. If a pair matches, the target label is added to the
- // mGlobalLabels list, and removed from the pair list.
+ // end of the mGlobalLabels list, and removed from the pair list.
+ //
+ // When we're done, mGlobalFlags[] will identify the offsets with global labels.
+ //
for (int index = 0; index < mGlobalLabels.Count; index++) {
FindIntersectingPairs(mGlobalLabels[index]);
}
- // Step 3: for each local label, add an entry to the map with the appropriate
- // local-label syntax.
+ // We're done with these. Take out the trash.
+ mGlobalLabels.Clear();
+ mOffsetPairs.Clear();
+
+ //
+ // Step 3: remap global labels. There are three reasons we might need to do this:
+ // (1) It has a leading underscore AND LocalPrefix is '_'.
+ // (2) The label matches an opcode mnemonic (case-insensitive) AND NoOpcodeMnemonics
+ // is set.
+ // (3) It's a non-unique local that got promoted to global.
+ //
+ // In each case we need to modify the label to meet the assembler requirements, and
+ // then modify the label until it's unique.
+ //
LabelMap = new Dictionary();
+ Dictionary allGlobalLabels = new Dictionary();
+ bool remapUnders = (LocalPrefix == "_");
+
+ Dictionary opNames = null;
+ if (QuirkNoOpcodeMnemonics) {
+ // Create a searchable list of opcode names using the current CPU definition.
+ // (All tested assemblers that failed on opcode names only did so for names
+ // that were part of the current definition, e.g. "TSB" was accepted as a label
+ // when the CPU was set to 6502.)
+ opNames = new Dictionary();
+ Asm65.CpuDef cpuDef = mProject.CpuDef;
+ for (int i = 0; i < 256; i++) {
+ Asm65.OpDef op = cpuDef.GetOpDef(i);
+ // There may be multiple entries with the same name (e.g. "NOP"). That's fine.
+ opNames[op.Mnemonic.ToUpperInvariant()] = op;
+ }
+ }
+
for (int i = 0; i < mProject.FileDataLength; i++) {
- if (mGlobalFlags[i]) {
+ if (!mGlobalFlags[i]) {
continue;
}
Symbol sym = mProject.GetAnattrib(i).Symbol;
if (sym == null) {
+ // Should only happen when we insert a dummy global label for the
+ // "variables end scope" quirk.
continue;
}
- LabelMap[sym.Label] = LocalPrefix + sym.Label;
+ string newLabel = sym.LabelWithoutTag;
+ if (remapUnders && newLabel[0] == '_') {
+ newLabel = NO_UNDER_PFX + newLabel;
+ // This could cause a conflict with an existing label. It's rare but
+ // possible.
+ if (allGlobalLabels.ContainsKey(newLabel)) {
+ newLabel = MakeUnique(newLabel, allGlobalLabels);
+ }
+ }
+ if (opNames != null && opNames.ContainsKey(newLabel.ToUpperInvariant())) {
+ // Clashed with mnemonic. Uniquify it.
+ newLabel = MakeUnique(newLabel, allGlobalLabels);
+ }
+
+ // We might have remapped something earlier and it happens to match this label.
+ // If so, we can either remap the current label, or remap the previous label
+ // a little harder. The easiest thing to do is remap the current label.
+ if (allGlobalLabels.ContainsKey(newLabel)) {
+ newLabel = MakeUnique(newLabel, allGlobalLabels);
+ }
+
+ // If we've changed it, add it to the map.
+ if (newLabel != sym.Label) {
+ LabelMap[sym.Label] = newLabel;
+ }
+
+ allGlobalLabels.Add(newLabel, newLabel);
}
- // Take out the trash.
- mGlobalLabels.Clear();
- mOffsetPairs.Clear();
+ //
+ // Step 4: remap local labels. There are two operations here.
+ //
+ // For each pair of global labels that have locals between them, we need to walk
+ // through the locals and confirm that they don't clash with each other. If they
+ // do, we need to uniquify them within the local scope. (This is only an issue
+ // for non-unique locals.)
+ //
+ // Once a unique name has been found, we add an entry to LabelMap that has the
+ // label with the LocalPrefix and without the non-unique tag.
+ //
+ // We also need to deal with symbols with a leading underscore when
+ // LocalPrefix is '_'.
+ //
+ int startGlobal = -1;
+ int numLocals = 0;
+
+ // Allocate a Dictionary here and pass it through so we don't have to allocate
+ // a new one each time.
+ Dictionary scopedLocals = new Dictionary();
+ for (int i = 0; i < mProject.FileDataLength; i++) {
+ if (mGlobalFlags[i]) {
+ if (startGlobal < 0) {
+ // very first one
+ startGlobal = i;
+ continue;
+ } else if (numLocals > 0) {
+ // There were locals following the previous global. Process them.
+ ProcessLocals(startGlobal, i, scopedLocals);
+ startGlobal = i;
+ numLocals = 0;
+ } else {
+ // Two adjacent globals.
+ startGlobal = i;
+ }
+ } else {
+ // Not a global. Is there a local symbol here?
+ Symbol sym = mProject.GetAnattrib(i).Symbol;
+ if (sym != null) {
+ numLocals++;
+ }
+ }
+ }
+ if (numLocals != 0) {
+ // do the last bit
+ ProcessLocals(startGlobal, mProject.FileDataLength, scopedLocals);
+ }
}
///
@@ -237,10 +357,11 @@ namespace SourceGen.AsmGen {
bool first = true;
for (int offset = 0; offset < mProject.FileDataLength; offset++) {
+ // Find all user labels and auto labels.
Symbol sym = mProject.GetAnattrib(offset).Symbol;
// In cc65, variable declarations end the local label scope. We insert a
- // fake global symbol if we counter a table with a nonzero number of entries.
+ // fake global symbol if we encounter a table with a nonzero number of entries.
if (QuirkVariablesEndScope &&
mProject.LvTables.TryGetValue(offset, out LocalVariableTable value) &&
value.Count > 0) {
@@ -253,7 +374,7 @@ namespace SourceGen.AsmGen {
continue;
}
- if (first || sym.SymbolType != Symbol.Type.LocalOrGlobalAddr) {
+ if (first || !sym.CanBeLocal) {
first = false;
mGlobalFlags[offset] = true;
mGlobalLabels.Add(new OffsetLabel(offset, sym.Label));
@@ -285,12 +406,11 @@ namespace SourceGen.AsmGen {
private void FindIntersectingPairs(OffsetLabel glabel) {
Debug.Assert(mGlobalFlags[glabel.Offset]);
- int globOffset = glabel.Offset;
+ int glabOffset = glabel.Offset;
for (int i = 0; i < mOffsetPairs.Count; i++) {
OffsetPair pair = mOffsetPairs[i];
- // If the destination was marked global earlier, remove and ignore this entry.
- // Note this also means that pair.DstOffset != label.Offset.
+ // If the destination was marked global earlier, remove the entry and move on.
if (mGlobalFlags[pair.DstOffset]) {
mOffsetPairs.RemoveAt(i);
i--;
@@ -301,15 +421,16 @@ namespace SourceGen.AsmGen {
// offsets.
//
// If the reference source is itself a global label, it can reference local
- // labels forward, but not backward. We need to take that into account for
- // the case where label.Offset==pair.SrcOffset.
+ // labels forward, but not backward (i.e. if it crosses itself, the destination
+ // must be made global). We need to take that into account for the case where
+ // label.Offset==pair.SrcOffset.
bool intersect;
if (pair.SrcOffset < pair.DstOffset) {
- // Forward reference. src==glob is ok
- intersect = pair.SrcOffset < globOffset && pair.DstOffset >= globOffset;
+ // Forward reference. src==glab is ok
+ intersect = pair.SrcOffset < glabOffset && pair.DstOffset >= glabOffset;
} else {
- // Backward reference. src==glob is bad
- intersect = pair.SrcOffset >= globOffset && pair.DstOffset <= globOffset;
+ // Backward reference. src==glab is bad
+ intersect = pair.SrcOffset >= glabOffset && pair.DstOffset <= glabOffset;
}
if (intersect) {
@@ -329,163 +450,67 @@ namespace SourceGen.AsmGen {
}
///
- /// Adjusts the label map so that only local variables start with an underscore ('_').
- /// This is necessary for assemblers like 64tass that use a leading underscore to
- /// indicate that a label should be local.
- ///
- /// Only call this if underscores are used to indicate local labels.
+ /// Generates map entries for local labels defined between the two globals.
///
- ///
- /// This may be called even if label localization is disabled. In that case we just
- /// create an empty label map and populate as needed.
- ///
- public void MaskLeadingUnderscores() {
- bool allGlobal = false;
- if (LabelMap == null) {
- allGlobal = true;
- LabelMap = new Dictionary();
- }
+ /// Offset of first global.
+ /// Offset of second global. If this range is at the end of the
+ /// file, this offset may be one past the end.
+ /// Work object (minor alloc optimization).
+ private void ProcessLocals(int startGlobal, int endGlobal,
+ Dictionary scopedLocals) {
+ //Debug.WriteLine("ProcessLocals: +" + startGlobal.ToString("x6") +
+ // " - +" + endGlobal.ToString("x6"));
+ scopedLocals.Clear();
+ bool remapUnders = (LocalPrefix == "_");
- // Throw out the original local label generation. We're going to redo the
- // label map generation step.
- LabelMap.Clear();
-
- // Use this to test for uniqueness. We add all labels here as we go, not just the
- // ones being remapped. For each label we either add the original or the localized
- // form.
- SortedList allLabels = new SortedList();
-
- for (int i = 0; i < mProject.FileDataLength; i++) {
+ for (int i = startGlobal + 1; i < endGlobal; i++) {
+ Debug.Assert(!mGlobalFlags[i]);
Symbol sym = mProject.GetAnattrib(i).Symbol;
if (sym == null) {
- // No label at this offset.
- continue;
+ continue; // no label here
}
- string newLabel;
- if (allGlobal || mGlobalFlags[i]) {
- // Global symbol. Don't let it start with '_'.
- if (sym.Label.StartsWith("_")) {
- // There's an underscore here that was added by the user. Stick some
- // other character in front.
- newLabel = "X" + sym.Label;
- } else {
- // No change needed.
- newLabel = sym.Label;
- }
+ string newLabel = sym.LabelWithoutTag;
+ if (remapUnders && newLabel[0] == '_') {
+ newLabel = LocalPrefix + NO_UNDER_PFX + newLabel;
} else {
- // Local symbol.
- if (sym.Label.StartsWith("_")) {
- // The original starts with one or more underscores. Adding another
- // will create a "__" label, which is reserved in 64tass.
- newLabel = "_X" + sym.Label;
- } else {
- newLabel = "_" + sym.Label;
- }
+ newLabel = LocalPrefix + newLabel;
}
- // Make sure it's unique.
- string uniqueLabel = newLabel;
- int uval = 0;
- while (allLabels.ContainsKey(uniqueLabel)) {
- uval++;
- uniqueLabel = newLabel + uval.ToString();
+ if (scopedLocals.ContainsKey(newLabel)) {
+ newLabel = MakeUnique(newLabel, scopedLocals);
}
- allLabels.Add(uniqueLabel, uniqueLabel);
- // If it's different, add it to the label map.
- if (sym.Label != uniqueLabel) {
- LabelMap.Add(sym.Label, uniqueLabel);
- }
+ // Map from the original symbol label to the local form. This works for
+ // unique and non-unique locals.
+ LabelMap[sym.Label] = newLabel;
+
+ scopedLocals.Add(newLabel, newLabel);
}
-
- Debug.WriteLine("UMAP: allcount=" + allLabels.Count + " mapcount=" + LabelMap.Count);
}
///
- /// Remaps labels that match opcode names. Updated names will be added to LabelMap.
- /// This should be run after localization and underscore concealment have finished.
+ /// Alters a label to make it unique. This may be called with a label that is unique
+ /// but illegal (e.g. an instruction mnemonic), so we guarantee that the label returned
+ /// is different from the argument.
///
///
- /// Most assemblers don't like it if you create a label with the same name as an
- /// opcode, e.g. "jmp LSR" doesn't work. We can use the label map to work around
- /// the issue.
- ///
- /// Most assemblers regard mnemonics as case-insensitive, even if labels are
- /// case-sensitive, so we want to remap both "lsr" and "LSR".
- ///
- /// This doesn't really have anything to do with label localization other than that
- /// we're updating the label remap table.
+ /// We can't put a '_' at the front or an 'L' at the end (LDAL), since that could run
+ /// afoul of the things we're trying to work around. We don't want to mess with the
+ /// start of the string since it may or may not have the LocalPrefix on it.
///
- public void FixOpcodeLabels() {
- if (LabelMap == null) {
- LabelMap = new Dictionary();
- }
+ /// Label to uniquify.
+ /// Dictionary to uniquify against.
+ /// Modified label
+ private static string MakeUnique(string label, Dictionary allLabels) {
+ int uval = 0;
+ string uniqueLabel;
+ do {
+ uval++;
+ uniqueLabel = label + uval.ToString();
+ } while (allLabels.ContainsKey(uniqueLabel));
- // Create a searchable list of opcode names using the current CPU definition.
- // (All tested assemblers that failed on opcode names only did so for names
- // that were part of the current definition, e.g. "TSB" was accepted as a label
- // when the CPU was set to 6502.)
- Dictionary opnames = new Dictionary();
- Asm65.CpuDef cpuDef = mProject.CpuDef;
- for (int i = 0; i < 256; i++) {
- Asm65.OpDef op = cpuDef.GetOpDef(i);
- // There may be multiple entries with the same name (e.g. "NOP"). That's fine.
- opnames[op.Mnemonic.ToUpperInvariant()] = op;
- }
-
- // Create a list of all labels, for uniqueness testing. If a label has been
- // remapped, we add the remapped entry.
- // (All tested assemblers that failed on opcode names only did so for names
- // in their non-localized form. While "LSR" failed, "@LSR", "_LSR", ".LSR", etc.
- // were accepted. So if it was remapped by the localizer, we don't need to
- // worry about it.)
- SortedList allLabels = new SortedList();
- for (int i = 0; i < mProject.FileDataLength; i++) {
- Symbol sym = mProject.GetAnattrib(i).Symbol;
- if (sym == null) {
- continue;
- }
- LabelMap.TryGetValue(sym.Label, out string mapLabel);
- if (mapLabel != null) {
- allLabels.Add(mapLabel, mapLabel);
- } else {
- allLabels.Add(sym.Label, sym.Label);
- }
- }
-
- // Now run through the list of labels, looking for any that match opcode
- // mnemonics.
- for (int i = 0; i < mProject.FileDataLength; i++) {
- Symbol sym = mProject.GetAnattrib(i).Symbol;
- if (sym == null) {
- // No label at this offset.
- continue;
- }
- string cmpLabel = sym.Label;
- if (LabelMap.TryGetValue(sym.Label, out string mapLabel)) {
- cmpLabel = mapLabel;
- }
-
- if (opnames.ContainsKey(cmpLabel.ToUpperInvariant())) {
- //Debug.WriteLine("Remapping label (op mnemonic): " + sym.Label);
-
- int uval = 0;
- string uniqueLabel;
- do {
- uval++;
- uniqueLabel = cmpLabel + "_" + uval.ToString();
- } while (allLabels.ContainsKey(uniqueLabel));
-
- allLabels.Add(uniqueLabel, uniqueLabel);
- LabelMap.Add(sym.Label, uniqueLabel);
- }
- }
-
- if (LabelMap.Count == 0) {
- // didn't do anything, lose the table
- LabelMap = null;
- }
+ return uniqueLabel;
}
}
}
diff --git a/SourceGen/SGTestData/2023-non-unique-labels b/SourceGen/SGTestData/2023-non-unique-labels
new file mode 100644
index 0000000..943bae0
Binary files /dev/null and b/SourceGen/SGTestData/2023-non-unique-labels differ
diff --git a/SourceGen/SGTestData/2023-non-unique-labels.dis65 b/SourceGen/SGTestData/2023-non-unique-labels.dis65
new file mode 100644
index 0000000..0d036e1
--- /dev/null
+++ b/SourceGen/SGTestData/2023-non-unique-labels.dis65
@@ -0,0 +1,149 @@
+### 6502bench SourceGen dis65 v1.0 ###
+{
+"_ContentVersion":3,"FileDataLength":154,"FileDataCrc32":-1096720699,"ProjectProps":{
+"CpuName":"6502","IncludeUndocumentedInstr":false,"TwoByteBrk":false,"EntryFlags":32702671,"AutoLabelStyle":"Simple","AnalysisParams":{
+"AnalyzeUncategorizedData":true,"DefaultTextScanMode":"LowHighAscii","MinCharsForString":4,"SeekNearbyTargets":true,"SmartPlpHandling":true},
+"PlatformSymbolFileIdentifiers":[],"ExtensionScriptFileIdentifiers":[],"ProjectSyms":{
+}},
+"AddressMap":[{
+"Offset":0,"Addr":4096}],"TypeHints":[{
+"Low":0,"High":0,"Hint":"Code"},
+{
+"Low":149,"High":152,"Hint":"InlineData"}],"StatusFlagOverrides":{
+},
+"Comments":{
+},
+"LongComments":{
+},
+"Notes":{
+},
+"UserLabels":{
+"2":{
+"Label":"L1000","Value":4098,"Source":"User","Type":"NonUniqueLocalAddr","LabelAnno":"None"},
+"12":{
+"Label":"loop1","Value":4108,"Source":"User","Type":"GlobalAddr","LabelAnno":"None"},
+"17":{
+"Label":"loop1","Value":4113,"Source":"User","Type":"NonUniqueLocalAddr","LabelAnno":"None"},
+"20":{
+"Label":"global1","Value":4116,"Source":"User","Type":"GlobalAddr","LabelAnno":"None"},
+"23":{
+"Label":"loop","Value":4119,"Source":"User","Type":"NonUniqueLocalAddr","LabelAnno":"None"},
+"25":{
+"Label":"loop","Value":4121,"Source":"User","Type":"NonUniqueLocalAddr","LabelAnno":"None"},
+"34":{
+"Label":"global2","Value":4130,"Source":"User","Type":"GlobalAddr","LabelAnno":"None"},
+"35":{
+"Label":"loop","Value":4131,"Source":"User","Type":"NonUniqueLocalAddr","LabelAnno":"None"},
+"36":{
+"Label":"global3","Value":4132,"Source":"User","Type":"GlobalAddr","LabelAnno":"None"},
+"47":{
+"Label":"fwd1","Value":4143,"Source":"User","Type":"NonUniqueLocalAddr","LabelAnno":"None"},
+"48":{
+"Label":"fwd2","Value":4144,"Source":"User","Type":"NonUniqueLocalAddr","LabelAnno":"None"},
+"49":{
+"Label":"global4","Value":4145,"Source":"User","Type":"GlobalAddr","LabelAnno":"None"},
+"52":{
+"Label":"loop","Value":4148,"Source":"User","Type":"NonUniqueLocalAddr","LabelAnno":"None"},
+"53":{
+"Label":"global5","Value":4149,"Source":"User","Type":"GlobalAddr","LabelAnno":"None"},
+"57":{
+"Label":"global6","Value":4153,"Source":"User","Type":"GlobalAddr","LabelAnno":"None"},
+"58":{
+"Label":"spin1","Value":4154,"Source":"User","Type":"NonUniqueLocalAddr","LabelAnno":"None"},
+"61":{
+"Label":"spin2","Value":4157,"Source":"User","Type":"NonUniqueLocalAddr","LabelAnno":"None"},
+"65":{
+"Label":"spin1","Value":4161,"Source":"User","Type":"NonUniqueLocalAddr","LabelAnno":"None"},
+"90":{
+"Label":"skip","Value":4186,"Source":"User","Type":"NonUniqueLocalAddr","LabelAnno":"None"},
+"91":{
+"Label":"global_","Value":4187,"Source":"User","Type":"GlobalAddr","LabelAnno":"None"},
+"92":{
+"Label":"_global","Value":4188,"Source":"User","Type":"GlobalAddr","LabelAnno":"None"},
+"94":{
+"Label":"__","Value":4190,"Source":"User","Type":"GlobalAddr","LabelAnno":"None"},
+"99":{
+"Label":"___","Value":4195,"Source":"User","Type":"GlobalAddr","LabelAnno":"None"},
+"101":{
+"Label":"__","Value":4197,"Source":"User","Type":"NonUniqueLocalAddr","LabelAnno":"None"},
+"105":{
+"Label":"anno","Value":4201,"Source":"User","Type":"LocalOrGlobalAddr","LabelAnno":"Uncertain"},
+"107":{
+"Label":"T106B","Value":4203,"Source":"User","Type":"LocalOrGlobalAddr","LabelAnno":"Generated"},
+"115":{
+"Label":"skip","Value":4211,"Source":"User","Type":"NonUniqueLocalAddr","LabelAnno":"None"},
+"116":{
+"Label":"JMP","Value":4212,"Source":"User","Type":"GlobalAddr","LabelAnno":"None"},
+"119":{
+"Label":"JMP0","Value":4215,"Source":"User","Type":"GlobalAddr","LabelAnno":"None"},
+"122":{
+"Label":"JMP1","Value":4218,"Source":"User","Type":"GlobalAddr","LabelAnno":"None"},
+"125":{
+"Label":"JMP","Value":4221,"Source":"User","Type":"NonUniqueLocalAddr","LabelAnno":"None"},
+"128":{
+"Label":"JMP0","Value":4224,"Source":"User","Type":"NonUniqueLocalAddr","LabelAnno":"None"},
+"131":{
+"Label":"JMP1","Value":4227,"Source":"User","Type":"NonUniqueLocalAddr","LabelAnno":"None"},
+"134":{
+"Label":"JMP","Value":4230,"Source":"User","Type":"NonUniqueLocalAddr","LabelAnno":"None"},
+"137":{
+"Label":"jmp","Value":4233,"Source":"User","Type":"GlobalAddr","LabelAnno":"None"},
+"140":{
+"Label":"Jmp","Value":4236,"Source":"User","Type":"GlobalAddr","LabelAnno":"None"},
+"143":{
+"Label":"BRA","Value":4239,"Source":"User","Type":"GlobalAddr","LabelAnno":"None"},
+"146":{
+"Label":"brl","Value":4242,"Source":"User","Type":"GlobalAddr","LabelAnno":"None"},
+"149":{
+"Label":"LDAL","Value":4245,"Source":"User","Type":"GlobalAddr","LabelAnno":"None"}},
+"OperandFormats":{
+"58":{
+"Length":3,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{
+"Label":"spin2§00003d","Part":"Low"}},
+"61":{
+"Length":3,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{
+"Label":"spin1§00003a","Part":"Low"}},
+"65":{
+"Length":3,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{
+"Label":"spin1§00003a","Part":"Low"}},
+"68":{
+"Length":2,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{
+"Label":"spin1§000041","Part":"Low"}},
+"70":{
+"Length":2,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{
+"Label":"spin1§00003a","Part":"Low"}},
+"72":{
+"Length":2,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{
+"Label":"spin2§00003d","Part":"Low"}},
+"74":{
+"Length":2,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{
+"Label":"spin1§00003a","Part":"High"}},
+"76":{
+"Length":2,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{
+"Label":"spin2§00003d","Part":"High"}},
+"80":{
+"Length":2,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{
+"Label":"spin1§00003a","Part":"Low"}},
+"82":{
+"Length":2,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{
+"Label":"spin2§00003d","Part":"Low"}},
+"84":{
+"Length":2,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{
+"Label":"spin1§000041","Part":"Low"}},
+"86":{
+"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{
+"Label":"spin1§00003a","Part":"Low"}},
+"87":{
+"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{
+"Label":"spin2§00003d","Part":"Low"}},
+"88":{
+"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{
+"Label":"spin1§00003a","Part":"High"}},
+"89":{
+"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{
+"Label":"spin2§00003d","Part":"High"}},
+"113":{
+"Length":2,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{
+"Label":"T106B","Part":"Low"}}},
+"LvTables":{
+}}
diff --git a/SourceGen/SGTestData/Expected/2012-label-localizer_64tass.S b/SourceGen/SGTestData/Expected/2012-label-localizer_64tass.S
index da0408d..15b7a5e 100644
--- a/SourceGen/SGTestData/Expected/2012-label-localizer_64tass.S
+++ b/SourceGen/SGTestData/Expected/2012-label-localizer_64tass.S
@@ -64,9 +64,9 @@ _X_uname2
nop
lda #$00
_AND bne _AND ;local
-JMP_1 bne JMP_1 ;global
-jmp_1 bne jmp_1
-TSB_1 bne TSB_1
+JMP1 bne JMP1 ;global
+jmp1 bne jmp1
+TSB1 bne TSB1
XCE bne XCE
rts
diff --git a/SourceGen/SGTestData/Expected/2012-label-localizer_acme.S b/SourceGen/SGTestData/Expected/2012-label-localizer_acme.S
index 26b8546..98d3c84 100644
--- a/SourceGen/SGTestData/Expected/2012-label-localizer_acme.S
+++ b/SourceGen/SGTestData/Expected/2012-label-localizer_acme.S
@@ -62,9 +62,9 @@ EXCESSIVELY_LONG_LABEL
nop
lda #$00
@AND bne @AND ;local
-JMP_1 bne JMP_1 ;global
-jmp_1 bne jmp_1
-TSB_1 bne TSB_1
+JMP1 bne JMP1 ;global
+jmp1 bne jmp1
+TSB1 bne TSB1
XCE bne XCE
rts
diff --git a/SourceGen/SGTestData/Expected/2012-label-localizer_cc65.S b/SourceGen/SGTestData/Expected/2012-label-localizer_cc65.S
index a01ca1e..fcbea3b 100644
--- a/SourceGen/SGTestData/Expected/2012-label-localizer_cc65.S
+++ b/SourceGen/SGTestData/Expected/2012-label-localizer_cc65.S
@@ -63,9 +63,9 @@ EXCESSIVELY_LONG_LABEL:
nop
lda #$00
@AND: bne @AND ;local
-JMP_1: bne JMP_1 ;global
-jmp_1: bne jmp_1
-TSB_1: bne TSB_1
+JMP1: bne JMP1 ;global
+jmp1: bne jmp1
+TSB1: bne TSB1
XCE: bne XCE
rts
diff --git a/SourceGen/SGTestData/Expected/2023-non-unique-labels_64tass.S b/SourceGen/SGTestData/Expected/2023-non-unique-labels_64tass.S
new file mode 100644
index 0000000..b978eb8
--- /dev/null
+++ b/SourceGen/SGTestData/Expected/2023-non-unique-labels_64tass.S
@@ -0,0 +1,95 @@
+ .cpu "6502"
+* = $1000
+L1000 lda #$00
+_L1000 lda #$01
+ ldx L1000
+ ldy _L1000
+ ldx #$02
+loop1 dex
+ bne loop1
+ ldx #$03
+_loop1 dex
+ bne _loop1
+global1 nop
+ ldx #$04
+_loop ldy #$05
+_loop1 dey
+ bne _loop1
+ dex
+ bne _loop
+ jmp loop
+
+global2 .byte $ea
+
+loop nop
+global3 nop
+ ldx #$06
+ ldy #$07
+ dex
+ beq _fwd1
+ dey
+ beq _fwd2
+_fwd1 nop
+_fwd2 nop
+global4 nop
+ ldx #$08
+loop2 dex
+global5 nop
+ bne loop2
+ nop
+global6 nop
+_spin1 jsr _spin2
+_spin2 jsr _spin1
+ nop
+_spin11 lda _spin1+7
+ beq _spin11
+ lda #<_spin1
+ ldx #<_spin2
+ lda #>_spin1
+ ldx #>_spin2
+ bne _skip
+
+ .word _spin1
+ .word _spin2
+ .word _spin11
+ .byte <_spin1
+ .byte <_spin2
+ .byte >_spin1
+ .byte >_spin2
+
+_skip nop
+global_ nop
+X_global ldx #$40
+X__ dex
+ bne X__
+ beq X___
+
+X___ ldx #$41
+_X__ dex
+ bne _X__
+ nop
+_anno lda #$42
+_T106B lda _anno
+ clc
+ bcc _skip
+
+ .word _T106B
+
+_skip nop
+JMP1 lda JMP1
+JMP0 lda JMP0
+JMP11 lda JMP11
+_JMP lda _JMP
+_JMP0 lda _JMP0
+_JMP1 lda _JMP1
+_JMP2 lda _JMP2
+jmp1 lda jmp1
+Jmp1 lda Jmp1
+BRA lda BRA
+brl lda brl
+LDAL .byte $af
+ .byte $95
+ .byte $10
+ .byte $00
+ rts
+
diff --git a/SourceGen/SGTestData/Expected/2023-non-unique-labels_Merlin32.S b/SourceGen/SGTestData/Expected/2023-non-unique-labels_Merlin32.S
new file mode 100644
index 0000000..9e29dea
--- /dev/null
+++ b/SourceGen/SGTestData/Expected/2023-non-unique-labels_Merlin32.S
@@ -0,0 +1,94 @@
+ org $1000
+L1000 lda #$00
+:L1000 lda #$01
+ ldx L1000
+ ldy :L1000
+ ldx #$02
+loop1 dex
+ bne loop1
+ ldx #$03
+:loop1 dex
+ bne :loop1
+global1 nop
+ ldx #$04
+:loop ldy #$05
+:loop1 dey
+ bne :loop1
+ dex
+ bne :loop
+ jmp loop
+
+global2 dfb $ea
+
+loop nop
+global3 nop
+ ldx #$06
+ ldy #$07
+ dex
+ beq :fwd1
+ dey
+ beq :fwd2
+:fwd1 nop
+:fwd2 nop
+global4 nop
+ ldx #$08
+loop2 dex
+global5 nop
+ bne loop2
+ nop
+global6 nop
+:spin1 jsr :spin2
+:spin2 jsr :spin1
+ nop
+:spin11 lda :spin1+7
+ beq :spin11
+ lda #<:spin1
+ ldx #<:spin2
+ lda #>:spin1
+ ldx #>:spin2
+ bne :skip
+
+ dw :spin1
+ dw :spin2
+ dw :spin11
+ dfb <:spin1
+ dfb <:spin2
+ dfb >:spin1
+ dfb >:spin2
+
+:skip nop
+global_ nop
+_global ldx #$40
+__ dex
+ bne __
+ beq ___
+
+___ ldx #$41
+:__ dex
+ bne :__
+ nop
+:anno lda #$42
+:T106B lda :anno
+ clc
+ bcc :skip
+
+ dw :T106B
+
+:skip nop
+JMP lda JMP
+JMP0 lda JMP0
+JMP1 lda JMP1
+:JMP lda :JMP
+:JMP0 lda :JMP0
+:JMP1 lda :JMP1
+:JMP2 lda :JMP2
+jmp lda jmp
+Jmp lda Jmp
+BRA lda BRA
+brl lda brl
+LDAL dfb $af
+ dfb $95
+ dfb $10
+ dfb $00
+ rts
+
diff --git a/SourceGen/SGTestData/Expected/2023-non-unique-labels_acme.S b/SourceGen/SGTestData/Expected/2023-non-unique-labels_acme.S
new file mode 100644
index 0000000..b4c36ef
--- /dev/null
+++ b/SourceGen/SGTestData/Expected/2023-non-unique-labels_acme.S
@@ -0,0 +1,95 @@
+ !cpu 6502
+* = $1000
+L1000 lda #$00
+@L1000 lda #$01
+ ldx L1000
+ ldy @L1000
+ ldx #$02
+loop1 dex
+ bne loop1
+ ldx #$03
+@loop1 dex
+ bne @loop1
+global1 nop
+ ldx #$04
+@loop ldy #$05
+@loop1 dey
+ bne @loop1
+ dex
+ bne @loop
+ jmp loop
+
+global2 !byte $ea
+
+loop nop
+global3 nop
+ ldx #$06
+ ldy #$07
+ dex
+ beq @fwd1
+ dey
+ beq @fwd2
+@fwd1 nop
+@fwd2 nop
+global4 nop
+ ldx #$08
+loop2 dex
+global5 nop
+ bne loop2
+ nop
+global6 nop
+@spin1 jsr @spin2
+@spin2 jsr @spin1
+ nop
+@spin11 lda @spin1+7
+ beq @spin11
+ lda #<@spin1
+ ldx #<@spin2
+ lda #>@spin1
+ ldx #>@spin2
+ bne @skip
+
+ !word @spin1
+ !word @spin2
+ !word @spin11
+ !byte <@spin1
+ !byte <@spin2
+ !byte >@spin1
+ !byte >@spin2
+
+@skip nop
+global_ nop
+_global ldx #$40
+__ dex
+ bne __
+ beq ___
+
+___ ldx #$41
+@__ dex
+ bne @__
+ nop
+@anno lda #$42
+@T106B lda @anno
+ clc
+ bcc @skip
+
+ !word @T106B
+
+@skip nop
+JMP1 lda JMP1
+JMP0 lda JMP0
+JMP11 lda JMP11
+@JMP lda @JMP
+@JMP0 lda @JMP0
+@JMP1 lda @JMP1
+@JMP2 lda @JMP2
+jmp1 lda jmp1
+Jmp1 lda Jmp1
+BRA lda BRA
+brl lda brl
+LDAL !byte $af
+ !byte $95
+ !byte $10
+ !byte $00
+ rts
+
diff --git a/SourceGen/SGTestData/Expected/2023-non-unique-labels_cc65.S b/SourceGen/SGTestData/Expected/2023-non-unique-labels_cc65.S
new file mode 100644
index 0000000..4231591
--- /dev/null
+++ b/SourceGen/SGTestData/Expected/2023-non-unique-labels_cc65.S
@@ -0,0 +1,96 @@
+ .setcpu "6502"
+; .segment "SEG000"
+ .org $1000
+L1000: lda #$00
+@L1000: lda #$01
+ ldx L1000
+ ldy @L1000
+ ldx #$02
+loop1: dex
+ bne loop1
+ ldx #$03
+@loop1: dex
+ bne @loop1
+global1: nop
+ ldx #$04
+@loop: ldy #$05
+@loop1: dey
+ bne @loop1
+ dex
+ bne @loop
+ jmp loop
+
+global2: .byte $ea
+
+loop: nop
+global3: nop
+ ldx #$06
+ ldy #$07
+ dex
+ beq @fwd1
+ dey
+ beq @fwd2
+@fwd1: nop
+@fwd2: nop
+global4: nop
+ ldx #$08
+loop2: dex
+global5: nop
+ bne loop2
+ nop
+global6: nop
+@spin1: jsr @spin2
+@spin2: jsr @spin1
+ nop
+@spin11: lda @spin1+7
+ beq @spin11
+ lda #<@spin1
+ ldx #<@spin2
+ lda #>@spin1
+ ldx #>@spin2
+ bne @skip
+
+ .word @spin1
+ .word @spin2
+ .word @spin11
+ .byte <@spin1
+ .byte <@spin2
+ .byte >@spin1
+ .byte >@spin2
+
+@skip: nop
+global_: nop
+_global: ldx #$40
+__: dex
+ bne __
+ beq ___
+
+___: ldx #$41
+@__: dex
+ bne @__
+ nop
+@anno: lda #$42
+@T106B: lda @anno
+ clc
+ bcc @skip
+
+ .word @T106B
+
+@skip: nop
+JMP1: lda JMP1
+JMP0: lda JMP0
+JMP11: lda JMP11
+@JMP: lda @JMP
+@JMP0: lda @JMP0
+@JMP1: lda @JMP1
+@JMP2: lda @JMP2
+jmp1: lda jmp1
+Jmp1: lda Jmp1
+BRA: lda BRA
+brl: lda brl
+LDAL: .byte $af
+ .byte $95
+ .byte $10
+ .byte $00
+ rts
+
diff --git a/SourceGen/SGTestData/Expected/2023-non-unique-labels_cc65.cfg b/SourceGen/SGTestData/Expected/2023-non-unique-labels_cc65.cfg
new file mode 100644
index 0000000..6c6ed08
--- /dev/null
+++ b/SourceGen/SGTestData/Expected/2023-non-unique-labels_cc65.cfg
@@ -0,0 +1,11 @@
+# 6502bench SourceGen generated linker script for 2023-non-unique-labels
+MEMORY {
+ MAIN: file=%O, start=%S, size=65536;
+# MEM000: file=%O, start=$1000, size=154;
+}
+SEGMENTS {
+ CODE: load=MAIN, type=rw;
+# SEG000: load=MEM000, type=rw;
+}
+FEATURES {}
+SYMBOLS {}
diff --git a/SourceGen/SGTestData/Source/2023-non-unique-labels.S b/SourceGen/SGTestData/Source/2023-non-unique-labels.S
index d47fcca..23edf76 100644
--- a/SourceGen/SGTestData/Source/2023-non-unique-labels.S
+++ b/SourceGen/SGTestData/Source/2023-non-unique-labels.S
@@ -3,6 +3,8 @@
;
; Assembler: Merlin 32
+; NOTE: configure for plain 6502
+
org $1000
; Test conflict with auto-label.
@@ -47,7 +49,7 @@ global3 nop ;EDIT
:fwd1 nop ;EDIT
:fwd2 nop ;EDIT
-; Test loop with a global in the middle.
+; Test loop with an unreferenced global in the middle.
global4 nop ;EDIT
ldx #$08
gloop dex ;EDIT: local, name "loop"
@@ -57,12 +59,13 @@ global5 nop
nop
; Test symbolic references.
+; NOTE: start them as spin1/spin2/spin3, then rename spin3 to spin1
global6 nop
:spin1 jsr :spin2 ;EDIT: local, name "spin1", operand ref to ":spin2"
:spin2 jsr :spin1 ;EDIT: local, name "spin2", operand ref to ":spin1"
nop
-:spin3 lda :spin3 ;EDIT: local, name "spin1", operand ref to ":spin1"
- beq :spin3 ;EDIT: operand ref to ":spin1"
+:spin3 lda :spin3 ;EDIT: local, name "spin1", operand ref to ":spin1" (will be adjusted)
+ beq :spin3 ;EDIT: operand ref to ":spin1" (NOT adjusted)
lda #<:spin1
ldx #<:spin2
@@ -84,7 +87,7 @@ global6 nop
; Semi-related: test labels that are nothing but underscores.
global_ nop
- ldx #$40
+_global ldx #$40
__ dex
bne __
beq ___
@@ -97,10 +100,26 @@ ___ ldx #$41
; Semi-related: test annotations (mostly to confirm that the suffix chars
; aren't appearing in the assembly output)
anno lda #$42 ;EDIT: add '?'
-anno1 lda anno
+anno1 lda anno ;NOTE: do not label, let table gen do it
+ clc
+ bcc :skip
+ dw anno1 ;EDIT: use table generator to get annotation
+:skip nop
+; Semi-related: test opcode name labels (which are illegal for assemblers
+; other than Merlin 32). We're configured for plain 6502, so it should
+; remap some but not others.
+JMP lda JMP ;EDIT set label (should be remapped)
+JMP0 lda JMP0 ;EDIT set label
+JMP1 lda JMP1 ;EDIT set label
+:JMP lda :JMP ;EDIT set label
+:JMP0 lda :JMP0 ;EDIT set label
+:JMP1 lda :JMP1 ;EDIT set label
+:JMP_ lda :JMP_ ;EDIT set label :JMP
+jmp lda jmp ;EDIT set label
+Jmp lda Jmp ;EDIT set label
+BRA lda BRA ;EDIT set label (should NOT be remapped)
+brl lda brl ;EDIT set label (should NOT be remapped)
+LDAL ldal LDAL ;EDIT set label (should NOT be remapped)
rts
-
- dw anno1 ;EDIT: use table generator
-
diff --git a/SourceGen/Symbol.cs b/SourceGen/Symbol.cs
index ea057c6..4f1a6f0 100644
--- a/SourceGen/Symbol.cs
+++ b/SourceGen/Symbol.cs
@@ -153,6 +153,11 @@ namespace SourceGen {
get { return SymbolType == Type.NonUniqueLocalAddr; }
}
+ public bool CanBeLocal {
+ get { return SymbolType == Type.LocalOrGlobalAddr ||
+ SymbolType == Type.NonUniqueLocalAddr; }
+ }
+
///
/// True if the symbol represents a constant value.
///