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