diff --git a/Asm65/Formatter.cs b/Asm65/Formatter.cs index b7d9f0c..5b4a9d7 100644 --- a/Asm65/Formatter.cs +++ b/Asm65/Formatter.cs @@ -63,7 +63,8 @@ namespace Asm65 { public bool mAllowHighAsciiCharConst; // can we do high-ASCII character constants? // (this might need to be generalized) - public string mForceAbsOpcodeSuffix; // these may be null or empty + public string mForceDirectOperandPrefix; // these may be null or empty + public string mForceAbsOpcodeSuffix; public string mForceAbsOperandPrefix; public string mForceLongOpcodeSuffix; public string mForceLongOperandPrefix; @@ -485,7 +486,9 @@ namespace Asm65 { /// public string FormatMnemonic(string mnemonic, OpDef.WidthDisambiguation wdis) { string opcodeStr = mnemonic; - if (wdis == OpDef.WidthDisambiguation.ForceAbs) { + if (wdis == OpDef.WidthDisambiguation.ForceDirect) { + // nothing to do for opcode + } else if (wdis == OpDef.WidthDisambiguation.ForceAbs) { if (!string.IsNullOrEmpty(mFormatConfig.mForceAbsOpcodeSuffix)) { opcodeStr += mFormatConfig.mForceAbsOpcodeSuffix; } @@ -507,13 +510,18 @@ namespace Asm65 { /// Generates an operand format. /// /// Addressing mode. + /// Width disambiguation mode. /// Format string. private string GenerateOperandFormat(OpDef.AddressMode addrMode, OpDef.WidthDisambiguation wdis) { string fmt; string wdisStr = string.Empty; - if (wdis == OpDef.WidthDisambiguation.ForceAbs) { + if (wdis == OpDef.WidthDisambiguation.ForceDirect) { + if (!string.IsNullOrEmpty(mFormatConfig.mForceDirectOperandPrefix)) { + wdisStr = mFormatConfig.mForceDirectOperandPrefix; + } + } else if (wdis == OpDef.WidthDisambiguation.ForceAbs) { if (!string.IsNullOrEmpty(mFormatConfig.mForceAbsOperandPrefix)) { wdisStr = mFormatConfig.mForceAbsOperandPrefix; } diff --git a/Asm65/OpDef.cs b/Asm65/OpDef.cs index 994fa43..4edd2cd 100644 --- a/Asm65/OpDef.cs +++ b/Asm65/OpDef.cs @@ -112,9 +112,10 @@ namespace Asm65 { /// public enum WidthDisambiguation : byte { None = 0, + ForceDirect, // only needed for forward DP label refs in single-pass assemblers ForceAbs, ForceLong, - ForceLongMaybe + ForceLongMaybe // add opcode suffix but not operand prefix // May want an additional item: "force long if operand suffix specified". This // would let us generate LDAL for assemblers that like to have that made explicit, diff --git a/SourceGen/AsmGen/AsmCc65.cs b/SourceGen/AsmGen/AsmCc65.cs index 1819012..bd4f3b1 100644 --- a/SourceGen/AsmGen/AsmCc65.cs +++ b/SourceGen/AsmGen/AsmCc65.cs @@ -164,6 +164,7 @@ namespace SourceGen.AsmGen { // cc65 v2.17: https://github.com/cc65/cc65/issues/754 Quirks.NoPcRelBankWrap = true; } + Quirks.SinglePassAssembler = true; mWorkDirectory = workDirectory; mFileNameBase = fileNameBase; @@ -182,6 +183,7 @@ namespace SourceGen.AsmGen { private void SetFormatConfigValues(ref Formatter.FormatConfig config) { config.mForceAbsOpcodeSuffix = string.Empty; config.mForceLongOpcodeSuffix = string.Empty; + config.mForceDirectOperandPrefix = "z:"; // zero config.mForceAbsOperandPrefix = "a:"; // absolute config.mForceLongOperandPrefix = "f:"; // far config.mEndOfLineCommentDelimiter = ";"; diff --git a/SourceGen/AsmGen/AsmMerlin32.cs b/SourceGen/AsmGen/AsmMerlin32.cs index 0b0d2fe..9464d3f 100644 --- a/SourceGen/AsmGen/AsmMerlin32.cs +++ b/SourceGen/AsmGen/AsmMerlin32.cs @@ -185,6 +185,7 @@ namespace SourceGen.AsmGen { private void SetFormatConfigValues(ref Formatter.FormatConfig config) { config.mForceAbsOpcodeSuffix = ":"; config.mForceLongOpcodeSuffix = "l"; + config.mForceDirectOperandPrefix = string.Empty; config.mForceAbsOperandPrefix = string.Empty; config.mForceLongOperandPrefix = string.Empty; config.mEndOfLineCommentDelimiter = ";"; diff --git a/SourceGen/AsmGen/AsmTass64.cs b/SourceGen/AsmGen/AsmTass64.cs index d4bb7aa..df1a2d3 100644 --- a/SourceGen/AsmGen/AsmTass64.cs +++ b/SourceGen/AsmGen/AsmTass64.cs @@ -179,6 +179,7 @@ namespace SourceGen.AsmGen { config.mForceAbsOpcodeSuffix = string.Empty; config.mForceLongOpcodeSuffix = string.Empty; + config.mForceDirectOperandPrefix = string.Empty; config.mForceAbsOperandPrefix = "@w"; // word config.mForceLongOperandPrefix = "@l"; // long config.mEndOfLineCommentDelimiter = ";"; diff --git a/SourceGen/AsmGen/GenCommon.cs b/SourceGen/AsmGen/GenCommon.cs index c3345e5..16ef05a 100644 --- a/SourceGen/AsmGen/GenCommon.cs +++ b/SourceGen/AsmGen/GenCommon.cs @@ -171,6 +171,15 @@ namespace SourceGen.AsmGen { if (op.IsWidthPotentiallyAmbiguous) { wdis = OpDef.GetWidthDisambiguation(instrLen, operand); } + if (gen.Quirks.SinglePassAssembler && wdis == OpDef.WidthDisambiguation.None && + (op.AddrMode == OpDef.AddressMode.DP || + op.AddrMode == OpDef.AddressMode.DPIndexX) || + op.AddrMode == OpDef.AddressMode.DPIndexY) { + // Could be a forward reference to a direct-page label. + if (IsForwardLabelReference(gen, offset)) { + wdis = OpDef.WidthDisambiguation.ForceDirect; + } + } string opcodeStr = formatter.FormatOpcode(op, wdis); @@ -298,6 +307,46 @@ namespace SourceGen.AsmGen { } } + /// + /// Determines whether the instruction at the specified offset has an operand that is + /// a forward reference. This only matters for single-pass assemblers. + /// + /// Source generator reference. + /// Offset of instruction opcode. + /// True if the instruction's operand is a forward reference to a label. + private static bool IsForwardLabelReference(IGenerator gen, int offset) { + DisasmProject proj = gen.Project; + Debug.Assert(proj.GetAnattrib(offset).IsInstructionStart); + + FormatDescriptor dfd = proj.GetAnattrib(offset).DataDescriptor; + if (dfd == null || !dfd.HasSymbol) { + return false; + } + if (!proj.SymbolTable.TryGetValue(dfd.SymbolRef.Label, out Symbol sym)) { + return false; + } + if (!sym.IsInternalLabel) { + return false; + } + + // It's an internal label reference. We don't currently have a data structure + // that lets us go from label name to file offset. This situation is sufficiently + // rare that an O(n) approach is acceptable. We may need to fix this someday. + // + // We only want to know if it is defined after the current instruction. This is + // probably being used for a direct-page reference, which is probably at the start + // of the file, so we run from the start to the current instruction. + for (int i = 0; i < offset; i++) { + Anattrib attr = proj.GetAnattrib(i); + if (attr.Symbol != null && attr.Symbol == sym) { + // Found it earlier in file. + return false; + } + } + // Must appear later in file. + return true; + } + /// /// Configures some common format config items from the app settings. Uses a /// passed-in settings object, rather than the global settings. diff --git a/SourceGen/AsmGen/IGenerator.cs b/SourceGen/AsmGen/IGenerator.cs index 6d7afbe..ce4ee31 100644 --- a/SourceGen/AsmGen/IGenerator.cs +++ b/SourceGen/AsmGen/IGenerator.cs @@ -183,5 +183,10 @@ namespace SourceGen.AsmGen { /// (Note this affects long-distance BRLs that don't appear to wrap.) /// public bool NoPcRelBankWrap { get; set; } + + /// + /// Is the assembler implemented as a single pass? + /// + public bool SinglePassAssembler { get; set; } } } \ No newline at end of file diff --git a/SourceGen/SGTestData/Expected/2014-label-dp_cc65.S b/SourceGen/SGTestData/Expected/2014-label-dp_cc65.S new file mode 100644 index 0000000..a9279b5 --- /dev/null +++ b/SourceGen/SGTestData/Expected/2014-label-dp_cc65.S @@ -0,0 +1,292 @@ +;6502bench SourceGen v1.1.0-dev1 + .setcpu "65816" + .org $1000 + .a8 + .i8 + sec + xce + jsr L101F + jsr L10AB + jsr L10F2 + jsr L1106 + jsr L1109 + jsr L112C + jsr L11F9 + jsr L11FC + nop + nop + nop + .byte $00,$80 + +L101F: ora (L0080,x) + cop $80 + ora $80,S + tsb z:L0080 + ora z:L0080 + asl z:L0080 + ora [L0080] + php + ora #$80 + asl A + phd + tsb a:L0086 + ora a:L0086 + asl a:L0086 + ora f:L0089 + bpl L1041 +L1041: ora (L0080),y + ora (L0080) + ora ($80,S),y + trb z:L0080 + ora z:L0080,x + asl z:L0080,x + ora [L0080],y + clc + ora L0086,y + inc A + tcs + trb a:L0086 + ora a:L0086,x + asl a:L0086,x + ora f:L0089,x + jsr L0086 + and (L0080,x) + jsl L0089 + and $80,S + bit z:L0080 + and z:L0080 + rol z:L0080 + and [L0080] + plp + and #$80 + rol A + pld + bit a:L0086 + and a:L0086 + rol a:L0086 + and f:L0089 + bmi L1089 +L1089: and (L0080),y + and (L0080) + and ($80,S),y + bit z:L0080,x + and z:L0080,x + rol z:L0080,x + and [L0080],y + sec + and L0086,y + dec A + tsc + bit a:L0086,x + and a:L0086,x + rol a:L0086,x + and f:L0089,x + rti + +L10AB: eor (L0080,x) + .byte $42,$80 + eor $80,S + .byte $44,$83,$84 + eor z:L0080 + lsr z:L0080 + eor [L0080] + pha + eor #$80 + lsr A + phk + jmp L10C2 + +L10C2: eor a:L0086 + lsr a:L0086 + eor f:L0089 + bvc L10CE +L10CE: eor (L0080),y + eor (L0080) + eor ($80,S),y + .byte $54,$83,$84 + eor z:L0080,x + lsr z:L0080,x + eor [L0080],y + cli + eor L0086,y + phy + tcd + jml L10E7 + +L10E7: eor a:L0086,x + lsr a:L0086,x + eor f:L0089,x + rts + +L10F2: adc (L0080,x) + per $0ff6 + adc $80,S + stz z:L0080 + adc z:L0080 + ror z:L0080 + adc [L0080] + pla + adc #$80 + ror A + rtl + +L1106: jmp (L0086) + +L1109: adc a:L0086 + ror a:L0086 + adc f:L0089 + bvs L1115 +L1115: adc (L0080),y + adc (L0080) + adc ($80,S),y + stz z:L0080,x + adc z:L0080,x + ror z:L0080,x + adc [L0080],y + sei + adc L0086,y + ply + tdc + jmp (L0086,x) + +L112C: adc a:L0086,x + ror a:L0086,x + adc f:L0089,x + bra L1138 + +L1138: sta (L0080,x) + brl L113D + +L113D: sta $80,S + sty z:L0080 + sta z:L0080 + stx z:L0080 + sta [L0080] + dey + bit #$80 + txa + phb + sty a:L0086 + sta a:L0086 + stx a:L0086 + sta f:L0089 + bcc L115B +L115B: sta (L0080),y + sta (L0080) + sta ($80,S),y + sty z:L0080,x + sta z:L0080,x + stx z:L0080,y + sta [L0080],y + tya + sta L0086,y + txs + txy + stz a:L0086 + sta a:L0086,x + stz a:L0086,x + sta f:L0089,x + ldy #$80 + lda (L0080,x) + ldx #$80 + lda $80,S + ldy z:L0080 + lda z:L0080 + ldx z:L0080 + lda [L0080] + tay + lda #$80 + tax + plb + ldy a:L0086 + lda a:L0086 + ldx a:L0086 + lda f:L0089 + bcs L11A0 +L11A0: lda (L0080),y + lda (L0080) + lda ($80,S),y + ldy z:L0080,x + lda z:L0080,x + ldx z:L0080,y + lda [L0080],y + clv + lda L0086,y + tsx + tyx + ldy a:L0086,x + lda a:L0086,x + ldx a:L0086,y + lda f:L0089,x + cpy #$80 + cmp (L0080,x) + rep #$00 + cmp $80,S + cpy z:L0080 + cmp z:L0080 + dec z:L0080 + cmp [L0080] + iny + cmp #$80 + dex + wai + cpy a:L0086 + cmp a:L0086 + dec a:L0086 + cmp f:L0089 + bne L11E5 +L11E5: cmp (L0080),y + cmp (L0080) + cmp ($80,S),y + pei (L0080) + cmp z:L0080,x + dec z:L0080,x + cmp [L0080],y + cld + cmp L0086,y + phx + stp + +L11F9: jml [L0086] + +L11FC: cmp a:L0086,x + dec a:L0086,x + cmp f:L0089,x + cpx #$80 + sbc (L0080,x) + sep #$00 + sbc $80,S + cpx z:L0080 + sbc z:L0080 + inc z:L0080 + sbc [L0080] + inx + sbc #$80 + nop + xba + cpx a:L0086 + sbc a:L0086 + inc a:L0086 + sbc f:L0089 + beq L122A +L122A: sbc (L0080),y + sbc (L0080) + sbc ($80,S),y + pea L0086 + sbc z:L0080,x + inc z:L0080,x + sbc [L0080],y + sed + sbc L0086,y + plx + xce + jsr (L0086,x) + sbc a:L0086,x + inc a:L0086,x + sbc f:L0089,x + .org $0080 +L0080: bit z:L0082 +L0082: bit z:L0082 + bit L0082 +L0086: bit a:L0086 +L0089: lda f:L0089