Path: blob/21.2-virgl/src/gallium/drivers/vc4/vc4_qir_schedule.c
4570 views
/*1* Copyright © 2010 Intel Corporation2* Copyright © 2014-2015 Broadcom3*4* Permission is hereby granted, free of charge, to any person obtaining a5* copy of this software and associated documentation files (the "Software"),6* to deal in the Software without restriction, including without limitation7* the rights to use, copy, modify, merge, publish, distribute, sublicense,8* and/or sell copies of the Software, and to permit persons to whom the9* Software is furnished to do so, subject to the following conditions:10*11* The above copyright notice and this permission notice (including the next12* paragraph) shall be included in all copies or substantial portions of the13* Software.14*15* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR16* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,17* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL18* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER19* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING20* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS21* IN THE SOFTWARE.22*/2324/**25* @file vc4_qir_schedule.c26*27* The basic model of the list scheduler is to take a basic block, compute a28* DAG of the dependencies from the bottom up, and make a list of the DAG29* heads. Heuristically pick a DAG head and schedule (remove) it, then put30* all the parents that are now DAG heads into the list of things to31* schedule.32*33* The goal of scheduling here, before register allocation and conversion to34* QPU instructions, is to reduce register pressure by reordering instructions35* to consume values when possible.36*/3738#include "vc4_qir.h"39#include "util/dag.h"4041static bool debug;4243struct schedule_node {44struct dag_node dag;45struct list_head link;46struct qinst *inst;4748/* Length of the longest (latency) chain from a DAG head to the this49* instruction.50*/51uint32_t delay;5253/* Longest time + latency_between(parent, this) of any parent of this54* node.55*/56uint32_t unblocked_time;57};5859struct schedule_state {60struct dag *dag;6162uint32_t time;6364uint32_t *temp_writes;6566BITSET_WORD *temp_live;67};6869/* When walking the instructions in reverse, we need to swap before/after in70* add_dep().71*/72enum direction { F, R };7374/**75* Marks a dependency between two intructions, that \p after must appear after76* \p before.77*78* Our dependencies are tracked as a DAG. Since we're scheduling bottom-up,79* the latest instructions with nothing left to schedule are the DAG heads,80* and their inputs are their children.81*/82static void83add_dep(enum direction dir,84struct schedule_node *before,85struct schedule_node *after)86{87if (!before || !after)88return;8990assert(before != after);9192if (dir == R) {93struct schedule_node *t = before;94before = after;95after = t;96}9798dag_add_edge(&after->dag, &before->dag, NULL);99}100101static void102add_write_dep(enum direction dir,103struct schedule_node **before,104struct schedule_node *after)105{106add_dep(dir, *before, after);107*before = after;108}109110struct schedule_setup_state {111struct schedule_node **last_temp_write;112struct schedule_node *last_sf;113struct schedule_node *last_vary_read;114struct schedule_node *last_vpm_read;115struct schedule_node *last_vpm_write;116struct schedule_node *last_tex_coord;117struct schedule_node *last_tex_result;118struct schedule_node *last_tlb;119struct schedule_node *last_uniforms_reset;120enum direction dir;121122/**123* Texture FIFO tracking. This is done top-to-bottom, and is used to124* track the QOP_TEX_RESULTs and add dependencies on previous ones125* when trying to submit texture coords with TFREQ full or new texture126* fetches with TXRCV full.127*/128struct {129struct schedule_node *node;130int coords;131} tex_fifo[8];132int tfreq_count; /**< Number of texture coords outstanding. */133int tfrcv_count; /**< Number of texture results outstanding. */134int tex_fifo_pos;135};136137static void138block_until_tex_result(struct schedule_setup_state *state, struct schedule_node *n)139{140add_dep(state->dir, state->tex_fifo[0].node, n);141142state->tfreq_count -= state->tex_fifo[0].coords;143state->tfrcv_count--;144145memmove(&state->tex_fifo[0],146&state->tex_fifo[1],147state->tex_fifo_pos * sizeof(state->tex_fifo[0]));148state->tex_fifo_pos--;149}150151/**152* Common code for dependencies that need to be tracked both forward and153* backward.154*155* This is for things like "all VPM reads have to happen in order."156*/157static void158calculate_deps(struct schedule_setup_state *state, struct schedule_node *n)159{160struct qinst *inst = n->inst;161enum direction dir = state->dir;162163164/* Add deps for temp registers and varyings accesses. Note that we165* ignore uniforms accesses, because qir_reorder_uniforms() happens166* after this.167*/168for (int i = 0; i < qir_get_nsrc(inst); i++) {169switch (inst->src[i].file) {170case QFILE_TEMP:171add_dep(dir,172state->last_temp_write[inst->src[i].index], n);173break;174175case QFILE_VARY:176add_write_dep(dir, &state->last_vary_read, n);177break;178179case QFILE_VPM:180add_write_dep(dir, &state->last_vpm_read, n);181break;182183default:184break;185}186}187188switch (inst->op) {189case QOP_VARY_ADD_C:190add_dep(dir, state->last_vary_read, n);191break;192193case QOP_TEX_RESULT:194/* Results have to be fetched in order. */195add_write_dep(dir, &state->last_tex_result, n);196break;197198case QOP_THRSW:199/* After a new THRSW, one must collect all texture samples200* queued since the previous THRSW/program start. For now, we201* have one THRSW in between each texture setup and its202* results collection as our input, and we just make sure that203* that ordering is maintained.204*/205add_write_dep(dir, &state->last_tex_coord, n);206add_write_dep(dir, &state->last_tex_result, n);207208/* accumulators and flags are lost across thread switches. */209add_write_dep(dir, &state->last_sf, n);210211/* Setup, like the varyings, will need to be drained before we212* thread switch.213*/214add_write_dep(dir, &state->last_vary_read, n);215216/* The TLB-locking operations have to stay after the last217* thread switch.218*/219add_write_dep(dir, &state->last_tlb, n);220break;221222case QOP_TLB_COLOR_READ:223case QOP_MS_MASK:224add_write_dep(dir, &state->last_tlb, n);225break;226227default:228break;229}230231switch (inst->dst.file) {232case QFILE_VPM:233add_write_dep(dir, &state->last_vpm_write, n);234break;235236case QFILE_TEMP:237add_write_dep(dir, &state->last_temp_write[inst->dst.index], n);238break;239240case QFILE_TLB_COLOR_WRITE:241case QFILE_TLB_COLOR_WRITE_MS:242case QFILE_TLB_Z_WRITE:243case QFILE_TLB_STENCIL_SETUP:244add_write_dep(dir, &state->last_tlb, n);245break;246247case QFILE_TEX_S_DIRECT:248case QFILE_TEX_S:249case QFILE_TEX_T:250case QFILE_TEX_R:251case QFILE_TEX_B:252/* Texturing setup gets scheduled in order, because253* the uniforms referenced by them have to land in a254* specific order.255*/256add_write_dep(dir, &state->last_tex_coord, n);257break;258259default:260break;261}262263if (qir_depends_on_flags(inst))264add_dep(dir, state->last_sf, n);265266if (inst->sf)267add_write_dep(dir, &state->last_sf, n);268}269270static void271calculate_forward_deps(struct vc4_compile *c, void *mem_ctx,272struct list_head *schedule_list)273{274struct schedule_setup_state state;275276memset(&state, 0, sizeof(state));277state.last_temp_write = rzalloc_array(mem_ctx, struct schedule_node *,278c->num_temps);279state.dir = F;280281list_for_each_entry(struct schedule_node, n, schedule_list, link) {282struct qinst *inst = n->inst;283284calculate_deps(&state, n);285286for (int i = 0; i < qir_get_nsrc(inst); i++) {287switch (inst->src[i].file) {288case QFILE_UNIF:289add_dep(state.dir, state.last_uniforms_reset, n);290break;291default:292break;293}294}295296switch (inst->dst.file) {297case QFILE_TEX_S_DIRECT:298case QFILE_TEX_S:299case QFILE_TEX_T:300case QFILE_TEX_R:301case QFILE_TEX_B:302/* From the VC4 spec:303*304* "The TFREQ input FIFO holds two full lots of s,305* t, r, b data, plus associated setup data, per306* QPU, that is, there are eight data slots. For307* each texture request, slots are only consumed308* for the components of s, t, r, and b actually309* written. Thus the FIFO can hold four requests310* of just (s, t) data, or eight requests of just311* s data (for direct addressed data lookups).312*313* Note that there is one FIFO per QPU, and the314* FIFO has no concept of threads - that is,315* multi-threaded shaders must be careful to use316* only 1/2 the FIFO depth before reading317* back. Multi-threaded programs must also318* therefore always thread switch on texture319* fetch as the other thread may have data320* waiting in the FIFO."321*322* If the texture coordinate fifo is full, block this323* on the last QOP_TEX_RESULT.324*/325if (state.tfreq_count == (c->fs_threaded ? 4 : 8)) {326block_until_tex_result(&state, n);327}328329/* From the VC4 spec:330*331* "Since the maximum number of texture requests332* in the input (TFREQ) FIFO is four lots of (s,333* t) data, the output (TFRCV) FIFO is sized to334* holds four lots of max-size color data per335* QPU. For non-float color, reads are packed336* RGBA8888 data (one read per pixel). For 16-bit337* float color, two reads are necessary per338* pixel, with reads packed as RG1616 then339* BA1616. So per QPU there are eight color slots340* in the TFRCV FIFO."341*342* If the texture result fifo is full, block adding343* any more to it until the last QOP_TEX_RESULT.344*/345if (inst->dst.file == QFILE_TEX_S ||346inst->dst.file == QFILE_TEX_S_DIRECT) {347if (state.tfrcv_count ==348(c->fs_threaded ? 2 : 4))349block_until_tex_result(&state, n);350state.tfrcv_count++;351}352353state.tex_fifo[state.tex_fifo_pos].coords++;354state.tfreq_count++;355break;356357default:358break;359}360361switch (inst->op) {362case QOP_TEX_RESULT:363/* Results have to be fetched after the364* coordinate setup. Note that we're assuming365* here that our input shader has the texture366* coord setup and result fetch in order,367* which is true initially but not of our368* instruction stream after this pass.369*/370add_dep(state.dir, state.last_tex_coord, n);371372state.tex_fifo[state.tex_fifo_pos].node = n;373374state.tex_fifo_pos++;375memset(&state.tex_fifo[state.tex_fifo_pos], 0,376sizeof(state.tex_fifo[0]));377break;378379case QOP_UNIFORMS_RESET:380add_write_dep(state.dir, &state.last_uniforms_reset, n);381break;382383default:384break;385}386}387}388389static void390calculate_reverse_deps(struct vc4_compile *c, void *mem_ctx,391struct list_head *schedule_list)392{393struct schedule_setup_state state;394395memset(&state, 0, sizeof(state));396state.dir = R;397state.last_temp_write = rzalloc_array(mem_ctx, struct schedule_node *,398c->num_temps);399400list_for_each_entry_rev(struct schedule_node, n, schedule_list, link) {401calculate_deps(&state, n);402}403}404405static int406get_register_pressure_cost(struct schedule_state *state, struct qinst *inst)407{408int cost = 0;409410if (inst->dst.file == QFILE_TEMP &&411state->temp_writes[inst->dst.index] == 1)412cost--;413414for (int i = 0; i < qir_get_nsrc(inst); i++) {415if (inst->src[i].file != QFILE_TEMP ||416BITSET_TEST(state->temp_live, inst->src[i].index)) {417continue;418}419420bool already_counted = false;421for (int j = 0; j < i; j++) {422if (inst->src[i].file == inst->src[j].file &&423inst->src[i].index == inst->src[j].index) {424already_counted = true;425}426}427if (!already_counted)428cost++;429}430431return cost;432}433434static bool435locks_scoreboard(struct qinst *inst)436{437if (inst->op == QOP_TLB_COLOR_READ)438return true;439440switch (inst->dst.file) {441case QFILE_TLB_Z_WRITE:442case QFILE_TLB_COLOR_WRITE:443case QFILE_TLB_COLOR_WRITE_MS:444return true;445default:446return false;447}448}449450static struct schedule_node *451choose_instruction(struct schedule_state *state)452{453struct schedule_node *chosen = NULL;454455list_for_each_entry(struct schedule_node, n, &state->dag->heads,456dag.link) {457/* The branches aren't being tracked as dependencies. Make458* sure that they stay scheduled as the last instruction of459* the block, which is to say the first one we choose to460* schedule.461*/462if (n->inst->op == QOP_BRANCH)463return n;464465if (!chosen) {466chosen = n;467continue;468}469470/* Prefer scheduling things that lock the scoreboard, so that471* they appear late in the program and we get more parallelism472* between shaders on multiple QPUs hitting the same fragment.473*/474if (locks_scoreboard(n->inst) &&475!locks_scoreboard(chosen->inst)) {476chosen = n;477continue;478} else if (!locks_scoreboard(n->inst) &&479locks_scoreboard(chosen->inst)) {480continue;481}482483/* If we would block on the previously chosen node, but would484* block less on this one, then prefer it.485*/486if (chosen->unblocked_time > state->time &&487n->unblocked_time < chosen->unblocked_time) {488chosen = n;489continue;490} else if (n->unblocked_time > state->time &&491n->unblocked_time > chosen->unblocked_time) {492continue;493}494495/* If we can definitely reduce register pressure, do so496* immediately.497*/498int register_pressure_cost =499get_register_pressure_cost(state, n->inst);500int chosen_register_pressure_cost =501get_register_pressure_cost(state, chosen->inst);502503if (register_pressure_cost < chosen_register_pressure_cost) {504chosen = n;505continue;506} else if (register_pressure_cost >507chosen_register_pressure_cost) {508continue;509}510511/* Otherwise, prefer instructions with the deepest chain to512* the end of the program. This avoids the problem of513* "everything generates a temp, nothing finishes freeing one,514* guess I'll just keep emitting varying mul/adds".515*/516if (n->delay > chosen->delay) {517chosen = n;518continue;519} else if (n->delay < chosen->delay) {520continue;521}522}523524return chosen;525}526527static void528dump_state(struct vc4_compile *c, struct schedule_state *state)529{530uint32_t i = 0;531list_for_each_entry(struct schedule_node, n, &state->dag->heads,532dag.link) {533fprintf(stderr, "%3d: ", i++);534qir_dump_inst(c, n->inst);535fprintf(stderr, " (%d cost)\n",536get_register_pressure_cost(state, n->inst));537538util_dynarray_foreach(&n->dag.edges, struct dag_edge, edge) {539struct schedule_node *child =540(struct schedule_node *)edge->child;541fprintf(stderr, " - ");542qir_dump_inst(c, child->inst);543fprintf(stderr, " (%d parents)\n",544child->dag.parent_count);545}546}547}548549/* Estimate of how many instructions we should schedule between operations.550*551* These aren't in real cycle counts, because we're just estimating cycle552* times anyway. QIR instructions will get paired up when turned into QPU553* instructions, or extra NOP delays will have to be added due to register554* allocation choices.555*/556static uint32_t557latency_between(struct schedule_node *before, struct schedule_node *after)558{559if ((before->inst->dst.file == QFILE_TEX_S ||560before->inst->dst.file == QFILE_TEX_S_DIRECT) &&561after->inst->op == QOP_TEX_RESULT)562return 100;563564switch (before->inst->op) {565case QOP_RCP:566case QOP_RSQ:567case QOP_EXP2:568case QOP_LOG2:569for (int i = 0; i < qir_get_nsrc(after->inst); i++) {570if (after->inst->src[i].file ==571before->inst->dst.file &&572after->inst->src[i].index ==573before->inst->dst.index) {574/* There are two QPU delay slots before we can575* read a math result, which could be up to 4576* QIR instructions if they packed well.577*/578return 4;579}580}581break;582default:583break;584}585586return 1;587}588589/** Recursive computation of the delay member of a node. */590static void591compute_delay(struct dag_node *node, void *state)592{593struct schedule_node *n = (struct schedule_node *)node;594595/* The color read needs to be scheduled late, to avoid locking the596* scoreboard early. This is our best tool for encouraging that. The597* other scoreboard locking ops will have this happen by default,598* since they are generally the DAG heads or close to them.599*/600if (n->inst->op == QOP_TLB_COLOR_READ)601n->delay = 1000;602else603n->delay = 1;604605util_dynarray_foreach(&n->dag.edges, struct dag_edge, edge) {606struct schedule_node *child =607(struct schedule_node *)edge->child;608609n->delay = MAX2(n->delay, (child->delay +610latency_between(child, n)));611}612}613614static void615schedule_instructions(struct vc4_compile *c,616struct qblock *block, struct schedule_state *state)617{618if (debug) {619fprintf(stderr, "initial deps:\n");620dump_state(c, state);621}622623state->time = 0;624while (!list_is_empty(&state->dag->heads)) {625struct schedule_node *chosen = choose_instruction(state);626struct qinst *inst = chosen->inst;627628if (debug) {629fprintf(stderr, "current list:\n");630dump_state(c, state);631fprintf(stderr, "chose: ");632qir_dump_inst(c, inst);633fprintf(stderr, " (%d cost)\n",634get_register_pressure_cost(state, inst));635}636637state->time = MAX2(state->time, chosen->unblocked_time);638639/* Schedule this instruction back onto the QIR list. */640list_add(&inst->link, &block->instructions);641642/* Now that we've scheduled a new instruction, some of its643* children can be promoted to the list of instructions ready to644* be scheduled. Update the children's unblocked time for this645* DAG edge as we do so.646*/647util_dynarray_foreach(&chosen->dag.edges,648struct dag_edge, edge) {649struct schedule_node *child =650(struct schedule_node *)edge->child;651652child->unblocked_time = MAX2(child->unblocked_time,653state->time +654latency_between(child,655chosen));656}657dag_prune_head(state->dag, &chosen->dag);658659/* Update our tracking of register pressure. */660for (int i = 0; i < qir_get_nsrc(inst); i++) {661if (inst->src[i].file == QFILE_TEMP)662BITSET_SET(state->temp_live, inst->src[i].index);663}664if (inst->dst.file == QFILE_TEMP) {665state->temp_writes[inst->dst.index]--;666if (state->temp_writes[inst->dst.index] == 0)667BITSET_CLEAR(state->temp_live, inst->dst.index);668}669670state->time++;671}672}673674static void675qir_schedule_instructions_block(struct vc4_compile *c,676struct qblock *block)677{678struct schedule_state *state = rzalloc(NULL, struct schedule_state);679680state->temp_writes = rzalloc_array(state, uint32_t, c->num_temps);681state->temp_live = rzalloc_array(state, BITSET_WORD,682BITSET_WORDS(c->num_temps));683state->dag = dag_create(state);684685struct list_head setup_list;686list_inithead(&setup_list);687688/* Wrap each instruction in a scheduler structure. */689qir_for_each_inst_safe(inst, block) {690struct schedule_node *n = rzalloc(state, struct schedule_node);691692n->inst = inst;693list_del(&inst->link);694list_addtail(&n->link, &setup_list);695dag_init_node(state->dag, &n->dag);696697if (inst->dst.file == QFILE_TEMP)698state->temp_writes[inst->dst.index]++;699}700701/* Dependencies tracked top-to-bottom. */702calculate_forward_deps(c, state, &setup_list);703/* Dependencies tracked bottom-to-top. */704calculate_reverse_deps(c, state, &setup_list);705706dag_traverse_bottom_up(state->dag, compute_delay, NULL);707708schedule_instructions(c, block, state);709710ralloc_free(state);711}712713void714qir_schedule_instructions(struct vc4_compile *c)715{716717if (debug) {718fprintf(stderr, "Pre-schedule instructions\n");719qir_dump(c);720}721722qir_for_each_block(block, c)723qir_schedule_instructions_block(c, block);724725if (debug) {726fprintf(stderr, "Post-schedule instructions\n");727qir_dump(c);728}729}730731732