Swap-Aggregator-Subgraph/node_modules/assemblyscript/lib/rtrace/index.js
Richa-iitr d211083153 Revert "Revert "added handler""
This reverts commit c36ee8c5ca.
2022-07-03 07:30:05 +05:30

347 lines
10 KiB
JavaScript

// WebAssembly pages are 65536 kb
const PAGE_SIZE_BITS = 16;
const PAGE_SIZE = 1 << PAGE_SIZE_BITS;
const PAGE_MASK = PAGE_SIZE - 1;
// Wasm32 pointer size is 4 bytes
const PTR_SIZE_BITS = 2;
const PTR_SIZE = 1 << PTR_SIZE_BITS;
const PTR_MASK = PTR_SIZE - 1;
const PTR_VIEW = Uint32Array;
export const BLOCK_OVERHEAD = PTR_SIZE;
export const OBJECT_OVERHEAD = 16;
export const TOTAL_OVERHEAD = BLOCK_OVERHEAD + OBJECT_OVERHEAD;
function assert(x) {
if (!x) throw Error("assertion failed");
return x;
}
Error.stackTraceLimit = 15;
function trimStacktrace(stack, levels) {
return stack.split(/\r?\n/).slice(1 + levels);
}
const hrtime = typeof performance !== "undefined" && performance.now
? performance.now
: typeof process !== "undefined" && process.hrtime
? () => { let t = process.hrtime(); return t[0] * 1e3 + t[1] / 1e6; }
: Date.now;
const mmTagsToString = [
"",
"FREE",
"LEFTFREE",
"FREE+LEFTFREE"
];
const gcColorToString = [
"BLACK/WHITE",
"WHITE/BLACK",
"GRAY",
"INVALID"
];
export class Rtrace {
constructor(options) {
this.options = options || {};
this.onerror = this.options.onerror || function() { /* nop */ };
this.oninfo = this.options.oninfo || function() { /* nop */ };
this.oncollect_ = this.options.oncollect || function() { /* nop */ };
this.memory = null;
this.shadow = null;
this.shadowStart = 0x100000000;
this.blocks = new Map();
this.allocSites = new Map();
this.freedBlocks = new Map();
this.gcProfileStart = 0;
this.gcProfile = [];
this.allocCount = 0;
this.resizeCount = 0;
this.moveCount = 0;
this.freeCount = 0;
this.heapBase = 0x100000000;
}
install(imports) {
if (!imports) imports = {};
imports.rtrace = Object.assign(imports.rtrace || {}, {
oninit: this.oninit.bind(this),
onalloc: this.onalloc.bind(this),
onresize: this.onresize.bind(this),
onmove: this.onmove.bind(this),
onvisit: this.onvisit.bind(this),
onfree: this.onfree.bind(this),
oninterrupt: this.oninterrupt.bind(this),
onyield: this.onyield.bind(this),
oncollect: this.oncollect.bind(this),
onstore: this.onstore.bind(this),
onload: this.onload.bind(this)
});
return imports;
}
/** Synchronizes the shadow memory with the module's memory. */
syncShadow() {
if (!this.memory) {
this.memory = assert(this.options.getMemory());
this.shadow = new WebAssembly.Memory({
initial: ((this.memory.buffer.byteLength + PAGE_MASK) & ~PAGE_MASK) >>> PAGE_SIZE_BITS
});
} else {
var diff = this.memory.buffer.byteLength - this.shadow.buffer.byteLength;
if (diff > 0) this.shadow.grow(diff >>> 16);
}
}
/** Marks a block's presence in shadow memory. */
markShadow(info, oldSize = 0) {
assert(this.shadow && this.shadow.byteLength == this.memory.byteLength);
assert((info.size & PTR_MASK) == 0);
if (info.ptr < this.shadowStart) {
this.shadowStart = info.ptr;
}
var len = info.size >>> PTR_SIZE_BITS;
var view = new PTR_VIEW(this.shadow.buffer, info.ptr, len);
var errored = false;
var start = oldSize >>> PTR_SIZE_BITS;
for (let i = 0; i < start; ++i) {
if (view[i] != info.ptr && !errored) {
this.onerror(Error("shadow region mismatch: " + view[i] + " != " + info.ptr), info);
errored = true;
}
}
errored = false;
for (let i = start; i < len; ++i) {
if (view[i] != 0 && !errored) {
this.onerror(Error("shadow region already in use: " + view[i] + " != 0"), info);
errored = true;
}
view[i] = info.ptr;
}
}
/** Unmarks a block's presence in shadow memory. */
unmarkShadow(info, oldSize = info.size) {
assert(this.shadow && this.shadow.byteLength == this.memory.byteLength);
var len = oldSize >>> PTR_SIZE_BITS;
var view = new PTR_VIEW(this.shadow.buffer, info.ptr, len);
var errored = false;
var start = 0;
if (oldSize != info.size) {
assert(oldSize > info.size);
start = info.size >>> PTR_SIZE_BITS;
}
for (let i = 0; i < len; ++i) {
if (view[i] != info.ptr && !errored) {
this.onerror(Error("shadow region mismatch: " + view[i] + " != " + info.ptr), info);
errored = true;
}
if (i >= start) view[i] = 0;
}
}
/** Performs an access to shadow memory. */
accessShadow(ptr, size, isLoad, isRT) {
this.syncShadow();
if (ptr < this.shadowStart) return;
var value = new Uint32Array(this.shadow.buffer, ptr & ~PTR_MASK, 1)[0];
if (value != 0) return;
if (!isRT) {
let stack = trimStacktrace(new Error().stack, 2);
this.onerror(new Error("OOB " + (isLoad ? "load" : "store") + (8 * size) + " at address " + ptr + "\n" + stack.join("\n")));
}
}
/** Obtains information about a block. */
getBlockInfo(ptr) {
const [
mmInfo,
gcInfo,
gcInfo2,
rtId,
rtSize
] = new Uint32Array(this.memory.buffer, ptr, 5);
const size = mmInfo & ~3;
return {
ptr,
size: BLOCK_OVERHEAD + size, // total incl. overhead
mmInfo: {
tags: mmTagsToString[mmInfo & 3],
size: size // as stored excl. overhead
},
gcInfo: {
color: gcColorToString[gcInfo & 3],
next: gcInfo & ~3,
prev: gcInfo2
},
rtId,
rtSize
};
}
/** Checks if rtrace is active, i.e. at least one event has occurred. */
get active() {
return Boolean(this.allocCount || this.resizeCount || this.moveCount || this.freeCount);
}
/** Checks if there are any leaks and emits them via `oninfo`. Returns the number of live blocks. */
check() {
if (this.oninfo) {
for (let [ptr, info] of this.blocks) {
this.oninfo("LIVE " + ptr + "\n" + info.allocStack.join("\n"));
}
}
return this.blocks.size;
}
// Runtime instrumentation
oninit(heapBase) {
this.heapBase = heapBase;
this.gcProfileStart = 0;
this.gcProfile.length = 0;
this.oninfo("INIT heapBase=" + heapBase);
}
onalloc(ptr) {
this.syncShadow();
++this.allocCount;
var info = this.getBlockInfo(ptr);
if (this.blocks.has(ptr)) {
this.onerror(Error("duplicate alloc: " + ptr), info);
} else {
this.oninfo("ALLOC " + ptr + ".." + (ptr + info.size));
this.markShadow(info);
let allocStack = trimStacktrace(new Error().stack, 1); // strip onalloc
this.blocks.set(ptr, Object.assign(info, { allocStack }));
}
}
onresize(ptr, oldSize) {
this.syncShadow();
++this.resizeCount;
const info = this.getBlockInfo(ptr);
if (!this.blocks.has(ptr)) {
this.onerror(Error("orphaned resize: " + ptr), info);
} else {
const beforeInfo = this.blocks.get(ptr);
if (beforeInfo.size != oldSize) {
this.onerror(Error(`size mismatch upon resize: ${ptr} (${beforeInfo.size} != ${oldSize})`), info);
}
const newSize = info.size;
this.oninfo("RESIZE " + ptr + ".." + (ptr + newSize) + " (" + oldSize + "->" + newSize + ")");
this.blocks.set(ptr, Object.assign(info, { allocStack: beforeInfo.allocStack }));
if (newSize > oldSize) {
this.markShadow(info, oldSize);
} else if (newSize < oldSize) {
this.unmarkShadow(info, oldSize);
}
}
}
onmove(oldPtr, newPtr) {
this.syncShadow();
++this.moveCount;
var oldInfo = this.getBlockInfo(oldPtr);
var newInfo = this.getBlockInfo(newPtr);
if (!this.blocks.has(oldPtr)) {
this.onerror(Error("orphaned move (old): " + oldPtr), oldInfo);
} else {
if (!this.blocks.has(newPtr)) {
this.onerror(Error("orphaned move (new): " + newPtr), newInfo);
} else {
const beforeInfo = this.blocks.get(oldPtr);
const oldSize = oldInfo.size;
const newSize = newInfo.size;
if (beforeInfo.size != oldSize) {
this.onerror(Error(`size mismatch upon move: ${oldPtr} (${beforeInfo.size} != ${oldSize})`), oldInfo);
}
this.oninfo("MOVE " + oldPtr + ".." + (oldPtr + oldSize) + " -> " + newPtr + ".." + (newPtr + newSize));
// calls new alloc before and old free after
}
}
}
onvisit(ptr) {
// Indicates that a block has been freed but it still visited by the GC
if (ptr > this.heapBase && !this.blocks.has(ptr)) {
let err = Error("orphaned visit: " + ptr);
let info = this.freedBlocks.get(ptr);
if (info) {
err.stack += "\n^ allocated at:\n" + info.allocStack.join("\n");
err.stack += "\n^ freed at:\n" + info.freeStack.join("\n");
}
this.onerror(err, null);
return false;
}
return true;
}
onfree(ptr) {
this.syncShadow();
++this.freeCount;
var info = this.getBlockInfo(ptr);
if (!this.blocks.has(ptr)) {
this.onerror(Error("orphaned free: " + ptr), info);
} else {
const oldInfo = this.blocks.get(ptr);
if (info.size != oldInfo.size) {
this.onerror(Error(`size mismatch upon free: ${ptr} (${oldInfo.size} != ${info.size})`), info);
}
this.oninfo("FREE " + ptr + ".." + (ptr + info.size));
this.unmarkShadow(info);
const allocInfo = this.blocks.get(ptr);
this.blocks.delete(ptr);
const allocStack = allocInfo.allocStack;
const freeStack = trimStacktrace(new Error().stack, 1); // strip onfree
// (not much) TODO: Maintaining these is essentially a memory leak
this.freedBlocks.set(ptr, { allocStack, freeStack });
}
}
oncollect(total) {
this.oninfo(`COLLECT at ${total}`);
this.plot(total);
this.oncollect_();
}
// GC profiling
plot(total, pause = 0) {
if (!this.gcProfileStart) this.gcProfileStart = Date.now();
this.gcProfile.push([ Date.now() - this.gcProfileStart, total, pause ]);
}
oninterrupt(total) {
this.interruptStart = hrtime();
this.plot(total);
}
onyield(total) {
var pause = hrtime() - this.interruptStart;
if (pause >= 1) console.log("interrupted for " + pause.toFixed(1) + "ms");
this.plot(total, pause);
}
// Memory instrumentation
onstore(ptr, offset, bytes, isRT) {
this.accessShadow(ptr + offset, bytes, false, isRT);
return ptr;
}
onload(ptr, offset, bytes, isRT) {
this.accessShadow(ptr + offset, bytes, true, isRT);
return ptr;
}
}
export default {
Rtrace
};