|
|
@@ -0,0 +1,886 @@ |
|
|
|
/* |
|
|
|
* 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 <tgsi/tgsi_strings.h> |
|
|
|
#include <program/prog_instruction.h> |
|
|
|
#include <limits> |
|
|
|
#include <cstdlib> |
|
|
|
|
|
|
|
/* std::sort is significantly faster than qsort */ |
|
|
|
#define USE_STL_SORT |
|
|
|
#ifdef USE_STL_SORT |
|
|
|
#include <algorithm> |
|
|
|
#endif |
|
|
|
|
|
|
|
#ifndef NDEBUG |
|
|
|
#include <iostream> |
|
|
|
#include <iomanip> |
|
|
|
#include <program/prog_print.h> |
|
|
|
#include <util/debug.h> |
|
|
|
using std::cerr; |
|
|
|
using std::setw; |
|
|
|
#endif |
|
|
|
|
|
|
|
using std::numeric_limits; |
|
|
|
|
|
|
|
/* Without c++11 define the nullptr for forward-compatibility |
|
|
|
* and better readibility */ |
|
|
|
#if __cplusplus < 201103L |
|
|
|
#define nullptr 0 |
|
|
|
#endif |
|
|
|
|
|
|
|
#ifndef NDEBUG |
|
|
|
/* Helper function to check whether we want to seen debugging output */ |
|
|
|
static inline bool is_debug_enabled () |
|
|
|
{ |
|
|
|
static int debug_enabled = -1; |
|
|
|
if (debug_enabled < 0) |
|
|
|
debug_enabled = env_var_as_boolean("GLSL_TO_TGSI_RENAME_DEBUG", false); |
|
|
|
return debug_enabled > 0; |
|
|
|
} |
|
|
|
#define RENAME_DEBUG(X) if (is_debug_enabled()) do { X; } while (false); |
|
|
|
#else |
|
|
|
#define RENAME_DEBUG(X) |
|
|
|
#endif |
|
|
|
|
|
|
|
namespace { |
|
|
|
|
|
|
|
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 *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_ifelse_scope() const; |
|
|
|
const prog_scope *in_switchcase_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_conditional() const; |
|
|
|
bool is_conditional_in_loop() const; |
|
|
|
|
|
|
|
bool break_is_for_switchcase() const; |
|
|
|
bool contains_range_of(const prog_scope& other) const; |
|
|
|
const st_src_reg *switch_register() 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; |
|
|
|
const st_src_reg *switch_reg; |
|
|
|
}; |
|
|
|
|
|
|
|
/* Some storage class to encapsulate the prog_scope (de-)allocations */ |
|
|
|
class prog_scope_storage { |
|
|
|
public: |
|
|
|
prog_scope_storage(void *mem_ctx, int n); |
|
|
|
~prog_scope_storage(); |
|
|
|
prog_scope * create(prog_scope *p, prog_scope_type type, int id, |
|
|
|
int lvl, int s_begin); |
|
|
|
private: |
|
|
|
void *mem_ctx; |
|
|
|
int current_slot; |
|
|
|
prog_scope *storage; |
|
|
|
}; |
|
|
|
|
|
|
|
class temp_comp_access { |
|
|
|
public: |
|
|
|
temp_comp_access(); |
|
|
|
void record_read(int line, prog_scope *scope); |
|
|
|
void record_write(int line, prog_scope *scope); |
|
|
|
lifetime get_required_lifetime(); |
|
|
|
private: |
|
|
|
void propagate_lifetime_to_dominant_write_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; |
|
|
|
bool keep_for_full_loop; |
|
|
|
}; |
|
|
|
|
|
|
|
class temp_access { |
|
|
|
public: |
|
|
|
temp_access(); |
|
|
|
void record_read(int line, prog_scope *scope, int swizzle); |
|
|
|
void record_write(int line, prog_scope *scope, int writemask); |
|
|
|
lifetime get_required_lifetime(); |
|
|
|
private: |
|
|
|
void update_access_mask(int mask); |
|
|
|
|
|
|
|
temp_comp_access comp[4]; |
|
|
|
int access_mask; |
|
|
|
bool needs_component_tracking; |
|
|
|
}; |
|
|
|
|
|
|
|
prog_scope_storage::prog_scope_storage(void *mc, int n): |
|
|
|
mem_ctx(mc), |
|
|
|
current_slot(0) |
|
|
|
{ |
|
|
|
storage = ralloc_array(mem_ctx, prog_scope, n); |
|
|
|
} |
|
|
|
|
|
|
|
prog_scope_storage::~prog_scope_storage() |
|
|
|
{ |
|
|
|
ralloc_free(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), |
|
|
|
switch_reg(nullptr) |
|
|
|
{ |
|
|
|
} |
|
|
|
|
|
|
|
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; |
|
|
|
} |
|
|
|
|
|
|
|
bool prog_scope::is_conditional_in_loop() const |
|
|
|
{ |
|
|
|
return is_conditional() && is_in_loop(); |
|
|
|
} |
|
|
|
|
|
|
|
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; |
|
|
|
} |
|
|
|
|
|
|
|
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_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; |
|
|
|
} |
|
|
|
|
|
|
|
const st_src_reg *prog_scope::switch_register() const |
|
|
|
{ |
|
|
|
return switch_reg; |
|
|
|
} |
|
|
|
|
|
|
|
const prog_scope *prog_scope::in_switchcase_scope() const |
|
|
|
{ |
|
|
|
if (scope_type == switch_case_branch || |
|
|
|
scope_type == switch_default_branch) |
|
|
|
return this; |
|
|
|
|
|
|
|
if (parent_scope) |
|
|
|
return parent_scope->in_switchcase_scope(); |
|
|
|
|
|
|
|
return nullptr; |
|
|
|
} |
|
|
|
|
|
|
|
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) |
|
|
|
{ |
|
|
|
} |
|
|
|
|
|
|
|
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) |
|
|
|
{ |
|
|
|
update_access_mask(writemask); |
|
|
|
|
|
|
|
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 swizzle) |
|
|
|
{ |
|
|
|
int readmask = 0; |
|
|
|
for (int idx = 0; idx < 4; ++idx) { |
|
|
|
int swz = GET_SWZ(swizzle, idx); |
|
|
|
readmask |= (1 << swz) & 0xF; |
|
|
|
} |
|
|
|
update_access_mask(readmask); |
|
|
|
|
|
|
|
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 lifetime make_lifetime(int b, int e) |
|
|
|
{ |
|
|
|
lifetime lt; |
|
|
|
lt.begin = b; |
|
|
|
lt.end = e; |
|
|
|
return lt; |
|
|
|
} |
|
|
|
|
|
|
|
lifetime temp_access::get_required_lifetime() |
|
|
|
{ |
|
|
|
lifetime result = make_lifetime(-1, -1); |
|
|
|
|
|
|
|
unsigned mask = access_mask; |
|
|
|
while (mask) { |
|
|
|
unsigned chan = u_bit_scan(&mask); |
|
|
|
lifetime lt = comp[chan].get_required_lifetime(); |
|
|
|
|
|
|
|
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; |
|
|
|
} |
|
|
|
return result; |
|
|
|
} |
|
|
|
|
|
|
|
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()) |
|
|
|
{ |
|
|
|
} |
|
|
|
|
|
|
|
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; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
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; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
void temp_comp_access::propagate_lifetime_to_dominant_write_scope() |
|
|
|
{ |
|
|
|
first_write = first_write_scope->begin(); |
|
|
|
int lr = first_write_scope->end(); |
|
|
|
|
|
|
|
if (last_read < lr) |
|
|
|
last_read = lr; |
|
|
|
} |
|
|
|
|
|
|
|
lifetime temp_comp_access::get_required_lifetime() |
|
|
|
{ |
|
|
|
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_lifetime(-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_lifetime(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, but only if it is read outside |
|
|
|
* the condition scope where we write. |
|
|
|
*/ |
|
|
|
const prog_scope *conditional = enclosing_scope_first_write->enclosing_conditional(); |
|
|
|
if (conditional && conditional->is_in_loop() && |
|
|
|
!conditional->contains_range_of(*last_read_scope)) { |
|
|
|
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 (enclosing_scope_first_read->contains_range_of(*enclosing_scope)) |
|
|
|
enclosing_scope = enclosing_scope_first_read; |
|
|
|
|
|
|
|
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 life time 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 life time. |
|
|
|
*/ |
|
|
|
if (keep_for_full_loop && first_write_scope->is_loop()) |
|
|
|
propagate_lifetime_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 lifetime 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_lifetime_to_dominant_write_scope(); |
|
|
|
} |
|
|
|
|
|
|
|
first_write_scope = first_write_scope->parent(); |
|
|
|
|
|
|
|
/* Propagte lifetime if we are now in a loop */ |
|
|
|
if (keep_for_full_loop && first_write_scope->is_loop()) |
|
|
|
propagate_lifetime_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 |
|
|
|
* lifetime 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_lifetime(first_write, last_read); |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
#ifndef NDEBUG |
|
|
|
/* Function used for debugging. */ |
|
|
|
static void dump_instruction(int line, prog_scope *scope, |
|
|
|
const glsl_to_tgsi_instruction& inst); |
|
|
|
#endif |
|
|
|
|
|
|
|
/* Scan the program and estimate the required register life times. |
|
|
|
* The array lifetimes must be pre-allocated |
|
|
|
*/ |
|
|
|
void |
|
|
|
get_temp_registers_required_lifetimes(void *mem_ctx, exec_list *instructions, |
|
|
|
int ntemps, struct lifetime *lifetimes) |
|
|
|
{ |
|
|
|
int line = 0; |
|
|
|
int loop_id = 0; |
|
|
|
int if_id = 0; |
|
|
|
int switch_id = 0; |
|
|
|
bool is_at_end = false; |
|
|
|
int n_scopes = 1; |
|
|
|
|
|
|
|
/* Count scopes to allocate the needed space without the need for |
|
|
|
* re-allocation |
|
|
|
*/ |
|
|
|
foreach_in_list(glsl_to_tgsi_instruction, inst, instructions) { |
|
|
|
if (inst->op == TGSI_OPCODE_BGNLOOP || |
|
|
|
inst->op == TGSI_OPCODE_SWITCH || |
|
|
|
inst->op == TGSI_OPCODE_CASE || |
|
|
|
inst->op == TGSI_OPCODE_IF || |
|
|
|
inst->op == TGSI_OPCODE_UIF || |
|
|
|
inst->op == TGSI_OPCODE_ELSE || |
|
|
|
inst->op == TGSI_OPCODE_DEFAULT) |
|
|
|
++n_scopes; |
|
|
|
} |
|
|
|
|
|
|
|
prog_scope_storage scopes(mem_ctx, n_scopes); |
|
|
|
temp_access *acc = new temp_access[ntemps]; |
|
|
|
|
|
|
|
prog_scope *cur_scope = scopes.create(nullptr, outer_scope, 0, 0, line); |
|
|
|
|
|
|
|
RENAME_DEBUG(cerr << "========= Begin shader ============\n"); |
|
|
|
|
|
|
|
foreach_in_list(glsl_to_tgsi_instruction, inst, instructions) { |
|
|
|
if (is_at_end) { |
|
|
|
assert(!"GLSL_TO_TGSI: shader has instructions past end marker"); |
|
|
|
break; |
|
|
|
} |
|
|
|
|
|
|
|
RENAME_DEBUG(dump_instruction(line, cur_scope, *inst)); |
|
|
|
|
|
|
|
switch (inst->op) { |
|
|
|
case TGSI_OPCODE_BGNLOOP: { |
|
|
|
cur_scope = scopes.create(cur_scope, loop_body, loop_id++, |
|
|
|
cur_scope->nesting_depth() + 1, line); |
|
|
|
break; |
|
|
|
} |
|
|
|
case TGSI_OPCODE_ENDLOOP: { |
|
|
|
cur_scope->set_end(line); |
|
|
|
cur_scope = cur_scope->parent(); |
|
|
|
assert(cur_scope); |
|
|
|
break; |
|
|
|
} |
|
|
|
case TGSI_OPCODE_IF: |
|
|
|
case TGSI_OPCODE_UIF: { |
|
|
|
assert(num_inst_src_regs(inst) == 1); |
|
|
|
const st_src_reg& src = inst->src[0]; |
|
|
|
if (src.file == PROGRAM_TEMPORARY) |
|
|
|
acc[src.index].record_read(line, cur_scope, src.swizzle); |
|
|
|
cur_scope = scopes.create(cur_scope, if_branch, if_id++, |
|
|
|
cur_scope->nesting_depth() + 1, line + 1); |
|
|
|
break; |
|
|
|
} |
|
|
|
case TGSI_OPCODE_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); |
|
|
|
break; |
|
|
|
} |
|
|
|
case TGSI_OPCODE_END: { |
|
|
|
cur_scope->set_end(line); |
|
|
|
is_at_end = true; |
|
|
|
break; |
|
|
|
} |
|
|
|
case TGSI_OPCODE_ENDIF: { |
|
|
|
cur_scope->set_end(line - 1); |
|
|
|
cur_scope = cur_scope->parent(); |
|
|
|
assert(cur_scope); |
|
|
|
break; |
|
|
|
} |
|
|
|
case TGSI_OPCODE_SWITCH: { |
|
|
|
assert(num_inst_src_regs(inst) == 1); |
|
|
|
const st_src_reg& src = inst->src[0]; |
|
|
|
prog_scope *scope = scopes.create(cur_scope, switch_body, switch_id++, |
|
|
|
cur_scope->nesting_depth() + 1, line); |
|
|
|
/* We record the read only for the SWITCH statement itself, like it |
|
|
|
* is used by the only consumer of TGSI_OPCODE_SWITCH in tgsi_exec.c. |
|
|
|
*/ |
|
|
|
if (src.file == PROGRAM_TEMPORARY) |
|
|
|
acc[src.index].record_read(line, cur_scope, src.swizzle); |
|
|
|
cur_scope = scope; |
|
|
|
break; |
|
|
|
} |
|
|
|
case TGSI_OPCODE_ENDSWITCH: { |
|
|
|
cur_scope->set_end(line - 1); |
|
|
|
/* Remove the case level, it might not have been |
|
|
|
* closed with a break. |
|
|
|
*/ |
|
|
|
if (cur_scope->type() != switch_body) |
|
|
|
cur_scope = cur_scope->parent(); |
|
|
|
|
|
|
|
cur_scope = cur_scope->parent(); |
|
|
|
assert(cur_scope); |
|
|
|
break; |
|
|
|
} |
|
|
|
case TGSI_OPCODE_CASE: { |
|
|
|
/* Take care of tracking the registers. */ |
|
|
|
prog_scope *switch_scope = cur_scope->type() == switch_body ? |
|
|
|
cur_scope : cur_scope->parent(); |
|
|
|
|
|
|
|
assert(num_inst_src_regs(inst) == 1); |
|
|
|
const st_src_reg& src = inst->src[0]; |
|
|
|
if (src.file == PROGRAM_TEMPORARY) |
|
|
|
acc[src.index].record_read(line, switch_scope, src.swizzle); |
|
|
|
|
|
|
|
/* Fall through to allocate the scope. */ |
|
|
|
} |
|
|
|
case TGSI_OPCODE_DEFAULT: { |
|
|
|
prog_scope_type t = inst->op == TGSI_OPCODE_CASE ? switch_case_branch |
|
|
|
: switch_default_branch; |
|
|
|
prog_scope *switch_scope = (cur_scope->type() == switch_body) ? |
|
|
|
cur_scope : cur_scope->parent(); |
|
|
|
assert(switch_scope->type() == switch_body); |
|
|
|
prog_scope *scope = scopes.create(switch_scope, t, |
|
|
|
switch_scope->id(), |
|
|
|
switch_scope->nesting_depth() + 1, |
|
|
|
line); |
|
|
|
/* Previous case falls through, so scope was not yet closed. */ |
|
|
|
if ((cur_scope != switch_scope) && (cur_scope->end() == -1)) |
|
|
|
cur_scope->set_end(line - 1); |
|
|
|
cur_scope = scope; |
|
|
|
break; |
|
|
|
} |
|
|
|
case TGSI_OPCODE_BRK: { |
|
|
|
if (cur_scope->break_is_for_switchcase()) { |
|
|
|
cur_scope->set_end(line - 1); |
|
|
|
} else { |
|
|
|
cur_scope->set_loop_break_line(line); |
|
|
|
} |
|
|
|
break; |
|
|
|
} |
|
|
|
default: { |
|
|
|
for (unsigned j = 0; j < num_inst_src_regs(inst); j++) { |
|
|
|
const st_src_reg& src = inst->src[j]; |
|
|
|
if (src.file == PROGRAM_TEMPORARY) |
|
|
|
acc[src.index].record_read(line, cur_scope, src.swizzle); |
|
|
|
} |
|
|
|
for (unsigned j = 0; j < inst->tex_offset_num_offset; j++) { |
|
|
|
const st_src_reg& src = inst->tex_offsets[j]; |
|
|
|
if (src.file == PROGRAM_TEMPORARY) |
|
|
|
acc[src.index].record_read(line, cur_scope, src.swizzle); |
|
|
|
} |
|
|
|
for (unsigned j = 0; j < num_inst_dst_regs(inst); j++) { |
|
|
|
const st_dst_reg& dst = inst->dst[j]; |
|
|
|
if (dst.file == PROGRAM_TEMPORARY) |
|
|
|
acc[dst.index].record_write(line, cur_scope, dst.writemask); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
++line; |
|
|
|
} |
|
|
|
|
|
|
|
RENAME_DEBUG(cerr << "==================================\n\n"); |
|
|
|
|
|
|
|
/* Make sure last scope is closed, even though no |
|
|
|
* TGSI_OPCODE_END was given. |
|
|
|
*/ |
|
|
|
if (cur_scope->end() < 0) |
|
|
|
cur_scope->set_end(line - 1); |
|
|
|
|
|
|
|
RENAME_DEBUG(cerr << "========= lifetimes ==============\n"); |
|
|
|
for(int i = 0; i < ntemps; ++i) { |
|
|
|
RENAME_DEBUG(cerr << setw(4) << i); |
|
|
|
lifetimes[i] = acc[i].get_required_lifetime(); |
|
|
|
RENAME_DEBUG(cerr << ": [" << lifetimes[i].begin << ", " |
|
|
|
<< lifetimes[i].end << "]\n"); |
|
|
|
} |
|
|
|
RENAME_DEBUG(cerr << "==================================\n\n"); |
|
|
|
|
|
|
|
delete[] acc; |
|
|
|
} |
|
|
|
|
|
|
|
/* Code below used for debugging */ |
|
|
|
#ifndef NDEBUG |
|
|
|
static const char swizzle_txt[] = "xyzw"; |
|
|
|
|
|
|
|
static const char *tgsi_file_names[PROGRAM_FILE_MAX] = { |
|
|
|
"TEMP", "ARRAY", "IN", "OUT", "STATE", "CONST", |
|
|
|
"UNIFORM", "WO", "ADDR", "SAMPLER", "SV", "UNDEF", |
|
|
|
"IMM", "BUF", "MEM", "IMAGE" |
|
|
|
}; |
|
|
|
|
|
|
|
static |
|
|
|
void dump_instruction(int line, prog_scope *scope, |
|
|
|
const glsl_to_tgsi_instruction& inst) |
|
|
|
{ |
|
|
|
const struct tgsi_opcode_info *info = tgsi_get_opcode_info(inst.op); |
|
|
|
|
|
|
|
int indent = scope->nesting_depth(); |
|
|
|
if ((scope->type() == switch_case_branch || |
|
|
|
scope->type() == switch_default_branch) && |
|
|
|
(info->opcode == TGSI_OPCODE_CASE || |
|
|
|
info->opcode == TGSI_OPCODE_DEFAULT)) |
|
|
|
--indent; |
|
|
|
|
|
|
|
if (info->opcode == TGSI_OPCODE_ENDIF || |
|
|
|
info->opcode == TGSI_OPCODE_ELSE || |
|
|
|
info->opcode == TGSI_OPCODE_ENDLOOP || |
|
|
|
info->opcode == TGSI_OPCODE_ENDSWITCH) |
|
|
|
--indent; |
|
|
|
|
|
|
|
cerr << setw(4) << line << ": "; |
|
|
|
for (int i = 0; i < indent; ++i) |
|
|
|
cerr << " "; |
|
|
|
cerr << tgsi_get_opcode_name(info->opcode) << " "; |
|
|
|
|
|
|
|
bool has_operators = false; |
|
|
|
for (unsigned j = 0; j < num_inst_dst_regs(&inst); j++) { |
|
|
|
has_operators = true; |
|
|
|
if (j > 0) |
|
|
|
cerr << ", "; |
|
|
|
|
|
|
|
const st_dst_reg& dst = inst.dst[j]; |
|
|
|
cerr << tgsi_file_names[dst.file]; |
|
|
|
|
|
|
|
if (dst.file == PROGRAM_ARRAY) |
|
|
|
cerr << "(" << dst.array_id << ")"; |
|
|
|
|
|
|
|
cerr << "[" << dst.index << "]"; |
|
|
|
|
|
|
|
if (dst.writemask != TGSI_WRITEMASK_XYZW) { |
|
|
|
cerr << "."; |
|
|
|
if (dst.writemask & TGSI_WRITEMASK_X) cerr << "x"; |
|
|
|
if (dst.writemask & TGSI_WRITEMASK_Y) cerr << "y"; |
|
|
|
if (dst.writemask & TGSI_WRITEMASK_Z) cerr << "z"; |
|
|
|
if (dst.writemask & TGSI_WRITEMASK_W) cerr << "w"; |
|
|
|
} |
|
|
|
} |
|
|
|
if (has_operators) |
|
|
|
cerr << " := "; |
|
|
|
|
|
|
|
for (unsigned j = 0; j < num_inst_src_regs(&inst); j++) { |
|
|
|
if (j > 0) |
|
|
|
cerr << ", "; |
|
|
|
|
|
|
|
const st_src_reg& src = inst.src[j]; |
|
|
|
cerr << tgsi_file_names[src.file] |
|
|
|
<< "[" << src.index << "]"; |
|
|
|
if (src.swizzle != SWIZZLE_XYZW) { |
|
|
|
cerr << "."; |
|
|
|
for (int idx = 0; idx < 4; ++idx) { |
|
|
|
int swz = GET_SWZ(src.swizzle, idx); |
|
|
|
if (swz < 4) { |
|
|
|
cerr << swizzle_txt[swz]; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (inst.tex_offset_num_offset > 0) { |
|
|
|
cerr << ", TEXOFS: "; |
|
|
|
for (unsigned j = 0; j < inst.tex_offset_num_offset; j++) { |
|
|
|
if (j > 0) |
|
|
|
cerr << ", "; |
|
|
|
|
|
|
|
const st_src_reg& src = inst.tex_offsets[j]; |
|
|
|
cerr << tgsi_file_names[src.file] |
|
|
|
<< "[" << src.index << "]"; |
|
|
|
if (src.swizzle != SWIZZLE_XYZW) { |
|
|
|
cerr << "."; |
|
|
|
for (int idx = 0; idx < 4; ++idx) { |
|
|
|
int swz = GET_SWZ(src.swizzle, idx); |
|
|
|
if (swz < 4) { |
|
|
|
cerr << swizzle_txt[swz]; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
cerr << "\n"; |
|
|
|
} |
|
|
|
#endif |