1
0
mirror of https://github.com/fadden/6502bench.git synced 2024-11-29 10:50:28 +00:00
6502bench/SourceGen/Anattrib.cs
Andy McFadden 63d7a48705 Fix bug in inline JSR/JSL no-continue handling
JSR/JSL calls with inline data have the option of reporting that
they don't continue, which causes the code analyzer to treat them
as JMPs instead.  There was a bug that was causing the no-continue
flag to be lost in certain circumstances.

The code now explicitly records the plugin's response in an Anattrib
flag.  Test 2022-extension-scripts has been updated with a test case
that exercises this situation.
2020-05-08 17:41:26 -07:00

387 lines
13 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;
using System.Text;
using Asm65;
namespace SourceGen {
/// <summary>
/// Analyzer attribute holder. Contains the output of the instruction and data analyzers.
/// Every byte in the input file has one of these associated with it.
/// </summary>
/// <remarks>
/// (Yes, it's a mutable struct. Yes, that fact has bitten me a few times. The array
/// of these may have millions of elements, so the reduction in overhead seems worthwhile.)
/// </remarks>
public struct Anattrib {
[FlagsAttribute]
private enum AttribFlags {
InstrStart = 1 << 0, // byte is first of an instruction
Instruction = 1 << 1, // byte is part of an instruction or inline data
InlineData = 1 << 2, // byte is inline data
Data = 1 << 3, // byte is data
EntryPoint = 1 << 8, // external code branches here
BranchTarget = 1 << 9, // internal code branches here
ExternalBranch = 1 << 10, // this abs/rel branch lands outside input file
NoContinue = 1 << 12, // execution does not continue to following instruction
NoContinueScript = 1 << 13, // no-continue flag set by extension script
Visited = 1 << 16, // has the analyzer visited this byte?
Changed = 1 << 17, // set/cleared as the analyzer works
Hinted = 1 << 18, // was this byte affected by a type hint?
}
// Flags indicating what type of data is here. Use the following Is* properties
// to set/clear.
private AttribFlags mAttribFlags;
public bool IsInstructionStart {
get {
return (mAttribFlags & AttribFlags.InstrStart) != 0;
}
set {
IsInstruction = value;
if (value) {
mAttribFlags |= AttribFlags.InstrStart;
} else {
mAttribFlags &= ~AttribFlags.InstrStart;
}
}
}
public bool IsInstruction {
get {
return (mAttribFlags & AttribFlags.Instruction) != 0;
}
set {
Debug.Assert(value == false ||
(mAttribFlags & (AttribFlags.InlineData | AttribFlags.Data)) == 0);
if (value) {
mAttribFlags |= AttribFlags.Instruction;
} else {
mAttribFlags &= ~AttribFlags.Instruction;
}
}
}
public bool IsInlineData {
get {
return (mAttribFlags & AttribFlags.InlineData) != 0;
}
set {
Debug.Assert(value == false ||
(mAttribFlags & (AttribFlags.Instruction | AttribFlags.Data)) == 0);
if (value) {
mAttribFlags |= AttribFlags.InlineData;
} else {
mAttribFlags &= ~AttribFlags.InlineData;
}
}
}
public bool IsData {
get {
return (mAttribFlags & AttribFlags.Data) != 0;
}
set {
Debug.Assert(value == false ||
(mAttribFlags & (AttribFlags.InlineData | AttribFlags.Instruction)) == 0);
if (value) {
mAttribFlags |= AttribFlags.Data;
} else {
mAttribFlags &= ~AttribFlags.Data;
}
}
}
public bool IsStart {
get {
return IsInstructionStart || IsDataStart || IsInlineDataStart;
}
}
public bool IsEntryPoint {
get {
return (mAttribFlags & AttribFlags.EntryPoint) != 0;
}
set {
if (value) {
mAttribFlags |= AttribFlags.EntryPoint;
} else {
mAttribFlags &= ~AttribFlags.EntryPoint;
}
}
}
public bool IsBranchTarget {
get {
return (mAttribFlags & AttribFlags.BranchTarget) != 0;
}
set {
if (value) {
mAttribFlags |= AttribFlags.BranchTarget;
} else {
mAttribFlags &= ~AttribFlags.BranchTarget;
}
}
}
public bool IsExternalBranch {
get {
return (mAttribFlags & AttribFlags.ExternalBranch) != 0;
}
set {
if (value) {
mAttribFlags |= AttribFlags.ExternalBranch;
} else {
mAttribFlags &= ~AttribFlags.ExternalBranch;
}
}
}
public bool NoContinue {
set {
if (value) {
mAttribFlags |= AttribFlags.NoContinue;
} else {
mAttribFlags &= ~AttribFlags.NoContinue;
}
}
}
public bool NoContinueScript {
set {
if (value) {
mAttribFlags |= AttribFlags.NoContinueScript;
} else {
mAttribFlags &= ~AttribFlags.NoContinueScript;
}
}
}
public bool DoesNotContinue {
get {
return (mAttribFlags & AttribFlags.NoContinue) != 0 ||
(mAttribFlags & AttribFlags.NoContinueScript) != 0;
}
}
public bool DoesNotBranch {
get {
return (BranchTaken == OpDef.BranchTaken.Never);
}
}
public bool IsVisited {
get {
return (mAttribFlags & AttribFlags.Visited) != 0;
}
set {
if (value) {
mAttribFlags |= AttribFlags.Visited;
} else {
mAttribFlags &= ~AttribFlags.Visited;
}
}
}
public bool IsChanged {
get {
return (mAttribFlags & AttribFlags.Changed) != 0;
}
set {
if (value) {
mAttribFlags |= AttribFlags.Changed;
} else {
mAttribFlags &= ~AttribFlags.Changed;
}
}
}
public bool IsHinted {
get {
return (mAttribFlags & AttribFlags.Hinted) != 0;
}
set {
if (value) {
mAttribFlags |= AttribFlags.Hinted;
} else {
mAttribFlags &= ~AttribFlags.Hinted;
}
}
}
public bool IsDataStart {
get {
return IsData && DataDescriptor != null;
}
}
public bool IsInlineDataStart {
get {
return IsInlineData && DataDescriptor != null;
}
}
/// <summary>
/// Get the target memory address for this byte.
/// </summary>
public int Address { get; set; }
/// <summary>
/// Instructions: length of the instruction (for InstrStart). If a FormatDescriptor is
/// assigned, the length must match, or the dfd will be ignored.
/// Inline data: FormatDescriptor length, or zero if no descriptor is defined.
/// Data: FormatDescriptor length, or zero if no descriptor is defined.
///
/// This field should only be set by CodeAnalysis methods, although the "get" value
/// can be changed for data/inline-data by setting the DataDescriptor field.
/// </summary>
public int Length {
get {
// For data we don't even use the field; this ensures that we're always
// using the FormatDescriptor's length.
if (IsData || IsInlineData) {
Debug.Assert(mLength == 0);
if (DataDescriptor != null) {
return DataDescriptor.Length;
} else {
return 0;
}
}
return mLength;
}
set {
Debug.Assert(!IsData);
mLength = value;
}
}
private int mLength;
/// <summary>
/// Instructions only: processor status flags.
///
/// Note this returns a copy of a struct, so modifications to the returned value
/// (including calls to Merge and Apply) are not permanent.
/// </summary>
public StatusFlags StatusFlags {
get { return mStatusFlags; }
set { mStatusFlags = value; }
}
private StatusFlags mStatusFlags;
public void MergeStatusFlags(StatusFlags other) {
mStatusFlags.Merge(other);
}
public void ApplyStatusFlags(StatusFlags other) {
mStatusFlags.Apply(other);
}
/// <summary>
/// Branch instructions only: outcome of branch.
/// </summary>
public OpDef.BranchTaken BranchTaken { get; set; }
/// <summary>
/// Instructions only: decoded operand address value. Will be -1 if not
/// yet computed or not applicable. For a relative branch instruction,
/// this will have the absolute branch target address. On the 65816, this
/// will be a 24-bit address.
/// </summary>
public int OperandAddress {
get { return mOperandAddressSet ? mOperandAddress : -1; }
set {
Debug.Assert(mOperandAddress >= -1);
mOperandAddress = value;
mOperandAddressSet = (value >= 0);
}
}
private int mOperandAddress;
private bool mOperandAddressSet;
/// <summary>
/// Instructions only: offset referenced by OperandAddress. Will be -1 if not
/// yet computed, not applicable, or if OperandAddress refers to a location
/// outside the scope of the file.
/// </summary>
public int OperandOffset {
get { return mOperandOffsetSet ? mOperandOffset : -1; }
set {
Debug.Assert(mOperandOffset >= -1);
mOperandOffset = value;
mOperandOffsetSet = (value >= 0);
}
}
private int mOperandOffset;
private bool mOperandOffsetSet;
/// <summary>
/// Instructions only: is OperandOffset a direct target offset? (This is used when
/// tracing jump instructions, to know if we should add the offset to the scan list.
/// It's determined by the opcode, e.g. "JMP addr" -> true, "JMP (addr,X)" -> false.)
/// </summary>
public bool IsOperandOffsetDirect { get; set; }
/// <summary>
/// Symbol defined as the label for this offset. All offsets that are instruction
/// or data target offsets will have one of these defined. Users can define additional
/// symbols as well.
///
/// Will be null if no label is defined for this offset.
/// </summary>
public Symbol Symbol { get; set; }
/// <summary>
/// Format descriptor for operands and data items. Will be null if no descriptor
/// is defined for this offset.
/// </summary>
public FormatDescriptor DataDescriptor { get; set; }
/// <summary>
/// Is this an instruction with an operand (i.e. not impl/acc)?
/// </summary>
public bool IsInstructionWithOperand {
get {
if (!IsInstructionStart) {
return false;
}
return Length != 1;
}
}
/// <summary>
/// Returns a fixed-width string with indicators for items of interest.
/// </summary>
public string ToAttrString() {
StringBuilder sb = new StringBuilder(5);
char blank = '.';
sb.Append(IsEntryPoint ? '@' : blank);
sb.Append(IsHinted ? 'H' : blank);
sb.Append(DoesNotBranch ? '!' : blank);
sb.Append(DoesNotContinue ? '#' : blank);
sb.Append(IsBranchTarget ? '>' : blank);
return sb.ToString();
}
public override string ToString() {
StringBuilder sb = new StringBuilder();
if (IsInstruction) {
sb.Append("Inst");
} else if (IsData) {
sb.Append("Data");
} else if (IsInlineData) {
sb.Append("Inli");
}
if (IsStart) {
sb.Append("Start");
}
sb.Append(" len=");
sb.Append(Length);
return sb.ToString();
}
}
}