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.
This commit is contained in:
Michael C. Martin 2012-05-24 18:12:35 -07:00
parent 3184b22e41
commit f8bc917601
11 changed files with 360 additions and 24 deletions

View File

@ -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:

View File

@ -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."""

View File

@ -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)

View File

@ -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]

View File

@ -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,

BIN
tests/branch_c02.bin Normal file

Binary file not shown.

74
tests/branch_c02.oph Normal file
View File

@ -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

106
tests/branch_c02_ref.oph Normal file
View File

@ -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
*

BIN
tests/longbranch.bin Normal file

Binary file not shown.

37
tests/longbranch.oph Normal file
View File

@ -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

54
tests/longbranch_ref.oph Normal file
View File

@ -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
*