diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-12 14:27:29 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-13 09:35:20 +0000 |
commit | c30a6232df03e1efbd9f3b226777b07e087a1122 (patch) | |
tree | e992f45784689f373bcc38d1b79a239ebe17ee23 /chromium/v8/tools | |
parent | 7b5b123ac58f58ffde0f4f6e488bcd09aa4decd3 (diff) | |
download | qtwebengine-chromium-85-based.tar.gz |
BASELINE: Update Chromium to 85.0.4183.14085-based
Change-Id: Iaa42f4680837c57725b1344f108c0196741f6057
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/v8/tools')
74 files changed, 4082 insertions, 453 deletions
diff --git a/chromium/v8/tools/clusterfuzz/v8_commands.py b/chromium/v8/tools/clusterfuzz/v8_commands.py index 1956ef2802b..58a757e7a79 100644 --- a/chromium/v8/tools/clusterfuzz/v8_commands.py +++ b/chromium/v8/tools/clusterfuzz/v8_commands.py @@ -40,9 +40,6 @@ JS_SUPPRESSIONS = os.path.join(BASE_PATH, 'v8_suppressions.js') ARCH_MOCKS = os.path.join(BASE_PATH, 'v8_mock_archs.js') WEBASSEMBLY_MOCKS = os.path.join(BASE_PATH, 'v8_mock_webassembly.js') -# Timeout in seconds for one d8 run. -TIMEOUT = 3 - def _startup_files(options): """Default files and optional config-specific mock files.""" @@ -70,7 +67,7 @@ class Command(object): self.files = _startup_files(options) - def run(self, testcase, verbose=False): + def run(self, testcase, timeout, verbose=False): """Run the executable with a specific testcase.""" args = [self.executable] + self.flags + self.files + [testcase] if verbose: @@ -82,7 +79,7 @@ class Command(object): return Execute( args, cwd=os.path.dirname(os.path.abspath(testcase)), - timeout=TIMEOUT, + timeout=timeout, ) @property diff --git a/chromium/v8/tools/clusterfuzz/v8_foozzie.py b/chromium/v8/tools/clusterfuzz/v8_foozzie.py index b6638cc3774..b3cb8cf94fa 100755 --- a/chromium/v8/tools/clusterfuzz/v8_foozzie.py +++ b/chromium/v8/tools/clusterfuzz/v8_foozzie.py @@ -94,6 +94,10 @@ RETURN_FAIL = 2 BASE_PATH = os.path.dirname(os.path.abspath(__file__)) SANITY_CHECKS = os.path.join(BASE_PATH, 'v8_sanity_checks.js') +# Timeout for one d8 run. +SANITY_CHECK_TIMEOUT_SEC = 1 +TEST_TIMEOUT_SEC = 3 + SUPPORTED_ARCHS = ['ia32', 'x64', 'arm', 'arm64'] # Output for suppressed failure case. @@ -143,6 +147,25 @@ ORIGINAL_SOURCE_HASH_LENGTH = 3 # Placeholder string if no original source file could be determined. ORIGINAL_SOURCE_DEFAULT = 'none' +# Placeholder string for failures from crash tests. If a failure is found with +# this signature, the matching sources should be moved to the mapping below. +ORIGINAL_SOURCE_CRASHTESTS = 'placeholder for CrashTests' + +# Mapping from relative original source path (e.g. CrashTests/path/to/file.js) +# to a string key. Map to the same key for duplicate issues. The key should +# have more than 3 characters to not collide with other existing hashes. +# If a symptom from a particular original source file is known to map to a +# known failure, it can be added to this mapping. This should be done for all +# failures from CrashTests, as those by default map to the placeholder above. +KNOWN_FAILURES = { + # Foo.caller with asm.js: https://crbug.com/1042556 + 'CrashTests/5712410200899584/04483.js': '.caller', + 'CrashTests/5703451898085376/02176.js': '.caller', + 'CrashTests/4846282433495040/04342.js': '.caller', + # Flaky issue that almost never repros. + 'CrashTests/5694376231632896/1033966.js': 'flaky', +} + def infer_arch(d8): """Infer the V8 architecture from the build configuration next to the @@ -229,7 +252,7 @@ def parse_args(): options.first = first_config_arguments.make_options(options) options.second = second_config_arguments.make_options(options) - # Ensure we make a sane comparison. + # Ensure we make a valid comparison. if (options.first.d8 == options.second.d8 and options.first.config == options.second.config): parser.error('Need either executable or config difference.') @@ -292,6 +315,7 @@ def print_difference( else: first_stdout = first_config_output.stdout.decode('utf-8', 'replace') second_stdout = second_config_output.stdout.decode('utf-8', 'replace') + difference = difference.decode('utf-8', 'replace') text = (FAILURE_TEMPLATE % dict( configs='%s:%s' % (first_config_label, second_config_label), @@ -313,6 +337,31 @@ def print_difference( print(text.encode('utf-8', 'replace')) +def cluster_failures(source, known_failures=None): + """Returns a string key for clustering duplicate failures. + + Args: + source: The original source path where the failure happened. + known_failures: Mapping from original source path to failure key. + """ + known_failures = known_failures or KNOWN_FAILURES + # No source known. Typical for manually uploaded issues. This + # requires also manual issue creation. + if not source: + return ORIGINAL_SOURCE_DEFAULT + # Source is known to produce a particular failure. + if source in known_failures: + return known_failures[source] + # Subsume all other sources from CrashTests under one key. Otherwise + # failures lead to new crash tests which in turn lead to new failures. + if source.startswith('CrashTests'): + return ORIGINAL_SOURCE_CRASHTESTS + + # We map all remaining failures to a short hash of the original source. + long_key = hashlib.sha1(source.encode('utf-8')).hexdigest() + return long_key[:ORIGINAL_SOURCE_HASH_LENGTH] + + def main(): options = parse_args() @@ -342,8 +391,20 @@ def main(): # Sanity checks. Run both configurations with the sanity-checks file only and # bail out early if different. if not options.skip_sanity_checks: - first_config_output = first_cmd.run(SANITY_CHECKS) - second_config_output = second_cmd.run(SANITY_CHECKS) + first_config_output = first_cmd.run( + SANITY_CHECKS, timeout=SANITY_CHECK_TIMEOUT_SEC) + + # Early bailout if first run was a timeout. + if timeout_bailout(first_config_output, 1): + return RETURN_PASS + + second_config_output = second_cmd.run( + SANITY_CHECKS, timeout=SANITY_CHECK_TIMEOUT_SEC) + + # Bailout if second run was a timeout. + if timeout_bailout(second_config_output, 2): + return RETURN_PASS + difference, _ = suppress.diff(first_config_output, second_config_output) if difference: # Special source key for sanity checks so that clusterfuzz dedupes all @@ -354,13 +415,15 @@ def main(): first_config_output, second_config_output, difference) return RETURN_FAIL - first_config_output = first_cmd.run(options.testcase, verbose=True) + first_config_output = first_cmd.run( + options.testcase, timeout=TEST_TIMEOUT_SEC, verbose=True) # Early bailout if first run was a timeout. if timeout_bailout(first_config_output, 1): return RETURN_PASS - second_config_output = second_cmd.run(options.testcase, verbose=True) + second_config_output = second_cmd.run( + options.testcase, timeout=TEST_TIMEOUT_SEC, verbose=True) # Bailout if second run was a timeout. if timeout_bailout(second_config_output, 2): @@ -368,12 +431,6 @@ def main(): difference, source = suppress.diff(first_config_output, second_config_output) - if source: - long_key = hashlib.sha1(source.encode('utf-8')).hexdigest() - source_key = long_key[:ORIGINAL_SOURCE_HASH_LENGTH] - else: - source_key = ORIGINAL_SOURCE_DEFAULT - if difference: # Only bail out due to suppressed output if there was a difference. If a # suppression doesn't show up anymore in the statistics, we might want to @@ -383,6 +440,7 @@ def main(): if fail_bailout(second_config_output, suppress.ignore_by_output2): return RETURN_FAIL + source_key = cluster_failures(source) print_difference( options, source_key, first_cmd, second_cmd, first_config_output, second_config_output, difference, source) diff --git a/chromium/v8/tools/clusterfuzz/v8_foozzie_test.py b/chromium/v8/tools/clusterfuzz/v8_foozzie_test.py index f82afc9e205..4a5affd76ea 100755 --- a/chromium/v8/tools/clusterfuzz/v8_foozzie_test.py +++ b/chromium/v8/tools/clusterfuzz/v8_foozzie_test.py @@ -51,8 +51,8 @@ class ConfigTest(unittest.TestCase): assert all(map(lambda x: x[3] in KNOWN_BUILDS, EXPERIMENTS)) # Ensure we compare different configs and same d8, or same config # to different d8. - is_sane_comparison = lambda x: (x[1] == x[2]) == ('d8' != x[3]) - assert all(map(is_sane_comparison, EXPERIMENTS)) + is_valid_comparison = lambda x: (x[1] == x[2]) == ('d8' != x[3]) + assert all(map(is_valid_comparison, EXPERIMENTS)) # All flags have a probability. first_is_float = lambda x: type(x[0]) == float assert all(map(first_is_float, FLAGS)) @@ -101,6 +101,23 @@ class ConfigTest(unittest.TestCase): class UnitTest(unittest.TestCase): + def testCluster(self): + crash_test_example_path = 'CrashTests/path/to/file.js' + self.assertEqual( + v8_foozzie.ORIGINAL_SOURCE_DEFAULT, + v8_foozzie.cluster_failures('')) + self.assertEqual( + v8_foozzie.ORIGINAL_SOURCE_CRASHTESTS, + v8_foozzie.cluster_failures(crash_test_example_path)) + self.assertEqual( + '_o_O_', + v8_foozzie.cluster_failures( + crash_test_example_path, + known_failures={crash_test_example_path: '_o_O_'})) + self.assertEqual( + '980', + v8_foozzie.cluster_failures('v8/test/mjsunit/apply.js')) + def testDiff(self): def diff_fun(one, two, skip=False): suppress = v8_suppressions.get_suppression( diff --git a/chromium/v8/tools/clusterfuzz/v8_mock.js b/chromium/v8/tools/clusterfuzz/v8_mock.js index 6372a7afe2b..ca1336a0df6 100644 --- a/chromium/v8/tools/clusterfuzz/v8_mock.js +++ b/chromium/v8/tools/clusterfuzz/v8_mock.js @@ -149,6 +149,20 @@ Object.defineProperty( Float64Array = mock(Float64Array); })(); +// Mock buffer access via DataViews because of varying NaN patterns. +(function() { + const origIsNaN = isNaN; + const deNaNify = function(value) { return origIsNaN(value) ? 1 : value; }; + const origSetFloat32 = DataView.prototype.setFloat32; + DataView.prototype.setFloat32 = function(offset, value, ...rest) { + origSetFloat32.call(this, offset, deNaNify(value), ...rest); + }; + const origSetFloat64 = DataView.prototype.setFloat64; + DataView.prototype.setFloat64 = function(offset, value, ...rest) { + origSetFloat64.call(this, offset, deNaNify(value), ...rest); + }; +})(); + // Mock Worker. (function() { let index = 0; diff --git a/chromium/v8/tools/debug_helper/BUILD.gn b/chromium/v8/tools/debug_helper/BUILD.gn index a2d76d1fda7..522a0e22702 100644 --- a/chromium/v8/tools/debug_helper/BUILD.gn +++ b/chromium/v8/tools/debug_helper/BUILD.gn @@ -76,6 +76,7 @@ v8_component("v8_debug_helper") { "$target_gen_dir/../../torque-generated/class-debug-readers-tq.h", "$target_gen_dir/../../torque-generated/instance-types-tq.h", "$target_gen_dir/heap-constants-gen.cc", + "compiler-types.cc", "debug-helper-internal.cc", "debug-helper-internal.h", "debug-helper.h", diff --git a/chromium/v8/tools/debug_helper/compiler-types.cc b/chromium/v8/tools/debug_helper/compiler-types.cc new file mode 100644 index 00000000000..1d9f6eb0d3e --- /dev/null +++ b/chromium/v8/tools/debug_helper/compiler-types.cc @@ -0,0 +1,31 @@ +// 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. + +#include "debug-helper-internal.h" +#include "src/compiler/types.h" + +namespace ic = v8::internal::compiler; + +extern "C" { +V8_DEBUG_HELPER_EXPORT const char* _v8_debug_helper_BitsetName( + uint64_t payload) { + // Check if payload is a bitset and return the bitset type. + // This line is duplicating the logic from Type::IsBitset. + bool is_bit_set = payload & 1; + if (!is_bit_set) return nullptr; + ic::BitsetType::bitset bits = + static_cast<ic::BitsetType::bitset>(payload ^ 1u); + switch (bits) { +#define RETURN_NAMED_TYPE(type, value) \ + case ic::BitsetType::k##type: \ + return #type; + PROPER_BITSET_TYPE_LIST(RETURN_NAMED_TYPE) + INTERNAL_BITSET_TYPE_LIST(RETURN_NAMED_TYPE) +#undef RETURN_NAMED_TYPE + + default: + return nullptr; + } +} +} diff --git a/chromium/v8/tools/debug_helper/debug-helper.h b/chromium/v8/tools/debug_helper/debug-helper.h index df850e81edb..dc08bf63469 100644 --- a/chromium/v8/tools/debug_helper/debug-helper.h +++ b/chromium/v8/tools/debug_helper/debug-helper.h @@ -195,6 +195,8 @@ V8_DEBUG_HELPER_EXPORT void _v8_debug_helper_Free_ObjectPropertiesResult( v8::debug_helper::ObjectPropertiesResult* result); V8_DEBUG_HELPER_EXPORT const v8::debug_helper::ClassList* _v8_debug_helper_ListObjectClasses(); +V8_DEBUG_HELPER_EXPORT const char* _v8_debug_helper_BitsetName( + uint64_t payload); } namespace v8 { @@ -229,6 +231,12 @@ inline const ClassList* ListObjectClasses() { return _v8_debug_helper_ListObjectClasses(); } +// Return a bitset name for a v8::internal::compiler::Type with payload or null +// if the payload is not a bitset. +inline const char* BitsetName(uint64_t payload) { + return _v8_debug_helper_BitsetName(payload); +} + } // namespace debug_helper } // namespace v8 diff --git a/chromium/v8/tools/dev/gm.py b/chromium/v8/tools/dev/gm.py index 9d5cbf056a2..f1c2f1fabc9 100755 --- a/chromium/v8/tools/dev/gm.py +++ b/chromium/v8/tools/dev/gm.py @@ -10,12 +10,15 @@ Uses Goma by default if it is detected (at output directory setup time). Expects to be run from the root of a V8 checkout. Usage: - gm.py [<arch>].[<mode>[-<suffix>]].[<target>] [testname...] + gm.py [<arch>].[<mode>[-<suffix>]].[<target>] [testname...] [--flag] All arguments are optional. Most combinations should work, e.g.: gm.py ia32.debug x64.release x64.release-my-custom-opts d8 - gm.py android_arm.release.check + gm.py android_arm.release.check --progress=verbose gm.py x64 mjsunit/foo cctest/test-bar/* + +Flags are passed unchanged to the test runner. They must start with -- and must +not contain spaces. """ # See HELP below for additional documentation. # Note on Python3 compatibility: gm.py itself is Python3 compatible, but @@ -170,14 +173,14 @@ def _CallWithOutput(cmd): print("# %s" % cmd) # The following trickery is required so that the 'cmd' thinks it's running # in a real terminal, while this script gets to intercept its output. - master, slave = pty.openpty() - p = subprocess.Popen(cmd, shell=True, stdin=slave, stdout=slave, stderr=slave) - os.close(slave) + parent, child = pty.openpty() + p = subprocess.Popen(cmd, shell=True, stdin=child, stdout=child, stderr=child) + os.close(child) output = [] try: while True: try: - data = os.read(master, 512).decode('utf-8') + data = os.read(parent, 512).decode('utf-8') except OSError as e: if e.errno != errno.EIO: raise break # EIO means EOF on some systems @@ -188,7 +191,7 @@ def _CallWithOutput(cmd): sys.stdout.flush() output.append(data) finally: - os.close(master) + os.close(parent) p.wait() return p.returncode, "".join(output) @@ -224,11 +227,12 @@ def PrepareMksnapshotCmdline(orig_cmdline, path): return result class Config(object): - def __init__(self, arch, mode, targets, tests=[]): + def __init__(self, arch, mode, targets, tests=[], testrunner_args=[]): self.arch = arch self.mode = mode self.targets = set(targets) self.tests = set(tests) + self.testrunner_args = testrunner_args def Extend(self, targets, tests=[]): self.targets.update(targets) @@ -303,7 +307,9 @@ class Config(object): tests = " ".join(self.tests) return _Call('"%s" ' % sys.executable + os.path.join("tools", "run-tests.py") + - " --outdir=%s %s" % (GetPath(self.arch, self.mode), tests)) + " --outdir=%s %s %s" % ( + GetPath(self.arch, self.mode), tests, + " ".join(self.testrunner_args))) def GetTestBinary(argstring): for suite in TESTSUITES_TARGETS: @@ -316,13 +322,15 @@ class ArgumentParser(object): self.global_tests = set() self.global_actions = set() self.configs = {} + self.testrunner_args = [] def PopulateConfigs(self, arches, modes, targets, tests): for a in arches: for m in modes: path = GetPath(a, m) if path not in self.configs: - self.configs[path] = Config(a, m, targets, tests) + self.configs[path] = Config(a, m, targets, tests, + self.testrunner_args) else: self.configs[path].Extend(targets, tests) @@ -348,6 +356,10 @@ class ArgumentParser(object): # tests have names like "S15.4.4.7_A4_T1", don't split these. if argstring.startswith("unittests/") or argstring.startswith("test262/"): words = [argstring] + elif argstring.startswith("--"): + # Pass all other flags to test runner. + self.testrunner_args.append(argstring) + return else: # Assume it's a word like "x64.release" -> split at the dot. words = argstring.split('.') diff --git a/chromium/v8/tools/gcmole/BUILD.gn b/chromium/v8/tools/gcmole/BUILD.gn index fc7d81e561d..9767acbf5a1 100644 --- a/chromium/v8/tools/gcmole/BUILD.gn +++ b/chromium/v8/tools/gcmole/BUILD.gn @@ -16,6 +16,7 @@ group("v8_run_gcmole") { "parallel.py", "run-gcmole.py", "suspects.whitelist", + "ignored_files", "test-expectations.txt", # The following contains all relevant source and build files. @@ -37,9 +38,7 @@ group("v8_run_gcmole") { "$target_gen_dir/../../torque-generated/", ] - deps = [ - "../../:run_torque", - ] + deps = [ "../../:run_torque" ] if (v8_gcmole) { # This assumes gcmole tools have been fetched by a hook diff --git a/chromium/v8/tools/gcmole/gcmole-test.cc b/chromium/v8/tools/gcmole/gcmole-test.cc index e0e2801f777..92f7a9eda88 100644 --- a/chromium/v8/tools/gcmole/gcmole-test.cc +++ b/chromium/v8/tools/gcmole/gcmole-test.cc @@ -189,7 +189,7 @@ void TestGuardedDeadVarAnalysisNotOnStack(Isolate* isolate) { void TestGuardedDeadVarAnalysisNested(JSObject raw_obj, Isolate* isolate) { CauseGCRaw(raw_obj, isolate); - // Shouldn't cause warning. + // Should cause warning. raw_obj.Print(); } @@ -198,6 +198,9 @@ void TestGuardedDeadVarAnalysisCaller(Isolate* isolate) { JSObject raw_obj = *isolate->factory()->NewJSObjectWithNullProto(); TestGuardedDeadVarAnalysisNested(raw_obj, isolate); + + // Shouldn't cause warning. + raw_obj.Print(); } JSObject GuardedAllocation(Isolate* isolate) { diff --git a/chromium/v8/tools/gcmole/gcmole-tools.tar.gz.sha1 b/chromium/v8/tools/gcmole/gcmole-tools.tar.gz.sha1 index 8f7876fa519..221e2e9d293 100644 --- a/chromium/v8/tools/gcmole/gcmole-tools.tar.gz.sha1 +++ b/chromium/v8/tools/gcmole/gcmole-tools.tar.gz.sha1 @@ -1 +1 @@ -d2f949820bf1ee7343a7b5f46987a3657aaea2e9
\ No newline at end of file +0af04ef475bc746a501fe17d3b56ccb03fc151fc diff --git a/chromium/v8/tools/gcmole/gcmole.cc b/chromium/v8/tools/gcmole/gcmole.cc index 606c90bb582..cce177caf9e 100644 --- a/chromium/v8/tools/gcmole/gcmole.cc +++ b/chromium/v8/tools/gcmole/gcmole.cc @@ -46,6 +46,7 @@ namespace { bool g_tracing_enabled = false; +bool g_dead_vars_analysis = false; #define TRACE(str) \ do { \ @@ -61,12 +62,14 @@ bool g_tracing_enabled = false; } \ } while (false) -#define TRACE_LLVM_DECL(str, decl) \ - do { \ - if (g_tracing_enabled) { \ - std::cout << str << std::endl; \ - decl->dump(); \ - } \ +// Node: The following is used when tracing --dead-vars +// to provide extra info for the GC suspect. +#define TRACE_LLVM_DECL(str, decl) \ + do { \ + if (g_tracing_enabled && g_dead_vars_analysis) { \ + std::cout << str << std::endl; \ + decl->dump(); \ + } \ } while (false) typedef std::string MangledName; @@ -365,6 +368,11 @@ static bool KnownToCauseGC(clang::MangleContext* ctx, if (!InV8Namespace(decl)) return false; + if (suspects_whitelist.find(decl->getNameAsString()) != + suspects_whitelist.end()) { + return false; + } + MangledName name; if (GetMangledName(ctx, decl, &name)) { return gc_suspects.find(name) != gc_suspects.end(); @@ -387,6 +395,7 @@ static bool SuspectedToCauseGC(clang::MangleContext* ctx, } if (gc_functions.find(decl->getNameAsString()) != gc_functions.end()) { + TRACE_LLVM_DECL("Suspected by ", decl); return true; } @@ -677,8 +686,7 @@ class FunctionAnalyzer { clang::CXXRecordDecl* smi_decl, clang::CXXRecordDecl* no_gc_decl, clang::CXXRecordDecl* no_heap_access_decl, - clang::DiagnosticsEngine& d, clang::SourceManager& sm, - bool dead_vars_analysis) + clang::DiagnosticsEngine& d, clang::SourceManager& sm) : ctx_(ctx), object_decl_(object_decl), maybe_object_decl_(maybe_object_decl), @@ -687,8 +695,7 @@ class FunctionAnalyzer { no_heap_access_decl_(no_heap_access_decl), d_(d), sm_(sm), - block_(NULL), - dead_vars_analysis_(dead_vars_analysis) {} + block_(NULL) {} // -------------------------------------------------------------------------- // Expressions @@ -984,7 +991,7 @@ class FunctionAnalyzer { // pointers produces too many false positives in the dead variable // analysis. if (IsInternalPointerType(var_type) && !env.IsAlive(var_name) && - !HasActiveGuard() && dead_vars_analysis_) { + !HasActiveGuard() && g_dead_vars_analysis) { ReportUnsafe(parent, DEAD_VAR_MSG); } return ExprEffect::RawUse(); @@ -1356,15 +1363,6 @@ class FunctionAnalyzer { } bool IsInternalPointerType(clang::QualType qtype) { - // Not yet assigned pointers can't get moved by the GC. - if (qtype.isNull()) { - return false; - } - // nullptr can't get moved by the GC. - if (qtype->isNullPtrType()) { - return false; - } - const clang::CXXRecordDecl* record = qtype->getAsCXXRecordDecl(); bool result = IsDerivedFromInternalPointer(record); TRACE_LLVM_TYPE("is internal " << result, qtype); @@ -1374,6 +1372,15 @@ class FunctionAnalyzer { // Returns weather the given type is a raw pointer or a wrapper around // 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; + } + // nullptr can't get moved by the GC. + if (qtype->isNullPtrType()) { + return false; + } + const clang::PointerType* pointer_type = llvm::dyn_cast_or_null<clang::PointerType>(qtype.getTypePtrOrNull()); if (pointer_type != NULL) { @@ -1490,7 +1497,6 @@ class FunctionAnalyzer { clang::SourceManager& sm_; Block* block_; - bool dead_vars_analysis_; struct GCGuard { clang::CompoundStmt* stmt = NULL; @@ -1507,10 +1513,10 @@ class ProblemsFinder : public clang::ASTConsumer, public: ProblemsFinder(clang::DiagnosticsEngine& d, clang::SourceManager& sm, const std::vector<std::string>& args) - : d_(d), sm_(sm), dead_vars_analysis_(false) { + : d_(d), sm_(sm) { for (unsigned i = 0; i < args.size(); ++i) { if (args[i] == "--dead-vars") { - dead_vars_analysis_ = true; + g_dead_vars_analysis = true; } if (args[i] == "--verbose") { g_tracing_enabled = true; @@ -1518,7 +1524,29 @@ class ProblemsFinder : public clang::ASTConsumer, } } + bool TranslationUnitIgnored() { + if (!ignored_files_loaded_) { + std::ifstream fin("tools/gcmole/ignored_files"); + std::string s; + while (fin >> s) ignored_files_.insert(s); + ignored_files_loaded_ = true; + } + + clang::FileID main_file_id = sm_.getMainFileID(); + std::string filename = sm_.getFileEntryForID(main_file_id)->getName().str(); + + bool result = ignored_files_.find(filename) != ignored_files_.end(); + if (result) { + llvm::outs() << "Ignoring file " << filename << "\n"; + } + return result; + } + virtual void HandleTranslationUnit(clang::ASTContext &ctx) { + if (TranslationUnitIgnored()) { + return; + } + Resolver r(ctx); // It is a valid situation that no_gc_decl == NULL when the @@ -1558,10 +1586,10 @@ class ProblemsFinder : public clang::ASTConsumer, 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_heap_access_decl, d_, sm_, - dead_vars_analysis_); + function_analyzer_ = + new FunctionAnalyzer(clang::ItaniumMangleContext::create(ctx, d_), + object_decl, maybe_object_decl, smi_decl, + no_gc_decl, no_heap_access_decl, d_, sm_); TraverseDecl(ctx.getTranslationUnitDecl()); } else { if (object_decl == NULL) { @@ -1594,7 +1622,9 @@ class ProblemsFinder : public clang::ASTConsumer, private: clang::DiagnosticsEngine& d_; clang::SourceManager& sm_; - bool dead_vars_analysis_; + + bool ignored_files_loaded_ = false; + std::set<std::string> ignored_files_; FunctionAnalyzer* function_analyzer_; }; diff --git a/chromium/v8/tools/gcmole/gcmole.lua b/chromium/v8/tools/gcmole/gcmole.lua index a09c3b61ad5..95ba0433516 100644 --- a/chromium/v8/tools/gcmole/gcmole.lua +++ b/chromium/v8/tools/gcmole/gcmole.lua @@ -40,9 +40,8 @@ local FLAGS = { -- Print commands to console before executing them. verbose = false; - -- Perform dead variable analysis (generates many false positives). - -- TODO add some sort of whiteliste to filter out false positives. - dead_vars = false; + -- Perform dead variable analysis. + dead_vars = true; -- Enable verbose tracing from the plugin itself. verbose_trace = false; @@ -322,6 +321,7 @@ local WHITELIST = { -- CodeCreateEvent receives AbstractCode (a raw ptr) as an argument. "CodeCreateEvent", + "WriteField", }; local function AddCause(name, cause) @@ -477,6 +477,17 @@ local function SafeCheckCorrectnessForArch(arch, for_test) 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 @@ -491,6 +502,16 @@ local function TestRun() 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 diff --git a/chromium/v8/tools/gcmole/ignored_files b/chromium/v8/tools/gcmole/ignored_files new file mode 100644 index 00000000000..05fcd7a129e --- /dev/null +++ b/chromium/v8/tools/gcmole/ignored_files @@ -0,0 +1,2 @@ +src/profiler/heap-snapshot-generator.cc +src/execution/isolate.cc diff --git a/chromium/v8/tools/gcmole/suspects.whitelist b/chromium/v8/tools/gcmole/suspects.whitelist index 01db7401f22..1ac855f2f76 100644 --- a/chromium/v8/tools/gcmole/suspects.whitelist +++ b/chromium/v8/tools/gcmole/suspects.whitelist @@ -2,3 +2,5 @@ IsConstructor IsEval IsAsync IsPromiseAll +IsPromiseAny +VisitRootPointers diff --git a/chromium/v8/tools/gcmole/test-expectations.txt b/chromium/v8/tools/gcmole/test-expectations.txt index 82134743f6b..780cea91811 100644 --- a/chromium/v8/tools/gcmole/test-expectations.txt +++ b/chromium/v8/tools/gcmole/test-expectations.txt @@ -1,4 +1,7 @@ +tools/gcmole/gcmole-test.cc:27:10: warning: Possibly dead variable. + return obj; + ^ tools/gcmole/gcmole-test.cc:45:3: warning: Possible problem with evaluation order. TwoArgumentsFunction(*CauseGC(obj1, isolate), *CauseGC(obj2, isolate)); ^ @@ -20,4 +23,13 @@ tools/gcmole/gcmole-test.cc:130:14: warning: Possible problem with evaluation or tools/gcmole/gcmole-test.cc:151:14: warning: Possible problem with evaluation order. so_handle->Method(*SomeClass::StaticCauseGC(obj1, isolate)); ^ -7 warnings generated. +tools/gcmole/gcmole-test.cc:161:3: warning: Possibly dead variable. + raw_obj.Print(); + ^ +tools/gcmole/gcmole-test.cc:193:3: warning: Possibly dead variable. + raw_obj.Print(); + ^ +tools/gcmole/gcmole-test.cc:216:3: warning: Possibly dead variable. + raw_obj.Print(); + ^ +11 warnings generated. diff --git a/chromium/v8/tools/gen-postmortem-metadata.py b/chromium/v8/tools/gen-postmortem-metadata.py index b36cd20221a..84b9ad49905 100644 --- a/chromium/v8/tools/gen-postmortem-metadata.py +++ b/chromium/v8/tools/gen-postmortem-metadata.py @@ -223,9 +223,6 @@ consts_misc = [ 'value': 'SimpleNumberDictionaryShape::kEntrySize' }, { 'name': 'type_JSError__JS_ERROR_TYPE', 'value': 'JS_ERROR_TYPE' }, - - { 'name': 'class_SharedFunctionInfo__function_data__Object', - 'value': 'SharedFunctionInfo::kFunctionDataOffset' }, ]; # @@ -263,6 +260,7 @@ extras_accessors = [ 'ExternalString, resource, Object, kResourceOffset', 'SeqOneByteString, chars, char, kHeaderSize', 'SeqTwoByteString, chars, char, kHeaderSize', + 'UncompiledData, inferred_name, String, kInferredNameOffset', 'UncompiledData, start_position, int32_t, kStartPositionOffset', 'UncompiledData, end_position, int32_t, kEndPositionOffset', 'SharedFunctionInfo, raw_function_token_offset, int16_t, kFunctionTokenOffsetOffset', diff --git a/chromium/v8/tools/heap-stats/details-selection-template.html b/chromium/v8/tools/heap-stats/details-selection-template.html index cd429bf1a5d..9f12bde4066 100644 --- a/chromium/v8/tools/heap-stats/details-selection-template.html +++ b/chromium/v8/tools/heap-stats/details-selection-template.html @@ -86,6 +86,17 @@ found in the LICENSE file. --> width: 50px; } +.categorySelectionButtons { + float: right; +} +.categoryLabels { + float: left; + min-wdith: 200px; +} +.categoryContent { + clear: both; +} + </style> <section id="dataSelectionSection"> <h2>Data selection</h2> diff --git a/chromium/v8/tools/heap-stats/details-selection.js b/chromium/v8/tools/heap-stats/details-selection.js index f7e32733d97..7130e19f408 100644 --- a/chromium/v8/tools/heap-stats/details-selection.js +++ b/chromium/v8/tools/heap-stats/details-selection.js @@ -30,6 +30,8 @@ defineCustomElement('details-selection', (templateText) => .addEventListener('click', e => this.filterCurrentSelection(e)); this.$('#category-auto-filter-btn') .addEventListener('click', e => this.filterTop20Categories(e)); + this._data = undefined; + this.selection = undefined; } connectedCallback() { @@ -38,6 +40,14 @@ defineCustomElement('details-selection', (templateText) => } } + dataChanged() { + this.selection = {categories: {}}; + this.resetUI(true); + this.populateIsolateSelect(); + this.handleIsolateChange(); + this.$('#dataSelectionSection').style.display = 'block'; + } + set data(value) { this._data = value; this.dataChanged(); @@ -85,45 +95,60 @@ defineCustomElement('details-selection', (templateText) => const div = document.createElement('div'); div.id = name; div.classList.add('box'); - const ul = document.createElement('ul'); + + let ul = document.createElement('ul'); + ul.className = 'categoryLabels' + { + const name_li = document.createElement('li'); + name_li.textContent = CATEGORY_NAMES.get(name); + ul.appendChild(name_li); + + const percent_li = document.createElement('li'); + percent_li.textContent = '0%'; + percent_li.id = name + 'PercentContent'; + ul.appendChild(percent_li); + } + div.appendChild(ul); + + ul = document.createElement('ul'); + ul.className = 'categorySelectionButtons' + { + const all_li = document.createElement('li'); + const all_button = document.createElement('button'); + all_button.textContent = 'All'; + all_button.addEventListener('click', e => this.selectCategory(name)); + all_li.appendChild(all_button); + ul.appendChild(all_li); + + const top_li = document.createElement('li'); + const top_button = document.createElement('button'); + top_button.textContent = 'Top 10'; + top_button.addEventListener( + 'click', e => this.selectCategoryTopEntries(name)); + top_li.appendChild(top_button); + ul.appendChild(top_li); + + const none_li = document.createElement('li'); + const none_button = document.createElement('button'); + none_button.textContent = 'None'; + none_button.addEventListener('click', e => this.unselectCategory(name)); + none_li.appendChild(none_button); + ul.appendChild(none_li); + } div.appendChild(ul); - const name_li = document.createElement('li'); - ul.appendChild(name_li); - name_li.innerHTML = CATEGORY_NAMES.get(name); - const percent_li = document.createElement('li'); - ul.appendChild(percent_li); - percent_li.innerHTML = '0%'; - percent_li.id = name + 'PercentContent'; - const all_li = document.createElement('li'); - ul.appendChild(all_li); - const all_button = document.createElement('button'); - all_li.appendChild(all_button); - all_button.innerHTML = 'All'; - all_button.addEventListener('click', e => this.selectCategory(name)); - const none_li = document.createElement('li'); - ul.appendChild(none_li); - const none_button = document.createElement('button'); - none_li.appendChild(none_button); - none_button.innerHTML = 'None'; - none_button.addEventListener('click', e => this.unselectCategory(name)); + const innerDiv = document.createElement('div'); - div.appendChild(innerDiv); innerDiv.id = name + 'Content'; + innerDiv.className = 'categoryContent'; + div.appendChild(innerDiv); + const percentDiv = document.createElement('div'); - div.appendChild(percentDiv); percentDiv.className = 'percentBackground'; percentDiv.id = name + 'PercentBackground'; + div.appendChild(percentDiv); return div; } - dataChanged() { - this.selection = {categories: {}}; - this.resetUI(true); - this.populateIsolateSelect(); - this.handleIsolateChange(); - this.$('#dataSelectionSection').style.display = 'block'; - } - populateIsolateSelect() { let isolates = Object.entries(this.data); // Sorty by peak heap memory consumption. @@ -259,7 +284,7 @@ defineCustomElement('details-selection', (templateText) => }); Object.entries(overalls).forEach(([category, category_overall]) => { let percents = category_overall / overall * 100; - this.$(`#${category}PercentContent`).innerHTML = + this.$(`#${category}PercentContent`).textContent = `${percents.toFixed(1)}%`; this.$('#' + category + 'PercentBackground').style.left = percents + '%'; }); @@ -324,7 +349,7 @@ defineCustomElement('details-selection', (templateText) => populateCategories() { this.clearCategories(); - const categories = {}; + const categories = {__proto__:null}; for (let cat of CATEGORIES.keys()) { categories[cat] = []; } @@ -354,6 +379,23 @@ defineCustomElement('details-selection', (templateText) => this.notifySelectionChanged(); } + selectCategoryTopEntries(category) { + // unselect all checkboxes in this category. + this.querySelectorAll('input[name=' + category + 'Checkbox]') + .forEach(checkbox => checkbox.checked = false); + const data = this.selectedData.instance_type_data; + + // Get the max values for instance_types in this category + const categoryInstanceTypes = Array.from(CATEGORIES.get(category)); + categoryInstanceTypes.filter(each => each in data) + .sort((a,b) => { + return data[b].overall - data[a].overall; + }).slice(0, 10).forEach((category) => { + this.$('#' + category + 'Checkbox').checked = true; + }); + this.notifySelectionChanged(); + } + createCheckBox(instance_type, category) { const div = document.createElement('div'); div.classList.add('instanceTypeSelectBox'); diff --git a/chromium/v8/tools/heap-stats/global-timeline.js b/chromium/v8/tools/heap-stats/global-timeline.js index 2f16b1bdfbd..05c69f558e6 100644 --- a/chromium/v8/tools/heap-stats/global-timeline.js +++ b/chromium/v8/tools/heap-stats/global-timeline.js @@ -209,6 +209,10 @@ defineCustomElement('global-timeline', (templateText) => } drawChart() { + setTimeout(() => this._drawChart(), 10); + } + + _drawChart() { console.assert(this.data, 'invalid data'); console.assert(this.selection, 'invalid selection'); diff --git a/chromium/v8/tools/ic-explorer.html b/chromium/v8/tools/ic-explorer.html index 4c725163c5f..4ca552d6c97 100644 --- a/chromium/v8/tools/ic-explorer.html +++ b/chromium/v8/tools/ic-explorer.html @@ -339,6 +339,7 @@ code is governed by a BSD-style license that can be found in the LICENSE file. function initGroupKeySelect() { let select = document.getElementById("group-key"); + select.options.length = 0; for (let i in properties) { let option = document.createElement("option"); option.text = properties[i]; diff --git a/chromium/v8/tools/lldb_commands.py b/chromium/v8/tools/lldb_commands.py index 2884cd60b01..dc96e5747d0 100644 --- a/chromium/v8/tools/lldb_commands.py +++ b/chromium/v8/tools/lldb_commands.py @@ -21,8 +21,17 @@ def current_frame(debugger): return current_thread(debugger).GetSelectedFrame() def no_arg_cmd(debugger, cmd): - current_frame(debugger).EvaluateExpression(cmd) - print("") + evaluate_result = current_frame(debugger).EvaluateExpression(cmd) + # When a void function is called the return value type is 0x1001 which + # is specified in http://tiny.cc/bigskz. This does not indicate + # an error so we check for that value below. + kNoResult = 0x1001 + error = evaluate_result.GetError() + if error.fail and error.value != kNoResult: + print("Failed to evaluate command {} :".format(cmd)) + print(error.description) + else: + print("") def ptr_arg_cmd(debugger, name, param, cmd): if not param: @@ -41,7 +50,7 @@ def job(debugger, param, *args): def jlh(debugger, param, *args): """Print v8::Local handle value""" ptr_arg_cmd(debugger, 'jlh', param, - "_v8_internal_Print_Object(*(v8::internal::Object**)(*{}))") + "_v8_internal_Print_Object(*(v8::internal::Object**)({}.val_))") def jco(debugger, param, *args): """Print the code object at the given pc (default: current pc)""" diff --git a/chromium/v8/tools/map-processor.html b/chromium/v8/tools/map-processor.html index 77e0e7b19c7..b2bc3d57df4 100644 --- a/chromium/v8/tools/map-processor.html +++ b/chromium/v8/tools/map-processor.html @@ -369,6 +369,10 @@ dd { z-index: 100; display: none; } +#searchBarInput { + width: 200px; +} + </style> <script src="./splaytree.js"></script> <script src="./codemap.js"></script> @@ -503,8 +507,23 @@ define(Array.prototype, "histogram", function(mapFn) { return histogram; }); + // ========================================================================= // EventHandlers +function handleSearchBar(){ + let searchBar = $('searchBarInput'); + let searchBarInput = searchBar.value; + let selectedMap = V8Map.get(searchBarInput); + //removeAllChildren($('mapIdList')); + if(selectedMap){ + let map = selectedMap; + document.state.map = map; + searchBar.className = "green"; + } else { + searchBar.className = "red"; + } +} + function handleBodyLoad() { let upload = $('fileReader'); upload.onclick = (e) => $("file").click(); @@ -1253,13 +1272,23 @@ function transitionTypeToColor(type) { <section id="transitionView"></section> <br/> + + <h2>Search Map by Address</h2> + <section id="searchBar"></section> + <input type="search" id="searchBarInput" placeholder="Search maps by address.."> + <button onclick="handleSearchBar()">Search</button> + <ul id="mapIdList" title="Map Id List"> + </ul> + + <h2>Selected Map</h2> <section id="mapDetails"></section> </div> <section> <h2>Instructions</h2> - <p>Visualize Map trees that have been gathered using <code>--trace-maps</code>.</p> + <p>Visualize Map trees that have been gathered using <code>path/to/d8 $FILE --trace-maps</code>.</p> + <p>You can inspect the transition tree in DevTools by looking at <code>document.state.timeline.values</code>. <h3>Keyboard Shortcuts</h3> <dl> <dt><kbd>SHIFT</kbd> + <kbd>Arrow Up</kbd></dt> diff --git a/chromium/v8/tools/map-processor.js b/chromium/v8/tools/map-processor.js index d743cba383a..9b261c7d1b8 100644 --- a/chromium/v8/tools/map-processor.js +++ b/chromium/v8/tools/map-processor.js @@ -43,17 +43,17 @@ class MapProcessor extends LogReader { processor: this.processFunctionMove }, 'map-create': { - parsers: [parseInt, parseInt, parseString], + parsers: [parseInt, parseString], processor: this.processMapCreate }, 'map': { - parsers: [parseString, parseInt, parseInt, parseInt, parseInt, parseInt, + parsers: [parseString, parseInt, parseString, parseString, parseInt, parseInt, parseString, parseString, parseString ], processor: this.processMap }, 'map-details': { - parsers: [parseInt, parseInt, parseString], + parsers: [parseInt, parseString, parseString], processor: this.processMapDetails } }; @@ -183,19 +183,16 @@ class MapProcessor extends LogReader { this.getExistingMap(id, time).deprecate(); } - processMapCreate(time, id, string) { + processMapCreate(time, id) { // map-create events might override existing maps if the addresses get - // rcycled. Hence we do not check for existing maps. + // recycled. Hence we do not check for existing maps. let map = this.createMap(id, time); - map.description = string; } processMapDetails(time, id, string) { //TODO(cbruni): fix initial map logging. let map = this.getExistingMap(id, time); - if (!map.description) { - //map.description = string; - } + map.description = string; } createMap(id, time) { @@ -205,8 +202,8 @@ class MapProcessor extends LogReader { } getExistingMap(id, time) { - if (id === 0) return undefined; - let map = V8Map.get(id); + if (id === "0x000000000000") return undefined; + let map = V8Map.get(id, time); if (map === undefined) { console.error("No map details provided: id=" + id); // Manually patch in a map to continue running. @@ -334,18 +331,34 @@ class V8Map { return parents; } - static get(id) { - return this.cache.get(id); + + static get(id, time = undefined) { + let maps = this.cache.get(id); + if(maps){ + for (let i = 0; i < maps.length; i++) { + //TODO: Implement time based map search + if(maps[i].time === time){ + return maps[i]; + } + } + // default return the latest + return maps[maps.length-1]; + } } static set(id, map) { - this.cache.set(id, map); + if(this.cache.has(id)){ + this.cache.get(id).push(map); + } else { + this.cache.set(id, [map]); + } } } V8Map.cache = new Map(); + // =========================================================================== class Edge { constructor(type, name, reason, time, from, to) { diff --git a/chromium/v8/tools/release/common_includes.py b/chromium/v8/tools/release/common_includes.py index dbb3ba5f24b..fd69075872f 100644 --- a/chromium/v8/tools/release/common_includes.py +++ b/chromium/v8/tools/release/common_includes.py @@ -511,7 +511,7 @@ class Step(GitRecipesMixin): def WaitForLGTM(self): print ("Please wait for an LGTM, then type \"LGTM<Return>\" to commit " "your change. (If you need to iterate on the patch or double check " - "that it's sane, do so in another shell, but remember to not " + "that it's sensible, do so in another shell, but remember to not " "change the headline of the uploaded CL.") answer = "" while answer != "LGTM": diff --git a/chromium/v8/tools/run.py b/chromium/v8/tools/run.py index 5a656e19b59..59b3c15e682 100755 --- a/chromium/v8/tools/run.py +++ b/chromium/v8/tools/run.py @@ -6,7 +6,18 @@ """This program wraps an arbitrary command since gn currently can only execute scripts.""" +from __future__ import print_function + import subprocess import sys -sys.exit(subprocess.call(sys.argv[1:])) +result = subprocess.call(sys.argv[1:]) +if result != 0: + # Windows error codes such as 0xC0000005 and 0xC0000409 are much easier + # to recognize and differentiate in hex. + if result < -100: + # Print negative hex numbers as positive by adding 2^32. + print('Return code is %08X' % (result + 2**32)) + else: + print('Return code is %d' % result) +sys.exit(result) diff --git a/chromium/v8/tools/testrunner/local/statusfile.py b/chromium/v8/tools/testrunner/local/statusfile.py index f99941eb991..c8a1b307e41 100644 --- a/chromium/v8/tools/testrunner/local/statusfile.py +++ b/chromium/v8/tools/testrunner/local/statusfile.py @@ -27,12 +27,13 @@ # for py2/py3 compatibility from __future__ import print_function +from __future__ import absolute_import import os import re -from variants import ALL_VARIANTS -from utils import Freeze +from .variants import ALL_VARIANTS +from .utils import Freeze # Possible outcomes FAIL = "FAIL" diff --git a/chromium/v8/tools/testrunner/local/statusfile_unittest.py b/chromium/v8/tools/testrunner/local/statusfile_unittest.py index e8d5ff99cd1..3e2493c0ce6 100755 --- a/chromium/v8/tools/testrunner/local/statusfile_unittest.py +++ b/chromium/v8/tools/testrunner/local/statusfile_unittest.py @@ -4,10 +4,17 @@ # found in the LICENSE file. +from __future__ import absolute_import +import os +import sys import unittest -import statusfile -from utils import Freeze +TOOLS_PATH = os.path.dirname(os.path.dirname(os.path.dirname( + os.path.abspath(__file__)))) +sys.path.append(TOOLS_PATH) + +from testrunner.local import statusfile +from testrunner.local.utils import Freeze TEST_VARIABLES = { diff --git a/chromium/v8/tools/testrunner/local/testsuite.py b/chromium/v8/tools/testrunner/local/testsuite.py index 864d7346fca..a72ef4be610 100644 --- a/chromium/v8/tools/testrunner/local/testsuite.py +++ b/chromium/v8/tools/testrunner/local/testsuite.py @@ -223,7 +223,7 @@ class TestGenerator(object): return self def __next__(self): - return self.next() + return next(self) def next(self): return next(self._iterator) diff --git a/chromium/v8/tools/testrunner/local/utils.py b/chromium/v8/tools/testrunner/local/utils.py index 9128c433a04..b1c3575621c 100644 --- a/chromium/v8/tools/testrunner/local/utils.py +++ b/chromium/v8/tools/testrunner/local/utils.py @@ -35,7 +35,7 @@ import os import platform import re import subprocess -import urllib2 +import urllib ### Exit codes and their meaning. diff --git a/chromium/v8/tools/testrunner/num_fuzzer.py b/chromium/v8/tools/testrunner/num_fuzzer.py index d4e92a61e80..7777f4c66d8 100755 --- a/chromium/v8/tools/testrunner/num_fuzzer.py +++ b/chromium/v8/tools/testrunner/num_fuzzer.py @@ -5,13 +5,14 @@ # found in the LICENSE file. # for py2/py3 compatibility +from __future__ import absolute_import from __future__ import print_function import random import sys # Adds testrunner to the path hence it has to be imported at the beggining. -import base_runner +from . import base_runner from testrunner.local import utils diff --git a/chromium/v8/tools/testrunner/objects/testcase.py b/chromium/v8/tools/testrunner/objects/testcase.py index 2a75cf60c45..ac4defd2d78 100644 --- a/chromium/v8/tools/testrunner/objects/testcase.py +++ b/chromium/v8/tools/testrunner/objects/testcase.py @@ -161,6 +161,11 @@ class TestCase(object): statusfile.CRASH not in self._statusfile_outcomes) @property + def is_fail(self): + return (statusfile.FAIL in self._statusfile_outcomes and + statusfile.PASS not in self._statusfile_outcomes) + + @property def only_standard_variant(self): return statusfile.NO_VARIANTS in self._statusfile_outcomes diff --git a/chromium/v8/tools/testrunner/outproc/base.py b/chromium/v8/tools/testrunner/outproc/base.py index 9b65ca564a9..847b2242ffa 100644 --- a/chromium/v8/tools/testrunner/outproc/base.py +++ b/chromium/v8/tools/testrunner/outproc/base.py @@ -193,6 +193,8 @@ class ExpectedOutProc(OutProc): line.startswith('**') or line.startswith('ANDROID') or line.startswith('###') or + # Android linker warning. + line.startswith('WARNING: linker:') or # FIXME(machenbach): The test driver shouldn't try to use slow # asserts if they weren't compiled. This fails in optdebug=2. line == 'Warning: unknown flag --enable-slow-asserts.' or diff --git a/chromium/v8/tools/testrunner/outproc/message.py b/chromium/v8/tools/testrunner/outproc/message.py index f196cfd614b..c253b6f8e06 100644 --- a/chromium/v8/tools/testrunner/outproc/message.py +++ b/chromium/v8/tools/testrunner/outproc/message.py @@ -59,5 +59,7 @@ class OutProc(base.OutProc): not string.strip() or string.startswith("==") or string.startswith("**") or - string.startswith("ANDROID") + string.startswith("ANDROID") or + # Android linker warning. + string.startswith('WARNING: linker:') ) diff --git a/chromium/v8/tools/testrunner/standard_runner.py b/chromium/v8/tools/testrunner/standard_runner.py index 10545fa5f24..9790e463437 100755 --- a/chromium/v8/tools/testrunner/standard_runner.py +++ b/chromium/v8/tools/testrunner/standard_runner.py @@ -5,6 +5,7 @@ # found in the LICENSE file. # for py2/py3 compatibility +from __future__ import absolute_import from __future__ import print_function from functools import reduce @@ -15,7 +16,7 @@ import sys import tempfile # Adds testrunner to the path hence it has to be imported at the beggining. -import base_runner +from . import base_runner from testrunner.local import utils from testrunner.local.variants import ALL_VARIANTS diff --git a/chromium/v8/tools/testrunner/testproc/progress.py b/chromium/v8/tools/testrunner/testproc/progress.py index 671eebb9222..7a47c1b692a 100644 --- a/chromium/v8/tools/testrunner/testproc/progress.py +++ b/chromium/v8/tools/testrunner/testproc/progress.py @@ -4,6 +4,7 @@ # for py2/py3 compatibility from __future__ import print_function +from __future__ import absolute_import import datetime import json @@ -12,7 +13,7 @@ import platform import subprocess import sys import time -import util +from . import util from . import base @@ -243,15 +244,22 @@ class CompactProgressIndicator(ProgressIndicator): self._clear_line(self._last_status_length) print_failure_header(test) if len(stdout): - print(self._templates['stdout'] % stdout) + self.printFormatted('stdout', stdout) if len(stderr): - print(self._templates['stderr'] % stderr) - print("Command: %s" % result.cmd.to_string(relative=True)) + self.printFormatted('stderr', stderr) + self.printFormatted( + 'command', "Command: %s" % result.cmd.to_string(relative=True)) if output.HasCrashed(): - print("exit code: %s" % output.exit_code_string) - print("--- CRASHED ---") - if output.HasTimedOut(): - print("--- TIMEOUT ---") + self.printFormatted( + 'failure', "exit code: %s" % output.exit_code_string) + self.printFormatted('failure', "--- CRASHED ---") + elif output.HasTimedOut(): + self.printFormatted('failure', "--- TIMEOUT ---") + else: + if test.is_fail: + self.printFormatted('failure', "--- UNEXPECTED PASS ---") + else: + self.printFormatted('failure', "--- FAILED ---") def finished(self): self._print_progress('Done') @@ -272,12 +280,12 @@ class CompactProgressIndicator(ProgressIndicator): 'mins': int(elapsed) // 60, 'secs': int(elapsed) % 60 } - status = self._truncate(status, 78) + status = self._truncateStatusLine(status, 78) self._last_status_length = len(status) print(status, end='') sys.stdout.flush() - def _truncate(self, string, length): + def _truncateStatusLine(self, string, length): if length and len(string) > (length - 3): return string[:(length - 3)] + "..." else: @@ -296,22 +304,33 @@ class ColorProgressIndicator(CompactProgressIndicator): "\033[31m-%(failed) 4d\033[0m]: %(test)s"), 'stdout': "\033[1m%s\033[0m", 'stderr': "\033[31m%s\033[0m", + 'failure': "\033[1;31m%s\033[0m", + 'command': "\033[33m%s\033[0m", } super(ColorProgressIndicator, self).__init__(templates) + def printFormatted(self, format, string): + print(self._templates[format] % string) + + def _truncateStatusLine(self, string, length): + # Add some slack for the color control chars + return super(ColorProgressIndicator, self)._truncateStatusLine( + string, length + 3*9) + def _clear_line(self, last_length): print("\033[1K\r", end='') class MonochromeProgressIndicator(CompactProgressIndicator): def __init__(self): - templates = { - 'status_line': ("[%(mins)02i:%(secs)02i|%%%(progress) 4d|" - "+%(passed) 4d|-%(failed) 4d]: %(test)s"), - 'stdout': '%s', - 'stderr': '%s', - } - super(MonochromeProgressIndicator, self).__init__(templates) + templates = { + 'status_line': ("[%(mins)02i:%(secs)02i|%%%(progress) 4d|" + "+%(passed) 4d|-%(failed) 4d]: %(test)s"), + } + super(MonochromeProgressIndicator, self).__init__(templates) + + def printFormatted(self, format, string): + print(string) def _clear_line(self, last_length): print(("\r" + (" " * last_length) + "\r"), end='') diff --git a/chromium/v8/tools/testrunner/testproc/timeout.py b/chromium/v8/tools/testrunner/testproc/timeout.py index 9a4e88c8f05..026ba02cd97 100644 --- a/chromium/v8/tools/testrunner/testproc/timeout.py +++ b/chromium/v8/tools/testrunner/testproc/timeout.py @@ -1,3 +1,4 @@ +from __future__ import print_function # Copyright 2018 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. diff --git a/chromium/v8/tools/testrunner/testproc/util_unittest.py b/chromium/v8/tools/testrunner/testproc/util_unittest.py index 243bf9789a7..5bf6a6e79ad 100644 --- a/chromium/v8/tools/testrunner/testproc/util_unittest.py +++ b/chromium/v8/tools/testrunner/testproc/util_unittest.py @@ -3,9 +3,18 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -from util import FixedSizeTopList +from __future__ import absolute_import + +import os +import sys import unittest +TOOLS_PATH = os.path.dirname(os.path.dirname(os.path.dirname( + os.path.abspath(__file__)))) +sys.path.append(TOOLS_PATH) + +from testrunner.testproc.util import FixedSizeTopList + class TestOrderedFixedSizeList(unittest.TestCase): def test_empty(self): ofsl = FixedSizeTopList(3) diff --git a/chromium/v8/tools/turbolizer/README.md b/chromium/v8/tools/turbolizer/README.md index c5ee729d64d..fa804f65e94 100644 --- a/chromium/v8/tools/turbolizer/README.md +++ b/chromium/v8/tools/turbolizer/README.md @@ -74,7 +74,6 @@ well as '--cpu' to specify which CPU to sample. Turbolizer build process ------------------------ -Turbolizer is currently migrating to TypeScript. The typescript sources reside in -tools/turbolizer/src, and the typescript compiler will put the JavaScript output -into tools/turbolizer/build/. The index.html file is set up to load the JavaScript -from that directory. +The typescript sources reside in tools/turbolizer/src, and the typescript +compiler will put the JavaScript output into tools/turbolizer/build/. The +index.html file is set up to load the JavaScript from that directory. diff --git a/chromium/v8/tools/turbolizer/down-arrow.png b/chromium/v8/tools/turbolizer/down-arrow.png Binary files differnew file mode 100644 index 00000000000..39339f289a3 --- /dev/null +++ b/chromium/v8/tools/turbolizer/down-arrow.png diff --git a/chromium/v8/tools/turbolizer/index.html b/chromium/v8/tools/turbolizer/index.html index 268e51e0200..ea1b0b74d27 100644 --- a/chromium/v8/tools/turbolizer/index.html +++ b/chromium/v8/tools/turbolizer/index.html @@ -8,6 +8,7 @@ code is governed by a BSD-style license that can be found in the LICENSE file. <meta charset="utf-8"> <title>V8 Turbolizer</title> <link rel="stylesheet" href="turbo-visualizer.css"> + <link rel="stylesheet" href="turbo-visualizer-ranges.css"> <link rel="stylesheet" href="tabs.css"> <link rel="icon" type="image/png" href="turbolizer.png"> </head> @@ -21,6 +22,12 @@ code is governed by a BSD-style license that can be found in the LICENSE file. <input id="upload-helper" type="file"> <input id="upload" type="image" title="load graph" class="button-input" src="upload-icon.png" alt="upload graph"> </div> + <div id="resizer-ranges" class="resizer" style="visibility:hidden;"></div> + <div id="ranges" class="content" style="visibility:hidden;"></div> + <div id="show-hide-ranges" class="show-hide-pane" style="visibility: hidden"> + <input id="ranges-expand" type="image" title="show ranges" src="up-arrow.png" class="button-input invisible"> + <input id="ranges-shrink" type="image" title="hide ranges" src="down-arrow.png" class="button-input"> + </div> </div> <div id="resizer-right" class="resizer"></div> <div id="right" class="content"></div> diff --git a/chromium/v8/tools/turbolizer/src/constants.ts b/chromium/v8/tools/turbolizer/src/constants.ts index ada39ae6b3b..47dee8547ff 100644 --- a/chromium/v8/tools/turbolizer/src/constants.ts +++ b/chromium/v8/tools/turbolizer/src/constants.ts @@ -14,6 +14,9 @@ export const GENERATED_PANE_ID = 'right'; export const DISASSEMBLY_PANE_ID = 'disassembly'; export const DISASSEMBLY_COLLAPSE_ID = 'disassembly-shrink'; export const DISASSEMBLY_EXPAND_ID = 'disassembly-expand'; +export const RANGES_PANE_ID = "ranges"; +export const RANGES_COLLAPSE_ID = "ranges-shrink"; +export const RANGES_EXPAND_ID = "ranges-expand"; export const UNICODE_BLOCK = '▋'; export const PROF_COLS = [ { perc: 0, col: { r: 255, g: 255, b: 255 } }, diff --git a/chromium/v8/tools/turbolizer/src/graphmultiview.ts b/chromium/v8/tools/turbolizer/src/graphmultiview.ts index 380f7df77db..4f8f6339199 100644 --- a/chromium/v8/tools/turbolizer/src/graphmultiview.ts +++ b/chromium/v8/tools/turbolizer/src/graphmultiview.ts @@ -38,6 +38,11 @@ export class GraphMultiView extends View { return pane; } + hide() { + this.hideCurrentPhase(); + super.hide(); + } + constructor(id, selectionBroker, sourceResolver) { super(id); const view = this; @@ -86,7 +91,9 @@ export class GraphMultiView extends View { } show() { - super.show(); + // Insert before is used so that the display is inserted before the + // resizer for the RangeView. + this.container.insertBefore(this.divNode, this.container.firstChild); this.initializeSelect(); const lastPhaseIndex = +window.sessionStorage.getItem("lastSelectedPhase"); const initialPhaseIndex = this.sourceResolver.repairPhaseId(lastPhaseIndex); diff --git a/chromium/v8/tools/turbolizer/src/range-view.ts b/chromium/v8/tools/turbolizer/src/range-view.ts new file mode 100644 index 00000000000..17058e4f3b2 --- /dev/null +++ b/chromium/v8/tools/turbolizer/src/range-view.ts @@ -0,0 +1,938 @@ +// 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 { createElement } from "../src/util"; +import { SequenceView } from "../src/sequence-view"; +import { RegisterAllocation, Range, ChildRange, Interval } from "../src/source-resolver"; + +class Constants { + // Determines how many rows each div group holds for the purposes of + // hiding by syncHidden. + static readonly ROW_GROUP_SIZE = 20; + static readonly POSITIONS_PER_INSTRUCTION = 4; + static readonly FIXED_REGISTER_LABEL_WIDTH = 6; + + static readonly INTERVAL_TEXT_FOR_NONE = "none"; + static readonly INTERVAL_TEXT_FOR_CONST = "const"; + static readonly INTERVAL_TEXT_FOR_STACK = "stack:"; +} + +// This class holds references to the HTMLElements that represent each cell. +class Grid { + elements: Array<Array<HTMLElement>>; + + constructor() { + this.elements = []; + } + + setRow(row: number, elementsRow: Array<HTMLElement>) { + this.elements[row] = elementsRow; + } + + getCell(row: number, column: number) { + return this.elements[row][column]; + } + + getInterval(row: number, column: number) { + // The cell is within an inner wrapper div which is within the interval div. + return this.getCell(row, column).parentElement.parentElement; + } +} + +// This class is used as a wrapper to hide the switch between the +// two different Grid objects used, one for each phase, +// before and after register allocation. +class GridAccessor { + sequenceView: SequenceView; + grids: Map<number, Grid>; + + constructor(sequenceView: SequenceView) { + this.sequenceView = sequenceView; + this.grids = new Map<number, Grid>(); + } + + private currentGrid() { + return this.grids.get(this.sequenceView.currentPhaseIndex); + } + + getAnyGrid() { + return this.grids.values().next().value; + } + + hasGrid() { + return this.grids.has(this.sequenceView.currentPhaseIndex); + } + + addGrid(grid: Grid) { + if (this.hasGrid()) console.warn("Overwriting existing Grid."); + this.grids.set(this.sequenceView.currentPhaseIndex, grid); + } + + getCell(row: number, column: number) { + return this.currentGrid().getCell(row, column); + } + + getInterval(row: number, column: number) { + return this.currentGrid().getInterval(row, column); + } +} + +// This class is used as a wrapper to access the interval HTMLElements +class IntervalElementsAccessor { + sequenceView: SequenceView; + map: Map<number, Array<HTMLElement>>; + + constructor(sequenceView: SequenceView) { + this.sequenceView = sequenceView; + this.map = new Map<number, Array<HTMLElement>>(); + } + + private currentIntervals() { + const intervals = this.map.get(this.sequenceView.currentPhaseIndex); + if (intervals == undefined) { + this.map.set(this.sequenceView.currentPhaseIndex, new Array<HTMLElement>()); + return this.currentIntervals(); + } + return intervals; + } + + addInterval(interval: HTMLElement) { + this.currentIntervals().push(interval); + } + + forEachInterval(callback: (phase: number, interval: HTMLElement) => void) { + for (const phase of this.map.keys()) { + for (const interval of this.map.get(phase)) { + callback(phase, interval); + } + } + } +} + +// A simple class used to hold two Range objects. This is used to allow the two fixed register live +// ranges of normal and deferred to be easily combined into a single row. +class RangePair { + ranges: [Range, Range]; + + constructor(ranges: [Range, Range]) { + this.ranges = ranges; + } + + forEachRange(callback: (range: Range) => void) { + this.ranges.forEach((range: Range) => { if (range) callback(range); }); + } +} + +// A number of css variables regarding dimensions of HTMLElements are required by RangeView. +class CSSVariables { + positionWidth: number; + blockBorderWidth: number; + + constructor() { + const getNumberValue = varName => { + return parseFloat(getComputedStyle(document.body) + .getPropertyValue(varName).match(/[+-]?\d+(\.\d+)?/g)[0]); + }; + this.positionWidth = getNumberValue("--range-position-width"); + this.blockBorderWidth = getNumberValue("--range-block-border"); + } +} + +// Store the required data from the blocks JSON. +class BlocksData { + blockBorders: Set<number>; + blockInstructionCountMap: Map<number, number>; + + constructor(blocks: Array<any>) { + this.blockBorders = new Set<number>(); + this.blockInstructionCountMap = new Map<number, number>(); + for (const block of blocks) { + this.blockInstructionCountMap.set(block.id, block.instructions.length); + const maxInstructionInBlock = block.instructions[block.instructions.length - 1].id; + this.blockBorders.add(maxInstructionInBlock); + } + } + + isInstructionBorder(position: number) { + return ((position + 1) % Constants.POSITIONS_PER_INSTRUCTION) == 0; + } + + isBlockBorder(position: number) { + return this.isInstructionBorder(position) + && this.blockBorders.has(Math.floor(position / Constants.POSITIONS_PER_INSTRUCTION)); + } +} + +class Divs { + // Already existing. + container: HTMLElement; + resizerBar: HTMLElement; + snapper: HTMLElement; + + // Created by constructor. + content: HTMLElement; + // showOnLoad contains all content that may change depending on the JSON. + showOnLoad: HTMLElement; + xAxisLabel: HTMLElement; + yAxisLabel: HTMLElement; + registerHeaders: HTMLElement; + registers: HTMLElement; + + // Assigned from RangeView. + wholeHeader: HTMLElement; + positionHeaders: HTMLElement; + yAxis: HTMLElement; + grid: HTMLElement; + + constructor() { + this.container = document.getElementById("ranges"); + this.resizerBar = document.getElementById("resizer-ranges"); + this.snapper = document.getElementById("show-hide-ranges"); + + this.content = document.createElement("div"); + this.content.appendChild(this.elementForTitle()); + + this.showOnLoad = document.createElement("div"); + this.showOnLoad.style.visibility = "hidden"; + this.content.appendChild(this.showOnLoad); + + this.xAxisLabel = createElement("div", "range-header-label-x"); + this.xAxisLabel.innerText = "Blocks, Instructions, and Positions"; + this.showOnLoad.appendChild(this.xAxisLabel); + this.yAxisLabel = createElement("div", "range-header-label-y"); + this.yAxisLabel.innerText = "Registers"; + this.showOnLoad.appendChild(this.yAxisLabel); + + this.registerHeaders = createElement("div", "range-register-labels"); + this.registers = createElement("div", "range-registers"); + this.registerHeaders.appendChild(this.registers); + } + + elementForTitle() { + const titleEl = createElement("div", "range-title-div"); + const titleBar = createElement("div", "range-title"); + titleBar.appendChild(createElement("div", "", "Live Ranges")); + const titleHelp = createElement("div", "range-title-help", "?"); + titleHelp.title = "Each row represents a single TopLevelLiveRange (or two if deferred exists)." + + "\nEach interval belongs to a LiveRange contained within that row's TopLevelLiveRange." + + "\nAn interval is identified by i, the index of the LiveRange within the TopLevelLiveRange," + + "\nand j, the index of the interval within the LiveRange, to give i:j."; + titleEl.appendChild(titleBar); + titleEl.appendChild(titleHelp); + return titleEl; + } +} + +class Helper { + static virtualRegisterName(registerIndex: string) { + return "v" + registerIndex; + } + + static fixedRegisterName(range: Range) { + return range.child_ranges[0].op.text; + } + + static getPositionElementsFromInterval(interval: HTMLElement) { + return interval.children[1].children; + } + + static forEachFixedRange(source: RegisterAllocation, row: number, + callback: (registerIndex: string, row: number, registerName: string, + ranges: RangePair) => void) { + + const forEachRangeInMap = (rangeMap: Map<string, Range>) => { + // There are two fixed live ranges for each register, one for normal, another for deferred. + // These are combined into a single row. + const fixedRegisterMap = new Map<string, {ranges: [Range, Range], registerIndex: number}>(); + for (const [registerIndex, range] of rangeMap) { + const registerName = this.fixedRegisterName(range); + if (fixedRegisterMap.has(registerName)) { + const entry = fixedRegisterMap.get(registerName); + entry.ranges[1] = range; + // Only use the deferred register index if no normal index exists. + if (!range.is_deferred) { + entry.registerIndex = parseInt(registerIndex, 10); + } + } else { + fixedRegisterMap.set(registerName, {ranges: [range, undefined], + registerIndex: parseInt(registerIndex, 10)}); + } + } + // Sort the registers by number. + const sortedMap = new Map([...fixedRegisterMap.entries()].sort(([nameA, _], [nameB, __]) => { + // Larger numbers create longer strings. + if (nameA.length > nameB.length) return 1; + if (nameA.length < nameB.length) return -1; + // Sort lexicographically if same length. + if (nameA > nameB) return 1; + if (nameA < nameB) return -1; + return 0; + })); + for (const [registerName, {ranges, registerIndex}] of sortedMap) { + callback("" + (-registerIndex - 1), row, registerName, new RangePair(ranges)); + ++row; + } + }; + + forEachRangeInMap(source.fixedLiveRanges); + forEachRangeInMap(source.fixedDoubleLiveRanges); + + return row; + } +} + +class RowConstructor { + view: RangeView; + + constructor(view: RangeView) { + this.view = view; + } + + // Constructs the row of HTMLElements for grid while providing a callback for each position + // depending on whether that position is the start of an interval or not. + // RangePair is used to allow the two fixed register live ranges of normal and deferred to be + // easily combined into a single row. + construct(grid: Grid, row: number, registerIndex: string, ranges: RangePair, + getElementForEmptyPosition: (position: number) => HTMLElement, + callbackForInterval: (position: number, interval: HTMLElement) => void) { + const positionArray = new Array<HTMLElement>(this.view.numPositions); + // Construct all of the new intervals. + const intervalMap = this.elementsForIntervals(registerIndex, ranges); + for (let position = 0; position < this.view.numPositions; ++position) { + const interval = intervalMap.get(position); + if (interval == undefined) { + positionArray[position] = getElementForEmptyPosition(position); + } else { + callbackForInterval(position, interval); + this.view.intervalsAccessor.addInterval(interval); + const intervalPositionElements = Helper.getPositionElementsFromInterval(interval); + for (let j = 0; j < intervalPositionElements.length; ++j) { + // Point positionsArray to the new elements. + positionArray[position + j] = (intervalPositionElements[j] as HTMLElement); + } + position += intervalPositionElements.length - 1; + } + } + grid.setRow(row, positionArray); + ranges.forEachRange((range: Range) => this.setUses(grid, row, range)); + } + + // This is the main function used to build new intervals. + // Returns a map of LifeTimePositions to intervals. + private elementsForIntervals(registerIndex: string, ranges: RangePair) { + const intervalMap = new Map<number, HTMLElement>(); + let tooltip = ""; + ranges.forEachRange((range: Range) => { + for (const childRange of range.child_ranges) { + switch (childRange.type) { + case "none": + tooltip = Constants.INTERVAL_TEXT_FOR_NONE; + break; + case "spill_range": + tooltip = Constants.INTERVAL_TEXT_FOR_STACK + registerIndex; + break; + default: + if (childRange.op.type == "constant") { + tooltip = Constants.INTERVAL_TEXT_FOR_CONST; + } else { + if (childRange.op.text) { + tooltip = childRange.op.text; + } else { + tooltip = childRange.op; + } + } + break; + } + childRange.intervals.forEach((intervalNums, index) => { + const interval = new Interval(intervalNums); + const intervalEl = this.elementForInterval(childRange, interval, tooltip, + index, range.is_deferred); + intervalMap.set(interval.start, intervalEl); + }); + } + }); + return intervalMap; + } + + private elementForInterval(childRange: ChildRange, interval: Interval, + tooltip: string, index: number, isDeferred: boolean): HTMLElement { + const intervalEl = createElement("div", "range-interval"); + const title = childRange.id + ":" + index + " " + tooltip; + intervalEl.setAttribute("title", isDeferred ? "deferred: " + title : title); + this.setIntervalColor(intervalEl, tooltip); + const intervalInnerWrapper = createElement("div", "range-interval-wrapper"); + intervalEl.style.gridColumn = (interval.start + 1) + " / " + (interval.end + 1); + intervalInnerWrapper.style.gridTemplateColumns = "repeat(" + (interval.end - interval.start) + + ",calc(" + this.view.cssVariables.positionWidth + "ch + " + + this.view.cssVariables.blockBorderWidth + "px)"; + const intervalTextEl = this.elementForIntervalString(tooltip, interval.end - interval.start); + intervalEl.appendChild(intervalTextEl); + for (let i = interval.start; i < interval.end; ++i) { + const classes = "range-position range-interval-position range-empty" + + (this.view.blocksData.isBlockBorder(i) ? " range-block-border" : + this.view.blocksData.isInstructionBorder(i) ? " range-instr-border" : ""); + const positionEl = createElement("div", classes, "_"); + positionEl.style.gridColumn = (i - interval.start + 1) + ""; + intervalInnerWrapper.appendChild(positionEl); + } + intervalEl.appendChild(intervalInnerWrapper); + return intervalEl; + } + + private setIntervalColor(interval: HTMLElement, tooltip: string) { + if (tooltip.includes(Constants.INTERVAL_TEXT_FOR_NONE)) return; + if (tooltip.includes(Constants.INTERVAL_TEXT_FOR_STACK + "-")) { + interval.style.backgroundColor = "rgb(250, 158, 168)"; + } else if (tooltip.includes(Constants.INTERVAL_TEXT_FOR_STACK)) { + interval.style.backgroundColor = "rgb(250, 158, 100)"; + } else if (tooltip.includes(Constants.INTERVAL_TEXT_FOR_CONST)) { + interval.style.backgroundColor = "rgb(153, 158, 230)"; + } else { + interval.style.backgroundColor = "rgb(153, 220, 168)"; + } + } + + private elementForIntervalString(tooltip: string, numCells: number) { + const spanEl = createElement("span", "range-interval-text"); + this.setIntervalString(spanEl, tooltip, numCells); + return spanEl; + } + + // Each interval displays a string of information about it. + private setIntervalString(spanEl: HTMLElement, tooltip: string, numCells: number) { + const spacePerCell = this.view.cssVariables.positionWidth; + // One character space is removed to accommodate for padding. + const spaceAvailable = (numCells * spacePerCell) - 0.5; + let str = tooltip + ""; + const length = tooltip.length; + spanEl.style.width = null; + let paddingLeft = null; + // Add padding if possible + if (length <= spaceAvailable) { + paddingLeft = (length == spaceAvailable) ? "0.5ch" : "1ch"; + } else { + str = ""; + } + spanEl.style.paddingTop = null; + spanEl.style.paddingLeft = paddingLeft; + spanEl.innerHTML = str; + } + + private setUses(grid: Grid, row: number, range: Range) { + for (const liveRange of range.child_ranges) { + if (liveRange.uses) { + for (const use of liveRange.uses) { + grid.getCell(row, use).classList.toggle("range-use", true); + } + } + } + } +} + +class RangeViewConstructor { + view: RangeView; + gridTemplateColumns: string; + grid: Grid; + + // Group the rows in divs to make hiding/showing divs more efficient. + currentGroup: HTMLElement; + currentPlaceholderGroup: HTMLElement; + + constructor(rangeView: RangeView) { + this.view = rangeView; + } + + construct() { + this.gridTemplateColumns = "repeat(" + this.view.numPositions + + ",calc(" + this.view.cssVariables.positionWidth + "ch + " + + this.view.cssVariables.blockBorderWidth + "px)"; + + this.grid = new Grid(); + this.view.gridAccessor.addGrid(this.grid); + + this.view.divs.wholeHeader = this.elementForHeader(); + this.view.divs.showOnLoad.appendChild(this.view.divs.wholeHeader); + + const gridContainer = document.createElement("div"); + this.view.divs.grid = this.elementForGrid(); + this.view.divs.yAxis = createElement("div", "range-y-axis"); + this.view.divs.yAxis.appendChild(this.view.divs.registerHeaders); + this.view.divs.yAxis.onscroll = () => { + this.view.scrollHandler.syncScroll(ToSync.TOP, this.view.divs.yAxis, this.view.divs.grid); + this.view.scrollHandler.saveScroll(); + }; + gridContainer.appendChild(this.view.divs.yAxis); + gridContainer.appendChild(this.view.divs.grid); + this.view.divs.showOnLoad.appendChild(gridContainer); + + this.resetGroups(); + let row = 0; + row = this.addVirtualRanges(row); + this.addFixedRanges(row); + } + + // The following three functions are for constructing the groups which the rows are contained + // within and which make up the grid. This is so as to allow groups of rows to easily be displayed + // and hidden for performance reasons. As rows are constructed, they are added to the currentGroup + // div. Each row in currentGroup is matched with an equivalent placeholder row in + // currentPlaceholderGroup that will be shown when currentGroup is hidden so as to maintain the + // dimensions and scroll positions of the grid. + + private resetGroups () { + this.currentGroup = createElement("div", "range-positions-group range-hidden"); + this.currentPlaceholderGroup = createElement("div", "range-positions-group"); + } + + private appendGroupsToGrid() { + this.view.divs.grid.appendChild(this.currentPlaceholderGroup); + this.view.divs.grid.appendChild(this.currentGroup); + } + + private addRowToGroup(row: number, rowEl: HTMLElement) { + this.currentGroup.appendChild(rowEl); + this.currentPlaceholderGroup + .appendChild(createElement("div", "range-positions range-positions-placeholder", "_")); + if ((row + 1) % Constants.ROW_GROUP_SIZE == 0) { + this.appendGroupsToGrid(); + this.resetGroups(); + } + } + + private addVirtualRanges(row: number) { + const source = this.view.sequenceView.sequence.register_allocation; + for (const [registerIndex, range] of source.liveRanges) { + const registerName = Helper.virtualRegisterName(registerIndex); + const registerEl = this.elementForVirtualRegister(registerName); + this.addRowToGroup(row, this.elementForRow(row, registerIndex, + new RangePair([range, undefined]))); + this.view.divs.registers.appendChild(registerEl); + ++row; + } + return row; + } + + private addFixedRanges(row: number) { + row = Helper.forEachFixedRange(this.view.sequenceView.sequence.register_allocation, row, + (registerIndex: string, row: number, + registerName: string, ranges: RangePair) => { + const registerEl = this.elementForFixedRegister(registerName); + this.addRowToGroup(row, this.elementForRow(row, registerIndex, ranges)); + this.view.divs.registers.appendChild(registerEl); + }); + if (row % Constants.ROW_GROUP_SIZE != 0) { + this.appendGroupsToGrid(); + } + } + + // Each row of positions and intervals associated with a register is contained in a single + // HTMLElement. RangePair is used to allow the two fixed register live ranges of normal and + // deferred to be easily combined into a single row. + private elementForRow(row: number, registerIndex: string, ranges: RangePair) { + const rowEl = createElement("div", "range-positions"); + rowEl.style.gridTemplateColumns = this.gridTemplateColumns; + + const getElementForEmptyPosition = (position: number) => { + const blockBorder = this.view.blocksData.isBlockBorder(position); + const classes = "range-position range-empty " + + (blockBorder ? "range-block-border" : + this.view.blocksData.isInstructionBorder(position) ? "range-instr-border" + : "range-position-border"); + const positionEl = createElement("div", classes, "_"); + positionEl.style.gridColumn = (position + 1) + ""; + rowEl.appendChild(positionEl); + return positionEl; + }; + + const callbackForInterval = (_, interval: HTMLElement) => { + rowEl.appendChild(interval); + }; + + this.view.rowConstructor.construct(this.grid, row, registerIndex, ranges, + getElementForEmptyPosition, callbackForInterval); + return rowEl; + } + + private elementForVirtualRegister(registerName: string) { + const regEl = createElement("div", "range-reg", registerName); + regEl.setAttribute("title", registerName); + return regEl; + } + + private elementForFixedRegister(registerName: string) { + let text = registerName; + const span = "".padEnd(Constants.FIXED_REGISTER_LABEL_WIDTH - text.length, "_"); + text = "HW - <span class='range-transparent'>" + span + "</span>" + text; + const regEl = createElement("div", "range-reg"); + regEl.innerHTML = text; + regEl.setAttribute("title", registerName); + return regEl; + } + + // The header element contains the three headers for the LifeTimePosition axis. + private elementForHeader() { + const headerEl = createElement("div", "range-header"); + this.view.divs.positionHeaders = createElement("div", "range-position-labels"); + + this.view.divs.positionHeaders.appendChild(this.elementForBlockHeader()); + this.view.divs.positionHeaders.appendChild(this.elementForInstructionHeader()); + this.view.divs.positionHeaders.appendChild(this.elementForPositionHeader()); + + headerEl.appendChild(this.view.divs.positionHeaders); + headerEl.onscroll = () => { + this.view.scrollHandler.syncScroll(ToSync.LEFT, + this.view.divs.wholeHeader, this.view.divs.grid); + this.view.scrollHandler.saveScroll(); + }; + return headerEl; + } + + // The LifeTimePosition axis shows three headers, for positions, instructions, and blocks. + + private elementForBlockHeader() { + const headerEl = createElement("div", "range-block-ids"); + headerEl.style.gridTemplateColumns = this.gridTemplateColumns; + + const elementForBlockIndex = (index: number, firstInstruction: number, instrCount: number) => { + const str = "B" + index; + const element = + createElement("div", "range-block-id range-header-element range-block-border", str); + element.setAttribute("title", str); + const firstGridCol = (firstInstruction * Constants.POSITIONS_PER_INSTRUCTION) + 1; + const lastGridCol = firstGridCol + (instrCount * Constants.POSITIONS_PER_INSTRUCTION); + element.style.gridColumn = firstGridCol + " / " + lastGridCol; + return element; + }; + + let blockIndex = 0; + for (let i = 0; i < this.view.sequenceView.numInstructions;) { + const instrCount = this.view.blocksData.blockInstructionCountMap.get(blockIndex); + headerEl.appendChild(elementForBlockIndex(blockIndex, i, instrCount)); + ++blockIndex; + i += instrCount; + } + return headerEl; + } + + private elementForInstructionHeader() { + const headerEl = createElement("div", "range-instruction-ids"); + headerEl.style.gridTemplateColumns = this.gridTemplateColumns; + + const elementForInstructionIndex = (index: number, isBlockBorder: boolean) => { + const classes = "range-instruction-id range-header-element " + + (isBlockBorder ? "range-block-border" : "range-instr-border"); + const element = createElement("div", classes, "" + index); + element.setAttribute("title", "" + index); + const firstGridCol = (index * Constants.POSITIONS_PER_INSTRUCTION) + 1; + element.style.gridColumn = firstGridCol + " / " + + (firstGridCol + Constants.POSITIONS_PER_INSTRUCTION); + return element; + }; + + for (let i = 0; i < this.view.sequenceView.numInstructions; ++i) { + const blockBorder = this.view.blocksData.blockBorders.has(i); + headerEl.appendChild(elementForInstructionIndex(i, blockBorder)); + } + return headerEl; + } + + private elementForPositionHeader() { + const headerEl = createElement("div", "range-positions range-positions-header"); + headerEl.style.gridTemplateColumns = this.gridTemplateColumns; + + const elementForPositionIndex = (index: number, isBlockBorder: boolean) => { + const classes = "range-position range-header-element " + + (isBlockBorder ? "range-block-border" + : this.view.blocksData.isInstructionBorder(index) ? "range-instr-border" + : "range-position-border"); + const element = createElement("div", classes, "" + index); + element.setAttribute("title", "" + index); + return element; + }; + + for (let i = 0; i < this.view.numPositions; ++i) { + headerEl.appendChild(elementForPositionIndex(i, this.view.blocksData.isBlockBorder(i))); + } + return headerEl; + } + + private elementForGrid() { + const gridEl = createElement("div", "range-grid"); + gridEl.onscroll = () => { + this.view.scrollHandler.syncScroll(ToSync.TOP, this.view.divs.grid, this.view.divs.yAxis); + this.view.scrollHandler.syncScroll(ToSync.LEFT, + this.view.divs.grid, this.view.divs.wholeHeader); + this.view.scrollHandler.saveScroll(); + }; + return gridEl; + } +} + +// Handles the work required when the phase is changed. +// Between before and after register allocation for example. +class PhaseChangeHandler { + view: RangeView; + + constructor(view: RangeView) { + this.view = view; + } + + // Called when the phase view is switched between before and after register allocation. + phaseChange() { + if (!this.view.gridAccessor.hasGrid()) { + // If this phase view has not been seen yet then the intervals need to be constructed. + this.addNewIntervals(); + } + // Show all intervals pertaining to the current phase view. + this.view.intervalsAccessor.forEachInterval((phase, interval) => { + interval.classList.toggle("range-hidden", phase != this.view.sequenceView.currentPhaseIndex); + }); + } + + private addNewIntervals() { + // All Grids should point to the same HTMLElement for empty cells in the grid, + // so as to avoid duplication. The current Grid is used to retrieve these elements. + const currentGrid = this.view.gridAccessor.getAnyGrid(); + const newGrid = new Grid(); + this.view.gridAccessor.addGrid(newGrid); + const source = this.view.sequenceView.sequence.register_allocation; + let row = 0; + for (const [registerIndex, range] of source.liveRanges) { + this.addnewIntervalsInRange(currentGrid, newGrid, row, registerIndex, + new RangePair([range, undefined])); + ++row; + } + Helper.forEachFixedRange(this.view.sequenceView.sequence.register_allocation, row, + (registerIndex, row, _, ranges) => { + this.addnewIntervalsInRange(currentGrid, newGrid, row, registerIndex, ranges); + }); + } + + private addnewIntervalsInRange(currentGrid: Grid, newGrid: Grid, row: number, + registerIndex: string, ranges: RangePair) { + const numReplacements = new Map<HTMLElement, number>(); + + const getElementForEmptyPosition = (position: number) => { + return currentGrid.getCell(row, position); + }; + + // Inserts new interval beside existing intervals. + const callbackForInterval = (position: number, interval: HTMLElement) => { + // Overlapping intervals are placed beside each other and the relevant ones displayed. + let currentInterval = currentGrid.getInterval(row, position); + // The number of intervals already inserted is tracked so that the inserted intervals + // are ordered correctly. + const intervalsAlreadyInserted = numReplacements.get(currentInterval); + numReplacements.set(currentInterval, intervalsAlreadyInserted ? intervalsAlreadyInserted + 1 + : 1); + if (intervalsAlreadyInserted) { + for (let j = 0; j < intervalsAlreadyInserted; ++j) { + currentInterval = (currentInterval.nextElementSibling as HTMLElement); + } + } + interval.classList.add("range-hidden"); + currentInterval.insertAdjacentElement('afterend', interval); + }; + + this.view.rowConstructor.construct(newGrid, row, registerIndex, ranges, + getElementForEmptyPosition, callbackForInterval); + } +} + +enum ToSync { LEFT, TOP } + +// Handles saving and syncing the scroll positions of the grid. +class ScrollHandler { + divs: Divs; + scrollTop: number; + scrollLeft: number; + scrollTopTimeout: NodeJS.Timeout; + scrollLeftTimeout: NodeJS.Timeout; + scrollTopFunc: (this: GlobalEventHandlers, ev: Event) => any; + scrollLeftFunc: (this: GlobalEventHandlers, ev: Event) => any; + + constructor(divs: Divs) { + this.divs = divs; + } + + // This function is used to hide the rows which are not currently in view and + // so reduce the performance cost of things like hit tests and scrolling. + syncHidden() { + + const getOffset = (rowEl: HTMLElement, placeholderRowEl: HTMLElement, isHidden: boolean) => { + return isHidden ? placeholderRowEl.offsetTop : rowEl.offsetTop; + }; + + const toHide = new Array<[HTMLElement, HTMLElement]>(); + + const sampleCell = this.divs.registers.children[1] as HTMLElement; + const buffer = 2 * sampleCell.clientHeight; + const min = this.divs.grid.offsetTop + this.divs.grid.scrollTop - buffer; + const max = min + this.divs.grid.clientHeight + buffer; + + // The rows are grouped by being contained within a group div. This is so as to allow + // groups of rows to easily be displayed and hidden with less of a performance cost. + // Each row in the mainGroup div is matched with an equivalent placeholder row in + // the placeholderGroup div that will be shown when mainGroup is hidden so as to maintain + // the dimensions and scroll positions of the grid. + + const rangeGroups = this.divs.grid.children; + for (let i = 1; i < rangeGroups.length; i += 2) { + const mainGroup = rangeGroups[i] as HTMLElement; + const placeholderGroup = rangeGroups[i - 1] as HTMLElement; + const isHidden = mainGroup.classList.contains("range-hidden"); + // The offsets are used to calculate whether the group is in view. + const offsetMin = getOffset(mainGroup.firstChild as HTMLElement, + placeholderGroup.firstChild as HTMLElement, isHidden); + const offsetMax = getOffset(mainGroup.lastChild as HTMLElement, + placeholderGroup.lastChild as HTMLElement, isHidden); + if (offsetMax > min && offsetMin < max) { + if (isHidden) { + // Show the rows, hide the placeholders. + mainGroup.classList.toggle("range-hidden", false); + placeholderGroup.classList.toggle("range-hidden", true); + } + } else if (!isHidden) { + // Only hide the rows once the new rows are shown so that scrollLeft is not lost. + toHide.push([mainGroup, placeholderGroup]); + } + } + for (const [mainGroup, placeholderGroup] of toHide) { + // Hide the rows, show the placeholders. + mainGroup.classList.toggle("range-hidden", true); + placeholderGroup.classList.toggle("range-hidden", false); + } + } + + // This function is required to keep the axes labels in line with the grid + // content when scrolling. + syncScroll(toSync: ToSync, source: HTMLElement, target: HTMLElement) { + // Continually delay timeout until scrolling has stopped. + toSync == ToSync.TOP ? clearTimeout(this.scrollTopTimeout) + : clearTimeout(this.scrollLeftTimeout); + if (target.onscroll) { + if (toSync == ToSync.TOP) this.scrollTopFunc = target.onscroll; + else this.scrollLeftFunc = target.onscroll; + } + // Clear onscroll to prevent the target syncing back with the source. + target.onscroll = null; + + if (toSync == ToSync.TOP) target.scrollTop = source.scrollTop; + else target.scrollLeft = source.scrollLeft; + + // Only show / hide the grid content once scrolling has stopped. + if (toSync == ToSync.TOP) { + this.scrollTopTimeout = setTimeout(() => { + target.onscroll = this.scrollTopFunc; + this.syncHidden(); + }, 500); + } else { + this.scrollLeftTimeout = setTimeout(() => { + target.onscroll = this.scrollLeftFunc; + this.syncHidden(); + }, 500); + } + } + + saveScroll() { + this.scrollLeft = this.divs.grid.scrollLeft; + this.scrollTop = this.divs.grid.scrollTop; + } + + restoreScroll() { + if (this.scrollLeft) { + this.divs.grid.scrollLeft = this.scrollLeft; + this.divs.grid.scrollTop = this.scrollTop; + } + } +} + +// RangeView displays the live range data as passed in by SequenceView. +// The data is displayed in a grid format, with the fixed and virtual registers +// along one axis, and the LifeTimePositions along the other. Each LifeTimePosition +// is part of an Instruction in SequenceView, which itself is part of an Instruction +// Block. The live ranges are displayed as intervals, each belonging to a register, +// and spanning across a certain range of LifeTimePositions. +// When the phase being displayed changes between before register allocation and +// after register allocation, only the intervals need to be changed. +export class RangeView { + sequenceView: SequenceView; + + initialized: boolean; + isShown: boolean; + numPositions: number; + cssVariables: CSSVariables; + divs: Divs; + rowConstructor: RowConstructor; + phaseChangeHandler: PhaseChangeHandler; + scrollHandler: ScrollHandler; + blocksData: BlocksData; + intervalsAccessor: IntervalElementsAccessor; + gridAccessor: GridAccessor; + + constructor(sequence: SequenceView) { + this.initialized = false; + this.isShown = false; + this.sequenceView = sequence; + } + + initializeContent(blocks: Array<any>) { + if (!this.initialized) { + this.gridAccessor = new GridAccessor(this.sequenceView); + this.intervalsAccessor = new IntervalElementsAccessor(this.sequenceView); + this.cssVariables = new CSSVariables(); + this.blocksData = new BlocksData(blocks); + this.divs = new Divs(); + this.scrollHandler = new ScrollHandler(this.divs); + this.numPositions = this.sequenceView.numInstructions * Constants.POSITIONS_PER_INSTRUCTION; + this.rowConstructor = new RowConstructor(this); + const constructor = new RangeViewConstructor(this); + constructor.construct(); + this.phaseChangeHandler = new PhaseChangeHandler(this); + this.initialized = true; + } else { + // If the RangeView has already been initialized then the phase must have + // been changed. + this.phaseChangeHandler.phaseChange(); + } + } + + show() { + if (!this.isShown) { + this.isShown = true; + this.divs.container.appendChild(this.divs.content); + this.divs.resizerBar.style.visibility = "visible"; + this.divs.container.style.visibility = "visible"; + this.divs.snapper.style.visibility = "visible"; + // Dispatch a resize event to ensure that the + // panel is shown. + window.dispatchEvent(new Event('resize')); + + setTimeout(() => { + this.scrollHandler.restoreScroll(); + this.scrollHandler.syncHidden(); + this.divs.showOnLoad.style.visibility = "visible"; + }, 100); + } + } + + hide() { + if (this.initialized) { + this.isShown = false; + this.divs.container.removeChild(this.divs.content); + this.divs.resizerBar.style.visibility = "hidden"; + this.divs.container.style.visibility = "hidden"; + this.divs.snapper.style.visibility = "hidden"; + this.divs.showOnLoad.style.visibility = "hidden"; + } else { + window.document.getElementById('ranges').style.visibility = "hidden"; + } + // Dispatch a resize event to ensure that the + // panel is hidden. + window.dispatchEvent(new Event('resize')); + } + + onresize() { + if (this.isShown) this.scrollHandler.syncHidden(); + } +} diff --git a/chromium/v8/tools/turbolizer/src/resizer.ts b/chromium/v8/tools/turbolizer/src/resizer.ts index 4bd771f7313..ce0519398bb 100644 --- a/chromium/v8/tools/turbolizer/src/resizer.ts +++ b/chromium/v8/tools/turbolizer/src/resizer.ts @@ -11,6 +11,8 @@ class Snapper { sourceCollapse: HTMLElement; disassemblyExpand: HTMLElement; disassemblyCollapse: HTMLElement; + rangesExpand: HTMLElement; + rangesCollapse: HTMLElement; constructor(resizer: Resizer) { this.resizer = resizer; @@ -18,6 +20,8 @@ class Snapper { this.sourceCollapse = document.getElementById(C.SOURCE_COLLAPSE_ID); this.disassemblyExpand = document.getElementById(C.DISASSEMBLY_EXPAND_ID); this.disassemblyCollapse = document.getElementById(C.DISASSEMBLY_COLLAPSE_ID); + this.rangesExpand = document.getElementById(C.RANGES_EXPAND_ID); + this.rangesCollapse = document.getElementById(C.RANGES_COLLAPSE_ID); document.getElementById("show-hide-source").addEventListener("click", () => { this.resizer.resizerLeft.classed("snapped", !this.resizer.resizerLeft.classed("snapped")); @@ -29,13 +33,20 @@ class Snapper { this.setDisassemblyExpanded(!this.disassemblyExpand.classList.contains("invisible")); this.resizer.updatePanes(); }); + document.getElementById("show-hide-ranges").addEventListener("click", () => { + this.resizer.resizerRanges.classed("snapped", !this.resizer.resizerRanges.classed("snapped")); + this.setRangesExpanded(!this.rangesExpand.classList.contains("invisible")); + this.resizer.updatePanes(); + }); } restoreExpandedState(): void { this.resizer.resizerLeft.classed("snapped", window.sessionStorage.getItem("expandedState-source") == "false"); this.resizer.resizerRight.classed("snapped", window.sessionStorage.getItem("expandedState-disassembly") == "false"); + this.resizer.resizerRanges.classed("snapped", window.sessionStorage.getItem("expandedState-ranges") == "false"); this.setSourceExpanded(this.getLastExpandedState("source", true)); this.setDisassemblyExpanded(this.getLastExpandedState("disassembly", true)); + this.setRangesExpanded(this.getLastExpandedState("ranges", true)); } getLastExpandedState(type: string, defaultState: boolean): boolean { @@ -48,6 +59,7 @@ class Snapper { window.sessionStorage.setItem("expandedState-source", `${isSourceExpanded}`); this.sourceExpand.classList.toggle("invisible", isSourceExpanded); this.sourceCollapse.classList.toggle("invisible", !isSourceExpanded); + document.getElementById("show-hide-ranges").style.marginLeft = isSourceExpanded ? null : "40px"; } setSourceExpanded(isSourceExpanded: boolean): void { @@ -65,30 +77,53 @@ class Snapper { this.disassemblyUpdate(isDisassemblyExpanded); this.resizer.updateRightWidth(); } + + rangesUpdate(isRangesExpanded: boolean): void { + window.sessionStorage.setItem("expandedState-ranges", `${isRangesExpanded}`); + this.rangesExpand.classList.toggle("invisible", isRangesExpanded); + this.rangesCollapse.classList.toggle("invisible", !isRangesExpanded); + } + + setRangesExpanded(isRangesExpanded: boolean): void { + this.rangesUpdate(isRangesExpanded); + this.resizer.updateRanges(); + } } export class Resizer { snapper: Snapper; deadWidth: number; + deadHeight: number; left: HTMLElement; right: HTMLElement; + ranges: HTMLElement; + middle: HTMLElement; sepLeft: number; sepRight: number; + sepRangesHeight: number; panesUpdatedCallback: () => void; resizerRight: d3.Selection<HTMLDivElement, any, any, any>; resizerLeft: d3.Selection<HTMLDivElement, any, any, any>; + resizerRanges: d3.Selection<HTMLDivElement, any, any, any>; private readonly SOURCE_PANE_DEFAULT_PERCENT = 1 / 4; private readonly DISASSEMBLY_PANE_DEFAULT_PERCENT = 3 / 4; + private readonly RANGES_PANE_HEIGHT_DEFAULT_PERCENT = 3 / 4; + private readonly RESIZER_RANGES_HEIGHT_BUFFER_PERCENTAGE = 5; + private readonly RESIZER_SIZE = document.getElementById("resizer-ranges").offsetHeight; - constructor(panesUpdatedCallback: () => void, deadWidth: number) { + constructor(panesUpdatedCallback: () => void, deadWidth: number, deadHeight: number) { const resizer = this; resizer.panesUpdatedCallback = panesUpdatedCallback; resizer.deadWidth = deadWidth; + resizer.deadHeight = deadHeight; resizer.left = document.getElementById(C.SOURCE_PANE_ID); resizer.right = document.getElementById(C.GENERATED_PANE_ID); + resizer.ranges = document.getElementById(C.RANGES_PANE_ID); + resizer.middle = document.getElementById("middle"); resizer.resizerLeft = d3.select('#resizer-left'); resizer.resizerRight = d3.select('#resizer-right'); + resizer.resizerRanges = d3.select('#resizer-ranges'); // Set default sizes, if they weren't set. if (window.sessionStorage.getItem("source-pane-percent") === null) { window.sessionStorage.setItem("source-pane-percent", `${this.SOURCE_PANE_DEFAULT_PERCENT}`); @@ -96,8 +131,11 @@ export class Resizer { if (window.sessionStorage.getItem("disassembly-pane-percent") === null) { window.sessionStorage.setItem("disassembly-pane-percent", `${this.DISASSEMBLY_PANE_DEFAULT_PERCENT}`); } + if (window.sessionStorage.getItem("ranges-pane-height-percent") === null) { + window.sessionStorage.setItem("ranges-pane-height-percent", `${this.RANGES_PANE_HEIGHT_DEFAULT_PERCENT}`); + } - this.updateWidths(); + this.updateSizes(); const dragResizeLeft = d3.drag() .on('drag', function () { @@ -151,8 +189,35 @@ export class Resizer { resizer.resizerRight.classed("dragged", false); }); resizer.resizerRight.call(dragResizeRight); + + const dragResizeRanges = d3.drag() + .on('drag', function () { + const y = d3.mouse(this.parentElement)[1]; + resizer.sepRangesHeight = Math.max(100, Math.min(y, window.innerHeight) - resizer.RESIZER_RANGES_HEIGHT_BUFFER_PERCENTAGE); + resizer.updatePanes(); + }) + .on('start', function () { + resizer.resizerRanges.classed("dragged", true); + }) + .on('end', function () { + // If the panel is close enough to the bottom, treat it as if it was pulled all the way to the bottom. + const y = d3.mouse(this.parentElement)[1]; + if (y >= (window.innerHeight - deadHeight)) { + resizer.sepRangesHeight = window.innerHeight; + resizer.updatePanes(); + } + // Snap if dragged all the way to the bottom. + resizer.resizerRanges.classed("snapped", resizer.sepRangesHeight >= window.innerHeight - 1); + if (!resizer.isRangesSnapped()) { + window.sessionStorage.setItem("ranges-pane-height-percent", `${resizer.sepRangesHeight / window.innerHeight}`); + } + resizer.snapper.setRangesExpanded(!resizer.isRangesSnapped()); + resizer.resizerRanges.classed("dragged", false); + }); + resizer.resizerRanges.call(dragResizeRanges); + window.onresize = function () { - resizer.updateWidths(); + resizer.updateSizes(); resizer.updatePanes(); }; resizer.snapper = new Snapper(resizer); @@ -167,15 +232,70 @@ export class Resizer { return this.resizerRight.classed("snapped"); } + isRangesSnapped() { + return this.resizerRanges.classed("snapped"); + } + + updateRangesPane() { + const clientHeight = window.innerHeight; + const rangesIsHidden = this.ranges.style.visibility == "hidden"; + let resizerSize = this.RESIZER_SIZE; + if (rangesIsHidden) { + resizerSize = 0; + this.sepRangesHeight = clientHeight; + } + + const rangeHeight = clientHeight - this.sepRangesHeight; + this.ranges.style.height = rangeHeight + 'px'; + const panelWidth = this.sepRight - this.sepLeft - (2 * resizerSize); + this.ranges.style.width = panelWidth + 'px'; + const multiview = document.getElementById("multiview"); + if (multiview && multiview.style) { + multiview.style.height = (this.sepRangesHeight - resizerSize) + 'px'; + multiview.style.width = panelWidth + 'px'; + } + + // Resize the range grid and labels. + const rangeGrid = (this.ranges.getElementsByClassName("range-grid")[0] as HTMLElement); + if (rangeGrid) { + const yAxis = (this.ranges.getElementsByClassName("range-y-axis")[0] as HTMLElement); + const rangeHeader = (this.ranges.getElementsByClassName("range-header")[0] as HTMLElement); + + const gridWidth = panelWidth - yAxis.clientWidth; + rangeGrid.style.width = Math.floor(gridWidth - 1) + 'px'; + // Take live ranges' right scrollbar into account. + rangeHeader.style.width = (gridWidth - rangeGrid.offsetWidth + rangeGrid.clientWidth - 1) + 'px'; + // Set resizer to horizontal. + this.resizerRanges.style('width', panelWidth + 'px'); + + const rangeTitle = (this.ranges.getElementsByClassName("range-title-div")[0] as HTMLElement); + const rangeHeaderLabel = (this.ranges.getElementsByClassName("range-header-label-x")[0] as HTMLElement); + const gridHeight = rangeHeight - rangeHeader.clientHeight - rangeTitle.clientHeight - rangeHeaderLabel.clientHeight; + rangeGrid.style.height = gridHeight + 'px'; + // Take live ranges' bottom scrollbar into account. + yAxis.style.height = (gridHeight - rangeGrid.offsetHeight + rangeGrid.clientHeight) + 'px'; + } + this.resizerRanges.style('ranges', this.ranges.style.height); + } + updatePanes() { this.left.style.width = this.sepLeft + 'px'; this.resizerLeft.style('left', this.sepLeft + 'px'); this.right.style.width = (document.body.getBoundingClientRect().width - this.sepRight) + 'px'; this.resizerRight.style('right', (document.body.getBoundingClientRect().width - this.sepRight - 1) + 'px'); - + this.updateRangesPane(); this.panesUpdatedCallback(); } + updateRanges() { + if (this.isRangesSnapped()) { + this.sepRangesHeight = window.innerHeight; + } else { + const sepRangesHeight = window.sessionStorage.getItem("ranges-pane-height-percent"); + this.sepRangesHeight = window.innerHeight * Number.parseFloat(sepRangesHeight); + } + } + updateLeftWidth() { if (this.isLeftSnapped()) { this.sepLeft = 0; @@ -194,8 +314,9 @@ export class Resizer { } } - updateWidths() { + updateSizes() { this.updateLeftWidth(); this.updateRightWidth(); + this.updateRanges(); } } diff --git a/chromium/v8/tools/turbolizer/src/sequence-view.ts b/chromium/v8/tools/turbolizer/src/sequence-view.ts index 49b7e9f7b2a..187b162b1cd 100644 --- a/chromium/v8/tools/turbolizer/src/sequence-view.ts +++ b/chromium/v8/tools/turbolizer/src/sequence-view.ts @@ -3,12 +3,21 @@ // found in the LICENSE file. import { Sequence } from "../src/source-resolver"; -import { isIterable } from "../src/util"; +import { createElement } from "../src/util"; import { TextView } from "../src/text-view"; +import { RangeView } from "../src/range-view"; export class SequenceView extends TextView { sequence: Sequence; searchInfo: Array<any>; + phaseSelect: HTMLSelectElement; + numInstructions: number; + currentPhaseIndex: number; + phaseIndexes: Set<number>; + isShown: boolean; + rangeView: RangeView; + showRangeView: boolean; + toggleRangeViewEl: HTMLElement; createViewElement() { const pane = document.createElement('div'); @@ -20,6 +29,12 @@ export class SequenceView extends TextView { constructor(parentId, broker) { super(parentId, broker); + this.numInstructions = 0; + this.phaseIndexes = new Set<number>(); + this.isShown = false; + this.showRangeView = false; + this.rangeView = null; + this.toggleRangeViewEl = this.elementForToggleRangeView(); } attachSelection(s) { @@ -37,34 +52,58 @@ export class SequenceView extends TextView { return this.selection.detachSelection(); } + show() { + this.currentPhaseIndex = this.phaseSelect.selectedIndex; + if (!this.isShown) { + this.isShown = true; + this.phaseIndexes.add(this.currentPhaseIndex); + this.container.appendChild(this.divNode); + this.container.getElementsByClassName("graph-toolbox")[0].appendChild(this.toggleRangeViewEl); + } + if (this.showRangeView) this.rangeView.show(); + } + + hide() { + // A single SequenceView object is used for two phases (i.e before and after + // register allocation), tracking the indexes lets the redundant hides and + // shows be avoided when switching between the two. + this.currentPhaseIndex = this.phaseSelect.selectedIndex; + if (!this.phaseIndexes.has(this.currentPhaseIndex)) { + this.isShown = false; + this.container.removeChild(this.divNode); + this.container.getElementsByClassName("graph-toolbox")[0].removeChild(this.toggleRangeViewEl); + if (this.showRangeView) this.rangeView.hide(); + } + } + + onresize() { + if (this.showRangeView) this.rangeView.onresize(); + } + initializeContent(data, rememberedSelection) { this.divNode.innerHTML = ''; this.sequence = data.sequence; this.searchInfo = []; - this.divNode.addEventListener('click', (e: MouseEvent) => { + this.divNode.onclick = (e: MouseEvent) => { if (!(e.target instanceof HTMLElement)) return; const instructionId = Number.parseInt(e.target.dataset.instructionId, 10); if (!instructionId) return; if (!e.shiftKey) this.broker.broadcastClear(null); this.broker.broadcastInstructionSelect(null, [instructionId], true); - }); + }; + this.phaseSelect = (document.getElementById('phase-select') as HTMLSelectElement); + this.currentPhaseIndex = this.phaseSelect.selectedIndex; + this.addBlocks(this.sequence.blocks); + const lastBlock = this.sequence.blocks[this.sequence.blocks.length - 1]; + this.numInstructions = lastBlock.instructions[lastBlock.instructions.length - 1].id + 1; + this.addRangeView(); this.attachSelection(rememberedSelection); this.show(); } elementForBlock(block) { const view = this; - function createElement(tag: string, cls: string | Array<string>, content?: string) { - const el = document.createElement(tag); - if (isIterable(cls)) { - for (const c of cls) el.classList.add(c); - } else { - el.classList.add(cls); - } - if (content != undefined) el.innerHTML = content; - return el; - } function mkLinkHandler(id, handler) { return function (e) { @@ -84,16 +123,33 @@ export class SequenceView extends TextView { return mkLinkHandler(text, view.selectionHandler); } + function elementForOperandWithSpan(span, text, searchInfo, isVirtual) { + const selectionText = isVirtual ? "virt_" + text : text; + span.onclick = mkOperandLinkHandler(selectionText); + searchInfo.push(text); + view.addHtmlElementForNodeId(selectionText, span); + const container = createElement("div", ""); + container.appendChild(span); + return container; + } + function elementForOperand(operand, searchInfo) { - const text = operand.text; - const operandEl = createElement("div", ["parameter", "tag", "clickable", operand.type], text); + let isVirtual = false; + let className = "parameter tag clickable " + operand.type; + if (operand.text[0] == 'v' && !(operand.tooltip && operand.tooltip.includes("Float"))) { + isVirtual = true; + className += " virtual-reg"; + } + const span = createElement("span", className, operand.text); if (operand.tooltip) { - operandEl.setAttribute("title", operand.tooltip); + span.setAttribute("title", operand.tooltip); } - operandEl.onclick = mkOperandLinkHandler(text); - searchInfo.push(text); - view.addHtmlElementForNodeId(text, operandEl); - return operandEl; + return elementForOperandWithSpan(span, operand.text, searchInfo, isVirtual); + } + + function elementForPhiOperand(text, searchInfo) { + const span = createElement("span", "parameter tag clickable virtual-reg", text); + return elementForOperandWithSpan(span, text, searchInfo, true); } function elementForInstruction(instruction, searchInfo) { @@ -115,7 +171,7 @@ export class SequenceView extends TextView { const gapEl = createElement("div", "gap", "gap"); let hasGaps = false; for (const gap of instruction.gaps) { - const moves = createElement("div", ["comma-sep-list", "gap-move"]); + const moves = createElement("div", "comma-sep-list gap-move"); for (const move of gap) { hasGaps = true; const moveEl = createElement("div", "move"); @@ -137,7 +193,7 @@ export class SequenceView extends TextView { instContentsEl.appendChild(instEl); if (instruction.outputs.length > 0) { - const outputs = createElement("div", ["comma-sep-list", "input-output-list"]); + const outputs = createElement("div", "comma-sep-list input-output-list"); for (const output of instruction.outputs) { const outputEl = elementForOperand(output, searchInfo); outputs.appendChild(outputEl); @@ -147,8 +203,8 @@ export class SequenceView extends TextView { instEl.appendChild(assignEl); } - let text = instruction.opcode + instruction.flags; - const instLabel = createElement("div", "node-label", text) + const text = instruction.opcode + instruction.flags; + const instLabel = createElement("div", "node-label", text); if (instruction.opcode == "ArchNop" && instruction.outputs.length == 1 && instruction.outputs[0].tooltip) { instLabel.innerText = instruction.outputs[0].tooltip; } @@ -158,7 +214,7 @@ export class SequenceView extends TextView { instEl.appendChild(instLabel); if (instruction.inputs.length > 0) { - const inputs = createElement("div", ["comma-sep-list", "input-output-list"]); + const inputs = createElement("div", "comma-sep-list input-output-list"); for (const input of instruction.inputs) { const inputEl = elementForOperand(input, searchInfo); inputs.appendChild(inputEl); @@ -167,7 +223,7 @@ export class SequenceView extends TextView { } if (instruction.temps.length > 0) { - const temps = createElement("div", ["comma-sep-list", "input-output-list", "temps"]); + const temps = createElement("div", "comma-sep-list input-output-list temps"); for (const temp of instruction.temps) { const tempEl = elementForOperand(temp, searchInfo); temps.appendChild(tempEl); @@ -181,12 +237,12 @@ export class SequenceView extends TextView { const sequenceBlock = createElement("div", "schedule-block"); sequenceBlock.classList.toggle("deferred", block.deferred); - const blockId = createElement("div", ["block-id", "com", "clickable"], block.id); + const blockId = createElement("div", "block-id com clickable", block.id); blockId.onclick = mkBlockLinkHandler(block.id); sequenceBlock.appendChild(blockId); - const blockPred = createElement("div", ["predecessor-list", "block-list", "comma-sep-list"]); + const blockPred = createElement("div", "predecessor-list block-list comma-sep-list"); for (const pred of block.predecessors) { - const predEl = createElement("div", ["block-id", "com", "clickable"], pred); + const predEl = createElement("div", "block-id com clickable", pred); predEl.onclick = mkBlockLinkHandler(pred); blockPred.appendChild(predEl); } @@ -211,7 +267,7 @@ export class SequenceView extends TextView { phiEl.appendChild(assignEl); for (const input of phi.operands) { - const inputEl = createElement("div", ["parameter", "tag", "clickable"], input); + const inputEl = elementForPhiOperand(input, this.searchInfo); phiEl.appendChild(inputEl); } } @@ -221,9 +277,9 @@ export class SequenceView extends TextView { instructions.appendChild(elementForInstruction(instruction, this.searchInfo)); } sequenceBlock.appendChild(instructions); - const blockSucc = createElement("div", ["successor-list", "block-list", "comma-sep-list"]); + const blockSucc = createElement("div", "successor-list block-list comma-sep-list"); for (const succ of block.successors) { - const succEl = createElement("div", ["block-id", "com", "clickable"], succ); + const succEl = createElement("div", "block-id com clickable", succ); succEl.onclick = mkBlockLinkHandler(succ); blockSucc.appendChild(succEl); } @@ -239,6 +295,63 @@ export class SequenceView extends TextView { } } + addRangeView() { + const preventRangeView = reason => { + const toggleRangesInput = this.toggleRangeViewEl.firstChild as HTMLInputElement; + if (this.rangeView) { + toggleRangesInput.checked = false; + this.toggleRangeView(toggleRangesInput); + } + toggleRangesInput.disabled = true; + this.toggleRangeViewEl.style.textDecoration = "line-through"; + this.toggleRangeViewEl.setAttribute("title", reason); + }; + + if (this.sequence.register_allocation) { + if (!this.rangeView) { + this.rangeView = new RangeView(this); + } + const source = this.sequence.register_allocation; + if (source.fixedLiveRanges.size == 0 && source.liveRanges.size == 0) { + preventRangeView("No live ranges to show"); + } else if (this.numInstructions >= 249) { + // This is due to the css grid-column being limited to 1000 columns. + // Performance issues would otherwise impose some limit. + // TODO(george.wort@arm.com): Allow the user to specify an instruction range + // to display that spans less than 249 instructions. + preventRangeView( + "Live range display is only supported for sequences with less than 249 instructions"); + } + if (this.showRangeView) { + this.rangeView.initializeContent(this.sequence.blocks); + } + } else { + preventRangeView("No live range data provided"); + } + } + + elementForToggleRangeView() { + const toggleRangeViewEl = createElement("label", "", "show live ranges"); + const toggleRangesInput = createElement("input", "range-toggle-show") as HTMLInputElement; + toggleRangesInput.setAttribute("type", "checkbox"); + toggleRangesInput.oninput = () => this.toggleRangeView(toggleRangesInput); + toggleRangeViewEl.insertBefore(toggleRangesInput, toggleRangeViewEl.firstChild); + return toggleRangeViewEl; + } + + toggleRangeView(toggleRangesInput: HTMLInputElement) { + toggleRangesInput.disabled = true; + this.showRangeView = toggleRangesInput.checked; + if (this.showRangeView) { + this.rangeView.initializeContent(this.sequence.blocks); + this.rangeView.show(); + } else { + this.rangeView.hide(); + } + window.dispatchEvent(new Event('resize')); + toggleRangesInput.disabled = false; + } + searchInputAction(searchBar, e) { e.stopPropagation(); this.selectionHandler.clear(); diff --git a/chromium/v8/tools/turbolizer/src/source-resolver.ts b/chromium/v8/tools/turbolizer/src/source-resolver.ts index 588eea5b995..085b44f3a75 100644 --- a/chromium/v8/tools/turbolizer/src/source-resolver.ts +++ b/chromium/v8/tools/turbolizer/src/source-resolver.ts @@ -83,7 +83,7 @@ interface InstructionsPhase { instructionOffsetToPCOffset?: any; blockIdtoInstructionRange?: any; nodeIdToInstructionRange?: any; - codeOffsetsInfo?: CodeOffsetsInfo + codeOffsetsInfo?: CodeOffsetsInfo; } interface GraphPhase { @@ -100,8 +100,44 @@ export interface Schedule { nodes: Array<any>; } +export class Interval { + start: number; + end: number; + + constructor(numbers: [number, number]) { + this.start = numbers[0]; + this.end = numbers[1]; + } +} + +export interface ChildRange { + id: string; + type: string; + op: any; + intervals: Array<[number, number]>; + uses: Array<number>; +} + +export interface Range { + child_ranges: Array<ChildRange>; + is_deferred: boolean; +} + +export class RegisterAllocation { + fixedDoubleLiveRanges: Map<string, Range>; + fixedLiveRanges: Map<string, Range>; + liveRanges: Map<string, Range>; + + constructor(registerAllocation) { + this.fixedDoubleLiveRanges = new Map<string, Range>(Object.entries(registerAllocation.fixed_double_live_ranges)); + this.fixedLiveRanges = new Map<string, Range>(Object.entries(registerAllocation.fixed_live_ranges)); + this.liveRanges = new Map<string, Range>(Object.entries(registerAllocation.live_ranges)); + } +} + export interface Sequence { blocks: Array<any>; + register_allocation: RegisterAllocation; } class CodeOffsetsInfo { @@ -720,8 +756,11 @@ export class SourceResolver { phase.schedule = state; return phase; } + parseSequence(phase) { - phase.sequence = { blocks: phase.blocks }; + phase.sequence = { blocks: phase.blocks, + register_allocation: phase.register_allocation ? new RegisterAllocation(phase.register_allocation) + : undefined }; return phase; } } diff --git a/chromium/v8/tools/turbolizer/src/turbo-visualizer.ts b/chromium/v8/tools/turbolizer/src/turbo-visualizer.ts index 22753cdda50..2dd01c28f74 100644 --- a/chromium/v8/tools/turbolizer/src/turbo-visualizer.ts +++ b/chromium/v8/tools/turbolizer/src/turbo-visualizer.ts @@ -18,7 +18,7 @@ window.onload = function () { let sourceViews: Array<CodeView> = []; let selectionBroker: SelectionBroker = null; let sourceResolver: SourceResolver = null; - const resizer = new Resizer(panesUpdatedCallback, 75); + const resizer = new Resizer(panesUpdatedCallback, 75, 75); const sourceTabsContainer = document.getElementById(C.SOURCE_PANE_ID); const sourceTabs = new Tabs(sourceTabsContainer); sourceTabs.addTab("+").classList.add("last-tab", "persistent-tab"); @@ -48,6 +48,9 @@ window.onload = function () { sourceViews.forEach(sv => sv.hide()); if (multiview) multiview.hide(); multiview = null; + document.getElementById("ranges").innerHTML = ''; + document.getElementById('ranges').style.visibility = "hidden"; + document.getElementById('show-hide-ranges').style.visibility = "hidden"; if (disassemblyView) disassemblyView.hide(); sourceViews = []; sourceResolver = new SourceResolver(); diff --git a/chromium/v8/tools/turbolizer/src/util.ts b/chromium/v8/tools/turbolizer/src/util.ts index d9c8dcdce05..8d2fc845115 100644 --- a/chromium/v8/tools/turbolizer/src/util.ts +++ b/chromium/v8/tools/turbolizer/src/util.ts @@ -91,3 +91,10 @@ export function measureText(text: string) { export function interpolate(val: number, max: number, start: number, end: number) { return start + (end - start) * (val / max); } + +export function createElement(tag: string, cls: string, content?: string) { + const el = document.createElement(tag); + el.className = cls; + if (content != undefined) el.innerText = content; + return el; +} diff --git a/chromium/v8/tools/turbolizer/turbo-visualizer-ranges.css b/chromium/v8/tools/turbolizer/turbo-visualizer-ranges.css new file mode 100644 index 00000000000..03976e2ec54 --- /dev/null +++ b/chromium/v8/tools/turbolizer/turbo-visualizer-ranges.css @@ -0,0 +1,238 @@ +/* CSS specific to the live ranges div associated with + the RangeView typescript class in src/range-view.ts. */ + +:root { + --range-y-axis-width: 18ch; + --range-position-width: 3.5ch; + --range-block-border: 6px; + --range-instr-border: 3px; + --range-position-border: 1px; +} + +.range-bold { + font-weight: bold; + color: black; +} + +#ranges { + font-family: monospace; + min-height: auto; + overflow: hidden; +} + +#resizer-ranges { + height: 10px; +} + +.range-title-div { + padding: 2ch 2ch 2ch 2ch; + white-space: nowrap; + overflow: auto; +} + +.range-title { + text-decoration: underline; + font-weight: bold; + font-size: large; + display: inline-block; +} + +.range-title-help { + margin-left: 2ch; + width: 1ch; + padding: 0 0.25ch; + border: 1px dotted black; + color: slategray; + display: inline-block; +} + +input.range-toggle-show { + vertical-align: middle; +} + +.range-header-label-x { + text-align: center; + margin-left: 13ch; +} + +.range-header-label-y { + width: 11ch; + float: left; + white-space: pre-wrap; + word-wrap: break-word; + margin-left: 6ch; + margin-top: 4ch; +} + +.range-y-axis { + display: inline-block; + width: var(--range-y-axis-width); + overflow: hidden; + white-space: nowrap; + vertical-align: top; +} + +.range-header { + display: flex; + overflow: hidden; + height: 8ch; + margin-left: var(--range-y-axis-width); +} + +.range-position-labels, +.range-register-labels { + background-color: lightgray; +} + +.range-register-labels { + float: right; +} + +.range-position-labels { + margin-top: auto; +} + +.range-registers { + float: right; + overflow: hidden; + text-align: right; +} + +.range-positions-header, +.range-instruction-ids, +.range-block-ids { + overflow: hidden; + white-space: nowrap; + display: grid; + grid-gap: 0; +} + +.range-reg { + width: 13ch; + text-align: right; +} + +.range-reg::after { + content: ":"; +} + +.range-grid { + overflow: auto; + display: inline-block; + white-space: nowrap; +} + +.range-block-id { + display: inline-block; + text-align: center; +} + +.range-instruction-id { + display: inline-block; + text-align: center; +} + +.range-position { + display: inline-block; + text-align: center; + z-index: 1; +} + +.range-transparent, +.range-position.range-empty { + color: transparent; +} + +.range-block-id:hover, +.range-instruction-id:hover, +.range-reg:hover, +.range-position:hover { + background-color: rgba(0, 0, 255, 0.10); +} + +.range-position.range-header-element { + border-bottom: 2px solid rgb(109, 107, 107); +} + +.range-block-id, +.range-instruction-id, +.range-reg, +.range-interval, +.range-position { + position: relative; + border: var(--range-position-border) solid rgb(109, 107, 107); +} + +.range-block-id, +.range-instruction-id, +.range-interval, +.range-position { + border-left: 0; +} + +.range-block-ids > .range-block-id:first-child, +.range-instruction-ids > .range-instruction-id:first-child, +.range-positions > .range-position:first-child { + border-left: var(--range-position-border) solid rgb(109, 107, 107); +} + +.range-position.range-interval-position { + border: none; +} + +.range-interval-text { + position: absolute; + padding-left: 0.5ch; + z-index: 2; + pointer-events: none +} + +.range-position.range-use { + border-left: var(--range-instr-border) solid red; +} + +.range-block-border, +.range-block-border.range-position.range-interval-position:last-child { + border-right: var(--range-block-border) solid rgb(109, 107, 107); +} + +.range-block-border.range-position.range-interval-position { + border-right: var(--range-block-border) solid transparent; +} + +.range-instr-border, +.range-instr-border.range-position.range-interval-position:last-child { + border-right: var(--range-instr-border) solid rgb(109, 107, 107); +} + +.range-instr-border.range-position.range-interval-position { + border-right: var(--range-instr-border) solid transparent; +} + +.range, +.range-interval, +.range-interval-wrapper, +.range-positions { + white-space: nowrap; + display: inline-block; +} + +.range-interval-wrapper, +.range-positions { + display: grid; + grid-gap: 0; +} + +.range-interval { + background-color: rgb(153, 158, 168); +} + +.range-hidden { + display: none !important; +} + +.range-positions-placeholder { + width: 100%; + border: var(--range-position-border) solid transparent; + color: transparent; +}
\ No newline at end of file diff --git a/chromium/v8/tools/turbolizer/turbo-visualizer.css b/chromium/v8/tools/turbolizer/turbo-visualizer.css index 6fb6da3b794..c7da769eb5d 100644 --- a/chromium/v8/tools/turbolizer/turbo-visualizer.css +++ b/chromium/v8/tools/turbolizer/turbo-visualizer.css @@ -342,6 +342,13 @@ input:hover, background-color: #F8F8F8; user-select: none; flex: 1; + z-index: 7; +} + +#middle.display-inline-flex, +#middle.display-inline-flex #multiview, +#middle.display-inline-flex #ranges { + display: inline-flex; } .viewpane { @@ -351,11 +358,6 @@ input:hover, flex-direction: column; } -.multiview { - width: 100%; -} - - #show-hide-disassembly { right: 0; } @@ -423,6 +425,10 @@ text { dominant-baseline: text-before-edge; } +.tab-content { + z-index: 6; +} + .resizer { z-index: 10; width: 10px; @@ -595,6 +601,10 @@ text { padding-right: .5ex; } +.instruction span { + padding-right: 0; +} + .phi-label, .instruction-id { display: inline-block; @@ -626,6 +636,10 @@ text { display: inline-block; } +.phi span { + padding-right: 0; +} + .gap .gap-move { padding-left: .5ex; padding-right: .5ex; @@ -639,6 +653,10 @@ text { content: ")"; } +.virtual-reg { + outline: 1px dotted blue; +} + .parameter.constant { outline: 1px dotted red; } diff --git a/chromium/v8/tools/turbolizer/up-arrow.png b/chromium/v8/tools/turbolizer/up-arrow.png Binary files differnew file mode 100644 index 00000000000..68cb14e80b3 --- /dev/null +++ b/chromium/v8/tools/turbolizer/up-arrow.png diff --git a/chromium/v8/tools/v8_presubmit.py b/chromium/v8/tools/v8_presubmit.py index 40677b3a0a3..587f8082a5f 100755 --- a/chromium/v8/tools/v8_presubmit.py +++ b/chromium/v8/tools/v8_presubmit.py @@ -59,12 +59,16 @@ from testrunner.local import utils # We now run our own header guard check in PRESUBMIT.py. # build/include_what_you_use: Started giving false positives for variables # named "string" and "map" assuming that you needed to include STL headers. +# runtime/references: As of May 2020 the C++ style guide suggests using +# references for out parameters, see +# https://google.github.io/styleguide/cppguide.html#Inputs_and_Outputs. LINT_RULES = """ -build/header_guard -build/include_what_you_use -readability/fn_size -readability/multiline_comment +-runtime/references -whitespace/comments """.split() diff --git a/chromium/v8/tools/v8heapconst.py b/chromium/v8/tools/v8heapconst.py index ac69cfb836b..57b1cf26bbd 100644 --- a/chromium/v8/tools/v8heapconst.py +++ b/chromium/v8/tools/v8heapconst.py @@ -72,31 +72,31 @@ INSTANCE_TYPES = { 108: "TEMPLATE_OBJECT_DESCRIPTION_TYPE", 109: "TUPLE2_TYPE", 110: "WASM_CAPI_FUNCTION_DATA_TYPE", - 111: "WASM_DEBUG_INFO_TYPE", - 112: "WASM_EXCEPTION_TAG_TYPE", - 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: "STRING_TABLE_TYPE", - 128: "CLOSURE_FEEDBACK_CELL_ARRAY_TYPE", - 129: "OBJECT_BOILERPLATE_DESCRIPTION_TYPE", - 130: "SCOPE_INFO_TYPE", - 131: "SCRIPT_CONTEXT_TABLE_TYPE", - 132: "BYTE_ARRAY_TYPE", - 133: "BYTECODE_ARRAY_TYPE", - 134: "FIXED_DOUBLE_ARRAY_TYPE", - 135: "INTERNAL_CLASS_WITH_SMI_ELEMENTS_TYPE", + 111: "WASM_EXCEPTION_TAG_TYPE", + 112: "WASM_EXPORTED_FUNCTION_DATA_TYPE", + 113: "WASM_INDIRECT_FUNCTION_TABLE_TYPE", + 114: "WASM_JS_FUNCTION_DATA_TYPE", + 115: "WASM_VALUE_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: "STRING_TABLE_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", @@ -107,46 +107,48 @@ INSTANCE_TYPES = { 143: "NATIVE_CONTEXT_TYPE", 144: "SCRIPT_CONTEXT_TYPE", 145: "WITH_CONTEXT_TYPE", - 146: "SMALL_ORDERED_HASH_MAP_TYPE", - 147: "SMALL_ORDERED_HASH_SET_TYPE", - 148: "SMALL_ORDERED_NAME_DICTIONARY_TYPE", - 149: "EXPORTED_SUB_CLASS_BASE_TYPE", - 150: "EXPORTED_SUB_CLASS_TYPE", - 151: "SOURCE_TEXT_MODULE_TYPE", - 152: "SYNTHETIC_MODULE_TYPE", - 153: "UNCOMPILED_DATA_WITH_PREPARSE_DATA_TYPE", - 154: "UNCOMPILED_DATA_WITHOUT_PREPARSE_DATA_TYPE", - 155: "WEAK_FIXED_ARRAY_TYPE", - 156: "TRANSITION_ARRAY_TYPE", - 157: "CELL_TYPE", - 158: "CODE_TYPE", - 159: "CODE_DATA_CONTAINER_TYPE", - 160: "COVERAGE_INFO_TYPE", - 161: "DESCRIPTOR_ARRAY_TYPE", - 162: "EMBEDDER_DATA_ARRAY_TYPE", - 163: "FEEDBACK_METADATA_TYPE", - 164: "FEEDBACK_VECTOR_TYPE", - 165: "FILLER_TYPE", - 166: "FREE_SPACE_TYPE", - 167: "INTERNAL_CLASS_TYPE", - 168: "INTERNAL_CLASS_WITH_STRUCT_ELEMENTS_TYPE", - 169: "MAP_TYPE", - 170: "PREPARSE_DATA_TYPE", - 171: "PROPERTY_ARRAY_TYPE", - 172: "PROPERTY_CELL_TYPE", - 173: "SHARED_FUNCTION_INFO_TYPE", - 174: "SMI_BOX_TYPE", - 175: "SMI_PAIR_TYPE", - 176: "SORT_STATE_TYPE", - 177: "WASM_ARRAY_TYPE", - 178: "WASM_STRUCT_TYPE", - 179: "WEAK_ARRAY_LIST_TYPE", - 180: "WEAK_CELL_TYPE", - 181: "JS_PROXY_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: "SOURCE_TEXT_MODULE_TYPE", + 153: "SYNTHETIC_MODULE_TYPE", + 154: "UNCOMPILED_DATA_WITH_PREPARSE_DATA_TYPE", + 155: "UNCOMPILED_DATA_WITHOUT_PREPARSE_DATA_TYPE", + 156: "WEAK_FIXED_ARRAY_TYPE", + 157: "TRANSITION_ARRAY_TYPE", + 158: "CELL_TYPE", + 159: "CODE_TYPE", + 160: "CODE_DATA_CONTAINER_TYPE", + 161: "COVERAGE_INFO_TYPE", + 162: "DESCRIPTOR_ARRAY_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_STRUCT_TYPE", + 181: "WEAK_ARRAY_LIST_TYPE", + 182: "WEAK_CELL_TYPE", + 183: "JS_PROXY_TYPE", 1057: "JS_OBJECT_TYPE", - 182: "JS_GLOBAL_OBJECT_TYPE", - 183: "JS_GLOBAL_PROXY_TYPE", - 184: "JS_MODULE_NAMESPACE_TYPE", + 184: "JS_GLOBAL_OBJECT_TYPE", + 185: "JS_GLOBAL_PROXY_TYPE", + 186: "JS_MODULE_NAMESPACE_TYPE", 1040: "JS_SPECIAL_API_OBJECT_TYPE", 1041: "JS_PRIMITIVE_WRAPPER_TYPE", 1042: "JS_MAP_KEY_ITERATOR_TYPE", @@ -164,195 +166,196 @@ INSTANCE_TYPES = { 1054: "JS_WEAK_MAP_TYPE", 1055: "JS_WEAK_SET_TYPE", 1056: "JS_API_OBJECT_TYPE", - 1058: "JS_AGGREGATE_ERROR_TYPE", - 1059: "JS_ARGUMENTS_OBJECT_TYPE", - 1060: "JS_ARRAY_TYPE", - 1061: "JS_ARRAY_BUFFER_TYPE", - 1062: "JS_ARRAY_ITERATOR_TYPE", - 1063: "JS_ASYNC_FROM_SYNC_ITERATOR_TYPE", - 1064: "JS_COLLATOR_TYPE", - 1065: "JS_CONTEXT_EXTENSION_OBJECT_TYPE", - 1066: "JS_DATE_TYPE", - 1067: "JS_DATE_TIME_FORMAT_TYPE", - 1068: "JS_DISPLAY_NAMES_TYPE", - 1069: "JS_ERROR_TYPE", - 1070: "JS_FINALIZATION_REGISTRY_TYPE", - 1071: "JS_LIST_FORMAT_TYPE", - 1072: "JS_LOCALE_TYPE", - 1073: "JS_MESSAGE_OBJECT_TYPE", - 1074: "JS_NUMBER_FORMAT_TYPE", - 1075: "JS_PLURAL_RULES_TYPE", - 1076: "JS_PROMISE_TYPE", - 1077: "JS_REG_EXP_TYPE", - 1078: "JS_REG_EXP_STRING_ITERATOR_TYPE", - 1079: "JS_RELATIVE_TIME_FORMAT_TYPE", - 1080: "JS_SEGMENT_ITERATOR_TYPE", - 1081: "JS_SEGMENTER_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_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_STRING_ITERATOR_TYPE", + 1082: "JS_V8_BREAK_ITERATOR_TYPE", + 1083: "JS_WEAK_REF_TYPE", + 1084: "WASM_EXCEPTION_OBJECT_TYPE", + 1085: "WASM_GLOBAL_OBJECT_TYPE", + 1086: "WASM_INSTANCE_OBJECT_TYPE", + 1087: "WASM_MEMORY_OBJECT_TYPE", + 1088: "WASM_MODULE_OBJECT_TYPE", + 1089: "WASM_TABLE_OBJECT_TYPE", + 1090: "JS_BOUND_FUNCTION_TYPE", + 1091: "JS_FUNCTION_TYPE", } # List of known V8 maps. KNOWN_MAPS = { - ("read_only_space", 0x00121): (166, "FreeSpaceMap"), - ("read_only_space", 0x00149): (169, "MetaMap"), + ("read_only_space", 0x00121): (167, "FreeSpaceMap"), + ("read_only_space", 0x00149): (170, "MetaMap"), ("read_only_space", 0x0018d): (67, "NullMap"), - ("read_only_space", 0x001c5): (161, "DescriptorArrayMap"), - ("read_only_space", 0x001f5): (155, "WeakFixedArrayMap"), - ("read_only_space", 0x0021d): (165, "OnePointerFillerMap"), - ("read_only_space", 0x00245): (165, "TwoPointerFillerMap"), + ("read_only_space", 0x001c5): (162, "DescriptorArrayMap"), + ("read_only_space", 0x001f5): (156, "WeakFixedArrayMap"), + ("read_only_space", 0x0021d): (166, "OnePointerFillerMap"), + ("read_only_space", 0x00245): (166, "TwoPointerFillerMap"), ("read_only_space", 0x00289): (67, "UninitializedMap"), ("read_only_space", 0x002cd): (8, "OneByteInternalizedStringMap"), ("read_only_space", 0x00329): (67, "UndefinedMap"), ("read_only_space", 0x0035d): (66, "HeapNumberMap"), ("read_only_space", 0x003a1): (67, "TheHoleMap"), ("read_only_space", 0x00401): (67, "BooleanMap"), - ("read_only_space", 0x00489): (132, "ByteArrayMap"), - ("read_only_space", 0x004b1): (117, "FixedArrayMap"), - ("read_only_space", 0x004d9): (117, "FixedCOWArrayMap"), - ("read_only_space", 0x00501): (118, "HashTableMap"), + ("read_only_space", 0x00489): (131, "ByteArrayMap"), + ("read_only_space", 0x004b1): (116, "FixedArrayMap"), + ("read_only_space", 0x004d9): (116, "FixedCOWArrayMap"), + ("read_only_space", 0x00501): (117, "HashTableMap"), ("read_only_space", 0x00529): (64, "SymbolMap"), ("read_only_space", 0x00551): (40, "OneByteStringMap"), - ("read_only_space", 0x00579): (130, "ScopeInfoMap"), - ("read_only_space", 0x005a1): (173, "SharedFunctionInfoMap"), - ("read_only_space", 0x005c9): (158, "CodeMap"), - ("read_only_space", 0x005f1): (157, "CellMap"), - ("read_only_space", 0x00619): (172, "GlobalPropertyCellMap"), + ("read_only_space", 0x00579): (129, "ScopeInfoMap"), + ("read_only_space", 0x005a1): (175, "SharedFunctionInfoMap"), + ("read_only_space", 0x005c9): (159, "CodeMap"), + ("read_only_space", 0x005f1): (158, "CellMap"), + ("read_only_space", 0x00619): (174, "GlobalPropertyCellMap"), ("read_only_space", 0x00641): (70, "ForeignMap"), - ("read_only_space", 0x00669): (156, "TransitionArrayMap"), + ("read_only_space", 0x00669): (157, "TransitionArrayMap"), ("read_only_space", 0x00691): (45, "ThinOneByteStringMap"), - ("read_only_space", 0x006b9): (164, "FeedbackVectorMap"), + ("read_only_space", 0x006b9): (165, "FeedbackVectorMap"), ("read_only_space", 0x0070d): (67, "ArgumentsMarkerMap"), ("read_only_space", 0x0076d): (67, "ExceptionMap"), ("read_only_space", 0x007c9): (67, "TerminationExceptionMap"), ("read_only_space", 0x00831): (67, "OptimizedOutMap"), ("read_only_space", 0x00891): (67, "StaleRegisterMap"), - ("read_only_space", 0x008d5): (131, "ScriptContextTableMap"), - ("read_only_space", 0x008fd): (128, "ClosureFeedbackCellArrayMap"), - ("read_only_space", 0x00925): (163, "FeedbackMetadataArrayMap"), - ("read_only_space", 0x0094d): (117, "ArrayListMap"), + ("read_only_space", 0x008d5): (130, "ScriptContextTableMap"), + ("read_only_space", 0x008fd): (127, "ClosureFeedbackCellArrayMap"), + ("read_only_space", 0x00925): (164, "FeedbackMetadataArrayMap"), + ("read_only_space", 0x0094d): (116, "ArrayListMap"), ("read_only_space", 0x00975): (65, "BigIntMap"), - ("read_only_space", 0x0099d): (129, "ObjectBoilerplateDescriptionMap"), - ("read_only_space", 0x009c5): (133, "BytecodeArrayMap"), - ("read_only_space", 0x009ed): (159, "CodeDataContainerMap"), - ("read_only_space", 0x00a15): (160, "CoverageInfoMap"), - ("read_only_space", 0x00a3d): (134, "FixedDoubleArrayMap"), - ("read_only_space", 0x00a65): (120, "GlobalDictionaryMap"), + ("read_only_space", 0x0099d): (128, "ObjectBoilerplateDescriptionMap"), + ("read_only_space", 0x009c5): (132, "BytecodeArrayMap"), + ("read_only_space", 0x009ed): (160, "CodeDataContainerMap"), + ("read_only_space", 0x00a15): (161, "CoverageInfoMap"), + ("read_only_space", 0x00a3d): (133, "FixedDoubleArrayMap"), + ("read_only_space", 0x00a65): (119, "GlobalDictionaryMap"), ("read_only_space", 0x00a8d): (96, "ManyClosuresCellMap"), - ("read_only_space", 0x00ab5): (117, "ModuleInfoMap"), - ("read_only_space", 0x00add): (121, "NameDictionaryMap"), + ("read_only_space", 0x00ab5): (116, "ModuleInfoMap"), + ("read_only_space", 0x00add): (120, "NameDictionaryMap"), ("read_only_space", 0x00b05): (96, "NoClosuresCellMap"), - ("read_only_space", 0x00b2d): (122, "NumberDictionaryMap"), + ("read_only_space", 0x00b2d): (121, "NumberDictionaryMap"), ("read_only_space", 0x00b55): (96, "OneClosureCellMap"), - ("read_only_space", 0x00b7d): (123, "OrderedHashMapMap"), - ("read_only_space", 0x00ba5): (124, "OrderedHashSetMap"), - ("read_only_space", 0x00bcd): (125, "OrderedNameDictionaryMap"), - ("read_only_space", 0x00bf5): (170, "PreparseDataMap"), - ("read_only_space", 0x00c1d): (171, "PropertyArrayMap"), + ("read_only_space", 0x00b7d): (122, "OrderedHashMapMap"), + ("read_only_space", 0x00ba5): (123, "OrderedHashSetMap"), + ("read_only_space", 0x00bcd): (124, "OrderedNameDictionaryMap"), + ("read_only_space", 0x00bf5): (172, "PreparseDataMap"), + ("read_only_space", 0x00c1d): (173, "PropertyArrayMap"), ("read_only_space", 0x00c45): (92, "SideEffectCallHandlerInfoMap"), ("read_only_space", 0x00c6d): (92, "SideEffectFreeCallHandlerInfoMap"), ("read_only_space", 0x00c95): (92, "NextCallSideEffectFreeCallHandlerInfoMap"), - ("read_only_space", 0x00cbd): (126, "SimpleNumberDictionaryMap"), - ("read_only_space", 0x00ce5): (117, "SloppyArgumentsElementsMap"), - ("read_only_space", 0x00d0d): (146, "SmallOrderedHashMapMap"), - ("read_only_space", 0x00d35): (147, "SmallOrderedHashSetMap"), - ("read_only_space", 0x00d5d): (148, "SmallOrderedNameDictionaryMap"), - ("read_only_space", 0x00d85): (151, "SourceTextModuleMap"), - ("read_only_space", 0x00dad): (127, "StringTableMap"), - ("read_only_space", 0x00dd5): (152, "SyntheticModuleMap"), - ("read_only_space", 0x00dfd): (154, "UncompiledDataWithoutPreparseDataMap"), - ("read_only_space", 0x00e25): (153, "UncompiledDataWithPreparseDataMap"), - ("read_only_space", 0x00e4d): (179, "WeakArrayListMap"), - ("read_only_space", 0x00e75): (119, "EphemeronHashTableMap"), - ("read_only_space", 0x00e9d): (162, "EmbedderDataArrayMap"), - ("read_only_space", 0x00ec5): (180, "WeakCellMap"), - ("read_only_space", 0x00eed): (32, "StringMap"), - ("read_only_space", 0x00f15): (41, "ConsOneByteStringMap"), - ("read_only_space", 0x00f3d): (33, "ConsStringMap"), - ("read_only_space", 0x00f65): (37, "ThinStringMap"), - ("read_only_space", 0x00f8d): (35, "SlicedStringMap"), - ("read_only_space", 0x00fb5): (43, "SlicedOneByteStringMap"), - ("read_only_space", 0x00fdd): (34, "ExternalStringMap"), - ("read_only_space", 0x01005): (42, "ExternalOneByteStringMap"), - ("read_only_space", 0x0102d): (50, "UncachedExternalStringMap"), - ("read_only_space", 0x01055): (0, "InternalizedStringMap"), - ("read_only_space", 0x0107d): (2, "ExternalInternalizedStringMap"), - ("read_only_space", 0x010a5): (10, "ExternalOneByteInternalizedStringMap"), - ("read_only_space", 0x010cd): (18, "UncachedExternalInternalizedStringMap"), - ("read_only_space", 0x010f5): (26, "UncachedExternalOneByteInternalizedStringMap"), - ("read_only_space", 0x0111d): (58, "UncachedExternalOneByteStringMap"), - ("read_only_space", 0x01145): (67, "SelfReferenceMarkerMap"), + ("read_only_space", 0x00cbd): (125, "SimpleNumberDictionaryMap"), + ("read_only_space", 0x00ce5): (149, "SmallOrderedHashMapMap"), + ("read_only_space", 0x00d0d): (150, "SmallOrderedHashSetMap"), + ("read_only_space", 0x00d35): (151, "SmallOrderedNameDictionaryMap"), + ("read_only_space", 0x00d5d): (152, "SourceTextModuleMap"), + ("read_only_space", 0x00d85): (126, "StringTableMap"), + ("read_only_space", 0x00dad): (153, "SyntheticModuleMap"), + ("read_only_space", 0x00dd5): (155, "UncompiledDataWithoutPreparseDataMap"), + ("read_only_space", 0x00dfd): (154, "UncompiledDataWithPreparseDataMap"), + ("read_only_space", 0x00e25): (181, "WeakArrayListMap"), + ("read_only_space", 0x00e4d): (118, "EphemeronHashTableMap"), + ("read_only_space", 0x00e75): (163, "EmbedderDataArrayMap"), + ("read_only_space", 0x00e9d): (182, "WeakCellMap"), + ("read_only_space", 0x00ec5): (32, "StringMap"), + ("read_only_space", 0x00eed): (41, "ConsOneByteStringMap"), + ("read_only_space", 0x00f15): (33, "ConsStringMap"), + ("read_only_space", 0x00f3d): (37, "ThinStringMap"), + ("read_only_space", 0x00f65): (35, "SlicedStringMap"), + ("read_only_space", 0x00f8d): (43, "SlicedOneByteStringMap"), + ("read_only_space", 0x00fb5): (34, "ExternalStringMap"), + ("read_only_space", 0x00fdd): (42, "ExternalOneByteStringMap"), + ("read_only_space", 0x01005): (50, "UncachedExternalStringMap"), + ("read_only_space", 0x0102d): (0, "InternalizedStringMap"), + ("read_only_space", 0x01055): (2, "ExternalInternalizedStringMap"), + ("read_only_space", 0x0107d): (10, "ExternalOneByteInternalizedStringMap"), + ("read_only_space", 0x010a5): (18, "UncachedExternalInternalizedStringMap"), + ("read_only_space", 0x010cd): (26, "UncachedExternalOneByteInternalizedStringMap"), + ("read_only_space", 0x010f5): (58, "UncachedExternalOneByteStringMap"), + ("read_only_space", 0x0111d): (67, "SelfReferenceMarkerMap"), + ("read_only_space", 0x01145): (67, "BasicBlockCountersMarkerMap"), ("read_only_space", 0x01179): (95, "EnumCacheMap"), ("read_only_space", 0x011c9): (86, "ArrayBoilerplateDescriptionMap"), - ("read_only_space", 0x012c5): (98, "InterceptorInfoMap"), - ("read_only_space", 0x03335): (71, "PromiseFulfillReactionJobTaskMap"), - ("read_only_space", 0x0335d): (72, "PromiseRejectReactionJobTaskMap"), - ("read_only_space", 0x03385): (73, "CallableTaskMap"), - ("read_only_space", 0x033ad): (74, "CallbackTaskMap"), - ("read_only_space", 0x033d5): (75, "PromiseResolveThenableJobTaskMap"), - ("read_only_space", 0x033fd): (78, "FunctionTemplateInfoMap"), - ("read_only_space", 0x03425): (79, "ObjectTemplateInfoMap"), - ("read_only_space", 0x0344d): (80, "AccessCheckInfoMap"), - ("read_only_space", 0x03475): (81, "AccessorInfoMap"), - ("read_only_space", 0x0349d): (82, "AccessorPairMap"), - ("read_only_space", 0x034c5): (83, "AliasedArgumentsEntryMap"), - ("read_only_space", 0x034ed): (84, "AllocationMementoMap"), - ("read_only_space", 0x03515): (87, "AsmWasmDataMap"), - ("read_only_space", 0x0353d): (88, "AsyncGeneratorRequestMap"), - ("read_only_space", 0x03565): (89, "BreakPointMap"), - ("read_only_space", 0x0358d): (90, "BreakPointInfoMap"), - ("read_only_space", 0x035b5): (91, "CachedTemplateObjectMap"), - ("read_only_space", 0x035dd): (93, "ClassPositionsMap"), - ("read_only_space", 0x03605): (94, "DebugInfoMap"), - ("read_only_space", 0x0362d): (97, "FunctionTemplateRareDataMap"), - ("read_only_space", 0x03655): (99, "InterpreterDataMap"), - ("read_only_space", 0x0367d): (100, "PromiseCapabilityMap"), - ("read_only_space", 0x036a5): (101, "PromiseReactionMap"), - ("read_only_space", 0x036cd): (102, "PropertyDescriptorObjectMap"), - ("read_only_space", 0x036f5): (103, "PrototypeInfoMap"), - ("read_only_space", 0x0371d): (104, "ScriptMap"), - ("read_only_space", 0x03745): (105, "SourceTextModuleInfoEntryMap"), - ("read_only_space", 0x0376d): (106, "StackFrameInfoMap"), - ("read_only_space", 0x03795): (107, "StackTraceFrameMap"), - ("read_only_space", 0x037bd): (108, "TemplateObjectDescriptionMap"), - ("read_only_space", 0x037e5): (109, "Tuple2Map"), - ("read_only_space", 0x0380d): (110, "WasmCapiFunctionDataMap"), - ("read_only_space", 0x03835): (111, "WasmDebugInfoMap"), - ("read_only_space", 0x0385d): (112, "WasmExceptionTagMap"), - ("read_only_space", 0x03885): (113, "WasmExportedFunctionDataMap"), - ("read_only_space", 0x038ad): (114, "WasmIndirectFunctionTableMap"), - ("read_only_space", 0x038d5): (115, "WasmJSFunctionDataMap"), - ("read_only_space", 0x038fd): (116, "WasmValueMap"), - ("read_only_space", 0x03925): (167, "InternalClassMap"), - ("read_only_space", 0x0394d): (175, "SmiPairMap"), - ("read_only_space", 0x03975): (174, "SmiBoxMap"), - ("read_only_space", 0x0399d): (149, "ExportedSubClassBaseMap"), - ("read_only_space", 0x039c5): (150, "ExportedSubClassMap"), - ("read_only_space", 0x039ed): (68, "AbstractInternalClassSubclass1Map"), - ("read_only_space", 0x03a15): (69, "AbstractInternalClassSubclass2Map"), - ("read_only_space", 0x03a3d): (135, "InternalClassWithSmiElementsMap"), - ("read_only_space", 0x03a65): (168, "InternalClassWithStructElementsMap"), - ("read_only_space", 0x03a8d): (176, "SortStateMap"), - ("read_only_space", 0x03ab5): (85, "AllocationSiteWithWeakNextMap"), - ("read_only_space", 0x03add): (85, "AllocationSiteWithoutWeakNextMap"), - ("read_only_space", 0x03b05): (76, "LoadHandler1Map"), - ("read_only_space", 0x03b2d): (76, "LoadHandler2Map"), - ("read_only_space", 0x03b55): (76, "LoadHandler3Map"), - ("read_only_space", 0x03b7d): (77, "StoreHandler0Map"), - ("read_only_space", 0x03ba5): (77, "StoreHandler1Map"), - ("read_only_space", 0x03bcd): (77, "StoreHandler2Map"), - ("read_only_space", 0x03bf5): (77, "StoreHandler3Map"), + ("read_only_space", 0x012b5): (98, "InterceptorInfoMap"), + ("read_only_space", 0x03369): (71, "PromiseFulfillReactionJobTaskMap"), + ("read_only_space", 0x03391): (72, "PromiseRejectReactionJobTaskMap"), + ("read_only_space", 0x033b9): (73, "CallableTaskMap"), + ("read_only_space", 0x033e1): (74, "CallbackTaskMap"), + ("read_only_space", 0x03409): (75, "PromiseResolveThenableJobTaskMap"), + ("read_only_space", 0x03431): (78, "FunctionTemplateInfoMap"), + ("read_only_space", 0x03459): (79, "ObjectTemplateInfoMap"), + ("read_only_space", 0x03481): (80, "AccessCheckInfoMap"), + ("read_only_space", 0x034a9): (81, "AccessorInfoMap"), + ("read_only_space", 0x034d1): (82, "AccessorPairMap"), + ("read_only_space", 0x034f9): (83, "AliasedArgumentsEntryMap"), + ("read_only_space", 0x03521): (84, "AllocationMementoMap"), + ("read_only_space", 0x03549): (87, "AsmWasmDataMap"), + ("read_only_space", 0x03571): (88, "AsyncGeneratorRequestMap"), + ("read_only_space", 0x03599): (89, "BreakPointMap"), + ("read_only_space", 0x035c1): (90, "BreakPointInfoMap"), + ("read_only_space", 0x035e9): (91, "CachedTemplateObjectMap"), + ("read_only_space", 0x03611): (93, "ClassPositionsMap"), + ("read_only_space", 0x03639): (94, "DebugInfoMap"), + ("read_only_space", 0x03661): (97, "FunctionTemplateRareDataMap"), + ("read_only_space", 0x03689): (99, "InterpreterDataMap"), + ("read_only_space", 0x036b1): (100, "PromiseCapabilityMap"), + ("read_only_space", 0x036d9): (101, "PromiseReactionMap"), + ("read_only_space", 0x03701): (102, "PropertyDescriptorObjectMap"), + ("read_only_space", 0x03729): (103, "PrototypeInfoMap"), + ("read_only_space", 0x03751): (104, "ScriptMap"), + ("read_only_space", 0x03779): (105, "SourceTextModuleInfoEntryMap"), + ("read_only_space", 0x037a1): (106, "StackFrameInfoMap"), + ("read_only_space", 0x037c9): (107, "StackTraceFrameMap"), + ("read_only_space", 0x037f1): (108, "TemplateObjectDescriptionMap"), + ("read_only_space", 0x03819): (109, "Tuple2Map"), + ("read_only_space", 0x03841): (110, "WasmCapiFunctionDataMap"), + ("read_only_space", 0x03869): (111, "WasmExceptionTagMap"), + ("read_only_space", 0x03891): (112, "WasmExportedFunctionDataMap"), + ("read_only_space", 0x038b9): (113, "WasmIndirectFunctionTableMap"), + ("read_only_space", 0x038e1): (114, "WasmJSFunctionDataMap"), + ("read_only_space", 0x03909): (115, "WasmValueMap"), + ("read_only_space", 0x03931): (135, "SloppyArgumentsElementsMap"), + ("read_only_space", 0x03959): (171, "OnHeapBasicBlockProfilerDataMap"), + ("read_only_space", 0x03981): (168, "InternalClassMap"), + ("read_only_space", 0x039a9): (177, "SmiPairMap"), + ("read_only_space", 0x039d1): (176, "SmiBoxMap"), + ("read_only_space", 0x039f9): (146, "ExportedSubClassBaseMap"), + ("read_only_space", 0x03a21): (147, "ExportedSubClassMap"), + ("read_only_space", 0x03a49): (68, "AbstractInternalClassSubclass1Map"), + ("read_only_space", 0x03a71): (69, "AbstractInternalClassSubclass2Map"), + ("read_only_space", 0x03a99): (134, "InternalClassWithSmiElementsMap"), + ("read_only_space", 0x03ac1): (169, "InternalClassWithStructElementsMap"), + ("read_only_space", 0x03ae9): (148, "ExportedSubClass2Map"), + ("read_only_space", 0x03b11): (178, "SortStateMap"), + ("read_only_space", 0x03b39): (85, "AllocationSiteWithWeakNextMap"), + ("read_only_space", 0x03b61): (85, "AllocationSiteWithoutWeakNextMap"), + ("read_only_space", 0x03b89): (76, "LoadHandler1Map"), + ("read_only_space", 0x03bb1): (76, "LoadHandler2Map"), + ("read_only_space", 0x03bd9): (76, "LoadHandler3Map"), + ("read_only_space", 0x03c01): (77, "StoreHandler0Map"), + ("read_only_space", 0x03c29): (77, "StoreHandler1Map"), + ("read_only_space", 0x03c51): (77, "StoreHandler2Map"), + ("read_only_space", 0x03c79): (77, "StoreHandler3Map"), ("map_space", 0x00121): (1057, "ExternalMap"), - ("map_space", 0x00149): (1073, "JSMessageObjectMap"), + ("map_space", 0x00149): (1072, "JSMessageObjectMap"), } # List of known V8 objects. @@ -381,26 +384,26 @@ KNOWN_OBJECTS = { ("read_only_space", 0x011b1): "EmptyObjectBoilerplateDescription", ("read_only_space", 0x011bd): "EmptyArrayBoilerplateDescription", ("read_only_space", 0x011f1): "EmptyClosureFeedbackCellArray", - ("read_only_space", 0x011f9): "EmptySloppyArgumentsElements", - ("read_only_space", 0x01209): "EmptySlowElementDictionary", - ("read_only_space", 0x0122d): "EmptyOrderedHashMap", - ("read_only_space", 0x01241): "EmptyOrderedHashSet", - ("read_only_space", 0x01255): "EmptyFeedbackMetadata", - ("read_only_space", 0x01261): "EmptyPropertyCell", - ("read_only_space", 0x01275): "EmptyPropertyDictionary", - ("read_only_space", 0x0129d): "NoOpInterceptorInfo", - ("read_only_space", 0x012ed): "EmptyWeakArrayList", - ("read_only_space", 0x012f9): "InfinityValue", - ("read_only_space", 0x01305): "MinusZeroValue", - ("read_only_space", 0x01311): "MinusInfinityValue", - ("read_only_space", 0x0131d): "SelfReferenceMarker", - ("read_only_space", 0x0135d): "OffHeapTrampolineRelocationInfo", - ("read_only_space", 0x01369): "TrampolineTrivialCodeDataContainer", - ("read_only_space", 0x01375): "TrampolinePromiseRejectionCodeDataContainer", - ("read_only_space", 0x01381): "GlobalThisBindingScopeInfo", - ("read_only_space", 0x013b9): "EmptyFunctionScopeInfo", - ("read_only_space", 0x013e1): "NativeScopeInfo", - ("read_only_space", 0x013fd): "HashSeed", + ("read_only_space", 0x011f9): "EmptySlowElementDictionary", + ("read_only_space", 0x0121d): "EmptyOrderedHashMap", + ("read_only_space", 0x01231): "EmptyOrderedHashSet", + ("read_only_space", 0x01245): "EmptyFeedbackMetadata", + ("read_only_space", 0x01251): "EmptyPropertyCell", + ("read_only_space", 0x01265): "EmptyPropertyDictionary", + ("read_only_space", 0x0128d): "NoOpInterceptorInfo", + ("read_only_space", 0x012dd): "EmptyWeakArrayList", + ("read_only_space", 0x012e9): "InfinityValue", + ("read_only_space", 0x012f5): "MinusZeroValue", + ("read_only_space", 0x01301): "MinusInfinityValue", + ("read_only_space", 0x0130d): "SelfReferenceMarker", + ("read_only_space", 0x0134d): "BasicBlockCountersMarker", + ("read_only_space", 0x01391): "OffHeapTrampolineRelocationInfo", + ("read_only_space", 0x0139d): "TrampolineTrivialCodeDataContainer", + ("read_only_space", 0x013a9): "TrampolinePromiseRejectionCodeDataContainer", + ("read_only_space", 0x013b5): "GlobalThisBindingScopeInfo", + ("read_only_space", 0x013ed): "EmptyFunctionScopeInfo", + ("read_only_space", 0x01415): "NativeScopeInfo", + ("read_only_space", 0x01431): "HashSeed", ("old_space", 0x00121): "ArgumentsIteratorAccessor", ("old_space", 0x00165): "ArrayLengthAccessor", ("old_space", 0x001a9): "BoundFunctionLengthAccessor", diff --git a/chromium/v8/tools/v8windbg/base/utilities.cc b/chromium/v8/tools/v8windbg/base/utilities.cc index a59e95f46fd..1f0e2bc6708 100644 --- a/chromium/v8/tools/v8windbg/base/utilities.cc +++ b/chromium/v8/tools/v8windbg/base/utilities.cc @@ -133,6 +133,15 @@ HRESULT UnboxULong64(IModelObject* object, ULONG64* value, bool convert) { return S_OK; } +HRESULT GetInt32(IDebugHostConstant* object, int* value) { + variant_t variant; + RETURN_IF_FAIL(object->GetValue(&variant)); + + if (variant.vt != VT_I4) return E_FAIL; + *value = variant.ullVal; + return S_OK; +} + HRESULT CreateInt32(int value, IModelObject** pp_int) { HRESULT hr = S_OK; *pp_int = nullptr; diff --git a/chromium/v8/tools/v8windbg/base/utilities.h b/chromium/v8/tools/v8windbg/base/utilities.h index e26bb287804..06af6c35875 100644 --- a/chromium/v8/tools/v8windbg/base/utilities.h +++ b/chromium/v8/tools/v8windbg/base/utilities.h @@ -55,6 +55,8 @@ HRESULT CreateULong64(ULONG64 value, IModelObject** pp_int); HRESULT UnboxULong64(IModelObject* object, ULONG64* value, bool convert = false); +HRESULT GetInt32(IDebugHostConstant* object, int* value); + HRESULT CreateInt32(int value, IModelObject** pp_int); HRESULT CreateUInt32(uint32_t value, IModelObject** pp_int); diff --git a/chromium/v8/tools/v8windbg/src/object-inspection.cc b/chromium/v8/tools/v8windbg/src/object-inspection.cc index ce0370a697f..6f90614bd5c 100644 --- a/chromium/v8/tools/v8windbg/src/object-inspection.cc +++ b/chromium/v8/tools/v8windbg/src/object-inspection.cc @@ -585,6 +585,79 @@ IFACEMETHODIMP V8LocalValueProperty::SetValue( return E_NOTIMPL; } +IFACEMETHODIMP V8InternalCompilerNodeIdProperty::GetValue( + PCWSTR pwsz_key, IModelObject* p_v8_compiler_node_instance, + IModelObject** pp_value) noexcept { + WRL::ComPtr<IModelObject> sp_bit_field; + RETURN_IF_FAIL(p_v8_compiler_node_instance->GetRawValue( + SymbolKind::SymbolField, L"bit_field_", RawSearchNone, &sp_bit_field)); + + uint64_t bit_field_value; + RETURN_IF_FAIL( + UnboxULong64(sp_bit_field.Get(), &bit_field_value, true /*convert*/)); + + WRL::ComPtr<IDebugHostContext> sp_host_context; + RETURN_IF_FAIL(p_v8_compiler_node_instance->GetContext(&sp_host_context)); + + WRL::ComPtr<IDebugHostType> sp_id_field_type; + RETURN_IF_FAIL(Extension::Current() + ->GetV8Module(sp_host_context) + ->FindTypeByName(L"v8::internal::compiler::Node::IdField", + &sp_id_field_type)); + + // Get 2nd template parameter as 24 in class. + // v8::base::BitField<v8::internal::compiler::NodeId, 0, 24>. + bool is_generic; + RETURN_IF_FAIL(sp_id_field_type->IsGeneric(&is_generic)); + if (!is_generic) return E_FAIL; + + WRL::ComPtr<IDebugHostSymbol> sp_k_size_arg; + RETURN_IF_FAIL(sp_id_field_type->GetGenericArgumentAt(2, &sp_k_size_arg)); + + WRL::ComPtr<IDebugHostConstant> sp_k_size_constant; + RETURN_IF_FAIL(sp_k_size_arg.As(&sp_k_size_constant)); + + int k_size; + RETURN_IF_FAIL(GetInt32(sp_k_size_constant.Get(), &k_size)); + + // Compute node_id. + uint32_t node_id = bit_field_value & (0xFFFFFFFF >> k_size); + RETURN_IF_FAIL(CreateUInt32(node_id, pp_value)); + + return S_OK; +} + +IFACEMETHODIMP V8InternalCompilerNodeIdProperty::SetValue( + PCWSTR /*pwsz_key*/, IModelObject* /*p_process_instance*/, + IModelObject* /*p_value*/) noexcept { + return E_NOTIMPL; +} + +IFACEMETHODIMP V8InternalCompilerBitsetNameProperty::GetValue( + PCWSTR pwsz_key, IModelObject* p_v8_compiler_type_instance, + IModelObject** pp_value) noexcept { + WRL::ComPtr<IModelObject> sp_payload; + RETURN_IF_FAIL(p_v8_compiler_type_instance->GetRawValue( + SymbolKind::SymbolField, L"payload_", RawSearchNone, &sp_payload)); + + uint64_t payload_value; + RETURN_IF_FAIL( + UnboxULong64(sp_payload.Get(), &payload_value, true /*convert*/)); + + const char* bitset_name = ::BitsetName(payload_value); + if (!bitset_name) return E_FAIL; + std::string name(bitset_name); + RETURN_IF_FAIL(CreateString(ConvertToU16String(name), pp_value)); + + return S_OK; +} + +IFACEMETHODIMP V8InternalCompilerBitsetNameProperty::SetValue( + PCWSTR /*pwsz_key*/, IModelObject* /*p_process_instance*/, + IModelObject* /*p_value*/) noexcept { + return E_NOTIMPL; +} + constexpr wchar_t usage[] = LR"(Invalid arguments. First argument should be a uint64 representing the tagged value to investigate. diff --git a/chromium/v8/tools/v8windbg/src/object-inspection.h b/chromium/v8/tools/v8windbg/src/object-inspection.h index 27283ca5569..a280b05cade 100644 --- a/chromium/v8/tools/v8windbg/src/object-inspection.h +++ b/chromium/v8/tools/v8windbg/src/object-inspection.h @@ -245,6 +245,38 @@ class V8LocalValueProperty IModelObject* /*p_value*/); }; +// The implemention of the "NodeId" getter for v8::internal::compiler::Node +// type. +class V8InternalCompilerNodeIdProperty + : public WRL::RuntimeClass< + WRL::RuntimeClassFlags<WRL::RuntimeClassType::ClassicCom>, + IModelPropertyAccessor> { + public: + IFACEMETHOD(GetValue) + (PCWSTR pwsz_key, IModelObject* p_v8_object_instance, + IModelObject** pp_value); + + IFACEMETHOD(SetValue) + (PCWSTR /*pwsz_key*/, IModelObject* /*p_process_instance*/, + IModelObject* /*p_value*/); +}; + +// The implemention of the "bitset_name" getter for v8::internal::compiler::Type +// type. +class V8InternalCompilerBitsetNameProperty + : public WRL::RuntimeClass< + WRL::RuntimeClassFlags<WRL::RuntimeClassType::ClassicCom>, + IModelPropertyAccessor> { + public: + IFACEMETHOD(GetValue) + (PCWSTR pwsz_key, IModelObject* p_v8_compiler_type_instance, + IModelObject** pp_value); + + IFACEMETHOD(SetValue) + (PCWSTR /*pwsz_key*/, IModelObject* /*p_process_instance*/, + IModelObject* /*p_value*/); +}; + // A way that someone can directly inspect a tagged value, even if that value // isn't in memory (from a register, or the user's imagination, etc.). class InspectV8ObjectMethod diff --git a/chromium/v8/tools/v8windbg/src/v8-debug-helper-interop.cc b/chromium/v8/tools/v8windbg/src/v8-debug-helper-interop.cc index 4a8dcc9add7..0767ff5f09e 100644 --- a/chromium/v8/tools/v8windbg/src/v8-debug-helper-interop.cc +++ b/chromium/v8/tools/v8windbg/src/v8-debug-helper-interop.cc @@ -155,3 +155,5 @@ std::vector<std::u16string> ListObjectClasses() { } return result; } + +const char* BitsetName(uint64_t payload) { return d::BitsetName(payload); } diff --git a/chromium/v8/tools/v8windbg/src/v8-debug-helper-interop.h b/chromium/v8/tools/v8windbg/src/v8-debug-helper-interop.h index 96bd59b30ea..9208f098327 100644 --- a/chromium/v8/tools/v8windbg/src/v8-debug-helper-interop.h +++ b/chromium/v8/tools/v8windbg/src/v8-debug-helper-interop.h @@ -135,4 +135,6 @@ inline uint64_t ExpandCompressedPointer(uint32_t ptr) { return ptr; } std::vector<std::u16string> ListObjectClasses(); +const char* BitsetName(uint64_t payload); + #endif // V8_TOOLS_V8WINDBG_SRC_V8_DEBUG_HELPER_INTEROP_H_ diff --git a/chromium/v8/tools/v8windbg/src/v8windbg-extension.cc b/chromium/v8/tools/v8windbg/src/v8windbg-extension.cc index 68c90d2833b..58a520cff1f 100644 --- a/chromium/v8/tools/v8windbg/src/v8windbg-extension.cc +++ b/chromium/v8/tools/v8windbg/src/v8windbg-extension.cc @@ -215,7 +215,8 @@ HRESULT Extension::Initialize() { &sp_object_type_signature)); RETURN_IF_FAIL(sp_data_model_manager->RegisterModelForTypeSignature( sp_object_type_signature.Get(), sp_object_data_model_.Get())); - registered_object_types_.push_back(sp_object_type_signature); + registered_types_.push_back( + {sp_object_type_signature.Get(), sp_object_data_model_.Get()}); } // Create an instance of the DataModel parent for custom iterable fields. @@ -244,7 +245,7 @@ HRESULT Extension::Initialize() { sp_debug_host_symbols->CreateTypeSignature(name, nullptr, &signature)); RETURN_IF_FAIL(sp_data_model_manager->RegisterModelForTypeSignature( signature.Get(), sp_local_data_model_.Get())); - registered_handle_types_.push_back(signature); + registered_types_.push_back({signature.Get(), sp_local_data_model_.Get()}); } // Add the 'Value' property to the parent model. @@ -279,6 +280,46 @@ HRESULT Extension::Initialize() { RETURN_IF_FAIL(OverrideLocalsGetter(stack_frame.Get(), L"Parameters", /*is_parameters=*/true)); + // Add node_id property for v8::internal::compiler::Node. + RETURN_IF_FAIL( + RegisterAndAddPropertyForClass<V8InternalCompilerNodeIdProperty>( + L"v8::internal::compiler::Node", L"node_id", + sp_compiler_node_data_model_)); + + // Add bitset_name property for v8::internal::compiler::Type. + RETURN_IF_FAIL( + RegisterAndAddPropertyForClass<V8InternalCompilerBitsetNameProperty>( + L"v8::internal::compiler::Type", L"bitset_name", + sp_compiler_type_data_model_)); + + return S_OK; +} + +template <class PropertyClass> +HRESULT Extension::RegisterAndAddPropertyForClass( + const wchar_t* class_name, const wchar_t* property_name, + WRL::ComPtr<IModelObject> sp_data_model) { + // Create an instance of the DataModel parent class. + auto instance_data_model{WRL::Make<V8LocalDataModel>()}; + RETURN_IF_FAIL(sp_data_model_manager->CreateDataModelObject( + instance_data_model.Get(), &sp_data_model)); + + // Register that parent model. + WRL::ComPtr<IDebugHostTypeSignature> class_signature; + RETURN_IF_FAIL(sp_debug_host_symbols->CreateTypeSignature(class_name, nullptr, + &class_signature)); + RETURN_IF_FAIL(sp_data_model_manager->RegisterModelForTypeSignature( + class_signature.Get(), sp_data_model.Get())); + registered_types_.push_back({class_signature.Get(), sp_data_model.Get()}); + + // Add the property to the parent model. + auto property{WRL::Make<PropertyClass>()}; + WRL::ComPtr<IModelObject> sp_property_model; + RETURN_IF_FAIL(CreateProperty(sp_data_model_manager.Get(), property.Get(), + &sp_property_model)); + RETURN_IF_FAIL( + sp_data_model->SetKey(property_name, sp_property_model.Get(), nullptr)); + return S_OK; } @@ -318,18 +359,24 @@ Extension::PropertyOverride::PropertyOverride(const PropertyOverride&) = Extension::PropertyOverride& Extension::PropertyOverride::operator=( const PropertyOverride&) = default; +Extension::RegistrationType::RegistrationType() = default; +Extension::RegistrationType::RegistrationType( + IDebugHostTypeSignature* sp_signature, IModelObject* sp_data_model) + : sp_signature(sp_signature), sp_data_model(sp_data_model) {} +Extension::RegistrationType::~RegistrationType() = default; +Extension::RegistrationType::RegistrationType(const RegistrationType&) = + default; +Extension::RegistrationType& Extension::RegistrationType::operator=( + const RegistrationType&) = default; + Extension::~Extension() { sp_debug_host_extensibility->DestroyFunctionAlias(pcur_isolate); sp_debug_host_extensibility->DestroyFunctionAlias(plist_chunks); sp_debug_host_extensibility->DestroyFunctionAlias(pv8_object); - for (const auto& registered : registered_object_types_) { - sp_data_model_manager->UnregisterModelForTypeSignature( - sp_object_data_model_.Get(), registered.Get()); - } - for (const auto& registered : registered_handle_types_) { + for (const auto& registered : registered_types_) { sp_data_model_manager->UnregisterModelForTypeSignature( - sp_local_data_model_.Get(), registered.Get()); + registered.sp_data_model.Get(), registered.sp_signature.Get()); } for (const auto& override : overridden_properties_) { diff --git a/chromium/v8/tools/v8windbg/src/v8windbg-extension.h b/chromium/v8/tools/v8windbg/src/v8windbg-extension.h index d54f43c847e..46331611523 100644 --- a/chromium/v8/tools/v8windbg/src/v8windbg-extension.h +++ b/chromium/v8/tools/v8windbg/src/v8windbg-extension.h @@ -46,6 +46,11 @@ class Extension { HRESULT OverrideLocalsGetter(IModelObject* parent, const wchar_t* key_name, bool is_parameters); + template <class PropertyClass> + HRESULT RegisterAndAddPropertyForClass( + const wchar_t* class_name, const wchar_t* property_name, + WRL::ComPtr<IModelObject> sp_data_model); + // A property that has been overridden by this extension. The original value // must be put back in place during ~Extension. struct PropertyOverride { @@ -62,20 +67,32 @@ class Extension { WRL::ComPtr<IKeyStore> original_metadata; }; + struct RegistrationType { + RegistrationType(); + RegistrationType(IDebugHostTypeSignature* sp_signature, + IModelObject* sp_data_model); + ~RegistrationType(); + RegistrationType(const RegistrationType&); + RegistrationType& operator=(const RegistrationType&); + + WRL::ComPtr<IDebugHostTypeSignature> sp_signature; + WRL::ComPtr<IModelObject> sp_data_model; + }; + static std::unique_ptr<Extension> current_extension_; WRL::ComPtr<IModelObject> sp_object_data_model_; WRL::ComPtr<IModelObject> sp_local_data_model_; + WRL::ComPtr<IModelObject> sp_compiler_node_data_model_; + WRL::ComPtr<IModelObject> sp_compiler_type_data_model_; WRL::ComPtr<IModelObject> sp_indexed_field_model_; WRL::ComPtr<IDebugHostModule> sp_v8_module_; std::unordered_map<std::u16string, WRL::ComPtr<IDebugHostType>> cached_v8_module_types_; - std::vector<WRL::ComPtr<IDebugHostTypeSignature>> registered_object_types_; - std::vector<WRL::ComPtr<IDebugHostTypeSignature>> registered_handle_types_; + std::vector<RegistrationType> registered_types_; std::vector<PropertyOverride> overridden_properties_; WRL::ComPtr<IDebugHostContext> sp_v8_module_ctx_; ULONG v8_module_proc_id_; }; - #endif // V8_TOOLS_V8WINDBG_SRC_V8WINDBG_EXTENSION_H_ diff --git a/chromium/v8/tools/wasm/update-wasm-spec-tests.sh b/chromium/v8/tools/wasm/update-wasm-spec-tests.sh index b3e9185c4df..8a63f94368e 100755 --- a/chromium/v8/tools/wasm/update-wasm-spec-tests.sh +++ b/chromium/v8/tools/wasm/update-wasm-spec-tests.sh @@ -95,7 +95,10 @@ for repo in ${repos}; do log_and_run ./run.py --wasm ../../interpreter/wasm ${rel_filename} --out _build 2> /dev/null fi done - log_and_run cp _build/*.js ${SPEC_TEST_DIR}/tests/proposals/${repo}/ + + if ls _build/*.js > /dev/null; then + log_and_run cp _build/*.js ${SPEC_TEST_DIR}/tests/proposals/${repo}/ + fi echo ">> Process js-api tests" log_and_run mkdir ${JS_API_TEST_DIR}/tests/proposals/${repo} diff --git a/chromium/v8/tools/whitespace.txt b/chromium/v8/tools/whitespace.txt index 4668daa518f..94f29d13401 100644 --- a/chromium/v8/tools/whitespace.txt +++ b/chromium/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/chromium/v8/tools/zone-stats/categories.js b/chromium/v8/tools/zone-stats/categories.js new file mode 100644 index 00000000000..96c4e32eaf7 --- /dev/null +++ b/chromium/v8/tools/zone-stats/categories.js @@ -0,0 +1,129 @@ +// 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. + +const UNCLASSIFIED_CATEGORY = 'unclassified'; +const UNCLASSIFIED_CATEGORY_NAME = 'Unclassified'; + +// Categories for zones. +export const CATEGORIES = new Map([ + [ + 'parser', new Set([ + 'AstStringConstants', + 'ParseInfo', + 'Parser', + ]) + ], + [ + 'misc', new Set([ + 'Run', + 'CanonicalHandleScope', + 'Temporary scoped zone', + 'UpdateFieldType', + ]) + ], + [ + 'interpreter', new Set([ + 'InterpreterCompilationJob', + ]) + ], + [ + 'regexp', new Set([ + 'CompileIrregexp', + ]) + ], + [ + 'compiler-huge', new Set([ + 'graph-zone', + 'instruction-zone', + 'pipeline-compilation-job-zone', + 'register-allocation-zone', + 'register-allocator-verifier-zone', + ]) + ], + [ + 'compiler-other', new Set([ + 'Compile', + 'V8.TFAllocateFPRegisters', + 'V8.TFAllocateGeneralRegisters', + 'V8.TFAssembleCode', + 'V8.TFAssignSpillSlots', + 'V8.TFBuildLiveRangeBundles', + 'V8.TFBuildLiveRanges', + 'V8.TFBytecodeGraphBuilder', + 'V8.TFCommitAssignment', + 'V8.TFConnectRanges', + 'V8.TFControlFlowOptimization', + 'V8.TFDecideSpillingMode', + 'V8.TFDecompressionOptimization', + 'V8.TFEarlyOptimization', + 'V8.TFEarlyTrimming', + 'V8.TFEffectLinearization', + 'V8.TFEscapeAnalysis', + 'V8.TFFinalizeCode', + 'V8.TFFrameElision', + 'V8.TFGenericLowering', + 'V8.TFHeapBrokerInitialization', + 'V8.TFInlining', + 'V8.TFJumpThreading', + 'V8.TFLateGraphTrimming', + 'V8.TFLateOptimization', + 'V8.TFLoadElimination', + 'V8.TFLocateSpillSlots', + 'V8.TFLoopPeeling', + 'V8.TFMachineOperatorOptimization', + 'V8.TFMeetRegisterConstraints', + 'V8.TFMemoryOptimization', + 'V8.TFOptimizeMoves', + 'V8.TFPopulatePointerMaps', + 'V8.TFResolveControlFlow', + 'V8.TFResolvePhis', + 'V8.TFScheduling', + 'V8.TFSelectInstructions', + 'V8.TFSerializeMetadata', + 'V8.TFSimplifiedLowering', + 'V8.TFStoreStoreElimination', + 'V8.TFTypedLowering', + 'V8.TFTyper', + 'V8.TFUntyper', + 'V8.TFVerifyGraph', + 'ValidatePendingAssessment', + 'codegen-zone', + ]) + ], + [UNCLASSIFIED_CATEGORY, new Set()], +]); + +// Maps category to description text that is shown in html. +export const CATEGORY_NAMES = new Map([ + ['parser', 'Parser'], + ['misc', 'Misc'], + ['interpreter', 'Ignition'], + ['regexp', 'Regexp compiler'], + ['compiler-huge', 'TurboFan (huge zones)'], + ['compiler-other', 'TurboFan (other zones)'], + [UNCLASSIFIED_CATEGORY, UNCLASSIFIED_CATEGORY_NAME], +]); + +function buildZoneToCategoryMap() { + const map = new Map(); + for (let [category, zone_names] of CATEGORIES.entries()) { + for (let zone_name of zone_names) { + if (map.has(zone_name)) { + console.error("Zone belongs to multiple categories: " + zone_name); + } else { + map.set(zone_name, category); + } + } + } + return map; +} + +const CATEGORY_BY_ZONE = buildZoneToCategoryMap(); + +// Maps zone name to category. +export function categoryByZoneName(zone_name) { + const category = CATEGORY_BY_ZONE.get(zone_name); + if (category !== undefined) return category; + return UNCLASSIFIED_CATEGORY; +} diff --git a/chromium/v8/tools/zone-stats/details-selection-template.html b/chromium/v8/tools/zone-stats/details-selection-template.html new file mode 100644 index 00000000000..ef1e2f68b9b --- /dev/null +++ b/chromium/v8/tools/zone-stats/details-selection-template.html @@ -0,0 +1,146 @@ +<!-- 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. --> +<style> +#dataSelectionSection { + display: none; +} + +.box { + border-left: dashed 1px #666666; + border-right: dashed 1px #666666; + border-bottom: dashed 1px #666666; + padding: 10px; + overflow: hidden; + position: relative; +} + +.box:nth-of-type(1) { + border-top: dashed 1px #666666; + border-radius: 5px 5px 0px 0px; +} + +.box:last-of-type { + border-radius: 0px 0px 5px 5px; +} + +.box > ul { + margin: 0px; + padding: 0px; +} + +.box > ul > li { + display: inline-block; +} + +.box > ul > li:not(:first-child) { + margin-left: 10px; +} + +.box > ul > li:first-child { + font-weight: bold; +} + +.zonesSelectBox { + position: relative; + overflow: hidden; + float: left; + padding: 0px 5px 2px 0px; + margin: 3px; + border-radius: 3px; +} + +.zonesSelectBox > label { + font-size: xx-small; +} + +.zonesSelectBox > input { + vertical-align: middle; +} + +.percentBackground { + position: absolute; + width: 200%; + height: 100%; + left: 0%; + top: 0px; + margin-left: -100%; + transition: all 1s ease-in-out; +} + +.zonesSelectBox > .percentBackground { + background: linear-gradient(90deg, #68b0f7 50%, #b3d9ff 50%); + z-index: -1; +} +.box > .percentBackground { + background: linear-gradient(90deg, #e0edfe 50%, #fff 50%); + z-index: -2; +} + +#categories { + margin-top: 10px; +} + +#category-filter { + text-align: right; + width: 50px; +} + +</style> +<section id="dataSelectionSection"> + <h2>Data selection</h2> + <ul> + <li> + <label for="isolate-select"> + Isolate + </label> + <select id="isolate-select"> + <option>No data</option> + </select> + </li> + <li> + <label for="data-view-select"> + Data view + </label> + <select id="data-view-select"> + <option>No data</option> + </select> + </li> + <li> + <label for="show-totals-select"> + Show total allocated/used zone memory + </label> + <input type="checkbox" id="show-totals-select" checked> + </li> + <li> + <label for="data-kind-select"> + Data kind + </label> + <select id="data-kind-select"> + <option>No data</option> + </select> + </li> + <li> + <label for="time-start-select"> + Time start + </label> + <input type="number" id="time-start-select" value="0">ms</input> + </li> + <li> + <label for="time-end-select"> + Time end + </label> + <input type="number" id="time-end-select" value="0">ms</input> + </li> + <li> + <label for="memory-usage-sample-select"> + Memory usage sample (at a specific time in ms) + </label> + <select id="memory-usage-sample-select"> + <option>No data</option> + </select> + </li> + </ul> + + <div id="categories"></div> +</section> diff --git a/chromium/v8/tools/zone-stats/details-selection.js b/chromium/v8/tools/zone-stats/details-selection.js new file mode 100644 index 00000000000..b25a11337a3 --- /dev/null +++ b/chromium/v8/tools/zone-stats/details-selection.js @@ -0,0 +1,365 @@ +// 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. + +'use strict'; + +import {CATEGORIES, CATEGORY_NAMES, categoryByZoneName} from './categories.js'; + +export const VIEW_TOTALS = 'by-totals'; +export const VIEW_BY_ZONE_NAME = 'by-zone-name'; +export const VIEW_BY_ZONE_CATEGORY = 'by-zone-category'; + +export const KIND_ALLOCATED_MEMORY = 'kind-detailed-allocated'; +export const KIND_USED_MEMORY = 'kind-detailed-used'; + +defineCustomElement('details-selection', (templateText) => + class DetailsSelection extends HTMLElement { + constructor() { + super(); + const shadowRoot = this.attachShadow({mode: 'open'}); + shadowRoot.innerHTML = templateText; + this.isolateSelect.addEventListener( + 'change', e => this.handleIsolateChange(e)); + this.dataViewSelect.addEventListener( + 'change', e => this.notifySelectionChanged(e)); + this.dataKindSelect.addEventListener( + 'change', e => this.notifySelectionChanged(e)); + this.showTotalsSelect.addEventListener( + 'change', e => this.notifySelectionChanged(e)); + this.memoryUsageSampleSelect.addEventListener( + 'change', e => this.notifySelectionChanged(e)); + this.timeStartSelect.addEventListener( + 'change', e => this.notifySelectionChanged(e)); + this.timeEndSelect.addEventListener( + 'change', e => this.notifySelectionChanged(e)); + } + + connectedCallback() { + for (let category of CATEGORIES.keys()) { + this.$('#categories').appendChild(this.buildCategory(category)); + } + } + + set data(value) { + this._data = value; + this.dataChanged(); + } + + get data() { + return this._data; + } + + get selectedIsolate() { + return this._data[this.selection.isolate]; + } + + get selectedData() { + console.assert(this.data, 'invalid data'); + console.assert(this.selection, 'invalid selection'); + const time = this.selection.time; + return this.selectedIsolate.samples.get(time); + } + + $(id) { + return this.shadowRoot.querySelector(id); + } + + querySelectorAll(query) { + return this.shadowRoot.querySelectorAll(query); + } + + get dataViewSelect() { + return this.$('#data-view-select'); + } + + get dataKindSelect() { + return this.$('#data-kind-select'); + } + + get isolateSelect() { + return this.$('#isolate-select'); + } + + get memoryUsageSampleSelect() { + return this.$('#memory-usage-sample-select'); + } + + get showTotalsSelect() { + return this.$('#show-totals-select'); + } + + get timeStartSelect() { + return this.$('#time-start-select'); + } + + get timeEndSelect() { + return this.$('#time-end-select'); + } + + buildCategory(name) { + const div = document.createElement('div'); + div.id = name; + div.classList.add('box'); + const ul = document.createElement('ul'); + div.appendChild(ul); + const name_li = document.createElement('li'); + ul.appendChild(name_li); + name_li.innerHTML = CATEGORY_NAMES.get(name); + const percent_li = document.createElement('li'); + ul.appendChild(percent_li); + percent_li.innerHTML = '0%'; + percent_li.id = name + 'PercentContent'; + const all_li = document.createElement('li'); + ul.appendChild(all_li); + const all_button = document.createElement('button'); + all_li.appendChild(all_button); + all_button.innerHTML = 'All'; + all_button.addEventListener('click', e => this.selectCategory(name)); + const none_li = document.createElement('li'); + ul.appendChild(none_li); + const none_button = document.createElement('button'); + none_li.appendChild(none_button); + none_button.innerHTML = 'None'; + none_button.addEventListener('click', e => this.unselectCategory(name)); + const innerDiv = document.createElement('div'); + div.appendChild(innerDiv); + innerDiv.id = name + 'Content'; + const percentDiv = document.createElement('div'); + div.appendChild(percentDiv); + percentDiv.className = 'percentBackground'; + percentDiv.id = name + 'PercentBackground'; + return div; + } + + dataChanged() { + this.selection = {categories: {}, zones: new Map()}; + this.resetUI(true); + this.populateIsolateSelect(); + this.handleIsolateChange(); + this.$('#dataSelectionSection').style.display = 'block'; + } + + populateIsolateSelect() { + let isolates = Object.entries(this.data); + // Sort by peak heap memory consumption. + isolates.sort((a, b) => b[1].peakAllocatedMemory - a[1].peakAllocatedMemory); + this.populateSelect( + '#isolate-select', isolates, (key, isolate) => isolate.getLabel()); + } + + resetUI(resetIsolateSelect) { + if (resetIsolateSelect) removeAllChildren(this.isolateSelect); + + removeAllChildren(this.dataViewSelect); + removeAllChildren(this.dataKindSelect); + removeAllChildren(this.memoryUsageSampleSelect); + this.clearCategories(); + } + + handleIsolateChange(e) { + this.selection.isolate = this.isolateSelect.value; + if (this.selection.isolate.length === 0) { + this.selection.isolate = null; + return; + } + this.resetUI(false); + this.populateSelect( + '#data-view-select', [ + [VIEW_TOTALS, 'Total memory usage'], + [VIEW_BY_ZONE_NAME, 'Selected zones types'], + [VIEW_BY_ZONE_CATEGORY, 'Selected zone categories'], + ], + (key, label) => label, VIEW_TOTALS); + this.populateSelect( + '#data-kind-select', [ + [KIND_ALLOCATED_MEMORY, 'Allocated memory per zone'], + [KIND_USED_MEMORY, 'Used memory per zone'], + ], + (key, label) => label, KIND_ALLOCATED_MEMORY); + + this.populateSelect( + '#memory-usage-sample-select', + [...this.selectedIsolate.samples.entries()].filter(([time, sample]) => { + // Remove samples that does not have detailed per-zone data. + return sample.zones !== undefined; + }), + (time, sample, index) => { + return ((index + ': ').padStart(6, '\u00A0') + + formatSeconds(time).padStart(8, '\u00A0') + ' ' + + formatBytes(sample.allocated).padStart(12, '\u00A0')); + }, + this.selectedIsolate.peakUsageTime); + + this.timeStartSelect.value = this.selectedIsolate.start; + this.timeEndSelect.value = this.selectedIsolate.end; + + this.populateCategories(); + this.notifySelectionChanged(); + } + + notifySelectionChanged(e) { + if (!this.selection.isolate) return; + + this.selection.data_view = this.dataViewSelect.value; + this.selection.data_kind = this.dataKindSelect.value; + this.selection.categories = Object.create(null); + this.selection.zones = new Map(); + this.$('#categories').style.display = 'none'; + for (let category of CATEGORIES.keys()) { + const selected = this.selectedInCategory(category); + if (selected.length > 0) this.selection.categories[category] = selected; + for (const zone_name of selected) { + this.selection.zones.set(zone_name, category); + } + } + this.$('#categories').style.display = 'block'; + this.selection.category_names = CATEGORY_NAMES; + this.selection.show_totals = this.showTotalsSelect.checked; + this.selection.time = Number(this.memoryUsageSampleSelect.value); + this.selection.timeStart = Number(this.timeStartSelect.value); + this.selection.timeEnd = Number(this.timeEndSelect.value); + this.updatePercentagesInCategory(); + this.updatePercentagesInZones(); + this.dispatchEvent(new CustomEvent( + 'change', {bubbles: true, composed: true, detail: this.selection})); + } + + updatePercentagesInCategory() { + const overalls = Object.create(null); + let overall = 0; + // Reset all categories. + this.selection.category_names.forEach((_, category) => { + overalls[category] = 0; + }); + // Only update categories that have selections. + Object.entries(this.selection.categories).forEach(([category, value]) => { + overalls[category] = + Object.values(value).reduce( + (accu, current) => { + const zone_data = this.selectedData.zones.get(current); + return zone_data === undefined ? accu + : accu + zone_data.allocated; + }, 0) / + KB; + overall += overalls[category]; + }); + Object.entries(overalls).forEach(([category, category_overall]) => { + let percents = category_overall / overall * 100; + this.$(`#${category}PercentContent`).innerHTML = + `${percents.toFixed(1)}%`; + this.$('#' + category + 'PercentBackground').style.left = percents + '%'; + }); + } + + updatePercentagesInZones() { + const selected_data = this.selectedData; + const zones_data = selected_data.zones; + const total_allocated = selected_data.allocated; + this.querySelectorAll('.zonesSelectBox input').forEach(checkbox => { + const zone_name = checkbox.value; + const zone_data = zones_data.get(zone_name); + const zone_allocated = zone_data === undefined ? 0 : zone_data.allocated; + if (zone_allocated == 0) { + checkbox.parentNode.style.display = 'none'; + } else { + const percents = zone_allocated / total_allocated; + const percent_div = checkbox.parentNode.querySelector('.percentBackground'); + percent_div.style.left = (percents * 100) + '%'; + checkbox.parentNode.style.display = 'block'; + } + }); + } + + selectedInCategory(category) { + let tmp = []; + this.querySelectorAll('input[name=' + category + 'Checkbox]:checked') + .forEach(checkbox => tmp.push(checkbox.value)); + return tmp; + } + + createOption(value, text) { + const option = document.createElement('option'); + option.value = value; + option.text = text; + return option; + } + + populateSelect(id, iterable, labelFn = null, autoselect = null) { + if (labelFn == null) labelFn = e => e; + let index = 0; + for (let [key, value] of iterable) { + index++; + const label = labelFn(key, value, index); + const option = this.createOption(key, label); + if (autoselect === key) { + option.selected = 'selected'; + } + this.$(id).appendChild(option); + } + } + + clearCategories() { + for (const category of CATEGORIES.keys()) { + let f = this.$('#' + category + 'Content'); + while (f.firstChild) { + f.removeChild(f.firstChild); + } + } + } + + populateCategories() { + this.clearCategories(); + const categories = Object.create(null); + for (let cat of CATEGORIES.keys()) { + categories[cat] = []; + } + + for (const [zone_name, zone_stats] of this.selectedIsolate.zones) { + const category = categoryByZoneName(zone_name); + categories[category].push(zone_name); + } + for (let category of Object.keys(categories)) { + categories[category].sort(); + for (let zone_name of categories[category]) { + this.$('#' + category + 'Content') + .appendChild(this.createCheckBox(zone_name, category)); + } + } + } + + unselectCategory(category) { + this.querySelectorAll('input[name=' + category + 'Checkbox]') + .forEach(checkbox => checkbox.checked = false); + this.notifySelectionChanged(); + } + + selectCategory(category) { + this.querySelectorAll('input[name=' + category + 'Checkbox]') + .forEach(checkbox => checkbox.checked = true); + this.notifySelectionChanged(); + } + + createCheckBox(instance_type, category) { + const div = document.createElement('div'); + div.classList.add('zonesSelectBox'); + div.style.width = "200px"; + const input = document.createElement('input'); + div.appendChild(input); + input.type = 'checkbox'; + input.name = category + 'Checkbox'; + input.checked = 'checked'; + input.id = instance_type + 'Checkbox'; + input.instance_type = instance_type; + input.value = instance_type; + input.addEventListener('change', e => this.notifySelectionChanged(e)); + const label = document.createElement('label'); + div.appendChild(label); + label.innerText = instance_type; + label.htmlFor = instance_type + 'Checkbox'; + const percentDiv = document.createElement('div'); + percentDiv.className = 'percentBackground'; + div.appendChild(percentDiv); + return div; + } +}); diff --git a/chromium/v8/tools/zone-stats/global-timeline-template.html b/chromium/v8/tools/zone-stats/global-timeline-template.html new file mode 100644 index 00000000000..49e75646f16 --- /dev/null +++ b/chromium/v8/tools/zone-stats/global-timeline-template.html @@ -0,0 +1,16 @@ +<!-- 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. --> +<style> +#chart { + width: 100%; + height: 500px; +} +</style> +<div id="container" style="display: none;"> + <h2>Stats</h2> + <p>Peak allocated zone memory <span id="peak-memory-label"></span></p> + + <h2>Timeline</h2> + <div id="chart"></div> +</div> diff --git a/chromium/v8/tools/zone-stats/global-timeline.js b/chromium/v8/tools/zone-stats/global-timeline.js new file mode 100644 index 00000000000..ea1793101b4 --- /dev/null +++ b/chromium/v8/tools/zone-stats/global-timeline.js @@ -0,0 +1,323 @@ +// 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. + +'use strict'; + +import {categoryByZoneName} from './categories.js'; + +import { + VIEW_TOTALS, + VIEW_BY_ZONE_NAME, + VIEW_BY_ZONE_CATEGORY, + + KIND_ALLOCATED_MEMORY, + KIND_USED_MEMORY, +} from './details-selection.js'; + +defineCustomElement('global-timeline', (templateText) => + class GlobalTimeline extends HTMLElement { + constructor() { + super(); + const shadowRoot = this.attachShadow({mode: 'open'}); + shadowRoot.innerHTML = templateText; + } + + $(id) { + return this.shadowRoot.querySelector(id); + } + + set data(value) { + this._data = value; + this.stateChanged(); + } + + get data() { + return this._data; + } + + set selection(value) { + this._selection = value; + this.stateChanged(); + } + + get selection() { + return this._selection; + } + + isValid() { + return this.data && this.selection; + } + + hide() { + this.$('#container').style.display = 'none'; + } + + show() { + this.$('#container').style.display = 'block'; + } + + stateChanged() { + if (this.isValid()) { + const isolate_data = this.data[this.selection.isolate]; + const peakAllocatedMemory = isolate_data.peakAllocatedMemory; + this.$('#peak-memory-label').innerText = formatBytes(peakAllocatedMemory); + this.drawChart(); + } else { + this.hide(); + } + } + + getZoneLabels(zone_names) { + switch (this.selection.data_kind) { + case KIND_ALLOCATED_MEMORY: + return zone_names.map(name => { + return {label: name + " (allocated)", type: 'number'}; + }); + + case KIND_USED_MEMORY: + return zone_names.map(name => { + return {label: name + " (used)", type: 'number'}; + }); + + default: + // Don't show detailed per-zone information. + return []; + } + } + + getTotalsData() { + const isolate_data = this.data[this.selection.isolate]; + const labels = [ + { label: "Time", type: "number" }, + { label: "Total allocated", type: "number" }, + { label: "Total used", type: "number" }, + ]; + const chart_data = [labels]; + + const timeStart = this.selection.timeStart; + const timeEnd = this.selection.timeEnd; + const filter_entries = timeStart > 0 || timeEnd > 0; + + for (const [time, zone_data] of isolate_data.samples) { + if (filter_entries && (time < timeStart || time > timeEnd)) continue; + const data = []; + data.push(time * kMillis2Seconds); + data.push(zone_data.allocated / KB); + data.push(zone_data.used / KB); + chart_data.push(data); + } + return chart_data; + } + + getZoneData() { + const isolate_data = this.data[this.selection.isolate]; + const zone_names = isolate_data.sorted_zone_names; + const selected_zones = this.selection.zones; + const data_kind = this.selection.data_kind; + const show_totals = this.selection.show_totals; + const zones_labels = this.getZoneLabels(zone_names); + + const totals_labels = show_totals + ? [ + { label: "Total allocated", type: "number" }, + { label: "Total used", type: "number" }, + ] + : []; + + const labels = [ + { label: "Time", type: "number" }, + ...totals_labels, + ...zones_labels, + ]; + const chart_data = [labels]; + + const timeStart = this.selection.timeStart; + const timeEnd = this.selection.timeEnd; + const filter_entries = timeStart > 0 || timeEnd > 0; + + for (const [time, zone_data] of isolate_data.samples) { + if (filter_entries && (time < timeStart || time > timeEnd)) continue; + const active_zone_stats = Object.create(null); + if (zone_data.zones !== undefined) { + for (const [zone_name, zone_stats] of zone_data.zones) { + if (!selected_zones.has(zone_name)) continue; // Not selected, skip. + + const current_stats = active_zone_stats[zone_name]; + if (current_stats === undefined) { + active_zone_stats[zone_name] = + { allocated: zone_stats.allocated, used: zone_stats.used }; + } else { + // We've got two zones with the same name. + console.log("=== Duplicate zone names: " + zone_name); + // Sum stats. + current_stats.allocated += zone_stats.allocated; + current_stats.used += zone_stats.used; + } + } + } + + const data = []; + data.push(time * kMillis2Seconds); + if (show_totals) { + data.push(zone_data.allocated / KB); + data.push(zone_data.used / KB); + } + + if (zone_data.used > 30 * MB) { + console.log("BOOOM!!!! Zone usage in a sample is too big: " + + (zone_data.used / MB) + " MB"); + } + + zone_names.forEach(zone => { + const sample = active_zone_stats[zone]; + let used = null; + let allocated = null; + if (sample !== undefined) { + used = sample.used / KB; + allocated = sample.allocated / KB; + } + if (data_kind == KIND_ALLOCATED_MEMORY) { + data.push(allocated); + } else { + // KIND_USED_MEMORY + data.push(used); + } + }); + chart_data.push(data); + } + return chart_data; + } + + getCategoryData() { + const isolate_data = this.data[this.selection.isolate]; + const categories = Object.keys(this.selection.categories); + const categories_names = + categories.map(k => this.selection.category_names.get(k)); + const selected_zones = this.selection.zones; + const data_kind = this.selection.data_kind; + const show_totals = this.selection.show_totals; + + const categories_labels = this.getZoneLabels(categories_names); + + const totals_labels = show_totals + ? [ + { label: "Total allocated", type: "number" }, + { label: "Total used", type: "number" }, + ] + : []; + + const labels = [ + { label: "Time", type: "number" }, + ...totals_labels, + ...categories_labels, + ]; + const chart_data = [labels]; + + const timeStart = this.selection.timeStart; + const timeEnd = this.selection.timeEnd; + const filter_entries = timeStart > 0 || timeEnd > 0; + + for (const [time, zone_data] of isolate_data.samples) { + if (filter_entries && (time < timeStart || time > timeEnd)) continue; + const active_category_stats = Object.create(null); + if (zone_data.zones !== undefined) { + for (const [zone_name, zone_stats] of zone_data.zones) { + const category = selected_zones.get(zone_name); + if (category === undefined) continue; // Zone was not selected. + + const current_stats = active_category_stats[category]; + if (current_stats === undefined) { + active_category_stats[category] = + { allocated: zone_stats.allocated, used: zone_stats.used }; + } else { + // Sum stats. + current_stats.allocated += zone_stats.allocated; + current_stats.used += zone_stats.used; + } + } + } + + const data = []; + data.push(time * kMillis2Seconds); + if (show_totals) { + data.push(zone_data.allocated / KB); + data.push(zone_data.used / KB); + } + + categories.forEach(category => { + const sample = active_category_stats[category]; + let used = null; + let allocated = null; + if (sample !== undefined) { + used = sample.used / KB; + allocated = sample.allocated / KB; + } + if (data_kind == KIND_ALLOCATED_MEMORY) { + data.push(allocated); + } else { + // KIND_USED_MEMORY + data.push(used); + } + }); + chart_data.push(data); + } + return chart_data; + } + + getChartData() { + switch (this.selection.data_view) { + case VIEW_BY_ZONE_NAME: + return this.getZoneData(); + case VIEW_BY_ZONE_CATEGORY: + return this.getCategoryData(); + case VIEW_TOTALS: + default: + return this.getTotalsData(); + } + } + + getChartOptions() { + const options = { + isStacked: true, + interpolateNulls: true, + hAxis: { + format: '###.##s', + title: 'Time [s]', + }, + vAxis: { + format: '#,###KB', + title: 'Memory consumption [KBytes]' + }, + chartArea: {left:100, width: '85%', height: '70%'}, + legend: {position: 'top', maxLines: '1'}, + pointsVisible: true, + pointSize: 3, + explorer: {}, + }; + + // Overlay total allocated/used points on top of the graph. + const series = {} + if (this.selection.data_view == VIEW_TOTALS) { + series[0] = {type: 'line', color: "red"}; + series[1] = {type: 'line', color: "blue"}; + } else if (this.selection.show_totals) { + series[0] = {type: 'line', color: "red", lineDashStyle: [13, 13]}; + series[1] = {type: 'line', color: "blue", lineDashStyle: [13, 13]}; + } + return Object.assign(options, {series: series}); + } + + drawChart() { + console.assert(this.data, 'invalid data'); + console.assert(this.selection, 'invalid selection'); + + const chart_data = this.getChartData(); + + const data = google.visualization.arrayToDataTable(chart_data); + const options = this.getChartOptions(); + const chart = new google.visualization.AreaChart(this.$('#chart')); + this.show(); + chart.draw(data, google.charts.Line.convertOptions(options)); + } +}); diff --git a/chromium/v8/tools/zone-stats/helper.js b/chromium/v8/tools/zone-stats/helper.js new file mode 100644 index 00000000000..a0d04859d1c --- /dev/null +++ b/chromium/v8/tools/zone-stats/helper.js @@ -0,0 +1,30 @@ +// 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. + +const KB = 1024; +const MB = KB * KB; +const GB = MB * KB; +const kMillis2Seconds = 1 / 1000; + +function formatBytes(bytes) { + const units = [' B', ' KB', ' MB', ' GB']; + const divisor = 1024; + let index = 0; + while (index < units.length && bytes >= divisor) { + index++; + bytes /= divisor; + } + return bytes.toFixed(2) + units[index]; +} + +function formatSeconds(millis) { + return (millis * kMillis2Seconds).toFixed(2) + 's'; +} + +function defineCustomElement(name, generator) { + let htmlTemplatePath = name + '-template.html'; + fetch(htmlTemplatePath) + .then(stream => stream.text()) + .then(templateText => customElements.define(name, generator(templateText))); +} diff --git a/chromium/v8/tools/zone-stats/index.html b/chromium/v8/tools/zone-stats/index.html new file mode 100644 index 00000000000..5997aab8831 --- /dev/null +++ b/chromium/v8/tools/zone-stats/index.html @@ -0,0 +1,93 @@ +<!DOCTYPE html> +<!-- 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. --> + +<html lang="en"> + +<head> + <meta charset="UTF-8"> + <title>V8 Heap Statistics</title> + <link href='https://fonts.googleapis.com/css?family=Roboto' rel='stylesheet'> + <script + src="https://www.gstatic.com/charts/loader.js"></script> + <script + src="https://cdnjs.cloudflare.com/ajax/libs/pako/1.0.6/pako_inflate.js" + integrity1="sha256-N1z6ddQzX83fjw8v7uSNe7/MgOmMKdwFUv1+AJMDqNM=" + crossorigin="anonymous"></script> + + <script src="https://cdnjs.cloudflare.com/ajax/libs/oboe.js/2.1.5/oboe-browser.js" + crossorigin="anonymous"></script> + <script src="helper.js"></script> + + <script type="module" src="details-selection.js"></script> + <script type="module" src="global-timeline.js"></script> + <script type="module" src="trace-file-reader.js"></script> + + <style> +body { + font-family: 'Roboto', sans-serif; + margin-left: 5%; + margin-right: 5%; +} + + </style> + <script> +'use strict'; + +google.charts.load('current', {'packages':['line', 'corechart', 'bar']}); + +function $(id) { return document.querySelector(id); } + +function removeAllChildren(node) { + while (node.firstChild) { + node.removeChild(node.firstChild); + } +} + +let state = Object.create(null); + +function globalDataChanged(e) { + state.data = e.detail; + // Emit one entry with the whole model for debugging purposes. + console.log(state.data); + state.selection = null; + $('#global-timeline').selection = state.selection; + $('#global-timeline').data = state.data; + $('#details-selection').data = state.data; +} + +function globalSelectionChangedA(e) { + state.selection = e.detail; + console.log(state.selection); + $('#global-timeline').selection = state.selection; +} + + </script> +</head> + +<body> + <h1>V8 Zone memory usage Statistics</h1> + <trace-file-reader onchange="globalDataChanged(event)"></trace-file-reader> + + <details-selection id="details-selection" onchange="globalSelectionChangedA(event)"></details-selection> + <global-timeline id="global-timeline"></global-timeline> + + <p>Visualize zone usage profile and statistics that have been gathered using</p> + <ul> + <li><code>--trace-zone-stats</code> on V8</li> + <li> + <a + href="https://www.chromium.org/developers/how-tos/trace-event-profiling-tool">Chrome's + tracing infrastructure</a> collecting data for the category + <code>v8.zone_stats</code>. + </li> + </ul> + <p> + Note that the visualizer needs to run on a web server due to HTML imports + requiring <a + href="https://en.wikipedia.org/wiki/Cross-origin_resource_sharing">CORS</a>. + </p> +</body> + +</html> diff --git a/chromium/v8/tools/zone-stats/model.js b/chromium/v8/tools/zone-stats/model.js new file mode 100644 index 00000000000..80f45237631 --- /dev/null +++ b/chromium/v8/tools/zone-stats/model.js @@ -0,0 +1,92 @@ +// 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. + +'use strict'; + +export class Isolate { + constructor(address) { + this.address = address; + this.start = null; + this.end = null; + this.peakUsageTime = null; + // Maps zone name to per-zone statistics. + this.zones = new Map(); + // Zone names sorted by memory usage (from low to high). + this.sorted_zone_names = []; + // Maps time to total and per-zone memory usages. + this.samples = new Map(); + + this.peakAllocatedMemory = 0; + + // Maps zone name to their max memory consumption. + this.zonePeakMemory = Object.create(null); + // Peak memory consumed by a single zone. + this.singleZonePeakMemory = 0; + } + + finalize() { + this.samples.forEach(sample => this.finalizeSample(sample)); + this.start = Math.floor(this.start); + this.end = Math.ceil(this.end); + this.sortZoneNamesByPeakMemory(); + } + + getLabel() { + let label = `${this.address}: `; + label += ` peak=${formatBytes(this.peakAllocatedMemory)}`; + label += ` time=[${this.start}, ${this.end}] ms`; + return label; + } + + finalizeSample(sample) { + const time = sample.time; + if (this.start == null) { + this.start = time; + this.end = time; + } else { + this.end = Math.max(this.end, time); + } + + const allocated = sample.allocated; + if (allocated > this.peakAllocatedMemory) { + this.peakUsageTime = time; + this.peakAllocatedMemory = allocated; + } + + const sample_zones = sample.zones; + if (sample_zones !== undefined) { + sample.zones.forEach((zone_sample, zone_name) => { + let zone_stats = this.zones.get(zone_name); + if (zone_stats === undefined) { + zone_stats = {max_allocated: 0, max_used: 0}; + this.zones.set(zone_name, zone_stats); + } + + zone_stats.max_allocated = + Math.max(zone_stats.max_allocated, zone_sample.allocated); + zone_stats.max_used = Math.max(zone_stats.max_used, zone_sample.used); + }); + } + } + + sortZoneNamesByPeakMemory() { + let entries = [...this.zones.keys()]; + entries.sort((a, b) => + this.zones.get(a).max_allocated - this.zones.get(b).max_allocated + ); + this.sorted_zone_names = entries; + + let max = 0; + for (let [key, value] of entries) { + this.zonePeakMemory[key] = value; + max = Math.max(max, value); + } + this.singleZonePeakMemory = max; + } + + getInstanceTypePeakMemory(type) { + if (!(type in this.zonePeakMemory)) return 0; + return this.zonePeakMemory[type]; + } +} diff --git a/chromium/v8/tools/zone-stats/trace-file-reader-template.html b/chromium/v8/tools/zone-stats/trace-file-reader-template.html new file mode 100644 index 00000000000..ede7ee9a75b --- /dev/null +++ b/chromium/v8/tools/zone-stats/trace-file-reader-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. --> +<style> +#fileReader { + width: 100%; + height: 100px; + line-height: 100px; + text-align: center; + border: solid 1px #000000; + border-radius: 5px; + cursor: pointer; + transition: all 0.5s ease-in-out; +} + +#fileReader.done { + height: 20px; + line-height: 20px; +} + +#fileReader:hover { + background-color: #e0edfe ; +} + +.loading #fileReader { + cursor: wait; +} + +#fileReader > input { + display: none; +} + + +#loader { + display: none; +} + +.loading #loader { + display: block; + position: fixed; + top: 0px; + left: 0px; + width: 100%; + height: 100%; + background-color: rgba(255, 255, 255, 0.5); +} + +#spinner { + position: absolute; + width: 100px; + height: 100px; + top: 40%; + left: 50%; + margin-left: -50px; + border: 30px solid #000; + border-top: 30px solid #36E; + border-radius: 50%; + animation: spin 1s ease-in-out infinite; +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} +</style> + +<section id="fileReaderSection"> + <div id="fileReader" tabindex=1 > + <span id="label"> + Drag and drop a trace file into this area, or click to choose from disk. + </span> + <input id="file" type="file" name="file" /> + </div> + <div id="loader"> + <div id="spinner"></div> + </div> +</section> diff --git a/chromium/v8/tools/zone-stats/trace-file-reader.js b/chromium/v8/tools/zone-stats/trace-file-reader.js new file mode 100644 index 00000000000..6decfa8f273 --- /dev/null +++ b/chromium/v8/tools/zone-stats/trace-file-reader.js @@ -0,0 +1,294 @@ +// 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. + +'use strict'; + +import {Isolate} from './model.js'; + +defineCustomElement('trace-file-reader', (templateText) => + class TraceFileReader extends HTMLElement { + constructor() { + super(); + const shadowRoot = this.attachShadow({mode: 'open'}); + shadowRoot.innerHTML = templateText; + this.addEventListener('click', e => this.handleClick(e)); + this.addEventListener('dragover', e => this.handleDragOver(e)); + this.addEventListener('drop', e => this.handleChange(e)); + this.$('#file').addEventListener('change', e => this.handleChange(e)); + this.$('#fileReader').addEventListener('keydown', e => this.handleKeyEvent(e)); + } + + $(id) { + return this.shadowRoot.querySelector(id); + } + + get section() { + return this.$('#fileReaderSection'); + } + + updateLabel(text) { + this.$('#label').innerText = text; + } + + handleKeyEvent(event) { + if (event.key == "Enter") this.handleClick(event); + } + + handleClick(event) { + this.$('#file').click(); + } + + handleChange(event) { + // Used for drop and file change. + event.preventDefault(); + var host = event.dataTransfer ? event.dataTransfer : event.target; + this.readFile(host.files[0]); + } + + handleDragOver(event) { + event.preventDefault(); + } + + connectedCallback() { + this.$('#fileReader').focus(); + } + + readFile(file) { + if (!file) { + this.updateLabel('Failed to load file.'); + return; + } + this.$('#fileReader').blur(); + + this.section.className = 'loading'; + const reader = new FileReader(); + + if (['application/gzip', 'application/x-gzip'].includes(file.type)) { + reader.onload = (e) => { + try { + // Decode data as strings of 64Kb chunks. Bigger chunks may cause + // parsing failures in Oboe.js. + const chunkedInflate = new pako.Inflate( + {to: 'string', chunkSize: 65536} + ); + let processingState = undefined; + chunkedInflate.onData = (chunk) => { + if (processingState === undefined) { + processingState = this.startProcessing(file, chunk); + } else { + processingState.processChunk(chunk); + } + }; + chunkedInflate.onEnd = () => { + if (processingState !== undefined) { + const result_data = processingState.endProcessing(); + this.processLoadedData(file, result_data); + } + }; + console.log("======"); + const textResult = chunkedInflate.push(e.target.result); + + this.section.className = 'success'; + this.$('#fileReader').classList.add('done'); + } catch (err) { + console.error(err); + this.section.className = 'failure'; + } + }; + // Delay the loading a bit to allow for CSS animations to happen. + setTimeout(() => reader.readAsArrayBuffer(file), 0); + } else { + reader.onload = (e) => { + try { + // Process the whole file in at once. + const processingState = this.startProcessing(file, e.target.result); + const dataModel = processingState.endProcessing(); + this.processLoadedData(file, dataModel); + + this.section.className = 'success'; + this.$('#fileReader').classList.add('done'); + } catch (err) { + console.error(err); + this.section.className = 'failure'; + } + }; + // Delay the loading a bit to allow for CSS animations to happen. + setTimeout(() => reader.readAsText(file), 0); + } + } + + processLoadedData(file, dataModel) { + console.log("Trace file parsed successfully."); + this.extendAndSanitizeModel(dataModel); + this.updateLabel('Finished loading \'' + file.name + '\'.'); + this.dispatchEvent(new CustomEvent( + 'change', {bubbles: true, composed: true, detail: dataModel})); + } + + createOrUpdateEntryIfNeeded(data, entry) { + console.assert(entry.isolate, 'entry should have an isolate'); + if (!(entry.isolate in data)) { + data[entry.isolate] = new Isolate(entry.isolate); + } + } + + extendAndSanitizeModel(data) { + const checkNonNegativeProperty = (obj, property) => { + console.assert(obj[property] >= 0, 'negative property', obj, property); + }; + + Object.values(data).forEach(isolate => isolate.finalize()); + } + + processOneZoneStatsEntry(data, entry_stats) { + this.createOrUpdateEntryIfNeeded(data, entry_stats); + const isolate_data = data[entry_stats.isolate]; + let zones = undefined; + const entry_zones = entry_stats.zones; + if (entry_zones !== undefined) { + zones = new Map(); + entry_zones.forEach(zone => { + // There might be multiple occurrences of the same zone in the set, + // combine numbers in this case. + const existing_zone_stats = zones.get(zone.name); + if (existing_zone_stats !== undefined) { + existing_zone_stats.allocated += zone.allocated; + existing_zone_stats.used += zone.used; + } else { + zones.set(zone.name, {allocated: zone.allocated, used: zone.used}); + } + }); + } + const time = entry_stats.time; + const sample = { + time: time, + allocated: entry_stats.allocated, + used: entry_stats.used, + zones: zones + }; + isolate_data.samples.set(time, sample); + } + + startProcessing(file, chunk) { + const isV8TraceFile = chunk.includes('v8-zone-trace'); + const processingState = + isV8TraceFile ? this.startProcessingAsV8TraceFile(file) + : this.startProcessingAsChromeTraceFile(file); + + processingState.processChunk(chunk); + return processingState; + } + + startProcessingAsChromeTraceFile(file) { + console.log(`Processing log as chrome trace file.`); + const data = Object.create(null); // Final data container. + const parseOneZoneEvent = (actual_data) => { + if ('stats' in actual_data) { + try { + const entry_stats = JSON.parse(actual_data.stats); + this.processOneZoneStatsEntry(data, entry_stats); + } catch (e) { + console.error('Unable to parse data set entry', e); + } + } + }; + const zone_events_filter = (event) => { + if (event.name == 'V8.Zone_Stats') { + parseOneZoneEvent(event.args); + } + return oboe.drop; + }; + + const oboe_stream = oboe(); + // Trace files support two formats. + oboe_stream + // 1) {traceEvents: [ data ]} + .node('traceEvents.*', zone_events_filter) + // 2) [ data ] + .node('!.*', zone_events_filter) + .fail((errorReport) => { + throw new Error("Trace data parse failed: " + errorReport.thrown); + }); + + let failed = false; + + const processingState = { + file: file, + + processChunk(chunk) { + if (failed) return false; + try { + oboe_stream.emit('data', chunk); + return true; + } catch (e) { + console.error('Unable to parse chrome trace file.', e); + failed = true; + return false; + } + }, + + endProcessing() { + if (failed) return null; + oboe_stream.emit('end'); + return data; + }, + }; + return processingState; + } + + startProcessingAsV8TraceFile(file) { + console.log('Processing log as V8 trace file.'); + const data = Object.create(null); // Final data container. + + const processOneLine = (line) => { + try { + // Strip away a potentially present adb logcat prefix. + line = line.replace(/^I\/v8\s*\(\d+\):\s+/g, ''); + + const entry = JSON.parse(line); + if (entry === null || entry.type === undefined) return; + if ((entry.type === 'v8-zone-trace') && ('stats' in entry)) { + const entry_stats = entry.stats; + this.processOneZoneStatsEntry(data, entry_stats); + } else { + console.log('Unknown entry type: ' + entry.type); + } + } catch (e) { + console.log('Unable to parse line: \'' + line + '\' (' + e + ')'); + } + }; + + let prev_chunk_leftover = ""; + + const processingState = { + file: file, + + processChunk(chunk) { + const contents = chunk.split('\n'); + const last_line = contents.pop(); + const linesCount = contents.length; + if (linesCount == 0) { + // There was only one line in the chunk, it may still be unfinished. + prev_chunk_leftover += last_line; + } else { + contents[0] = prev_chunk_leftover + contents[0]; + prev_chunk_leftover = last_line; + for (let line of contents) { + processOneLine(line); + } + } + return true; + }, + + endProcessing() { + if (prev_chunk_leftover.length > 0) { + processOneLine(prev_chunk_leftover); + prev_chunk_leftover = ""; + } + return data; + }, + }; + return processingState; + } +}); |