1
0
mirror of https://github.com/fadden/6502bench.git synced 2025-01-02 18:30:41 +00:00

Label rework, part 5

Implemented assembly source generation of non-unique local labels.
The new 2023-non-unique-labels test exercises various edge cases
(though we're still missing local variable interaction).

The format of uniquified labels changed slightly, so the expected
output of 2012-label-localizer needed to be updated.

This changes the "no opcode mnemonics" and "mask leading underscores"
functions into integrated parts of the label localization process.
This commit is contained in:
Andy McFadden 2019-11-17 16:05:51 -08:00
parent 4e08810278
commit 88c56616f7
17 changed files with 771 additions and 183 deletions

View File

@ -224,8 +224,8 @@ namespace SourceGen.AsmGen {
// While '.' labels are limited to the current zone, '@' labels are visible // While '.' labels are limited to the current zone, '@' labels are visible
// between global labels. (This is poorly documented.) // between global labels. (This is poorly documented.)
mLocalizer.LocalPrefix = "@"; mLocalizer.LocalPrefix = "@";
mLocalizer.QuirkNoOpcodeMnemonics = true;
mLocalizer.Analyze(); mLocalizer.Analyze();
mLocalizer.FixOpcodeLabels();
// Use UTF-8 encoding, without a byte-order mark. // Use UTF-8 encoding, without a byte-order mark.
using (StreamWriter sw = new StreamWriter(pathName, false, new UTF8Encoding(false))) { using (StreamWriter sw = new StreamWriter(pathName, false, new UTF8Encoding(false))) {

View File

@ -218,9 +218,9 @@ namespace SourceGen.AsmGen {
mLocalizer = new LabelLocalizer(Project); mLocalizer = new LabelLocalizer(Project);
mLocalizer.LocalPrefix = "@"; mLocalizer.LocalPrefix = "@";
mLocalizer.QuirkVariablesEndScope = true; mLocalizer.QuirkVariablesEndScope = true; // https://github.com/cc65/cc65/issues/938
mLocalizer.QuirkNoOpcodeMnemonics = true;
mLocalizer.Analyze(); mLocalizer.Analyze();
mLocalizer.FixOpcodeLabels();
// Use UTF-8 encoding, without a byte-order mark. // Use UTF-8 encoding, without a byte-order mark.
using (StreamWriter sw = new StreamWriter(cfgName, false, new UTF8Encoding(false))) { using (StreamWriter sw = new StreamWriter(cfgName, false, new UTF8Encoding(false))) {

View File

@ -191,8 +191,8 @@ namespace SourceGen.AsmGen {
mLocalizer = new LabelLocalizer(Project); mLocalizer = new LabelLocalizer(Project);
mLocalizer.LocalPrefix = ":"; mLocalizer.LocalPrefix = ":";
// don't need to set QuirkNoOpcodeMnemonics
mLocalizer.Analyze(); mLocalizer.Analyze();
//mLocalizer.FixOpcodeLabels();
// Use UTF-8 encoding, without a byte-order mark. // Use UTF-8 encoding, without a byte-order mark.
using (StreamWriter sw = new StreamWriter(pathName, false, new UTF8Encoding(false))) { using (StreamWriter sw = new StreamWriter(pathName, false, new UTF8Encoding(false))) {

View File

@ -230,9 +230,8 @@ namespace SourceGen.AsmGen {
mLocalizer = new LabelLocalizer(Project); mLocalizer = new LabelLocalizer(Project);
mLocalizer.LocalPrefix = "_"; mLocalizer.LocalPrefix = "_";
mLocalizer.QuirkNoOpcodeMnemonics = true;
mLocalizer.Analyze(); mLocalizer.Analyze();
mLocalizer.MaskLeadingUnderscores();
mLocalizer.FixOpcodeLabels();
// Use UTF-8 encoding, without a byte-order mark. // Use UTF-8 encoding, without a byte-order mark.
using (StreamWriter sw = new StreamWriter(pathName, false, new UTF8Encoding(false))) { using (StreamWriter sw = new StreamWriter(pathName, false, new UTF8Encoding(false))) {

View File

@ -40,7 +40,7 @@ but this would cause an error:
because the local symbol table is cleared when a global symbol is encountered. 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 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 As a further limitation, assemblers seem to want the first label encountered in a program
to be global. to be global.
@ -78,6 +78,10 @@ name. This must be applied to both labels and operands.
namespace SourceGen.AsmGen { namespace SourceGen.AsmGen {
public class LabelLocalizer { 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";
/// <summary> /// <summary>
/// A pairing of an offset with a label string. (Essentially mAnattribs[n].Symbol /// A pairing of an offset with a label string. (Essentially mAnattribs[n].Symbol
/// with all the fluff trimmed away.) /// with all the fluff trimmed away.)
@ -85,8 +89,8 @@ namespace SourceGen.AsmGen {
/// <remarks> /// <remarks>
/// The label string isn't actually all that useful, since we can pull it back out /// 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 /// 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 /// put into a List, so simply storing a plain int offset it's much better (in terms
/// much because the ints get boxed. /// of memory and allocations) because the ints get boxed.
/// </remarks> /// </remarks>
private class OffsetLabel { private class OffsetLabel {
public int Offset { get; private set; } public int Offset { get; private set; }
@ -136,6 +140,12 @@ namespace SourceGen.AsmGen {
/// </summary> /// </summary>
public bool QuirkVariablesEndScope { get; set; } public bool QuirkVariablesEndScope { get; set; }
/// <summary>
/// Set this if global variables are not allowed to have the same name as an opcode
/// mnemonic.
/// </summary>
public bool QuirkNoOpcodeMnemonics { get; set; }
/// <summary> /// <summary>
/// Project reference. /// Project reference.
/// </summary> /// </summary>
@ -175,6 +185,8 @@ namespace SourceGen.AsmGen {
public void Analyze() { public void Analyze() {
Debug.Assert(LocalPrefix.Length > 0); 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); mGlobalFlags.SetAll(false);
// Currently we only support the "local labels have scope that ends at a global // 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. // The current algorithm uses a straightforward O(n^2) approach.
//
// Step 1: generate source/target pairs and global label list // Step 1: generate source/target pairs and global label list
//
GenerateLists(); GenerateLists();
//
// Step 2: walk through the list of global symbols, identifying source/target // 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 // 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++) { for (int index = 0; index < mGlobalLabels.Count; index++) {
FindIntersectingPairs(mGlobalLabels[index]); FindIntersectingPairs(mGlobalLabels[index]);
} }
// Step 3: for each local label, add an entry to the map with the appropriate // We're done with these. Take out the trash.
// local-label syntax. 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<string, string>(); LabelMap = new Dictionary<string, string>();
Dictionary<string, string> allGlobalLabels = new Dictionary<string, string>();
bool remapUnders = (LocalPrefix == "_");
Dictionary<string, Asm65.OpDef> 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<string, Asm65.OpDef>();
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++) { for (int i = 0; i < mProject.FileDataLength; i++) {
if (mGlobalFlags[i]) { if (!mGlobalFlags[i]) {
continue; continue;
} }
Symbol sym = mProject.GetAnattrib(i).Symbol; Symbol sym = mProject.GetAnattrib(i).Symbol;
if (sym == null) { if (sym == null) {
// Should only happen when we insert a dummy global label for the
// "variables end scope" quirk.
continue; 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(); // Step 4: remap local labels. There are two operations here.
mOffsetPairs.Clear(); //
// 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<string, string> scopedLocals = new Dictionary<string, string>();
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);
}
} }
/// <summary> /// <summary>
@ -237,10 +357,11 @@ namespace SourceGen.AsmGen {
bool first = true; bool first = true;
for (int offset = 0; offset < mProject.FileDataLength; offset++) { for (int offset = 0; offset < mProject.FileDataLength; offset++) {
// Find all user labels and auto labels.
Symbol sym = mProject.GetAnattrib(offset).Symbol; Symbol sym = mProject.GetAnattrib(offset).Symbol;
// In cc65, variable declarations end the local label scope. We insert a // 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 && if (QuirkVariablesEndScope &&
mProject.LvTables.TryGetValue(offset, out LocalVariableTable value) && mProject.LvTables.TryGetValue(offset, out LocalVariableTable value) &&
value.Count > 0) { value.Count > 0) {
@ -253,7 +374,7 @@ namespace SourceGen.AsmGen {
continue; continue;
} }
if (first || sym.SymbolType != Symbol.Type.LocalOrGlobalAddr) { if (first || !sym.CanBeLocal) {
first = false; first = false;
mGlobalFlags[offset] = true; mGlobalFlags[offset] = true;
mGlobalLabels.Add(new OffsetLabel(offset, sym.Label)); mGlobalLabels.Add(new OffsetLabel(offset, sym.Label));
@ -285,12 +406,11 @@ namespace SourceGen.AsmGen {
private void FindIntersectingPairs(OffsetLabel glabel) { private void FindIntersectingPairs(OffsetLabel glabel) {
Debug.Assert(mGlobalFlags[glabel.Offset]); Debug.Assert(mGlobalFlags[glabel.Offset]);
int globOffset = glabel.Offset; int glabOffset = glabel.Offset;
for (int i = 0; i < mOffsetPairs.Count; i++) { for (int i = 0; i < mOffsetPairs.Count; i++) {
OffsetPair pair = mOffsetPairs[i]; OffsetPair pair = mOffsetPairs[i];
// If the destination was marked global earlier, remove and ignore this entry. // If the destination was marked global earlier, remove the entry and move on.
// Note this also means that pair.DstOffset != label.Offset.
if (mGlobalFlags[pair.DstOffset]) { if (mGlobalFlags[pair.DstOffset]) {
mOffsetPairs.RemoveAt(i); mOffsetPairs.RemoveAt(i);
i--; i--;
@ -301,15 +421,16 @@ namespace SourceGen.AsmGen {
// offsets. // offsets.
// //
// If the reference source is itself a global label, it can reference local // 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 // labels forward, but not backward (i.e. if it crosses itself, the destination
// the case where label.Offset==pair.SrcOffset. // must be made global). We need to take that into account for the case where
// label.Offset==pair.SrcOffset.
bool intersect; bool intersect;
if (pair.SrcOffset < pair.DstOffset) { if (pair.SrcOffset < pair.DstOffset) {
// Forward reference. src==glob is ok // Forward reference. src==glab is ok
intersect = pair.SrcOffset < globOffset && pair.DstOffset >= globOffset; intersect = pair.SrcOffset < glabOffset && pair.DstOffset >= glabOffset;
} else { } else {
// Backward reference. src==glob is bad // Backward reference. src==glab is bad
intersect = pair.SrcOffset >= globOffset && pair.DstOffset <= globOffset; intersect = pair.SrcOffset >= glabOffset && pair.DstOffset <= glabOffset;
} }
if (intersect) { if (intersect) {
@ -329,163 +450,67 @@ namespace SourceGen.AsmGen {
} }
/// <summary> /// <summary>
/// Adjusts the label map so that only local variables start with an underscore ('_'). /// Generates map entries for local labels defined between the two globals.
/// 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.
/// </summary> /// </summary>
/// <remarks> /// <param name="startGlobal">Offset of first global.</param>
/// This may be called even if label localization is disabled. In that case we just /// <param name="endGlobal">Offset of second global. If this range is at the end of the
/// create an empty label map and populate as needed. /// file, this offset may be one past the end.</param>
/// </remarks> /// <param name="scopedLocals">Work object (minor alloc optimization).</param>
public void MaskLeadingUnderscores() { private void ProcessLocals(int startGlobal, int endGlobal,
bool allGlobal = false; Dictionary<string, string> scopedLocals) {
if (LabelMap == null) { //Debug.WriteLine("ProcessLocals: +" + startGlobal.ToString("x6") +
allGlobal = true; // " - +" + endGlobal.ToString("x6"));
LabelMap = new Dictionary<string, string>(); scopedLocals.Clear();
} bool remapUnders = (LocalPrefix == "_");
// Throw out the original local label generation. We're going to redo the for (int i = startGlobal + 1; i < endGlobal; i++) {
// label map generation step. Debug.Assert(!mGlobalFlags[i]);
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<string, string> allLabels = new SortedList<string, string>();
for (int i = 0; i < mProject.FileDataLength; i++) {
Symbol sym = mProject.GetAnattrib(i).Symbol; Symbol sym = mProject.GetAnattrib(i).Symbol;
if (sym == null) { if (sym == null) {
// No label at this offset. continue; // no label here
continue;
} }
string newLabel; string newLabel = sym.LabelWithoutTag;
if (allGlobal || mGlobalFlags[i]) { if (remapUnders && newLabel[0] == '_') {
// Global symbol. Don't let it start with '_'. newLabel = LocalPrefix + NO_UNDER_PFX + newLabel;
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;
}
} else { } else {
// Local symbol. newLabel = LocalPrefix + newLabel;
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;
}
} }
// Make sure it's unique. if (scopedLocals.ContainsKey(newLabel)) {
string uniqueLabel = newLabel; newLabel = MakeUnique(newLabel, scopedLocals);
int uval = 0;
while (allLabels.ContainsKey(uniqueLabel)) {
uval++;
uniqueLabel = newLabel + uval.ToString();
} }
allLabels.Add(uniqueLabel, uniqueLabel);
// If it's different, add it to the label map. // Map from the original symbol label to the local form. This works for
if (sym.Label != uniqueLabel) { // unique and non-unique locals.
LabelMap.Add(sym.Label, uniqueLabel); LabelMap[sym.Label] = newLabel;
}
scopedLocals.Add(newLabel, newLabel);
} }
Debug.WriteLine("UMAP: allcount=" + allLabels.Count + " mapcount=" + LabelMap.Count);
} }
/// <summary> /// <summary>
/// Remaps labels that match opcode names. Updated names will be added to LabelMap. /// Alters a label to make it unique. This may be called with a label that is unique
/// This should be run after localization and underscore concealment have finished. /// but illegal (e.g. an instruction mnemonic), so we guarantee that the label returned
/// is different from the argument.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Most assemblers don't like it if you create a label with the same name as an /// We can't put a '_' at the front or an 'L' at the end (LDAL), since that could run
/// opcode, e.g. "jmp LSR" doesn't work. We can use the label map to work around /// afoul of the things we're trying to work around. We don't want to mess with the
/// the issue. /// start of the string since it may or may not have the LocalPrefix on it.
///
/// 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.
/// </remarks> /// </remarks>
public void FixOpcodeLabels() { /// <param name="label">Label to uniquify.</param>
if (LabelMap == null) { /// <param name="allLabels">Dictionary to uniquify against.</param>
LabelMap = new Dictionary<string, string>(); /// <returns>Modified label</returns>
} private static string MakeUnique(string label, Dictionary<string, string> 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. return uniqueLabel;
// (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<string, Asm65.OpDef> opnames = new Dictionary<string, Asm65.OpDef>();
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<string, string> allLabels = new SortedList<string, string>();
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;
}
} }
} }
} }

Binary file not shown.

View File

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

View File

@ -64,9 +64,9 @@ _X_uname2
nop nop
lda #$00 lda #$00
_AND bne _AND ;local _AND bne _AND ;local
JMP_1 bne JMP_1 ;global JMP1 bne JMP1 ;global
jmp_1 bne jmp_1 jmp1 bne jmp1
TSB_1 bne TSB_1 TSB1 bne TSB1
XCE bne XCE XCE bne XCE
rts rts

View File

@ -62,9 +62,9 @@ EXCESSIVELY_LONG_LABEL
nop nop
lda #$00 lda #$00
@AND bne @AND ;local @AND bne @AND ;local
JMP_1 bne JMP_1 ;global JMP1 bne JMP1 ;global
jmp_1 bne jmp_1 jmp1 bne jmp1
TSB_1 bne TSB_1 TSB1 bne TSB1
XCE bne XCE XCE bne XCE
rts rts

View File

@ -63,9 +63,9 @@ EXCESSIVELY_LONG_LABEL:
nop nop
lda #$00 lda #$00
@AND: bne @AND ;local @AND: bne @AND ;local
JMP_1: bne JMP_1 ;global JMP1: bne JMP1 ;global
jmp_1: bne jmp_1 jmp1: bne jmp1
TSB_1: bne TSB_1 TSB1: bne TSB1
XCE: bne XCE XCE: bne XCE
rts rts

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,6 +3,8 @@
; ;
; Assembler: Merlin 32 ; Assembler: Merlin 32
; NOTE: configure for plain 6502
org $1000 org $1000
; Test conflict with auto-label. ; Test conflict with auto-label.
@ -47,7 +49,7 @@ global3 nop ;EDIT
:fwd1 nop ;EDIT :fwd1 nop ;EDIT
:fwd2 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 global4 nop ;EDIT
ldx #$08 ldx #$08
gloop dex ;EDIT: local, name "loop" gloop dex ;EDIT: local, name "loop"
@ -57,12 +59,13 @@ global5 nop
nop nop
; Test symbolic references. ; Test symbolic references.
; NOTE: start them as spin1/spin2/spin3, then rename spin3 to spin1
global6 nop global6 nop
:spin1 jsr :spin2 ;EDIT: local, name "spin1", operand ref to ":spin2" :spin1 jsr :spin2 ;EDIT: local, name "spin1", operand ref to ":spin2"
:spin2 jsr :spin1 ;EDIT: local, name "spin2", operand ref to ":spin1" :spin2 jsr :spin1 ;EDIT: local, name "spin2", operand ref to ":spin1"
nop nop
:spin3 lda :spin3 ;EDIT: local, name "spin1", 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" beq :spin3 ;EDIT: operand ref to ":spin1" (NOT adjusted)
lda #<:spin1 lda #<:spin1
ldx #<:spin2 ldx #<:spin2
@ -84,7 +87,7 @@ global6 nop
; Semi-related: test labels that are nothing but underscores. ; Semi-related: test labels that are nothing but underscores.
global_ nop global_ nop
ldx #$40 _global ldx #$40
__ dex __ dex
bne __ bne __
beq ___ beq ___
@ -97,10 +100,26 @@ ___ ldx #$41
; Semi-related: test annotations (mostly to confirm that the suffix chars ; Semi-related: test annotations (mostly to confirm that the suffix chars
; aren't appearing in the assembly output) ; aren't appearing in the assembly output)
anno lda #$42 ;EDIT: add '?' 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 rts
dw anno1 ;EDIT: use table generator

View File

@ -153,6 +153,11 @@ namespace SourceGen {
get { return SymbolType == Type.NonUniqueLocalAddr; } get { return SymbolType == Type.NonUniqueLocalAddr; }
} }
public bool CanBeLocal {
get { return SymbolType == Type.LocalOrGlobalAddr ||
SymbolType == Type.NonUniqueLocalAddr; }
}
/// <summary> /// <summary>
/// True if the symbol represents a constant value. /// True if the symbol represents a constant value.
/// </summary> /// </summary>