aboutsummaryrefslogtreecommitdiffstats
path: root/src/gallium/drivers
diff options
context:
space:
mode:
authorGert Wollny <[email protected]>2019-12-27 17:49:26 +0100
committerMarge Bot <[email protected]>2020-02-10 19:09:08 +0000
commit393655d5cb2ae499783408d36a96e34257473fcf (patch)
tree264e3f57cd41fe48cad49ee306137ccd8ff72730 /src/gallium/drivers
parent24f683fe810904ae7355ddb036e1e4f37f1480c4 (diff)
r600/sfn: add live range evaluation for the GPR
The algoritm is basically a copy of the TGSI implementation without the array bits. Signed-off-by: Gert Wollny <[email protected]> Part-of: <https://gitlab.freedesktop.org/mesa/mesa/merge_requests/3225>
Diffstat (limited to 'src/gallium/drivers')
-rw-r--r--src/gallium/drivers/r600/Makefile.sources2
-rw-r--r--src/gallium/drivers/r600/meson.build2
-rw-r--r--src/gallium/drivers/r600/sfn/sfn_instruction_base.cpp101
-rw-r--r--src/gallium/drivers/r600/sfn/sfn_instruction_base.h7
-rw-r--r--src/gallium/drivers/r600/sfn/sfn_instruction_cf.cpp31
-rw-r--r--src/gallium/drivers/r600/sfn/sfn_instruction_cf.h6
-rw-r--r--src/gallium/drivers/r600/sfn/sfn_instruction_export.cpp1
-rw-r--r--src/gallium/drivers/r600/sfn/sfn_liverange.cpp987
-rw-r--r--src/gallium/drivers/r600/sfn/sfn_liverange.h314
-rw-r--r--src/gallium/drivers/r600/sfn/sfn_nir.cpp4
-rw-r--r--src/gallium/drivers/r600/sfn/sfn_shader_base.cpp75
-rw-r--r--src/gallium/drivers/r600/sfn/sfn_value_gpr.cpp35
-rw-r--r--src/gallium/drivers/r600/sfn/sfn_value_gpr.h7
13 files changed, 1571 insertions, 1 deletions
diff --git a/src/gallium/drivers/r600/Makefile.sources b/src/gallium/drivers/r600/Makefile.sources
index 8032e984229..f16bce16113 100644
--- a/src/gallium/drivers/r600/Makefile.sources
+++ b/src/gallium/drivers/r600/Makefile.sources
@@ -120,6 +120,8 @@ CXX_SOURCES = \
sfn/sfn_instruction_tex.h \
sfn/sfn_ir_to_assembly.cpp \
sfn/sfn_ir_to_assembly.h \
+ sfn/sfn_liverange.cpp \
+ sfn/sfn_liverange.h \
sfn/sfn_nir.cpp \
sfn/sfn_nir.h \
sfn/sfn_nir_lower_fs_out_to_vector.cpp \
diff --git a/src/gallium/drivers/r600/meson.build b/src/gallium/drivers/r600/meson.build
index 6df33788691..bfc17dae153 100644
--- a/src/gallium/drivers/r600/meson.build
+++ b/src/gallium/drivers/r600/meson.build
@@ -137,6 +137,8 @@ files_r600 = files(
'sfn/sfn_instruction_tex.h',
'sfn/sfn_ir_to_assembly.cpp',
'sfn/sfn_ir_to_assembly.h',
+ 'sfn/sfn_liverange.cpp',
+ 'sfn/sfn_liverange.h',
'sfn/sfn_nir.cpp',
'sfn/sfn_nir.h',
'sfn/sfn_nir_lower_fs_out_to_vector.cpp',
diff --git a/src/gallium/drivers/r600/sfn/sfn_instruction_base.cpp b/src/gallium/drivers/r600/sfn/sfn_instruction_base.cpp
index 6930747550f..4e4e28f1436 100644
--- a/src/gallium/drivers/r600/sfn/sfn_instruction_base.cpp
+++ b/src/gallium/drivers/r600/sfn/sfn_instruction_base.cpp
@@ -29,11 +29,62 @@
#include <cassert>
#include "sfn_instruction_base.h"
+#include "sfn_liverange.h"
#include "sfn_valuepool.h"
-#include "sfn_debug.h"
namespace r600 {
+ValueRemapper::ValueRemapper(std::vector<rename_reg_pair>& m,
+ ValueMap& values):
+ m_map(m),
+ m_values(values)
+{
+}
+
+void ValueRemapper::remap(PValue& v)
+{
+ if (!v)
+ return;
+ if (v->type() == Value::gpr) {
+ v = remap_one_registers(v);
+ } else if (v->type() == Value::gpr_array_value) {
+ GPRArrayValue& val = static_cast<GPRArrayValue&>(*v);
+ auto value = val.value();
+ auto addr = val.indirect();
+ val.reset_value(remap_one_registers(value));
+ if (addr) {
+ if (addr->type() == Value::gpr)
+ val.reset_addr(remap_one_registers(addr));
+ }
+ size_t range_start = val.sel();
+ size_t range_end = range_start + val.array_size();
+ while (range_start < range_end)
+ m_map[range_start++].used = true;
+ }
+}
+
+void ValueRemapper::remap(GPRVector& v)
+{
+ for (int i = 0; i < 4; ++i) {
+ if (v.reg_i(i)) {
+ auto& ns_idx = m_map[v.reg_i(i)->sel()];
+ if (ns_idx.valid)
+ v.set_reg_i(i,m_values.get_or_inject(ns_idx.new_reg, v.reg_i(i)->chan()));
+ m_map[v.reg_i(i)->sel()].used = true;
+ }
+ }
+}
+
+PValue ValueRemapper::remap_one_registers(PValue& reg)
+{
+ auto new_index = m_map[reg->sel()];
+ if (new_index.valid)
+ reg = m_values.get_or_inject(new_index.new_reg, reg->chan());
+ m_map[reg->sel()].used = true;
+ return reg;
+}
+
+
Instruction::Instruction(instr_type t):
m_type(t)
{
@@ -49,6 +100,54 @@ void Instruction::print(std::ostream& os) const
do_print(os);
}
+
+void Instruction::remap_registers(ValueRemapper& map)
+{
+ sfn_log << SfnLog::merge << "REMAP " << *this << "\n";
+ for (auto& v: m_mappable_src_registers)
+ map.remap(*v);
+
+ for (auto& v: m_mappable_src_vectors)
+ map.remap(*v);
+
+ for (auto& v: m_mappable_dst_registers)
+ map.remap(*v);
+
+ for (auto& v: m_mappable_dst_vectors)
+ map.remap(*v);
+ sfn_log << SfnLog::merge << "TO " << *this << "\n\n";
+}
+
+void Instruction::replace_values(UNUSED const ValueSet& candiates, UNUSED PValue new_value)
+{
+
+}
+
+void Instruction::evalue_liveness(LiverangeEvaluator& eval) const
+{
+ sfn_log << SfnLog::merge << "Scan " << *this << "\n";
+ for (const auto& s: m_mappable_src_registers)
+ if (*s)
+ eval.record_read(**s);
+
+ for (const auto& s: m_mappable_src_vectors)
+ eval.record_read(*s);
+
+ for (const auto& s: m_mappable_dst_registers)
+ if (*s)
+ eval.record_write(**s);
+
+ for (const auto& s: m_mappable_dst_vectors)
+ eval.record_write(*s);
+
+ do_evalue_liveness(eval);
+}
+
+void Instruction::do_evalue_liveness(UNUSED LiverangeEvaluator& eval) const
+{
+
+}
+
bool operator == (const Instruction& lhs, const Instruction& rhs)
{
if (rhs.m_type != lhs.m_type)
diff --git a/src/gallium/drivers/r600/sfn/sfn_instruction_base.h b/src/gallium/drivers/r600/sfn/sfn_instruction_base.h
index fe481fbabfb..e44154ebe91 100644
--- a/src/gallium/drivers/r600/sfn/sfn_instruction_base.h
+++ b/src/gallium/drivers/r600/sfn/sfn_instruction_base.h
@@ -39,6 +39,8 @@
namespace r600 {
+
+class LiverangeEvaluator;
using OutputRegisterMap = std::map<unsigned, const GPRVector *>;
class Instruction {
@@ -78,7 +80,12 @@ public:
void print(std::ostream& os) const;
+ void evalue_liveness(LiverangeEvaluator& eval) const;
+
private:
+
+ virtual void do_evalue_liveness(LiverangeEvaluator& eval) const;
+
virtual bool is_equal_to(const Instruction& lhs) const = 0;
instr_type m_type;
diff --git a/src/gallium/drivers/r600/sfn/sfn_instruction_cf.cpp b/src/gallium/drivers/r600/sfn/sfn_instruction_cf.cpp
index 7dde127cd20..82b0f47403a 100644
--- a/src/gallium/drivers/r600/sfn/sfn_instruction_cf.cpp
+++ b/src/gallium/drivers/r600/sfn/sfn_instruction_cf.cpp
@@ -25,6 +25,7 @@
*/
#include "sfn_instruction_cf.h"
+#include "sfn_liverange.h"
namespace r600 {
@@ -46,6 +47,11 @@ IfInstruction::IfInstruction(AluInstruction *pred):
PValue *v = m_pred->psrc(0);
}
+void IfInstruction::do_evalue_liveness(LiverangeEvaluator& eval) const
+{
+ eval.scope_if();
+}
+
bool IfInstruction::is_equal_to(const Instruction& lhs) const
{
assert(lhs.type() == cond_if);
@@ -65,6 +71,11 @@ ElseInstruction::ElseInstruction(IfInstruction *jump_src):
{
}
+void ElseInstruction::do_evalue_liveness(LiverangeEvaluator& eval) const
+{
+ eval.scope_else();
+}
+
bool ElseInstruction::is_equal_to(const Instruction& lhs) const
{
@@ -84,6 +95,11 @@ IfElseEndInstruction::IfElseEndInstruction():
{
}
+void IfElseEndInstruction::do_evalue_liveness(LiverangeEvaluator& eval) const
+{
+ eval.scope_endif();
+}
+
bool IfElseEndInstruction::is_equal_to(const Instruction& lhs) const
{
if (lhs.type() != cond_endif)
@@ -101,6 +117,11 @@ LoopBeginInstruction::LoopBeginInstruction():
{
}
+void LoopBeginInstruction::do_evalue_liveness(LiverangeEvaluator& eval) const
+{
+ eval.scope_loop_begin();
+}
+
bool LoopBeginInstruction::is_equal_to(const Instruction& lhs) const
{
assert(lhs.type() == loop_begin);
@@ -118,6 +139,11 @@ LoopEndInstruction::LoopEndInstruction(LoopBeginInstruction *start):
{
}
+void LoopEndInstruction::do_evalue_liveness(LiverangeEvaluator& eval) const
+{
+ eval.scope_loop_end();
+}
+
bool LoopEndInstruction::is_equal_to(const Instruction& lhs) const
{
assert(lhs.type() == loop_end);
@@ -135,6 +161,11 @@ LoopBreakInstruction::LoopBreakInstruction():
{
}
+void LoopBreakInstruction::do_evalue_liveness(LiverangeEvaluator& eval) const
+{
+ eval.scope_loop_break();
+}
+
bool LoopBreakInstruction::is_equal_to(UNUSED const Instruction& lhs) const
{
return true;
diff --git a/src/gallium/drivers/r600/sfn/sfn_instruction_cf.h b/src/gallium/drivers/r600/sfn/sfn_instruction_cf.h
index abf84f0eb18..10da90f7cbd 100644
--- a/src/gallium/drivers/r600/sfn/sfn_instruction_cf.h
+++ b/src/gallium/drivers/r600/sfn/sfn_instruction_cf.h
@@ -46,6 +46,7 @@ public:
IfInstruction(AluInstruction *pred);
const AluInstruction& pred() const {return *m_pred;}
private:
+ void do_evalue_liveness(LiverangeEvaluator& eval) const override;
bool is_equal_to(const Instruction& lhs) const override;
void do_print(std::ostream& os) const override;
std::shared_ptr<AluInstruction> m_pred;
@@ -55,6 +56,7 @@ class ElseInstruction : public IfElseInstruction {
public:
ElseInstruction(IfInstruction *jump_src);
private:
+ void do_evalue_liveness(LiverangeEvaluator& eval) const override;
bool is_equal_to(const Instruction& lhs) const override;
void do_print(std::ostream& os) const override;
@@ -65,6 +67,7 @@ class IfElseEndInstruction : public IfElseInstruction {
public:
IfElseEndInstruction();
private:
+ void do_evalue_liveness(LiverangeEvaluator& eval) const override;
bool is_equal_to(const Instruction& lhs) const override;
void do_print(std::ostream& os) const override;
};
@@ -73,6 +76,7 @@ class LoopBeginInstruction: public CFInstruction {
public:
LoopBeginInstruction();
private:
+ void do_evalue_liveness(LiverangeEvaluator& eval) const override;
bool is_equal_to(const Instruction& lhs) const override;
void do_print(std::ostream& os) const override;
};
@@ -81,6 +85,7 @@ class LoopEndInstruction: public CFInstruction {
public:
LoopEndInstruction(LoopBeginInstruction *start);
private:
+ void do_evalue_liveness(LiverangeEvaluator& eval) const override;
bool is_equal_to(const Instruction& lhs) const override;
void do_print(std::ostream& os) const override;
LoopBeginInstruction *m_start;
@@ -90,6 +95,7 @@ class LoopBreakInstruction: public CFInstruction {
public:
LoopBreakInstruction();
private:
+ void do_evalue_liveness(LiverangeEvaluator& eval) const override;
bool is_equal_to(const Instruction& lhs) const override;
void do_print(std::ostream& os) const override;
};
diff --git a/src/gallium/drivers/r600/sfn/sfn_instruction_export.cpp b/src/gallium/drivers/r600/sfn/sfn_instruction_export.cpp
index 7efa0832da1..14ae52f325e 100644
--- a/src/gallium/drivers/r600/sfn/sfn_instruction_export.cpp
+++ b/src/gallium/drivers/r600/sfn/sfn_instruction_export.cpp
@@ -26,6 +26,7 @@
#include "sfn_instruction_export.h"
+#include "sfn_liverange.h"
#include "sfn_valuepool.h"
namespace r600 {
diff --git a/src/gallium/drivers/r600/sfn/sfn_liverange.cpp b/src/gallium/drivers/r600/sfn/sfn_liverange.cpp
new file mode 100644
index 00000000000..1c95c3bc4d1
--- /dev/null
+++ b/src/gallium/drivers/r600/sfn/sfn_liverange.cpp
@@ -0,0 +1,987 @@
+/*
+ * Copyright (c) 2017-2019 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 "sfn_liverange.h"
+#include "sfn_debug.h"
+#include "sfn_value.h"
+#include "sfn_value_gpr.h"
+
+#include "program/prog_instruction.h"
+#include "util/bitscan.h"
+#include "util/u_math.h"
+
+#include <limits>
+#include <cstdlib>
+#include <iomanip>
+
+/* std::sort is significantly faster than qsort */
+#include <algorithm>
+
+/* If <windows.h> is included this is defined and clashes with
+ * std::numeric_limits<>::max()
+ */
+#ifdef max
+#undef max
+#endif
+
+
+namespace r600 {
+
+using std::numeric_limits;
+using std::unique_ptr;
+using std::setw;
+
+prog_scope_storage::prog_scope_storage(int n):
+ current_slot(0),
+ storage(n)
+{
+}
+
+prog_scope_storage::~prog_scope_storage()
+{
+}
+
+prog_scope*
+prog_scope_storage::create(prog_scope *p, prog_scope_type type, int id,
+ int lvl, int s_begin)
+{
+ storage[current_slot] = prog_scope(p, type, id, lvl, s_begin);
+ return &storage[current_slot++];
+}
+
+prog_scope::prog_scope(prog_scope *parent, prog_scope_type type, int id,
+ int depth, int scope_begin):
+ scope_type(type),
+ scope_id(id),
+ scope_nesting_depth(depth),
+ scope_begin(scope_begin),
+ scope_end(-1),
+ break_loop_line(numeric_limits<int>::max()),
+ parent_scope(parent)
+{
+}
+
+prog_scope::prog_scope():
+ prog_scope(nullptr, undefined_scope, -1, -1, -1)
+{
+}
+
+prog_scope_type prog_scope::type() const
+{
+ return scope_type;
+}
+
+prog_scope *prog_scope::parent() const
+{
+ return parent_scope;
+}
+
+int prog_scope::nesting_depth() const
+{
+ return scope_nesting_depth;
+}
+
+bool prog_scope::is_loop() const
+{
+ return (scope_type == loop_body);
+}
+
+bool prog_scope::is_in_loop() const
+{
+ if (scope_type == loop_body)
+ return true;
+
+ if (parent_scope)
+ return parent_scope->is_in_loop();
+
+ return false;
+}
+
+const prog_scope *prog_scope::innermost_loop() const
+{
+ if (scope_type == loop_body)
+ return this;
+
+ if (parent_scope)
+ return parent_scope->innermost_loop();
+
+ return nullptr;
+}
+
+const prog_scope *prog_scope::outermost_loop() const
+{
+ const prog_scope *loop = nullptr;
+ const prog_scope *p = this;
+
+ do {
+ if (p->type() == loop_body)
+ loop = p;
+ p = p->parent();
+ } while (p);
+
+ return loop;
+}
+
+bool prog_scope::is_child_of_ifelse_id_sibling(const prog_scope *scope) const
+{
+ const prog_scope *my_parent = in_parent_ifelse_scope();
+ while (my_parent) {
+ /* is a direct child? */
+ if (my_parent == scope)
+ return false;
+ /* is a child of the conditions sibling? */
+ if (my_parent->id() == scope->id())
+ return true;
+ my_parent = my_parent->in_parent_ifelse_scope();
+ }
+ return false;
+}
+
+bool prog_scope::is_child_of(const prog_scope *scope) const
+{
+ const prog_scope *my_parent = parent();
+ while (my_parent) {
+ if (my_parent == scope)
+ return true;
+ my_parent = my_parent->parent();
+ }
+ return false;
+}
+
+const prog_scope *prog_scope::enclosing_conditional() const
+{
+ if (is_conditional())
+ return this;
+
+ if (parent_scope)
+ return parent_scope->enclosing_conditional();
+
+ return nullptr;
+}
+
+bool prog_scope::contains_range_of(const prog_scope& other) const
+{
+ return (begin() <= other.begin()) && (end() >= other.end());
+}
+
+bool prog_scope::is_conditional() const
+{
+ return scope_type == if_branch ||
+ scope_type == else_branch ||
+ scope_type == switch_case_branch ||
+ scope_type == switch_default_branch;
+}
+
+const prog_scope *prog_scope::in_else_scope() const
+{
+ if (scope_type == else_branch)
+ return this;
+
+ if (parent_scope)
+ return parent_scope->in_else_scope();
+
+ return nullptr;
+}
+
+const prog_scope *prog_scope::in_parent_ifelse_scope() const
+{
+ if (parent_scope)
+ return parent_scope->in_ifelse_scope();
+ else
+ return nullptr;
+}
+
+const prog_scope *prog_scope::in_ifelse_scope() const
+{
+ if (scope_type == if_branch ||
+ scope_type == else_branch)
+ return this;
+
+ if (parent_scope)
+ return parent_scope->in_ifelse_scope();
+
+ return nullptr;
+}
+
+bool prog_scope::is_switchcase_scope_in_loop() const
+{
+ return (scope_type == switch_case_branch ||
+ scope_type == switch_default_branch) &&
+ is_in_loop();
+}
+
+bool prog_scope::break_is_for_switchcase() const
+{
+ if (scope_type == loop_body)
+ return false;
+
+ if (scope_type == switch_case_branch ||
+ scope_type == switch_default_branch ||
+ scope_type == switch_body)
+ return true;
+
+ if (parent_scope)
+ return parent_scope->break_is_for_switchcase();
+
+ return false;
+}
+
+int prog_scope::id() const
+{
+ return scope_id;
+}
+
+int prog_scope::begin() const
+{
+ return scope_begin;
+}
+
+int prog_scope::end() const
+{
+ return scope_end;
+}
+
+void prog_scope::set_end(int end)
+{
+ if (scope_end == -1)
+ scope_end = end;
+}
+
+void prog_scope::set_loop_break_line(int line)
+{
+ if (scope_type == loop_body) {
+ break_loop_line = MIN2(break_loop_line, line);
+ } else {
+ if (parent_scope)
+ parent()->set_loop_break_line(line);
+ }
+}
+
+int prog_scope::loop_break_line() const
+{
+ return break_loop_line;
+}
+
+temp_access::temp_access():
+ access_mask(0),
+ needs_component_tracking(false),
+ is_array_element(false)
+{
+}
+
+void temp_access::update_access_mask(int mask)
+{
+ if (access_mask && access_mask != mask)
+ needs_component_tracking = true;
+ access_mask |= mask;
+}
+
+void temp_access::record_write(int line, prog_scope *scope, int writemask, bool is_array_elm)
+{
+
+
+ update_access_mask(writemask);
+ is_array_element |= is_array_elm;
+
+ if (writemask & WRITEMASK_X)
+ comp[0].record_write(line, scope);
+ if (writemask & WRITEMASK_Y)
+ comp[1].record_write(line, scope);
+ if (writemask & WRITEMASK_Z)
+ comp[2].record_write(line, scope);
+ if (writemask & WRITEMASK_W)
+ comp[3].record_write(line, scope);
+}
+
+void temp_access::record_read(int line, prog_scope *scope, int readmask, bool is_array_elm)
+{
+ update_access_mask(readmask);
+ is_array_element |= is_array_elm;
+
+ if (readmask & WRITEMASK_X)
+ comp[0].record_read(line, scope);
+ if (readmask & WRITEMASK_Y)
+ comp[1].record_read(line, scope);
+ if (readmask & WRITEMASK_Z)
+ comp[2].record_read(line, scope);
+ if (readmask & WRITEMASK_W)
+ comp[3].record_read(line, scope);
+}
+
+inline static register_live_range make_live_range(int b, int e)
+{
+ register_live_range lt;
+ lt.begin = b;
+ lt.end = e;
+ lt.is_array_elm = false;
+ return lt;
+}
+
+register_live_range temp_access::get_required_live_range()
+{
+ register_live_range result = make_live_range(-1, -1);
+
+ unsigned mask = access_mask;
+ while (mask) {
+ unsigned chan = u_bit_scan(&mask);
+ register_live_range lt = comp[chan].get_required_live_range();
+
+ if (lt.begin >= 0) {
+ if ((result.begin < 0) || (result.begin > lt.begin))
+ result.begin = lt.begin;
+ }
+
+ if (lt.end > result.end)
+ result.end = lt.end;
+
+ if (!needs_component_tracking)
+ break;
+ }
+ result.is_array_elm = is_array_element;
+ return result;
+}
+
+const int
+temp_comp_access::conditionality_untouched = std::numeric_limits<int>::max();
+
+const int
+temp_comp_access::write_is_unconditional = std::numeric_limits<int>::max() - 1;
+
+
+temp_comp_access::temp_comp_access():
+ last_read_scope(nullptr),
+ first_read_scope(nullptr),
+ first_write_scope(nullptr),
+ first_write(-1),
+ last_read(-1),
+ last_write(-1),
+ first_read(numeric_limits<int>::max()),
+ conditionality_in_loop_id(conditionality_untouched),
+ if_scope_write_flags(0),
+ next_ifelse_nesting_depth(0),
+ current_unpaired_if_write_scope(nullptr),
+ was_written_in_current_else_scope(false)
+{
+}
+
+void temp_comp_access::record_read(int line, prog_scope *scope)
+{
+ last_read_scope = scope;
+ last_read = line;
+
+ if (first_read > line) {
+ first_read = line;
+ first_read_scope = scope;
+ }
+
+ /* If the conditionality of the first write is already resolved then
+ * no further checks are required.
+ */
+ if (conditionality_in_loop_id == write_is_unconditional ||
+ conditionality_in_loop_id == write_is_conditional)
+ return;
+
+ /* Check whether we are in a condition within a loop */
+ const prog_scope *ifelse_scope = scope->in_ifelse_scope();
+ const prog_scope *enclosing_loop;
+ if (ifelse_scope && (enclosing_loop = ifelse_scope->innermost_loop())) {
+
+ /* If we have either not yet written to this register nor writes are
+ * resolved as unconditional in the enclosing loop then check whether
+ * we read before write in an IF/ELSE branch.
+ */
+ if ((conditionality_in_loop_id != write_is_conditional) &&
+ (conditionality_in_loop_id != enclosing_loop->id())) {
+
+ if (current_unpaired_if_write_scope) {
+
+ /* Has been written in this or a parent scope? - this makes the temporary
+ * unconditionally set at this point.
+ */
+ if (scope->is_child_of(current_unpaired_if_write_scope))
+ return;
+
+ /* Has been written in the same scope before it was read? */
+ if (ifelse_scope->type() == if_branch) {
+ if (current_unpaired_if_write_scope->id() == scope->id())
+ return;
+ } else {
+ if (was_written_in_current_else_scope)
+ return;
+ }
+ }
+
+ /* The temporary was read (conditionally) before it is written, hence
+ * it should survive a loop. This can be signaled like if it were
+ * conditionally written.
+ */
+ conditionality_in_loop_id = write_is_conditional;
+ }
+ }
+}
+
+void temp_comp_access::record_write(int line, prog_scope *scope)
+{
+ last_write = line;
+
+ if (first_write < 0) {
+ first_write = line;
+ first_write_scope = scope;
+
+ /* If the first write we encounter is not in a conditional branch, or
+ * the conditional write is not within a loop, then this is to be
+ * considered an unconditional dominant write.
+ */
+ const prog_scope *conditional = scope->enclosing_conditional();
+ if (!conditional || !conditional->innermost_loop()) {
+ conditionality_in_loop_id = write_is_unconditional;
+ }
+ }
+
+ /* The conditionality of the first write is already resolved. */
+ if (conditionality_in_loop_id == write_is_unconditional ||
+ conditionality_in_loop_id == write_is_conditional)
+ return;
+
+ /* If the nesting depth is larger than the supported level,
+ * then we assume conditional writes.
+ */
+ if (next_ifelse_nesting_depth >= supported_ifelse_nesting_depth) {
+ conditionality_in_loop_id = write_is_conditional;
+ return;
+ }
+
+ /* If we are in an IF/ELSE scope within a loop and the loop has not
+ * been resolved already, then record this write.
+ */
+ const prog_scope *ifelse_scope = scope->in_ifelse_scope();
+ if (ifelse_scope && ifelse_scope->innermost_loop() &&
+ ifelse_scope->innermost_loop()->id() != conditionality_in_loop_id)
+ record_ifelse_write(*ifelse_scope);
+}
+
+void temp_comp_access::record_ifelse_write(const prog_scope& scope)
+{
+ if (scope.type() == if_branch) {
+ /* The first write in an IF branch within a loop implies unresolved
+ * conditionality (if it was untouched or unconditional before).
+ */
+ conditionality_in_loop_id = conditionality_unresolved;
+ was_written_in_current_else_scope = false;
+ record_if_write(scope);
+ } else {
+ was_written_in_current_else_scope = true;
+ record_else_write(scope);
+ }
+}
+
+void temp_comp_access::record_if_write(const prog_scope& scope)
+{
+ /* Don't record write if this IF scope if it ...
+ * - is not the first write in this IF scope,
+ * - has already been written in a parent IF scope.
+ * In both cases this write is a secondary write that doesn't contribute
+ * to resolve conditionality.
+ *
+ * Record the write if it
+ * - is the first one (obviously),
+ * - happens in an IF branch that is a child of the ELSE branch of the
+ * last active IF/ELSE pair. In this case recording this write is used to
+ * established whether the write is (un-)conditional in the scope enclosing
+ * this outer IF/ELSE pair.
+ */
+ if (!current_unpaired_if_write_scope ||
+ (current_unpaired_if_write_scope->id() != scope.id() &&
+ scope.is_child_of_ifelse_id_sibling(current_unpaired_if_write_scope))) {
+ if_scope_write_flags |= 1 << next_ifelse_nesting_depth;
+ current_unpaired_if_write_scope = &scope;
+ next_ifelse_nesting_depth++;
+ }
+}
+
+void temp_comp_access::record_else_write(const prog_scope& scope)
+{
+ int mask = 1 << (next_ifelse_nesting_depth - 1);
+
+ /* If the temporary was written in an IF branch on the same scope level
+ * and this branch is the sibling of this ELSE branch, then we have a
+ * pair of writes that makes write access to this temporary unconditional
+ * in the enclosing scope.
+ */
+
+ if ((if_scope_write_flags & mask) &&
+ (scope.id() == current_unpaired_if_write_scope->id())) {
+ --next_ifelse_nesting_depth;
+ if_scope_write_flags &= ~mask;
+
+ /* The following code deals with propagating unconditionality from
+ * inner levels of nested IF/ELSE to the outer levels like in
+ *
+ * 1: var t;
+ * 2: if (a) { <- start scope A
+ * 3: if (b)
+ * 4: t = ...
+ * 5: else
+ * 6: t = ...
+ * 7: } else { <- start scope B
+ * 8: if (c)
+ * 9: t = ...
+ * A: else <- start scope C
+ * B: t = ...
+ * C: }
+ *
+ */
+
+ const prog_scope *parent_ifelse = scope.parent()->in_ifelse_scope();
+
+ if (1 << (next_ifelse_nesting_depth - 1) & if_scope_write_flags) {
+ /* We are at the end of scope C and already recorded a write
+ * within an IF scope (A), the sibling of the parent ELSE scope B,
+ * and it is not yet resolved. Mark that as the last relevant
+ * IF scope. Below the write will be resolved for the A/B
+ * scope pair.
+ */
+ current_unpaired_if_write_scope = parent_ifelse;
+ } else {
+ current_unpaired_if_write_scope = nullptr;
+ }
+ /* Promote the first write scope to the enclosing scope because
+ * the current IF/ELSE pair is now irrelevant for the analysis.
+ * This is also required to evaluate the minimum life time for t in
+ * {
+ * var t;
+ * if (a)
+ * t = ...
+ * else
+ * t = ...
+ * x = t;
+ * ...
+ * }
+ */
+ first_write_scope = scope.parent();
+
+ /* If some parent is IF/ELSE and in a loop then propagate the
+ * write to that scope. Otherwise the write is unconditional
+ * because it happens in both corresponding IF/ELSE branches
+ * in this loop, and hence, record the loop id to signal the
+ * resolution.
+ */
+ if (parent_ifelse && parent_ifelse->is_in_loop()) {
+ record_ifelse_write(*parent_ifelse);
+ } else {
+ conditionality_in_loop_id = scope.innermost_loop()->id();
+ }
+ } else {
+ /* The temporary was not written in the IF branch corresponding
+ * to this ELSE branch, hence the write is conditional.
+ */
+ conditionality_in_loop_id = write_is_conditional;
+ }
+}
+
+bool temp_comp_access::conditional_ifelse_write_in_loop() const
+{
+ return conditionality_in_loop_id <= conditionality_unresolved;
+}
+
+void temp_comp_access::propagate_live_range_to_dominant_write_scope()
+{
+ first_write = first_write_scope->begin();
+ int lr = first_write_scope->end();
+
+ if (last_read < lr)
+ last_read = lr;
+}
+
+register_live_range temp_comp_access::get_required_live_range()
+{
+ bool keep_for_full_loop = false;
+
+ /* This register component is not used at all, or only read,
+ * mark it as unused and ignore it when renaming.
+ * glsl_to_tgsi_visitor::renumber_registers will take care of
+ * eliminating registers that are not written to.
+ */
+ if (last_write < 0)
+ return make_live_range(-1, -1);
+
+ assert(first_write_scope);
+
+ /* Only written to, just make sure the register component is not
+ * reused in the range it is used to write to
+ */
+ if (!last_read_scope)
+ return make_live_range(first_write, last_write + 1);
+
+ const prog_scope *enclosing_scope_first_read = first_read_scope;
+ const prog_scope *enclosing_scope_first_write = first_write_scope;
+
+ /* We read before writing in a loop
+ * hence the value must survive the loops
+ */
+ if ((first_read <= first_write) &&
+ first_read_scope->is_in_loop()) {
+ keep_for_full_loop = true;
+ enclosing_scope_first_read = first_read_scope->outermost_loop();
+ }
+
+ /* A conditional write within a (nested) loop must survive the outermost
+ * loop if the last read was not within the same scope.
+ */
+ const prog_scope *conditional = enclosing_scope_first_write->enclosing_conditional();
+ if (conditional && !conditional->contains_range_of(*last_read_scope) &&
+ (conditional->is_switchcase_scope_in_loop() ||
+ conditional_ifelse_write_in_loop())) {
+ keep_for_full_loop = true;
+ enclosing_scope_first_write = conditional->outermost_loop();
+ }
+
+ /* Evaluate the scope that is shared by all: required first write scope,
+ * required first read before write scope, and last read scope.
+ */
+ const prog_scope *enclosing_scope = enclosing_scope_first_read;
+ if (enclosing_scope_first_write->contains_range_of(*enclosing_scope))
+ enclosing_scope = enclosing_scope_first_write;
+
+ if (last_read_scope->contains_range_of(*enclosing_scope))
+ enclosing_scope = last_read_scope;
+
+ while (!enclosing_scope->contains_range_of(*enclosing_scope_first_write) ||
+ !enclosing_scope->contains_range_of(*last_read_scope)) {
+ enclosing_scope = enclosing_scope->parent();
+ assert(enclosing_scope);
+ }
+
+ /* Propagate the last read scope to the target scope */
+ while (enclosing_scope->nesting_depth() < last_read_scope->nesting_depth()) {
+ /* If the read is in a loop and we have to move up the scope we need to
+ * extend the live range to the end of this current loop because at this
+ * point we don't know whether the component was written before
+ * un-conditionally in the same loop.
+ */
+ if (last_read_scope->is_loop())
+ last_read = last_read_scope->end();
+
+ last_read_scope = last_read_scope->parent();
+ }
+
+ /* If the variable has to be kept for the whole loop, and we
+ * are currently in a loop, then propagate the live range.
+ */
+ if (keep_for_full_loop && first_write_scope->is_loop())
+ propagate_live_range_to_dominant_write_scope();
+
+ /* Propagate the first_dominant_write scope to the target scope */
+ while (enclosing_scope->nesting_depth() < first_write_scope->nesting_depth()) {
+ /* Propagate live_range if there was a break in a loop and the write was
+ * after the break inside that loop. Note, that this is only needed if
+ * we move up in the scopes.
+ */
+ if (first_write_scope->loop_break_line() < first_write) {
+ keep_for_full_loop = true;
+ propagate_live_range_to_dominant_write_scope();
+ }
+
+ first_write_scope = first_write_scope->parent();
+
+ /* Propagte live_range if we are now in a loop */
+ if (keep_for_full_loop && first_write_scope->is_loop())
+ propagate_live_range_to_dominant_write_scope();
+ }
+
+ /* The last write past the last read is dead code, but we have to
+ * ensure that the component is not reused too early, hence extend the
+ * live_range past the last write.
+ */
+ if (last_write >= last_read)
+ last_read = last_write + 1;
+
+ /* Here we are at the same scope, all is resolved */
+ return make_live_range(first_write, last_read);
+}
+
+/* Helper class for sorting and searching the registers based
+ * on live ranges. */
+class register_merge_record {
+public:
+ int begin;
+ int end;
+ int reg;
+ bool erase;
+ bool is_array_elm;
+
+ bool operator < (const register_merge_record& rhs) const {
+ return begin < rhs.begin;
+ }
+};
+
+LiverangeEvaluator::LiverangeEvaluator():
+ line(0),
+ loop_id(1),
+ if_id(1),
+ switch_id(0),
+ is_at_end(false),
+ n_scopes(1),
+ cur_scope(nullptr)
+{
+}
+
+void LiverangeEvaluator::run(const Shader& shader,
+ std::vector<register_live_range>& register_live_ranges)
+{
+ temp_acc.resize(register_live_ranges.size());
+ fill(temp_acc.begin(), temp_acc.end(), temp_access());
+
+ sfn_log << SfnLog::merge << "have " << temp_acc.size() << " temps\n";
+
+ for (const auto& ir: shader.m_ir) {
+ switch (ir->type()) {
+ case Instruction::cond_if:
+ case Instruction::cond_else:
+ case Instruction::loop_begin:
+ ++n_scopes;
+ default:
+ ;
+ }
+ }
+
+ scopes.reset(new prog_scope_storage(n_scopes));
+
+ cur_scope = scopes->create(nullptr, outer_scope, 0, 0, line);
+
+ line = 0;
+
+ for (auto& v: shader.m_temp) {
+ if (v.second->type() == Value::gpr) {
+ const auto& g = static_cast<const GPRValue&>(*v.second);
+ if (g.is_input()) {
+ sfn_log << SfnLog::merge << "Record INPUT write for "
+ << g << " in " << temp_acc.size() << " temps\n";
+ temp_acc[g.sel()].record_write(line, cur_scope, 1 << g.chan(), false);
+ temp_acc[g.sel()].record_read(line, cur_scope, 1 << g.chan(), false);
+ }
+ }
+ }
+
+ for (const auto& ir: shader.m_ir) {
+ ir->evalue_liveness(*this);
+ if (ir->type() != Instruction::alu ||
+ static_cast<const AluInstruction&>(*ir).flag(alu_last_instr))
+ ++line;
+ }
+
+ assert(cur_scope->type() == outer_scope);
+ cur_scope->set_end(line);
+ is_at_end = true;
+
+ get_required_live_ranges(register_live_ranges);
+}
+
+
+void LiverangeEvaluator::record_read(const Value& src, bool is_array_elm)
+{
+ sfn_log << SfnLog::merge << "Record read l:" << line << " reg:" << src << "\n";
+ if (src.type() == Value::gpr) {
+ const GPRValue& v = static_cast<const GPRValue&>(src);
+ if (v.chan() < 4)
+ temp_acc[v.sel()].record_read(line, cur_scope, 1 << v.chan(), is_array_elm);
+ return;
+ } else if (src.type() == Value::gpr_array_value) {
+ const GPRArrayValue& v = static_cast<const GPRArrayValue&>(src);
+ v.record_read(*this);
+ }
+}
+
+void LiverangeEvaluator::record_write(const Value& src, bool is_array_elm)
+{
+ sfn_log << SfnLog::merge << "Record write for "
+ << src << " in " << temp_acc.size() << " temps\n";
+
+ if (src.type() == Value::gpr) {
+ const GPRValue& v = static_cast<const GPRValue&>(src);
+ assert(v.sel() < temp_acc.size());
+ if (v.chan() < 4)
+ temp_acc[v.sel()].record_write(line, cur_scope, 1 << v.chan(), is_array_elm);
+ return;
+ } else if (src.type() == Value::gpr_array_value) {
+ const GPRArrayValue& v = static_cast<const GPRArrayValue&>(src);
+ v.record_write(*this);
+ }
+}
+
+void LiverangeEvaluator::record_read(const GPRVector& src)
+{
+ for (int i = 0; i < 4; ++i)
+ if (src.reg_i(i))
+ record_read(*src.reg_i(i));
+}
+
+void LiverangeEvaluator::record_write(const GPRVector& dst)
+{
+ for (int i = 0; i < 4; ++i)
+ if (dst.reg_i(i))
+ record_write(*dst.reg_i(i));
+}
+
+void LiverangeEvaluator::get_required_live_ranges(std::vector<register_live_range>& register_live_ranges)
+{
+ sfn_log << SfnLog::merge << "== register live ranges ==========\n";
+ for(unsigned i = 0; i < register_live_ranges.size(); ++i) {
+ sfn_log << SfnLog::merge << setw(4) << i;
+ register_live_ranges[i] = temp_acc[i].get_required_live_range();
+ sfn_log << SfnLog::merge << ": [" << register_live_ranges[i].begin << ", "
+ << register_live_ranges[i].end << "]\n";
+ }
+ sfn_log << SfnLog::merge << "==================================\n\n";
+}
+
+void LiverangeEvaluator::scope_if()
+{
+ cur_scope = scopes->create(cur_scope, if_branch, if_id++,
+ cur_scope->nesting_depth() + 1, line + 1);
+}
+
+void LiverangeEvaluator::scope_else()
+{
+ assert(cur_scope->type() == if_branch);
+ cur_scope->set_end(line - 1);
+ cur_scope = scopes->create(cur_scope->parent(), else_branch,
+ cur_scope->id(), cur_scope->nesting_depth(),
+ line + 1);
+}
+
+void LiverangeEvaluator::scope_endif()
+{
+ cur_scope->set_end(line - 1);
+ cur_scope = cur_scope->parent();
+ assert(cur_scope);
+}
+
+void LiverangeEvaluator::scope_loop_begin()
+{
+ cur_scope = scopes->create(cur_scope, loop_body, loop_id++,
+ cur_scope->nesting_depth() + 1, line);
+}
+
+void LiverangeEvaluator::scope_loop_end()
+{
+ assert(cur_scope->type() == loop_body);
+ cur_scope->set_end(line);
+ cur_scope = cur_scope->parent();
+ assert(cur_scope);
+}
+
+void LiverangeEvaluator::scope_loop_break()
+{
+ cur_scope->set_loop_break_line(line);
+}
+
+/* This functions evaluates the register merges by using a binary
+ * search to find suitable merge candidates. */
+
+std::vector<rename_reg_pair>
+get_temp_registers_remapping(const std::vector<register_live_range>& live_ranges)
+{
+
+ std::vector<rename_reg_pair> result(live_ranges.size(), rename_reg_pair{false, false, 0});
+ std::vector<register_merge_record> reg_access;
+
+ for (unsigned i = 0; i < live_ranges.size(); ++i) {
+ if (live_ranges[i].begin >= 0) {
+ register_merge_record r;
+ r.begin = live_ranges[i].begin;
+ r.end = live_ranges[i].end;
+ r.is_array_elm = live_ranges[i].is_array_elm;
+ r.reg = i;
+ r.erase = false;
+ reg_access.push_back(r);
+ }
+ }
+
+ std::sort(reg_access.begin(), reg_access.end());
+
+ for (auto& r : reg_access)
+ sfn_log << SfnLog::merge << "Use Range " <<r.reg << " ["
+ << r.begin << ", " << r.end << "]\n";
+
+ auto trgt = reg_access.begin();
+ auto reg_access_end = reg_access.end();
+ auto first_erase = reg_access_end;
+ auto search_start = trgt + 1;
+
+ while (trgt != reg_access_end) {
+ /* Find the next register that has a live-range starting past the
+ * search start and that is not an array element. Array elements can't
+ * be moved (Moving the whole array could be an option to be implemented later)*/
+
+ sfn_log << SfnLog::merge << "Next target is "
+ << trgt->reg << "[" << trgt->begin << ", " << trgt->end << "]\n";
+
+
+ auto src = upper_bound(search_start, reg_access_end, trgt->end,
+ [](int bound, const register_merge_record& m){
+ return bound < m.begin && !m.is_array_elm;}
+ );
+
+ if (src != reg_access_end) {
+ result[src->reg].new_reg = trgt->reg;
+ result[src->reg].valid = true;
+
+ sfn_log << SfnLog::merge << "Map "
+ << src->reg << "[" << src->begin << ", " << src->end << "] to "
+ << trgt->reg << "[" << trgt->begin << ", " << trgt->end << ":";
+ trgt->end = src->end;
+ sfn_log << SfnLog::merge << trgt->end << "]\n";
+
+ /* Since we only search forward, don't remove the renamed
+ * register just now, only mark it. */
+ src->erase = true;
+
+ if (first_erase == reg_access_end)
+ first_erase = src;
+
+ search_start = src + 1;
+ } else {
+ /* Moving to the next target register it is time to remove
+ * the already merged registers from the search range */
+ if (first_erase != reg_access_end) {
+ auto outp = first_erase;
+ auto inp = first_erase + 1;
+
+ while (inp != reg_access_end) {
+ if (!inp->erase)
+ *outp++ = *inp;
+ ++inp;
+ }
+
+ reg_access_end = outp;
+ first_erase = reg_access_end;
+ }
+ ++trgt;
+ search_start = trgt + 1;
+ }
+ }
+ return result;
+}
+
+} // end ns r600
diff --git a/src/gallium/drivers/r600/sfn/sfn_liverange.h b/src/gallium/drivers/r600/sfn/sfn_liverange.h
new file mode 100644
index 00000000000..d3480ab85ca
--- /dev/null
+++ b/src/gallium/drivers/r600/sfn/sfn_liverange.h
@@ -0,0 +1,314 @@
+/* -*- mesa-c++ -*-
+ *
+ * Copyright (c) 2018-2019 Collabora LTD
+ *
+ * Author: Gert Wollny <[email protected]>
+ *
+ * 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
+ * on the rights to use, copy, modify, merge, publish, distribute, sub
+ * license, 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 NON-INFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHOR(S) AND/OR THEIR SUPPLIERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+ * USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SFN_LIVERANGE_H
+#define SFN_LIVERANGE_H
+
+#include <cstdint>
+#include <ostream>
+#include <vector>
+#include <limits>
+
+#include "sfn_instruction_base.h"
+#include "sfn_nir.h"
+
+namespace r600 {
+
+/** Storage to record the required live range of a temporary register
+ * begin == end == -1 indicates that the register can be reused without
+ * limitations. Otherwise, "begin" indicates the first instruction in which
+ * a write operation may target this temporary, and end indicates the
+ * last instruction in which a value can be read from this temporary.
+ * Hence, a register R2 can be merged with a register R1 if R1.end <= R2.begin.
+ */
+struct register_live_range {
+ int begin;
+ int end;
+ bool is_array_elm;
+};
+
+enum prog_scope_type {
+ outer_scope, /* Outer program scope */
+ loop_body, /* Inside a loop */
+ if_branch, /* Inside if branch */
+ else_branch, /* Inside else branch */
+ switch_body, /* Inside switch statmenet */
+ switch_case_branch, /* Inside switch case statmenet */
+ switch_default_branch, /* Inside switch default statmenet */
+ undefined_scope
+};
+
+class prog_scope {
+public:
+ prog_scope();
+ prog_scope(prog_scope *parent, prog_scope_type type, int id,
+ int depth, int begin);
+
+ prog_scope_type type() const;
+ prog_scope *parent() const;
+ int nesting_depth() const;
+ int id() const;
+ int end() const;
+ int begin() const;
+ int loop_break_line() const;
+
+ const prog_scope *in_else_scope() const;
+ const prog_scope *in_ifelse_scope() const;
+ const prog_scope *in_parent_ifelse_scope() const;
+ const prog_scope *innermost_loop() const;
+ const prog_scope *outermost_loop() const;
+ const prog_scope *enclosing_conditional() const;
+
+ bool is_loop() const;
+ bool is_in_loop() const;
+ bool is_switchcase_scope_in_loop() const;
+ bool is_conditional() const;
+ bool is_child_of(const prog_scope *scope) const;
+ bool is_child_of_ifelse_id_sibling(const prog_scope *scope) const;
+
+ bool break_is_for_switchcase() const;
+ bool contains_range_of(const prog_scope& other) const;
+
+ void set_end(int end);
+ void set_loop_break_line(int line);
+
+private:
+ prog_scope_type scope_type;
+ int scope_id;
+ int scope_nesting_depth;
+ int scope_begin;
+ int scope_end;
+ int break_loop_line;
+ prog_scope *parent_scope;
+};
+
+/* Some storage class to encapsulate the prog_scope (de-)allocations */
+class prog_scope_storage {
+public:
+ prog_scope_storage(int n);
+ ~prog_scope_storage();
+ prog_scope * create(prog_scope *p, prog_scope_type type, int id,
+ int lvl, int s_begin);
+private:
+ int current_slot;
+ std::vector<prog_scope> storage;
+};
+
+/* Class to track the access to a component of a temporary register. */
+
+class temp_comp_access {
+public:
+ temp_comp_access();
+
+ void record_read(int line, prog_scope *scope);
+ void record_write(int line, prog_scope *scope);
+ register_live_range get_required_live_range();
+private:
+ void propagate_live_range_to_dominant_write_scope();
+ bool conditional_ifelse_write_in_loop() const;
+
+ void record_ifelse_write(const prog_scope& scope);
+ void record_if_write(const prog_scope& scope);
+ void record_else_write(const prog_scope& scope);
+
+ prog_scope *last_read_scope;
+ prog_scope *first_read_scope;
+ prog_scope *first_write_scope;
+
+ int first_write;
+ int last_read;
+ int last_write;
+ int first_read;
+
+ /* This member variable tracks the current resolution of conditional writing
+ * to this temporary in IF/ELSE clauses.
+ *
+ * The initial value "conditionality_untouched" indicates that this
+ * temporary has not yet been written to within an if clause.
+ *
+ * A positive (other than "conditionality_untouched") number refers to the
+ * last loop id for which the write was resolved as unconditional. With each
+ * new loop this value will be overwitten by "conditionality_unresolved"
+ * on entering the first IF clause writing this temporary.
+ *
+ * The value "conditionality_unresolved" indicates that no resolution has
+ * been achieved so far. If the variable is set to this value at the end of
+ * the processing of the whole shader it also indicates a conditional write.
+ *
+ * The value "write_is_conditional" marks that the variable is written
+ * conditionally (i.e. not in all relevant IF/ELSE code path pairs) in at
+ * least one loop.
+ */
+ int conditionality_in_loop_id;
+
+ /* Helper constants to make the tracking code more readable. */
+ static const int write_is_conditional = -1;
+ static const int conditionality_unresolved = 0;
+ static const int conditionality_untouched;
+ static const int write_is_unconditional;
+
+ /* A bit field tracking the nexting levels of if-else clauses where the
+ * temporary has (so far) been written to in the if branch, but not in the
+ * else branch.
+ */
+ unsigned int if_scope_write_flags;
+
+ int next_ifelse_nesting_depth;
+ static const int supported_ifelse_nesting_depth = 32;
+
+ /* Tracks the last if scope in which the temporary was written to
+ * without a write in the correspondig else branch. Is also used
+ * to track read-before-write in the according scope.
+ */
+ const prog_scope *current_unpaired_if_write_scope;
+
+ /* Flag to resolve read-before-write in the else scope. */
+ bool was_written_in_current_else_scope;
+};
+
+/* Class to track the access to all components of a temporary register. */
+class temp_access {
+public:
+ temp_access();
+ void record_read(int line, prog_scope *scope, int swizzle, bool is_array_elm);
+ void record_write(int line, prog_scope *scope, int writemask, bool is_array_elm);
+ register_live_range get_required_live_range();
+private:
+ void update_access_mask(int mask);
+
+ temp_comp_access comp[4];
+ int access_mask;
+ bool needs_component_tracking;
+ bool is_array_element;
+};
+
+/* Helper class to merge the live ranges of an arrays.
+ *
+ * For arrays the array length, live range, and component access needs to
+ * be kept, because when live ranges are merged or arrays are interleaved
+ * one can only merge or interleave an array into another with equal or more
+ * elements. For interleaving it is also required that the sum of used swizzles
+ * is at most four.
+ */
+
+class array_live_range {
+public:
+ array_live_range();
+ array_live_range(unsigned aid, unsigned alength);
+ array_live_range(unsigned aid, unsigned alength, int first_access,
+ int last_access, int mask);
+
+ void set_live_range(int first_access, int last_access);
+ void set_begin(int _begin){first_access = _begin;}
+ void set_end(int _end){last_access = _end;}
+ void set_access_mask(int s);
+
+ static void merge(array_live_range *a, array_live_range *b);
+ static void interleave(array_live_range *a, array_live_range *b);
+
+ int array_id() const {return id;}
+ int target_array_id() const {return target_array ? target_array->id : 0;}
+ const array_live_range *final_target() const {return target_array ?
+ target_array->final_target() : this;}
+ unsigned array_length() const { return length;}
+ int begin() const { return first_access;}
+ int end() const { return last_access;}
+ int access_mask() const { return component_access_mask;}
+ int used_components() const {return used_component_count;}
+
+ bool time_doesnt_overlap(const array_live_range& other) const;
+
+ void print(std::ostream& os) const;
+
+ bool is_mapped() const { return target_array != nullptr;}
+
+ int8_t remap_one_swizzle(int8_t idx) const;
+
+private:
+ void init_swizzles();
+ void set_target(array_live_range *target);
+ void merge_live_range_from(array_live_range *other);
+ void interleave_into(array_live_range *other);
+
+ unsigned id;
+ unsigned length;
+ int first_access;
+ int last_access;
+ uint8_t component_access_mask;
+ uint8_t used_component_count;
+ array_live_range *target_array;
+ int8_t swizzle_map[4];
+};
+
+
+
+class LiverangeEvaluator {
+public:
+ LiverangeEvaluator();
+
+ void run(const Shader& shader,
+ std::vector<register_live_range> &register_live_ranges);
+
+ void scope_if();
+ void scope_else();
+ void scope_endif();
+ void scope_loop_begin();
+ void scope_loop_end();
+ void scope_loop_break();
+
+ void record_read(const Value& src, bool is_array_elm = false);
+ void record_write(const Value& dst, bool is_array_elm = false);
+
+ void record_read(const GPRVector& src);
+ void record_write(const GPRVector& dst);
+
+private:
+
+ prog_scope *create_scope(prog_scope *parent, prog_scope_type type, int id,
+ int lvl, int s_begin);
+
+
+ void get_required_live_ranges(std::vector<register_live_range>& register_live_ranges);
+
+ int line;
+ int loop_id;
+ int if_id;
+ int switch_id;
+ bool is_at_end;
+ int n_scopes;
+ std::unique_ptr<prog_scope_storage> scopes;
+ prog_scope *cur_scope;
+
+ std::vector<temp_access> temp_acc;
+
+};
+
+std::vector<rename_reg_pair>
+get_temp_registers_remapping(const std::vector<register_live_range>& live_ranges);
+
+} // end namespace r600
+
+#endif
diff --git a/src/gallium/drivers/r600/sfn/sfn_nir.cpp b/src/gallium/drivers/r600/sfn/sfn_nir.cpp
index ef368cd1d4e..3bf430438c9 100644
--- a/src/gallium/drivers/r600/sfn/sfn_nir.cpp
+++ b/src/gallium/drivers/r600/sfn/sfn_nir.cpp
@@ -121,6 +121,10 @@ bool ShaderFromNir::lower(const nir_shader *shader, r600_pipe_shader *pipe_shade
sfn_log << SfnLog::trans << "Finalize\n";
impl->finalize();
+ if (!sfn_log.has_debug_flag(SfnLog::nomerge)) {
+ sfn_log << SfnLog::trans << "Merge registers\n";
+ impl->remap_registers();
+ }
sfn_log << SfnLog::trans << "Finished translating to R600 IR\n";
return true;
}
diff --git a/src/gallium/drivers/r600/sfn/sfn_shader_base.cpp b/src/gallium/drivers/r600/sfn/sfn_shader_base.cpp
index 04e1ee58444..a0b0d8b7f87 100644
--- a/src/gallium/drivers/r600/sfn/sfn_shader_base.cpp
+++ b/src/gallium/drivers/r600/sfn/sfn_shader_base.cpp
@@ -29,6 +29,7 @@
#include "sfn_shader_vertex.h"
#include "sfn_shader_fragment.h"
+#include "sfn_liverange.h"
#include "sfn_ir_to_assembly.h"
#include "sfn_nir.h"
#include "sfn_instruction_misc.h"
@@ -86,6 +87,80 @@ bool ShaderFromNirProcessor::scan_instruction(nir_instr *instr)
return scan_sysvalue_access(instr);
}
+static void remap_shader_info(r600_shader& sh_info,
+ std::vector<rename_reg_pair>& map,
+ UNUSED ValueMap& values)
+{
+ for (unsigned i = 0; i < sh_info.ninput; ++i) {
+ sfn_log << SfnLog::merge << "Input " << i << " gpr:" << sh_info.input[i].gpr
+ << " of map.size()\n";
+
+ assert(sh_info.input[i].gpr < map.size());
+ auto new_index = map[sh_info.input[i].gpr];
+ if (new_index.valid)
+ sh_info.input[i].gpr = new_index.new_reg;
+ map[sh_info.input[i].gpr].used = true;
+ }
+
+ for (unsigned i = 0; i < sh_info.noutput; ++i) {
+ assert(sh_info.output[i].gpr < map.size());
+ auto new_index = map[sh_info.output[i].gpr];
+ if (new_index.valid)
+ sh_info.output[i].gpr = new_index.new_reg;
+ map[sh_info.output[i].gpr].used = true;
+ }
+}
+
+void ShaderFromNirProcessor::remap_registers()
+{
+ // register renumbering
+ auto rc = register_count();
+ if (!rc)
+ return;
+
+ std::vector<register_live_range> register_live_ranges(rc);
+
+ auto temp_register_map = get_temp_registers();
+
+ Shader sh{m_output, temp_register_map};
+ LiverangeEvaluator().run(sh, register_live_ranges);
+ auto register_map = get_temp_registers_remapping(register_live_ranges);
+
+ sfn_log << SfnLog::merge << "=========Mapping===========\n";
+ for (size_t i = 0; i < register_map.size(); ++i)
+ if (register_map[i].valid)
+ sfn_log << SfnLog::merge << "Map:" << i << " -> " << register_map[i].new_reg << "\n";
+
+
+ ValueRemapper vmap0(register_map, temp_register_map);
+ for (auto ir: m_output)
+ ir->remap_registers(vmap0);
+
+ remap_shader_info(m_sh_info, register_map, temp_register_map);
+
+ /* Mark inputs as used registers, these registers should no be remapped */
+ for (auto& v: sh.m_temp) {
+ if (v.second->type() == Value::gpr) {
+ const auto& g = static_cast<const GPRValue&>(*v.second);
+ if (g.is_input())
+ register_map[g.sel()].used = true;
+ }
+ }
+
+ int new_index = 0;
+ for (auto& i : register_map) {
+ i.valid = i.used;
+ if (i.used)
+ i.new_reg = new_index++;
+ }
+
+ ValueRemapper vmap1(register_map, temp_register_map);
+ for (auto ir: m_output)
+ ir->remap_registers(vmap1);
+
+ remap_shader_info(m_sh_info, register_map, temp_register_map);
+}
+
bool ShaderFromNirProcessor::process_uniforms(nir_variable *uniform)
{
// m_uniform_type_map
diff --git a/src/gallium/drivers/r600/sfn/sfn_value_gpr.cpp b/src/gallium/drivers/r600/sfn/sfn_value_gpr.cpp
index fab4837cc77..4eb5bfb18c4 100644
--- a/src/gallium/drivers/r600/sfn/sfn_value_gpr.cpp
+++ b/src/gallium/drivers/r600/sfn/sfn_value_gpr.cpp
@@ -27,6 +27,7 @@
#include "sfn_value_gpr.h"
#include "sfn_valuepool.h"
#include "sfn_debug.h"
+#include "sfn_liverange.h"
namespace r600 {
@@ -218,6 +219,28 @@ bool GPRArrayValue::is_equal_to(const Value& other) const
*m_array == *v.m_array;
}
+void GPRArrayValue::record_read(LiverangeEvaluator& ev) const
+{
+ if (m_addr) {
+ ev.record_read(*m_addr);
+ unsigned chan = m_value->chan();
+ assert(m_array);
+ m_array->record_read(ev, chan);
+ } else
+ ev.record_read(*m_value);
+}
+
+void GPRArrayValue::record_write(LiverangeEvaluator& ev) const
+{
+ if (m_addr) {
+ ev.record_read(*m_addr);
+ unsigned chan = m_value->chan();
+ assert(m_array);
+ m_array->record_write(ev, chan);
+ } else
+ ev.record_write(*m_value);
+}
+
void GPRArrayValue::reset_value(PValue new_value)
{
m_value = new_value;
@@ -305,6 +328,18 @@ PValue GPRArray::get_indirect(unsigned index, PValue indirect, unsigned componen
return v;
}
+void GPRArray::record_read(LiverangeEvaluator& ev, int chan) const
+{
+ for (auto& v: m_values)
+ ev.record_read(*v.reg_i(chan), true);
+}
+
+void GPRArray::record_write(LiverangeEvaluator& ev, int chan) const
+{
+ for (auto& v: m_values)
+ ev.record_write(*v.reg_i(chan), true);
+}
+
void GPRArray::collect_registers(ValueMap& output) const
{
for (auto& v: m_values) {
diff --git a/src/gallium/drivers/r600/sfn/sfn_value_gpr.h b/src/gallium/drivers/r600/sfn/sfn_value_gpr.h
index 2faf84aac1f..eee12c46c6a 100644
--- a/src/gallium/drivers/r600/sfn/sfn_value_gpr.h
+++ b/src/gallium/drivers/r600/sfn/sfn_value_gpr.h
@@ -34,6 +34,7 @@ namespace r600 {
class ValuePool;
class ValueMap;
+class LiverangeEvaluator;
class GPRValue : public Value {
public:
@@ -121,6 +122,9 @@ public:
PValue get_indirect(unsigned index, PValue indirect, unsigned component);
+ void record_read(LiverangeEvaluator& ev, int chan)const;
+ void record_write(LiverangeEvaluator& ev, int chan)const;
+
void collect_registers(ValueMap& output) const;
private:
@@ -142,6 +146,9 @@ public:
GPRArrayValue(PValue value, GPRArray *array);
GPRArrayValue(PValue value, PValue index, GPRArray *array);
+ void record_read(LiverangeEvaluator& ev) const;
+ void record_write(LiverangeEvaluator& ev) const;
+
size_t array_size() const;
uint32_t sel() const override;