diff options
Diffstat (limited to 'deps/v8/tools')
104 files changed, 5581 insertions, 4266 deletions
diff --git a/deps/v8/tools/clusterfuzz/BUILD.gn b/deps/v8/tools/clusterfuzz/BUILD.gn index d75e6f9687..54e4fded96 100644 --- a/deps/v8/tools/clusterfuzz/BUILD.gn +++ b/deps/v8/tools/clusterfuzz/BUILD.gn @@ -16,7 +16,7 @@ if (v8_correctness_fuzzer) { "v8_mock.js", "v8_mock_archs.js", "v8_mock_webassembly.js", - "v8_sanity_checks.js", + "v8_smoke_tests.js", "v8_suppressions.js", "v8_suppressions.py", ] diff --git a/deps/v8/tools/clusterfuzz/js_fuzzer/README.md b/deps/v8/tools/clusterfuzz/js_fuzzer/README.md index a537ad7a62..71a1140fe1 100644 --- a/deps/v8/tools/clusterfuzz/js_fuzzer/README.md +++ b/deps/v8/tools/clusterfuzz/js_fuzzer/README.md @@ -14,8 +14,11 @@ contained binary) out of this. You need to intall nodejs and npm. Run `npm install` in this directory. ## Fuzzing DB -This fuzzer requires a fuzzing DB. To build one, get the latest web_tests.zip -from `gs://clusterfuzz-data/web_tests.zip` and run: +This fuzzer requires a fuzzing DB. To build one, get the latest `web_tests.zip` +from [gs://clusterfuzz-data/web_tests.zip]( +https://storage.cloud.google.com/clusterfuzz-data/web_tests.zip) and unzip it +(note https://crbug.com/v8/10891 for making this data publicly available). +Then run: ```bash $ mkdir db @@ -90,7 +93,7 @@ $ workdir/output The `app_dir` folder can be a symlink or should contain the bundled version of `d8` with all files required for execution. -The copy the packaged `ochang_js_fuzzer` executable and the `db` folder +Copy the packaged `ochang_js_fuzzer` executable and the `db` folder to the `fuzzer` directory or use a symlink. The `input` directory is the root folder of the corpus, i.e. pointing to the unzipped data of `gs://clusterfuzz-data/web_tests.zip`. diff --git a/deps/v8/tools/clusterfuzz/js_fuzzer/exceptions.js b/deps/v8/tools/clusterfuzz/js_fuzzer/exceptions.js index 41255aa4d9..f981b82efc 100644 --- a/deps/v8/tools/clusterfuzz/js_fuzzer/exceptions.js +++ b/deps/v8/tools/clusterfuzz/js_fuzzer/exceptions.js @@ -113,6 +113,7 @@ const DISALLOWED_FLAGS = [ // Disallowed due to false positives. '--check-handle-count', + '--correctness-fuzzer-suppressions', '--expose-debug-as', '--expose-natives-as', '--expose-trigger-failure', diff --git a/deps/v8/tools/clusterfuzz/js_fuzzer/run.js b/deps/v8/tools/clusterfuzz/js_fuzzer/run.js index 3712172d9f..29049e326c 100644 --- a/deps/v8/tools/clusterfuzz/js_fuzzer/run.js +++ b/deps/v8/tools/clusterfuzz/js_fuzzer/run.js @@ -163,9 +163,11 @@ function main() { app_name = app_name.substr(0, app_name.length - 4); } - if (app_name === 'd8' || app_name === 'v8_foozzie.py') { - // V8 supports running the raw d8 executable or the differential fuzzing - // harness 'foozzie'. + if (app_name === 'd8' || + app_name === 'v8_simple_inspector_fuzzer' || + app_name === 'v8_foozzie.py') { + // V8 supports running the raw d8 executable, the inspector fuzzer or + // the differential fuzzing harness 'foozzie'. settings.engine = 'V8'; } else if (app_name === 'ch') { settings.engine = 'chakra'; diff --git a/deps/v8/tools/clusterfuzz/js_fuzzer/tools/run_one.py b/deps/v8/tools/clusterfuzz/js_fuzzer/tools/run_one.py index 7762cb2c94..7886c30b95 100644 --- a/deps/v8/tools/clusterfuzz/js_fuzzer/tools/run_one.py +++ b/deps/v8/tools/clusterfuzz/js_fuzzer/tools/run_one.py @@ -22,6 +22,8 @@ BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) FOOZZIE = os.path.join(BASE_PATH, 'workdir', 'app_dir', 'v8_foozzie.py') TEST_CASES = os.path.join(BASE_PATH, 'workdir', 'output') +assert os.path.exists(FOOZZIE) + # Output pattern from foozzie.py when it finds a failure. FAILURE_RE = re.compile( r'# V8 correctness failure.' diff --git a/deps/v8/tools/clusterfuzz/js_fuzzer/tools/workbench.py b/deps/v8/tools/clusterfuzz/js_fuzzer/tools/workbench.py index 52a84bfca3..ee371f5482 100644 --- a/deps/v8/tools/clusterfuzz/js_fuzzer/tools/workbench.py +++ b/deps/v8/tools/clusterfuzz/js_fuzzer/tools/workbench.py @@ -37,8 +37,11 @@ RUN_ONE = os.path.join(BASE_PATH, 'tools', 'run_one.py') os.chdir(BASE_PATH) -assert not os.path.exists(TEST_CASES) -os.makedirs(TEST_CASES) +if os.path.exists(TEST_CASES): + if not os.path.isdir(TEST_CASES) or os.listdir(TEST_CASES): + sys.exit("'output' must be an empty directory") +else: + os.mkdir(TEST_CASES) # Use ~40000 for 24 hours of fuzzing on a modern work station. RUNS = 8 diff --git a/deps/v8/tools/clusterfuzz/testdata/sanity_check_output.txt b/deps/v8/tools/clusterfuzz/testdata/smoke_test_output.txt index ea6b8a9466..4a41f9cbb5 100644 --- a/deps/v8/tools/clusterfuzz/testdata/sanity_check_output.txt +++ b/deps/v8/tools/clusterfuzz/testdata/smoke_test_output.txt @@ -1,7 +1,7 @@ # # V8 correctness failure # V8 correctness configs: x64,ignition:x64,ignition_turbo -# V8 correctness sources: sanity check failed +# V8 correctness sources: smoke test failed # V8 correctness suppression: # # CHECK diff --git a/deps/v8/tools/clusterfuzz/v8_foozzie.py b/deps/v8/tools/clusterfuzz/v8_foozzie.py index 558145973d..9f3810c9f5 100755 --- a/deps/v8/tools/clusterfuzz/v8_foozzie.py +++ b/deps/v8/tools/clusterfuzz/v8_foozzie.py @@ -96,10 +96,10 @@ RETURN_PASS = 0 RETURN_FAIL = 2 BASE_PATH = os.path.dirname(os.path.abspath(__file__)) -SANITY_CHECKS = os.path.join(BASE_PATH, 'v8_sanity_checks.js') +SMOKE_TESTS = os.path.join(BASE_PATH, 'v8_smoke_tests.js') # Timeout for one d8 run. -SANITY_CHECK_TIMEOUT_SEC = 1 +SMOKE_TEST_TIMEOUT_SEC = 1 TEST_TIMEOUT_SEC = 3 SUPPORTED_ARCHS = ['ia32', 'x64', 'arm', 'arm64'] @@ -243,6 +243,10 @@ class ExecutionConfig(object): def flags(self): return self.command.flags + @property + def is_error_simulation(self): + return '--simulate-errors' in self.flags + def parse_args(): first_config_arguments = ExecutionArgumentsConfig('first') @@ -253,8 +257,8 @@ def parse_args(): '--random-seed', type=int, required=True, help='random seed passed to both runs') parser.add_argument( - '--skip-sanity-checks', default=False, action='store_true', - help='skip sanity checks for testing purposes') + '--skip-smoke-tests', default=False, action='store_true', + help='skip smoke tests for testing purposes') parser.add_argument( '--skip-suppressions', default=False, action='store_true', help='skip suppressions to reproduce known issues') @@ -384,7 +388,7 @@ def run_comparisons(suppress, execution_configs, test_case, timeout, timeout: Timeout in seconds for one run. verbose: Prints the executed commands. ignore_crashes: Typically we ignore crashes during fuzzing as they are - frequent. However, when running sanity checks we should not crash + frequent. However, when running smoke tests we should not crash and immediately flag crashes as a failure. source_key: A fixed source key. If not given, it will be inferred from the output. @@ -396,11 +400,13 @@ def run_comparisons(suppress, execution_configs, test_case, timeout, baseline_config = execution_configs[0] baseline_output = run_test_case(baseline_config) has_crashed = baseline_output.HasCrashed() + simulated = baseline_config.is_error_simulation # Iterate over the remaining configurations, run and compare. for comparison_config in execution_configs[1:]: comparison_output = run_test_case(comparison_config) has_crashed = has_crashed or comparison_output.HasCrashed() + simulated = simulated or comparison_config.is_error_simulation difference, source = suppress.diff(baseline_output, comparison_output) if difference: @@ -421,10 +427,11 @@ def run_comparisons(suppress, execution_configs, test_case, timeout, # detected. This is only for the statistics during experiments. raise PassException('# V8 correctness - C-R-A-S-H') else: - # Subsume unexpected crashes (e.g. during sanity checks) with one failure - # state. + # Subsume simulated and unexpected crashes (e.g. during smoke tests) + # with one failure state. + crash_state = '_simulated_crash_' if simulated else '_unexpected_crash_' raise FailException(FAILURE_HEADER_TEMPLATE % dict( - configs='', source_key='', suppression='unexpected crash')) + configs='', source_key='', suppression=crash_state)) def main(): @@ -451,18 +458,18 @@ def main(): # First, run some fixed smoke tests in all configs to ensure nothing # is fundamentally wrong, in order to prevent bug flooding. - if not options.skip_sanity_checks: + if not options.skip_smoke_tests: run_comparisons( suppress, execution_configs, - test_case=SANITY_CHECKS, - timeout=SANITY_CHECK_TIMEOUT_SEC, + test_case=SMOKE_TESTS, + timeout=SMOKE_TEST_TIMEOUT_SEC, verbose=False, - # Don't accept crashes during sanity checks. A crash would hint at + # Don't accept crashes during smoke tests. A crash would hint at # a flag that might be incompatible or a broken test file. ignore_crashes=False, - # Special source key for sanity checks so that clusterfuzz dedupes all + # Special source key for smoke tests so that clusterfuzz dedupes all # cases on this in case it's hit. - source_key = 'sanity check failed', + source_key = 'smoke test failed', ) # Second, run all configs against the fuzz test case. diff --git a/deps/v8/tools/clusterfuzz/v8_foozzie_test.py b/deps/v8/tools/clusterfuzz/v8_foozzie_test.py index 8bb568cb60..eb8322ce62 100755 --- a/deps/v8/tools/clusterfuzz/v8_foozzie_test.py +++ b/deps/v8/tools/clusterfuzz/v8_foozzie_test.py @@ -270,7 +270,7 @@ class SystemTest(unittest.TestCase): build3: As build1 but with an architecture difference as well. """ def testSyntaxErrorDiffPass(self): - stdout = run_foozzie('build1', '--skip-sanity-checks') + stdout = run_foozzie('build1', '--skip-smoke-tests') self.assertEqual('# V8 correctness - pass\n', cut_verbose_output(stdout, 3)) # Default comparison includes suppressions. @@ -283,7 +283,7 @@ class SystemTest(unittest.TestCase): with open(os.path.join(TEST_DATA, 'failure_output.txt')) as f: expected_output = f.read() with self.assertRaises(subprocess.CalledProcessError) as ctx: - run_foozzie('build2', '--skip-sanity-checks', + run_foozzie('build2', '--skip-smoke-tests', '--first-config-extra-flags=--flag1', '--first-config-extra-flags=--flag2=0', '--second-config-extra-flags=--flag3') @@ -291,8 +291,8 @@ class SystemTest(unittest.TestCase): self.assertEqual(v8_foozzie.RETURN_FAIL, e.returncode) self.assertEqual(expected_output, cut_verbose_output(e.output, 2)) - def testSanityCheck(self): - with open(os.path.join(TEST_DATA, 'sanity_check_output.txt')) as f: + def testSmokeTest(self): + with open(os.path.join(TEST_DATA, 'smoke_test_output.txt')) as f: expected_output = f.read() with self.assertRaises(subprocess.CalledProcessError) as ctx: run_foozzie('build2') @@ -305,7 +305,7 @@ class SystemTest(unittest.TestCase): we use executables with different architectures. """ # Build 3 simulates x86, while the baseline is x64. - stdout = run_foozzie('build3', '--skip-sanity-checks') + stdout = run_foozzie('build3', '--skip-smoke-tests') lines = stdout.split('\n') # TODO(machenbach): Don't depend on the command-lines being printed in # particular lines. @@ -315,7 +315,7 @@ class SystemTest(unittest.TestCase): def testJitless(self): """Test that webassembly is mocked out when comparing with jitless.""" stdout = run_foozzie( - 'build1', '--skip-sanity-checks', second_config='jitless') + 'build1', '--skip-smoke-tests', second_config='jitless') lines = stdout.split('\n') # TODO(machenbach): Don't depend on the command-lines being printed in # particular lines. @@ -328,14 +328,14 @@ class SystemTest(unittest.TestCase): """ # Compare baseline with baseline. This passes as there is no difference. stdout = run_foozzie( - 'baseline', '--skip-sanity-checks', '--skip-suppressions') + 'baseline', '--skip-smoke-tests', '--skip-suppressions') self.assertNotIn('v8_suppressions.js', stdout) # Compare with a build that usually suppresses a difference. Now we fail # since we skip suppressions. with self.assertRaises(subprocess.CalledProcessError) as ctx: run_foozzie( - 'build1', '--skip-sanity-checks', '--skip-suppressions') + 'build1', '--skip-smoke-tests', '--skip-suppressions') e = ctx.exception self.assertEqual(v8_foozzie.RETURN_FAIL, e.returncode) self.assertNotIn('v8_suppressions.js', e.output) diff --git a/deps/v8/tools/clusterfuzz/v8_fuzz_flags.json b/deps/v8/tools/clusterfuzz/v8_fuzz_flags.json index 71ab2c29e5..e3a6ef4066 100644 --- a/deps/v8/tools/clusterfuzz/v8_fuzz_flags.json +++ b/deps/v8/tools/clusterfuzz/v8_fuzz_flags.json @@ -30,5 +30,6 @@ [0.1, "--no-enable-popcnt"], [0.25, "--no-lazy-feedback-allocation"], [0.1, "--no-lazy-feedback-allocation --interrupt-budget=100"], - [0.05, "--budget-for-feedback-vector-allocation=0"] + [0.05, "--budget-for-feedback-vector-allocation=0"], + [0.0001, "--simulate-errors"] ]
\ No newline at end of file diff --git a/deps/v8/tools/clusterfuzz/v8_sanity_checks.js b/deps/v8/tools/clusterfuzz/v8_smoke_tests.js index c2f0b2a4d9..39eb2d4e21 100644 --- a/deps/v8/tools/clusterfuzz/v8_sanity_checks.js +++ b/deps/v8/tools/clusterfuzz/v8_smoke_tests.js @@ -31,7 +31,7 @@ print("https://crbug.com/985154"); print(Object.getOwnPropertyNames(foo().bar)); })(); -print("Suppresses sensitive natives"); +print("Sensitive runtime functions are neutered"); (function () { function foo() {} %PrepareFunctionForOptimization(foo); diff --git a/deps/v8/tools/clusterfuzz/v8_suppressions.py b/deps/v8/tools/clusterfuzz/v8_suppressions.py index 71c69fb6b2..18f9de7ac1 100644 --- a/deps/v8/tools/clusterfuzz/v8_suppressions.py +++ b/deps/v8/tools/clusterfuzz/v8_suppressions.py @@ -57,6 +57,8 @@ IGNORE_TEST_CASES = { IGNORE_OUTPUT = { 'crbug.com/689877': re.compile(r'^.*SyntaxError: .*Stack overflow$', re.M), + '_fake_difference_': + re.compile(r'^.*___fake_difference___$', re.M), } # Lines matching any of the following regular expressions will be ignored @@ -70,7 +72,6 @@ ALLOWED_LINE_DIFFS = [ # Lines matching any of the following regular expressions will be ignored. # Use uncompiled regular expressions - they'll be compiled later. IGNORE_LINES = [ - r'^Warning: unknown flag .*$', r'^Warning: .+ is deprecated.*$', r'^Try --help for options$', diff --git a/deps/v8/tools/codemap.mjs b/deps/v8/tools/codemap.mjs index 245b6ba42e..4986fbd3b0 100644 --- a/deps/v8/tools/codemap.mjs +++ b/deps/v8/tools/codemap.mjs @@ -266,6 +266,7 @@ export class CodeMap { * @param {number} size Code entry size in bytes. * @param {string} opt_name Code entry name. * @param {string} opt_type Code entry type, e.g. SHARED_LIB, CPP. + * @param {object} source Optional source position information * @constructor */ export class CodeEntry { @@ -274,6 +275,7 @@ export class CodeEntry { this.name = opt_name || ''; this.type = opt_type || ''; this.nameUpdated_ = false; + this.source = undefined; } getName() { @@ -283,6 +285,10 @@ export class CodeEntry { toString() { return this.name + ': ' + this.size.toString(16); } + + getSourceCode() { + return ''; + } } class NameGenerator { diff --git a/deps/v8/tools/consarray.mjs b/deps/v8/tools/consarray.mjs index 1dc2afe886..b212eb720a 100644 --- a/deps/v8/tools/consarray.mjs +++ b/deps/v8/tools/consarray.mjs @@ -36,49 +36,47 @@ * * @constructor */ -export function ConsArray() { - this.tail_ = new ConsArray.Cell(null, null); - this.currCell_ = this.tail_; - this.currCellPos_ = 0; -}; - - -/** - * Concatenates another array for iterating. Empty arrays are ignored. - * This operation can be safely performed during ongoing ConsArray - * iteration. - * - * @param {Array} arr Array to concatenate. - */ -ConsArray.prototype.concat = function(arr) { - if (arr.length > 0) { - this.tail_.data = arr; - this.tail_ = this.tail_.next = new ConsArray.Cell(null, null); +export class ConsArray { + constructor() { + this.tail_ = new ConsArrayCell(null, null); + this.currCell_ = this.tail_; + this.currCellPos_ = 0; + } + /** + * Concatenates another array for iterating. Empty arrays are ignored. + * This operation can be safely performed during ongoing ConsArray + * iteration. + * + * @param {Array} arr Array to concatenate. + */ + concat(arr) { + if (arr.length > 0) { + this.tail_.data = arr; + this.tail_ = this.tail_.next = new ConsArrayCell(null, null); + } } -}; - - -/** - * Whether the end of iteration is reached. - */ -ConsArray.prototype.atEnd = function() { - return this.currCell_ === null || - this.currCell_.data === null || - this.currCellPos_ >= this.currCell_.data.length; -}; + /** + * Whether the end of iteration is reached. + */ + atEnd() { + return this.currCell_ === null || + this.currCell_.data === null || + this.currCellPos_ >= this.currCell_.data.length; + } -/** - * Returns the current item, moves to the next one. - */ -ConsArray.prototype.next = function() { - const result = this.currCell_.data[this.currCellPos_++]; - if (this.currCellPos_ >= this.currCell_.data.length) { - this.currCell_ = this.currCell_.next; - this.currCellPos_ = 0; + /** + * Returns the current item, moves to the next one. + */ + next() { + const result = this.currCell_.data[this.currCellPos_++]; + if (this.currCellPos_ >= this.currCell_.data.length) { + this.currCell_ = this.currCell_.next; + this.currCellPos_ = 0; + } + return result; } - return result; -}; +} /** @@ -86,7 +84,9 @@ ConsArray.prototype.next = function() { * * @constructor */ -ConsArray.Cell = function(data, next) { - this.data = data; - this.next = next; -}; +class ConsArrayCell { + constructor(data, next) { + this.data = data; + this.next = next; + } +} diff --git a/deps/v8/tools/csvparser.mjs b/deps/v8/tools/csvparser.mjs index e027d47384..c43ee4c4fc 100644 --- a/deps/v8/tools/csvparser.mjs +++ b/deps/v8/tools/csvparser.mjs @@ -62,7 +62,11 @@ export class CsvParser { } // Convert the selected escape sequence to a single character. let escapeChars = string.substring(pos, nextPos); - result += String.fromCharCode(parseInt(escapeChars, 16)); + if (escapeChars === '2C') { + result += ','; + } else { + result += String.fromCharCode(parseInt(escapeChars, 16)); + } } // Continue looking for the next escape sequence. diff --git a/deps/v8/tools/debug_helper/get-object-properties.cc b/deps/v8/tools/debug_helper/get-object-properties.cc index 181c58dbf0..a6ebcc0761 100644 --- a/deps/v8/tools/debug_helper/get-object-properties.cc +++ b/deps/v8/tools/debug_helper/get-object-properties.cc @@ -413,6 +413,8 @@ class ReadStringVisitor : public TqObjectVisitor { that_->index_ += index_adjust_; that_->limit_ += limit_adjust_; } + IndexModifier(const IndexModifier&) = delete; + IndexModifier& operator=(const IndexModifier&) = delete; ~IndexModifier() { that_->index_ -= index_adjust_; that_->limit_ -= limit_adjust_; @@ -422,7 +424,6 @@ class ReadStringVisitor : public TqObjectVisitor { ReadStringVisitor* that_; int32_t index_adjust_; int32_t limit_adjust_; - DISALLOW_COPY_AND_ASSIGN(IndexModifier); }; static constexpr int kMaxCharacters = 80; // How many characters to print. diff --git a/deps/v8/tools/deprecation_stats.py b/deps/v8/tools/deprecation_stats.py index 628eebc779..56b26c39ad 100755 --- a/deps/v8/tools/deprecation_stats.py +++ b/deps/v8/tools/deprecation_stats.py @@ -11,11 +11,13 @@ from datetime import datetime import re import subprocess import sys +from os import path RE_GITHASH = re.compile(r"^[0-9a-f]{40}") RE_AUTHOR_TIME = re.compile(r"^author-time (\d+)$") RE_FILENAME = re.compile(r"^filename (.+)$") + def GetBlame(file_path): result = subprocess.check_output( ['git', 'blame', '-t', '--line-porcelain', file_path]) @@ -43,66 +45,78 @@ def GetBlame(file_path): blame_list.append(current_blame) return blame_list -RE_MACRO_END = re.compile(r"\);"); + +RE_MACRO_END = re.compile(r"\);") RE_DEPRECATE_MACRO = re.compile(r"\(.*?,(.*)\);", re.MULTILINE) -def FilterAndPrint(blame_list, macro, before): + +def FilterAndPrint(blame_list, macro, options): + before = options.before index = 0 re_macro = re.compile(macro) deprecated = list() while index < len(blame_list): blame = blame_list[index] - match = re_macro.search(blame['content']) - if match and blame['time'] < before: - line = blame['content'] - time = blame['time'] + time = blame['time'] + if time >= before: + index += 1 + continue + line = blame['content'] + match = re_macro.search(line) + if match: pos = match.end() start = -1 parens = 0 - quotes = 0 while True: if pos >= len(line): # extend to next line index = index + 1 blame = blame_list[index] - if line.endswith(','): - # add whitespace when breaking line due to comma - line = line + ' ' line = line + blame['content'] if line[pos] == '(': parens = parens + 1 elif line[pos] == ')': parens = parens - 1 if parens == 0: + # Exclud closing ") + pos = pos - 2 break - elif line[pos] == '"': - quotes = quotes + 1 - elif line[pos] == ',' and quotes % 2 == 0 and start == -1: + elif line[pos] == '"' and start == -1: start = pos + 1 pos = pos + 1 - deprecated.append([index + 1, time, line[start:pos].strip()]) + # Extract content and replace double quotes from merged lines + content = line[start:pos].strip().replace('""', '') + deprecated.append([index + 1, time, content]) index = index + 1 print("Marked as " + macro + ": " + str(len(deprecated))) for linenumber, time, content in deprecated: - print(str(linenumber).rjust(8) + " : " + str(time) + " : " + content) + print(" " + (options.v8_header + ":" + + str(linenumber)).rjust(len(options.v8_header) + 5) + "\t" + + str(time) + "\t" + content) return len(deprecated) + def ParseOptions(args): - parser = argparse.ArgumentParser(description="Collect deprecation statistics") - parser.add_argument("file_path", help="Path to v8.h") + parser = argparse.ArgumentParser( + description="Collect deprecation statistics") + parser.add_argument("v8_header", nargs='?', help="Path to v8.h") parser.add_argument("--before", help="Filter by date") options = parser.parse_args(args) if options.before: options.before = datetime.strptime(options.before, '%Y-%m-%d') else: options.before = datetime.now() + if options.v8_header is None: + options.v8_header = path.join(path.dirname(__file__), '..', 'include', 'v8.h') return options + def Main(args): options = ParseOptions(args) - blame_list = GetBlame(options.file_path) - FilterAndPrint(blame_list, "V8_DEPRECATE_SOON", options.before) - FilterAndPrint(blame_list, "V8_DEPRECATED", options.before) + blame_list = GetBlame(options.v8_header) + FilterAndPrint(blame_list, "V8_DEPRECATE_SOON", options) + FilterAndPrint(blame_list, "V8_DEPRECATED", options) + if __name__ == "__main__": Main(sys.argv[1:]) diff --git a/deps/v8/tools/dumpcpp.mjs b/deps/v8/tools/dumpcpp.mjs index be2dd996e4..9deab5d2aa 100644 --- a/deps/v8/tools/dumpcpp.mjs +++ b/deps/v8/tools/dumpcpp.mjs @@ -5,10 +5,9 @@ import { LogReader, parseString } from "./logreader.mjs"; import { CodeMap, CodeEntry } from "./codemap.mjs"; export { - ArgumentsProcessor, UnixCppEntriesProvider, + ArgumentsProcessor, UnixCppEntriesProvider, WindowsCppEntriesProvider, MacCppEntriesProvider, } from "./tickprocessor.mjs"; - import { inherits } from "./tickprocessor.mjs"; export class CppProcessor extends LogReader { @@ -29,7 +28,7 @@ export class CppProcessor extends LogReader { */ printError(str) { print(str); - }; + } processLogFile(fileName) { this.lastLogFileName_ = fileName; @@ -37,14 +36,14 @@ export class CppProcessor extends LogReader { while (line = readline()) { this.processLogLine(line); } - }; + } processLogFileInTest(fileName) { // Hack file name to avoid dealing with platform specifics. this.lastLogFileName_ = 'v8.log'; const contents = readFile(fileName); this.processLogChunk(contents); - }; + } processSharedLibrary(name, startAddr, endAddr, aslrSlide) { const self = this; @@ -53,7 +52,7 @@ export class CppProcessor extends LogReader { const entry = new CodeEntry(fEnd - fStart, fName, 'CPP'); self.codeMap_.addStaticCode(fStart, entry); }); - }; + } dumpCppSymbols() { const staticEntries = this.codeMap_.getAllStaticEntriesWithAddresses(); diff --git a/deps/v8/tools/gcmole/BUILD.gn b/deps/v8/tools/gcmole/BUILD.gn index 9767acbf5a..558766487d 100644 --- a/deps/v8/tools/gcmole/BUILD.gn +++ b/deps/v8/tools/gcmole/BUILD.gn @@ -8,12 +8,10 @@ group("v8_run_gcmole") { testonly = true data = [ - "gccause.lua", "GCMOLE.gn", - "gcmole.lua", + "gcmole.py", "gcmole-test.cc", "gcmole-tools/", - "parallel.py", "run-gcmole.py", "suspects.whitelist", "ignored_files", diff --git a/deps/v8/tools/gcmole/README b/deps/v8/tools/gcmole/README index 46b4717e3c..1d2acd3b1a 100644 --- a/deps/v8/tools/gcmole/README +++ b/deps/v8/tools/gcmole/README @@ -35,9 +35,9 @@ simply to store it as a Handle. PREREQUISITES ----------------------------------------------------------------- -(1) Install Lua 5.1 +(1) Install Python - $ sudo apt-get install lua5.1 + $ sudo apt-get install python (2) Get LLVM 8.0 and Clang 8.0 sources and build them. @@ -62,14 +62,14 @@ PREREQUISITES ----------------------------------------------------------------- USING GCMOLE ------------------------------------------------------------------ -gcmole consists of driver script written in Lua and Clang plugin that does +gcmole consists of driver script written in Python and Clang plugin that does C++ AST processing. Plugin (libgcmole.so) is expected to be in the same -folder as driver (gcmole.lua). +folder as driver (gcmole.py). To start analysis cd into the root of v8 checkout and execute the following command: -CLANG_BIN=<path-to-clang-bin-folder> lua tools/gcmole/gcmole.lua [<arch>] +CLANG_BIN=<path-to-clang-bin-folder> python tools/gcmole/gcmole.py [<arch>] where arch should be one of architectures supported by V8 (arm, ia32, x64). @@ -89,7 +89,7 @@ If any errors were found driver exits with non-zero status. TESTING ----------------------------------------------------------------------- -Tests are automatically run by the main lua runner. Expectations are in +Tests are automatically run by the main python runner. Expectations are in test-expectations.txt and need to be updated whenever the sources of the tests in gcmole-test.cc are modified (line numbers also count). diff --git a/deps/v8/tools/gcmole/bootstrap.sh b/deps/v8/tools/gcmole/bootstrap.sh index 05ab1cbb8e..a0386a8054 100755 --- a/deps/v8/tools/gcmole/bootstrap.sh +++ b/deps/v8/tools/gcmole/bootstrap.sh @@ -33,14 +33,11 @@ # that the resulting binary is easier transferable between different # environments. -CLANG_RELEASE=8.0 +LLVM_RELEASE=9.0.1 THIS_DIR="$(readlink -f "$(dirname "${0}")")" -LLVM_DIR="${THIS_DIR}/../../third_party/llvm" -CLANG_DIR="${THIS_DIR}/../../third_party/clang" -BUILD_DIR="${THIS_DIR}/../../third_party/llvm+clang-build" - -LLVM_REPO_URL=${LLVM_URL:-https://llvm.org/svn/llvm-project} +LLVM_PROJECT_DIR="${THIS_DIR}/bootstrap/llvm" +BUILD_DIR="${THIS_DIR}/bootstrap/build" # Die if any command dies. set -e @@ -72,59 +69,59 @@ if [[ "${OS}" = "Darwin" ]] && xcodebuild -version | grep -q 'Xcode 3.2' ; then fi fi -echo Getting LLVM release "${CLANG_RELEASE}" in "${LLVM_DIR}" -if ! svn co --force \ - "${LLVM_REPO_URL}/llvm/branches/release_${CLANG_RELEASE/./}" \ - "${LLVM_DIR}"; then - echo Checkout failed, retrying - rm -rf "${LLVM_DIR}" - svn co --force \ - "${LLVM_REPO_URL}/llvm/branches/release_${CLANG_RELEASE/./}" \ - "${LLVM_DIR}" +echo Getting LLVM release "${LLVM_RELEASE}" in "${LLVM_PROJECT_DIR}" +if ! [ -d "${LLVM_PROJECT_DIR}" ] || ! git -C "${LLVM_PROJECT_DIR}" remote get-url origin | grep -q -F "https://github.com/llvm/llvm-project.git" ; then + rm -rf "${LLVM_PROJECT_DIR}" + git clone --depth=1 --branch "llvmorg-${LLVM_RELEASE}" "https://github.com/llvm/llvm-project.git" "${LLVM_PROJECT_DIR}" +else + git -C "${LLVM_PROJECT_DIR}" fetch --depth=1 origin "llvmorg-${LLVM_RELEASE}" + git -C "${LLVM_PROJECT_DIR}" checkout FETCH_HEAD fi -echo Getting clang release "${CLANG_RELEASE}" in "${CLANG_DIR}" -svn co --force \ - "${LLVM_REPO_URL}/cfe/branches/release_${CLANG_RELEASE/./}" \ - "${CLANG_DIR}" - # Echo all commands set -x NUM_JOBS=3 if [[ "${OS}" = "Linux" ]]; then - NUM_JOBS="$(grep -c "^processor" /proc/cpuinfo)" + if [[ -e "/proc/cpuinfo" ]]; then + NUM_JOBS="$(grep -c "^processor" /proc/cpuinfo)" + else + # Hack when running in chroot + NUM_JOBS="32" + fi elif [ "${OS}" = "Darwin" ]; then NUM_JOBS="$(sysctl -n hw.ncpu)" fi # Build clang. -if [ ! -e "${BUILD_DIR}" ]; then - mkdir "${BUILD_DIR}" -fi -cd "${BUILD_DIR}" -cmake -DCMAKE_CXX_FLAGS="-static-libstdc++" -DLLVM_ENABLE_TERMINFO=OFF \ - -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS=clang "${LLVM_DIR}" -MACOSX_DEPLOYMENT_TARGET=10.5 make -j"${NUM_JOBS}" - -# Strip the clang binary. -STRIP_FLAGS= -if [ "${OS}" = "Darwin" ]; then - # See http://crbug.com/256342 - STRIP_FLAGS=-x -fi -strip ${STRIP_FLAGS} bin/clang -cd - +# if [ ! -e "${BUILD_DIR}" ]; then +# mkdir "${BUILD_DIR}" +# fi +# cd "${BUILD_DIR}" +# cmake -GNinja -DCMAKE_CXX_FLAGS="-static-libstdc++" -DLLVM_ENABLE_TERMINFO=OFF \ +# -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS=clang \ +# -DLLVM_ENABLE_Z3_SOLVER=OFF "${LLVM_PROJECT_DIR}/llvm" +# MACOSX_DEPLOYMENT_TARGET=10.5 ninja -j"${NUM_JOBS}" +# +# # Strip the clang binary. +# STRIP_FLAGS= +# if [ "${OS}" = "Darwin" ]; then +# # See http://crbug.com/256342 +# STRIP_FLAGS=-x +# fi +# strip ${STRIP_FLAGS} bin/clang +# cd - # Build libgcmole.so make -C "${THIS_DIR}" clean -make -C "${THIS_DIR}" LLVM_SRC_ROOT="${LLVM_DIR}" \ - CLANG_SRC_ROOT="${CLANG_DIR}" BUILD_ROOT="${BUILD_DIR}" libgcmole.so +make -C "${THIS_DIR}" LLVM_SRC_ROOT="${LLVM_PROJECT_DIR}/llvm" \ + CLANG_SRC_ROOT="${LLVM_PROJECT_DIR}/clang" \ + BUILD_ROOT="${BUILD_DIR}" libgcmole.so set +x echo echo You can now run gcmole using this command: echo -echo CLANG_BIN=\"third_party/llvm+clang-build/bin\" lua tools/gcmole/gcmole.lua +echo CLANG_BIN=\"tools/gcmole/gcmole-tools/bin\" python tools/gcmole/gcmole.py echo diff --git a/deps/v8/tools/gcmole/gccause.lua b/deps/v8/tools/gcmole/gccause.lua deleted file mode 100644 index b9891767de..0000000000 --- a/deps/v8/tools/gcmole/gccause.lua +++ /dev/null @@ -1,62 +0,0 @@ --- Copyright 2011 the V8 project authors. All rights reserved. --- Redistribution and use in source and binary forms, with or without --- modification, are permitted provided that the following conditions are --- met: --- --- * Redistributions of source code must retain the above copyright --- notice, this list of conditions and the following disclaimer. --- * Redistributions in binary form must reproduce the above --- copyright notice, this list of conditions and the following --- disclaimer in the documentation and/or other materials provided --- with the distribution. --- * Neither the name of Google Inc. nor the names of its --- contributors may be used to endorse or promote products derived --- from this software without specific prior written permission. --- --- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS --- "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT --- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR --- A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT --- OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, --- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT --- LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, --- DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY --- THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT --- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE --- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - --- This is an auxiliary tool that reads gccauses file generated by --- gcmole.lua and prints tree of the calls that can potentially cause a GC --- inside a given function. --- --- Usage: lua tools/gcmole/gccause.lua <function-name-pattern> --- - -assert(loadfile "gccauses")() - -local P = ... - -local T = {} - -local function TrackCause(name, lvl) - io.write((" "):rep(lvl or 0), name, "\n") - if GC[name] then - local causes = GC[name] - for i = 1, #causes do - local f = causes[i] - if not T[f] then - T[f] = true - TrackCause(f, (lvl or 0) + 1) - end - - if f == '<GC>' then break end - end - end -end - -for name, _ in pairs(GC) do - if name:match(P) then - T = {} - TrackCause(name) - end -end diff --git a/deps/v8/tools/gcmole/gcmole-test.cc b/deps/v8/tools/gcmole/gcmole-test.cc index 8512d7ab4c..038a514189 100644 --- a/deps/v8/tools/gcmole/gcmole-test.cc +++ b/deps/v8/tools/gcmole/gcmole-test.cc @@ -5,6 +5,7 @@ #include "src/execution/isolate.h" #include "src/handles/handles-inl.h" #include "src/handles/handles.h" +#include "src/heap/local-heap.h" #include "src/objects/foreign-inl.h" #include "src/objects/managed.h" #include "src/objects/maybe-object.h" @@ -15,6 +16,8 @@ namespace internal { // ------- Test simple argument evaluation order problems --------- +void Safepoint() { LocalHeap::Current()->Safepoint(); } + Handle<Object> CauseGC(Handle<Object> obj, Isolate* isolate) { isolate->heap()->CollectGarbage(OLD_SPACE, GarbageCollectionReason::kTesting); @@ -161,70 +164,187 @@ void TestDeadVarAnalysis(Isolate* isolate) { raw_obj.Print(); } +void TestDeadVarBecauseOfSafepointAnalysis(Isolate* isolate) { + JSObject raw_obj = *isolate->factory()->NewJSObjectWithNullProto(); + Safepoint(); + + // Should cause warning. + raw_obj.Print(); +} + void TestGuardedDeadVarAnalysis(Isolate* isolate) { JSObject raw_obj = *isolate->factory()->NewJSObjectWithNullProto(); - // Note: having DisallowHeapAllocation with the same function as CauseGC - // normally doesn't make sense, but we want to test whether the gurads + // Note: having DisableGCMole with the same function as CauseGC + // normally doesn't make sense, but we want to test whether the guards // are recognized by GCMole. - DisallowHeapAllocation no_gc; + DisableGCMole no_gc_mole; CauseGCRaw(raw_obj, isolate); // Shouldn't cause warning. raw_obj.Print(); } -void TestGuardedDeadVarAnalysisNotOnStack(Isolate* isolate) { +void TestGuardedDeadVarAnalysis2(Isolate* isolate) { JSObject raw_obj = *isolate->factory()->NewJSObjectWithNullProto(); - // {DisallowHeapAccess} has a {DisallowHeapAllocation} embedded as a member - // field, so both are treated equally by gcmole. - DisallowHeapAccess no_gc; + // Note: having DisallowGarbageCollection with the same function as CauseGC + // normally doesn't make sense, but we want to test whether the guards + // are recognized by GCMole. + DisallowGarbageCollection no_gc; CauseGCRaw(raw_obj, isolate); + // Should cause warning. + raw_obj.Print(); +} + +void TestGuardedAgainstSafepointDeadVarAnalysis(Isolate* isolate) { + JSObject raw_obj = *isolate->factory()->NewJSObjectWithNullProto(); + + // Note: having DisableGCMole with the same function as CauseGC + // normally doesn't make sense, but we want to test whether the guards + // are recognized by GCMole. + DisableGCMole no_gc_mole; + Safepoint(); + // Shouldn't cause warning. raw_obj.Print(); } -void TestGuardedDeadVarAnalysisNested(JSObject raw_obj, Isolate* isolate) { +void TestGuardedAgainstSafepointDeadVarAnalysis2(Isolate* isolate) { + JSObject raw_obj = *isolate->factory()->NewJSObjectWithNullProto(); + + // Note: having DisallowGarbageCollection with the same function as CauseGC + // normally doesn't make sense, but we want to test whether the guards + // are recognized by GCMole. + DisallowGarbageCollection no_gc; + Safepoint(); + + // Should cause warning. + raw_obj.Print(); +} + +void TestGuardedAgainstSafepointDeadVarAnalysis3(Isolate* isolate) { + JSObject raw_obj = *isolate->factory()->NewJSObjectWithNullProto(); + // Note: having DisallowGarbageCollection with the same function as CauseGC + // normally doesn't make sense, but we want to test whether the guards + // are recognized by GCMole. + DisallowGarbageCollection no_gc; + Safepoint(); + // Should cause warning. + raw_obj.Print(); + { + DisableGCMole no_gc_mole; + // Shouldn't cause warning. + raw_obj.Print(); + } + // Should cause warning. + raw_obj.Print(); +} + +void TestOnlyHeapGuardedDeadVarAnalysisInCompound(Isolate* isolate) { + JSObject raw_obj = *isolate->factory()->NewJSObjectWithNullProto(); + // {DisallowHeapAccess} has a {DisallowHeapAllocation}, but no + // {DisallowSafepoints}, so it could see objects move due to safepoints. + DisallowHeapAccess no_gc; CauseGCRaw(raw_obj, isolate); + // Should cause warning. + raw_obj.Print(); +} + +void TestOnlyHeapGuardedDeadVarAnalysisInCompound2(Isolate* isolate) { + JSObject raw_obj = *isolate->factory()->NewJSObjectWithNullProto(); + // {DisallowHeapAccess} has a {DisallowHeapAllocation}, but no + // {DisallowSafepoints}, so it could see objects move due to safepoints. + DisallowHeapAccess no_gc; + CauseGCRaw(raw_obj, isolate); + // Should cause warning. + raw_obj.Print(); + DisableGCMole no_gc_mole; + // Should cause warning. + raw_obj.Print(); +} +void TestGuardedDeadVarAnalysisNested(JSObject raw_obj, Isolate* isolate) { + CauseGCRaw(raw_obj, isolate); // Should cause warning. raw_obj.Print(); } void TestGuardedDeadVarAnalysisCaller(Isolate* isolate) { - DisallowHeapAccess no_gc; + DisableGCMole no_gc_mole; JSObject raw_obj = *isolate->factory()->NewJSObjectWithNullProto(); + TestGuardedDeadVarAnalysisNested(raw_obj, isolate); + // Shouldn't cause warning. + raw_obj.Print(); +} +void TestGuardedDeadVarAnalysisCaller2(Isolate* isolate) { + DisallowGarbageCollection no_gc; + JSObject raw_obj = *isolate->factory()->NewJSObjectWithNullProto(); TestGuardedDeadVarAnalysisNested(raw_obj, isolate); + // Should cause warning. + raw_obj.Print(); +} - // Shouldn't cause warning. +void TestGuardedDeadVarAnalysisCaller3(Isolate* isolate) { + DisallowHeapAccess no_gc; + JSObject raw_obj = *isolate->factory()->NewJSObjectWithNullProto(); + TestGuardedDeadVarAnalysisNested(raw_obj, isolate); + // Should cause warning. + raw_obj.Print(); +} + +void TestGuardedDeadVarAnalysisCaller4(Isolate* isolate) { + JSObject raw_obj = *isolate->factory()->NewJSObjectWithNullProto(); + TestGuardedDeadVarAnalysisNested(raw_obj, isolate); + // Should cause warning. raw_obj.Print(); } JSObject GuardedAllocation(Isolate* isolate) { - DisallowHeapAllocation no_gc; + DisallowGarbageCollection no_gc; + return *isolate->factory()->NewJSObjectWithNullProto(); +} + +JSObject GuardedAllocation2(Isolate* isolate) { + DisableGCMole no_gc_mole; return *isolate->factory()->NewJSObjectWithNullProto(); } void TestNestedDeadVarAnalysis(Isolate* isolate) { JSObject raw_obj = GuardedAllocation(isolate); CauseGCRaw(raw_obj, isolate); - // Should cause warning. raw_obj.Print(); } +void TestNestedDeadVarAnalysis2(Isolate* isolate) { + DisableGCMole no_gc_mole; + JSObject raw_obj = GuardedAllocation(isolate); + CauseGCRaw(raw_obj, isolate); + // Shouldn't cause warning. + raw_obj.Print(); +} + // Test that putting a guard in the middle of the function doesn't // mistakenly cover the whole scope of the raw variable. void TestGuardedDeadVarAnalysisMidFunction(Isolate* isolate) { JSObject raw_obj = *isolate->factory()->NewJSObjectWithNullProto(); - CauseGCRaw(raw_obj, isolate); + // Guarding the rest of the function from triggering a GC. + DisallowGarbageCollection no_gc; + // Should cause warning. + raw_obj.Print(); +} +// Test that putting a guard in the middle of the function doesn't +// mistakenly cover the whole scope of the raw variable. +void TestGuardedDeadVarAnalysisMidFunction2(Isolate* isolate) { + JSObject raw_obj = *isolate->factory()->NewJSObjectWithNullProto(); + CauseGCRaw(raw_obj, isolate); // Guarding the rest of the function from triggering a GC. - DisallowHeapAllocation no_gc; + DisableGCMole no_gc_mole; // Should cause warning. raw_obj.Print(); } diff --git a/deps/v8/tools/gcmole/gcmole-tools.tar.gz.sha1 b/deps/v8/tools/gcmole/gcmole-tools.tar.gz.sha1 index 84b3657c6c..4e18bae709 100644 --- a/deps/v8/tools/gcmole/gcmole-tools.tar.gz.sha1 +++ b/deps/v8/tools/gcmole/gcmole-tools.tar.gz.sha1 @@ -1 +1 @@ -7e31d257a711b1a77823633e4f19152c3e0718f4 +165dd133e7dff1583a6b6611e3d5a8382bcd0893 diff --git a/deps/v8/tools/gcmole/gcmole.cc b/deps/v8/tools/gcmole/gcmole.cc index 7b32f6c7fd..3dc4baa722 100644 --- a/deps/v8/tools/gcmole/gcmole.cc +++ b/deps/v8/tools/gcmole/gcmole.cc @@ -27,22 +27,23 @@ // This is clang plugin used by gcmole tool. See README for more details. +#include <bitset> +#include <fstream> +#include <iostream> +#include <map> +#include <set> +#include <stack> + #include "clang/AST/AST.h" #include "clang/AST/ASTConsumer.h" #include "clang/AST/Mangle.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/AST/StmtVisitor.h" -#include "clang/Frontend/FrontendPluginRegistry.h" +#include "clang/Basic/FileManager.h" #include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendPluginRegistry.h" #include "llvm/Support/raw_ostream.h" -#include <bitset> -#include <fstream> -#include <iostream> -#include <map> -#include <set> -#include <stack> - namespace { bool g_tracing_enabled = false; @@ -228,8 +229,9 @@ class CalleesPrinter : public clang::RecursiveASTVisitor<CalleesPrinter> { virtual bool VisitDeclRefExpr(clang::DeclRefExpr* expr) { // If function mentions EXTERNAL VMState add artificial garbage collection // mark. - if (IsExternalVMState(expr->getDecl())) + if (IsExternalVMState(expr->getDecl())) { AddCallee("CollectGarbage", "CollectGarbage"); + } return true; } @@ -684,17 +686,13 @@ class FunctionAnalyzer { FunctionAnalyzer(clang::MangleContext* ctx, clang::CXXRecordDecl* object_decl, clang::CXXRecordDecl* maybe_object_decl, clang::CXXRecordDecl* smi_decl, - clang::CXXRecordDecl* no_gc_decl, - clang::CXXRecordDecl* no_gc_or_safepoint_decl, - clang::CXXRecordDecl* no_heap_access_decl, + clang::CXXRecordDecl* no_gc_mole_decl, clang::DiagnosticsEngine& d, clang::SourceManager& sm) : ctx_(ctx), object_decl_(object_decl), maybe_object_decl_(maybe_object_decl), smi_decl_(smi_decl), - no_gc_decl_(no_gc_decl), - no_gc_or_safepoint_decl_(no_gc_or_safepoint_decl), - no_heap_access_decl_(no_heap_access_decl), + no_gc_mole_decl_(no_gc_mole_decl), d_(d), sm_(sm), block_(NULL) {} @@ -1281,9 +1279,7 @@ class FunctionAnalyzer { DECL_VISIT_STMT(ForStmt) { Block block (VisitStmt(stmt->getInit(), env), this); do { - block.Loop(stmt->getCond(), - stmt->getBody(), - stmt->getInc()); + block.Loop(stmt->getCond(), stmt->getBody(), stmt->getInc()); } while (block.changed()); return block.out(); } @@ -1338,25 +1334,15 @@ class FunctionAnalyzer { const clang::CXXRecordDecl* GetDefinitionOrNull( const clang::CXXRecordDecl* record) { - if (record == NULL) { - return NULL; - } - + if (record == NULL) return NULL; if (!InV8Namespace(record)) return NULL; - - if (!record->hasDefinition()) { - return NULL; - } - + if (!record->hasDefinition()) return NULL; return record->getDefinition(); } bool IsDerivedFromInternalPointer(const clang::CXXRecordDecl* record) { const clang::CXXRecordDecl* definition = GetDefinitionOrNull(record); - if (!definition) { - return false; - } - + if (!definition) return false; bool result = (IsDerivedFrom(record, object_decl_) && !IsDerivedFrom(record, smi_decl_)) || IsDerivedFrom(record, maybe_object_decl_); @@ -1381,13 +1367,9 @@ class FunctionAnalyzer { // such. For V8 that means Object and MaybeObject instances. bool RepresentsRawPointerType(clang::QualType qtype) { // Not yet assigned pointers can't get moved by the GC. - if (qtype.isNull()) { - return false; - } + if (qtype.isNull()) return false; // nullptr can't get moved by the GC. - if (qtype->isNullPtrType()) { - return false; - } + if (qtype->isNullPtrType()) return false; const clang::PointerType* pointer_type = llvm::dyn_cast_or_null<clang::PointerType>(qtype.getTypePtrOrNull()); @@ -1399,25 +1381,15 @@ class FunctionAnalyzer { } bool IsGCGuard(clang::QualType qtype) { - if (qtype.isNull()) { - return false; - } - if (qtype->isNullPtrType()) { - return false; - } + if (!no_gc_mole_decl_) return false; + if (qtype.isNull()) return false; + if (qtype->isNullPtrType()) return false; const clang::CXXRecordDecl* record = qtype->getAsCXXRecordDecl(); const clang::CXXRecordDecl* definition = GetDefinitionOrNull(record); - if (!definition) { - return false; - } - - return (no_gc_decl_ && IsDerivedFrom(definition, no_gc_decl_)) || - (no_gc_or_safepoint_decl_ && - IsDerivedFrom(definition, no_gc_or_safepoint_decl_)) || - (no_heap_access_decl_ && - IsDerivedFrom(definition, no_heap_access_decl_)); + if (!definition) return false; + return no_gc_mole_decl_ == definition; } Environment VisitDecl(clang::Decl* decl, Environment& env) { @@ -1501,8 +1473,7 @@ class FunctionAnalyzer { clang::CXXRecordDecl* object_decl_; clang::CXXRecordDecl* maybe_object_decl_; clang::CXXRecordDecl* smi_decl_; - clang::CXXRecordDecl* no_gc_decl_; - clang::CXXRecordDecl* no_gc_or_safepoint_decl_; + clang::CXXRecordDecl* no_gc_mole_decl_; clang::CXXRecordDecl* no_heap_access_decl_; clang::DiagnosticsEngine& d_; @@ -1567,58 +1538,38 @@ class ProblemsFinder : public clang::ASTConsumer, } virtual void HandleTranslationUnit(clang::ASTContext &ctx) { - if (TranslationUnitIgnored()) { - return; - } + if (TranslationUnitIgnored()) return; Resolver r(ctx); - // It is a valid situation that no_gc_decl == NULL when the - // DisallowHeapAllocation is not included and can't be resolved. - // This is gracefully handled in the FunctionAnalyzer later. - clang::CXXRecordDecl* no_gc_decl = - r.ResolveNamespace("v8") - .ResolveNamespace("internal") - .ResolveTemplate("DisallowHeapAllocation"); - - clang::CXXRecordDecl* no_gc_or_safepoint_decl = - r.ResolveNamespace("v8") - .ResolveNamespace("internal") - .ResolveTemplate("DisallowGarbageCollection"); - - clang::CXXRecordDecl* no_heap_access_decl = - r.ResolveNamespace("v8") - .ResolveNamespace("internal") - .Resolve<clang::CXXRecordDecl>("DisallowHeapAccess"); + // It is a valid situation that no_gc_mole_decl == NULL when DisableGCMole + // is not included and can't be resolved. This is gracefully handled in the + // FunctionAnalyzer later. + auto v8_internal = r.ResolveNamespace("v8").ResolveNamespace("internal"); + clang::CXXRecordDecl* no_gc_mole_decl = + v8_internal.ResolveTemplate("DisableGCMole"); clang::CXXRecordDecl* object_decl = - r.ResolveNamespace("v8").ResolveNamespace("internal"). - Resolve<clang::CXXRecordDecl>("Object"); + v8_internal.Resolve<clang::CXXRecordDecl>("Object"); clang::CXXRecordDecl* maybe_object_decl = - r.ResolveNamespace("v8") - .ResolveNamespace("internal") - .Resolve<clang::CXXRecordDecl>("MaybeObject"); + v8_internal.Resolve<clang::CXXRecordDecl>("MaybeObject"); clang::CXXRecordDecl* smi_decl = - r.ResolveNamespace("v8").ResolveNamespace("internal"). - Resolve<clang::CXXRecordDecl>("Smi"); + v8_internal.Resolve<clang::CXXRecordDecl>("Smi"); if (object_decl != NULL) object_decl = object_decl->getDefinition(); - if (maybe_object_decl != NULL) + if (maybe_object_decl != NULL) { maybe_object_decl = maybe_object_decl->getDefinition(); + } if (smi_decl != NULL) smi_decl = smi_decl->getDefinition(); - if (no_heap_access_decl != NULL) - no_heap_access_decl = no_heap_access_decl->getDefinition(); - if (object_decl != NULL && smi_decl != NULL && maybe_object_decl != NULL) { function_analyzer_ = new FunctionAnalyzer( clang::ItaniumMangleContext::create(ctx, d_), object_decl, - maybe_object_decl, smi_decl, no_gc_decl, no_gc_or_safepoint_decl, - no_heap_access_decl, d_, sm_); + maybe_object_decl, smi_decl, no_gc_mole_decl, d_, sm_); TraverseDecl(ctx.getTranslationUnitDecl()); } else { if (object_decl == NULL) { diff --git a/deps/v8/tools/gcmole/gcmole.lua b/deps/v8/tools/gcmole/gcmole.lua deleted file mode 100644 index 9705165d36..0000000000 --- a/deps/v8/tools/gcmole/gcmole.lua +++ /dev/null @@ -1,532 +0,0 @@ --- Copyright 2011 the V8 project authors. All rights reserved. --- Redistribution and use in source and binary forms, with or without --- modification, are permitted provided that the following conditions are --- met: --- --- * Redistributions of source code must retain the above copyright --- notice, this list of conditions and the following disclaimer. --- * Redistributions in binary form must reproduce the above --- copyright notice, this list of conditions and the following --- disclaimer in the documentation and/or other materials provided --- with the distribution. --- * Neither the name of Google Inc. nor the names of its --- contributors may be used to endorse or promote products derived --- from this software without specific prior written permission. --- --- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS --- "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT --- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR --- A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT --- OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, --- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT --- LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, --- DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY --- THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT --- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE --- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - --- This is main driver for gcmole tool. See README for more details. --- Usage: CLANG_BIN=clang-bin-dir lua tools/gcmole/gcmole.lua [arm|ia32|x64] - -local DIR = arg[0]:match("^(.+)/[^/]+$") - -local FLAGS = { - -- Do not build gcsuspects file and reuse previously generated one. - reuse_gcsuspects = false; - - -- Don't use parallel python runner. - sequential = false; - - -- Print commands to console before executing them. - verbose = false; - - -- Perform dead variable analysis. - dead_vars = true; - - -- Enable verbose tracing from the plugin itself. - verbose_trace = false; - - -- When building gcsuspects whitelist certain functions as if they - -- can be causing GC. Currently used to reduce number of false - -- positives in dead variables analysis. See TODO for WHITELIST - -- below. - whitelist = true; -} -local ARGS = {} - -for i = 1, #arg do - local flag = arg[i]:match "^%-%-([%w_-]+)$" - if flag then - local no, real_flag = flag:match "^(no)([%w_-]+)$" - if real_flag then flag = real_flag end - - flag = flag:gsub("%-", "_") - if FLAGS[flag] ~= nil then - FLAGS[flag] = (no ~= "no") - else - error("Unknown flag: " .. flag) - end - else - table.insert(ARGS, arg[i]) - end -end - -local ARCHS = ARGS[1] and { ARGS[1] } or { 'ia32', 'arm', 'x64', 'arm64' } - -local io = require "io" -local os = require "os" - -function log(...) - io.stderr:write(string.format(...)) - io.stderr:write "\n" -end - -------------------------------------------------------------------------------- --- Clang invocation - -local CLANG_BIN = os.getenv "CLANG_BIN" -local CLANG_PLUGINS = os.getenv "CLANG_PLUGINS" - -if not CLANG_BIN or CLANG_BIN == "" then - error "CLANG_BIN not set" -end - -if not CLANG_PLUGINS or CLANG_PLUGINS == "" then - CLANG_PLUGINS = DIR -end - -local function MakeClangCommandLine( - plugin, plugin_args, triple, arch_define, arch_options) - if plugin_args then - for i = 1, #plugin_args do - plugin_args[i] = "-Xclang -plugin-arg-" .. plugin - .. " -Xclang " .. plugin_args[i] - end - plugin_args = " " .. table.concat(plugin_args, " ") - end - return CLANG_BIN .. "/clang++ -std=c++14 -c" - .. " -Xclang -load -Xclang " .. CLANG_PLUGINS .. "/libgcmole.so" - .. " -Xclang -plugin -Xclang " .. plugin - .. (plugin_args or "") - .. " -Xclang -triple -Xclang " .. triple - .. " -fno-exceptions" - .. " -D" .. arch_define - .. " -DENABLE_DEBUGGER_SUPPORT" - .. " -DV8_INTL_SUPPORT" - .. " -I./" - .. " -Iinclude/" - .. " -Iout/build/gen" - .. " -Ithird_party/icu/source/common" - .. " -Ithird_party/icu/source/i18n" - .. " " .. arch_options -end - -local function IterTable(t) - return coroutine.wrap(function () - for i, v in ipairs(t) do - coroutine.yield(v) - end - end) -end - -local function SplitResults(lines, func) - -- Splits the output of parallel.py and calls func on each result. - -- Bails out in case of an error in one of the executions. - local current = {} - local filename = "" - for line in lines do - local new_file = line:match "^______________ (.*)$" - local code = line:match "^______________ finish (%d+) ______________$" - if code then - if tonumber(code) > 0 then - log(table.concat(current, "\n")) - log("Failed to examine " .. filename) - return false - end - log("-- %s", filename) - func(filename, IterTable(current)) - elseif new_file then - filename = new_file - current = {} - else - table.insert(current, line) - end - end - return true -end - -function InvokeClangPluginForEachFile(filenames, cfg, func) - local cmd_line = MakeClangCommandLine(cfg.plugin, - cfg.plugin_args, - cfg.triple, - cfg.arch_define, - cfg.arch_options) - if FLAGS.sequential then - log("** Sequential execution.") - for _, filename in ipairs(filenames) do - log("-- %s", filename) - local action = cmd_line .. " " .. filename .. " 2>&1" - if FLAGS.verbose then print('popen ', action) end - local pipe = io.popen(action) - func(filename, pipe:lines()) - local success = pipe:close() - if not success then error("Failed to run: " .. action) end - end - else - log("** Parallel execution.") - local action = "python tools/gcmole/parallel.py \"" - .. cmd_line .. "\" " .. table.concat(filenames, " ") - if FLAGS.verbose then print('popen ', action) end - local pipe = io.popen(action) - local success = SplitResults(pipe:lines(), func) - local closed = pipe:close() - if not (success and closed) then error("Failed to run: " .. action) end - end -end - -------------------------------------------------------------------------------- - -local function ParseGNFile(for_test) - local result = {} - local gn_files - if for_test then - gn_files = { - { "tools/gcmole/GCMOLE.gn", '"([^"]-%.cc)"', "" } - } - else - gn_files = { - { "BUILD.gn", '"([^"]-%.cc)"', "" }, - { "test/cctest/BUILD.gn", '"(test-[^"]-%.cc)"', "test/cctest/" } - } - end - - for i = 1, #gn_files do - local filename = gn_files[i][1] - local pattern = gn_files[i][2] - local prefix = gn_files[i][3] - local gn_file = assert(io.open(filename), "failed to open GN file") - local gn = gn_file:read('*a') - for condition, sources in - gn:gmatch "### gcmole%((.-)%) ###(.-)%]" do - if result[condition] == nil then result[condition] = {} end - for file in sources:gmatch(pattern) do - table.insert(result[condition], prefix .. file) - end - end - gn_file:close() - end - - return result -end - -local function EvaluateCondition(cond, props) - if cond == 'all' then return true end - - local p, v = cond:match "(%w+):(%w+)" - - assert(p and v, "failed to parse condition: " .. cond) - assert(props[p] ~= nil, "undefined configuration property: " .. p) - - return props[p] == v -end - -local function BuildFileList(sources, props) - local list = {} - for condition, files in pairs(sources) do - if EvaluateCondition(condition, props) then - for i = 1, #files do table.insert(list, files[i]) end - end - end - return list -end - - -local gn_sources = ParseGNFile(false) -local gn_test_sources = ParseGNFile(true) - -local function FilesForArch(arch) - return BuildFileList(gn_sources, { os = 'linux', - arch = arch, - mode = 'debug', - simulator = ''}) -end - -local function FilesForTest(arch) - return BuildFileList(gn_test_sources, { os = 'linux', - arch = arch, - mode = 'debug', - simulator = ''}) -end - -local mtConfig = {} - -mtConfig.__index = mtConfig - -local function config (t) return setmetatable(t, mtConfig) end - -function mtConfig:extend(t) - local e = {} - for k, v in pairs(self) do e[k] = v end - for k, v in pairs(t) do e[k] = v end - return config(e) -end - -local ARCHITECTURES = { - ia32 = config { triple = "i586-unknown-linux", - arch_define = "V8_TARGET_ARCH_IA32", - arch_options = "-m32" }, - arm = config { triple = "i586-unknown-linux", - arch_define = "V8_TARGET_ARCH_ARM", - arch_options = "-m32" }, - x64 = config { triple = "x86_64-unknown-linux", - arch_define = "V8_TARGET_ARCH_X64", - arch_options = "" }, - arm64 = config { triple = "x86_64-unknown-linux", - arch_define = "V8_TARGET_ARCH_ARM64", - arch_options = "" }, -} - -------------------------------------------------------------------------------- --- GCSuspects Generation - -local gc, gc_caused, funcs - --- Note that the gcsuspects file lists functions in the form: --- mangled_name,unmangled_function_name --- --- This means that we can match just the function name by matching only --- after a comma. -local WHITELIST = { - -- The following functions call CEntryStub which is always present. - "MacroAssembler.*,CallRuntime", - "CompileCallLoadPropertyWithInterceptor", - "CallIC.*,GenerateMiss", - - -- DirectCEntryStub is a special stub used on ARM. - -- It is pinned and always present. - "DirectCEntryStub.*,GenerateCall", - - -- TODO GCMole currently is sensitive enough to understand that certain - -- functions only cause GC and return Failure simulataneously. - -- Callsites of such functions are safe as long as they are properly - -- check return value and propagate the Failure to the caller. - -- It should be possible to extend GCMole to understand this. - "Heap.*,TryEvacuateObject", - - -- Ignore all StateTag methods. - "StateTag", - - -- Ignore printing of elements transition. - "PrintElementsTransition", - - -- CodeCreateEvent receives AbstractCode (a raw ptr) as an argument. - "CodeCreateEvent", - "WriteField", -}; - -local function AddCause(name, cause) - local t = gc_caused[name] - if not t then - t = {} - gc_caused[name] = t - end - table.insert(t, cause) -end - -local function resolve(name) - local f = funcs[name] - - if not f then - f = {} - funcs[name] = f - - if name:match ",.*Collect.*Garbage" then - gc[name] = true - AddCause(name, "<GC>") - end - - if FLAGS.whitelist then - for i = 1, #WHITELIST do - if name:match(WHITELIST[i]) then - gc[name] = false - end - end - end - end - - return f -end - -local function parse (filename, lines) - local scope - - for funcname in lines do - if funcname:sub(1, 1) ~= '\t' then - resolve(funcname) - scope = funcname - else - local name = funcname:sub(2) - resolve(name)[scope] = true - end - end -end - -local function propagate () - log "** Propagating GC information" - - local function mark(from, callers) - for caller, _ in pairs(callers) do - if gc[caller] == nil then - gc[caller] = true - mark(caller, funcs[caller]) - end - AddCause(caller, from) - end - end - - for funcname, callers in pairs(funcs) do - if gc[funcname] then mark(funcname, callers) end - end -end - -local function GenerateGCSuspects(arch, files, cfg) - -- Reset the global state. - gc, gc_caused, funcs = {}, {}, {} - - log ("** Building GC Suspects for %s", arch) - InvokeClangPluginForEachFile (files, - cfg:extend { plugin = "dump-callees" }, - parse) - - propagate() - - local out = assert(io.open("gcsuspects", "w")) - for name, value in pairs(gc) do if value then out:write (name, '\n') end end - out:close() - - local out = assert(io.open("gccauses", "w")) - out:write "GC = {" - for name, causes in pairs(gc_caused) do - out:write("['", name, "'] = {") - for i = 1, #causes do out:write ("'", causes[i], "';") end - out:write("};\n") - end - out:write "}" - out:close() - - log ("** GCSuspects generated for %s", arch) -end - --------------------------------------------------------------------------------- --- Analysis - -local function CheckCorrectnessForArch(arch, for_test) - local files - if for_test then - files = FilesForTest(arch) - else - files = FilesForArch(arch) - end - local cfg = ARCHITECTURES[arch] - - if not FLAGS.reuse_gcsuspects then - GenerateGCSuspects(arch, files, cfg) - end - - local processed_files = 0 - local errors_found = false - local output = "" - local function SearchForErrors(filename, lines) - processed_files = processed_files + 1 - for l in lines do - errors_found = errors_found or - l:match "^[^:]+:%d+:%d+:" or - l:match "error" or - l:match "warning" - if for_test then - output = output.."\n"..l - else - print(l) - end - end - end - - log("** Searching for evaluation order problems%s for %s", - FLAGS.dead_vars and " and dead variables" or "", - arch) - local plugin_args = {} - if FLAGS.dead_vars then table.insert(plugin_args, "--dead-vars") end - if FLAGS.verbose_trace then table.insert(plugin_args, '--verbose') end - InvokeClangPluginForEachFile(files, - cfg:extend { plugin = "find-problems", - plugin_args = plugin_args }, - SearchForErrors) - log("** Done processing %d files. %s", - processed_files, - errors_found and "Errors found" or "No errors found") - - return errors_found, output -end - -local function SafeCheckCorrectnessForArch(arch, for_test) - local status, errors, output = pcall(CheckCorrectnessForArch, arch, for_test) - if not status then - print(string.format("There was an error: %s", errors)) - errors = true - end - return errors, output -end - --- Source: https://stackoverflow.com/a/41515925/1540248 -local function StringDifference(str1,str2) - for i = 1,#str1 do -- Loop over strings - -- If that character is not equal to its counterpart - if str1:sub(i,i) ~= str2:sub(i,i) then - return i --Return that index - end - end - return #str1+1 -- Return the index after where the shorter one ends as fallback. -end - -local function TestRun() - local errors, output = SafeCheckCorrectnessForArch('x64', true) - if not errors then - log("** Test file should produce errors, but none were found. Output:") - log(output) - return false - end - - local filename = "tools/gcmole/test-expectations.txt" - local exp_file = assert(io.open(filename), "failed to open test expectations file") - local expectations = exp_file:read('*all') - - if output ~= expectations then - log("** Output mismatch from running tests. Please run them manually.") - local idx = StringDifference(output, expectations) - - log("Difference at byte "..idx) - log("Expected: "..expectations:sub(idx-10,idx+10)) - log("Actual: "..output:sub(idx-10,idx+10)) - - log("--- Full output ---") - log(output) - log("------") - - return false - end - - log("** Tests ran successfully") - return true -end - -local errors = not TestRun() - -for _, arch in ipairs(ARCHS) do - if not ARCHITECTURES[arch] then - error("Unknown arch: " .. arch) - end - - errors = SafeCheckCorrectnessForArch(arch, false) or errors -end - -os.exit(errors and 1 or 0) diff --git a/deps/v8/tools/gcmole/gcmole.py b/deps/v8/tools/gcmole/gcmole.py new file mode 100644 index 0000000000..978cd31598 --- /dev/null +++ b/deps/v8/tools/gcmole/gcmole.py @@ -0,0 +1,575 @@ +#!/usr/bin/env python +# 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. + +# This is main driver for gcmole tool. See README for more details. +# Usage: CLANG_BIN=clang-bin-dir python tools/gcmole/gcmole.py [arm|arm64|ia32|x64] + +# for py2/py3 compatibility +from __future__ import print_function + +import collections +import difflib +from multiprocessing import cpu_count +import os +import re +import subprocess +import sys +import threading +if sys.version_info.major > 2: + import queue +else: + import Queue as queue + +ArchCfg = collections.namedtuple("ArchCfg", + ["triple", "arch_define", "arch_options"]) + +ARCHITECTURES = { + "ia32": + ArchCfg( + triple="i586-unknown-linux", + arch_define="V8_TARGET_ARCH_IA32", + arch_options=["-m32"], + ), + "arm": + ArchCfg( + triple="i586-unknown-linux", + arch_define="V8_TARGET_ARCH_ARM", + arch_options=["-m32"], + ), + "x64": + ArchCfg( + triple="x86_64-unknown-linux", + arch_define="V8_TARGET_ARCH_X64", + arch_options=[]), + "arm64": + ArchCfg( + triple="x86_64-unknown-linux", + arch_define="V8_TARGET_ARCH_ARM64", + arch_options=[], + ), +} + + +def log(format, *args): + print(format.format(*args)) + + +def fatal(format, *args): + log(format, *args) + sys.exit(1) + + +# ----------------------------------------------------------------------------- +# Clang invocation + + +def MakeClangCommandLine(plugin, plugin_args, arch_cfg, clang_bin_dir, + clang_plugins_dir): + prefixed_plugin_args = [] + if plugin_args: + for arg in plugin_args: + prefixed_plugin_args += [ + "-Xclang", + "-plugin-arg-{}".format(plugin), + "-Xclang", + arg, + ] + + return ([ + os.path.join(clang_bin_dir, "clang++"), + "-std=c++14", + "-c", + "-Xclang", + "-load", + "-Xclang", + os.path.join(clang_plugins_dir, "libgcmole.so"), + "-Xclang", + "-plugin", + "-Xclang", + plugin, + ] + prefixed_plugin_args + [ + "-Xclang", + "-triple", + "-Xclang", + arch_cfg.triple, + "-fno-exceptions", + "-D", + arch_cfg.arch_define, + "-DENABLE_DEBUGGER_SUPPORT", + "-DV8_INTL_SUPPORT", + "-I./", + "-Iinclude/", + "-Iout/build/gen", + "-Ithird_party/icu/source/common", + "-Ithird_party/icu/source/i18n", + ] + arch_cfg.arch_options) + + +def InvokeClangPluginForFile(filename, cmd_line, verbose): + if verbose: + print("popen ", " ".join(cmd_line + [filename])) + p = subprocess.Popen( + cmd_line + [filename], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout, stderr = p.communicate() + return p.returncode, stdout, stderr + + +def InvokeClangPluginForFilesInQueue(i, input_queue, output_queue, cancel_event, + cmd_line, verbose): + success = False + try: + while not cancel_event.is_set(): + filename = input_queue.get_nowait() + ret, stdout, stderr = InvokeClangPluginForFile(filename, cmd_line, + verbose) + output_queue.put_nowait((filename, ret, stdout.decode('utf-8'), stderr.decode('utf-8'))) + if ret != 0: + break + except KeyboardInterrupt: + log("-- [{}] Interrupting", i) + except queue.Empty: + success = True + finally: + # Emit a success bool so that the reader knows that there was either an + # error or all files were processed. + output_queue.put_nowait(success) + + +def InvokeClangPluginForEachFile( + filenames, + plugin, + plugin_args, + arch_cfg, + flags, + clang_bin_dir, + clang_plugins_dir, +): + cmd_line = MakeClangCommandLine(plugin, plugin_args, arch_cfg, clang_bin_dir, + clang_plugins_dir) + verbose = flags["verbose"] + if flags["sequential"]: + log("** Sequential execution.") + for filename in filenames: + log("-- {}", filename) + returncode, stdout, stderr = InvokeClangPluginForFile( + filename, cmd_line, verbose) + if returncode != 0: + sys.stderr.write(stderr) + sys.exit(returncode) + yield filename, stdout, stderr + else: + log("** Parallel execution.") + cpus = cpu_count() + input_queue = queue.Queue() + output_queue = queue.Queue() + threads = [] + try: + for filename in filenames: + input_queue.put(filename) + + cancel_event = threading.Event() + + for i in range(min(len(filenames), cpus)): + threads.append( + threading.Thread( + target=InvokeClangPluginForFilesInQueue, + args=(i, input_queue, output_queue, cancel_event, cmd_line, + verbose))) + + for t in threads: + t.start() + + num_finished = 0 + while num_finished < len(threads): + output = output_queue.get() + if type(output) == bool: + if output: + num_finished += 1 + continue + else: + break + filename, returncode, stdout, stderr = output + log("-- {}", filename) + if returncode != 0: + sys.stderr.write(stderr) + sys.exit(returncode) + yield filename, stdout, stderr + + finally: + cancel_event.set() + for t in threads: + t.join() + + +# ----------------------------------------------------------------------------- + + +def ParseGNFile(for_test): + result = {} + if for_test: + gn_files = [("tools/gcmole/GCMOLE.gn", re.compile('"([^"]*?\.cc)"'), "")] + else: + gn_files = [ + ("BUILD.gn", re.compile('"([^"]*?\.cc)"'), ""), + ("test/cctest/BUILD.gn", re.compile('"(test-[^"]*?\.cc)"'), + "test/cctest/"), + ] + + for filename, pattern, prefix in gn_files: + with open(filename) as gn_file: + gn = gn_file.read() + for condition, sources in re.findall("### gcmole\((.*?)\) ###(.*?)\]", gn, + re.MULTILINE | re.DOTALL): + if condition not in result: + result[condition] = [] + for file in pattern.findall(sources): + result[condition].append(prefix + file) + + return result + + +def EvaluateCondition(cond, props): + if cond == "all": + return True + + m = re.match("(\w+):(\w+)", cond) + if m is None: + fatal("failed to parse condition: {}", cond) + p, v = m.groups() + if p not in props: + fatal("undefined configuration property: {}", p) + + return props[p] == v + + +def BuildFileList(sources, props): + ret = [] + for condition, files in sources.items(): + if EvaluateCondition(condition, props): + ret += files + return ret + + +gn_sources = ParseGNFile(for_test=False) +gn_test_sources = ParseGNFile(for_test=True) + + +def FilesForArch(arch): + return BuildFileList(gn_sources, { + "os": "linux", + "arch": arch, + "mode": "debug", + "simulator": "" + }) + + +def FilesForTest(arch): + return BuildFileList(gn_test_sources, { + "os": "linux", + "arch": arch, + "mode": "debug", + "simulator": "" + }) + + +# ----------------------------------------------------------------------------- +# GCSuspects Generation + +# Note that the gcsuspects file lists functions in the form: +# mangled_name,unmangled_function_name +# +# This means that we can match just the function name by matching only +# after a comma. +ALLOWLIST = [ + # The following functions call CEntryStub which is always present. + "MacroAssembler.*,CallRuntime", + "CompileCallLoadPropertyWithInterceptor", + "CallIC.*,GenerateMiss", + # DirectCEntryStub is a special stub used on ARM. + # It is pinned and always present. + "DirectCEntryStub.*,GenerateCall", + # TODO GCMole currently is sensitive enough to understand that certain + # functions only cause GC and return Failure simulataneously. + # Callsites of such functions are safe as long as they are properly + # check return value and propagate the Failure to the caller. + # It should be possible to extend GCMole to understand this. + "Heap.*,TryEvacuateObject", + # Ignore all StateTag methods. + "StateTag", + # Ignore printing of elements transition. + "PrintElementsTransition", + # CodeCreateEvent receives AbstractCode (a raw ptr) as an argument. + "CodeCreateEvent", + "WriteField", +] + +GC_PATTERN = ",.*Collect.*Garbage" +SAFEPOINT_PATTERN = ",EnterSafepoint" +ALLOWLIST_PATTERN = "|".join("(?:%s)" % p for p in ALLOWLIST) + + +def MergeRegexp(pattern_dict): + return re.compile("|".join( + "(?P<%s>%s)" % (key, value) for (key, value) in pattern_dict.items())) + + +IS_SPECIAL_WITHOUT_ALLOW_LIST = MergeRegexp({ + "gc": GC_PATTERN, + "safepoint": SAFEPOINT_PATTERN +}) +IS_SPECIAL_WITH_ALLOW_LIST = MergeRegexp({ + "gc": GC_PATTERN, + "safepoint": SAFEPOINT_PATTERN, + "allow": ALLOWLIST_PATTERN +}) + + +class GCSuspectsCollector: + + def __init__(self, flags): + self.gc = {} + self.gc_caused = collections.defaultdict(lambda: []) + self.funcs = {} + self.current_caller = None + self.allowlist = flags["allowlist"] + self.is_special = IS_SPECIAL_WITH_ALLOW_LIST if self.allowlist else IS_SPECIAL_WITHOUT_ALLOW_LIST + + def AddCause(self, name, cause): + self.gc_caused[name].append(cause) + + def Parse(self, lines): + for funcname in lines: + if not funcname: + continue + + if funcname[0] != "\t": + self.Resolve(funcname) + self.current_caller = funcname + else: + name = funcname[1:] + callers_for_name = self.Resolve(name) + callers_for_name.add(self.current_caller) + + def Resolve(self, name): + if name not in self.funcs: + self.funcs[name] = set() + m = self.is_special.search(name) + if m: + if m.group("gc"): + self.gc[name] = True + self.AddCause(name, "<GC>") + elif m.group("safepoint"): + self.gc[name] = True + self.AddCause(name, "<Safepoint>") + elif m.group("allow"): + self.gc[name] = False + + return self.funcs[name] + + def Propagate(self): + log("** Propagating GC information") + + def mark(funcname, callers): + for caller in callers: + if caller not in self.gc: + self.gc[caller] = True + mark(caller, self.funcs[caller]) + + self.AddCause(caller, funcname) + + for funcname, callers in self.funcs.items(): + if self.gc.get(funcname, False): + mark(funcname, callers) + + +def GenerateGCSuspects(arch, files, arch_cfg, flags, clang_bin_dir, + clang_plugins_dir): + # Reset the global state. + collector = GCSuspectsCollector(flags) + + log("** Building GC Suspects for {}", arch) + for filename, stdout, stderr in InvokeClangPluginForEachFile( + files, "dump-callees", [], arch_cfg, flags, clang_bin_dir, + clang_plugins_dir): + collector.Parse(stdout.splitlines()) + + collector.Propagate() + + with open("gcsuspects", "w") as out: + for name, value in collector.gc.items(): + if value: + out.write(name + "\n") + + with open("gccauses", "w") as out: + out.write("GC = {\n") + for name, causes in collector.gc_caused.items(): + out.write(" '{}': [\n".format(name)) + for cause in causes: + out.write(" '{}',\n".format(cause)) + out.write(" ],\n") + out.write("}\n") + + log("** GCSuspects generated for {}", arch) + + +# ------------------------------------------------------------------------------ +# Analysis + + +def CheckCorrectnessForArch(arch, for_test, flags, clang_bin_dir, + clang_plugins_dir): + if for_test: + files = FilesForTest(arch) + else: + files = FilesForArch(arch) + arch_cfg = ARCHITECTURES[arch] + + if not flags["reuse_gcsuspects"]: + GenerateGCSuspects(arch, files, arch_cfg, flags, clang_bin_dir, + clang_plugins_dir) + else: + log("** Reusing GCSuspects for {}", arch) + + processed_files = 0 + errors_found = False + output = "" + + log( + "** Searching for evaluation order problems{} for {}", + " and dead variables" if flags["dead_vars"] else "", + arch, + ) + plugin_args = [] + if flags["dead_vars"]: + plugin_args.append("--dead-vars") + if flags["verbose_trace"]: + plugin_args.append("--verbose") + for filename, stdout, stderr in InvokeClangPluginForEachFile( + files, + "find-problems", + plugin_args, + arch_cfg, + flags, + clang_bin_dir, + clang_plugins_dir, + ): + processed_files = processed_files + 1 + if not errors_found: + errors_found = re.search("^[^:]+:\d+:\d+: (warning|error)", stderr, + re.MULTILINE) is not None + if for_test: + output = output + stderr + else: + sys.stdout.write(stderr) + + log( + "** Done processing {} files. {}", + processed_files, + "Errors found" if errors_found else "No errors found", + ) + + return errors_found, output + + +def TestRun(flags, clang_bin_dir, clang_plugins_dir): + log("** Test Run") + errors_found, output = CheckCorrectnessForArch("x64", True, flags, + clang_bin_dir, + clang_plugins_dir) + if not errors_found: + log("** Test file should produce errors, but none were found. Output:") + log(output) + return False + + filename = "tools/gcmole/test-expectations.txt" + with open(filename) as exp_file: + expectations = exp_file.read() + + if output != expectations: + log("** Output mismatch from running tests. Please run them manually.") + + for line in difflib.unified_diff( + expectations.splitlines(), + output.splitlines(), + fromfile=filename, + tofile="output", + lineterm="", + ): + log("{}", line) + + log("------") + log("--- Full output ---") + log(output) + log("------") + + return False + + log("** Tests ran successfully") + return True + + +def main(args): + DIR = os.path.dirname(args[0]) + + clang_bin_dir = os.getenv("CLANG_BIN") + clang_plugins_dir = os.getenv("CLANG_PLUGINS") + + if not clang_bin_dir or clang_bin_dir == "": + fatal("CLANG_BIN not set") + + if not clang_plugins_dir or clang_plugins_dir == "": + clang_plugins_dir = DIR + + flags = { + #: not build gcsuspects file and reuse previously generated one. + "reuse_gcsuspects": False, + #:n't use parallel python runner. + "sequential": False, + # Print commands to console before executing them. + "verbose": True, + # Perform dead variable analysis. + "dead_vars": True, + # Enable verbose tracing from the plugin itself. + "verbose_trace": False, + # When building gcsuspects allowlist certain functions as if they can be + # causing GC. Currently used to reduce number of false positives in dead + # variables analysis. See TODO for ALLOWLIST + "allowlist": True, + } + pos_args = [] + + flag_regexp = re.compile("^--(no[-_]?)?([\w\-_]+)$") + for arg in args[1:]: + m = flag_regexp.match(arg) + if m: + no, flag = m.groups() + flag = flag.replace("-", "_") + if flag in flags: + flags[flag] = no is None + else: + fatal("Unknown flag: {}", flag) + else: + pos_args.append(arg) + + archs = pos_args if len(pos_args) > 0 else ["ia32", "arm", "x64", "arm64"] + + any_errors_found = False + if not TestRun(flags, clang_bin_dir, clang_plugins_dir): + any_errors_found = True + else: + for arch in archs: + if not ARCHITECTURES[arch]: + fatal("Unknown arch: {}", arch) + + errors_found, output = CheckCorrectnessForArch(arch, False, flags, + clang_bin_dir, + clang_plugins_dir) + any_errors_found = any_errors_found or errors_found + + sys.exit(1 if any_errors_found else 0) + + +if __name__ == "__main__": + main(sys.argv) diff --git a/deps/v8/tools/gcmole/package.sh b/deps/v8/tools/gcmole/package.sh index 6206e7bb2e..7178de5c11 100755 --- a/deps/v8/tools/gcmole/package.sh +++ b/deps/v8/tools/gcmole/package.sh @@ -10,14 +10,22 @@ THIS_DIR="$(readlink -f "$(dirname "${0}")")" -PACKAGE_DIR="${THIS_DIR}/../../tools/gcmole/gcmole-tools" -PACKAGE_FILE="${THIS_DIR}/../../tools/gcmole/gcmole-tools.tar.gz" -PACKAGE_SUM="${THIS_DIR}/../../tools/gcmole/gcmole-tools.tar.gz.sha1" -BUILD_DIR="${THIS_DIR}/../../third_party/llvm+clang-build" +PACKAGE_DIR="${THIS_DIR}/gcmole-tools" +PACKAGE_FILE="${THIS_DIR}/gcmole-tools.tar.gz" +PACKAGE_SUM="${THIS_DIR}/gcmole-tools.tar.gz.sha1" +BUILD_DIR="${THIS_DIR}/bootstrap/build" # Echo all commands set -x +# Clean out any old files +if [ -e "${PACKAGE_DIR:?}/bin" ] ; then + rm -rf "${PACKAGE_DIR:?}/bin" +fi +if [ -e "${PACKAGE_DIR:?}/lib" ] ; then + rm -rf "${PACKAGE_DIR:?}/lib" +fi + # Copy all required files mkdir -p "${PACKAGE_DIR}/bin" cp "${BUILD_DIR}/bin/clang++" "${PACKAGE_DIR}/bin" @@ -39,7 +47,22 @@ echo You can find a packaged version of gcmole here: echo echo $(readlink -f "${PACKAGE_FILE}") echo +echo Upload the update package to the chrome infra: +echo +echo 'gsutil.py cp tools/gcmole/gcmole-tools.tar.gz gs://chrome-v8-gcmole/$(cat tools/gcmole/gcmole-tools.tar.gz.sha1)' +echo +echo Run bootstrap.sh in chroot if glibc versions mismatch with bots: +echo '# Create chroot' +echo 'mkdir -p $CHROOT_DIR' +echo 'sudo debootstrap $DEBIAN_VERSION $CHROOT_DIR' +echo 'sudo chroot $CHROOT_DIR apt install g++ cmake python git' +echo '# Mount ./depot_tools and ./v8 dirs in the chroot:' +echo 'sudo chroot $CHROOT_DIR mkdir /docs' +echo 'sudo mount --bind /path/to/workspace /docs' +echo '# Build gcmole' +echo "sudo chroot \$CHROOT_DIR bash -c 'PATH=/docs/depot_tools:\$PATH; /docs/v8/v8/tools/gcmole/bootstrap.sh'" +echo echo You can now run gcmole using this command: echo -echo CLANG_BIN="tools/gcmole/gcmole-tools/bin" lua tools/gcmole/gcmole.lua +echo CLANG_BIN=\"tools/gcmole/gcmole-tools/bin\" python tools/gcmole/gcmole.py echo diff --git a/deps/v8/tools/gcmole/parallel.py b/deps/v8/tools/gcmole/parallel.py deleted file mode 100755 index 7ff95ccadc..0000000000 --- a/deps/v8/tools/gcmole/parallel.py +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env python -# Copyright 2015 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. - -""" -This script calls the first argument for each of the following arguments in -parallel. E.g. -parallel.py "clang --opt" file1 file2 -calls -clang --opt file1 -clang --opt file2 - -The output (stdout and stderr) is concatenated sequentially in the form: -______________ file1 -<output of clang --opt file1> -______________ finish <exit code of clang --opt file1> ______________ -______________ file2 -<output of clang --opt file2> -______________ finish <exit code of clang --opt file2> ______________ -""" - -# for py2/py3 compatibility -from __future__ import print_function - -import itertools -import multiprocessing -import subprocess -import sys - -def invoke(cmdline): - try: - return (subprocess.check_output( - cmdline, shell=True, stderr=subprocess.STDOUT), 0) - except subprocess.CalledProcessError as e: - return (e.output, e.returncode) - -if __name__ == '__main__': - assert len(sys.argv) > 2 - processes = multiprocessing.cpu_count() - pool = multiprocessing.Pool(processes=processes) - cmdlines = ["%s %s" % (sys.argv[1], filename) for filename in sys.argv[2:]] - for filename, result in itertools.izip( - sys.argv[2:], pool.imap(invoke, cmdlines)): - print("______________ %s" % filename) - print(result[0]) - print("______________ finish %d ______________" % result[1]) diff --git a/deps/v8/tools/gcmole/run-gcmole.py b/deps/v8/tools/gcmole/run-gcmole.py index 02174b218a..cfcb2dd410 100755 --- a/deps/v8/tools/gcmole/run-gcmole.py +++ b/deps/v8/tools/gcmole/run-gcmole.py @@ -15,19 +15,18 @@ import sys GCMOLE_PATH = os.path.dirname(os.path.abspath(__file__)) CLANG_BIN = os.path.join(GCMOLE_PATH, 'gcmole-tools', 'bin') CLANG_PLUGINS = os.path.join(GCMOLE_PATH, 'gcmole-tools') -LUA = os.path.join(GCMOLE_PATH, 'gcmole-tools', 'lua52') -DRIVER = os.path.join(GCMOLE_PATH, 'gcmole.lua') +DRIVER = os.path.join(GCMOLE_PATH, 'gcmole.py') BASE_PATH = os.path.dirname(os.path.dirname(GCMOLE_PATH)) -assert len(sys.argv) == 2 +assert len(sys.argv) >= 2 if not os.path.isfile("out/build/gen/torque-generated/builtin-definitions.h"): print("Expected generated headers in out/build/gen.") - print("Either build v8 in out/build or change gcmole.lua:115") + print("Either build v8 in out/build or change the 'out/build/gen' location in gcmole.py") sys.exit(-1) proc = subprocess.Popen( - [LUA, DRIVER, sys.argv[1]], + [sys.executable, DRIVER] + sys.argv[1:], env={'CLANG_BIN': CLANG_BIN, 'CLANG_PLUGINS': CLANG_PLUGINS}, cwd=BASE_PATH, ) diff --git a/deps/v8/tools/gcmole/test-expectations.txt b/deps/v8/tools/gcmole/test-expectations.txt index f6c04e4a6c..92256b3e18 100644 --- a/deps/v8/tools/gcmole/test-expectations.txt +++ b/deps/v8/tools/gcmole/test-expectations.txt @@ -1,38 +1,73 @@ - -tools/gcmole/gcmole-test.cc:27:10: warning: Possibly dead variable. +tools/gcmole/gcmole-test.cc:30:10: warning: Possibly dead variable. return obj; ^ -tools/gcmole/gcmole-test.cc:45:3: warning: Possible problem with evaluation order. +tools/gcmole/gcmole-test.cc:48:3: warning: Possible problem with evaluation order. TwoArgumentsFunction(*CauseGC(obj1, isolate), *CauseGC(obj2, isolate)); ^ -tools/gcmole/gcmole-test.cc:57:3: warning: Possible problem with evaluation order. +tools/gcmole/gcmole-test.cc:60:3: warning: Possible problem with evaluation order. TwoSizeTArgumentsFunction(sizeof(*CauseGC(obj1, isolate)), ^ -tools/gcmole/gcmole-test.cc:82:7: warning: Possible problem with evaluation order. +tools/gcmole/gcmole-test.cc:85:7: warning: Possible problem with evaluation order. so->Method(*CauseGC(obj1, isolate)); ^ -tools/gcmole/gcmole-test.cc:84:7: warning: Possible problem with evaluation order. +tools/gcmole/gcmole-test.cc:87:7: warning: Possible problem with evaluation order. so->Method(CauseGCRaw(*obj1, isolate)); ^ -tools/gcmole/gcmole-test.cc:128:14: warning: Possible problem with evaluation order. +tools/gcmole/gcmole-test.cc:131:14: warning: Possible problem with evaluation order. so_handle->Method(*derived.VirtualCauseGC(obj1, isolate)); ^ -tools/gcmole/gcmole-test.cc:130:14: warning: Possible problem with evaluation order. +tools/gcmole/gcmole-test.cc:133:14: warning: Possible problem with evaluation order. so_handle->Method(*base->VirtualCauseGC(obj1, isolate)); ^ -tools/gcmole/gcmole-test.cc:151:14: warning: Possible problem with evaluation order. +tools/gcmole/gcmole-test.cc:154:14: warning: Possible problem with evaluation order. so_handle->Method(*SomeClass::StaticCauseGC(obj1, isolate)); ^ -tools/gcmole/gcmole-test.cc:161:3: warning: Possibly dead variable. +tools/gcmole/gcmole-test.cc:164:3: warning: Possibly dead variable. raw_obj.Print(); ^ -tools/gcmole/gcmole-test.cc:193:3: warning: Possibly dead variable. +tools/gcmole/gcmole-test.cc:172:3: warning: Possibly dead variable. raw_obj.Print(); ^ -tools/gcmole/gcmole-test.cc:216:3: warning: Possibly dead variable. +tools/gcmole/gcmole-test.cc:198:3: warning: Possibly dead variable. raw_obj.Print(); ^ -tools/gcmole/gcmole-test.cc:229:3: warning: Possibly dead variable. +tools/gcmole/gcmole-test.cc:224:3: warning: Possibly dead variable. raw_obj.Print(); ^ -12 warnings generated. +tools/gcmole/gcmole-test.cc:235:3: warning: Possibly dead variable. + raw_obj.Print(); + ^ +tools/gcmole/gcmole-test.cc:242:3: warning: Possibly dead variable. + raw_obj.Print(); + ^ +tools/gcmole/gcmole-test.cc:252:3: warning: Possibly dead variable. + raw_obj.Print(); + ^ +tools/gcmole/gcmole-test.cc:262:3: warning: Possibly dead variable. + raw_obj.Print(); + ^ +tools/gcmole/gcmole-test.cc:265:3: warning: Possibly dead variable. + raw_obj.Print(); + ^ +tools/gcmole/gcmole-test.cc:271:3: warning: Possibly dead variable. + raw_obj.Print(); + ^ +tools/gcmole/gcmole-test.cc:287:3: warning: Possibly dead variable. + raw_obj.Print(); + ^ +tools/gcmole/gcmole-test.cc:295:3: warning: Possibly dead variable. + raw_obj.Print(); + ^ +tools/gcmole/gcmole-test.cc:302:3: warning: Possibly dead variable. + raw_obj.Print(); + ^ +tools/gcmole/gcmole-test.cc:319:3: warning: Possibly dead variable. + raw_obj.Print(); + ^ +tools/gcmole/gcmole-test.cc:338:3: warning: Possibly dead variable. + raw_obj.Print(); + ^ +tools/gcmole/gcmole-test.cc:349:3: warning: Possibly dead variable. + raw_obj.Print(); + ^ +24 warnings generated. diff --git a/deps/v8/tools/heap-stats/categories.js b/deps/v8/tools/heap-stats/categories.js index 2bd08fad02..d3aa8480dc 100644 --- a/deps/v8/tools/heap-stats/categories.js +++ b/deps/v8/tools/heap-stats/categories.js @@ -94,8 +94,6 @@ export const CATEGORIES = new Map([ 'SYMBOL_TYPE', 'THIN_ONE_BYTE_STRING_TYPE', 'THIN_STRING_TYPE', - 'UNCACHED_EXTERNAL_INTERNALIZED_STRING_TYPE', - 'UNCACHED_EXTERNAL_ONE_BYTE_INTERNALIZED_STRING_TYPE', 'UNCACHED_EXTERNAL_ONE_BYTE_STRING_TYPE', 'UNCACHED_EXTERNAL_STRING_TYPE', 'WASM_INSTANCE_OBJECT_TYPE', diff --git a/deps/v8/tools/ic-processor-driver.mjs b/deps/v8/tools/ic-processor-driver.mjs index ef6d83e1e4..c8042736b0 100644 --- a/deps/v8/tools/ic-processor-driver.mjs +++ b/deps/v8/tools/ic-processor-driver.mjs @@ -78,11 +78,7 @@ const accumulator = { StoreInArrayLiteralIC: 0, } for (const ic of processor.icTimeline.all) { - print( - ic.type + ' (' + ic.oldState + '->' + ic.newState + ic.modifier + ') at ' + - ic.filePosition + ' ' + ic.key + - ' (map 0x' + ic.map.toString(16) + ')' + - (ic.reason ? ` ${ic.reason}` : '') + ' time: ' + ic.time); + print(Object.values(ic)); accumulator[ic.type]++; } diff --git a/deps/v8/tools/logreader.mjs b/deps/v8/tools/logreader.mjs index 1bd9a4ba02..a46c4bc87d 100644 --- a/deps/v8/tools/logreader.mjs +++ b/deps/v8/tools/logreader.mjs @@ -28,9 +28,13 @@ /** * @fileoverview Log Reader is used to process log file produced by V8. */ - import { CsvParser } from "./csvparser.mjs"; + +// Parses dummy variable for readability; +export const parseString = 'parse-string'; +export const parseVarArgs = 'parse-var-args'; + /** * Base class for processing log files. * @@ -41,206 +45,200 @@ * markers. * @constructor */ -export function LogReader(dispatchTable, timedRange, pairwiseTimedRange) { - /** - * @type {Array.<Object>} - */ - this.dispatchTable_ = dispatchTable; +export class LogReader { + constructor (dispatchTable, timedRange, pairwiseTimedRange) { + /** + * @type {Array.<Object>} + */ + this.dispatchTable_ = dispatchTable; + + /** + * @type {boolean} + */ + this.timedRange_ = timedRange; + + /** + * @type {boolean} + */ + this.pairwiseTimedRange_ = pairwiseTimedRange; + if (pairwiseTimedRange) { + this.timedRange_ = true; + } + + /** + * Current line. + * @type {number} + */ + this.lineNum_ = 0; + + /** + * CSV lines parser. + * @type {CsvParser} + */ + this.csvParser_ = new CsvParser(); + + /** + * Keeps track of whether we've seen a "current-time" tick yet. + * @type {boolean} + */ + this.hasSeenTimerMarker_ = false; + + /** + * List of log lines seen since last "current-time" tick. + * @type {Array.<String>} + */ + this.logLinesSinceLastTimerMarker_ = []; + } /** - * @type {boolean} + * Used for printing error messages. + * + * @param {string} str Error message. */ - this.timedRange_ = timedRange; + printError(str) { + // Do nothing. + } /** - * @type {boolean} + * Processes a portion of V8 profiler event log. + * + * @param {string} chunk A portion of log. */ - this.pairwiseTimedRange_ = pairwiseTimedRange; - if (pairwiseTimedRange) { - this.timedRange_ = true; + processLogChunk(chunk) { + this.processLog_(chunk.split('\n')); } /** - * Current line. - * @type {number} + * Processes a line of V8 profiler event log. + * + * @param {string} line A line of log. */ - this.lineNum_ = 0; + processLogLine(line) { + if (!this.timedRange_) { + this.processLogLine_(line); + return; + } + if (line.startsWith("current-time")) { + if (this.hasSeenTimerMarker_) { + this.processLog_(this.logLinesSinceLastTimerMarker_); + this.logLinesSinceLastTimerMarker_ = []; + // In pairwise mode, a "current-time" line ends the timed range. + if (this.pairwiseTimedRange_) { + this.hasSeenTimerMarker_ = false; + } + } else { + this.hasSeenTimerMarker_ = true; + } + } else { + if (this.hasSeenTimerMarker_) { + this.logLinesSinceLastTimerMarker_.push(line); + } else if (!line.startsWith("tick")) { + this.processLogLine_(line); + } + } + } /** - * CSV lines parser. - * @type {CsvParser} + * Processes stack record. + * + * @param {number} pc Program counter. + * @param {number} func JS Function. + * @param {Array.<string>} stack String representation of a stack. + * @return {Array.<number>} Processed stack. */ - this.csvParser_ = new CsvParser(); + processStack(pc, func, stack) { + const fullStack = func ? [pc, func] : [pc]; + let prevFrame = pc; + for (let i = 0, n = stack.length; i < n; ++i) { + const frame = stack[i]; + const firstChar = frame.charAt(0); + if (firstChar == '+' || firstChar == '-') { + // An offset from the previous frame. + prevFrame += parseInt(frame, 16); + fullStack.push(prevFrame); + // Filter out possible 'overflow' string. + } else if (firstChar != 'o') { + fullStack.push(parseInt(frame, 16)); + } else { + this.printError(`dropping: ${frame}`); + } + } + return fullStack; + } /** - * Keeps track of whether we've seen a "current-time" tick yet. - * @type {boolean} + * Returns whether a particular dispatch must be skipped. + * + * @param {!Object} dispatch Dispatch record. + * @return {boolean} True if dispatch must be skipped. */ - this.hasSeenTimerMarker_ = false; + skipDispatch(dispatch) { + return false; + } /** - * List of log lines seen since last "current-time" tick. - * @type {Array.<String>} + * Does a dispatch of a log record. + * + * @param {Array.<string>} fields Log record. + * @private */ - this.logLinesSinceLastTimerMarker_ = []; -}; - - -/** - * Used for printing error messages. - * - * @param {string} str Error message. - */ -LogReader.prototype.printError = function(str) { - // Do nothing. -}; - - -/** - * Processes a portion of V8 profiler event log. - * - * @param {string} chunk A portion of log. - */ -LogReader.prototype.processLogChunk = function(chunk) { - this.processLog_(chunk.split('\n')); -}; - - -/** - * Processes a line of V8 profiler event log. - * - * @param {string} line A line of log. - */ -LogReader.prototype.processLogLine = function(line) { - if (!this.timedRange_) { - this.processLogLine_(line); - return; - } - if (line.startsWith("current-time")) { - if (this.hasSeenTimerMarker_) { - this.processLog_(this.logLinesSinceLastTimerMarker_); - this.logLinesSinceLastTimerMarker_ = []; - // In pairwise mode, a "current-time" line ends the timed range. - if (this.pairwiseTimedRange_) { - this.hasSeenTimerMarker_ = false; - } - } else { - this.hasSeenTimerMarker_ = true; + dispatchLogRow_(fields) { + // Obtain the dispatch. + const command = fields[0]; + const dispatch = this.dispatchTable_[command]; + if (dispatch === undefined) return; + if (dispatch === null || this.skipDispatch(dispatch)) { + return; } - } else { - if (this.hasSeenTimerMarker_) { - this.logLinesSinceLastTimerMarker_.push(line); - } else if (!line.startsWith("tick")) { - this.processLogLine_(line); - } - } -}; - -/** - * Processes stack record. - * - * @param {number} pc Program counter. - * @param {number} func JS Function. - * @param {Array.<string>} stack String representation of a stack. - * @return {Array.<number>} Processed stack. - */ -LogReader.prototype.processStack = function(pc, func, stack) { - const fullStack = func ? [pc, func] : [pc]; - let prevFrame = pc; - for (let i = 0, n = stack.length; i < n; ++i) { - const frame = stack[i]; - const firstChar = frame.charAt(0); - if (firstChar == '+' || firstChar == '-') { - // An offset from the previous frame. - prevFrame += parseInt(frame, 16); - fullStack.push(prevFrame); - // Filter out possible 'overflow' string. - } else if (firstChar != 'o') { - fullStack.push(parseInt(frame, 16)); - } else { - this.printError(`dropping: ${frame}`); + // Parse fields. + const parsedFields = []; + for (let i = 0; i < dispatch.parsers.length; ++i) { + const parser = dispatch.parsers[i]; + if (parser === parseString) { + parsedFields.push(fields[1 + i]); + } else if (typeof parser == 'function') { + parsedFields.push(parser(fields[1 + i])); + } else if (parser === parseVarArgs) { + // var-args + parsedFields.push(fields.slice(1 + i)); + break; + } else { + throw new Error(`Invalid log field parser: ${parser}`); + } } - } - return fullStack; -}; - -/** - * Returns whether a particular dispatch must be skipped. - * - * @param {!Object} dispatch Dispatch record. - * @return {boolean} True if dispatch must be skipped. - */ -LogReader.prototype.skipDispatch = dispatch => false; - -// Parses dummy variable for readability; -export const parseString = 'parse-string'; -export const parseVarArgs = 'parse-var-args'; - -/** - * Does a dispatch of a log record. - * - * @param {Array.<string>} fields Log record. - * @private - */ -LogReader.prototype.dispatchLogRow_ = function(fields) { - // Obtain the dispatch. - const command = fields[0]; - const dispatch = this.dispatchTable_[command]; - if (dispatch === undefined) return; - if (dispatch === null || this.skipDispatch(dispatch)) { - return; + // Run the processor. + dispatch.processor.apply(this, parsedFields); } - // Parse fields. - const parsedFields = []; - for (let i = 0; i < dispatch.parsers.length; ++i) { - const parser = dispatch.parsers[i]; - if (parser === parseString) { - parsedFields.push(fields[1 + i]); - } else if (typeof parser == 'function') { - parsedFields.push(parser(fields[1 + i])); - } else if (parser === parseVarArgs) { - // var-args - parsedFields.push(fields.slice(1 + i)); - break; - } else { - throw new Error(`Invalid log field parser: ${parser}`); + /** + * Processes log lines. + * + * @param {Array.<string>} lines Log lines. + * @private + */ + processLog_(lines) { + for (let i = 0, n = lines.length; i < n; ++i) { + this.processLogLine_(lines[i]); } } - // Run the processor. - dispatch.processor.apply(this, parsedFields); -}; - - -/** - * Processes log lines. - * - * @param {Array.<string>} lines Log lines. - * @private - */ -LogReader.prototype.processLog_ = function(lines) { - for (let i = 0, n = lines.length; i < n; ++i) { - this.processLogLine_(lines[i]); - } -} - -/** - * Processes a single log line. - * - * @param {String} a log line - * @private - */ -LogReader.prototype.processLogLine_ = function(line) { - if (line.length > 0) { - try { - const fields = this.csvParser_.parseLine(line); - this.dispatchLogRow_(fields); - } catch (e) { - this.printError(`line ${this.lineNum_ + 1}: ${e.message || e}\n${e.stack}`); + /** + * Processes a single log line. + * + * @param {String} a log line + * @private + */ + processLogLine_(line) { + if (line.length > 0) { + try { + const fields = this.csvParser_.parseLine(line); + this.dispatchLogRow_(fields); + } catch (e) { + this.printError(`line ${this.lineNum_ + 1}: ${e.message || e}\n${e.stack}`); + } } + this.lineNum_++; } - this.lineNum_++; -}; +} diff --git a/deps/v8/tools/memory/asan/blacklist_win.txt b/deps/v8/tools/memory/asan/blacklist_win.txt deleted file mode 100644 index 2bb1aa9714..0000000000 --- a/deps/v8/tools/memory/asan/blacklist_win.txt +++ /dev/null @@ -1,4 +0,0 @@ -# The rules in this file are only applied at compile time. If you can modify the -# source in question, consider function attributes to disable instrumentation. -# -# Please think twice before you add or remove these rules.
\ No newline at end of file diff --git a/deps/v8/tools/memory/asan/blacklist.txt b/deps/v8/tools/memory/asan/blocklist_win.txt index 2bb1aa9714..2bb1aa9714 100644 --- a/deps/v8/tools/memory/asan/blacklist.txt +++ b/deps/v8/tools/memory/asan/blocklist_win.txt diff --git a/deps/v8/tools/parse-processor.mjs b/deps/v8/tools/parse-processor.mjs index f78c4c0261..debde68665 100644 --- a/deps/v8/tools/parse-processor.mjs +++ b/deps/v8/tools/parse-processor.mjs @@ -40,7 +40,7 @@ function BYTES(bytes, total) { unitIndex++; } let result = formatNumber(value).padStart(10) + ' ' + units[unitIndex]; - if (total !== void 0 && total != 0) { + if (total !== undefined && total != 0) { result += PERCENT(bytes, total).padStart(5); } return result; @@ -161,7 +161,7 @@ class CompilationUnit { class Script extends CompilationUnit { constructor(id) { super(); - if (id === void 0 || id <= 0) { + if (id === undefined || id <= 0) { throw new Error(`Invalid id=${id} for script`); } this.file = ''; @@ -214,7 +214,7 @@ class Script extends CompilationUnit { addMissingFunktions(list) { if (this.finalized) throw 'script is finalized!'; list.forEach(fn => { - if (this.funktions[fn.start] === void 0) { + if (this.funktions[fn.start] === undefined) { this.addFunktion(fn); } }); @@ -222,8 +222,8 @@ class Script extends CompilationUnit { addFunktion(fn) { if (this.finalized) throw 'script is finalized!'; - if (fn.start === void 0) throw "Funktion has no start position"; - if (this.funktions[fn.start] !== void 0) { + if (fn.start === undefined) throw "Funktion has no start position"; + if (this.funktions[fn.start] !== undefined) { fn.print(); throw "adding same function twice to script"; } @@ -441,7 +441,7 @@ class Script extends CompilationUnit { for (let i = 1; i < metricProperties.length; i += kMetricIncrement) { let timestampPropertyName = metricProperties[i]; let timestamp = funktionOrScript[timestampPropertyName]; - if (timestamp === void 0) continue; + if (timestamp === undefined) continue; if (timestamp < start || end < timestamp) continue; timestamp -= start; let index = Math.floor(timestamp / delta); @@ -906,7 +906,7 @@ export class ParseProcessor extends LogReader { } let script = this.lookupScript(scriptId); let funktion = script.getFunktionAtStartPosition(startPosition); - if (funktion === void 0) { + if (funktion === undefined) { funktion = new Funktion(functionName, startPosition, endPosition, script); } return funktion; diff --git a/deps/v8/tools/profile.mjs b/deps/v8/tools/profile.mjs index b2e953f247..f443740324 100644 --- a/deps/v8/tools/profile.mjs +++ b/deps/v8/tools/profile.mjs @@ -28,6 +28,7 @@ import { CodeMap, CodeEntry } from "./codemap.mjs"; import { ConsArray } from "./consarray.mjs"; +// Used to associate log entries with source positions in scripts. // TODO: move to separate modules export class SourcePosition { constructor(script, line, column) { @@ -36,19 +37,43 @@ export class SourcePosition { this.column = column; this.entries = []; } + addEntry(entry) { this.entries.push(entry); } + + toString() { + return `${this.script.name}:${this.line}:${this.column}`; + } + + toStringLong() { + return this.toString(); + } } export class Script { - constructor(id, name, source) { + name; + source; + // Map<line, Map<column, SourcePosition>> + lineToColumn = new Map(); + _entries = []; + + constructor(id) { this.id = id; + this.sourcePositions = []; + } + + update(name, source) { this.name = name; this.source = source; - this.sourcePositions = []; - // Map<line, Map<column, SourcePosition>> - this.lineToColumn = new Map(); + } + + get length() { + return this.source.length; + } + + get entries() { + return this._entries; } addSourcePosition(line, column, entry) { @@ -58,6 +83,7 @@ export class Script { this._addSourcePosition(line, column, sourcePosition); } sourcePosition.addEntry(entry); + this._entries.push(entry); return sourcePosition; } @@ -72,6 +98,42 @@ export class Script { this.sourcePositions.push(sourcePosition); columnToSourcePosition.set(column, sourcePosition); } + + toString() { + return `Script(${this.id}): ${this.name}`; + } + + toStringLong() { + return this.source; + } +} + + +class SourceInfo { + script; + start; + end; + positions; + inlined ; + fns; + disassemble; + + setSourcePositionInfo(script, startPos, endPos, sourcePositionTable, inliningPositions, inlinedFunctions) { + this.script = script; + this.start = startPos; + this.end = endPos; + this.positions = sourcePositionTable; + this.inlined = inliningPositions; + this.fns = inlinedFunctions; + } + + setDisassemble(code) { + this.disassemble = code; + } + + getSourceCode() { + return this.script.source?.substring(this.start, this.end); + } } /** @@ -118,8 +180,44 @@ export class Profile { */ static CodeState = { COMPILED: 0, - OPTIMIZABLE: 1, - OPTIMIZED: 2 + IGNITION: 1, + NATIVE_CONTEXT_INDEPENDENT: 2, + TURBOPROP: 3, + TURBOFAN: 4, + } + + /** + * Parser for dynamic code optimization state. + */ + static parseState(s) { + switch (s) { + case '': + return this.CodeState.COMPILED; + case '~': + return this.CodeState.IGNITION; + case '-': + return this.CodeState.NATIVE_CONTEXT_INDEPENDENT; + case '+': + return this.CodeState.TURBOPROP; + case '*': + return this.CodeState.TURBOFAN; + } + throw new Error(`unknown code state: ${s}`); + } + + static getKindFromState(state) { + if (state === this.CodeState.COMPILED) { + return "Builtin"; + } else if (state === this.CodeState.IGNITION) { + return "Unopt"; + } else if (state === this.CodeState.NATIVE_CONTEXT_INDEPENDENT) { + return "NCI"; + } else if (state === this.CodeState.TURBOPROP) { + return "Turboprop"; + } else if (state === this.CodeState.TURBOFAN) { + return "Opt"; + } + throw new Error(`unknown code state: ${state}`); } /** @@ -228,7 +326,7 @@ export class Profile { } } - deoptCode( timestamp, code, inliningId, scriptOffset, bailoutType, + deoptCode(timestamp, code, inliningId, scriptOffset, bailoutType, sourcePositionText, deoptReasonText) { } @@ -248,23 +346,59 @@ export class Profile { /** * Adds source positions for given code. */ - addSourcePositions(start, script, startPos, endPos, sourcePositions, + addSourcePositions(start, scriptId, startPos, endPos, sourcePositionTable, inliningPositions, inlinedFunctions) { - // CLI does not need source code => ignore. + const script = this.getOrCreateScript(scriptId); + const entry = this.codeMap_.findDynamicEntryByStartAddress(start); + if (!entry) return; + // Resolve the inlined functions list. + if (inlinedFunctions.length > 0) { + inlinedFunctions = inlinedFunctions.substring(1).split("S"); + for (let i = 0; i < inlinedFunctions.length; i++) { + const funcAddr = parseInt(inlinedFunctions[i]); + const func = this.codeMap_.findDynamicEntryByStartAddress(funcAddr); + if (!func || func.funcId === undefined) { + // TODO: fix + console.warn(`Could not find function ${inlinedFunctions[i]}`); + inlinedFunctions[i] = null; + } else { + inlinedFunctions[i] = func.funcId; + } + } + } else { + inlinedFunctions = []; + } + + this.getOrCreateSourceInfo(entry).setSourcePositionInfo( + script, startPos, endPos, sourcePositionTable, inliningPositions, + inlinedFunctions); + } + + addDisassemble(start, kind, disassemble) { + const entry = this.codeMap_.findDynamicEntryByStartAddress(start); + if (!entry) return; + this.getOrCreateSourceInfo(entry).setDisassemble(disassemble); + } + + getOrCreateSourceInfo(entry) { + return entry.source ?? (entry.source = new SourceInfo()); } - /** - * Adds script source code. - */ addScriptSource(id, url, source) { - const script = new Script(id, url, source); - this.scripts_[id] = script; + const script = this.getOrCreateScript(id); + script.update(url, source); this.urlToScript_.set(url, script); } - /** - * Adds script source code. - */ + getOrCreateScript(id) { + let script = this.scripts_[id]; + if (!script) { + script = new Script(id); + this.scripts_[id] = script; + } + return script; + } + getScript(url) { return this.urlToScript_.get(url); } @@ -547,10 +681,19 @@ class DynamicFuncCodeEntry extends CodeEntry { constructor(size, type, func, state) { super(size, '', type); this.func = func; + func.addDynamicCode(this); this.state = state; } - static STATE_PREFIX = ["", "~", "*"]; + get functionName() { + return this.func.functionName; + } + + getSourceCode() { + return this.source?.getSourceCode(); + } + + static STATE_PREFIX = ["", "~", "-", "+", "*"]; getState() { return DynamicFuncCodeEntry.STATE_PREFIX[this.state]; } @@ -583,8 +726,26 @@ class DynamicFuncCodeEntry extends CodeEntry { * @constructor */ class FunctionEntry extends CodeEntry { + + // Contains the list of generated code for this function. + _codeEntries = new Set(); + constructor(name) { super(0, name); + const index = name.lastIndexOf(' '); + this.functionName = 1 <= index ? name.substring(0, index) : '<anonymous>'; + } + + addDynamicCode(code) { + if (code.func != this) { + throw new Error("Adding dynamic code to wrong function"); + } + this._codeEntries.add(code); + } + + getSourceCode() { + // All code entries should map to the same source positions. + return this._codeEntries.values().next().value.getSourceCode(); } /** @@ -593,10 +754,10 @@ class FunctionEntry extends CodeEntry { getName() { let name = this.name; if (name.length == 0) { - name = '<anonymous>'; + return '<anonymous>'; } else if (name.charAt(0) == ' ') { // An anonymous function with location: " aaa.js:10". - name = `<anonymous>${name}`; + return `<anonymous>${name}`; } return name; } @@ -947,13 +1108,7 @@ JsonProfile.prototype.addFuncCode = function ( this.functionEntries_[func.funcId].codes.push(entry.codeId); - if (state === 0) { - kind = "Builtin"; - } else if (state === 1) { - kind = "Unopt"; - } else if (state === 2) { - kind = "Opt"; - } + kind = Profile.getKindFromState(state); this.codeEntries_.push({ name: entry.name, diff --git a/deps/v8/tools/profview/index.html b/deps/v8/tools/profview/index.html index 8695a41e3a..64f4e512c3 100644 --- a/deps/v8/tools/profview/index.html +++ b/deps/v8/tools/profview/index.html @@ -47,6 +47,25 @@ found in the LICENSE file. --> To view the profile, click the <i>Choose file</i> button above and choose the file in the dialog box. + <br> + <br> + For recording a profile from Chrome use: + <pre> + --no-sandbox --js-flags=--prof + </pre> + + If running on Android use also use --single-process: + <pre> + --single-process --no-sandbox --js-flags=--prof + </pre> + + If the profile is from Android pass the unstripped Chrome library to + resolve C++ symbols when processing the file using linux-tick-processor. + <pre> + <v8-dir>/tools/linux-tick-processor --preprocess --apk-embedded-library=./out/Release/lib.unstripped/libchrome.so v8.log > v8.json + </pre> + + More detailed instructions for Android here: https://v8.dev/docs/profile-chromium#android </div> <div id="timeline" style="display : none"> @@ -112,7 +131,7 @@ found in the LICENSE file. --> <br> <br> <br> -Copyright the V8 Authors - Last change to this page: 2018/08/13 +Copyright the V8 Authors - Last change to this page: 2020/12/04 </p> </body> diff --git a/deps/v8/tools/profview/profile-utils.js b/deps/v8/tools/profview/profile-utils.js index 4be55893dd..9007855ea9 100644 --- a/deps/v8/tools/profview/profile-utils.js +++ b/deps/v8/tools/profview/profile-utils.js @@ -19,7 +19,9 @@ let codeKinds = [ "BUILTIN", "REGEXP", "JSOPT", - "JSUNOPT" + "JSUNOPT", + "JSNCI", + "JSTURBOPROP" ]; function resolveCodeKind(code) { @@ -55,6 +57,10 @@ function resolveCodeKind(code) { return "JSOPT"; } else if (code.kind === "Unopt") { return "JSUNOPT"; + } else if (code.kind === "NCI") { + return "JSNCI"; + } else if (code.kind === "Turboprop") { + return "JSTURBOPROP"; } } console.log("Unknown code type '" + type + "'."); @@ -264,6 +270,8 @@ function buildCategoryTreeAndLookup() { root.children.push(n); } addCategory("JS Optimized", [ "JSOPT" ]); + addCategory("JS NCI", [ "JSNCI" ]); + addCategory("JS Turboprop", [ "JSTURBOPROP" ]); addCategory("JS Unoptimized", [ "JSUNOPT", "BC" ]); addCategory("IC", [ "IC" ]); addCategory("RegExp", [ "REGEXP" ]); @@ -526,11 +534,15 @@ function computeOptimizationStats(file, let functionCount = 0; let optimizedFunctionCount = 0; + let turbopropOptimizedFunctionCount = 0; let deoptimizedFunctionCount = 0; let optimizations = newCollection(); + let turbopropOptimizations = newCollection(); let eagerDeoptimizations = newCollection(); let softDeoptimizations = newCollection(); let lazyDeoptimizations = newCollection(); + let softBailouts = newCollection(); + let eagerBailouts = newCollection(); for (let i = 0; i < file.functions.length; i++) { let f = file.functions[i]; @@ -541,6 +553,7 @@ function computeOptimizationStats(file, functionCount++; let optimized = false; + let turboprop_optimized = false; let deoptimized = false; for (let j = 0; j < f.codes.length; j++) { @@ -552,19 +565,33 @@ function computeOptimizationStats(file, addToCollection(optimizations, code); } } + if (code.kind === "Turboprop") { + turboprop_optimized = true; + if (code.tm >= timeStart && code.tm <= timeEnd) { + addToCollection(turbopropOptimizations, code); + } + } if (code.deopt) { - deoptimized = true; + if (code.deopt.bailoutType === "deopt-lazy" || code.deopt.bailoutType === "deopt-eager" || code.deopt.bailoutType === "deopt-lazy") { + deoptimized = true; + } if (code.deopt.tm >= timeStart && code.deopt.tm <= timeEnd) { switch (code.deopt.bailoutType) { - case "lazy": + case "deopt-lazy": addToCollection(lazyDeoptimizations, code); break; - case "eager": + case "deopt-eager": addToCollection(eagerDeoptimizations, code); break; - case "soft": + case "deopt-soft": addToCollection(softDeoptimizations, code); break; + case "bailout-soft": + addToCollection(softBailouts, code); + break; + case "bailout": + addToCollection(eagerBailouts, code); + break; } } } @@ -572,6 +599,9 @@ function computeOptimizationStats(file, if (optimized) { optimizedFunctionCount++; } + if (turboprop_optimized) { + turbopropOptimizedFunctionCount++; + } if (deoptimized) { deoptimizedFunctionCount++; } @@ -586,15 +616,20 @@ function computeOptimizationStats(file, sortCollection(lazyDeoptimizations); sortCollection(softDeoptimizations); sortCollection(optimizations); + sortCollection(turbopropOptimizations); return { functionCount, optimizedFunctionCount, + turbopropOptimizedFunctionCount, deoptimizedFunctionCount, optimizations, + turbopropOptimizations, eagerDeoptimizations, lazyDeoptimizations, softDeoptimizations, + softBailouts, + eagerBailouts, }; } diff --git a/deps/v8/tools/profview/profview.js b/deps/v8/tools/profview/profview.js index 210cec7618..248146f99f 100644 --- a/deps/v8/tools/profview/profview.js +++ b/deps/v8/tools/profview/profview.js @@ -216,6 +216,14 @@ const bucketDescriptors = color : "#64dd17", backgroundColor : "#80e27e", text : "JS Optimized" }, + { kinds : [ "JSNCI" ], + color : "#3289a8", + backgroundColor : "#3289a8", + text : "JS NCI" }, + { kinds : [ "JSTURBOPROP" ], + color : "#693eb8", + backgroundColor : "#a6c452", + text : "JS Turboprop" }, { kinds : [ "JSUNOPT", "BC" ], color : "#dd2c00", backgroundColor : "#ff9e80", @@ -308,6 +316,10 @@ function codeTypeToText(type) { return "RegExp"; case "JSOPT": return "JS opt"; + case "JSNCI": + return "JS NCI"; + case "JSTURBOPROP": + return "JS Turboprop"; case "JSUNOPT": return "JS unopt"; } @@ -1275,15 +1287,27 @@ class SummaryView { addRow("Total function count:", stats.functionCount); addRow("Optimized function count:", stats.optimizedFunctionCount, 1); + if (stats.turbopropOptimizedFunctionCount != 0) { + addRow("Turboprop optimized function count:", stats.turbopropOptimizedFunctionCount, 1); + } addRow("Deoptimized function count:", stats.deoptimizedFunctionCount, 2); addExpandableRow("Optimization count:", stats.optimizations, 0, "opt"); + if (stats.turbopropOptimizedFunctionCount != 0) { + addExpandableRow("Turboprop Optimization count:", stats.turbopropOptimizations, 0, "tp"); + } let deoptCount = stats.eagerDeoptimizations.count + stats.softDeoptimizations.count + stats.lazyDeoptimizations.count; addRow("Deoptimization count:", deoptCount); addExpandableRow("Eager:", stats.eagerDeoptimizations, 1, "eager"); addExpandableRow("Lazy:", stats.lazyDeoptimizations, 1, "lazy"); addExpandableRow("Soft:", stats.softDeoptimizations, 1, "soft"); + if (stats.softBailouts.count != 0) { + addExpandableRow("SoftBailout:", stats.softBailouts, 1, "softbailout"); + } + if (stats.eagerBailouts.count != 0) { + addExpandableRow("EagerBailout:", stats.eagerBailouts, 1, "eagerbailout"); + } table.appendChild(rows); this.element.appendChild(table); @@ -1369,6 +1393,7 @@ class SourceData { for (let i = 0; i < file.scripts.length; i++) { const scriptBlock = file.scripts[i]; if (scriptBlock === null) continue; // Array may be sparse. + if (scriptBlock.source === undefined) continue; let source = scriptBlock.source.split("\n"); this.scripts.set(i, source); } diff --git a/deps/v8/tools/splaytree.mjs b/deps/v8/tools/splaytree.mjs index eaba4e4b57..d942d1f463 100644 --- a/deps/v8/tools/splaytree.mjs +++ b/deps/v8/tools/splaytree.mjs @@ -34,274 +34,250 @@ * * @constructor */ -export function SplayTree() { -}; - - -/** - * Pointer to the root node of the tree. - * - * @type {SplayTree.Node} - * @private - */ -SplayTree.prototype.root_ = null; - - -/** - * @return {boolean} Whether the tree is empty. - */ -SplayTree.prototype.isEmpty = function() { - return !this.root_; -}; - - - -/** - * Inserts a node into the tree with the specified key and value if - * the tree does not already contain a node with the specified key. If - * the value is inserted, it becomes the root of the tree. - * - * @param {number} key Key to insert into the tree. - * @param {*} value Value to insert into the tree. - */ -SplayTree.prototype.insert = function(key, value) { - if (this.isEmpty()) { - this.root_ = new SplayTree.Node(key, value); - return; +export class SplayTree { + + /** + * Pointer to the root node of the tree. + * + * @type {SplayTreeNode} + * @private + */ + root_ = null; + + + /** + * @return {boolean} Whether the tree is empty. + */ + isEmpty() { + return !this.root_; } - // Splay on the key to move the last node on the search path for - // the key to the root of the tree. - this.splay_(key); - if (this.root_.key == key) { - return; - } - const node = new SplayTree.Node(key, value); - if (key > this.root_.key) { - node.left = this.root_; - node.right = this.root_.right; - this.root_.right = null; - } else { - node.right = this.root_; - node.left = this.root_.left; - this.root_.left = null; - } - this.root_ = node; -}; + /** + * Inserts a node into the tree with the specified key and value if + * the tree does not already contain a node with the specified key. If + * the value is inserted, it becomes the root of the tree. + * + * @param {number} key Key to insert into the tree. + * @param {*} value Value to insert into the tree. + */ + insert(key, value) { + if (this.isEmpty()) { + this.root_ = new SplayTreeNode(key, value); + return; + } + // Splay on the key to move the last node on the search path for + // the key to the root of the tree. + this.splay_(key); + if (this.root_.key == key) return; -/** - * Removes a node with the specified key from the tree if the tree - * contains a node with this key. The removed node is returned. If the - * key is not found, an exception is thrown. - * - * @param {number} key Key to find and remove from the tree. - * @return {SplayTree.Node} The removed node. - */ -SplayTree.prototype.remove = function(key) { - if (this.isEmpty()) { - throw Error(`Key not found: ${key}`); - } - this.splay_(key); - if (this.root_.key != key) { - throw Error(`Key not found: ${key}`); + const node = new SplayTreeNode(key, value); + if (key > this.root_.key) { + node.left = this.root_; + node.right = this.root_.right; + this.root_.right = null; + } else { + node.right = this.root_; + node.left = this.root_.left; + this.root_.left = null; + } + this.root_ = node; } - const removed = this.root_; - if (!this.root_.left) { - this.root_ = this.root_.right; - } else { - const { right } = this.root_; - this.root_ = this.root_.left; - // Splay to make sure that the new root has an empty right child. + + /** + * Removes a node with the specified key from the tree if the tree + * contains a node with this key. The removed node is returned. If the + * key is not found, an exception is thrown. + * + * @param {number} key Key to find and remove from the tree. + * @return {SplayTreeNode} The removed node. + */ + remove(key) { + if (this.isEmpty()) { + throw Error(`Key not found: ${key}`); + } this.splay_(key); - // Insert the original right child as the right child of the new - // root. - this.root_.right = right; + if (this.root_.key != key) { + throw Error(`Key not found: ${key}`); + } + const removed = this.root_; + if (!this.root_.left) { + this.root_ = this.root_.right; + } else { + const { right } = this.root_; + this.root_ = this.root_.left; + // Splay to make sure that the new root has an empty right child. + this.splay_(key); + // Insert the original right child as the right child of the new + // root. + this.root_.right = right; + } + return removed; } - return removed; -}; - -/** - * Returns the node having the specified key or null if the tree doesn't contain - * a node with the specified key. - * - * @param {number} key Key to find in the tree. - * @return {SplayTree.Node} Node having the specified key. - */ -SplayTree.prototype.find = function(key) { - if (this.isEmpty()) { - return null; + /** + * Returns the node having the specified key or null if the tree doesn't contain + * a node with the specified key. + * + * @param {number} key Key to find in the tree. + * @return {SplayTreeNode} Node having the specified key. + */ + find(key) { + if (this.isEmpty()) return null; + this.splay_(key); + return this.root_.key == key ? this.root_ : null; } - this.splay_(key); - return this.root_.key == key ? this.root_ : null; -}; - -/** - * @return {SplayTree.Node} Node having the minimum key value. - */ -SplayTree.prototype.findMin = function() { - if (this.isEmpty()) { - return null; - } - let current = this.root_; - while (current.left) { - current = current.left; + /** + * @return {SplayTreeNode} Node having the minimum key value. + */ + findMin() { + if (this.isEmpty()) return null; + let current = this.root_; + while (current.left) { + current = current.left; + } + return current; } - return current; -}; - -/** - * @return {SplayTree.Node} Node having the maximum key value. - */ -SplayTree.prototype.findMax = function(opt_startNode) { - if (this.isEmpty()) { - return null; - } - let current = opt_startNode || this.root_; - while (current.right) { - current = current.right; + /** + * @return {SplayTreeNode} Node having the maximum key value. + */ + findMax(opt_startNode) { + if (this.isEmpty()) return null; + let current = opt_startNode || this.root_; + while (current.right) { + current = current.right; + } + return current; } - return current; -}; - -/** - * @return {SplayTree.Node} Node having the maximum key value that - * is less or equal to the specified key value. - */ -SplayTree.prototype.findGreatestLessThan = function(key) { - if (this.isEmpty()) { - return null; - } - // Splay on the key to move the node with the given key or the last - // node on the search path to the top of the tree. - this.splay_(key); - // Now the result is either the root node or the greatest node in - // the left subtree. - if (this.root_.key <= key) { - return this.root_; - } else if (this.root_.left) { - return this.findMax(this.root_.left); - } else { - return null; + /** + * @return {SplayTreeNode} Node having the maximum key value that + * is less or equal to the specified key value. + */ + findGreatestLessThan(key) { + if (this.isEmpty()) return null; + // Splay on the key to move the node with the given key or the last + // node on the search path to the top of the tree. + this.splay_(key); + // Now the result is either the root node or the greatest node in + // the left subtree. + if (this.root_.key <= key) { + return this.root_; + } else if (this.root_.left) { + return this.findMax(this.root_.left); + } else { + return null; + } } -}; - - -/** - * @return {Array<*>} An array containing all the values of tree's nodes paired - * with keys. - */ -SplayTree.prototype.exportKeysAndValues = function() { - const result = []; - this.traverse_(function(node) { result.push([node.key, node.value]); }); - return result; -}; - - -/** - * @return {Array<*>} An array containing all the values of tree's nodes. - */ -SplayTree.prototype.exportValues = function() { - const result = []; - this.traverse_(function(node) { result.push(node.value); }); - return result; -}; + /** + * @return {Array<*>} An array containing all the values of tree's nodes paired + * with keys. + */ + exportKeysAndValues() { + const result = []; + this.traverse_(function(node) { result.push([node.key, node.value]); }); + return result; + } -/** - * Perform the splay operation for the given key. Moves the node with - * the given key to the top of the tree. If no node has the given - * key, the last node on the search path is moved to the top of the - * tree. This is the simplified top-down splaying algorithm from: - * "Self-adjusting Binary Search Trees" by Sleator and Tarjan - * - * @param {number} key Key to splay the tree on. - * @private - */ -SplayTree.prototype.splay_ = function(key) { - if (this.isEmpty()) { - return; + /** + * @return {Array<*>} An array containing all the values of tree's nodes. + */ + exportValues() { + const result = []; + this.traverse_(function(node) { result.push(node.value); }); + return result; } - // Create a dummy node. The use of the dummy node is a bit - // counter-intuitive: The right child of the dummy node will hold - // the L tree of the algorithm. The left child of the dummy node - // will hold the R tree of the algorithm. Using a dummy node, left - // and right will always be nodes and we avoid special cases. - let dummy, left, right; - dummy = left = right = new SplayTree.Node(null, null); - let current = this.root_; - while (true) { - if (key < current.key) { - if (!current.left) { - break; - } - if (key < current.left.key) { - // Rotate right. - const tmp = current.left; - current.left = tmp.right; - tmp.right = current; - current = tmp; + + /** + * Perform the splay operation for the given key. Moves the node with + * the given key to the top of the tree. If no node has the given + * key, the last node on the search path is moved to the top of the + * tree. This is the simplified top-down splaying algorithm from: + * "Self-adjusting Binary Search Trees" by Sleator and Tarjan + * + * @param {number} key Key to splay the tree on. + * @private + */ + splay_(key) { + if (this.isEmpty()) return; + // Create a dummy node. The use of the dummy node is a bit + // counter-intuitive: The right child of the dummy node will hold + // the L tree of the algorithm. The left child of the dummy node + // will hold the R tree of the algorithm. Using a dummy node, left + // and right will always be nodes and we avoid special cases. + let dummy, left, right; + dummy = left = right = new SplayTreeNode(null, null); + let current = this.root_; + while (true) { + if (key < current.key) { if (!current.left) { break; } - } - // Link right. - right.left = current; - right = current; - current = current.left; - } else if (key > current.key) { - if (!current.right) { - break; - } - if (key > current.right.key) { - // Rotate left. - const tmp = current.right; - current.right = tmp.left; - tmp.left = current; - current = tmp; + if (key < current.left.key) { + // Rotate right. + const tmp = current.left; + current.left = tmp.right; + tmp.right = current; + current = tmp; + if (!current.left) { + break; + } + } + // Link right. + right.left = current; + right = current; + current = current.left; + } else if (key > current.key) { if (!current.right) { break; } + if (key > current.right.key) { + // Rotate left. + const tmp = current.right; + current.right = tmp.left; + tmp.left = current; + current = tmp; + if (!current.right) { + break; + } + } + // Link left. + left.right = current; + left = current; + current = current.right; + } else { + break; } - // Link left. - left.right = current; - left = current; - current = current.right; - } else { - break; } + // Assemble. + left.right = current.left; + right.left = current.right; + current.left = dummy.right; + current.right = dummy.left; + this.root_ = current; } - // Assemble. - left.right = current.left; - right.left = current.right; - current.left = dummy.right; - current.right = dummy.left; - this.root_ = current; -}; - -/** - * Performs a preorder traversal of the tree. - * - * @param {function(SplayTree.Node)} f Visitor function. - * @private - */ -SplayTree.prototype.traverse_ = function(f) { - const nodesToVisit = [this.root_]; - while (nodesToVisit.length > 0) { - const node = nodesToVisit.shift(); - if (node == null) { - continue; + /** + * Performs a preorder traversal of the tree. + * + * @param {function(SplayTreeNode)} f Visitor function. + * @private + */ + traverse_(f) { + const nodesToVisit = [this.root_]; + while (nodesToVisit.length > 0) { + const node = nodesToVisit.shift(); + if (node == null) { + continue; + } + f(node); + nodesToVisit.push(node.left); + nodesToVisit.push(node.right); } - f(node); - nodesToVisit.push(node.left); - nodesToVisit.push(node.right); } -}; - +} /** * Constructs a Splay tree node. @@ -309,19 +285,17 @@ SplayTree.prototype.traverse_ = function(f) { * @param {number} key Key. * @param {*} value Value. */ -SplayTree.Node = function(key, value) { - this.key = key; - this.value = value; -}; - - -/** - * @type {SplayTree.Node} - */ -SplayTree.Node.prototype.left = null; - - -/** - * @type {SplayTree.Node} - */ -SplayTree.Node.prototype.right = null; +class SplayTreeNode { + constructor(key, value) { + this.key = key; + this.value = value; + /** + * @type {SplayTreeNode} + */ + this.left = null; + /** + * @type {SplayTreeNode} + */ + this.right = null; + } +};
\ No newline at end of file diff --git a/deps/v8/tools/system-analyzer/app-model.mjs b/deps/v8/tools/system-analyzer/app-model.mjs index a0b176c170..5d558e248b 100644 --- a/deps/v8/tools/system-analyzer/app-model.mjs +++ b/deps/v8/tools/system-analyzer/app-model.mjs @@ -9,17 +9,22 @@ class State { _selectedMapLogEntries; _selectedIcLogEntries; _selectedDeoptLogEntries; + _selecteCodeLogEntries; _selectedSourcePositions; _nofChunks; _chunks; _icTimeline; _mapTimeline; _deoptTimeline; + _codeTimeline; + _apiTimeline; _minStartTime = Number.POSITIVE_INFINITY; _maxEndTime = Number.NEGATIVE_INFINITY; + get minStartTime() { return this._minStartTime; } + get maxEndTime() { return this._maxEndTime; } @@ -27,46 +32,68 @@ class State { selectTimeRange(start, end) { this.timeSelection.start = start; this.timeSelection.end = end; - this._icTimeline.selectTimeRange(start, end); - this._mapTimeline.selectTimeRange(start, end); - this._deoptTimeline.selectTimeRange(start, end); + if (start == 0 && end == Infinity) { + this.timelines.forEach(each => each.clearSelection()); + } else { + this.timelines.forEach(each => each.selectTimeRange(start, end)); + } } - _updateTimeRange(timeline) { - this._minStartTime = Math.min(this._minStartTime, timeline.startTime); - this._maxEndTime = Math.max(this._maxEndTime, timeline.endTime); - timeline.startTime = this._minStartTime; - timeline.endTime = this._maxEndTime; + setTimelines( + mapTimeline, icTimeline, deoptTimeline, codeTimeline, apiTimeline) { + this._mapTimeline = mapTimeline; + this._icTimeline = icTimeline; + this._deoptTimeline = deoptTimeline; + this._codeTimeline = codeTimeline; + this._apiTimeline = apiTimeline; + for (let timeline of arguments) { + if (timeline === undefined) return; + this._minStartTime = Math.min(this._minStartTime, timeline.startTime); + this._maxEndTime = Math.max(this._maxEndTime, timeline.endTime); + } + for (let timeline of arguments) { + timeline.startTime = this._minStartTime; + timeline.endTime = this._maxEndTime; + } } + get mapTimeline() { return this._mapTimeline; } - set mapTimeline(timeline) { - this._updateTimeRange(timeline); - this._mapTimeline = timeline; - } + get icTimeline() { return this._icTimeline; } - set icTimeline(timeline) { - this._updateTimeRange(timeline); - this._icTimeline = timeline; - } + get deoptTimeline() { return this._deoptTimeline; } - set deoptTimeline(timeline) { - this._updateTimeRange(timeline); - this._deoptTimeline = timeline; + + get codeTimeline() { + return this._codeTimeline; } + + get apiTimeline() { + return this._apiTimeline; + } + + get timelines() { + return [ + this._mapTimeline, this._icTimeline, this._deoptTimeline, + this._codeTimeline, this._apiTimeline + ]; + } + set chunks(value) { // TODO(zcankara) split up between maps and ics, and every timeline track this._chunks = value; } + get chunks() { // TODO(zcankara) split up between maps and ics, and every timeline track return this._chunks; } + get nofChunks() { return this._nofChunks; } diff --git a/deps/v8/tools/system-analyzer/events.mjs b/deps/v8/tools/system-analyzer/events.mjs deleted file mode 100644 index 69529233b4..0000000000 --- a/deps/v8/tools/system-analyzer/events.mjs +++ /dev/null @@ -1,51 +0,0 @@ -// 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. - -class SelectionEvent extends CustomEvent { - // TODO: turn into static class fields once Safari supports it. - static get name() { - return 'showentries'; - } - constructor(entries) { - super(SelectionEvent.name, {bubbles: true, composed: true}); - if (!Array.isArray(entries) || entries.length == 0) { - throw new Error('No valid entries selected!'); - } - this.entries = entries; - } -} - -class FocusEvent extends CustomEvent { - static get name() { - return 'showentrydetail'; - } - constructor(entry) { - super(FocusEvent.name, {bubbles: true, composed: true}); - this.entry = entry; - } -} - -class SelectTimeEvent extends CustomEvent { - static get name() { - return 'timerangeselect'; - } - constructor(start, end) { - super(SelectTimeEvent.name, {bubbles: true, composed: true}); - this.start = start; - this.end = end; - } -} - -class SynchronizeSelectionEvent extends CustomEvent { - static get name() { - return 'syncselection'; - } - constructor(start, end) { - super(SynchronizeSelectionEvent.name, {bubbles: true, composed: true}); - this.start = start; - this.end = end; - } -} - -export {SelectionEvent, FocusEvent, SelectTimeEvent, SynchronizeSelectionEvent}; diff --git a/deps/v8/tools/system-analyzer/helper.mjs b/deps/v8/tools/system-analyzer/helper.mjs index 854a51fcf3..1eccfbdc93 100644 --- a/deps/v8/tools/system-analyzer/helper.mjs +++ b/deps/v8/tools/system-analyzer/helper.mjs @@ -2,12 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -const KB = 1024; -const MB = KB * KB; -const GB = MB * KB; -const kMillis2Seconds = 1 / 1000; +export const KB = 1024; +export const MB = KB * KB; +export const GB = MB * KB; +export const kMicro2Milli = 1 / 1000; -function formatBytes(bytes) { +export function formatBytes(bytes) { const units = ['B', 'KiB', 'MiB', 'GiB']; const divisor = 1024; let index = 0; @@ -18,230 +18,57 @@ function formatBytes(bytes) { return bytes.toFixed(2) + units[index]; } -function formatSeconds(millis) { - return (millis * kMillis2Seconds).toFixed(2) + 's'; +export function formatMicroSeconds(millis) { + return (millis * kMicro2Milli).toFixed(1) + 'ms'; } -class CSSColor { - static getColor(name) { - const style = getComputedStyle(document.body); - return style.getPropertyValue(`--${name}`); - } - static get backgroundColor() { - return CSSColor.getColor('backgroud-color'); - } - static get surfaceColor() { - return CSSColor.getColor('surface-color'); - } - static get primaryColor() { - return CSSColor.getColor('primary-color'); - } - static get secondaryColor() { - return CSSColor.getColor('secondary-color'); - } - static get onSurfaceColor() { - return CSSColor.getColor('on-surface-color'); - } - static get onBackgroundColor() { - return CSSColor.getColor('on-background-color'); - } - static get onPrimaryColor() { - return CSSColor.getColor('on-primary-color'); - } - static get onSecondaryColor() { - return CSSColor.getColor('on-secondary-color'); - } - static get defaultColor() { - return CSSColor.getColor('default-color'); - } - static get errorColor() { - return CSSColor.getColor('error-color'); - } - static get mapBackgroundColor() { - return CSSColor.getColor('map-background-color'); - } - static get timelineBackgroundColor() { - return CSSColor.getColor('timeline-background-color'); - } - static get red() { - return CSSColor.getColor('red'); - } - static get green() { - return CSSColor.getColor('green'); - } - static get yellow() { - return CSSColor.getColor('yellow'); - } - static get blue() { - return CSSColor.getColor('blue'); - } - static get orange() { - return CSSColor.getColor('orange'); - } - static get violet() { - return CSSColor.getColor('violet'); - } -} - -function typeToColor(type) { - switch (type) { - case 'new': - return CSSColor.green; - case 'Normalize': - return CSSColor.violet; - case 'SlowToFast': - return CSSColor.orange; - case 'InitialMap': - return CSSColor.yellow; - case 'Transition': - return CSSColor.primaryColor; - case 'ReplaceDescriptors': - return CSSColor.red; - case 'LoadGlobalIC': - return CSSColor.green; - case 'LoadIC': - return CSSColor.primaryColor; - case 'StoreInArrayLiteralIC': - return CSSColor.violet; - case 'StoreGlobalIC': - return CSSColor.blue; - case 'StoreIC': - return CSSColor.orange; - case 'KeyedLoadIC': - return CSSColor.red; - case 'KeyedStoreIC': - return CSSColor.yellow; - } - return CSSColor.secondaryColor; -} - -class DOM { - static div(classes) { - const node = document.createElement('div'); - if (classes !== void 0) { - if (typeof classes === 'string') { - node.classList.add(classes); - } else { - classes.forEach(cls => node.classList.add(cls)); - } - } - return node; - } - - static table(className) { - const node = document.createElement('table'); - if (className) node.classList.add(className); - return node; - } - - static td(textOrNode, className) { - const node = document.createElement('td'); - if (typeof textOrNode === 'object') { - node.appendChild(textOrNode); - } else if (textOrNode) { - node.innerText = textOrNode; - } - if (className) node.classList.add(className); - return node; - } - - static tr(className) { - const node = document.createElement('tr'); - if (className) node.classList.add(className); - return node; - } - - static text(string) { - return document.createTextNode(string); - } - - static removeAllChildren(node) { - let range = document.createRange(); - range.selectNodeContents(node); - range.deleteContents(); - } - - static defineCustomElement(path, generator) { - let name = path.substring(path.lastIndexOf('/') + 1, path.length); - path = path + '-template.html'; - fetch(path) - .then(stream => stream.text()) - .then( - templateText => - customElements.define(name, generator(templateText))); - } -} - -function $(id) { - return document.querySelector(id) +export function delay(time) { + return new Promise(resolver => setTimeout(resolver, time)); } -class V8CustomElement extends HTMLElement { - _updateTimeoutId; - _updateCallback = this._update.bind(this); - - constructor(templateText) { - super(); - const shadowRoot = this.attachShadow({mode: 'open'}); - shadowRoot.innerHTML = templateText; - } - - $(id) { - return this.shadowRoot.querySelector(id); +export class Group { + constructor(key, id, parentTotal, entries) { + this.key = key; + this.id = id; + this.count = 1; + this.entries = entries; + this.parentTotal = parentTotal; } - querySelectorAll(query) { - return this.shadowRoot.querySelectorAll(query); + get percent() { + return this.count / this.parentTotal * 100; } - update() { - // Use timeout tasks to asynchronously update the UI without blocking. - clearTimeout(this._updateTimeoutId); - const kDelayMs = 5; - this._updateTimeoutId = setTimeout(this._updateCallback, kDelayMs); + add() { + this.count++; } - _update() { - throw Error('Subclass responsibility'); + addEntry(entry) { + this.count++; + this.entries.push(entry); } } -class LazyTable { - constructor(table, rowData, rowElementCreator) { - this._table = table; - this._rowData = rowData; - this._rowElementCreator = rowElementCreator; - const tbody = table.querySelector('tbody'); - table.replaceChild(document.createElement('tbody'), tbody); - table.querySelector('tfoot td').onclick = (e) => this._addMoreRows(); - this._addMoreRows(); - } - - _nextRowDataSlice() { - return this._rowData.splice(0, 100); - } - - _addMoreRows() { - const fragment = new DocumentFragment(); - for (let row of this._nextRowDataSlice()) { - const tr = this._rowElementCreator(row); - fragment.appendChild(tr); +export function groupBy(array, keyFunction, collect = false) { + if (array.length === 0) return []; + if (keyFunction === undefined) keyFunction = each => each; + const keyToGroup = new Map(); + const groups = []; + let id = 0; + // This is performance critical, resorting to for-loop + for (let each of array) { + const key = keyFunction(each); + let group = keyToGroup.get(key); + if (group !== undefined) { + collect ? group.addEntry(each) : group.add(); + continue; } - this._table.querySelector('tbody').appendChild(fragment); - } -} - -function delay(time) { - return new Promise(resolver => setTimeout(resolver, time)); -} - -export { - DOM, - $, - V8CustomElement, - formatBytes, - typeToColor, - CSSColor, - delay, - LazyTable, -}; + let entries = undefined; + if (collect) entries = [each]; + group = new Group(key, id++, array.length, entries); + groups.push(group); + keyToGroup.set(key, group); + } + // Sort by count + return groups.sort((a, b) => b.count - a.count); +}
\ No newline at end of file diff --git a/deps/v8/tools/system-analyzer/ic-model.mjs b/deps/v8/tools/system-analyzer/ic-model.mjs deleted file mode 100644 index 2bb40b6853..0000000000 --- a/deps/v8/tools/system-analyzer/ic-model.mjs +++ /dev/null @@ -1,57 +0,0 @@ -// 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. - -import {IcLogEntry} from './log/ic.mjs'; - -// For compatibility with console scripts: -print = console.log; - -export class Group { - constructor(property, key, entry) { - this.property = property; - this.key = key; - this.count = 1; - this.entries = [entry]; - this.percentage = undefined; - this.groups = undefined; - } - - add(entry) { - this.count++; - this.entries.push(entry) - } - - createSubGroups() { - // TODO: use Map - this.groups = {}; - for (const propertyName of IcLogEntry.propertyNames) { - if (this.property == propertyName) continue; - this.groups[propertyName] = Group.groupBy(this.entries, propertyName); - } - } - - static groupBy(entries, property) { - let accumulator = Object.create(null); - let length = entries.length; - for (let i = 0; i < length; i++) { - let entry = entries[i]; - let key = entry[property]; - if (accumulator[key] == undefined) { - accumulator[key] = new Group(property, key, entry); - } else { - let group = accumulator[key]; - if (group.entries == undefined) console.log([group, entry]); - group.add(entry) - } - } - let result = []; - for (let key in accumulator) { - let group = accumulator[key]; - group.percentage = Math.round(group.count / length * 100 * 100) / 100; - result.push(group); - } - result.sort((a, b) => {return b.count - a.count}); - return result; - } -} diff --git a/deps/v8/tools/system-analyzer/ic-panel-template.html b/deps/v8/tools/system-analyzer/ic-panel-template.html deleted file mode 100644 index ee08901fb0..0000000000 --- a/deps/v8/tools/system-analyzer/ic-panel-template.html +++ /dev/null @@ -1,98 +0,0 @@ -<!-- 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. --> - -<head> - <link href="./index.css" rel="stylesheet"> -</head> -<style> - .count { - text-align: right; - width: 5em; - } - - .percentage { - text-align: right; - width: 5em; - } - - .key { - padding-left: 1em; - } - - .drilldown-group-title { - font-weight: bold; - padding: 0.5em 0 0.2em 0; - } - - .toggle { - width: 1em; - text-align: center; - cursor: -webkit-zoom-in; - color: rgba(var(--border-color), 1); - } - .toggle::before { - content: "â–¶"; - } - .open .toggle::before { - content: "â–¼"; - } - - .panel { - position: relative; - min-height: 200px; - } - - #legend { - position: absolute; - right: 10px; - top: 10px; - background-color: var(--surface-color); - border-radius: 5px; - border: 3px solid rgba(var(--border-color), 0.2); - padding: 0 10px 0 10px; - } - - #legend dt { - font-family: monospace; - } - #legend h3 { - margin-top: 10px; - } - .scroller { - max-height: 800px; - overflow-y: scroll; - } -</style> -<div class="panel"> - <h2>IC Panel <span id="count"></span></h2> - <div id="legend"> - <h3>Legend</h3> - <dl> - <dt>0</dt> - <dd>uninitialized</dd> - <dt>X</dt> - <dd>no feedback</dd> - <dt>1</dt> - <dd>monomorphic</dd> - <dt>^</dt> - <dd>recompute handler</dd> - <dt>P</dt> - <dd>polymorphic</dd> - <dt>N</dt> - <dd>megamorphic</dd> - <dt>G</dt> - <dd>generic</dd> - </dl> - </div> - <p> - Group by IC-property: - <select id="group-key"></select> - </p> - <div class="panelBody"> - <table id="table" width="100%"> - <tbody id="table-body"> - </tbody> - </table> - </div> -</div> diff --git a/deps/v8/tools/system-analyzer/ic-panel.mjs b/deps/v8/tools/system-analyzer/ic-panel.mjs deleted file mode 100644 index d81d06d0d6..0000000000 --- a/deps/v8/tools/system-analyzer/ic-panel.mjs +++ /dev/null @@ -1,191 +0,0 @@ -// 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. - -import {FocusEvent, SelectionEvent, SelectTimeEvent} from './events.mjs'; -import {delay, DOM, V8CustomElement} from './helper.mjs'; -import {Group} from './ic-model.mjs'; -import {IcLogEntry} from './log/ic.mjs'; -import {MapLogEntry} from './log/map.mjs'; - -DOM.defineCustomElement( - 'ic-panel', (templateText) => class ICPanel extends V8CustomElement { - _selectedLogEntries; - _timeline; - constructor() { - super(templateText); - this.initGroupKeySelect(); - this.groupKey.addEventListener('change', e => this.updateTable(e)); - } - set timeline(value) { - console.assert(value !== undefined, 'timeline undefined!'); - this._timeline = value; - this.selectedLogEntries = this._timeline.all; - this.update(); - } - get groupKey() { - return this.$('#group-key'); - } - - get table() { - return this.$('#table'); - } - - get tableBody() { - return this.$('#table-body'); - } - - get count() { - return this.$('#count'); - } - - get spanSelectAll() { - return this.querySelectorAll('span'); - } - - set selectedLogEntries(value) { - this._selectedLogEntries = value; - this.update(); - } - - _update() { - this._updateCount(); - this._updateTable(); - } - - _updateCount() { - this.count.innerHTML = `length=${this._selectedLogEntries.length}`; - } - - _updateTable(event) { - let select = this.groupKey; - let key = select.options[select.selectedIndex].text; - DOM.removeAllChildren(this.tableBody); - let groups = Group.groupBy(this._selectedLogEntries, key, true); - this._render(groups, this.tableBody); - } - - escapeHtml(unsafe) { - if (!unsafe) return ''; - return unsafe.toString() - .replace(/&/g, '&') - .replace(/</g, '<') - .replace(/>/g, '>') - .replace(/"/g, '"') - .replace(/'/g, '''); - } - - handleMapClick(e) { - const group = e.target.parentNode.entry; - const id = group.key; - const selectedMapLogEntries = - this.searchIcLogEntryToMapLogEntry(id, group.entries); - this.dispatchEvent(new SelectionEvent(selectedMapLogEntries)); - } - - searchIcLogEntryToMapLogEntry(id, icLogEntries) { - // searches for mapLogEntries using the id, time - const selectedMapLogEntriesSet = new Set(); - for (const icLogEntry of icLogEntries) { - const selectedMap = MapLogEntry.get(id, icLogEntry.time); - selectedMapLogEntriesSet.add(selectedMap); - } - return Array.from(selectedMapLogEntriesSet); - } - - // TODO(zcankara) Handle in the processor for events with source - // positions. - handleFilePositionClick(e) { - const tr = e.target.parentNode; - const sourcePosition = tr.group.entries[0].sourcePosition; - this.dispatchEvent(new FocusEvent(sourcePosition)); - } - - _render(groups, parent) { - const fragment = document.createDocumentFragment(); - const max = Math.min(1000, groups.length) - const detailsClickHandler = this.handleDetailsClick.bind(this); - const mapClickHandler = this.handleMapClick.bind(this); - const fileClickHandler = this.handleFilePositionClick.bind(this); - for (let i = 0; i < max; i++) { - const group = groups[i]; - const tr = DOM.tr(); - tr.group = group; - const details = tr.appendChild(DOM.td('', 'toggle')); - details.onclick = detailsClickHandler; - tr.appendChild(DOM.td(group.percentage + '%', 'percentage')); - tr.appendChild(DOM.td(group.count, 'count')); - const valueTd = tr.appendChild(DOM.td(group.key, 'key')); - if (group.property === 'map') { - valueTd.onclick = mapClickHandler; - valueTd.classList.add('clickable'); - } else if (group.property == 'filePosition') { - valueTd.classList.add('clickable'); - valueTd.onclick = fileClickHandler; - } - fragment.appendChild(tr); - } - const omitted = groups.length - max; - if (omitted > 0) { - const tr = DOM.tr(); - const tdNode = tr.appendChild(DOM.td(`Omitted ${omitted} entries.`)); - tdNode.colSpan = 4; - fragment.appendChild(tr); - } - parent.appendChild(fragment); - } - - handleDetailsClick(event) { - const tr = event.target.parentNode; - const group = tr.group; - // Create subgroup in-place if the don't exist yet. - if (group.groups === undefined) { - group.createSubGroups(); - this.renderDrilldown(group, tr); - } - let detailsTr = tr.nextSibling; - if (tr.classList.contains('open')) { - tr.classList.remove('open'); - detailsTr.style.display = 'none'; - } else { - tr.classList.add('open'); - detailsTr.style.display = 'table-row'; - } - } - - renderDrilldown(group, previousSibling) { - let tr = DOM.tr('entry-details'); - tr.style.display = 'none'; - // indent by one td. - tr.appendChild(DOM.td()); - let td = DOM.td(); - td.colSpan = 3; - for (let key in group.groups) { - this.renderDrilldownGroup(td, group.groups[key], key); - } - tr.appendChild(td); - // Append the new TR after previousSibling. - previousSibling.parentNode.insertBefore(tr, previousSibling.nextSibling) - } - - renderDrilldownGroup(td, children, key) { - const max = 20; - const div = DOM.div('drilldown-group-title'); - div.textContent = - `Grouped by ${key} [top ${max} out of ${children.length}]`; - td.appendChild(div); - const table = DOM.table(); - this._render(children.slice(0, max), table, false) - td.appendChild(table); - } - - initGroupKeySelect() { - const select = this.groupKey; - select.options.length = 0; - for (const propertyName of IcLogEntry.propertyNames) { - const option = document.createElement('option'); - option.text = propertyName; - select.add(option); - } - } - }); diff --git a/deps/v8/tools/system-analyzer/index.css b/deps/v8/tools/system-analyzer/index.css index 5b55182f68..27b07531bf 100644 --- a/deps/v8/tools/system-analyzer/index.css +++ b/deps/v8/tools/system-analyzer/index.css @@ -18,7 +18,8 @@ --blue: #6e77dc; --orange: #dc9b6e; --violet: #d26edc; - --border-color: 128, 128, 128; + --border-color-rgb: 128, 128, 128; + --border-color: rgba(var(--border-color-rgb), 0.2); } [data-theme="light"] { @@ -110,19 +111,62 @@ dd { .panel { background-color: var(--surface-color); color: var(--on-surface-color); - padding: 10px 10px 10px 10px; + padding: 10px; border-radius: 10px; - border: 3px solid rgba(var(--border-color), 0.2); + border: 3px solid var(--border-color); + overflow: hidden; } .panelBody { + position: relative; max-height: 800px; overflow-y: scroll; margin: 0 -10px -10px 0; } -.panel > h2 { - margin-top: 5px; +.panel > h2, .panelTitle { + margin: -10px -10px 0 -10px; + padding: 5px 10px 5px 10px; + background-color: var(--border-color); + border-radius: 7px 7px 0 0; + font-weight: 400; +} + +.panel > select{ + width: calc(100% + 20px); + margin: 0 -10px 10px -10px; +} + +.panel > .selection { + display: flex; + margin: 0 -10px 0 -10px; +} +.panel > .selection input { + display: none; +} + +.panel > .selection label { + flex: 1; + padding: 5px; + cursor: pointer; + background-color: var( --surface-color); + font-weight: normal; + text-align: center; +} +.panel > .selection label ~ label { + border-left: 2px var(--border-color) solid; +} + +.panel > .selection label:hover { + background-color: var(--primary-color); +} +.panel > .selection [type=radio]:checked + label { + background-color: var(--border-color); +} + +.panel > .selection select { + flex: 1; + width: 50%; } button { @@ -133,17 +177,18 @@ select, button { background-color: var(--surface-color); color: var(--on-surface-color); - border: 2px solid rgba(var(--border-color), 0.4); + border: 2px solid rgba(var(--border-color-rgb), 0.4); border-radius: 5px; padding: 2px; } input:hover, select:hover, button:hover { - border: 2px solid rgba(var(--border-color), 0.6); + border: 2px solid rgba(var(--border-color-rgb), 0.6); } .colorbox { + display: inline-block; width: 10px; height: 10px; border: 1px var(--background-color) solid; @@ -192,6 +237,8 @@ button:hover { background-color: var(--primary-color); color: var(--on-primary-color); } + +button:hover, .clickable:hover, .mark:hover, .clickable:active, @@ -199,4 +246,48 @@ button:hover { background-color: var(--primary-color); color: var(--on-primary-color); cursor: pointer; +} + + +.legend { + position: absolute; + right: 0px; + top: 0px; + background-color: var(--surface-color); + border-radius: 5px; + border: 3px solid var(--border-color); + padding: 0 10px 0 10px; +} +.legend dt { + font-family: monospace; +} +.legend h3 { + margin-top: 10px; +} + + +.panelCloserLabel { + float: left; + cursor: zoom-out; + margin: 0 10px 0 0; + transition: transform 0.2s ease-out; + user-select: none; +} +.panelCloserInput { + display: none; +} +.panel .panelCloserInput:checked ~ .panelCloserLabel, +.panelCloserInput:checked ~ .panelCloserLabel { + cursor: zoom-in; + transform: rotate(-90deg); + display: inherit; +} +.panel .panelCloserInput:checked ~ h2 { + display: inherit; + border-radius: 7px; + margin-bottom: -20px; + padding-bottom: 10px; +} +.panelCloserInput:checked ~ * { + display: none; }
\ No newline at end of file diff --git a/deps/v8/tools/system-analyzer/index.html b/deps/v8/tools/system-analyzer/index.html index a861300f91..fa09830240 100644 --- a/deps/v8/tools/system-analyzer/index.html +++ b/deps/v8/tools/system-analyzer/index.html @@ -8,33 +8,27 @@ found in the LICENSE file. --> <title>Indicium</title> <!-- <link rel="icon" type="image/png" href="/images/favicon.png"/> --> - <link rel="modulepreload" href="./log-file-reader.mjs" > <link rel="modulepreload" href="./helper.mjs" > - <link rel="preload" href="./log-file-reader-template.html" as="fetch" crossorigin="anonymous"> + <link rel="modulepreload" href="./view/log-file-reader.mjs" > + <link rel="modulepreload" href="./view/helper.mjs" > + <link rel="preload" href="./view/log-file-reader-template.html" as="fetch" crossorigin="anonymous"> <script type="module"> // Force instatiating the log-reader before anything else. - import "./log-file-reader.mjs"; + import "./view/log-file-reader.mjs"; // Delay loading of the main App (async function() { let module = await import('./index.mjs'); - globalThis.app = new module.App("#log-file-reader", "#map-panel", "#map-stats-panel", - "#timeline-panel", "#ic-panel", "#map-track", "#ic-track", "#deopt-track", - "#source-panel"); + globalThis.app = new module.App(); })(); </script> - <link rel="stylesheet" type="text/css" href="./index.css"> <style> - .theme-switch-wrapper { - display: inline-block; - align-items: center; - } - .theme-switch { display: inline-block; height: 16px; position: relative; width: 38px; + vertical-align: middle; } .theme-switch input { @@ -75,7 +69,7 @@ found in the LICENSE file. --> width: 100%; } - .panels{ + .panels { display: grid; align-content: center; grid-template-columns: repeat(auto-fill, minmax(500px, 1fr)); @@ -91,31 +85,60 @@ found in the LICENSE file. --> </head> <body> + <tool-tip id="tool-tip"></tool-tip> + <section id="file-reader"> <log-file-reader id="log-file-reader"></log-file-reader> </section> <section id="container" class="initial"> <timeline-panel id="timeline-panel"> - <timeline-track id="map-track"></timeline-track> - <timeline-track id="ic-track"></timeline-track> - <timeline-track id="deopt-track"></timeline-track> + <timeline-track id="map-track" title="Map"></timeline-track> + <timeline-track id="ic-track" title="IC"></timeline-track> + <timeline-track id="deopt-track" title="Deopt"></timeline-track> + <timeline-track id="code-track" title="Code"></timeline-track> + <timeline-track id="api-track" title="API"></timeline-track> </timeline-panel> + <div class="panels"> <map-panel id="map-panel"></map-panel> - <stats-panel id="map-stats-panel"></stats-panel> - <ic-panel id="ic-panel" onchange="app.handleSelectIc(event)"></ic-panel> - <source-panel id="source-panel"></source-panel> + <list-panel id="ic-list" title="IC List"> + <div class="legend"> + <h3>Legend</h3> + <dl> + <dt>0</dt> + <dd>uninitialized</dd> + <dt>X</dt> + <dd>no feedback</dd> + <dt>1</dt> + <dd>monomorphic</dd> + <dt>^</dt> + <dd>recompute handler</dd> + <dt>P</dt> + <dd>polymorphic</dd> + <dt>N</dt> + <dd>megamorphic</dd> + <dt>G</dt> + <dd>generic</dd> + </dl> + </div> + </list-panel> + <list-panel id="map-list" title="Map Events"></list-panel> + <list-panel id="deopt-list" title="Deopt Events"></list-panel> + <list-panel id="code-list" title="Code Events"></list-panel> + <list-panel id="api-list" title="API Events"></list-panel> + <script-panel id="script-panel"></script-panel> + <code-panel id="code-panel"></code-panel> </div> </section> <div class="panels"> <section id="settings" class="panel"> <h2>Settings</h2> - <span>Theme:</span> - <div class="theme-switch-wrapper"> + <div class="panelBody"> + <span>Theme:</span> <label class="theme-switch" for="theme-switch-input"> - <input type="checkbox" id="theme-switch-input" /> + <input type="checkbox" id="theme-switch-input" > <div class="slider"></div> </label> </div> @@ -123,63 +146,96 @@ found in the LICENSE file. --> <section id="instructions" class="panel"> <h2>Instructions</h2> - <p> - Unified web interface to analyse runtime information stored in the v8 log. - </p> - For generating a v8.log file from <a href="https://v8.dev/docs/build">d8</a>: - <ul> - <li> - <code>/path/do/d8 --trace-maps --trace_ic --log-source-code $FILE</code> - </li> - </ul> - For generating a v8.log file from Chrome: - <ul> - <li> - <code>/path/to/chrome --user-data-dir=/var/tmp/chr$RANDOM --no-sandbox - --js-flags='--trace-ic --trace-maps --log-source-code’ - $WEBSITE_URL</code> - </li> - </ul> - - <h3>Log Options:</h3> - <dl class="d8-options"> - <dt><code>--trace-maps</code></dt> - <dd>Log<a href="https://v8.dev/blog/fast-properties" target="_blank"> - Maps</a></dd> - <dt><code>--trace-ic</code></dt> - <dd>Log - <a href="https://mathiasbynens.be/notes/shapes-ics" target="_blank"> - ICs</a></dd> - <dt><code>--log-source-code</code></dt> - <dd>Log source code</dd> - </dl> - - <h3>Keyboard Shortcuts for Navigation</h3> - <dl> - <dt><kbd>SHIFT</kbd> + <kbd>Arrow Up</kbd></dt> - <dd>Follow Map transition forward (first child)</dd> - - <dt><kbd>SHIFT</kbd> + <kbd>Arrow Down</kbd></dt> - <dd>Follow Map transition backwards</dd> - - <dt><kbd>Arrow Up</kbd></dt> - <dd>Go to previous Map chunk</dd> - - <dt><kbd>Arrow Down</kbd></dt> - <dd>Go to next Map in chunk</dd> - - <dt><kbd>Arrow Left</kbd></dt> - <dd>Go to previous chunk</dd> - - <dt><kbd>Arrow Right</kbd></dt> - <dd>Go to next chunk</dd> - - <dt><kbd>+</kbd></dt> - <dd>Timeline zoom in</dd> - - <dt><kbd>-</kbd></dt> - <dd>Timeline zoom out</dd> - </dl> + <div class="panelBody"> + <p> + Unified web interface to analyse runtime information stored in the v8 log. + </p> + For generating a v8.log file from <a href="https://v8.dev/docs/build">d8</a>: + <ul> + <li> + <code>/path/do/d8 $LOG_FLAGS $FILE</code> + </li> + </ul> + For generating a v8.log file from Chrome: + <ul> + <li> + <code>/path/to/chrome --user-data-dir=/var/tmp/chr$RANDOM --no-sandbox + --js-flags='$LOG_FLAGS’ + $WEBSITE_URL</code> + </li> + </ul> + + <h3><code>LOG_FLAGS</code>:</h3> + <dl class="d8-options"> + <dt> + <a href="https://source.chromium.org/search?q=FLAG_log_all"> + <code>--log-all</code> + </a> + </dt> + <dd>Enable all V8 logging options.</dd> + <dt> + <a href="https://source.chromium.org/search?q=FLAG_trace_maps"> + <code>--trace-maps</code> + </a> + </dt> + <dd> + Log<a href="https://v8.dev/blog/fast-properties">Maps</a> + </dd> + <dt> + <a href="https://source.chromium.org/search?q=FLAG_trace_ic"> + <code>--trace-ic</code> + </a> + </dt> + <dd> + Log <a href="https://mathiasbynens.be/notes/shapes-ics">ICs</a> + </dd> + <dt> + <a href="https://source.chromium.org/search?q=FLAG_log_source_code"> + <code>--log-source-code</code> + </a> + </dt> + <dd>Log source code</dd> + <dt> + <a href="https://source.chromium.org/search?q=FLAG_log_code_disassemble"> + <code>--log-code-disassemble</code> + </a> + </dt> + <dd>Log detailed generated generated code</dd> + <dt> + <a href="https://source.chromium.org/search?q=FLAG_log_api"> + <code>--log-api</code> + </a> + </dt> + <dd>Log various API uses.</dd> + </dl> + + <h3>Keyboard Shortcuts for Navigation</h3> + <dl> + <dt><kbd>SHIFT</kbd> + <kbd>Arrow Up</kbd></dt> + <dd>Follow Map transition forward (first child)</dd> + + <dt><kbd>SHIFT</kbd> + <kbd>Arrow Down</kbd></dt> + <dd>Follow Map transition backwards</dd> + + <dt><kbd>Arrow Up</kbd></dt> + <dd>Go to previous Map chunk</dd> + + <dt><kbd>Arrow Down</kbd></dt> + <dd>Go to next Map in chunk</dd> + + <dt><kbd>Arrow Left</kbd></dt> + <dd>Go to previous chunk</dd> + + <dt><kbd>Arrow Right</kbd></dt> + <dd>Go to next chunk</dd> + + <dt><kbd>+</kbd></dt> + <dd>Timeline zoom in</dd> + + <dt><kbd>-</kbd></dt> + <dd>Timeline zoom out</dd> + </dl> + </div> </section> </div> </body> diff --git a/deps/v8/tools/system-analyzer/index.mjs b/deps/v8/tools/system-analyzer/index.mjs index dfc858e5d6..531ae79138 100644 --- a/deps/v8/tools/system-analyzer/index.mjs +++ b/deps/v8/tools/system-analyzer/index.mjs @@ -2,34 +2,46 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import {SourcePosition} from '../profile.mjs'; +import {Script, SourcePosition} from '../profile.mjs'; import {State} from './app-model.mjs'; -import {FocusEvent, SelectionEvent, SelectTimeEvent} from './events.mjs'; -import {$} from './helper.mjs'; +import {ApiLogEntry} from './log/api.mjs'; +import {DeoptLogEntry} from './log/code.mjs'; +import {CodeLogEntry} from './log/code.mjs'; import {IcLogEntry} from './log/ic.mjs'; import {MapLogEntry} from './log/map.mjs'; import {Processor} from './processor.mjs'; +import {FocusEvent, SelectionEvent, SelectRelatedEvent, SelectTimeEvent, ToolTipEvent,} from './view/events.mjs'; +import {$, CSSColor, groupBy} from './view/helper.mjs'; class App { _state; _view; _navigation; _startupPromise; - constructor( - fileReaderId, mapPanelId, mapStatsPanelId, timelinePanelId, icPanelId, - mapTrackId, icTrackId, deoptTrackId, sourcePanelId) { + constructor() { this._view = { __proto__: null, - logFileReader: $(fileReaderId), - icPanel: $(icPanelId), - mapPanel: $(mapPanelId), - mapStatsPanel: $(mapStatsPanelId), - timelinePanel: $(timelinePanelId), - mapTrack: $(mapTrackId), - icTrack: $(icTrackId), - deoptTrack: $(deoptTrackId), - sourcePanel: $(sourcePanelId) + logFileReader: $('#log-file-reader'), + + timelinePanel: $('#timeline-panel'), + mapTrack: $('#map-track'), + icTrack: $('#ic-track'), + deoptTrack: $('#deopt-track'), + codeTrack: $('#code-track'), + apiTrack: $('#api-track'), + + icList: $('#ic-list'), + mapList: $('#map-list'), + codeList: $('#code-list'), + deoptList: $('#deopt-list'), + apiList: $('#api-list'), + + mapPanel: $('#map-panel'), + codePanel: $('#code-panel'), + scriptPanel: $('#script-panel'), + + toolTip: $('#tool-tip'), }; this.toggleSwitch = $('.theme-switch input[type="checkbox"]'); this.toggleSwitch.addEventListener('change', (e) => this.switchTheme(e)); @@ -40,90 +52,208 @@ class App { this._startupPromise = this.runAsyncInitialize(); } + static get allEventTypes() { + return new Set([ + SourcePosition, MapLogEntry, IcLogEntry, ApiLogEntry, CodeLogEntry, + DeoptLogEntry + ]); + } + async runAsyncInitialize() { await Promise.all([ - import('./ic-panel.mjs'), - import('./timeline-panel.mjs'), - import('./stats-panel.mjs'), - import('./map-panel.mjs'), - import('./source-panel.mjs'), + import('./view/list-panel.mjs'), + import('./view/timeline-panel.mjs'), + import('./view/map-panel.mjs'), + import('./view/script-panel.mjs'), + import('./view/code-panel.mjs'), + import('./view/tool-tip.mjs'), ]); document.addEventListener( 'keydown', e => this._navigation?.handleKeyDown(e)); document.addEventListener( - SelectionEvent.name, e => this.handleShowEntries(e)); + SelectRelatedEvent.name, e => this.handleSelectRelatedEntries(e)); document.addEventListener( - FocusEvent.name, e => this.handleShowEntryDetail(e)); + SelectionEvent.name, e => this.handleSelectEntries(e)) + document.addEventListener( + FocusEvent.name, e => this.handleFocusLogEntryl(e)); document.addEventListener( SelectTimeEvent.name, e => this.handleTimeRangeSelect(e)); + document.addEventListener(ToolTipEvent.name, e => this.handleToolTip(e)); } - handleShowEntries(e) { - if (e.entries[0] instanceof MapLogEntry) { - this.showMapEntries(e.entries); - } else if (e.entries[0] instanceof IcLogEntry) { - this.showIcEntries(e.entries); - } else if (e.entries[0] instanceof SourcePosition) { - this.showSourcePositionEntries(e.entries); - } else { - throw new Error('Unknown selection type!'); + handleSelectRelatedEntries(e) { + e.stopImmediatePropagation(); + this.selectRelatedEntries(e.entry); + } + + selectRelatedEntries(entry) { + let entries = [entry]; + switch (entry.constructor) { + case SourcePosition: + entries = entries.concat(entry.entries); + break; + case MapLogEntry: + entries = this._state.icTimeline.filter(each => each.map === entry); + break; + case IcLogEntry: + if (entry.map) entries.push(entry.map); + break; + case ApiLogEntry: + break; + case CodeLogEntry: + break; + case DeoptLogEntry: + // TODO select map + code entries + if (entry.fileSourcePosition) entries.push(entry.fileSourcePosition); + break; + case Script: + entries = entry.entries.concat(entry.sourcePositions); + break; + default: + throw new Error('Unknown selection type!'); + } + if (entry.sourcePosition) { + entries.push(entry.sourcePosition); + // TODO: find the matching Code log entries. + } + this.selectEntries(entries); + } + + handleSelectEntries(e) { + e.stopImmediatePropagation(); + this.showEntries(e.entries); + } + + selectEntries(entries) { + const missingTypes = App.allEventTypes; + groupBy(entries, each => each.constructor, true).forEach(group => { + this.selectEntriesOfSingleType(group.entries); + missingTypes.delete(group.key); + }); + missingTypes.forEach(type => this.selectEntriesOfSingleType([], type)); + } + + selectEntriesOfSingleType(entries, type) { + switch (entries[0]?.constructor ?? type) { + case SourcePosition: + return this.showSourcePositions(entries); + case MapLogEntry: + return this.showMapEntries(entries); + case IcLogEntry: + return this.showIcEntries(entries); + case ApiLogEntry: + return this.showApiEntries(entries); + case CodeLogEntry: + return this.showCodeEntries(entries); + case DeoptLogEntry: + return this.showDeoptEntries(entries); + default: + throw new Error('Unknown selection type!'); } - e.stopPropagation(); } + showMapEntries(entries) { this._state.selectedMapLogEntries = entries; - this._view.mapPanel.selectedMapLogEntries = entries; - this._view.mapStatsPanel.selectedLogEntries = entries; + this._view.mapPanel.selectedLogEntries = entries; + this._view.mapList.selectedLogEntries = entries; } + showIcEntries(entries) { this._state.selectedIcLogEntries = entries; - this._view.icPanel.selectedLogEntries = entries; + this._view.icList.selectedLogEntries = entries; } + showDeoptEntries(entries) { this._state.selectedDeoptLogEntries = entries; + this._view.deoptList.selectedLogEntries = entries; } - showSourcePositionEntries(entries) { - // TODO: Handle multiple source position selection events - this._view.sourcePanel.selectedSourcePositions = entries + + showCodeEntries(entries) { + this._state.selectedCodeLogEntries = entries; + this._view.codePanel.selectedEntries = entries; + this._view.codeList.selectedLogEntries = entries; + } + + showApiEntries(entries) { + this._state.selectedApiLogEntries = entries; + this._view.apiList.selectedLogEntries = entries; + } + + showSourcePositions(entries) { + this._view.scriptPanel.selectedSourcePositions = entries } handleTimeRangeSelect(e) { + e.stopImmediatePropagation(); this.selectTimeRange(e.start, e.end); - e.stopPropagation(); } selectTimeRange(start, end) { this._state.selectTimeRange(start, end); - this.showMapEntries(this._state.mapTimeline.selection); - this.showIcEntries(this._state.icTimeline.selection); - this.showDeoptEntries(this._state.deoptTimeline.selection); + this.showMapEntries(this._state.mapTimeline.selectionOrSelf); + this.showIcEntries(this._state.icTimeline.selectionOrSelf); + this.showDeoptEntries(this._state.deoptTimeline.selectionOrSelf); + this.showCodeEntries(this._state.codeTimeline.selectionOrSelf); + this.showApiEntries(this._state.apiTimeline.selectionOrSelf); this._view.timelinePanel.timeSelection = {start, end}; } - handleShowEntryDetail(e) { - if (e.entry instanceof MapLogEntry) { - this.selectMapLogEntry(e.entry); - } else if (e.entry instanceof IcLogEntry) { - this.selectICLogEntry(e.entry); - } else if (e.entry instanceof SourcePosition) { - this.selectSourcePosition(e.entry); - } else { - throw new Error('Unknown selection type!'); + handleFocusLogEntryl(e) { + e.stopImmediatePropagation(); + this.focusLogEntry(e.entry); + } + + focusLogEntry(entry) { + switch (entry.constructor) { + case SourcePosition: + return this.focusSourcePosition(entry); + case MapLogEntry: + return this.focusMapLogEntry(entry); + case IcLogEntry: + return this.focusIcLogEntry(entry); + case ApiLogEntry: + return this.focusApiLogEntry(entry); + case CodeLogEntry: + return this.focusCodeLogEntry(entry); + case DeoptLogEntry: + return this.focusDeoptLogEntry(entry); + default: + throw new Error('Unknown selection type!'); } - e.stopPropagation(); } - selectMapLogEntry(entry) { + + focusMapLogEntry(entry) { this._state.map = entry; - this._view.mapTrack.selectedEntry = entry; + this._view.mapTrack.focusedEntry = entry; this._view.mapPanel.map = entry; } - selectICLogEntry(entry) { + + focusIcLogEntry(entry) { this._state.ic = entry; - this._view.icPanel.selectedLogEntries = [entry]; } - selectSourcePosition(sourcePositions) { - if (!sourcePositions.script) return; - this._view.sourcePanel.selectedSourcePositions = [sourcePositions]; + + focusCodeLogEntry(entry) { + this._state.code = entry; + this._view.codePanel.entry = entry; + } + + focusDeoptLogEntry(entry) { + this._view.deoptList.focusedLogEntry = entry; + } + + focusApiLogEntry(entry) { + this._state.apiLogEntry = entry; + this._view.apiTrack.focusedEntry = entry; + } + + focusSourcePosition(sourcePosition) { + if (!sourcePosition) return; + this._view.sourcePanel.focusedSourcePositions = [sourcePosition]; + } + + handleToolTip(event) { + this._view.toolTip.positionOrTargetNode = event.positionOrTargetNode; + this._view.toolTip.content = event.content; } handleFileUploadStart(e) { @@ -140,22 +270,23 @@ class App { await this._startupPromise; try { const processor = new Processor(e.detail); + this._state.profile = processor.profile; const mapTimeline = processor.mapTimeline; const icTimeline = processor.icTimeline; const deoptTimeline = processor.deoptTimeline; - this._state.mapTimeline = mapTimeline; - this._state.icTimeline = icTimeline; - this._state.deoptTimeline = deoptTimeline; - // Transitions must be set before timeline for stats panel. + const codeTimeline = processor.codeTimeline; + const apiTimeline = processor.apiTimeline; + this._state.setTimelines( + mapTimeline, icTimeline, deoptTimeline, codeTimeline, apiTimeline); this._view.mapPanel.timeline = mapTimeline; - this._view.mapTrack.data = mapTimeline; - this._view.mapStatsPanel.transitions = - this._state.mapTimeline.transitions; - this._view.mapStatsPanel.timeline = mapTimeline; - this._view.icPanel.timeline = icTimeline; - this._view.icTrack.data = icTimeline; - this._view.deoptTrack.data = deoptTimeline; - this._view.sourcePanel.data = processor.scripts + this._view.icList.timeline = icTimeline; + this._view.mapList.timeline = mapTimeline; + this._view.deoptList.timeline = deoptTimeline; + this._view.codeList.timeline = codeTimeline; + this._view.apiList.timeline = apiTimeline; + this._view.scriptPanel.scripts = processor.scripts; + this._view.codePanel.timeline = codeTimeline; + this.refreshTimelineTrackView(); } catch (e) { this._view.logFileReader.error = 'Log file contains errors!' throw (e); @@ -169,14 +300,16 @@ class App { this._view.mapTrack.data = this._state.mapTimeline; this._view.icTrack.data = this._state.icTimeline; this._view.deoptTrack.data = this._state.deoptTimeline; + this._view.codeTrack.data = this._state.codeTimeline; + this._view.apiTrack.data = this._state.apiTimeline; } switchTheme(event) { document.documentElement.dataset.theme = event.target.checked ? 'light' : 'dark'; - if (this.fileLoaded) { - this.refreshTimelineTrackView(); - } + CSSColor.reset(); + if (!this.fileLoaded) return; + this.refreshTimelineTrackView(); } } diff --git a/deps/v8/tools/system-analyzer/log/api.mjs b/deps/v8/tools/system-analyzer/log/api.mjs new file mode 100644 index 0000000000..760ac54914 --- /dev/null +++ b/deps/v8/tools/system-analyzer/log/api.mjs @@ -0,0 +1,32 @@ +// 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. +import {LogEntry} from './log.mjs'; + +export class ApiLogEntry extends LogEntry { + constructor(type, time, name, argument) { + super(type, time); + this._name = name; + this._argument = argument; + } + + get name() { + return this._name; + } + + get argument() { + return this._argument; + } + + toString() { + return `Api(${this.type})`; + } + + toStringLong() { + return `Api(${this.type}): ${this._name}`; + } + + static get propertyNames() { + return ['type', 'name', 'argument']; + } +} diff --git a/deps/v8/tools/system-analyzer/log/code.mjs b/deps/v8/tools/system-analyzer/log/code.mjs new file mode 100644 index 0000000000..4f69f16c64 --- /dev/null +++ b/deps/v8/tools/system-analyzer/log/code.mjs @@ -0,0 +1,95 @@ +// 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. +import {LogEntry} from './log.mjs'; + +export class DeoptLogEntry extends LogEntry { + constructor( + type, time, entry, deoptReason, deoptLocation, scriptOffset, + instructionStart, codeSize, inliningId) { + super(type, time); + this._entry = entry; + this._reason = deoptReason; + this._location = deoptLocation; + this._scriptOffset = scriptOffset; + this._instructionStart = instructionStart; + this._codeSize = codeSize; + this._inliningId = inliningId; + this.fileSourcePosition = undefined; + } + + get reason() { + return this._reason; + } + + get location() { + return this._location; + } + + get entry() { + return this._entry; + } + + get functionName() { + return this._entry.functionName; + } + + toString() { + return `Deopt(${this.type})`; + } + + toStringLong() { + return `Deopt(${this.type})${this._reason}: ${this._location}`; + } + + static get propertyNames() { + return [ + 'type', 'reason', 'functionName', 'sourcePosition', + 'functionSourcePosition', 'script' + ]; + } +} + +export class CodeLogEntry extends LogEntry { + constructor(type, time, kind, entry) { + super(type, time); + this._kind = kind; + this._entry = entry; + } + + get kind() { + return this._kind; + } + + get entry() { + return this._entry; + } + + get functionName() { + return this._entry.functionName; + } + + get size() { + return this._entry.size; + } + + get source() { + return this._entry?.getSourceCode() ?? ''; + } + + get disassemble() { + return this._entry?.source?.disassemble; + } + + toString() { + return `Code(${this.type})`; + } + + toStringLong() { + return `Code(${this.type}): ${this._entry.toString()}`; + } + + static get propertyNames() { + return ['type', 'kind', 'functionName', 'sourcePosition', 'script']; + } +} diff --git a/deps/v8/tools/system-analyzer/log/deopt.mjs b/deps/v8/tools/system-analyzer/log/deopt.mjs deleted file mode 100644 index f3ff1a71a2..0000000000 --- a/deps/v8/tools/system-analyzer/log/deopt.mjs +++ /dev/null @@ -1,10 +0,0 @@ -// 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. -import {LogEntry} from './log.mjs'; - -export class DeoptLogEntry extends LogEntry { - constructor(type, time) { - super(type, time); - } -} diff --git a/deps/v8/tools/system-analyzer/log/ic.mjs b/deps/v8/tools/system-analyzer/log/ic.mjs index b6c7ec5553..68078ccc68 100644 --- a/deps/v8/tools/system-analyzer/log/ic.mjs +++ b/deps/v8/tools/system-analyzer/log/ic.mjs @@ -6,7 +6,7 @@ import {LogEntry} from './log.mjs'; export class IcLogEntry extends LogEntry { constructor( type, fn_file, time, line, column, key, oldState, newState, map, reason, - script, modifier, additional) { + modifier, additional) { super(type, time); this.category = 'other'; if (this.type.indexOf('Store') !== -1) { @@ -14,11 +14,10 @@ export class IcLogEntry extends LogEntry { } else if (this.type.indexOf('Load') !== -1) { this.category = 'Load'; } - let parts = fn_file.split(' '); + const parts = fn_file.split(' '); this.functionName = parts[0]; this.file = parts[1]; let position = line + ':' + column; - this.filePosition = this.file + ':' + position; this.oldState = oldState; this.newState = newState; this.state = this.oldState + ' → ' + this.newState; @@ -26,10 +25,17 @@ export class IcLogEntry extends LogEntry { this.map = map; this.reason = reason; this.additional = additional; - this.script = script; this.modifier = modifier; } + toString() { + return `IC(${this.type})`; + } + + toStringLong() { + return `IC(${this.type}):\n${this.state}`; + } + parseMapProperties(parts, offset) { let next = parts[++offset]; if (!next.startsWith('dict')) return offset; @@ -58,8 +64,8 @@ export class IcLogEntry extends LogEntry { static get propertyNames() { return [ - 'type', 'category', 'functionName', 'filePosition', 'state', 'key', 'map', - 'reason', 'file' + 'type', 'category', 'functionName', 'script', 'sourcePosition', 'state', + 'key', 'map', 'reason', 'file' ]; } } diff --git a/deps/v8/tools/system-analyzer/log/log.mjs b/deps/v8/tools/system-analyzer/log/log.mjs index 69195d7853..5339d88432 100644 --- a/deps/v8/tools/system-analyzer/log/log.mjs +++ b/deps/v8/tools/system-analyzer/log/log.mjs @@ -3,19 +3,32 @@ // found in the LICENSE file. export class LogEntry { - _time; - _type; constructor(type, time) { - // TODO(zcankara) remove type and add empty getters to override this._time = time; this._type = type; + this.sourcePosition = undefined; } + get time() { return this._time; } + get type() { return this._type; } + + get script() { + return this.sourcePosition?.script; + } + + toString() { + return `${this.constructor.name}(${this._type})`; + } + + toStringLong() { + return this.toString(); + } + // Returns an Array of all possible #type values. static get allTypes() { throw new Error('Not implemented.'); diff --git a/deps/v8/tools/system-analyzer/log/map.mjs b/deps/v8/tools/system-analyzer/log/map.mjs index 4df6fb847c..d97f340ca1 100644 --- a/deps/v8/tools/system-analyzer/log/map.mjs +++ b/deps/v8/tools/system-analyzer/log/map.mjs @@ -34,21 +34,34 @@ define(Array.prototype, 'last', function() { // Map Log Events class MapLogEntry extends LogEntry { - edge = void 0; - children = []; - depth = 0; - _isDeprecated = false; - deprecatedTargets = null; - leftId = 0; - rightId = 0; - filePosition = ''; - script = ''; - id = -1; constructor(id, time) { if (!time) throw new Error('Invalid time'); - super(id, time); - MapLogEntry.set(id, this); + // Use MapLogEntry.type getter instead of property, since we only know the + // type lazily from the incoming transition. + super(undefined, time); this.id = id; + MapLogEntry.set(id, this); + this.edge = undefined; + this.children = []; + this.depth = 0; + this._isDeprecated = false; + this.deprecatedTargets = null; + this.leftId = 0; + this.rightId = 0; + this.entry = undefined; + this.description = ''; + } + + get functionName() { + return this.entry?.functionName; + } + + toString() { + return `Map(${this.id})`; + } + + toStringLong() { + return `Map(${this.id}):\n${this.description}`; } finalizeRootMap(id) { @@ -59,9 +72,9 @@ class MapLogEntry extends LogEntry { console.warn('Skipping potential parent loop between maps:', current) continue; } - current.finalize(id) + current.finalize(id); id += 1; - current.children.forEach(edge => stack.push(edge.to)) + current.children.forEach(edge => stack.push(edge.to)); // TODO implement rightId } return id; @@ -75,8 +88,7 @@ class MapLogEntry extends LogEntry { } parent() { - if (this.edge === void 0) return void 0; - return this.edge.from; + return this.edge?.from; } isDeprecated() { @@ -88,7 +100,7 @@ class MapLogEntry extends LogEntry { } isRoot() { - return this.edge === void 0 || this.edge.from === void 0; + return this.edge === undefined || this.edge.from === undefined; } contains(map) { @@ -111,9 +123,10 @@ class MapLogEntry extends LogEntry { } position(chunks) { - let index = this.chunkIndex(chunks); - let xFrom = (index + 1.5) * kChunkWidth; - let yFrom = kChunkHeight - chunks[index].yOffset(this); + const index = this.chunkIndex(chunks); + if (index === -1) return [0, 0]; + const xFrom = (index + 1.5) * kChunkWidth; + const yFrom = kChunkHeight - chunks[index].yOffset(this); return [xFrom, yFrom]; } @@ -131,11 +144,19 @@ class MapLogEntry extends LogEntry { } get type() { - return this.edge === void 0 ? 'new' : this.edge.type; + return this.edge?.type ?? 'new'; + } + + get reason() { + return this.edge?.reason; + } + + get property() { + return this.edge?.name; } isBootstrapped() { - return this.edge === void 0; + return this.edge === undefined; } getParents() { @@ -150,15 +171,16 @@ class MapLogEntry extends LogEntry { static get(id, time = undefined) { let maps = this.cache.get(id); - if (maps) { + if (maps === undefined) return undefined; + if (time !== undefined) { for (let i = 1; i < maps.length; i++) { if (maps[i].time > time) { return maps[i - 1]; } } - // default return the latest - return (maps.length > 0) ? maps[maps.length - 1] : undefined; } + // default return the latest + return maps[maps.length - 1]; } static set(id, map) { @@ -168,6 +190,13 @@ class MapLogEntry extends LogEntry { this.cache.set(id, [map]); } } + + static get propertyNames() { + return [ + 'type', 'reason', 'property', 'functionName', 'sourcePosition', 'script', + 'id' + ]; + } } MapLogEntry.cache = new Map(); @@ -183,17 +212,29 @@ class Edge { this.to = to; } + updateFrom(edge) { + if (this.to !== edge.to || this.from !== edge.from) { + throw new Error('Invalid Edge updated', this, to); + } + this.type = edge.type; + this.name = edge.name; + this.reason = edge.reason; + this.time = edge.time; + } + finishSetup() { const from = this.from; - if (from) from.addEdge(this); const to = this.to; + if (to?.time < from?.time) { + // This happens for map deprecation where the transition tree is converted + // in reverse order. + console.warn('Invalid time order'); + } + if (from) from.addEdge(this); if (to === undefined) return; to.edge = this; if (from === undefined) return; if (to === from) throw 'From and to must be distinct.'; - if (to.time < from.time) { - console.warn('invalid time order'); - } let newDepth = from.depth + 1; if (to.depth > 0 && to.depth != newDepth) { console.warn('Depth has already been initialized'); diff --git a/deps/v8/tools/system-analyzer/map-panel-template.html b/deps/v8/tools/system-analyzer/map-panel-template.html deleted file mode 100644 index 12d6ec5a14..0000000000 --- a/deps/v8/tools/system-analyzer/map-panel-template.html +++ /dev/null @@ -1,21 +0,0 @@ -<!-- 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. --> - -<head> - <link href="./index.css" rel="stylesheet"> -</head> -<style> - #searchBarInput { - width: 200px; - } -</style> -<div class="panel"> - <h2>Map Panel</h2> - <map-transitions id="map-transitions"></map-transitions> - <h3>Search Map by Address</h3> - <section id="searchBar"></section> - <input type="search" id="searchBarInput"></input> - <button id="searchBarBtn">Search</button> - <map-details id="map-details"></map-details> -</div> diff --git a/deps/v8/tools/system-analyzer/map-panel.mjs b/deps/v8/tools/system-analyzer/map-panel.mjs deleted file mode 100644 index 1516038a2d..0000000000 --- a/deps/v8/tools/system-analyzer/map-panel.mjs +++ /dev/null @@ -1,72 +0,0 @@ -// 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. -import './stats-panel.mjs'; -import './map-panel/map-details.mjs'; -import './map-panel/map-transitions.mjs'; - -import {FocusEvent} from './events.mjs'; -import {DOM, V8CustomElement} from './helper.mjs'; -import {MapLogEntry} from './log/map.mjs'; - -DOM.defineCustomElement('map-panel', - (templateText) => - class MapPanel extends V8CustomElement { - _map; - constructor() { - super(templateText); - this.searchBarBtn.addEventListener('click', e => this.handleSearchBar(e)); - this.addEventListener(FocusEvent.name, e => this.handleUpdateMapDetails(e)); - } - - handleUpdateMapDetails(e) { - if (e.entry instanceof MapLogEntry) { - this.mapDetailsPanel.map = e.entry; - } - } - - get mapTransitionsPanel() { - return this.$('#map-transitions'); - } - - get mapDetailsPanel() { - return this.$('#map-details'); - } - - get searchBarBtn() { - return this.$('#searchBarBtn'); - } - - get searchBar() { - return this.$('#searchBar'); - } - - set timeline(timeline) { - this._timeline = timeline; - } - - set map(value) { - this._map = value; - this.mapTransitionsPanel.map = this._map; - } - - handleSearchBar(e) { - let searchBar = this.$('#searchBarInput'); - let searchBarInput = searchBar.value; - // access the map from model cache - let selectedMap = MapLogEntry.get(parseInt(searchBarInput)); - if (selectedMap) { - searchBar.className = 'success'; - } else { - searchBar.className = 'failure'; - } - this.dispatchEvent(new FocusEvent(selectedMap)); - } - - set selectedMapLogEntries(list) { - this.mapTransitionsPanel.selectedMapLogEntries = list; - } - get selectedMapLogEntries() { - return this.mapTransitionsPanel.selectedMapLogEntries; - } -}); diff --git a/deps/v8/tools/system-analyzer/map-panel/map-transitions.mjs b/deps/v8/tools/system-analyzer/map-panel/map-transitions.mjs deleted file mode 100644 index 60462a1db2..0000000000 --- a/deps/v8/tools/system-analyzer/map-panel/map-transitions.mjs +++ /dev/null @@ -1,184 +0,0 @@ -// 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. -import {FocusEvent, SelectionEvent} from '../events.mjs'; -import {DOM, typeToColor, V8CustomElement} from '../helper.mjs'; - -DOM.defineCustomElement('./map-panel/map-transitions', - (templateText) => - class MapTransitions extends V8CustomElement { - _map; - _selectedMapLogEntries; - _displayedMapsInTree; - - constructor() { - super(templateText); - this.transitionView.addEventListener( - 'mousemove', (e) => this.handleTransitionViewChange(e)); - this.currentNode = this.transitionView; - this.currentMap = undefined; - } - - get transitionView() { - return this.$('#transitionView'); - } - - get tooltip() { - return this.$('#tooltip'); - } - - get tooltipContents() { - return this.$('#tooltipContents'); - } - - set map(value) { - this._map = value; - this.showMap(); - } - - handleTransitionViewChange(e) { - this.tooltip.style.left = e.pageX + 'px'; - this.tooltip.style.top = e.pageY + 'px'; - const map = e.target.map; - if (map) { - this.tooltipContents.innerText = map.description; - } - } - - _selectMap(map) { - this.dispatchEvent(new SelectionEvent([map])); - } - - showMap() { - if (this.currentMap === this._map) return; - this.currentMap = this._map; - this.selectedMapLogEntries = [this._map]; - this.update(); - } - - _update() { - this.transitionView.style.display = 'none'; - DOM.removeAllChildren(this.transitionView); - this._displayedMapsInTree = new Set(); - // Limit view to 200 maps for performance reasons. - this.selectedMapLogEntries.slice(0, 200).forEach( - (map) => this.addMapAndParentTransitions(map)); - this._displayedMapsInTree = undefined; - this.transitionView.style.display = ''; - } - - set selectedMapLogEntries(list) { - this._selectedMapLogEntries = list; - this.update(); - } - - get selectedMapLogEntries() { - return this._selectedMapLogEntries; - } - - addMapAndParentTransitions(map) { - if (map === void 0) return; - if (this._displayedMapsInTree.has(map)) return; - this._displayedMapsInTree.add(map); - this.currentNode = this.transitionView; - let parents = map.getParents(); - if (parents.length > 0) { - this.addTransitionTo(parents.pop()); - parents.reverse().forEach((each) => this.addTransitionTo(each)); - } - let mapNode = this.addSubtransitions(map); - // Mark and show the selected map. - mapNode.classList.add('selected'); - if (this.selectedMap == map) { - setTimeout( - () => mapNode.scrollIntoView({ - behavior: 'smooth', - block: 'nearest', - inline: 'nearest', - }), - 1); - } - } - - addSubtransitions(map) { - let mapNode = this.addTransitionTo(map); - // Draw outgoing linear transition line. - let current = map; - while (current.children.length == 1) { - current = current.children[0].to; - this.addTransitionTo(current); - } - return mapNode; - } - - addTransitionEdge(map) { - let classes = ['transitionEdge']; - let edge = DOM.div(classes); - edge.style.backgroundColor = typeToColor(map.edge); - let labelNode = DOM.div('transitionLabel'); - labelNode.innerText = map.edge.toString(); - edge.appendChild(labelNode); - return edge; - } - - addTransitionTo(map) { - // transition[ transitions[ transition[...], transition[...], ...]]; - this._displayedMapsInTree?.add(map); - let transition = DOM.div('transition'); - if (map.isDeprecated()) transition.classList.add('deprecated'); - if (map.edge) { - transition.appendChild(this.addTransitionEdge(map)); - } - let mapNode = this.addMapNode(map); - transition.appendChild(mapNode); - - let subtree = DOM.div('transitions'); - transition.appendChild(subtree); - - this.currentNode.appendChild(transition); - this.currentNode = subtree; - - return mapNode; - } - - addMapNode(map) { - let node = DOM.div('map'); - if (map.edge) node.style.backgroundColor = typeToColor(map.edge); - node.map = map; - node.addEventListener('click', () => this._selectMap(map)); - if (map.children.length > 1) { - node.innerText = map.children.length; - let showSubtree = DOM.div('showSubtransitions'); - showSubtree.addEventListener('click', (e) => this.toggleSubtree(e, node)); - node.appendChild(showSubtree); - } else if (map.children.length == 0) { - node.innerHTML = '●'; - } - this.currentNode.appendChild(node); - return node; - } - - toggleSubtree(event, node) { - let map = node.map; - event.target.classList.toggle('opened'); - let transitionsNode = node.parentElement.querySelector('.transitions'); - let subtransitionNodes = transitionsNode.children; - if (subtransitionNodes.length <= 1) { - // Add subtransitions excepth the one that's already shown. - let visibleTransitionMap = subtransitionNodes.length == 1 ? - transitionsNode.querySelector('.map').map : - void 0; - map.children.forEach((edge) => { - if (edge.to != visibleTransitionMap) { - this.currentNode = transitionsNode; - this.addSubtransitions(edge.to); - } - }); - } else { - // remove all but the first (currently selected) subtransition - for (let i = subtransitionNodes.length - 1; i > 0; i--) { - transitionsNode.removeChild(subtransitionNodes[i]); - } - } - } -}); diff --git a/deps/v8/tools/system-analyzer/processor.mjs b/deps/v8/tools/system-analyzer/processor.mjs index 49448bb9da..9685e09ad6 100644 --- a/deps/v8/tools/system-analyzer/processor.mjs +++ b/deps/v8/tools/system-analyzer/processor.mjs @@ -5,7 +5,8 @@ import {LogReader, parseString, parseVarArgs} from '../logreader.mjs'; import {Profile} from '../profile.mjs'; -import {DeoptLogEntry} from './log/deopt.mjs'; +import {ApiLogEntry} from './log/api.mjs'; +import {CodeLogEntry, DeoptLogEntry} from './log/code.mjs'; import {IcLogEntry} from './log/ic.mjs'; import {Edge, MapLogEntry} from './log/map.mjs'; import {Timeline} from './timeline.mjs'; @@ -17,17 +18,28 @@ export class Processor extends LogReader { _mapTimeline = new Timeline(); _icTimeline = new Timeline(); _deoptTimeline = new Timeline(); + _codeTimeline = new Timeline(); + _apiTimeline = new Timeline(); _formatPCRegexp = /(.*):[0-9]+:[0-9]+$/; + _lastTimestamp = 0; + _lastCodeLogEntry; MAJOR_VERSION = 7; MINOR_VERSION = 6; constructor(logString) { super(); - this.propertyICParser = [ + const propertyICParser = [ parseInt, parseInt, parseInt, parseInt, parseString, parseString, parseString, parseString, parseString, parseString ]; this.dispatchTable_ = { __proto__: null, + 'v8-version': { + parsers: [ + parseInt, + parseInt, + ], + processor: this.processV8Version + }, 'code-creation': { parsers: [ parseString, parseInt, parseInt, parseInt, parseInt, parseString, @@ -42,20 +54,28 @@ export class Processor extends LogReader { ], processor: this.processCodeDeopt }, - 'v8-version': { + 'code-move': + {parsers: [parseInt, parseInt], processor: this.processCodeMove}, + 'code-delete': {parsers: [parseInt], processor: this.processCodeDelete}, + 'code-source-info': { + parsers: [ + parseInt, parseInt, parseInt, parseInt, parseString, parseString, + parseString + ], + processor: this.processCodeSourceInfo + }, + 'code-disassemble': { parsers: [ parseInt, - parseInt, + parseString, + parseString, ], - processor: this.processV8Version + processor: this.processCodeDisassemble }, 'script-source': { parsers: [parseInt, parseString, parseString], processor: this.processScriptSource }, - 'code-move': - {parsers: [parseInt, parseInt], processor: this.processCodeMove}, - 'code-delete': {parsers: [parseInt], processor: this.processCodeDelete}, 'sfi-move': {parsers: [parseInt, parseInt], processor: this.processFunctionMove}, 'map-create': @@ -72,33 +92,37 @@ export class Processor extends LogReader { processor: this.processMapDetails }, 'LoadGlobalIC': { - parsers: this.propertyICParser, + parsers: propertyICParser, processor: this.processPropertyIC.bind(this, 'LoadGlobalIC') }, 'StoreGlobalIC': { - parsers: this.propertyICParser, + parsers: propertyICParser, processor: this.processPropertyIC.bind(this, 'StoreGlobalIC') }, 'LoadIC': { - parsers: this.propertyICParser, + parsers: propertyICParser, processor: this.processPropertyIC.bind(this, 'LoadIC') }, 'StoreIC': { - parsers: this.propertyICParser, + parsers: propertyICParser, processor: this.processPropertyIC.bind(this, 'StoreIC') }, 'KeyedLoadIC': { - parsers: this.propertyICParser, + parsers: propertyICParser, processor: this.processPropertyIC.bind(this, 'KeyedLoadIC') }, 'KeyedStoreIC': { - parsers: this.propertyICParser, + parsers: propertyICParser, processor: this.processPropertyIC.bind(this, 'KeyedStoreIC') }, 'StoreInArrayLiteralIC': { - parsers: this.propertyICParser, + parsers: propertyICParser, processor: this.processPropertyIC.bind(this, 'StoreInArrayLiteralIC') }, + 'api': { + parsers: [parseString, parseVarArgs], + processor: this.processApiEvent + }, }; if (logString) this.processString(logString); } @@ -166,45 +190,56 @@ export class Processor extends LogReader { }); } - /** - * Parser for dynamic code optimization state. - */ - parseState(s) { - switch (s) { - case '': - return Profile.CodeState.COMPILED; - case '~': - return Profile.CodeState.OPTIMIZABLE; - case '*': - return Profile.CodeState.OPTIMIZED; + processV8Version(majorVersion, minorVersion) { + if ((majorVersion == this.MAJOR_VERSION && + minorVersion <= this.MINOR_VERSION) || + (majorVersion < this.MAJOR_VERSION)) { + window.alert( + `Unsupported version ${majorVersion}.${minorVersion}. \n` + + `Please use the matching tool for given the V8 version.`); } - throw new Error(`unknown code state: ${s}`); } processCodeCreation(type, kind, timestamp, start, size, name, maybe_func) { + this._lastTimestamp = timestamp; + let entry; + let stateName = ''; if (maybe_func.length) { const funcAddr = parseInt(maybe_func[0]); - const state = this.parseState(maybe_func[1]); - this._profile.addFuncCode( + stateName = maybe_func[1] ?? ''; + const state = Profile.parseState(maybe_func[1]); + entry = this._profile.addFuncCode( type, name, timestamp, start, size, funcAddr, state); } else { - this._profile.addCode(type, name, timestamp, start, size); + entry = this._profile.addCode(type, name, timestamp, start, size); } + this._lastCodeLogEntry = + new CodeLogEntry(type + stateName, timestamp, kind, entry); + this._codeTimeline.push(this._lastCodeLogEntry); } processCodeDeopt( timestamp, codeSize, instructionStart, inliningId, scriptOffset, deoptKind, deoptLocation, deoptReason) { - this._deoptTimeline.push(new DeoptLogEntry(deoptKind, timestamp)); - } - - processV8Version(majorVersion, minorVersion) { - if ((majorVersion == this.MAJOR_VERSION && - minorVersion <= this.MINOR_VERSION) || - (majorVersion < this.MAJOR_VERSION)) { - window.alert( - `Unsupported version ${majorVersion}.${minorVersion}. \n` + - `Please use the matching tool for given the V8 version.`); + this._lastTimestamp = timestamp; + const codeEntry = this._profile.findEntry(instructionStart); + const logEntry = new DeoptLogEntry( + deoptKind, timestamp, codeEntry, deoptReason, deoptLocation, + scriptOffset, instructionStart, codeSize, inliningId); + this._deoptTimeline.push(logEntry); + this.addSourcePosition(codeEntry, logEntry); + logEntry.functionSourcePosition = logEntry.sourcePosition; + // custom parse deopt location + if (deoptLocation !== '<unknown>') { + const colSeparator = deoptLocation.lastIndexOf(':'); + const rowSeparator = deoptLocation.lastIndexOf(':', colSeparator - 1); + const script = this.getScript(deoptLocation.substring(1, rowSeparator)); + const line = + parseInt(deoptLocation.substring(rowSeparator + 1, colSeparator)); + const column = parseInt( + deoptLocation.substring(colSeparator + 1, deoptLocation.length - 1)); + logEntry.sourcePosition = + script.addSourcePosition(line, column, logEntry); } } @@ -224,111 +259,138 @@ export class Processor extends LogReader { this._profile.moveFunc(from, to); } - formatName(entry) { - if (!entry) return '<unknown>'; - let name = entry.func.getName(); - let re = /(.*):[0-9]+:[0-9]+$/; - let array = re.exec(name); - if (!array) return name; - return entry.getState() + array[1]; + processCodeSourceInfo( + start, scriptId, startPos, endPos, sourcePositions, inliningPositions, + inlinedFunctions) { + this._profile.addSourcePositions( + start, scriptId, startPos, endPos, sourcePositions, inliningPositions, + inlinedFunctions); + let profileEntry = this._profile.findEntry(start); + if (profileEntry !== this._lastCodeLogEntry._entry) return; + this.addSourcePosition(profileEntry, this._lastCodeLogEntry); + this._lastCodeLogEntry = undefined; + } + + addSourcePosition(profileEntry, logEntry) { + let script = this.getProfileEntryScript(profileEntry); + const parts = profileEntry.getRawName().split(':'); + if (parts.length < 3) return; + const line = parseInt(parts[parts.length - 2]); + const column = parseInt(parts[parts.length - 1]); + logEntry.sourcePosition = script.addSourcePosition(line, column, logEntry); + } + + processCodeDisassemble(start, kind, disassemble) { + this._profile.addDisassemble(start, kind, disassemble); } processPropertyIC( - type, pc, time, line, column, old_state, new_state, map, key, modifier, + type, pc, time, line, column, old_state, new_state, mapId, key, modifier, slow_reason) { - let fnName = this.functionName(pc); - let parts = fnName.split(' '); - let fileName = parts[parts.length - 1]; - let script = this.getScript(fileName); + this._lastTimestamp = time; + const profileEntry = this._profile.findEntry(pc); + const fnName = this.formatProfileEntry(profileEntry); + const script = this.getProfileEntryScript(profileEntry); + const map = this.getOrCreateMapEntry(mapId, time); // TODO: Use SourcePosition here directly let entry = new IcLogEntry( type, fnName, time, line, column, key, old_state, new_state, map, - slow_reason, script, modifier); + slow_reason, modifier); if (script) { entry.sourcePosition = script.addSourcePosition(line, column, entry); } this._icTimeline.push(entry); } - functionName(pc) { - let entry = this._profile.findEntry(pc); - return this.formatName(entry); - } - formatPC(pc, line, column) { - let entry = this._profile.findEntry(pc); - if (!entry) return '<unknown>' - if (entry.type === 'Builtin') { - return entry.name; - } - let name = entry.func.getName(); - let array = this._formatPCRegexp.exec(name); - if (array === null) { - entry = name; - } else { - entry = entry.getState() + array[1]; - } - return entry + ':' + line + ':' + column; + formatProfileEntry(profileEntry, line, column) { + if (!profileEntry) return '<unknown>'; + if (profileEntry.type === 'Builtin') return profileEntry.name; + const name = profileEntry.func.getName(); + const array = this._formatPCRegexp.exec(name); + const formatted = + (array === null) ? name : profileEntry.getState() + array[1]; + if (line === undefined || column === undefined) return formatted; + return `${formatted}:${line}:${column}`; } - processFileName(filePositionLine) { - if (!filePositionLine.includes(' ')) return; - // Try to handle urls with file positions: https://foo.bar.com/:17:330" - filePositionLine = filePositionLine.split(' '); - let parts = filePositionLine[1].split(':'); - if (parts[0].length <= 5) return parts[0] + ':' + parts[1]; - return parts[1]; + getProfileEntryScript(profileEntry) { + if (!profileEntry) return undefined; + if (profileEntry.type === 'Builtin') return undefined; + const script = profileEntry.source?.script; + if (script !== undefined) return script; + // Slow path, try to get the script from the url: + const fnName = this.formatProfileEntry(profileEntry); + let parts = fnName.split(' '); + let fileName = parts[parts.length - 1]; + return this.getScript(fileName); } processMap(type, time, from, to, pc, line, column, reason, name) { - let time_ = parseInt(time); + this._lastTimestamp = time; + const time_ = parseInt(time); if (type === 'Deprecate') return this.deprecateMap(type, time_, from); - let from_ = this.getExistingMapEntry(from, time_); - let to_ = this.getExistingMapEntry(to, time_); + // Skip normalized maps that were cached so we don't introduce multiple + // edges with the same source and target map. + if (type === 'NormalizeCached') return; + const from_ = this.getOrCreateMapEntry(from, time_); + const to_ = this.getOrCreateMapEntry(to, time_); + if (type === 'Normalize') { + // Fix a bug where we log "Normalize" transitions for maps created from + // the NormalizedMapCache. + if (to_.parent()?.id === from || to_.time < from_.time || to_.depth > 0) { + console.log(`Skipping transition to cached normalized map`); + return; + } + } // TODO: use SourcePosition directly. let edge = new Edge(type, name, reason, time, from_, to_); - to_.filePosition = this.formatPC(pc, line, column); - let fileName = this.processFileName(to_.filePosition); - // TODO: avoid undefined source positions. - if (fileName !== undefined) { - to_.script = this.getScript(fileName); + const profileEntry = this._profile.findEntry(pc) + to_.entry = profileEntry; + let script = this.getProfileEntryScript(profileEntry); + if (script) { + to_.sourcePosition = script.addSourcePosition(line, column, to_) } - if (to_.script) { - to_.sourcePosition = to_.script.addSourcePosition(line, column, to_) + if (to_.parent() !== undefined && to_.parent() === from_) { + // Fix bug where we double log transitions. + console.warn('Fixing up double transition'); + to_.edge.updateFrom(edge); + } else { + edge.finishSetup(); } - edge.finishSetup(); } deprecateMap(type, time, id) { - this.getExistingMapEntry(id, time).deprecate(); + this._lastTimestamp = time; + this.getOrCreateMapEntry(id, time).deprecate(); } processMapCreate(time, id) { // map-create events might override existing maps if the addresses get // recycled. Hence we do not check for existing maps. - let map = this.createMapEntry(id, time); + this._lastTimestamp = time; + this.createMapEntry(id, time); } processMapDetails(time, id, string) { // TODO(cbruni): fix initial map logging. - let map = this.getExistingMapEntry(id, time); + const map = this.getOrCreateMapEntry(id, time); map.description = string; } createMapEntry(id, time) { - let map = new MapLogEntry(id, time); + this._lastTimestamp = time; + const map = new MapLogEntry(id, time); this._mapTimeline.push(map); return map; } - getExistingMapEntry(id, time) { + getOrCreateMapEntry(id, time) { if (id === '0x000000000000') return undefined; - let map = MapLogEntry.get(id, time); - if (map === undefined) { - console.error(`No map details provided: id=${id}`); - // Manually patch in a map to continue running. - return this.createMapEntry(id, time); - }; - return map; + const map = MapLogEntry.get(id, time); + if (map !== undefined) return map; + console.warn(`No map details provided: id=${id}`); + // Manually patch in a map to continue running. + return this.createMapEntry(id, time); } getScript(url) { @@ -340,6 +402,22 @@ export class Processor extends LogReader { return script; } + processApiEvent(type, varArgs) { + let name, arg1; + if (varArgs.length == 0) { + const index = type.indexOf(':'); + if (index > 0) { + name = type; + type = type.substr(0, index); + } + } else { + name = varArgs[0]; + arg1 = varArgs[1]; + } + this._apiTimeline.push( + new ApiLogEntry(type, this._lastTimestamp, name, arg1)); + } + get icTimeline() { return this._icTimeline; } @@ -352,7 +430,19 @@ export class Processor extends LogReader { return this._deoptTimeline; } + get codeTimeline() { + return this._codeTimeline; + } + + get apiTimeline() { + return this._apiTimeline; + } + get scripts() { return this._profile.scripts_.filter(script => script !== undefined); } + + get profile() { + return this._profile; + } } diff --git a/deps/v8/tools/system-analyzer/source-panel-template.html b/deps/v8/tools/system-analyzer/source-panel-template.html deleted file mode 100644 index 01b777042f..0000000000 --- a/deps/v8/tools/system-analyzer/source-panel-template.html +++ /dev/null @@ -1,54 +0,0 @@ -<!-- 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. --> - -<head> - <link href="./index.css" rel="stylesheet"> -</head> -<style> - pre.scriptNode { - white-space: pre-wrap; - } - - pre.scriptNode:before { - counter-reset: sourceLineCounter; - } - - pre.scriptNode span { - counter-increment: sourceLineCounter; - } - - pre.scriptNode span::before { - content: counter(sourceLineCounter) ": "; - display: inline-block; - width: 4em; - padding-left: auto; - margin-left: auto; - text-align: right; - } - - mark { - width: 1ch; - border-radius: 2px; - border: 0.5px var(--background-color) solid; - cursor: pointer; - background-color: var(--primary-color); - color: var(--on-primary-color); - } - - .marked { - background-color: var(--secondary-color); - } - - #script-dropdown { - width: 100%; - margin-bottom: 10px; - } -</style> -<div class="panel"> - <h2>Source Panel</h2> - <select id="script-dropdown"></select> - <div id="script" class="panelBody"> - <pre class="scripNode"></pre> - </div> -</div> diff --git a/deps/v8/tools/system-analyzer/stats-panel-template.html b/deps/v8/tools/system-analyzer/stats-panel-template.html deleted file mode 100644 index fb91fad1cf..0000000000 --- a/deps/v8/tools/system-analyzer/stats-panel-template.html +++ /dev/null @@ -1,73 +0,0 @@ -<!-- 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. --> - -<head> - <link href="./index.css" rel="stylesheet"> -</head> -<style> - #stats { - display: flex; - height: 250px; - background-color: var(--surface-color); - padding: 10px 10px 10px 10px; - margin: auto; - } - - table { - flex: 1; - max-height: 250px; - display: inline-block; - overflow-y: scroll; - border-collapse: collapse; - } - table td { - padding: 2px; - } - - table thead td { - border-bottom: 1px var(--on-surface-color) dotted; - } - - table tbody td { - cursor: pointer; - } - - #nameTable tr { - max-width: 200px; - - } - - #nameTable tr td:nth-child(1) { - text-align: right; - } - - #typeTable { - text-align: right; - max-width: 380px; - } - - #typeTable tr td:nth-child(2) { - text-align: left; - } -</style> -<div class="panel"> - <h2>Map Stats</h2> - <section id="stats"> - <table id="typeTable" class="statsTable"> - <thead> - <tr><td></td><td>Type</td><td>Count</td><td>Percent</td></tr> - </thead> - <tbody></tbody> - </table> - <table id="nameTable"> - <thead> - <tr><td>Count</td><td>Propery Name</td></tr> - </thead> - <tbody></tbody> - <tfoot> - <tr><td colspan="2" class="clickable">Show more...</td></tr> - </tfoo> - </table> - </section> -</div> diff --git a/deps/v8/tools/system-analyzer/stats-panel.mjs b/deps/v8/tools/system-analyzer/stats-panel.mjs deleted file mode 100644 index dd0ac78489..0000000000 --- a/deps/v8/tools/system-analyzer/stats-panel.mjs +++ /dev/null @@ -1,129 +0,0 @@ -// 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. -import {SelectionEvent} from './events.mjs'; -import {DOM, V8CustomElement} from './helper.mjs'; -import {delay, LazyTable} from './helper.mjs'; - -DOM.defineCustomElement( - 'stats-panel', (templateText) => class StatsPanel extends V8CustomElement { - _timeline; - _transitions; - _selectedLogEntries; - constructor() { - super(templateText); - } - - get stats() { - return this.$('#stats'); - } - - set timeline(timeline) { - this._timeline = timeline; - this.selectedLogEntries = timeline.all - } - - set selectedLogEntries(entries) { - this._selectedLogEntries = entries; - this.update(); - } - - set transitions(value) { - this._transitions = value; - } - - _filterUniqueTransitions(filter) { - // Returns a list of Maps whose parent is not in the list. - return this._selectedLogEntries.filter((map) => { - if (filter(map) === false) return false; - let parent = map.parent(); - if (parent === undefined) return true; - return filter(parent) === false; - }); - } - - _update() { - this._updateGeneralStats(); - this._updateNamedTransitionsStats(); - } - - _updateGeneralStats() { - console.assert(this._timeline !== undefined, 'Timeline not set yet!'); - let pairs = [ - ['Transitions', 'primary', (e) => e.edge && e.edge.isTransition()], - ['Fast to Slow', 'violet', (e) => e.edge && e.edge.isFastToSlow()], - ['Slow to Fast', 'orange', (e) => e.edge && e.edge.isSlowToFast()], - ['Initial Map', 'yellow', (e) => e.edge && e.edge.isInitial()], - [ - 'Replace Descriptors', - 'red', - (e) => e.edge && e.edge.isReplaceDescriptors(), - ], - [ - 'Copy as Prototype', - 'red', - (e) => e.edge && e.edge.isCopyAsPrototype(), - ], - [ - 'Optimize as Prototype', - null, - (e) => e.edge && e.edge.isOptimizeAsPrototype(), - ], - ['Deprecated', null, (e) => e.isDeprecated()], - ['Bootstrapped', 'green', (e) => e.isBootstrapped()], - ['Total', null, (e) => true], - ]; - - let tbody = document.createElement('tbody'); - let total = this._selectedLogEntries.length; - pairs.forEach(([name, color, filter]) => { - let row = DOM.tr(); - if (color !== null) { - row.appendChild(DOM.td(DOM.div(['colorbox', color]))); - } else { - row.appendChild(DOM.td('')); - } - row.classList.add('clickable'); - row.onclick = (e) => { - // lazily compute the stats - let node = e.target.parentNode; - if (node.maps == undefined) { - node.maps = this._filterUniqueTransitions(filter); - } - this.dispatchEvent(new SelectionEvent(node.maps)); - }; - row.appendChild(DOM.td(name)); - let count = this._count(filter); - row.appendChild(DOM.td(count)); - let percent = Math.round((count / total) * 1000) / 10; - row.appendChild(DOM.td(percent.toFixed(1) + '%')); - tbody.appendChild(row); - }); - this.$('#typeTable').replaceChild(tbody, this.$('#typeTable tbody')); - } - - _count(filter) { - let count = 0; - for (const map of this._selectedLogEntries) { - if (filter(map)) count++; - } - return count; - } - - _updateNamedTransitionsStats() { - let rowData = Array.from(this._transitions.entries()); - rowData.sort((a, b) => b[1].length - a[1].length); - new LazyTable(this.$('#nameTable'), rowData, ([name, maps]) => { - let row = DOM.tr(); - row.maps = maps; - row.classList.add('clickable'); - row.addEventListener( - 'click', - (e) => this.dispatchEvent(new SelectionEvent( - e.target.parentNode.maps.map((map) => map.to)))); - row.appendChild(DOM.td(maps.length)); - row.appendChild(DOM.td(name)); - return row; - }); - } - }); diff --git a/deps/v8/tools/system-analyzer/timeline-panel-template.html b/deps/v8/tools/system-analyzer/timeline-panel-template.html deleted file mode 100644 index 2641c71441..0000000000 --- a/deps/v8/tools/system-analyzer/timeline-panel-template.html +++ /dev/null @@ -1,13 +0,0 @@ -<!-- 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. --> - -<head> - <link href="./index.css" rel="stylesheet"> -</head> -<div class="panel"> - <h2>Timeline Panel</h2> - <div> - <slot></slot> - </div> -</div> diff --git a/deps/v8/tools/system-analyzer/timeline.mjs b/deps/v8/tools/system-analyzer/timeline.mjs index 996f108b6a..13e97553e6 100644 --- a/deps/v8/tools/system-analyzer/timeline.mjs +++ b/deps/v8/tools/system-analyzer/timeline.mjs @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import {groupBy} from './helper.mjs' + class Timeline { // Class: _model; @@ -9,13 +11,13 @@ class Timeline { _values; // Current selection, subset of #values: _selection; - _uniqueTypes; + _breakdown; - constructor(model) { + constructor(model, values = [], startTime = 0, endTime = 0) { this._model = model; - this._values = []; - this.startTime = 0; - this.endTime = 0; + this._values = values; + this.startTime = startTime; + this.endTime = endTime; } get model() { @@ -30,21 +32,28 @@ class Timeline { return this._selection; } + get selectionOrSelf() { + return this._selection ?? this; + } + set selection(value) { this._selection = value; } - selectTimeRange(start, end) { - this._selection = this.filter(e => e.time >= start && e.time <= end); + selectTimeRange(startTime, endTime) { + const items = this.range(startTime, endTime); + this._selection = new Timeline(this._model, items, startTime, endTime); + } + + clearSelection() { + this._selection = undefined; } getChunks(windowSizeMs) { - // TODO(zcankara) Fill this one return this.chunkSizes(windowSizeMs); } get values() { - // TODO(zcankara) Not to break something delete later return this._values; } @@ -94,6 +103,10 @@ class Timeline { return this._values.length; } + slice(startIndex, endIndex) { + return this._values.slice(startIndex, endIndex); + } + first() { return this._values[0]; } @@ -102,17 +115,23 @@ class Timeline { return this._values[this._values.length - 1]; } + * [Symbol.iterator]() { + yield* this._values; + } + duration() { + if (this.isEmpty()) return 0; return this.last().time - this.first().time; } forEachChunkSize(count, fn) { + if (this.isEmpty()) return; const increment = this.duration() / count; let currentTime = this.first().time + increment; let index = 0; for (let i = 0; i < count; i++) { - let nextIndex = this.find(currentTime, index); - let nextTime = currentTime + increment; + const nextIndex = this.find(currentTime, index); + const nextTime = currentTime + increment; fn(index, nextIndex, currentTime, nextTime); index = nextIndex; currentTime = nextTime; @@ -120,56 +139,55 @@ class Timeline { } chunkSizes(count) { - let chunks = []; + const chunks = []; this.forEachChunkSize(count, (start, end) => chunks.push(end - start)); return chunks; } - chunks(count) { - let chunks = []; + chunks(count, predicate = undefined) { + const chunks = []; this.forEachChunkSize(count, (start, end, startTime, endTime) => { let items = this._values.slice(start, end); + if (predicate !== undefined) items = items.filter(predicate); chunks.push(new Chunk(chunks.length, startTime, endTime, items)); }); return chunks; } - range(start, end) { - const first = this.find(start); - if (first < 0) return []; - const last = this.find(end, first); - return this._values.slice(first, last); + // Return all entries in ({startTime}, {endTime}] + range(startTime, endTime) { + const firstIndex = this.find(startTime); + if (firstIndex < 0) return []; + const lastIndex = this.find(endTime, firstIndex + 1); + return this._values.slice(firstIndex, lastIndex); } + // Return the first index for the first element at {time}. find(time, offset = 0) { return this._find(this._values, each => each.time - time, offset); } - _find(array, cmp, offset = 0) { - let min = offset; - let max = array.length; - while (min < max) { - let mid = min + Math.floor((max - min) / 2); - let result = cmp(array[mid]); - if (result > 0) { - max = mid - 1; + // Return the first index for which compareFn(item) is >= 0; + _find(array, compareFn, offset = 0) { + let minIndex = offset; + let maxIndex = array.length - 1; + while (minIndex < maxIndex) { + const midIndex = minIndex + (((maxIndex - minIndex) / 2) | 0); + if (compareFn(array[midIndex]) < 0) { + minIndex = midIndex + 1; } else { - min = mid + 1; + maxIndex = midIndex; } } - return min; + return minIndex; } - initializeTypes() { - const types = new Map(); - for (const entry of this.all) { - types.get(entry.type)?.push(entry) ?? types.set(entry.type, [entry]) + getBreakdown(keyFunction) { + if (keyFunction) return groupBy(this._values, keyFunction); + if (this._breakdown === undefined) { + this._breakdown = groupBy(this._values, each => each.type); } - return this._uniqueTypes = types; - } - - get uniqueTypes() { - return this._uniqueTypes ?? this.initializeTypes(); + return this._breakdown; } depthHistogram() { @@ -215,6 +233,10 @@ class Chunk { return this.items.length; } + get length() { + return this.items.length; + } + yOffset(event) { // items[0] == oldest event, displayed at the top of the chunk // items[n-1] == youngest event, displayed at the bottom of the chunk @@ -248,17 +270,8 @@ class Chunk { return chunk; } - getBreakdown(event_fn) { - if (event_fn === void 0) { - event_fn = each => each; - } - let breakdown = {__proto__: null}; - this.items.forEach(each => { - const type = event_fn(each); - const v = breakdown[type]; - breakdown[type] = (v | 0) + 1; - }); - return Object.entries(breakdown).sort((a, b) => a[1] - b[1]); + getBreakdown(keyFunction) { + return groupBy(this.items, keyFunction); } filter() { diff --git a/deps/v8/tools/system-analyzer/timeline/timeline-track-template.html b/deps/v8/tools/system-analyzer/timeline/timeline-track-template.html deleted file mode 100644 index e14b927e4b..0000000000 --- a/deps/v8/tools/system-analyzer/timeline/timeline-track-template.html +++ /dev/null @@ -1,138 +0,0 @@ -<!-- 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. --> - -<head> - <link href="./index.css" rel="stylesheet"> -</head> -<style> - #timeline { - position: relative; - height: calc(200px + 12px); - overflow-y: hidden; - overflow-x: scroll; - user-select: none; - } - - #timelineLabel { - transform: rotate(90deg); - transform-origin: left bottom 0; - position: absolute; - left: 0; - width: 200px; - text-align: center; - font-size: 10px; - opacity: 0.5; - } - - #timelineChunks { - height: 200px; - position: absolute; - margin-right: 100px; - } - - #timelineCanvas { - height: 200px; - position: relative; - overflow: visible; - pointer-events: none; - } - - .chunk { - width: 6px; - position: absolute; - background-size: 100% 100%; - image-rendering: pixelated; - bottom: 0px; - background-color: var(--on-surface-color); - cursor: pointer; - } - .chunk:hover { - border-radius: 2px 2px 0 0; - margin: 0 0 -2px -2px; - border: 2px var(--primary-color) solid; - } - - .timestamp { - height: 200px; - width: 100px; - border-left: 1px var(--on-surface-color) dashed; - padding-left: 4px; - position: absolute; - pointer-events: none; - font-size: 10px; - } - - #legend { - position: relative; - float: right; - width: 100%; - max-width: 280px; - padding-left: 20px; - padding-top: 10px; - border-collapse: collapse; - } - - th, - td { - width: 200px; - text-align: left; - padding-bottom: 3px; - } - - /* right align numbers */ - #legend td:nth-of-type(4n+3), - #legend td:nth-of-type(4n+4) { - text-align: right; - } - - .legendTypeColumn { - width: 100%; - } - - .timeline { - background-color: var(--timeline-background-color); - } - - #timeline .rightHandle, - #timeline .leftHandle { - background-color: rgba(200, 200, 200, 0.5); - height: 100%; - width: 5px; - position: absolute; - z-index: 3; - cursor: col-resize; - } - #timeline .leftHandle { - border-left: 1px solid var(--on-surface-color); - } - #timeline .rightHandle { - border-right: 1px solid var(--on-surface-color); - } - - #timeline .selection { - background-color: rgba(133, 68, 163, 0.5); - height: 100%; - position: absolute; - } -</style> -<table id="legend" class="typeStatsTable"> - <thead> - <tr> - <td></td> - <td>Type</td> - <td>Count</td> - <td>Percent</td> - </tr> - </thead> - <tbody id="legendContent"> - </tbody> -</table> -<div id="timeline"> - <div class="leftHandle"></div> - <div class="selection"></div> - <div class="rightHandle"></div> - <div id="timelineLabel">Frequency</div> - <div id="timelineChunks"></div> - <canvas id="timelineCanvas"></canvas> -</div>
\ No newline at end of file diff --git a/deps/v8/tools/system-analyzer/timeline/timeline-track.mjs b/deps/v8/tools/system-analyzer/timeline/timeline-track.mjs deleted file mode 100644 index a37bcce2c5..0000000000 --- a/deps/v8/tools/system-analyzer/timeline/timeline-track.mjs +++ /dev/null @@ -1,518 +0,0 @@ -// 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. - -import {FocusEvent, SelectionEvent, SelectTimeEvent, SynchronizeSelectionEvent} from '../events.mjs'; -import {CSSColor, delay, DOM, V8CustomElement} from '../helper.mjs'; -import {kChunkHeight, kChunkWidth} from '../log/map.mjs'; - -const kColors = [ - CSSColor.green, - CSSColor.violet, - CSSColor.orange, - CSSColor.yellow, - CSSColor.primaryColor, - CSSColor.red, - CSSColor.blue, - CSSColor.yellow, - CSSColor.secondaryColor, -]; - -DOM.defineCustomElement('./timeline/timeline-track', - (templateText) => - class TimelineTrack extends V8CustomElement { - // TODO turn into static field once Safari supports it. - static get SELECTION_OFFSET() { - return 10 - }; - _timeline; - _nofChunks = 400; - _chunks; - _selectedEntry; - _timeToPixel; - _timeSelection = {start: -1, end: Infinity}; - _timeStartOffset; - _selectionOriginTime; - _typeToColor; - constructor() { - super(templateText); - this.timeline.addEventListener('scroll', e => this.handleTimelineScroll(e)); - this.timeline.addEventListener( - 'mousedown', e => this.handleTimeSelectionMouseDown(e)); - this.timeline.addEventListener( - 'mouseup', e => this.handleTimeSelectionMouseUp(e)); - this.timeline.addEventListener( - 'mousemove', e => this.handleTimeSelectionMouseMove(e)); - this.backgroundCanvas = document.createElement('canvas'); - this.isLocked = false; - } - - handleTimeSelectionMouseDown(e) { - let xPosition = e.clientX - // Update origin time in case we click on a handle. - if (this.isOnLeftHandle(xPosition)) { - xPosition = this.rightHandlePosX; - } - else if (this.isOnRightHandle(xPosition)) { - xPosition = this.leftHandlePosX; - } - this._selectionOriginTime = this.positionToTime(xPosition); - } - - isOnLeftHandle(posX) { - return ( - Math.abs(this.leftHandlePosX - posX) <= TimelineTrack.SELECTION_OFFSET); - } - - isOnRightHandle(posX) { - return ( - Math.abs(this.rightHandlePosX - posX) <= - TimelineTrack.SELECTION_OFFSET); - } - - handleTimeSelectionMouseMove(e) { - if (!this._isSelecting) return; - const currentTime = this.positionToTime(e.clientX); - this.dispatchEvent(new SynchronizeSelectionEvent( - Math.min(this._selectionOriginTime, currentTime), - Math.max(this._selectionOriginTime, currentTime))); - } - - handleTimeSelectionMouseUp(e) { - this._selectionOriginTime = -1; - const delta = this._timeSelection.end - this._timeSelection.start; - if (delta <= 1 || isNaN(delta)) return; - this.dispatchEvent(new SelectTimeEvent( - this._timeSelection.start, this._timeSelection.end)); - } - - set timeSelection(selection) { - this._timeSelection.start = selection.start; - this._timeSelection.end = selection.end; - this.updateSelection(); - } - - get _isSelecting() { - return this._selectionOriginTime >= 0; - } - - updateSelection() { - const startPosition = this.timeToPosition(this._timeSelection.start); - const endPosition = this.timeToPosition(this._timeSelection.end); - const delta = endPosition - startPosition; - this.leftHandle.style.left = startPosition + 'px'; - this.selection.style.left = startPosition + 'px'; - this.rightHandle.style.left = endPosition + 'px'; - this.selection.style.width = delta + 'px'; - } - - get leftHandlePosX() { - return this.leftHandle.getBoundingClientRect().x; - } - - get rightHandlePosX() { - return this.rightHandle.getBoundingClientRect().x; - } - - // Maps the clicked x position to the x position on timeline canvas - positionOnTimeline(posX) { - let rect = this.timeline.getBoundingClientRect(); - let posClickedX = posX - rect.left + this.timeline.scrollLeft; - return posClickedX; - } - - positionToTime(posX) { - let posTimelineX = this.positionOnTimeline(posX) + this._timeStartOffset; - return posTimelineX / this._timeToPixel; - } - - timeToPosition(time) { - let posX = time * this._timeToPixel; - posX -= this._timeStartOffset - return posX; - } - - get leftHandle() { - return this.$('.leftHandle'); - } - - get rightHandle() { - return this.$('.rightHandle'); - } - - get selection() { - return this.$('.selection'); - } - - get timelineCanvas() { - return this.$('#timelineCanvas'); - } - - get timelineChunks() { - return this.$('#timelineChunks'); - } - - get timeline() { - return this.$('#timeline'); - } - - get timelineLegend() { - return this.$('#legend'); - } - - get timelineLegendContent() { - return this.$('#legendContent'); - } - - set data(value) { - this._timeline = value; - this._resetTypeToColorCache(); - this.update(); - } - - _update() { - this._updateChunks(); - this._updateTimeline(); - this._renderLegend(); - } - - _resetTypeToColorCache() { - this._typeToColor = new Map(); - let lastIndex = 0; - for (const type of this.data.uniqueTypes.keys()) { - this._typeToColor.set(type, kColors[lastIndex++]); - } - } - - get data() { - return this._timeline; - } - - set nofChunks(count) { - this._nofChunks = count; - this.update(); - } - - get nofChunks() { - return this._nofChunks; - } - - _updateChunks() { - this._chunks = this.data.chunks(this.nofChunks); - } - - get chunks() { - return this._chunks; - } - - set selectedEntry(value) { - this._selectedEntry = value; - if (value.edge) this.redraw(); - } - - get selectedEntry() { - return this._selectedEntry; - } - - set scrollLeft(offset) { - this.timeline.scrollLeft = offset; - } - - typeToColor(type) { - return this._typeToColor.get(type); - } - - _renderLegend() { - let timelineLegendContent = this.timelineLegendContent; - DOM.removeAllChildren(timelineLegendContent); - this._timeline.uniqueTypes.forEach((entries, type) => { - let row = DOM.tr('clickable'); - row.entries = entries; - row.addEventListener('dblclick', e => this.handleEntryTypeDblClick(e)); - let color = this.typeToColor(type); - if (color !== null) { - let div = DOM.div('colorbox'); - div.style.backgroundColor = color; - row.appendChild(DOM.td(div)); - } else { - row.appendChild(DOM.td()); - } - let td = DOM.td(type); - row.appendChild(td); - row.appendChild(DOM.td(entries.length)); - let percent = (entries.length / this.data.all.length) * 100; - row.appendChild(DOM.td(percent.toFixed(1) + '%')); - timelineLegendContent.appendChild(row); - }); - // Add Total row. - let row = DOM.tr(); - row.appendChild(DOM.td('')); - row.appendChild(DOM.td('All')); - row.appendChild(DOM.td(this.data.all.length)); - row.appendChild(DOM.td('100%')); - timelineLegendContent.appendChild(row); - this.timelineLegend.appendChild(timelineLegendContent); - } - - handleEntryTypeDblClick(e) { - this.dispatchEvent(new SelectionEvent(e.target.parentNode.entries)); - } - - timelineIndicatorMove(offset) { - this.timeline.scrollLeft += offset; - } - - handleTimelineScroll(e) { - let horizontal = e.currentTarget.scrollLeft; - this.dispatchEvent(new CustomEvent( - 'scrolltrack', {bubbles: true, composed: true, detail: horizontal})); - } - - async setChunkBackgrounds(backgroundTodo) { - const kMaxDuration = 50; - let lastTime = 0; - for (let [chunk, node] of backgroundTodo) { - const current = performance.now(); - if (current - lastTime > kMaxDuration) { - await delay(25); - lastTime = current; - } - this.setChunkBackground(chunk, node); - } - } - - setChunkBackground(chunk, node) { - // Render the types of transitions as bar charts - const kHeight = chunk.height; - const kWidth = 1; - this.backgroundCanvas.width = kWidth; - this.backgroundCanvas.height = kHeight; - let ctx = this.backgroundCanvas.getContext('2d'); - ctx.clearRect(0, 0, kWidth, kHeight); - let y = 0; - let total = chunk.size(); - let type, count; - if (true) { - chunk.getBreakdown(map => map.type).forEach(([type, count]) => { - ctx.fillStyle = this.typeToColor(type); - let height = count / total * kHeight; - ctx.fillRect(0, y, kWidth, y + height); - y += height; - }); - } else { - chunk.items.forEach(map => { - ctx.fillStyle = this.typeToColor(map.type); - let y = chunk.yOffset(map); - ctx.fillRect(0, y, kWidth, y + 1); - }); - } - - let imageData = this.backgroundCanvas.toDataURL('image/webp', 0.2); - node.style.backgroundImage = `url(${imageData})`; - } - - _updateTimeline() { - let chunksNode = this.timelineChunks; - DOM.removeAllChildren(chunksNode); - let chunks = this.chunks; - let max = chunks.max(each => each.size()); - let start = this.data.startTime; - let end = this.data.endTime; - let duration = end - start; - this._timeToPixel = chunks.length * kChunkWidth / duration; - this._timeStartOffset = start * this._timeToPixel; - let addTimestamp = (time, name) => { - let timeNode = DOM.div('timestamp'); - timeNode.innerText = name; - timeNode.style.left = ((time - start) * this._timeToPixel) + 'px'; - chunksNode.appendChild(timeNode); - }; - let backgroundTodo = []; - for (let i = 0; i < chunks.length; i++) { - let chunk = chunks[i]; - let height = (chunk.size() / max * kChunkHeight); - chunk.height = height; - if (chunk.isEmpty()) continue; - let node = DOM.div(); - node.className = 'chunk'; - node.style.left = ((chunks[i].start - start) * this._timeToPixel) + 'px'; - node.style.height = height + 'px'; - node.chunk = chunk; - node.addEventListener('mousemove', e => this.handleChunkMouseMove(e)); - node.addEventListener('click', e => this.handleChunkClick(e)); - node.addEventListener('dblclick', e => this.handleChunkDoubleClick(e)); - backgroundTodo.push([chunk, node]) - chunksNode.appendChild(node); - } - this.setChunkBackgrounds(backgroundTodo); - - // Put a time marker roughly every 20 chunks. - let expected = duration / chunks.length * 20; - let interval = (10 ** Math.floor(Math.log10(expected))); - let correction = Math.log10(expected / interval); - correction = (correction < 0.33) ? 1 : (correction < 0.75) ? 2.5 : 5; - interval *= correction; - - let time = start; - while (time < end) { - addTimestamp(time, ((time - start) / 1000) + ' ms'); - time += interval; - } - this.redraw(); - } - - handleChunkMouseMove(event) { - if (this.isLocked) return false; - if (this._isSelecting) return false; - let chunk = event.target.chunk; - if (!chunk) return; - // topmost map (at chunk.height) == map #0. - let relativeIndex = - Math.round(event.layerY / event.target.offsetHeight * chunk.size()); - let map = chunk.at(relativeIndex); - this.dispatchEvent(new FocusEvent(map)); - } - - handleChunkClick(event) { - this.isLocked = !this.isLocked; - } - - handleChunkDoubleClick(event) { - let chunk = event.target.chunk; - if (!chunk) return; - this.dispatchEvent(new SelectTimeEvent(chunk.start, chunk.end)); - } - - redraw() { - let canvas = this.timelineCanvas; - canvas.width = (this.chunks.length + 1) * kChunkWidth; - canvas.height = kChunkHeight; - let ctx = canvas.getContext('2d'); - ctx.clearRect(0, 0, canvas.width, kChunkHeight); - if (!this.selectedEntry || !this.selectedEntry.edge) return; - this.drawEdges(ctx); - } - setMapStyle(map, ctx) { - ctx.fillStyle = map.edge && map.edge.from ? CSSColor.onBackgroundColor : - CSSColor.onPrimaryColor; - } - - setEdgeStyle(edge, ctx) { - let color = this.typeToColor(edge.type); - ctx.strokeStyle = color; - ctx.fillStyle = color; - } - - markMap(ctx, map) { - let [x, y] = map.position(this.chunks); - ctx.beginPath(); - this.setMapStyle(map, ctx); - ctx.arc(x, y, 3, 0, 2 * Math.PI); - ctx.fill(); - ctx.beginPath(); - ctx.fillStyle = CSSColor.onBackgroundColor; - ctx.arc(x, y, 2, 0, 2 * Math.PI); - ctx.fill(); - } - - markSelectedMap(ctx, map) { - let [x, y] = map.position(this.chunks); - ctx.beginPath(); - this.setMapStyle(map, ctx); - ctx.arc(x, y, 6, 0, 2 * Math.PI); - ctx.strokeStyle = CSSColor.onBackgroundColor; - ctx.stroke(); - } - - drawEdges(ctx) { - // Draw the trace of maps in reverse order to make sure the outgoing - // transitions of previous maps aren't drawn over. - const kMaxOutgoingEdges = 100; - let nofEdges = 0; - let stack = []; - let current = this.selectedEntry; - while (current && nofEdges < kMaxOutgoingEdges) { - nofEdges += current.children.length; - stack.push(current); - current = current.parent(); - } - ctx.save(); - this.drawOutgoingEdges(ctx, this.selectedEntry, 3); - ctx.restore(); - - let labelOffset = 15; - let xPrev = 0; - while (current = stack.pop()) { - if (current.edge) { - this.setEdgeStyle(current.edge, ctx); - let [xTo, yTo] = this.drawEdge(ctx, current.edge, true, labelOffset); - if (xTo == xPrev) { - labelOffset += 8; - } else { - labelOffset = 15 - } - xPrev = xTo; - } - this.markMap(ctx, current); - current = current.parent(); - ctx.save(); - // this.drawOutgoingEdges(ctx, current, 1); - ctx.restore(); - } - // Mark selected map - this.markSelectedMap(ctx, this.selectedEntry); - } - - drawEdge(ctx, edge, showLabel = true, labelOffset = 20) { - if (!edge.from || !edge.to) return [-1, -1]; - let [xFrom, yFrom] = edge.from.position(this.chunks); - let [xTo, yTo] = edge.to.position(this.chunks); - let sameChunk = xTo == xFrom; - if (sameChunk) labelOffset += 8; - - ctx.beginPath(); - ctx.moveTo(xFrom, yFrom); - let offsetX = 20; - let offsetY = 20; - let midX = xFrom + (xTo - xFrom) / 2; - let midY = (yFrom + yTo) / 2 - 100; - if (!sameChunk) { - ctx.quadraticCurveTo(midX, midY, xTo, yTo); - } else { - ctx.lineTo(xTo, yTo); - } - if (!showLabel) { - ctx.stroke(); - } else { - let centerX, centerY; - if (!sameChunk) { - centerX = (xFrom / 2 + midX + xTo / 2) / 2; - centerY = (yFrom / 2 + midY + yTo / 2) / 2; - } else { - centerX = xTo; - centerY = yTo; - } - ctx.moveTo(centerX, centerY); - ctx.lineTo(centerX + offsetX, centerY - labelOffset); - ctx.stroke(); - ctx.textAlign = 'left'; - ctx.fillStyle = this.typeToColor(edge.type); - ctx.fillText( - edge.toString(), centerX + offsetX + 2, centerY - labelOffset); - } - return [xTo, yTo]; - } - - drawOutgoingEdges(ctx, map, max = 10, depth = 0) { - if (!map) return; - if (depth >= max) return; - ctx.globalAlpha = 0.5 - depth * (0.3 / max); - ctx.strokeStyle = CSSColor.timelineBackgroundColor; - const limit = Math.min(map.children.length, 100) - for (let i = 0; i < limit; i++) { - let edge = map.children[i]; - this.drawEdge(ctx, edge, true); - this.drawOutgoingEdges(ctx, edge.to, max, depth + 1); - } - } -}); diff --git a/deps/v8/tools/system-analyzer/view/code-panel-template.html b/deps/v8/tools/system-analyzer/view/code-panel-template.html new file mode 100644 index 0000000000..e04c6be8c1 --- /dev/null +++ b/deps/v8/tools/system-analyzer/view/code-panel-template.html @@ -0,0 +1,25 @@ +<!-- 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. --> + +<head> + <link href="./index.css" rel="stylesheet"> +</head> +<style> + #sourceCode { + white-space: pre-line; + } +</style> +<div class="panel"> + <h2>Code Panel</h2> + <div class="selection"> + <select id="codeSelect"></select> + <button id="selectedRelatedButton">Select Related Events</button> + </div> + <div class="panelBody"> + <h3>Disassembly</h3> + <pre id="disassembly"></pre> + <h3>Source Code</h3> + <pre id="sourceCode"></pre> + </div> +</div> diff --git a/deps/v8/tools/system-analyzer/view/code-panel.mjs b/deps/v8/tools/system-analyzer/view/code-panel.mjs new file mode 100644 index 0000000000..3b5261e03c --- /dev/null +++ b/deps/v8/tools/system-analyzer/view/code-panel.mjs @@ -0,0 +1,82 @@ +// 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. +import {IcLogEntry} from '../log/ic.mjs'; +import {MapLogEntry} from '../log/map.mjs'; + +import {FocusEvent, SelectionEvent, ToolTipEvent} from './events.mjs'; +import {delay, DOM, formatBytes, formatMicroSeconds, V8CustomElement} from './helper.mjs'; + +DOM.defineCustomElement('view/code-panel', + (templateText) => + class CodePanel extends V8CustomElement { + _timeline; + _selectedEntries; + _entry; + + constructor() { + super(templateText); + this._codeSelectNode.onchange = this._handleSelectCode.bind(this); + this.$('#selectedRelatedButton').onclick = + this._handleSelectRelated.bind(this) + } + + set timeline(timeline) { + this._timeline = timeline; + this.$('.panel').style.display = timeline.isEmpty() ? 'none' : 'inherit'; + this.update(); + } + + set selectedEntries(entries) { + this._selectedEntries = entries; + // TODO: add code selection dropdown + this._updateSelect(); + this.entry = entries.first(); + } + + set entry(entry) { + this._entry = entry; + this.update(); + } + + get _disassemblyNode() { + return this.$('#disassembly'); + } + + get _sourceNode() { + return this.$('#sourceCode'); + } + + get _codeSelectNode() { + return this.$('#codeSelect'); + } + + _update() { + this._disassemblyNode.innerText = this._entry?.disassemble ?? ''; + this._sourceNode.innerText = this._entry?.source ?? ''; + } + + _updateSelect() { + const select = this._codeSelectNode; + select.options.length = 0; + const sorted = + this._selectedEntries.slice().sort((a, b) => a.time - b.time); + for (const code of this._selectedEntries) { + const option = DOM.element('option'); + option.text = + `${code.name}(...) t=${formatMicroSeconds(code.time)} size=${ + formatBytes(code.size)} script=${code.script?.toString()}`; + option.data = code; + select.add(option); + } + } + + _handleSelectCode() { + this.entry = this._codeSelectNode.selectedOptions[0].data; + } + + _handleSelectRelated(e) { + if (!this._entry) return; + this.dispatchEvent(new SelectRelatedEvent(this._entry)); + } +});
\ No newline at end of file diff --git a/deps/v8/tools/system-analyzer/view/events.mjs b/deps/v8/tools/system-analyzer/view/events.mjs new file mode 100644 index 0000000000..4204973f8f --- /dev/null +++ b/deps/v8/tools/system-analyzer/view/events.mjs @@ -0,0 +1,93 @@ +// 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. + +class AppEvent extends CustomEvent { + constructor(name) { + super(name, {bubbles: true, composed: true}); + } +} + +export class SelectionEvent extends AppEvent { + // TODO: turn into static class fields once Safari supports it. + static get name() { + return 'select'; + } + + constructor(entries) { + super(SelectionEvent.name); + if (!Array.isArray(entries) || entries.length == 0) { + throw new Error('No valid entries selected!'); + } + this.entries = entries; + } +} + +export class SelectRelatedEvent extends AppEvent { + static get name() { + return 'selectrelated'; + } + + constructor(entry) { + super(SelectRelatedEvent.name); + this.entry = entry; + } +} + +export class FocusEvent extends AppEvent { + static get name() { + return 'showentrydetail'; + } + + constructor(entry) { + super(FocusEvent.name); + this.entry = entry; + } +} + +export class SelectTimeEvent extends AppEvent { + static get name() { + return 'timerangeselect'; + } + + constructor(start = 0, end = Infinity) { + super(SelectTimeEvent.name); + this.start = start; + this.end = end; + } +} + +export class SynchronizeSelectionEvent extends AppEvent { + static get name() { + return 'syncselection'; + } + + constructor(start, end) { + super(SynchronizeSelectionEvent.name); + this.start = start; + this.end = end; + } +} + +export class ToolTipEvent extends AppEvent { + static get name() { + return 'showtooltip'; + } + + constructor(content, positionOrTargetNode) { + super(ToolTipEvent.name); + this._content = content; + if (!positionOrTargetNode && !node) { + throw Error('Either provide a valid position or targetNode'); + } + this._positionOrTargetNode = positionOrTargetNode; + } + + get content() { + return this._content; + } + + get positionOrTargetNode() { + return this._positionOrTargetNode; + } +} diff --git a/deps/v8/tools/system-analyzer/view/helper.mjs b/deps/v8/tools/system-analyzer/view/helper.mjs new file mode 100644 index 0000000000..780864ef5a --- /dev/null +++ b/deps/v8/tools/system-analyzer/view/helper.mjs @@ -0,0 +1,314 @@ +// 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. + +class CSSColor { + static _cache = new Map(); + + static get(name) { + let color = this._cache.get(name); + if (color !== undefined) return color; + const style = getComputedStyle(document.body); + color = style.getPropertyValue(`--${name}`); + if (color === undefined) { + throw new Error(`CSS color does not exist: ${name}`); + } + color = color.trim(); + this._cache.set(name, color); + return color; + } + static reset() { + this._cache.clear(); + } + + static get backgroundColor() { + return this.get('background-color'); + } + static get surfaceColor() { + return this.get('surface-color'); + } + static get primaryColor() { + return this.get('primary-color'); + } + static get secondaryColor() { + return this.get('secondary-color'); + } + static get onSurfaceColor() { + return this.get('on-surface-color'); + } + static get onBackgroundColor() { + return this.get('on-background-color'); + } + static get onPrimaryColor() { + return this.get('on-primary-color'); + } + static get onSecondaryColor() { + return this.get('on-secondary-color'); + } + static get defaultColor() { + return this.get('default-color'); + } + static get errorColor() { + return this.get('error-color'); + } + static get mapBackgroundColor() { + return this.get('map-background-color'); + } + static get timelineBackgroundColor() { + return this.get('timeline-background-color'); + } + static get red() { + return this.get('red'); + } + static get green() { + return this.get('green'); + } + static get yellow() { + return this.get('yellow'); + } + static get blue() { + return this.get('blue'); + } + + static get orange() { + return this.get('orange'); + } + + static get violet() { + return this.get('violet'); + } + + static at(index) { + return this.list[index % this.list.length]; + } + + static darken(hexColorString, amount = -40) { + if (hexColorString[0] !== '#') { + throw new Error(`Unsupported color: ${hexColorString}`); + } + let color = parseInt(hexColorString.substring(1), 16); + let b = Math.min(Math.max((color & 0xFF) + amount, 0), 0xFF); + let g = Math.min(Math.max(((color >> 8) & 0xFF) + amount, 0), 0xFF); + let r = Math.min(Math.max(((color >> 16) & 0xFF) + amount, 0), 0xFF); + color = (r << 16) + (g << 8) + b; + return `#${color.toString(16).padStart(6, '0')}`; + } + + static get list() { + if (!this._colors) { + this._colors = [ + this.green, + this.violet, + this.orange, + this.yellow, + this.primaryColor, + this.red, + this.blue, + this.yellow, + this.secondaryColor, + this.darken(this.green), + this.darken(this.violet), + this.darken(this.orange), + this.darken(this.yellow), + this.darken(this.primaryColor), + this.darken(this.red), + this.darken(this.blue), + this.darken(this.yellow), + this.darken(this.secondaryColor), + ]; + } + return this._colors; + } +} + +class DOM { + static element(type, classes) { + const node = document.createElement(type); + if (classes === undefined) return node; + if (typeof classes === 'string') { + node.className = classes; + } else { + classes.forEach(cls => node.classList.add(cls)); + } + return node; + } + + static text(string) { + return document.createTextNode(string); + } + + static div(classes) { + return this.element('div', classes); + } + + static span(classes) { + return this.element('span', classes); + } + + static table(classes) { + return this.element('table', classes); + } + + static tbody(classes) { + return this.element('tbody', classes); + } + + static td(textOrNode, className) { + const node = this.element('td'); + if (typeof textOrNode === 'object') { + node.appendChild(textOrNode); + } else if (textOrNode) { + node.innerText = textOrNode; + } + if (className) node.className = className; + return node; + } + + static tr(classes) { + return this.element('tr', classes); + } + + static removeAllChildren(node) { + let range = document.createRange(); + range.selectNodeContents(node); + range.deleteContents(); + } + + static defineCustomElement(path, generator) { + let name = path.substring(path.lastIndexOf('/') + 1, path.length); + path = path + '-template.html'; + fetch(path) + .then(stream => stream.text()) + .then( + templateText => + customElements.define(name, generator(templateText))); + } +} + +function $(id) { + return document.querySelector(id) +} + +class V8CustomElement extends HTMLElement { + _updateTimeoutId; + _updateCallback = this._update.bind(this); + + constructor(templateText) { + super(); + const shadowRoot = this.attachShadow({mode: 'open'}); + shadowRoot.innerHTML = templateText; + this._updateCallback = this._update.bind(this); + } + + $(id) { + return this.shadowRoot.querySelector(id); + } + + querySelectorAll(query) { + return this.shadowRoot.querySelectorAll(query); + } + + update(useAnimation = false) { + if (useAnimation) { + window.cancelAnimationFrame(this._updateTimeoutId); + this._updateTimeoutId = + window.requestAnimationFrame(this._updateCallback); + } else { + // Use timeout tasks to asynchronously update the UI without blocking. + clearTimeout(this._updateTimeoutId); + const kDelayMs = 5; + this._updateTimeoutId = setTimeout(this._updateCallback, kDelayMs); + } + } + + _update() { + throw Error('Subclass responsibility'); + } +} + +class Chunked { + constructor(iterable, limit) { + this._iterator = iterable[Symbol.iterator](); + this._limit = limit; + } + + * next(limit = undefined) { + for (let i = 0; i < (limit ?? this._limit); i++) { + const {value, done} = this._iterator.next(); + if (done) { + this._iterator = undefined; + return; + }; + yield value; + } + } + + get hasMore() { + return this._iterator !== undefined; + } +} + +class LazyTable { + constructor(table, rowData, rowElementCreator, limit = 100) { + this._table = table; + this._chunkedRowData = new Chunked(rowData, limit); + this._rowElementCreator = rowElementCreator; + if (table.tBodies.length == 0) { + table.appendChild(DOM.tbody()); + } else { + table.replaceChild(DOM.tbody(), table.tBodies[0]); + } + if (!table.tFoot) { + const td = table.appendChild(DOM.element('tfoot')) + .appendChild(DOM.tr()) + .appendChild(DOM.td()); + for (let count of [10, 100]) { + const button = DOM.element('button'); + button.innerText = `+${count}`; + button.onclick = (e) => this._addMoreRows(count); + td.appendChild(button); + } + td.setAttribute('colspan', 100); + } + table.tFoot.addEventListener('click', this._clickHandler); + this._addMoreRows(); + } + + _addMoreRows(count = undefined) { + const fragment = new DocumentFragment(); + for (let row of this._chunkedRowData.next(count)) { + const tr = this._rowElementCreator(row); + fragment.appendChild(tr); + } + this._table.tBodies[0].appendChild(fragment); + if (!this._chunkedRowData.hasMore) { + DOM.removeAllChildren(this._table.tFoot); + } + } +} + +export function gradientStopsFromGroups( + totalLength, maxHeight, groups, colorFn) { + const kMaxHeight = maxHeight === '%' ? 100 : maxHeight; + const kUnit = maxHeight === '%' ? '%' : 'px'; + let increment = 0; + let lastHeight = 0.0; + const stops = []; + for (let group of groups) { + const color = colorFn(group.key); + increment += group.count; + let height = (increment / totalLength * kMaxHeight) | 0; + stops.push(`${color} ${lastHeight}${kUnit} ${height}${kUnit}`) + lastHeight = height; + } + return stops; +} + +export * from '../helper.mjs'; +export { + DOM, + $, + V8CustomElement, + CSSColor, + LazyTable, +}; diff --git a/deps/v8/tools/system-analyzer/view/list-panel-template.html b/deps/v8/tools/system-analyzer/view/list-panel-template.html new file mode 100644 index 0000000000..4714f97c02 --- /dev/null +++ b/deps/v8/tools/system-analyzer/view/list-panel-template.html @@ -0,0 +1,78 @@ +<!-- 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. --> + +<head> + <link href="./index.css" rel="stylesheet"> +</head> +<style> + .count { + text-align: right; + width: 5em; + } + + .percentage { + text-align: right; + width: 4em; + } + + .key { + padding-left: 1em; + } + + .drilldown-group-title { + font-weight: bold; + padding: 0.5em 0 0.2em 0; + } + + .toggle { + width: 1em; + text-align: left; + cursor: -webkit-zoom-in; + color: rgba(var(--border-color), 1); + } + + .toggle::before { + content: "â–¶"; + } + .open .toggle::before { + content: "â–¼"; + } + + .panel { + position: relative; + } + + .panelBody { + height: 400px; + } + + .scroller { + max-height: 800px; + overflow-y: scroll; + } + + .legend { + top: 40px; + } +</style> + +<div class="panel"> + <input type="checkbox" id="closer" class="panelCloserInput"> + <label class="panelCloserLabel" for="closer">â–¼</label> + <h2 id="title"></h2> + <div class="selection"> + <input type="radio" id="show-all" name="selectionType" value="all"> + <label for="show-all">All</label> + <input type="radio" id="show-timerange" name="selectionType" value="timerange"> + <label for="show-timerange">Time Range</label> + <input type="radio" id="show-selection" name="selectionType" value="selection"> + <label for="show-selection">Last Selection</label> + </div> + <select id="group-key"></select> + <div id="content" class="panelBody"> + <slot></slot> + <table id="table" width="100%"> + </table> + </div> +</div> diff --git a/deps/v8/tools/system-analyzer/view/list-panel.mjs b/deps/v8/tools/system-analyzer/view/list-panel.mjs new file mode 100644 index 0000000000..85e3cd47e2 --- /dev/null +++ b/deps/v8/tools/system-analyzer/view/list-panel.mjs @@ -0,0 +1,198 @@ +// 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. + +import {Script, SourcePosition} from '../../profile.mjs'; +import {LogEntry} from '../log/log.mjs'; + +import {FocusEvent} from './events.mjs'; +import {groupBy, LazyTable} from './helper.mjs'; +import {DOM, V8CustomElement} from './helper.mjs'; + +DOM.defineCustomElement('view/list-panel', + (templateText) => + class ListPanel extends V8CustomElement { + _selectedLogEntries = []; + _displayedLogEntries = []; + _timeline; + + _detailsClickHandler = this._handleDetailsClick.bind(this); + _logEntryClickHandler = this._handleLogEntryClick.bind(this); + + constructor() { + super(templateText); + this.groupKey.addEventListener('change', e => this.update()); + this.showAllRadio.onclick = _ => this._showEntries(this._timeline); + this.showTimerangeRadio.onclick = _ => + this._showEntries(this._timeline.selectionOrSelf); + this.showSelectionRadio.onclick = _ => + this._showEntries(this._selectedLogEntries); + } + + static get observedAttributes() { + return ['title']; + } + + attributeChangedCallback(name, oldValue, newValue) { + if (name == 'title') { + this.$('#title').innerHTML = newValue; + } + } + + set timeline(timeline) { + console.assert(timeline !== undefined, 'timeline undefined!'); + this._timeline = timeline; + this.$('.panel').style.display = timeline.isEmpty() ? 'none' : 'inherit'; + this._initGroupKeySelect(); + } + + set selectedLogEntries(entries) { + if (entries === this._timeline) { + this.showAllRadio.click(); + } else if (entries === this._timeline.selection) { + this.showTimerangeRadio.click(); + } else { + this._selectedLogEntries = entries; + this.showSelectionRadio.click(); + } + } + + get entryClass() { + return this._timeline.at(0)?.constructor; + } + + get groupKey() { + return this.$('#group-key'); + } + + get table() { + return this.$('#table'); + } + + get showAllRadio() { + return this.$('#show-all'); + } + get showTimerangeRadio() { + return this.$('#show-timerange'); + } + get showSelectionRadio() { + return this.$('#show-selection'); + } + + get _propertyNames() { + return this.entryClass?.propertyNames ?? []; + } + + _initGroupKeySelect() { + const select = this.groupKey; + select.options.length = 0; + for (const propertyName of this._propertyNames) { + const option = DOM.element('option'); + option.text = propertyName; + select.add(option); + } + } + + _showEntries(entries) { + this._displayedLogEntries = entries; + this.update(); + } + + _update() { + if (this._timeline.isEmpty()) return; + DOM.removeAllChildren(this.table); + if (this._displayedLogEntries.length == 0) return; + const propertyName = this.groupKey.selectedOptions[0].text; + const groups = + groupBy(this._displayedLogEntries, each => each[propertyName], true); + this._render(groups, this.table); + } + + createSubgroups(group) { + const map = new Map(); + for (let propertyName of this._propertyNames) { + map.set( + propertyName, + groupBy(group.entries, each => each[propertyName], true)); + } + return map; + } + + _handleLogEntryClick(e) { + const group = e.currentTarget.group; + this.dispatchEvent(new FocusEvent(group.key)); + } + + _handleDetailsClick(event) { + event.stopPropagation(); + const tr = event.target.parentNode; + const group = tr.group; + // Create subgroup in-place if the don't exist yet. + if (tr.groups === undefined) { + const groups = tr.groups = this.createSubgroups(group); + this.renderDrilldown(groups, tr); + } + const detailsTr = tr.nextSibling; + if (tr.classList.contains('open')) { + tr.classList.remove('open'); + detailsTr.style.display = 'none'; + } else { + tr.classList.add('open'); + detailsTr.style.display = 'table-row'; + } + } + + renderDrilldown(groups, previousSibling) { + const tr = DOM.tr('entry-details'); + tr.style.display = 'none'; + // indent by one td. + tr.appendChild(DOM.td()); + const td = DOM.td(); + td.colSpan = 3; + groups.forEach((group, key) => { + this.renderDrilldownGroup(td, group, key); + }); + tr.appendChild(td); + // Append the new TR after previousSibling. + previousSibling.parentNode.insertBefore(tr, previousSibling.nextSibling); + } + + renderDrilldownGroup(td, groups, key) { + const div = DOM.div('drilldown-group-title'); + div.textContent = `Grouped by ${key}: ${groups[0]?.parentTotal ?? 0}#`; + td.appendChild(div); + const table = DOM.table(); + this._render(groups, table, false) + td.appendChild(table); + } + + _render(groups, table) { + let last; + new LazyTable(table, groups, group => { + if (last && last.count < group.count) { + console.log(last, group); + } + last = group; + const tr = DOM.tr(); + tr.group = group; + const details = tr.appendChild(DOM.td('', 'toggle')); + details.onclick = this._detailsClickHandler; + tr.appendChild(DOM.td(`${group.percent.toFixed(2)}%`, 'percentage')); + tr.appendChild(DOM.td(group.count, 'count')); + const valueTd = tr.appendChild(DOM.td(`${group.key}`, 'key')); + if (this._isClickable(group.key)) { + tr.onclick = this._logEntryClickHandler; + valueTd.classList.add('clickable'); + } + return tr; + }, 10); + } + + _isClickable(object) { + if (typeof object !== 'object') return false; + if (object instanceof LogEntry) return true; + if (object instanceof SourcePosition) return true; + if (object instanceof Script) return true; + return false; + } +}); diff --git a/deps/v8/tools/system-analyzer/log-file-reader-template.html b/deps/v8/tools/system-analyzer/view/log-file-reader-template.html index e54d45990a..e54d45990a 100644 --- a/deps/v8/tools/system-analyzer/log-file-reader-template.html +++ b/deps/v8/tools/system-analyzer/view/log-file-reader-template.html diff --git a/deps/v8/tools/system-analyzer/log-file-reader.mjs b/deps/v8/tools/system-analyzer/view/log-file-reader.mjs index c46d792d00..fd935f2f83 100644 --- a/deps/v8/tools/system-analyzer/log-file-reader.mjs +++ b/deps/v8/tools/system-analyzer/view/log-file-reader.mjs @@ -3,7 +3,7 @@ // found in the LICENSE file. import {DOM, V8CustomElement} from './helper.mjs'; -DOM.defineCustomElement('log-file-reader', +DOM.defineCustomElement('view/log-file-reader', (templateText) => class LogFileReader extends V8CustomElement { constructor() { diff --git a/deps/v8/tools/system-analyzer/view/map-panel-template.html b/deps/v8/tools/system-analyzer/view/map-panel-template.html new file mode 100644 index 0000000000..8a2b23ee3d --- /dev/null +++ b/deps/v8/tools/system-analyzer/view/map-panel-template.html @@ -0,0 +1,36 @@ +<!-- 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. --> + +<head> + <link href="./index.css" rel="stylesheet"> +</head> +<style> + #searchBarInput { + width: 200px; + } +</style> +<div class="panel"> + <input type="checkbox" id="closer" class="panelCloserInput"> + <label class="panelCloserLabel" for="closer">â–¼</label> + <h2>Map Panel</h2> + <div class="selection"> + <input type="radio" id="show-all" name="selectionType" value="all"> + <label for="show-all">All</label> + <input type="radio" id="show-timerange" name="selectionType" value="timerange"> + <label for="show-timerange">Time Range</label> + <input type="radio" id="show-selection" name="selectionType" value="selection"> + <label for="show-selection">Last Selection</label> + </div> + <h3>All Transitions</h3> + <map-transitions id="map-transitions"></map-transitions> + <h3>Search Map by Address</h3> + <section id="searchBar"></section> + <input type="search" id="searchBarInput"></input> + <button id="searchBarBtn">Search</button> + + <h3>Details Transitions</h3> + <map-transitions id="map-details-transitions"></map-transitions> + <h3>Details</h3> + <map-details id="map-details"></map-details> +</div> diff --git a/deps/v8/tools/system-analyzer/view/map-panel.mjs b/deps/v8/tools/system-analyzer/view/map-panel.mjs new file mode 100644 index 0000000000..7ee2325f34 --- /dev/null +++ b/deps/v8/tools/system-analyzer/view/map-panel.mjs @@ -0,0 +1,106 @@ +// 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. +import './map-panel/map-details.mjs'; +import './map-panel/map-transitions.mjs'; + +import {MapLogEntry} from '../log/map.mjs'; + +import {FocusEvent} from './events.mjs'; +import {DOM, V8CustomElement} from './helper.mjs'; + +DOM.defineCustomElement( + 'view/map-panel', (templateText) => class MapPanel extends V8CustomElement { + _map; + _timeline; + _selectedLogEntries = []; + _displayedLogEntries = []; + + constructor() { + super(templateText); + this.searchBarBtn.addEventListener('click', e => this._handleSearch(e)); + this.showAllRadio.onclick = _ => this._showEntries(this._timeline); + this.showTimerangeRadio.onclick = _ => + this._showEntries(this._timeline.selectionOrSelf); + this.showSelectionRadio.onclick = _ => + this._showEntries(this._selectedLogEntries); + } + + get showAllRadio() { + return this.$('#show-all'); + } + get showTimerangeRadio() { + return this.$('#show-timerange'); + } + get showSelectionRadio() { + return this.$('#show-selection'); + } + + get mapTransitionsPanel() { + return this.$('#map-transitions'); + } + + get mapDetailsTransitionsPanel() { + return this.$('#map-details-transitions'); + } + + get mapDetailsPanel() { + return this.$('#map-details'); + } + + get searchBarBtn() { + return this.$('#searchBarBtn'); + } + + get searchBar() { + return this.$('#searchBar'); + } + + set timeline(timeline) { + console.assert(timeline !== undefined, 'timeline undefined!'); + this._timeline = timeline; + this.$('.panel').style.display = + timeline.isEmpty() ? 'none' : 'inherit'; + this.mapTransitionsPanel.timeline = timeline; + this.mapDetailsTransitionsPanel.timeline = timeline; + } + + set selectedLogEntries(entries) { + if (entries === this._timeline.selection) { + this.showTimerangeRadio.click(); + } else if (entries == this._timeline) { + this.showAllRadio.click(); + } else { + this._selectedLogEntries = entries; + this.showSelectionRadio.click(); + } + } + + set map(map) { + this._map = map; + this.mapDetailsTransitionsPanel.selectedLogEntries = [map]; + this.mapDetailsPanel.map = map; + } + + _showEntries(entries) { + this._displayedLogEntries = entries; + this.mapTransitionsPanel.selectedLogEntries = entries; + } + + update() { + // nothing to do + } + + _handleSearch(e) { + let searchBar = this.$('#searchBarInput'); + let searchBarInput = searchBar.value; + // access the map from model cache + let selectedMap = MapLogEntry.get(searchBarInput); + if (selectedMap) { + searchBar.className = 'success'; + this.dispatchEvent(new FocusEvent(selectedMap)); + } else { + searchBar.className = 'failure'; + } + } + }); diff --git a/deps/v8/tools/system-analyzer/map-panel/map-details-template.html b/deps/v8/tools/system-analyzer/view/map-panel/map-details-template.html index 6d1b268c5d..b109d4c8ac 100644 --- a/deps/v8/tools/system-analyzer/map-panel/map-details-template.html +++ b/deps/v8/tools/system-analyzer/view/map-panel/map-details-template.html @@ -8,16 +8,12 @@ found in the LICENSE file. --> <style> #mapDetails, #filePositionNode { - overflow-x: scroll; + overflow: scroll; } - - #mapDetails::-webkit-scrollbar { - width: 0; - background-color: transparent; + #mapDetails { + font-family: monospace; + white-space: pre; } </style> -<div class="panel"> - <h4>Map Details</h4> - <section id="filePositionNode"></section> - <section id="mapDetails"></section> -</div> +<section id="filePositionNode"></section> +<section id="mapDetails"></section> diff --git a/deps/v8/tools/system-analyzer/map-panel/map-details.mjs b/deps/v8/tools/system-analyzer/view/map-panel/map-details.mjs index bcf8f9c9aa..446475a5b0 100644 --- a/deps/v8/tools/system-analyzer/map-panel/map-details.mjs +++ b/deps/v8/tools/system-analyzer/view/map-panel/map-details.mjs @@ -5,7 +5,7 @@ import {FocusEvent} from '../events.mjs'; import {DOM, V8CustomElement} from '../helper.mjs'; DOM.defineCustomElement( - './map-panel/map-details', + './view/map-panel/map-details', (templateText) => class MapDetails extends V8CustomElement { _map; diff --git a/deps/v8/tools/system-analyzer/map-panel/map-transitions-template.html b/deps/v8/tools/system-analyzer/view/map-panel/map-transitions-template.html index c4cab2bf46..d5dcca07a7 100644 --- a/deps/v8/tools/system-analyzer/map-panel/map-transitions-template.html +++ b/deps/v8/tools/system-analyzer/view/map-panel/map-transitions-template.html @@ -137,12 +137,4 @@ found in the LICENSE file. --> padding-bottom: 10px; } </style> -<div class="panel"> - <div id="title"> - <h4>Transitions</h4> - </div> - <section id="transitionView"></section> - <div id="tooltip"> - <div id="tooltipContents"></div> - </div> -</div> +<section id="transitionView"></section> diff --git a/deps/v8/tools/system-analyzer/view/map-panel/map-transitions.mjs b/deps/v8/tools/system-analyzer/view/map-panel/map-transitions.mjs new file mode 100644 index 0000000000..f60bd37d39 --- /dev/null +++ b/deps/v8/tools/system-analyzer/view/map-panel/map-transitions.mjs @@ -0,0 +1,181 @@ +// 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. +import {FocusEvent, SelectRelatedEvent, ToolTipEvent} from '../events.mjs'; +import {CSSColor} from '../helper.mjs'; +import {DOM, V8CustomElement} from '../helper.mjs'; + +DOM.defineCustomElement( + './view/map-panel/map-transitions', + (templateText) => class MapTransitions extends V8CustomElement { + _timeline; + _map; + _edgeToColor = new Map(); + _selectedLogEntries; + _displayedMapsInTree; + _toggleSubtreeHandler = this._handleToggleSubtree.bind(this); + _mapClickHandler = this._handleMapClick.bind(this); + _mapDoubleClickHandler = this._handleMapDoubleClick.bind(this); + _mouseoverMapHandler = this._handleMouseoverMap.bind(this); + + constructor() { + super(templateText); + this.currentNode = this.transitionView; + } + + get transitionView() { + return this.$('#transitionView'); + } + + set timeline(timeline) { + this._timeline = timeline; + this._edgeToColor.clear(); + timeline.getBreakdown().forEach(breakdown => { + this._edgeToColor.set(breakdown.key, CSSColor.at(breakdown.id)); + }); + } + + set selectedLogEntries(list) { + this._selectedLogEntries = list; + this.update(); + } + + _update() { + this.transitionView.style.display = 'none'; + DOM.removeAllChildren(this.transitionView); + if (this._selectedLogEntries.length == 0) return; + this._displayedMapsInTree = new Set(); + // Limit view to 200 maps for performance reasons. + this._selectedLogEntries.slice(0, 200).forEach( + (map) => this._addMapAndParentTransitions(map)); + this._displayedMapsInTree = undefined; + this.transitionView.style.display = ''; + } + + _addMapAndParentTransitions(map) { + if (map === undefined) return; + if (this._displayedMapsInTree.has(map)) return; + this._displayedMapsInTree.add(map); + this.currentNode = this.transitionView; + let parents = map.getParents(); + if (parents.length > 0) { + this._addTransitionTo(parents.pop()); + parents.reverse().forEach((each) => this._addTransitionTo(each)); + } + let mapNode = this._addSubtransitions(map); + // Mark and show the selected map. + mapNode.classList.add('selected'); + if (this.selectedMap == map) { + setTimeout( + () => mapNode.scrollIntoView({ + behavior: 'smooth', + block: 'nearest', + inline: 'nearest', + }), + 1); + } + } + + _addSubtransitions(map) { + let mapNode = this._addTransitionTo(map); + // Draw outgoing linear transition line. + let current = map; + while (current.children.length == 1) { + current = current.children[0].to; + this._addTransitionTo(current); + } + return mapNode; + } + + _addTransitionEdge(map) { + let classes = ['transitionEdge']; + let edge = DOM.div(classes); + edge.style.backgroundColor = this._edgeToColor.get(map.edge.type); + let labelNode = DOM.div('transitionLabel'); + labelNode.innerText = map.edge.toString(); + edge.appendChild(labelNode); + return edge; + } + + _addTransitionTo(map) { + // transition[ transitions[ transition[...], transition[...], ...]]; + this._displayedMapsInTree?.add(map); + let transition = DOM.div('transition'); + if (map.isDeprecated()) transition.classList.add('deprecated'); + if (map.edge) { + transition.appendChild(this._addTransitionEdge(map)); + } + let mapNode = this._addMapNode(map); + transition.appendChild(mapNode); + + let subtree = DOM.div('transitions'); + transition.appendChild(subtree); + + this.currentNode.appendChild(transition); + this.currentNode = subtree; + + return mapNode; + } + + _addMapNode(map) { + let node = DOM.div('map'); + if (map.edge) + node.style.backgroundColor = this._edgeToColor.get(map.edge.type); + node.map = map; + node.onclick = this._mapClickHandler + node.ondblclick = this._mapDoubleClickHandler + node.onmouseover = this._mouseoverMapHandler + if (map.children.length > 1) { + node.innerText = map.children.length; + const showSubtree = DOM.div('showSubtransitions'); + showSubtree.onclick = this._toggleSubtreeHandler + node.appendChild(showSubtree); + } + else if (map.children.length == 0) { + node.innerHTML = '●'; + } + this.currentNode.appendChild(node); + return node; + } + + _handleMapClick(event) { + const map = event.currentTarget.map; + this.dispatchEvent(new FocusEvent(map)); + } + + _handleMapDoubleClick(event) { + this.dispatchEvent(new SelectRelatedEvent(event.currentTarget.map)); + } + + _handleMouseoverMap(event) { + this.dispatchEvent(new ToolTipEvent( + event.currentTarget.map.toStringLong(), event.currentTarget)); + } + + _handleToggleSubtree(event) { + event.preventDefault(); + const node = event.currentTarget.parentElement; + let map = node.map; + event.target.classList.toggle('opened'); + let transitionsNode = node.parentElement.querySelector('.transitions'); + let subtransitionNodes = transitionsNode.children; + if (subtransitionNodes.length <= 1) { + // Add subtransitions except the one that's already shown. + let visibleTransitionMap = subtransitionNodes.length == 1 ? + transitionsNode.querySelector('.map').map : + undefined; + map.children.forEach((edge) => { + if (edge.to != visibleTransitionMap) { + this.currentNode = transitionsNode; + this._addSubtransitions(edge.to); + } + }); + } else { + // remove all but the first (currently selected) subtransition + for (let i = subtransitionNodes.length - 1; i > 0; i--) { + transitionsNode.removeChild(subtransitionNodes[i]); + } + } + return false; + } + }); diff --git a/deps/v8/tools/system-analyzer/view/script-panel-template.html b/deps/v8/tools/system-analyzer/view/script-panel-template.html new file mode 100644 index 0000000000..27fd3d83eb --- /dev/null +++ b/deps/v8/tools/system-analyzer/view/script-panel-template.html @@ -0,0 +1,72 @@ +<!-- 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. --> + +<head> + <link href="./index.css" rel="stylesheet"> +</head> +<style> + .scriptNode { + font-family: Consolas, monospace; + } + + .scriptNode:before { + counter-reset: sourceLineCounter; + } + + .scriptNode span { + counter-increment: sourceLineCounter 1; + text-indent: -3.5em; + padding-left: 3.5em; + display: block; + } + + .scriptNode span::before { + content: counter(sourceLineCounter) ": "; + width: 3.5em; + display: inline-block; + white-space: pre; + text-align: right; + } + + mark { + width: 1ch; + border-radius: 2px; + border: 0.5px var(--background-color) solid; + cursor: pointer; + background-color: var(--primary-color); + color: var(--on-primary-color); + } + + .marked { + background-color: var(--secondary-color); + } + + @keyframes pulse { + 0% { + box-shadow: 0px 0px 0px 0px var(--secondary-color); + } + 5% { + box-shadow: 0px 0px 0px 10px var(--secondary-color); + } + 10% { + box-shadow: 0px 0px 0px 0px var(--secondary-color); + } + 15% { + box-shadow: 0px 0px 0px 10px var(--secondary-color); + } + 20% { + box-shadow: 0px 0px 0px 0px var(--secondary-color); + } + } +</style> +<div class="panel"> + <h2>Source Panel</h2> + <div class="selection"> + <select id="script-dropdown"></select> + <button id="selectedRelatedButton">Select Related Events</button> + </div> + <div id="script" class="panelBody"> + <div class="scriptNode"></div> + </div> +</div> diff --git a/deps/v8/tools/system-analyzer/source-panel.mjs b/deps/v8/tools/system-analyzer/view/script-panel.mjs index a4dc07fb45..b0dac6960c 100644 --- a/deps/v8/tools/system-analyzer/source-panel.mjs +++ b/deps/v8/tools/system-analyzer/view/script-panel.mjs @@ -1,22 +1,26 @@ // 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. -import {FocusEvent, SelectionEvent} from './events.mjs'; -import {delay, DOM, formatBytes, V8CustomElement} from './helper.mjs'; -import {IcLogEntry} from './log/ic.mjs'; -import {MapLogEntry} from './log/map.mjs'; +import {groupBy} from '../helper.mjs'; +import {App} from '../index.mjs' -DOM.defineCustomElement('source-panel', +import {SelectRelatedEvent, ToolTipEvent} from './events.mjs'; +import {CSSColor, delay, DOM, formatBytes, gradientStopsFromGroups, V8CustomElement} from './helper.mjs'; + +DOM.defineCustomElement('view/script-panel', (templateText) => class SourcePanel extends V8CustomElement { _selectedSourcePositions = []; - _sourcePositionsToMarkNodes; + _sourcePositionsToMarkNodes = []; _scripts = []; _script; + constructor() { super(templateText); this.scriptDropdown.addEventListener( 'change', e => this._handleSelectScript(e)); + this.$('#selectedRelatedButton').onclick = + this._handleSelectRelated.bind(this); } get script() { @@ -41,7 +45,11 @@ DOM.defineCustomElement('source-panel', this._focusSelectedMarkers(); } - set data(scripts) { + set focusedSourcePositions(sourcePositions) { + this.selectedSourcePositions = sourcePositions; + } + + set scripts(scripts) { this._scripts = scripts; this._initializeScriptDropdown(); } @@ -71,13 +79,13 @@ DOM.defineCustomElement('source-panel', let scriptNode; if (this._script) { await delay(1); - const builder = - new LineBuilder(this, this._script, this._selectedSourcePositions); + const builder = new LineBuilder(this, this._script); scriptNode = builder.createScriptNode(); this._sourcePositionsToMarkNodes = builder.sourcePositionToMarkers; } else { - scriptNode = document.createElement('pre'); + scriptNode = DOM.div(); this._selectedMarkNodes = undefined; + this._sourcePositionsToMarkNodes = new Map(); } const oldScriptNode = this.script.childNodes[1]; this.script.replaceChild(scriptNode, oldScriptNode); @@ -90,9 +98,15 @@ DOM.defineCustomElement('source-panel', markNode.className = ''; } for (let sourcePosition of this._selectedSourcePositions) { + if (sourcePosition.script !== this._script) continue; this._sourcePositionsToMarkNodes.get(sourcePosition).className = 'marked'; } - const sourcePosition = this._selectedSourcePositions[0]; + this._scrollToFirstSourcePosition() + } + + _scrollToFirstSourcePosition() { + const sourcePosition = this._selectedSourcePositions.find( + each => each.script === this._script); if (!sourcePosition) return; const markNode = this._sourcePositionsToMarkNodes.get(sourcePosition); markNode.scrollIntoView( @@ -103,29 +117,32 @@ DOM.defineCustomElement('source-panel', const option = this.scriptDropdown.options[this.scriptDropdown.selectedIndex]; this.script = option.script; - this.selectLogEntries(this._script.entries()); + } + + _handleSelectRelated(e) { + if (!this._script) return; + this.dispatchEvent(new SelectRelatedEvent(this._script)); } handleSourcePositionClick(e) { - this.selectLogEntries(e.target.sourcePosition.entries) - } - - selectLogEntries(logEntries) { - let icLogEntries = []; - let mapLogEntries = []; - for (const entry of logEntries) { - if (entry instanceof MapLogEntry) { - mapLogEntries.push(entry); - } else if (entry instanceof IcLogEntry) { - icLogEntries.push(entry); - } - } - if (icLogEntries.length > 0) { - this.dispatchEvent(new SelectionEvent(icLogEntries)); - } - if (mapLogEntries.length > 0) { - this.dispatchEvent(new SelectionEvent(mapLogEntries)); - } + const sourcePosition = e.target.sourcePosition; + this.dispatchEvent(new SelectRelatedEvent(sourcePosition)); + } + + handleSourcePositionMouseOver(e) { + const entries = e.target.sourcePosition.entries; + let text = groupBy(entries, each => each.constructor, true) + .map(group => { + let text = `${group.key.name}: ${group.count}\n` + text += groupBy(group.entries, each => each.type, true) + .map(group => { + return ` - ${group.key}: ${group.count}`; + }) + .join('\n'); + return text; + }) + .join('\n'); + this.dispatchEvent(new ToolTipEvent(text, e.target)); } }); @@ -177,16 +194,28 @@ function* lineIterator(source) { } class LineBuilder { + static _colorMap = (function() { + const map = new Map(); + let i = 0; + for (let type of App.allEventTypes) { + map.set(type, CSSColor.at(i++)); + } + return map; + })(); + static get colorMap() { + return this._colorMap; + } + _script; _clickHandler; + _mouseoverHandler; _sourcePositions; - _selection; _sourcePositionToMarkers = new Map(); - constructor(panel, script, highlightPositions) { + constructor(panel, script) { this._script = script; - this._selection = new Set(highlightPositions); this._clickHandler = panel.handleSourcePositionClick.bind(panel); + this._mouseoverHandler = panel.handleSourcePositionMouseOver.bind(panel); // TODO: sort on script finalization. script.sourcePositions.sort((a, b) => { if (a.line === b.line) return a.column - b.column; @@ -200,8 +229,7 @@ class LineBuilder { } createScriptNode() { - const scriptNode = document.createElement('pre'); - scriptNode.classList.add('scriptNode'); + const scriptNode = DOM.div('scriptNode'); for (let [lineIndex, line] of lineIterator(this._script.source)) { scriptNode.appendChild(this._createLineNode(lineIndex, line)); } @@ -209,7 +237,7 @@ class LineBuilder { } _createLineNode(lineIndex, line) { - const lineNode = document.createElement('span'); + const lineNode = DOM.span(); let columnIndex = 0; for (const sourcePosition of this._sourcePositions.forLine(lineIndex)) { const nextColumnIndex = sourcePosition.column - 1; @@ -232,6 +260,14 @@ class LineBuilder { marker.textContent = text; marker.sourcePosition = sourcePosition; marker.onclick = this._clickHandler; + marker.onmouseover = this._mouseoverHandler; + + const entries = sourcePosition.entries; + const stops = gradientStopsFromGroups( + entries.length, '%', groupBy(entries, entry => entry.constructor), + type => LineBuilder.colorMap.get(type)); + marker.style.backgroundImage = `linear-gradient(0deg,${stops.join(',')})` + return marker; } -}
\ No newline at end of file +} diff --git a/deps/v8/tools/system-analyzer/view/timeline-panel-template.html b/deps/v8/tools/system-analyzer/view/timeline-panel-template.html new file mode 100644 index 0000000000..da0ce26380 --- /dev/null +++ b/deps/v8/tools/system-analyzer/view/timeline-panel-template.html @@ -0,0 +1,29 @@ +<!-- 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. --> + +<head> + <link href="./index.css" rel="stylesheet"> + <style> + .panel { + padding-bottom: 0px; + position: relative; + } + .titleBackground { + position: absolute; + left: 0px; + top: 34px; + border-radius: 0 0 0 7px; + height: calc(100% - 34px); + width: 30px; + background-color: var(--border-color); + } + </style> +</head> +<div class="panel"> + <h2>Timeline Panel</h2> + <div class="titleBackground"></div> + <div> + <slot></slot> + </div> +</div> diff --git a/deps/v8/tools/system-analyzer/timeline-panel.mjs b/deps/v8/tools/system-analyzer/view/timeline-panel.mjs index a61d2efc90..331db401e9 100644 --- a/deps/v8/tools/system-analyzer/timeline-panel.mjs +++ b/deps/v8/tools/system-analyzer/view/timeline-panel.mjs @@ -8,7 +8,7 @@ import {SynchronizeSelectionEvent} from './events.mjs'; import {DOM, V8CustomElement} from './helper.mjs'; DOM.defineCustomElement( - 'timeline-panel', + 'view/timeline-panel', (templateText) => class TimelinePanel extends V8CustomElement { constructor() { super(templateText); diff --git a/deps/v8/tools/system-analyzer/view/timeline/timeline-track-template.html b/deps/v8/tools/system-analyzer/view/timeline/timeline-track-template.html new file mode 100644 index 0000000000..b27ad66b59 --- /dev/null +++ b/deps/v8/tools/system-analyzer/view/timeline/timeline-track-template.html @@ -0,0 +1,205 @@ +<!-- 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. --> + +<head> + <link href="./index.css" rel="stylesheet"> +</head> +<style> + #timeline { + position: relative; + height: calc(200px + 12px); + overflow-y: hidden; + overflow-x: scroll; + user-select: none; + } + + #timelineLabel { + transform: rotate(90deg); + transform-origin: left bottom 0; + position: absolute; + left: 0; + width: 200px; + text-align: center; + font-size: 10px; + opacity: 0.5; + } + + #timelineChunks { + height: 200px; + position: absolute; + margin-right: 100px; + } + + #timelineCanvas { + height: 200px; + position: relative; + overflow: visible; + pointer-events: none; + } + + .chunk { + width: 6px; + position: absolute; + background-size: 100% 100%; + image-rendering: pixelated; + bottom: 0px; + background-color: var(--on-surface-color); + cursor: pointer; + } + .chunk:hover { + border-radius: 2px 2px 0 0; + margin: 0 0 -2px -2px; + border: 2px var(--primary-color) solid; + } + + .timestamp { + height: 200px; + width: 100px; + border-left: 1px var(--on-surface-color) dashed; + padding-left: 4px; + position: absolute; + pointer-events: none; + font-size: 10px; + } + + .title { + position: relative; + float: left; + width: 20px; + writing-mode: vertical-rl; + text-orientation: mixed; + margin: 20px 0 0 -10px; + padding: 5px 5px 0px 5px; + overflow: hidden; + border-radius: 7px; + font-weight: 400; + } + + .panelCloserInput:checked ~ h3 { + display: inherit; + flex: 1; + writing-mode: unset; + text-orientation: unset; + background-color: var(--border-color); + border-radius: 0px; + padding: 5px; + margin: 0 -10px 0 20px; + } + + .timelineLegend { + position: relative; + float: right; + height: calc(200px + 12px); + overflow-y: scroll; + margin-right: -10px; + padding-right: 2px; + } + + #legendTable { + width: 280px; + border-collapse: collapse; + } + + th, + td { + padding: 1px 3px 2px 3px; + } + + /* Center colors */ + #legendTable td:nth-of-type(4n+1) { + text-align: center; + padding-top: 3px; + } + /* Left align text*/ + #legendTable td:nth-of-type(4n+2) { + text-align: left; + width: 100%; + } + /* right align numbers */ + #legendTable td:nth-of-type(4n+3), + #legendTable td:nth-of-type(4n+4) { + text-align: right; + } + + .timeline { + background-color: var(--timeline-background-color); + } + + #selection { + display: none; + } + + #rightHandle, + #leftHandle { + background-color: rgba(200, 200, 200, 0.5); + height: 100%; + width: 5px; + position: absolute; + z-index: 3; + cursor: col-resize; + } + #leftHandle { + border-left: 1px solid var(--on-surface-color); + } + #rightHandle { + border-right: 1px solid var(--on-surface-color); + } + + #selectionBackground { + background-color: rgba(133, 68, 163, 0.5); + height: 100%; + position: absolute; + } + + + .content { + display: flex; + position: relative; + } + .panelCloserLabel { + position: absolute; + top: 5px; + left: 0px; + } + .title { + flex: initial; + } + #timeline { + flex: 1; + } + .legend { + flex: initial; + } + +</style> + +<div class="content"> + <input type="checkbox" id="closer" class="panelCloserInput"> + <label class="panelCloserLabel" for="closer">â–¼</label> + <h3 class="title" id="title"></h3> + + <div id="timeline"> + <div id="selection"> + <div id="leftHandle"></div> + <div id="selectionBackground"></div> + <div id="rightHandle"></div> + </div> + <div id="timelineLabel">Frequency</div> + <div id="timelineChunks"></div> + <canvas id="timelineCanvas"></canvas> + </div> + + <div class="timelineLegend"> + <table id="legendTable"> + <thead> + <tr> + <td>Type</td> + <td>Count</td> + <td>Percent</td> + </tr> + </thead> + <tbody></tbody> + </table> + </div> +</div>
\ No newline at end of file diff --git a/deps/v8/tools/system-analyzer/view/timeline/timeline-track.mjs b/deps/v8/tools/system-analyzer/view/timeline/timeline-track.mjs new file mode 100644 index 0000000000..60216af2ee --- /dev/null +++ b/deps/v8/tools/system-analyzer/view/timeline/timeline-track.mjs @@ -0,0 +1,605 @@ +// 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. + +import {kChunkHeight, kChunkWidth} from '../../log/map.mjs'; +import {MapLogEntry} from '../../log/map.mjs'; +import {FocusEvent, SelectionEvent, SelectTimeEvent, SynchronizeSelectionEvent, ToolTipEvent,} from '../events.mjs'; +import {CSSColor, DOM, gradientStopsFromGroups, V8CustomElement} from '../helper.mjs'; + +DOM.defineCustomElement('view/timeline/timeline-track', + (templateText) => + class TimelineTrack extends V8CustomElement { + _timeline; + _nofChunks = 400; + _chunks; + _selectedEntry; + _timeToPixel; + _timeStartOffset; + _legend; + + _chunkMouseMoveHandler = this._handleChunkMouseMove.bind(this); + _chunkClickHandler = this._handleChunkClick.bind(this); + _chunkDoubleClickHandler = this._handleChunkDoubleClick.bind(this); + + constructor() { + super(templateText); + this._selectionHandler = new SelectionHandler(this); + this._legend = new Legend(this.$('#legendTable')); + this._legend.onFilter = (type) => this._handleFilterTimeline(); + this.timelineNode.addEventListener( + 'scroll', e => this._handleTimelineScroll(e)); + this.timelineNode.ondblclick = (e) => + this._selectionHandler.clearSelection(); + this.isLocked = false; + } + + static get observedAttributes() { + return ['title']; + } + + attributeChangedCallback(name, oldValue, newValue) { + if (name == 'title') { + this.$('#title').innerHTML = newValue; + } + } + + _handleFilterTimeline(type) { + this._updateChunks(); + } + + set data(timeline) { + this._timeline = timeline; + this._legend.timeline = timeline; + this.$('.content').style.display = timeline.isEmpty() ? 'none' : 'relative'; + this._updateChunks(); + } + + set timeSelection(selection) { + this._selectionHandler.timeSelection = selection; + this.updateSelection(); + } + + updateSelection() { + this._selectionHandler.update(); + this._legend.update(); + } + + // Maps the clicked x position to the x position on timeline canvas + positionOnTimeline(posX) { + let rect = this.timelineNode.getBoundingClientRect(); + let posClickedX = posX - rect.left + this.timelineNode.scrollLeft; + return posClickedX; + } + + positionToTime(posX) { + let posTimelineX = this.positionOnTimeline(posX) + this._timeStartOffset; + return posTimelineX / this._timeToPixel; + } + + timeToPosition(time) { + let posX = time * this._timeToPixel; + posX -= this._timeStartOffset; + return posX; + } + + get timelineCanvas() { + return this.$('#timelineCanvas'); + } + + get timelineChunks() { + return this.$('#timelineChunks'); + } + + get timelineNode() { + return this.$('#timeline'); + } + + _update() { + this._updateTimeline(); + this._legend.update(); + } + + set nofChunks(count) { + this._nofChunks = count; + this._updateChunks(); + } + + get nofChunks() { + return this._nofChunks; + } + + _updateChunks() { + this._chunks = + this._timeline.chunks(this.nofChunks, this._legend.filterPredicate); + this.update(); + } + + get chunks() { + return this._chunks; + } + + set selectedEntry(value) { + this._selectedEntry = value; + if (value.edge) this.redraw(); + } + + get selectedEntry() { + return this._selectedEntry; + } + + set scrollLeft(offset) { + this.timelineNode.scrollLeft = offset; + } + + handleEntryTypeDoubleClick(e) { + this.dispatchEvent(new SelectionEvent(e.target.parentNode.entries)); + } + + timelineIndicatorMove(offset) { + this.timelineNode.scrollLeft += offset; + } + + _handleTimelineScroll(e) { + let horizontal = e.currentTarget.scrollLeft; + this.dispatchEvent(new CustomEvent( + 'scrolltrack', {bubbles: true, composed: true, detail: horizontal})); + } + + _createBackgroundImage(chunk) { + const stops = gradientStopsFromGroups( + chunk.length, chunk.height, chunk.getBreakdown(event => event.type), + type => this._legend.colorForType(type)); + return `linear-gradient(0deg,${stops.join(',')})`; + } + + _updateTimeline() { + const reusableNodes = Array.from(this.timelineChunks.childNodes).reverse(); + let fragment = new DocumentFragment(); + let chunks = this.chunks; + let max = chunks.max(each => each.size()); + let start = this._timeline.startTime; + let end = this._timeline.endTime; + let duration = end - start; + this._timeToPixel = chunks.length * kChunkWidth / duration; + this._timeStartOffset = start * this._timeToPixel; + for (let i = 0; i < chunks.length; i++) { + let chunk = chunks[i]; + let height = (chunk.size() / max * kChunkHeight); + chunk.height = height; + if (chunk.isEmpty()) continue; + let node = reusableNodes[reusableNodes.length - 1]; + let reusedNode = false; + if (node?.className == 'chunk') { + reusableNodes.pop(); + reusedNode = true; + } else { + node = DOM.div('chunk'); + node.onmousemove = this._chunkMouseMoveHandler; + node.onclick = this._chunkClickHandler; + node.ondblclick = this._chunkDoubleClickHandler; + } + const style = node.style; + style.left = `${((chunk.start - start) * this._timeToPixel) | 0}px`; + style.height = `${height | 0}px`; + style.backgroundImage = this._createBackgroundImage(chunk); + node.chunk = chunk; + if (!reusedNode) fragment.appendChild(node); + } + + // Put a time marker roughly every 20 chunks. + let expected = duration / chunks.length * 20; + let interval = (10 ** Math.floor(Math.log10(expected))); + let correction = Math.log10(expected / interval); + correction = (correction < 0.33) ? 1 : (correction < 0.75) ? 2.5 : 5; + interval *= correction; + + let time = start; + while (time < end) { + let timeNode = DOM.div('timestamp'); + timeNode.innerText = `${((time - start) / 1000) | 0} ms`; + timeNode.style.left = `${((time - start) * this._timeToPixel) | 0}px`; + fragment.appendChild(timeNode); + time += interval; + } + + // Remove superfluos nodes lazily, for Chrome this is a very expensive + // operation. + if (reusableNodes.length > 0) { + for (const node of reusableNodes) { + node.style.display = 'none'; + } + setTimeout(() => { + const range = document.createRange(); + const first = reusableNodes[reusableNodes.length - 1]; + const last = reusableNodes[0]; + range.setStartBefore(first); + range.setEndAfter(last); + range.deleteContents(); + }, 100); + } + this.timelineChunks.appendChild(fragment); + this.redraw(); + } + + _handleChunkMouseMove(event) { + if (this.isLocked) return false; + if (this._selectionHandler.isSelecting) return false; + let chunk = event.target.chunk; + if (!chunk) return; + if (chunk.isEmpty()) return; + // topmost map (at chunk.height) == map #0. + let relativeIndex = Math.round( + event.layerY / event.target.offsetHeight * (chunk.size() - 1)); + let logEntry = chunk.at(relativeIndex); + this.dispatchEvent(new FocusEvent(logEntry)); + this.dispatchEvent(new ToolTipEvent(logEntry.toStringLong(), event.target)); + } + + _handleChunkClick(event) { + this.isLocked = !this.isLocked; + } + + _handleChunkDoubleClick(event) { + let chunk = event.target.chunk; + if (!chunk) return; + event.stopPropagation(); + this.dispatchEvent(new SelectTimeEvent(chunk.start, chunk.end)); + } + + redraw() { + window.requestAnimationFrame(() => this._redraw()); + } + + _redraw() { + if (!(this._timeline.at(0) instanceof MapLogEntry)) return; + let canvas = this.timelineCanvas; + let width = (this.chunks.length + 1) * kChunkWidth; + if (width > 32767) width = 32767; + canvas.width = width; + canvas.height = kChunkHeight; + let ctx = canvas.getContext('2d'); + ctx.clearRect(0, 0, canvas.width, kChunkHeight); + if (!this.selectedEntry || !this.selectedEntry.edge) return; + this.drawEdges(ctx); + } + + setMapStyle(map, ctx) { + ctx.fillStyle = map.edge && map.edge.from ? CSSColor.onBackgroundColor : + CSSColor.onPrimaryColor; + } + + setEdgeStyle(edge, ctx) { + let color = this._legend.colorForType(edge.type); + ctx.strokeStyle = color; + ctx.fillStyle = color; + } + + markMap(ctx, map) { + let [x, y] = map.position(this.chunks); + ctx.beginPath(); + this.setMapStyle(map, ctx); + ctx.arc(x, y, 3, 0, 2 * Math.PI); + ctx.fill(); + ctx.beginPath(); + ctx.fillStyle = CSSColor.onBackgroundColor; + ctx.arc(x, y, 2, 0, 2 * Math.PI); + ctx.fill(); + } + + markSelectedMap(ctx, map) { + let [x, y] = map.position(this.chunks); + ctx.beginPath(); + this.setMapStyle(map, ctx); + ctx.arc(x, y, 6, 0, 2 * Math.PI); + ctx.strokeStyle = CSSColor.onBackgroundColor; + ctx.stroke(); + } + + drawEdges(ctx) { + // Draw the trace of maps in reverse order to make sure the outgoing + // transitions of previous maps aren't drawn over. + const kMaxOutgoingEdges = 100; + let nofEdges = 0; + let stack = []; + let current = this.selectedEntry; + while (current && nofEdges < kMaxOutgoingEdges) { + nofEdges += current.children.length; + stack.push(current); + current = current.parent(); + } + ctx.save(); + this.drawOutgoingEdges(ctx, this.selectedEntry, 3); + ctx.restore(); + + let labelOffset = 15; + let xPrev = 0; + while (current = stack.pop()) { + if (current.edge) { + this.setEdgeStyle(current.edge, ctx); + let [xTo, yTo] = this.drawEdge(ctx, current.edge, true, labelOffset); + if (xTo == xPrev) { + labelOffset += 8; + } else { + labelOffset = 15 + } + xPrev = xTo; + } + this.markMap(ctx, current); + current = current.parent(); + ctx.save(); + // this.drawOutgoingEdges(ctx, current, 1); + ctx.restore(); + } + // Mark selected map + this.markSelectedMap(ctx, this.selectedEntry); + } + + drawEdge(ctx, edge, showLabel = true, labelOffset = 20) { + if (!edge.from || !edge.to) return [-1, -1]; + let [xFrom, yFrom] = edge.from.position(this.chunks); + let [xTo, yTo] = edge.to.position(this.chunks); + let sameChunk = xTo == xFrom; + if (sameChunk) labelOffset += 8; + + ctx.beginPath(); + ctx.moveTo(xFrom, yFrom); + let offsetX = 20; + let offsetY = 20; + let midX = xFrom + (xTo - xFrom) / 2; + let midY = (yFrom + yTo) / 2 - 100; + if (!sameChunk) { + ctx.quadraticCurveTo(midX, midY, xTo, yTo); + } else { + ctx.lineTo(xTo, yTo); + } + if (!showLabel) { + ctx.stroke(); + } else { + let centerX, centerY; + if (!sameChunk) { + centerX = (xFrom / 2 + midX + xTo / 2) / 2; + centerY = (yFrom / 2 + midY + yTo / 2) / 2; + } else { + centerX = xTo; + centerY = yTo; + } + ctx.moveTo(centerX, centerY); + ctx.lineTo(centerX + offsetX, centerY - labelOffset); + ctx.stroke(); + ctx.textAlign = 'left'; + ctx.fillStyle = this._legend.colorForType(edge.type); + ctx.fillText( + edge.toString(), centerX + offsetX + 2, centerY - labelOffset); + } + return [xTo, yTo]; + } + + drawOutgoingEdges(ctx, map, max = 10, depth = 0) { + if (!map) return; + if (depth >= max) return; + ctx.globalAlpha = 0.5 - depth * (0.3 / max); + ctx.strokeStyle = CSSColor.timelineBackgroundColor; + const limit = Math.min(map.children.length, 100) + for (let i = 0; i < limit; i++) { + let edge = map.children[i]; + this.drawEdge(ctx, edge, true); + this.drawOutgoingEdges(ctx, edge.to, max, depth + 1); + } + } +}); + +class SelectionHandler { + // TODO turn into static field once Safari supports it. + static get SELECTION_OFFSET() { + return 10 + }; + + _timeSelection = {start: -1, end: Infinity}; + _selectionOriginTime = -1; + + constructor(timeline) { + this._timeline = timeline; + this._timelineNode.addEventListener( + 'mousedown', e => this._handleTimeSelectionMouseDown(e)); + this._timelineNode.addEventListener( + 'mouseup', e => this._handleTimeSelectionMouseUp(e)); + this._timelineNode.addEventListener( + 'mousemove', e => this._handleTimeSelectionMouseMove(e)); + } + + update() { + if (!this.hasSelection) { + this._selectionNode.style.display = 'none'; + return; + } + this._selectionNode.style.display = 'inherit'; + const startPosition = this.timeToPosition(this._timeSelection.start); + const endPosition = this.timeToPosition(this._timeSelection.end); + this._leftHandleNode.style.left = startPosition + 'px'; + this._rightHandleNode.style.left = endPosition + 'px'; + const delta = endPosition - startPosition; + const selectionNode = this._selectionBackgroundNode; + selectionNode.style.left = startPosition + 'px'; + selectionNode.style.width = delta + 'px'; + } + + set timeSelection(selection) { + this._timeSelection.start = selection.start; + this._timeSelection.end = selection.end; + } + + clearSelection() { + this._timeline.dispatchEvent(new SelectTimeEvent()); + } + + timeToPosition(posX) { + return this._timeline.timeToPosition(posX); + } + + positionToTime(posX) { + return this._timeline.positionToTime(posX); + } + + get isSelecting() { + return this._selectionOriginTime >= 0; + } + + get hasSelection() { + return this._timeSelection.start >= 0 && + this._timeSelection.end != Infinity; + } + + get _timelineNode() { + return this._timeline.$('#timeline'); + } + + get _selectionNode() { + return this._timeline.$('#selection'); + } + + get _selectionBackgroundNode() { + return this._timeline.$('#selectionBackground'); + } + + get _leftHandleNode() { + return this._timeline.$('#leftHandle'); + } + + get _rightHandleNode() { + return this._timeline.$('#rightHandle'); + } + + get _leftHandlePosX() { + return this._leftHandleNode.getBoundingClientRect().x; + } + + get _rightHandlePosX() { + return this._rightHandleNode.getBoundingClientRect().x; + } + + _isOnLeftHandle(posX) { + return Math.abs(this._leftHandlePosX - posX) <= + SelectionHandler.SELECTION_OFFSET; + } + + _isOnRightHandle(posX) { + return Math.abs(this._rightHandlePosX - posX) <= + SelectionHandler.SELECTION_OFFSET; + } + + _handleTimeSelectionMouseDown(e) { + let xPosition = e.clientX + // Update origin time in case we click on a handle. + if (this._isOnLeftHandle(xPosition)) { + xPosition = this._rightHandlePosX; + } + else if (this._isOnRightHandle(xPosition)) { + xPosition = this._leftHandlePosX; + } + this._selectionOriginTime = this.positionToTime(xPosition); + } + + _handleTimeSelectionMouseMove(e) { + if (!this.isSelecting) return; + const currentTime = this.positionToTime(e.clientX); + this._timeline.dispatchEvent(new SynchronizeSelectionEvent( + Math.min(this._selectionOriginTime, currentTime), + Math.max(this._selectionOriginTime, currentTime))); + } + + _handleTimeSelectionMouseUp(e) { + this._selectionOriginTime = -1; + const delta = this._timeSelection.end - this._timeSelection.start; + if (delta <= 1 || isNaN(delta)) return; + this._timeline.dispatchEvent(new SelectTimeEvent( + this._timeSelection.start, this._timeSelection.end)); + } +} + +class Legend { + _timeline; + _typesFilters = new Map(); + _typeClickHandler = this._handleTypeClick.bind(this); + _filterPredicate = this.filter.bind(this); + onFilter = () => {}; + + constructor(table) { + this._table = table; + } + + set timeline(timeline) { + this._timeline = timeline; + const groups = timeline.getBreakdown(); + this._typesFilters = new Map(groups.map(each => [each.key, true])); + this._colors = + new Map(groups.map(each => [each.key, CSSColor.at(each.id)])); + } + + get selection() { + return this._timeline.selectionOrSelf; + } + + get filterPredicate() { + for (let visible of this._typesFilters.values()) { + if (!visible) return this._filterPredicate; + } + return undefined; + } + + colorForType(type) { + return this._colors.get(type); + } + + filter(logEntry) { + return this._typesFilters.get(logEntry.type); + } + + update() { + const tbody = DOM.tbody(); + const missingTypes = new Set(this._typesFilters.keys()); + this.selection.getBreakdown().forEach(group => { + tbody.appendChild(this._addTypeRow(group)); + missingTypes.delete(group.key); + }); + missingTypes.forEach(key => tbody.appendChild(this._row('', key, 0, '0%'))); + if (this._timeline.selection) { + tbody.appendChild( + this._row('', 'Selection', this.selection.length, '100%')); + } + tbody.appendChild(this._row('', 'All', this._timeline.length, '')); + this._table.tBodies[0].replaceWith(tbody); + } + + _row(color, type, count, percent) { + const row = DOM.tr(); + row.appendChild(DOM.td(color)); + row.appendChild(DOM.td(type)); + row.appendChild(DOM.td(count.toString())); + row.appendChild(DOM.td(percent)); + return row + } + + _addTypeRow(group) { + const color = this.colorForType(group.key); + const colorDiv = DOM.div('colorbox'); + if (this._typesFilters.get(group.key)) { + colorDiv.style.backgroundColor = color; + } else { + colorDiv.style.borderColor = color; + colorDiv.style.backgroundColor = CSSColor.backgroundImage; + } + let percent = `${(group.count / this.selection.length * 100).toFixed(1)}%`; + const row = this._row(colorDiv, group.key, group.count, percent); + row.className = 'clickable'; + row.onclick = this._typeClickHandler; + row.data = group.key; + return row; + } + + _handleTypeClick(e) { + const type = e.currentTarget.data; + this._typesFilters.set(type, !this._typesFilters.get(type)); + this.onFilter(type); + } +}
\ No newline at end of file diff --git a/deps/v8/tools/system-analyzer/view/tool-tip-template.html b/deps/v8/tools/system-analyzer/view/tool-tip-template.html new file mode 100644 index 0000000000..b0e9c72c45 --- /dev/null +++ b/deps/v8/tools/system-analyzer/view/tool-tip-template.html @@ -0,0 +1,81 @@ +<!-- 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. --> +<head> + <link href="./index.css" rel="stylesheet"> +</head> +<style> + :host { + position: absolute; + z-index: 100; + } + + #content { + background-color: var(--surface-color); + border: 3px var(--primary-color) solid; + border-radius: 10px; + min-width: 100px; + min-height: 100px; + padding: 10px; + box-shadow: 0px 0px 10px rgba(0,0,0,0.5); + width: auto; + } + + .textContent { + font-family: monospace; + white-space: pre; + overflow-x: hidden; + max-width: 500px; + } + + #body { + display: none; + position: absolute; + --tip-offset: 10px; + --tip-width: 10px; + --tip-height: 15px; + } + + #body.top { + bottom: var(--tip-height); + } + #body.bottom { + top: var(--tip-height); + } + #body.left { + right: calc(var(--tip-offset) * -1 - var(--tip-width)); + } + #body.right { + left: calc(var(--tip-offset) * -1 - var(--tip-width)); + } + + .tip { + width: 0; + height: 0; + border-style: solid; + position: absolute; + border-width: var(--tip-height) var(--tip-width) 0 var(--tip-width); + border-color: var(--primary-color) transparent transparent transparent; + pointer-events: none; + } + + .top > .tip { + bottom: calc(var(--tip-height) * -1); + } + .bottom > .tip { + top: calc(var(--tip-height) * -1); + transform: scaleY(-1); + } + .left > .tip { + right: var(--tip-offset); + } + .right > .tip { + left: var(--tip-offset); + } +</style> + +<div id="body"> + <div id="content"> + </div> + <div class="tip"></div> +</div> diff --git a/deps/v8/tools/system-analyzer/view/tool-tip.mjs b/deps/v8/tools/system-analyzer/view/tool-tip.mjs new file mode 100644 index 0000000000..896c04dd29 --- /dev/null +++ b/deps/v8/tools/system-analyzer/view/tool-tip.mjs @@ -0,0 +1,110 @@ +// 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. + +import {DOM, V8CustomElement} from './helper.mjs'; + +DOM.defineCustomElement( + 'view/tool-tip', (templateText) => class Tooltip extends V8CustomElement { + _targetNode; + _content; + _isHidden = true; + constructor() { + super(templateText); + this._intersectionObserver = new IntersectionObserver((entries) => { + if (entries[0].intersectionRatio <= 0) { + this.hide(); + } else { + this.show(); + this.update(true); + } + }); + } + + _update() { + if (!this._targetNode || this._isHidden) return; + const rect = this._targetNode.getBoundingClientRect(); + rect.x += rect.width / 2; + let atRight = this._useRight(rect.x); + let atBottom = this._useBottom(rect.y); + if (atBottom) { + rect.y += rect.height; + } + this._setPosition(rect, atRight, atBottom); + this.update(true); + } + + set positionOrTargetNode(positionOrTargetNode) { + if (positionOrTargetNode.nodeType === undefined) { + this.position = positionOrTargetNode; + } else { + this.targetNode = positionOrTargetNode; + } + } + + set targetNode(targetNode) { + this._intersectionObserver.disconnect(); + this._targetNode = targetNode; + if (targetNode) { + this._intersectionObserver.observe(targetNode); + this.update(true); + } + } + + set position(position) { + this._targetNode = undefined; + this._setPosition( + position, this._useRight(position.x), this._useBottom(position.y)); + } + + _setPosition(viewportPosition, atRight, atBottom) { + const horizontalMode = atRight ? 'right' : 'left'; + const verticalMode = atBottom ? 'bottom' : 'top'; + this.bodyNode.className = horizontalMode + ' ' + verticalMode; + const pageX = viewportPosition.x + window.scrollX; + this.style.left = `${pageX}px`; + const pageY = viewportPosition.y + window.scrollY; + this.style.top = `${pageY}px`; + } + + _useBottom(viewportY) { + return viewportY <= 400; + } + + _useRight(viewportX) { + return viewportX < document.documentElement.clientWidth / 2; + } + + set content(content) { + if (!content) return this.hide(); + this.show(); + if (typeof content === 'string') { + this.contentNode.innerHTML = content; + this.contentNode.className = 'textContent'; + } else { + const newContent = DOM.div(); + newContent.appendChild(content); + this.contentNode.replaceWith(newContent); + newContent.id = 'content'; + } + } + + hide() { + this._isHidden = true; + this.bodyNode.style.display = 'none'; + this.targetNode = undefined; + } + + show() { + this.bodyNode.style.display = 'block'; + this._isHidden = false; + } + + get bodyNode() { + return this.$('#body'); + } + + get contentNode() { + return this.$('#content'); + } + }); diff --git a/deps/v8/tools/testrunner/base_runner.py b/deps/v8/tools/testrunner/base_runner.py index d3674a4f8b..54a9e61b16 100644 --- a/deps/v8/tools/testrunner/base_runner.py +++ b/deps/v8/tools/testrunner/base_runner.py @@ -351,9 +351,6 @@ class BaseTestRunner(object): help="Path to a file for storing json results.") parser.add_option('--slow-tests-cutoff', type="int", default=100, help='Collect N slowest tests') - parser.add_option("--junitout", help="File name of the JUnit output") - parser.add_option("--junittestsuite", default="v8tests", - help="The testsuite name in the JUnit output file") parser.add_option("--exit-after-n-failures", type="int", default=100, help="Exit after the first N failures instead of " "running all tests. Pass 0 to disable this feature.") @@ -760,9 +757,6 @@ class BaseTestRunner(object): def _create_progress_indicators(self, test_count, options): procs = [PROGRESS_INDICATORS[options.progress]()] - if options.junitout: - procs.append(progress.JUnitTestProgressIndicator(options.junitout, - options.junittestsuite)) if options.json_test_results: procs.append(progress.JsonTestProgressIndicator(self.framework_name)) diff --git a/deps/v8/tools/testrunner/local/junit_output.py b/deps/v8/tools/testrunner/local/junit_output.py deleted file mode 100644 index 52f31ec422..0000000000 --- a/deps/v8/tools/testrunner/local/junit_output.py +++ /dev/null @@ -1,49 +0,0 @@ -# Copyright 2013 the V8 project authors. All rights reserved. -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following -# disclaimer in the documentation and/or other materials provided -# with the distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived -# from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -import xml.etree.ElementTree as xml - - -class JUnitTestOutput: - def __init__(self, test_suite_name): - self.root = xml.Element("testsuite") - self.root.attrib["name"] = test_suite_name - - def HasRunTest(self, test_name, test_cmd, test_duration, test_failure): - testCaseElement = xml.Element("testcase") - testCaseElement.attrib["name"] = test_name - testCaseElement.attrib["cmd"] = test_cmd - testCaseElement.attrib["time"] = str(round(test_duration, 3)) - if len(test_failure): - failureElement = xml.Element("failure") - failureElement.text = test_failure - testCaseElement.append(failureElement) - self.root.append(testCaseElement) - - def FinishAndWrite(self, f): - xml.ElementTree(self.root).write(f, "UTF-8") diff --git a/deps/v8/tools/testrunner/local/utils.py b/deps/v8/tools/testrunner/local/utils.py index a6b92dc756..8fdc16b4bb 100644 --- a/deps/v8/tools/testrunner/local/utils.py +++ b/deps/v8/tools/testrunner/local/utils.py @@ -90,7 +90,8 @@ def GuessOS(): return 'solaris' elif system == 'NetBSD': return 'netbsd' - elif system == 'AIX': + elif system in ['AIX', 'OS400']: + # OS400 runs an AIX emulator called PASE return 'aix' else: return None diff --git a/deps/v8/tools/testrunner/local/variants.py b/deps/v8/tools/testrunner/local/variants.py index 4236c1678a..69ca853de3 100644 --- a/deps/v8/tools/testrunner/local/variants.py +++ b/deps/v8/tools/testrunner/local/variants.py @@ -53,12 +53,14 @@ ALL_VARIANT_FLAGS = { # implications defined in flag-definitions.h. INCOMPATIBLE_FLAGS_PER_VARIANT = { "assert_types": ["--no-assert-types"], - "jitless": ["--opt", "--liftoff", "--track-field-types", "--validate-asm"], + "jitless": ["--opt", "--always-opt", "--liftoff", "--track-field-types", "--validate-asm"], "no_wasm_traps": ["--wasm-trap-handler"], - "nooptimization": ["--opt", "--no-liftoff", "--predictable", "--wasm-tier-up"], + "nooptimization": ["--opt", "--always-opt", "--no-liftoff", "--wasm-tier-up"], "slow_path": ["--no-force-slow-path"], + "stress_concurrent_allocation": ["--single-threaded-gc", "--predictable"], "stress_incremental_marking": ["--no-stress-incremental-marking"], - "stress_js_bg_compile_wasm_code_gc": ["--no-stress-background-compile"], + "future": ["--parallel-compile-tasks"], + "stress_js_bg_compile_wasm_code_gc": ["--no-stress-background-compile", "--parallel-compile-tasks"], "stress": ["--no-stress-opt", "--always-opt", "--no-always-opt", "--liftoff", "--max-inlined-bytecode-size=*", "--max-inlined-bytecode-size-cumulative=*", "--stress-inline"], "turboprop": ["--interrupt-budget=*", "--no-turboprop"], @@ -77,7 +79,8 @@ INCOMPATIBLE_FLAGS_PER_BUILD_VARIABLE = { + INCOMPATIBLE_FLAGS_PER_VARIANT["jitless"], "predictable": ["--liftoff", "--parallel-compile-tasks", "--concurrent-recompilation", - "--wasm-num-compilation-tasks=*"], + "--wasm-num-compilation-tasks=*", + "--stress-concurrent-allocation"], } # Flags that lead to a contradiction when a certain extra-flag is present. @@ -92,7 +95,9 @@ INCOMPATIBLE_FLAGS_PER_EXTRA_FLAG = { "--no-enable-sse3": ["--enable-sse3"], "--no-enable-sse4-1": ["--enable-sse4-1"], "--optimize-for-size": ["--max-semi-space-size=*"], + "--stress_concurrent_allocation": ["--single-threaded-gc", "--predictable"], "--stress-flush-bytecode": ["--no-stress-flush-bytecode"], + "--future": ["--parallel-compile-tasks"], "--stress-incremental-marking": INCOMPATIBLE_FLAGS_PER_VARIANT["stress_incremental_marking"], } diff --git a/deps/v8/tools/testrunner/testproc/progress.py b/deps/v8/tools/testrunner/testproc/progress.py index 634ef7c2f2..9ff943a5c2 100644 --- a/deps/v8/tools/testrunner/testproc/progress.py +++ b/deps/v8/tools/testrunner/testproc/progress.py @@ -15,7 +15,6 @@ import time from . import base from . import util -from ..local import junit_output def print_failure_header(test): @@ -349,45 +348,6 @@ class MonochromeProgressIndicator(CompactProgressIndicator): print(("\r" + (" " * last_length) + "\r"), end='') -class JUnitTestProgressIndicator(ProgressIndicator): - def __init__(self, junitout, junittestsuite): - super(JUnitTestProgressIndicator, self).__init__() - self._requirement = base.DROP_PASS_STDOUT - - self.outputter = junit_output.JUnitTestOutput(junittestsuite) - if junitout: - self.outfile = open(junitout, "w") - else: - self.outfile = sys.stdout - - def _on_result_for(self, test, result): - # TODO(majeski): Support for dummy/grouped results - fail_text = "" - output = result.output - if result.has_unexpected_output: - stdout = output.stdout.strip() - if len(stdout): - fail_text += "stdout:\n%s\n" % stdout - stderr = output.stderr.strip() - if len(stderr): - fail_text += "stderr:\n%s\n" % stderr - fail_text += "Command: %s" % result.cmd.to_string() - if output.HasCrashed(): - fail_text += "exit code: %d\n--- CRASHED ---" % output.exit_code - if output.HasTimedOut(): - fail_text += "--- TIMEOUT ---" - self.outputter.HasRunTest( - test_name=str(test), - test_cmd=result.cmd.to_string(relative=True), - test_duration=output.duration, - test_failure=fail_text) - - def finished(self): - self.outputter.FinishAndWrite(self.outfile) - if self.outfile != sys.stdout: - self.outfile.close() - - class JsonTestProgressIndicator(ProgressIndicator): def __init__(self, framework_name): super(JsonTestProgressIndicator, self).__init__() diff --git a/deps/v8/tools/tickprocessor.mjs b/deps/v8/tools/tickprocessor.mjs index 5b746d943a..54c37e68e0 100644 --- a/deps/v8/tools/tickprocessor.mjs +++ b/deps/v8/tools/tickprocessor.mjs @@ -31,11 +31,6 @@ import { Profile, JsonProfile } from "./profile.mjs"; import { ViewBuilder } from "./profile_view.mjs"; -export function inherits(childCtor, parentCtor) { - childCtor.prototype.__proto__ = parentCtor.prototype; -}; - - class V8Profile extends Profile { static IC_RE = /^(LoadGlobalIC: )|(Handler: )|(?:CallIC|LoadIC|StoreIC)|(?:Builtin: (?:Keyed)?(?:Load|Store)IC_)/; @@ -75,20 +70,8 @@ export function readFile(fileName) { } -/** - * Parser for dynamic code optimization state. - */ -function parseState(s) { - switch (s) { - case "": return Profile.CodeState.COMPILED; - case "~": return Profile.CodeState.OPTIMIZABLE; - case "*": return Profile.CodeState.OPTIMIZED; - } - throw new Error(`unknown code state: ${s}`); -} - - -export function TickProcessor( +export class TickProcessor extends LogReader { + constructor( cppEntriesProvider, separateIc, separateBytecodes, @@ -105,8 +88,10 @@ export function TickProcessor( onlySummary, runtimeTimerFilter, preprocessJson) { - this.preprocessJson = preprocessJson; - LogReader.call(this, { + super({}, + timedRange, + pairwiseTimedRange); + this.dispatchTable_ = { 'shared-library': { parsers: [parseString, parseInt, parseInt, parseInt], processor: this.processSharedLibrary }, 'code-creation': { @@ -155,10 +140,10 @@ export function TickProcessor( // Obsolete row types. 'code-allocate': null, 'begin-code-region': null, - 'end-code-region': null }, - timedRange, - pairwiseTimedRange); + 'end-code-region': null + }; + this.preprocessJson = preprocessJson; this.cppEntriesProvider_ = cppEntriesProvider; this.callGraphSize_ = callGraphSize; this.ignoreUnknown_ = ignoreUnknown; @@ -214,11 +199,10 @@ export function TickProcessor( this.generation_ = 1; this.currentProducerProfile_ = null; this.onlySummary_ = onlySummary; -}; -inherits(TickProcessor, LogReader); +} -TickProcessor.VmStates = { +static VmStates = { JS: 0, GC: 1, PARSER: 2, @@ -230,7 +214,7 @@ TickProcessor.VmStates = { }; -TickProcessor.CodeTypes = { +static CodeTypes = { CPP: 0, SHARED_LIB: 1 }; @@ -238,56 +222,56 @@ TickProcessor.CodeTypes = { // codeTypes_ map because there can be zillions of them. -TickProcessor.CALL_PROFILE_CUTOFF_PCT = 1.0; +static CALL_PROFILE_CUTOFF_PCT = 1.0; -TickProcessor.CALL_GRAPH_SIZE = 5; +static CALL_GRAPH_SIZE = 5; /** * @override */ -TickProcessor.prototype.printError = function(str) { +printError(str) { printErr(str); -}; +} -TickProcessor.prototype.setCodeType = function(name, type) { +setCodeType(name, type) { this.codeTypes_[name] = TickProcessor.CodeTypes[type]; -}; +} -TickProcessor.prototype.isSharedLibrary = function(name) { +isSharedLibrary(name) { return this.codeTypes_[name] == TickProcessor.CodeTypes.SHARED_LIB; -}; +} -TickProcessor.prototype.isCppCode = function(name) { +isCppCode(name) { return this.codeTypes_[name] == TickProcessor.CodeTypes.CPP; -}; +} -TickProcessor.prototype.isJsCode = function(name) { +isJsCode(name) { return name !== "UNKNOWN" && !(name in this.codeTypes_); -}; +} -TickProcessor.prototype.processLogFile = function(fileName) { +processLogFile(fileName) { this.lastLogFileName_ = fileName; let line; while (line = readline()) { this.processLogLine(line); } -}; +} -TickProcessor.prototype.processLogFileInTest = function(fileName) { +processLogFileInTest(fileName) { // Hack file name to avoid dealing with platform specifics. this.lastLogFileName_ = 'v8.log'; const contents = readFile(fileName); this.processLogChunk(contents); -}; +} -TickProcessor.prototype.processSharedLibrary = function( +processSharedLibrary( name, startAddr, endAddr, aslrSlide) { const entry = this.profile_.addLibrary(name, startAddr, endAddr, aslrSlide); this.setCodeType(entry.getName(), 'SHARED_LIB'); @@ -298,67 +282,67 @@ TickProcessor.prototype.processSharedLibrary = function( self.profile_.addStaticCode(fName, fStart, fEnd); self.setCodeType(fName, 'CPP'); }); -}; +} -TickProcessor.prototype.processCodeCreation = function( +processCodeCreation( type, kind, timestamp, start, size, name, maybe_func) { if (maybe_func.length) { const funcAddr = parseInt(maybe_func[0]); - const state = parseState(maybe_func[1]); + const state = Profile.parseState(maybe_func[1]); this.profile_.addFuncCode(type, name, timestamp, start, size, funcAddr, state); } else { this.profile_.addCode(type, name, timestamp, start, size); } -}; +} -TickProcessor.prototype.processCodeDeopt = function( +processCodeDeopt( timestamp, size, code, inliningId, scriptOffset, bailoutType, sourcePositionText, deoptReasonText) { this.profile_.deoptCode(timestamp, code, inliningId, scriptOffset, bailoutType, sourcePositionText, deoptReasonText); -}; +} -TickProcessor.prototype.processCodeMove = function(from, to) { +processCodeMove(from, to) { this.profile_.moveCode(from, to); -}; +} -TickProcessor.prototype.processCodeDelete = function(start) { +processCodeDelete(start) { this.profile_.deleteCode(start); -}; +} -TickProcessor.prototype.processCodeSourceInfo = function( +processCodeSourceInfo( start, script, startPos, endPos, sourcePositions, inliningPositions, inlinedFunctions) { this.profile_.addSourcePositions(start, script, startPos, endPos, sourcePositions, inliningPositions, inlinedFunctions); -}; +} -TickProcessor.prototype.processScriptSource = function(script, url, source) { +processScriptSource(script, url, source) { this.profile_.addScriptSource(script, url, source); -}; +} -TickProcessor.prototype.processFunctionMove = function(from, to) { +processFunctionMove(from, to) { this.profile_.moveFunc(from, to); -}; +} -TickProcessor.prototype.includeTick = function(vmState) { +includeTick(vmState) { if (this.stateFilter_ !== null) { return this.stateFilter_ == vmState; } else if (this.runtimeTimerFilter_ !== null) { return this.currentRuntimeTimer == this.runtimeTimerFilter_; } return true; -}; +} -TickProcessor.prototype.processRuntimeTimerEvent = function(name) { +processRuntimeTimerEvent(name) { this.currentRuntimeTimer = name; } -TickProcessor.prototype.processTick = function(pc, +processTick(pc, ns_since_start, is_external_callback, tos_or_external_callback, @@ -394,21 +378,21 @@ TickProcessor.prototype.processTick = function(pc, this.profile_.recordTick( ns_since_start, vmState, this.processStack(pc, tos_or_external_callback, stack)); -}; +} -TickProcessor.prototype.advanceDistortion = function() { +advanceDistortion() { this.distortion += this.distortion_per_entry; } -TickProcessor.prototype.processHeapSampleBegin = function(space, state, ticks) { +processHeapSampleBegin(space, state, ticks) { if (space != 'Heap') return; this.currentProducerProfile_ = new CallTree(); -}; +} -TickProcessor.prototype.processHeapSampleEnd = function(space, state) { +processHeapSampleEnd(space, state) { if (space != 'Heap' || !this.currentProducerProfile_) return; print(`Generation ${this.generation_}:`); @@ -423,10 +407,10 @@ TickProcessor.prototype.processHeapSampleEnd = function(space, state) { this.currentProducerProfile_ = null; this.generation_++; -}; +} -TickProcessor.prototype.printStatistics = function() { +printStatistics() { if (this.preprocessJson) { this.profile_.writeJson(); return; @@ -505,29 +489,16 @@ TickProcessor.prototype.printStatistics = function() { (rec2.internalFuncName < rec1.internalFuncName ? -1 : 1) ); this.printHeavyProfile(heavyView.head.children); } -}; - - -function padLeft(s, len) { - s = s.toString(); - if (s.length < len) { - const padLength = len - s.length; - if (!(padLength in padLeft)) { - padLeft[padLength] = new Array(padLength + 1).join(' '); - } - s = padLeft[padLength] + s; - } - return s; -}; +} -TickProcessor.prototype.printHeader = function(headerTitle) { +printHeader(headerTitle) { print(`\n [${headerTitle}]:`); print(' ticks total nonlib name'); -}; +} -TickProcessor.prototype.printLine = function( +printLine( entry, ticks, totalTicks, nonLibTicks) { const pct = ticks * 100 / totalTicks; const nonLibPct = nonLibTicks != null @@ -539,7 +510,7 @@ TickProcessor.prototype.printLine = function( entry); } -TickProcessor.prototype.printHeavyProfHeader = function() { +printHeavyProfHeader() { print('\n [Bottom up (heavy) profile]:'); print(' Note: percentage shows a share of a particular caller in the ' + 'total\n' + @@ -548,10 +519,10 @@ TickProcessor.prototype.printHeavyProfHeader = function() { TickProcessor.CALL_PROFILE_CUTOFF_PCT.toFixed(1) + '% are not shown.\n'); print(' ticks parent name'); -}; +} -TickProcessor.prototype.processProfile = function( +processProfile( profile, filterP, func) { for (let i = 0, n = profile.length; i < n; ++i) { const rec = profile[i]; @@ -562,7 +533,7 @@ TickProcessor.prototype.processProfile = function( } }; -TickProcessor.prototype.getLineAndColumn = function(name) { +getLineAndColumn(name) { const re = /:([0-9]+):([0-9]+)$/; const array = re.exec(name); if (!array) { @@ -571,12 +542,12 @@ TickProcessor.prototype.getLineAndColumn = function(name) { return {line: array[1], column: array[2]}; } -TickProcessor.prototype.hasSourceMap = function() { +hasSourceMap() { return this.sourceMap != null; -}; +} -TickProcessor.prototype.formatFunctionName = function(funcName) { +formatFunctionName(funcName) { if (!this.hasSourceMap()) { return funcName; } @@ -593,9 +564,9 @@ TickProcessor.prototype.formatFunctionName = function(funcName) { const sourceColumn = entry[4] + 1; return sourceFile + ':' + sourceLine + ':' + sourceColumn + ' -> ' + funcName; -}; +} -TickProcessor.prototype.printEntries = function( +printEntries( profile, totalTicks, nonLibTicks, filterP, callback, printAllTicks) { const that = this; this.processProfile(profile, filterP, function (rec) { @@ -606,10 +577,9 @@ TickProcessor.prototype.printEntries = function( that.printLine(funcName, rec.selfTime, totalTicks, nonLibTicks); } }); -}; - +} -TickProcessor.prototype.printHeavyProfile = function(profile, opt_indent) { + printHeavyProfile(profile, opt_indent) { const self = this; const indent = opt_indent || 0; const indentStr = padLeft('', indent); @@ -629,14 +599,27 @@ TickProcessor.prototype.printHeavyProfile = function(profile, opt_indent) { print(''); } }); -}; +} +} -function CppEntriesProvider() { + +function padLeft(s, len) { + s = s.toString(); + if (s.length < len) { + const padLength = len - s.length; + if (!(padLength in padLeft)) { + padLeft[padLength] = new Array(padLength + 1).join(' '); + } + s = padLeft[padLength] + s; + } + return s; }; -CppEntriesProvider.prototype.parseVmSymbols = function( +class CppEntriesProvider { + +parseVmSymbols( libName, libStart, libEnd, libASLRSlide, processorFunc) { this.loadSymbols(libName); @@ -699,17 +682,20 @@ CppEntriesProvider.prototype.parseVmSymbols = function( addEntry(funcInfo); } addEntry({name: '', start: libEnd}); -}; +} -CppEntriesProvider.prototype.loadSymbols = function(libName) { -}; +loadSymbols(libName) { +} +parseNextLine() { return false } -CppEntriesProvider.prototype.parseNextLine = () => false; +} -export function UnixCppEntriesProvider(nmExec, objdumpExec, targetRootFS, apkEmbeddedLibrary) { +export class UnixCppEntriesProvider extends CppEntriesProvider { + constructor(nmExec, objdumpExec, targetRootFS, apkEmbeddedLibrary) { + super(); this.symbols = []; // File offset of a symbol minus the virtual address of a symbol found in // the symbol table. @@ -720,11 +706,10 @@ export function UnixCppEntriesProvider(nmExec, objdumpExec, targetRootFS, apkEmb this.targetRootFS = targetRootFS; this.apkEmbeddedLibrary = apkEmbeddedLibrary; this.FUNC_RE = /^([0-9a-fA-F]{8,16}) ([0-9a-fA-F]{8,16} )?[tTwW] (.*)$/; -}; -inherits(UnixCppEntriesProvider, CppEntriesProvider); +} -UnixCppEntriesProvider.prototype.loadSymbols = function(libName) { +loadSymbols(libName) { this.parsePos = 0; if (this.apkEmbeddedLibrary && libName.endsWith('.apk')) { libName = this.apkEmbeddedLibrary; @@ -750,10 +735,10 @@ UnixCppEntriesProvider.prototype.loadSymbols = function(libName) { // If the library cannot be found on this system let's not panic. this.symbols = ['', '']; } -}; +} -UnixCppEntriesProvider.prototype.parseNextLine = function() { +parseNextLine() { if (this.symbols.length == 0) { return false; } @@ -775,18 +760,18 @@ UnixCppEntriesProvider.prototype.parseNextLine = function() { } } return funcInfo; -}; - +} +} -export function MacCppEntriesProvider(nmExec, objdumpExec, targetRootFS, apkEmbeddedLibrary) { - UnixCppEntriesProvider.call(this, nmExec, objdumpExec, targetRootFS, apkEmbeddedLibrary); +export class MacCppEntriesProvider extends UnixCppEntriesProvider { + constructor(nmExec, objdumpExec, targetRootFS, apkEmbeddedLibrary) { + super(nmExec, objdumpExec, targetRootFS, apkEmbeddedLibrary); // Note an empty group. It is required, as UnixCppEntriesProvider expects 3 groups. this.FUNC_RE = /^([0-9a-fA-F]{8,16})() (.*)$/; -}; -inherits(MacCppEntriesProvider, UnixCppEntriesProvider); +} -MacCppEntriesProvider.prototype.loadSymbols = function(libName) { +loadSymbols(libName) { this.parsePos = 0; libName = this.targetRootFS + libName; @@ -798,34 +783,36 @@ MacCppEntriesProvider.prototype.loadSymbols = function(libName) { // If the library cannot be found on this system let's not panic. this.symbols = ''; } -}; +} +} -export function WindowsCppEntriesProvider(_ignored_nmExec, _ignored_objdumpExec, targetRootFS, +export class WindowsCppEntriesProvider extends CppEntriesProvider { + constructor(_ignored_nmExec, _ignored_objdumpExec, targetRootFS, _ignored_apkEmbeddedLibrary) { + super(); this.targetRootFS = targetRootFS; this.symbols = ''; this.parsePos = 0; }; -inherits(WindowsCppEntriesProvider, CppEntriesProvider); -WindowsCppEntriesProvider.FILENAME_RE = /^(.*)\.([^.]+)$/; +static FILENAME_RE = /^(.*)\.([^.]+)$/; -WindowsCppEntriesProvider.FUNC_RE = +static FUNC_RE = /^\s+0001:[0-9a-fA-F]{8}\s+([_\?@$0-9a-zA-Z]+)\s+([0-9a-fA-F]{8}).*$/; -WindowsCppEntriesProvider.IMAGE_BASE_RE = +static IMAGE_BASE_RE = /^\s+0000:00000000\s+___ImageBase\s+([0-9a-fA-F]{8}).*$/; // This is almost a constant on Windows. -WindowsCppEntriesProvider.EXE_IMAGE_BASE = 0x00400000; +static EXE_IMAGE_BASE = 0x00400000; -WindowsCppEntriesProvider.prototype.loadSymbols = function(libName) { +loadSymbols(libName) { libName = this.targetRootFS + libName; const fileNameFields = libName.match(WindowsCppEntriesProvider.FILENAME_RE); if (!fileNameFields) return; @@ -840,7 +827,7 @@ WindowsCppEntriesProvider.prototype.loadSymbols = function(libName) { }; -WindowsCppEntriesProvider.prototype.parseNextLine = function() { +parseNextLine() { const lineEndPos = this.symbols.indexOf('\r\n', this.parsePos); if (lineEndPos == -1) { return false; @@ -875,14 +862,15 @@ WindowsCppEntriesProvider.prototype.parseNextLine = function() { * * ?LookupInDescriptor@JSObject@internal@v8@@...arguments info... */ -WindowsCppEntriesProvider.prototype.unmangleName = function(name) { + unmangleName(name) { // Empty or non-mangled name. if (name.length < 1 || name.charAt(0) != '?') return name; const nameEndPos = name.indexOf('@@'); const components = name.substring(1, nameEndPos).split('@'); components.reverse(); return components.join('::'); -}; +} +} export class ArgumentsProcessor extends BaseArgumentsProcessor { diff --git a/deps/v8/tools/v8heapconst.py b/deps/v8/tools/v8heapconst.py index 0dd31d4ad2..cfd41e6bbd 100644 --- a/deps/v8/tools/v8heapconst.py +++ b/deps/v8/tools/v8heapconst.py @@ -11,8 +11,6 @@ INSTANCE_TYPES = { 2: "EXTERNAL_INTERNALIZED_STRING_TYPE", 8: "ONE_BYTE_INTERNALIZED_STRING_TYPE", 10: "EXTERNAL_ONE_BYTE_INTERNALIZED_STRING_TYPE", - 18: "UNCACHED_EXTERNAL_INTERNALIZED_STRING_TYPE", - 26: "UNCACHED_EXTERNAL_ONE_BYTE_INTERNALIZED_STRING_TYPE", 32: "STRING_TYPE", 33: "CONS_STRING_TYPE", 34: "EXTERNAL_STRING_TYPE", @@ -77,294 +75,301 @@ INSTANCE_TYPES = { 113: "WASM_EXPORTED_FUNCTION_DATA_TYPE", 114: "WASM_INDIRECT_FUNCTION_TABLE_TYPE", 115: "WASM_JS_FUNCTION_DATA_TYPE", - 116: "WASM_VALUE_TYPE", - 117: "FIXED_ARRAY_TYPE", - 118: "HASH_TABLE_TYPE", - 119: "EPHEMERON_HASH_TABLE_TYPE", - 120: "GLOBAL_DICTIONARY_TYPE", - 121: "NAME_DICTIONARY_TYPE", - 122: "NUMBER_DICTIONARY_TYPE", - 123: "ORDERED_HASH_MAP_TYPE", - 124: "ORDERED_HASH_SET_TYPE", - 125: "ORDERED_NAME_DICTIONARY_TYPE", - 126: "SIMPLE_NUMBER_DICTIONARY_TYPE", - 127: "CLOSURE_FEEDBACK_CELL_ARRAY_TYPE", - 128: "OBJECT_BOILERPLATE_DESCRIPTION_TYPE", - 129: "SCOPE_INFO_TYPE", - 130: "SCRIPT_CONTEXT_TABLE_TYPE", - 131: "BYTE_ARRAY_TYPE", - 132: "BYTECODE_ARRAY_TYPE", - 133: "FIXED_DOUBLE_ARRAY_TYPE", - 134: "INTERNAL_CLASS_WITH_SMI_ELEMENTS_TYPE", - 135: "SLOPPY_ARGUMENTS_ELEMENTS_TYPE", - 136: "AWAIT_CONTEXT_TYPE", - 137: "BLOCK_CONTEXT_TYPE", - 138: "CATCH_CONTEXT_TYPE", - 139: "DEBUG_EVALUATE_CONTEXT_TYPE", - 140: "EVAL_CONTEXT_TYPE", - 141: "FUNCTION_CONTEXT_TYPE", - 142: "MODULE_CONTEXT_TYPE", - 143: "NATIVE_CONTEXT_TYPE", - 144: "SCRIPT_CONTEXT_TYPE", - 145: "WITH_CONTEXT_TYPE", - 146: "EXPORTED_SUB_CLASS_BASE_TYPE", - 147: "EXPORTED_SUB_CLASS_TYPE", - 148: "EXPORTED_SUB_CLASS2_TYPE", - 149: "SMALL_ORDERED_HASH_MAP_TYPE", - 150: "SMALL_ORDERED_HASH_SET_TYPE", - 151: "SMALL_ORDERED_NAME_DICTIONARY_TYPE", - 152: "DESCRIPTOR_ARRAY_TYPE", - 153: "STRONG_DESCRIPTOR_ARRAY_TYPE", - 154: "SOURCE_TEXT_MODULE_TYPE", - 155: "SYNTHETIC_MODULE_TYPE", - 156: "UNCOMPILED_DATA_WITH_PREPARSE_DATA_TYPE", - 157: "UNCOMPILED_DATA_WITHOUT_PREPARSE_DATA_TYPE", - 158: "WEAK_FIXED_ARRAY_TYPE", - 159: "TRANSITION_ARRAY_TYPE", - 160: "CELL_TYPE", - 161: "CODE_TYPE", - 162: "CODE_DATA_CONTAINER_TYPE", - 163: "COVERAGE_INFO_TYPE", - 164: "EMBEDDER_DATA_ARRAY_TYPE", - 165: "FEEDBACK_METADATA_TYPE", - 166: "FEEDBACK_VECTOR_TYPE", - 167: "FILLER_TYPE", - 168: "FREE_SPACE_TYPE", - 169: "INTERNAL_CLASS_TYPE", - 170: "INTERNAL_CLASS_WITH_STRUCT_ELEMENTS_TYPE", - 171: "MAP_TYPE", - 172: "ON_HEAP_BASIC_BLOCK_PROFILER_DATA_TYPE", - 173: "PREPARSE_DATA_TYPE", - 174: "PROPERTY_ARRAY_TYPE", - 175: "PROPERTY_CELL_TYPE", - 176: "SHARED_FUNCTION_INFO_TYPE", - 177: "SMI_BOX_TYPE", - 178: "SMI_PAIR_TYPE", - 179: "SORT_STATE_TYPE", - 180: "WASM_ARRAY_TYPE", - 181: "WASM_CAPI_FUNCTION_DATA_TYPE", - 182: "WASM_STRUCT_TYPE", - 183: "WEAK_ARRAY_LIST_TYPE", - 184: "WEAK_CELL_TYPE", - 185: "JS_PROXY_TYPE", + 116: "FIXED_ARRAY_TYPE", + 117: "HASH_TABLE_TYPE", + 118: "EPHEMERON_HASH_TABLE_TYPE", + 119: "GLOBAL_DICTIONARY_TYPE", + 120: "NAME_DICTIONARY_TYPE", + 121: "NUMBER_DICTIONARY_TYPE", + 122: "ORDERED_HASH_MAP_TYPE", + 123: "ORDERED_HASH_SET_TYPE", + 124: "ORDERED_NAME_DICTIONARY_TYPE", + 125: "SIMPLE_NUMBER_DICTIONARY_TYPE", + 126: "CLOSURE_FEEDBACK_CELL_ARRAY_TYPE", + 127: "OBJECT_BOILERPLATE_DESCRIPTION_TYPE", + 128: "SCOPE_INFO_TYPE", + 129: "SCRIPT_CONTEXT_TABLE_TYPE", + 130: "BYTE_ARRAY_TYPE", + 131: "BYTECODE_ARRAY_TYPE", + 132: "FIXED_DOUBLE_ARRAY_TYPE", + 133: "INTERNAL_CLASS_WITH_SMI_ELEMENTS_TYPE", + 134: "SLOPPY_ARGUMENTS_ELEMENTS_TYPE", + 135: "AWAIT_CONTEXT_TYPE", + 136: "BLOCK_CONTEXT_TYPE", + 137: "CATCH_CONTEXT_TYPE", + 138: "DEBUG_EVALUATE_CONTEXT_TYPE", + 139: "EVAL_CONTEXT_TYPE", + 140: "FUNCTION_CONTEXT_TYPE", + 141: "MODULE_CONTEXT_TYPE", + 142: "NATIVE_CONTEXT_TYPE", + 143: "SCRIPT_CONTEXT_TYPE", + 144: "WITH_CONTEXT_TYPE", + 145: "EXPORTED_SUB_CLASS_BASE_TYPE", + 146: "EXPORTED_SUB_CLASS_TYPE", + 147: "EXPORTED_SUB_CLASS2_TYPE", + 148: "SMALL_ORDERED_HASH_MAP_TYPE", + 149: "SMALL_ORDERED_HASH_SET_TYPE", + 150: "SMALL_ORDERED_NAME_DICTIONARY_TYPE", + 151: "DESCRIPTOR_ARRAY_TYPE", + 152: "STRONG_DESCRIPTOR_ARRAY_TYPE", + 153: "SOURCE_TEXT_MODULE_TYPE", + 154: "SYNTHETIC_MODULE_TYPE", + 155: "UNCOMPILED_DATA_WITH_PREPARSE_DATA_TYPE", + 156: "UNCOMPILED_DATA_WITHOUT_PREPARSE_DATA_TYPE", + 157: "WEAK_FIXED_ARRAY_TYPE", + 158: "TRANSITION_ARRAY_TYPE", + 159: "CELL_TYPE", + 160: "CODE_TYPE", + 161: "CODE_DATA_CONTAINER_TYPE", + 162: "COVERAGE_INFO_TYPE", + 163: "EMBEDDER_DATA_ARRAY_TYPE", + 164: "FEEDBACK_METADATA_TYPE", + 165: "FEEDBACK_VECTOR_TYPE", + 166: "FILLER_TYPE", + 167: "FREE_SPACE_TYPE", + 168: "INTERNAL_CLASS_TYPE", + 169: "INTERNAL_CLASS_WITH_STRUCT_ELEMENTS_TYPE", + 170: "MAP_TYPE", + 171: "ON_HEAP_BASIC_BLOCK_PROFILER_DATA_TYPE", + 172: "PREPARSE_DATA_TYPE", + 173: "PROPERTY_ARRAY_TYPE", + 174: "PROPERTY_CELL_TYPE", + 175: "SHARED_FUNCTION_INFO_TYPE", + 176: "SMI_BOX_TYPE", + 177: "SMI_PAIR_TYPE", + 178: "SORT_STATE_TYPE", + 179: "WASM_ARRAY_TYPE", + 180: "WASM_CAPI_FUNCTION_DATA_TYPE", + 181: "WASM_STRUCT_TYPE", + 182: "WEAK_ARRAY_LIST_TYPE", + 183: "WEAK_CELL_TYPE", + 184: "JS_PROXY_TYPE", 1057: "JS_OBJECT_TYPE", - 186: "JS_GLOBAL_OBJECT_TYPE", - 187: "JS_GLOBAL_PROXY_TYPE", - 188: "JS_MODULE_NAMESPACE_TYPE", + 185: "JS_GLOBAL_OBJECT_TYPE", + 186: "JS_GLOBAL_PROXY_TYPE", + 187: "JS_MODULE_NAMESPACE_TYPE", 1040: "JS_SPECIAL_API_OBJECT_TYPE", 1041: "JS_PRIMITIVE_WRAPPER_TYPE", - 1042: "JS_MAP_KEY_ITERATOR_TYPE", - 1043: "JS_MAP_KEY_VALUE_ITERATOR_TYPE", - 1044: "JS_MAP_VALUE_ITERATOR_TYPE", - 1045: "JS_SET_KEY_VALUE_ITERATOR_TYPE", - 1046: "JS_SET_VALUE_ITERATOR_TYPE", - 1047: "JS_GENERATOR_OBJECT_TYPE", - 1048: "JS_ASYNC_FUNCTION_OBJECT_TYPE", - 1049: "JS_ASYNC_GENERATOR_OBJECT_TYPE", - 1050: "JS_DATA_VIEW_TYPE", - 1051: "JS_TYPED_ARRAY_TYPE", - 1052: "JS_MAP_TYPE", - 1053: "JS_SET_TYPE", - 1054: "JS_WEAK_MAP_TYPE", - 1055: "JS_WEAK_SET_TYPE", + 1042: "JS_ARRAY_ITERATOR_PROTOTYPE_TYPE", + 1043: "JS_ITERATOR_PROTOTYPE_TYPE", + 1044: "JS_MAP_ITERATOR_PROTOTYPE_TYPE", + 1045: "JS_OBJECT_PROTOTYPE_TYPE", + 1046: "JS_PROMISE_PROTOTYPE_TYPE", + 1047: "JS_REG_EXP_PROTOTYPE_TYPE", + 1048: "JS_SET_ITERATOR_PROTOTYPE_TYPE", + 1049: "JS_SET_PROTOTYPE_TYPE", + 1050: "JS_STRING_ITERATOR_PROTOTYPE_TYPE", + 1051: "JS_TYPED_ARRAY_PROTOTYPE_TYPE", + 1052: "JS_GENERATOR_OBJECT_TYPE", + 1053: "JS_ASYNC_FUNCTION_OBJECT_TYPE", + 1054: "JS_ASYNC_GENERATOR_OBJECT_TYPE", + 1055: "JS_ARGUMENTS_OBJECT_TYPE", 1056: "JS_API_OBJECT_TYPE", - 1058: "JS_ARGUMENTS_OBJECT_TYPE", - 1059: "JS_ARRAY_TYPE", - 1060: "JS_ARRAY_BUFFER_TYPE", - 1061: "JS_ARRAY_ITERATOR_TYPE", - 1062: "JS_ASYNC_FROM_SYNC_ITERATOR_TYPE", - 1063: "JS_COLLATOR_TYPE", - 1064: "JS_CONTEXT_EXTENSION_OBJECT_TYPE", - 1065: "JS_DATE_TYPE", - 1066: "JS_DATE_TIME_FORMAT_TYPE", - 1067: "JS_DISPLAY_NAMES_TYPE", - 1068: "JS_ERROR_TYPE", - 1069: "JS_FINALIZATION_REGISTRY_TYPE", - 1070: "JS_LIST_FORMAT_TYPE", - 1071: "JS_LOCALE_TYPE", - 1072: "JS_MESSAGE_OBJECT_TYPE", - 1073: "JS_NUMBER_FORMAT_TYPE", - 1074: "JS_PLURAL_RULES_TYPE", - 1075: "JS_PROMISE_TYPE", - 1076: "JS_REG_EXP_TYPE", - 1077: "JS_REG_EXP_STRING_ITERATOR_TYPE", - 1078: "JS_RELATIVE_TIME_FORMAT_TYPE", - 1079: "JS_SEGMENT_ITERATOR_TYPE", - 1080: "JS_SEGMENTER_TYPE", - 1081: "JS_SEGMENTS_TYPE", - 1082: "JS_STRING_ITERATOR_TYPE", - 1083: "JS_V8_BREAK_ITERATOR_TYPE", - 1084: "JS_WEAK_REF_TYPE", - 1085: "WASM_EXCEPTION_OBJECT_TYPE", - 1086: "WASM_GLOBAL_OBJECT_TYPE", - 1087: "WASM_INSTANCE_OBJECT_TYPE", - 1088: "WASM_MEMORY_OBJECT_TYPE", - 1089: "WASM_MODULE_OBJECT_TYPE", - 1090: "WASM_TABLE_OBJECT_TYPE", - 1091: "JS_BOUND_FUNCTION_TYPE", - 1092: "JS_FUNCTION_TYPE", + 1058: "JS_MAP_KEY_ITERATOR_TYPE", + 1059: "JS_MAP_KEY_VALUE_ITERATOR_TYPE", + 1060: "JS_MAP_VALUE_ITERATOR_TYPE", + 1061: "JS_SET_KEY_VALUE_ITERATOR_TYPE", + 1062: "JS_SET_VALUE_ITERATOR_TYPE", + 1063: "JS_DATA_VIEW_TYPE", + 1064: "JS_TYPED_ARRAY_TYPE", + 1065: "JS_MAP_TYPE", + 1066: "JS_SET_TYPE", + 1067: "JS_BOUND_FUNCTION_TYPE", + 1068: "JS_FUNCTION_TYPE", + 1069: "JS_WEAK_MAP_TYPE", + 1070: "JS_WEAK_SET_TYPE", + 1071: "JS_ARRAY_TYPE", + 1072: "JS_ARRAY_BUFFER_TYPE", + 1073: "JS_ARRAY_ITERATOR_TYPE", + 1074: "JS_ASYNC_FROM_SYNC_ITERATOR_TYPE", + 1075: "JS_COLLATOR_TYPE", + 1076: "JS_CONTEXT_EXTENSION_OBJECT_TYPE", + 1077: "JS_DATE_TYPE", + 1078: "JS_DATE_TIME_FORMAT_TYPE", + 1079: "JS_DISPLAY_NAMES_TYPE", + 1080: "JS_ERROR_TYPE", + 1081: "JS_FINALIZATION_REGISTRY_TYPE", + 1082: "JS_LIST_FORMAT_TYPE", + 1083: "JS_LOCALE_TYPE", + 1084: "JS_MESSAGE_OBJECT_TYPE", + 1085: "JS_NUMBER_FORMAT_TYPE", + 1086: "JS_PLURAL_RULES_TYPE", + 1087: "JS_PROMISE_TYPE", + 1088: "JS_REG_EXP_TYPE", + 1089: "JS_REG_EXP_STRING_ITERATOR_TYPE", + 1090: "JS_RELATIVE_TIME_FORMAT_TYPE", + 1091: "JS_SEGMENT_ITERATOR_TYPE", + 1092: "JS_SEGMENTER_TYPE", + 1093: "JS_SEGMENTS_TYPE", + 1094: "JS_STRING_ITERATOR_TYPE", + 1095: "JS_V8_BREAK_ITERATOR_TYPE", + 1096: "JS_WEAK_REF_TYPE", + 1097: "WASM_EXCEPTION_OBJECT_TYPE", + 1098: "WASM_GLOBAL_OBJECT_TYPE", + 1099: "WASM_INSTANCE_OBJECT_TYPE", + 1100: "WASM_MEMORY_OBJECT_TYPE", + 1101: "WASM_MODULE_OBJECT_TYPE", + 1102: "WASM_TABLE_OBJECT_TYPE", } # List of known V8 maps. KNOWN_MAPS = { - ("read_only_space", 0x02115): (171, "MetaMap"), + ("read_only_space", 0x02115): (170, "MetaMap"), ("read_only_space", 0x0213d): (67, "NullMap"), - ("read_only_space", 0x02165): (153, "StrongDescriptorArrayMap"), - ("read_only_space", 0x0218d): (158, "WeakFixedArrayMap"), + ("read_only_space", 0x02165): (152, "StrongDescriptorArrayMap"), + ("read_only_space", 0x0218d): (157, "WeakFixedArrayMap"), ("read_only_space", 0x021cd): (96, "EnumCacheMap"), - ("read_only_space", 0x02201): (117, "FixedArrayMap"), + ("read_only_space", 0x02201): (116, "FixedArrayMap"), ("read_only_space", 0x0224d): (8, "OneByteInternalizedStringMap"), - ("read_only_space", 0x02299): (168, "FreeSpaceMap"), - ("read_only_space", 0x022c1): (167, "OnePointerFillerMap"), - ("read_only_space", 0x022e9): (167, "TwoPointerFillerMap"), + ("read_only_space", 0x02299): (167, "FreeSpaceMap"), + ("read_only_space", 0x022c1): (166, "OnePointerFillerMap"), + ("read_only_space", 0x022e9): (166, "TwoPointerFillerMap"), ("read_only_space", 0x02311): (67, "UninitializedMap"), ("read_only_space", 0x02389): (67, "UndefinedMap"), ("read_only_space", 0x023cd): (66, "HeapNumberMap"), ("read_only_space", 0x02401): (67, "TheHoleMap"), ("read_only_space", 0x02461): (67, "BooleanMap"), - ("read_only_space", 0x02505): (131, "ByteArrayMap"), - ("read_only_space", 0x0252d): (117, "FixedCOWArrayMap"), - ("read_only_space", 0x02555): (118, "HashTableMap"), + ("read_only_space", 0x02505): (130, "ByteArrayMap"), + ("read_only_space", 0x0252d): (116, "FixedCOWArrayMap"), + ("read_only_space", 0x02555): (117, "HashTableMap"), ("read_only_space", 0x0257d): (64, "SymbolMap"), ("read_only_space", 0x025a5): (40, "OneByteStringMap"), - ("read_only_space", 0x025cd): (129, "ScopeInfoMap"), - ("read_only_space", 0x025f5): (176, "SharedFunctionInfoMap"), - ("read_only_space", 0x0261d): (161, "CodeMap"), - ("read_only_space", 0x02645): (160, "CellMap"), - ("read_only_space", 0x0266d): (175, "GlobalPropertyCellMap"), + ("read_only_space", 0x025cd): (128, "ScopeInfoMap"), + ("read_only_space", 0x025f5): (175, "SharedFunctionInfoMap"), + ("read_only_space", 0x0261d): (160, "CodeMap"), + ("read_only_space", 0x02645): (159, "CellMap"), + ("read_only_space", 0x0266d): (174, "GlobalPropertyCellMap"), ("read_only_space", 0x02695): (70, "ForeignMap"), - ("read_only_space", 0x026bd): (159, "TransitionArrayMap"), + ("read_only_space", 0x026bd): (158, "TransitionArrayMap"), ("read_only_space", 0x026e5): (45, "ThinOneByteStringMap"), - ("read_only_space", 0x0270d): (166, "FeedbackVectorMap"), - ("read_only_space", 0x0273d): (67, "ArgumentsMarkerMap"), - ("read_only_space", 0x0279d): (67, "ExceptionMap"), - ("read_only_space", 0x027f9): (67, "TerminationExceptionMap"), - ("read_only_space", 0x02861): (67, "OptimizedOutMap"), - ("read_only_space", 0x028c1): (67, "StaleRegisterMap"), - ("read_only_space", 0x02921): (130, "ScriptContextTableMap"), - ("read_only_space", 0x02949): (127, "ClosureFeedbackCellArrayMap"), - ("read_only_space", 0x02971): (165, "FeedbackMetadataArrayMap"), - ("read_only_space", 0x02999): (117, "ArrayListMap"), - ("read_only_space", 0x029c1): (65, "BigIntMap"), - ("read_only_space", 0x029e9): (128, "ObjectBoilerplateDescriptionMap"), - ("read_only_space", 0x02a11): (132, "BytecodeArrayMap"), - ("read_only_space", 0x02a39): (162, "CodeDataContainerMap"), - ("read_only_space", 0x02a61): (163, "CoverageInfoMap"), - ("read_only_space", 0x02a89): (133, "FixedDoubleArrayMap"), - ("read_only_space", 0x02ab1): (120, "GlobalDictionaryMap"), - ("read_only_space", 0x02ad9): (97, "ManyClosuresCellMap"), - ("read_only_space", 0x02b01): (117, "ModuleInfoMap"), - ("read_only_space", 0x02b29): (121, "NameDictionaryMap"), - ("read_only_space", 0x02b51): (97, "NoClosuresCellMap"), - ("read_only_space", 0x02b79): (122, "NumberDictionaryMap"), - ("read_only_space", 0x02ba1): (97, "OneClosureCellMap"), - ("read_only_space", 0x02bc9): (123, "OrderedHashMapMap"), - ("read_only_space", 0x02bf1): (124, "OrderedHashSetMap"), - ("read_only_space", 0x02c19): (125, "OrderedNameDictionaryMap"), - ("read_only_space", 0x02c41): (173, "PreparseDataMap"), - ("read_only_space", 0x02c69): (174, "PropertyArrayMap"), - ("read_only_space", 0x02c91): (93, "SideEffectCallHandlerInfoMap"), - ("read_only_space", 0x02cb9): (93, "SideEffectFreeCallHandlerInfoMap"), - ("read_only_space", 0x02ce1): (93, "NextCallSideEffectFreeCallHandlerInfoMap"), - ("read_only_space", 0x02d09): (126, "SimpleNumberDictionaryMap"), - ("read_only_space", 0x02d31): (149, "SmallOrderedHashMapMap"), - ("read_only_space", 0x02d59): (150, "SmallOrderedHashSetMap"), - ("read_only_space", 0x02d81): (151, "SmallOrderedNameDictionaryMap"), - ("read_only_space", 0x02da9): (154, "SourceTextModuleMap"), - ("read_only_space", 0x02dd1): (155, "SyntheticModuleMap"), - ("read_only_space", 0x02df9): (157, "UncompiledDataWithoutPreparseDataMap"), - ("read_only_space", 0x02e21): (156, "UncompiledDataWithPreparseDataMap"), - ("read_only_space", 0x02e49): (71, "WasmTypeInfoMap"), - ("read_only_space", 0x02e71): (183, "WeakArrayListMap"), - ("read_only_space", 0x02e99): (119, "EphemeronHashTableMap"), - ("read_only_space", 0x02ec1): (164, "EmbedderDataArrayMap"), - ("read_only_space", 0x02ee9): (184, "WeakCellMap"), - ("read_only_space", 0x02f11): (32, "StringMap"), - ("read_only_space", 0x02f39): (41, "ConsOneByteStringMap"), - ("read_only_space", 0x02f61): (33, "ConsStringMap"), - ("read_only_space", 0x02f89): (37, "ThinStringMap"), - ("read_only_space", 0x02fb1): (35, "SlicedStringMap"), - ("read_only_space", 0x02fd9): (43, "SlicedOneByteStringMap"), - ("read_only_space", 0x03001): (34, "ExternalStringMap"), - ("read_only_space", 0x03029): (42, "ExternalOneByteStringMap"), - ("read_only_space", 0x03051): (50, "UncachedExternalStringMap"), - ("read_only_space", 0x03079): (0, "InternalizedStringMap"), - ("read_only_space", 0x030a1): (2, "ExternalInternalizedStringMap"), - ("read_only_space", 0x030c9): (10, "ExternalOneByteInternalizedStringMap"), - ("read_only_space", 0x030f1): (18, "UncachedExternalInternalizedStringMap"), - ("read_only_space", 0x03119): (26, "UncachedExternalOneByteInternalizedStringMap"), - ("read_only_space", 0x03141): (58, "UncachedExternalOneByteStringMap"), - ("read_only_space", 0x03169): (67, "SelfReferenceMarkerMap"), - ("read_only_space", 0x03191): (67, "BasicBlockCountersMarkerMap"), - ("read_only_space", 0x031d5): (87, "ArrayBoilerplateDescriptionMap"), - ("read_only_space", 0x032bd): (99, "InterceptorInfoMap"), - ("read_only_space", 0x053c9): (72, "PromiseFulfillReactionJobTaskMap"), - ("read_only_space", 0x053f1): (73, "PromiseRejectReactionJobTaskMap"), - ("read_only_space", 0x05419): (74, "CallableTaskMap"), - ("read_only_space", 0x05441): (75, "CallbackTaskMap"), - ("read_only_space", 0x05469): (76, "PromiseResolveThenableJobTaskMap"), - ("read_only_space", 0x05491): (79, "FunctionTemplateInfoMap"), - ("read_only_space", 0x054b9): (80, "ObjectTemplateInfoMap"), - ("read_only_space", 0x054e1): (81, "AccessCheckInfoMap"), - ("read_only_space", 0x05509): (82, "AccessorInfoMap"), - ("read_only_space", 0x05531): (83, "AccessorPairMap"), - ("read_only_space", 0x05559): (84, "AliasedArgumentsEntryMap"), - ("read_only_space", 0x05581): (85, "AllocationMementoMap"), - ("read_only_space", 0x055a9): (88, "AsmWasmDataMap"), - ("read_only_space", 0x055d1): (89, "AsyncGeneratorRequestMap"), - ("read_only_space", 0x055f9): (90, "BreakPointMap"), - ("read_only_space", 0x05621): (91, "BreakPointInfoMap"), - ("read_only_space", 0x05649): (92, "CachedTemplateObjectMap"), - ("read_only_space", 0x05671): (94, "ClassPositionsMap"), - ("read_only_space", 0x05699): (95, "DebugInfoMap"), - ("read_only_space", 0x056c1): (98, "FunctionTemplateRareDataMap"), - ("read_only_space", 0x056e9): (100, "InterpreterDataMap"), - ("read_only_space", 0x05711): (101, "ModuleRequestMap"), - ("read_only_space", 0x05739): (102, "PromiseCapabilityMap"), - ("read_only_space", 0x05761): (103, "PromiseReactionMap"), - ("read_only_space", 0x05789): (104, "PropertyDescriptorObjectMap"), - ("read_only_space", 0x057b1): (105, "PrototypeInfoMap"), - ("read_only_space", 0x057d9): (106, "ScriptMap"), - ("read_only_space", 0x05801): (107, "SourceTextModuleInfoEntryMap"), - ("read_only_space", 0x05829): (108, "StackFrameInfoMap"), - ("read_only_space", 0x05851): (109, "StackTraceFrameMap"), - ("read_only_space", 0x05879): (110, "TemplateObjectDescriptionMap"), - ("read_only_space", 0x058a1): (111, "Tuple2Map"), - ("read_only_space", 0x058c9): (112, "WasmExceptionTagMap"), - ("read_only_space", 0x058f1): (113, "WasmExportedFunctionDataMap"), - ("read_only_space", 0x05919): (114, "WasmIndirectFunctionTableMap"), - ("read_only_space", 0x05941): (115, "WasmJSFunctionDataMap"), - ("read_only_space", 0x05969): (116, "WasmValueMap"), - ("read_only_space", 0x05991): (135, "SloppyArgumentsElementsMap"), - ("read_only_space", 0x059b9): (152, "DescriptorArrayMap"), - ("read_only_space", 0x059e1): (172, "OnHeapBasicBlockProfilerDataMap"), - ("read_only_space", 0x05a09): (181, "WasmCapiFunctionDataMap"), - ("read_only_space", 0x05a31): (169, "InternalClassMap"), - ("read_only_space", 0x05a59): (178, "SmiPairMap"), - ("read_only_space", 0x05a81): (177, "SmiBoxMap"), - ("read_only_space", 0x05aa9): (146, "ExportedSubClassBaseMap"), - ("read_only_space", 0x05ad1): (147, "ExportedSubClassMap"), - ("read_only_space", 0x05af9): (68, "AbstractInternalClassSubclass1Map"), - ("read_only_space", 0x05b21): (69, "AbstractInternalClassSubclass2Map"), - ("read_only_space", 0x05b49): (134, "InternalClassWithSmiElementsMap"), - ("read_only_space", 0x05b71): (170, "InternalClassWithStructElementsMap"), - ("read_only_space", 0x05b99): (148, "ExportedSubClass2Map"), - ("read_only_space", 0x05bc1): (179, "SortStateMap"), - ("read_only_space", 0x05be9): (86, "AllocationSiteWithWeakNextMap"), - ("read_only_space", 0x05c11): (86, "AllocationSiteWithoutWeakNextMap"), - ("read_only_space", 0x05c39): (77, "LoadHandler1Map"), - ("read_only_space", 0x05c61): (77, "LoadHandler2Map"), - ("read_only_space", 0x05c89): (77, "LoadHandler3Map"), - ("read_only_space", 0x05cb1): (78, "StoreHandler0Map"), - ("read_only_space", 0x05cd9): (78, "StoreHandler1Map"), - ("read_only_space", 0x05d01): (78, "StoreHandler2Map"), - ("read_only_space", 0x05d29): (78, "StoreHandler3Map"), + ("read_only_space", 0x0270d): (165, "FeedbackVectorMap"), + ("read_only_space", 0x02749): (67, "ArgumentsMarkerMap"), + ("read_only_space", 0x027a9): (67, "ExceptionMap"), + ("read_only_space", 0x02805): (67, "TerminationExceptionMap"), + ("read_only_space", 0x0286d): (67, "OptimizedOutMap"), + ("read_only_space", 0x028cd): (67, "StaleRegisterMap"), + ("read_only_space", 0x0292d): (129, "ScriptContextTableMap"), + ("read_only_space", 0x02955): (126, "ClosureFeedbackCellArrayMap"), + ("read_only_space", 0x0297d): (164, "FeedbackMetadataArrayMap"), + ("read_only_space", 0x029a5): (116, "ArrayListMap"), + ("read_only_space", 0x029cd): (65, "BigIntMap"), + ("read_only_space", 0x029f5): (127, "ObjectBoilerplateDescriptionMap"), + ("read_only_space", 0x02a1d): (131, "BytecodeArrayMap"), + ("read_only_space", 0x02a45): (161, "CodeDataContainerMap"), + ("read_only_space", 0x02a6d): (162, "CoverageInfoMap"), + ("read_only_space", 0x02a95): (132, "FixedDoubleArrayMap"), + ("read_only_space", 0x02abd): (119, "GlobalDictionaryMap"), + ("read_only_space", 0x02ae5): (97, "ManyClosuresCellMap"), + ("read_only_space", 0x02b0d): (116, "ModuleInfoMap"), + ("read_only_space", 0x02b35): (120, "NameDictionaryMap"), + ("read_only_space", 0x02b5d): (97, "NoClosuresCellMap"), + ("read_only_space", 0x02b85): (121, "NumberDictionaryMap"), + ("read_only_space", 0x02bad): (97, "OneClosureCellMap"), + ("read_only_space", 0x02bd5): (122, "OrderedHashMapMap"), + ("read_only_space", 0x02bfd): (123, "OrderedHashSetMap"), + ("read_only_space", 0x02c25): (124, "OrderedNameDictionaryMap"), + ("read_only_space", 0x02c4d): (172, "PreparseDataMap"), + ("read_only_space", 0x02c75): (173, "PropertyArrayMap"), + ("read_only_space", 0x02c9d): (93, "SideEffectCallHandlerInfoMap"), + ("read_only_space", 0x02cc5): (93, "SideEffectFreeCallHandlerInfoMap"), + ("read_only_space", 0x02ced): (93, "NextCallSideEffectFreeCallHandlerInfoMap"), + ("read_only_space", 0x02d15): (125, "SimpleNumberDictionaryMap"), + ("read_only_space", 0x02d3d): (148, "SmallOrderedHashMapMap"), + ("read_only_space", 0x02d65): (149, "SmallOrderedHashSetMap"), + ("read_only_space", 0x02d8d): (150, "SmallOrderedNameDictionaryMap"), + ("read_only_space", 0x02db5): (153, "SourceTextModuleMap"), + ("read_only_space", 0x02ddd): (154, "SyntheticModuleMap"), + ("read_only_space", 0x02e05): (71, "WasmTypeInfoMap"), + ("read_only_space", 0x02e2d): (182, "WeakArrayListMap"), + ("read_only_space", 0x02e55): (118, "EphemeronHashTableMap"), + ("read_only_space", 0x02e7d): (163, "EmbedderDataArrayMap"), + ("read_only_space", 0x02ea5): (183, "WeakCellMap"), + ("read_only_space", 0x02ecd): (32, "StringMap"), + ("read_only_space", 0x02ef5): (41, "ConsOneByteStringMap"), + ("read_only_space", 0x02f1d): (33, "ConsStringMap"), + ("read_only_space", 0x02f45): (37, "ThinStringMap"), + ("read_only_space", 0x02f6d): (35, "SlicedStringMap"), + ("read_only_space", 0x02f95): (43, "SlicedOneByteStringMap"), + ("read_only_space", 0x02fbd): (34, "ExternalStringMap"), + ("read_only_space", 0x02fe5): (42, "ExternalOneByteStringMap"), + ("read_only_space", 0x0300d): (50, "UncachedExternalStringMap"), + ("read_only_space", 0x03035): (0, "InternalizedStringMap"), + ("read_only_space", 0x0305d): (2, "ExternalInternalizedStringMap"), + ("read_only_space", 0x03085): (10, "ExternalOneByteInternalizedStringMap"), + ("read_only_space", 0x030ad): (58, "UncachedExternalOneByteStringMap"), + ("read_only_space", 0x030d5): (67, "SelfReferenceMarkerMap"), + ("read_only_space", 0x030fd): (67, "BasicBlockCountersMarkerMap"), + ("read_only_space", 0x03141): (87, "ArrayBoilerplateDescriptionMap"), + ("read_only_space", 0x03229): (99, "InterceptorInfoMap"), + ("read_only_space", 0x05355): (72, "PromiseFulfillReactionJobTaskMap"), + ("read_only_space", 0x0537d): (73, "PromiseRejectReactionJobTaskMap"), + ("read_only_space", 0x053a5): (74, "CallableTaskMap"), + ("read_only_space", 0x053cd): (75, "CallbackTaskMap"), + ("read_only_space", 0x053f5): (76, "PromiseResolveThenableJobTaskMap"), + ("read_only_space", 0x0541d): (79, "FunctionTemplateInfoMap"), + ("read_only_space", 0x05445): (80, "ObjectTemplateInfoMap"), + ("read_only_space", 0x0546d): (81, "AccessCheckInfoMap"), + ("read_only_space", 0x05495): (82, "AccessorInfoMap"), + ("read_only_space", 0x054bd): (83, "AccessorPairMap"), + ("read_only_space", 0x054e5): (84, "AliasedArgumentsEntryMap"), + ("read_only_space", 0x0550d): (85, "AllocationMementoMap"), + ("read_only_space", 0x05535): (88, "AsmWasmDataMap"), + ("read_only_space", 0x0555d): (89, "AsyncGeneratorRequestMap"), + ("read_only_space", 0x05585): (90, "BreakPointMap"), + ("read_only_space", 0x055ad): (91, "BreakPointInfoMap"), + ("read_only_space", 0x055d5): (92, "CachedTemplateObjectMap"), + ("read_only_space", 0x055fd): (94, "ClassPositionsMap"), + ("read_only_space", 0x05625): (95, "DebugInfoMap"), + ("read_only_space", 0x0564d): (98, "FunctionTemplateRareDataMap"), + ("read_only_space", 0x05675): (100, "InterpreterDataMap"), + ("read_only_space", 0x0569d): (101, "ModuleRequestMap"), + ("read_only_space", 0x056c5): (102, "PromiseCapabilityMap"), + ("read_only_space", 0x056ed): (103, "PromiseReactionMap"), + ("read_only_space", 0x05715): (104, "PropertyDescriptorObjectMap"), + ("read_only_space", 0x0573d): (105, "PrototypeInfoMap"), + ("read_only_space", 0x05765): (106, "ScriptMap"), + ("read_only_space", 0x0578d): (107, "SourceTextModuleInfoEntryMap"), + ("read_only_space", 0x057b5): (108, "StackFrameInfoMap"), + ("read_only_space", 0x057dd): (109, "StackTraceFrameMap"), + ("read_only_space", 0x05805): (110, "TemplateObjectDescriptionMap"), + ("read_only_space", 0x0582d): (111, "Tuple2Map"), + ("read_only_space", 0x05855): (112, "WasmExceptionTagMap"), + ("read_only_space", 0x0587d): (113, "WasmExportedFunctionDataMap"), + ("read_only_space", 0x058a5): (114, "WasmIndirectFunctionTableMap"), + ("read_only_space", 0x058cd): (115, "WasmJSFunctionDataMap"), + ("read_only_space", 0x058f5): (134, "SloppyArgumentsElementsMap"), + ("read_only_space", 0x0591d): (151, "DescriptorArrayMap"), + ("read_only_space", 0x05945): (156, "UncompiledDataWithoutPreparseDataMap"), + ("read_only_space", 0x0596d): (155, "UncompiledDataWithPreparseDataMap"), + ("read_only_space", 0x05995): (171, "OnHeapBasicBlockProfilerDataMap"), + ("read_only_space", 0x059bd): (180, "WasmCapiFunctionDataMap"), + ("read_only_space", 0x059e5): (168, "InternalClassMap"), + ("read_only_space", 0x05a0d): (177, "SmiPairMap"), + ("read_only_space", 0x05a35): (176, "SmiBoxMap"), + ("read_only_space", 0x05a5d): (145, "ExportedSubClassBaseMap"), + ("read_only_space", 0x05a85): (146, "ExportedSubClassMap"), + ("read_only_space", 0x05aad): (68, "AbstractInternalClassSubclass1Map"), + ("read_only_space", 0x05ad5): (69, "AbstractInternalClassSubclass2Map"), + ("read_only_space", 0x05afd): (133, "InternalClassWithSmiElementsMap"), + ("read_only_space", 0x05b25): (169, "InternalClassWithStructElementsMap"), + ("read_only_space", 0x05b4d): (147, "ExportedSubClass2Map"), + ("read_only_space", 0x05b75): (178, "SortStateMap"), + ("read_only_space", 0x05b9d): (86, "AllocationSiteWithWeakNextMap"), + ("read_only_space", 0x05bc5): (86, "AllocationSiteWithoutWeakNextMap"), + ("read_only_space", 0x05bed): (77, "LoadHandler1Map"), + ("read_only_space", 0x05c15): (77, "LoadHandler2Map"), + ("read_only_space", 0x05c3d): (77, "LoadHandler3Map"), + ("read_only_space", 0x05c65): (78, "StoreHandler0Map"), + ("read_only_space", 0x05c8d): (78, "StoreHandler1Map"), + ("read_only_space", 0x05cb5): (78, "StoreHandler2Map"), + ("read_only_space", 0x05cdd): (78, "StoreHandler3Map"), ("map_space", 0x02115): (1057, "ExternalMap"), - ("map_space", 0x0213d): (1072, "JSMessageObjectMap"), - ("map_space", 0x02165): (182, "WasmRttEqrefMap"), - ("map_space", 0x0218d): (182, "WasmRttExternrefMap"), - ("map_space", 0x021b5): (182, "WasmRttFuncrefMap"), - ("map_space", 0x021dd): (182, "WasmRttI31refMap"), + ("map_space", 0x0213d): (1084, "JSMessageObjectMap"), + ("map_space", 0x02165): (181, "WasmRttEqrefMap"), + ("map_space", 0x0218d): (181, "WasmRttAnyrefMap"), + ("map_space", 0x021b5): (181, "WasmRttExternrefMap"), + ("map_space", 0x021dd): (181, "WasmRttFuncrefMap"), + ("map_space", 0x02205): (181, "WasmRttI31refMap"), } # List of known V8 objects. @@ -383,37 +388,37 @@ KNOWN_OBJECTS = { ("read_only_space", 0x024c9): "FalseValue", ("read_only_space", 0x024f9): "empty_string", ("read_only_space", 0x02735): "EmptyScopeInfo", - ("read_only_space", 0x02765): "ArgumentsMarker", - ("read_only_space", 0x027c5): "Exception", - ("read_only_space", 0x02821): "TerminationException", - ("read_only_space", 0x02889): "OptimizedOut", - ("read_only_space", 0x028e9): "StaleRegister", - ("read_only_space", 0x031b9): "EmptyPropertyArray", - ("read_only_space", 0x031c1): "EmptyByteArray", - ("read_only_space", 0x031c9): "EmptyObjectBoilerplateDescription", - ("read_only_space", 0x031fd): "EmptyArrayBoilerplateDescription", - ("read_only_space", 0x03209): "EmptyClosureFeedbackCellArray", - ("read_only_space", 0x03211): "EmptySlowElementDictionary", - ("read_only_space", 0x03235): "EmptyOrderedHashMap", - ("read_only_space", 0x03249): "EmptyOrderedHashSet", - ("read_only_space", 0x0325d): "EmptyFeedbackMetadata", - ("read_only_space", 0x03269): "EmptyPropertyCell", - ("read_only_space", 0x0327d): "EmptyPropertyDictionary", - ("read_only_space", 0x032a5): "EmptyOrderedPropertyDictionary", - ("read_only_space", 0x032e5): "NoOpInterceptorInfo", - ("read_only_space", 0x0330d): "EmptyWeakArrayList", - ("read_only_space", 0x03319): "InfinityValue", - ("read_only_space", 0x03325): "MinusZeroValue", - ("read_only_space", 0x03331): "MinusInfinityValue", - ("read_only_space", 0x0333d): "SelfReferenceMarker", - ("read_only_space", 0x0337d): "BasicBlockCountersMarker", - ("read_only_space", 0x033c1): "OffHeapTrampolineRelocationInfo", - ("read_only_space", 0x033cd): "TrampolineTrivialCodeDataContainer", - ("read_only_space", 0x033d9): "TrampolinePromiseRejectionCodeDataContainer", - ("read_only_space", 0x033e5): "GlobalThisBindingScopeInfo", - ("read_only_space", 0x0341d): "EmptyFunctionScopeInfo", - ("read_only_space", 0x03445): "NativeScopeInfo", - ("read_only_space", 0x03461): "HashSeed", + ("read_only_space", 0x02771): "ArgumentsMarker", + ("read_only_space", 0x027d1): "Exception", + ("read_only_space", 0x0282d): "TerminationException", + ("read_only_space", 0x02895): "OptimizedOut", + ("read_only_space", 0x028f5): "StaleRegister", + ("read_only_space", 0x03125): "EmptyPropertyArray", + ("read_only_space", 0x0312d): "EmptyByteArray", + ("read_only_space", 0x03135): "EmptyObjectBoilerplateDescription", + ("read_only_space", 0x03169): "EmptyArrayBoilerplateDescription", + ("read_only_space", 0x03175): "EmptyClosureFeedbackCellArray", + ("read_only_space", 0x0317d): "EmptySlowElementDictionary", + ("read_only_space", 0x031a1): "EmptyOrderedHashMap", + ("read_only_space", 0x031b5): "EmptyOrderedHashSet", + ("read_only_space", 0x031c9): "EmptyFeedbackMetadata", + ("read_only_space", 0x031d5): "EmptyPropertyCell", + ("read_only_space", 0x031e9): "EmptyPropertyDictionary", + ("read_only_space", 0x03211): "EmptyOrderedPropertyDictionary", + ("read_only_space", 0x03251): "NoOpInterceptorInfo", + ("read_only_space", 0x03279): "EmptyWeakArrayList", + ("read_only_space", 0x03285): "InfinityValue", + ("read_only_space", 0x03291): "MinusZeroValue", + ("read_only_space", 0x0329d): "MinusInfinityValue", + ("read_only_space", 0x032a9): "SelfReferenceMarker", + ("read_only_space", 0x032e9): "BasicBlockCountersMarker", + ("read_only_space", 0x0332d): "OffHeapTrampolineRelocationInfo", + ("read_only_space", 0x03339): "TrampolineTrivialCodeDataContainer", + ("read_only_space", 0x03345): "TrampolinePromiseRejectionCodeDataContainer", + ("read_only_space", 0x03351): "GlobalThisBindingScopeInfo", + ("read_only_space", 0x03389): "EmptyFunctionScopeInfo", + ("read_only_space", 0x033b1): "NativeScopeInfo", + ("read_only_space", 0x033cd): "HashSeed", ("old_space", 0x02115): "ArgumentsIteratorAccessor", ("old_space", 0x02159): "ArrayLengthAccessor", ("old_space", 0x0219d): "BoundFunctionLengthAccessor", @@ -427,49 +432,49 @@ KNOWN_OBJECTS = { ("old_space", 0x023bd): "RegExpResultIndicesAccessor", ("old_space", 0x02401): "StringLengthAccessor", ("old_space", 0x02445): "InvalidPrototypeValidityCell", - ("old_space", 0x024cd): "EmptyScript", - ("old_space", 0x0250d): "ManyClosuresCell", - ("old_space", 0x02519): "ArrayConstructorProtector", - ("old_space", 0x0252d): "NoElementsProtector", - ("old_space", 0x02541): "IsConcatSpreadableProtector", - ("old_space", 0x02555): "ArraySpeciesProtector", - ("old_space", 0x02569): "TypedArraySpeciesProtector", - ("old_space", 0x0257d): "PromiseSpeciesProtector", - ("old_space", 0x02591): "RegExpSpeciesProtector", - ("old_space", 0x025a5): "StringLengthProtector", - ("old_space", 0x025b9): "ArrayIteratorProtector", - ("old_space", 0x025cd): "ArrayBufferDetachingProtector", - ("old_space", 0x025e1): "PromiseHookProtector", - ("old_space", 0x025f5): "PromiseResolveProtector", - ("old_space", 0x02609): "MapIteratorProtector", - ("old_space", 0x0261d): "PromiseThenProtector", - ("old_space", 0x02631): "SetIteratorProtector", - ("old_space", 0x02645): "StringIteratorProtector", - ("old_space", 0x02659): "SingleCharacterStringCache", - ("old_space", 0x02a61): "StringSplitCache", - ("old_space", 0x02e69): "RegExpMultipleCache", - ("old_space", 0x03271): "BuiltinsConstantsTable", - ("old_space", 0x03651): "AsyncFunctionAwaitRejectSharedFun", - ("old_space", 0x03679): "AsyncFunctionAwaitResolveSharedFun", - ("old_space", 0x036a1): "AsyncGeneratorAwaitRejectSharedFun", - ("old_space", 0x036c9): "AsyncGeneratorAwaitResolveSharedFun", - ("old_space", 0x036f1): "AsyncGeneratorYieldResolveSharedFun", - ("old_space", 0x03719): "AsyncGeneratorReturnResolveSharedFun", - ("old_space", 0x03741): "AsyncGeneratorReturnClosedRejectSharedFun", - ("old_space", 0x03769): "AsyncGeneratorReturnClosedResolveSharedFun", - ("old_space", 0x03791): "AsyncIteratorValueUnwrapSharedFun", - ("old_space", 0x037b9): "PromiseAllResolveElementSharedFun", - ("old_space", 0x037e1): "PromiseAllSettledResolveElementSharedFun", - ("old_space", 0x03809): "PromiseAllSettledRejectElementSharedFun", - ("old_space", 0x03831): "PromiseAnyRejectElementSharedFun", - ("old_space", 0x03859): "PromiseCapabilityDefaultRejectSharedFun", - ("old_space", 0x03881): "PromiseCapabilityDefaultResolveSharedFun", - ("old_space", 0x038a9): "PromiseCatchFinallySharedFun", - ("old_space", 0x038d1): "PromiseGetCapabilitiesExecutorSharedFun", - ("old_space", 0x038f9): "PromiseThenFinallySharedFun", - ("old_space", 0x03921): "PromiseThrowerFinallySharedFun", - ("old_space", 0x03949): "PromiseValueThunkFinallySharedFun", - ("old_space", 0x03971): "ProxyRevokeSharedFun", + ("old_space", 0x02531): "EmptyScript", + ("old_space", 0x02571): "ManyClosuresCell", + ("old_space", 0x0257d): "ArrayConstructorProtector", + ("old_space", 0x02591): "NoElementsProtector", + ("old_space", 0x025a5): "IsConcatSpreadableProtector", + ("old_space", 0x025b9): "ArraySpeciesProtector", + ("old_space", 0x025cd): "TypedArraySpeciesProtector", + ("old_space", 0x025e1): "PromiseSpeciesProtector", + ("old_space", 0x025f5): "RegExpSpeciesProtector", + ("old_space", 0x02609): "StringLengthProtector", + ("old_space", 0x0261d): "ArrayIteratorProtector", + ("old_space", 0x02631): "ArrayBufferDetachingProtector", + ("old_space", 0x02645): "PromiseHookProtector", + ("old_space", 0x02659): "PromiseResolveProtector", + ("old_space", 0x0266d): "MapIteratorProtector", + ("old_space", 0x02681): "PromiseThenProtector", + ("old_space", 0x02695): "SetIteratorProtector", + ("old_space", 0x026a9): "StringIteratorProtector", + ("old_space", 0x026bd): "SingleCharacterStringCache", + ("old_space", 0x02ac5): "StringSplitCache", + ("old_space", 0x02ecd): "RegExpMultipleCache", + ("old_space", 0x032d5): "BuiltinsConstantsTable", + ("old_space", 0x036bd): "AsyncFunctionAwaitRejectSharedFun", + ("old_space", 0x036e1): "AsyncFunctionAwaitResolveSharedFun", + ("old_space", 0x03705): "AsyncGeneratorAwaitRejectSharedFun", + ("old_space", 0x03729): "AsyncGeneratorAwaitResolveSharedFun", + ("old_space", 0x0374d): "AsyncGeneratorYieldResolveSharedFun", + ("old_space", 0x03771): "AsyncGeneratorReturnResolveSharedFun", + ("old_space", 0x03795): "AsyncGeneratorReturnClosedRejectSharedFun", + ("old_space", 0x037b9): "AsyncGeneratorReturnClosedResolveSharedFun", + ("old_space", 0x037dd): "AsyncIteratorValueUnwrapSharedFun", + ("old_space", 0x03801): "PromiseAllResolveElementSharedFun", + ("old_space", 0x03825): "PromiseAllSettledResolveElementSharedFun", + ("old_space", 0x03849): "PromiseAllSettledRejectElementSharedFun", + ("old_space", 0x0386d): "PromiseAnyRejectElementSharedFun", + ("old_space", 0x03891): "PromiseCapabilityDefaultRejectSharedFun", + ("old_space", 0x038b5): "PromiseCapabilityDefaultResolveSharedFun", + ("old_space", 0x038d9): "PromiseCatchFinallySharedFun", + ("old_space", 0x038fd): "PromiseGetCapabilitiesExecutorSharedFun", + ("old_space", 0x03921): "PromiseThenFinallySharedFun", + ("old_space", 0x03945): "PromiseThrowerFinallySharedFun", + ("old_space", 0x03969): "PromiseValueThunkFinallySharedFun", + ("old_space", 0x0398d): "ProxyRevokeSharedFun", } # Lower 32 bits of first page addresses for various heap spaces. diff --git a/deps/v8/tools/v8windbg/src/object-inspection.cc b/deps/v8/tools/v8windbg/src/object-inspection.cc index b206dfa792..b682f5b1b9 100644 --- a/deps/v8/tools/v8windbg/src/object-inspection.cc +++ b/deps/v8/tools/v8windbg/src/object-inspection.cc @@ -17,6 +17,7 @@ V8CachedObject::V8CachedObject(Location location, uncompressed_type_name_(std::move(uncompressed_type_name)), context_(std::move(context)), is_compressed_(is_compressed) {} + HRESULT V8CachedObject::Create(IModelObject* p_v8_object_instance, IV8CachedObject** result) { Location location; @@ -25,15 +26,16 @@ HRESULT V8CachedObject::Create(IModelObject* p_v8_object_instance, WRL::ComPtr<IDebugHostContext> context; RETURN_IF_FAIL(p_v8_object_instance->GetContext(&context)); + WRL::ComPtr<IDebugHostType> sp_type; + _bstr_t type_name; + RETURN_IF_FAIL(p_v8_object_instance->GetTypeInfo(&sp_type)); + RETURN_IF_FAIL(sp_type->GetName(type_name.GetAddress())); + // If the object is of type v8::internal::TaggedValue, and this build uses // compressed pointers, then the value is compressed. Other types such as // v8::internal::Object represent uncompressed tagged values. - WRL::ComPtr<IDebugHostType> sp_type; - _bstr_t type_name; bool is_compressed = COMPRESS_POINTERS_BOOL && - SUCCEEDED(p_v8_object_instance->GetTypeInfo(&sp_type)) && - SUCCEEDED(sp_type->GetName(type_name.GetAddress())) && static_cast<const char*>(type_name) == std::string(kTaggedValue); const char* uncompressed_type_name = @@ -44,6 +46,7 @@ HRESULT V8CachedObject::Create(IModelObject* p_v8_object_instance, .Detach(); return S_OK; } + V8CachedObject::V8CachedObject(V8HeapObject heap_object) : heap_object_(std::move(heap_object)), heap_object_initialized_(true) {} diff --git a/deps/v8/tools/v8windbg/src/v8-debug-helper-interop.cc b/deps/v8/tools/v8windbg/src/v8-debug-helper-interop.cc index 74d0a9df26..4433034237 100644 --- a/deps/v8/tools/v8windbg/src/v8-debug-helper-interop.cc +++ b/deps/v8/tools/v8windbg/src/v8-debug-helper-interop.cc @@ -16,7 +16,7 @@ namespace d = v8::debug_helper; // We need a plain C function pointer for interop with v8_debug_helper. We can // use this to get one as long as we never need two at once. -class MemReaderScope { +class V8_NODISCARD MemReaderScope { public: explicit MemReaderScope(WRL::ComPtr<IDebugHostContext> sp_context) : sp_context_(sp_context) { diff --git a/deps/v8/tools/v8windbg/test/v8windbg-test.cc b/deps/v8/tools/v8windbg/test/v8windbg-test.cc index 59414f341d..6b40af0ff1 100644 --- a/deps/v8/tools/v8windbg/test/v8windbg-test.cc +++ b/deps/v8/tools/v8windbg/test/v8windbg-test.cc @@ -20,7 +20,7 @@ namespace { // Loads a named extension library upon construction and unloads it upon // destruction. -class LoadExtensionScope { +class V8_NODISCARD LoadExtensionScope { public: LoadExtensionScope(WRL::ComPtr<IDebugControl4> p_debug_control, std::wstring extension_path) @@ -49,7 +49,7 @@ class LoadExtensionScope { }; // Initializes COM upon construction and uninitializes it upon destruction. -class ComScope { +class V8_NODISCARD ComScope { public: ComScope() { hr_ = CoInitializeEx(nullptr, COINIT_MULTITHREADED); } ~ComScope() { diff --git a/deps/v8/tools/wasm/update-wasm-fuzzers.sh b/deps/v8/tools/wasm/update-wasm-fuzzers.sh index ffd7e01633..d85b86700b 100755 --- a/deps/v8/tools/wasm/update-wasm-fuzzers.sh +++ b/deps/v8/tools/wasm/update-wasm-fuzzers.sh @@ -3,40 +3,29 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -set -e +set -ex -TOOLS_WASM_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +cd "$( dirname "${BASH_SOURCE[0]}" )" +cd ../.. -cd ${TOOLS_WASM_DIR}/../.. +BUILD_DIR=out/mk_wasm_fuzzer_corpus +CORPUS_DIR=test/fuzzer/wasm_corpus -rm -rf test/fuzzer/wasm_corpus +rm -rf $BUILD_DIR $CORPUS_DIR +mkdir -p $CORPUS_DIR -tools/dev/gm.py x64.release all +# Build optdebug such that the --dump-wasm-module flag is available. +gn gen $BUILD_DIR --args='is_debug=true v8_optimized_debug=true target_cpu="x64" use_goma=true' +autoninja -C $BUILD_DIR -mkdir -p test/fuzzer/wasm_corpus +./tools/run-tests.py --outdir=$BUILD_DIR --extra-flags="--dump-wasm-module \ + --dump-wasm-module-path=./$CORPUS_DIR/" -# wasm -./tools/run-tests.py -j8 --variants=default --timeout=10 --arch=x64 \ - --mode=release --no-presubmit --extra-flags="--dump-wasm-module \ - --dump-wasm-module-path=./test/fuzzer/wasm_corpus/" unittests -./tools/run-tests.py -j8 --variants=default --timeout=10 --arch=x64 \ - --mode=release --no-presubmit --extra-flags="--dump-wasm-module \ - --dump-wasm-module-path=./test/fuzzer/wasm_corpus/" wasm-spec-tests/* -./tools/run-tests.py -j8 --variants=default --timeout=10 --arch=x64 \ - --mode=release --no-presubmit --extra-flags="--dump-wasm-module \ - --dump-wasm-module-path=./test/fuzzer/wasm_corpus/" mjsunit/wasm/* -./tools/run-tests.py -j8 --variants=default --timeout=10 --arch=x64 \ - --mode=release --no-presubmit --extra-flags="--dump-wasm-module \ - --dump-wasm-module-path=./test/fuzzer/wasm_corpus/" \ - $(cd test/; ls cctest/wasm/test-*.cc | \ - sed -es/wasm\\///g | sed -es/[.]cc/\\/\\*/g) +rm -rf $BUILD_DIR # Delete items over 20k. -for x in $(find ./test/fuzzer/wasm_corpus/ -type f -size +20k) -do - rm $x -done +find $CORPUS_DIR -type f -size +20k | xargs rm # Upload changes. -cd test/fuzzer +cd $CORPUS_DIR/.. upload_to_google_storage.py -a -b v8-wasm-fuzzer wasm_corpus diff --git a/deps/v8/tools/whitespace.txt b/deps/v8/tools/whitespace.txt index d6024b5b7d..8fcce8dda7 100644 --- a/deps/v8/tools/whitespace.txt +++ b/deps/v8/tools/whitespace.txt @@ -8,7 +8,7 @@ The doubles heard this and started to unbox. The Smi looked at them when a crazy v8-autoroll account showed up... The autoroller bought a round of Himbeerbrause. Suddenly..... The bartender starts to shake the bottles........................ -I can't add trailing whitespaces, so I'm adding this line....... +I can't add trailing whitespaces, so I'm adding this line........... I'm starting to think that just adding trailing whitespaces might not be bad. Because whitespaces are not that funny..... diff --git a/deps/v8/tools/windows-tick-processor.bat b/deps/v8/tools/windows-tick-processor.bat index 56637e051c..ae74df9535 100755 --- a/deps/v8/tools/windows-tick-processor.bat +++ b/deps/v8/tools/windows-tick-processor.bat @@ -27,4 +27,4 @@ IF NOT %arg8:~0,2% == 8 (IF NOT %arg8:~0,2% == 8- SET log_file=%8) SET arg9=9%9 IF NOT %arg9:~0,2% == 9 (IF NOT %arg9:~0,2% == 9- SET log_file=%9) -type %log_file% | %D8_PATH%\d8 --module %tools_dir%tickprocessor-driver.js -- --windows %* +type %log_file% | %D8_PATH%\d8 --module %tools_dir%tickprocessor-driver.mjs -- --windows %* diff --git a/deps/v8/tools/wpr.wprp b/deps/v8/tools/wpr.wprp new file mode 100644 index 0000000000..a6282490a8 --- /dev/null +++ b/deps/v8/tools/wpr.wprp @@ -0,0 +1,68 @@ +<?xml version="1.0" encoding="utf-8"?> +<WindowsPerformanceRecorder Version="1.0"> + <!-- +Note: The following utilities are usually installed to: "C:\Program Files (x86)\Windows Kits\10\Windows Performance Toolkit" +See https://docs.microsoft.com/en-us/windows-hardware/test/wpt/ for an overview of the tools. + +Start and stop the trace: + + wpr -start wpr.wprp!V8js +...run scenario... + wpr -stop v8js.etl + +You can also run "wpr -status collectors details" while recording to check on status. + +Note: If you have issues with the command line, run WRPUI, and load this profile via the dialog. + +Run the below to open the trace: + + wpa v8js.etl + +Set _NT_SYMBOL_PATH to a value such as "C:\src\v8\v8\out.gn\x64.debug;srv*c:\symbols*https://msdl.microsoft.com/download/symbols" first. +Append "-symcacheonly" on the WPA command to save it trying to reload prior failed symbols on subsequent runs. + +For details on editing this file, see https://docs.microsoft.com/en-us/windows-hardware/test/wpt/authoring-recording-profiles + --> + <Profiles> + <SystemCollector Id="SystemCollector" Name="NT Kernel Logger"> + <BufferSize Value="1024"/> + <Buffers Value="384"/> + </SystemCollector> + <EventCollector Id="EventCollector_V8js" Name="V8js Event Collector"> + <BufferSize Value="1024"/> + <Buffers Value="256"/> + </EventCollector> + <SystemProvider Id="SystemProvider"> + <Keywords> + <Keyword Value="ProcessThread"/> + <Keyword Value="Loader"/> + <Keyword Value="SampledProfile"/> + <Keyword Value="ReadyThread"/> + <Keyword Value="CSwitch"/> + <Keyword Value="DiskIOInit"/> + <Keyword Value="FileIOInit"/> + <Keyword Value="HardFaults"/> + </Keywords> + <Stacks> + <!-- See https://docs.microsoft.com/en-us/windows-hardware/test/wpt/stack-wpa for options --> + <Stack Value="SampledProfile"/> + <Stack Value="ReadyThread"/> + <Stack Value="CSwitch"/> + </Stacks> + </SystemProvider> + <EventProvider Id="Provider_V8js" Name="57277741-3638-4A4B-BDBA-0AC6E45DA56C" Level="5" Stack="true"></EventProvider> + <Profile Id="V8js.Verbose.File" Name="V8js" DetailLevel="Verbose" LoggingMode="File" Description="V8.js profile"> + <Collectors> + <SystemCollectorId Value="SystemCollector"> + <SystemProviderId Value="SystemProvider"></SystemProviderId> + </SystemCollectorId> + <EventCollectorId Value="EventCollector_V8js"> + <EventProviders> + <EventProviderId Value="Provider_V8js"></EventProviderId> + </EventProviders> + </EventCollectorId> + </Collectors> + </Profile> + <Profile Id="V8js.Verbose.Memory" Base="V8js.Verbose.File" Name="V8js" DetailLevel="Verbose" LoggingMode="Memory" Description="V8.js profile"></Profile> + </Profiles> +</WindowsPerformanceRecorder> |