mirror of
https://github.com/brazilofmux/tinymux
synced 2026-08-13 00:23:11 -04:00
Knowing chaining is the mechanism does not say WHICH chained edge is wrong, and with 70 of them in the reproducer that is the difference between a lead and a location. 2019-chain-bisect.patch adds two scratch knobs to dbt.cpp -- list the chain targets, and suppress named ones -- and bisect.sh binary-searches for the smallest set whose suppression makes the failure go away. It is a debugging patch, not a proposed change: nothing here is meant to be merged into the engine. It converges on one PC out of 70. Skipping that edge alone: 0/40 wrong. Skipping the TAKEN side of the same branch: 19/40. Skipping wc_next's own entry: 28/40. Skipping an arbitrary other edge: 28/40. So the fault is one specific edge rather than chaining being generally fragile here. That edge is the fall-through of a shrink-wrapped early-out: gcc sank wc_next's prologue below the `finished` test, which makes the fall-through target both a mid-function entry point and a PC that is not a branch target in the guest at all -- control simply continues into it. Its sibling, a real branch target, chains correctly. The sampling matters and is documented in the script: at ~50% failure, "0 wrong" over 30 runs is a false clean with probability about 1e-9, and lowering RUNS quietly turns the bisection into a coin flip. runner.cpp gains the two hooks the patch defines. They are declared __attribute__((weak)) and null-checked, because a normal build links the unpatched dbt.cpp and defines neither -- declaring them plainly breaks `make test-codiff` at link time, which is how the first version of this commit was wrong. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
121 lines
4.2 KiB
C++
121 lines
4.2 KiB
C++
/* runner.cpp -- run one RV64 ELF through the interpreter and the DBT with
|
|
* full visibility into the DBT's counters and (optionally) its trace.
|
|
*
|
|
* tests/dbt/dbt_test can already run an ELF both ways, but it prints only
|
|
* blocks/hits/misses and offers no way to turn on DBT_TRACE_*. This is the
|
|
* same two routes with the internals exposed, so that a divergence can be
|
|
* localised to a translation event rather than merely observed.
|
|
*
|
|
* Env:
|
|
* RUN_TRACE=1 DBT_TRACE_TRANSLATE
|
|
* RUN_TRACE=3 + DBT_TRACE_EXEC (very loud)
|
|
*/
|
|
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include "dbt.h"
|
|
#include "dbt_interp.h"
|
|
#include "dbt_elf64.h"
|
|
|
|
static int io_ecall(rv64_state_t *state, void *user)
|
|
{
|
|
rv64_memory_t *mem = static_cast<rv64_memory_t *>(user);
|
|
switch (state->x[17]) {
|
|
case 93: return static_cast<int>(state->x[10]);
|
|
case 64: {
|
|
uint64_t buf = state->x[11], len = state->x[12];
|
|
if (buf + len > mem->size) { state->x[10] = (uint64_t)-1LL; return -1; }
|
|
fwrite(mem->data + buf, 1, len, stdout);
|
|
state->x[10] = len;
|
|
return -1;
|
|
}
|
|
default: return -1;
|
|
}
|
|
}
|
|
|
|
/* Hooks supplied only by repro/2019-chain-bisect.patch, a scratch patch to
|
|
* dbt.cpp used to bisect chained edges. Declared weak so a normal build --
|
|
* which links the unpatched dbt.cpp and therefore defines neither -- still
|
|
* links; they resolve to null and are skipped. */
|
|
extern "C" void dbt_dump_chained(void) __attribute__((weak));
|
|
extern "C" void dbt_dump_heads(void) __attribute__((weak));
|
|
|
|
struct dctx { uint8_t *memory; size_t size; };
|
|
|
|
static int dbt_io_ecall(rv64_ctx_t *ctx, void *user)
|
|
{
|
|
dctx *d = static_cast<dctx *>(user);
|
|
switch (ctx->x[17]) {
|
|
case 93: return static_cast<int>(ctx->x[10]);
|
|
case 64: {
|
|
uint64_t buf = ctx->x[11], len = ctx->x[12];
|
|
if (buf + len > d->size) { ctx->x[10] = (uint64_t)-1LL; return -1; }
|
|
fwrite(d->memory + buf, 1, len, stdout);
|
|
ctx->x[10] = len;
|
|
return -1;
|
|
}
|
|
default: return -1;
|
|
}
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
if (argc < 2) { fprintf(stderr, "usage: runner <elf>\n"); return 2; }
|
|
const char *tr = getenv("RUN_TRACE");
|
|
int trace = tr ? atoi(tr) : 0;
|
|
|
|
/* ---- interpreter ---- */
|
|
{
|
|
rv64_binary_t bin;
|
|
if (rv64_load_elf(argv[1], &bin) != 0) return 2;
|
|
rv64_state_t st = {};
|
|
st.pc = bin.entry_point;
|
|
st.x[2] = bin.stack_top;
|
|
rv64_memory_t mem = { bin.memory, bin.memory_size };
|
|
printf("--- interp ---\n");
|
|
fflush(stdout);
|
|
rv64_interp_run(&st, &mem, io_ecall, &mem);
|
|
fflush(stdout);
|
|
rv64_free_binary(&bin);
|
|
}
|
|
|
|
/* ---- DBT ----
|
|
* RUN_REPS>1 repeats the whole init/run/cleanup cycle in one process,
|
|
* to test whether the divergence needs process-global DBT state rather
|
|
* than anything in this ELF. */
|
|
const char *rp = getenv("RUN_REPS");
|
|
int reps = rp ? atoi(rp) : 1;
|
|
for (int rep = 0; rep < reps; rep++) {
|
|
rv64_binary_t bin;
|
|
if (rv64_load_elf(argv[1], &bin) != 0) return 2;
|
|
dctx d = { bin.memory, bin.memory_size };
|
|
dbt_state_t dbt;
|
|
if (dbt_init(&dbt, bin.memory, bin.memory_size, dbt_io_ecall, &d) != 0) {
|
|
fprintf(stderr, "dbt_init failed\n");
|
|
return 2;
|
|
}
|
|
dbt.trace = trace;
|
|
printf("--- dbt rep=%d ---\n", rep);
|
|
fflush(stdout);
|
|
int rc = dbt_run(&dbt, bin.entry_point, bin.stack_top);
|
|
fflush(stdout);
|
|
printf("rc=%d blocks=%llu hits=%llu misses=%llu chain_hits=%llu "
|
|
"chain_misses=%llu code_full=%llu reclaims=%u code_used=%u "
|
|
"blob_end=%u\n",
|
|
rc,
|
|
(unsigned long long)dbt.blocks_translated,
|
|
(unsigned long long)dbt.cache_hits,
|
|
(unsigned long long)dbt.cache_misses,
|
|
(unsigned long long)dbt.chain_hits,
|
|
(unsigned long long)dbt.chain_misses,
|
|
(unsigned long long)dbt.code_full,
|
|
dbt.reclaims_this_run,
|
|
dbt.code_used, dbt.blob_code_end);
|
|
if (dbt_dump_chained) dbt_dump_chained();
|
|
if (dbt_dump_heads) dbt_dump_heads();
|
|
dbt_cleanup(&dbt);
|
|
rv64_free_binary(&bin);
|
|
}
|
|
return 0;
|
|
}
|