From e6c5c7f8dfc0bf565e5fdd1f432f945236e2b26c Mon Sep 17 00:00:00 2001 From: Andy McFadden Date: Thu, 30 Sep 2021 18:07:21 -0700 Subject: [PATCH] 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. --- Asm65/Address.cs | 22 ++++++++ CommonUtil/AddressMap.cs | 9 ++-- SourceGen/Anattrib.cs | 26 +++++++++ SourceGen/AsmGen/AsmAcme.cs | 24 ++++++--- SourceGen/AsmGen/AsmCc65.cs | 23 ++++++-- SourceGen/AsmGen/AsmMerlin32.cs | 25 ++++++--- SourceGen/AsmGen/AsmTass64.cs | 16 ++++-- SourceGen/AsmGen/GenCommon.cs | 12 ++++- SourceGen/AsmGen/IGenerator.cs | 10 +++- SourceGen/CodeAnalysis.cs | 71 ++++++++++++++++++------- SourceGen/DataAnalysis.cs | 4 +- SourceGen/DisasmProject.cs | 13 ++++- SourceGen/DisplayList.cs | 36 ++++++++----- SourceGen/LineListGen.cs | 59 ++++++++++++++------ SourceGen/MainController.cs | 59 ++++++++++++++++---- SourceGen/MessageList.cs | 4 ++ SourceGen/RenderAddressMap.cs | 17 +++--- SourceGen/Res/Strings.xaml | 1 + SourceGen/Res/Strings.xaml.cs | 2 + SourceGen/Res/Theme_Dark.xaml | 1 + SourceGen/Res/Theme_Light.xaml | 1 + SourceGen/WpfGui/CodeListItemStyle.xaml | 3 ++ SourceGen/WpfGui/EditAddress.xaml | 3 ++ SourceGen/WpfGui/EditAddress.xaml.cs | 21 ++++---- 24 files changed, 359 insertions(+), 103 deletions(-) diff --git a/Asm65/Address.cs b/Asm65/Address.cs index 955855d..bc3c614 100644 --- a/Asm65/Address.cs +++ b/Asm65/Address.cs @@ -21,6 +21,25 @@ namespace Asm65 { /// Memory address primitives. /// public static class Address { + /// + /// 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. + /// + /// + /// If you change this value, also update the copy in CommonUtil.AddressMap. + /// + public const int NON_ADDR = -1025; + + /// + /// Human-readable string that represents a non-addressable location. + /// + /// + /// This is a bit deep to bury a human-readable string, but it's useful to have the + /// value in one place. + /// + public const string NON_ADDR_STR = "NA"; + + /// /// Converts a 16- or 24-bit address to a string. /// @@ -41,6 +60,9 @@ namespace Asm65 { /// /// The following all evaluate to the same thing: 1000, $1000, 0x1000, 00/1000. /// + /// + /// This doesn't handle "NA", because most of the time we want to parse an actual address. + /// /// String to validate. /// Maximum valid address value. /// Integer form. diff --git a/CommonUtil/AddressMap.cs b/CommonUtil/AddressMap.cs index caddc2a..ef50165 100644 --- a/CommonUtil/AddressMap.cs +++ b/CommonUtil/AddressMap.cs @@ -78,10 +78,13 @@ namespace CommonUtil { public const int FLOATING_LEN = -1024; /// - /// 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. /// - public const int NON_ADDR = -1025; + /// + /// 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. + /// + private const int NON_ADDR = -1025; #region Structural diff --git a/SourceGen/Anattrib.cs b/SourceGen/Anattrib.cs index 5ee423b..d678e77 100644 --- a/SourceGen/Anattrib.cs +++ b/SourceGen/Anattrib.cs @@ -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 { diff --git a/SourceGen/AsmGen/AsmAcme.cs b/SourceGen/AsmGen/AsmAcme.cs index d75a0ac..6c7a81f 100644 --- a/SourceGen/AsmGen/AsmAcme.cs +++ b/SourceGen/AsmGen/AsmAcme.cs @@ -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) { diff --git a/SourceGen/AsmGen/AsmCc65.cs b/SourceGen/AsmGen/AsmCc65.cs index 760d47c..e344690 100644 --- a/SourceGen/AsmGen/AsmCc65.cs +++ b/SourceGen/AsmGen/AsmCc65.cs @@ -91,6 +91,11 @@ namespace SourceGen.AsmGen { /// private bool mHighAsciiMacroOutput; + /// + /// Address of next byte of output. + /// + private int mNextAddress = -1; + /// /// Holds detected version of configured assembler. /// @@ -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 diff --git a/SourceGen/AsmGen/AsmMerlin32.cs b/SourceGen/AsmGen/AsmMerlin32.cs index 437d1f5..aa7e12f 100644 --- a/SourceGen/AsmGen/AsmMerlin32.cs +++ b/SourceGen/AsmGen/AsmMerlin32.cs @@ -86,6 +86,11 @@ namespace SourceGen.AsmGen { /// private StreamWriter mOutStream; + /// + /// Address of next byte of output. + /// + private int mNextAddress = -1; + /// /// Holds detected version of configured assembler. /// @@ -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 diff --git a/SourceGen/AsmGen/AsmTass64.cs b/SourceGen/AsmGen/AsmTass64.cs index 32d0063..a781286 100644 --- a/SourceGen/AsmGen/AsmTass64.cs +++ b/SourceGen/AsmGen/AsmTass64.cs @@ -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) { diff --git a/SourceGen/AsmGen/GenCommon.cs b/SourceGen/AsmGen/GenCommon.cs index c63a48c..16741ee 100644 --- a/SourceGen/AsmGen/GenCommon.cs +++ b/SourceGen/AsmGen/GenCommon.cs @@ -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 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 { diff --git a/SourceGen/AsmGen/IGenerator.cs b/SourceGen/AsmGen/IGenerator.cs index 4f1cdf5..62bc727 100644 --- a/SourceGen/AsmGen/IGenerator.cs +++ b/SourceGen/AsmGen/IGenerator.cs @@ -168,9 +168,15 @@ namespace SourceGen.AsmGen { /// /// Outputs an address region directive. /// - /// Address region object. /// True if this is the start of a region. - void OutputArDirective(CommonUtil.AddressMap.AddressRegion region, bool isStart); + /// Address map change record. + void OutputArDirective(CommonUtil.AddressMap.AddressChange change); + + /// + /// Signals the code generator to flush any pending address region directives. Useful + /// for generation of non-hierarchical directives. + /// + void FlushArDirectives(); /// /// Notify the assembler of a change in register width. diff --git a/SourceGen/CodeAnalysis.cs b/SourceGen/CodeAnalysis.cs index 2714e4d..3b584a2 100644 --- a/SourceGen/CodeAnalysis.cs +++ b/SourceGen/CodeAnalysis.cs @@ -358,20 +358,43 @@ namespace SourceGen { IEnumerator 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]; } diff --git a/SourceGen/DataAnalysis.cs b/SourceGen/DataAnalysis.cs index 41272be..eff399a 100644 --- a/SourceGen/DataAnalysis.cs +++ b/SourceGen/DataAnalysis.cs @@ -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; diff --git a/SourceGen/DisasmProject.cs b/SourceGen/DisasmProject.cs index 52a31e4..e621665 100644 --- a/SourceGen/DisasmProject.cs +++ b/SourceGen/DisasmProject.cs @@ -808,7 +808,7 @@ namespace SourceGen { /// Task timestamp collection object. 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 labelList = new SortedList(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) { diff --git a/SourceGen/DisplayList.cs b/SourceGen/DisplayList.cs index 8bda1dd..cec5e43 100644 --- a/SourceGen/DisplayList.cs +++ b/SourceGen/DisplayList.cs @@ -345,7 +345,7 @@ namespace SourceGen { } /// - /// List elements. Instances are immutable. + /// List elements. Instances are immutable except for ListIndex. /// 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 + "]"; } diff --git a/SourceGen/LineListGen.cs b/SourceGen/LineListGen.cs index 29911a5..5c0d572 100644 --- a/SourceGen/LineListGen.cs +++ b/SourceGen/LineListGen.cs @@ -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; } diff --git a/SourceGen/MainController.cs b/SourceGen/MainController.cs index 11ddbd7..c8fbfd2 100644 --- a/SourceGen/MainController.cs +++ b/SourceGen/MainController.cs @@ -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, diff --git a/SourceGen/MessageList.cs b/SourceGen/MessageList.cs index 824309e..f4a2af6 100644 --- a/SourceGen/MessageList.cs +++ b/SourceGen/MessageList.cs @@ -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; diff --git a/SourceGen/RenderAddressMap.cs b/SourceGen/RenderAddressMap.cs index 9f6d6b1..bb77bf7 100644 --- a/SourceGen/RenderAddressMap.cs +++ b/SourceGen/RenderAddressMap.cs @@ -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 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)); diff --git a/SourceGen/Res/Strings.xaml b/SourceGen/Res/Strings.xaml index a81acba..7d2e1a2 100644 --- a/SourceGen/Res/Strings.xaml +++ b/SourceGen/Res/Strings.xaml @@ -133,6 +133,7 @@ limitations under the License. Invalid offset or len Label ignored LV table skipped over + Ref to non-addressable label Ref'd symbol not found Visualization ignored no files available diff --git a/SourceGen/Res/Strings.xaml.cs b/SourceGen/Res/Strings.xaml.cs index 764cfb1..acdcaf8 100644 --- a/SourceGen/Res/Strings.xaml.cs +++ b/SourceGen/Res/Strings.xaml.cs @@ -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 = diff --git a/SourceGen/Res/Theme_Dark.xaml b/SourceGen/Res/Theme_Dark.xaml index a0a44df..c8ab073 100644 --- a/SourceGen/Res/Theme_Dark.xaml +++ b/SourceGen/Res/Theme_Dark.xaml @@ -21,6 +21,7 @@ limitations under the License. + #FF787D7F #FF6A787F diff --git a/SourceGen/Res/Theme_Light.xaml b/SourceGen/Res/Theme_Light.xaml index 86ceb88..0c2f8dd 100644 --- a/SourceGen/Res/Theme_Light.xaml +++ b/SourceGen/Res/Theme_Light.xaml @@ -21,6 +21,7 @@ limitations under the License. + #FFF1FBFF #FFD5F1FE diff --git a/SourceGen/WpfGui/CodeListItemStyle.xaml b/SourceGen/WpfGui/CodeListItemStyle.xaml index ce3aa52..aa6e30d 100644 --- a/SourceGen/WpfGui/CodeListItemStyle.xaml +++ b/SourceGen/WpfGui/CodeListItemStyle.xaml @@ -356,6 +356,9 @@ See also https://github.com/fadden/DisasmUiTest