Path: blob/21.2-virgl/src/gallium/drivers/vc4/vc4_qir.c
4570 views
/*1* Copyright © 2014 Broadcom2*3* Permission is hereby granted, free of charge, to any person obtaining a4* copy of this software and associated documentation files (the "Software"),5* to deal in the Software without restriction, including without limitation6* the rights to use, copy, modify, merge, publish, distribute, sublicense,7* and/or sell copies of the Software, and to permit persons to whom the8* Software is furnished to do so, subject to the following conditions:9*10* The above copyright notice and this permission notice (including the next11* paragraph) shall be included in all copies or substantial portions of the12* Software.13*14* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR15* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,16* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL17* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER18* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING19* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS20* IN THE SOFTWARE.21*/2223#include "util/u_memory.h"24#include "util/ralloc.h"2526#include "vc4_qir.h"27#include "vc4_qpu.h"2829struct qir_op_info {30const char *name;31uint8_t ndst, nsrc;32bool has_side_effects;33};3435static const struct qir_op_info qir_op_info[] = {36[QOP_MOV] = { "mov", 1, 1 },37[QOP_FMOV] = { "fmov", 1, 1 },38[QOP_MMOV] = { "mmov", 1, 1 },39[QOP_FADD] = { "fadd", 1, 2 },40[QOP_FSUB] = { "fsub", 1, 2 },41[QOP_FMUL] = { "fmul", 1, 2 },42[QOP_MUL24] = { "mul24", 1, 2 },43[QOP_V8MULD] = {"v8muld", 1, 2 },44[QOP_V8MIN] = {"v8min", 1, 2 },45[QOP_V8MAX] = {"v8max", 1, 2 },46[QOP_V8ADDS] = {"v8adds", 1, 2 },47[QOP_V8SUBS] = {"v8subs", 1, 2 },48[QOP_FMIN] = { "fmin", 1, 2 },49[QOP_FMAX] = { "fmax", 1, 2 },50[QOP_FMINABS] = { "fminabs", 1, 2 },51[QOP_FMAXABS] = { "fmaxabs", 1, 2 },52[QOP_FTOI] = { "ftoi", 1, 1 },53[QOP_ITOF] = { "itof", 1, 1 },54[QOP_ADD] = { "add", 1, 2 },55[QOP_SUB] = { "sub", 1, 2 },56[QOP_SHR] = { "shr", 1, 2 },57[QOP_ASR] = { "asr", 1, 2 },58[QOP_SHL] = { "shl", 1, 2 },59[QOP_MIN] = { "min", 1, 2 },60[QOP_MIN_NOIMM] = { "min_noimm", 1, 2 },61[QOP_MAX] = { "max", 1, 2 },62[QOP_AND] = { "and", 1, 2 },63[QOP_OR] = { "or", 1, 2 },64[QOP_XOR] = { "xor", 1, 2 },65[QOP_NOT] = { "not", 1, 1 },6667[QOP_RCP] = { "rcp", 1, 1 },68[QOP_RSQ] = { "rsq", 1, 1 },69[QOP_EXP2] = { "exp2", 1, 1 },70[QOP_LOG2] = { "log2", 1, 1 },71[QOP_TLB_COLOR_READ] = { "tlb_color_read", 1, 0 },72[QOP_MS_MASK] = { "ms_mask", 0, 1, true },73[QOP_VARY_ADD_C] = { "vary_add_c", 1, 1 },7475[QOP_FRAG_Z] = { "frag_z", 1, 0 },76[QOP_FRAG_W] = { "frag_w", 1, 0 },7778[QOP_TEX_RESULT] = { "tex_result", 1, 0, true },7980[QOP_THRSW] = { "thrsw", 0, 0, true },8182[QOP_LOAD_IMM] = { "load_imm", 0, 1 },83[QOP_LOAD_IMM_U2] = { "load_imm_u2", 0, 1 },84[QOP_LOAD_IMM_I2] = { "load_imm_i2", 0, 1 },8586[QOP_ROT_MUL] = { "rot_mul", 0, 2 },8788[QOP_BRANCH] = { "branch", 0, 0, true },89[QOP_UNIFORMS_RESET] = { "uniforms_reset", 0, 2, true },90};9192static const char *93qir_get_op_name(enum qop qop)94{95if (qop < ARRAY_SIZE(qir_op_info) && qir_op_info[qop].name)96return qir_op_info[qop].name;97else98return "???";99}100101int102qir_get_non_sideband_nsrc(struct qinst *inst)103{104assert(qir_op_info[inst->op].name);105return qir_op_info[inst->op].nsrc;106}107108int109qir_get_nsrc(struct qinst *inst)110{111assert(qir_op_info[inst->op].name);112113int nsrc = qir_get_non_sideband_nsrc(inst);114115/* Normal (non-direct) texture coordinate writes also implicitly load116* a uniform for the texture parameters.117*/118if (qir_is_tex(inst) && inst->dst.file != QFILE_TEX_S_DIRECT)119nsrc++;120121return nsrc;122}123124/* The sideband uniform for textures gets stored after the normal ALU125* arguments.126*/127int128qir_get_tex_uniform_src(struct qinst *inst)129{130return qir_get_nsrc(inst) - 1;131}132133/**134* Returns whether the instruction has any side effects that must be135* preserved.136*/137bool138qir_has_side_effects(struct vc4_compile *c, struct qinst *inst)139{140switch (inst->dst.file) {141case QFILE_TLB_Z_WRITE:142case QFILE_TLB_COLOR_WRITE:143case QFILE_TLB_COLOR_WRITE_MS:144case QFILE_TLB_STENCIL_SETUP:145case QFILE_TEX_S_DIRECT:146case QFILE_TEX_S:147case QFILE_TEX_T:148case QFILE_TEX_R:149case QFILE_TEX_B:150return true;151default:152break;153}154155return qir_op_info[inst->op].has_side_effects;156}157158bool159qir_has_side_effect_reads(struct vc4_compile *c, struct qinst *inst)160{161/* We can dead-code eliminate varyings, because we only tell the VS162* about the live ones at the end. But we have to preserve the163* point/line coordinates reads, because they're generated by164* fixed-function hardware.165*/166for (int i = 0; i < qir_get_nsrc(inst); i++) {167if (inst->src[i].file == QFILE_VARY &&168c->input_slots[inst->src[i].index].slot == 0xff) {169return true;170}171172if (inst->src[i].file == QFILE_VPM)173return true;174}175176if (inst->dst.file == QFILE_VPM)177return true;178179return false;180}181182bool183qir_has_uniform_read(struct qinst *inst)184{185for (int i = 0; i < qir_get_nsrc(inst); i++) {186if (inst->src[i].file == QFILE_UNIF)187return true;188}189190return false;191}192193bool194qir_is_mul(struct qinst *inst)195{196switch (inst->op) {197case QOP_MMOV:198case QOP_FMUL:199case QOP_MUL24:200case QOP_V8MULD:201case QOP_V8MIN:202case QOP_V8MAX:203case QOP_V8ADDS:204case QOP_V8SUBS:205case QOP_ROT_MUL:206return true;207default:208return false;209}210}211212bool213qir_is_float_input(struct qinst *inst)214{215switch (inst->op) {216case QOP_FMOV:217case QOP_FMUL:218case QOP_FADD:219case QOP_FSUB:220case QOP_FMIN:221case QOP_FMAX:222case QOP_FMINABS:223case QOP_FMAXABS:224case QOP_FTOI:225return true;226default:227return false;228}229}230231bool232qir_is_raw_mov(struct qinst *inst)233{234return ((inst->op == QOP_MOV ||235inst->op == QOP_FMOV ||236inst->op == QOP_MMOV) &&237inst->cond == QPU_COND_ALWAYS &&238!inst->dst.pack &&239!inst->src[0].pack);240}241242bool243qir_is_tex(struct qinst *inst)244{245switch (inst->dst.file) {246case QFILE_TEX_S_DIRECT:247case QFILE_TEX_S:248case QFILE_TEX_T:249case QFILE_TEX_R:250case QFILE_TEX_B:251return true;252default:253return false;254}255}256257bool258qir_has_implicit_tex_uniform(struct qinst *inst)259{260switch (inst->dst.file) {261case QFILE_TEX_S:262case QFILE_TEX_T:263case QFILE_TEX_R:264case QFILE_TEX_B:265return true;266default:267return false;268}269}270271bool272qir_depends_on_flags(struct qinst *inst)273{274if (inst->op == QOP_BRANCH) {275return inst->cond != QPU_COND_BRANCH_ALWAYS;276} else {277return (inst->cond != QPU_COND_ALWAYS &&278inst->cond != QPU_COND_NEVER);279}280}281282bool283qir_writes_r4(struct qinst *inst)284{285switch (inst->op) {286case QOP_TEX_RESULT:287case QOP_TLB_COLOR_READ:288case QOP_RCP:289case QOP_RSQ:290case QOP_EXP2:291case QOP_LOG2:292return true;293default:294return false;295}296}297298uint8_t299qir_channels_written(struct qinst *inst)300{301if (qir_is_mul(inst)) {302switch (inst->dst.pack) {303case QPU_PACK_MUL_NOP:304case QPU_PACK_MUL_8888:305return 0xf;306case QPU_PACK_MUL_8A:307return 0x1;308case QPU_PACK_MUL_8B:309return 0x2;310case QPU_PACK_MUL_8C:311return 0x4;312case QPU_PACK_MUL_8D:313return 0x8;314}315} else {316switch (inst->dst.pack) {317case QPU_PACK_A_NOP:318case QPU_PACK_A_8888:319case QPU_PACK_A_8888_SAT:320case QPU_PACK_A_32_SAT:321return 0xf;322case QPU_PACK_A_8A:323case QPU_PACK_A_8A_SAT:324return 0x1;325case QPU_PACK_A_8B:326case QPU_PACK_A_8B_SAT:327return 0x2;328case QPU_PACK_A_8C:329case QPU_PACK_A_8C_SAT:330return 0x4;331case QPU_PACK_A_8D:332case QPU_PACK_A_8D_SAT:333return 0x8;334case QPU_PACK_A_16A:335case QPU_PACK_A_16A_SAT:336return 0x3;337case QPU_PACK_A_16B:338case QPU_PACK_A_16B_SAT:339return 0xc;340}341}342unreachable("Bad pack field");343}344345char *346qir_describe_uniform(enum quniform_contents contents, uint32_t data,347const uint32_t *uniforms)348{349static const char *quniform_names[] = {350[QUNIFORM_VIEWPORT_X_SCALE] = "vp_x_scale",351[QUNIFORM_VIEWPORT_Y_SCALE] = "vp_y_scale",352[QUNIFORM_VIEWPORT_Z_OFFSET] = "vp_z_offset",353[QUNIFORM_VIEWPORT_Z_SCALE] = "vp_z_scale",354[QUNIFORM_TEXTURE_CONFIG_P0] = "tex_p0",355[QUNIFORM_TEXTURE_CONFIG_P1] = "tex_p1",356[QUNIFORM_TEXTURE_CONFIG_P2] = "tex_p2",357[QUNIFORM_TEXTURE_FIRST_LEVEL] = "tex_first_level",358};359360switch (contents) {361case QUNIFORM_CONSTANT:362return ralloc_asprintf(NULL, "0x%08x / %f", data, uif(data));363case QUNIFORM_UNIFORM:364if (uniforms) {365uint32_t unif = uniforms[data];366return ralloc_asprintf(NULL, "unif[%d] = 0x%08x / %f",367data, unif, uif(unif));368} else {369return ralloc_asprintf(NULL, "unif[%d]", data);370}371372case QUNIFORM_TEXTURE_CONFIG_P0:373case QUNIFORM_TEXTURE_CONFIG_P1:374case QUNIFORM_TEXTURE_CONFIG_P2:375case QUNIFORM_TEXTURE_FIRST_LEVEL:376return ralloc_asprintf(NULL, "%s[%d]",377quniform_names[contents], data);378379default:380if (contents < ARRAY_SIZE(quniform_names) &&381quniform_names[contents]) {382return ralloc_asprintf(NULL, "%s",383quniform_names[contents]);384} else {385return ralloc_asprintf(NULL, "??? %d", contents);386}387}388}389390static void391qir_print_reg(struct vc4_compile *c, struct qreg reg, bool write)392{393static const char *files[] = {394[QFILE_TEMP] = "t",395[QFILE_VARY] = "v",396[QFILE_TLB_COLOR_WRITE] = "tlb_c",397[QFILE_TLB_COLOR_WRITE_MS] = "tlb_c_ms",398[QFILE_TLB_Z_WRITE] = "tlb_z",399[QFILE_TLB_STENCIL_SETUP] = "tlb_stencil",400[QFILE_FRAG_X] = "frag_x",401[QFILE_FRAG_Y] = "frag_y",402[QFILE_FRAG_REV_FLAG] = "frag_rev_flag",403[QFILE_QPU_ELEMENT] = "elem",404[QFILE_TEX_S_DIRECT] = "tex_s_direct",405[QFILE_TEX_S] = "tex_s",406[QFILE_TEX_T] = "tex_t",407[QFILE_TEX_R] = "tex_r",408[QFILE_TEX_B] = "tex_b",409};410411switch (reg.file) {412413case QFILE_NULL:414fprintf(stderr, "null");415break;416417case QFILE_LOAD_IMM:418fprintf(stderr, "0x%08x (%f)", reg.index, uif(reg.index));419break;420421case QFILE_SMALL_IMM:422if ((int)reg.index >= -16 && (int)reg.index <= 15)423fprintf(stderr, "%d", reg.index);424else425fprintf(stderr, "%f", uif(reg.index));426break;427428case QFILE_VPM:429if (write) {430fprintf(stderr, "vpm");431} else {432fprintf(stderr, "vpm%d.%d",433reg.index / 4, reg.index % 4);434}435break;436437case QFILE_TLB_COLOR_WRITE:438case QFILE_TLB_COLOR_WRITE_MS:439case QFILE_TLB_Z_WRITE:440case QFILE_TLB_STENCIL_SETUP:441case QFILE_TEX_S_DIRECT:442case QFILE_TEX_S:443case QFILE_TEX_T:444case QFILE_TEX_R:445case QFILE_TEX_B:446fprintf(stderr, "%s", files[reg.file]);447break;448449case QFILE_UNIF: {450char *desc = qir_describe_uniform(c->uniform_contents[reg.index],451c->uniform_data[reg.index],452NULL);453fprintf(stderr, "u%d (%s)", reg.index, desc);454ralloc_free(desc);455break;456}457458default:459fprintf(stderr, "%s%d", files[reg.file], reg.index);460break;461}462}463464void465qir_dump_inst(struct vc4_compile *c, struct qinst *inst)466{467fprintf(stderr, "%s", qir_get_op_name(inst->op));468if (inst->op == QOP_BRANCH)469vc4_qpu_disasm_cond_branch(stderr, inst->cond);470else471vc4_qpu_disasm_cond(stderr, inst->cond);472if (inst->sf)473fprintf(stderr, ".sf");474fprintf(stderr, " ");475476if (inst->op != QOP_BRANCH) {477qir_print_reg(c, inst->dst, true);478if (inst->dst.pack) {479if (inst->dst.pack) {480if (qir_is_mul(inst))481vc4_qpu_disasm_pack_mul(stderr, inst->dst.pack);482else483vc4_qpu_disasm_pack_a(stderr, inst->dst.pack);484}485}486}487488for (int i = 0; i < qir_get_nsrc(inst); i++) {489fprintf(stderr, ", ");490qir_print_reg(c, inst->src[i], false);491vc4_qpu_disasm_unpack(stderr, inst->src[i].pack);492}493}494495void496qir_dump(struct vc4_compile *c)497{498int ip = 0;499int pressure = 0;500501qir_for_each_block(block, c) {502fprintf(stderr, "BLOCK %d:\n", block->index);503qir_for_each_inst(inst, block) {504if (c->temp_start) {505bool first = true;506507fprintf(stderr, "%3d ", pressure);508509for (int i = 0; i < c->num_temps; i++) {510if (c->temp_start[i] != ip)511continue;512513if (first) {514first = false;515} else {516fprintf(stderr, ", ");517}518fprintf(stderr, "S%4d", i);519pressure++;520}521522if (first)523fprintf(stderr, " ");524else525fprintf(stderr, " ");526}527528if (c->temp_end) {529bool first = true;530531for (int i = 0; i < c->num_temps; i++) {532if (c->temp_end[i] != ip)533continue;534535if (first) {536first = false;537} else {538fprintf(stderr, ", ");539}540fprintf(stderr, "E%4d", i);541pressure--;542}543544if (first)545fprintf(stderr, " ");546else547fprintf(stderr, " ");548}549550qir_dump_inst(c, inst);551fprintf(stderr, "\n");552ip++;553}554if (block->successors[1]) {555fprintf(stderr, "-> BLOCK %d, %d\n",556block->successors[0]->index,557block->successors[1]->index);558} else if (block->successors[0]) {559fprintf(stderr, "-> BLOCK %d\n",560block->successors[0]->index);561}562}563}564565struct qreg566qir_get_temp(struct vc4_compile *c)567{568struct qreg reg;569570reg.file = QFILE_TEMP;571reg.index = c->num_temps++;572reg.pack = 0;573574if (c->num_temps > c->defs_array_size) {575uint32_t old_size = c->defs_array_size;576c->defs_array_size = MAX2(old_size * 2, 16);577c->defs = reralloc(c, c->defs, struct qinst *,578c->defs_array_size);579memset(&c->defs[old_size], 0,580sizeof(c->defs[0]) * (c->defs_array_size - old_size));581}582583return reg;584}585586struct qinst *587qir_inst(enum qop op, struct qreg dst, struct qreg src0, struct qreg src1)588{589struct qinst *inst = CALLOC_STRUCT(qinst);590591inst->op = op;592inst->dst = dst;593inst->src[0] = src0;594inst->src[1] = src1;595inst->cond = QPU_COND_ALWAYS;596597return inst;598}599600static void601qir_emit(struct vc4_compile *c, struct qinst *inst)602{603list_addtail(&inst->link, &c->cur_block->instructions);604}605606/* Updates inst to write to a new temporary, emits it, and notes the def. */607struct qreg608qir_emit_def(struct vc4_compile *c, struct qinst *inst)609{610assert(inst->dst.file == QFILE_NULL);611612inst->dst = qir_get_temp(c);613614if (inst->dst.file == QFILE_TEMP)615c->defs[inst->dst.index] = inst;616617qir_emit(c, inst);618619return inst->dst;620}621622struct qinst *623qir_emit_nondef(struct vc4_compile *c, struct qinst *inst)624{625if (inst->dst.file == QFILE_TEMP)626c->defs[inst->dst.index] = NULL;627628qir_emit(c, inst);629630return inst;631}632633bool634qir_reg_equals(struct qreg a, struct qreg b)635{636return a.file == b.file && a.index == b.index && a.pack == b.pack;637}638639struct qblock *640qir_new_block(struct vc4_compile *c)641{642struct qblock *block = rzalloc(c, struct qblock);643644list_inithead(&block->instructions);645list_inithead(&block->qpu_inst_list);646647block->predecessors = _mesa_set_create(block,648_mesa_hash_pointer,649_mesa_key_pointer_equal);650651block->index = c->next_block_index++;652653return block;654}655656void657qir_set_emit_block(struct vc4_compile *c, struct qblock *block)658{659c->cur_block = block;660list_addtail(&block->link, &c->blocks);661}662663struct qblock *664qir_entry_block(struct vc4_compile *c)665{666return list_first_entry(&c->blocks, struct qblock, link);667}668669struct qblock *670qir_exit_block(struct vc4_compile *c)671{672return list_last_entry(&c->blocks, struct qblock, link);673}674675void676qir_link_blocks(struct qblock *predecessor, struct qblock *successor)677{678_mesa_set_add(successor->predecessors, predecessor);679if (predecessor->successors[0]) {680assert(!predecessor->successors[1]);681predecessor->successors[1] = successor;682} else {683predecessor->successors[0] = successor;684}685}686687struct vc4_compile *688qir_compile_init(void)689{690struct vc4_compile *c = rzalloc(NULL, struct vc4_compile);691692list_inithead(&c->blocks);693qir_set_emit_block(c, qir_new_block(c));694c->last_top_block = c->cur_block;695696c->output_position_index = -1;697c->output_color_index = -1;698c->output_point_size_index = -1;699c->output_sample_mask_index = -1;700701c->def_ht = _mesa_hash_table_create(c, _mesa_hash_pointer,702_mesa_key_pointer_equal);703704return c;705}706707void708qir_remove_instruction(struct vc4_compile *c, struct qinst *qinst)709{710if (qinst->dst.file == QFILE_TEMP)711c->defs[qinst->dst.index] = NULL;712713list_del(&qinst->link);714free(qinst);715}716717struct qreg718qir_follow_movs(struct vc4_compile *c, struct qreg reg)719{720int pack = reg.pack;721722while (reg.file == QFILE_TEMP &&723c->defs[reg.index] &&724(c->defs[reg.index]->op == QOP_MOV ||725c->defs[reg.index]->op == QOP_FMOV ||726c->defs[reg.index]->op == QOP_MMOV)&&727!c->defs[reg.index]->dst.pack &&728!c->defs[reg.index]->src[0].pack) {729reg = c->defs[reg.index]->src[0];730}731732reg.pack = pack;733return reg;734}735736void737qir_compile_destroy(struct vc4_compile *c)738{739qir_for_each_block(block, c) {740while (!list_is_empty(&block->instructions)) {741struct qinst *qinst =742list_first_entry(&block->instructions,743struct qinst, link);744qir_remove_instruction(c, qinst);745}746}747748ralloc_free(c);749}750751const char *752qir_get_stage_name(enum qstage stage)753{754static const char *names[] = {755[QSTAGE_FRAG] = "FS",756[QSTAGE_VERT] = "VS",757[QSTAGE_COORD] = "CS",758};759760return names[stage];761}762763struct qreg764qir_uniform(struct vc4_compile *c,765enum quniform_contents contents,766uint32_t data)767{768for (int i = 0; i < c->num_uniforms; i++) {769if (c->uniform_contents[i] == contents &&770c->uniform_data[i] == data) {771return qir_reg(QFILE_UNIF, i);772}773}774775uint32_t uniform = c->num_uniforms++;776777if (uniform >= c->uniform_array_size) {778c->uniform_array_size = MAX2(MAX2(16, uniform + 1),779c->uniform_array_size * 2);780781c->uniform_data = reralloc(c, c->uniform_data,782uint32_t,783c->uniform_array_size);784c->uniform_contents = reralloc(c, c->uniform_contents,785enum quniform_contents,786c->uniform_array_size);787}788789c->uniform_contents[uniform] = contents;790c->uniform_data[uniform] = data;791792return qir_reg(QFILE_UNIF, uniform);793}794795void796qir_SF(struct vc4_compile *c, struct qreg src)797{798struct qinst *last_inst = NULL;799800if (!list_is_empty(&c->cur_block->instructions))801last_inst = (struct qinst *)c->cur_block->instructions.prev;802803/* We don't have any way to guess which kind of MOV is implied. */804assert(!src.pack);805806if (src.file != QFILE_TEMP ||807!c->defs[src.index] ||808last_inst != c->defs[src.index]) {809last_inst = qir_MOV_dest(c, qir_reg(QFILE_NULL, 0), src);810last_inst = (struct qinst *)c->cur_block->instructions.prev;811}812last_inst->sf = true;813}814815#define OPTPASS(func) \816do { \817bool stage_progress = func(c); \818if (stage_progress) { \819progress = true; \820if (print_opt_debug) { \821fprintf(stderr, \822"QIR opt pass %2d: %s progress\n", \823pass, #func); \824} \825qir_validate(c); \826} \827} while (0)828829void830qir_optimize(struct vc4_compile *c)831{832bool print_opt_debug = false;833int pass = 1;834835while (true) {836bool progress = false;837838OPTPASS(qir_opt_algebraic);839OPTPASS(qir_opt_constant_folding);840OPTPASS(qir_opt_copy_propagation);841OPTPASS(qir_opt_peephole_sf);842OPTPASS(qir_opt_dead_code);843OPTPASS(qir_opt_small_immediates);844OPTPASS(qir_opt_vpm);845OPTPASS(qir_opt_coalesce_ff_writes);846847if (!progress)848break;849850pass++;851}852}853854855