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