diff --git a/test/mjsunit/tools/processor-bigint.mjs b/test/mjsunit/tools/processor-bigint.mjs new file mode 100644 index 0000000000..8b6cd54e94 --- /dev/null +++ b/test/mjsunit/tools/processor-bigint.mjs @@ -0,0 +1,59 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Flags: --logfile='+' --log --log-maps --log-ic --log-code +// Flags: --log-function-events --no-predictable + +import { Processor } from "../../../tools/system-analyzer/processor.mjs"; + +// log code start +function doWork() { + let array = []; + for (let i = 0; i < 500; i++) { + doWorkStep(i, array); + } + let sum = 0; + for (let i = 0; i < 500; i++) { + sum += array[i]["property" + i]; + } + return sum; +} + +function doWorkStep(i, array) { + const obj = { + ["property" + i]: i, + }; + array.push(obj); + obj.custom1 = 1; + obj.custom2 = 2; +} + +const result = doWork(); + // log code end + +const logString = d8.log.getAndStop(); +assertTrue(logString.length > 0); +const useBigInts = true; +const processor = new Processor(useBigInts); +await processor.processChunk(logString); +await processor.finalize(); + +const maps = processor.mapTimeline; +const ics = processor.icTimeline; +const scripts = processor.scripts; + +(function testResults() { + assertEquals(result, 124750); + assertTrue(maps.length > 0); + assertTrue(ics.length > 0); + assertTrue(scripts.length > 0); +})(); + +(function testIcKeys() { + const keys = new Set(); + ics.forEach(ic => keys.add(ic.key)); + assertTrue(keys.has("custom1")); + assertTrue(keys.has("custom2")); + assertTrue(keys.has("push")); +})(); diff --git a/tools/codemap.mjs b/tools/codemap.mjs index 57040635c3..985e721c34 100644 --- a/tools/codemap.mjs +++ b/tools/codemap.mjs @@ -65,6 +65,12 @@ export class CodeMap { */ pages_ = new Set(); + constructor(useBigInt=false) { + this.useBigInt = useBigInt; + this.kPageSize = useBigInt ? BigInt(kPageSize) : kPageSize; + this.kOne = useBigInt ? 1n : 1; + this.kZero = useBigInt ? 0n : 0; + } /** * Adds a code entry that might overlap with static code (e.g. for builtins). @@ -73,7 +79,7 @@ export class CodeMap { * @param {CodeEntry} codeEntry Code entry object. */ addAnyCode(start, codeEntry) { - const pageAddr = (start / kPageSize) | 0; + const pageAddr = (start / this.kPageSize) | this.kZero; if (!this.pages_.has(pageAddr)) return this.addCode(start, codeEntry); // We might have loaded static code (builtins, bytecode handlers) // and we get more information later in v8.log with code-creation events. @@ -147,8 +153,8 @@ export class CodeMap { * @private */ markPages_(start, end) { - for (let addr = start; addr <= end; addr += kPageSize) { - this.pages_.add((addr / kPageSize) | 0); + for (let addr = start; addr <= end; addr += this.kPageSize) { + this.pages_.add((addr / this.kPageSize) | this.kZero); } } @@ -157,13 +163,13 @@ export class CodeMap { */ deleteAllCoveredNodes_(tree, start, end) { const to_delete = []; - let addr = end - 1; + let addr = end - this.kOne; while (addr >= start) { const node = tree.findGreatestLessThan(addr); if (node === null) break; const start2 = node.key, end2 = start2 + node.value.size; if (start2 < end && start < end2) to_delete.push(start2); - addr = start2 - 1; + addr = start2 - this.kOne; } for (let i = 0, l = to_delete.length; i < l; ++i) tree.remove(to_delete[i]); } @@ -191,7 +197,7 @@ export class CodeMap { * @param {number} addr Address. */ findAddress(addr) { - const pageAddr = (addr / kPageSize) | 0; + const pageAddr = (addr / this.kPageSize) | this.kZero; if (this.pages_.has(pageAddr)) { // Static code entries can contain "holes" of unnamed code. // In this case, the whole library is assigned to this address. diff --git a/tools/logreader.mjs b/tools/logreader.mjs index e4d8b4d057..339017c488 100644 --- a/tools/logreader.mjs +++ b/tools/logreader.mjs @@ -35,6 +35,16 @@ export function parseString(field) { return field }; export const parseVarArgs = 'parse-var-args'; +// Checks fields for numbers that are not safe integers. Returns true if any are +// found. +function containsUnsafeInts(fields) { + for (let i = 0; i < fields.length; i++) { + let field = fields[i]; + if ('number' == typeof(field) && !Number.isSafeInteger(field)) return true; + } + return false; +} + /** * Base class for processing log files. * @@ -44,7 +54,7 @@ export const parseVarArgs = 'parse-var-args'; * @constructor */ export class LogReader { - constructor(timedRange=false, pairwiseTimedRange=false) { + constructor(timedRange=false, pairwiseTimedRange=false, useBigInt=false) { this.dispatchTable_ = new Map(); this.timedRange_ = timedRange; this.pairwiseTimedRange_ = pairwiseTimedRange; @@ -54,6 +64,11 @@ export class LogReader { // Variables for tracking of 'current-time' log entries: this.hasSeenTimerMarker_ = false; this.logLinesSinceLastTimerMarker_ = []; + // Flag to parse all numeric fields as BigInt to avoid arithmetic errors + // caused by memory addresses being greater than MAX_SAFE_INTEGER + this.useBigInt = useBigInt; + this.parseFrame = useBigInt ? BigInt : parseInt; + this.hasSeenUnsafeIntegers = false; } /** @@ -180,11 +195,11 @@ export class LogReader { const firstChar = frame[0]; if (firstChar === '+' || firstChar === '-') { // An offset from the previous frame. - prevFrame += parseInt(frame, 16); + prevFrame += this.parseFrame(frame); fullStack.push(prevFrame); // Filter out possible 'overflow' string. } else if (firstChar !== 'o') { - fullStack.push(parseInt(frame, 16)); + fullStack.push(this.parseFrame(frame)); } else { console.error(`Dropping unknown tick frame: ${frame}`); } @@ -216,6 +231,12 @@ export class LogReader { parsedFields[i] = parser(fields[1 + i]); } } + if (!this.useBigInt) { + if (!this.hasSeenUnsafeIntegers && containsUnsafeInts(parsedFields)) { + console.warn(`Log line containts unsafe integers: ${fields}`); + this.hasSeenUnsafeIntegers = true; + } + } // Run the processor. await dispatch.processor(...parsedFields); } diff --git a/tools/profile.mjs b/tools/profile.mjs index 28490ea72e..5ca36b80d1 100644 --- a/tools/profile.mjs +++ b/tools/profile.mjs @@ -305,7 +305,6 @@ const kProfileOperationTick = 2; * @constructor */ export class Profile { - codeMap_ = new CodeMap(); topDownTree_ = new CallTree(); bottomUpTree_ = new CallTree(); c_entries_ = {__proto__:null}; @@ -313,6 +312,11 @@ export class Profile { urlToScript_ = new Map(); warnings = new Set(); + constructor(useBigInt=false) { + this.useBigInt = useBigInt; + this.codeMap_ = new CodeMap(useBigInt); + } + serializeVMSymbols() { let result = this.codeMap_.getAllStaticEntriesWithAddresses(); result.concat(this.codeMap_.getAllLibraryEntriesWithAddresses()) @@ -513,7 +517,7 @@ export class Profile { // it is safe to put them in a single code map. let func = this.codeMap_.findDynamicEntryByStartAddress(funcAddr); if (func === null) { - func = new FunctionEntry(name); + func = new FunctionEntry(name, this.useBigInt); this.codeMap_.addCode(funcAddr, func); } else if (func.name !== name) { // Function object has been overwritten with a new one. @@ -961,8 +965,8 @@ class FunctionEntry extends CodeEntry { /** @type {Set} */ _codeEntries = new Set(); - constructor(name) { - super(0, name); + constructor(name, useBigInt=false) { + super(useBigInt ? 0n : 0, name); const index = name.lastIndexOf(' '); this.functionName = 1 <= index ? name.substring(0, index) : ''; } diff --git a/tools/system-analyzer/log/tick.mjs b/tools/system-analyzer/log/tick.mjs index 3c16cc6207..023a881117 100644 --- a/tools/system-analyzer/log/tick.mjs +++ b/tools/system-analyzer/log/tick.mjs @@ -42,7 +42,7 @@ export class TickLogEntry extends LogEntry { return 'Idle'; } const topOfStack = processedStack[0]; - if (typeof topOfStack === 'number') { + if (typeof topOfStack === 'number' || typeof topOfStack === 'bigint') { // TODO(cbruni): Handle VmStack and native ticks better. return 'Other'; } diff --git a/tools/system-analyzer/processor.mjs b/tools/system-analyzer/processor.mjs index 15eccd70c9..c4cd1f9915 100644 --- a/tools/system-analyzer/processor.mjs +++ b/tools/system-analyzer/processor.mjs @@ -47,7 +47,6 @@ class AsyncConsumer { } export class Processor extends LogReader { - _profile = new Profile(); _codeTimeline = new Timeline(); _deoptTimeline = new Timeline(); _icTimeline = new Timeline(); @@ -70,12 +69,16 @@ export class Processor extends LogReader { MAJOR_VERSION = 7; MINOR_VERSION = 6; - constructor() { - super(); + constructor(useBigInt = false) { + super(false, false, useBigInt); + this.useBigInt = useBigInt; + this.kZero = useBigInt ? 0n : 0; + this.parseAddress = useBigInt ? BigInt : parseInt; this._chunkConsumer = new AsyncConsumer((chunk) => this._processChunk(chunk)); + this._profile = new Profile(useBigInt); const propertyICParser = [ - parseInt, parseInt, parseInt, parseInt, parseString, parseString, + this.parseAddress, parseInt, parseInt, parseInt, parseString, parseString, parseString, parseString, parseString, parseString ]; this.setDispatchTable({ @@ -88,46 +91,47 @@ export class Processor extends LogReader { processor: this.processV8Version, }, 'shared-library': { - parsers: [parseString, parseInt, parseInt, parseInt], + parsers: [ + parseString, this.parseAddress, this.parseAddress, this.parseAddress + ], processor: this.processSharedLibrary.bind(this), isAsync: true, }, 'code-creation': { parsers: [ - parseString, parseInt, parseInt, parseInt, parseInt, parseString, - parseVarArgs + parseString, parseInt, parseInt, this.parseAddress, this.parseAddress, + parseString, parseVarArgs ], processor: this.processCodeCreation }, 'code-deopt': { parsers: [ - parseInt, parseInt, parseInt, parseInt, parseInt, parseString, - parseString, parseString + parseInt, parseInt, this.parseAddress, parseInt, parseInt, + parseString, parseString, parseString ], processor: this.processCodeDeopt }, - 'code-move': - {parsers: [parseInt, parseInt], processor: this.processCodeMove}, - 'code-delete': {parsers: [parseInt], processor: this.processCodeDelete}, + 'code-move': { + parsers: [this.parseAddress, this.parseAddress], + processor: this.processCodeMove + }, + 'code-delete': + {parsers: [this.parseAddress], processor: this.processCodeDelete}, 'code-source-info': { parsers: [ - parseInt, parseInt, parseInt, parseInt, parseString, parseString, - parseString + this.parseAddress, parseInt, parseInt, parseInt, parseString, + parseString, parseString ], processor: this.processCodeSourceInfo }, 'code-disassemble': { - parsers: [ - parseInt, - parseString, - parseString, - ], + parsers: [this.parseAddress, parseString, parseString], processor: this.processCodeDisassemble }, 'feedback-vector': { parsers: [ - parseInt, parseString, parseInt, parseInt, parseString, parseString, - parseInt, parseInt, parseString + parseInt, parseString, parseInt, this.parseAddress, parseString, + parseString, parseInt, parseInt, parseString ], processor: this.processFeedbackVector }, @@ -135,11 +139,15 @@ export class Processor extends LogReader { parsers: [parseInt, parseString, parseString], processor: this.processScriptSource }, - 'sfi-move': - {parsers: [parseInt, parseInt], processor: this.processFunctionMove}, + 'sfi-move': { + parsers: [this.parseAddress, this.parseAddress], + processor: this.processFunctionMove + }, 'tick': { - parsers: - [parseInt, parseInt, parseInt, parseInt, parseInt, parseVarArgs], + parsers: [ + this.parseAddress, parseInt, parseInt, this.parseAddress, parseInt, + parseVarArgs + ], processor: this.processTick }, 'active-runtime-timer': undefined, @@ -157,8 +165,8 @@ export class Processor extends LogReader { {parsers: [parseInt, parseString], processor: this.processMapCreate}, 'map': { parsers: [ - parseString, parseInt, parseString, parseString, parseInt, parseInt, - parseInt, parseString, parseString + parseString, parseInt, parseString, parseString, this.parseAddress, + parseInt, parseInt, parseString, parseString ], processor: this.processMap }, @@ -352,7 +360,7 @@ export class Processor extends LogReader { let profilerEntry; let stateName = ''; if (maybe_func.length) { - const funcAddr = parseInt(maybe_func[0]); + const funcAddr = this.parseAddress(maybe_func[0]); stateName = maybe_func[1] ?? ''; const state = Profile.parseState(maybe_func[1]); profilerEntry = this._profile.addFuncCode( @@ -404,7 +412,7 @@ export class Processor extends LogReader { optimization_tier, invocation_count, profiler_ticks, fbv_string) { const profCodeEntry = this._profile.findEntry(instructionStart); if (!profCodeEntry) { - console.warn('Didn\'t find code for FBV', {fbv, instructionStart}); + console.warn('Didn\'t find code for FBV', {fbv_string, instructionStart}); return; } const fbv = new FeedbackVectorEntry( @@ -439,13 +447,13 @@ export class Processor extends LogReader { // that a callback calls itself. Instead we use tos_or_external_callback, // as simply resetting PC will produce unaccounted ticks. pc = tos_or_external_callback; - tos_or_external_callback = 0; + tos_or_external_callback = this.kZero; } else if (tos_or_external_callback) { // Find out, if top of stack was pointing inside a JS function // meaning that we have encountered a frameless invocation. const funcEntry = this._profile.findEntry(tos_or_external_callback); if (!funcEntry?.isJSFunction?.()) { - tos_or_external_callback = 0; + tos_or_external_callback = this.kZero; } } const entryStack = this._profile.recordTick(