1
0
mirror of https://github.com/fadden/6502bench.git synced 2024-11-29 10:50:28 +00:00

Fix some 65816 code generation issues

Two basic problems:

(1) cc65, being a one-pass assembler, can't tell if a forward-referenced
label is 16-bit or 24-bit.  If the operand is potentially ambiguous,
such as "LDA label", we need to add an operand width disambiguator.
(The existing tests managed to only do backward references.)

(2) 64tass wants the labels on JMP/JSR absolute operands to have 24-bit
values that match the current program bank.  This is the opposite of
cc65, which requires 16-bit values.  We need to distinguish PBR vs.
DBR instructions (i.e. "LDA abs" vs. "JMP abs") and handle them
differently when formatting for "Common".

Merlin32 doesn't care, and ACME doesn't work at all, so neither of
those needed updating.

The 20052-branches-and-banks test was expanded to cover the problematic
cases.
This commit is contained in:
Andy McFadden 2020-07-01 17:59:12 -07:00
parent d979571880
commit fdd2bcf847
13 changed files with 459 additions and 17 deletions

View File

@ -252,6 +252,32 @@ namespace Asm65 {
}
}
/// <summary>
/// True if this is an absolute-address instruction whose operand is combined with
/// the Program Bank Register.
/// </summary>
/// <remarks>
/// As noted in Eyes & Lichty, the Absolute addressing mode uses the Data Bank Register
/// if locating data, or the Program Bank Register if transferring control. When
/// we "LDA symbol" we can only care about the 16-bit value because we can't know what B
/// will hold at run time. When we "JMP symbol" we can either (1) complain if it's in
/// a different bank, (2) complain if a bank is specified at all, or (3) just not care.
/// All three approaches are in use.
///
/// This call is a way to know that the instruction is merged with the PBR rather than
/// the DBR.
/// </remarks>
public bool IsAbsolutePBR {
get {
// Too lazy to add new field. Set for:
// JSR abs
// JMP abs
// JMP (abs,X)
// JSR (abs,X)
return Opcode == 0x20 || Opcode == 0x4c || Opcode == 0x7c || Opcode == 0xfc;
}
}
/// <summary>
/// True if the operand's width is uniquely determined by the opcode mnemonic, even
/// if the operation supports operands with varying widths.

View File

@ -201,6 +201,14 @@ namespace SourceGen.AsmGen {
wdis = OpDef.WidthDisambiguation.ForceDirect;
}
}
if (wdis == OpDef.WidthDisambiguation.ForceLongMaybe &&
gen.Quirks.SinglePassAssembler &&
IsForwardLabelReference(gen, offset)) {
// Assemblers like cc65 can't tell if a symbol reference is Absolute or
// Long if they haven't seen the symbol yet. Irrelevant for ACME, which
// doesn't currently handle 65816 outside bank 0.
wdis = OpDef.WidthDisambiguation.ForceLong;
}
string opcodeStr = formatter.FormatOpcode(op, wdis);
@ -228,6 +236,9 @@ namespace SourceGen.AsmGen {
int branchDist = attr.Address - attr.OperandAddress;
isPcRelBankWrap = branchDist > 32767 || branchDist < -32768;
}
if (op.IsAbsolutePBR) {
opFlags |= PseudoOp.FormatNumericOpFlags.IsAbsolutePBR;
}
// 16-bit operands outside bank 0 need to include the bank when computing
// symbol adjustment.

View File

@ -1240,6 +1240,9 @@ namespace SourceGen {
op.AddrMode == OpDef.AddressMode.ImmLongXY) {
opFlags = PseudoOp.FormatNumericOpFlags.HasHashPrefix;
}
if (op.IsAbsolutePBR) {
opFlags |= PseudoOp.FormatNumericOpFlags.IsAbsolutePBR;
}
// Use the OperandAddress when available. This is important for relative branch
// instructions and PER, where we want to show the target address rather than the

View File

@ -1160,7 +1160,7 @@ namespace SourceGen {
/// <param name="dataPathName">Full path to file.</param>
/// <param name="proj">Project object.</param>
/// <param name="cancel">Returns true if we want to cancel the attempt.</param>
/// <returns></returns>
/// <returns>File data.</returns>
private byte[] FindValidDataFile(ref string dataPathName, DisasmProject proj,
out bool cancel) {
FileInfo fi = new FileInfo(dataPathName);

View File

@ -558,8 +558,9 @@ namespace SourceGen {
public enum FormatNumericOpFlags {
None = 0,
IsPcRel = 1, // opcode is PC relative, e.g. branch or PER
HasHashPrefix = 1 << 1, // operand has a leading '#', reducing ambiguity
OmitLabelPrefixSuffix = 1 << 2, // don't show annotation char or non-unique prefix
IsAbsolutePBR = 1 << 1, // operand implicitly uses 'K' on 65816 (JMP/JSR)
HasHashPrefix = 1 << 2, // operand has a leading '#', reducing ambiguity
OmitLabelPrefixSuffix = 1 << 3, // don't show annotation char or non-unique prefix
}
/// <summary>
@ -786,6 +787,20 @@ namespace SourceGen {
symbolValue &= 0xffff;
}
if ((flags & FormatNumericOpFlags.IsAbsolutePBR) != 0) {
if ((operandValue & 0x00ff0000) == (symbolValue & 0x00ff0000)) {
// JMP or JSR to something within the same bank. We don't need to
// mask the value.
symbolValue &= 0xffff;
} else {
// This is an absolute JMP/JSR to an out-of-bank location, which is
// bogus and should probably be prevented at a higher level. We handle
// it by altering the mask so an adjustment that covers the difference
// in bank values is generated.
mask = 0x00ffffff;
}
}
bool needMask = false;
if (symbolValue > mask) {
// Post-shift value won't fit in an operand-size box.

View File

@ -1,8 +1,8 @@
### 6502bench SourceGen dis65 v1.0 ###
{
"_ContentVersion":3,
"FileDataLength":83,
"FileDataCrc32":1678697595,
"FileDataLength":202,
"FileDataCrc32":530517490,
"ProjectProps":{
"CpuName":"65816",
"IncludeUndocumentedInstr":false,
@ -35,7 +35,11 @@
{
"Offset":54,
"Addr":8192}],
"Addr":8192},
{
"Offset":86,
"Addr":5517840}],
"TypeHints":[{
"Low":0,
"High":0,
@ -79,6 +83,48 @@
"Value":8210,
"Source":"User",
"Type":"GlobalAddr",
"LabelAnno":"None"},
"68":{
"Label":"skip",
"Value":8206,
"Source":"User",
"Type":"GlobalAddr",
"LabelAnno":"None"},
"86":{
"Label":"bank54",
"Value":5517840,
"Source":"User",
"Type":"GlobalAddr",
"LabelAnno":"None"},
"92":{
"Label":"backchk",
"Value":5517846,
"Source":"User",
"Type":"GlobalAddr",
"LabelAnno":"None"},
"195":{
"Label":"fwdchk",
"Value":5517949,
"Source":"User",
"Type":"GlobalAddr",
"LabelAnno":"None"},
"192":{
"Label":"fwdval",
"Value":5517946,
"Source":"User",
"Type":"GlobalAddr",
"LabelAnno":"None"},
"95":{
"Label":"backval",
"Value":5517849,
"Source":"User",
"Type":"GlobalAddr",
"LabelAnno":"None"}},
"OperandFormats":{
@ -124,6 +170,86 @@
"SubFormat":"Symbol",
"SymbolRef":{
"Label":"j2",
"Part":"Low"}},
"95":{
"Length":3,
"Format":"NumericLE",
"SubFormat":"Address",
"SymbolRef":null},
"106":{
"Length":4,
"Format":"NumericLE",
"SubFormat":"Hex",
"SymbolRef":null},
"110":{
"Length":4,
"Format":"NumericLE",
"SubFormat":"Hex",
"SymbolRef":null},
"114":{
"Length":3,
"Format":"NumericLE",
"SubFormat":"Symbol",
"SymbolRef":{
"Label":"backchk",
"Part":"Low"}},
"117":{
"Length":3,
"Format":"NumericLE",
"SubFormat":"Symbol",
"SymbolRef":{
"Label":"backchk",
"Part":"Low"}},
"120":{
"Length":3,
"Format":"NumericLE",
"SubFormat":"Symbol",
"SymbolRef":{
"Label":"fwdchk",
"Part":"Low"}},
"130":{
"Length":3,
"Format":"NumericLE",
"SubFormat":"Symbol",
"SymbolRef":{
"Label":"backchk",
"Part":"Low"}},
"139":{
"Length":3,
"Format":"NumericLE",
"SubFormat":"Symbol",
"SymbolRef":{
"Label":"fwdchk",
"Part":"Low"}},
"148":{
"Length":3,
"Format":"NumericLE",
"SubFormat":"Symbol",
"SymbolRef":{
"Label":"backval",
"Part":"Low"}},
"192":{
"Length":3,
"Format":"NumericLE",
"SubFormat":"Address",
"SymbolRef":null},
"198":{
"Length":3,
"Format":"NumericLE",
"SubFormat":"Symbol",
"SymbolRef":{
"Label":"skip",
"Part":"Low"}}},
"LvTables":{

View File

@ -41,14 +41,73 @@ _L44FFCB jml _L2000
_L2000 bit _L2000
pea 0+(dat44 & $ffff)
pea 0+(dat44 >> 16)
bne _L200E
bne skip
jml [lodat]
_L200E nop
skip nop
jsr j2
j2 jsr j2+3
jsr j2-3
jsl longsym
rts
jml bank54
.here
.logical $543210
bank54 cmp bank54
bra L54321C
backchk nop
nop
L543218 rts
backval .long backchk
L54321C lda backchk
lda fwdchk
lda $543216
lda $54327d
lda 0+(backchk & $ffff)+1
lda 0+(backchk & $ffff)-1
lda 0+(fwdchk & $ffff)+1
lda 0+(fwdval & $ffff)+2
nop
jsr backchk
jsr backchk+1
jsr L543218
jsr fwdchk
jsr fwdchk+1
jsr L54327F
nop
ldx #$00
jsr (backval,x)
jsr (fwdval,x)
jsr _L54326E
jsr _L543271
jsr _L543268
jsr _L54326B
jsr _L543274
jsr _L543277
bra L543280
_L543268 jmp (backval,x)
_L54326B jmp (fwdval,x)
_L54326E jmp ($1008)
_L543271 jmp ($1008)
_L543274 jml [$1008]
_L543277 jml [$1008]
fwdval .long fwdchk
fwdchk nop
nop
L54327F rts
L543280 jsr skip+$540000
rtl
.here

View File

@ -36,13 +36,71 @@ high44 beq :L44FFCB
:L2000 bit :L2000
pea dat44
pea ^dat44
bne :L200E
bne skip
jml [lodat]
:L200E nop
skip nop
jsr j2
j2 jsr j2+3
jsr j2-3
jsl longsym
rts
jml bank54
org $543210
bank54 cmpl bank54
bra L54321C
backchk nop
nop
L543218 rts
backval adr backchk
L54321C ldal backchk
ldal fwdchk
ldal $543216
ldal $54327d
lda backchk+1
lda backchk-1
lda fwdchk+1
lda fwdval+2
nop
jsr backchk
jsr backchk+1
jsr L543218
jsr fwdchk
jsr fwdchk+1
jsr L54327F
nop
ldx #$00
jsr (backval,x)
jsr (fwdval,x)
jsr :L54326E
jsr :L543271
jsr :L543268
jsr :L54326B
jsr :L543274
jsr :L543277
bra L543280
:L543268 jmp (backval,x)
:L54326B jmp (fwdval,x)
:L54326E jmp ($1008)
:L543271 jmp ($1008)
:L543274 jml [$1008]
:L543277 jml [$1008]
fwdval adr $54327d
fwdchk nop
nop
L54327F rts
L543280 jsr skip
rtl

View File

@ -3,5 +3,9 @@
!pseudopc $1000 {
!hex 18fbe2305c000044000102cf000044af000044ad0000a50030f562b2ffd0b082
!hex a9ff1700170044cfc0ff44f005303c8239005c0020002c0020f41700f44400d0
!hex 03dc0810ea201220201520200f202256341260
!hex 03dc0810ea201220201520200f20225634125c103254cf1032548006eaea6016
!hex 3254af163254af7d3254af163254af7d3254ad1732ad1532ad7e32ad7c32ea20
!hex 1632201732201832207d32207e32207f32eaa200fc1932fc7a32206e32207132
!hex 206832206b3220743220773280187c19327c7a326c08106c0810dc0810dc0810
!hex 7d3254eaea60200e206b
} ;!pseudopc

View File

@ -43,13 +43,72 @@ high44: beq @L44FFCB
@L2000: bit @L2000
pea dat44 & $ffff
pea dat44 >> 16
bne @L200E
bne skip
jml [lodat]
@L200E: nop
skip: nop
jsr j2
j2: jsr j2+3
jsr j2-3
jsl longsym
rts
jml bank54
; .segment "SEG004"
.org $543210
bank54: cmp bank54
bra L54321C
backchk: nop
nop
L543218: rts
backval: .faraddr backchk
L54321C: lda backchk
lda f:fwdchk
lda $543216
lda $54327d
lda backchk & $ffff +1
lda backchk & $ffff -1
lda fwdchk & $ffff +1
lda fwdval & $ffff +2
nop
jsr backchk & $ffff
jsr backchk & $ffff +1
jsr L543218 & $ffff
jsr fwdchk & $ffff
jsr fwdchk & $ffff +1
jsr L54327F & $ffff
nop
ldx #$00
jsr (backval & $ffff,x)
jsr (fwdval & $ffff,x)
jsr @L54326E & $ffff
jsr @L543271 & $ffff
jsr @L543268 & $ffff
jsr @L54326B & $ffff
jsr @L543274 & $ffff
jsr @L543277 & $ffff
bra L543280
@L543268: jmp (backval & $ffff,x)
@L54326B: jmp (fwdval & $ffff,x)
@L54326E: jmp ($1008)
@L543271: jmp ($1008)
@L543274: jml [$1008]
@L543277: jml [$1008]
fwdval: .faraddr fwdchk
fwdchk: nop
nop
L54327F: rts
L543280: jsr skip
rtl

View File

@ -4,7 +4,8 @@ MEMORY {
# MEM000: file=%O, start=$1000, size=11;
# MEM001: file=%O, start=$440000, size=28;
# MEM002: file=%O, start=$44ffc0, size=15;
# MEM003: file=%O, start=$2000, size=29;
# MEM003: file=%O, start=$2000, size=32;
# MEM004: file=%O, start=$543210, size=116;
}
SEGMENTS {
CODE: load=MAIN, type=rw;
@ -12,6 +13,7 @@ SEGMENTS {
# SEG001: load=MEM001, type=rw;
# SEG002: load=MEM002, type=rw;
# SEG003: load=MEM003, type=rw;
# SEG004: load=MEM004, type=rw;
}
FEATURES {}
SYMBOLS {}

View File

@ -51,4 +51,83 @@ j2: jsr j3 ;EDIT: set label
j3: jsr j1
jsl symlong
jml bank54
.org $543210
bank54: cmp f:bank54
bra nxt54a
backchk:
nop ;EDIT: set label
nop
rts
backval:
.word backchk & $ffff
.byte bank54 >> 16
nxt54a:
; Test forward/backward refs. In cc65 this makes a difference because it's
; a one-pass assembler.
lda f:backchk ;EDIT: use symbol
lda f:fwdchk ;EDIT: use symbol
lda f:backchk ;EDIT: use hex
lda f:fwdchk ;EDIT: use hex
lda a:backchk & $ffff + 1 ;EDIT: use symbol
lda a:backchk & $ffff - 1 ;EDIT: use symbol
lda a:fwdchk & $ffff + 1 ;EDIT: use symbol
lda a:fwdchk & $ffff - 1 ;EDIT: use symbol
; Test non-bank-0 JSRs. The behavior varies significantly by assembler. The
; trick is that the high byte comes from the 'K' register. cc65 wants you
; to remove it for a 16-bit JSR (or fails because the operand is too big),
; 64tass wants you to keep it in place (or fails because you're trying to
; jump to the wrong bank), and Merlin32 doesn't care.
nop
jsr backchk & $ffff
jsr backchk & $ffff + 1 ;EDIT: set to "backchk"
jsr backchk & $ffff + 2 ;leave in hex
jsr fwdchk & $ffff
jsr fwdchk & $ffff + 1 ;EDIT: set to "fwdchk"
jsr fwdchk & $ffff + 2 ;leave in hex
nop
ldx #$00
jsr (backval & $ffff,X)
jsr (fwdval & $ffff,X)
jsr jmp1b & $ffff
jsr jmp1f & $ffff
jsr jmp2b & $ffff
jsr jmp2f & $ffff
jsr jmp3b & $ffff
jsr jmp3f & $ffff
bra nxt54b
; Fun fact: "JMP (addr)" and "JML [addr]" always load the indirect value
; from zero page. They do *not* use B or K. The (addr,X) mode uses the
; program bank (K). According to Eyes & Lichty, this is because the
; non-indexed form assumes you're jumping through a variable, while the
; indexed form assumes you've got an address table in your code. (Think
; how they would be used in ROM.)
jmp2b: jmp (backval & $ffff,X)
jmp2f: jmp (fwdval & $ffff,X)
jmp1b: jmp (lodat)
jmp1f: jmp (lodat)
jmp3b: jmp [lodat]
jmp3f: jmp [lodat]
fwdval:
.word fwdchk & $ffff
.byte bank54 >> 16
fwdchk:
nop ;EDIT: set label
nop
rts
nxt54b:
jsr skip ;EDIT: set to "skip" label
rtl