/* * 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.Diagnostics; namespace SourceGen { /// <summary> /// Subclass of Symbol used for symbols defined in a platform symbol file, in the project /// symbol table, or in a local variable table. /// /// Instances are immutable, except for the Xrefs field. /// </summary> /// <remarks> /// The Xrefs field isn't really part of the object. It's just convenient to access /// them from here. /// </remarks> public class DefSymbol : Symbol { // Absolute min/max width. Zero-page variables are more limited, because they're not // allowed to wrap around the end of the page. public const int MIN_WIDTH = 1; public const int MAX_WIDTH = 65536; // Value to pass to the FormatDescriptor when no width is given. private const int DEFAULT_WIDTH = 1; /// <summary> /// Data format descriptor. /// </summary> public FormatDescriptor DataDescriptor { get; private set; } /// <summary> /// True if a width was specified for this symbol. /// </summary> /// <remarks> /// All symbols have a positive width, stored in the FormatDescriptor Length property. /// We may not want to display widths that haven't been explicitly set, however, so we /// keep track here. /// </remarks> public bool HasWidth { get; private set; } /// <summary> /// User-supplied comment. /// </summary> public string Comment { get; private set; } /// <summary> /// Platform symbols only: tag used to organize symbols into groups. Used by /// extension scripts. /// /// Not serialized. /// </summary> public string Tag { get; private set; } /// <summary> /// Platform symbols only: this indicates the position of the defining platform symbol /// file in the set of symbol files. Higher numbers mean higher priority. /// /// Not serialized. /// </summary> public int LoadOrdinal { get; private set; } /// <summary> /// Platform symbols only: external file identifier for the platform symbol file that /// defined this symbol. Can be displayed to the user in the Info panel. /// /// Not serialized. /// </summary> public string FileIdentifier { get; private set; } /// <summary> /// I/O direction enumeration. /// </summary> /// <remarks> /// The numeric value determines the sort order in the Symbols window. See the Compare /// function over in Symbol. /// </remarks> [Flags] public enum DirectionFlags { None = 0, Read = 1 << 1, Write = 1 << 2, ReadWrite = Read | Write } /// <summary> /// I/O direction, used for memory-mapped I/O locations that have different meanings /// (and hence different symbols) depending on whether they're read or written. /// </summary> public DirectionFlags Direction { get; private set; } /// <summary> /// Bit masks for symbols that represent multiple addresses. Instances are immutable. /// </summary> /// <remarks> /// Given an integer "addr" to test: /// <code> /// if ((addr & CompareMask) == CompareValue && /// (addr & AddressMask) == (Value & AddressMask)) { /// // match! /// } /// </code> /// </remarks> public class MultiAddressMask { public int CompareMask { get; private set; } public int CompareValue { get; private set; } public int AddressMask { get; private set; } public MultiAddressMask(int cmpMask, int cmpValue, int addrMask) { CompareMask = cmpMask; CompareValue = cmpValue; AddressMask = addrMask; } public override string ToString() { return "MultiAddrMask: cmpMask=$" + CompareMask.ToString("x4") + " cmpValue=$" + CompareValue.ToString("x4") + " addrMask=$" + AddressMask.ToString("x4"); } public static bool operator ==(MultiAddressMask a, MultiAddressMask b) { if (ReferenceEquals(a, b)) { return true; // same object, or both null } if (ReferenceEquals(a, null) || ReferenceEquals(b, null)) { return false; // one is null } return a.CompareMask == b.CompareMask && a.CompareValue == b.CompareValue && a.AddressMask == b.AddressMask; } public static bool operator !=(MultiAddressMask a, MultiAddressMask b) { return !(a == b); } public override bool Equals(object obj) { return obj is MultiAddressMask && this == (MultiAddressMask)obj; } public override int GetHashCode() { return CompareMask ^ CompareValue ^ AddressMask; } } /// <summary> /// Bit masks to apply when performing comparisons. Useful when more than one address /// maps to the same thing (e.g. Atari 2600 registers). /// /// Will be null if no mask is specified. /// </summary> public MultiAddressMask MultiMask { get; private set; } /// <summary> /// Cross-reference data, generated by the analyzer. /// </summary> /// <remarks> /// This is just a convenient place to reference some data generated at run-time. It's /// not serialized, and not included in the test for equality. /// </remarks> public XrefSet Xrefs { get; private set; } /// <summary> /// Internal base-object (Symbol) constructor, called by other constructors. /// </summary> private DefSymbol(string label, int value, Source source, Type type, LabelAnnotation labelAnno) : base(label, value, source, type, labelAnno) { Debug.Assert(source == Source.Platform || source == Source.Project || source == Source.Variable); Debug.Assert(type == Type.ExternalAddr || type == Type.Constant); Xrefs = new XrefSet(); } /// <summary> /// Constructor. Limited form, used in a couple of places, e.g. when we need to start /// with a default value. The symbol will have unspecified width, ReadWrite direction, /// and no mask. /// </summary> /// <param name="label">Symbol's label.</param> /// <param name="value">Symbol's value.</param> /// <param name="source">Symbol source (general point of origin).</param> /// <param name="type">Symbol type.</param> /// <param name="formatSubType">Format descriptor sub-type, so we know how the /// user wants the value to be displayed.</param> public DefSymbol(string label, int value, Source source, Type type, FormatDescriptor.SubType formatSubType) : this(label, value, source, type, LabelAnnotation.None, formatSubType, -1, false, string.Empty, DirectionFlags.ReadWrite, null, string.Empty) { } /// <summary> /// Constructor. General form. /// </summary> /// <param name="label">Symbol's label.</param> /// <param name="value">Symbol's value.</param> /// <param name="source">Symbol source (general point of origin).</param> /// <param name="type">Symbol type.</param> /// <param name="formatSubType">Format descriptor sub-type, so we know how the /// user wants the value to be displayed.</param> /// <param name="width">Variable width.</param> /// <param name="widthSpecified">True if width was explicitly specified. If this is /// <param name="comment">End-of-line comment.</param> /// <param name="direction">I/O direction.</param> /// <param name="multiMask">Bit mask to apply before comparisons.</param> /// <param name="tag">Symbol tag, used for grouping platform symbols.</param> /// false, the value of the "width" argument is ignored.</param> public DefSymbol(string label, int value, Source source, Type type, LabelAnnotation labelAnno, FormatDescriptor.SubType formatSubType, int width, bool widthSpecified, string comment, DirectionFlags direction, MultiAddressMask multiMask, string tag) : this(label, value, source, type, labelAnno) { Debug.Assert(comment != null); Debug.Assert(tag != null); if (widthSpecified && type == Type.Constant && source != Source.Variable) { // non-variable constants don't have a width; override arg Debug.WriteLine("Overriding constant DefSymbol width"); widthSpecified = false; } HasWidth = widthSpecified; if (!widthSpecified) { width = DEFAULT_WIDTH; } Debug.Assert(width >= MIN_WIDTH && width <= MAX_WIDTH); DataDescriptor = FormatDescriptor.Create(width, FormatDescriptor.Type.NumericLE, formatSubType); Comment = comment; Debug.Assert(((int)direction & ~(int)DirectionFlags.ReadWrite) == 0); Direction = direction; // constants don't have masks if (type != Type.Constant) { MultiMask = multiMask; } Tag = tag; } /// <summary> /// Constructor. Used for platform symbol files. /// </summary> /// <param name="loadOrdinal">Indicates the order in which the defining platform /// symbol file was loaded. Higher numbers indicate later loading, which translates /// to higher priority.</param> /// <param name="fileIdent">Platform symbol file identifier, for the Info panel.</param> public DefSymbol(string label, int value, Source source, Type type, FormatDescriptor.SubType formatSubType, int width, bool widthSpecified, string comment, DirectionFlags direction, MultiAddressMask multiMask, string tag, int loadOrdinal, string fileIdent) : this(label, value, source, type, LabelAnnotation.None, formatSubType, width, widthSpecified, comment, direction, multiMask, tag) { LoadOrdinal = loadOrdinal; FileIdentifier = fileIdent; } /// <summary> /// Create a DefSymbol given a Symbol, FormatDescriptor, and a few other things. Used /// for deserialization. /// </summary> /// <param name="sym">Base symbol.</param> /// <param name="dfd">Format descriptor.</param> /// <param name="widthSpecified">Set if a width was explicitly specified.</param> /// <param name="comment">End-of-line comment.</param> /// <param name="direction">I/O direction.</param> /// <param name="multiMask">Bit mask to apply before comparisons.</param> public static DefSymbol Create(Symbol sym, FormatDescriptor dfd, bool widthSpecified, string comment, DirectionFlags direction, MultiAddressMask multiMask) { int width = dfd.Length; if (widthSpecified && sym.SymbolType == Type.Constant && sym.SymbolSource != Source.Variable) { // non-variable constants don't have a width; override arg Debug.WriteLine("Overriding constant DefSymbol width"); widthSpecified = false; } Debug.Assert(dfd.FormatType == FormatDescriptor.Type.NumericLE); return new DefSymbol(sym.Label, sym.Value, sym.SymbolSource, sym.SymbolType, sym.LabelAnno, dfd.FormatSubType, width, widthSpecified, comment, direction, multiMask, string.Empty); } /// <summary> /// Constructs a DefSymbol from an existing DefSymbol, with a different label. Use /// this to change the label while keeping everything else the same. /// </summary> /// <remarks> /// This can't be a simple Rename() function that uses a copy constructor because /// the label is in the base class. /// /// The Xrefs reference points to the actual XrefSet in the original. This is not /// ideal, but it's the easiest way to keep xrefs working across Lv de-duplication /// (you actually *want* xrefs added to copies to be held by the original). /// </remarks> /// <param name="defSym">Source DefSymbol.</param> /// <param name="label">Label to use.</param> public DefSymbol(DefSymbol defSym, string label) : this(label, defSym.Value, defSym.SymbolSource, defSym.SymbolType, defSym.LabelAnno, defSym.DataDescriptor.FormatSubType, defSym.DataDescriptor.Length, defSym.HasWidth, defSym.Comment, defSym.Direction, defSym.MultiMask, defSym.Tag) { Debug.Assert(SymbolSource == Source.Variable); Xrefs = defSym.Xrefs; } /// <summary> /// Determines whether a symbol overlaps with a region. Useful for variables. /// </summary> /// <param name="a">Symbol to check.</param> /// <param name="value">Address.</param> /// <param name="width">Symbol width.</param> /// <param name="type">Symbol type to check against.</param> /// <returns>True if the symbols overlap.</returns> public static bool CheckOverlap(DefSymbol a, int value, int width, Type type) { if (a.DataDescriptor.Length <= 0 || width <= 0) { return false; } if (a.Value < 0 || value < 0) { return false; } if (a.SymbolType != type) { return false; } int maxStart = Math.Max(a.Value, value); int minEnd = Math.Min(a.Value + a.DataDescriptor.Length - 1, value + width - 1); return (maxStart <= minEnd); } /// <summary> /// Determines whether symbol one is "wider" than symbol two. It's wider if it /// has a width, and the other symbol either doesn't have a width or has a width /// but is narrower. /// </summary> public static bool IsWider(DefSymbol a, DefSymbol b) { if (!a.HasWidth && !b.HasWidth) { return false; } else if (a.HasWidth && !b.HasWidth) { return true; } else if (!a.HasWidth && b.HasWidth) { return false; } else { return a.DataDescriptor.Length > b.DataDescriptor.Length; } } public static bool operator ==(DefSymbol a, DefSymbol b) { if (ReferenceEquals(a, b)) { return true; // same object, or both null } if (ReferenceEquals(a, null) || ReferenceEquals(b, null)) { return false; // one is null } return a.Equals(b); } public static bool operator !=(DefSymbol a, DefSymbol b) { return !(a == b); } public override bool Equals(object obj) { if (!(obj is DefSymbol)) { return false; } // Do base-class equality comparison and the ReferenceEquals check. if (!base.Equals(obj)) { return false; } // All fields must be equal, except Xrefs. DefSymbol other = (DefSymbol)obj; if (DataDescriptor != other.DataDescriptor || Comment != other.Comment || Tag != other.Tag) { return false; } return true; } public override int GetHashCode() { return base.GetHashCode() ^ DataDescriptor.GetHashCode() ^ Comment.GetHashCode() ^ Tag.GetHashCode(); } public override string ToString() { return base.ToString() + ":" + DataDescriptor + ";" + Comment + " dir=" + Direction + " mask=" + (MultiMask == null ? "-" : MultiMask.ToString()) + (string.IsNullOrEmpty(Tag) ? "" : " [" + Tag + "]"); } } }