tcg/wasm32: remove compiled-store investigation probes
This commit is contained in:
@@ -41,9 +41,6 @@
|
||||
#include "tb-context.h"
|
||||
#include "internal-common.h"
|
||||
#include "internal-target.h"
|
||||
#if defined(EMSCRIPTEN) && !defined(CONFIG_TCG_INTERPRETER)
|
||||
#include "../../tcg/wasm32.h"
|
||||
#endif
|
||||
|
||||
/* -icount align implementation. */
|
||||
|
||||
@@ -176,14 +173,6 @@ uint32_t curr_cflags(CPUState *cpu)
|
||||
cflags |= CF_NO_GOTO_TB;
|
||||
}
|
||||
|
||||
#if defined(EMSCRIPTEN) && !defined(CONFIG_TCG_INTERPRETER)
|
||||
/* E10 needs one TB per dispatch so wasm_cur_tb_pc stays exact; E11 keeps
|
||||
* chaining live to avoid watchdog-only slowdowns before the armed window. */
|
||||
if (wasm_diff_needs_nochain()) {
|
||||
cflags |= CF_NO_GOTO_TB | CF_NO_GOTO_PTR;
|
||||
}
|
||||
#endif
|
||||
|
||||
return cflags;
|
||||
}
|
||||
|
||||
@@ -428,11 +417,6 @@ const void *HELPER(lookup_tb_ptr)(CPUArchState *env)
|
||||
cpu_get_tb_cpu_state(env, &pc, &cs_base, &flags);
|
||||
|
||||
cflags = curr_cflags(cpu);
|
||||
#if defined(EMSCRIPTEN) && !defined(CONFIG_TCG_INTERPRETER)
|
||||
if (wasm_diff_needs_nochain_at(pc)) {
|
||||
cflags |= CF_NO_GOTO_TB | CF_NO_GOTO_PTR;
|
||||
}
|
||||
#endif
|
||||
if (check_for_breakpoints(cpu, pc, &cflags)) {
|
||||
cpu_loop_exit(cpu);
|
||||
}
|
||||
@@ -471,9 +455,6 @@ cpu_tb_exec(CPUState *cpu, TranslationBlock *itb, int *tb_exit)
|
||||
}
|
||||
|
||||
qemu_thread_jit_execute();
|
||||
#if defined(EMSCRIPTEN) && !defined(CONFIG_TCG_INTERPRETER)
|
||||
wasm_cur_tb_pc = itb->pc;
|
||||
#endif
|
||||
ret = tcg_qemu_tb_exec(cpu_env(cpu), tb_ptr);
|
||||
cpu->neg.can_do_io = true;
|
||||
qemu_plugin_disable_mem_helpers(cpu);
|
||||
@@ -995,11 +976,6 @@ cpu_exec_loop(CPUState *cpu, SyncClocks *sc)
|
||||
cflags = cpu->cflags_next_tb;
|
||||
if (cflags == -1) {
|
||||
cflags = curr_cflags(cpu);
|
||||
#if defined(EMSCRIPTEN) && !defined(CONFIG_TCG_INTERPRETER)
|
||||
if (wasm_diff_needs_nochain_at(pc)) {
|
||||
cflags |= CF_NO_GOTO_TB | CF_NO_GOTO_PTR;
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
cpu->cflags_next_tb = -1;
|
||||
}
|
||||
|
||||
@@ -73,28 +73,6 @@
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#if defined(EMSCRIPTEN) && !defined(CONFIG_TCG_INTERPRETER)
|
||||
extern int wasm_ramrace_trace;
|
||||
static uint32_t wasm_ramrace_tlb_logs;
|
||||
|
||||
static void wasm_ramrace_log_tlb(const char *kind, CPUState *cpu, vaddr addr)
|
||||
{
|
||||
if (!wasm_ramrace_trace || current_cpu) {
|
||||
return;
|
||||
}
|
||||
|
||||
wasm_ramrace_tlb_logs++;
|
||||
if (wasm_ramrace_tlb_logs <= 200 || (wasm_ramrace_tlb_logs % 10000) == 0) {
|
||||
printf("RAMRACE tlb-%s off-vcpu #%u tid=%lu cpu=%p addr=0x%llx\n",
|
||||
kind, wasm_ramrace_tlb_logs, (unsigned long)pthread_self(),
|
||||
cpu, (unsigned long long)addr);
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
#else
|
||||
#define wasm_ramrace_log_tlb(kind, cpu, addr) ((void)0)
|
||||
#endif
|
||||
|
||||
#define assert_cpu_is_self(cpu) do { \
|
||||
if (DEBUG_TLB_GATE) { \
|
||||
g_assert(!(cpu)->created || qemu_cpu_is_self(cpu)); \
|
||||
@@ -439,7 +417,6 @@ static void tlb_flush_by_mmuidx_async_work(CPUState *cpu, run_on_cpu_data data)
|
||||
void tlb_flush_by_mmuidx(CPUState *cpu, uint16_t idxmap)
|
||||
{
|
||||
tlb_debug("mmu_idx: 0x%" PRIx16 "\n", idxmap);
|
||||
wasm_ramrace_log_tlb("full", cpu, 0);
|
||||
|
||||
assert_cpu_is_self(cpu);
|
||||
|
||||
@@ -456,7 +433,6 @@ void tlb_flush_by_mmuidx_all_cpus_synced(CPUState *src_cpu, uint16_t idxmap)
|
||||
const run_on_cpu_func fn = tlb_flush_by_mmuidx_async_work;
|
||||
|
||||
tlb_debug("mmu_idx: 0x%"PRIx16"\n", idxmap);
|
||||
wasm_ramrace_log_tlb("full-all", src_cpu, 0);
|
||||
|
||||
flush_all_helper(src_cpu, fn, RUN_ON_CPU_HOST_INT(idxmap));
|
||||
async_safe_run_on_cpu(src_cpu, fn, RUN_ON_CPU_HOST_INT(idxmap));
|
||||
@@ -632,7 +608,6 @@ static void tlb_flush_page_by_mmuidx_async_2(CPUState *cpu,
|
||||
void tlb_flush_page_by_mmuidx(CPUState *cpu, vaddr addr, uint16_t idxmap)
|
||||
{
|
||||
tlb_debug("addr: %016" VADDR_PRIx " mmu_idx:%" PRIx16 "\n", addr, idxmap);
|
||||
wasm_ramrace_log_tlb("page", cpu, addr);
|
||||
|
||||
assert_cpu_is_self(cpu);
|
||||
|
||||
@@ -652,7 +627,6 @@ void tlb_flush_page_by_mmuidx_all_cpus_synced(CPUState *src_cpu,
|
||||
uint16_t idxmap)
|
||||
{
|
||||
tlb_debug("addr: %016" VADDR_PRIx " mmu_idx:%"PRIx16"\n", addr, idxmap);
|
||||
wasm_ramrace_log_tlb("page-all", src_cpu, addr);
|
||||
|
||||
/* This should already be page aligned */
|
||||
addr &= TARGET_PAGE_MASK;
|
||||
@@ -803,7 +777,6 @@ void tlb_flush_range_by_mmuidx(CPUState *cpu, vaddr addr,
|
||||
{
|
||||
TLBFlushRangeData d;
|
||||
|
||||
wasm_ramrace_log_tlb("range", cpu, addr);
|
||||
assert_cpu_is_self(cpu);
|
||||
|
||||
/*
|
||||
@@ -844,8 +817,6 @@ void tlb_flush_range_by_mmuidx_all_cpus_synced(CPUState *src_cpu,
|
||||
TLBFlushRangeData d, *p;
|
||||
CPUState *dst_cpu;
|
||||
|
||||
wasm_ramrace_log_tlb("range-all", src_cpu, addr);
|
||||
|
||||
/*
|
||||
* If all bits are significant, and len is small,
|
||||
* this devolves to tlb_flush_page.
|
||||
@@ -986,27 +957,9 @@ void tlb_reset_dirty(CPUState *cpu, ram_addr_t start1, ram_addr_t length)
|
||||
static inline void tlb_set_dirty1_locked(CPUTLBEntry *tlb_entry,
|
||||
vaddr addr)
|
||||
{
|
||||
#if defined(EMSCRIPTEN) && !defined(CONFIG_TCG_INTERPRETER)
|
||||
if (wasm_ramrace_trace && (addr & TARGET_PAGE_MASK) == 0x3fcb8000u) {
|
||||
printf("FAULTTRACE tlb_set_dirty1 entry=%p before_read=0x%llx before_write=0x%llx before_addend=0x%llx addr=0x%llx\n",
|
||||
tlb_entry, (unsigned long long)tlb_entry->addr_read,
|
||||
(unsigned long long)tlb_entry->addr_write,
|
||||
(unsigned long long)tlb_entry->addend, (unsigned long long)addr);
|
||||
fflush(stdout);
|
||||
}
|
||||
#endif
|
||||
if (tlb_entry->addr_write == (addr | TLB_NOTDIRTY)) {
|
||||
tlb_entry->addr_write = addr;
|
||||
}
|
||||
#if defined(EMSCRIPTEN) && !defined(CONFIG_TCG_INTERPRETER)
|
||||
if (wasm_ramrace_trace && (addr & TARGET_PAGE_MASK) == 0x3fcb8000u) {
|
||||
printf("FAULTTRACE tlb_set_dirty1 after entry=%p read=0x%llx write=0x%llx addend=0x%llx\n",
|
||||
tlb_entry, (unsigned long long)tlb_entry->addr_read,
|
||||
(unsigned long long)tlb_entry->addr_write,
|
||||
(unsigned long long)tlb_entry->addend);
|
||||
fflush(stdout);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/* update the TLB corresponding to virtual page vaddr
|
||||
@@ -1125,16 +1078,6 @@ void tlb_set_page_full(CPUState *cpu, int mmu_idx,
|
||||
|
||||
is_ram = memory_region_is_ram(section->mr);
|
||||
is_romd = memory_region_is_romd(section->mr);
|
||||
#if defined(EMSCRIPTEN) && !defined(CONFIG_TCG_INTERPRETER)
|
||||
if (wasm_ramrace_trace && paddr_page == 0x3fcb8000u) {
|
||||
printf("FAULTTRACE tlb_fill vaddr=0x%llx paddr=0x%llx asidx=%d prot=0x%x mr=%p name=%s ram=%d romd=%d readonly=%d xlat=0x%llx sz=0x%llx\n",
|
||||
(unsigned long long)addr_page, (unsigned long long)paddr_page,
|
||||
asidx, prot, section->mr, memory_region_name(section->mr) ?: "(null)",
|
||||
is_ram, is_romd, section->readonly,
|
||||
(unsigned long long)xlat, (unsigned long long)sz);
|
||||
fflush(stdout);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (is_ram || is_romd) {
|
||||
/* RAM and ROMD both have associated host memory. */
|
||||
@@ -1227,15 +1170,6 @@ void tlb_set_page_full(CPUState *cpu, int mmu_idx,
|
||||
full = &desc->fulltlb[index];
|
||||
full->xlat_section = iotlb - addr_page;
|
||||
full->phys_addr = paddr_page;
|
||||
#if defined(EMSCRIPTEN) && !defined(CONFIG_TCG_INTERPRETER)
|
||||
if (wasm_ramrace_trace && paddr_page == 0x3fcb8000u) {
|
||||
printf("FAULTTRACE tlb_store index=%u iotlb=0x%llx addr_page=0x%llx stored_xlat=0x%llx phys=0x%llx\n",
|
||||
index, (unsigned long long)iotlb, (unsigned long long)addr_page,
|
||||
(unsigned long long)full->xlat_section,
|
||||
(unsigned long long)full->phys_addr);
|
||||
fflush(stdout);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Now calculate the new entry */
|
||||
tn.addend = addend - addr_page;
|
||||
@@ -1455,15 +1389,6 @@ static int probe_access_internal(CPUState *cpu, vaddr addr,
|
||||
|
||||
*pfull = full = &cpu->neg.tlb.d[mmu_idx].fulltlb[index];
|
||||
flags |= full->slow_flags[access_type];
|
||||
#if defined(EMSCRIPTEN) && !defined(CONFIG_TCG_INTERPRETER)
|
||||
if (wasm_ramrace_trace && ((addr & TARGET_PAGE_MASK) == 0x3fcb8000u)) {
|
||||
printf("FAULTTRACE probe addr=0x%llx index=%lu access=%d tlb_addr=0x%llx flags=0x%x slow=0x%x addend=0x%llx full_xlat=0x%llx\n",
|
||||
(unsigned long long)addr, (unsigned long)index, access_type,
|
||||
(unsigned long long)tlb_addr, flags, full->slow_flags[access_type],
|
||||
(unsigned long long)entry->addend, (unsigned long long)full->xlat_section);
|
||||
fflush(stdout);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Fold all "mmio-like" bits into TLB_MMIO. This is not RAM. */
|
||||
if (unlikely(flags & ~(TLB_WATCHPOINT | TLB_NOTDIRTY | TLB_CHECK_ALIGNED))
|
||||
@@ -1754,16 +1679,6 @@ static bool mmu_lookup1(CPUState *cpu, MMULookupPageData *data, MemOp memop,
|
||||
|
||||
data->full = full;
|
||||
data->flags = flags;
|
||||
#if defined(EMSCRIPTEN) && !defined(CONFIG_TCG_INTERPRETER)
|
||||
if (wasm_ramrace_trace && ((addr & TARGET_PAGE_MASK) == 0x3fcb8000u)) {
|
||||
printf("FAULTTRACE mmu_lookup1 addr=0x%llx mmu=%d index=%lu maybe_resized=%d tlb_addr=0x%llx flags=0x%x entry=%p full=%p full_xlat=0x%llx addend=0x%llx\n",
|
||||
(unsigned long long)addr, mmu_idx, (unsigned long)index, maybe_resized,
|
||||
(unsigned long long)tlb_addr, flags, entry, full,
|
||||
(unsigned long long)full->xlat_section,
|
||||
(unsigned long long)entry->addend);
|
||||
fflush(stdout);
|
||||
}
|
||||
#endif
|
||||
/* Compute haddr speculatively; depending on flags it might be invalid. */
|
||||
data->haddr = (void *)((uintptr_t)addr + entry->addend);
|
||||
|
||||
@@ -2063,15 +1978,6 @@ static uint64_t do_ld_mmio_beN(CPUState *cpu, CPUTLBEntryFull *full,
|
||||
attrs = full->attrs;
|
||||
section = io_prepare(&mr_offset, cpu, full->xlat_section, attrs, addr, ra);
|
||||
mr = section->mr;
|
||||
#if defined(EMSCRIPTEN) && !defined(CONFIG_TCG_INTERPRETER)
|
||||
if (wasm_ramrace_trace && ((addr & TARGET_PAGE_MASK) == 0x3fcb8000u)) {
|
||||
printf("FAULTTRACE mmio_read addr=0x%llx xlat_section=0x%llx mr_offset=0x%llx mr=%p name=%s ram=%d romd=%d readonly=%d\n",
|
||||
(unsigned long long)addr, (unsigned long long)full->xlat_section,
|
||||
(unsigned long long)mr_offset, mr, memory_region_name(mr) ?: "(null)",
|
||||
memory_region_is_ram(mr), memory_region_is_romd(mr), section->readonly);
|
||||
fflush(stdout);
|
||||
}
|
||||
#endif
|
||||
|
||||
BQL_LOCK_GUARD();
|
||||
return int_ld_mmio_beN(cpu, full, ret_be, addr, size, mmu_idx,
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
#include "sysemu/tcg.h"
|
||||
#include "qemu/accel.h"
|
||||
#include "hw/boards.h"
|
||||
#include "hw/core/cpu.h"
|
||||
#include "migration/vmstate.h"
|
||||
#include "exec/address-spaces.h"
|
||||
|
||||
@@ -1405,10 +1404,6 @@ static const MemoryRegionOps ram_device_mem_ops = {
|
||||
},
|
||||
};
|
||||
|
||||
#if defined(EMSCRIPTEN) && !defined(CONFIG_TCG_INTERPRETER)
|
||||
extern int wasm_ramrace_trace;
|
||||
#endif
|
||||
|
||||
bool memory_region_access_valid(MemoryRegion *mr,
|
||||
hwaddr addr,
|
||||
unsigned size,
|
||||
@@ -1421,16 +1416,6 @@ bool memory_region_access_valid(MemoryRegion *mr,
|
||||
", size %u, region '%s', reason: rejected\n",
|
||||
is_write ? "write" : "read",
|
||||
addr, size, memory_region_name(mr));
|
||||
#if defined(EMSCRIPTEN) && !defined(CONFIG_TCG_INTERPRETER)
|
||||
if (wasm_ramrace_trace) {
|
||||
printf("FAULTTRACE rejected %s addr=0x%llx size=%u mr=%p name=%s ops=%p opaque=%p ram=%d rom=%d readonly=%d current_cpu=%p\n",
|
||||
is_write ? "write" : "read", (unsigned long long)addr,
|
||||
size, mr, memory_region_name(mr) ?: "(null)", mr->ops,
|
||||
mr->opaque, memory_region_is_ram(mr), memory_region_is_rom(mr),
|
||||
mr->readonly, current_cpu);
|
||||
fflush(stdout);
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -78,10 +78,6 @@
|
||||
|
||||
#include "monitor/monitor.h"
|
||||
|
||||
#ifdef EMSCRIPTEN
|
||||
#include <emscripten/emscripten.h>
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_LIBDAXCTL
|
||||
#include <daxctl/libdaxctl.h>
|
||||
#endif
|
||||
@@ -93,47 +89,6 @@
|
||||
*/
|
||||
RAMList ram_list = { .blocks = QLIST_HEAD_INITIALIZER(ram_list.blocks) };
|
||||
|
||||
#if defined(EMSCRIPTEN) && !defined(CONFIG_TCG_INTERPRETER)
|
||||
int wasm_ramrace_trace;
|
||||
static uint32_t wasm_ramrace_read_logs;
|
||||
static uint32_t wasm_ramrace_cached_read_logs;
|
||||
static uint32_t wasm_ramrace_write_logs;
|
||||
static uint32_t wasm_ramrace_cached_write_logs;
|
||||
static uint32_t wasm_ramrace_map_logs;
|
||||
static uint32_t wasm_ramrace_unmap_logs;
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE void wasm_enable_ramrace_trace(void)
|
||||
{
|
||||
wasm_ramrace_trace = 1;
|
||||
}
|
||||
|
||||
static bool wasm_ramrace_overlaps_dram(hwaddr addr, hwaddr len)
|
||||
{
|
||||
const hwaddr dram_lo = 0x3fc80000u;
|
||||
const hwaddr dram_hi = 0x3fce0000u;
|
||||
|
||||
return len > 0 && addr < dram_hi && addr + len > dram_lo;
|
||||
}
|
||||
|
||||
static void wasm_ramrace_log_access(const char *kind, hwaddr addr, hwaddr len,
|
||||
uint32_t *counter)
|
||||
{
|
||||
if (!wasm_ramrace_trace || current_cpu || !wasm_ramrace_overlaps_dram(addr, len)) {
|
||||
return;
|
||||
}
|
||||
|
||||
(*counter)++;
|
||||
if (*counter <= 200 || (*counter % 10000) == 0) {
|
||||
printf("RAMRACE %s off-vcpu #%u tid=%lu addr=0x%llx len=0x%llx\n",
|
||||
kind, *counter, (unsigned long)pthread_self(),
|
||||
(unsigned long long)addr, (unsigned long long)len);
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
#else
|
||||
#define wasm_ramrace_log_access(kind, addr, len, counter) ((void)0)
|
||||
#endif
|
||||
|
||||
static MemoryRegion *system_memory;
|
||||
static MemoryRegion *system_io;
|
||||
|
||||
@@ -2979,15 +2934,6 @@ static MemTxResult flatview_read(FlatView *fv, hwaddr addr,
|
||||
|
||||
l = len;
|
||||
mr = flatview_translate(fv, addr, &mr_addr, &l, false, attrs);
|
||||
#if defined(EMSCRIPTEN) && !defined(CONFIG_TCG_INTERPRETER)
|
||||
if (wasm_ramrace_trace && current_cpu && mr->ops == &unassigned_mem_ops) {
|
||||
printf("FAULTTRACE flatview_read addr=0x%llx len=0x%llx mr_addr=0x%llx translated_len=0x%llx fv=%p root=%p root_name=%s nr=%u\n",
|
||||
(unsigned long long)addr, (unsigned long long)len,
|
||||
(unsigned long long)mr_addr, (unsigned long long)l,
|
||||
fv, fv->root, memory_region_name(fv->root) ?: "(null)", fv->nr);
|
||||
fflush(stdout);
|
||||
}
|
||||
#endif
|
||||
if (!flatview_access_allowed(mr, attrs, addr, len)) {
|
||||
return MEMTX_ACCESS_ERROR;
|
||||
}
|
||||
@@ -3002,9 +2948,6 @@ MemTxResult address_space_read_full(AddressSpace *as, hwaddr addr,
|
||||
FlatView *fv;
|
||||
|
||||
if (len > 0) {
|
||||
#if defined(EMSCRIPTEN) && !defined(CONFIG_TCG_INTERPRETER)
|
||||
wasm_ramrace_log_access("address_space_read", addr, len, &wasm_ramrace_read_logs);
|
||||
#endif
|
||||
RCU_READ_LOCK_GUARD();
|
||||
fv = address_space_to_flatview(as);
|
||||
result = flatview_read(fv, addr, attrs, buf, len);
|
||||
@@ -3021,9 +2964,6 @@ MemTxResult address_space_write(AddressSpace *as, hwaddr addr,
|
||||
FlatView *fv;
|
||||
|
||||
if (len > 0) {
|
||||
#if defined(EMSCRIPTEN) && !defined(CONFIG_TCG_INTERPRETER)
|
||||
wasm_ramrace_log_access("address_space_write", addr, len, &wasm_ramrace_write_logs);
|
||||
#endif
|
||||
RCU_READ_LOCK_GUARD();
|
||||
fv = address_space_to_flatview(as);
|
||||
result = flatview_write(fv, addr, attrs, buf, len);
|
||||
@@ -3299,15 +3239,6 @@ void *address_space_map(AddressSpace *as,
|
||||
FlatView *fv;
|
||||
|
||||
trace_address_space_map(as, addr, len, is_write, *(uint32_t *) &attrs);
|
||||
#if defined(EMSCRIPTEN) && !defined(CONFIG_TCG_INTERPRETER)
|
||||
if (is_write) {
|
||||
wasm_ramrace_log_access("address_space_map_write", addr, len,
|
||||
&wasm_ramrace_map_logs);
|
||||
} else {
|
||||
wasm_ramrace_log_access("address_space_map_read", addr, len,
|
||||
&wasm_ramrace_map_logs);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (len == 0) {
|
||||
return NULL;
|
||||
@@ -3373,18 +3304,6 @@ void address_space_unmap(AddressSpace *as, void *buffer, hwaddr len,
|
||||
mr = memory_region_from_host(buffer, &addr1);
|
||||
if (mr != NULL) {
|
||||
if (is_write) {
|
||||
#if defined(EMSCRIPTEN) && !defined(CONFIG_TCG_INTERPRETER)
|
||||
if (wasm_ramrace_trace && !current_cpu && access_len > 0) {
|
||||
wasm_ramrace_unmap_logs++;
|
||||
if (wasm_ramrace_unmap_logs <= 200 ||
|
||||
(wasm_ramrace_unmap_logs % 10000) == 0) {
|
||||
printf("RAMRACE address_space_unmap_write off-vcpu #%u tid=%lu host-ram=0x%llx len=0x%llx\n",
|
||||
wasm_ramrace_unmap_logs, (unsigned long)pthread_self(),
|
||||
(unsigned long long)addr1, (unsigned long long)access_len);
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
invalidate_and_set_dirty(mr, addr1, access_len);
|
||||
}
|
||||
if (xen_enabled()) {
|
||||
@@ -3600,10 +3519,6 @@ address_space_read_cached_slow(MemoryRegionCache *cache, hwaddr addr,
|
||||
hwaddr mr_addr, l;
|
||||
MemoryRegion *mr;
|
||||
|
||||
#if defined(EMSCRIPTEN) && !defined(CONFIG_TCG_INTERPRETER)
|
||||
wasm_ramrace_log_access("address_space_read_cached", addr, len,
|
||||
&wasm_ramrace_cached_read_logs);
|
||||
#endif
|
||||
l = len;
|
||||
mr = address_space_translate_cached(cache, addr, &mr_addr, &l, false,
|
||||
MEMTXATTRS_UNSPECIFIED);
|
||||
@@ -3621,10 +3536,6 @@ address_space_write_cached_slow(MemoryRegionCache *cache, hwaddr addr,
|
||||
hwaddr mr_addr, l;
|
||||
MemoryRegion *mr;
|
||||
|
||||
#if defined(EMSCRIPTEN) && !defined(CONFIG_TCG_INTERPRETER)
|
||||
wasm_ramrace_log_access("address_space_write_cached", addr, len,
|
||||
&wasm_ramrace_cached_write_logs);
|
||||
#endif
|
||||
l = len;
|
||||
mr = address_space_translate_cached(cache, addr, &mr_addr, &l, true,
|
||||
MEMTXATTRS_UNSPECIFIED);
|
||||
|
||||
@@ -6607,15 +6607,6 @@ int tcg_gen_code(TCGContext *s, TranslationBlock *tb, uint64_t pc_start)
|
||||
#endif
|
||||
|
||||
#if defined(EMSCRIPTEN) && !defined(CONFIG_TCG_INTERPRETER)
|
||||
wasm_diff_register_tb(tb->tc.ptr, tcg_splitwx_to_rx(tb->tc.ptr), pc_start,
|
||||
wasm_tb_had_store,
|
||||
wasm_tb_had_store && !wasm_tb_had_helper);
|
||||
if (wasm_tb_had_store && wasm_store_pc_hi && pc_start >= wasm_store_pc_lo &&
|
||||
pc_start < wasm_store_pc_hi) {
|
||||
wasm_tci_only_tb = true;
|
||||
printf("STPC TCI pc=0x%llx\n", (unsigned long long)pc_start);
|
||||
fflush(stdout);
|
||||
}
|
||||
if (wasm_tci_only_tb) {
|
||||
for (int i = 0; i < get_core_nums(); i++) {
|
||||
export_vec_base[i] = WASM_TCI_ONLY_ENTRY;
|
||||
@@ -6684,14 +6675,6 @@ int tcg_gen_code(TCGContext *s, TranslationBlock *tb, uint64_t pc_start)
|
||||
}
|
||||
*(uint32_t *)wasm_blob_ptr_base = s->code_ptr - wasm_blob_ptr_base - 4;
|
||||
|
||||
if (wasm_dump_pc_hi && pc_start >= wasm_dump_pc_lo && pc_start < wasm_dump_pc_hi) {
|
||||
uint32_t mod_len = *(uint32_t *)wasm_blob_ptr_base;
|
||||
const uint8_t *mod = wasm_blob_ptr_base + 4;
|
||||
char dump_name[32];
|
||||
snprintf(dump_name, sizeof(dump_name), "pc-%08llx", (unsigned long long)pc_start);
|
||||
wasm_dump_module(mod, mod_len, dump_name);
|
||||
}
|
||||
|
||||
size_base = (uint32_t*)s->code_ptr;
|
||||
s->code_ptr += 4;
|
||||
memcpy(s->code_ptr, target_helper_funcs, num_helper_funcs * 4);
|
||||
|
||||
+1
-603
@@ -32,10 +32,7 @@
|
||||
#include <emscripten/threading.h>
|
||||
#include "wasm32.h"
|
||||
|
||||
extern int wasm_ramrace_trace;
|
||||
extern int wasm_tlb_guard_enabled;
|
||||
__thread uintptr_t tci_tb_ptr;
|
||||
static __thread int tlbtrace_guard_announced;
|
||||
__thread bool wasm_tci_only_tb;
|
||||
static int instantiated_wasm_count;
|
||||
|
||||
@@ -44,28 +41,6 @@ EMSCRIPTEN_KEEPALIVE int wasm_instantiated_tb_count(void)
|
||||
return qatomic_read(&instantiated_wasm_count);
|
||||
}
|
||||
|
||||
EM_JS(void, wasm_dump_module_js, (int ptr, int len, int name), {
|
||||
const bytes = Uint8Array.from(HEAP8.subarray(ptr, ptr + len));
|
||||
const dumpName = UTF8ToString(name);
|
||||
try {
|
||||
const fs = require('fs');
|
||||
const fname = '/tmp/tbdump-' + dumpName + '.wasm';
|
||||
fs.writeFileSync(fname, bytes);
|
||||
out('WASM_DUMP wrote ' + fname + ' (' + len + ' bytes)');
|
||||
} catch (e) {
|
||||
let hex = '';
|
||||
for (let i = 0; i < bytes.length; i++) hex += bytes[i].toString(16).padStart(2, '0');
|
||||
out('WASM_DUMP_HEX_BEGIN ' + dumpName + ' ' + len);
|
||||
for (let i = 0; i < hex.length; i += 2048) out('WASM_DUMP_HEX ' + dumpName + ' ' + hex.slice(i, i + 2048));
|
||||
out('WASM_DUMP_HEX_END ' + dumpName);
|
||||
}
|
||||
});
|
||||
|
||||
void wasm_dump_module(const uint8_t *p, int len, const char *name)
|
||||
{
|
||||
wasm_dump_module_js((int)p, len, (int)name);
|
||||
}
|
||||
|
||||
/* Disassemble TCI bytecode. */
|
||||
int print_insn_tci(bfd_vma addr, disassemble_info *info)
|
||||
{
|
||||
@@ -207,39 +182,6 @@ EM_JS(void, init_wasm32_js, (int tb_ptr_ptr, int cur_core_num, int to_remove_ins
|
||||
};
|
||||
});
|
||||
|
||||
static void wasm_update_tlb_guard(CPUArchState *env)
|
||||
{
|
||||
uintptr_t lo = UINTPTR_MAX;
|
||||
uintptr_t hi = 0;
|
||||
CPUState *cpu = env_cpu(env);
|
||||
|
||||
ctx.tlb_guard_on = wasm_tlb_guard_enabled;
|
||||
if (!ctx.tlb_guard_on) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < NB_MMU_MODES; i++) {
|
||||
CPUTLBDescFast *fast = &cpu->neg.tlb.f[i];
|
||||
CPUTLBDesc *desc = &cpu->neg.tlb.d[i];
|
||||
size_t n = fast->table ? ((fast->mask >> CPU_TLB_ENTRY_BITS) + 1) : 0;
|
||||
if (n) {
|
||||
lo = MIN(lo, (uintptr_t)fast->table);
|
||||
hi = MAX(hi, (uintptr_t)(fast->table + n));
|
||||
}
|
||||
if (desc->fulltlb && n) {
|
||||
lo = MIN(lo, (uintptr_t)desc->fulltlb);
|
||||
hi = MAX(hi, (uintptr_t)(desc->fulltlb + n));
|
||||
}
|
||||
}
|
||||
ctx.tlb_guard_lo = (uint32_t)lo;
|
||||
ctx.tlb_guard_hi = (uint32_t)hi;
|
||||
if (wasm_ramrace_trace && !tlbtrace_guard_announced) {
|
||||
printf("TLBGUARD range lo=0x%x hi=0x%x\n", ctx.tlb_guard_lo, ctx.tlb_guard_hi);
|
||||
fflush(stdout);
|
||||
tlbtrace_guard_announced = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void init_wasm32()
|
||||
{
|
||||
if (!initdone) {
|
||||
@@ -1313,544 +1255,17 @@ static inline uintptr_t tcg_qemu_tb_exec_tci(CPUArchState *env)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Compiled-vs-TCI differential (debug).
|
||||
*
|
||||
* TCI stays the live/authoritative path (never crashes). For a TB whose guest PC
|
||||
* is in the inflate window we additionally run the compiled blob as a shadow
|
||||
* against a DRAM + register snapshot, then compare the resulting registers and
|
||||
* DRAM against the authoritative TCI run. The first mismatch pinpoints the TB
|
||||
* (and the first divergent guest register / DRAM word) whose compiled codegen is
|
||||
* wrong — the corruption only manifests through a store, so a divergent DRAM word
|
||||
* is the miscompiled write. Opt-in via wasm_diff_enable(); no effect otherwise.
|
||||
*/
|
||||
#define DIFF_DRAM_BASE 0x3fc80000u
|
||||
#define DIFF_DRAM_SIZE 0x60000u
|
||||
#define DIFF_PC_LO 0x42000000ull
|
||||
#define DIFF_PC_HI 0x42200000ull
|
||||
|
||||
__thread uint64_t wasm_cur_tb_pc;
|
||||
static int wasm_diff_enabled;
|
||||
static GHashTable *diff_store_tbs;
|
||||
static GHashTable *diff_safe_tbs;
|
||||
static __thread int diff_reported;
|
||||
static __thread int diff_announced;
|
||||
static __thread int diff_started;
|
||||
static __thread int diff_skipped_slow;
|
||||
static __thread uint32_t diff_compared;
|
||||
static __thread uint8_t *diff_dram_pre, *diff_dram_c, *diff_dram_t;
|
||||
static __thread uint8_t diff_env_pre[sizeof(CPUArchState)];
|
||||
static __thread uint8_t diff_env_c[sizeof(CPUArchState)];
|
||||
static __thread uint8_t diff_env_t[sizeof(CPUArchState)];
|
||||
|
||||
/*
|
||||
* TB Entry Reads CPUNegativeOffsetState - The exit/interrupt check at TB start
|
||||
* loads env-8 (icount_decr/exit flag), which lives before CPUArchState. The
|
||||
* shadow must see the same pre-state here or it wrongly takes the early-exit
|
||||
* path, so snapshot a window immediately below env too.
|
||||
*/
|
||||
#define DIFF_NEG sizeof(CPUNegativeOffsetState)
|
||||
static __thread uint8_t diff_neg_pre[DIFF_NEG];
|
||||
static __thread uint8_t diff_neg_t[DIFF_NEG];
|
||||
static __thread uint8_t *diff_tlb_pre[NB_MMU_MODES];
|
||||
static __thread uint8_t *diff_fulltlb_pre[NB_MMU_MODES];
|
||||
static __thread size_t diff_tlb_size[NB_MMU_MODES];
|
||||
static __thread size_t diff_fulltlb_size[NB_MMU_MODES];
|
||||
static __thread uint32_t diff_compiled_ret;
|
||||
static __thread int diff_tci_iters;
|
||||
static __thread uint32_t diff2_eligible, diff2_incomplete, diff2_race;
|
||||
static __thread uint64_t tlbtrace_last_read, tlbtrace_last_write, tlbtrace_last_addend, tlbtrace_last_xlat;
|
||||
|
||||
/* Mode 2 (E11): compiled runs LIVE, so its post-state must be re-applied after
|
||||
* the TCI shadow. These hold the full compiled-post machine state. */
|
||||
static __thread uint8_t diff_neg_c[DIFF_NEG];
|
||||
static __thread uint8_t *diff_tlb_c[NB_MMU_MODES];
|
||||
static __thread uint8_t *diff_fulltlb_c[NB_MMU_MODES];
|
||||
|
||||
int wasm_diff_mode;
|
||||
int wasm_stateflush_enabled;
|
||||
int wasm_tlb_guard_enabled;
|
||||
uint64_t wasm_dump_pc_lo;
|
||||
uint64_t wasm_dump_pc_hi;
|
||||
int wasm_store_tci_disabled;
|
||||
int wasm_store_tci_hash;
|
||||
int wasm_store_helper_tci;
|
||||
int wasm_store_bisect_on;
|
||||
int wasm_store_bisect_width;
|
||||
uint64_t wasm_store_pc_lo;
|
||||
uint64_t wasm_store_pc_hi;
|
||||
int wasm_yield_trace;
|
||||
uint32_t wasm_yield_reentries;
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE void wasm_enable_yield_trace(void)
|
||||
{
|
||||
wasm_yield_trace = 1;
|
||||
}
|
||||
uint32_t wasm_store_bisect_mask;
|
||||
uint32_t wasm_store_bisect_val;
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE void wasm_disable_store_tci(void)
|
||||
{
|
||||
wasm_store_tci_disabled = 1;
|
||||
}
|
||||
|
||||
/* E15 isolation: 1 = interpret even-hash store TBs, 2 = odd-hash. Both widths
|
||||
* stay in the compiled half, so it separates a width-semantic bug from a
|
||||
* compiled-store-population threshold. */
|
||||
EMSCRIPTEN_KEEPALIVE void wasm_set_store_tci_hash(int mode)
|
||||
{
|
||||
wasm_store_tci_hash = mode;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE void wasm_set_store_helper_tci(void)
|
||||
{
|
||||
wasm_store_helper_tci = 1;
|
||||
}
|
||||
|
||||
/* E15 bisection: compile a store TB only when (hash(pc) & mask) == val, else
|
||||
* TCI. Runtime-settable so the culprit set can be binary-searched with no
|
||||
* rebuild. */
|
||||
EMSCRIPTEN_KEEPALIVE void wasm_set_store_bisect(int width, uint32_t mask, uint32_t val)
|
||||
{
|
||||
wasm_store_bisect_on = 1;
|
||||
wasm_store_bisect_width = width;
|
||||
wasm_store_bisect_mask = mask;
|
||||
wasm_store_bisect_val = val;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE void wasm_set_store_pc_window(uint32_t lo, uint32_t hi)
|
||||
{
|
||||
wasm_store_pc_lo = lo;
|
||||
wasm_store_pc_hi = hi;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE void wasm_diff_enable(void)
|
||||
{
|
||||
wasm_diff_enabled = 1;
|
||||
wasm_diff_mode = 1;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE void wasm_diff_enable2(void)
|
||||
{
|
||||
wasm_diff_enabled = 1;
|
||||
wasm_diff_mode = 2;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE void wasm_stateflush_enable(void)
|
||||
{
|
||||
wasm_stateflush_enabled = 1;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE void wasm_tlb_guard_enable(void)
|
||||
{
|
||||
wasm_tlb_guard_enabled = 1;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE void wasm_set_dump_pc_window(uint32_t lo, uint32_t hi)
|
||||
{
|
||||
wasm_dump_pc_lo = lo;
|
||||
wasm_dump_pc_hi = hi;
|
||||
}
|
||||
|
||||
int wasm_diff_on(void)
|
||||
{
|
||||
return wasm_diff_enabled;
|
||||
}
|
||||
|
||||
int wasm_diff_needs_nochain(void)
|
||||
{
|
||||
return wasm_diff_mode == 1;
|
||||
}
|
||||
|
||||
int wasm_diff_needs_nochain_at(uint64_t pc)
|
||||
{
|
||||
return (wasm_stateflush_enabled || wasm_diff_mode == 2) &&
|
||||
pc >= DIFF_PC_LO && pc < DIFF_PC_HI;
|
||||
}
|
||||
|
||||
static void wasm_trace_dram_tlb(CPUArchState *env, uint64_t pc)
|
||||
{
|
||||
CPUState *cpu;
|
||||
const vaddr page = 0x3fcb8000u;
|
||||
|
||||
if (!wasm_ramrace_trace || pc < DIFF_PC_LO || pc >= DIFF_PC_HI) {
|
||||
return;
|
||||
}
|
||||
|
||||
cpu = env_cpu(env);
|
||||
for (int mmu = 0; mmu < NB_MMU_MODES; mmu++) {
|
||||
CPUTLBDescFast *fast = &cpu->neg.tlb.f[mmu];
|
||||
CPUTLBDesc *desc = &cpu->neg.tlb.d[mmu];
|
||||
if (!fast->table || !desc->fulltlb) {
|
||||
continue;
|
||||
}
|
||||
size_t n = (fast->mask >> CPU_TLB_ENTRY_BITS) + 1;
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
CPUTLBEntry *e = &fast->table[i];
|
||||
if (((e->addr_read & TARGET_PAGE_MASK) == page) ||
|
||||
((e->addr_write & TARGET_PAGE_MASK) == page)) {
|
||||
CPUTLBEntryFull *f = &desc->fulltlb[i];
|
||||
if (e->addr_read != tlbtrace_last_read ||
|
||||
e->addr_write != tlbtrace_last_write ||
|
||||
e->addend != tlbtrace_last_addend ||
|
||||
f->xlat_section != tlbtrace_last_xlat) {
|
||||
printf("TLBTRACE pc=0x%llx mmu=%d index=%zu entry=%p full=%p read=0x%llx write=0x%llx addend=0x%llx full_xlat=0x%llx\n",
|
||||
(unsigned long long)pc, mmu, i, e, f,
|
||||
(unsigned long long)e->addr_read,
|
||||
(unsigned long long)e->addr_write,
|
||||
(unsigned long long)e->addend,
|
||||
(unsigned long long)f->xlat_section);
|
||||
fflush(stdout);
|
||||
tlbtrace_last_read = e->addr_read;
|
||||
tlbtrace_last_write = e->addr_write;
|
||||
tlbtrace_last_addend = e->addend;
|
||||
tlbtrace_last_xlat = f->xlat_section;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void wasm_diff_register_tb(const void *rw_ptr, const void *rx_ptr, uint64_t pc,
|
||||
bool has_store, bool safe_shadow)
|
||||
{
|
||||
if (!diff_store_tbs) {
|
||||
diff_store_tbs = g_hash_table_new(g_direct_hash, g_direct_equal);
|
||||
diff_safe_tbs = g_hash_table_new(g_direct_hash, g_direct_equal);
|
||||
}
|
||||
g_hash_table_remove(diff_store_tbs, rw_ptr);
|
||||
g_hash_table_remove(diff_store_tbs, rx_ptr);
|
||||
g_hash_table_remove(diff_safe_tbs, rw_ptr);
|
||||
g_hash_table_remove(diff_safe_tbs, rx_ptr);
|
||||
if (!has_store) {
|
||||
return;
|
||||
}
|
||||
gpointer value = GUINT_TO_POINTER((uint32_t)pc);
|
||||
g_hash_table_insert(diff_store_tbs, (void *)rw_ptr, value);
|
||||
g_hash_table_insert(diff_store_tbs, (void *)rx_ptr, value);
|
||||
if (safe_shadow) {
|
||||
g_hash_table_insert(diff_safe_tbs, (void *)rw_ptr, GINT_TO_POINTER(1));
|
||||
g_hash_table_insert(diff_safe_tbs, (void *)rx_ptr, GINT_TO_POINTER(1));
|
||||
}
|
||||
}
|
||||
|
||||
bool wasm_diff_lookup_store_tb(const void *tb_ptr, uint64_t *pc)
|
||||
{
|
||||
gpointer value = diff_store_tbs ? g_hash_table_lookup(diff_store_tbs, tb_ptr) : NULL;
|
||||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
if (pc) {
|
||||
*pc = GPOINTER_TO_UINT(value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool wasm_diff_lookup_safe_tb(const void *tb_ptr)
|
||||
{
|
||||
return diff_safe_tbs && g_hash_table_contains(diff_safe_tbs, tb_ptr);
|
||||
}
|
||||
|
||||
static void diff_report(uint64_t pc)
|
||||
{
|
||||
uint32_t pre_ra, pre_sp, stack_m4 = 0, stack_0 = 0, stack_4 = 0;
|
||||
memcpy(&pre_ra, diff_env_pre + 4, 4);
|
||||
memcpy(&pre_sp, diff_env_pre + 8, 4);
|
||||
if (pre_sp >= DIFF_DRAM_BASE + 4 && pre_sp + 8 <= DIFF_DRAM_BASE + DIFF_DRAM_SIZE) {
|
||||
uint32_t off = pre_sp - DIFF_DRAM_BASE;
|
||||
memcpy(&stack_m4, diff_dram_pre + off - 4, 4);
|
||||
memcpy(&stack_0, diff_dram_pre + off, 4);
|
||||
memcpy(&stack_4, diff_dram_pre + off + 4, 4);
|
||||
}
|
||||
/* Compare guest integer registers (env offset 0..127 = gpr[0..31]). */
|
||||
for (int i = 0; i < 32; i++) {
|
||||
uint32_t c, t;
|
||||
memcpy(&c, diff_env_c + i * 4, 4);
|
||||
memcpy(&t, diff_env_t + i * 4, 4);
|
||||
if (c != t) {
|
||||
uint32_t neg8_pre, neg8_t;
|
||||
memcpy(&neg8_pre, diff_neg_pre + DIFF_NEG - 8, 4);
|
||||
memcpy(&neg8_t, diff_neg_t + DIFF_NEG - 8, 4);
|
||||
printf("TCGDIFF pc=0x%llx gpr[%d] compiled=0x%08x tci=0x%08x "
|
||||
"pre-ra=0x%08x pre-sp=0x%08x cret=0x%08x neg8_pre=0x%08x neg8_t=0x%08x tci_iters=%d\n",
|
||||
(unsigned long long)pc, i, c, t, pre_ra, pre_sp,
|
||||
diff_compiled_ret, neg8_pre, neg8_t, diff_tci_iters);
|
||||
fflush(stdout);
|
||||
diff_reported = 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
/* Compare DRAM word-by-word; first divergent word is the miscompiled store. */
|
||||
for (uint32_t off = 0; off < DIFF_DRAM_SIZE; off += 4) {
|
||||
uint32_t c, t;
|
||||
memcpy(&c, diff_dram_c + off, 4);
|
||||
memcpy(&t, diff_dram_t + off, 4);
|
||||
if (c != t) {
|
||||
printf("TCGDIFF pc=0x%llx dram[0x%08x] compiled=0x%08x tci=0x%08x\n",
|
||||
(unsigned long long)pc, DIFF_DRAM_BASE + off, c, t);
|
||||
fflush(stdout);
|
||||
diff_reported = 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void diff_snapshot_pre(void)
|
||||
{
|
||||
if (!diff_dram_pre) {
|
||||
diff_dram_pre = malloc(DIFF_DRAM_SIZE);
|
||||
diff_dram_c = malloc(DIFF_DRAM_SIZE);
|
||||
diff_dram_t = malloc(DIFF_DRAM_SIZE);
|
||||
}
|
||||
CPUNegativeOffsetState *neg = (void *)((uint8_t *)ctx.env - DIFF_NEG);
|
||||
memcpy(diff_env_pre, ctx.env, sizeof(CPUArchState));
|
||||
memcpy(diff_neg_pre, neg, DIFF_NEG);
|
||||
for (int i = 0; i < NB_MMU_MODES; i++) {
|
||||
size_t tlb_size = neg->tlb.f[i].mask + (1 << CPU_TLB_ENTRY_BITS);
|
||||
size_t n = (neg->tlb.f[i].mask >> CPU_TLB_ENTRY_BITS) + 1;
|
||||
size_t fulltlb_size = n * sizeof(CPUTLBEntryFull);
|
||||
diff_tlb_pre[i] = g_realloc(diff_tlb_pre[i], tlb_size);
|
||||
diff_fulltlb_pre[i] = g_realloc(diff_fulltlb_pre[i], fulltlb_size);
|
||||
diff_tlb_size[i] = tlb_size;
|
||||
diff_fulltlb_size[i] = fulltlb_size;
|
||||
memcpy(diff_tlb_pre[i], neg->tlb.f[i].table, tlb_size);
|
||||
memcpy(diff_fulltlb_pre[i], neg->tlb.d[i].fulltlb, fulltlb_size);
|
||||
}
|
||||
cpu_physical_memory_read(DIFF_DRAM_BASE, diff_dram_pre, DIFF_DRAM_SIZE);
|
||||
}
|
||||
|
||||
static bool diff_restore_pre(CPUArchState *env)
|
||||
{
|
||||
CPUNegativeOffsetState *neg = (void *)((uint8_t *)env - DIFF_NEG);
|
||||
CPUNegativeOffsetState *pre = (void *)diff_neg_pre;
|
||||
for (int i = 0; i < NB_MMU_MODES; i++) {
|
||||
if (neg->tlb.f[i].table != pre->tlb.f[i].table ||
|
||||
neg->tlb.d[i].fulltlb != pre->tlb.d[i].fulltlb) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
cpu_physical_memory_write(DIFF_DRAM_BASE, diff_dram_pre, DIFF_DRAM_SIZE);
|
||||
memcpy(env, diff_env_pre, sizeof(CPUArchState));
|
||||
memcpy(neg, diff_neg_pre, DIFF_NEG);
|
||||
for (int i = 0; i < NB_MMU_MODES; i++) {
|
||||
memcpy(neg->tlb.f[i].table, diff_tlb_pre[i], diff_tlb_size[i]);
|
||||
memcpy(neg->tlb.d[i].fulltlb, diff_fulltlb_pre[i], diff_fulltlb_size[i]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void diff_ensure_compiled(const void *tb_ptr)
|
||||
{
|
||||
ctx.tb_ptr = (uint32_t *)tb_ptr;
|
||||
int slot_off = (uint32_t)ctx.tb_ptr + export_vec_off;
|
||||
if (*(int32_t *)slot_off <= 0) {
|
||||
instantiate_wasm();
|
||||
}
|
||||
}
|
||||
|
||||
/* Capture/restore full compiled-post machine state (mode 2 live trajectory). */
|
||||
static void diff_capture_compiled_post(CPUArchState *env)
|
||||
{
|
||||
CPUNegativeOffsetState *neg = (void *)((uint8_t *)env - DIFF_NEG);
|
||||
memcpy(diff_neg_c, neg, DIFF_NEG);
|
||||
for (int i = 0; i < NB_MMU_MODES; i++) {
|
||||
diff_tlb_c[i] = g_realloc(diff_tlb_c[i], diff_tlb_size[i]);
|
||||
diff_fulltlb_c[i] = g_realloc(diff_fulltlb_c[i], diff_fulltlb_size[i]);
|
||||
memcpy(diff_tlb_c[i], neg->tlb.f[i].table, diff_tlb_size[i]);
|
||||
memcpy(diff_fulltlb_c[i], neg->tlb.d[i].fulltlb, diff_fulltlb_size[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void diff_restore_compiled_post(CPUArchState *env)
|
||||
{
|
||||
CPUNegativeOffsetState *neg = (void *)((uint8_t *)env - DIFF_NEG);
|
||||
cpu_physical_memory_write(DIFF_DRAM_BASE, diff_dram_c, DIFF_DRAM_SIZE);
|
||||
memcpy(env, diff_env_c, sizeof(CPUArchState));
|
||||
memcpy(neg, diff_neg_c, DIFF_NEG);
|
||||
for (int i = 0; i < NB_MMU_MODES; i++) {
|
||||
memcpy(neg->tlb.f[i].table, diff_tlb_c[i], diff_tlb_size[i]);
|
||||
memcpy(neg->tlb.d[i].fulltlb, diff_fulltlb_c[i], diff_fulltlb_size[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static bool diff_run_compiled(const void *tb_ptr)
|
||||
{
|
||||
ctx.tb_ptr = (uint32_t *)tb_ptr;
|
||||
ctx.do_init = 1;
|
||||
ctx.done_flag = 2;
|
||||
int slot_off = (uint32_t)ctx.tb_ptr + export_vec_off;
|
||||
g_assert(*(int32_t *)slot_off > 0);
|
||||
diff_compiled_ret = ((wasm_func_ptr)(*(uint32_t *)slot_off))(&ctx);
|
||||
memcpy(diff_env_c, ctx.env, sizeof(CPUArchState));
|
||||
cpu_physical_memory_read(DIFF_DRAM_BASE, diff_dram_c, DIFF_DRAM_SIZE);
|
||||
/* done_flag 2 = pure inline (no slow path); 1 = completed store slow-path
|
||||
* helper. Both finished this TB (tb_ptr==0); 0 = genuine unwind, skip. */
|
||||
return ctx.done_flag != 0 && (uint32_t)ctx.tb_ptr == 0;
|
||||
}
|
||||
|
||||
uintptr_t QEMU_DISABLE_CFI tcg_qemu_tb_exec(CPUArchState *env,
|
||||
const void *v_tb_ptr)
|
||||
{
|
||||
ctx.env = env;
|
||||
ctx.tb_ptr = (uint32_t*)v_tb_ptr;
|
||||
ctx.do_init = 1;
|
||||
wasm_update_tlb_guard(env);
|
||||
|
||||
if (wasm_diff_mode == 2) {
|
||||
if (!diff_started) {
|
||||
printf("TCGDIFF2 enabled first-pc=0x%llx\n",
|
||||
(unsigned long long)wasm_cur_tb_pc);
|
||||
fflush(stdout);
|
||||
diff_started = 1;
|
||||
}
|
||||
uint64_t pc = 0;
|
||||
int slot = *(int32_t *)((uint32_t)ctx.tb_ptr + export_vec_off);
|
||||
bool eligible = !diff_reported && slot > 0 &&
|
||||
wasm_diff_lookup_store_tb(v_tb_ptr, &pc) &&
|
||||
wasm_diff_lookup_safe_tb(v_tb_ptr) &&
|
||||
pc >= DIFF_PC_LO && pc < DIFF_PC_HI;
|
||||
if (eligible) {
|
||||
if (!diff_announced) {
|
||||
printf("TCGDIFF2 active first-store-pc=0x%llx\n",
|
||||
(unsigned long long)pc);
|
||||
fflush(stdout);
|
||||
diff_announced = 1;
|
||||
}
|
||||
diff_snapshot_pre();
|
||||
struct wasmContext pre_ctx = ctx;
|
||||
|
||||
/* Run compiled LIVE (authoritative buggy path); accumulate its state. */
|
||||
bool compiled_complete = diff_run_compiled(v_tb_ptr);
|
||||
uint32_t res = diff_compiled_ret;
|
||||
diff_capture_compiled_post(env);
|
||||
struct wasmContext c_ctx = ctx;
|
||||
|
||||
diff2_eligible++;
|
||||
if (!compiled_complete) {
|
||||
diff2_incomplete++;
|
||||
}
|
||||
if ((diff2_eligible % 20000) == 0) {
|
||||
printf("TCGDIFF2 progress eligible=%u incomplete=%u race=%u compared=%u pc=0x%llx\n",
|
||||
diff2_eligible, diff2_incomplete, diff2_race, diff_compared,
|
||||
(unsigned long long)pc);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
/* Rewind, run the known-good TCI shadow, compare. */
|
||||
if (compiled_complete && diff_restore_pre(env)) {
|
||||
ctx = pre_ctx;
|
||||
do {
|
||||
tcg_qemu_tb_exec_tci(env);
|
||||
} while ((uint32_t)ctx.tb_ptr != 0);
|
||||
memcpy(diff_env_t, env, sizeof(CPUArchState));
|
||||
memcpy(diff_neg_t, (uint8_t *)env - DIFF_NEG, DIFF_NEG);
|
||||
cpu_physical_memory_read(DIFF_DRAM_BASE, diff_dram_t, DIFF_DRAM_SIZE);
|
||||
uint32_t neg8_t_flag;
|
||||
memcpy(&neg8_t_flag, diff_neg_t + DIFF_NEG - 8, 4);
|
||||
if (neg8_t_flag & 0xffff0000) {
|
||||
diff2_race++;
|
||||
} else {
|
||||
diff_compared++;
|
||||
if (diff_compared == 1 || diff_compared % 10000 == 0) {
|
||||
printf("TCGDIFF2 compared=%u pc=0x%llx\n", diff_compared,
|
||||
(unsigned long long)pc);
|
||||
fflush(stdout);
|
||||
}
|
||||
diff_report(pc);
|
||||
}
|
||||
}
|
||||
|
||||
/* Restore the live compiled trajectory and continue. */
|
||||
diff_restore_compiled_post(env);
|
||||
ctx = c_ctx;
|
||||
return res;
|
||||
}
|
||||
/* Non-eligible: fall through to normal dispatch (compiled runs live). */
|
||||
} else if (wasm_diff_mode == 1) {
|
||||
if (!diff_started) {
|
||||
printf("TCGDIFF enabled first-pc=0x%llx tb-meta=0x%08x\n",
|
||||
(unsigned long long)wasm_cur_tb_pc, *(uint32_t *)ctx.tb_ptr);
|
||||
fflush(stdout);
|
||||
diff_started = 1;
|
||||
}
|
||||
uint64_t pc = 0;
|
||||
bool is_store_tb = wasm_diff_lookup_store_tb(v_tb_ptr, &pc);
|
||||
int eligible = !diff_reported && is_store_tb &&
|
||||
wasm_diff_lookup_safe_tb(v_tb_ptr) &&
|
||||
pc >= DIFF_PC_LO && pc < DIFF_PC_HI;
|
||||
if (eligible) {
|
||||
if (!diff_announced) {
|
||||
printf("TCGDIFF active first-store-pc=0x%llx\n", (unsigned long long)pc);
|
||||
fflush(stdout);
|
||||
diff_announced = 1;
|
||||
}
|
||||
|
||||
diff_ensure_compiled(v_tb_ptr);
|
||||
struct wasmContext pre_ctx = ctx;
|
||||
diff_snapshot_pre();
|
||||
|
||||
/* Run and retain the authoritative TCI result first. */
|
||||
uint32_t res;
|
||||
int tci_iters = 0;
|
||||
do {
|
||||
res = tcg_qemu_tb_exec_tci(env);
|
||||
tci_iters++;
|
||||
} while ((uint32_t)ctx.tb_ptr != 0);
|
||||
diff_tci_iters = tci_iters;
|
||||
struct wasmContext tci_ctx = ctx;
|
||||
memcpy(diff_env_t, env, sizeof(CPUArchState));
|
||||
memcpy(diff_neg_t, (uint8_t *)env - DIFF_NEG, DIFF_NEG);
|
||||
cpu_physical_memory_read(DIFF_DRAM_BASE, diff_dram_t, DIFF_DRAM_SIZE);
|
||||
|
||||
/* Rewind guest state, run compiled as a shadow, then restore TCI. */
|
||||
bool restored = diff_restore_pre(env);
|
||||
ctx = pre_ctx;
|
||||
bool compiled_complete = restored && diff_run_compiled(v_tb_ptr);
|
||||
cpu_physical_memory_write(DIFF_DRAM_BASE, diff_dram_t, DIFF_DRAM_SIZE);
|
||||
memcpy(env, diff_env_t, sizeof(CPUArchState));
|
||||
memcpy((uint8_t *)env - DIFF_NEG, diff_neg_t, DIFF_NEG);
|
||||
ctx = tci_ctx;
|
||||
|
||||
/* Skip Async-Exit Races - If the IO thread forced a CPU exit
|
||||
* (icount_decr.high == -1) around the TCI run, TCI took the TB-entry
|
||||
* interrupt exit while the restored compiled shadow saw a cleared
|
||||
* flag and ran fully. That divergence is a harness race, not codegen. */
|
||||
uint32_t neg8_t_flag;
|
||||
memcpy(&neg8_t_flag, diff_neg_t + DIFF_NEG - 8, 4);
|
||||
if (neg8_t_flag & 0xffff0000) {
|
||||
compiled_complete = false;
|
||||
}
|
||||
|
||||
if (compiled_complete && !diff_reported) {
|
||||
diff_compared++;
|
||||
if (diff_compared == 1 || diff_compared % 10000 == 0) {
|
||||
printf("TCGDIFF compared=%u pc=0x%llx\n", diff_compared,
|
||||
(unsigned long long)pc);
|
||||
fflush(stdout);
|
||||
}
|
||||
diff_report(pc);
|
||||
} else if (!diff_skipped_slow) {
|
||||
printf("TCGDIFF skip-slow-path pc=0x%llx\n", (unsigned long long)pc);
|
||||
fflush(stdout);
|
||||
diff_skipped_slow = 1;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
while (true) {
|
||||
int tb_entry_ptr = (uint32_t)ctx.tb_ptr + export_vec_off;
|
||||
uint32_t res;
|
||||
uint32_t prev_tb = (uint32_t)ctx.tb_ptr;
|
||||
bool was_compiled = false;
|
||||
bool diff_store_tci = wasm_diff_mode == 1 &&
|
||||
wasm_diff_lookup_store_tb(ctx.tb_ptr, NULL);
|
||||
if (diff_store_tci) {
|
||||
res = tcg_qemu_tb_exec_tci(env);
|
||||
} else if (*(int32_t*)tb_entry_ptr > 0) {
|
||||
if (*(int32_t*)tb_entry_ptr > 0) {
|
||||
res = ((wasm_func_ptr)(*(uint32_t*)tb_entry_ptr))(&ctx);
|
||||
was_compiled = true;
|
||||
} else if (*(int32_t *)tb_entry_ptr == WASM_TCI_ONLY_ENTRY) {
|
||||
res = tcg_qemu_tb_exec_tci(env);
|
||||
} else if (*(int32_t*)tb_entry_ptr > (-1 * INSTANTIATE_NUM)) {
|
||||
@@ -1860,23 +1275,6 @@ uintptr_t QEMU_DISABLE_CFI tcg_qemu_tb_exec(CPUArchState *env,
|
||||
instantiate_wasm();
|
||||
qatomic_inc(&instantiated_wasm_count);
|
||||
res = ((wasm_func_ptr)(*(uint32_t*)tb_entry_ptr))(&ctx);
|
||||
was_compiled = true;
|
||||
}
|
||||
if (was_compiled) {
|
||||
uint64_t pc = 0;
|
||||
wasm_diff_lookup_store_tb((void *)prev_tb, &pc);
|
||||
wasm_trace_dram_tlb(env, pc);
|
||||
}
|
||||
/* E17: a compiled TB that returns non-zero with the same tb_ptr and
|
||||
* do_init still clear is a mid-TB yield/resume (rewind path), not a
|
||||
* chain (chains set do_init=1) and not completion (tb_ptr==0). */
|
||||
if (wasm_yield_trace && was_compiled && (uint32_t)ctx.tb_ptr == prev_tb &&
|
||||
(uint32_t)ctx.tb_ptr != 0 && ctx.do_init == 0) {
|
||||
wasm_yield_reentries++;
|
||||
if (wasm_yield_reentries <= 20 || (wasm_yield_reentries % 5000) == 0) {
|
||||
printf("YIELD reentry #%u tb=0x%x\n", wasm_yield_reentries, prev_tb);
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
if ((uint32_t)ctx.tb_ptr == 0) {
|
||||
return res;
|
||||
|
||||
@@ -18,12 +18,6 @@ struct wasmContext {
|
||||
uint64_t *stack128;
|
||||
// 28
|
||||
uint32_t unwinding;
|
||||
// 32
|
||||
uint32_t tlb_guard_lo;
|
||||
// 36
|
||||
uint32_t tlb_guard_hi;
|
||||
// 40
|
||||
uint32_t tlb_guard_on;
|
||||
};
|
||||
|
||||
#define ENV_OFF 0
|
||||
@@ -34,9 +28,6 @@ struct wasmContext {
|
||||
#define DONE_FLAG_OFF 20
|
||||
#define STACK128_OFF 24
|
||||
#define UNWINDING_OFF 28
|
||||
#define TLB_GUARD_LO_OFF 32
|
||||
#define TLB_GUARD_HI_OFF 36
|
||||
#define TLB_GUARD_ON_OFF 40
|
||||
|
||||
void set_done_flag();
|
||||
|
||||
@@ -50,39 +41,6 @@ void flush_tb_instances(void);
|
||||
|
||||
void init_wasm32();
|
||||
|
||||
void wasm_dump_module(const uint8_t *p, int len, const char *name);
|
||||
|
||||
/* TCG compiled-vs-TCI differential (debug, opt-in via wasm_diff_enable()).
|
||||
* wasm_cur_tb_pc is the guest PC of the TB about to run, set by cpu_tb_exec. */
|
||||
extern __thread uint64_t wasm_cur_tb_pc;
|
||||
extern int wasm_diff_mode;
|
||||
extern int wasm_store_tci_disabled;
|
||||
extern int wasm_store_tci_hash;
|
||||
extern int wasm_store_helper_tci;
|
||||
extern int wasm_store_bisect_on;
|
||||
extern int wasm_store_bisect_width;
|
||||
extern uint64_t wasm_store_pc_lo;
|
||||
extern uint64_t wasm_store_pc_hi;
|
||||
extern uint64_t wasm_dump_pc_lo;
|
||||
extern uint64_t wasm_dump_pc_hi;
|
||||
extern uint32_t wasm_store_bisect_mask;
|
||||
extern uint32_t wasm_store_bisect_val;
|
||||
void wasm_disable_store_tci(void);
|
||||
void wasm_set_store_tci_hash(int mode);
|
||||
void wasm_set_store_helper_tci(void);
|
||||
void wasm_set_store_bisect(int width, uint32_t mask, uint32_t val);
|
||||
void wasm_set_store_pc_window(uint32_t lo, uint32_t hi);
|
||||
void wasm_set_dump_pc_window(uint32_t lo, uint32_t hi);
|
||||
void wasm_diff_enable(void);
|
||||
void wasm_diff_enable2(void);
|
||||
int wasm_diff_on(void);
|
||||
int wasm_diff_needs_nochain(void);
|
||||
int wasm_diff_needs_nochain_at(uint64_t pc);
|
||||
void wasm_diff_register_tb(const void *rw_ptr, const void *rx_ptr, uint64_t pc,
|
||||
bool has_store, bool safe_shadow);
|
||||
bool wasm_diff_lookup_store_tb(const void *tb_ptr, uint64_t *pc);
|
||||
bool wasm_diff_lookup_safe_tb(const void *tb_ptr);
|
||||
|
||||
extern __thread bool wasm_tci_only_tb;
|
||||
|
||||
#define INSTANTIATE_NUM 1500
|
||||
|
||||
@@ -2235,25 +2235,6 @@ static uint8_t tcg_wasm_out_tlb_load(TCGContext *s, TCGReg addr, MemOpIdx oi, bo
|
||||
tcg_wasm_out_op_i64_add(s);
|
||||
tcg_wasm_out_op_local_set(s, TMP64_0_IDX);
|
||||
|
||||
if (!is_ld) {
|
||||
tcg_wasm_out_ctx_i32_load(s, TLB_GUARD_ON_OFF);
|
||||
tcg_wasm_out_op_if_noret(s);
|
||||
tcg_wasm_out_op_local_get(s, TMP64_0_IDX);
|
||||
tcg_wasm_out_op_i32_wrap_i64(s);
|
||||
tcg_wasm_out_ctx_i32_load(s, TLB_GUARD_LO_OFF);
|
||||
tcg_wasm_out_op_i32_ge_u(s);
|
||||
tcg_wasm_out_op_local_get(s, TMP64_0_IDX);
|
||||
tcg_wasm_out_op_i32_wrap_i64(s);
|
||||
tcg_wasm_out_ctx_i32_load(s, TLB_GUARD_HI_OFF);
|
||||
tcg_wasm_out_op_i32_lt_u(s);
|
||||
tcg_wasm_out_op_i32_and(s);
|
||||
tcg_wasm_out_op_if_noret(s);
|
||||
tcg_wasm_out_op_i64_const(s, 0);
|
||||
tcg_wasm_out_op_local_set(s, TMP64_0_IDX);
|
||||
tcg_wasm_out_op_end(s);
|
||||
tcg_wasm_out_op_end(s);
|
||||
}
|
||||
|
||||
tcg_wasm_out_op_end(s);
|
||||
|
||||
|
||||
@@ -3383,14 +3364,8 @@ static uint8_t tcg_tci_out_qemu_ldst(TCGContext *s, TCGOpcode opc, const TCGArg
|
||||
insn = deposit32(insn, 0, 8, opc);
|
||||
tcg_tci_out32(s, insn);
|
||||
}
|
||||
/* Dump tags: instrumentation-only, set during codegen so tcg.c can dump one store-TB / load-TB
|
||||
* module for static analysis. No effect on emitted code. */
|
||||
__thread bool wasm_tb_had_store;
|
||||
__thread bool wasm_tb_had_load;
|
||||
__thread bool wasm_tb_had_helper;
|
||||
static void tcg_out_qemu_ld(TCGContext *s, TCGOpcode opc, const TCGArg *args, bool is_64)
|
||||
{
|
||||
wasm_tb_had_load = true;
|
||||
tcg_tci_out_qemu_ldst(s, opc, args);
|
||||
// Aliased qemu_ld - The wasm32 wrapper corrupts vCPU TLB state when the load destination also holds the address.
|
||||
if (args[0] == args[1]) {
|
||||
@@ -3400,40 +3375,8 @@ static void tcg_out_qemu_ld(TCGContext *s, TCGOpcode opc, const TCGArg *args, bo
|
||||
}
|
||||
static void tcg_out_qemu_st(TCGContext *s, TCGOpcode opc, const TCGArg *args, bool is_64)
|
||||
{
|
||||
wasm_tb_had_store = true;
|
||||
tcg_tci_out_qemu_ldst(s, opc, args);
|
||||
tcg_wasm_out_qemu_st(s, args, is_64);
|
||||
bool store_tci = false;
|
||||
if (s->gen_tb && (wasm_store_tci_hash || wasm_store_bisect_on)) {
|
||||
uint64_t pc = s->gen_tb->pc;
|
||||
uint32_t h = (uint32_t)((pc * 0x9e3779b97f4a7c15ull) >> 33);
|
||||
if (wasm_store_bisect_on) {
|
||||
/* Bisect one width: other widths stay compiled so their culprit is
|
||||
* always present; the bisected width compiles only its hash bucket.
|
||||
* This makes finding each culprit a single-target binary search. */
|
||||
int w = 1 << (opc & MO_SIZE);
|
||||
bool compiled;
|
||||
if (wasm_store_bisect_width && w != wasm_store_bisect_width) {
|
||||
compiled = true;
|
||||
} else {
|
||||
compiled = (h & wasm_store_bisect_mask) == wasm_store_bisect_val;
|
||||
if (compiled) {
|
||||
printf("STBISECT compile pc=0x%llx h=0x%x w=%d\n",
|
||||
(unsigned long long)pc, h, w);
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
store_tci = !compiled;
|
||||
} else {
|
||||
store_tci = ((h & 1) == (uint32_t)(wasm_store_tci_hash - 1));
|
||||
}
|
||||
}
|
||||
if (wasm_store_helper_tci && (wasm_tb_had_helper || wasm_tb_had_load)) {
|
||||
store_tci = true;
|
||||
}
|
||||
if (wasm_diff_mode != 2 && store_tci) {
|
||||
wasm_tci_only_tb = true;
|
||||
}
|
||||
}
|
||||
static void tcg_out_deposit_i32(TCGContext *s, TCGOpcode opc, TCGReg dest, TCGReg arg1, TCGReg arg2, int pos, int len)
|
||||
{
|
||||
@@ -3557,7 +3500,6 @@ static bool tcg_out_sti(TCGContext *s, TCGType type, TCGArg val,
|
||||
static void tcg_out_call(TCGContext *s, const tcg_insn_unit *target,
|
||||
const TCGHelperInfo *info)
|
||||
{
|
||||
wasm_tb_had_helper = true;
|
||||
tcg_tci_out_call(s, target, info);
|
||||
tcg_wasm_out_call(s, target, info);
|
||||
}
|
||||
@@ -3837,9 +3779,6 @@ void tcg_out_init() {
|
||||
current_label_pos = 0;
|
||||
env_cached = false;
|
||||
wasm_tci_only_tb = false;
|
||||
wasm_tb_had_store = false;
|
||||
wasm_tb_had_load = false;
|
||||
wasm_tb_had_helper = false;
|
||||
}
|
||||
|
||||
/* Test if a constant matches the constraint. */
|
||||
|
||||
Reference in New Issue
Block a user