mirror of
https://github.com/brazilofmux/tinymux
synced 2026-08-13 00:23:11 -04:00
Two ELF assumptions kept the harness from running on Darwin, both failing in the direction this harness exists to avoid -- quietly, and looking like a fact about the code under test rather than about the toolchain. The symbol probe used `nm -D --defined-only`, neither of which macOS nm accepts, and grepped for a bare `co_insert_at` where Mach-O exports `_co_insert_at`. With 2>/dev/null hiding the flag error, the pipeline yielded nothing, the probe concluded the library was stale, and the run exited 0 advising `make install` -- which had already been run. `nm -g` plus an optional-underscore pattern is the portable spelling. The bisect hooks were declared weak and left undefined. An undefined weak symbol resolves to null on ELF; the Mach-O linker rejects it, so a normal build failed to link here. Empty weak *definitions* link everywhere and are still overridden by the strong ones in repro/2019-chain-bisect.patch. Their null tests go with them -- an always-defined weak symbol makes the test vacuously true. Verified on macOS arm64: 1221 transcript lines, interp vs host OK, dbt vs host OK. Negative control (off-by-one in the wc_next max_words cap) exits 1 with exactly 4 differing lines, all cap/ cases. The qemu oracle still skips here and says so -- Homebrew ships no user-mode riscv64 emulator on macOS -- so this box gets three of the four routes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
126 lines
4.5 KiB
C++
126 lines
4.5 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. Weak *definitions*, not weak
|
|
* declarations: an undefined weak symbol resolves to null only on ELF, while
|
|
* the Mach-O linker rejects it outright, so the declaration form makes a
|
|
* normal build fail to link on macOS. An empty weak definition links
|
|
* everywhere and is overridden by the patch's strong one when applied. */
|
|
extern "C" __attribute__((weak)) void dbt_dump_chained(void) { }
|
|
extern "C" __attribute__((weak)) void dbt_dump_heads(void) { }
|
|
|
|
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);
|
|
/* No null test: the weak definitions above make these no-ops in a
|
|
* normal build, and a weak symbol that is always defined would make
|
|
* the test vacuously true anyway. */
|
|
dbt_dump_chained();
|
|
dbt_dump_heads();
|
|
dbt_cleanup(&dbt);
|
|
rv64_free_binary(&bin);
|
|
}
|
|
return 0;
|
|
}
|