Mesa (master): r300/compiler: Add simple unit test framework

Tom Stellard tstellar at kemper.freedesktop.org
Wed May 11 23:54:02 UTC 2011


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

Author: Tom Stellard <tstellar at gmail.com>
Date:   Sun May  8 15:50:45 2011 -0700

r300/compiler: Add simple unit test framework

Plus three tests for rc_inst_can_use_presub()

---

 src/mesa/drivers/dri/r300/compiler/Makefile        |    3 +
 src/mesa/drivers/dri/r300/compiler/tests/Makefile  |   55 +++
 .../compiler/tests/radeon_compiler_util_tests.c    |   76 ++++
 .../dri/r300/compiler/tests/rc_test_helpers.c      |  380 ++++++++++++++++++++
 .../dri/r300/compiler/tests/rc_test_helpers.h      |   13 +
 .../drivers/dri/r300/compiler/tests/unit_test.c    |   35 ++
 .../drivers/dri/r300/compiler/tests/unit_test.h    |   17 +
 7 files changed, 579 insertions(+), 0 deletions(-)

diff --git a/src/mesa/drivers/dri/r300/compiler/Makefile b/src/mesa/drivers/dri/r300/compiler/Makefile
index 5c9f57b..4bedfac 100644
--- a/src/mesa/drivers/dri/r300/compiler/Makefile
+++ b/src/mesa/drivers/dri/r300/compiler/Makefile
@@ -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
index 0000000..e268543
--- /dev/null
+++ b/src/mesa/drivers/dri/r300/compiler/tests/Makefile
@@ -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
index 0000000..be5036b
--- /dev/null
+++ b/src/mesa/drivers/dri/r300/compiler/tests/radeon_compiler_util_tests.c
@@ -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
index 0000000..ca4738a
--- /dev/null
+++ b/src/mesa/drivers/dri/r300/compiler/tests/rc_test_helpers.c
@@ -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
index 0000000..1a6bf96
--- /dev/null
+++ b/src/mesa/drivers/dri/r300/compiler/tests/rc_test_helpers.h
@@ -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
index 0000000..266f336
--- /dev/null
+++ b/src/mesa/drivers/dri/r300/compiler/tests/unit_test.c
@@ -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
index 0000000..441e8b6
--- /dev/null
+++ b/src/mesa/drivers/dri/r300/compiler/tests/unit_test.h
@@ -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);




More information about the mesa-commit mailing list