6502bench/SourceGen/DefSymbol.cs

399 lines
17 KiB
C#

/*
* 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 &amp; CompareMask) == CompareValue &amp;&amp;
/// (addr &amp; AddressMask) == (Value &amp; 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 + "]");
}
}
}