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.
-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 a6fae47..47e2524 100644
Binary files a/SourceGen/SGTestData/2004-numeric-types and b/SourceGen/SGTestData/2004-numeric-types differ
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;