From 6a2532588b32ea842089601a6cb7b647f3a10d7c Mon Sep 17 00:00:00 2001 From: Andy McFadden Date: Fri, 30 Aug 2019 18:33:05 -0700 Subject: [PATCH] Local variables mostly work Variables are now handled properly end-to-end, except for label uniquification. So cc65 and ACME can't yet handle a file that redefines a local variable. This required a bunch of plumbing, but I think it came out okay. --- Asm65/Formatter.cs | 13 ++ Asm65/OpDef.cs | 10 ++ SourceGen/AsmGen/AsmMerlin32.cs | 6 +- SourceGen/AsmGen/AsmTass64.cs | 4 +- SourceGen/AsmGen/GenCommon.cs | 35 ++++- SourceGen/DisasmProject.cs | 114 ++++++-------- SourceGen/FormatDescriptor.cs | 6 +- SourceGen/LineListGen.cs | 18 ++- SourceGen/LocalVariableLookup.cs | 199 +++++++++++++++++++++++++ SourceGen/LocalVariableTable.cs | 5 +- SourceGen/MainController.cs | 1 + SourceGen/ProjectFile.cs | 2 +- SourceGen/PseudoOp.cs | 63 +++++++- SourceGen/SourceGen.csproj | 1 + SourceGen/Symbol.cs | 14 +- SourceGen/WeakSymbolRef.cs | 37 ++++- SourceGen/WpfGui/EditDefSymbol.xaml.cs | 7 +- SourceGen/WpfGui/MainWindow.xaml.cs | 3 +- 18 files changed, 436 insertions(+), 102 deletions(-) create mode 100644 SourceGen/LocalVariableLookup.cs diff --git a/Asm65/Formatter.cs b/Asm65/Formatter.cs index fbbeb90..f836dc3 100644 --- a/Asm65/Formatter.cs +++ b/Asm65/Formatter.cs @@ -68,6 +68,8 @@ namespace Asm65 { public string mForceLongOpcodeSuffix; public string mForceLongOperandPrefix; + public string mLocalVariableLablePrefix; // Merlin 32 puts ']' before var names + public string mEndOfLineCommentDelimiter; // usually ';' public string mFullLineCommentDelimiterBase; // usually ';' or '*', WITHOUT extra space public string mBoxLineCommentDelimiter; // usually blank or ';' @@ -610,6 +612,17 @@ namespace Asm65 { } } + /// + /// Formats a local variable label, prepending a prefix if needed. + /// + public string FormatVariableLabel(string label) { + if (!string.IsNullOrEmpty(mFormatConfig.mLocalVariableLablePrefix)) { + return mFormatConfig.mLocalVariableLablePrefix + label; + } else { + return label; + } + } + /// /// Formats an adjustment, as "+decimal" or "-decimal". If no adjustment /// is required, an empty string is returned. diff --git a/Asm65/OpDef.cs b/Asm65/OpDef.cs index a795bd5..4d2f074 100644 --- a/Asm65/OpDef.cs +++ b/Asm65/OpDef.cs @@ -217,6 +217,16 @@ namespace Asm65 { } } + /// + /// True if the instruction's operand is a stack-relative offset. + /// + public bool IsStackRelInstruction { + get { + return AddrMode == AddressMode.StackRel || + AddrMode == AddressMode.StackRelIndIndexY; + } + } + /// /// True if the operand's width is uniquely determined by the opcode mnemonic, even /// if the operation supports operands with varying widths. diff --git a/SourceGen/AsmGen/AsmMerlin32.cs b/SourceGen/AsmGen/AsmMerlin32.cs index 1e36550..13c9e5f 100644 --- a/SourceGen/AsmGen/AsmMerlin32.cs +++ b/SourceGen/AsmGen/AsmMerlin32.cs @@ -94,7 +94,7 @@ namespace SourceGen.AsmGen { private static PseudoOp.PseudoOpNames sDataOpNames = new PseudoOp.PseudoOpNames(new Dictionary { { "EquDirective", "equ" }, - { "VarDirective", "~=" }, // not really + { "VarDirective", "equ" }, { "OrgDirective", "org" }, //RegWidthDirective { "DefineData1", "dfb" }, @@ -158,6 +158,7 @@ namespace SourceGen.AsmGen { config.mForceDirectOperandPrefix = string.Empty; config.mForceAbsOperandPrefix = string.Empty; config.mForceLongOperandPrefix = string.Empty; + config.mLocalVariableLablePrefix = "]"; config.mEndOfLineCommentDelimiter = ";"; config.mFullLineCommentDelimiterBase = ";"; config.mBoxLineCommentDelimiter = string.Empty; @@ -400,7 +401,8 @@ namespace SourceGen.AsmGen { // IGenerator public void OutputVarDirective(string name, string valueStr, string comment) { - OutputLine("]" + name, SourceFormatter.FormatPseudoOp(sDataOpNames.EquDirective), + OutputLine(SourceFormatter.FormatVariableLabel(name), + SourceFormatter.FormatPseudoOp(sDataOpNames.VarDirective), valueStr, SourceFormatter.FormatEolComment(comment)); } diff --git a/SourceGen/AsmGen/AsmTass64.cs b/SourceGen/AsmGen/AsmTass64.cs index a784926..13b7ccc 100644 --- a/SourceGen/AsmGen/AsmTass64.cs +++ b/SourceGen/AsmGen/AsmTass64.cs @@ -589,9 +589,11 @@ namespace SourceGen.AsmGen { // IGenerator public void OutputLine(string label, string opcode, string operand, string comment) { - // Break the line if the label is long and it's not a .EQ directive. + // Break the line if the label is long and it's not a .EQ/.VAR directive. if (!string.IsNullOrEmpty(label) && !string.Equals(opcode, sDataOpNames.EquDirective, + StringComparison.InvariantCultureIgnoreCase) && + !string.Equals(opcode, sDataOpNames.VarDirective, StringComparison.InvariantCultureIgnoreCase)) { if (mLongLabelNewLine && label.Length >= mColumnWidths[0]) { diff --git a/SourceGen/AsmGen/GenCommon.cs b/SourceGen/AsmGen/GenCommon.cs index 5fbec47..6d321a9 100644 --- a/SourceGen/AsmGen/GenCommon.cs +++ b/SourceGen/AsmGen/GenCommon.cs @@ -39,6 +39,10 @@ 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); + GenerateHeader(gen, sw); // Used for M/X flag tracking. @@ -69,6 +73,11 @@ namespace SourceGen.AsmGen { gen.OutputOrgDirective(offset, orgAddr); } + List lvars = lvLookup.GetVariablesDefinedAtOffset(offset); + if (lvars != null) { + GenerateLocalVariables(gen, sw, lvars); + } + if (attr.IsInstructionStart) { // Generate M/X reg width directive, if necessary. // NOTE: we can suppress the initial directive if we know what the @@ -96,7 +105,7 @@ namespace SourceGen.AsmGen { } // Output instruction. - GenerateInstruction(gen, sw, offset, len, doAddCycles); + GenerateInstruction(gen, sw, lvLookup, offset, len, doAddCycles); if (attr.DoesNotContinue) { gen.OutputLine(string.Empty); @@ -139,7 +148,7 @@ namespace SourceGen.AsmGen { // Format symbols. foreach (DefSymbol defSym in proj.ActiveDefSymbolList) { - // Use an operand length of 1 so things are shown as concisely as possible. + // Use an operand length of 1 so values are shown as concisely as possible. string valueStr = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable, gen.Localizer.LabelMap, defSym.DataDescriptor, defSym.Value, 1, PseudoOp.FormatNumericOpFlags.None); @@ -152,8 +161,22 @@ namespace SourceGen.AsmGen { } } - private static void GenerateInstruction(IGenerator gen, StreamWriter sw, int offset, - int instrBytes, bool doAddCycles) { + private static void GenerateLocalVariables(IGenerator gen, StreamWriter sw, + List vars) { + foreach (DefSymbol defSym in vars) { + DisasmProject proj = gen.Project; + Formatter formatter = gen.SourceFormatter; + + // Use an operand length of 1 so values are shown as concisely as possible. + string valueStr = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable, + null, defSym.DataDescriptor, defSym.Value, 1, + PseudoOp.FormatNumericOpFlags.None); + gen.OutputVarDirective(defSym.Label, valueStr, defSym.Comment); + } + } + + private static void GenerateInstruction(IGenerator gen, StreamWriter sw, + LocalVariableLookup lvLookup, int offset, int instrBytes, bool doAddCycles) { DisasmProject proj = gen.Project; Formatter formatter = gen.SourceFormatter; byte[] data = proj.FileData; @@ -241,8 +264,8 @@ namespace SourceGen.AsmGen { gen.UpdateCharacterEncoding(attr.DataDescriptor); } formattedOperand = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable, - gen.Localizer.LabelMap, attr.DataDescriptor, - operandForSymbol, operandLen, opFlags); + lvLookup, gen.Localizer.LabelMap, attr.DataDescriptor, + offset, operandForSymbol, operandLen, opFlags); } } else { // Show operand value in hex. diff --git a/SourceGen/DisasmProject.cs b/SourceGen/DisasmProject.cs index 75954ef..1090f45 100644 --- a/SourceGen/DisasmProject.cs +++ b/SourceGen/DisasmProject.cs @@ -911,76 +911,45 @@ namespace SourceGen { /// for the benefit of uniqueness checks. /// private void GenerateVariableRefs() { - LocalVariableTable curTab = new LocalVariableTable(); - - int nextLvtIndex, nextLvtOffset; - if (LvTables.Count > 0) { - nextLvtIndex = 0; - nextLvtOffset = LvTables.Keys[0]; - } else { - nextLvtIndex = -1; - nextLvtOffset = FileData.Length; - } + LocalVariableLookup lvLookup = new LocalVariableLookup(LvTables, SymbolTable, this); for (int offset = 0; offset < FileData.Length; ) { - // Have we reached the start of the next LV table? - while (offset >= nextLvtOffset) { - // We want to skip over any "hidden" tables. It's possible, if a bunch of - // tables got collapsed inside a data area, that we need to skip more than one. - if (offset == nextLvtOffset && mAnattribs[offset].IsStart) { - //Debug.WriteLine("FOUND +" + offset.ToString("x6")); - LocalVariableTable lvt = LvTables.Values[nextLvtIndex]; - if (lvt.ClearPrevious) { - curTab.Clear(); + // 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. + List vars = lvLookup.GetVariablesDefinedAtOffset(offset); + if (vars != null) { + foreach (DefSymbol defSym in vars) { + if (!SymbolTable.TryGetValue(defSym.Label, out Symbol sym)) { + 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); } - - // Merge the new entries into the work table. This automatically - // discards entries that clash. - for (int i = 0; i < lvt.Count; i++) { - curTab.AddOrReplace(lvt[i]); - } - //curTab.DebugDump(); - - // 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. - AddVariablesToSymbolTable(lvt); - } else { - // Either this wasn't an instruction/data start, or we passed this - // one, which only happens for non-start items. Whatever the case, - // we're going to ignore it. - Debug.WriteLine("Ignoring LvTable +" + offset.ToString("x6")); - } - // Advance to next table. - nextLvtIndex++; - if (nextLvtIndex < LvTables.Keys.Count) { - nextLvtOffset = LvTables.Keys[nextLvtIndex]; - } else { - nextLvtOffset = FileData.Length; } } Anattrib attr = mAnattribs[offset]; if (attr.IsInstructionStart && attr.DataDescriptor == null) { OpDef op = CpuDef.GetOpDef(FileData[offset]); + DefSymbol defSym = null; if (op.IsDirectPageInstruction) { Debug.Assert(attr.OperandAddress == FileData[offset + 1]); - DefSymbol defSym = curTab.GetByValueRange(attr.OperandAddress, 1, + defSym = lvLookup.GetSymbol(offset, FileData[offset + 1], Symbol.Type.ExternalAddr); - if (defSym != null) { - mAnattribs[offset].DataDescriptor = - FormatDescriptor.Create(attr.Length, - new WeakSymbolRef(defSym.Label, WeakSymbolRef.Part.Low), false); - } - } else if (op.AddrMode == OpDef.AddressMode.StackRel || - op.AddrMode == OpDef.AddressMode.StackRelIndIndexY) { - DefSymbol defSym = curTab.GetByValueRange(FileData[offset + 1], 1, + } else if (op.IsStackRelInstruction) { + defSym = lvLookup.GetSymbol(offset, FileData[offset + 1], Symbol.Type.Constant); - if (defSym != null) { - mAnattribs[offset].DataDescriptor = - FormatDescriptor.Create(attr.Length, - new WeakSymbolRef(defSym.Label, WeakSymbolRef.Part.Low), false); - } + } + if (defSym != null) { + WeakSymbolRef vref = new WeakSymbolRef(defSym.Label, + WeakSymbolRef.Part.Low, op.IsStackRelInstruction ? + WeakSymbolRef.LocalVariableType.StackRelConst : + WeakSymbolRef.LocalVariableType.DpAddr); + mAnattribs[offset].DataDescriptor = + FormatDescriptor.Create(attr.Length, vref, false); } } @@ -993,15 +962,6 @@ namespace SourceGen { } } - private void AddVariablesToSymbolTable(LocalVariableTable lvt) { - for (int i = 0; i < lvt.Count; i++) { - DefSymbol defSym = lvt[i]; - if (!SymbolTable.TryGetValue(defSym.Label, out Symbol sym)) { - SymbolTable[defSym.Label] = defSym; - } - } - } - /// /// Generates references to symbols in the project/platform symbol tables. /// @@ -1048,7 +1008,7 @@ namespace SourceGen { if (sym == null && (attr.OperandAddress & 0xffff) > 1 && checkNearby) { sym = SymbolTable.FindAddressByValue(attr.OperandAddress - 2); } - if (sym != null) { + if (sym != null && !sym.IsVariable) { mAnattribs[offset].DataDescriptor = FormatDescriptor.Create(mAnattribs[offset].Length, new WeakSymbolRef(sym.Label, WeakSymbolRef.Part.Low), false); @@ -1126,6 +1086,8 @@ namespace SourceGen { } } + LocalVariableLookup lvLookup = new LocalVariableLookup(LvTables, null, this); + // Walk through the Anattrib array, adding xref entries to things referenced // by the entity at the current offset. for (int offset = 0; offset < mAnattribs.Length; ) { @@ -1174,11 +1136,21 @@ namespace SourceGen { if (adj == 0) { hasZeroOffsetSym = true; } + } else if (dfd.SymbolRef.IsVariable) { + DefSymbol defSym = lvLookup.GetSymbol(offset, dfd.SymbolRef); + if (defSym != null) { + int adj = 0; + if (operandOffset >= 0) { + adj = defSym.Value - operandOffset; + } + defSym.Xrefs.Add( + new XrefSet.Xref(offset, true, xrefType, accType, adj)); + } } else if (SymbolTable.TryGetValue(dfd.SymbolRef.Label, out Symbol sym)) { - // Is this a reference to a project/platform symbol? + // Is this a reference to a project/platform symbol? We also handle + // local variables here. if (sym.SymbolSource == Symbol.Source.Project || - sym.SymbolSource == Symbol.Source.Platform || - sym.SymbolSource == Symbol.Source.Variable) { + sym.SymbolSource == Symbol.Source.Platform) { DefSymbol defSym = sym as DefSymbol; int adj = 0; if (operandOffset >= 0) { @@ -1286,7 +1258,7 @@ namespace SourceGen { ActiveDefSymbolList.Clear(); foreach (Symbol sym in SymbolTable) { - if (!(sym is DefSymbol)) { + if (!(sym is DefSymbol) || sym.IsVariable) { continue; } DefSymbol defSym = sym as DefSymbol; diff --git a/SourceGen/FormatDescriptor.cs b/SourceGen/FormatDescriptor.cs index bea50db..2dbc227 100644 --- a/SourceGen/FormatDescriptor.cs +++ b/SourceGen/FormatDescriptor.cs @@ -440,7 +440,11 @@ namespace SourceGen { case SubType.Address: return "Address"; case SubType.Symbol: - return "Symbol \"" + SymbolRef.Label + "\""; + if (SymbolRef.IsVariable) { + return "Local var \"" + SymbolRef.Label + "\""; + } else { + return "Symbol \"" + SymbolRef.Label + "\""; + } case SubType.Ascii: return "Numeric, ASCII"; case SubType.HighAscii: diff --git a/SourceGen/LineListGen.cs b/SourceGen/LineListGen.cs index 9cae379..9c112f1 100644 --- a/SourceGen/LineListGen.cs +++ b/SourceGen/LineListGen.cs @@ -69,6 +69,11 @@ namespace SourceGen { /// private FormattedOperandCache mFormattedLineCache; + /// + /// Local variable table data extractor. + /// + private LocalVariableLookup mLvLookup; + /// /// One of these per line of output in the display. It should be possible to draw @@ -437,6 +442,8 @@ 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); mDisplayList.ListGen = this; } @@ -886,6 +893,9 @@ namespace SourceGen { Debug.Assert(startOffset >= 0); Debug.Assert(endOffset >= startOffset); + // Assume variables may have changed. + mLvLookup.Reset(); + // Find the previous status flags for M/X tracking. StatusFlags prevFlags = StatusFlags.AllIndeterminate; if (mProject.CpuDef.HasEmuFlag) { @@ -1220,8 +1230,9 @@ namespace SourceGen { PseudoOp.FormatNumericOpFlags.None); formattedOperand = '#' + opstr1 + "," + '#' + opstr2; } else { - formattedOperand = PseudoOp.FormatNumericOperand(mFormatter, mProject.SymbolTable, - null, attr.DataDescriptor, operandForSymbol, operandLen, opFlags); + formattedOperand = PseudoOp.FormatNumericOperand(mFormatter, + mProject.SymbolTable, mLvLookup, null, attr.DataDescriptor, offset, + operandForSymbol, operandLen, opFlags); } } else { // Show operand value in hex. @@ -1330,7 +1341,8 @@ namespace SourceGen { null, defSym.DataDescriptor, defSym.Value, 1, PseudoOp.FormatNumericOpFlags.None); string comment = mFormatter.FormatEolComment(defSym.Comment); - return FormattedParts.CreateEquDirective(defSym.Label, + return FormattedParts.CreateEquDirective( + mFormatter.FormatVariableLabel(defSym.Label), mFormatter.FormatPseudoOp(mPseudoOpNames.VarDirective), addrStr, comment); } diff --git a/SourceGen/LocalVariableLookup.cs b/SourceGen/LocalVariableLookup.cs new file mode 100644 index 0000000..5272e4e --- /dev/null +++ b/SourceGen/LocalVariableLookup.cs @@ -0,0 +1,199 @@ +/* + * Copyright 2019 faddenSoft + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace SourceGen { + /// + /// Given a list of LocalVariableTables, this determines the mapping of values to symbols + /// at a specific offset. + /// + public class LocalVariableLookup { + /// + /// List of tables. The table's file offset is used as the key. + /// + private SortedList mLvTables; + + /// + /// Table of symbols, used to ensure that all symbols are globally unique. Only used + /// when generating code for an assembler that doesn't support redefinable variables. + /// + private SymbolTable mSymbolTable; + + /// + /// Reference to project, so we can query the Anattrib array to identify "hidden" tables. + /// + private DisasmProject mProject; + + /// + /// Most recently processed offset. + /// + private int mRecentOffset; + + /// + /// Symbols defined at mRecentOffset. + /// + private List mRecentSymbols; + + /// + /// Cumulative symbols defined at the current offset. + /// + private LocalVariableTable mCurrentTable; + + private int mNextLvtIndex; + private int mNextLvtOffset; + + + /// + /// Constructor. + /// + /// List of tables from the DisasmProject. + /// Full SymbolTable from the DisasmProject. Used to + /// generate globally unique symbol names. Pass null if uniqueness is not a + /// requirement. + /// Project reference. + public LocalVariableLookup(SortedList lvTables, + SymbolTable symbolTable, DisasmProject project) { + mLvTables = lvTables; + mSymbolTable = symbolTable; + mProject = project; + + mCurrentTable = new LocalVariableTable(); + Reset(); + } + + public void Reset() { + mRecentOffset = -1; + mRecentSymbols = null; + mCurrentTable.Clear(); + if (mLvTables.Count == 0) { + mNextLvtIndex = -1; + mNextLvtOffset = mProject.FileDataLength; + } else { + mNextLvtIndex = 0; + mNextLvtOffset = mLvTables.Keys[0]; + } + } + + /// + /// Gets the symbol associated with the operand of an instruction. + /// + /// Offset of start of instruction. + /// Operand value. + /// Operand type. Should be ExternalAddress for DP ops, or + /// Constant for StackRel ops. + /// 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. + /// + /// Offset of start of instruction. + /// Reference to symbol. + /// Symbol, or null if no match found. + public DefSymbol GetSymbol(int offset, WeakSymbolRef symRef) { + AdvanceToOffset(offset); + + return mCurrentTable.GetByLabel(symRef.Label); + } + + /// + /// Gets a LocalVariableTable that is the result of merging all tables up to this point. + /// + /// Target offset. + /// Combined table. + public LocalVariableTable GetMergedTableAtOffset(int offset) { + AdvanceToOffset(offset); + return mCurrentTable; + } + + /// + /// Generates a list of variables defined at the specified offset, if a table is + /// associated with that offset. + /// + /// File data offset. + /// List of symbols, uniquified if desired, or null if no LocalVariableTable + /// exists at the specified offset. + public List GetVariablesDefinedAtOffset(int offset) { + AdvanceToOffset(offset); + + if (mRecentOffset == offset) { + return mRecentSymbols; + } + + return null; + } + + /// + /// Updates internal state to reflect the state of the world at the specified offset. + /// + /// + /// When the offset is greater than or equal to its value on a previous call, we can + /// do an incremental update. If the offset moves backward, we have to reset and walk + /// forward again. + /// + /// Target offset. + private void AdvanceToOffset(int offset) { + if (mNextLvtIndex < 0) { + return; + } + if (offset < mRecentOffset) { + // We went backwards. + Reset(); + } + while (mNextLvtOffset <= offset) { + if (!mProject.GetAnattrib(mNextLvtOffset).IsStart) { + // Hidden table, ignore it. + Debug.WriteLine("Ignoring LvTable at +" + mNextLvtOffset.ToString("x6")); + } else { + // Process this table. + LocalVariableTable lvt = mLvTables.Values[mNextLvtIndex]; + if (lvt.ClearPrevious) { + mCurrentTable.Clear(); + } + + // Create a list for GetVariablesDefinedAtOffset + mRecentSymbols = new List(); + mRecentOffset = mNextLvtOffset; + + // Merge the new entries into the work table. This automatically + // discards entries that clash. + for (int i = 0; i < lvt.Count; i++) { + // TODO: uniquify + mCurrentTable.AddOrReplace(lvt[i]); + + mRecentSymbols.Add(lvt[i]); + } + + mCurrentTable.DebugDump(); + } + + // Update state to look for next table. + mNextLvtIndex++; + if (mNextLvtIndex < mLvTables.Keys.Count) { + mNextLvtOffset = mLvTables.Keys[mNextLvtIndex]; + } else { + mNextLvtOffset = mProject.FileDataLength; // never reached + } + } + } + } +} diff --git a/SourceGen/LocalVariableTable.cs b/SourceGen/LocalVariableTable.cs index 2a98cc4..168eb1c 100644 --- a/SourceGen/LocalVariableTable.cs +++ b/SourceGen/LocalVariableTable.cs @@ -159,7 +159,8 @@ namespace SourceGen { /// not clash. /// /// Value to compare. - /// Width to check. + /// Width to check, useful when checking for collisions. When + /// doing a simple variable lookup, this should be set to 1. /// One matching symbol, or null if none matched. public DefSymbol GetByValueRange(int value, int width, Symbol.Type type) { foreach (KeyValuePair kvp in mVarByLabel) { @@ -181,7 +182,7 @@ namespace SourceGen { Debug.Assert(false, "Unexpected symbol type " + newSym.SymbolType); return; } - if (newSym.SymbolSource != Symbol.Source.Variable) { + if (!newSym.IsVariable) { Debug.Assert(false, "Unexpected symbol source " + newSym.SymbolSource); return; } diff --git a/SourceGen/MainController.cs b/SourceGen/MainController.cs index 3e18ce2..4683e2f 100644 --- a/SourceGen/MainController.cs +++ b/SourceGen/MainController.cs @@ -435,6 +435,7 @@ namespace SourceGen { mFormatterConfig = new Formatter.FormatConfig(); AsmGen.GenCommon.ConfigureFormatterFromSettings(AppSettings.Global, ref mFormatterConfig); + //mFormatterConfig.mLocalVariableLablePrefix = "\u00a4"; // CURRENCY SIGN mFormatterConfig.mEndOfLineCommentDelimiter = ";"; mFormatterConfig.mFullLineCommentDelimiterBase = ";"; mFormatterConfig.mBoxLineCommentDelimiter = string.Empty; diff --git a/SourceGen/ProjectFile.cs b/SourceGen/ProjectFile.cs index 36fb8de..99ba153 100644 --- a/SourceGen/ProjectFile.cs +++ b/SourceGen/ProjectFile.cs @@ -831,7 +831,7 @@ namespace SourceGen { if (!CreateDefSymbol(serDef, contentVersion, report, out DefSymbol defSym)) { return false; } - if (defSym.SymbolSource != Symbol.Source.Variable) { + if (!defSym.IsVariable) { // not expected to happen Debug.WriteLine("Found local variable with bad source: " + defSym.SymbolSource); diff --git a/SourceGen/PseudoOp.cs b/SourceGen/PseudoOp.cs index 4ad47fa..613fe7a 100644 --- a/SourceGen/PseudoOp.cs +++ b/SourceGen/PseudoOp.cs @@ -332,14 +332,14 @@ namespace SourceGen { case FormatDescriptor.Type.NumericLE: po.Opcode = opNames.GetDefineData(length); operand = RawData.GetWord(data, offset, length, false); - po.Operand = FormatNumericOperand(formatter, symbolTable, labelMap, dfd, - operand, length, FormatNumericOpFlags.None); + po.Operand = FormatNumericOperand(formatter, symbolTable, labelMap, + dfd, operand, length, FormatNumericOpFlags.None); break; case FormatDescriptor.Type.NumericBE: po.Opcode = opNames.GetDefineBigData(length); operand = RawData.GetWord(data, offset, length, true); - po.Operand = FormatNumericOperand(formatter, symbolTable, labelMap, dfd, - operand, length, FormatNumericOpFlags.None); + po.Operand = FormatNumericOperand(formatter, symbolTable, labelMap, + dfd, operand, length, FormatNumericOpFlags.None); break; case FormatDescriptor.Type.Fill: po.Opcode = opNames.Fill; @@ -533,8 +533,33 @@ namespace SourceGen { /// does not include the opcode byte. For a relative branch, this will be 2. /// Special handling. public static string FormatNumericOperand(Formatter formatter, SymbolTable symbolTable, - Dictionary labelMap, FormatDescriptor dfd, - int operandValue, int operandLen, FormatNumericOpFlags flags) { + Dictionary labelMap, FormatDescriptor dfd, int operandValue, + int operandLen, FormatNumericOpFlags flags) { + return FormatNumericOperand(formatter, symbolTable, null, labelMap, dfd, -1, + operandValue, operandLen, flags); + } + + /// + /// Format a numeric operand value according to the specified sub-format. + /// + /// Text formatter. + /// Full table of project symbols. + /// Local variable lookup object. May be null if not + /// formatting an instruction. + /// Symbol label remap, for local label conversion. May be + /// null. + /// Operand format descriptor. + /// Offset of start of instruction or data pseudo-op, for + /// variable name lookup. Okay to pass -1 when not formatting an instruction. + /// Operand's value. For most things this comes directly + /// out of the code, for relative branches it's a 24-bit absolute address. + /// Length of operand, in bytes. For an instruction, this + /// does not include the opcode byte. For a relative branch, this will be 2. + /// Special handling. + public static string FormatNumericOperand(Formatter formatter, SymbolTable symbolTable, + LocalVariableLookup lvLookup, Dictionary labelMap, + FormatDescriptor dfd, int offset, int operandValue, int operandLen, + FormatNumericOpFlags flags) { Debug.Assert(operandLen > 0); int hexMinLen = operandLen * 2; @@ -554,7 +579,28 @@ namespace SourceGen { CharEncoding.Encoding enc = SubTypeToEnc(dfd.FormatSubType); return formatter.FormatCharacterValue(operandValue, enc); case FormatDescriptor.SubType.Symbol: - if (symbolTable.TryGetValue(dfd.SymbolRef.Label, out Symbol sym)) { + if (lvLookup != null && dfd.SymbolRef.IsVariable) { + Debug.Assert(operandLen == 1); // only doing 8-bit stuff + //Symbol.Type symType; + //if (dfd.SymbolRef.VarType == WeakSymbolRef.LocalVariableType.DpAddr) { + // symType = Symbol.Type.ExternalAddr; + //} else { + // symType = Symbol.Type.Constant; + //} + //DefSymbol defSym = lvLookup.GetSymbol(offset, operandValue, symType); + DefSymbol defSym = lvLookup.GetSymbol(offset, dfd.SymbolRef); + if (defSym != null) { + StringBuilder sb = new StringBuilder(); + FormatNumericSymbolCommon(formatter, defSym, null, + dfd, operandValue, operandLen, flags, sb); + return sb.ToString(); + } else { + Debug.WriteLine("Local variable format failed"); + Debug.Assert(false); + return formatter.FormatHexValue(operandValue, hexMinLen); + } + } else if (symbolTable.TryGetValue(dfd.SymbolRef.Label, out Symbol sym) && + !sym.IsVariable) { StringBuilder sb = new StringBuilder(); switch (formatter.ExpressionMode) { @@ -604,6 +650,9 @@ namespace SourceGen { if (labelMap != null && labelMap.TryGetValue(symLabel, out string newLabel)) { symLabel = newLabel; } + if (sym.IsVariable) { + symLabel = formatter.FormatVariableLabel(symLabel); + } if (operandLen == 1) { // Use the byte-selection operator to get the right piece. In 64tass the diff --git a/SourceGen/SourceGen.csproj b/SourceGen/SourceGen.csproj index 58d38d9..683e4af 100644 --- a/SourceGen/SourceGen.csproj +++ b/SourceGen/SourceGen.csproj @@ -77,6 +77,7 @@ + diff --git a/SourceGen/Symbol.cs b/SourceGen/Symbol.cs index ff6ab31..a4315e2 100644 --- a/SourceGen/Symbol.cs +++ b/SourceGen/Symbol.cs @@ -50,8 +50,9 @@ namespace SourceGen { Constant // constant value } - /// Returns true if the symbol's type is an internal label (auto or user). Returns - /// false for external addresses and constants. + /// + /// True if the symbol's type is an internal label (auto or user). Will be false + /// for external addresses and constants. /// public bool IsInternalLabel { get { @@ -60,6 +61,15 @@ namespace SourceGen { } } + /// + /// True if the symbol is a local variable. + /// + public bool IsVariable { + get { + return SymbolSource == Source.Variable; + } + } + /// /// Label sent to assembler. diff --git a/SourceGen/WeakSymbolRef.cs b/SourceGen/WeakSymbolRef.cs index fe19b9a..a221dbc 100644 --- a/SourceGen/WeakSymbolRef.cs +++ b/SourceGen/WeakSymbolRef.cs @@ -48,6 +48,16 @@ namespace SourceGen { Bank, // LDA #^label } + /// + /// If this is a local varaiable reference, what type is it. + /// + public enum LocalVariableType { + Unknown = 0, + NotVar, + DpAddr, + StackRelConst + } + /// /// Label of symbol of interest. /// @@ -59,14 +69,32 @@ namespace SourceGen { public Part ValuePart { get; private set; } /// - /// Full constructor. + /// Is this a local variable reference, and if so, what type. /// - public WeakSymbolRef(string label, Part part) { + public LocalVariableType VarType { get; private set; } + + /// + /// True for local variable references. + /// + public bool IsVariable { get { return VarType != LocalVariableType.NotVar; } } + + /// + /// Constructor. + /// + public WeakSymbolRef(string label, Part part) : + this(label, part, LocalVariableType.NotVar) { } + + /// + /// Constructor. + /// + public WeakSymbolRef(string label, Part part, LocalVariableType varType) { Debug.Assert(label != null); Label = label; ValuePart = part; + VarType = varType; } + public static bool operator ==(WeakSymbolRef a, WeakSymbolRef b) { if (ReferenceEquals(a, b)) { return true; // same object, or both null @@ -75,7 +103,8 @@ namespace SourceGen { return false; // one is null } return Asm65.Label.LABEL_COMPARER.Equals(a.Label, b.Label) && - a.ValuePart == b.ValuePart; + a.ValuePart == b.ValuePart && + a.VarType == b.VarType; } public static bool operator !=(WeakSymbolRef a, WeakSymbolRef b) { return !(a == b); @@ -84,7 +113,7 @@ namespace SourceGen { return obj is WeakSymbolRef && this == (WeakSymbolRef)obj; } public override int GetHashCode() { - return Asm65.Label.ToNormal(Label).GetHashCode() ^ (int)ValuePart; + return Asm65.Label.ToNormal(Label).GetHashCode() ^ (int)ValuePart ^ (int)VarType; } public override string ToString() { diff --git a/SourceGen/WpfGui/EditDefSymbol.xaml.cs b/SourceGen/WpfGui/EditDefSymbol.xaml.cs index 9889d9d..25c77f4 100644 --- a/SourceGen/WpfGui/EditDefSymbol.xaml.cs +++ b/SourceGen/WpfGui/EditDefSymbol.xaml.cs @@ -171,7 +171,7 @@ namespace SourceGen.WpfGui { return; } - // Label must be valid and not already exist in project symbol list. (For project + // Label must be valid and not already exist in the table we're editing. (For project // symbols, it's okay if an identical label exists elsewhere.) bool labelValid = Asm65.Label.ValidateLabel(Label); bool labelUnique; @@ -187,6 +187,11 @@ namespace SourceGen.WpfGui { // For local variables, do a secondary uniqueness check across the full symbol table. if (labelUnique && mSymbolTable != null) { labelUnique = !mSymbolTable.TryGetValue(Label, out Symbol sym); + + // It's okay if this and the other are both variables. + if (!labelUnique && mIsVariable && sym.IsVariable) { + labelUnique = true; + } } // Value must be blank, meaning "erase any earlier definition", or valid value. diff --git a/SourceGen/WpfGui/MainWindow.xaml.cs b/SourceGen/WpfGui/MainWindow.xaml.cs index b71abbd..329d5a4 100644 --- a/SourceGen/WpfGui/MainWindow.xaml.cs +++ b/SourceGen/WpfGui/MainWindow.xaml.cs @@ -1505,7 +1505,8 @@ namespace SourceGen.WpfGui { (SymFilterPlatformSymbols != true && sli.Sym.SymbolSource == Symbol.Source.Platform) || (SymFilterAutoLabels != true && sli.Sym.SymbolSource == Symbol.Source.Auto) || (SymFilterAddresses != true && sli.Sym.SymbolType != Symbol.Type.Constant) || - (SymFilterConstants != true && sli.Sym.SymbolType == Symbol.Type.Constant)) + (SymFilterConstants != true && sli.Sym.SymbolType == Symbol.Type.Constant) || + sli.Sym.IsVariable) { e.Accepted = false; } else {