diff --git a/SourceGen/AsmGen/AsmMerlin32.cs b/SourceGen/AsmGen/AsmMerlin32.cs index 13c9e5f..7c6a1e8 100644 --- a/SourceGen/AsmGen/AsmMerlin32.cs +++ b/SourceGen/AsmGen/AsmMerlin32.cs @@ -134,8 +134,9 @@ namespace SourceGen.AsmGen { Project = project; Quirks = new AssemblerQuirks(); - Quirks.TracksSepRepNotEmu = true; + Quirks.HasRedefinableSymbols = true; Quirks.NoPcRelBankWrap = true; + Quirks.TracksSepRepNotEmu = true; mWorkDirectory = workDirectory; mFileNameBase = fileNameBase; diff --git a/SourceGen/AsmGen/AsmTass64.cs b/SourceGen/AsmGen/AsmTass64.cs index 13b7ccc..14567d6 100644 --- a/SourceGen/AsmGen/AsmTass64.cs +++ b/SourceGen/AsmGen/AsmTass64.cs @@ -160,6 +160,7 @@ namespace SourceGen.AsmGen { Project = project; Quirks = new AssemblerQuirks(); + Quirks.HasRedefinableSymbols = true; Quirks.StackIntOperandIsImmediate = true; mWorkDirectory = workDirectory; diff --git a/SourceGen/AsmGen/GenCommon.cs b/SourceGen/AsmGen/GenCommon.cs index 6d321a9..1fc9801 100644 --- a/SourceGen/AsmGen/GenCommon.cs +++ b/SourceGen/AsmGen/GenCommon.cs @@ -39,9 +39,8 @@ namespace SourceGen.AsmGen { bool doAddCycles = gen.Settings.GetBool(AppSettings.SRCGEN_SHOW_CYCLE_COUNTS, false); - // TODO: switch uniqueness based on quirk LocalVariableLookup lvLookup = new LocalVariableLookup(proj.LvTables, - proj.SymbolTable, proj); + gen.Quirks.HasRedefinableSymbols ? null : proj.SymbolTable, proj); GenerateHeader(gen, sw); diff --git a/SourceGen/AsmGen/IGenerator.cs b/SourceGen/AsmGen/IGenerator.cs index d366523..3736333 100644 --- a/SourceGen/AsmGen/IGenerator.cs +++ b/SourceGen/AsmGen/IGenerator.cs @@ -186,26 +186,21 @@ namespace SourceGen.AsmGen { /// Enumeration of quirky or buggy behavior that GenCommon needs to handle. /// public class AssemblerQuirks { - /// - /// Are the arguments to MVN/MVP reversed? - /// - public bool BlockMoveArgsReversed { get; set; } - /// /// Are 8-bit constant args to MVN/MVP output without a leading '#'? /// public bool BlockMoveArgsNoHash { get; set; } /// - /// Do 8-bit constant args to StackInt ops (BRK/COP) require a leading '#'? + /// Are the arguments to MVN/MVP reversed? /// - public bool StackIntOperandIsImmediate { get; set; } + public bool BlockMoveArgsReversed { get; set; } /// - /// Does the assembler configure assembler widths based on SEP/REP, but doesn't - /// track the emulation bit? + /// Does the assembler support a type of label whose value can be redefined to + /// act as a local variable? /// - public bool TracksSepRepNotEmu { get; set; } + public bool HasRedefinableSymbols { get; set; } /// /// Is the assembler unable to generate relative branches that wrap around banks? @@ -213,6 +208,11 @@ namespace SourceGen.AsmGen { /// public bool NoPcRelBankWrap { get; set; } + /// + /// Do 8-bit constant args to StackInt ops (BRK/COP) require a leading '#'? + /// + public bool StackIntOperandIsImmediate { get; set; } + /// /// Is the assembler implemented as a single pass? (e.g. cc65) /// @@ -223,5 +223,11 @@ namespace SourceGen.AsmGen { /// and not corrected when the actual width is determined? /// public bool SinglePassNoLabelCorrection { get; set; } + + /// + /// Does the assembler configure assembler widths based on SEP/REP, but doesn't + /// track the emulation bit? + /// + public bool TracksSepRepNotEmu { get; set; } } } \ No newline at end of file diff --git a/SourceGen/DefSymbol.cs b/SourceGen/DefSymbol.cs index c07f300..c4be472 100644 --- a/SourceGen/DefSymbol.cs +++ b/SourceGen/DefSymbol.cs @@ -134,6 +134,17 @@ namespace SourceGen { Tag = string.Empty; } + /// + /// Constructs a DefSymbol from an existing DefSymbol, with a different label. Use + /// this to change the label while keeping everything else the same. + /// + /// Source DefSymbol. + /// Label to use. + public DefSymbol(DefSymbol defSym, string label) + : this(label, defSym.Value, defSym.SymbolSource, defSym.SymbolType, + defSym.DataDescriptor.FormatSubType, defSym.Comment, defSym.Tag, + defSym.DataDescriptor.Length) { } + /// /// Determines whether a symbol overlaps with a region. Useful for variables. /// diff --git a/SourceGen/DisasmProject.cs b/SourceGen/DisasmProject.cs index 1090f45..0732e2c 100644 --- a/SourceGen/DisasmProject.cs +++ b/SourceGen/DisasmProject.cs @@ -908,25 +908,39 @@ namespace SourceGen { /// variables to take precedence. /// /// This also adds all symbols in non-hidden variable tables to the main SymbolTable, - /// for the benefit of uniqueness checks. + /// for the benefit of future uniqueness checks. /// private void GenerateVariableRefs() { - LocalVariableLookup lvLookup = new LocalVariableLookup(LvTables, SymbolTable, this); + LocalVariableLookup lvLookup = new LocalVariableLookup(LvTables, null, this); for (int offset = 0; offset < FileData.Length; ) { - // All entries also get added to the main SymbolTable. This is a little - // wonky because the symbol might already exist with a different value. - // So long as the previous thing was also a variable, it doesn't matter. + // Was a table defined at this offset? List vars = lvLookup.GetVariablesDefinedAtOffset(offset); if (vars != null) { + // All entries also get added to the main SymbolTable. This is a little + // wonky because the symbol might already exist with a different value. + // So long as the previous thing was also a variable, it doesn't matter. foreach (DefSymbol defSym in vars) { if (!SymbolTable.TryGetValue(defSym.Label, out Symbol sym)) { + // Symbol not yet in symbol table. Add it. + // + // NOTE: if you try to run the main app with uniqification enabled, + // this will cause the various uniquified forms of local variables + // to end up in the main symbol table. This can clashes with user + // labels that would not occur otherwise. SymbolTable[defSym.Label] = defSym; } else if (!sym.IsVariable) { - // Shouldn't happen, and will cause trouble if we're not - // uniquifying names. - Debug.Assert(false, - "Found non-variable with var name in symbol table: " + sym); + // Somehow we have a variable and a non-variable with the same + // name. Platform/project symbols haven't been processed yet, so + // this must be a clash with a user label. This will likely cause + // assembly source gen to fail later on. It's possible to do this + // by "hiding" a table and then adding a user label, so we can't just + // fix it at project load time. The full fix is to permanently + // rename the dup in the LvTable and reset the LvLookup here, but I + // hate trashing user data. + Debug.WriteLine("Found non-variable with var name in symbol table: " + + sym); + Debug.Assert(false); } } } diff --git a/SourceGen/LineListGen.cs b/SourceGen/LineListGen.cs index 9c112f1..9001a40 100644 --- a/SourceGen/LineListGen.cs +++ b/SourceGen/LineListGen.cs @@ -442,8 +442,7 @@ namespace SourceGen { mFormattedLineCache = new FormattedOperandCache(); mShowCycleCounts = AppSettings.Global.GetBool(AppSettings.SRCGEN_SHOW_CYCLE_COUNTS, false); - // TODO: remove SymbolTable -- don't need unique - mLvLookup = new LocalVariableLookup(mProject.LvTables, mProject.SymbolTable, mProject); + mLvLookup = new LocalVariableLookup(mProject.LvTables, null, mProject); mDisplayList.ListGen = this; } diff --git a/SourceGen/LocalVariableLookup.cs b/SourceGen/LocalVariableLookup.cs index 5272e4e..f629092 100644 --- a/SourceGen/LocalVariableLookup.cs +++ b/SourceGen/LocalVariableLookup.cs @@ -34,6 +34,42 @@ namespace SourceGen { /// private SymbolTable mSymbolTable; + /// + /// Label uniquification helper. + /// + private class UniqueLabel { + public string BaseLabel { get; private set; } + public string Label { get; private set; } + private int Counter { get; set; } + + public UniqueLabel(string baseLabel) { + Label = BaseLabel = baseLabel; + Counter = 0; + } + + /// + /// Updates the Label to be unique. Call this when a symbol is defined or + /// re-defined. + /// + /// Symbol table, for uniqueness check. + public void MakeUnique(SymbolTable symbolTable) { + // The main symbol table might have user-supplied labels like "ptr_2", so we + // need to keep testing against that. However, it should not be possible for + // us to clash with other uniquified variables. So we don't need to check + // for clashes in the UniqueLabel list. + // + // It *is* possible to clash with other variable base names, so we can't + // exclude variables from our SymbolTable lookup. + string testLabel; + do { + Counter++; + testLabel = BaseLabel + "_" + Counter; + } while (symbolTable.TryGetValue(testLabel, out Symbol unused1)); + Label = testLabel; + } + } + private Dictionary mUniqueLabels; + /// /// Reference to project, so we can query the Anattrib array to identify "hidden" tables. /// @@ -73,6 +109,9 @@ namespace SourceGen { mProject = project; mCurrentTable = new LocalVariableTable(); + if (mSymbolTable != null) { + mUniqueLabels = new Dictionary(); + } Reset(); } @@ -80,6 +119,7 @@ namespace SourceGen { mRecentOffset = -1; mRecentSymbols = null; mCurrentTable.Clear(); + mUniqueLabels?.Clear(); if (mLvTables.Count == 0) { mNextLvtIndex = -1; mNextLvtOffset = mProject.FileDataLength; @@ -99,12 +139,13 @@ namespace SourceGen { /// Symbol, or null if no match found. public DefSymbol GetSymbol(int offset, int operandValue, Symbol.Type type) { AdvanceToOffset(offset); - return mCurrentTable.GetByValueRange(operandValue, 1, type); } /// - /// Gets the symbol associated with a symbol reference. + /// Gets the symbol associated with a symbol reference. If uniquification is enabled, + /// the unique-label map for the specified offset will be used to transform the + /// symbol reference. /// /// Offset of start of instruction. /// Reference to symbol. @@ -112,7 +153,19 @@ namespace SourceGen { public DefSymbol GetSymbol(int offset, WeakSymbolRef symRef) { AdvanceToOffset(offset); - return mCurrentTable.GetByLabel(symRef.Label); + // The symRef uses the non-uniqified symbol, so we need to get the unique value at + // the current offset. + string label = symRef.Label; + if (mUniqueLabels != null && mUniqueLabels.TryGetValue(label, out UniqueLabel ulab)) { + label = ulab.Label; + } + DefSymbol defSym = mCurrentTable.GetByLabel(label); + + // In theory this is okay, but in practice the only things asking for symbols are + // entirely convinced that the symbol exists here. So this is probably a bug. + Debug.Assert(defSym != null); + + return defSym; } /// @@ -138,7 +191,6 @@ namespace SourceGen { if (mRecentOffset == offset) { return mRecentSymbols; } - return null; } @@ -150,16 +202,16 @@ namespace SourceGen { /// do an incremental update. If the offset moves backward, we have to reset and walk /// forward again. /// - /// Target offset. - private void AdvanceToOffset(int offset) { + /// Target offset. + private void AdvanceToOffset(int targetOffset) { if (mNextLvtIndex < 0) { return; } - if (offset < mRecentOffset) { + if (targetOffset < mRecentOffset) { // We went backwards. Reset(); } - while (mNextLvtOffset <= offset) { + while (mNextLvtOffset <= targetOffset) { if (!mProject.GetAnattrib(mNextLvtOffset).IsStart) { // Hidden table, ignore it. Debug.WriteLine("Ignoring LvTable at +" + mNextLvtOffset.ToString("x6")); @@ -175,15 +227,25 @@ namespace SourceGen { mRecentOffset = mNextLvtOffset; // Merge the new entries into the work table. This automatically - // discards entries that clash. + // discards entries that clash by name or value. for (int i = 0; i < lvt.Count; i++) { - // TODO: uniquify - mCurrentTable.AddOrReplace(lvt[i]); + DefSymbol defSym = lvt[i]; + if (mSymbolTable != null) { + if (mUniqueLabels.TryGetValue(defSym.Label, out UniqueLabel ulab)) { + // We've seen this label before; generate a unique version. + ulab.MakeUnique(mSymbolTable); + defSym = new DefSymbol(defSym, ulab.Label); + } else { + // Haven't seen this before. Add it to the unique-labels table. + mUniqueLabels.Add(defSym.Label, new UniqueLabel(defSym.Label)); + } + } + mCurrentTable.AddOrReplace(defSym); - mRecentSymbols.Add(lvt[i]); + mRecentSymbols.Add(defSym); } - mCurrentTable.DebugDump(); + mCurrentTable.DebugDump(mNextLvtOffset); } // Update state to look for next table. diff --git a/SourceGen/LocalVariableTable.cs b/SourceGen/LocalVariableTable.cs index 168eb1c..dbec864 100644 --- a/SourceGen/LocalVariableTable.cs +++ b/SourceGen/LocalVariableTable.cs @@ -133,15 +133,25 @@ namespace SourceGen { } } + /// + /// Clears the tables. + /// public void Clear() { mVarByLabel.Clear(); mVarByValue.Clear(); } + /// + /// Returns the symbol that matches the label, or null if not found. + /// public DefSymbol GetByLabel(string label) { - return mVarByLabel[label]; + mVarByLabel.TryGetValue(label, out DefSymbol defSym); + return defSym; } + /// + /// Removes the symbol with the matching label. + /// public void RemoveByLabel(string label) { if (mVarByLabel.TryGetValue(label, out DefSymbol defSym)) { mVarByLabel.Remove(defSym.Label); @@ -260,9 +270,9 @@ namespace SourceGen { return hashCode; } - public void DebugDump() { - Debug.WriteLine("LocalVariableTable count=" + Count + " clear-previous=" + - ClearPrevious); + public void DebugDump(int offset) { + Debug.WriteLine("LocalVariableTable +" + offset.ToString("x6") + " count=" + + Count + " clear-previous=" + ClearPrevious); for (int i = 0; i < Count; i++) { Debug.WriteLine(" " + i + ": " + this[i]); } diff --git a/SourceGen/PseudoOp.cs b/SourceGen/PseudoOp.cs index 613fe7a..61329c9 100644 --- a/SourceGen/PseudoOp.cs +++ b/SourceGen/PseudoOp.cs @@ -599,8 +599,8 @@ namespace SourceGen { Debug.Assert(false); return formatter.FormatHexValue(operandValue, hexMinLen); } - } else if (symbolTable.TryGetValue(dfd.SymbolRef.Label, out Symbol sym) && - !sym.IsVariable) { + } else if (symbolTable.TryGetNonVariableValue(dfd.SymbolRef.Label, + out Symbol sym)) { StringBuilder sb = new StringBuilder(); switch (formatter.ExpressionMode) { diff --git a/SourceGen/SymbolTable.cs b/SourceGen/SymbolTable.cs index 29d3759..6ab4c45 100644 --- a/SourceGen/SymbolTable.cs +++ b/SourceGen/SymbolTable.cs @@ -203,6 +203,21 @@ namespace SourceGen { return mSymbols.TryGetValue(key, out sym); } + /// + /// Gets the value associated with the key, unless it's a variable. + /// + /// Label to look up. + /// Symbol, or null if not found, or found but it's a variable. + /// True if the key is present, false otherwise. + public bool TryGetNonVariableValue(string key, out Symbol sym) { + bool found = mSymbols.TryGetValue(key, out sym); + if (found && sym.IsVariable) { + sym = null; + found = false; + } + return found; + } + /// /// Removes the specified symbol. /// diff --git a/SourceGen/WeakSymbolRef.cs b/SourceGen/WeakSymbolRef.cs index a221dbc..edf789f 100644 --- a/SourceGen/WeakSymbolRef.cs +++ b/SourceGen/WeakSymbolRef.cs @@ -117,7 +117,7 @@ namespace SourceGen { } public override string ToString() { - return "WeakSym: " + Label + ":" + ValuePart; + return "WeakSym: " + (IsVariable ? "var " : "") + Label + ":" + ValuePart; } } }