From d542a809f831e0f5917f02c40dc749abccecd43a Mon Sep 17 00:00:00 2001 From: Andy McFadden Date: Sun, 1 Sep 2019 10:55:19 -0700 Subject: [PATCH] Implement local variables for ACME Unlike 64tass and Merlin, which allow you to redefine symbols, ACME uses "zones" that provide scope for local variables. This means that, at the point of a local variable table definition, we have to start a new zone and output the full set of active symbols, not just the newly-defined ones. (If you set the "clear previous" flag in the LvTable there's no difference.) We could do a bit better by only outputting the symbols that are actually used within the zone, similar to what we do for global project/platform symbols, but that's a bunch of work for questionable benefit. --- SourceGen/AsmGen/AsmAcme.cs | 15 +- SourceGen/AsmGen/AsmCc65.cs | 14 +- SourceGen/AsmGen/AsmMerlin32.cs | 15 +- SourceGen/AsmGen/AsmTass64.cs | 14 +- SourceGen/AsmGen/GenCommon.cs | 20 +- SourceGen/AsmGen/IGenerator.cs | 14 +- SourceGen/LocalVariableLookup.cs | 16 +- .../Expected/2019-local-variables_acme.S | 205 +++++++++++++----- 8 files changed, 218 insertions(+), 95 deletions(-) diff --git a/SourceGen/AsmGen/AsmAcme.cs b/SourceGen/AsmGen/AsmAcme.cs index 24f85bb..a2ccf33 100644 --- a/SourceGen/AsmGen/AsmAcme.cs +++ b/SourceGen/AsmGen/AsmAcme.cs @@ -188,6 +188,7 @@ namespace SourceGen.AsmGen { config.mForceDirectOperandPrefix = string.Empty; config.mForceAbsOperandPrefix = string.Empty; config.mForceLongOperandPrefix = string.Empty; + config.mLocalVariableLablePrefix = "."; config.mEndOfLineCommentDelimiter = ";"; config.mFullLineCommentDelimiterBase = ";"; config.mBoxLineCommentDelimiter = ";"; @@ -472,8 +473,18 @@ namespace SourceGen.AsmGen { } // IGenerator - public void OutputVarDirective(string name, string valueStr, string comment) { - OutputEquDirective(name, valueStr, comment); + public void OutputLocalVariableTable(int offset, List newDefs, + LocalVariableTable allDefs) { + OutputLine(string.Empty, "!zone", "Z" + offset.ToString("x6"), string.Empty); + for (int i = 0; i < allDefs.Count; i++) { + DefSymbol defSym = allDefs[i]; + + string valueStr = PseudoOp.FormatNumericOperand(SourceFormatter, + Project.SymbolTable, null, defSym.DataDescriptor, defSym.Value, 1, + PseudoOp.FormatNumericOpFlags.None); + OutputEquDirective(SourceFormatter.FormatVariableLabel(defSym.Label), + valueStr, defSym.Comment); + } } // IGenerator diff --git a/SourceGen/AsmGen/AsmCc65.cs b/SourceGen/AsmGen/AsmCc65.cs index 43bb542..0e35d09 100644 --- a/SourceGen/AsmGen/AsmCc65.cs +++ b/SourceGen/AsmGen/AsmCc65.cs @@ -164,6 +164,8 @@ namespace SourceGen.AsmGen { // Special handling for forward references to zero-page labels is required. Quirks.SinglePassAssembler = true; + Quirks.NoRedefinableSymbols = true; + mWorkDirectory = workDirectory; mFileNameBase = fileNameBase; Settings = settings; @@ -506,8 +508,16 @@ namespace SourceGen.AsmGen { } // IGenerator - public void OutputVarDirective(string name, string valueStr, string comment) { - OutputEquDirective(name, valueStr, comment); + public void OutputLocalVariableTable(int offset, List newDefs, + LocalVariableTable allDefs) { + foreach (DefSymbol defSym in newDefs) { + // Use an operand length of 1 so values are shown as concisely as possible. + string valueStr = PseudoOp.FormatNumericOperand(SourceFormatter, + Project.SymbolTable, null, defSym.DataDescriptor, defSym.Value, 1, + PseudoOp.FormatNumericOpFlags.None); + OutputEquDirective(SourceFormatter.FormatVariableLabel(defSym.Label), + valueStr, defSym.Comment); + } } // IGenerator diff --git a/SourceGen/AsmGen/AsmMerlin32.cs b/SourceGen/AsmGen/AsmMerlin32.cs index 7c6a1e8..ed098b7 100644 --- a/SourceGen/AsmGen/AsmMerlin32.cs +++ b/SourceGen/AsmGen/AsmMerlin32.cs @@ -134,7 +134,6 @@ namespace SourceGen.AsmGen { Project = project; Quirks = new AssemblerQuirks(); - Quirks.HasRedefinableSymbols = true; Quirks.NoPcRelBankWrap = true; Quirks.TracksSepRepNotEmu = true; @@ -401,10 +400,16 @@ namespace SourceGen.AsmGen { } // IGenerator - public void OutputVarDirective(string name, string valueStr, string comment) { - OutputLine(SourceFormatter.FormatVariableLabel(name), - SourceFormatter.FormatPseudoOp(sDataOpNames.VarDirective), - valueStr, SourceFormatter.FormatEolComment(comment)); + public void OutputLocalVariableTable(int offset, List newDefs, + LocalVariableTable allDefs) { + foreach (DefSymbol defSym in newDefs) { + string valueStr = PseudoOp.FormatNumericOperand(SourceFormatter, + Project.SymbolTable, null, defSym.DataDescriptor, defSym.Value, 1, + PseudoOp.FormatNumericOpFlags.None); + OutputLine(SourceFormatter.FormatVariableLabel(defSym.Label), + SourceFormatter.FormatPseudoOp(sDataOpNames.VarDirective), + valueStr, SourceFormatter.FormatEolComment(defSym.Comment)); + } } // IGenerator diff --git a/SourceGen/AsmGen/AsmTass64.cs b/SourceGen/AsmGen/AsmTass64.cs index 14567d6..7b98085 100644 --- a/SourceGen/AsmGen/AsmTass64.cs +++ b/SourceGen/AsmGen/AsmTass64.cs @@ -160,7 +160,6 @@ namespace SourceGen.AsmGen { Project = project; Quirks = new AssemblerQuirks(); - Quirks.HasRedefinableSymbols = true; Quirks.StackIntOperandIsImmediate = true; mWorkDirectory = workDirectory; @@ -538,9 +537,16 @@ namespace SourceGen.AsmGen { } // IGenerator - public void OutputVarDirective(string name, string valueStr, string comment) { - OutputLine(name, SourceFormatter.FormatPseudoOp(sDataOpNames.VarDirective), - valueStr, SourceFormatter.FormatEolComment(comment)); + public void OutputLocalVariableTable(int offset, List newDefs, + LocalVariableTable allDefs) { + foreach (DefSymbol defSym in newDefs) { + string valueStr = PseudoOp.FormatNumericOperand(SourceFormatter, + Project.SymbolTable, null, defSym.DataDescriptor, defSym.Value, 1, + PseudoOp.FormatNumericOpFlags.None); + OutputLine(SourceFormatter.FormatVariableLabel(defSym.Label), + SourceFormatter.FormatPseudoOp(sDataOpNames.VarDirective), + valueStr, SourceFormatter.FormatEolComment(defSym.Comment)); + } } // IGenerator diff --git a/SourceGen/AsmGen/GenCommon.cs b/SourceGen/AsmGen/GenCommon.cs index 352021c..a8a62b3 100644 --- a/SourceGen/AsmGen/GenCommon.cs +++ b/SourceGen/AsmGen/GenCommon.cs @@ -40,7 +40,7 @@ namespace SourceGen.AsmGen { bool doAddCycles = gen.Settings.GetBool(AppSettings.SRCGEN_SHOW_CYCLE_COUNTS, false); LocalVariableLookup lvLookup = new LocalVariableLookup(proj.LvTables, proj, - !gen.Quirks.HasRedefinableSymbols); + gen.Quirks.NoRedefinableSymbols); GenerateHeader(gen, sw); @@ -74,7 +74,9 @@ namespace SourceGen.AsmGen { List lvars = lvLookup.GetVariablesDefinedAtOffset(offset); if (lvars != null) { - GenerateLocalVariables(gen, sw, lvars); + // table defined here + gen.OutputLocalVariableTable(offset, lvars, + lvLookup.GetMergedTableAtOffset(offset)); } if (attr.IsInstructionStart) { @@ -160,20 +162,6 @@ namespace SourceGen.AsmGen { } } - private static void GenerateLocalVariables(IGenerator gen, StreamWriter sw, - List vars) { - foreach (DefSymbol defSym in vars) { - DisasmProject proj = gen.Project; - Formatter formatter = gen.SourceFormatter; - - // Use an operand length of 1 so values are shown as concisely as possible. - string valueStr = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable, - null, defSym.DataDescriptor, defSym.Value, 1, - PseudoOp.FormatNumericOpFlags.None); - gen.OutputVarDirective(defSym.Label, valueStr, defSym.Comment); - } - } - private static void GenerateInstruction(IGenerator gen, StreamWriter sw, LocalVariableLookup lvLookup, int offset, int instrBytes, bool doAddCycles) { DisasmProject proj = gen.Project; diff --git a/SourceGen/AsmGen/IGenerator.cs b/SourceGen/AsmGen/IGenerator.cs index 3736333..427adde 100644 --- a/SourceGen/AsmGen/IGenerator.cs +++ b/SourceGen/AsmGen/IGenerator.cs @@ -137,12 +137,14 @@ namespace SourceGen.AsmGen { void OutputEquDirective(string name, string valueStr, string comment); /// - /// Outputs a variable definition directive. The numeric value is already formatted. + /// Outputs a series of local variable definitions. /// - /// Symbol label. - /// Formatted value. - /// End-of-line comment. - void OutputVarDirective(string name, string valueStr, string comment); + /// Offset at which table is defined. + /// New definitions, i.e. just the variables that were defined + /// at this offset. + /// All variable definitions that are active at this point. + void OutputLocalVariableTable(int offset, List newDefs, + LocalVariableTable allDefs); /// /// Outputs a code origin directive. @@ -200,7 +202,7 @@ namespace SourceGen.AsmGen { /// Does the assembler support a type of label whose value can be redefined to /// act as a local variable? /// - public bool HasRedefinableSymbols { get; set; } + public bool NoRedefinableSymbols { get; set; } /// /// Is the assembler unable to generate relative branches that wrap around banks? diff --git a/SourceGen/LocalVariableLookup.cs b/SourceGen/LocalVariableLookup.cs index db69bff..5711a40 100644 --- a/SourceGen/LocalVariableLookup.cs +++ b/SourceGen/LocalVariableLookup.cs @@ -49,6 +49,20 @@ namespace SourceGen { /// /// The BaseLabel does not change, but Label is updated by MakeUnique. /// + /// + /// LvLookup is run multiple times, and can be restarted in the middle of a run. It's + /// essential that UniqueLabel behaves deterministically. For this to happen, the + /// contents of SymbolTable can't change in a way that affects the outcome unless it + /// also causes us to redo the uniquification. This mostly means that we have to be + /// very careful about creating duplicate symbols, so that we don't get halfway through + /// the analysis pass and invalidate our previous work. It's best to leave + /// uniquification disabled until we're generating assembly source code. + /// + /// The issues also make it hard to do the uniquification once, rather than every time we + /// walk the code. Not all symbol changes cause a re-analysis (e.g. renaming a user + /// label does not), and we don't want to fill the symbol table with the uniquified + /// names because it could block user labels that would otherwise be valid. + /// private class UniqueLabel { public string BaseLabel { get; private set; } public string Label { get; private set; } @@ -87,7 +101,7 @@ namespace SourceGen { /// /// /// It's hard to do this as part of uniquification because the remapped base name ends - /// up in the symbol table, and the uniqifier isn't able to tell that the entry in the + /// up in the symbol table, and the uniquifier isn't able to tell that the entry in the /// symbol table is itself. The logic is simpler if we just rename the label before /// the uniquifier ever sees it. /// diff --git a/SourceGen/SGTestData/Expected/2019-local-variables_acme.S b/SourceGen/SGTestData/Expected/2019-local-variables_acme.S index 48b1114..f4d88ce 100644 --- a/SourceGen/SGTestData/Expected/2019-local-variables_acme.S +++ b/SourceGen/SGTestData/Expected/2019-local-variables_acme.S @@ -13,28 +13,42 @@ CONST_ZERO = $f0 ;project const ldx $04 lda CONST_ZERO,S sta $f1,S -VAR_ZERO = $00 -VAR_TWO = $02 -VAR_THREE = $03 -CONST_ZERO_VAR = $f0 - ldy VAR_ZERO - lda (VAR_ZERO+1),y - sta VAR_THREE + !zone Z00000c +.VAR_ZERO = $00 +.VAR_TWO = $02 +.VAR_THREE = $03 +.CONST_ZERO_VAR = $f0 + ldy .VAR_ZERO + lda (.VAR_ZERO+1),y + sta .VAR_THREE ldx $04 - lda CONST_ZERO_VAR,S + lda .CONST_ZERO_VAR,S sta $f1,S eor 0 ora 240,S -PROJ_ZERO_DUP1 = $10 ;clash with project symbol -DPCODE_DUP1 = $80 ;clash with user label - lda VAR_ZERO - lda VAR_ZERO+1 - lda VAR_TWO - lda VAR_THREE + !zone Z00001c +.VAR_ZERO = $00 +.VAR_TWO = $02 +.VAR_THREE = $03 +.PROJ_ZERO_DUP1 = $10 ;clash with project symbol +.DPCODE_DUP1 = $80 ;clash with user label +.CONST_ZERO_VAR = $f0 + lda .VAR_ZERO + lda .VAR_ZERO+1 + lda .VAR_TWO + !zone Z000022 +.VAR_ZERO = $00 +.VAR_TWO = $02 +.VAR_THREE = $03 +.PROJ_ZERO_DUP1 = $10 ;clash with project symbol +.DPCODE_DUP1 = $80 ;clash with user label +.CONST_ZERO_VAR = $f0 + lda .VAR_THREE lda $04 - lda PROJ_ZERO_DUP1 + lda .PROJ_ZERO_DUP1 lda $11 lda+1 DPCODE + !zone Z00002c ldx PROJ_ZERO ldx PROJ_ONE ldx $02 @@ -43,58 +57,131 @@ DPCODE_DUP1 = $80 ;clash with user label ldy PROJ_ONE ldy $02 !byte $2c -NH0 = $00 ;not hidden -NH1 = $01 ;not hidden + !zone Z00003c +.NH0 = $00 ;not hidden +.NH1 = $01 ;not hidden L103C lda #$fe beq L103C - ldy NH0 - ldy NH1 + ldy .NH0 + ldy .NH1 ldy $02 nop -PTR0 = $10 -CONST0 = $10 - lda PTR0 - ldx PTR0+1 + !zone Z000047 +.NH0 = $00 ;not hidden +.NH1 = $01 ;not hidden +.PTR0 = $10 +.CONST0 = $10 + lda .PTR0 + ldx .PTR0+1 ldy $12 - lda (CONST0,S),y - sta (CONST0+3,S),y + lda (.CONST0,S),y + sta (.CONST0+3,S),y ;Test name redefinition. This is mostly of interest for assemblers without ;redefinable variables, but also of interest to the cross-reference window. -PTR = $20 ;#1 - ldx PTR -PTR_3 = $22 ;#2 - ldx PTR_3 -PTR_4 = $24 ;#3 - ldx PTR_4 + !zone Z000051 +.NH0 = $00 ;not hidden +.NH1 = $01 ;not hidden +.PTR0 = $10 +.CONST0 = $10 +.PTR = $20 ;#1 + ldx .PTR + !zone Z000053 +.NH0 = $00 ;not hidden +.NH1 = $01 ;not hidden +.PTR0 = $10 +.CONST0 = $10 +.PTR = $22 ;#2 + ldx .PTR + !zone Z000055 +.NH0 = $00 ;not hidden +.NH1 = $01 ;not hidden +.PTR0 = $10 +.CONST0 = $10 +.PTR = $24 ;#3 + ldx .PTR PTR_1 nop -PTR_A = $20 - ldy PTR_A -PTR_B = $1f - ldy PTR_B+1 -PTR_C = $1d - ldy PTR_C+3 -PTR_D = $21 - ldy PTR_C+3 -VAL0 = $30 -VAL1 = $31 -VAL2 = $32 -VAL3 = $33 -VAL4 = $34 -VAL5 = $35 - and VAL0 - and VAL1 - and VAL2 - and VAL3 - and VAL4 - and VAL5 -VAL14 = $31 - and VAL0 - and VAL14 - and VAL14+1 - and VAL14+2 - and VAL14+3 - and VAL5 -DPNOP = $80 ;same as org + !zone Z000058 +.NH0 = $00 ;not hidden +.NH1 = $01 ;not hidden +.PTR0 = $10 +.CONST0 = $10 +.PTR_A = $20 +.PTR = $24 ;#3 + ldy .PTR_A + !zone Z00005a +.NH0 = $00 ;not hidden +.NH1 = $01 ;not hidden +.PTR0 = $10 +.CONST0 = $10 +.PTR_B = $1f +.PTR = $24 ;#3 + ldy .PTR_B+1 + !zone Z00005c +.NH0 = $00 ;not hidden +.NH1 = $01 ;not hidden +.PTR0 = $10 +.CONST0 = $10 +.PTR_C = $1d +.PTR = $24 ;#3 + ldy .PTR_C+3 + !zone Z00005e +.NH0 = $00 ;not hidden +.NH1 = $01 ;not hidden +.PTR0 = $10 +.CONST0 = $10 +.PTR_C = $1d +.PTR_D = $21 +.PTR = $24 ;#3 + ldy .PTR_C+3 + !zone Z000060 +.NH0 = $00 ;not hidden +.NH1 = $01 ;not hidden +.PTR0 = $10 +.CONST0 = $10 +.PTR_C = $1d +.PTR_D = $21 +.PTR = $24 ;#3 +.VAL0 = $30 +.VAL1 = $31 +.VAL2 = $32 +.VAL3 = $33 +.VAL4 = $34 +.VAL5 = $35 + and .VAL0 + and .VAL1 + and .VAL2 + and .VAL3 + and .VAL4 + and .VAL5 + !zone Z00006c +.NH0 = $00 ;not hidden +.NH1 = $01 ;not hidden +.PTR0 = $10 +.CONST0 = $10 +.PTR_C = $1d +.PTR_D = $21 +.PTR = $24 ;#3 +.VAL0 = $30 +.VAL14 = $31 +.VAL5 = $35 + and .VAL0 + and .VAL14 + and .VAL14+1 + and .VAL14+2 + and .VAL14+3 + and .VAL5 + !zone Z000078 +.NH0 = $00 ;not hidden +.NH1 = $01 ;not hidden +.PTR0 = $10 +.CONST0 = $10 +.PTR_C = $1d +.PTR_D = $21 +.PTR = $24 ;#3 +.VAL0 = $30 +.VAL14 = $31 +.VAL5 = $35 +.DPNOP = $80 ;same as org lda+1 DPCODE jsr DPCODE rts