diff --git a/js/src/jit-test/lib/wasm.js b/js/src/jit-test/lib/wasm.js index a2c8245a899f..cb49a91981fb 100644 --- a/js/src/jit-test/lib/wasm.js +++ b/js/src/jit-test/lib/wasm.js @@ -172,3 +172,141 @@ function wasmGetScriptBreakpoints(wasmScript) { }); return result; } + +const WasmHelpers = {}; + +(function() { + let enabled = false; + try { + enableSingleStepProfiling(); + disableSingleStepProfiling(); + enabled = true; + } catch (e) { + print(e.message); + } + WasmHelpers.isSingleStepProfilingEnabled = enabled; +})(); + +WasmHelpers._normalizeStack = (stack, preciseStacks) => { + var wasmFrameTypes = [ + {re:/^jit call to int64 wasm function$/, sub:"i64>"}, + {re:/^out-of-line coercion for jit entry arguments \(in wasm\)$/, sub:"ool>"}, + {re:/^wasm-function\[(\d+)\] \(.*\)$/, sub:"$1"}, + {re:/^(fast|slow) exit trampoline (to native )?\(in wasm\)$/, sub:"<"}, + {re:/^call to[ asm.js]? native (.*) \(in wasm\)$/, sub:"$1"}, + {re:/ \(in wasm\)$/, sub:""} + ]; + + let entryRegexps; + if (preciseStacks) { + entryRegexps = [ + {re:/^slow entry trampoline \(in wasm\)$/, sub:"!>"}, + {re:/^fast entry trampoline \(in wasm\)$/, sub:">"}, + ]; + } else { + entryRegexps = [ + {re:/^(fast|slow) entry trampoline \(in wasm\)$/, sub:">"} + ]; + } + wasmFrameTypes = entryRegexps.concat(wasmFrameTypes); + + var framesIn = stack.split(','); + var framesOut = []; + for (let frame of framesIn) { + for (let {re, sub} of wasmFrameTypes) { + if (re.test(frame)) { + framesOut.push(frame.replace(re, sub)); + break; + } + } + } + + return framesOut.join(','); +}; + +WasmHelpers._removeAdjacentDuplicates = array => { + if (array.length < 2) + return; + let i = 0; + for (let j = 1; j < array.length; j++) { + if (array[i] !== array[j]) + array[++i] = array[j]; + } + array.length = i + 1; +} + +WasmHelpers.normalizeStacks = (stacks, preciseStacks = false) => { + let observed = []; + for (let i = 0; i < stacks.length; i++) + observed[i] = WasmHelpers._normalizeStack(stacks[i], preciseStacks); + WasmHelpers._removeAdjacentDuplicates(observed); + return observed; +}; + +WasmHelpers._compareStacks = (got, expect) => { + if (got.length != expect.length) { + return false; + } + for (let i = 0; i < got.length; i++) { + if (got[i] !== expect[i]) + return false; + } + return true; +} + +WasmHelpers.assertEqImpreciseStacks = (got, expect) => { + let observed = WasmHelpers.normalizeStacks(got, /* precise */ false); + let same = WasmHelpers._compareStacks(observed, expect); + if (!same) { + if (observed.length != expect.length) { + print(`Got:\n${observed.toSource()}\nExpect:\n${expect.toSource()}`); + assertEq(observed.length, expect.length); + } + for (let i = 0; i < observed.length; i++) { + if (observed[i] !== expect[i]) { + print(`On stack ${i}, Got:\n${observed[i]}\nExpect:\n${expect[i]}`); + assertEq(observed[i], expect[i]); + } + } + } +} + +WasmHelpers.assertStackTrace = (exception, expected) => { + let callsites = exception.stack.trim().split('\n').map(line => line.split('@')[0]); + assertEq(callsites.length, expected.length); + for (let i = 0; i < callsites.length; i++) { + assertEq(callsites[i], expected[i]); + } +}; + +WasmHelpers.nextLineNumber = (n=1) => { + return +(new Error().stack).split('\n')[1].split(':')[1] + n; +} + +WasmHelpers.startProfiling = () => { + if (!WasmHelpers.isSingleStepProfilingEnabled) + return; + enableSingleStepProfiling(); +} + +WasmHelpers.endProfiling = () => { + if (!WasmHelpers.isSingleStepProfilingEnabled) + return; + return disableSingleStepProfiling(); +} + +WasmHelpers.assertEqPreciseStacks = (observed, expectedStacks) => { + if (!WasmHelpers.isSingleStepProfilingEnabled) + return null; + + observed = WasmHelpers.normalizeStacks(observed, /* precise */ true); + + for (let i = 0; i < expectedStacks.length; i++) { + if (WasmHelpers._compareStacks(observed, expectedStacks[i])) + return i; + } + + throw new Error(`no plausible stacks found, observed: ${observed.join('/')} +Expected one of: +${expectedStacks.map(stacks => stacks.join("/")).join('\n')}`); +} diff --git a/js/src/jit-test/tests/asm.js/testProfiling.js b/js/src/jit-test/tests/asm.js/testProfiling.js index 0bbcd28bcc7e..3bdeb4fac075 100644 --- a/js/src/jit-test/tests/asm.js/testProfiling.js +++ b/js/src/jit-test/tests/asm.js/testProfiling.js @@ -37,10 +37,10 @@ function assertStackContainsSeq(got, expect) for (var j = 0; j < parts.length; j++) { var frame = parts[j]; frame = frame.replace(/ \([^\)]*\)/g, ""); - frame = frame.replace(/fast FFI trampoline to native/g, "N"); + frame = frame.replace(/fast exit trampoline to native/g, "N"); frame = frame.replace(/^call to( asm.js)? native .*\(in wasm\)$/g, "N"); - frame = frame.replace(/(fast|slow) FFI trampoline/g, "<"); - frame = frame.replace(/slow entry trampoline/g, ">"); + frame = frame.replace(/(fast|slow) exit trampoline/g, "<"); + frame = frame.replace(/(fast|slow) entry trampoline/g, ">"); frame = frame.replace(/(\/[^\/,<]+)*\/testProfiling.js/g, ""); frame = frame.replace(/testBuiltinD2D/g, ""); frame = frame.replace(/testBuiltinF2F/g, ""); diff --git a/js/src/jit-test/tests/wasm/ion-args.js b/js/src/jit-test/tests/wasm/ion-args.js new file mode 100644 index 000000000000..2bfa17468995 --- /dev/null +++ b/js/src/jit-test/tests/wasm/ion-args.js @@ -0,0 +1,60 @@ +let { exports } = wasmEvalText(`(module + (func (export "i32") (result i32) (param i32) + get_local 0 + ) + + (func (export "f32") (result f32) (param f32) + get_local 0 + ) + + (func (export "f64") (result f64) (param f64) + get_local 0 + ) +)`); + +const options = getJitCompilerOptions(); +const jitThreshold = options['ion.warmup.trigger'] * 2; + +let coercions = { + i32(x) { return x|0; }, + f32(x) { return Math.fround(x); }, + f64(x) { return +x; } +} + +function call(func, coercion, arg) { + let expected; + try { + expected = coercion(arg); + } catch(e) { + expected = e.message; + } + + for (var i = jitThreshold; i --> 0;) { + try { + assertEq(func(arg), expected); + } catch(e) { + assertEq(e.message, expected); + } + } +} + +const inputs = [ + 42, + 3.5, + -0, + -Infinity, + 2**32, + true, + Symbol(), + undefined, + null, + {}, + { valueOf() { return 13.37; } }, + "bonjour" +]; + +for (let arg of inputs) { + for (let func of ['i32', 'f32', 'f64']) { + call(exports[func], coercions[func], arg); + } +} diff --git a/js/src/jit-test/tests/wasm/ion-asmjs-ctor.js b/js/src/jit-test/tests/wasm/ion-asmjs-ctor.js new file mode 100644 index 000000000000..8496fd1ff3b3 --- /dev/null +++ b/js/src/jit-test/tests/wasm/ion-asmjs-ctor.js @@ -0,0 +1,15 @@ +if (typeof evaluate === 'undefined') + quit(); + +evaluate(` + var f = (function module() { + "use asm"; + function f(i) { + i=i|0; + if (!i) + return; + } + return f; + })(); + evaluate(\`new f({}, {});\`); +`); diff --git a/js/src/jit-test/tests/wasm/ion-debugger.js b/js/src/jit-test/tests/wasm/ion-debugger.js new file mode 100644 index 000000000000..5ff2065ea9be --- /dev/null +++ b/js/src/jit-test/tests/wasm/ion-debugger.js @@ -0,0 +1,18 @@ +var g = newGlobal(); +g.parent = this; +g.eval("Debugger(parent).onExceptionUnwind = function () {};"); +lfModule = new WebAssembly.Module(wasmTextToBinary(` +(module + (export "f" $func0) + (func $func0 (result i32) + i32.const -1 + ) +) +`)); +processModule(lfModule); +function processModule(module, jscode) { + for (let i = 0; i < 2; ++i) { + imports = {} + instance = new WebAssembly.Instance(module, imports); + } +} diff --git a/js/src/jit-test/tests/wasm/ion-error-i64.js b/js/src/jit-test/tests/wasm/ion-error-i64.js new file mode 100644 index 000000000000..cb8bb5b82805 --- /dev/null +++ b/js/src/jit-test/tests/wasm/ion-error-i64.js @@ -0,0 +1,85 @@ +const options = getJitCompilerOptions(); + +// These tests need at least baseline to make sense. +if (!options['baseline.enable']) + quit(); + +const { nextLineNumber, startProfiling, endProfiling, assertEqPreciseStacks } = WasmHelpers; + +const TRIGGER = options['ion.warmup.trigger'] + 10; +const ITER = 2 * TRIGGER; +const EXCEPTION_ITER = ITER - 2; + +enableGeckoProfiling(); + +var instance = wasmEvalText(`(module + (func $add (export "add") (result i32) (param i32) (param i32) + get_local 0 + get_local 1 + i32.add + ) + + (func $addi64 (export "add64") (result i64) (param i32) (param i32) + get_local 0 + get_local 1 + call $add + i64.extend_s/i32 + ) +)`).exports; + +var callToMain; + +function main() { + var arr = [instance.add, (x,y)=>x+y]; + var arrayCallLine = nextLineNumber(6); + for (var i = 0; i < ITER; i++) { + var caught = null; + + startProfiling(); + try { + arr[i%2](i, i); + } catch(e) { + caught = e; + } + let profilingStack = endProfiling(); + + if (i === EXCEPTION_ITER - 1) { + arr[0] = instance.add64; + } else if (i === EXCEPTION_ITER) { + arr[0] = instance.add; + } + + assertEq(!!caught, i === EXCEPTION_ITER); + if (caught) { + assertEqPreciseStacks(profilingStack, [ + // Error stack: control flow is redirected to a builtin thunk + // then calling into C++ from the wasm entry before jumping to + // the wasm jit entry exception handler. + ['', '>', '<,>', 'i64>,>', '<,>', '>', ''], + [''] // the jit path wasn't taken (interpreter/baseline only). + ]); + + assertEq(caught.message, 'cannot pass i64 to or from JS'); + + let stack = caught.stack.split('\n'); + + // Which callsites appear on the error stack. + let callsites = stack.map(s => s.split('@')[0]); + assertEq(callsites[0], 'main'); + assertEq(callsites[1], ''); // global scope + + // Which line numbers appear in the error stack. + let lines = stack.map(s => s.split(':')[1]); + assertEq(+lines[0], arrayCallLine); + assertEq(+lines[1], callToMain); + } else if ((i % 2) == 0) { + assertEqPreciseStacks(profilingStack, [ + ['', '>', '0,>', '>', ''], // fast path + ['', '!>', '0,!>', '!>', ''], // slow path + ]); + } + } +} + +callToMain = nextLineNumber(); +main(); diff --git a/js/src/jit-test/tests/wasm/ion-error-ool.js b/js/src/jit-test/tests/wasm/ion-error-ool.js new file mode 100644 index 000000000000..1316d4f4c116 --- /dev/null +++ b/js/src/jit-test/tests/wasm/ion-error-ool.js @@ -0,0 +1,78 @@ +const options = getJitCompilerOptions(); + +// These tests need at least baseline to make sense. +if (!options['baseline.enable']) + quit(); + +const { assertStackTrace, startProfiling, endProfiling, assertEqPreciseStacks } = WasmHelpers; + +const TRIGGER = options['baseline.warmup.trigger'] + 10; +const ITER = 2 * TRIGGER; +const EXCEPTION_ITER = TRIGGER + 5; + +const SLOW_ENTRY_STACK = ['', '!>', '0,!>', '!>', '']; +const FAST_ENTRY_STACK = ['', '>', '0,>', '>', '']; +const FAST_OOL_ENTRY_STACK = ['', '>', '<,>', 'ool>,>', '<,>', '>', '0,>', '>', '']; +const EXCEPTION_ENTRY_STACK = ['', '>', '<,>', 'ool>,>', '<,>', '>', '']; + +enableGeckoProfiling(); + +for (let type of ['i32', 'f32', 'f64']) { + var instance = wasmEvalText(`(module + (func $add (export "add") (result ${type}) (param ${type}) (param ${type}) + get_local 0 + get_local 1 + ${type}.add + ) + )`).exports; + + function loopBody(a, b) { + var caught = null; + try { + instance.add(a, b); + } catch(e) { + assertEq(e.message, "ph34r"); + assertStackTrace(e, ['innerValueOf', 'outerValueOf', 'loopBody', 'main', '']); + caught = e; + } + assertEq(!!caught, b === EXCEPTION_ITER); + } + + var x = 0; + function main() { + let observedStacks = [0, 0, 0]; + for (var i = 0; i < ITER; i++) { + startProfiling(); + loopBody(i + 1, i + EXCEPTION_ITER + 1); + assertEqPreciseStacks(endProfiling(), [FAST_ENTRY_STACK, SLOW_ENTRY_STACK]); + + if (i === EXCEPTION_ITER) { + x = { valueOf: function innerValueOf() { throw new Error("ph34r"); }}; + } else { + x = i; + } + + startProfiling(); + loopBody({valueOf: function outerValueOf() { return x|0; }}, i); + let stack = endProfiling(); + let which = assertEqPreciseStacks(stack, [FAST_OOL_ENTRY_STACK, SLOW_ENTRY_STACK, EXCEPTION_ENTRY_STACK]); + if (which !== null) { + if (i === EXCEPTION_ITER) { + assertEq(which, 2); + } + observedStacks[which]++; + } + } + + let sum = observedStacks.reduce((acc, x) => acc + x); + assertEq(sum === 0 || sum === ITER, true); + if (sum === ITER) { + assertEq(observedStacks[0] > 0, true, "the fast entry should have been taken at least once"); + assertEq(observedStacks[2], 1, "the error path should have been taken exactly once"); + } + } + + main(); +} + +disableGeckoProfiling(); diff --git a/js/src/jit-test/tests/wasm/ion-error-trace.js b/js/src/jit-test/tests/wasm/ion-error-trace.js new file mode 100644 index 000000000000..8e9f15e67f28 --- /dev/null +++ b/js/src/jit-test/tests/wasm/ion-error-trace.js @@ -0,0 +1,126 @@ +/////////////////////////////////////////////////////////////////////////////// +// FIRST TEST ///////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +(function() { + +function debug() { + throw new Error('gotcha'); +} + +var imports = { + numCalls:0, + main: { + f() { + debug(); + } + } +}; + +var instance = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(`(module + (import $main "main" "f" (func)) + (func $lol (export "add") (result i32) (param i32) (param i32) + get_local 0 + get_local 1 + call $add + ) + (func $add (result i32) (param i32) (param i32) + get_local 0 + i32.const 5000 + i32.eq + if + call $main + end + + get_local 0 + get_local 1 + i32.add + ) +)`)), imports).exports; + +function loopBody(i) { + var caught = null; + try { + assertEq(instance.add(i, i), 2 * i); + } catch(e) { + // TODO check stack trace + print(e.stack); + caught = e; + } + assertEq(!!caught, i === 5000); +} + +function main() { + for (var i = 0; i < 100000; i++) { + loopBody(i); + } + assertEq(i, 100000); +} + +main(); + +})(); + +/////////////////////////////////////////////////////////////////////////////// +// SECOND TEST //////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +(function() { + +function debug() { + gc(); +} + +var imports = { + numCalls:0, + main: { + f() { + debug(); + } + } +}; + +var instance = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(`(module + (import $main "main" "f" (func)) + (func $lol (export "add") (result i32) (param i32) (param i32) + get_local 0 + get_local 1 + call $add + ) + (func $add (result i32) (param i32) (param i32) + get_local 0 + i32.const 5000 + i32.eq + if + call $main + unreachable + end + + get_local 0 + get_local 1 + i32.add + ) +)`)), imports).exports; + +function loopBody(i) { + var caught = null; + try { + assertEq(instance.add(i, i), 2 * i); + } catch(e) { + // TODO check stack trace + print(e.stack); + caught = e; + } + assertEq(!!caught, i === 5000); +} + +function main() { + for (var i = 0; i < 100000; i++) { + loopBody(i); + } + assertEq(i, 100000); +} + +main(); + +})(); diff --git a/js/src/jit-test/tests/wasm/ion-gc.js b/js/src/jit-test/tests/wasm/ion-gc.js new file mode 100644 index 000000000000..104fdc310320 --- /dev/null +++ b/js/src/jit-test/tests/wasm/ion-gc.js @@ -0,0 +1,55 @@ +const options = getJitCompilerOptions(); + +// These tests need at least baseline to make sense. +if (!options['baseline.enable']) + quit(); + +const TRIGGER = options['baseline.warmup.trigger'] + 10; +const ITER = 2 * TRIGGER; +const EXCEPTION_ITER = TRIGGER + 5; + +for (let type of ['i32', 'f32', 'f64']) { + var instance = wasmEvalText(`(module + (func $add (export "add") (result ${type}) (param ${type}) (param ${type}) + get_local 0 + get_local 1 + ${type}.add + ) + )`).exports; + + function loopBody(a, b) { + var caught = null; + try { + instance.add(a, b); + } catch(e) { + caught = e; + } + assertEq(!!caught, b === EXCEPTION_ITER); + } + + var x = 0; + function main() { + for (var i = 0; i <= EXCEPTION_ITER; i++) { + loopBody(i + 1, i + EXCEPTION_ITER + 1); + + let otherArg = { valueOf() { return i|0; } }; + + if (i === EXCEPTION_ITER) { + x = { valueOf: function innerValueOf() { + // Supress callee. + instance = null; + // Suppress other arguments. + otherArg = null; + gc(); + return 42; + }}; + } else { + x = i; + } + + loopBody({valueOf: function outerValueOf() { return x|0; }}, otherArg); + } + } + + main(); +} diff --git a/js/src/jit-test/tests/wasm/ion2wasm.js b/js/src/jit-test/tests/wasm/ion2wasm.js new file mode 100644 index 000000000000..0b16f4c7eb92 --- /dev/null +++ b/js/src/jit-test/tests/wasm/ion2wasm.js @@ -0,0 +1,174 @@ +var ITERATIONS = 10; +var INNER_ITERATIONS = 100; + +let instance = wasmEvalText(`(module + (func (export "add") (result i32) (param i32) (param i32) + get_local 0 + get_local 1 + i32.add + ) + + (func (export "no_arg") (result i32) + i32.const 42 + i32.const 58 + i32.add + ) + + (global $g (mut i32) (i32.const 0)) + + (func (export "set_global_one") (param i32) + get_local 0 + set_global $g + ) + + (func (export "set_global_two") (param i32) (param i32) + get_local 0 + get_local 1 + i32.add + set_global $g + ) + + (func (export "glob") (result i32) + get_global $g + ) +)`).exports; + +function run(name, func) { + for (let i = ITERATIONS; i --> 0;) { + func(); + } +} + +function testCallKnown() { + for (let i = 0; i < INNER_ITERATIONS; i++) { + assertEq(instance.add(i, i + 1), 2*i + 1); + } +} + +function testCallKnownRectifying() { + for (let i = 0; i < INNER_ITERATIONS; i++) { + assertEq(instance.add(i + 1), i+1); + } +} + +function jsAdd(x, y) { + return (x|0) + (y|0) | 0; +} + +function testCallGeneric() { + var arr = [instance.add, jsAdd]; + for (let i = 0; i < INNER_ITERATIONS; i++) { + assertEq(arr[i%2](i, i+1), 2*i + 1); + } +} + +function testCallGenericRectifying() { + var arr = [instance.add, jsAdd]; + for (let i = 0; i < INNER_ITERATIONS; i++) { + assertEq(arr[i%2](i+1), i + 1); + } +} + +function testCallScriptedGetter() { + var obj = {}; + Object.defineProperty(obj, 'x', { + get: instance.no_arg + }); + for (let i = 0; i < INNER_ITERATIONS; i++) { + assertEq(obj.x, 100); + } +} + +function testCallScriptedGetterRectifying() { + var obj = {}; + Object.defineProperty(obj, 'x', { + // Missing two arguments. + get: instance.add + }); + for (let i = 0; i < INNER_ITERATIONS; i++) { + assertEq(obj.x, 0); + } +} + +function testCallScriptedSetter() { + var obj = {}; + Object.defineProperty(obj, 'x', { + set: instance.set_global_one + }); + for (let i = 0; i < INNER_ITERATIONS; i++) { + obj.x = i; + } + assertEq(instance.glob(), INNER_ITERATIONS-1); +} + +function testCallScriptedSetterRectifying() { + var obj = {}; + Object.defineProperty(obj, 'x', { + set: instance.set_global_two + }); + for (let i = 0; i < INNER_ITERATIONS; i++) { + obj.x = i; + } + assertEq(instance.glob(), INNER_ITERATIONS-1); +} + +function testFunctionApplyArray() { + for (let i = 0; i < INNER_ITERATIONS; i++) { + assertEq(instance.add.apply(null, [i, i + 1]), 2*i+1); + } +} + +function testFunctionApplyArrayRectifying() { + for (let i = 0; i < INNER_ITERATIONS; i++) { + assertEq(instance.add.apply(null, [i + 1]), i+1); + } +} + +function testFunctionApplyArgs() { + function wrapper() { + assertEq(instance.add.apply(null, arguments), 2*arguments[0]+1); + } + for (let i = 0; i < INNER_ITERATIONS; i++) { + wrapper(i, i + 1); + } +} + +function testFunctionApplyArgsRectifying() { + function wrapper() { + assertEq(instance.add.apply(null, arguments), arguments[0]); + } + for (let i = 0; i < INNER_ITERATIONS; i++) { + wrapper(i + 1); + } +} + +function testFunctionCall() { + for (let i = 0; i < INNER_ITERATIONS; i++) { + assertEq(instance.add.call(null, i, i + 1), 2*i+1); + } +} + +function testFunctionCallRectifying() { + for (let i = 0; i < INNER_ITERATIONS; i++) { + assertEq(instance.add.call(null, i + 1), i+1); + } +} + +run('call known', testCallKnown); +run('call known rectifying', testCallKnownRectifying); + +run('call generic', testCallGeneric); +run('call generic rectifying', testCallGenericRectifying); + +run('scripted getter', testCallScriptedGetter); +run('scripted getter rectifiying', testCallScriptedGetterRectifying); +run('scripted setter', testCallScriptedSetter); +run('scripted setter rectifiying', testCallScriptedSetterRectifying); + +run('function.apply array', testFunctionApplyArray); +run('function.apply array rectifying', testFunctionApplyArrayRectifying); +run('function.apply args', testFunctionApplyArgs); +run('function.apply args rectifying', testFunctionApplyArgsRectifying); + +run('function.call', testFunctionCall); +run('function.call rectifying', testFunctionCallRectifying); diff --git a/js/src/jit-test/tests/wasm/profiling.js b/js/src/jit-test/tests/wasm/profiling.js index 85bfc0443128..73a8a52878b8 100644 --- a/js/src/jit-test/tests/wasm/profiling.js +++ b/js/src/jit-test/tests/wasm/profiling.js @@ -1,78 +1,20 @@ -try { - enableSingleStepProfiling(); - disableSingleStepProfiling(); -} catch(e) { - // Single step profiling not supported here. +if (!WasmHelpers.isSingleStepProfilingEnabled) quit(); -} const Module = WebAssembly.Module; const Instance = WebAssembly.Instance; const Table = WebAssembly.Table; -function normalize(stack) -{ - var wasmFrameTypes = [ - {re:/^slow entry trampoline \(in wasm\)$/, sub:">"}, - {re:/^wasm-function\[(\d+)\] \(.*\)$/, sub:"$1"}, - {re:/^(fast|slow) FFI trampoline (to native )?\(in wasm\)$/, sub:"<"}, - {re:/^call to[ asm.js]? native (.*) \(in wasm\)$/, sub:"$1"}, - {re:/ \(in wasm\)$/, sub:""} - ]; +const { assertEqImpreciseStacks, startProfiling, endProfiling } = WasmHelpers; - var framesIn = stack.split(','); - var framesOut = []; - for (let frame of framesIn) { - for (let {re, sub} of wasmFrameTypes) { - if (re.test(frame)) { - framesOut.push(frame.replace(re, sub)); - break; - } - } - } - - return framesOut.join(','); -} - -function removeAdjacentDuplicates(array) { - if (array.length < 2) - return; - let i = 0; - for (let j = 1; j < array.length; j++) { - if (array[i] !== array[j]) - array[++i] = array[j]; - } - array.length = i + 1; -} - -function assertEqStacks(got, expect) -{ - for (let i = 0; i < got.length; i++) - got[i] = normalize(got[i]); - - removeAdjacentDuplicates(got); - - if (got.length != expect.length) { - print(`Got:\n${got.toSource()}\nExpect:\n${expect.toSource()}`); - assertEq(got.length, expect.length); - } - - for (let i = 0; i < got.length; i++) { - if (got[i] !== expect[i]) { - print(`On stack ${i}, Got:\n${got[i]}\nExpect:\n${expect[i]}`); - assertEq(got[i], expect[i]); - } - } -} - -function test(code, importObj, expect) +function test(code, importObj, expectedStacks) { enableGeckoProfiling(); var f = wasmEvalText(code, importObj).exports[""]; - enableSingleStepProfiling(); + startProfiling(); f(); - assertEqStacks(disableSingleStepProfiling(), expect); + assertEqImpreciseStacks(endProfiling(), expectedStacks); disableGeckoProfiling(); } @@ -137,7 +79,8 @@ if (getBuildConfiguration()["arm-simulator"]) { ) )`, this, - ["", ">", "0,>", "<,0,>", `i64.${op},0,>`, "<,0,>", "0,>", ">", ""]); + ["", ">", "0,>", "<,0,>", `i64.${op},0,>`, "<,0,>", "0,>", ">", ""], + ); } } @@ -149,7 +92,8 @@ test(`(module ) )`, this, -["", ">", "0,>", "<,0,>", "current_memory,0,>", "<,0,>", "0,>", ">", ""]); +["", ">", "0,>", "<,0,>", "current_memory,0,>", "<,0,>", "0,>", ">", ""], +); // grow_memory is a callout. test(`(module @@ -160,7 +104,8 @@ test(`(module ) )`, this, -["", ">", "0,>", "<,0,>", "grow_memory,0,>", "<,0,>", "0,>", ">", ""]); +["", ">", "0,>", "<,0,>", "grow_memory,0,>", "<,0,>", "0,>", ">", ""], +); // A few math builtins. for (let type of ['f32', 'f64']) { @@ -184,7 +129,7 @@ for (let type of ['f32', 'f64']) { var f = wasmEvalText(code).exports[""]; enableSingleStepProfiling(); assertThrowsInstanceOf(f, error); - assertEqStacks(disableSingleStepProfiling(), expect); + assertEqImpreciseStacks(disableSingleStepProfiling(), expect); disableGeckoProfiling(); } @@ -230,7 +175,7 @@ for (let type of ['f32', 'f64']) { enableGeckoProfiling(); enableSingleStepProfiling(); assertEq(e.tbl.get(0)(), 42); - assertEqStacks(disableSingleStepProfiling(), ["", ">", "0,>", ">", ""]); + assertEqImpreciseStacks(disableSingleStepProfiling(), ["", ">", "0,>", ">", ""]); disableGeckoProfiling(); assertEq(e.foo(), 42); @@ -240,7 +185,7 @@ for (let type of ['f32', 'f64']) { enableGeckoProfiling(); enableSingleStepProfiling(); assertEq(e.tbl.get(1)(), 13); - assertEqStacks(disableSingleStepProfiling(), ["", ">", "1,>", ">", ""]); + assertEqImpreciseStacks(disableSingleStepProfiling(), ["", ">", "1,>", ">", ""]); disableGeckoProfiling(); assertEq(e.tbl.get(0)(), 42); @@ -251,7 +196,7 @@ for (let type of ['f32', 'f64']) { enableSingleStepProfiling(); assertEq(e.foo(), 42); assertEq(e.tbl.get(1)(), 13); - assertEqStacks(disableSingleStepProfiling(), ["", ">", "0,>", ">", "", ">", "1,>", ">", ""]); + assertEqImpreciseStacks(disableSingleStepProfiling(), ["", ">", "0,>", ">", "", ">", "1,>", ">", ""]); disableGeckoProfiling(); var e2 = wasmEvalText(` @@ -267,19 +212,19 @@ for (let type of ['f32', 'f64']) { enableGeckoProfiling(); enableSingleStepProfiling(); assertEq(e2.baz(0), 42); - assertEqStacks(disableSingleStepProfiling(), ["", ">", "1,>", "0,1,>", "1,>", ">", ""]); + assertEqImpreciseStacks(disableSingleStepProfiling(), ["", ">", "1,>", "0,1,>", "1,>", ">", ""]); disableGeckoProfiling(); enableGeckoProfiling(); enableSingleStepProfiling(); assertEq(e2.baz(1), 13); - assertEqStacks(disableSingleStepProfiling(), ["", ">", "1,>", "1,1,>", "1,>", ">", ""]); + assertEqImpreciseStacks(disableSingleStepProfiling(), ["", ">", "1,>", "1,1,>", "1,>", ">", ""]); disableGeckoProfiling(); enableGeckoProfiling(); enableSingleStepProfiling(); assertEq(e2.baz(2), 99); - assertEqStacks(disableSingleStepProfiling(), ["", ">", "1,>", "0,1,>", "1,>", ">", ""]); + assertEqImpreciseStacks(disableSingleStepProfiling(), ["", ">", "1,>", "0,1,>", "1,>", ">", ""]); disableGeckoProfiling(); })(); @@ -301,7 +246,7 @@ for (let type of ['f32', 'f64']) { enableGeckoProfiling(); enableSingleStepProfiling(); assertEq(e2.bar(), 42); - assertEqStacks(disableSingleStepProfiling(), ["", ">", "1,>", "0,1,>", "1,>", ">", ""]); + assertEqImpreciseStacks(disableSingleStepProfiling(), ["", ">", "1,>", "0,1,>", "1,>", ">", ""]); disableGeckoProfiling(); assertEq(e2.bar(), 42); @@ -311,7 +256,7 @@ for (let type of ['f32', 'f64']) { var e4 = new Instance(m2, {a:e3}).exports; enableSingleStepProfiling(); assertEq(e4.bar(), 42); - assertEqStacks(disableSingleStepProfiling(), ["", ">", "1,>", "0,1,>", "1,>", ">", ""]); + assertEqImpreciseStacks(disableSingleStepProfiling(), ["", ">", "1,>", "0,1,>", "1,>", ">", ""]); disableGeckoProfiling(); assertEq(e4.bar(), 42); })(); @@ -377,7 +322,7 @@ for (let type of ['f32', 'f64']) { // Test normal conditions. enableSingleStepProfiling(); assertEq(i.foo(0), 42); - assertEqStacks(disableSingleStepProfiling(), ["", ">", "2,>", "<,2,>", + assertEqImpreciseStacks(disableSingleStepProfiling(), ["", ">", "2,>", "<,2,>", // Losing stack information while the JIT func prologue sets profiler // virtual FP. "", @@ -394,7 +339,7 @@ for (let type of ['f32', 'f64']) { // Test rectifier frame. enableSingleStepProfiling(); assertEq(i.id(100), 100); - assertEqStacks(disableSingleStepProfiling(), ["", ">", "3,>", "<,3,>", + assertEqImpreciseStacks(disableSingleStepProfiling(), ["", ">", "3,>", "<,3,>", // Rectifier frame time is spent here (lastProfilingFrame has not been // set). "", @@ -409,7 +354,7 @@ for (let type of ['f32', 'f64']) { enableSingleStepProfiling(); assertEq(i.foo(1337), -(2**31)); - assertEqStacks(disableSingleStepProfiling(), ["", ">", "2,>", "<,2,>", "", "<,2,>", "", + assertEqImpreciseStacks(disableSingleStepProfiling(), ["", ">", "2,>", "<,2,>", "", "<,2,>", "", // Back into the jit exit (frame info has been recovered). // Inline conversion fails, we skip to the OOL path, call from there // and get back to the jit exit.