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. ///