+
+ // propagate the first_write scope to the target_level
+ while (target_level < scopes[first_write_scope].level()) {
+
+ first_write_scope = scopes[first_write_scope].parent();
+
+ if (scopes[first_write_scope].loop_continue_line() < first_write) {
+ keep_for_full_loop = true;
+ }
+
+ // if the value is conditionally written in a loop
+ // then propagate its lifetime to the full loop
+ if (scopes[first_write_scope].type() == sct_loop) {
+ if (keep_for_full_loop || (undefined_read < first_write)) {
+ first_write = scopes[first_write_scope].begin();
+ int lr = scopes[first_write_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 &&
+ scopes[first_write_scope].is_conditional() &&
+ scopes[first_write_scope].in_loop()) {
+ keep_for_full_loop = true;
+ }
+ }
+
+
+ /* We do not correct the last_write for scope, but
+ * if it is past the last_read we have to keep the
+ * temporary alive past this instructions */
+ if (last_write > last_read) {
+ last_read = last_write + 1;
+ }
+
+ return make_pair(first_write, last_read);
+}
+
+vector<pair<int, int>>
+estimate_temporary_lifetimes(exec_list *instructions, int ntemps)
+{
+ return tgsi_temp_lifetime().get_lifetimes(instructions, ntemps);
+}
+
+void evaluate_remapping(const std::vector<std::pair<int, int>>& lifetimes,
+ struct rename_reg_pair *result)
+{
+ struct access_record {
+ int begin;
+ int end;
+ unsigned reg;
+ bool erase;
+ };
+
+ auto compare_begin = [](const access_record& a, const access_record& b) {
+ return a.begin < b.begin;
+ };
+ auto compare_end_begin = [](const access_record& a, const access_record& b)
{
+ return a.end <= b.begin;
+ };
+
+ vector<access_record> m(lifetimes.size() - 1);
+
+ for (unsigned i = 1; i < lifetimes.size(); ++i) {
+ m[i-1] = {lifetimes[i].first, lifetimes[i].second, i, false};
+ }
+
+ std::sort(m.begin(), m.end(), compare_begin);
+
+ auto trgt = m.begin();
+ auto mend = m.end();
+ auto first_erase = mend;
+ auto search_start = trgt + 1;
+
+ while (trgt != mend) {
+
+ auto src = std::upper_bound(search_start, mend, *trgt,
compare_end_begin);
+ if (src != mend) {
+ result[src->reg].new_reg = trgt->reg;
+ result[src->reg].valid = true;
+ trgt->end = src->end;
+
+ /* Since we only search forward, don't erase the renamed
+ * register just now, just mark it for removal. The alternative
+ * to call m.erase(src) here would be quite expensive. */
+ src->erase = true;
+ if (first_erase == mend)
+ first_erase = src;
+ search_start = src + 1;
+ } else {
+ /* Moving to the next target register it is time to
+ * erase the already merged registers */
+ if (first_erase != mend) {
+ auto out = first_erase;
+ auto in_start = first_erase + 1;
+ while (in_start != mend) {
+ if (!in_start->erase)
+ *out++ = *in_start;
+ ++in_start;
+ }
+ mend = out;
+ first_erase = mend;
+ }
+ ++trgt;
+ search_start = trgt + 1;
+ }
+ }
+}
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..04d5321682
--- /dev/null
+++ b/src/mesa/state_tracker/st_glsl_to_tgsi_temprename.h
@@ -0,0 +1,30 @@
+/*
+ * 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"
+
+std::vector<std::pair<int, int>>
+estimate_temporary_lifetimes(exec_list *instructions, int ntemps);
+
+void evaluate_remapping(const std::vector<std::pair<int, int>>& lt,
+ struct 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..ac6def682a
--- /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) \
+ -ldl
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..a2c59fb28f
--- /dev/null
+++ b/src/mesa/state_tracker/tests/test_glsl_to_tgsi_lifetime.cpp
@@ -0,0 +1,959 @@
+/*
+ * 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;
+
+
+/* A line to describe a TGSI instruction for building mock shaders */
+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;
+};
+
+/* A few constants to use in the mock shaders */
+const int in0 = 0;
+const int in1 = -1;
+const int in2 = -2;
+
+const int out0 = 0;
+const int out1 = -1;
+
+/* A class to create a shader program to check the register allocation
+ * and renaming. The created exec_list is not completely set up and can
+ * only be used for the register tife-time analyis. */
+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;
+};
+
+/* type for register lifetime expectation */
+using expectation = vector<vector<int>>;
+
+
+/* This is a teat class to check the exact life times of
+ * registers. */
+class LifetimeEvaluatorExactTest : public testing::Test {
+protected:
+ void run(const vector<MockCodeline>& code, const expectation& e);
+};
+
+/* This test class checks that the life time covers at least
+ * in the expected range. It is used for cases where we know that
+ * a the implementation could be improved on estimating the minimal
+ * life time.
+ */
+class LifetimeEvaluatorAtLeastTest : public testing::Test {
+protected:
+ void run(const vector<MockCodeline>& code, const expectation& e);
+};
+
+
+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}, {}},
+ { TGSI_OPCODE_BGNLOOP },
+ { TGSI_OPCODE_UADD, {2}, {1, in0}, {}},
+ { TGSI_OPCODE_UADD, {3}, {1, 2}, {}},
+ { TGSI_OPCODE_UADD, {3}, {3, in1}, {}},
+ { TGSI_OPCODE_ENDLOOP },
+ { TGSI_OPCODE_MOV, {out0}, {3}, {}},
+ { 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}, {}},
+ { TGSI_OPCODE_BGNLOOP },
+ { TGSI_OPCODE_IF},
+ { TGSI_OPCODE_UADD, {2}, {1, in0}, {}},
+ { TGSI_OPCODE_ENDIF},
+ { TGSI_OPCODE_UADD, {3}, {1, 2}, {}},
+ { TGSI_OPCODE_UADD, {3}, {3, in1}, {}},
+ { TGSI_OPCODE_ENDLOOP },
+ { TGSI_OPCODE_MOV, {out0}, {3}, {}},
+ { 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}, {}},
+ { TGSI_OPCODE_BGNLOOP },
+ { TGSI_OPCODE_IF},
+ { TGSI_OPCODE_UADD, {2}, {1, in0}, {}},
+ { TGSI_OPCODE_ELSE },
+ { TGSI_OPCODE_MOV, {2}, {1}, {}},
+ { TGSI_OPCODE_ENDIF},
+ { TGSI_OPCODE_UADD, {3}, {1, 2}, {}},
+ { TGSI_OPCODE_UADD, {3}, {3, in1}, {}},
+ { TGSI_OPCODE_ENDLOOP },
+ { TGSI_OPCODE_MOV, {out0}, {3}, {}},
+ { 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}, {}},
+ { TGSI_OPCODE_BGNLOOP },
+ { TGSI_OPCODE_IF},
+ { TGSI_OPCODE_UADD, {2}, {1, in0}, {}},
+ { TGSI_OPCODE_ELSE },
+ { TGSI_OPCODE_ADD, {2}, {1, 2}, {}},
+ { TGSI_OPCODE_ENDIF},
+ { TGSI_OPCODE_UADD, {3}, {1, 2}, {}},
+ { TGSI_OPCODE_UADD, {3}, {3, in1}, {}},
+ { TGSI_OPCODE_ENDLOOP },
+ { TGSI_OPCODE_MOV, {out0}, {3}, {}},
+ { 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}, {}},
+ { TGSI_OPCODE_BGNLOOP },
+ { TGSI_OPCODE_IF, {}, {in0}, {}},
+ { TGSI_OPCODE_UADD, {2}, {1, 3}, {}},
+ { TGSI_OPCODE_ENDIF},
+ { TGSI_OPCODE_UADD, {3}, {1, 2}, {}},
+ { TGSI_OPCODE_UADD, {3}, {3, in1}, {}},
+ { TGSI_OPCODE_ENDLOOP },
+ { TGSI_OPCODE_MOV, {out0}, {3}, {}},
+ { 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 },
+ { TGSI_OPCODE_IF, {}, {in0}, {}},
+ { TGSI_OPCODE_IF, {}, {in0}, {}},
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}},
+ { TGSI_OPCODE_ELSE},
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}},
+ { TGSI_OPCODE_ENDIF},
+ { TGSI_OPCODE_ELSE},
+ { TGSI_OPCODE_IF, {}, {in0}, {}},
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}},
+ { TGSI_OPCODE_ELSE},
+ { 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 },
+ { TGSI_OPCODE_IF, {}, {in0}, {}},
+ { TGSI_OPCODE_IF, {}, {in0}, {}},
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}},
+ { TGSI_OPCODE_ELSE},
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}},
+ { TGSI_OPCODE_ENDIF},
+ { TGSI_OPCODE_ELSE},
+ { TGSI_OPCODE_IF, {}, {in0}, {}},
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}},
+ { TGSI_OPCODE_ENDIF},
+ { 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 },
+ { TGSI_OPCODE_IF, {}, {in0}, {}},
+ { TGSI_OPCODE_CONT},
+ { TGSI_OPCODE_ENDIF},
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}},
+ { TGSI_OPCODE_ENDLOOP },
+ { TGSI_OPCODE_MOV, {out0}, {1}, {}},
+ { 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 outer loop */
+TEST_F(LifetimeEvaluatorExactTest, NestedLoopWithWriteAfterContinue)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_BGNLOOP },
+ { TGSI_OPCODE_BGNLOOP },
+ { TGSI_OPCODE_IF, {}, {in0}, {}},
+ { TGSI_OPCODE_CONT},
+ { TGSI_OPCODE_ENDIF},
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}},
+ { TGSI_OPCODE_ENDLOOP },
+ { TGSI_OPCODE_MOV, {out0}, {1}, {}},
+ { TGSI_OPCODE_ENDLOOP },
+ { TGSI_OPCODE_END}
+ };
+ run (code, expectation({{-1,-1},{0, 8}}));
+}
+
+/* Test whether variable is kept also if the continue is in a
+ * higher scope than the variable write */
+TEST_F(LifetimeEvaluatorExactTest, NestedLoopWithWriteInLoopAfterContinue)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_BGNLOOP },
+ { TGSI_OPCODE_BGNLOOP },
+ { TGSI_OPCODE_IF, {}, {in0}, {}},
+ { TGSI_OPCODE_CONT},
+ { TGSI_OPCODE_ENDIF},
+ { TGSI_OPCODE_BGNLOOP },
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}},
+ { TGSI_OPCODE_ENDLOOP },
+ { TGSI_OPCODE_ENDLOOP },
+ { TGSI_OPCODE_MOV, {out0}, {1}, {}},
+ { TGSI_OPCODE_ENDLOOP },
+ { TGSI_OPCODE_END}
+ };
+ run (code, expectation({{-1,-1},{0, 10}}));
+}
+
+/* if a continue is in the loop, all variables written after the
+ * continue and used outside the loop must be maintained for all
+ * loops including the read loop */
+TEST_F(LifetimeEvaluatorExactTest, Nested2LoopWithWriteAfterContinue)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_BGNLOOP },
+ { TGSI_OPCODE_BGNLOOP },
+ { TGSI_OPCODE_BGNLOOP },
+ { 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}, {}},
+ { TGSI_OPCODE_ENDLOOP },
+ { TGSI_OPCODE_END}
+ };
+ run (code, expectation({{-1,-1},{0, 10}}));
+}
+
+/* if a break is in the loop, all variables written after the
+ * break and used outside the loop must be maintained for the
+ * whole loop */
+TEST_F(LifetimeEvaluatorExactTest, LoopWithWriteAfterBreak)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_BGNLOOP },
+ { TGSI_OPCODE_IF, {}, {in0}, {}},
+ { TGSI_OPCODE_BRK},
+ { TGSI_OPCODE_ENDIF},
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}},
+ { TGSI_OPCODE_ENDLOOP },
+ { TGSI_OPCODE_MOV, {out0}, {1}, {}},
+ { TGSI_OPCODE_END}
+ };
+ run (code, expectation({{-1,-1},{0, 6}}));
+}
+
+/* if a break is in the loop, but inside a switch case, so it
+ * referes to the case and not to the loop, the variable doesn't
+ * need to survive the loop */
+TEST_F(LifetimeEvaluatorExactTest, LoopWithWriteAfterBreakInSwitch)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_BGNLOOP },
+ { TGSI_OPCODE_SWITCH, {}, {in1}, {}},
+ { TGSI_OPCODE_CASE, {}, {in1}, {}},
+ { TGSI_OPCODE_IF, {}, {in0}, {}},
+ { TGSI_OPCODE_BRK},
+ { TGSI_OPCODE_ENDIF},
+ { TGSI_OPCODE_DEFAULT},
+ { TGSI_OPCODE_ENDSWITCH},
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}},
+ { TGSI_OPCODE_ENDLOOP },
+ { TGSI_OPCODE_MOV, {out0}, {1}, {}},
+ { TGSI_OPCODE_END}
+ };
+ run (code, expectation({{-1,-1},{8, 10}}));
+}
+
+/* if a break is in the loop, but inside a switch case, so it
+ * referes to that inner loop. The variable has to survive the loop */
+TEST_F(LifetimeEvaluatorExactTest, LoopWithWriteAfterBreakInSwitchInLoop)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_SWITCH, {}, {in1}, {}},
+ { TGSI_OPCODE_CASE, {}, {in1}, {}},
+ { TGSI_OPCODE_BGNLOOP },
+ { TGSI_OPCODE_IF, {}, {in0}, {}},
+ { TGSI_OPCODE_BRK},
+ { TGSI_OPCODE_ENDIF},
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}},
+ { TGSI_OPCODE_ENDLOOP },
+ { TGSI_OPCODE_DEFAULT, {}, {}, {}},
+ { TGSI_OPCODE_ENDSWITCH, {}, {}, {}},
+ { TGSI_OPCODE_MOV, {out0}, {1}, {}},
+ { TGSI_OPCODE_END}
+ };
+ run (code, expectation({{-1,-1},{2, 10}}));
+}
+
+
+/* if a break is in the loop, all variables written after the
+ * break and used outside the loop must be maintained for the
+ * whole loop that includes the read */
+TEST_F(LifetimeEvaluatorExactTest, NestedLoopWithWriteAfterBreak)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_BGNLOOP },
+ { TGSI_OPCODE_BGNLOOP },
+ { TGSI_OPCODE_IF, {}, {in0}, {}},
+ { TGSI_OPCODE_BRK},
+ { TGSI_OPCODE_ENDIF},
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}},
+ { TGSI_OPCODE_ENDLOOP },
+ { TGSI_OPCODE_MOV, {out0}, {1}, {}},
+ { TGSI_OPCODE_ENDLOOP },
+ { TGSI_OPCODE_END}
+ };
+ run (code, expectation({{-1,-1},{0, 8}}));
+}
+
+/* if a break is in the loop, all variables written after the
+ * break and used outside the loop must be maintained for all
+ * loops up onto the read scope */
+TEST_F(LifetimeEvaluatorExactTest, Nested2LoopWithWriteAfterBreak)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_BGNLOOP },
+ { TGSI_OPCODE_BGNLOOP },
+ { TGSI_OPCODE_BGNLOOP },
+ { TGSI_OPCODE_BGNLOOP },
+ { TGSI_OPCODE_IF, {}, {in0}, {}},
+ { TGSI_OPCODE_BRK},
+ { TGSI_OPCODE_ENDIF},
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}},
+ { TGSI_OPCODE_ENDLOOP },
+ { TGSI_OPCODE_ENDLOOP },
+ { TGSI_OPCODE_MOV, {out0}, {1}, {}},
+ { TGSI_OPCODE_ENDLOOP },
+ { TGSI_OPCODE_ENDLOOP },
+ { TGSI_OPCODE_END}
+ };
+ run (code, expectation({{-1,-1},{1, 11}}));
+}
+
+/* Temporary used to switch must live through all case statememts */
+TEST_F(LifetimeEvaluatorExactTest, UseSwitchCase)
+{
+ const vector<MockCodeline> code = {
+ {TGSI_OPCODE_MOV, {1}, {in0}, {}},
+ {TGSI_OPCODE_SWITCH, {}, {1}, {}},
+ { TGSI_OPCODE_CASE, {}, {1}, {}},
+ { TGSI_OPCODE_CASE, {}, {1}, {}},
+ { TGSI_OPCODE_BRK},
+ { TGSI_OPCODE_DEFAULT},
+ {TGSI_OPCODE_ENDSWITCH},
+ { TGSI_OPCODE_END}
+ };
+ run (code, expectation({{-1,-1},{0, 3}}));
+}
+
+/* variable written in a switch within a loop must survive the loop */
+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 },
+ { TGSI_OPCODE_SWITCH, {}, {in0}, {}},
+ { TGSI_OPCODE_CASE, {}, {in0}, {}},
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}},
+ { TGSI_OPCODE_BRK },
+ { TGSI_OPCODE_DEFAULT },
+ { TGSI_OPCODE_MOV, {out0}, {1}, {}},
+ { TGSI_OPCODE_BRK },
+ { TGSI_OPCODE_ENDSWITCH },
+ { TGSI_OPCODE_ENDLOOP },
+ { TGSI_OPCODE_END}
+ };
+ run (code, expectation({{-1,-1},{0, 9}}));
+}
+
+/* Make sure SWITCH is closed correctly in the scope stack */
+TEST_F(LifetimeEvaluatorExactTest, LoopRWInSwitchCaseLastCaseWithoutBreak)
+{
+ 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_MOV, {out0}, {1}, {}},
+ { TGSI_OPCODE_ENDSWITCH },
+ { TGSI_OPCODE_ENDLOOP },
+ { 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 },
+ { TGSI_OPCODE_SWITCH, {}, {in0}, {}},
+ { TGSI_OPCODE_CASE, {}, {in0}, {}},
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}},
+ { TGSI_OPCODE_MOV, {out0}, {1}, {}},
+ { TGSI_OPCODE_BRK },
+ { TGSI_OPCODE_DEFAULT },
+ { TGSI_OPCODE_BRK },
+ { TGSI_OPCODE_ENDSWITCH },
+ { TGSI_OPCODE_ENDLOOP },
+ { 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 },
+ { TGSI_OPCODE_SWITCH, {}, {in0}, {}},
+ { TGSI_OPCODE_CASE, {}, {in0}, {}},
+ { TGSI_OPCODE_MOV, {}, {in0}, {}},
+ { TGSI_OPCODE_BRK },
+ { TGSI_OPCODE_DEFAULT },
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}},
+ { TGSI_OPCODE_BRK },
+ { TGSI_OPCODE_ENDSWITCH },
+ { TGSI_OPCODE_MOV, {out0}, {1}, {}},
+ { TGSI_OPCODE_ENDLOOP },
+ { TGSI_OPCODE_END}
+ };
+ run (code, expectation({{-1,-1},{3,9}}));
+}
+
+
+/* value read/write in differnt loops */
+TEST_F(LifetimeEvaluatorExactTest, LoopsWithDifferntScopes)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_BGNLOOP },
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}},
+ { TGSI_OPCODE_ENDLOOP },
+ { TGSI_OPCODE_BGNLOOP },
+ { TGSI_OPCODE_MOV, {out0}, {1}, {}},
+ { TGSI_OPCODE_ENDLOOP },
+ { TGSI_OPCODE_END}
+ };
+ run (code, expectation({{-1,-1},{1,5}}));
+}
+
+/* value read/write in differnt loops, conditional */
+TEST_F(LifetimeEvaluatorExactTest, LoopsWithDifferntScopesConditionalWrite)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_BGNLOOP },
+ { TGSI_OPCODE_IF, {}, {in0}, {}},
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}},
+ { TGSI_OPCODE_ENDIF},
+ { TGSI_OPCODE_ENDLOOP },
+ { TGSI_OPCODE_BGNLOOP },
+ { TGSI_OPCODE_MOV, {out0}, {1}, {}},
+ { TGSI_OPCODE_ENDLOOP },
+ { TGSI_OPCODE_END}
+ };
+ run (code, expectation({{-1,-1},{0,7}}));
+}
+
+/* first read before first write wiredness with nested loops */
+TEST_F(LifetimeEvaluatorExactTest, LoopsWithDifferentScopesCondReadBeforeWrite)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_BGNLOOP },
+ { TGSI_OPCODE_BGNLOOP },
+ { TGSI_OPCODE_IF, {}, {in0}, {}},
+ { TGSI_OPCODE_MOV, {out0}, {1}, {}},
+ { TGSI_OPCODE_ENDIF},
+ { TGSI_OPCODE_ENDLOOP },
+ { TGSI_OPCODE_BGNLOOP },
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}},
+ { TGSI_OPCODE_ENDLOOP },
+ { TGSI_OPCODE_ENDLOOP },
+ { 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 past the write instruction */
+TEST_F(LifetimeEvaluatorExactTest, WriteOnly)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}},
+ { TGSI_OPCODE_END}
+ };
+ run (code, expectation({{-1,-1},{0,1}}));
+}
+
+/* register read in if */
+TEST_F(LifetimeEvaluatorExactTest, SimpleReadForIf)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}},
+ { TGSI_OPCODE_ADD, {out0}, {in0, in1}, {}},
+ { 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}, {}},
+ { TGSI_OPCODE_SWITCH, {}, {1}, {}},
+ { TGSI_OPCODE_CASE, {}, {1}, {}},
+ { TGSI_OPCODE_CASE, {}, {1}, {}},
+ { TGSI_OPCODE_ENDSWITCH},
+ };
+ run (code, expectation({{-1,-1},{0,3}}));
+}
+
+/* Check that a missing END is handled (Unigine-Haven creates such a
+ * shader) */
+TEST_F(LifetimeEvaluatorExactTest, DistinceScopesAndNoEndProgramId)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}},
+ { 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}}));
+}
+
+/* Dead code elimination should catch and remove the case
+ * when a variable is written after its last read, but
+ * we want the code to be aware of this case.
+ * The life time of this uselessly written variable is set
+ * to the instruction after the write, because
+ * otherwise it could be re-used too early.
+*/
+TEST_F(LifetimeEvaluatorExactTest, WritePastLastRead)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}},
+ { TGSI_OPCODE_MOV, {2}, {1}, {}},
+ { TGSI_OPCODE_MOV, {1}, {2}, {}},
+ { TGSI_OPCODE_END},
+
+ };
+ run (code, expectation({{-1,-1},{0,3}, {1,2}}));
+}
+
+/*
+ * Somehow a duplicate of above tests specifically using the
+ * problematic corner case n question. DFRACEXP has two
+ * destinations, and if one value is thrown away, we must ensure
+ * that the two output registers don't merge.
+ * In this test case the last access for 2 and 3 is in line 4,
+ * but only 3 can be merged with 4 because it is read, 2 on the
+ * other hand is written to, and merging it with 4 would result in
+ * undefined behaviour.
+*/
+TEST_F(LifetimeEvaluatorExactTest, WritePastLastRead2)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_MOV, {1}, {in0}, {}},
+ { TGSI_OPCODE_MOV, {2}, {in0}, {}},
+ { TGSI_OPCODE_ADD, {3}, {1,2}, {}},
+ { TGSI_OPCODE_DFRACEXP , {2,4}, {3}, {}},
+ { TGSI_OPCODE_MOV, {out1}, {4}, {}},
+ { TGSI_OPCODE_END},
+
+ };
+ run (code, expectation({{-1,-1},{0,2}, {1,4}, {2,3}, {3,4}}));
+}
+
+
+TEST_F(LifetimeEvaluatorExactTest, OnlyWriteOne)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_DFRACEXP , {1, 2}, {in0}, {}},
+ { TGSI_OPCODE_ADD , {3}, {2, in0}, {}},
+ { TGSI_OPCODE_MOV, {out1}, {3}, {}},
+ { TGSI_OPCODE_END},
+
+ };
+ run (code, expectation({{-1,-1},{0,1}, {0,1}, {1,2}}));
+}
+
+
+/* Check that two destination registers are actually used */
+TEST_F(LifetimeEvaluatorExactTest, TwoDestRegisters)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_DFRACEXP , {1,2}, {in0}, {}},
+ { TGSI_OPCODE_ADD, {out0}, {1,2}, {}},
+ { TGSI_OPCODE_END}
+ };
+ run (code, expectation({{-1,-1},{0,1}, {0,1}}));
+}
+
+/* Check that two destination registers and three source registers
+ * are used */
+TEST_F(LifetimeEvaluatorExactTest, ThreeSourceRegisters)
+{
+ const vector<MockCodeline> code = {
+ { TGSI_OPCODE_DFRACEXP , {1,2}, {in0}, {}},
+ { TGSI_OPCODE_ADD , {3}, {in0, in1}, {}},
+ { TGSI_OPCODE_MAD, {out0}, {1,2, 3}, {}},
+ { TGSI_OPCODE_END}
+ };
+ run (code, expectation({{-1,-1},{0,2}, {0,2}, {1,2}}));
+}
+
+
+class RegisterRemapping : public testing::Test {
+protected:
+ void run(const vector<pair<int, int>>& lt, const vector<int>& expect);
+};
+
+void RegisterRemapping::run(const vector<pair<int, int>>& lt,
+ const vector<int>& expect)
+{
+ rename_reg_pair proto{false, 0};
+ vector<rename_reg_pair> result(lt.size(), proto);
+
+ evaluate_remapping(lt, &result[0]);
+
+ vector<int> remap(lt.size());
+ for (unsigned i = 0; i < lt.size(); ++i) {
+ remap[i] = result[i].valid ? result[i].new_reg : i;
+ }
+
+ std::transform(remap.begin(), remap.end(), result.begin(), remap.begin(),
+ [](int x, const rename_reg_pair& rn) {
+ return rn.valid ? rn.new_reg : x;
+ });
+
+ for(unsigned i = 1; i < remap.size(); ++i)
+ EXPECT_EQ(remap[i], expect[i]);
+}
+
+TEST_F(RegisterRemapping, RegisterRemapping1)
+{
+ vector<pair<int, int>> lt({{-1,-1},
+ {0, 1},
+ {0, 2},
+ {1, 2},
+ {2, 10},
+ {3, 5},
+ {5, 10}
+ });
+
+ vector<int> expect({0, 1, 2, 1, 1, 2, 2});
+ run(lt, expect);
+}
+
+
+TEST_F(RegisterRemapping, RegisterRemapping2)
+{
+ vector<pair<int, int>> lt({{-1,-1},
+ {0, 1},
+ {0, 2},
+ {3, 3},
+ {4, 4},
+ });
+ vector<int> expect({0, 1, 2, 1, 1});
+ run(lt, expect);
+}
+
+
+
+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();
+ if (i.tex_offsets.size() > 0)
+ next_instr->tex_offsets = new st_src_reg[i.tex_offsets.size()];
+ else
+ next_instr->tex_offsets = 0;
+ 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);
+}
+
+void LifetimeEvaluatorExactTest::run(const vector<MockCodeline>& code, const
expectation& e)
+{
+ MockShader shader(code);
+
+ auto lifetimes = estimate_temporary_lifetimes(shader.get_program(),
shader.get_num_temps());
+
+ // 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);
+
+ auto lifetimes = estimate_temporary_lifetimes(shader.get_program(),
shader.get_num_temps());
+
+ // 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]);
+ }
+}