Mesa (master): aco: add framework for unit testing

GitLab Mirror gitlab-mirror at kemper.freedesktop.org
Thu Jul 30 16:28:49 UTC 2020


Module: Mesa
Branch: master
Commit: e6366f9094326a2841058678174289827f504905
URL:    http://cgit.freedesktop.org/mesa/mesa/commit/?id=e6366f9094326a2841058678174289827f504905

Author: Rhys Perry <pendingchaos02 at gmail.com>
Date:   Wed Jan 22 19:58:27 2020 +0000

aco: add framework for unit testing

And add some "tests" to test and document currently unused features of the
framework.

Signed-off-by: Rhys Perry <pendingchaos02 at gmail.com>
Acked-by: Samuel Pitoiset <samuel.pitoiset at gmail.com>
Acked-by: Daniel Schürmann <daniel at schuermann.dev>
Acked-by: Timur Kristóf <timur.kristof at gmail.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/3521>

---

 meson.build                                |   4 +
 meson_options.txt                          |   8 +-
 src/amd/compiler/aco_assembler.cpp         |   4 +-
 src/amd/compiler/aco_builder_h.py          |   2 +-
 src/amd/compiler/aco_lower_to_hw_instr.cpp |   2 +-
 src/amd/compiler/aco_opcodes.py            |   1 +
 src/amd/compiler/tests/README.md           |  19 ++
 src/amd/compiler/tests/check_output.py     | 512 +++++++++++++++++++++++++++++
 src/amd/compiler/tests/framework.h         |  70 ++++
 src/amd/compiler/tests/helpers.cpp         | 170 ++++++++++
 src/amd/compiler/tests/helpers.h           |  50 +++
 src/amd/compiler/tests/main.cpp            | 290 ++++++++++++++++
 src/amd/compiler/tests/meson.build         |  49 +++
 src/amd/compiler/tests/test_tests.cpp      |  78 +++++
 src/amd/meson.build                        |   3 +
 15 files changed, 1258 insertions(+), 4 deletions(-)

diff --git a/meson.build b/meson.build
index 5d7b3112304..fc6eecaeaa9 100644
--- a/meson.build
+++ b/meson.build
@@ -51,6 +51,7 @@ pre_args = [
 
 with_vulkan_icd_dir = get_option('vulkan-icd-dir')
 with_tests = get_option('build-tests')
+with_aco_tests = get_option('build-aco-tests')
 with_glx_read_only_text = get_option('glx-read-only-text')
 with_glx_direct = get_option('glx-direct')
 with_osmesa = get_option('osmesa')
@@ -275,6 +276,9 @@ endif
 if with_gallium_tegra and not with_gallium_nouveau
   error('tegra driver requires nouveau driver')
 endif
+if with_aco_tests and not with_amd_vk
+  error('ACO tests require Radv')
+endif
 
 if host_machine.system() == 'darwin'
   with_dri_platform = 'apple'
diff --git a/meson_options.txt b/meson_options.txt
index 913dd497449..63290eba415 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -286,7 +286,13 @@ option(
   'build-tests',
   type : 'boolean',
   value : false,
-  description : 'Build unit tests. Currently this will build *all* unit tests, which may build more than expected.'
+  description : 'Build unit tests. Currently this will build *all* unit tests except the ACO tests, which may build more than expected.'
+)
+option(
+  'build-aco-tests',
+  type : 'boolean',
+  value : false,
+  description : 'Build ACO tests. These do not require an AMD GPU.'
 )
 option(
   'install-intel-gpu-tests',
diff --git a/src/amd/compiler/aco_assembler.cpp b/src/amd/compiler/aco_assembler.cpp
index 8d77951feff..3972429a383 100644
--- a/src/amd/compiler/aco_assembler.cpp
+++ b/src/amd/compiler/aco_assembler.cpp
@@ -527,7 +527,9 @@ void emit_instruction(asm_context& ctx, std::vector<uint32_t>& out, Instruction*
    }
    case Format::PSEUDO:
    case Format::PSEUDO_BARRIER:
-      unreachable("Pseudo instructions should be lowered before assembly.");
+      if (instr->opcode != aco_opcode::p_unit_test)
+         unreachable("Pseudo instructions should be lowered before assembly.");
+      break;
    default:
       if ((uint16_t) instr->format & (uint16_t) Format::VOP3A) {
          VOP3A_instruction* vop3 = static_cast<VOP3A_instruction*>(instr);
diff --git a/src/amd/compiler/aco_builder_h.py b/src/amd/compiler/aco_builder_h.py
index b3adb14dc8e..fb28181a9b4 100644
--- a/src/amd/compiler/aco_builder_h.py
+++ b/src/amd/compiler/aco_builder_h.py
@@ -178,7 +178,7 @@ public:
    bool is_precise = false;
    bool is_nuw = false;
 
-   Builder(Program *pgm) : program(pgm), use_iterator(false), start(false), lm(pgm->lane_mask), instructions(NULL) {}
+   Builder(Program *pgm) : program(pgm), use_iterator(false), start(false), lm(pgm ? pgm->lane_mask : s2), instructions(NULL) {}
    Builder(Program *pgm, Block *block) : program(pgm), use_iterator(false), start(false), lm(pgm ? pgm->lane_mask : s2), instructions(&block->instructions) {}
    Builder(Program *pgm, std::vector<aco_ptr<Instruction>> *instrs) : program(pgm), use_iterator(false), start(false), lm(pgm ? pgm->lane_mask : s2), instructions(instrs) {}
 
diff --git a/src/amd/compiler/aco_lower_to_hw_instr.cpp b/src/amd/compiler/aco_lower_to_hw_instr.cpp
index c7e4acd542e..e8c72485772 100644
--- a/src/amd/compiler/aco_lower_to_hw_instr.cpp
+++ b/src/amd/compiler/aco_lower_to_hw_instr.cpp
@@ -1684,7 +1684,7 @@ void lower_to_hw_instr(Program* program)
       for (size_t j = 0; j < block->instructions.size(); j++) {
          aco_ptr<Instruction>& instr = block->instructions[j];
          aco_ptr<Instruction> mov;
-         if (instr->format == Format::PSEUDO) {
+         if (instr->format == Format::PSEUDO && instr->opcode != aco_opcode::p_unit_test) {
             Pseudo_instruction *pi = (Pseudo_instruction*)instr.get();
 
             switch (instr->opcode)
diff --git a/src/amd/compiler/aco_opcodes.py b/src/amd/compiler/aco_opcodes.py
index 019ad9c0e0d..1b61d2df2d7 100644
--- a/src/amd/compiler/aco_opcodes.py
+++ b/src/amd/compiler/aco_opcodes.py
@@ -246,6 +246,7 @@ opcode("p_startpgm")
 opcode("p_phi")
 opcode("p_linear_phi")
 opcode("p_as_uniform")
+opcode("p_unit_test")
 
 opcode("p_create_vector")
 opcode("p_extract_vector")
diff --git a/src/amd/compiler/tests/README.md b/src/amd/compiler/tests/README.md
new file mode 100644
index 00000000000..ab5186a6eeb
--- /dev/null
+++ b/src/amd/compiler/tests/README.md
@@ -0,0 +1,19 @@
+Tests are wrapped in a `BEGIN_TEST`/`END_TEST` and write data to the `output` file pointer. Tests have checks against the output. They are single line comments prefixed with certain characters:
+
+- `!` fails the test if the current line does not match the pattern
+- `>>` skips to the first line which matches the pattern, or fails the test if there is none
+- `;` executes python code to extend the pattern syntax by inserting functions into the variable dictionary, fail the test, insert more checks or consume characters from the output
+
+Before this prefix, there can be a `~` to only perform the check for certain
+variants (a regex directly following the `~` is used).
+
+# Pattern Syntax
+Patterns can define variables which can be accessed in both python code and the pattern itself. These are useful for readability or dealing with unstable identifiers in the output. Variable identifiers are sequences of digits, ascii letters or `_` (though they cannot start with a digit).
+
+- `\` can be used to match the following literal character without interpreting it.
+- Most characters expect the same characters in the output.
+- A sequence of spaces in the pattern expects a sequence of spaces or tabs in the output.
+- A `#` in the pattern expects an unsigned integer in the output. The `#` can be followed by an identifier to store the integer in a variable.
+- A `$` in the pattern stores the output until the first whitespace character into a variable.
+- A `%` in the pattern followed by an identifier is the same as a `#` but it expects a `%` before the integer in the output. It basically matches a ACO temporary.
+- A `@` calls a variable as a function. It can be followed by an argument string wrapped in `(` and `)`.
diff --git a/src/amd/compiler/tests/check_output.py b/src/amd/compiler/tests/check_output.py
new file mode 100644
index 00000000000..e54bae656ec
--- /dev/null
+++ b/src/amd/compiler/tests/check_output.py
@@ -0,0 +1,512 @@
+#
+# Copyright (c) 2020 Valve 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 sys
+import os.path
+import struct
+import string
+import copy
+from math import floor
+
+if os.isatty(sys.stdout.fileno()):
+    set_red = "\033[31m"
+    set_green = "\033[1;32m"
+    set_normal = "\033[0m"
+else:
+    set_red = ''
+    set_green = ''
+    set_normal = ''
+
+initial_code = '''
+def insert_code(code):
+    insert_queue.append(CodeCheck(code))
+
+def insert_pattern(pattern):
+    insert_queue.append(PatternCheck(pattern))
+
+def vector_gpr(prefix, name, size, align):
+    insert_code(f'{name} = {name}0')
+    for i in range(size):
+        insert_code(f'{name}{i} = {name}0 + {i}')
+    insert_code(f'success = {name}0 + {size - 1} == {name}{size - 1}')
+    insert_code(f'success = {name}0 % {align} == 0')
+    return f'{prefix}[#{name}0:#{name}{size - 1}]'
+
+def sgpr_vector(name, size, align):
+    return vector_gpr('s', name, size, align)
+
+funcs.update({
+    's64': lambda name: vector_gpr('s', name, 2, 2),
+    's96': lambda name: vector_gpr('s', name, 3, 2),
+    's128': lambda name: vector_gpr('s', name, 4, 4),
+    's256': lambda name: vector_gpr('s', name, 8, 4),
+    's512': lambda name: vector_gpr('s', name, 16, 4),
+})
+for i in range(2, 14):
+    funcs['v%d' % (i * 32)] = lambda name: vector_gpr('v', name, i, 1)
+'''
+
+class Check:
+    def __init__(self, data):
+        self.data = data.rstrip()
+
+    def run(self, state):
+        pass
+
+class CodeCheck(Check):
+    def run(self, state):
+        indent = 0
+        first_line = [l for l in self.data.split('\n') if l.strip() != ''][0]
+        indent_amount = len(first_line) - len(first_line.lstrip())
+        indent = first_line[:indent_amount]
+        new_lines = []
+        for line in self.data.split('\n'):
+            if line.strip() == '':
+                new_lines.append('')
+                continue
+            if line[:indent_amount] != indent:
+                state.result.log += 'unexpected indent in code check:\n'
+                state.result.log += self.data + '\n'
+                return False
+            new_lines.append(line[indent_amount:])
+        code = '\n'.join(new_lines)
+
+        try:
+            exec(code, state.g)
+            state.result.log += state.g['log']
+            state.g['log'] = ''
+        except BaseException as e:
+            state.result.log += 'code check raised exception:\n'
+            state.result.log += code + '\n'
+            state.result.log += str(e)
+            return False
+        if not state.g['success']:
+            state.result.log += 'code check failed:\n'
+            state.result.log += code + '\n'
+            return False
+        return True
+
+class StringStream:
+    class Pos:
+        def __init__(self):
+            self.line = 1
+            self.column = 1
+
+    def __init__(self, data, name):
+        self.name = name
+        self.data = data
+        self.offset = 0
+        self.pos = StringStream.Pos()
+
+    def reset(self):
+        self.offset = 0
+        self.pos = StringStream.Pos()
+
+    def peek(self, num=1):
+        return self.data[self.offset:self.offset+num]
+
+    def peek_test(self, chars):
+        c = self.peek(1)
+        return c != '' and c in chars
+
+    def read(self, num=4294967296):
+        res = self.peek(num)
+        self.offset += len(res)
+        for c in res:
+            if c == '\n':
+                self.pos.line += 1
+                self.pos.column = 1
+            else:
+                self.pos.column += 1
+        return res
+
+    def get_line(self, num):
+        return self.data.split('\n')[num - 1].rstrip()
+
+    def skip_line(self):
+        while self.peek(1) not in ['\n', '']:
+            self.read(1)
+        self.read(1)
+
+    def skip_whitespace(self, inc_line):
+        chars = [' ', '\t'] + (['\n'] if inc_line else [])
+        while self.peek(1) in chars:
+            self.read(1)
+
+    def get_number(self):
+        num = ''
+        while self.peek() in string.digits:
+            num += self.read(1)
+        return num
+
+    def check_identifier(self):
+        return self.peek_test(string.ascii_letters + '_')
+
+    def get_identifier(self):
+        res = ''
+        if self.check_identifier():
+            while self.peek_test(string.ascii_letters + string.digits + '_'):
+                res += self.read(1)
+        return res
+
+def format_error_lines(at, line_num, column_num, ctx, line):
+    pred = '%s line %d, column %d of %s: "' % (at, line_num, column_num, ctx)
+    return [pred + line + '"',
+            '-' * (column_num - 1 + len(pred)) + '^']
+
+class MatchResult:
+    def __init__(self, pattern):
+        self.success = True
+        self.func_res = None
+        self.pattern = pattern
+        self.pattern_pos = StringStream.Pos()
+        self.output_pos = StringStream.Pos()
+        self.fail_message = ''
+
+    def set_pos(self, pattern, output):
+        self.pattern_pos.line = pattern.pos.line
+        self.pattern_pos.column = pattern.pos.column
+        self.output_pos.line = output.pos.line
+        self.output_pos.column = output.pos.column
+
+    def fail(self, msg):
+        self.success = False
+        self.fail_message = msg
+
+    def format_pattern_pos(self):
+        pat_pos = self.pattern_pos
+        pat_line = self.pattern.get_line(pat_pos.line)
+        res = format_error_lines('at', pat_pos.line, pat_pos.column, 'pattern', pat_line)
+        func_res = self.func_res
+        while func_res:
+            pat_pos = func_res.pattern_pos
+            pat_line = func_res.pattern.get_line(pat_pos.line)
+            res += format_error_lines('in', pat_pos.line, pat_pos.column, func_res.pattern.name, pat_line)
+            func_res = func_res.func_res
+        return '\n'.join(res)
+
+def do_match(g, pattern, output, skip_lines, in_func=False):
+    assert(not in_func or not skip_lines)
+
+    if not in_func:
+        output.skip_whitespace(False)
+    pattern.skip_whitespace(False)
+
+    old_g = copy.copy(g)
+    old_g_keys = list(g.keys())
+    res = MatchResult(pattern)
+    escape = False
+    while True:
+        res.set_pos(pattern, output)
+
+        c = pattern.read(1)
+        fail = False
+        if c == '':
+            break
+        elif output.peek() == '':
+            res.fail('unexpected end of output')
+        elif c == '\\':
+            escape = True
+            continue
+        elif c == '\n':
+            old_line = output.pos.line
+            output.skip_whitespace(True)
+            if output.pos.line == old_line:
+                res.fail('expected newline in output')
+        elif not escape and c == '#':
+            num = output.get_number()
+            if num == '':
+                res.fail('expected number in output')
+            elif pattern.check_identifier():
+                name = pattern.get_identifier()
+                if name in g and int(num) != g[name]:
+                    res.fail('unexpected number for \'%s\': %d (expected %d)' % (name, int(num), g[name]))
+                elif name != '_':
+                    g[name] = int(num)
+        elif not escape and c == '$':
+            name = pattern.get_identifier()
+
+            val = ''
+            while not output.peek_test(string.whitespace):
+                val += output.read(1)
+
+            if name in g and val != g[name]:
+                res.fail('unexpected value for \'%s\': \'%s\' (expected \'%s\')' % (name, val, g[name]))
+            elif name != '_':
+                g[name] = val
+        elif not escape and c == '%' and pattern.check_identifier():
+            if output.read(1) != '%':
+                res.fail('expected \'%\' in output')
+            else:
+                num = output.get_number()
+                if num == '':
+                    res.fail('expected number in output')
+                else:
+                    name = pattern.get_identifier()
+                    if name in g and int(num) != g[name]:
+                        res.fail('unexpected number for \'%s\': %d (expected %d)' % (name, int(num), g[name]))
+                    elif name != '_':
+                        g[name] = int(num)
+        elif not escape and c == '@' and pattern.check_identifier():
+            name = pattern.get_identifier()
+            args = ''
+            if pattern.peek_test('('):
+                pattern.read(1)
+                while pattern.peek() not in ['', ')']:
+                    args += pattern.read(1)
+                assert(pattern.read(1) == ')')
+            func_res = g['funcs'][name](args)
+            match_res = do_match(g, StringStream(func_res, 'expansion of "%s(%s)"' % (name, args)), output, False, True)
+            if not match_res.success:
+                res.func_res = match_res
+                res.output_pos = match_res.output_pos
+                res.fail(match_res.fail_message)
+        elif not escape and c == ' ':
+            while pattern.peek_test(' '):
+                pattern.read(1)
+
+            read_whitespace = False
+            while output.peek_test(' \t'):
+                output.read(1)
+                read_whitespace = True
+            if not read_whitespace:
+                res.fail('expected whitespace in output, got %r' % (output.peek(1)))
+        else:
+            outc = output.peek(1)
+            if outc != c:
+                res.fail('expected %r in output, got %r' % (c, outc))
+            else:
+                output.read(1)
+        if not res.success:
+            if skip_lines and output.peek() != '':
+                g.clear()
+                g.update(old_g)
+                res.success = True
+                output.skip_line()
+                pattern.reset()
+                output.skip_whitespace(False)
+                pattern.skip_whitespace(False)
+            else:
+                return res
+
+        escape = False
+
+    if not in_func:
+        while output.peek() in [' ', '\t']:
+            output.read(1)
+
+        if output.read(1) not in ['', '\n']:
+            res.fail('expected end of output')
+            return res
+
+    return res
+
+class PatternCheck(Check):
+    def __init__(self, data, search, position):
+        Check.__init__(self, data)
+        self.search = search
+        self.position = position
+
+    def run(self, state):
+        pattern_stream = StringStream(self.data.rstrip(), 'pattern')
+        res = do_match(state.g, pattern_stream, state.g['output'], self.search)
+        if not res.success:
+            state.result.log += 'pattern at %s failed: %s\n' % (self.position, res.fail_message)
+            state.result.log += res.format_pattern_pos() + '\n\n'
+            if not self.search:
+                out_line = state.g['output'].get_line(res.output_pos.line)
+                state.result.log += '\n'.join(format_error_lines('at', res.output_pos.line, res.output_pos.column, 'output', out_line))
+            else:
+                state.result.log += 'output was:\n'
+                state.result.log += state.g['output'].data.rstrip() + '\n'
+            return False
+        return True
+
+class CheckState:
+    def __init__(self, result, variant, checks, output):
+        self.result = result
+        self.variant = variant
+        self.checks = checks
+
+        self.checks.insert(0, CodeCheck(initial_code))
+        self.insert_queue = []
+
+        self.g = {'success': True, 'funcs': {}, 'insert_queue': self.insert_queue,
+                  'variant': variant, 'log': '', 'output': StringStream(output, 'output'),
+                  'CodeCheck': CodeCheck, 'PatternCheck': PatternCheck}
+
+class TestResult:
+    def __init__(self, expected):
+        self.result = ''
+        self.expected = expected
+        self.log = ''
+
+def check_output(result, variant, checks, output):
+    state = CheckState(result, variant, checks, output)
+
+    while len(state.checks):
+        check = state.checks.pop(0)
+        if not check.run(state):
+            result.result = 'failed'
+            return
+
+        for check in state.insert_queue[::-1]:
+            state.checks.insert(0, check)
+        state.insert_queue.clear()
+
+    result.result = 'passed'
+    return
+
+def parse_check(variant, line, checks, pos):
+    if line.startswith(';'):
+        line = line[1:]
+        if len(checks) and isinstance(checks[-1], CodeCheck):
+            checks[-1].data += '\n' + line
+        else:
+            checks.append(CodeCheck(line))
+    elif line.startswith('!'):
+        checks.append(PatternCheck(line[1:], False, pos))
+    elif line.startswith('>>'):
+        checks.append(PatternCheck(line[2:], True, pos))
+    elif line.startswith('~'):
+        end = len(line)
+        start = len(line)
+        for c in [';', '!', '>>']:
+            if line.find(c) != -1 and line.find(c) < end:
+                end = line.find(c)
+        if end != len(line):
+            match = re.match(line[1:end], variant)
+            if match and match.end() == len(variant):
+                parse_check(variant, line[end:], checks, pos)
+
+def parse_test_source(test_name, variant, fname):
+    in_test = False
+    test = []
+    expected_result = 'passed'
+    line_num = 1
+    for line in open(fname, 'r').readlines():
+        if line.startswith('BEGIN_TEST(%s)' % test_name):
+            in_test = True
+        elif line.startswith('BEGIN_TEST_TODO(%s)' % test_name):
+            in_test = True
+            expected_result = 'todo'
+        elif line.startswith('BEGIN_TEST_FAIL(%s)' % test_name):
+            in_test = True
+            expected_result = 'failed'
+        elif line.startswith('END_TEST'):
+            in_test = False
+        elif in_test:
+            test.append((line_num, line.strip()))
+        line_num += 1
+
+    checks = []
+    for line_num, check in [(line_num, l[2:]) for line_num, l in test if l.startswith('//')]:
+         parse_check(variant, check, checks, 'line %d of %s' % (line_num, os.path.split(fname)[1]))
+
+    return checks, expected_result
+
+def parse_and_check_test(test_name, variant, test_file, output, current_result):
+    checks, expected = parse_test_source(test_name, variant, test_file)
+
+    result = TestResult(expected)
+    if len(checks) == 0:
+        result.result = 'empty'
+        result.log = 'no checks found'
+    elif current_result != None:
+        result.result, result.log = current_result
+    else:
+        check_output(result, variant, checks, output)
+        if result.result == 'failed' and expected == 'todo':
+            result.result = 'todo'
+
+    return result
+
+def print_results(results, output, expected):
+    results = {name: result for name, result in results.items() if result.result == output}
+    results = {name: result for name, result in results.items() if (result.result == result.expected) == expected}
+
+    if not results:
+        return 0
+
+    print('%s tests (%s):' % (output, 'expected' if expected else 'unexpected'))
+    for test, result in results.items():
+        color = '' if expected else set_red
+        print('   %s%s%s' % (color, test, set_normal))
+        if result.log.strip() != '':
+            for line in result.log.rstrip().split('\n'):
+                print('      ' + line.rstrip())
+    print('')
+
+    return len(results)
+
+def get_cstr(fp):
+    res = b''
+    while True:
+        c = fp.read(1)
+        if c == b'\x00':
+            return res.decode('utf-8')
+        else:
+            res += c
+
+if __name__ == "__main__":
+   results = {}
+
+   stdin = sys.stdin.buffer
+   while True:
+       packet_type = stdin.read(4)
+       if packet_type == b'':
+           break;
+
+       test_name = get_cstr(stdin)
+       test_variant = get_cstr(stdin)
+       if test_variant != '':
+           full_name = test_name + '/' + test_variant
+       else:
+           full_name = test_name
+
+       test_source_file = get_cstr(stdin)
+       current_result = None
+       if ord(stdin.read(1)):
+           current_result = (get_cstr(stdin), get_cstr(stdin))
+       code_size = struct.unpack("=L", stdin.read(4))[0]
+       code = stdin.read(code_size).decode('utf-8')
+
+       results[full_name] = parse_and_check_test(test_name, test_variant, test_source_file, code, current_result)
+
+   result_types = ['passed', 'failed', 'todo', 'empty']
+   num_expected = 0
+   num_unexpected = 0
+   for t in result_types:
+       num_expected += print_results(results, t, True)
+   for t in result_types:
+       num_unexpected += print_results(results, t, False)
+   num_expected_skipped = print_results(results, 'skipped', True)
+   num_unexpected_skipped = print_results(results, 'skipped', False)
+
+   num_unskipped = len(results) - num_expected_skipped - num_unexpected_skipped
+   color = set_red if num_unexpected else set_green
+   print('%s%d (%.0f%%) of %d unskipped tests had an expected result%s' % (color, num_expected, floor(num_expected / num_unskipped * 100), num_unskipped, set_normal))
+   if num_unexpected_skipped:
+       print('%s%d tests had been unexpectedly skipped%s' % (set_red, num_unexpected_skipped, set_normal))
+
+   if num_unexpected:
+       sys.exit(1)
diff --git a/src/amd/compiler/tests/framework.h b/src/amd/compiler/tests/framework.h
new file mode 100644
index 00000000000..217029afda3
--- /dev/null
+++ b/src/amd/compiler/tests/framework.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright © 2020 Valve 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.
+ *
+ */
+#ifndef ACO_TEST_COMMON_H
+#define ACO_TEST_COMMON_H
+#include <map>
+#include <string>
+#include <stdio.h>
+
+#include "amd_family.h"
+#include "aco_ir.h"
+#include "aco_builder.h"
+#include "vulkan/radv_shader.h"
+
+struct TestDef {
+   const char *name;
+   const char *source_file;
+   void (*func)();
+};
+
+extern std::map<std::string, TestDef> tests;
+extern FILE *output;
+
+bool set_variant(const char *name);
+
+inline bool set_variant(chip_class cls, const char *rest="")
+{
+   char buf[8+strlen(rest)];
+   snprintf(buf, sizeof(buf), "gfx%d%s", cls - GFX6 + 6, rest);
+   return set_variant(buf);
+}
+
+void fail_test(const char *fmt, ...);
+void skip_test(const char *fmt, ...);
+
+#define _PASTE(a, b) a##b
+#define PASTE(a, b) _PASTE(a, b)
+
+#define _BEGIN_TEST(name, struct_name) static void struct_name(); static __attribute__((constructor)) void PASTE(add_test_, __COUNTER__)() {\
+      tests[#name] = (TestDef){#name, ACO_TEST_BUILD_ROOT "/" __FILE__, &struct_name};\
+   }\
+   static void struct_name() {\
+
+#define BEGIN_TEST(name) _BEGIN_TEST(name, PASTE(Test_, __COUNTER__))
+#define BEGIN_TEST_TODO(name) _BEGIN_TEST(name, PASTE(Test_, __COUNTER__))
+#define BEGIN_TEST_FAIL(name) _BEGIN_TEST(name, PASTE(Test_, __COUNTER__))
+#define END_TEST \
+   }
+
+#endif /* ACO_TEST_COMMON_H */
diff --git a/src/amd/compiler/tests/helpers.cpp b/src/amd/compiler/tests/helpers.cpp
new file mode 100644
index 00000000000..f2b7ec786ab
--- /dev/null
+++ b/src/amd/compiler/tests/helpers.cpp
@@ -0,0 +1,170 @@
+/*
+ * Copyright © 2020 Valve 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.
+ *
+ */
+#include "helpers.h"
+#include <stdio.h>
+#include <sstream>
+#include <llvm-c/Target.h>
+
+using namespace aco;
+
+ac_shader_config config;
+radv_shader_info info;
+std::unique_ptr<Program> program;
+Builder bld(NULL);
+Temp inputs[16];
+Temp exec_input;
+const char *subvariant = "";
+
+void create_program(enum chip_class chip_class, Stage stage, unsigned wave_size, enum radeon_family family)
+{
+   memset(&config, 0, sizeof(config));
+   info.wave_size = wave_size;
+
+   program.reset(new Program);
+   aco::init_program(program.get(), stage, &info, chip_class, family, &config);
+
+   Block *block = program->create_and_insert_block();
+   block->kind = block_kind_top_level;
+
+   bld = Builder(program.get(), &program->blocks[0]);
+
+   config.float_mode = program->blocks[0].fp_mode.val;
+}
+
+bool setup_cs(const char *input_spec, enum chip_class chip_class,
+              enum radeon_family family, unsigned wave_size)
+{
+   const char *old_subvariant = subvariant;
+   subvariant = "";
+   if (!set_variant(chip_class, old_subvariant))
+      return false;
+
+   memset(&info, 0, sizeof(info));
+   info.cs.block_size[0] = 1;
+   info.cs.block_size[1] = 1;
+   info.cs.block_size[2] = 1;
+
+   create_program(chip_class, compute_cs, wave_size, family);
+
+   if (input_spec) {
+      unsigned num_inputs = DIV_ROUND_UP(strlen(input_spec), 3u);
+      aco_ptr<Instruction> startpgm{create_instruction<Pseudo_instruction>(aco_opcode::p_startpgm, Format::PSEUDO, 0, num_inputs + 1)};
+      for (unsigned i = 0; i < num_inputs; i++) {
+         RegClass cls(input_spec[i * 3] == 'v' ? RegType::vgpr : RegType::sgpr, input_spec[i * 3 + 1] - '0');
+         inputs[i] = bld.tmp(cls);
+         startpgm->definitions[i] = Definition(inputs[i]);
+      }
+      exec_input = bld.tmp(program->lane_mask);
+      startpgm->definitions[num_inputs] = bld.exec(Definition(exec_input));
+      bld.insert(std::move(startpgm));
+   }
+
+   return true;
+}
+
+void finish_program(Program *program)
+{
+   for (Block& BB : program->blocks) {
+      for (unsigned idx : BB.linear_preds)
+         program->blocks[idx].linear_succs.emplace_back(BB.index);
+      for (unsigned idx : BB.logical_preds)
+         program->blocks[idx].logical_succs.emplace_back(BB.index);
+   }
+
+   for (Block& block : program->blocks) {
+      if (block.linear_succs.size() == 0) {
+         block.kind |= block_kind_uniform;
+         Builder bld(program, &block);
+         if (program->wb_smem_l1_on_end)
+            bld.smem(aco_opcode::s_dcache_wb, false);
+         bld.sopp(aco_opcode::s_endpgm);
+      }
+   }
+}
+
+void finish_validator_test()
+{
+   finish_program(program.get());
+   aco_print_program(program.get(), output);
+   fprintf(output, "Validation results:\n");
+   if (aco::validate(program.get(), output))
+      fprintf(output, "Validation passed\n");
+   else
+      fprintf(output, "Validation failed\n");
+}
+
+void finish_opt_test()
+{
+   finish_program(program.get());
+   if (!aco::validate(program.get(), output)) {
+      fail_test("Validation before optimization failed");
+      return;
+   }
+   aco::optimize(program.get());
+   if (!aco::validate(program.get(), output)) {
+      fail_test("Validation after optimization failed");
+      return;
+   }
+   aco_print_program(program.get(), output);
+}
+
+void finish_to_hw_instr_test()
+{
+   finish_program(program.get());
+   aco::lower_to_hw_instr(program.get());
+   aco_print_program(program.get(), output);
+}
+
+void finish_assembler_test()
+{
+   finish_program(program.get());
+   std::vector<uint32_t> binary;
+   unsigned exec_size = emit_program(program.get(), binary);
+
+   /* we could use CLRX for disassembly but that would require it to be
+    * installed */
+   if (program->chip_class == GFX10_3 && LLVM_VERSION_MAJOR < 9) {
+      skip_test("LLVM 11 needed for GFX10_3 disassembly");
+   } else if (program->chip_class == GFX10 && LLVM_VERSION_MAJOR < 9) {
+      skip_test("LLVM 9 needed for GFX10 disassembly");
+   } else if (program->chip_class >= GFX8) {
+      std::ostringstream ss;
+      print_asm(program.get(), binary, exec_size / 4u, ss);
+
+      fputs(ss.str().c_str(), output);
+   } else {
+      //TODO: maybe we should use CLRX and skip this test if it's not available?
+      for (uint32_t dword : binary)
+         fprintf(output, "%.8x\n", dword);
+   }
+}
+
+void writeout(unsigned i, Temp tmp)
+{
+   if (tmp.id())
+      bld.pseudo(aco_opcode::p_unit_test, Operand(i), tmp);
+   else
+      bld.pseudo(aco_opcode::p_unit_test, Operand(i));
+}
+
diff --git a/src/amd/compiler/tests/helpers.h b/src/amd/compiler/tests/helpers.h
new file mode 100644
index 00000000000..50f50497951
--- /dev/null
+++ b/src/amd/compiler/tests/helpers.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright © 2020 Valve 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.
+ *
+ */
+#ifndef ACO_TEST_HELPERS_H
+#define ACO_TEST_HELPERS_H
+
+#include "framework.h"
+
+extern ac_shader_config config;
+extern radv_shader_info info;
+extern std::unique_ptr<aco::Program> program;
+extern aco::Builder bld;
+extern aco::Temp exec_input;
+extern aco::Temp inputs[16];
+extern const char *subvariant;
+
+void create_program(enum chip_class chip_class, aco::Stage stage,
+                    unsigned wave_size=64, enum radeon_family family=CHIP_UNKNOWN);
+bool setup_cs(const char *input_spec, enum chip_class chip_class,
+              enum radeon_family family=CHIP_UNKNOWN, unsigned wave_size=64);
+
+void finish_program(aco::Program *program);
+void finish_validator_test();
+void finish_opt_test();
+void finish_to_hw_instr_test();
+void finish_assembler_test();
+
+void writeout(unsigned i, aco::Temp tmp=aco::Temp(0, aco::s1));
+
+#endif /* ACO_TEST_HELPERS_H */
diff --git a/src/amd/compiler/tests/main.cpp b/src/amd/compiler/tests/main.cpp
new file mode 100644
index 00000000000..cb646e2dd30
--- /dev/null
+++ b/src/amd/compiler/tests/main.cpp
@@ -0,0 +1,290 @@
+/*
+ * Copyright © 2020 Valve 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.
+ *
+ */
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+#include <stdio.h>
+#include <string.h>
+#include <getopt.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <llvm-c/Target.h>
+#include "aco_ir.h"
+#include "framework.h"
+
+static const char *help_message =
+   "Usage: %s [-h] [-l --list] [--no-check] [TEST [TEST ...]]\n"
+   "\n"
+   "Run ACO unit test(s). If TEST is not provided, all tests are run.\n"
+   "\n"
+   "positional arguments:\n"
+   "  TEST        Run TEST. If TEST ends with a '.', run tests with names\n"
+   "              starting with TEST. The test variant (after the '/') can\n"
+   "              be omitted to run all variants\n"
+   "\n"
+   "optional arguments:\n"
+   "  -h, --help  Show this help message and exit.\n"
+   "  -l --list   List unit tests.\n"
+   "  --no-check  Print test output instead of checking it.\n";
+
+std::map<std::string, TestDef> tests;
+FILE *output = NULL;
+
+static TestDef current_test;
+static unsigned tests_written = 0;
+static FILE *checker_stdin = NULL;
+static char *checker_stdin_data = NULL;
+static size_t checker_stdin_size = 0;
+
+static char *output_data = NULL;
+static size_t output_size = 0;
+static size_t output_offset = 0;
+
+static char current_variant[64] = {0};
+static std::set<std::string> *variant_filter = NULL;
+
+bool test_failed = false;
+bool test_skipped = false;
+static char fail_message[256] = {0};
+
+void write_test()
+{
+   if (!checker_stdin) {
+      /* not entirely correct, but shouldn't matter */
+      tests_written++;
+      return;
+   }
+
+   fflush(output);
+   if (output_offset == output_size && !test_skipped && !test_failed)
+      return;
+
+   char *data = output_data + output_offset;
+   uint32_t size = output_size - output_offset;
+
+   fwrite("test", 1, 4, checker_stdin);
+   fwrite(current_test.name, 1, strlen(current_test.name)+1, checker_stdin);
+   fwrite(current_variant, 1, strlen(current_variant)+1, checker_stdin);
+   fwrite(current_test.source_file, 1, strlen(current_test.source_file)+1, checker_stdin);
+   if (test_failed || test_skipped) {
+      const char *res = test_failed ? "failed" : "skipped";
+      fwrite("\x01", 1, 1, checker_stdin);
+      fwrite(res, 1, strlen(res)+1, checker_stdin);
+      fwrite(fail_message, 1, strlen(fail_message)+1, checker_stdin);
+   } else {
+      fwrite("\x00", 1, 1, checker_stdin);
+   }
+   fwrite(&size, 4, 1, checker_stdin);
+   fwrite(data, 1, size, checker_stdin);
+
+   tests_written++;
+   output_offset += size;
+}
+
+bool set_variant(const char *name)
+{
+   if (variant_filter && !variant_filter->count(name))
+      return false;
+
+   write_test();
+   test_failed = false;
+   test_skipped = false;
+   strncpy(current_variant, name, sizeof(current_variant) - 1);
+
+   printf("Running '%s/%s'\n", current_test.name, name);
+
+   return true;
+}
+
+void fail_test(const char *fmt, ...)
+{
+   va_list args;
+   va_start(args, fmt);
+
+   test_failed = true;
+   vsnprintf(fail_message, sizeof(fail_message), fmt, args);
+
+   va_end(args);
+}
+
+void skip_test(const char *fmt, ...)
+{
+   va_list args;
+   va_start(args, fmt);
+
+   test_skipped = true;
+   vsnprintf(fail_message, sizeof(fail_message), fmt, args);
+
+   va_end(args);
+}
+
+void run_test(TestDef def)
+{
+   current_test = def;
+   output_data = NULL;
+   output_size = 0;
+   output_offset = 0;
+   test_failed = false;
+   test_skipped = false;
+   memset(current_variant, 0, sizeof(current_variant));
+
+   if (checker_stdin)
+      output = open_memstream(&output_data, &output_size);
+   else
+      output = stdout;
+
+   current_test.func();
+   write_test();
+
+   if (checker_stdin)
+      fclose(output);
+   free(output_data);
+}
+
+int check_output(char **argv)
+{
+   fflush(stdout);
+   fflush(stderr);
+
+   fclose(checker_stdin);
+
+   int stdin_pipe[2];
+   pipe(stdin_pipe);
+
+   write(stdin_pipe[1], checker_stdin_data, checker_stdin_size);
+   close(stdin_pipe[1]);
+   dup2(stdin_pipe[0], STDIN_FILENO);
+
+   execlp(ACO_TEST_PYTHON_BIN, ACO_TEST_PYTHON_BIN, ACO_TEST_SOURCE_DIR "/check_output.py", NULL);
+
+   fprintf(stderr, "%s: execl() failed: %s\n", argv[0], strerror(errno));
+   return 99;
+}
+
+bool match_test(std::string name, std::string pattern)
+{
+   if (name.length() < pattern.length())
+      return false;
+   if (pattern.back() == '.')
+      name.resize(pattern.length());
+   return name == pattern;
+}
+
+int main(int argc, char **argv)
+{
+   int print_help = 0;
+   int do_list = 0;
+   int do_check = 1;
+   const struct option opts[] = {
+      { "help",     no_argument, &print_help, 1 },
+      { "list",     no_argument, &do_list,    1 },
+      { "no-check", no_argument, &do_check,   0 },
+      { NULL,       0,           NULL,        0 }
+   };
+
+   int c;
+   while ((c = getopt_long(argc, argv, "hl", opts, NULL)) != -1) {
+      switch (c) {
+      case 'h':
+         print_help = 1;
+         break;
+      case 'l':
+         do_list = 1;
+         break;
+      case 0:
+         break;
+      case '?':
+      default:
+         fprintf(stderr, "%s: Invalid argument\n", argv[0]);
+         return 99;
+      }
+   }
+
+   if (print_help) {
+      fprintf(stderr, help_message, argv[0]);
+      return 99;
+   }
+
+   if (do_list) {
+      for (auto test : tests)
+         printf("%s\n", test.first.c_str());
+      return 99;
+   }
+
+   std::vector<std::pair<std::string, std::string>> names;
+   for (int i = optind; i < argc; i++) {
+      std::string name = argv[i];
+      std::string variant;
+      size_t pos = name.find('/');
+      if (pos != std::string::npos) {
+         variant = name.substr(pos + 1);
+         name = name.substr(0, pos);
+      }
+      names.emplace_back(std::pair<std::string, std::string>(name, variant));
+   }
+
+   if (do_check)
+      checker_stdin = open_memstream(&checker_stdin_data, &checker_stdin_size);
+
+	LLVMInitializeAMDGPUTargetInfo();
+	LLVMInitializeAMDGPUTarget();
+	LLVMInitializeAMDGPUTargetMC();
+	LLVMInitializeAMDGPUDisassembler();
+
+   aco::init();
+
+   for (auto pair : tests) {
+      bool found = names.empty();
+      bool all_variants = names.empty();
+      std::set<std::string> variants;
+      for (const std::pair<std::string, std::string>& name : names) {
+         if (match_test(pair.first, name.first)) {
+            found = true;
+            if (name.second.empty())
+               all_variants = true;
+            else
+               variants.insert(name.second);
+         }
+      }
+
+      if (found) {
+         variant_filter = all_variants ? NULL : &variants;
+         printf("Running '%s'\n", pair.first.c_str());
+         run_test(pair.second);
+      }
+   }
+   if (!tests_written) {
+      fprintf(stderr, "%s: No matching tests\n", argv[0]);
+      return 99;
+   }
+
+   if (checker_stdin) {
+      printf("\n");
+      return check_output(argv);
+   } else {
+      printf("Tests ran\n");
+      return 99;
+   }
+}
diff --git a/src/amd/compiler/tests/meson.build b/src/amd/compiler/tests/meson.build
new file mode 100644
index 00000000000..13c7455d1b8
--- /dev/null
+++ b/src/amd/compiler/tests/meson.build
@@ -0,0 +1,49 @@
+# Copyright © 2020 Valve 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 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.
+aco_tests_files = files(
+  'framework.h',
+  'helpers.cpp',
+  'helpers.h',
+  'main.cpp',
+  'test_tests.cpp',
+)
+
+test(
+  'aco_tests',
+  executable(
+    'aco_tests',
+    aco_tests_files,
+    cpp_args : ['-DACO_TEST_SOURCE_DIR="@0@"'.format(meson.current_source_dir()),
+                '-DACO_TEST_BUILD_ROOT="@0@"'.format(meson.build_root()),
+                '-DACO_TEST_PYTHON_BIN="@0@"'.format(prog_python.path())],
+    include_directories : [
+      inc_include, inc_src, inc_gallium, inc_compiler, inc_mesa, inc_mapi, inc_amd, inc_amd_common, inc_amd_common_llvm,
+    ],
+    link_with : [
+      libamd_common, libamd_common_llvm
+    ],
+    dependencies : [
+      dep_llvm, dep_thread, idep_aco, idep_nir, idep_mesautil
+    ],
+    gnu_symbol_visibility : 'hidden',
+    build_by_default : true,
+  ),
+  suite : ['amd', 'compiler'],
+)
diff --git a/src/amd/compiler/tests/test_tests.cpp b/src/amd/compiler/tests/test_tests.cpp
new file mode 100644
index 00000000000..88b33ab8440
--- /dev/null
+++ b/src/amd/compiler/tests/test_tests.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright © 2020 Valve 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.
+ *
+ */
+#include "helpers.h"
+#include <stdio.h>
+
+using namespace aco;
+
+BEGIN_TEST_TODO(todo)
+   //!test!
+   fprintf(output, "woops!\n");
+END_TEST
+
+BEGIN_TEST_FAIL(expect_fail)
+   //!test!
+   fprintf(output, "woops!\n");
+END_TEST
+
+BEGIN_TEST(simple.1)
+   //! s_buffer_load_dwordx2 @s64(a)
+   fprintf(output, "s_buffer_load_dwordx2 s[6:7]\n");
+   //! s_add_u32 s#b0, s#a, 1
+   //! s_addc_u32 s#b1, s#a1, 0
+   //; success = int(b0) == 8
+   fprintf(output, "s_add_u32 s8, s6, 1\n");
+   fprintf(output, "s_addc_u32 s9, s7, 0\n");
+   //! s_buffer_store_dwordx2 @s64(b)
+   fprintf(output, "s_buffer_store_dwordx2 s[8:9]\n");
+END_TEST
+
+BEGIN_TEST(simple.2)
+   //~gfx[67]! test gfx67
+   //~gfx8! test gfx8
+   //~gfx9! test gfx9
+   //! test all
+   for (int cls = GFX6; cls <= GFX7; cls++) {
+      if (!set_variant((enum chip_class)cls))
+         continue;
+      fprintf(output, "test gfx67\n");
+      fprintf(output, "test all\n");
+   }
+
+   if (set_variant("gfx8")) {
+      fprintf(output, "test gfx8\n");
+      fprintf(output, "test all\n");
+   }
+
+   if (set_variant("gfx9")) {
+      fprintf(output, "test gfx9\n");
+      fprintf(output, "test all\n");
+   }
+END_TEST
+
+BEGIN_TEST(simple.3)
+   //; funcs['test'] = lambda a: a
+   //! @test(s_buffer_load_dwordx2) @s64(a)
+   fprintf(output, "s_buffer_load_dwordx2 s[6:7]\n");
+END_TEST
diff --git a/src/amd/meson.build b/src/amd/meson.build
index 10b04c00333..4a27c8c3e4a 100644
--- a/src/amd/meson.build
+++ b/src/amd/meson.build
@@ -26,4 +26,7 @@ subdir('llvm')
 if with_amd_vk
   subdir('compiler')
   subdir('vulkan')
+  if with_aco_tests
+    subdir('compiler/tests')
+  endif
 endif



More information about the mesa-commit mailing list