1
0
mirror of https://github.com/fadden/6502bench.git synced 2024-12-28 01:29:29 +00:00

External symbol I/O direction and address mask, part 2

First cut at lookup-by-address implementation.  Seems to work, but
needs full tests.
This commit is contained in:
Andy McFadden 2019-10-16 14:55:10 -07:00
parent 9c3422623d
commit 4d8ee3fd07
8 changed files with 306 additions and 64 deletions

View File

@ -24,7 +24,7 @@ namespace Asm65 {
/// encapsulated here. All code should be case-preserving, but the comparison method
/// and "normal form" are defined here.
/// </summary>
public class Label {
public static class Label {
// Arbitrary choice for SourceGen. Different assemblers have different limits.
public const int MAX_LABEL_LEN = 32;

View File

@ -55,7 +55,7 @@ namespace Asm65 {
/// of bytes required by the instruction.
/// </summary>
public enum AddressMode : byte {
Unknown = 0,
Unknown = 0, // format bytes (example)
Abs, // OP addr 3
AbsInd, // OP (addr) 3 (JMP)
AbsIndLong, // OP [addr] 3 (JML)
@ -78,7 +78,7 @@ namespace Asm65 {
ImmLongA, // OP #const8/16 2 or 3, depending on 'm' flag
ImmLongXY, // OP #const8/16 2 or 3, depending on 'x' flag
Implied, // OP 1
PCRel, // OP label 2 (branch instructions)
PCRel, // OP label 2 (BCC, BNE, ...)
PCRelLong, // OP label 3 (BRL)
StackAbs, // OP addr 3 (PEA)
StackDPInd, // OP (dp) 2 (PEI)
@ -3393,6 +3393,7 @@ namespace Asm65 {
IsUndocumented = true,
Mnemonic = OpName.NOP,
Effect = FlowEffect.Cont,
BaseMemEffect = MemoryEffect.None,
AddrMode = AddressMode.Implied,
CycDef = 1
};
@ -3402,6 +3403,7 @@ namespace Asm65 {
IsUndocumented = true,
Mnemonic = OpName.LDD,
Effect = FlowEffect.Cont,
BaseMemEffect = MemoryEffect.Read,
AddrMode = AddressMode.Implied
};
public static readonly OpDef OpLDD_Absolute = new OpDef(OpLDD) {

View File

@ -102,15 +102,17 @@ namespace SourceGen {
public DirectionFlags Direction { get; private set; }
/// <summary>
/// Masks for symbols that represent multiple addresses. Usage:
/// <pre>
/// if ((addr & CompareMask) == CompareValue &&
/// (addr & AddressMask) == (Value & AddressMask)) {
/// Bit masks for symbols that represent multiple addresses. Instances are immutable.
/// </summary>
/// <remarks>
/// Given an integer "addr" to test:
/// <code>
/// if ((addr &amp; CompareMask) == CompareValue &amp;&amp;
/// (addr &amp; AddressMask) == (Value &amp; AddressMask)) {
/// // match!
/// }
/// </pre>
/// Instances are immutable.
/// </summary>
/// </code>
/// </remarks>
public class MultiAddressMask {
public int CompareMask { get; private set; }
public int CompareValue { get; private set; }
@ -126,6 +128,25 @@ namespace SourceGen {
" cmpValue=$" + CompareValue.ToString("x4") +
" addrMask=$" + AddressMask.ToString("x4");
}
public static bool operator ==(MultiAddressMask a, MultiAddressMask b) {
if (ReferenceEquals(a, b)) {
return true; // same object, or both null
}
if (ReferenceEquals(a, null) || ReferenceEquals(b, null)) {
return false; // one is null
}
return a.CompareMask == b.CompareMask && a.CompareValue == b.CompareValue &&
a.AddressMask == b.AddressMask;
}
public static bool operator !=(MultiAddressMask a, MultiAddressMask b) {
return !(a == b);
}
public override bool Equals(object obj) {
return obj is MultiAddressMask && this == (MultiAddressMask)obj;
}
public override int GetHashCode() {
return CompareMask ^ CompareValue ^ AddressMask;
}
}
/// <summary>

View File

@ -800,6 +800,7 @@ namespace SourceGen {
debugLog.LogI("Analysis complete");
//Problems.DebugDump();
Debug.WriteLine(SymbolTable.ToString());
}
/// <summary>
@ -1216,7 +1217,7 @@ namespace SourceGen {
OpDef op = CpuDef.GetOpDef(FileData[offset]);
accType = op.MemEffect;
address = attr.OperandAddress;
sym = SymbolTable.FindNonVariableByAddress(address);
sym = SymbolTable.FindNonVariableByAddress(address, accType);
} else if ((attr.IsDataStart || attr.IsInlineDataStart) &&
attr.DataDescriptor != null && attr.DataDescriptor.IsNumeric &&
attr.DataDescriptor.FormatSubType == FormatDescriptor.SubType.Address) {
@ -1232,7 +1233,7 @@ namespace SourceGen {
attr.DataDescriptor.FormatType == FormatDescriptor.Type.NumericBE);
if (AddrMap.AddressToOffset(offset, address) < 0) {
accType = OpDef.MemoryEffect.ReadModifyWrite; // guess
sym = SymbolTable.FindNonVariableByAddress(address);
sym = SymbolTable.FindNonVariableByAddress(address, accType);
} else {
Debug.WriteLine("Found unhandled internal data addr ref at +" +
offset.ToString("x6"));
@ -1251,7 +1252,7 @@ namespace SourceGen {
// finding a label reference rather than project/platform symbol; only
// works if the location already has a label.
if (sym == null && (address & 0xffff) < 0xffff && checkNearby) {
sym = SymbolTable.FindNonVariableByAddress(address + 1);
sym = SymbolTable.FindNonVariableByAddress(address + 1, accType);
if (sym != null && sym.SymbolSource != Symbol.Source.Project &&
sym.SymbolSource != Symbol.Source.Platform) {
Debug.WriteLine("Applying non-platform in GeneratePlatform: " + sym);

View File

@ -329,6 +329,7 @@ namespace SourceGen {
public string Direction { get; set; }
public SerMultiMask MultiMask { get; set; }
// Tag not relevant, Xrefs not recorded
// MultiMask currently not set for project symbols, but we support it anyway.
public SerDefSymbol() { }
public SerDefSymbol(DefSymbol defSym) : base(defSym) {

View File

@ -9,8 +9,8 @@
; There's a lot here that you wouldn't find on an Apple ][+, but since
; this is for a disassembler it's okay to have extra stuff so long as it
; doesn't conflict.
KBD @ $C000 ;R last key pressed + 128
CLR80COL = $c000 ;W use $C002-C005 for aux mem (80STOREOFF)
KBD < $C000 ;R last key pressed + 128
CLR80COL > $C000 ;W use $C002-C005 for aux mem (80STOREOFF)
SET80COL @ $C001 ;W use PAGE2 for aux mem (80STOREON)
RDMAINRAM @ $C002 ;W if 80STORE off: read main mem $0200-BFFF
RDCARDRAM @ $C003 ;W if 80STORE off: read aux mem $0200-BFFF
@ -100,8 +100,8 @@ SETAN2 @ $C05C ;RW annunciator 2 off
CLRAN2 @ $C05D ;RW annunciator 2 on
SETAN3 @ $C05E ;RW annunciator 3 off
CLRAN3 @ $C05F ;RW annunciator 3 on
TAPEIN = $C060 ;R read cassette input
BUTN3 @ $C060 ;R switch input 3
TAPEIN @ $C060 ;R read cassette input
BUTN3 = $C060 ;R switch input 3
BUTN0 @ $C061 ;R switch input 0 / open-apple
BUTN1 @ $C062 ;R switch input 1 / closed-apple
BUTN2 @ $C063 ;R switch input 2 / shift key
@ -172,4 +172,13 @@ ROM_XFER @ $C314
; system, e.g. DOS puts the slot number of the peripheral card from
; which it was booted into $5F8.
;
ACTV_PERIP_SLOT @ $07F8 ;slot num ($Cn) of active peripheral card
SCRNHOLE0 @ $0478 8 ;text page 1 screen holes
SCRNHOLE1 @ $04F8 8 ;text page 1 screen holes
SCRNHOLE2 @ $0578 8 ;text page 1 screen holes
SCRNHOLE3 @ $05F8 8 ;text page 1 screen holes
SCRNHOLE4 @ $0678 8 ;text page 1 screen holes
SCRNHOLE5 @ $06F8 8 ;text page 1 screen holes
SCRNHOLE6 @ $0778 8 ;text page 1 screen holes
SCRNHOLE7 @ $07F8 8 ;text page 1 screen holes
ACTV_PERIP_SLOT @ $07F8 ;slot num ($Cn) of active peripheral card

View File

@ -32,8 +32,8 @@ VSYNC > $00 ;W 0000 00x0 Vertical Sync Set-Clear
VBLANK > $01 ;W xx00 00x0 Vertical Blank Set-Clear
WSYNC > $02 ;W ---- ---- Wait for Horizontal Blank
RSYNC > $03 ;W ---- ---- Reset Horizontal Sync Counter
NUSIZ0 > $04 ;W 00xx 0xxx Number-Size player/missle 0
NUSIZ1 > $05 ;W 00xx 0xxx Number-Size player/missle 1
NUSIZ0 > $04 ;W 00xx 0xxx Number-Size player/missile 0
NUSIZ1 > $05 ;W 00xx 0xxx Number-Size player/missile 1
COLUP0 > $06 ;W xxxx xxx0 Color-Luminance Player 0
COLUP1 > $07 ;W xxxx xxx0 Color-Luminance Player 1
COLUPF > $08 ;W xxxx xxx0 Color-Luminance Playfield
@ -46,8 +46,8 @@ PF1 > $0e ;W xxxx xxxx Playfield Register Byte 1
PF2 > $0f ;W xxxx xxxx Playfield Register Byte 2
RESP0 > $10 ;W ---- ---- Reset Player 0
RESP1 > $11 ;W ---- ---- Reset Player 1
RESM0 > $12 ;W ---- ---- Reset Missle 0
RESM1 > $13 ;W ---- ---- Reset Missle 1
RESM0 > $12 ;W ---- ---- Reset Missile 0
RESM1 > $13 ;W ---- ---- Reset Missile 1
RESBL > $14 ;W ---- ---- Reset Ball
AUDC0 > $15 ;W 0000 xxxx Audio Control 0
AUDC1 > $16 ;W 0000 xxxx Audio Control 1
@ -57,19 +57,19 @@ AUDV0 > $19 ;W 0000 xxxx Audio Volume 0
AUDV1 > $1a ;W 0000 xxxx Audio Volume 1
GRP0 > $1b ;W xxxx xxxx Graphics Register Player 0
GRP1 > $1c ;W xxxx xxxx Graphics Register Player 1
ENAM0 > $1d ;W 0000 00x0 Graphics Enable Missle 0
ENAM1 > $1e ;W 0000 00x0 Graphics Enable Missle 1
ENAM0 > $1d ;W 0000 00x0 Graphics Enable Missile 0
ENAM1 > $1e ;W 0000 00x0 Graphics Enable Missile 1
ENABL > $1f ;W 0000 00x0 Graphics Enable Ball
HMP0 > $20 ;W xxxx 0000 Horizontal Motion Player 0
HMP1 > $21 ;W xxxx 0000 Horizontal Motion Player 1
HMM0 > $22 ;W xxxx 0000 Horizontal Motion Missle 0
HMM1 > $23 ;W xxxx 0000 Horizontal Motion Missle 1
HMM0 > $22 ;W xxxx 0000 Horizontal Motion Missile 0
HMM1 > $23 ;W xxxx 0000 Horizontal Motion Missile 1
HMBL > $24 ;W xxxx 0000 Horizontal Motion Ball
VDELP0 > $25 ;W 0000 000x Vertical Delay Player 0
VDELP1 > $26 ;W 0000 000x Vertical Delay Player 1
VDELBL > $27 ;W 0000 000x Vertical Delay Ball
RESMP0 > $28 ;W 0000 00x0 Reset Missle 0 to Player 0
RESMP1 > $29 ;W 0000 00x0 Reset Missle 1 to Player 1
RESMP0 > $28 ;W 0000 00x0 Reset Missile 0 to Player 0
RESMP1 > $29 ;W 0000 00x0 Reset Missile 1 to Player 1
HMOVE > $2a ;W ---- ---- Apply Horizontal Motion
HMCLR > $2b ;W ---- ---- Clear Horizontal Move Registers
CXCLR > $2c ;W ---- ---- Clear Collision Latches

View File

@ -18,8 +18,10 @@ using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using Asm65;
/*
A few words about by-value lookups.
A few words about by-value lookups of external addresses.
We guarantee that symbol labels are unique, but multiple symbols can have the same value.
This becomes interesting when we're trying to match external address references to symbols
@ -45,7 +47,7 @@ The basic rules for determining the symbol associated with a given address are:
Working through the math on every access could get painful, so we create a dictionary with
the value as the key, and add symbols to it as we work our way through that platform and
project files. Every address is represented, so a label with a width of 10 would have 10
project files. Every address is represented, so a symbol with a width of 10 would have 10
entries in the dictionary. If we're adding a symbol at an address that already has an entry,
we do a priority check, and either leave it alone or replace it with the new value.
@ -53,7 +55,32 @@ For 8-bit code it would be slightly more efficient to use a 64K array representi
memory, but that doesn't scale for 65816. That said, we probably want to break it up by
bank anyway to allow for partial updates.
----------
It's worth noting that the only public by-address interface is the find-by-address method.
Everything is done by symbol or label, and the by-address stuff happens behind the scenes.
-----
A few words about I/O direction.
Some memory-mapped I/O locations have different behavior when read than they do when written.
For example, on later models of the Apple II, reading from $C000 returns the last key hit,
while writing to $C000 disables 80-column mode. When doing an external address lookup, we
may need to know what sort of access is happening.
We need to be able to define independent symbols for reading and writing, which means either
having two separate address lookup tables, or one table with two references per entry. If
we use an array of structs as our table, having two refs per entry is efficient. If we use
a collection class like Dictionary, two refs per entry requires allocating an additional
object to hold the object pair, so it's more memory efficient to have two separate dictionaries.
However, separate data structures means double the dictionary lookups (which are O(1) in an
unsorted Dictionary).
Not all instructions perform an access. For example, JSR doesn't immediately read from the
location, it just sets the program counter. On the 65816, instructions like PEA take an
address, but don't cause any access at all. We can't generally know what's coming next, so
for access type "none" we try Read then Write.
-----
A few words about address masks.
@ -61,28 +88,73 @@ On the Atari 2600, you can access registers, RAM, and ROM from multiple addresse
example, the first TIA register can be accessed at $0000, $0040, $0100, $0140, and so on,
but only in "even" 4K pages ($0000, $2000, $4000, ...). Because the underlying hardware is
just watching for specific values on certain address lines, the set of matching addresses can
be described with a pair of bit masks, plus one more mask to define which lines are relevant.
be described with a pair of bit masks. We need one more mask to define which address bits
are used to select a specific register.
The question is how to handle a by-address lookup here. There are two basic approaches:
1. Add all possible entries to the dictionary.
2. Maintain a separate list of masked symbols, and match against those.
1. Add all possibly matching addresses to the dictionary.
2. Maintain a separate list of masked symbols, and match against those in a separate step.
Option #1 makes adding a symbol expensive, but lookups very cheap. We have to add
potentially thousands of entries to the dictionary for each masked symbol. When we want
to look up a symbol, though, we don't have to do anything different.
to look up a symbol, though, we just check the entry for the address.
Option #2 makes adding a symbol cheap, but lookups are problematic. The problem arises if
a masked symbol overlaps with a non-masked symbol. If we want the priority to work the way
we described earlier, some non-masked symbols might have priority over a given masked symbol
while others don't.
(I kinda feel like I'm solving problems I don't have, but consistent behavior is a Good Thing.)
Option #2 makes adding a symbol cheap, but lookups are inconsistent with the established rules.
The problem arises if a masked symbol overlaps with a non-masked symbol. If we want the priority
to work the way we described earlier, some non-masked symbols might have priority over a given
masked symbol while others don't.
v---------
It's possible to mitigate the problems of both with a hybrid approach:
- Non-masked symbols get added to the dictionary as usual.
- Masked symbols are compared to all dictionary entries. If the mask matches, the
- Masked symbols are compared to all existing dictionary entries. If the mask matches, the
existing entry is kept or replaced according to the usual rules.
- On lookup-by-value, we check for a match in the dictionary. If we don't find one, we
test it against all masked values.
We still have to iterate for each masked symbol, but only over the set of non-masked symbols
rather than an entire bank (or more... need to define what masking means for 16-bit code),
and we don't have to create any new entries.
We can improve the lookup speed by keeping the symbols grouped by CompareMask/CompareValue. If
the test fails we can ignore all symbols in the group. If more than one symbol has the same
value (after being masked with AddressMask), we replace symbols in priority order.
The behavior when two masked groups overlap is somewhat unspecified, especially if we combine
symbols with identical mask sets. We could set the mask set to ABC, define symbols, switch
to DEF, define symbols, then switch back to ABC and define more symbols. If ABC is a subset
of DEF, it's possible that symbols are defined in the third set that should replace symbols
in the second set. But because ABC was defined first, an ordered list would check DEF for a
match before checking ABC. (I'm pretty comfortable with declaring that the behavior of
overlapping mask sets is undefined... if it's ambiguous to the hardware, I'm just not going
to worry about it.)
---------^
I think approach #2 is entirely reasonable. Make masked lookups the lowest priority, so
that specific overrides can be defined in the usual way. The masked symbols catch anything
that falls through the cracks. We can revisit this if there turns out to be an interesting
use case that justifies the additional work.
All of the above must be done twice for ReadWrite symbols, once per direction.
-----
A few words about updating the by-address table.
The previous notes were largely concerned with populating the table. We also need to worry
about updating the table when new entries are added, edited, or removed. We want, whenever,
possible, to avoid updating the entire table.
The troubles arise when we remove an entry, or we add/edit an entry with a label that conflicts
with another entry, effectively adding or removing the conflicting symbol. Because of the
layered approach we use, it's sometimes necessary to regenerate the by-address table from the
contents of the by-label table. For example, if we have a platform symbol named "FOO" with a
width of 10, and we create a user label named "FOO", the platform symbol disappears completely.
Overlapping symbols that had been hidden due to lower priority must be restored.
We could reduce the update cost by making each table entry a priority-ordered list of symbols.
I'm expecting conflicts to be rare in practice, so no need to worry about this yet.
*/
namespace SourceGen {
@ -108,9 +180,75 @@ namespace SourceGen {
/// For efficiency on larger data files, we may want to break this up by bank. That
/// way we can do a partial update.
/// </remarks>
private Dictionary<int, Symbol> mSymbolsByAddress = new Dictionary<int, Symbol>();
private Dictionary<int, Symbol> mSymbolsByReadAddress = new Dictionary<int, Symbol>();
private Dictionary<int, Symbol> mSymbolsByWriteAddress = new Dictionary<int, Symbol>();
/// <summary>
/// Container for a collection of symbols that share a common mask definition.
/// </summary>
private class MaskGroup {
private DefSymbol.MultiAddressMask mMultiMask;
// Keyed by minimal address, not canonical address.
private Dictionary<int, DefSymbol> mByReadAddress = new Dictionary<int, DefSymbol>();
private Dictionary<int, DefSymbol> mByWriteAddress = new Dictionary<int, DefSymbol>();
public MaskGroup(DefSymbol.MultiAddressMask multiMask) {
Debug.Assert(multiMask != null);
mMultiMask = multiMask;
}
public void Add(DefSymbol defSym) {
bool doRead = true;
bool doWrite = true;
if (defSym.Direction == DefSymbol.DirectionFlags.Read) {
doWrite = false;
} else if (defSym.Direction == DefSymbol.DirectionFlags.Write) {
doRead = false;
}
for (int i = 0; i < defSym.DataDescriptor.Length; i++) {
// See if there's already something here. If we reach the end of the
// bank, wrap around.
int addr = (defSym.Value & 0xff0000) + ((defSym.Value + i) & 0xffff);
addr &= mMultiMask.AddressMask; // use minimal address
DefSymbol curSym;
if (doRead) {
mByReadAddress.TryGetValue(addr, out curSym);
mByReadAddress[addr] = (curSym == null) ? defSym :
(DefSymbol)HighestPriority(defSym, curSym);
}
if (doWrite) {
mByWriteAddress.TryGetValue(addr, out curSym);
mByWriteAddress[addr] = (curSym == null) ? defSym :
(DefSymbol)HighestPriority(defSym, curSym);
}
}
}
public DefSymbol Find(int addr, bool tryRead, bool tryWrite) {
addr &= mMultiMask.AddressMask;
DefSymbol defSym;
if (tryRead && mByReadAddress.TryGetValue(addr, out defSym)) {
return defSym;
}
if (tryWrite && mByWriteAddress.TryGetValue(addr, out defSym)) {
return defSym;
}
return null;
}
}
/// <summary>
/// Collection of MaskGroups.
/// </summary>
private Dictionary<DefSymbol.MultiAddressMask, MaskGroup> mMaskGroups =
new Dictionary<DefSymbol.MultiAddressMask, MaskGroup>();
/// <summary>
/// Constructor.
/// </summary>
public SymbolTable() { }
// IEnumerable
@ -129,8 +267,8 @@ namespace SourceGen {
/// </summary>
public void Clear() {
mSymbols.Clear();
mSymbolsByAddress.Clear();
//ChangeSerial++;
mSymbolsByReadAddress.Clear();
mSymbolsByWriteAddress.Clear();
}
/// <summary>
@ -149,7 +287,6 @@ namespace SourceGen {
// and the by-value add won't happen.
mSymbols.Add(sym.Label, sym);
AddAddressTableEntry(sym);
//ChangeSerial++;
}
/// <summary>
@ -169,13 +306,11 @@ namespace SourceGen {
} else {
AddAddressTableEntry(value);
}
//ChangeSerial++;
}
}
/// <summary>
/// Gets the value associated with the key.
/// Gets the value associated with the label.
/// </summary>
/// <param name="key">Label to look up.</param>
/// <param name="sym">Symbol, or null if not found.</param>
@ -185,7 +320,7 @@ namespace SourceGen {
}
/// <summary>
/// Gets the value associated with the key, unless it's a variable.
/// Gets the value associated with the label, 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>
@ -205,7 +340,6 @@ namespace SourceGen {
public void Remove(Symbol sym) {
mSymbols.Remove(sym.Label);
RemoveAddressTableEntry(sym);
//ChangeSerial++;
}
/// <summary>
@ -221,25 +355,53 @@ namespace SourceGen {
if (sym.SymbolSource == Symbol.Source.Variable) {
return;
}
if (sym is DefSymbol && ((DefSymbol)sym).MultiMask != null) {
AddMultiMaskEntry((DefSymbol)sym);
return;
}
bool doRead = true;
bool doWrite = true;
int width = 1;
if (sym is DefSymbol) {
width = ((DefSymbol)sym).DataDescriptor.Length;
DefSymbol defSym = (DefSymbol)sym;
width = defSym.DataDescriptor.Length;
if (defSym.Direction == DefSymbol.DirectionFlags.Read) {
doWrite = false;
} else if (defSym.Direction == DefSymbol.DirectionFlags.Write) {
doRead = false;
}
}
// we could restore some older behavior by giving user labels a width of 3, but
// we'd have to make sure that they didn't win for addresses outside the file
for (int i = 0; i < width; i++) {
// See if there's already something here. If we reach the end of the
// bank, wrap around.
int addr = (sym.Value & 0xff0000) + ((sym.Value + i) & 0xffff);
mSymbolsByAddress.TryGetValue(addr, out Symbol curSym);
mSymbolsByAddress[addr] = (curSym == null) ? sym :
HighestPriority(sym, curSym);
Symbol curSym;
if (doRead) {
mSymbolsByReadAddress.TryGetValue(addr, out curSym);
mSymbolsByReadAddress[addr] = (curSym == null) ? sym :
HighestPriority(sym, curSym);
}
if (doWrite) {
mSymbolsByWriteAddress.TryGetValue(addr, out curSym);
mSymbolsByWriteAddress[addr] = (curSym == null) ? sym :
HighestPriority(sym, curSym);
}
}
}
private Symbol HighestPriority(Symbol sym1, Symbol sym2) {
private void AddMultiMaskEntry(DefSymbol defSym) {
DefSymbol.MultiAddressMask multiMask = defSym.MultiMask;
mMaskGroups.TryGetValue(multiMask, out MaskGroup group);
if (group == null) {
group = new MaskGroup(multiMask);
mMaskGroups.Add(multiMask, group);
}
group.Add(defSym);
}
private static Symbol HighestPriority(Symbol sym1, Symbol sym2) {
// First determinant is symbol source. User labels have highest priority, then
// project symbols, then platform symbols, then auto labels.
if ((int)sym1.SymbolSource < (int)sym2.SymbolSource) {
@ -312,11 +474,13 @@ namespace SourceGen {
//
// Note we do this *a lot* when the fancier auto labels are enabled, because we
// generate plain labels and then replace them with annotated labels.
mSymbolsByAddress.Remove(sym.Value);
mSymbolsByReadAddress.Remove(sym.Value);
mSymbolsByWriteAddress.Remove(sym.Value);
return;
}
// Removing a project/platform symbol requires re-evaluating the by-address table.
Debug.WriteLine("SymbolTable: regenerating table after removal of " + sym);
RegenerateAddressTable();
}
@ -326,12 +490,13 @@ namespace SourceGen {
/// <remarks>
/// This is a little painful, but if a symbol gets removed we don't have a way to
/// restore lower-priority items. If this becomes a performance issue we can create
/// an ordered list of symbols at each address, but with a few hundred symbols this
/// an ordered list of symbols at each address, but even with a few hundred symbols this
/// should take very little time.
/// </remarks>
private void RegenerateAddressTable() {
Debug.WriteLine("SymbolTable: regenerating address table");
mSymbolsByAddress.Clear();
//Debug.WriteLine("SymbolTable: regenerating address table");
mSymbolsByReadAddress.Clear();
mSymbolsByWriteAddress.Clear();
foreach (KeyValuePair<string, Symbol> kvp in mSymbols) {
AddAddressTableEntry(kvp.Value);
@ -344,9 +509,52 @@ namespace SourceGen {
/// </summary>
/// <param name="addr">Address to find.</param>
/// <returns>First matching symbol found, or null if nothing matched.</returns>
public Symbol FindNonVariableByAddress(int addr) {
mSymbolsByAddress.TryGetValue(addr, out Symbol sym);
public Symbol FindNonVariableByAddress(int addr, OpDef.MemoryEffect effect) {
bool tryRead, tryWrite;
if (effect == OpDef.MemoryEffect.Read) {
tryRead = true;
tryWrite = false;
} else if (effect == OpDef.MemoryEffect.Write) {
tryRead = false;
tryWrite = true;
} else if (effect == OpDef.MemoryEffect.ReadModifyWrite ||
effect == OpDef.MemoryEffect.None) {
tryRead = tryWrite = true;
} else {
Debug.Assert(false);
return null;
}
Symbol sym = null;
if (tryRead) {
mSymbolsByReadAddress.TryGetValue(addr, out sym);
}
if (tryWrite && sym == null) {
mSymbolsByWriteAddress.TryGetValue(addr, out sym);
}
if (sym == null) {
// Nothing matched, check the match groups.
foreach (KeyValuePair<DefSymbol.MultiAddressMask, MaskGroup> kvp in mMaskGroups) {
DefSymbol.MultiAddressMask multiMask = kvp.Key;
if ((addr & multiMask.CompareMask) == multiMask.CompareValue) {
MaskGroup group = kvp.Value;
DefSymbol defSym = kvp.Value.Find(addr, tryRead, tryWrite);
if (defSym != null) {
sym = defSym;
break;
}
}
}
}
return sym;
}
public override string ToString() {
return "SymbolTable: " + mSymbols.Count + " by label, " +
mSymbolsByReadAddress.Count + " by addr(r), " +
mSymbolsByWriteAddress.Count + " by addr(w), " +
mMaskGroups.Count + " mask groups";
}
}
}