Skip to content

Commit

Permalink
[trace] Factor out implementation of -ti and calls to BasicTracing
Browse files Browse the repository at this point in the history
  • Loading branch information
titzer committed Sep 23, 2024
1 parent 6e141a5 commit 7505937
Show file tree
Hide file tree
Showing 11 changed files with 232 additions and 109 deletions.
10 changes: 2 additions & 8 deletions src/engine/Instrumentation.v3
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,12 @@
// Provides a common entrypoint to the engine's instrumentation capabilities, including
// the insertion/removal of probes, as well as firing probes during execution.
component Instrumentation {
def TRACE = TraceProbe.new();
def probes = ProbeList.new(); // probes on the global interpreter loop

// Reset the instrumentation to the default, i.e. only tracing probes.
def reset() {
def reset() { // TODO: only used in testing.
probes.clear();
if (Trace.interpreter) probes.add(TRACE);
}
// Special mode to enable interpreter tracing.
def enableTraceInt() {
probes.add(TRACE);
BasicTracing.reset(); // TODO: engine depends on tracing in util
}
// Insert a probe into the global interpreter loop.
def insertGlobalProbe(p: Probe) {
Expand Down Expand Up @@ -92,7 +87,6 @@ component Instrumentation {
if (map == null) return null;
return map[offset];
}

// Called by the engine to fire probes on the global interpreter loop.
def fireGlobalProbes(dynamicLoc: DynamicLoc) -> Throwable {
Metrics.probe_fires.val++;
Expand Down
21 changes: 0 additions & 21 deletions src/engine/Trace.v3
Original file line number Diff line number Diff line change
Expand Up @@ -21,33 +21,12 @@ component Trace {
var stack = false;
var whamm = false;

var moduleFilter: DeclFilter;
var callsFilter: DeclFilter;

def OUT = TraceBuilder.new();
def STDOUT = System.write(1, _);
def STDOUT_void(r: Range<byte>) {
System.write(1, r);
}

def CALL_TRACE_PROBE = CallTraceProbe.new();

def instrumentModule(module: Module) {
if (callsFilter == null) return;
// Add a call tracer if the global option is turned on.
for (i < module.functions.length) {
var func = module.functions[i];
if (callsFilter.matches(module, func)) {
Instrumentation.insertFuncEntryProbe(module, func.func_index, CALL_TRACE_PROBE);
}
}
}
def instrumentFunc(module: Module, func: FuncDecl) {
if (callsFilter == null) return;
if (callsFilter.matches(module, func)) {
Instrumentation.insertFuncEntryProbe(module, func.func_index, CALL_TRACE_PROBE);
}
}
def renderCspRange<T>(buf: StringBuilder, r: Range<T>, render: (T, StringBuilder) -> StringBuilder) -> StringBuilder {
for (i < r.length) {
if (i > 0) buf.csp();
Expand Down
8 changes: 4 additions & 4 deletions src/engine/TraceOptions.v3
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ component TraceOptions {
if (arg.length >= (traceModule.length + 1) && arg[traceModule.length] == '=') {
pat = Arrays.range(arg, traceModule.length + 1, arg.length);
}
Trace.moduleFilter = DeclFilters.parseString(pat);
BasicTracing.moduleFilter = DeclFilters.parseString(pat);
return true;
}
var traceCalls = "--trace-calls";
Expand All @@ -55,7 +55,7 @@ component TraceOptions {
if (arg.length >= (traceCalls.length + 1) && arg[traceCalls.length] == '=') {
pat = Arrays.range(arg, traceCalls.length + 1, arg.length);
}
Trace.callsFilter = DeclFilters.parseString(pat);
BasicTracing.callsFilter = DeclFilters.parseString(pat);
return true;
}
} else if (Strings.startsWith(arg, "-t")) {
Expand All @@ -82,13 +82,13 @@ component TraceOptions {
}
private def setOption(opt: TraceOption) {
match (opt) {
int => { Trace.interpreter = true; Instrumentation.enableTraceInt(); }
int => { Trace.interpreter = true; BasicTracing.enableTraceInt(); }
binparse => Trace.binparse = true;
validation => Trace.validation = true;
test => Trace.test = true;
spectest => Trace.spectest = true;
operands => Trace.operands = true;
memory => Trace.memory = true;
memory => { Trace.memory = true; BasicTracing.memoryFilter = DeclFilters.parseString(null); }
canon => { Trace.canon = true; Trace.uid = true; }
uid => Trace.uid = true;
compiler => Trace.compiler = true;
Expand Down
4 changes: 2 additions & 2 deletions src/monitors/ModuleInstrumenter.v3
Original file line number Diff line number Diff line change
Expand Up @@ -358,8 +358,8 @@ class MemoryVisitor(
}

def visit_MEMORY_GROW(memory_index: u31) {
if (growFn != null)
Instrumentation.insertLocalProbe(module, bi.func.func_index, bi.pc, MemoryGrowProbe.new(memory_index, growFn));
if (growFn == null) return;
Instrumentation.insertLocalProbe(module, bi.func.func_index, bi.pc, MemoryGrowProbe.new(memory_index, growFn));
}
}
private class MemoryAccessProbe(imm: MemArg, operand: int, size: u64, f: (DynamicLoc, Memory, u64, u64) -> Resumption) extends Probe {
Expand Down
202 changes: 202 additions & 0 deletions src/util/BasicTracing.v3
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
// Copyright 2024 Wizard authors. All rights reserved.
// See LICENSE for details of Apache 2.0 license.

def OUT = Trace.OUT;
// Implements basic tracing functionality such as --trace-interpreter, --trace-calls, --trace-memory.
component BasicTracing {
var moduleFilter: DeclFilter;
var callsFilter: DeclFilter;
var memoryFilter: DeclFilter;

def reset() {
if (Trace.interpreter) Instrumentation.probes.add(TRACE_INTERP_PROBE);
}
// Special mode to enable interpreter tracing.
def enableTraceInt() {
Instrumentation.probes.add(TRACE_INTERP_PROBE);
}
def instrumentFunc(module: Module, func: FuncDecl) {
// Add a call and memory tracing if the global options are turned on.
if (callsFilter != null && callsFilter.matches(module, func)) addTraceCallsProbes(module, func);
if (memoryFilter != null && memoryFilter.matches(module, func)) addTraceMemoryProbes(module, func);
}
def instrumentModule(module: Module) {
if (callsFilter == null && memoryFilter == null) return;
for (i < module.functions.length) instrumentFunc(module, module.functions[i]);
}
def addTraceCallsProbes(module: Module, func: FuncDecl) {
Instrumentation.insertFuncEntryProbe(module, func.func_index, CALL_TRACE_PROBE);
}
def addTraceMemoryProbes(module: Module, func: FuncDecl) {
TraceMemoryInstrumenter.new(module).runOne(func);
}
}

// Basic tracing support for the interpreter.
def TRACE_INTERP_PROBE = TraceInstrProbe.new();
class TraceInstrProbe extends Probe {
def tracer = InstrTracer.new();
def codeptr = DataReader.new([]);

def fire(dynamicLoc: DynamicLoc) -> Resumption {
var func = dynamicLoc.func;
var offset = dynamicLoc.pc;
codeptr.reset(func.decl.cur_bytecode, offset, func.decl.cur_bytecode.length);
var module = if(func.instance != null, func.instance.module);
var len = OUT.length;
var accessor = dynamicLoc.frame.getFrameAccessor();
OUT.pad(' ', len + 2 * accessor.depth());
OUT.mark();
OUT.putc('+').putd(offset).puts(": ");
OUT.ljustify_mark(6);

var opcode = codeptr.data[codeptr.pos];
OUT.mark();
if (opcode == InternalOpcode.PROBE.code) {
OUT.puts("<probe> ");
var prev = (codeptr.data, codeptr.pos, codeptr.limit);
codeptr.reset(func.decl.orig_bytecode, prev.1, prev.2);
tracer.putInstr(OUT, module, codeptr);
codeptr.reset(prev.0, prev.1, prev.2);
} else {
tracer.putInstr(OUT, module, codeptr);
}
if (Trace.operands) {
OUT.sp();
OUT.ljustify_mark(24);
OUT.puts("|");
for (i < accessor.numLocals()) {
OUT.sp().putv(accessor.getLocal(i));
}
var count = accessor.numOperands();
OUT.puts(" |");
for (j = 1 - count; j <= 0; j++) {
OUT.sp().putv(accessor.getOperand(j));
}
}
OUT.ln();
return Resumption.Continue;
}
}

// Basic tracing support for calls.
def CALL_TRACE_PROBE = CallTraceProbe.new();
class CallTraceProbe extends Probe {
def fire(dynamicLoc: DynamicLoc) -> Resumption {
var func = dynamicLoc.func;
var accessor = dynamicLoc.frame.getFrameAccessor();
OUT.indent(accessor.depth());
func.render(OUT);
OUT.puts("(");
for (i < func.sig.params.length) {
var val = accessor.getLocal(i);
if (i > 0) OUT.csp();
Values.render(val, OUT);
}
OUT.puts(")").ln();
return Resumption.Continue;
}
}

// Basic tracing support for memory loads.
class TraceMemoryInstrumenter extends BytecodeInstrumenter {
new(module: Module) super(module) { }

def visitLoad(op: Opcode, imm: MemArg, size: u8) {
insertProbeHere(MemoryTraceLoadProbe.new(op, imm, size));
}
def visitStore(op: Opcode, imm: MemArg, size: u8) {
insertProbeHere(MemoryTraceStoreProbe.new(op, imm, size));
}
def visit_MEMORY_SIZE(memory_index: u31) { // TODO
}
def visit_MEMORY_GROW(memory_index: u31) { // TODO
}
def visit_MEMORY_INIT(data_index: u31, memory_index: u31) { // TODO
}
def visit_MEMORY_COPY(dst_memory_index: u31, src_memory_index: u31) { // TODO
}
def visit_MEMORY_FILL(memory_index: u31) { // TODO
}
def visit_MEMORY_ATOMIC_NOTIFY(imm: MemArg) { // TODO
}
def visit_MEMORY_ATOMIC_WAIT32(imm: MemArg) { // TODO
}
def visit_MEMORY_ATOMIC_WAIT64(imm: MemArg) { // TODO
}
def visit_ATOMIC_FENCE(flags: u8) { // TODO
}
}

class MemoryTraceLoadProbe(op: Opcode, imm: MemArg, size: u8) extends Probe {
def fire(dynamicLoc: DynamicLoc) -> Resumption {
var func = dynamicLoc.func;
var accessor = dynamicLoc.frame.getFrameAccessor();
OUT.indent(accessor.depth());
var index = outputOpAndAddr(op, imm.memory_index, imm.offset, accessor.getOperand(0));
OUT.puts(" -> ");
var mem = func.instance.memories[imm.memory_index];
var t = mem.range_oil_64(imm.offset, index, size);
if (t.ok()) {
for (i < t.result.length) OUT.putx_8(t.result[i]);
} else {
OUT.puts("!out-of-bounds");
}
OUT.ln();
return Resumption.Continue;
}
}

class MemoryTraceStoreProbe(op: Opcode, imm: MemArg, size: u8) extends Probe {
def fire(dynamicLoc: DynamicLoc) -> Resumption {
var func = dynamicLoc.func;
var accessor = dynamicLoc.frame.getFrameAccessor();
OUT.indent(accessor.depth());
var index = outputOpAndAddr(op, imm.memory_index, imm.offset, accessor.getOperand(-1));
OUT.puts(" <- ");
var mem = func.instance.memories[imm.memory_index];
var val = accessor.getOperand(0);

match (size) {
1 => match (val) {
I32(v) => OUT.putx_8(byte.view(v));
I64(v) => OUT.putx_8(byte.view(v));
_ => ; // TODO: error
}
2 => match (val) {
I32(v) => OUT.putx_16(u16.view(v));
I64(v) => OUT.putx_16(u16.view(v));
_ => ; // TODO: error
}
4 => match (val) {
I32(v) => OUT.putx_32(u32.view(v));
I64(v) => OUT.putx_32(u32.view(v));
F32(v) => OUT.putx_32(u32.view(v));
_ => ; // TODO: error
}
8 => match (val) {
I64(v) => OUT.putx_64(u64.view(v));
F64(v) => OUT.putx_64(u64.view(v));
_ => ; // TODO: error
}
_ => ; // TODO: other sizes
}
OUT.ln();
return Resumption.Continue;
}
}

def outputOpAndAddr(op: Opcode, memory_index: u31, offset: u64, index: Value) -> u64 {
OUT.mark()
.puts(op.mnemonic).sp()
.ljustify_mark(20)
.put1("mem%d[", memory_index);
var result: u64;
match (index) {
I32(v) => OUT.putx_32(u32.view(offset + (result = v)));
I64(v) => OUT.putx_64(offset + (result = v));
_ => ; // TODO: dynamic type error
}
OUT.putc(']');
return result;
}
10 changes: 7 additions & 3 deletions src/util/BytecodeInstrumenter.v3
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class BytecodeInstrumenter(module: Module) extends BytecodeVisitor {
def bi = BytecodeIterator.new();
var filter: DeclFilter; // client can set to filter functions instrumented

// Runs instrumentation on all functions in this module that match
// Runs instrumentation on all functions in this module that match any {filter} that's been installed.
def run() {
for (i < module.functions.length) {
var func = module.functions[i];
Expand All @@ -17,7 +17,7 @@ class BytecodeInstrumenter(module: Module) extends BytecodeVisitor {
bi.iterate(func, this);
}
}

// Runs instrumentation on all functions in this module that match {matcher}.
def runMatching(matcher: (Module, FuncDecl) -> bool) {
for (i < module.functions.length) {
var func = module.functions[i];
Expand All @@ -26,7 +26,11 @@ class BytecodeInstrumenter(module: Module) extends BytecodeVisitor {
bi.iterate(func, this);
}
}

// Runs instrumentation on the given function {func}, if it is not imported.
def runOne(func: FuncDecl) {
if (func.imp == null) bi.iterate(func, this);
}
// Utility that can be used by subclasses to insert a probe on the current bytecode while iterating.
def insertProbeHere(probe: Probe) {
Instrumentation.insertLocalProbe(module, bi.func.func_index, bi.pc, probe);
}
Expand Down
7 changes: 7 additions & 0 deletions src/util/OptionsRegistry.v3
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ component OptionsRegistry {
if (parse(a)) args[i] = null;
}
}
def matchesPrefixWithOptionalEquals(arg: string, prefix: string) -> (bool, Range<byte>) {
if (!Strings.startsWith(arg, prefix)) return (false, null);
var sub = arg[prefix.length ...];
if (sub.length == 0) return (true, null);
if (sub.length > 0 && sub[0] == '=') return (true, sub[1 ...]);
return (false, null);
}
}

// Collects related options and stores a help string for each.
Expand Down
Loading

0 comments on commit 7505937

Please sign in to comment.