|
|
|
@@ -0,0 +1,939 @@
|
|
|
|
|
/*
|
|
|
|
|
* Copyright © 2019 Intel Corporation
|
|
|
|
|
*
|
|
|
|
|
* 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.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/** @file brw_fs_scoreboard.cpp
|
|
|
|
|
*
|
|
|
|
|
* Gen12+ hardware lacks the register scoreboard logic that used to guarantee
|
|
|
|
|
* data coherency between register reads and writes in previous generations.
|
|
|
|
|
* This lowering pass runs after register allocation in order to make up for
|
|
|
|
|
* it.
|
|
|
|
|
*
|
|
|
|
|
* It works by performing global dataflow analysis in order to determine the
|
|
|
|
|
* set of potential dependencies of every instruction in the shader, and then
|
|
|
|
|
* inserts any required SWSB annotations and additional SYNC instructions in
|
|
|
|
|
* order to guarantee data coherency.
|
|
|
|
|
*
|
|
|
|
|
* WARNING - Access of the following (rarely used) ARF registers is not
|
|
|
|
|
* tracked here, and require the RegDist SWSB annotation to be set
|
|
|
|
|
* to 1 by the generator in order to avoid data races:
|
|
|
|
|
*
|
|
|
|
|
* - sp stack pointer
|
|
|
|
|
* - sr0 state register
|
|
|
|
|
* - cr0 control register
|
|
|
|
|
* - ip instruction pointer
|
|
|
|
|
* - tm0 timestamp register
|
|
|
|
|
* - dbg0 debug register
|
|
|
|
|
*
|
|
|
|
|
* The following ARF registers don't need to be tracked here because data
|
|
|
|
|
* coherency is still provided transparently by the hardware:
|
|
|
|
|
*
|
|
|
|
|
* - f0-1 flag registers
|
|
|
|
|
* - n0 notification register
|
|
|
|
|
* - tdr0 thread dependency register
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include <tuple>
|
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
|
|
#include "brw_fs.h"
|
|
|
|
|
#include "brw_cfg.h"
|
|
|
|
|
|
|
|
|
|
using namespace brw;
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
/**
|
|
|
|
|
* In-order instruction accounting.
|
|
|
|
|
* @{
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Number of in-order hardware instructions contained in this IR
|
|
|
|
|
* instruction. This determines the increment applied to the RegDist
|
|
|
|
|
* counter calculated for any ordered dependency that crosses this
|
|
|
|
|
* instruction.
|
|
|
|
|
*/
|
|
|
|
|
unsigned
|
|
|
|
|
ordered_unit(const fs_inst *inst)
|
|
|
|
|
{
|
|
|
|
|
switch (inst->opcode) {
|
|
|
|
|
case BRW_OPCODE_SYNC:
|
|
|
|
|
case BRW_OPCODE_DO:
|
|
|
|
|
case SHADER_OPCODE_UNDEF:
|
|
|
|
|
case FS_OPCODE_PLACEHOLDER_HALT:
|
|
|
|
|
return 0;
|
|
|
|
|
default:
|
|
|
|
|
/* Note that the following is inaccurate for virtual instructions
|
|
|
|
|
* that expand to more in-order instructions than assumed here, but
|
|
|
|
|
* that can only lead to suboptimal execution ordering, data
|
|
|
|
|
* coherency won't be impacted. Providing exact RegDist counts for
|
|
|
|
|
* each virtual instruction would allow better ALU performance, but
|
|
|
|
|
* it would require keeping this switch statement in perfect sync
|
|
|
|
|
* with the generator in order to avoid data corruption. Lesson is
|
|
|
|
|
* (again) don't use virtual instructions if you want optimal
|
|
|
|
|
* scheduling.
|
|
|
|
|
*/
|
|
|
|
|
return is_unordered(inst) ? 0 : 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Type for an instruction counter that increments for in-order
|
|
|
|
|
* instructions only, arbitrarily denoted 'jp' throughout this lowering
|
|
|
|
|
* pass in order to distinguish it from the regular instruction counter.
|
|
|
|
|
*/
|
|
|
|
|
typedef int ordered_address;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Calculate the local ordered_address instruction counter at every
|
|
|
|
|
* instruction of the shader for subsequent constant-time look-up.
|
|
|
|
|
*/
|
|
|
|
|
std::vector<ordered_address>
|
|
|
|
|
ordered_inst_addresses(const fs_visitor *shader)
|
|
|
|
|
{
|
|
|
|
|
std::vector<ordered_address> jps;
|
|
|
|
|
ordered_address jp = 0;
|
|
|
|
|
|
|
|
|
|
foreach_block_and_inst(block, fs_inst, inst, shader->cfg) {
|
|
|
|
|
jps.push_back(jp);
|
|
|
|
|
jp += ordered_unit(inst);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return jps;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Synchronization mode required for data manipulated by in-order
|
|
|
|
|
* instructions.
|
|
|
|
|
*
|
|
|
|
|
* Similar to tgl_sbid_mode, but without SET mode. Defined as a separate
|
|
|
|
|
* enum for additional type safety. The hardware doesn't provide control
|
|
|
|
|
* over the synchronization mode for RegDist annotations, this is only used
|
|
|
|
|
* internally in this pass in order to optimize out redundant read
|
|
|
|
|
* dependencies where possible.
|
|
|
|
|
*/
|
|
|
|
|
enum tgl_regdist_mode {
|
|
|
|
|
TGL_REGDIST_NULL = 0,
|
|
|
|
|
TGL_REGDIST_SRC = 1,
|
|
|
|
|
TGL_REGDIST_DST = 2
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Allow bitwise arithmetic of tgl_regdist_mode enums.
|
|
|
|
|
*/
|
|
|
|
|
tgl_regdist_mode
|
|
|
|
|
operator|(tgl_regdist_mode x, tgl_regdist_mode y)
|
|
|
|
|
{
|
|
|
|
|
return tgl_regdist_mode(unsigned(x) | unsigned(y));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tgl_regdist_mode
|
|
|
|
|
operator&(tgl_regdist_mode x, tgl_regdist_mode y)
|
|
|
|
|
{
|
|
|
|
|
return tgl_regdist_mode(unsigned(x) & unsigned(y));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tgl_regdist_mode &
|
|
|
|
|
operator|=(tgl_regdist_mode &x, tgl_regdist_mode y)
|
|
|
|
|
{
|
|
|
|
|
return x = x | y;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tgl_regdist_mode &
|
|
|
|
|
operator&=(tgl_regdist_mode &x, tgl_regdist_mode y)
|
|
|
|
|
{
|
|
|
|
|
return x = x & y;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @} */
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Representation of an equivalence relation among the set of unsigned
|
|
|
|
|
* integers.
|
|
|
|
|
*
|
|
|
|
|
* Its initial state is the identity relation '~' such that i ~ j if and
|
|
|
|
|
* only if i == j for every pair of unsigned integers i and j.
|
|
|
|
|
*/
|
|
|
|
|
struct equivalence_relation {
|
|
|
|
|
/**
|
|
|
|
|
* Return equivalence class index of the specified element. Effectively
|
|
|
|
|
* this is the numeric value of an arbitrary representative from the
|
|
|
|
|
* equivalence class.
|
|
|
|
|
*
|
|
|
|
|
* Allows the evaluation of the equivalence relation according to the
|
|
|
|
|
* rule that i ~ j if and only if lookup(i) == lookup(j).
|
|
|
|
|
*/
|
|
|
|
|
unsigned
|
|
|
|
|
lookup(unsigned i) const
|
|
|
|
|
{
|
|
|
|
|
if (i < is.size() && is[i] != i)
|
|
|
|
|
return lookup(is[i]);
|
|
|
|
|
else
|
|
|
|
|
return i;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Create an array with the results of the lookup() method for
|
|
|
|
|
* constant-time evaluation.
|
|
|
|
|
*/
|
|
|
|
|
std::vector<unsigned>
|
|
|
|
|
flatten() const {
|
|
|
|
|
std::vector<unsigned> ids;
|
|
|
|
|
|
|
|
|
|
for (const auto i : is)
|
|
|
|
|
ids.push_back(lookup(i));
|
|
|
|
|
|
|
|
|
|
return ids;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Mutate the existing equivalence relation minimally by imposing the
|
|
|
|
|
* additional requirement that i ~ j.
|
|
|
|
|
*
|
|
|
|
|
* The algorithm updates the internal representation recursively in
|
|
|
|
|
* order to guarantee transitivity while preserving the previously
|
|
|
|
|
* specified equivalence requirements.
|
|
|
|
|
*/
|
|
|
|
|
unsigned
|
|
|
|
|
link(unsigned i, unsigned j)
|
|
|
|
|
{
|
|
|
|
|
const unsigned k = lookup(i);
|
|
|
|
|
assign(i, k);
|
|
|
|
|
assign(j, k);
|
|
|
|
|
return k;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
/**
|
|
|
|
|
* Assign the representative of \p from to be equivalent to \p to.
|
|
|
|
|
*
|
|
|
|
|
* At the same time the data structure is partially flattened as much as
|
|
|
|
|
* it's possible without increasing the number of recursive calls.
|
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
assign(unsigned from, unsigned to)
|
|
|
|
|
{
|
|
|
|
|
if (from != to) {
|
|
|
|
|
if (from < is.size() && is[from] != from)
|
|
|
|
|
assign(is[from], to);
|
|
|
|
|
|
|
|
|
|
for (unsigned i = is.size(); i <= from; i++)
|
|
|
|
|
is.push_back(i);
|
|
|
|
|
|
|
|
|
|
is[from] = to;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<unsigned> is;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Representation of a data dependency between two instructions in the
|
|
|
|
|
* program.
|
|
|
|
|
* @{
|
|
|
|
|
*/
|
|
|
|
|
struct dependency {
|
|
|
|
|
/**
|
|
|
|
|
* No dependency information.
|
|
|
|
|
*/
|
|
|
|
|
dependency() : ordered(TGL_REGDIST_NULL), jp(INT_MIN),
|
|
|
|
|
unordered(TGL_SBID_NULL), id(0) {}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Construct a dependency on the in-order instruction with the provided
|
|
|
|
|
* ordered_address instruction counter.
|
|
|
|
|
*/
|
|
|
|
|
dependency(tgl_regdist_mode mode, ordered_address jp) :
|
|
|
|
|
ordered(mode), jp(jp), unordered(TGL_SBID_NULL), id(0) {}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Construct a dependency on the out-of-order instruction with the
|
|
|
|
|
* specified synchronization token.
|
|
|
|
|
*/
|
|
|
|
|
dependency(tgl_sbid_mode mode, unsigned id) :
|
|
|
|
|
ordered(TGL_REGDIST_NULL), jp(INT_MIN), unordered(mode), id(id) {}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Synchronization mode of in-order dependency, or zero if no in-order
|
|
|
|
|
* dependency is present.
|
|
|
|
|
*/
|
|
|
|
|
tgl_regdist_mode ordered;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Instruction counter of in-order dependency.
|
|
|
|
|
*
|
|
|
|
|
* For a dependency part of a different block in the program, this is
|
|
|
|
|
* relative to the specific control flow path taken between the
|
|
|
|
|
* dependency and the current block: It is the ordered_address such that
|
|
|
|
|
* the difference between it and the ordered_address of the first
|
|
|
|
|
* instruction of the current block is exactly the number of in-order
|
|
|
|
|
* instructions across that control flow path. It is not guaranteed to
|
|
|
|
|
* be equal to the local ordered_address of the generating instruction
|
|
|
|
|
* [as returned by ordered_inst_addresses()], except for block-local
|
|
|
|
|
* dependencies.
|
|
|
|
|
*/
|
|
|
|
|
ordered_address jp;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Synchronization mode of unordered dependency, or zero if no unordered
|
|
|
|
|
* dependency is present.
|
|
|
|
|
*/
|
|
|
|
|
tgl_sbid_mode unordered;
|
|
|
|
|
|
|
|
|
|
/** Synchronization token of out-of-order dependency. */
|
|
|
|
|
unsigned id;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Trivial in-order dependency that's always satisfied.
|
|
|
|
|
*
|
|
|
|
|
* Note that unlike a default-constructed dependency() which is also
|
|
|
|
|
* trivially satisfied, this is considered to provide dependency
|
|
|
|
|
* information and can be used to clear a previously pending dependency
|
|
|
|
|
* via shadow().
|
|
|
|
|
*/
|
|
|
|
|
static const dependency done;
|
|
|
|
|
|
|
|
|
|
friend bool
|
|
|
|
|
operator==(const dependency &dep0, const dependency &dep1)
|
|
|
|
|
{
|
|
|
|
|
return dep0.ordered == dep1.ordered &&
|
|
|
|
|
dep0.jp == dep1.jp &&
|
|
|
|
|
dep0.unordered == dep1.unordered &&
|
|
|
|
|
dep0.id == dep1.id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
friend bool
|
|
|
|
|
operator!=(const dependency &dep0, const dependency &dep1)
|
|
|
|
|
{
|
|
|
|
|
return !(dep0 == dep1);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const dependency dependency::done = dependency(TGL_REGDIST_SRC, INT_MIN);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Return whether \p dep contains any dependency information.
|
|
|
|
|
*/
|
|
|
|
|
bool
|
|
|
|
|
is_valid(const dependency &dep)
|
|
|
|
|
{
|
|
|
|
|
return dep.ordered || dep.unordered;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Combine \p dep0 and \p dep1 into a single dependency object that is only
|
|
|
|
|
* satisfied when both original dependencies are satisfied. This might
|
|
|
|
|
* involve updating the equivalence relation \p eq in order to make sure
|
|
|
|
|
* that both out-of-order dependencies are assigned the same hardware SBID
|
|
|
|
|
* as synchronization token.
|
|
|
|
|
*/
|
|
|
|
|
dependency
|
|
|
|
|
merge(equivalence_relation &eq,
|
|
|
|
|
const dependency &dep0, const dependency &dep1)
|
|
|
|
|
{
|
|
|
|
|
dependency dep;
|
|
|
|
|
|
|
|
|
|
if (dep0.ordered || dep1.ordered) {
|
|
|
|
|
dep.ordered = dep0.ordered | dep1.ordered;
|
|
|
|
|
dep.jp = MAX2(dep0.jp, dep1.jp);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (dep0.unordered || dep1.unordered) {
|
|
|
|
|
dep.unordered = dep0.unordered | dep1.unordered;
|
|
|
|
|
dep.id = eq.link(dep0.unordered ? dep0.id : dep1.id,
|
|
|
|
|
dep1.unordered ? dep1.id : dep0.id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return dep;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Override dependency information of \p dep0 with that of \p dep1.
|
|
|
|
|
*/
|
|
|
|
|
dependency
|
|
|
|
|
shadow(const dependency &dep0, const dependency &dep1)
|
|
|
|
|
{
|
|
|
|
|
return is_valid(dep1) ? dep1 : dep0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Translate dependency information across the program.
|
|
|
|
|
*
|
|
|
|
|
* This returns a dependency on the same instruction translated to the
|
|
|
|
|
* ordered_address space of a different block. The correct shift for
|
|
|
|
|
* transporting a dependency across an edge of the CFG is the difference
|
|
|
|
|
* between the local ordered_address of the first instruction of the target
|
|
|
|
|
* block and the local ordered_address of the instruction immediately after
|
|
|
|
|
* the end of the origin block.
|
|
|
|
|
*/
|
|
|
|
|
dependency
|
|
|
|
|
transport(dependency dep, int delta)
|
|
|
|
|
{
|
|
|
|
|
if (dep.ordered && dep.jp > INT_MIN)
|
|
|
|
|
dep.jp += delta;
|
|
|
|
|
|
|
|
|
|
return dep;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Return simplified dependency removing any synchronization modes not
|
|
|
|
|
* applicable to an instruction reading the same register location.
|
|
|
|
|
*/
|
|
|
|
|
dependency
|
|
|
|
|
dependency_for_read(dependency dep)
|
|
|
|
|
{
|
|
|
|
|
dep.ordered &= TGL_REGDIST_DST;
|
|
|
|
|
return dep;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Return simplified dependency removing any synchronization modes not
|
|
|
|
|
* applicable to an instruction \p inst writing the same register location.
|
|
|
|
|
*/
|
|
|
|
|
dependency
|
|
|
|
|
dependency_for_write(const fs_inst *inst, dependency dep)
|
|
|
|
|
{
|
|
|
|
|
if (!is_unordered(inst))
|
|
|
|
|
dep.ordered &= TGL_REGDIST_DST;
|
|
|
|
|
return dep;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @} */
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Scoreboard representation. This keeps track of the data dependencies of
|
|
|
|
|
* registers with GRF granularity.
|
|
|
|
|
*/
|
|
|
|
|
class scoreboard {
|
|
|
|
|
public:
|
|
|
|
|
/**
|
|
|
|
|
* Look up the most current data dependency for register \p r.
|
|
|
|
|
*/
|
|
|
|
|
dependency
|
|
|
|
|
get(const fs_reg &r) const
|
|
|
|
|
{
|
|
|
|
|
if (const dependency *p = const_cast<scoreboard *>(this)->dep(r))
|
|
|
|
|
return *p;
|
|
|
|
|
else
|
|
|
|
|
return dependency();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Specify the most current data dependency for register \p r.
|
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
set(const fs_reg &r, const dependency &d)
|
|
|
|
|
{
|
|
|
|
|
if (dependency *p = dep(r))
|
|
|
|
|
*p = d;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Component-wise merge() of corresponding dependencies from two
|
|
|
|
|
* scoreboard objects. \sa merge().
|
|
|
|
|
*/
|
|
|
|
|
friend scoreboard
|
|
|
|
|
merge(equivalence_relation &eq,
|
|
|
|
|
const scoreboard &sb0, const scoreboard &sb1)
|
|
|
|
|
{
|
|
|
|
|
scoreboard sb;
|
|
|
|
|
|
|
|
|
|
for (unsigned i = 0; i < ARRAY_SIZE(sb.grf_deps); i++)
|
|
|
|
|
sb.grf_deps[i] = merge(eq, sb0.grf_deps[i], sb1.grf_deps[i]);
|
|
|
|
|
|
|
|
|
|
sb.addr_dep = merge(eq, sb0.addr_dep, sb1.addr_dep);
|
|
|
|
|
|
|
|
|
|
for (unsigned i = 0; i < ARRAY_SIZE(sb.accum_deps); i++)
|
|
|
|
|
sb.accum_deps[i] = merge(eq, sb0.accum_deps[i], sb1.accum_deps[i]);
|
|
|
|
|
|
|
|
|
|
return sb;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Component-wise shadow() of corresponding dependencies from two
|
|
|
|
|
* scoreboard objects. \sa shadow().
|
|
|
|
|
*/
|
|
|
|
|
friend scoreboard
|
|
|
|
|
shadow(const scoreboard &sb0, const scoreboard &sb1)
|
|
|
|
|
{
|
|
|
|
|
scoreboard sb;
|
|
|
|
|
|
|
|
|
|
for (unsigned i = 0; i < ARRAY_SIZE(sb.grf_deps); i++)
|
|
|
|
|
sb.grf_deps[i] = shadow(sb0.grf_deps[i], sb1.grf_deps[i]);
|
|
|
|
|
|
|
|
|
|
sb.addr_dep = shadow(sb0.addr_dep, sb1.addr_dep);
|
|
|
|
|
|
|
|
|
|
for (unsigned i = 0; i < ARRAY_SIZE(sb.accum_deps); i++)
|
|
|
|
|
sb.accum_deps[i] = shadow(sb0.accum_deps[i], sb1.accum_deps[i]);
|
|
|
|
|
|
|
|
|
|
return sb;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Component-wise transport() of dependencies from a scoreboard
|
|
|
|
|
* object. \sa transport().
|
|
|
|
|
*/
|
|
|
|
|
friend scoreboard
|
|
|
|
|
transport(const scoreboard &sb0, int delta)
|
|
|
|
|
{
|
|
|
|
|
scoreboard sb;
|
|
|
|
|
|
|
|
|
|
for (unsigned i = 0; i < ARRAY_SIZE(sb.grf_deps); i++)
|
|
|
|
|
sb.grf_deps[i] = transport(sb0.grf_deps[i], delta);
|
|
|
|
|
|
|
|
|
|
sb.addr_dep = transport(sb0.addr_dep, delta);
|
|
|
|
|
|
|
|
|
|
for (unsigned i = 0; i < ARRAY_SIZE(sb.accum_deps); i++)
|
|
|
|
|
sb.accum_deps[i] = transport(sb0.accum_deps[i], delta);
|
|
|
|
|
|
|
|
|
|
return sb;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
friend bool
|
|
|
|
|
operator==(const scoreboard &sb0, const scoreboard &sb1)
|
|
|
|
|
{
|
|
|
|
|
for (unsigned i = 0; i < ARRAY_SIZE(sb0.grf_deps); i++) {
|
|
|
|
|
if (sb0.grf_deps[i] != sb1.grf_deps[i])
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (sb0.addr_dep != sb1.addr_dep)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
for (unsigned i = 0; i < ARRAY_SIZE(sb0.accum_deps); i++) {
|
|
|
|
|
if (sb0.accum_deps[i] != sb1.accum_deps[i])
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
friend bool
|
|
|
|
|
operator!=(const scoreboard &sb0, const scoreboard &sb1)
|
|
|
|
|
{
|
|
|
|
|
return !(sb0 == sb1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
dependency grf_deps[BRW_MAX_GRF];
|
|
|
|
|
dependency addr_dep;
|
|
|
|
|
dependency accum_deps[10];
|
|
|
|
|
|
|
|
|
|
dependency *
|
|
|
|
|
dep(const fs_reg &r)
|
|
|
|
|
{
|
|
|
|
|
const unsigned reg = (r.file == VGRF ? r.nr + r.offset / REG_SIZE :
|
|
|
|
|
reg_offset(r) / REG_SIZE);
|
|
|
|
|
|
|
|
|
|
return (r.file == VGRF || r.file == FIXED_GRF ? &grf_deps[reg] :
|
|
|
|
|
r.file == MRF ? &grf_deps[GEN7_MRF_HACK_START + reg] :
|
|
|
|
|
r.file == ARF && reg >= BRW_ARF_ADDRESS &&
|
|
|
|
|
reg < BRW_ARF_ACCUMULATOR ? &addr_dep :
|
|
|
|
|
r.file == ARF && reg >= BRW_ARF_ACCUMULATOR &&
|
|
|
|
|
reg < BRW_ARF_FLAG ? &accum_deps[
|
|
|
|
|
reg - BRW_ARF_ACCUMULATOR] :
|
|
|
|
|
NULL);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Dependency list handling.
|
|
|
|
|
* @{
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Add dependency \p dep to the list of dependencies of an instruction
|
|
|
|
|
* \p deps.
|
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
add_dependency(const std::vector<unsigned> &ids,
|
|
|
|
|
std::vector<dependency> &deps, dependency dep)
|
|
|
|
|
{
|
|
|
|
|
if (is_valid(dep)) {
|
|
|
|
|
/* Translate the unordered dependency token first in order to keep
|
|
|
|
|
* the list minimally redundant.
|
|
|
|
|
*/
|
|
|
|
|
if (dep.unordered && dep.id < ids.size())
|
|
|
|
|
dep.id = ids[dep.id];
|
|
|
|
|
|
|
|
|
|
/* Try to combine the specified dependency with any existing ones. */
|
|
|
|
|
for (auto &dep1 : deps) {
|
|
|
|
|
if (dep.ordered && dep1.ordered) {
|
|
|
|
|
dep1.jp = MAX2(dep1.jp, dep.jp);
|
|
|
|
|
dep1.ordered |= dep.ordered;
|
|
|
|
|
dep.ordered = TGL_REGDIST_NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (dep.unordered && dep1.unordered && dep1.id == dep.id) {
|
|
|
|
|
dep1.unordered |= dep.unordered;
|
|
|
|
|
dep.unordered = TGL_SBID_NULL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Add it to the end of the list if necessary. */
|
|
|
|
|
if (is_valid(dep))
|
|
|
|
|
deps.push_back(dep);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Construct a tgl_swsb annotation encoding any ordered dependencies from
|
|
|
|
|
* the dependency list \p deps of an instruction with ordered_address
|
|
|
|
|
* \p jp.
|
|
|
|
|
*/
|
|
|
|
|
tgl_swsb
|
|
|
|
|
ordered_dependency_swsb(const std::vector<dependency> &deps,
|
|
|
|
|
const ordered_address &jp)
|
|
|
|
|
{
|
|
|
|
|
unsigned min_dist = ~0u;
|
|
|
|
|
|
|
|
|
|
for (const auto &dep : deps) {
|
|
|
|
|
if (dep.ordered) {
|
|
|
|
|
const unsigned dist = jp - dep.jp;
|
|
|
|
|
const unsigned max_dist = 10;
|
|
|
|
|
assert(jp > dep.jp);
|
|
|
|
|
if (dist <= max_dist)
|
|
|
|
|
min_dist = MIN3(min_dist, dist, 7);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return { min_dist == ~0u ? 0 : min_dist };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Return whether the dependency list \p deps of an instruction with
|
|
|
|
|
* ordered_address \p jp has any non-trivial ordered dependencies.
|
|
|
|
|
*/
|
|
|
|
|
bool
|
|
|
|
|
find_ordered_dependency(const std::vector<dependency> &deps,
|
|
|
|
|
const ordered_address &jp)
|
|
|
|
|
{
|
|
|
|
|
return ordered_dependency_swsb(deps, jp).regdist;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Return the full tgl_sbid_mode bitset for the first unordered dependency
|
|
|
|
|
* on the list \p deps that matches the specified tgl_sbid_mode, or zero if
|
|
|
|
|
* no such dependency is present.
|
|
|
|
|
*/
|
|
|
|
|
tgl_sbid_mode
|
|
|
|
|
find_unordered_dependency(const std::vector<dependency> &deps,
|
|
|
|
|
tgl_sbid_mode unordered)
|
|
|
|
|
{
|
|
|
|
|
if (unordered) {
|
|
|
|
|
for (const auto &dep : deps) {
|
|
|
|
|
if (unordered & dep.unordered)
|
|
|
|
|
return dep.unordered;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return TGL_SBID_NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Return the tgl_sbid_mode bitset of an unordered dependency from the list
|
|
|
|
|
* \p deps that can be represented directly in the SWSB annotation of the
|
|
|
|
|
* instruction without additional SYNC instructions, or zero if no such
|
|
|
|
|
* dependency is present.
|
|
|
|
|
*/
|
|
|
|
|
tgl_sbid_mode
|
|
|
|
|
baked_unordered_dependency_mode(const fs_inst *inst,
|
|
|
|
|
const std::vector<dependency> &deps,
|
|
|
|
|
const ordered_address &jp)
|
|
|
|
|
{
|
|
|
|
|
const bool has_ordered = find_ordered_dependency(deps, jp);
|
|
|
|
|
|
|
|
|
|
if (find_unordered_dependency(deps, TGL_SBID_SET))
|
|
|
|
|
return find_unordered_dependency(deps, TGL_SBID_SET);
|
|
|
|
|
else if (has_ordered && is_unordered(inst))
|
|
|
|
|
return TGL_SBID_NULL;
|
|
|
|
|
else if (find_unordered_dependency(deps, TGL_SBID_DST) &&
|
|
|
|
|
(!has_ordered || !is_unordered(inst)))
|
|
|
|
|
return find_unordered_dependency(deps, TGL_SBID_DST);
|
|
|
|
|
else if (!has_ordered)
|
|
|
|
|
return find_unordered_dependency(deps, TGL_SBID_SRC);
|
|
|
|
|
else
|
|
|
|
|
return TGL_SBID_NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @} */
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Shader instruction dependency calculation.
|
|
|
|
|
* @{
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Update scoreboard object \p sb to account for the execution of
|
|
|
|
|
* instruction \p inst.
|
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
update_inst_scoreboard(const fs_visitor *shader,
|
|
|
|
|
const std::vector<ordered_address> &jps,
|
|
|
|
|
const fs_inst *inst, unsigned ip, scoreboard &sb)
|
|
|
|
|
{
|
|
|
|
|
/* Track any source registers that may be fetched asynchronously by this
|
|
|
|
|
* instruction, otherwise clear the dependency in order to avoid
|
|
|
|
|
* subsequent redundant synchronization.
|
|
|
|
|
*/
|
|
|
|
|
for (unsigned i = 0; i < inst->sources; i++) {
|
|
|
|
|
const dependency rd_dep =
|
|
|
|
|
inst->is_payload(i) || inst->is_math() ? dependency(TGL_SBID_SRC, ip) :
|
|
|
|
|
ordered_unit(inst) ? dependency(TGL_REGDIST_SRC, jps[ip]) :
|
|
|
|
|
dependency::done;
|
|
|
|
|
|
|
|
|
|
for (unsigned j = 0; j < regs_read(inst, i); j++)
|
|
|
|
|
sb.set(byte_offset(inst->src[i], REG_SIZE * j), rd_dep);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (is_send(inst) && inst->base_mrf != -1) {
|
|
|
|
|
const dependency rd_dep = dependency(TGL_SBID_SRC, ip);
|
|
|
|
|
|
|
|
|
|
for (unsigned j = 0; j < inst->mlen; j++)
|
|
|
|
|
sb.set(brw_uvec_mrf(8, inst->base_mrf + j, 0), rd_dep);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Track any destination registers of this instruction. */
|
|
|
|
|
const dependency wr_dep =
|
|
|
|
|
is_unordered(inst) ? dependency(TGL_SBID_DST, ip) :
|
|
|
|
|
ordered_unit(inst) ? dependency(TGL_REGDIST_DST, jps[ip]) :
|
|
|
|
|
dependency();
|
|
|
|
|
|
|
|
|
|
if (is_valid(wr_dep) && inst->dst.file != BAD_FILE &&
|
|
|
|
|
!inst->dst.is_null()) {
|
|
|
|
|
for (unsigned j = 0; j < regs_written(inst); j++)
|
|
|
|
|
sb.set(byte_offset(inst->dst, REG_SIZE * j), wr_dep);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Calculate scoreboard objects locally that represent any pending (and
|
|
|
|
|
* unconditionally resolved) dependencies at the end of each block of the
|
|
|
|
|
* program.
|
|
|
|
|
*/
|
|
|
|
|
std::vector<scoreboard>
|
|
|
|
|
gather_block_scoreboards(const fs_visitor *shader,
|
|
|
|
|
const std::vector<ordered_address> &jps)
|
|
|
|
|
{
|
|
|
|
|
std::vector<scoreboard> sbs(shader->cfg->num_blocks);
|
|
|
|
|
unsigned ip = 0;
|
|
|
|
|
|
|
|
|
|
foreach_block_and_inst(block, fs_inst, inst, shader->cfg)
|
|
|
|
|
update_inst_scoreboard(shader, jps, inst, ip++, sbs[block->num]);
|
|
|
|
|
|
|
|
|
|
return sbs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Propagate data dependencies globally through the control flow graph
|
|
|
|
|
* until a fixed point is reached.
|
|
|
|
|
*
|
|
|
|
|
* Calculates the set of dependencies potentially pending at the beginning
|
|
|
|
|
* of each block, and returns it as an array of scoreboard objects.
|
|
|
|
|
*/
|
|
|
|
|
std::pair<std::vector<scoreboard>, std::vector<unsigned>>
|
|
|
|
|
propagate_block_scoreboards(const fs_visitor *shader,
|
|
|
|
|
const std::vector<ordered_address> &jps)
|
|
|
|
|
{
|
|
|
|
|
const std::vector<scoreboard> delta_sbs =
|
|
|
|
|
gather_block_scoreboards(shader, jps);
|
|
|
|
|
std::vector<scoreboard> in_sbs(shader->cfg->num_blocks);
|
|
|
|
|
std::vector<scoreboard> out_sbs(shader->cfg->num_blocks);
|
|
|
|
|
equivalence_relation eq;
|
|
|
|
|
|
|
|
|
|
for (bool progress = true; progress;) {
|
|
|
|
|
progress = false;
|
|
|
|
|
|
|
|
|
|
foreach_block(block, shader->cfg) {
|
|
|
|
|
const scoreboard sb = shadow(in_sbs[block->num],
|
|
|
|
|
delta_sbs[block->num]);
|
|
|
|
|
|
|
|
|
|
if (sb != out_sbs[block->num]) {
|
|
|
|
|
foreach_list_typed(bblock_link, child_link, link,
|
|
|
|
|
&block->children) {
|
|
|
|
|
scoreboard &in_sb = in_sbs[child_link->block->num];
|
|
|
|
|
const int delta =
|
|
|
|
|
jps[child_link->block->start_ip] - jps[block->end_ip]
|
|
|
|
|
- ordered_unit(static_cast<const fs_inst *>(block->end()));
|
|
|
|
|
|
|
|
|
|
in_sb = merge(eq, in_sb, transport(sb, delta));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
out_sbs[block->num] = sb;
|
|
|
|
|
progress = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return { std::move(in_sbs), eq.flatten() };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Return the list of potential dependencies of each instruction in the
|
|
|
|
|
* shader based on the result of global dependency analysis.
|
|
|
|
|
*/
|
|
|
|
|
std::vector<std::vector<dependency>>
|
|
|
|
|
gather_inst_dependencies(const fs_visitor *shader,
|
|
|
|
|
const std::vector<ordered_address> &jps)
|
|
|
|
|
{
|
|
|
|
|
std::vector<scoreboard> sbs;
|
|
|
|
|
std::vector<unsigned> ids;
|
|
|
|
|
std::vector<std::vector<dependency>> deps;
|
|
|
|
|
unsigned ip = 0;
|
|
|
|
|
|
|
|
|
|
std::tie(sbs, ids) = propagate_block_scoreboards(shader, jps);
|
|
|
|
|
|
|
|
|
|
foreach_block_and_inst(block, fs_inst, inst, shader->cfg) {
|
|
|
|
|
scoreboard &sb = sbs[block->num];
|
|
|
|
|
std::vector<dependency> inst_deps;
|
|
|
|
|
|
|
|
|
|
for (unsigned i = 0; i < inst->sources; i++) {
|
|
|
|
|
for (unsigned j = 0; j < regs_read(inst, i); j++)
|
|
|
|
|
add_dependency(ids, inst_deps, dependency_for_read(
|
|
|
|
|
sb.get(byte_offset(inst->src[i], REG_SIZE * j))));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (is_send(inst) && inst->base_mrf != -1) {
|
|
|
|
|
for (unsigned j = 0; j < inst->mlen; j++)
|
|
|
|
|
add_dependency(ids, inst_deps, dependency_for_read(
|
|
|
|
|
sb.get(brw_uvec_mrf(8, inst->base_mrf + j, 0))));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (is_unordered(inst))
|
|
|
|
|
add_dependency(ids, inst_deps, dependency(TGL_SBID_SET, ip));
|
|
|
|
|
|
|
|
|
|
if (!inst->no_dd_check) {
|
|
|
|
|
if (inst->dst.file != BAD_FILE && !inst->dst.is_null()) {
|
|
|
|
|
for (unsigned j = 0; j < regs_written(inst); j++) {
|
|
|
|
|
add_dependency(ids, inst_deps, dependency_for_write(inst,
|
|
|
|
|
sb.get(byte_offset(inst->dst, REG_SIZE * j))));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (is_send(inst) && inst->base_mrf != -1) {
|
|
|
|
|
for (int j = 0; j < shader->implied_mrf_writes(inst); j++)
|
|
|
|
|
add_dependency(ids, inst_deps, dependency_for_write(inst,
|
|
|
|
|
sb.get(brw_uvec_mrf(8, inst->base_mrf + j, 0))));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
deps.push_back(inst_deps);
|
|
|
|
|
update_inst_scoreboard(shader, jps, inst, ip, sb);
|
|
|
|
|
ip++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return deps;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @} */
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Allocate SBID tokens to track the execution of every out-of-order
|
|
|
|
|
* instruction of the shader.
|
|
|
|
|
*/
|
|
|
|
|
std::vector<std::vector<dependency>>
|
|
|
|
|
allocate_inst_dependencies(const fs_visitor *shader,
|
|
|
|
|
const std::vector<std::vector<dependency>> &deps0)
|
|
|
|
|
{
|
|
|
|
|
/* XXX - Use bin-packing algorithm to assign hardware SBIDs optimally in
|
|
|
|
|
* shaders with a large number of SEND messages.
|
|
|
|
|
*/
|
|
|
|
|
std::vector<std::vector<dependency>> deps1;
|
|
|
|
|
std::vector<unsigned> ids(deps0.size(), ~0u);
|
|
|
|
|
unsigned next_id = 0;
|
|
|
|
|
|
|
|
|
|
for (const auto &inst_deps0 : deps0) {
|
|
|
|
|
std::vector<dependency> inst_deps1;
|
|
|
|
|
|
|
|
|
|
for (const auto &dep : inst_deps0) {
|
|
|
|
|
if (dep.unordered && ids[dep.id] == ~0u)
|
|
|
|
|
ids[dep.id] = (next_id++) & 0xf;
|
|
|
|
|
|
|
|
|
|
add_dependency(ids, inst_deps1, dep);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
deps1.push_back(inst_deps1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return deps1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Emit dependency information provided by \p deps into the shader,
|
|
|
|
|
* inserting additional SYNC instructions for dependencies that can't be
|
|
|
|
|
* represented directly by annotating existing instructions.
|
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
emit_inst_dependencies(fs_visitor *shader,
|
|
|
|
|
const std::vector<ordered_address> &jps,
|
|
|
|
|
const std::vector<std::vector<dependency>> &deps)
|
|
|
|
|
{
|
|
|
|
|
unsigned ip = 0;
|
|
|
|
|
|
|
|
|
|
foreach_block_and_inst_safe(block, fs_inst, inst, shader->cfg) {
|
|
|
|
|
tgl_swsb swsb = ordered_dependency_swsb(deps[ip], jps[ip]);
|
|
|
|
|
const tgl_sbid_mode unordered_mode =
|
|
|
|
|
baked_unordered_dependency_mode(inst, deps[ip], jps[ip]);
|
|
|
|
|
|
|
|
|
|
for (const auto &dep : deps[ip]) {
|
|
|
|
|
if (dep.unordered) {
|
|
|
|
|
if (unordered_mode == dep.unordered && !swsb.mode) {
|
|
|
|
|
/* Bake unordered dependency into the instruction's SWSB if
|
|
|
|
|
* possible.
|
|
|
|
|
*/
|
|
|
|
|
swsb.sbid = dep.id;
|
|
|
|
|
swsb.mode = dep.unordered;
|
|
|
|
|
} else {
|
|
|
|
|
/* Emit dependency into the SWSB of an extra SYNC
|
|
|
|
|
* instruction.
|
|
|
|
|
*/
|
|
|
|
|
const fs_builder ibld = fs_builder(shader, block, inst)
|
|
|
|
|
.exec_all().group(1, 0);
|
|
|
|
|
fs_inst *sync = ibld.emit(BRW_OPCODE_SYNC, ibld.null_reg_ud(),
|
|
|
|
|
brw_imm_ud(TGL_SYNC_NOP));
|
|
|
|
|
sync->sched.sbid = dep.id;
|
|
|
|
|
sync->sched.mode = dep.unordered;
|
|
|
|
|
assert(!(sync->sched.mode & TGL_SBID_SET));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Update the IR. */
|
|
|
|
|
inst->sched = swsb;
|
|
|
|
|
inst->no_dd_check = inst->no_dd_clear = false;
|
|
|
|
|
ip++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
fs_visitor::lower_scoreboard()
|
|
|
|
|
{
|
|
|
|
|
if (devinfo->gen >= 12) {
|
|
|
|
|
const std::vector<ordered_address> jps = ordered_inst_addresses(this);
|
|
|
|
|
emit_inst_dependencies(this, jps,
|
|
|
|
|
allocate_inst_dependencies(this,
|
|
|
|
|
gather_inst_dependencies(this, jps)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|