2019-09-17 13:22:17 +02:00
|
|
|
/*
|
|
|
|
|
* Copyright © 2018 Valve Corporation
|
|
|
|
|
* Copyright © 2018 Google
|
|
|
|
|
*
|
2024-04-08 09:02:30 +02:00
|
|
|
* SPDX-License-Identifier: MIT
|
2019-09-17 13:22:17 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "aco_ir.h"
|
2021-06-09 10:14:54 +02:00
|
|
|
|
2019-09-13 16:41:00 +01:00
|
|
|
#include "util/u_math.h"
|
2019-09-17 13:22:17 +02:00
|
|
|
|
|
|
|
|
#include <set>
|
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
|
|
namespace aco {
|
2020-02-21 20:14:03 +00:00
|
|
|
RegisterDemand
|
|
|
|
|
get_live_changes(aco_ptr<Instruction>& instr)
|
|
|
|
|
{
|
|
|
|
|
RegisterDemand changes;
|
|
|
|
|
for (const Definition& def : instr->definitions) {
|
|
|
|
|
if (!def.isTemp() || def.isKill())
|
|
|
|
|
continue;
|
|
|
|
|
changes += def.getTemp();
|
|
|
|
|
}
|
2019-09-17 13:22:17 +02:00
|
|
|
|
2020-02-21 20:14:03 +00:00
|
|
|
for (const Operand& op : instr->operands) {
|
|
|
|
|
if (!op.isTemp() || !op.isFirstKill())
|
|
|
|
|
continue;
|
|
|
|
|
changes -= op.getTemp();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return changes;
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-12 16:00:10 +01:00
|
|
|
void
|
|
|
|
|
handle_def_fixed_to_op(RegisterDemand* demand, RegisterDemand demand_before, Instruction* instr,
|
|
|
|
|
int op_idx)
|
|
|
|
|
{
|
|
|
|
|
/* Usually the register demand before an instruction would be considered part of the previous
|
|
|
|
|
* instruction, since it's not greater than the register demand for that previous instruction.
|
|
|
|
|
* Except, it can be greater in the case of an definition fixed to a non-killed operand: the RA
|
|
|
|
|
* needs to reserve space between the two instructions for the definition (containing a copy of
|
|
|
|
|
* the operand).
|
|
|
|
|
*/
|
|
|
|
|
demand_before += instr->definitions[0].getTemp();
|
|
|
|
|
demand->update(demand_before);
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-21 20:14:03 +00:00
|
|
|
RegisterDemand
|
|
|
|
|
get_temp_registers(aco_ptr<Instruction>& instr)
|
|
|
|
|
{
|
aco: calculate register demand per instruction as maximum necessary to execute the instruction
Previously, the register demand per instruction was calculated as the number of
live variables in the register file after executing an instruction plus additional
temporary registers, necessary during the execution of the instruction.
With this change, now it also includes all variables which are live right before
executing an instruction, i.e. killed Operands.
Care has been taken so that the invariant
register_demand[idx] = register_demand[idx - 1] - get_temp_registers(prev_instr)
+ get_live_changes(instr) + get_temp_registers(instr)
still holds.
Slight changes in scheduling:
Totals from 316 (0.40% of 79395) affected shaders: (GFX11)
Instrs: 301329 -> 300777 (-0.18%); split: -0.31%, +0.12%
CodeSize: 1577976 -> 1576204 (-0.11%); split: -0.21%, +0.10%
SpillSGPRs: 448 -> 447 (-0.22%)
Latency: 1736349 -> 1726182 (-0.59%); split: -2.01%, +1.42%
InvThroughput: 243894 -> 243883 (-0.00%); split: -0.03%, +0.03%
VClause: 6134 -> 6280 (+2.38%); split: -1.04%, +3.42%
SClause: 6142 -> 6137 (-0.08%); split: -0.13%, +0.05%
Copies: 14037 -> 14032 (-0.04%); split: -0.56%, +0.52%
Branches: 3284 -> 3283 (-0.03%)
VALU: 182750 -> 182718 (-0.02%); split: -0.04%, +0.03%
SALU: 18522 -> 18538 (+0.09%)
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/29804>
2024-06-20 10:03:11 +02:00
|
|
|
RegisterDemand demand_before;
|
|
|
|
|
RegisterDemand demand_after;
|
2020-02-21 15:46:39 +00:00
|
|
|
|
2020-02-21 20:14:03 +00:00
|
|
|
for (Definition def : instr->definitions) {
|
|
|
|
|
if (def.isKill())
|
aco: calculate register demand per instruction as maximum necessary to execute the instruction
Previously, the register demand per instruction was calculated as the number of
live variables in the register file after executing an instruction plus additional
temporary registers, necessary during the execution of the instruction.
With this change, now it also includes all variables which are live right before
executing an instruction, i.e. killed Operands.
Care has been taken so that the invariant
register_demand[idx] = register_demand[idx - 1] - get_temp_registers(prev_instr)
+ get_live_changes(instr) + get_temp_registers(instr)
still holds.
Slight changes in scheduling:
Totals from 316 (0.40% of 79395) affected shaders: (GFX11)
Instrs: 301329 -> 300777 (-0.18%); split: -0.31%, +0.12%
CodeSize: 1577976 -> 1576204 (-0.11%); split: -0.21%, +0.10%
SpillSGPRs: 448 -> 447 (-0.22%)
Latency: 1736349 -> 1726182 (-0.59%); split: -2.01%, +1.42%
InvThroughput: 243894 -> 243883 (-0.00%); split: -0.03%, +0.03%
VClause: 6134 -> 6280 (+2.38%); split: -1.04%, +3.42%
SClause: 6142 -> 6137 (-0.08%); split: -0.13%, +0.05%
Copies: 14037 -> 14032 (-0.04%); split: -0.56%, +0.52%
Branches: 3284 -> 3283 (-0.03%)
VALU: 182750 -> 182718 (-0.02%); split: -0.04%, +0.03%
SALU: 18522 -> 18538 (+0.09%)
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/29804>
2024-06-20 10:03:11 +02:00
|
|
|
demand_after += def.getTemp();
|
|
|
|
|
else if (def.isTemp())
|
|
|
|
|
demand_before -= def.getTemp();
|
2020-02-21 20:14:03 +00:00
|
|
|
}
|
2020-02-21 15:46:39 +00:00
|
|
|
|
|
|
|
|
for (Operand op : instr->operands) {
|
aco: calculate register demand per instruction as maximum necessary to execute the instruction
Previously, the register demand per instruction was calculated as the number of
live variables in the register file after executing an instruction plus additional
temporary registers, necessary during the execution of the instruction.
With this change, now it also includes all variables which are live right before
executing an instruction, i.e. killed Operands.
Care has been taken so that the invariant
register_demand[idx] = register_demand[idx - 1] - get_temp_registers(prev_instr)
+ get_live_changes(instr) + get_temp_registers(instr)
still holds.
Slight changes in scheduling:
Totals from 316 (0.40% of 79395) affected shaders: (GFX11)
Instrs: 301329 -> 300777 (-0.18%); split: -0.31%, +0.12%
CodeSize: 1577976 -> 1576204 (-0.11%); split: -0.21%, +0.10%
SpillSGPRs: 448 -> 447 (-0.22%)
Latency: 1736349 -> 1726182 (-0.59%); split: -2.01%, +1.42%
InvThroughput: 243894 -> 243883 (-0.00%); split: -0.03%, +0.03%
VClause: 6134 -> 6280 (+2.38%); split: -1.04%, +3.42%
SClause: 6142 -> 6137 (-0.08%); split: -0.13%, +0.05%
Copies: 14037 -> 14032 (-0.04%); split: -0.56%, +0.52%
Branches: 3284 -> 3283 (-0.03%)
VALU: 182750 -> 182718 (-0.02%); split: -0.04%, +0.03%
SALU: 18522 -> 18538 (+0.09%)
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/29804>
2024-06-20 10:03:11 +02:00
|
|
|
if (op.isFirstKill()) {
|
|
|
|
|
demand_before += op.getTemp();
|
|
|
|
|
if (op.isLateKill())
|
|
|
|
|
demand_after += op.getTemp();
|
|
|
|
|
}
|
2020-02-21 15:46:39 +00:00
|
|
|
}
|
|
|
|
|
|
2023-04-12 16:00:10 +01:00
|
|
|
int op_idx = get_op_fixed_to_def(instr.get());
|
aco: calculate register demand per instruction as maximum necessary to execute the instruction
Previously, the register demand per instruction was calculated as the number of
live variables in the register file after executing an instruction plus additional
temporary registers, necessary during the execution of the instruction.
With this change, now it also includes all variables which are live right before
executing an instruction, i.e. killed Operands.
Care has been taken so that the invariant
register_demand[idx] = register_demand[idx - 1] - get_temp_registers(prev_instr)
+ get_live_changes(instr) + get_temp_registers(instr)
still holds.
Slight changes in scheduling:
Totals from 316 (0.40% of 79395) affected shaders: (GFX11)
Instrs: 301329 -> 300777 (-0.18%); split: -0.31%, +0.12%
CodeSize: 1577976 -> 1576204 (-0.11%); split: -0.21%, +0.10%
SpillSGPRs: 448 -> 447 (-0.22%)
Latency: 1736349 -> 1726182 (-0.59%); split: -2.01%, +1.42%
InvThroughput: 243894 -> 243883 (-0.00%); split: -0.03%, +0.03%
VClause: 6134 -> 6280 (+2.38%); split: -1.04%, +3.42%
SClause: 6142 -> 6137 (-0.08%); split: -0.13%, +0.05%
Copies: 14037 -> 14032 (-0.04%); split: -0.56%, +0.52%
Branches: 3284 -> 3283 (-0.03%)
VALU: 182750 -> 182718 (-0.02%); split: -0.04%, +0.03%
SALU: 18522 -> 18538 (+0.09%)
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/29804>
2024-06-20 10:03:11 +02:00
|
|
|
if (op_idx != -1 && !instr->operands[op_idx].isKill())
|
|
|
|
|
demand_before += instr->definitions[0].getTemp();
|
2023-04-12 16:00:10 +01:00
|
|
|
|
aco: calculate register demand per instruction as maximum necessary to execute the instruction
Previously, the register demand per instruction was calculated as the number of
live variables in the register file after executing an instruction plus additional
temporary registers, necessary during the execution of the instruction.
With this change, now it also includes all variables which are live right before
executing an instruction, i.e. killed Operands.
Care has been taken so that the invariant
register_demand[idx] = register_demand[idx - 1] - get_temp_registers(prev_instr)
+ get_live_changes(instr) + get_temp_registers(instr)
still holds.
Slight changes in scheduling:
Totals from 316 (0.40% of 79395) affected shaders: (GFX11)
Instrs: 301329 -> 300777 (-0.18%); split: -0.31%, +0.12%
CodeSize: 1577976 -> 1576204 (-0.11%); split: -0.21%, +0.10%
SpillSGPRs: 448 -> 447 (-0.22%)
Latency: 1736349 -> 1726182 (-0.59%); split: -2.01%, +1.42%
InvThroughput: 243894 -> 243883 (-0.00%); split: -0.03%, +0.03%
VClause: 6134 -> 6280 (+2.38%); split: -1.04%, +3.42%
SClause: 6142 -> 6137 (-0.08%); split: -0.13%, +0.05%
Copies: 14037 -> 14032 (-0.04%); split: -0.56%, +0.52%
Branches: 3284 -> 3283 (-0.03%)
VALU: 182750 -> 182718 (-0.02%); split: -0.04%, +0.03%
SALU: 18522 -> 18538 (+0.09%)
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/29804>
2024-06-20 10:03:11 +02:00
|
|
|
demand_after.update(demand_before);
|
|
|
|
|
return demand_after;
|
2020-02-21 20:14:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RegisterDemand
|
|
|
|
|
get_demand_before(RegisterDemand demand, aco_ptr<Instruction>& instr,
|
|
|
|
|
aco_ptr<Instruction>& instr_before)
|
|
|
|
|
{
|
|
|
|
|
demand -= get_live_changes(instr);
|
|
|
|
|
demand -= get_temp_registers(instr);
|
|
|
|
|
if (instr_before)
|
|
|
|
|
demand += get_temp_registers(instr_before);
|
|
|
|
|
return demand;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
namespace {
|
2021-07-26 17:55:48 +01:00
|
|
|
struct PhiInfo {
|
|
|
|
|
uint16_t logical_phi_sgpr_ops = 0;
|
|
|
|
|
uint16_t linear_phi_ops = 0;
|
|
|
|
|
uint16_t linear_phi_defs = 0;
|
|
|
|
|
};
|
|
|
|
|
|
2022-03-16 10:56:26 +01:00
|
|
|
bool
|
|
|
|
|
instr_needs_vcc(Instruction* instr)
|
|
|
|
|
{
|
|
|
|
|
if (instr->isVOPC())
|
|
|
|
|
return true;
|
|
|
|
|
if (instr->isVOP2() && !instr->isVOP3()) {
|
|
|
|
|
if (instr->operands.size() == 3 && instr->operands[2].isTemp() &&
|
|
|
|
|
instr->operands[2].regClass().type() == RegType::sgpr)
|
|
|
|
|
return true;
|
|
|
|
|
if (instr->definitions.size() == 2)
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-17 13:22:17 +02:00
|
|
|
void
|
2024-06-13 11:55:27 +02:00
|
|
|
process_live_temps_per_block(Program* program, Block* block, unsigned& worklist,
|
2021-07-26 17:55:48 +01:00
|
|
|
std::vector<PhiInfo>& phi_info)
|
2019-09-17 13:22:17 +02:00
|
|
|
{
|
2024-06-13 11:55:27 +02:00
|
|
|
std::vector<RegisterDemand>& register_demand = program->live.register_demand[block->index];
|
2019-09-17 13:22:17 +02:00
|
|
|
RegisterDemand new_demand;
|
|
|
|
|
|
|
|
|
|
register_demand.resize(block->instructions.size());
|
2024-06-13 11:55:27 +02:00
|
|
|
IDSet live = program->live.live_out[block->index];
|
2019-09-17 13:22:17 +02:00
|
|
|
|
2020-03-10 13:39:42 +01:00
|
|
|
/* initialize register demand */
|
2020-09-14 16:45:55 +01:00
|
|
|
for (unsigned t : live)
|
|
|
|
|
new_demand += Temp(t, program->temp_rc[t]);
|
2021-07-26 17:55:48 +01:00
|
|
|
new_demand.sgpr -= phi_info[block->index].logical_phi_sgpr_ops;
|
2019-09-17 13:22:17 +02:00
|
|
|
|
|
|
|
|
/* traverse the instructions backwards */
|
2019-10-29 11:56:09 +01:00
|
|
|
int idx;
|
|
|
|
|
for (idx = block->instructions.size() - 1; idx >= 0; idx--) {
|
|
|
|
|
Instruction* insn = block->instructions[idx].get();
|
|
|
|
|
if (is_phi(insn))
|
|
|
|
|
break;
|
|
|
|
|
|
2022-03-16 10:56:26 +01:00
|
|
|
program->needs_vcc |= instr_needs_vcc(insn);
|
aco: always set exec_live=false
Register demand calculation for exec masks doesn't always match
get_live_changes() and get_temp_registers(). For now, just set
exec_live=false.
fossil-db (GFX10.3):
Totals from 108230 (77.64% of 139391) affected shaders:
SGPRs: 5759658 -> 5756818 (-0.05%); split: -0.08%, +0.03%
VGPRs: 4061104 -> 4061248 (+0.00%); split: -0.00%, +0.01%
SpillSGPRs: 14114 -> 15198 (+7.68%); split: -0.10%, +7.78%
CodeSize: 266548396 -> 266603288 (+0.02%); split: -0.01%, +0.03%
MaxWaves: 1390885 -> 1390855 (-0.00%); split: +0.00%, -0.00%
Instrs: 50983353 -> 50992972 (+0.02%); split: -0.02%, +0.04%
Cycles: 1733042048 -> 1735443264 (+0.14%); split: -0.02%, +0.16%
VMEM: 41933625 -> 41914722 (-0.05%); split: +0.04%, -0.09%
SMEM: 7197675 -> 7197789 (+0.00%); split: +0.16%, -0.16%
VClause: 1050885 -> 1050978 (+0.01%); split: -0.02%, +0.03%
SClause: 2074913 -> 2071844 (-0.15%); split: -0.23%, +0.08%
Copies: 3181464 -> 3188125 (+0.21%); split: -0.38%, +0.59%
Branches: 1127526 -> 1127716 (+0.02%); split: -0.10%, +0.12%
PreSGPRs: 3376687 -> 3586076 (+6.20%); split: -0.00%, +6.20%
PreVGPRs: 3339740 -> 3339811 (+0.00%)
Signed-off-by: Rhys Perry <pendingchaos02@gmail.com>
Reviewed-by: Daniel Schürmann <daniel@schuermann.dev>
Cc: mesa-stable
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/8807>
2021-01-27 14:04:10 +00:00
|
|
|
register_demand[idx] = RegisterDemand(new_demand.vgpr, new_demand.sgpr);
|
2019-09-17 13:22:17 +02:00
|
|
|
|
|
|
|
|
/* KILL */
|
|
|
|
|
for (Definition& definition : insn->definitions) {
|
|
|
|
|
if (!definition.isTemp()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2022-03-16 10:56:26 +01:00
|
|
|
if (definition.isFixed() && definition.physReg() == vcc)
|
2020-02-21 12:23:28 +00:00
|
|
|
program->needs_vcc = true;
|
2019-09-17 13:22:17 +02:00
|
|
|
|
|
|
|
|
const Temp temp = definition.getTemp();
|
2020-09-14 16:45:55 +01:00
|
|
|
const size_t n = live.erase(temp.id());
|
2019-09-17 13:22:17 +02:00
|
|
|
|
|
|
|
|
if (n) {
|
|
|
|
|
new_demand -= temp;
|
|
|
|
|
definition.setKill(false);
|
|
|
|
|
} else {
|
2020-02-21 15:46:39 +00:00
|
|
|
register_demand[idx] += temp;
|
2019-09-17 13:22:17 +02:00
|
|
|
definition.setKill(true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* GEN */
|
2019-10-29 11:56:09 +01:00
|
|
|
if (insn->opcode == aco_opcode::p_logical_end) {
|
2021-07-26 17:55:48 +01:00
|
|
|
new_demand.sgpr += phi_info[block->index].logical_phi_sgpr_ops;
|
2019-09-17 13:22:17 +02:00
|
|
|
} else {
|
2020-01-21 14:24:01 +00:00
|
|
|
/* we need to do this in a separate loop because the next one can
|
|
|
|
|
* setKill() for several operands at once and we don't want to
|
|
|
|
|
* overwrite that in a later iteration */
|
|
|
|
|
for (Operand& op : insn->operands)
|
|
|
|
|
op.setKill(false);
|
|
|
|
|
|
2019-09-17 13:22:17 +02:00
|
|
|
for (unsigned i = 0; i < insn->operands.size(); ++i) {
|
|
|
|
|
Operand& operand = insn->operands[i];
|
2020-02-21 12:23:28 +00:00
|
|
|
if (!operand.isTemp())
|
2019-09-17 13:22:17 +02:00
|
|
|
continue;
|
2020-02-21 12:23:28 +00:00
|
|
|
if (operand.isFixed() && operand.physReg() == vcc)
|
|
|
|
|
program->needs_vcc = true;
|
2019-09-17 13:22:17 +02:00
|
|
|
const Temp temp = operand.getTemp();
|
2020-09-14 16:45:55 +01:00
|
|
|
const bool inserted = live.insert(temp.id()).second;
|
2019-09-17 13:22:17 +02:00
|
|
|
if (inserted) {
|
|
|
|
|
operand.setFirstKill(true);
|
|
|
|
|
for (unsigned j = i + 1; j < insn->operands.size(); ++j) {
|
|
|
|
|
if (insn->operands[j].isTemp() &&
|
|
|
|
|
insn->operands[j].tempId() == operand.tempId()) {
|
|
|
|
|
insn->operands[j].setFirstKill(false);
|
|
|
|
|
insn->operands[j].setKill(true);
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-02-21 15:46:39 +00:00
|
|
|
if (operand.isLateKill())
|
|
|
|
|
register_demand[idx] += temp;
|
2019-09-17 13:22:17 +02:00
|
|
|
new_demand += temp;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-12 16:00:10 +01:00
|
|
|
int op_idx = get_op_fixed_to_def(insn);
|
|
|
|
|
if (op_idx != -1 && !insn->operands[op_idx].isKill()) {
|
|
|
|
|
RegisterDemand before_instr = new_demand;
|
|
|
|
|
handle_def_fixed_to_op(®ister_demand[idx], before_instr, insn, op_idx);
|
|
|
|
|
}
|
aco: calculate register demand per instruction as maximum necessary to execute the instruction
Previously, the register demand per instruction was calculated as the number of
live variables in the register file after executing an instruction plus additional
temporary registers, necessary during the execution of the instruction.
With this change, now it also includes all variables which are live right before
executing an instruction, i.e. killed Operands.
Care has been taken so that the invariant
register_demand[idx] = register_demand[idx - 1] - get_temp_registers(prev_instr)
+ get_live_changes(instr) + get_temp_registers(instr)
still holds.
Slight changes in scheduling:
Totals from 316 (0.40% of 79395) affected shaders: (GFX11)
Instrs: 301329 -> 300777 (-0.18%); split: -0.31%, +0.12%
CodeSize: 1577976 -> 1576204 (-0.11%); split: -0.21%, +0.10%
SpillSGPRs: 448 -> 447 (-0.22%)
Latency: 1736349 -> 1726182 (-0.59%); split: -2.01%, +1.42%
InvThroughput: 243894 -> 243883 (-0.00%); split: -0.03%, +0.03%
VClause: 6134 -> 6280 (+2.38%); split: -1.04%, +3.42%
SClause: 6142 -> 6137 (-0.08%); split: -0.13%, +0.05%
Copies: 14037 -> 14032 (-0.04%); split: -0.56%, +0.52%
Branches: 3284 -> 3283 (-0.03%)
VALU: 182750 -> 182718 (-0.02%); split: -0.04%, +0.03%
SALU: 18522 -> 18538 (+0.09%)
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/29804>
2024-06-20 10:03:11 +02:00
|
|
|
|
|
|
|
|
register_demand[idx].update(new_demand);
|
2019-09-17 13:22:17 +02:00
|
|
|
}
|
|
|
|
|
|
2019-10-29 11:56:09 +01:00
|
|
|
/* handle phi definitions */
|
2021-07-26 17:55:48 +01:00
|
|
|
uint16_t linear_phi_defs = 0;
|
2019-10-29 11:56:09 +01:00
|
|
|
int phi_idx = idx;
|
|
|
|
|
while (phi_idx >= 0) {
|
|
|
|
|
Instruction* insn = block->instructions[phi_idx].get();
|
|
|
|
|
|
2021-02-04 15:55:23 +01:00
|
|
|
assert(is_phi(insn) && insn->definitions.size() == 1);
|
|
|
|
|
if (!insn->definitions[0].isTemp()) {
|
|
|
|
|
assert(insn->definitions[0].isFixed() && insn->definitions[0].physReg() == exec);
|
|
|
|
|
phi_idx--;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2019-10-29 11:56:09 +01:00
|
|
|
Definition& definition = insn->definitions[0];
|
2022-03-16 10:56:26 +01:00
|
|
|
if (definition.isFixed() && definition.physReg() == vcc)
|
2020-02-21 12:23:28 +00:00
|
|
|
program->needs_vcc = true;
|
2019-10-29 11:56:09 +01:00
|
|
|
const Temp temp = definition.getTemp();
|
2020-09-14 16:45:55 +01:00
|
|
|
const size_t n = live.erase(temp.id());
|
2019-10-29 11:56:09 +01:00
|
|
|
|
2024-06-19 14:11:51 +02:00
|
|
|
if (n) {
|
2019-10-29 11:56:09 +01:00
|
|
|
definition.setKill(false);
|
2024-06-19 14:11:51 +02:00
|
|
|
} else {
|
|
|
|
|
new_demand += temp;
|
2019-10-29 11:56:09 +01:00
|
|
|
definition.setKill(true);
|
2024-06-19 14:11:51 +02:00
|
|
|
}
|
2019-10-29 11:56:09 +01:00
|
|
|
|
2021-07-26 17:55:48 +01:00
|
|
|
if (insn->opcode == aco_opcode::p_linear_phi) {
|
|
|
|
|
assert(definition.getTemp().type() == RegType::sgpr);
|
|
|
|
|
linear_phi_defs += definition.size();
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-29 11:56:09 +01:00
|
|
|
phi_idx--;
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-26 17:55:48 +01:00
|
|
|
for (unsigned pred_idx : block->linear_preds)
|
|
|
|
|
phi_info[pred_idx].linear_phi_defs = linear_phi_defs;
|
|
|
|
|
|
2020-03-10 13:39:42 +01:00
|
|
|
/* now, we need to merge the live-ins into the live-out sets */
|
2022-08-17 00:23:36 +02:00
|
|
|
bool fast_merge =
|
|
|
|
|
block->logical_preds.size() == 0 || block->logical_preds == block->linear_preds;
|
2019-09-17 13:22:17 +02:00
|
|
|
|
2020-03-10 13:39:42 +01:00
|
|
|
#ifndef NDEBUG
|
2022-08-17 00:23:36 +02:00
|
|
|
if ((block->linear_preds.empty() && !live.empty()) ||
|
|
|
|
|
(block->logical_preds.empty() && new_demand.vgpr > 0))
|
|
|
|
|
fast_merge = false; /* we might have errors */
|
2020-03-10 13:39:42 +01:00
|
|
|
#endif
|
|
|
|
|
|
2022-08-17 00:23:36 +02:00
|
|
|
if (fast_merge) {
|
|
|
|
|
for (unsigned pred_idx : block->linear_preds) {
|
2024-06-13 11:55:27 +02:00
|
|
|
if (program->live.live_out[pred_idx].insert(live))
|
2021-07-14 14:56:48 +02:00
|
|
|
worklist = std::max(worklist, pred_idx + 1);
|
2019-09-17 13:22:17 +02:00
|
|
|
}
|
2022-08-17 00:23:36 +02:00
|
|
|
} else {
|
|
|
|
|
for (unsigned t : live) {
|
|
|
|
|
RegClass rc = program->temp_rc[t];
|
2023-12-18 11:21:08 +01:00
|
|
|
Block::edge_vec& preds = rc.is_linear() ? block->linear_preds : block->logical_preds;
|
2022-08-17 00:23:36 +02:00
|
|
|
|
|
|
|
|
#ifndef NDEBUG
|
|
|
|
|
if (preds.empty())
|
|
|
|
|
aco_err(program, "Temporary never defined or are defined after use: %%%d in BB%d", t,
|
|
|
|
|
block->index);
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
for (unsigned pred_idx : preds) {
|
2024-06-13 11:55:27 +02:00
|
|
|
auto it = program->live.live_out[pred_idx].insert(t);
|
2022-08-17 00:23:36 +02:00
|
|
|
if (it.second)
|
|
|
|
|
worklist = std::max(worklist, pred_idx + 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-09-17 13:22:17 +02:00
|
|
|
}
|
|
|
|
|
|
2019-10-29 11:56:09 +01:00
|
|
|
/* handle phi operands */
|
|
|
|
|
phi_idx = idx;
|
|
|
|
|
while (phi_idx >= 0) {
|
2024-06-19 14:11:51 +02:00
|
|
|
register_demand[phi_idx] = new_demand;
|
2019-10-29 11:56:09 +01:00
|
|
|
Instruction* insn = block->instructions[phi_idx].get();
|
|
|
|
|
assert(is_phi(insn));
|
|
|
|
|
/* directly insert into the predecessors live-out set */
|
2023-12-18 11:21:08 +01:00
|
|
|
Block::edge_vec& preds =
|
2019-10-29 11:56:09 +01:00
|
|
|
insn->opcode == aco_opcode::p_phi ? block->logical_preds : block->linear_preds;
|
|
|
|
|
for (unsigned i = 0; i < preds.size(); ++i) {
|
|
|
|
|
Operand& operand = insn->operands[i];
|
2020-02-21 12:23:28 +00:00
|
|
|
if (!operand.isTemp())
|
2019-10-29 11:56:09 +01:00
|
|
|
continue;
|
2020-02-21 12:23:28 +00:00
|
|
|
if (operand.isFixed() && operand.physReg() == vcc)
|
|
|
|
|
program->needs_vcc = true;
|
2019-10-29 11:56:09 +01:00
|
|
|
/* check if we changed an already processed block */
|
2024-06-13 11:55:27 +02:00
|
|
|
const bool inserted = program->live.live_out[preds[i]].insert(operand.tempId()).second;
|
2019-10-29 11:56:09 +01:00
|
|
|
if (inserted) {
|
2021-07-14 14:56:48 +02:00
|
|
|
worklist = std::max(worklist, preds[i] + 1);
|
2021-07-26 17:55:48 +01:00
|
|
|
if (insn->opcode == aco_opcode::p_phi && operand.getTemp().type() == RegType::sgpr) {
|
|
|
|
|
phi_info[preds[i]].logical_phi_sgpr_ops += operand.size();
|
|
|
|
|
} else if (insn->opcode == aco_opcode::p_linear_phi) {
|
|
|
|
|
assert(operand.getTemp().type() == RegType::sgpr);
|
|
|
|
|
phi_info[preds[i]].linear_phi_ops += operand.size();
|
|
|
|
|
}
|
2019-10-29 11:56:09 +01:00
|
|
|
}
|
2021-03-04 16:31:22 +01:00
|
|
|
|
|
|
|
|
/* set if the operand is killed by this (or another) phi instruction */
|
|
|
|
|
operand.setKill(!live.count(operand.tempId()));
|
2019-10-29 11:56:09 +01:00
|
|
|
}
|
|
|
|
|
phi_idx--;
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-19 14:11:51 +02:00
|
|
|
block->live_in_demand = new_demand;
|
|
|
|
|
block->live_in_demand.sgpr += 2; /* Add 2 SGPRs for potential long-jumps. */
|
|
|
|
|
|
2023-06-12 13:28:11 +02:00
|
|
|
assert(!block->linear_preds.empty() || (new_demand == RegisterDemand() && live.empty()));
|
2019-09-17 13:22:17 +02:00
|
|
|
}
|
2019-12-18 16:18:35 +00:00
|
|
|
|
|
|
|
|
unsigned
|
|
|
|
|
calc_waves_per_workgroup(Program* program)
|
|
|
|
|
{
|
2020-03-12 16:28:48 +01:00
|
|
|
/* When workgroup size is not known, just go with wave_size */
|
|
|
|
|
unsigned workgroup_size =
|
|
|
|
|
program->workgroup_size == UINT_MAX ? program->wave_size : program->workgroup_size;
|
|
|
|
|
|
2019-12-18 16:18:35 +00:00
|
|
|
return align(workgroup_size, program->wave_size) / program->wave_size;
|
|
|
|
|
}
|
2019-09-17 13:22:17 +02:00
|
|
|
} /* end namespace */
|
|
|
|
|
|
2023-08-11 20:58:32 +01:00
|
|
|
bool
|
|
|
|
|
uses_scratch(Program* program)
|
|
|
|
|
{
|
|
|
|
|
/* RT uses scratch but we don't yet know how much. */
|
|
|
|
|
return program->config->scratch_bytes_per_wave || program->stage == raytracing_cs;
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-13 16:41:00 +01:00
|
|
|
uint16_t
|
|
|
|
|
get_extra_sgprs(Program* program)
|
|
|
|
|
{
|
2023-08-11 20:58:32 +01:00
|
|
|
/* We don't use this register on GFX6-8 and it's removed on GFX10+. */
|
|
|
|
|
bool needs_flat_scr = uses_scratch(program) && program->gfx_level == GFX9;
|
2022-05-19 14:12:08 +01:00
|
|
|
|
2022-05-12 02:50:17 -04:00
|
|
|
if (program->gfx_level >= GFX10) {
|
2021-01-28 13:07:11 +00:00
|
|
|
assert(!program->dev.xnack_enabled);
|
aco: use VCC as regular SGPR pair on GFX10
There is no need to reserve it for special purposes, only.
Totals from 139391 (100.00% of 139391) affected shaders (Navi10):
VGPRs: 4738296 -> 4738156 (-0.00%); split: -0.01%, +0.00%
SpillSGPRs: 16188 -> 14968 (-7.54%); split: -7.60%, +0.06%
CodeSize: 294204472 -> 294118048 (-0.03%); split: -0.04%, +0.01%
MaxWaves: 2119584 -> 2119619 (+0.00%); split: +0.00%, -0.00%
Instrs: 56075079 -> 56056235 (-0.03%); split: -0.05%, +0.01%
Cycles: 1757781564 -> 1755354032 (-0.14%); split: -0.16%, +0.02%
VMEM: 52995887 -> 52996319 (+0.00%); split: +0.07%, -0.07%
SMEM: 9005338 -> 9004858 (-0.01%); split: +0.16%, -0.17%
VClause: 1178436 -> 1178331 (-0.01%); split: -0.02%, +0.01%
SClause: 2403649 -> 2404542 (+0.04%); split: -0.14%, +0.18%
Copies: 3447073 -> 3432417 (-0.43%); split: -0.66%, +0.23%
Branches: 1166542 -> 1166422 (-0.01%); split: -0.11%, +0.10%
PreSGPRs: 4229322 -> 4235538 (+0.15%)
PreVGPRs: 3817111 -> 3817040 (-0.00%)
Reviewed-by: Rhys Perry <pendingchaos02@gmail.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/8921>
2021-02-02 17:46:35 +01:00
|
|
|
return 0;
|
2022-05-12 02:50:17 -04:00
|
|
|
} else if (program->gfx_level >= GFX8) {
|
2022-05-19 14:12:08 +01:00
|
|
|
if (needs_flat_scr)
|
2019-09-13 16:41:00 +01:00
|
|
|
return 6;
|
2021-01-28 13:07:11 +00:00
|
|
|
else if (program->dev.xnack_enabled)
|
2019-09-13 16:41:00 +01:00
|
|
|
return 4;
|
|
|
|
|
else if (program->needs_vcc)
|
|
|
|
|
return 2;
|
|
|
|
|
else
|
|
|
|
|
return 0;
|
|
|
|
|
} else {
|
2021-01-28 13:07:11 +00:00
|
|
|
assert(!program->dev.xnack_enabled);
|
2022-05-19 14:12:08 +01:00
|
|
|
if (needs_flat_scr)
|
2019-09-13 16:41:00 +01:00
|
|
|
return 4;
|
|
|
|
|
else if (program->needs_vcc)
|
|
|
|
|
return 2;
|
|
|
|
|
else
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint16_t
|
|
|
|
|
get_sgpr_alloc(Program* program, uint16_t addressable_sgprs)
|
|
|
|
|
{
|
|
|
|
|
uint16_t sgprs = addressable_sgprs + get_extra_sgprs(program);
|
2021-01-28 13:07:11 +00:00
|
|
|
uint16_t granule = program->dev.sgpr_alloc_granule;
|
2021-02-02 17:33:09 +01:00
|
|
|
return ALIGN_NPOT(std::max(sgprs, granule), granule);
|
2019-09-13 16:41:00 +01:00
|
|
|
}
|
|
|
|
|
|
2019-12-03 14:21:16 +00:00
|
|
|
uint16_t
|
|
|
|
|
get_vgpr_alloc(Program* program, uint16_t addressable_vgprs)
|
|
|
|
|
{
|
2021-01-28 13:07:11 +00:00
|
|
|
assert(addressable_vgprs <= program->dev.vgpr_limit);
|
|
|
|
|
uint16_t granule = program->dev.vgpr_alloc_granule;
|
2022-09-26 17:18:48 +01:00
|
|
|
return ALIGN_NPOT(std::max(addressable_vgprs, granule), granule);
|
2019-12-03 14:21:16 +00:00
|
|
|
}
|
|
|
|
|
|
2021-02-05 14:36:39 +01:00
|
|
|
unsigned
|
|
|
|
|
round_down(unsigned a, unsigned b)
|
2019-09-13 16:41:00 +01:00
|
|
|
{
|
2021-02-05 14:36:39 +01:00
|
|
|
return a - (a % b);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint16_t
|
|
|
|
|
get_addr_sgpr_from_waves(Program* program, uint16_t waves)
|
|
|
|
|
{
|
|
|
|
|
/* it's not possible to allocate more than 128 SGPRs */
|
2021-01-28 13:07:11 +00:00
|
|
|
uint16_t sgprs = std::min(program->dev.physical_sgprs / waves, 128);
|
|
|
|
|
sgprs = round_down(sgprs, program->dev.sgpr_alloc_granule);
|
2021-02-02 17:33:09 +01:00
|
|
|
sgprs -= get_extra_sgprs(program);
|
2021-01-28 13:07:11 +00:00
|
|
|
return std::min(sgprs, program->dev.sgpr_limit);
|
2019-09-13 16:41:00 +01:00
|
|
|
}
|
|
|
|
|
|
2021-02-05 14:36:39 +01:00
|
|
|
uint16_t
|
|
|
|
|
get_addr_vgpr_from_waves(Program* program, uint16_t waves)
|
2019-12-03 14:21:16 +00:00
|
|
|
{
|
2022-09-26 17:18:48 +01:00
|
|
|
uint16_t vgprs = program->dev.physical_vgprs / waves;
|
|
|
|
|
vgprs = vgprs / program->dev.vgpr_alloc_granule * program->dev.vgpr_alloc_granule;
|
2021-02-05 14:38:08 +01:00
|
|
|
vgprs -= program->config->num_shared_vgprs / 2;
|
2021-01-28 13:07:11 +00:00
|
|
|
return std::min(vgprs, program->dev.vgpr_limit);
|
2019-12-03 14:21:16 +00:00
|
|
|
}
|
|
|
|
|
|
2019-12-18 16:18:35 +00:00
|
|
|
void
|
|
|
|
|
calc_min_waves(Program* program)
|
|
|
|
|
{
|
|
|
|
|
unsigned waves_per_workgroup = calc_waves_per_workgroup(program);
|
2021-01-28 13:07:11 +00:00
|
|
|
unsigned simd_per_cu_wgp = program->dev.simd_per_cu * (program->wgp_mode ? 2 : 1);
|
2019-12-18 16:18:35 +00:00
|
|
|
program->min_waves = DIV_ROUND_UP(waves_per_workgroup, simd_per_cu_wgp);
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-19 11:32:56 +02:00
|
|
|
uint16_t
|
|
|
|
|
max_suitable_waves(Program* program, uint16_t waves)
|
2019-09-17 13:22:17 +02:00
|
|
|
{
|
2022-04-19 11:32:56 +02:00
|
|
|
unsigned num_simd = program->dev.simd_per_cu * (program->wgp_mode ? 2 : 1);
|
|
|
|
|
unsigned waves_per_workgroup = calc_waves_per_workgroup(program);
|
|
|
|
|
unsigned num_workgroups = waves * num_simd / waves_per_workgroup;
|
|
|
|
|
|
|
|
|
|
/* Adjust #workgroups for LDS */
|
|
|
|
|
unsigned lds_per_workgroup = align(program->config->lds_size * program->dev.lds_encoding_granule,
|
|
|
|
|
program->dev.lds_alloc_granule);
|
|
|
|
|
|
|
|
|
|
if (program->stage == fragment_fs) {
|
|
|
|
|
/* PS inputs are moved from PC (parameter cache) to LDS before PS waves are launched.
|
|
|
|
|
* Each PS input occupies 3x vec4 of LDS space. See Figure 10.3 in GCN3 ISA manual.
|
|
|
|
|
* These limit occupancy the same way as other stages' LDS usage does.
|
|
|
|
|
*/
|
|
|
|
|
unsigned lds_bytes_per_interp = 3 * 16;
|
2022-05-05 11:32:53 +10:00
|
|
|
unsigned lds_param_bytes = lds_bytes_per_interp * program->info.ps.num_interp;
|
2022-04-19 11:32:56 +02:00
|
|
|
lds_per_workgroup += align(lds_param_bytes, program->dev.lds_alloc_granule);
|
|
|
|
|
}
|
2021-01-28 13:07:11 +00:00
|
|
|
unsigned lds_limit = program->wgp_mode ? program->dev.lds_limit * 2 : program->dev.lds_limit;
|
2022-04-19 11:32:56 +02:00
|
|
|
if (lds_per_workgroup)
|
|
|
|
|
num_workgroups = std::min(num_workgroups, lds_limit / lds_per_workgroup);
|
|
|
|
|
|
|
|
|
|
/* Hardware limitation */
|
|
|
|
|
if (waves_per_workgroup > 1)
|
|
|
|
|
num_workgroups = std::min(num_workgroups, program->wgp_mode ? 32u : 16u);
|
|
|
|
|
|
|
|
|
|
/* Adjust #waves for workgroup multiples:
|
|
|
|
|
* In cases like waves_per_workgroup=3 or lds=65536 and
|
|
|
|
|
* waves_per_workgroup=1, we want the maximum possible number of waves per
|
|
|
|
|
* SIMD and not the minimum. so DIV_ROUND_UP is used
|
|
|
|
|
*/
|
|
|
|
|
unsigned workgroup_waves = num_workgroups * waves_per_workgroup;
|
|
|
|
|
return DIV_ROUND_UP(workgroup_waves, num_simd);
|
|
|
|
|
}
|
2019-10-18 19:06:10 +01:00
|
|
|
|
2022-04-19 11:32:56 +02:00
|
|
|
void
|
|
|
|
|
update_vgpr_sgpr_demand(Program* program, const RegisterDemand new_demand)
|
|
|
|
|
{
|
2021-02-05 14:36:39 +01:00
|
|
|
assert(program->min_waves >= 1);
|
|
|
|
|
uint16_t sgpr_limit = get_addr_sgpr_from_waves(program, program->min_waves);
|
|
|
|
|
uint16_t vgpr_limit = get_addr_vgpr_from_waves(program, program->min_waves);
|
|
|
|
|
|
2019-09-17 13:22:17 +02:00
|
|
|
/* this won't compile, register pressure reduction necessary */
|
2021-02-05 14:36:39 +01:00
|
|
|
if (new_demand.vgpr > vgpr_limit || new_demand.sgpr > sgpr_limit) {
|
2019-09-17 13:22:17 +02:00
|
|
|
program->num_waves = 0;
|
|
|
|
|
program->max_reg_demand = new_demand;
|
|
|
|
|
} else {
|
2021-01-28 13:07:11 +00:00
|
|
|
program->num_waves = program->dev.physical_sgprs / get_sgpr_alloc(program, new_demand.sgpr);
|
2021-02-05 14:38:08 +01:00
|
|
|
uint16_t vgpr_demand =
|
|
|
|
|
get_vgpr_alloc(program, new_demand.vgpr) + program->config->num_shared_vgprs / 2;
|
2021-01-28 13:07:11 +00:00
|
|
|
program->num_waves =
|
|
|
|
|
std::min<uint16_t>(program->num_waves, program->dev.physical_vgprs / vgpr_demand);
|
2023-12-05 16:58:13 +01:00
|
|
|
program->num_waves = std::min(program->num_waves, program->dev.max_waves_per_simd);
|
2019-10-18 19:06:10 +01:00
|
|
|
|
2022-04-19 11:32:56 +02:00
|
|
|
/* Adjust for LDS and workgroup multiples and calculate max_reg_demand */
|
|
|
|
|
program->num_waves = max_suitable_waves(program, program->num_waves);
|
2019-12-03 14:21:16 +00:00
|
|
|
program->max_reg_demand.vgpr = get_addr_vgpr_from_waves(program, program->num_waves);
|
2019-09-13 16:41:00 +01:00
|
|
|
program->max_reg_demand.sgpr = get_addr_sgpr_from_waves(program, program->num_waves);
|
2019-09-17 13:22:17 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-13 11:55:27 +02:00
|
|
|
void
|
2021-04-20 17:35:41 +01:00
|
|
|
live_var_analysis(Program* program)
|
2019-09-17 13:22:17 +02:00
|
|
|
{
|
2024-06-13 11:55:27 +02:00
|
|
|
program->live.live_out.clear();
|
2023-02-10 14:03:11 +01:00
|
|
|
program->live.memory.release();
|
|
|
|
|
program->live.live_out.resize(program->blocks.size(), IDSet(program->live.memory));
|
2024-06-13 11:55:27 +02:00
|
|
|
program->live.register_demand.resize(program->blocks.size());
|
2021-07-14 14:56:48 +02:00
|
|
|
unsigned worklist = program->blocks.size();
|
2021-07-26 17:55:48 +01:00
|
|
|
std::vector<PhiInfo> phi_info(program->blocks.size());
|
2019-09-17 13:22:17 +02:00
|
|
|
RegisterDemand new_demand;
|
|
|
|
|
|
2022-05-12 02:50:17 -04:00
|
|
|
program->needs_vcc = program->gfx_level >= GFX10;
|
2020-02-21 12:23:28 +00:00
|
|
|
|
2019-09-17 13:22:17 +02:00
|
|
|
/* this implementation assumes that the block idx corresponds to the block's position in
|
|
|
|
|
* program->blocks vector */
|
2021-07-14 14:56:48 +02:00
|
|
|
while (worklist) {
|
|
|
|
|
unsigned block_idx = --worklist;
|
2024-06-13 11:55:27 +02:00
|
|
|
process_live_temps_per_block(program, &program->blocks[block_idx], worklist, phi_info);
|
2019-09-17 13:22:17 +02:00
|
|
|
}
|
|
|
|
|
|
2021-07-26 17:55:48 +01:00
|
|
|
/* Handle branches: we will insert copies created for linear phis just before the branch. */
|
|
|
|
|
for (Block& block : program->blocks) {
|
2024-06-13 11:55:27 +02:00
|
|
|
program->live.register_demand[block.index].back().sgpr +=
|
|
|
|
|
phi_info[block.index].linear_phi_defs;
|
|
|
|
|
program->live.register_demand[block.index].back().sgpr -=
|
|
|
|
|
phi_info[block.index].linear_phi_ops;
|
2023-08-08 21:02:21 +01:00
|
|
|
|
|
|
|
|
/* update block's register demand */
|
|
|
|
|
if (program->progress < CompilationProgress::after_ra) {
|
|
|
|
|
block.register_demand = RegisterDemand();
|
2024-06-13 11:55:27 +02:00
|
|
|
for (RegisterDemand& demand : program->live.register_demand[block.index])
|
2023-08-08 21:02:21 +01:00
|
|
|
block.register_demand.update(demand);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
new_demand.update(block.register_demand);
|
2021-07-26 17:55:48 +01:00
|
|
|
}
|
|
|
|
|
|
2019-09-17 13:22:17 +02:00
|
|
|
/* calculate the program's register demand and number of waves */
|
2021-04-20 17:35:41 +01:00
|
|
|
if (program->progress < CompilationProgress::after_ra)
|
2021-04-19 11:24:03 +01:00
|
|
|
update_vgpr_sgpr_demand(program, new_demand);
|
2019-09-17 13:22:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace aco
|