Path: blob/21.2-virgl/src/freedreno/afuc/disasm.c
4564 views
/*1* Copyright (c) 2017 Rob Clark <[email protected]>2*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, ARISING FROM,19* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE20* SOFTWARE.21*/2223#include <assert.h>24#include <err.h>25#include <fcntl.h>26#include <getopt.h>27#include <stdarg.h>28#include <stdbool.h>29#include <stdint.h>30#include <stdio.h>31#include <stdlib.h>32#include <string.h>33#include <unistd.h>3435#include "util/os_file.h"3637#include "freedreno_pm4.h"3839#include "afuc.h"40#include "util.h"41#include "emu.h"4243static int gpuver;4445/* non-verbose mode should output something suitable to feed back into46* assembler.. verbose mode has additional output useful for debugging47* (like unexpected bits that are set)48*/49static bool verbose = false;5051/* emulator mode: */52static bool emulator = false;5354static void55print_gpu_reg(uint32_t regbase)56{57if (regbase < 0x100)58return;5960char *name = afuc_gpu_reg_name(regbase);61if (name) {62printf("\t; %s", name);63free(name);64}65}6667#define printerr(fmt, ...) afuc_printc(AFUC_ERR, fmt, ##__VA_ARGS__)68#define printlbl(fmt, ...) afuc_printc(AFUC_LBL, fmt, ##__VA_ARGS__)6970void71print_src(unsigned reg)72{73if (reg == REG_REM)74printf("$rem"); /* remainding dwords in packet */75else if (reg == REG_MEMDATA)76printf("$memdata");77else if (reg == REG_REGDATA)78printf("$regdata");79else if (reg == REG_DATA)80printf("$data");81else82printf("$%02x", reg);83}8485void86print_dst(unsigned reg)87{88if (reg == REG_REM)89printf("$rem"); /* remainding dwords in packet */90else if (reg == REG_ADDR)91printf("$addr");92else if (reg == REG_USRADDR)93printf("$usraddr");94else if (reg == REG_DATA)95printf("$data");96else97printf("$%02x", reg);98}99100static void101print_alu_name(afuc_opc opc, uint32_t instr)102{103if (opc == OPC_ADD) {104printf("add ");105} else if (opc == OPC_ADDHI) {106printf("addhi ");107} else if (opc == OPC_SUB) {108printf("sub ");109} else if (opc == OPC_SUBHI) {110printf("subhi ");111} else if (opc == OPC_AND) {112printf("and ");113} else if (opc == OPC_OR) {114printf("or ");115} else if (opc == OPC_XOR) {116printf("xor ");117} else if (opc == OPC_NOT) {118printf("not ");119} else if (opc == OPC_SHL) {120printf("shl ");121} else if (opc == OPC_USHR) {122printf("ushr ");123} else if (opc == OPC_ISHR) {124printf("ishr ");125} else if (opc == OPC_ROT) {126printf("rot ");127} else if (opc == OPC_MUL8) {128printf("mul8 ");129} else if (opc == OPC_MIN) {130printf("min ");131} else if (opc == OPC_MAX) {132printf("max ");133} else if (opc == OPC_CMP) {134printf("cmp ");135} else if (opc == OPC_MSB) {136printf("msb ");137} else {138printerr("[%08x]", instr);139printf(" ; alu%02x ", opc);140}141}142143static const char *144getpm4(uint32_t id)145{146return afuc_pm_id_name(id);147}148149static struct {150uint32_t offset;151uint32_t num_jump_labels;152uint32_t jump_labels[256];153} jump_labels[1024];154int num_jump_labels;155156static void157add_jump_table_entry(uint32_t n, uint32_t offset)158{159int i;160161if (n > 128) /* can't possibly be a PM4 PKT3.. */162return;163164for (i = 0; i < num_jump_labels; i++)165if (jump_labels[i].offset == offset)166goto add_label;167168num_jump_labels = i + 1;169jump_labels[i].offset = offset;170jump_labels[i].num_jump_labels = 0;171172add_label:173jump_labels[i].jump_labels[jump_labels[i].num_jump_labels++] = n;174assert(jump_labels[i].num_jump_labels < 256);175}176177static int178get_jump_table_entry(uint32_t offset)179{180int i;181182for (i = 0; i < num_jump_labels; i++)183if (jump_labels[i].offset == offset)184return i;185186return -1;187}188189static uint32_t label_offsets[0x512];190static int num_label_offsets;191192static int193label_idx(uint32_t offset, bool create)194{195int i;196for (i = 0; i < num_label_offsets; i++)197if (offset == label_offsets[i])198return i;199if (!create)200return -1;201label_offsets[i] = offset;202num_label_offsets = i + 1;203return i;204}205206static const char *207label_name(uint32_t offset, bool allow_jt)208{209static char name[12];210int lidx;211212if (allow_jt) {213lidx = get_jump_table_entry(offset);214if (lidx >= 0) {215int j;216for (j = 0; j < jump_labels[lidx].num_jump_labels; j++) {217uint32_t jump_label = jump_labels[lidx].jump_labels[j];218const char *str = getpm4(jump_label);219if (str)220return str;221}222// if we don't find anything w/ known name, maybe we should223// return UNKN%d to at least make it clear that this is some224// sort of jump-table entry?225}226}227228lidx = label_idx(offset, false);229if (lidx < 0)230return NULL;231sprintf(name, "l%03d", lidx);232return name;233}234235static uint32_t fxn_offsets[0x512];236static int num_fxn_offsets;237238static int239fxn_idx(uint32_t offset, bool create)240{241int i;242for (i = 0; i < num_fxn_offsets; i++)243if (offset == fxn_offsets[i])244return i;245if (!create)246return -1;247fxn_offsets[i] = offset;248num_fxn_offsets = i + 1;249return i;250}251252static const char *253fxn_name(uint32_t offset)254{255static char name[14];256int fidx = fxn_idx(offset, false);257if (fidx < 0)258return NULL;259sprintf(name, "fxn%02d", fidx);260return name;261}262263void264print_control_reg(uint32_t id)265{266char *name = afuc_control_reg_name(id);267if (name) {268printf("@%s", name);269free(name);270} else {271printf("0x%03x", id);272}273}274275void276print_pipe_reg(uint32_t id)277{278char *name = afuc_pipe_reg_name(id);279if (name) {280printf("|%s", name);281free(name);282} else {283printf("0x%03x", id);284}285}286287static void288disasm_instr(uint32_t *instrs, unsigned pc)289{290int jump_label_idx;291afuc_instr *instr = (void *)&instrs[pc];292const char *fname, *lname;293afuc_opc opc;294bool rep;295296afuc_get_opc(instr, &opc, &rep);297298lname = label_name(pc, false);299fname = fxn_name(pc);300jump_label_idx = get_jump_table_entry(pc);301302if (jump_label_idx >= 0) {303int j;304printf("\n");305for (j = 0; j < jump_labels[jump_label_idx].num_jump_labels; j++) {306uint32_t jump_label = jump_labels[jump_label_idx].jump_labels[j];307const char *name = getpm4(jump_label);308if (name) {309printlbl("%s", name);310} else {311printlbl("UNKN%d", jump_label);312}313printf(":\n");314}315}316317if (fname) {318printlbl("%s", fname);319printf(":\n");320}321322if (lname) {323printlbl(" %s", lname);324printf(":");325} else {326printf(" ");327}328329if (verbose) {330printf("\t%04x: %08x ", pc, instrs[pc]);331} else {332printf(" ");333}334335switch (opc) {336case OPC_NOP: {337/* a6xx changed the default immediate, and apparently 0338* is illegal now.339*/340const uint32_t nop = gpuver >= 6 ? 0x1000000 : 0x0;341if (instrs[pc] != nop) {342printerr("[%08x]", instrs[pc]);343printf(" ; ");344}345if (rep)346printf("(rep)");347printf("nop");348print_gpu_reg(instrs[pc]);349350break;351}352case OPC_ADD:353case OPC_ADDHI:354case OPC_SUB:355case OPC_SUBHI:356case OPC_AND:357case OPC_OR:358case OPC_XOR:359case OPC_NOT:360case OPC_SHL:361case OPC_USHR:362case OPC_ISHR:363case OPC_ROT:364case OPC_MUL8:365case OPC_MIN:366case OPC_MAX:367case OPC_CMP: {368bool src1 = true;369370if (opc == OPC_NOT)371src1 = false;372373if (rep)374printf("(rep)");375376print_alu_name(opc, instrs[pc]);377print_dst(instr->alui.dst);378printf(", ");379if (src1) {380print_src(instr->alui.src);381printf(", ");382}383printf("0x%04x", instr->alui.uimm);384print_gpu_reg(instr->alui.uimm);385386/* print out unexpected bits: */387if (verbose) {388if (instr->alui.src && !src1)389printerr(" (src=%02x)", instr->alui.src);390}391392break;393}394case OPC_MOVI: {395if (rep)396printf("(rep)");397printf("mov ");398print_dst(instr->movi.dst);399printf(", 0x%04x", instr->movi.uimm);400if (instr->movi.shift)401printf(" << %u", instr->movi.shift);402403if ((instr->movi.dst == REG_ADDR) && (instr->movi.shift >= 16)) {404uint32_t val = instr->movi.uimm << instr->movi.shift;405val &= ~0x40000; /* b18 seems to be a flag */406407if ((val & 0x00ffffff) == 0) {408printf("\t; ");409print_pipe_reg(val >> 24);410break;411}412}413/* using mov w/ << 16 is popular way to construct a pkt7414* header to send (for ex, from PFP to ME), so check that415* case first416*/417if ((instr->movi.shift == 16) &&418((instr->movi.uimm & 0xff00) == 0x7000)) {419unsigned opc, p;420421opc = instr->movi.uimm & 0x7f;422p = pm4_odd_parity_bit(opc);423424/* So, you'd think that checking the parity bit would be425* a good way to rule out false positives, but seems like426* ME doesn't really care.. at least it would filter out427* things that look like actual legit packets between428* PFP and ME..429*/430if (1 || p == ((instr->movi.uimm >> 7) & 0x1)) {431const char *name = getpm4(opc);432printf("\t; ");433if (name)434printlbl("%s", name);435else436printlbl("UNKN%u", opc);437break;438}439}440441print_gpu_reg(instr->movi.uimm << instr->movi.shift);442443break;444}445case OPC_ALU: {446bool src1 = true;447448if (instr->alu.alu == OPC_NOT || instr->alu.alu == OPC_MSB)449src1 = false;450451if (instr->alu.pad)452printf("[%08x] ; ", instrs[pc]);453454if (rep)455printf("(rep)");456if (instr->alu.xmov)457printf("(xmov%d)", instr->alu.xmov);458459/* special case mnemonics:460* reading $00 seems to always yield zero, and so:461* or $dst, $00, $src -> mov $dst, $src462* Maybe add one for negate too, ie.463* sub $dst, $00, $src ???464*/465if ((instr->alu.alu == OPC_OR) && !instr->alu.src1) {466printf("mov ");467src1 = false;468} else {469print_alu_name(instr->alu.alu, instrs[pc]);470}471472print_dst(instr->alu.dst);473if (src1) {474printf(", ");475print_src(instr->alu.src1);476}477printf(", ");478print_src(instr->alu.src2);479480/* print out unexpected bits: */481if (verbose) {482if (instr->alu.pad)483printerr(" (pad=%01x)", instr->alu.pad);484if (instr->alu.src1 && !src1)485printerr(" (src1=%02x)", instr->alu.src1);486}487488/* xmov is a modifier that makes the processor execute up to 3489* extra mov's after the current instruction. Given an ALU490* instruction:491*492* (xmovN) alu $dst, $src1, $src2493*494* In all of the uses in the firmware blob, $dst and $src2 are one495* of the "special" registers $data, $addr, $addr2. I've observed496* that if $dst isn't "special" then it's replaced with $00497* instead of $data, but I haven't checked what happens if $src2498* isn't "special". Anyway, in the usual case, the HW produces a499* count M = min(N, $rem) and then does the following:500*501* M = 1:502* mov $data, $src2503*504* M = 2:505* mov $data, $src2506* mov $data, $src2507*508* M = 3:509* mov $data, $src2510* mov $dst, $src2 (special case for CP_CONTEXT_REG_BUNCH)511* mov $data, $src2512*513* It seems to be frequently used in combination with (rep) to514* provide a kind of hardware-based loop unrolling, and there's515* even a special case in the ISA to be able to do this with516* CP_CONTEXT_REG_BUNCH. However (rep) isn't required.517*518* This dumps the expected extra instructions, assuming that $rem519* isn't too small.520*/521if (verbose && instr->alu.xmov) {522for (int i = 0; i < instr->alu.xmov; i++) {523printf("\n ; mov ");524if (instr->alu.dst < 0x1d)525printf("$00");526else if (instr->alu.xmov == 3 && i == 1)527print_dst(instr->alu.dst);528else529printf("$data");530printf(", ");531print_src(instr->alu.src2);532}533}534535break;536}537case OPC_CWRITE6:538case OPC_CREAD6:539case OPC_STORE6:540case OPC_LOAD6: {541if (rep)542printf("(rep)");543544bool is_control_reg = true;545bool is_store = true;546if (gpuver >= 6) {547switch (opc) {548case OPC_CWRITE6:549printf("cwrite ");550break;551case OPC_CREAD6:552is_store = false;553printf("cread ");554break;555case OPC_STORE6:556is_control_reg = false;557printf("store ");558break;559case OPC_LOAD6:560is_control_reg = false;561is_store = false;562printf("load ");563break;564default:565assert(!"unreachable");566}567} else {568switch (opc) {569case OPC_CWRITE5:570printf("cwrite ");571break;572case OPC_CREAD5:573is_store = false;574printf("cread ");575break;576default:577fprintf(stderr, "A6xx control opcode on A5xx?\n");578exit(1);579}580}581582if (is_store)583print_src(instr->control.src1);584else585print_dst(instr->control.src1);586printf(", [");587print_src(instr->control.src2);588printf(" + ");589if (is_control_reg && instr->control.flags != 0x4)590print_control_reg(instr->control.uimm);591else592printf("0x%03x", instr->control.uimm);593printf("], 0x%x", instr->control.flags);594break;595}596case OPC_BRNEI:597case OPC_BREQI:598case OPC_BRNEB:599case OPC_BREQB: {600unsigned off = pc + instr->br.ioff;601602assert(!rep);603604/* Since $00 reads back zero, it can be used as src for605* unconditional branches. (This only really makes sense606* for the BREQB.. or possible BRNEI if imm==0.)607*608* If bit=0 then branch is taken if *all* bits are zero.609* Otherwise it is taken if bit (bit-1) is clear.610*611* Note the instruction after a jump/branch is executed612* regardless of whether branch is taken, so use nop or613* take that into account in code.614*/615if (instr->br.src || (opc != OPC_BRNEB)) {616bool immed = false;617618if (opc == OPC_BRNEI) {619printf("brne ");620immed = true;621} else if (opc == OPC_BREQI) {622printf("breq ");623immed = true;624} else if (opc == OPC_BRNEB) {625printf("brne ");626} else if (opc == OPC_BREQB) {627printf("breq ");628}629print_src(instr->br.src);630if (immed) {631printf(", 0x%x,", instr->br.bit_or_imm);632} else {633printf(", b%u,", instr->br.bit_or_imm);634}635} else {636printf("jump");637if (verbose && instr->br.bit_or_imm) {638printerr(" (src=%03x, bit=%03x) ", instr->br.src,639instr->br.bit_or_imm);640}641}642643printf(" #");644printlbl("%s", label_name(off, true));645if (verbose)646printf(" (#%d, %04x)", instr->br.ioff, off);647break;648}649case OPC_CALL:650assert(!rep);651printf("call #");652printlbl("%s", fxn_name(instr->call.uoff));653if (verbose) {654printf(" (%04x)", instr->call.uoff);655if (instr->br.bit_or_imm || instr->br.src) {656printerr(" (src=%03x, bit=%03x) ", instr->br.src,657instr->br.bit_or_imm);658}659}660break;661case OPC_RET:662assert(!rep);663if (instr->ret.pad)664printf("[%08x] ; ", instrs[pc]);665if (instr->ret.interrupt)666printf("iret");667else668printf("ret");669break;670case OPC_WIN:671assert(!rep);672if (instr->waitin.pad)673printf("[%08x] ; ", instrs[pc]);674printf("waitin");675if (verbose && instr->waitin.pad)676printerr(" (pad=%x)", instr->waitin.pad);677break;678case OPC_PREEMPTLEAVE6:679if (gpuver < 6) {680printf("[%08x] ; op38", instrs[pc]);681} else {682printf("preemptleave #");683printlbl("%s", label_name(instr->call.uoff, true));684}685break;686case OPC_SETSECURE:687/* Note: This seems to implicitly read the secure/not-secure state688* to set from the low bit of $02, and implicitly jumps to pc + 3689* (i.e. skipping the next two instructions) if it succeeds. We690* print these implicit parameters to make reading the disassembly691* easier.692*/693if (instr->pad)694printf("[%08x] ; ", instrs[pc]);695printf("setsecure $02, #");696printlbl("%s", label_name(pc + 3, true));697break;698default:699printerr("[%08x]", instrs[pc]);700printf(" ; op%02x ", opc);701print_dst(instr->alui.dst);702printf(", ");703print_src(instr->alui.src);704print_gpu_reg(instrs[pc] & 0xffff);705break;706}707printf("\n");708}709710static void711setup_packet_table(uint32_t *jmptbl, uint32_t sizedwords)712{713num_jump_labels = 0;714715for (unsigned i = 0; i < sizedwords; i++) {716unsigned offset = jmptbl[i];717unsigned n = i; // + CP_NOP;718add_jump_table_entry(n, offset);719}720}721722static void723setup_labels(uint32_t *instrs, uint32_t sizedwords)724{725afuc_opc opc;726bool rep;727728num_label_offsets = 0;729730for (unsigned i = 0; i < sizedwords; i++) {731afuc_instr *instr = (void *)&instrs[i];732733afuc_get_opc(instr, &opc, &rep);734735switch (opc) {736case OPC_BRNEI:737case OPC_BREQI:738case OPC_BRNEB:739case OPC_BREQB:740label_idx(i + instr->br.ioff, true);741break;742case OPC_PREEMPTLEAVE6:743if (gpuver >= 6)744label_idx(instr->call.uoff, true);745break;746case OPC_CALL:747fxn_idx(instr->call.uoff, true);748break;749case OPC_SETSECURE:750/* this implicitly jumps to pc + 3 if successful */751label_idx(i + 3, true);752break;753default:754break;755}756}757}758759static void760disasm(struct emu *emu)761{762uint32_t sizedwords = emu->sizedwords;763uint32_t lpac_offset = 0;764765EMU_GPU_REG(CP_SQE_INSTR_BASE);766EMU_GPU_REG(CP_LPAC_SQE_INSTR_BASE);767768emu_init(emu);769770#ifdef BOOTSTRAP_DEBUG771while (true) {772disasm_instr(emu->instrs, emu->gpr_regs.pc);773emu_step(emu);774}775#endif776777emu_run_bootstrap(emu);778779/* Figure out if we have LPAC SQE appended: */780if (emu_get_reg64(emu, &CP_LPAC_SQE_INSTR_BASE)) {781lpac_offset = emu_get_reg64(emu, &CP_LPAC_SQE_INSTR_BASE) -782emu_get_reg64(emu, &CP_SQE_INSTR_BASE);783lpac_offset /= 4;784sizedwords = lpac_offset;785}786787setup_packet_table(emu->jmptbl, ARRAY_SIZE(emu->jmptbl));788setup_labels(emu->instrs, emu->sizedwords);789790/* TODO add option to emulate LPAC SQE instead: */791if (emulator) {792/* Start from clean slate: */793emu_fini(emu);794emu_init(emu);795796while (true) {797disasm_instr(emu->instrs, emu->gpr_regs.pc);798emu_step(emu);799}800}801802/* print instructions: */803for (int i = 0; i < sizedwords; i++) {804disasm_instr(emu->instrs, i);805}806807if (!lpac_offset)808return;809810printf(";\n");811printf("; LPAC microcode:\n");812printf(";\n");813814emu_fini(emu);815816emu->lpac = true;817emu->instrs += lpac_offset;818emu->sizedwords -= lpac_offset;819820emu_init(emu);821emu_run_bootstrap(emu);822823setup_packet_table(emu->jmptbl, ARRAY_SIZE(emu->jmptbl));824setup_labels(emu->instrs, emu->sizedwords);825826/* print instructions: */827for (int i = 0; i < emu->sizedwords; i++) {828disasm_instr(emu->instrs, i);829}830}831832833static void834disasm_legacy(uint32_t *buf, int sizedwords)835{836uint32_t *instrs = buf;837const int jmptbl_start = instrs[1] & 0xffff;838uint32_t *jmptbl = &buf[jmptbl_start];839int i;840841/* parse jumptable: */842setup_packet_table(jmptbl, 0x80);843844/* do a pre-pass to find instructions that are potential branch targets,845* and add labels for them:846*/847setup_labels(instrs, jmptbl_start);848849/* print instructions: */850for (i = 0; i < jmptbl_start; i++) {851disasm_instr(instrs, i);852}853854/* print jumptable: */855if (verbose) {856printf(";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n");857printf("; JUMP TABLE\n");858for (i = 0; i < 0x7f; i++) {859int n = i; // + CP_NOP;860uint32_t offset = jmptbl[i];861const char *name = getpm4(n);862printf("%3d %02x: ", n, n);863printf("%04x", offset);864if (name) {865printf(" ; %s", name);866} else {867printf(" ; UNKN%d", n);868}869printf("\n");870}871}872}873874static void875usage(void)876{877fprintf(stderr, "Usage:\n"878"\tdisasm [-g GPUVER] [-v] [-c] filename.asm\n"879"\t\t-g - specify GPU version (5, etc)\n"880"\t\t-c - use colors\n"881"\t\t-v - verbose output\n"882"\t\t-e - emulator mode\n");883exit(2);884}885886int887main(int argc, char **argv)888{889uint32_t *buf;890char *file;891bool colors = false;892uint32_t gpu_id = 0;893size_t sz;894int c, ret;895896/* Argument parsing: */897while ((c = getopt(argc, argv, "g:vce")) != -1) {898switch (c) {899case 'g':900gpu_id = atoi(optarg);901break;902case 'v':903verbose = true;904break;905case 'c':906colors = true;907break;908case 'e':909emulator = true;910verbose = true;911break;912default:913usage();914}915}916917if (optind >= argc) {918fprintf(stderr, "no file specified!\n");919usage();920}921922file = argv[optind];923924/* if gpu version not specified, infer from filename: */925if (!gpu_id) {926char *str = strstr(file, "a5");927if (!str)928str = strstr(file, "a6");929if (str)930gpu_id = atoi(str + 1);931}932933if (gpu_id < 500) {934printf("invalid gpu_id: %d\n", gpu_id);935return -1;936}937938gpuver = gpu_id / 100;939940/* a6xx is *mostly* a superset of a5xx, but some opcodes shuffle941* around, and behavior of special regs is a bit different. Right942* now we only bother to support the a6xx variant.943*/944if (emulator && (gpuver != 6)) {945fprintf(stderr, "Emulator only supported on a6xx!\n");946return 1;947}948949ret = afuc_util_init(gpuver, colors);950if (ret < 0) {951usage();952}953954printf("; a%dxx microcode\n", gpuver);955956buf = (uint32_t *)os_read_file(file, &sz);957958printf("; Disassembling microcode: %s\n", file);959printf("; Version: %08x\n\n", buf[1]);960961if (gpuver < 6) {962disasm_legacy(&buf[1], sz / 4 - 1);963} else {964struct emu emu = {965.instrs = &buf[1],966.sizedwords = sz / 4 - 1,967.gpu_id = gpu_id,968};969970disasm(&emu);971}972973return 0;974}975976977