[Mesa-dev] [PATCH 2/3] mesa/st: glsl_to_tgsi Implement a new lifetime tracker for temporaries
Gert Wollny
gw.fossdev at gmail.com
Fri Jun 9 23:15:07 UTC 2017
This patch adds new classes and tests to implement a tracker for the
life time of temporary registers for the register renaming stage of
glsl_to_tgsi. The tracker aims at estimating the shortest possible
life time for each register. The code base requires c++11, the flag is
propagated from the LLVM_CXXFLAGS.
---
configure.ac | 1 +
src/mesa/Makefile.am | 4 +-
src/mesa/Makefile.sources | 2 +
.../state_tracker/st_glsl_to_tgsi_temprename.cpp | 551 ++++++++++++++
.../state_tracker/st_glsl_to_tgsi_temprename.h | 114 +++
src/mesa/state_tracker/tests/Makefile.am | 40 ++
src/mesa/state_tracker/tests/st-renumerate-test | 210 ++++++
.../tests/test_glsl_to_tgsi_lifetime.cpp | 789 +++++++++++++++++++++
8 files changed, 1709 insertions(+), 2 deletions(-)
create mode 100644 src/mesa/state_tracker/st_glsl_to_tgsi_temprename.cpp
create mode 100644 src/mesa/state_tracker/st_glsl_to_tgsi_temprename.h
create mode 100644 src/mesa/state_tracker/tests/Makefile.am
create mode 100755 src/mesa/state_tracker/tests/st-renumerate-test
create mode 100644 src/mesa/state_tracker/tests/test_glsl_to_tgsi_lifetime.cpp
diff --git a/configure.ac b/configure.ac
index f379ba8573..579e159420 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2827,6 +2827,7 @@ AC_CONFIG_FILES([Makefile
src/mesa/drivers/osmesa/osmesa.pc
src/mesa/drivers/x11/Makefile
src/mesa/main/tests/Makefile
+ src/mesa/state_tracker/tests/Makefile
src/util/Makefile
src/util/tests/hash_table/Makefile
src/vulkan/Makefile])
diff --git a/src/mesa/Makefile.am b/src/mesa/Makefile.am
index 53f311d2a9..72ffd61212 100644
--- a/src/mesa/Makefile.am
+++ b/src/mesa/Makefile.am
@@ -19,7 +19,7 @@
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
-SUBDIRS = . main/tests
+SUBDIRS = . main/tests state_tracker/tests
if HAVE_XLIB_GLX
SUBDIRS += drivers/x11
@@ -101,7 +101,7 @@ AM_CFLAGS = \
$(VISIBILITY_CFLAGS) \
$(MSVC2013_COMPAT_CFLAGS)
AM_CXXFLAGS = \
- $(LLVM_CFLAGS) \
+ $(LLVM_CXXFLAGS) \
$(VISIBILITY_CXXFLAGS) \
$(MSVC2013_COMPAT_CXXFLAGS)
diff --git a/src/mesa/Makefile.sources b/src/mesa/Makefile.sources
index 4450d80090..908d1acff6 100644
--- a/src/mesa/Makefile.sources
+++ b/src/mesa/Makefile.sources
@@ -507,6 +507,8 @@ STATETRACKER_FILES = \
state_tracker/st_glsl_to_tgsi.h \
state_tracker/st_glsl_to_tgsi_private.cpp \
state_tracker/st_glsl_to_tgsi_private.h \
+ state_tracker/st_glsl_to_tgsi_temprename.cpp \
+ state_tracker/st_glsl_to_tgsi_temprename.h \
state_tracker/st_glsl_types.cpp \
state_tracker/st_glsl_types.h \
state_tracker/st_manager.c \
diff --git a/src/mesa/state_tracker/st_glsl_to_tgsi_temprename.cpp b/src/mesa/state_tracker/st_glsl_to_tgsi_temprename.cpp
new file mode 100644
index 0000000000..389a4b6b5f
--- /dev/null
+++ b/src/mesa/state_tracker/st_glsl_to_tgsi_temprename.cpp
@@ -0,0 +1,551 @@
+/*
+ * Copyright © 2017 Gert Wollny
+ *
+ * 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 "st_glsl_to_tgsi_temprename.h"
+#include <tgsi/tgsi_info.h>
+#include <mesa/program/prog_instruction.h>
+#include <stack>
+#include <limits>
+#include <iostream>
+
+
+using std::vector;
+using std::stack;
+using std::shared_ptr;
+using std::weak_ptr;
+using std::pair;
+using std::make_pair;
+using std::make_shared;
+using std::numeric_limits;
+
+tgsi_temp_lifetime::tgsi_temp_lifetime(exec_list *instructions, int ntemps):
+ lifetimes(ntemps)
+{
+ evaluate(instructions);
+}
+
+const std::vector<std::pair<int, int> >& tgsi_temp_lifetime::get_lifetimes() const
+{
+ return lifetimes;
+}
+
+void tgsi_temp_lifetime::evaluate(exec_list *instructions)
+{
+ int i = 0;
+ int loop_id = 0;
+ int if_id = 0;
+ int switch_id = 0;
+ int scope_level = 0;
+ bool is_at_end = false;
+ shared_ptr<prog_scope> current = make_shared<prog_scope>(sct_outer, 0, scope_level++, i);
+ stack<shared_ptr<prog_scope>> scope_stack;
+
+ vector<temp_access> acc(lifetimes.size());
+
+ foreach_in_list(glsl_to_tgsi_instruction, inst, instructions) {
+ if (is_at_end) {
+ std::cerr << "GLSL_TO_TGSI: shader has instructions past end marker\n";
+ break;
+ }
+
+ switch (inst->op) {
+ case TGSI_OPCODE_BGNLOOP: {
+ shared_ptr<prog_scope> scope = make_shared<prog_scope>(current, sct_loop, loop_id, scope_level, i);
+ ++loop_id;
+ ++scope_level;
+ scope_stack.push(current);
+ current = scope;
+ break;
+ }
+ case TGSI_OPCODE_ENDLOOP: {
+ --scope_level;
+ current->set_end(i);
+ current = scope_stack.top();
+ scope_stack.pop();
+ break;
+ }
+ case TGSI_OPCODE_IF:
+ case TGSI_OPCODE_UIF:{
+ for (unsigned j = 0; j < num_inst_src_regs(inst); j++) {
+ if (inst->src[j].file == PROGRAM_TEMPORARY) {
+ acc[inst->src[j].index].append(i, acc_read, current);
+ }
+ }
+ shared_ptr<prog_scope> scope = make_shared<prog_scope>(current, sct_if, if_id, scope_level, i);
+ ++if_id;
+ ++scope_level;
+ scope_stack.push(current);
+ current = scope;
+ break;
+ }
+ case TGSI_OPCODE_ELSE: {
+ current->set_end(i-1);
+ current = make_shared<prog_scope>(current->parent(), sct_else, current->id(),
+ current->level(), i);
+ break;
+ }
+ case TGSI_OPCODE_END:{
+ current->set_end(i);
+ is_at_end = true;
+ break;
+ }
+ case TGSI_OPCODE_ENDIF:{
+ --scope_level;
+ current->set_end(i-1);
+ current = scope_stack.top();
+ scope_stack.pop();
+ break;
+ }
+ case TGSI_OPCODE_SWITCH: {
+ shared_ptr<prog_scope> scope = make_shared<prog_scope>(current, sct_switch, switch_id, scope_level, i);
+ ++scope_level;
+ ++switch_id;
+ scope_stack.push(current);
+ current = scope;
+ break;
+ }
+ case TGSI_OPCODE_ENDSWITCH: {
+ --scope_level;
+ current->set_end(i-1);
+
+ // remove the case level
+ if (current->type() != sct_switch ) {
+ current = scope_stack.top();
+ scope_stack.pop();
+ }
+ current = scope_stack.top();
+ scope_stack.pop();
+ break;
+ }
+
+ case TGSI_OPCODE_CASE:
+ case TGSI_OPCODE_DEFAULT: {
+ if ( current->type() == sct_switch ) {
+ scope_stack.push(current);
+ current = make_shared<prog_scope>(current, sct_case, current->id(), scope_level, i);
+ }else{
+ auto p = current->parent();
+ auto scope = make_shared<prog_scope>(p, sct_case, p->id(), p->level(), i);
+ if (current->end() == -1)
+ scope->set_previous(current);
+ current = scope;
+ }
+ for (unsigned j = 0; j < num_inst_src_regs(inst); j++) {
+ if (inst->src[j].file == PROGRAM_TEMPORARY) {
+ acc[inst->src[j].index].append(i, acc_read, current);
+ }
+ }
+ }
+ case TGSI_OPCODE_BRK: {
+ if ( current->type() == sct_case) {
+ current->set_end(i-1);
+ }
+ }
+ case TGSI_OPCODE_CONT: {
+ current->set_continue(current, i);
+ break;
+ }
+
+ default: {
+
+ for (unsigned j = 0; j < num_inst_dst_regs(inst); j++) {
+ if (inst->dst[j].file == PROGRAM_TEMPORARY) {
+ acc[inst->dst[j].index].append(i, acc_write, current);
+ }
+ }
+
+ for (unsigned j = 0; j < num_inst_src_regs(inst); j++) {
+ if (inst->src[j].file == PROGRAM_TEMPORARY) {
+ acc[inst->src[j].index].append(i, acc_read, current);
+ }
+ }
+
+ for (unsigned j = 0; j < inst->tex_offset_num_offset; j++) {
+ if (inst->tex_offsets[j].file == PROGRAM_TEMPORARY) {
+ acc[inst->tex_offsets[j].index].append(i, acc_read, current);
+ }
+ }
+
+ } // end default
+ } // end switch (op)
+
+ ++i;
+ }
+
+ // make sure last scope is closed, even though no
+ // TGSI_OPCODE_END was given
+ if (current->end() < 0) {
+ current->set_end(i-1);
+ }
+
+ for(unsigned i = 1; i < lifetimes.size(); ++i) {
+ lifetimes[i] = acc[i].get_required_lifetime();
+ }
+}
+
+
+tgsi_temp_lifetime::prog_scope::prog_scope(e_scope_type type, int id, int lvl, int s_begin):
+ prog_scope(std::shared_ptr<prog_scope>(), type, id, lvl, s_begin)
+{
+}
+
+tgsi_temp_lifetime::prog_scope::prog_scope(std::shared_ptr<prog_scope> p, e_scope_type type, int id, int lvl, int s_begin):
+ scope_type(type),
+ scope_id(id),
+ nested_level(lvl),
+ scope_begin(s_begin),
+ scope_end(-1),
+ loop_continue(numeric_limits<int>::max()),
+ parent_scope(p)
+{
+}
+
+tgsi_temp_lifetime::e_scope_type tgsi_temp_lifetime::prog_scope::type() const
+{
+ return scope_type;
+}
+
+
+std::shared_ptr<tgsi_temp_lifetime::prog_scope>
+tgsi_temp_lifetime::prog_scope::parent() const
+{
+ return parent_scope;
+}
+
+int tgsi_temp_lifetime::prog_scope::level() const
+{
+ return nested_level;
+}
+
+bool tgsi_temp_lifetime::prog_scope::in_loop() const
+{
+ if (scope_type == sct_loop)
+ return true;
+ if (parent_scope)
+ return parent_scope->in_loop();
+ return false;
+}
+
+const tgsi_temp_lifetime::prog_scope *
+tgsi_temp_lifetime::prog_scope::get_parent_loop() const
+{
+ if (scope_type == sct_loop)
+ return this;
+ if (parent_scope)
+ return parent_scope->get_parent_loop();
+ else
+ return nullptr;
+}
+
+bool tgsi_temp_lifetime::prog_scope::contains(const prog_scope& other) const
+{
+ return (begin() <= other.begin()) && (end() >= other.end());
+}
+
+bool tgsi_temp_lifetime::prog_scope::is_conditional() const
+{
+ return scope_type == sct_if || scope_type == sct_else ||
+ scope_type == sct_case;
+}
+
+const tgsi_temp_lifetime::prog_scope *
+tgsi_temp_lifetime::prog_scope::in_ifelse() const
+{
+ if (scope_type == sct_if ||
+ scope_type == sct_else)
+ return this;
+ else if (parent_scope)
+ return parent_scope->in_ifelse();
+ else
+ return nullptr;
+}
+
+const tgsi_temp_lifetime::prog_scope *
+tgsi_temp_lifetime::prog_scope::in_switchcase() const
+{
+ if (scope_type == sct_case)
+ return this;
+ else if (parent_scope)
+ return parent_scope->in_switchcase();
+ else
+ return nullptr;
+}
+
+int tgsi_temp_lifetime::prog_scope::id() const
+{
+ return scope_id;
+}
+
+int tgsi_temp_lifetime::prog_scope::begin() const
+{
+ return scope_begin;
+}
+
+int tgsi_temp_lifetime::prog_scope::end() const
+{
+ return scope_end;
+}
+
+void tgsi_temp_lifetime::prog_scope::set_previous(std::shared_ptr<prog_scope> prev)
+{
+ previous = prev;
+}
+
+void tgsi_temp_lifetime::prog_scope::set_end(int end)
+{
+ if (scope_end == -1) {
+ scope_end = end;
+ if (previous)
+ previous->set_end(end);
+ }
+}
+
+void tgsi_temp_lifetime::prog_scope::set_continue(weak_ptr<prog_scope> scope, int i)
+{
+ if (scope_type == sct_loop) {
+ loop_to_continue_scope = scope;
+ loop_continue = i;
+ } else if (parent_scope)
+ parent()->set_continue(scope, i);
+}
+
+int tgsi_temp_lifetime::prog_scope::loop_continue_idx() const
+{
+ return loop_continue;
+}
+
+void tgsi_temp_lifetime::temp_access::append(int index, e_acc_type rw, std::shared_ptr<prog_scope> pstate)
+{
+ temp_access_record r = {index, rw, pstate};
+ timeline.push_back(r);
+}
+
+pair<int, int> tgsi_temp_lifetime::temp_access::get_required_lifetime() const
+{
+ bool keep_for_full_loop = false;
+
+ std::shared_ptr<prog_scope> lr_scope;
+ std::shared_ptr<prog_scope> fr_scope;
+ std::shared_ptr<prog_scope> fw_scope;
+ const prog_scope *fw_ifthen_scope = nullptr;
+ const prog_scope *fw_switch_scope = nullptr;
+
+ int first_write = -1;
+ int last_read = -1;
+ int first_read = numeric_limits<int>::max();
+
+ for (temp_access_record i: timeline) {
+ if (i.acc == acc_read) {
+ last_read = i.index;
+ lr_scope = i.pstate;
+ if (first_read > i.index) {
+ first_read = i.index;
+ fr_scope = i.pstate;
+ }
+ }else{
+ if (first_write == -1) {
+ first_write = i.index;
+ fw_scope = i.pstate;
+
+ // we write in an if-branch
+ fw_ifthen_scope = i.pstate->in_ifelse();
+ if (fw_ifthen_scope && fw_ifthen_scope->in_loop()) {
+ // value not always written, in loops we must keep it
+ keep_for_full_loop = true;
+ } else {
+ // test for switch-case
+ fw_switch_scope = i.pstate->in_switchcase();
+
+ if (fw_switch_scope && fw_switch_scope->in_loop()) {
+ keep_for_full_loop = true;
+ }
+ }
+ } else if (keep_for_full_loop) {
+
+ // written in if branch?
+ // disable because read first in else branch
+ // makes this invalid and this is not (yet) tracked
+ if (0 && fw_ifthen_scope) {
+ auto s = i.pstate->in_ifelse();
+ // also written in the else branch?
+ if (s && (s->id() == fw_ifthen_scope->id())) {
+ keep_for_full_loop = false;
+ }
+ }
+ }
+ }
+ }
+
+ // this temp is only read, this is undefined
+ // behaviour, so we can use the register otherwise
+ if (!fw_scope) {
+ return make_pair(-1, -1);
+ }
+
+ // Only written to, just make sure it doesn't overlap
+ if (!lr_scope) {
+ return make_pair(first_write, first_write);
+ }
+
+ int target_level = -1;
+ // evaluate the shared scope
+ while (target_level < 0) {
+ if (lr_scope->contains(*fw_scope)) {
+ target_level = lr_scope->level();
+ } else if (fw_scope->contains(*lr_scope)) {
+ target_level = fw_scope->level();
+ } else {
+ // scopes (partially) disjunct, move up
+ if (lr_scope->type() == sct_loop) {
+ last_read = lr_scope->end();
+ }
+ lr_scope = lr_scope->parent();
+ }
+ }
+
+ // propagate the read scope to the target_level
+ while (lr_scope->level() > target_level) {
+ // if the read is in a loop we need to extend the
+ // variables life time to the end of that loop
+ if (lr_scope->type() == sct_loop) {
+ last_read = lr_scope->end();
+ }
+ lr_scope = lr_scope->parent();
+ }
+
+
+ // propagate the first_write scope to the target_level
+ bool has_continue = false;
+ while (target_level < fw_scope->level()) {
+
+ // propagate lifetime also if there was a continue
+ // in a loop and the write was after the continue
+ if (has_continue || (fw_scope->loop_continue_idx() < first_write)) {
+ has_continue = true;
+ first_write = fw_scope->begin();
+ int lr = fw_scope->end();
+ if (last_read < lr)
+ last_read = lr;
+ }
+
+ if ((fw_scope->type() == sct_loop) && (first_read < first_write)) {
+ first_write = fw_scope->begin();
+ int lr = fw_scope->end();
+ if (last_read < lr)
+ last_read = lr;
+ }
+
+ fw_scope = fw_scope->parent();
+
+ // if the value is conditionally written in a loop
+ // then propagate its lifetime to the full loop
+ if (fw_scope->type() == sct_loop) {
+ if (keep_for_full_loop || (first_read < first_write)) {
+ first_write = fw_scope->begin();
+ int lr = fw_scope->end();
+ if (last_read < lr)
+ last_read = lr;
+ }
+ }
+
+ // if we currently don't propagate the lifetime but
+ // the enclosing scope is a conditional within a loop
+ // up to the last-read level we need to propagate,
+ // todo: to tighten the life time check whether the value
+ // is written in all consitional code path below the loop
+ if (!keep_for_full_loop &&
+ fw_scope->is_conditional() &&
+ fw_scope->in_loop()) {
+ keep_for_full_loop = true;
+ }
+ }
+
+
+
+
+ // same level and same range means it is first
+ // written and last read in the same scope
+ // ignore the case when first read is before
+ // first write, because it is undefined behaviour
+ if ((lr_scope->begin() == fw_scope->begin()) &&
+ (lr_scope->end() == fw_scope->end())) {
+ return make_pair(first_write, last_read);
+ }
+
+ // different scopes,
+ if (!keep_for_full_loop && first_read > first_write) {
+ return make_pair(first_write, last_read);
+ }else{
+ // 1. if the value is not always written in a loop
+ // it must be kept for the whole loop scope.
+ //
+ // 2. if the value is read (maybe conditionally)
+ // before it is written first it also must be
+ // kept valid for the whole loop
+ auto enclosing_loop = lr_scope->get_parent_loop();
+ assert(enclosing_loop);
+ return make_pair(enclosing_loop->begin(), enclosing_loop->end());
+ }
+}
+
+
+void evaluate_remapping(const vector<std::pair<int, int>>& lt, std::vector<rename_reg_pair>& result)
+{
+
+ struct out_access_record {
+ int end;
+ unsigned reg;
+ bool tested;
+ };
+
+ std::multimap<int, out_access_record> m;
+ for (unsigned i = 1; i < lt.size(); ++i) {
+ out_access_record oar = {lt[i].second, i, false};
+ m.insert(make_pair(lt[i].first, oar));
+ }
+
+ while (true) {
+ auto trgt = m.begin();
+
+ while ( trgt != m.end() && trgt->second.tested)
+ ++trgt;
+
+ if (trgt == m.end())
+ break;
+
+ while (trgt != m.end()) {
+ auto src = m.upper_bound(trgt->second.end - 1);
+ if (src == m.end()) {
+ trgt->second.tested = true;
+ } else {
+ result[src->second.reg].new_reg = trgt->second.reg;
+ result[src->second.reg].valid = true;
+ trgt->second.end = src->second.end;
+ m.erase(src);
+ break;
+ }
+ ++trgt;
+ }
+ }
+}
diff --git a/src/mesa/state_tracker/st_glsl_to_tgsi_temprename.h b/src/mesa/state_tracker/st_glsl_to_tgsi_temprename.h
new file mode 100644
index 0000000000..e764f0f0c2
--- /dev/null
+++ b/src/mesa/state_tracker/st_glsl_to_tgsi_temprename.h
@@ -0,0 +1,114 @@
+/*
+ * Copyright © 2017 Gert Wollny
+ *
+ * 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 "st_glsl_to_tgsi_private.h"
+#include <memory>
+#include <map>
+
+class tgsi_temp_lifetime {
+public:
+
+ enum e_scope_type {
+ sct_outer,
+ sct_loop,
+ sct_if,
+ sct_else,
+ sct_switch,
+ sct_case,
+ sct_unknown
+ };
+
+ enum e_acc_type {
+ acc_read,
+ acc_write
+ };
+
+ class prog_scope {
+
+ public:
+ prog_scope(e_scope_type type, int id, int lvl, int s_begin);
+ prog_scope(std::shared_ptr<prog_scope> p, e_scope_type type, int id, int lvl, int s_begin);
+
+ e_scope_type type() const;
+ std::shared_ptr<prog_scope> parent() const;
+ int level() const;
+ int id() const;
+ int end() const;
+ int begin() const;
+ const prog_scope *in_ifelse() const;
+ const prog_scope *in_switchcase() const;
+ int loop_continue_idx() const;
+ bool in_loop() const;
+ const prog_scope *get_parent_loop() const;
+ bool is_conditional() const;
+ bool contains(const prog_scope& other) const;
+ void set_end(int end);
+ void set_previous(std::shared_ptr<prog_scope> prev);
+ void set_continue(std::weak_ptr<prog_scope> scope, int i);
+ private:
+ e_scope_type scope_type;
+ int scope_id;
+ int nested_level;
+ int scope_begin;
+ int scope_end;
+ int loop_continue;
+ std::weak_ptr<prog_scope> loop_to_continue_scope;
+ std::shared_ptr<prog_scope> previous;
+ std::shared_ptr<prog_scope> parent_scope;
+
+ };
+
+ class temp_access {
+
+ public:
+
+ void append(int index, e_acc_type rw, std::shared_ptr<prog_scope> pstate);
+ std::pair<int, int> get_required_lifetime() const;
+
+ private:
+
+ struct temp_access_record {
+ int index;
+ e_acc_type acc;
+ std::shared_ptr<prog_scope> pstate;
+ };
+
+ std::vector< temp_access_record > timeline;
+
+ };
+
+ tgsi_temp_lifetime(exec_list *instructions, int ntemps);
+
+ const std::vector<std::pair<int, int> >& get_lifetimes() const;
+
+
+private:
+
+ void evaluate(exec_list *instructions);
+
+ std::vector<std::pair<int, int> > lifetimes;
+};
+
+void evaluate_remapping(const std::vector<std::pair<int, int>>& lt, std::vector<rename_reg_pair>& result);
+
+
diff --git a/src/mesa/state_tracker/tests/Makefile.am b/src/mesa/state_tracker/tests/Makefile.am
new file mode 100644
index 0000000000..a2bcad8dde
--- /dev/null
+++ b/src/mesa/state_tracker/tests/Makefile.am
@@ -0,0 +1,40 @@
+AM_CFLAGS = \
+ $(PTHREAD_CFLAGS)
+
+AM_CXXFLAGS = \
+ $(LLVM_CXXFLAGS)
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/gtest/include \
+ -I$(top_srcdir)/src \
+ -I$(top_srcdir)/src/mapi \
+ -I$(top_builddir)/src/mesa \
+ -I$(top_srcdir)/src/mesa \
+ -I$(top_srcdir)/include \
+ -I$(top_srcdir)/src/gallium/include \
+ -I$(top_srcdir)/src/gallium/auxiliary \
+ $(DEFINES) $(INCLUDE_DIRS)
+
+TESTS = st-renumerate-test
+check_PROGRAMS = st-renumerate-test
+
+st_renumerate_test_SOURCES = \
+ test_glsl_to_tgsi_lifetime.cpp
+
+st_renumerate_test_LDFLAGS = \
+ $(LLVM_LDFLAGS)
+
+st_renumerate_test_LDADD = \
+ $(top_builddir)/src/mesa/libmesagallium.la \
+ $(top_builddir)/src/mapi/shared-glapi/libglapi.la \
+ $(top_builddir)/src/gallium/auxiliary/libgallium.la \
+ $(top_builddir)/src/util/libmesautil.la \
+ $(top_builddir)/src/gallium/drivers/trace/libtrace.la \
+ $(top_builddir)/src/gallium/winsys/sw/null/libws_null.la \
+ $(top_builddir)/src/gallium/drivers/softpipe/libsoftpipe.la \
+ $(top_builddir)/src/gtest/libgtest.la \
+ $(GALLIUM_COMMON_LIB_DEPS) \
+ $(LLVM_LIBS) \
+ $(PTHREAD_LIBS) \
+ $(DLOPEN_LIBS)
+
diff --git a/src/mesa/state_tracker/tests/st-renumerate-test b/src/mesa/state_tracker/tests/st-renumerate-test
new file mode 100755
index 0000000000..bc2f325899
--- /dev/null
+++ b/src/mesa/state_tracker/tests/st-renumerate-test
@@ -0,0 +1,210 @@
+#! /bin/sh
+
+# st-renumerate-test - temporary wrapper script for .libs/st-renumerate-test
+# Generated by libtool (GNU libtool) 2.4.6
+#
+# The st-renumerate-test program cannot be directly executed until all the libtool
+# libraries that it depends on are installed.
+#
+# This wrapper script should never be moved out of the build directory.
+# If it is, it will not operate correctly.
+
+# Sed substitution that helps us do robust quoting. It backslashifies
+# metacharacters that are still active within double-quoted strings.
+sed_quote_subst='s|\([`"$\\]\)|\\\1|g'
+
+# Be Bourne compatible
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then
+ emulate sh
+ NULLCMD=:
+ # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which
+ # is contrary to our usage. Disable this feature.
+ alias -g '${1+"$@"}'='"$@"'
+ setopt NO_GLOB_SUBST
+else
+ case `(set -o) 2>/dev/null` in *posix*) set -o posix;; esac
+fi
+BIN_SH=xpg4; export BIN_SH # for Tru64
+DUALCASE=1; export DUALCASE # for MKS sh
+
+# The HP-UX ksh and POSIX shell print the target directory to stdout
+# if CDPATH is set.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+relink_command=""
+
+# This environment variable determines our operation mode.
+if test "$libtool_install_magic" = "%%%MAGIC variable%%%"; then
+ # install mode needs the following variables:
+ generated_by_libtool_version='2.4.6'
+ notinst_deplibs=' ../../../../src/mapi/shared-glapi/libglapi.la'
+else
+ # When we are sourced in execute mode, $file and $ECHO are already set.
+ if test "$libtool_execute_magic" != "%%%MAGIC variable%%%"; then
+ file="$0"
+
+# A function that is used when there is no print builtin or printf.
+func_fallback_echo ()
+{
+ eval 'cat <<_LTECHO_EOF
+$1
+_LTECHO_EOF'
+}
+ ECHO="printf %s\\n"
+ fi
+
+# Very basic option parsing. These options are (a) specific to
+# the libtool wrapper, (b) are identical between the wrapper
+# /script/ and the wrapper /executable/ that is used only on
+# windows platforms, and (c) all begin with the string --lt-
+# (application programs are unlikely to have options that match
+# this pattern).
+#
+# There are only two supported options: --lt-debug and
+# --lt-dump-script. There is, deliberately, no --lt-help.
+#
+# The first argument to this parsing function should be the
+# script's ../../../../libtool value, followed by no.
+lt_option_debug=
+func_parse_lt_options ()
+{
+ lt_script_arg0=$0
+ shift
+ for lt_opt
+ do
+ case "$lt_opt" in
+ --lt-debug) lt_option_debug=1 ;;
+ --lt-dump-script)
+ lt_dump_D=`$ECHO "X$lt_script_arg0" | /bin/sed -e 's/^X//' -e 's%/[^/]*$%%'`
+ test "X$lt_dump_D" = "X$lt_script_arg0" && lt_dump_D=.
+ lt_dump_F=`$ECHO "X$lt_script_arg0" | /bin/sed -e 's/^X//' -e 's%^.*/%%'`
+ cat "$lt_dump_D/$lt_dump_F"
+ exit 0
+ ;;
+ --lt-*)
+ $ECHO "Unrecognized --lt- option: '$lt_opt'" 1>&2
+ exit 1
+ ;;
+ esac
+ done
+
+ # Print the debug banner immediately:
+ if test -n "$lt_option_debug"; then
+ echo "st-renumerate-test:st-renumerate-test:$LINENO: libtool wrapper (GNU libtool) 2.4.6" 1>&2
+ fi
+}
+
+# Used when --lt-debug. Prints its arguments to stdout
+# (redirection is the responsibility of the caller)
+func_lt_dump_args ()
+{
+ lt_dump_args_N=1;
+ for lt_arg
+ do
+ $ECHO "st-renumerate-test:st-renumerate-test:$LINENO: newargv[$lt_dump_args_N]: $lt_arg"
+ lt_dump_args_N=`expr $lt_dump_args_N + 1`
+ done
+}
+
+# Core function for launching the target application
+func_exec_program_core ()
+{
+
+ if test -n "$lt_option_debug"; then
+ $ECHO "st-renumerate-test:st-renumerate-test:$LINENO: newargv[0]: $progdir/$program" 1>&2
+ func_lt_dump_args ${1+"$@"} 1>&2
+ fi
+ exec "$progdir/$program" ${1+"$@"}
+
+ $ECHO "$0: cannot exec $program $*" 1>&2
+ exit 1
+}
+
+# A function to encapsulate launching the target application
+# Strips options in the --lt-* namespace from $@ and
+# launches target application with the remaining arguments.
+func_exec_program ()
+{
+ case " $* " in
+ *\ --lt-*)
+ for lt_wr_arg
+ do
+ case $lt_wr_arg in
+ --lt-*) ;;
+ *) set x "$@" "$lt_wr_arg"; shift;;
+ esac
+ shift
+ done ;;
+ esac
+ func_exec_program_core ${1+"$@"}
+}
+
+ # Parse options
+ func_parse_lt_options "$0" ${1+"$@"}
+
+ # Find the directory that this script lives in.
+ thisdir=`$ECHO "$file" | /bin/sed 's%/[^/]*$%%'`
+ test "x$thisdir" = "x$file" && thisdir=.
+
+ # Follow symbolic links until we get to the real thisdir.
+ file=`ls -ld "$file" | /bin/sed -n 's/.*-> //p'`
+ while test -n "$file"; do
+ destdir=`$ECHO "$file" | /bin/sed 's%/[^/]*$%%'`
+
+ # If there was a directory component, then change thisdir.
+ if test "x$destdir" != "x$file"; then
+ case "$destdir" in
+ [\\/]* | [A-Za-z]:[\\/]*) thisdir="$destdir" ;;
+ *) thisdir="$thisdir/$destdir" ;;
+ esac
+ fi
+
+ file=`$ECHO "$file" | /bin/sed 's%^.*/%%'`
+ file=`ls -ld "$thisdir/$file" | /bin/sed -n 's/.*-> //p'`
+ done
+
+ # Usually 'no', except on cygwin/mingw when embedded into
+ # the cwrapper.
+ WRAPPER_SCRIPT_BELONGS_IN_OBJDIR=no
+ if test "$WRAPPER_SCRIPT_BELONGS_IN_OBJDIR" = "yes"; then
+ # special case for '.'
+ if test "$thisdir" = "."; then
+ thisdir=`pwd`
+ fi
+ # remove .libs from thisdir
+ case "$thisdir" in
+ *[\\/].libs ) thisdir=`$ECHO "$thisdir" | /bin/sed 's%[\\/][^\\/]*$%%'` ;;
+ .libs ) thisdir=. ;;
+ esac
+ fi
+
+ # Try to get the absolute directory name.
+ absdir=`cd "$thisdir" && pwd`
+ test -n "$absdir" && thisdir="$absdir"
+
+ program='st-renumerate-test'
+ progdir="$thisdir/.libs"
+
+
+ if test -f "$progdir/$program"; then
+ # Add our own library path to LD_LIBRARY_PATH
+ LD_LIBRARY_PATH="/home/gerddie/src/Freedesktop/mesa-orig/src/mapi/shared-glapi/.libs:$LD_LIBRARY_PATH"
+
+ # Some systems cannot cope with colon-terminated LD_LIBRARY_PATH
+ # The second colon is a workaround for a bug in BeOS R4 sed
+ LD_LIBRARY_PATH=`$ECHO "$LD_LIBRARY_PATH" | /bin/sed 's/::*$//'`
+
+ export LD_LIBRARY_PATH
+
+ if test "$libtool_execute_magic" != "%%%MAGIC variable%%%"; then
+ # Run the actual program with our arguments.
+ func_exec_program ${1+"$@"}
+ fi
+ else
+ # The program doesn't exist.
+ $ECHO "$0: error: '$progdir/$program' does not exist" 1>&2
+ $ECHO "This script is just a wrapper for $program." 1>&2
+ $ECHO "See the libtool documentation for more information." 1>&2
+ exit 1
+ fi
+fi
diff --git a/src/mesa/state_tracker/tests/test_glsl_to_tgsi_lifetime.cpp b/src/mesa/state_tracker/tests/test_glsl_to_tgsi_lifetime.cpp
new file mode 100644
index 0000000000..3e094f0dda
--- /dev/null
+++ b/src/mesa/state_tracker/tests/test_glsl_to_tgsi_lifetime.cpp
@@ -0,0 +1,789 @@
+/*
+ * Copyright © 2017 Gert Wollny
+ *
+ * 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 <state_tracker/st_glsl_to_tgsi_temprename.h>
+#include <tgsi/tgsi_ureg.h>
+#include <tgsi/tgsi_info.h>
+#include <compiler/glsl/list.h>
+#include <gtest/gtest.h>
+
+using std::vector;
+using std::pair;
+
+struct MockCodeline {
+ MockCodeline(unsigned _op): op(_op) {}
+ MockCodeline(unsigned _op, const vector<int>& _dst, const vector<int>& _src, const vector<int>&_to):
+ op(_op), dst(_dst), src(_src), tex_offsets(_to){}
+ unsigned op;
+ vector<int> dst;
+ vector<int> src;
+ vector<int> tex_offsets;
+};
+
+const int in0 = 0;
+const int in1 = -1;
+const int in2 = -2;
+
+const int out0 = 0;
+const int out1 = -1;
+
+
+
+/*
+ * supported opcodes for mock-shaders:
+ *
+ * Arithmentic
+ *
+ * TGSI_OPCODE_MOV, TGSI_OPCODE_UADD, TGSI_OPCODE_UMAD
+ *
+ * Flow control
+ * loops:
+ * TGSI_OPCODE_BGNLOOP, TGSI_OPCODE_ENDLOOP,
+ * TGSI_OPCODE_BRK, TGSI_OPCODE_CONT
+ *
+ * if/switch:
+ * TGSI_OPCODE_IF, TGSI_OPCODE_UIF, TGSI_OPCODE_ELSE, TGSI_OPCODE_ENDIF
+ * TGSI_OPCODE_SWITCH, TGSI_OPCODE_CASE, TGSI_OPCODE_DEFAULT
+ *
+ * TGSI_OPCODE_END
+ */
+
+
+class MockShader {
+public:
+ MockShader(const vector<MockCodeline>& source);
+ ~MockShader();
+
+ void free();
+
+ exec_list* get_program();
+ int get_num_temps();
+private:
+ st_src_reg create_src_register(int src_idx);
+ st_dst_reg create_dst_register(int dst_idx);
+ exec_list* program;
+ int num_temps;
+ void *mem_ctx;
+};
+
+using expectation = vector<vector<int>>;
+
+MockShader::~MockShader()
+{
+ free();
+ ralloc_free(mem_ctx);
+}
+
+int MockShader::get_num_temps()
+{
+ return num_temps;
+}
+
+
+exec_list* MockShader::get_program()
+{
+ return program;
+}
+
+MockShader::MockShader(const vector<MockCodeline>& source):
+ num_temps(0)
+{
+ mem_ctx = ralloc_context(NULL);
+
+ program = new(mem_ctx) exec_list();
+
+ for (MockCodeline i: source) {
+ glsl_to_tgsi_instruction *next_instr = new(mem_ctx) glsl_to_tgsi_instruction();
+ next_instr->op = i.op;
+ next_instr->info = tgsi_get_opcode_info(i.op);
+
+ assert(i.src.size() < 4);
+ assert(i.dst.size() < 3);
+ assert(i.tex_offsets.size() < 3);
+
+ for (unsigned k = 0; k < i.src.size(); ++k) {
+ next_instr->src[k] = create_src_register(i.src[k]);
+ }
+ for (unsigned k = 0; k < i.dst.size(); ++k) {
+ next_instr->dst[k] = create_dst_register(i.dst[k]);
+ }
+
+ // set texture registers
+ next_instr->tex_offset_num_offset = i.tex_offsets.size();
+ next_instr->tex_offsets = new st_src_reg[i.tex_offsets.size()];
+ for (unsigned k = 0; k < i.tex_offsets.size(); ++k) {
+ next_instr->tex_offsets[k] = create_src_register(i.tex_offsets[k]);
+ }
+
+ program->push_tail(next_instr);
+ }
+ ++num_temps;
+}
+
+void MockShader::free()
+{
+ // the list is not fully initialized, so
+ // tearing it down also must be done manually.
+ exec_node *p;
+ while ((p = program->pop_head())) {
+ glsl_to_tgsi_instruction * instr = static_cast<glsl_to_tgsi_instruction *>(p);
+ if (instr->tex_offset_num_offset > 0)
+ delete[] instr->tex_offsets;
+ delete p;
+ }
+ program = 0;
+ num_temps = 0;
+}
+
+st_src_reg MockShader::create_src_register(int src_idx)
+{
+ gl_register_file file;
+ int idx = 0;
+ if (src_idx > 0) {
+ file = PROGRAM_TEMPORARY;
+ idx = src_idx;
+ if (num_temps < idx)
+ num_temps = idx;
+ } else {
+ file = PROGRAM_INPUT;
+ idx = -src_idx;
+ }
+ return st_src_reg(file, idx, GLSL_TYPE_INT);
+
+}
+
+st_dst_reg MockShader::create_dst_register(int dst_idx)
+{
+ gl_register_file file;
+ int idx = 0;
+ if (dst_idx > 0) {
+ file = PROGRAM_TEMPORARY;
+ idx = dst_idx;
+ if (num_temps < idx)
+ num_temps = idx;
+ } else {
+ file = PROGRAM_OUTPUT;
+ idx = - dst_idx;
+ }
+ return st_dst_reg(file, 0xF, GLSL_TYPE_INT, idx);
+}
+
+/**
+ This is a text class to check the exact life times
+*/
+class LifetimeEvaluatorExactTest : public testing::Test {
+protected:
+ void run(const vector<MockCodeline>& code, const expectation& e);
+};
+
+/**
+ This is a text class to check that the life time is at least
+ in the expected range
+*/
+class LifetimeEvaluatorAtLeastTest : public testing::Test {
+protected:
+ void run(const vector<MockCodeline>& code, const expectation& e);
+};
+
+
+void LifetimeEvaluatorExactTest::run(const vector<MockCodeline>& code, const expectation& e)
+{
+ MockShader shader(code);
+
+ tgsi_temp_lifetime ana(shader.get_program(), shader.get_num_temps());
+ auto lifetimes = ana.get_lifetimes();
+
+ // lifetimes[0] not used, but created for simpler processing
+ ASSERT_EQ(lifetimes.size(), e.size());
+
+ for (unsigned i = 1; i < lifetimes.size(); ++i) {
+ EXPECT_EQ(lifetimes[i].first, e[i][0]);
+ EXPECT_EQ(lifetimes[i].second, e[i][1]);
+ }
+}
+
+void LifetimeEvaluatorAtLeastTest::run(const vector<MockCodeline>& code, const expectation& e)
+{
+ MockShader shader(code);
+
+ tgsi_temp_lifetime ana(shader.get_program(), shader.get_num_temps());
+ auto lifetimes = ana.get_lifetimes();
+
+ // lifetimes[0] not used, but created for simpler processing
+ ASSERT_EQ(lifetimes.size(), e.size());
+
+ for (unsigned i = 1; i < lifetimes.size(); ++i) {
+ EXPECT_LE(lifetimes[i].first, e[i][0]);
+ EXPECT_GE(lifetimes[i].second, e[i][1]);
+ }
+}
+
+TEST_F(LifetimeEvaluatorExactTest, SimpleMoveAdd)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}},
+ { TGSI_OPCODE_UADD, {out0}, {1, in0}, {}},
+ { TGSI_OPCODE_END}
+ };
+ run(code, expectation({{-1, -1}, {0,1}}));
+}
+
+
+TEST_F(LifetimeEvaluatorExactTest, SimpleMoveAddMove)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}},
+ { TGSI_OPCODE_UADD, {2}, {1, in0}, {}},
+ { TGSI_OPCODE_MOV, {out0}, {2}, {}},
+ { TGSI_OPCODE_END}
+ };
+ run(code, expectation({{-1, -1}, {0,1}, {1,2}}));
+}
+
+TEST_F(LifetimeEvaluatorExactTest, SimpleMoveAddMoveTexoffset)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}},
+ { TGSI_OPCODE_MOV, {2}, {in1}, {}},
+ { TGSI_OPCODE_UADD, {out0}, {}, {1,2}},
+ { TGSI_OPCODE_END}
+ };
+ run(code, expectation({{-1, -1}, {0,2}, {1,2}}));
+}
+
+
+TEST_F(LifetimeEvaluatorExactTest, SimpleMoveInLoop)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}}, // 0
+ { TGSI_OPCODE_BGNLOOP }, // 1
+ { TGSI_OPCODE_UADD, {2}, {1, in0}, {}}, // 2
+ { TGSI_OPCODE_UADD, {3}, {1, 2}, {}}, // 3
+ { TGSI_OPCODE_UADD, {3}, {3, in1}, {}}, // 4
+ { TGSI_OPCODE_ENDLOOP }, // 5
+ { TGSI_OPCODE_MOV, {out0}, {3}, {}}, // 6
+ { TGSI_OPCODE_END}
+ };
+ run (code, expectation({{-1,-1},{0, 5}, {2,3}, {3, 6}}));
+}
+
+
+// in loop if/else value written only in one path, and read later
+// - value must survive the whole loop
+TEST_F(LifetimeEvaluatorExactTest, MoveInIfInLoop)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}}, // 0
+ { TGSI_OPCODE_BGNLOOP }, // 1
+ { TGSI_OPCODE_IF}, // 2
+ { TGSI_OPCODE_UADD, {2}, {1, in0}, {}}, // 3
+ { TGSI_OPCODE_ENDIF}, // 4
+ { TGSI_OPCODE_UADD, {3}, {1, 2}, {}}, // 5
+ { TGSI_OPCODE_UADD, {3}, {3, in1}, {}}, // 6
+ { TGSI_OPCODE_ENDLOOP }, // 7
+ { TGSI_OPCODE_MOV, {out0}, {3}, {}}, // 8
+ { TGSI_OPCODE_END}
+ };
+ run (code, expectation({{-1,-1},{0, 7}, {1,7}, {5, 8}}));
+}
+
+
+// in loop if/else value written in both path, and read later
+// - value must survive from first write to last read in loop
+// for now we only check that the minimum life time is correct
+TEST_F(LifetimeEvaluatorAtLeastTest, WriteInIfAndElseInLoop)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}}, // 0
+ { TGSI_OPCODE_BGNLOOP }, // 1
+ { TGSI_OPCODE_IF}, // 2
+ { TGSI_OPCODE_UADD, {2}, {1, in0}, {}}, // 3
+ { TGSI_OPCODE_ELSE }, // 4
+ { TGSI_OPCODE_MOV, {2}, {1}, {}}, // 5
+ { TGSI_OPCODE_ENDIF}, // 6
+ { TGSI_OPCODE_UADD, {3}, {1, 2}, {}}, // 7
+ { TGSI_OPCODE_UADD, {3}, {3, in1}, {}}, // 8
+ { TGSI_OPCODE_ENDLOOP }, // 9
+ { TGSI_OPCODE_MOV, {out0}, {3}, {}}, // 10
+ { TGSI_OPCODE_END}
+ };
+ run (code, expectation({{-1,-1},{0, 9}, {3,7}, {7, 10}}));
+}
+
+// in loop if/else value written in both path, red in else path
+// before read and also read later
+// - value must survive from first write to last read in loop
+TEST_F(LifetimeEvaluatorExactTest, WriteInIfAndElseReadInElseInLoop)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}}, // 0
+ { TGSI_OPCODE_BGNLOOP }, // 1
+ { TGSI_OPCODE_IF}, // 2
+ { TGSI_OPCODE_UADD, {2}, {1, in0}, {}}, // 3
+ { TGSI_OPCODE_ELSE }, // 4
+ { TGSI_OPCODE_ADD, {2}, {1, 2}, {}}, // 5
+ { TGSI_OPCODE_ENDIF}, // 6
+ { TGSI_OPCODE_UADD, {3}, {1, 2}, {}}, // 7
+ { TGSI_OPCODE_UADD, {3}, {3, in1}, {}}, // 8
+ { TGSI_OPCODE_ENDLOOP }, // 9
+ { TGSI_OPCODE_MOV, {out0}, {3}, {}}, // 10
+ { TGSI_OPCODE_END}
+ };
+ run (code, expectation({{-1,-1},{0, 9}, {1,9}, {7, 10}}));
+}
+
+// in loop if/else read in one path before written in the same loop
+// - value must survive the whole loop
+TEST_F(LifetimeEvaluatorExactTest, ReadInIfInLoopBeforeWrite)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}}, // 0
+ { TGSI_OPCODE_BGNLOOP }, // 1
+ { TGSI_OPCODE_IF, {}, {in0}, {}}, // 2
+ { TGSI_OPCODE_UADD, {2}, {1, 3}, {}}, // 3
+ { TGSI_OPCODE_ENDIF}, // 4
+ { TGSI_OPCODE_UADD, {3}, {1, 2}, {}}, // 5
+ { TGSI_OPCODE_UADD, {3}, {3, in1}, {}}, // 6
+ { TGSI_OPCODE_ENDLOOP }, // 7
+ { TGSI_OPCODE_MOV, {out0}, {3}, {}}, // 8
+ { TGSI_OPCODE_END}
+ };
+ run (code, expectation({{-1,-1},{0, 7}, {1,7}, {1, 8}}));
+}
+
+/* Write in nested ifs in loop, for now we do test whether the
+ * life time is atleast what is required, but we know that the
+ * implementation doesn't do a full check and sets larger boundaries
+ */
+TEST_F(LifetimeEvaluatorAtLeastTest, NestedIfInLoopAlwaysWriteButNotPropagated)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_BGNLOOP }, // 1
+ { TGSI_OPCODE_IF, {}, {in0}, {}},
+ { TGSI_OPCODE_IF, {}, {in0}, {}},
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}},
+ { TGSI_OPCODE_ELSE},
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}}, // 5
+ { TGSI_OPCODE_ENDIF},
+ { TGSI_OPCODE_ELSE},
+ { TGSI_OPCODE_IF, {}, {in0}, {}},
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}},
+ { TGSI_OPCODE_ELSE}, // 10
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}},
+ { TGSI_OPCODE_ENDIF},
+ { TGSI_OPCODE_ENDIF},
+ { TGSI_OPCODE_MOV, {out0}, {1}, {}},
+ { TGSI_OPCODE_ENDLOOP }, // 15
+ { TGSI_OPCODE_END}
+ };
+ run (code, expectation({{-1,-1},{3, 14}}));
+}
+
+
+
+TEST_F(LifetimeEvaluatorExactTest, NestedIfInLoopWriteNotAlways)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_BGNLOOP }, // 0
+ { TGSI_OPCODE_IF, {}, {in0}, {}},
+ { TGSI_OPCODE_IF, {}, {in0}, {}},
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}},
+ { TGSI_OPCODE_ELSE},
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}}, // 5
+ { TGSI_OPCODE_ENDIF},
+ { TGSI_OPCODE_ELSE},
+ { TGSI_OPCODE_IF, {}, {in0}, {}},
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}},
+ { TGSI_OPCODE_ENDIF}, // 10
+ { TGSI_OPCODE_ENDIF},
+ { TGSI_OPCODE_MOV, {out0}, {1}, {}},
+ { TGSI_OPCODE_ENDLOOP }, // 13
+ { TGSI_OPCODE_END}
+ };
+ run (code, expectation({{-1,-1},{0, 13}}));
+}
+
+
+// if a continue is in the loop, all variables written after the
+// continue and used outside the loop must be maintained for the
+// whole loop
+TEST_F(LifetimeEvaluatorExactTest, LoopWithWriteAfterContinue)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_BGNLOOP }, // 0
+ { TGSI_OPCODE_IF, {}, {in0}, {}},
+ { TGSI_OPCODE_CONT},
+ { TGSI_OPCODE_ENDIF},
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}},
+ { TGSI_OPCODE_ENDLOOP }, // 5
+ { TGSI_OPCODE_MOV, {out0}, {1}, {}}, // 6
+ { TGSI_OPCODE_END}
+ };
+ run (code, expectation({{-1,-1},{0, 6}}));
+}
+
+// if a continue is in the loop, all variables written after the
+// continue and used outside the loop must be maintained for the
+// whole loop, but not further
+TEST_F(LifetimeEvaluatorExactTest, NestedLoopWithWriteAfterContinue)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_BGNLOOP }, // 0
+ { TGSI_OPCODE_BGNLOOP }, // 1
+ { TGSI_OPCODE_IF, {}, {in0}, {}},
+ { TGSI_OPCODE_CONT},
+ { TGSI_OPCODE_ENDIF},
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}},
+ { TGSI_OPCODE_ENDLOOP }, // 6
+ { TGSI_OPCODE_MOV, {out0}, {1}, {}}, // 7
+ { TGSI_OPCODE_ENDLOOP }, // 6
+ { TGSI_OPCODE_END}
+ };
+ run (code, expectation({{-1,-1},{1, 7}}));
+}
+
+// if a continue is in the loop, all variables written after the
+// continue and used outside the loop must be maintained for all
+// loops up untto the read scope, but not further
+TEST_F(LifetimeEvaluatorExactTest, Nested2LoopWithWriteAfterContinue)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_BGNLOOP }, // 0
+ { TGSI_OPCODE_BGNLOOP }, // 1
+ { TGSI_OPCODE_BGNLOOP }, // 2
+ { TGSI_OPCODE_IF, {}, {in0}, {}},
+ { TGSI_OPCODE_CONT},
+ { TGSI_OPCODE_ENDIF},
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}},
+ { TGSI_OPCODE_ENDLOOP },
+ { TGSI_OPCODE_ENDLOOP },
+ { TGSI_OPCODE_MOV, {out0}, {1}, {}}, // 9
+ { TGSI_OPCODE_ENDLOOP }, // 6
+ { TGSI_OPCODE_END}
+ };
+ run (code, expectation({{-1,-1},{1, 9}}));
+}
+
+TEST_F(LifetimeEvaluatorExactTest, LoopWithWriteInSwitch)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_BGNLOOP },
+ { TGSI_OPCODE_SWITCH, {}, {in0}, {} },
+ { TGSI_OPCODE_CASE, {}, {in0}, {} },
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}},
+ { TGSI_OPCODE_BRK },
+ { TGSI_OPCODE_DEFAULT },
+ { TGSI_OPCODE_BRK },
+ { TGSI_OPCODE_ENDSWITCH },
+ { TGSI_OPCODE_MOV, {out0}, {1}, {}},
+ { TGSI_OPCODE_ENDLOOP },
+ { TGSI_OPCODE_END}
+ };
+ run (code, expectation({{-1,-1},{0, 9}}));
+}
+
+// value written in one case, and read in other, in loop
+// - must survive the loop
+TEST_F(LifetimeEvaluatorExactTest, LoopWithReadWriteInSwitchDifferentCase)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_BGNLOOP }, // 0
+ { TGSI_OPCODE_SWITCH, {}, {in0}, {} }, // 0
+ { TGSI_OPCODE_CASE, {}, {in0}, {} }, // 0
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}},
+ { TGSI_OPCODE_BRK }, // 0
+ { TGSI_OPCODE_DEFAULT }, // 0
+ { TGSI_OPCODE_MOV, {out0}, {1}, {}},
+ { TGSI_OPCODE_BRK }, // 0
+ { TGSI_OPCODE_ENDSWITCH }, // 0
+ { TGSI_OPCODE_ENDLOOP }, // 6
+ { TGSI_OPCODE_END}
+ };
+ run (code, expectation({{-1,-1},{0, 9}}));
+}
+
+
+TEST_F(LifetimeEvaluatorExactTest, LoopRWInSwitchCaseLastCaseWithoutBreak)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_BGNLOOP }, // 0
+ { TGSI_OPCODE_SWITCH, {}, {in0}, {} }, // 0
+ { TGSI_OPCODE_CASE, {}, {in0}, {} }, // 0
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}},
+ { TGSI_OPCODE_BRK }, // 0
+ { TGSI_OPCODE_DEFAULT }, // 0
+ { TGSI_OPCODE_MOV, {out0}, {1}, {}},
+ { TGSI_OPCODE_ENDSWITCH }, // 0
+ { TGSI_OPCODE_ENDLOOP }, // 6
+ { TGSI_OPCODE_END}
+ };
+ run (code, expectation({{-1,-1},{0, 8}}));
+}
+
+
+// value read/write in same case, stays there
+
+
+TEST_F(LifetimeEvaluatorExactTest, LoopWithReadWriteInSwitchSameCase)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_BGNLOOP }, // 0
+ { TGSI_OPCODE_SWITCH, {}, {in0}, {} }, // 0
+ { TGSI_OPCODE_CASE, {}, {in0}, {} }, // 0
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}},
+ { TGSI_OPCODE_MOV, {out0}, {1}, {}},
+ { TGSI_OPCODE_BRK }, // 0
+ { TGSI_OPCODE_DEFAULT }, // 0
+ { TGSI_OPCODE_BRK }, // 0
+ { TGSI_OPCODE_ENDSWITCH }, // 0
+ { TGSI_OPCODE_ENDLOOP }, // 6
+ { TGSI_OPCODE_END}
+ };
+ run (code, expectation({{-1,-1},{3,4}}));
+}
+
+// value read/write in all cases, should only live from first
+// write to last read, but currently the whole loop is used.
+TEST_F(LifetimeEvaluatorAtLeastTest, LoopWithReadWriteInSwitchSameCase)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_BGNLOOP }, // 0
+ { TGSI_OPCODE_SWITCH, {}, {in0}, {}}, // 0
+ { TGSI_OPCODE_CASE, {}, {in0}, {} }, // 0
+ { TGSI_OPCODE_MOV, {}, {in0}, {}},
+ { TGSI_OPCODE_BRK }, // 0
+ { TGSI_OPCODE_DEFAULT }, // 0
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}},
+ { TGSI_OPCODE_BRK }, // 0
+ { TGSI_OPCODE_ENDSWITCH }, // 0
+ { TGSI_OPCODE_MOV, {out0}, {1}, {}},
+ { TGSI_OPCODE_ENDLOOP }, // 6
+ { TGSI_OPCODE_END}
+ };
+ run (code, expectation({{-1,-1},{3,9}}));
+}
+
+// value written in one case, and read in other, in loop, may fall through
+// - must survive the loop
+
+// value read/write in differnt loops
+TEST_F(LifetimeEvaluatorExactTest, LoopsWithDifferntScopes)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_BGNLOOP }, // 0
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}}, // 1
+ { TGSI_OPCODE_ENDLOOP }, // 2
+ { TGSI_OPCODE_BGNLOOP }, // 3
+ { TGSI_OPCODE_MOV, {out0}, {1}, {}}, // 4
+ { TGSI_OPCODE_ENDLOOP }, // 5
+ { TGSI_OPCODE_END}
+ };
+ run (code, expectation({{-1,-1},{1,5}}));
+}
+
+// value read/write in differnt loops
+TEST_F(LifetimeEvaluatorExactTest, LoopsWithDifferntScopesConditionalWrite)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_BGNLOOP }, // 0
+ { TGSI_OPCODE_IF, {}, {in0}, {}},
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}}, // 3
+ { TGSI_OPCODE_ENDIF}, // 1
+ { TGSI_OPCODE_ENDLOOP }, // 5
+ { TGSI_OPCODE_BGNLOOP }, // 6
+ { TGSI_OPCODE_MOV, {out0}, {1}, {}}, // 7
+ { TGSI_OPCODE_ENDLOOP }, // 5
+ { TGSI_OPCODE_END}
+ };
+ run (code, expectation({{-1,-1},{0,7}}));
+}
+
+// first read before first write wiredness with nested loops
+TEST_F(LifetimeEvaluatorExactTest, LoopsWithDifferntScopesConditionalReadBeforeWrite)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_BGNLOOP }, // 0
+ { TGSI_OPCODE_BGNLOOP }, // 1
+ { TGSI_OPCODE_IF, {}, {in0}, {}}, // 2
+ { TGSI_OPCODE_MOV, {out0}, {1}, {}}, // 3
+ { TGSI_OPCODE_ENDIF}, // 4
+ { TGSI_OPCODE_ENDLOOP }, // 5
+ { TGSI_OPCODE_BGNLOOP }, // 6
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}}, // 7
+ { TGSI_OPCODE_ENDLOOP }, // 8
+ { TGSI_OPCODE_ENDLOOP }, // 9
+ { TGSI_OPCODE_END}
+ };
+ run (code, expectation({{-1,-1},{0,9}}));
+}
+
+// register is only written. This should not happen,
+// but to handle the case we want the register to life
+// at least one instruction
+TEST_F(LifetimeEvaluatorExactTest, WriteOnly)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}}, // 3
+ { TGSI_OPCODE_END}
+ };
+ run (code, expectation({{-1,-1},{0,0}}));
+}
+
+// register read in if
+TEST_F(LifetimeEvaluatorExactTest, SimpleReadForIf)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}}, // 3
+ { TGSI_OPCODE_ADD, {out0}, {in0, in1}, {}}, // 3
+ { TGSI_OPCODE_IF, {}, {1}, {}},
+ { TGSI_OPCODE_ENDIF}
+ };
+ run (code, expectation({{-1,-1},{0,2}}));
+}
+
+// register read in switch
+TEST_F(LifetimeEvaluatorExactTest, SimpleReadForSwitchAndCase)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}}, // 3
+ { TGSI_OPCODE_SWITCH, {}, {1}, {}},
+ { TGSI_OPCODE_CASE, {}, {1}, {}},
+ { TGSI_OPCODE_CASE, {}, {1}, {}},
+ { TGSI_OPCODE_END, {}, {1}, {}},
+ };
+ run (code, expectation({{-1,-1},{0,3}}));
+}
+
+TEST_F(LifetimeEvaluatorExactTest, DistinceScopesAndNoEndProgramId)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}}, // 3
+ { TGSI_OPCODE_IF, {}, {1}, {}},
+ { TGSI_OPCODE_MOV, {2}, {1}, {}},
+ { TGSI_OPCODE_ENDIF},
+ { TGSI_OPCODE_IF, {}, {1}, {}},
+ { TGSI_OPCODE_MOV, {out0}, {2}, {}},
+ { TGSI_OPCODE_ENDIF},
+
+ };
+ run (code, expectation({{-1,-1},{0,4}, {2,5}}));
+}
+
+/* Check that two destination registers are used
+*/
+TEST_F(LifetimeEvaluatorExactTest, TwoDestRegisters)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_DFRACEXP , {1,2}, {in0}, {}}, // 3
+ { TGSI_OPCODE_ADD, {out0}, {1,2}, {}},
+ { TGSI_OPCODE_END}
+ };
+ run (code, expectation({{-1,-1},{0,1}, {0,1}}));
+}
+
+/* Check that two destination registers are used
+*/
+TEST_F(LifetimeEvaluatorExactTest, ThreeSourceRegisters)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_DFRACEXP , {1,2}, {in0}, {}}, // 3
+ { TGSI_OPCODE_ADD , {3}, {in0, in1}, {}}, // 3
+ { TGSI_OPCODE_MAD, {out0}, {1,2, 3}, {}},
+ { TGSI_OPCODE_END}
+ };
+ run (code, expectation({{-1,-1},{0,2}, {0,2}, {1,2}}));
+}
+
+/* Check that two destination registers are used
+*/
+TEST_F(LifetimeEvaluatorExactTest, OverwriteWrittenOnlyTemps)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_MOV , {1}, {in0}, {}}, // 3
+ { TGSI_OPCODE_MOV , {2}, {in1}, {}}, // 3
+ { TGSI_OPCODE_END}
+ };
+ run (code, expectation({{-1,-1},{0,0}, {1,1}}));
+}
+
+
+TEST(RegisterRemapping, RegisterRemapping)
+{
+ rename_reg_pair proto{false, 0};
+ vector<rename_reg_pair> result(7, proto);
+
+ vector<pair<int, int>> lt({{-1,-1},
+ {0, 1}, // 1
+ {0, 2}, // 2
+ {1, 2}, // 3
+ {2, 10}, // 4
+ {3, 5}, // 5
+ {5, 10} // 6
+ });
+
+
+
+ evaluate_remapping(lt, result);
+
+ vector<int> remap({0,1, 2, 3, 4, 5, 6});
+
+ std::transform(remap.begin(), remap.end(), result.begin(), remap.begin(),
+ [](int x, const rename_reg_pair& rn) {return rn.valid ? rn.new_reg : x;});
+
+ vector<int> expect({0, 1, 2, 1, 1, 2, 2});
+
+ for(unsigned i = 1; i < remap.size(); ++i) {
+ EXPECT_EQ(remap[i], expect[i]);
+ }
+
+}
+
+
+TEST(RegisterRemapping, RegisterRemapping2)
+{
+ rename_reg_pair proto{false, 0};
+ vector<rename_reg_pair> result(7, proto);
+
+ vector<pair<int, int>> lt({{-1,-1},
+ {0, 1}, // 1
+ {0, 2}, // 2
+ {3, 3}, // 3
+ {4, 4}, // 4
+ });
+
+
+
+ evaluate_remapping(lt, result);
+
+ vector<int> remap({0, 1, 2, 3, 4});
+
+ std::transform(remap.begin(), remap.end(), result.begin(), remap.begin(),
+ [](int x, const rename_reg_pair& rn) {return rn.valid ? rn.new_reg : x;});
+
+ vector<int> expect({0, 1, 2, 1, 1});
+
+ for(unsigned i = 1; i < remap.size(); ++i) {
+ EXPECT_EQ(remap[i], expect[i]);
+ }
+
+}
--
2.13.0
More information about the mesa-dev
mailing list