r300/compiler: Add simple unit test framework
authorTom Stellard <tstellar@gmail.com>
Sun, 8 May 2011 22:50:45 +0000 (15:50 -0700)
committerTom Stellard <tstellar@gmail.com>
Wed, 11 May 2011 23:16:29 +0000 (16:16 -0700)
Plus three tests for rc_inst_can_use_presub()

src/mesa/drivers/dri/r300/compiler/Makefile
src/mesa/drivers/dri/r300/compiler/tests/Makefile [new file with mode: 0644]
src/mesa/drivers/dri/r300/compiler/tests/radeon_compiler_util_tests.c [new file with mode: 0644]
src/mesa/drivers/dri/r300/compiler/tests/rc_test_helpers.c [new file with mode: 0644]
src/mesa/drivers/dri/r300/compiler/tests/rc_test_helpers.h [new file with mode: 0644]
src/mesa/drivers/dri/r300/compiler/tests/unit_test.c [new file with mode: 0644]
src/mesa/drivers/dri/r300/compiler/tests/unit_test.h [new file with mode: 0644]

index 5c9f57b..4bedfac 100644 (file)
@@ -74,6 +74,9 @@ tags:
 clean:
        rm -f $(OBJECTS) lib$(LIBNAME).a depend depend.bak
 
+test: default
+       @$(MAKE) -s -C tests/
+
 # Dummy target
 install:
        @echo -n ""
diff --git a/src/mesa/drivers/dri/r300/compiler/tests/Makefile b/src/mesa/drivers/dri/r300/compiler/tests/Makefile
new file mode 100644 (file)
index 0000000..e268543
--- /dev/null
@@ -0,0 +1,55 @@
+# src/mesa/drivers/dri/r300/compiler/Makefile
+
+TOP = ../../../../../../..
+include $(TOP)/configs/current
+
+CFLAGS += -Wall -Werror
+
+### Basic defines ###
+TESTS =        radeon_compiler_util_tests
+
+TEST_SOURCES := $(TESTS:=.c)
+
+SHARED_SOURCES =               \
+       rc_test_helpers.c       \
+       unit_test.c
+
+C_SOURCES = $(SHARED_SOURCES) $(TEST_SOURCES)
+
+INCLUDES = \
+       -I. \
+       -I..
+
+COMPILER_LIB = ../libr300compiler.a
+
+##### TARGETS #####
+
+default: depend run_tests
+
+depend: $(C_SOURCES)
+       rm -f depend
+       touch depend
+       $(MKDEP) $(MKDEP_OPTIONS) $(INCLUDES) $^ 2> /dev/null
+
+# Remove .o and backup files
+clean:
+       rm -f $(TESTS) depend depend.bak
+
+$(TESTS): $(TESTS:=.o) $(SHARED_SOURCES:.c=.o) $(COMPILER_LIB)
+       $(APP_CC) -o $@ $^
+
+run_tests: $(TESTS)
+       @echo "RUNNING TESTS:"
+       @echo ""
+       $(foreach test, $^, @./$(test))
+
+.PHONY: $(COMPILER_LIB)
+$(COMPILER_LIB):
+       $(MAKE) -C ..
+
+##### RULES #####
+.c.o:
+       $(CC) -c $(INCLUDES) $(CFLAGS) $(LIBRARY_DEFINES) $< -o $@
+
+
+sinclude depend
diff --git a/src/mesa/drivers/dri/r300/compiler/tests/radeon_compiler_util_tests.c b/src/mesa/drivers/dri/r300/compiler/tests/radeon_compiler_util_tests.c
new file mode 100644 (file)
index 0000000..be5036b
--- /dev/null
@@ -0,0 +1,76 @@
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include "radeon_compiler_util.h"
+#include "radeon_program.h"
+
+#include "rc_test_helpers.h"
+#include "unit_test.h"
+
+static void test_rc_inst_can_use_presub(
+       struct test_result * result,
+       int expected,
+       const char * add_str,
+       const char * replace_str)
+{
+       struct rc_instruction add_inst, replace_inst;
+       int ret;
+
+       test_begin(result);
+       init_rc_normal_instruction(&add_inst, add_str);
+       init_rc_normal_instruction(&replace_inst, replace_str);
+
+       ret = rc_inst_can_use_presub(&replace_inst, RC_PRESUB_ADD, 0,
+                       replace_inst.U.I.SrcReg[0],
+                       add_inst.U.I.SrcReg[0], add_inst.U.I.SrcReg[1]);
+
+       test_check(result, ret == expected);
+}
+
+static void test_runner_rc_inst_can_use_presub(struct test_result * result)
+{
+
+       /* This tests the case where the source being replace has the same
+        * register file and register index as another source register in the
+        * CMP instruction.  A previous version of this function was ignoring
+        * all registers that shared the same file and index as the replacement
+        * register when counting the number of source selects.
+        *
+        * https://bugs.freedesktop.org/show_bug.cgi?id=36527
+        */
+       test_rc_inst_can_use_presub(result, 0,
+               "ADD temp[0].z, temp[6].__x_, const[1].__x_;",
+               "CMP temp[0].y, temp[0]._z__, const[0]._z__, temp[0]._y__;");
+
+
+       /* Testing a random case that should fail
+        *
+        * https://bugs.freedesktop.org/show_bug.cgi?id=36527
+        */
+       test_rc_inst_can_use_presub(result, 0,
+               "ADD temp[3], temp[1], temp[2];",
+               "MAD temp[1], temp[0], const[0].xxxx, -temp[3];");
+
+       /* This tests the case where the arguments of the ADD
+        * instruction share the same register file and index.  Normally, we
+        * would need only one source select for these two arguments, but since
+        * they will be part of a presubtract operation we need to use the two
+        * source selects that the presubtract instruction expects
+        * (src0 and src1).
+        *
+        * https://bugs.freedesktop.org/show_bug.cgi?id=36527
+        */
+       test_rc_inst_can_use_presub(result, 0,
+               "ADD temp[3].x, temp[0].x___, temp[0].x___;",
+               "MAD temp[0].xyz, temp[2].xyz_, -temp[3].xxx_, input[5].xyz_;");
+}
+
+int main(int argc, char ** argv)
+{
+       struct test tests[] = {
+               {"rc_inst_can_use_presub()", test_runner_rc_inst_can_use_presub},
+               {NULL, NULL}
+       };
+       run_tests(tests);
+}
diff --git a/src/mesa/drivers/dri/r300/compiler/tests/rc_test_helpers.c b/src/mesa/drivers/dri/r300/compiler/tests/rc_test_helpers.c
new file mode 100644 (file)
index 0000000..ca4738a
--- /dev/null
@@ -0,0 +1,380 @@
+#include <errno.h>
+#include <regex.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include "../radeon_compiler_util.h"
+#include "../radeon_opcodes.h"
+#include "../radeon_program.h"
+
+#include "rc_test_helpers.h"
+
+/* This file contains some helper functions for filling out the rc_instruction
+ * data structures.  These functions take a string as input based on the format
+ * output by rc_program_print().
+ */
+
+#define VERBOSE 0
+
+#define DBG(...) do { if (VERBOSE) fprintf(stderr, __VA_ARGS__); } while(0)
+
+#define REGEX_ERR_BUF_SIZE 50
+
+struct match_info {
+       const char * String;
+       int Length;
+};
+
+static int match_length(regmatch_t * matches, int index)
+{
+       return matches[index].rm_eo - matches[index].rm_so;
+}
+
+static int regex_helper(
+       const char * regex_str,
+       const char * search_str,
+       regmatch_t * matches,
+       int num_matches)
+{
+       char err_buf[REGEX_ERR_BUF_SIZE];
+       regex_t regex;
+       int err_code;
+       unsigned int i;
+
+       err_code = regcomp(&regex, regex_str, REG_EXTENDED);
+       if (err_code) {
+               regerror(err_code, &regex, err_buf, REGEX_ERR_BUF_SIZE);
+               fprintf(stderr, "Failed to compile regex: %s\n", err_buf);
+               return 0;
+       }
+
+       err_code = regexec(&regex, search_str, num_matches, matches, 0);
+       DBG("Search string: '%s'\n", search_str);
+       for (i = 0; i < num_matches; i++) {
+               DBG("Match %u start = %d end = %d\n", i,
+                                       matches[i].rm_so, matches[i].rm_eo);
+       }
+       if (err_code) {
+               regerror(err_code, &regex, err_buf, REGEX_ERR_BUF_SIZE);
+               fprintf(stderr, "Failed to match regex: %s\n", err_buf);
+               return 0;
+       }
+       return 1;
+}
+
+#define REGEX_SRC_MATCHES 6
+
+struct src_tokens {
+       struct match_info Negate;
+       struct match_info Abs;
+       struct match_info File;
+       struct match_info Index;
+       struct match_info Swizzle;
+};
+
+/**
+ * Initialize the source register at index src_index for the instruction based
+ * on src_str.
+ *
+ * NOTE: Warning in init_rc_normal_instruction() applies to this function as
+ * well.
+ *
+ * @param src_str A string that represents the source register.  The format for
+ * this string is the same that is output by rc_program_print.
+ * @return 1 On success, 0 on failure
+ */
+int init_rc_normal_src(
+       struct rc_instruction * inst,
+       unsigned int src_index,
+       const char * src_str)
+{
+       const char * regex_str = "(-*)(\\|*)([[:lower:]]*)\\[([[:digit:]])\\](\\.*[[:lower:]-]*)";
+       regmatch_t matches[REGEX_SRC_MATCHES];
+       struct src_tokens tokens;
+       struct rc_src_register * src_reg = &inst->U.I.SrcReg[src_index];
+       unsigned int i;
+
+       /* Execute the regex */
+       if (!regex_helper(regex_str, src_str, matches, REGEX_SRC_MATCHES)) {
+               fprintf(stderr, "Failed to execute regex for src register.\n");
+               return 0;
+       }
+
+       /* Create Tokens */
+       tokens.Negate.String = src_str + matches[1].rm_so;
+       tokens.Negate.Length = match_length(matches, 1);
+       tokens.Abs.String = src_str + matches[2].rm_so;
+       tokens.Abs.Length = match_length(matches, 2);
+       tokens.File.String = src_str + matches[3].rm_so;
+       tokens.File.Length = match_length(matches, 3);
+       tokens.Index.String = src_str + matches[4].rm_so;
+       tokens.Index.Length = match_length(matches, 4);
+       tokens.Swizzle.String = src_str + matches[5].rm_so;
+       tokens.Swizzle.Length = match_length(matches, 5);
+
+       /* Negate */
+       if (tokens.Negate.Length  > 0) {
+               src_reg->Negate = RC_MASK_XYZW;
+       }
+
+       /* Abs */
+       if (tokens.Abs.Length > 0) {
+               src_reg->Abs = 1;
+       }
+
+       /* File */
+       if (!strncmp(tokens.File.String, "temp", tokens.File.Length)) {
+               src_reg->File = RC_FILE_TEMPORARY;
+       } else if (!strncmp(tokens.File.String, "input", tokens.File.Length)) {
+               src_reg->File = RC_FILE_INPUT;
+       } else if (!strncmp(tokens.File.String, "const", tokens.File.Length)) {
+               src_reg->File = RC_FILE_CONSTANT;
+       } else if (!strncmp(tokens.File.String, "none", tokens.File.Length)) {
+               src_reg->File = RC_FILE_NONE;
+       }
+
+       /* Index */
+       errno = 0;
+       src_reg->Index = strtol(tokens.Index.String, NULL, 10);
+       if (errno > 0) {
+               fprintf(stderr, "Could not convert src register index.\n");
+               return 0;
+       }
+
+       /* Swizzle */
+       if (tokens.Swizzle.Length == 0) {
+               src_reg->Swizzle = RC_SWIZZLE_XYZW;
+       } else {
+               int str_index = 1;
+               src_reg->Swizzle = RC_MAKE_SWIZZLE_SMEAR(RC_SWIZZLE_UNUSED);
+               if (tokens.Swizzle.String[0] != '.') {
+                       fprintf(stderr, "First char of swizzle is not valid.\n");
+                       return 0;
+               }
+               for (i = 0; i < 4; i++, str_index++) {
+                       if (tokens.Swizzle.String[str_index] == '-') {
+                               src_reg->Negate |= (1 << i);
+                               str_index++;
+                       }
+                       switch(tokens.Swizzle.String[str_index]) {
+                       case 'x':
+                               SET_SWZ(src_reg->Swizzle, i, RC_SWIZZLE_X);
+                               break;
+                       case 'y':
+                               SET_SWZ(src_reg->Swizzle, i, RC_SWIZZLE_Y);
+                               break;
+                       case 'z':
+                               SET_SWZ(src_reg->Swizzle, i, RC_SWIZZLE_Z);
+                               break;
+                       case 'w':
+                               SET_SWZ(src_reg->Swizzle, i, RC_SWIZZLE_W);
+                               break;
+                       case '1':
+                               SET_SWZ(src_reg->Swizzle, i, RC_SWIZZLE_ONE);
+                               break;
+                       case '0':
+                               SET_SWZ(src_reg->Swizzle, i, RC_SWIZZLE_ZERO);
+                               break;
+                       case 'H':
+                               SET_SWZ(src_reg->Swizzle, i, RC_SWIZZLE_HALF);
+                               break;
+                       case '_':
+                               SET_SWZ(src_reg->Swizzle, i, RC_SWIZZLE_UNUSED);
+                               break;
+                       default:
+                               fprintf(stderr, "Unknown src register swizzle.\n");
+                               return 0;
+                       }
+               }
+       }
+       DBG("File=%u index=%u swizzle=%x negate=%u abs=%u\n",
+                       src_reg->File, src_reg->Index, src_reg->Swizzle,
+                       src_reg->Negate, src_reg->Abs);
+       return 1;
+}
+
+#define REGEX_DST_MATCHES 4
+
+struct dst_tokens {
+       struct match_info File;
+       struct match_info Index;
+       struct match_info WriteMask;
+};
+
+/**
+ * Initialize the destination for the instruction based on dst_str.
+ *
+ * NOTE: Warning in init_rc_normal_instruction() applies to this function as
+ * well.
+ *
+ * @param dst_str A string that represents the destination register.  The format
+ * for this string is the same that is output by rc_program_print.
+ * @return 1 On success, 0 on failure
+ */
+int init_rc_normal_dst(
+       struct rc_instruction * inst,
+       const char * dst_str)
+{
+       const char * regex_str = "([[:lower:]]*)\\[([[:digit:]]*)\\](\\.*[[:lower:]]*)";
+       regmatch_t matches[REGEX_DST_MATCHES];
+       struct dst_tokens tokens;
+       unsigned int i;
+
+       /* Execute the regex */
+       if (!regex_helper(regex_str, dst_str, matches, REGEX_DST_MATCHES)) {
+               fprintf(stderr, "Failed to execute regex for dst register.\n");
+               return 0;
+       }
+
+       /* Create Tokens */
+       tokens.File.String = dst_str + matches[1].rm_so;
+       tokens.File.Length = match_length(matches, 1);
+       tokens.Index.String = dst_str + matches[2].rm_so;
+       tokens.Index.Length = match_length(matches, 2);
+       tokens.WriteMask.String = dst_str + matches[3].rm_so;
+       tokens.WriteMask.Length = match_length(matches, 3);
+
+       /* File Type */
+       if (!strncmp(tokens.File.String, "temp", tokens.File.Length)) {
+               inst->U.I.DstReg.File = RC_FILE_TEMPORARY;
+       } else if (!strncmp(tokens.File.String, "output", tokens.File.Length)) {
+               inst->U.I.DstReg.File = RC_FILE_OUTPUT;
+       } else {
+               fprintf(stderr, "Unknown dst register file type.\n");
+               return 0;
+       }
+
+       /* File Index */
+       errno = 0;
+       inst->U.I.DstReg.Index = strtol(tokens.Index.String, NULL, 10);
+
+       if (errno > 0) {
+               fprintf(stderr, "Could not convert dst register index\n");
+               return 0;
+       }
+
+       /* WriteMask */
+       if (tokens.WriteMask.Length == 0) {
+               inst->U.I.DstReg.WriteMask = RC_MASK_XYZW;
+       } else {
+               /* The first character should be '.' */
+               if (tokens.WriteMask.String[0] != '.') {
+                       fprintf(stderr, "1st char of writemask is not valid.\n");
+                       return 0;
+               }
+               for (i = 1; i < tokens.WriteMask.Length; i++) {
+                       switch(tokens.WriteMask.String[i]) {
+                       case 'x':
+                               inst->U.I.DstReg.WriteMask |= RC_MASK_X;
+                               break;
+                       case 'y':
+                               inst->U.I.DstReg.WriteMask |= RC_MASK_Y;
+                               break;
+                       case 'z':
+                               inst->U.I.DstReg.WriteMask |= RC_MASK_Z;
+                               break;
+                       case 'w':
+                               inst->U.I.DstReg.WriteMask |= RC_MASK_W;
+                               break;
+                       default:
+                               fprintf(stderr, "Unknown swizzle in writemask.\n");
+                               return 0;
+                       }
+               }
+       }
+       DBG("Dst Reg File=%u Index=%d Writemask=%d\n",
+                       inst->U.I.DstReg.File,
+                       inst->U.I.DstReg.Index,
+                       inst->U.I.DstReg.WriteMask);
+       return 1;
+}
+
+#define REGEX_INST_MATCHES 7
+
+struct inst_tokens {
+       struct match_info Opcode;
+       struct match_info Sat;
+       struct match_info Dst;
+       struct match_info Srcs[3];
+};
+
+/**
+ * Initialize a normal instruction based on inst_str.
+ *
+ * WARNING: This function might not be able to handle every kind of format that
+ * rc_program_print() can output.  If you are having problems with a
+ * particular string, you may need to add support for it to this functions.
+ *
+ * @param inst_str A string that represents the source register.  The format for
+ * this string is the same that is output by rc_program_print.
+ * @return 1 On success, 0 on failure
+ */
+int init_rc_normal_instruction(
+       struct rc_instruction * inst,
+       const char * inst_str)
+{
+       const char * regex_str = "([[:upper:]]+)(_SAT)* ([^,]*)[, ]*([^,]*)[, ]*([^,]*)[, ]*([^;]*)";
+       int i;
+       regmatch_t matches[REGEX_INST_MATCHES];
+       struct inst_tokens tokens;
+
+       /* Initialize inst */
+       memset(inst, 0, sizeof(struct rc_instruction));
+       inst->Type = RC_INSTRUCTION_NORMAL;
+
+       /* Execute the regex */
+       if (!regex_helper(regex_str, inst_str, matches, REGEX_INST_MATCHES)) {
+               return 0;
+       }
+       memset(&tokens, 0, sizeof(tokens));
+
+       /* Create Tokens */
+       tokens.Opcode.String = inst_str + matches[1].rm_so;
+       tokens.Opcode.Length = match_length(matches, 1);
+       if (matches[2].rm_so > -1) {
+               tokens.Sat.String = inst_str + matches[2].rm_so;
+               tokens.Sat.Length = match_length(matches, 2);
+       }
+
+
+       /* Fill out the rest of the instruction. */
+       for (i = 0; i < MAX_RC_OPCODE; i++) {
+               const struct rc_opcode_info * info = rc_get_opcode_info(i);
+               unsigned int first_src = 3;
+               unsigned int j;
+               if (strncmp(tokens.Opcode.String, info->Name, tokens.Opcode.Length)) {
+                       continue;
+               }
+               inst->U.I.Opcode = info->Opcode;
+               if (info->HasDstReg) {
+                       char * dst_str;
+                       tokens.Dst.String = inst_str + matches[3].rm_so;
+                       tokens.Dst.Length = match_length(matches, 3);
+                       first_src++;
+
+                       dst_str = malloc(sizeof(char) * (tokens.Dst.Length + 1));
+                       strncpy(dst_str, tokens.Dst.String, tokens.Dst.Length);
+                       dst_str[tokens.Dst.Length] = '\0';
+                       init_rc_normal_dst(inst, dst_str);
+                       free(dst_str);
+               }
+               for (j = 0; j < info->NumSrcRegs; j++) {
+                       char * src_str;
+                       tokens.Srcs[j].String =
+                               inst_str + matches[first_src + j].rm_so;
+                       tokens.Srcs[j].Length =
+                               match_length(matches, first_src + j);
+
+                       src_str = malloc(sizeof(char) *
+                                               (tokens.Srcs[j].Length + 1));
+                       strncpy(src_str, tokens.Srcs[j].String,
+                                               tokens.Srcs[j].Length);
+                       src_str[tokens.Srcs[j].Length] = '\0';
+                       init_rc_normal_src(inst, j, src_str);
+               }
+               break;
+       }
+       return 1;
+}
diff --git a/src/mesa/drivers/dri/r300/compiler/tests/rc_test_helpers.h b/src/mesa/drivers/dri/r300/compiler/tests/rc_test_helpers.h
new file mode 100644 (file)
index 0000000..1a6bf96
--- /dev/null
@@ -0,0 +1,13 @@
+
+int init_rc_normal_src(
+       struct rc_instruction * inst,
+       unsigned int src_index,
+       const char * src_str);
+
+int init_rc_normal_dst(
+       struct rc_instruction * inst,
+       const char * dst_str);
+
+int init_rc_normal_instruction(
+       struct rc_instruction * inst,
+       const char * inst_str);
diff --git a/src/mesa/drivers/dri/r300/compiler/tests/unit_test.c b/src/mesa/drivers/dri/r300/compiler/tests/unit_test.c
new file mode 100644 (file)
index 0000000..266f336
--- /dev/null
@@ -0,0 +1,35 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "unit_test.h"
+
+void run_tests(struct test tests[])
+{
+       int i;
+       for (i = 0; tests[i].name; i++) {
+               printf("Test %s\n", tests[i].name);
+               memset(&tests[i].result, 0, sizeof(tests[i].result));
+               tests[i].test_func(&tests[i].result);
+               printf("Test %s (%d/%d) pass\n", tests[i].name,
+                       tests[i].result.pass, tests[i].result.test_count);
+       }
+}
+
+void test_begin(struct test_result * result)
+{
+       result->test_count++;
+}
+
+void test_check(struct test_result * result, int cond)
+{
+       printf("Subtest %u -> ", result->test_count);
+       if (cond) {
+               result->pass++;
+               printf("Pass");
+       } else {
+               result->fail++;
+               printf("Fail");
+       }
+       printf("\n");
+}
diff --git a/src/mesa/drivers/dri/r300/compiler/tests/unit_test.h b/src/mesa/drivers/dri/r300/compiler/tests/unit_test.h
new file mode 100644 (file)
index 0000000..441e8b6
--- /dev/null
@@ -0,0 +1,17 @@
+
+struct test_result {
+       unsigned int test_count;
+       unsigned int pass;
+       unsigned int fail;
+};
+
+struct test {
+       const char * name;
+       void (*test_func)(struct test_result * result);
+       struct test_result result;
+};
+
+void run_tests(struct test tests[]);
+
+void test_begin(struct test_result * result);
+void test_check(struct test_result * result, int cond);