Commit 5acd670a authored by Jayke Meijer's avatar Jayke Meijer

Fixed merge conflict

parents 16a7f083 b8616183
...@@ -12,3 +12,5 @@ parsetab.py ...@@ -12,3 +12,5 @@ parsetab.py
coverage/ coverage/
build/ build/
src/Makefile_old src/Makefile_old
in.s
out.s
BUILD=build/ BUILD=build/
CLEAN=src/*.pyc src/optimize/*.pyc CLEAN=*.pyc src/*.pyc src/optimize/*.pyc parser.out parsetab.py
# Fix pdflatex search path # Fix pdflatex search path
TGT_DIR := TGT_DIR := report
TGT_DOC :=
# Default target is 'all'. The 'build' target is defined here so that all # Default target is 'all'. The 'build' target is defined here so that all
# sub rules.mk can add prerequisites to the 'build' target. # sub rules.mk can add prerequisites to the 'build' target.
all: all:
build: build:
d := tests/ d := report/
include base.mk include base.mk
include $(d)/rules.mk include $(d)/rules.mk
.PHONY: doc d := tests/
include base.mk
include $(d)/rules.mk
all: doc build all: report
clean: clean:
rm -rf $(CLEAN) rm -rf $(CLEAN)
......
.file 1 "pi.c"
# GNU C 2.7.2.3 [AL 1.1, MM 40, tma 0.1] SimpleScalar running sstrix compiled by GNU C
# Cc1 defaults:
# -mgas -mgpOPT
# Cc1 arguments (-G value = 8, Cpu = default, ISA = 1):
# -quiet -dumpbase -o
gcc2_compiled.:
__gnu_compiled_c:
.rdata
.align 2
$LC0:
.ascii "Usage: %s <iterations>\n\000"
.sdata
.align 2
$LC3:
.ascii "%.10f\n\000"
.align 3
$LC1:
.word 0xffc00000 # 2147483647
.word 0x41dfffff
.align 3
$LC2:
.word 0x00000000 # 1
.word 0x3ff00000
.align 3
$LC4:
.word 0x00000000 # 4
.word 0x40100000
.text
.align 2
.globl main
.extern stderr, 4
.text
.loc 1 5
.ent main
main:
.frame $fp,56,$31 # vars= 32, regs= 2/0, args= 16, extra= 0
.mask 0xc0000000,-4
.fmask 0x00000000,0
subu $sp,$sp,56
sw $31,52($sp)
sw $fp,48($sp)
move $fp,$sp
sw $4,56($fp)
sw $5,60($fp)
jal __main
sw $0,24($fp)
lw $2,56($fp)
li $3,0x00000002 # 2
beq $2,$3,$L2
lw $2,60($fp)
lw $4,stderr
la $5,$LC0
lw $6,0($2)
jal fprintf
move $4,$0
jal exit
$L2:
lw $3,60($fp)
addu $2,$3,4
lw $4,0($2)
jal atoi
sw $2,20($fp)
li $4,0x00000001 # 1
jal srandom
sw $0,16($fp)
$L3:
lw $2,16($fp)
lw $3,20($fp)
slt $2,$2,$3
bne $2,$0,$L6
j $L4
$L6:
jal random
mtc1 $2,$f0
#nop
cvt.d.w $f0,$f0
l.d $f2,$LC1
div.d $f0,$f0,$f2
s.d $f0,32($fp)
jal random
mtc1 $2,$f0
#nop
cvt.d.w $f0,$f0
l.d $f2,$LC1
div.d $f0,$f0,$f2
s.d $f0,40($fp)
l.d $f0,32($fp)
l.d $f2,32($fp)
mul.d $f0,$f0,$f2
l.d $f2,40($fp)
l.d $f4,40($fp)
mul.d $f2,$f2,$f4
add.d $f0,$f0,$f2
l.d $f2,$LC2
c.le.d $f0,$f2
bc1f $L7
lw $3,24($fp)
addu $2,$3,1
move $3,$2
sw $3,24($fp)
$L7:
$L5:
lw $3,16($fp)
addu $2,$3,1
move $3,$2
sw $3,16($fp)
j $L3
$L4:
l.s $f0,24($fp)
#nop
cvt.d.w $f0,$f0
l.s $f2,20($fp)
#nop
cvt.d.w $f2,$f2
div.d $f0,$f0,$f2
l.d $f2,$LC4
mul.d $f0,$f0,$f2
la $4,$LC3
dmfc1 $6,$f0
jal printf
li $2,0x00000001 # 1
j $L1
$L1:
move $sp,$fp # sp not trusted here
lw $31,52($sp)
lw $fp,48($sp)
addu $sp,$sp,56
j $31
.end main
\ No newline at end of file
#include <stdio.h>
int main(void)
{
int a = 3, b = 5, d = 5, x = 100;
int c;
if (a > b)
{
int c = a + b;
d = 2;
}
c = 4;
return b * d + c;
}
#!/usr/bin/python #!/usr/bin/python
from src.parser import parse_file from src.parser import parse_file
from src.optimize import optimize from src.optimize import optimize
from src.writer import write_statements
if __name__ == '__main__': if __name__ == '__main__':
from sys import argv, exit from sys import argv, exit
if len(argv) < 2: if len(argv) < 2:
print 'Usage: python %s FILE' % argv[0] print 'Usage: python %s SOURCE_FILE [ OUT_FILE [ SOURCE_OUT_FILE ] ]' \
% argv[0]
exit(1) exit(1)
# Parse File # Parse file
original = parse_file(argv[1]) program = parse_file(argv[1])
optimized = optimize(original, verbose=1) program.debug = True
if len(argv) > 3:
# Save input assembly in new file for easy comparison
program.save(argv[3])
optimize(program, verbose=1)
if len(argv) > 2: if len(argv) > 2:
# Save output assembly # Save output assembly
out = write_statements(optimized) program.save(argv[2])
f = open(argv[2], 'w+')
f.write(out)
f.close()
RM=rm -rf RM=rm -rf
all: report.pdf report: report.pdf
%.pdf: %.tex %.pdf: %.tex
pdflatex $^ pdflatex $^
......
\documentclass[10pt,a4paper]{article} \documentclass[10pt,a4paper]{article}
\usepackage[latin1]{inputenc} \usepackage[latin1]{inputenc}
\usepackage{amsmath} \usepackage{amsmath,amsfonts,amssymb,booktabs,graphicx,listings,subfigure}
\usepackage{amsfonts} \usepackage{float,hyperref}
\usepackage{amssymb}
\usepackage{booktabs}
\usepackage{graphicx}
\usepackage{listings}
\usepackage{subfigure}
\usepackage{float}
\usepackage{hyperref}
\title{Peephole Optimizer} \title{Peephole Optimizer}
\author{Jayke Meijer (6049885), Richard Torenvliet (6138861), Tadde\"us Kroes \author{Jayke Meijer (6049885), Richard Torenvliet (6138861), Tadde\"us Kroes
(6054129)} (6054129)}
\begin{document} \begin{document}
\maketitle \maketitle
\tableofcontents \tableofcontents
\pagebreak \pagebreak
\section{Introduction} \section{Introduction}
...@@ -35,7 +30,7 @@ the keywords in to an action. ...@@ -35,7 +30,7 @@ the keywords in to an action.
\section{Design} \section{Design}
There are two general types of of optimizations of the assembly code, global There are two general types of optimizations of the assembly code, global
optimizations and optimizations on a so-called basic block. These optimizations optimizations and optimizations on a so-called basic block. These optimizations
will be discussed separately will be discussed separately
...@@ -57,8 +52,7 @@ of course be done for the opposite case, where a \texttt{bne} is changed into a ...@@ -57,8 +52,7 @@ of course be done for the opposite case, where a \texttt{bne} is changed into a
\texttt{beq}. \texttt{beq}.
Since this optimization is done between two series of codes with jumps and Since this optimization is done between two series of codes with jumps and
labels, we can not perform this code during the basic block optimizations. The labels, we can not perform this code during the basic block optimizations.
reason for this will become clearer in the following section.
\subsection{Basic Block Optimizations} \subsection{Basic Block Optimizations}
...@@ -81,7 +75,7 @@ These are optimizations that simply look for a certain statement or pattern of ...@@ -81,7 +75,7 @@ These are optimizations that simply look for a certain statement or pattern of
statements, and optimize these. For example, statements, and optimize these. For example,
\begin{verbatim} \begin{verbatim}
mov $regA,$regB mov $regA,$regB
instr $regA, $regA,... instr $regA, $regA,...
\end{verbatim} \end{verbatim}
can be optimized into can be optimized into
\begin{verbatim} \begin{verbatim}
...@@ -99,6 +93,15 @@ Appendix \ref{opt}. ...@@ -99,6 +93,15 @@ Appendix \ref{opt}.
A more advanced optimization is common subexpression elimination. This means A more advanced optimization is common subexpression elimination. This means
that expensive operations as a multiplication or addition are performed only that expensive operations as a multiplication or addition are performed only
once and the result is then `copied' into variables where needed. once and the result is then `copied' into variables where needed.
\begin{verbatim}
addu $2,$4,$3 addu = $t1, $4, $3
... mov = $2, $t1
... -> ...
... ...
addu $5,$4,$3 mov = $4, $t1
\end{verbatim}
A standard method for doing this is the creation of a DAG or Directed Acyclic A standard method for doing this is the creation of a DAG or Directed Acyclic
Graph. However, this requires a fairly advanced implementation. Our Graph. However, this requires a fairly advanced implementation. Our
...@@ -112,27 +115,34 @@ We now add the instruction above the first use, and write the result in a new ...@@ -112,27 +115,34 @@ We now add the instruction above the first use, and write the result in a new
variable. Then all occurrences of this expression can be replaced by a move of variable. Then all occurrences of this expression can be replaced by a move of
from new variable into the original destination variable of the instruction. from new variable into the original destination variable of the instruction.
This is a less efficient method then the DAG, but because the basic blocks are This is a less efficient method then the dag, but because the basic blocks are
in general not very large and the execution time of the optimizer is not a in general not very large and the execution time of the optimizer is not a
primary concern, this is not a big problem. primary concern, this is not a big problem.
\subsubsection*{Constant folding} \subsubsection*{Fold constants}
Constant folding is an optimization where the outcome of arithmetics are
calculated at compile time. If a value x is assigned to a certain value, lets
say 10, than all next occurences of \texttt{x} are replaced by 10 until a
redefinition of x. Arithmetics in Assembly are always performed between two
variables or a variable and a constant. If this is not the case the calculation
is not possible. See \ref{opt} for an example. In other words until the current
definition of \texttt{x} becomes dead. Therefore reaching definitions analysis
is needed. Reaching definitions is a form of liveness analysis, we use the
liveness analysis within a block and not between blocks.
During the constant folding, so-called algebraic transformations are performed
as well. Some expression can easily be replaced with more simple once if you
look at what they are saying algebraically. An example is the statement
$x = y + 0$, or in Assembly \texttt{addu \$1, \$2, 0}. This can easily be
changed into $x = y$ or \texttt{move \$1, \$2}.
Another optimization is to do constant folding. Constant folding is replacing Another case is the multiplication with a power of two. This can be done way
a expensive step like addition with a more simple step like loading a constant. more efficiently by shifting left a number of times. An example:
Of course, this is not always possible. It is possible in cases where you apply \texttt{mult \$regA, \$regB, 4 -> sll \$regA, \$regB, 2}. We perform this
an operation on two constants, or a constant and a variable of which you know optimization for any multiplication with a power of two.
for sure that it always has a certain value at that point. For example:
\begin{verbatim}
li $regA, 1 li $regA, 1
addu $regB, $regA, 2 -> li $regB, 3
\end{verbatim}
Of course, if \texttt{\$regA} is not used after this, it can be removed, which
will be done by the dead code elimination.
One problem we encountered with this is that the use of a \texttt{li} is that There are a number of such cases, all of which are once again stated in
the program often also stores this in the memory, so we had to check whether appendix \ref{opt}.
this was necessary here as well.
\subsubsection*{Copy propagation} \subsubsection*{Copy propagation}
...@@ -159,30 +169,29 @@ of the move operation. ...@@ -159,30 +169,29 @@ of the move operation.
An example would be the following: An example would be the following:
\begin{verbatim} \begin{verbatim}
move $regA, $regB move $regA, $regB move $regA, $regB move $regA, $regB
... ... ... ...
Code not writing $regA, $regB -> ... Code not writing $regA, -> ...
... ... $regB ...
addu $regC, $regA, ... addu $regC, $regB, ... ... ...
addu $regC, $regA, ... addu $regC, $regB, ...
\end{verbatim} \end{verbatim}
This code shows that \texttt{\$regA} is replaced with \texttt{\$regB}. This This code shows that \texttt{\$regA} is replaced with \texttt{\$regB}. This
way, the move instruction might have become useless, and it will then be way, the move instruction might have become useless, and it will then be
removed by the dead code elimination. removed by the dead code elimination.
\subsubsection*{Algebraic transformations} \subsection{Dead code elimination}
Some expression can easily be replaced with more simple once if you look at The final optimization that is performed is dead code elimination. This means
what they are saying algebraically. An example is the statement $x = y + 0$, or that when an instruction is executed, but the result is never used, that
in Assembly \texttt{addu \$1, \$2, 0}. This can easily be changed into $x = y$ instruction can be removed.
or \texttt{move \$1, \$2}.
Another case is the multiplication with a power of two. This can be done way To be able to properly perform dead code elimination, we need to know whether a
more efficiently by shifting left a number of times. An example: variable will be used, before it is overwritten again. If it does, we call the
\texttt{mult \$regA, \$regB, 4 -> sll \$regA, \$regB, 2}. We perform this variable live, otherwise the variable is dead. The technique to find out if a
optimization for any multiplication with a power of two. variable is live is called liveness analysis. We implemented this for the
entire code, by analyzing each block, and using the variables that come in the
There are a number of such cases, all of which are once again stated in block live as the variables that exit its predecessor live.
appendix \ref{opt}.
\section{Implementation} \section{Implementation}
...@@ -206,7 +215,7 @@ languages like we should do otherwise since Lex and Yacc are coupled with C. ...@@ -206,7 +215,7 @@ languages like we should do otherwise since Lex and Yacc are coupled with C.
The decision was made to not recognize exactly every possible instruction in The decision was made to not recognize exactly every possible instruction in
the parser, but only if something is for example a command, a comment or a gcc the parser, but only if something is for example a command, a comment or a gcc
directive. We then transform per line to a object called a Statement. A directive. We then transform per line to an object called a Statement. A
statement has a type, a name and optionally a list of arguments. These statement has a type, a name and optionally a list of arguments. These
statements together form a statement list, which is placed in another object statements together form a statement list, which is placed in another object
called a Block. In the beginning there is one block for the entire program, but called a Block. In the beginning there is one block for the entire program, but
...@@ -219,7 +228,7 @@ The optimizations are done in two different steps. First the global ...@@ -219,7 +228,7 @@ The optimizations are done in two different steps. First the global
optimizations are performed, which are only the optimizations on branch-jump optimizations are performed, which are only the optimizations on branch-jump
constructions. This is done repeatedly until there are no more changes. constructions. This is done repeatedly until there are no more changes.
After all possible global optimizations are done, the program is separated into After all possible global optimizations are done, the program is seperated into
basic blocks. The algorithm to do this is described earlier, and means all basic blocks. The algorithm to do this is described earlier, and means all
jump and branch instructions are called leaders, as are their targets. A basic jump and branch instructions are called leaders, as are their targets. A basic
block then goes from leader to leader. block then goes from leader to leader.
...@@ -231,26 +240,71 @@ steps can be done to optimize something. ...@@ -231,26 +240,71 @@ steps can be done to optimize something.
\subsection{Writing} \subsection{Writing}
Once all the optimizations have been done, the IR needs to be rewritten into Once all the optimizations have been done, the IR needs to be rewritten into
Assembly code, so the xgcc cross compiler can make binary code out of it. Assembly code. After this step the xgcc crosscompiler can make binary code from
the generated Assembly code.
The writer expects a list of statements, so first the blocks have to be The writer expects a list of statements, so first the blocks have to be
concatenated again into a list. After this is done, the list is passed on to concatenated again into a list. After this is done, the list is passed on to
the writer, which writes the instructions back to Assembly and saves the file the writer, which writes the instructions back to Assembly and saves the file
so we can let xgcc compile it. so we can let xgcc compile it. We also write the original statements to a file,
so differences in tabs, spaces and newlines do not show up when we check the
differences between the optimized and non-optimized files.
\section{Results} \subsection{Execution}
\subsection{pi.c} To execute the optimizer, the following command can be given:\\
\texttt{./main <original file> <optimized file> <rewritten original file>}
\subsection{acron.c} \section{Testing}
\subsection{whet.c} Of course, it has to be guaranteed that the optimized code still functions
exactly the same as the none-optimized code. To do this, testing is an
important part of out program. We have two stages of testing. The first stage
is unit testing. The second stage is to test whether the compiled code has
exactly the same output.
\subsection{slalom.c} \subsection{Unit testing}
\subsection{clinpack.c} For almost every piece of important code, unit tests are available. Unit tests
give the possibility to check whether each small part of the program, for
instance each small function, is performing as expected. This way bugs are
found early and very exactly. Otherwise, one would only see that there is a
mistake in the program, not knowing where this bug is. Naturally, this means
debugging is a lot easier.
The unit tests can be run by executing \texttt{make test} in the root folder of
the project. This does require the \texttt{textrunner} module.
Also available is a coverage report. This report shows how much of the code has
been unit tested. To make this report, the command \texttt{make coverage} can
be run in the root folder. The report is than added as a folder \emph{coverage}
in which a \emph{index.html} can be used to see the entire report.
\subsection{Ouput comparison}
In order to check whether the optimization does not change the functioning of
the program, the output of the provided benchmark programs has to be compared
to the output after optimization. If any of these outputs is not equal to the
original output, our optimizations are to aggressive, or there is a bug
somewhere in the code.
\section{Results}
The following results have been obtained:\\
\begin{tabular}{|c|c|c|c|c|c|}
\hline
Benchmark & Original & Optimized & Original & Optimized & Performance \\
& Instructions & instructions & cycles & cycles & boost(cycles)\\
\hline
pi & 134 & & & & \\
acron & & & & & \\
dhrystone & & & & & \\
whet & & & & & \\
slalom & & & & & \\
clinpack & & & & & \\
\hline
\end{tabular}
\section{Conclusion}
\appendix \appendix
...@@ -307,7 +361,13 @@ addu $regC, $regB, 4 move $regC, $regD ...@@ -307,7 +361,13 @@ addu $regC, $regB, 4 move $regC, $regD
# Constant folding # Constant folding
li $regA, constA ""
sw $regA, 16($fp) ""
li $regA, constB -> ""
sw $regA, 20($fp) ""
lw $regA, 16($fp) ""
lw $regB, 20($fp) ""
addu $regA, $regA, $regA $li regA, (constA + constB) at compile time
# Copy propagation # Copy propagation
move $regA, $regB move $regA, $regB move $regA, $regB move $regA, $regB
...@@ -329,4 +389,5 @@ mult $regA, $regB, 0 -> li $regA, 0 ...@@ -329,4 +389,5 @@ mult $regA, $regB, 0 -> li $regA, 0
mult $regA, $regB, 2 -> sll $regA, $regB, 1 mult $regA, $regB, 2 -> sll $regA, $regB, 1
\end{verbatim} \end{verbatim}
\end{document} \end{document}
CLEAN := $(CLEAN) report/*.pdf report/*.aux report/*.log \
report/*.out report/*.toc report/*.snm report/*.nav
report: report/report.pdf
report/%.pdf: report/%.tex
cd report; \
pdflatex report.tex; \
pdflatex report.tex
#!/bin/sh
python main.py benchmarks/build/$1.s out.s in.s && meld in.s out.s
class Dag:
def __init__(self, block):
"""Create the Directed Acyclic Graph of all binary operations in a
basic block."""
self.nodes = []
for s in block:
if s.is_command('move') or s.is_monop():
rd, rs = s
y = self.find_reg_node(rs)
self.find_op_node(s.name, rd, y)
elif s.is_binop():
rd, rs, rt = s
y = self.find_reg_node(rs)
z = self.find_reg_node(rt)
self.find_op_node(s.name, rd, y, z)
def find_reg_node(self, reg):
for n in self.nodes:
if reg in n.reg:
return n
node = DagLeaf(reg)
self.nodes.append(node)
return node
def find_op_node(self, op, rd, *args):
for n in self.nodes:
if not isinstance(n, DagLeaf) and n.op == op and n.nodes == args:
n.labels.append(rd)
return n
node = DagNode(op, rd, *args)
self.nodes.append(node)
return node
class DagNode:
def __init__(self, op, label, *args):
self.op = op
self.labels = [label]
self.nodes = args
class DagLeaf:
def __init__(self, reg):
self.reg = reg
from copy import copy
from statement import Block from statement import Block
...@@ -11,10 +9,6 @@ class BasicBlock(Block): ...@@ -11,10 +9,6 @@ class BasicBlock(Block):
self.dominates = [] self.dominates = []
self.dominated_by = [] self.dominated_by = []
self.in_set = set([])
self.out_set = set([])
self.gen_set = set([])
self.kill_set = set([])
def add_edge_to(self, block): def add_edge_to(self, block):
if block not in self.edges_to: if block not in self.edges_to:
...@@ -26,84 +20,6 @@ class BasicBlock(Block): ...@@ -26,84 +20,6 @@ class BasicBlock(Block):
self.dominates.append(block) self.dominates.append(block)
block.dominated_by.append(self) block.dominated_by.append(self)
def create_gen_kill(self, defs):
used = set()
self_defs = {}
# Get the last of each definition series and put in in the `def' set
self.gen_set = set()
for s in reversed(self):
for reg in s.get_def():
if reg not in self_defs:
print 'Found def:', s
self_defs[reg] = s.sid
self.gen_set.add(s.sid)
# Generate kill set
self.kill_set = set()
for reg, statement_ids in defs.iteritems():
if reg in self_defs:
add = statement_ids - set([self_defs[reg]])
else:
add = statement_ids
self.kill_set |= add
def defs(blocks):
# Collect definitions of all registers
defs = {}
for b in blocks:
for s in b:
for reg in s.get_def():
if reg not in defs:
defs[reg] = set([s.sid])
else:
defs[reg].add(s.sid)
return defs
def reaching_definitions(blocks):
"""Generate the `in' and `out' sets of the given blocks using the iterative
algorithm from the slides."""
defs = defs(blocks)
for b in blocks:
b.create_gen_kill(defs)
b.out_set = b.gen_set
change = True
while change:
change = False
for b in blocks:
b.in_set = set()
for pred in b.edges_from:
b.in_set |= pred.out_set
oldout = copy(p.out_set)
p.out_set = b.gen_set | (b.in_set - b.kill_set)
if b.out_set != oldout:
change = True
def pred(n, known=[]):
"""Recursively find all predecessors of a node."""
direct = filter(lambda b: b not in known, n.edges_from)
p = copy(direct)
for ancestor in direct:
p += pred(ancestor, direct)
return p
def find_leaders(statements): def find_leaders(statements):
"""Determine the leaders, which are: """Determine the leaders, which are:
...@@ -160,109 +76,10 @@ def generate_flow_graph(blocks): ...@@ -160,109 +76,10 @@ def generate_flow_graph(blocks):
if other[0].is_label(target): if other[0].is_label(target):
b.add_edge_to(other) b.add_edge_to(other)
# A branch instruction also creates an edge to the next block # A branch and jump-and-line instruction also creates an edge to
if last_statement.is_branch() and i < len(blocks) - 1: # the next block
if (last_statement.is_branch() or last_statement.name == 'jal') \
and i < len(blocks) - 1:
b.add_edge_to(blocks[i + 1]) b.add_edge_to(blocks[i + 1])
elif i < len(blocks) - 1: elif i < len(blocks) - 1:
b.add_edge_to(blocks[i + 1]) b.add_edge_to(blocks[i + 1])
#def generate_dominator_tree(nodes):
# """Add dominator administration to the given flow graph nodes."""
# # Dominator of the start node is the start itself
# nodes[0].dom = set([nodes[0]])
#
# # For all other nodes, set all nodes as the dominators
# for n in nodes[1:]:
# n.dom = set(copy(nodes))
#
# def pred(n, known=[]):
# """Recursively find all predecessors of a node."""
# direct = filter(lambda x: x not in known, n.edges_from)
# p = copy(direct)
#
# for ancestor in direct:
# p += pred(ancestor, direct)
#
# return p
#
# # Iteratively eliminate nodes that are not dominators
# changed = True
#
# while changed:
# changed = False
#
# for n in nodes[1:]:
# old_dom = n.dom
# intersection = lambda p1, p2: p1.dom & p2.dom
# n.dom = set([n]) | reduce(intersection, pred(n), set([]))
#
# if n.dom != old_dom:
# changed = True
#
# def idom(d, n):
# """Check if d immediately dominates n."""
# for b in n.dom:
# if b != d and b != n and b in n.dom:
# return False
#
# return True
#
# # Build tree using immediate dominators
# for n in nodes:
# for d in n.dom:
# if idom(d, n):
# d.set_dominates(n)
# break
class Dag:
def __init__(self, block):
"""Create the Directed Acyclic Graph of all binary operations in a
basic block."""
self.nodes = []
for s in block:
if s.is_command('move') or s.is_monop():
rd, rs = s
y = self.find_reg_node(rs)
self.find_op_node(s.name, rd, y)
elif s.is_binop():
rd, rs, rt = s
y = self.find_reg_node(rs)
z = self.find_reg_node(rt)
self.find_op_node(s.name, rd, y, z)
def find_reg_node(self, reg):
for n in self.nodes:
if reg in n.reg:
return n
node = DagLeaf(reg)
self.nodes.append(node)
return node
def find_op_node(self, op, rd, *args):
for n in self.nodes:
if not isinstance(n, DagLeaf) and n.op == op and n.nodes == args:
n.labels.append(rd)
return n
node = DagNode(op, rd, *args)
self.nodes.append(node)
return node
class DagNode:
def __init__(self, op, label, *args):
self.op = op
self.labels = [label]
self.nodes = args
class DagLeaf:
def __init__(self, reg):
self.reg = reg
from copy import copy
def generate_dominator_tree(nodes):
"""Add dominator administration to the given flow graph nodes."""
# Dominator of the start node is the start itself
nodes[0].dom = set([nodes[0]])
# For all other nodes, set all nodes as the dominators
for n in nodes[1:]:
n.dom = set(copy(nodes))
def pred(n, known=[]):
"""Recursively find all predecessors of a node."""
direct = filter(lambda x: x not in known, n.edges_from)
p = copy(direct)
for ancestor in direct:
p += pred(ancestor, direct)
return p
# Iteratively eliminate nodes that are not dominators
changed = True
while changed:
changed = False
for n in nodes[1:]:
old_dom = n.dom
intersection = lambda p1, p2: p1.dom & p2.dom
n.dom = set([n]) | reduce(intersection, pred(n), set([]))
if n.dom != old_dom:
changed = True
def idom(d, n):
"""Check if d immediately dominates n."""
for b in n.dom:
if b != d and b != n and b in n.dom:
return False
return True
# Build tree using immediate dominators
for n in nodes:
for d in n.dom:
if idom(d, n):
d.set_dominates(n)
break
from copy import copy
RESERVED_REGISTERS = ['$fp', '$sp', '$31']
def is_reg_dead_after(reg, block, index):
"""Check if a register is dead after a certain point in a basic block."""
if reg in RESERVED_REGISTERS:
return False
if index < len(block) - 1:
for s in block[index + 1:]:
# If used, the previous definition is live
if s.uses(reg):
return False
# If redefined, the previous definition is dead
if s.defines(reg):
return True
# If dead within the same block, check if the register is in the block's
# live_out set
return reg not in block.live_out
def create_use_def(block):
used = set()
defined = set()
# Get the last of each definition series and put in in the `def' set
block.use_set = set()
block.def_set = set()
for s in block:
# use[B] is the set of variables whose values may be used in B prior to
# any definition of the variable
for reg in s.get_use():
used.add(reg)
if reg not in defined:
block.use_set.add(reg)
# def[B] is the set of variables assigned values in B prior to any use
# of that variable in B
for reg in s.get_def():
defined.add(reg)
if reg not in used:
block.def_set.add(reg)
def succ(block, known=[]):
"""Recursively find all successors of a node."""
direct = filter(lambda b: b != block and b not in known, block.edges_to)
s = copy(direct)
for successor in direct:
s += succ(successor, known + direct)
return s
return s
def create_in_out(blocks):
for b in blocks:
create_use_def(b)
b.live_in = b.use_set
b.live_out = set()
change = True
while change:
change = False
for b in blocks:
# in[B] = use[B] | (out[B] - def[B])
new_in = b.use_set | (b.live_out - b.def_set)
# out[B] = union of in[S] for S in succ(B)
new_out = set()
for s in succ(b):
new_out |= s.live_in
# Check if either `in' or `out' changed
if new_in != b.live_in:
b.live_in = new_in
change = True
if new_out != b.live_out:
b.live_out = new_out
change = True
from src.dataflow import find_basic_blocks from src.dataflow import find_basic_blocks, generate_flow_graph
from redundancies import remove_redundancies
from redundancies import remove_redundant_jumps, move_1, move_2, move_3, \
move_4, load, shift, add
from advanced import eliminate_common_subexpressions, fold_constants, \ from advanced import eliminate_common_subexpressions, fold_constants, \
copy_propagation, algebraic_transformations, eliminate_dead_code copy_propagation, eliminate_dead_code
import src.liveness as liveness
import src.reaching_definitions as reaching_definitions
def remove_redundancies(block):
"""Execute all functions that remove redundant statements."""
callbacks = [move_1, move_2, move_3, move_4, load, shift, add]
old_len = -1
changed = False
while old_len != len(block):
old_len = len(block)
while not block.end():
s = block.read()
for callback in callbacks:
if callback(s, block):
changed = True
break
return changed
def optimize(program, verbose=0):
def optimize_block(block):
"""Optimize a basic block."""
while remove_redundancies(block) \
| eliminate_common_subexpressions(block) \
| fold_constants(block) \
| copy_propagation(block)\
| algebraic_transformations(block) \
| eliminate_dead_code(block):
pass
def optimize(statements, verbose=0):
"""Optimization wrapper function, calls global and basic-block level """Optimization wrapper function, calls global and basic-block level
optimization functions.""" optimization functions."""
# Remember original number of statements
o = program.count_instructions()
# Optimize on a global level # Optimize on a global level
o = len(statements) program.optimize_global()
remove_redundant_jumps(statements) g = program.count_instructions()
g = len(statements)
# Perform dataflow analysis
program.perform_dataflow_analysis()
# Optimize basic blocks # Optimize basic blocks
blocks = find_basic_blocks(statements) program.optimize_blocks()
map(optimize_block, blocks)
block_statements = map(lambda b: b.statements, blocks)
opt_blocks = reduce(lambda a, b: a + b, block_statements)
b = len(opt_blocks)
# - Common subexpression elimination # Concatenate optimized blocks to obtain
# - Constant folding b = program.count_instructions()
# - Copy propagation
# - Dead-code elimination
# - Temporary variable renaming
# - Interchange of independent statements
# Print results
if verbose: if verbose:
print 'Original statements: %d' % o print 'Original statements: %d' % o
print 'After global optimization: %d' % g print 'After global optimization: %d (%d removed)' % (g, o - g)
print 'After basic blocks optimization: %d' % b print 'After basic block optimization: %d (%d removed)' % (b, g - b)
print 'Optimization: %d (%d%%)' \ print 'Statements removed: %d (%d%%)' \
% (o - b, int((o - b) / float(b) * 100)) % (o - b, int((o - b) / float(b) * 100))
return opt_blocks
from src.statement import Statement as S
from math import log from math import log
from src.statement import Statement as S
from src.liveness import is_reg_dead_after
def reg_can_be_used_in(reg, block, start, end): def reg_can_be_used_in(reg, block, start, end):
"""Check if a register addres safely be used in a block section using local """Check if a register addres safely be used in a block section using local
...@@ -18,7 +20,7 @@ def reg_can_be_used_in(reg, block, start, end): ...@@ -18,7 +20,7 @@ def reg_can_be_used_in(reg, block, start, end):
elif s.defines(reg): elif s.defines(reg):
return True return True
return True return reg not in block.live_out
def find_free_reg(block, start, end): def find_free_reg(block, start, end):
...@@ -50,6 +52,8 @@ def eliminate_common_subexpressions(block): ...@@ -50,6 +52,8 @@ def eliminate_common_subexpressions(block):
""" """
changed = False changed = False
block.reset()
while not block.end(): while not block.end():
s = block.read() s = block.read()
...@@ -77,15 +81,20 @@ def eliminate_common_subexpressions(block): ...@@ -77,15 +81,20 @@ def eliminate_common_subexpressions(block):
new_reg = find_free_reg(block, occurrences[0], occurrences[-1]) new_reg = find_free_reg(block, occurrences[0], occurrences[-1])
# Replace all occurrences with a move statement # Replace all occurrences with a move statement
message = 'Common subexpression reference: %s %s' \
% (s.name, ', '.join(map(str, [new_reg] + s[1:])))
for occurrence in occurrences: for occurrence in occurrences:
rd = block[occurrence][0] rd = block[occurrence][0]
block.replace(1, [S('command', 'move', rd, new_reg)], \ block.replace(1, [S('command', 'move', rd, new_reg)], \
start=occurrence) start=occurrence, message=message)
# Insert the calculation before the original with the new # Insert the calculation before the original with the new
# destination address # destination address
message = 'Common subexpression: %s %s' \
% (s.name, ', '.join(map(str, s)))
block.insert(S('command', s.name, *([new_reg] + args)), \ block.insert(S('command', s.name, *([new_reg] + args)), \
index=occurrences[0]) index=occurrences[0], message=message)
changed = True changed = True
...@@ -123,71 +132,124 @@ def fold_constants(block): ...@@ -123,71 +132,124 @@ def fold_constants(block):
# Current known values in register # Current known values in register
register = {} register = {}
block.reset()
while not block.end(): while not block.end():
s = block.read() s = block.read()
known = []
if not s.is_command(): if not s.is_command():
continue continue
if s.name == 'li': if s.name == 'li':
# Save value in register # Save value in register
register[s[0]] = int(s[1], 16) if not isinstance(s[1], int): # Negative numbers are stored as int
register[s[0]] = int(s[1], 16)
else:
register[s[0]] = s[1]
known.append((s[0], register[s[0]]))
elif s.name == 'move' and s[0] in register: elif s.name == 'move' and s[0] in register:
reg_to, reg_from = s reg_to, reg_from = s
if reg_from in register: if reg_from in register:
# Other value is also known, copy its value # Other value is also known, copy its value
register[reg_to] = register[reg_from] register[reg_to] = register[reg_from]
known.append((reg_to, register[reg_to]))
else: else:
# Other value is unknown, delete the value # Other value is unknown, delete the value
del register[reg_to] del register[reg_to]
known.append((reg_to, 'unknown'))
elif s.name == 'sw' and s[0] in register: elif s.name == 'sw' and s[0] in register:
# Constant variable definition, e.g. 'int a = 1;' # Constant variable definition, e.g. 'int a = 1;'
constants[s[1]] = register[s[0]] constants[s[1]] = register[s[0]]
known.append((s[1], register[s[0]]))
elif s.name == 'lw' and s[1] in constants: elif s.name == 'lw' and s[1] in constants:
# Usage of variable with constant value # Usage of variable with constant value
register[s[0]] = constants[s[1]] register[s[0]] = constants[s[1]]
elif s.name == 'mflo': known.append((s[0], register[s[0]]))
elif s.name == 'mflo' and '$lo' in register:
# Move of `Lo' register to another register # Move of `Lo' register to another register
register[s[0]] = register['Lo'] register[s[0]] = register['$lo']
elif s.name == 'mfhi': known.append((s[0], register[s[0]]))
elif s.name == 'mfhi' and '$hi' in register:
# Move of `Hi' register to another register # Move of `Hi' register to another register
register[s[0]] = register['Hi'] register[s[0]] = register['$hi']
elif s.name in ['mult', 'div'] \ known.append((s[0], register[s[0]]))
and s[0] in register and s[1] in register: elif s.name == 'mult' and s[0]in register and s[1] in register:
# Multiplication/division with constants # Multiplication/division with constants
rs, rt = s rs, rt = s
a, b = register[rs], register[rt]
if s.name == 'mult':
binary = bin(register[rs] * register[rt])[2:] if not a or not b:
# Multiplication by 0
hi = lo = to_hex(0)
message = 'Multiplication by 0: %d * 0' % (b if a else a)
elif a == 1:
# Multiplication by 1
hi = to_hex(0)
lo = to_hex(b)
message = 'Multiplication by 1: %d * 1' % b
elif b == 1:
# Multiplication by 1
hi = to_hex(0)
lo = to_hex(a)
message = 'Multiplication by 1: %d * 1' % a
else:
# Calculate result and fill Hi/Lo registers
result = a * b
binary = bin(result)[2:]
binary = '0' * (64 - len(binary)) + binary binary = '0' * (64 - len(binary)) + binary
register['Hi'] = int(binary[:32], base=2) hi = int(binary[:32], base=2)
register['Lo'] = int(binary[32:], base=2) lo = int(binary[32:], base=2)
elif s.name == 'div': message = 'Constant multiplication: %d * %d = %d' \
register['Lo'], register['Hi'] = divmod(rs, rt) % (a, b, result)
elif s.name in ['addu', 'subu']:
# Replace the multiplication with two immidiate loads to the
# Hi/Lo registers
block.replace(1, [S('command', 'li', '$hi', hi),
S('command', 'li', '$lo', li)],
message=message)
register['$lo'], register['$hi'] = lo, hi
known += [('$lo', lo), ('$hi', hi)]
changed = True
elif s.name in ['addu', 'subu', 'div']:
# Addition/subtraction with constants # Addition/subtraction with constants
rd, rs, rt = s rd, rs, rt = s
rs_known = rs in register rs_known = rs in register
rt_known = rt in register rt_known = rt in register
if rs_known and rt_known: if (rs_known or isinstance(rs, int)) and \
(rt_known or isinstance(rt, int)):
# a = 5 -> b = 15 # a = 5 -> b = 15
# c = 10 # c = 10
# b = a + c # b = a + c
rs_val = register[rs] rs_val = register[rs] if rs_known else rs
rt_val = register[rt] rt_val = register[rt] if rt_known else rt
if s.name == 'addu': if s.name == 'addu':
result = to_hex(rs_val + rt_val) result = rs_val + rt_val
message = 'Constant addition: %d + %d = %d' \
% (rs_val, rt_val, result)
if s.name == 'subu': if s.name == 'subu':
result = to_hex(rs_val - rt_val) result = rs_val - rt_val
message = 'Constant subtraction: %d - %d = %d' \
% (rs_val, rt_val, result)
block.replace(1, [S('command', 'li', rd, result)]) if s.name == 'div':
result = rs_val / rt_val
message = 'Constant division: %d - %d = %d' \
% (rs_val, rt_val, result)
block.replace(1, [S('command', 'li', rd, to_hex(result))],
message=message)
register[rd] = result register[rd] = result
known.append((rd, result))
changed = True changed = True
elif rt_known: continue
if rt_known:
# a = 10 -> b = c + 10 # a = 10 -> b = c + 10
# b = c + a # b = c + a
s[2] = register[rt] s[2] = register[rt]
...@@ -198,9 +260,22 @@ def fold_constants(block): ...@@ -198,9 +260,22 @@ def fold_constants(block):
s[1] = rt s[1] = rt
s[2] = register[rs] s[2] = register[rs]
changed = True changed = True
elif len(s) and s[0] in register:
# Known register is overwritten, remove its value if s[2] == 0:
del register[s[0]] # Addition/subtraction by 0
message = '%s by 0: %s * 1' % ('Addition' if s.name == 'addu' \
else 'Substraction', s[1])
block.replace(1, [S('command', 'move', rd, s[1])], \
message=message)
else:
for reg in s.get_def():
if reg in register:
# Known register is overwritten, remove its value
del register[reg]
known.append((reg, 'unknown'))
if block.debug and len(known):
s.set_inline_comment(','.join([' %s = %s' % k for k in known]))
return changed return changed
...@@ -222,6 +297,8 @@ def copy_propagation(block): ...@@ -222,6 +297,8 @@ def copy_propagation(block):
moves_to = [] moves_to = []
changed = False changed = False
block.reset()
while not block.end(): while not block.end():
s = block.read() s = block.read()
...@@ -241,7 +318,7 @@ def copy_propagation(block): ...@@ -241,7 +318,7 @@ def copy_propagation(block):
# the list. # the list.
i = 0 i = 0
while i < len(moves_to): while i < len(moves_to):
if moves_to[i] == s[0] or moves_to[i] == s[1]: if moves_to[i] == s[0] or moves_to[i] == s[1]:
del moves_to[i] del moves_to[i]
del moves_from[i] del moves_from[i]
...@@ -275,6 +352,8 @@ def algebraic_transformations(block): ...@@ -275,6 +352,8 @@ def algebraic_transformations(block):
""" """
changed = False changed = False
block.reset()
while not block.end(): while not block.end():
s = block.read() s = block.read()
...@@ -316,27 +395,44 @@ def eliminate_dead_code(block): ...@@ -316,27 +395,44 @@ def eliminate_dead_code(block):
is not used in the rest of the block, and is not in the `out' set of the is not used in the rest of the block, and is not in the `out' set of the
block. block.
""" """
# TODO: Finish
changed = False changed = False
block.reverse_statements() for n, s in enumerate(block):
unused = set()
while not block.end():
s = block.read()
for reg in s.get_def(): for reg in s.get_def():
if reg in unused: if is_reg_dead_after(reg, block, n):
# Statement is redefined later, so this statement is useless # Statement is redefined later, so this statement is useless
s.remove = True if block.debug:
#print 'reg %s is in %s, remove:' % (reg, unused), \ s.stype = 'comment'
# block.pointer - 1, s s.options['block'] = False
else: s.set_inline_comment(' dead register %s' % reg)
unused.add(reg) s.name = ' Dead:\t%s\t%s' \
% (s.name, ','.join(map(str, s)))
else:
s.remove = True
changed = True
#unused = set()
#for s in reversed(block):
# for reg in s.get_def():
# if reg in unused:
# # Statement is redefined later, so this statement is useless
# if block.debug:
# s.stype = 'comment'
# s.options['block'] = False
# s.name = ' Dead:\t%s\t%s' \
# % (s.name, ','.join(map(str, s)))
# else:
# s.remove = True
# changed = True
# else:
# unused.add(reg)
unused -= set(s.get_use()) # unused -= set(s.get_use())
block.apply_filter(lambda s: not hasattr(s, 'remove')) if not block.debug:
block.reverse_statements() block.apply_filter(lambda s: not hasattr(s, 'remove'))
return changed return changed
import re import re
def move_1(mov, statements): def remove_redundancies(block):
"""Execute all functions that remove redundant statements."""
callbacks = [move_aa, move_inst, instr_move_jal, move_move, sw_ld, shift,
add_lw]
old_len = -1
changed = False
while old_len != len(block):
old_len = len(block)
block.reset()
while not block.end():
s = block.read()
for callback in callbacks:
if callback(s, block):
changed = True
break
return changed
def move_aa(mov, statements):
""" """
mov $regA, $regA -> --- remove it move $regA, $regA -> --- remove it
""" """
if mov.is_command('move') and mov[0] == mov[1]: if mov.is_command('move') and mov[0] == mov[1]:
statements.replace(1, []) statements.replace(1, [])
...@@ -11,9 +34,9 @@ def move_1(mov, statements): ...@@ -11,9 +34,9 @@ def move_1(mov, statements):
return True return True
def move_2(mov, statements): def move_inst(mov, statements):
""" """
mov $regA, $regB -> instr $regA, $regB, ... move $regA, $regB -> instr $regA, $regB, ...
instr $regA, $regA, ... instr $regA, $regA, ...
""" """
if mov.is_command('move'): if mov.is_command('move'):
...@@ -26,10 +49,10 @@ def move_2(mov, statements): ...@@ -26,10 +49,10 @@ def move_2(mov, statements):
return True return True
def move_3(ins, statements): def instr_move_jal(ins, statements):
""" """
instr $regA, ... -> instr $4, ... instr $regA, ... -> instr $4, ...
mov $4, $regA jal XX move $4, $regA jal XX
jal XX jal XX
""" """
if ins.is_command() and len(ins): if ins.is_command() and len(ins):
...@@ -47,10 +70,10 @@ def move_3(ins, statements): ...@@ -47,10 +70,10 @@ def move_3(ins, statements):
return True return True
def move_4(mov1, statements): def move_move(mov1, statements):
""" """
mov $RegA, $RegB -> move $RegA, $RegB move $RegA, $RegB -> move $RegA, $RegB
mov $RegB, $RegA move $RegB, $RegA
""" """
if mov1.is_command('move'): if mov1.is_command('move'):
mov2 = statements.peek() mov2 = statements.peek()
...@@ -62,7 +85,7 @@ def move_4(mov1, statements): ...@@ -62,7 +85,7 @@ def move_4(mov1, statements):
return True return True
def load(sw, statements): def sw_ld(sw, statements):
""" """
sw $regA, XX -> sw $regA, XX sw $regA, XX -> sw $regA, XX
ld $regA, XX ld $regA, XX
...@@ -86,7 +109,7 @@ def shift(shift, statements): ...@@ -86,7 +109,7 @@ def shift(shift, statements):
return True return True
def add(add, statements): def add_lw(add, statements):
""" """
add $regA, $regA, X -> lw ..., X($regA) add $regA, $regA, X -> lw ..., X($regA)
lw ..., 0($regA) lw ..., 0($regA)
...@@ -124,3 +147,5 @@ def remove_redundant_jumps(statements): ...@@ -124,3 +147,5 @@ def remove_redundant_jumps(statements):
s.name = 'bne' if s.is_command('beq') else 'beq' s.name = 'bne' if s.is_command('beq') else 'beq'
s[2] = j[0] s[2] = j[0]
statements.replace(3, [s, label]) statements.replace(3, [s, label])
statements.reset()
import ply.lex as lex import ply.lex as lex
import ply.yacc as yacc import ply.yacc as yacc
from statement import Statement as S, Block from statement import Statement as S
from program import Program
# Global statements administration # Global statements administration
...@@ -46,6 +47,7 @@ def t_offset_address(t): ...@@ -46,6 +47,7 @@ def t_offset_address(t):
def t_int(t): def t_int(t):
r'-?[0-9]+' r'-?[0-9]+'
t.type = 'WORD' t.type = 'WORD'
t.value = int(t.value)
return t return t
def t_WORD(t): def t_WORD(t):
...@@ -79,11 +81,12 @@ def p_line_instruction(p): ...@@ -79,11 +81,12 @@ def p_line_instruction(p):
def p_line_comment(p): def p_line_comment(p):
'line : COMMENT NEWLINE' 'line : COMMENT NEWLINE'
statements.append(S('comment', p[1], inline=False)) statements.append(S('comment', p[1]))
def p_line_inline_comment(p): def p_line_inline_comment(p):
'line : instruction COMMENT NEWLINE' 'line : instruction COMMENT NEWLINE'
statements.append(S('comment', p[2], inline=True)) # Add the inline comment to the last parsed statement
statements[-1].options['comment'] = p[2]
def p_instruction_command(p): def p_instruction_command(p):
'instruction : command' 'instruction : command'
...@@ -125,4 +128,4 @@ def parse_file(filename): ...@@ -125,4 +128,4 @@ def parse_file(filename):
except IOError: except IOError:
raise Exception('File "%s" could not be opened' % filename) raise Exception('File "%s" could not be opened' % filename)
return Block(statements) return Program(statements)
from statement import Statement as S, Block
from dataflow import find_basic_blocks, generate_flow_graph
from optimize.redundancies import remove_redundant_jumps, remove_redundancies
from optimize.advanced import eliminate_common_subexpressions, \
fold_constants, copy_propagation, eliminate_dead_code
from writer import write_statements
import liveness
import reaching_definitions
class Program(Block):
def __len__(self):
"""Get the number of statements in the program."""
return len(self.statements) if hasattr(self, 'statements') \
else reduce(lambda a, b: len(a) + len(b), self.blocks, 0)
def get_statements(self, add_block_comments=False):
"""Concatenate the statements of all blocks and return the resulting
list."""
if hasattr(self, 'statements'):
return self.statements
# Only add block start and end comments when in debug mode
if add_block_comments and self.debug:
get_id = lambda b: b.bid
statements = []
for b in self.blocks:
message = ' Block %d (%d statements), edges from: %s' \
% (b.bid, len(b), map(get_id, b.edges_from))
if hasattr(b, 'live_in'):
message += ', LIVE_in: %s' % list(b.live_in)
if hasattr(b, 'reach_in'):
message += ', REACH_in: %s' % list(b.reach_in)
statements.append(S('comment', message, block=False))
statements += b.statements
message = ' End of block %d, edges to: %s' \
% (b.bid, map(get_id, b.edges_to))
if hasattr(b, 'live_out'):
message += ', LIVE_out: %s' % list(b.live_out)
if hasattr(b, 'reach_out'):
message += ', REACH_out: %s' % list(b.reach_out)
statements.append(S('comment', message, block=False))
return statements
return reduce(lambda a, b: a + b,
[b.statements for b in self.blocks])
def count_instructions(self):
"""Count the number of statements that are commands or labels."""
return len(filter(lambda s: s.is_command() or s.is_label(),
self.get_statements()))
def optimize_global(self):
"""Optimize on a global level."""
remove_redundant_jumps(self)
def optimize_blocks(self):
"""Optimize on block level. Keep executing all optimizations until no
more changes occur."""
self.program_iterations = self.block_iterations = 0
program_changed = True
while program_changed:
self.program_iterations += 1
program_changed = False
for block in self.blocks:
self.block_iterations += 1
block_changed = True
while block_changed:
block_changed = False
if remove_redundancies(block):
block_changed = True
if eliminate_common_subexpressions(block):
block_changed = True
if fold_constants(block):
block_changed = True
if copy_propagation(block):
block_changed = True
if eliminate_dead_code(block):
block_changed = True
if block_changed:
program_changed = True
def find_basic_blocks(self):
"""Divide the statement list into basic blocks."""
self.blocks = find_basic_blocks(self.statements)
for b in self.blocks:
b.debug = self.debug
# Remove the old statement list, since it will probably change
del self.statements
def perform_dataflow_analysis(self):
"""Perform dataflow analysis:
- Divide the statement list into basic blocks
- Generate flow graph
- Create liveness sets: def, use, in, out
- Create reaching definitions sets: gen, kill, in, out"""
self.find_basic_blocks()
generate_flow_graph(self.blocks)
liveness.create_in_out(self.blocks)
reaching_definitions.create_in_out(self.blocks)
def save(self, filename):
"""Save the program in the specified file."""
f = open(filename, 'w+')
f.write(write_statements(self.get_statements(True)))
f.close()
from dataflow import BasicBlock as B
def get_defs(blocks):
"""Collect definitions of all registers."""
defs = {}
for b in blocks:
for s in b:
for reg in s.get_def():
if reg not in defs:
defs[reg] = set([s.sid])
else:
defs[reg].add(s.sid)
return defs
def create_gen_kill(block, global_defs):
block_defs = {}
# Get the last of each definition series and put in in the `def' set
block.gen_set = set()
for s in reversed(block):
for reg in s.get_def():
if reg not in block_defs:
block_defs[reg] = s.sid
block.gen_set.add(s.sid)
# Generate kill set
block.kill_set = set()
for reg, statement_ids in global_defs.iteritems():
if reg in block_defs:
block.kill_set |= statement_ids - set([block_defs[reg]])
def create_in_out(blocks):
"""Generate the `in' and `out' sets of the given blocks using the iterative
algorithm from the lecture slides."""
# Create gen/kill sets
defs = get_defs(blocks)
for b in blocks:
create_gen_kill(b, defs)
b.reach_out = b.gen_set
change = True
while change:
change = False
for b in blocks:
b.reach_in = set()
for pred in b.edges_from:
b.reach_in |= pred.reach_out
new_out = b.gen_set | (b.reach_in - b.kill_set)
if new_out != b.reach_out:
b.reach_out = new_out
change = True
...@@ -10,7 +10,7 @@ class Statement: ...@@ -10,7 +10,7 @@ class Statement:
self.args = list(args) self.args = list(args)
self.options = kwargs self.options = kwargs
# Assign a unique ID to each satement # Assign a unique ID to each statement
self.sid = Statement.sid self.sid = Statement.sid
Statement.sid += 1 Statement.sid += 1
...@@ -38,12 +38,15 @@ class Statement: ...@@ -38,12 +38,15 @@ class Statement:
def __repr__(self): # pragma: nocover def __repr__(self): # pragma: nocover
return str(self) return str(self)
def set_inline_comment(self, comment):
self.options['comment'] = comment
def has_inline_comment(self):
return 'comment' in self.options and len(self.options['comment'])
def is_comment(self): def is_comment(self):
return self.stype == 'comment' return self.stype == 'comment'
def is_inline_comment(self):
return self.is_comment() and self.options['inline']
def is_directive(self): def is_directive(self):
return self.stype == 'directive' return self.stype == 'directive'
...@@ -57,15 +60,20 @@ class Statement: ...@@ -57,15 +60,20 @@ class Statement:
def is_jump(self): def is_jump(self):
"""Check if the statement is a jump.""" """Check if the statement is a jump."""
return self.is_command() \ return self.is_command() \
and re.match('^j|jal|beq|bne|blez|bgtz|bltz|bgez|bct|bcf$', \ and re.match('^j|jal|beq|bne|blez|bgtz|bltz|bgez|bc1t|bc1f$', \
self.name) self.name)
def is_branch(self): def is_branch(self):
"""Check if the statement is a branch.""" """Check if the statement is a branch."""
return self.is_command() \ return self.is_command() \
and re.match('^beq|bne|blez|bgtz|bltz|bgez|bct|bcf$', \ and re.match('^beq|bne|blez|bgtz|bltz|bgez|bct|bcf|bc1f|bc1t$',\
self.name) self.name)
def is_branch_zero(self):
"""Check if statement is a branch that compares with zero."""
return self.is_command() \
and re.match('^blez|bgtz|bltz|bgez$', self.name)
def is_shift(self): def is_shift(self):
"""Check if the statement is a shift operation.""" """Check if the statement is a shift operation."""
return self.is_command() and re.match('^s(ll|rl|ra)$', self.name) return self.is_command() and re.match('^s(ll|rl|ra)$', self.name)
...@@ -74,7 +82,12 @@ class Statement: ...@@ -74,7 +82,12 @@ class Statement:
"""Check if the statement is a load instruction.""" """Check if the statement is a load instruction."""
return self.is_command() and self.name in ['lw', 'li', 'dlw', 'l.s', \ return self.is_command() and self.name in ['lw', 'li', 'dlw', 'l.s', \
'l.d'] 'l.d']
def is_store(self):
"""Check if the statement is a store instruction."""
return self.is_command() and self.name in ['sw', 'sb', 's.d', 'dsw', \
's.s', 's.b']
def is_arith(self): def is_arith(self):
"""Check if the statement is an arithmetic operation.""" """Check if the statement is an arithmetic operation."""
return self.is_command() \ return self.is_command() \
...@@ -91,82 +104,111 @@ class Statement: ...@@ -91,82 +104,111 @@ class Statement:
def is_binop(self): def is_binop(self):
"""Check if the statement is an binary operation.""" """Check if the statement is an binary operation."""
return self.is_command() and len(self) == 3 and not self.is_jump() return self.is_command() and len(self) == 3 and not self.is_jump()
def is_load_non_immediate(self): def is_load_non_immediate(self):
"""Check if the statement is a load statement.""" """Check if the statement is a load statement."""
return self.is_command() \ return self.is_command() \
and re.match('^l(w|a|b|bu|\.d|\.s)|dlw$', \ and re.match('^l(w|a|b|bu|\.d|\.s)|dlw$', \
self.name) self.name)
def is_logical(self): def is_logical(self):
"""Check if the statement is a logical operator.""" """Check if the statement is a logical operator."""
return self.is_command() and re.match('^(xor|or|and)i?$', self.name) return self.is_command() and re.match('^(xor|or|and)i?$', self.name)
def is_double_aritmethic(self): def is_double_arithmetic(self):
"""Check if the statement is a arithmetic .d operator.""" """Check if the statement is a arithmetic .d operator."""
return self.is_command() and \ return self.is_command() and \
re.match('^(add|sub|div|mul)\.d$', self.name) re.match('^(add|sub|div|mul)\.d$', self.name)
def is_double_unary(self): def is_double_unary(self):
"""Check if the statement is a unary .d operator.""" """Check if the statement is a unary .d operator."""
return self.is_command() and \ return self.is_command() and \
re.match('^(abs|neg|mov)\.d$', self.name) re.match('^(abs|neg|mov)\.d$', self.name)
def is_move_from_spec(self): def is_move_from_spec(self):
"""Check if the statement is a move from the result register.""" """Check if the statement is a move from the result register."""
return self.is_command() and self.name in ['mflo', 'mthi'] return self.is_command() and self.name in ['mflo', 'mthi']
def is_set_if_less(self): def is_set_if_less(self):
"""Check if the statement is a shift if less then.""" """Check if the statement is a shift if less then."""
return self.is_command() and self.name in ['slt', 'sltu'] return self.is_command() and self.name in ['slt', 'sltu']
def is_convert(self): def is_convert(self):
"""Check if the statement is a convert operator.""" """Check if the statement is a convert operator."""
return self.is_command() and re.match('^cvt\.[a-z\.]*$', self.name) return self.is_command() and re.match('^cvt\.[a-z\.]*$', self.name)
def is_truncate(self): def is_truncate(self):
"""Check if the statement is a convert operator.""" """Check if the statement is a convert operator."""
return self.is_command() and re.match('^trunc\.[a-z\.]*$', self.name) return self.is_command() and re.match('^trunc\.[a-z\.]*$', self.name)
def is_compare(self):
"""Check if the statement is a comparison."""
return self.is_command() and re.match('^c\.[a-z\.]*$', self.name)
def jump_target(self): def jump_target(self):
"""Get the jump target of this statement.""" """Get the jump target of this statement."""
if not self.is_jump(): if not self.is_jump():
raise Exception('Command "%s" has no jump target' % self.name) raise Exception('Command "%s" has no jump target' % self.name)
return self[-1] return self[-1]
def get_def(self): def get_def(self):
"""Get the variable that this statement defines, if any.""" """Get the variable that this statement defines, if any."""
instr = ['move', 'addu', 'subu', 'li', 'mtc1', 'dmfc1'] instr = ['div', 'move', 'addu', 'subu', 'li', 'dmfc1', 'mov.d']
if self.is_command('mtc1'):
return [self[1]]
if self.is_load_non_immediate() or self.is_arith() \ if self.is_load_non_immediate() or self.is_arith() \
or self.is_logical() or self.is_double_arithmetic() \ or self.is_logical() or self.is_double_arithmetic() \
or self.is_move_from_spec() or self.is_double_unary() \ or self.is_move_from_spec() or self.is_double_unary() \
or self.is_set_if_less() or self.is_convert() \ or self.is_set_if_less() or self.is_convert() \
or self.is_truncate() or self.is_load() \ or self.is_truncate() or self.is_load() \
or (self.is_command and self.name in instr): or self.is_command(*instr):
return self[0] return self[:1]
return [] return []
def get_use(self): def get_use(self):
# TODO: Finish with ALL the available commands! """Get the variables that this statement uses, if any."""
instr = ['addu', 'subu', 'mult', 'div', 'move', 'mov.d', \
'dmfc1']
use = [] use = []
if self.is_binop(): # Case arg0
use += self[1:] if (self.is_branch() \
elif self.is_command('move'): and not self.is_command(*['bc1f', 'bc1t', 'bct', 'bcf'])) \
or self.is_store() or self.is_compare() \
or self.is_command(*['mult', 'dsz', 'mtc1']):
if self.name == 'dsz':
m = re.match('^[^(]+\(([^)]+)\)$', self[0])
if m:
use.append(m.group(1))
else:
use.append(self[0])
# Case arg1 direct adressing
if (self.is_branch() and not self.is_branch_zero() \
and not self.is_command(*['bc1f', 'bc1t', 'bct', 'bcf'])) \
or self.is_shift() \
or self.is_double_arithmetic() or self.is_double_unary() \
or self.is_logical() or self.is_convert() \
or self.is_truncate() or self.is_set_if_less() \
or self.is_compare() or self.is_command(*instr):
use.append(self[1]) use.append(self[1])
elif self.is_command('lw', 'sb', 'sw', 'dsw', 's.s', 's.d'): # Case arg1 relative adressing
m = re.match('^\d+\(([^)]+)\)$', self[1]) if self.is_load_non_immediate() or self.is_store():
m = re.match('^[^(]+\(([^)]+)\)$', self[1])
if m: if m:
use.append(m.group(1)) use.append(m.group(1))
else:
# 'sw' also uses its first argument use.append(self[1])
if self.name in ['sw', 'dsw']: # Case arg2
use.append(self[0]) if self.is_double_arithmetic() or self.is_set_if_less() \
elif len(self) == 2: # FIXME: temporary fix, manually add all commands or self.is_logical() or self.is_truncate() \
use.append(self[1]) or self.is_command(*['addu', 'subu', 'div']):
if not isinstance(self[2], int):
use.append(self[2])
return use return use
...@@ -180,10 +222,24 @@ class Statement: ...@@ -180,10 +222,24 @@ class Statement:
class Block: class Block:
def __init__(self, statements=[]): bid = 1
def __init__(self, statements=[], debug=False):
self.statements = statements self.statements = statements
self.pointer = 0 self.pointer = 0
# Assign a unique ID to each block for printing purposes
self.bid = Block.bid
Block.bid += 1
self.debug = debug
def __str__(self):
return '<Block bid=%d statements=%d>' % (self.bid, len(self))
def __repr__(self):
return str(self)
def __iter__(self): def __iter__(self):
return iter(self.statements) return iter(self.statements)
...@@ -203,7 +259,7 @@ class Block: ...@@ -203,7 +259,7 @@ class Block:
def end(self): def end(self):
"""Check if the pointer is at the end of the statement list.""" """Check if the pointer is at the end of the statement list."""
return self.pointer == len(self) return self.pointer >= len(self)
def peek(self, count=1): def peek(self, count=1):
"""Read the statements until an offset from the current pointer """Read the statements until an offset from the current pointer
...@@ -214,7 +270,7 @@ class Block: ...@@ -214,7 +270,7 @@ class Block:
return self.statements[self.pointer] if count == 1 \ return self.statements[self.pointer] if count == 1 \
else self.statements[self.pointer:self.pointer + count] else self.statements[self.pointer:self.pointer + count]
def replace(self, count, replacement, start=None): def replace(self, count, replacement, start=None, message=''):
"""Replace the given range start-(start + count) with the given """Replace the given range start-(start + count) with the given
statement list, and move the pointer to the first statement after the statement list, and move the pointer to the first statement after the
replacement.""" replacement."""
...@@ -224,15 +280,35 @@ class Block: ...@@ -224,15 +280,35 @@ class Block:
if start == None: if start == None:
start = self.pointer - 1 start = self.pointer - 1
# Add a message in inline comments
if self.debug:
if len(message):
message = ' ' + message
if len(replacement):
replacement[0].set_inline_comment(message)
for s in replacement[1:]:
s.set_inline_comment('|')
else:
replacement = [Statement('comment', message)]
elif not len(replacement):
# Statement is removed, comment it
replacement = [Statement('comment', str(b)) \
for b in self.statements[start:start + count]]
before = self.statements[:start] before = self.statements[:start]
after = self.statements[start + count:] after = self.statements[start + count:]
self.statements = before + replacement + after self.statements = before + replacement + after
self.pointer = start + len(replacement) self.pointer = start + len(replacement)
def insert(self, statement, index=None): def insert(self, statement, index=None, message=''):
if index == None: if index == None:
index = self.pointer index = self.pointer
if self.debug and len(message):
statement.set_inline_comment(' ' + message)
self.statements.insert(index, statement) self.statements.insert(index, statement)
def apply_filter(self, callback): def apply_filter(self, callback):
...@@ -243,4 +319,8 @@ class Block: ...@@ -243,4 +319,8 @@ class Block:
def reverse_statements(self): def reverse_statements(self):
"""Reverse the statement list and reset the pointer.""" """Reverse the statement list and reset the pointer."""
self.statements = self.statements[::-1] self.statements = self.statements[::-1]
self.reset()
def reset(self):
"""Reset the internal pointer."""
self.pointer = 0 self.pointer = 0
* Apart python script maken die de generator genereert dmv config file * Apart python script maken die de generator genereert dmv config file
* 'verbose' argument in main.py
* Gaat fout:
li $5,0x00008000 -> l.d $f0,32768($4)
addu $4,$4,$5
l.d $f0,0($4)
from math import ceil from math import ceil
TABSIZE = 4 # Size in spaces of a single tab
INLINE_COMMENT_LEVEL = 6 # Number of tabs to inline commment level
COMMAND_SIZE = 8 # Default length of a command name, used for
# indenting
ADD_COMMENT_BLOCKS = True # Wether to add newlines before and after
# non-inline comment
ADD_ARGUMENT_SPACE = False # Wether to add a space between command arguments
# and the previous comma
def write_statements(statements): def write_statements(statements):
"""Write a list of statements to valid assembly code.""" """Write a list of statements to valid assembly code."""
out = '' out = ''
indent_level = 0 indent_level = 0
prevline = '' prev_comment = False
for i, s in enumerate(statements): for i, s in enumerate(statements):
newline = '\n' if i else '' current_comment = False
if s.is_label(): if s.is_label():
line = s.name + ':' line = s.name + ':'
indent_level = 1 indent_level = 1
elif s.is_comment(): elif s.is_comment():
line = '#' + s.name line = '\t' * indent_level + '#' + s.name
current_comment = s.options.get('block', True)
if s.is_inline_comment():
l = len(prevline.expandtabs(4))
tabs = int(ceil((24 - l) / 4.)) + 1
newline = '\t' * tabs
else:
line = '\t' * indent_level + line
elif s.is_directive(): elif s.is_directive():
line = '\t' + s.name line = '\t' + s.name
elif s.is_command(): elif s.is_command():
line = '\t' + s.name line = '\t' + s.name
# If there are arguments, add tabs until the 8 character limit has
# been reached. If the command name is 8 or more characers long,
# add a single space
if len(s): if len(s):
if len(s.name) < 8: l = len(s.name)
line += '\t'
if l < COMMAND_SIZE:
line += '\t' * int(ceil((COMMAND_SIZE - l)
/ float(TABSIZE)))
else: else:
line += ' ' line += ' '
line += ','.join(s.args) delim = ', ' if ADD_ARGUMENT_SPACE else ','
line += delim.join(map(str, s))
else: else:
raise Exception('Unsupported statement type "%s"' % s.stype) raise Exception('Unsupported statement type "%s"' % s.stype)
out += newline + line # Add the inline comment, if there is any
prevline = line if s.has_inline_comment():
start = INLINE_COMMENT_LEVEL * TABSIZE
diff = start - len(line.expandtabs(TABSIZE))
# Add newline at end of file # The comment must not be directly adjacent to the command itself
out += '\n' if diff > 0:
tabs = '\t' * (int(ceil(diff / float(TABSIZE))) + 1)
else:
tabs = ' '
line += tabs + '#' + s.options['comment']
# Add newline at end of command
line += '\n'
if ADD_COMMENT_BLOCKS:
if prev_comment ^ current_comment:
out += '\n'
out += line
prev_comment = current_comment
return out return out
def write_to_file(filename, statements): def write_to_file(filename, statements):
"""Convert a list of statements to valid assembly code and write it to a """Convert a list of statements to valid assembly code and write it to a
file.""" file."""
......
#!/usr/bin/python
from testrunner import main from testrunner import main
import sys import sys
main(sys.argv[1:]) main(sys.argv[1:])
TESTS=$(wildcard tests/test_*.py) TESTS=$(wildcard tests/test_*.py)
COVERAGE_OUTPUT_DIR := coverage COVERAGE_OUTPUT_DIR := coverage
OMIT := /usr/share/pyshared/*,test*,*__init__.py OMIT := /usr/share/pyshared/*,test*,*__init__.py
CLEAN := $(CLEAN) tests/*.pyc
ifeq ($(findstring python-coverage,$(wildcard /usr/bin/*)), python-coverage) ifeq ($(findstring python-coverage,$(wildcard /usr/bin/*)), python-coverage)
COVERAGE=/usr/bin/python-coverage COVERAGE=/usr/bin/python-coverage
......
import unittest
from src.statement import Statement as S
from src.dataflow import BasicBlock as B
from src.dag import Dag, DagNode, DagLeaf
class TestDag(unittest.TestCase):
def setUp(self):
pass
def tearDown(self):
pass
def test_dag_unary(self):
dag = Dag(B([S('command', 'neg.d', '$rd', '$rs')]))
expect = Dag([])
expect.nodes = [DagLeaf('$rs'), DagNode('neg.d', '$rd', \
DagLeaf('$rs'))]
self.assertEqualDag(dag, expect)
def test_dag_binary(self):
dag = Dag(B([S('command', 'addu', '$rd', '$r1', '$r2')]))
expect = Dag([])
expect.nodes = [DagLeaf('$r1'),
DagLeaf('$r2'),
DagNode('addu', '$rd', DagLeaf('$r1'), DagLeaf('$r2'))]
self.assertEqualDag(dag, expect)
# def test_dag_combinednode(self):
# dag = Dag(B([S('command', 'mult', '$rd1', '$r1', '$r2'),
# S('command', 'mult', '$rd2', '$r1', '$r2')]))
# expect = Dag([])
# multnode = DagNode('mult',
# DagLeaf('$r1'),
# DagLeaf('$r2'))
# multnode.labels = ['$rd1', '$rd2']
# expect.nodes = [DagLeaf('$r1'),
# DagLeaf('$r2'),
# multnode]
#
# self.assertEqualDag(dag, expect)
def assertEqualDag(self, dag1, dag2):
self.assertEqual(len(dag1.nodes), len(dag2.nodes))
for node1, node2 in zip(dag1.nodes, dag2.nodes):
self.assertEqualNodes(node1, node2)
def assertEqualNodes(self, node1, node2):
if isinstance(node1, DagLeaf):
self.assertIsInstance(node2, DagLeaf)
self.assertEqual(node1.reg, node2.reg)
elif isinstance(node2, DagLeaf):
raise AssertionError
else:
self.assertEqual(node1.op, node2.op)
self.assertEqual(node1.labels, node2.labels)
self.assertEqual(len(node1.nodes), len(node2.nodes))
for child1, child2 in zip(node1.nodes, node2.nodes):
self.assertEqualNodes(child1, child2)
...@@ -2,7 +2,7 @@ import unittest ...@@ -2,7 +2,7 @@ import unittest
from src.statement import Statement as S from src.statement import Statement as S
from src.dataflow import BasicBlock as B, find_leaders, find_basic_blocks, \ from src.dataflow import BasicBlock as B, find_leaders, find_basic_blocks, \
generate_flow_graph, Dag, DagNode, DagLeaf, defs, reaching_definitions generate_flow_graph
class TestDataflow(unittest.TestCase): class TestDataflow(unittest.TestCase):
...@@ -24,42 +24,6 @@ class TestDataflow(unittest.TestCase): ...@@ -24,42 +24,6 @@ class TestDataflow(unittest.TestCase):
[B(s[:2]).statements, B(s[2:4]).statements, \ [B(s[:2]).statements, B(s[2:4]).statements, \
B(s[4:]).statements]) B(s[4:]).statements])
# def test_get_gen(self):
# b1 = B([S('command', 'add', '$1', '$2', '$3'), \
# S('command', 'add', '$2', '$3', '$4'), \
# S('command', 'add', '$1', '$4', '$5')])
#
# self.assertEqual(b1.get_gen(), ['$1', '$2'])
# def test_get_out(self):
# b1 = B([S('command', 'add', '$1', '$2', '$3'), \
# S('command', 'add', '$2', '$3', '$4'), \
# S('command', 'add', '$1', '$4', '$5'), \
# S('command', 'j', 'b2')])
#
# b2 = B([S('command', 'add', '$3', '$5', '$6'), \
# S('command', 'add', '$1', '$2', '$3'), \
# S('command', 'add', '$6', '$4', '$5')])
#
# blocks = [b1, b2]
#
# # initialize out[B] = gen[B] for every block
# for block in blocks:
# block.out_set = block.get_gen()
# print 'block.out_set', block.out_set
#
# generate_flow_graph(blocks)
# change = True
# while change:
# for i, block in enumerate(blocks):
# block.get_in()
# oldout = block.out_set
# newout = block.get_out()
# if (block.out_set == block.get_out()):
# change = False
def test_generate_flow_graph_simple(self): def test_generate_flow_graph_simple(self):
b1 = B([S('command', 'foo'), S('command', 'j', 'b2')]) b1 = B([S('command', 'foo'), S('command', 'j', 'b2')])
b2 = B([S('label', 'b2'), S('command', 'bar')]) b2 = B([S('label', 'b2'), S('command', 'bar')])
...@@ -80,94 +44,3 @@ class TestDataflow(unittest.TestCase): ...@@ -80,94 +44,3 @@ class TestDataflow(unittest.TestCase):
self.assertEqual(b2.edges_to, [b3]) self.assertEqual(b2.edges_to, [b3])
self.assertIn(b1, b3.edges_from) self.assertIn(b1, b3.edges_from)
self.assertIn(b2, b3.edges_from) self.assertIn(b2, b3.edges_from)
def test_dag_unary(self):
dag = Dag(B([S('command', 'neg.d', '$rd', '$rs')]))
expect = Dag([])
expect.nodes = [DagLeaf('$rs'), DagNode('neg.d', '$rd', \
DagLeaf('$rs'))]
self.assertEqualDag(dag, expect)
def test_dag_binary(self):
dag = Dag(B([S('command', 'addu', '$rd', '$r1', '$r2')]))
expect = Dag([])
expect.nodes = [DagLeaf('$r1'),
DagLeaf('$r2'),
DagNode('addu', '$rd', DagLeaf('$r1'), DagLeaf('$r2'))]
self.assertEqualDag(dag, expect)
# def test_dag_combinednode(self):
# dag = Dag(B([S('command', 'mult', '$rd1', '$r1', '$r2'),
# S('command', 'mult', '$rd2', '$r1', '$r2')]))
# expect = Dag([])
# multnode = DagNode('mult',
# DagLeaf('$r1'),
# DagLeaf('$r2'))
# multnode.labels = ['$rd1', '$rd2']
# expect.nodes = [DagLeaf('$r1'),
# DagLeaf('$r2'),
# multnode]
#
# self.assertEqualDag(dag, expect)
def test_defs(self):
s1 = S('command', 'addu', '$3', '$1', '$2')
s2 = S('command', 'addu', '$1', '$3', 10)
s3 = S('command', 'subu', '$3', '$1', 5)
s4 = S('command', 'li', '$4', '0x00000001')
block = B([s1, s2, s3, s4])
self.assertEqual(defs([block]), {
'$3': set([s1.sid, s3.sid]),
'$1': set([s2.sid]),
'$4': set([s4.sid])
})
#def test_defs(self):
# s1 = S('command', 'add', '$3', '$1', '$2')
# s2 = S('command', 'move', '$1', '$3')
# s3 = S('command', 'move', '$3', '$2')
# s4 = S('command', 'li', '$4', '0x00000001')
# block = B([s1, s2, s3, s4])
# self.assertEqual(defs([block]), {
# '$3': set([s1.sid, s3.sid]),
# '$1': set([s2.sid]),
# '$4': set([s4.sid])
# })
def test_create_gen_kill_gen(self):
s1 = S('command', 'addu', '$3', '$1', '$2')
s2 = S('command', 'addu', '$1', '$3', 10)
s3 = S('command', 'subu', '$3', '$1', 5)
s4 = S('command', 'li', '$4', '0x00000001')
block = B([s1, s2, s3, s4])
block.create_gen_kill(defs([block]))
self.assertEqual(block.gen_set, set([s2.sid, s3.sid, s4.sid]))
#def test_get_kill_used(self):
# block = B([S('command', 'move', '$1', '$3'),
# S('command', 'add', '$3', '$1', '$2'),
# S('command', 'move', '$1', '$3'),
# S('command', 'move', '$2', '$3')])
# self.assertEqual(block.get_kill(), set())
def assertEqualDag(self, dag1, dag2):
self.assertEqual(len(dag1.nodes), len(dag2.nodes))
for node1, node2 in zip(dag1.nodes, dag2.nodes):
self.assertEqualNodes(node1, node2)
def assertEqualNodes(self, node1, node2):
if isinstance(node1, DagLeaf):
self.assertIsInstance(node2, DagLeaf)
self.assertEqual(node1.reg, node2.reg)
elif isinstance(node2, DagLeaf):
raise AssertionError
else:
self.assertEqual(node1.op, node2.op)
self.assertEqual(node1.labels, node2.labels)
self.assertEqual(len(node1.nodes), len(node2.nodes))
for child1, child2 in zip(node1.nodes, node2.nodes):
self.assertEqualNodes(child1, child2)
import unittest
from src.statement import Statement as S
from src.dataflow import BasicBlock as B, find_basic_blocks, \
generate_flow_graph
from src.liveness import create_use_def, create_in_out
class TestLiveness(unittest.TestCase):
def setUp(self):
pass
def tearDown(self):
pass
def test_create_gen_kill(self):
s1 = S('command', 'addu', '$3', '$1', '$2')
s2 = S('command', 'addu', '$1', '$3', 10)
s3 = S('command', 'subu', '$3', '$1', 5)
s4 = S('command', 'li', '$4', '0x00000001')
block = B([s1, s2, s3, s4])
create_use_def(block)
self.assertEqual(block.use_set, set(['$1', '$2']))
self.assertEqual(block.def_set, set(['$3', '$4']))
def test_create_in_out(self):
s11 = S('command', 'li', 'a', 3)
s12 = S('command', 'li', 'b', 5)
s13 = S('command', 'li', 'd', 4)
s14 = S('command', 'li', 'x', 100)
s15 = S('command', 'beq', 'a', 'b', 'L1')
s21 = S('command', 'addu', 'c', 'a', 'b')
s22 = S('command', 'li', 'd', 2)
s31 = S('label', 'L1')
s32 = S('command', 'li', 'c', 4)
s33 = S('command', 'mult', 'b', 'd')
s34 = S('command', 'mflo', 'temp')
s35 = S('command', 'addu', 'return', 'temp', 'c')
b1, b2, b3 = find_basic_blocks([s11, s12, s13, s14, s15, s21, s22, \
s31, s32, s33, s34, s35])
generate_flow_graph([b1, b2, b3])
create_in_out([b1, b2, b3])
self.assertEqual(b1.use_set, set())
self.assertEqual(b1.def_set, set(['a', 'b', 'd', 'x']))
self.assertEqual(b2.use_set, set(['a', 'b']))
self.assertEqual(b2.def_set, set(['c', 'd']))
self.assertEqual(b3.use_set, set(['b', 'd']))
self.assertEqual(b3.def_set, set(['c', 'temp', 'return']))
self.assertEqual(b1.live_in, set())
self.assertEqual(b1.live_out, set(['a', 'b', 'd']))
self.assertEqual(b2.live_in, set(['a', 'b']))
self.assertEqual(b2.live_out, set(['b', 'd']))
self.assertEqual(b3.live_in, set(['b', 'd']))
self.assertEqual(b3.live_out, set())
import unittest import unittest
from src.optimize.redundancies import remove_redundant_jumps from src.optimize.redundancies import remove_redundancies, remove_redundant_jumps
from src.optimize import optimize_block from src.program import Program
from src.statement import Statement as S, Block as B from src.statement import Statement as S, Block as B
def optimize_block(block):
"""Optimize a basic block using a Program object."""
# program = Program([])
# program.blocks = [block]
# del program.statements
# program.optimize_blocks()
remove_redundancies(block)
eliminate_common_subexpressions(block)
fold_constants(block)
copy_propagation(block)
return program.blocks
class TestOptimize(unittest.TestCase): class TestOptimize(unittest.TestCase):
def setUp(self): def setUp(self):
...@@ -19,7 +36,7 @@ class TestOptimize(unittest.TestCase): ...@@ -19,7 +36,7 @@ class TestOptimize(unittest.TestCase):
block = B([self.foo, block = B([self.foo,
S('command', 'move', '$regA', '$regA'), S('command', 'move', '$regA', '$regA'),
self.bar]) self.bar])
optimize_block(block) remove_redundancies(block)
self.assertEquals(block.statements, [self.foo, self.bar]) self.assertEquals(block.statements, [self.foo, self.bar])
def test_optimize_block_movab(self): def test_optimize_block_movab(self):
...@@ -27,7 +44,7 @@ class TestOptimize(unittest.TestCase): ...@@ -27,7 +44,7 @@ class TestOptimize(unittest.TestCase):
block = B([self.foo, block = B([self.foo,
move, move,
self.bar]) self.bar])
optimize_block(block) remove_redundancies(block)
self.assertEquals(block.statements, [self.foo, move, self.bar]) self.assertEquals(block.statements, [self.foo, move, self.bar])
def test_optimize_block_movinst_true(self): def test_optimize_block_movinst_true(self):
...@@ -35,7 +52,7 @@ class TestOptimize(unittest.TestCase): ...@@ -35,7 +52,7 @@ class TestOptimize(unittest.TestCase):
S('command', 'move', '$regA', '$regB'), S('command', 'move', '$regA', '$regB'),
S('command', 'addu', '$regA', '$regA', 2), S('command', 'addu', '$regA', '$regA', 2),
self.bar]) self.bar])
optimize_block(block) remove_redundancies(block)
self.assertEquals(block.statements, [self.foo, self.assertEquals(block.statements, [self.foo,
S('command', 'addu', '$regA', '$regB', 2), S('command', 'addu', '$regA', '$regB', 2),
self.bar]) self.bar])
...@@ -43,11 +60,11 @@ class TestOptimize(unittest.TestCase): ...@@ -43,11 +60,11 @@ class TestOptimize(unittest.TestCase):
def test_optimize_block_movinst_false(self): def test_optimize_block_movinst_false(self):
statements = [self.foo, \ statements = [self.foo, \
S('command', 'move', '$regA', '$regB'), \ S('command', 'move', '$regA', '$regB'), \
S('command', 'addu', '$regA', '$regC', 2), \ S('command', 'addu', '$regD', '$regC', 2), \
self.bar] self.bar]
block = B(statements) block = B(statements)
optimize_block(block) remove_redundancies(block)
self.assertEquals(block.statements, statements) self.assertEquals(block.statements, statements)
def test_optimize_block_instr_mov_jal_true(self): def test_optimize_block_instr_mov_jal_true(self):
...@@ -56,7 +73,7 @@ class TestOptimize(unittest.TestCase): ...@@ -56,7 +73,7 @@ class TestOptimize(unittest.TestCase):
S('command', 'move', '$4', '$regA'), S('command', 'move', '$4', '$regA'),
S('command', 'jal', 'L1'), S('command', 'jal', 'L1'),
self.bar]) self.bar])
optimize_block(block) remove_redundancies(block)
self.assertEquals(block.statements, [self.foo, self.assertEquals(block.statements, [self.foo,
S('command', 'addu', '$4', '$regC', 2), S('command', 'addu', '$4', '$regC', 2),
...@@ -70,7 +87,7 @@ class TestOptimize(unittest.TestCase): ...@@ -70,7 +87,7 @@ class TestOptimize(unittest.TestCase):
S('command', 'jal', 'L1'), \ S('command', 'jal', 'L1'), \
self.bar] self.bar]
block = B(arguments) block = B(arguments)
optimize_block(block) remove_redundancies(block)
self.assertEquals(block.statements, arguments) self.assertEquals(block.statements, arguments)
...@@ -79,7 +96,7 @@ class TestOptimize(unittest.TestCase): ...@@ -79,7 +96,7 @@ class TestOptimize(unittest.TestCase):
S('command', 'sw', '$regA', '$regB'), S('command', 'sw', '$regA', '$regB'),
S('command', 'lw', '$regA', '$regB'), S('command', 'lw', '$regA', '$regB'),
self.bar]) self.bar])
optimize_block(block) remove_redundancies(block)
self.assertEquals(block.statements, [self.foo, self.assertEquals(block.statements, [self.foo,
S('command', 'sw', '$regA', '$regB'), S('command', 'sw', '$regA', '$regB'),
...@@ -91,7 +108,7 @@ class TestOptimize(unittest.TestCase): ...@@ -91,7 +108,7 @@ class TestOptimize(unittest.TestCase):
S('command', 'lw', '$regD', '$regC'), \ S('command', 'lw', '$regD', '$regC'), \
self.bar] self.bar]
block = B(arguments) block = B(arguments)
optimize_block(block) remove_redundancies(block)
self.assertEquals(block.statements, arguments) self.assertEquals(block.statements, arguments)
...@@ -99,7 +116,7 @@ class TestOptimize(unittest.TestCase): ...@@ -99,7 +116,7 @@ class TestOptimize(unittest.TestCase):
block = B([self.foo, block = B([self.foo,
S('command', 'sll', '$regA', '$regA', 0), S('command', 'sll', '$regA', '$regA', 0),
self.bar]) self.bar])
optimize_block(block) remove_redundancies(block)
self.assertEquals(block.statements, [self.foo, self.bar]) self.assertEquals(block.statements, [self.foo, self.bar])
...@@ -108,7 +125,7 @@ class TestOptimize(unittest.TestCase): ...@@ -108,7 +125,7 @@ class TestOptimize(unittest.TestCase):
S('command', 'sll', '$regA', '$regB', 0), \ S('command', 'sll', '$regA', '$regB', 0), \
self.bar] self.bar]
block = B(arguments) block = B(arguments)
optimize_block(block) remove_redundancies(block)
self.assertEquals(block.statements, arguments) self.assertEquals(block.statements, arguments)
...@@ -116,7 +133,7 @@ class TestOptimize(unittest.TestCase): ...@@ -116,7 +133,7 @@ class TestOptimize(unittest.TestCase):
S('command', 'sll', '$regA', '$regA', 1), \ S('command', 'sll', '$regA', '$regA', 1), \
self.bar] self.bar]
block2 = B(arguments2) block2 = B(arguments2)
optimize_block(block2) remove_redundancies(block2)
self.assertEquals(block2.statements, arguments2) self.assertEquals(block2.statements, arguments2)
...@@ -125,7 +142,7 @@ class TestOptimize(unittest.TestCase): ...@@ -125,7 +142,7 @@ class TestOptimize(unittest.TestCase):
S('command', 'addu', '$regA', '$regA', 10), S('command', 'addu', '$regA', '$regA', 10),
S('command', 'lw', '$regB', '0($regA)'), S('command', 'lw', '$regB', '0($regA)'),
self.bar]) self.bar])
optimize_block(block) remove_redundancies(block)
self.assertEquals(block.statements, [self.foo, self.assertEquals(block.statements, [self.foo,
S('command', 'lw', '$regB', '10($regA)'), S('command', 'lw', '$regB', '10($regA)'),
...@@ -137,7 +154,7 @@ class TestOptimize(unittest.TestCase): ...@@ -137,7 +154,7 @@ class TestOptimize(unittest.TestCase):
S('command', 'lw', '$regB', '0($regC)'), \ S('command', 'lw', '$regB', '0($regC)'), \
self.bar] self.bar]
block = B(arguments) block = B(arguments)
optimize_block(block) remove_redundancies(block)
arguments2 = [self.foo, \ arguments2 = [self.foo, \
S('command', 'addu', '$regA', '$regB', 10), \ S('command', 'addu', '$regA', '$regB', 10), \
...@@ -150,7 +167,7 @@ class TestOptimize(unittest.TestCase): ...@@ -150,7 +167,7 @@ class TestOptimize(unittest.TestCase):
S('command', 'lw', '$regB', '1($regA)'), \ S('command', 'lw', '$regB', '1($regA)'), \
self.bar] self.bar]
block3 = B(arguments3) block3 = B(arguments3)
optimize_block(block3) remove_redundancies(block3)
self.assertEquals(block.statements, arguments) self.assertEquals(block.statements, arguments)
self.assertEquals(block2.statements, arguments2) self.assertEquals(block2.statements, arguments2)
...@@ -209,7 +226,7 @@ class TestOptimize(unittest.TestCase): ...@@ -209,7 +226,7 @@ class TestOptimize(unittest.TestCase):
S('command', 'move', '$regA', '$regB'), S('command', 'move', '$regA', '$regB'),
S('command', 'move', '$regB', '$regA'), S('command', 'move', '$regB', '$regA'),
self.bar]) self.bar])
optimize_block(block) remove_redundancies(block)
self.assertEquals(block.statements, [self.foo, self.assertEquals(block.statements, [self.foo,
S('command', 'move', '$regA', '$regB'), S('command', 'move', '$regA', '$regB'),
...@@ -221,6 +238,6 @@ class TestOptimize(unittest.TestCase): ...@@ -221,6 +238,6 @@ class TestOptimize(unittest.TestCase):
S('command', 'move', '$regB', '$regC'), \ S('command', 'move', '$regB', '$regC'), \
self.bar] self.bar]
block = B(arguments) block = B(arguments)
optimize_block(block) remove_redundancies(block)
self.assertEquals(block.statements, arguments) self.assertEquals(block.statements, arguments)
...@@ -3,7 +3,9 @@ from copy import copy ...@@ -3,7 +3,9 @@ from copy import copy
from src.optimize.advanced import eliminate_common_subexpressions, \ from src.optimize.advanced import eliminate_common_subexpressions, \
fold_constants, copy_propagation, algebraic_transformations fold_constants, copy_propagation, algebraic_transformations
from src.statement import Statement as S, Block as B from src.statement import Statement as S
from src.dataflow import BasicBlock as B, generate_flow_graph
import src.liveness as liveness
class TestOptimizeAdvanced(unittest.TestCase): class TestOptimizeAdvanced(unittest.TestCase):
...@@ -22,6 +24,7 @@ class TestOptimizeAdvanced(unittest.TestCase): ...@@ -22,6 +24,7 @@ class TestOptimizeAdvanced(unittest.TestCase):
e = [S('command', 'addu', '$8', '$regA', '$regB'), \ e = [S('command', 'addu', '$8', '$regA', '$regB'), \
S('command', 'move', '$regC', '$8'), \ S('command', 'move', '$regC', '$8'), \
S('command', 'move', '$regD', '$8')] S('command', 'move', '$regD', '$8')]
liveness.create_in_out([b])
eliminate_common_subexpressions(b) eliminate_common_subexpressions(b)
self.assertEqual(b.statements, e) self.assertEqual(b.statements, e)
...@@ -30,6 +33,7 @@ class TestOptimizeAdvanced(unittest.TestCase): ...@@ -30,6 +33,7 @@ class TestOptimizeAdvanced(unittest.TestCase):
S('command', 'li', '$regA', '0x00000001'), S('command', 'li', '$regA', '0x00000001'),
S('command', 'addu', '$regD', '$regA', '$regB')]) S('command', 'addu', '$regD', '$regA', '$regB')])
e = copy(b.statements) e = copy(b.statements)
liveness.create_in_out([b])
eliminate_common_subexpressions(b) eliminate_common_subexpressions(b)
self.assertEqual(b.statements, e) self.assertEqual(b.statements, e)
...@@ -49,7 +53,7 @@ class TestOptimizeAdvanced(unittest.TestCase): ...@@ -49,7 +53,7 @@ class TestOptimizeAdvanced(unittest.TestCase):
self.foo, self.foo,
S('command', 'addu', '$3', '$2', '$4'), S('command', 'addu', '$3', '$2', '$4'),
self.bar]) self.bar])
def test_copy_propagation_other_arg(self): def test_copy_propagation_other_arg(self):
block = B([self.foo, block = B([self.foo,
S('command', 'move', '$1', '$2'), S('command', 'move', '$1', '$2'),
......
import unittest
from src.statement import Statement as S
from src.dataflow import BasicBlock as B, find_basic_blocks, \
generate_flow_graph
from src.reaching_definitions import get_defs, create_gen_kill, create_in_out
class TestReachingDefinitions(unittest.TestCase):
def setUp(self):
pass
def tearDown(self):
pass
def test_get_defs(self):
s1 = S('command', 'add', '$3', '$1', '$2')
s2 = S('command', 'move', '$1', '$3')
s3 = S('command', 'move', '$3', '$2')
s4 = S('command', 'li', '$4', '0x00000001')
block = B([s1, s2, s3, s4])
self.assertEqual(get_defs([block]), {
'$3': set([s1.sid, s3.sid]),
'$1': set([s2.sid]),
'$4': set([s4.sid])
})
def test_create_gen_kill(self):
s1 = S('command', 'addu', '$3', '$1', '$2')
s2 = S('command', 'addu', '$1', '$3', 10)
s3 = S('command', 'subu', '$3', '$1', 5)
s4 = S('command', 'li', '$4', '0x00000001')
block = B([s1, s2, s3, s4])
create_gen_kill(block, get_defs([block]))
self.assertEqual(block.gen_set, set([s2.sid, s3.sid, s4.sid]))
self.assertEqual(block.kill_set, set([s1.sid]))
def test_create_in_out(self):
s11 = S('command', 'li', 'a', 3)
s12 = S('command', 'li', 'b', 5)
s13 = S('command', 'li', 'd', 4)
s14 = S('command', 'li', 'x', 100)
s15 = S('command', 'beq', 'a', 'b', 'L1')
s21 = S('command', 'addu', 'c', 'a', 'b')
s22 = S('command', 'li', 'd', 2)
s31 = S('label', 'L1')
s32 = S('command', 'li', 'c', 4)
s33 = S('command', 'mult', 'b', 'd')
s34 = S('command', 'mflo', 'temp')
s35 = S('command', 'addu', 'return', 'temp', 'c')
b1, b2, b3 = find_basic_blocks([s11, s12, s13, s14, s15, s21, s22, \
s31, s32, s33, s34, s35])
generate_flow_graph([b1, b2, b3])
create_in_out([b1, b2, b3])
self.assertEqual(b1.gen_set, set([s11.sid, s12.sid, s13.sid,
s14.sid]))
self.assertEqual(b1.kill_set, set([s22.sid]))
self.assertEqual(b2.gen_set, set([s21.sid, s22.sid]))
self.assertEqual(b2.kill_set, set([s13.sid, s32.sid]))
self.assertEqual(b3.gen_set, set([s32.sid, s34.sid, s35.sid]))
self.assertEqual(b3.kill_set, set([s21.sid]))
self.assertEqual(b1.reach_in, set())
self.assertEqual(b1.reach_out, set([s11.sid, s12.sid, s13.sid,
s14.sid]))
self.assertEqual(b2.reach_in, set([s11.sid, s12.sid, s13.sid,
s14.sid]))
self.assertEqual(b2.reach_out, set([s21.sid, s22.sid, s11.sid, \
s12.sid, s14.sid]))
self.assertEqual(b3.reach_in, set([s21.sid, s22.sid, s11.sid, \
s12.sid, s13.sid, s14.sid]))
self.assertEqual(b3.reach_out, set([s32.sid, s34.sid, s35.sid, \
s22.sid, s11.sid, s12.sid, \
s13.sid, s14.sid]))
...@@ -36,9 +36,10 @@ class TestStatement(unittest.TestCase): ...@@ -36,9 +36,10 @@ class TestStatement(unittest.TestCase):
self.assertFalse(S('comment', 'foo', inline=False).is_label()) self.assertFalse(S('comment', 'foo', inline=False).is_label())
self.assertFalse(S('directive', 'foo').is_command()) self.assertFalse(S('directive', 'foo').is_command())
def test_is_inline_comment(self): def test_has_inline_comment(self):
self.assertTrue(S('comment', 'foo', inline=True).is_inline_comment()) self.assertTrue(S('comment', 'foo', comment='a').has_inline_comment())
self.assertFalse(S('comment', 'foo', inline=False).is_inline_comment()) self.assertFalse(S('comment', 'foo', comment='').has_inline_comment())
self.assertFalse(S('comment', 'foo').has_inline_comment())
def test_jump_target(self): def test_jump_target(self):
self.assertEqual(S('command', 'j', 'foo').jump_target(), 'foo') self.assertEqual(S('command', 'j', 'foo').jump_target(), 'foo')
...@@ -93,3 +94,79 @@ class TestStatement(unittest.TestCase): ...@@ -93,3 +94,79 @@ class TestStatement(unittest.TestCase):
self.assertTrue(S('command', 'addu', '$1', '$2', '$3').is_arith()) self.assertTrue(S('command', 'addu', '$1', '$2', '$3').is_arith())
self.assertFalse(S('command', 'foo').is_arith()) self.assertFalse(S('command', 'foo').is_arith())
self.assertFalse(S('label', 'addu').is_arith()) self.assertFalse(S('label', 'addu').is_arith())
def test_get_def_true(self):
a = ['a']
self.assertEqual(S('command', 'move', 'a', 'b').get_def(), a)
self.assertEqual(S('command', 'subu', 'a', 'b', 'c').get_def(), a)
self.assertEqual(S('command', 'addu', 'a', 'b', 'c').get_def(), a)
self.assertEqual(S('command', 'div', 'a', 'b', 'c').get_def(), a)
self.assertEqual(S('command', 'sll', 'a', 'b', 'c').get_def(), a)
self.assertEqual(S('command', 'srl', 'a', 'b', 'c').get_def(), a)
self.assertEqual(S('command', 'la', 'a', '16($fp)').get_def(), a)
self.assertEqual(S('command', 'li', 'a', '16($fp)').get_def(), a)
self.assertEqual(S('command', 'lw', 'a', 'b').get_def(), a)
self.assertEqual(S('command', 'l.d', 'a', 'b').get_def(), a)
self.assertEqual(S('command', 'add.d', 'a', 'b', 'c').get_def(), a)
self.assertEqual(S('command', 'neg.d', 'a', 'b').get_def(), a)
self.assertEqual(S('command', 'sub.d', 'a', 'b', 'c').get_def(), a)
self.assertEqual(S('command', 'slt', 'a', 'b').get_def(), a)
self.assertEqual(S('command', 'xori', 'a', 'b', '0x0000').get_def(), a)
self.assertEqual(S('command', 'mov.d', 'a', 'b').get_def(), a)
self.assertEqual(S('command', 'dmfc1', 'a', '$f0').get_def(), a)
self.assertEqual(S('command', 'mtc1', 'b', 'a').get_def(), a)
self.assertEqual(S('command', 'trunc.w.d', 'a', 'b', 'c').get_def(), a)
def test_get_def_false(self):
self.assertEqual(S('command', 'bne', 'a', 'b', 'L1').get_def(), [])
self.assertEqual(S('command', 'beq', 'a', 'b', 'L1').get_def(), [])
def test_get_use_true(self):
arg1 = ['$1']
arg2 = ['$1', '$2']
self.assertEqual(S('command', 'addu', '$3', '$1', '$2').get_use(), \
arg2)
self.assertEqual(S('command', 'subu', '$3', '$1', '$2').get_use(), \
arg2)
self.assertEqual(S('command', 'mult', '$1', '$2').get_use(), arg2)
self.assertEqual(S('command', 'div', '$3', '$1', '$2').get_use(), arg2)
self.assertEqual(S('command', 'move', '$2', '$1').get_use(), arg1)
self.assertEqual(S('command', 'beq', '$1', '$2', '$L1').get_use(), \
arg2)
self.assertEqual(S('command', 'bne', '$1', '$2', '$L1').get_use(), \
arg2)
self.assertEqual(S('command', 'sll', '$2', '$1', 2).get_use(), arg1)
self.assertEqual(S('command', 'lb', '$2', '10($1)').get_use(), arg1)
self.assertEqual(S('command', 'lw', '$2', '10($1)').get_use(), arg1)
self.assertEqual(S('command', 'la', '$2', '10($1)').get_use(), arg1)
self.assertEqual(S('command', 'lb', '$2', 'n.7').get_use(), ['n.7'])
self.assertEqual(S('command', 'lbu', '$2', '10($1)').get_use(), arg1)
self.assertEqual(S('command', 'l.d', '$2', '10($1)').get_use(), arg1)
self.assertEqual(S('command', 's.d', '$1', '10($2)').get_use(), \
arg2)
self.assertEqual(S('command', 's.s', '$1', '10($2)').get_use(), \
arg2)
self.assertEqual(S('command', 'sw', '$1', '10($2)').get_use(), \
arg2)
self.assertEqual(S('command', 'sb', '$1', '10($2)').get_use(), \
arg2)
self.assertEqual(S('command', 'mtc1', '$1', '$2').get_use(), arg1)
self.assertEqual(S('command', 'add.d', '$3', '$1', '$2').get_use(), \
arg2)
self.assertEqual(S('command', 'sub.d', '$3', '$1', '$2').get_use(), \
arg2)
self.assertEqual(S('command', 'div.d', '$3', '$1', '$2').get_use(), \
arg2)
self.assertEqual(S('command', 'mul.d', '$3', '$1', '$2').get_use(), \
arg2)
self.assertEqual(S('command', 'neg.d', '$2', '$1').get_use(), arg1)
self.assertEqual(S('command', 'abs.d', '$2', '$1').get_use(), arg1)
self.assertEqual(S('command', 'dsz', '10($1)', '$2').get_use(), arg1)
self.assertEqual(S('command', 'dsw', '$1', '10($2)').get_use(), arg2)
self.assertEqual(S('command', 'c.lt.d', '$1', '$2').get_use(), arg2)
self.assertEqual(S('command', 'bgez', '$1', '$2').get_use(), arg1)
self.assertEqual(S('command', 'bltz', '$1', '$2').get_use(), arg1)
self.assertEqual(S('command', 'trunc.w.d', '$3', '$1', '$2').get_use(),
arg2)
import unittest
from src.writer import write_statements
from src.statement import Statement as S, Block as B
class TestWriter(unittest.TestCase):
def setUp(self):
self.foo = S('command', 'move', '$regA', '$regB')
self.bar = S('command', 'addu', '$regC', '$regA', '$regB')
def tearDown(self):
del self.foo
del self.bar
def test_writer_one(self):
output = write_statements([self.foo])
expect = "\tmove\t$regA,$regB\n"
self.assertEqual(output, expect)
def test_writer_longname(self):
command = S('command', 'movemovemove', '$regA', '$regB')
output = write_statements([command])
expect = "\tmovemovemove $regA,$regB\n"
self.assertEqual(output, expect)
def test_writer_several(self):
output = write_statements([self.foo, self.bar, self.foo])
expect = "\tmove\t$regA,$regB\n" \
+ "\taddu\t$regC,$regA,$regB\n" \
+ "\tmove\t$regA,$regB\n"
self.assertEqual(output, expect)
def test_writer_with_label(self):
label = S('label', '$L1')
output = write_statements([self.foo, label, self.bar])
expect = "\tmove\t$regA,$regB\n" \
+ "$L1:\n" \
+ "\taddu\t$regC,$regA,$regB\n"
self.assertEqual(output, expect)
def test_writer_with_comment(self):
comment = S('comment', 'tralala')
output = write_statements([self.foo, comment, self.bar])
expect = "\tmove\t$regA,$regB\n" \
+ "\n#tralala\n\n" \
+ "\taddu\t$regC,$regA,$regB\n"
self.assertEqual(output, expect)
def test_writer_with_comment_non_tabbed(self):
directive = S('comment', 'tralala')
output = write_statements([directive, self.foo, self.bar])
expect = "\n#tralala\n\n" \
+ "\tmove\t$regA,$regB\n" \
+ "\taddu\t$regC,$regA,$regB\n"
self.assertEqual(output, expect)
def test_writer_with_inlinecomment(self):
self.foo.options['comment'] = 'tralala'
output = write_statements([self.foo, self.bar])
expect = "\tmove\t$regA,$regB" \
+ "\t\t#tralala\n" \
+ "\taddu\t$regC,$regA,$regB\n"
self.assertEqual(output, expect)
def test_writer_with_directive(self):
directive = S('directive', '.tralala trololo')
output = write_statements([self.foo, directive, self.bar])
expect = "\tmove\t$regA,$regB\n" \
+ "\t.tralala trololo\n" \
+ "\taddu\t$regC,$regA,$regB\n"
self.assertEqual(output, expect)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment