From f8bc917601906564628ceaf608484379aa5e4dda Mon Sep 17 00:00:00 2001 From: "Michael C. Martin" Date: Thu, 24 May 2012 18:12:35 -0700 Subject: [PATCH] A new 'correctness optimization': ExtendBranches. This pass actually isn't an optimizer in that it produces larger binaries when it triggers. However, the larger binaries created will actually assemble properly. The ExtendBranches pass detects Relative instructions (that is, branches) that extend past the signed-8-bit range Relative instructions permit, and replaces them with a branch-jump combination with identical semantics. Since this may be evidence of a program bug, Ophis will warn when the optimization is triggered. Due to similarities between this pass and UpdateLabels, both passes have been refactored in passing. --- src/Ophis/Frontend.py | 2 +- src/Ophis/IR.py | 2 +- src/Ophis/Main.py | 3 +- src/Ophis/Opcodes.py | 22 ++++---- src/Ophis/Passes.py | 84 +++++++++++++++++++++++++++---- tests/branch_c02.bin | Bin 0 -> 490 bytes tests/branch_c02.oph | 74 +++++++++++++++++++++++++++ tests/branch_c02_ref.oph | 106 +++++++++++++++++++++++++++++++++++++++ tests/longbranch.bin | Bin 0 -> 312 bytes tests/longbranch.oph | 37 ++++++++++++++ tests/longbranch_ref.oph | 54 ++++++++++++++++++++ 11 files changed, 360 insertions(+), 24 deletions(-) create mode 100644 tests/branch_c02.bin create mode 100644 tests/branch_c02.oph create mode 100644 tests/branch_c02_ref.oph create mode 100644 tests/longbranch.bin create mode 100644 tests/longbranch.oph create mode 100644 tests/longbranch_ref.oph diff --git a/src/Ophis/Frontend.py b/src/Ophis/Frontend.py index 0bd3c19..0c418a1 100644 --- a/src/Ophis/Frontend.py +++ b/src/Ophis/Frontend.py @@ -307,7 +307,7 @@ def parse_line(ppt, lexemelist): return IR.SequenceNode(ppt, result) def parse_file(ppt, filename): - "Loads a .P65 source file, and returns an IR list." + "Loads an Ophis source file, and returns an IR list." Err.currentpoint = ppt if Cmd.verbose > 0: print "Loading "+filename try: diff --git a/src/Ophis/IR.py b/src/Ophis/IR.py index a0255e2..6e017f6 100644 --- a/src/Ophis/IR.py +++ b/src/Ophis/IR.py @@ -39,7 +39,7 @@ def SequenceNode(ppt, nodelist): return Node(ppt, "SEQUENCE", *nodelist) class Expr: - """Base class for P65 expressions + """Base class for Ophis expressions All expressions have a field called "data" and a boolean field called "hardcoded". An expression is hardcoded if it has no symbolic values in it.""" diff --git a/src/Ophis/Main.py b/src/Ophis/Main.py index 6cc43f4..0977e0a 100644 --- a/src/Ophis/Main.py +++ b/src/Ophis/Main.py @@ -40,6 +40,7 @@ def run_all(infile, outfile): l_basic = Ophis.Passes.UpdateLabels() l = Ophis.Passes.FixPoint("label update", [l_basic], lambda: l_basic.changed == 0) c = Ophis.Passes.Collapse() + b = Ophis.Passes.ExtendBranches() a = Ophis.Passes.Assembler() passes = [] @@ -47,7 +48,7 @@ def run_all(infile, outfile): passes.append(Ophis.Passes.FixPoint("macro expansion", [m], lambda: m.changed == 0)) passes.append(Ophis.Passes.FixPoint("label initialization", [i], lambda: i.changed == 0)) passes.extend([Ophis.Passes.CircularityCheck(), Ophis.Passes.CheckExprs(), Ophis.Passes.EasyModes()]) - passes.append(Ophis.Passes.FixPoint("instruction selection", [l, c], lambda: c.collapsed == 0)) + passes.append(Ophis.Passes.FixPoint("instruction selection", [l, c, b], lambda: c.collapsed == 0 and b.expanded == 0)) passes.extend([Ophis.Passes.NormalizeModes(), Ophis.Passes.UpdateLabels(), a]) for p in passes: p.go(z, env) diff --git a/src/Ophis/Opcodes.py b/src/Ophis/Opcodes.py index 5a8270f..0dc45a7 100644 --- a/src/Ophis/Opcodes.py +++ b/src/Ophis/Opcodes.py @@ -9,20 +9,20 @@ # Names of addressing modes modes = ["Implied", # 0 - "Immediate", # 1 - "Zero Page", # 2 - "Zero Page, X", # 3 - "Zero Page, Y", # 4 - "Absolute", # 5 - "Absolute, X", # 6 - "Absolute, Y", # 7 - "(Absolute)", # 8 + "Immediate", # 1 + "Zero Page", # 2 + "Zero Page, X", # 3 + "Zero Page, Y", # 4 + "Absolute", # 5 + "Absolute, X", # 6 + "Absolute, Y", # 7 + "(Absolute)", # 8 "(Absolute, X)", # 9 "(Absolute), Y", # 10 "(Zero Page)", # 11 - "(Zero Page, X)", # 12 - "(Zero Page), Y", # 13 - "Relative"] # 14 + "(Zero Page, X)", # 12 + "(Zero Page), Y", # 13 + "Relative"] # 14 # Lengths of the argument lengths = [0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1] diff --git a/src/Ophis/Passes.py b/src/Ophis/Passes.py index 7a2ff70..7b6fd25 100644 --- a/src/Ophis/Passes.py +++ b/src/Ophis/Passes.py @@ -227,11 +227,9 @@ class EasyModes(Pass): def visitUnknown(self, node, env): pass -class UpdateLabels(Pass): - "Computes the new values for all entries in the symbol table" - name = "Label Update Pass" - def prePass(self): - self.changed = 0 +class PCTracker(Pass): + "Superclass for passes that need an accurate program counter." + name = "**BUG** PC Tracker Superpass used directly" def visitSetPC(self, node, env): env.setPC(node.data[0].value(env)) def visitAdvance(self, node, env): env.setPC(node.data[0].value(env)) def visitImplied(self, node, env): env.incPC(1) @@ -256,17 +254,24 @@ class UpdateLabels(Pass): def visitPointerX(self, node, env): env.incPC(3) def visitPointerY(self, node, env): env.incPC(3) def visitCheckPC(self, node, env): pass + def visitLabel(self, node, env): pass + def visitByte(self, node, env): env.incPC(len(node.data)) + def visitWord(self, node, env): env.incPC(len(node.data)*2) + def visitDword(self, node, env): env.incPC(len(node.data)*4) + def visitWordBE(self, node, env): env.incPC(len(node.data)*2) + def visitDwordBE(self, node, env): env.incPC(len(node.data)*4) + +class UpdateLabels(PCTracker): + "Computes the new values for all entries in the symbol table" + name = "Label Update Pass" + def prePass(self): + self.changed = 0 def visitLabel(self, node, env): (label, val) = node.data old = env[label] env[label] = val.value(env) if old != env[label]: self.changed = 1 - def visitByte(self, node, env): env.incPC(len(node.data)) - def visitWord(self, node, env): env.incPC(len(node.data)*2) - def visitDword(self, node, env): env.incPC(len(node.data)*4) - def visitWordBE(self, node, env): env.incPC(len(node.data)*2) - def visitDwordBE(self, node, env): env.incPC(len(node.data)*4) class Collapse(Pass): """Selects as many zero-page instructions to convert as @@ -344,6 +349,65 @@ def collapse_y_ind(node, env): else: return 0 +class ExtendBranches(PCTracker): + """Eliminates any branch instructions that would end up going past + the 128-byte range, and replaces them with a branch-jump + pair. Also tracks how many elements where changed this pass.""" + name = "Branch Expansion Pass" + reversed = { 'bcc': 'bcs', + 'bcs': 'bcc', + 'beq': 'bne', + 'bmi': 'bpl', + 'bne': 'beq', + 'bpl': 'bmi', + 'bvc': 'bvs', + 'bvs': 'bvc', + # 65c02 ones. 'bra' is special, though, having no inverse + 'bbr0': 'bbs0', + 'bbs0': 'bbr0', + 'bbr1': 'bbs1', + 'bbs1': 'bbr1', + 'bbr2': 'bbs2', + 'bbs2': 'bbr2', + 'bbr3': 'bbs3', + 'bbs3': 'bbr3', + 'bbr4': 'bbs4', + 'bbs4': 'bbr4', + 'bbr5': 'bbs5', + 'bbs5': 'bbr5', + 'bbr6': 'bbs6', + 'bbs6': 'bbr6', + 'bbr7': 'bbs7', + 'bbs7': 'bbr7' + } + def prePass(self): + self.expanded = 0 + def visitRelative(self, node, env): + (opcode, expr) = node.data + arg = expr.value(env) + arg = arg-(env.getPC()+2) + if arg < -128 or arg > 127: + if opcode == 'bra': + # If BRA - BRanch Always - is out of range, it's a JMP. + node.data = ('jmp', expr) + node.nodetype = "Absolute" + if Cmd.verbose > 0: + print str(node.ppt) + ": WARNING: bra out of range, replacing with jmp" + else: + # Otherwise, we replace it with a 'macro' of sorts by hand: + # $branch LOC -> $reversed_branch ^+5; JMP LOC + # We don't use temp labels here because labels need to have been fixed + # in place by this point, and JMP is always 3 bytes long. + expansion = [IR.Node(node.ppt, "Relative", ExtendBranches.reversed[opcode], IR.SequenceExpr([IR.PCExpr(), "+", IR.ConstantExpr(5)])), + IR.Node(node.ppt, "Absolute", 'jmp', expr)] + node.nodetype='SEQUENCE' + node.data = expansion + if Cmd.verbose > 0: + print str(node.ppt) + ": WARNING: "+opcode+" out of range, replacing with "+ExtendBranches.reversed[opcode] +"/jmp combo" + self.expanded += 1 + node.accept(self, env) + else: + env.incPC(2) class NormalizeModes(Pass): """Eliminates the intermediate "Memory" and "Pointer" nodes, diff --git a/tests/branch_c02.bin b/tests/branch_c02.bin new file mode 100644 index 0000000000000000000000000000000000000000..0683ba6a0dcf2be07ef0445e0c9a8a5eecaf8f9c GIT binary patch literal 490 zcmd_bu?Ye(0EOX$YqZJ;86hKNfrE^Y5mH&_dXXR^A|gRVM6SpP86g`O5fKqFA|fJU zyf=rBpYNHn>UAw}z=HrHNFakYf~SB14g`=u0S!n)H*C#UVksBO>UljgQ#)~EKMKQW z(jfPXKOgzh4}0^Mc*=*m>sx&_7khRme+-AXPrJM|WmR-6aKM8AB1j;EHiD;s0S*L^ HKmiSYOC3-+ literal 0 HcmV?d00001 diff --git a/tests/branch_c02.oph b/tests/branch_c02.oph new file mode 100644 index 0000000..6040ef3 --- /dev/null +++ b/tests/branch_c02.oph @@ -0,0 +1,74 @@ +.text +.org $0800 +early: + bbr0 late + bbr1 late + bbr2 late + bbr3 late + bbr4 late + bbr5 late + bbr6 late + bbr7 late + bra late + bbs0 late + bbs1 late + bbs2 late + bbs3 late + bbs4 late + bbs5 late + bbs6 late + bbs7 late + bbr0 early + bbr1 early + bbr2 early + bbr3 early + bbr4 early + bbr5 early + bbr6 early + bbr7 early + bra early + bbs0 early + bbs1 early + bbs2 early + bbs3 early + bbs4 early + bbs5 early + bbs6 early + bbs7 early + .advance ^+256 +late: + bbr0 late + bbr1 late + bbr2 late + bbr3 late + bbr4 late + bbr5 late + bbr6 late + bbr7 late + bra late + bbs0 late + bbs1 late + bbs2 late + bbs3 late + bbs4 late + bbs5 late + bbs6 late + bbs7 late + bbr0 early + bbr1 early + bbr2 early + bbr3 early + bbr4 early + bbr5 early + bbr6 early + bbr7 early + bra early + bbs0 early + bbs1 early + bbs2 early + bbs3 early + bbs4 early + bbs5 early + bbs6 early + bbs7 early + diff --git a/tests/branch_c02_ref.oph b/tests/branch_c02_ref.oph new file mode 100644 index 0000000..0c8e0c2 --- /dev/null +++ b/tests/branch_c02_ref.oph @@ -0,0 +1,106 @@ +.text +.org $0800 +early: + bbs0 + + jmp late +* bbs1 + + jmp late +* bbs2 + + jmp late +* bbs3 + + jmp late +* bbs4 + + jmp late +* bbs5 + + jmp late +* bbs6 + + jmp late +* bbs7 + + jmp late +* jmp late +* bbr0 + + jmp late +* bbr1 + + jmp late +* bbr2 + + jmp late +* bbr3 + + jmp late +* bbr4 + + jmp late +* bbr5 + + jmp late +* bbr6 + + jmp late +* bbr7 + + jmp late +* bbr0 early + bbr1 early + bbr2 early + bbr3 early + bbr4 early + bbr5 early + bbr6 early + bbr7 early + bra early + bbs0 early + bbs1 early + bbs2 early + bbs3 early + bbs4 early + bbs5 early + bbs6 early + bbs7 early +.advance ^+256 +late: + bbr0 late + bbr1 late + bbr2 late + bbr3 late + bbr4 late + bbr5 late + bbr6 late + bbr7 late + bra late + bbs0 late + bbs1 late + bbs2 late + bbs3 late + bbs4 late + bbs5 late + bbs6 late + bbs7 late + bbs0 + + jmp early +* bbs1 + + jmp early +* bbs2 + + jmp early +* bbs3 + + jmp early +* bbs4 + + jmp early +* bbs5 + + jmp early +* bbs6 + + jmp early +* bbs7 + + jmp early +* jmp early + bbr0 + + jmp early +* bbr1 + + jmp early +* bbr2 + + jmp early +* bbr3 + + jmp early +* bbr4 + + jmp early +* bbr5 + + jmp early +* bbr6 + + jmp early +* bbr7 + + jmp early +* diff --git a/tests/longbranch.bin b/tests/longbranch.bin new file mode 100644 index 0000000000000000000000000000000000000000..06b78c7033174cde3b6f6aea9d06952613837fac GIT binary patch literal 312 zcmdtdu?c`s3`NloHri#B3}TT{GD=3t7$PF!BhC>-L_|d28s6gcmz%zH1QZBxU?4#N lT6vMGVY5#$=1_hTJ$%chryah>mdW32OF)4D2L=)ZcmWi@BdGuY literal 0 HcmV?d00001 diff --git a/tests/longbranch.oph b/tests/longbranch.oph new file mode 100644 index 0000000..c5c9e7a --- /dev/null +++ b/tests/longbranch.oph @@ -0,0 +1,37 @@ +.text +.org $0800 +early: + bpl late + bmi late + bvc late + bvs late + bcc late + bcs late + bne late + beq late + bpl early + bmi early + bvc early + bvs early + bcc early + bcs early + bne early + beq early +.advance $0900 +late: + bpl late + bmi late + bvc late + bvs late + bcc late + bcs late + bne late + beq late + bpl early + bmi early + bvc early + bvs early + bcc early + bcs early + bne early + beq early diff --git a/tests/longbranch_ref.oph b/tests/longbranch_ref.oph new file mode 100644 index 0000000..72f585d --- /dev/null +++ b/tests/longbranch_ref.oph @@ -0,0 +1,54 @@ +.text +.org $0800 +early: + bmi + + jmp late +* bpl + + jmp late +* bvs + + jmp late +* bvc + + jmp late +* bcs + + jmp late +* bcc + + jmp late +* beq + + jmp late +* bne + + jmp late +* bpl early + bmi early + bvc early + bvs early + bcc early + bcs early + bne early + beq early +.advance $0900 +late: + bpl late + bmi late + bvc late + bvs late + bcc late + bcs late + bne late + beq late + bmi + + jmp early +* bpl + + jmp early +* bvs + + jmp early +* bvc + + jmp early +* bcs + + jmp early +* bcc + + jmp early +* beq + + jmp early +* bne + + jmp early +* \ No newline at end of file