[Mesa-dev] [PATCH 04/11] glsl: Add a unit test for lower_jumps.cpp

Paul Berry stereotype441 at gmail.com
Tue Jul 5 15:07:25 PDT 2011


This test invokes do_lower_jumps() in isolation (using the glsl_test
executable) and verifies that it transforms the IR in the expected
way.

The unit test may be run from the top level directory using "make
check".
---
 Makefile                         |    6 +-
 src/glsl/testing/lower_jumps.py  |  479 ++++++++++++++++++++++++++++++++++++++
 src/glsl/testing/optimization.py |  263 +++++++++++++++++++++
 src/glsl/testing/run_tests       |    4 +
 4 files changed, 751 insertions(+), 1 deletions(-)
 create mode 100644 src/glsl/testing/lower_jumps.py
 create mode 100644 src/glsl/testing/optimization.py
 create mode 100755 src/glsl/testing/run_tests

diff --git a/Makefile b/Makefile
index 0a3deb8..ac0a394 100644
--- a/Makefile
+++ b/Makefile
@@ -21,6 +21,10 @@ all: default
 doxygen:
 	cd doxygen && $(MAKE)
 
+check:
+	cd src/glsl/testing && ./run_tests
+	@echo "All tests passed."
+
 clean:
 	- at touch $(TOP)/configs/current
 	- at for dir in $(SUBDIRS) ; do \
@@ -51,7 +55,7 @@ install:
 	done
 
 
-.PHONY: default doxygen clean realclean distclean install
+.PHONY: default doxygen clean realclean distclean install check
 
 # If there's no current configuration file
 $(TOP)/configs/current:
diff --git a/src/glsl/testing/lower_jumps.py b/src/glsl/testing/lower_jumps.py
new file mode 100644
index 0000000..6b3eac1
--- /dev/null
+++ b/src/glsl/testing/lower_jumps.py
@@ -0,0 +1,479 @@
+# coding=utf-8
+#
+# Copyright © 2011 Intel Corporation
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice (including the next
+# paragraph) shall be included in all copies or substantial portions of the
+# Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+from optimization import *
+
+def complex_if(var_prefix, statements):
+    """Create a statement of the form
+
+    if (<var_prefix>a > 0.0) {
+       if (<var_prefix>b > 0.0) {
+          <statements>
+       }
+    }
+
+    This is useful in testing jump lowering, because if <statements>
+    ends in a jump, lower_jumps.cpp won't try to combine this
+    construct with the code that follows it, as it might do for a
+    simple if.
+
+    All variables used in the if statement are prefixed with
+    var_prefix.  This can be used to ensure uniqueness.
+    """
+    check_sexp(statements)
+    return simple_if(var_prefix + 'a', simple_if(var_prefix + 'b', statements))
+
+def declare_execute_flag():
+    """Create the statements that lower_jumps.cpp uses to declare and
+    initialize the temporary boolean execute_flag.
+    """
+    return declare_temp('bool', 'execute_flag') + \
+        assign_x('execute_flag', const_bool(True))
+
+def declare_return_flag():
+    """Create the statements that lower_jumps.cpp uses to declare and
+    initialize the temporary boolean return_flag.
+    """
+    return declare_temp('bool', 'return_flag') + \
+        assign_x('return_flag', const_bool(False))
+
+def declare_return_value():
+    """Create the statements that lower_jumps.cpp uses to declare and
+    initialize the temporary variable return_value.  Assume that
+    return_value is a float.
+    """
+    return declare_temp('float', 'return_value')
+
+def declare_break_flag():
+    """Create the statements that lower_jumps.cpp uses to declare and
+    initialize the temporary boolean break_flag.
+    """
+    return declare_temp('bool', 'break_flag') + \
+        assign_x('break_flag', const_bool(False))
+
+def lowered_return_simple(value = None):
+    """Create the statements that lower_jumps.cpp lowers a return
+    statement to, in situations where it does not need to clear the
+    execute flag.
+    """
+    if value:
+        result = assign_x('return_value', value)
+    else:
+        result = []
+    return result + assign_x('return_flag', const_bool(True))
+
+def lowered_return(value = None):
+    """Create the statements that lower_jumps.cpp lowers a return
+    statement to, in situations where it needs to clear the execute
+    flag.
+    """
+    return lowered_return_simple(value) + \
+        assign_x('execute_flag', const_bool(False))
+
+def lowered_continue():
+    """Create the statement that lower_jumps.cpp lowers a continue
+    statement to.
+    """
+    return assign_x('execute_flag', const_bool(False))
+
+def lowered_break_simple():
+    """Create the statement that lower_jumps.cpp lowers a break
+    statement to, in situations where it does not need to clear the
+    execute flag.
+    """
+    return assign_x('break_flag', const_bool(True))
+
+def lowered_break():
+    """Create the statement that lower_jumps.cpp lowers a break
+    statement to, in situations where it needs to clear the execute
+    flag.
+    """
+    return lowered_break_simple() + assign_x('execute_flag', const_bool(False))
+
+def if_execute_flag(statements):
+    """Wrap statements in an if test so that they will only execute if
+    execute_flag is True.
+    """
+    check_sexp(statements)
+    return [['if', ['var_ref', 'execute_flag'], statements, []]]
+
+def if_not_return_flag(statements):
+    """Wrap statements in an if test so that they will only execute if
+    return_flag is False.
+    """
+    check_sexp(statements)
+    return [['if', ['var_ref', 'return_flag'], [], statements]]
+
+def final_return():
+    """Create the return statement that lower_jumps.cpp places at the
+    end of a function when lowering returns.
+    """
+    return [['return', ['var_ref', 'return_value']]]
+
+def final_break():
+    """Create the conditional break statement that lower_jumps.cpp
+    places at the end of a function when lowering breaks.
+    """
+    return [['if', ['var_ref', 'break_flag'], break_(), []]]
+
+class TestLowerJumps(OptimizationTestCase):
+    """Unit tests for lower_jumps.cpp."""
+
+    def test_lower_returns_main(self):
+        """Test that do_lower_jumps respects the lower_main_return
+        flag in deciding whether to lower returns in the main
+        function.
+        """
+        input_sexp = make_test_case('main', 'void', (
+                complex_if('', return_())
+                ))
+        expected_sexp = make_test_case('main', 'void', (
+                declare_execute_flag() +
+                declare_return_flag() +
+                complex_if('', lowered_return())
+                ))
+        self.check_lower_jumps(input_sexp, expected_sexp,
+                               lower_main_return=True)
+        self.check_lower_jumps(input_sexp, input_sexp,
+                               lower_main_return=False)
+
+    def test_lower_returns_sub(self):
+        """Test that do_lower_jumps respects the lower_sub_return flag
+        in deciding whether to lower returns in subroutines.
+        """
+        input_sexp = make_test_case('sub', 'void', (
+                complex_if('', return_())
+                ))
+        expected_sexp = make_test_case('sub', 'void', (
+                declare_execute_flag() +
+                declare_return_flag() +
+                complex_if('', lowered_return())
+                ))
+        self.check_lower_jumps(input_sexp, expected_sexp,
+                               lower_sub_return=True)
+        self.check_lower_jumps(input_sexp, input_sexp,
+                               lower_sub_return=False)
+
+    def test_lower_returns_1(self):
+        """Test that a void return at the end of a function is
+        eliminated.
+        """
+        input_sexp = make_test_case('main', 'void', (
+                assign_x('a', const_float(1)) +
+                return_()
+                ))
+        expected_sexp = make_test_case('main', 'void', (
+                assign_x('a', const_float(1))
+                ))
+        self.check_lower_jumps(input_sexp, expected_sexp,
+                               lower_main_return=True)
+
+    def test_lower_returns_2(self):
+        """Test that lowering is not performed on a non-void return at
+        the end of subroutine.
+        """
+        input_sexp = make_test_case('sub', 'float', (
+                assign_x('a', const_float(1)) +
+                return_(const_float(1))
+                ))
+        self.check_lower_jumps(input_sexp, input_sexp,
+                               lower_sub_return=True)
+
+    def test_lower_returns_3(self):
+        """Test lowering of returns when there is one nested inside a
+        complex structure of ifs, and one at the end of a function.
+
+        In this case, the latter return needs to be lowered because it
+        will not be at the end of the function once the final return
+        is inserted.
+        """
+        input_sexp = make_test_case('sub', 'float', (
+                complex_if('', return_(const_float(1))) +
+                return_(const_float(2))
+                ))
+        expected_sexp = make_test_case('sub', 'float', (
+                declare_execute_flag() +
+                declare_return_value() +
+                declare_return_flag() +
+                complex_if('', lowered_return(const_float(1))) +
+                if_execute_flag(lowered_return(const_float(2))) +
+                final_return()
+                ))
+        self.check_lower_jumps(input_sexp, expected_sexp,
+                               lower_sub_return=True)
+
+    def test_lower_returns_4(self):
+        """Test that returns are properly lowered when they occur in
+        both branches of an if-statement.
+        """
+        input_sexp = make_test_case('sub', 'float', (
+                simple_if('a', return_(const_float(1)),
+                          return_(const_float(2)))
+                ))
+        expected_sexp = make_test_case('sub', 'float', (
+                declare_execute_flag() +
+                declare_return_value() +
+                declare_return_flag() +
+                simple_if('a', lowered_return(const_float(1)),
+                          lowered_return(const_float(2))) +
+                final_return()
+                ))
+        self.check_lower_jumps(input_sexp, expected_sexp,
+                               lower_sub_return=True)
+
+    def test_lower_unified_returns(self):
+        """If both branches of an if statement end in a return, and
+        pull_out_jumps is True, then those returns should be lifted
+        outside the if and then properly lowered.
+
+        Verify that this lowering occurs during the same pass as the
+        lowering of other returns by checking that extra temporary
+        variables aren't generated.
+        """
+        input_sexp = make_test_case('main', 'void', (
+                complex_if('a', return_()) +
+                simple_if('b', simple_if('c', return_(), return_()))
+                ))
+        expected_sexp = make_test_case('main', 'void', (
+                declare_execute_flag() +
+                declare_return_flag() +
+                complex_if('a', lowered_return()) +
+                if_execute_flag(simple_if('b', (simple_if('c', [], []) +
+                                                lowered_return())))
+                ))
+        self.check_lower_jumps(input_sexp, expected_sexp,
+                               lower_main_return=True, pull_out_jumps=True)
+
+    def test_lower_pulled_out_jump(self):
+        """If one branch of an if ends in a jump, and control cannot
+        fall out the bottom of the other branch, and pull_out_jumps is
+        True, then the jump is lifted outside the if.
+
+        Verify that this lowering occurs during the same pass as the
+        lowering of other jumps by checking that extra temporary
+        variables aren't generated.
+        """
+        input_sexp = make_test_case('main', 'void', (
+                complex_if('a', return_()) +
+                loop(simple_if('b', simple_if('c', break_(), continue_()),
+                               return_())) +
+                assign_x('d', const_float(1))
+                ))
+        # Note: optimization produces two other effects: the break
+        # gets lifted out of the if statements, and the code after the
+        # loop gets guarded so that it only executes if the return
+        # flag is clear.
+        expected_sexp = make_test_case('main', 'void', (
+                declare_execute_flag() +
+                declare_return_flag() +
+                complex_if('a', lowered_return()) +
+                if_execute_flag(
+                    loop(simple_if('b', simple_if('c', [], continue_()),
+                                   lowered_return_simple()) +
+                         break_()) +
+                    if_not_return_flag(assign_x('d', const_float(1))))
+                ))
+        self.check_lower_jumps(input_sexp, expected_sexp,
+                               lower_main_return=True, pull_out_jumps=True)
+
+    def test_lower_breaks_1(self):
+        """If a loop contains an unconditional break at the bottom of
+        it, it should not be lowered."""
+        input_sexp = make_test_case('main', 'void', (
+                loop(assign_x('a', const_float(1)) +
+                     break_())
+                ))
+        expected_sexp = input_sexp
+        self.check_lower_jumps(input_sexp, expected_sexp, lower_break=True)
+
+    def test_lower_breaks_2(self):
+        """If a loop contains a conditional break at the bottom of it,
+        it should not be lowered if it is in the then-clause.
+        """
+        input_sexp = make_test_case('main', 'void', (
+                loop(assign_x('a', const_float(1)) +
+                     simple_if('b', break_()))
+                ))
+        expected_sexp = input_sexp
+        self.check_lower_jumps(input_sexp, expected_sexp, lower_break=True)
+
+    def test_lower_breaks_3(self):
+        """If a loop contains a conditional break at the bottom of it,
+        it should not be lowered if it is in the then-clause, even if
+        there are statements preceding the break.
+        """
+        input_sexp = make_test_case('main', 'void', (
+                loop(assign_x('a', const_float(1)) +
+                     simple_if('b', (assign_x('c', const_float(1)) +
+                                     break_())))
+                ))
+        expected_sexp = input_sexp
+        self.check_lower_jumps(input_sexp, expected_sexp, lower_break=True)
+
+    def test_lower_breaks_4(self):
+        """If a loop contains a conditional break at the bottom of it,
+        it should not be lowered if it is in the else-clause.
+        """
+        input_sexp = make_test_case('main', 'void', (
+                loop(assign_x('a', const_float(1)) +
+                     simple_if('b', [], break_()))
+                ))
+        expected_sexp = input_sexp
+        self.check_lower_jumps(input_sexp, expected_sexp, lower_break=True)
+
+    def test_lower_breaks_5(self):
+        """If a loop contains a conditional break at the bottom of it,
+        it should not be lowered if it is in the else-clause, even if
+        there are statements preceding the break.
+        """
+        input_sexp = make_test_case('main', 'void', (
+                loop(assign_x('a', const_float(1)) +
+                     simple_if('b', [], (assign_x('c', const_float(1)) +
+                                         break_())))
+                ))
+        expected_sexp = input_sexp
+        self.check_lower_jumps(input_sexp, expected_sexp, lower_break=True)
+
+    def test_lower_breaks_6(self):
+        """If a loop contains conditional breaks and continues, and
+        ends in an unconditional break, then the unconditional break
+        needs to be lowered, because it will no longer be at the end
+        of the loop after the final break is added.
+        """
+        input_sexp = make_test_case('main', 'void', (
+                loop(simple_if('a', (complex_if('b', continue_()) +
+                                     complex_if('c', break_()))) +
+                     break_())
+                ))
+        expected_sexp = make_test_case('main', 'void', (
+                declare_break_flag() +
+                loop(declare_execute_flag() +
+                     simple_if(
+                        'a',
+                        (complex_if('b', lowered_continue()) +
+                         if_execute_flag(
+                                complex_if('c', lowered_break())))) +
+                     if_execute_flag(lowered_break_simple()) +
+                     final_break())
+                ))
+        self.check_lower_jumps(input_sexp, expected_sexp,
+                               lower_break=True, lower_continue=True)
+
+    def test_lower_guarded_conditional_break(self):
+        """Normally a conditional break at the end of a loop isn't
+        lowered, however if the conditional break gets placed inside
+        an if(execute_flag) because of earlier lowering of continues,
+        then the break needs to be lowered.
+        """
+        input_sexp = make_test_case('main', 'void', (
+                loop(complex_if('a', continue_()) +
+                     simple_if('b', break_()))
+                ))
+        expected_sexp = make_test_case('main', 'void', (
+                declare_break_flag() +
+                loop(declare_execute_flag() +
+                     complex_if('a', lowered_continue()) +
+                     if_execute_flag(simple_if('b', lowered_break())) +
+                     final_break())
+                ))
+        self.check_lower_jumps(input_sexp, expected_sexp,
+                               lower_break=True, lower_continue=True)
+
+    def test_remove_continue_at_end_of_loop(self):
+        """Test that a redundant continue-statement at the end of a
+        loop is removed.
+        """
+        input_sexp = make_test_case('main', 'void', (
+                loop(assign_x('a', const_float(1)) +
+                     continue_())
+                ))
+        expected_sexp = make_test_case('main', 'void', (
+                loop(assign_x('a', const_float(1)))
+                ))
+        self.check_lower_jumps(input_sexp, expected_sexp)
+
+    def test_lower_return_void_at_end_of_loop(self):
+        """Test that a return of void at the end of a loop is properly
+        lowered.
+        """
+        input_sexp = make_test_case('main', 'void', (
+                loop(assign_x('a', const_float(1)) +
+                     return_()) +
+                assign_x('b', const_float(2))
+                ))
+        expected_sexp = make_test_case('main', 'void', (
+                declare_return_flag() +
+                loop(assign_x('a', const_float(1)) +
+                     lowered_return_simple() +
+                     break_()) +
+                if_not_return_flag(assign_x('b', const_float(2)))
+                ))
+        self.check_lower_jumps(input_sexp, input_sexp)
+        self.check_lower_jumps(input_sexp, expected_sexp,
+                               lower_main_return=True)
+        self.check_lower_jumps(input_sexp, expected_sexp,
+                               lower_main_return=True, lower_break=True)
+
+    def test_lower_return_non_void_at_end_of_loop(self):
+        """Test that a non-void return at the end of a loop is
+        properly lowered.
+        """
+        input_sexp = make_test_case('sub', 'float', (
+                loop(assign_x('a', const_float(1)) +
+                     return_(const_float(2))) +
+                assign_x('b', const_float(3)) +
+                return_(const_float(4))
+                ))
+        expected_sexp = make_test_case('sub', 'float', (
+                declare_execute_flag() +
+                declare_return_value() +
+                declare_return_flag() +
+                loop(assign_x('a', const_float(1)) +
+                     lowered_return_simple(const_float(2)) +
+                     break_()) +
+                if_not_return_flag(assign_x('b', const_float(3)) +
+                                   lowered_return(const_float(4))) +
+                final_return()
+                ))
+        self.check_lower_jumps(input_sexp, input_sexp)
+        self.check_lower_jumps(input_sexp, expected_sexp,
+                               lower_sub_return=True)
+        self.check_lower_jumps(input_sexp, expected_sexp,
+                               lower_sub_return=True, lower_break=True)
+
+    def check_lower_jumps(self, input_sexp, expected_sexp,
+                          pull_out_jumps=False, lower_sub_return=False,
+                          lower_main_return=False, lower_continue=False,
+                          lower_break=False):
+        """Verify that do_lower_jumps transforms the given code in the
+        expected way.
+        """
+
+        optimization_name = (
+            'do_lower_jumps({0:d}, {1:d}, {2:d}, {3:d}, {4:d})'.format(
+                pull_out_jumps, lower_sub_return, lower_main_return,
+                lower_continue, lower_break))
+        self.check_optimization(input_sexp, expected_sexp, optimization_name)
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/src/glsl/testing/optimization.py b/src/glsl/testing/optimization.py
new file mode 100644
index 0000000..88d0675
--- /dev/null
+++ b/src/glsl/testing/optimization.py
@@ -0,0 +1,263 @@
+# coding=utf-8
+#
+# Copyright © 2011 Intel Corporation
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice (including the next
+# paragraph) shall be included in all copies or substantial portions of the
+# Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+import re
+import subprocess
+import unittest
+
+# Convention: we represent a sexp in Python using nested lists
+# containing strings.  So, for example, the sexp (constant float
+# (1.000000)) is represented as ['constant', 'float', ['1.000000']].
+
+def check_sexp(sexp):
+    """Verify that the argument is a proper sexp.
+
+    That is, raise an exception if the argument is not a string or a
+    list, or if it contains anything that is not a string or a list at
+    any nesting level.
+    """
+    if isinstance(sexp, list):
+        for s in sexp:
+            check_sexp(s)
+    elif not isinstance(sexp, basestring):
+        raise Exception('Not a sexp: {0!r}'.format(sexp))
+
+def parse_sexp(sexp):
+    """Convert a string, of the form that would be output by mesa,
+    into a sexp represented as nested lists containing strings.
+    """
+    sexp_token_regexp = re.compile(
+        '[a-zA-Z_]+(@[0-9]+)?|[0-9]+(\\.[0-9]+)?|[^ \n]')
+    stack = [[]]
+    for match in sexp_token_regexp.finditer(sexp):
+        token = match.group(0)
+        if token == '(':
+            stack.append([])
+        elif token == ')':
+            if len(stack) == 1:
+                raise Exception('Unmatched )')
+            sexp = stack.pop()
+            stack[-1].append(sexp)
+        else:
+            stack[-1].append(token)
+    if len(stack) != 1:
+        raise Exception('Unmatched (')
+    if len(stack[0]) != 1:
+        raise Exception('Multiple sexps')
+    return stack[0][0]
+
+def sexp_to_string(sexp):
+    """Convert a sexp, represented as nested lists containing strings,
+    into a single string of the form parseable by mesa.
+    """
+    if isinstance(sexp, basestring):
+        return sexp
+    assert isinstance(sexp, list)
+    result = ''
+    for s in sexp:
+        sub_result = sexp_to_string(s)
+        if result == '':
+            result = sub_result
+        elif '\n' not in result and '\n' not in sub_result and \
+                len(result) + len(sub_result) + 1 <= 70:
+            result += ' ' + sub_result
+        else:
+            result += '\n' + sub_result
+    return '({0})'.format(result.replace('\n', '\n '))
+
+def separate_decls(sexp):
+    """Separate all toplevel variable declarations from sexp, and
+    place them in a dictionary whose key is the name of the variable.
+
+    Raises an exception if a given variable appears more than once.
+
+    This is used to work around the fact that
+    ir_reader::read_instructions reorders declarations.
+    """
+    assert isinstance(sexp, list)
+    decls = {}
+    code = []
+    for s in sexp:
+        if isinstance(s, list) and len(s) >= 4 and s[0] == 'declare':
+            assert s[3] not in decls
+            decls[s[3]] = s
+        else:
+            code.append(s)
+    return decls, code
+
+def ir_same(x, y):
+    """Check that two sexps represent the same code, ignoring the
+    order of declarations.
+    """
+    return separate_decls(x) == separate_decls(y)
+
+def make_test_case(f_name, ret_type, body):
+    """Create a simple optimization test case consisting of a single
+    function with the given name, return type, and body.
+
+    Global declarations are automatically created for any undeclared
+    variables that are referenced by the function.  All undeclared
+    variables are assumed to be floats.
+    """
+    check_sexp(body)
+    declarations = {}
+    def make_declarations(sexp, already_declared = ()):
+        if isinstance(sexp, list):
+            if len(sexp) == 2 and sexp[0] == 'var_ref':
+                if sexp[1] not in already_declared:
+                    declarations[sexp[1]] = [
+                        'declare', ['in'], 'float', sexp[1]]
+            elif len(sexp) == 4 and sexp[0] == 'assign':
+                assert sexp[2][0] == 'var_ref'
+                if sexp[2][1] not in already_declared:
+                    declarations[sexp[2][1]] = [
+                        'declare', ['out'], 'float', sexp[2][1]]
+                make_declarations(sexp[3], already_declared)
+            else:
+                already_declared = set(already_declared)
+                for s in sexp:
+                    if isinstance(s, list) and len(s) >= 4 and \
+                            s[0] == 'declare':
+                        already_declared.add(s[3])
+                    else:
+                        make_declarations(s, already_declared)
+    make_declarations(body)
+    return declarations.values() + \
+        [['function', f_name, ['signature', ret_type, ['parameters'], body]]]
+
+
+# The following functions can be used to build expressions.
+
+def const_float(value):
+    """Create an expression representing the given floating point value."""
+    return ['constant', 'float', ['{0:.6f}'.format(value)]]
+
+def const_bool(value):
+    """Create an expression representing the given boolean value.
+
+    If value is not a boolean, it is converted to a boolean.  So, for
+    instance, const_bool(1) is equivalent to const_bool(True).
+    """
+    return ['constant', 'bool', ['{0}'.format(1 if value else 0)]]
+
+def gt_zero(var_name):
+    """Create Construct the expression var_name > 0"""
+    return ['expression', 'bool', '>', ['var_ref', var_name], const_float(0)]
+
+
+# The following functions can be used to build complex control flow
+# statements.  All of these functions return statement lists (even
+# those which only create a single statement), so that statements can
+# be sequenced together using the '+' operator.
+
+def return_(value = None):
+    """Create a return statement."""
+    if value is not None:
+        return [['return', value]]
+    else:
+        return [['return']]
+
+def break_():
+    """Create a break statement."""
+    return ['break']
+
+def continue_():
+    """Create a continue statement."""
+    return ['continue']
+
+def simple_if(var_name, then_statements, else_statements = None):
+    """Create a statement of the form
+
+    if (var_name > 0.0) {
+       <then_statements>
+    } else {
+       <else_statements>
+    }
+
+    else_statements may be omitted.
+    """
+    if else_statements is None:
+        else_statements = []
+    check_sexp(then_statements)
+    check_sexp(else_statements)
+    return [['if', gt_zero(var_name), then_statements, else_statements]]
+
+def loop(statements):
+    """Create a loop containing the given statements as its loop
+    body.
+    """
+    check_sexp(statements)
+    return [['loop', [], [], [], [], statements]]
+
+def declare_temp(var_type, var_name):
+    """Create a declaration of the form
+
+    (declare (temporary) <var_type> <var_name)
+    """
+    return [['declare', ['temporary'], var_type, var_name]]
+
+def assign_x(var_name, value):
+    """Create a statement that assigns <value> to the variable
+    <var_name>.  The assignment uses the mask (x).
+    """
+    check_sexp(value)
+    return [['assign', ['x'], ['var_ref', var_name], value]]
+
+class OptimizationTestCase(unittest.TestCase):
+    """Base class for a unit test of an optimization pass."""
+
+    def check_optimization(self, input_sexp, expected_sexp, optimization):
+        """Verify that the given optimization pass transforms
+        input_sexp into expected_sexp.
+        """
+        resulting_ir_start_regexp = re.compile('\\*\\*\\* resulting IR:\n')
+        embedded_string_end_regexp = re.compile('\n--\n')
+
+        check_sexp(input_sexp)
+        check_sexp(expected_sexp)
+        input_str = sexp_to_string(input_sexp)
+        expected_output = sexp_to_string(expected_sexp)
+
+        args = ['../glsl_test', 'optpass', '--input-ir', optimization]
+        proc = subprocess.Popen(
+            args, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+            stderr=subprocess.STDOUT)
+        stdoutdata, stderrdata = proc.communicate(input_str)
+        if proc.returncode != 0:
+            raise Exception(
+                'glsl_test returned code {0}.  Input:\n{1}\nOutput: {2}'
+                .format(proc.returncode, input_str, stdoutdata))
+        match = resulting_ir_start_regexp.search(stdoutdata)
+        if match is None:
+            raise Exception('resulting IR not found in glsl_test output')
+        output_start = match.end()
+        match = embedded_string_end_regexp.search(stdoutdata, output_start)
+        if match is None:
+            raise Exception('resulting IR unterminated in glsl_test output')
+        output_end = match.start()
+        actual_output = stdoutdata[output_start:output_end]
+        actual_sexp = parse_sexp(actual_output)
+        if not ir_same(expected_sexp, actual_sexp):
+            self.fail('Args: {0}\nInput:\n{1}\nExpected:\n{2}\nActual:\n{3}'
+                      .format(args, input_str, expected_output,
+                              sexp_to_string(actual_sexp)))
diff --git a/src/glsl/testing/run_tests b/src/glsl/testing/run_tests
new file mode 100755
index 0000000..e94ee94
--- /dev/null
+++ b/src/glsl/testing/run_tests
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+set -e
+python lower_jumps.py
-- 
1.7.5.4



More information about the mesa-dev mailing list