1
0
mirror of https://github.com/fadden/6502bench.git synced 2024-06-12 08:29:29 +00:00
6502bench/SourceGen/LocalVariableTable.cs
Andy McFadden b387298685 Fix various local variable de-duplication bugs
In 1.5.0-dev1, as part of changes to the way label localization
works, the local variable de-duplicator started checking against a
filtered copy of the symbol table.  Unfortunately it never
re-generated the table, so a long-lived LocalVariableLookup (like
the one used by LineListGen) would set up the dup map wrong and
be inconsistent with other parts of the program.

We now regenerate the table on every Reset().

The de-duplication stuff also had problems when opcodes and
operands were double-clicked on.  When the opcode is clicked, the
selection should jump to the appropriate variable declaration, but
it wasn't being found because the label generated in the list was
in its original form.  Fixed.

When an instruction operand is double-clicked, the instruction operand
editor opens with an "edit variable" shortcut.  This was showing
the de-duplicated name, which isn't necessarily a bad thing, but it
was passing that value on to the DefSymbol editor, which thought it
was being asked to create a new entry.  Fixed.  (Entering the editor
through the LvTable editor works correctly, with nary a de-duplicated
name in sight.  You'll be forced to rename it because it'll fail the
uniqueness test.)

References to de-duplicated local variables were getting lost when
the symbol's label was replaced (due largely to a convenient but
flawed shortcut: xrefs are attached to DefSymbol objects).  Fixed by
linking the XrefSets.

Given the many issues and their relative subtlety, I decided to make
the modified names more obvious, and went back to the "_DUPn" naming
strategy.  (I'm also considering just making it an error and
discarding conflicting entries during analysis... this is much more
complicated than I expected it to be.)

Quick tests can be performed in 2019-local-variables:
 - go to +000026, double-click on the opcode, confirm sel change
 - go to +000026, double-click on the operand, confirm orig name
   shown in shortcut and that shortcut opens editor with orig name
 - go to +00001a, down a line, click on PROJ_ZERO_DUP1 and confirm
   that it has a single reference (from +000026)
 - double-click on var table and confirm editing entry
2020-01-13 18:32:56 -08:00

285 lines
11 KiB
C#

/*
* 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 {
/// <summary>
/// Table of redefinable variables. A project may have several of these, at different
/// offsets.
///
/// The class is mutable, but may only be modified by the LvTable editor (which makes
/// changes to a work object that moves through the undo/redo buffer) or the
/// deserializer.
/// </summary>
/// <remarks>
/// The contents of later tables overwrite the contents of earlier tables. A
/// variable is replaced if the name is re-used (because a symbol can have only one
/// value at a time) or if the value is re-used (because they're applied automatically
/// and we need to know which symbol to use).
///
/// The DefSymbols should have symbol type Constant or ExternalAddr. These do not clash
/// with each other, e.g. the statements "LDA $10,S" and "STA $10" use two different
/// variables, because one is an 8-bit stack offset while the other is an 8-bit direct page
/// address.
///
/// (Referring to these as "local" variables is a bit of a misnomer, since they have
/// global scope from the point where they're defined. The name reflects their intended
/// usage, rather than how the assembler will treat them.)
/// </remarks>
public class LocalVariableTable {
/// <summary>
/// If set, all values from previous VariableTables should be discarded when this
/// table is encountered.
/// </summary>
/// <remarks>
/// This does not correspond to any output in generated assembly code. We simply stop
/// trying to associate the symbols with instructions. The code will either use a
/// less tightly-scoped value (e.g. project symbol) or output as hex. There is no need
/// to tell the assembler to forget the symbol.
///
/// It might be useful to allow addresses (DP ops) and constants (StackRel ops) to be
/// cleared independently, but I suspect the typical compiled-language scenario will
/// involve StackRel for args and a sliding DP for locals, so generally it makes
/// sense to just clear everything.
/// </remarks>
public bool ClearPrevious { get; set; }
/// <summary>
/// List of variables, sorted by label.
/// </summary>
/// <remarks>
/// This is the original form of the label. De-duplication and uniquification have
/// not been applied.
/// </remarks>
private SortedList<string, DefSymbol> mVarByLabel;
/// <summary>
/// List of variables. This is manually sorted when needed. The key is a combination
/// of the value and the symbol type, so we can't use a simple SortedList.
/// </summary>
private List<DefSymbol> mVarByValue;
private bool mNeedSort = true;
/// <summary>
/// Constructs an empty table.
/// </summary>
public LocalVariableTable() {
mVarByLabel = new SortedList<string, DefSymbol>();
mVarByValue = new List<DefSymbol>();
}
/// <summary>
/// Copy constructor.
/// </summary>
/// <param name="src">Object to clone.</param>
public LocalVariableTable(LocalVariableTable src) : this() {
ClearPrevious = src.ClearPrevious;
foreach (KeyValuePair<string, DefSymbol> kvp in src.mVarByLabel) {
mVarByLabel[kvp.Value.Label] = kvp.Value;
mVarByValue.Add(kvp.Value);
}
Debug.Assert(this == src);
}
/// <summary>
/// Number of entries in the variable table.
/// </summary>
public int Count { get { return mVarByLabel.Count; } }
/// <summary>
/// Returns the Nth item, sorted by value. This is NOT a lookup by value.
/// </summary>
public DefSymbol this[int index] {
get {
SortIfNeeded();
return mVarByValue[index];
}
}
private void SortIfNeeded() {
if (mNeedSort) {
// Currently sorting primarily by value, secondarily by symbol type. This
// ordering determines how it appears in the code list. If we want to make it
// configurable we just need to replace the sort function.
mVarByValue.Sort((a, b) => {
// Numeric ascending.
int diff = a.Value - b.Value;
if (diff != 0) {
return diff;
}
// DP addr first, StackRel const second
if (a.SymbolType == Symbol.Type.ExternalAddr) {
return -1;
} else {
return 1;
}
//return a.Label.CompareTo(b.Label);
});
mNeedSort = false;
}
}
/// <summary>
/// Clears the tables.
/// </summary>
public void Clear() {
mVarByLabel.Clear();
mVarByValue.Clear();
}
/// <summary>
/// Returns the symbol that matches the label, or null if not found.
/// </summary>
public DefSymbol GetByLabel(string label) {
mVarByLabel.TryGetValue(label, out DefSymbol defSym);
return defSym;
}
/// <summary>
/// Removes the symbol with the matching label.
/// </summary>
public void RemoveByLabel(string label) {
if (mVarByLabel.TryGetValue(label, out DefSymbol defSym)) {
mVarByLabel.Remove(defSym.Label);
mVarByValue.Remove(defSym);
}
Debug.Assert(mVarByValue.Count == mVarByLabel.Count);
// Should not be necessary to re-sort the by-value list.
}
/// <summary>
/// Finds symbols that overlap with the specified value and width. If more than one
/// matching symbol is found, an arbitrary match will be returned. Comparisons are
/// only performed between symbols of the same type, so addresses and constants do
/// not clash.
/// </summary>
/// <param name="value">Value to compare.</param>
/// <param name="width">Width to check, useful when checking for collisions. When
/// doing a simple variable lookup, this should be set to 1.</param>
/// <returns>One matching symbol, or null if none matched.</returns>
public DefSymbol GetByValueRange(int value, int width, Symbol.Type type) {
foreach (KeyValuePair<string, DefSymbol> kvp in mVarByLabel) {
if (DefSymbol.CheckOverlap(kvp.Value, value, width, type)) {
return kvp.Value;
}
}
return null;
}
/// <summary>
/// Adds a symbol to the variable table. Existing entries with the same name or
/// overlapping values will be removed.
/// </summary>
/// <param name="newSym">Symbol to add.</param>
public void AddOrReplace(DefSymbol newSym) {
if (!newSym.IsConstant && newSym.SymbolType != Symbol.Type.ExternalAddr) {
Debug.Assert(false, "Unexpected symbol type " + newSym.SymbolType);
return;
}
if (!newSym.IsVariable) {
Debug.Assert(false, "Unexpected symbol source " + newSym.SymbolSource);
return;
}
// Remove existing entries that match on label or value. The value check must
// take the width into account.
if (mVarByLabel.TryGetValue(newSym.Label, out DefSymbol labelSym)) {
mVarByLabel.Remove(labelSym.Label);
mVarByValue.Remove(labelSym);
}
// Inefficient, but the list should be small.
DefSymbol valSym;
while ((valSym = GetByValueRange(newSym.Value,
newSym.DataDescriptor.Length, newSym.SymbolType)) != null) {
mVarByLabel.Remove(valSym.Label);
mVarByValue.Remove(valSym);
}
mVarByLabel.Add(newSym.Label, newSym);
mVarByValue.Add(newSym);
Debug.Assert(mVarByValue.Count == mVarByLabel.Count);
mNeedSort = true;
}
/// <summary>
/// Returns a reference to the sorted-by-label list. The caller must not modify it.
/// </summary>
/// <remarks>
/// This exists primarily for EditDefSymbol, which wants a list of this type to
/// perform uniqueness checks.
/// </remarks>
public SortedList<string, DefSymbol> GetSortedByLabel() {
return mVarByLabel;
}
public static bool operator ==(LocalVariableTable a, LocalVariableTable b) {
if (ReferenceEquals(a, b)) {
return true; // same object, or both null
}
if (ReferenceEquals(a, null) || ReferenceEquals(b, null)) {
return false; // one is null
}
// All fields must be equal.
if (a.ClearPrevious != b.ClearPrevious) {
return false;
}
if (a.mVarByLabel.Count != b.mVarByLabel.Count) {
return false;
}
// Compare all list entries.
for (int i = 0; i < a.mVarByLabel.Count; i++) {
if (a.mVarByLabel.Values[i] != b.mVarByLabel.Values[i]) {
return false;
}
}
return true;
}
public static bool operator !=(LocalVariableTable a, LocalVariableTable b) {
return !(a == b);
}
public override bool Equals(object obj) {
return obj is LocalVariableTable && this == (LocalVariableTable)obj;
}
public override int GetHashCode() {
int hashCode = 0;
foreach (KeyValuePair<string, DefSymbol> kvp in mVarByLabel) {
hashCode ^= kvp.Value.GetHashCode();
}
if (ClearPrevious) {
hashCode++;
}
return hashCode;
}
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]);
}
}
}
}