/* codetrace.h - generic (OS-agnostic) register def-use over one function. * * Handler layer, gated like semsig.h: a process-scoped, STATIC register def-use * analysis built on insn_decode (insndec.h) and the basic-block partition of * cfg_blocks (codeanalysis.h, REUSED - this header writes no second splitter). * For one function view it computes per-instruction register read (use) / write * (def) sets - the building material a caller needs for reaching-definitions. * * It returns DATA. It does NOT execute or model values, does NOT unroll loops, * does NOT build use-def CHAINS, and carries no dataflow solver: those are the * caller's analysis. v1 deliberately stops at per-instruction use/def so the * surface does not predetermine the caller's algorithm. * * Family placement. codeanalysis.h is PURE / always available; semsig.h is gated * on the disassembler backend. codetrace.h sits in the second row with semsig: it * needs the backend decode (via insndec.h) plus cfg_blocks. With no backend the * symbol still exists (ABI stable) but returns the sentinel, and VMIE_HAVE_DISASM * is 0. The chosen split keeps codeanalysis.h's "zero-dep, works always" contract * literally true. * * GENERIC / OS-agnostic. The analysis names no OS. A calling convention enters * ONLY through the explicit trace_abi argument, which selects a static caller- * saved (volatile) clobber table applied at call sites. Win64 is just one enum * value, not a hardcoded dependency; TRACE_ABI_NONE reproduces conservative * behavior for any non-Windows consumer. The OS-specific CHOICE of Win64 is made * by the win32 wrapper (vmie_win32_func_usedef, win32.h), not by this header. */ #ifndef VMIE_CODETRACE_H #define VMIE_CODETRACE_H #include #include #include "memmodel.h" /* mem_view_t (the single owner of the view type) */ /* Shared feature gate (see semsig.h / insndec.h). Default keeps the header valid * when compiled outside the project build. */ #ifndef VMIE_HAVE_DISASM #define VMIE_HAVE_DISASM 0 #endif /* Calling convention applied to call sites when computing def-use. The analysis * itself stays OS-agnostic: an ABI is just a parameter selecting a static * caller-saved (volatile) clobber table. An unknown value is treated as * TRACE_ABI_NONE (conservative), not an error. */ typedef enum { TRACE_ABI_NONE = 0, /* conservative: a call clobbers only what insn_decode * reports architecturally (rsp/rip write, indirect * target read); no volatile registers are killed. */ TRACE_ABI_WIN64 /* standard Win64: volatile GPR/vec/FLAGS are killed * (full-kill) across every call site (see codetrace.c * for the exact set and its honest limits). */ } trace_abi; /* A compact register set over canonical reg_id roots (a bitset). The field split * is an implementation detail of the struct; consumers should treat it opaquely * and compare/inspect via the documented def/use semantics, not the raw words. * gpr - the 16 GPR roots (REGID_RAX..REGID_R15) * vec_lo - vector roots REGID_VEC0..REGID_VEC31 * misc - mask roots (k0..k7), REGID_FLAGS, REGID_OTHER */ typedef struct { uint16_t gpr; /* bit i set => REGID_RAX + i present (16 GPR roots) */ uint32_t vec_lo; /* bit i set => REGID_VEC0 + i present (32 vector roots) */ uint16_t misc; /* bit i set => (REGID_K0 + i) for i<8, then FLAGS, OTHER */ } regset; /* Per-instruction use/def in the function view's coordinate space. * off - byte offset of the instruction within fn * len - instruction length * use - roots READ (incl. implicit, incl. address regs of mem operands) * def - roots WRITTEN as a FULL/zero-extending write (these KILL the * enclosing root); for a call site under a non-NONE trace_abi the * ABI volatile clobber is merged here as a full kill too * def_partial - roots written PARTIALLY (8/16-bit GPR, legacy-SSE): they do NOT * kill the enclosing root, so a wider live root survives * cond_def - roots written CONDITIONALLY (cmovcc/setcc): do not kill either */ typedef struct { uint32_t off; uint8_t len; regset use, def, def_partial, cond_def; } insn_usedef; /* Compute per-instruction use/def for one function view under calling convention * `abi`. `fn` is a view spanning EXACTLY one function (as for cfg_blocks / * semsig_hash). REUSES cfg_blocks only to validate the partition and iterate in * block order; the use/def itself comes from insn_decode per instruction. * * `abi` selects the call-site volatile clobber: TRACE_ABI_NONE = conservative (no * volatile kill, only the architectural rsp/rip write insn_decode reports); * TRACE_ABI_WIN64 = full-kill of the Win64 volatile roots at every call site. * The analysis stays OS-agnostic: `abi` is just a parameter, not a hardcoded OS. * * Writes up to `max` records in ascending off order (out=NULL to count). Returns * the TOTAL instruction count, 0 if `fn` is empty, or -1 on a decode desync * (cfg_blocks could not split, or the backend could not decode an instruction) / * a build with no backend (VMIE_HAVE_DISASM==0). The sentinel convention mirrors * cfg_blocks: -1 = desync/no-backend, 0 = empty. * * PURE: only the view and the decoder, no vmie_mem / no I/O (a stack buffer with * a heap spill only when the block count overflows, like semsig_hash). * * Example - registers killed by each instruction of a function: * insn_usedef ud[512]; * int n = func_usedef(fn, TRACE_ABI_WIN64, ud, 512); * for (int i = 0; i < n && i < 512; i++) ... inspect ud[i].def ... */ int func_usedef(mem_view_t fn, trace_abi abi, insn_usedef* out, int max); #endif /* VMIE_CODETRACE_H */