Add a string operand cache

String operands used to be simple -- each line had 62 characters
plus two hard-coded non-ASCII delimiters -- but now we're mixing
character and hex data, so we can't use simple math to tell where
the lines will break.  We want to render them and keep the result
around until some dependency changes, e.g. different delimiters
or a change to the pseudo-op table.

Also, cleaned up LineListGen a little.  It had some methods that
were declared static because they were expected to be shared, but
that never happened.

Also, fixed a bug in GatherEntityCounts where multi-line items were
being scanned multiple times.
This commit is contained in:
Andy McFadden 2019-08-17 16:59:08 -07:00
parent 4902b89cf8
commit f87ac20f32
6 changed files with 368 additions and 140 deletions

View File

@ -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 {
/// <summary>
/// Holds a cache of formatted lines.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public class FormattedOperandCache {
private const bool VERBOSE = false;
private class FormattedStringEntry {
public List<string> Lines { get; private set; }
public string PseudoOpcode { get; private set; }
private Formatter mFormatter;
private FormatDescriptor mFormatDescriptor;
private PseudoOp.PseudoOpNames mPseudoOpNames;
public FormattedStringEntry(List<string> 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<string>(lines.Count);
foreach (string str in lines) {
Lines.Add(str);
}
PseudoOpcode = popcode;
mFormatter = formatter;
mFormatDescriptor = formatDescriptor;
mPseudoOpNames = pseudoOpNames;
}
/// <summary>
/// Checks the entry's dependencies.
/// </summary>
/// <remarks>
/// The data analyzer regenerates stuff in Anattribs, so we can't expect to have
/// the same FormatDescriptor object.
/// </remarks>
/// <returns>True if the dependencies match.</returns>
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<int, FormattedStringEntry> mStringEntries =
new Dictionary<int, FormattedStringEntry>();
/// <summary>
/// Retrieves the formatted string data for the specified offset.
/// </summary>
/// <param name="offset">File offset.</param>
/// <param name="formatter">Formatter dependency.</param>
/// <param name="formatDescriptor">FormatDescriptor dependency.</param>
/// <param name="pseudoOpNames">PseudoOpNames dependency.</param>
/// <param name="PseudoOpcode">Pseudo-op for this string.</param>
/// <returns>A reference to the string list. The caller must not modify the
/// list.</returns>
public List<string> 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;
}
/// <summary>
/// Sets the string data entry for the specified offset.
/// </summary>
/// <param name="offset">File offset.</param>
/// <param name="lines">String data.</param>
/// <param name="pseudoOpcode">Pseudo-opcode for this line.</param>
/// <param name="formatter">Formatter dependency.</param>
/// <param name="formatDescriptor">FormatDescriptor dependency.</param>
/// <param name="pseudoOpNames">PseudoOpNames dependency.</param>
public void SetStringEntry(int offset, List<string> 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;
}
}
}

View File

@ -63,6 +63,12 @@ namespace SourceGen {
/// </summary>
private PseudoOp.PseudoOpNames mPseudoOpNames;
/// <summary>
/// Cache of previously-formatted data. The data is stored with references to
/// dependencies, so it should not be necessary to explicitly clear this.
/// </summary>
private FormattedOperandCache mFormattedLineCache;
/// <summary>
/// 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<Line>();
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<Line> 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<Line> newLines = new List<Line>(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.
/// </summary>
/// <param name="proj">Project reference.</param>
/// <param name="formatter">Output formatter.</param>
/// <param name="startOffset">Offset of first byte.</param>
/// <param name="endOffset">Offset of last byte.</param>
/// <param name="lines">List to add output lines to.</param>
private static void GenerateLineList(DisasmProject proj, Formatter formatter,
PseudoOp.PseudoOpNames opNames, byte[] data, int startOffset, int endOffset,
List<Line> lines) {
private void GenerateLineList(int startOffset, int endOffset, List<Line> 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<string> 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<string> formatted = noteData.FormatText(mFormatter, "NOTE: ");
StringListToLines(formatted, offset, Line.Type.Note,
noteData.BackgroundColor, lines);
}
if (proj.LongComments.TryGetValue(offset, out MultiLineComment longComment)) {
List<string> formatted = longComment.FormatText(formatter, string.Empty);
if (mProject.LongComments.TryGetValue(offset, out MultiLineComment longComment)) {
List<string> 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<string> 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<string> 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 {
/// <summary>
/// Takes a list of strings and adds them to the Line list as long comments.
/// </summary>
/// <param name="list"></param>
/// <param name="offset"></param>
/// <param name="lineType"></param>
/// <param name="color"></param>
/// <param name="lines"></param>
/// <param name="list">String list.</param>
/// <param name="offset">File offset of item start.</param>
/// <param name="lineType">What type of line this is.</param>
/// <param name="color">Background color (for Notes).</param>
/// <param name="lines">Line list to add data to.</param>
private static void StringListToLines(List<string> list, int offset, Line.Type lineType,
Color color, List<Line> 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<string> 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;
}
}
}

View File

@ -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,

View File

@ -36,7 +36,7 @@ namespace SourceGen {
private const int MAX_OPERAND_LEN = 64;
/// <summary>
/// One piece of the operand.
/// One piece of the pseudo-instruction.
/// </summary>
public struct PseudoOut {
/// <summary>
@ -58,6 +58,8 @@ namespace SourceGen {
}
}
#region PseudoOpNames
/// <summary>
/// Pseudo-op name collection. Instances are immutable.
/// </summary>
@ -238,6 +240,7 @@ namespace SourceGen {
{ "StrDci", ".dstr" }
});
#endregion PseudoOpNames
/// <summary>
/// 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<string> 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.
/// </summary>
/// <param name="formatter">Format definition.</param>
@ -305,6 +309,7 @@ namespace SourceGen {
PseudoOut po = new PseudoOut();
if (dfd.IsString) {
Debug.Assert(false); // shouldn't be calling here anymore
List<string> lines = FormatStringOp(formatter, opNames, dfd, data,
offset, out string popcode);
po.Opcode = popcode;
@ -372,8 +377,8 @@ namespace SourceGen {
/// <param name="data">File data.</param>
/// <param name="offset">Offset, within data, of start of string.</param>
/// <param name="popcode">Pseudo-opcode string.</param>
/// <returns>Array of strings.</returns>
private static List<string> FormatStringOp(Formatter formatter, PseudoOpNames opNames,
/// <returns>Array of operand strings.</returns>
public static List<string> FormatStringOp(Formatter formatter, PseudoOpNames opNames,
FormatDescriptor dfd, byte[] data, int offset, out string popcode) {
int hiddenLeadingBytes = 0;

View File

@ -76,6 +76,7 @@
<Compile Include="AsmGen\IAssembler.cs" />
<Compile Include="AsmGen\IGenerator.cs" />
<Compile Include="AsmGen\LabelLocalizer.cs" />
<Compile Include="FormattedOperandCache.cs" />
<Compile Include="Tests\GenTest.cs" />
<Compile Include="Tests\ProgressMessage.cs" />
<Compile Include="Tests\WpfGui\GenTestRunner.xaml.cs">

View File

@ -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() {