1
0
mirror of https://github.com/fadden/6502bench.git synced 2025-01-30 06:32:24 +00:00

Progress toward 64tass expression support

Gave cc65 its own expression generator, as the precedence table seems
atypical if not unique.  Configured 64tass to use the "simple"
expression mode.

Added some operations on a 32-bit constant to 2007-labels-and-symbols
to exercise the current worst-case expression (shift + AND + add).
Tweaked the Merlin expression generator to handle it.

(issue #16)
This commit is contained in:
Andy McFadden 2018-10-24 13:17:03 -07:00
parent f26a03869a
commit 61914c8f79
12 changed files with 203 additions and 39 deletions

View File

@ -54,6 +54,7 @@ namespace Asm65 {
public bool mUpperOperandA; // display acc operand in upper case?
public bool mUpperOperandS; // display stack operand in upper case?
public bool mUpperOperandXY; // display index register operand in upper case?
public bool mBankSelectBackQuote; // use '`' rather than '^' for bank select?
public bool mAddSpaceLongComment; // insert space after delimiter for long comments?
// functional changes to assembly output
@ -79,7 +80,8 @@ namespace Asm65 {
public enum CharConvMode { Unknown = 0, PlainAscii, HighLowAscii };
public CharConvMode mHexDumpCharConvMode; // character conversion mode for dumps
public enum ExpressionMode { Unknown = 0, Simple, Merlin };
// Hopefully we don't need a separate mode for every assembler in existence.
public enum ExpressionMode { Unknown = 0, Simple, Cc65, Merlin };
public ExpressionMode mExpressionMode; // symbol rendering mode
// Deserialization helper.
@ -601,22 +603,6 @@ namespace Asm65 {
return fmt;
}
/// <summary>
/// Formats a pseudo-opcode.
/// </summary>
/// <param name="opstr">Pseudo-op string to format.</param>
/// <returns>Formatted string.</returns>
public string FormatPseudoOp(string opstr) {
if (!mPseudoOpStrings.TryGetValue(opstr, out string result)) {
if (mFormatConfig.mUpperPseudoOpcodes) {
result = mPseudoOpStrings[opstr] = opstr.ToUpperInvariant();
} else {
result = mPseudoOpStrings[opstr] = opstr;
}
}
return result;
}
/// <summary>
/// Formats the instruction operand.
/// </summary>
@ -634,6 +620,22 @@ namespace Asm65 {
return string.Format(format, contents);
}
/// <summary>
/// Formats a pseudo-opcode.
/// </summary>
/// <param name="opstr">Pseudo-op string to format.</param>
/// <returns>Formatted string.</returns>
public string FormatPseudoOp(string opstr) {
if (!mPseudoOpStrings.TryGetValue(opstr, out string result)) {
if (mFormatConfig.mUpperPseudoOpcodes) {
result = mPseudoOpStrings[opstr] = opstr.ToUpperInvariant();
} else {
result = mPseudoOpStrings[opstr] = opstr;
}
}
return result;
}
/// <summary>
/// Generates a format string for N hex bytes.
/// </summary>

View File

@ -611,7 +611,7 @@ namespace SourceGen.AppForms {
private void shiftAfterAdjustCheckBox_CheckedChanged(object sender, EventArgs e) {
string mode = useMerlinExpressions.Checked ?
Asm65.Formatter.FormatConfig.ExpressionMode.Merlin.ToString() :
Asm65.Formatter.FormatConfig.ExpressionMode.Simple.ToString();
Asm65.Formatter.FormatConfig.ExpressionMode.Cc65.ToString();
mSettings.SetString(AppSettings.FMT_EXPRESSION_MODE, mode);
SetDirty(true);
}

View File

@ -188,7 +188,7 @@ namespace SourceGen.AsmGen {
config.mFullLineCommentDelimiterBase = ";";
config.mBoxLineCommentDelimiter = ";";
config.mAllowHighAsciiCharConst = false;
config.mExpressionMode = Formatter.FormatConfig.ExpressionMode.Simple;
config.mExpressionMode = Formatter.FormatConfig.ExpressionMode.Cc65;
}
// IGenerator

View File

@ -168,6 +168,15 @@ namespace SourceGen.AsmGen {
/// Configures the assembler-specific format items.
/// </summary>
private void SetFormatConfigValues(ref Formatter.FormatConfig config) {
// Must be lower case when --case-sensitive is used.
config.mUpperOpcodes = false;
config.mUpperPseudoOpcodes = false;
config.mUpperOperandA = false;
config.mUpperOperandS = false;
config.mUpperOperandXY = false;
config.mBankSelectBackQuote = true;
config.mForceAbsOpcodeSuffix = string.Empty;
config.mForceLongOpcodeSuffix = string.Empty;
config.mForceAbsOperandPrefix = "@w"; // word
@ -176,11 +185,6 @@ namespace SourceGen.AsmGen {
config.mFullLineCommentDelimiterBase = ";";
config.mBoxLineCommentDelimiter = ";";
config.mAllowHighAsciiCharConst = false;
config.mUpperOpcodes = false;
config.mUpperPseudoOpcodes = false;
config.mUpperOperandA = false;
config.mUpperOperandS = false;
config.mUpperOperandXY = false;
config.mExpressionMode = Formatter.FormatConfig.ExpressionMode.Simple;
}

View File

@ -202,7 +202,8 @@ namespace SourceGen.AsmGen {
operandForSymbol = attr.OperandAddress;
}
// Check Length to watch for bogus descriptors (?)
// 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) {
// Format operand as directed.
if (op.AddrMode == OpDef.AddressMode.BlockMove) {

View File

@ -541,6 +541,10 @@ namespace SourceGen {
FormatNumericSymbolSimple(formatter, sym, labelMap,
dfd, operandValue, operandLen, isPcRel, sb);
break;
case Formatter.FormatConfig.ExpressionMode.Cc65:
FormatNumericSymbolCc65(formatter, sym, labelMap,
dfd, operandValue, operandLen, isPcRel, sb);
break;
case Formatter.FormatConfig.ExpressionMode.Merlin:
FormatNumericSymbolMerlin(formatter, sym, labelMap,
dfd, operandValue, operandLen, isPcRel, sb);
@ -584,7 +588,11 @@ namespace SourceGen {
string selOp;
if (dfd.SymbolRef.ValuePart == WeakSymbolRef.Part.Bank) {
symbolValue = (sym.Value >> 16) & 0xff;
selOp = "^";
if (formatter.Config.mBankSelectBackQuote) {
selOp = "`";
} else {
selOp = "^";
}
} else if (dfd.SymbolRef.ValuePart == WeakSymbolRef.Part.High) {
symbolValue = (sym.Value >> 8) & 0xff;
selOp = ">";
@ -651,16 +659,113 @@ namespace SourceGen {
sb.Append(formatter.FormatAdjustment(adjustment));
}
/// <summary>
/// Format the symbol and adjustment using cc65 expression syntax.
/// </summary>
private static void FormatNumericSymbolCc65(Formatter formatter, Symbol sym,
Dictionary<string, string> labelMap, FormatDescriptor dfd,
int operandValue, int operandLen, bool isPcRel, StringBuilder sb) {
// The key difference between cc65 and other assemblers with general expressions
// is that the bitwise shift and AND operators have higher precedence than the
// arithmetic ops like add and subtract. (The bitwise ops are equal to multiply
// and divide.) This means that, if we want to mask off the low 16 bits and add one
// to a label, we can write "start & $ffff + 1" rather than "(start & $ffff) + 1".
//
// This is particularly convenient for PEA, since "PEA (start & $ffff)" looks like
// we're trying to use a (non-existent) indirect form of PEA. We can write things
// in a simpler way.
int adjustment, symbolValue;
string symLabel = sym.Label;
if (labelMap != null && labelMap.TryGetValue(symLabel, out string newLabel)) {
symLabel = newLabel;
}
if (operandLen == 1) {
// Use the byte-selection operator to get the right piece.
string selOp;
if (dfd.SymbolRef.ValuePart == WeakSymbolRef.Part.Bank) {
symbolValue = (sym.Value >> 16) & 0xff;
selOp = "^";
} else if (dfd.SymbolRef.ValuePart == WeakSymbolRef.Part.High) {
symbolValue = (sym.Value >> 8) & 0xff;
selOp = ">";
} else {
symbolValue = sym.Value & 0xff;
if (symbolValue == sym.Value) {
selOp = string.Empty;
} else {
selOp = "<";
}
}
sb.Append(selOp);
sb.Append(symLabel);
operandValue &= 0xff;
} else if (operandLen <= 4) {
// Operands and values should be 8/16/24 bit unsigned quantities. 32-bit
// support is really there so you can have a 24-bit pointer in a 32-bit hole.
// Might need to adjust this if 32-bit signed quantities become interesting.
uint mask = 0xffffffff >> ((4 - operandLen) * 8);
string shOp;
if (dfd.SymbolRef.ValuePart == WeakSymbolRef.Part.Bank) {
symbolValue = (sym.Value >> 16);
shOp = " >> 16";
} else if (dfd.SymbolRef.ValuePart == WeakSymbolRef.Part.High) {
symbolValue = (sym.Value >> 8);
shOp = " >> 8";
} else {
symbolValue = sym.Value;
shOp = "";
}
if (isPcRel) {
// PC-relative operands are funny, because an 8- or 16-bit value is always
// expanded to 24 bits. We output a 16-bit value that the assembler will
// convert back to 8-bit or 16-bit. In any event, the bank byte is never
// relevant to our computations.
operandValue &= 0xffff;
symbolValue &= 0xffff;
}
sb.Append(symLabel);
sb.Append(shOp);
if (symbolValue > mask) {
// Post-shift value won't fit in an operand-size box.
symbolValue = (int)(symbolValue & mask);
sb.Append(" & ");
sb.Append(formatter.FormatHexValue((int)mask, 2));
}
if (sb.Length != symLabel.Length) {
sb.Append(' ');
}
operandValue = (int)(operandValue & mask);
} else {
Debug.Assert(false, "bad numeric len");
sb.Append("?????");
symbolValue = 0;
}
adjustment = operandValue - symbolValue;
sb.Append(formatter.FormatAdjustment(adjustment));
}
/// <summary>
/// Format the symbol and adjustment using Merlin expression syntax.
/// </summary>
private static void FormatNumericSymbolMerlin(Formatter formatter, Symbol sym,
Dictionary<string, string> labelMap, FormatDescriptor dfd,
int operandValue, int operandLen, bool isPcRel, StringBuilder sb) {
// Merlin expressions are compatible with the original 8-bit Merlin. They're
// evaluated from left to right, with (almost) no regard for operator precedence.
//
// The part-selection operators differ from "simple" in two ways:
// (1) They always happen last. If FOO=$10f0, "#>FOO+$18" == $11. (Strangely,
// all other operators are evaluated from left to right, with no concept
// of operator precedence.)
// (1) They always happen last. If FOO=$10f0, "#>FOO+$18" == $11. One of the
// few cases where left-to-right evaluation is overridden.
// (2) They select words, not bytes. If FOO=$123456, "#>FOO" is $1234. This is
// best thought of as a shift operator, rather than byte-selection. For
// 8-bit code this doesn't matter.
@ -676,13 +781,13 @@ namespace SourceGen {
// If we add or subtract an adjustment, it will be done on the full value, which
// is then shifted to the appropriate part. So we need to left-shift the operand
// value to match. We fill in the low bytes with the contes of the symbol, so
// value to match. We fill in the low bytes with the contents of the symbol, so
// that the adjustment doesn't include unnecessary values. (For example, let
// FOO=$10f0, with operand "#>FOO" ($10). We shift the operand to get $1000, then
// OR in the low byte to get $10f0, so that when we subtract we get adjustment==0.)
int adjOperand, keepLen;
if (dfd.SymbolRef.ValuePart == WeakSymbolRef.Part.Bank) {
adjOperand = operandValue << 16 | (sym.Value & 0xffff);
adjOperand = operandValue << 16 | (int)(sym.Value & 0xff00ffff);
keepLen = 3;
} else if (dfd.SymbolRef.ValuePart == WeakSymbolRef.Part.High) {
adjOperand = (operandValue << 8) | (sym.Value & 0xff);
@ -696,7 +801,7 @@ namespace SourceGen {
adjustment = adjOperand - sym.Value;
if (keepLen == 1) {
adjustment %= 256;
// Adjust for aesthetics. The assembler implicitly appiles a modulo operation,
// Adjust for aesthetics. The assembler implicitly applies a modulo operation,
// so we can use the value closest to zero.
if (adjustment > 127) {
adjustment = -(256 - adjustment) /*% 256*/;

View File

@ -198,7 +198,7 @@ code, but also needs to know how to handle the corner cases.</p>
left to right, with no operator precedence.</li>
<li>The byte selection operators ('&lt;', '&gt;', '^') are actually
word-selection operators, yielding 16-bit values when wide registers
are enabled on the 65816.</p>
are enabled on the 65816.</li>
<li>The assembler tracks register widths when it sees SEP/REP instructions,
but doesn't attempt to track the emulation flag. So if the registers
are long when you switch to emulation, incorrect code is generated.

View File

@ -1,6 +1,6 @@
### 6502bench SourceGen dis65 v1.0 ###
{
"_ContentVersion":1,"FileDataLength":678,"FileDataCrc32":1825804405,"ProjectProps":{
"_ContentVersion":1,"FileDataLength":695,"FileDataCrc32":1242388944,"ProjectProps":{
"CpuName":"65816","IncludeUndocumentedInstr":false,"EntryFlags":33489103,"AnalysisParams":{
"AnalyzeUncategorizedData":true,"MinCharsForString":4,"SeekNearbyTargets":true},
"PlatformSymbolFileIdentifiers":["PROJ:2007-labels-and-symbols.sym65","RT:TestData/TestSyms.sym65"],"ExtensionScriptFileIdentifiers":[],"ProjectSyms":{
@ -19,7 +19,11 @@
"projover":{
"DataDescriptor":{
"Length":1,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null},
"Comment":"replaces platform symbol","Label":"projover","Value":16384,"Source":"Project","Type":"ExternalAddr"}}},
"Comment":"replaces platform symbol","Label":"projover","Value":16384,"Source":"Project","Type":"ExternalAddr"},
"thirty2":{
"DataDescriptor":{
"Length":1,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null},
"Comment":"32-bit constant test","Label":"thirty2","Value":305419896,"Source":"Project","Type":"Constant"}}},
"AddressMap":[{
"Offset":0,"Addr":74565},
{
@ -30,7 +34,7 @@
"397":"bulky","509":"stringy"},
"LongComments":{
"-2147483647":{
"Text":"Project was edited to add a label in the middle of a dense hex region, and add a duplicate label.","BoxMode":false,"MaxWidth":80}},
"Text":"Project was edited to add a label in the middle of a dense hex region, and add a duplicate label.","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}},
"Notes":{
},
"UserLabels":{
@ -381,4 +385,22 @@
"Length":3,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{
"Label":"badmid","Part":"Low"}},
"397":{
"Length":112,"Format":"Dense","SubFormat":"None","SymbolRef":null}}}
"Length":112,"Format":"Dense","SubFormat":"None","SymbolRef":null},
"677":{
"Length":2,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{
"Label":"thirty2","Part":"Low"}},
"679":{
"Length":2,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{
"Label":"thirty2","Part":"High"}},
"681":{
"Length":2,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{
"Label":"thirty2","Part":"Bank"}},
"685":{
"Length":3,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{
"Label":"thirty2","Part":"Low"}},
"688":{
"Length":3,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{
"Label":"thirty2","Part":"High"}},
"691":{
"Length":3,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{
"Label":"thirty2","Part":"Bank"}}}}

View File

@ -6,6 +6,7 @@ plataddr equ $3000 ;address only in platform file
projalsa equ $3200 ;same val as projalso
absh equ $feed
biggie equ $123456
thirty2 equ $12345678 ;32-bit constant test
org $012345
start clc
@ -186,5 +187,13 @@ string asc 'This is a long string. Put a label and comment on it to confir'
asc 'm that the label and comment only appear on the first line. T'
asc 'he quick brown fox jumps over the lazy dogs.'
L118E rts
L118E lda #<thirty2+2
lda #>thirty2+768
lda #^thirty2
rep #$30
mx %00
lda #thirty2+3
lda #>thirty2+1024
lda #^thirty2
rts

View File

@ -7,6 +7,7 @@ plataddr = $3000 ;address only in platform file
projalsa = $3200 ;same val as projalso
absh = $feed
biggie = $123456
thirty2 = $12345678 ;32-bit constant test
.org $012345
.a8
@ -194,5 +195,14 @@ string: .byte "This is a long string. Put a label and comment on it to confir
.byte "m that the label and comment only appear on the first line. T"
.byte "he quick brown fox jumps over the lazy dogs."
L118E: rts
L118E: lda #<thirty2+2
lda #>thirty2+3
lda #^thirty2
rep #$30
.a16
.i16
lda #thirty2 & $ffff +3
lda #thirty2 >> 8 & $ffff +4
lda #thirty2 >> 16
rts

View File

@ -7,6 +7,7 @@ absl equ $1029
absh equ $feed
zip equ $cd
biggie equ $123456
thirty2 equ $12345678
org $012345
@ -217,5 +218,15 @@ t4c jml target2
:skiphex
; extract bytes from 32-bit value with short regs
lda #<thirty2 + 2
lda #>thirty2 + 768
lda #^thirty2
rep #$30
mx %00
lda #<thirty2 + 3
lda #>thirty2 + 1024
lda #^thirty2
rts