Add local variable uniquification

For ACME and cc65, enable uniqification.  This works with my basic
tests, but there are a lot of potential edge cases.
This commit is contained in:
Andy McFadden 2019-08-31 14:10:59 -07:00
parent 6a2532588b
commit 02c79db749
12 changed files with 162 additions and 44 deletions

View File

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

View File

@ -160,6 +160,7 @@ namespace SourceGen.AsmGen {
Project = project;
Quirks = new AssemblerQuirks();
Quirks.HasRedefinableSymbols = true;
Quirks.StackIntOperandIsImmediate = true;
mWorkDirectory = workDirectory;

View File

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

View File

@ -186,26 +186,21 @@ namespace SourceGen.AsmGen {
/// Enumeration of quirky or buggy behavior that GenCommon needs to handle.
/// </summary>
public class AssemblerQuirks {
/// <summary>
/// Are the arguments to MVN/MVP reversed?
/// </summary>
public bool BlockMoveArgsReversed { get; set; }
/// <summary>
/// Are 8-bit constant args to MVN/MVP output without a leading '#'?
/// </summary>
public bool BlockMoveArgsNoHash { get; set; }
/// <summary>
/// Do 8-bit constant args to StackInt ops (BRK/COP) require a leading '#'?
/// Are the arguments to MVN/MVP reversed?
/// </summary>
public bool StackIntOperandIsImmediate { get; set; }
public bool BlockMoveArgsReversed { get; set; }
/// <summary>
/// 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?
/// </summary>
public bool TracksSepRepNotEmu { get; set; }
public bool HasRedefinableSymbols { get; set; }
/// <summary>
/// Is the assembler unable to generate relative branches that wrap around banks?
@ -213,6 +208,11 @@ namespace SourceGen.AsmGen {
/// </summary>
public bool NoPcRelBankWrap { get; set; }
/// <summary>
/// Do 8-bit constant args to StackInt ops (BRK/COP) require a leading '#'?
/// </summary>
public bool StackIntOperandIsImmediate { get; set; }
/// <summary>
/// Is the assembler implemented as a single pass? (e.g. cc65)
/// </summary>
@ -223,5 +223,11 @@ namespace SourceGen.AsmGen {
/// and not corrected when the actual width is determined?
/// </summary>
public bool SinglePassNoLabelCorrection { get; set; }
/// <summary>
/// Does the assembler configure assembler widths based on SEP/REP, but doesn't
/// track the emulation bit?
/// </summary>
public bool TracksSepRepNotEmu { get; set; }
}
}

View File

@ -134,6 +134,17 @@ namespace SourceGen {
Tag = string.Empty;
}
/// <summary>
/// Constructs a DefSymbol from an existing DefSymbol, with a different label. Use
/// this to change the label while keeping everything else the same.
/// </summary>
/// <param name="defSym">Source DefSymbol.</param>
/// <param name="label">Label to use.</param>
public DefSymbol(DefSymbol defSym, string label)
: this(label, defSym.Value, defSym.SymbolSource, defSym.SymbolType,
defSym.DataDescriptor.FormatSubType, defSym.Comment, defSym.Tag,
defSym.DataDescriptor.Length) { }
/// <summary>
/// Determines whether a symbol overlaps with a region. Useful for variables.
/// </summary>

View File

@ -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.
/// </summary>
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<DefSymbol> 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);
}
}
}

View File

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

View File

@ -34,6 +34,42 @@ namespace SourceGen {
/// </summary>
private SymbolTable mSymbolTable;
/// <summary>
/// Label uniquification helper.
/// </summary>
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;
}
/// <summary>
/// Updates the Label to be unique. Call this when a symbol is defined or
/// re-defined.
/// </summary>
/// <param name="symbolTable">Symbol table, for uniqueness check.</param>
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<string, UniqueLabel> mUniqueLabels;
/// <summary>
/// Reference to project, so we can query the Anattrib array to identify "hidden" tables.
/// </summary>
@ -73,6 +109,9 @@ namespace SourceGen {
mProject = project;
mCurrentTable = new LocalVariableTable();
if (mSymbolTable != null) {
mUniqueLabels = new Dictionary<string, UniqueLabel>();
}
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 {
/// <returns>Symbol, or null if no match found.</returns>
public DefSymbol GetSymbol(int offset, int operandValue, Symbol.Type type) {
AdvanceToOffset(offset);
return mCurrentTable.GetByValueRange(operandValue, 1, type);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="offset">Offset of start of instruction.</param>
/// <param name="symRef">Reference to symbol.</param>
@ -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;
}
/// <summary>
@ -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.
/// </remarks>
/// <param name="offset">Target offset.</param>
private void AdvanceToOffset(int offset) {
/// <param name="targetOffset">Target offset.</param>
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.

View File

@ -133,15 +133,25 @@ namespace SourceGen {
}
}
/// <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) {
return mVarByLabel[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);
@ -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]);
}

View File

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

View File

@ -203,6 +203,21 @@ namespace SourceGen {
return mSymbols.TryGetValue(key, out sym);
}
/// <summary>
/// Gets the value associated with the key, unless it's a variable.
/// </summary>
/// <param name="key">Label to look up.</param>
/// <param name="sym">Symbol, or null if not found, or found but it's a variable.</param>
/// <returns>True if the key is present, false otherwise.</returns>
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;
}
/// <summary>
/// Removes the specified symbol.
/// </summary>

View File

@ -117,7 +117,7 @@ namespace SourceGen {
}
public override string ToString() {
return "WeakSym: " + Label + ":" + ValuePart;
return "WeakSym: " + (IsVariable ? "var " : "") + Label + ":" + ValuePart;
}
}
}