1
0
mirror of https://github.com/fadden/6502bench.git synced 2024-12-30 14:31:13 +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
// 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))) {

View File

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

View File

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

View File

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

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.
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";
/// <summary>
/// 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 {
/// <remarks>
/// 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.
/// </remarks>
private class OffsetLabel {
public int Offset { get; private set; }
@ -136,6 +140,12 @@ namespace SourceGen.AsmGen {
/// </summary>
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>
/// Project reference.
/// </summary>
@ -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<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++) {
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<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>
@ -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 {
}
/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// This may be called even if label localization is disabled. In that case we just
/// create an empty label map and populate as needed.
/// </remarks>
public void MaskLeadingUnderscores() {
bool allGlobal = false;
if (LabelMap == null) {
allGlobal = true;
LabelMap = new Dictionary<string, string>();
}
/// <param name="startGlobal">Offset of first global.</param>
/// <param name="endGlobal">Offset of second global. If this range is at the end of the
/// file, this offset may be one past the end.</param>
/// <param name="scopedLocals">Work object (minor alloc optimization).</param>
private void ProcessLocals(int startGlobal, int endGlobal,
Dictionary<string, string> 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<string, string> allLabels = new SortedList<string, string>();
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);
}
/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public void FixOpcodeLabels() {
if (LabelMap == null) {
LabelMap = new Dictionary<string, string>();
}
/// <param name="label">Label to uniquify.</param>
/// <param name="allLabels">Dictionary to uniquify against.</param>
/// <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.
// (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;
}
return uniqueLabel;
}
}
}

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

View File

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

View File

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

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

View File

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