Răsfoiți Sursa

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

Richard Torenvliet 14 ani în urmă
părinte
comite
18a29e6f04

+ 1 - 0
benchmarks/optimized/.gitignore

@@ -0,0 +1 @@
+*.s

+ 1 - 2
benchmarks/optimized/acron.s

@@ -180,7 +180,6 @@ $L5:
 	addu	$3,$16,$17
 	addu	$2,$3,$2
 	sw	$2,24($fp)
-	lw	$2,24($fp)
 	beq	$2,$0,$L8
 	lw	$2,24($fp)
 	li	$3,0x00000003		# 3
@@ -410,4 +409,4 @@ $L27:
 	lw	$fp,24($sp)
 	addu	$sp,$sp,32
 	j	$31
-	.end	main
+	.end	main

+ 68 - 0
report/report.tex

@@ -116,6 +116,50 @@ 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
 primary concern, this is not a big problem.
 
+\subsubsection*{Fold constants}
+
+
+
+\subsubsection*{Copy propagation}
+
+Copy propagation `unpacks' a move instruction, by replacing its destination
+address with its source address in the code following the move instruction.
+
+This is not a direct optimization, but this does allow for a more effective
+dead code elimination.
+
+The code of the block is checked linearly. When a move operation is
+encountered, the source and destination address of this move are stored. When
+a normal operation with a source and a destination address are found, a number
+of checks are performed.
+
+The first check is whether the destination address is stored as a destination
+address of a move instruction. If so, this move instruction is no longer valid,
+so the optimizations can not be done. Otherwise, continue with the second
+check.
+
+In the second check, the source address is compared to the destination
+addresses of all still valid move operations. If these are the same, in the
+current operation the found source address is replaced with the source address
+of the move operation.
+
+An example would be the following:
+\begin{verbatim}
+move $regA, $regB           move $regA, $regB
+...                         ...
+Code not writing $regA, ->  ...
+$regB                       ...
+...                         ...
+addu $regC, $regA, ...      addu $regC, $regB, ...
+\end{verbatim}
+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
+removed by the dead code elimination.
+
+\subsubsection*{Algebraic transformations}
+
+
+
 \section{Implementation}
 
 We decided to implement the optimization in Python. We chose this programming
@@ -236,5 +280,29 @@ addu $regA, $regB, 4        addu $regD, $regB, 4
 Code not writing $regB  ->  ...
 ...                         ...
 addu $regC, $regB, 4        move $regC, $regD
+
+
+# Constant folding
+
+
+# Copy propagation
+move $regA, $regB           move $regA, $regB
+...                         ...
+Code not writing $regA, ->  ...
+$regB                       ...
+...                         ...
+addu $regC, $regA, ...      addu $regC, $regB, ...
+
+
+# Algebraic transformations
+addu $regA, $regB, 0    ->  move $regA, $regB
+
+subu $regA, $regB, 0    ->  move $regA, $regB
+
+mult $regA, $regB, 1    ->  move $regA, $regB
+
+mult $regA, $regB, 0    ->  li   $regA, 0
+
+mult $regA, $regB, 2    ->  sll  $regA, $regB, 1
 \end{verbatim}
 \end{document}

+ 18 - 44
src/optimize/__init__.py

@@ -1,73 +1,47 @@
 from src.dataflow import find_basic_blocks
 
-from standard import redundant_move_1, redundant_move_2, \
-        redundant_move_3, redundant_move_4, redundant_load, \
-        redundant_shift, redundant_add
+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, \
-    copy_propagation
+        copy_propagation, algebraic_transformations
 
 
-def optimize_global(statements):
-    """Optimize statement sequences on a global level."""
+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(statements):
-        old_len = len(statements)
-
-        while not statements.end():
-            s = statements.read()
-
-            #     beq/bne ..., $Lx      ->      bne/beq ..., $Ly
-            #     j $Ly                     $Lx:
-            # $Lx:
-            if s.is_command('beq', 'bne'):
-                following = statements.peek(2)
-
-                if len(following) == 2:
-                    j, label = following
-
-                    if j.is_command('j') and label.is_label(s[2]):
-                        s.name = 'bne' if s.is_command('beq') else 'beq'
-                        s[2] = j[0]
-                        statements.replace(3, [s, label])
-
-
-def optimize_block(block):
-    """Optimize a basic block."""
-    standard = [redundant_move_1, redundant_move_2, redundant_move_3, \
-                redundant_move_4, redundant_load, redundant_shift, \
-                redundant_add]
-    old_len = -1
-
-    # Standard optimizations
     while old_len != len(block):
         old_len = len(block)
 
         while not block.end():
             s = block.read()
 
-            for callback in standard:
+            for callback in callbacks:
                 if callback(s, block):
+                    changed = True
                     break
 
-    # Advanced optimizations
-    #changed = True
+    return changed
 
-    #while changed:
-    #    changed = eliminate_common_subexpressions(block) \
-    #              or fold_constants(block)
 
-    while eliminate_common_subexpressions(block) \
+def optimize_block(block):
+    """Optimize a basic block."""
+    while remove_redundancies(block) \
+            | eliminate_common_subexpressions(block) \
             | fold_constants(block) \
-            | copy_propagation(block):
+            | copy_propagation(block)\
+            | algebraic_transformations(block):
         pass
 
+
 def optimize(statements, verbose=0):
     """Optimization wrapper function, calls global and basic-block level
     optimization functions."""
     # Optimize on a global level
     o = len(statements)
-    optimize_global(statements)
+    remove_redundant_jumps(statements)
     g = len(statements)
 
     # Optimize basic blocks

+ 102 - 34
src/optimize/advanced.py

@@ -1,13 +1,36 @@
 from src.statement import Statement as S
+from math import log
 
 
-def create_variable():
-    return '$15'
+def reg_dead_in(var, context):
+    """Check if a register is `dead' in a given list of statements."""
+    # TODO: Finish
+    for s in context:
+        if s.defines(var) or s.uses(var):
+            return False
+
+    return True
+
+
+def find_free_reg(context):
+    """Find a temporary register that is free in a given list of statements."""
+    for i in xrange(8):
+        tmp = '$t%d' % i
+
+        if reg_dead_in(tmp, context):
+            return tmp
+
+    raise Exception('No temporary register is available.')
 
 
 def eliminate_common_subexpressions(block):
     """
     Common subexpression elimination:
+    x = a + b           ->  u = a + b
+    y = a + b               x = u
+                            y = u
+
+    The algorithm used is as follows:
     - Traverse through the statements in reverse order.
     - If the statement can be possibly be eliminated, walk further collecting
       all other occurrences of the expression until one of the arguments is
@@ -39,19 +62,22 @@ def eliminate_common_subexpressions(block):
                 # Replace a similar expression by a move instruction
                 if s2.name == s.name and s2[1:] == args:
                     if not new_reg:
-                        new_reg = create_variable()
+                        new_reg = find_free_reg(block[:pointer])
 
                     block.replace(1, [S('command', 'move', s2[0], new_reg)])
                     last = block.pointer
 
-            # Insert an additional expression with a new destination address
-            if last:
-                block.insert(S('command', s.name, [new_reg] + args), last)
-                found = True
-
             # Reset pointer to and continue from the original statement
             block.pointer = pointer
 
+            if last:
+                # Insert an additional expression with a new destination address
+                block.insert(S('command', s.name, *([new_reg] + args)), last)
+
+                # Replace the original expression with a move statement
+                block.replace(1, [S('command', 'move', s[0], new_reg)])
+                found = True
+
     block.reverse_statements()
 
     return found
@@ -65,7 +91,11 @@ def to_hex(value):
 def fold_constants(block):
     """
     Constant folding:
-    - An immidiate load defines a register value:
+    x = 3 + 5           ->  x = 8
+    y = x * 2               y = 16
+
+    To keep track of constant values, the following assumptions are made:
+    - An immediate load defines a register value:
         li $reg, XX     ->  register[$reg] = XX
     - Integer variable definition is of the following form:
         li $reg, XX     ->  constants[VAR] = XX
@@ -130,7 +160,7 @@ def fold_constants(block):
                 if s.name == 'div':
                     result = to_hex(rs_val / rt_val)
 
-                block.replace(1, [S('command', 'li', result)])
+                block.replace(1, [S('command', 'li', rd, result)])
                 register[rd] = result
                 found = True
             elif rt_known:
@@ -153,50 +183,88 @@ def fold_constants(block):
 
 def copy_propagation(block):
     """
-    Rename values that were copied to there original, so the copy statement
-    might be useless, allowing it to be removed by dead code elimination.
+    Unpack a move instruction, by replacing its destination
+    address with its source address in the code following the move instruction.
+    This way, the move statement might be a target for dead code elimination.
+
+    move $regA, $regB           move $regA, $regB
+    ...                         ...
+    Code not writing $regA, ->  ...
+    $regB                       ...
+    ...                         ...
+    addu $regC, $regA, ...      addu $regC, $regB, ...
     """
     moves_from = []
     moves_to = []
+    changed = False
 
     while not block.end():
         s = block.read()
-        
-        if len(s) == 3:
-            print "s[0] = ", s[0]
-            print "s[1] = ", s[1]
-            print "s[2] = ", s[2]
-            
-            if moves_from:
-                print "fr: ", moves_from
-                print "to: ", moves_to
 
         if s.is_command('move') and s[0] not in moves_to:
+            # Add this move to the lists, because it is not yet there.
             moves_from.append(s[1])
             moves_to.append(s[0])
-            print "Added move to list."
-        elif s.is_command('move'):
+        elif s.is_command('move') and s[0] in moves_to:
+            # This move is already in the lists, so only update it
             for i in xrange(len(moves_to)):
                 if moves_to[i] == s[0]:
                     moves_from[i] = s[1]
+                    break
         elif len(s) == 3 and s[0] in moves_to:
-            print len(s)
-            print len(moves_to)
-            for i in xrange(len(moves_to)):
+            # The result gets overwritten, so remove the data from the list.
+            i = 0
+            while i  < len(moves_to):
                 if moves_to[i] == s[0]:
                     del moves_to[i]
                     del moves_from[i]
-                    "Removed move from list."
+                else:
+                    i += 1
         elif len(s) == 3 and (s[1] in moves_to or s[2] in moves_to):
-            print "Have to propagate."
+            # Check where the result of the move is used and replace it with
+            # the original variable.
             for i in xrange(len(moves_to)):
                 if s[1] == moves_to[i]:
                     s[1] = moves_from[i]
-                    print "Propagated"
-                
+                    break
+
                 if s[2] == moves_to[i]:
                     s[2] = moves_from[i]
-                    print "Propagated"
-                    
-        print ""       
-    return False
+                    break
+
+            changed = True
+
+    return changed
+
+
+def algebraic_transformations(block):
+    """
+    Change ineffective or useless algebraic transformations. Handled are:
+    - x = y + 0 -> x = y
+    - x = y - 0 -> x = y
+    - x = y * 1 -> x = y
+    - x = y * 0 -> x = 0
+    - x = y * 2 -> x = x << 1
+    """
+    changed = False
+
+    while not block.end():
+        s = block.read()
+
+        if (s.is_command('addu') or s.is_command('subu')) and s[2] == 0:
+            block.replace(1, [S('command', 'move', s[0], s[1])])
+            changed = True
+        elif s.is_command('mult') and s[2] == 1:
+            block.replace(1, [S('command', 'move', s[0], s[1])])
+            changed = True
+        elif s.is_command('mult') and s[2] == 0:
+            block.replace(1, [S('command', 'li', '$1', to_hex(0))])
+            changed = True
+        elif s.is_command('mult'):
+            shift_amount = log(s[2], 2)
+            if shift_amount.is_integer():
+                new_command = S('command', 'sll', s[0], s[1], shift_amount)
+                block.replace(1, [new_command])
+                changed = True
+
+    return changed

+ 32 - 7
src/optimize/standard.py → src/optimize/redundancies.py

@@ -1,7 +1,7 @@
 import re
 
 
-def redundant_move_1(mov, statements):
+def move_1(mov, statements):
     """
     mov $regA, $regA          ->  --- remove it
     """
@@ -11,7 +11,7 @@ def redundant_move_1(mov, statements):
         return True
 
 
-def redundant_move_2(mov, statements):
+def move_2(mov, statements):
     """
     mov $regA, $regB          ->  instr $regA, $regB, ...
     instr $regA, $regA, ...
@@ -26,7 +26,7 @@ def redundant_move_2(mov, statements):
             return True
 
 
-def redundant_move_3(ins, statements):
+def move_3(ins, statements):
     """
     instr $regA, ...          ->  instr $4, ...
     mov $4, $regA                 jal XX
@@ -47,7 +47,7 @@ def redundant_move_3(ins, statements):
                 return True
 
 
-def redundant_move_4(mov1, statements):
+def move_4(mov1, statements):
     """
     mov $RegA, $RegB         ->  move $RegA, $RegB
     mov $RegB, $RegA
@@ -62,7 +62,7 @@ def redundant_move_4(mov1, statements):
             return True
 
 
-def redundant_load(sw, statements):
+def load(sw, statements):
     """
     sw $regA, XX              ->  sw $regA, XX
     ld $regA, XX
@@ -76,7 +76,7 @@ def redundant_load(sw, statements):
             return True
 
 
-def redundant_shift(shift, statements):
+def shift(shift, statements):
     """
     shift $regA, $regA, 0     ->  --- remove it
     """
@@ -86,7 +86,7 @@ def redundant_shift(shift, statements):
         return True
 
 
-def redundant_add(add, statements):
+def add(add, statements):
     """
     add $regA, $regA, X       ->  lw ..., X($regA)
     lw ..., 0($regA)
@@ -99,3 +99,28 @@ def redundant_add(add, statements):
             statements.replace(2, [lw])
 
             return True
+
+
+def remove_redundant_jumps(statements):
+    """Optimize statement sequences on a global level."""
+    old_len = -1
+
+    while old_len != len(statements):
+        old_len = len(statements)
+
+        while not statements.end():
+            s = statements.read()
+
+            #     beq/bne ..., $Lx      ->      bne/beq ..., $Ly
+            #     j $Ly                     $Lx:
+            # $Lx:
+            if s.is_command('beq', 'bne'):
+                following = statements.peek(2)
+
+                if len(following) == 2:
+                    j, label = following
+
+                    if j.is_command('j') and label.is_label(s[2]):
+                        s.name = 'bne' if s.is_command('beq') else 'beq'
+                        s[2] = j[0]
+                        statements.replace(3, [s, label])

+ 1 - 1
src/parser.py

@@ -123,6 +123,6 @@ def parse_file(filename):
         content = open(filename).read()
         yacc.parse(content)
     except IOError:
-        print 'File "%s" could not be opened' % filename
+        raise Exception('File "%s" could not be opened' % filename)
 
     return Block(statements)

+ 10 - 0
src/statement.py

@@ -88,6 +88,16 @@ class Statement:
 
         return self[-1]
 
+    def defines(self, reg):
+        """Check if this statement defines the given register."""
+        # TODO: Finish
+        return (self.is_load() or self.is_arith()) and self[0] == reg
+
+    def uses(self, reg):
+        """Check if this statement uses the given register."""
+        # TODO: Finish
+        return (self.is_load() or self.is_arith()) and reg in self[1:]
+
 
 class Block:
     def __init__(self, statements=[]):

+ 10 - 9
tests/test_optimize.py

@@ -1,6 +1,7 @@
 import unittest
 
-from src.optimize import optimize_global, optimize_block
+from src.optimize.redundancies import remove_redundant_jumps
+from src.optimize import optimize_block
 from src.statement import Statement as S, Block as B
 
 
@@ -155,51 +156,51 @@ class TestOptimize(unittest.TestCase):
         self.assertEquals(block2.statements, arguments2)
         self.assertEquals(block3.statements, arguments3)
 
-    def test_optimize_global_beq_j_true(self):
+    def test_remove_redundant_jumps_beq_j_true(self):
         block = B([self.foo,
                    S('command', 'beq', '$regA', '$regB', '$Lx'),
                    S('command', 'j', '$Ly'),
                    S('label', '$Lx'),
                    self.bar])
-        optimize_global(block)
+        remove_redundant_jumps(block)
 
         self.assertEquals(block.statements, [self.foo,
                    S('command', 'bne', '$regA', '$regB', '$Ly'),
                    S('label', '$Lx'),
                    self.bar])
 
-    def test_optimize_global_beq_j_false(self):
+    def test_remove_redundant_jumps_beq_j_false(self):
         arguments = [self.foo, \
                      S('command', 'beq', '$regA', '$regB', '$Lz'), \
                      S('command', 'j', '$Ly'), \
                      S('label', '$Lx'), \
                      self.bar]
         block = B(arguments)
-        optimize_global(block)
+        remove_redundant_jumps(block)
 
         self.assertEquals(block.statements, arguments)
 
-    def test_optimize_global_bne_j_true(self):
+    def test_remove_redundant_jumps_bne_j_true(self):
         block = B([self.foo,
                    S('command', 'bne', '$regA', '$regB', '$Lx'),
                    S('command', 'j', '$Ly'),
                    S('label', '$Lx'),
                    self.bar])
-        optimize_global(block)
+        remove_redundant_jumps(block)
 
         self.assertEquals(block.statements, [self.foo,
                    S('command', 'beq', '$regA', '$regB', '$Ly'),
                    S('label', '$Lx'),
                    self.bar])
 
-    def test_optimize_global_bne_j_false(self):
+    def test_remove_redundant_jumps_bne_j_false(self):
         arguments = [self.foo, \
                      S('command', 'bne', '$regA', '$regB', '$Lz'), \
                      S('command', 'j', '$Ly'), \
                      S('label', '$Lx'), \
                      self.bar]
         block = B(arguments)
-        optimize_global(block)
+        remove_redundant_jumps(block)
 
         self.assertEquals(block.statements, arguments)
 

+ 150 - 20
tests/test_optimize_advanced.py

@@ -1,7 +1,8 @@
 import unittest
+from copy import copy
 
 from src.optimize.advanced import eliminate_common_subexpressions, \
-        fold_constants, copy_propagation
+        fold_constants, copy_propagation, algebraic_transformations
 from src.statement import Statement as S, Block as B
 
 
@@ -11,33 +12,162 @@ class TestOptimizeAdvanced(unittest.TestCase):
         self.foo = S('command', 'foo')
         self.bar = S('command', 'bar')
 
-    def test_eliminate_common_subexpressions(self):
+    def tearDown(self):
+        del self.foo
+        del self.bar
+
+    def test_eliminate_common_subexpressions_simple(self):
+        b = B([S('command', 'addu', '$regC', '$regA', '$regB'),
+               S('command', 'addu', '$regD', '$regA', '$regB')])
+        e = [S('command', 'addu', '$t0', '$regA', '$regB'), \
+             S('command', 'move', '$regC', '$t0'), \
+             S('command', 'move', '$regD', '$t0')]
+        eliminate_common_subexpressions(b)
+        self.assertEqual(b.statements, e)
+
+    def test_eliminate_common_subexpressions_assigned(self):
+        b = B([S('command', 'addu', '$regC', '$regA', '$regB'),
+               S('command', 'li', '$regA', '0x00000001'),
+               S('command', 'addu', '$regD', '$regA', '$regB')])
+        e = copy(b.statements)
+        eliminate_common_subexpressions(b)
+        self.assertEqual(b.statements, e)
+
+    def test_fold_constants(self):
         pass
-        
-    def test_copy_propagation_true(self):        
-        print "testing true"
+
+    def test_copy_propagation_true(self):
         block = B([self.foo,
                    S('command', 'move', '$1', '$2'),
                    self.foo,
                    S('command', 'addu', '$3', '$1', '$4'),
                    self.bar])
-                   
-        copy_propagation(block)
+
+        self.assertTrue(copy_propagation(block))
         self.assertEqual(block.statements, [self.foo,
                    S('command', 'move', '$1', '$2'),
                    self.foo,
                    S('command', 'addu', '$3', '$2', '$4'),
                    self.bar])
-        print "Test true succesfull"
-                   
-#    def test_copy_propagation_false(self):
-#        print "Testing false"
-#        arguments = [self.foo,
-#                   S('command', 'move', '$1', '$2'),
-#                   S('command', 'move', '$10', '$20'),
-#                   S('command', 'addu', '$1', '$5', 1),
-#                   S('command', 'addu', '$3', '$1', '$4'),
-#                   self.bar]
-#        block = B(arguments)
-#        copy_propagation(block)
-#        self.assertEqual(block.statements, arguments)
+
+    def test_copy_propagation_overwrite(self):
+        block = B([self.foo, \
+                    S('command', 'move', '$1', '$2'),
+                    S('command', 'move', '$1', '$5'),
+                    S('command', 'addu', '$3', '$1', '$4'),
+                    self.bar])
+
+        self.assertTrue(copy_propagation(block))
+        self.assertEqual(block.statements, [self.foo,
+                   S('command', 'move', '$1', '$2'),
+                   S('command', 'move', '$1', '$5'),
+                   S('command', 'addu', '$3', '$5', '$4'),
+                   self.bar])
+
+    def test_copy_propagation_false(self):
+        arguments = [self.foo,
+                   S('command', 'move', '$1', '$2'),
+                   S('command', 'move', '$10', '$20'),
+                   S('command', 'addu', '$1', '$5', 1),
+                   S('command', 'addu', '$3', '$1', '$4'),
+                   self.bar]
+        block = B(arguments)
+        self.assertFalse(copy_propagation(block))
+        self.assertEqual(block.statements, arguments)
+
+    def test_copy_propagation_false_severalmoves(self):
+        arguments = [self.foo,
+                   S('command', 'move', '$1', '$2'),
+                   self.foo,
+                   S('command', 'addu', '$1', '$5', 1),
+                   S('command', 'addu', '$3', '$1', '$4'),
+                   self.bar]
+        block = B(arguments)
+        self.assertFalse(copy_propagation(block))
+        self.assertEqual(block.statements, arguments)
+
+    def test_algebraic_transforms_add0(self):
+        block = B([self.foo,
+                   S('command', 'addu', '$1', '$2', 0),
+                   self.bar])
+        self.assertTrue(algebraic_transformations(block))
+        self.assertEqual(block.statements, [self.foo,
+                         S('command', 'move', '$1', '$2'),
+                         self.bar])
+
+    def test_algebraic_transforms_add1(self):
+        arguments = [self.foo,
+                   S('command', 'addu', '$1', '$2', 1),
+                   self.bar]
+        block = B(arguments)
+
+        self.assertFalse(algebraic_transformations(block))
+        self.assertEqual(block.statements, arguments)
+
+    def test_algebraic_transforms_sub0(self):
+        block = B([self.foo,
+                   S('command', 'subu', '$1', '$2', 0),
+                   self.bar])
+
+        self.assertTrue(algebraic_transformations(block))
+        self.assertEqual(block.statements, [self.foo,
+                         S('command', 'move', '$1', '$2'),
+                         self.bar])
+
+    def test_algebraic_transforms_sub1(self):
+        arguments = [self.foo,
+                   S('command', 'subu', '$1', '$2', 1),
+                   self.bar]
+        block = B(arguments)
+
+        self.assertFalse(algebraic_transformations(block))
+        self.assertEqual(block.statements, arguments)
+
+    def test_algebraic_transforms_mult0(self):
+        block = B([self.foo,
+                   S('command', 'mult', '$1', '$2', 0),
+                   self.bar])
+
+        self.assertTrue(algebraic_transformations(block))
+        self.assertEqual(block.statements, [self.foo,
+                         S('command', 'li', '$1', '0x00000000'),
+                         self.bar])
+
+    def test_algebraic_transforms_mult1(self):
+        block = B([self.foo,
+                   S('command', 'mult', '$1', '$2', 1),
+                   self.bar])
+
+        self.assertTrue(algebraic_transformations(block))
+        self.assertEqual(block.statements, [self.foo,
+                         S('command', 'move', '$1', '$2'),
+                         self.bar])
+
+    def test_algebraic_transforms_mult2(self):
+        block = B([self.foo,
+                   S('command', 'mult', '$1', '$2', 2),
+                   self.bar])
+
+        self.assertTrue(algebraic_transformations(block))
+        self.assertEqual(block.statements, [self.foo,
+                         S('command', 'sll', '$1', '$2', 1),
+                         self.bar])
+
+    def test_algebraic_transforms_mult16(self):
+        block = B([self.foo,
+                   S('command', 'mult', '$1', '$2', 16),
+                   self.bar])
+
+        self.assertTrue(algebraic_transformations(block))
+        self.assertEqual(block.statements, [self.foo,
+                         S('command', 'sll', '$1', '$2', 4),
+                         self.bar])
+
+    def test_algebraic_transforms_mult3(self):
+        arguments = [self.foo,
+                     S('command', 'mult', '$1', '$2', 3),
+                     self.bar]
+        block = B(arguments)
+
+        self.assertFalse(algebraic_transformations(block))
+        self.assertEqual(block.statements, arguments)