Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 144 additions & 0 deletions src/monitors/PathProfilingMonitor.v3
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// Implements a monitor that profiles dynamic execution paths through functions.
def monitor_ = MonitorRegistry.add(
"path-profile", "Profiles dynamic execution paths through functions.",
PathProfilingMonitor.new());

class PathProfilingMonitor extends Monitor {
// counts for each path
def entries = Vector<PathCountsProbe>.new();

def onParse(m: Module, err: ErrorGen) {
def bi = BytecodeIterator.new();
for (i < m.functions.length) {
def func: FuncDecl = m.functions[i];
if (func.imp != null) continue; // skip imported functions
def profile = PathProfile.new(func);
if (profile.success) {
// define entry probe for the function to get index for the per-call site path counts
def entry = PathCountsProbe.new(m, func, profile);
Instrumentation.insertFuncEntryProbe(m, func.func_index, entry);
entries.put(entry);

// place Ball-Larus instrumentation
def pathRegVar = PathInstrumentation.newFrameVar(func);
bi.reset(func);
profile.entry.dfs(fun (b: PathProfilingBlock) {
if (!b.instrumented()) return;
def probe = PathMonitorProbe.new(bi, b, pathRegVar, entry);
// XXX can be combiend with the probe above? this is only for one probe on `ENTRY -> head` edge
if (b.last_instr_pc < 0) Instrumentation.insertFuncEntryProbe(m, func.func_index, probe);
else Instrumentation.insertLocalProbe(m, func.func_index, b.last_instr_pc, probe);
}, null, null);
}
}
}
def onFinish(i: Instance, r: Result) {
for (j < entries.length) {
def entry = entries[j];
def profile = entry.profile;
out.put1("func %q", entry.func.render(entry.module.names, _)).ln();
printCfg(out, profile);
// each call site lists only the paths its calls took
for (s < entry.sites.length) {
def site = entry.sites[s];
def counts = entry.counts[s];
// a call site no call counted under
var hit = false;
for (c in counts) if (c > 0) hit = true;
if (!hit) continue;
if (site.0 == null) out.puts(" caller engine");
else out.put2(" caller %q +%d", site.0.render(entry.module.names, _), site.1);
out.ln();
for (k < profile.numPaths) {
if (counts[k] > 0) out.put2(" counts[%d] = %d", k, counts[k]).ln();
}
}
// each path number with the route it denotes, so counts[k] can be read back to a route
for (k < profile.numPaths) {
out.put2(" counts[%d] = %d", k, entry.total(k)).ln();
out.puts(" path:");
profile.renderPath(k, out);
out.ln();
}
// (Section 4.1) a self-loop is counted, not path-profiled
for (i < profile.selfLoops.length) {
def block = profile.selfLoops[i];
out.put3(" selfloop[block %d +%d] = %d", block.id, block.last_instr_pc, entry.total(profile.numPaths + i)).ln();
}
}
}
}


private class PathCountsProbe(module: Module, func: FuncDecl, profile: PathProfile) extends Probe {
def numPaths = profile.numPaths + profile.selfLoops.length;
def callSiteVar = PathInstrumentation.newFrameVar(func); // holds the activation's call site index

// (func, pc) of the call sites TO this func, added as their first call enters
def sites = Vector<(FuncDecl, int)>.new(); // list of callers
def counts = Vector<Array<int>>.new(); // corresponding list of path counts

new() {
// add misc calls: the engine and host functions, which have no wasm frame
addSite((null, -1));
}

def fire(loc: DynamicLoc) -> ProbeAction {
def accessor = loc.frame.getFrameAccessor();
def site = siteOf(accessor.caller());
accessor.getWriter().setFrameVar(callSiteVar, Values.box_i(site));
return ProbeAction.Continue;
}
// Returns the index of the call site that {caller} describes, adding it if new.
private def siteOf(caller: FrameLoc) -> int {
match (caller) {
Wasm(func, pc, frame) => {
for (i < sites.length) if (sites[i] == (func.decl, pc)) return i; // XXX: linear search
return addSite((func.decl, pc));
}
_ => return 0;
}
}
// Adds {site} and returns its index.
private def addSite(site: (FuncDecl, int)) -> int {
sites.put(site);
counts.put(Array<int>.new(numPaths));
return sites.length - 1;
}
// Returns the count of {path} summed over every call site.
def total(path: int) -> int {
var sum = 0;
for (i < counts.length) sum += counts[i][path];
return sum;
}
}
// The monitor's probe, which counts each completed path under the activation's call site.
private class PathMonitorProbe extends PathSiteProbe {
def counts: PathCountsProbe;
new(bi: BytecodeIterator, block: PathProfilingBlock, pathRegVar: int, counts) super(bi, block, pathRegVar) { }
def incCount(accessor: FrameAccessor, path: int) {
counts.counts[Values.unbox_i(accessor.getFrameVar(counts.callSiteVar))][path]++;
}
}

// Prints each block of the CFG and its outgoing edges, with their Val, increment and kind.
def printCfg(out: TraceBuilder, p: PathProfile) {
p.entry.dfs(printBlock(out, _), null, null);
}
def printBlock(out: TraceBuilder, b: PathProfilingBlock) {
// the range of bytes this block owns; its outgoing edges are probed at {last_instr_pc}
out.put3(" block %d +%d ... %d", b.id, b.bind_pos, b.end).ln();
b.outgoing.apply(fun (e: PathProfilingEdge) {
// Increment(e) is defined for chords only; a tree edge carries none
if (e.isChord) {
out.put3(" -> block %d val=%d inc=%d", e.target.id, e.val, e.increment);
} else {
out.put2(" -> block %d val=%d", e.target.id, e.val);
}
// Section 4 edge kinds: without these a dummy is indistinguishable from a real val=0 edge
if (e.isDummy) out.puts(" DUMMY");
// a backedge is left out of the acyclic graph the paths are numbered in (Section 4)
if (e.isBackedge) out.puts(" BACKEDGE");
out.ln();
});
}
23 changes: 22 additions & 1 deletion src/util/CfgBuilder.v3
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,24 @@
class CfgBuilder {
private def bi = BytecodeIterator.new();
private def ctl_stack = ControlStack<CfgBlock>.new();
private var prev_pc: int = -1; // for recording `last_instr_pc` when ending a block

new() {
ctl_stack.newLabel = newCfgBlock;
ctl_stack.splitBlock = splitCfgBlock;
ctl_stack.mergeBlock = mergeCfgBlock;
ctl_stack.bindLabel = bindCfgBlock;
ctl_stack.endBlock = endCfgBlock;
}

def build(func: FuncDecl) -> CfgBlock {
var start = ctl_stack.reset();
bi.reset(func);
bi.dispatchLocalDecls(ctl_stack); // advance bi.pc past locals
def start = ctl_stack.resetAtPc(bi.pc);
while (bi.more()) {
ctl_stack.pc = bi.pc;
bi.dispatch(ctl_stack);
prev_pc = bi.pc;
bi.next();
}
return start;
Expand Down Expand Up @@ -55,6 +59,20 @@ class CfgBuilder {
label.bind_pos = pc;
if (verbose) Trace.OUT.put2("bind id:%d @%d", label.id, pc).ln();
}
private def endCfgBlock(cs: ControlStack<CfgBlock>, b: CfgBlock, pc: int) {
if (b == null) return;
var last: int;
if (pc < 0) {
// end of the block is at the pc of the next instruction
last = bi.pc;
b.end = bi.nextPc();
} else {
// current instruction at pc belongs to new block
last = prev_pc;
b.end = pc;
}
if (b.end > b.bind_pos) b.last_instr_pc = last;
}
}

def verbose = false;
Expand All @@ -66,7 +84,10 @@ class CfgEdge(pc: int, source: CfgBlock, target: CfgBlock) {

class CfgBlock(decl_pos: int) {
def id = labelId++;
// the bytecode range [bind_pos ... end) this block owns
var bind_pos: int = -1;
var end: int = -1;
var last_instr_pc: int = -1; // last instruction this block owns, or < 0 when it owns none
var outgoing: List<CfgEdge>;
var incoming: List<CfgEdge>;
var mark: int;
Expand Down
24 changes: 22 additions & 2 deletions src/util/ControlStack.v3
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,22 @@ class ControlStack<B> extends BytecodeVisitor {
var mergeBlock: (ControlStack<B>, B, B) -> B = ControlStack<B>.defaultMergeBlock;
// Binds a label position.
var bindLabel: (ControlStack<B>, int, B) -> void = ControlStack<B>.defaultBindLabel;
// Ends a block at a position, or after the current bytecode if the position is -1.
var endBlock: (ControlStack<B>, B, int) -> void = ControlStack<B>.defaultEndBlock;
// current block
var block: B;

private def stack = ArrayStack<ControlStackEntry<B>>.new();

// Resets this control to the initial state for entry.
def reset() -> B {
pc = 0;
return resetAtPc(0);
}
def resetAtPc(new_pc: int) -> B {
pc = new_pc;
stack.clear();
block = newLabel(this);
bindLabel(this, 0, block);
bindLabel(this, new_pc, block);
push(Opcode.UNREACHABLE);
return block;
}
Expand All @@ -56,13 +61,15 @@ class ControlStack<B> extends BytecodeVisitor {
def visit_LOOP(btc: BlockTypeCode) {
var from = stack.peek();
var ctl = push(Opcode.LOOP);
endBlock(this, block, pc);
bindLabel(this, pc, ctl.label);
block = ctl.label = mergeBlock(this, block, ctl.label);
}
def visit_IF(btc: BlockTypeCode) {
var from = stack.peek();
var t = splitBlock(this, block);
var ctl = push(Opcode.IF);
endBlock(this, block, -1);
block = t.0;
bindLabel(this, -1, block);
ctl.else_block = t.1;
Expand All @@ -73,6 +80,7 @@ class ControlStack<B> extends BytecodeVisitor {
ctl.start_pos = pc;
ctl.start_opcode = Opcode.ELSE;
ctl.reachable = true;
endBlock(this, block, -1);
block = ctl.else_block;
bindLabel(this, -1, block);
var d: B;
Expand All @@ -90,12 +98,14 @@ class ControlStack<B> extends BytecodeVisitor {
var ctl = stack.peek();
ctl.start_pos = pc;
ctl.start_opcode = Opcode.CATCH;
endBlock(this, block, -1);
block = newLabel(this);
}
def visit_CATCH_ALL() {
var ctl = stack.peek();
ctl.start_pos = pc;
ctl.start_opcode = Opcode.CATCH_ALL;
endBlock(this, block, -1);
block = newLabel(this);
}
def visit_THROW(tag_code: u31) {
Expand Down Expand Up @@ -180,35 +190,43 @@ class ControlStack<B> extends BytecodeVisitor {
end = mergeBlock(this, block, end); // fallthru
ctl.reachable = true;
end = mergeBlock(this, ctl.else_block, end); // else merge
endBlock(this, block, pc);
bindLabel(this, pc, ctl.else_block); // the empty else clause
endBlock(this, ctl.else_block, pc);
bindLabel(this, pc, end);
block = end;
}
_ => {
var end = ctl.label;
end = mergeBlock(this, block, end); // fallthru
endBlock(this, block, pc);
bindLabel(this, pc, end);
block = end;
}
}
stack.pop();
if (stack.top == 0) endBlock(this, block, -1); // the function's `end`
}
private def setUnreachable() {
var from = stack.peek();
from.reachable = false;
endBlock(this, block, -1);
block = newBlock(this);
}
private def br(depth: u31) {
var from = stack.peek();
var to = stack.elems[stack.top - depth - 1];
to.label = mergeBlock(this, block, to.label);
from.reachable = false;
endBlock(this, block, -1);
block = newBlock(this);
}
private def brIf(depth: u31) {
var from = stack.peek();
var to = stack.elems[stack.top - depth - 1];
var t = splitBlockInto(this, block, to.label);
to.label = t.0;
endBlock(this, block, -1);
block = t.1;
}
// Default implementations of block management functions.
Expand All @@ -232,6 +250,8 @@ class ControlStack<B> extends BytecodeVisitor {
}
def defaultBindLabel(pc: int, b: B) {
}
def defaultEndBlock(b: B, pc: int) {
}
}

// A control stack entry tracked by the control stack.
Expand Down
Loading
Loading