diff --git a/SourceGen/FormattedOperandCache.cs b/SourceGen/FormattedOperandCache.cs new file mode 100644 index 0000000..13abc6b --- /dev/null +++ b/SourceGen/FormattedOperandCache.cs @@ -0,0 +1,141 @@ +/* + * Copyright 2019 faddenSoft + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +using System; +using System.Collections.Generic; +using System.Diagnostics; + +using Asm65; + +namespace SourceGen { + /// + /// Holds a cache of formatted lines. + /// + /// + /// This is intended for multi-line items with lengths that are non-trivial to compute, + /// such as long comments (which have to do word wrapping) and strings (which may + /// be a mix of characters and hex data). The on-demand line formatter needs to be able + /// to render the Nth line of a multi-line string, and will potentially be very + /// inefficient if it has to render lies 0 through N-1 as well. (Imagine the list is + /// rendered from end to start...) Single-line items, and multi-line items that are + /// easy to generate at an arbitrary offset (dense hex), aren't stored here. + /// + /// The trick is knowing when the cached data must be invalidated. For example, a + /// fully formatted string line must be invalidated if: + /// - The Formatter changes (different delimiter definition) + /// - The FormatDescriptor changes (different length, different text encoding, different + /// type of string) + /// - The PseudoOpNames table changes (potentially altering the pseudo-op string used) + /// + /// Doing a full .equals() on the various items would reduce performance, so we use a + /// simple test on reference equality when possible, and expect that the client will try + /// to ensure that the various bits that are depended upon don't get replaced unnecessarily. + /// + public class FormattedOperandCache { + private const bool VERBOSE = false; + + private class FormattedStringEntry { + public List Lines { get; private set; } + public string PseudoOpcode { get; private set; } + + private Formatter mFormatter; + private FormatDescriptor mFormatDescriptor; + private PseudoOp.PseudoOpNames mPseudoOpNames; + + public FormattedStringEntry(List lines, string popcode, Formatter formatter, + FormatDescriptor formatDescriptor, PseudoOp.PseudoOpNames pseudoOpNames) { + // Can't be sure the list won't change, so duplicate it. + Lines = new List(lines.Count); + foreach (string str in lines) { + Lines.Add(str); + } + PseudoOpcode = popcode; + + mFormatter = formatter; + mFormatDescriptor = formatDescriptor; + mPseudoOpNames = pseudoOpNames; + } + + /// + /// Checks the entry's dependencies. + /// + /// + /// The data analyzer regenerates stuff in Anattribs, so we can't expect to have + /// the same FormatDescriptor object. + /// + /// True if the dependencies match. + public bool CheckDeps(Formatter formatter, FormatDescriptor formatDescriptor, + PseudoOp.PseudoOpNames pseudoOpNames) { + bool ok = (ReferenceEquals(mFormatter, formatter) && + ReferenceEquals(mPseudoOpNames, pseudoOpNames) && + mFormatDescriptor == formatDescriptor); + //if (!ok) { + // Debug.WriteLine("CheckDeps:" + + // (ReferenceEquals(mFormatter, formatter) ? "" : " fmt") + + // (ReferenceEquals(mPseudoOpNames, pseudoOpNames) ? "" : " pop") + + // (mFormatDescriptor == formatDescriptor ? "" : " dfd")); + //} + return ok; + } + } + + private Dictionary mStringEntries = + new Dictionary(); + + + /// + /// Retrieves the formatted string data for the specified offset. + /// + /// File offset. + /// Formatter dependency. + /// FormatDescriptor dependency. + /// PseudoOpNames dependency. + /// Pseudo-op for this string. + /// A reference to the string list. The caller must not modify the + /// list. + public List GetStringEntry(int offset, Formatter formatter, + FormatDescriptor formatDescriptor, PseudoOp.PseudoOpNames pseudoOpNames, + out string PseudoOpcode) { + PseudoOpcode = null; + if (!mStringEntries.TryGetValue(offset, out FormattedStringEntry entry)) { + return null; + } + if (!entry.CheckDeps(formatter, formatDescriptor, pseudoOpNames)) { + //Debug.WriteLine(" stale entry at +" + offset.ToString("x6")); + return null; + } + PseudoOpcode = entry.PseudoOpcode; + return entry.Lines; + } + + /// + /// Sets the string data entry for the specified offset. + /// + /// File offset. + /// String data. + /// Pseudo-opcode for this line. + /// Formatter dependency. + /// FormatDescriptor dependency. + /// PseudoOpNames dependency. + public void SetStringEntry(int offset, List lines, string pseudoOpcode, + Formatter formatter, FormatDescriptor formatDescriptor, + PseudoOp.PseudoOpNames pseudoOpNames) { + Debug.Assert(lines != null); + FormattedStringEntry fse = new FormattedStringEntry(lines, pseudoOpcode, + formatter, formatDescriptor, pseudoOpNames); + mStringEntries[offset] = fse; + } + } +} diff --git a/SourceGen/LineListGen.cs b/SourceGen/LineListGen.cs index f195304..cbe0860 100644 --- a/SourceGen/LineListGen.cs +++ b/SourceGen/LineListGen.cs @@ -63,6 +63,12 @@ namespace SourceGen { /// private PseudoOp.PseudoOpNames mPseudoOpNames; + /// + /// Cache of previously-formatted data. The data is stored with references to + /// dependencies, so it should not be necessary to explicitly clear this. + /// + private FormattedOperandCache mFormattedLineCache; + /// /// One of these per line of output in the display. It should be possible to draw @@ -421,6 +427,7 @@ namespace SourceGen { mPseudoOpNames = opNames; mLineList = new List(); + mFormattedLineCache = new FormattedOperandCache(); mShowCycleCounts = AppSettings.Global.GetBool(AppSettings.SRCGEN_SHOW_CYCLE_COUNTS, false); @@ -481,12 +488,10 @@ namespace SourceGen { FormattedParts parts; switch (line.LineType) { case Line.Type.Code: - parts = GenerateInstructionLine(mProject, mFormatter, - line.FileOffset, line.OffsetSpan, mShowCycleCounts); + parts = GenerateInstructionLine(line.FileOffset, line.OffsetSpan); break; case Line.Type.Data: - parts = GenerateDataLine(mProject, mFormatter, mPseudoOpNames, - line.FileOffset, line.SubLineIndex); + parts = GenerateDataLine(line.FileOffset, line.SubLineIndex); break; case Line.Type.Blank: // Nothing to do. @@ -624,8 +629,7 @@ namespace SourceGen { List headerLines = GenerateHeaderLines(mProject, mFormatter, mPseudoOpNames); mLineList.InsertRange(0, headerLines); - GenerateLineList(mProject, mFormatter, mPseudoOpNames, - mProject.FileData, 0, mProject.FileData.Length - 1, mLineList); + GenerateLineList(0, mProject.FileData.Length - 1, mLineList); mDisplayList.ResetList(mLineList.Count); @@ -709,8 +713,7 @@ namespace SourceGen { // Create temporary list to hold new lines. Set the initial capacity to // the previous size, on the assumption that it won't change much. List newLines = new List(endIndex - startIndex + 1); - GenerateLineList(mProject, mFormatter, mPseudoOpNames, mProject.FileData, - startOffset, endOffset, newLines); + GenerateLineList(startOffset, endOffset, newLines); // Out with the old, in with the new. mLineList.RemoveRange(startIndex, endIndex - startIndex + 1); @@ -862,14 +865,10 @@ namespace SourceGen { /// options, like maximum per-line operand length, might affect how many lines /// are generated. /// - /// Project reference. - /// Output formatter. /// Offset of first byte. /// Offset of last byte. /// List to add output lines to. - private static void GenerateLineList(DisasmProject proj, Formatter formatter, - PseudoOp.PseudoOpNames opNames, byte[] data, int startOffset, int endOffset, - List lines) { + private void GenerateLineList(int startOffset, int endOffset, List lines) { //Debug.WriteLine("GenerateRange [+" + startOffset.ToString("x6") + ",+" + // endOffset.ToString("x6") + "]"); @@ -878,9 +877,9 @@ namespace SourceGen { // Find the previous status flags for M/X tracking. StatusFlags prevFlags = StatusFlags.AllIndeterminate; - if (proj.CpuDef.HasEmuFlag) { + if (mProject.CpuDef.HasEmuFlag) { for (int scanoff = startOffset - 1; scanoff >= 0; scanoff--) { - Anattrib attr = proj.GetAnattrib(scanoff); + Anattrib attr = mProject.GetAnattrib(scanoff); if (attr.IsInstructionStart) { prevFlags = attr.StatusFlags; // Apply the same tweak here that we do to curFlags below. @@ -900,17 +899,16 @@ namespace SourceGen { // partial-list generation. bool addBlank = false; if (startOffset > 0) { - int baseOff = DataAnalysis.GetBaseOperandOffset(proj, startOffset - 1); - if (proj.GetAnattrib(baseOff).DoesNotContinue) { + int baseOff = DataAnalysis.GetBaseOperandOffset(mProject, startOffset - 1); + if (mProject.GetAnattrib(baseOff).DoesNotContinue) { addBlank = true; } } - int offset = startOffset; while (offset <= endOffset) { - Anattrib attr = proj.GetAnattrib(offset); + Anattrib attr = mProject.GetAnattrib(offset); if (attr.IsInstructionStart && offset > 0 && - proj.GetAnattrib(offset - 1).IsData) { + mProject.GetAnattrib(offset - 1).IsData) { // Transition from data to code. (Don't add blank line for inline data.) lines.Add(GenerateBlankLine(offset)); } else if (addBlank) { @@ -921,20 +919,22 @@ namespace SourceGen { // Insert long comments and notes. These may span multiple display lines, // and require word-wrap, so it's easiest just to render them fully here. - if (proj.Notes.TryGetValue(offset, out MultiLineComment noteData)) { - List formatted = noteData.FormatText(formatter, "NOTE: "); + // TODO: integrate into FormattedOperandCache so we don't have to + // regenerate them unless they change. Use the MLC as the dependency. + if (mProject.Notes.TryGetValue(offset, out MultiLineComment noteData)) { + List formatted = noteData.FormatText(mFormatter, "NOTE: "); StringListToLines(formatted, offset, Line.Type.Note, noteData.BackgroundColor, lines); } - if (proj.LongComments.TryGetValue(offset, out MultiLineComment longComment)) { - List formatted = longComment.FormatText(formatter, string.Empty); + if (mProject.LongComments.TryGetValue(offset, out MultiLineComment longComment)) { + List formatted = longComment.FormatText(mFormatter, string.Empty); StringListToLines(formatted, offset, Line.Type.LongComment, longComment.BackgroundColor, lines); } if (attr.IsInstructionStart) { // Generate reg width directive, if necessary. - if (proj.CpuDef.HasEmuFlag) { + if (mProject.CpuDef.HasEmuFlag) { // Changing from "ambiguous but assumed short" to "definitively short" // merits a directive, notably at the start of the file. The tricky // part is that E=1 means definitively M=1 X=1. And maybe @@ -964,8 +964,8 @@ namespace SourceGen { // possible values. Having the operand capitalization match the // pseudo-op's feels reasonable. rwLine.Parts = FormattedParts.CreateDirective( - formatter.FormatPseudoOp(opNames.RegWidthDirective), - formatter.FormatPseudoOp(operandStr)); + mFormatter.FormatPseudoOp(mPseudoOpNames.RegWidthDirective), + mFormatter.FormatPseudoOp(operandStr)); lines.Add(rwLine); } prevFlags = curFlags; @@ -974,7 +974,7 @@ namespace SourceGen { // Look for embedded instructions. int len; for (len = 1; len < attr.Length; len++) { - if (proj.GetAnattrib(offset + len).IsInstructionStart) { + if (mProject.GetAnattrib(offset + len).IsInstructionStart) { break; } } @@ -1003,15 +1003,32 @@ namespace SourceGen { offset += len; } else { Debug.Assert(attr.DataDescriptor != null); - // TODO: replace this with something that caches expensive items like - // string operands; maybe have an out List that is null for the - // easy stuff - int numLines = - PseudoOp.ComputeRequiredLineCount(formatter, opNames, attr.DataDescriptor, - data, offset); - for (int i = 0; i < numLines; i++) { - Line line = new Line(offset, attr.Length, Line.Type.Data, i); - lines.Add(line); + if (attr.DataDescriptor.IsString) { + // See if we've already got this one. + List strLines = mFormattedLineCache.GetStringEntry(offset, + mFormatter, attr.DataDescriptor, mPseudoOpNames, out string popcode); + if (strLines == null) { + //Debug.WriteLine("FMT string at +" + offset.ToString("x6")); + strLines = PseudoOp.FormatStringOp(mFormatter, mPseudoOpNames, + attr.DataDescriptor, mProject.FileData, offset, + out popcode); + mFormattedLineCache.SetStringEntry(offset, strLines, popcode, + mFormatter, attr.DataDescriptor, mPseudoOpNames); + } + FormattedParts[] partsArray = GenerateStringLines(offset, + popcode, strLines); + for (int i = 0; i < strLines.Count; i++) { + Line line = new Line(offset, attr.Length, Line.Type.Data, i); + line.Parts = partsArray[i]; + lines.Add(line); + } + } else { + int numLines = PseudoOp.ComputeRequiredLineCount(mFormatter, + mPseudoOpNames,attr.DataDescriptor, mProject.FileData, offset); + for (int i = 0; i < numLines; i++) { + Line line = new Line(offset, attr.Length, Line.Type.Data, i); + lines.Add(line); + } } offset += attr.Length; } @@ -1025,7 +1042,7 @@ namespace SourceGen { // // It should not be possible for an address map change to appear in the middle // of an instruction or data item. - foreach (AddressMap.AddressMapEntry ent in proj.AddrMap) { + foreach (AddressMap.AddressMapEntry ent in mProject.AddrMap) { if (ent.Offset < startOffset || ent.Offset > endOffset) { continue; } @@ -1041,9 +1058,9 @@ namespace SourceGen { } Line topLine = lines[index]; Line newLine = new Line(topLine.FileOffset, 0, Line.Type.OrgDirective); - string addrStr = formatter.FormatHexValue(ent.Addr, 4); + string addrStr = mFormatter.FormatHexValue(ent.Addr, 4); newLine.Parts = FormattedParts.CreateDirective( - formatter.FormatPseudoOp(opNames.OrgDirective), addrStr); + mFormatter.FormatPseudoOp(mPseudoOpNames.OrgDirective), addrStr); lines.Insert(index, newLine); // Prepend a blank line if the previous line wasn't already blank, and this @@ -1067,11 +1084,11 @@ namespace SourceGen { /// /// Takes a list of strings and adds them to the Line list as long comments. /// - /// - /// - /// - /// - /// + /// String list. + /// File offset of item start. + /// What type of line this is. + /// Background color (for Notes). + /// Line list to add data to. private static void StringListToLines(List list, int offset, Line.Type lineType, Color color, List lines) { foreach (string str in list) { @@ -1083,17 +1100,16 @@ namespace SourceGen { } } - private static FormattedParts GenerateInstructionLine(DisasmProject proj, - Formatter formatter, int offset, int instrBytes, bool showCycleCounts) { - Anattrib attr = proj.GetAnattrib(offset); - byte[] data = proj.FileData; + private FormattedParts GenerateInstructionLine(int offset, int instrBytes) { + Anattrib attr = mProject.GetAnattrib(offset); + byte[] data = mProject.FileData; - string offsetStr = formatter.FormatOffset24(offset); + string offsetStr = mFormatter.FormatOffset24(offset); int addr = attr.Address; - string addrStr = formatter.FormatAddress(addr, !proj.CpuDef.HasAddr16); - string bytesStr = formatter.FormatBytes(data, offset, instrBytes); - string flagsStr = attr.StatusFlags.ToString(proj.CpuDef.HasEmuFlag); + string addrStr = mFormatter.FormatAddress(addr, !mProject.CpuDef.HasAddr16); + string bytesStr = mFormatter.FormatBytes(data, offset, instrBytes); + string flagsStr = attr.StatusFlags.ToString(mProject.CpuDef.HasEmuFlag); string attrStr = attr.ToAttrString(); string labelStr = string.Empty; @@ -1101,7 +1117,7 @@ namespace SourceGen { labelStr = attr.Symbol.Label; } - OpDef op = proj.CpuDef.GetOpDef(data[offset]); + OpDef op = mProject.CpuDef.GetOpDef(data[offset]); int operand = op.GetOperand(data, offset, attr.StatusFlags); int instrLen = op.GetLength(attr.StatusFlags); OpDef.WidthDisambiguation wdis = OpDef.WidthDisambiguation.None; @@ -1109,7 +1125,7 @@ namespace SourceGen { wdis = OpDef.GetWidthDisambiguation(instrLen, operand); } - string opcodeStr = formatter.FormatOpcode(op, wdis); + string opcodeStr = mFormatter.FormatOpcode(op, wdis); if (attr.Length != instrBytes) { // An instruction is embedded inside this one. Note that BRK is a two-byte // instruction, so don't freak out if you see it marked as embedded when a @@ -1156,22 +1172,22 @@ namespace SourceGen { // Format operand as directed. if (op.AddrMode == OpDef.AddressMode.BlockMove) { // Special handling for the double-operand block move. - string opstr1 = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable, + string opstr1 = PseudoOp.FormatNumericOperand(mFormatter, mProject.SymbolTable, null, attr.DataDescriptor, operand >> 8, 1, PseudoOp.FormatNumericOpFlags.None); - string opstr2 = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable, + string opstr2 = PseudoOp.FormatNumericOperand(mFormatter, mProject.SymbolTable, null, attr.DataDescriptor, operand & 0xff, 1, PseudoOp.FormatNumericOpFlags.None); formattedOperand = '#' + opstr1 + "," + '#' + opstr2; } else { - formattedOperand = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable, + formattedOperand = PseudoOp.FormatNumericOperand(mFormatter, mProject.SymbolTable, null, attr.DataDescriptor, operandForSymbol, operandLen, opFlags); } } else { // Show operand value in hex. if (op.AddrMode == OpDef.AddressMode.BlockMove) { - formattedOperand = '#' + formatter.FormatHexValue(operand >> 8, 2) + "," + - '#' + formatter.FormatHexValue(operand & 0xff, 2); + formattedOperand = '#' + mFormatter.FormatHexValue(operand >> 8, 2) + "," + + '#' + mFormatter.FormatHexValue(operand & 0xff, 2); } else { if (operandLen == 2) { // This is necessary for 16-bit operands, like "LDA abs" and "PEA val", @@ -1179,66 +1195,107 @@ namespace SourceGen { // but we don't want to show it here. operandForSymbol &= 0xffff; } - formattedOperand = formatter.FormatHexValue(operandForSymbol, operandLen * 2); + formattedOperand = mFormatter.FormatHexValue(operandForSymbol, operandLen * 2); } } - string operandStr = formatter.FormatOperand(op, formattedOperand, wdis); + string operandStr = mFormatter.FormatOperand(op, formattedOperand, wdis); - string eolComment = proj.Comments[offset]; - if (showCycleCounts) { + string eolComment = mProject.Comments[offset]; + if (mShowCycleCounts) { bool branchCross = (attr.Address & 0xff00) != (operandForSymbol & 0xff00); - int cycles = proj.CpuDef.GetCycles(op.Opcode, attr.StatusFlags, attr.BranchTaken, - branchCross); + int cycles = mProject.CpuDef.GetCycles(op.Opcode, attr.StatusFlags, + attr.BranchTaken, branchCross); if (cycles > 0) { eolComment = cycles.ToString() + " " + eolComment; } else { eolComment = (-cycles).ToString() + "+ " + eolComment; } } - string commentStr = formatter.FormatEolComment(eolComment); + string commentStr = mFormatter.FormatEolComment(eolComment); FormattedParts parts = FormattedParts.Create(offsetStr, addrStr, bytesStr, flagsStr, attrStr, labelStr, opcodeStr, operandStr, commentStr); return parts; } - private static FormattedParts GenerateDataLine(DisasmProject proj, Formatter formatter, - PseudoOp.PseudoOpNames opNames, int offset, int subLineIndex) { - Anattrib attr = proj.GetAnattrib(offset); - byte[] data = proj.FileData; + private FormattedParts GenerateDataLine(int offset, int subLineIndex) { + Anattrib attr = mProject.GetAnattrib(offset); + byte[] data = mProject.FileData; string offsetStr, addrStr, bytesStr, flagsStr, attrStr, labelStr, opcodeStr, operandStr, commentStr; offsetStr = addrStr = bytesStr = flagsStr = attrStr = labelStr = opcodeStr = operandStr = commentStr = string.Empty; - PseudoOp.PseudoOut pout = PseudoOp.FormatDataOp(formatter, opNames, proj.SymbolTable, - null, attr.DataDescriptor, proj.FileData, offset, subLineIndex); + PseudoOp.PseudoOut pout = PseudoOp.FormatDataOp(mFormatter, mPseudoOpNames, + mProject.SymbolTable, null, attr.DataDescriptor, mProject.FileData, offset, + subLineIndex); if (subLineIndex == 0) { - offsetStr = formatter.FormatOffset24(offset); + offsetStr = mFormatter.FormatOffset24(offset); - addrStr = formatter.FormatAddress(attr.Address, !proj.CpuDef.HasAddr16); + addrStr = mFormatter.FormatAddress(attr.Address, !mProject.CpuDef.HasAddr16); if (attr.Symbol != null) { labelStr = attr.Symbol.Label; } - bytesStr = formatter.FormatBytes(data, offset, attr.Length); + bytesStr = mFormatter.FormatBytes(data, offset, attr.Length); attrStr = attr.ToAttrString(); - opcodeStr = formatter.FormatPseudoOp(pout.Opcode); + opcodeStr = mFormatter.FormatPseudoOp(pout.Opcode); + commentStr = mFormatter.FormatEolComment(mProject.Comments[offset]); } else { opcodeStr = " +"; } operandStr = pout.Operand; - if (subLineIndex == 0) { - commentStr = formatter.FormatEolComment(proj.Comments[offset]); - } - FormattedParts parts = FormattedParts.Create(offsetStr, addrStr, bytesStr, flagsStr, attrStr, labelStr, opcodeStr, operandStr, commentStr); return parts; } + + private FormattedParts[] GenerateStringLines(int offset, string popcode, + List operands) { + FormattedParts[] partsArray = new FormattedParts[operands.Count]; + + Anattrib attr = mProject.GetAnattrib(offset); + byte[] data = mProject.FileData; + + string offsetStr, addrStr, bytesStr, attrStr, labelStr, opcodeStr, + operandStr, commentStr; + + for (int subLineIndex = 0; subLineIndex < operands.Count; subLineIndex++) { + if (subLineIndex == 0) { + offsetStr = mFormatter.FormatOffset24(offset); + + addrStr = mFormatter.FormatAddress(attr.Address, !mProject.CpuDef.HasAddr16); + if (attr.Symbol != null) { + labelStr = attr.Symbol.Label; + } else { + labelStr = string.Empty; + } + + bytesStr = mFormatter.FormatBytes(data, offset, attr.Length); + attrStr = attr.ToAttrString(); + + opcodeStr = mFormatter.FormatPseudoOp(popcode); + commentStr = mFormatter.FormatEolComment(mProject.Comments[offset]); + } else { + offsetStr = addrStr = bytesStr = attrStr = labelStr = commentStr = + string.Empty; + + opcodeStr = " +"; + } + + operandStr = operands[subLineIndex]; + + FormattedParts parts = FormattedParts.Create(offsetStr, addrStr, bytesStr, + /*flags*/string.Empty, attrStr, labelStr, opcodeStr, operandStr, commentStr); + + partsArray[subLineIndex] = parts; + } + + return partsArray; + } } } diff --git a/SourceGen/MainController.cs b/SourceGen/MainController.cs index 0336749..284955f 100644 --- a/SourceGen/MainController.cs +++ b/SourceGen/MainController.cs @@ -2462,30 +2462,37 @@ namespace SourceGen { // A single line can span multiple offsets, each of which could have a // different hint. Note the code/data hints are only applied to the first // byte of each selected line, so we're not quite in sync with that. - for (int offset = line.FileOffset; offset < line.FileOffset + line.OffsetSpan; - offset++) { - switch (mProject.TypeHints[offset]) { - case CodeAnalysis.TypeHint.Code: - codeHints++; - break; - case CodeAnalysis.TypeHint.Data: - dataHints++; - break; - case CodeAnalysis.TypeHint.InlineData: - inlineDataHints++; - break; - case CodeAnalysis.TypeHint.NoHint: - noHints++; - break; - default: - Debug.Assert(false); - break; + // + // For multi-line items, the OffsetSpan of the first item covers the entire + // item (it's the same for all Line instances), so we only want to do this for + // the first entry. + if (line.SubLineIndex == 0) { + for (int offset = line.FileOffset; offset < line.FileOffset + line.OffsetSpan; + offset++) { + switch (mProject.TypeHints[offset]) { + case CodeAnalysis.TypeHint.Code: + codeHints++; + break; + case CodeAnalysis.TypeHint.Data: + dataHints++; + break; + case CodeAnalysis.TypeHint.InlineData: + inlineDataHints++; + break; + case CodeAnalysis.TypeHint.NoHint: + noHints++; + break; + default: + Debug.Assert(false); + break; + } } } } - //Debug.WriteLine("GatherEntityCounts (len=" + CodeListGen.Count + ") took " + - // (DateTime.Now - startWhen).TotalMilliseconds + " ms"); + //Debug.WriteLine("GatherEntityCounts (start=" + startIndex + " end=" + endIndex + + // " len=" + mMainWin.CodeDisplayList.Count + + // ") took " + (DateTime.Now - startWhen).TotalMilliseconds + " ms"); return new EntityCounts() { mCodeLines = codeLines, diff --git a/SourceGen/PseudoOp.cs b/SourceGen/PseudoOp.cs index 1d27d85..a1626f2 100644 --- a/SourceGen/PseudoOp.cs +++ b/SourceGen/PseudoOp.cs @@ -36,7 +36,7 @@ namespace SourceGen { private const int MAX_OPERAND_LEN = 64; /// - /// One piece of the operand. + /// One piece of the pseudo-instruction. /// public struct PseudoOut { /// @@ -58,6 +58,8 @@ namespace SourceGen { } } + #region PseudoOpNames + /// /// Pseudo-op name collection. Instances are immutable. /// @@ -238,6 +240,7 @@ namespace SourceGen { { "StrDci", ".dstr" } }); + #endregion PseudoOpNames /// /// Computes the number of lines of output required to hold the formatted output. @@ -248,6 +251,7 @@ namespace SourceGen { public static int ComputeRequiredLineCount(Formatter formatter, PseudoOpNames opNames, FormatDescriptor dfd, byte[] data, int offset) { if (dfd.IsString) { + Debug.Assert(false); // shouldn't be calling here anymore List lines = FormatStringOp(formatter, opNames, dfd, data, offset, out string popcode); return lines.Count; @@ -275,7 +279,7 @@ namespace SourceGen { /// Generates a pseudo-op statement for the specified data operation. /// /// For most operations, only one output line will be generated. For larger items, - /// like long comments, the value may be split into multiple lines. The sub-index + /// like dense hex, the value may be split into multiple lines. The sub-index /// indicates which line should be formatted. /// /// Format definition. @@ -305,6 +309,7 @@ namespace SourceGen { PseudoOut po = new PseudoOut(); if (dfd.IsString) { + Debug.Assert(false); // shouldn't be calling here anymore List lines = FormatStringOp(formatter, opNames, dfd, data, offset, out string popcode); po.Opcode = popcode; @@ -372,8 +377,8 @@ namespace SourceGen { /// File data. /// Offset, within data, of start of string. /// Pseudo-opcode string. - /// Array of strings. - private static List FormatStringOp(Formatter formatter, PseudoOpNames opNames, + /// Array of operand strings. + public static List FormatStringOp(Formatter formatter, PseudoOpNames opNames, FormatDescriptor dfd, byte[] data, int offset, out string popcode) { int hiddenLeadingBytes = 0; diff --git a/SourceGen/SourceGen.csproj b/SourceGen/SourceGen.csproj index fc28150..de56815 100644 --- a/SourceGen/SourceGen.csproj +++ b/SourceGen/SourceGen.csproj @@ -76,6 +76,7 @@ + diff --git a/SourceGen/WpfGui/MainWindow.xaml.cs b/SourceGen/WpfGui/MainWindow.xaml.cs index f8ab083..ab5c956 100644 --- a/SourceGen/WpfGui/MainWindow.xaml.cs +++ b/SourceGen/WpfGui/MainWindow.xaml.cs @@ -732,43 +732,60 @@ namespace SourceGen.WpfGui { // 50K: 10 seconds, 20K: 1.6 sec, 10K: 0.6 sec, 5K: 0.2 sec const int MAX_SEL_COUNT = 5000; - // The caller will clear the DisplayListSelection before calling here, so we - // need to clear the ListView selection to match, even if we're about to call - // SelectAll. If we don't, the SelectAll() call won't generate the necessary - // events, and our DisplayListSelection will get out of sync. - codeListView.SelectedItems.Clear(); + TaskTimer timer = new TaskTimer(); + timer.StartTask("TOTAL"); - if (sel.IsAllSelected()) { - Debug.WriteLine("SetSelection: re-selecting all items"); - codeListView.SelectAll(); - return; + try { + timer.StartTask("Clear"); + // The caller will clear the DisplayListSelection before calling here, so we + // need to clear the ListView selection to match, even if we're about to call + // SelectAll. If we don't, the SelectAll() call won't generate the necessary + // events, and our DisplayListSelection will get out of sync. + codeListView.SelectedItems.Clear(); + timer.EndTask("Clear"); + + if (sel.IsAllSelected()) { + Debug.WriteLine("SetSelection: re-selecting all items"); + timer.StartTask("SelectAll"); + codeListView.SelectAll(); + timer.EndTask("SelectAll"); + return; + } + + if (sel.Count > MAX_SEL_COUNT) { + // Too much for WPF ListView -- only restore the first item. + Debug.WriteLine("SetSelection: not restoring (" + sel.Count + " items)"); + codeListView.SelectedItems.Add(CodeDisplayList[sel.GetFirstSelectedIndex()]); + return; + } + + Debug.WriteLine("SetSelection: selecting " + sel.Count + " of " + + CodeDisplayList.Count); + + // Note: if you refresh the display list with F5, the selection will be lost. This + // appears to be a consequence of hitting a key -- changing from the built-in + // "Refresh" command to a locally defined "Re-analyze" command bound to F6 didn't + // change the behavior. Selecting "re-analyze" from the DEBUG menu doesn't lose + // the selection. + + timer.StartTask("tmpArray " + sel.Count); + DisplayList.FormattedParts[] tmpArray = new DisplayList.FormattedParts[sel.Count]; + int ai = 0; + foreach (int listIndex in sel) { + tmpArray[ai++] = CodeDisplayList[listIndex]; + } + timer.EndTask("tmpArray " + sel.Count); + + // Use a reflection call to provide the full set. This is much faster than + // adding the items one at a time to SelectedItems. (For one thing, it only + // invokes the SelectionChanged method once.) + timer.StartTask("Invoke"); + listViewSetSelectedItems.Invoke(codeListView, new object[] { tmpArray }); + timer.EndTask("Invoke"); + } finally { + timer.EndTask("TOTAL"); + //timer.DumpTimes("CodeListView_SetSelection"); } - - if (sel.Count > MAX_SEL_COUNT) { - // Too much for WPF ListView -- only restore the first item. - Debug.WriteLine("SetSelection: not restoring (" + sel.Count + " items)"); - codeListView.SelectedItems.Add(CodeDisplayList[sel.GetFirstSelectedIndex()]); - return; - } - - Debug.WriteLine("SetSelection: selecting " + sel.Count + " of " + - CodeDisplayList.Count); - - //DateTime startWhen = DateTime.Now; - - DisplayList.FormattedParts[] tmpArray = new DisplayList.FormattedParts[sel.Count]; - int ai = 0; - foreach (int listIndex in sel) { - tmpArray[ai++] = CodeDisplayList[listIndex]; - } - - // Use a reflection call to provide the full set. This is much faster than - // adding the items one at a time to SelectedItems. (For one thing, it only - // invokes the SelectionChanged method once.) - listViewSetSelectedItems.Invoke(codeListView, new object[] { tmpArray }); - - //Debug.WriteLine("SetSelection on " + sel.Count + " items took " + - // (DateTime.Now - startWhen).TotalMilliseconds + " ms"); } public void CodeListView_DebugValidateSelectionCount() {