/*
* 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.ComponentModel;
using System.Diagnostics;
using System.IO;
using Asm65;
namespace SourceGen.AsmGen {
public class GenCommon {
///
/// Generates assembly source.
///
/// This code is common to all generators.
///
/// Reference to generator object (presumably the caller).
/// Text output sink.
/// Background worker object, for progress updates and
/// cancelation requests.
public static void Generate(IGenerator gen, StreamWriter sw, BackgroundWorker worker) {
DisasmProject proj = gen.Project;
Formatter formatter = gen.SourceFormatter;
int offset = 0;
bool doAddCycles = gen.Settings.GetBool(AppSettings.SRCGEN_SHOW_CYCLE_COUNTS, false);
LocalVariableLookup lvLookup = new LocalVariableLookup(proj.LvTables, proj,
gen.Quirks.NoRedefinableSymbols);
GenerateHeader(gen, sw);
// Used for M/X flag tracking.
StatusFlags prevFlags = StatusFlags.AllIndeterminate;
int lastProgress = 0;
while (offset < proj.FileData.Length) {
Anattrib attr = proj.GetAnattrib(offset);
if (attr.IsInstructionStart && offset > 0 &&
proj.GetAnattrib(offset - 1).IsData) {
// Transition from data to code. (Don't add blank line for inline data.)
gen.OutputLine(string.Empty);
}
// Long comments come first.
if (proj.LongComments.TryGetValue(offset, out MultiLineComment longComment)) {
List formatted = longComment.FormatText(formatter, string.Empty);
foreach (string str in formatted) {
gen.OutputLine(str);
}
}
// Check for address change.
int orgAddr = proj.AddrMap.Get(offset);
if (orgAddr >= 0) {
gen.OutputOrgDirective(offset, orgAddr);
}
List lvars = lvLookup.GetVariablesDefinedAtOffset(offset);
if (lvars != null) {
// table defined here
gen.OutputLocalVariableTable(offset, lvars,
lvLookup.GetMergedTableAtOffset(offset));
}
if (attr.IsInstructionStart) {
// Generate M/X reg width directive, if necessary.
// NOTE: we can suppress the initial directive if we know what the
// target assembler's default assumption is. Probably want to handle
// that in the ORG output handler.
if (proj.CpuDef.HasEmuFlag) {
StatusFlags curFlags = attr.StatusFlags;
curFlags.M = attr.StatusFlags.ShortM ? 1 : 0;
curFlags.X = attr.StatusFlags.ShortX ? 1 : 0;
if (curFlags.M != prevFlags.M || curFlags.X != prevFlags.X) {
// changed, output directive
gen.OutputRegWidthDirective(offset, prevFlags.M, prevFlags.X,
curFlags.M, curFlags.X);
}
prevFlags = curFlags;
}
// Look for embedded instructions.
int len;
for (len = 1; len < attr.Length; len++) {
if (proj.GetAnattrib(offset + len).IsInstructionStart) {
break;
}
}
// Output instruction.
GenerateInstruction(gen, sw, lvLookup, offset, len, doAddCycles);
if (attr.DoesNotContinue) {
gen.OutputLine(string.Empty);
}
offset += len;
} else {
gen.OutputDataOp(offset);
offset += attr.Length;
}
// Update progress meter. We don't want to spam it, so just ping it 10x.
int curProgress = (offset * 10) / proj.FileData.Length;
if (lastProgress != curProgress) {
if (worker.CancellationPending) {
Debug.WriteLine("GenCommon got cancellation request");
return;
}
lastProgress = curProgress;
worker.ReportProgress(curProgress * 10);
//System.Threading.Thread.Sleep(500);
}
}
}
private static void GenerateHeader(IGenerator gen, StreamWriter sw) {
DisasmProject proj = gen.Project;
Formatter formatter = gen.SourceFormatter;
// Check for header comment.
if (proj.LongComments.TryGetValue(LineListGen.Line.HEADER_COMMENT_OFFSET,
out MultiLineComment headerComment)) {
List formatted = headerComment.FormatText(formatter, string.Empty);
foreach (string str in formatted) {
gen.OutputLine(str);
}
}
gen.OutputAsmConfig();
// Format symbols.
bool prevConst = false;
foreach (DefSymbol defSym in proj.ActiveDefSymbolList) {
if (prevConst && !defSym.IsConstant) {
// Output a blank line between the constants and the address equates.
gen.OutputLine(string.Empty);
}
// Use an operand length of 1 so values are shown as concisely as possible.
string valueStr = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable,
gen.Localizer.LabelMap, defSym.DataDescriptor, defSym.Value, 1,
PseudoOp.FormatNumericOpFlags.None);
gen.OutputEquDirective(defSym.Label, valueStr, defSym.Comment);
prevConst = defSym.IsConstant;
}
// If there was at least one symbol, output a blank line.
if (proj.ActiveDefSymbolList.Count != 0) {
gen.OutputLine(string.Empty);
}
}
private static void GenerateInstruction(IGenerator gen, StreamWriter sw,
LocalVariableLookup lvLookup, int offset, int instrBytes, bool doAddCycles) {
DisasmProject proj = gen.Project;
Formatter formatter = gen.SourceFormatter;
byte[] data = proj.FileData;
Anattrib attr = proj.GetAnattrib(offset);
string labelStr = string.Empty;
if (attr.Symbol != null) {
labelStr = gen.Localizer.ConvLabel(attr.Symbol.Label);
}
OpDef op = proj.CpuDef.GetOpDef(data[offset]);
int operand = op.GetOperand(data, offset, attr.StatusFlags);
int instrLen = op.GetLength(attr.StatusFlags);
OpDef.WidthDisambiguation wdis = OpDef.WidthDisambiguation.None;
if (op.IsWidthPotentiallyAmbiguous) {
wdis = OpDef.GetWidthDisambiguation(instrLen, operand);
}
if (gen.Quirks.SinglePassAssembler && wdis == OpDef.WidthDisambiguation.None &&
(op.AddrMode == OpDef.AddressMode.DP ||
op.AddrMode == OpDef.AddressMode.DPIndexX) ||
op.AddrMode == OpDef.AddressMode.DPIndexY) {
// Could be a forward reference to a direct-page label. For ACME, we don't
// care if it's forward or not.
if ((gen.Quirks.SinglePassNoLabelCorrection && IsLabelReference(gen, offset)) ||
IsForwardLabelReference(gen, offset)) {
wdis = OpDef.WidthDisambiguation.ForceDirect;
}
}
string opcodeStr = formatter.FormatOpcode(op, wdis);
string formattedOperand = null;
int operandLen = instrLen - 1;
PseudoOp.FormatNumericOpFlags opFlags = PseudoOp.FormatNumericOpFlags.None;
bool isPcRelBankWrap = false;
// Tweak branch instructions. We want to show the absolute address rather
// than the relative offset (which happens with the OperandAddress assignment
// below), and 1-byte branches should always appear as a 4-byte hex value.
if (op.AddrMode == OpDef.AddressMode.PCRel) {
Debug.Assert(attr.OperandAddress >= 0);
operandLen = 2;
opFlags = PseudoOp.FormatNumericOpFlags.IsPcRel;
} else if (op.AddrMode == OpDef.AddressMode.PCRelLong ||
op.AddrMode == OpDef.AddressMode.StackPCRelLong) {
opFlags = PseudoOp.FormatNumericOpFlags.IsPcRel;
} else if (op.AddrMode == OpDef.AddressMode.Imm ||
op.AddrMode == OpDef.AddressMode.ImmLongA ||
op.AddrMode == OpDef.AddressMode.ImmLongXY) {
opFlags = PseudoOp.FormatNumericOpFlags.HasHashPrefix;
}
if (opFlags == PseudoOp.FormatNumericOpFlags.IsPcRel) {
int branchDist = attr.Address - attr.OperandAddress;
isPcRelBankWrap = branchDist > 32767 || branchDist < -32768;
}
// 16-bit operands outside bank 0 need to include the bank when computing
// symbol adjustment.
int operandForSymbol = operand;
if (attr.OperandAddress >= 0) {
operandForSymbol = attr.OperandAddress;
}
// Check Length to watch for bogus descriptors. (ApplyFormatDescriptors() should
// now be screening bad descriptors out, so we may not need the Length test.)
if (attr.DataDescriptor != null && attr.Length == attr.DataDescriptor.Length) {
FormatDescriptor dfd = gen.ModifyInstructionOperandFormat(offset,
attr.DataDescriptor, operand);
// Format operand as directed.
if (op.AddrMode == OpDef.AddressMode.BlockMove) {
// Special handling for the double-operand block move.
string opstr1 = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable,
gen.Localizer.LabelMap, dfd, operand >> 8, 1,
PseudoOp.FormatNumericOpFlags.None);
string opstr2 = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable,
gen.Localizer.LabelMap, dfd, operand & 0xff, 1,
PseudoOp.FormatNumericOpFlags.None);
if (gen.Quirks.BlockMoveArgsReversed) {
string tmp = opstr1;
opstr1 = opstr2;
opstr2 = tmp;
}
string hash = gen.Quirks.BlockMoveArgsNoHash ? "" : "#";
formattedOperand = hash + opstr1 + "," + hash + opstr2;
} else {
if (attr.DataDescriptor.IsStringOrCharacter) {
gen.UpdateCharacterEncoding(dfd);
}
formattedOperand = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable,
lvLookup, gen.Localizer.LabelMap, dfd,
offset, operandForSymbol, operandLen, opFlags);
}
} else {
// Show operand value in hex.
if (op.AddrMode == OpDef.AddressMode.BlockMove) {
int arg1, arg2;
if (gen.Quirks.BlockMoveArgsReversed) {
arg1 = operand & 0xff;
arg2 = operand >> 8;
} else {
arg1 = operand >> 8;
arg2 = operand & 0xff;
}
string hash = gen.Quirks.BlockMoveArgsNoHash ? "" : "#";
formattedOperand = hash + formatter.FormatHexValue(arg1, 2) + "," +
hash + formatter.FormatHexValue(arg2, 2);
} else {
if (operandLen == 2) {
// This is necessary for 16-bit operands, like "LDA abs" and "PEA val",
// when outside bank zero. The bank is included in the operand address,
// but we don't want to show it here.
operandForSymbol &= 0xffff;
}
formattedOperand = formatter.FormatHexValue(operandForSymbol, operandLen * 2);
}
}
string operandStr = formatter.FormatOperand(op, formattedOperand, wdis);
if (gen.Quirks.StackIntOperandIsImmediate && op.AddrMode == OpDef.AddressMode.StackInt) {
// COP $02 is standard, but some require COP #$02
operandStr = '#' + operandStr;
}
string eolComment = proj.Comments[offset];
if (doAddCycles) {
bool branchCross = (attr.Address & 0xff00) != (operandForSymbol & 0xff00);
int cycles = proj.CpuDef.GetCycles(op.Opcode, attr.StatusFlags, attr.BranchTaken,
branchCross);
if (cycles > 0) {
if (!string.IsNullOrEmpty(eolComment)) {
eolComment = cycles.ToString() + " " + eolComment;
} else {
eolComment = cycles.ToString();
}
} else {
if (!string.IsNullOrEmpty(eolComment)) {
eolComment = (-cycles).ToString() + "+ " + eolComment;
} else {
eolComment = (-cycles).ToString() + "+";
}
}
}
string commentStr = formatter.FormatEolComment(eolComment);
string replMnemonic = gen.ModifyOpcode(offset, op);
if (attr.Length != instrBytes) {
// This instruction has another instruction inside it. Throw out what we
// computed and just output as bytes.
gen.GenerateShortSequence(offset, instrBytes, out opcodeStr, out operandStr);
} else if (isPcRelBankWrap && gen.Quirks.NoPcRelBankWrap) {
// Some assemblers have trouble generating PC-relative operands that wrap
// around the bank. Output as raw hex.
gen.GenerateShortSequence(offset, instrBytes, out opcodeStr, out operandStr);
} else if (op.AddrMode == OpDef.AddressMode.BlockMove &&
gen.Quirks.BlockMoveArgsReversed) {
// On second thought, just don't even output the wrong thing.
gen.GenerateShortSequence(offset, instrBytes, out opcodeStr, out operandStr);
} else if (replMnemonic == null) {
// No mnemonic exists for this opcode.
gen.GenerateShortSequence(offset, instrBytes, out opcodeStr, out operandStr);
} else if (replMnemonic != string.Empty) {
// A replacement mnemonic has been provided.
opcodeStr = formatter.FormatMnemonic(replMnemonic, wdis);
}
gen.OutputLine(labelStr, opcodeStr, operandStr, commentStr);
// Assemblers like Merlin32 try to be helpful and track SEP/REP, but they do the
// wrong thing if we're in emulation mode. Force flags back to short.
if (proj.CpuDef.HasEmuFlag && gen.Quirks.TracksSepRepNotEmu && op == OpDef.OpREP_Imm) {
if ((operand & 0x30) != 0 && attr.StatusFlags.E == 1) {
gen.OutputRegWidthDirective(offset, 0, 0, 1, 1);
}
}
}
///
/// Determines whether the instruction at the specified offset has an operand that is
/// a forward reference. This only matters for single-pass assemblers.
///
/// Source generator reference.
/// Offset of instruction opcode.
/// True if the instruction's operand is a forward reference to a label.
private static bool IsForwardLabelReference(IGenerator gen, int offset) {
return (GetLabelOffsetFromOperand(gen, offset) > offset);
}
///
/// Determines whether the instruction at the specified offset has an operand
/// that references a symbol.
///
/// Source generator reference.
/// Offset of instruction opcode.
/// True if the instruction's operand is a forward reference to a label.
private static bool IsLabelReference(IGenerator gen, int offset) {
return (GetLabelOffsetFromOperand(gen, offset) >= 0);
}
///
/// Determines the offset of the label that the operand's symbol references.
///
/// Source generator reference.
/// Offset of instruction opcode.
/// The offset of the label, or -1 if the operand isn't a symbolic reference
/// to a known label.
private static int GetLabelOffsetFromOperand(IGenerator gen, int offset) {
DisasmProject proj = gen.Project;
Debug.Assert(proj.GetAnattrib(offset).IsInstructionStart);
FormatDescriptor dfd = proj.GetAnattrib(offset).DataDescriptor;
if (dfd == null || !dfd.HasSymbol) {
return -1;
}
return proj.FindLabelOffsetByName(dfd.SymbolRef.Label);
}
///
/// Configures some common format config items from the app settings. Uses a
/// passed-in settings object, rather than the global settings.
///
/// Application settings.
/// Format config struct.
public static void ConfigureFormatterFromSettings(AppSettings settings,
ref Formatter.FormatConfig config) {
config.mUpperHexDigits =
settings.GetBool(AppSettings.FMT_UPPER_HEX_DIGITS, false);
config.mUpperOpcodes =
settings.GetBool(AppSettings.FMT_UPPER_OP_MNEMONIC, false);
config.mUpperPseudoOpcodes =
settings.GetBool(AppSettings.FMT_UPPER_PSEUDO_OP_MNEMONIC, false);
config.mUpperOperandA =
settings.GetBool(AppSettings.FMT_UPPER_OPERAND_A, false);
config.mUpperOperandS =
settings.GetBool(AppSettings.FMT_UPPER_OPERAND_S, false);
config.mUpperOperandXY =
settings.GetBool(AppSettings.FMT_UPPER_OPERAND_XY, false);
config.mSpacesBetweenBytes =
settings.GetBool(AppSettings.FMT_SPACES_BETWEEN_BYTES, false);
config.mAddSpaceLongComment =
settings.GetBool(AppSettings.FMT_ADD_SPACE_FULL_COMMENT, true);
config.mForceAbsOpcodeSuffix =
settings.GetString(AppSettings.FMT_OPCODE_SUFFIX_ABS, string.Empty);
config.mForceLongOpcodeSuffix =
settings.GetString(AppSettings.FMT_OPCODE_SUFFIX_LONG, string.Empty);
config.mForceAbsOperandPrefix =
settings.GetString(AppSettings.FMT_OPERAND_PREFIX_ABS, string.Empty);
config.mForceLongOperandPrefix =
settings.GetString(AppSettings.FMT_OPERAND_PREFIX_LONG, string.Empty);
string exprMode = settings.GetString(AppSettings.FMT_EXPRESSION_MODE, string.Empty);
config.mExpressionMode = Formatter.FormatConfig.ParseExpressionMode(exprMode);
// Not doing the delimiter patterns here, because what's in the config file is
// intended for on-screen display, and hence likely to be unsuited for an assembler.
// Ditto for the local variable prefix.
}
///
/// Checks to see if the junk alignment directive is compatible with the actual
/// address. This is used to screen out alignment values that no longer match up
/// with the actual addresses.
///
/// File offset of directive.
/// Format descriptor.
/// Offset to address map.
/// True if the .junk alignment directive is correct.
public static bool CheckJunkAlign(int offset, FormatDescriptor dfd,
CommonUtil.AddressMap addrMap) {
Debug.Assert(dfd.FormatType == FormatDescriptor.Type.Junk);
if (dfd.FormatSubType == FormatDescriptor.SubType.None) {
return true;
}
// Just check the address. Shouldn't need to check the length.
int lastOffset = offset + dfd.Length - 1;
int alignToAddr = addrMap.OffsetToAddress(lastOffset) + 1;
int alignPwr = FormatDescriptor.AlignmentToPower(dfd.FormatSubType);
int alignMask = alignPwr - 1;
bool result = (alignToAddr & alignMask) == 0;
//Debug.WriteLine(dfd.FormatSubType + " at +" + offset.ToString("x6") +
// "(" + alignToAddr.ToString("x4") + "): " + result);
return result;
}
}
}