Commit 405dc7a5 authored by Richard Torenvliet's avatar Richard Torenvliet

Merge branch 'master' of github.com:taddeus/peephole

parents 41528750 58c6acb7
This diff is collapsed.
This diff is collapsed.
...@@ -120,13 +120,27 @@ We now add the instruction above the first use, and write the result in a new ...@@ -120,13 +120,27 @@ 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*{Fold constants} \subsubsection*{Constant folding}
Another optimization is to do constant folding. Constant folding is replacing
a expensive step like addition with a more simple step like loading a constant.
Of course, this is not always possible. It is possible in cases where you apply
an operation on two constants, or a constant and a variable of which you know
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
the program often also stores this in the memory, so we had to check whether
this was necessary here as well.
\subsubsection*{Copy propagation} \subsubsection*{Copy propagation}
...@@ -153,12 +167,11 @@ of the move operation. ...@@ -153,12 +167,11 @@ 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, -> ... Code not writing $regA, $regB -> ...
$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
...@@ -166,7 +179,18 @@ removed by the dead code elimination. ...@@ -166,7 +179,18 @@ removed by the dead code elimination.
\subsubsection*{Algebraic transformations} \subsubsection*{Algebraic transformations}
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 case is the multiplication with a power of two. This can be done way
more efficiently by shifting left a number of times. An example:
\texttt{mult \$regA, \$regB, 4 -> sll \$regA, \$regB, 2}. We perform this
optimization for any multiplication with a power of two.
There are a number of such cases, all of which are once again stated in
appendix \ref{opt}.
\section{Implementation} \section{Implementation}
...@@ -203,7 +227,7 @@ The optimizations are done in two different steps. First the global ...@@ -203,7 +227,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 seperated into After all possible global optimizations are done, the program is separated 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.
...@@ -215,7 +239,7 @@ steps can be done to optimize something. ...@@ -215,7 +239,7 @@ 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 crosscompiler can make binary code out of it. Assembly code, so the xgcc cross compiler can make binary code out of it.
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
......
#from copy import copy from copy import copy
from statement import Block from statement import Block
...@@ -25,40 +25,85 @@ class BasicBlock(Block): ...@@ -25,40 +25,85 @@ class BasicBlock(Block):
if block not in self.dominates: if block not in self.dominates:
self.dominates.append(block) self.dominates.append(block)
block.dominated_by.append(self) block.dominated_by.append(self)
def create_gen_kill(self, defs):
# def get_gen(self): used = set()
# for s in self.statements: self_defs = {}
# if s.is_arith():
# self.gen_set.add(s[0]) # Get the last of each definition series and put in in the `def' set
# print 'added: ', s[0] self.gen_set = set()
#
# return self.gen_set for s in reversed(self):
# for reg in s.get_def():
# def get_kill(self): if reg not in self_defs:
## if self.edges_from != []: print 'Found def:', s
# self_defs[reg] = s.sid
# for backw in self.edges_from: self.gen_set.add(s.sid)
# self.kill_set = self.gen_set & backw.kill_set
# # Generate kill set
# self.kill_set = self.kill_set - self.get_gen() self.kill_set = set()
# print 'get_kill_set', self.kill_set
# return self.kill_set for reg, statement_ids in defs.iteritems():
if reg in self_defs:
# def get_in(self): add = statement_ids - set([self_defs[reg]])
# for backw in self.edges_from: else:
# self.in_set = self.in_set | backw.out_set add = statement_ids
# print 'in_set', self.in_set
# return self.in_set self.kill_set |= add
# def get_out(self):
# print 'gen_set', self.gen_set def defs(blocks):
# print 'get_in', self.get_in() # Collect definitions of all registers
# print 'get_kill', self.get_kill() defs = {}
# self.out_set = self.gen_set | (self.get_in() - self.get_kill())
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:
......
...@@ -3,7 +3,7 @@ from src.dataflow import find_basic_blocks ...@@ -3,7 +3,7 @@ from src.dataflow import find_basic_blocks
from redundancies import remove_redundant_jumps, move_1, move_2, move_3, \ from redundancies import remove_redundant_jumps, move_1, move_2, move_3, \
move_4, load, shift, add 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 copy_propagation, algebraic_transformations, eliminate_dead_code
def remove_redundancies(block): def remove_redundancies(block):
...@@ -32,7 +32,8 @@ def optimize_block(block): ...@@ -32,7 +32,8 @@ def optimize_block(block):
| eliminate_common_subexpressions(block) \ | eliminate_common_subexpressions(block) \
| fold_constants(block) \ | fold_constants(block) \
| copy_propagation(block)\ | copy_propagation(block)\
| algebraic_transformations(block): | algebraic_transformations(block) \
| eliminate_dead_code(block):
pass pass
...@@ -63,6 +64,6 @@ def optimize(statements, verbose=0): ...@@ -63,6 +64,6 @@ def optimize(statements, verbose=0):
print 'After global optimization: %d' % g print 'After global optimization: %d' % g
print 'After basic blocks optimization: %d' % b print 'After basic blocks optimization: %d' % b
print 'Optimization: %d (%d%%)' \ print 'Optimization: %d (%d%%)' \
% (b - o, int((b - o) / float(o) * 100)) % (o - b, int((o - b) / float(b) * 100))
return opt_blocks return opt_blocks
This diff is collapsed.
...@@ -2,12 +2,18 @@ import re ...@@ -2,12 +2,18 @@ import re
class Statement: class Statement:
sid = 1
def __init__(self, stype, name, *args, **kwargs): def __init__(self, stype, name, *args, **kwargs):
self.stype = stype self.stype = stype
self.name = name self.name = name
self.args = list(args) self.args = list(args)
self.options = kwargs self.options = kwargs
# Assign a unique ID to each satement
self.sid = Statement.sid
Statement.sid += 1
def __getitem__(self, n): def __getitem__(self, n):
"""Get an argument.""" """Get an argument."""
return self.args[n] return self.args[n]
...@@ -26,8 +32,8 @@ class Statement: ...@@ -26,8 +32,8 @@ class Statement:
return len(self.args) return len(self.args)
def __str__(self): # pragma: nocover def __str__(self): # pragma: nocover
return '<Statement type=%s name=%s args=%s>' \ return '<Statement sid=%d type=%s name=%s args=%s>' \
% (self.stype, self.name, self.args) % (self.sid, self.stype, self.name, self.args)
def __repr__(self): # pragma: nocover def __repr__(self): # pragma: nocover
return str(self) return str(self)
...@@ -62,16 +68,21 @@ class Statement: ...@@ -62,16 +68,21 @@ class Statement:
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|la|rl|ra)$', self.name) return self.is_command() and re.match('^s(ll|rl|ra)$', self.name)
def is_load(self): def is_load(self):
"""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', 'dlw', 'l.s', 'l.d'] return self.is_command() and self.name in ['lw', 'li', 'dlw', 'l.s', \
'l.d']
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() \
and re.match('^(add|sub|mult|div|abs|neg)(u|\.d)?$', self.name) and re.match('^s(ll|rl|ra)'
+ '|(mfhi|mflo|abs|neg|and|[xn]?or)'
+ '|(add|sub|slt)u?'
+ '|(add|sub|mult|div|abs|neg|sqrt|c)\.[sd]$', \
self.name)
def is_monop(self): def is_monop(self):
"""Check if the statement is an unary operation.""" """Check if the statement is an unary operation."""
...@@ -80,23 +91,92 @@ class Statement: ...@@ -80,23 +91,92 @@ 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):
"""Check if the statement is a load statement."""
return self.is_command() \
and re.match('^l(w|a|b|bu|\.d|\.s)|dlw$', \
self.name)
def is_logical(self):
"""Check if the statement is a logical operator."""
return self.is_command() and re.match('^(xor|or|and)i?$', self.name)
def is_double_aritmethic(self):
"""Check if the statement is a arithmetic .d operator."""
return self.is_command() and \
re.match('^(add|sub|div|mul)\.d$', self.name)
def is_double_unary(self):
"""Check if the statement is a unary .d operator."""
return self.is_command() and \
re.match('^(abs|neg|mov)\.d$', self.name)
def is_move_from_spec(self):
"""Check if the statement is a move from the result register."""
return self.is_command() and self.name in ['mflo', 'mthi']
def is_set_if_less(self):
"""Check if the statement is a shift if less then."""
return self.is_command() and self.name in ['slt', 'sltu']
def is_convert(self):
"""Check if the statement is a convert operator."""
return self.is_command() and re.match('^cvt\.[a-z\.]*$', self.name)
def is_truncate(self):
"""Check if the statement is a convert operator."""
return self.is_command() and re.match('^trunc\.[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):
"""Get the variable that this statement defines, if any."""
instr = ['move', 'addu', 'subu', 'li', 'mtc1', 'dmfc1']
if self.is_load_non_immediate() or self.is_arith() \
or self.is_logical() or self.is_double_arithmetic() \
or self.is_move_from_spec() or self.is_double_unary() \
or self.is_set_if_less() or self.is_convert() \
or self.is_truncate() or self.is_load() \
or (self.is_command and self.name in instr):
return self[0]
return []
def get_use(self):
# TODO: Finish with ALL the available commands!
use = []
if self.is_binop():
use += self[1:]
elif self.is_command('move'):
use.append(self[1])
elif self.is_command('lw', 'sb', 'sw', 'dsw', 's.s', 's.d'):
m = re.match('^\d+\(([^)]+)\)$', self[1])
if m:
use.append(m.group(1))
# 'sw' also uses its first argument
if self.name in ['sw', 'dsw']:
use.append(self[0])
elif len(self) == 2: # FIXME: temporary fix, manually add all commands
use.append(self[1])
return use
def defines(self, reg): def defines(self, reg):
"""Check if this statement defines the given register.""" """Check if this statement defines the given register."""
# TODO: Finish return reg in self.get_def()
return (self.is_load() or self.is_arith()) and self[0] == reg
def uses(self, reg): def uses(self, reg):
"""Check if this statement uses the given register.""" """Check if this statement uses the given register."""
# TODO: Finish return reg in self.get_use()
return (self.is_load() or self.is_arith()) and reg in self[1:]
class Block: class Block:
......
...@@ -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 generate_flow_graph, Dag, DagNode, DagLeaf, defs, reaching_definitions
class TestDataflow(unittest.TestCase): class TestDataflow(unittest.TestCase):
...@@ -23,12 +23,12 @@ class TestDataflow(unittest.TestCase): ...@@ -23,12 +23,12 @@ class TestDataflow(unittest.TestCase):
self.assertEqual(map(lambda b: b.statements, find_basic_blocks(s)), \ self.assertEqual(map(lambda b: b.statements, find_basic_blocks(s)), \
[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): # def test_get_gen(self):
# b1 = B([S('command', 'add', '$1', '$2', '$3'), \ # b1 = B([S('command', 'add', '$1', '$2', '$3'), \
# S('command', 'add', '$2', '$3', '$4'), \ # S('command', 'add', '$2', '$3', '$4'), \
# S('command', 'add', '$1', '$4', '$5')]) # S('command', 'add', '$1', '$4', '$5')])
# #
# self.assertEqual(b1.get_gen(), ['$1', '$2']) # self.assertEqual(b1.get_gen(), ['$1', '$2'])
# def test_get_out(self): # def test_get_out(self):
...@@ -36,18 +36,18 @@ class TestDataflow(unittest.TestCase): ...@@ -36,18 +36,18 @@ class TestDataflow(unittest.TestCase):
# S('command', 'add', '$2', '$3', '$4'), \ # S('command', 'add', '$2', '$3', '$4'), \
# S('command', 'add', '$1', '$4', '$5'), \ # S('command', 'add', '$1', '$4', '$5'), \
# S('command', 'j', 'b2')]) # S('command', 'j', 'b2')])
# #
# b2 = B([S('command', 'add', '$3', '$5', '$6'), \ # b2 = B([S('command', 'add', '$3', '$5', '$6'), \
# S('command', 'add', '$1', '$2', '$3'), \ # S('command', 'add', '$1', '$2', '$3'), \
# S('command', 'add', '$6', '$4', '$5')]) # S('command', 'add', '$6', '$4', '$5')])
# #
# blocks = [b1, b2] # blocks = [b1, b2]
# #
# # initialize out[B] = gen[B] for every block # # initialize out[B] = gen[B] for every block
# for block in blocks: # for block in blocks:
# block.out_set = block.get_gen() # block.out_set = block.get_gen()
# print 'block.out_set', block.out_set # print 'block.out_set', block.out_set
# #
# generate_flow_graph(blocks) # generate_flow_graph(blocks)
# change = True # change = True
...@@ -112,6 +112,46 @@ class TestDataflow(unittest.TestCase): ...@@ -112,6 +112,46 @@ class TestDataflow(unittest.TestCase):
# #
# self.assertEqualDag(dag, expect) # 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): def assertEqualDag(self, dag1, dag2):
self.assertEqual(len(dag1.nodes), len(dag2.nodes)) self.assertEqual(len(dag1.nodes), len(dag2.nodes))
......
...@@ -19,9 +19,9 @@ class TestOptimizeAdvanced(unittest.TestCase): ...@@ -19,9 +19,9 @@ class TestOptimizeAdvanced(unittest.TestCase):
def test_eliminate_common_subexpressions_simple(self): def test_eliminate_common_subexpressions_simple(self):
b = B([S('command', 'addu', '$regC', '$regA', '$regB'), b = B([S('command', 'addu', '$regC', '$regA', '$regB'),
S('command', 'addu', '$regD', '$regA', '$regB')]) S('command', 'addu', '$regD', '$regA', '$regB')])
e = [S('command', 'addu', '$t0', '$regA', '$regB'), \ e = [S('command', 'addu', '$8', '$regA', '$regB'), \
S('command', 'move', '$regC', '$t0'), \ S('command', 'move', '$regC', '$8'), \
S('command', 'move', '$regD', '$t0')] S('command', 'move', '$regD', '$8')]
eliminate_common_subexpressions(b) eliminate_common_subexpressions(b)
self.assertEqual(b.statements, e) self.assertEqual(b.statements, e)
...@@ -49,6 +49,20 @@ class TestOptimizeAdvanced(unittest.TestCase): ...@@ -49,6 +49,20 @@ 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):
block = B([self.foo,
S('command', 'move', '$1', '$2'),
self.foo,
S('command', 'addu', '$3', '$4', '$1'),
self.bar])
self.assertTrue(copy_propagation(block))
self.assertEqual(block.statements, [self.foo,
S('command', 'move', '$1', '$2'),
self.foo,
S('command', 'addu', '$3', '$4', '$2'),
self.bar])
def test_copy_propagation_overwrite(self): def test_copy_propagation_overwrite(self):
block = B([self.foo, \ block = B([self.foo, \
...@@ -125,7 +139,8 @@ class TestOptimizeAdvanced(unittest.TestCase): ...@@ -125,7 +139,8 @@ class TestOptimizeAdvanced(unittest.TestCase):
def test_algebraic_transforms_mult0(self): def test_algebraic_transforms_mult0(self):
block = B([self.foo, block = B([self.foo,
S('command', 'mult', '$1', '$2', 0), S('command', 'mult', '$2', 0),
S('command', 'mflo', '$1'),
self.bar]) self.bar])
self.assertTrue(algebraic_transformations(block)) self.assertTrue(algebraic_transformations(block))
...@@ -135,7 +150,8 @@ class TestOptimizeAdvanced(unittest.TestCase): ...@@ -135,7 +150,8 @@ class TestOptimizeAdvanced(unittest.TestCase):
def test_algebraic_transforms_mult1(self): def test_algebraic_transforms_mult1(self):
block = B([self.foo, block = B([self.foo,
S('command', 'mult', '$1', '$2', 1), S('command', 'mult', '$2', 1),
S('command', 'mflo', '$1'),
self.bar]) self.bar])
self.assertTrue(algebraic_transformations(block)) self.assertTrue(algebraic_transformations(block))
...@@ -145,7 +161,8 @@ class TestOptimizeAdvanced(unittest.TestCase): ...@@ -145,7 +161,8 @@ class TestOptimizeAdvanced(unittest.TestCase):
def test_algebraic_transforms_mult2(self): def test_algebraic_transforms_mult2(self):
block = B([self.foo, block = B([self.foo,
S('command', 'mult', '$1', '$2', 2), S('command', 'mult', '$2', 2),
S('command', 'mflo', '$1'),
self.bar]) self.bar])
self.assertTrue(algebraic_transformations(block)) self.assertTrue(algebraic_transformations(block))
...@@ -155,7 +172,8 @@ class TestOptimizeAdvanced(unittest.TestCase): ...@@ -155,7 +172,8 @@ class TestOptimizeAdvanced(unittest.TestCase):
def test_algebraic_transforms_mult16(self): def test_algebraic_transforms_mult16(self):
block = B([self.foo, block = B([self.foo,
S('command', 'mult', '$1', '$2', 16), S('command', 'mult', '$2', 16),
S('command', 'mflo', '$1'),
self.bar]) self.bar])
self.assertTrue(algebraic_transformations(block)) self.assertTrue(algebraic_transformations(block))
...@@ -165,7 +183,8 @@ class TestOptimizeAdvanced(unittest.TestCase): ...@@ -165,7 +183,8 @@ class TestOptimizeAdvanced(unittest.TestCase):
def test_algebraic_transforms_mult3(self): def test_algebraic_transforms_mult3(self):
arguments = [self.foo, arguments = [self.foo,
S('command', 'mult', '$1', '$2', 3), S('command', 'mult', '$2', 3),
S('command', 'mflo', '$1'),
self.bar] self.bar]
block = B(arguments) block = B(arguments)
......
...@@ -90,6 +90,6 @@ class TestStatement(unittest.TestCase): ...@@ -90,6 +90,6 @@ class TestStatement(unittest.TestCase):
self.assertFalse(S('label', 'lw').is_load()) self.assertFalse(S('label', 'lw').is_load())
def test_is_arith(self): def test_is_arith(self):
self.assertTrue(S('command', 'add', '$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', 'add').is_arith()) self.assertFalse(S('label', 'addu').is_arith())
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