/*
* 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 operands that may span multiple lines.
///
///
/// This is intended for multi-line items with line counts that are non-trivial to
/// compute, such as 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 operand, and will
/// potentially be very inefficient if it has to render lines 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.
/// We don't make much of an effort to purge stale entries, since that can only happen
/// when the operand at a specific offset changes to something that doesn't require fancy
/// formatting. The total memory required for all entries is relatively small.
///
public class FormattedOperandCache {
///
/// One entry in the cache.
///
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;
}
}
///
/// Cached entries, keyed by file offset.
///
private Dictionary mStringEntries =
new Dictionary();
///
/// Retrieves the formatted string data for the specified offset.
///
/// File offset.
/// Formatter dependency.
/// FormatDescriptor dependency.
/// PseudoOpNames dependency.
/// Result: pseudo-op for this string.
/// A reference to the string list, or null if the entry is absent or invalid.
/// 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)) {
DebugNotFoundCount++;
return null;
}
if (!entry.CheckDeps(formatter, formatDescriptor, pseudoOpNames)) {
//Debug.WriteLine(" stale entry at +" + offset.ToString("x6"));
DebugFoundStaleCount++;
return null;
}
DebugFoundValidCount++;
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;
}
// Some counters for evaluating efficacy.
public int DebugFoundValidCount { get; private set; }
public int DebugFoundStaleCount { get; private set; }
public int DebugNotFoundCount { get; private set; }
public void DebugResetCounters() {
DebugFoundValidCount = DebugFoundStaleCount = DebugNotFoundCount = 0;
}
public void DebugLogCounters() {
Debug.WriteLine("Operand cache: valid=" + DebugFoundValidCount + ", stale=" +
DebugFoundStaleCount + ", missing=" + DebugNotFoundCount);
}
}
}