/* * 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); } } }