2019-05-28 01:46:09 +00:00
|
|
|
|
/*
|
|
|
|
|
* 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;
|
2020-10-18 20:22:24 +00:00
|
|
|
|
using CommonUtil;
|
2019-05-28 01:46:09 +00:00
|
|
|
|
|
2019-07-20 20:28:10 +00:00
|
|
|
|
namespace SourceGen.AsmGen {
|
2019-05-28 01:46:09 +00:00
|
|
|
|
public class GenCommon {
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Generates assembly source.
|
|
|
|
|
///
|
|
|
|
|
/// This code is common to all generators.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="gen">Reference to generator object (presumably the caller).</param>
|
|
|
|
|
/// <param name="sw">Text output sink.</param>
|
|
|
|
|
/// <param name="worker">Background worker object, for progress updates and
|
|
|
|
|
/// cancelation requests.</param>
|
|
|
|
|
public static void Generate(IGenerator gen, StreamWriter sw, BackgroundWorker worker) {
|
|
|
|
|
DisasmProject proj = gen.Project;
|
|
|
|
|
Formatter formatter = gen.SourceFormatter;
|
2020-10-17 23:10:48 +00:00
|
|
|
|
int offset = gen.StartOffset;
|
2019-05-28 01:46:09 +00:00
|
|
|
|
|
|
|
|
|
bool doAddCycles = gen.Settings.GetBool(AppSettings.SRCGEN_SHOW_CYCLE_COUNTS, false);
|
|
|
|
|
|
2019-09-01 04:54:20 +00:00
|
|
|
|
LocalVariableLookup lvLookup = new LocalVariableLookup(proj.LvTables, proj,
|
Label rework, part 6
Correct handling of local variables. We now correctly uniquify them
with regard to non-unique labels. Because local vars can effectively
have global scope we mostly want to treat them as global, but they're
uniquified relative to other globals very late in the process, so we
can't just throw them in the symbol table and be done. Fortunately
local variables exist in a separate namespace, so we just need to
uniquify the variables relative to the post-localization symbol table.
In other words, we take the symbol table, apply the label map, and
rename any variable that clashes.
This also fixes an older problem where we weren't masking the
leading '_' on variable labels when generating 64tass output.
The code list now makes non-unique labels obvious, but you can't tell
the difference between unique global and unique local. What's more,
the default type value in Edit Label is now adjusted to Global for
unique locals that were auto-generated. To make it a bit easier to
figure out what's what, the Info panel now has a "label type" line
that reports the type.
The 2023-non-unique-labels test had some additional tests added to
exercise conflicts with local variables. The 2019-local-variables
test output changed slightly because the de-duplicated variable
naming convention was simplified.
2019-11-18 21:26:03 +00:00
|
|
|
|
gen.Localizer.LabelMap, gen.Quirks.LeadingUnderscoreSpecial,
|
2019-09-01 17:55:19 +00:00
|
|
|
|
gen.Quirks.NoRedefinableSymbols);
|
2019-08-31 01:33:05 +00:00
|
|
|
|
|
2019-05-28 01:46:09 +00:00
|
|
|
|
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<string> 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);
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-31 01:33:05 +00:00
|
|
|
|
List<DefSymbol> lvars = lvLookup.GetVariablesDefinedAtOffset(offset);
|
|
|
|
|
if (lvars != null) {
|
2019-09-01 17:55:19 +00:00
|
|
|
|
// table defined here
|
|
|
|
|
gen.OutputLocalVariableTable(offset, lvars,
|
|
|
|
|
lvLookup.GetMergedTableAtOffset(offset));
|
2019-08-31 01:33:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-05-28 01:46:09 +00:00
|
|
|
|
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;
|
2020-07-06 15:27:42 +00:00
|
|
|
|
curFlags.M = attr.StatusFlags.IsShortM ? 1 : 0;
|
|
|
|
|
curFlags.X = attr.StatusFlags.IsShortX ? 1 : 0;
|
2019-05-28 01:46:09 +00:00
|
|
|
|
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.
|
2019-08-31 01:33:05 +00:00
|
|
|
|
GenerateInstruction(gen, sw, lvLookup, offset, len, doAddCycles);
|
2019-05-28 01:46:09 +00:00
|
|
|
|
|
|
|
|
|
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<string> formatted = headerComment.FormatText(formatter, string.Empty);
|
|
|
|
|
foreach (string str in formatted) {
|
|
|
|
|
gen.OutputLine(str);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gen.OutputAsmConfig();
|
|
|
|
|
|
|
|
|
|
// Format symbols.
|
2019-10-23 05:43:10 +00:00
|
|
|
|
bool prevConst = false;
|
2019-05-28 01:46:09 +00:00
|
|
|
|
foreach (DefSymbol defSym in proj.ActiveDefSymbolList) {
|
2019-10-23 05:43:10 +00:00
|
|
|
|
if (prevConst && !defSym.IsConstant) {
|
|
|
|
|
// Output a blank line between the constants and the address equates.
|
|
|
|
|
gen.OutputLine(string.Empty);
|
|
|
|
|
}
|
2019-08-31 01:33:05 +00:00
|
|
|
|
// Use an operand length of 1 so values are shown as concisely as possible.
|
2019-05-28 01:46:09 +00:00
|
|
|
|
string valueStr = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable,
|
|
|
|
|
gen.Localizer.LabelMap, defSym.DataDescriptor, defSym.Value, 1,
|
2019-11-16 00:15:31 +00:00
|
|
|
|
PseudoOp.FormatNumericOpFlags.OmitLabelPrefixSuffix);
|
2020-01-18 02:29:20 +00:00
|
|
|
|
string labelStr = gen.Localizer.ConvLabel(defSym.Label);
|
|
|
|
|
gen.OutputEquDirective(labelStr, valueStr, defSym.Comment);
|
2019-10-23 05:43:10 +00:00
|
|
|
|
|
|
|
|
|
prevConst = defSym.IsConstant;
|
2019-05-28 01:46:09 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If there was at least one symbol, output a blank line.
|
|
|
|
|
if (proj.ActiveDefSymbolList.Count != 0) {
|
|
|
|
|
gen.OutputLine(string.Empty);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-31 01:33:05 +00:00
|
|
|
|
private static void GenerateInstruction(IGenerator gen, StreamWriter sw,
|
|
|
|
|
LocalVariableLookup lvLookup, int offset, int instrBytes, bool doAddCycles) {
|
2019-05-28 01:46:09 +00:00
|
|
|
|
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) {
|
2019-08-04 03:54:07 +00:00
|
|
|
|
// 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)) {
|
2019-05-28 01:46:09 +00:00
|
|
|
|
wdis = OpDef.WidthDisambiguation.ForceDirect;
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-07-02 00:59:12 +00:00
|
|
|
|
if (wdis == OpDef.WidthDisambiguation.ForceLongMaybe &&
|
|
|
|
|
gen.Quirks.SinglePassAssembler &&
|
|
|
|
|
IsForwardLabelReference(gen, offset)) {
|
|
|
|
|
// Assemblers like cc65 can't tell if a symbol reference is Absolute or
|
|
|
|
|
// Long if they haven't seen the symbol yet. Irrelevant for ACME, which
|
|
|
|
|
// doesn't currently handle 65816 outside bank 0.
|
|
|
|
|
wdis = OpDef.WidthDisambiguation.ForceLong;
|
|
|
|
|
}
|
2019-05-28 01:46:09 +00:00
|
|
|
|
|
|
|
|
|
string opcodeStr = formatter.FormatOpcode(op, wdis);
|
|
|
|
|
|
|
|
|
|
string formattedOperand = null;
|
|
|
|
|
int operandLen = instrLen - 1;
|
2019-11-16 00:15:31 +00:00
|
|
|
|
PseudoOp.FormatNumericOpFlags opFlags = PseudoOp.FormatNumericOpFlags.OmitLabelPrefixSuffix;
|
2019-05-28 01:46:09 +00:00
|
|
|
|
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.
|
2020-10-17 23:10:48 +00:00
|
|
|
|
// Unless we're outside bank 0 on 65816, in which case most assemblers require
|
|
|
|
|
// them to be 6-byte hex values.
|
2020-10-11 21:35:17 +00:00
|
|
|
|
if (op.AddrMode == OpDef.AddressMode.PCRel ||
|
|
|
|
|
op.AddrMode == OpDef.AddressMode.DPPCRel) {
|
2019-05-28 01:46:09 +00:00
|
|
|
|
Debug.Assert(attr.OperandAddress >= 0);
|
|
|
|
|
operandLen = 2;
|
2019-11-09 04:44:45 +00:00
|
|
|
|
opFlags |= PseudoOp.FormatNumericOpFlags.IsPcRel;
|
2019-05-28 01:46:09 +00:00
|
|
|
|
} else if (op.AddrMode == OpDef.AddressMode.PCRelLong ||
|
|
|
|
|
op.AddrMode == OpDef.AddressMode.StackPCRelLong) {
|
2019-11-09 04:44:45 +00:00
|
|
|
|
opFlags |= PseudoOp.FormatNumericOpFlags.IsPcRel;
|
2019-05-28 01:46:09 +00:00
|
|
|
|
} else if (op.AddrMode == OpDef.AddressMode.Imm ||
|
|
|
|
|
op.AddrMode == OpDef.AddressMode.ImmLongA ||
|
|
|
|
|
op.AddrMode == OpDef.AddressMode.ImmLongXY) {
|
2019-11-09 04:44:45 +00:00
|
|
|
|
opFlags |= PseudoOp.FormatNumericOpFlags.HasHashPrefix;
|
2019-05-28 01:46:09 +00:00
|
|
|
|
}
|
2019-11-09 04:44:45 +00:00
|
|
|
|
if ((opFlags & PseudoOp.FormatNumericOpFlags.IsPcRel) != 0) {
|
2019-05-28 01:46:09 +00:00
|
|
|
|
int branchDist = attr.Address - attr.OperandAddress;
|
|
|
|
|
isPcRelBankWrap = branchDist > 32767 || branchDist < -32768;
|
|
|
|
|
}
|
2020-07-02 00:59:12 +00:00
|
|
|
|
if (op.IsAbsolutePBR) {
|
|
|
|
|
opFlags |= PseudoOp.FormatNumericOpFlags.IsAbsolutePBR;
|
|
|
|
|
}
|
2019-05-28 01:46:09 +00:00
|
|
|
|
|
|
|
|
|
// 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) {
|
2019-09-20 21:05:17 +00:00
|
|
|
|
FormatDescriptor dfd = gen.ModifyInstructionOperandFormat(offset,
|
|
|
|
|
attr.DataDescriptor, operand);
|
|
|
|
|
|
2019-05-28 01:46:09 +00:00
|
|
|
|
// 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,
|
2019-09-20 21:05:17 +00:00
|
|
|
|
gen.Localizer.LabelMap, dfd, operand >> 8, 1,
|
2019-11-16 00:15:31 +00:00
|
|
|
|
PseudoOp.FormatNumericOpFlags.OmitLabelPrefixSuffix);
|
2019-05-28 01:46:09 +00:00
|
|
|
|
string opstr2 = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable,
|
2019-09-20 21:05:17 +00:00
|
|
|
|
gen.Localizer.LabelMap, dfd, operand & 0xff, 1,
|
2019-11-16 00:15:31 +00:00
|
|
|
|
PseudoOp.FormatNumericOpFlags.OmitLabelPrefixSuffix);
|
2019-05-28 01:46:09 +00:00
|
|
|
|
if (gen.Quirks.BlockMoveArgsReversed) {
|
|
|
|
|
string tmp = opstr1;
|
|
|
|
|
opstr1 = opstr2;
|
|
|
|
|
opstr2 = tmp;
|
|
|
|
|
}
|
2019-08-08 20:02:01 +00:00
|
|
|
|
string hash = gen.Quirks.BlockMoveArgsNoHash ? "" : "#";
|
|
|
|
|
formattedOperand = hash + opstr1 + "," + hash + opstr2;
|
2020-10-11 21:35:17 +00:00
|
|
|
|
} else if (op.AddrMode == OpDef.AddressMode.DPPCRel) {
|
|
|
|
|
// Special handling for double-operand BBR/BBS. The instruction generally
|
|
|
|
|
// behaves like a branch, so format that first.
|
|
|
|
|
string branchStr = PseudoOp.FormatNumericOperand(formatter,
|
|
|
|
|
proj.SymbolTable, gen.Localizer.LabelMap, dfd,
|
|
|
|
|
operandForSymbol, operandLen, opFlags);
|
|
|
|
|
string dpStr = formatter.FormatHexValue(operand & 0xff, 2);
|
|
|
|
|
formattedOperand = dpStr + "," + branchStr;
|
2019-05-28 01:46:09 +00:00
|
|
|
|
} else {
|
2019-08-21 20:30:25 +00:00
|
|
|
|
if (attr.DataDescriptor.IsStringOrCharacter) {
|
2019-09-20 21:05:17 +00:00
|
|
|
|
gen.UpdateCharacterEncoding(dfd);
|
2019-08-21 20:30:25 +00:00
|
|
|
|
}
|
2019-05-28 01:46:09 +00:00
|
|
|
|
formattedOperand = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable,
|
2019-09-20 21:05:17 +00:00
|
|
|
|
lvLookup, gen.Localizer.LabelMap, dfd,
|
2019-08-31 01:33:05 +00:00
|
|
|
|
offset, operandForSymbol, operandLen, opFlags);
|
2019-05-28 01:46:09 +00:00
|
|
|
|
}
|
|
|
|
|
} 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;
|
|
|
|
|
}
|
2019-08-08 20:02:01 +00:00
|
|
|
|
string hash = gen.Quirks.BlockMoveArgsNoHash ? "" : "#";
|
|
|
|
|
formattedOperand = hash + formatter.FormatHexValue(arg1, 2) + "," +
|
|
|
|
|
hash + formatter.FormatHexValue(arg2, 2);
|
2020-10-11 21:35:17 +00:00
|
|
|
|
} else if (op.AddrMode == OpDef.AddressMode.DPPCRel) {
|
|
|
|
|
formattedOperand = formatter.FormatHexValue(operand & 0xff, 2) + "," +
|
|
|
|
|
formatter.FormatHexValue(operandForSymbol, operandLen * 2);
|
2019-05-28 01:46:09 +00:00
|
|
|
|
} else {
|
2020-10-17 23:10:48 +00:00
|
|
|
|
if (operandLen == 2 && !(op.IsAbsolutePBR && gen.Quirks.Need24BitsForAbsPBR) &&
|
|
|
|
|
(opFlags & PseudoOp.FormatNumericOpFlags.IsPcRel) == 0) {
|
2019-05-28 01:46:09 +00:00
|
|
|
|
// 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,
|
2020-10-17 23:10:48 +00:00
|
|
|
|
// but we don't want to show it here. We may need it for JSR/JMP though,
|
|
|
|
|
// and the bank is required for relative branch instructions.
|
2019-05-28 01:46:09 +00:00
|
|
|
|
operandForSymbol &= 0xffff;
|
|
|
|
|
}
|
|
|
|
|
formattedOperand = formatter.FormatHexValue(operandForSymbol, operandLen * 2);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
string operandStr = formatter.FormatOperand(op, formattedOperand, wdis);
|
|
|
|
|
|
2020-07-03 20:58:18 +00:00
|
|
|
|
if (gen.Quirks.StackIntOperandIsImmediate &&
|
|
|
|
|
op.AddrMode == OpDef.AddressMode.StackInt) {
|
2019-08-19 23:09:11 +00:00
|
|
|
|
// COP $02 is standard, but some require COP #$02
|
|
|
|
|
operandStr = '#' + operandStr;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-11 21:35:17 +00:00
|
|
|
|
// The BBR/BBS/RMB/SMB instructions include a bit index (0-7). The standard way is
|
|
|
|
|
// to make it part of the mnemonic, but some assemblers make it an argument.
|
|
|
|
|
if (gen.Quirks.BitNumberIsArg && op.IsNumberedBitOp) {
|
|
|
|
|
// Easy way: do some string manipulation.
|
|
|
|
|
char bitIndex = opcodeStr[opcodeStr.Length - 1];
|
|
|
|
|
opcodeStr = opcodeStr.Substring(0, opcodeStr.Length - 1);
|
|
|
|
|
operandStr = bitIndex.ToString() + "," + operandStr;
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-28 01:46:09 +00:00
|
|
|
|
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) {
|
2019-09-16 00:09:00 +00:00
|
|
|
|
if (!string.IsNullOrEmpty(eolComment)) {
|
|
|
|
|
eolComment = cycles.ToString() + " " + eolComment;
|
|
|
|
|
} else {
|
|
|
|
|
eolComment = cycles.ToString();
|
|
|
|
|
}
|
2019-05-28 01:46:09 +00:00
|
|
|
|
} else {
|
2019-09-16 00:09:00 +00:00
|
|
|
|
if (!string.IsNullOrEmpty(eolComment)) {
|
|
|
|
|
eolComment = (-cycles).ToString() + "+ " + eolComment;
|
|
|
|
|
} else {
|
|
|
|
|
eolComment = (-cycles).ToString() + "+";
|
|
|
|
|
}
|
2019-05-28 01:46:09 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
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) {
|
2020-07-06 15:27:42 +00:00
|
|
|
|
if ((operand & 0x30) != 0 && attr.StatusFlags.IsEmulationMode) {
|
2019-05-28 01:46:09 +00:00
|
|
|
|
gen.OutputRegWidthDirective(offset, 0, 0, 1, 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Determines whether the instruction at the specified offset has an operand that is
|
|
|
|
|
/// a forward reference. This only matters for single-pass assemblers.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="gen">Source generator reference.</param>
|
|
|
|
|
/// <param name="offset">Offset of instruction opcode.</param>
|
|
|
|
|
/// <returns>True if the instruction's operand is a forward reference to a label.</returns>
|
|
|
|
|
private static bool IsForwardLabelReference(IGenerator gen, int offset) {
|
2019-08-04 03:54:07 +00:00
|
|
|
|
return (GetLabelOffsetFromOperand(gen, offset) > offset);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Determines whether the instruction at the specified offset has an operand
|
|
|
|
|
/// that references a symbol.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="gen">Source generator reference.</param>
|
|
|
|
|
/// <param name="offset">Offset of instruction opcode.</param>
|
|
|
|
|
/// <returns>True if the instruction's operand is a forward reference to a label.</returns>
|
|
|
|
|
private static bool IsLabelReference(IGenerator gen, int offset) {
|
|
|
|
|
return (GetLabelOffsetFromOperand(gen, offset) >= 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Determines the offset of the label that the operand's symbol references.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="gen">Source generator reference.</param>
|
|
|
|
|
/// <param name="offset">Offset of instruction opcode.</param>
|
|
|
|
|
/// <returns>The offset of the label, or -1 if the operand isn't a symbolic reference
|
|
|
|
|
/// to a known label.</returns>
|
|
|
|
|
private static int GetLabelOffsetFromOperand(IGenerator gen, int offset) {
|
2019-05-28 01:46:09 +00:00
|
|
|
|
DisasmProject proj = gen.Project;
|
|
|
|
|
Debug.Assert(proj.GetAnattrib(offset).IsInstructionStart);
|
|
|
|
|
|
|
|
|
|
FormatDescriptor dfd = proj.GetAnattrib(offset).DataDescriptor;
|
|
|
|
|
if (dfd == null || !dfd.HasSymbol) {
|
2019-08-04 03:54:07 +00:00
|
|
|
|
return -1;
|
2019-05-28 01:46:09 +00:00
|
|
|
|
}
|
2019-08-04 03:54:07 +00:00
|
|
|
|
return proj.FindLabelOffsetByName(dfd.SymbolRef.Label);
|
2019-05-28 01:46:09 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Configures some common format config items from the app settings. Uses a
|
|
|
|
|
/// passed-in settings object, rather than the global settings.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="settings">Application settings.</param>
|
|
|
|
|
/// <param name="config">Format config struct.</param>
|
|
|
|
|
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);
|
2020-07-20 01:39:27 +00:00
|
|
|
|
config.mOperandWrapLen =
|
|
|
|
|
settings.GetInt(AppSettings.FMT_OPERAND_WRAP_LEN, 0);
|
2019-05-28 01:46:09 +00:00
|
|
|
|
|
|
|
|
|
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);
|
2019-08-12 00:59:20 +00:00
|
|
|
|
|
|
|
|
|
// 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.
|
2019-08-31 21:54:44 +00:00
|
|
|
|
|
|
|
|
|
// Ditto for the local variable prefix.
|
2019-05-28 01:46:09 +00:00
|
|
|
|
}
|
2019-10-19 03:28:02 +00:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 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.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="offset">File offset of directive.</param>
|
|
|
|
|
/// <param name="dfd">Format descriptor.</param>
|
|
|
|
|
/// <param name="addrMap">Offset to address map.</param>
|
2020-01-18 01:26:31 +00:00
|
|
|
|
/// <returns>True if the .junk alignment directive is correct, false if it's
|
|
|
|
|
/// incorrect or not an alignment sub-type (e.g. None).</returns>
|
2019-10-19 03:28:02 +00:00
|
|
|
|
public static bool CheckJunkAlign(int offset, FormatDescriptor dfd,
|
|
|
|
|
CommonUtil.AddressMap addrMap) {
|
|
|
|
|
Debug.Assert(dfd.FormatType == FormatDescriptor.Type.Junk);
|
|
|
|
|
if (dfd.FormatSubType == FormatDescriptor.SubType.None) {
|
2020-01-18 01:26:31 +00:00
|
|
|
|
return false;
|
2019-10-19 03:28:02 +00:00
|
|
|
|
}
|
2019-10-23 20:25:50 +00:00
|
|
|
|
Debug.Assert(dfd.IsAlignedJunk);
|
2019-10-19 03:28:02 +00:00
|
|
|
|
|
|
|
|
|
// 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);
|
2019-10-23 20:25:50 +00:00
|
|
|
|
int alignMask = (1 << alignPwr) - 1;
|
2019-10-19 03:28:02 +00:00
|
|
|
|
bool result = (alignToAddr & alignMask) == 0;
|
|
|
|
|
//Debug.WriteLine(dfd.FormatSubType + " at +" + offset.ToString("x6") +
|
|
|
|
|
// "(" + alignToAddr.ToString("x4") + "): " + result);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
2020-10-18 20:22:24 +00:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Determines whether the project appears to have a PRG header.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="project">Project to check.</param>
|
|
|
|
|
/// <returns>True if we think we found a PRG header.</returns>
|
|
|
|
|
public static bool HasPrgHeader(DisasmProject project) {
|
Fix 64tass output for non-loadable files
64tass wants to place its output into a 64KB region of memory,
starting at the address "*" is set to, and continuing without
wrapping around the end of the bank. Some files aren't meant to be
handled that way, so we need to generate the output differently.
If the file's output fits nicely, it's considered "loadable", and
is generated in the usual way. If it doesn't, it's treated as
"streamable", and the initial "* = addr" directive is omitted
(leaving "*" at zero), and we go straight to ".logical" directives.
65816 code with an initial address outside bank 0 is treated as
"streamable" whether or not the contents fit nicely in the designated
64K area. This caused a minor change to a few of the 65816 tests.
A new test, 20240-large-overlay, exercises "streamable" by creating
a file with eight overlapping 8KB segments that load at $8000.
While the file as a whole fits in 64KB, it wouldn't if loaded at
the desired start address.
Also, updated the regression test harness to report assembler
failure independently of overall test failure. This makes it easier
to confirm that (say) ACME v0.96.4 still works with the code we
generate, even though it doesn't match the expected output (which
was generated for v0.97).
(problem was raised in issue #98)
2021-08-02 00:09:52 +00:00
|
|
|
|
if (project.FileDataLength < 3 || project.FileDataLength > 65536+2) {
|
|
|
|
|
// Must fit in 64KB of memory. A 65538-byte file will work if the
|
|
|
|
|
// first two bytes are the PRG header (and it starts at address zero).
|
2020-10-18 20:22:24 +00:00
|
|
|
|
//Debug.WriteLine("PRG test: incompatible file length");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
Anattrib attr0 = project.GetAnattrib(0);
|
|
|
|
|
Anattrib attr1 = project.GetAnattrib(1);
|
|
|
|
|
if (!(attr0.IsDataStart && attr1.IsData)) {
|
|
|
|
|
//Debug.WriteLine("PRG test: +0/1 not data");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (attr0.Length != 2) {
|
|
|
|
|
//Debug.WriteLine("PRG test: +0/1 not 16-bit value");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (attr0.Symbol != null || attr1.Symbol != null) {
|
|
|
|
|
//Debug.WriteLine("PRG test: +0/1 has label");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// Confirm there's an address map entry at offset 2.
|
|
|
|
|
if (project.AddrMap.Get(2) == AddressMap.NO_ENTRY_ADDR) {
|
|
|
|
|
//Debug.WriteLine("PRG test: no ORG at +2");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// See if the address at offset 2 matches the value at 0/1.
|
|
|
|
|
int value01 = project.FileData[0] | (project.FileData[1] << 8);
|
|
|
|
|
int addr2 = project.AddrMap.OffsetToAddress(2);
|
|
|
|
|
if (value01 != addr2) {
|
|
|
|
|
//Debug.WriteLine("PRG test: +0/1 value is " + value01.ToString("x4") +
|
|
|
|
|
// ", address at +2 is " + addr2);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
Fix 64tass output for non-loadable files
64tass wants to place its output into a 64KB region of memory,
starting at the address "*" is set to, and continuing without
wrapping around the end of the bank. Some files aren't meant to be
handled that way, so we need to generate the output differently.
If the file's output fits nicely, it's considered "loadable", and
is generated in the usual way. If it doesn't, it's treated as
"streamable", and the initial "* = addr" directive is omitted
(leaving "*" at zero), and we go straight to ".logical" directives.
65816 code with an initial address outside bank 0 is treated as
"streamable" whether or not the contents fit nicely in the designated
64K area. This caused a minor change to a few of the 65816 tests.
A new test, 20240-large-overlay, exercises "streamable" by creating
a file with eight overlapping 8KB segments that load at $8000.
While the file as a whole fits in 64KB, it wouldn't if loaded at
the desired start address.
Also, updated the regression test harness to report assembler
failure independently of overall test failure. This makes it easier
to confirm that (say) ACME v0.96.4 still works with the code we
generate, even though it doesn't match the expected output (which
was generated for v0.97).
(problem was raised in issue #98)
2021-08-02 00:09:52 +00:00
|
|
|
|
// TODO? confirm project fits in 64K of memory
|
|
|
|
|
|
2020-10-18 20:22:24 +00:00
|
|
|
|
return true;
|
|
|
|
|
}
|
2019-05-28 01:46:09 +00:00
|
|
|
|
}
|
|
|
|
|
}
|