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:
parent
9c3422623d
commit
4d8ee3fd07
@ -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;
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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 & CompareMask) == CompareValue &&
|
||||
/// (addr & AddressMask) == (Value & 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>
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user