From cd23580cc572839cb2a3ba4d1e7bbdc18e4bdc00 Mon Sep 17 00:00:00 2001 From: Andy McFadden Date: Fri, 18 Oct 2019 20:28:02 -0700 Subject: [PATCH] Add junk/align directives Sometimes there's a bunch of junk in the binary that isn't used for anything. Often it's there to make things line up at the start of a page boundary. This adds a ".junk" directive that tells the disassembler that it can safely disregard the contents of a region. If the region ends on a power-of-two boundary, an alignment value can be specified. The assembly source generators will output an alignment directive when possible, a .fill directive when appropriate, and a .dense directive when all else fails. Because we're required to regenerate the original data file, it's not always possible to avoid generating a hex dump. --- Asm65/Helper.cs | 24 ++++ CommonUtil/BitTwiddle.cs | 94 ++++++++++++ SourceGen/AsmGen/AsmAcme.cs | 21 +++ SourceGen/AsmGen/AsmCc65.cs | 17 +++ SourceGen/AsmGen/AsmMerlin32.cs | 20 +++ SourceGen/AsmGen/AsmTass64.cs | 21 +++ SourceGen/AsmGen/GenCommon.cs | 27 ++++ SourceGen/FormatDescriptor.cs | 88 +++++++++++- SourceGen/MainController.cs | 7 +- SourceGen/ProjectFile.cs | 7 +- SourceGen/PseudoOp.cs | 18 +++ SourceGen/RuntimeData/Help/editors.html | 7 +- SourceGen/RuntimeData/Help/intro.html | 20 ++- SourceGen/SGTestData/2004-numeric-types | Bin 631 -> 1024 bytes SourceGen/SGTestData/2004-numeric-types.dis65 | 28 +++- .../Expected/2004-numeric-types_64tass.S | 14 +- .../Expected/2004-numeric-types_Merlin32.S | 14 +- .../Expected/2004-numeric-types_acme.S | 14 +- .../Expected/2004-numeric-types_cc65.S | 14 +- .../Expected/2004-numeric-types_cc65.cfg | 2 +- .../SGTestData/Source/2004-numeric-types.S | 32 +++-- SourceGen/WpfGui/EditAppSettings.xaml | 67 +++++---- SourceGen/WpfGui/EditAppSettings.xaml.cs | 2 + SourceGen/WpfGui/EditDataOperand.xaml | 11 +- SourceGen/WpfGui/EditDataOperand.xaml.cs | 135 +++++++++++++++++- 25 files changed, 638 insertions(+), 66 deletions(-) create mode 100644 CommonUtil/BitTwiddle.cs diff --git a/Asm65/Helper.cs b/Asm65/Helper.cs index e4954c5..8cb701e 100644 --- a/Asm65/Helper.cs +++ b/Asm65/Helper.cs @@ -52,5 +52,29 @@ namespace Asm65 { target |= addr & 0x7fff0000; return target; } + + /// + /// Determines whether a range of bytes is composed of a single value. If so, the + /// value is returned. + /// + /// Bytes to examine. + /// Start offset. + /// Number of bytes. Must be greater than zero. + /// The value found, or -1 if multiple values were found. + public static int CheckRangeHoldsSingleValue(byte[] data, int offset, int length) { + Debug.Assert(data != null); + Debug.Assert(offset >= 0 && offset < data.Length); + Debug.Assert(length >= 0 && offset + length <= data.Length); + if (length < 0) { + return -1; + } + byte testVal = data[offset++]; + while (--length > 0) { + if (data[offset++] != testVal) { + return -1; + } + } + return testVal; + } } } diff --git a/CommonUtil/BitTwiddle.cs b/CommonUtil/BitTwiddle.cs new file mode 100644 index 0000000..f5b5fd4 --- /dev/null +++ b/CommonUtil/BitTwiddle.cs @@ -0,0 +1,94 @@ +/* + * 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; + +namespace CommonUtil { + public class BitTwiddle { + /// + /// Returns the argument, rounded up to the next highest power of 2. If the argument + /// is an exact power of two, it is returned unmodified. + /// + public static int RoundUpPowerOf2(int val) { + val--; // handle exact power of 2 case; works correctly for val=0 + return NextHighestPowerOf2(val); + } + + /// + /// Returns the first power of 2 value that is higher than val. If the argument is + /// an exact power of two, the next power of 2 is returned. + /// + /// + /// Classic bit-twiddling approach. I can't find a "count leading zeroes" function + /// in C# that turns into a CPU instruction; if we had that, we could just use 1< + public static int NextHighestPowerOf2(int val) { + val |= val >> 1; // "smear" bits across integer + val |= val >> 2; + val |= val >> 4; + val |= val >> 8; + val |= val >> 16; + return val + 1; + } + + /// + /// Returns an integer in which the only bit set is the least-significant set bit in + /// the argument. + /// + /// + /// If you pass in 10110100, this will return 00000100. + /// + /// Two's complement negation inverts and adds one, so 01100 --> 10011+1 --> 10100. The + /// only set bit they have in common is the one we want. + /// + public static int IsolateLeastSignificantOne(int val) { + return val & -val; + } + + /// + /// Returns the number of bits that are set in the argument. + /// + /// + /// If you pass in 10110100, this will return 4. + /// + /// This comes from http://aggregate.org/MAGIC/#Population%20Count%20(Ones%20Count) . + /// + public static int CountOneBits(int val) { + // 32-bit recursive reduction using SWAR... + // but first step is mapping 2-bit values + // into sum of 2 1-bit values in sneaky way + val -= ((val >> 1) & 0x55555555); + val = (((val >> 2) & 0x33333333) + (val & 0x33333333)); + val = (((val >> 4) + val) & 0x0f0f0f0f); + val += (val >> 8); + val += (val >> 16); + return (val & 0x0000003f); + } + + /// + /// Returns the number of trailing zero bits in the argument. + /// + /// + /// If you pass in 10110100, this will return 2. + /// + /// Also from http://aggregate.org/MAGIC/ . + /// + public static int CountTrailingZeroes(int val) { + // Lazy: reduce to least-significant 1 bit, subtract one to clear that bit and set + // all the bits to the right of it, then just count the 1s. + return CountOneBits(IsolateLeastSignificantOne(val) - 1); + } + } +} diff --git a/SourceGen/AsmGen/AsmAcme.cs b/SourceGen/AsmGen/AsmAcme.cs index 44aa8a0..6fd31fb 100644 --- a/SourceGen/AsmGen/AsmAcme.cs +++ b/SourceGen/AsmGen/AsmAcme.cs @@ -118,6 +118,8 @@ namespace SourceGen.AsmGen { //DefineBigData4 { "Fill", "!fill" }, { "Dense", "!hex" }, + //Junk + { "Align", "!align" }, { "StrGeneric", "!text" }, // can use !xor for high ASCII //StrReverse //StrNullTerm @@ -406,6 +408,25 @@ namespace SourceGen.AsmGen { opcodeStr = operandStr = null; OutputDenseHex(offset, length, labelStr, commentStr); break; + case FormatDescriptor.Type.Junk: + int fillVal = Helper.CheckRangeHoldsSingleValue(data, offset, length); + if (fillVal >= 0 && GenCommon.CheckJunkAlign(offset, dfd, Project.AddrMap)) { + // !align ANDVALUE, EQUALVALUE [, FILLVALUE] + opcodeStr = sDataOpNames.Align; + int alignVal = 1 << FormatDescriptor.AlignmentToPower(dfd.FormatSubType); + operandStr = (alignVal - 1).ToString() + + ",0," + formatter.FormatHexValue(fillVal, 2); + } else if (fillVal >= 0) { + // treat same as Fill + opcodeStr = sDataOpNames.Fill; + operandStr = length + "," + formatter.FormatHexValue(fillVal, 2); + } else { + // treat same as Dense + multiLine = true; + opcodeStr = operandStr = null; + OutputDenseHex(offset, length, labelStr, commentStr); + } + break; case FormatDescriptor.Type.StringGeneric: case FormatDescriptor.Type.StringReverse: case FormatDescriptor.Type.StringNullTerm: diff --git a/SourceGen/AsmGen/AsmCc65.cs b/SourceGen/AsmGen/AsmCc65.cs index 3965883..9bbf5a1 100644 --- a/SourceGen/AsmGen/AsmCc65.cs +++ b/SourceGen/AsmGen/AsmCc65.cs @@ -115,6 +115,7 @@ namespace SourceGen.AsmGen { //DefineBigData4 { "Fill", ".res" }, //Dense // no equivalent, use .byte with comma-separated args + //Junk { "StrGeneric", ".byte" }, //StrReverse { "StrNullTerm", ".asciiz" }, @@ -434,6 +435,22 @@ namespace SourceGen.AsmGen { opcodeStr = operandStr = null; OutputDenseHex(offset, length, labelStr, commentStr); break; + case FormatDescriptor.Type.Junk: + // The ca65 .align directive has a dependency on the alignment of the + // segment as a whole. We're not currently declaring multiple segments, + // so we can't use .align without generating complaints. + int fillVal = Helper.CheckRangeHoldsSingleValue(data, offset, length); + if (fillVal >= 0) { + // treat same as Fill + opcodeStr = sDataOpNames.Fill; + operandStr = length + "," + formatter.FormatHexValue(fillVal, 2); + } else { + // treat same as Dense + multiLine = true; + opcodeStr = operandStr = null; + OutputDenseHex(offset, length, labelStr, commentStr); + } + break; case FormatDescriptor.Type.StringGeneric: case FormatDescriptor.Type.StringReverse: case FormatDescriptor.Type.StringNullTerm: diff --git a/SourceGen/AsmGen/AsmMerlin32.cs b/SourceGen/AsmGen/AsmMerlin32.cs index e9807f3..1ce3856 100644 --- a/SourceGen/AsmGen/AsmMerlin32.cs +++ b/SourceGen/AsmGen/AsmMerlin32.cs @@ -106,6 +106,8 @@ namespace SourceGen.AsmGen { //DefineBigData4 { "Fill", "ds" }, { "Dense", "hex" }, + //Junk + //Align { "StrGeneric", "asc" }, { "StrReverse", "rev" }, //StrNullTerm @@ -269,6 +271,24 @@ namespace SourceGen.AsmGen { opcodeStr = operandStr = null; OutputDenseHex(offset, length, labelStr, commentStr); break; + case FormatDescriptor.Type.Junk: + int fillVal = Helper.CheckRangeHoldsSingleValue(data, offset, length); + if (fillVal >= 0) { + opcodeStr = sDataOpNames.Fill; + if (dfd.FormatSubType == FormatDescriptor.SubType.Align256 && + GenCommon.CheckJunkAlign(offset, dfd, Project.AddrMap)) { + // special syntax for page alignment + operandStr = "\\," + formatter.FormatHexValue(fillVal, 2); + } else { + operandStr = length + "," + formatter.FormatHexValue(fillVal, 2); + } + } else { + // treat same as Dense + multiLine = true; + opcodeStr = operandStr = null; + OutputDenseHex(offset, length, labelStr, commentStr); + } + break; case FormatDescriptor.Type.StringGeneric: case FormatDescriptor.Type.StringReverse: case FormatDescriptor.Type.StringNullTerm: diff --git a/SourceGen/AsmGen/AsmTass64.cs b/SourceGen/AsmGen/AsmTass64.cs index 6bff8ff..290298c 100644 --- a/SourceGen/AsmGen/AsmTass64.cs +++ b/SourceGen/AsmGen/AsmTass64.cs @@ -132,6 +132,8 @@ namespace SourceGen.AsmGen { //DefineBigData4 { "Fill", ".fill" }, //Dense // no equivalent, use .byte with comma-separated args + //Junk + { "Align", ".align" }, { "StrGeneric", ".text" }, //StrReverse { "StrNullTerm", ".null" }, @@ -463,6 +465,25 @@ namespace SourceGen.AsmGen { opcodeStr = operandStr = null; OutputDenseHex(offset, length, labelStr, commentStr); break; + case FormatDescriptor.Type.Junk: + int fillVal = Helper.CheckRangeHoldsSingleValue(data, offset, length); + if (fillVal >= 0 && GenCommon.CheckJunkAlign(offset, dfd, Project.AddrMap)) { + // .align [, ] + opcodeStr = sDataOpNames.Align; + int alignVal = 1 << FormatDescriptor.AlignmentToPower(dfd.FormatSubType); + operandStr = alignVal.ToString() + + "," + formatter.FormatHexValue(fillVal, 2); + } else if (fillVal >= 0) { + // treat same as Fill + opcodeStr = sDataOpNames.Fill; + operandStr = length + "," + formatter.FormatHexValue(fillVal, 2); + } else { + // treat same as Dense + multiLine = true; + opcodeStr = operandStr = null; + OutputDenseHex(offset, length, labelStr, commentStr); + } + break; case FormatDescriptor.Type.StringGeneric: case FormatDescriptor.Type.StringReverse: case FormatDescriptor.Type.StringNullTerm: diff --git a/SourceGen/AsmGen/GenCommon.cs b/SourceGen/AsmGen/GenCommon.cs index 4d60800..0b7e509 100644 --- a/SourceGen/AsmGen/GenCommon.cs +++ b/SourceGen/AsmGen/GenCommon.cs @@ -422,5 +422,32 @@ namespace SourceGen.AsmGen { // 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; + } } } diff --git a/SourceGen/FormatDescriptor.cs b/SourceGen/FormatDescriptor.cs index 0cf3e7c..c541981 100644 --- a/SourceGen/FormatDescriptor.cs +++ b/SourceGen/FormatDescriptor.cs @@ -60,7 +60,8 @@ namespace SourceGen { StringDci, // string terminated by flipped high bit (Dextral Char Inverted) Dense, // raw data, represented as compactly as possible - Fill // fill memory with a value + Fill, // fill memory with a value + Junk // contents of memory are not interesting } /// @@ -88,8 +89,26 @@ namespace SourceGen { // Dense; no sub-types - // Fill; default is non-ignore - Ignore // TODO(someday): use this for "don't care" sections + // Fill; no sub-types + + // Junk; data may exist for alignment purposes. Sub-type indicates boundary. + // (SubType=None indicates no alignment) + Align2, // must be consecutive ascending powers of 2 + Align4, + Align8, + Align16, + Align32, + Align64, + Align128, + Align256, + Align512, + Align1024, + Align2048, + Align4096, + Align8192, + Align16384, + Align32768, + Align65536 } // Maximum length of a NumericLE/BE item (32-bit value or 4-byte instruction). @@ -169,6 +188,7 @@ namespace SourceGen { Debug.Assert(length > 0); Debug.Assert(length <= MAX_NUMERIC_LEN || !IsNumeric); Debug.Assert(fmt != Type.Default || length == 1); + Debug.Assert(subFmt == SubType.None || (fmt != Type.Junk) ^ IsJunkSubType(subFmt)); Length = length; FormatType = fmt; @@ -200,8 +220,12 @@ namespace SourceGen { /// Length, in bytes. /// Format type. /// Format sub-type. - /// New or pre-allocated descriptor. + /// New or pre-allocated descriptor, or null if the arguments are + /// invalid. public static FormatDescriptor Create(int length, Type fmt, SubType subFmt) { + if (subFmt != SubType.None && !((fmt != Type.Junk) ^ IsJunkSubType(subFmt))) { + return null; + } DebugCreateCount++; DebugPrefabCount++; if (length == 1) { @@ -364,6 +388,41 @@ namespace SourceGen { } } + /// + /// Returns true if the sub-type is exclusively for use with the Junk type. Notably, + /// returns false for SubType.None. + /// + private static bool IsJunkSubType(SubType subType) { + return ((int)subType >= (int)SubType.Align2 && + (int)subType <= (int)SubType.Align65536); + } + + /// + /// Converts a power of 2 value to the corresponding alignment sub-type. + /// + /// Power of 2. + /// The matching sub-type, or None if nothing matches. + public static SubType PowerToAlignment(int pwr) { + if (pwr < 1 || pwr > 16) { + return SubType.None; + } + // pwr==1 --> 2^1 --> Align2 + return (SubType)((int)SubType.Align2 - 1 + pwr); + } + + /// + /// Converts an alignment sub-type to the corresponding power of 2. + /// + /// Alignment value. + /// The matching power of 2, or -1 if the sub-type isn't valid. + public static int AlignmentToPower(SubType align) { + Debug.Assert(IsJunkSubType(align)); + if ((int)align < (int)SubType.Align2 || (int)align > (int)SubType.Align65536) { + return -1; + } + return (int)align - (int)SubType.Align2 + 1; + } + /// /// Generates a string describing the format, suitable for use in the UI. /// @@ -432,6 +491,9 @@ namespace SourceGen { case Type.Fill: retstr += "Fill"; break; + case Type.Junk: + retstr += "Unaligned junk"; + break; default: // strings handled earlier retstr += "???"; @@ -469,6 +531,24 @@ namespace SourceGen { case SubType.C64Screen: retstr += "Numeric, C64 Screen"; break; + case SubType.Align2: + case SubType.Align4: + case SubType.Align8: + case SubType.Align16: + case SubType.Align32: + case SubType.Align64: + case SubType.Align128: + case SubType.Align256: + case SubType.Align512: + case SubType.Align1024: + case SubType.Align2048: + case SubType.Align4096: + case SubType.Align8192: + case SubType.Align16384: + case SubType.Align32768: + case SubType.Align65536: + retstr += "Alignment to " + (1 << AlignmentToPower(FormatSubType)); + break; default: retstr += "???"; diff --git a/SourceGen/MainController.cs b/SourceGen/MainController.cs index 311aa31..6c5b5bb 100644 --- a/SourceGen/MainController.cs +++ b/SourceGen/MainController.cs @@ -1988,10 +1988,9 @@ namespace SourceGen { TypedRangeSet.Tuple firstOffset = iter.Current; mProject.OperandFormats.TryGetValue(firstOffset.Value, out FormatDescriptor dfd); - EditDataOperand dlg = new EditDataOperand(mMainWin, mProject.FileData, - mProject.SymbolTable, mOutputFormatter, trs, dfd); - dlg.ShowDialog(); - if (dlg.DialogResult == true) { + EditDataOperand dlg = + new EditDataOperand(mMainWin, mProject, mOutputFormatter, trs, dfd); + if (dlg.ShowDialog() == true) { // Merge the changes into the OperandFormats list. We need to remove all // FormatDescriptors that overlap the selected region. We don't need to // pass the selection set in, because the dlg.Results list spans the exact diff --git a/SourceGen/ProjectFile.cs b/SourceGen/ProjectFile.cs index ccd05ee..daab0c2 100644 --- a/SourceGen/ProjectFile.cs +++ b/SourceGen/ProjectFile.cs @@ -680,6 +680,8 @@ namespace SourceGen { if (!CreateFormatDescriptor(kvp.Value, spf._ContentVersion, report, out FormatDescriptor dfd)) { + report.Add(FileLoadItem.Type.Warning, + string.Format(Res.Strings.ERR_BAD_FD_FMT, intKey)); continue; } // Extra validation: make sure dfd doesn't run off end. @@ -827,7 +829,7 @@ namespace SourceGen { } Debug.WriteLine("Found v1 string, fmt=" + format + ", sub=" + subFormat); dfd = FormatDescriptor.Create(sfd.Length, format, subFormat); - return true; + return dfd != null; } try { @@ -842,7 +844,6 @@ namespace SourceGen { subFormat = (FormatDescriptor.SubType)Enum.Parse( typeof(FormatDescriptor.SubType), sfd.SubFormat); } - } catch (ArgumentException) { report.Add(FileLoadItem.Type.Warning, Res.Strings.ERR_BAD_FD_FORMAT + ": " + sfd.Format + "/" + sfd.SubFormat); @@ -865,7 +866,7 @@ namespace SourceGen { new WeakSymbolRef(sfd.SymbolRef.Label, part), format == FormatDescriptor.Type.NumericBE); } - return true; + return dfd != null; } /// diff --git a/SourceGen/PseudoOp.cs b/SourceGen/PseudoOp.cs index 7ed9a92..0c23fb8 100644 --- a/SourceGen/PseudoOp.cs +++ b/SourceGen/PseudoOp.cs @@ -78,6 +78,8 @@ namespace SourceGen { public string DefineBigData4 { get; private set; } public string Fill { get; private set; } public string Dense { get; private set; } + public string Junk { get; private set; } + public string Align { get; private set; } public string StrGeneric { get; private set; } public string StrReverse { get; private set; } public string StrLen8 { get; private set; } @@ -125,6 +127,8 @@ namespace SourceGen { a.DefineBigData4 == b.DefineBigData4 && a.Fill == b.Fill && a.Dense == b.Dense && + a.Junk == b.Junk && + a.Align == b.Align && a.StrGeneric == b.StrGeneric && a.StrReverse == b.StrReverse && a.StrLen8 == b.StrLen8 && @@ -234,6 +238,8 @@ namespace SourceGen { { "DefineBigData4", ".dbd4" }, { "Fill", ".fill" }, { "Dense", ".bulk" }, + { "Junk", ".junk" }, + { "Align", ".align" }, { "StrGeneric", ".str" }, { "StrReverse", ".rstr" }, @@ -265,6 +271,7 @@ namespace SourceGen { case FormatDescriptor.Type.NumericLE: case FormatDescriptor.Type.NumericBE: case FormatDescriptor.Type.Fill: + case FormatDescriptor.Type.Junk: return 1; case FormatDescriptor.Type.Dense: { // no delimiter, two output bytes per input byte @@ -345,6 +352,17 @@ namespace SourceGen { po.Opcode = opNames.Fill; po.Operand = length + "," + formatter.FormatHexValue(data[offset], 2); break; + case FormatDescriptor.Type.Junk: + if (dfd.FormatSubType != FormatDescriptor.SubType.None) { + po.Opcode = opNames.Align; + int alignPow = FormatDescriptor.AlignmentToPower(dfd.FormatSubType); + po.Operand = formatter.FormatHexValue(1 << alignPow, 2) + + " (" + length.ToString() + " bytes)"; + } else { + po.Opcode = opNames.Junk; + po.Operand = length.ToString(); + } + break; case FormatDescriptor.Type.Dense: { int maxPerLine = MAX_OPERAND_LEN / 2; offset += subIndex * maxPerLine; diff --git a/SourceGen/RuntimeData/Help/editors.html b/SourceGen/RuntimeData/Help/editors.html index 395044b..dc997dc 100644 --- a/SourceGen/RuntimeData/Help/editors.html +++ b/SourceGen/RuntimeData/Help/editors.html @@ -174,7 +174,12 @@ the data file, each address will be assigned a label.

The "Bulk Data" items can represent large chunks of data compactly. The "fill" option is only available if all selected bytes have the -same value.

+same value. +If a region of bytes is irrelevant, perhaps used only as padding, you +can mark it as "junk". If it appears to be adding bytes to reach a +power-of-two address boundary, you can designate it as an alignment +directive. If you have multiple regions selected, only options that +work for all regions will be shown.

The "String" items are enabled or disabled depending on whether the data you have selected is in the appropriate format. For example, diff --git a/SourceGen/RuntimeData/Help/intro.html b/SourceGen/RuntimeData/Help/intro.html index a98258a..857f20f 100644 --- a/SourceGen/RuntimeData/Help/intro.html +++ b/SourceGen/RuntimeData/Help/intro.html @@ -808,7 +808,13 @@ absolute long load, and because it can make 65816 code easier to read.

Data and Directive Pseudo-Opcodes

-

There are only four assembler directives that appear in the code list:

+

The on-screen code list shows assembler directives that are similar +to what the various cross-assemblers provide. The actual directives +generated for a given assembler may match exactly or be totally different. +The idea is to represent the concept behind the directive, then let the +code generator figure out the implementation details.

+ +

There are six assembler directives that appear in the code list:

  • .EQ - defines a symbol's value. These are generated automatically when an operand that matches a platform or project symbol is found.
  • @@ -818,6 +824,14 @@ absolute long load, and because it can make 65816 code easier to read.

  • .RWID - specifies the width of the accumulator and index registers (65816 only). Note this doesn't change the actual width, just tells the assembler that the width has changed.
  • +
  • .JUNK - indicates that the data in a range of bytes is irrelevant. + (When generating sources, this will become .FILL or .BULK + depending on the contents of the memory region and the assembler's + capabilities.)
  • +
  • .ALIGN - a special case of .JUNK that indicates the irrelevant + bytes exist to force alignment to a memory boundary (usually a + 256-byte page). Depending on the memory contents, it may be possible + to output this as an assembler-specific alignment directive.

Every data item is represented by a pseudo-op. Some of them may @@ -826,7 +840,9 @@ represent hundreds of bytes and span multiple lines.

  • .DD1, .DD2, .DD3, .DD4 - basic "define data" op. A 1-4 byte little-endian value.
  • .DBD2, .DBD3, .DBD4 - "define big-endian data". 2-4 bytes of - big-endian data.
  • + big-endian data. (The 3- and 4-byte versions are not currently + available in the UI, since they're very unusual and few assemblers + support them.)
  • .BULK - data packed in as compact a form as the assembler allows. Useful for chunks of graphics data.
  • .FILL - a series of identical bytes. The operand diff --git a/SourceGen/SGTestData/2004-numeric-types b/SourceGen/SGTestData/2004-numeric-types index a6fae47778d5fffa03d68ffa7c2f9d48e1efe190..47e2524502e99c91732705abac5d18b216389e39 100644 GIT binary patch literal 1024 zcmYcgR1!2+5_AD#K_FAf7>HaL7#bKL7|eh&fkGn|G@KdfK(GOnGD6eJJ7xkC1)RNV z_nEuz{uAQ=e|PuH?o~58%F{wYDUG3F)nEaQ6ej7$)%XF_c*aHnD68@AT?(zFs&)XH C3~EpS delta 7 OcmZqR_|CGSoCyF6ngXo= diff --git a/SourceGen/SGTestData/2004-numeric-types.dis65 b/SourceGen/SGTestData/2004-numeric-types.dis65 index 9a98fb4..834df91 100644 --- a/SourceGen/SGTestData/2004-numeric-types.dis65 +++ b/SourceGen/SGTestData/2004-numeric-types.dis65 @@ -1,8 +1,8 @@ ### 6502bench SourceGen dis65 v1.0 ### { -"FileDataLength":631,"FileDataCrc32":-1812792607,"ProjectProps":{ -"CpuName":"6502","IncludeUndocumentedInstr":false,"EntryFlags":13566159,"AnalysisParams":{ -"AnalyzeUncategorizedData":true,"MinCharsForString":4}, +"_ContentVersion":2,"FileDataLength":1024,"FileDataCrc32":-1387500320,"ProjectProps":{ +"CpuName":"6502","IncludeUndocumentedInstr":false,"TwoByteBrk":false,"EntryFlags":13566159,"AutoLabelStyle":"Simple","AnalysisParams":{ +"AnalyzeUncategorizedData":true,"DefaultTextScanMode":"LowHighAscii","MinCharsForString":4,"SeekNearbyTargets":false,"SmartPlpHandling":true}, "PlatformSymbolFileIdentifiers":[],"ExtensionScriptFileIdentifiers":[],"ProjectSyms":{ }}, "AddressMap":[{ @@ -10,10 +10,10 @@ "Low":0,"High":0,"Hint":"Code"}],"StatusFlagOverrides":{ }, "Comments":{ -"566":"comment"}, +"566":"comment","882":"incorrect alignment"}, "LongComments":{ "-2147483647":{ -"Text":"Project file was edited to get all big-endian data types.","BoxMode":false,"MaxWidth":80}}, +"Text":"Project file was edited to get all big-endian data types, and to have an incorrect .junk alignment directive.","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}}, "Notes":{ }, "UserLabels":{ @@ -53,4 +53,20 @@ "555":{ "Length":10,"Format":"Dense","SubFormat":"None","SymbolRef":null}, "566":{ -"Length":64,"Format":"Dense","SubFormat":"None","SymbolRef":null}}} +"Length":64,"Format":"Dense","SubFormat":"None","SymbolRef":null}, +"631":{ +"Length":137,"Format":"Junk","SubFormat":"Align256","SymbolRef":null}, +"769":{ +"Length":63,"Format":"Junk","SubFormat":"Align64","SymbolRef":null}, +"833":{ +"Length":31,"Format":"Junk","SubFormat":"Align32","SymbolRef":null}, +"864":{ +"Length":8,"Format":"Junk","SubFormat":"None","SymbolRef":null}, +"873":{ +"Length":8,"Format":"Junk","SubFormat":"None","SymbolRef":null}, +"882":{ +"Length":2,"Format":"Junk","SubFormat":"Align128","SymbolRef":null}, +"884":{ +"Length":140,"Format":"Junk","SubFormat":"Align256","SymbolRef":null}}, +"LvTables":{ +}} diff --git a/SourceGen/SGTestData/Expected/2004-numeric-types_64tass.S b/SourceGen/SGTestData/Expected/2004-numeric-types_64tass.S index f310257..94b37f0 100644 --- a/SourceGen/SGTestData/Expected/2004-numeric-types_64tass.S +++ b/SourceGen/SGTestData/Expected/2004-numeric-types_64tass.S @@ -1,4 +1,5 @@ -;Project file was edited to get all big-endian data types. +;Project file was edited to get all big-endian data types, and to have an +;incorrect .junk alignment directive. .cpu "6502" * = $1000 rts @@ -31,3 +32,14 @@ LABEL .byte $00,$11,$22,$33,$44,$55,$66,$77,$88,$99,$aa,$bb,$cc,$dd,$ee,$ff .byte $00,$11,$22,$33,$44,$55,$66,$77,$88,$99,$aa,$bb,$cc,$dd,$ee,$ff .byte $ff,$ee,$dd,$cc,$bb,$aa,$99,$88,$77,$66,$55,$44,$33,$22,$11,$00 .byte $80 + .align 256,$aa + .byte $81 + .align 64,$00 + .byte $81 + .align 32,$ab + .byte $00,$00,$00,$00,$00,$00,$00,$01 + .byte $81 + .byte $10,$00,$00,$00,$00,$00,$00,$00 + .byte $81 + .fill 2,$dd ;incorrect alignment + .align 256,$00 diff --git a/SourceGen/SGTestData/Expected/2004-numeric-types_Merlin32.S b/SourceGen/SGTestData/Expected/2004-numeric-types_Merlin32.S index cd93da7..8459ca0 100644 --- a/SourceGen/SGTestData/Expected/2004-numeric-types_Merlin32.S +++ b/SourceGen/SGTestData/Expected/2004-numeric-types_Merlin32.S @@ -1,4 +1,5 @@ -;Project file was edited to get all big-endian data types. +;Project file was edited to get all big-endian data types, and to have an +;incorrect .junk alignment directive. org $1000 rts @@ -28,3 +29,14 @@ LABEL hex 00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff ;comment hex 00112233445566778899aabbccddeeffffeeddccbbaa99887766554433221100 dfb $80 + ds \,$aa + dfb $81 + ds 63,$00 + dfb $81 + ds 31,$ab + hex 0000000000000001 + dfb $81 + hex 1000000000000000 + dfb $81 + ds 2,$dd ;incorrect alignment + ds \,$00 diff --git a/SourceGen/SGTestData/Expected/2004-numeric-types_acme.S b/SourceGen/SGTestData/Expected/2004-numeric-types_acme.S index be6ed0c..16e4527 100644 --- a/SourceGen/SGTestData/Expected/2004-numeric-types_acme.S +++ b/SourceGen/SGTestData/Expected/2004-numeric-types_acme.S @@ -1,4 +1,5 @@ -;Project file was edited to get all big-endian data types. +;Project file was edited to get all big-endian data types, and to have an +;incorrect .junk alignment directive. !cpu 6502 * = $1000 rts @@ -29,3 +30,14 @@ LABEL !hex 00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff ;comment !hex 00112233445566778899aabbccddeeffffeeddccbbaa99887766554433221100 !byte $80 + !align 255,0,$aa + !byte $81 + !align 63,0,$00 + !byte $81 + !align 31,0,$ab + !hex 0000000000000001 + !byte $81 + !hex 1000000000000000 + !byte $81 + !fill 2,$dd ;incorrect alignment + !align 255,0,$00 diff --git a/SourceGen/SGTestData/Expected/2004-numeric-types_cc65.S b/SourceGen/SGTestData/Expected/2004-numeric-types_cc65.S index 202e665..68b2a35 100644 --- a/SourceGen/SGTestData/Expected/2004-numeric-types_cc65.S +++ b/SourceGen/SGTestData/Expected/2004-numeric-types_cc65.S @@ -1,4 +1,5 @@ -;Project file was edited to get all big-endian data types. +;Project file was edited to get all big-endian data types, and to have an +;incorrect .junk alignment directive. .setcpu "6502" ; .segment "SEG000" .org $1000 @@ -32,3 +33,14 @@ LABEL: .byte $00,$11,$22,$33,$44,$55,$66,$77,$88,$99,$aa,$bb,$cc,$dd,$ee,$ff .byte $00,$11,$22,$33,$44,$55,$66,$77,$88,$99,$aa,$bb,$cc,$dd,$ee,$ff .byte $ff,$ee,$dd,$cc,$bb,$aa,$99,$88,$77,$66,$55,$44,$33,$22,$11,$00 .byte $80 + .res 137,$aa + .byte $81 + .res 63,$00 + .byte $81 + .res 31,$ab + .byte $00,$00,$00,$00,$00,$00,$00,$01 + .byte $81 + .byte $10,$00,$00,$00,$00,$00,$00,$00 + .byte $81 + .res 2,$dd ;incorrect alignment + .res 140,$00 diff --git a/SourceGen/SGTestData/Expected/2004-numeric-types_cc65.cfg b/SourceGen/SGTestData/Expected/2004-numeric-types_cc65.cfg index 98389a6..a642b3c 100644 --- a/SourceGen/SGTestData/Expected/2004-numeric-types_cc65.cfg +++ b/SourceGen/SGTestData/Expected/2004-numeric-types_cc65.cfg @@ -1,7 +1,7 @@ # 6502bench SourceGen generated linker script for 2004-numeric-types MEMORY { MAIN: file=%O, start=%S, size=65536; -# MEM000: file=%O, start=$1000, size=631; +# MEM000: file=%O, start=$1000, size=1024; } SEGMENTS { CODE: load=MAIN, type=rw; diff --git a/SourceGen/SGTestData/Source/2004-numeric-types.S b/SourceGen/SGTestData/Source/2004-numeric-types.S index 78058a6..f990582 100644 --- a/SourceGen/SGTestData/Source/2004-numeric-types.S +++ b/SourceGen/SGTestData/Source/2004-numeric-types.S @@ -15,26 +15,38 @@ dfb $11,$22,$33 ;.dbd3 dfb $11,$22,$33,$44 ;.dbd4 - ds 2 + ds 2 ;.fill dfb $80 - ds 3 + ds 3 ;.fill dfb $80 - ds 4 + ds 4 ;.fill dfb $80 - ds 5 + ds 5 ;.fill dfb $80 - ds 256 + ds 256 ;.fill dfb $80 - ds 257,$cc + ds 257,$cc ;.fill - hex 11 + hex 11 ;.bulk dfb $80 - hex 11223344556677889900 + hex 11223344556677889900 ;.bulk dfb $80 - hex 00112233445566778899aabbccddeeff - hex 00112233445566778899aabbccddeeff + hex 00112233445566778899aabbccddeeff ;4 lines .bulk + hex 00112233445566778899aabbccddeeff ;add a comment hex 00112233445566778899aabbccddeeff hex ffeeddccbbaa99887766554433221100 dfb $80 +; align to 256-byte boundary + ds \,$aa ;.junk, align 256 + dfb $81 + ds 63,$00 ;.junk, align 64 + dfb $81 + ds 31,$ab ;.junk, align 32 + hex 0000000000000001 ;.junk (should become .dense) + dfb $81 + hex 1000000000000000 ;.junk (should become .dense) + dfb $81 + hex dddd ;EDIT FILE: give this a bogus alignment + ds \,$00 ;.junk, align 256 diff --git a/SourceGen/WpfGui/EditAppSettings.xaml b/SourceGen/WpfGui/EditAppSettings.xaml index c67de58..d09c3de 100644 --- a/SourceGen/WpfGui/EditAppSettings.xaml +++ b/SourceGen/WpfGui/EditAppSettings.xaml @@ -616,6 +616,7 @@ limitations under the License. + @@ -673,63 +674,77 @@ limitations under the License. FontFamily="{StaticResource GeneralMonoFont}"/> - + - - - - - - - - - - - - - - - - - + + + + + + + + + + diff --git a/SourceGen/WpfGui/EditAppSettings.xaml.cs b/SourceGen/WpfGui/EditAppSettings.xaml.cs index 6b5d36c..67abbe5 100644 --- a/SourceGen/WpfGui/EditAppSettings.xaml.cs +++ b/SourceGen/WpfGui/EditAppSettings.xaml.cs @@ -1099,6 +1099,8 @@ namespace SourceGen.WpfGui { new TextBoxPropertyMap(defineBigData2TextBox, "DefineBigData2"), new TextBoxPropertyMap(fillTextBox, "Fill"), new TextBoxPropertyMap(denseTextBox, "Dense"), + new TextBoxPropertyMap(junkTextBox, "Junk"), + new TextBoxPropertyMap(alignTextBox, "Align"), new TextBoxPropertyMap(strGenericTextBox, "StrGeneric"), new TextBoxPropertyMap(strReverseTextBox, "StrReverse"), new TextBoxPropertyMap(strLen8TextBox, "StrLen8"), diff --git a/SourceGen/WpfGui/EditDataOperand.xaml b/SourceGen/WpfGui/EditDataOperand.xaml index 93c4943..f2e49d2 100644 --- a/SourceGen/WpfGui/EditDataOperand.xaml +++ b/SourceGen/WpfGui/EditDataOperand.xaml @@ -39,6 +39,9 @@ limitations under the License. Strings prefixed with 8-bit length ({0}) Strings prefixed with 16-bit length ({0}) Dextral character inverted ({0}) + + arbitrary boundary + {0}-byte boundary ({1}) @@ -125,8 +128,14 @@ limitations under the License. - + + + + diff --git a/SourceGen/WpfGui/EditDataOperand.xaml.cs b/SourceGen/WpfGui/EditDataOperand.xaml.cs index 1663676..1487f8f 100644 --- a/SourceGen/WpfGui/EditDataOperand.xaml.cs +++ b/SourceGen/WpfGui/EditDataOperand.xaml.cs @@ -74,6 +74,11 @@ namespace SourceGen.WpfGui { ///
  • private SymbolTable mSymbolTable; + /// + /// Map of offsets to addresses. + /// + private AddressMap mAddrMap; + /// /// Formatter to use when displaying addresses and hex values. /// @@ -100,6 +105,17 @@ namespace SourceGen.WpfGui { } public StringEncodingItem[] StringEncodingItems { get; private set; } + public class JunkAlignmentItem { + public string Description { get; private set; } + public FormatDescriptor.SubType FormatSubType { get; private set; } + + public JunkAlignmentItem(string descr, FormatDescriptor.SubType subFmt) { + Description = descr; + FormatSubType = subFmt; + } + } + public List JunkAlignmentItems { get; private set; } + // INotifyPropertyChanged implementation public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged([CallerMemberName] string propertyName = "") { @@ -107,14 +123,15 @@ namespace SourceGen.WpfGui { } - public EditDataOperand(Window owner, byte[] fileData, SymbolTable symbolTable, + public EditDataOperand(Window owner, DisasmProject project, Asm65.Formatter formatter, TypedRangeSet trs, FormatDescriptor firstDesc) { InitializeComponent(); Owner = owner; DataContext = this; - mFileData = fileData; - mSymbolTable = symbolTable; + mFileData = project.FileData; + mSymbolTable = project.SymbolTable; + mAddrMap = project.AddrMap; mFormatter = formatter; mSelection = trs; mFirstFormatDescriptor = firstDesc; @@ -129,6 +146,40 @@ namespace SourceGen.WpfGui { new StringEncodingItem(Res.Strings.SCAN_C64_SCREEN_CODE, TextScanMode.C64ScreenCode), }; + + GetMinMaxAlignment(out FormatDescriptor.SubType min, out FormatDescriptor.SubType max); + //Debug.WriteLine("ALIGN: min=" + min + " max=" + max); + Debug.Assert(min == FormatDescriptor.SubType.None ^ // both or neither are None + max != FormatDescriptor.SubType.None); + + int junkSel = 0; + string noAlign = (string)FindResource("str_AlignmentNone"); + string alignFmt = (string)FindResource("str_AlignmentItemFmt"); + JunkAlignmentItems = new List(); + JunkAlignmentItems.Add(new JunkAlignmentItem(noAlign, FormatDescriptor.SubType.None)); + if (min != FormatDescriptor.SubType.None) { + int index = 1; + // We assume the enum values are consecutive and ascending. + FormatDescriptor.SubType end = (FormatDescriptor.SubType)(((int)max) + 1); + while (min != end) { + int pwr = FormatDescriptor.AlignmentToPower(min); + string endStr = mFormatter.FormatHexValue(1 << pwr, 4); + JunkAlignmentItems.Add(new JunkAlignmentItem( + string.Format(alignFmt, 1 << pwr, endStr), min)); + + // See if this matches previous value. + if (mFirstFormatDescriptor != null && + mFirstFormatDescriptor.FormatType == FormatDescriptor.Type.Junk && + mFirstFormatDescriptor.FormatSubType == min) { + junkSel = index; + } + + // Advance. + min = (FormatDescriptor.SubType)(((int)min) + 1); + index++; + } + } + junkAlignComboBox.SelectedIndex = junkSel; } private void Window_Loaded(object sender, RoutedEventArgs e) { @@ -212,7 +263,8 @@ namespace SourceGen.WpfGui { stringEncodingComboBox.SelectedItem = choice; } - private void StringEncodingComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) { + private void StringEncodingComboBox_SelectionChanged(object sender, + SelectionChangedEventArgs e) { if (!IsLoaded) { return; } @@ -287,6 +339,9 @@ namespace SourceGen.WpfGui { symbolEntryTextBox.Focus(); } + // Disable the alignment pop-up unless Junk is selected. + junkAlignComboBox.IsEnabled = (radioJunk.IsChecked == true); + bool isOk = true; if (radioSimpleDataSymbolic.IsChecked == true) { // Just check for correct format. References to non-existent labels are allowed. @@ -302,6 +357,71 @@ namespace SourceGen.WpfGui { #region Setup + /// + /// Determines the minimum and maximum alignment values, based on the sizes of the + /// regions and the address they end on. + /// + /// Minimum allowed format, or None. + /// Maximum allowed format, or None. + private void GetMinMaxAlignment(out FormatDescriptor.SubType min, + out FormatDescriptor.SubType max) { + min = max = FormatDescriptor.SubType.None; + + int maxLenPow = -1; + int minAlignPow = 65535; + + IEnumerator iter = mSelection.RangeListIterator; + while (iter.MoveNext()) { + TypedRangeSet.TypedRange rng = iter.Current; + int length = rng.High - rng.Low + 1; + Debug.Assert(length > 0); + + // The goal is to find an instruction that fills an entire region with zeroes + // or junk bytes for the sole purpose of ending at a specific boundary. + // + // If we have a 100-byte region that ends at address $103f (inclusive), it + // can't be the result of an assembler alignment directive. "align $40" would + // have stopped at $1000, "align $80" would have continued on to $107f. + // + // Alignment of junk whose last byte $103f could be due to Align2, Align4 (1-3 + // bytes at $103d/e/f), Align8 (1-7 bytes at $1039-f), and so on, up to Align64. + // The size of the buffer determines the minimum value, the end address + // determines the maximum. + // + // Bear in mind that assembler alignment directives will do nothing if the + // address is already aligned: Align256 at $1000 generates no output. So we + // cannot use Align8 on a buffer of length 8. + + // Count the trailing 1 bits in the address. This gets us the power of 2 + // alignment value. Note alignPow will be zero if the last byte is stored at + // an even address. + int endAddress = mAddrMap.OffsetToAddress(rng.High) & 0x0000ffff; + int alignPow = BitTwiddle.CountTrailingZeroes(~endAddress); + + // Round length up to next highest power of 2, and compute Log2(). Unfortunately + // .NET Standard 2.0 doesn't have Math.Log2(). Note we want the next-highest + // even if it's already a power of 2. + int lenRound = BitTwiddle.NextHighestPowerOf2(length); + int lenPow = BitTwiddle.CountTrailingZeroes(lenRound); + Debug.Assert(lenPow > 0); // length==1 -> lenRound=2 --> lenPow=1 + + // Want the biggest minimum value and the smallest maximum value. + if (maxLenPow < lenPow) { + maxLenPow = lenPow; + } + if (minAlignPow > alignPow) { + minAlignPow = alignPow; + } + + if (maxLenPow > minAlignPow) { + return; + } + } + + min = FormatDescriptor.PowerToAlignment(maxLenPow); + max = FormatDescriptor.PowerToAlignment(minAlignPow); + } + /// /// Analyzes the selection to see which data formatting options are suitable. /// Disables radio buttons and updates labels. @@ -733,6 +853,9 @@ namespace SourceGen.WpfGui { case FormatDescriptor.Type.Fill: preferredFormat = radioFill; break; + case FormatDescriptor.Type.Junk: + preferredFormat = radioJunk; + break; default: // Should not be here. Debug.Assert(false); @@ -875,6 +998,10 @@ namespace SourceGen.WpfGui { type = FormatDescriptor.Type.Dense; } else if (radioFill.IsChecked == true) { type = FormatDescriptor.Type.Fill; + } else if (radioJunk.IsChecked == true) { + type = FormatDescriptor.Type.Junk; + JunkAlignmentItem comboItem = (JunkAlignmentItem)junkAlignComboBox.SelectedItem; + subType = comboItem.FormatSubType; } else if (radioStringMixed.IsChecked == true) { type = FormatDescriptor.Type.StringGeneric; subType = charSubType;