ORG rework, part 6

Added support for non-addressable regions, which are useful for things
like file headers stripped out by the system loader, or chunks that
get loaded into non-addressable graphics RAM.  Regions are specified
with the "NA" address value.  The code list displays the address field
greyed out, starting from zero (which is kind of handy if you want to
know the relative offset within the region).

Putting labels in non-addressable regions doesn't make sense, but
symbol resolution is complicated enough that we really only have two
options: ignore the labels entirely, or allow them but warn of their
presence.  The problem isn't so much the label, which you could
legitimately want to access from an extension script, but rather the
references to them from code or data.  So we keep the label and add a
warning to the Messages list when we see a reference.

Moved NON_ADDR constants to Address class.  AddressMap now has a copy.
This is awkward because Asm65 and CommonUtil don't share.

Updated the asm code generators to understand NON_ADDR, and reworked
the API so that Merlin and cc65 output is correct for nested regions.

Address region changes are now noted in the anattribs array, which
makes certain operations faster than checking the address map.  It
also fixes a failure to recognize mid-instruction region changes in
the code analyzer.

Tweaked handling of synthetic regions, which are non-addressable areas
generated by the linear address map traversal to fill in any "holes".
The address region editor now treats attempts to edit them as
creation of a new region.
This commit is contained in:
Andy McFadden 2021-09-30 18:07:21 -07:00
parent 2fed19ac47
commit e6c5c7f8df
24 changed files with 359 additions and 103 deletions

View File

@ -21,6 +21,25 @@ namespace Asm65 {
/// Memory address primitives.
/// </summary>
public static class Address {
/// <summary>
/// Address value to use for non-addressable regions of the file, such as file headers
/// stripped by the system loader or chunks loaded into non-addressable memory.
/// </summary>
/// <remarks>
/// If you change this value, also update the copy in CommonUtil.AddressMap.
/// </remarks>
public const int NON_ADDR = -1025;
/// <summary>
/// Human-readable string that represents a non-addressable location.
/// </summary>
/// <remarks>
/// This is a bit deep to bury a human-readable string, but it's useful to have the
/// value in one place.
/// </remarks>
public const string NON_ADDR_STR = "NA";
/// <summary>
/// Converts a 16- or 24-bit address to a string.
/// </summary>
@ -41,6 +60,9 @@ namespace Asm65 {
///
/// The following all evaluate to the same thing: 1000, $1000, 0x1000, 00/1000.
/// </summary>
/// <remarks>
/// This doesn't handle "NA", because most of the time we want to parse an actual address.
/// </remarks>
/// <param name="addrStr">String to validate.</param>
/// <param name="max">Maximum valid address value.</param>
/// <param name="addr">Integer form.</param>

View File

@ -78,10 +78,13 @@ namespace CommonUtil {
public const int FLOATING_LEN = -1024;
/// <summary>
/// Address value to use for non-addressable regions of the file, such as file headers
/// stripped by the system loader or chunks loaded into non-addressable memory.
/// Value for non-addressable locations.
/// </summary>
public const int NON_ADDR = -1025;
/// <remarks>
/// MUST match Asm65.Address.NON_ADDR. We can't use the constant directly here because
/// the classes are in different packages that aren't dependent upon each other.
/// </remarks>
private const int NON_ADDR = -1025;
#region Structural

View File

@ -49,6 +49,8 @@ namespace SourceGen {
Changed = 1 << 17, // set/cleared as the analyzer works
ATagged = 1 << 18, // was this byte affected by an analyzer tag?
AddrRegionChange = 1 << 19, // is this byte in a different address region from prev?
NonAddressable = 1 << 20, // is this byte in a non-addressable range?
}
// Flags indicating what type of data is here. Use the following Is* properties
@ -231,6 +233,30 @@ namespace SourceGen {
}
}
}
public bool IsAddrRegionChange {
get {
return (mAttribFlags & AttribFlags.AddrRegionChange) != 0;
}
set {
if (value) {
mAttribFlags |= AttribFlags.AddrRegionChange;
} else {
mAttribFlags &= ~AttribFlags.AddrRegionChange;
}
}
}
public bool IsNonAddressable {
get {
return (mAttribFlags & AttribFlags.NonAddressable) != 0;
}
set {
if (value) {
mAttribFlags |= AttribFlags.NonAddressable;
} else {
mAttribFlags &= ~AttribFlags.NonAddressable;
}
}
}
public bool IsDataStart {
get {

View File

@ -208,7 +208,7 @@ namespace SourceGen.AsmGen {
// boundary and cause a failure. In that case we want to set the initial address
// to zero and "stream" the rest.
int firstAddr = project.AddrMap.OffsetToAddress(0);
if (firstAddr == AddressMap.NON_ADDR) {
if (firstAddr == Address.NON_ADDR) {
firstAddr = 0;
}
if (firstAddr + project.FileDataLength > 65536) {
@ -289,9 +289,11 @@ namespace SourceGen.AsmGen {
int firstAddr = Project.AddrMap.OffsetToAddress(0);
AddressMap.AddressRegion fakeRegion = new AddressMap.AddressRegion(0,
Project.FileData.Length, firstAddr);
OutputArDirective(fakeRegion, true);
OutputArDirective(new AddressMap.AddressChange(true,
0, firstAddr, fakeRegion, true));
OutputDenseHex(0, Project.FileData.Length, string.Empty, string.Empty);
OutputArDirective(fakeRegion, false);
OutputArDirective(new AddressMap.AddressChange(false,
0, firstAddr, fakeRegion, true));
} else {
GenCommon.Generate(this, sw, worker);
}
@ -568,10 +570,15 @@ namespace SourceGen.AsmGen {
}
// IGenerator
public void OutputArDirective(AddressMap.AddressRegion addrEntry, bool isStart) {
public void OutputArDirective(CommonUtil.AddressMap.AddressChange change) {
// This is similar in operation to the AsmTass64 implementation. See comments there.
Debug.Assert(mPcDepth >= 0);
if (isStart) {
int nextAddress = change.Address;
if (nextAddress == Address.NON_ADDR) {
// Start non-addressable regions at zero to ensure they don't overflow bank.
nextAddress = 0;
}
if (change.IsStart) {
if (mPcDepth == 0 && mFirstIsOpen) {
mPcDepth++;
@ -579,7 +586,7 @@ namespace SourceGen.AsmGen {
// mode, just set "*=". If we're in "streaming" mode, we set "*=" to zero
// and then use a pseudo-PC.
if (mOutputMode == OutputMode.Loadable) {
OutputLine("*", "=", SourceFormatter.FormatHexValue(addrEntry.Address, 4),
OutputLine("*", "=", SourceFormatter.FormatHexValue(nextAddress, 4),
string.Empty);
return;
} else {
@ -589,7 +596,7 @@ namespace SourceGen.AsmGen {
}
OutputLine(string.Empty,
SourceFormatter.FormatPseudoOp(sDataOpNames.ArStartDirective),
SourceFormatter.FormatHexValue(addrEntry.Address, 4) + " {",
SourceFormatter.FormatHexValue(nextAddress, 4) + " {",
string.Empty);
mPcDepth++;
} else {
@ -607,6 +614,9 @@ namespace SourceGen.AsmGen {
}
}
// IGenerator
public void FlushArDirectives() { }
// IGenerator
public void OutputRegWidthDirective(int offset, int prevM, int prevX, int newM, int newX) {
if (prevM != newM) {

View File

@ -91,6 +91,11 @@ namespace SourceGen.AsmGen {
/// </summary>
private bool mHighAsciiMacroOutput;
/// <summary>
/// Address of next byte of output.
/// </summary>
private int mNextAddress = -1;
/// <summary>
/// Holds detected version of configured assembler.
/// </summary>
@ -534,13 +539,21 @@ namespace SourceGen.AsmGen {
}
// IGenerator
public void OutputArDirective(AddressMap.AddressRegion addrEntry, bool isStart) {
if (!isStart) {
return;
public void OutputArDirective(CommonUtil.AddressMap.AddressChange change) {
int nextAddress = change.Address;
if (nextAddress == Address.NON_ADDR) {
// Start non-addressable regions at zero to ensure they don't overflow bank.
nextAddress = 0;
}
mNextAddress = nextAddress;
}
OutputLine(string.Empty, SourceFormatter.FormatPseudoOp(sDataOpNames.ArStartDirective),
SourceFormatter.FormatHexValue(addrEntry.Address, 4), string.Empty);
// IGenerator
public void FlushArDirectives() {
OutputLine(string.Empty,
SourceFormatter.FormatPseudoOp(sDataOpNames.ArStartDirective),
SourceFormatter.FormatHexValue(mNextAddress, 4),
string.Empty);
}
// IGenerator

View File

@ -86,6 +86,11 @@ namespace SourceGen.AsmGen {
/// </summary>
private StreamWriter mOutStream;
/// <summary>
/// Address of next byte of output.
/// </summary>
private int mNextAddress = -1;
/// <summary>
/// Holds detected version of configured assembler.
/// </summary>
@ -485,13 +490,21 @@ namespace SourceGen.AsmGen {
}
// IGenerator
public void OutputArDirective(AddressMap.AddressRegion addrEntry, bool isStart) {
if (isStart) {
OutputLine(string.Empty,
SourceFormatter.FormatPseudoOp(sDataOpNames.ArStartDirective),
SourceFormatter.FormatHexValue(addrEntry.Address, 4),
string.Empty);
public void OutputArDirective(CommonUtil.AddressMap.AddressChange change) {
int nextAddress = change.Address;
if (nextAddress == Address.NON_ADDR) {
// Start non-addressable regions at zero to ensure they don't overflow bank.
nextAddress = 0;
}
mNextAddress = nextAddress;
}
// IGenerator
public void FlushArDirectives() {
OutputLine(string.Empty,
SourceFormatter.FormatPseudoOp(sDataOpNames.ArStartDirective),
SourceFormatter.FormatHexValue(mNextAddress, 4),
string.Empty);
}
// IGenerator

View File

@ -653,7 +653,7 @@ namespace SourceGen.AsmGen {
}
// IGenerator
public void OutputArDirective(AddressMap.AddressRegion addrEntry, bool isStart) {
public void OutputArDirective(CommonUtil.AddressMap.AddressChange change) {
// 64tass separates the "compile offset", which determines where the output fits
// into the generated binary, and "program counter", which determines the code
// the assembler generates. Since we need to explicitly specify every byte in
@ -675,7 +675,12 @@ namespace SourceGen.AsmGen {
// inside that. So we treat the first region specially, whether or not it wraps
// the rest of the file.
Debug.Assert(mPcDepth >= 0);
if (isStart) {
int nextAddress = change.Address;
if (nextAddress == Address.NON_ADDR) {
// Start non-addressable regions at zero to ensure they don't overflow bank.
nextAddress = 0;
}
if (change.IsStart) {
if (mPcDepth == 0 && mFirstIsOpen) {
mPcDepth++;
@ -684,7 +689,7 @@ namespace SourceGen.AsmGen {
// and then use a pseudo-PC.
if (mOutputMode == OutputMode.Loadable) {
OutputLine("*", "=",
SourceFormatter.FormatHexValue(addrEntry.Address, 4), string.Empty);
SourceFormatter.FormatHexValue(nextAddress, 4), string.Empty);
return;
} else {
// Set the real PC to address zero to ensure we get a full 64KB. The
@ -694,7 +699,7 @@ namespace SourceGen.AsmGen {
}
OutputLine(string.Empty,
SourceFormatter.FormatPseudoOp(sDataOpNames.ArStartDirective),
SourceFormatter.FormatHexValue(addrEntry.Address, 4),
SourceFormatter.FormatHexValue(nextAddress, 4),
string.Empty);
mPcDepth++;
} else {
@ -711,6 +716,9 @@ namespace SourceGen.AsmGen {
}
}
// IGenerator
public void FlushArDirectives() { }
// IGenerator
public void OutputRegWidthDirective(int offset, int prevM, int prevX, int newM, int newX) {
if (prevM != newM) {

View File

@ -59,6 +59,7 @@ namespace SourceGen.AsmGen {
}
}
bool arDirectPending = false;
while (offset < proj.FileData.Length) {
Anattrib attr = proj.GetAnattrib(offset);
@ -80,7 +81,8 @@ namespace SourceGen.AsmGen {
AddressMap.AddressChange change = addrIter.Current;
while (change != null && change.Offset == offset) {
if (change.IsStart) {
gen.OutputArDirective(change.Region, change.IsStart);
gen.OutputArDirective(change);
arDirectPending = true;
addrIter.MoveNext();
change = addrIter.Current;
} else {
@ -88,6 +90,11 @@ namespace SourceGen.AsmGen {
}
}
if (arDirectPending) {
gen.FlushArDirectives();
arDirectPending = false;
}
List<DefSymbol> lvars = lvLookup.GetVariablesDefinedAtOffset(offset);
if (lvars != null) {
// table defined here
@ -142,7 +149,8 @@ namespace SourceGen.AsmGen {
// loop iteration.
while (change != null && change.Offset + 1 == offset) {
if (!change.IsStart) {
gen.OutputArDirective(change.Region, change.IsStart);
gen.OutputArDirective(change);
arDirectPending = true;
addrIter.MoveNext();
change = addrIter.Current;
} else {

View File

@ -168,9 +168,15 @@ namespace SourceGen.AsmGen {
/// <summary>
/// Outputs an address region directive.
/// </summary>
/// <param name="region">Address region object.</param>
/// <param name="isStart">True if this is the start of a region.</param>
void OutputArDirective(CommonUtil.AddressMap.AddressRegion region, bool isStart);
/// <param name="change">Address map change record.</param>
void OutputArDirective(CommonUtil.AddressMap.AddressChange change);
/// <summary>
/// Signals the code generator to flush any pending address region directives. Useful
/// for generation of non-hierarchical directives.
/// </summary>
void FlushArDirectives();
/// <summary>
/// Notify the assembler of a change in register width.

View File

@ -358,20 +358,43 @@ namespace SourceGen {
IEnumerator<AddressMap.AddressChange> addrIter = mAddrMap.AddressChangeIterator;
addrIter.MoveNext();
int addr = 0;
bool nonAddr = false;
bool addrChange = false;
for (int offset = 0; offset < mAnattribs.Length; offset++) {
// Process all change events at this offset.
AddressMap.AddressChange change = addrIter.Current;
// Process all start events at this offset. The new address takes effect
// immediately.
while (change != null && change.IsStart && change.Offset == offset) {
addr = change.Address;
if (addr == Address.NON_ADDR) {
addr = 0;
nonAddr = true;
} else {
nonAddr = false;
}
addrChange = true;
addrIter.MoveNext();
change = addrIter.Current;
}
mAnattribs[offset].Address = addr++;
mAnattribs[offset].IsAddrRegionChange = addrChange;
mAnattribs[offset].IsNonAddressable = nonAddr;
addrChange = false;
// Process all end events at this offset. The new address and "address
// region change" flag take effect on the *following* offset.
while (change != null && !change.IsStart && change.Offset == offset) {
addr = change.Address;
if (addr == Address.NON_ADDR) {
addr = 0;
nonAddr = true;
} else {
nonAddr = false;
}
addrChange = true;
addrIter.MoveNext();
change = addrIter.Current;
}
@ -522,6 +545,10 @@ namespace SourceGen {
// the inline data "wins", and we stop here.
LogW(offset, "Code ran into inline data section");
return;
} else if (mAnattribs[offset].IsNonAddressable) {
mAnattribs[offset].IsInstruction = false;
LogW(offset, "Code ran into non-addressable area");
return;
}
// Identify the instruction, and see if it runs off the end of the file.
@ -541,14 +568,18 @@ namespace SourceGen {
mAnattribs[offset].IsData = true;
return;
}
if (mAnattribs[offset + instrLen -1].Address !=
mAnattribs[offset].Address + instrLen - 1) {
// Address change happened mid-instruction. Mark it as data.
LogW(offset, "Detected address change mid-instruction");
mAnattribs[offset].IsInstructionStart = false;
mAnattribs[offset].IsInstruction = false;
mAnattribs[offset].IsData = true;
return;
// Check for mid-instruction address region changes. An address change on the
// first byte is fine.
for (int i = offset + 1; i < offset + instrLen; i++) {
if (mAnattribs[i].IsAddrRegionChange) {
// Found a region start and/or end. Mark this offset as data and return.
LogW(offset, "Detected address change mid-instruction");
mAnattribs[offset].IsInstructionStart = false;
mAnattribs[offset].IsInstruction = false;
mAnattribs[offset].IsData = true;
return;
}
}
// Instruction not defined for this CPU. Treat as data.
@ -782,10 +813,10 @@ namespace SourceGen {
break;
}
// Make sure we don't "continue" across an ORG.
// NOTE: it's possible to do some crazy things with multiple ORGs that will
// cause us to misinterpret things, but I don't think that matters. What's
// important is that the code analyzer doesn't drive into a data area.
// Make sure we don't "continue" across an address change. This is different
// from the earlier mid-instruction check in that we don't actually care if
// there's a region change between instructions so long as the next address
// has the expected value.
int expectedAddr = mAnattribs[offset].Address + mAnattribs[offset].Length +
inlineDataGapLen;
if (mAnattribs[nextOffset].Address != expectedAddr) {
@ -1118,6 +1149,7 @@ namespace SourceGen {
" label='" + label + "'; file length is" + mFileData.Length);
}
// NOTE: might be faster to check Anattrib IsAddrRegionChange for short regions
if (!mAddrMap.IsRangeUnbroken(offset, length)) {
LogW(offset, "SIDF: format crosses address map boundary (len=" + length + ")");
return false;
@ -1383,12 +1415,15 @@ namespace SourceGen {
}
// Run through file, updating instructions as needed.
// TODO(org): this is wrong if the file starts with a non-addr region; can walk
// through anattribs and set to mAnattribs[].Address of first instruction; maybe
// just init to DbrValue.UNKNOWN and set it inside the loop on first relevant instr
int firstAddr = mAddrMap.OffsetToAddress(0);
short curVal = (byte)(firstAddr >> 16); // start with B=K
short curVal = DbrValue.UNKNOWN;
for (int offset = 0; offset < mAnattribs.Length; offset++) {
if (mAnattribs[offset].IsNonAddressable) {
continue;
}
if (curVal == DbrValue.UNKNOWN) {
// On first encounter with addressable memory, init curVal so B=K.
curVal = (byte)(mAddrMap.OffsetToAddress(offset) >> 16);
}
if (bval[offset] != DbrValue.UNKNOWN) {
curVal = bval[offset];
}

View File

@ -681,8 +681,8 @@ namespace SourceGen {
offset++;
// Check to see if we just crossed an address change.
if (offset < mAnattribs.Length &&
!mProject.AddrMap.IsRangeUnbroken(offset - 1, 2)) {
if (offset < mAnattribs.Length && mAnattribs[offset].IsAddrRegionChange
/*!mProject.AddrMap.IsRangeUnbroken(offset - 1, 2)*/) {
// Must be an ORG here. End region and scan.
AnalyzeRange(startOffset, offset - 1);
startOffset = -1;

View File

@ -808,7 +808,7 @@ namespace SourceGen {
/// <param name="reanalysisTimer">Task timestamp collection object.</param>
public void Analyze(UndoableChange.ReanalysisScope reanalysisRequired,
CommonUtil.DebugLog debugLog, TaskTimer reanalysisTimer) {
// This method doesn't report failures. It succeeds to the best of its ability,
// This method doesn't return an error code. It succeeds to the best of its ability,
// and handles problems by discarding bad data. The overall philosophy is that
// the program will never generate bad data, and any bad project file contents
// (possibly introduced by hand-editing) are identified at load time, called out
@ -1530,7 +1530,7 @@ namespace SourceGen {
// Create a mapping from label (which must be unique) to file offset. This
// is different from UserLabels (which only has user-created labels, and is
// sorted by offset) and SymbolTable (which has constants and platform symbols,
// and uses the address as value rather than the offset).
// and uses the address as the value rather than the offset).
SortedList<string, int> labelList = new SortedList<string, int>(mFileData.Length,
Asm65.Label.LABEL_COMPARER);
for (int offset = 0; offset < mAnattribs.Length; offset++) {
@ -1596,6 +1596,15 @@ namespace SourceGen {
// Is this a reference to a label?
if (labelList.TryGetValue(dfd.SymbolRef.Label, out int symOffset)) {
if (mAnattribs[symOffset].IsNonAddressable) {
Messages.Add(new MessageList.MessageEntry(
MessageList.MessageEntry.SeverityLevel.Warning,
offset,
MessageList.MessageEntry.MessageType.NonAddrLabelRef,
dfd.SymbolRef.Label,
MessageList.MessageEntry.ProblemResolution.None));
}
// Compute adjustment.
int adj = 0;
if (operandOffset >= 0) {

View File

@ -345,7 +345,7 @@ namespace SourceGen {
}
/// <summary>
/// List elements. Instances are immutable.
/// List elements. Instances are immutable except for ListIndex.
/// </summary>
public class FormattedParts {
public string Offset { get; private set; }
@ -365,14 +365,32 @@ namespace SourceGen {
public bool IsVisualizationSet { get; private set; }
public Visualization[] VisualizationSet { get; private set; }
// Set to true if we want to highlight the address and label fields.
// Set to true if we want to highlight the address and label fields. This is
// examined by a data trigger in CodeListItemStyle.xaml.
public bool HasAddrLabelHighlight { get; private set; }
// Set to true if the Flags field has been modified.
public bool HasModifiedFlags { get; private set; }
public bool HasModifiedFlags {
get { return (mPartFlags & PartFlags.HasModifiedFlags) != 0; }
}
// Set to true if the address here is actually non-addressable.
public bool IsNonAddressable {
get { return (mPartFlags & PartFlags.IsNonAddressable) != 0; }
}
// List index, filled in on demand. If the list is regenerated we want to
// renumber elements without having to recreate them, so this field is mutable.
public int ListIndex { get; set; } = -1;
[Flags]
public enum PartFlags {
None = 0,
HasModifiedFlags = 1, // Flags field is non-default
IsNonAddressable = 1 << 1, // this is a non-addressable region
}
private PartFlags mPartFlags;
private static Color NoColor = CommonWPF.Helper.ZeroColor;
@ -388,11 +406,10 @@ namespace SourceGen {
private static FormattedParts Clone(FormattedParts orig) {
FormattedParts newParts = FormattedParts.Create(orig.Offset, orig.Addr,
orig.Bytes, orig.Flags, orig.Attr, orig.Label, orig.Opcode, orig.Operand,
orig.Comment);
orig.Comment, orig.mPartFlags);
newParts.IsLongComment = orig.IsLongComment;
newParts.HasAddrLabelHighlight = orig.HasAddrLabelHighlight;
newParts.HasModifiedFlags = orig.HasModifiedFlags;
newParts.ListIndex = orig.ListIndex;
return newParts;
@ -400,7 +417,7 @@ namespace SourceGen {
public static FormattedParts Create(string offset, string addr, string bytes,
string flags, string attr, string label, string opcode, string operand,
string comment) {
string comment, PartFlags pflags) {
FormattedParts parts = new FormattedParts();
parts.Offset = offset;
parts.Addr = addr;
@ -412,6 +429,7 @@ namespace SourceGen {
parts.Operand = operand;
parts.Comment = comment;
parts.IsLongComment = false;
parts.mPartFlags = pflags;
return parts;
}
@ -489,12 +507,6 @@ namespace SourceGen {
return newParts;
}
public static FormattedParts SetFlagsModified(FormattedParts orig) {
FormattedParts newParts = Clone(orig);
newParts.HasModifiedFlags = true;
return newParts;
}
public override string ToString() {
return "[Parts: index=" + ListIndex + " off=" + Offset + "]";
}

View File

@ -554,7 +554,8 @@ namespace SourceGen {
// should have been done already
default:
Debug.Assert(false);
parts = FormattedParts.Create("x", "x", "x", "x", "x", "x", "x", "x", "x");
parts = FormattedParts.Create("x", "x", "x", "x", "x", "x", "x", "x", "x",
FormattedParts.PartFlags.None);
break;
}
line.Parts = parts;
@ -1083,8 +1084,8 @@ namespace SourceGen {
// TODO(org): pre-label (address / label only, logically part of ORG)
Line newLine = new Line(offset, 0, Line.Type.ArStartDirective, arSubLine++);
string addrStr;
if (region.Address == AddressMap.NON_ADDR) {
addrStr = "NA";
if (region.Address == Address.NON_ADDR) {
addrStr = Address.NON_ADDR_STR;
} else {
addrStr = mFormatter.FormatHexValue(region.Address, 4);
}
@ -1265,7 +1266,18 @@ namespace SourceGen {
while (addrIter.Current != null && addrIter.Current.Offset < offset) {
AddressMap.AddressChange change = addrIter.Current;
// Range starts/ends shouldn't be embedded in something.
Debug.Assert(change.Offset == offset - 1);
if (change.Offset != offset - 1) {
Debug.Assert(false, "Bad offset: change.Offset=+" +
change.Offset.ToString("x6") + " offset-1=+" +
(offset - 1).ToString("x6"));
}
// The .arend can only appear on the same offset as .arstart in a single-byte
// region, and we can't have more than one of those at the same offset.
// If this is not a single-byte region, we need to reset the sub-line count.
if (change.Region.Length != 1) {
arSubLine = 0;
}
if (change.Region.Offset == 0 && hasPrgHeader) {
// Suppress the .arend at offset +000001.
@ -1277,11 +1289,11 @@ namespace SourceGen {
if (!change.IsStart) {
// Show the start address to make it easier to pair them visually.
string addrStr;
if (change.Region.Address == AddressMap.NON_ADDR) {
addrStr = "(NA)";
if (change.Region.Address == Address.NON_ADDR) {
addrStr = "\u2191 " + Address.NON_ADDR_STR;
} else {
addrStr = "(" + mFormatter.FormatHexValue(change.Region.Address, 4) +
")";
addrStr = "\u2191 " +
mFormatter.FormatHexValue(change.Region.Address, 4);
}
// Associate with offset of previous byte.
@ -1369,7 +1381,6 @@ namespace SourceGen {
opcodeStr = opcodeStr + " \u25bc"; // BLACK DOWN-POINTING TRIANGLE
}
string formattedOperand = null;
int operandLen = instrLen - 1;
PseudoOp.FormatNumericOpFlags opFlags = PseudoOp.FormatNumericOpFlags.None;
@ -1401,6 +1412,8 @@ namespace SourceGen {
operandForSymbol = attr.OperandAddress;
}
string formattedOperand;
// Check Length to watch for bogus descriptors. ApplyFormatDescriptors() should
// have discarded anything appropriate, so we might be able to eliminate this test.
if (attr.DataDescriptor != null && attr.Length == attr.DataDescriptor.Length) {
@ -1472,11 +1485,15 @@ namespace SourceGen {
}
string commentStr = mFormatter.FormatEolComment(eolComment);
FormattedParts parts = FormattedParts.Create(offsetStr, addrStr, bytesStr,
flagsStr, attrStr, labelStr, opcodeStr, operandStr, commentStr);
FormattedParts.PartFlags pflags = FormattedParts.PartFlags.None;
if (mProject.StatusFlagOverrides[offset] != StatusFlags.DefaultValue) {
parts = FormattedParts.SetFlagsModified(parts);
pflags |= FormattedParts.PartFlags.HasModifiedFlags;
}
if (attr.IsNonAddressable) {
pflags |= FormattedParts.PartFlags.IsNonAddressable;
}
FormattedParts parts = FormattedParts.Create(offsetStr, addrStr, bytesStr,
flagsStr, attrStr, labelStr, opcodeStr, operandStr, commentStr, pflags);
return parts;
}
@ -1527,8 +1544,12 @@ namespace SourceGen {
}
}
FormattedParts.PartFlags pflags = FormattedParts.PartFlags.None;
if (attr.IsNonAddressable) {
pflags |= FormattedParts.PartFlags.IsNonAddressable;
}
FormattedParts parts = FormattedParts.Create(offsetStr, addrStr, bytesStr,
flagsStr, attrStr, labelStr, opcodeStr, operandStr, commentStr);
flagsStr, attrStr, labelStr, opcodeStr, operandStr, commentStr, pflags);
return parts;
}
@ -1604,7 +1625,7 @@ namespace SourceGen {
return lvars[tableIndex];
}
public AddressMap.AddressRegion GetAddrRegionFromLine(Line line) {
public AddressMap.AddressRegion GetAddrRegionFromLine(Line line, out bool isSynth) {
// A given offset can have one or more .arstart lines and one or more .arend lines.
// You can't have an end followed by a start, because that would mean the regions
// overlap. If there's both start and end present, we have a 1-byte region.
@ -1622,11 +1643,13 @@ namespace SourceGen {
while (addrIter.Current != null && addrIter.Current.Offset == offset) {
AddressMap.AddressChange change = addrIter.Current;
if (count == 0) {
isSynth = change.IsSynthetic;
return change.Region;
}
if (change.IsStart && !string.IsNullOrEmpty(change.Region.PreLabel)) {
count--;
if (count == 0) {
isSynth = change.IsSynthetic;
return change.Region;
}
}
@ -1635,6 +1658,7 @@ namespace SourceGen {
addrIter.MoveNext();
}
isSynth = false;
return null;
}
@ -1673,8 +1697,13 @@ namespace SourceGen {
operandStr = operands[subLineIndex];
FormattedParts.PartFlags pflags = FormattedParts.PartFlags.None;
if (attr.IsNonAddressable) {
pflags |= FormattedParts.PartFlags.IsNonAddressable;
}
FormattedParts parts = FormattedParts.Create(offsetStr, addrStr, bytesStr,
/*flags*/string.Empty, attrStr, labelStr, opcodeStr, operandStr, commentStr);
/*flags*/string.Empty, attrStr, labelStr, opcodeStr, operandStr, commentStr,
pflags);
partsArray[subLineIndex] = parts;
}

View File

@ -1660,7 +1660,8 @@ namespace SourceGen {
if (line.IsAddressRangeDirective) {
// TODO(someday): make this jump to the actual directive rather than nearby code
AddressMap.AddressRegion region = CodeLineList.GetAddrRegionFromLine(line);
AddressMap.AddressRegion region = CodeLineList.GetAddrRegionFromLine(line,
out bool unused);
if (region == null) {
Debug.Assert(false);
return false;
@ -1843,6 +1844,15 @@ namespace SourceGen {
int nextOffset = lastOffset + CodeLineList[lastIndex].OffsetSpan;
AddressMap addrMap = mProject.AddrMap;
// The offset of a .arend directive is the last byte in the address region. It
// has a span length of zero because it's a directive, so if it's selected as
// the last offset then our nextOffset calculation will be off by one. (This would
// be avoided by using an exclusive end offset, but that causes other problems.)
// Work around it here.
if (CodeLineList[lastIndex].LineType == LineListGen.Line.Type.ArEndDirective) {
nextOffset++;
}
// Compute length of selection. May be zero if it's entirely .arstart/.arend.
int selectedLen = nextOffset - firstOffset;
@ -1850,10 +1860,19 @@ namespace SourceGen {
if (CodeLineList[selIndex].LineType == LineListGen.Line.Type.ArStartDirective ||
CodeLineList[selIndex].LineType == LineListGen.Line.Type.ArEndDirective) {
// First selected line was .arstart/.arend, find the address map entry.
curRegion = CodeLineList.GetAddrRegionFromLine(CodeLineList[selIndex]);
curRegion = CodeLineList.GetAddrRegionFromLine(CodeLineList[selIndex],
out bool isSynth);
Debug.Assert(curRegion != null);
Debug.WriteLine("Using region from " + CodeLineList[selIndex].LineType +
": " + curRegion);
if (isSynth) {
// Synthetic regions are created for non-addressable "holes" in the map.
// They're not part of the map, so this is a create operation rather than
// a resize operation.
curRegion = null;
Debug.WriteLine("Ignoring synthetic region");
} else {
Debug.WriteLine("Using region from " + CodeLineList[selIndex].LineType +
": " + curRegion);
}
} else {
if (selectedLen == 0) {
// A length of zero is only possible if nothing but directives were selected,
@ -1866,10 +1885,14 @@ namespace SourceGen {
AddressMap.AddressMapEntry newEntry = null;
if (curRegion == null) {
// No entry, create a new one.
// No entry, create a new one. Use the current address as the default value,
// unless the region is non-addressable.
int addr = addrMap.OffsetToAddress(firstOffset);
// Create a prototype entry with the various values.
if (addr == Address.NON_ADDR) {
addr = 0;
}
// Create a prototype entry with the various values.
newEntry = new AddressMap.AddressMapEntry(firstOffset,
selectedLen, addr, string.Empty, false);
Debug.WriteLine("New entry prototype: " + newEntry);
@ -1970,7 +1993,11 @@ namespace SourceGen {
int offset = CodeLineList[selIndex].FileOffset;
Anattrib attr = mProject.GetAnattrib(offset);
EditLabel dlg = new EditLabel(mMainWin, attr.Symbol, attr.Address, offset,
int addr = attr.Address;
if (attr.IsNonAddressable) {
addr = Address.NON_ADDR;
}
EditLabel dlg = new EditLabel(mMainWin, attr.Symbol, addr, offset,
mProject.SymbolTable, mFormatter);
if (dlg.ShowDialog() != true) {
return;
@ -3849,7 +3876,12 @@ namespace SourceGen {
private void PopulateSymbolsList() {
mMainWin.SymbolsList.Clear();
foreach (Symbol sym in mProject.SymbolTable) {
string valueStr = mFormatter.FormatHexValue(sym.Value, 0);
string valueStr;
if (sym.SymbolSource == Symbol.Source.User && sym.Value == Address.NON_ADDR) {
valueStr = Address.NON_ADDR_STR;
} else {
valueStr = mFormatter.FormatHexValue(sym.Value, 0);
}
string sourceTypeStr = sym.SourceTypeString;
if (sym is DefSymbol) {
DefSymbol defSym = (DefSymbol)sym;
@ -3976,10 +4008,15 @@ namespace SourceGen {
if (line.LineType == LineListGen.Line.Type.ArStartDirective ||
line.LineType == LineListGen.Line.Type.ArEndDirective) {
AddressMap.AddressRegion region = CodeLineList.GetAddrRegionFromLine(line);
AddressMap.AddressRegion region = CodeLineList.GetAddrRegionFromLine(line,
out bool isSynth);
if (region == null) {
Debug.Assert(false, "Unable to find region at: " + line);
return;
}
StringBuilder esb = new StringBuilder();
esb.Append("Address: ");
if (region.Address == AddressMap.NON_ADDR) {
if (region.Address == Address.NON_ADDR) {
esb.Append("non-addressable");
} else {
esb.Append("$" +
@ -3997,6 +4034,8 @@ namespace SourceGen {
esb.Append(" (floating)");
}
esb.Append(CRLF);
esb.Append("Synthetic: " + isSynth);
esb.Append(CRLF);
if (!string.IsNullOrEmpty(region.PreLabel)) {
esb.Append("Pre-label: '" + region.PreLabel + "' addr=$");
esb.Append(mFormatter.FormatAddress(region.PreLabelAddress,

View File

@ -51,6 +51,7 @@ namespace SourceGen {
HiddenLocalVariableTable,
HiddenVisualization,
UnresolvedWeakRef,
NonAddrLabelRef,
InvalidOffsetOrLength,
InvalidDescriptor,
BankOverrun,
@ -165,6 +166,9 @@ namespace SourceGen {
case MessageEntry.MessageType.UnresolvedWeakRef:
problem = Res.Strings.MSG_UNRESOLVED_WEAK_REF;
break;
case MessageEntry.MessageType.NonAddrLabelRef:
problem = Res.Strings.MSG_NON_ADDR_LABEL_REF;
break;
case MessageEntry.MessageType.InvalidOffsetOrLength:
problem = Res.Strings.MSG_INVALID_OFFSET_OR_LENGTH;
break;

View File

@ -45,7 +45,9 @@ namespace SourceGen {
int prevAddr = 0;
int lastEndOffset = -1;
sb.AppendLine("Map of address regions");
sb.AppendLine("Address region map for " + project.DataFileName);
sb.Append(CRLF);
IEnumerator<AddressChange> iter = addrMap.AddressChangeIterator;
while (iter.MoveNext()) {
AddressChange change = iter.Current;
@ -109,7 +111,10 @@ namespace SourceGen {
//sb.Append(")");
sb.Append(CRLF);
PrintDepthLines(sb, depth, true);
// Add a blank line, but with the depth lines.
if (depth > 0) {
PrintDepthLines(sb, depth, true);
}
sb.Append(CRLF);
// Use offset+1 here so it lines up with start records.
@ -139,8 +144,8 @@ namespace SourceGen {
PrintDepthLines(sb, depth, true);
sb.Append(' ');
if (startAddr == AddressMap.NON_ADDR) {
sb.Append("-NA-");
if (startAddr == Address.NON_ADDR) {
sb.Append("-" + Address.NON_ADDR_STR + "-");
} else {
PrintAddress(sb, formatter, startAddr, showBank);
sb.Append(" - ");
@ -155,8 +160,8 @@ namespace SourceGen {
private static void PrintAddress(StringBuilder sb, Formatter formatter, int addr,
bool showBank) {
if (addr == AddressMap.NON_ADDR) {
sb.Append("-NA-");
if (addr == Address.NON_ADDR) {
sb.Append("-" + Address.NON_ADDR_STR + "-");
} else {
sb.Append("$");
sb.Append(formatter.FormatAddress(addr, showBank));

View File

@ -133,6 +133,7 @@ limitations under the License.
<system:String x:Key="str_MsgInvalidOffsetOrLength">Invalid offset or len</system:String>
<system:String x:Key="str_MsgLabelIgnored">Label ignored</system:String>
<system:String x:Key="str_MsgLocalVariableTableIgnored">LV table skipped over</system:String>
<system:String x:Key="str_MsgNonAddrLabelRef">Ref to non-addressable label</system:String>
<system:String x:Key="str_MsgUnresolvedWeakRef">Ref'd symbol not found</system:String>
<system:String x:Key="str_MsgVisualizationIgnored">Visualization ignored</system:String>
<system:String x:Key="str_NoFilesAvailable">no files available</system:String>

View File

@ -247,6 +247,8 @@ namespace SourceGen.Res {
(string)Application.Current.FindResource("str_MsgLabelIgnored");
public static string MSG_LOCAL_VARIABLE_TABLE_IGNORED =
(string)Application.Current.FindResource("str_MsgLocalVariableTableIgnored");
public static string MSG_NON_ADDR_LABEL_REF =
(string)Application.Current.FindResource("str_MsgNonAddrLabelRef");
public static string MSG_UNRESOLVED_WEAK_REF =
(string)Application.Current.FindResource("str_MsgUnresolvedWeakRef");
public static string MSG_VISUALIZATION_IGNORED =

View File

@ -21,6 +21,7 @@ limitations under the License.
<!-- colors for ListView and ListViewItem (see CodeListItemStyle.xaml) -->
<SolidColorBrush x:Key="Brush_ListViewForeground" Color="White"/>
<SolidColorBrush x:Key="Brush_ListViewBackground" Color="Black"/>
<SolidColorBrush x:Key="Brush_ListViewGreyText" Color="DarkGray"/>
<Color x:Key="Color_HoverFill0">#FF787D7F</Color>
<Color x:Key="Color_HoverFill1">#FF6A787F</Color>

View File

@ -21,6 +21,7 @@ limitations under the License.
<!-- colors for ListView and ListViewItem (see CodeListItemStyle.xaml) -->
<SolidColorBrush x:Key="Brush_ListViewForeground" Color="Black"/>
<SolidColorBrush x:Key="Brush_ListViewBackground" Color="White"/>
<SolidColorBrush x:Key="Brush_ListViewGreyText" Color="LightGray"/>
<Color x:Key="Color_HoverFill0">#FFF1FBFF</Color>
<Color x:Key="Color_HoverFill1">#FFD5F1FE</Color>

View File

@ -356,6 +356,9 @@ See also https://github.com/fadden/DisasmUiTest
<Style TargetType="TextBlock">
<Setter Property="TextTrimming" Value="CharacterEllipsis"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsNonAddressable}" Value="True">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource Brush_ListViewGreyText}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=HasAddrLabelHighlight}" Value="True">
<Setter Property="TextBlock.Background" Value="{StaticResource HighlightedCellFill}"/>
<Setter Property="TextBlock.Foreground" Value="{DynamicResource Brush_ListViewForeground}"/>

View File

@ -43,6 +43,9 @@ limitations under the License.
Edit the region's attributes, and resize it to the selection. The new
end offset will be {0}, for a length of {1}.
</system:String>
<system:String x:Key="str_OptResizeFail">
Cannot resize to the selection.
</system:String>
<system:String x:Key="str_CreateFixed">
Create a new region, with a fixed end point. The region will start at {0}
and have a length of {1}.

View File

@ -27,8 +27,6 @@ namespace SourceGen.WpfGui {
/// Edit Address Region dialog.
/// </summary>
public partial class EditAddress : Window, INotifyPropertyChanged {
private const string NON_ADDR_STR = "NA";
/// <summary>
/// Updated address map entry. Will be null if we want to delete the existing entry.
/// </summary>
@ -130,8 +128,8 @@ namespace SourceGen.WpfGui {
private int mPreLabelAddress;
public string PreLabelAddressStr {
get {
if (mPreLabelAddress == AddressMap.NON_ADDR) {
return "NA";
if (mPreLabelAddress == Address.NON_ADDR) {
return Address.NON_ADDR_STR;
} else {
return "$" + mFormatter.FormatAddress(mPreLabelAddress, mShowBank);
}
@ -286,7 +284,11 @@ namespace SourceGen.WpfGui {
CanDeleteRegion = true;
ShowExistingRegion = true;
AddressText = Asm65.Address.AddressToString(curRegion.Address, false);
if (curRegion.Address == Address.NON_ADDR) {
AddressText = Address.NON_ADDR_STR;
} else {
AddressText = Asm65.Address.AddressToString(curRegion.Address, false);
}
PreLabelText = curRegion.PreLabel;
UseRelativeAddressing = curRegion.IsRelative;
@ -337,7 +339,8 @@ namespace SourceGen.WpfGui {
TryCreateRegion(curRegion, curRegion.Offset, selectionLen,
curRegion.Address, out ares);
if (ares != AddressMap.AddResult.Okay) {
// Can't create the new region, so disable that option (still visible).
// Can't resize the new region, so disable that option (still visible).
Option1Str = (string)FindResource("str_OptResizeFail");
EnableOption1 = false;
CheckOption2 = true;
}
@ -404,7 +407,7 @@ namespace SourceGen.WpfGui {
// Unable to create region here. Explain why not.
EnableAttributeControls = false;
CheckOption1 = CheckOption2 = false;
mPreLabelAddress = AddressMap.NON_ADDR;
mPreLabelAddress = Address.NON_ADDR;
SetErrorString(ares1);
}
@ -510,8 +513,8 @@ namespace SourceGen.WpfGui {
private bool ParseAddress(out int addr) {
// "NA" for non-addressable?
string upper = AddressText.ToUpper();
if (upper == NON_ADDR_STR) {
addr = AddressMap.NON_ADDR;
if (upper == Address.NON_ADDR_STR) {
addr = Address.NON_ADDR;
return true;
}
// Parse numerically.