Import Cobalt 20.master.0.234144
Includes the following patches:
https://cobalt-review.googlesource.com/c/cobalt/+/5590
by n1214.hwang@samsung.com
https://cobalt-review.googlesource.com/c/cobalt/+/5530
by errong.leng@samsung.com
https://cobalt-review.googlesource.com/c/cobalt/+/5570
by devin.cai@mediatek.com
diff --git a/src/v8/tools/BUILD.gn b/src/v8/tools/BUILD.gn
index 1c0864d..e6fd743 100644
--- a/src/v8/tools/BUILD.gn
+++ b/src/v8/tools/BUILD.gn
@@ -2,59 +2,65 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-import("../gni/isolate.gni")
+import("//build/config/sanitizers/sanitizers.gni")
+import("../gni/v8.gni")
group("gn_all") {
testonly = true
- if (v8_test_isolation_mode != "noop") {
- deps = [
- ":check-static-initializers_run",
- ":jsfunfuzz_run",
- ":run-deopt-fuzzer_run",
- ":run-gcmole_run",
- ":run-num-fuzzer_run",
+ data_deps = [
+ ":v8_check_static_initializers",
+ "gcmole:v8_run_gcmole",
+ "jsfunfuzz:v8_jsfunfuzz",
+ ]
+}
+
+group("v8_check_static_initializers") {
+ data_deps = [
+ "..:d8",
+ ]
+
+ data = [
+ "check-static-initializers.sh",
+ ]
+}
+
+group("v8_android_test_runner_deps") {
+ testonly = true
+
+ if (is_android && !build_with_chromium) {
+ data_deps = [
+ "//build/android:test_runner_py",
+ ]
+ data = [
+ # This is used by android.py, but not included by test_runner_py above.
+ "//third_party/catapult/devil/devil/android/perf/",
]
}
}
-v8_isolate_run("check-static-initializers") {
- deps = [
- "..:d8_run",
+group("v8_testrunner") {
+ testonly = true
+
+ data_deps = [
+ "..:v8_python_base",
+ "..:v8_dump_build_config",
+ ":v8_android_test_runner_deps",
]
- isolate = "check-static-initializers.isolate"
-}
-
-v8_isolate_run("jsfunfuzz") {
- deps = [
- "..:d8_run",
+ data = [
+ # Also add the num-fuzzer wrapper script in order to be able to run the
+ # num-fuzzer on all existing isolated V8 test suites.
+ "predictable_wrapper.py",
+ "run-num-fuzzer.py",
+ "run-tests.py",
+ "testrunner/",
]
- isolate = "jsfunfuzz/jsfunfuzz.isolate"
-}
-
-v8_isolate_run("run-deopt-fuzzer") {
- deps = [
- "..:d8_run",
- ]
-
- isolate = "run-deopt-fuzzer.isolate"
-}
-
-v8_isolate_run("run-gcmole") {
- deps = [
- "..:d8_run",
- ]
-
- isolate = "gcmole/run-gcmole.isolate"
-}
-
-# TODO(machenbach): Add tests as dependencies.
-v8_isolate_run("run-num-fuzzer") {
- deps = [
- "..:d8_run",
- ]
-
- isolate = "run-num-fuzzer.isolate"
+ if (v8_code_coverage && sanitizer_coverage_flags == "bb,trace-pc-guard") {
+ data += [
+ "sanitizers/sancov_merger.py",
+ "../third_party/llvm/projects/compiler-rt/lib/sanitizer_common/scripts/sancov.py",
+ ]
+ }
}
diff --git a/src/v8/tools/Makefile.tags b/src/v8/tools/Makefile.tags
new file mode 100644
index 0000000..372824d
--- /dev/null
+++ b/src/v8/tools/Makefile.tags
@@ -0,0 +1,30 @@
+# 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.
+
+
+# Variable default definitions. Override them by exporting them in your shell.
+V8_DIR ?= $(realpath $(dir $(lastword $(MAKEFILE_LIST)))/..)
+
+# Support for the GNU GLOBAL Source Code Tag System.
+$(V8_DIR)/gtags.files: $(wildcard $(addprefix $(V8_DIR)/,$(shell cat $(V8_DIR)/gtags.files 2> /dev/null)))
+ @(cd $(V8_DIR) && find include src test -name '*.h' -o -name '*.cc' -o -name '*.c') > $@
+
+# We need to manually set the stack limit here, to work around bugs in
+# gmake-3.81 and global-5.7.1 on recent 64-bit Linux systems.
+# Using $(wildcard ...) gracefully ignores non-existing files, so that stale
+# gtags.files after switching branches don't cause recipe failures.
+$(V8_DIR)/GPATH $(V8_DIR)/GRTAGS $(V8_DIR)/GSYMS $(V8_DIR)/GTAGS: $(V8_DIR)/gtags.files $(wildcard $(addprefix $(V8_DIR)/,$(shell cat $(V8_DIR)/gtags.files 2> /dev/null)))
+ @cd $(V8_DIR) && bash -c 'ulimit -s 10240 && GTAGSFORCECPP=yes gtags -i -q -f $<'
+
+$(V8_DIR)/tags: $(V8_DIR)/gtags.files $(wildcard $(addprefix $(V8_DIR)/,$(shell cat $(V8_DIR)/gtags.files 2> /dev/null)))
+ @(ctags --version | grep 'Exuberant Ctags' >/dev/null) || \
+ (echo "Please install Exuberant Ctags (check 'ctags --version')" >&2; false)
+ @cd $(V8_DIR) && ctags --fields=+l -L gtags.files
+
+tags: $(V8_DIR)/tags
+
+tags.clean:
+ @rm -f $(addprefix $(V8_DIR), gtags.files GPATH GRTAGS GSYMS GTAGS tags)
+
+clean: tags.clean
diff --git a/src/v8/tools/__init__.py b/src/v8/tools/__init__.py
new file mode 100644
index 0000000..3841a86
--- /dev/null
+++ b/src/v8/tools/__init__.py
@@ -0,0 +1,4 @@
+#!/usr/bin/env python
+# 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/src/v8/tools/adb-d8.py b/src/v8/tools/adb-d8.py
index 4167146..4d4390f 100755
--- a/src/v8/tools/adb-d8.py
+++ b/src/v8/tools/adb-d8.py
@@ -158,7 +158,7 @@
# command.
adb = os.path.join(
script_dir,
- "../third_party/android_tools/sdk/platform-tools/adb"
+ "../third_party/android_sdk/public/platform-tools/adb"
)
# Read off any command line flags before build_dir (or --). Do this
diff --git a/src/v8/tools/android-run.py b/src/v8/tools/android-run.py
index 4765f86..66d333a 100755
--- a/src/v8/tools/android-run.py
+++ b/src/v8/tools/android-run.py
@@ -35,6 +35,9 @@
# and output special error string in case of non-zero exit code.
# Then we parse the output of 'adb shell' and look for that error string.
+# for py2/py3 compatibility
+from __future__ import print_function
+
import os
from os.path import join, dirname, abspath
import subprocess
@@ -58,8 +61,8 @@
exit_code = process.wait()
os.close(fd_out)
os.close(fd_err)
- output = file(outname).read()
- errors = file(errname).read()
+ output = open(outname).read()
+ errors = open(errname).read()
os.unlink(outname)
os.unlink(errname)
sys.stdout.write(output)
diff --git a/src/v8/tools/avg.py b/src/v8/tools/avg.py
new file mode 100755
index 0000000..5741acd
--- /dev/null
+++ b/src/v8/tools/avg.py
@@ -0,0 +1,248 @@
+#!/usr/bin/env python
+
+# 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.
+"""
+This script averages numbers output from another script. It is useful
+to average over a benchmark that outputs one or more results of the form
+ <key> <number> <unit>
+key and unit are optional, but only one number per line is processed.
+
+For example, if
+ $ bch --allow-natives-syntax toNumber.js
+outputs
+ Number('undefined'): 155763
+ (+'undefined'): 193050 Kps
+ 23736 Kps
+then
+ $ avg.py 10 bch --allow-natives-syntax toNumber.js
+will output
+ [10/10] (+'undefined') : avg 192,240.40 stddev 6,486.24 (185,529.00 - 206,186.00)
+ [10/10] Number('undefined') : avg 156,990.10 stddev 16,327.56 (144,718.00 - 202,840.00) Kps
+ [10/10] [default] : avg 22,885.80 stddev 1,941.80 ( 17,584.00 - 24,266.00) Kps
+"""
+
+# for py2/py3 compatibility
+from __future__ import print_function
+
+import argparse
+import math
+import re
+import signal
+import subprocess
+import sys
+
+PARSER = argparse.ArgumentParser(
+ description="A script that averages numbers from another script's output",
+ epilog="Example:\n\tavg.py 10 bash -c \"echo A: 100; echo B 120; sleep .1\""
+)
+PARSER.add_argument(
+ 'repetitions',
+ type=int,
+ help="number of times the command should be repeated")
+PARSER.add_argument(
+ 'command',
+ nargs=argparse.REMAINDER,
+ help="command to run (no quotes needed)")
+PARSER.add_argument(
+ '--echo',
+ '-e',
+ action='store_true',
+ default=False,
+ help="set this flag to echo the command's output")
+
+ARGS = vars(PARSER.parse_args())
+
+if not ARGS['command']:
+ print("No command provided.")
+ exit(1)
+
+
+class FieldWidth:
+
+ def __init__(self, points=0, key=0, average=0, stddev=0, min_width=0, max_width=0):
+ self.widths = dict(points=points, key=key, average=average, stddev=stddev,
+ min=min_width, max=max_width)
+
+ def max_widths(self, other):
+ self.widths = {k: max(v, other.widths[k]) for k, v in self.widths.items()}
+
+ def __getattr__(self, key):
+ return self.widths[key]
+
+
+def fmtS(string, width=0):
+ return "{0:<{1}}".format(string, width)
+
+
+def fmtN(num, width=0):
+ return "{0:>{1},.2f}".format(num, width)
+
+
+def fmt(num):
+ return "{0:>,.2f}".format(num)
+
+
+def format_line(points, key, average, stddev, min_value, max_value,
+ unit_string, widths):
+ return "{:>{}}; {:<{}}; {:>{}}; {:>{}}; {:>{}}; {:>{}}; {}".format(
+ points, widths.points,
+ key, widths.key,
+ average, widths.average,
+ stddev, widths.stddev,
+ min_value, widths.min,
+ max_value, widths.max,
+ unit_string)
+
+
+def fmt_reps(msrmnt):
+ rep_string = str(ARGS['repetitions'])
+ return "[{0:>{1}}/{2}]".format(msrmnt.size(), len(rep_string), rep_string)
+
+
+class Measurement:
+
+ def __init__(self, key, unit):
+ self.key = key
+ self.unit = unit
+ self.values = []
+ self.average = 0
+ self.count = 0
+ self.M2 = 0
+ self.min = float("inf")
+ self.max = -float("inf")
+
+ def addValue(self, value):
+ try:
+ num_value = float(value)
+ self.values.append(num_value)
+ self.min = min(self.min, num_value)
+ self.max = max(self.max, num_value)
+ self.count = self.count + 1
+ delta = num_value - self.average
+ self.average = self.average + delta / self.count
+ delta2 = num_value - self.average
+ self.M2 = self.M2 + delta * delta2
+ except ValueError:
+ print("Ignoring non-numeric value", value)
+
+ def status(self, widths):
+ return "{} {}: avg {} stddev {} ({} - {}) {}".format(
+ fmt_reps(self),
+ fmtS(self.key, widths.key), fmtN(self.average, widths.average),
+ fmtN(self.stddev(), widths.stddev), fmtN(self.min, widths.min),
+ fmtN(self.max, widths.max), fmtS(self.unit_string()))
+
+ def result(self, widths):
+ return format_line(self.size(), self.key, fmt(self.average),
+ fmt(self.stddev()), fmt(self.min),
+ fmt(self.max), self.unit_string(),
+ widths)
+
+ def unit_string(self):
+ if not self.unit:
+ return ""
+ return self.unit
+
+ def variance(self):
+ if self.count < 2:
+ return float('NaN')
+ return self.M2 / (self.count - 1)
+
+ def stddev(self):
+ return math.sqrt(self.variance())
+
+ def size(self):
+ return len(self.values)
+
+ def widths(self):
+ return FieldWidth(
+ points=len("{}".format(self.size())) + 2,
+ key=len(self.key),
+ average=len(fmt(self.average)),
+ stddev=len(fmt(self.stddev())),
+ min_width=len(fmt(self.min)),
+ max_width=len(fmt(self.max)))
+
+
+def result_header(widths):
+ return format_line("#/{}".format(ARGS['repetitions']),
+ "id", "avg", "stddev", "min", "max", "unit", widths)
+
+
+class Measurements:
+
+ def __init__(self):
+ self.all = {}
+ self.default_key = '[default]'
+ self.max_widths = FieldWidth(
+ points=len("{}".format(ARGS['repetitions'])) + 2,
+ key=len("id"),
+ average=len("avg"),
+ stddev=len("stddev"),
+ min_width=len("min"),
+ max_width=len("max"))
+ self.last_status_len = 0
+
+ def record(self, key, value, unit):
+ if not key:
+ key = self.default_key
+ if key not in self.all:
+ self.all[key] = Measurement(key, unit)
+ self.all[key].addValue(value)
+ self.max_widths.max_widths(self.all[key].widths())
+
+ def any(self):
+ if self.all:
+ return next(iter(self.all.values()))
+ return None
+
+ def print_results(self):
+ print("{:<{}}".format("", self.last_status_len), end="\r")
+ print(result_header(self.max_widths), sep=" ")
+ for key in sorted(self.all):
+ print(self.all[key].result(self.max_widths), sep=" ")
+
+ def print_status(self):
+ status = "No results found. Check format?"
+ measurement = MEASUREMENTS.any()
+ if measurement:
+ status = measurement.status(MEASUREMENTS.max_widths)
+ print("{:<{}}".format(status, self.last_status_len), end="\r")
+ self.last_status_len = len(status)
+
+
+MEASUREMENTS = Measurements()
+
+
+def signal_handler(signum, frame):
+ print("", end="\r")
+ MEASUREMENTS.print_results()
+ sys.exit(0)
+
+
+signal.signal(signal.SIGINT, signal_handler)
+
+SCORE_REGEX = (r'\A((console.timeEnd: )?'
+ r'(?P<key>[^\s:,]+)[,:]?)?'
+ r'(^\s*|\s+)'
+ r'(?P<value>[0-9]+(.[0-9]+)?)'
+ r'\ ?(?P<unit>[^\d\W]\w*)?[.\s]*\Z')
+
+for x in range(0, ARGS['repetitions']):
+ proc = subprocess.Popen(ARGS['command'], stdout=subprocess.PIPE)
+ for line in proc.stdout:
+ if ARGS['echo']:
+ print(line.decode(), end="")
+ for m in re.finditer(SCORE_REGEX, line.decode()):
+ MEASUREMENTS.record(m.group('key'), m.group('value'), m.group('unit'))
+ proc.wait()
+ if proc.returncode != 0:
+ print("Child exited with status %d" % proc.returncode)
+ break
+
+ MEASUREMENTS.print_status()
+
+# Print final results
+MEASUREMENTS.print_results()
diff --git a/src/v8/tools/bash-completion.sh b/src/v8/tools/bash-completion.sh
index 5b9f7f5..27e73b7 100755
--- a/src/v8/tools/bash-completion.sh
+++ b/src/v8/tools/bash-completion.sh
@@ -37,11 +37,11 @@
_v8_flag() {
local cur defines targets
cur="${COMP_WORDS[COMP_CWORD]}"
- defines=$(cat $v8_source/src/flag-definitions.h \
+ defines=$(cat $v8_source/src/flags/flag-definitions.h \
| grep "^DEFINE" \
| grep -v "DEFINE_IMPLICATION" \
| sed -e 's/_/-/g'; \
- cat $v8_source/src/flag-definitions.h \
+ cat $v8_source/src/flags/flag-definitions.h \
| grep "^ V(harmony_" \
| sed -e 's/^ V/DEFINE-BOOL/' \
| sed -e 's/_/-/g')
@@ -49,7 +49,7 @@
| sed -ne 's/^DEFINE-[^(]*(\([^,]*\).*/--\1/p'; \
echo "$defines" \
| sed -ne 's/^DEFINE-BOOL(\([^,]*\).*/--no\1/p'; \
- cat $v8_source/src/d8.cc \
+ cat $v8_source/src/d8/d8.cc \
| grep "strcmp(argv\[i\]" \
| sed -ne 's/^[^"]*"--\([^"]*\)".*/--\1/p')
COMPREPLY=($(compgen -W "$targets" -- "$cur"))
diff --git a/src/v8/tools/bigint-tester.py b/src/v8/tools/bigint-tester.py
index 0452a0d..0940369 100755
--- a/src/v8/tools/bigint-tester.py
+++ b/src/v8/tools/bigint-tester.py
@@ -3,6 +3,9 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+# for py2/py3 compatibility
+from __future__ import print_function
+
import argparse
import math
import multiprocessing
@@ -13,9 +16,9 @@
import tempfile
# Configuration.
-kChars = "0123456789abcdefghijklmnopqrstuvwxyz"
+kChars = "0123456789abcdef"
kBase = 16
-kLineLength = 71 # A bit less than 80.
+kLineLength = 70 # A bit less than 80.
kNumInputsGenerate = 20
kNumInputsStress = 1000
@@ -30,8 +33,6 @@
// found in the LICENSE file.
// Generated by %s.
-
-// Flags: --harmony-bigint
""" % sys.argv[0]
TEST_BODY = """
@@ -46,29 +47,36 @@
}"""
def GenRandom(length, negative=kRandom):
- if length == 0: return "0"
+ if length == 0: return "0n"
s = []
if negative == kYes or (negative == kRandom and (random.randint(0, 1) == 0)):
s.append("-") # 50% chance of negative.
+ s.append("0x")
s.append(kChars[random.randint(1, kBase - 1)]) # No leading zero.
for i in range(1, length):
s.append(kChars[random.randint(0, kBase - 1)])
+ s.append("n")
return "".join(s)
-def Format(x, base):
+def Parse(x):
+ assert x[-1] == 'n', x
+ return int(x[:-1], kBase)
+
+def Format(x):
original = x
negative = False
- if x == 0: return "0"
+ if x == 0: return "0n"
if x < 0:
negative = True
x = -x
s = ""
while x > 0:
- s = kChars[x % base] + s
- x = x / base
+ s = kChars[x % kBase] + s
+ x = x / kBase
+ s = "0x" + s + "n"
if negative:
s = "-" + s
- assert int(s, base) == original
+ assert Parse(s) == original
return s
class TestGenerator(object):
@@ -102,7 +110,7 @@
with open(path, "w") as f:
f.write(self.EmitData(count))
f.write(self.EmitTestBody())
- return subprocess.call("%s --harmony-bigint %s" % (binary, path),
+ return subprocess.call("%s %s" % (binary, path),
shell=True)
finally:
os.close(fd)
@@ -120,17 +128,16 @@
# Subclasses should not override anything below.
def EmitOne(self):
x_str = self.GenerateInput()
- x_num = int(x_str, kBase)
+ x_num = Parse(x_str)
result_num = self.GenerateResult(x_num)
- result_str = Format(result_num, kBase)
- return "{\n a: \"%s\",\n r: \"%s\"\n}" % (x_str, result_str)
+ result_str = Format(result_num)
+ return "{\n a: %s,\n r: %s\n}" % (x_str, result_str)
def EmitTestCore(self):
return """\
- var a = BigInt.parseInt(d.a, %(base)d);
- var r = %(op)sa;
- if (d.r !== r.toString(%(base)d)) {
- print("Input: " + a.toString(%(base)d));
+ var r = %(op)sd.a;
+ if (d.r !== r) {
+ print("Input: " + d.a.toString(%(base)d));
print("Result: " + r.toString(%(base)d));
print("Expected: " + d.r);
error_count++;
@@ -152,21 +159,19 @@
# Subclasses should not override anything below.
def EmitOne(self):
left_str, right_str = self.GenerateInputs()
- left_num = int(left_str, kBase)
- right_num = int(right_str, kBase)
+ left_num = Parse(left_str)
+ right_num = Parse(right_str)
result_num = self.GenerateResult(left_num, right_num)
- result_str = Format(result_num, kBase)
- return ("{\n a: \"%s\",\n b: \"%s\",\n r: \"%s\"\n}" %
+ result_str = Format(result_num)
+ return ("{\n a: %s,\n b: %s,\n r: %s\n}" %
(left_str, right_str, result_str))
def EmitTestCore(self):
return """\
- var a = BigInt.parseInt(d.a, %(base)d);
- var b = BigInt.parseInt(d.b, %(base)d);
- var r = a %(op)s b;
- if (d.r !== r.toString(%(base)d)) {
- print("Input A: " + a.toString(%(base)d));
- print("Input B: " + b.toString(%(base)d));
+ var r = d.a %(op)s d.b;
+ if (d.r !== r) {
+ print("Input A: " + d.a.toString(%(base)d));
+ print("Input B: " + d.b.toString(%(base)d));
print("Result: " + r.toString(%(base)d));
print("Expected: " + d.r);
print("Op: %(op)s");
@@ -292,7 +297,7 @@
return RunOne(*args)
def RunAll(args):
for op in args.op:
- for r in xrange(args.runs):
+ for r in range(args.runs):
yield (op, args.num_inputs, args.binary)
def Main():
diff --git a/src/v8/tools/blink_tests/TestExpectations b/src/v8/tools/blink_tests/TestExpectations
index 3655c5c..e69de29 100644
--- a/src/v8/tools/blink_tests/TestExpectations
+++ b/src/v8/tools/blink_tests/TestExpectations
@@ -1,5 +0,0 @@
-[ Linux ] virtual/pointerevent/fast/events/mouse-cursor-style-change-iframe.html [ Skip ]
-
-# Turn off Slimming Paint tests on linux.
-[ Linux ] virtual/slimmingpaint/ [ Skip ]
-
diff --git a/src/v8/tools/callstats.html b/src/v8/tools/callstats.html
index 2618b50..1ceca83 100644
--- a/src/v8/tools/callstats.html
+++ b/src/v8/tools/callstats.html
@@ -1,3 +1,4 @@
+<!DOCTYPE html>
<html>
<!--
Copyright 2016 the V8 project authors. All rights reserved. Use of this source
@@ -5,7 +6,8 @@
-->
<head>
- <meta charset="UTF-8">
+ <meta charset="utf-8">
+ <title>V8 Runtime Stats Komparator</title>
<style>
body {
font-family: arial;
@@ -228,8 +230,8 @@
display: none;
}
</style>
- <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
- <script type="text/javascript">
+ <script src="https://www.gstatic.com/charts/loader.js"></script>
+ <script>
"use strict"
google.charts.load('current', {packages: ['corechart']});
@@ -957,7 +959,7 @@
}
}
</script>
- <script type="text/javascript">
+ <script>
"use strict"
// =========================================================================
// Helpers
@@ -1058,7 +1060,7 @@
}
</script>
- <script type="text/javascript">
+ <script>
"use strict"
// =========================================================================
// EventHandlers
@@ -1305,7 +1307,7 @@
window.open(url,'_blank');
}
</script>
- <script type="text/javascript">
+ <script>
"use strict"
// =========================================================================
class Versions {
@@ -1507,6 +1509,7 @@
this.groups = [
this.total,
Group.groups.get('ic').entry(),
+ Group.groups.get('optimize-background').entry(),
Group.groups.get('optimize').entry(),
Group.groups.get('compile-background').entry(),
Group.groups.get('compile').entry(),
@@ -1715,14 +1718,16 @@
}
Group.add('total', new Group('Total', /.*Total.*/, '#BBB'));
Group.add('ic', new Group('IC', /.*IC_.*/, "#3366CC"));
+ Group.add('optimize-background', new Group('Optimize-Background',
+ /(.*OptimizeConcurrent.*)|RecompileConcurrent.*/, "#702000"));
Group.add('optimize', new Group('Optimize',
/StackGuard|.*Optimize.*|.*Deoptimize.*|Recompile.*/, "#DC3912"));
Group.add('compile-background', new Group('Compile-Background',
- /(.*CompileBackground.*)/, "#b9a720"));
+ /(.*CompileBackground.*)/, "#b08000"));
Group.add('compile', new Group('Compile',
/(^Compile.*)|(.*_Compile.*)/, "#FFAA00"));
Group.add('parse-background',
- new Group('Parse-Background', /.*ParseBackground.*/, "#af744d"));
+ new Group('Parse-Background', /.*ParseBackground.*/, "#c05000"));
Group.add('parse', new Group('Parse', /.*Parse.*/, "#FF6600"));
Group.add('callback', new Group('Blink C++', /.*Callback.*/, "#109618"));
Group.add('api', new Group('API', /.*API.*/, "#990099"));
diff --git a/src/v8/tools/callstats.py b/src/v8/tools/callstats.py
index 5215d63..7c5bd4a 100755
--- a/src/v8/tools/callstats.py
+++ b/src/v8/tools/callstats.py
@@ -17,6 +17,9 @@
For each command, you can try ./runtime-call-stats.py help command.
'''
+# for py2/py3 compatibility
+from __future__ import print_function
+
import argparse
import json
import os
@@ -33,6 +36,9 @@
from math import sqrt
+MAX_NOF_RETRIES = 5
+
+
# Run benchmarks.
def print_command(cmd_args):
@@ -43,7 +49,7 @@
elif ' ' in arg:
arg = "'{}'".format(arg)
return arg
- print " ".join(map(fix_for_printing, cmd_args))
+ print(" ".join(map(fix_for_printing, cmd_args)))
def start_replay_server(args, sites, discard_output=True):
@@ -63,15 +69,15 @@
"--inject_scripts=deterministic.js,{}".format(injection),
args.replay_wpr,
]
- print "=" * 80
+ print("=" * 80)
print_command(cmd_args)
if discard_output:
with open(os.devnull, 'w') as null:
server = subprocess.Popen(cmd_args, stdout=null, stderr=null)
else:
server = subprocess.Popen(cmd_args)
- print "RUNNING REPLAY SERVER: %s with PID=%s" % (args.replay_bin, server.pid)
- print "=" * 80
+ print("RUNNING REPLAY SERVER: %s with PID=%s" % (args.replay_bin, server.pid))
+ print("=" * 80)
return {'process': server, 'injection': injection}
@@ -82,7 +88,7 @@
def generate_injection(f, sites, refreshes=0):
- print >> f, """\
+ print("""\
(function() {
var s = window.sessionStorage.getItem("refreshCounter");
var refreshTotal = """, refreshes, """;
@@ -124,7 +130,7 @@
var sites =
""", json.dumps(sites), """;
onLoad(window.location.href);
-})();"""
+})();""", file=f)
def get_chrome_flags(js_flags, user_data_dir, arg_delimiter=""):
return [
@@ -137,6 +143,8 @@
"--no-first-run",
"--user-data-dir={}{}{}".format(arg_delimiter, user_data_dir,
arg_delimiter),
+ "--data-path={}{}{}".format(arg_delimiter,
+ os.path.join(user_data_dir, 'content-shell-data'), arg_delimiter),
]
def get_chrome_replay_flags(args, arg_delimiter=""):
@@ -156,9 +164,9 @@
]
def run_site(site, domain, args, timeout=None):
- print "="*80
- print "RUNNING DOMAIN %s" % domain
- print "="*80
+ print("="*80)
+ print("RUNNING DOMAIN %s" % domain)
+ print("="*80)
result_template = "{domain}#{count}.txt" if args.repeat else "{domain}.txt"
count = 0
if timeout is None: timeout = args.timeout
@@ -177,7 +185,7 @@
user_data_dir = args.user_data_dir
else:
user_data_dir = tempfile.mkdtemp(prefix="chr_")
- js_flags = "--runtime-call-stats --noconcurrent-recompilation"
+ js_flags = "--runtime-call-stats"
if args.replay_wpr: js_flags += " --allow-natives-syntax"
if args.js_flags: js_flags += " " + args.js_flags
chrome_flags = get_chrome_flags(js_flags, user_data_dir)
@@ -191,9 +199,9 @@
"timeout", str(timeout),
args.with_chrome
] + chrome_flags + [ site ]
- print "- " * 40
+ print("- " * 40)
print_command(cmd_args)
- print "- " * 40
+ print("- " * 40)
with open(result, "wt") as f:
with open(args.log_stderr or os.devnull, 'at') as err:
status = subprocess.call(cmd_args, stdout=f, stderr=err)
@@ -207,13 +215,17 @@
if os.path.isfile(result) and os.path.getsize(result) > 0:
if args.print_url:
with open(result, "at") as f:
- print >> f
- print >> f, "URL: {}".format(site)
+ print(file=f)
+ print("URL: {}".format(site), file=f)
retries_since_good_run = 0
break
- if retries_since_good_run < 6:
- timeout += 2 ** retries_since_good_run
- retries_since_good_run += 1
+ if retries_since_good_run > MAX_NOF_RETRIES:
+ # Abort after too many retries, no point in ever increasing the
+ # timeout.
+ print("TOO MANY EMPTY RESULTS ABORTING RUN")
+ return
+ timeout += 2 ** retries_since_good_run
+ retries_since_good_run += 1
print("EMPTY RESULT, REPEATING RUN ({})".format(
retries_since_good_run));
finally:
@@ -233,6 +245,8 @@
if item['timeout'] > args.timeout: item['timeout'] = args.timeout
sites.append(item)
except ValueError:
+ args.error("Warning: Could not read sites file as JSON, falling back to "
+ "primitive file")
with open(args.sites_file, "rt") as f:
for line in f:
line = line.strip()
@@ -283,7 +297,7 @@
# Run them.
for site, domain, count, timeout in L:
if count is not None: domain = "{}%{}".format(domain, count)
- print(site, domain, timeout)
+ print((site, domain, timeout))
run_site(site, domain, args, timeout)
finally:
if replay_server:
@@ -342,11 +356,22 @@
'stddev': stddev, 'min': low, 'max': high, 'ci': ci }
+def add_category_total(entries, groups, category_prefix):
+ group_data = { 'time': 0, 'count': 0 }
+ for group_name, regexp in groups:
+ if not group_name.startswith('Group-' + category_prefix): continue
+ group_data['time'] += entries[group_name]['time']
+ group_data['count'] += entries[group_name]['count']
+ entries['Group-' + category_prefix + '-Total'] = group_data
+
+
def read_stats(path, domain, args):
groups = [];
if args.aggregate:
groups = [
('Group-IC', re.compile(".*IC_.*")),
+ ('Group-OptimizeBackground',
+ re.compile(".*OptimizeConcurrent.*|RecompileConcurrent.*")),
('Group-Optimize',
re.compile("StackGuard|.*Optimize.*|.*Deoptimize.*|Recompile.*")),
('Group-CompileBackground', re.compile("(.*CompileBackground.*)")),
@@ -398,20 +423,10 @@
group_data['time'] += entries[group_name]['time']
group_data['count'] += entries[group_name]['count']
entries['Group-Total-V8'] = group_data
- # Calculate the Parse-Total group
- group_data = { 'time': 0, 'count': 0 }
- for group_name, regexp in groups:
- if not group_name.startswith('Group-Parse'): continue
- group_data['time'] += entries[group_name]['time']
- group_data['count'] += entries[group_name]['count']
- entries['Group-Parse-Total'] = group_data
- # Calculate the Compile-Total group
- group_data = { 'time': 0, 'count': 0 }
- for group_name, regexp in groups:
- if not group_name.startswith('Group-Compile'): continue
- group_data['time'] += entries[group_name]['time']
- group_data['count'] += entries[group_name]['count']
- entries['Group-Compile-Total'] = group_data
+ # Calculate the Parse-Total, Compile-Total and Optimize-Total groups
+ add_category_total(entries, groups, 'Parse')
+ add_category_total(entries, groups, 'Compile')
+ add_category_total(entries, groups, 'Optimize')
# Append the sums as single entries to domain.
for key in entries:
if key not in domain: domain[key] = { 'time_list': [], 'count_list': [] }
@@ -447,11 +462,11 @@
def stats(s, units=""):
conf = "{:0.1f}({:0.2f}%)".format(s['ci']['abs'], s['ci']['perc'])
return "{:8.1f}{} +/- {:15s}".format(s['average'], units, conf)
- print "{:>50s} {} {}".format(
+ print("{:>50s} {} {}".format(
key,
stats(value['time_stat'], units="ms"),
stats(value['count_stat'])
- )
+ ))
# Print and calculate partial sums, if necessary.
for i in range(low, high):
print_entry(*L[i])
@@ -467,7 +482,7 @@
partial['count_list'][j] += v
# Print totals, if necessary.
if args.totals:
- print '-' * 80
+ print('-' * 80)
if args.limit != 0 and not args.aggregate:
partial['time_stat'] = statistics(partial['time_list'])
partial['count_stat'] = statistics(partial['count_list'])
@@ -488,9 +503,9 @@
create_total_page_stats(domains, args)
for i, domain in enumerate(sorted(domains)):
if len(domains) > 1:
- if i > 0: print
- print "{}:".format(domain)
- print '=' * 80
+ if i > 0: print()
+ print("{}:".format(domain))
+ print('=' * 80)
domain_stats = domains[domain]
for key in domain_stats:
domain_stats[key]['time_stat'] = \
@@ -530,10 +545,9 @@
# Add a new "Total" page containing the summed up metrics.
domains['Total'] = total
+# Generate Raw JSON file.
-# Generate JSON file.
-
-def do_json(args):
+def _read_logs(args):
versions = {}
for path in args.logdirs:
if os.path.isdir(path):
@@ -547,6 +561,36 @@
if domain not in versions[version]: versions[version][domain] = {}
read_stats(os.path.join(root, filename),
versions[version][domain], args)
+
+ return versions
+
+def do_raw_json(args):
+ versions = _read_logs(args)
+
+ for version, domains in versions.items():
+ if args.aggregate:
+ create_total_page_stats(domains, args)
+ for domain, entries in domains.items():
+ raw_entries = []
+ for name, value in entries.items():
+ # We don't want the calculated sum in the JSON file.
+ if name == "Sum": continue
+ raw_entries.append({
+ 'name': name,
+ 'duration': value['time_list'],
+ 'count': value['count_list'],
+ })
+
+ domains[domain] = raw_entries
+
+ print(json.dumps(versions, separators=(',', ':')))
+
+
+# Generate JSON file.
+
+def do_json(args):
+ versions = _read_logs(args)
+
for version, domains in versions.items():
if args.aggregate:
create_total_page_stats(domains, args)
@@ -563,7 +607,7 @@
entry.append(round(s['ci']['perc'], 2))
stats.append(entry)
domains[domain] = stats
- print json.dumps(versions, separators=(',', ':'))
+ print(json.dumps(versions, separators=(',', ':')))
# Help.
@@ -644,7 +688,7 @@
"-l", "--log-stderr", type=str, metavar="<path>",
help="specify where chrome's stderr should go (default: /dev/null)")
subparser.add_argument(
- "sites", type=str, metavar="<URL>", nargs="*",
+ "--sites", type=str, metavar="<URL>", nargs="*",
help="specify benchmark website")
add_replay_args(subparsers["run"])
@@ -691,6 +735,20 @@
help="Create aggregated entries. Adds Group-* entries at the toplevel. " \
"Additionally creates a Total page with all entries.")
+ # Command: raw-json.
+ subparsers["raw-json"] = subparser_adder.add_parser(
+ "raw-json", help="Collect raw results from 'run' command into" \
+ "a single json file.")
+ subparsers["raw-json"].set_defaults(
+ func=do_raw_json, error=subparsers["json"].error)
+ subparsers["raw-json"].add_argument(
+ "logdirs", type=str, metavar="<logdir>", nargs="*",
+ help="specify directories with log files to parse")
+ subparsers["raw-json"].add_argument(
+ "--aggregate", dest="aggregate", action="store_true", default=False,
+ help="Create aggregated entries. Adds Group-* entries at the toplevel. " \
+ "Additionally creates a Total page with all entries.")
+
# Command: help.
subparsers["help"] = subparser_adder.add_parser(
"help", help="help information")
diff --git a/src/v8/tools/callstats.py.vpython b/src/v8/tools/callstats.py.vpython
new file mode 100644
index 0000000..11e3f34
--- /dev/null
+++ b/src/v8/tools/callstats.py.vpython
@@ -0,0 +1,43 @@
+# This is a vpython "spec" file.
+#
+# It describes patterns for python wheel dependencies of the python scripts in
+# the callstats.py, particularly for dependencies that have compiled components
+# (since pure-python dependencies can be easily vendored into third_party).
+#
+# When vpython is invoked, it finds this file and builds a python VirtualEnv,
+# containing all of the dependencies described in this file, fetching them from
+# CIPD (the "Chrome Infrastructure Package Deployer" service). Unlike `pip`,
+# this never requires the end-user machine to have a working python extension
+# compilation environment. All of these packages are built using:
+# https://chromium.googlesource.com/infra/infra/+/master/infra/tools/dockerbuild/
+#
+# All python scripts in the repo share this same spec, to avoid dependency
+# fragmentation.
+#
+# If you have depot_tools installed in your $PATH, you can invoke python scripts
+# in this repo by running them as you normally would run them, except
+# substituting `vpython` instead of `python` on the command line, e.g.:
+# vpython path/to/script.py some --arguments
+#
+# Read more about `vpython` and how to modify this file here:
+# https://chromium.googlesource.com/infra/infra/+/master/doc/users/vpython.md
+
+python_version: "2.7"
+
+wheel: <
+ name: "infra/python/wheels/numpy/${vpython_platform}"
+ version: "version:1.11.3"
+>
+
+wheel: <
+ name: "infra/python/wheels/scipy/${vpython_platform}"
+ version: "version:0.19.0"
+ match_tag: <
+ abi: "cp27mu"
+ platform: "manylinux1_i686"
+ >
+ match_tag: <
+ abi: "cp27mu"
+ platform: "manylinux1_x86_64"
+ >
+>
diff --git a/src/v8/tools/cfi/blacklist.txt b/src/v8/tools/cfi/blacklist.txt
index 0ad565e..9886fd3 100644
--- a/src/v8/tools/cfi/blacklist.txt
+++ b/src/v8/tools/cfi/blacklist.txt
@@ -1,4 +1,26 @@
# All std:: types
# This should be possible to remove, if/when we build against
# a statically linked libc++.
-type:std::*
\ No newline at end of file
+type:std::*
+
+# Following entries Taken from chromium's tools/cfi/blacklist.txt
+[cfi-icall]
+
+######### Function pointers cast to incorrect type signatures
+
+# libicu is currently compiled such that in libicu the 'UChar' type is a
+# defined as a char16_t internally, but for the rest of chromium it's an
+# unsigned short, causing mismatched type signatures for icalls to/from icu
+# v8/src/intl.cc
+fun:*LocaleConvertCase*
+
+# PropertyCallbackArguments::Call methods cast function pointers
+src:*src/api/api-arguments-inl.h
+
+# v8 callback that casts argument template parameters
+fun:*PendingPhantomCallback*Invoke*
+
+# weak_callback_ is cast from original type.
+fun:*GlobalHandles*PostGarbageCollectionProcessing*
+
+fun:*InvokeAccessorGetterCallback*
diff --git a/src/v8/tools/check-static-initializers.sh b/src/v8/tools/check-static-initializers.sh
index da43170..fdd1e84 100755
--- a/src/v8/tools/check-static-initializers.sh
+++ b/src/v8/tools/check-static-initializers.sh
@@ -30,8 +30,8 @@
# initializer in d8 matches the one defined below.
# Allow:
-# - _GLOBAL__I__ZN2v810LineEditor6first_E
-# - _GLOBAL__I__ZN2v88internal32AtomicOps_Internalx86CPUFeaturesE
+# _GLOBAL__sub_I_d8.cc
+# _GLOBAL__sub_I_iostream.cpp
expected_static_init_count=2
v8_root=$(readlink -f $(dirname $BASH_SOURCE)/../)
diff --git a/src/v8/tools/check-unused-symbols.sh b/src/v8/tools/check-unused-symbols.sh
new file mode 100755
index 0000000..0348938
--- /dev/null
+++ b/src/v8/tools/check-unused-symbols.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+# 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.
+
+v8_root=$(readlink -f $(dirname $BASH_SOURCE)/../)
+symbols=$(
+ grep \
+ --only-matching \
+ --perl-regexp 'V\(_, \K([^,\)]*)' \
+ -- "$v8_root/src/heap-symbols.h")
+
+# Find symbols which appear exactly once (in heap-symbols.h)
+grep \
+ --only-matching \
+ --no-filename \
+ --recursive \
+ --fixed-strings "$symbols" \
+ -- "$v8_root/src" "$v8_root/test/cctest" \
+| sort \
+| uniq -u \
+| sed -e 's/.*/Heap symbol "&" seems to be unused./'
+
+echo "Kthxbye."
diff --git a/src/v8/tools/clusterfuzz/BUILD.gn b/src/v8/tools/clusterfuzz/BUILD.gn
new file mode 100644
index 0000000..88219600
--- /dev/null
+++ b/src/v8/tools/clusterfuzz/BUILD.gn
@@ -0,0 +1,21 @@
+# Copyright 2017 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("../../gni/v8.gni")
+
+if (v8_correctness_fuzzer) {
+ copy("v8_correctness_fuzzer_resources") {
+ sources = [
+ "v8_commands.py",
+ "v8_foozzie.py",
+ "v8_foozzie_harness_adjust.js",
+ "v8_fuzz_config.py",
+ "v8_mock.js",
+ "v8_mock_archs.js",
+ "v8_suppressions.js",
+ "v8_suppressions.py",
+ ]
+ outputs = [ "$root_out_dir/{{source_file_part}}" ]
+ }
+}
diff --git a/src/v8/tools/clusterfuzz/testdata/failure_output.txt b/src/v8/tools/clusterfuzz/testdata/failure_output.txt
new file mode 100644
index 0000000..fe94bb9
--- /dev/null
+++ b/src/v8/tools/clusterfuzz/testdata/failure_output.txt
@@ -0,0 +1,50 @@
+#
+# V8 correctness failure
+# V8 correctness configs: x64,ignition:x64,ignition_turbo
+# V8 correctness sources: f60
+# V8 correctness suppression:
+#
+# CHECK
+#
+# Compared x64,ignition with x64,ignition_turbo
+#
+# Flags of x64,ignition:
+--correctness-fuzzer-suppressions --expose-gc --allow-natives-syntax --invoke-weak-callbacks --omit-quit --es-staging --no-wasm-async-compilation --suppress-asm-messages --random-seed 12345 --turbo-filter=~ --noopt --liftoff --no-wasm-tier-up --flag1 --flag2=0
+# Flags of x64,ignition_turbo:
+--correctness-fuzzer-suppressions --expose-gc --allow-natives-syntax --invoke-weak-callbacks --omit-quit --es-staging --no-wasm-async-compilation --suppress-asm-messages --random-seed 12345 --flag3
+#
+# Difference:
+- unknown
++ not unknown
+#
+# Source file:
+name/to/file.js
+#
+### Start of configuration x64,ignition:
+
+1
+v8-foozzie source: name/to/a/file.js
+2
+v8-foozzie source: name/to/file.js
+ weird error
+ ^
+3
+unknown
+
+
+### End of configuration x64,ignition
+#
+### Start of configuration x64,ignition_turbo:
+
+1
+v8-foozzie source: name/to/a/file.js
+2
+v8-foozzie source: name/to/file.js
+ weird other error
+^
+3
+not unknown
+
+
+### End of configuration x64,ignition_turbo
+
diff --git a/src/v8/tools/clusterfuzz/testdata/fuzz-123.js b/src/v8/tools/clusterfuzz/testdata/fuzz-123.js
new file mode 100644
index 0000000..fbde573
--- /dev/null
+++ b/src/v8/tools/clusterfuzz/testdata/fuzz-123.js
@@ -0,0 +1,6 @@
+// Copyright 2016 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.
+
+// Empty test dummy.
+print("js-mutation: start generated test case");
diff --git a/src/v8/tools/clusterfuzz/testdata/sanity_check_output.txt b/src/v8/tools/clusterfuzz/testdata/sanity_check_output.txt
new file mode 100644
index 0000000..636f4c9
--- /dev/null
+++ b/src/v8/tools/clusterfuzz/testdata/sanity_check_output.txt
@@ -0,0 +1,47 @@
+#
+# V8 correctness failure
+# V8 correctness configs: x64,ignition:x64,ignition_turbo
+# V8 correctness sources: sanity check failed
+# V8 correctness suppression:
+#
+# CHECK
+#
+# Compared x64,ignition with x64,ignition_turbo
+#
+# Flags of x64,ignition:
+--correctness-fuzzer-suppressions --expose-gc --allow-natives-syntax --invoke-weak-callbacks --omit-quit --es-staging --no-wasm-async-compilation --suppress-asm-messages --random-seed 12345 --turbo-filter=~ --noopt --liftoff --no-wasm-tier-up
+# Flags of x64,ignition_turbo:
+--correctness-fuzzer-suppressions --expose-gc --allow-natives-syntax --invoke-weak-callbacks --omit-quit --es-staging --no-wasm-async-compilation --suppress-asm-messages --random-seed 12345
+#
+# Difference:
+- unknown
++ not unknown
+#
+### Start of configuration x64,ignition:
+
+1
+v8-foozzie source: name/to/a/file.js
+2
+v8-foozzie source: name/to/file.js
+ weird error
+ ^
+3
+unknown
+
+
+### End of configuration x64,ignition
+#
+### Start of configuration x64,ignition_turbo:
+
+1
+v8-foozzie source: name/to/a/file.js
+2
+v8-foozzie source: name/to/file.js
+ weird other error
+^
+3
+not unknown
+
+
+### End of configuration x64,ignition_turbo
+
diff --git a/src/v8/tools/clusterfuzz/testdata/test_d8_1.py b/src/v8/tools/clusterfuzz/testdata/test_d8_1.py
new file mode 100644
index 0000000..4a3d008
--- /dev/null
+++ b/src/v8/tools/clusterfuzz/testdata/test_d8_1.py
@@ -0,0 +1,17 @@
+# Copyright 2016 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.
+
+# for py2/py3 compatibility
+from __future__ import print_function
+
+print("""
+1
+v8-foozzie source: name/to/a/file.js
+2
+v8-foozzie source: name/to/file.js
+ weird error
+ ^
+3
+unknown
+""")
diff --git a/src/v8/tools/clusterfuzz/testdata/test_d8_2.py b/src/v8/tools/clusterfuzz/testdata/test_d8_2.py
new file mode 100644
index 0000000..824b222
--- /dev/null
+++ b/src/v8/tools/clusterfuzz/testdata/test_d8_2.py
@@ -0,0 +1,17 @@
+# Copyright 2016 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.
+
+# for py2/py3 compatibility
+from __future__ import print_function
+
+print("""
+1
+v8-foozzie source: name/to/a/file.js
+2
+v8-foozzie source: name/to/file.js
+ weird other error
+^
+3
+unknown
+""")
diff --git a/src/v8/tools/clusterfuzz/testdata/test_d8_3.py b/src/v8/tools/clusterfuzz/testdata/test_d8_3.py
new file mode 100644
index 0000000..0b19a3f
--- /dev/null
+++ b/src/v8/tools/clusterfuzz/testdata/test_d8_3.py
@@ -0,0 +1,17 @@
+# Copyright 2016 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.
+
+# for py2/py3 compatibility
+from __future__ import print_function
+
+print("""
+1
+v8-foozzie source: name/to/a/file.js
+2
+v8-foozzie source: name/to/file.js
+ weird other error
+^
+3
+not unknown
+""")
diff --git a/src/v8/tools/clusterfuzz/testdata/v8_build_config.json b/src/v8/tools/clusterfuzz/testdata/v8_build_config.json
new file mode 100644
index 0000000..ea27b1c
--- /dev/null
+++ b/src/v8/tools/clusterfuzz/testdata/v8_build_config.json
@@ -0,0 +1 @@
+{"v8_current_cpu": "x64"}
diff --git a/src/v8/tools/clusterfuzz/v8_commands.py b/src/v8/tools/clusterfuzz/v8_commands.py
new file mode 100644
index 0000000..0b3cae7
--- /dev/null
+++ b/src/v8/tools/clusterfuzz/v8_commands.py
@@ -0,0 +1,64 @@
+# Copyright 2016 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.
+
+# Fork from commands.py and output.py in v8 test driver.
+
+import signal
+import subprocess
+import sys
+from threading import Event, Timer
+
+
+class Output(object):
+ def __init__(self, exit_code, timed_out, stdout, pid):
+ self.exit_code = exit_code
+ self.timed_out = timed_out
+ self.stdout = stdout
+ self.pid = pid
+
+ def HasCrashed(self):
+ # Timed out tests will have exit_code -signal.SIGTERM.
+ if self.timed_out:
+ return False
+ return (self.exit_code < 0 and
+ self.exit_code != -signal.SIGABRT)
+
+ def HasTimedOut(self):
+ return self.timed_out
+
+
+def Execute(args, cwd, timeout=None):
+ popen_args = [c for c in args if c != ""]
+ try:
+ process = subprocess.Popen(
+ args=popen_args,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ cwd=cwd
+ )
+ except Exception as e:
+ sys.stderr.write("Error executing: %s\n" % popen_args)
+ raise e
+
+ timeout_event = Event()
+
+ def kill_process():
+ timeout_event.set()
+ try:
+ process.kill()
+ except OSError:
+ sys.stderr.write('Error: Process %s already ended.\n' % process.pid)
+
+
+ timer = Timer(timeout, kill_process)
+ timer.start()
+ stdout, _ = process.communicate()
+ timer.cancel()
+
+ return Output(
+ process.returncode,
+ timeout_event.is_set(),
+ stdout.decode('utf-8', 'replace').encode('utf-8'),
+ process.pid,
+ )
diff --git a/src/v8/tools/clusterfuzz/v8_foozzie.py b/src/v8/tools/clusterfuzz/v8_foozzie.py
new file mode 100755
index 0000000..55f76e8
--- /dev/null
+++ b/src/v8/tools/clusterfuzz/v8_foozzie.py
@@ -0,0 +1,423 @@
+#!/usr/bin/env python
+# Copyright 2016 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.
+
+"""
+V8 correctness fuzzer launcher script.
+"""
+
+# for py2/py3 compatibility
+from __future__ import print_function
+
+import argparse
+import hashlib
+import itertools
+import json
+import os
+import random
+import re
+import sys
+import traceback
+
+import v8_commands
+import v8_suppressions
+
+CONFIGS = dict(
+ default=[],
+ ignition=[
+ '--turbo-filter=~',
+ '--noopt',
+ '--liftoff',
+ '--no-wasm-tier-up',
+ ],
+ ignition_asm=[
+ '--turbo-filter=~',
+ '--noopt',
+ '--validate-asm',
+ '--stress-validate-asm',
+ ],
+ ignition_eager=[
+ '--turbo-filter=~',
+ '--noopt',
+ '--no-lazy',
+ '--no-lazy-inner-functions',
+ ],
+ ignition_no_ic=[
+ '--turbo-filter=~',
+ '--noopt',
+ '--liftoff',
+ '--no-wasm-tier-up',
+ '--no-use-ic',
+ '--no-lazy-feedback-allocation',
+ ],
+ ignition_turbo=[],
+ ignition_turbo_no_ic=[
+ '--no-use-ic',
+ ],
+ ignition_turbo_opt=[
+ '--always-opt',
+ '--no-liftoff',
+ '--no-wasm-tier-up',
+ '--no-lazy-feedback-allocation'
+ ],
+ ignition_turbo_opt_eager=[
+ '--always-opt',
+ '--no-lazy',
+ '--no-lazy-inner-functions',
+ '--no-lazy-feedback-allocation',
+ ],
+ jitless=[
+ '--jitless',
+ ],
+ slow_path=[
+ '--force-slow-path',
+ ],
+ slow_path_opt=[
+ '--always-opt',
+ '--force-slow-path',
+ '--no-lazy-feedback-allocation',
+ ],
+ trusted=[
+ '--no-untrusted-code-mitigations',
+ ],
+ trusted_opt=[
+ '--always-opt',
+ '--no-untrusted-code-mitigations',
+ '--no-lazy-feedback-allocation',
+ ],
+)
+
+# Timeout in seconds for one d8 run.
+TIMEOUT = 3
+
+# Return codes.
+RETURN_PASS = 0
+RETURN_FAIL = 2
+
+BASE_PATH = os.path.dirname(os.path.abspath(__file__))
+PREAMBLE = [
+ os.path.join(BASE_PATH, 'v8_mock.js'),
+ os.path.join(BASE_PATH, 'v8_suppressions.js'),
+]
+ARCH_MOCKS = os.path.join(BASE_PATH, 'v8_mock_archs.js')
+SANITY_CHECKS = os.path.join(BASE_PATH, 'v8_sanity_checks.js')
+
+FLAGS = ['--correctness-fuzzer-suppressions', '--expose-gc',
+ '--allow-natives-syntax', '--invoke-weak-callbacks', '--omit-quit',
+ '--es-staging', '--no-wasm-async-compilation',
+ '--suppress-asm-messages']
+
+SUPPORTED_ARCHS = ['ia32', 'x64', 'arm', 'arm64']
+
+# Output for suppressed failure case.
+FAILURE_HEADER_TEMPLATE = """#
+# V8 correctness failure
+# V8 correctness configs: %(configs)s
+# V8 correctness sources: %(source_key)s
+# V8 correctness suppression: %(suppression)s
+"""
+
+# Extended output for failure case. The 'CHECK' is for the minimizer.
+FAILURE_TEMPLATE = FAILURE_HEADER_TEMPLATE + """#
+# CHECK
+#
+# Compared %(first_config_label)s with %(second_config_label)s
+#
+# Flags of %(first_config_label)s:
+%(first_config_flags)s
+# Flags of %(second_config_label)s:
+%(second_config_flags)s
+#
+# Difference:
+%(difference)s%(source_file_text)s
+#
+### Start of configuration %(first_config_label)s:
+%(first_config_output)s
+### End of configuration %(first_config_label)s
+#
+### Start of configuration %(second_config_label)s:
+%(second_config_output)s
+### End of configuration %(second_config_label)s
+"""
+
+SOURCE_FILE_TEMPLATE = """
+#
+# Source file:
+%s"""
+
+
+FUZZ_TEST_RE = re.compile(r'.*fuzz(-\d+\.js)')
+SOURCE_RE = re.compile(r'print\("v8-foozzie source: (.*)"\);')
+
+# The number of hex digits used from the hash of the original source file path.
+# Keep the number small to avoid duplicate explosion.
+ORIGINAL_SOURCE_HASH_LENGTH = 3
+
+# Placeholder string if no original source file could be determined.
+ORIGINAL_SOURCE_DEFAULT = 'none'
+
+
+def infer_arch(d8):
+ """Infer the V8 architecture from the build configuration next to the
+ executable.
+ """
+ with open(os.path.join(os.path.dirname(d8), 'v8_build_config.json')) as f:
+ arch = json.load(f)['v8_current_cpu']
+ return 'ia32' if arch == 'x86' else arch
+
+
+def parse_args():
+ parser = argparse.ArgumentParser()
+ parser.add_argument(
+ '--random-seed', type=int, required=True,
+ help='random seed passed to both runs')
+ parser.add_argument(
+ '--first-config', help='first configuration', default='ignition')
+ parser.add_argument(
+ '--second-config', help='second configuration', default='ignition_turbo')
+ parser.add_argument(
+ '--first-config-extra-flags', action='append', default=[],
+ help='Additional flags to pass to the run of the first configuration')
+ parser.add_argument(
+ '--second-config-extra-flags', action='append', default=[],
+ help='Additional flags to pass to the run of the second configuration')
+ parser.add_argument(
+ '--first-d8', default='d8',
+ help='optional path to first d8 executable, '
+ 'default: bundled in the same directory as this script')
+ parser.add_argument(
+ '--second-d8',
+ help='optional path to second d8 executable, default: same as first')
+ parser.add_argument(
+ '--skip-sanity-checks', default=False, action='store_true',
+ help='skip sanity checks for testing purposes')
+ parser.add_argument('testcase', help='path to test case')
+ options = parser.parse_args()
+
+ # Ensure we have a test case.
+ assert (os.path.exists(options.testcase) and
+ os.path.isfile(options.testcase)), (
+ 'Test case %s doesn\'t exist' % options.testcase)
+
+ # Use first d8 as default for second d8.
+ options.second_d8 = options.second_d8 or options.first_d8
+
+ # Ensure absolute paths.
+ if not os.path.isabs(options.first_d8):
+ options.first_d8 = os.path.join(BASE_PATH, options.first_d8)
+ if not os.path.isabs(options.second_d8):
+ options.second_d8 = os.path.join(BASE_PATH, options.second_d8)
+
+ # Ensure executables exist.
+ assert os.path.exists(options.first_d8)
+ assert os.path.exists(options.second_d8)
+
+ # Infer architecture from build artifacts.
+ options.first_arch = infer_arch(options.first_d8)
+ options.second_arch = infer_arch(options.second_d8)
+
+ # Ensure we make a sane comparison.
+ if (options.first_arch == options.second_arch and
+ options.first_config == options.second_config):
+ parser.error('Need either arch or config difference.')
+ assert options.first_arch in SUPPORTED_ARCHS
+ assert options.second_arch in SUPPORTED_ARCHS
+ assert options.first_config in CONFIGS
+ assert options.second_config in CONFIGS
+
+ return options
+
+
+def get_meta_data(content):
+ """Extracts original-source-file paths from test case content."""
+ sources = []
+ for line in content.splitlines():
+ match = SOURCE_RE.match(line)
+ if match:
+ sources.append(match.group(1))
+ return {'sources': sources}
+
+
+def content_bailout(content, ignore_fun):
+ """Print failure state and return if ignore_fun matches content."""
+ bug = (ignore_fun(content) or '').strip()
+ if bug:
+ print(FAILURE_HEADER_TEMPLATE % dict(
+ configs='', source_key='', suppression=bug))
+ return True
+ return False
+
+
+def pass_bailout(output, step_number):
+ """Print info and return if in timeout or crash pass states."""
+ if output.HasTimedOut():
+ # Dashed output, so that no other clusterfuzz tools can match the
+ # words timeout or crash.
+ print('# V8 correctness - T-I-M-E-O-U-T %d' % step_number)
+ return True
+ if output.HasCrashed():
+ print('# V8 correctness - C-R-A-S-H %d' % step_number)
+ return True
+ return False
+
+
+def fail_bailout(output, ignore_by_output_fun):
+ """Print failure state and return if ignore_by_output_fun matches output."""
+ bug = (ignore_by_output_fun(output.stdout) or '').strip()
+ if bug:
+ print(FAILURE_HEADER_TEMPLATE % dict(
+ configs='', source_key='', suppression=bug))
+ return True
+ return False
+
+
+def print_difference(
+ options, source_key, first_config_flags, second_config_flags,
+ first_config_output, second_config_output, difference, source=None):
+ # The first three entries will be parsed by clusterfuzz. Format changes
+ # will require changes on the clusterfuzz side.
+ first_config_label = '%s,%s' % (options.first_arch, options.first_config)
+ second_config_label = '%s,%s' % (options.second_arch, options.second_config)
+ source_file_text = SOURCE_FILE_TEMPLATE % source if source else ''
+ print((FAILURE_TEMPLATE % dict(
+ configs='%s:%s' % (first_config_label, second_config_label),
+ source_file_text=source_file_text,
+ source_key=source_key,
+ suppression='', # We can't tie bugs to differences.
+ first_config_label=first_config_label,
+ second_config_label=second_config_label,
+ first_config_flags=' '.join(first_config_flags),
+ second_config_flags=' '.join(second_config_flags),
+ first_config_output=
+ first_config_output.stdout.decode('utf-8', 'replace'),
+ second_config_output=
+ second_config_output.stdout.decode('utf-8', 'replace'),
+ source=source,
+ difference=difference.decode('utf-8', 'replace'),
+ )).encode('utf-8', 'replace'))
+
+
+def main():
+ options = parse_args()
+
+ # Suppressions are architecture and configuration specific.
+ suppress = v8_suppressions.get_suppression(
+ options.first_arch, options.first_config,
+ options.second_arch, options.second_config,
+ )
+
+ # Static bailout based on test case content or metadata.
+ with open(options.testcase) as f:
+ content = f.read()
+ if content_bailout(get_meta_data(content), suppress.ignore_by_metadata):
+ return RETURN_FAIL
+ if content_bailout(content, suppress.ignore_by_content):
+ return RETURN_FAIL
+
+ # Set up runtime arguments.
+ common_flags = FLAGS + ['--random-seed', str(options.random_seed)]
+ first_config_flags = (common_flags + CONFIGS[options.first_config] +
+ options.first_config_extra_flags)
+ second_config_flags = (common_flags + CONFIGS[options.second_config] +
+ options.second_config_extra_flags)
+
+ def run_d8(d8, config_flags, config_label=None, testcase=options.testcase):
+ preamble = PREAMBLE[:]
+ if options.first_arch != options.second_arch:
+ preamble.append(ARCH_MOCKS)
+ args = [d8] + config_flags + preamble + [testcase]
+ if config_label:
+ print('# Command line for %s comparison:' % config_label)
+ print(' '.join(args))
+ if d8.endswith('.py'):
+ # Wrap with python in tests.
+ args = [sys.executable] + args
+ return v8_commands.Execute(
+ args,
+ cwd=os.path.dirname(os.path.abspath(testcase)),
+ timeout=TIMEOUT,
+ )
+
+ # 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 = run_d8(
+ options.first_d8, first_config_flags, testcase=SANITY_CHECKS)
+ second_config_output = run_d8(
+ options.second_d8, second_config_flags, testcase=SANITY_CHECKS)
+ difference, _ = suppress.diff(
+ first_config_output.stdout, second_config_output.stdout)
+ if difference:
+ # Special source key for sanity checks so that clusterfuzz dedupes all
+ # cases on this in case it's hit.
+ source_key = 'sanity check failed'
+ print_difference(
+ options, source_key, first_config_flags, second_config_flags,
+ first_config_output, second_config_output, difference)
+ return RETURN_FAIL
+
+ first_config_output = run_d8(options.first_d8, first_config_flags, 'first')
+
+ # Early bailout based on first run's output.
+ if pass_bailout(first_config_output, 1):
+ return RETURN_PASS
+
+ second_config_output = run_d8(
+ options.second_d8, second_config_flags, 'second')
+
+ # Bailout based on second run's output.
+ if pass_bailout(second_config_output, 2):
+ return RETURN_PASS
+
+ difference, source = suppress.diff(
+ first_config_output.stdout, second_config_output.stdout)
+
+ if source:
+ source_key = hashlib.sha1(source).hexdigest()[: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
+ # remove it.
+ if fail_bailout(first_config_output, suppress.ignore_by_output1):
+ return RETURN_FAIL
+ if fail_bailout(second_config_output, suppress.ignore_by_output2):
+ return RETURN_FAIL
+
+ print_difference(
+ options, source_key, first_config_flags, second_config_flags,
+ first_config_output, second_config_output, difference, source)
+ return RETURN_FAIL
+
+ # TODO(machenbach): Figure out if we could also return a bug in case there's
+ # no difference, but one of the line suppressions has matched - and without
+ # the match there would be a difference.
+
+ print('# V8 correctness - pass')
+ return RETURN_PASS
+
+
+if __name__ == "__main__":
+ try:
+ result = main()
+ except SystemExit:
+ # Make sure clusterfuzz reports internal errors and wrong usage.
+ # Use one label for all internal and usage errors.
+ print(FAILURE_HEADER_TEMPLATE % dict(
+ configs='', source_key='', suppression='wrong_usage'))
+ result = RETURN_FAIL
+ except MemoryError:
+ # Running out of memory happens occasionally but is not actionable.
+ print('# V8 correctness - pass')
+ result = RETURN_PASS
+ except Exception as e:
+ print(FAILURE_HEADER_TEMPLATE % dict(
+ configs='', source_key='', suppression='internal_error'))
+ print('# Internal error: %s' % e)
+ traceback.print_exc(file=sys.stdout)
+ result = RETURN_FAIL
+
+ sys.exit(result)
diff --git a/src/v8/tools/clusterfuzz/v8_foozzie_harness_adjust.js b/src/v8/tools/clusterfuzz/v8_foozzie_harness_adjust.js
new file mode 100644
index 0000000..4a8ed35
--- /dev/null
+++ b/src/v8/tools/clusterfuzz/v8_foozzie_harness_adjust.js
@@ -0,0 +1,96 @@
+// 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.
+
+// Extensions to mjsunit and other test harnesses added between harness and
+// fuzzing code.
+
+try {
+ // Scope for utility functions.
+ (function() {
+ // Same as in mjsunit.js.
+ function classOf(object) {
+ // Argument must not be null or undefined.
+ var string = Object.prototype.toString.call(object);
+ // String has format [object <ClassName>].
+ return string.substring(8, string.length - 1);
+ }
+
+ // Override prettyPrinted with a version that also recusively prints object
+ // properties (with a depth of 3).
+ let origPrettyPrinted = prettyPrinted;
+ prettyPrinted = function prettyPrinted(value, depth=3) {
+ if (depth == 0) {
+ return "...";
+ }
+ switch (typeof value) {
+ case "object":
+ if (value === null) return "null";
+ var objectClass = classOf(value);
+ switch (objectClass) {
+ case "Object":
+ var name = value.constructor.name;
+ if (!name)
+ name = "Object";
+ return name + "{" + Object.keys(value).map(function(key, index) {
+ return (
+ prettyPrinted(key, depth - 1) +
+ ": " +
+ prettyPrinted(value[key], depth - 1)
+ );
+ }).join(",") + "}";
+ }
+ }
+ // Fall through to original version for all other types.
+ return origPrettyPrinted(value);
+ }
+
+ // We're not interested in stack traces.
+ MjsUnitAssertionError = function MjsUnitAssertionError(message) {}
+ MjsUnitAssertionError.prototype.toString = function () { return ""; };
+
+ // Do more printing in assertions for more correctness coverage.
+ failWithMessage = function failWithMessage(message) {
+ print(prettyPrinted(message))
+ }
+
+ assertSame = function assertSame(expected, found, name_opt) {
+ print(prettyPrinted(found));
+ }
+
+ assertNotSame = function assertNotSame(expected, found, name_opt) {
+ print(prettyPrinted(found));
+ }
+
+ assertEquals = function assertEquals(expected, found, name_opt) {
+ print(prettyPrinted(found));
+ }
+
+ assertNotEquals = function assertNotEquals(expected, found, name_opt) {
+ print(prettyPrinted(found));
+ }
+
+ assertNull = function assertNull(value, name_opt) {
+ print(prettyPrinted(value));
+ }
+
+ assertNotNull = function assertNotNull(value, name_opt) {
+ print(prettyPrinted(value));
+ }
+
+ // Suppress optimization status as it leads to false positives.
+ assertUnoptimized = function assertUnoptimized() {}
+
+ assertOptimized = function assertOptimized() {}
+
+ isNeverOptimize = function isNeverOptimize() {}
+
+ isAlwaysOptimize = function isAlwaysOptimize() {}
+
+ isInterpreted = function isInterpreted() {}
+
+ isOptimized = function isOptimized() {}
+
+ isTurboFanned = function isTurboFanned() {}
+ })();
+} catch(e) { }
diff --git a/src/v8/tools/clusterfuzz/v8_foozzie_test.py b/src/v8/tools/clusterfuzz/v8_foozzie_test.py
new file mode 100755
index 0000000..43b65e8
--- /dev/null
+++ b/src/v8/tools/clusterfuzz/v8_foozzie_test.py
@@ -0,0 +1,160 @@
+#!/usr/bin/env python
+# Copyright 2016 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 os
+import subprocess
+import sys
+import unittest
+
+import v8_foozzie
+import v8_fuzz_config
+import v8_suppressions
+
+BASE_DIR = os.path.dirname(os.path.abspath(__file__))
+FOOZZIE = os.path.join(BASE_DIR, 'v8_foozzie.py')
+TEST_DATA = os.path.join(BASE_DIR, 'testdata')
+
+
+class ConfigTest(unittest.TestCase):
+ def testExperiments(self):
+ """Test that probabilities add up to 100 and that all config names exist.
+ """
+ EXPERIMENTS = v8_fuzz_config.FOOZZIE_EXPERIMENTS
+ CONFIGS = v8_foozzie.CONFIGS
+ assert sum(x[0] for x in EXPERIMENTS) == 100
+ assert all(map(lambda x: x[1] in CONFIGS, EXPERIMENTS))
+ assert all(map(lambda x: x[2] in CONFIGS, EXPERIMENTS))
+ assert all(map(lambda x: x[3].endswith('d8'), EXPERIMENTS))
+
+ def testConfig(self):
+ """Smoke test how to choose experiments.
+
+ When experiment distribution changes this test might change, too.
+ """
+ class Rng(object):
+ def random(self):
+ return 0.5
+ self.assertEqual(
+ [
+ '--first-config=ignition',
+ '--second-config=ignition_turbo',
+ '--second-d8=d8',
+ '--second-config-extra-flags=--stress-scavenge=100',
+ ],
+ v8_fuzz_config.Config('foo', Rng(), 42).choose_foozzie_flags(),
+ )
+
+
+class UnitTest(unittest.TestCase):
+ def testDiff(self):
+ # TODO(machenbach): Mock out suppression configuration.
+ suppress = v8_suppressions.get_suppression(
+ 'x64', 'ignition', 'x64', 'ignition_turbo')
+ one = ''
+ two = ''
+ diff = None, None
+ self.assertEquals(diff, suppress.diff(one, two))
+
+ one = 'a \n b\nc();'
+ two = 'a \n b\nc();'
+ diff = None, None
+ self.assertEquals(diff, suppress.diff(one, two))
+
+ # Ignore line before caret, caret position and error message.
+ one = """
+undefined
+weird stuff
+ ^
+somefile.js: TypeError: undefined is not a function
+ undefined
+"""
+ two = """
+undefined
+other weird stuff
+ ^
+somefile.js: TypeError: baz is not a function
+ undefined
+"""
+ diff = None, None
+ self.assertEquals(diff, suppress.diff(one, two))
+
+ one = """
+Still equal
+Extra line
+"""
+ two = """
+Still equal
+"""
+ diff = '- Extra line', None
+ self.assertEquals(diff, suppress.diff(one, two))
+
+ one = """
+Still equal
+"""
+ two = """
+Still equal
+Extra line
+"""
+ diff = '+ Extra line', None
+ self.assertEquals(diff, suppress.diff(one, two))
+
+ one = """
+undefined
+somefile.js: TypeError: undefined is not a constructor
+"""
+ two = """
+undefined
+otherfile.js: TypeError: undefined is not a constructor
+"""
+ diff = """- somefile.js: TypeError: undefined is not a constructor
++ otherfile.js: TypeError: undefined is not a constructor""", None
+ self.assertEquals(diff, suppress.diff(one, two))
+
+
+def cut_verbose_output(stdout):
+ # This removes first lines containing d8 commands.
+ return '\n'.join(stdout.split('\n')[4:])
+
+
+def run_foozzie(first_d8, second_d8, *extra_flags):
+ return subprocess.check_output([
+ sys.executable, FOOZZIE,
+ '--random-seed', '12345',
+ '--first-d8', os.path.join(TEST_DATA, first_d8),
+ '--second-d8', os.path.join(TEST_DATA, second_d8),
+ '--first-config', 'ignition',
+ '--second-config', 'ignition_turbo',
+ os.path.join(TEST_DATA, 'fuzz-123.js'),
+ ] + list(extra_flags))
+
+
+class SystemTest(unittest.TestCase):
+ def testSyntaxErrorDiffPass(self):
+ stdout = run_foozzie('test_d8_1.py', 'test_d8_2.py', '--skip-sanity-checks')
+ self.assertEquals('# V8 correctness - pass\n', cut_verbose_output(stdout))
+
+ def testDifferentOutputFail(self):
+ with open(os.path.join(TEST_DATA, 'failure_output.txt')) as f:
+ expected_output = f.read()
+ with self.assertRaises(subprocess.CalledProcessError) as ctx:
+ run_foozzie('test_d8_1.py', 'test_d8_3.py', '--skip-sanity-checks',
+ '--first-config-extra-flags=--flag1',
+ '--first-config-extra-flags=--flag2=0',
+ '--second-config-extra-flags=--flag3')
+ e = ctx.exception
+ self.assertEquals(v8_foozzie.RETURN_FAIL, e.returncode)
+ self.assertEquals(expected_output, cut_verbose_output(e.output))
+
+ def testSanityCheck(self):
+ with open(os.path.join(TEST_DATA, 'sanity_check_output.txt')) as f:
+ expected_output = f.read()
+ with self.assertRaises(subprocess.CalledProcessError) as ctx:
+ run_foozzie('test_d8_1.py', 'test_d8_3.py')
+ e = ctx.exception
+ self.assertEquals(v8_foozzie.RETURN_FAIL, e.returncode)
+ self.assertEquals(expected_output, e.output)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/src/v8/tools/clusterfuzz/v8_fuzz_config.py b/src/v8/tools/clusterfuzz/v8_fuzz_config.py
new file mode 100644
index 0000000..0dcacf2
--- /dev/null
+++ b/src/v8/tools/clusterfuzz/v8_fuzz_config.py
@@ -0,0 +1,86 @@
+# 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.
+
+import random
+
+# List of configuration experiments for correctness fuzzing.
+# List of <probability>, <1st config name>, <2nd config name>, <2nd d8>.
+# Probabilities must add up to 100.
+FOOZZIE_EXPERIMENTS = [
+ [10, 'ignition', 'jitless', 'd8'],
+ [10, 'ignition', 'slow_path', 'd8'],
+ [5, 'ignition', 'slow_path_opt', 'd8'],
+ [26, 'ignition', 'ignition_turbo', 'd8'],
+ [2, 'ignition_no_ic', 'ignition_turbo', 'd8'],
+ [2, 'ignition', 'ignition_turbo_no_ic', 'd8'],
+ [18, 'ignition', 'ignition_turbo_opt', 'd8'],
+ [2, 'ignition_no_ic', 'ignition_turbo_opt', 'd8'],
+ [5, 'ignition_turbo_opt', 'ignition_turbo_opt', 'clang_x86/d8'],
+ [5, 'ignition_turbo', 'ignition_turbo', 'clang_x86/d8'],
+ [5, 'ignition', 'ignition', 'clang_x86/d8'],
+ [5, 'ignition', 'ignition', 'clang_x64_v8_arm64/d8'],
+ [5, 'ignition', 'ignition', 'clang_x86_v8_arm/d8'],
+]
+
+# Additional flag experiments. List of tuples like
+# (<likelihood to use flags in [0,1)>, <flag>).
+ADDITIONAL_FLAGS = [
+ (0.1, '--stress-marking=100'),
+ (0.1, '--stress-scavenge=100'),
+ (0.1, '--stress-compaction-random'),
+ (0.1, '--random-gc-interval=2000'),
+ (0.2, '--noanalyze-environment-liveness'),
+ (0.1, '--stress-delay-tasks'),
+ (0.01, '--thread-pool-size=1'),
+ (0.01, '--thread-pool-size=2'),
+ (0.01, '--thread-pool-size=4'),
+ (0.01, '--thread-pool-size=8'),
+ (0.1, '--interrupt-budget=1000'),
+]
+
+class Config(object):
+ def __init__(self, name, rng=None, random_seed=None):
+ """
+ Args:
+ name: Name of the used fuzzer.
+ rng: Random number generator for generating experiments.
+ random_seed: Random-seed used for d8 throughout one fuzz session.
+ TODO(machenbach): Remove random_seed after a grace period of a couple of
+ days. We only have it to keep bisection stable. Afterwards we can just
+ use rng.
+ """
+ self.name = name
+ self.rng = rng or random.Random()
+ self.random_seed = random_seed
+
+ def choose_foozzie_flags(self):
+ """Randomly chooses a configuration from FOOZZIE_EXPERIMENTS.
+
+ Returns: List of flags to pass to v8_foozzie.py fuzz harness.
+ """
+ # TODO(machenbach): Temporarily use same RNG state for all test cases in one
+ # fuzz session. See also TODO above.
+ if self.random_seed is not None:
+ flags_rng = random.Random(self.random_seed)
+ else:
+ flags_rng = random.Random()
+
+ # Add additional flags to second config based on experiment percentages.
+ extra_flags = []
+ for p, flag in ADDITIONAL_FLAGS:
+ if flags_rng.random() < p:
+ extra_flags.append('--second-config-extra-flags=%s' % flag)
+
+ # Calculate flags determining the experiment.
+ acc = 0
+ threshold = self.rng.random() * 100
+ for prob, first_config, second_config, second_d8 in FOOZZIE_EXPERIMENTS:
+ acc += prob
+ if acc > threshold:
+ return [
+ '--first-config=' + first_config,
+ '--second-config=' + second_config,
+ '--second-d8=' + second_d8,
+ ] + extra_flags
+ assert False
diff --git a/src/v8/tools/clusterfuzz/v8_mock.js b/src/v8/tools/clusterfuzz/v8_mock.js
new file mode 100644
index 0000000..2f797dd
--- /dev/null
+++ b/src/v8/tools/clusterfuzz/v8_mock.js
@@ -0,0 +1,139 @@
+// Copyright 2016 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This is intended for permanent JS behavior changes for mocking out
+// non-deterministic behavior. For temporary suppressions, please refer to
+// v8_suppressions.js.
+// This file is loaded before each correctness test cases and won't get
+// minimized.
+
+
+// This will be overridden in the test cases. The override can be minimized.
+var prettyPrinted = function prettyPrinted(msg) { return msg; };
+
+// Mock Math.random.
+(function () {
+ var index = 0
+ Math.random = function() {
+ index = (index + 1) % 10;
+ return index / 10.0;
+ }
+})();
+
+// Mock Date.
+(function () {
+ var index = 0
+ var mockDate = 1477662728696
+ var mockDateNow = function() {
+ index = (index + 1) % 10
+ mockDate = mockDate + index + 1
+ return mockDate
+ }
+
+ var origDate = Date;
+ var constructDate = function(args) {
+ if (args.length == 1) {
+ var result = new origDate(args[0]);
+ } else if (args.length == 2) {
+ var result = new origDate(args[0], args[1]);
+ } else if (args.length == 3) {
+ var result = new origDate(args[0], args[1], args[2]);
+ } else if (args.length == 4) {
+ var result = new origDate(args[0], args[1], args[2], args[3]);
+ } else if (args.length == 5) {
+ var result = new origDate(args[0], args[1], args[2], args[3], args[4]);
+ } else if (args.length == 6) {
+ var result = new origDate(
+ args[0], args[1], args[2], args[3], args[4], args[5]);
+ } else if (args.length >= 7) {
+ var result = new origDate(
+ args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
+ } else {
+ var result = new origDate(mockDateNow());
+ }
+ result.constructor = function(...args) { return constructDate(args); }
+ Object.defineProperty(
+ result, "constructor", { configurable: false, writable: false });
+ return result
+ }
+
+ var handler = {
+ apply: function (target, thisArg, args) {
+ return constructDate(args)
+ },
+ construct: function (target, args, newTarget) {
+ return constructDate(args)
+ },
+ get: function(target, property, receiver) {
+ if (property == "now") {
+ return mockDateNow;
+ }
+ if (property == "prototype") {
+ return origDate.prototype
+ }
+ },
+ }
+
+ Date = new Proxy(Date, handler);
+})();
+
+// Mock performace.now().
+(function () {
+ performance.now = function () { return 1.2; }
+})();
+
+// Mock stack traces.
+Error.prepareStackTrace = function (error, structuredStackTrace) {
+ return "";
+};
+Object.defineProperty(
+ Error, 'prepareStackTrace', { configurable: false, writable: false });
+
+// Mock buffer access in float typed arrays because of varying NaN patterns.
+// Note, for now we just use noop forwarding proxies, because they already
+// turn off optimizations.
+(function () {
+ var mock = function(arrayType) {
+ var handler = {
+ construct: function(target, args) {
+ var obj = new (Function.prototype.bind.apply(arrayType, [null].concat(args)));
+ return new Proxy(obj, {
+ get: function(x, prop) {
+ if (typeof x[prop] == "function")
+ return x[prop].bind(obj)
+ return x[prop];
+ },
+ });
+ },
+ };
+ return new Proxy(arrayType, handler);
+ }
+
+ Float32Array = mock(Float32Array);
+ Float64Array = mock(Float64Array);
+})();
+
+// Mock Worker.
+(function () {
+ var index = 0;
+ // TODO(machenbach): Randomize this for each test case, but keep stable
+ // during comparison. Also data and random above.
+ var workerMessages = [
+ undefined, 0, -1, "", "foo", 42, [], {}, [0], {"x": 0}
+ ];
+ Worker = function(code){
+ try {
+ print(prettyPrinted(eval(code)));
+ } catch(e) {
+ print(prettyPrinted(e));
+ }
+ this.getMessage = function(){
+ index = (index + 1) % 10;
+ return workerMessages[index];
+ }
+ this.postMessage = function(msg){
+ print(prettyPrinted(msg));
+ }
+ };
+})();
diff --git a/src/v8/tools/clusterfuzz/v8_mock_archs.js b/src/v8/tools/clusterfuzz/v8_mock_archs.js
new file mode 100644
index 0000000..acbaef8
--- /dev/null
+++ b/src/v8/tools/clusterfuzz/v8_mock_archs.js
@@ -0,0 +1,65 @@
+// Copyright 2017 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This is intended for permanent JS behavior changes for mocking out
+// non-deterministic behavior. For temporary suppressions, please refer to
+// v8_suppressions.js.
+// This mocks only architecture specific differences. Refer to v8_mocks.js
+// for the general case.
+// This file is loaded before each correctness test cases and won't get
+// minimized.
+
+// Mock maximum typed-array length and limit to 1MiB.
+(function () {
+ var mock = function(arrayType) {
+ var handler = {
+ construct: function(target, args) {
+ for (let i = 0; i < args.length; i++) {
+ if (typeof args[i] != "object") {
+ args[i] = Math.min(1048576, args[i]);
+ }
+ }
+ return new (
+ Function.prototype.bind.apply(arrayType, [null].concat(args)));
+ },
+ };
+ return new Proxy(arrayType, handler);
+ }
+
+ ArrayBuffer = mock(ArrayBuffer);
+ Int8Array = mock(Int8Array);
+ Uint8Array = mock(Uint8Array);
+ Uint8ClampedArray = mock(Uint8ClampedArray);
+ Int16Array = mock(Int16Array);
+ Uint16Array = mock(Uint16Array);
+ Int32Array = mock(Int32Array);
+ Uint32Array = mock(Uint32Array);
+ BigInt64Array = mock(BigInt64Array);
+ BigUint64Array = mock(BigUint64Array);
+ Float32Array = mock(Float32Array);
+ Float64Array = mock(Float64Array);
+})();
+
+// Mock typed array set function and limit maximum offset to 1MiB.
+(function () {
+ var typedArrayTypes = [
+ Int8Array,
+ Uint8Array,
+ Uint8ClampedArray,
+ Int16Array,
+ Uint16Array,
+ Int32Array,
+ Uint32Array,
+ BigInt64Array,
+ BigUint64Array,
+ Float32Array,
+ Float64Array,
+ ];
+ for (let typedArrayType of typedArrayTypes) {
+ let set = typedArrayType.prototype.set
+ typedArrayType.prototype.set = function(array, offset) {
+ set.apply(this, [array, offset > 1048576 ? 1048576 : offset])
+ };
+ }
+})();
diff --git a/src/v8/tools/clusterfuzz/v8_sanity_checks.js b/src/v8/tools/clusterfuzz/v8_sanity_checks.js
new file mode 100644
index 0000000..f2cb893
--- /dev/null
+++ b/src/v8/tools/clusterfuzz/v8_sanity_checks.js
@@ -0,0 +1,32 @@
+// Copyright 2019 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file is executed separately before the correctness test case. Add here
+// checking of global properties that should never differ in any configuration.
+// A difference found in the prints below will prevent any further correctness
+// comparison for the selected configurations to avoid flooding bugs.
+
+print("https://crbug.com/932656");
+print(Object.getOwnPropertyNames(this));
+
+print("https://crbug.com/935800");
+(function () {
+ function foo() {
+ "use asm";
+ function baz() {}
+ return {bar: baz};
+ }
+ print(Object.getOwnPropertyNames(foo().bar));
+})();
+
+print("https://crbug.com/985154");
+(function () {
+ "use strict";
+ function foo() {
+ "use asm";
+ function baz() {}
+ return {bar: baz};
+ }
+ print(Object.getOwnPropertyNames(foo().bar));
+})();
diff --git a/src/v8/tools/clusterfuzz/v8_suppressions.js b/src/v8/tools/clusterfuzz/v8_suppressions.js
new file mode 100644
index 0000000..011e727
--- /dev/null
+++ b/src/v8/tools/clusterfuzz/v8_suppressions.js
@@ -0,0 +1,33 @@
+// Copyright 2016 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file is loaded before each correctness test case and after v8_mock.js.
+// You can temporarily change JS behavior here to silence known problems.
+// Please refer to a bug in a comment and remove the suppression once the
+// problem is fixed.
+
+// Suppress http://crbug.com/662429
+(function () {
+ var oldMathPow = Math.pow
+ Math.pow = function(a, b){
+ if (b < 0) {
+ return 0.000017;
+ } else {
+ return oldMathPow(a, b);
+ }
+ }
+})();
+
+// Suppress http://crbug.com/693426
+(function () {
+ var oldMathPow = Math.pow
+ Math.pow = function(a, b){
+ var s = "" + oldMathPow(a, b)
+ // Low tech precision mock. Limit digits in string representation.
+ // The phrases Infinity and NaN don't match the split("e").
+ s = s.split("e");
+ s[0] = s[0].substr(0, 17);
+ return parseFloat(s.join("e"));
+ }
+})();
diff --git a/src/v8/tools/clusterfuzz/v8_suppressions.py b/src/v8/tools/clusterfuzz/v8_suppressions.py
new file mode 100644
index 0000000..04f67b2
--- /dev/null
+++ b/src/v8/tools/clusterfuzz/v8_suppressions.py
@@ -0,0 +1,316 @@
+# Copyright 2016 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.
+
+"""
+Suppressions for V8 correctness fuzzer failures.
+
+We support three types of suppressions:
+1. Ignore test case by pattern.
+Map a regular expression to a bug entry. A new failure will be reported
+when the pattern matches a JS test case.
+Subsequent matches will be recoreded under the first failure.
+
+2. Ignore test run by output pattern:
+Map a regular expression to a bug entry. A new failure will be reported
+when the pattern matches the output of a particular run.
+Subsequent matches will be recoreded under the first failure.
+
+3. Relax line-to-line comparisons with expressions of lines to ignore and
+lines to be normalized (i.e. ignore only portions of lines).
+These are not tied to bugs, be careful to not silently switch off this tool!
+
+Alternatively, think about adding a behavior change to v8_suppressions.js
+to silence a particular class of problems.
+"""
+
+import itertools
+import re
+
+# Max line length for regular experessions checking for lines to ignore.
+MAX_LINE_LENGTH = 512
+
+# For ignoring lines before carets and to ignore caret positions.
+CARET_RE = re.compile(r'^\s*\^\s*$')
+
+# Ignore by original source files. Map from bug->list of relative file paths in
+# V8, e.g. '/v8/test/mjsunit/d8-performance-now.js' including /v8/. A test will
+# be suppressed if one of the files below was used to mutate the test.
+IGNORE_SOURCES = {
+ # This contains a usage of f.arguments that often fires.
+ 'crbug.com/662424': [
+ '/v8/test/mjsunit/bugs/bug-222.js',
+ '/v8/test/mjsunit/bugs/bug-941049.js',
+ '/v8/test/mjsunit/regress/regress-crbug-668795.js',
+ '/v8/test/mjsunit/regress/regress-1079.js',
+ '/v8/test/mjsunit/regress/regress-2989.js',
+ ],
+
+ 'crbug.com/688159': [
+ '/v8/test/mjsunit/es7/exponentiation-operator.js',
+ ],
+
+ # TODO(machenbach): Implement blacklisting files for particular configs only,
+ # here ignition_eager.
+ 'crbug.com/691589': [
+ '/v8/test/mjsunit/regress/regress-1200351.js',
+ ],
+
+ 'crbug.com/691587': [
+ '/v8/test/mjsunit/asm/regress-674089.js',
+ ],
+
+ 'crbug.com/774805': [
+ '/v8/test/mjsunit/console.js',
+ ],
+}
+
+# Ignore by test case pattern. Map from config->bug->regexp. Config '' is used
+# to match all configurations. Otherwise use either a compiler configuration,
+# e.g. ignition or validate_asm or an architecture, e.g. x64 or ia32.
+# Bug is preferred to be a crbug.com/XYZ, but can be any short distinguishable
+# label.
+# Regular expressions are assumed to be compiled. We use regexp.search.
+IGNORE_TEST_CASES = {
+}
+
+# Ignore by output pattern. Map from config->bug->regexp. See IGNORE_TEST_CASES
+# on how to specify config keys.
+# Bug is preferred to be a crbug.com/XYZ, but can be any short distinguishable
+# label.
+# Regular expressions are assumed to be compiled. We use regexp.search.
+IGNORE_OUTPUT = {
+ '': {
+ 'crbug.com/664068':
+ re.compile(r'RangeError(?!: byte length)', re.S),
+ 'crbug.com/667678':
+ re.compile(r'\[native code\]', re.S),
+ 'crbug.com/689877':
+ re.compile(r'^.*SyntaxError: .*Stack overflow$', re.M),
+ },
+}
+
+# Lines matching any of the following regular expressions will be ignored
+# if appearing on both sides. The capturing groups need to match exactly.
+# Use uncompiled regular expressions - they'll be compiled later.
+ALLOWED_LINE_DIFFS = [
+ # Ignore caret position in stack traces.
+ r'^\s*\^\s*$',
+
+ # Ignore some stack trace headers as messages might not match.
+ r'^(.*)TypeError: .* is not a function$',
+ r'^(.*)TypeError: .* is not a constructor$',
+ r'^(.*)TypeError: (.*) is not .*$',
+ r'^(.*)ReferenceError: .* is not defined$',
+ r'^(.*):\d+: ReferenceError: .* is not defined$',
+
+ # These are rarely needed. It includes some cases above.
+ r'^\w*Error: .* is not .*$',
+ r'^(.*) \w*Error: .* is not .*$',
+ r'^(.*):\d+: \w*Error: .* is not .*$',
+
+ # Some test cases just print the message.
+ r'^.* is not a function(.*)$',
+ r'^(.*) is not a .*$',
+
+ # crbug.com/680064. This subsumes one of the above expressions.
+ r'^(.*)TypeError: .* function$',
+
+ # crbug.com/664068
+ r'^(.*)(?:Array buffer allocation failed|Invalid array buffer length)(.*)$',
+]
+
+# Lines matching any of the following regular expressions will be ignored.
+# Use uncompiled regular expressions - they'll be compiled later.
+IGNORE_LINES = [
+ r'^Warning: unknown flag .*$',
+ r'^Warning: .+ is deprecated.*$',
+ r'^Try --help for options$',
+
+ # crbug.com/705962
+ r'^\s\[0x[0-9a-f]+\]$',
+]
+
+
+###############################################################################
+# Implementation - you should not need to change anything below this point.
+
+# Compile regular expressions.
+ALLOWED_LINE_DIFFS = [re.compile(exp) for exp in ALLOWED_LINE_DIFFS]
+IGNORE_LINES = [re.compile(exp) for exp in IGNORE_LINES]
+
+ORIGINAL_SOURCE_PREFIX = 'v8-foozzie source: '
+
+def line_pairs(lines):
+ return itertools.izip_longest(
+ lines, itertools.islice(lines, 1, None), fillvalue=None)
+
+
+def caret_match(line1, line2):
+ if (not line1 or
+ not line2 or
+ len(line1) > MAX_LINE_LENGTH or
+ len(line2) > MAX_LINE_LENGTH):
+ return False
+ return bool(CARET_RE.match(line1) and CARET_RE.match(line2))
+
+
+def short_line_output(line):
+ if len(line) <= MAX_LINE_LENGTH:
+ # Avoid copying.
+ return line
+ return line[0:MAX_LINE_LENGTH] + '...'
+
+
+def ignore_by_regexp(line1, line2, allowed):
+ if len(line1) > MAX_LINE_LENGTH or len(line2) > MAX_LINE_LENGTH:
+ return False
+ for exp in allowed:
+ match1 = exp.match(line1)
+ match2 = exp.match(line2)
+ if match1 and match2:
+ # If there are groups in the regexp, ensure the groups matched the same
+ # things.
+ if match1.groups() == match2.groups(): # tuple comparison
+ return True
+ return False
+
+
+def diff_output(output1, output2, allowed, ignore1, ignore2):
+ """Returns a tuple (difference, source).
+
+ The difference is None if there's no difference, otherwise a string
+ with a readable diff.
+
+ The source is the last source output within the test case, or None if no
+ such output existed.
+ """
+ def useful_line(ignore):
+ def fun(line):
+ return all(not e.match(line) for e in ignore)
+ return fun
+
+ lines1 = filter(useful_line(ignore1), output1)
+ lines2 = filter(useful_line(ignore2), output2)
+
+ # This keeps track where we are in the original source file of the fuzz
+ # test case.
+ source = None
+
+ for ((line1, lookahead1), (line2, lookahead2)) in itertools.izip_longest(
+ line_pairs(lines1), line_pairs(lines2), fillvalue=(None, None)):
+
+ # Only one of the two iterators should run out.
+ assert not (line1 is None and line2 is None)
+
+ # One iterator ends earlier.
+ if line1 is None:
+ return '+ %s' % short_line_output(line2), source
+ if line2 is None:
+ return '- %s' % short_line_output(line1), source
+
+ # If lines are equal, no further checks are necessary.
+ if line1 == line2:
+ # Instrumented original-source-file output must be equal in both
+ # versions. It only makes sense to update it here when both lines
+ # are equal.
+ if line1.startswith(ORIGINAL_SOURCE_PREFIX):
+ source = line1[len(ORIGINAL_SOURCE_PREFIX):]
+ continue
+
+ # Look ahead. If next line is a caret, ignore this line.
+ if caret_match(lookahead1, lookahead2):
+ continue
+
+ # Check if a regexp allows these lines to be different.
+ if ignore_by_regexp(line1, line2, allowed):
+ continue
+
+ # Lines are different.
+ return (
+ '- %s\n+ %s' % (short_line_output(line1), short_line_output(line2)),
+ source,
+ )
+
+ # No difference found.
+ return None, source
+
+
+def get_suppression(arch1, config1, arch2, config2):
+ return V8Suppression(arch1, config1, arch2, config2)
+
+
+class Suppression(object):
+ def diff(self, output1, output2):
+ return None
+
+ def ignore_by_metadata(self, metadata):
+ return None
+
+ def ignore_by_content(self, testcase):
+ return None
+
+ def ignore_by_output1(self, output):
+ return None
+
+ def ignore_by_output2(self, output):
+ return None
+
+
+class V8Suppression(Suppression):
+ def __init__(self, arch1, config1, arch2, config2):
+ self.arch1 = arch1
+ self.config1 = config1
+ self.arch2 = arch2
+ self.config2 = config2
+
+ def diff(self, output1, output2):
+ return diff_output(
+ output1.splitlines(),
+ output2.splitlines(),
+ ALLOWED_LINE_DIFFS,
+ IGNORE_LINES,
+ IGNORE_LINES,
+ )
+
+ def ignore_by_content(self, testcase):
+ # Strip off test case preamble.
+ try:
+ lines = testcase.splitlines()
+ lines = lines[lines.index(
+ 'print("js-mutation: start generated test case");'):]
+ content = '\n'.join(lines)
+ except ValueError:
+ # Search the whole test case if preamble can't be found. E.g. older
+ # already minimized test cases might have dropped the delimiter line.
+ content = testcase
+ for key in ['', self.arch1, self.arch2, self.config1, self.config2]:
+ for bug, exp in IGNORE_TEST_CASES.get(key, {}).iteritems():
+ if exp.search(content):
+ return bug
+ return None
+
+ def ignore_by_metadata(self, metadata):
+ for bug, sources in IGNORE_SOURCES.iteritems():
+ for source in sources:
+ if source in metadata['sources']:
+ return bug
+ return None
+
+ def ignore_by_output1(self, output):
+ return self.ignore_by_output(output, self.arch1, self.config1)
+
+ def ignore_by_output2(self, output):
+ return self.ignore_by_output(output, self.arch2, self.config2)
+
+ def ignore_by_output(self, output, arch, config):
+ def check(mapping):
+ for bug, exp in mapping.iteritems():
+ if exp.search(output):
+ return bug
+ return None
+ for key in ['', arch, config]:
+ bug = check(IGNORE_OUTPUT.get(key, {}))
+ if bug:
+ return bug
+ return None
diff --git a/src/v8/tools/collect_deprecation_stats.sh b/src/v8/tools/collect_deprecation_stats.sh
new file mode 100755
index 0000000..aa3f413
--- /dev/null
+++ b/src/v8/tools/collect_deprecation_stats.sh
@@ -0,0 +1,35 @@
+#!/bin/bash
+
+# Collect the number of [[deprecated]] calls detected when compiling V8.
+# Requires "v8_deprecate_get_isolate = true" to be useful.
+
+set -e
+
+if [ -z "$1" ]; then
+ (>&2 echo "Usage: collect_deprecation_stats.sh [<outdir>|<log>]")
+ exit 1
+fi
+
+if [ -d "$1" ]; then
+ OUTDIR=$1
+ FULL_LOG=/tmp/get_isolate_deprecation.log
+ gn clean "$OUTDIR"
+ autoninja -C "$OUTDIR" > $FULL_LOG
+else
+ FULL_LOG=$1
+fi
+
+FILTERED_LOG=/tmp/filtered_isolate_deprecation.log
+UNIQUE_WARNINGS_LOG=/tmp/unique_warnings.log
+
+grep "warning:" "$FULL_LOG" | sed $'
+s|^\.\./\.\./||;
+s/: warning: \'/: /;
+
+# strip everything after deprecated function name (including template param).
+s/\(<.*>\)\\?\'.*//' > $FILTERED_LOG
+
+sort -u $FILTERED_LOG > $UNIQUE_WARNINGS_LOG
+
+echo "Total deprecated calls: $(wc -l < $UNIQUE_WARNINGS_LOG)"
+cut -f2 -d' ' $UNIQUE_WARNINGS_LOG | sort | uniq -c
diff --git a/src/v8/tools/concatenate-files.py b/src/v8/tools/concatenate-files.py
index 8a9012c..a5dbe45 100644
--- a/src/v8/tools/concatenate-files.py
+++ b/src/v8/tools/concatenate-files.py
@@ -35,6 +35,9 @@
# on all supported build platforms, but Python is, and hence this provides
# us with an easy and uniform way of doing this on all platforms.
+# for py2/py3 compatibility
+from __future__ import print_function
+
import optparse
@@ -49,7 +52,7 @@
True, if the operation was successful.
"""
if len(filenames) < 2:
- print "An error occurred generating %s:\nNothing to do." % filenames[-1]
+ print("An error occurred generating %s:\nNothing to do." % filenames[-1])
return False
try:
@@ -59,7 +62,7 @@
target.write(current.read())
return True
except IOError as e:
- print "An error occurred when writing %s:\n%s" % (filenames[-1], e)
+ print("An error occurred when writing %s:\n%s" % (filenames[-1], e))
return False
diff --git a/src/v8/tools/csvparser.js b/src/v8/tools/csvparser.js
index f0f8680..bd106a7 100644
--- a/src/v8/tools/csvparser.js
+++ b/src/v8/tools/csvparser.js
@@ -49,6 +49,9 @@
if (escapeIdentifier == 'n') {
result += '\n';
nextPos = pos;
+ } else if (escapeIdentifier == '\\') {
+ result += '\\';
+ nextPos = pos;
} else {
if (escapeIdentifier == 'x') {
// \x00 ascii range escapes consume 2 chars.
diff --git a/src/v8/tools/deprecation_stats.py b/src/v8/tools/deprecation_stats.py
new file mode 100755
index 0000000..628eebc
--- /dev/null
+++ b/src/v8/tools/deprecation_stats.py
@@ -0,0 +1,108 @@
+#!/usr/bin/env python
+# 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.
+
+# for py2/py3 compatibility
+from __future__ import print_function
+
+import argparse
+from datetime import datetime
+import re
+import subprocess
+import sys
+
+RE_GITHASH = re.compile(r"^[0-9a-f]{40}")
+RE_AUTHOR_TIME = re.compile(r"^author-time (\d+)$")
+RE_FILENAME = re.compile(r"^filename (.+)$")
+
+def GetBlame(file_path):
+ result = subprocess.check_output(
+ ['git', 'blame', '-t', '--line-porcelain', file_path])
+ line_iter = iter(result.splitlines())
+ blame_list = list()
+ current_blame = None
+ while True:
+ line = next(line_iter, None)
+ if line is None:
+ break
+ if RE_GITHASH.match(line):
+ if current_blame is not None:
+ blame_list.append(current_blame)
+ current_blame = {'time': 0, 'filename': None, 'content': None}
+ continue
+ match = RE_AUTHOR_TIME.match(line)
+ if match:
+ current_blame['time'] = datetime.fromtimestamp(int(match.groups()[0]))
+ continue
+ match = RE_FILENAME.match(line)
+ if match:
+ current_blame['filename'] = match.groups()[0]
+ current_blame['content'] = next(line_iter).strip()
+ continue
+ blame_list.append(current_blame)
+ return blame_list
+
+RE_MACRO_END = re.compile(r"\);");
+RE_DEPRECATE_MACRO = re.compile(r"\(.*?,(.*)\);", re.MULTILINE)
+
+def FilterAndPrint(blame_list, macro, before):
+ index = 0
+ re_macro = re.compile(macro)
+ deprecated = list()
+ while index < len(blame_list):
+ blame = blame_list[index]
+ match = re_macro.search(blame['content'])
+ if match and blame['time'] < before:
+ line = blame['content']
+ time = blame['time']
+ pos = match.end()
+ start = -1
+ parens = 0
+ quotes = 0
+ while True:
+ if pos >= len(line):
+ # extend to next line
+ index = index + 1
+ blame = blame_list[index]
+ if line.endswith(','):
+ # add whitespace when breaking line due to comma
+ line = line + ' '
+ line = line + blame['content']
+ if line[pos] == '(':
+ parens = parens + 1
+ elif line[pos] == ')':
+ parens = parens - 1
+ if parens == 0:
+ break
+ elif line[pos] == '"':
+ quotes = quotes + 1
+ elif line[pos] == ',' and quotes % 2 == 0 and start == -1:
+ start = pos + 1
+ pos = pos + 1
+ deprecated.append([index + 1, time, line[start:pos].strip()])
+ index = index + 1
+ print("Marked as " + macro + ": " + str(len(deprecated)))
+ for linenumber, time, content in deprecated:
+ print(str(linenumber).rjust(8) + " : " + str(time) + " : " + content)
+ return len(deprecated)
+
+def ParseOptions(args):
+ parser = argparse.ArgumentParser(description="Collect deprecation statistics")
+ parser.add_argument("file_path", help="Path to v8.h")
+ parser.add_argument("--before", help="Filter by date")
+ options = parser.parse_args(args)
+ if options.before:
+ options.before = datetime.strptime(options.before, '%Y-%m-%d')
+ else:
+ options.before = datetime.now()
+ return options
+
+def Main(args):
+ options = ParseOptions(args)
+ blame_list = GetBlame(options.file_path)
+ FilterAndPrint(blame_list, "V8_DEPRECATE_SOON", options.before)
+ FilterAndPrint(blame_list, "V8_DEPRECATED", options.before)
+
+if __name__ == "__main__":
+ Main(sys.argv[1:])
diff --git a/src/v8/tools/dev/gen-tags.py b/src/v8/tools/dev/gen-tags.py
index 256f65a..a478ee3 100755
--- a/src/v8/tools/dev/gen-tags.py
+++ b/src/v8/tools/dev/gen-tags.py
@@ -15,6 +15,10 @@
If no <arch> is given, it generates tags file for all arches:
$ tools/dev/gen-tags.py
"""
+
+# for py2/py3 compatibility
+from __future__ import print_function
+
import os
import subprocess
import sys
diff --git a/src/v8/tools/dev/gm.py b/src/v8/tools/dev/gm.py
index c3dab0a..0e01f4f 100755
--- a/src/v8/tools/dev/gm.py
+++ b/src/v8/tools/dev/gm.py
@@ -10,31 +10,32 @@
Expects to be run from the root of a V8 checkout.
Usage:
- gm.py [<arch>].[<mode>].[<target>] [testname...]
+ gm.py [<arch>].[<mode>[-<suffix>]].[<target>] [testname...]
All arguments are optional. Most combinations should work, e.g.:
- gm.py ia32.debug x64.release d8
+ gm.py ia32.debug x64.release x64.release-my-custom-opts d8
+ gm.py android_arm.release.check
gm.py x64 mjsunit/foo cctest/test-bar/*
"""
# See HELP below for additional documentation.
from __future__ import print_function
import errno
-import multiprocessing
import os
-import pty
import re
import subprocess
import sys
-BUILD_OPTS_DEFAULT = ""
-BUILD_OPTS_GOMA = "-j1000 -l%d" % (multiprocessing.cpu_count() + 2)
+USE_PTY = "linux" in sys.platform
+if USE_PTY:
+ import pty
+
BUILD_TARGETS_TEST = ["d8", "cctest", "unittests"]
BUILD_TARGETS_ALL = ["all"]
# All arches that this script understands.
ARCHES = ["ia32", "x64", "arm", "arm64", "mipsel", "mips64el", "ppc", "ppc64",
- "s390", "s390x"]
+ "s390", "s390x", "android_arm", "android_arm64"]
# Arches that get built/run when you don't specify any.
DEFAULT_ARCHES = ["ia32", "x64", "arm", "arm64"]
# Modes that this script understands.
@@ -42,8 +43,8 @@
# Modes that get built/run when you don't specify any.
DEFAULT_MODES = ["release", "debug"]
# Build targets that can be manually specified.
-TARGETS = ["d8", "cctest", "unittests", "v8_fuzzers", "mkgrokdump",
- "generate-bytecode-expectations", "inspector-test"]
+TARGETS = ["d8", "cctest", "unittests", "v8_fuzzers", "wasm_api_tests", "wee8",
+ "mkgrokdump", "generate-bytecode-expectations", "inspector-test"]
# Build targets that get built when you don't specify any (and specified tests
# don't imply any other targets).
DEFAULT_TARGETS = ["d8"]
@@ -63,13 +64,14 @@
HELP = """<arch> can be any of: %(arches)s
<mode> can be any of: %(modes)s
<target> can be any of:
- - cctest, d8, unittests, v8_fuzzers (build respective binary)
+ - %(targets)s (build respective binary)
- all (build all binaries)
- tests (build test binaries)
- check (build test binaries, run most tests)
- checkall (build all binaries, run more tests)
""" % {"arches": " ".join(ARCHES),
- "modes": " ".join(MODES)}
+ "modes": " ".join(MODES),
+ "targets": ", ".join(TARGETS)}
TESTSUITES_TARGETS = {"benchmarks": "d8",
"cctest": "cctest",
@@ -83,6 +85,7 @@
"preparser": "d8",
"test262": "d8",
"unittests": "unittests",
+ "wasm-api-tests": "wasm_api_tests",
"webkit": "d8"}
OUTDIR = "out"
@@ -122,6 +125,7 @@
use_goma = {GOMA}
goma_dir = \"{GOMA_DIR}\"
v8_enable_backtrace = true
+v8_enable_fast_mksnapshot = true
v8_enable_slow_dchecks = true
v8_optimized_debug = false
""".replace("{GOMA}", USE_GOMA).replace("{GOMA_DIR}", str(GOMADIR))
@@ -134,6 +138,7 @@
use_goma = {GOMA}
goma_dir = \"{GOMA_DIR}\"
v8_enable_backtrace = true
+v8_enable_fast_mksnapshot = true
v8_enable_verify_heap = true
v8_optimized_debug = true
""".replace("{GOMA}", USE_GOMA).replace("{GOMA_DIR}", str(GOMADIR))
@@ -203,6 +208,16 @@
subdir = "%s.%s" % (arch, mode)
return os.path.join(OUTDIR, subdir)
+def PrepareMksnapshotCmdline(orig_cmdline, path):
+ result = "gdb --args %s/mksnapshot " % path
+ for w in orig_cmdline.split(" "):
+ if w.startswith("gen/") or w.startswith("snapshot_blob"):
+ result += ("%(path)s%(sep)s%(arg)s " %
+ {"path": path, "sep": os.sep, "arg": w})
+ else:
+ result += "%s " % w
+ return result
+
class Config(object):
def __init__(self, arch, mode, targets, tests=[]):
self.arch = arch
@@ -215,57 +230,64 @@
self.tests.update(tests)
def GetTargetCpu(self):
+ if self.arch == "android_arm": return "target_cpu = \"arm\""
+ if self.arch == "android_arm64": return "target_cpu = \"arm64\""
cpu = "x86"
if "64" in self.arch or self.arch == "s390x":
cpu = "x64"
return "target_cpu = \"%s\"" % cpu
def GetV8TargetCpu(self):
+ if self.arch == "android_arm": return "\nv8_target_cpu = \"arm\""
+ if self.arch == "android_arm64": return "\nv8_target_cpu = \"arm64\""
if self.arch in ("arm", "arm64", "mipsel", "mips64el", "ppc", "ppc64",
"s390", "s390x"):
return "\nv8_target_cpu = \"%s\"" % self.arch
return ""
- def GetGnArgs(self):
- template = ARGS_TEMPLATES[self.mode]
- arch_specific = self.GetTargetCpu() + self.GetV8TargetCpu()
- return template % arch_specific
+ def GetTargetOS(self):
+ if self.arch in ("android_arm", "android_arm64"):
+ return "\ntarget_os = \"android\""
+ return ""
- def WantsGoma(self):
- output = _CallWithOutputNoTerminal(
- "gn args --short --list=use_goma %s" % (GetPath(self.arch, self.mode)))
- return "true" in output
+ def GetGnArgs(self):
+ # Use only substring before first '-' as the actual mode
+ mode = re.match("([^-]+)", self.mode).group(1)
+ template = ARGS_TEMPLATES[mode]
+ arch_specific = (self.GetTargetCpu() + self.GetV8TargetCpu() +
+ self.GetTargetOS())
+ return template % arch_specific
def Build(self):
path = GetPath(self.arch, self.mode)
args_gn = os.path.join(path, "args.gn")
+ build_ninja = os.path.join(path, "build.ninja")
if not os.path.exists(path):
print("# mkdir -p %s" % path)
os.makedirs(path)
if not os.path.exists(args_gn):
_Write(args_gn, self.GetGnArgs())
+ if not os.path.exists(build_ninja):
code = _Call("gn gen %s" % path)
if code != 0: return code
targets = " ".join(self.targets)
- build_opts = BUILD_OPTS_GOMA if self.WantsGoma() else BUILD_OPTS_DEFAULT
# The implementation of mksnapshot failure detection relies on
# the "pty" module and GDB presence, so skip it on non-Linux.
- if "linux" not in sys.platform:
- return _Call("ninja -C %s %s %s" % (path, build_opts, targets))
+ if not USE_PTY:
+ return _Call("autoninja -C %s %s" % (path, targets))
- return_code, output = _CallWithOutput("ninja -C %s %s %s" %
- (path, build_opts, targets))
- if return_code != 0 and "FAILED: gen/snapshot.cc" in output:
+ return_code, output = _CallWithOutput("autoninja -C %s %s" %
+ (path, targets))
+ if return_code != 0 and "FAILED:" in output and "snapshot_blob" in output:
csa_trap = re.compile("Specify option( --csa-trap-on-node=[^ ]*)")
match = csa_trap.search(output)
extra_opt = match.group(1) if match else ""
+ cmdline = re.compile("python ../../tools/run.py ./mksnapshot (.*)")
+ orig_cmdline = cmdline.search(output).group(1).strip()
+ cmdline = PrepareMksnapshotCmdline(orig_cmdline, path) + extra_opt
_Notify("V8 build requires your attention",
"Detected mksnapshot failure, re-running in GDB...")
- _Call("gdb -args %(path)s/mksnapshot "
- "--startup_src %(path)s/gen/snapshot.cc "
- "--random-seed 314159265 "
- "--startup-blob %(path)s/snapshot_blob.bin"
- "%(extra)s"% {"path": path, "extra": extra_opt})
+ _Call(cmdline)
return return_code
def RunTests(self):
@@ -274,8 +296,9 @@
tests = ""
else:
tests = " ".join(self.tests)
- return _Call("tools/run-tests.py --arch=%s --mode=%s %s" %
- (self.arch, self.mode, tests))
+ return _Call('"%s" ' % sys.executable +
+ os.path.join("tools", "run-tests.py") +
+ " --outdir=%s %s" % (GetPath(self.arch, self.mode), tests))
def GetTestBinary(argstring):
for suite in TESTSUITES_TARGETS:
@@ -343,6 +366,8 @@
targets.append(word)
elif word in ACTIONS:
actions.append(word)
+ elif any(map(lambda x: word.startswith(x + "-"), MODES)):
+ modes.append(word)
else:
print("Didn't understand: %s" % word)
sys.exit(1)
diff --git a/src/v8/tools/dev/v8gen.py b/src/v8/tools/dev/v8gen.py
index b8a34e2..0b6e1d1 100755
--- a/src/v8/tools/dev/v8gen.py
+++ b/src/v8/tools/dev/v8gen.py
@@ -38,6 +38,9 @@
-------------------------------------------------------------------------------
"""
+# for py2/py3 compatibility
+from __future__ import print_function
+
import argparse
import os
import re
@@ -144,8 +147,8 @@
# Check for builder/config in mb config.
if self._options.builder not in self._mbw.masters[self._options.master]:
- print '%s does not exist in %s for %s' % (
- self._options.builder, CONFIG, self._options.master)
+ print('%s does not exist in %s for %s' % (
+ self._options.builder, CONFIG, self._options.master))
return 1
# TODO(machenbach): Check if the requested configurations has switched to
@@ -186,19 +189,19 @@
return 0
def cmd_list(self):
- print '\n'.join(sorted(self._mbw.masters[self._options.master]))
+ print('\n'.join(sorted(self._mbw.masters[self._options.master])))
return 0
def verbose_print_1(self, text):
if self._options.verbosity >= 1:
- print '#' * 80
- print text
+ print('#' * 80)
+ print(text)
def verbose_print_2(self, text):
if self._options.verbosity >= 2:
indent = ' ' * 2
for l in text.splitlines():
- print indent + l
+ print(indent + l)
def _call_cmd(self, args):
self.verbose_print_1(' '.join(args))
@@ -290,9 +293,9 @@
self._mbw.ReadConfigFile()
if not self._options.master in self._mbw.masters:
- print '%s not found in %s\n' % (self._options.master, CONFIG)
- print 'Choose one of:\n%s\n' % (
- '\n'.join(sorted(self._mbw.masters.keys())))
+ print('%s not found in %s\n' % (self._options.master, CONFIG))
+ print('Choose one of:\n%s\n' % (
+ '\n'.join(sorted(self._mbw.masters.keys()))))
return 1
return self._options.func()
diff --git a/src/v8/tools/dump-cpp.py b/src/v8/tools/dump-cpp.py
index 1fc15d9..31dc053 100644
--- a/src/v8/tools/dump-cpp.py
+++ b/src/v8/tools/dump-cpp.py
@@ -6,6 +6,9 @@
# This script executes dumpcpp.js, collects all dumped C++ symbols,
# and merges them back into v8 log.
+# for py2/py3 compatibility
+from __future__ import print_function
+
import os
import platform
import re
@@ -44,10 +47,10 @@
if d8_line:
d8_exec = d8_line.group(1)
if not is_file_executable(d8_exec):
- print 'd8 binary path found in {} is not executable.'.format(log_file)
+ print('d8 binary path found in {} is not executable.'.format(log_file))
sys.exit(-1)
else:
- print 'No d8 binary path found in {}.'.format(log_file)
+ print('No d8 binary path found in {}.'.format(log_file))
sys.exit(-1)
args = [d8_exec] + JS_FILES + ['--'] + args
@@ -57,9 +60,9 @@
stdin=f)
out, err = sp.communicate()
if debug:
- print err
+ print(err)
if sp.returncode != 0:
- print out
+ print(out)
exit(-1)
if on_windows and out:
diff --git a/src/v8/tools/dumpcpp-driver.js b/src/v8/tools/dumpcpp-driver.js
index 4452777..6073dea 100644
--- a/src/v8/tools/dumpcpp-driver.js
+++ b/src/v8/tools/dumpcpp-driver.js
@@ -39,7 +39,8 @@
}
var cppProcessor = new CppProcessor(
- new (entriesProviders[params.platform])(params.nm, params.targetRootFS),
+ new (entriesProviders[params.platform])(params.nm, params.targetRootFS,
+ params.apkEmbeddedLibrary),
params.timedRange, params.pairwiseTimedRange);
cppProcessor.processLogFile(params.logFileName);
cppProcessor.dumpCppSymbols();
diff --git a/src/v8/tools/dumpcpp.js b/src/v8/tools/dumpcpp.js
index ca5ff67..58cb74c 100644
--- a/src/v8/tools/dumpcpp.js
+++ b/src/v8/tools/dumpcpp.js
@@ -4,7 +4,7 @@
function CppProcessor(cppEntriesProvider, timedRange, pairwiseTimedRange) {
LogReader.call(this, {
- 'shared-library': { parsers: [null, parseInt, parseInt, parseInt],
+ 'shared-library': { parsers: [parseString, parseInt, parseInt, parseInt],
processor: this.processSharedLibrary }
}, timedRange, pairwiseTimedRange);
diff --git a/src/v8/tools/eval_gc_nvp.py b/src/v8/tools/eval_gc_nvp.py
index 25afe8e..222ebef 100755
--- a/src/v8/tools/eval_gc_nvp.py
+++ b/src/v8/tools/eval_gc_nvp.py
@@ -7,10 +7,14 @@
"""This script is used to analyze GCTracer's NVP output."""
+# for py2/py3 compatibility
+from __future__ import print_function
+
+
from argparse import ArgumentParser
from copy import deepcopy
from gc_nvp_common import split_nvp
-from math import ceil,log
+from math import ceil, log
from sys import stdin
diff --git a/src/v8/tools/eval_gc_time.sh b/src/v8/tools/eval_gc_time.sh
index 9abc93a..f809c35 100755
--- a/src/v8/tools/eval_gc_time.sh
+++ b/src/v8/tools/eval_gc_time.sh
@@ -94,7 +94,6 @@
clear.slots_buffer \
clear.store_buffer \
clear.string_table \
- clear.weak_cells \
clear.weak_collections \
clear.weak_lists \
evacuate.candidates \
diff --git a/src/v8/tools/find-commit-for-patch.py b/src/v8/tools/find-commit-for-patch.py
index 657826c..cca1f40 100755
--- a/src/v8/tools/find-commit-for-patch.py
+++ b/src/v8/tools/find-commit-for-patch.py
@@ -3,6 +3,9 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+# for py2/py3 compatibility
+from __future__ import print_function
+
import argparse
import subprocess
import sys
diff --git a/src/v8/tools/find_depot_tools.py b/src/v8/tools/find_depot_tools.py
index 95ae9e8..db3ffa2 100644
--- a/src/v8/tools/find_depot_tools.py
+++ b/src/v8/tools/find_depot_tools.py
@@ -4,6 +4,9 @@
"""Small utility function to find depot_tools and add it to the python path.
"""
+# for py2/py3 compatibility
+from __future__ import print_function
+
import os
import sys
@@ -36,5 +39,5 @@
return i
previous_dir = root_dir
root_dir = os.path.dirname(root_dir)
- print >> sys.stderr, 'Failed to find depot_tools'
+ print('Failed to find depot_tools', file=sys.stderr)
return None
diff --git a/src/v8/tools/gc-nvp-to-csv.py b/src/v8/tools/gc-nvp-to-csv.py
index 26ed8e1..b3ad374 100755
--- a/src/v8/tools/gc-nvp-to-csv.py
+++ b/src/v8/tools/gc-nvp-to-csv.py
@@ -11,20 +11,25 @@
# Usage: gc-nvp-to-csv.py <GC-trace-filename>
#
+
+# for py2/py3 compatibility
+from __future__ import print_function
+
import sys
import gc_nvp_common
+
def process_trace(filename):
trace = gc_nvp_common.parse_gc_trace(filename)
if len(trace):
keys = trace[0].keys()
- print ', '.join(keys)
+ print(', '.join(keys))
for entry in trace:
- print ', '.join(map(lambda key: str(entry[key]), keys))
+ print(', '.join(map(lambda key: str(entry[key]), keys)))
if len(sys.argv) != 2:
- print "Usage: %s <GC-trace-filename>" % sys.argv[0]
+ print("Usage: %s <GC-trace-filename>" % sys.argv[0])
sys.exit(1)
process_trace(sys.argv[1])
diff --git a/src/v8/tools/gc-nvp-trace-processor.py b/src/v8/tools/gc-nvp-trace-processor.py
index 21526ae..75d50b1 100755
--- a/src/v8/tools/gc-nvp-trace-processor.py
+++ b/src/v8/tools/gc-nvp-trace-processor.py
@@ -37,10 +37,21 @@
#
+# for py2/py3 compatibility
from __future__ import with_statement
+from __future__ import print_function
+from functools import reduce
+
import sys, types, subprocess, math
import gc_nvp_common
+
+try:
+ long # Python 2
+except NameError:
+ long = int # Python 3
+
+
def flatten(l):
flat = []
for i in l: flat.extend(i)
@@ -62,7 +73,7 @@
self.title = title
self.axis = axis
self.props = keywords
- if type(field) is types.ListType:
+ if type(field) is list:
self.field = field
else:
self.field = [field]
@@ -135,7 +146,7 @@
def get_field(trace_line, field):
t = type(field)
- if t is types.StringType:
+ if t is bytes:
return trace_line[field]
elif t is types.FunctionType:
return field(trace_line)
@@ -177,7 +188,7 @@
outfilename = "%s_%d.png" % (prefix, len(charts))
charts.append(outfilename)
script = generate_script_and_datafile(plot, trace, '~datafile', outfilename)
- print 'Plotting %s...' % outfilename
+ print('Plotting %s...' % outfilename)
gnuplot(script)
return charts
@@ -350,10 +361,10 @@
out.write('<img src="%s">' % chart)
out.write('</body></html>')
- print "%s generated." % (filename + '.html')
+ print("%s generated." % (filename + '.html'))
if len(sys.argv) != 2:
- print "Usage: %s <GC-trace-filename>" % sys.argv[0]
+ print("Usage: %s <GC-trace-filename>" % sys.argv[0])
sys.exit(1)
process_trace(sys.argv[1])
diff --git a/src/v8/tools/gcmole/BUILD.gn b/src/v8/tools/gcmole/BUILD.gn
new file mode 100644
index 0000000..0434a64
--- /dev/null
+++ b/src/v8/tools/gcmole/BUILD.gn
@@ -0,0 +1,43 @@
+# 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.
+
+import("../../gni/v8.gni")
+
+group("v8_run_gcmole") {
+ testonly = true
+
+ data = [
+ "gccause.lua",
+ "gcmole.lua",
+ "gcmole-tools/",
+ "parallel.py",
+ "run-gcmole.py",
+
+ # The following contains all relevant source and build files.
+ "../../BUILD.gn",
+ "../../base/",
+ "../../include/",
+ "../../src/",
+ "../../test/cctest/",
+ "../../test/common/",
+ "../../testing/gtest/include/gtest/gtest_prod.h",
+ "../../third_party/googletest/src/googletest/include/gtest/gtest_prod.h",
+ "../../third_party/icu/source/",
+ "../../third_party/wasm-api/wasm.h",
+ "../../third_party/wasm-api/wasm.hh",
+ "../../third_party/inspector_protocol/",
+ "$target_gen_dir/../../",
+ "$target_gen_dir/../../torque-generated/",
+ ]
+
+ deps = [
+ "../../:run_torque",
+ ]
+
+ if (v8_gcmole) {
+ # This assumes gcmole tools have been fetched by a hook
+ # into v8/tools/gcmole/gcmole_tools.
+ data += [ "gcmole-tools/" ]
+ }
+}
diff --git a/src/v8/tools/gcmole/Makefile b/src/v8/tools/gcmole/Makefile
index ee43c00..e1bde68 100644
--- a/src/v8/tools/gcmole/Makefile
+++ b/src/v8/tools/gcmole/Makefile
@@ -27,16 +27,20 @@
# This is Makefile for clang plugin part of gcmole tool. See README.
-LLVM_INCLUDE:=$(LLVM_SRC_ROOT)/include
-CLANG_INCLUDE:=$(LLVM_SRC_ROOT)/tools/clang/include
+LLVM_SRC_INCLUDE:=$(LLVM_SRC_ROOT)/include
+LLVM_BUILD_INCLUDE:=$(BUILD_ROOT)/include
+CLANG_SRC_INCLUDE:=$(CLANG_SRC_ROOT)/include
+CLANG_BUILD_INCLUDE:=$(BUILD_ROOT)/tools/clang/include
libgcmole.so: gcmole.cc
- $(CXX) -I$(LLVM_INCLUDE) -I$(CLANG_INCLUDE) -I. -D_DEBUG \
+ $(CXX) -I$(LLVM_BUILD_INCLUDE) -I$(LLVM_SRC_INCLUDE) \
+ -I$(CLANG_BUILD_INCLUDE) -I$(CLANG_SRC_INCLUDE) -I. -D_DEBUG \
-D_GNU_SOURCE -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS \
-D__STDC_LIMIT_MACROS -O3 -fomit-frame-pointer -fno-exceptions \
-fno-rtti -fPIC -Woverloaded-virtual -Wcast-qual -fno-strict-aliasing \
-pedantic -Wno-long-long -Wall -W -Wno-unused-parameter \
- -Wwrite-strings -std=c++0x -shared -o libgcmole.so gcmole.cc
+ -Wwrite-strings -static-libstdc++ -std=c++0x -shared -o libgcmole.so \
+ gcmole.cc
clean:
$(RM) libgcmole.so
diff --git a/src/v8/tools/gcmole/README b/src/v8/tools/gcmole/README
index 37f8afb..578ea56 100644
--- a/src/v8/tools/gcmole/README
+++ b/src/v8/tools/gcmole/README
@@ -5,12 +5,12 @@
For example the following code is GC-unsafe:
-Handle<Object> Foo(); // Assume Foo can trigger a GC.
-void Bar(Object*, Object*);
+ Handle<Object> Foo(); // Assume Foo can trigger a GC.
+ void Bar(Object*, Object*);
-Handle<Object> baz;
-baz->Qux(*Foo()); // (a)
-Bar(*Foo(), *baz); // (b)
+ Handle<Object> baz;
+ baz->Qux(*Foo()); // (a)
+ Bar(*Foo(), *baz); // (b)
Both in cases (a) and (b) compiler is free to evaluate call arguments (that
includes receiver) in any order. That means it can dereference baz before
@@ -19,20 +19,30 @@
PREREQUISITES -----------------------------------------------------------------
-1) Install Lua 5.1
+(1) Install Lua 5.1
-2) Get LLVM 2.9 and Clang 2.9 sources and build them.
+ $ sudo apt-get install lua5.1
-Follow the instructions on http://clang.llvm.org/get_started.html.
+(2) Get LLVM 8.0 and Clang 8.0 sources and build them.
-Make sure to pass --enable-optimized to configure to get Release build
-instead of a Debug one.
+ Follow the instructions on http://clang.llvm.org/get_started.html.
-3) Build gcmole Clang plugin (libgcmole.so)
+ Make sure to pass -DCMAKE_BUILD_TYPE=Release to cmake to get Release build
+ instead of a Debug one.
-In the tools/gcmole execute the following command:
+(3) Build gcmole Clang plugin (libgcmole.so)
-LLVM_SRC_ROOT=<path-to-llvm-source-root> make
+ In the tools/gcmole directory execute the following command:
+
+ $ BUILD_ROOT=<path> LLVM_SRC_ROOT=<path> CLANG_SRC_ROOT=<path> make
+
+(*) Note that steps (2) and (3) can also be achieved by just using the included
+ bootstrapping script in this directory:
+
+ $ ./tools/gcmole/bootstrap.sh
+
+ This will use "third_party/llvm+clang-build" as a build directory and checkout
+ required sources in the "third_party" directory.
USING GCMOLE ------------------------------------------------------------------
@@ -60,3 +70,41 @@
can be ignored.
If any errors were found driver exits with non-zero status.
+
+PACKAGING ---------------------------------------------------------------------
+
+gcmole is deployed on V8's buildbot infrastructure to run it as part of the
+continuous integration. A pre-built package of gcmole together with Clang is
+hosted on Google Cloud Storage for this purpose. To update this package to a
+newer version, use the provided packaging script:
+
+ $ ./tools/gcmole/package.sh
+
+This will create a new "tools/gcmole/gcmole-tools.tar.gz" package with the
+corresponding SHA1 sum suitable to be used for this purpose. It assumes that
+Clang was built in "third_party/llvm+clang-build" (e.g. by the bootstrapping
+script "bootstrap.sh" mentioned above).
+
+TROUBLESHOOTING ---------------------------------------------------------------
+
+gcmole is tighly coupled with the AST structure that Clang produces. Therefore
+when upgrading to a newer Clang version, it might start producing bogus output
+or completely stop outputting warnings. In such occasion, one might start the
+debugging process by checking weather a new AST node type is introduced which
+is currently not supported by gcmole. Insert the following code at the end of
+the FunctionAnalyzer::VisitExpr method to see the unsupported AST class(es)
+and the source position which generates them:
+
+ if (expr) {
+ clang::Stmt::StmtClass stmtClass = expr->getStmtClass();
+ d_.Report(clang::FullSourceLoc(expr->getExprLoc(), sm_),
+ d_.getCustomDiagID(clang::DiagnosticsEngine::Remark, "%0")) << stmtClass;
+ }
+
+For instance, gcmole currently doesn't support AtomicExprClass statements
+introduced for atomic operations.
+
+A convenient way to observe the AST generated by Clang is to pass the following
+flags when invoking clang++
+
+ -Xclang -ast-dump -fsyntax-only
diff --git a/src/v8/tools/gcmole/bootstrap.sh b/src/v8/tools/gcmole/bootstrap.sh
index ac6593c..05ab1cb 100755
--- a/src/v8/tools/gcmole/bootstrap.sh
+++ b/src/v8/tools/gcmole/bootstrap.sh
@@ -27,16 +27,18 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-# This script will build libgcmole.so. Building a recent clang needs a
-# recent GCC, so if you explicitly want to use GCC 4.8, use:
-#
-# CC=gcc-4.8 CPP=cpp-4.8 CXX=g++-4.8 CXXFLAGS=-static-libstdc++ CXXCPP=cpp-4.8 ./bootstrap.sh
+# This script will build libgcmole.so as well as a corresponding recent
+# version of Clang and LLVM. The Clang will be built with the locally
+# installed compiler and statically link against the local libstdc++ so
+# that the resulting binary is easier transferable between different
+# environments.
-CLANG_RELEASE=3.5
+CLANG_RELEASE=8.0
-THIS_DIR="$(dirname "${0}")"
+THIS_DIR="$(readlink -f "$(dirname "${0}")")"
LLVM_DIR="${THIS_DIR}/../../third_party/llvm"
-CLANG_DIR="${LLVM_DIR}/tools/clang"
+CLANG_DIR="${THIS_DIR}/../../third_party/clang"
+BUILD_DIR="${THIS_DIR}/../../third_party/llvm+clang-build"
LLVM_REPO_URL=${LLVM_URL:-https://llvm.org/svn/llvm-project}
@@ -70,7 +72,7 @@
fi
fi
-echo Getting LLVM r"${CLANG_RELEASE}" in "${LLVM_DIR}"
+echo Getting LLVM release "${CLANG_RELEASE}" in "${LLVM_DIR}"
if ! svn co --force \
"${LLVM_REPO_URL}/llvm/branches/release_${CLANG_RELEASE/./}" \
"${LLVM_DIR}"; then
@@ -81,7 +83,7 @@
"${LLVM_DIR}"
fi
-echo Getting clang r"${CLANG_RELEASE}" in "${CLANG_DIR}"
+echo Getting clang release "${CLANG_RELEASE}" in "${CLANG_DIR}"
svn co --force \
"${LLVM_REPO_URL}/cfe/branches/release_${CLANG_RELEASE/./}" \
"${CLANG_DIR}"
@@ -97,33 +99,32 @@
fi
# Build clang.
-cd "${LLVM_DIR}"
-if [[ ! -f ./config.status ]]; then
- ../llvm/configure \
- --enable-optimized \
- --disable-threads \
- --disable-pthreads \
- --without-llvmgcc \
- --without-llvmgxx
+if [ ! -e "${BUILD_DIR}" ]; then
+ mkdir "${BUILD_DIR}"
fi
-
+cd "${BUILD_DIR}"
+cmake -DCMAKE_CXX_FLAGS="-static-libstdc++" -DLLVM_ENABLE_TERMINFO=OFF \
+ -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS=clang "${LLVM_DIR}"
MACOSX_DEPLOYMENT_TARGET=10.5 make -j"${NUM_JOBS}"
+
+# Strip the clang binary.
STRIP_FLAGS=
if [ "${OS}" = "Darwin" ]; then
# See http://crbug.com/256342
STRIP_FLAGS=-x
fi
-strip ${STRIP_FLAGS} Release+Asserts/bin/clang
+strip ${STRIP_FLAGS} bin/clang
cd -
# Build libgcmole.so
make -C "${THIS_DIR}" clean
-make -C "${THIS_DIR}" LLVM_SRC_ROOT="${LLVM_DIR}" libgcmole.so
+make -C "${THIS_DIR}" LLVM_SRC_ROOT="${LLVM_DIR}" \
+ CLANG_SRC_ROOT="${CLANG_DIR}" BUILD_ROOT="${BUILD_DIR}" libgcmole.so
set +x
echo
echo You can now run gcmole using this command:
echo
-echo CLANG_BIN=\"third_party/llvm/Release+Asserts/bin\" lua tools/gcmole/gcmole.lua
+echo CLANG_BIN=\"third_party/llvm+clang-build/bin\" lua tools/gcmole/gcmole.lua
echo
diff --git a/src/v8/tools/gcmole/gcmole-test.cc b/src/v8/tools/gcmole/gcmole-test.cc
new file mode 100644
index 0000000..c00c6e5
--- /dev/null
+++ b/src/v8/tools/gcmole/gcmole-test.cc
@@ -0,0 +1,71 @@
+// Copyright 2019 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 "src/execution/isolate.h"
+#include "src/handles/handles-inl.h"
+#include "src/handles/handles.h"
+#include "src/objects/maybe-object.h"
+#include "src/objects/object-macros.h"
+
+namespace v8 {
+namespace internal {
+
+Handle<Object> CauseGC(Handle<Object> obj, Isolate* isolate) {
+ isolate->heap()->CollectGarbage(OLD_SPACE, GarbageCollectionReason::kTesting);
+
+ return obj;
+}
+
+void TwoArgumentsFunction(Object a, Object b) {
+ a->Print();
+ b->Print();
+}
+
+void TestTwoArguments(Isolate* isolate) {
+ Handle<JSObject> obj1 = isolate->factory()->NewJSObjectWithNullProto();
+ Handle<JSObject> obj2 = isolate->factory()->NewJSObjectWithNullProto();
+ TwoArgumentsFunction(*CauseGC(obj1, isolate), *CauseGC(obj2, isolate));
+}
+
+void TwoSizeTArgumentsFunction(size_t a, size_t b) {
+ USE(a);
+ USE(b);
+}
+
+void TestTwoSizeTArguments(Isolate* isolate) {
+ Handle<JSObject> obj1 = isolate->factory()->NewJSObjectWithNullProto();
+ Handle<JSObject> obj2 = isolate->factory()->NewJSObjectWithNullProto();
+ TwoSizeTArgumentsFunction(sizeof(*CauseGC(obj1, isolate)),
+ sizeof(*CauseGC(obj2, isolate)));
+}
+
+class SomeObject : public Object {
+ public:
+ void Method(Object a) { a->Print(); }
+
+ SomeObject& operator=(const Object& b) {
+ this->Print();
+ return *this;
+ }
+
+ DECL_CAST(SomeObject)
+
+ OBJECT_CONSTRUCTORS(SomeObject, Object);
+};
+
+void TestMethodCall(Isolate* isolate) {
+ SomeObject obj;
+ Handle<SomeObject> so = handle(obj, isolate);
+ Handle<JSObject> obj1 = isolate->factory()->NewJSObjectWithNullProto();
+ so->Method(*CauseGC(obj1, isolate));
+}
+
+void TestOperatorCall(Isolate* isolate) {
+ SomeObject obj;
+ Handle<JSObject> obj1 = isolate->factory()->NewJSObjectWithNullProto();
+ obj = *CauseGC(obj1, isolate);
+}
+
+} // namespace internal
+} // namespace v8
diff --git a/src/v8/tools/gcmole/gcmole-tools.tar.gz.sha1 b/src/v8/tools/gcmole/gcmole-tools.tar.gz.sha1
index f9e3e01..718e967 100644
--- a/src/v8/tools/gcmole/gcmole-tools.tar.gz.sha1
+++ b/src/v8/tools/gcmole/gcmole-tools.tar.gz.sha1
@@ -1 +1 @@
-a21e6b0d08afcfe454042c2c1fbf1d1738caf129
\ No newline at end of file
+3d4ba1759c3d5bc7e98c466d24fa0c43f186ba79
\ No newline at end of file
diff --git a/src/v8/tools/gcmole/gcmole.cc b/src/v8/tools/gcmole/gcmole.cc
index f7a6c94..6631583 100644
--- a/src/v8/tools/gcmole/gcmole.cc
+++ b/src/v8/tools/gcmole/gcmole.cc
@@ -458,7 +458,9 @@
CallProps() : env_(NULL) { }
void SetEffect(int arg, ExprEffect in) {
- if (in.hasGC()) gc_.set(arg);
+ if (in.hasGC()) {
+ gc_.set(arg);
+ }
if (in.hasRawDef()) raw_def_.set(arg);
if (in.hasRawUse()) raw_use_.set(arg);
if (in.env() != NULL) {
@@ -472,17 +474,24 @@
ExprEffect ComputeCumulativeEffect(bool result_is_raw) {
ExprEffect out = ExprEffect::NoneWithEnv(env_);
- if (gc_.any()) out.setGC();
+ if (gc_.any()) {
+ out.setGC();
+ }
if (raw_use_.any()) out.setRawUse();
if (result_is_raw) out.setRawDef();
return out;
}
bool IsSafe() {
- if (!gc_.any()) return true;
+ if (!gc_.any()) {
+ return true;
+ }
std::bitset<kMaxNumberOfArguments> raw = (raw_def_ | raw_use_);
- if (!raw.any()) return true;
- return gc_.count() == 1 && !((raw ^ gc_).any());
+ if (!raw.any()) {
+ return true;
+ }
+ bool result = gc_.count() == 1 && !((raw ^ gc_).any());
+ return result;
}
private:
@@ -537,20 +546,19 @@
class FunctionAnalyzer {
public:
FunctionAnalyzer(clang::MangleContext* ctx,
- clang::DeclarationName handle_decl_name,
clang::CXXRecordDecl* object_decl,
+ clang::CXXRecordDecl* maybe_object_decl,
clang::CXXRecordDecl* smi_decl, clang::DiagnosticsEngine& d,
clang::SourceManager& sm, bool dead_vars_analysis)
: ctx_(ctx),
- handle_decl_name_(handle_decl_name),
object_decl_(object_decl),
+ maybe_object_decl_(maybe_object_decl),
smi_decl_(smi_decl),
d_(d),
sm_(sm),
block_(NULL),
dead_vars_analysis_(dead_vars_analysis) {}
-
// --------------------------------------------------------------------------
// Expressions
// --------------------------------------------------------------------------
@@ -574,6 +582,7 @@
VISIT(CharacterLiteral);
VISIT(ChooseExpr);
VISIT(CompoundLiteralExpr);
+ VISIT(ConstantExpr);
VISIT(CXXBindTemporaryExpr);
VISIT(CXXBoolLiteralExpr);
VISIT(CXXConstructExpr);
@@ -598,9 +607,11 @@
VISIT(FloatingLiteral);
VISIT(GNUNullExpr);
VISIT(ImaginaryLiteral);
+ VISIT(ImplicitCastExpr);
VISIT(ImplicitValueInitExpr);
VISIT(InitListExpr);
VISIT(IntegerLiteral);
+ VISIT(MaterializeTemporaryExpr);
VISIT(MemberExpr);
VISIT(OffsetOfExpr);
VISIT(OpaqueValueExpr);
@@ -616,6 +627,7 @@
VISIT(SubstNonTypeTemplateParmPackExpr);
VISIT(TypeTraitExpr);
VISIT(UnaryOperator);
+ VISIT(UnaryExprOrTypeTraitExpr);
VISIT(VAArgExpr);
#undef VISIT
@@ -685,6 +697,7 @@
llvm::cast<clang::DeclRefExpr>(expr)->getDecl()->getNameAsString();
return true;
}
+
return false;
}
@@ -701,14 +714,6 @@
case clang::BO_LOr:
return ExprEffect::Merge(VisitExpr(lhs, env), VisitExpr(rhs, env));
- case clang::BO_Assign: {
- std::string var_name;
- if (IsRawPointerVar(lhs, &var_name)) {
- return VisitExpr(rhs, env).Define(var_name);
- }
- return Par(expr, 2, exprs, env);
- }
-
default:
return Par(expr, 2, exprs, env);
}
@@ -718,6 +723,10 @@
return VisitExpr(expr->getSubExpr(), env);
}
+ DECL_VISIT_EXPR(MaterializeTemporaryExpr) {
+ return VisitExpr(expr->GetTemporaryExpr(), env);
+ }
+
DECL_VISIT_EXPR(CXXConstructExpr) {
return VisitArguments<>(expr, env);
}
@@ -740,6 +749,12 @@
return VisitExpr(expr->getSubExpr(), env);
}
+ DECL_VISIT_EXPR(ImplicitCastExpr) {
+ return VisitExpr(expr->getSubExpr(), env);
+ }
+
+ DECL_VISIT_EXPR(ConstantExpr) { return VisitExpr(expr->getSubExpr(), env); }
+
DECL_VISIT_EXPR(InitListExpr) {
return Seq(expr, expr->getNumInits(), expr->getInits(), env);
}
@@ -761,11 +776,11 @@
}
DECL_VISIT_EXPR(UnaryOperator) {
- // TODO We are treating all expressions that look like &raw_pointer_var
- // as definitions of raw_pointer_var. This should be changed to
- // recognize less generic pattern:
+ // TODO(mstarzinger): We are treating all expressions that look like
+ // {&raw_pointer_var} as definitions of {raw_pointer_var}. This should be
+ // changed to recognize less generic pattern:
//
- // if (maybe_object->ToObject(&obj)) return maybe_object;
+ // if (maybe_object->ToObject(&obj)) return maybe_object;
//
if (expr->getOpcode() == clang::UO_AddrOf) {
std::string var_name;
@@ -776,6 +791,14 @@
return VisitExpr(expr->getSubExpr(), env);
}
+ DECL_VISIT_EXPR(UnaryExprOrTypeTraitExpr) {
+ if (expr->isArgumentType()) {
+ return ExprEffect::None();
+ }
+
+ return VisitExpr(expr->getArgumentExpr(), env);
+ }
+
DECL_VISIT_EXPR(CastExpr) {
return VisitExpr(expr->getSubExpr(), env);
}
@@ -796,7 +819,8 @@
if (!props.IsSafe()) ReportUnsafe(parent, BAD_EXPR_MSG);
- return props.ComputeCumulativeEffect(IsRawPointerType(parent->getType()));
+ return props.ComputeCumulativeEffect(
+ RepresentsRawPointerType(parent->getType()));
}
ExprEffect Seq(clang::Stmt* parent,
@@ -816,7 +840,7 @@
const clang::QualType& var_type,
const std::string& var_name,
const Environment& env) {
- if (IsRawPointerType(var_type)) {
+ if (RepresentsRawPointerType(var_type)) {
if (!env.IsAlive(var_name) && dead_vars_analysis_) {
ReportUnsafe(parent, DEAD_VAR_MSG);
}
@@ -840,7 +864,8 @@
CallProps props;
VisitArguments<>(call, &props, env);
if (!props.IsSafe()) ReportUnsafe(call, BAD_EXPR_MSG);
- return props.ComputeCumulativeEffect(IsRawPointerType(call->getType()));
+ return props.ComputeCumulativeEffect(
+ RepresentsRawPointerType(call->getType()));
}
template<typename ExprType>
@@ -864,12 +889,25 @@
props.SetEffect(0, VisitExpr(receiver, env));
}
- VisitArguments<>(call, &props, env);
+ std::string var_name;
+ clang::CXXOperatorCallExpr* opcall =
+ llvm::dyn_cast_or_null<clang::CXXOperatorCallExpr>(call);
+ if (opcall != NULL && opcall->isAssignmentOp() &&
+ IsRawPointerVar(opcall->getArg(0), &var_name)) {
+ // TODO(mstarzinger): We are treating all assignment operator calls with
+ // the left hand side looking like {raw_pointer_var} as safe independent
+ // of the concrete assignment operator implementation. This should be
+ // changed to be more narrow only if the assignment operator of the base
+ // {Object} or {HeapObject} class was used, which we know to be safe.
+ props.SetEffect(1, VisitExpr(call->getArg(1), env).Define(var_name));
+ } else {
+ VisitArguments<>(call, &props, env);
+ }
if (!props.IsSafe()) ReportUnsafe(call, BAD_EXPR_MSG);
- ExprEffect out =
- props.ComputeCumulativeEffect(IsRawPointerType(call->getType()));
+ ExprEffect out = props.ComputeCumulativeEffect(
+ RepresentsRawPointerType(call->getType()));
clang::FunctionDecl* callee = call->getDirectCallee();
if ((callee != NULL) && KnownToCauseGC(ctx_, callee)) {
@@ -1104,45 +1142,88 @@
}
}
- bool IsDerivedFrom(clang::CXXRecordDecl* record,
- clang::CXXRecordDecl* base) {
+ bool IsDerivedFrom(const clang::CXXRecordDecl* record,
+ const clang::CXXRecordDecl* base) {
return (record == base) || record->isDerivedFrom(base);
}
- bool IsRawPointerType(clang::QualType qtype) {
- const clang::PointerType* type =
+ const clang::CXXRecordDecl* GetDefinitionOrNull(
+ const clang::CXXRecordDecl* record) {
+ if (record == NULL) {
+ return NULL;
+ }
+
+ if (!InV8Namespace(record)) return NULL;
+
+ if (!record->hasDefinition()) {
+ return NULL;
+ }
+
+ return record->getDefinition();
+ }
+
+ bool IsRawPointerType(const clang::PointerType* type) {
+ const clang::CXXRecordDecl* record = type->getPointeeCXXRecordDecl();
+
+ const clang::CXXRecordDecl* definition = GetDefinitionOrNull(record);
+ if (!definition) {
+ return false;
+ }
+
+ // TODO(mstarzinger): Unify the common parts of {IsRawPointerType} and
+ // {IsInternalPointerType} once gcmole is up and running again.
+ bool result = (IsDerivedFrom(record, object_decl_) &&
+ !IsDerivedFrom(record, smi_decl_)) ||
+ IsDerivedFrom(record, maybe_object_decl_);
+ return result;
+ }
+
+ bool IsInternalPointerType(clang::QualType qtype) {
+ if (qtype.isNull()) {
+ return false;
+ }
+ if (qtype->isNullPtrType()) {
+ return true;
+ }
+
+ const clang::CXXRecordDecl* record = qtype->getAsCXXRecordDecl();
+
+ const clang::CXXRecordDecl* definition = GetDefinitionOrNull(record);
+ if (!definition) {
+ return false;
+ }
+
+ // TODO(mstarzinger): Unify the common parts of {IsRawPointerType} and
+ // {IsInternalPointerType} once gcmole is up and running again.
+ bool result = (IsDerivedFrom(record, object_decl_) &&
+ !IsDerivedFrom(record, smi_decl_)) ||
+ IsDerivedFrom(record, maybe_object_decl_);
+ return result;
+ }
+
+ // 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) {
+ const clang::PointerType* pointer_type =
llvm::dyn_cast_or_null<clang::PointerType>(qtype.getTypePtrOrNull());
- if (type == NULL) return false;
-
- const clang::TagType* pointee =
- ToTagType(type->getPointeeType().getTypePtr());
- if (pointee == NULL) return false;
-
- clang::CXXRecordDecl* record =
- llvm::dyn_cast_or_null<clang::CXXRecordDecl>(pointee->getDecl());
- if (record == NULL) return false;
-
- if (!InV8Namespace(record)) return false;
-
- if (!record->hasDefinition()) return false;
-
- record = record->getDefinition();
-
- return IsDerivedFrom(record, object_decl_) &&
- !IsDerivedFrom(record, smi_decl_);
+ if (pointer_type != NULL) {
+ return IsRawPointerType(pointer_type);
+ } else {
+ return IsInternalPointerType(qtype);
+ }
}
Environment VisitDecl(clang::Decl* decl, const Environment& env) {
if (clang::VarDecl* var = llvm::dyn_cast<clang::VarDecl>(decl)) {
Environment out = var->hasInit() ? VisitStmt(var->getInit(), env) : env;
- if (IsRawPointerType(var->getType())) {
+ if (RepresentsRawPointerType(var->getType())) {
out = out.Define(var->getNameAsString());
}
return out;
}
- // TODO: handle other declarations?
+ // TODO(mstarzinger): handle other declarations?
return env;
}
@@ -1199,8 +1280,8 @@
clang::MangleContext* ctx_;
- clang::DeclarationName handle_decl_name_;
clang::CXXRecordDecl* object_decl_;
+ clang::CXXRecordDecl* maybe_object_decl_;
clang::CXXRecordDecl* smi_decl_;
clang::DiagnosticsEngine& d_;
@@ -1231,23 +1312,34 @@
r.ResolveNamespace("v8").ResolveNamespace("internal").
Resolve<clang::CXXRecordDecl>("Object");
+ clang::CXXRecordDecl* maybe_object_decl =
+ r.ResolveNamespace("v8")
+ .ResolveNamespace("internal")
+ .Resolve<clang::CXXRecordDecl>("MaybeObject");
+
clang::CXXRecordDecl* smi_decl =
r.ResolveNamespace("v8").ResolveNamespace("internal").
Resolve<clang::CXXRecordDecl>("Smi");
if (object_decl != NULL) object_decl = object_decl->getDefinition();
+ if (maybe_object_decl != NULL)
+ maybe_object_decl = maybe_object_decl->getDefinition();
+
if (smi_decl != NULL) smi_decl = smi_decl->getDefinition();
- if (object_decl != NULL && smi_decl != NULL) {
+ if (object_decl != NULL && smi_decl != NULL && maybe_object_decl != NULL) {
function_analyzer_ = new FunctionAnalyzer(
- clang::ItaniumMangleContext::create(ctx, d_), r.ResolveName("Handle"),
- object_decl, smi_decl, d_, sm_, dead_vars_analysis_);
+ clang::ItaniumMangleContext::create(ctx, d_), object_decl,
+ maybe_object_decl, smi_decl, d_, sm_, dead_vars_analysis_);
TraverseDecl(ctx.getTranslationUnitDecl());
} else {
if (object_decl == NULL) {
llvm::errs() << "Failed to resolve v8::internal::Object\n";
}
+ if (maybe_object_decl == NULL) {
+ llvm::errs() << "Failed to resolve v8::internal::MaybeObject\n";
+ }
if (smi_decl == NULL) {
llvm::errs() << "Failed to resolve v8::internal::Smi\n";
}
@@ -1271,9 +1363,10 @@
template<typename ConsumerType>
class Action : public clang::PluginASTAction {
protected:
- clang::ASTConsumer *CreateASTConsumer(clang::CompilerInstance &CI,
- llvm::StringRef InFile) {
- return new ConsumerType(CI.getDiagnostics(), CI.getSourceManager(), args_);
+ virtual std::unique_ptr<clang::ASTConsumer> CreateASTConsumer(
+ clang::CompilerInstance& CI, llvm::StringRef InFile) {
+ return std::unique_ptr<clang::ASTConsumer>(
+ new ConsumerType(CI.getDiagnostics(), CI.getSourceManager(), args_));
}
bool ParseArgs(const clang::CompilerInstance &CI,
diff --git a/src/v8/tools/gcmole/gcmole.lua b/src/v8/tools/gcmole/gcmole.lua
index 862b7b0..ae17fdc 100644
--- a/src/v8/tools/gcmole/gcmole.lua
+++ b/src/v8/tools/gcmole/gcmole.lua
@@ -112,6 +112,7 @@
.. " -DV8_INTL_SUPPORT"
.. " -I./"
.. " -Iinclude/"
+ .. " -Iout/Release/gen"
.. " -Ithird_party/icu/source/common"
.. " -Ithird_party/icu/source/i18n"
.. " " .. arch_options
@@ -181,34 +182,6 @@
end
-------------------------------------------------------------------------------
--- GYP file parsing
-
--- TODO(machenbach): Remove this when deprecating gyp.
-local function ParseGYPFile()
- local result = {}
- local gyp_files = {
- { "src/v8.gyp", "'([^']-%.cc)'", "src/" },
- { "test/cctest/cctest.gyp", "'(test-[^']-%.cc)'", "test/cctest/" }
- }
-
- for i = 1, #gyp_files do
- local filename = gyp_files[i][1]
- local pattern = gyp_files[i][2]
- local prefix = gyp_files[i][3]
- local gyp_file = assert(io.open(filename), "failed to open GYP file")
- local gyp = gyp_file:read('*a')
- for condition, sources in
- gyp:gmatch "%[.-### gcmole%((.-)%) ###(.-)%]" do
- if result[condition] == nil then result[condition] = {} end
- for file in sources:gmatch(pattern) do
- table.insert(result[condition], prefix .. file)
- end
- end
- gyp_file:close()
- end
-
- return result
-end
local function ParseGNFile()
local result = {}
@@ -258,34 +231,8 @@
end
-local gyp_sources = ParseGYPFile()
local gn_sources = ParseGNFile()
--- TODO(machenbach): Remove this comparison logic when deprecating gyp.
-local function CompareSources(sources1, sources2, what)
- for condition, files1 in pairs(sources1) do
- local files2 = sources2[condition]
- assert(
- files2 ~= nil,
- "Missing gcmole condition in " .. what .. ": " .. condition)
-
- -- Turn into set for speed.
- files2_set = {}
- for i, file in pairs(files2) do files2_set[file] = true end
-
- for i, file in pairs(files1) do
- assert(
- files2_set[file] ~= nil,
- "Missing file " .. file .. " in " .. what .. " for condition " ..
- condition)
- end
- end
-end
-
-CompareSources(gyp_sources, gn_sources, "GN")
-CompareSources(gn_sources, gyp_sources, "GYP")
-
-
local function FilesForArch(arch)
return BuildFileList(gn_sources, { os = 'linux',
arch = arch,
diff --git a/src/v8/tools/gcmole/package.sh b/src/v8/tools/gcmole/package.sh
new file mode 100755
index 0000000..6206e7b
--- /dev/null
+++ b/src/v8/tools/gcmole/package.sh
@@ -0,0 +1,45 @@
+#!/usr/bin/env bash
+
+# Copyright 2019 the V8 project authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# This script will package a built gcmole plugin together with the
+# corresponding clang binary into an archive which can be used on the
+# buildbot infrastructure to be run against V8 checkouts.
+
+THIS_DIR="$(readlink -f "$(dirname "${0}")")"
+
+PACKAGE_DIR="${THIS_DIR}/../../tools/gcmole/gcmole-tools"
+PACKAGE_FILE="${THIS_DIR}/../../tools/gcmole/gcmole-tools.tar.gz"
+PACKAGE_SUM="${THIS_DIR}/../../tools/gcmole/gcmole-tools.tar.gz.sha1"
+BUILD_DIR="${THIS_DIR}/../../third_party/llvm+clang-build"
+
+# Echo all commands
+set -x
+
+# Copy all required files
+mkdir -p "${PACKAGE_DIR}/bin"
+cp "${BUILD_DIR}/bin/clang++" "${PACKAGE_DIR}/bin"
+mkdir -p "${PACKAGE_DIR}/lib"
+cp -r "${BUILD_DIR}/lib/clang" "${PACKAGE_DIR}/lib"
+cp "${THIS_DIR}/libgcmole.so" "${PACKAGE_DIR}"
+
+# Generate the archive
+cd "$(dirname "${PACKAGE_DIR}")"
+tar -c -z -f "${PACKAGE_FILE}" "$(basename "${PACKAGE_DIR}")"
+
+# Generate checksum
+sha1sum "${PACKAGE_FILE}" | awk '{print $1}' > "${PACKAGE_SUM}"
+
+set +x
+
+echo
+echo You can find a packaged version of gcmole here:
+echo
+echo $(readlink -f "${PACKAGE_FILE}")
+echo
+echo You can now run gcmole using this command:
+echo
+echo CLANG_BIN="tools/gcmole/gcmole-tools/bin" lua tools/gcmole/gcmole.lua
+echo
diff --git a/src/v8/tools/gcmole/parallel.py b/src/v8/tools/gcmole/parallel.py
index 0c045f4..7ff95cc 100755
--- a/src/v8/tools/gcmole/parallel.py
+++ b/src/v8/tools/gcmole/parallel.py
@@ -20,6 +20,9 @@
______________ finish <exit code of clang --opt file2> ______________
"""
+# for py2/py3 compatibility
+from __future__ import print_function
+
import itertools
import multiprocessing
import subprocess
@@ -39,6 +42,6 @@
cmdlines = ["%s %s" % (sys.argv[1], filename) for filename in sys.argv[2:]]
for filename, result in itertools.izip(
sys.argv[2:], pool.imap(invoke, cmdlines)):
- print "______________ %s" % filename
- print result[0]
- print "______________ finish %d ______________" % result[1]
+ print("______________ %s" % filename)
+ print(result[0])
+ print("______________ finish %d ______________" % result[1])
diff --git a/src/v8/tools/gcmole/run-gcmole.py b/src/v8/tools/gcmole/run-gcmole.py
index 88799e3..6f2a091 100755
--- a/src/v8/tools/gcmole/run-gcmole.py
+++ b/src/v8/tools/gcmole/run-gcmole.py
@@ -3,7 +3,11 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+# for py2/py3 compatibility
+from __future__ import print_function
+
import os
+import os.path
import signal
import subprocess
import sys
@@ -17,6 +21,11 @@
assert len(sys.argv) == 2
+if not os.path.isfile("out/Release/gen/torque-generated/builtin-definitions-tq.h"):
+ print("Expected generated headers in out/Release/gen.")
+ print("Either build v8 in out/Release or change gcmole.lua:115")
+ sys.exit(-1)
+
proc = subprocess.Popen(
[LUA, DRIVER, sys.argv[1]],
env={'CLANG_BIN': CLANG_BIN, 'CLANG_PLUGINS': CLANG_PLUGINS},
diff --git a/src/v8/tools/gdb-v8-support.py b/src/v8/tools/gdb-v8-support.py
index a0262f0..f8442bf 100644
--- a/src/v8/tools/gdb-v8-support.py
+++ b/src/v8/tools/gdb-v8-support.py
@@ -25,12 +25,15 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# for py2/py3 compatibility
+from __future__ import print_function
+
import re
import tempfile
import os
import subprocess
import time
-
+import gdb
kSmiTag = 0
kSmiTagSize = 1
diff --git a/src/v8/tools/gdbinit b/src/v8/tools/gdbinit
index fa9f434..ad7847d 100644
--- a/src/v8/tools/gdbinit
+++ b/src/v8/tools/gdbinit
@@ -2,18 +2,27 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-# Print HeapObjects.
+# Print tagged object.
define job
-call _v8_internal_Print_Object((void*)($arg0))
+call (void) _v8_internal_Print_Object((void*)($arg0))
end
document job
Print a v8 JavaScript object
Usage: job tagged_ptr
end
-# Print v8::Local handle value.
+# Print content of v8::internal::Handle.
+define jh
+call (void) _v8_internal_Print_Object(*((v8::internal::Object**)($arg0).location_))
+end
+document jh
+Print content of a v8::internal::Handle
+Usage: jh internal_handle
+end
+
+# Print content of v8::Local handle.
define jlh
-call _v8_internal_Print_Object(*((v8::internal::Object**)($arg0).val_))
+call (void) _v8_internal_Print_Object(*((v8::internal::Object**)($arg0).val_))
end
document jlh
Print content of a v8::Local handle
@@ -22,62 +31,25 @@
# Print Code objects containing given PC.
define jco
-call _v8_internal_Print_Code((void*)($arg0))
+call (void) _v8_internal_Print_Code((void*)($arg0))
end
document jco
Print a v8 Code object from an internal code address
Usage: jco pc
end
-# Print FeedbackVector
-define jfv
-call _v8_internal_Print_FeedbackVector((void*)($arg0))
-end
-document jfv
-Print a v8 FeedbackVector object
-Usage: jfv tagged_ptr
-end
-
-# Print FeedbackMetadata
-define jfm
-call _v8_internal_Print_FeedbackMetadata((void*)($arg0))
-end
-document jfm
-Print a v8 FeedbackMetadata object
-Usage: jfm tagged_ptr
-end
-
-
-# Print DescriptorArray.
-define jda
-call _v8_internal_Print_DescriptorArray((void*)($arg0))
-end
-document jda
-Print a v8 DescriptorArray object
-Usage: jda tagged_ptr
-end
-
# Print LayoutDescriptor.
define jld
-call _v8_internal_Print_LayoutDescriptor((void*)($arg0))
+call (void) _v8_internal_Print_LayoutDescriptor((void*)($arg0))
end
document jld
Print a v8 LayoutDescriptor object
Usage: jld tagged_ptr
end
-# Print TransitionArray.
-define jta
-call _v8_internal_Print_TransitionArray((void*)($arg0))
-end
-document jta
-Print a v8 TransitionArray object
-Usage: jta tagged_ptr
-end
-
# Print TransitionTree.
define jtt
-call _v8_internal_Print_TransitionTree((void*)($arg0))
+call (void) _v8_internal_Print_TransitionTree((void*)($arg0))
end
document jtt
Print the complete transition tree of the given v8 Map.
@@ -86,13 +58,22 @@
# Print JavaScript stack trace.
define jst
-call _v8_internal_Print_StackTrace()
+call (void) _v8_internal_Print_StackTrace()
end
document jst
Print the current JavaScript stack trace
Usage: jst
end
+# Print TurboFan graph node.
+define pn
+call _v8_internal_Node_Print((void*)($arg0))
+end
+document pn
+Print a v8 TurboFan graph node
+Usage: pn node_address
+end
+
# Skip the JavaScript stack.
define jss
set $js_entry_sp=v8::internal::Isolate::Current()->thread_local_top()->js_entry_sp_
@@ -110,7 +91,7 @@
python
import re
frame_re = re.compile("^#(\d+)\s*(?:0x[a-f\d]+ in )?(.+) \(.+ at (.+)")
-assert_re = re.compile("^\s*(\S+) = .+<v8::internal::Per\w+AssertType::(\w+)_ASSERT, (false|true)>")
+assert_re = re.compile("^\s*(\S+) = .+<v8::internal::Per\w+AssertScope<v8::internal::(\S*), (false|true)>")
btl = gdb.execute("backtrace full", to_string = True).splitlines()
for l in btl:
match = frame_re.match(l)
@@ -135,8 +116,8 @@
# Search for a pointer inside all valid pages.
define space_find
set $space = $arg0
- set $current_page = $space->anchor()->next_page()
- while ($current_page != $space->anchor())
+ set $current_page = $space->first_page()
+ while ($current_page != 0)
printf "# Searching in %p - %p\n", $current_page->area_start(), $current_page->area_end()-1
find $current_page->area_start(), $current_page->area_end()-1, $arg1
set $current_page = $current_page->next_page()
@@ -159,3 +140,87 @@
set disassembly-flavor intel
set disable-randomization off
+
+# Install a handler whenever the debugger stops due to a signal. It walks up the
+# stack looking for V8_Dcheck and moves the frame to the one above it so it's
+# immediately at the line of code that triggered the DCHECK.
+python
+def dcheck_stop_handler(event):
+ frame = gdb.selected_frame()
+ select_frame = None
+ message = None
+ count = 0
+ # limit stack scanning since they're usually shallow and otherwise stack
+ # overflows can be very slow.
+ while frame is not None and count < 5:
+ count += 1
+ if frame.name() == 'V8_Dcheck':
+ frame_message = gdb.lookup_symbol('message', frame.block())[0]
+ if frame_message:
+ message = frame_message.value(frame).string()
+ select_frame = frame.older()
+ break
+ if frame.name() is not None and frame.name().startswith('V8_Fatal'):
+ select_frame = frame.older()
+ frame = frame.older()
+
+ if select_frame is not None:
+ select_frame.select()
+ gdb.execute('frame')
+ if message:
+ print('DCHECK error: {}'.format(message))
+
+gdb.events.stop.connect(dcheck_stop_handler)
+end
+
+# Code imported from chromium/src/tools/gdb/gdbinit
+python
+
+import os
+import subprocess
+import sys
+
+compile_dirs = set()
+
+
+def get_current_debug_file_directories():
+ dir = gdb.execute("show debug-file-directory", to_string=True)
+ dir = dir[
+ len('The directory where separate debug symbols are searched for is "'
+ ):-len('".') - 1]
+ return set(dir.split(":"))
+
+
+def add_debug_file_directory(dir):
+ # gdb has no function to add debug-file-directory, simulates that by using
+ # `show debug-file-directory` and `set debug-file-directory <directories>`.
+ current_dirs = get_current_debug_file_directories()
+ current_dirs.add(dir)
+ gdb.execute(
+ "set debug-file-directory %s" % ":".join(current_dirs), to_string=True)
+
+
+def newobj_handler(event):
+ global compile_dirs
+ compile_dir = os.path.dirname(event.new_objfile.filename)
+ if not compile_dir:
+ return
+ if compile_dir in compile_dirs:
+ return
+ compile_dirs.add(compile_dir)
+
+ # Add source path
+ gdb.execute("dir %s" % compile_dir)
+
+ # Need to tell the location of .dwo files.
+ # https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html
+ # https://crbug.com/603286#c35
+ add_debug_file_directory(compile_dir)
+
+# Event hook for newly loaded objfiles.
+# https://sourceware.org/gdb/onlinedocs/gdb/Events-In-Python.html
+gdb.events.new_objfile.connect(newobj_handler)
+
+gdb.execute("set environment V8_GDBINIT_SOURCED=1")
+
+end
diff --git a/src/v8/tools/gen-inlining-tests.py b/src/v8/tools/gen-inlining-tests.py
index a790236..400386c 100644
--- a/src/v8/tools/gen-inlining-tests.py
+++ b/src/v8/tools/gen-inlining-tests.py
@@ -1,9 +1,11 @@
-#!/usr/bin/env python3
+#!/usr/bin/env python
# Copyright 2016 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.
+# for py2/py3 compatibility
+from __future__ import print_function
from collections import namedtuple
import textwrap
diff --git a/src/v8/tools/gen-keywords-gen-h.py b/src/v8/tools/gen-keywords-gen-h.py
new file mode 100755
index 0000000..02750dc
--- /dev/null
+++ b/src/v8/tools/gen-keywords-gen-h.py
@@ -0,0 +1,252 @@
+#!/usr/bin/env python
+# 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.
+
+import os
+import sys
+import subprocess
+import re
+import math
+
+INPUT_PATH = "src/parsing/keywords.txt"
+OUTPUT_PATH = "src/parsing/keywords-gen.h"
+
+# TODO(leszeks): Trimming seems to regress performance, investigate.
+TRIM_CHAR_TABLE = False
+
+
+def next_power_of_2(x):
+ return 1 if x == 0 else 2**int(math.ceil(math.log(x, 2)))
+
+
+def call_with_input(cmd, input_string=""):
+ p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
+ stdout, _ = p.communicate(input_string)
+ retcode = p.wait()
+ if retcode != 0:
+ raise subprocess.CalledProcessError(retcode, cmd)
+ return stdout
+
+
+def checked_sub(pattern, sub, out, count=1, flags=0):
+ out, n = re.subn(pattern, sub, out, flags=flags)
+ if n != count:
+ raise Exception("Didn't get exactly %d replacement(s) for pattern: %s" %
+ (count, pattern))
+ return out
+
+
+def change_sizet_to_int(out):
+ # Literal buffer lengths are given as ints, not size_t
+ return checked_sub(r'\bsize_t\b', 'int', out, count=4)
+
+
+def drop_line_directives(out):
+ # #line causes gcov issue, so drop it
+ return re.sub(r'^#\s*line .*$\n', '', out, flags=re.MULTILINE)
+
+
+def trim_and_dcheck_char_table(out):
+ # Potential keyword strings are known to be lowercase ascii, so chop off the
+ # rest of the table and mask out the char
+
+ reads_re = re.compile(
+ r'asso_values\[static_cast<unsigned char>\(str\[(\d+)\]\)\]')
+
+ dchecks = []
+ for str_read in reads_re.finditer(out):
+ dchecks.append("DCHECK_LT(str[%d], 128);" % int(str_read.group(1)))
+
+ if TRIM_CHAR_TABLE:
+ out = checked_sub(
+ r'static const unsigned char asso_values\[\]\s*=\s*\{(\s*\d+\s*,){96}',
+ "".join(dchecks) + r'static const unsigned char asso_values[32] = {',
+ out,
+ flags=re.MULTILINE)
+ out = checked_sub(
+ reads_re.pattern,
+ r'asso_values[static_cast<unsigned char>(str[(\1)]&31)]',
+ out,
+ count=len(dchecks),
+ flags=re.MULTILINE)
+ else:
+ out = checked_sub(
+ r'static const unsigned char asso_values\[\]\s*=\s*\{',
+ "".join(dchecks) + r'static const unsigned char asso_values[128] = {',
+ out,
+ flags=re.MULTILINE)
+
+ return out
+
+
+def use_isinrange(out):
+ # Our IsInRange method is more efficient than checking for min/max length
+ return checked_sub(r'if \(len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH\)',
+ r'if (IsInRange(len, MIN_WORD_LENGTH, MAX_WORD_LENGTH))',
+ out)
+
+
+def pad_tables(out):
+ # We don't want to compare against the max hash value, so pad the tables up
+ # to a power of two and mask the hash.
+
+ # First get the new size
+ max_hash_value = int(re.search(r'MAX_HASH_VALUE\s*=\s*(\d+)', out).group(1))
+ old_table_length = max_hash_value + 1
+ new_table_length = next_power_of_2(old_table_length)
+ table_padding_len = new_table_length - old_table_length
+
+ # Pad the length table.
+ single_lengthtable_entry = r'\d+'
+ out = checked_sub(
+ r"""
+ static\ const\ unsigned\ char\ kPerfectKeywordLengthTable\[\]\s*=\s*\{
+ (
+ \s*%(single_lengthtable_entry)s\s*
+ (?:,\s*%(single_lengthtable_entry)s\s*)*
+ )
+ \}
+ """ % {'single_lengthtable_entry': single_lengthtable_entry},
+ r'static const unsigned char kPerfectKeywordLengthTable[%d] = { \1 %s }'
+ % (new_table_length, "".join([',0'] * table_padding_len)),
+ out,
+ flags=re.MULTILINE | re.VERBOSE)
+
+ # Pad the word list.
+ single_wordlist_entry = r"""
+ (?:\#line\ \d+\ ".*"$\s*)?
+ \{\s*"[a-z]*"\s*,\s*Token::[A-Z_]+\}
+ """
+ out = checked_sub(
+ r"""
+ static\ const\ struct\ PerfectKeywordHashTableEntry\ kPerfectKeywordHashTable\[\]\s*=\s*\{
+ (
+ \s*%(single_wordlist_entry)s\s*
+ (?:,\s*%(single_wordlist_entry)s\s*)*
+ )
+ \}
+ """ % {'single_wordlist_entry': single_wordlist_entry},
+ r'static const struct PerfectKeywordHashTableEntry kPerfectKeywordHashTable[%d] = {\1 %s }'
+ % (new_table_length, "".join(
+ [',{"",Token::IDENTIFIER}'] * table_padding_len)),
+ out,
+ flags=re.MULTILINE | re.VERBOSE)
+
+ # Mask the hash and replace the range check with DCHECKs.
+ out = checked_sub(r'Hash\s*\(\s*str,\s*len\s*\)',
+ r'Hash(str, len)&0x%x' % (new_table_length - 1), out)
+ out = checked_sub(
+ r'if \(key <= MAX_HASH_VALUE\)',
+ r'DCHECK_LT(key, arraysize(kPerfectKeywordLengthTable));DCHECK_LT(key, arraysize(kPerfectKeywordHashTable));',
+ out)
+
+ return out
+
+
+def return_token(out):
+ # We want to return the actual token rather than the table entry.
+
+ # Change the return type of the function. Make it inline too.
+ out = checked_sub(
+ r'const\s*struct\s*PerfectKeywordHashTableEntry\s*\*\s*((?:PerfectKeywordHash::)?GetToken)',
+ r'inline Token::Value \1',
+ out,
+ count=2)
+
+ # Change the return value when the keyword is found
+ out = checked_sub(r'return &kPerfectKeywordHashTable\[key\];',
+ r'return kPerfectKeywordHashTable[key].value;', out)
+
+ # Change the return value when the keyword is not found
+ out = checked_sub(r'return 0;', r'return Token::IDENTIFIER;', out)
+
+ return out
+
+
+def memcmp_to_while(out):
+ # It's faster to loop over the keyword with a while loop than calling memcmp.
+ # Careful, this replacement is quite flaky, because otherwise the regex is
+ # unreadable.
+ return checked_sub(
+ re.escape("if (*str == *s && !memcmp (str + 1, s + 1, len - 1))") + r"\s*"
+ + re.escape("return kPerfectKeywordHashTable[key].value;"),
+ """
+ while(*s!=0) {
+ if (*s++ != *str++) return Token::IDENTIFIER;
+ }
+ return kPerfectKeywordHashTable[key].value;
+ """,
+ out,
+ flags=re.MULTILINE)
+
+
+def wrap_namespace(out):
+ return """// 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.
+
+// This file is automatically generated by gen-keywords-gen-h.py and should not
+// be modified manually.
+
+#ifndef V8_PARSING_KEYWORDS_GEN_H_
+#define V8_PARSING_KEYWORDS_GEN_H_
+
+#include "src/parsing/token.h"
+
+namespace v8 {
+namespace internal {
+
+%s
+
+} // namespace internal
+} // namespace v8
+
+#endif // V8_PARSING_KEYWORDS_GEN_H_
+""" % (out)
+
+
+def trim_character_set_warning(out):
+ # gperf generates an error message that is too large, trim it
+
+ return out.replace(
+ '"gperf generated tables don\'t work with this execution character set. Please report a bug to <bug-gperf@gnu.org>."',
+ '"gperf generated tables don\'t work with this execution character set."\\\n// If you see this error, please report a bug to <bug-gperf@gnu.org>.'
+ )
+
+
+def main():
+ try:
+ script_dir = os.path.dirname(sys.argv[0])
+ root_dir = os.path.join(script_dir, '..')
+
+ out = subprocess.check_output(["gperf", "-m100", INPUT_PATH], cwd=root_dir)
+
+ # And now some munging of the generated file.
+ out = change_sizet_to_int(out)
+ out = drop_line_directives(out)
+ out = trim_and_dcheck_char_table(out)
+ out = use_isinrange(out)
+ out = pad_tables(out)
+ out = return_token(out)
+ out = memcmp_to_while(out)
+ out = wrap_namespace(out)
+ out = trim_character_set_warning(out)
+
+ # Final formatting.
+ clang_format_path = os.path.join(root_dir,
+ 'third_party/depot_tools/clang-format')
+ out = call_with_input([clang_format_path], out)
+
+ with open(os.path.join(root_dir, OUTPUT_PATH), 'w') as f:
+ f.write(out)
+
+ return 0
+
+ except subprocess.CalledProcessError as e:
+ sys.stderr.write("Error calling '{}'\n".format(" ".join(e.cmd)))
+ return e.returncode
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/src/v8/tools/gen-postmortem-metadata.py b/src/v8/tools/gen-postmortem-metadata.py
index 043ecc3..2323e8a 100644
--- a/src/v8/tools/gen-postmortem-metadata.py
+++ b/src/v8/tools/gen-postmortem-metadata.py
@@ -46,6 +46,9 @@
# the generated libv8 binary.
#
+# for py2/py3 compatibility
+from __future__ import print_function
+
import re
import sys
@@ -58,6 +61,9 @@
{ 'name': 'APIObjectType', 'value': 'JS_API_OBJECT_TYPE' },
{ 'name': 'SpecialAPIObjectType', 'value': 'JS_SPECIAL_API_OBJECT_TYPE' },
+ { 'name': 'FirstContextType', 'value': 'FIRST_CONTEXT_TYPE' },
+ { 'name': 'LastContextType', 'value': 'LAST_CONTEXT_TYPE' },
+
{ 'name': 'IsNotStringMask', 'value': 'kIsNotStringMask' },
{ 'name': 'StringTag', 'value': 'kStringTag' },
@@ -79,7 +85,6 @@
{ 'name': 'SmiTagMask', 'value': 'kSmiTagMask' },
{ 'name': 'SmiValueShift', 'value': 'kSmiTagSize' },
{ 'name': 'SmiShiftSize', 'value': 'kSmiShiftSize' },
- { 'name': 'PointerSizeLog2', 'value': 'kPointerSizeLog2' },
{ 'name': 'OddballFalse', 'value': 'Oddball::kFalse' },
{ 'name': 'OddballTrue', 'value': 'Oddball::kTrue' },
@@ -91,8 +96,6 @@
{ 'name': 'OddballOther', 'value': 'Oddball::kOther' },
{ 'name': 'OddballException', 'value': 'Oddball::kException' },
- { 'name': 'prop_idx_first',
- 'value': 'DescriptorArray::kFirstIndex' },
{ 'name': 'prop_kind_Data',
'value': 'kData' },
{ 'name': 'prop_kind_Accessor',
@@ -123,26 +126,14 @@
'value': 'PropertyDetails::RepresentationField::kMask' },
{ 'name': 'prop_representation_shift',
'value': 'PropertyDetails::RepresentationField::kShift' },
- { 'name': 'prop_representation_integer8',
- 'value': 'Representation::Kind::kInteger8' },
- { 'name': 'prop_representation_uinteger8',
- 'value': 'Representation::Kind::kUInteger8' },
- { 'name': 'prop_representation_integer16',
- 'value': 'Representation::Kind::kInteger16' },
- { 'name': 'prop_representation_uinteger16',
- 'value': 'Representation::Kind::kUInteger16' },
{ 'name': 'prop_representation_smi',
'value': 'Representation::Kind::kSmi' },
- { 'name': 'prop_representation_integer32',
- 'value': 'Representation::Kind::kInteger32' },
{ 'name': 'prop_representation_double',
'value': 'Representation::Kind::kDouble' },
{ 'name': 'prop_representation_heapobject',
'value': 'Representation::Kind::kHeapObject' },
{ 'name': 'prop_representation_tagged',
'value': 'Representation::Kind::kTagged' },
- { 'name': 'prop_representation_external',
- 'value': 'Representation::Kind::kExternal' },
{ 'name': 'prop_desc_key',
'value': 'DescriptorArray::kEntryKeyIndex' },
@@ -184,25 +175,18 @@
{ 'name': 'scopeinfo_idx_nparams',
'value': 'ScopeInfo::kParameterCount' },
- { 'name': 'scopeinfo_idx_nstacklocals',
- 'value': 'ScopeInfo::kStackLocalCount' },
{ 'name': 'scopeinfo_idx_ncontextlocals',
'value': 'ScopeInfo::kContextLocalCount' },
{ 'name': 'scopeinfo_idx_first_vars',
'value': 'ScopeInfo::kVariablePartIndex' },
- { 'name': 'sharedfunctioninfo_start_position_mask',
- 'value': 'SharedFunctionInfo::StartPositionBits::kMask' },
- { 'name': 'sharedfunctioninfo_start_position_shift',
- 'value': 'SharedFunctionInfo::StartPositionBits::kShift' },
+ { 'name': 'jsarray_buffer_was_detached_mask',
+ 'value': 'JSArrayBuffer::WasDetachedBit::kMask' },
+ { 'name': 'jsarray_buffer_was_detached_shift',
+ 'value': 'JSArrayBuffer::WasDetachedBit::kShift' },
- { 'name': 'jsarray_buffer_was_neutered_mask',
- 'value': 'JSArrayBuffer::WasNeutered::kMask' },
- { 'name': 'jsarray_buffer_was_neutered_shift',
- 'value': 'JSArrayBuffer::WasNeutered::kShift' },
-
- { 'name': 'context_idx_closure',
- 'value': 'Context::CLOSURE_INDEX' },
+ { 'name': 'context_idx_scope_info',
+ 'value': 'Context::SCOPE_INFO_INDEX' },
{ 'name': 'context_idx_native',
'value': 'Context::NATIVE_CONTEXT_INDEX' },
{ 'name': 'context_idx_prev',
@@ -211,6 +195,9 @@
'value': 'Context::EXTENSION_INDEX' },
{ 'name': 'context_min_slots',
'value': 'Context::MIN_CONTEXT_SLOTS' },
+ { 'name': 'native_context_embedder_data_offset',
+ 'value': 'Internals::kNativeContextEmbedderDataOffset' },
+
{ 'name': 'namedictionaryshape_prefix_size',
'value': 'NameDictionaryShape::kPrefixSize' },
@@ -226,6 +213,16 @@
'value': 'NumberDictionaryShape::kPrefixSize' },
{ 'name': 'numberdictionaryshape_entry_size',
'value': 'NumberDictionaryShape::kEntrySize' },
+
+ { 'name': 'simplenumberdictionaryshape_prefix_size',
+ 'value': 'SimpleNumberDictionaryShape::kPrefixSize' },
+ { 'name': 'simplenumberdictionaryshape_entry_size',
+ 'value': 'SimpleNumberDictionaryShape::kEntrySize' },
+
+ { 'name': 'type_JSError__JS_ERROR_TYPE', 'value': 'JS_ERROR_TYPE' },
+
+ { 'name': 'class_SharedFunctionInfo__function_data__Object',
+ 'value': 'SharedFunctionInfo::kFunctionDataOffset' },
];
#
@@ -238,13 +235,15 @@
#
extras_accessors = [
'JSFunction, context, Context, kContextOffset',
+ 'JSFunction, shared, SharedFunctionInfo, kSharedFunctionInfoOffset',
'HeapObject, map, Map, kMapOffset',
'JSObject, elements, Object, kElementsOffset',
'JSObject, internal_fields, uintptr_t, kHeaderSize',
'FixedArray, data, uintptr_t, kHeaderSize',
- 'FixedTypedArrayBase, external_pointer, Object, kExternalPointerOffset',
- 'JSArrayBuffer, backing_store, Object, kBackingStoreOffset',
- 'JSArrayBufferView, byte_offset, Object, kByteOffsetOffset',
+ 'JSArrayBuffer, backing_store, uintptr_t, kBackingStoreOffset',
+ 'JSArrayBuffer, byte_length, size_t, kByteLengthOffset',
+ 'JSArrayBufferView, byte_length, size_t, kByteLengthOffset',
+ 'JSArrayBufferView, byte_offset, size_t, kByteOffsetOffset',
'JSTypedArray, length, Object, kLengthOffset',
'Map, instance_size_in_words, char, kInstanceSizeInWordsOffset',
'Map, inobject_properties_start_or_constructor_function_index, char, kInObjectPropertiesStartOrConstructorFunctionIndexOffset',
@@ -255,22 +254,19 @@
'Map, prototype, Object, kPrototypeOffset',
'Oddball, kind_offset, int, kKindOffset',
'HeapNumber, value, double, kValueOffset',
- 'ConsString, first, String, kFirstOffset',
- 'ConsString, second, String, kSecondOffset',
'ExternalString, resource, Object, kResourceOffset',
'SeqOneByteString, chars, char, kHeaderSize',
'SeqTwoByteString, chars, char, kHeaderSize',
- 'SharedFunctionInfo, code, Code, kCodeOffset',
- 'SharedFunctionInfo, scope_info, ScopeInfo, kScopeInfoOffset',
- 'SharedFunctionInfo, function_token_position, int, kFunctionTokenPositionOffset',
- 'SharedFunctionInfo, start_position_and_type, int, kStartPositionAndTypeOffset',
- 'SharedFunctionInfo, end_position, int, kEndPositionOffset',
- 'SharedFunctionInfo, internal_formal_parameter_count, int, kFormalParameterCountOffset',
- 'SharedFunctionInfo, compiler_hints, int, kCompilerHintsOffset',
- 'SharedFunctionInfo, length, int, kLengthOffset',
+ 'UncompiledData, start_position, int32_t, kStartPositionOffset',
+ 'UncompiledData, end_position, int32_t, kEndPositionOffset',
+ 'SharedFunctionInfo, raw_function_token_offset, int16_t, kFunctionTokenOffsetOffset',
+ 'SharedFunctionInfo, internal_formal_parameter_count, uint16_t, kFormalParameterCountOffset',
+ 'SharedFunctionInfo, flags, int, kFlagsOffset',
+ 'SharedFunctionInfo, length, uint16_t, kLengthOffset',
'SlicedString, parent, String, kParentOffset',
'Code, instruction_start, uintptr_t, kHeaderSize',
'Code, instruction_size, int, kInstructionSizeOffset',
+ 'String, length, int32_t, kLengthOffset',
];
#
@@ -280,8 +276,8 @@
#
expected_classes = [
'ConsString', 'FixedArray', 'HeapNumber', 'JSArray', 'JSFunction',
- 'JSObject', 'JSRegExp', 'JSValue', 'Map', 'Oddball', 'Script',
- 'SeqOneByteString', 'SharedFunctionInfo'
+ 'JSObject', 'JSRegExp', 'JSPrimitiveWrapper', 'Map', 'Oddball', 'Script',
+ 'SeqOneByteString', 'SharedFunctionInfo', 'ScopeInfo', 'JSPromise'
];
@@ -299,12 +295,17 @@
* This file is generated by %s. Do not edit directly.
*/
-#include "src/v8.h"
-#include "src/frames.h"
-#include "src/frames-inl.h" /* for architecture-specific frame constants */
-#include "src/contexts.h"
+#include "src/init/v8.h"
+#include "src/execution/frames.h"
+#include "src/execution/frames-inl.h" /* for architecture-specific frame constants */
+#include "src/objects/contexts.h"
+#include "src/objects/objects.h"
+#include "src/objects/data-handler.h"
+#include "src/objects/js-promise.h"
+#include "src/objects/js-regexp-string-iterator.h"
-using namespace v8::internal;
+namespace v8 {
+namespace internal {
extern "C" {
@@ -320,6 +321,9 @@
footer = '''
}
+
+}
+}
'''
#
@@ -364,6 +368,7 @@
in_insttype = False;
typestr = '';
+ uncommented_file = ''
#
# Iterate the header file line-by-line to collect type and class
@@ -386,15 +391,26 @@
typestr += line;
continue;
- match = re.match('class (\w[^:]*)(: public (\w[^{]*))?\s*{\s*',
- line);
+ uncommented_file += '\n' + line
- if (match):
- klass = match.group(1).strip();
- pklass = match.group(3);
- if (pklass):
- pklass = pklass.strip();
- klasses[klass] = { 'parent': pklass };
+ for match in re.finditer(r'\nclass(?:\s+V8_EXPORT(?:_PRIVATE)?)?'
+ r'\s+(\w[^:;]*)'
+ r'(?:: public (\w[^{]*))?\s*{\s*',
+ uncommented_file):
+ klass = match.group(1).strip();
+ pklass = match.group(2);
+ if (pklass):
+ # Check for generated Torque class.
+ gen_match = re.match(
+ r'TorqueGenerated\w+\s*<\s*\w+,\s*(\w+)\s*>',
+ pklass)
+ if (gen_match):
+ pklass = gen_match.group(1)
+ # Strip potential template arguments from parent
+ # class.
+ match = re.match(r'(\w+)(<.*>)?', pklass.strip());
+ pklass = match.group(1).strip();
+ klasses[klass] = { 'parent': pklass };
#
# Process the instance type declaration.
@@ -412,14 +428,9 @@
#
for type in types:
#
- # Symbols and Strings are implemented using the same classes.
- #
- usetype = re.sub('SYMBOL_', 'STRING_', type);
-
- #
# REGEXP behaves like REG_EXP, as in JS_REGEXP_TYPE => JSRegExp.
#
- usetype = re.sub('_REGEXP_', '_REG_EXP_', usetype);
+ usetype = re.sub('_REGEXP_', '_REG_EXP_', type);
#
# Remove the "_TYPE" suffix and then convert to camel case,
@@ -513,7 +524,8 @@
consts = [];
- if (kind == 'ACCESSORS' or kind == 'ACCESSORS_GCSAFE'):
+ if (kind == 'ACCESSORS' or kind == 'ACCESSORS2' or
+ kind == 'ACCESSORS_GCSAFE'):
klass = args[0];
field = args[1];
dtype = args[2].replace('<', '_').replace('>', '_')
@@ -556,7 +568,7 @@
# may span multiple lines and may contain nested parentheses. We also
# call parse_field() to pick apart the invocation.
#
- prefixes = [ 'ACCESSORS', 'ACCESSORS_GCSAFE',
+ prefixes = [ 'ACCESSORS', 'ACCESSORS2', 'ACCESSORS_GCSAFE',
'SMI_ACCESSORS', 'ACCESSORS_TO_SMI' ];
current = '';
opens = 0;
@@ -616,7 +628,7 @@
# Emit the whole output file.
#
def emit_config():
- out = file(sys.argv[1], 'w');
+ out = open(sys.argv[1], 'w');
out.write(header);
diff --git a/src/v8/tools/generate-builtins-tests.py b/src/v8/tools/generate-builtins-tests.py
index 4e6961d..4380f91 100755
--- a/src/v8/tools/generate-builtins-tests.py
+++ b/src/v8/tools/generate-builtins-tests.py
@@ -3,6 +3,9 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+# for py2/py3 compatibility
+from __future__ import print_function
+
import json
import optparse
import os
diff --git a/src/v8/tools/generate-header-include-checks.py b/src/v8/tools/generate-header-include-checks.py
new file mode 100755
index 0000000..fa18d85
--- /dev/null
+++ b/src/v8/tools/generate-header-include-checks.py
@@ -0,0 +1,148 @@
+#!/usr/bin/env python
+# vim:fenc=utf-8:shiftwidth=2
+
+# 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.
+
+"""Check that each header can be included in isolation.
+
+For each header we generate one .cc file which only includes this one header.
+All these .cc files are then added to a sources.gni file which is included in
+BUILD.gn. Just compile to check whether there are any violations to the rule
+that each header must be includable in isolation.
+"""
+
+# for py2/py3 compatibility
+from __future__ import print_function
+
+import argparse
+import os
+import os.path
+import re
+import sys
+
+# TODO(clemensh): Extend to tests.
+DEFAULT_INPUT = ['base', 'src']
+DEFAULT_GN_FILE = 'BUILD.gn'
+MY_DIR = os.path.dirname(os.path.realpath(__file__))
+V8_DIR = os.path.dirname(MY_DIR)
+OUT_DIR = os.path.join(V8_DIR, 'check-header-includes')
+AUTO_EXCLUDE = [
+ # flag-definitions.h needs a mode set for being included.
+ 'src/flags/flag-definitions.h',
+]
+AUTO_EXCLUDE_PATTERNS = [
+ 'src/base/atomicops_internals_.*',
+ # TODO(petermarshall): Enable once Perfetto is built by default.
+ 'src/libplatform/tracing/perfetto*',
+] + [
+ # platform-specific headers
+ '\\b{}\\b'.format(p) for p in
+ ('win', 'win32', 'ia32', 'x64', 'arm', 'arm64', 'mips', 'mips64', 's390',
+ 'ppc')]
+
+args = None
+def parse_args():
+ global args
+ parser = argparse.ArgumentParser()
+ parser.add_argument('-i', '--input', type=str, action='append',
+ help='Headers or directories to check (directories '
+ 'are scanned for headers recursively); default: ' +
+ ','.join(DEFAULT_INPUT))
+ parser.add_argument('-x', '--exclude', type=str, action='append',
+ help='Add an exclude pattern (regex)')
+ parser.add_argument('-v', '--verbose', action='store_true',
+ help='Be verbose')
+ args = parser.parse_args()
+ args.exclude = (args.exclude or []) + AUTO_EXCLUDE_PATTERNS
+ args.exclude += ['^' + re.escape(x) + '$' for x in AUTO_EXCLUDE]
+ if not args.input:
+ args.input=DEFAULT_INPUT
+
+
+def printv(line):
+ if args.verbose:
+ print(line)
+
+
+def find_all_headers():
+ printv('Searching for headers...')
+ header_files = []
+ exclude_patterns = [re.compile(x) for x in args.exclude]
+ def add_recursively(filename):
+ full_name = os.path.join(V8_DIR, filename)
+ if not os.path.exists(full_name):
+ sys.exit('File does not exist: {}'.format(full_name))
+ if os.path.isdir(full_name):
+ for subfile in os.listdir(full_name):
+ full_name = os.path.join(filename, subfile)
+ printv('Scanning {}'.format(full_name))
+ add_recursively(full_name)
+ elif filename.endswith('.h'):
+ printv('--> Found header file {}'.format(filename))
+ for p in exclude_patterns:
+ if p.search(filename):
+ printv('--> EXCLUDED (matches {})'.format(p.pattern))
+ return
+ header_files.append(filename)
+
+ for filename in args.input:
+ add_recursively(filename)
+
+ return header_files
+
+
+def get_cc_file_name(header):
+ split = os.path.split(header)
+ header_dir = os.path.relpath(split[0], V8_DIR)
+ # Prefix with the directory name, to avoid collisions in the object files.
+ prefix = header_dir.replace(os.path.sep, '-')
+ cc_file_name = 'test-include-' + prefix + '-' + split[1][:-1] + 'cc'
+ return os.path.join(OUT_DIR, cc_file_name)
+
+
+def create_including_cc_files(header_files):
+ comment = 'check including this header in isolation'
+ for header in header_files:
+ cc_file_name = get_cc_file_name(header)
+ rel_cc_file_name = os.path.relpath(cc_file_name, V8_DIR)
+ content = '#include "{}" // {}\n'.format(header, comment)
+ if os.path.exists(cc_file_name):
+ with open(cc_file_name) as cc_file:
+ if cc_file.read() == content:
+ printv('File {} is up to date'.format(rel_cc_file_name))
+ continue
+ printv('Creating file {}'.format(rel_cc_file_name))
+ with open(cc_file_name, 'w') as cc_file:
+ cc_file.write(content)
+
+
+def generate_gni(header_files):
+ gni_file = os.path.join(OUT_DIR, 'sources.gni')
+ printv('Generating file "{}"'.format(os.path.relpath(gni_file, V8_DIR)))
+ with open(gni_file, 'w') as gn:
+ gn.write("""\
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# This list is filled automatically by tools/check_header_includes.py.
+check_header_includes_sources = [
+""");
+ for header in header_files:
+ cc_file_name = get_cc_file_name(header)
+ gn.write(' "{}",\n'.format(os.path.relpath(cc_file_name, V8_DIR)))
+ gn.write(']\n')
+
+
+def main():
+ parse_args()
+ header_files = find_all_headers()
+ if not os.path.exists(OUT_DIR):
+ os.mkdir(OUT_DIR)
+ create_including_cc_files(header_files)
+ generate_gni(header_files)
+
+if __name__ == '__main__':
+ main()
diff --git a/src/v8/tools/get_landmines.py b/src/v8/tools/get_landmines.py
new file mode 100755
index 0000000..bf8efa5
--- /dev/null
+++ b/src/v8/tools/get_landmines.py
@@ -0,0 +1,61 @@
+#!/usr/bin/env python
+# Copyright 2014 the V8 project authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""
+This file emits the list of reasons why a particular build needs to be clobbered
+(or a list of 'landmines').
+"""
+
+# for py2/py3 compatibility
+from __future__ import print_function
+
+import os
+import sys
+
+sys.path.insert(0, os.path.abspath(
+ os.path.join(os.path.dirname(__file__), '..', 'build')))
+
+import get_landmines as build_get_landmines
+
+
+def print_landmines(): # pylint: disable=invalid-name
+ """
+ ALL LANDMINES ARE EMITTED FROM HERE.
+ """
+ # DO NOT add landmines as part of a regular CL. Landmines are a last-effort
+ # bandaid fix if a CL that got landed has a build dependency bug and all bots
+ # need to be cleaned up. If you're writing a new CL that causes build
+ # dependency problems, fix the dependency problems instead of adding a
+ # landmine.
+ # See the Chromium version in src/build/get_landmines.py for usage examples.
+ print('Need to clobber after ICU52 roll.')
+ print('Landmines test.')
+ print('Activating MSVS 2013.')
+ print('Revert activation of MSVS 2013.')
+ print('Activating MSVS 2013 again.')
+ print('Clobber after ICU roll.')
+ print('Moar clobbering...')
+ print('Remove build/android.gypi')
+ print('Cleanup after windows ninja switch attempt.')
+ print('Switching to pinned msvs toolchain.')
+ print('Clobbering to hopefully resolve problem with mksnapshot')
+ print('Clobber after ICU roll.')
+ print('Clobber after Android NDK update.')
+ print('Clober to fix windows build problems.')
+ print('Clober again to fix windows build problems.')
+ print('Clobber to possibly resolve failure on win-32 bot.')
+ print('Clobber for http://crbug.com/668958.')
+ print('Clobber to possibly resolve build failure on Misc V8 Linux gcc.')
+ build_get_landmines.print_landmines()
+ return 0
+
+
+def main():
+ print_landmines()
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/src/v8/tools/grokdump.py b/src/v8/tools/grokdump.py
index 5d9ffff..773622d 100755
--- a/src/v8/tools/grokdump.py
+++ b/src/v8/tools/grokdump.py
@@ -27,6 +27,12 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# flake8: noqa # https://bugs.chromium.org/p/v8/issues/detail?id=8784
+
+
+# for py2/py3 compatibility
+from __future__ import print_function
+
import BaseHTTPServer
import bisect
import cgi
@@ -69,7 +75,7 @@
def DebugPrint(s):
if not DEBUG: return
- print s
+ print(s)
class Descriptor(object):
@@ -120,7 +126,7 @@
def FullDump(reader, heap):
"""Dump all available memory regions."""
def dump_region(reader, start, size, location):
- print
+ print()
while start & 3 != 0:
start += 1
size -= 1
@@ -131,17 +137,17 @@
if is_executable is not False:
lines = reader.GetDisasmLines(start, size)
for line in lines:
- print FormatDisasmLine(start, heap, line)
- print
+ print(FormatDisasmLine(start, heap, line))
+ print()
if is_ascii is not False:
# Output in the same format as the Unix hd command
addr = start
- for i in xrange(0, size, 16):
+ for i in range(0, size, 16):
slot = i + location
hex_line = ""
asc_line = ""
- for i in xrange(16):
+ for i in range(16):
if slot + i < location + size:
byte = ctypes.c_uint8.from_buffer(reader.minidump, slot + i).value
if byte >= 0x20 and byte < 0x7f:
@@ -153,24 +159,24 @@
hex_line += " "
if i == 7:
hex_line += " "
- print "%s %s |%s|" % (reader.FormatIntPtr(addr),
+ print("%s %s |%s|" % (reader.FormatIntPtr(addr),
hex_line,
- asc_line)
+ asc_line))
addr += 16
if is_executable is not True and is_ascii is not True:
- print "%s - %s" % (reader.FormatIntPtr(start),
- reader.FormatIntPtr(start + size))
- print start + size + 1;
- for i in xrange(0, size, reader.PointerSize()):
+ print("%s - %s" % (reader.FormatIntPtr(start),
+ reader.FormatIntPtr(start + size)))
+ print(start + size + 1);
+ for i in range(0, size, reader.PointerSize()):
slot = start + i
maybe_address = reader.ReadUIntPtr(slot)
heap_object = heap.FindObject(maybe_address)
- print "%s: %s" % (reader.FormatIntPtr(slot),
- reader.FormatIntPtr(maybe_address))
+ print("%s: %s" % (reader.FormatIntPtr(slot),
+ reader.FormatIntPtr(maybe_address)))
if heap_object:
heap_object.Print(Printer())
- print
+ print()
reader.ForEachMemoryRegion(dump_region)
@@ -185,6 +191,10 @@
(0xbbbbbbbb, 0xbbbbbbbb),
(0xfefefefe, 0xfefefeff),
)
+# See StackTraceFailureMessage in isolate.h
+STACK_TRACE_MARKER = 0xdecade30
+# See FailureMessage in logging.cc
+ERROR_MESSAGE_MARKER = 0xdecade10
# Set of structures and constants that describe the layout of minidump
# files. Based on MSDN and Google Breakpad.
@@ -576,7 +586,10 @@
MD_CPU_ARCHITECTURE_X86 = 0
MD_CPU_ARCHITECTURE_ARM = 5
-MD_CPU_ARCHITECTURE_ARM64 = 0x8003
+# Breakpad used a custom value of 0x8003 here; Crashpad uses the new
+# standardized value 12.
+MD_CPU_ARCHITECTURE_ARM64 = 12
+MD_CPU_ARCHITECTURE_ARM64_BREAKPAD_LEGACY = 0x8003
MD_CPU_ARCHITECTURE_AMD64 = 9
OBJDUMP_BIN = None
@@ -607,11 +620,11 @@
self.minidump = mmap.mmap(self.minidump_file.fileno(), 0, mmap.MAP_PRIVATE)
self.header = MINIDUMP_HEADER.Read(self.minidump, 0)
if self.header.signature != MinidumpReader._HEADER_MAGIC:
- print >>sys.stderr, "Warning: Unsupported minidump header magic!"
+ print("Warning: Unsupported minidump header magic!", file=sys.stderr)
DebugPrint(self.header)
directories = []
offset = self.header.stream_directories_rva
- for _ in xrange(self.header.stream_count):
+ for _ in range(self.header.stream_count):
directories.append(MINIDUMP_DIRECTORY.Read(self.minidump, offset))
offset += MINIDUMP_DIRECTORY.size
self.arch = None
@@ -637,6 +650,8 @@
system_info = MINIDUMP_RAW_SYSTEM_INFO.Read(
self.minidump, d.location.rva)
self.arch = system_info.processor_architecture
+ if self.arch == MD_CPU_ARCHITECTURE_ARM64_BREAKPAD_LEGACY:
+ self.arch = MD_CPU_ARCHITECTURE_ARM64
assert self.arch in [MD_CPU_ARCHITECTURE_AMD64,
MD_CPU_ARCHITECTURE_ARM,
MD_CPU_ARCHITECTURE_ARM64,
@@ -673,7 +688,7 @@
assert ctypes.sizeof(self.module_list) == d.location.data_size
DebugPrint(self.module_list)
elif d.stream_type == MD_MEMORY_LIST_STREAM:
- print >>sys.stderr, "Warning: This is not a full minidump!"
+ print("Warning: This is not a full minidump!", file=sys.stderr)
assert self.memory_list is None
self.memory_list = MINIDUMP_MEMORY_LIST.Read(
self.minidump, d.location.rva)
@@ -695,8 +710,8 @@
else:
objdump_bin = self._FindThirdPartyObjdump()
if not objdump_bin or not os.path.exists(objdump_bin):
- print "# Cannot find '%s', falling back to default objdump '%s'" % (
- objdump_bin, DEFAULT_OBJDUMP_BIN)
+ print("# Cannot find '%s', falling back to default objdump '%s'" % (
+ objdump_bin, DEFAULT_OBJDUMP_BIN))
objdump_bin = DEFAULT_OBJDUMP_BIN
global OBJDUMP_BIN
OBJDUMP_BIN = objdump_bin
@@ -718,12 +733,12 @@
else:
# use default otherwise
return None
- print ("# Looking for platform specific (%s) objdump in "
- "third_party directory.") % platform_filter
+ print(("# Looking for platform specific (%s) objdump in "
+ "third_party directory.") % platform_filter)
objdumps = filter(lambda file: platform_filter in file >= 0, objdumps)
if len(objdumps) == 0:
- print "# Could not find platform specific objdump in third_party."
- print "# Make sure you installed the correct SDK."
+ print("# Could not find platform specific objdump in third_party.")
+ print("# Make sure you installed the correct SDK.")
return None
return objdumps[0]
@@ -757,7 +772,7 @@
def IsValidExceptionStackAddress(self, address):
if not self.IsValidAddress(address): return False
- return self.isExceptionStackAddress(address)
+ return self.IsExceptionStackAddress(address)
def IsModuleAddress(self, address):
return self.GetModuleForAddress(address) != None
@@ -818,7 +833,7 @@
def IsProbableASCIIRegion(self, location, length):
ascii_bytes = 0
non_ascii_bytes = 0
- for i in xrange(length):
+ for i in range(length):
loc = location + i
byte = ctypes.c_uint8.from_buffer(self.minidump, loc).value
if byte >= 0x7f:
@@ -840,7 +855,7 @@
def IsProbableExecutableRegion(self, location, length):
opcode_bytes = 0
sixty_four = self.Is64()
- for i in xrange(length):
+ for i in range(length):
loc = location + i
byte = ctypes.c_uint8.from_buffer(self.minidump, loc).value
if (byte == 0x8b or # mov
@@ -889,19 +904,19 @@
def FindWord(self, word, alignment=0):
def search_inside_region(reader, start, size, location):
location = (location + alignment) & ~alignment
- for i in xrange(size - self.PointerSize()):
+ for i in range(size - self.PointerSize()):
loc = location + i
if reader._ReadWord(loc) == word:
slot = start + (loc - location)
- print "%s: %s" % (reader.FormatIntPtr(slot),
- reader.FormatIntPtr(word))
+ print("%s: %s" % (reader.FormatIntPtr(slot),
+ reader.FormatIntPtr(word)))
self.ForEachMemoryRegion(search_inside_region)
def FindWordList(self, word):
aligned_res = []
unaligned_res = []
def search_inside_region(reader, start, size, location):
- for i in xrange(size - self.PointerSize()):
+ for i in range(size - self.PointerSize()):
loc = location + i
if reader._ReadWord(loc) == word:
slot = start + (loc - location)
@@ -1022,7 +1037,7 @@
# http://code.google.com/p/google-breakpad/wiki/SymbolFiles
#
def _LoadSymbolsFrom(self, symfile, baseaddr):
- print "Loading symbols from %s" % (symfile)
+ print("Loading symbols from %s" % (symfile))
funcs = []
with open(symfile) as f:
for line in f:
@@ -1034,7 +1049,7 @@
name = result.group(4).rstrip()
bisect.insort_left(self.symbols,
FuncSymbol(baseaddr + start, size, name))
- print " ... done"
+ print(" ... done")
def TryLoadSymbolsFor(self, modulename, module):
try:
@@ -1044,7 +1059,7 @@
self._LoadSymbolsFrom(symfile, module.base_of_image)
self.modules_with_symbols.append(module)
except Exception as e:
- print " ... failure (%s)" % (e)
+ print(" ... failure (%s)" % (e))
# Returns true if address is covered by some module that has loaded symbols.
def _IsInModuleWithSymbols(self, addr):
@@ -1086,11 +1101,11 @@
self.indent -= 2
def Print(self, string):
- print "%s%s" % (self._IndentString(), string)
+ print("%s%s" % (self._IndentString(), string))
def PrintLines(self, lines):
indent = self._IndentString()
- print "\n".join("%s%s" % (indent, line) for line in lines)
+ print("\n".join("%s%s" % (indent, line) for line in lines))
def _IndentString(self):
return self.indent * " "
@@ -1227,9 +1242,6 @@
def DependentCodeOffset(self):
return self.CodeCacheOffset() + self.heap.PointerSize()
- def WeakCellCacheOffset(self):
- return self.DependentCodeOffset() + self.heap.PointerSize()
-
def ReadByte(self, offset):
return self.heap.reader.ReadU8(self.address + offset)
@@ -1427,7 +1439,7 @@
p.Indent()
p.Print("length: %d" % self.length)
base_offset = self.ElementsOffset()
- for i in xrange(self.length):
+ for i in range(self.length):
offset = base_offset + 4 * i
try:
p.Print("[%08d] = %s" % (i, self.ObjectField(offset)))
@@ -1497,7 +1509,7 @@
p.Print("Descriptors(%08x, length=%d)" % (array.address, length))
p.Print("[et] %s" % (array.Get(1)))
- for di in xrange(length):
+ for di in range(length):
i = 2 + di * 3
p.Print("0x%x" % (array.address + array.MemberOffset(i)))
p.Print("[%i] name: %s" % (di, array.Get(i + 0)))
@@ -1542,7 +1554,7 @@
if prototype is not None:
p.Print("[prototype ] %s" % (prototype))
- for di in xrange(length):
+ for di in range(length):
i = 3 + di * 2
p.Print("[%i] symbol: %s" % (di, array.Get(i + 0)))
p.Print("[%i] target: %s" % (di, array.Get(i + 1)))
@@ -1714,9 +1726,9 @@
"EXTERNAL_SYMBOL_TYPE": ExternalString,
"EXTERNAL_SYMBOL_WITH_ONE_BYTE_DATA_TYPE": ExternalString,
"EXTERNAL_ONE_BYTE_SYMBOL_TYPE": ExternalString,
- "SHORT_EXTERNAL_SYMBOL_TYPE": ExternalString,
- "SHORT_EXTERNAL_SYMBOL_WITH_ONE_BYTE_DATA_TYPE": ExternalString,
- "SHORT_EXTERNAL_ONE_BYTE_SYMBOL_TYPE": ExternalString,
+ "UNCACHED_EXTERNAL_SYMBOL_TYPE": ExternalString,
+ "UNCACHED_EXTERNAL_SYMBOL_WITH_ONE_BYTE_DATA_TYPE": ExternalString,
+ "UNCACHED_EXTERNAL_ONE_BYTE_SYMBOL_TYPE": ExternalString,
"STRING_TYPE": SeqString,
"ONE_BYTE_STRING_TYPE": SeqString,
"CONS_STRING_TYPE": ConsString,
@@ -1728,6 +1740,8 @@
"ODDBALL_TYPE": Oddball,
"FIXED_ARRAY_TYPE": FixedArray,
"HASH_TABLE_TYPE": FixedArray,
+ "OBJECT_BOILERPLATE_DESCRIPTION_TYPE": FixedArray,
+ "SCOPE_INFO_TYPE": FixedArray,
"JS_FUNCTION_TYPE": JSFunction,
"SHARED_FUNCTION_INFO_TYPE": SharedFunctionInfo,
"SCRIPT_TYPE": Script,
@@ -1938,10 +1952,10 @@
exception_thread.stack.memory.data_size
frame_pointer = self.reader.ExceptionFP()
self.styles[frame_pointer] = "frame"
- for slot in xrange(stack_top, stack_bottom, self.reader.PointerSize()):
+ for slot in range(stack_top, stack_bottom, self.reader.PointerSize()):
# stack address
self.styles[slot] = "sa"
- for slot in xrange(stack_top, stack_bottom, self.reader.PointerSize()):
+ for slot in range(stack_top, stack_bottom, self.reader.PointerSize()):
maybe_address = self.reader.ReadUIntPtr(slot)
# stack value
self.styles[maybe_address] = "sv"
@@ -2057,7 +2071,8 @@
def SenseMap(self, tagged_address):
if self.IsInKnownMapSpace(tagged_address):
offset = self.GetPageOffset(tagged_address)
- known_map_info = KNOWN_MAPS.get(offset)
+ lookup_key = ("MAP_SPACE", offset)
+ known_map_info = KNOWN_MAPS.get(lookup_key)
if known_map_info:
known_map_type, known_map_name = known_map_info
return KnownMap(self, known_map_name, known_map_type)
@@ -2083,15 +2098,15 @@
raise NotImplementedError
def PrintKnowledge(self):
- print " known_first_map_page = %s\n"\
+ print(" known_first_map_page = %s\n"\
" known_first_old_page = %s" % (
self.reader.FormatIntPtr(self.known_first_map_page),
- self.reader.FormatIntPtr(self.known_first_old_page))
+ self.reader.FormatIntPtr(self.known_first_old_page)))
def FindFirstAsciiString(self, start, end=None, min_length=32):
""" Walk the memory until we find a large string """
if not end: end = start + 64
- for slot in xrange(start, end):
+ for slot in range(start, end):
if not self.reader.IsValidAddress(slot): break
message = self.reader.ReadAsciiString(slot)
if len(message) > min_length:
@@ -2105,22 +2120,75 @@
"""
# Only look at the first 1k words on the stack
ptr_size = self.reader.PointerSize()
- if start is None:
- start = self.reader.ExceptionSP()
+ if start is None: start = self.reader.ExceptionSP()
if not self.reader.IsValidAddress(start): return start
end = start + ptr_size * 1024 * 4
- message_start = 0
magic1 = None
- for slot in xrange(start, end, ptr_size):
+ for slot in range(start, end, ptr_size):
if not self.reader.IsValidAddress(slot + ptr_size): break
magic1 = self.reader.ReadUIntPtr(slot)
magic2 = self.reader.ReadUIntPtr(slot + ptr_size)
pair = (magic1 & 0xFFFFFFFF, magic2 & 0xFFFFFFFF)
if pair in MAGIC_MARKER_PAIRS:
- message_slot = slot + ptr_size * 4
- message_start = self.reader.ReadUIntPtr(message_slot)
- break
- if message_start == 0:
+ return self.TryExtractOldStyleStackTrace(slot, start, end,
+ print_message)
+ if pair[0] == STACK_TRACE_MARKER:
+ return self.TryExtractStackTrace(slot, start, end, print_message)
+ elif pair[0] == ERROR_MESSAGE_MARKER:
+ return self.TryExtractErrorMessage(slot, start, end, print_message)
+ # Simple fallback in case not stack trace object was found
+ return self.TryExtractOldStyleStackTrace(0, start, end,
+ print_message)
+
+ def TryExtractStackTrace(self, slot, start, end, print_message):
+ ptr_size = self.reader.PointerSize()
+ assert self.reader.ReadUIntPtr(slot) & 0xFFFFFFFF == STACK_TRACE_MARKER
+ end_marker = STACK_TRACE_MARKER + 1;
+ header_size = 10
+ # Look for the end marker after the fields and the message buffer.
+ end_search = start + (32 * 1024) + (header_size * ptr_size);
+ end_slot = self.FindPtr(end_marker, end_search, end_search + ptr_size * 512)
+ if not end_slot: return start
+ print("Stack Message (start=%s):" % self.heap.FormatIntPtr(slot))
+ slot += ptr_size
+ for name in ("isolate","ptr1", "ptr2", "ptr3", "ptr4", "codeObject1",
+ "codeObject2", "codeObject3", "codeObject4"):
+ value = self.reader.ReadUIntPtr(slot)
+ print(" %s: %s" % (name.rjust(14), self.heap.FormatIntPtr(value)))
+ slot += ptr_size
+ print(" message start: %s" % self.heap.FormatIntPtr(slot))
+ stack_start = end_slot + ptr_size
+ print(" stack_start: %s" % self.heap.FormatIntPtr(stack_start))
+ (message_start, message) = self.FindFirstAsciiString(slot)
+ self.FormatStackTrace(message, print_message)
+ return stack_start
+
+ def FindPtr(self, expected_value, start, end):
+ ptr_size = self.reader.PointerSize()
+ for slot in range(start, end, ptr_size):
+ if not self.reader.IsValidAddress(slot): return None
+ value = self.reader.ReadUIntPtr(slot)
+ if value == expected_value: return slot
+ return None
+
+ def TryExtractErrorMessage(self, slot, start, end, print_message):
+ ptr_size = self.reader.PointerSize()
+ end_marker = ERROR_MESSAGE_MARKER + 1;
+ header_size = 1
+ end_search = start + 1024 + (header_size * ptr_size);
+ end_slot = self.FindPtr(end_marker, end_search, end_search + ptr_size * 512)
+ if not end_slot: return start
+ print("Error Message (start=%s):" % self.heap.FormatIntPtr(slot))
+ slot += ptr_size
+ (message_start, message) = self.FindFirstAsciiString(slot)
+ self.FormatStackTrace(message, print_message)
+ stack_start = end_slot + ptr_size
+ return stack_start
+
+ def TryExtractOldStyleStackTrace(self, message_slot, start, end,
+ print_message):
+ ptr_size = self.reader.PointerSize()
+ if message_slot == 0:
"""
On Mac we don't always get proper magic markers, so just try printing
the first long ascii string found on the stack.
@@ -2130,29 +2198,35 @@
message_start, message = self.FindFirstAsciiString(start, end, 128)
if message_start is None: return start
else:
+ message_start = self.reader.ReadUIntPtr(message_slot + ptr_size * 4)
message = self.reader.ReadAsciiString(message_start)
stack_start = message_start + len(message) + 1
# Make sure the address is word aligned
stack_start = stack_start - (stack_start % ptr_size)
if magic1 is None:
- print "Stack Message:"
- print " message start: %s" % self.heap.FormatIntPtr(message_start)
- print " stack_start: %s" % self.heap.FormatIntPtr(stack_start )
+ print("Stack Message:")
+ print(" message start: %s" % self.heap.FormatIntPtr(message_start))
+ print(" stack_start: %s" % self.heap.FormatIntPtr(stack_start ))
else:
ptr1 = self.reader.ReadUIntPtr(slot + ptr_size * 2)
ptr2 = self.reader.ReadUIntPtr(slot + ptr_size * 3)
- print "Stack Message:"
- print " magic1: %s" % self.heap.FormatIntPtr(magic1)
- print " magic2: %s" % self.heap.FormatIntPtr(magic2)
- print " ptr1: %s" % self.heap.FormatIntPtr(ptr1)
- print " ptr2: %s" % self.heap.FormatIntPtr(ptr2)
- print " message start: %s" % self.heap.FormatIntPtr(message_start)
- print " stack_start: %s" % self.heap.FormatIntPtr(stack_start )
- print ""
+ print("Stack Message:")
+ print(" magic1: %s" % self.heap.FormatIntPtr(magic1))
+ print(" magic2: %s" % self.heap.FormatIntPtr(magic2))
+ print(" ptr1: %s" % self.heap.FormatIntPtr(ptr1))
+ print(" ptr2: %s" % self.heap.FormatIntPtr(ptr2))
+ print(" message start: %s" % self.heap.FormatIntPtr(message_start))
+ print(" stack_start: %s" % self.heap.FormatIntPtr(stack_start ))
+ print("")
+ self.FormatStackTrace(message, print_message)
+ return stack_start
+
+ def FormatStackTrace(self, message, print_message):
if not print_message:
- print " Use `dsa` to print the message with annotated addresses."
- print ""
- return stack_start
+ print(" Use `dsa` to print the message with annotated addresses.")
+ print("")
+ return
+ ptr_size = self.reader.PointerSize()
# Annotate all addresses in the dumped message
prog = re.compile("[0-9a-fA-F]{%s}" % ptr_size*2)
addresses = list(set(prog.findall(message)))
@@ -2161,12 +2235,12 @@
address = self.heap.FormatIntPtr(int(address_org, 16))
if address_org != address:
message = message.replace(address_org, address)
- print "Message:"
- print "="*80
- print message
- print "="*80
- print ""
- return stack_start
+ print("Message:")
+ print("="*80)
+ print(message)
+ print("="*80)
+ print("")
+
def TryInferFramePointer(self, slot, address):
""" Assume we have a framepointer if we find 4 consecutive links """
@@ -2216,9 +2290,9 @@
free_space_end = 0
ptr_size = self.reader.PointerSize()
- for slot in xrange(start, end, ptr_size):
+ for slot in range(start, end, ptr_size):
if not self.reader.IsValidAddress(slot):
- print "%s: Address is not contained within the minidump!" % slot
+ print("%s: Address is not contained within the minidump!" % slot)
return
maybe_address = self.reader.ReadUIntPtr(slot)
address_info = []
@@ -2276,17 +2350,17 @@
frame_pointer = maybe_address
address_type_marker = self.heap.AddressTypeMarker(maybe_address)
string_value = self.reader.ReadAsciiPtr(slot)
- print "%s: %s %s %s %s" % (self.reader.FormatIntPtr(slot),
+ print("%s: %s %s %s %s" % (self.reader.FormatIntPtr(slot),
self.reader.FormatIntPtr(maybe_address),
address_type_marker,
string_value,
- ' | '.join(address_info))
+ ' | '.join(address_info)))
if maybe_address_contents == 0xdecade01:
in_oom_dump_area = False
heap_object = self.heap.FindObject(maybe_address)
if heap_object:
heap_object.Print(Printer())
- print ""
+ print("")
WEB_HEADER = """
<!DOCTYPE html>
@@ -2638,7 +2712,7 @@
stack_bottom = exception_thread.stack.start + \
exception_thread.stack.memory.data_size
stack_map = {self.reader.ExceptionIP(): -1}
- for slot in xrange(stack_top, stack_bottom, self.reader.PointerSize()):
+ for slot in range(stack_top, stack_bottom, self.reader.PointerSize()):
maybe_address = self.reader.ReadUIntPtr(slot)
if not maybe_address in stack_map:
stack_map[maybe_address] = slot
@@ -2656,7 +2730,7 @@
address = int(straddress, 0)
self.comments.set_comment(address, comment)
except ValueError:
- print "Invalid address"
+ print("Invalid address")
def set_page_address(self, kind, straddress):
try:
@@ -2667,7 +2741,7 @@
self.padawan.known_first_map_page = address
self.comments.save_page_address(kind, address)
except ValueError:
- print "Invalid address"
+ print("Invalid address")
def td_from_address(self, f, address):
f.write("<td %s>" % self.comments.get_style_class_string(address))
@@ -2790,7 +2864,7 @@
if details == InspectionWebFormatter.CONTEXT_FULL:
if self.reader.exception.exception.parameter_count > 0:
f.write(" Exception parameters: ")
- for i in xrange(0, self.reader.exception.exception.parameter_count):
+ for i in range(0, self.reader.exception.exception.parameter_count):
f.write("%08x" % self.reader.exception.exception.information[i])
f.write("<br><br>")
@@ -2866,19 +2940,19 @@
f.write('<div class="code">')
f.write("<table class=codedump>")
- for j in xrange(0, end_address - start_address, size):
+ for j in range(0, end_address - start_address, size):
slot = start_address + j
heap_object = ""
maybe_address = None
end_region = region[0] + region[1]
if slot < region[0] or slot + size > end_region:
straddress = "0x"
- for i in xrange(end_region, slot + size):
+ for i in range(end_region, slot + size):
straddress += "??"
for i in reversed(
- xrange(max(slot, region[0]), min(slot + size, end_region))):
+ range(max(slot, region[0]), min(slot + size, end_region))):
straddress += "%02x" % self.reader.ReadU8(i)
- for i in xrange(slot, region[0]):
+ for i in range(slot, region[0]):
straddress += "??"
else:
maybe_address = self.reader.ReadUIntPtr(slot)
@@ -2940,7 +3014,7 @@
start = self.align_down(start_address, line_width)
- for i in xrange(end_address - start):
+ for i in range(end_address - start):
address = start + i
if address % 64 == 0:
if address != start:
@@ -3010,7 +3084,7 @@
(start_address, end_address, highlight_address, expand))
f.write('<div class="code">')
f.write("<table class=\"codedump\">");
- for i in xrange(len(lines)):
+ for i in range(len(lines)):
line = lines[i]
next_address = count
if i + 1 < len(lines):
@@ -3257,7 +3331,7 @@
class InspectionWebServer(BaseHTTPServer.HTTPServer):
def __init__(self, port_number, switches, minidump_name):
BaseHTTPServer.HTTPServer.__init__(
- self, ('', port_number), InspectionWebHandler)
+ self, ('localhost', port_number), InspectionWebHandler)
splitpath = os.path.split(minidump_name)
self.dumppath = splitpath[0]
self.dumpfilename = splitpath[1]
@@ -3386,8 +3460,8 @@
def do_help(self, cmd=None):
if len(cmd) == 0:
- print "Available commands"
- print "=" * 79
+ print("Available commands")
+ print("=" * 79)
prefix = "do_"
methods = inspect.getmembers(InspectionShell, predicate=inspect.ismethod)
for name,method in methods:
@@ -3396,8 +3470,8 @@
if not doc: continue
name = prefix.join(name.split(prefix)[1:])
description = doc.splitlines()[0]
- print (name + ": ").ljust(16) + description
- print "=" * 79
+ print((name + ": ").ljust(16) + description)
+ print("=" * 79)
else:
return super(InspectionShell, self).do_help(cmd)
@@ -3425,9 +3499,9 @@
address = self.ParseAddressExpr(address)
string = self.reader.ReadAsciiString(address)
if string == "":
- print "Not an ASCII string at %s" % self.reader.FormatIntPtr(address)
+ print("Not an ASCII string at %s" % self.reader.FormatIntPtr(address))
else:
- print "%s\n" % string
+ print("%s\n" % string)
def do_dsa(self, address):
""" see display_stack_ascii"""
@@ -3438,7 +3512,7 @@
Print ASCII stack error message.
"""
if self.reader.exception is None:
- print "Minidump has no exception info"
+ print("Minidump has no exception info")
return
if len(address) == 0:
address = None
@@ -3463,7 +3537,7 @@
else:
self.dd_start += self.dd_num * self.reader.PointerSize()
if not self.reader.IsAlignedAddress(self.dd_start):
- print "Warning: Dumping un-aligned memory, is this what you had in mind?"
+ print("Warning: Dumping un-aligned memory, is this what you had in mind?")
end = self.dd_start + self.reader.PointerSize() * self.dd_num
self.padawan.InterpretMemory(self.dd_start, end)
@@ -3482,13 +3556,13 @@
if self.reader.IsAlignedAddress(address):
address = address + 1
elif not self.heap.IsTaggedObjectAddress(address):
- print "Address doesn't look like a valid pointer!"
+ print("Address doesn't look like a valid pointer!")
return
heap_object = self.padawan.SenseObject(address)
if heap_object:
heap_object.Print(Printer())
else:
- print "Address cannot be interpreted as object!"
+ print("Address cannot be interpreted as object!")
def do_dso(self, args):
""" see display_stack_objects """
@@ -3555,10 +3629,10 @@
address = self.ParseAddressExpr(address)
page_address = address & ~self.heap.PageAlignmentMask()
if self.reader.IsValidAddress(page_address):
- print "**** Not Implemented"
+ print("**** Not Implemented")
return
else:
- print "Page header is not available!"
+ print("Page header is not available!")
def do_k(self, arguments):
"""
@@ -3603,10 +3677,10 @@
List all available memory regions.
"""
def print_region(reader, start, size, location):
- print " %s - %s (%d bytes)" % (reader.FormatIntPtr(start),
+ print(" %s - %s (%d bytes)" % (reader.FormatIntPtr(start),
reader.FormatIntPtr(start + size),
- size)
- print "Available memory regions:"
+ size))
+ print("Available memory regions:")
self.reader.ForEachMemoryRegion(print_region)
def do_lm(self, arg):
@@ -3627,7 +3701,7 @@
PrintModuleDetails(self.reader, module)
else:
PrintModuleDetails(self.reader, module)
- print
+ print()
def do_s(self, word):
""" see search """
@@ -3644,9 +3718,10 @@
try:
word = self.ParseAddressExpr(word)
except ValueError:
- print "Malformed word, prefix with '0x' to use hexadecimal format."
+ print("Malformed word, prefix with '0x' to use hexadecimal format.")
return
- print "Searching for word %d/0x%s:" % (word, self.reader.FormatIntPtr(word))
+ print(
+ "Searching for word %d/0x%s:" % (word, self.reader.FormatIntPtr(word)))
self.reader.FindWord(word)
def do_sh(self, none):
@@ -3656,7 +3731,7 @@
You might get lucky and find this rare treasure full of invaluable
information.
"""
- print "**** Not Implemented"
+ print("**** Not Implemented")
def do_u(self, args):
""" see disassemble """
@@ -3679,24 +3754,24 @@
skip = True
if not self.reader.IsValidAddress(self.u_start):
- print "Address %s is not contained within the minidump!" % (
- self.reader.FormatIntPtr(self.u_start))
+ print("Address %s is not contained within the minidump!" % (
+ self.reader.FormatIntPtr(self.u_start)))
return
lines = self.reader.GetDisasmLines(self.u_start, self.u_size)
if len(lines) == 0:
- print "Address %s could not be disassembled!" % (
- self.reader.FormatIntPtr(self.u_start))
- print " Could not disassemble using %s." % OBJDUMP_BIN
- print " Pass path to architecture specific objdump via --objdump?"
+ print("Address %s could not be disassembled!" % (
+ self.reader.FormatIntPtr(self.u_start)))
+ print(" Could not disassemble using %s." % OBJDUMP_BIN)
+ print(" Pass path to architecture specific objdump via --objdump?")
return
for line in lines:
if skip:
skip = False
continue
- print FormatDisasmLine(self.u_start, self.heap, line)
+ print(FormatDisasmLine(self.u_start, self.heap, line))
# Set the next start address = last line
self.u_start += lines[-1][0]
- print
+ print()
def do_EOF(self, none):
raise KeyboardInterrupt
@@ -3733,18 +3808,18 @@
def PrintModuleDetails(reader, module):
- print "%s" % GetModuleName(reader, module)
+ print("%s" % GetModuleName(reader, module))
file_version = GetVersionString(module.version_info.dwFileVersionMS,
module.version_info.dwFileVersionLS);
product_version = GetVersionString(module.version_info.dwProductVersionMS,
module.version_info.dwProductVersionLS)
- print " base: %s" % reader.FormatIntPtr(module.base_of_image)
- print " end: %s" % reader.FormatIntPtr(module.base_of_image +
- module.size_of_image)
- print " file version: %s" % file_version
- print " product version: %s" % product_version
+ print(" base: %s" % reader.FormatIntPtr(module.base_of_image))
+ print(" end: %s" % reader.FormatIntPtr(module.base_of_image +
+ module.size_of_image))
+ print(" file version: %s" % file_version)
+ print(" product version: %s" % product_version)
time_date_stamp = datetime.datetime.fromtimestamp(module.time_date_stamp)
- print " timestamp: %s" % time_date_stamp
+ print(" timestamp: %s" % time_date_stamp)
def AnalyzeMinidump(options, minidump_name):
@@ -3754,7 +3829,7 @@
stack_top = reader.ExceptionSP()
stack_bottom = reader.StackBottom()
stack_map = {reader.ExceptionIP(): -1}
- for slot in xrange(stack_top, stack_bottom, reader.PointerSize()):
+ for slot in range(stack_top, stack_bottom, reader.PointerSize()):
maybe_address = reader.ReadUIntPtr(slot)
if not maybe_address in stack_map:
stack_map[maybe_address] = slot
@@ -3764,51 +3839,51 @@
DebugPrint("========================================")
if reader.exception is None:
- print "Minidump has no exception info"
+ print("Minidump has no exception info")
else:
- print "Address markers:"
- print " T = valid tagged pointer in the minidump"
- print " S = address on the exception stack"
- print " C = address in loaded C/C++ module"
- print " * = address in the minidump"
- print ""
- print "Exception info:"
+ print("Address markers:")
+ print(" T = valid tagged pointer in the minidump")
+ print(" S = address on the exception stack")
+ print(" C = address in loaded C/C++ module")
+ print(" * = address in the minidump")
+ print("")
+ print("Exception info:")
exception_thread = reader.ExceptionThread()
- print " thread id: %d" % exception_thread.id
- print " code: %08X" % reader.exception.exception.code
- print " context:"
+ print(" thread id: %d" % exception_thread.id)
+ print(" code: %08X" % reader.exception.exception.code)
+ print(" context:")
context = CONTEXT_FOR_ARCH[reader.arch]
maxWidth = max(map(lambda s: len(s), context))
for r in context:
register_value = reader.Register(r)
- print " %s: %s" % (r.rjust(maxWidth),
- heap.FormatIntPtr(register_value))
+ print(" %s: %s" % (r.rjust(maxWidth),
+ heap.FormatIntPtr(register_value)))
# TODO(vitalyr): decode eflags.
if reader.arch in [MD_CPU_ARCHITECTURE_ARM, MD_CPU_ARCHITECTURE_ARM64]:
- print " cpsr: %s" % bin(reader.exception_context.cpsr)[2:]
+ print(" cpsr: %s" % bin(reader.exception_context.cpsr)[2:])
else:
- print " eflags: %s" % bin(reader.exception_context.eflags)[2:]
+ print(" eflags: %s" % bin(reader.exception_context.eflags)[2:])
- print
- print " modules:"
+ print()
+ print(" modules:")
for module in reader.module_list.modules:
name = GetModuleName(reader, module)
if name in KNOWN_MODULES:
- print " %s at %08X" % (name, module.base_of_image)
+ print(" %s at %08X" % (name, module.base_of_image))
reader.TryLoadSymbolsFor(name, module)
- print
+ print()
- print " stack-top: %s" % heap.FormatIntPtr(reader.StackTop())
- print " stack-bottom: %s" % heap.FormatIntPtr(reader.StackBottom())
- print ""
+ print(" stack-top: %s" % heap.FormatIntPtr(reader.StackTop()))
+ print(" stack-bottom: %s" % heap.FormatIntPtr(reader.StackBottom()))
+ print("")
if options.shell:
padawan.PrintStackTraceMessage(print_message=False)
- print "Disassembly around exception.eip:"
+ print("Disassembly around exception.eip:")
eip_symbol = reader.FindSymbol(reader.ExceptionIP())
if eip_symbol is not None:
- print eip_symbol
+ print(eip_symbol)
disasm_start = reader.ExceptionIP() - EIP_PROXIMITY
disasm_bytes = 2 * EIP_PROXIMITY
if (options.full):
@@ -3820,12 +3895,12 @@
lines = reader.GetDisasmLines(disasm_start, disasm_bytes)
if not lines:
- print "Could not disassemble using %s." % OBJDUMP_BIN
- print "Pass path to architecture specific objdump via --objdump?"
+ print("Could not disassemble using %s." % OBJDUMP_BIN)
+ print("Pass path to architecture specific objdump via --objdump?")
for line in lines:
- print FormatDisasmLine(disasm_start, heap, line)
- print
+ print(FormatDisasmLine(disasm_start, heap, line))
+ print()
if heap is None:
heap = V8Heap(reader, None)
@@ -3840,10 +3915,10 @@
try:
InspectionShell(reader, heap).cmdloop("type help to get help")
except KeyboardInterrupt:
- print "Kthxbye."
+ print("Kthxbye.")
elif not options.command:
if reader.exception is not None:
- print "Annotated stack (from exception.esp to bottom):"
+ print("Annotated stack (from exception.esp to bottom):")
stack_start = padawan.PrintStackTraceMessage()
padawan.InterpretMemory(stack_start, stack_bottom)
reader.Dispose()
@@ -3871,11 +3946,11 @@
if options.web:
try:
server = InspectionWebServer(PORT_NUMBER, options, args[0])
- print 'Started httpserver on port ' , PORT_NUMBER
+ print('Started httpserver on port ' , PORT_NUMBER)
webbrowser.open('http://localhost:%i/summary.html' % PORT_NUMBER)
server.serve_forever()
except KeyboardInterrupt:
- print '^C received, shutting down the web server'
+ print('^C received, shutting down the web server')
server.socket.close()
else:
AnalyzeMinidump(options, args[0])
diff --git a/src/v8/tools/heap-stats/README.md b/src/v8/tools/heap-stats/README.md
index 70083fe..9cf6e56 100644
--- a/src/v8/tools/heap-stats/README.md
+++ b/src/v8/tools/heap-stats/README.md
@@ -6,8 +6,9 @@
The tool consumes log files produced by d8 (or Chromium) by passing
`--trace-gc-object-stats` or a trace captured using Chrome's tracing
-infrastructure. Chrome trace files need to be unpacked before they can
-be used though.
+infrastructure. Chrome trace files can either be processed as gzip or raw text
+files.
+
Hosting requires a web server, e.g.:
diff --git a/src/v8/tools/heap-stats/categories.js b/src/v8/tools/heap-stats/categories.js
index 0a836d5..e02571b 100644
--- a/src/v8/tools/heap-stats/categories.js
+++ b/src/v8/tools/heap-stats/categories.js
@@ -6,20 +6,17 @@
const CATEGORIES = new Map([
[
'user', new Set([
- '*FIXED_ARRAY_CONTEXT_SUB_TYPE',
- '*FIXED_ARRAY_COPY_ON_WRITE_SUB_TYPE',
- '*FIXED_ARRAY_DICTIONARY_PROPERTIES_SUB_TYPE',
- '*FIXED_ARRAY_JS_COLLECTION_SUB_TYPE',
- '*FIXED_ARRAY_JS_WEAK_COLLECTION_SUB_TYPE',
- '*FIXED_ARRAY_PACKED_ELEMENTS_SUB_TYPE',
'CONS_ONE_BYTE_STRING_TYPE',
'CONS_STRING_TYPE',
'DESCRIPTOR_ARRAY_TYPE',
+ 'ELEMENTS_TYPE',
'EXTERNAL_INTERNALIZED_STRING_TYPE',
'EXTERNAL_ONE_BYTE_INTERNALIZED_STRING_TYPE',
'EXTERNAL_ONE_BYTE_STRING_TYPE',
'EXTERNAL_STRING_TYPE',
'EXTERNAL_STRING_WITH_ONE_BYTE_DATA_TYPE',
+ 'FIXED_BIGINT64_ARRAY_TYPE',
+ 'FIXED_BIGUINT64_ARRAY_TYPE',
'FIXED_DOUBLE_ARRAY_TYPE',
'FIXED_FLOAT32_ARRAY_TYPE',
'FIXED_FLOAT64_ARRAY_TYPE',
@@ -30,10 +27,14 @@
'FIXED_UINT32_ARRAY_TYPE',
'FIXED_UINT8_ARRAY_TYPE',
'FIXED_UINT8_CLAMPED_ARRAY_TYPE',
+ 'FUNCTION_CONTEXT_TYPE',
+ 'GLOBAL_ELEMENTS_TYPE',
+ 'GLOBAL_PROPERTIES_TYPE',
'HEAP_NUMBER_TYPE',
'INTERNALIZED_STRING_TYPE',
'JS_ARGUMENTS_TYPE',
'JS_ARRAY_BUFFER_TYPE',
+ 'JS_ARRAY_ITERATOR_TYPE',
'JS_ARRAY_TYPE',
'JS_BOUND_FUNCTION_TYPE',
'JS_DATE_TYPE',
@@ -49,31 +50,55 @@
'JS_GENERIC_ARRAY_VALUE_ITERATOR_TYPE',
'JS_GLOBAL_OBJECT_TYPE',
'JS_GLOBAL_PROXY_TYPE',
+ 'JS_INTL_COLLATOR_TYPE',
+ 'JS_INTL_DATE_TIME_FORMAT_TYPE',
+ 'JS_INTL_LIST_FORMAT_TYPE',
+ 'JS_INTL_LOCALE_TYPE',
+ 'JS_INTL_NUMBER_FORMAT_TYPE',
+ 'JS_INTL_PLURAL_RULES_TYPE',
+ 'JS_INTL_RELATIVE_TIME_FORMAT_TYPE',
+ 'JS_INTL_SEGMENT_ITERATOR_TYPE',
+ 'JS_INTL_SEGMENTER_TYPE',
+ 'JS_INTL_V8_BREAK_ITERATOR_TYPE',
+ 'JS_MAP_KEY_ITERATOR_TYPE',
'JS_MAP_KEY_VALUE_ITERATOR_TYPE',
'JS_MAP_TYPE',
+ 'JS_MAP_VALUE_ITERATOR_TYPE',
'JS_MESSAGE_OBJECT_TYPE',
'JS_OBJECT_TYPE',
+ 'JS_PRIMITIVE_WRAPPER_TYPE',
'JS_PROMISE_TYPE',
+ 'JS_PROXY_TYPE',
'JS_REGEXP_TYPE',
+ 'JS_SET_KEY_VALUE_ITERATOR_TYPE',
'JS_SET_TYPE',
+ 'JS_SET_VALUE_ITERATOR_TYPE',
'JS_STRING_ITERATOR_TYPE',
+ 'JS_TO_WASM_FUNCTION',
'JS_TYPED_ARRAY_TYPE',
- 'JS_VALUE_TYPE',
'JS_WEAK_MAP_TYPE',
'MUTABLE_HEAP_NUMBER_TYPE',
+ 'NATIVE_CONTEXT_TYPE',
+ 'OBJECT_PROPERTY_DICTIONARY_TYPE',
'ONE_BYTE_INTERNALIZED_STRING_TYPE',
'ONE_BYTE_STRING_TYPE',
+ 'OTHER_CONTEXT_TYPE',
'PROPERTY_ARRAY_TYPE',
- 'SHORT_EXTERNAL_INTERNALIZED_STRING_TYPE',
- 'SHORT_EXTERNAL_ONE_BYTE_INTERNALIZED_STRING_TYPE',
- 'SHORT_EXTERNAL_ONE_BYTE_STRING_TYPE',
- 'SHORT_EXTERNAL_STRING_TYPE',
'SLICED_ONE_BYTE_STRING_TYPE',
'SLICED_STRING_TYPE',
+ 'STRING_EXTERNAL_RESOURCE_ONE_BYTE_TYPE',
+ 'STRING_EXTERNAL_RESOURCE_TWO_BYTE_TYPE',
'STRING_TYPE',
'SYMBOL_TYPE',
'THIN_ONE_BYTE_STRING_TYPE',
'THIN_STRING_TYPE',
+ 'UNCACHED_EXTERNAL_INTERNALIZED_STRING_TYPE',
+ 'UNCACHED_EXTERNAL_ONE_BYTE_INTERNALIZED_STRING_TYPE',
+ 'UNCACHED_EXTERNAL_ONE_BYTE_STRING_TYPE',
+ 'UNCACHED_EXTERNAL_STRING_TYPE',
+ 'WASM_INSTANCE_TYPE',
+ 'WASM_MEMORY_TYPE',
+ 'WASM_MODULE_TYPE',
])
],
[
@@ -83,27 +108,20 @@
'ACCESSOR_PAIR_TYPE',
'ALLOCATION_MEMENTO_TYPE',
'ALLOCATION_SITE_TYPE',
+ 'ARRAY_BOILERPLATE_DESCRIPTION_TYPE',
+ 'ARRAY_BOILERPLATE_DESCRIPTION_ELEMENTS_TYPE',
'BOILERPLATE_ELEMENTS_TYPE',
- 'BOILERPLATE_NAME_DICTIONARY_TYPE',
'BOILERPLATE_PROPERTY_ARRAY_TYPE',
+ 'BOILERPLATE_PROPERTY_DICTIONARY_TYPE',
'BYTE_ARRAY_TYPE',
+ 'CALL_HANDLER_INFO_TYPE',
'CELL_TYPE',
+ 'CODE_STUBS_TABLE_TYPE',
'CONTEXT_EXTENSION_TYPE',
- '*FIXED_ARRAY_DEPENDENT_CODE_SUB_TYPE',
- '*FIXED_ARRAY_ENUM_CACHE_SUB_TYPE',
- '*FIXED_ARRAY_ENUM_INDICES_CACHE_SUB_TYPE',
- '*FIXED_ARRAY_FAST_TEMPLATE_INSTANTIATIONS_CACHE_SUB_TYPE',
- '*FIXED_ARRAY_NUMBER_STRING_CACHE_SUB_TYPE',
- '*FIXED_ARRAY_PROTOTYPE_USERS_SUB_TYPE',
- '*FIXED_ARRAY_REGEXP_MULTIPLE_CACHE_SUB_TYPE',
- '*FIXED_ARRAY_RETAINED_MAPS_SUB_TYPE',
- '*FIXED_ARRAY_SCOPE_INFO_SUB_TYPE',
- '*FIXED_ARRAY_SCRIPT_LIST_SUB_TYPE',
- '*FIXED_ARRAY_SINGLE_CHARACTER_STRING_CACHE_SUB_TYPE',
- '*FIXED_ARRAY_STRING_SPLIT_CACHE_SUB_TYPE',
- '*FIXED_ARRAY_TEMPLATE_INFO_SUB_TYPE',
- '*FIXED_ARRAY_WEAK_NEW_SPACE_OBJECT_TO_CODE_SUB_TYPE',
+ 'ENUM_CACHE_TYPE',
+ 'ENUM_INDICES_CACHE_TYPE',
'FOREIGN_TYPE',
+ 'FUNCTION_TEMPLATE_INFO_ENTRIES_TYPE',
'FUNCTION_TEMPLATE_INFO_TYPE',
'INTERCEPTOR_INFO_TYPE',
'JS_API_OBJECT_TYPE',
@@ -111,41 +129,74 @@
'JS_OBJECT_BOILERPLATE_TYPE',
'JS_SPECIAL_API_OBJECT_TYPE',
'MAP_TYPE',
+ 'NUMBER_STRING_CACHE_TYPE',
+ 'OBJECT_BOILERPLATE_DESCRIPTION_TYPE',
'OBJECT_TEMPLATE_INFO_TYPE',
+ 'OBJECT_TO_CODE_TYPE',
'ODDBALL_TYPE',
'PROMISE_REACTION_JOB_INFO_TYPE',
'PROMISE_RESOLVE_THENABLE_JOB_INFO_TYPE',
'PROPERTY_CELL_TYPE',
'PROTOTYPE_INFO_TYPE',
+ 'PROTOTYPE_USERS_TYPE',
+ 'REGEXP_MULTIPLE_CACHE_TYPE',
+ 'RETAINED_MAPS_TYPE',
+ 'SCOPE_INFO_TYPE',
+ 'SCRIPT_LIST_TYPE',
+ 'SCRIPT_SHARED_FUNCTION_INFOS_TYPE',
+ 'SERIALIZED_OBJECTS_TYPE',
+ 'SINGLE_CHARACTER_STRING_CACHE_TYPE',
'STACK_FRAME_INFO_TYPE',
+ 'STRING_SPLIT_CACHE_TYPE',
+ 'STRING_TABLE_TYPE',
'TRANSITION_ARRAY_TYPE',
- 'WEAK_CELL_TYPE',
+ 'WEAK_NEW_SPACE_OBJECT_TO_CODE_TYPE',
])
],
[
'code', new Set([
- '*CODE_BUILTIN',
- '*CODE_BYTECODE_HANDLER',
- '*CODE_OPTIMIZED_FUNCTION',
- '*CODE_REGEXP',
- '*CODE_STUB',
- '*FIXED_ARRAY_BYTECODE_ARRAY_CONSTANT_POOL_SUB_TYPE',
- '*FIXED_ARRAY_BYTECODE_ARRAY_HANDLER_TABLE_SUB_TYPE',
- '*FIXED_ARRAY_CODE_STUBS_TABLE_SUB_TYPE',
- '*FIXED_ARRAY_COMPILATION_CACHE_TABLE_SUB_TYPE',
- '*FIXED_ARRAY_DEOPTIMIZATION_DATA_SUB_TYPE',
- '*FIXED_ARRAY_EMBEDDED_OBJECT_SUB_TYPE',
- '*FIXED_ARRAY_HANDLER_TABLE_SUB_TYPE',
- '*FIXED_ARRAY_NOSCRIPT_SHARED_FUNCTION_INFOS_SUB_TYPE',
- '*FIXED_ARRAY_OPTIMIZED_CODE_LITERALS_SUB_TYPE',
- '*FIXED_ARRAY_SHARED_FUNCTION_INFOS_SUB_TYPE',
+ 'BUILTIN',
+ 'BYTECODE_ARRAY_CONSTANT_POOL_TYPE',
+ 'BYTECODE_ARRAY_HANDLER_TABLE_TYPE',
'BYTECODE_ARRAY_TYPE',
+ 'BYTECODE_HANDLER',
'CODE_DATA_CONTAINER_TYPE',
+ 'DEOPTIMIZATION_DATA_TYPE',
+ 'EMBEDDED_OBJECT_TYPE',
+ 'FEEDBACK_CELL_TYPE',
+ 'FEEDBACK_METADATA_TYPE',
+ 'FEEDBACK_VECTOR_ENTRY_TYPE',
+ 'FEEDBACK_VECTOR_HEADER_TYPE',
+ 'FEEDBACK_VECTOR_SLOT_CALL_TYPE',
+ 'FEEDBACK_VECTOR_SLOT_CALL_UNUSED_TYPE',
+ 'FEEDBACK_VECTOR_SLOT_ENUM_TYPE',
+ 'FEEDBACK_VECTOR_SLOT_LOAD_TYPE',
+ 'FEEDBACK_VECTOR_SLOT_LOAD_UNUSED_TYPE',
+ 'FEEDBACK_VECTOR_SLOT_OTHER_TYPE',
+ 'FEEDBACK_VECTOR_SLOT_STORE_TYPE',
+ 'FEEDBACK_VECTOR_SLOT_STORE_UNUSED_TYPE',
'FEEDBACK_VECTOR_TYPE',
'LOAD_HANDLER_TYPE',
+ 'NOSCRIPT_SHARED_FUNCTION_INFOS_TYPE',
+ 'OPTIMIZED_CODE_LITERALS_TYPE',
+ 'OPTIMIZED_FUNCTION',
+ 'PREPARSE_DATA_TYPE',
+ 'REGEXP',
+ 'RELOC_INFO_TYPE',
+ 'SCRIPT_SOURCE_EXTERNAL_ONE_BYTE_TYPE',
+ 'SCRIPT_SOURCE_EXTERNAL_TWO_BYTE_TYPE',
+ 'SCRIPT_SOURCE_EXTERNAL_TYPE',
+ 'SCRIPT_SOURCE_NON_EXTERNAL_ONE_BYTE_TYPE',
+ 'SCRIPT_SOURCE_NON_EXTERNAL_TWO_BYTE_TYPE',
'SCRIPT_TYPE',
'SHARED_FUNCTION_INFO_TYPE',
+ 'SOURCE_POSITION_TABLE_TYPE',
'STORE_HANDLER_TYPE',
+ 'STUB',
+ 'UNCOMPILED_DATA_WITHOUT_PREPARSE_DATA_TYPE',
+ 'UNCOMPILED_DATA_WITH_PREPARSE_DATA_TYPE',
+ 'UNCOMPILED_JS_FUNCTION_TYPE',
+ 'UNCOMPILED_SHARED_FUNCTION_INFO_TYPE'
])
],
['unclassified', new Set()],
@@ -158,10 +209,3 @@
['code', 'Code'],
['unclassified', 'Unclassified'],
]);
-
-// Instance types that are constructed from their sub types and
-// should thus be hidden.
-const IGNORED_INSTANCE_TYPES = new Set([
- 'FIXED_ARRAY_TYPE',
- 'CODE_TYPE',
-]);
diff --git a/src/v8/tools/heap-stats/details-selection.html b/src/v8/tools/heap-stats/details-selection.html
index d60aef9..04b274a 100644
--- a/src/v8/tools/heap-stats/details-selection.html
+++ b/src/v8/tools/heap-stats/details-selection.html
@@ -3,12 +3,17 @@
found in the LICENSE file. -->
<template id="details-selection-template">
<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) {
@@ -20,53 +25,121 @@
border-radius: 0px 0px 5px 5px;
}
-span {
- display: block;
- padding: 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;
}
-.boxDiv {
- padding: 3px;
+.instanceTypeSelectBox {
+ position: relative;
+ overflow: hidden;
float: left;
+ padding: 0px 5px 2px 0px;
+ margin: 3px;
+ border-radius: 3px;
}
-.boxDiv > label {
+.instanceTypeSelectBox > label {
font-size: xx-small;
}
+.instanceTypeSelectBox > input {
+ vertical-align: middle;
+}
+
+.percentBackground {
+ position: absolute;
+ width: 200%;
+ height: 100%;
+ left: 0%;
+ top: 0px;
+ margin-left: -100%;
+ transition: all 1s ease-in-out;
+}
+
+.instanceTypeSelectBox > .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>
-<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="dataset-select">
- Data set
- </label>
- <select id="dataset-select">
- <option>No data</option>
- </select>
- </li>
- <li>
- <input type="checkbox" id="merge-categories" checked=checked />
- <label for="merge-categories">
- Merge categories
- </label>
- </li>
-</ul>
+<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="dataset-select">
+ Data set
+ </label>
+ <select id="dataset-select">
+ <option>No data</option>
+ </select>
+ </li>
+ <li>
+ <label for="gc-select">
+ Garbage collection (at a specific time in ms)
+ </label>
+ <select id="gc-select">
+ <option>No data</option>
+ </select>
+ </li>
+ <li>
+ <input id="category-filter" type="text" value="0" disabled="disabled" />KB
+ <button id="category-filter-btn" disabled="disabled">
+ Filter categories with less memory
+ </button>
+ <button id="category-auto-filter-btn" disabled="disabled">
+ Show top 20 categories only
+ </button>
+ </li>
+ <li>
+ <button id="csv-export-btn" disabled="disabled">Export selection as CSV</button>
+ </li>
+ </ul>
-
-<div id="categories"></div>
+ <div id="categories"></div>
+</section>
</template>
+
<script type="text/javascript" src="categories.js"></script>
-<script type="text/javascript" src="details-selection.js"></script>
\ No newline at end of file
+<script type="text/javascript" src="details-selection.js"></script>
diff --git a/src/v8/tools/heap-stats/details-selection.js b/src/v8/tools/heap-stats/details-selection.js
index 43c000d..1e9cc83 100644
--- a/src/v8/tools/heap-stats/details-selection.js
+++ b/src/v8/tools/heap-stats/details-selection.js
@@ -8,6 +8,10 @@
document.currentScript.ownerDocument.querySelector(
'#details-selection-template');
+const VIEW_BY_INSTANCE_TYPE = 'by-instance-type';
+const VIEW_BY_INSTANCE_CATEGORY = 'by-instance-category';
+const VIEW_BY_FIELD_TYPE = 'by-field-type';
+
class DetailsSelection extends HTMLElement {
constructor() {
super();
@@ -15,10 +19,18 @@
shadowRoot.appendChild(details_selection_template.content.cloneNode(true));
this.isolateSelect.addEventListener(
'change', e => this.handleIsolateChange(e));
+ this.dataViewSelect.addEventListener(
+ 'change', e => this.notifySelectionChanged(e));
this.datasetSelect.addEventListener(
'change', e => this.notifySelectionChanged(e));
- this.$('#merge-categories')
- .addEventListener('change', e => this.notifySelectionChanged(e));
+ this.gcSelect.addEventListener(
+ 'change', e => this.notifySelectionChanged(e));
+ this.$('#csv-export-btn')
+ .addEventListener('click', e => this.exportCurrentSelection(e));
+ this.$('#category-filter-btn')
+ .addEventListener('click', e => this.filterCurrentSelection(e));
+ this.$('#category-auto-filter-btn')
+ .addEventListener('click', e => this.filterTop20Categories(e));
}
connectedCallback() {
@@ -36,31 +48,28 @@
return this._data;
}
- buildCategory(name) {
- const div = document.createElement('div');
- div.id = name;
- div.classList.add('box');
- const span = document.createElement('span');
- div.appendChild(span);
- span.innerHTML = CATEGORY_NAMES.get(name) + ' ';
- const all_button = document.createElement('button');
- span.appendChild(all_button);
- all_button.innerHTML = 'All';
- all_button.addEventListener('click', e => this.selectCategory(name));
- const none_button = document.createElement('button');
- span.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';
- return div;
+ get selectedIsolate() {
+ return this._data[this.selection.isolate];
+ }
+
+ get selectedData() {
+ console.assert(this.data, 'invalid data');
+ console.assert(this.selection, 'invalid selection');
+ return this.selectedIsolate.gcs[this.selection.gc][this.selection.data_set];
}
$(id) {
return this.shadowRoot.querySelector(id);
}
+ querySelectorAll(query) {
+ return this.shadowRoot.querySelectorAll(query);
+ }
+
+ get dataViewSelect() {
+ return this.$('#data-view-select');
+ }
+
get datasetSelect() {
return this.$('#dataset-select');
}
@@ -69,17 +78,76 @@
return this.$('#isolate-select');
}
- dataChanged() {
- this.clearUI();
- this.populateSelect('#isolate-select', Object.keys(this.data));
- this.handleIsolateChange();
+ get gcSelect() {
+ return this.$('#gc-select');
}
- clearUI() {
+ 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: {}};
- removeAllChildren(this.isolateSelect);
+ 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.
+ isolates.sort((a, b) => b[1].peakMemory - a[1].peakMemory);
+ this.populateSelect(
+ '#isolate-select', isolates, (key, isolate) => isolate.getLabel());
+ }
+
+ resetUI(resetIsolateSelect) {
+ if (resetIsolateSelect) removeAllChildren(this.isolateSelect);
+
+ removeAllChildren(this.dataViewSelect);
removeAllChildren(this.datasetSelect);
+ removeAllChildren(this.gcSelect);
this.clearCategories();
+ this.setButtonState('disabled');
+ }
+
+ setButtonState(disabled) {
+ this.$('#csv-export-btn').disabled = disabled;
+ this.$('#category-filter').disabled = disabled;
+ this.$('#category-filter-btn').disabled = disabled;
+ this.$('#category-auto-filter-btn').disabled = disabled;
}
handleIsolateChange(e) {
@@ -88,9 +156,27 @@
this.selection.isolate = null;
return;
}
-
+ this.resetUI(false);
this.populateSelect(
- '#dataset-select', this.data[this.selection.isolate].data_sets, 'live');
+ '#data-view-select', [
+ [VIEW_BY_INSTANCE_TYPE, 'Selected instance types'],
+ [VIEW_BY_INSTANCE_CATEGORY, 'Selected type categories'],
+ [VIEW_BY_FIELD_TYPE, 'Field type statistics']
+ ],
+ (key, label) => label, VIEW_BY_INSTANCE_TYPE);
+ this.populateSelect(
+ '#dataset-select', this.selectedIsolate.data_sets.entries(), null,
+ 'live');
+ this.populateSelect(
+ '#gc-select',
+ Object.keys(this.selectedIsolate.gcs)
+ .map(id => [id, this.selectedIsolate.gcs[id].time]),
+ (key, time, index) => {
+ return (index + ': ').padStart(4, '0') +
+ formatSeconds(time).padStart(6, '0') + ' ' +
+ formatBytes(this.selectedIsolate.gcs[key].live.overall)
+ .padStart(9, '0');
+ });
this.populateCategories();
this.notifySelectionChanged();
}
@@ -98,23 +184,105 @@
notifySelectionChanged(e) {
if (!this.selection.isolate) return;
+ this.selection.data_view = this.dataViewSelect.value;
this.selection.categories = {};
- for (let category of CATEGORIES.keys()) {
- const selected = this.selectedInCategory(category);
- if (selected.length > 0) this.selection.categories[category] = selected;
+ if (this.selection.data_view === VIEW_BY_FIELD_TYPE) {
+ this.$('#categories').style.display = 'none';
+ } else {
+ for (let category of CATEGORIES.keys()) {
+ const selected = this.selectedInCategory(category);
+ if (selected.length > 0) this.selection.categories[category] = selected;
+ }
+ this.$('#categories').style.display = 'block';
}
this.selection.category_names = CATEGORY_NAMES;
this.selection.data_set = this.datasetSelect.value;
- this.selection.merge_categories = this.$('#merge-categories').checked;
+ this.selection.gc = this.gcSelect.value;
+ this.setButtonState(false);
+ this.updatePercentagesInCategory();
+ this.updatePercentagesInInstanceTypes();
this.dispatchEvent(new CustomEvent(
'change', {bubbles: true, composed: true, detail: this.selection}));
}
+ filterCurrentSelection(e) {
+ const minSize = this.$('#category-filter').value * KB;
+ this.filterCurrentSelectionWithThresold(minSize);
+ }
+
+ filterTop20Categories(e) {
+ // Limit to show top 20 categories only.
+ let minSize = 0;
+ let count = 0;
+ let sizes = this.selectedIsolate.instanceTypePeakMemory;
+ for (let key in sizes) {
+ if (count == 20) break;
+ minSize = sizes[key];
+ count++;
+ }
+ this.filterCurrentSelectionWithThresold(minSize);
+ }
+
+ filterCurrentSelectionWithThresold(minSize) {
+ if (minSize === 0) return;
+
+ this.selection.category_names.forEach((_, category) => {
+ for (let checkbox of this.querySelectorAll(
+ 'input[name=' + category + 'Checkbox]')) {
+ checkbox.checked =
+ this.selectedData.instance_type_data[checkbox.instance_type]
+ .overall > minSize;
+ console.log(
+ checkbox.instance_type, checkbox.checked,
+ this.selectedData.instance_type_data[checkbox.instance_type]
+ .overall);
+ }
+ });
+ this.notifySelectionChanged();
+ }
+
+ updatePercentagesInCategory() {
+ const overalls = {};
+ 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) =>
+ accu + this.selectedData.instance_type_data[current].overall,
+ 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 + '%';
+ });
+ }
+
+ updatePercentagesInInstanceTypes() {
+ const instanceTypeData = this.selectedData.instance_type_data;
+ const maxInstanceType = this.selectedData.singleInstancePeakMemory;
+ this.querySelectorAll('.instanceTypeSelectBox input').forEach(checkbox => {
+ let instanceType = checkbox.value;
+ let instanceTypeSize = instanceTypeData[instanceType].overall;
+ let percents = instanceTypeSize / maxInstanceType;
+ let percentDiv = checkbox.parentNode.querySelector('.percentBackground');
+ percentDiv.style.left = (percents * 100) + '%';
+
+ });
+ }
+
selectedInCategory(category) {
- const selected = this.shadowRoot.querySelectorAll(
- 'input[name=' + category + 'Checkbox]:checked');
- var tmp = [];
- for (var val of selected.values()) tmp.push(val.value);
+ let tmp = [];
+ this.querySelectorAll('input[name=' + category + 'Checkbox]:checked')
+ .forEach(checkbox => tmp.push(checkbox.value));
return tmp;
}
@@ -125,17 +293,21 @@
return 'unclassified';
}
- createOption(text) {
+ createOption(value, text) {
const option = document.createElement('option');
- option.value = text;
+ option.value = value;
option.text = text;
return option;
}
- populateSelect(id, iterable, autoselect = null) {
- for (let option_value of iterable) {
- const option = this.createOption(option_value);
- if (autoselect === option_value) {
+ 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);
@@ -158,9 +330,7 @@
categories[cat] = [];
}
- for (let instance_type of this.data[this.selection.isolate]
- .non_empty_instance_types) {
- if (IGNORED_INSTANCE_TYPES.has(instance_type)) continue;
+ for (let instance_type of this.selectedIsolate.non_empty_instance_types) {
const category = this.categoryForType(instance_type);
categories[category].push(instance_type);
}
@@ -174,38 +344,65 @@
}
unselectCategory(category) {
- for (let checkbox of this.shadowRoot.querySelectorAll(
- 'input[name=' + category + 'Checkbox]')) {
- checkbox.checked = false;
- }
+ this.querySelectorAll('input[name=' + category + 'Checkbox]')
+ .forEach(checkbox => checkbox.checked = false);
this.notifySelectionChanged();
}
selectCategory(category) {
- for (let checkbox of this.shadowRoot.querySelectorAll(
- 'input[name=' + category + 'Checkbox]')) {
- checkbox.checked = true;
- }
+ 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('boxDiv');
+ div.classList.add('instanceTypeSelectBox');
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;
}
+
+ exportCurrentSelection(e) {
+ const data = [];
+ const selected_data =
+ this.selectedIsolate.gcs[this.selection.gc][this.selection.data_set]
+ .instance_type_data;
+ Object.values(this.selection.categories).forEach(instance_types => {
+ instance_types.forEach(instance_type => {
+ data.push([instance_type, selected_data[instance_type].overall / KB]);
+ });
+ });
+ const createInlineContent = arrayOfRows => {
+ const content = arrayOfRows.reduce(
+ (accu, rowAsArray) => {return accu + `${rowAsArray.join(',')}\n`},
+ '');
+ return `data:text/csv;charset=utf-8,${content}`;
+ };
+ const encodedUri = encodeURI(createInlineContent(data));
+ const link = document.createElement('a');
+ link.setAttribute('href', encodedUri);
+ link.setAttribute(
+ 'download',
+ `heap_objects_data_${this.selection.isolate}_${this.selection.gc}.csv`);
+ this.shadowRoot.appendChild(link);
+ link.click();
+ this.shadowRoot.removeChild(link);
+ }
}
customElements.define('details-selection', DetailsSelection);
diff --git a/src/v8/tools/heap-stats/global-timeline.js b/src/v8/tools/heap-stats/global-timeline.js
index 0533f21..3830b7c 100644
--- a/src/v8/tools/heap-stats/global-timeline.js
+++ b/src/v8/tools/heap-stats/global-timeline.js
@@ -4,9 +4,6 @@
'use strict';
-const KB = 1024;
-const MB = KB * KB;
-
const global_timeline_template =
document.currentScript.ownerDocument.querySelector(
'#global-timeline-template');
@@ -60,6 +57,57 @@
}
}
+ getFieldData() {
+ const labels = [
+ {type: 'number', label: 'Time'},
+ {type: 'number', label: 'Ptr compression benefit'},
+ {type: 'string', role: 'tooltip'},
+ {type: 'number', label: 'Embedder fields'},
+ {type: 'number', label: 'Tagged fields'},
+ {type: 'number', label: 'Other raw fields'},
+ {type: 'number', label: 'Unboxed doubles'}
+ ];
+ const chart_data = [labels];
+ const isolate_data = this.data[this.selection.isolate];
+ let sum_total = 0;
+ let sum_ptr_compr_benefit_perc = 0;
+ let count = 0;
+ Object.keys(isolate_data.gcs).forEach(gc_key => {
+ const gc_data = isolate_data.gcs[gc_key];
+ const data_set = gc_data[this.selection.data_set].field_data;
+ const data = [];
+ data.push(gc_data.time * kMillis2Seconds);
+ const total = data_set.tagged_fields +
+ data_set.embedder_fields +
+ data_set.other_raw_fields +
+ data_set.unboxed_double_fields;
+ const ptr_compr_benefit = data_set.tagged_fields / 2;
+ const ptr_compr_benefit_perc = ptr_compr_benefit / total * 100;
+ sum_total += total;
+ sum_ptr_compr_benefit_perc += ptr_compr_benefit_perc;
+ count++;
+ const tooltip = "Ptr compression benefit: " +
+ (ptr_compr_benefit / KB).toFixed(2) + "KB " +
+ " (" + ptr_compr_benefit_perc.toFixed(2) + "%)";
+ data.push(ptr_compr_benefit / KB);
+ data.push(tooltip);
+ data.push(data_set.embedder_fields / KB);
+ data.push(data_set.tagged_fields / KB);
+ data.push(data_set.other_raw_fields / KB);
+ data.push(data_set.unboxed_double_fields / KB);
+ chart_data.push(data);
+ });
+ const avg_ptr_compr_benefit_perc =
+ count ? sum_ptr_compr_benefit_perc / count : 0;
+ console.log("==================================================");
+ console.log("= Average ptr compression benefit is " +
+ avg_ptr_compr_benefit_perc.toFixed(2) + "%");
+ console.log("= Average V8 heap size " +
+ (sum_total / count / KB).toFixed(2) + " KB");
+ console.log("==================================================");
+ return chart_data;
+ }
+
getCategoryData() {
const categories = Object.keys(this.selection.categories)
.map(k => this.selection.category_names.get(k));
@@ -70,7 +118,7 @@
const gc_data = isolate_data.gcs[gc_key];
const data_set = gc_data[this.selection.data_set].instance_type_data;
const data = [];
- data.push(gc_data.time);
+ data.push(gc_data.time * kMillis2Seconds);
Object.values(this.selection.categories).forEach(instance_types => {
data.push(
instance_types
@@ -86,7 +134,6 @@
}
getInstanceTypeData() {
- const categories = Object.keys(this.selection.categories);
const instance_types =
Object.values(this.selection.categories)
.reduce((accu, current) => accu.concat(current), []);
@@ -97,7 +144,7 @@
const gc_data = isolate_data.gcs[gc_key];
const data_set = gc_data[this.selection.data_set].instance_type_data;
const data = [];
- data.push(gc_data.time);
+ data.push(gc_data.time * kMillis2Seconds);
instance_types.forEach(instance_type => {
data.push(data_set[instance_type].overall / KB);
});
@@ -106,26 +153,56 @@
return chart_data;
}
- drawChart() {
- console.assert(this.data, 'invalid data');
- console.assert(this.selection, 'invalid selection');
+ getChartData() {
+ switch (this.selection.data_view) {
+ case VIEW_BY_FIELD_TYPE:
+ return this.getFieldData();
+ case VIEW_BY_INSTANCE_CATEGORY:
+ return this.getCategoryData();
+ case VIEW_BY_INSTANCE_TYPE:
+ default:
+ return this.getInstanceTypeData();
+ }
+ }
- const chart_data = (this.selection.merge_categories) ?
- this.getCategoryData() :
- this.getInstanceTypeData();
- const data = google.visualization.arrayToDataTable(chart_data);
+ getChartOptions() {
const options = {
isStacked: true,
hAxis: {
- title: 'Time [ms]',
+ format: '###.##s',
+ title: 'Time [s]',
},
- vAxis: {title: 'Memory consumption [KBytes]'},
- chartArea: {width: '85%', height: '70%'},
+ vAxis: {
+ format: '#,###KB',
+ title: 'Memory consumption [KBytes]'
+ },
+ chartArea: {left:100, width: '85%', height: '70%'},
legend: {position: 'top', maxLines: '1'},
pointsVisible: true,
pointSize: 5,
explorer: {},
};
+ switch (this.selection.data_view) {
+ case VIEW_BY_FIELD_TYPE:
+ // Overlay pointer compression benefit on top of the graph
+ return Object.assign(options, {
+ series: {0: {type: 'line', lineDashStyle: [13, 13]}},
+ });
+ case VIEW_BY_INSTANCE_CATEGORY:
+ case VIEW_BY_INSTANCE_TYPE:
+ default:
+ return options;
+ }
+ }
+
+ 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/src/v8/tools/heap-stats/helper.js b/src/v8/tools/heap-stats/helper.js
new file mode 100644
index 0000000..00f68f9
--- /dev/null
+++ b/src/v8/tools/heap-stats/helper.js
@@ -0,0 +1,23 @@
+// 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.
+
+const KB = 1024;
+const MB = KB * KB;
+const GB = MB * KB;
+const kMillis2Seconds = 1 / 1000;
+
+function formatBytes(bytes) {
+ const units = ['B', 'KiB', 'MiB', 'GiB'];
+ 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';
+}
diff --git a/src/v8/tools/heap-stats/histogram-viewer.html b/src/v8/tools/heap-stats/histogram-viewer.html
new file mode 100644
index 0000000..93fe980
--- /dev/null
+++ b/src/v8/tools/heap-stats/histogram-viewer.html
@@ -0,0 +1,19 @@
+<!-- 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. -->
+<template id="histogram-viewer-template">
+<style>
+#chart {
+ width: 100%;
+ height: 800px;
+}
+</style>
+<div id="container" style="display: none;">
+ <h2>Details</h2>
+ <ul>
+ <li><span id="overall"></span></li>
+ </ul>
+ <div id="chart"></div>
+</div>
+</template>
+<script type="text/javascript" src="histogram-viewer.js"></script>
\ No newline at end of file
diff --git a/src/v8/tools/heap-stats/histogram-viewer.js b/src/v8/tools/heap-stats/histogram-viewer.js
new file mode 100644
index 0000000..240c6cb
--- /dev/null
+++ b/src/v8/tools/heap-stats/histogram-viewer.js
@@ -0,0 +1,190 @@
+// 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.
+
+'use strict';
+
+const histogram_viewer_template =
+ document.currentScript.ownerDocument.querySelector(
+ '#histogram-viewer-template');
+
+class HistogramViewer extends HTMLElement {
+ constructor() {
+ super();
+ const shadowRoot = this.attachShadow({mode: 'open'});
+ shadowRoot.appendChild(histogram_viewer_template.content.cloneNode(true));
+ }
+
+ $(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 &&
+ (this.selection.data_view === VIEW_BY_INSTANCE_CATEGORY ||
+ this.selection.data_view === VIEW_BY_INSTANCE_TYPE);
+ ;
+ }
+
+ hide() {
+ this.$('#container').style.display = 'none';
+ }
+
+ show() {
+ this.$('#container').style.display = 'block';
+ }
+
+ getOverallValue() {
+ switch (this.selection.data_view) {
+ case VIEW_BY_FIELD_TYPE:
+ return NaN;
+ case VIEW_BY_INSTANCE_CATEGORY:
+ return this.getPropertyForCategory('overall');
+ case VIEW_BY_INSTANCE_TYPE:
+ default:
+ return this.getPropertyForInstanceTypes('overall');
+ }
+ }
+
+ stateChanged() {
+ if (this.isValid()) {
+ const overall_bytes = this.getOverallValue();
+ this.$('#overall').innerHTML = `Overall: ${overall_bytes / KB} KB`;
+ this.drawChart();
+ } else {
+ this.hide();
+ }
+ }
+
+ get selectedData() {
+ console.assert(this.data, 'invalid data');
+ console.assert(this.selection, 'invalid selection');
+ return this.data[this.selection.isolate]
+ .gcs[this.selection.gc][this.selection.data_set];
+ }
+
+ get selectedInstanceTypes() {
+ console.assert(this.selection, 'invalid selection');
+ return Object.values(this.selection.categories)
+ .reduce((accu, current) => accu.concat(current), []);
+ }
+
+ getPropertyForCategory(property) {
+ return Object.values(this.selection.categories)
+ .reduce(
+ (outer_accu, instance_types) => outer_accu +
+ instance_types.reduce(
+ (inner_accu, instance_type) => inner_accu +
+ this.selectedData
+ .instance_type_data[instance_type][property],
+ 0),
+ 0);
+ }
+
+ getPropertyForInstanceTypes(property) {
+ return this.selectedInstanceTypes.reduce(
+ (accu, instance_type) => accu +
+ this.selectedData.instance_type_data[instance_type][property],
+ 0);
+ }
+
+ formatBytes(bytes) {
+ const units = ['B', 'KiB', 'MiB'];
+ const divisor = 1024;
+ let index = 0;
+ while (index < units.length && bytes >= divisor) {
+ index++;
+ bytes /= divisor;
+ }
+ return bytes + units[index];
+ }
+
+ getCategoryData() {
+ const labels = [
+ 'Bucket',
+ ...Object.keys(this.selection.categories)
+ .map(k => this.selection.category_names.get(k))
+ ];
+ const data = this.selectedData.bucket_sizes.map(
+ (bucket_size, index) =>
+ [`<${this.formatBytes(bucket_size)}`,
+ ...Object.values(this.selection.categories)
+ .map(
+ instance_types =>
+ instance_types
+ .map(
+ instance_type =>
+ this.selectedData
+ .instance_type_data[instance_type]
+ .histogram[index])
+ .reduce((accu, current) => accu + current, 0))]);
+ // Adjust last histogram bucket label.
+ data[data.length - 1][0] = 'rest';
+ return [labels, ...data];
+ }
+
+ getInstanceTypeData() {
+ const instance_types = this.selectedInstanceTypes;
+ const labels = ['Bucket', ...instance_types];
+ const data = this.selectedData.bucket_sizes.map(
+ (bucket_size, index) =>
+ [`<${bucket_size}`,
+ ...instance_types.map(
+ instance_type =>
+ this.selectedData.instance_type_data[instance_type]
+ .histogram[index])]);
+ // Adjust last histogram bucket label.
+ data[data.length - 1][0] = 'rest';
+ return [labels, ...data];
+ }
+
+ getChartData() {
+ switch (this.selection.data_view) {
+ case VIEW_BY_FIELD_TYPE:
+ return this.getFieldData();
+ case VIEW_BY_INSTANCE_CATEGORY:
+ return this.getCategoryData();
+ case VIEW_BY_INSTANCE_TYPE:
+ default:
+ return this.getInstanceTypeData();
+ }
+ }
+
+ drawChart() {
+ const chart_data = this.getChartData();
+ const data = google.visualization.arrayToDataTable(chart_data);
+ const options = {
+ legend: {position: 'top', maxLines: '1'},
+ chartArea: {width: '85%', height: '85%'},
+ bar: {groupWidth: '80%'},
+ hAxis: {
+ title: 'Count',
+ minValue: 0
+ },
+ explorer: {},
+ };
+ const chart = new google.visualization.BarChart(this.$('#chart'));
+ this.show();
+ chart.draw(data, options);
+ }
+}
+
+customElements.define('histogram-viewer', HistogramViewer);
diff --git a/src/v8/tools/heap-stats/index.html b/src/v8/tools/heap-stats/index.html
index 3c2e62b..11fac21 100644
--- a/src/v8/tools/heap-stats/index.html
+++ b/src/v8/tools/heap-stats/index.html
@@ -8,15 +8,22 @@
<head>
<meta charset="UTF-8">
<title>V8 Heap Statistics</title>
- <link href='https://fonts.googleapis.com/css?family=Roboto' rel='stylesheet' type='text/css'>
- <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
+ <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.min.js"
+ integrity="sha256-N1z6ddQzX83fjw8v7uSNe7/MgOmMKdwFUv1+AJMDqNM="
+ crossorigin="anonymous"></script>
+
+ <script src="helper.js"></script>
<link rel="import" href="details-selection.html">
<link rel="import" href="global-timeline.html">
+ <link rel="import" href="histogram-viewer.html">
<link rel="import" href="trace-file-reader.html">
- <style type="text/css">
-
+ <style>
body {
font-family: 'Roboto', sans-serif;
margin-left: 5%;
@@ -24,11 +31,10 @@
}
</style>
- <script type="text/javascript">
-
+ <script>
'use strict';
-google.charts.load('current', {'packages':['line', 'corechart']});
+google.charts.load('current', {'packages':['line', 'corechart', 'bar']});
function $(id) { return document.querySelector(id); }
@@ -47,42 +53,48 @@
state.selection = null;
$('#global-timeline').selection = state.selection;
$('#global-timeline').data = state.data;
- $('#type-details').selection = state.selection;
- $('#type-details').data = state.data;
+ $('#histogram-viewer').selection = state.selection;
+ $('#histogram-viewer').data = state.data;
$('#details-selection').data = state.data;
}
function globalSelectionChangedA(e) {
state.selection = e.detail;
+ console.log(state.selection);
$('#global-timeline').selection = state.selection;
- $('#type-details').selection = state.selection;
+ $('#histogram-viewer').selection = state.selection;
}
</script>
</head>
<body>
- <trace-file-reader onchange="globalDataChanged(event)"></trace-file-reader>
<h1>V8 Heap 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>
+ <histogram-viewer id="histogram-viewer"></histogram-viewer>
+
<p>Visualize object statistics that have been gathered using</p>
<ul>
- <li><code>--trace-gc-object-stats on V8</code></li>
+ <li><code>--trace-gc-object-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.gc_stats</code>. The trace file needs to be unpacked (e.g. using
- <code>gunzip</code>).
+ <code>v8.gc_stats</code>.
</li>
</ul>
<p>
+ Note that you only get a data point on major GCs. You can enforce this by
+ using the <code>--gc-global</code> flag.
+ </p>
+ <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>
- <details-selection id="details-selection" onchange="globalSelectionChangedA(event)"></details-selection>
- <global-timeline id="global-timeline"></global-timeline>
- <type-details id="type-details"></type-details>
</body>
</html>
diff --git a/src/v8/tools/heap-stats/model.js b/src/v8/tools/heap-stats/model.js
new file mode 100644
index 0000000..cd6e1e5
--- /dev/null
+++ b/src/v8/tools/heap-stats/model.js
@@ -0,0 +1,105 @@
+// 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.
+
+'use strict';
+
+class Isolate {
+ constructor(address) {
+ this.address = address;
+ this.start = null;
+ this.end = null;
+ this.samples = Object.create(null);
+ this.non_empty_instance_types = new Set();
+ this.gcs = Object.create(null);
+ this.zonetags = [];
+ this.samples = {zone: {}};
+ this.data_sets = new Set();
+ this.peakMemory = 0;
+ // Maps instance_types to their max memory consumption over all gcs.
+ this.instanceTypePeakMemory = Object.create(null);
+ // Peak memory consumed by any single instance type.
+ this.singleInstanceTypePeakMemory = 0;
+ }
+
+ finalize() {
+ Object.values(this.gcs).forEach(gc => this.finalizeGC(gc));
+ this.sortInstanceTypePeakMemory();
+ }
+
+ getLabel() {
+ let label = `${this.address}: gc=#${Object.keys(this.gcs).length}`;
+ label += ` peak=${formatBytes(this.peakMemory)}`
+ return label;
+ }
+
+ finalizeGC(gc_data) {
+ this.data_sets.forEach(key => this.finalizeDataSet(gc_data[key]));
+ if (!('live' in gc_data)) return;
+ let liveData = gc_data.live;
+ this.peakMemory = Math.max(this.peakMemory, liveData.overall);
+ let data = liveData.instance_type_data;
+ for (let name in data) {
+ let prev = this.instanceTypePeakMemory[name] || 0;
+ this.instanceTypePeakMemory[name] = Math.max(prev, data[name].overall);
+ }
+ }
+
+ finalizeDataSet(data_set) {
+ // Create a ranked instance type array that sorts instance types by
+ // memory size (overall).
+ let data = data_set.instance_type_data;
+ let ranked_instance_types =
+ [...data_set.non_empty_instance_types].sort((a, b) => {
+ return data[a].overall - data[b].overall;
+ });
+ // Reassemble the instance_type list sorted by size.
+ let sorted_data = Object.create(null);
+ let max = 0;
+ ranked_instance_types.forEach((name) => {
+ let entry = sorted_data[name] = data[name];
+ max = Math.max(max, entry.overall);
+ });
+ data_set.instance_type_data = data;
+ data_set.singleInstancePeakMemory = max;
+
+ Object.entries(data_set.instance_type_data).forEach(([name, entry]) => {
+ this.checkHistogram(
+ name, entry, data_set.bucket_sizes, 'histogram', ' overall');
+ this.checkHistogram(
+ name, entry, data_set.bucket_sizes, 'over_allocated_histogram',
+ ' over_allocated');
+ });
+ }
+
+ // Check that a lower bound for histogram memory does not exceed the
+ // overall counter.
+ checkHistogram(type, entry, bucket_sizes, histogram, overallProperty) {
+ let sum = 0;
+ for (let i = 1; i < entry[histogram].length; i++) {
+ sum += entry[histogram][i] * bucket_sizes[i - 1];
+ }
+ const overall = entry[overallProperty];
+ if (sum >= overall) {
+ console.error(
+ `${type}: sum('${histogram}') > overall (${sum} > ${overall})`);
+ }
+ }
+
+ sortInstanceTypePeakMemory() {
+ let entries = Object.entries(this.instanceTypePeakMemory);
+ entries.sort((a, b) => {return b[1] - a[1]});
+ this.instanceTypePeakMemory = Object.create(null);
+ let max = 0;
+ for (let [key, value] of entries) {
+ this.instanceTypePeakMemory[key] = value;
+ max = Math.max(max, value);
+ }
+ this.singleInstanceTypePeakMemory = max;
+ }
+
+ getInstanceTypePeakMemory(type) {
+ if (!(type in this.instanceTypePeakMemory)) return 0;
+ return this.instanceTypePeakMemory[type];
+ }
+}
diff --git a/src/v8/tools/heap-stats/trace-file-reader.html b/src/v8/tools/heap-stats/trace-file-reader.html
index 98c2ef0..c5e5c6f 100644
--- a/src/v8/tools/heap-stats/trace-file-reader.html
+++ b/src/v8/tools/heap-stats/trace-file-reader.html
@@ -10,17 +10,77 @@
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>
-<div id="fileReader">
- <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>
+
+<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>
</template>
-<script type="text/javascript" src="trace-file-reader.js"></script>
+<script type="text/javascript" src="model.js"></script>
+
+<script src="trace-file-reader.js"></script>
diff --git a/src/v8/tools/heap-stats/trace-file-reader.js b/src/v8/tools/heap-stats/trace-file-reader.js
index 59825fe..4fec9a1 100644
--- a/src/v8/tools/heap-stats/trace-file-reader.js
+++ b/src/v8/tools/heap-stats/trace-file-reader.js
@@ -17,16 +17,25 @@
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();
}
@@ -42,56 +51,81 @@
event.preventDefault();
}
- connectedCallback() {}
+ connectedCallback() {
+ this.$('#fileReader').focus();
+ }
readFile(file) {
if (!file) {
this.updateLabel('Failed to load file.');
return;
}
+ this.$('#fileReader').blur();
- const result = new FileReader();
- result.onload = (e) => {
- let contents = e.target.result.split('\n');
- const return_data = (e.target.result.includes('V8.GC_Objects_Stats')) ?
- this.createModelFromChromeTraceFile(contents) :
- this.createModelFromV8TraceFile(contents);
- this.updateLabel('Finished loading \'' + file.name + '\'.');
- this.dispatchEvent(new CustomEvent(
- 'change', {bubbles: true, composed: true, detail: return_data}));
- };
- result.readAsText(file);
+ this.section.className = 'loading';
+ const reader = new FileReader();
+
+ if (['application/gzip', 'application/x-gzip'].includes(file.type)) {
+ reader.onload = (e) => {
+ try {
+ const textResult = pako.inflate(e.target.result, {to: 'string'});
+ this.processRawText(file, textResult);
+ 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 {
+ this.processRawText(file, 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.readAsText(file), 0);
+ }
}
- createOrUpdateEntryIfNeeded(data, keys, entry) {
+ processRawText(file, result) {
+ let contents = result.split('\n');
+ const return_data = (result.includes('V8.GC_Objects_Stats')) ?
+ this.createModelFromChromeTraceFile(contents) :
+ this.createModelFromV8TraceFile(contents);
+ this.extendAndSanitizeModel(return_data);
+ this.updateLabel('Finished loading \'' + file.name + '\'.');
+ this.dispatchEvent(new CustomEvent(
+ 'change', {bubbles: true, composed: true, detail: return_data}));
+ }
+
+ createOrUpdateEntryIfNeeded(data, entry) {
console.assert(entry.isolate, 'entry should have an isolate');
- if (!(entry.isolate in keys)) {
- keys[entry.isolate] = new Set();
- }
if (!(entry.isolate in data)) {
- data[entry.isolate] = {
- non_empty_instance_types: new Set(),
- gcs: {},
- zonetags: [],
- samples: {zone: {}},
- start: null,
- end: null,
- data_sets: new Set()
- };
+ data[entry.isolate] = new Isolate(entry.isolate);
}
const data_object = data[entry.isolate];
if (('id' in entry) && !(entry.id in data_object.gcs)) {
data_object.gcs[entry.id] = {non_empty_instance_types: new Set()};
}
if ('time' in entry) {
- if (data_object.end === null || data_object.end < entry.time)
+ if (data_object.end === null || data_object.end < entry.time) {
data_object.end = entry.time;
- if (data_object.start === null || data_object.start > entry.time)
+ }
+ if (data_object.start === null || data_object.start > entry.time) {
data_object.start = entry.time;
+ }
}
}
- createDatasetIfNeeded(data, keys, entry, data_set) {
+ createDatasetIfNeeded(data, entry, data_set) {
if (!(data_set in data[entry.isolate].gcs[entry.id])) {
data[entry.isolate].gcs[entry.id][data_set] = {
instance_type_data: {},
@@ -102,9 +136,17 @@
}
}
- addInstanceTypeData(
- data, keys, isolate, gc_id, data_set, instance_type, entry) {
- keys[isolate].add(data_set);
+ addFieldTypeData(data, isolate, gc_id, data_set, tagged_fields,
+ embedder_fields, unboxed_double_fields, other_raw_fields) {
+ data[isolate].gcs[gc_id][data_set].field_data = {
+ tagged_fields,
+ embedder_fields,
+ unboxed_double_fields,
+ other_raw_fields
+ };
+ }
+
+ addInstanceTypeData(data, isolate, gc_id, data_set, instance_type, entry) {
data[isolate].gcs[gc_id][data_set].instance_type_data[instance_type] = {
overall: entry.overall,
count: entry.count,
@@ -121,117 +163,82 @@
}
}
- extendAndSanitizeModel(data, keys) {
+ extendAndSanitizeModel(data) {
const checkNonNegativeProperty = (obj, property) => {
console.assert(obj[property] >= 0, 'negative property', obj, property);
};
- for (const isolate of Object.keys(data)) {
- for (const gc of Object.keys(data[isolate].gcs)) {
- for (const data_set_key of keys[isolate]) {
- const data_set = data[isolate].gcs[gc][data_set_key];
- // 1. Create a ranked instance type array that sorts instance
- // types by memory size (overall).
- data_set.ranked_instance_types =
- [...data_set.non_empty_instance_types].sort(function(a, b) {
- if (data_set.instance_type_data[a].overall >
- data_set.instance_type_data[b].overall) {
- return 1;
- } else if (
- data_set.instance_type_data[a].overall <
- data_set.instance_type_data[b].overall) {
- return -1;
- }
- return 0;
- });
-
- let known_count = 0;
- let known_overall = 0;
- let known_histogram =
- Array(
- data_set.instance_type_data.FIXED_ARRAY_TYPE.histogram.length)
- .fill(0);
- for (const instance_type in data_set.instance_type_data) {
- if (!instance_type.startsWith('*FIXED_ARRAY')) continue;
- const subtype = data_set.instance_type_data[instance_type];
- known_count += subtype.count;
- known_overall += subtype.count;
- for (let i = 0; i < subtype.histogram.length; i++) {
- known_histogram[i] += subtype.histogram[i];
- }
- }
-
- const fixed_array_data = data_set.instance_type_data.FIXED_ARRAY_TYPE;
- const unknown_entry = {
- count: fixed_array_data.count - known_count,
- overall: fixed_array_data.overall - known_overall,
- histogram: fixed_array_data.histogram.map(
- (value, index) => value - known_histogram[index])
- };
-
- // Check for non-negative values.
- checkNonNegativeProperty(unknown_entry, 'count');
- checkNonNegativeProperty(unknown_entry, 'overall');
- for (let i = 0; i < unknown_entry.histogram.length; i++) {
- checkNonNegativeProperty(unknown_entry.histogram, i);
- }
-
- data_set.instance_type_data['*FIXED_ARRAY_UNKNOWN_SUB_TYPE'] =
- unknown_entry;
- data_set.non_empty_instance_types.add(
- '*FIXED_ARRAY_UNKNOWN_SUB_TYPE');
- }
- }
- }
+ Object.values(data).forEach(isolate => isolate.finalize());
}
createModelFromChromeTraceFile(contents) {
- console.log('Processing log as chrome trace file.');
- const data = Object.create(null); // Final data container.
- const keys = Object.create(null); // Collecting 'keys' per isolate.
+ // Trace files support two formats.
+ // {traceEvents: [ data ]}
+ const kObjectTraceFile = {
+ name: 'object',
+ endToken: ']}',
+ getDataArray: o => o.traceEvents
+ };
+ // [ data ]
+ const kArrayTraceFile = {
+ name: 'array',
+ endToken: ']',
+ getDataArray: o => o
+ };
+ const handler =
+ (contents[0][0] === '{') ? kObjectTraceFile : kArrayTraceFile;
+ console.log(`Processing log as chrome trace file (${handler.name}).`);
// Pop last line in log as it might be broken.
contents.pop();
// Remove trailing comma.
contents[contents.length - 1] = contents[contents.length - 1].slice(0, -1);
// Terminate JSON.
- const sanitized_contents = [...contents, ']}'].join('');
+ const sanitized_contents = [...contents, handler.endToken].join('');
+
+ const data = Object.create(null); // Final data container.
try {
const raw_data = JSON.parse(sanitized_contents);
- const objects_stats_data =
- raw_data.traceEvents.filter(e => e.name == 'V8.GC_Objects_Stats');
- objects_stats_data.forEach(trace_data => {
- const actual_data = trace_data.args;
- const data_sets = new Set(Object.keys(actual_data));
- Object.keys(actual_data).forEach(data_set => {
- const string_entry = actual_data[data_set];
- try {
- const entry = JSON.parse(string_entry);
- this.createOrUpdateEntryIfNeeded(data, keys, entry);
- this.createDatasetIfNeeded(data, keys, entry, data_set);
- const isolate = entry.isolate;
- const time = entry.time;
- const gc_id = entry.id;
- data[isolate].gcs[gc_id].time = time;
- data[isolate].gcs[gc_id][data_set].bucket_sizes =
- entry.bucket_sizes;
- for (let [instance_type, value] of Object.entries(
- entry.type_data)) {
- // Trace file format uses markers that do not have actual
- // properties.
- if (!('overall' in value)) continue;
- this.addInstanceTypeData(
- data, keys, isolate, gc_id, data_set, instance_type, value);
- }
- } catch (e) {
- console.log('Unable to parse data set entry', e);
- }
- });
- });
+ const raw_array_data = handler.getDataArray(raw_data);
+ raw_array_data.filter(e => e.name === 'V8.GC_Objects_Stats')
+ .forEach(trace_data => {
+ const actual_data = trace_data.args;
+ const data_sets = new Set(Object.keys(actual_data));
+ Object.keys(actual_data).forEach(data_set => {
+ const string_entry = actual_data[data_set];
+ try {
+ const entry = JSON.parse(string_entry);
+ this.createOrUpdateEntryIfNeeded(data, entry);
+ this.createDatasetIfNeeded(data, entry, data_set);
+ const isolate = entry.isolate;
+ const time = entry.time;
+ const gc_id = entry.id;
+ data[isolate].gcs[gc_id].time = time;
+
+ const field_data = entry.field_data;
+ this.addFieldTypeData(data, isolate, gc_id, data_set,
+ field_data.tagged_fields, field_data.embedder_fields,
+ field_data.unboxed_double_fields,
+ field_data.other_raw_fields);
+
+ data[isolate].gcs[gc_id][data_set].bucket_sizes =
+ entry.bucket_sizes;
+ for (let [instance_type, value] of Object.entries(
+ entry.type_data)) {
+ // Trace file format uses markers that do not have actual
+ // properties.
+ if (!('overall' in value)) continue;
+ this.addInstanceTypeData(
+ data, isolate, gc_id, data_set, instance_type, value);
+ }
+ } catch (e) {
+ console.log('Unable to parse data set entry', e);
+ }
+ });
+ });
} catch (e) {
- console.log('Unable to parse chrome trace file.', e);
+ console.error('Unable to parse chrome trace file.', e);
}
- this.extendAndSanitizeModel(data, keys);
return data;
}
@@ -243,20 +250,18 @@
line = line.replace(/^I\/v8\s*\(\d+\):\s+/g, '');
return JSON.parse(line);
} catch (e) {
- console.log('Unable to parse line: \'' + line + '\'\' (' + e + ')');
+ console.log('Unable to parse line: \'' + line + '\' (' + e + ')');
}
return null;
});
const data = Object.create(null); // Final data container.
- const keys = Object.create(null); // Collecting 'keys' per isolate.
-
for (var entry of contents) {
if (entry === null || entry.type === undefined) {
continue;
}
if (entry.type === 'zone') {
- this.createOrUpdateEntryIfNeeded(data, keys, entry);
+ this.createOrUpdateEntryIfNeeded(data, entry);
const stacktrace = ('stacktrace' in entry) ? entry.stacktrace : [];
data[entry.isolate].samples.zone[entry.time] = {
allocated: entry.allocated,
@@ -265,26 +270,32 @@
};
} else if (
entry.type === 'zonecreation' || entry.type === 'zonedestruction') {
- this.createOrUpdateEntryIfNeeded(data, keys, entry);
+ this.createOrUpdateEntryIfNeeded(data, entry);
data[entry.isolate].zonetags.push(
Object.assign({opening: entry.type === 'zonecreation'}, entry));
} else if (entry.type === 'gc_descriptor') {
- this.createOrUpdateEntryIfNeeded(data, keys, entry);
+ this.createOrUpdateEntryIfNeeded(data, entry);
data[entry.isolate].gcs[entry.id].time = entry.time;
if ('zone' in entry)
data[entry.isolate].gcs[entry.id].malloced = entry.zone;
+ } else if (entry.type === 'field_data') {
+ this.createOrUpdateEntryIfNeeded(data, entry);
+ this.createDatasetIfNeeded(data, entry, entry.key);
+ this.addFieldTypeData(data, entry.isolate, entry.id, entry.key,
+ entry.tagged_fields, entry.embedder_fields,
+ entry.unboxed_double_fields, entry.other_raw_fields);
} else if (entry.type === 'instance_type_data') {
if (entry.id in data[entry.isolate].gcs) {
- this.createOrUpdateEntryIfNeeded(data, keys, entry);
- this.createDatasetIfNeeded(data, keys, entry, entry.key);
+ this.createOrUpdateEntryIfNeeded(data, entry);
+ this.createDatasetIfNeeded(data, entry, entry.key);
this.addInstanceTypeData(
- data, keys, entry.isolate, entry.id, entry.key,
+ data, entry.isolate, entry.id, entry.key,
entry.instance_type_name, entry);
}
} else if (entry.type === 'bucket_sizes') {
if (entry.id in data[entry.isolate].gcs) {
- this.createOrUpdateEntryIfNeeded(data, keys, entry);
- this.createDatasetIfNeeded(data, keys, entry, entry.key);
+ this.createOrUpdateEntryIfNeeded(data, entry);
+ this.createDatasetIfNeeded(data, entry, entry.key);
data[entry.isolate].gcs[entry.id][entry.key].bucket_sizes =
entry.sizes;
}
@@ -292,7 +303,6 @@
console.log('Unknown entry type: ' + entry.type);
}
}
- this.extendAndSanitizeModel(data, keys);
return data;
}
}
diff --git a/src/v8/tools/ic-explorer.html b/src/v8/tools/ic-explorer.html
index f60a356..aede91e 100644
--- a/src/v8/tools/ic-explorer.html
+++ b/src/v8/tools/ic-explorer.html
@@ -1,3 +1,4 @@
+<!DOCTYPE html>
<html>
<!--
Copyright 2016 the V8 project authors. All rights reserved. Use of this source
@@ -5,6 +6,8 @@
-->
<head>
+ <meta charset="utf-8">
+ <title>V8 IC explorer</title>
<style>
html {
font-family: monospace;
@@ -46,16 +49,16 @@
padding: 0.5em 0 0.2em 0;
}
</style>
- <script src="./splaytree.js" type="text/javascript"></script>
- <script src="./codemap.js" type="text/javascript"></script>
- <script src="./csvparser.js" type="text/javascript"></script>
- <script src="./consarray.js" type="text/javascript"></script>
- <script src="./profile.js" type="text/javascript"></script>
- <script src="./profile_view.js" type="text/javascript"></script>
- <script src="./logreader.js" type="text/javascript"></script>
- <script src="./arguments.js" type="text/javascript"></script>
- <script src="./ic-processor.js" type="text/javascript"></script>
- <script src="./SourceMap.js" type="text/javascript"></script>
+ <script src="./splaytree.js"></script>
+ <script src="./codemap.js"></script>
+ <script src="./csvparser.js"></script>
+ <script src="./consarray.js"></script>
+ <script src="./profile.js"></script>
+ <script src="./profile_view.js"></script>
+ <script src="./logreader.js"></script>
+ <script src="./arguments.js"></script>
+ <script src="./ic-processor.js"></script>
+ <script src="./SourceMap.js"></script>
<script>
"use strict"
diff --git a/src/v8/tools/ic-processor.js b/src/v8/tools/ic-processor.js
index 93f40b3..a97fe0e 100644
--- a/src/v8/tools/ic-processor.js
+++ b/src/v8/tools/ic-processor.js
@@ -32,11 +32,12 @@
function IcProcessor() {
- var propertyICParser = [parseInt, parseInt, parseInt, null, null, parseInt,
- null, null, null];
+ var propertyICParser = [parseInt, parseInt, parseInt, parseString,
+ parseString, parseInt, parseString, parseString, parseString];
LogReader.call(this, {
'code-creation': {
- parsers: [null, parseInt, parseInt, parseInt, parseInt, null, 'var-args'],
+ parsers: [parseString, parseInt, parseInt, parseInt, parseInt,
+ parseString, parseVarArgs],
processor: this.processCodeCreation },
'code-move': { parsers: [parseInt, parseInt],
processor: this.processCodeMove },
@@ -44,6 +45,12 @@
processor: this.processCodeDelete },
'sfi-move': { parsers: [parseInt, parseInt],
processor: this.processFunctionMove },
+ 'LoadGlobalIC': {
+ parsers : propertyICParser,
+ processor: this.processPropertyIC.bind(this, "LoadGlobalIC") },
+ 'StoreGlobalIC': {
+ parsers : propertyICParser,
+ processor: this.processPropertyIC.bind(this, "StoreGlobalIC") },
'LoadIC': {
parsers : propertyICParser,
processor: this.processPropertyIC.bind(this, "LoadIC") },
@@ -56,14 +63,19 @@
'KeyedStoreIC': {
parsers : propertyICParser,
processor: this.processPropertyIC.bind(this, "KeyedStoreIC") },
+ 'StoreInArrayLiteralIC': {
+ parsers : propertyICParser,
+ processor: this.processPropertyIC.bind(this, "StoreInArrayLiteralIC") },
});
- this.deserializedEntriesNames_ = [];
this.profile_ = new Profile();
+ this.LoadGlobalIC = 0;
+ this.StoreGlobalIC = 0;
this.LoadIC = 0;
this.StoreIC = 0;
this.KeyedLoadIC = 0;
this.KeyedStoreIC = 0;
+ this.StoreInArrayLiteralIC = 0;
}
inherits(IcProcessor, LogReader);
@@ -100,10 +112,13 @@
}
print();
print("=====================");
+ print("LoadGlobal: " + this.LoadGlobalIC);
+ print("StoreGlobal: " + this.StoreGlobalIC);
print("Load: " + this.LoadIC);
print("Store: " + this.StoreIC);
print("KeyedLoad: " + this.KeyedLoadIC);
print("KeyedStore: " + this.KeyedStoreIC);
+ print("StoreInArrayLiteral: " + this.StoreInArrayLiteralIC);
};
IcProcessor.prototype.addEntry = function(entry) {
@@ -112,10 +127,6 @@
IcProcessor.prototype.processCodeCreation = function(
type, kind, timestamp, start, size, name, maybe_func) {
- name = this.deserializedEntriesNames_[start] || name;
- if (name.startsWith("onComplete")) {
- console.log(name);
- }
if (maybe_func.length) {
var funcAddr = parseInt(maybe_func[0]);
var state = parseState(maybe_func[1]);
diff --git a/src/v8/tools/ignition/bytecode_dispatches_report.py b/src/v8/tools/ignition/bytecode_dispatches_report.py
index 97f8e83..aa5a9c9 100755
--- a/src/v8/tools/ignition/bytecode_dispatches_report.py
+++ b/src/v8/tools/ignition/bytecode_dispatches_report.py
@@ -5,6 +5,9 @@
# found in the LICENSE file.
#
+# for py2/py3 compatibility
+from __future__ import print_function
+
import argparse
import heapq
import json
@@ -54,8 +57,8 @@
for source, counters_from_source in iteritems(dispatches_table):
for destination, counter in iteritems(counters_from_source):
if counter == __COUNTER_MAX:
- print "WARNING: {} -> {} may have saturated.".format(source,
- destination)
+ print("WARNING: {} -> {} may have saturated.".format(source,
+ destination))
def find_top_bytecode_dispatch_pairs(dispatches_table, top_count):
@@ -71,9 +74,9 @@
def print_top_bytecode_dispatch_pairs(dispatches_table, top_count):
top_bytecode_dispatch_pairs = (
find_top_bytecode_dispatch_pairs(dispatches_table, top_count))
- print "Top {} bytecode dispatch pairs:".format(top_count)
+ print("Top {} bytecode dispatch pairs:".format(top_count))
for source, destination, counter in top_bytecode_dispatch_pairs:
- print "{:>12d}\t{} -> {}".format(counter, source, destination)
+ print("{:>12d}\t{} -> {}".format(counter, source, destination))
def find_top_bytecodes(dispatches_table):
@@ -87,9 +90,9 @@
def print_top_bytecodes(dispatches_table):
top_bytecodes = find_top_bytecodes(dispatches_table)
- print "Top bytecodes:"
+ print("Top bytecodes:")
for bytecode, counter in top_bytecodes:
- print "{:>12d}\t{}".format(counter, bytecode)
+ print("{:>12d}\t{}".format(counter, bytecode))
def find_top_dispatch_sources_and_destinations(
@@ -116,13 +119,13 @@
top_count, sort_relative):
top_sources, top_destinations = find_top_dispatch_sources_and_destinations(
dispatches_table, bytecode, top_count, sort_relative)
- print "Top sources of dispatches to {}:".format(bytecode)
+ print("Top sources of dispatches to {}:".format(bytecode))
for source_name, counter, ratio in top_sources:
- print "{:>12d}\t{:>5.1f}%\t{}".format(counter, ratio * 100, source_name)
+ print("{:>12d}\t{:>5.1f}%\t{}".format(counter, ratio * 100, source_name))
- print "\nTop destinations of dispatches from {}:".format(bytecode)
+ print("\nTop destinations of dispatches from {}:".format(bytecode))
for destination_name, counter, ratio in top_destinations:
- print "{:>12d}\t{:>5.1f}%\t{}".format(counter, ratio * 100, destination_name)
+ print("{:>12d}\t{:>5.1f}%\t{}".format(counter, ratio * 100, destination_name))
def build_counters_matrix(dispatches_table):
diff --git a/src/v8/tools/ignition/linux_perf_bytecode_annotate.py b/src/v8/tools/ignition/linux_perf_bytecode_annotate.py
index b242232..96cb0ee 100755
--- a/src/v8/tools/ignition/linux_perf_bytecode_annotate.py
+++ b/src/v8/tools/ignition/linux_perf_bytecode_annotate.py
@@ -1,10 +1,13 @@
-#! /usr/bin/python2
+#! /usr/bin/python
#
# Copyright 2016 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.
#
+# for py2/py3 compatibility
+from __future__ import print_function
+
import argparse
import collections
import os
@@ -94,18 +97,18 @@
return offsets.pop() if offsets else -1
current_offset = next_offset()
- print current_offset;
+ print(current_offset);
for line in bytecode_disassembly:
disassembly_offset = int(line.split()[1])
if disassembly_offset == current_offset:
count = offset_counts[current_offset]
percentage = 100.0 * count / total
- print "{:>8d} ({:>5.1f}%) ".format(count, percentage),
+ print("{:>8d} ({:>5.1f}%) ".format(count, percentage), end=' ')
current_offset = next_offset()
else:
- print " ",
- print line
+ print(" ", end=' ')
+ print(line)
if offsets:
print ("WARNING: Offsets not empty. Output is most likely invalid due to "
diff --git a/src/v8/tools/js2c.py b/src/v8/tools/js2c.py
index 0107436..b94f3ad 100755
--- a/src/v8/tools/js2c.py
+++ b/src/v8/tools/js2c.py
@@ -31,9 +31,11 @@
# char arrays. It is used for embedded JavaScript code in the V8
# library.
+# for py2/py3 compatibility
+from functools import reduce
+
import os, re
import optparse
-import jsmin
import textwrap
@@ -96,161 +98,6 @@
return lines
-def ExpandMacroDefinition(lines, pos, name_pattern, macro, expander):
- pattern_match = name_pattern.search(lines, pos)
- while pattern_match is not None:
- # Scan over the arguments
- height = 1
- start = pattern_match.start()
- end = pattern_match.end()
- assert lines[end - 1] == '('
- last_match = end
- arg_index = [0] # Wrap state into array, to work around Python "scoping"
- mapping = { }
- def add_arg(str):
- # Remember to expand recursively in the arguments
- if arg_index[0] >= len(macro.args):
- lineno = lines.count(os.linesep, 0, start) + 1
- raise Error('line %s: Too many arguments for macro "%s"' % (lineno, name_pattern.pattern))
- replacement = expander(str.strip())
- mapping[macro.args[arg_index[0]]] = replacement
- arg_index[0] += 1
- while end < len(lines) and height > 0:
- # We don't count commas at higher nesting levels.
- if lines[end] == ',' and height == 1:
- add_arg(lines[last_match:end])
- last_match = end + 1
- elif lines[end] in ['(', '{', '[']:
- height = height + 1
- elif lines[end] in [')', '}', ']']:
- height = height - 1
- end = end + 1
- # Remember to add the last match.
- add_arg(lines[last_match:end-1])
- if arg_index[0] < len(macro.args) -1:
- lineno = lines.count(os.linesep, 0, start) + 1
- raise Error('line %s: Too few arguments for macro "%s"' % (lineno, name_pattern.pattern))
- result = macro.expand(mapping)
- # Replace the occurrence of the macro with the expansion
- lines = lines[:start] + result + lines[end:]
- pattern_match = name_pattern.search(lines, start + len(result))
- return lines
-
-def ExpandMacros(lines, macros):
- # We allow macros to depend on the previously declared macros, but
- # we don't allow self-dependecies or recursion.
- for name_pattern, macro in reversed(macros):
- def expander(s):
- return ExpandMacros(s, macros)
- lines = ExpandMacroDefinition(lines, 0, name_pattern, macro, expander)
- return lines
-
-class TextMacro:
- def __init__(self, args, body):
- self.args = args
- self.body = body
- def expand(self, mapping):
- # Keys could be substrings of earlier values. To avoid unintended
- # clobbering, apply all replacements simultaneously.
- any_key_pattern = "|".join(re.escape(k) for k in mapping.iterkeys())
- def replace(match):
- return mapping[match.group(0)]
- return re.sub(any_key_pattern, replace, self.body)
-
-CONST_PATTERN = re.compile(r'^define\s+([a-zA-Z0-9_]+)\s*=\s*([^;]*);$')
-MACRO_PATTERN = re.compile(r'^macro\s+([a-zA-Z0-9_]+)\s*\(([^)]*)\)\s*=\s*([^;]*);$')
-
-
-def ReadMacros(lines):
- constants = []
- macros = []
- for line in lines.split('\n'):
- hash = line.find('#')
- if hash != -1: line = line[:hash]
- line = line.strip()
- if len(line) is 0: continue
- const_match = CONST_PATTERN.match(line)
- if const_match:
- name = const_match.group(1)
- value = const_match.group(2).strip()
- constants.append((re.compile("\\b%s\\b" % name), value))
- else:
- macro_match = MACRO_PATTERN.match(line)
- if macro_match:
- name = macro_match.group(1)
- args = [match.strip() for match in macro_match.group(2).split(',')]
- body = macro_match.group(3).strip()
- macros.append((re.compile("\\b%s\\(" % name), TextMacro(args, body)))
- else:
- raise Error("Illegal line: " + line)
- return (constants, macros)
-
-
-TEMPLATE_PATTERN = re.compile(r'^\s+T\(([A-Z][a-zA-Z0-9]*),')
-
-def ReadMessageTemplates(lines):
- templates = []
- index = 0
- for line in lines.split('\n'):
- template_match = TEMPLATE_PATTERN.match(line)
- if template_match:
- name = "k%s" % template_match.group(1)
- value = index
- index = index + 1
- templates.append((re.compile("\\b%s\\b" % name), value))
- return templates
-
-INLINE_MACRO_PATTERN = re.compile(r'macro\s+([a-zA-Z0-9_]+)\s*\(([^)]*)\)\s*\n')
-INLINE_MACRO_END_PATTERN = re.compile(r'endmacro\s*\n')
-
-def ExpandInlineMacros(lines):
- pos = 0
- while True:
- macro_match = INLINE_MACRO_PATTERN.search(lines, pos)
- if macro_match is None:
- # no more macros
- return lines
- name = macro_match.group(1)
- args = [match.strip() for match in macro_match.group(2).split(',')]
- end_macro_match = INLINE_MACRO_END_PATTERN.search(lines, macro_match.end());
- if end_macro_match is None:
- raise Error("Macro %s unclosed" % name)
- body = lines[macro_match.end():end_macro_match.start()]
-
- # remove macro definition
- lines = lines[:macro_match.start()] + lines[end_macro_match.end():]
- name_pattern = re.compile("\\b%s\\(" % name)
- macro = TextMacro(args, body)
-
- # advance position to where the macro definition was
- pos = macro_match.start()
-
- def non_expander(s):
- return s
- lines = ExpandMacroDefinition(lines, pos, name_pattern, macro, non_expander)
-
-
-INLINE_CONSTANT_PATTERN = re.compile(r'define\s+([a-zA-Z0-9_]+)\s*=\s*([^;\n]+);\n')
-
-def ExpandInlineConstants(lines):
- pos = 0
- while True:
- const_match = INLINE_CONSTANT_PATTERN.search(lines, pos)
- if const_match is None:
- # no more constants
- return lines
- name = const_match.group(1)
- replacement = const_match.group(2)
- name_pattern = re.compile("\\b%s\\b" % name)
-
- # remove constant definition and replace
- lines = (lines[:const_match.start()] +
- re.sub(name_pattern, replacement, lines[const_match.end():]))
-
- # advance position to where the constant definition was
- pos = const_match.start()
-
-
HEADER_TEMPLATE = """\
// Copyright 2011 Google Inc. All Rights Reserved.
@@ -258,9 +105,9 @@
// want to make changes to this file you should either change the
// javascript source files or the GYP script.
-#include "src/v8.h"
+#include "src/init/v8.h"
#include "src/snapshot/natives.h"
-#include "src/utils.h"
+#include "src/utils/utils.h"
namespace v8 {
namespace internal {
@@ -273,11 +120,6 @@
}
template <>
- int NativesCollection<%(type)s>::GetDebuggerCount() {
- return %(debugger_count)i;
- }
-
- template <>
int NativesCollection<%(type)s>::GetIndex(const char* name) {
%(get_index_cases)s\
return -1;
@@ -323,33 +165,16 @@
"""
-def BuildFilterChain(macro_filename, message_template_file):
+def BuildFilterChain():
"""Build the chain of filter functions to be applied to the sources.
- Args:
- macro_filename: Name of the macro file, if any.
-
Returns:
A function (string -> string) that processes a source file.
"""
- filter_chain = []
-
- if macro_filename:
- (consts, macros) = ReadMacros(ReadFile(macro_filename))
- filter_chain.append(lambda l: ExpandMacros(l, macros))
- filter_chain.append(lambda l: ExpandConstants(l, consts))
-
- if message_template_file:
- message_templates = ReadMessageTemplates(ReadFile(message_template_file))
- filter_chain.append(lambda l: ExpandConstants(l, message_templates))
-
- filter_chain.extend([
+ filter_chain = [
RemoveCommentsEmptyLinesAndWhitespace,
- ExpandInlineMacros,
- ExpandInlineConstants,
Validate,
- jsmin.JavaScriptMinifier().JSMinify
- ])
+ ]
def chain(f1, f2):
return lambda x: f2(f1(x))
@@ -363,25 +188,12 @@
def __init__(self):
self.names = []
self.modules = []
- self.is_debugger_id = []
-
-
-def IsDebuggerFile(filename):
- return os.path.basename(os.path.dirname(filename)) == "debug"
-
-def IsMacroFile(filename):
- return filename.endswith("macros.py")
-
-def IsMessageTemplateFile(filename):
- return filename.endswith("messages.h")
-
def PrepareSources(source_files, native_type, emit_js):
"""Read, prepare and assemble the list of source files.
Args:
- source_files: List of JavaScript-ish source files. A file named macros.py
- will be treated as a list of macros.
+ source_files: List of JavaScript-ish source files.
native_type: String corresponding to a NativeType enum value, allowing us
to treat different types of sources differently.
emit_js: True if we should skip the byte conversion and just leave the
@@ -390,29 +202,7 @@
Returns:
An instance of Sources.
"""
- macro_file = None
- macro_files = filter(IsMacroFile, source_files)
- assert len(macro_files) in [0, 1]
- if macro_files:
- source_files.remove(macro_files[0])
- macro_file = macro_files[0]
-
- message_template_file = None
- message_template_files = filter(IsMessageTemplateFile, source_files)
- assert len(message_template_files) in [0, 1]
- if message_template_files:
- source_files.remove(message_template_files[0])
- message_template_file = message_template_files[0]
-
- filters = None
- if native_type in ("EXTRAS", "EXPERIMENTAL_EXTRAS"):
- filters = BuildExtraFilterChain()
- else:
- filters = BuildFilterChain(macro_file, message_template_file)
-
- # Sort 'debugger' sources first.
- source_files = sorted(source_files,
- lambda l,r: IsDebuggerFile(r) - IsDebuggerFile(l))
+ filters = BuildFilterChain()
source_files_and_contents = [(f, ReadFile(f)) for f in source_files]
@@ -433,9 +223,6 @@
result.modules.append(lines)
- is_debugger = IsDebuggerFile(source)
- result.is_debugger_id.append(is_debugger)
-
name = os.path.basename(source)[:-3]
result.names.append(name)
@@ -458,14 +245,17 @@
raw_sources = "".join(sources.modules)
# The sources are expected to be ASCII-only.
- assert not filter(lambda value: ord(value) >= 128, raw_sources)
+ try:
+ raw_sources.encode('ascii')
+ except UnicodeEncodeError:
+ assert False
# Loop over modules and build up indices into the source blob:
get_index_cases = []
get_script_name_cases = []
get_script_source_cases = []
offset = 0
- for i in xrange(len(sources.modules)):
+ for i in range(len(sources.modules)):
native_name = "native %s.js" % sources.names[i]
d = {
"i": i,
@@ -483,7 +273,6 @@
metadata = {
"builtin_count": len(sources.modules),
- "debugger_count": sum(sources.is_debugger_id),
"sources_declaration": SOURCES_DECLARATION % ToCArray(source_bytes),
"total_length": total_length,
"get_index_cases": "".join(get_index_cases),
@@ -507,15 +296,15 @@
value_with_length = (value << 2) | (size - 1)
byte_sequence = bytearray()
- for i in xrange(size):
+ for i in range(size):
byte_sequence.append(value_with_length & 255)
value_with_length >>= 8;
blob_file.write(byte_sequence)
def PutStr(blob_file, value):
- PutInt(blob_file, len(value));
- blob_file.write(value);
+ PutInt(blob_file, len(value.encode()))
+ blob_file.write(value.encode())
def WriteStartupBlob(sources, startup_blob):
@@ -528,14 +317,8 @@
"""
output = open(startup_blob, "wb")
- debug_sources = sum(sources.is_debugger_id);
- PutInt(output, debug_sources)
- for i in xrange(debug_sources):
- PutStr(output, sources.names[i]);
- PutStr(output, sources.modules[i]);
-
- PutInt(output, len(sources.names) - debug_sources)
- for i in xrange(debug_sources, len(sources.names)):
+ PutInt(output, len(sources.names))
+ for i in range(len(sources.names)):
PutStr(output, sources.names[i]);
PutStr(output, sources.modules[i]);
@@ -578,7 +361,7 @@
parser.set_usage("""js2c out.cc type sources.js ...
out.cc: C code to be generated.
type: type parameter for NativesCollection template.
- sources.js: JS internal sources or macros.py.""")
+ sources.js: JS internal sources.""")
(options, args) = parser.parse_args()
JS2C(args[2:],
args[0],
diff --git a/src/v8/tools/jsfunfuzz/BUILD.gn b/src/v8/tools/jsfunfuzz/BUILD.gn
new file mode 100644
index 0000000..3c40460
--- /dev/null
+++ b/src/v8/tools/jsfunfuzz/BUILD.gn
@@ -0,0 +1,19 @@
+# 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.
+
+import("../../gni/v8.gni")
+
+group("v8_jsfunfuzz") {
+ testonly = true
+
+ data_deps = [
+ "../..:d8",
+ ]
+
+ data = [
+ # Grab current directory. This avoids adding logic for checking the
+ # existence of the jsfunfuzz subdirectory.
+ "./",
+ ]
+}
diff --git a/src/v8/tools/jsfunfuzz/fuzz-harness.sh b/src/v8/tools/jsfunfuzz/fuzz-harness.sh
index 8d064b2..fa4f9d9 100755
--- a/src/v8/tools/jsfunfuzz/fuzz-harness.sh
+++ b/src/v8/tools/jsfunfuzz/fuzz-harness.sh
@@ -51,8 +51,17 @@
cat << EOF | patch -s -p0 -d "$v8_root"
--- tools/jsfunfuzz/jsfunfuzz/multi_timed_run.py~
+++ tools/jsfunfuzz/jsfunfuzz/multi_timed_run.py
-@@ -125,7 +125,7 @@
-
+@@ -118,19 +118,19 @@
+-def showtail(logfilename):
++def showtail(logfilename, method="tail"):
+- cmd = "tail -n 20 %s" % logfilename
++ cmd = "%s -n 20 %s" % (method, logfilename)
+ print cmd
+ print ""
+ os.system(cmd)
+ print ""
+ print ""
+
def many_timed_runs():
iteration = 0
- while True:
@@ -60,6 +69,12 @@
iteration += 1
logfilename = "w%d" % iteration
one_timed_run(logfilename)
+ if not succeeded(logfilename):
+ showtail(logfilename)
+- showtail("err-" + logfilename)
++ showtail("err-" + logfilename, method="head")
+
+ many_timed_runs()
EOF
fi
diff --git a/src/v8/tools/jsfunfuzz/jsfunfuzz.tar.gz.sha1 b/src/v8/tools/jsfunfuzz/jsfunfuzz.tar.gz.sha1
index 4499960..d12877e 100644
--- a/src/v8/tools/jsfunfuzz/jsfunfuzz.tar.gz.sha1
+++ b/src/v8/tools/jsfunfuzz/jsfunfuzz.tar.gz.sha1
@@ -1 +1 @@
-d92e66273ea2a0da89456a977edd0224a8e837e9
\ No newline at end of file
+936f3baf5a24313da5eb98195d5e01d76fe602fb
\ No newline at end of file
diff --git a/src/v8/tools/linux-tick-processor b/src/v8/tools/linux-tick-processor
index 705e07d..8b856ca 100755
--- a/src/v8/tools/linux-tick-processor
+++ b/src/v8/tools/linux-tick-processor
@@ -27,8 +27,8 @@
fi
if [ ! -x "$d8_exec" ]; then
- echo "d8 shell not found in $D8_PATH"
- echo "To build, execute 'make native' from the V8 directory"
+ echo "d8 shell not found in $D8_PATH" >&2
+ echo "Please provide path to d8 as env var in D8_PATH" >&2
exit 1
fi
diff --git a/src/v8/tools/ll_prof.py b/src/v8/tools/ll_prof.py
index ca2cb00..4499874 100755
--- a/src/v8/tools/ll_prof.py
+++ b/src/v8/tools/ll_prof.py
@@ -27,6 +27,10 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+# for py2/py3 compatibility
+from __future__ import print_function
+
import bisect
import collections
import ctypes
@@ -157,7 +161,7 @@
# Print annotated lines.
address = lines[0][0]
total_count = 0
- for i in xrange(len(lines)):
+ for i in range(len(lines)):
start_offset = lines[i][0] - address
if i == len(lines) - 1:
end_offset = self.end_address - self.start_address
@@ -183,10 +187,10 @@
# 6 for the percentage number, incl. the '.'
# 1 for the '%' sign
# => 15
- print "%5d | %6.2f%% %x(%d): %s" % (count, percent, offset, offset, lines[i][1])
+ print("%5d | %6.2f%% %x(%d): %s" % (count, percent, offset, offset, lines[i][1]))
else:
- print "%s %x(%d): %s" % (" " * 15, offset, offset, lines[i][1])
- print
+ print("%s %x(%d): %s" % (" " * 15, offset, offset, lines[i][1]))
+ print()
assert total_count == self.self_ticks, \
"Lost ticks (%d != %d) in %s" % (total_count, self.self_ticks, self)
@@ -267,9 +271,8 @@
pages = 0
while page_id < limit_id:
if max_pages >= 0 and pages > max_pages:
- print >>sys.stderr, \
- "Warning: page limit (%d) reached for %s [%s]" % (
- max_pages, code.name, code.origin)
+ print("Warning: page limit (%d) reached for %s [%s]" % (
+ max_pages, code.name, code.origin), file=sys.stderr)
break
if page_id in self.pages:
page = self.pages[page_id]
@@ -309,7 +312,7 @@
def Print(self):
for code in self.AllCode():
- print code
+ print(code)
def Find(self, pc):
if pc < self.min_address or pc >= self.max_address:
@@ -411,7 +414,7 @@
continue
code = self.code_map.Find(old_start_address)
if not code:
- print >>sys.stderr, "Warning: Not found %x" % old_start_address
+ print("Warning: Not found %x" % old_start_address, file=sys.stderr)
continue
assert code.start_address == old_start_address, \
"Inexact move address %x for %s" % (old_start_address, code)
@@ -591,7 +594,7 @@
self.trace = mmap.mmap(self.trace_file.fileno(), 0, mmap.MAP_PRIVATE)
self.trace_header = TRACE_HEADER_DESC.Read(self.trace, 0)
if self.trace_header.magic != TraceReader._TRACE_HEADER_MAGIC:
- print >>sys.stderr, "Warning: unsupported trace header magic"
+ print("Warning: unsupported trace header magic", file=sys.stderr)
self.offset = self.trace_header.data_offset
self.limit = self.trace_header.data_offset + self.trace_header.data_size
assert self.limit <= self.trace.size(), \
@@ -642,7 +645,7 @@
return sample
sample.ips = []
offset += self.header_size + ctypes.sizeof(sample)
- for _ in xrange(sample.nr):
+ for _ in range(sample.nr):
sample.ips.append(
self.ip_struct.from_buffer(self.trace, offset).value)
offset += self.ip_size
@@ -786,7 +789,7 @@
def _LoadKernelSymbols(self, code_map):
if not os.path.exists(KERNEL_ALLSYMS_FILE):
- print >>sys.stderr, "Warning: %s not found" % KERNEL_ALLSYMS_FILE
+ print("Warning: %s not found" % KERNEL_ALLSYMS_FILE, file=sys.stderr)
return False
kallsyms = open(KERNEL_ALLSYMS_FILE, "r")
code = None
@@ -804,35 +807,35 @@
def PrintReport(code_map, library_repo, arch, ticks, options):
- print "Ticks per symbol:"
+ print("Ticks per symbol:")
used_code = [code for code in code_map.UsedCode()]
used_code.sort(key=lambda x: x.self_ticks, reverse=True)
for i, code in enumerate(used_code):
code_ticks = code.self_ticks
- print "%10d %5.1f%% %s [%s]" % (code_ticks, 100. * code_ticks / ticks,
- code.FullName(), code.origin)
+ print("%10d %5.1f%% %s [%s]" % (code_ticks, 100. * code_ticks / ticks,
+ code.FullName(), code.origin))
if options.disasm_all or i < options.disasm_top:
code.PrintAnnotated(arch, options)
- print
- print "Ticks per library:"
+ print()
+ print("Ticks per library:")
mmap_infos = [m for m in library_repo.infos if m.ticks > 0]
mmap_infos.sort(key=lambda m: m.ticks, reverse=True)
for mmap_info in mmap_infos:
mmap_ticks = mmap_info.ticks
- print "%10d %5.1f%% %s" % (mmap_ticks, 100. * mmap_ticks / ticks,
- mmap_info.unique_name)
+ print("%10d %5.1f%% %s" % (mmap_ticks, 100. * mmap_ticks / ticks,
+ mmap_info.unique_name))
def PrintDot(code_map, options):
- print "digraph G {"
+ print("digraph G {")
for code in code_map.UsedCode():
if code.self_ticks < 10:
continue
- print "n%d [shape=box,label=\"%s\"];" % (code.id, code.name)
+ print("n%d [shape=box,label=\"%s\"];" % (code.id, code.name))
if code.callee_ticks:
for callee, ticks in code.callee_ticks.iteritems():
- print "n%d -> n%d [label=\"%d\"];" % (code.id, callee.id, ticks)
- print "}"
+ print("n%d -> n%d [label=\"%d\"];" % (code.id, callee.id, ticks))
+ print("}")
if __name__ == "__main__":
@@ -877,8 +880,8 @@
options, args = parser.parse_args()
if not options.quiet:
- print "V8 log: %s, %s.ll" % (options.log, options.log)
- print "Perf trace file: %s" % options.trace
+ print("V8 log: %s, %s.ll" % (options.log, options.log))
+ print("Perf trace file: %s" % options.trace)
V8_GC_FAKE_MMAP = options.gc_fake_mmap
HOST_ROOT = options.host_root
@@ -886,7 +889,7 @@
disasm.OBJDUMP_BIN = options.objdump
OBJDUMP_BIN = options.objdump
else:
- print "Cannot find %s, falling back to default objdump" % options.objdump
+ print("Cannot find %s, falling back to default objdump" % options.objdump)
# Stats.
events = 0
@@ -904,8 +907,8 @@
log_reader = LogReader(log_name=options.log + ".ll",
code_map=code_map)
if not options.quiet:
- print "Generated code architecture: %s" % log_reader.arch
- print
+ print("Generated code architecture: %s" % log_reader.arch)
+ print()
sys.stdout.flush()
# Process the code and trace logs.
@@ -968,11 +971,11 @@
def PrintTicks(number, total, description):
print("%10d %5.1f%% ticks in %s" %
(number, 100.0*number/total, description))
- print
- print "Stats:"
- print "%10d total trace events" % events
- print "%10d total ticks" % ticks
- print "%10d ticks not in symbols" % missed_ticks
+ print()
+ print("Stats:")
+ print("%10d total trace events" % events)
+ print("%10d total ticks" % ticks)
+ print("%10d ticks not in symbols" % missed_ticks)
unaccounted = "unaccounted ticks"
if really_missed_ticks > 0:
unaccounted += " (probably in the kernel, try --kernel)"
@@ -980,10 +983,10 @@
PrintTicks(optimized_ticks, ticks, "ticks in optimized code")
PrintTicks(generated_ticks, ticks, "ticks in other lazily compiled code")
PrintTicks(v8_internal_ticks, ticks, "ticks in v8::internal::*")
- print "%10d total symbols" % len([c for c in code_map.AllCode()])
- print "%10d used symbols" % len([c for c in code_map.UsedCode()])
- print "%9.2fs library processing time" % mmap_time
- print "%9.2fs tick processing time" % sample_time
+ print("%10d total symbols" % len([c for c in code_map.AllCode()]))
+ print("%10d used symbols" % len([c for c in code_map.UsedCode()]))
+ print("%9.2fs library processing time" % mmap_time)
+ print("%9.2fs tick processing time" % sample_time)
log_reader.Dispose()
trace_reader.Dispose()
diff --git a/src/v8/tools/lldb_commands.py b/src/v8/tools/lldb_commands.py
index d8946ee..2884cd6 100644
--- a/src/v8/tools/lldb_commands.py
+++ b/src/v8/tools/lldb_commands.py
@@ -2,24 +2,69 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+# Load this file by adding this to your ~/.lldbinit:
+# command script import <this_dir>/lldb_commands.py
+
+# for py2/py3 compatibility
+from __future__ import print_function
+
import lldb
import re
+#####################
+# Helper functions. #
+#####################
+def current_thread(debugger):
+ return debugger.GetSelectedTarget().GetProcess().GetSelectedThread()
+
+def current_frame(debugger):
+ return current_thread(debugger).GetSelectedFrame()
+
+def no_arg_cmd(debugger, cmd):
+ current_frame(debugger).EvaluateExpression(cmd)
+ print("")
+
+def ptr_arg_cmd(debugger, name, param, cmd):
+ if not param:
+ print("'{}' requires an argument".format(name))
+ return
+ param = '(void*)({})'.format(param)
+ no_arg_cmd(debugger, cmd.format(param))
+
+#####################
+# lldb commands. #
+#####################
+def job(debugger, param, *args):
+ """Print a v8 heap object"""
+ ptr_arg_cmd(debugger, 'job', param, "_v8_internal_Print_Object({})")
+
+def jlh(debugger, param, *args):
+ """Print v8::Local handle value"""
+ ptr_arg_cmd(debugger, 'jlh', param,
+ "_v8_internal_Print_Object(*(v8::internal::Object**)(*{}))")
+
+def jco(debugger, param, *args):
+ """Print the code object at the given pc (default: current pc)"""
+ if not param:
+ param = str(current_frame(debugger).FindRegister("pc").value)
+ ptr_arg_cmd(debugger, 'jco', param, "_v8_internal_Print_Code({})")
+
+def jld(debugger, param, *args):
+ """Print a v8 LayoutDescriptor object"""
+ ptr_arg_cmd(debugger, 'jld', param,
+ "_v8_internal_Print_LayoutDescriptor({})")
+
+def jtt(debugger, param, *args):
+ """Print the transition tree of a v8 Map"""
+ ptr_arg_cmd(debugger, 'jtt', param, "_v8_internal_Print_TransitionTree({})")
+
def jst(debugger, *args):
"""Print the current JavaScript stack trace"""
- target = debugger.GetSelectedTarget()
- process = target.GetProcess()
- thread = process.GetSelectedThread()
- frame = thread.GetSelectedFrame()
- frame.EvaluateExpression("_v8_internal_Print_StackTrace();")
- print("")
+ no_arg_cmd(debugger, "_v8_internal_Print_StackTrace()")
def jss(debugger, *args):
"""Skip the jitted stack on x64 to where we entered JS last"""
- target = debugger.GetSelectedTarget()
- process = target.GetProcess()
- thread = process.GetSelectedThread()
- frame = thread.GetSelectedFrame()
+ frame = current_frame(debugger)
js_entry_sp = frame.EvaluateExpression(
"v8::internal::Isolate::Current()->thread_local_top()->js_entry_sp_;") \
.GetValue()
@@ -36,10 +81,7 @@
func_name_re = re.compile("([^(<]+)(?:\(.+\))?")
assert_re = re.compile(
"^v8::internal::Per\w+AssertType::(\w+)_ASSERT, (false|true)>")
- target = debugger.GetSelectedTarget()
- process = target.GetProcess()
- thread = process.GetSelectedThread()
- frame = thread.GetSelectedFrame()
+ thread = current_thread(debugger)
for frame in thread:
functionSignature = frame.GetDisplayFunctionName()
if functionSignature is None:
@@ -66,7 +108,8 @@
print("%s -> %s %s (%s)\033[0m" % (
color, prefix, match.group(2), match.group(1)))
-def __lldb_init_module (debugger, dict):
- debugger.HandleCommand('command script add -f lldb_commands.jst jst')
- debugger.HandleCommand('command script add -f lldb_commands.jss jss')
- debugger.HandleCommand('command script add -f lldb_commands.bta bta')
+def __lldb_init_module(debugger, dict):
+ debugger.HandleCommand('settings set target.x86-disassembly-flavor intel')
+ for cmd in ('job', 'jlh', 'jco', 'jld', 'jtt', 'jst', 'jss', 'bta'):
+ debugger.HandleCommand(
+ 'command script add -f lldb_commands.{} {}'.format(cmd, cmd))
diff --git a/src/v8/tools/locs.py b/src/v8/tools/locs.py
new file mode 100755
index 0000000..bd1609d
--- /dev/null
+++ b/src/v8/tools/locs.py
@@ -0,0 +1,458 @@
+#!/usr/bin/env python
+
+# 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.
+
+""" locs.py - Count lines of code before and after preprocessor expansion
+ Consult --help for more information.
+"""
+
+# for py2/py3 compatibility
+from __future__ import print_function
+
+import argparse
+import json
+import multiprocessing
+import os
+import re
+import subprocess
+import sys
+import tempfile
+import time
+from collections import defaultdict
+from concurrent.futures import ThreadPoolExecutor
+from pathlib import Path
+
+# for py2/py3 compatibility
+try:
+ FileNotFoundError
+except NameError:
+ FileNotFoundError = IOError
+
+ARGPARSE = argparse.ArgumentParser(
+ description=("A script that computes LoC for a build dir"),
+ epilog="""Examples:
+ Count with default settings for build in out/Default:
+ locs.py --build-dir out/Default
+ Count only a custom group of files settings for build in out/Default:
+ tools/locs.py --build-dir out/Default
+ --group src-compiler '\.\./\.\./src/compiler'
+ --only src-compiler
+ Report the 10 files with the worst expansion:
+ tools/locs.py --build-dir out/Default --worst 10
+ Report the 10 files with the worst expansion in src/compiler:
+ tools/locs.py --build-dir out/Default --worst 10
+ --group src-compiler '\.\./\.\./src/compiler'
+ --only src-compiler
+ Report the 10 largest files after preprocessing:
+ tools/locs.py --build-dir out/Default --largest 10
+ Report the 10 smallest input files:
+ tools/locs.py --build-dir out/Default --smallest 10""",
+ formatter_class=argparse.RawTextHelpFormatter
+)
+
+ARGPARSE.add_argument(
+ '--json',
+ action='store_true',
+ default=False,
+ help="output json instead of short summary")
+ARGPARSE.add_argument(
+ '--build-dir',
+ type=str,
+ help="Use specified build dir and generate necessary files",
+ required=True)
+ARGPARSE.add_argument(
+ '--echocmd',
+ action='store_true',
+ default=False,
+ help="output command used to compute LoC")
+ARGPARSE.add_argument(
+ '--only',
+ action='append',
+ default=[],
+ help="Restrict counting to report group (can be passed multiple times)")
+ARGPARSE.add_argument(
+ '--not',
+ action='append',
+ default=[],
+ help="Exclude specific group (can be passed multiple times)")
+ARGPARSE.add_argument(
+ '--list-groups',
+ action='store_true',
+ default=False,
+ help="List groups and associated regular expressions")
+ARGPARSE.add_argument(
+ '--group',
+ nargs=2,
+ action='append',
+ default=[],
+ help="Add a report group (can be passed multiple times)")
+ARGPARSE.add_argument(
+ '--largest',
+ type=int,
+ nargs='?',
+ default=0,
+ const=3,
+ help="Output the n largest files after preprocessing")
+ARGPARSE.add_argument(
+ '--worst',
+ type=int,
+ nargs='?',
+ default=0,
+ const=3,
+ help="Output the n files with worst expansion by preprocessing")
+ARGPARSE.add_argument(
+ '--smallest',
+ type=int,
+ nargs='?',
+ default=0,
+ const=3,
+ help="Output the n smallest input files")
+ARGPARSE.add_argument(
+ '--files',
+ type=int,
+ nargs='?',
+ default=0,
+ const=3,
+ help="Output results for each file separately")
+ARGPARSE.add_argument(
+ '--jobs',
+ type=int,
+ default=multiprocessing.cpu_count(),
+ help="Process specified number of files concurrently")
+
+ARGS = vars(ARGPARSE.parse_args())
+
+
+def MaxWidth(strings):
+ max_width = 0
+ for s in strings:
+ max_width = max(max_width, len(s))
+ return max_width
+
+
+def GenerateCompileCommandsAndBuild(build_dir, out):
+ if not os.path.isdir(build_dir):
+ print("Error: Specified build dir {} is not a directory.".format(
+ build_dir), file=sys.stderr)
+ exit(1)
+
+ autoninja = "autoninja -C {}".format(build_dir)
+ if subprocess.call(autoninja, shell=True, stdout=out) != 0:
+ print("Error: Building {} failed.".format(build_dir), file=sys.stderr)
+ exit(1)
+
+ compile_commands_file = "{}/compile_commands.json".format(build_dir)
+ print("Generating compile commands in {}.".format(
+ compile_commands_file), file=out)
+ ninja = "ninja -C {} -t compdb cxx cc > {}".format(
+ build_dir, compile_commands_file)
+ if subprocess.call(ninja, shell=True, stdout=out) != 0:
+ print("Error: Cound not generate {} for {}.".format(
+ compile_commands_file, build_dir), file=sys.stderr)
+ exit(1)
+
+ ninja_deps_file = "{}/ninja-deps.txt".format(build_dir)
+ print("Generating ninja dependencies in {}.".format(
+ ninja_deps_file), file=out)
+ ninja = "ninja -C {} -t deps > {}".format(
+ build_dir, ninja_deps_file)
+ if subprocess.call(ninja, shell=True, stdout=out) != 0:
+ print("Error: Cound not generate {} for {}.".format(
+ ninja_deps_file, build_dir), file=sys.stderr)
+ exit(1)
+
+ return compile_commands_file, ninja_deps_file
+
+
+def fmt_bytes(num_bytes):
+ if num_bytes > 1024*1024*1024:
+ return int(num_bytes / (1024*1024)), "MB"
+ elif num_bytes > 1024*1024:
+ return int(num_bytes / (1024)), "kB"
+ return int(num_bytes), " B"
+
+
+class CompilationData:
+ def __init__(self, loc, in_bytes, expanded, expanded_bytes):
+ self.loc = loc
+ self.in_bytes = in_bytes
+ self.expanded = expanded
+ self.expanded_bytes = expanded_bytes
+
+ def ratio(self):
+ return self.expanded / (self.loc+1)
+
+ def to_string(self):
+ exp_bytes, exp_unit = fmt_bytes(self.expanded_bytes)
+ in_bytes, in_unit = fmt_bytes(self.in_bytes)
+ return "{:>9,} LoC ({:>7,} {}) to {:>12,} LoC ({:>7,} {}) ({:>5.0f}x)".format(
+ self.loc, in_bytes, in_unit, self.expanded, exp_bytes, exp_unit, self.ratio())
+
+
+class File(CompilationData):
+ def __init__(self, file, target, loc, in_bytes, expanded, expanded_bytes):
+ super().__init__(loc, in_bytes, expanded, expanded_bytes)
+ self.file = file
+ self.target = target
+
+ def to_string(self):
+ return "{} {} {}".format(super().to_string(), self.file, self.target)
+
+
+class Group(CompilationData):
+ def __init__(self, name, regexp_string):
+ super().__init__(0, 0, 0, 0)
+ self.name = name
+ self.count = 0
+ self.regexp = re.compile(regexp_string)
+
+ def account(self, unit):
+ if (self.regexp.match(unit.file)):
+ self.loc += unit.loc
+ self.in_bytes += unit.in_bytes
+ self.expanded += unit.expanded
+ self.expanded_bytes += unit.expanded_bytes
+ self.count += 1
+
+ def to_string(self, name_width):
+ return "{:<{}} ({:>5} files): {}".format(
+ self.name, name_width, self.count, super().to_string())
+
+
+def SetupReportGroups():
+ default_report_groups = {"total": '.*',
+ "src": '\\.\\./\\.\\./src',
+ "test": '\\.\\./\\.\\./test',
+ "third_party": '\\.\\./\\.\\./third_party',
+ "gen": 'gen'}
+
+ report_groups = default_report_groups.copy()
+ report_groups.update(dict(ARGS['group']))
+
+ if ARGS['only']:
+ for only_arg in ARGS['only']:
+ if not only_arg in report_groups.keys():
+ print("Error: specified report group '{}' is not defined.".format(
+ ARGS['only']))
+ exit(1)
+ else:
+ report_groups = {
+ k: v for (k, v) in report_groups.items() if k in ARGS['only']}
+
+ if ARGS['not']:
+ report_groups = {
+ k: v for (k, v) in report_groups.items() if k not in ARGS['not']}
+
+ if ARGS['list_groups']:
+ print_cat_max_width = MaxWidth(list(report_groups.keys()) + ["Category"])
+ print(" {:<{}} {}".format("Category",
+ print_cat_max_width, "Regular expression"))
+ for cat, regexp_string in report_groups.items():
+ print(" {:<{}}: {}".format(
+ cat, print_cat_max_width, regexp_string))
+
+ report_groups = {k: Group(k, v) for (k, v) in report_groups.items()}
+
+ return report_groups
+
+
+class Results:
+ def __init__(self):
+ self.groups = SetupReportGroups()
+ self.units = {}
+ self.source_dependencies = {}
+ self.header_dependents = {}
+
+ def track(self, filename):
+ is_tracked = False
+ for group in self.groups.values():
+ if group.regexp.match(filename):
+ is_tracked = True
+ return is_tracked
+
+ def recordFile(self, filename, targetname, loc, in_bytes, expanded, expanded_bytes):
+ unit = File(filename, targetname, loc, in_bytes, expanded, expanded_bytes)
+ self.units[filename] = unit
+ for group in self.groups.values():
+ group.account(unit)
+
+ def maxGroupWidth(self):
+ return MaxWidth([v.name for v in self.groups.values()])
+
+ def printGroupResults(self, file):
+ for key in sorted(self.groups.keys()):
+ print(self.groups[key].to_string(self.maxGroupWidth()), file=file)
+
+ def printSorted(self, key, count, reverse, out):
+ for unit in sorted(list(self.units.values()), key=key, reverse=reverse)[:count]:
+ print(unit.to_string(), file=out)
+
+ def addHeaderDeps(self, source_dependencies, header_dependents):
+ self.source_dependencies = source_dependencies
+ self.header_dependents = header_dependents
+
+
+class LocsEncoder(json.JSONEncoder):
+ def default(self, o):
+ if isinstance(o, File):
+ return {"file": o.file, "target": o.target, "loc": o.loc, "in_bytes": o.in_bytes,
+ "expanded": o.expanded, "expanded_bytes": o.expanded_bytes}
+ if isinstance(o, Group):
+ return {"name": o.name, "loc": o.loc, "in_bytes": o.in_bytes,
+ "expanded": o.expanded, "expanded_bytes": o.expanded_bytes}
+ if isinstance(o, Results):
+ return {"groups": o.groups, "units": o.units,
+ "source_dependencies": o.source_dependencies,
+ "header_dependents": o.header_dependents}
+ return json.JSONEncoder.default(self, o)
+
+
+class StatusLine:
+ def __init__(self):
+ self.max_width = 0
+
+ def print(self, statusline, end="\r", file=sys.stdout):
+ self.max_width = max(self.max_width, len(statusline))
+ print("{0:<{1}}".format(statusline, self.max_width),
+ end=end, file=file, flush=True)
+
+
+class CommandSplitter:
+ def __init__(self):
+ self.cmd_pattern = re.compile(
+ "([^\\s]*\\s+)?(?P<clangcmd>[^\\s]*clang.*)"
+ " -c (?P<infile>.*) -o (?P<outfile>.*)")
+
+ def process(self, compilation_unit):
+ cmd = self.cmd_pattern.match(compilation_unit['command'])
+ outfilename = cmd.group('outfile')
+ infilename = cmd.group('infile')
+ infile = Path(compilation_unit['directory']).joinpath(infilename)
+ return (cmd.group('clangcmd'), infilename, infile, outfilename)
+
+
+def parse_ninja_deps(ninja_deps):
+ source_dependencies = {}
+ header_dependents = defaultdict(int)
+ current_target = None
+ for line in ninja_deps:
+ line = line.rstrip()
+ # Ignore empty lines
+ if not line:
+ current_target = None
+ continue
+ if line[0] == ' ':
+ # New dependency
+ if len(line) < 5 or line[0:4] != ' ' or line[5] == ' ':
+ sys.exit('Lines must have no indentation or exactly four ' +
+ 'spaces.')
+ dep = line[4:]
+ if not re.search(r"\.(h|hpp)$", dep):
+ continue
+ header_dependents[dep] += 1
+ continue
+ # New target
+ colon_pos = line.find(':')
+ if colon_pos < 0:
+ sys.exit('Unindented line must have a colon')
+ if current_target is not None:
+ sys.exit('Missing empty line before new target')
+ current_target = line[0:colon_pos]
+ match = re.search(r"#deps (\d+)", line)
+ deps_number = match.group(1)
+ source_dependencies[current_target] = int(deps_number)
+
+ return (source_dependencies, header_dependents)
+
+
+def Main():
+ out = sys.stdout
+ if ARGS['json']:
+ out = sys.stderr
+
+ compile_commands_file, ninja_deps_file = GenerateCompileCommandsAndBuild(
+ ARGS['build_dir'], out)
+
+ result = Results()
+ status = StatusLine()
+
+ try:
+ with open(compile_commands_file) as file:
+ compile_commands = json.load(file)
+ with open(ninja_deps_file) as file:
+ source_dependencies, header_dependents = parse_ninja_deps(file)
+ result.addHeaderDeps(source_dependencies, header_dependents)
+ except FileNotFoundError:
+ print("Error: Cannot read '{}'. Consult --help to get started.".format(
+ ninja_deps_file))
+ exit(1)
+
+ cmd_splitter = CommandSplitter()
+
+ def count_lines_of_unit(ikey):
+ i, key = ikey
+ if not result.track(key['file']):
+ return
+ message = "[{}/{}] Counting LoCs of {}".format(
+ i, len(compile_commands), key['file'])
+ status.print(message, file=out)
+ clangcmd, infilename, infile, outfilename = cmd_splitter.process(key)
+ if not infile.is_file():
+ return
+
+ clangcmd = clangcmd + " -E -P " + \
+ str(infile) + " -o /dev/stdout | sed '/^\\s*$/d' | wc -lc"
+ loccmd = ("cat {} | sed '\\;^\\s*//;d' | sed '\\;^/\\*;d'"
+ " | sed '/^\\*/d' | sed '/^\\s*$/d' | wc -lc")
+ loccmd = loccmd.format(infile)
+ runcmd = " {} ; {}".format(clangcmd, loccmd)
+ if ARGS['echocmd']:
+ print(runcmd)
+ process = subprocess.Popen(
+ runcmd, shell=True, cwd=key['directory'], stdout=subprocess.PIPE)
+ p = {'process': process, 'infile': infilename, 'outfile': outfilename}
+ output, _ = p['process'].communicate()
+ expanded, expanded_bytes, loc, in_bytes = list(map(int, output.split()))
+ result.recordFile(p['infile'], p['outfile'], loc,
+ in_bytes, expanded, expanded_bytes)
+
+ with tempfile.TemporaryDirectory(dir='/tmp/', prefix="locs.") as temp:
+ start = time.time()
+
+ with ThreadPoolExecutor(max_workers=ARGS['jobs']) as executor:
+ list(executor.map(count_lines_of_unit, enumerate(compile_commands)))
+
+ end = time.time()
+ if ARGS['json']:
+ print(json.dumps(result, ensure_ascii=False, cls=LocsEncoder))
+ status.print("Processed {:,} files in {:,.2f} sec.".format(
+ len(compile_commands), end-start), end="\n", file=out)
+ result.printGroupResults(file=out)
+
+ if ARGS['largest']:
+ print("Largest {} files after expansion:".format(ARGS['largest']))
+ result.printSorted(
+ lambda v: v.expanded, ARGS['largest'], reverse=True, out=out)
+
+ if ARGS['worst']:
+ print("Worst expansion ({} files):".format(ARGS['worst']))
+ result.printSorted(
+ lambda v: v.ratio(), ARGS['worst'], reverse=True, out=out)
+
+ if ARGS['smallest']:
+ print("Smallest {} input files:".format(ARGS['smallest']))
+ result.printSorted(
+ lambda v: v.loc, ARGS['smallest'], reverse=False, out=out)
+
+ if ARGS['files']:
+ print("List of input files:")
+ result.printSorted(
+ lambda v: v.file, ARGS['files'], reverse=False, out=out)
+
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(Main())
diff --git a/src/v8/tools/logreader.js b/src/v8/tools/logreader.js
index d9dfd17..ebdc55a 100644
--- a/src/v8/tools/logreader.js
+++ b/src/v8/tools/logreader.js
@@ -112,7 +112,7 @@
*/
LogReader.prototype.processLogLine = function(line) {
if (!this.timedRange_) {
- this.processLog_([line]);
+ this.processLogLine_(line);
return;
}
if (line.startsWith("current-time")) {
@@ -130,7 +130,7 @@
if (this.hasSeenTimerMarker_) {
this.logLinesSinceLastTimerMarker_.push(line);
} else if (!line.startsWith("tick")) {
- this.processLog_([line]);
+ this.processLogLine_(line);
}
}
};
@@ -175,6 +175,9 @@
return false;
};
+// Parses dummy variable for readability;
+const parseString = 'parse-string';
+const parseVarArgs = 'parse-var-args';
/**
* Does a dispatch of a log record.
@@ -185,9 +188,8 @@
LogReader.prototype.dispatchLogRow_ = function(fields) {
// Obtain the dispatch.
var command = fields[0];
- if (!(command in this.dispatchTable_)) return;
-
var dispatch = this.dispatchTable_[command];
+ if (dispatch === undefined) return;
if (dispatch === null || this.skipDispatch(dispatch)) {
return;
@@ -197,14 +199,16 @@
var parsedFields = [];
for (var i = 0; i < dispatch.parsers.length; ++i) {
var parser = dispatch.parsers[i];
- if (parser === null) {
+ if (parser === parseString) {
parsedFields.push(fields[1 + i]);
} else if (typeof parser == 'function') {
parsedFields.push(parser(fields[1 + i]));
- } else {
+ } else if (parser === parseVarArgs) {
// var-args
parsedFields.push(fields.slice(1 + i));
break;
+ } else {
+ throw new Error("Invalid log field parser: " + parser);
}
}
@@ -220,11 +224,19 @@
* @private
*/
LogReader.prototype.processLog_ = function(lines) {
- for (var i = 0, n = lines.length; i < n; ++i, ++this.lineNum_) {
- var line = lines[i];
- if (!line) {
- continue;
- }
+ for (var i = 0, n = lines.length; i < n; ++i) {
+ this.processLogLine_(lines[i]);
+ }
+}
+
+/**
+ * Processes a single log line.
+ *
+ * @param {String} a log line
+ * @private
+ */
+LogReader.prototype.processLogLine_ = function(line) {
+ if (line.length > 0) {
try {
var fields = this.csvParser_.parseLine(line);
this.dispatchLogRow_(fields);
@@ -232,4 +244,5 @@
this.printError('line ' + (this.lineNum_ + 1) + ': ' + (e.message || e));
}
}
+ this.lineNum_++;
};
diff --git a/src/v8/tools/map-processor b/src/v8/tools/map-processor
index c0713bd..cf18c31 100755
--- a/src/v8/tools/map-processor
+++ b/src/v8/tools/map-processor
@@ -28,7 +28,7 @@
if [ ! -x "$d8_exec" ]; then
echo "d8 shell not found in $D8_PATH"
- echo "To build, execute 'make native' from the V8 directory"
+ echo "Please provide path to d8 as env var in D8_PATH"
exit 1
fi
diff --git a/src/v8/tools/map-processor.html b/src/v8/tools/map-processor.html
index 4029e96..70c205c 100644
--- a/src/v8/tools/map-processor.html
+++ b/src/v8/tools/map-processor.html
@@ -5,7 +5,7 @@
code is governed by a BSD-style license that can be found in the LICENSE file.
-->
<head>
-<meta charset="UTF-8">
+<meta charset="utf-8">
<style>
html, body {
font-family: sans-serif;
@@ -15,10 +15,100 @@
h1, h2, h3, section {
padding-left: 15px;
}
+
+#content {
+ opacity: 0.0;
+ height: 0px;
+ transition: all 0.5s ease-in-out;
+}
+
+.success #content {
+ height: auto;
+ opacity: 1.0;
+}
+
+#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;
+}
+
+.failure #fileReader {
+ background-color: #FFAAAA;
+}
+
+.success #fileReader {
+ 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);
+ }
+}
+
+.colorbox {
+ width: 10px;
+ height: 10px;
+ border: 1px black solid;
+}
+
#stats table {
display: inline-block;
padding-right: 50px;
}
+#stats table td {
+ cursor: pointer;
+}
#stats .transitionTable {
max-height: 200px;
overflow-y: scroll;
@@ -30,6 +120,16 @@
overflow-x: scroll;
user-select: none;
}
+#timelineLabel {
+ transform: rotate(90deg);
+ transform-origin: left bottom 0;
+ position: absolute;
+ left: 0;
+ width: 250px;
+ text-align: center;
+ font-size: 10px;
+ opacity: 0.5;
+}
#timelineChunks {
height: 250px;
position: absolute;
@@ -179,6 +279,9 @@
word-break: break-all;
background-color: rgba(255,255,255,0.5);
}
+.black{
+ background-color: black;
+}
.red {
background-color: red;
}
@@ -308,7 +411,7 @@
function div(classes) {
let node = document.createElement('div');
if (classes !== void 0) {
- if (typeof classes == "string") {
+ if (typeof classes === "string") {
node.classList.add(classes);
} else {
classes.forEach(cls => node.classList.add(cls));
@@ -322,11 +425,17 @@
if (className) node.classList.add(className)
return node;
}
-function td(text) {
+
+function td(textOrNode) {
let node = document.createElement("td");
- node.innerText = text;
+ if (typeof textOrNode === "object") {
+ node.appendChild(textOrNode);
+ } else {
+ node.innerText = textOrNode;
+ }
return node;
}
+
function tr() {
let node = document.createElement("tr");
return node;
@@ -369,9 +478,14 @@
// =========================================================================
// EventHandlers
function handleBodyLoad() {
- let upload = $('uploadInput');
- upload.onclick = (e) => { e.target.value = null };
- upload.onchange = (e) => { handleLoadFile(e.target) };
+ let upload = $('fileReader');
+ upload.onclick = (e) => $("file").click();
+ upload.ondragover = (e) => e.preventDefault();
+ upload.ondrop = (e) => handleLoadFile(e);
+ $('file').onchange = (e) => handleLoadFile(e);
+ upload.onkeydown = (e) => {
+ if (event.key == "Enter") $("file").click();
+ };
upload.focus();
document.state = new State();
@@ -381,20 +495,31 @@
tooltip.style.top = e.pageY + "px";
let map = e.target.map;
if (map) {
- $("tooltipContents").innerText = map.description.join("\n");
+ $("tooltipContents").innerText = map.description;
}
});
+
+ function handleLoadFile(event) {
+ // Used for drop and file change.
+ event.preventDefault();
+ let host = event.dataTransfer ? event.dataTransfer : event.target;
+ let file = host.files[0];
+ let reader = new FileReader();
+ document.body.className = 'loading';
+ reader.onload = function(evt) {
+ try {
+ handleLoadText(this.result);
+ document.body.className = 'success';
+ } catch(e) {
+ document.body.className = 'failure';
+ console.error(e);
+ }
+ }
+ // Defer the reading to allow spinner CSS animation.
+ setTimeout(() => reader.readAsText(file), 0);
+ }
}
-function handleLoadFile(upload) {
- let files = upload.files;
- let file = files[0];
- let reader = new FileReader();
- reader.onload = function(evt) {
- handleLoadText(this.result);
- }
- reader.readAsText(file);
-}
function handleLoadText(text) {
let mapProcessor = new MapProcessor();
@@ -540,6 +665,7 @@
let details = "";
if (this.map) {
details += "ID: " + this.map.id;
+ details += "\nSource location: " + this.map.filePosition;
details += "\n" + this.map.description;
}
$("mapDetails").innerText = details;
@@ -591,7 +717,6 @@
time += interval;
}
this.drawOverview();
- this.drawHistograms();
this.redraw();
}
@@ -686,47 +811,6 @@
$("timelineOverview").style.backgroundImage = "url(" + imageData + ")";
}
- drawHistograms() {
- $("mapsDepthHistogram").histogram = this.timeline.depthHistogram();
- $("mapsFanOutHistogram").histogram = this.timeline.fanOutHistogram();
- }
-
- drawMapsDepthHistogram() {
- let canvas = $("mapsDepthCanvas");
- let histogram = this.timeline.depthHistogram();
- this.drawHistogram(canvas, histogram, true);
- }
-
- drawMapsFanOutHistogram() {
- let canvas = $("mapsFanOutCanvas");
- let histogram = this.timeline.fanOutHistogram();
- this.drawHistogram(canvas, histogram, true, true);
- }
-
- drawHistogram(canvas, histogram, logScaleX=false, logScaleY=false) {
- let ctx = canvas.getContext("2d");
- let yMax = histogram.max(each => each.length);
- if (logScaleY) yMax = Math.log(yMax);
- let xMax = histogram.length;
- if (logScaleX) xMax = Math.log(xMax);
- ctx.clearRect(0, 0, canvas.width, canvas.height);
- ctx.beginPath();
- ctx.moveTo(0,canvas.height);
- for (let i = 0; i < histogram.length; i++) {
- let x = i;
- if (logScaleX) x = Math.log(x);
- x = x / xMax * canvas.width;
- let bucketLength = histogram[i].length;
- if (logScaleY) bucketLength = Math.log(bucketLength);
- let y = (1 - bucketLength / yMax) * canvas.height;
- ctx.lineTo(x, y);
- }
- ctx.lineTo(canvas.width, canvas.height);
- ctx.closePath;
- ctx.stroke();
- ctx.fill();
- }
-
redraw() {
let canvas= $("timelineCanvas");
canvas.width = (this.chunks.length+1) * kChunkWidth;
@@ -1006,26 +1090,32 @@
}
updateGeneralStats() {
let pairs = [
- ["Maps", e => true],
- ["Transitions", e => e.edge && e.edge.isTransition()],
- ["Fast to Slow", e => e.edge && e.edge.isFastToSlow()],
- ["Slow to Fast", e => e.edge && e.edge.isSlowToFast()],
- ["Initial Map", e => e.edge && e.edge.isInitial()],
- ["Replace Descriptors", e => e.edge && e.edge.isReplaceDescriptors()],
- ["Copy as Prototype", e => e.edge && e.edge.isCopyAsPrototype()],
- ["Optimize as Prototype", e => e.edge && e.edge.isOptimizeAsPrototype()],
- ["Deprecated", e => e.isDeprecated()],
+ ["Maps", null, e => true],
+ ["Transitions", 'black', e => e.edge && e.edge.isTransition()],
+ ["Fast to Slow", 'violet', e => e.edge && e.edge.isFastToSlow()],
+ ["Slow to Fast", 'orange', e => e.edge && e.edge.isSlowToFast()],
+ ["Initial Map", 'yellow', e => e.edge && e.edge.isInitial()],
+ ["Replace Descriptors", 'red', e => e.edge && e.edge.isReplaceDescriptors()],
+ ["Copy as Prototype", 'red', e => e.edge && e.edge.isCopyAsPrototype()],
+ ["Optimize as Prototype", null, e => e.edge && e.edge.isOptimizeAsPrototype()],
+ ["Deprecated", null, e => e.isDeprecated()],
+ ["Bootstrapped", 'green', e => e.isBootstrapped()],
];
let text = "";
let tableNode = table();
let name, filter;
let total = this.timeline.size();
- pairs.forEach(([name, filter]) => {
+ pairs.forEach(([name, color, filter]) => {
let row = tr();
+ if (color !== null) {
+ row.appendChild(td(div(['colorbox', color])));
+ } else {
+ row.appendChild(td(""));
+ }
row.maps = this.timeline.filterUniqueTransitions(filter);
- row.addEventListener("click",
- e => this.transitionView.showMaps(e.target.parentNode.maps));
+ row.onclick =
+ (e) => this.transitionView.showMaps(e.target.parentNode.maps);
row.appendChild(td(name));
let count = this.timeline.count(filter);
row.appendChild(td(count));
@@ -1060,7 +1150,7 @@
switch(type) {
case "new": return "green";
case "Normalize": return "violet";
- case "map=SlowToFast": return "orange";
+ case "SlowToFast": return "orange";
case "InitialMap": return "yellow";
case "Transition": return "black";
case "ReplaceDescriptors": return "red";
@@ -1069,183 +1159,53 @@
}
// ShadowDom elements =========================================================
-customElements.define('x-histogram', class extends HTMLElement {
- constructor() {
- super();
- let shadowRoot = this.attachShadow({mode: 'open'});
- const t = document.querySelector('#x-histogram-template');
- const instance = t.content.cloneNode(true);
- shadowRoot.appendChild(instance);
- this._histogram = undefined;
- this.mouseX = 0;
- this.mouseY = 0;
- this.canvas.addEventListener('mousemove', event => this.handleCanvasMove(event));
- }
- setBoolAttribute(name, value) {
- if (value) {
- this.setAttribute(name, "");
- } else {
- this.deleteAttribute(name);
- }
- }
- static get observedAttributes() {
- return ['title', 'xlog', 'ylog', 'xlabel', 'ylabel'];
- }
- $(query) { return this.shadowRoot.querySelector(query) }
- get h1() { return this.$("h2") }
- get canvas() { return this.$("canvas") }
- get xLabelDiv() { return this.$("#xLabel") }
- get yLabelDiv() { return this.$("#yLabel") }
-
- get histogram() {
- return this._histogram;
- }
- set histogram(array) {
- this._histogram = array;
- if (this._histogram) {
- this.yMax = this._histogram.max(each => each.length);
- this.xMax = this._histogram.length;
- }
- this.draw();
- }
-
- get title() { return this.getAttribute("title") }
- set title(string) { this.setAttribute("title", string) }
- get xLabel() { return this.getAttribute("xlabel") }
- set xLabel(string) { this.setAttribute("xlabel", string)}
- get yLabel() { return this.getAttribute("ylabel") }
- set yLabel(string) { this.setAttribute("ylabel", string)}
- get xLog() { return this.hasAttribute("xlog") }
- set xLog(value) { this.setBoolAttribute("xlog", value) }
- get yLog() { return this.hasAttribute("ylog") }
- set yLog(value) { this.setBoolAttribute("ylog", value) }
-
- attributeChangedCallback(name, oldValue, newValue) {
- if (name == "title") {
- this.h1.innerText = newValue;
- return;
- }
- if (name == "ylabel") {
- this.yLabelDiv.innerText = newValue;
- return;
- }
- if (name == "xlabel") {
- this.xLabelDiv.innerText = newValue;
- return;
- }
- this.draw();
- }
-
- handleCanvasMove(event) {
- this.mouseX = event.offsetX;
- this.mouseY = event.offsetY;
- this.draw();
- }
- xPosition(i) {
- let x = i;
- if (this.xLog) x = Math.log(x);
- return x / this.xMax * this.canvas.width;
- }
- yPosition(i) {
- let bucketLength = this.histogram[i].length;
- if (this.yLog) {
- return (1 - Math.log(bucketLength) / Math.log(this.yMax)) * this.drawHeight + 10;
- } else {
- return (1 - bucketLength / this.yMax) * this.drawHeight + 10;
- }
- }
-
- get drawHeight() { return this.canvas.height - 10 }
-
- draw() {
- if (!this.histogram) return;
- let width = this.canvas.width;
- let height = this.drawHeight;
- let ctx = this.canvas.getContext("2d");
- if (this.xLog) yMax = Math.log(yMax);
- let xMax = this.histogram.length;
- if (this.yLog) xMax = Math.log(xMax);
- ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
- ctx.beginPath();
- ctx.moveTo(0, height);
- for (let i = 0; i < this.histogram.length; i++) {
- ctx.lineTo(this.xPosition(i), this.yPosition(i));
- }
- ctx.lineTo(width, height);
- ctx.closePath;
- ctx.stroke();
- ctx.fill();
- if (!this.mouseX) return;
- ctx.beginPath();
- let index = Math.round(this.mouseX);
- let yBucket = this.histogram[index];
- let y = this.yPosition(index);
- if (this.yLog) y = Math.log(y);
- ctx.moveTo(0, y);
- ctx.lineTo(width-40, y);
- ctx.moveTo(this.mouseX, 0);
- ctx.lineTo(this.mouseX, height);
- ctx.stroke();
- ctx.textAlign = "left";
- ctx.fillText(yBucket.length, width-30, y);
- }
-});
</script>
</head>
-<template id="x-histogram-template">
- <style>
- #yLabel {
- transform: rotate(90deg);
- }
- canvas, #yLabel, #info { float: left; }
- #xLabel { clear: both }
- </style>
- <h2></h2>
- <div id="yLabel"></div>
- <canvas height=50></canvas>
- <div id="info">
- </div>
- <div id="xLabel"></div>
-</template>
-
<body onload="handleBodyLoad(event)" onkeypress="handleKeyDown(event)">
- <h2>Data</h2>
+ <h1>V8 Map Explorer</h1>
<section>
- <form name="fileForm">
- <p>
- <input id="uploadInput" type="file" name="files">
- </p>
- </form>
+ <div id="fileReader" tabindex=1 >
+ <span id="label">
+ Drag and drop a v8.log file into this area, or click to choose from disk.
+ </span>
+ <input id="file" type="file" name="files">
+ </div>
+ <div id="loader">
+ <div id="spinner"></div>
+ </div>
</section>
- <h2>Stats</h2>
- <section id="stats"></section>
+ <div id="content">
+ <h2>Stats</h2>
+ <section id="stats"></section>
- <h2>Timeline</h2>
- <div id="timeline">
- <div id=timelineChunks></div>
- <canvas id="timelineCanvas" ></canvas>
- </div>
- <div id="timelineOverview"
- onmousemove="handleTimelineIndicatorMove(event)" >
- <div id="timelineOverviewIndicator">
- <div class="leftMask"></div>
- <div class="rightMask"></div>
+ <h2>Timeline</h2>
+ <div id="timeline">
+ <div id="timelineLabel">Frequency</div>
+ <div id="timelineChunks"></div>
+ <canvas id="timelineCanvas"></canvas>
</div>
+ <div id="timelineOverview"
+ onmousemove="handleTimelineIndicatorMove(event)" >
+ <div id="timelineOverviewIndicator">
+ <div class="leftMask"></div>
+ <div class="rightMask"></div>
+ </div>
+ </div>
+
+ <h2>Transitions</h2>
+ <section id="transitionView"></section>
+ <br/>
+
+ <h2>Selected Map</h2>
+ <section id="mapDetails"></section>
</div>
- <h2>Transitions</h2>
- <section id="transitionView"></section>
- <br/>
-
- <h2>Selected Map</h2>
- <section id="mapDetails"></section>
-
- <x-histogram id="mapsDepthHistogram"
- title="Maps Depth" xlabel="depth" ylabel="nof"></x-histogram>
- <x-histogram id="mapsFanOutHistogram" xlabel="fan-out"
- title="Maps Fan-out" ylabel="nof"></x-histogram>
+ <h2>Instructions</h2>
+ <section>
+ <p>Visualize Map trees that have been gathere using <code>--trace-maps</code>.</p>
+ </section>
<div id="tooltip">
<div id="tooltipContents"></div>
diff --git a/src/v8/tools/map-processor.js b/src/v8/tools/map-processor.js
index 5b0e469..7e8572a 100644
--- a/src/v8/tools/map-processor.js
+++ b/src/v8/tools/map-processor.js
@@ -8,7 +8,8 @@
super();
this.dispatchTable_ = {
'code-creation': {
- parsers: [null, parseInt, parseInt, parseInt, parseInt, null, 'var-args'],
+ parsers: [parseString, parseInt, parseInt, parseInt, parseInt,
+ parseString, parseVarArgs],
processor: this.processCodeCreation
},
'code-move': {
@@ -24,21 +25,20 @@
processor: this.processFunctionMove
},
'map-create': {
- parsers: [parseInt, parseInt, null],
+ parsers: [parseInt, parseInt, parseString],
processor: this.processMapCreate
},
'map': {
- parsers: [null, parseInt, parseInt, parseInt, parseInt, parseInt,
- null, null, null
+ parsers: [parseString, parseInt, parseInt, parseInt, parseInt, parseInt,
+ parseString, parseString, parseString
],
processor: this.processMap
},
'map-details': {
- parsers: [parseInt, parseInt, null],
+ parsers: [parseInt, parseInt, parseString],
processor: this.processMapDetails
}
};
- this.deserializedEntriesNames_ = [];
this.profile_ = new Profile();
this.timeline_ = new Timeline();
}
@@ -65,7 +65,7 @@
this.processLogLine(line);
}
} catch(e) {
- console.log("Error occurred during parsing, trying to continue: " + e);
+ console.error("Error occurred during parsing, trying to continue: " + e);
}
return this.finalize();
}
@@ -107,10 +107,6 @@
processCodeCreation(
type, kind, timestamp, start, size, name, maybe_func) {
- name = this.deserializedEntriesNames_[start] || name;
- if (name.startsWith("onComplete")) {
- console.log(name);
- }
if (maybe_func.length) {
let funcAddr = parseInt(maybe_func[0]);
let state = this.parseState(maybe_func[1]);
@@ -155,7 +151,7 @@
from = this.getExistingMap(from, time);
to = this.getExistingMap(to, time);
let edge = new Edge(type, name, reason, time, from, to);
- edge.filePosition = this.formatPC(pc, line, column);
+ to.filePosition = this.formatPC(pc, line, column);
edge.finishSetup();
}
@@ -179,9 +175,6 @@
}
createMap(id, time) {
- if (id == 0x1821257d1761) {
- console.log(id);
- }
let map = new V8Map(id, time);
this.timeline_.push(map);
return map;
@@ -216,6 +209,7 @@
V8Map.set(id, this);
this.leftId = 0;
this.rightId = 0;
+ this.filePosition = "";
}
finalize(id) {
@@ -291,6 +285,10 @@
return this.edge === void 0 ? "new" : this.edge.type;
}
+ isBootstrapped() {
+ return this.edge === void 0;
+ }
+
getParents() {
let parents = [];
let current = this.parent();
@@ -322,7 +320,6 @@
this.time = time;
this.from = from;
this.to = to;
- this.filePosition = "";
}
finishSetup() {
@@ -370,31 +367,35 @@
}
isTransition() {
- return this.type == "Transition"
+ return this.type === "Transition"
}
isFastToSlow() {
- return this.type == "Normalize"
+ return this.type === "Normalize"
}
isSlowToFast() {
- return this.type == "SlowToFast"
+ return this.type === "SlowToFast"
}
isInitial() {
- return this.type == "InitialMap"
+ return this.type === "InitialMap"
+ }
+
+ isBootstrapped() {
+ return this.type === "new"
}
isReplaceDescriptors() {
- return this.type == "ReplaceDescriptors"
+ return this.type === "ReplaceDescriptors"
}
isCopyAsPrototype() {
- return this.reason == "CopyAsPrototype"
+ return this.reason === "CopyAsPrototype"
}
isOptimizeAsPrototype() {
- return this.reason == "OptimizeAsPrototype"
+ return this.reason === "OptimizeAsPrototype"
}
symbol() {
diff --git a/src/v8/tools/mb/docs/design_spec.md b/src/v8/tools/mb/docs/design_spec.md
index fb202da..c119e65 100644
--- a/src/v8/tools/mb/docs/design_spec.md
+++ b/src/v8/tools/mb/docs/design_spec.md
@@ -169,7 +169,7 @@
* `compile_targets`, which is a list of pruned targets to be
passed to Ninja to build. It is acceptable to replace a list of
pruned targets by a meta target if it turns out that all of the
- dependendencies of the target are affected by the patch (i.e.,
+ dependencies of the target are affected by the patch (i.e.,
all ten binaries that blink_tests depends on), but doing so is
not required.
* `test_targets`, which is a list of unpruned targets to be mapped
diff --git a/src/v8/tools/mb/docs/user_guide.md b/src/v8/tools/mb/docs/user_guide.md
index a7d72c8..75c195a 100644
--- a/src/v8/tools/mb/docs/user_guide.md
+++ b/src/v8/tools/mb/docs/user_guide.md
@@ -20,7 +20,7 @@
### `mb analyze`
-`mb analyze` is reponsible for determining what targets are affected by
+`mb analyze` is responsible for determining what targets are affected by
a list of files (e.g., the list of files in a patch on a trybot):
```
@@ -229,7 +229,7 @@
There should be an key in this dict for every supported configuration
of Chromium, meaning every configuration we have a bot for, and every
-configuration commonly used by develpers but that we may not have a bot
+configuration commonly used by developers but that we may not have a bot
for.
The value of each key is a list of "mixins" that will define what that
diff --git a/src/v8/tools/mb/mb.py b/src/v8/tools/mb/mb.py
index 9a660022..f66f82a 100755
--- a/src/v8/tools/mb/mb.py
+++ b/src/v8/tools/mb/mb.py
@@ -4,16 +4,13 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-"""MB - the Meta-Build wrapper around GYP and GN
+"""MB - the Meta-Build wrapper around GN.
-MB is a wrapper script for GYP and GN that can be used to generate build files
+MB is a wrapper script for GN that can be used to generate build files
for sets of canned configurations and analyze them.
"""
-# TODO(thomasanderson): Remove this comment. It is added to
-# workaround https://crbug.com/736215 for CL
-# https://codereview.chromium.org/2974603002/
-
+# for py2/py3 compatibility
from __future__ import print_function
import argparse
@@ -22,6 +19,7 @@
import json
import os
import pipes
+import platform
import pprint
import re
import shutil
@@ -39,6 +37,12 @@
import gn_helpers
+try:
+ cmp # Python 2
+except NameError: # Python 3
+ def cmp(x, y): # pylint: disable=redefined-builtin
+ return (x > y) - (x < y)
+
def main(args):
mbw = MetaBuildWrapper()
@@ -95,21 +99,17 @@
help='path to config file '
'(default is %(default)s)')
subp.add_argument('-i', '--isolate-map-file', metavar='PATH',
- default=self.default_isolate_map,
help='path to isolate map file '
- '(default is %(default)s)')
+ '(default is %(default)s)',
+ default=[],
+ action='append',
+ dest='isolate_map_files')
subp.add_argument('-g', '--goma-dir',
help='path to goma directory')
- subp.add_argument('--gyp-script', metavar='PATH',
- default=self.PathJoin('build', 'gyp_chromium'),
- help='path to gyp script relative to project root '
- '(default is %(default)s)')
subp.add_argument('--android-version-code',
- help='Sets GN arg android_default_version_code and '
- 'GYP_DEFINE app_manifest_version_code')
+ help='Sets GN arg android_default_version_code')
subp.add_argument('--android-version-name',
- help='Sets GN arg android_default_version_name and '
- 'GYP_DEFINE app_manifest_version_name')
+ help='Sets GN arg android_default_version_name')
subp.add_argument('-n', '--dryrun', action='store_true',
help='Do a dry run (i.e., do nothing, just print '
'the commands that will run)')
@@ -131,6 +131,8 @@
subp.add_argument('output_path', nargs=1,
help='path to a file containing the output arguments '
'as a JSON object.')
+ subp.add_argument('--json-output',
+ help='Write errors to json.output')
subp.set_defaults(func=self.CmdAnalyze)
subp = subps.add_parser('export',
@@ -149,6 +151,8 @@
subp.add_argument('--swarming-targets-file',
help='save runtime dependencies for targets listed '
'in file.')
+ subp.add_argument('--json-output',
+ help='Write errors to json.output')
subp.add_argument('path', nargs=1,
help='path to generate build into')
subp.set_defaults(func=self.CmdGen)
@@ -167,6 +171,12 @@
help='look up the command for a given config or '
'builder')
AddCommonOptions(subp)
+ subp.add_argument('--quiet', default=False, action='store_true',
+ help='Print out just the arguments, '
+ 'do not emulate the output of the gen subcommand.')
+ subp.add_argument('--recursive', default=False, action='store_true',
+ help='Lookup arguments from imported files, '
+ 'implies --quiet')
subp.set_defaults(func=self.CmdLookup)
subp = subps.add_parser(
@@ -190,7 +200,6 @@
' --test-launcher-retry-limit=0'
'\n'
)
-
AddCommonOptions(subp)
subp.add_argument('-j', '--jobs', dest='jobs', type=int,
help='Number of jobs to pass to ninja')
@@ -202,6 +211,14 @@
' This can be either a regular path or a '
'GN-style source-relative path like '
'//out/Default.'))
+ subp.add_argument('-s', '--swarmed', action='store_true',
+ help='Run under swarming with the default dimensions')
+ subp.add_argument('-d', '--dimension', default=[], action='append', nargs=2,
+ dest='dimensions', metavar='FOO bar',
+ help='dimension to filter on')
+ subp.add_argument('--no-default-dimensions', action='store_false',
+ dest='default_dimensions', default=True,
+ help='Do not automatically add dimensions to the task')
subp.add_argument('target', nargs=1,
help='ninja target to build and run')
subp.add_argument('extra_args', nargs='*',
@@ -217,26 +234,6 @@
help='path to config file (default is %(default)s)')
subp.set_defaults(func=self.CmdValidate)
- subp = subps.add_parser('audit',
- help='Audit the config file to track progress')
- subp.add_argument('-f', '--config-file', metavar='PATH',
- default=self.default_config,
- help='path to config file (default is %(default)s)')
- subp.add_argument('-i', '--internal', action='store_true',
- help='check internal masters also')
- subp.add_argument('-m', '--master', action='append',
- help='master to audit (default is all non-internal '
- 'masters in file)')
- subp.add_argument('-u', '--url-template', action='store',
- default='https://build.chromium.org/p/'
- '{master}/json/builders',
- help='URL scheme for JSON APIs to buildbot '
- '(default: %(default)s) ')
- subp.add_argument('-c', '--check-compile', action='store_true',
- help='check whether tbd and master-only bots actually'
- ' do compiles')
- subp.set_defaults(func=self.CmdAudit)
-
subp = subps.add_parser('gerrit-buildbucket-config',
help='Print buildbucket.config for gerrit '
'(see MB user guide)')
@@ -253,10 +250,6 @@
self.args = parser.parse_args(argv)
- # TODO(machenbach): This prepares passing swarming targets to isolate on the
- # infra side.
- self.args.swarming_targets_file = None
-
def DumpInputFiles(self):
def DumpContentsOfFilePassedTo(arg_name, path):
@@ -276,11 +269,7 @@
def CmdAnalyze(self):
vals = self.Lookup()
- self.ClobberIfNeeded(vals)
- if vals['type'] == 'gn':
- return self.RunGNAnalyze(vals)
- else:
- return self.RunGYPAnalyze(vals)
+ return self.RunGNAnalyze(vals)
def CmdExport(self):
self.ReadConfigFile()
@@ -312,11 +301,7 @@
def CmdGen(self):
vals = self.Lookup()
- self.ClobberIfNeeded(vals)
- if vals['type'] == 'gn':
- return self.RunGNGen(vals)
- else:
- return self.RunGYPGen(vals)
+ return self.RunGNGen(vals)
def CmdHelp(self):
if self.args.subcommand:
@@ -328,23 +313,19 @@
vals = self.GetConfig()
if not vals:
return 1
-
- if vals['type'] == 'gn':
- return self.RunGNIsolate()
- else:
- return self.Build('%s_run' % self.args.target[0])
+ return self.RunGNIsolate()
def CmdLookup(self):
vals = self.Lookup()
- if vals['type'] == 'gn':
+ gn_args = self.GNArgs(vals, expand_imports=self.args.recursive)
+ if self.args.quiet or self.args.recursive:
+ self.Print(gn_args, end='')
+ else:
cmd = self.GNCmd('gen', '_path_')
- gn_args = self.GNArgs(vals)
self.Print('\nWriting """\\\n%s""" to _path_/args.gn.\n' % gn_args)
env = None
- else:
- cmd, env = self.GYPCmd('_path_', vals)
- self.PrintCmd(cmd, env)
+ self.PrintCmd(cmd, env)
return 0
def CmdRun(self):
@@ -355,33 +336,87 @@
build_dir = self.args.path[0]
target = self.args.target[0]
- if vals['type'] == 'gn':
- if self.args.build:
- ret = self.Build(target)
- if ret:
- return ret
- ret = self.RunGNIsolate()
+ if self.args.build:
+ ret = self.Build(target)
if ret:
return ret
- else:
- ret = self.Build('%s_run' % target)
- if ret:
- return ret
+ ret = self.RunGNIsolate()
+ if ret:
+ return ret
+ if self.args.swarmed:
+ return self._RunUnderSwarming(build_dir, target)
+ else:
+ return self._RunLocallyIsolated(build_dir, target)
+
+ def _RunUnderSwarming(self, build_dir, target):
+ # TODO(dpranke): Look up the information for the target in
+ # the //testing/buildbot.json file, if possible, so that we
+ # can determine the isolate target, command line, and additional
+ # swarming parameters, if possible.
+ #
+ # TODO(dpranke): Also, add support for sharding and merging results.
+ dimensions = []
+ for k, v in self._DefaultDimensions() + self.args.dimensions:
+ dimensions += ['-d', k, v]
+
+ cmd = [
+ self.executable,
+ self.PathJoin('tools', 'swarming_client', 'isolate.py'),
+ 'archive',
+ '-s',
+ self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
+ '-I', 'isolateserver.appspot.com',
+ ]
+ ret, out, _ = self.Run(cmd, force_verbose=False)
+ if ret:
+ return ret
+
+ isolated_hash = out.splitlines()[0].split()[0]
+ cmd = [
+ self.executable,
+ self.PathJoin('tools', 'swarming_client', 'swarming.py'),
+ 'run',
+ '-s', isolated_hash,
+ '-I', 'isolateserver.appspot.com',
+ '-S', 'chromium-swarm.appspot.com',
+ ] + dimensions
+ if self.args.extra_args:
+ cmd += ['--'] + self.args.extra_args
+ ret, _, _ = self.Run(cmd, force_verbose=True, buffer_output=False)
+ return ret
+
+ def _RunLocallyIsolated(self, build_dir, target):
cmd = [
self.executable,
self.PathJoin('tools', 'swarming_client', 'isolate.py'),
'run',
'-s',
self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
- ]
+ ]
if self.args.extra_args:
- cmd += ['--'] + self.args.extra_args
-
- ret, _, _ = self.Run(cmd, force_verbose=False, buffer_output=False)
-
+ cmd += ['--'] + self.args.extra_args
+ ret, _, _ = self.Run(cmd, force_verbose=True, buffer_output=False)
return ret
+ def _DefaultDimensions(self):
+ if not self.args.default_dimensions:
+ return []
+
+ # This code is naive and just picks reasonable defaults per platform.
+ if self.platform == 'darwin':
+ os_dim = ('os', 'Mac-10.12')
+ elif self.platform.startswith('linux'):
+ os_dim = ('os', 'Ubuntu-16.04')
+ elif self.platform == 'win32':
+ os_dim = ('os', 'Windows-10')
+ else:
+ raise MBErr('unrecognized platform string "%s"' % self.platform)
+
+ return [('pool', 'Chrome'),
+ ('cpu', 'x86-64'),
+ os_dim]
+
def CmdBuildbucket(self):
self.ReadConfigFile()
@@ -462,154 +497,26 @@
self.Print('mb config file %s looks ok.' % self.args.config_file)
return 0
- def CmdAudit(self):
- """Track the progress of the GYP->GN migration on the bots."""
-
- # First, make sure the config file is okay, but don't print anything
- # if it is (it will throw an error if it isn't).
- self.CmdValidate(print_ok=False)
-
- stats = OrderedDict()
- STAT_MASTER_ONLY = 'Master only'
- STAT_CONFIG_ONLY = 'Config only'
- STAT_TBD = 'Still TBD'
- STAT_GYP = 'Still GYP'
- STAT_DONE = 'Done (on GN)'
- stats[STAT_MASTER_ONLY] = 0
- stats[STAT_CONFIG_ONLY] = 0
- stats[STAT_TBD] = 0
- stats[STAT_GYP] = 0
- stats[STAT_DONE] = 0
-
- def PrintBuilders(heading, builders, notes):
- stats.setdefault(heading, 0)
- stats[heading] += len(builders)
- if builders:
- self.Print(' %s:' % heading)
- for builder in sorted(builders):
- self.Print(' %s%s' % (builder, notes[builder]))
-
- self.ReadConfigFile()
-
- masters = self.args.master or self.masters
- for master in sorted(masters):
- url = self.args.url_template.replace('{master}', master)
-
- self.Print('Auditing %s' % master)
-
- MASTERS_TO_SKIP = (
- 'client.skia',
- 'client.v8.fyi',
- 'tryserver.v8',
- )
- if master in MASTERS_TO_SKIP:
- # Skip these bots because converting them is the responsibility of
- # those teams and out of scope for the Chromium migration to GN.
- self.Print(' Skipped (out of scope)')
- self.Print('')
- continue
-
- INTERNAL_MASTERS = ('official.desktop', 'official.desktop.continuous',
- 'internal.client.kitchensync')
- if master in INTERNAL_MASTERS and not self.args.internal:
- # Skip these because the servers aren't accessible by default ...
- self.Print(' Skipped (internal)')
- self.Print('')
- continue
-
- try:
- # Fetch the /builders contents from the buildbot master. The
- # keys of the dict are the builder names themselves.
- json_contents = self.Fetch(url)
- d = json.loads(json_contents)
- except Exception as e:
- self.Print(str(e))
- return 1
-
- config_builders = set(self.masters[master])
- master_builders = set(d.keys())
- both = master_builders & config_builders
- master_only = master_builders - config_builders
- config_only = config_builders - master_builders
- tbd = set()
- gyp = set()
- done = set()
- notes = {builder: '' for builder in config_builders | master_builders}
-
- for builder in both:
- config = self.masters[master][builder]
- if config == 'tbd':
- tbd.add(builder)
- elif isinstance(config, dict):
- vals = self.FlattenConfig(config.values()[0])
- if vals['type'] == 'gyp':
- gyp.add(builder)
- else:
- done.add(builder)
- elif config.startswith('//'):
- done.add(builder)
- else:
- vals = self.FlattenConfig(config)
- if vals['type'] == 'gyp':
- gyp.add(builder)
- else:
- done.add(builder)
-
- if self.args.check_compile and (tbd or master_only):
- either = tbd | master_only
- for builder in either:
- notes[builder] = ' (' + self.CheckCompile(master, builder) +')'
-
- if master_only or config_only or tbd or gyp:
- PrintBuilders(STAT_MASTER_ONLY, master_only, notes)
- PrintBuilders(STAT_CONFIG_ONLY, config_only, notes)
- PrintBuilders(STAT_TBD, tbd, notes)
- PrintBuilders(STAT_GYP, gyp, notes)
- else:
- self.Print(' All GN!')
-
- stats[STAT_DONE] += len(done)
-
- self.Print('')
-
- fmt = '{:<27} {:>4}'
- self.Print(fmt.format('Totals', str(sum(int(v) for v in stats.values()))))
- self.Print(fmt.format('-' * 27, '----'))
- for stat, count in stats.items():
- self.Print(fmt.format(stat, str(count)))
-
- return 0
-
def GetConfig(self):
build_dir = self.args.path[0]
vals = self.DefaultVals()
if self.args.builder or self.args.master or self.args.config:
vals = self.Lookup()
- if vals['type'] == 'gn':
- # Re-run gn gen in order to ensure the config is consistent with the
- # build dir.
- self.RunGNGen(vals)
+ # Re-run gn gen in order to ensure the config is consistent with the
+ # build dir.
+ self.RunGNGen(vals)
return vals
- mb_type_path = self.PathJoin(self.ToAbsPath(build_dir), 'mb_type')
- if not self.Exists(mb_type_path):
- toolchain_path = self.PathJoin(self.ToAbsPath(build_dir),
- 'toolchain.ninja')
- if not self.Exists(toolchain_path):
- self.Print('Must either specify a path to an existing GN build dir '
- 'or pass in a -m/-b pair or a -c flag to specify the '
- 'configuration')
- return {}
- else:
- mb_type = 'gn'
- else:
- mb_type = self.ReadFile(mb_type_path).strip()
+ toolchain_path = self.PathJoin(self.ToAbsPath(build_dir),
+ 'toolchain.ninja')
+ if not self.Exists(toolchain_path):
+ self.Print('Must either specify a path to an existing GN build dir '
+ 'or pass in a -m/-b pair or a -c flag to specify the '
+ 'configuration')
+ return {}
- if mb_type == 'gn':
- vals['gn_args'] = self.GNArgsFromDir(build_dir)
- vals['type'] = mb_type
-
+ vals['gn_args'] = self.GNArgsFromDir(build_dir)
return vals
def GNArgsFromDir(self, build_dir):
@@ -641,14 +548,6 @@
raise MBErr('Config "%s" not found in %s' %
(config, self.args.config_file))
vals = self.FlattenConfig(config)
-
- # Do some basic sanity checking on the config so that we
- # don't have to do this in every caller.
- if 'type' not in vals:
- vals['type'] = 'gn'
- assert vals['type'] in ('gn', 'gyp'), (
- 'Unknown meta-build type "%s"' % vals['gn_args'])
-
return vals
def ReadIOSBotConfig(self):
@@ -660,17 +559,10 @@
return {}
contents = json.loads(self.ReadFile(path))
- gyp_vals = contents.get('GYP_DEFINES', {})
- if isinstance(gyp_vals, dict):
- gyp_defines = ' '.join('%s=%s' % (k, v) for k, v in gyp_vals.items())
- else:
- gyp_defines = ' '.join(gyp_vals)
gn_args = ' '.join(contents.get('gn_args', []))
vals = self.DefaultVals()
vals['gn_args'] = gn_args
- vals['gyp_defines'] = gyp_defines
- vals['type'] = contents.get('mb_type', 'gn')
return vals
def ReadConfigFile(self):
@@ -689,14 +581,26 @@
self.mixins = contents['mixins']
def ReadIsolateMap(self):
- if not self.Exists(self.args.isolate_map_file):
- raise MBErr('isolate map file not found at %s' %
- self.args.isolate_map_file)
- try:
- return ast.literal_eval(self.ReadFile(self.args.isolate_map_file))
- except SyntaxError as e:
- raise MBErr('Failed to parse isolate map file "%s": %s' %
- (self.args.isolate_map_file, e))
+ if not self.args.isolate_map_files:
+ self.args.isolate_map_files = [self.default_isolate_map]
+
+ for f in self.args.isolate_map_files:
+ if not self.Exists(f):
+ raise MBErr('isolate map file not found at %s' % f)
+ isolate_maps = {}
+ for isolate_map in self.args.isolate_map_files:
+ try:
+ isolate_map = ast.literal_eval(self.ReadFile(isolate_map))
+ duplicates = set(isolate_map).intersection(isolate_maps)
+ if duplicates:
+ raise MBErr(
+ 'Duplicate targets in isolate map files: %s.' %
+ ', '.join(duplicates))
+ isolate_maps.update(isolate_map)
+ except SyntaxError as e:
+ raise MBErr(
+ 'Failed to parse isolate map file "%s": %s' % (isolate_map, e))
+ return isolate_maps
def ConfigFromArgs(self):
if self.args.config:
@@ -747,9 +651,6 @@
'args_file': '',
'cros_passthrough': False,
'gn_args': '',
- 'gyp_defines': '',
- 'gyp_crosscompile': False,
- 'type': 'gn',
}
def FlattenMixins(self, mixins, vals, visited):
@@ -773,50 +674,11 @@
vals['gn_args'] += ' ' + mixin_vals['gn_args']
else:
vals['gn_args'] = mixin_vals['gn_args']
- if 'gyp_crosscompile' in mixin_vals:
- vals['gyp_crosscompile'] = mixin_vals['gyp_crosscompile']
- if 'gyp_defines' in mixin_vals:
- if vals['gyp_defines']:
- vals['gyp_defines'] += ' ' + mixin_vals['gyp_defines']
- else:
- vals['gyp_defines'] = mixin_vals['gyp_defines']
- if 'type' in mixin_vals:
- vals['type'] = mixin_vals['type']
if 'mixins' in mixin_vals:
self.FlattenMixins(mixin_vals['mixins'], vals, visited)
return vals
- def ClobberIfNeeded(self, vals):
- path = self.args.path[0]
- build_dir = self.ToAbsPath(path)
- mb_type_path = self.PathJoin(build_dir, 'mb_type')
- needs_clobber = False
- new_mb_type = vals['type']
- if self.Exists(build_dir):
- if self.Exists(mb_type_path):
- old_mb_type = self.ReadFile(mb_type_path)
- if old_mb_type != new_mb_type:
- self.Print("Build type mismatch: was %s, will be %s, clobbering %s" %
- (old_mb_type, new_mb_type, path))
- needs_clobber = True
- else:
- # There is no 'mb_type' file in the build directory, so this probably
- # means that the prior build(s) were not done through mb, and we
- # have no idea if this was a GYP build or a GN build. Clobber it
- # to be safe.
- self.Print("%s/mb_type missing, clobbering to be safe" % path)
- needs_clobber = True
-
- if self.args.dryrun:
- return
-
- if needs_clobber:
- self.RemoveDirectory(build_dir)
-
- self.MaybeMakeDirectory(build_dir)
- self.WriteFile(mb_type_path, new_mb_type)
-
def RunGNGen(self, vals, compute_grit_inputs_for_analyze=False):
build_dir = self.args.path[0]
@@ -853,14 +715,18 @@
self.WriteFile(gn_runtime_deps_path, '\n'.join(labels) + '\n')
cmd.append('--runtime-deps-list-file=%s' % gn_runtime_deps_path)
- ret, _, _ = self.Run(cmd)
+ ret, output, _ = self.Run(cmd)
if ret:
+ if self.args.json_output:
+ # write errors to json.output
+ self.WriteJSON({'output': output}, self.args.json_output)
# If `gn gen` failed, we should exit early rather than trying to
# generate isolates. Run() will have already logged any error output.
self.Print('GN gen failed: %d' % ret)
return ret
android = 'target_os="android"' in vals['gn_args']
+ fuchsia = 'target_os="fuchsia"' in vals['gn_args']
for target in swarming_targets:
if android:
# Android targets may be either android_apk or executable. The former
@@ -870,6 +736,11 @@
runtime_deps_targets = [
target + '.runtime_deps',
'obj/%s.stamp.runtime_deps' % label.replace(':', '/')]
+ elif fuchsia:
+ # Only emit a runtime deps file for the group() target on Fuchsia.
+ label = isolate_map[target]['label']
+ runtime_deps_targets = [
+ 'obj/%s.stamp.runtime_deps' % label.replace(':', '/')]
elif (isolate_map[target]['type'] == 'script' or
isolate_map[target].get('label_type') == 'group'):
# For script targets, the build target is usually a group,
@@ -987,11 +858,17 @@
else:
subdir, exe = 'win', 'gn.exe'
- gn_path = self.PathJoin(self.chromium_src_dir, 'buildtools', subdir, exe)
+ arch = platform.machine()
+ if (arch.startswith('s390') or arch.startswith('ppc') or
+ self.platform.startswith('aix')):
+ # use gn in PATH
+ gn_path = 'gn'
+ else:
+ gn_path = self.PathJoin(self.chromium_src_dir, 'buildtools', subdir, exe)
return [gn_path, subcommand, path] + list(args)
- def GNArgs(self, vals):
+ def GNArgs(self, vals, expand_imports=False):
if vals['cros_passthrough']:
if not 'GN_ARGS' in os.environ:
raise MBErr('MB is expecting GN_ARGS to be in the environment')
@@ -1013,47 +890,24 @@
if android_version_name:
gn_args += ' android_default_version_name="%s"' % android_version_name
- # Canonicalize the arg string into a sorted, newline-separated list
- # of key-value pairs, and de-dup the keys if need be so that only
- # the last instance of each arg is listed.
- gn_args = gn_helpers.ToGNString(gn_helpers.FromGNArgs(gn_args))
+ args_gn_lines = []
+ parsed_gn_args = {}
args_file = vals.get('args_file', None)
if args_file:
- gn_args = ('import("%s")\n' % vals['args_file']) + gn_args
- return gn_args
+ if expand_imports:
+ content = self.ReadFile(self.ToAbsPath(args_file))
+ parsed_gn_args = gn_helpers.FromGNArgs(content)
+ else:
+ args_gn_lines.append('import("%s")' % args_file)
- def RunGYPGen(self, vals):
- path = self.args.path[0]
+ # Canonicalize the arg string into a sorted, newline-separated list
+ # of key-value pairs, and de-dup the keys if need be so that only
+ # the last instance of each arg is listed.
+ parsed_gn_args.update(gn_helpers.FromGNArgs(gn_args))
+ args_gn_lines.append(gn_helpers.ToGNString(parsed_gn_args))
- output_dir = self.ParseGYPConfigPath(path)
- cmd, env = self.GYPCmd(output_dir, vals)
- ret, _, _ = self.Run(cmd, env=env)
- return ret
-
- def RunGYPAnalyze(self, vals):
- output_dir = self.ParseGYPConfigPath(self.args.path[0])
- if self.args.verbose:
- inp = self.ReadInputJSON(['files', 'test_targets',
- 'additional_compile_targets'])
- self.Print()
- self.Print('analyze input:')
- self.PrintJSON(inp)
- self.Print()
-
- cmd, env = self.GYPCmd(output_dir, vals)
- cmd.extend(['-f', 'analyzer',
- '-G', 'config_path=%s' % self.args.input_path[0],
- '-G', 'analyzer_output_path=%s' % self.args.output_path[0]])
- ret, _, _ = self.Run(cmd, env=env)
- if not ret and self.args.verbose:
- outp = json.loads(self.ReadFile(self.args.output_path[0]))
- self.Print()
- self.Print('analyze output:')
- self.PrintJSON(outp)
- self.Print()
-
- return ret
+ return '\n'.join(args_gn_lines)
def ToAbsPath(self, build_path, *comps):
return self.PathJoin(self.chromium_src_dir,
@@ -1066,86 +920,6 @@
return path[2:].replace('/', self.sep)
return self.RelPath(path, self.chromium_src_dir)
- def ParseGYPConfigPath(self, path):
- rpath = self.ToSrcRelPath(path)
- output_dir, _, _ = rpath.rpartition(self.sep)
- return output_dir
-
- def GYPCmd(self, output_dir, vals):
- if vals['cros_passthrough']:
- if not 'GYP_DEFINES' in os.environ:
- raise MBErr('MB is expecting GYP_DEFINES to be in the environment')
- gyp_defines = os.environ['GYP_DEFINES']
- if not 'chromeos=1' in gyp_defines:
- raise MBErr('GYP_DEFINES is missing chromeos=1: (GYP_DEFINES=%s)' %
- gyp_defines)
- else:
- gyp_defines = vals['gyp_defines']
-
- goma_dir = self.args.goma_dir
-
- # GYP uses shlex.split() to split the gyp defines into separate arguments,
- # so we can support backslashes and and spaces in arguments by quoting
- # them, even on Windows, where this normally wouldn't work.
- if goma_dir and ('\\' in goma_dir or ' ' in goma_dir):
- goma_dir = "'%s'" % goma_dir
-
- if goma_dir:
- gyp_defines += ' gomadir=%s' % goma_dir
-
- android_version_code = self.args.android_version_code
- if android_version_code:
- gyp_defines += ' app_manifest_version_code=%s' % android_version_code
-
- android_version_name = self.args.android_version_name
- if android_version_name:
- gyp_defines += ' app_manifest_version_name=%s' % android_version_name
-
- cmd = [
- self.executable,
- self.args.gyp_script,
- '-G',
- 'output_dir=' + output_dir,
- ]
-
- # Ensure that we have an environment that only contains
- # the exact values of the GYP variables we need.
- env = os.environ.copy()
-
- # This is a terrible hack to work around the fact that
- # //tools/clang/scripts/update.py is invoked by GYP and GN but
- # currently relies on an environment variable to figure out
- # what revision to embed in the command line #defines.
- # For GN, we've made this work via a gn arg that will cause update.py
- # to get an additional command line arg, but getting that to work
- # via GYP_DEFINES has proven difficult, so we rewrite the GYP_DEFINES
- # to get rid of the arg and add the old var in, instead.
- # See crbug.com/582737 for more on this. This can hopefully all
- # go away with GYP.
- m = re.search('llvm_force_head_revision=1\s*', gyp_defines)
- if m:
- env['LLVM_FORCE_HEAD_REVISION'] = '1'
- gyp_defines = gyp_defines.replace(m.group(0), '')
-
- # This is another terrible hack to work around the fact that
- # GYP sets the link concurrency to use via the GYP_LINK_CONCURRENCY
- # environment variable, and not via a proper GYP_DEFINE. See
- # crbug.com/611491 for more on this.
- m = re.search('gyp_link_concurrency=(\d+)(\s*)', gyp_defines)
- if m:
- env['GYP_LINK_CONCURRENCY'] = m.group(1)
- gyp_defines = gyp_defines.replace(m.group(0), '')
-
- env['GYP_GENERATORS'] = 'ninja'
- if 'GYP_CHROMIUM_NO_ACTION' in env:
- del env['GYP_CHROMIUM_NO_ACTION']
- if 'GYP_CROSSCOMPILE' in env:
- del env['GYP_CROSSCOMPILE']
- env['GYP_DEFINES'] = gyp_defines
- if vals['gyp_crosscompile']:
- env['GYP_CROSSCOMPILE'] = '1'
- return cmd, env
-
def RunGNAnalyze(self, vals):
# Analyze runs before 'gn gen' now, so we need to run gn gen
# in order to ensure that we have a build directory.
@@ -1200,8 +974,11 @@
try:
self.WriteJSON(gn_inp, gn_input_path)
cmd = self.GNCmd('analyze', build_path, gn_input_path, gn_output_path)
- ret, _, _ = self.Run(cmd, force_verbose=True)
+ ret, output, _ = self.Run(cmd, force_verbose=True)
if ret:
+ if self.args.json_output:
+ # write errors to json.output
+ self.WriteJSON({'output': output}, self.args.json_output)
return ret
gn_outp_str = self.ReadFile(gn_output_path)
@@ -1347,9 +1124,6 @@
if env and var in env:
self.Print('%s%s=%s' % (env_prefix, var, env_quoter(env[var])))
- print_env('GYP_CROSSCOMPILE')
- print_env('GYP_DEFINES')
- print_env('GYP_LINK_CONCURRENCY')
print_env('LLVM_FORCE_HEAD_REVISION')
if cmd[0] == self.executable:
@@ -1416,7 +1190,7 @@
def MaybeMakeDirectory(self, path):
try:
os.makedirs(path)
- except OSError, e:
+ except OSError as e:
if e.errno != errno.EEXIST:
raise
@@ -1486,7 +1260,6 @@
def QuoteForCmd(arg):
# First, escape the arg so that CommandLineToArgvW will parse it properly.
- # From //tools/gyp/pylib/gyp/msvs_emulation.py:23.
if arg == '' or ' ' in arg or '"' in arg:
quote_re = re.compile(r'(\\*)"')
arg = '"%s"' % (quote_re.sub(lambda mo: 2 * mo.group(1) + '\\"', arg))
diff --git a/src/v8/tools/mb/mb_unittest.py b/src/v8/tools/mb/mb_unittest.py
index 1576375..a22686a 100755
--- a/src/v8/tools/mb/mb_unittest.py
+++ b/src/v8/tools/mb/mb_unittest.py
@@ -65,8 +65,6 @@
self.files[path] = contents
def Call(self, cmd, env=None, buffer_output=True):
- if env:
- self.cross_compile = env.get('GYP_CROSSCOMPILE')
self.calls.append(cmd)
if self.cmds:
return self.cmds.pop(0)
@@ -112,13 +110,10 @@
'masters': {
'chromium': {},
'fake_master': {
- 'fake_builder': 'gyp_rel_bot',
- 'fake_gn_builder': 'gn_rel_bot',
- 'fake_gyp_crosscompile_builder': 'gyp_crosscompile',
- 'fake_gn_debug_builder': 'gn_debug_goma',
- 'fake_gyp_builder': 'gyp_debug',
- 'fake_gn_args_bot': '//build/args/bots/fake_master/fake_gn_args_bot.gn',
- 'fake_multi_phase': { 'phase_1': 'gn_phase_1', 'phase_2': 'gn_phase_2'},
+ 'fake_builder': 'rel_bot',
+ 'fake_debug_builder': 'debug_goma',
+ 'fake_args_bot': '//build/args/bots/fake_master/fake_args_bot.gn',
+ 'fake_multi_phase': { 'phase_1': 'phase_1', 'phase_2': 'phase_2'},
'fake_args_file': 'args_file_goma',
'fake_args_file_twice': 'args_file_twice',
},
@@ -126,38 +121,26 @@
'configs': {
'args_file_goma': ['args_file', 'goma'],
'args_file_twice': ['args_file', 'args_file'],
- 'gyp_rel_bot': ['gyp', 'rel', 'goma'],
- 'gn_debug_goma': ['gn', 'debug', 'goma'],
- 'gyp_debug': ['gyp', 'debug', 'fake_feature1'],
- 'gn_rel_bot': ['gn', 'rel', 'goma'],
- 'gyp_crosscompile': ['gyp', 'crosscompile'],
- 'gn_phase_1': ['gn', 'phase_1'],
- 'gn_phase_2': ['gn', 'phase_2'],
+ 'rel_bot': ['rel', 'goma', 'fake_feature1'],
+ 'debug_goma': ['debug', 'goma'],
+ 'phase_1': ['phase_1'],
+ 'phase_2': ['phase_2'],
},
'mixins': {
- 'crosscompile': {
- 'gyp_crosscompile': True,
- },
'fake_feature1': {
'gn_args': 'enable_doom_melon=true',
- 'gyp_defines': 'doom_melon=1',
},
- 'gyp': {'type': 'gyp'},
- 'gn': {'type': 'gn'},
'goma': {
'gn_args': 'use_goma=true',
- 'gyp_defines': 'goma=1',
},
'args_file': {
'args_file': '//build/args/fake.gn',
},
'phase_1': {
'gn_args': 'phase=1',
- 'gyp_args': 'phase=1',
},
'phase_2': {
'gn_args': 'phase=2',
- 'gyp_args': 'phase=2',
},
'rel': {
'gn_args': 'is_debug=false',
@@ -169,28 +152,6 @@
}
"""
-GYP_HACKS_CONFIG = """\
-{
- 'masters': {
- 'chromium': {},
- 'fake_master': {
- 'fake_builder': 'fake_config',
- },
- },
- 'configs': {
- 'fake_config': ['fake_mixin'],
- },
- 'mixins': {
- 'fake_mixin': {
- 'type': 'gyp',
- 'gn_args': '',
- 'gyp_defines':
- ('foo=bar llvm_force_head_revision=1 '
- 'gyp_link_concurrency=1 baz=1'),
- },
- },
-}
-"""
TRYSERVER_CONFIG = """\
{
@@ -229,7 +190,7 @@
},
}''')
mbw.files.setdefault(
- mbw.ToAbsPath('//build/args/bots/fake_master/fake_gn_args_bot.gn'),
+ mbw.ToAbsPath('//build/args/bots/fake_master/fake_args_bot.gn'),
'is_debug = false\n')
if files:
for path, contents in files.items():
@@ -249,37 +210,6 @@
self.assertEqual(mbw.err, err)
return mbw
- def test_clobber(self):
- files = {
- '/fake_src/out/Debug': None,
- '/fake_src/out/Debug/mb_type': None,
- }
- mbw = self.fake_mbw(files)
-
- # The first time we run this, the build dir doesn't exist, so no clobber.
- self.check(['gen', '-c', 'gn_debug_goma', '//out/Debug'], mbw=mbw, ret=0)
- self.assertEqual(mbw.rmdirs, [])
- self.assertEqual(mbw.files['/fake_src/out/Debug/mb_type'], 'gn')
-
- # The second time we run this, the build dir exists and matches, so no
- # clobber.
- self.check(['gen', '-c', 'gn_debug_goma', '//out/Debug'], mbw=mbw, ret=0)
- self.assertEqual(mbw.rmdirs, [])
- self.assertEqual(mbw.files['/fake_src/out/Debug/mb_type'], 'gn')
-
- # Now we switch build types; this should result in a clobber.
- self.check(['gen', '-c', 'gyp_debug', '//out/Debug'], mbw=mbw, ret=0)
- self.assertEqual(mbw.rmdirs, ['/fake_src/out/Debug'])
- self.assertEqual(mbw.files['/fake_src/out/Debug/mb_type'], 'gyp')
-
- # Now we delete mb_type; this checks the case where the build dir
- # exists but wasn't populated by mb; this should also result in a clobber.
- del mbw.files['/fake_src/out/Debug/mb_type']
- self.check(['gen', '-c', 'gyp_debug', '//out/Debug'], mbw=mbw, ret=0)
- self.assertEqual(mbw.rmdirs,
- ['/fake_src/out/Debug', '/fake_src/out/Debug'])
- self.assertEqual(mbw.files['/fake_src/out/Debug/mb_type'], 'gyp')
-
def test_analyze(self):
files = {'/tmp/in.json': '''{\
"files": ["foo/foo_unittest.cc"],
@@ -295,7 +225,7 @@
mbw = self.fake_mbw(files)
mbw.Call = lambda cmd, env=None, buffer_output=True: (0, '', '')
- self.check(['analyze', '-c', 'gn_debug_goma', '//out/Default',
+ self.check(['analyze', '-c', 'debug_goma', '//out/Default',
'/tmp/in.json', '/tmp/out.json'], mbw=mbw, ret=0)
out = json.loads(mbw.files['/tmp/out.json'])
self.assertEqual(out, {
@@ -319,7 +249,7 @@
mbw = self.fake_mbw(files)
mbw.Call = lambda cmd, env=None, buffer_output=True: (0, '', '')
- self.check(['analyze', '-c', 'gn_debug_goma', '//out/Default',
+ self.check(['analyze', '-c', 'debug_goma', '//out/Default',
'/tmp/in.json', '/tmp/out.json'], mbw=mbw, ret=0)
out = json.loads(mbw.files['/tmp/out.json'])
@@ -342,7 +272,7 @@
mbw = self.fake_mbw(files)
mbw.Call = lambda cmd, env=None, buffer_output=True: (0, '', '')
- self.check(['analyze', '-c', 'gn_debug_goma', '//out/Default',
+ self.check(['analyze', '-c', 'debug_goma', '//out/Default',
'/tmp/in.json', '/tmp/out.json'], mbw=mbw, ret=0)
out = json.loads(mbw.files['/tmp/out.json'])
@@ -354,7 +284,7 @@
self.assertEqual(['all', 'foo_unittests'], out['compile_targets'])
def test_analyze_handles_way_too_many_results(self):
- too_many_files = ', '.join(['"//foo:foo%d"' % i for i in xrange(4 * 1024)])
+ too_many_files = ', '.join(['"//foo:foo%d"' % i for i in range(4 * 1024)])
files = {'/tmp/in.json': '''{\
"files": ["foo/foo_unittest.cc"],
"test_targets": ["foo_unittests"],
@@ -369,7 +299,7 @@
mbw = self.fake_mbw(files)
mbw.Call = lambda cmd, env=None, buffer_output=True: (0, '', '')
- self.check(['analyze', '-c', 'gn_debug_goma', '//out/Default',
+ self.check(['analyze', '-c', 'debug_goma', '//out/Default',
'/tmp/in.json', '/tmp/out.json'], mbw=mbw, ret=0)
out = json.loads(mbw.files['/tmp/out.json'])
@@ -379,9 +309,9 @@
# test_targets and additional_compile_targets.
self.assertEqual(['all', 'foo_unittests'], out['compile_targets'])
- def test_gn_gen(self):
+ def test_gen(self):
mbw = self.fake_mbw()
- self.check(['gen', '-c', 'gn_debug_goma', '//out/Default', '-g', '/goma'],
+ self.check(['gen', '-c', 'debug_goma', '//out/Default', '-g', '/goma'],
mbw=mbw, ret=0)
self.assertMultiLineEqual(mbw.files['/fake_src/out/Default/args.gn'],
('goma_dir = "/goma"\n'
@@ -394,7 +324,7 @@
mbw.out)
mbw = self.fake_mbw(win32=True)
- self.check(['gen', '-c', 'gn_debug_goma', '-g', 'c:\\goma', '//out/Debug'],
+ self.check(['gen', '-c', 'debug_goma', '-g', 'c:\\goma', '//out/Debug'],
mbw=mbw, ret=0)
self.assertMultiLineEqual(mbw.files['c:\\fake_src\\out\\Debug\\args.gn'],
('goma_dir = "c:\\\\goma"\n'
@@ -404,14 +334,14 @@
'--check\n', mbw.out)
mbw = self.fake_mbw()
- self.check(['gen', '-m', 'fake_master', '-b', 'fake_gn_args_bot',
+ self.check(['gen', '-m', 'fake_master', '-b', 'fake_args_bot',
'//out/Debug'],
mbw=mbw, ret=0)
self.assertEqual(
mbw.files['/fake_src/out/Debug/args.gn'],
- 'import("//build/args/bots/fake_master/fake_gn_args_bot.gn")\n')
+ 'import("//build/args/bots/fake_master/fake_args_bot.gn")\n')
- def test_gn_gen_args_file_mixins(self):
+ def test_gen_args_file_mixins(self):
mbw = self.fake_mbw()
self.check(['gen', '-m', 'fake_master', '-b', 'fake_args_file',
'//out/Debug'], mbw=mbw, ret=0)
@@ -425,14 +355,12 @@
self.check(['gen', '-m', 'fake_master', '-b', 'fake_args_file_twice',
'//out/Debug'], mbw=mbw, ret=1)
- def test_gn_gen_fails(self):
+ def test_gen_fails(self):
mbw = self.fake_mbw()
mbw.Call = lambda cmd, env=None, buffer_output=True: (1, '', '')
- self.check(['gen', '-c', 'gn_debug_goma', '//out/Default'], mbw=mbw, ret=1)
+ self.check(['gen', '-c', 'debug_goma', '//out/Default'], mbw=mbw, ret=1)
- # TODO(machenbach): Comment back in after swarming file parameter is used.
- """
- def test_gn_gen_swarming(self):
+ def test_gen_swarming(self):
files = {
'/tmp/swarming_targets': 'base_unittests\n',
'/fake_src/testing/buildbot/gn_isolate_map.pyl': (
@@ -448,7 +376,7 @@
}
mbw = self.fake_mbw(files)
self.check(['gen',
- '-c', 'gn_debug_goma',
+ '-c', 'debug_goma',
'--swarming-targets-file', '/tmp/swarming_targets',
'//out/Default'], mbw=mbw, ret=0)
self.assertIn('/fake_src/out/Default/base_unittests.isolate',
@@ -456,7 +384,7 @@
self.assertIn('/fake_src/out/Default/base_unittests.isolated.gen.json',
mbw.files)
- def test_gn_gen_swarming_script(self):
+ def test_gen_swarming_script(self):
files = {
'/tmp/swarming_targets': 'cc_perftests\n',
'/fake_src/testing/buildbot/gn_isolate_map.pyl': (
@@ -473,7 +401,7 @@
}
mbw = self.fake_mbw(files=files, win32=True)
self.check(['gen',
- '-c', 'gn_debug_goma',
+ '-c', 'debug_goma',
'--swarming-targets-file', '/tmp/swarming_targets',
'--isolate-map-file',
'/fake_src/testing/buildbot/gn_isolate_map.pyl',
@@ -482,9 +410,77 @@
mbw.files)
self.assertIn('c:\\fake_src\\out\\Default\\cc_perftests.isolated.gen.json',
mbw.files)
- """ # pylint: disable=pointless-string-statement
- def test_gn_isolate(self):
+
+ def test_multiple_isolate_maps(self):
+ files = {
+ '/tmp/swarming_targets': 'cc_perftests\n',
+ '/fake_src/testing/buildbot/gn_isolate_map.pyl': (
+ "{'cc_perftests': {"
+ " 'label': '//cc:cc_perftests',"
+ " 'type': 'raw',"
+ " 'args': [],"
+ "}}\n"
+ ),
+ '/fake_src/testing/buildbot/gn_isolate_map2.pyl': (
+ "{'cc_perftests2': {"
+ " 'label': '//cc:cc_perftests',"
+ " 'type': 'raw',"
+ " 'args': [],"
+ "}}\n"
+ ),
+ 'c:\\fake_src\out\Default\cc_perftests.exe.runtime_deps': (
+ "cc_perftests\n"
+ ),
+ }
+ mbw = self.fake_mbw(files=files, win32=True)
+ self.check(['gen',
+ '-c', 'debug_goma',
+ '--swarming-targets-file', '/tmp/swarming_targets',
+ '--isolate-map-file',
+ '/fake_src/testing/buildbot/gn_isolate_map.pyl',
+ '--isolate-map-file',
+ '/fake_src/testing/buildbot/gn_isolate_map2.pyl',
+ '//out/Default'], mbw=mbw, ret=0)
+ self.assertIn('c:\\fake_src\\out\\Default\\cc_perftests.isolate',
+ mbw.files)
+ self.assertIn('c:\\fake_src\\out\\Default\\cc_perftests.isolated.gen.json',
+ mbw.files)
+
+
+ def test_duplicate_isolate_maps(self):
+ files = {
+ '/tmp/swarming_targets': 'cc_perftests\n',
+ '/fake_src/testing/buildbot/gn_isolate_map.pyl': (
+ "{'cc_perftests': {"
+ " 'label': '//cc:cc_perftests',"
+ " 'type': 'raw',"
+ " 'args': [],"
+ "}}\n"
+ ),
+ '/fake_src/testing/buildbot/gn_isolate_map2.pyl': (
+ "{'cc_perftests': {"
+ " 'label': '//cc:cc_perftests',"
+ " 'type': 'raw',"
+ " 'args': [],"
+ "}}\n"
+ ),
+ 'c:\\fake_src\out\Default\cc_perftests.exe.runtime_deps': (
+ "cc_perftests\n"
+ ),
+ }
+ mbw = self.fake_mbw(files=files, win32=True)
+ # Check that passing duplicate targets into mb fails.
+ self.check(['gen',
+ '-c', 'debug_goma',
+ '--swarming-targets-file', '/tmp/swarming_targets',
+ '--isolate-map-file',
+ '/fake_src/testing/buildbot/gn_isolate_map.pyl',
+ '--isolate-map-file',
+ '/fake_src/testing/buildbot/gn_isolate_map2.pyl',
+ '//out/Default'], mbw=mbw, ret=1)
+
+ def test_isolate(self):
files = {
'/fake_src/out/Default/toolchain.ninja': "",
'/fake_src/testing/buildbot/gn_isolate_map.pyl': (
@@ -498,7 +494,7 @@
"base_unittests\n"
),
}
- self.check(['isolate', '-c', 'gn_debug_goma', '//out/Default',
+ self.check(['isolate', '-c', 'debug_goma', '//out/Default',
'base_unittests'], files=files, ret=0)
# test running isolate on an existing build_dir
@@ -506,11 +502,10 @@
self.check(['isolate', '//out/Default', 'base_unittests'],
files=files, ret=0)
- files['/fake_src/out/Default/mb_type'] = 'gn\n'
self.check(['isolate', '//out/Default', 'base_unittests'],
files=files, ret=0)
- def test_gn_run(self):
+ def test_run(self):
files = {
'/fake_src/testing/buildbot/gn_isolate_map.pyl': (
"{'base_unittests': {"
@@ -523,55 +518,61 @@
"base_unittests\n"
),
}
- self.check(['run', '-c', 'gn_debug_goma', '//out/Default',
+ self.check(['run', '-c', 'debug_goma', '//out/Default',
'base_unittests'], files=files, ret=0)
- def test_gn_lookup(self):
- self.check(['lookup', '-c', 'gn_debug_goma'], ret=0)
+ def test_run_swarmed(self):
+ files = {
+ '/fake_src/testing/buildbot/gn_isolate_map.pyl': (
+ "{'base_unittests': {"
+ " 'label': '//base:base_unittests',"
+ " 'type': 'raw',"
+ " 'args': [],"
+ "}}\n"
+ ),
+ '/fake_src/out/Default/base_unittests.runtime_deps': (
+ "base_unittests\n"
+ ),
+ }
- def test_gn_lookup_goma_dir_expansion(self):
- self.check(['lookup', '-c', 'gn_rel_bot', '-g', '/foo'], ret=0,
+ def run_stub(cmd, **_kwargs):
+ if 'isolate.py' in cmd[1]:
+ return 0, 'fake_hash base_unittests', ''
+ else:
+ return 0, '', ''
+
+ mbw = self.fake_mbw(files=files)
+ mbw.Run = run_stub
+ self.check(['run', '-s', '-c', 'debug_goma', '//out/Default',
+ 'base_unittests'], mbw=mbw, ret=0)
+ self.check(['run', '-s', '-c', 'debug_goma', '-d', 'os', 'Win7',
+ '//out/Default', 'base_unittests'], mbw=mbw, ret=0)
+
+ def test_lookup(self):
+ self.check(['lookup', '-c', 'debug_goma'], ret=0,
out=('\n'
'Writing """\\\n'
+ 'is_debug = true\n'
+ 'use_goma = true\n'
+ '""" to _path_/args.gn.\n\n'
+ '/fake_src/buildtools/linux64/gn gen _path_\n'))
+
+ def test_quiet_lookup(self):
+ self.check(['lookup', '-c', 'debug_goma', '--quiet'], ret=0,
+ out=('is_debug = true\n'
+ 'use_goma = true\n'))
+
+ def test_lookup_goma_dir_expansion(self):
+ self.check(['lookup', '-c', 'rel_bot', '-g', '/foo'], ret=0,
+ out=('\n'
+ 'Writing """\\\n'
+ 'enable_doom_melon = true\n'
'goma_dir = "/foo"\n'
'is_debug = false\n'
'use_goma = true\n'
'""" to _path_/args.gn.\n\n'
'/fake_src/buildtools/linux64/gn gen _path_\n'))
- def test_gyp_analyze(self):
- mbw = self.check(['analyze', '-c', 'gyp_rel_bot', '//out/Release',
- '/tmp/in.json', '/tmp/out.json'], ret=0)
- self.assertIn('analyzer', mbw.calls[0])
-
- def test_gyp_crosscompile(self):
- mbw = self.fake_mbw()
- self.check(['gen', '-c', 'gyp_crosscompile', '//out/Release'],
- mbw=mbw, ret=0)
- self.assertTrue(mbw.cross_compile)
-
- def test_gyp_gen(self):
- self.check(['gen', '-c', 'gyp_rel_bot', '-g', '/goma', '//out/Release'],
- ret=0,
- out=("GYP_DEFINES='goma=1 gomadir=/goma'\n"
- "python build/gyp_chromium -G output_dir=out\n"))
-
- mbw = self.fake_mbw(win32=True)
- self.check(['gen', '-c', 'gyp_rel_bot', '-g', 'c:\\goma', '//out/Release'],
- mbw=mbw, ret=0,
- out=("set GYP_DEFINES=goma=1 gomadir='c:\\goma'\n"
- "python build\\gyp_chromium -G output_dir=out\n"))
-
- def test_gyp_gen_fails(self):
- mbw = self.fake_mbw()
- mbw.Call = lambda cmd, env=None, buffer_output=True: (1, '', '')
- self.check(['gen', '-c', 'gyp_rel_bot', '//out/Release'], mbw=mbw, ret=1)
-
- def test_gyp_lookup_goma_dir_expansion(self):
- self.check(['lookup', '-c', 'gyp_rel_bot', '-g', '/foo'], ret=0,
- out=("GYP_DEFINES='goma=1 gomadir=/foo'\n"
- "python build/gyp_chromium -G output_dir=_path_\n"))
-
def test_help(self):
orig_stdout = sys.stdout
try:
@@ -589,7 +590,7 @@
self.assertIn('Must specify a build --phase', mbw.out)
# Check that passing a --phase to a single-phase builder fails.
- mbw = self.check(['lookup', '-m', 'fake_master', '-b', 'fake_gn_builder',
+ mbw = self.check(['lookup', '-m', 'fake_master', '-b', 'fake_builder',
'--phase', 'phase_1'], ret=1)
self.assertIn('Must not specify a build --phase', mbw.out)
@@ -607,20 +608,23 @@
'--phase', 'phase_2'], ret=0)
self.assertIn('phase = 2', mbw.out)
+ def test_recursive_lookup(self):
+ files = {
+ '/fake_src/build/args/fake.gn': (
+ 'enable_doom_melon = true\n'
+ 'enable_antidoom_banana = true\n'
+ )
+ }
+ self.check(['lookup', '-m', 'fake_master', '-b', 'fake_args_file',
+ '--recursive'], files=files, ret=0,
+ out=('enable_antidoom_banana = true\n'
+ 'enable_doom_melon = true\n'
+ 'use_goma = true\n'))
+
def test_validate(self):
mbw = self.fake_mbw()
self.check(['validate'], mbw=mbw, ret=0)
- def test_gyp_env_hacks(self):
- mbw = self.fake_mbw()
- mbw.files[mbw.default_config] = GYP_HACKS_CONFIG
- self.check(['lookup', '-c', 'fake_config'], mbw=mbw,
- ret=0,
- out=("GYP_DEFINES='foo=bar baz=1'\n"
- "GYP_LINK_CONCURRENCY=1\n"
- "LLVM_FORCE_HEAD_REVISION=1\n"
- "python build/gyp_chromium -G output_dir=_path_\n"))
-
def test_buildbucket(self):
mbw = self.fake_mbw()
mbw.files[mbw.default_config] = TRYSERVER_CONFIG
diff --git a/src/v8/tools/mips_toolchain.tar.gz.sha1 b/src/v8/tools/mips_toolchain.tar.gz.sha1
new file mode 100644
index 0000000..8d45723
--- /dev/null
+++ b/src/v8/tools/mips_toolchain.tar.gz.sha1
@@ -0,0 +1 @@
+d51b5d903340262d8d13ecd51054c16a901b3cf3
\ No newline at end of file
diff --git a/src/v8/tools/node/README.md b/src/v8/tools/node/README.md
new file mode 100644
index 0000000..dc16c91
--- /dev/null
+++ b/src/v8/tools/node/README.md
@@ -0,0 +1,12 @@
+# Node.js Backports
+
+We no longer maintain our own backport script.
+
+For backporting V8 changes to Node.js, there is a useful script in
+[node-core-utils][1]. You can use the `git node v8 backport` command, which will
+bump the necessary V8 version numbers depending on the specific branch.
+
+See the [Node.js documentation][2] on V8 backports for a guide.
+
+[1]: https://github.com/nodejs/node-core-utils
+[2]: https://github.com/nodejs/node/blob/master/doc/guides/maintaining-V8.md
diff --git a/src/v8/tools/node/fetch_deps.py b/src/v8/tools/node/fetch_deps.py
index a3e6d74..ee5b629 100755
--- a/src/v8/tools/node/fetch_deps.py
+++ b/src/v8/tools/node/fetch_deps.py
@@ -9,6 +9,9 @@
Usage: fetch_deps.py <v8-path>
"""
+# for py2/py3 compatibility
+from __future__ import print_function
+
import os
import subprocess
import sys
@@ -22,42 +25,43 @@
"managed" : False,
"custom_deps" : {
# These deps are already part of Node.js.
- "v8/base/trace_event/common" : None,
- "v8/testing/gtest" : None,
- "v8/third_party/jinja2" : None,
- "v8/third_party/markupsafe" : None,
+ "v8/base/trace_event/common" : None,
+ "v8/third_party/googletest/src" : None,
# These deps are unnecessary for building.
"v8/test/benchmarks/data" : None,
"v8/testing/gmock" : None,
"v8/test/mozilla/data" : None,
"v8/test/test262/data" : None,
"v8/test/test262/harness" : None,
- "v8/test/wasm-js" : None,
- "v8/third_party/android_tools" : None,
+ "v8/third_party/android_ndk" : None,
+ "v8/third_party/android_sdk" : None,
"v8/third_party/catapult" : None,
"v8/third_party/colorama/src" : None,
+ "v8/third_party/fuchsia-sdk" : None,
"v8/third_party/instrumented_libraries" : None,
- "v8/tools/gyp" : None,
"v8/tools/luci-go" : None,
"v8/tools/swarming_client" : None,
- },
- "custom_vars": {
- "build_for_node" : True,
+ "v8/third_party/qemu-linux-x64" : None,
},
},
]
def EnsureGit(v8_path):
+ def git(args):
+ # shell=True needed on Windows to resolve git.bat.
+ return subprocess.check_output(
+ "git " + args, cwd=v8_path, shell=True).strip()
+
expected_git_dir = os.path.join(v8_path, ".git")
- actual_git_dir = subprocess.check_output(
- ["git", "rev-parse", "--absolute-git-dir"], cwd=v8_path).strip()
+ actual_git_dir = git("rev-parse --absolute-git-dir")
if expected_git_dir == actual_git_dir:
- print "V8 is tracked stand-alone by git."
+ print("V8 is tracked stand-alone by git.")
return False
- print "Initializing temporary git repository in v8."
- subprocess.check_call(["git", "init"], cwd=v8_path)
- subprocess.check_call(["git", "commit", "--allow-empty", "-m", "init"],
- cwd=v8_path)
+ print("Initializing temporary git repository in v8.")
+ git("init")
+ git("config user.name \"Ada Lovelace\"")
+ git("config user.email ada@lovela.ce")
+ git("commit --allow-empty -m init")
return True
def FetchDeps(v8_path):
@@ -70,14 +74,15 @@
temporary_git = EnsureGit(v8_path)
try:
- print "Fetching dependencies."
+ print("Fetching dependencies.")
env = os.environ.copy()
# gclient needs to have depot_tools in the PATH.
env["PATH"] = depot_tools + os.pathsep + env["PATH"]
+ gclient = os.path.join(depot_tools, "gclient.py")
spec = "solutions = %s" % GCLIENT_SOLUTION
- subprocess.check_call(["gclient", "sync", "--spec", spec],
- cwd=os.path.join(v8_path, os.path.pardir),
- env=env)
+ subprocess.check_call([sys.executable, gclient, "sync", "--spec", spec],
+ cwd=os.path.join(v8_path, os.path.pardir),
+ env=env)
except:
raise
finally:
@@ -88,8 +93,8 @@
os.path.join(v8_path, os.pardir, ".gclient_entries"))
if os.path.isfile(gclient_entries):
os.remove(gclient_entries)
- # Enable building with GN for configure script.
- return True
+
+ return depot_tools
if __name__ == "__main__":
diff --git a/src/v8/tools/node/node_common.py b/src/v8/tools/node/node_common.py
index f7ca3a6..2efb218 100755
--- a/src/v8/tools/node/node_common.py
+++ b/src/v8/tools/node/node_common.py
@@ -3,10 +3,15 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+# for py2/py3 compatibility
+from __future__ import print_function
+
import os
+import pipes
import shutil
import stat
import subprocess
+import sys
DEPOT_TOOLS_URL = \
"https://chromium.googlesource.com/chromium/tools/depot_tools.git"
@@ -15,27 +20,33 @@
def _Get(v8_path):
depot_tools = os.path.join(v8_path, "_depot_tools")
try:
- gclient_path = os.path.join(depot_tools, "gclient")
- gclient_check = subprocess.check_output([gclient_path, "--version"])
- if "gclient.py" in gclient_check:
+ gclient_path = os.path.join(depot_tools, "gclient.py")
+ if os.path.isfile(gclient_path):
return depot_tools
except:
pass
if fetch_if_not_exist:
- print "Checking out depot_tools."
- subprocess.check_call(["git", "clone", DEPOT_TOOLS_URL, depot_tools])
+ print("Checking out depot_tools.")
+ # shell=True needed on Windows to resolve git.bat.
+ subprocess.check_call("git clone {} {}".format(
+ pipes.quote(DEPOT_TOOLS_URL),
+ pipes.quote(depot_tools)), shell=True)
+ # Using check_output to hide warning messages.
+ subprocess.check_output(
+ [sys.executable, gclient_path, "metrics", "--opt-out"],
+ cwd=depot_tools)
return depot_tools
return None
depot_tools = _Get(v8_path)
assert depot_tools is not None
- print "Using depot tools in %s" % depot_tools
+ print("Using depot tools in %s" % depot_tools)
return depot_tools
def UninitGit(v8_path):
- print "Uninitializing temporary git repository"
+ print("Uninitializing temporary git repository")
target = os.path.join(v8_path, ".git")
if os.path.isdir(target):
- print ">> Cleaning up %s" % target
+ print(">> Cleaning up %s" % target)
def OnRmError(func, path, exec_info):
# This might happen on Windows
os.chmod(path, stat.S_IWRITE)
diff --git a/src/v8/tools/node/testdata/v8/testing/gtest/baz/gtest_foo b/src/v8/tools/node/testdata/v8/testing/gtest/baz/gtest_foo
deleted file mode 100644
index eb1ae45..0000000
--- a/src/v8/tools/node/testdata/v8/testing/gtest/baz/gtest_foo
+++ /dev/null
@@ -1 +0,0 @@
-...
diff --git a/src/v8/tools/node/testdata/v8/testing/gtest/baz/gtest_new b/src/v8/tools/node/testdata/v8/testing/gtest/baz/gtest_new
deleted file mode 100644
index eb1ae45..0000000
--- a/src/v8/tools/node/testdata/v8/testing/gtest/baz/gtest_new
+++ /dev/null
@@ -1 +0,0 @@
-...
diff --git a/src/v8/tools/node/testdata/v8/testing/gtest/gtest_bar b/src/v8/tools/node/testdata/v8/testing/gtest/gtest_bar
deleted file mode 100644
index eb1ae45..0000000
--- a/src/v8/tools/node/testdata/v8/testing/gtest/gtest_bar
+++ /dev/null
@@ -1 +0,0 @@
-...
diff --git a/src/v8/tools/node/testdata/v8/testing/gtest/gtest_new b/src/v8/tools/node/testdata/v8/testing/gtest/gtest_new
deleted file mode 100644
index eb1ae45..0000000
--- a/src/v8/tools/node/testdata/v8/testing/gtest/gtest_new
+++ /dev/null
@@ -1 +0,0 @@
-...
diff --git a/src/v8/tools/node/testdata/v8/testing/gtest/new/gtest_new b/src/v8/tools/node/testdata/v8/testing/gtest/new/gtest_new
deleted file mode 100644
index eb1ae45..0000000
--- a/src/v8/tools/node/testdata/v8/testing/gtest/new/gtest_new
+++ /dev/null
@@ -1 +0,0 @@
-...
diff --git a/src/v8/tools/node/testdata/v8/third_party/markupsafe/markupsafe b/src/v8/tools/node/testdata/v8/third_party/markupsafe/markupsafe
deleted file mode 100644
index e69de29..0000000
--- a/src/v8/tools/node/testdata/v8/third_party/markupsafe/markupsafe
+++ /dev/null
diff --git a/src/v8/tools/parse-processor b/src/v8/tools/parse-processor
index 588f120..1c71752 100755
--- a/src/v8/tools/parse-processor
+++ b/src/v8/tools/parse-processor
@@ -28,12 +28,13 @@
if [ ! -x "$d8_exec" ]; then
echo "d8 shell not found in $D8_PATH"
- echo "To build, execute 'make native' from the V8 directory"
+ echo "Please provide path to d8 as env var in D8_PATH"
exit 1
fi
# nm spits out 'no symbols found' messages to stderr.
-cat $log_file | $d8_exec --trace-maps --allow-natives-syntax --trace-deopt $tools_path/splaytree.js $tools_path/codemap.js \
+cat $log_file | $d8_exec --allow-natives-syntax \
+ $tools_path/splaytree.js $tools_path/codemap.js \
$tools_path/csvparser.js $tools_path/consarray.js \
$tools_path/profile.js $tools_path/profile_view.js \
$tools_path/logreader.js $tools_path/arguments.js \
diff --git a/src/v8/tools/parse-processor.html b/src/v8/tools/parse-processor.html
index e41fffb..0f5818e 100644
--- a/src/v8/tools/parse-processor.html
+++ b/src/v8/tools/parse-processor.html
@@ -1,3 +1,4 @@
+<!DOCTYPE html>
<html>
<!--
Copyright 2016 the V8 project authors. All rights reserved. Use of this source
@@ -5,6 +6,8 @@
-->
<head>
+<meta charset="utf-8">
+<title>V8 Parse Processor</title>
<style>
html {
font-family: monospace;
@@ -62,23 +65,56 @@
overflow-y: scroll;
}
-
.funktion {
}
+ .script-size {
+ display: inline-flex;
+ background-color: #505050;
+ border-radius: 3px;
+ padding: 3px;
+ margin: 2px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-decoration: none;
+ color: white;
+ }
+ .script-size.eval {
+ background-color: #ee6300fc;
+ }
+ .script-size.streaming {
+ background-color: #008aff;
+ }
+ .script-size.deserialized {
+ background-color: #1fad00fc;
+ }
+
+ .script-details {
+ padding-right: 5px;
+ margin-right: 4px;
+ }
+ /* all but the last need a border */
+ .script-details:nth-last-child(n+2) {
+ border-right: 1px white solid;
+ }
+
+ .script-details.id {
+ min-width: 2em;
+ text-align: right;
+ }
</style>
-<script src="./splaytree.js" type="text/javascript"></script>
-<script src="./codemap.js" type="text/javascript"></script>
-<script src="./csvparser.js" type="text/javascript"></script>
-<script src="./consarray.js" type="text/javascript"></script>
-<script src="./profile.js" type="text/javascript"></script>
-<script src="./profile_view.js" type="text/javascript"></script>
-<script src="./logreader.js" type="text/javascript"></script>
-<script src="./arguments.js" type="text/javascript"></script>
-<script src="./parse-processor.js" type="text/javascript"></script>
-<script src="./SourceMap.js" type="text/javascript"></script>
-<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
-<script type="text/javascript">
+<script src="./splaytree.js"></script>
+<script src="./codemap.js"></script>
+<script src="./csvparser.js"></script>
+<script src="./consarray.js"></script>
+<script src="./profile.js"></script>
+<script src="./profile_view.js"></script>
+<script src="./logreader.js"></script>
+<script src="./arguments.js"></script>
+<script src="./parse-processor.js"></script>
+<script src="./SourceMap.js"></script>
+<script src="https://www.gstatic.com/charts/loader.js"></script>
+<script>
"use strict";
google.charts.load('current', {packages: ['corechart']});
@@ -86,7 +122,6 @@
return document.querySelector(query);
}
-
function loadFile() {
let files = $('#uploadInput').files;
@@ -149,19 +184,20 @@
}
function delay(t) {
- return new Promise(resolve = > setTimeout(resolve, t));
+ return new Promise(resolve => setTimeout(resolve, t));
}
function renderParseResults(parseProcessor) {
let result = $('#result');
// clear out all existing result pages;
result.innerHTML = '';
- const start = parseProcessor.firstEvent;
- const end = parseProcessor.lastEvent;
+ const start = parseProcessor.firstEventTimestamp;
+ const end = parseProcessor.lastEventTimestamp;
renderScript(result, parseProcessor.totalScript, start, end);
// Build up the graphs lazily to keep the page responsive.
parseProcessor.scripts.forEach(
script => renderScript(result, script, start, end));
+ renderScriptSizes(parseProcessor);
// Install an intersection observer to lazily load the graphs when the script
// div becomes visible for the first time.
var io = new IntersectionObserver((entries, observer) => {
@@ -172,11 +208,10 @@
appendGraph(target.script, target, start, end);
observer.unobserve(entry.target);
});
- }, {});
+ }, {rootMargin: '400px'});
document.querySelectorAll('.script').forEach(div => io.observe(div));
}
-
const kTimeFactor = 10;
const kHeight = 20;
const kFunktionTopOffset = 50;
@@ -189,32 +224,69 @@
scriptDiv.script = script;
let scriptTitle = h3();
- if (script.file) scriptTitle.appendChild(a(script.file, script.file));
- let anchor = a("", ' id=' + script.id);
+ let anchor = a("", 'Script #' + script.id);
anchor.name = "script"+script.id
scriptTitle.appendChild(anchor);
scriptDiv.appendChild(scriptTitle);
+ if (script.file) scriptTitle.appendChild(a(script.file, script.file));
let summary = createNode('pre', 'script-details');
summary.appendChild(text(script.summary));
scriptDiv.appendChild(summary);
result.appendChild(scriptDiv);
- return scriptDiv;
+}
+
+function renderScriptSizes(parseProcessor) {
+ let scriptsDiv = $('#scripts');
+ parseProcessor.scripts.forEach(
+ script => {
+ let scriptDiv = a('#script'+script.id, '', 'script-size');
+ let scriptId = div('script-details');
+ scriptId.classList.add('id');
+ scriptId.innerText = script.id;
+ scriptDiv.appendChild(scriptId);
+ let scriptSize = div('script-details');
+ scriptSize.innerText = BYTES(script.bytesTotal);
+ scriptDiv.appendChild(scriptSize);
+ let scriptUrl = div('script-details');
+ if (script.isEval) {
+ scriptUrl.innerText = "eval";
+ scriptDiv.classList.add('eval');
+ } else {
+ scriptUrl.innerText = script.file.split("/").pop();
+ }
+ if (script.isStreamingCompiled ) {
+ scriptDiv.classList.add('streaming');
+ } else if (script.deserializationTimestamp > 0) {
+ scriptDiv.classList.add('deserialized');
+ }
+ scriptDiv.appendChild(scriptUrl);
+ scriptDiv.style.width = script.bytesTotal * 0.001;
+ scriptsDiv.appendChild(scriptDiv);
+ });
}
const kMaxTime = 120 * kSecondsToMillis;
// Resolution of the graphs
const kTimeIncrement = 1;
const kSelectionTimespan = 2;
+// TODO(cbruni): support compilation cache hit.
const series = [
-// ['firstParseEvent', 'Any Parse Event'],
+ ['firstParseEvent', 'Any Parse', 'area'],
+ ['execution', '1st Exec', 'area'],
+ ['firstCompileEvent', 'Any Compile', 'area'],
+ ['compile', 'Eager Compile'],
+ ['lazyCompile', 'Lazy Compile'],
['parse', 'Parsing'],
-// ['preparse', 'Preparsing'],
-// ['resolution', 'Preparsing with Var. Resolution'],
- ['lazyCompile', 'Lazy Compilation'],
- ['compile', 'Eager Compilation'],
- ['execution', 'First Execution'],
+ ['preparse', 'Preparse'],
+ ['resolution', 'Preparse with Var. Resolution'],
+ ['deserialization', 'Deserialization'],
+ ['optimization', 'Optimize'],
];
const metricNames = series.map(each => each[0]);
+// Display cumulative values (useuful for bytes).
+const kCumulative = true;
+// Include durations in the graphs.
+const kUseDuration = false;
function appendGraph(script, parentNode, start, end) {
@@ -223,27 +295,34 @@
console.time(timerLabel);
let data = new google.visualization.DataTable();
- data.addColumn('number', 'Time');
+ data.addColumn('number', 'Duration');
// The series are interleave bytes processed, time spent and thus have two
// different vAxes.
let seriesOptions = [];
- series.forEach(each => {
- let description = each[1];
+ let colors = ['#4D4D4D', '#fff700', '#5DA5DA', '#FAA43A', '#60BD68',
+ '#F17CB0', '#B2912F', '#B276B2', '#DECF3F', '#F15854'];
+ series.forEach(([metric, description, type]) => {
+ let color = colors.shift();
// Add the bytes column.
- data.addColumn('number', description + ' Bytes');
- seriesOptions.push({targetAxisIndex: 0});
+ data.addColumn('number', description);
+ let options = {targetAxisIndex: 0, color: color};
+ if (type == 'area') options.type = 'area';
+ seriesOptions.push(options)
// Add the time column.
- data.addColumn('number', description + ' Time');
- seriesOptions.push({targetAxisIndex: 1, lineDashStyle: [3, 2]});
+ if (kUseDuration) {
+ data.addColumn('number', description + ' Duration');
+ seriesOptions.push(
+ {targetAxisIndex: 1, color: color, lineDashStyle: [3, 2]});
+ }
});
- // The first entry contains the total.
- seriesOptions[0].type = 'area';
const maxTime = Math.min(kMaxTime, end);
console.time('metrics');
let metricValues =
- script.getAccumulatedTimeMetrics(metricNames , 0, maxTime, kTimeIncrement);
+ script.getAccumulatedTimeMetrics(metricNames , 0, maxTime, kTimeIncrement,
+ kCumulative, kUseDuration);
console.timeEnd('metrics');
+ // Make sure that the series added to the graph matches the returned values.
console.assert(metricValues[0].length == seriesOptions.length + 1);
data.addRows(metricValues);
@@ -257,11 +336,11 @@
},
vAxes: {
0: {title: 'Bytes Touched', format: 'short'},
- 1: {title: 'Time', format: '#,###ms'}
+ 1: {title: 'Duration', format: '#,###ms'}
},
height: 400,
width: 1000,
- chartArea: {left: '5%', top: '15%', width: "85%", height: "75%"},
+ chartArea: {left: 70, top: 0, right: 160, height: "90%"},
// The first series should be a area chart (total bytes touched),
series: seriesOptions,
// everthing else is a line.
@@ -275,27 +354,29 @@
google.visualization.events.addListener(chart, 'select',
() => selectGraphPointHandler(chart, data, script, parentNode));
chart.draw(data, options);
+ // Add event listeners
console.timeEnd(timerLabel);
}
-
function selectGraphPointHandler(chart, data, script, parentNode) {
let selection = chart.getSelection();
if (selection.length <= 0) return;
// Display a list of funktions with events at the given time.
let {row, column} = selection[0];
if (row === null|| column === null) return;
- let name = series[((column-1)/2) | 0][0];
+ const kEntrySize = kUseDuration ? 2 : 1;
+ let [metric, description] = series[((column-1)/ kEntrySize) | 0];
let time = data.getValue(row, 0);
let funktions = script.getFunktionsAtTime(
- time * kSecondsToMillis, kSelectionTimespan, name);
+ time * kSecondsToMillis, kSelectionTimespan, metric);
let oldList = parentNode.querySelector('.funktion-list');
- parentNode.replaceChild(createFunktionList(name, time, funktions), oldList);
+ parentNode.replaceChild(
+ createFunktionList(metric, description, time, funktions), oldList);
}
-function createFunktionList(metric, time, funktions) {
+function createFunktionList(metric, description, time, funktions) {
let container = createNode('div', 'funktion-list');
- container.appendChild(h3('Changes of ' + metric + ' at ' +
+ container.appendChild(h3('Changes of "' + description + '" at ' +
time + 's: ' + funktions.length));
let listNode = createNode('ul');
funktions.forEach(funktion => {
@@ -311,8 +392,6 @@
container.appendChild(listNode);
return container;
}
-
-
</script>
</head>
@@ -326,10 +405,14 @@
<h2>Data</h2>
<form name="fileForm">
<p>
- <input id="uploadInput" type="file" name="files" onchange="loadFile();"> trace entries: <span id="count">0</span>
+ <input id="uploadInput" type="file" name="files" onchange="loadFile();" accept=".log"> trace entries: <span id="count">0</span>
</p>
</form>
+
+ <h2>Scripts</h2>
+ <div id="scripts"></div>
+
<h2>Result</h2>
<div id="result"></div>
</body>
diff --git a/src/v8/tools/parse-processor.js b/src/v8/tools/parse-processor.js
index 30b593a..b829cf2 100644
--- a/src/v8/tools/parse-processor.js
+++ b/src/v8/tools/parse-processor.js
@@ -22,13 +22,13 @@
// confusion between the decimal and thousands separator is big (alternating
// between comma "," vs dot "."). The Swiss formatting uses "'" as a thousands
// separator, dropping most of that confusion.
-var numberFormat = new Intl.NumberFormat('de-CH', {
+const numberFormat = new Intl.NumberFormat('de-CH', {
maximumFractionDigits: 2,
minimumFractionDigits: 2,
});
function formatNumber(value) {
- return formatNumber(value);
+ return numberFormat.format(value);
}
function BYTES(bytes, total) {
@@ -50,48 +50,136 @@
return Math.round(value / total * 100) + "%";
}
-function timestampMin(list) {
- let result = -1;
- list.forEach(timestamp => {
- if (result === -1) {
- result = timestamp;
- } else if (timestamp != -1) {
- result = Math.min(result, timestamp);
+// ===========================================================================
+const kNoTimeMetrics = {
+ __proto__: null,
+ executionDuration: 0,
+ firstEventTimestamp: 0,
+ firstParseEventTimestamp: 0,
+ lastParseEventTimestamp: 0,
+ lastEventTimestamp: 0
+};
+
+class CompilationUnit {
+ constructor() {
+ this.isEval = false;
+
+ // Lazily computed properties.
+ this.firstEventTimestamp = -1;
+ this.firstParseEventTimestamp = -1;
+ this.firstCompileEventTimestamp = -1;
+ this.lastParseEventTimestamp = -1;
+ this.lastEventTimestamp = -1;
+ this.deserializationTimestamp = -1;
+
+ this.preparseTimestamp = -1;
+ this.parseTimestamp = -1;
+ this.parse2Timestamp = -1;
+ this.resolutionTimestamp = -1;
+ this.compileTimestamp = -1;
+ this.lazyCompileTimestamp = -1;
+ this.executionTimestamp = -1;
+ this.optimizationTimestamp = -1;
+
+ this.deserializationDuration = -0.0;
+ this.preparseDuration = -0.0;
+ this.parseDuration = -0.0;
+ this.parse2Duration = -0.0;
+ this.resolutionDuration = -0.0;
+ this.scopeResolutionDuration = -0.0;
+ this.lazyCompileDuration = -0.0;
+ this.compileDuration = -0.0;
+ this.optimizeDuration = -0.0;
+
+ this.ownBytes = -1;
+ this.compilationCacheHits = [];
+ }
+
+ finalize() {
+ this.firstEventTimestamp = this.timestampMin(
+ this.deserializationTimestamp, this.parseTimestamp,
+ this.preparseTimestamp, this.resolutionTimestamp,
+ this.executionTimestamp);
+
+ this.firstParseEventTimestamp = this.timestampMin(
+ this.deserializationTimestamp, this.parseTimestamp,
+ this.preparseTimestamp, this.resolutionTimestamp);
+
+ this.firstCompileEventTimestamp = this.rawTimestampMin(
+ this.deserializationTimestamp, this.compileTimestamp,
+ this.lazyCompileTimestamp);
+ // Any excuted script needs to be compiled.
+ if (this.hasBeenExecuted() &&
+ (this.firstCompileEventTimestamp <= 0 ||
+ this.executionTimestamp < this.firstCompileTimestamp)) {
+ console.error('Compile < execution timestamp', this);
}
- });
- return Math.round(result);
+
+ if (this.ownBytes < 0) console.error(this, 'Own bytes must be positive');
+ }
+
+ hasBeenExecuted() {
+ return this.executionTimestamp > 0;
+ }
+
+ addCompilationCacheHit(timestamp) {
+ this.compilationCacheHits.push(timestamp);
+ }
+
+ // Returns the smallest timestamp from the given list, ignoring
+ // uninitialized (-1) values.
+ rawTimestampMin(...timestamps) {
+ timestamps = timestamps.length == 1 ? timestamps[0] : timestamps;
+ let result = timestamps.reduce((min, item) => {
+ return item == -1 ? min : (min == -1 ? item : Math.min(item, item));
+ }, -1);
+ return result;
+ }
+ timestampMin(...timestamps) {
+ let result = this.rawTimestampMin(...timestamps);
+ if (Number.isNaN(result) || result < 0) {
+ console.error(
+ 'Invalid timestamp min:', {result, timestamps, script: this});
+ return 0;
+ }
+ return result;
+ }
+
+ timestampMax(...timestamps) {
+ timestamps = timestamps.length == 1 ? timestamps[0] : timestamps;
+ let result = Math.max(...timestamps);
+ if (Number.isNaN(result) || result < 0) {
+ console.error(
+ 'Invalid timestamp max:', {result, timestamps, script: this});
+ return 0;
+ }
+ return result;
+ }
}
-
// ===========================================================================
-class Script {
- constructor(file, id) {
- this.file = file;
- this.isNative = false;
- this.id = id;
+class Script extends CompilationUnit {
+ constructor(id) {
+ super();
if (id === void 0 || id <= 0) {
- throw new Error(`Invalid id=${id} for script with file='${file}'`);
+ throw new Error(`Invalid id=${id} for script`);
}
- this.isEval = false;
+ this.file = '';
+ this.id = id;
+
+ this.isNative = false;
+ this.isBackgroundCompiled = false;
+ this.isStreamingCompiled = false;
+
this.funktions = [];
this.metrics = new Map();
this.maxNestingLevel = 0;
- this.firstEvent = -1;
- this.firstParseEvent = -1;
- this.lastParseEvent = -1;
- this.executionTimestamp = -1;
- this.compileTimestamp = -1;
- this.lastEvent = -1;
-
- this.compileTime = -0.0;
-
this.width = 0;
- this.bytesTotal = 0;
- this.ownBytes = -1;
+ this.bytesTotal = -1;
this.finalized = false;
this.summary = '';
- this.setFile(file);
+ this.source = '';
}
setFile(name) {
@@ -100,15 +188,29 @@
}
isEmpty() {
- return this.funktions.length === 0
+ return this.funktions.length === 0;
}
- funktionAtPosition(start) {
- if (start === 0) throw "position 0 is reserved for the script";
- if (this.finalized) throw 'Finalized script has no source position!';
+ getFunktionAtStartPosition(start) {
+ if (!this.isEval && start === 0) {
+ throw 'position 0 is reserved for the script';
+ }
+ if (this.finalized) {
+ return this.funktions.find(funktion => funktion.start == start);
+ }
return this.funktions[start];
}
+ // Return the innermost function at the given source position.
+ getFunktionForPosition(position) {
+ if (!this.finalized) throw 'Incomplete script';
+ for (let i = this.funktions.length - 1; i >= 0; i--) {
+ let funktion = this.funktions[i];
+ if (funktion.containsPosition(position)) return funktion;
+ }
+ return undefined;
+ }
+
addMissingFunktions(list) {
if (this.finalized) throw 'script is finalized!';
list.forEach(fn => {
@@ -136,7 +238,7 @@
let maxNesting = 0;
// Iterate over the Funktions in byte position order.
this.funktions.forEach(fn => {
- fn.fromEval = this.isEval;
+ fn.isEval = this.isEval;
if (parent === null) {
parent = fn;
} else {
@@ -150,18 +252,48 @@
}
parent = fn;
}
- this.firstParseEvent = this.firstParseEvent === -1 ?
- fn.getFirstParseEvent() :
- Math.min(this.firstParseEvent, fn.getFirstParseEvent());
- this.lastParseEvent =
- Math.max(this.lastParseEvent, fn.getLastParseEvent());
- fn.getFirstEvent();
- if (Number.isNaN(this.lastEvent)) throw "Invalid lastEvent";
- this.lastEvent = Math.max(this.lastEvent, fn.getLastEvent());
- if (Number.isNaN(this.lastEvent)) throw "Invalid lastEvent";
+ });
+ // Sanity checks to ensure that scripts are executed and parsed before any
+ // of its funktions.
+ let funktionFirstParseEventTimestamp = -1;
+ // Second iteration step to finalize the funktions once the proper
+ // hierarchy has been set up.
+ this.funktions.forEach(fn => {
+ fn.finalize();
+
+ funktionFirstParseEventTimestamp = this.timestampMin(
+ funktionFirstParseEventTimestamp, fn.firstParseEventTimestamp);
+
+ this.lastParseEventTimestamp = this.timestampMax(
+ this.lastParseEventTimestamp, fn.lastParseEventTimestamp);
+
+ this.lastEventTimestamp =
+ this.timestampMax(this.lastEventTimestamp, fn.lastEventTimestamp);
});
this.maxNestingLevel = maxNesting;
- this.getFirstEvent();
+
+ // Initialize sizes.
+ if (!this.ownBytes === -1) throw 'Invalid state';
+ if (this.funktions.length == 0) {
+ this.bytesTotal = this.ownBytes = 0;
+ return;
+ }
+ let toplevelFunktionBytes = this.funktions.reduce(
+ (bytes, each) => bytes + (each.isToplevel() ? each.getBytes() : 0), 0);
+ if (this.isDeserialized || this.isEval || this.isStreamingCompiled) {
+ if (this.getBytes() === -1) {
+ this.bytesTotal = toplevelFunktionBytes;
+ }
+ }
+ this.ownBytes = this.bytesTotal - toplevelFunktionBytes;
+ // Initialize common properties.
+ super.finalize();
+ // Sanity checks after the minimum timestamps have been computed.
+ if (funktionFirstParseEventTimestamp < this.firstParseEventTimestamp) {
+ console.error(
+ 'invalid firstCompileEventTimestamp', this,
+ funktionFirstParseEventTimestamp, this.firstParseEventTimestamp);
+ }
}
print() {
@@ -180,22 +312,16 @@
}
getOwnBytes() {
- if (this.ownBytes === -1) {
- this.ownBytes = this.funktions.reduce(
- (bytes, each) => bytes - each.parent == null ? each.getBytes() : 0,
- this.getBytes());
- if (this.ownBytes < 0) throw "Own bytes must be positive";
- }
return this.ownBytes;
}
// Also see Funktion.prototype.getMetricBytes
getMetricBytes(name) {
if (name == 'lazyCompileTimestamp') return this.getOwnBytes();
- return this.getBytes();
+ return this.getOwnBytes();
}
- getMetricTime(name) {
+ getMetricDuration(name) {
return this[name];
}
@@ -235,55 +361,59 @@
};
log(" - file: " + this.file);
+ log(' - details: ' +
+ 'isEval=' + this.isEval + ' deserialized=' + this.isDeserialized +
+ ' streamed=' + this.isStreamingCompiled);
info("scripts", this.getScripts());
info("functions", all);
info("toplevel fn", all.filter(each => each.isToplevel()));
- info("preparsed", all.filter(each => each.preparseTime > 0));
+ info('preparsed', all.filter(each => each.preparseDuration > 0));
-
- info("fully parsed", all.filter(each => each.parseTime > 0));
- // info("fn parsed", all.filter(each => each.parse2Time > 0));
- // info("resolved", all.filter(each => each.resolutionTime > 0));
+ info('fully parsed', all.filter(each => each.parseDuration > 0));
+ // info("fn parsed", all.filter(each => each.parse2Duration > 0));
+ // info("resolved", all.filter(each => each.resolutionDuration > 0));
info("executed", all.filter(each => each.executionTimestamp > 0));
- info("forEval", all.filter(each => each.fromEval));
+ info('forEval', all.filter(each => each.isEval));
info("lazy compiled", all.filter(each => each.lazyCompileTimestamp > 0));
info("eager compiled", all.filter(each => each.compileTimestamp > 0));
- let parsingCost = new ExecutionCost('parse', all,
- each => each.parseTime);
+ let parsingCost =
+ new ExecutionCost('parse', all, each => each.parseDuration);
parsingCost.setMetrics(this.metrics);
- log(parsingCost.toString())
+ log(parsingCost.toString());
- let preParsingCost = new ExecutionCost('preparse', all,
- each => each.preparseTime);
+ let preParsingCost =
+ new ExecutionCost('preparse', all, each => each.preparseDuration);
preParsingCost.setMetrics(this.metrics);
- log(preParsingCost.toString())
+ log(preParsingCost.toString());
- let resolutionCost = new ExecutionCost('resolution', all,
- each => each.resolutionTime);
+ let resolutionCost =
+ new ExecutionCost('resolution', all, each => each.resolutionDuration);
resolutionCost.setMetrics(this.metrics);
- log(resolutionCost.toString())
+ log(resolutionCost.toString());
let nesting = new NestingDistribution(all);
nesting.setMetrics(this.metrics);
- log(nesting.toString())
+ log(nesting.toString());
if (printSummary) console.log(this.summary);
}
- getAccumulatedTimeMetrics(metrics, start, end, delta, incremental = false) {
+ getAccumulatedTimeMetrics(
+ metrics, start, end, delta, cumulative = true, useDuration = false) {
// Returns an array of the following format:
- // [ [start, acc(metric0, start, start), acc(metric1, ...), ...],
- // [start+delta, acc(metric0, start, start+delta), ...],
+ // [ [start, acc(metric0, start, start), acc(metric1, ...), ...],
+ // [start+delta, acc(metric0, start, start+delta), ...],
// [start+delta*2, acc(metric0, start, start+delta*2), ...],
// ...
// ]
+ if (end <= start) throw 'Invalid ranges [' + start + ',' + end + ']';
const timespan = end - start;
const kSteps = Math.ceil(timespan / delta);
// To reduce the time spent iterating over the funktions of this script
// we iterate once over all funktions and add the metric changes to each
// timepoint:
- // [ [0, 300, ...], [1, 15, ...], [2, 100, ...], [3, 0, ...] ... ]
+ // [ [0, 300, ...], [1, 15, ...], [2, 100, ...], [3, 0, ...] ... ]
// In a second step we accumulate all values:
// [ [0, 300, ...], [1, 315, ...], [2, 415, ...], [3, 415, ...] ... ]
//
@@ -293,7 +423,7 @@
const metricProperties = ["time"];
metrics.forEach(each => {
metricProperties.push(each + 'Timestamp');
- metricProperties.push(each + 'Time');
+ if (useDuration) metricProperties.push(each + 'Duration');
});
// Create a packed {rowTemplate} which is copied later-on.
let indexToTime = (t) => (start + t * delta) / kSecondsToMillis;
@@ -305,12 +435,15 @@
// Create the real metric's property name on the Funktion object.
// Add the increments of each Funktion's metric to the result.
this.forEach(funktionOrScript => {
- // Iterate over the Funktion's metric names, position 0 is the time.
- for (let i = 1; i < metricProperties.length; i += 2) {
- let property = metricProperties[i];
- let timestamp = funktionOrScript[property];
+ // Iterate over the Funktion's metric names, skipping position 0 which
+ // is the time.
+ const kMetricIncrement = useDuration ? 2 : 1;
+ for (let i = 1; i < metricProperties.length; i += kMetricIncrement) {
+ let timestampPropertyName = metricProperties[i];
+ let timestamp = funktionOrScript[timestampPropertyName];
if (timestamp === void 0) continue;
- if (timestamp < 0 || end < timestamp) continue;
+ if (timestamp < start || end < timestamp) continue;
+ timestamp -= start;
let index = Math.floor(timestamp / delta);
let row = rows[index];
if (row === null) {
@@ -320,9 +453,10 @@
row[0] = indexToTime(index);
}
// Add the metric value.
- row[i] += funktionOrScript.getMetricBytes(property);
- let timeMetricName = metricProperties[i + 1];
- row[i + 1] += funktionOrScript.getMetricTime(timeMetricName);
+ row[i] += funktionOrScript.getMetricBytes(timestampPropertyName);
+ if (!useDuration) continue;
+ let durationPropertyName = metricProperties[i + 1];
+ row[i + 1] += funktionOrScript.getMetricDuration(durationPropertyName);
}
});
// Create a packed array again with only the valid entries.
@@ -334,14 +468,14 @@
let current = rows[t];
if (current === null) {
// Ensure a zero data-point after each non-zero point.
- if (incremental && rows[t - 1] !== null) {
+ if (!cumulative && rows[t - 1] !== null) {
let duplicate = rowTemplate.slice();
duplicate[0] = indexToTime(t);
result.push(duplicate);
}
continue;
}
- if (!incremental) {
+ if (cumulative) {
// Skip i==0 where the corresponding time value in seconds is.
for (let i = 1; i < metricProperties.length; i++) {
current[i] += previous[i];
@@ -349,7 +483,7 @@
}
// Make sure we have a data-point in time right before the current one.
if (rows[t - 1] === null) {
- let duplicate = (incremental ? rowTemplate : previous).slice();
+ let duplicate = (!cumulative ? rowTemplate : previous).slice();
duplicate[0] = indexToTime(t - 1);
result.push(duplicate);
}
@@ -374,14 +508,6 @@
funktion => funktion.didMetricChange(time, delta, metric));
return result;
}
-
- getFirstEvent() {
- if (this.firstEvent === -1) {
- // TODO(cbruni): add support for network request timestanp
- this.firstEvent = this.firstParseEvent;
- }
- return this.firstEvent;
- }
}
@@ -491,127 +617,67 @@
}
// ===========================================================================
-const kNoTimeMetrics = {
- __proto__: null,
- executionTime: 0,
- firstEventTimestamp: 0,
- firstParseEventTimestamp: 0,
- lastParseTimestamp: 0,
- lastEventTimestamp: 0
-};
-class Funktion {
+class Funktion extends CompilationUnit {
constructor(name, start, end, script) {
+ super();
if (start < 0) throw "invalid start position: " + start;
- if (end <= 0) throw "invalid end position: " + end;
- if (end <= start) throw "invalid start end positions";
+ if (script.isEval) {
+ if (end < start) throw 'invalid start end positions';
+ } else {
+ if (end <= 0) throw 'invalid end position: ' + end;
+ if (end <= start) throw 'invalid start end positions';
+ }
this.name = name;
this.start = start;
this.end = end;
- this.ownBytes = -1;
this.script = script;
this.parent = null;
- this.fromEval = false;
this.nested = [];
this.nestingLevel = 0;
- this.preparseTimestamp = -1;
- this.parseTimestamp = -1;
- this.parse2Timestamp = -1;
- this.resolutionTimestamp = -1;
- this.lazyCompileTimestamp = -1;
- this.compileTimestamp = -1;
- this.executionTimestamp = -1;
-
- this.preparseTime = -0.0;
- this.parseTime = -0.0;
- this.parse2Time = -0.0;
- this.resolutionTime = -0.0;
- this.scopeResolutionTime = -0.0;
- this.lazyCompileTime = -0.0;
- this.compileTime = -0.0;
-
- // Lazily computed properties.
- this.firstEventTimestamp = -1;
- this.firstParseEventTimestamp = -1;
- this.lastParseTimestamp = -1;
- this.lastEventTimestamp = -1;
-
if (script) this.script.addFunktion(this);
}
+ finalize() {
+ this.lastParseEventTimestamp = Math.max(
+ this.preparseTimestamp + this.preparseDuration,
+ this.parseTimestamp + this.parseDuration,
+ this.resolutionTimestamp + this.resolutionDuration);
+ if (!(this.lastParseEventTimestamp > 0)) this.lastParseEventTimestamp = 0;
+
+ this.lastEventTimestamp =
+ Math.max(this.lastParseEventTimestamp, this.executionTimestamp);
+ if (!(this.lastEventTimestamp > 0)) this.lastEventTimestamp = 0;
+
+ this.ownBytes = this.nested.reduce(
+ (bytes, each) => bytes - each.getBytes(), this.getBytes());
+
+ super.finalize();
+ }
+
getMetricBytes(name) {
if (name == 'lazyCompileTimestamp') return this.getOwnBytes();
- return this.getBytes();
+ return this.getOwnBytes();
}
- getMetricTime(name) {
+ getMetricDuration(name) {
if (name in kNoTimeMetrics) return 0;
return this[name];
}
- getFirstEvent() {
- if (this.firstEventTimestamp === -1) {
- this.firstEventTimestamp = timestampMin(
- [this.parseTimestamp, this.preparseTimestamp,
- this.resolutionTimestamp, this.executionTimestamp
- ]);
- if (!(this.firstEventTimestamp > 0)) {
- this.firstEventTimestamp = 0;
- }
- }
- return this.firstEventTimestamp;
- }
-
- getFirstParseEvent() {
- if (this.firstParseEventTimestamp === -1) {
- this.firstParseEventTimestamp = timestampMin(
- [this.parseTimestamp, this.preparseTimestamp,
- this.resolutionTimestamp
- ]);
- if (!(this.firstParseEventTimestamp > 0)) {
- this.firstParseEventTimestamp = 0;
- }
- }
- return this.firstParseEventTimestamp;
- }
-
- getLastParseEvent() {
- if (this.lastParseTimestamp === -1) {
- this.lastParseTimestamp = Math.max(
- this.preparseTimestamp + this.preparseTime,
- this.parseTimestamp + this.parseTime,
- this.resolutionTimestamp + this.resolutionTime);
- if (!(this.lastParseTimestamp > 0)) {
- this.lastParseTimestamp = 0;
- }
- }
- return this.lastParseTimestamp;
- }
-
- getLastEvent() {
- if (this.lastEventTimestamp === -1) {
- this.lastEventTimestamp = Math.max(
- this.getLastParseEvent(), this.executionTimestamp);
- if (!(this.lastEventTimestamp > 0)) {
- this.lastEventTimestamp = 0;
- }
- }
- return this.lastEventTimestamp;
- }
-
isNestedIn(funktion) {
if (this.script != funktion.script) throw "Incompatible script";
return funktion.start < this.start && this.end <= funktion.end;
}
isToplevel() {
- return this.parent === null
+ return this.parent === null;
}
- hasBeenExecuted() {
- return this.executionTimestamp > 0
+ containsPosition(position) {
+ return this.start <= position && position <= this.end;
}
accumulateNestingLevel(accumulator) {
@@ -641,12 +707,6 @@
}
getOwnBytes() {
- if (this.ownBytes === -1) {
- this.ownBytes = this.nested.reduce(
- (bytes, each) => bytes - each.getBytes(),
- this.getBytes());
- if (this.ownBytes < 0) throw "Own bytes must be positive";
- }
return this.ownBytes;
}
@@ -661,7 +721,7 @@
toString(details = true) {
let result = 'function' + (this.name ? ' ' + this.name : '') +
- `() range=${this.start}-${this.end}`;
+ `() range=${this.start}-${this.end}`;
if (details) result += ` script=${this.script ? this.script.id : 'X'}`;
return result;
}
@@ -687,25 +747,60 @@
class ParseProcessor extends LogReader {
constructor() {
super();
- let config = (processor) => {
- // {script file},{script id},{start position},{end position},
- // {time},{timestamp},{function name}
- return {
- parsers: [null, parseInt, parseInt, parseInt, parseFloat, parseInt, null],
- processor: processor
- }
- };
-
this.dispatchTable_ = {
- 'parse-full': config(this.processFull),
- 'parse-function': config(this.processFunction),
- 'parse-script': config(this.processScript),
- 'parse-eval': config(this.processEval),
- 'preparse-no-resolution': config(this.processPreparseNoResolution),
- 'preparse-resolution': config(this.processPreparseResolution),
- 'first-execution': config(this.processFirstExecution),
- 'compile-lazy': config(this.processCompileLazy),
- 'compile': config(this.processCompile)
+ // Avoid accidental leaking of __proto__ properties and force this object
+ // to be in dictionary-mode.
+ __proto__: null,
+ // "function",{event type},
+ // {script id},{start position},{end position},{time},{timestamp},
+ // {function name}
+ 'function': {
+ parsers: [
+ parseString, parseInt, parseInt, parseInt, parseFloat, parseInt,
+ parseString
+ ],
+ processor: this.processFunctionEvent
+ },
+ // "compilation-cache","hit"|"put",{type},{scriptid},{start position},
+ // {end position},{timestamp}
+ 'compilation-cache': {
+ parsers:
+ [parseString, parseString, parseInt, parseInt, parseInt, parseInt],
+ processor: this.processCompilationCacheEvent
+ },
+ 'script': {
+ parsers: [parseString, parseInt, parseInt],
+ processor: this.processScriptEvent
+ },
+ // "script-details", {script_id}, {file}, {line}, {column}, {size}
+ 'script-details': {
+ parsers: [parseInt, parseString, parseInt, parseInt, parseInt],
+ processor: this.processScriptDetails
+ },
+ 'script-source': {
+ parsers: [parseInt, parseString, parseString],
+ processor: this.processScriptSource
+ },
+ };
+ this.functionEventDispatchTable_ = {
+ // Avoid accidental leaking of __proto__ properties and force this object
+ // to be in dictionary-mode.
+ __proto__: null,
+ 'full-parse': this.processFull.bind(this),
+ 'parse-function': this.processParseFunction.bind(this),
+ // TODO(cbruni): make sure arrow functions emit a normal parse-function
+ // event.
+ 'parse': this.processParseFunction.bind(this),
+ 'parse-script': this.processParseScript.bind(this),
+ 'parse-eval': this.processParseEval.bind(this),
+ 'preparse-no-resolution': this.processPreparseNoResolution.bind(this),
+ 'preparse-resolution': this.processPreparseResolution.bind(this),
+ 'first-execution': this.processFirstExecution.bind(this),
+ 'compile-lazy': this.processCompileLazy.bind(this),
+ 'compile': this.processCompile.bind(this),
+ 'compile-eval': this.processCompileEval.bind(this),
+ 'optimize-lazy': this.processOptimizeLazy.bind(this),
+ 'deserialize': this.processDeserialize.bind(this),
};
this.idToScript = new Map();
@@ -713,9 +808,9 @@
this.nameToFunction = new Map();
this.scripts = [];
this.totalScript = new TotalScript();
- this.firstEvent = -1;
- this.lastParseEvent = -1;
- this.lastEvent = -1;
+ this.firstEventTimestamp = -1;
+ this.lastParseEventTimestamp = -1;
+ this.lastEventTimestamp = -1;
}
print() {
@@ -755,152 +850,283 @@
this.scripts = Array.from(this.idToScript.values())
.filter(each => !each.isNative);
- this.scripts.forEach(script => script.finalize());
- this.scripts.forEach(script => script.calculateMetrics(false));
-
- this.firstEvent =
- timestampMin(this.scripts.map(each => each.firstEvent));
- this.lastParseEvent = this.scripts.reduce(
- (max, script) => Math.max(max, script.lastParseEvent), -1);
- this.lastEvent = this.scripts.reduce(
- (max, script) => Math.max(max, script.lastEvent), -1);
+ this.scripts.forEach(script => {
+ script.finalize();
+ script.calculateMetrics(false)
+ });
this.scripts.forEach(script => this.totalScript.addAllFunktions(script));
this.totalScript.calculateMetrics(true);
- const series = [
- ['firstParseEvent', 'Any Parse Event'],
- ['parse', 'Parsing'],
- ['preparse', 'Preparsing'],
- ['resolution', 'Preparsing with Var. Resolution'],
- ['lazyCompile', 'Lazy Compilation'],
- ['compile', 'Eager Compilation'],
- ['execution', 'First Execution'],
- ];
- let metrics = series.map(each => each[0]);
- this.totalScript.getAccumulatedTimeMetrics(metrics, 0, this.lastEvent, 10);
- };
+
+ this.firstEventTimestamp = this.totalScript.timestampMin(
+ this.scripts.map(each => each.firstEventTimestamp));
+ this.lastParseEventTimestamp = this.totalScript.timestampMax(
+ this.scripts.map(each => each.lastParseEventTimestamp));
+ this.lastEventTimestamp = this.totalScript.timestampMax(
+ this.scripts.map(each => each.lastEventTimestamp));
+
+ const series = {
+ firstParseEvent: 'Any Parse Event',
+ parse: 'Parsing',
+ preparse: 'Preparsing',
+ resolution: 'Preparsing with Var. Resolution',
+ lazyCompile: 'Lazy Compilation',
+ compile: 'Eager Compilation',
+ execution: 'First Execution',
+ };
+ let metrics = Object.keys(series);
+ this.totalScript.getAccumulatedTimeMetrics(
+ metrics, 0, this.lastEventTimestamp, 10);
+ }
+
+ processFunctionEvent(
+ eventName, scriptId, startPosition, endPosition, duration, timestamp,
+ functionName) {
+ let handlerFn = this.functionEventDispatchTable_[eventName];
+ if (handlerFn === undefined) {
+ console.error('Couldn\'t find handler for function event:' + eventName);
+ }
+ handlerFn(
+ scriptId, startPosition, endPosition, duration, timestamp,
+ functionName);
+ }
addEntry(entry) {
this.entries.push(entry);
}
- lookupScript(file, id) {
- // During preparsing we only have the temporary ranges and no script yet.
- let script;
- if (this.idToScript.has(id)) {
- script = this.idToScript.get(id);
- } else {
- script = new Script(file, id);
- this.idToScript.set(id, script);
- }
- if (file.length > 0 && script.file.length === 0) {
- script.setFile(file);
- this.fileToScript.set(file, script);
- }
- return script;
+ lookupScript(id) {
+ return this.idToScript.get(id);
}
- lookupFunktion(file, scriptId,
- startPosition, endPosition, time, timestamp, functionName) {
- let script = this.lookupScript(file, scriptId);
- let funktion = script.funktionAtPosition(startPosition);
+ getOrCreateFunction(
+ scriptId, startPosition, endPosition, duration, timestamp, functionName) {
+ if (scriptId == -1) {
+ return this.lookupFunktionByRange(startPosition, endPosition);
+ }
+ let script = this.lookupScript(scriptId);
+ let funktion = script.getFunktionAtStartPosition(startPosition);
if (funktion === void 0) {
funktion = new Funktion(functionName, startPosition, endPosition, script);
}
return funktion;
}
- processEval(file, scriptId, startPosition,
- endPosition, time, timestamp, functionName) {
- let script = this.lookupScript(file, scriptId);
+ // Iterates over all functions and tries to find matching ones.
+ lookupFunktionsByRange(start, end) {
+ let results = [];
+ this.idToScript.forEach(script => {
+ script.forEach(funktion => {
+ if (funktion.startPostion == start && funktion.endPosition == end) {
+ results.push(funktion);
+ }
+ });
+ });
+ return results;
+ }
+ lookupFunktionByRange(start, end) {
+ let results = this.lookupFunktionsByRange(start, end);
+ if (results.length != 1) throw "Could not find unique function by range";
+ return results[0];
+ }
+
+ processScriptEvent(eventName, scriptId, timestamp) {
+ let script = this.idToScript.get(scriptId);
+ switch (eventName) {
+ case 'create':
+ case 'reserve-id':
+ case 'deserialize': {
+ if (script !== undefined) return;
+ script = new Script(scriptId);
+ this.idToScript.set(scriptId, script);
+ if (eventName == 'deserialize') {
+ script.deserializationTimestamp = toTimestamp(timestamp);
+ }
+ return;
+ }
+ case 'background-compile':
+ if (script.isBackgroundCompiled) {
+ throw 'Cannot background-compile twice';
+ }
+ script.isBackgroundCompiled = true;
+ // TODO(cbruni): remove once backwards compatibility is no longer needed.
+ script.isStreamingCompiled = true;
+ // TODO(cbruni): fix parse events for background compilation scripts
+ script.preparseTimestamp = toTimestamp(timestamp);
+ return;
+ case 'streaming-compile':
+ if (script.isStreamingCompiled) throw 'Cannot stream-compile twice';
+ // TODO(cbruni): remove once backwards compatibility is no longer needed.
+ script.isBackgroundCompiled = true;
+ script.isStreamingCompiled = true;
+ // TODO(cbruni): fix parse events for background compilation scripts
+ script.preparseTimestamp = toTimestamp(timestamp);
+ return;
+ default:
+ console.error('Unhandled script event: ' + eventName);
+ }
+ }
+
+ processScriptDetails(scriptId, file, startLine, startColumn, size) {
+ let script = this.lookupScript(scriptId);
+ script.setFile(file);
+ }
+
+ processScriptSource(scriptId, url, source) {
+ let script = this.lookupScript(scriptId);
+ script.source = source;
+ }
+
+ processParseEval(
+ scriptId, startPosition, endPosition, duration, timestamp, functionName) {
+ if (startPosition != 0 && startPosition != -1) {
+ console.error('Invalid start position for parse-eval', arguments);
+ }
+ let script = this.processParseScript(...arguments);
script.isEval = true;
}
- processFull(file, scriptId, startPosition,
- endPosition, time, timestamp, functionName) {
- let funktion = this.lookupFunktion(...arguments);
+ processFull(
+ scriptId, startPosition, endPosition, duration, timestamp, functionName) {
+ if (startPosition == 0) {
+ // This should only happen for eval.
+ let script = this.lookupScript(scriptId);
+ script.isEval = true;
+ return;
+ }
+ let funktion = this.getOrCreateFunction(...arguments);
// TODO(cbruni): this should never happen, emit differen event from the
// parser.
if (funktion.parseTimestamp > 0) return;
- funktion.parseTimestamp = startOf(timestamp, time);
- funktion.parseTime = time;
+ funktion.parseTimestamp = startOf(timestamp, duration);
+ funktion.parseDuration = duration;
}
- processFunction(file, scriptId, startPosition,
- endPosition, time, timestamp, functionName) {
- let funktion = this.lookupFunktion(...arguments);
- funktion.parseTimestamp = startOf(timestamp, time);
- funktion.parseTime = time;
+ processParseFunction(
+ scriptId, startPosition, endPosition, duration, timestamp, functionName) {
+ let funktion = this.getOrCreateFunction(...arguments);
+ funktion.parseTimestamp = startOf(timestamp, duration);
+ funktion.parseDuration = duration;
}
- processScript(file, scriptId, startPosition,
- endPosition, time, timestamp, functionName) {
- // TODO timestamp and time
- let script = this.lookupScript(file, scriptId);
- let ts = startOf(timestamp, time);
+ processParseScript(
+ scriptId, startPosition, endPosition, duration, timestamp, functionName) {
+ // TODO timestamp and duration
+ let script = this.lookupScript(scriptId);
+ let ts = startOf(timestamp, duration);
script.parseTimestamp = ts;
- script.firstEventTimestamp = ts;
- script.firstParseEventTimestamp = ts;
- script.parseTime = time;
+ script.parseDuration = duration;
+ return script;
}
- processPreparseResolution(file, scriptId,
- startPosition, endPosition, time, timestamp, functionName) {
- let funktion = this.lookupFunktion(...arguments);
+ processPreparseResolution(
+ scriptId, startPosition, endPosition, duration, timestamp, functionName) {
+ let funktion = this.getOrCreateFunction(...arguments);
// TODO(cbruni): this should never happen, emit different event from the
// parser.
if (funktion.resolutionTimestamp > 0) return;
- funktion.resolutionTimestamp = startOf(timestamp, time);
- funktion.resolutionTime = time;
+ funktion.resolutionTimestamp = startOf(timestamp, duration);
+ funktion.resolutionDuration = duration;
}
- processPreparseNoResolution(file, scriptId,
- startPosition, endPosition, time, timestamp, functionName) {
- let funktion = this.lookupFunktion(...arguments);
- funktion.preparseTimestamp = startOf(timestamp, time);
- funktion.preparseTime = time;
+ processPreparseNoResolution(
+ scriptId, startPosition, endPosition, duration, timestamp, functionName) {
+ let funktion = this.getOrCreateFunction(...arguments);
+ funktion.preparseTimestamp = startOf(timestamp, duration);
+ funktion.preparseDuration = duration;
}
- processFirstExecution(file, scriptId,
- startPosition, endPosition, time, timestamp, functionName) {
- let script = this.lookupScript(file, scriptId);
+ processFirstExecution(
+ scriptId, startPosition, endPosition, duration, timestamp, functionName) {
+ let script = this.lookupScript(scriptId);
if (startPosition === 0) {
// undefined = eval fn execution
if (script) {
script.executionTimestamp = toTimestamp(timestamp);
}
} else {
- let funktion = script.funktionAtPosition(startPosition);
+ let funktion = script.getFunktionAtStartPosition(startPosition);
if (funktion) {
funktion.executionTimestamp = toTimestamp(timestamp);
- } else if (functionName.length > 0) {
- // throw new Error("Could not find function: " + functionName);
+ } else {
+ // TODO(cbruni): handle funktions from compilation-cache hits.
}
}
}
- processCompileLazy(file, scriptId,
- startPosition, endPosition, time, timestamp, functionName) {
- let funktion = this.lookupFunktion(...arguments);
- funktion.lazyCompileTimestamp = startOf(timestamp, time);
- funktion.lazyCompileTime = time;
- script.firstPar
+ processCompileLazy(
+ scriptId, startPosition, endPosition, duration, timestamp, functionName) {
+ let funktion = this.getOrCreateFunction(...arguments);
+ funktion.lazyCompileTimestamp = startOf(timestamp, duration);
+ funktion.lazyCompileDuration = duration;
}
- processCompile(file, scriptId,
- startPosition, endPosition, time, timestamp, functionName) {
-
- let script = this.lookupScript(file, scriptId);
+ processCompile(
+ scriptId, startPosition, endPosition, duration, timestamp, functionName) {
+ let script = this.lookupScript(scriptId);
if (startPosition === 0) {
- script.compileTimestamp = startOf(timestamp, time);
- script.compileTime = time;
+ script.compileTimestamp = startOf(timestamp, duration);
+ script.compileDuration = duration;
script.bytesTotal = endPosition;
+ return script;
} else {
- let funktion = script.funktionAtPosition(startPosition);
- funktion.compileTimestamp = startOf(timestamp, time);
- funktion.compileTime = time;
+ let funktion = script.getFunktionAtStartPosition(startPosition);
+ if (funktion === undefined) {
+ // This should not happen since any funktion has to be parsed first.
+ console.error('processCompile funktion not found', ...arguments);
+ return;
+ }
+ funktion.compileTimestamp = startOf(timestamp, duration);
+ funktion.compileDuration = duration;
+ return funktion;
}
}
+
+ processCompileEval(
+ scriptId, startPosition, endPosition, duration, timestamp, functionName) {
+ let compilationUnit = this.processCompile(...arguments);
+ compilationUnit.isEval = true;
+ }
+
+ processOptimizeLazy(
+ scriptId, startPosition, endPosition, duration, timestamp, functionName) {
+ let compilationUnit = this.lookupScript(scriptId);
+ if (startPosition > 0) {
+ compilationUnit =
+ compilationUnit.getFunktionAtStartPosition(startPosition);
+ if (compilationUnit === undefined) {
+ // This should not happen since any funktion has to be parsed first.
+ console.error('processOptimizeLazy funktion not found', ...arguments);
+ return;
+ }
+ }
+ compilationUnit.optimizationTimestamp = startOf(timestamp, duration);
+ compilationUnit.optimizationDuration = duration;
+ }
+
+ processDeserialize(
+ scriptId, startPosition, endPosition, duration, timestamp, functionName) {
+ let compilationUnit = this.lookupScript(scriptId);
+ if (startPosition === 0) {
+ compilationUnit.bytesTotal = endPosition;
+ } else {
+ compilationUnit = this.getOrCreateFunction(...arguments);
+ }
+ compilationUnit.deserializationTimestamp = startOf(timestamp, duration);
+ compilationUnit.deserializationDuration = duration;
+ }
+
+ processCompilationCacheEvent(
+ eventType, cacheType, scriptId, startPosition, endPosition, timestamp) {
+ if (eventType !== 'hit') return;
+ let compilationUnit = this.lookupScript(scriptId);
+ if (startPosition > 0) {
+ compilationUnit =
+ compilationUnit.getFunktionAtStartPosition(startPosition);
+ }
+ compilationUnit.addCompilationCacheHit(toTimestamp(timestamp));
+ }
+
}
diff --git a/src/v8/tools/perf-compare.py b/src/v8/tools/perf-compare.py
index 75f3c73..744f6aa 100755
--- a/src/v8/tools/perf-compare.py
+++ b/src/v8/tools/perf-compare.py
@@ -11,6 +11,9 @@
%prog -t "x64 results" ../result.json master.json -o results.html
'''
+# for py2/py3 compatibility
+from __future__ import print_function
+
from collections import OrderedDict
import json
import math
@@ -418,7 +421,7 @@
run_names[run_name] = 0
for error in data["errors"]:
- print "Error:", error
+ print("Error:", error)
for trace in data["traces"]:
suite_name = trace["graphs"][0]
diff --git a/src/v8/tools/plot-timer-events b/src/v8/tools/plot-timer-events
index 3294e85..0217632 100755
--- a/src/v8/tools/plot-timer-events
+++ b/src/v8/tools/plot-timer-events
@@ -32,7 +32,7 @@
if test ! -x "$d8_exec"; then
echo "d8 shell not found in $D8_PATH"
- echo "To build, execute 'make native' from the V8 directory"
+ echo "Please provide path to d8 as env var in D8_PATH"
exit 1
fi
diff --git a/src/v8/tools/predictable_wrapper.py b/src/v8/tools/predictable_wrapper.py
index cf7bf00b..31b62c8 100644
--- a/src/v8/tools/predictable_wrapper.py
+++ b/src/v8/tools/predictable_wrapper.py
@@ -14,11 +14,21 @@
compared. Differences are reported as errors.
"""
+
+# for py2/py3 compatibility
+from __future__ import print_function
+
import sys
from testrunner.local import command
+from testrunner.local import utils
+
MAX_TRIES = 3
+TIMEOUT = 120
+
+# Predictable mode works only when run on the host os.
+command.setup(utils.GuessOS(), None)
def main(args):
def allocation_str(stdout):
@@ -27,23 +37,23 @@
return line
return None
- cmd = command.Command(args[0], args[1:])
+ cmd = command.Command(args[0], args[1:], timeout=TIMEOUT)
previous_allocations = None
for run in range(1, MAX_TRIES + 1):
- print '### Predictable run #%d' % run
+ print('### Predictable run #%d' % run)
output = cmd.execute()
if output.stdout:
- print '### Stdout:'
- print output.stdout
+ print('### Stdout:')
+ print(output.stdout)
if output.stderr:
- print '### Stderr:'
- print output.stderr
- print '### Return code: %s' % output.exit_code
+ print('### Stderr:')
+ print(output.stderr)
+ print('### Return code: %s' % output.exit_code)
if output.HasTimedOut():
# If we get a timeout in any run, we are in an unpredictable state. Just
# report it as a failure and don't rerun.
- print '### Test timed out'
+ print('### Test timed out')
return 1
allocations = allocation_str(output.stdout)
if not allocations:
@@ -52,7 +62,7 @@
'--verify-predictable is passed at the cmd line.')
return 2
if previous_allocations and previous_allocations != allocations:
- print '### Allocations differ'
+ print('### Allocations differ')
return 3
if run >= MAX_TRIES:
# No difference on the last run -> report a success.
diff --git a/src/v8/tools/profile.js b/src/v8/tools/profile.js
index cddadaa..ef36271 100644
--- a/src/v8/tools/profile.js
+++ b/src/v8/tools/profile.js
@@ -892,16 +892,24 @@
JsonProfile.prototype.addCode = function(
kind, name, timestamp, start, size) {
+ let codeId = this.codeEntries_.length;
+ // Find out if we have a static code entry for the code. If yes, we will
+ // make sure it is written to the JSON file just once.
+ let staticEntry = this.codeMap_.findAddress(start);
+ if (staticEntry && staticEntry.entry.type === 'CPP') {
+ codeId = staticEntry.entry.codeId;
+ }
+
var entry = new CodeMap.CodeEntry(size, name, 'CODE');
this.codeMap_.addCode(start, entry);
- entry.codeId = this.codeEntries_.length;
- this.codeEntries_.push({
+ entry.codeId = codeId;
+ this.codeEntries_[codeId] = {
name : entry.name,
timestamp: timestamp,
type : entry.type,
kind : kind
- });
+ };
return entry;
};
@@ -975,7 +983,7 @@
if (!entry) return;
var codeId = entry.codeId;
- // Resolve the inlined fucntions list.
+ // Resolve the inlined functions list.
if (inlinedFunctions.length > 0) {
inlinedFunctions = inlinedFunctions.substring(1).split("S");
for (var i = 0; i < inlinedFunctions.length; i++) {
@@ -1002,33 +1010,10 @@
};
};
-function unescapeString(s) {
- s = s.split("\\");
- for (var i = 1; i < s.length; i++) {
- if (s[i] === "") {
- // Double backslash.
- s[i] = "\\";
- } else if (i > 0 && s[i].startsWith("x")) {
- // Escaped Ascii character.
- s[i] = String.fromCharCode(parseInt(s[i].substring(1, 3), 16)) +
- s[i].substring(3);
- } else if (i > 0 && s[i].startsWith("u")) {
- // Escaped unicode character.
- s[i] = String.fromCharCode(parseInt(s[i].substring(1, 5), 16)) +
- s[i].substring(5);
- } else {
- if (i > 0 && s[i - 1] !== "\\") {
- printErr("Malformed source string");
- }
- }
- }
- return s.join("");
-}
-
JsonProfile.prototype.addScriptSource = function(script, url, source) {
this.scripts_[script] = {
- name : unescapeString(url),
- source : unescapeString(source)
+ name : url,
+ source : source
};
};
diff --git a/src/v8/tools/profview/index.html b/src/v8/tools/profview/index.html
index 32f7c7b..8695a41 100644
--- a/src/v8/tools/profview/index.html
+++ b/src/v8/tools/profview/index.html
@@ -22,7 +22,7 @@
Chrome V8 profiling log processor
</h3>
-<input type="file" id="fileinput" />
+<input type="file" id="fileinput" /><div id="source-status"></div>
<br>
<hr>
@@ -59,6 +59,10 @@
</table>
<div>
Current code object: <span id="timeline-currentCode"></span>
+ <button id="source-viewer-hide-button">Hide source</button>
+ </div>
+ <div>
+ <table id="source-viewer"> </table>
</div>
</div>
@@ -108,7 +112,7 @@
<br>
<br>
<br>
-Copyright the V8 Authors - Last change to this page: 2017/02/15
+Copyright the V8 Authors - Last change to this page: 2018/08/13
</p>
</body>
diff --git a/src/v8/tools/profview/profile-utils.js b/src/v8/tools/profview/profile-utils.js
index f5a85be..4be5589 100644
--- a/src/v8/tools/profview/profile-utils.js
+++ b/src/v8/tools/profview/profile-utils.js
@@ -93,9 +93,10 @@
function createNodeFromStackEntry(code, codeId, vmState) {
let name = code ? code.name : "UNKNOWN";
-
- return { name, codeId, type : resolveCodeKindAndVmState(code, vmState),
- children : [], ownTicks : 0, ticks : 0 };
+ let node = createEmptyNode(name);
+ node.codeId = codeId;
+ node.type = resolveCodeKindAndVmState(code, vmState);
+ return node;
}
function childIdFromCode(codeId, code) {
@@ -148,29 +149,30 @@
}
function addOrUpdateChildNode(parent, file, stackIndex, stackPos, ascending) {
- let stack = file.ticks[stackIndex].s;
- let vmState = file.ticks[stackIndex].vm;
- let codeId = stack[stackPos];
- let code = codeId >= 0 ? file.code[codeId] : undefined;
if (stackPos === -1) {
// We reached the end without finding the next step.
// If we are doing top-down call tree, update own ticks.
if (!ascending) {
parent.ownTicks++;
}
- } else {
- console.assert(stackPos >= 0 && stackPos < stack.length);
- // We found a child node.
- let childId = childIdFromCode(codeId, code);
- let child = parent.children[childId];
- if (!child) {
- child = createNodeFromStackEntry(code, codeId, vmState);
- child.delayedExpansion = { frameList : [], ascending };
- parent.children[childId] = child;
- }
- child.ticks++;
- addFrameToFrameList(child.delayedExpansion.frameList, stackIndex, stackPos);
+ return;
}
+
+ let stack = file.ticks[stackIndex].s;
+ console.assert(stackPos >= 0 && stackPos < stack.length);
+ let codeId = stack[stackPos];
+ let code = codeId >= 0 ? file.code[codeId] : undefined;
+ // We found a child node.
+ let childId = childIdFromCode(codeId, code);
+ let child = parent.children[childId];
+ if (!child) {
+ let vmState = file.ticks[stackIndex].vm;
+ child = createNodeFromStackEntry(code, codeId, vmState);
+ child.delayedExpansion = { frameList : [], ascending };
+ parent.children[childId] = child;
+ }
+ child.ticks++;
+ addFrameToFrameList(child.delayedExpansion.frameList, stackIndex, stackPos);
}
// This expands a tree node (direct children only).
@@ -314,13 +316,7 @@
this.tree = root;
this.categories = categories;
} else {
- this.tree = {
- name : "root",
- codeId: -1,
- children : [],
- ownTicks : 0,
- ticks : 0
- };
+ this.tree = createEmptyNode("root");
this.categories = null;
}
@@ -339,7 +335,7 @@
let codeId = stack[i];
if (codeId < 0 || this.codeVisited[codeId]) continue;
- let code = codeId >= 0 ? file.code[codeId] : undefined;
+ let code = file.code[codeId];
if (this.filter) {
let type = code ? code.type : undefined;
let kind = code ? code.kind : undefined;
@@ -601,3 +597,15 @@
softDeoptimizations,
};
}
+
+function normalizeLeadingWhitespace(lines) {
+ let regex = /^\s*/;
+ let minimumLeadingWhitespaceChars = Infinity;
+ for (let line of lines) {
+ minimumLeadingWhitespaceChars =
+ Math.min(minimumLeadingWhitespaceChars, regex.exec(line)[0].length);
+ }
+ for (let i = 0; i < lines.length; i++) {
+ lines[i] = lines[i].substring(minimumLeadingWhitespaceChars);
+ }
+}
diff --git a/src/v8/tools/profview/profview.css b/src/v8/tools/profview/profview.css
index 106bfe2..ca39745 100644
--- a/src/v8/tools/profview/profview.css
+++ b/src/v8/tools/profview/profview.css
@@ -2,6 +2,11 @@
width : 100%;
}
+td {
+ padding-top: 0.1em;
+ padding-bottom: 0.1em;
+}
+
.numeric {
width : 12ex;
}
@@ -14,31 +19,82 @@
font-family: 'Roboto', sans-serif;
}
-div.code-type-chip {
- display : inline-block;
- padding : 0.0em;
+#source-status {
+ display: inline-block;
}
-span.code-type-chip {
+.tree-row-arrow {
+ margin-right: 0.2em;
+ text-align: right;
+}
+
+.code-type-chip {
border-radius : 1em;
- display : inline-block;
- padding : 0.1em;
+ padding : 0.2em;
background-color : #4040c0;
color: #ffffff;
font-size : small;
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
}
-span.code-type-chip-space {
- width : 0.5ex;
- display : inline-block;
+.tree-row-name {
+ margin-left: 0.2em;
+ margin-right: 0.2em;
}
-span.codeid-link {
+.codeid-link {
text-decoration: underline;
cursor: pointer;
}
+.view-source-link {
+ text-decoration: underline;
+ cursor: pointer;
+ font-size: 10pt;
+ margin-left: 0.6em;
+ color: #555555;
+}
+
+#source-viewer {
+ border: 1px solid black;
+ padding: 0.2em;
+ font-family: 'Roboto Mono', monospace;
+ white-space: pre;
+ margin-top: 1em;
+ margin-bottom: 1em;
+}
+
+#source-viewer td.line-none {
+ background-color: white;
+}
+
+#source-viewer td.line-cold {
+ background-color: #e1f5fe;
+}
+
+#source-viewer td.line-mediumcold {
+ background-color: #b2ebf2;
+}
+
+#source-viewer td.line-mediumhot {
+ background-color: #c5e1a5;
+}
+
+#source-viewer td.line-hot {
+ background-color: #dce775;
+}
+
+#source-viewer td.line-superhot {
+ background-color: #ffee58;
+}
+
+#source-viewer .source-line-number {
+ padding-left: 0.2em;
+ padding-right: 0.2em;
+ color: #003c8f;
+ background-color: #eceff1;
+}
+
div.mode-button {
padding: 1em 3em;
display: inline-block;
diff --git a/src/v8/tools/profview/profview.js b/src/v8/tools/profview/profview.js
index d480cd4..210cec7 100644
--- a/src/v8/tools/profview/profview.js
+++ b/src/v8/tools/profview/profview.js
@@ -8,34 +8,42 @@
return document.getElementById(id);
}
-let components = [];
+function removeAllChildren(element) {
+ while (element.firstChild) {
+ element.removeChild(element.firstChild);
+ }
+}
+let components;
function createViews() {
- components.push(new CallTreeView());
- components.push(new TimelineView());
- components.push(new HelpView());
- components.push(new SummaryView());
- components.push(new ModeBarView());
-
- main.setMode("summary");
+ components = [
+ new CallTreeView(),
+ new TimelineView(),
+ new HelpView(),
+ new SummaryView(),
+ new ModeBarView(),
+ new ScriptSourceView(),
+ ];
}
function emptyState() {
return {
file : null,
- mode : "none",
+ mode : null,
currentCodeId : null,
+ viewingSource: false,
start : 0,
end : Infinity,
- timeLine : {
- width : 100,
- height : 100
+ timelineSize : {
+ width : 0,
+ height : 0
},
callTree : {
attribution : "js-exclude-bc",
categories : "code-type",
sort : "time"
- }
+ },
+ sourceData: null
};
}
@@ -47,6 +55,7 @@
let main = {
currentState : emptyState(),
+ renderPending : false,
setMode(mode) {
if (mode !== main.currentState.mode) {
@@ -120,22 +129,28 @@
}
},
- setTimeLineDimensions(width, height) {
- if (width !== main.currentState.timeLine.width ||
- height !== main.currentState.timeLine.height) {
- let timeLine = Object.assign({}, main.currentState.timeLine);
- timeLine.width = width;
- timeLine.height = height;
- main.currentState = Object.assign({}, main.currentState);
- main.currentState.timeLine = timeLine;
- main.delayRender();
+ updateSources(file) {
+ let statusDiv = $("source-status");
+ if (!file) {
+ statusDiv.textContent = "";
+ return;
}
+ if (!file.scripts || file.scripts.length === 0) {
+ statusDiv.textContent =
+ "Script source not available. Run profiler with --log-source-code.";
+ return;
+ }
+ statusDiv.textContent = "Script source is available.";
+ main.currentState.sourceData = new SourceData(file);
},
setFile(file) {
if (file !== main.currentState.file) {
- main.currentState = Object.assign({}, main.currentState);
+ let lastMode = main.currentState.mode || "summary";
+ main.currentState = emptyState();
main.currentState.file = file;
+ main.updateSources(file);
+ main.setMode(lastMode);
main.delayRender();
}
},
@@ -148,10 +163,16 @@
}
},
+ setViewingSource(value) {
+ if (main.currentState.viewingSource !== value) {
+ main.currentState = Object.assign({}, main.currentState);
+ main.currentState.viewingSource = value;
+ main.delayRender();
+ }
+ },
+
onResize() {
- main.setTimeLineDimensions(
- Math.round(window.innerWidth - 20),
- Math.round(window.innerHeight / 5));
+ main.delayRender();
},
onLoad() {
@@ -160,9 +181,7 @@
if (f) {
let reader = new FileReader();
reader.onload = function(event) {
- let profData = JSON.parse(event.target.result);
- main.setViewInterval(0, Infinity);
- main.setFile(profData);
+ main.setFile(JSON.parse(event.target.result));
};
reader.onerror = function(event) {
console.error(
@@ -176,11 +195,14 @@
$("fileinput").addEventListener(
"change", loadHandler, false);
createViews();
- main.onResize();
},
delayRender() {
- Promise.resolve().then(() => {
+ if (main.renderPending) return;
+ main.renderPending = true;
+
+ window.requestAnimationFrame(() => {
+ main.renderPending = false;
for (let c of components) {
c.render(main.currentState);
}
@@ -188,50 +210,51 @@
}
};
-let bucketDescriptors =
+const CATEGORY_COLOR = "#f5f5f5";
+const bucketDescriptors =
[ { kinds : [ "JSOPT" ],
- color : "#00ff00",
- backgroundColor : "#c0ffc0",
+ color : "#64dd17",
+ backgroundColor : "#80e27e",
text : "JS Optimized" },
{ kinds : [ "JSUNOPT", "BC" ],
- color : "#ffb000",
- backgroundColor : "#ffe0c0",
+ color : "#dd2c00",
+ backgroundColor : "#ff9e80",
text : "JS Unoptimized" },
{ kinds : [ "IC" ],
- color : "#ffff00",
- backgroundColor : "#ffffc0",
+ color : "#ff6d00",
+ backgroundColor : "#ffab40",
text : "IC" },
{ kinds : [ "STUB", "BUILTIN", "REGEXP" ],
- color : "#ffb0b0",
- backgroundColor : "#fff0f0",
+ color : "#ffd600",
+ backgroundColor : "#ffea00",
text : "Other generated" },
{ kinds : [ "CPP", "LIB" ],
- color : "#0000ff",
- backgroundColor : "#c0c0ff",
+ color : "#304ffe",
+ backgroundColor : "#6ab7ff",
text : "C++" },
{ kinds : [ "CPPEXT" ],
- color : "#8080ff",
- backgroundColor : "#e0e0ff",
+ color : "#003c8f",
+ backgroundColor : "#c0cfff",
text : "C++/external" },
{ kinds : [ "CPPPARSE" ],
- color : "#b890f7",
- backgroundColor : "#ebdeff",
+ color : "#aa00ff",
+ backgroundColor : "#ffb2ff",
text : "C++/Parser" },
{ kinds : [ "CPPCOMPBC" ],
- color : "#52b0ce",
- backgroundColor : "#a5c8d4",
+ color : "#43a047",
+ backgroundColor : "#88c399",
text : "C++/Bytecode compiler" },
{ kinds : [ "CPPCOMP" ],
- color : "#00ffff",
- backgroundColor : "#c0ffff",
+ color : "#00e5ff",
+ backgroundColor : "#6effff",
text : "C++/Compiler" },
{ kinds : [ "CPPGC" ],
- color : "#ff00ff",
- backgroundColor : "#ffc0ff",
+ color : "#6200ea",
+ backgroundColor : "#e1bee7",
text : "C++/GC" },
{ kinds : [ "UNKNOWN" ],
- color : "#f0f0f0",
- backgroundColor : "#e0e0e0",
+ color : "#bdbdbd",
+ backgroundColor : "#efefef",
text : "Unknown" }
];
@@ -260,13 +283,13 @@
case "UNKNOWN":
return "Unknown";
case "CPPPARSE":
- return "C++ (parser)";
+ return "C++ Parser";
case "CPPCOMPBC":
- return "C++ (bytecode compiler)";
+ return "C++ Bytecode Compiler)";
case "CPPCOMP":
- return "C++ (compiler)";
+ return "C++ Compiler";
case "CPPGC":
- return "C++";
+ return "C++ GC";
case "CPPEXT":
return "C++ External";
case "CPP":
@@ -291,27 +314,15 @@
console.error("Unknown type: " + type);
}
-function createTypeDiv(type) {
+function createTypeNode(type) {
if (type === "CAT") {
return document.createTextNode("");
}
- let div = document.createElement("div");
- div.classList.add("code-type-chip");
-
let span = document.createElement("span");
span.classList.add("code-type-chip");
span.textContent = codeTypeToText(type);
- div.appendChild(span);
- span = document.createElement("span");
- span.classList.add("code-type-chip-space");
- div.appendChild(span);
-
- return div;
-}
-
-function isBytecodeHandler(kind) {
- return kind === "BytecodeHandler";
+ return span;
}
function filterFromFilterId(id) {
@@ -322,31 +333,56 @@
return (type, kind) => type !== 'CODE';
case "js-exclude-bc":
return (type, kind) =>
- type !== 'CODE' || !isBytecodeHandler(kind);
+ type !== 'CODE' || kind !== "BytecodeHandler";
}
}
-function createTableExpander(indent) {
+function createIndentNode(indent) {
let div = document.createElement("div");
- div.style.width = (indent + 0.5) + "em";
div.style.display = "inline-block";
- div.style.textAlign = "right";
+ div.style.width = (indent + 0.5) + "em";
return div;
}
+function createArrowNode() {
+ let span = document.createElement("span");
+ span.classList.add("tree-row-arrow");
+ return span;
+}
+
function createFunctionNode(name, codeId) {
- if (codeId === -1) {
- return document.createTextNode(name);
- }
let nameElement = document.createElement("span");
- nameElement.classList.add("codeid-link");
- nameElement.onclick = function() {
- main.setCurrentCode(codeId);
- };
nameElement.appendChild(document.createTextNode(name));
+ nameElement.classList.add("tree-row-name");
+ if (codeId !== -1) {
+ nameElement.classList.add("codeid-link");
+ nameElement.onclick = (event) => {
+ main.setCurrentCode(codeId);
+ // Prevent the click from bubbling to the row and causing it to
+ // collapse/expand.
+ event.stopPropagation();
+ };
+ }
return nameElement;
}
+function createViewSourceNode(codeId) {
+ let linkElement = document.createElement("span");
+ linkElement.appendChild(document.createTextNode("View source"));
+ linkElement.classList.add("view-source-link");
+ linkElement.onclick = (event) => {
+ main.setCurrentCode(codeId);
+ main.setViewingSource(true);
+ // Prevent the click from bubbling to the row and causing it to
+ // collapse/expand.
+ event.stopPropagation();
+ };
+ return linkElement;
+}
+
+const COLLAPSED_ARROW = "\u25B6";
+const EXPANDED_ARROW = "\u25BC";
+
class CallTreeView {
constructor() {
this.element = $("calltree");
@@ -400,22 +436,19 @@
}
expandTree(tree, indent) {
- let that = this;
let index = 0;
let id = "R/";
let row = tree.row;
- let expander = tree.expander;
if (row) {
index = row.rowIndex;
id = row.id;
- // Make sure we collapse the children when the row is clicked
- // again.
- expander.textContent = "\u25BE";
- let expandHandler = expander.onclick;
- expander.onclick = () => {
- that.collapseRow(tree, expander, expandHandler);
+ tree.arrow.textContent = EXPANDED_ARROW;
+ // Collapse the children when the row is clicked again.
+ let expandHandler = row.onclick;
+ row.onclick = () => {
+ this.collapseRow(tree, expandHandler);
}
}
@@ -439,7 +472,9 @@
let row = this.rows.insertRow(index);
row.id = id + i + "/";
- if (node.type !== "CAT") {
+ if (node.type === "CAT") {
+ row.style.backgroundColor = CATEGORY_COLOR;
+ } else {
row.style.backgroundColor = bucketFromKind(node.type).backgroundColor;
}
@@ -460,10 +495,17 @@
// Create the name cell.
let nameCell = row.insertCell();
- let expander = createTableExpander(indent + 1);
- nameCell.appendChild(expander);
- nameCell.appendChild(createTypeDiv(node.type));
+ nameCell.appendChild(createIndentNode(indent + 1));
+ let arrow = createArrowNode();
+ nameCell.appendChild(arrow);
+ nameCell.appendChild(createTypeNode(node.type));
nameCell.appendChild(createFunctionNode(node.name, node.codeId));
+ if (main.currentState.sourceData &&
+ node.codeId >= 0 &&
+ main.currentState.sourceData.hasSource(
+ this.currentState.file.code[node.codeId].func)) {
+ nameCell.appendChild(createViewSourceNode(node.codeId));
+ }
// Inclusive ticks cell.
c = row.insertCell();
@@ -476,18 +518,18 @@
c.style.textAlign = "right";
}
if (node.children.length > 0) {
- expander.textContent = "\u25B8";
- expander.onclick = () => { that.expandTree(node, indent + 1); };
+ arrow.textContent = COLLAPSED_ARROW;
+ row.onclick = () => { this.expandTree(node, indent + 1); };
}
node.row = row;
- node.expander = expander;
+ node.arrow = arrow;
index++;
}
}
- collapseRow(tree, expander, expandHandler) {
+ collapseRow(tree, expandHandler) {
let row = tree.row;
let id = row.id;
let index = row.rowIndex;
@@ -496,8 +538,8 @@
this.rows.deleteRow(index);
}
- expander.textContent = "\u25B8";
- expander.onclick = expandHandler;
+ tree.arrow.textContent = COLLAPSED_ARROW;
+ row.onclick = expandHandler;
}
fillSelects(mode, calltree) {
@@ -809,10 +851,12 @@
return;
}
- this.currentState = newState;
+ let width = Math.round(document.documentElement.clientWidth - 20);
+ let height = Math.round(document.documentElement.clientHeight / 5);
+
if (oldState) {
- if (newState.timeLine.width === oldState.timeLine.width &&
- newState.timeLine.height === oldState.timeLine.height &&
+ if (width === oldState.timelineSize.width &&
+ height === oldState.timelineSize.height &&
newState.file === oldState.file &&
newState.currentCodeId === oldState.currentCodeId &&
newState.start === oldState.start &&
@@ -821,21 +865,27 @@
return;
}
}
+ this.currentState = newState;
+ this.currentState.timelineSize.width = width;
+ this.currentState.timelineSize.height = height;
this.element.style.display = "inherit";
+ let file = this.currentState.file;
+
+ const minPixelsPerBucket = 10;
+ const minTicksPerBucket = 8;
+ let maxBuckets = Math.round(file.ticks.length / minTicksPerBucket);
+ let bucketCount = Math.min(
+ Math.round(width / minPixelsPerBucket), maxBuckets);
+
// Make sure the canvas has the right dimensions.
- let width = this.currentState.timeLine.width;
- let height = this.currentState.timeLine.height;
this.canvas.width = width;
this.canvas.height = height;
// Make space for the selection text.
height -= this.imageOffset;
- let file = this.currentState.file;
- if (!file) return;
-
let currentCodeId = this.currentState.currentCodeId;
let firstTime = file.ticks[0].tm;
@@ -846,13 +896,6 @@
this.selectionStart = (start - firstTime) / (lastTime - firstTime) * width;
this.selectionEnd = (end - firstTime) / (lastTime - firstTime) * width;
- let tickCount = file.ticks.length;
-
- let minBucketPixels = 10;
- let minBucketSamples = 30;
- let bucketCount = Math.min(width / minBucketPixels,
- tickCount / minBucketSamples);
-
let stackProcessor = new CategorySampler(file, bucketCount);
generateTree(file, 0, Infinity, stackProcessor);
let codeIdProcessor = new FunctionTimelineProcessor(
@@ -873,28 +916,36 @@
let sum = 0;
let bucketData = [];
let total = buckets[i].total;
- for (let j = 0; j < bucketDescriptors.length; j++) {
- let desc = bucketDescriptors[j];
- for (let k = 0; k < desc.kinds.length; k++) {
- sum += buckets[i][desc.kinds[k]];
+ if (total > 0) {
+ for (let j = 0; j < bucketDescriptors.length; j++) {
+ let desc = bucketDescriptors[j];
+ for (let k = 0; k < desc.kinds.length; k++) {
+ sum += buckets[i][desc.kinds[k]];
+ }
+ bucketData.push(Math.round(graphHeight * sum / total));
}
- bucketData.push(Math.round(graphHeight * sum / total));
+ } else {
+ // No ticks fell into this bucket. Fill with "Unknown."
+ for (let j = 0; j < bucketDescriptors.length; j++) {
+ let desc = bucketDescriptors[j];
+ bucketData.push(desc.text === "Unknown" ? graphHeight : 0);
+ }
}
bucketsGraph.push(bucketData);
}
// Draw the category graph into the buffer.
- let bucketWidth = width / bucketsGraph.length;
+ let bucketWidth = width / (bucketsGraph.length - 1);
let ctx = buffer.getContext('2d');
for (let i = 0; i < bucketsGraph.length - 1; i++) {
let bucketData = bucketsGraph[i];
let nextBucketData = bucketsGraph[i + 1];
+ let x1 = Math.round(i * bucketWidth);
+ let x2 = Math.round((i + 1) * bucketWidth);
for (let j = 0; j < bucketData.length; j++) {
- let x1 = Math.round(i * bucketWidth);
- let x2 = Math.round((i + 1) * bucketWidth);
ctx.beginPath();
- ctx.moveTo(x1, j && bucketData[j - 1]);
- ctx.lineTo(x2, j && nextBucketData[j - 1]);
+ ctx.moveTo(x1, j > 0 ? bucketData[j - 1] : 0);
+ ctx.lineTo(x2, j > 0 ? nextBucketData[j - 1] : 0);
ctx.lineTo(x2, nextBucketData[j]);
ctx.lineTo(x1, bucketData[j]);
ctx.closePath();
@@ -1017,9 +1068,7 @@
cell.appendChild(document.createTextNode(" " + desc.text));
}
- while (this.currentCode.firstChild) {
- this.currentCode.removeChild(this.currentCode.firstChild);
- }
+ removeAllChildren(this.currentCode);
if (currentCodeId) {
let currentCode = file.code[currentCodeId];
this.currentCode.appendChild(document.createTextNode(currentCode.name));
@@ -1090,10 +1139,7 @@
}
this.element.style.display = "inherit";
-
- while (this.element.firstChild) {
- this.element.removeChild(this.element.firstChild);
- }
+ removeAllChildren(this.element);
let stats = computeOptimizationStats(
this.currentState.file, newState.start, newState.end);
@@ -1114,22 +1160,22 @@
return row;
}
- function makeCollapsible(row, expander) {
- expander.textContent = "\u25BE";
- let expandHandler = expander.onclick;
- expander.onclick = () => {
+ function makeCollapsible(row, arrow) {
+ arrow.textContent = EXPANDED_ARROW;
+ let expandHandler = row.onclick;
+ row.onclick = () => {
let id = row.id;
let index = row.rowIndex + 1;
while (index < rows.rows.length &&
rows.rows[index].id.startsWith(id)) {
rows.deleteRow(index);
}
- expander.textContent = "\u25B8";
- expander.onclick = expandHandler;
+ arrow.textContent = COLLAPSED_ARROW;
+ row.onclick = expandHandler;
}
}
- function expandDeoptInstances(row, expander, instances, indent, kind) {
+ function expandDeoptInstances(row, arrow, instances, indent, kind) {
let index = row.rowIndex;
for (let i = 0; i < instances.length; i++) {
let childRow = rows.insertRow(index + 1);
@@ -1145,18 +1191,19 @@
document.createTextNode("Reason: " + deopt.reason));
reasonCell.style.textIndent = indent + "em";
}
- makeCollapsible(row, expander);
+ makeCollapsible(row, arrow);
}
- function expandDeoptFunctionList(row, expander, list, indent, kind) {
+ function expandDeoptFunctionList(row, arrow, list, indent, kind) {
let index = row.rowIndex;
for (let i = 0; i < list.length; i++) {
let childRow = rows.insertRow(index + 1);
childRow.id = row.id + i + "/";
let textCell = childRow.insertCell(-1);
- let expander = createTableExpander(indent);
- textCell.appendChild(expander);
+ textCell.appendChild(createIndentNode(indent));
+ let childArrow = createArrowNode();
+ textCell.appendChild(childArrow);
textCell.appendChild(
createFunctionNode(list[i].f.name, list[i].f.codes[0]));
@@ -1164,16 +1211,16 @@
numberCell.textContent = list[i].instances.length;
numberCell.style.textIndent = indent + "em";
- expander.textContent = "\u25B8";
- expander.onclick = () => {
+ childArrow.textContent = COLLAPSED_ARROW;
+ childRow.onclick = () => {
expandDeoptInstances(
- childRow, expander, list[i].instances, indent + 1);
+ childRow, childArrow, list[i].instances, indent + 1);
};
}
- makeCollapsible(row, expander);
+ makeCollapsible(row, arrow);
}
- function expandOptimizedFunctionList(row, expander, list, indent, kind) {
+ function expandOptimizedFunctionList(row, arrow, list, indent, kind) {
let index = row.rowIndex;
for (let i = 0; i < list.length; i++) {
let childRow = rows.insertRow(index + 1);
@@ -1188,17 +1235,19 @@
numberCell.textContent = list[i].instances.length;
numberCell.style.textIndent = indent + "em";
}
- makeCollapsible(row, expander);
+ makeCollapsible(row, arrow);
}
function addExpandableRow(text, list, indent, kind) {
let row = rows.insertRow(-1);
row.id = "opt-table/" + kind + "/";
+ row.style.backgroundColor = CATEGORY_COLOR;
let textCell = row.insertCell(-1);
- let expander = createTableExpander(indent);
- textCell.appendChild(expander);
+ textCell.appendChild(createIndentNode(indent));
+ let arrow = createArrowNode();
+ textCell.appendChild(arrow);
textCell.appendChild(document.createTextNode(text));
let numberCell = row.insertCell(-1);
@@ -1208,16 +1257,16 @@
}
if (list.count > 0) {
- expander.textContent = "\u25B8";
+ arrow.textContent = COLLAPSED_ARROW;
if (kind === "opt") {
- expander.onclick = () => {
+ row.onclick = () => {
expandOptimizedFunctionList(
- row, expander, list.functions, indent + 1, kind);
+ row, arrow, list.functions, indent + 1, kind);
};
} else {
- expander.onclick = () => {
+ row.onclick = () => {
expandDeoptFunctionList(
- row, expander, list.functions, indent + 1, kind);
+ row, arrow, list.functions, indent + 1, kind);
};
}
}
@@ -1241,6 +1290,217 @@
}
}
+class ScriptSourceView {
+ constructor() {
+ this.table = $("source-viewer");
+ this.hideButton = $("source-viewer-hide-button");
+ this.hideButton.onclick = () => {
+ main.setViewingSource(false);
+ };
+ }
+
+ render(newState) {
+ let oldState = this.currentState;
+ if (!newState.file || !newState.viewingSource) {
+ this.table.style.display = "none";
+ this.hideButton.style.display = "none";
+ this.currentState = null;
+ return;
+ }
+ if (oldState) {
+ if (newState.file === oldState.file &&
+ newState.currentCodeId === oldState.currentCodeId &&
+ newState.viewingSource === oldState.viewingSource) {
+ // No change, nothing to do.
+ return;
+ }
+ }
+ this.currentState = newState;
+
+ this.table.style.display = "inline-block";
+ this.hideButton.style.display = "inline";
+ removeAllChildren(this.table);
+
+ let functionId =
+ this.currentState.file.code[this.currentState.currentCodeId].func;
+ let sourceView =
+ this.currentState.sourceData.generateSourceView(functionId);
+ for (let i = 0; i < sourceView.source.length; i++) {
+ let sampleCount = sourceView.lineSampleCounts[i] || 0;
+ let sampleProportion = sourceView.samplesTotal > 0 ?
+ sampleCount / sourceView.samplesTotal : 0;
+ let heatBucket;
+ if (sampleProportion === 0) {
+ heatBucket = "line-none";
+ } else if (sampleProportion < 0.2) {
+ heatBucket = "line-cold";
+ } else if (sampleProportion < 0.4) {
+ heatBucket = "line-mediumcold";
+ } else if (sampleProportion < 0.6) {
+ heatBucket = "line-mediumhot";
+ } else if (sampleProportion < 0.8) {
+ heatBucket = "line-hot";
+ } else {
+ heatBucket = "line-superhot";
+ }
+
+ let row = this.table.insertRow(-1);
+
+ let lineNumberCell = row.insertCell(-1);
+ lineNumberCell.classList.add("source-line-number");
+ lineNumberCell.textContent = i + sourceView.firstLineNumber;
+
+ let sampleCountCell = row.insertCell(-1);
+ sampleCountCell.classList.add(heatBucket);
+ sampleCountCell.textContent = sampleCount;
+
+ let sourceLineCell = row.insertCell(-1);
+ sourceLineCell.classList.add(heatBucket);
+ sourceLineCell.textContent = sourceView.source[i];
+ }
+
+ $("timeline-currentCode").scrollIntoView();
+ }
+}
+
+class SourceData {
+ constructor(file) {
+ this.scripts = new Map();
+ for (let i = 0; i < file.scripts.length; i++) {
+ const scriptBlock = file.scripts[i];
+ if (scriptBlock === null) continue; // Array may be sparse.
+ let source = scriptBlock.source.split("\n");
+ this.scripts.set(i, source);
+ }
+
+ this.functions = new Map();
+ for (let codeId = 0; codeId < file.code.length; ++codeId) {
+ let codeBlock = file.code[codeId];
+ if (codeBlock.source && codeBlock.func !== undefined) {
+ let data = this.functions.get(codeBlock.func);
+ if (!data) {
+ data = new FunctionSourceData(codeBlock.source.script,
+ codeBlock.source.start,
+ codeBlock.source.end);
+ this.functions.set(codeBlock.func, data);
+ }
+ data.addSourceBlock(codeId, codeBlock.source);
+ }
+ }
+
+ for (let tick of file.ticks) {
+ let stack = tick.s;
+ for (let i = 0; i < stack.length; i += 2) {
+ let codeId = stack[i];
+ if (codeId < 0) continue;
+ let functionId = file.code[codeId].func;
+ if (this.functions.has(functionId)) {
+ let codeOffset = stack[i + 1];
+ this.functions.get(functionId).addOffsetSample(codeId, codeOffset);
+ }
+ }
+ }
+ }
+
+ getScript(scriptId) {
+ return this.scripts.get(scriptId);
+ }
+
+ getLineForScriptOffset(script, scriptOffset) {
+ let line = 0;
+ let charsConsumed = 0;
+ for (; line < script.length; ++line) {
+ charsConsumed += script[line].length + 1; // Add 1 for newline.
+ if (charsConsumed > scriptOffset) break;
+ }
+ return line;
+ }
+
+ hasSource(functionId) {
+ return this.functions.has(functionId);
+ }
+
+ generateSourceView(functionId) {
+ console.assert(this.hasSource(functionId));
+ let data = this.functions.get(functionId);
+ let scriptId = data.scriptId;
+ let script = this.getScript(scriptId);
+ let firstLineNumber =
+ this.getLineForScriptOffset(script, data.startScriptOffset);
+ let lastLineNumber =
+ this.getLineForScriptOffset(script, data.endScriptOffset);
+ let lines = script.slice(firstLineNumber, lastLineNumber + 1);
+ normalizeLeadingWhitespace(lines);
+
+ let samplesTotal = 0;
+ let lineSampleCounts = [];
+ for (let [codeId, block] of data.codes) {
+ block.offsets.forEach((sampleCount, codeOffset) => {
+ let sourceOffset = block.positionTable.getScriptOffset(codeOffset);
+ let lineNumber =
+ this.getLineForScriptOffset(script, sourceOffset) - firstLineNumber;
+ samplesTotal += sampleCount;
+ lineSampleCounts[lineNumber] =
+ (lineSampleCounts[lineNumber] || 0) + sampleCount;
+ });
+ }
+
+ return {
+ source: lines,
+ lineSampleCounts: lineSampleCounts,
+ samplesTotal: samplesTotal,
+ firstLineNumber: firstLineNumber + 1 // Source code is 1-indexed.
+ };
+ }
+}
+
+class FunctionSourceData {
+ constructor(scriptId, startScriptOffset, endScriptOffset) {
+ this.scriptId = scriptId;
+ this.startScriptOffset = startScriptOffset;
+ this.endScriptOffset = endScriptOffset;
+
+ this.codes = new Map();
+ }
+
+ addSourceBlock(codeId, source) {
+ this.codes.set(codeId, {
+ positionTable: new SourcePositionTable(source.positions),
+ offsets: []
+ });
+ }
+
+ addOffsetSample(codeId, codeOffset) {
+ let codeIdOffsets = this.codes.get(codeId).offsets;
+ codeIdOffsets[codeOffset] = (codeIdOffsets[codeOffset] || 0) + 1;
+ }
+}
+
+class SourcePositionTable {
+ constructor(encodedTable) {
+ this.offsetTable = [];
+ let offsetPairRegex = /C([0-9]+)O([0-9]+)/g;
+ while (true) {
+ let regexResult = offsetPairRegex.exec(encodedTable);
+ if (!regexResult) break;
+ let codeOffset = parseInt(regexResult[1]);
+ let scriptOffset = parseInt(regexResult[2]);
+ if (isNaN(codeOffset) || isNaN(scriptOffset)) continue;
+ this.offsetTable.push(codeOffset, scriptOffset);
+ }
+ }
+
+ getScriptOffset(codeOffset) {
+ console.assert(codeOffset >= 0);
+ for (let i = this.offsetTable.length - 2; i >= 0; i -= 2) {
+ if (this.offsetTable[i] <= codeOffset) {
+ return this.offsetTable[i + 1];
+ }
+ }
+ return this.offsetTable[1];
+ }
+}
+
class HelpView {
constructor() {
this.element = $("help");
diff --git a/src/v8/tools/profviz/composer.js b/src/v8/tools/profviz/composer.js
index ce625ad..411a724 100644
--- a/src/v8/tools/profviz/composer.js
+++ b/src/v8/tools/profviz/composer.js
@@ -176,7 +176,9 @@
}
function MergeRanges(ranges) {
- ranges.sort(function(a, b) { return a.start - b.start; });
+ ranges.sort(function(a, b) {
+ return (a.start == b.start) ? a.end - b.end : a.start - b.start;
+ });
var result = [];
var j = 0;
for (var i = 0; i < ranges.length; i = j) {
@@ -306,13 +308,14 @@
};
// Collect data from log.
var logreader = new LogReader(
- { 'timer-event-start': { parsers: [null, parseTimeStamp],
+ { 'timer-event-start': { parsers: [parseString, parseTimeStamp],
processor: processTimerEventStart },
- 'timer-event-end': { parsers: [null, parseTimeStamp],
+ 'timer-event-end': { parsers: [parseString, parseTimeStamp],
processor: processTimerEventEnd },
- 'shared-library': { parsers: [null, parseInt, parseInt],
+ 'shared-library': { parsers: [parseString, parseInt, parseInt],
processor: processSharedLibrary },
- 'code-creation': { parsers: [null, parseInt, parseInt, parseInt, null],
+ 'code-creation': { parsers: [parseString, parseInt, parseInt,
+ parseInt, parseString],
processor: processCodeCreateEvent },
'code-move': { parsers: [parseInt, parseInt],
processor: processCodeMoveEvent },
@@ -322,8 +325,8 @@
processor: processCodeDeoptEvent },
'current-time': { parsers: [parseTimeStamp],
processor: processCurrentTimeEvent },
- 'tick': { parsers: [parseInt, parseTimeStamp,
- null, null, parseInt, 'var-args'],
+ 'tick': { parsers: [parseInt, parseTimeStamp, parseString,
+ parseString, parseInt, parseVarArgs],
processor: processTickEvent }
});
@@ -516,8 +519,13 @@
// Label the longest pauses.
execution_pauses =
RestrictRangesTo(execution_pauses, range_start, range_end);
- execution_pauses.sort(
- function(a, b) { return b.duration() - a.duration(); });
+ execution_pauses.sort(function(a, b) {
+ if (a.duration() == b.duration() && b.end == a.end)
+ return b.start - a.start;
+
+ return (a.duration() == b.duration())
+ ? b.end - a.end : b.duration() - a.duration();
+ });
var max_pause_time = execution_pauses.length > 0
? execution_pauses[0].duration() : 0;
diff --git a/src/v8/tools/profviz/worker.js b/src/v8/tools/profviz/worker.js
index 7f16308..95ed40b 100644
--- a/src/v8/tools/profviz/worker.js
+++ b/src/v8/tools/profviz/worker.js
@@ -100,7 +100,7 @@
var profile = "";
print = function(text) { profile += text + "\n"; };
// Dummy entries provider, as we cannot call nm.
- var entriesProvider = new UnixCppEntriesProvider("", "");
+ var entriesProvider = new UnixCppEntriesProvider("", "", "");
var targetRootFS = "";
var separateIc = false;
var callGraphSize = 5;
diff --git a/src/v8/tools/release/auto_push.py b/src/v8/tools/release/auto_push.py
index ca9e5e8..4cb9687 100755
--- a/src/v8/tools/release/auto_push.py
+++ b/src/v8/tools/release/auto_push.py
@@ -26,6 +26,9 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# for py2/py3 compatibility
+from __future__ import print_function
+
import argparse
import json
import os
@@ -63,7 +66,7 @@
format="%H", git_hash="%s..%s" % (last_release, self["candidate"]))
if not commits:
- print "Already pushed current candidate %s" % self["candidate"]
+ print("Already pushed current candidate %s" % self["candidate"])
return True
@@ -71,7 +74,7 @@
MESSAGE = "Creating release if specified."
def RunStep(self):
- print "Creating release for %s." % self["candidate"]
+ print("Creating release for %s." % self["candidate"])
args = [
"--author", self._options.author,
@@ -96,7 +99,7 @@
def _ProcessOptions(self, options):
if not options.author or not options.reviewer: # pragma: no cover
- print "You need to specify author and reviewer."
+ print("You need to specify author and reviewer.")
return False
options.requires_editor = False
return True
diff --git a/src/v8/tools/release/auto_roll.py b/src/v8/tools/release/auto_roll.py
index b27675e..829aefb 100755
--- a/src/v8/tools/release/auto_roll.py
+++ b/src/v8/tools/release/auto_roll.py
@@ -3,6 +3,9 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+# for py2/py3 compatibility
+from __future__ import print_function
+
import argparse
import os
import sys
@@ -14,13 +17,17 @@
ISSUE_MSG = (
"""Please follow these instructions for assigning/CC'ing issues:
-https://github.com/v8/v8/wiki/Triaging%20issues
+https://v8.dev/docs/triage-issues
Please close rolling in case of a roll revert:
https://v8-roll.appspot.com/
This only works with a Google account.
-CQ_INCLUDE_TRYBOTS=master.tryserver.blink:linux_trusty_blink_rel;master.tryserver.chromium.linux:linux_optional_gpu_tests_rel;master.tryserver.chromium.mac:mac_optional_gpu_tests_rel;master.tryserver.chromium.win:win_optional_gpu_tests_rel;master.tryserver.chromium.android:android_optional_gpu_tests_rel""")
+CQ_INCLUDE_TRYBOTS=luci.chromium.try:linux-blink-rel
+CQ_INCLUDE_TRYBOTS=luci.chromium.try:linux_optional_gpu_tests_rel
+CQ_INCLUDE_TRYBOTS=luci.chromium.try:mac_optional_gpu_tests_rel
+CQ_INCLUDE_TRYBOTS=luci.chromium.try:win_optional_gpu_tests_rel
+CQ_INCLUDE_TRYBOTS=luci.chromium.try:android_optional_gpu_tests_rel""")
class Preparation(Step):
MESSAGE = "Preparation."
@@ -40,7 +47,7 @@
self["last_roll"] = self._options.last_roll
if not self["last_roll"]:
# Interpret the DEPS file to retrieve the v8 revision.
- # TODO(machenbach): This should be part or the roll-deps api of
+ # TODO(machenbach): This should be part or the setdep api of
# depot_tools.
Var = lambda var: '%s'
exec(FileToText(os.path.join(self._options.chromium, "DEPS")))
@@ -140,7 +147,7 @@
self['json_output']['monitoring_state'] = 'upload'
cwd = self._options.chromium
# Patch DEPS file.
- if self.Command("roll-dep-svn", "v8 %s" %
+ if self.Command("gclient", "setdep -r src/v8@%s" %
self["roll"], cwd=cwd) is None:
self.Die("Failed to create deps for %s" % self["roll"])
@@ -155,15 +162,14 @@
message.append("TBR=%s" % self._options.reviewer)
self.GitCommit("\n\n".join(message), author=self._options.author, cwd=cwd)
if not self._options.dry_run:
- self.GitUpload(author=self._options.author,
- force=True,
+ self.GitUpload(force=True,
bypass_hooks=True,
cq=self._options.use_commit_queue,
cq_dry_run=self._options.use_dry_run,
cwd=cwd)
- print "CL uploaded."
+ print("CL uploaded.")
else:
- print "Dry run - don't upload."
+ print("Dry run - don't upload.")
self.GitCheckout("master", cwd=cwd)
self.GitDeleteBranch("work-branch", cwd=cwd)
@@ -206,7 +212,7 @@
def _ProcessOptions(self, options): # pragma: no cover
if not options.author or not options.reviewer:
- print "A reviewer (-r) and an author (-a) are required."
+ print("A reviewer (-r) and an author (-a) are required.")
return False
options.requires_editor = False
diff --git a/src/v8/tools/release/auto_tag.py b/src/v8/tools/release/auto_tag.py
index a52a028..fddefed 100755
--- a/src/v8/tools/release/auto_tag.py
+++ b/src/v8/tools/release/auto_tag.py
@@ -3,6 +3,9 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+# for py2/py3 compatibility
+from __future__ import print_function
+
import argparse
import sys
@@ -15,7 +18,7 @@
def RunStep(self):
# TODO(machenbach): Remove after the git switch.
if self.Config("PERSISTFILE_BASENAME") == "/tmp/v8-auto-tag-tempfile":
- print "This script is disabled until after the v8 git migration."
+ print("This script is disabled until after the v8 git migration.")
return True
self.CommonPrepare()
@@ -80,7 +83,7 @@
self["candidate_version"] = version
if not self["candidate"] or not self["candidate_version"]:
- print "Nothing found to tag."
+ print("Nothing found to tag.")
self.CommonCleanup()
return True
@@ -120,18 +123,18 @@
# Don't include the version change commit itself if there is no upper
# limit yet.
candidate_svn = str(int(candidate_svn) + 1)
- next_svn = sys.maxint
+ next_svn = sys.maxsize
lkgr_svn = self.LastLKGR(candidate_svn, next_svn)
if not lkgr_svn:
- print "There is no lkgr since the candidate version yet."
+ print("There is no lkgr since the candidate version yet.")
self.CommonCleanup()
return True
# Let's check if the lkgr is at least three hours old.
self["lkgr"] = self.vc.SvnGit(lkgr_svn)
if not self["lkgr"]:
- print "Couldn't find git hash for lkgr %s" % lkgr_svn
+ print("Couldn't find git hash for lkgr %s" % lkgr_svn)
self.CommonCleanup()
return True
@@ -139,11 +142,11 @@
current_utc_time = self._side_effect_handler.GetUTCStamp()
if current_utc_time < lkgr_utc_time + 10800:
- print "Candidate lkgr %s is too recent for tagging." % lkgr_svn
+ print("Candidate lkgr %s is too recent for tagging." % lkgr_svn)
self.CommonCleanup()
return True
- print "Tagging revision %s with %s" % (lkgr_svn, self["candidate_version"])
+ print("Tagging revision %s with %s" % (lkgr_svn, self["candidate_version"]))
class MakeTag(Step):
@@ -172,7 +175,7 @@
def _ProcessOptions(self, options): # pragma: no cover
if not options.dry_run and not options.author:
- print "Specify your chromium.org email with -a"
+ print("Specify your chromium.org email with -a")
return False
options.wait_for_lgtm = False
options.force_readline_defaults = True
diff --git a/src/v8/tools/release/check_clusterfuzz.py b/src/v8/tools/release/check_clusterfuzz.py
index 8af8351..021cd55 100755
--- a/src/v8/tools/release/check_clusterfuzz.py
+++ b/src/v8/tools/release/check_clusterfuzz.py
@@ -13,6 +13,8 @@
suppress stdout and stderr and only process contents of the results_file.
"""
+# for py2/py3 compatibility
+from __future__ import print_function
import argparse
import httplib
@@ -222,7 +224,7 @@
with open(options.results_file, "w") as f:
f.write(json.dumps(results))
else:
- print results
+ print(results)
if __name__ == "__main__":
diff --git a/src/v8/tools/release/common_includes.py b/src/v8/tools/release/common_includes.py
index d295e37..af4709b 100644
--- a/src/v8/tools/release/common_includes.py
+++ b/src/v8/tools/release/common_includes.py
@@ -26,6 +26,9 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# for py2/py3 compatibility
+from __future__ import print_function
+
import argparse
import datetime
import httplib
@@ -51,11 +54,19 @@
PUSH_MSG_NEW_RE = re.compile(r"^Version \d+\.\d+\.\d+$")
VERSION_FILE = os.path.join("include", "v8-version.h")
WATCHLISTS_FILE = "WATCHLISTS"
+RELEASE_WORKDIR = "/tmp/v8-release-scripts-work-dir/"
# V8 base directory.
V8_BASE = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+# Add our copy of depot_tools to the PATH as many scripts use tools from there,
+# e.g. git-cl, fetch, git-new-branch etc, and we can not depend on depot_tools
+# being in the PATH on the LUCI bots.
+path_to_depot_tools = os.path.join(V8_BASE, 'third_party', 'depot_tools')
+new_path = path_to_depot_tools + os.pathsep + os.environ.get('PATH')
+os.environ['PATH'] = new_path
+
def TextToFile(text, file_name):
with open(file_name, "w") as f:
@@ -191,8 +202,8 @@
cwd = cwd or os.getcwd()
# TODO(machenbach): Use timeout.
cmd_line = "%s %s %s" % (prefix, cmd, args)
- print "Command: %s" % cmd_line
- print "in %s" % cwd
+ print("Command: %s" % cmd_line)
+ print("in %s" % cwd)
sys.stdout.flush()
try:
if pipe:
@@ -264,8 +275,8 @@
try:
return json.loads(data)
except:
- print data
- print "ERROR: Could not read response. Is your key valid?"
+ print(data)
+ print("ERROR: Could not read response. Is your key valid?")
raise
def Sleep(self, seconds):
@@ -440,7 +451,7 @@
if not self._state and os.path.exists(state_file):
self._state.update(json.loads(FileToText(state_file)))
- print ">>> Step %d: %s" % (self._number, self._text)
+ print(">>> Step %d: %s" % (self._number, self._text))
try:
return self.RunStep()
finally:
@@ -476,16 +487,16 @@
raise Exception("Retried too often. Giving up. Reason: %s" %
str(got_exception))
wait_time = wait_plan.pop()
- print "Waiting for %f seconds." % wait_time
+ print("Waiting for %f seconds." % wait_time)
self._side_effect_handler.Sleep(wait_time)
- print "Retrying..."
+ print("Retrying...")
else:
return result
def ReadLine(self, default=None):
# Don't prompt in forced mode.
if self._options.force_readline_defaults and default is not None:
- print "%s (forced)" % default
+ print("%s (forced)" % default)
return default
else:
return self._side_effect_handler.ReadLine()
@@ -521,8 +532,8 @@
def Die(self, msg=""):
if msg != "":
- print "Error: %s" % msg
- print "Exiting"
+ print("Error: %s" % msg)
+ print("Exiting")
raise Exception(msg)
def DieNoManualMode(self, msg=""):
@@ -531,7 +542,7 @@
self.Die(msg)
def Confirm(self, msg):
- print "%s [Y/n] " % msg,
+ print("%s [Y/n] " % msg, end=' ')
answer = self.ReadLine(default="Y")
return answer == "" or answer == "Y" or answer == "y"
@@ -541,7 +552,7 @@
msg = "Branch %s exists, do you want to delete it?" % name
if self.Confirm(msg):
self.GitDeleteBranch(name, cwd=cwd)
- print "Branch %s deleted." % name
+ print("Branch %s deleted." % name)
else:
msg = "Can't continue. Please delete branch %s and try again." % name
self.Die(msg)
@@ -604,10 +615,10 @@
"change the headline of the uploaded CL.")
answer = ""
while answer != "LGTM":
- print "> ",
+ print("> ", end=' ')
answer = self.ReadLine(None if self._options.wait_for_lgtm else "LGTM")
if answer != "LGTM":
- print "That was not 'LGTM'."
+ print("That was not 'LGTM'.")
def WaitForResolvingConflicts(self, patch_file):
print("Applying the patch \"%s\" failed. Either type \"ABORT<Return>\", "
@@ -619,8 +630,8 @@
if answer == "ABORT":
self.Die("Applying the patch failed.")
if answer != "":
- print "That was not 'RESOLVED' or 'ABORT'."
- print "> ",
+ print("That was not 'RESOLVED' or 'ABORT'.")
+ print("> ", end=' ')
answer = self.ReadLine()
# Takes a file containing the patch to apply as first argument.
@@ -759,16 +770,26 @@
MESSAGE = "Upload for code review."
def RunStep(self):
+ reviewer = None
if self._options.reviewer:
- print "Using account %s for review." % self._options.reviewer
+ print("Using account %s for review." % self._options.reviewer)
reviewer = self._options.reviewer
- else:
- print "Please enter the email address of a V8 reviewer for your patch: ",
+
+ tbr_reviewer = None
+ if self._options.tbr_reviewer:
+ print("Using account %s for TBR review." % self._options.tbr_reviewer)
+ tbr_reviewer = self._options.tbr_reviewer
+
+ if not reviewer and not tbr_reviewer:
+ print(
+ "Please enter the email address of a V8 reviewer for your patch: ",
+ end=' ')
self.DieNoManualMode("A reviewer must be specified in forced mode.")
reviewer = self.ReadLine()
- self.GitUpload(reviewer, self._options.author, self._options.force_upload,
+
+ self.GitUpload(reviewer, self._options.force_upload,
bypass_hooks=self._options.bypass_upload_hooks,
- cc=self._options.cc)
+ cc=self._options.cc, tbr_reviewer=tbr_reviewer)
def MakeStep(step_class=Step, number=0, state=None, config=None,
@@ -821,6 +842,8 @@
help="File to write results summary to.")
parser.add_argument("-r", "--reviewer", default="",
help="The account name to be used for reviews.")
+ parser.add_argument("--tbr-reviewer", "--tbr", default="",
+ help="The account name to be used for TBR reviews.")
parser.add_argument("-s", "--step",
help="Specify the step where to start work. Default: 0.",
default=0, type=int)
@@ -836,7 +859,7 @@
# Process common options.
if options.step < 0: # pragma: no cover
- print "Bad step number %d" % options.step
+ print("Bad step number %d" % options.step)
parser.print_help()
return None
@@ -865,6 +888,11 @@
if not options:
return 1
+ # Ensure temp dir exists for state files.
+ state_dir = os.path.dirname(self._config["PERSISTFILE_BASENAME"])
+ if not os.path.exists(state_dir):
+ os.makedirs(state_dir)
+
state_file = "%s-state.json" % self._config["PERSISTFILE_BASENAME"]
if options.step == 0 and os.path.exists(state_file):
os.remove(state_file)
diff --git a/src/v8/tools/release/create_release.py b/src/v8/tools/release/create_release.py
index e5c2114..cbf07f4 100755
--- a/src/v8/tools/release/create_release.py
+++ b/src/v8/tools/release/create_release.py
@@ -3,6 +3,9 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+# for py2/py3 compatibility
+from __future__ import print_function
+
import argparse
import os
import sys
@@ -27,7 +30,7 @@
self["push_hash"] = (self._options.revision or
self.GitLog(n=1, format="%H", branch="origin/master"))
assert self["push_hash"]
- print "Release revision %s" % self["push_hash"]
+ print("Release revision %s" % self["push_hash"])
class IncrementVersion(Step):
@@ -138,7 +141,7 @@
def RunStep(self):
cmd = "push origin %s:refs/heads/%s" % (self["push_hash"], self["version"])
if self._options.dry_run:
- print "Dry run. Command:\ngit %s" % cmd
+ print("Dry run. Command:\ngit %s" % cmd)
else:
self.Git(cmd)
@@ -207,8 +210,7 @@
self["commit_title"] = text.splitlines()[0]
TextToFile(text, self.Config("COMMITMSG_FILE"))
- self.GitCommit(file_name = self.Config("COMMITMSG_FILE"))
- os.remove(self.Config("COMMITMSG_FILE"))
+ self.GitCommit(file_name=self.Config("COMMITMSG_FILE"))
os.remove(self.Config("CHANGELOG_ENTRY_FILE"))
@@ -217,18 +219,20 @@
def RunStep(self):
if self._options.dry_run:
- print "Dry run - upload CL."
+ print("Dry run - upload CL.")
else:
- self.GitUpload(author=self._options.author,
- force=True,
+ self.GitUpload(force=True,
bypass_hooks=True,
- private=True)
+ no_autocc=True,
+ message_file=self.Config("COMMITMSG_FILE"))
cmd = "cl land --bypass-hooks -f"
if self._options.dry_run:
- print "Dry run. Command:\ngit %s" % cmd
+ print("Dry run. Command:\ngit %s" % cmd)
else:
self.Git(cmd)
+ os.remove(self.Config("COMMITMSG_FILE"))
+
class TagRevision(Step):
MESSAGE = "Tag the new revision."
@@ -269,7 +273,7 @@
def _ProcessOptions(self, options): # pragma: no cover
if not options.author or not options.reviewer:
- print "Reviewer (-r) and author (-a) are required."
+ print("Reviewer (-r) and author (-a) are required.")
return False
return True
diff --git a/src/v8/tools/release/filter_build_files.py b/src/v8/tools/release/filter_build_files.py
index 7d3f221..1afabe8 100755
--- a/src/v8/tools/release/filter_build_files.py
+++ b/src/v8/tools/release/filter_build_files.py
@@ -25,6 +25,11 @@
'd8',
]
+# Additional executable files added only to ref archive type.
+REFBUILD_EXECUTABLE_FILES = [
+ 'cctest',
+]
+
SUPPLEMENTARY_FILES = [
'icudtl.dat',
'natives_blob.bin',
@@ -35,7 +40,7 @@
LIBRARY_FILES = {
'android': ['*.a', '*.so'],
'linux': ['*.a', '*.so'],
- 'mac': ['*.a', '*.so'],
+ 'mac': ['*.a', '*.so', '*.dylib'],
'win': ['*.lib', '*.dll'],
}
@@ -51,7 +56,7 @@
help='Path to an output file. The files will '
'be stored in json list with absolute paths.')
parser.add_argument('-t', '--type',
- choices=['all', 'exe', 'lib'], default='all',
+ choices=['all', 'exe', 'lib', 'ref'], default='all',
help='Specifies the archive type.')
args = parser.parse_args()
@@ -60,8 +65,8 @@
args.dir = os.path.abspath(args.dir)
- # Skip libraries for exe archive type.
- if args.type == 'exe':
+ # Skip libraries for exe and ref archive types.
+ if args.type in ('exe', 'ref'):
library_files = []
else:
library_files = LIBRARY_FILES[args.platform]
@@ -72,6 +77,9 @@
else:
executable_files = EXECUTABLE_FILES
+ if args.type == 'ref':
+ executable_files.extend(REFBUILD_EXECUTABLE_FILES)
+
list_of_files = []
def add_files_from_globs(globs):
list_of_files.extend(itertools.chain(*map(glob.iglob, globs)))
diff --git a/src/v8/tools/release/git_recipes.py b/src/v8/tools/release/git_recipes.py
index 9dedae8..0997e0b 100644
--- a/src/v8/tools/release/git_recipes.py
+++ b/src/v8/tools/release/git_recipes.py
@@ -205,14 +205,14 @@
args.append(Quoted(patch_file))
self.Git(MakeArgs(args), **kwargs)
- def GitUpload(self, reviewer="", author="", force=False, cq=False,
- cq_dry_run=False, bypass_hooks=False, cc="", private=False,
- **kwargs):
+ def GitUpload(self, reviewer="", force=False, cq=False,
+ cq_dry_run=False, bypass_hooks=False, cc="", tbr_reviewer="",
+ no_autocc=False, message_file=None, **kwargs):
args = ["cl upload --send-mail"]
- if author:
- args += ["--email", Quoted(author)]
if reviewer:
args += ["-r", Quoted(reviewer)]
+ if tbr_reviewer:
+ args += ["--tbrs", Quoted(tbr_reviewer)]
if force:
args.append("-f")
if cq:
@@ -221,11 +221,13 @@
args.append("--cq-dry-run")
if bypass_hooks:
args.append("--bypass-hooks")
+ if no_autocc:
+ args.append("--no-autocc")
if cc:
args += ["--cc", Quoted(cc)]
+ if message_file:
+ args += ["--message-file", Quoted(message_file)]
args += ["--gerrit"]
- if private:
- args += ["--private"]
# TODO(machenbach): Check output in forced mode. Verify that all required
# base files were uploaded, if not retry.
self.Git(MakeArgs(args), pipe=False, **kwargs)
diff --git a/src/v8/tools/release/merge_to_branch.py b/src/v8/tools/release/merge_to_branch.py
index 877d121..c959429 100755
--- a/src/v8/tools/release/merge_to_branch.py
+++ b/src/v8/tools/release/merge_to_branch.py
@@ -26,6 +26,9 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# for py2/py3 compatibility
+from __future__ import print_function
+
import argparse
from collections import OrderedDict
import sys
@@ -186,10 +189,10 @@
def RunStep(self):
self.CommonCleanup()
- print "*** SUMMARY ***"
- print "branch: %s" % self["merge_to_branch"]
+ print("*** SUMMARY ***")
+ print("branch: %s" % self["merge_to_branch"])
if self["revision_list"]:
- print "patches: %s" % self["revision_list"]
+ print("patches: %s" % self["revision_list"])
class MergeToBranch(ScriptsBase):
@@ -215,10 +218,10 @@
def _ProcessOptions(self, options):
if len(options.revisions) < 1:
if not options.patch:
- print "Either a patch file or revision numbers must be specified"
+ print("Either a patch file or revision numbers must be specified")
return False
if not options.message:
- print "You must specify a merge comment if no patches are specified"
+ print("You must specify a merge comment if no patches are specified")
return False
options.bypass_upload_hooks = True
# CC ulan to make sure that fixes are merged to Google3.
@@ -233,19 +236,20 @@
for revision in options.revisions:
if (IsSvnNumber(revision) or
(revision[0:1] == "r" and IsSvnNumber(revision[1:]))):
- print "Please provide full git hashes of the patches to merge."
- print "Got: %s" % revision
+ print("Please provide full git hashes of the patches to merge.")
+ print("Got: %s" % revision)
return False
return True
def _Config(self):
return {
"BRANCHNAME": "prepare-merge",
- "PERSISTFILE_BASENAME": "/tmp/v8-merge-to-branch-tempfile",
+ "PERSISTFILE_BASENAME": RELEASE_WORKDIR + "v8-merge-to-branch-tempfile",
"ALREADY_MERGING_SENTINEL_FILE":
- "/tmp/v8-merge-to-branch-tempfile-already-merging",
- "TEMPORARY_PATCH_FILE": "/tmp/v8-prepare-merge-tempfile-temporary-patch",
- "COMMITMSG_FILE": "/tmp/v8-prepare-merge-tempfile-commitmsg",
+ RELEASE_WORKDIR + "v8-merge-to-branch-tempfile-already-merging",
+ "TEMPORARY_PATCH_FILE":
+ RELEASE_WORKDIR + "v8-prepare-merge-tempfile-temporary-patch",
+ "COMMITMSG_FILE": RELEASE_WORKDIR + "v8-prepare-merge-tempfile-commitmsg",
}
def _Steps(self):
diff --git a/src/v8/tools/release/mergeinfo.py b/src/v8/tools/release/mergeinfo.py
index 1e29ece..bed7441 100755
--- a/src/v8/tools/release/mergeinfo.py
+++ b/src/v8/tools/release/mergeinfo.py
@@ -3,6 +3,9 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+# for py2/py3 compatibility
+from __future__ import print_function
+
import argparse
import os
import sys
@@ -77,32 +80,32 @@
return version
def print_analysis(git_working_dir, hash_to_search):
- print '1.) Searching for "' + hash_to_search + '"'
- print '=====================ORIGINAL COMMIT START==================='
- print describe_commit(git_working_dir, hash_to_search)
- print '=====================ORIGINAL COMMIT END====================='
- print '2.) General information:'
+ print('1.) Searching for "' + hash_to_search + '"')
+ print('=====================ORIGINAL COMMIT START===================')
+ print(describe_commit(git_working_dir, hash_to_search))
+ print('=====================ORIGINAL COMMIT END=====================')
+ print('2.) General information:')
branches = get_branches_for_commit(git_working_dir, hash_to_search)
- print 'Is LKGR: ' + str(is_lkgr(branches))
- print 'Is on Canary: ' + str(get_first_canary(branches))
- print 'First V8 branch: ' + str(get_first_v8_version(branches)) + \
- ' (Might not be the rolled version)'
- print '3.) Found follow-up commits, reverts and ports:'
+ print('Is LKGR: ' + str(is_lkgr(branches)))
+ print('Is on Canary: ' + str(get_first_canary(branches)))
+ print('First V8 branch: ' + str(get_first_v8_version(branches)) + \
+ ' (Might not be the rolled version)')
+ print('3.) Found follow-up commits, reverts and ports:')
followups = get_followup_commits(git_working_dir, hash_to_search)
for followup in followups:
- print describe_commit(git_working_dir, followup, True)
+ print(describe_commit(git_working_dir, followup, True))
- print '4.) Found merges:'
+ print('4.) Found merges:')
merges = get_merge_commits(git_working_dir, hash_to_search)
for currentMerge in merges:
- print describe_commit(git_working_dir, currentMerge, True)
- print '---Merged to:'
+ print(describe_commit(git_working_dir, currentMerge, True))
+ print('---Merged to:')
mergeOutput = git_execute(git_working_dir, ['branch',
'--contains',
currentMerge,
'-r']).strip()
- print mergeOutput
- print 'Finished successfully'
+ print(mergeOutput)
+ print('Finished successfully')
if __name__ == '__main__': # pragma: no cover
parser = argparse.ArgumentParser('Tool to check where a git commit was'
diff --git a/src/v8/tools/release/push_to_candidates.py b/src/v8/tools/release/push_to_candidates.py
index 538b988..c706896 100755
--- a/src/v8/tools/release/push_to_candidates.py
+++ b/src/v8/tools/release/push_to_candidates.py
@@ -26,6 +26,9 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# for py2/py3 compatibility
+from __future__ import print_function
+
import argparse
import os
import sys
@@ -46,7 +49,7 @@
if(self["current_branch"] == self.Config("CANDIDATESBRANCH")
or self["current_branch"] == self.Config("BRANCHNAME")):
- print "Warning: Script started on branch %s" % self["current_branch"]
+ print("Warning: Script started on branch %s" % self["current_branch"])
self.PrepareBranch()
self.DeleteBranch(self.Config("CANDIDATESBRANCH"))
@@ -347,10 +350,10 @@
def _ProcessOptions(self, options): # pragma: no cover
if not options.manual and not options.reviewer:
- print "A reviewer (-r) is required in (semi-)automatic mode."
+ print("A reviewer (-r) is required in (semi-)automatic mode.")
return False
if not options.manual and not options.author:
- print "Specify your chromium.org email with -a in (semi-)automatic mode."
+ print("Specify your chromium.org email with -a in (semi-)automatic mode.")
return False
options.tbr_commit = not options.manual
diff --git a/src/v8/tools/release/roll_merge.py b/src/v8/tools/release/roll_merge.py
index 2dd43ea..2f03abb 100755
--- a/src/v8/tools/release/roll_merge.py
+++ b/src/v8/tools/release/roll_merge.py
@@ -26,6 +26,9 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# for py2/py3 compatibility
+from __future__ import print_function
+
import argparse
from collections import OrderedDict
import sys
@@ -202,7 +205,7 @@
MESSAGE = "Create the tag."
def RunStep(self):
- print "Creating tag %s" % self["version"]
+ print("Creating tag %s" % self["version"])
self.vc.Tag(self["version"],
self.vc.RemoteBranch(self["merge_to_branch"]),
self["commit_title"])
@@ -213,11 +216,11 @@
def RunStep(self):
self.CommonCleanup()
- print "*** SUMMARY ***"
- print "version: %s" % self["version"]
- print "branch: %s" % self["merge_to_branch"]
+ print("*** SUMMARY ***")
+ print("version: %s" % self["version"])
+ print("branch: %s" % self["merge_to_branch"])
if self["revision_list"]:
- print "patches: %s" % self["revision_list"]
+ print("patches: %s" % self["revision_list"])
class RollMerge(ScriptsBase):
@@ -241,10 +244,10 @@
def _ProcessOptions(self, options):
if len(options.revisions) < 1:
if not options.patch:
- print "Either a patch file or revision numbers must be specified"
+ print("Either a patch file or revision numbers must be specified")
return False
if not options.message:
- print "You must specify a merge comment if no patches are specified"
+ print("You must specify a merge comment if no patches are specified")
return False
options.bypass_upload_hooks = True
# CC ulan to make sure that fixes are merged to Google3.
@@ -254,19 +257,21 @@
for revision in options.revisions:
if (IsSvnNumber(revision) or
(revision[0:1] == "r" and IsSvnNumber(revision[1:]))):
- print "Please provide full git hashes of the patches to merge."
- print "Got: %s" % revision
+ print("Please provide full git hashes of the patches to merge.")
+ print("Got: %s" % revision)
return False
return True
def _Config(self):
return {
"BRANCHNAME": "prepare-merge",
- "PERSISTFILE_BASENAME": "/tmp/v8-merge-to-branch-tempfile",
+ "PERSISTFILE_BASENAME":
+ RELEASE_WORKDIR + "v8-merge-to-branch-tempfile",
"ALREADY_MERGING_SENTINEL_FILE":
- "/tmp/v8-merge-to-branch-tempfile-already-merging",
- "TEMPORARY_PATCH_FILE": "/tmp/v8-prepare-merge-tempfile-temporary-patch",
- "COMMITMSG_FILE": "/tmp/v8-prepare-merge-tempfile-commitmsg",
+ RELEASE_WORKDIR + "v8-merge-to-branch-tempfile-already-merging",
+ "TEMPORARY_PATCH_FILE":
+ RELEASE_WORKDIR + "v8-prepare-merge-tempfile-temporary-patch",
+ "COMMITMSG_FILE": RELEASE_WORKDIR + "v8-prepare-merge-tempfile-commitmsg",
}
def _Steps(self):
diff --git a/src/v8/tools/release/script_test.py b/src/v8/tools/release/script_test.py
index b9a17e9..0f345b7 100755
--- a/src/v8/tools/release/script_test.py
+++ b/src/v8/tools/release/script_test.py
@@ -29,6 +29,9 @@
# Wraps test execution with a coverage analysis. To get the best speed, the
# native python coverage version >= 3.7.1 should be installed.
+# for py2/py3 compatibility
+from __future__ import print_function
+
import coverage
import os
import unittest
@@ -46,7 +49,7 @@
])
unittest.TextTestRunner(verbosity=2).run(unittest.TestSuite(alltests))
cov.stop()
- print cov.report()
+ print(cov.report())
if __name__ == '__main__':
diff --git a/src/v8/tools/release/search_related_commits.py b/src/v8/tools/release/search_related_commits.py
index d27aa56..e6e52d2 100755
--- a/src/v8/tools/release/search_related_commits.py
+++ b/src/v8/tools/release/search_related_commits.py
@@ -3,6 +3,9 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+# for py2/py3 compatibility
+from __future__ import print_function
+
import argparse
import operator
import os
@@ -17,7 +20,7 @@
all_commits_raw = _find_commits_inbetween(
start_hash, until, git_working_dir, verbose)
if verbose:
- print "All commits between <of> and <until>: " + all_commits_raw
+ print("All commits between <of> and <until>: " + all_commits_raw)
# Adding start hash too
all_commits = [start_hash]
@@ -61,7 +64,7 @@
commit_position = matches.group(2)
if verbose:
- print "1.) Commit position to look for: " + commit_position
+ print("1.) Commit position to look for: " + commit_position)
search_range = start_hash + ".." + until
@@ -78,13 +81,13 @@
git_working_dir, git_args(start_hash), verbose).strip()
if verbose:
- print "2.) Found by hash: " + found_by_hash
+ print("2.) Found by hash: " + found_by_hash)
found_by_commit_pos = git_execute(
git_working_dir, git_args(commit_position), verbose).strip()
if verbose:
- print "3.) Found by commit position: " + found_by_commit_pos
+ print("3.) Found by commit position: " + found_by_commit_pos)
# Replace brackets or else they are wrongly interpreted by --grep
title = title.replace("[", "\\[")
@@ -94,7 +97,7 @@
git_working_dir, git_args(title), verbose).strip()
if verbose:
- print "4.) Found by title: " + found_by_title
+ print("4.) Found by title: " + found_by_title)
hits = (
_convert_to_array(found_by_hash) +
@@ -132,8 +135,8 @@
def git_execute(working_dir, args, verbose=False):
command = ["git", "-C", working_dir] + args
if verbose:
- print "Git working dir: " + working_dir
- print "Executing git command:" + str(command)
+ print("Git working dir: " + working_dir)
+ print("Executing git command:" + str(command))
p = Popen(args=command, stdin=PIPE,
stdout=PIPE, stderr=PIPE)
output, err = p.communicate()
@@ -141,7 +144,7 @@
if rc != 0:
raise Exception(err)
if verbose:
- print "Git return value: " + output
+ print("Git return value: " + output)
return output
def _pretty_print_entry(hash, git_dir, pre_text, verbose):
@@ -215,4 +218,4 @@
args = sys.argv[1:]
options = parser.parse_args(args)
for current_line in main(options):
- print current_line
+ print(current_line)
diff --git a/src/v8/tools/release/test_scripts.py b/src/v8/tools/release/test_scripts.py
index 759012d..62158d9 100755
--- a/src/v8/tools/release/test_scripts.py
+++ b/src/v8/tools/release/test_scripts.py
@@ -26,6 +26,9 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# for py2/py3 compatibility
+from __future__ import print_function
+
import os
import shutil
import tempfile
@@ -43,8 +46,6 @@
from merge_to_branch import MergeToBranch
import push_to_candidates
from push_to_candidates import *
-import releases
-from releases import Releases
from auto_tag import AutoTag
import roll_merge
from roll_merge import RollMerge
@@ -97,38 +98,6 @@
]
self.assertEquals(expected, NormalizeVersionTags(input))
- def testSortBranches(self):
- S = releases.SortBranches
- self.assertEquals(["3.1", "2.25"], S(["2.25", "3.1"])[0:2])
- self.assertEquals(["3.0", "2.25"], S(["2.25", "3.0", "2.24"])[0:2])
- self.assertEquals(["3.11", "3.2"], S(["3.11", "3.2", "2.24"])[0:2])
-
- def testFilterDuplicatesAndReverse(self):
- F = releases.FilterDuplicatesAndReverse
- self.assertEquals([], F([]))
- self.assertEquals([["100", "10"]], F([["100", "10"]]))
- self.assertEquals([["99", "9"], ["100", "10"]],
- F([["100", "10"], ["99", "9"]]))
- self.assertEquals([["98", "9"], ["100", "10"]],
- F([["100", "10"], ["99", "9"], ["98", "9"]]))
- self.assertEquals([["98", "9"], ["99", "10"]],
- F([["100", "10"], ["99", "10"], ["98", "9"]]))
-
- def testBuildRevisionRanges(self):
- B = releases.BuildRevisionRanges
- self.assertEquals({}, B([]))
- self.assertEquals({"10": "100"}, B([["100", "10"]]))
- self.assertEquals({"10": "100", "9": "99:99"},
- B([["100", "10"], ["99", "9"]]))
- self.assertEquals({"10": "100", "9": "97:99"},
- B([["100", "10"], ["98", "9"], ["97", "9"]]))
- self.assertEquals({"10": "100", "9": "99:99", "3": "91:98"},
- B([["100", "10"], ["99", "9"], ["91", "3"]]))
- self.assertEquals({"13": "101", "12": "100:100", "9": "94:97",
- "3": "91:93, 98:99"},
- B([["101", "13"], ["100", "12"], ["98", "3"],
- ["94", "9"], ["91", "3"]]))
-
def testMakeComment(self):
self.assertEquals("# Line 1\n# Line 2\n#",
MakeComment(" Line 1\n Line 2\n"))
@@ -417,11 +386,11 @@
return script(TEST_CONFIG, self, self._state).RunSteps([step_class], args)
def Call(self, fun, *args, **kwargs):
- print "Calling %s with %s and %s" % (str(fun), str(args), str(kwargs))
+ print("Calling %s with %s and %s" % (str(fun), str(args), str(kwargs)))
def Command(self, cmd, args="", prefix="", pipe=True, cwd=None):
- print "%s %s" % (cmd, args)
- print "in %s" % cwd
+ print("%s %s" % (cmd, args))
+ print("in %s" % cwd)
return self._mock.Call("command", cmd + " " + args, cwd=cwd)
def ReadLine(self):
@@ -967,8 +936,9 @@
cb=self.WriteFakeWatchlistsFile),
Cmd("git commit -aF \"%s\"" % TEST_CONFIG["COMMITMSG_FILE"], "",
cb=CheckVersionCommit),
- Cmd("git cl upload --send-mail --email \"author@chromium.org\" "
- "-f --bypass-hooks --gerrit --private", ""),
+ Cmd("git cl upload --send-mail "
+ "-f --bypass-hooks --no-autocc --message-file "
+ "\"%s\" --gerrit" % TEST_CONFIG["COMMITMSG_FILE"], ""),
Cmd("git cl land --bypass-hooks -f", ""),
Cmd("git fetch", ""),
Cmd("git log -1 --format=%H --grep="
@@ -1031,13 +1001,17 @@
https://chromium.googlesource.com/v8/v8/+log/last_rol..roll_hsh
Please follow these instructions for assigning/CC'ing issues:
-https://github.com/v8/v8/wiki/Triaging%20issues
+https://v8.dev/docs/triage-issues
Please close rolling in case of a roll revert:
https://v8-roll.appspot.com/
This only works with a Google account.
-CQ_INCLUDE_TRYBOTS=master.tryserver.blink:linux_trusty_blink_rel;master.tryserver.chromium.linux:linux_optional_gpu_tests_rel;master.tryserver.chromium.mac:mac_optional_gpu_tests_rel;master.tryserver.chromium.win:win_optional_gpu_tests_rel;master.tryserver.chromium.android:android_optional_gpu_tests_rel
+CQ_INCLUDE_TRYBOTS=luci.chromium.try:linux-blink-rel
+CQ_INCLUDE_TRYBOTS=luci.chromium.try:linux_optional_gpu_tests_rel
+CQ_INCLUDE_TRYBOTS=luci.chromium.try:mac_optional_gpu_tests_rel
+CQ_INCLUDE_TRYBOTS=luci.chromium.try:win_optional_gpu_tests_rel
+CQ_INCLUDE_TRYBOTS=luci.chromium.try:android_optional_gpu_tests_rel
TBR=reviewer@chromium.org"""
@@ -1113,12 +1087,13 @@
Cmd("git pull", "", cwd=chrome_dir),
Cmd("git fetch origin", ""),
Cmd("git new-branch work-branch", "", cwd=chrome_dir),
- Cmd("roll-dep-svn v8 roll_hsh", "rolled", cb=WriteDeps, cwd=chrome_dir),
+ Cmd("gclient setdep -r src/v8@roll_hsh", "", cb=WriteDeps,
+ cwd=chrome_dir),
Cmd(("git commit -am \"%s\" "
"--author \"author@chromium.org <author@chromium.org>\"" %
self.ROLL_COMMIT_MSG),
"", cwd=chrome_dir),
- Cmd("git cl upload --send-mail --email \"author@chromium.org\" -f "
+ Cmd("git cl upload --send-mail -f "
"--cq-dry-run --bypass-hooks --gerrit", "",
cwd=chrome_dir),
Cmd("git checkout -f master", "", cwd=chrome_dir),
@@ -1307,251 +1282,6 @@
args += ["-s", "4"]
RollMerge(TEST_CONFIG, self).Run(args)
- def testReleases(self):
- c_hash1_commit_log = """Update V8 to Version 4.2.71.
-
-Cr-Commit-Position: refs/heads/master@{#5678}
-"""
- c_hash2_commit_log = """Revert something.
-
-BUG=12345
-
-Reason:
-> Some reason.
-> Cr-Commit-Position: refs/heads/master@{#12345}
-> git-svn-id: svn://svn.chromium.org/chrome/trunk/src@12345 003-1c4
-
-Review URL: https://codereview.chromium.org/12345
-
-Cr-Commit-Position: refs/heads/master@{#4567}
-git-svn-id: svn://svn.chromium.org/chrome/trunk/src@4567 0039-1c4b
-
-"""
- c_hash3_commit_log = """Simple.
-
-git-svn-id: svn://svn.chromium.org/chrome/trunk/src@3456 0039-1c4b
-
-"""
- c_hash_234_commit_log = """Version 3.3.1.1 (cherry-pick).
-
-Merged abc12.
-
-Review URL: fake.com
-
-Cr-Commit-Position: refs/heads/candidates@{#234}
-"""
- c_hash_123_commit_log = """Version 3.3.1.0
-
-git-svn-id: googlecode@123 0039-1c4b
-"""
- c_hash_345_commit_log = """Version 3.4.0.
-
-Cr-Commit-Position: refs/heads/candidates@{#345}
-"""
- c_hash_456_commit_log = """Version 4.2.71.
-
-Cr-Commit-Position: refs/heads/4.2.71@{#1}
-"""
- c_deps = "Line\n \"v8_revision\": \"%s\",\n line\n"
-
- json_output = self.MakeEmptyTempFile()
- csv_output = self.MakeEmptyTempFile()
- self.WriteFakeVersionFile()
-
- TEST_CONFIG["CHROMIUM"] = self.MakeEmptyTempDirectory()
- chrome_dir = TEST_CONFIG["CHROMIUM"]
- chrome_v8_dir = os.path.join(chrome_dir, "v8")
- os.makedirs(chrome_v8_dir)
-
- def ResetVersion(major, minor, build, patch=0):
- return lambda: self.WriteFakeVersionFile(major=major,
- minor=minor,
- build=build,
- patch=patch)
-
- self.Expect([
- Cmd("git status -s -uno", ""),
- Cmd("git checkout -f origin/master", ""),
- Cmd("git fetch", ""),
- Cmd("git branch", " branch1\n* branch2\n"),
- Cmd("git new-branch %s" % TEST_CONFIG["BRANCHNAME"], ""),
- Cmd("git fetch origin +refs/tags/*:refs/tags/*", ""),
- Cmd("git rev-list --max-age=395200 --tags",
- "bad_tag\nhash_234\nhash_123\nhash_345\nhash_456\n"),
- Cmd("git describe --tags bad_tag", "3.23.42-1-deadbeef"),
- Cmd("git describe --tags hash_234", "3.3.1.1"),
- Cmd("git describe --tags hash_123", "3.21.2"),
- Cmd("git describe --tags hash_345", "3.22.3"),
- Cmd("git describe --tags hash_456", "4.2.71"),
- Cmd("git diff --name-only hash_234 hash_234^", VERSION_FILE),
- Cmd("git checkout -f hash_234 -- %s" % VERSION_FILE, "",
- cb=ResetVersion(3, 3, 1, 1)),
- Cmd("git branch -r --contains hash_234", " branch-heads/3.3\n"),
- Cmd("git log -1 --format=%B hash_234", c_hash_234_commit_log),
- Cmd("git log -1 --format=%s hash_234", ""),
- Cmd("git log -1 --format=%B hash_234", c_hash_234_commit_log),
- Cmd("git log -1 --format=%ci hash_234", "18:15"),
- Cmd("git checkout -f HEAD -- %s" % VERSION_FILE, "",
- cb=ResetVersion(3, 22, 5)),
- Cmd("git diff --name-only hash_123 hash_123^", VERSION_FILE),
- Cmd("git checkout -f hash_123 -- %s" % VERSION_FILE, "",
- cb=ResetVersion(3, 21, 2)),
- Cmd("git branch -r --contains hash_123", " branch-heads/3.21\n"),
- Cmd("git log -1 --format=%B hash_123", c_hash_123_commit_log),
- Cmd("git log -1 --format=%s hash_123", ""),
- Cmd("git log -1 --format=%B hash_123", c_hash_123_commit_log),
- Cmd("git log -1 --format=%ci hash_123", "03:15"),
- Cmd("git checkout -f HEAD -- %s" % VERSION_FILE, "",
- cb=ResetVersion(3, 22, 5)),
- Cmd("git diff --name-only hash_345 hash_345^", VERSION_FILE),
- Cmd("git checkout -f hash_345 -- %s" % VERSION_FILE, "",
- cb=ResetVersion(3, 22, 3)),
- Cmd("git branch -r --contains hash_345", " origin/candidates\n"),
- Cmd("git log -1 --format=%B hash_345", c_hash_345_commit_log),
- Cmd("git log -1 --format=%s hash_345", ""),
- Cmd("git log -1 --format=%B hash_345", c_hash_345_commit_log),
- Cmd("git log -1 --format=%ci hash_345", ""),
- Cmd("git checkout -f HEAD -- %s" % VERSION_FILE, "",
- cb=ResetVersion(3, 22, 5)),
- Cmd("git diff --name-only hash_456 hash_456^", VERSION_FILE),
- Cmd("git checkout -f hash_456 -- %s" % VERSION_FILE, "",
- cb=ResetVersion(4, 2, 71)),
- Cmd("git branch -r --contains hash_456", " origin/4.2.71\n"),
- Cmd("git log -1 --format=%B hash_456", c_hash_456_commit_log),
- Cmd("git log -1 --format=%H 4.2.71", "hash_456"),
- Cmd("git log -1 --format=%s hash_456", "Version 4.2.71"),
- Cmd("git log -1 --format=%H hash_456^", "master_456"),
- Cmd("git log -1 --format=%B master_456",
- "Cr-Commit-Position: refs/heads/master@{#456}"),
- Cmd("git log -1 --format=%B hash_456", c_hash_456_commit_log),
- Cmd("git log -1 --format=%ci hash_456", "02:15"),
- Cmd("git checkout -f HEAD -- %s" % VERSION_FILE, "",
- cb=ResetVersion(3, 22, 5)),
- Cmd("git fetch origin +refs/heads/*:refs/remotes/origin/* "
- "+refs/branch-heads/*:refs/remotes/branch-heads/*", "",
- cwd=chrome_dir),
- Cmd("git fetch origin", "", cwd=chrome_v8_dir),
- Cmd("git log --format=%H --grep=\"V8\" origin/master -- DEPS",
- "c_hash1\nc_hash2\nc_hash3\n",
- cwd=chrome_dir),
- Cmd("git show c_hash1:DEPS", c_deps % "hash_456", cwd=chrome_dir),
- Cmd("git log -1 --format=%B c_hash1", c_hash1_commit_log,
- cwd=chrome_dir),
- Cmd("git show c_hash2:DEPS", c_deps % "hash_345", cwd=chrome_dir),
- Cmd("git log -1 --format=%B c_hash2", c_hash2_commit_log,
- cwd=chrome_dir),
- Cmd("git show c_hash3:DEPS", c_deps % "deadbeef", cwd=chrome_dir),
- Cmd("git log -1 --format=%B c_hash3", c_hash3_commit_log,
- cwd=chrome_dir),
- Cmd("git branch -r", " weird/123\n branch-heads/7\n", cwd=chrome_dir),
- Cmd("git show refs/branch-heads/7:DEPS", c_deps % "hash_345",
- cwd=chrome_dir),
- URL("http://omahaproxy.appspot.com/all.json", """[{
- "os": "win",
- "versions": [{
- "version": "2.2.2.2",
- "v8_version": "22.2.2.2",
- "current_reldate": "04/09/15",
- "os": "win",
- "channel": "canary",
- "previous_version": "1.1.1.0"
- }]
- }]"""),
- URL("http://omahaproxy.appspot.com/v8.json?version=1.1.1.0", """{
- "chromium_version": "1.1.1.0",
- "v8_version": "11.1.1.0"
- }"""),
- Cmd("git rev-list -1 11.1.1", "v8_previous_version_hash"),
- Cmd("git rev-list -1 22.2.2.2", "v8_version_hash"),
- Cmd("git checkout -f origin/master", ""),
- Cmd("git branch -D %s" % TEST_CONFIG["BRANCHNAME"], "")
- ])
-
- args = ["-c", TEST_CONFIG["CHROMIUM"],
- "--json", json_output,
- "--csv", csv_output,
- "--max-releases", "1"]
- Releases(TEST_CONFIG, self).Run(args)
-
- # Check expected output.
- csv = ("4.2.71,4.2.71,1,5678,\r\n"
- "3.22.3,candidates,345,4567:5677,\r\n"
- "3.21.2,3.21,123,,\r\n"
- "3.3.1.1,3.3,234,,abc12\r\n")
- self.assertEquals(csv, FileToText(csv_output))
-
- expected_json = {"chrome_releases":{
- "canaries": [
- {
- "chrome_version": "2.2.2.2",
- "os": "win",
- "release_date": "04/09/15",
- "v8_version": "22.2.2.2",
- "v8_version_hash": "v8_version_hash",
- "v8_previous_version": "11.1.1.0",
- "v8_previous_version_hash": "v8_previous_version_hash"
- }]},
- "releases":[
- {
- "revision": "1",
- "revision_git": "hash_456",
- "master_position": "456",
- "master_hash": "master_456",
- "patches_merged": "",
- "version": "4.2.71",
- "chromium_revision": "5678",
- "branch": "4.2.71",
- "review_link": "",
- "date": "02:15",
- "chromium_branch": "",
- # FIXME(machenbach): Fix revisions link for git.
- "revision_link": "https://code.google.com/p/v8/source/detail?r=1",
- },
- {
- "revision": "345",
- "revision_git": "hash_345",
- "master_position": "",
- "master_hash": "",
- "patches_merged": "",
- "version": "3.22.3",
- "chromium_revision": "4567:5677",
- "branch": "candidates",
- "review_link": "",
- "date": "",
- "chromium_branch": "7",
- "revision_link": "https://code.google.com/p/v8/source/detail?r=345",
- },
- {
- "revision": "123",
- "revision_git": "hash_123",
- "patches_merged": "",
- "master_position": "",
- "master_hash": "",
- "version": "3.21.2",
- "chromium_revision": "",
- "branch": "3.21",
- "review_link": "",
- "date": "03:15",
- "chromium_branch": "",
- "revision_link": "https://code.google.com/p/v8/source/detail?r=123",
- },
- {
- "revision": "234",
- "revision_git": "hash_234",
- "patches_merged": "abc12",
- "master_position": "",
- "master_hash": "",
- "version": "3.3.1.1",
- "chromium_revision": "",
- "branch": "3.3",
- "review_link": "fake.com",
- "date": "18:15",
- "chromium_branch": "",
- "revision_link": "https://code.google.com/p/v8/source/detail?r=234",
- },],
- }
- self.assertEquals(expected_json, json.loads(FileToText(json_output)))
-
def testMergeToBranch(self):
TEST_CONFIG["ALREADY_MERGING_SENTINEL_FILE"] = self.MakeEmptyTempFile()
TextToFile("", os.path.join(TEST_CONFIG["DEFAULT_CWD"], ".git"))
@@ -1677,250 +1407,5 @@
args += ["-s", "4"]
MergeToBranch(TEST_CONFIG, self).Run(args)
- def testReleases(self):
- c_hash1_commit_log = """Update V8 to Version 4.2.71.
-
-Cr-Commit-Position: refs/heads/master@{#5678}
-"""
- c_hash2_commit_log = """Revert something.
-
-BUG=12345
-
-Reason:
-> Some reason.
-> Cr-Commit-Position: refs/heads/master@{#12345}
-> git-svn-id: svn://svn.chromium.org/chrome/trunk/src@12345 003-1c4
-
-Review URL: https://codereview.chromium.org/12345
-
-Cr-Commit-Position: refs/heads/master@{#4567}
-git-svn-id: svn://svn.chromium.org/chrome/trunk/src@4567 0039-1c4b
-
-"""
- c_hash3_commit_log = """Simple.
-
-git-svn-id: svn://svn.chromium.org/chrome/trunk/src@3456 0039-1c4b
-
-"""
- c_hash_234_commit_log = """Version 3.3.1.1 (cherry-pick).
-
-Merged abc12.
-
-Review URL: fake.com
-
-Cr-Commit-Position: refs/heads/candidates@{#234}
-"""
- c_hash_123_commit_log = """Version 3.3.1.0
-
-git-svn-id: googlecode@123 0039-1c4b
-"""
- c_hash_345_commit_log = """Version 3.4.0.
-
-Cr-Commit-Position: refs/heads/candidates@{#345}
-"""
- c_hash_456_commit_log = """Version 4.2.71.
-
-Cr-Commit-Position: refs/heads/4.2.71@{#1}
-"""
- c_deps = "Line\n \"v8_revision\": \"%s\",\n line\n"
-
- json_output = self.MakeEmptyTempFile()
- csv_output = self.MakeEmptyTempFile()
- self.WriteFakeVersionFile()
-
- TEST_CONFIG["CHROMIUM"] = self.MakeEmptyTempDirectory()
- chrome_dir = TEST_CONFIG["CHROMIUM"]
- chrome_v8_dir = os.path.join(chrome_dir, "v8")
- os.makedirs(chrome_v8_dir)
-
- def ResetVersion(major, minor, build, patch=0):
- return lambda: self.WriteFakeVersionFile(major=major,
- minor=minor,
- build=build,
- patch=patch)
-
- self.Expect([
- Cmd("git status -s -uno", ""),
- Cmd("git checkout -f origin/master", ""),
- Cmd("git fetch", ""),
- Cmd("git branch", " branch1\n* branch2\n"),
- Cmd("git new-branch %s" % TEST_CONFIG["BRANCHNAME"], ""),
- Cmd("git fetch origin +refs/tags/*:refs/tags/*", ""),
- Cmd("git rev-list --max-age=395200 --tags",
- "bad_tag\nhash_234\nhash_123\nhash_345\nhash_456\n"),
- Cmd("git describe --tags bad_tag", "3.23.42-1-deadbeef"),
- Cmd("git describe --tags hash_234", "3.3.1.1"),
- Cmd("git describe --tags hash_123", "3.21.2"),
- Cmd("git describe --tags hash_345", "3.22.3"),
- Cmd("git describe --tags hash_456", "4.2.71"),
- Cmd("git diff --name-only hash_234 hash_234^", VERSION_FILE),
- Cmd("git checkout -f hash_234 -- %s" % VERSION_FILE, "",
- cb=ResetVersion(3, 3, 1, 1)),
- Cmd("git branch -r --contains hash_234", " branch-heads/3.3\n"),
- Cmd("git log -1 --format=%B hash_234", c_hash_234_commit_log),
- Cmd("git log -1 --format=%s hash_234", ""),
- Cmd("git log -1 --format=%B hash_234", c_hash_234_commit_log),
- Cmd("git log -1 --format=%ci hash_234", "18:15"),
- Cmd("git checkout -f HEAD -- %s" % VERSION_FILE, "",
- cb=ResetVersion(3, 22, 5)),
- Cmd("git diff --name-only hash_123 hash_123^", VERSION_FILE),
- Cmd("git checkout -f hash_123 -- %s" % VERSION_FILE, "",
- cb=ResetVersion(3, 21, 2)),
- Cmd("git branch -r --contains hash_123", " branch-heads/3.21\n"),
- Cmd("git log -1 --format=%B hash_123", c_hash_123_commit_log),
- Cmd("git log -1 --format=%s hash_123", ""),
- Cmd("git log -1 --format=%B hash_123", c_hash_123_commit_log),
- Cmd("git log -1 --format=%ci hash_123", "03:15"),
- Cmd("git checkout -f HEAD -- %s" % VERSION_FILE, "",
- cb=ResetVersion(3, 22, 5)),
- Cmd("git diff --name-only hash_345 hash_345^", VERSION_FILE),
- Cmd("git checkout -f hash_345 -- %s" % VERSION_FILE, "",
- cb=ResetVersion(3, 22, 3)),
- Cmd("git branch -r --contains hash_345", " origin/candidates\n"),
- Cmd("git log -1 --format=%B hash_345", c_hash_345_commit_log),
- Cmd("git log -1 --format=%s hash_345", ""),
- Cmd("git log -1 --format=%B hash_345", c_hash_345_commit_log),
- Cmd("git log -1 --format=%ci hash_345", ""),
- Cmd("git checkout -f HEAD -- %s" % VERSION_FILE, "",
- cb=ResetVersion(3, 22, 5)),
- Cmd("git diff --name-only hash_456 hash_456^", VERSION_FILE),
- Cmd("git checkout -f hash_456 -- %s" % VERSION_FILE, "",
- cb=ResetVersion(4, 2, 71)),
- Cmd("git branch -r --contains hash_456", " origin/4.2.71\n"),
- Cmd("git log -1 --format=%B hash_456", c_hash_456_commit_log),
- Cmd("git log -1 --format=%H 4.2.71", "hash_456"),
- Cmd("git log -1 --format=%s hash_456", "Version 4.2.71"),
- Cmd("git log -1 --format=%H hash_456^", "master_456"),
- Cmd("git log -1 --format=%B master_456",
- "Cr-Commit-Position: refs/heads/master@{#456}"),
- Cmd("git log -1 --format=%B hash_456", c_hash_456_commit_log),
- Cmd("git log -1 --format=%ci hash_456", "02:15"),
- Cmd("git checkout -f HEAD -- %s" % VERSION_FILE, "",
- cb=ResetVersion(3, 22, 5)),
- Cmd("git fetch origin +refs/heads/*:refs/remotes/origin/* "
- "+refs/branch-heads/*:refs/remotes/branch-heads/*", "",
- cwd=chrome_dir),
- Cmd("git fetch origin", "", cwd=chrome_v8_dir),
- Cmd("git log --format=%H --grep=\"V8\" origin/master -- DEPS",
- "c_hash1\nc_hash2\nc_hash3\n",
- cwd=chrome_dir),
- Cmd("git show c_hash1:DEPS", c_deps % "hash_456", cwd=chrome_dir),
- Cmd("git log -1 --format=%B c_hash1", c_hash1_commit_log,
- cwd=chrome_dir),
- Cmd("git show c_hash2:DEPS", c_deps % "hash_345", cwd=chrome_dir),
- Cmd("git log -1 --format=%B c_hash2", c_hash2_commit_log,
- cwd=chrome_dir),
- Cmd("git show c_hash3:DEPS", c_deps % "deadbeef", cwd=chrome_dir),
- Cmd("git log -1 --format=%B c_hash3", c_hash3_commit_log,
- cwd=chrome_dir),
- Cmd("git branch -r", " weird/123\n branch-heads/7\n", cwd=chrome_dir),
- Cmd("git show refs/branch-heads/7:DEPS", c_deps % "hash_345",
- cwd=chrome_dir),
- URL("http://omahaproxy.appspot.com/all.json", """[{
- "os": "win",
- "versions": [{
- "version": "2.2.2.2",
- "v8_version": "22.2.2.2",
- "current_reldate": "04/09/15",
- "os": "win",
- "channel": "canary",
- "previous_version": "1.1.1.0"
- }]
- }]"""),
- URL("http://omahaproxy.appspot.com/v8.json?version=1.1.1.0", """{
- "chromium_version": "1.1.1.0",
- "v8_version": "11.1.1.0"
- }"""),
- Cmd("git rev-list -1 11.1.1", "v8_previous_version_hash"),
- Cmd("git rev-list -1 22.2.2.2", "v8_version_hash"),
- Cmd("git checkout -f origin/master", ""),
- Cmd("git branch -D %s" % TEST_CONFIG["BRANCHNAME"], "")
- ])
-
- args = ["-c", TEST_CONFIG["CHROMIUM"],
- "--json", json_output,
- "--csv", csv_output,
- "--max-releases", "1"]
- Releases(TEST_CONFIG, self).Run(args)
-
- # Check expected output.
- csv = ("4.2.71,4.2.71,1,5678,\r\n"
- "3.22.3,candidates,345,4567:5677,\r\n"
- "3.21.2,3.21,123,,\r\n"
- "3.3.1.1,3.3,234,,abc12\r\n")
- self.assertEquals(csv, FileToText(csv_output))
-
- expected_json = {"chrome_releases":{
- "canaries": [
- {
- "chrome_version": "2.2.2.2",
- "os": "win",
- "release_date": "04/09/15",
- "v8_version": "22.2.2.2",
- "v8_version_hash": "v8_version_hash",
- "v8_previous_version": "11.1.1.0",
- "v8_previous_version_hash": "v8_previous_version_hash"
- }]},
- "releases":[
- {
- "revision": "1",
- "revision_git": "hash_456",
- "master_position": "456",
- "master_hash": "master_456",
- "patches_merged": "",
- "version": "4.2.71",
- "chromium_revision": "5678",
- "branch": "4.2.71",
- "review_link": "",
- "date": "02:15",
- "chromium_branch": "",
- # FIXME(machenbach): Fix revisions link for git.
- "revision_link": "https://code.google.com/p/v8/source/detail?r=1",
- },
- {
- "revision": "345",
- "revision_git": "hash_345",
- "master_position": "",
- "master_hash": "",
- "patches_merged": "",
- "version": "3.22.3",
- "chromium_revision": "4567:5677",
- "branch": "candidates",
- "review_link": "",
- "date": "",
- "chromium_branch": "7",
- "revision_link": "https://code.google.com/p/v8/source/detail?r=345",
- },
- {
- "revision": "123",
- "revision_git": "hash_123",
- "patches_merged": "",
- "master_position": "",
- "master_hash": "",
- "version": "3.21.2",
- "chromium_revision": "",
- "branch": "3.21",
- "review_link": "",
- "date": "03:15",
- "chromium_branch": "",
- "revision_link": "https://code.google.com/p/v8/source/detail?r=123",
- },
- {
- "revision": "234",
- "revision_git": "hash_234",
- "patches_merged": "abc12",
- "master_position": "",
- "master_hash": "",
- "version": "3.3.1.1",
- "chromium_revision": "",
- "branch": "3.3",
- "review_link": "fake.com",
- "date": "18:15",
- "chromium_branch": "",
- "revision_link": "https://code.google.com/p/v8/source/detail?r=234",
- },],
- }
- self.assertEquals(expected_json, json.loads(FileToText(json_output)))
-
if __name__ == '__main__':
unittest.main()
diff --git a/src/v8/tools/node/testdata/v8/third_party/jinja2/jinja2 b/src/v8/tools/release/testdata/v8/third_party/googletest/src/googletest/include/gtest/baz/gtest_new
similarity index 100%
copy from src/v8/tools/node/testdata/v8/third_party/jinja2/jinja2
copy to src/v8/tools/release/testdata/v8/third_party/googletest/src/googletest/include/gtest/baz/gtest_new
diff --git a/src/v8/tools/node/testdata/v8/third_party/jinja2/jinja2 b/src/v8/tools/release/testdata/v8/third_party/googletest/src/googletest/include/gtest/gtest_new
similarity index 100%
rename from src/v8/tools/node/testdata/v8/third_party/jinja2/jinja2
rename to src/v8/tools/release/testdata/v8/third_party/googletest/src/googletest/include/gtest/gtest_new
diff --git a/src/v8/tools/release/testdata/v8/third_party/googletest/src/googletest/include/gtest/gtest_prod.h b/src/v8/tools/release/testdata/v8/third_party/googletest/src/googletest/include/gtest/gtest_prod.h
new file mode 100644
index 0000000..847c8bc
--- /dev/null
+++ b/src/v8/tools/release/testdata/v8/third_party/googletest/src/googletest/include/gtest/gtest_prod.h
@@ -0,0 +1 @@
+gtest_prod
diff --git a/src/v8/tools/node/testdata/v8/third_party/jinja2/jinja2 b/src/v8/tools/release/testdata/v8/third_party/googletest/src/googletest/include/gtest/new/gtest_new
similarity index 100%
copy from src/v8/tools/node/testdata/v8/third_party/jinja2/jinja2
copy to src/v8/tools/release/testdata/v8/third_party/googletest/src/googletest/include/gtest/new/gtest_new
diff --git a/src/v8/tools/run-clang-tidy.py b/src/v8/tools/run-clang-tidy.py
new file mode 100755
index 0000000..aee1b40
--- /dev/null
+++ b/src/v8/tools/run-clang-tidy.py
@@ -0,0 +1,424 @@
+#!/usr/bin/env python
+#
+# 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.
+
+# for py2/py3 compatibility
+from __future__ import print_function
+
+import json
+import multiprocessing
+import optparse
+import os
+import re
+import subprocess
+import sys
+
+CLANG_TIDY_WARNING = re.compile(r'(\/.*?)\ .*\[(.*)\]$')
+CLANG_TIDY_CMDLINE_OUT = re.compile(r'^clang-tidy.*\ .*|^\./\.\*')
+FILE_REGEXS = ['../src/*', '../test/*']
+HEADER_REGEX = ['\.\.\/src\/.*|\.\.\/include\/.*|\.\.\/test\/.*']
+
+THREADS = multiprocessing.cpu_count()
+
+
+class ClangTidyWarning(object):
+ """
+ Wraps up a clang-tidy warning to present aggregated information.
+ """
+
+ def __init__(self, warning_type):
+ self.warning_type = warning_type
+ self.occurrences = set()
+
+ def add_occurrence(self, file_path):
+ self.occurrences.add(file_path.lstrip())
+
+ def __hash__(self):
+ return hash(self.warning_type)
+
+ def to_string(self, file_loc):
+ s = '[%s] #%d\n' % (self.warning_type, len(self.occurrences))
+ if file_loc:
+ s += ' ' + '\n '.join(self.occurrences)
+ s += '\n'
+ return s
+
+ def __str__(self):
+ return self.to_string(False)
+
+ def __lt__(self, other):
+ return len(self.occurrences) < len(other.occurrences)
+
+
+def GenerateCompileCommands(build_folder):
+ """
+ Generate a compilation database.
+
+ Currently clang-tidy-4 does not understand all flags that are passed
+ by the build system, therefore, we remove them from the generated file.
+ """
+ ninja_ps = subprocess.Popen(
+ ['ninja', '-t', 'compdb', 'cxx', 'cc'],
+ stdout=subprocess.PIPE,
+ cwd=build_folder)
+
+ out_filepath = os.path.join(build_folder, 'compile_commands.json')
+ with open(out_filepath, 'w') as cc_file:
+ while True:
+ line = ninja_ps.stdout.readline()
+
+ if line == '':
+ break
+
+ line = line.replace('-fcomplete-member-pointers', '')
+ line = line.replace('-Wno-enum-compare-switch', '')
+ line = line.replace('-Wno-ignored-pragma-optimize', '')
+ line = line.replace('-Wno-null-pointer-arithmetic', '')
+ line = line.replace('-Wno-unused-lambda-capture', '')
+ line = line.replace('-Wno-defaulted-function-deleted', '')
+ cc_file.write(line)
+
+
+def skip_line(line):
+ """
+ Check if a clang-tidy output line should be skipped.
+ """
+ return bool(CLANG_TIDY_CMDLINE_OUT.search(line))
+
+
+def ClangTidyRunFull(build_folder, skip_output_filter, checks, auto_fix):
+ """
+ Run clang-tidy on the full codebase and print warnings.
+ """
+ extra_args = []
+ if auto_fix:
+ extra_args.append('-fix')
+
+ if checks is not None:
+ extra_args.append('-checks')
+ extra_args.append('-*, ' + checks)
+
+ with open(os.devnull, 'w') as DEVNULL:
+ ct_process = subprocess.Popen(
+ ['run-clang-tidy', '-j' + str(THREADS), '-p', '.']
+ + ['-header-filter'] + HEADER_REGEX + extra_args
+ + FILE_REGEXS,
+ cwd=build_folder,
+ stdout=subprocess.PIPE,
+ stderr=DEVNULL)
+ removing_check_header = False
+ empty_lines = 0
+
+ while True:
+ line = ct_process.stdout.readline()
+ if line == '':
+ break
+
+ # Skip all lines after Enbale checks and before two newlines,
+ # i.e., skip clang-tidy check list.
+ if line.startswith('Enabled checks'):
+ removing_check_header = True
+ if removing_check_header and not skip_output_filter:
+ if line == '\n':
+ empty_lines += 1
+ if empty_lines == 2:
+ removing_check_header = False
+ continue
+
+ # Different lines get removed to ease output reading.
+ if not skip_output_filter and skip_line(line):
+ continue
+
+ # Print line, because no filter was matched.
+ if line != '\n':
+ sys.stdout.write(line)
+
+
+def ClangTidyRunAggregate(build_folder, print_files):
+ """
+ Run clang-tidy on the full codebase and aggregate warnings into categories.
+ """
+ with open(os.devnull, 'w') as DEVNULL:
+ ct_process = subprocess.Popen(
+ ['run-clang-tidy', '-j' + str(THREADS), '-p', '.'] +
+ ['-header-filter'] + HEADER_REGEX +
+ FILE_REGEXS,
+ cwd=build_folder,
+ stdout=subprocess.PIPE,
+ stderr=DEVNULL)
+ warnings = dict()
+ while True:
+ line = ct_process.stdout.readline()
+ if line == '':
+ break
+
+ res = CLANG_TIDY_WARNING.search(line)
+ if res is not None:
+ warnings.setdefault(
+ res.group(2),
+ ClangTidyWarning(res.group(2))).add_occurrence(res.group(1))
+
+ for warning in sorted(warnings.values(), reverse=True):
+ sys.stdout.write(warning.to_string(print_files))
+
+
+def ClangTidyRunDiff(build_folder, diff_branch, auto_fix):
+ """
+ Run clang-tidy on the diff between current and the diff_branch.
+ """
+ if diff_branch is None:
+ diff_branch = subprocess.check_output(['git', 'merge-base',
+ 'HEAD', 'origin/master']).strip()
+
+ git_ps = subprocess.Popen(
+ ['git', 'diff', '-U0', diff_branch], stdout=subprocess.PIPE)
+
+ extra_args = []
+ if auto_fix:
+ extra_args.append('-fix')
+
+ with open(os.devnull, 'w') as DEVNULL:
+ """
+ The script `clang-tidy-diff` does not provide support to add header-
+ filters. To still analyze headers we use the build path option `-path` to
+ inject our header-filter option. This works because the script just adds
+ the passed path string to the commandline of clang-tidy.
+ """
+ modified_build_folder = build_folder
+ modified_build_folder += ' -header-filter='
+ modified_build_folder += '\'' + ''.join(HEADER_REGEX) + '\''
+
+ ct_ps = subprocess.Popen(
+ ['clang-tidy-diff.py', '-path', modified_build_folder, '-p1'] +
+ extra_args,
+ stdin=git_ps.stdout,
+ stdout=subprocess.PIPE,
+ stderr=DEVNULL)
+ git_ps.wait()
+ while True:
+ line = ct_ps.stdout.readline()
+ if line == '':
+ break
+
+ if skip_line(line):
+ continue
+
+ sys.stdout.write(line)
+
+
+def rm_prefix(string, prefix):
+ """
+ Removes prefix from a string until the new string
+ no longer starts with the prefix.
+ """
+ while string.startswith(prefix):
+ string = string[len(prefix):]
+ return string
+
+
+def ClangTidyRunSingleFile(build_folder, filename_to_check, auto_fix,
+ line_ranges=[]):
+ """
+ Run clang-tidy on a single file.
+ """
+ files_with_relative_path = []
+
+ compdb_filepath = os.path.join(build_folder, 'compile_commands.json')
+ with open(compdb_filepath) as raw_json_file:
+ compdb = json.load(raw_json_file)
+
+ for db_entry in compdb:
+ if db_entry['file'].endswith(filename_to_check):
+ files_with_relative_path.append(db_entry['file'])
+
+ with open(os.devnull, 'w') as DEVNULL:
+ for file_with_relative_path in files_with_relative_path:
+ line_filter = None
+ if len(line_ranges) != 0:
+ line_filter = '['
+ line_filter += '{ \"lines\":[' + ', '.join(line_ranges)
+ line_filter += '], \"name\":\"'
+ line_filter += rm_prefix(file_with_relative_path,
+ '../') + '\"}'
+ line_filter += ']'
+
+ extra_args = ['-line-filter=' + line_filter] if line_filter else []
+
+ if auto_fix:
+ extra_args.append('-fix')
+
+ subprocess.call(['clang-tidy', '-p', '.'] +
+ extra_args +
+ [file_with_relative_path],
+ cwd=build_folder,
+ stderr=DEVNULL)
+
+
+def CheckClangTidy():
+ """
+ Checks if a clang-tidy binary exists.
+ """
+ with open(os.devnull, 'w') as DEVNULL:
+ return subprocess.call(['which', 'clang-tidy'], stdout=DEVNULL) == 0
+
+
+def CheckCompDB(build_folder):
+ """
+ Checks if a compilation database exists in the build_folder.
+ """
+ return os.path.isfile(os.path.join(build_folder, 'compile_commands.json'))
+
+
+def DetectBuildFolder():
+ """
+ Tries to auto detect the last used build folder in out/
+ """
+ outdirs_folder = 'out/'
+ last_used = None
+ last_timestamp = -1
+ for outdir in [outdirs_folder + folder_name
+ for folder_name in os.listdir(outdirs_folder)
+ if os.path.isdir(outdirs_folder + folder_name)]:
+ outdir_modified_timestamp = os.path.getmtime(outdir)
+ if outdir_modified_timestamp > last_timestamp:
+ last_timestamp = outdir_modified_timestamp
+ last_used = outdir
+
+ return last_used
+
+
+def GetOptions():
+ """
+ Generate the option parser for this script.
+ """
+ result = optparse.OptionParser()
+ result.add_option(
+ '-b',
+ '--build-folder',
+ help='Set V8 build folder',
+ dest='build_folder',
+ default=None)
+ result.add_option(
+ '-j',
+ help='Set the amount of threads that should be used',
+ dest='threads',
+ default=None)
+ result.add_option(
+ '--gen-compdb',
+ help='Generate a compilation database for clang-tidy',
+ default=False,
+ action='store_true')
+ result.add_option(
+ '--no-output-filter',
+ help='Done use any output filterning',
+ default=False,
+ action='store_true')
+ result.add_option(
+ '--fix',
+ help='Fix auto fixable issues',
+ default=False,
+ dest='auto_fix',
+ action='store_true'
+ )
+
+ # Full clang-tidy.
+ full_run_g = optparse.OptionGroup(result, 'Clang-tidy full', '')
+ full_run_g.add_option(
+ '--full',
+ help='Run clang-tidy on the whole codebase',
+ default=False,
+ action='store_true')
+ full_run_g.add_option('--checks',
+ help='Clang-tidy checks to use.',
+ default=None)
+ result.add_option_group(full_run_g)
+
+ # Aggregate clang-tidy.
+ agg_run_g = optparse.OptionGroup(result, 'Clang-tidy aggregate', '')
+ agg_run_g.add_option('--aggregate', help='Run clang-tidy on the whole '\
+ 'codebase and aggregate the warnings',
+ default=False, action='store_true')
+ agg_run_g.add_option('--show-loc', help='Show file locations when running '\
+ 'in aggregate mode', default=False,
+ action='store_true')
+ result.add_option_group(agg_run_g)
+
+ # Diff clang-tidy.
+ diff_run_g = optparse.OptionGroup(result, 'Clang-tidy diff', '')
+ diff_run_g.add_option('--branch', help='Run clang-tidy on the diff '\
+ 'between HEAD and the merge-base between HEAD '\
+ 'and DIFF_BRANCH (origin/master by default).',
+ default=None, dest='diff_branch')
+ result.add_option_group(diff_run_g)
+
+ # Single clang-tidy.
+ single_run_g = optparse.OptionGroup(result, 'Clang-tidy single', '')
+ single_run_g.add_option(
+ '--single', help='', default=False, action='store_true')
+ single_run_g.add_option(
+ '--file', help='File name to check', default=None, dest='file_name')
+ single_run_g.add_option('--lines', help='Limit checks to a line range. '\
+ 'For example: --lines="[2,4], [5,6]"',
+ default=[], dest='line_ranges')
+
+ result.add_option_group(single_run_g)
+ return result
+
+
+def main():
+ parser = GetOptions()
+ (options, _) = parser.parse_args()
+
+ if options.threads is not None:
+ global THREADS
+ THREADS = options.threads
+
+ if options.build_folder is None:
+ options.build_folder = DetectBuildFolder()
+
+ if not CheckClangTidy():
+ print('Could not find clang-tidy')
+ elif options.build_folder is None or not os.path.isdir(options.build_folder):
+ print('Please provide a build folder with -b')
+ elif options.gen_compdb:
+ GenerateCompileCommands(options.build_folder)
+ elif not CheckCompDB(options.build_folder):
+ print('Could not find compilation database, ' \
+ 'please generate it with --gen-compdb')
+ else:
+ print('Using build folder:', options.build_folder)
+ if options.full:
+ print('Running clang-tidy - full')
+ ClangTidyRunFull(options.build_folder,
+ options.no_output_filter,
+ options.checks,
+ options.auto_fix)
+ elif options.aggregate:
+ print('Running clang-tidy - aggregating warnings')
+ if options.auto_fix:
+ print('Auto fix not working in aggregate mode, running without.')
+ ClangTidyRunAggregate(options.build_folder, options.show_loc)
+ elif options.single:
+ print('Running clang-tidy - single on ' + options.file_name)
+ if options.file_name is not None:
+ line_ranges = []
+ for match in re.findall(r'(\[.*?\])', options.line_ranges):
+ if match is not []:
+ line_ranges.append(match)
+ ClangTidyRunSingleFile(options.build_folder,
+ options.file_name,
+ options.auto_fix,
+ line_ranges)
+ else:
+ print('Filename provided, please specify a filename with --file')
+ else:
+ print('Running clang-tidy')
+ ClangTidyRunDiff(options.build_folder,
+ options.diff_branch,
+ options.auto_fix)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/src/v8/tools/run-num-fuzzer.py b/src/v8/tools/run-num-fuzzer.py
new file mode 100755
index 0000000..9b5a065
--- /dev/null
+++ b/src/v8/tools/run-num-fuzzer.py
@@ -0,0 +1,14 @@
+#!/usr/bin/env python
+#
+# 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.
+
+
+import sys
+
+from testrunner import num_fuzzer
+
+
+if __name__ == "__main__":
+ sys.exit(num_fuzzer.NumFuzzer().execute())
diff --git a/src/v8/tools/run-perf.sh b/src/v8/tools/run-perf.sh
index 8375093..0317a9a 100755
--- a/src/v8/tools/run-perf.sh
+++ b/src/v8/tools/run-perf.sh
@@ -55,4 +55,4 @@
-e $EVENT_TYPE \
-c $SAMPLE_EVERY_N_CYCLES \
--call-graph $CALL_GRAPH_METHOD \
- -i "$COMMAND" --perf_basic_prof "$@"
+ -i "$COMMAND" --perf-basic-prof "$@"
diff --git a/src/v8/tools/run-wasm-api-tests.py b/src/v8/tools/run-wasm-api-tests.py
new file mode 100755
index 0000000..79f53cb
--- /dev/null
+++ b/src/v8/tools/run-wasm-api-tests.py
@@ -0,0 +1,167 @@
+#!/usr/bin/env python
+#
+# Copyright 2019 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.
+"""\
+Helper script for compiling and running the Wasm C/C++ API examples.
+
+Usage: tools/run-wasm-api-tests.py outdir tempdir [filters...]
+
+"outdir" is the build output directory containing libwee8, e.g. out/x64.release
+"tempdir" is a temporary dir where this script may put its artifacts. It is
+the caller's responsibility to clean it up afterwards.
+
+By default, this script builds and runs all examples, both the respective
+C and C++ versions, both with GCC ("gcc" and "g++" binaries found in $PATH)
+and V8's bundled Clang in third_party/llvm-build/. You can use any number
+of "filters" arguments to run only a subset:
+ - "c": run C versions of examples
+ - "cc": run C++ versions of examples
+ - "gcc": compile with GCC
+ - "clang": compile with Clang
+ - "hello" etc.: run "hello" example
+"""
+
+from __future__ import print_function
+
+import os
+import shutil
+import subprocess
+import sys
+
+CFLAGS = "-DDEBUG -Wall -Werror -O0 -ggdb -fsanitize=address"
+
+CHECKOUT_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+WASM_PATH = os.path.join(CHECKOUT_PATH, "third_party", "wasm-api")
+CLANG_PATH = os.path.join(CHECKOUT_PATH, "third_party", "llvm-build",
+ "Release+Asserts", "bin")
+
+EXAMPLES = ["hello", "callback", "trap", "reflect", "global", "table",
+ "memory", "finalize", "serialize", "threads"]
+
+CLANG = {
+ "name": "Clang",
+ "c": os.path.join(CLANG_PATH, "clang"),
+ "cc": os.path.join(CLANG_PATH, "clang++"),
+ "ldflags": "-fsanitize-memory-track-origins -fsanitize-memory-use-after-dtor",
+}
+GCC = {
+ "name": "GCC",
+ "c": "gcc",
+ "cc": "g++",
+ "ldflags": "",
+}
+
+C = {
+ "name": "C",
+ "suffix": "c",
+ "cflags": "",
+}
+CXX = {
+ "name": "C++",
+ "suffix": "cc",
+ "cflags": "-std=c++11",
+}
+
+MIN_ARGS = 3 # Script, outdir, tempdir
+
+def _Call(cmd_list, silent=False):
+ cmd = " ".join(cmd_list)
+ if not silent: print("# %s" % cmd)
+ return subprocess.call(cmd, shell=True)
+
+class Runner(object):
+ def __init__(self, name, outdir, tempdir):
+ self.name = name
+ self.outdir = outdir
+ self.tempdir = tempdir
+ self.src_file_basename = os.path.join(WASM_PATH, "example", name)
+ self.dst_file_basename = os.path.join(tempdir, name)
+ self.lib_file = os.path.join(outdir, "obj", "libwee8.a")
+ if not os.path.exists(self.lib_file):
+ print("libwee8 library not found, make sure to pass the outdir as "
+ "first argument; see --help")
+ sys.exit(1)
+ src_wasm_file = self.src_file_basename + ".wasm"
+ dst_wasm_file = self.dst_file_basename + ".wasm"
+ shutil.copyfile(src_wasm_file, dst_wasm_file)
+
+ def _Error(self, step, lang, compiler, code):
+ print("Error: %s failed. To repro: tools/run-wasm-api-tests.py "
+ "%s %s %s %s %s" %
+ (step, self.outdir, self.tempdir, self.name, lang,
+ compiler["name"].lower()))
+ return code
+
+ def CompileAndRun(self, compiler, language):
+ print("==== %s %s/%s ====" %
+ (self.name, language["name"], compiler["name"]))
+ lang = language["suffix"]
+ src_file = self.src_file_basename + "." + lang
+ exe_file = self.dst_file_basename + "-" + lang
+ obj_file = exe_file + ".o"
+ # Compile.
+ c = _Call([compiler[lang], "-c", language["cflags"], CFLAGS,
+ "-I", WASM_PATH, "-o", obj_file, src_file])
+ if c: return self._Error("compilation", lang, compiler, c)
+ # Link.
+ c = _Call([compiler["cc"], CFLAGS, compiler["ldflags"], obj_file,
+ "-o", exe_file, self.lib_file, "-ldl -pthread"])
+ if c: return self._Error("linking", lang, compiler, c)
+ # Execute.
+ exe_file = "./%s-%s" % (self.name, lang)
+ c = _Call(["cd", self.tempdir, ";", exe_file])
+ if c: return self._Error("execution", lang, compiler, c)
+ return 0
+
+def Main(args):
+ if (len(args) < MIN_ARGS or args[1] in ("-h", "--help", "help")):
+ print(__doc__)
+ return 1
+
+ outdir = sys.argv[1]
+ tempdir = sys.argv[2]
+ result = 0
+ examples = EXAMPLES
+ compilers = (GCC, CLANG)
+ languages = (C, CXX)
+ if len(args) > MIN_ARGS:
+ custom_compilers = []
+ custom_languages = []
+ custom_examples = []
+ for i in range(MIN_ARGS, len(args)):
+ arg = args[i]
+ if arg == "c" and C not in custom_languages:
+ custom_languages.append(C)
+ elif arg in ("cc", "cpp", "cxx", "c++") and CXX not in custom_languages:
+ custom_languages.append(CXX)
+ elif arg in ("gcc", "g++") and GCC not in custom_compilers:
+ custom_compilers.append(GCC)
+ elif arg in ("clang", "clang++") and CLANG not in custom_compilers:
+ custom_compilers.append(CLANG)
+ elif arg in EXAMPLES and arg not in custom_examples:
+ custom_examples.append(arg)
+ else:
+ print("Didn't understand '%s'" % arg)
+ return 1
+ if custom_compilers:
+ compilers = custom_compilers
+ if custom_languages:
+ languages = custom_languages
+ if custom_examples:
+ examples = custom_examples
+ for example in examples:
+ runner = Runner(example, outdir, tempdir)
+ for compiler in compilers:
+ for language in languages:
+ c = runner.CompileAndRun(compiler, language)
+ if c: result = c
+ if result:
+ print("\nFinished with errors.")
+ else:
+ print("\nFinished successfully.")
+ return result
+
+if __name__ == "__main__":
+ sys.exit(Main(sys.argv))
diff --git a/src/v8/tools/run_perf.py b/src/v8/tools/run_perf.py
old mode 100755
new mode 100644
index 3823eb5..a98dcae
--- a/src/v8/tools/run_perf.py
+++ b/src/v8/tools/run_perf.py
@@ -1,4 +1,3 @@
-#!/usr/bin/env python
# Copyright 2014 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.
@@ -11,6 +10,7 @@
The suite json format is expected to be:
{
"path": <relative path chunks to perf resources and main file>,
+ "owners": [<list of email addresses of benchmark owners (required)>],
"name": <optional suite name, file name is default>,
"archs": [<architecture name for which this suite is run>, ...],
"binary": <name of binary to run, default "d8">,
@@ -18,6 +18,10 @@
"test_flags": [<flag to the test file>, ...],
"run_count": <how often will this suite run (optional)>,
"run_count_XXX": <how often will this suite run for arch XXX (optional)>,
+ "timeout": <how long test is allowed to run>,
+ "timeout_XXX": <how long test is allowed run run for arch XXX>,
+ "retry_count": <how many times to retry failures (in addition to first try)",
+ "retry_count_XXX": <how many times to retry failures for arch XXX>
"resources": [<js file to be moved to android device>, ...]
"main": <main js perf runner file>,
"results_regexp": <optional regexp>,
@@ -55,6 +59,7 @@
Full example (suite with one runner):
{
"path": ["."],
+ "owners": ["username@chromium.org"],
"flags": ["--expose-gc"],
"test_flags": ["5"],
"archs": ["ia32", "x64"],
@@ -74,6 +79,7 @@
Full example (suite with several runners):
{
"path": ["."],
+ "owners": ["username@chromium.org", "otherowner@google.com"],
"flags": ["--expose-gc"],
"archs": ["ia32", "x64"],
"run_count": 5,
@@ -96,50 +102,48 @@
The test flags are passed to the js test file after '--'.
"""
+# for py2/py3 compatibility
+from __future__ import print_function
+from functools import reduce
+
from collections import OrderedDict
+import copy
import json
import logging
import math
-import optparse
+import argparse
import os
import re
import subprocess
import sys
+import time
+import traceback
+import numpy
+
+from testrunner.local import android
from testrunner.local import command
from testrunner.local import utils
+from testrunner.objects.output import Output, NULL_OUTPUT
-ARCH_GUESS = utils.DefaultArch()
-SUPPORTED_ARCHS = ["arm",
- "ia32",
- "mips",
- "mipsel",
- "x64",
- "arm64"]
+try:
+ basestring # Python 2
+except NameError: # Python 3
+ basestring = str
-GENERIC_RESULTS_RE = re.compile(r"^RESULT ([^:]+): ([^=]+)= ([^ ]+) ([^ ]*)$")
-RESULT_STDDEV_RE = re.compile(r"^\{([^\}]+)\}$")
-RESULT_LIST_RE = re.compile(r"^\[([^\]]+)\]$")
+SUPPORTED_ARCHS = ['arm',
+ 'ia32',
+ 'mips',
+ 'mipsel',
+ 'x64',
+ 'arm64']
+
+GENERIC_RESULTS_RE = re.compile(r'^RESULT ([^:]+): ([^=]+)= ([^ ]+) ([^ ]*)$')
+RESULT_STDDEV_RE = re.compile(r'^\{([^\}]+)\}$')
+RESULT_LIST_RE = re.compile(r'^\[([^\]]+)\]$')
TOOLS_BASE = os.path.abspath(os.path.dirname(__file__))
-
-
-def LoadAndroidBuildTools(path): # pragma: no cover
- assert os.path.exists(path)
- sys.path.insert(0, path)
-
- import devil_chromium
- from devil.android import device_errors # pylint: disable=import-error
- from devil.android import device_utils # pylint: disable=import-error
- from devil.android.sdk import adb_wrapper # pylint: disable=import-error
- from devil.android.perf import cache_control # pylint: disable=import-error
- from devil.android.perf import perf_control # pylint: disable=import-error
- global adb_wrapper
- global cache_control
- global device_errors
- global device_utils
- global perf_control
-
- devil_chromium.Initialize()
+INFRA_FAILURE_RETCODE = 87
+MIN_RUNS_FOR_CONFIDENCE = 10
def GeometricMean(values):
@@ -148,102 +152,131 @@
The mean is calculated using log to avoid overflow.
"""
values = map(float, values)
- return str(math.exp(sum(map(math.log, values)) / len(values)))
+ return math.exp(sum(map(math.log, values)) / len(values))
-class Results(object):
- """Place holder for result traces."""
- def __init__(self, traces=None, errors=None):
- self.traces = traces or []
- self.errors = errors or []
+class ResultTracker(object):
+ """Class that tracks trace/runnable results and produces script output.
+
+ The output is structured like this:
+ {
+ "traces": [
+ {
+ "graphs": ["path", "to", "trace", "config"],
+ "units": <string describing units, e.g. "ms" or "KB">,
+ "results": [<list of values measured over several runs>],
+ "stddev": <stddev of the value if measure by script or ''>
+ },
+ ...
+ ],
+ "runnables": [
+ {
+ "graphs": ["path", "to", "runnable", "config"],
+ "durations": [<list of durations of each runnable run in seconds>],
+ "timeout": <timeout configured for runnable in seconds>,
+ },
+ ...
+ ],
+ "errors": [<list of strings describing errors>],
+ }
+ """
+ def __init__(self):
+ self.traces = {}
+ self.errors = []
+ self.runnables = {}
+
+ def AddTraceResult(self, trace, result, stddev):
+ if trace.name not in self.traces:
+ self.traces[trace.name] = {
+ 'graphs': trace.graphs,
+ 'units': trace.units,
+ 'results': [result],
+ 'stddev': stddev or '',
+ }
+ else:
+ existing_entry = self.traces[trace.name]
+ assert trace.graphs == existing_entry['graphs']
+ assert trace.units == existing_entry['units']
+ if stddev:
+ existing_entry['stddev'] = stddev
+ existing_entry['results'].append(result)
+
+ def TraceHasStdDev(self, trace):
+ return trace.name in self.traces and self.traces[trace.name]['stddev'] != ''
+
+ def AddError(self, error):
+ self.errors.append(error)
+
+ def AddRunnableDuration(self, runnable, duration):
+ """Records a duration of a specific run of the runnable."""
+ if runnable.name not in self.runnables:
+ self.runnables[runnable.name] = {
+ 'graphs': runnable.graphs,
+ 'durations': [duration],
+ 'timeout': runnable.timeout,
+ }
+ else:
+ existing_entry = self.runnables[runnable.name]
+ assert runnable.timeout == existing_entry['timeout']
+ assert runnable.graphs == existing_entry['graphs']
+ existing_entry['durations'].append(duration)
def ToDict(self):
- return {"traces": self.traces, "errors": self.errors}
+ return {
+ 'traces': self.traces.values(),
+ 'errors': self.errors,
+ 'runnables': self.runnables.values(),
+ }
def WriteToFile(self, file_name):
- with open(file_name, "w") as f:
+ with open(file_name, 'w') as f:
f.write(json.dumps(self.ToDict()))
- def __add__(self, other):
- self.traces += other.traces
- self.errors += other.errors
- return self
+ def HasEnoughRuns(self, graph_config, confidence_level):
+ """Checks if the mean of the results for a given trace config is within
+ 0.1% of the true value with the specified confidence level.
+
+ This assumes Gaussian distribution of the noise and based on
+ https://en.wikipedia.org/wiki/68%E2%80%9395%E2%80%9399.7_rule.
+
+ Args:
+ graph_config: An instance of GraphConfig.
+ confidence_level: Number of standard deviations from the mean that all
+ values must lie within. Typical values are 1, 2 and 3 and correspond
+ to 68%, 95% and 99.7% probability that the measured value is within
+ 0.1% of the true value.
+
+ Returns:
+ True if specified confidence level have been achieved.
+ """
+ if not isinstance(graph_config, TraceConfig):
+ return all(self.HasEnoughRuns(child, confidence_level)
+ for child in graph_config.children)
+
+ trace = self.traces.get(graph_config.name, {})
+ results = trace.get('results', [])
+ logging.debug('HasEnoughRuns for %s', graph_config.name)
+
+ if len(results) < MIN_RUNS_FOR_CONFIDENCE:
+ logging.debug(' Ran %d times, need at least %d',
+ len(results), MIN_RUNS_FOR_CONFIDENCE)
+ return False
+
+ logging.debug(' Results: %d entries', len(results))
+ mean = numpy.mean(results)
+ mean_stderr = numpy.std(results) / numpy.sqrt(len(results))
+ logging.debug(' Mean: %.2f, mean_stderr: %.2f', mean, mean_stderr)
+ logging.info('>>> Confidence level is %.2f', mean / (1000.0 * mean_stderr))
+ return confidence_level * mean_stderr < mean / 1000.0
def __str__(self): # pragma: no cover
- return str(self.ToDict())
+ return json.dumps(self.ToDict(), indent=2, separators=(',', ': '))
-class Measurement(object):
- """Represents a series of results of one trace.
-
- The results are from repetitive runs of the same executable. They are
- gathered by repeated calls to ConsumeOutput.
- """
- def __init__(self, graphs, units, results_regexp, stddev_regexp):
- self.name = '/'.join(graphs)
- self.graphs = graphs
- self.units = units
- self.results_regexp = results_regexp
- self.stddev_regexp = stddev_regexp
- self.results = []
- self.errors = []
- self.stddev = ""
- self.process_size = False
-
- def ConsumeOutput(self, stdout):
- try:
- result = re.search(self.results_regexp, stdout, re.M).group(1)
- self.results.append(str(float(result)))
- except ValueError:
- self.errors.append("Regexp \"%s\" returned a non-numeric for test %s."
- % (self.results_regexp, self.name))
- except:
- self.errors.append("Regexp \"%s\" didn't match for test %s."
- % (self.results_regexp, self.name))
-
- try:
- if self.stddev_regexp and self.stddev:
- self.errors.append("Test %s should only run once since a stddev "
- "is provided by the test." % self.name)
- if self.stddev_regexp:
- self.stddev = re.search(self.stddev_regexp, stdout, re.M).group(1)
- except:
- self.errors.append("Regexp \"%s\" didn't match for test %s."
- % (self.stddev_regexp, self.name))
-
- def GetResults(self):
- return Results([{
- "graphs": self.graphs,
- "units": self.units,
- "results": self.results,
- "stddev": self.stddev,
- }], self.errors)
-
-
-class NullMeasurement(object):
- """Null object to avoid having extra logic for configurations that don't
- require secondary run, e.g. CI bots.
- """
- def ConsumeOutput(self, stdout):
- pass
-
- def GetResults(self):
- return Results()
-
-
-def Unzip(iterable):
- left = []
- right = []
- for l, r in iterable:
- left.append(l)
- right.append(r)
- return lambda: iter(left), lambda: iter(right)
-
-
-def RunResultsProcessor(results_processor, stdout, count):
+def RunResultsProcessor(results_processor, output, count):
# Dummy pass through for null-runs.
- if stdout is None:
- return None
+ if output.stdout is None:
+ return output
# We assume the results processor is relative to the suite.
assert os.path.exists(results_processor)
@@ -253,113 +286,10 @@
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
- result, _ = p.communicate(input=stdout)
- print ">>> Processed stdout (#%d):" % count
- print result
- return result
-
-
-def AccumulateResults(
- graph_names, trace_configs, iter_output, perform_measurement, calc_total):
- """Iterates over the output of multiple benchmark reruns and accumulates
- results for a configured list of traces.
-
- Args:
- graph_names: List of names that configure the base path of the traces. E.g.
- ['v8', 'Octane'].
- trace_configs: List of "TraceConfig" instances. Each trace config defines
- how to perform a measurement.
- iter_output: Iterator over the standard output of each test run.
- perform_measurement: Whether to actually run tests and perform measurements.
- This is needed so that we reuse this script for both CI
- and trybot, but want to ignore second run on CI without
- having to spread this logic throughout the script.
- calc_total: Boolean flag to speficy the calculation of a summary trace.
- Returns: A "Results" object.
- """
- measurements = [
- trace.CreateMeasurement(perform_measurement) for trace in trace_configs]
- for stdout in iter_output():
- for measurement in measurements:
- measurement.ConsumeOutput(stdout)
-
- res = reduce(lambda r, m: r + m.GetResults(), measurements, Results())
-
- if not res.traces or not calc_total:
- return res
-
- # Assume all traces have the same structure.
- if len(set(map(lambda t: len(t["results"]), res.traces))) != 1:
- res.errors.append("Not all traces have the same number of results.")
- return res
-
- # Calculate the geometric means for all traces. Above we made sure that
- # there is at least one trace and that the number of results is the same
- # for each trace.
- n_results = len(res.traces[0]["results"])
- total_results = [GeometricMean(t["results"][i] for t in res.traces)
- for i in range(0, n_results)]
- res.traces.append({
- "graphs": graph_names + ["Total"],
- "units": res.traces[0]["units"],
- "results": total_results,
- "stddev": "",
- })
- return res
-
-
-def AccumulateGenericResults(graph_names, suite_units, iter_output):
- """Iterates over the output of multiple benchmark reruns and accumulates
- generic results.
-
- Args:
- graph_names: List of names that configure the base path of the traces. E.g.
- ['v8', 'Octane'].
- suite_units: Measurement default units as defined by the benchmark suite.
- iter_output: Iterator over the standard output of each test run.
- Returns: A "Results" object.
- """
- traces = OrderedDict()
- for stdout in iter_output():
- if stdout is None:
- # The None value is used as a null object to simplify logic.
- continue
- for line in stdout.strip().splitlines():
- match = GENERIC_RESULTS_RE.match(line)
- if match:
- stddev = ""
- graph = match.group(1)
- trace = match.group(2)
- body = match.group(3)
- units = match.group(4)
- match_stddev = RESULT_STDDEV_RE.match(body)
- match_list = RESULT_LIST_RE.match(body)
- errors = []
- if match_stddev:
- result, stddev = map(str.strip, match_stddev.group(1).split(","))
- results = [result]
- elif match_list:
- results = map(str.strip, match_list.group(1).split(","))
- else:
- results = [body.strip()]
-
- try:
- results = map(lambda r: str(float(r)), results)
- except ValueError:
- results = []
- errors = ["Found non-numeric in %s" %
- "/".join(graph_names + [graph, trace])]
-
- trace_result = traces.setdefault(trace, Results([{
- "graphs": graph_names + [graph, trace],
- "units": (units or suite_units).strip(),
- "results": [],
- "stddev": "",
- }], errors))
- trace_result.traces[0]["results"].extend(results)
- trace_result.traces[0]["stddev"] = stddev
-
- return reduce(lambda r, t: r + t, traces.itervalues(), Results())
+ new_output = copy.copy(output)
+ new_output.stdout, _ = p.communicate(input=output.stdout)
+ logging.info('>>> Processed stdout (#%d):\n%s', count, output.stdout)
+ return new_output
class Node(object):
@@ -370,14 +300,19 @@
def AppendChild(self, child):
self._children.append(child)
+ @property
+ def children(self):
+ return self._children
+
class DefaultSentinel(Node):
"""Fake parent node with all default values."""
- def __init__(self, binary = "d8"):
+ def __init__(self, binary = 'd8'):
super(DefaultSentinel, self).__init__()
self.binary = binary
self.run_count = 10
self.timeout = 60
+ self.retry_count = 4
self.path = []
self.graphs = []
self.flags = []
@@ -387,8 +322,9 @@
self.results_processor = None
self.results_regexp = None
self.stddev_regexp = None
- self.units = "score"
+ self.units = 'score'
self.total = False
+ self.owners = []
class GraphConfig(Node):
@@ -400,32 +336,36 @@
super(GraphConfig, self).__init__()
self._suite = suite
- assert isinstance(suite.get("path", []), list)
- assert isinstance(suite["name"], basestring)
- assert isinstance(suite.get("flags", []), list)
- assert isinstance(suite.get("test_flags", []), list)
- assert isinstance(suite.get("resources", []), list)
+ assert isinstance(suite.get('path', []), list)
+ assert isinstance(suite.get('owners', []), list)
+ assert isinstance(suite['name'], basestring)
+ assert isinstance(suite.get('flags', []), list)
+ assert isinstance(suite.get('test_flags', []), list)
+ assert isinstance(suite.get('resources', []), list)
# Accumulated values.
- self.path = parent.path[:] + suite.get("path", [])
- self.graphs = parent.graphs[:] + [suite["name"]]
- self.flags = parent.flags[:] + suite.get("flags", [])
- self.test_flags = parent.test_flags[:] + suite.get("test_flags", [])
+ self.path = parent.path[:] + suite.get('path', [])
+ self.graphs = parent.graphs[:] + [suite['name']]
+ self.flags = parent.flags[:] + suite.get('flags', [])
+ self.test_flags = parent.test_flags[:] + suite.get('test_flags', [])
+ self.owners = parent.owners[:] + suite.get('owners', [])
# Values independent of parent node.
- self.resources = suite.get("resources", [])
+ self.resources = suite.get('resources', [])
# Descrete values (with parent defaults).
- self.binary = suite.get("binary", parent.binary)
- self.run_count = suite.get("run_count", parent.run_count)
- self.run_count = suite.get("run_count_%s" % arch, self.run_count)
- self.timeout = suite.get("timeout", parent.timeout)
- self.timeout = suite.get("timeout_%s" % arch, self.timeout)
- self.units = suite.get("units", parent.units)
- self.total = suite.get("total", parent.total)
+ self.binary = suite.get('binary', parent.binary)
+ self.run_count = suite.get('run_count', parent.run_count)
+ self.run_count = suite.get('run_count_%s' % arch, self.run_count)
+ self.retry_count = suite.get('retry_count', parent.retry_count)
+ self.retry_count = suite.get('retry_count_%s' % arch, self.retry_count)
+ self.timeout = suite.get('timeout', parent.timeout)
+ self.timeout = suite.get('timeout_%s' % arch, self.timeout)
+ self.units = suite.get('units', parent.units)
+ self.total = suite.get('total', parent.total)
self.results_processor = suite.get(
- "results_processor", parent.results_processor)
- self.process_size = suite.get("process_size", parent.process_size)
+ 'results_processor', parent.results_processor)
+ self.process_size = suite.get('process_size', parent.process_size)
# A regular expression for results. If the parent graph provides a
# regexp and the current suite has none, a string place holder for the
@@ -433,17 +373,21 @@
# TODO(machenbach): Currently that makes only sense for the leaf level.
# Multiple place holders for multiple levels are not supported.
if parent.results_regexp:
- regexp_default = parent.results_regexp % re.escape(suite["name"])
+ regexp_default = parent.results_regexp % re.escape(suite['name'])
else:
regexp_default = None
- self.results_regexp = suite.get("results_regexp", regexp_default)
+ self.results_regexp = suite.get('results_regexp', regexp_default)
# A similar regular expression for the standard deviation (optional).
if parent.stddev_regexp:
- stddev_default = parent.stddev_regexp % re.escape(suite["name"])
+ stddev_default = parent.stddev_regexp % re.escape(suite['name'])
else:
stddev_default = None
- self.stddev_regexp = suite.get("stddev_regexp", stddev_default)
+ self.stddev_regexp = suite.get('stddev_regexp', stddev_default)
+
+ @property
+ def name(self):
+ return '/'.join(self.graphs)
class TraceConfig(GraphConfig):
@@ -451,34 +395,60 @@
def __init__(self, suite, parent, arch):
super(TraceConfig, self).__init__(suite, parent, arch)
assert self.results_regexp
+ assert self.owners
- def CreateMeasurement(self, perform_measurement):
- if not perform_measurement:
- return NullMeasurement()
+ def ConsumeOutput(self, output, result_tracker):
+ """Extracts trace results from the output.
- return Measurement(
- self.graphs,
- self.units,
- self.results_regexp,
- self.stddev_regexp,
- )
+ Args:
+ output: Output object from the test run.
+ result_tracker: Result tracker to be updated.
+
+ Returns:
+ The raw extracted result value or None if an error occurred.
+ """
+ result = None
+ stddev = None
+
+ try:
+ result = float(
+ re.search(self.results_regexp, output.stdout, re.M).group(1))
+ except ValueError:
+ result_tracker.AddError(
+ 'Regexp "%s" returned a non-numeric for test %s.' %
+ (self.results_regexp, self.name))
+ except:
+ result_tracker.AddError(
+ 'Regexp "%s" did not match for test %s.' %
+ (self.results_regexp, self.name))
+
+ try:
+ if self.stddev_regexp:
+ if result_tracker.TraceHasStdDev(self):
+ result_tracker.AddError(
+ 'Test %s should only run once since a stddev is provided by the '
+ 'test.' % self.name)
+ stddev = re.search(self.stddev_regexp, output.stdout, re.M).group(1)
+ except:
+ result_tracker.AddError(
+ 'Regexp "%s" did not match for test %s.' %
+ (self.stddev_regexp, self.name))
+
+ if result:
+ result_tracker.AddTraceResult(self, result, stddev)
+ return result
class RunnableConfig(GraphConfig):
"""Represents a runnable suite definition (i.e. has a main file).
"""
+ def __init__(self, suite, parent, arch):
+ super(RunnableConfig, self).__init__(suite, parent, arch)
+ self.arch = arch
+
@property
def main(self):
- return self._suite.get("main", "")
-
- def PostProcess(self, stdouts_iter):
- if self.results_processor:
- def it():
- for i, stdout in enumerate(stdouts_iter()):
- yield RunResultsProcessor(self.results_processor, stdout, i + 1)
- return it
- else:
- return stdouts_iter
+ return self._suite.get('main', '')
def ChangeCWD(self, suite_path):
"""Changes the cwd to to path defined in the current graph.
@@ -490,17 +460,17 @@
os.chdir(os.path.join(suite_dir, bench_dir))
def GetCommandFlags(self, extra_flags=None):
- suffix = ["--"] + self.test_flags if self.test_flags else []
+ suffix = ['--'] + self.test_flags if self.test_flags else []
return self.flags + (extra_flags or []) + [self.main] + suffix
def GetCommand(self, cmd_prefix, shell_dir, extra_flags=None):
# TODO(machenbach): This requires +.exe if run on windows.
extra_flags = extra_flags or []
if self.binary != 'd8' and '--prof' in extra_flags:
- print "Profiler supported only on a benchmark run with d8"
+ logging.info('Profiler supported only on a benchmark run with d8')
if self.process_size:
- cmd_prefix = ["/usr/bin/time", "--format=MaxMemory: %MKB"] + cmd_prefix
+ cmd_prefix = ['/usr/bin/time', '--format=MaxMemory: %MKB'] + cmd_prefix
if self.binary.endswith('.py'):
# Copy cmd_prefix instead of update (+=).
cmd_prefix = cmd_prefix + [sys.executable]
@@ -511,25 +481,36 @@
args=self.GetCommandFlags(extra_flags=extra_flags),
timeout=self.timeout or 60)
- def Run(self, runner, trybot):
- """Iterates over several runs and handles the output for all traces."""
- stdout, stdout_secondary = Unzip(runner())
- return (
- AccumulateResults(
- self.graphs,
- self._children,
- iter_output=self.PostProcess(stdout),
- perform_measurement=True,
- calc_total=self.total,
- ),
- AccumulateResults(
- self.graphs,
- self._children,
- iter_output=self.PostProcess(stdout_secondary),
- perform_measurement=trybot, # only run second time on trybots
- calc_total=self.total,
- ),
- )
+ def ProcessOutput(self, output, result_tracker, count):
+ """Processes test run output and updates result tracker.
+
+ Args:
+ output: Output object from the test run.
+ result_tracker: ResultTracker object to be updated.
+ count: Index of the test run (used for better logging).
+ """
+ if self.results_processor:
+ output = RunResultsProcessor(self.results_processor, output, count)
+
+ results_for_total = []
+ for trace in self.children:
+ result = trace.ConsumeOutput(output, result_tracker)
+ if result:
+ results_for_total.append(result)
+
+ if self.total:
+ # Produce total metric only when all traces have produced results.
+ if len(self.children) != len(results_for_total):
+ result_tracker.AddError(
+ 'Not all traces have produced results. Can not compute total for '
+ '%s.' % self.name)
+ return
+
+ # Calculate total as a the geometric mean for results from all traces.
+ total_trace = TraceConfig(
+ {'name': 'Total', 'units': self.children[0].units}, self, self.arch)
+ result_tracker.AddTraceResult(
+ total_trace, GeometricMean(results_for_total), '')
class RunnableTraceConfig(TraceConfig, RunnableConfig):
@@ -537,30 +518,9 @@
def __init__(self, suite, parent, arch):
super(RunnableTraceConfig, self).__init__(suite, parent, arch)
- def Run(self, runner, trybot):
- """Iterates over several runs and handles the output."""
- measurement = self.CreateMeasurement(perform_measurement=True)
- measurement_secondary = self.CreateMeasurement(perform_measurement=trybot)
- for stdout, stdout_secondary in runner():
- measurement.ConsumeOutput(stdout)
- measurement_secondary.ConsumeOutput(stdout_secondary)
- return (
- measurement.GetResults(),
- measurement_secondary.GetResults(),
- )
-
-
-class RunnableGenericConfig(RunnableConfig):
- """Represents a runnable suite definition with generic traces."""
- def __init__(self, suite, parent, arch):
- super(RunnableGenericConfig, self).__init__(suite, parent, arch)
-
- def Run(self, runner, trybot):
- stdout, stdout_secondary = Unzip(runner())
- return (
- AccumulateGenericResults(self.graphs, self.units, stdout),
- AccumulateGenericResults(self.graphs, self.units, stdout_secondary),
- )
+ def ProcessOutput(self, output, result_tracker, count):
+ result_tracker.AddRunnableDuration(self, output.duration)
+ self.ConsumeOutput(output, result_tracker)
def MakeGraphConfig(suite, arch, parent):
@@ -568,23 +528,19 @@
if isinstance(parent, RunnableConfig):
# Below a runnable can only be traces.
return TraceConfig(suite, parent, arch)
- elif suite.get("main") is not None:
+ elif suite.get('main') is not None:
# A main file makes this graph runnable. Empty strings are accepted.
- if suite.get("tests"):
+ if suite.get('tests'):
# This graph has subgraphs (traces).
return RunnableConfig(suite, parent, arch)
else:
# This graph has no subgraphs, it's a leaf.
return RunnableTraceConfig(suite, parent, arch)
- elif suite.get("generic"):
- # This is a generic suite definition. It is either a runnable executable
- # or has a main js file.
- return RunnableGenericConfig(suite, parent, arch)
- elif suite.get("tests"):
+ elif suite.get('tests'):
# This is neither a leaf nor a runnable.
return GraphConfig(suite, parent, arch)
else: # pragma: no cover
- raise Exception("Invalid suite configuration.")
+ raise Exception('Invalid suite configuration.')
def BuildGraphConfigs(suite, arch, parent):
@@ -593,11 +549,11 @@
"""
# TODO(machenbach): Implement notion of cpu type?
- if arch not in suite.get("archs", SUPPORTED_ARCHS):
+ if arch not in suite.get('archs', SUPPORTED_ARCHS):
return None
graph = MakeGraphConfig(suite, arch, parent)
- for subsuite in suite.get("tests", []):
+ for subsuite in suite.get('tests', []):
BuildGraphConfigs(subsuite, arch, graph)
parent.AppendChild(graph)
return graph
@@ -615,61 +571,92 @@
for result in FlattenRunnables(child, node_cb):
yield result
else: # pragma: no cover
- raise Exception("Invalid suite configuration.")
+ raise Exception('Invalid suite configuration.')
class Platform(object):
- def __init__(self, options):
- self.shell_dir = options.shell_dir
- self.shell_dir_secondary = options.shell_dir_secondary
- self.extra_flags = options.extra_flags.split()
+ def __init__(self, args):
+ self.shell_dir = args.shell_dir
+ self.shell_dir_secondary = args.shell_dir_secondary
+ self.extra_flags = args.extra_flags.split()
+ self.args = args
@staticmethod
- def GetPlatform(options):
- if options.android_build_tools:
- return AndroidPlatform(options)
+ def ReadBuildConfig(args):
+ config_path = os.path.join(args.shell_dir, 'v8_build_config.json')
+ if not os.path.isfile(config_path):
+ return {}
+ with open(config_path) as f:
+ return json.load(f)
+
+ @staticmethod
+ def GetPlatform(args):
+ if Platform.ReadBuildConfig(args).get('is_android', False):
+ return AndroidPlatform(args)
else:
- return DesktopPlatform(options)
+ return DesktopPlatform(args)
def _Run(self, runnable, count, secondary=False):
raise NotImplementedError() # pragma: no cover
- def Run(self, runnable, count):
+ def _LoggedRun(self, runnable, count, secondary=False):
+ suffix = ' - secondary' if secondary else ''
+ title = '>>> %%s (#%d)%s:' % ((count + 1), suffix)
+ try:
+ output = self._Run(runnable, count, secondary)
+ except OSError:
+ logging.exception(title % 'OSError')
+ raise
+ if output.stdout:
+ logging.info(title % 'Stdout' + '\n%s', output.stdout)
+ if output.stderr: # pragma: no cover
+ # Print stderr for debugging.
+ logging.info(title % 'Stderr' + '\n%s', output.stderr)
+ logging.warning('>>> Test timed out after %ss.', runnable.timeout)
+ if output.exit_code != 0:
+ logging.warning('>>> Test crashed with exit code %d.', output.exit_code)
+ return output
+
+ def Run(self, runnable, count, secondary):
"""Execute the benchmark's main file.
- If options.shell_dir_secondary is specified, the benchmark is run twice,
- e.g. with and without patch.
Args:
runnable: A Runnable benchmark instance.
count: The number of this (repeated) run.
- Returns: A tuple with the two benchmark outputs. The latter will be None if
- options.shell_dir_secondary was not specified.
+ secondary: True if secondary run should be executed.
+
+ Returns:
+ A tuple with the two benchmark outputs. The latter will be NULL_OUTPUT if
+ secondary is False.
"""
- stdout = self._Run(runnable, count, secondary=False)
- if self.shell_dir_secondary:
- return stdout, self._Run(runnable, count, secondary=True)
+ output = self._LoggedRun(runnable, count, secondary=False)
+ if secondary:
+ return output, self._LoggedRun(runnable, count, secondary=True)
else:
- return stdout, None
+ return output, NULL_OUTPUT
class DesktopPlatform(Platform):
- def __init__(self, options):
- super(DesktopPlatform, self).__init__(options)
+ def __init__(self, args):
+ super(DesktopPlatform, self).__init__(args)
self.command_prefix = []
- if options.prioritize or options.affinitize != None:
- self.command_prefix = ["schedtool"]
- if options.prioritize:
- self.command_prefix += ["-n", "-20"]
- if options.affinitize != None:
+ # Setup command class to OS specific version.
+ command.setup(utils.GuessOS(), args.device)
+
+ if args.prioritize or args.affinitize != None:
+ self.command_prefix = ['schedtool']
+ if args.prioritize:
+ self.command_prefix += ['-n', '-20']
+ if args.affinitize != None:
# schedtool expects a bit pattern when setting affinity, where each
# bit set to '1' corresponds to a core where the process may run on.
# First bit corresponds to CPU 0. Since the 'affinitize' parameter is
# a core number, we need to map to said bit pattern.
- cpu = int(options.affinitize)
+ cpu = int(args.affinitize)
core = 1 << cpu
- self.command_prefix += ["-a", ("0x%x" % core)]
- self.command_prefix += ["-e"]
+ self.command_prefix += ['-a', ('0x%x' % core)]
+ self.command_prefix += ['-e']
def PreExecution(self):
pass
@@ -682,123 +669,37 @@
node.ChangeCWD(path)
def _Run(self, runnable, count, secondary=False):
- suffix = ' - secondary' if secondary else ''
shell_dir = self.shell_dir_secondary if secondary else self.shell_dir
- title = ">>> %%s (#%d)%s:" % ((count + 1), suffix)
cmd = runnable.GetCommand(self.command_prefix, shell_dir, self.extra_flags)
- try:
- output = cmd.execute()
- except OSError as e: # pragma: no cover
- print title % "OSError"
- print e
- return ""
+ output = cmd.execute()
- print title % "Stdout"
- print output.stdout
- if output.stderr: # pragma: no cover
- # Print stderr for debugging.
- print title % "Stderr"
- print output.stderr
- if output.timed_out:
- print ">>> Test timed out after %ss." % runnable.timeout
- if '--prof' in self.extra_flags:
- os_prefix = {"linux": "linux", "macos": "mac"}.get(utils.GuessOS())
+ if output.IsSuccess() and '--prof' in self.extra_flags:
+ os_prefix = {'linux': 'linux', 'macos': 'mac'}.get(utils.GuessOS())
if os_prefix:
- tick_tools = os.path.join(TOOLS_BASE, "%s-tick-processor" % os_prefix)
- subprocess.check_call(tick_tools + " --only-summary", shell=True)
+ tick_tools = os.path.join(TOOLS_BASE, '%s-tick-processor' % os_prefix)
+ subprocess.check_call(tick_tools + ' --only-summary', shell=True)
else: # pragma: no cover
- print "Profiler option currently supported on Linux and Mac OS."
+ logging.warning(
+ 'Profiler option currently supported on Linux and Mac OS.')
- # time outputs to stderr
+ # /usr/bin/time outputs to stderr
if runnable.process_size:
- return output.stdout + output.stderr
- return output.stdout
+ output.stdout += output.stderr
+ return output
class AndroidPlatform(Platform): # pragma: no cover
- DEVICE_DIR = "/data/local/tmp/v8/"
- def __init__(self, options):
- super(AndroidPlatform, self).__init__(options)
- LoadAndroidBuildTools(options.android_build_tools)
-
- if not options.device:
- # Detect attached device if not specified.
- devices = adb_wrapper.AdbWrapper.Devices()
- assert devices and len(devices) == 1, (
- "None or multiple devices detected. Please specify the device on "
- "the command-line with --device")
- options.device = str(devices[0])
- self.adb_wrapper = adb_wrapper.AdbWrapper(options.device)
- self.device = device_utils.DeviceUtils(self.adb_wrapper)
+ def __init__(self, args):
+ super(AndroidPlatform, self).__init__(args)
+ self.driver = android.android_driver(args.device)
def PreExecution(self):
- perf = perf_control.PerfControl(self.device)
- perf.SetHighPerfMode()
-
- # Remember what we have already pushed to the device.
- self.pushed = set()
+ self.driver.set_high_perf_mode()
def PostExecution(self):
- perf = perf_control.PerfControl(self.device)
- perf.SetDefaultPerfMode()
- self.device.RemovePath(
- AndroidPlatform.DEVICE_DIR, force=True, recursive=True)
-
- def _PushFile(self, host_dir, file_name, target_rel=".",
- skip_if_missing=False):
- file_on_host = os.path.join(host_dir, file_name)
- file_on_device_tmp = os.path.join(
- AndroidPlatform.DEVICE_DIR, "_tmp_", file_name)
- file_on_device = os.path.join(
- AndroidPlatform.DEVICE_DIR, target_rel, file_name)
- folder_on_device = os.path.dirname(file_on_device)
-
- # Only attempt to push files that exist.
- if not os.path.exists(file_on_host):
- if not skip_if_missing:
- logging.critical('Missing file on host: %s' % file_on_host)
- return
-
- # Only push files not yet pushed in one execution.
- if file_on_host in self.pushed:
- return
- else:
- self.pushed.add(file_on_host)
-
- # Work-around for "text file busy" errors. Push the files to a temporary
- # location and then copy them with a shell command.
- output = self.adb_wrapper.Push(file_on_host, file_on_device_tmp)
- # Success looks like this: "3035 KB/s (12512056 bytes in 4.025s)".
- # Errors look like this: "failed to copy ... ".
- if output and not re.search('^[0-9]', output.splitlines()[-1]):
- logging.critical('PUSH FAILED: ' + output)
- self.adb_wrapper.Shell("mkdir -p %s" % folder_on_device)
- self.adb_wrapper.Shell("cp %s %s" % (file_on_device_tmp, file_on_device))
-
- def _PushExecutable(self, shell_dir, target_dir, binary):
- self._PushFile(shell_dir, binary, target_dir)
-
- # Push external startup data. Backwards compatible for revisions where
- # these files didn't exist.
- self._PushFile(
- shell_dir,
- "natives_blob.bin",
- target_dir,
- skip_if_missing=True,
- )
- self._PushFile(
- shell_dir,
- "snapshot_blob.bin",
- target_dir,
- skip_if_missing=True,
- )
- self._PushFile(
- shell_dir,
- "icudtl.dat",
- target_dir,
- skip_if_missing=True,
- )
+ self.driver.set_default_perf_mode()
+ self.driver.tear_down()
def PreTests(self, node, path):
if isinstance(node, RunnableConfig):
@@ -808,52 +709,59 @@
bench_rel = os.path.normpath(os.path.join(*node.path))
bench_abs = os.path.join(suite_dir, bench_rel)
else:
- bench_rel = "."
+ bench_rel = '.'
bench_abs = suite_dir
- self._PushExecutable(self.shell_dir, "bin", node.binary)
+ self.driver.push_executable(self.shell_dir, 'bin', node.binary)
if self.shell_dir_secondary:
- self._PushExecutable(
- self.shell_dir_secondary, "bin_secondary", node.binary)
+ self.driver.push_executable(
+ self.shell_dir_secondary, 'bin_secondary', node.binary)
if isinstance(node, RunnableConfig):
- self._PushFile(bench_abs, node.main, bench_rel)
+ self.driver.push_file(bench_abs, node.main, bench_rel)
for resource in node.resources:
- self._PushFile(bench_abs, resource, bench_rel)
+ self.driver.push_file(bench_abs, resource, bench_rel)
def _Run(self, runnable, count, secondary=False):
- suffix = ' - secondary' if secondary else ''
- target_dir = "bin_secondary" if secondary else "bin"
- title = ">>> %%s (#%d)%s:" % ((count + 1), suffix)
- cache = cache_control.CacheControl(self.device)
- cache.DropRamCaches()
- binary_on_device = os.path.join(
- AndroidPlatform.DEVICE_DIR, target_dir, runnable.binary)
- cmd = [binary_on_device] + runnable.GetCommandFlags(self.extra_flags)
+ target_dir = 'bin_secondary' if secondary else 'bin'
+ self.driver.drop_ram_caches()
# Relative path to benchmark directory.
if runnable.path:
bench_rel = os.path.normpath(os.path.join(*runnable.path))
else:
- bench_rel = "."
+ bench_rel = '.'
+ logcat_file = None
+ if self.args.dump_logcats_to:
+ runnable_name = '-'.join(runnable.graphs)
+ logcat_file = os.path.join(
+ self.args.dump_logcats_to, 'logcat-%s-#%d%s.log' % (
+ runnable_name, count + 1, '-secondary' if secondary else ''))
+ logging.debug('Dumping logcat into %s', logcat_file)
+
+ output = Output()
+ start = time.time()
try:
- output = self.device.RunShellCommand(
- cmd,
- cwd=os.path.join(AndroidPlatform.DEVICE_DIR, bench_rel),
- check_return=True,
+ output.stdout = self.driver.run(
+ target_dir=target_dir,
+ binary=runnable.binary,
+ args=runnable.GetCommandFlags(self.extra_flags),
+ rel_path=bench_rel,
timeout=runnable.timeout,
- retries=0,
+ logcat_file=logcat_file,
)
- stdout = "\n".join(output)
- print title % "Stdout"
- print stdout
- except device_errors.CommandTimeoutError:
- print ">>> Test timed out after %ss." % runnable.timeout
- stdout = ""
+ except android.CommandFailedException as e:
+ output.stdout = e.output
+ output.exit_code = e.status
+ except android.TimeoutException as e:
+ output.stdout = e.output
+ output.timed_out = True
if runnable.process_size:
- return stdout + "MaxMemory: Unsupported"
- return stdout
+ output.stdout += 'MaxMemory: Unsupported'
+ output.duration = time.time() - start
+ return output
+
class CustomMachineConfiguration:
def __init__(self, disable_aslr = False, governor = None):
@@ -880,44 +788,44 @@
@staticmethod
def GetASLR():
try:
- with open("/proc/sys/kernel/randomize_va_space", "r") as f:
+ with open('/proc/sys/kernel/randomize_va_space', 'r') as f:
return int(f.readline().strip())
- except Exception as e:
- print "Failed to get current ASLR settings."
- raise e
+ except Exception:
+ logging.exception('Failed to get current ASLR settings.')
+ raise
@staticmethod
def SetASLR(value):
try:
- with open("/proc/sys/kernel/randomize_va_space", "w") as f:
+ with open('/proc/sys/kernel/randomize_va_space', 'w') as f:
f.write(str(value))
- except Exception as e:
- print "Failed to update ASLR to %s." % value
- print "Are we running under sudo?"
- raise e
+ except Exception:
+ logging.exception(
+ 'Failed to update ASLR to %s. Are we running under sudo?', value)
+ raise
new_value = CustomMachineConfiguration.GetASLR()
if value != new_value:
- raise Exception("Present value is %s" % new_value)
+ raise Exception('Present value is %s' % new_value)
@staticmethod
def GetCPUCoresRange():
try:
- with open("/sys/devices/system/cpu/present", "r") as f:
+ with open('/sys/devices/system/cpu/present', 'r') as f:
indexes = f.readline()
- r = map(int, indexes.split("-"))
+ r = map(int, indexes.split('-'))
if len(r) == 1:
return range(r[0], r[0] + 1)
return range(r[0], r[1] + 1)
- except Exception as e:
- print "Failed to retrieve number of CPUs."
- raise e
+ except Exception:
+ logging.exception('Failed to retrieve number of CPUs.')
+ raise
@staticmethod
def GetCPUPathForId(cpu_index):
- ret = "/sys/devices/system/cpu/cpu"
+ ret = '/sys/devices/system/cpu/cpu'
ret += str(cpu_index)
- ret += "/cpufreq/scaling_governor"
+ ret += '/cpufreq/scaling_governor'
return ret
@staticmethod
@@ -927,18 +835,18 @@
ret = None
for cpu_index in cpu_indices:
cpu_device = CustomMachineConfiguration.GetCPUPathForId(cpu_index)
- with open(cpu_device, "r") as f:
+ with open(cpu_device, 'r') as f:
# We assume the governors of all CPUs are set to the same value
val = f.readline().strip()
if ret == None:
ret = val
elif ret != val:
- raise Exception("CPU cores have differing governor settings")
+ raise Exception('CPU cores have differing governor settings')
return ret
- except Exception as e:
- print "Failed to get the current CPU governor."
- print "Is the CPU governor disabled? Check BIOS."
- raise e
+ except Exception:
+ logging.exception('Failed to get the current CPU governor. Is the CPU '
+ 'governor disabled? Check BIOS.')
+ raise
@staticmethod
def SetCPUGovernor(value):
@@ -946,205 +854,278 @@
cpu_indices = CustomMachineConfiguration.GetCPUCoresRange()
for cpu_index in cpu_indices:
cpu_device = CustomMachineConfiguration.GetCPUPathForId(cpu_index)
- with open(cpu_device, "w") as f:
+ with open(cpu_device, 'w') as f:
f.write(value)
- except Exception as e:
- print "Failed to change CPU governor to %s." % value
- print "Are we running under sudo?"
- raise e
+ except Exception:
+ logging.exception('Failed to change CPU governor to %s. Are we '
+ 'running under sudo?', value)
+ raise
cur_value = CustomMachineConfiguration.GetCPUGovernor()
if cur_value != value:
- raise Exception("Could not set CPU governor. Present value is %s"
+ raise Exception('Could not set CPU governor. Present value is %s'
% cur_value )
-def Main(args):
- logging.getLogger().setLevel(logging.INFO)
- parser = optparse.OptionParser()
- parser.add_option("--android-build-tools",
- help="Path to chromium's build/android. Specifying this "
- "option will run tests using android platform.")
- parser.add_option("--arch",
- help=("The architecture to run tests for, "
- "'auto' or 'native' for auto-detect"),
- default="x64")
- parser.add_option("--buildbot",
- help="Adapt to path structure used on buildbots",
- default=False, action="store_true")
- parser.add_option("--device",
- help="The device ID to run Android tests on. If not given "
- "it will be autodetected.")
- parser.add_option("--extra-flags",
- help="Additional flags to pass to the test executable",
- default="")
- parser.add_option("--json-test-results",
- help="Path to a file for storing json results.")
- parser.add_option("--json-test-results-secondary",
- "--json-test-results-no-patch", # TODO(sergiyb): Deprecate.
- help="Path to a file for storing json results from run "
- "without patch or for reference build run.")
- parser.add_option("--outdir", help="Base directory with compile output",
- default="out")
- parser.add_option("--outdir-secondary",
- "--outdir-no-patch", # TODO(sergiyb): Deprecate.
- help="Base directory with compile output without patch or "
- "for reference build")
- parser.add_option("--binary-override-path",
- help="JavaScript engine binary. By default, d8 under "
- "architecture-specific build dir. "
- "Not supported in conjunction with outdir-secondary.")
- parser.add_option("--prioritize",
- help="Raise the priority to nice -20 for the benchmarking "
- "process.Requires Linux, schedtool, and sudo privileges.",
- default=False, action="store_true")
- parser.add_option("--affinitize",
- help="Run benchmarking process on the specified core. "
- "For example: "
- "--affinitize=0 will run the benchmark process on core 0. "
- "--affinitize=3 will run the benchmark process on core 3. "
- "Requires Linux, schedtool, and sudo privileges.",
- default=None)
- parser.add_option("--noaslr",
- help="Disable ASLR for the duration of the benchmarked "
- "process. Requires Linux and sudo privileges.",
- default=False, action="store_true")
- parser.add_option("--cpu-governor",
- help="Set cpu governor to specified policy for the "
- "duration of the benchmarked process. Typical options: "
- "'powersave' for more stable results, or 'performance' "
- "for shorter completion time of suite, with potentially "
- "more noise in results.")
- parser.add_option("--filter",
- help="Only run the benchmarks beginning with this string. "
- "For example: "
- "--filter=JSTests/TypedArrays/ will run only TypedArray "
- "benchmarks from the JSTests suite.",
- default="")
- (options, args) = parser.parse_args(args)
+class MaxTotalDurationReachedError(Exception):
+ """Exception used to stop running tests when max total duration is reached."""
+ pass
- if len(args) == 0: # pragma: no cover
- parser.print_help()
- return 1
- if options.arch in ["auto", "native"]: # pragma: no cover
- options.arch = ARCH_GUESS
+def Main(argv):
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--arch',
+ help='The architecture to run tests for. Pass "auto" '
+ 'to auto-detect.', default='x64',
+ choices=SUPPORTED_ARCHS + ['auto'])
+ parser.add_argument('--buildbot',
+ help='Adapt to path structure used on buildbots and adds '
+ 'timestamps/level to all logged status messages',
+ default=False, action='store_true')
+ parser.add_argument('-d', '--device',
+ help='The device ID to run Android tests on. If not '
+ 'given it will be autodetected.')
+ parser.add_argument('--extra-flags',
+ help='Additional flags to pass to the test executable',
+ default='')
+ parser.add_argument('--json-test-results',
+ help='Path to a file for storing json results.')
+ parser.add_argument('--json-test-results-secondary',
+ help='Path to a file for storing json results from run '
+ 'without patch or for reference build run.')
+ parser.add_argument('--outdir', help='Base directory with compile output',
+ default='out')
+ parser.add_argument('--outdir-secondary',
+ help='Base directory with compile output without patch '
+ 'or for reference build')
+ parser.add_argument('--binary-override-path',
+ help='JavaScript engine binary. By default, d8 under '
+ 'architecture-specific build dir. '
+ 'Not supported in conjunction with outdir-secondary.')
+ parser.add_argument('--prioritize',
+ help='Raise the priority to nice -20 for the '
+ 'benchmarking process.Requires Linux, schedtool, and '
+ 'sudo privileges.', default=False, action='store_true')
+ parser.add_argument('--affinitize',
+ help='Run benchmarking process on the specified core. '
+ 'For example: --affinitize=0 will run the benchmark '
+ 'process on core 0. --affinitize=3 will run the '
+ 'benchmark process on core 3. Requires Linux, schedtool, '
+ 'and sudo privileges.', default=None)
+ parser.add_argument('--noaslr',
+ help='Disable ASLR for the duration of the benchmarked '
+ 'process. Requires Linux and sudo privileges.',
+ default=False, action='store_true')
+ parser.add_argument('--cpu-governor',
+ help='Set cpu governor to specified policy for the '
+ 'duration of the benchmarked process. Typical options: '
+ '"powersave" for more stable results, or "performance" '
+ 'for shorter completion time of suite, with potentially '
+ 'more noise in results.')
+ parser.add_argument('--filter',
+ help='Only run the benchmarks beginning with this '
+ 'string. For example: '
+ '--filter=JSTests/TypedArrays/ will run only TypedArray '
+ 'benchmarks from the JSTests suite.',
+ default='')
+ parser.add_argument('--confidence-level', type=float,
+ help='Repeatedly runs each benchmark until specified '
+ 'confidence level is reached. The value is interpreted '
+ 'as the number of standard deviations from the mean that '
+ 'all values must lie within. Typical values are 1, 2 and '
+ '3 and correspond to 68%%, 95%% and 99.7%% probability '
+ 'that the measured value is within 0.1%% of the true '
+ 'value. Larger values result in more retries and thus '
+ 'longer runtime, but also provide more reliable results. '
+ 'Also see --max-total-duration flag.')
+ parser.add_argument('--max-total-duration', type=int, default=7140, # 1h 59m
+ help='Max total duration in seconds allowed for retries '
+ 'across all tests. This is especially useful in '
+ 'combination with the --confidence-level flag.')
+ parser.add_argument('--dump-logcats-to',
+ help='Writes logcat output from each test into specified '
+ 'directory. Only supported for android targets.')
+ parser.add_argument('--run-count', type=int, default=0,
+ help='Override the run count specified by the test '
+ 'suite. The default 0 uses the suite\'s config.')
+ parser.add_argument('-v', '--verbose', default=False, action='store_true',
+ help='Be verbose and print debug output.')
+ parser.add_argument('suite', nargs='+', help='Path to the suite config file.')
- if not options.arch in SUPPORTED_ARCHS: # pragma: no cover
- print "Unknown architecture %s" % options.arch
- return 1
+ try:
+ args = parser.parse_args(argv)
+ except SystemExit:
+ return INFRA_FAILURE_RETCODE
- if options.device and not options.android_build_tools: # pragma: no cover
- print "Specifying a device requires Android build tools."
- return 1
+ logging.basicConfig(
+ level=logging.DEBUG if args.verbose else logging.INFO,
+ format='%(asctime)s %(levelname)-8s %(message)s')
- if (options.json_test_results_secondary and
- not options.outdir_secondary): # pragma: no cover
- print("For writing secondary json test results, a secondary outdir patch "
- "must be specified.")
- return 1
+ if args.arch == 'auto': # pragma: no cover
+ args.arch = utils.DefaultArch()
+ if args.arch not in SUPPORTED_ARCHS:
+ logging.error(
+ 'Auto-detected architecture "%s" is not supported.', args.arch)
+ return INFRA_FAILURE_RETCODE
- workspace = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
+ if (args.json_test_results_secondary and
+ not args.outdir_secondary): # pragma: no cover
+ logging.error('For writing secondary json test results, a secondary outdir '
+ 'patch must be specified.')
+ return INFRA_FAILURE_RETCODE
- if options.buildbot:
- build_config = "Release"
+ workspace = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
+
+ if args.buildbot:
+ build_config = 'Release'
else:
- build_config = "%s.release" % options.arch
+ build_config = '%s.release' % args.arch
- if options.binary_override_path == None:
- options.shell_dir = os.path.join(workspace, options.outdir, build_config)
- default_binary_name = "d8"
+ if args.binary_override_path == None:
+ args.shell_dir = os.path.join(workspace, args.outdir, build_config)
+ default_binary_name = 'd8'
else:
- if not os.path.isfile(options.binary_override_path):
- print "binary-override-path must be a file name"
- return 1
- if options.outdir_secondary:
- print "specify either binary-override-path or outdir-secondary"
- return 1
- options.shell_dir = os.path.abspath(
- os.path.dirname(options.binary_override_path))
- default_binary_name = os.path.basename(options.binary_override_path)
+ if not os.path.isfile(args.binary_override_path):
+ logging.error('binary-override-path must be a file name')
+ return INFRA_FAILURE_RETCODE
+ if args.outdir_secondary:
+ logging.error('specify either binary-override-path or outdir-secondary')
+ return INFRA_FAILURE_RETCODE
+ args.shell_dir = os.path.abspath(
+ os.path.dirname(args.binary_override_path))
+ default_binary_name = os.path.basename(args.binary_override_path)
- if options.outdir_secondary:
- options.shell_dir_secondary = os.path.join(
- workspace, options.outdir_secondary, build_config)
+ if args.outdir_secondary:
+ args.shell_dir_secondary = os.path.join(
+ workspace, args.outdir_secondary, build_config)
else:
- options.shell_dir_secondary = None
+ args.shell_dir_secondary = None
- if options.json_test_results:
- options.json_test_results = os.path.abspath(options.json_test_results)
+ if args.json_test_results:
+ args.json_test_results = os.path.abspath(args.json_test_results)
- if options.json_test_results_secondary:
- options.json_test_results_secondary = os.path.abspath(
- options.json_test_results_secondary)
+ if args.json_test_results_secondary:
+ args.json_test_results_secondary = os.path.abspath(
+ args.json_test_results_secondary)
# Ensure all arguments have absolute path before we start changing current
# directory.
- args = map(os.path.abspath, args)
+ args.suite = map(os.path.abspath, args.suite)
prev_aslr = None
prev_cpu_gov = None
- platform = Platform.GetPlatform(options)
+ platform = Platform.GetPlatform(args)
- results = Results()
- results_secondary = Results()
- with CustomMachineConfiguration(governor = options.cpu_governor,
- disable_aslr = options.noaslr) as conf:
- for path in args:
+ result_tracker = ResultTracker()
+ result_tracker_secondary = ResultTracker()
+ have_failed_tests = False
+ with CustomMachineConfiguration(governor = args.cpu_governor,
+ disable_aslr = args.noaslr) as conf:
+ for path in args.suite:
if not os.path.exists(path): # pragma: no cover
- results.errors.append("Configuration file %s does not exist." % path)
+ result_tracker.AddError('Configuration file %s does not exist.' % path)
continue
with open(path) as f:
suite = json.loads(f.read())
# If no name is given, default to the file name without .json.
- suite.setdefault("name", os.path.splitext(os.path.basename(path))[0])
+ suite.setdefault('name', os.path.splitext(os.path.basename(path))[0])
# Setup things common to one test suite.
platform.PreExecution()
# Build the graph/trace tree structure.
default_parent = DefaultSentinel(default_binary_name)
- root = BuildGraphConfigs(suite, options.arch, default_parent)
+ root = BuildGraphConfigs(suite, args.arch, default_parent)
# Callback to be called on each node on traversal.
def NodeCB(node):
platform.PreTests(node, path)
# Traverse graph/trace tree and iterate over all runnables.
- for runnable in FlattenRunnables(root, NodeCB):
- runnable_name = "/".join(runnable.graphs)
- if not runnable_name.startswith(options.filter):
- continue
- print ">>> Running suite: %s" % runnable_name
+ start = time.time()
+ try:
+ for runnable in FlattenRunnables(root, NodeCB):
+ runnable_name = '/'.join(runnable.graphs)
+ if (not runnable_name.startswith(args.filter) and
+ runnable_name + '/' != args.filter):
+ continue
+ logging.info('>>> Running suite: %s', runnable_name)
- def Runner():
- """Output generator that reruns several times."""
- for i in xrange(0, max(1, runnable.run_count)):
- # TODO(machenbach): Allow timeout per arch like with run_count per
- # arch.
- yield platform.Run(runnable, i)
+ def RunGenerator(runnable):
+ if args.confidence_level:
+ counter = 0
+ while not result_tracker.HasEnoughRuns(
+ runnable, args.confidence_level):
+ yield counter
+ counter += 1
+ else:
+ for i in range(0, max(1, args.run_count or runnable.run_count)):
+ yield i
- # Let runnable iterate over all runs and handle output.
- result, result_secondary = runnable.Run(
- Runner, trybot=options.shell_dir_secondary)
- results += result
- results_secondary += result_secondary
+ for i in RunGenerator(runnable):
+ attempts_left = runnable.retry_count + 1
+ while attempts_left:
+ total_duration = time.time() - start
+ if total_duration > args.max_total_duration:
+ logging.info(
+ '>>> Stopping now since running for too long (%ds > %ds)',
+ total_duration, args.max_total_duration)
+ raise MaxTotalDurationReachedError()
+
+ output, output_secondary = platform.Run(
+ runnable, i, secondary=args.shell_dir_secondary)
+ result_tracker.AddRunnableDuration(runnable, output.duration)
+ result_tracker_secondary.AddRunnableDuration(
+ runnable, output_secondary.duration)
+
+ if output.IsSuccess() and output_secondary.IsSuccess():
+ runnable.ProcessOutput(output, result_tracker, i)
+ if output_secondary is not NULL_OUTPUT:
+ runnable.ProcessOutput(
+ output_secondary, result_tracker_secondary, i)
+ break
+
+ attempts_left -= 1
+ if not attempts_left:
+ logging.info('>>> Suite %s failed after %d retries',
+ runnable_name, runnable.retry_count + 1)
+ have_failed_tests = True
+ else:
+ logging.info('>>> Retrying suite: %s', runnable_name)
+ except MaxTotalDurationReachedError:
+ have_failed_tests = True
+
platform.PostExecution()
- if options.json_test_results:
- results.WriteToFile(options.json_test_results)
+ if args.json_test_results:
+ result_tracker.WriteToFile(args.json_test_results)
else: # pragma: no cover
- print results
+ print('Primary results:', result_tracker)
- if options.json_test_results_secondary:
- results_secondary.WriteToFile(options.json_test_results_secondary)
- else: # pragma: no cover
- print results_secondary
+ if args.shell_dir_secondary:
+ if args.json_test_results_secondary:
+ result_tracker_secondary.WriteToFile(args.json_test_results_secondary)
+ else: # pragma: no cover
+ print('Secondary results:', result_tracker_secondary)
- return min(1, len(results.errors))
+ if (result_tracker.errors or result_tracker_secondary.errors or
+ have_failed_tests):
+ return 1
-if __name__ == "__main__": # pragma: no cover
- sys.exit(Main(sys.argv[1:]))
+ return 0
+
+
+def MainWrapper():
+ try:
+ return Main(sys.argv[1:])
+ except:
+ # Log uncaptured exceptions and report infra failure to the caller.
+ traceback.print_exc()
+ return INFRA_FAILURE_RETCODE
+
+
+if __name__ == '__main__': # pragma: no cover
+ sys.exit(MainWrapper())
diff --git a/src/v8/tools/sanitizers/sancov_formatter.py b/src/v8/tools/sanitizers/sancov_formatter.py
index 2e168fb..b66bfed 100755
--- a/src/v8/tools/sanitizers/sancov_formatter.py
+++ b/src/v8/tools/sanitizers/sancov_formatter.py
@@ -39,6 +39,10 @@
'https://chromium.googlesource.com/external/llvm.org/compiler-rt.git'
"""
+# for py2/py3 compatibility
+from __future__ import print_function
+from functools import reduce
+
import argparse
import json
import logging
@@ -426,26 +430,26 @@
options.build_dir = os.path.abspath(options.build_dir)
if options.action.lower() == 'all':
if not options.json_output:
- print '--json-output is required'
+ print('--json-output is required')
return 1
write_instrumented(options)
elif options.action.lower() == 'merge':
if not options.coverage_dir:
- print '--coverage-dir is required'
+ print('--coverage-dir is required')
return 1
if not options.json_input:
- print '--json-input is required'
+ print('--json-input is required')
return 1
if not options.json_output:
- print '--json-output is required'
+ print('--json-output is required')
return 1
merge(options)
elif options.action.lower() == 'split':
if not options.json_input:
- print '--json-input is required'
+ print('--json-input is required')
return 1
if not options.output_dir:
- print '--output-dir is required'
+ print('--output-dir is required')
return 1
split(options)
return 0
diff --git a/src/v8/tools/sanitizers/sancov_merger.py b/src/v8/tools/sanitizers/sancov_merger.py
index 867f8b4..6fd2eb2 100755
--- a/src/v8/tools/sanitizers/sancov_merger.py
+++ b/src/v8/tools/sanitizers/sancov_merger.py
@@ -106,7 +106,7 @@
n = max(2, int(math.ceil(len(files) / float(cpus))))
# Chop files into buckets.
- buckets = [files[i:i+n] for i in xrange(0, len(files), n)]
+ buckets = [files[i:i+n] for i in range(0, len(files), n)]
# Inputs for multiprocessing. List of tuples containing:
# Keep-files option, base path, executable name, index of bucket,
diff --git a/src/v8/tools/sanitizers/sanitize_pcs.py b/src/v8/tools/sanitizers/sanitize_pcs.py
index 47f2715..a1e3a1d 100755
--- a/src/v8/tools/sanitizers/sanitize_pcs.py
+++ b/src/v8/tools/sanitizers/sanitize_pcs.py
@@ -5,7 +5,10 @@
"""Corrects objdump output. The logic is from sancov.py, see comments there."""
-import sys;
+# for py2/py3 compatibility
+from __future__ import print_function
+
+import sys
for line in sys.stdin:
- print '0x%x' % (int(line.strip(), 16) + 4)
+ print('0x%x' % (int(line.strip(), 16) + 4))
diff --git a/src/v8/tools/shell-utils.h b/src/v8/tools/shell-utils.h
index bfd729d..b41d327 100644
--- a/src/v8/tools/shell-utils.h
+++ b/src/v8/tools/shell-utils.h
@@ -27,7 +27,7 @@
// Utility functions used by parser-shell.
-#include "src/globals.h"
+#include "src/common/globals.h"
#include <stdio.h>
diff --git a/src/v8/tools/snapshot/asm_to_inline_asm.py b/src/v8/tools/snapshot/asm_to_inline_asm.py
new file mode 100644
index 0000000..ad8fdcb
--- /dev/null
+++ b/src/v8/tools/snapshot/asm_to_inline_asm.py
@@ -0,0 +1,31 @@
+#!/usr/bin/env python
+
+# 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.
+
+'''
+Converts a given file in clang assembly syntax to a corresponding
+representation in inline assembly. Specifically, this is used to convert
+embedded.S to embedded.cc for Windows clang builds.
+'''
+
+import argparse
+import sys
+
+def asm_to_inl_asm(in_filename, out_filename):
+ with open(in_filename, 'r') as infile, open(out_filename, 'wb') as outfile:
+ outfile.write('__asm__(\n')
+ for line in infile:
+ # Escape " in .S file before outputing it to inline asm file.
+ line = line.replace('"', '\\"')
+ outfile.write(' "%s\\n"\n' % line.rstrip())
+ outfile.write(');\n')
+ return 0
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('input', help='Name of the input assembly file')
+ parser.add_argument('output', help='Name of the target CC file')
+ args = parser.parse_args()
+ sys.exit(asm_to_inl_asm(args.input, args.output))
diff --git a/src/v8/tools/stats-viewer.py b/src/v8/tools/stats-viewer.py
index e8fc69e..dd9d2c2 100755
--- a/src/v8/tools/stats-viewer.py
+++ b/src/v8/tools/stats-viewer.py
@@ -34,6 +34,9 @@
in a window, re-reading and re-displaying with regular intervals.
"""
+# for py2/py3 compatibility
+from __future__ import print_function
+
import mmap
import optparse
import os
@@ -100,7 +103,7 @@
if not os.path.exists(self.data_name):
maps_name = "/proc/%s/maps" % self.data_name
if not os.path.exists(maps_name):
- print "\"%s\" is neither a counter file nor a PID." % self.data_name
+ print("\"%s\" is neither a counter file nor a PID." % self.data_name)
sys.exit(1)
maps_file = open(maps_name, "r")
try:
@@ -110,7 +113,7 @@
self.data_name = m.group(0)
break
if self.data_name is None:
- print "Can't find counter file in maps for PID %s." % self.data_name
+ print("Can't find counter file in maps for PID %s." % self.data_name)
sys.exit(1)
finally:
maps_file.close()
@@ -123,7 +126,7 @@
return CounterCollection(data_access)
elif data_access.IntAt(0) == CHROME_COUNTERS_FILE_MAGIC_NUMBER:
return ChromeCounterCollection(data_access)
- print "File %s is not stats data." % self.data_name
+ print("File %s is not stats data." % self.data_name)
sys.exit(1)
def CleanUp(self):
@@ -143,7 +146,7 @@
self.RefreshCounters()
changed = True
else:
- for i in xrange(self.data.CountersInUse()):
+ for i in range(self.data.CountersInUse()):
counter = self.data.Counter(i)
name = counter.Name()
if name in self.ui_counters:
@@ -188,7 +191,7 @@
sorted by prefix.
"""
names = {}
- for i in xrange(self.data.CountersInUse()):
+ for i in range(self.data.CountersInUse()):
counter = self.data.Counter(i)
name = counter.Name()
names[name] = counter
@@ -233,7 +236,7 @@
text=counter_name)
name.grid(row=index, column=0, padx=1, pady=1)
count = len(counter_objs)
- for i in xrange(count):
+ for i in range(count):
counter = counter_objs[i]
name = counter.Name()
var = Tkinter.StringVar()
@@ -435,7 +438,7 @@
def CountersInUse(self):
"""Return the number of counters in active use."""
- for i in xrange(self.max_counters):
+ for i in range(self.max_counters):
name_offset = self.counter_names_offset + i * self._COUNTER_NAME_SIZE
if self.data.ByteAt(name_offset) == 0:
return i
diff --git a/src/v8/tools/test262-results-parser.js b/src/v8/tools/test262-results-parser.js
new file mode 100644
index 0000000..379436e
--- /dev/null
+++ b/src/v8/tools/test262-results-parser.js
@@ -0,0 +1,41 @@
+// Copyright 2016 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.
+//
+// Run the test runner and dump a json file. Use this script to pass
+// the json file and return a list of failing tests that can be copied
+// to test262.status.
+//
+// Usage:
+//
+// Run the test runner to generate the results:
+// $ tools/run-tests.py --gn test262 --json-test-results=tools/.test262-results.json
+//
+// Run this script to print the formatted results:
+// $ node tools/test262-results-parser.js .test262-results.json
+//
+// Note: The json results file generated by the test runner should be
+// in the tools/ directly, which is the same dir as this script.
+
+var fs = require('fs'),
+ path = require('path');
+
+function main() {
+ if (process.argv.length === 2) {
+ throw new Error('File name required as first arg.');
+ }
+
+ var fileName = process.argv[2],
+ fullPath = path.join(__dirname, fileName),
+ results = require(fullPath)[0].results,
+ tests = new Set();
+ for (let result of results) {
+ let [_, ...test] = result.name.split('/');
+ tests.add(` '${test.join('/')}': [FAIL],`);
+ }
+
+
+ [...tests].sort().forEach(i => console.log(i));
+}
+
+main();
diff --git a/src/v8/tools/testrunner/base_runner.py b/src/v8/tools/testrunner/base_runner.py
index 8fc09ee..7f9b434 100644
--- a/src/v8/tools/testrunner/base_runner.py
+++ b/src/v8/tools/testrunner/base_runner.py
@@ -2,12 +2,19 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+# for py2/py3 compatibility
+from __future__ import print_function
+from functools import reduce
from collections import OrderedDict
import json
+import multiprocessing
import optparse
import os
+import shlex
import sys
+import traceback
+
# Add testrunner to the path.
@@ -17,10 +24,15 @@
os.path.dirname(os.path.abspath(__file__))))
-from local import testsuite
-from local import utils
-
-from testproc.shard import ShardProc
+from testrunner.local import command
+from testrunner.local import testsuite
+from testrunner.local import utils
+from testrunner.test_config import TestConfig
+from testrunner.testproc import progress
+from testrunner.testproc.rerun import RerunProc
+from testrunner.testproc.shard import ShardProc
+from testrunner.testproc.sigproc import SignalProc
+from testrunner.testproc.timeout import TimeoutProc
BASE_DIR = (
@@ -31,8 +43,6 @@
DEFAULT_OUT_GN = 'out.gn'
-ARCH_GUESS = utils.DefaultArch()
-
# Map of test name synonyms to lists of test suites. Should be ordered by
# expected runtimes (suites with slow test cases first). These groups are
# invoked in separate steps on the bots.
@@ -46,6 +56,7 @@
"inspector",
"webkit",
"mkgrokdump",
+ "wasm-js",
"fuzzer",
"message",
"preparser",
@@ -60,6 +71,7 @@
"wasm-spec-tests",
"inspector",
"mkgrokdump",
+ "wasm-js",
"fuzzer",
"message",
"preparser",
@@ -68,13 +80,12 @@
],
# This needs to stay in sync with test/d8_default.isolate.
"d8_default": [
- # TODO(machenbach): uncomment after infra side lands.
- #"debugger",
+ "debugger",
"mjsunit",
"webkit",
- #"message",
- #"preparser",
- #"intl",
+ "message",
+ "preparser",
+ "intl",
],
# This needs to stay in sync with test/optimize_for_size.isolate.
"optimize_for_size": [
@@ -90,6 +101,16 @@
],
}
+# Double the timeout for these:
+SLOW_ARCHS = ["arm",
+ "mips",
+ "mipsel",
+ "mips64",
+ "mips64el",
+ "s390",
+ "s390x",
+ "arm64"]
+
class ModeConfig(object):
def __init__(self, flags, timeout_scalefactor, status_mode, execution_mode):
@@ -99,8 +120,9 @@
self.execution_mode = execution_mode
-DEBUG_FLAGS = ["--nohard-abort", "--enable-slow-asserts", "--verify-heap"]
-RELEASE_FLAGS = ["--nohard-abort"]
+DEBUG_FLAGS = ["--nohard-abort", "--enable-slow-asserts", "--verify-heap",
+ "--testing-d8-test-runner"]
+RELEASE_FLAGS = ["--nohard-abort", "--testing-d8-test-runner"]
MODES = {
"debug": ModeConfig(
flags=DEBUG_FLAGS,
@@ -138,6 +160,12 @@
),
}
+PROGRESS_INDICATORS = {
+ 'verbose': progress.VerboseProgressIndicator,
+ 'dots': progress.DotsProgressIndicator,
+ 'color': progress.ColorProgressIndicator,
+ 'mono': progress.MonochromeProgressIndicator,
+}
class TestRunnerError(Exception):
pass
@@ -151,17 +179,34 @@
else:
self.arch = build_config['v8_target_cpu']
- self.is_debug = build_config['is_debug']
self.asan = build_config['is_asan']
self.cfi_vptr = build_config['is_cfi']
self.dcheck_always_on = build_config['dcheck_always_on']
self.gcov_coverage = build_config['is_gcov_coverage']
+ self.is_android = build_config['is_android']
+ self.is_clang = build_config['is_clang']
+ self.is_debug = build_config['is_debug']
+ self.is_full_debug = build_config['is_full_debug']
self.msan = build_config['is_msan']
self.no_i18n = not build_config['v8_enable_i18n_support']
self.no_snap = not build_config['v8_use_snapshot']
self.predictable = build_config['v8_enable_verify_predictable']
self.tsan = build_config['is_tsan']
+ # TODO(machenbach): We only have ubsan not ubsan_vptr.
self.ubsan_vptr = build_config['is_ubsan_vptr']
+ self.embedded_builtins = build_config['v8_enable_embedded_builtins']
+ self.verify_csa = build_config['v8_enable_verify_csa']
+ self.lite_mode = build_config['v8_enable_lite_mode']
+ self.pointer_compression = build_config['v8_enable_pointer_compression']
+ # Export only for MIPS target
+ if self.arch in ['mips', 'mipsel', 'mips64', 'mips64el']:
+ self.mips_arch_variant = build_config['mips_arch_variant']
+ self.mips_use_msa = build_config['mips_use_msa']
+
+ @property
+ def use_sanitizer(self):
+ return (self.asan or self.cfi_vptr or self.msan or self.tsan or
+ self.ubsan_vptr)
def __str__(self):
detected_options = []
@@ -186,6 +231,14 @@
detected_options.append('tsan')
if self.ubsan_vptr:
detected_options.append('ubsan_vptr')
+ if self.embedded_builtins:
+ detected_options.append('embedded_builtins')
+ if self.verify_csa:
+ detected_options.append('verify_csa')
+ if self.lite_mode:
+ detected_options.append('lite_mode')
+ if self.pointer_compression:
+ detected_options.append('pointer_compression')
return '\n'.join(detected_options)
@@ -197,6 +250,12 @@
self.build_config = None
self.mode_name = None
self.mode_options = None
+ self.target_os = None
+
+ @property
+ def framework_name(self):
+ """String name of the base-runner subclass, used in test results."""
+ raise NotImplementedError()
def execute(self, sys_args=None):
if sys_args is None: # pragma: no cover
@@ -204,8 +263,13 @@
try:
parser = self._create_parser()
options, args = self._parse_args(parser, sys_args)
+ if options.swarming:
+ # Swarming doesn't print how isolated commands are called. Lets make
+ # this less cryptic by printing it ourselves.
+ print(' '.join(sys.argv))
self._load_build_config(options)
+ command.setup(self.target_os, options.device)
try:
self._process_default_options(options)
@@ -215,14 +279,26 @@
raise
args = self._parse_test_args(args)
- suites = self._get_suites(args, options.verbose)
-
+ tests = self._load_testsuite_generators(args, options)
self._setup_env()
- return self._do_execute(suites, args, options)
+ print(">>> Running tests for %s.%s" % (self.build_config.arch,
+ self.mode_name))
+ exit_code = self._do_execute(tests, args, options)
+ if exit_code == utils.EXIT_CODE_FAILURES and options.json_test_results:
+ print("Force exit code 0 after failures. Json test results file "
+ "generated with failure information.")
+ exit_code = utils.EXIT_CODE_PASS
+ return exit_code
except TestRunnerError:
- return 1
+ traceback.print_exc()
+ return utils.EXIT_CODE_INTERNAL_ERROR
except KeyboardInterrupt:
- return 2
+ return utils.EXIT_CODE_INTERRUPTED
+ except Exception:
+ traceback.print_exc()
+ return utils.EXIT_CODE_INTERNAL_ERROR
+ finally:
+ command.tear_down()
def _create_parser(self):
parser = optparse.OptionParser()
@@ -247,14 +323,70 @@
" and buildbot builds): %s" % MODES.keys())
parser.add_option("--shell-dir", help="DEPRECATED! Executables from build "
"directory will be used")
- parser.add_option("-v", "--verbose", help="Verbose output",
+ parser.add_option("--test-root", help="Root directory of the test suites",
+ default=os.path.join(self.basedir, 'test'))
+ parser.add_option("--total-timeout-sec", default=0, type="int",
+ help="How long should fuzzer run")
+ parser.add_option("--swarming", default=False, action="store_true",
+ help="Indicates running test driver on swarming.")
+
+ parser.add_option("-j", help="The number of parallel tasks to run",
+ default=0, type=int)
+ parser.add_option("-d", "--device",
+ help="The device ID to run Android tests on. If not "
+ "given it will be autodetected.")
+
+ # Shard
+ parser.add_option("--shard-count", default=1, type=int,
+ help="Split tests into this number of shards")
+ parser.add_option("--shard-run", default=1, type=int,
+ help="Run this shard from the split up tests.")
+
+ # Progress
+ parser.add_option("-p", "--progress",
+ choices=PROGRESS_INDICATORS.keys(), default="mono",
+ help="The style of progress indicator (verbose, dots, "
+ "color, mono)")
+ parser.add_option("--json-test-results",
+ help="Path to a file for storing json results.")
+ parser.add_option("--exit-after-n-failures", type="int", default=100,
+ help="Exit after the first N failures instead of "
+ "running all tests. Pass 0 to disable this feature.")
+
+ # Rerun
+ parser.add_option("--rerun-failures-count", default=0, type=int,
+ help="Number of times to rerun each failing test case. "
+ "Very slow tests will be rerun only once.")
+ parser.add_option("--rerun-failures-max", default=100, type=int,
+ help="Maximum number of failing test cases to rerun")
+
+ # Test config
+ parser.add_option("--command-prefix", default="",
+ help="Prepended to each shell command used to run a test")
+ parser.add_option("--extra-flags", action="append", default=[],
+ help="Additional flags to pass to each test command")
+ parser.add_option("--isolates", action="store_true", default=False,
+ help="Whether to test isolates")
+ parser.add_option("--no-harness", "--noharness",
+ default=False, action="store_true",
+ help="Run without test harness of a given suite")
+ parser.add_option("--random-seed", default=0, type=int,
+ help="Default seed for initializing random generator")
+ parser.add_option("--run-skipped", help="Also run skipped tests.",
default=False, action="store_true")
- parser.add_option("--shard-count",
- help="Split tests into this number of shards",
- default=1, type="int")
- parser.add_option("--shard-run",
- help="Run this shard from the split up tests.",
- default=1, type="int")
+ parser.add_option("-t", "--timeout", default=60, type=int,
+ help="Timeout for single test in seconds")
+ parser.add_option("-v", "--verbose", default=False, action="store_true",
+ help="Verbose output")
+
+ # TODO(machenbach): Temporary options for rolling out new test runner
+ # features.
+ parser.add_option("--mastername", default='',
+ help="Mastername property from infrastructure. Not "
+ "setting this option indicates manual usage.")
+ parser.add_option("--buildername", default='',
+ help="Buildername property from infrastructure. Not "
+ "setting this option indicates manual usage.")
def _add_parser_options(self, parser):
pass
@@ -264,7 +396,7 @@
if any(map(lambda v: v and ',' in v,
[options.arch, options.mode])): # pragma: no cover
- print 'Multiple arch/mode are deprecated'
+ print('Multiple arch/mode are deprecated')
raise TestRunnerError()
return options, args
@@ -277,13 +409,20 @@
pass
if not self.build_config: # pragma: no cover
- print 'Failed to load build config'
+ print('Failed to load build config')
raise TestRunnerError
- print 'Build found: %s' % self.outdir
+ print('Build found: %s' % self.outdir)
if str(self.build_config):
- print '>>> Autodetected:'
- print self.build_config
+ print('>>> Autodetected:')
+ print(self.build_config)
+
+ # Represents the OS where tests are run on. Same as host OS except for
+ # Android, which is determined by build output.
+ if self.build_config.is_android:
+ self.target_os = 'android'
+ else:
+ self.target_os = utils.GuessOS()
# Returns possible build paths in order:
# gn
@@ -353,7 +492,7 @@
build_config_mode = 'debug' if self.build_config.is_debug else 'release'
if options.mode:
if options.mode not in MODES: # pragma: no cover
- print '%s mode is invalid' % options.mode
+ print('%s mode is invalid' % options.mode)
raise TestRunnerError()
if MODES[options.mode].execution_mode != build_config_mode:
print ('execution mode (%s) for %s is inconsistent with build config '
@@ -378,6 +517,16 @@
print('Warning: --shell-dir is deprecated. Searching for executables in '
'build directory (%s) instead.' % self.outdir)
+ if options.j == 0:
+ if self.build_config.is_android:
+ # Adb isn't happy about multi-processed file pushing.
+ options.j = 1
+ else:
+ options.j = multiprocessing.cpu_count()
+
+ options.command_prefix = shlex.split(options.command_prefix)
+ options.extra_flags = sum(map(shlex.split, options.extra_flags), [])
+
def _buildbot_to_v8_mode(self, config):
"""Convert buildbot build configs to configs understood by the v8 runner.
@@ -410,6 +559,9 @@
asan_options.append('detect_leaks=1')
else:
asan_options.append('detect_leaks=0')
+ if utils.GuessOS() == 'windows':
+ # https://crbug.com/967663
+ asan_options.append('detect_stack_use_after_return=0')
os.environ['ASAN_OPTIONS'] = ":".join(asan_options)
if self.build_config.cfi_vptr:
@@ -471,34 +623,129 @@
return reduce(list.__add__, map(expand_test_group, args), [])
- def _get_suites(self, args, verbose=False):
- names = self._args_to_suite_names(args)
- return self._load_suites(names, verbose)
-
- def _args_to_suite_names(self, args):
+ def _args_to_suite_names(self, args, test_root):
# Use default tests if no test configuration was provided at the cmd line.
- all_names = set(utils.GetSuitePaths(os.path.join(self.basedir, 'test')))
+ all_names = set(utils.GetSuitePaths(test_root))
args_names = OrderedDict([(arg.split('/')[0], None) for arg in args]) # set
return [name for name in args_names if name in all_names]
def _get_default_suite_names(self):
return []
- def _expand_test_group(self, name):
- return TEST_MAP.get(name, [name])
+ def _load_testsuite_generators(self, args, options):
+ names = self._args_to_suite_names(args, options.test_root)
+ test_config = self._create_test_config(options)
+ variables = self._get_statusfile_variables(options)
- def _load_suites(self, names, verbose=False):
- def load_suite(name):
- if verbose:
- print '>>> Loading test suite: %s' % name
- return testsuite.TestSuite.LoadTestSuite(
- os.path.join(self.basedir, 'test', name))
- return map(load_suite, names)
+ # Head generator with no elements
+ test_chain = testsuite.TestGenerator(0, [], [])
+ for name in names:
+ if options.verbose:
+ print('>>> Loading test suite: %s' % name)
+ suite = testsuite.TestSuite.Load(
+ os.path.join(options.test_root, name), test_config,
+ self.framework_name)
+
+ if self._is_testsuite_supported(suite, options):
+ tests = suite.load_tests_from_disk(variables)
+ test_chain.merge(tests)
+
+ return test_chain
+
+ def _is_testsuite_supported(self, suite, options):
+ """A predicate that can be overridden to filter out unsupported TestSuite
+ instances (see NumFuzzer for usage)."""
+ return True
+
+ def _get_statusfile_variables(self, options):
+ simd_mips = (
+ self.build_config.arch in ['mipsel', 'mips', 'mips64', 'mips64el'] and
+ self.build_config.mips_arch_variant == "r6" and
+ self.build_config.mips_use_msa)
+
+ mips_arch_variant = (
+ self.build_config.arch in ['mipsel', 'mips', 'mips64', 'mips64el'] and
+ self.build_config.mips_arch_variant)
+
+ # TODO(machenbach): In GN we can derive simulator run from
+ # target_arch != v8_target_arch in the dumped build config.
+ return {
+ "arch": self.build_config.arch,
+ "asan": self.build_config.asan,
+ "byteorder": sys.byteorder,
+ "dcheck_always_on": self.build_config.dcheck_always_on,
+ "deopt_fuzzer": False,
+ "endurance_fuzzer": False,
+ "gc_fuzzer": False,
+ "gc_stress": False,
+ "gcov_coverage": self.build_config.gcov_coverage,
+ "isolates": options.isolates,
+ "is_clang": self.build_config.is_clang,
+ "is_full_debug": self.build_config.is_full_debug,
+ "mips_arch_variant": mips_arch_variant,
+ "mode": self.mode_options.status_mode
+ if not self.build_config.dcheck_always_on
+ else "debug",
+ "msan": self.build_config.msan,
+ "no_harness": options.no_harness,
+ "no_i18n": self.build_config.no_i18n,
+ "no_snap": self.build_config.no_snap,
+ "novfp3": False,
+ "optimize_for_size": "--optimize-for-size" in options.extra_flags,
+ "predictable": self.build_config.predictable,
+ "simd_mips": simd_mips,
+ "simulator_run": False,
+ "system": self.target_os,
+ "tsan": self.build_config.tsan,
+ "ubsan_vptr": self.build_config.ubsan_vptr,
+ "embedded_builtins": self.build_config.embedded_builtins,
+ "verify_csa": self.build_config.verify_csa,
+ "lite_mode": self.build_config.lite_mode,
+ "pointer_compression": self.build_config.pointer_compression,
+ }
+
+ def _create_test_config(self, options):
+ timeout = options.timeout * self._timeout_scalefactor(options)
+ return TestConfig(
+ command_prefix=options.command_prefix,
+ extra_flags=options.extra_flags,
+ isolates=options.isolates,
+ mode_flags=self.mode_options.flags,
+ no_harness=options.no_harness,
+ noi18n=self.build_config.no_i18n,
+ random_seed=options.random_seed,
+ run_skipped=options.run_skipped,
+ shell_dir=self.outdir,
+ timeout=timeout,
+ verbose=options.verbose,
+ )
+
+ def _timeout_scalefactor(self, options):
+ """Increases timeout for slow build configurations."""
+ factor = self.mode_options.timeout_scalefactor
+ if self.build_config.arch in SLOW_ARCHS:
+ factor *= 4
+ if self.build_config.lite_mode:
+ factor *= 2
+ if self.build_config.predictable:
+ factor *= 4
+ if self.build_config.use_sanitizer:
+ factor *= 1.5
+ if self.build_config.is_full_debug:
+ factor *= 4
+
+ return factor
# TODO(majeski): remove options & args parameters
def _do_execute(self, suites, args, options):
raise NotImplementedError()
+ def _prepare_procs(self, procs):
+ procs = filter(None, procs)
+ for i in range(0, len(procs) - 1):
+ procs[i].connect_to(procs[i + 1])
+ procs[0].setup()
+
def _create_shard_proc(self, options):
myid, count = self._get_shard_info(options)
if count == 1:
@@ -536,8 +783,42 @@
# TODO(machenbach): Turn this into an assert. If that's wrong on the
# bots, printing will be quite useless. Or refactor this code to make
# sure we get a return code != 0 after testing if we got here.
- print "shard-run not a valid number, should be in [1:shard-count]"
- print "defaulting back to running all tests"
+ print("shard-run not a valid number, should be in [1:shard-count]")
+ print("defaulting back to running all tests")
return 1, 1
return shard_run, shard_count
+
+ def _create_progress_indicators(self, test_count, options):
+ procs = [PROGRESS_INDICATORS[options.progress]()]
+ if options.json_test_results:
+ procs.append(progress.JsonTestProgressIndicator(
+ self.framework_name,
+ options.json_test_results,
+ self.build_config.arch,
+ self.mode_options.execution_mode))
+
+ for proc in procs:
+ try:
+ proc.set_test_count(test_count)
+ except AttributeError:
+ pass
+
+ return procs
+
+ def _create_result_tracker(self, options):
+ return progress.ResultsTracker(options.exit_after_n_failures)
+
+ def _create_timeout_proc(self, options):
+ if not options.total_timeout_sec:
+ return None
+ return TimeoutProc(options.total_timeout_sec)
+
+ def _create_signal_proc(self):
+ return SignalProc()
+
+ def _create_rerun_proc(self, options):
+ if not options.rerun_failures_count:
+ return None
+ return RerunProc(options.rerun_failures_count,
+ options.rerun_failures_max)
diff --git a/src/v8/tools/testrunner/local/android.py b/src/v8/tools/testrunner/local/android.py
new file mode 100644
index 0000000..ebf04af
--- /dev/null
+++ b/src/v8/tools/testrunner/local/android.py
@@ -0,0 +1,205 @@
+# 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.
+
+"""
+Wrapper around the Android device abstraction from src/build/android.
+"""
+
+import logging
+import os
+import sys
+import re
+
+BASE_DIR = os.path.normpath(
+ os.path.join(os.path.dirname(__file__), '..', '..', '..'))
+ANDROID_DIR = os.path.join(BASE_DIR, 'build', 'android')
+DEVICE_DIR = '/data/local/tmp/v8/'
+
+
+class TimeoutException(Exception):
+ def __init__(self, timeout, output=None):
+ self.timeout = timeout
+ self.output = output
+
+
+class CommandFailedException(Exception):
+ def __init__(self, status, output):
+ self.status = status
+ self.output = output
+
+
+class _Driver(object):
+ """Helper class to execute shell commands on an Android device."""
+ def __init__(self, device=None):
+ assert os.path.exists(ANDROID_DIR)
+ sys.path.insert(0, ANDROID_DIR)
+
+ # We import the dependencies only on demand, so that this file can be
+ # imported unconditionally.
+ import devil_chromium
+ from devil.android import device_errors # pylint: disable=import-error
+ from devil.android import device_utils # pylint: disable=import-error
+ from devil.android.perf import cache_control # pylint: disable=import-error
+ from devil.android.perf import perf_control # pylint: disable=import-error
+ global cache_control
+ global device_errors
+ global perf_control
+
+ devil_chromium.Initialize()
+
+ # Find specified device or a single attached device if none was specified.
+ # In case none or multiple devices are attached, this raises an exception.
+ self.device = device_utils.DeviceUtils.HealthyDevices(
+ retries=5, enable_usb_resets=True, device_arg=device)[0]
+
+ # This remembers what we have already pushed to the device.
+ self.pushed = set()
+
+ def tear_down(self):
+ """Clean up files after running all tests."""
+ self.device.RemovePath(DEVICE_DIR, force=True, recursive=True)
+
+ def push_file(self, host_dir, file_name, target_rel='.',
+ skip_if_missing=False):
+ """Push a single file to the device (cached).
+
+ Args:
+ host_dir: Absolute parent directory of the file to push.
+ file_name: Name of the file to push.
+ target_rel: Parent directory of the target location on the device
+ (relative to the device's base dir for testing).
+ skip_if_missing: Keeps silent about missing files when set. Otherwise logs
+ error.
+ """
+ # TODO(sergiyb): Implement this method using self.device.PushChangedFiles to
+ # avoid accessing low-level self.device.adb.
+ file_on_host = os.path.join(host_dir, file_name)
+
+ # Only push files not yet pushed in one execution.
+ if file_on_host in self.pushed:
+ return
+
+ file_on_device_tmp = os.path.join(DEVICE_DIR, '_tmp_', file_name)
+ file_on_device = os.path.join(DEVICE_DIR, target_rel, file_name)
+ folder_on_device = os.path.dirname(file_on_device)
+
+ # Only attempt to push files that exist.
+ if not os.path.exists(file_on_host):
+ if not skip_if_missing:
+ logging.critical('Missing file on host: %s' % file_on_host)
+ return
+
+ # Work-around for 'text file busy' errors. Push the files to a temporary
+ # location and then copy them with a shell command.
+ output = self.device.adb.Push(file_on_host, file_on_device_tmp)
+ # Success looks like this: '3035 KB/s (12512056 bytes in 4.025s)'.
+ # Errors look like this: 'failed to copy ... '.
+ if output and not re.search('^[0-9]', output.splitlines()[-1]):
+ logging.critical('PUSH FAILED: ' + output)
+ self.device.adb.Shell('mkdir -p %s' % folder_on_device)
+ self.device.adb.Shell('cp %s %s' % (file_on_device_tmp, file_on_device))
+ self.pushed.add(file_on_host)
+
+ def push_executable(self, shell_dir, target_dir, binary):
+ """Push files required to run a V8 executable.
+
+ Args:
+ shell_dir: Absolute parent directory of the executable on the host.
+ target_dir: Parent directory of the executable on the device (relative to
+ devices' base dir for testing).
+ binary: Name of the binary to push.
+ """
+ self.push_file(shell_dir, binary, target_dir)
+
+ # Push external startup data. Backwards compatible for revisions where
+ # these files didn't exist. Or for bots that don't produce these files.
+ self.push_file(
+ shell_dir,
+ 'natives_blob.bin',
+ target_dir,
+ skip_if_missing=True,
+ )
+ self.push_file(
+ shell_dir,
+ 'snapshot_blob.bin',
+ target_dir,
+ skip_if_missing=True,
+ )
+ self.push_file(
+ shell_dir,
+ 'snapshot_blob_trusted.bin',
+ target_dir,
+ skip_if_missing=True,
+ )
+ self.push_file(
+ shell_dir,
+ 'icudtl.dat',
+ target_dir,
+ skip_if_missing=True,
+ )
+
+ def run(self, target_dir, binary, args, rel_path, timeout, env=None,
+ logcat_file=False):
+ """Execute a command on the device's shell.
+
+ Args:
+ target_dir: Parent directory of the executable on the device (relative to
+ devices' base dir for testing).
+ binary: Name of the binary.
+ args: List of arguments to pass to the binary.
+ rel_path: Relative path on device to use as CWD.
+ timeout: Timeout in seconds.
+ env: The environment variables with which the command should be run.
+ logcat_file: File into which to stream adb logcat log.
+ """
+ binary_on_device = os.path.join(DEVICE_DIR, target_dir, binary)
+ cmd = [binary_on_device] + args
+ def run_inner():
+ try:
+ output = self.device.RunShellCommand(
+ cmd,
+ cwd=os.path.join(DEVICE_DIR, rel_path),
+ check_return=True,
+ env=env,
+ timeout=timeout,
+ retries=0,
+ )
+ return '\n'.join(output)
+ except device_errors.AdbCommandFailedError as e:
+ raise CommandFailedException(e.status, e.output)
+ except device_errors.CommandTimeoutError as e:
+ raise TimeoutException(timeout, e.output)
+
+
+ if logcat_file:
+ with self.device.GetLogcatMonitor(output_file=logcat_file) as logmon:
+ result = run_inner()
+ logmon.Close()
+ return result
+ else:
+ return run_inner()
+
+ def drop_ram_caches(self):
+ """Drop ran caches on device."""
+ cache = cache_control.CacheControl(self.device)
+ cache.DropRamCaches()
+
+ def set_high_perf_mode(self):
+ """Set device into high performance mode."""
+ perf = perf_control.PerfControl(self.device)
+ perf.SetHighPerfMode()
+
+ def set_default_perf_mode(self):
+ """Set device into default performance mode."""
+ perf = perf_control.PerfControl(self.device)
+ perf.SetDefaultPerfMode()
+
+
+_ANDROID_DRIVER = None
+def android_driver(device=None):
+ """Singleton access method to the driver class."""
+ global _ANDROID_DRIVER
+ if not _ANDROID_DRIVER:
+ _ANDROID_DRIVER = _Driver(device)
+ return _ANDROID_DRIVER
diff --git a/src/v8/tools/testrunner/local/command.py b/src/v8/tools/testrunner/local/command.py
index 93b1ac9..b68252c 100644
--- a/src/v8/tools/testrunner/local/command.py
+++ b/src/v8/tools/testrunner/local/command.py
@@ -2,24 +2,57 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+# for py2/py3 compatibility
+from __future__ import print_function
import os
+import re
+import signal
import subprocess
import sys
import threading
import time
+from ..local.android import (
+ android_driver, CommandFailedException, TimeoutException)
from ..local import utils
from ..objects import output
+BASE_DIR = os.path.normpath(
+ os.path.join(os.path.dirname(os.path.abspath(__file__)), '..' , '..', '..'))
+
SEM_INVALID_VALUE = -1
SEM_NOGPFAULTERRORBOX = 0x0002 # Microsoft Platform SDK WinBase.h
+def setup_testing():
+ """For testing only: We use threading under the hood instead of
+ multiprocessing to make coverage work. Signal handling is only supported
+ in the main thread, so we disable it for testing.
+ """
+ signal.signal = lambda *_: None
+
+
+class AbortException(Exception):
+ """Indicates early abort on SIGINT, SIGTERM or internal hard timeout."""
+ pass
+
+
class BaseCommand(object):
def __init__(self, shell, args=None, cmd_prefix=None, timeout=60, env=None,
- verbose=False):
+ verbose=False, resources_func=None):
+ """Initialize the command.
+
+ Args:
+ shell: The name of the executable (e.g. d8).
+ args: List of args to pass to the executable.
+ cmd_prefix: Prefix of command (e.g. a wrapper script).
+ timeout: Timeout in seconds.
+ env: Environment dict for execution.
+ verbose: Print additional output.
+ resources_func: Callable, returning all test files needed by this command.
+ """
assert(timeout > 0)
self.shell = shell
@@ -29,16 +62,22 @@
self.env = env or {}
self.verbose = verbose
- def execute(self, **additional_popen_kwargs):
+ def execute(self):
if self.verbose:
- print '# %s' % self
+ print('# %s' % self)
- process = self._start_process(**additional_popen_kwargs)
+ process = self._start_process()
+
+ # Variable to communicate with the signal handler.
+ abort_occured = [False]
+ def handler(signum, frame):
+ self._abort(process, abort_occured)
+ signal.signal(signal.SIGTERM, handler)
# Variable to communicate with the timer.
timeout_occured = [False]
timer = threading.Timer(
- self.timeout, self._on_timeout, [process, timeout_occured])
+ self.timeout, self._abort, [process, timeout_occured])
timer.start()
start_time = time.time()
@@ -47,6 +86,9 @@
timer.cancel()
+ if abort_occured[0]:
+ raise AbortException()
+
return output.Output(
process.returncode,
timeout_occured[0],
@@ -56,14 +98,13 @@
duration
)
- def _start_process(self, **additional_popen_kwargs):
+ def _start_process(self):
try:
return subprocess.Popen(
args=self._get_popen_args(),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=self._get_env(),
- **additional_popen_kwargs
)
except Exception as e:
sys.stderr.write('Error executing: %s\n' % self)
@@ -85,12 +126,16 @@
def _kill_process(self, process):
raise NotImplementedError()
- def _on_timeout(self, process, timeout_occured):
- timeout_occured[0] = True
+ def _abort(self, process, abort_called):
+ abort_called[0] = True
try:
+ print('Attempting to kill process %s' % process.pid)
+ sys.stdout.flush()
self._kill_process(process)
- except OSError:
- sys.stderr.write('Error: Process %s already ended.\n' % process.pid)
+ except OSError as e:
+ print(e)
+ sys.stdout.flush()
+ pass
def __str__(self):
return self.to_string()
@@ -114,6 +159,25 @@
class PosixCommand(BaseCommand):
+ # TODO(machenbach): Use base process start without shell once
+ # https://crbug.com/v8/8889 is resolved.
+ def _start_process(self):
+ def wrapped(arg):
+ if set('() \'"') & set(arg):
+ return "'%s'" % arg.replace("'", "'\"'\"'")
+ return arg
+ try:
+ return subprocess.Popen(
+ args=' '.join(map(wrapped, self._get_popen_args())),
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ env=self._get_env(),
+ shell=True,
+ )
+ except Exception as e:
+ sys.stderr.write('Error executing: %s\n' % self)
+ raise e
+
def _kill_process(self, process):
process.kill()
@@ -147,9 +211,6 @@
return subprocess.list2cmdline(self._to_args_list())
def _kill_process(self, process):
- if self.verbose:
- print 'Attempting to kill process %d' % process.pid
- sys.stdout.flush()
tk = subprocess.Popen(
'taskkill /T /F /PID %d' % process.pid,
stdout=subprocess.PIPE,
@@ -157,15 +218,96 @@
)
stdout, stderr = tk.communicate()
if self.verbose:
- print 'Taskkill results for %d' % process.pid
- print stdout
- print stderr
- print 'Return code: %d' % tk.returncode
+ print('Taskkill results for %d' % process.pid)
+ print(stdout)
+ print(stderr)
+ print('Return code: %d' % tk.returncode)
sys.stdout.flush()
-# Set the Command class to the OS-specific version.
-if utils.IsWindows():
- Command = WindowsCommand
-else:
- Command = PosixCommand
+class AndroidCommand(BaseCommand):
+ # This must be initialized before creating any instances of this class.
+ driver = None
+
+ def __init__(self, shell, args=None, cmd_prefix=None, timeout=60, env=None,
+ verbose=False, resources_func=None):
+ """Initialize the command and all files that need to be pushed to the
+ Android device.
+ """
+ self.shell_name = os.path.basename(shell)
+ self.shell_dir = os.path.dirname(shell)
+ self.files_to_push = (resources_func or (lambda: []))()
+
+ # Make all paths in arguments relative and also prepare files from arguments
+ # for pushing to the device.
+ rel_args = []
+ find_path_re = re.compile(r'.*(%s/[^\'"]+).*' % re.escape(BASE_DIR))
+ for arg in (args or []):
+ match = find_path_re.match(arg)
+ if match:
+ self.files_to_push.append(match.group(1))
+ rel_args.append(
+ re.sub(r'(.*)%s/(.*)' % re.escape(BASE_DIR), r'\1\2', arg))
+
+ super(AndroidCommand, self).__init__(
+ shell, args=rel_args, cmd_prefix=cmd_prefix, timeout=timeout, env=env,
+ verbose=verbose)
+
+ def execute(self, **additional_popen_kwargs):
+ """Execute the command on the device.
+
+ This pushes all required files to the device and then runs the command.
+ """
+ if self.verbose:
+ print('# %s' % self)
+
+ self.driver.push_executable(self.shell_dir, 'bin', self.shell_name)
+
+ for abs_file in self.files_to_push:
+ abs_dir = os.path.dirname(abs_file)
+ file_name = os.path.basename(abs_file)
+ rel_dir = os.path.relpath(abs_dir, BASE_DIR)
+ self.driver.push_file(abs_dir, file_name, rel_dir)
+
+ start_time = time.time()
+ return_code = 0
+ timed_out = False
+ try:
+ stdout = self.driver.run(
+ 'bin', self.shell_name, self.args, '.', self.timeout, self.env)
+ except CommandFailedException as e:
+ return_code = e.status
+ stdout = e.output
+ except TimeoutException as e:
+ return_code = 1
+ timed_out = True
+ # Sadly the Android driver doesn't provide output on timeout.
+ stdout = ''
+
+ duration = time.time() - start_time
+ return output.Output(
+ return_code,
+ timed_out,
+ stdout,
+ '', # No stderr available.
+ -1, # No pid available.
+ duration,
+ )
+
+
+Command = None
+def setup(target_os, device):
+ """Set the Command class to the OS-specific version."""
+ global Command
+ if target_os == 'android':
+ AndroidCommand.driver = android_driver(device)
+ Command = AndroidCommand
+ elif target_os == 'windows':
+ Command = WindowsCommand
+ else:
+ Command = PosixCommand
+
+def tear_down():
+ """Clean up after using commands."""
+ if Command == AndroidCommand:
+ AndroidCommand.driver.tear_down()
diff --git a/src/v8/tools/testrunner/local/fake_testsuite/fake_testsuite.status b/src/v8/tools/testrunner/local/fake_testsuite/fake_testsuite.status
new file mode 100644
index 0000000..b5ebc84
--- /dev/null
+++ b/src/v8/tools/testrunner/local/fake_testsuite/fake_testsuite.status
@@ -0,0 +1,5 @@
+# Copyright 2019 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/src/v8/tools/testrunner/local/fake_testsuite/testcfg.py b/src/v8/tools/testrunner/local/fake_testsuite/testcfg.py
new file mode 100644
index 0000000..28de737
--- /dev/null
+++ b/src/v8/tools/testrunner/local/fake_testsuite/testcfg.py
@@ -0,0 +1,33 @@
+#!/usr/bin/env python
+# Copyright 2019 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 sys
+
+from testrunner.local import testsuite, statusfile
+
+
+class TestLoader(testsuite.TestLoader):
+ def _list_test_filenames(self):
+ return ["fast", "slow"]
+
+ def list_tests(self):
+ self.test_count_estimation = 2
+ fast = self._create_test("fast", self.suite)
+ slow = self._create_test("slow", self.suite)
+
+ slow._statusfile_outcomes.append(statusfile.SLOW)
+ yield fast
+ yield slow
+
+
+class TestSuite(testsuite.TestSuite):
+ def _test_loader_class(self):
+ return TestLoader
+
+ def _test_class(self):
+ return testsuite.TestCase
+
+def GetSuite(*args, **kwargs):
+ return TestSuite(*args, **kwargs)
diff --git a/src/v8/tools/testrunner/local/pool.py b/src/v8/tools/testrunner/local/pool.py
index 9199b62..e0b0ec4 100644
--- a/src/v8/tools/testrunner/local/pool.py
+++ b/src/v8/tools/testrunner/local/pool.py
@@ -3,43 +3,53 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-from Queue import Empty
-from multiprocessing import Event, Process, Queue
+# for py2/py3 compatibility
+from __future__ import print_function
+
+from contextlib import contextmanager
+from multiprocessing import Process, Queue
+import os
+import signal
+import time
import traceback
+try:
+ from queue import Empty # Python 3
+except ImportError:
+ from Queue import Empty # Python 2
+
+from . import command
+
def setup_testing():
"""For testing only: Use threading under the hood instead of multiprocessing
to make coverage work.
"""
global Queue
- global Event
global Process
del Queue
- del Event
del Process
- from Queue import Queue
- from threading import Event
+ try:
+ from queue import Queue # Python 3
+ except ImportError:
+ from Queue import Queue # Python 2
+
from threading import Thread as Process
+ # Monkeypatch threading Queue to look like multiprocessing Queue.
+ Queue.cancel_join_thread = lambda self: None
+ # Monkeypatch os.kill and add fake pid property on Thread.
+ os.kill = lambda *args: None
+ Process.pid = property(lambda self: None)
class NormalResult():
def __init__(self, result):
self.result = result
- self.exception = False
- self.break_now = False
-
+ self.exception = None
class ExceptionResult():
- def __init__(self):
- self.exception = True
- self.break_now = False
-
-
-class BreakResult():
- def __init__(self):
- self.exception = False
- self.break_now = True
+ def __init__(self, exception):
+ self.exception = exception
class MaybeResult():
@@ -56,26 +66,43 @@
return MaybeResult(False, value)
-def Worker(fn, work_queue, done_queue, done,
+def Worker(fn, work_queue, done_queue,
process_context_fn=None, process_context_args=None):
"""Worker to be run in a child process.
- The worker stops on two conditions. 1. When the poison pill "STOP" is
- reached or 2. when the event "done" is set."""
+ The worker stops when the poison pill "STOP" is reached.
+ """
try:
kwargs = {}
if process_context_fn and process_context_args is not None:
kwargs.update(process_context=process_context_fn(*process_context_args))
for args in iter(work_queue.get, "STOP"):
- if done.is_set():
- break
try:
done_queue.put(NormalResult(fn(*args, **kwargs)))
- except Exception, e:
+ except command.AbortException:
+ # SIGINT, SIGTERM or internal hard timeout.
+ break
+ except Exception as e:
traceback.print_exc()
print(">>> EXCEPTION: %s" % e)
- done_queue.put(ExceptionResult())
+ done_queue.put(ExceptionResult(e))
+ # When we reach here on normal tear down, all items have been pulled from
+ # the done_queue before and this should have no effect. On fast abort, it's
+ # possible that a fast worker left items on the done_queue in memory, which
+ # will never be pulled. This call purges those to avoid a deadlock.
+ done_queue.cancel_join_thread()
except KeyboardInterrupt:
- done_queue.put(BreakResult())
+ assert False, 'Unreachable'
+
+
+@contextmanager
+def without_sig():
+ int_handler = signal.signal(signal.SIGINT, signal.SIG_IGN)
+ term_handler = signal.signal(signal.SIGTERM, signal.SIG_IGN)
+ try:
+ yield
+ finally:
+ signal.signal(signal.SIGINT, int_handler)
+ signal.signal(signal.SIGTERM, term_handler)
class Pool():
@@ -88,24 +115,28 @@
# Necessary to not overflow the queue's pipe if a keyboard interrupt happens.
BUFFER_FACTOR = 4
- def __init__(self, num_workers, heartbeat_timeout=30):
+ def __init__(self, num_workers, heartbeat_timeout=1):
self.num_workers = num_workers
self.processes = []
self.terminated = False
+ self.abort_now = False
- # Invariant: count >= #work_queue + #done_queue. It is greater when a
- # worker takes an item from the work_queue and before the result is
+ # Invariant: processing_count >= #work_queue + #done_queue. It is greater
+ # when a worker takes an item from the work_queue and before the result is
# submitted to the done_queue. It is equal when no worker is working,
# e.g. when all workers have finished, and when no results are processed.
# Count is only accessed by the parent process. Only the parent process is
# allowed to remove items from the done_queue and to add items to the
# work_queue.
- self.count = 0
- self.work_queue = Queue()
- self.done_queue = Queue()
- self.done = Event()
+ self.processing_count = 0
self.heartbeat_timeout = heartbeat_timeout
+ # Disable sigint and sigterm to prevent subprocesses from capturing the
+ # signals.
+ with without_sig():
+ self.work_queue = Queue()
+ self.done_queue = Queue()
+
def imap_unordered(self, fn, gen,
process_context_fn=None, process_context_args=None):
"""Maps function "fn" to items in generator "gen" on the worker processes
@@ -123,58 +154,63 @@
process_context_fn. All arguments will be pickled and sent beyond the
process boundary.
"""
+ if self.terminated:
+ return
try:
internal_error = False
gen = iter(gen)
self.advance = self._advance_more
- for w in xrange(self.num_workers):
- p = Process(target=Worker, args=(fn,
- self.work_queue,
- self.done_queue,
- self.done,
- process_context_fn,
- process_context_args))
- p.start()
- self.processes.append(p)
+ # Disable sigint and sigterm to prevent subprocesses from capturing the
+ # signals.
+ with without_sig():
+ for w in range(self.num_workers):
+ p = Process(target=Worker, args=(fn,
+ self.work_queue,
+ self.done_queue,
+ process_context_fn,
+ process_context_args))
+ p.start()
+ self.processes.append(p)
self.advance(gen)
- while self.count > 0:
+ while self.processing_count > 0:
while True:
try:
- result = self.done_queue.get(timeout=self.heartbeat_timeout)
- break
- except Empty:
- # Indicate a heartbeat. The iterator will continue fetching the
- # next result.
- yield MaybeResult.create_heartbeat()
- self.count -= 1
- if result.exception:
- # TODO(machenbach): Handle a few known types of internal errors
- # gracefully, e.g. missing test files.
- internal_error = True
- continue
- elif result.break_now:
- # A keyboard interrupt happened in one of the worker processes.
- raise KeyboardInterrupt
- else:
- yield MaybeResult.create_result(result.result)
+ # Read from result queue in a responsive fashion. If available,
+ # this will return a normal result immediately or a heartbeat on
+ # heartbeat timeout (default 1 second).
+ result = self._get_result_from_queue()
+ except:
+ # TODO(machenbach): Handle a few known types of internal errors
+ # gracefully, e.g. missing test files.
+ internal_error = True
+ continue
+
+ if self.abort_now:
+ # SIGINT, SIGTERM or internal hard timeout.
+ return
+
+ yield result
+ break
+
self.advance(gen)
except KeyboardInterrupt:
- raise
+ assert False, 'Unreachable'
except Exception as e:
traceback.print_exc()
print(">>> EXCEPTION: %s" % e)
finally:
- self.terminate()
+ self._terminate()
+
if internal_error:
raise Exception("Internal error in a worker process.")
def _advance_more(self, gen):
- while self.count < self.num_workers * self.BUFFER_FACTOR:
+ while self.processing_count < self.num_workers * self.BUFFER_FACTOR:
try:
- self.work_queue.put(gen.next())
- self.count += 1
+ self.work_queue.put(next(gen))
+ self.processing_count += 1
except StopIteration:
self.advance = self._advance_empty
break
@@ -185,27 +221,51 @@
def add(self, args):
"""Adds an item to the work queue. Can be called dynamically while
processing the results from imap_unordered."""
- self.work_queue.put(args)
- self.count += 1
+ assert not self.terminated
- def terminate(self):
+ self.work_queue.put(args)
+ self.processing_count += 1
+
+ def abort(self):
+ """Schedules abort on next queue read.
+
+ This is safe to call when handling SIGINT, SIGTERM or when an internal
+ hard timeout is reached.
+ """
+ self.abort_now = True
+
+ def _terminate(self):
+ """Terminates execution and cleans up the queues.
+
+ If abort() was called before termination, this also terminates the
+ subprocesses and doesn't wait for ongoing tests.
+ """
if self.terminated:
return
self.terminated = True
- # For exceptional tear down set the "done" event to stop the workers before
- # they empty the queue buffer.
- self.done.set()
+ # Drain out work queue from tests
+ try:
+ while True:
+ self.work_queue.get(True, 0.1)
+ except Empty:
+ pass
- for p in self.processes:
+ # Make sure all processes stop
+ for _ in self.processes:
# During normal tear down the workers block on get(). Feed a poison pill
# per worker to make them stop.
self.work_queue.put("STOP")
+ if self.abort_now:
+ for p in self.processes:
+ os.kill(p.pid, signal.SIGTERM)
+
for p in self.processes:
p.join()
- # Drain the queues to prevent failures when queues are garbage collected.
+ # Drain the queues to prevent stderr chatter when queues are garbage
+ # collected.
try:
while True: self.work_queue.get(False)
except:
@@ -214,3 +274,22 @@
while True: self.done_queue.get(False)
except:
pass
+
+ def _get_result_from_queue(self):
+ """Attempts to get the next result from the queue.
+
+ Returns: A wrapped result if one was available within heartbeat timeout,
+ a heartbeat result otherwise.
+ Raises:
+ Exception: If an exception occured when processing the task on the
+ worker side, it is reraised here.
+ """
+ while True:
+ try:
+ result = self.done_queue.get(timeout=self.heartbeat_timeout)
+ self.processing_count -= 1
+ if result.exception:
+ raise result.exception
+ return MaybeResult.create_result(result.result)
+ except Empty:
+ return MaybeResult.create_heartbeat()
diff --git a/src/v8/tools/testrunner/local/pool_unittest.py b/src/v8/tools/testrunner/local/pool_unittest.py
old mode 100644
new mode 100755
index 235eca6..240cd56
--- a/src/v8/tools/testrunner/local/pool_unittest.py
+++ b/src/v8/tools/testrunner/local/pool_unittest.py
@@ -3,9 +3,16 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+import os
+import sys
import unittest
-from pool import Pool
+# Needed because the test runner contains relative imports.
+TOOLS_PATH = os.path.dirname(os.path.dirname(os.path.dirname(
+ os.path.abspath(__file__))))
+sys.path.append(TOOLS_PATH)
+
+from testrunner.local.pool import Pool
def Run(x):
if x == 10:
@@ -17,6 +24,9 @@
results = set()
pool = Pool(3)
for result in pool.imap_unordered(Run, [[x] for x in range(0, 10)]):
+ if result.heartbeat:
+ # Any result can be a heartbeat due to timings.
+ continue
results.add(result.value)
self.assertEquals(set(range(0, 10)), results)
@@ -25,6 +35,9 @@
pool = Pool(3)
with self.assertRaises(Exception):
for result in pool.imap_unordered(Run, [[x] for x in range(0, 12)]):
+ if result.heartbeat:
+ # Any result can be a heartbeat due to timings.
+ continue
# Item 10 will not appear in results due to an internal exception.
results.add(result.value)
expect = set(range(0, 12))
@@ -35,8 +48,15 @@
results = set()
pool = Pool(3)
for result in pool.imap_unordered(Run, [[x] for x in range(0, 10)]):
+ if result.heartbeat:
+ # Any result can be a heartbeat due to timings.
+ continue
results.add(result.value)
if result.value < 30:
pool.add([result.value + 20])
self.assertEquals(set(range(0, 10) + range(20, 30) + range(40, 50)),
results)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/src/v8/tools/testrunner/local/statusfile.py b/src/v8/tools/testrunner/local/statusfile.py
index 988750d..e477832 100644
--- a/src/v8/tools/testrunner/local/statusfile.py
+++ b/src/v8/tools/testrunner/local/statusfile.py
@@ -25,6 +25,9 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# for py2/py3 compatibility
+from __future__ import print_function
+
import os
import re
@@ -34,8 +37,8 @@
# Possible outcomes
FAIL = "FAIL"
PASS = "PASS"
-TIMEOUT = "TIMEOUT" # TODO(majeski): unused in status files
-CRASH = "CRASH" # TODO(majeski): unused in status files
+TIMEOUT = "TIMEOUT"
+CRASH = "CRASH"
# Outcomes only for status file, need special handling
FAIL_OK = "FAIL_OK"
@@ -45,21 +48,22 @@
SKIP = "SKIP"
SLOW = "SLOW"
NO_VARIANTS = "NO_VARIANTS"
+FAIL_PHASE_ONLY = "FAIL_PHASE_ONLY"
ALWAYS = "ALWAYS"
KEYWORDS = {}
for key in [SKIP, FAIL, PASS, CRASH, SLOW, FAIL_OK, NO_VARIANTS, FAIL_SLOPPY,
- ALWAYS]:
+ ALWAYS, FAIL_PHASE_ONLY]:
KEYWORDS[key] = key
# Support arches, modes to be written as keywords instead of strings.
VARIABLES = {ALWAYS: True}
-for var in ["debug", "release", "big", "little",
+for var in ["debug", "release", "big", "little", "android",
"android_arm", "android_arm64", "android_ia32", "android_x64",
"arm", "arm64", "ia32", "mips", "mipsel", "mips64", "mips64el",
"x64", "ppc", "ppc64", "s390", "s390x", "macos", "windows",
- "linux", "aix"]:
+ "linux", "aix", "r1", "r2", "r3", "r5", "r6"]:
VARIABLES[var] = var
# Allow using variants as keywords.
@@ -132,7 +136,7 @@
variant_desc = 'variant independent'
else:
variant_desc = 'variant: %s' % variant
- print 'Unused rule: %s -> %s (%s)' % (rule, value, variant_desc)
+ print('Unused rule: %s -> %s (%s)' % (rule, value, variant_desc))
def _JoinsPassAndFail(outcomes1, outcomes2):
@@ -328,5 +332,5 @@
"missing file for %s test %s" % (basename, rule))
return status["success"]
except Exception as e:
- print e
+ print(e)
return False
diff --git a/src/v8/tools/testrunner/local/statusfile_unittest.py b/src/v8/tools/testrunner/local/statusfile_unittest.py
index 299e332..e8d5ff9 100755
--- a/src/v8/tools/testrunner/local/statusfile_unittest.py
+++ b/src/v8/tools/testrunner/local/statusfile_unittest.py
@@ -3,6 +3,7 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+
import unittest
import statusfile
diff --git a/src/v8/tools/testrunner/local/testsuite.py b/src/v8/tools/testrunner/local/testsuite.py
index 6a9e983..864d734 100644
--- a/src/v8/tools/testrunner/local/testsuite.py
+++ b/src/v8/tools/testrunner/local/testsuite.py
@@ -28,38 +28,20 @@
import fnmatch
import imp
+import itertools
import os
+from contextlib import contextmanager
from . import command
from . import statusfile
from . import utils
from ..objects.testcase import TestCase
-from variants import ALL_VARIANTS, ALL_VARIANT_FLAGS
+from .variants import ALL_VARIANTS, ALL_VARIANT_FLAGS
STANDARD_VARIANT = set(["default"])
-class LegacyVariantsGenerator(object):
- def __init__(self, suite, variants):
- self.suite = suite
- self.all_variants = ALL_VARIANTS & variants
- self.standard_variant = STANDARD_VARIANT & variants
-
- def FilterVariantsByTest(self, test):
- if test.only_standard_variant:
- return self.standard_variant
- return self.all_variants
-
- def GetFlagSets(self, test, variant):
- return ALL_VARIANT_FLAGS[variant]
-
-
-class StandardLegacyVariantsGenerator(LegacyVariantsGenerator):
- def FilterVariantsByTest(self, testcase):
- return self.standard_variant
-
-
class VariantsGenerator(object):
def __init__(self, variants):
self._all_variants = [v for v in variants if v in ALL_VARIANTS]
@@ -80,45 +62,235 @@
return self._all_variants
+class TestCombiner(object):
+ def get_group_key(self, test):
+ """To indicate what tests can be combined with each other we define a group
+ key for each test. Tests with the same group key can be combined. Test
+ without a group key (None) is not combinable with any other test.
+ """
+ raise NotImplementedError()
+
+ def combine(self, name, tests):
+ """Returns test combined from `tests`. Since we identify tests by their
+ suite and name, `name` parameter should be unique within one suite.
+ """
+ return self._combined_test_class()(name, tests)
+
+ def _combined_test_class(self):
+ raise NotImplementedError()
+
+
+class TestLoader(object):
+ """Base class for loading TestSuite tests after applying test suite
+ transformations."""
+
+ def __init__(self, suite, test_class, test_config, test_root):
+ self.suite = suite
+ self.test_class = test_class
+ self.test_config = test_config
+ self.test_root = test_root
+ self.test_count_estimation = len(list(self._list_test_filenames()))
+
+ def _list_test_filenames(self):
+ """Implemented by the subclassed TestLoaders to list filenames.
+
+ Filenames are expected to be sorted and are deterministic."""
+ raise NotImplementedError
+
+ def _should_filter_by_name(self, name):
+ return False
+
+ def _should_filter_by_test(self, test):
+ return False
+
+ def _filename_to_testname(self, filename):
+ """Hook for subclasses to write their own filename transformation
+ logic before the test creation."""
+ return filename
+
+ # TODO: not needed for every TestLoader, extract it into a subclass.
+ def _path_to_name(self, path):
+ if utils.IsWindows():
+ return path.replace(os.path.sep, "/")
+
+ return path
+
+ def _create_test(self, path, suite, **kwargs):
+ """Converts paths into test objects using the given options"""
+ return self.test_class(
+ suite, path, self._path_to_name(path), self.test_config, **kwargs)
+
+ def list_tests(self):
+ """Loads and returns the test objects for a TestSuite"""
+ # TODO: detect duplicate tests.
+ for filename in self._list_test_filenames():
+ if self._should_filter_by_name(filename):
+ continue
+
+ testname = self._filename_to_testname(filename)
+ case = self._create_test(testname, self.suite)
+ if self._should_filter_by_test(case):
+ continue
+
+ yield case
+
+
+class GenericTestLoader(TestLoader):
+ """Generic TestLoader implementing the logic for listing filenames"""
+ @property
+ def excluded_files(self):
+ return set()
+
+ @property
+ def excluded_dirs(self):
+ return set()
+
+ @property
+ def excluded_suffixes(self):
+ return set()
+
+ @property
+ def test_dirs(self):
+ return [self.test_root]
+
+ @property
+ def extensions(self):
+ return []
+
+ def __find_extension(self, filename):
+ for extension in self.extensions:
+ if filename.endswith(extension):
+ return extension
+
+ return False
+
+ def _should_filter_by_name(self, filename):
+ if not self.__find_extension(filename):
+ return True
+
+ for suffix in self.excluded_suffixes:
+ if filename.endswith(suffix):
+ return True
+
+ if os.path.basename(filename) in self.excluded_files:
+ return True
+
+ return False
+
+ def _filename_to_testname(self, filename):
+ extension = self.__find_extension(filename)
+ if not extension:
+ return filename
+
+ return filename[:-len(extension)]
+
+ def _to_relpath(self, abspath, test_root):
+ return os.path.relpath(abspath, test_root)
+
+ def _list_test_filenames(self):
+ for test_dir in sorted(self.test_dirs):
+ test_root = os.path.join(self.test_root, test_dir)
+ for dirname, dirs, files in os.walk(test_root, followlinks=True):
+ dirs.sort()
+ for dir in dirs:
+ if dir in self.excluded_dirs or dir.startswith('.'):
+ dirs.remove(dir)
+
+ files.sort()
+ for filename in files:
+ abspath = os.path.join(dirname, filename)
+
+ yield self._to_relpath(abspath, test_root)
+
+
+class JSTestLoader(GenericTestLoader):
+ @property
+ def extensions(self):
+ return [".js", ".mjs"]
+
+
+class TestGenerator(object):
+ def __init__(self, test_count_estimate, slow_tests, fast_tests):
+ self.test_count_estimate = test_count_estimate
+ self.slow_tests = slow_tests
+ self.fast_tests = fast_tests
+ self._rebuild_iterator()
+
+ def _rebuild_iterator(self):
+ self._iterator = itertools.chain(self.slow_tests, self.fast_tests)
+
+ def __iter__(self):
+ return self
+
+ def __next__(self):
+ return self.next()
+
+ def next(self):
+ return next(self._iterator)
+
+ def merge(self, test_generator):
+ self.test_count_estimate += test_generator.test_count_estimate
+ self.slow_tests = itertools.chain(
+ self.slow_tests, test_generator.slow_tests)
+ self.fast_tests = itertools.chain(
+ self.fast_tests, test_generator.fast_tests)
+ self._rebuild_iterator()
+
+
+@contextmanager
+def _load_testsuite_module(name, root):
+ f = None
+ try:
+ (f, pathname, description) = imp.find_module("testcfg", [root])
+ yield imp.load_module(name + "_testcfg", f, pathname, description)
+ finally:
+ if f:
+ f.close()
+
class TestSuite(object):
@staticmethod
- def LoadTestSuite(root):
+ def Load(root, test_config, framework_name):
name = root.split(os.path.sep)[-1]
- f = None
- try:
- (f, pathname, description) = imp.find_module("testcfg", [root])
- module = imp.load_module(name + "_testcfg", f, pathname, description)
- return module.GetSuite(name, root)
- finally:
- if f:
- f.close()
+ with _load_testsuite_module(name, root) as module:
+ return module.GetSuite(name, root, test_config, framework_name)
- def __init__(self, name, root):
- # Note: This might be called concurrently from different processes.
+ def __init__(self, name, root, test_config, framework_name):
self.name = name # string
self.root = root # string containing path
+ self.test_config = test_config
+ self.framework_name = framework_name # name of the test runner impl
self.tests = None # list of TestCase objects
self.statusfile = None
+ self._test_loader = self._test_loader_class()(
+ self, self._test_class(), self.test_config, self.root)
+
def status_file(self):
return "%s/%s.status" % (self.root, self.name)
- def ListTests(self, context):
+ @property
+ def _test_loader_class(self):
raise NotImplementedError
- def _LegacyVariantsGeneratorFactory(self):
- """The variant generator class to be used."""
- return LegacyVariantsGenerator
+ def ListTests(self):
+ return self._test_loader.list_tests()
- def CreateLegacyVariantsGenerator(self, variants):
- """Return a generator for the testing variants of this suite.
+ def __initialize_test_count_estimation(self):
+ # Retrieves a single test to initialize the test generator.
+ next(iter(self.ListTests()), None)
- Args:
- variants: List of variant names to be run as specified by the test
- runner.
- Returns: An object of type LegacyVariantsGenerator.
- """
- return self._LegacyVariantsGeneratorFactory()(self, set(variants))
+ def __calculate_test_count(self):
+ self.__initialize_test_count_estimation()
+ return self._test_loader.test_count_estimation
+
+ def load_tests_from_disk(self, statusfile_variables):
+ self.statusfile = statusfile.StatusFile(
+ self.status_file(), statusfile_variables)
+
+ test_count = self.__calculate_test_count()
+ slow_tests = (test for test in self.ListTests() if test.is_slow)
+ fast_tests = (test for test in self.ListTests() if not test.is_slow)
+ return TestGenerator(test_count, slow_tests, fast_tests)
def get_variants_gen(self, variants):
return self._variants_gen_class()(variants)
@@ -126,83 +298,20 @@
def _variants_gen_class(self):
return VariantsGenerator
- def ReadStatusFile(self, variables):
- self.statusfile = statusfile.StatusFile(self.status_file(), variables)
+ def test_combiner_available(self):
+ return bool(self._test_combiner_class())
- def ReadTestCases(self, context):
- self.tests = self.ListTests(context)
+ def get_test_combiner(self):
+ cls = self._test_combiner_class()
+ if cls:
+ return cls()
+ return None
-
- def FilterTestCasesByStatus(self,
- slow_tests_mode=None,
- pass_fail_tests_mode=None):
- """Filters tests by outcomes from status file.
-
- Status file has to be loaded before using this function.
-
- Args:
- slow_tests_mode: What to do with slow tests.
- pass_fail_tests_mode: What to do with pass or fail tests.
-
- Mode options:
- None (default) - don't skip
- "skip" - skip if slow/pass_fail
- "run" - skip if not slow/pass_fail
+ def _test_combiner_class(self):
+ """Returns Combiner subclass. None if suite doesn't support combining
+ tests.
"""
- def _skip_slow(is_slow, mode):
- return (
- (mode == 'run' and not is_slow) or
- (mode == 'skip' and is_slow))
-
- def _skip_pass_fail(pass_fail, mode):
- return (
- (mode == 'run' and not pass_fail) or
- (mode == 'skip' and pass_fail))
-
- def _compliant(test):
- if test.do_skip:
- return False
- if _skip_slow(test.is_slow, slow_tests_mode):
- return False
- if _skip_pass_fail(test.is_pass_or_fail, pass_fail_tests_mode):
- return False
- return True
-
- self.tests = filter(_compliant, self.tests)
-
- def FilterTestCasesByArgs(self, args):
- """Filter test cases based on command-line arguments.
-
- args can be a glob: asterisks in any position of the argument
- represent zero or more characters. Without asterisks, only exact matches
- will be used with the exeption of the test-suite name as argument.
- """
- filtered = []
- globs = []
- for a in args:
- argpath = a.split('/')
- if argpath[0] != self.name:
- continue
- if len(argpath) == 1 or (len(argpath) == 2 and argpath[1] == '*'):
- return # Don't filter, run all tests in this suite.
- path = '/'.join(argpath[1:])
- globs.append(path)
-
- for t in self.tests:
- for g in globs:
- if fnmatch.fnmatch(t.path, g):
- filtered.append(t)
- break
- self.tests = filtered
-
- def _create_test(self, path, **kwargs):
- test = self._test_class()(self, path, self._path_to_name(path), **kwargs)
- return test
+ return None
def _test_class(self):
raise NotImplementedError
-
- def _path_to_name(self, path):
- if utils.IsWindows():
- return path.replace("\\", "/")
- return path
diff --git a/src/v8/tools/testrunner/local/testsuite_unittest.py b/src/v8/tools/testrunner/local/testsuite_unittest.py
index efefe4c..b74fef1 100755
--- a/src/v8/tools/testrunner/local/testsuite_unittest.py
+++ b/src/v8/tools/testrunner/local/testsuite_unittest.py
@@ -3,8 +3,10 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+import itertools
import os
import sys
+import tempfile
import unittest
# Needed because the test runner contains relative imports.
@@ -12,109 +14,72 @@
os.path.abspath(__file__))))
sys.path.append(TOOLS_PATH)
-from testrunner.local.testsuite import TestSuite
+from testrunner.local.testsuite import TestSuite, TestGenerator
from testrunner.objects.testcase import TestCase
+from testrunner.test_config import TestConfig
class TestSuiteTest(unittest.TestCase):
- def test_filter_testcases_by_status_first_pass(self):
- suite = TestSuite('foo', 'bar')
- suite.rules = {
- '': {
- 'foo/bar': set(['PASS', 'SKIP']),
- 'baz/bar': set(['PASS', 'FAIL']),
- },
- }
- suite.prefix_rules = {
- '': {
- 'baz/': set(['PASS', 'SLOW']),
- },
- }
- suite.tests = [
- TestCase(suite, 'foo/bar', 'foo/bar'),
- TestCase(suite, 'baz/bar', 'baz/bar'),
- ]
- suite.FilterTestCasesByStatus()
- self.assertEquals(
- [TestCase(suite, 'baz/bar', 'baz/bar')],
- suite.tests,
- )
- outcomes = suite.GetStatusFileOutcomes(suite.tests[0].name,
- suite.tests[0].variant)
- self.assertEquals(set(['PASS', 'FAIL', 'SLOW']), outcomes)
-
- def test_filter_testcases_by_status_second_pass(self):
- suite = TestSuite('foo', 'bar')
-
- suite.rules = {
- '': {
- 'foo/bar': set(['PREV']),
- },
- 'default': {
- 'foo/bar': set(['PASS', 'SKIP']),
- 'baz/bar': set(['PASS', 'FAIL']),
- },
- 'stress': {
- 'baz/bar': set(['SKIP']),
- },
- }
- suite.prefix_rules = {
- '': {
- 'baz/': set(['PREV']),
- },
- 'default': {
- 'baz/': set(['PASS', 'SLOW']),
- },
- 'stress': {
- 'foo/': set(['PASS', 'SLOW']),
- },
- }
-
- test1 = TestCase(suite, 'foo/bar', 'foo/bar')
- test2 = TestCase(suite, 'baz/bar', 'baz/bar')
- suite.tests = [
- test1.create_variant(variant='default', flags=[]),
- test1.create_variant(variant='stress', flags=['-v']),
- test2.create_variant(variant='default', flags=[]),
- test2.create_variant(variant='stress', flags=['-v']),
- ]
-
- suite.FilterTestCasesByStatus()
- self.assertEquals(
- [
- TestCase(suite, 'foo/bar', 'foo/bar').create_variant(None, ['-v']),
- TestCase(suite, 'baz/bar', 'baz/bar'),
- ],
- suite.tests,
+ def setUp(self):
+ test_dir = os.path.dirname(__file__)
+ self.test_root = os.path.join(test_dir, "fake_testsuite")
+ self.test_config = TestConfig(
+ command_prefix=[],
+ extra_flags=[],
+ isolates=False,
+ mode_flags=[],
+ no_harness=False,
+ noi18n=False,
+ random_seed=0,
+ run_skipped=False,
+ shell_dir='fake_testsuite/fake_d8',
+ timeout=10,
+ verbose=False,
)
- self.assertEquals(
- set(['PREV', 'PASS', 'SLOW']),
- suite.GetStatusFileOutcomes(suite.tests[0].name,
- suite.tests[0].variant),
- )
- self.assertEquals(
- set(['PREV', 'PASS', 'FAIL', 'SLOW']),
- suite.GetStatusFileOutcomes(suite.tests[1].name,
- suite.tests[1].variant),
- )
+ self.suite = TestSuite.Load(
+ self.test_root, self.test_config, "standard_runner")
- def test_fail_ok_outcome(self):
- suite = TestSuite('foo', 'bar')
- suite.rules = {
- '': {
- 'foo/bar': set(['FAIL_OK']),
- 'baz/bar': set(['FAIL']),
- },
- }
- suite.prefix_rules = {}
- suite.tests = [
- TestCase(suite, 'foo/bar', 'foo/bar'),
- TestCase(suite, 'baz/bar', 'baz/bar'),
- ]
+ def testLoadingTestSuites(self):
+ self.assertEquals(self.suite.name, "fake_testsuite")
+ self.assertEquals(self.suite.test_config, self.test_config)
- for t in suite.tests:
- self.assertEquals(['FAIL'], t.expected_outcomes)
+ # Verify that the components of the TestSuite aren't loaded yet.
+ self.assertIsNone(self.suite.tests)
+ self.assertIsNone(self.suite.statusfile)
+
+ def testLoadingTestsFromDisk(self):
+ tests = self.suite.load_tests_from_disk(
+ statusfile_variables={})
+ def is_generator(iterator):
+ return iterator == iter(iterator)
+
+ self.assertTrue(is_generator(tests))
+ self.assertEquals(tests.test_count_estimate, 2)
+
+ slow_tests, fast_tests = list(tests.slow_tests), list(tests.fast_tests)
+ # Verify that the components of the TestSuite are loaded.
+ self.assertTrue(len(slow_tests) == len(fast_tests) == 1)
+ self.assertTrue(all(test.is_slow for test in slow_tests))
+ self.assertFalse(any(test.is_slow for test in fast_tests))
+ self.assertIsNotNone(self.suite.statusfile)
+
+ def testMergingTestGenerators(self):
+ tests = self.suite.load_tests_from_disk(
+ statusfile_variables={})
+ more_tests = self.suite.load_tests_from_disk(
+ statusfile_variables={})
+
+ # Merge the test generators
+ tests.merge(more_tests)
+ self.assertEquals(tests.test_count_estimate, 4)
+
+ # Check the tests are sorted by speed
+ test_speeds = []
+ for test in tests:
+ test_speeds.append(test.is_slow)
+
+ self.assertEquals(test_speeds, [True, True, False, False])
if __name__ == '__main__':
diff --git a/src/v8/tools/testrunner/local/utils.py b/src/v8/tools/testrunner/local/utils.py
index bf8c3d9..9128c43 100644
--- a/src/v8/tools/testrunner/local/utils.py
+++ b/src/v8/tools/testrunner/local/utils.py
@@ -25,6 +25,8 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# for py2/py3 compatibility
+from __future__ import print_function
from os.path import exists
from os.path import isdir
@@ -36,6 +38,21 @@
import urllib2
+### Exit codes and their meaning.
+# Normal execution.
+EXIT_CODE_PASS = 0
+# Execution with test failures.
+EXIT_CODE_FAILURES = 1
+# Execution with no tests executed.
+EXIT_CODE_NO_TESTS = 2
+# Execution aborted with SIGINT (Ctrl-C).
+EXIT_CODE_INTERRUPTED = 3
+# Execution aborted with SIGTERM.
+EXIT_CODE_TERMINATED = 4
+# Internal error.
+EXIT_CODE_INTERNAL_ERROR = 5
+
+
def GetSuitePaths(test_root):
return [ f for f in os.listdir(test_root) if isdir(join(test_root, f)) ]
@@ -132,7 +149,7 @@
return
except:
# If there's no curl, fall back to urlopen.
- print "Curl is currently not installed. Falling back to python."
+ print("Curl is currently not installed. Falling back to python.")
pass
with open(destination, 'w') as f:
f.write(urllib2.urlopen(source).read())
diff --git a/src/v8/tools/testrunner/local/variants.py b/src/v8/tools/testrunner/local/variants.py
index f1e9ad3..4b0cf15 100644
--- a/src/v8/tools/testrunner/local/variants.py
+++ b/src/v8/tools/testrunner/local/variants.py
@@ -4,26 +4,57 @@
# Use this to run several variants of the tests.
ALL_VARIANT_FLAGS = {
+ "assert_types": [["--assert-types"]],
"code_serializer": [["--cache=code"]],
"default": [[]],
"future": [["--future"]],
+ "gc_stats": [["--gc-stats=1"]],
# Alias of exhaustive variants, but triggering new test framework features.
"infra_staging": [[]],
- "liftoff": [["--liftoff"]],
+ "interpreted_regexp": [["--regexp-interpret-all"]],
+ "jitless": [["--jitless"]],
"minor_mc": [["--minor-mc"]],
# No optimization means disable all optimizations. OptimizeFunctionOnNextCall
# would not force optimization too. It turns into a Nop. Please see
# https://chromium-review.googlesource.com/c/452620/ for more discussion.
- "nooptimization": [["--noopt"]],
+ # For WebAssembly, we test "Liftoff-only" in the nooptimization variant and
+ # "TurboFan-only" in the stress variant. The WebAssembly configuration is
+ # independent of JS optimizations, so we can combine those configs.
+ "nooptimization": [["--no-opt", "--liftoff", "--no-wasm-tier-up"]],
"slow_path": [["--force-slow-path"]],
- "stress": [["--stress-opt", "--always-opt"]],
- "stress_background_compile": [["--background-compile", "--stress-background-compile"]],
+ "stress": [["--stress-opt", "--always-opt", "--no-liftoff",
+ "--no-wasm-tier-up"]],
+ "stress_js_bg_compile_wasm_code_gc": [["--stress-background-compile",
+ "--wasm-code-gc",
+ "--stress-wasm-code-gc"]],
"stress_incremental_marking": [["--stress-incremental-marking"]],
# Trigger stress sampling allocation profiler with sample interval = 2^14
"stress_sampling": [["--stress-sampling-allocation-profiler=16384"]],
"trusted": [["--no-untrusted-code-mitigations"]],
- "wasm_traps": [["--wasm_trap_handler", "--invoke-weak-callbacks", "--wasm-jit-to-native"]],
- "wasm_no_native": [["--no-wasm-jit-to-native"]],
+ "no_wasm_traps": [["--no-wasm-trap-handler"]],
}
-ALL_VARIANTS = set(ALL_VARIANT_FLAGS.keys())
+SLOW_VARIANTS = set([
+ 'stress',
+ 'nooptimization',
+])
+
+FAST_VARIANTS = set([
+ 'default'
+])
+
+
+def _variant_order_key(v):
+ if v in SLOW_VARIANTS:
+ return 0
+ if v in FAST_VARIANTS:
+ return 100
+ return 50
+
+ALL_VARIANTS = sorted(ALL_VARIANT_FLAGS.keys(),
+ key=_variant_order_key)
+
+# Check {SLOW,FAST}_VARIANTS entries
+for variants in [SLOW_VARIANTS, FAST_VARIANTS]:
+ for v in variants:
+ assert v in ALL_VARIANT_FLAGS
diff --git a/src/v8/tools/testrunner/local/verbose.py b/src/v8/tools/testrunner/local/verbose.py
index 49e8085..8569368 100644
--- a/src/v8/tools/testrunner/local/verbose.py
+++ b/src/v8/tools/testrunner/local/verbose.py
@@ -25,6 +25,8 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# for py2/py3 compatibility
+from __future__ import print_function
import sys
import time
@@ -63,7 +65,7 @@
else:
assert False # Unreachable # TODO: check this in outcomes parsing phase.
- print REPORT_TEMPLATE % {
+ print(REPORT_TEMPLATE % {
"total": total,
"skipped": skipped,
"nocrash": nocrash,
@@ -71,17 +73,17 @@
"fail_ok": fail_ok,
"fail": fail,
"crash": crash,
- }
+ })
def PrintTestSource(tests):
for test in tests:
- print "--- begin source: %s ---" % test
+ print("--- begin source: %s ---" % test)
if test.is_source_available():
- print test.get_source()
+ print(test.get_source())
else:
- print '(no source available)'
- print "--- end source: %s ---" % test
+ print('(no source available)')
+ print("--- end source: %s ---" % test)
def FormatTime(d):
@@ -92,11 +94,11 @@
def PrintTestDurations(suites, outputs, overall_time):
# Write the times to stderr to make it easy to separate from the
# test output.
- print
+ print()
sys.stderr.write("--- Total time: %s ---\n" % FormatTime(overall_time))
timed_tests = [(t, outputs[t].duration) for s in suites for t in s.tests
if t in outputs]
- timed_tests.sort(key=lambda (_, duration): duration, reverse=True)
+ timed_tests.sort(key=lambda test_duration: test_duration[1], reverse=True)
index = 1
for test, duration in timed_tests[:20]:
t = FormatTime(duration)
diff --git a/src/v8/tools/testrunner/num_fuzzer.py b/src/v8/tools/testrunner/num_fuzzer.py
new file mode 100755
index 0000000..d4e92a6
--- /dev/null
+++ b/src/v8/tools/testrunner/num_fuzzer.py
@@ -0,0 +1,226 @@
+#!/usr/bin/env python
+#
+# Copyright 2017 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.
+
+# for py2/py3 compatibility
+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 testrunner.local import utils
+
+from testrunner.testproc import fuzzer
+from testrunner.testproc.base import TestProcProducer
+from testrunner.testproc.combiner import CombinerProc
+from testrunner.testproc.execution import ExecutionProc
+from testrunner.testproc.expectation import ForgiveTimeoutProc
+from testrunner.testproc.filter import StatusFileFilterProc, NameFilterProc
+from testrunner.testproc.loader import LoadProc
+from testrunner.testproc.progress import ResultsTracker
+from testrunner.utils import random_utils
+
+
+DEFAULT_SUITES = ["mjsunit", "webkit", "benchmarks"]
+
+
+class NumFuzzer(base_runner.BaseTestRunner):
+ def __init__(self, *args, **kwargs):
+ super(NumFuzzer, self).__init__(*args, **kwargs)
+
+ @property
+ def framework_name(self):
+ return 'num_fuzzer'
+
+ def _add_parser_options(self, parser):
+ parser.add_option("--fuzzer-random-seed", default=0,
+ help="Default seed for initializing fuzzer random "
+ "generator")
+ parser.add_option("--tests-count", default=5, type="int",
+ help="Number of tests to generate from each base test. "
+ "Can be combined with --total-timeout-sec with "
+ "value 0 to provide infinite number of subtests. "
+ "When --combine-tests is set it indicates how many "
+ "tests to create in total")
+
+ # Stress gc
+ parser.add_option("--stress-marking", default=0, type="int",
+ help="probability [0-10] of adding --stress-marking "
+ "flag to the test")
+ parser.add_option("--stress-scavenge", default=0, type="int",
+ help="probability [0-10] of adding --stress-scavenge "
+ "flag to the test")
+ parser.add_option("--stress-compaction", default=0, type="int",
+ help="probability [0-10] of adding --stress-compaction "
+ "flag to the test")
+ parser.add_option("--stress-gc", default=0, type="int",
+ help="probability [0-10] of adding --random-gc-interval "
+ "flag to the test")
+
+ # Stress tasks
+ parser.add_option("--stress-delay-tasks", default=0, type="int",
+ help="probability [0-10] of adding --stress-delay-tasks "
+ "flag to the test")
+ parser.add_option("--stress-thread-pool-size", default=0, type="int",
+ help="probability [0-10] of adding --thread-pool-size "
+ "flag to the test")
+
+ # Stress deopt
+ parser.add_option("--stress-deopt", default=0, type="int",
+ help="probability [0-10] of adding --deopt-every-n-times "
+ "flag to the test")
+ parser.add_option("--stress-deopt-min", default=1, type="int",
+ help="extends --stress-deopt to have minimum interval "
+ "between deopt points")
+
+ # Combine multiple tests
+ parser.add_option("--combine-tests", default=False, action="store_true",
+ help="Combine multiple tests as one and run with "
+ "try-catch wrapper")
+ parser.add_option("--combine-max", default=100, type="int",
+ help="Maximum number of tests to combine")
+ parser.add_option("--combine-min", default=2, type="int",
+ help="Minimum number of tests to combine")
+
+ # Miscellaneous
+ parser.add_option("--variants", default='default',
+ help="Comma-separated list of testing variants")
+
+ return parser
+
+
+ def _process_options(self, options):
+ if not options.fuzzer_random_seed:
+ options.fuzzer_random_seed = random_utils.random_seed()
+
+ if options.total_timeout_sec:
+ options.tests_count = 0
+
+ if options.combine_tests:
+ if options.combine_min > options.combine_max:
+ print(('min_group_size (%d) cannot be larger than max_group_size (%d)' %
+ options.min_group_size, options.max_group_size))
+ raise base_runner.TestRunnerError()
+
+ if options.variants != 'default':
+ print ('Only default testing variant is supported with numfuzz')
+ raise base_runner.TestRunnerError()
+
+ return True
+
+ def _get_default_suite_names(self):
+ return DEFAULT_SUITES
+
+ def _get_statusfile_variables(self, options):
+ variables = (
+ super(NumFuzzer, self)._get_statusfile_variables(options))
+ variables.update({
+ 'deopt_fuzzer': bool(options.stress_deopt),
+ 'endurance_fuzzer': bool(options.combine_tests),
+ 'gc_stress': bool(options.stress_gc),
+ 'gc_fuzzer': bool(max([options.stress_marking,
+ options.stress_scavenge,
+ options.stress_compaction,
+ options.stress_gc,
+ options.stress_delay_tasks,
+ options.stress_thread_pool_size])),
+ })
+ return variables
+
+ def _do_execute(self, tests, args, options):
+ loader = LoadProc(tests)
+ fuzzer_rng = random.Random(options.fuzzer_random_seed)
+
+ combiner = self._create_combiner(fuzzer_rng, options)
+ results = self._create_result_tracker(options)
+ execproc = ExecutionProc(options.j)
+ sigproc = self._create_signal_proc()
+ indicators = self._create_progress_indicators(
+ tests.test_count_estimate, options)
+ procs = [
+ loader,
+ NameFilterProc(args) if args else None,
+ StatusFileFilterProc(None, None),
+ # TODO(majeski): Improve sharding when combiner is present. Maybe select
+ # different random seeds for shards instead of splitting tests.
+ self._create_shard_proc(options),
+ ForgiveTimeoutProc(),
+ combiner,
+ self._create_fuzzer(fuzzer_rng, options),
+ sigproc,
+ ] + indicators + [
+ results,
+ self._create_timeout_proc(options),
+ self._create_rerun_proc(options),
+ execproc,
+ ]
+ self._prepare_procs(procs)
+ loader.load_initial_tests(initial_batch_size=float('inf'))
+
+ # TODO(majeski): maybe some notification from loader would be better?
+ if combiner:
+ combiner.generate_initial_tests(options.j * 4)
+
+ # This starts up worker processes and blocks until all tests are
+ # processed.
+ execproc.run()
+
+ for indicator in indicators:
+ indicator.finished()
+
+ print('>>> %d tests ran' % results.total)
+ if results.failed:
+ return utils.EXIT_CODE_FAILURES
+
+ # Indicate if a SIGINT or SIGTERM happened.
+ return sigproc.exit_code
+
+ def _is_testsuite_supported(self, suite, options):
+ return not options.combine_tests or suite.test_combiner_available()
+
+ def _create_combiner(self, rng, options):
+ if not options.combine_tests:
+ return None
+ return CombinerProc(rng, options.combine_min, options.combine_max,
+ options.tests_count)
+
+ def _create_fuzzer(self, rng, options):
+ return fuzzer.FuzzerProc(
+ rng,
+ self._tests_count(options),
+ self._create_fuzzer_configs(options),
+ self._disable_analysis(options),
+ )
+
+ def _tests_count(self, options):
+ if options.combine_tests:
+ return 1
+ return options.tests_count
+
+ def _disable_analysis(self, options):
+ """Disable analysis phase when options are used that don't support it."""
+ return options.combine_tests
+
+ def _create_fuzzer_configs(self, options):
+ fuzzers = []
+ def add(name, prob, *args):
+ if prob:
+ fuzzers.append(fuzzer.create_fuzzer_config(name, prob, *args))
+
+ add('compaction', options.stress_compaction)
+ add('marking', options.stress_marking)
+ add('scavenge', options.stress_scavenge)
+ add('gc_interval', options.stress_gc)
+ add('threads', options.stress_thread_pool_size)
+ add('delay', options.stress_delay_tasks)
+ add('deopt', options.stress_deopt, options.stress_deopt_min)
+ return fuzzers
+
+
+if __name__ == '__main__':
+ sys.exit(NumFuzzer().execute())
diff --git a/src/v8/tools/testrunner/objects/output.py b/src/v8/tools/testrunner/objects/output.py
index adc33c9..78aa63d 100644
--- a/src/v8/tools/testrunner/objects/output.py
+++ b/src/v8/tools/testrunner/objects/output.py
@@ -27,12 +27,15 @@
import signal
+import copy
from ..local import utils
+
class Output(object):
- def __init__(self, exit_code, timed_out, stdout, stderr, pid, duration):
+ def __init__(self, exit_code=0, timed_out=False, stdout=None, stderr=None,
+ pid=None, duration=None):
self.exit_code = exit_code
self.timed_out = timed_out
self.stdout = stdout
@@ -40,6 +43,13 @@
self.pid = pid
self.duration = duration
+ def without_text(self):
+ """Returns copy of the output without stdout and stderr."""
+ other = copy.copy(self)
+ other.stdout = None
+ other.stderr = None
+ return other
+
def HasCrashed(self):
if utils.IsWindows():
return 0x80000000 & self.exit_code and not (0x3FFFFF00 & self.exit_code)
@@ -52,3 +62,16 @@
def HasTimedOut(self):
return self.timed_out
+
+ def IsSuccess(self):
+ return not self.HasCrashed() and not self.HasTimedOut()
+
+
+class _NullOutput(Output):
+ """Useful to signal that the binary has not been run."""
+ def __init__(self):
+ super(_NullOutput, self).__init__()
+
+
+# Default instance of the _NullOutput class above.
+NULL_OUTPUT = _NullOutput()
diff --git a/src/v8/tools/testrunner/objects/predictable.py b/src/v8/tools/testrunner/objects/predictable.py
index ad93077..52d14ea 100644
--- a/src/v8/tools/testrunner/objects/predictable.py
+++ b/src/v8/tools/testrunner/objects/predictable.py
@@ -4,6 +4,7 @@
from ..local import statusfile
from ..outproc import base as outproc_base
+from ..testproc import base as testproc_base
from ..testproc.result import Result
@@ -15,11 +16,7 @@
def get_outproc(test):
- output_proc = test.output_proc
- if output_proc.negative or statusfile.FAIL in test.expected_outcomes:
- # TODO(majeski): Skip these tests instead of having special outproc.
- return NeverUnexpectedOutputOutProc(output_proc)
- return OutProc(output_proc)
+ return OutProc(test.output_proc)
class OutProc(outproc_base.BaseOutProc):
@@ -31,9 +28,6 @@
super(OutProc, self).__init__()
self._outproc = _outproc
- def process(self, output):
- return Result(self.has_unexpected_output(output), output)
-
def has_unexpected_output(self, output):
return output.exit_code != 0
@@ -49,9 +43,6 @@
return self._outproc.expected_outcomes
-class NeverUnexpectedOutputOutProc(OutProc):
- """Output processor wrapper for tests that we will return False for
- has_unexpected_output in the predictable mode.
- """
- def has_unexpected_output(self, output):
- return False
+class PredictableFilterProc(testproc_base.TestProcFilter):
+ def _filter(self, test):
+ return test.skip_predictable()
diff --git a/src/v8/tools/testrunner/objects/testcase.py b/src/v8/tools/testrunner/objects/testcase.py
index 06db328..6d4dcd1 100644
--- a/src/v8/tools/testrunner/objects/testcase.py
+++ b/src/v8/tools/testrunner/objects/testcase.py
@@ -37,10 +37,33 @@
FLAGS_PATTERN = re.compile(r"//\s+Flags:(.*)")
+# Patterns for additional resource files on Android. Files that are not covered
+# by one of the other patterns below will be specified in the resources section.
+RESOURCES_PATTERN = re.compile(r"//\s+Resources:(.*)")
+# Pattern to auto-detect files to push on Android for statements like:
+# load("path/to/file.js")
+LOAD_PATTERN = re.compile(
+ r"(?:load|readbuffer|read)\((?:'|\")([^'\"]+)(?:'|\")\)")
+# Pattern to auto-detect files to push on Android for statements like:
+# import "path/to/file.js"
+MODULE_RESOURCES_PATTERN_1 = re.compile(
+ r"(?:import|export)(?:\(| )(?:'|\")([^'\"]+)(?:'|\")")
+# Pattern to auto-detect files to push on Android for statements like:
+# import foobar from "path/to/file.js"
+MODULE_RESOURCES_PATTERN_2 = re.compile(
+ r"(?:import|export).*from (?:'|\")([^'\"]+)(?:'|\")")
+
+TIMEOUT_LONG = "long"
+
+try:
+ cmp # Python 2
+except NameError:
+ def cmp(x, y): # Python 3
+ return (x > y) - (x < y)
class TestCase(object):
- def __init__(self, suite, path, name):
+ def __init__(self, suite, path, name, test_config):
self.suite = suite # TestSuite object
self.path = path # string, e.g. 'div-mod', 'test-api/foo'
@@ -49,60 +72,40 @@
self.variant = None # name of the used testing variant
self.variant_flags = [] # list of strings, flags specific to this test
- self.id = None # int, used to map result back to TestCase instance
- self.run = 1 # The nth time this test is executed.
- self.cmd = None
-
# Fields used by the test processors.
self.origin = None # Test that this test is subtest of.
self.processor = None # Processor that created this subtest.
self.procid = '%s/%s' % (self.suite.name, self.name) # unique id
self.keep_output = False # Can output of this test be dropped
+ # Test config contains information needed to build the command.
+ self._test_config = test_config
+ self._random_seed = None # Overrides test config value if not None
+
+ # Outcomes
self._statusfile_outcomes = None
- self._expected_outcomes = None # optimization: None == [statusfile.PASS]
+ self.expected_outcomes = None
self._statusfile_flags = None
+
self._prepare_outcomes()
def create_subtest(self, processor, subtest_id, variant=None, flags=None,
- keep_output=False):
+ keep_output=False, random_seed=None):
subtest = copy.copy(self)
subtest.origin = self
subtest.processor = processor
subtest.procid += '.%s' % subtest_id
- subtest.keep_output = keep_output
+ subtest.keep_output |= keep_output
+ if random_seed:
+ subtest._random_seed = random_seed
+ if flags:
+ subtest.variant_flags = subtest.variant_flags + flags
if variant is not None:
assert self.variant is None
subtest.variant = variant
- subtest.variant_flags = flags
subtest._prepare_outcomes()
return subtest
- def create_variant(self, variant, flags, procid_suffix=None):
- """Makes a shallow copy of the object and updates variant, variant flags and
- all fields that depend on it, e.g. expected outcomes.
-
- Args
- variant - variant name
- flags - flags that should be added to origin test's variant flags
- procid_suffix - for multiple variants with the same name set suffix to
- keep procid unique.
- """
- other = copy.copy(self)
- if not self.variant_flags:
- other.variant_flags = flags
- else:
- other.variant_flags = self.variant_flags + flags
- other.variant = variant
- if procid_suffix:
- other.procid += '[%s-%s]' % (variant, procid_suffix)
- else:
- other.procid += '[%s]' % variant
-
- other._prepare_outcomes(variant != self.variant)
-
- return other
-
def _prepare_outcomes(self, force_update=True):
if force_update or self._statusfile_outcomes is None:
def is_flag(outcome):
@@ -140,7 +143,8 @@
@property
def do_skip(self):
- return statusfile.SKIP in self._statusfile_outcomes
+ return (statusfile.SKIP in self._statusfile_outcomes and
+ not self.suite.test_config.run_skipped)
@property
def is_slow(self):
@@ -160,43 +164,59 @@
def only_standard_variant(self):
return statusfile.NO_VARIANTS in self._statusfile_outcomes
- def get_command(self, context):
- params = self._get_cmd_params(context)
+ def get_command(self):
+ params = self._get_cmd_params()
env = self._get_cmd_env()
- shell, shell_flags = self._get_shell_with_flags(context)
- timeout = self._get_timeout(params, context.timeout)
- return self._create_cmd(shell, shell_flags + params, env, timeout, context)
+ shell = self.get_shell()
+ if utils.IsWindows():
+ shell += '.exe'
+ shell_flags = self._get_shell_flags()
+ timeout = self._get_timeout(params)
+ return self._create_cmd(shell, shell_flags + params, env, timeout)
- def _get_cmd_params(self, ctx):
+ def _get_cmd_params(self):
"""Gets command parameters and combines them in the following order:
- files [empty by default]
+ - random seed
- extra flags (from command line)
- user flags (variant/fuzzer flags)
- - statusfile flags
- mode flags (based on chosen mode)
- source flags (from source code) [empty by default]
+ - test-suite flags
+ - statusfile flags
The best way to modify how parameters are created is to only override
methods for getting partial parameters.
"""
return (
- self._get_files_params(ctx) +
- self._get_extra_flags(ctx) +
+ self._get_files_params() +
+ self._get_random_seed_flags() +
+ self._get_extra_flags() +
self._get_variant_flags() +
- self._get_statusfile_flags() +
- self._get_mode_flags(ctx) +
+ self._get_mode_flags() +
self._get_source_flags() +
- self._get_suite_flags(ctx)
+ self._get_suite_flags() +
+ self._get_statusfile_flags()
)
def _get_cmd_env(self):
return {}
- def _get_files_params(self, ctx):
+ def _get_files_params(self):
return []
- def _get_extra_flags(self, ctx):
- return ctx.extra_flags
+ def _get_timeout_param(self):
+ return None
+
+ def _get_random_seed_flags(self):
+ return ['--random-seed=%d' % self.random_seed]
+
+ @property
+ def random_seed(self):
+ return self._random_seed or self._test_config.random_seed
+
+ def _get_extra_flags(self):
+ return self._test_config.extra_flags
def _get_variant_flags(self):
return self.variant_flags
@@ -208,50 +228,49 @@
"""
return self._statusfile_flags
- def _get_mode_flags(self, ctx):
- return ctx.mode_flags
+ def _get_mode_flags(self):
+ return self._test_config.mode_flags
def _get_source_flags(self):
return []
- def _get_suite_flags(self, ctx):
+ def _get_suite_flags(self):
return []
- def _get_shell_with_flags(self, ctx):
- shell = self.get_shell()
- shell_flags = []
- if shell == 'd8':
- shell_flags.append('--test')
- if utils.IsWindows():
- shell += '.exe'
- if ctx.random_seed:
- shell_flags.append('--random-seed=%s' % ctx.random_seed)
- return shell, shell_flags
+ def _get_shell_flags(self):
+ return []
- def _get_timeout(self, params, timeout):
+ def _get_timeout(self, params):
+ timeout = self._test_config.timeout
if "--stress-opt" in params:
timeout *= 4
+ if "--jitless" in params:
+ timeout *= 2
+ if "--no-opt" in params:
+ timeout *= 2
if "--noenable-vfp3" in params:
timeout *= 2
-
- # TODO(majeski): make it slow outcome dependent.
- timeout *= 2
+ if self._get_timeout_param() == TIMEOUT_LONG:
+ timeout *= 10
+ if self.is_slow:
+ timeout *= 4
return timeout
def get_shell(self):
- return 'd8'
+ raise NotImplementedError()
def _get_suffix(self):
return '.js'
- def _create_cmd(self, shell, params, env, timeout, ctx):
+ def _create_cmd(self, shell, params, env, timeout):
return command.Command(
- cmd_prefix=ctx.command_prefix,
- shell=os.path.abspath(os.path.join(ctx.shell_dir, shell)),
+ cmd_prefix=self._test_config.command_prefix,
+ shell=os.path.abspath(os.path.join(self._test_config.shell_dir, shell)),
args=params,
env=env,
timeout=timeout,
- verbose=ctx.verbose
+ verbose=self._test_config.verbose,
+ resources_func=self._get_resources,
)
def _parse_source_flags(self, source=None):
@@ -271,6 +290,18 @@
def _get_source_path(self):
return None
+ def _get_resources(self):
+ """Returns a list of absolute paths with additional files needed by the
+ test case.
+
+ Used to push additional files to Android devices.
+ """
+ return []
+
+ def skip_predictable(self):
+ """Returns True if the test case is not suitable for predictable testing."""
+ return True
+
@property
def output_proc(self):
if self.expected_outcomes is outproc.OUTCOMES_PASS:
@@ -281,18 +312,63 @@
# Make sure that test cases are sorted correctly if sorted without
# key function. But using a key function is preferred for speed.
return cmp(
- (self.suite.name, self.name, self.variant_flags),
- (other.suite.name, other.name, other.variant_flags)
+ (self.suite.name, self.name, self.variant),
+ (other.suite.name, other.name, other.variant)
)
- def __hash__(self):
- return hash((self.suite.name, self.name, ''.join(self.variant_flags)))
-
def __str__(self):
return self.suite.name + '/' + self.name
- # TODO(majeski): Rename `id` field or `get_id` function since they're
- # unrelated.
- def get_id(self):
- return '%s/%s %s' % (
- self.suite.name, self.name, ' '.join(self.variant_flags))
+
+class D8TestCase(TestCase):
+ def get_shell(self):
+ return "d8"
+
+ def _get_shell_flags(self):
+ return ['--test']
+
+ def _get_resources_for_file(self, file):
+ """Returns for a given file a list of absolute paths of files needed by the
+ given file.
+ """
+ with open(file) as f:
+ source = f.read()
+ result = []
+ def add_path(path):
+ result.append(os.path.abspath(path.replace('/', os.path.sep)))
+ for match in RESOURCES_PATTERN.finditer(source):
+ # There are several resources per line. Relative to base dir.
+ for path in match.group(1).strip().split():
+ add_path(path)
+ for match in LOAD_PATTERN.finditer(source):
+ # Files in load statements are relative to base dir.
+ add_path(match.group(1))
+ for match in MODULE_RESOURCES_PATTERN_1.finditer(source):
+ # Imported files are relative to the file importing them.
+ add_path(os.path.join(os.path.dirname(file), match.group(1)))
+ for match in MODULE_RESOURCES_PATTERN_2.finditer(source):
+ # Imported files are relative to the file importing them.
+ add_path(os.path.join(os.path.dirname(file), match.group(1)))
+ return result
+
+ def _get_resources(self):
+ """Returns the list of files needed by a test case."""
+ if not self._get_source_path():
+ return []
+ result = set()
+ to_check = [self._get_source_path()]
+ # Recurse over all files until reaching a fixpoint.
+ while to_check:
+ next_resource = to_check.pop()
+ result.add(next_resource)
+ for resource in self._get_resources_for_file(next_resource):
+ # Only add files that exist on disc. The pattens we check for give some
+ # false positives otherwise.
+ if resource not in result and os.path.exists(resource):
+ to_check.append(resource)
+ return sorted(list(result))
+
+ def skip_predictable(self):
+ """Returns True if the test case is not suitable for predictable testing."""
+ return (statusfile.FAIL in self.expected_outcomes or
+ self.output_proc.negative)
diff --git a/src/v8/tools/testrunner/outproc/base.py b/src/v8/tools/testrunner/outproc/base.py
index 9a9db4e..39efb60 100644
--- a/src/v8/tools/testrunner/outproc/base.py
+++ b/src/v8/tools/testrunner/outproc/base.py
@@ -2,24 +2,45 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-import collections
import itertools
+from ..testproc.base import (
+ DROP_RESULT, DROP_OUTPUT, DROP_PASS_OUTPUT, DROP_PASS_STDOUT)
from ..local import statusfile
from ..testproc.result import Result
OUTCOMES_PASS = [statusfile.PASS]
OUTCOMES_FAIL = [statusfile.FAIL]
+OUTCOMES_PASS_OR_TIMEOUT = [statusfile.PASS, statusfile.TIMEOUT]
+OUTCOMES_FAIL_OR_TIMEOUT = [statusfile.FAIL, statusfile.TIMEOUT]
class BaseOutProc(object):
- def process(self, output):
- return Result(self.has_unexpected_output(output), output)
+ def process(self, output, reduction=None):
+ has_unexpected_output = self.has_unexpected_output(output)
+ return self._create_result(has_unexpected_output, output, reduction)
def has_unexpected_output(self, output):
return self.get_outcome(output) not in self.expected_outcomes
+ def _create_result(self, has_unexpected_output, output, reduction):
+ """Creates Result instance. When reduction is passed it tries to drop some
+ parts of the result to save memory and time needed to send the result
+ across process boundary. None disables reduction and full result is created.
+ """
+ if reduction == DROP_RESULT:
+ return None
+ if reduction == DROP_OUTPUT:
+ return Result(has_unexpected_output, None)
+ if not has_unexpected_output:
+ if reduction == DROP_PASS_OUTPUT:
+ return Result(has_unexpected_output, None)
+ if reduction == DROP_PASS_STDOUT:
+ return Result(has_unexpected_output, output.without_text())
+
+ return Result(has_unexpected_output, output)
+
def get_outcome(self, output):
if output.HasCrashed():
return statusfile.CRASH
@@ -64,6 +85,11 @@
return OUTCOMES_PASS
+class NegPassOutProc(Negative, PassOutProc):
+ """Output processor optimized for negative tests expected to PASS"""
+ pass
+
+
class OutProc(BaseOutProc):
"""Output processor optimized for positive tests with expected outcomes
different than a single PASS.
@@ -92,6 +118,7 @@
# TODO(majeski): Override __reduce__ to make it deserialize as one instance.
DEFAULT = PassOutProc()
+DEFAULT_NEGATIVE = NegPassOutProc()
class ExpectedOutProc(OutProc):
diff --git a/src/v8/tools/testrunner/outproc/message.py b/src/v8/tools/testrunner/outproc/message.py
index bbfc1cd..f196cfd 100644
--- a/src/v8/tools/testrunner/outproc/message.py
+++ b/src/v8/tools/testrunner/outproc/message.py
@@ -32,8 +32,15 @@
if len(expected_lines) != len(actual_lines):
return True
+ # Try .js first, and fall back to .mjs.
+ # TODO(v8:9406): clean this up by never separating the path from
+ # the extension in the first place.
+ base_path = self._basepath + '.js'
+ if not os.path.exists(base_path):
+ base_path = self._basepath + '.mjs'
+
env = {
- 'basename': os.path.basename(self._basepath + '.js'),
+ 'basename': os.path.basename(base_path),
}
for (expected, actual) in itertools.izip_longest(
expected_lines, actual_lines, fillvalue=''):
diff --git a/src/v8/tools/testrunner/outproc/mkgrokdump.py b/src/v8/tools/testrunner/outproc/mkgrokdump.py
index 8efde12..4013023 100644
--- a/src/v8/tools/testrunner/outproc/mkgrokdump.py
+++ b/src/v8/tools/testrunner/outproc/mkgrokdump.py
@@ -20,7 +20,7 @@
diff = difflib.unified_diff(expected_lines, actual_lines, lineterm="",
fromfile="expected_path")
diffstring = '\n'.join(diff)
- if diffstring is not "":
+ if diffstring != "":
if "generated from a non-shipping build" in output.stdout:
return False
if not "generated from a shipping build" in output.stdout:
diff --git a/src/v8/tools/testrunner/outproc/test262.py b/src/v8/tools/testrunner/outproc/test262.py
index b5eb554..bf3bc05 100644
--- a/src/v8/tools/testrunner/outproc/test262.py
+++ b/src/v8/tools/testrunner/outproc/test262.py
@@ -7,18 +7,29 @@
from . import base
+def _is_failure_output(output):
+ return (
+ output.exit_code != 0 or
+ 'FAILED!' in output.stdout
+ )
+
+
class ExceptionOutProc(base.OutProc):
"""Output processor for tests with expected exception."""
- def __init__(self, expected_outcomes, expected_exception=None):
+ def __init__(
+ self, expected_outcomes, expected_exception=None, negative=False):
super(ExceptionOutProc, self).__init__(expected_outcomes)
self._expected_exception = expected_exception
+ self._negative = negative
+
+ @property
+ def negative(self):
+ return self._negative
def _is_failure_output(self, output):
- if output.exit_code != 0:
- return True
if self._expected_exception != self._parse_exception(output.stdout):
return True
- return 'FAILED!' in output.stdout
+ return _is_failure_output(output)
def _parse_exception(self, string):
# somefile:somelinenumber: someerror[: sometext]
@@ -31,16 +42,13 @@
return None
-def _is_failure_output(self, output):
- return (
- output.exit_code != 0 or
- 'FAILED!' in output.stdout
- )
-
-
class NoExceptionOutProc(base.OutProc):
"""Output processor optimized for tests without expected exception."""
-NoExceptionOutProc._is_failure_output = _is_failure_output
+ def __init__(self, expected_outcomes):
+ super(NoExceptionOutProc, self).__init__(expected_outcomes)
+
+ def _is_failure_output(self, output):
+ return _is_failure_output(output)
class PassNoExceptionOutProc(base.PassOutProc):
@@ -48,7 +56,8 @@
Output processor optimized for tests expected to PASS without expected
exception.
"""
-PassNoExceptionOutProc._is_failure_output = _is_failure_output
+ def _is_failure_output(self, output):
+ return _is_failure_output(output)
PASS_NO_EXCEPTION = PassNoExceptionOutProc()
diff --git a/src/v8/tools/testrunner/standard_runner.py b/src/v8/tools/testrunner/standard_runner.py
index 3be2099..51e7860 100755
--- a/src/v8/tools/testrunner/standard_runner.py
+++ b/src/v8/tools/testrunner/standard_runner.py
@@ -4,595 +4,356 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+# for py2/py3 compatibility
+from __future__ import print_function
+from functools import reduce
-from collections import OrderedDict
-from os.path import join
-import multiprocessing
import os
-import random
-import shlex
-import subprocess
import sys
-import time
# Adds testrunner to the path hence it has to be imported at the beggining.
import base_runner
-from testrunner.local import execution
-from testrunner.local import progress
-from testrunner.local import testsuite
from testrunner.local import utils
-from testrunner.local import verbose
from testrunner.local.variants import ALL_VARIANTS
-from testrunner.objects import context
from testrunner.objects import predictable
from testrunner.testproc.execution import ExecutionProc
from testrunner.testproc.filter import StatusFileFilterProc, NameFilterProc
from testrunner.testproc.loader import LoadProc
-from testrunner.testproc.progress import (VerboseProgressIndicator,
- ResultsTracker,
- TestsCounter)
-from testrunner.testproc.rerun import RerunProc
+from testrunner.testproc.seed import SeedProc
from testrunner.testproc.variant import VariantProc
-TIMEOUT_DEFAULT = 60
+ARCH_GUESS = utils.DefaultArch()
-# Variants ordered by expected runtime (slowest first).
-VARIANTS = ["default"]
+VARIANTS = ['default']
MORE_VARIANTS = [
- "stress",
- "stress_incremental_marking",
- "nooptimization",
- "stress_background_compile",
- "wasm_traps",
+ 'jitless',
+ 'stress',
+ 'stress_js_bg_compile_wasm_code_gc',
+ 'stress_incremental_marking',
]
VARIANT_ALIASES = {
# The default for developer workstations.
- "dev": VARIANTS,
+ 'dev': VARIANTS,
# Additional variants, run on all bots.
- "more": MORE_VARIANTS,
- # Shortcut for the two above ("more" first - it has the longer running tests).
- "exhaustive": MORE_VARIANTS + VARIANTS,
+ 'more': MORE_VARIANTS,
+ # Shortcut for the two above ('more' first - it has the longer running tests)
+ 'exhaustive': MORE_VARIANTS + VARIANTS,
# Additional variants, run on a subset of bots.
- "extra": ["future", "liftoff", "trusted"],
+ 'extra': ['nooptimization', 'future', 'no_wasm_traps'],
}
-GC_STRESS_FLAGS = ["--gc-interval=500", "--stress-compaction",
- "--concurrent-recompilation-queue-length=64",
- "--concurrent-recompilation-delay=500",
- "--concurrent-recompilation"]
+GC_STRESS_FLAGS = ['--gc-interval=500', '--stress-compaction',
+ '--concurrent-recompilation-queue-length=64',
+ '--concurrent-recompilation-delay=500',
+ '--concurrent-recompilation',
+ '--stress-flush-bytecode',
+ '--wasm-code-gc', '--stress-wasm-code-gc']
-# Double the timeout for these:
-SLOW_ARCHS = ["arm",
- "mips",
- "mipsel",
- "mips64",
- "mips64el",
- "s390",
- "s390x",
- "arm64"]
+RANDOM_GC_STRESS_FLAGS = ['--random-gc-interval=5000',
+ '--stress-compaction-random']
+
PREDICTABLE_WRAPPER = os.path.join(
base_runner.BASE_DIR, 'tools', 'predictable_wrapper.py')
class StandardTestRunner(base_runner.BaseTestRunner):
- def __init__(self, *args, **kwargs):
- super(StandardTestRunner, self).__init__(*args, **kwargs)
+ def __init__(self, *args, **kwargs):
+ super(StandardTestRunner, self).__init__(*args, **kwargs)
- self.sancov_dir = None
+ self.sancov_dir = None
+ self._variants = None
- def _get_default_suite_names(self):
- return ['default']
+ @property
+ def framework_name(self):
+ return 'standard_runner'
- def _do_execute(self, suites, args, options):
- if options.swarming:
- # Swarming doesn't print how isolated commands are called. Lets make
- # this less cryptic by printing it ourselves.
- print ' '.join(sys.argv)
+ def _get_default_suite_names(self):
+ return ['default']
- if utils.GuessOS() == "macos":
- # TODO(machenbach): Temporary output for investigating hanging test
- # driver on mac.
- print "V8 related processes running on this host:"
- try:
- print subprocess.check_output(
- "ps -e | egrep 'd8|cctest|unittests'", shell=True)
- except Exception:
- pass
+ def _add_parser_options(self, parser):
+ parser.add_option('--novfp3',
+ help='Indicates that V8 was compiled without VFP3'
+ ' support',
+ default=False, action='store_true')
- return self._execute(args, options, suites)
+ # Variants
+ parser.add_option('--no-variants', '--novariants',
+ help='Deprecated. '
+ 'Equivalent to passing --variants=default',
+ default=False, dest='no_variants', action='store_true')
+ parser.add_option('--variants',
+ help='Comma-separated list of testing variants;'
+ ' default: "%s"' % ','.join(VARIANTS))
+ parser.add_option('--exhaustive-variants',
+ default=False, action='store_true',
+ help='Deprecated. '
+ 'Equivalent to passing --variants=exhaustive')
- def _add_parser_options(self, parser):
- parser.add_option("--sancov-dir",
- help="Directory where to collect coverage data")
- parser.add_option("--cfi-vptr",
- help="Run tests with UBSAN cfi_vptr option.",
- default=False, action="store_true")
- parser.add_option("--novfp3",
- help="Indicates that V8 was compiled without VFP3"
- " support",
- default=False, action="store_true")
- parser.add_option("--cat", help="Print the source of the tests",
- default=False, action="store_true")
- parser.add_option("--slow-tests",
- help="Regard slow tests (run|skip|dontcare)",
- default="dontcare")
- parser.add_option("--pass-fail-tests",
- help="Regard pass|fail tests (run|skip|dontcare)",
- default="dontcare")
- parser.add_option("--gc-stress",
- help="Switch on GC stress mode",
- default=False, action="store_true")
- parser.add_option("--command-prefix",
- help="Prepended to each shell command used to run a"
- " test",
- default="")
- parser.add_option("--extra-flags",
- help="Additional flags to pass to each test command",
- action="append", default=[])
- parser.add_option("--infra-staging", help="Use new test runner features",
- default=False, action="store_true")
- parser.add_option("--isolates", help="Whether to test isolates",
- default=False, action="store_true")
- parser.add_option("-j", help="The number of parallel tasks to run",
- default=0, type="int")
- parser.add_option("--no-harness", "--noharness",
- help="Run without test harness of a given suite",
- default=False, action="store_true")
- parser.add_option("--no-presubmit", "--nopresubmit",
- help='Skip presubmit checks (deprecated)',
- default=False, dest="no_presubmit", action="store_true")
- parser.add_option("--no-sorting", "--nosorting",
- help="Don't sort tests according to duration of last"
- " run.",
- default=False, dest="no_sorting", action="store_true")
- parser.add_option("--no-variants", "--novariants",
- help="Deprecated. "
- "Equivalent to passing --variants=default",
- default=False, dest="no_variants", action="store_true")
- parser.add_option("--variants",
- help="Comma-separated list of testing variants;"
- " default: \"%s\"" % ",".join(VARIANTS))
- parser.add_option("--exhaustive-variants",
- default=False, action="store_true",
- help="Deprecated. "
- "Equivalent to passing --variants=exhaustive")
- parser.add_option("-p", "--progress",
- help=("The style of progress indicator"
- " (verbose, dots, color, mono)"),
- choices=progress.PROGRESS_INDICATORS.keys(),
- default="mono")
- parser.add_option("--quickcheck", default=False, action="store_true",
- help=("Quick check mode (skip slow tests)"))
- parser.add_option("--report", help="Print a summary of the tests to be"
- " run",
- default=False, action="store_true")
- parser.add_option("--json-test-results",
- help="Path to a file for storing json results.")
- parser.add_option("--flakiness-results",
- help="Path to a file for storing flakiness json.")
- parser.add_option("--rerun-failures-count",
- help=("Number of times to rerun each failing test case."
- " Very slow tests will be rerun only once."),
- default=0, type="int")
- parser.add_option("--rerun-failures-max",
- help="Maximum number of failing test cases to rerun.",
- default=100, type="int")
- parser.add_option("--dont-skip-slow-simulator-tests",
- help="Don't skip more slow tests when using a"
- " simulator.",
- default=False, action="store_true",
- dest="dont_skip_simulator_slow_tests")
- parser.add_option("--swarming",
- help="Indicates running test driver on swarming.",
- default=False, action="store_true")
- parser.add_option("--time", help="Print timing information after running",
- default=False, action="store_true")
- parser.add_option("-t", "--timeout", help="Timeout in seconds",
- default=TIMEOUT_DEFAULT, type="int")
- parser.add_option("--warn-unused", help="Report unused rules",
- default=False, action="store_true")
- parser.add_option("--junitout", help="File name of the JUnit output")
- parser.add_option("--junittestsuite",
- help="The testsuite name in the JUnit output file",
- default="v8tests")
- parser.add_option("--random-seed", default=0, dest="random_seed",
- help="Default seed for initializing random generator",
- type=int)
- parser.add_option("--random-seed-stress-count", default=1, type="int",
- dest="random_seed_stress_count",
- help="Number of runs with different random seeds")
+ # Filters
+ parser.add_option('--slow-tests', default='dontcare',
+ help='Regard slow tests (run|skip|dontcare)')
+ parser.add_option('--pass-fail-tests', default='dontcare',
+ help='Regard pass|fail tests (run|skip|dontcare)')
+ parser.add_option('--quickcheck', default=False, action='store_true',
+ help=('Quick check mode (skip slow tests)'))
+ parser.add_option('--dont-skip-slow-simulator-tests',
+ help='Don\'t skip more slow tests when using a'
+ ' simulator.',
+ default=False, action='store_true',
+ dest='dont_skip_simulator_slow_tests')
- def _process_options(self, options):
- global VARIANTS
+ # Stress modes
+ parser.add_option('--gc-stress',
+ help='Switch on GC stress mode',
+ default=False, action='store_true')
+ parser.add_option('--random-gc-stress',
+ help='Switch on random GC stress mode',
+ default=False, action='store_true')
+ parser.add_option('--random-seed-stress-count', default=1, type='int',
+ dest='random_seed_stress_count',
+ help='Number of runs with different random seeds. Only '
+ 'with test processors: 0 means infinite '
+ 'generation.')
- if options.sancov_dir:
- self.sancov_dir = options.sancov_dir
- if not os.path.exists(self.sancov_dir):
- print("sancov-dir %s doesn't exist" % self.sancov_dir)
- raise base_runner.TestRunnerError()
+ # Noop
+ parser.add_option('--cfi-vptr',
+ help='Run tests with UBSAN cfi_vptr option.',
+ default=False, action='store_true')
+ parser.add_option('--infra-staging', help='Use new test runner features',
+ dest='infra_staging', default=None,
+ action='store_true')
+ parser.add_option('--no-infra-staging',
+ help='Opt out of new test runner features',
+ dest='infra_staging', default=None,
+ action='store_false')
+ parser.add_option('--no-sorting', '--nosorting',
+ help='Don\'t sort tests according to duration of last'
+ ' run.',
+ default=False, dest='no_sorting', action='store_true')
+ parser.add_option('--no-presubmit', '--nopresubmit',
+ help='Skip presubmit checks (deprecated)',
+ default=False, dest='no_presubmit', action='store_true')
- options.command_prefix = shlex.split(options.command_prefix)
- options.extra_flags = sum(map(shlex.split, options.extra_flags), [])
+ # Unimplemented for test processors
+ parser.add_option('--sancov-dir',
+ help='Directory where to collect coverage data')
+ parser.add_option('--cat', help='Print the source of the tests',
+ default=False, action='store_true')
+ parser.add_option('--flakiness-results',
+ help='Path to a file for storing flakiness json.')
+ parser.add_option('--time', help='Print timing information after running',
+ default=False, action='store_true')
+ parser.add_option('--warn-unused', help='Report unused rules',
+ default=False, action='store_true')
+ parser.add_option('--report', default=False, action='store_true',
+ help='Print a summary of the tests to be run')
- if options.gc_stress:
- options.extra_flags += GC_STRESS_FLAGS
-
- if self.build_config.asan:
- options.extra_flags.append("--invoke-weak-callbacks")
- options.extra_flags.append("--omit-quit")
-
- if options.novfp3:
- options.extra_flags.append("--noenable-vfp3")
-
- if options.no_variants: # pragma: no cover
- print ("Option --no-variants is deprecated. "
- "Pass --variants=default instead.")
- assert not options.variants
- options.variants = "default"
-
- if options.exhaustive_variants: # pragma: no cover
- # TODO(machenbach): Switch infra to --variants=exhaustive after M65.
- print ("Option --exhaustive-variants is deprecated. "
- "Pass --variants=exhaustive instead.")
- # This is used on many bots. It includes a larger set of default
- # variants.
- # Other options for manipulating variants still apply afterwards.
- assert not options.variants
- options.variants = "exhaustive"
-
- if options.quickcheck:
- assert not options.variants
- options.variants = "stress,default"
- options.slow_tests = "skip"
- options.pass_fail_tests = "skip"
-
- if self.build_config.predictable:
- options.variants = "default"
- options.extra_flags.append("--predictable")
- options.extra_flags.append("--verify_predictable")
- options.extra_flags.append("--no-inline-new")
- # Add predictable wrapper to command prefix.
- options.command_prefix = (
- [sys.executable, PREDICTABLE_WRAPPER] + options.command_prefix)
-
- # TODO(machenbach): Figure out how to test a bigger subset of variants on
- # msan.
- if self.build_config.msan:
- options.variants = "default"
-
- if options.j == 0:
- options.j = multiprocessing.cpu_count()
-
- if options.random_seed_stress_count <= 1 and options.random_seed == 0:
- options.random_seed = self._random_seed()
-
- # Use developer defaults if no variant was specified.
- options.variants = options.variants or "dev"
-
- if options.variants == "infra_staging":
- options.variants = "exhaustive"
- options.infra_staging = True
-
- # Resolve variant aliases and dedupe.
- # TODO(machenbach): Don't mutate global variable. Rather pass mutated
- # version as local variable.
- VARIANTS = list(set(reduce(
- list.__add__,
- (VARIANT_ALIASES.get(v, [v]) for v in options.variants.split(",")),
- [],
- )))
-
- if not set(VARIANTS).issubset(ALL_VARIANTS):
- print "All variants must be in %s" % str(ALL_VARIANTS)
+ def _process_options(self, options):
+ if options.sancov_dir:
+ self.sancov_dir = options.sancov_dir
+ if not os.path.exists(self.sancov_dir):
+ print('sancov-dir %s doesn\'t exist' % self.sancov_dir)
raise base_runner.TestRunnerError()
- def CheckTestMode(name, option): # pragma: no cover
- if not option in ["run", "skip", "dontcare"]:
- print "Unknown %s mode %s" % (name, option)
- raise base_runner.TestRunnerError()
- CheckTestMode("slow test", options.slow_tests)
- CheckTestMode("pass|fail test", options.pass_fail_tests)
- if self.build_config.no_i18n:
- base_runner.TEST_MAP["bot_default"].remove("intl")
- base_runner.TEST_MAP["default"].remove("intl")
- # TODO(machenbach): uncomment after infra side lands.
- # base_runner.TEST_MAP["d8_default"].remove("intl")
+ if options.gc_stress:
+ options.extra_flags += GC_STRESS_FLAGS
- def _setup_env(self):
- super(StandardTestRunner, self)._setup_env()
+ if options.random_gc_stress:
+ options.extra_flags += RANDOM_GC_STRESS_FLAGS
- symbolizer_option = self._get_external_symbolizer_option()
+ if self.build_config.asan:
+ options.extra_flags.append('--invoke-weak-callbacks')
+ options.extra_flags.append('--omit-quit')
- if self.sancov_dir:
- os.environ['ASAN_OPTIONS'] = ":".join([
- 'coverage=1',
- 'coverage_dir=%s' % self.sancov_dir,
- symbolizer_option,
- "allow_user_segv_handler=1",
- ])
+ if self.build_config.no_snap:
+ # Speed up slow nosnap runs. Allocation verification is covered by
+ # running mksnapshot on other builders.
+ options.extra_flags.append('--no-turbo-verify-allocation')
- def _random_seed(self):
- seed = 0
- while not seed:
- seed = random.SystemRandom().randint(-2147483648, 2147483647)
- return seed
+ if options.novfp3:
+ options.extra_flags.append('--noenable-vfp3')
- def _execute(self, args, options, suites):
- print(">>> Running tests for %s.%s" % (self.build_config.arch,
- self.mode_name))
- # Populate context object.
+ if options.no_variants: # pragma: no cover
+ print ('Option --no-variants is deprecated. '
+ 'Pass --variants=default instead.')
+ assert not options.variants
+ options.variants = 'default'
- # Simulators are slow, therefore allow a longer timeout.
- if self.build_config.arch in SLOW_ARCHS:
- options.timeout *= 2
+ if options.exhaustive_variants: # pragma: no cover
+ # TODO(machenbach): Switch infra to --variants=exhaustive after M65.
+ print ('Option --exhaustive-variants is deprecated. '
+ 'Pass --variants=exhaustive instead.')
+ # This is used on many bots. It includes a larger set of default
+ # variants.
+ # Other options for manipulating variants still apply afterwards.
+ assert not options.variants
+ options.variants = 'exhaustive'
- options.timeout *= self.mode_options.timeout_scalefactor
+ if options.quickcheck:
+ assert not options.variants
+ options.variants = 'stress,default'
+ options.slow_tests = 'skip'
+ options.pass_fail_tests = 'skip'
- if self.build_config.predictable:
- # Predictable mode is slower.
- options.timeout *= 2
+ if self.build_config.predictable:
+ options.variants = 'default'
+ options.extra_flags.append('--predictable')
+ options.extra_flags.append('--verify-predictable')
+ options.extra_flags.append('--no-inline-new')
+ # Add predictable wrapper to command prefix.
+ options.command_prefix = (
+ [sys.executable, PREDICTABLE_WRAPPER] + options.command_prefix)
- ctx = context.Context(self.build_config.arch,
- self.mode_options.execution_mode,
- self.outdir,
- self.mode_options.flags,
- options.verbose,
- options.timeout,
- options.isolates,
- options.command_prefix,
- options.extra_flags,
- self.build_config.no_i18n,
- options.random_seed,
- options.no_sorting,
- options.rerun_failures_count,
- options.rerun_failures_max,
- options.no_harness,
- use_perf_data=not options.swarming,
- sancov_dir=self.sancov_dir,
- infra_staging=options.infra_staging)
+ # TODO(machenbach): Figure out how to test a bigger subset of variants on
+ # msan.
+ if self.build_config.msan:
+ options.variants = 'default'
- # TODO(all): Combine "simulator" and "simulator_run".
- # TODO(machenbach): In GN we can derive simulator run from
- # target_arch != v8_target_arch in the dumped build config.
- simulator_run = (
- not options.dont_skip_simulator_slow_tests and
- self.build_config.arch in [
- 'arm64', 'arm', 'mipsel', 'mips', 'mips64', 'mips64el', 'ppc',
- 'ppc64', 's390', 's390x'] and
- bool(base_runner.ARCH_GUESS) and
- self.build_config.arch != base_runner.ARCH_GUESS)
- # Find available test suites and read test cases from them.
- variables = {
- "arch": self.build_config.arch,
- "asan": self.build_config.asan,
- "byteorder": sys.byteorder,
- "dcheck_always_on": self.build_config.dcheck_always_on,
- "deopt_fuzzer": False,
- "gc_fuzzer": False,
- "gc_stress": options.gc_stress,
- "gcov_coverage": self.build_config.gcov_coverage,
- "isolates": options.isolates,
- "mode": self.mode_options.status_mode,
- "msan": self.build_config.msan,
- "no_harness": options.no_harness,
- "no_i18n": self.build_config.no_i18n,
- "no_snap": self.build_config.no_snap,
- "novfp3": options.novfp3,
- "predictable": self.build_config.predictable,
- "simulator": utils.UseSimulator(self.build_config.arch),
- "simulator_run": simulator_run,
- "system": utils.GuessOS(),
- "tsan": self.build_config.tsan,
- "ubsan_vptr": self.build_config.ubsan_vptr,
- }
+ if options.variants == 'infra_staging':
+ options.variants = 'exhaustive'
- progress_indicator = progress.IndicatorNotifier()
- progress_indicator.Register(
- progress.PROGRESS_INDICATORS[options.progress]())
- if options.junitout: # pragma: no cover
- progress_indicator.Register(progress.JUnitTestProgressIndicator(
- options.junitout, options.junittestsuite))
- if options.json_test_results:
- progress_indicator.Register(progress.JsonTestProgressIndicator(
- options.json_test_results,
- self.build_config.arch,
- self.mode_options.execution_mode,
- ctx.random_seed))
- if options.flakiness_results: # pragma: no cover
- progress_indicator.Register(progress.FlakinessTestProgressIndicator(
- options.flakiness_results))
+ self._variants = self._parse_variants(options.variants)
- if options.infra_staging:
- for s in suites:
- s.ReadStatusFile(variables)
- s.ReadTestCases(ctx)
+ def CheckTestMode(name, option): # pragma: no cover
+ if option not in ['run', 'skip', 'dontcare']:
+ print('Unknown %s mode %s' % (name, option))
+ raise base_runner.TestRunnerError()
+ CheckTestMode('slow test', options.slow_tests)
+ CheckTestMode('pass|fail test', options.pass_fail_tests)
+ if self.build_config.no_i18n:
+ base_runner.TEST_MAP['bot_default'].remove('intl')
+ base_runner.TEST_MAP['default'].remove('intl')
+ # TODO(machenbach): uncomment after infra side lands.
+ # base_runner.TEST_MAP['d8_default'].remove('intl')
- return self._run_test_procs(suites, args, options, progress_indicator,
- ctx)
+ def _parse_variants(self, aliases_str):
+ # Use developer defaults if no variant was specified.
+ aliases_str = aliases_str or 'dev'
+ aliases = aliases_str.split(',')
+ user_variants = set(reduce(
+ list.__add__, [VARIANT_ALIASES.get(a, [a]) for a in aliases]))
- all_tests = []
- num_tests = 0
- for s in suites:
- s.ReadStatusFile(variables)
- s.ReadTestCases(ctx)
- if len(args) > 0:
- s.FilterTestCasesByArgs(args)
- all_tests += s.tests
+ result = [v for v in ALL_VARIANTS if v in user_variants]
+ if len(result) == len(user_variants):
+ return result
- # First filtering by status applying the generic rules (tests without
- # variants)
- if options.warn_unused:
- tests = [(t.name, t.variant) for t in s.tests]
- s.statusfile.warn_unused_rules(tests, check_variant_rules=False)
- s.FilterTestCasesByStatus(options.slow_tests, options.pass_fail_tests)
+ for v in user_variants:
+ if v not in ALL_VARIANTS:
+ print('Unknown variant: %s' % v)
+ raise base_runner.TestRunnerError()
+ assert False, 'Unreachable'
- if options.cat:
- verbose.PrintTestSource(s.tests)
- continue
- variant_gen = s.CreateLegacyVariantsGenerator(VARIANTS)
- variant_tests = [ t.create_variant(v, flags)
- for t in s.tests
- for v in variant_gen.FilterVariantsByTest(t)
- for flags in variant_gen.GetFlagSets(t, v) ]
+ def _setup_env(self):
+ super(StandardTestRunner, self)._setup_env()
- if options.random_seed_stress_count > 1:
- # Duplicate test for random seed stress mode.
- def iter_seed_flags():
- for _ in range(0, options.random_seed_stress_count):
- # Use given random seed for all runs (set by default in
- # execution.py) or a new random seed if none is specified.
- if options.random_seed:
- yield []
- else:
- yield ["--random-seed=%d" % self._random_seed()]
- s.tests = [
- t.create_variant(t.variant, flags, 'seed-stress-%d' % n)
- for t in variant_tests
- for n, flags in enumerate(iter_seed_flags())
- ]
- else:
- s.tests = variant_tests
+ symbolizer_option = self._get_external_symbolizer_option()
- # Second filtering by status applying also the variant-dependent rules.
- if options.warn_unused:
- tests = [(t.name, t.variant) for t in s.tests]
- s.statusfile.warn_unused_rules(tests, check_variant_rules=True)
+ if self.sancov_dir:
+ os.environ['ASAN_OPTIONS'] = ':'.join([
+ 'coverage=1',
+ 'coverage_dir=%s' % self.sancov_dir,
+ symbolizer_option,
+ 'allow_user_segv_handler=1',
+ ])
- s.FilterTestCasesByStatus(options.slow_tests, options.pass_fail_tests)
- s.tests = self._shard_tests(s.tests, options)
+ def _get_statusfile_variables(self, options):
+ variables = (
+ super(StandardTestRunner, self)._get_statusfile_variables(options))
- for t in s.tests:
- t.cmd = t.get_command(ctx)
+ simulator_run = (
+ not options.dont_skip_simulator_slow_tests and
+ self.build_config.arch in [
+ 'arm64', 'arm', 'mipsel', 'mips', 'mips64', 'mips64el', 'ppc',
+ 'ppc64', 's390', 's390x'] and
+ bool(ARCH_GUESS) and
+ self.build_config.arch != ARCH_GUESS)
- num_tests += len(s.tests)
+ variables.update({
+ 'gc_stress': options.gc_stress or options.random_gc_stress,
+ 'gc_fuzzer': options.random_gc_stress,
+ 'novfp3': options.novfp3,
+ 'simulator_run': simulator_run,
+ })
+ return variables
- if options.cat:
- return 0 # We're done here.
+ def _do_execute(self, tests, args, options):
+ jobs = options.j
- if options.report:
- verbose.PrintReport(all_tests)
+ print('>>> Running with test processors')
+ loader = LoadProc(tests)
+ results = self._create_result_tracker(options)
+ indicators = self._create_progress_indicators(
+ tests.test_count_estimate, options)
- # Run the tests.
- start_time = time.time()
+ outproc_factory = None
+ if self.build_config.predictable:
+ outproc_factory = predictable.get_outproc
+ execproc = ExecutionProc(jobs, outproc_factory)
+ sigproc = self._create_signal_proc()
- if self.build_config.predictable:
- outproc_factory = predictable.get_outproc
- else:
- outproc_factory = None
+ procs = [
+ loader,
+ NameFilterProc(args) if args else None,
+ StatusFileFilterProc(options.slow_tests, options.pass_fail_tests),
+ VariantProc(self._variants),
+ StatusFileFilterProc(options.slow_tests, options.pass_fail_tests),
+ self._create_predictable_filter(),
+ self._create_shard_proc(options),
+ self._create_seed_proc(options),
+ sigproc,
+ ] + indicators + [
+ results,
+ self._create_timeout_proc(options),
+ self._create_rerun_proc(options),
+ execproc,
+ ]
- runner = execution.Runner(suites, progress_indicator, ctx,
- outproc_factory)
- exit_code = runner.Run(options.j)
- overall_duration = time.time() - start_time
+ self._prepare_procs(procs)
- if options.time:
- verbose.PrintTestDurations(suites, runner.outputs, overall_duration)
+ loader.load_initial_tests(initial_batch_size=options.j * 2)
- if num_tests == 0:
- print("Warning: no tests were run!")
+ # This starts up worker processes and blocks until all tests are
+ # processed.
+ execproc.run()
- if exit_code == 1 and options.json_test_results:
- print("Force exit code 0 after failures. Json test results file "
- "generated with failure information.")
- exit_code = 0
+ for indicator in indicators:
+ indicator.finished()
- if self.sancov_dir:
- # If tests ran with sanitizer coverage, merge coverage files in the end.
- try:
- print "Merging sancov files."
- subprocess.check_call([
- sys.executable,
- join(self.basedir, "tools", "sanitizers", "sancov_merger.py"),
- "--coverage-dir=%s" % self.sancov_dir])
- except:
- print >> sys.stderr, "Error: Merging sancov files failed."
- exit_code = 1
+ if tests.test_count_estimate:
+ percentage = float(results.total) / tests.test_count_estimate * 100
+ else:
+ percentage = 0
- return exit_code
+ print (('>>> %d base tests produced %d (%d%s)'
+ ' non-filtered tests') % (
+ tests.test_count_estimate, results.total, percentage, '%'))
- def _shard_tests(self, tests, options):
- shard_run, shard_count = self._get_shard_info(options)
+ print('>>> %d tests ran' % (results.total - results.remaining))
- if shard_count < 2:
- return tests
- count = 0
- shard = []
- for test in tests:
- if count % shard_count == shard_run - 1:
- shard.append(test)
- count += 1
- return shard
+ exit_code = utils.EXIT_CODE_PASS
+ if results.failed:
+ exit_code = utils.EXIT_CODE_FAILURES
+ if not results.total:
+ exit_code = utils.EXIT_CODE_NO_TESTS
- def _run_test_procs(self, suites, args, options, progress_indicator,
- context):
- jobs = options.j
+ # Indicate if a SIGINT or SIGTERM happened.
+ return max(exit_code, sigproc.exit_code)
- print '>>> Running with test processors'
- loader = LoadProc()
- tests_counter = TestsCounter()
- results = ResultsTracker()
- indicators = progress_indicator.ToProgressIndicatorProcs()
- execproc = ExecutionProc(jobs, context)
+ def _create_predictable_filter(self):
+ if not self.build_config.predictable:
+ return None
+ return predictable.PredictableFilterProc()
- procs = [
- loader,
- NameFilterProc(args) if args else None,
- StatusFileFilterProc(options.slow_tests, options.pass_fail_tests),
- self._create_shard_proc(options),
- tests_counter,
- VariantProc(VARIANTS),
- StatusFileFilterProc(options.slow_tests, options.pass_fail_tests),
- ] + indicators + [
- results,
- self._create_rerun_proc(context),
- execproc,
- ]
-
- procs = filter(None, procs)
-
- for i in xrange(0, len(procs) - 1):
- procs[i].connect_to(procs[i + 1])
-
- tests = [t for s in suites for t in s.tests]
- tests.sort(key=lambda t: t.is_slow, reverse=True)
-
- loader.setup()
- loader.load_tests(tests)
-
- print '>>> Running %d base tests' % tests_counter.total
- tests_counter.remove_from_chain()
-
- execproc.start()
-
- for indicator in indicators:
- indicator.finished()
-
- print '>>> %d tests ran' % results.total
-
- exit_code = 0
- if results.failed:
- exit_code = 1
- if results.remaining:
- exit_code = 2
-
-
- if exit_code == 1 and options.json_test_results:
- print("Force exit code 0 after failures. Json test results file "
- "generated with failure information.")
- exit_code = 0
- return exit_code
-
- def _create_rerun_proc(self, ctx):
- if not ctx.rerun_failures_count:
- return None
- return RerunProc(ctx.rerun_failures_count,
- ctx.rerun_failures_max)
-
+ def _create_seed_proc(self, options):
+ if options.random_seed_stress_count == 1:
+ return None
+ return SeedProc(options.random_seed_stress_count, options.random_seed,
+ options.j * 4)
if __name__ == '__main__':
diff --git a/src/v8/tools/testrunner/test_config.py b/src/v8/tools/testrunner/test_config.py
new file mode 100644
index 0000000..27ac72b
--- /dev/null
+++ b/src/v8/tools/testrunner/test_config.py
@@ -0,0 +1,34 @@
+# 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.
+
+import random
+
+from .utils import random_utils
+
+
+class TestConfig(object):
+ def __init__(self,
+ command_prefix,
+ extra_flags,
+ isolates,
+ mode_flags,
+ no_harness,
+ noi18n,
+ random_seed,
+ run_skipped,
+ shell_dir,
+ timeout,
+ verbose):
+ self.command_prefix = command_prefix
+ self.extra_flags = extra_flags
+ self.isolates = isolates
+ self.mode_flags = mode_flags
+ self.no_harness = no_harness
+ self.noi18n = noi18n
+ # random_seed is always not None.
+ self.random_seed = random_seed or random_utils.random_seed()
+ self.run_skipped = run_skipped
+ self.shell_dir = shell_dir
+ self.timeout = timeout
+ self.verbose = verbose
diff --git a/src/v8/tools/testrunner/testproc/base.py b/src/v8/tools/testrunner/testproc/base.py
index 1a87dbe..c52c779 100644
--- a/src/v8/tools/testrunner/testproc/base.py
+++ b/src/v8/tools/testrunner/testproc/base.py
@@ -37,36 +37,12 @@
DROP_PASS_OUTPUT = 2
DROP_PASS_STDOUT = 3
-def get_reduce_result_function(requirement):
- if requirement == DROP_RESULT:
- return lambda _: None
-
- if requirement == DROP_OUTPUT:
- def f(result):
- result.output = None
- return result
- return f
-
- if requirement == DROP_PASS_OUTPUT:
- def f(result):
- if not result.has_unexpected_output:
- result.output = None
- return result
- return f
-
- if requirement == DROP_PASS_STDOUT:
- def f(result):
- if not result.has_unexpected_output:
- result.output.stdout = None
- result.output.stderr = None
- return result
- return f
-
class TestProc(object):
def __init__(self):
self._prev_proc = None
self._next_proc = None
+ self._stopped = False
self._requirement = DROP_RESULT
self._prev_requirement = None
self._reduce_result = lambda result: result
@@ -90,13 +66,21 @@
self._prev_requirement = requirement
if self._next_proc:
self._next_proc.setup(max(requirement, self._requirement))
- if self._prev_requirement < self._requirement:
- self._reduce_result = get_reduce_result_function(self._prev_requirement)
+
+ # Since we're not winning anything by droping part of the result we are
+ # dropping the whole result or pass it as it is. The real reduction happens
+ # during result creation (in the output processor), so the result is
+ # immutable.
+ if (self._prev_requirement < self._requirement and
+ self._prev_requirement == DROP_RESULT):
+ self._reduce_result = lambda _: None
def next_test(self, test):
"""
Method called by previous processor whenever it produces new test.
This method shouldn't be called by anyone except previous processor.
+ Returns a boolean value to signal whether the test was loaded into the
+ execution queue successfully or not.
"""
raise NotImplementedError()
@@ -111,15 +95,28 @@
if self._prev_proc:
self._prev_proc.heartbeat()
+ def stop(self):
+ if not self._stopped:
+ self._stopped = True
+ if self._prev_proc:
+ self._prev_proc.stop()
+ if self._next_proc:
+ self._next_proc.stop()
+
+ @property
+ def is_stopped(self):
+ return self._stopped
+
### Communication
def _send_test(self, test):
"""Helper method for sending test to the next processor."""
- self._next_proc.next_test(test)
+ return self._next_proc.next_test(test)
def _send_result(self, test, result):
"""Helper method for sending result to the previous processor."""
- result = self._reduce_result(result)
+ if not test.keep_output:
+ result = self._reduce_result(result)
self._prev_proc.result_for(test, result)
@@ -131,7 +128,7 @@
def next_test(self, test):
self._on_next_test(test)
- self._send_test(test)
+ return self._send_test(test)
def result_for(self, test, result):
self._on_result_for(test, result)
@@ -163,7 +160,7 @@
self._name = name
def next_test(self, test):
- self._next_test(test)
+ return self._next_test(test)
def result_for(self, subtest, result):
self._result_for(subtest.origin, subtest, result)
@@ -195,9 +192,9 @@
def next_test(self, test):
if self._filter(test):
- self._send_result(test, SKIPPED)
- else:
- self._send_test(test)
+ return False
+
+ return self._send_test(test)
def result_for(self, test, result):
self._send_result(test, result)
diff --git a/src/v8/tools/testrunner/testproc/combiner.py b/src/v8/tools/testrunner/testproc/combiner.py
new file mode 100644
index 0000000..4d992f4
--- /dev/null
+++ b/src/v8/tools/testrunner/testproc/combiner.py
@@ -0,0 +1,127 @@
+# 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.
+
+# for py2/py3 compatibility
+from __future__ import print_function
+
+from collections import defaultdict
+import time
+
+from . import base
+from ..objects import testcase
+from ..outproc import base as outproc
+
+class CombinerProc(base.TestProc):
+ def __init__(self, rng, min_group_size, max_group_size, count):
+ """
+ Args:
+ rng: random number generator
+ min_group_size: minimum number of tests to combine
+ max_group_size: maximum number of tests to combine
+ count: how many tests to generate. 0 means infinite running
+ """
+ super(CombinerProc, self).__init__()
+
+ self._rng = rng
+ self._min_size = min_group_size
+ self._max_size = max_group_size
+ self._count = count
+
+ # Index of the last generated test
+ self._current_num = 0
+
+ # {suite name: instance of TestGroups}
+ self._groups = defaultdict(TestGroups)
+
+ # {suite name: instance of TestCombiner}
+ self._combiners = {}
+
+ def setup(self, requirement=base.DROP_RESULT):
+ # Combiner is not able to pass results (even as None) to the previous
+ # processor.
+ assert requirement == base.DROP_RESULT
+ self._next_proc.setup(base.DROP_RESULT)
+
+ def next_test(self, test):
+ group_key = self._get_group_key(test)
+ if not group_key:
+ # Test not suitable for combining
+ return False
+
+ self._groups[test.suite.name].add_test(group_key, test)
+ return True
+
+ def _get_group_key(self, test):
+ combiner = self._get_combiner(test.suite)
+ if not combiner:
+ print ('>>> Warning: There is no combiner for %s testsuite' %
+ test.suite.name)
+ return None
+ return combiner.get_group_key(test)
+
+ def result_for(self, test, result):
+ self._send_next_test()
+
+ def generate_initial_tests(self, num=1):
+ for _ in range(0, num):
+ self._send_next_test()
+
+ def _send_next_test(self):
+ if self.is_stopped:
+ return False
+
+ if self._count and self._current_num >= self._count:
+ return False
+
+ combined_test = self._create_new_test()
+ if not combined_test:
+ # Not enough tests
+ return False
+
+ return self._send_test(combined_test)
+
+ def _create_new_test(self):
+ suite, combiner = self._select_suite()
+ groups = self._groups[suite]
+
+ max_size = self._rng.randint(self._min_size, self._max_size)
+ sample = groups.sample(self._rng, max_size)
+ if not sample:
+ return None
+
+ self._current_num += 1
+ return combiner.combine('%s-%d' % (suite, self._current_num), sample)
+
+ def _select_suite(self):
+ """Returns pair (suite name, combiner)."""
+ selected = self._rng.randint(0, len(self._groups) - 1)
+ for n, suite in enumerate(self._groups):
+ if n == selected:
+ return suite, self._combiners[suite]
+
+ def _get_combiner(self, suite):
+ combiner = self._combiners.get(suite.name)
+ if not combiner:
+ combiner = suite.get_test_combiner()
+ self._combiners[suite.name] = combiner
+ return combiner
+
+
+class TestGroups(object):
+ def __init__(self):
+ self._groups = defaultdict(list)
+ self._keys = []
+
+ def add_test(self, key, test):
+ self._groups[key].append(test)
+ self._keys.append(key)
+
+ def sample(self, rng, max_size):
+ # Not enough tests
+ if not self._groups:
+ return None
+
+ group_key = rng.choice(self._keys)
+ tests = self._groups[group_key]
+ return [rng.choice(tests) for _ in range(0, max_size)]
diff --git a/src/v8/tools/testrunner/testproc/execution.py b/src/v8/tools/testrunner/testproc/execution.py
index 021b02a..68ecf45 100644
--- a/src/v8/tools/testrunner/testproc/execution.py
+++ b/src/v8/tools/testrunner/testproc/execution.py
@@ -15,12 +15,12 @@
return job.run(process_context)
-def create_process_context(requirement):
- return ProcessContext(base.get_reduce_result_function(requirement))
+def create_process_context(result_reduction):
+ return ProcessContext(result_reduction)
JobResult = collections.namedtuple('JobResult', ['id', 'result'])
-ProcessContext = collections.namedtuple('ProcessContext', ['reduce_result_f'])
+ProcessContext = collections.namedtuple('ProcessContext', ['result_reduction'])
class Job(object):
@@ -32,9 +32,8 @@
def run(self, process_ctx):
output = self.cmd.execute()
- result = self.outproc.process(output)
- if not self.keep_output:
- result = process_ctx.reduce_result_f(result)
+ reduction = process_ctx.result_reduction if not self.keep_output else None
+ result = self.outproc.process(output, reduction)
return JobResult(self.test_id, result)
@@ -44,49 +43,53 @@
sends results to the previous processor.
"""
- def __init__(self, jobs, context):
+ def __init__(self, jobs, outproc_factory=None):
super(ExecutionProc, self).__init__()
self._pool = pool.Pool(jobs)
- self._context = context
+ self._outproc_factory = outproc_factory or (lambda t: t.output_proc)
self._tests = {}
def connect_to(self, next_proc):
assert False, 'ExecutionProc cannot be connected to anything'
- def start(self):
- try:
- it = self._pool.imap_unordered(
+ def run(self):
+ it = self._pool.imap_unordered(
fn=run_job,
gen=[],
process_context_fn=create_process_context,
process_context_args=[self._prev_requirement],
- )
- for pool_result in it:
- if pool_result.heartbeat:
- continue
-
- job_result = pool_result.value
- test_id, result = job_result
-
- test, result.cmd = self._tests[test_id]
- del self._tests[test_id]
- self._send_result(test, result)
- except KeyboardInterrupt:
- raise
- except:
- traceback.print_exc()
- raise
- finally:
- self._pool.terminate()
+ )
+ for pool_result in it:
+ self._unpack_result(pool_result)
def next_test(self, test):
+ if self.is_stopped:
+ return False
+
test_id = test.procid
- cmd = test.get_command(self._context)
+ cmd = test.get_command()
self._tests[test_id] = test, cmd
- # TODO(majeski): Needs factory for outproc as in local/execution.py
- outproc = test.output_proc
+ outproc = self._outproc_factory(test)
self._pool.add([Job(test_id, cmd, outproc, test.keep_output)])
+ return True
+
def result_for(self, test, result):
assert False, 'ExecutionProc cannot receive results'
+
+ def stop(self):
+ super(ExecutionProc, self).stop()
+ self._pool.abort()
+
+ def _unpack_result(self, pool_result):
+ if pool_result.heartbeat:
+ self.heartbeat()
+ return
+
+ job_result = pool_result.value
+ test_id, result = job_result
+
+ test, result.cmd = self._tests[test_id]
+ del self._tests[test_id]
+ self._send_result(test, result)
diff --git a/src/v8/tools/testrunner/testproc/expectation.py b/src/v8/tools/testrunner/testproc/expectation.py
new file mode 100644
index 0000000..fdc9e3e
--- /dev/null
+++ b/src/v8/tools/testrunner/testproc/expectation.py
@@ -0,0 +1,28 @@
+# 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.
+
+from . import base
+
+from testrunner.local import statusfile
+from testrunner.outproc import base as outproc
+
+class ForgiveTimeoutProc(base.TestProcProducer):
+ """Test processor passing tests and results through and forgiving timeouts."""
+ def __init__(self):
+ super(ForgiveTimeoutProc, self).__init__('no-timeout')
+
+ def _next_test(self, test):
+ subtest = self._create_subtest(test, 'no_timeout')
+ if subtest.expected_outcomes == outproc.OUTCOMES_PASS:
+ subtest.expected_outcomes = outproc.OUTCOMES_PASS_OR_TIMEOUT
+ elif subtest.expected_outcomes == outproc.OUTCOMES_FAIL:
+ subtest.expected_outcomes = outproc.OUTCOMES_FAIL_OR_TIMEOUT
+ elif statusfile.TIMEOUT not in subtest.expected_outcomes:
+ subtest.expected_outcomes = (
+ subtest.expected_outcomes + [statusfile.TIMEOUT])
+
+ return self._send_test(subtest)
+
+ def _result_for(self, test, subtest, result):
+ self._send_result(test, result)
diff --git a/src/v8/tools/testrunner/testproc/filter.py b/src/v8/tools/testrunner/testproc/filter.py
index 5081997..e2a5e97 100644
--- a/src/v8/tools/testrunner/testproc/filter.py
+++ b/src/v8/tools/testrunner/testproc/filter.py
@@ -59,25 +59,25 @@
super(NameFilterProc, self).__init__()
self._globs = defaultdict(list)
+ self._exact_matches = defaultdict(dict)
for a in args:
argpath = a.split('/')
suitename = argpath[0]
path = '/'.join(argpath[1:]) or '*'
- self._globs[suitename].append(path)
+ if '*' in path:
+ self._globs[suitename].append(path)
+ else:
+ self._exact_matches[suitename][path] = True
for s, globs in self._globs.iteritems():
if not globs or '*' in globs:
- self._globs[s] = []
+ self._globs[s] = ['*']
def _filter(self, test):
- globs = self._globs.get(test.suite.name)
- if globs is None:
- return True
-
- if not globs:
- return False
-
+ globs = self._globs.get(test.suite.name, [])
for g in globs:
+ if g == '*': return False
if fnmatch.fnmatch(test.path, g):
return False
- return True
+ exact_matches = self._exact_matches.get(test.suite.name, {})
+ return test.path not in exact_matches
diff --git a/src/v8/tools/testrunner/testproc/fuzzer.py b/src/v8/tools/testrunner/testproc/fuzzer.py
new file mode 100644
index 0000000..187145b
--- /dev/null
+++ b/src/v8/tools/testrunner/testproc/fuzzer.py
@@ -0,0 +1,287 @@
+# 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.
+
+from collections import namedtuple
+import time
+
+from . import base
+
+
+class FuzzerConfig(object):
+ def __init__(self, probability, analyzer, fuzzer):
+ """
+ Args:
+ probability: of choosing this fuzzer (0; 10]
+ analyzer: instance of Analyzer class, can be None if no analysis is needed
+ fuzzer: instance of Fuzzer class
+ """
+ assert probability > 0 and probability <= 10
+
+ self.probability = probability
+ self.analyzer = analyzer
+ self.fuzzer = fuzzer
+
+
+class Analyzer(object):
+ def get_analysis_flags(self):
+ raise NotImplementedError()
+
+ def do_analysis(self, result):
+ raise NotImplementedError()
+
+
+class Fuzzer(object):
+ def create_flags_generator(self, rng, test, analysis_value):
+ """
+ Args:
+ rng: random number generator
+ test: test for which to create flags
+ analysis_value: value returned by the analyzer. None if there is no
+ corresponding analyzer to this fuzzer or the analysis phase is disabled
+ """
+ raise NotImplementedError()
+
+
+# TODO(majeski): Allow multiple subtests to run at once.
+class FuzzerProc(base.TestProcProducer):
+ def __init__(self, rng, count, fuzzers, disable_analysis=False):
+ """
+ Args:
+ rng: random number generator used to select flags and values for them
+ count: number of tests to generate based on each base test
+ fuzzers: list of FuzzerConfig instances
+ disable_analysis: disable analysis phase and filtering base on it. When
+ set, processor passes None as analysis result to fuzzers
+ """
+ super(FuzzerProc, self).__init__('Fuzzer')
+
+ self._rng = rng
+ self._count = count
+ self._fuzzer_configs = fuzzers
+ self._disable_analysis = disable_analysis
+ self._gens = {}
+
+ def setup(self, requirement=base.DROP_RESULT):
+ # Fuzzer is optimized to not store the results
+ assert requirement == base.DROP_RESULT
+ super(FuzzerProc, self).setup(requirement)
+
+ def _next_test(self, test):
+ if self.is_stopped:
+ return False
+
+ analysis_subtest = self._create_analysis_subtest(test)
+ if analysis_subtest:
+ return self._send_test(analysis_subtest)
+
+ self._gens[test.procid] = self._create_gen(test)
+ return self._try_send_next_test(test)
+
+ def _create_analysis_subtest(self, test):
+ if self._disable_analysis:
+ return None
+
+ analysis_flags = []
+ for fuzzer_config in self._fuzzer_configs:
+ if fuzzer_config.analyzer:
+ analysis_flags += fuzzer_config.analyzer.get_analysis_flags()
+
+ if analysis_flags:
+ analysis_flags = list(set(analysis_flags))
+ return self._create_subtest(test, 'analysis', flags=analysis_flags,
+ keep_output=True)
+
+
+ def _result_for(self, test, subtest, result):
+ if not self._disable_analysis:
+ if result is not None:
+ # Analysis phase, for fuzzing we drop the result.
+ if result.has_unexpected_output:
+ self._send_result(test, None)
+ return
+
+ self._gens[test.procid] = self._create_gen(test, result)
+
+ self._try_send_next_test(test)
+
+ def _create_gen(self, test, analysis_result=None):
+ # It will be called with analysis_result==None only when there is no
+ # analysis phase at all, so no fuzzer has it's own analyzer.
+ gens = []
+ indexes = []
+ for i, fuzzer_config in enumerate(self._fuzzer_configs):
+ analysis_value = None
+ if analysis_result and fuzzer_config.analyzer:
+ analysis_value = fuzzer_config.analyzer.do_analysis(analysis_result)
+ if not analysis_value:
+ # Skip fuzzer for this test since it doesn't have analysis data
+ continue
+ p = fuzzer_config.probability
+ flag_gen = fuzzer_config.fuzzer.create_flags_generator(self._rng, test,
+ analysis_value)
+ indexes += [len(gens)] * p
+ gens.append((p, flag_gen))
+
+ if not gens:
+ # No fuzzers for this test, skip it
+ return
+
+ i = 0
+ while not self._count or i < self._count:
+ main_index = self._rng.choice(indexes)
+ _, main_gen = gens[main_index]
+
+ flags = next(main_gen)
+ for index, (p, gen) in enumerate(gens):
+ if index == main_index:
+ continue
+ if self._rng.randint(1, 10) <= p:
+ flags += next(gen)
+
+ flags.append('--fuzzer-random-seed=%s' % self._next_seed())
+ yield self._create_subtest(test, str(i), flags=flags)
+
+ i += 1
+
+ def _try_send_next_test(self, test):
+ if not self.is_stopped:
+ for subtest in self._gens[test.procid]:
+ if self._send_test(subtest):
+ return True
+
+ del self._gens[test.procid]
+ return False
+
+ def _next_seed(self):
+ seed = None
+ while not seed:
+ seed = self._rng.randint(-2147483648, 2147483647)
+ return seed
+
+
+class ScavengeAnalyzer(Analyzer):
+ def get_analysis_flags(self):
+ return ['--fuzzer-gc-analysis']
+
+ def do_analysis(self, result):
+ for line in reversed(result.output.stdout.splitlines()):
+ if line.startswith('### Maximum new space size reached = '):
+ return int(float(line.split()[7]))
+
+
+class ScavengeFuzzer(Fuzzer):
+ def create_flags_generator(self, rng, test, analysis_value):
+ while True:
+ yield ['--stress-scavenge=%d' % (analysis_value or 100)]
+
+
+class MarkingAnalyzer(Analyzer):
+ def get_analysis_flags(self):
+ return ['--fuzzer-gc-analysis']
+
+ def do_analysis(self, result):
+ for line in reversed(result.output.stdout.splitlines()):
+ if line.startswith('### Maximum marking limit reached = '):
+ return int(float(line.split()[6]))
+
+
+class MarkingFuzzer(Fuzzer):
+ def create_flags_generator(self, rng, test, analysis_value):
+ while True:
+ yield ['--stress-marking=%d' % (analysis_value or 100)]
+
+
+class GcIntervalAnalyzer(Analyzer):
+ def get_analysis_flags(self):
+ return ['--fuzzer-gc-analysis']
+
+ def do_analysis(self, result):
+ for line in reversed(result.output.stdout.splitlines()):
+ if line.startswith('### Allocations = '):
+ return int(float(line.split()[3][:-1]))
+
+
+class GcIntervalFuzzer(Fuzzer):
+ def create_flags_generator(self, rng, test, analysis_value):
+ if analysis_value:
+ value = analysis_value / 10
+ else:
+ value = 10000
+ while True:
+ yield ['--random-gc-interval=%d' % value]
+
+
+class CompactionFuzzer(Fuzzer):
+ def create_flags_generator(self, rng, test, analysis_value):
+ while True:
+ yield ['--stress-compaction-random']
+
+
+class TaskDelayFuzzer(Fuzzer):
+ def create_flags_generator(self, rng, test, analysis_value):
+ while True:
+ yield ['--stress-delay-tasks']
+
+
+class ThreadPoolSizeFuzzer(Fuzzer):
+ def create_flags_generator(self, rng, test, analysis_value):
+ while True:
+ yield ['--thread-pool-size=%d' % rng.randint(1, 8)]
+
+
+class DeoptAnalyzer(Analyzer):
+ MAX_DEOPT=1000000000
+
+ def __init__(self, min_interval):
+ super(DeoptAnalyzer, self).__init__()
+ self._min = min_interval
+
+ def get_analysis_flags(self):
+ return ['--deopt-every-n-times=%d' % self.MAX_DEOPT,
+ '--print-deopt-stress']
+
+ def do_analysis(self, result):
+ for line in reversed(result.output.stdout.splitlines()):
+ if line.startswith('=== Stress deopt counter: '):
+ counter = self.MAX_DEOPT - int(line.split(' ')[-1])
+ if counter < self._min:
+ # Skip this test since we won't generate any meaningful interval with
+ # given minimum.
+ return None
+ return counter
+
+
+class DeoptFuzzer(Fuzzer):
+ def __init__(self, min_interval):
+ super(DeoptFuzzer, self).__init__()
+ self._min = min_interval
+
+ def create_flags_generator(self, rng, test, analysis_value):
+ while True:
+ if analysis_value:
+ value = analysis_value / 2
+ else:
+ value = 10000
+ interval = rng.randint(self._min, max(value, self._min))
+ yield ['--deopt-every-n-times=%d' % interval]
+
+
+FUZZERS = {
+ 'compaction': (None, CompactionFuzzer),
+ 'delay': (None, TaskDelayFuzzer),
+ 'deopt': (DeoptAnalyzer, DeoptFuzzer),
+ 'gc_interval': (GcIntervalAnalyzer, GcIntervalFuzzer),
+ 'marking': (MarkingAnalyzer, MarkingFuzzer),
+ 'scavenge': (ScavengeAnalyzer, ScavengeFuzzer),
+ 'threads': (None, ThreadPoolSizeFuzzer),
+}
+
+
+def create_fuzzer_config(name, probability, *args, **kwargs):
+ analyzer_class, fuzzer_class = FUZZERS[name]
+ return FuzzerConfig(
+ probability,
+ analyzer_class(*args, **kwargs) if analyzer_class else None,
+ fuzzer_class(*args, **kwargs),
+ )
diff --git a/src/v8/tools/testrunner/testproc/loader.py b/src/v8/tools/testrunner/testproc/loader.py
index 0a3d0df..f4afeae 100644
--- a/src/v8/tools/testrunner/testproc/loader.py
+++ b/src/v8/tools/testrunner/testproc/loader.py
@@ -9,19 +9,34 @@
"""First processor in the chain that passes all tests to the next processor.
"""
- def load_tests(self, tests):
- loaded = set()
- for test in tests:
- if test.procid in loaded:
- print 'Warning: %s already obtained' % test.procid
- continue
+ def __init__(self, tests):
+ super(LoadProc, self).__init__()
- loaded.add(test.procid)
- self._send_test(test)
+ self.tests = tests
+
+ def load_initial_tests(self, initial_batch_size):
+ """
+ Args:
+ exec_proc: execution processor that the tests are being loaded into
+ initial_batch_size: initial number of tests to load
+ """
+ loaded_tests = 0
+ while loaded_tests < initial_batch_size:
+ try:
+ t = next(self.tests)
+ except StopIteration:
+ return
+
+ if self._send_test(t):
+ loaded_tests += 1
def next_test(self, test):
assert False, 'Nothing can be connected to the LoadProc'
def result_for(self, test, result):
- # Ignore all results.
- pass
+ try:
+ while not self._send_test(next(self.tests)):
+ pass
+ except StopIteration:
+ # No more tests to load.
+ pass
diff --git a/src/v8/tools/testrunner/testproc/progress.py b/src/v8/tools/testrunner/testproc/progress.py
index 78514f7..aad6740 100644
--- a/src/v8/tools/testrunner/testproc/progress.py
+++ b/src/v8/tools/testrunner/testproc/progress.py
@@ -2,13 +2,22 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+# for py2/py3 compatibility
+from __future__ import print_function
+
import json
import os
+import platform
+import subprocess
import sys
import time
from . import base
-from ..local import junit_output
+
+
+# Base dir of the build products for Release and Debug.
+OUT_DIR = os.path.abspath(
+ os.path.join(os.path.dirname(__file__), '..', '..', '..', 'out'))
def print_failure_header(test):
@@ -16,29 +25,22 @@
negative_marker = '[negative] '
else:
negative_marker = ''
- print "=== %(label)s %(negative)s===" % {
+ print("=== %(label)s %(negative)s===" % {
'label': test,
'negative': negative_marker,
- }
-
-
-class TestsCounter(base.TestProcObserver):
- def __init__(self):
- super(TestsCounter, self).__init__()
- self.total = 0
-
- def _on_next_test(self, test):
- self.total += 1
+ })
class ResultsTracker(base.TestProcObserver):
- def __init__(self):
+ """Tracks number of results and stops to run tests if max_failures reached."""
+ def __init__(self, max_failures):
super(ResultsTracker, self).__init__()
self._requirement = base.DROP_OUTPUT
self.failed = 0
self.remaining = 0
self.total = 0
+ self.max_failures = max_failures
def _on_next_test(self, test):
self.total += 1
@@ -48,6 +50,9 @@
self.remaining -= 1
if result.has_unexpected_output:
self.failed += 1
+ if self.max_failures and self.failed >= self.max_failures:
+ print('>>> Too many failures, exiting...')
+ self.stop()
class ProgressIndicator(base.TestProcObserver):
@@ -61,10 +66,6 @@
self._requirement = base.DROP_PASS_OUTPUT
self._failed = []
- self._total = 0
-
- def _on_next_test(self, test):
- self._total += 1
def _on_result_for(self, test, result):
# TODO(majeski): Support for dummy/grouped results
@@ -73,36 +74,45 @@
def finished(self):
crashed = 0
- print
+ print()
for test, result in self._failed:
print_failure_header(test)
if result.output.stderr:
- print "--- stderr ---"
- print result.output.stderr.strip()
+ print("--- stderr ---")
+ print(result.output.stderr.strip())
if result.output.stdout:
- print "--- stdout ---"
- print result.output.stdout.strip()
- print "Command: %s" % result.cmd.to_string()
+ print("--- stdout ---")
+ print(result.output.stdout.strip())
+ print("Command: %s" % result.cmd.to_string())
if result.output.HasCrashed():
- print "exit code: %d" % result.output.exit_code
- print "--- CRASHED ---"
+ print("exit code: %d" % result.output.exit_code)
+ print("--- CRASHED ---")
crashed += 1
if result.output.HasTimedOut():
- print "--- TIMEOUT ---"
+ print("--- TIMEOUT ---")
if len(self._failed) == 0:
- print "==="
- print "=== All tests succeeded"
- print "==="
+ print("===")
+ print("=== All tests succeeded")
+ print("===")
else:
- print
- print "==="
- print "=== %i tests failed" % len(self._failed)
+ print()
+ print("===")
+ print("=== %i tests failed" % len(self._failed))
if crashed > 0:
- print "=== %i tests CRASHED" % crashed
- print "==="
+ print("=== %i tests CRASHED" % crashed)
+ print("===")
class VerboseProgressIndicator(SimpleProgressIndicator):
+ def __init__(self):
+ super(VerboseProgressIndicator, self).__init__()
+ self._last_printed_time = time.time()
+
+ def _print(self, text):
+ print(text)
+ sys.stdout.flush()
+ self._last_printed_time = time.time()
+
def _on_result_for(self, test, result):
super(VerboseProgressIndicator, self)._on_result_for(test, result)
# TODO(majeski): Support for dummy/grouped results
@@ -113,12 +123,31 @@
outcome = 'FAIL'
else:
outcome = 'pass'
- print 'Done running %s: %s' % (test, outcome)
- sys.stdout.flush()
+
+ self._print('Done running %s %s: %s' % (
+ test, test.variant or 'default', outcome))
+
+ # TODO(machenbach): Remove this platform specific hack and implement a proper
+ # feedback channel from the workers, providing which tests are currently run.
+ def _print_processes_linux(self):
+ if platform.system() == 'Linux':
+ try:
+ cmd = 'ps -aux | grep "%s"' % OUT_DIR
+ output = subprocess.check_output(cmd, shell=True)
+ self._print('List of processes:')
+ for line in (output or '').splitlines():
+ # Show command with pid, but other process info cut off.
+ self._print('pid: %s cmd: %s' %
+ (line.split()[1], line[line.index(OUT_DIR):]))
+ except:
+ pass
def _on_heartbeat(self):
- print 'Still working...'
- sys.stdout.flush()
+ if time.time() - self._last_printed_time > 30:
+ # Print something every 30 seconds to not get killed by an output
+ # timeout.
+ self._print('Still working...')
+ self._print_processes_linux()
class DotsProgressIndicator(SimpleProgressIndicator):
@@ -127,6 +156,7 @@
self._count = 0
def _on_result_for(self, test, result):
+ super(DotsProgressIndicator, self)._on_result_for(test, result)
# TODO(majeski): Support for dummy/grouped results
self._count += 1
if self._count > 1 and self._count % 50 == 1:
@@ -155,12 +185,11 @@
self._last_status_length = 0
self._start_time = time.time()
- self._total = 0
self._passed = 0
self._failed = 0
- def _on_next_test(self, test):
- self._total += 1
+ def set_test_count(self, test_count):
+ self._total = test_count
def _on_result_for(self, test, result):
# TODO(majeski): Support for dummy/grouped results
@@ -178,27 +207,27 @@
self._clear_line(self._last_status_length)
print_failure_header(test)
if len(stdout):
- print self._templates['stdout'] % stdout
+ print(self._templates['stdout'] % stdout)
if len(stderr):
- print self._templates['stderr'] % stderr
- print "Command: %s" % result.cmd
+ print(self._templates['stderr'] % stderr)
+ print("Command: %s" % result.cmd.to_string(relative=True))
if output.HasCrashed():
- print "exit code: %d" % output.exit_code
- print "--- CRASHED ---"
+ print("exit code: %d" % output.exit_code)
+ print("--- CRASHED ---")
if output.HasTimedOut():
- print "--- TIMEOUT ---"
+ print("--- TIMEOUT ---")
def finished(self):
self._print_progress('Done')
- print
+ print()
def _print_progress(self, name):
self._clear_line(self._last_status_length)
elapsed = time.time() - self._start_time
- if not self._total:
- progress = 0
- else:
+ if self._total:
progress = (self._passed + self._failed) * 100 // self._total
+ else:
+ progress = 0
status = self._templates['status_line'] % {
'passed': self._passed,
'progress': progress,
@@ -209,7 +238,7 @@
}
status = self._truncate(status, 78)
self._last_status_length = len(status)
- print status,
+ print(status, end='')
sys.stdout.flush()
def _truncate(self, string, length):
@@ -235,7 +264,7 @@
super(ColorProgressIndicator, self).__init__(templates)
def _clear_line(self, last_length):
- print "\033[1K\r",
+ print("\033[1K\r", end='')
class MonochromeProgressIndicator(CompactProgressIndicator):
@@ -249,50 +278,11 @@
super(MonochromeProgressIndicator, self).__init__(templates)
def _clear_line(self, last_length):
- print ("\r" + (" " * last_length) + "\r"),
-
-
-class JUnitTestProgressIndicator(ProgressIndicator):
- def __init__(self, junitout, junittestsuite):
- super(JUnitTestProgressIndicator, self).__init__()
- self._requirement = base.DROP_PASS_STDOUT
-
- self.outputter = junit_output.JUnitTestOutput(junittestsuite)
- if junitout:
- self.outfile = open(junitout, "w")
- else:
- self.outfile = sys.stdout
-
- def _on_result_for(self, test, result):
- # TODO(majeski): Support for dummy/grouped results
- fail_text = ""
- output = result.output
- if result.has_unexpected_output:
- stdout = output.stdout.strip()
- if len(stdout):
- fail_text += "stdout:\n%s\n" % stdout
- stderr = output.stderr.strip()
- if len(stderr):
- fail_text += "stderr:\n%s\n" % stderr
- fail_text += "Command: %s" % result.cmd.to_string()
- if output.HasCrashed():
- fail_text += "exit code: %d\n--- CRASHED ---" % output.exit_code
- if output.HasTimedOut():
- fail_text += "--- TIMEOUT ---"
- self.outputter.HasRunTest(
- test_name=str(test),
- test_cmd=result.cmd.to_string(relative=True),
- test_duration=output.duration,
- test_failure=fail_text)
-
- def finished(self):
- self.outputter.FinishAndWrite(self.outfile)
- if self.outfile != sys.stdout:
- self.outfile.close()
+ print(("\r" + (" " * last_length) + "\r"), end='')
class JsonTestProgressIndicator(ProgressIndicator):
- def __init__(self, json_test_results, arch, mode, random_seed):
+ def __init__(self, framework_name, json_test_results, arch, mode):
super(JsonTestProgressIndicator, self).__init__()
# We want to drop stdout/err for all passed tests on the first try, but we
# need to get outputs for all runs after the first one. To accommodate that,
@@ -300,10 +290,10 @@
# keep_output set to True in the RerunProc.
self._requirement = base.DROP_PASS_STDOUT
+ self.framework_name = framework_name
self.json_test_results = json_test_results
self.arch = arch
self.mode = mode
- self.random_seed = random_seed
self.results = []
self.tests = []
@@ -338,12 +328,11 @@
"result": test.output_proc.get_outcome(output),
"expected": test.expected_outcomes,
"duration": output.duration,
-
- # TODO(machenbach): This stores only the global random seed from the
- # context and not possible overrides when using random-seed stress.
- "random_seed": self.random_seed,
+ "random_seed": test.random_seed,
"target_name": test.get_shell(),
"variant": test.variant,
+ "variant_flags": test.variant_flags,
+ "framework_name": self.framework_name,
})
def finished(self):
@@ -361,7 +350,7 @@
float(len(self.tests)))
# Sort tests by duration.
- self.tests.sort(key=lambda (_, duration, cmd): duration, reverse=True)
+ self.tests.sort(key=lambda __duration_cmd: __duration_cmd[1], reverse=True)
slowest_tests = [
{
"name": str(test),
diff --git a/src/v8/tools/testrunner/testproc/rerun.py b/src/v8/tools/testrunner/testproc/rerun.py
index 7f96e02..d085c55 100644
--- a/src/v8/tools/testrunner/testproc/rerun.py
+++ b/src/v8/tools/testrunner/testproc/rerun.py
@@ -19,7 +19,7 @@
self._rerun_total_left = rerun_max_total
def _next_test(self, test):
- self._send_next_subtest(test)
+ return self._send_next_subtest(test)
def _result_for(self, test, subtest, result):
# First result
@@ -34,7 +34,7 @@
results = self._results[test.procid]
results.append(result)
- if self._needs_rerun(test, result):
+ if not self.is_stopped and self._needs_rerun(test, result):
self._rerun[test.procid] += 1
if self._rerun_total_left is not None:
self._rerun_total_left -= 1
@@ -52,7 +52,7 @@
def _send_next_subtest(self, test, run=0):
subtest = self._create_subtest(test, str(run + 1), keep_output=(run != 0))
- self._send_test(subtest)
+ return self._send_test(subtest)
def _finalize_test(self, test):
del self._rerun[test.procid]
diff --git a/src/v8/tools/testrunner/testproc/seed.py b/src/v8/tools/testrunner/testproc/seed.py
new file mode 100644
index 0000000..160eac8
--- /dev/null
+++ b/src/v8/tools/testrunner/testproc/seed.py
@@ -0,0 +1,63 @@
+# 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.
+
+import random
+from collections import defaultdict
+
+from . import base
+from ..utils import random_utils
+
+
+class SeedProc(base.TestProcProducer):
+ def __init__(self, count, seed=None, parallel_subtests=1):
+ """
+ Args:
+ count: How many subtests with different seeds to create for each test.
+ 0 means infinite.
+ seed: seed to use. None means random seed for each subtest.
+ parallel_subtests: How many subtest of each test to run at the same time.
+ """
+ super(SeedProc, self).__init__('Seed')
+ self._count = count
+ self._seed = seed
+ self._last_idx = defaultdict(int)
+ self._todo = defaultdict(int)
+ self._parallel_subtests = parallel_subtests
+ if count:
+ self._parallel_subtests = min(self._parallel_subtests, count)
+
+ def setup(self, requirement=base.DROP_RESULT):
+ super(SeedProc, self).setup(requirement)
+
+ # SeedProc is optimized for dropping the result
+ assert requirement == base.DROP_RESULT
+
+ def _next_test(self, test):
+ is_loaded = False
+ for _ in range(0, self._parallel_subtests):
+ is_loaded |= self._try_send_next_test(test)
+
+ return is_loaded
+
+ def _result_for(self, test, subtest, result):
+ self._todo[test.procid] -= 1
+ if not self._try_send_next_test(test):
+ if not self._todo.get(test.procid):
+ del self._last_idx[test.procid]
+ del self._todo[test.procid]
+ self._send_result(test, None)
+
+ def _try_send_next_test(self, test):
+ def create_subtest(idx):
+ seed = self._seed or random_utils.random_seed()
+ return self._create_subtest(test, idx, random_seed=seed)
+
+ num = self._last_idx[test.procid]
+ if not self._count or num < self._count:
+ num += 1
+ self._todo[test.procid] += 1
+ self._last_idx[test.procid] = num
+ return self._send_test(create_subtest(num))
+
+ return False
diff --git a/src/v8/tools/testrunner/testproc/shard.py b/src/v8/tools/testrunner/testproc/shard.py
index 1caac9f..9475ea1 100644
--- a/src/v8/tools/testrunner/testproc/shard.py
+++ b/src/v8/tools/testrunner/testproc/shard.py
@@ -5,10 +5,21 @@
from . import base
+# Alphabet size determines the hashing radix. Choosing a prime number prevents
+# clustering of the hashes.
+HASHING_ALPHABET_SIZE = 2 ** 7 -1
+
+def radix_hash(capacity, key):
+ h = 0
+ for character in key:
+ h = (h * HASHING_ALPHABET_SIZE + ord(character)) % capacity
+
+ return h
+
+
class ShardProc(base.TestProcFilter):
"""Processor distributing tests between shards.
- It simply passes every n-th test. To be deterministic it has to be placed
- before all processors that generate tests dynamically.
+ It hashes the unique test identifiers uses the hash to shard tests.
"""
def __init__(self, myid, shards_count):
"""
@@ -22,9 +33,6 @@
self._myid = myid
self._shards_count = shards_count
- self._last = 0
def _filter(self, test):
- res = self._last != self._myid
- self._last = (self._last + 1) % self._shards_count
- return res
+ return self._myid != radix_hash(self._shards_count, test.procid)
diff --git a/src/v8/tools/testrunner/testproc/shard_unittest.py b/src/v8/tools/testrunner/testproc/shard_unittest.py
new file mode 100755
index 0000000..33a094e
--- /dev/null
+++ b/src/v8/tools/testrunner/testproc/shard_unittest.py
@@ -0,0 +1,54 @@
+#!/usr/bin/env python
+# Copyright 2019 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 os
+import sys
+import tempfile
+import unittest
+
+# Needed because the test runner contains relative imports.
+TOOLS_PATH = os.path.dirname(os.path.dirname(os.path.dirname(
+ os.path.abspath(__file__))))
+sys.path.append(TOOLS_PATH)
+
+from testrunner.testproc.shard import radix_hash
+
+
+class TestRadixHashing(unittest.TestCase):
+ def test_hash_character_by_radix(self):
+ self.assertEqual(97, radix_hash(capacity=2**32, key="a"))
+
+ def test_hash_character_by_radix_with_capacity(self):
+ self.assertEqual(6, radix_hash(capacity=7, key="a"))
+
+ def test_hash_string(self):
+ self.assertEqual(6, radix_hash(capacity=7, key="ab"))
+
+ def test_hash_test_id(self):
+ self.assertEqual(
+ 5,
+ radix_hash(capacity=7,
+ key="test262/Map/class-private-method-Variant-0-1"))
+
+ def test_hash_boundaries(self):
+ total_variants = 5
+ cases = []
+ for case in [
+ "test262/Map/class-private-method",
+ "test262/Map/class-public-method",
+ "test262/Map/object-retrieval",
+ "test262/Map/object-deletion",
+ "test262/Map/object-creation",
+ "test262/Map/garbage-collection",
+ ]:
+ for variant_index in range(total_variants):
+ cases.append("%s-Variant-%d" % (case, variant_index))
+
+ for case in cases:
+ self.assertTrue(0 <= radix_hash(capacity=7, key=case) < 7)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/src/v8/tools/testrunner/testproc/sigproc.py b/src/v8/tools/testrunner/testproc/sigproc.py
new file mode 100644
index 0000000..f29fa22
--- /dev/null
+++ b/src/v8/tools/testrunner/testproc/sigproc.py
@@ -0,0 +1,34 @@
+# 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.
+
+# for py2/py3 compatibility
+from __future__ import print_function
+
+import signal
+
+from . import base
+from testrunner.local import utils
+
+
+class SignalProc(base.TestProcObserver):
+ def __init__(self):
+ super(SignalProc, self).__init__()
+ self.exit_code = utils.EXIT_CODE_PASS
+
+ def setup(self, *args, **kwargs):
+ super(SignalProc, self).setup(*args, **kwargs)
+ # It should be called after processors are chained together to not loose
+ # catched signal.
+ signal.signal(signal.SIGINT, self._on_ctrlc)
+ signal.signal(signal.SIGTERM, self._on_sigterm)
+
+ def _on_ctrlc(self, _signum, _stack_frame):
+ print('>>> Ctrl-C detected, early abort...')
+ self.exit_code = utils.EXIT_CODE_INTERRUPTED
+ self.stop()
+
+ def _on_sigterm(self, _signum, _stack_frame):
+ print('>>> SIGTERM received, early abort...')
+ self.exit_code = utils.EXIT_CODE_TERMINATED
+ self.stop()
diff --git a/src/v8/tools/testrunner/testproc/timeout.py b/src/v8/tools/testrunner/testproc/timeout.py
new file mode 100644
index 0000000..54dc60e
--- /dev/null
+++ b/src/v8/tools/testrunner/testproc/timeout.py
@@ -0,0 +1,29 @@
+# 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.
+
+import time
+
+from . import base
+
+
+class TimeoutProc(base.TestProcObserver):
+ def __init__(self, duration_sec):
+ super(TimeoutProc, self).__init__()
+ self._duration_sec = duration_sec
+ self._start = time.time()
+
+ def _on_next_test(self, test):
+ self._on_event()
+
+ def _on_result_for(self, test, result):
+ self._on_event()
+
+ def _on_heartbeat(self):
+ self._on_event()
+
+ def _on_event(self):
+ if not self.is_stopped:
+ if time.time() - self._start > self._duration_sec:
+ print('>>> Total timeout reached.')
+ self.stop()
diff --git a/src/v8/tools/testrunner/testproc/variant.py b/src/v8/tools/testrunner/testproc/variant.py
index dba1af9..0164ad8 100644
--- a/src/v8/tools/testrunner/testproc/variant.py
+++ b/src/v8/tools/testrunner/testproc/variant.py
@@ -39,21 +39,22 @@
def _next_test(self, test):
gen = self._variants_gen(test)
self._next_variant[test.procid] = gen
- self._try_send_new_subtest(test, gen)
+ return self._try_send_new_subtest(test, gen)
def _result_for(self, test, subtest, result):
gen = self._next_variant[test.procid]
- self._try_send_new_subtest(test, gen)
+ if not self._try_send_new_subtest(test, gen):
+ self._send_result(test, None)
def _try_send_new_subtest(self, test, variants_gen):
for variant, flags, suffix in variants_gen:
subtest = self._create_subtest(test, '%s-%s' % (variant, suffix),
variant=variant, flags=flags)
- self._send_test(subtest)
- return
+ if self._send_test(subtest):
+ return True
del self._next_variant[test.procid]
- self._send_result(test, None)
+ return False
def _variants_gen(self, test):
"""Generator producing (variant, flags, procid suffix) tuples."""
diff --git a/src/v8/tools/testrunner/testproc/variant_unittest.py b/src/v8/tools/testrunner/testproc/variant_unittest.py
new file mode 100755
index 0000000..56e28c8
--- /dev/null
+++ b/src/v8/tools/testrunner/testproc/variant_unittest.py
@@ -0,0 +1,172 @@
+#!/usr/bin/env python
+# Copyright 2019 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 os
+import sys
+import tempfile
+import unittest
+
+# Needed because the test runner contains relative imports.
+TOOLS_PATH = os.path.dirname(os.path.dirname(os.path.dirname(
+ os.path.abspath(__file__))))
+sys.path.append(TOOLS_PATH)
+
+from testrunner.testproc import base
+from testrunner.testproc.variant import VariantProc
+
+
+class FakeResultObserver(base.TestProcObserver):
+ def __init__(self):
+ super(FakeResultObserver, self).__init__()
+
+ self.results = set()
+
+ def result_for(self, test, result):
+ self.results.add((test, result))
+
+
+class FakeFilter(base.TestProcFilter):
+ def __init__(self, filter_predicate):
+ super(FakeFilter, self).__init__()
+
+ self._filter_predicate = filter_predicate
+
+ self.loaded = set()
+ self.call_counter = 0
+
+ def next_test(self, test):
+ self.call_counter += 1
+
+ if self._filter_predicate(test):
+ return False
+
+ self.loaded.add(test)
+ return True
+
+
+class FakeSuite(object):
+ def __init__(self, name):
+ self.name = name
+
+
+class FakeTest(object):
+ def __init__(self, procid):
+ self.suite = FakeSuite("fake_suite")
+ self.procid = procid
+
+ self.keep_output = False
+
+ def create_subtest(self, proc, subtest_id, **kwargs):
+ variant = kwargs['variant']
+
+ variant.origin = self
+ return variant
+
+
+class FakeVariantGen(object):
+ def __init__(self, variants):
+ self._variants = variants
+
+ def gen(self, test):
+ for variant in self._variants:
+ yield variant, [], "fake_suffix"
+
+
+class TestVariantProcLoading(unittest.TestCase):
+ def setUp(self):
+ self.test = FakeTest("test")
+
+ def _simulate_proc(self, variants):
+ """Expects the list of instantiated test variants to load into the
+ VariantProc."""
+ variants_mapping = {self.test: variants}
+
+ # Creates a Variant processor containing the possible types of test
+ # variants.
+ self.variant_proc = VariantProc(variants=["to_filter", "to_load"])
+ self.variant_proc._variant_gens = {
+ "fake_suite": FakeVariantGen(variants)}
+
+ # FakeFilter only lets tests passing the predicate to be loaded.
+ self.fake_filter = FakeFilter(
+ filter_predicate=(lambda t: t.procid == "to_filter"))
+
+ # FakeResultObserver to verify that VariantProc calls result_for correctly.
+ self.fake_result_observer = FakeResultObserver()
+
+ # Links up processors together to form a test processing pipeline.
+ self.variant_proc._prev_proc = self.fake_result_observer
+ self.fake_filter._prev_proc = self.variant_proc
+ self.variant_proc._next_proc = self.fake_filter
+
+ # Injects the test into the VariantProc
+ is_loaded = self.variant_proc.next_test(self.test)
+
+ # Verifies the behavioral consistency by using the instrumentation in
+ # FakeFilter
+ loaded_variants = list(self.fake_filter.loaded)
+ self.assertEqual(is_loaded, any(loaded_variants))
+ return self.fake_filter.loaded, self.fake_filter.call_counter
+
+ def test_filters_first_two_variants(self):
+ variants = [
+ FakeTest('to_filter'),
+ FakeTest('to_filter'),
+ FakeTest('to_load'),
+ FakeTest('to_load'),
+ ]
+ expected_load_results = {variants[2]}
+
+ load_results, call_count = self._simulate_proc(variants)
+
+ self.assertSetEqual(expected_load_results, load_results)
+ self.assertEqual(call_count, 3)
+
+ def test_stops_loading_after_first_successful_load(self):
+ variants = [
+ FakeTest('to_load'),
+ FakeTest('to_load'),
+ FakeTest('to_filter'),
+ ]
+ expected_load_results = {variants[0]}
+
+ loaded_tests, call_count = self._simulate_proc(variants)
+
+ self.assertSetEqual(expected_load_results, loaded_tests)
+ self.assertEqual(call_count, 1)
+
+ def test_return_result_when_out_of_variants(self):
+ variants = [
+ FakeTest('to_filter'),
+ FakeTest('to_load'),
+ ]
+
+ self._simulate_proc(variants)
+
+ self.variant_proc.result_for(variants[1], None)
+
+ expected_results = {(self.test, None)}
+
+ self.assertSetEqual(expected_results, self.fake_result_observer.results)
+
+ def test_return_result_after_running_variants(self):
+ variants = [
+ FakeTest('to_filter'),
+ FakeTest('to_load'),
+ FakeTest('to_load'),
+ ]
+
+ self._simulate_proc(variants)
+ self.variant_proc.result_for(variants[1], None)
+
+ self.assertSetEqual(set(variants[1:]), self.fake_filter.loaded)
+
+ self.variant_proc.result_for(variants[2], None)
+
+ expected_results = {(self.test, None)}
+ self.assertSetEqual(expected_results, self.fake_result_observer.results)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/src/v8/tools/testrunner/trycatch_loader.js b/src/v8/tools/testrunner/trycatch_loader.js
new file mode 100644
index 0000000..737c8e4
--- /dev/null
+++ b/src/v8/tools/testrunner/trycatch_loader.js
@@ -0,0 +1,42 @@
+// 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.
+
+
+// Wrapper loading javascript tests passed as arguments used by gc fuzzer.
+// It ignores all exceptions and run tests in a separate namespaces.
+//
+// It can't prevent %AbortJS function from aborting execution, so it should be
+// used with d8's --disable-abortjs flag to ignore all possible errors inside
+// tests.
+
+// We use -- as an additional separator for test preamble files and test files.
+// The preamble files (before --) will be loaded in each realm before each
+// test.
+var separator = arguments.indexOf("--")
+var preamble = arguments.slice(0, separator)
+var tests = arguments.slice(separator + 1)
+
+var preambleString = ""
+for (let jstest of preamble) {
+ preambleString += "load(\"" + jstest + "\");"
+}
+
+for (let jstest of tests) {
+ print("Loading " + jstest);
+ let start = performance.now();
+
+ // anonymous function to not populate global namespace.
+ (function () {
+ let realm = Realm.create();
+ try {
+ Realm.eval(realm, preambleString + "load(\"" + jstest + "\");");
+ } catch (err) {
+ // ignore all errors
+ }
+ Realm.dispose(realm);
+ })();
+
+ let durationSec = ((performance.now() - start) / 1000.0).toFixed(2);
+ print("Duration " + durationSec + "s");
+}
diff --git a/src/v8/tools/testrunner/utils/__init__.py b/src/v8/tools/testrunner/utils/__init__.py
new file mode 100644
index 0000000..4433538
--- /dev/null
+++ b/src/v8/tools/testrunner/utils/__init__.py
@@ -0,0 +1,3 @@
+# 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/src/v8/tools/testrunner/utils/dump_build_config_gyp.py b/src/v8/tools/testrunner/utils/dump_build_config_gyp.py
index 7f72627..963b0e2 100644
--- a/src/v8/tools/testrunner/utils/dump_build_config_gyp.py
+++ b/src/v8/tools/testrunner/utils/dump_build_config_gyp.py
@@ -11,6 +11,9 @@
"""
# TODO(machenbach): Remove this when gyp is deprecated.
+# for py2/py3 compatibility
+from __future__ import print_function
+
import json
import os
import sys
@@ -47,7 +50,7 @@
try:
return k, json.loads(v2)
except ValueError as e:
- print(k, v, v2)
+ print((k, v, v2))
raise e
with open(sys.argv[1], 'w') as f:
diff --git a/src/v8/tools/testrunner/utils/random_utils.py b/src/v8/tools/testrunner/utils/random_utils.py
new file mode 100644
index 0000000..0d2cb3f
--- /dev/null
+++ b/src/v8/tools/testrunner/utils/random_utils.py
@@ -0,0 +1,13 @@
+# 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.
+
+import random
+
+
+def random_seed():
+ """Returns random, non-zero seed."""
+ seed = 0
+ while not seed:
+ seed = random.SystemRandom().randint(-2147483648, 2147483647)
+ return seed
diff --git a/src/v8/tools/tick-processor.html b/src/v8/tools/tick-processor.html
index b841cc0..32f8d66 100644
--- a/src/v8/tools/tick-processor.html
+++ b/src/v8/tools/tick-processor.html
@@ -27,10 +27,10 @@
<html lang="en">
<head>
- <meta charset="utf-8"/>
+ <meta charset="utf-8">
<title>V8 Tick Processor</title>
- <style type="text/css">
+ <style>
body {
font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: 10pt;
@@ -53,7 +53,7 @@
<script src="arguments.js"></script>
<script src="tickprocessor.js"></script>
- <script type="text/javascript">
+ <script>
var v8log_content;
var textout;
@@ -89,6 +89,7 @@
ignoreUnknown: false,
separateIc: true,
targetRootFS: '',
+ apkEmbeddedLibrary: '',
nm: 'nm'
};
@@ -100,7 +101,7 @@
var tickProcessor = new TickProcessor(
new (entriesProviders[DEFAULTS.platform])(
- DEFAULTS.nm, DEFAULTS.targetRootFS),
+ DEFAULTS.nm, DEFAULTS.targetRootFS, DEFAULTS.apkEmbeddedLibrary),
DEFAULTS.separateIc, DEFAULTS.callGraphSize,
DEFAULTS.ignoreUnknown, DEFAULTS.stateFilter);
diff --git a/src/v8/tools/tickprocessor-driver.js b/src/v8/tools/tickprocessor-driver.js
index 58844c1..93331cf 100644
--- a/src/v8/tools/tickprocessor-driver.js
+++ b/src/v8/tools/tickprocessor-driver.js
@@ -62,7 +62,8 @@
sourceMap = SourceMap.load(params.sourceMap);
}
var tickProcessor = new TickProcessor(
- new (entriesProviders[params.platform])(params.nm, params.targetRootFS),
+ new (entriesProviders[params.platform])(params.nm, params.targetRootFS,
+ params.apkEmbeddedLibrary),
params.separateIc,
params.separateBytecodes,
params.separateBuiltins,
diff --git a/src/v8/tools/tickprocessor.js b/src/v8/tools/tickprocessor.js
index 057d328..34c2249 100644
--- a/src/v8/tools/tickprocessor.js
+++ b/src/v8/tools/tickprocessor.js
@@ -102,42 +102,43 @@
preprocessJson) {
this.preprocessJson = preprocessJson;
LogReader.call(this, {
- 'shared-library': { parsers: [null, parseInt, parseInt, parseInt],
+ 'shared-library': { parsers: [parseString, parseInt, parseInt, parseInt],
processor: this.processSharedLibrary },
'code-creation': {
- parsers: [null, parseInt, parseInt, parseInt, parseInt,
- null, 'var-args'],
+ parsers: [parseString, parseInt, parseInt, parseInt, parseInt,
+ parseString, parseVarArgs],
processor: this.processCodeCreation },
'code-deopt': {
parsers: [parseInt, parseInt, parseInt, parseInt, parseInt,
- null, null, null],
+ parseString, parseString, parseString],
processor: this.processCodeDeopt },
'code-move': { parsers: [parseInt, parseInt, ],
processor: this.processCodeMove },
'code-delete': { parsers: [parseInt],
processor: this.processCodeDelete },
'code-source-info': {
- parsers: [parseInt, parseInt, parseInt, parseInt, null, null, null],
+ parsers: [parseInt, parseInt, parseInt, parseInt, parseString,
+ parseString, parseString],
processor: this.processCodeSourceInfo },
- 'script': {
- parsers: [parseInt, null, null],
- processor: this.processCodeScript },
+ 'script-source': {
+ parsers: [parseInt, parseString, parseString],
+ processor: this.processScriptSource },
'sfi-move': { parsers: [parseInt, parseInt],
processor: this.processFunctionMove },
'active-runtime-timer': {
- parsers: [null],
+ parsers: [parseString],
processor: this.processRuntimeTimerEvent },
'tick': {
parsers: [parseInt, parseInt, parseInt,
- parseInt, parseInt, 'var-args'],
+ parseInt, parseInt, parseVarArgs],
processor: this.processTick },
- 'heap-sample-begin': { parsers: [null, null, parseInt],
+ 'heap-sample-begin': { parsers: [parseString, parseString, parseInt],
processor: this.processHeapSampleBegin },
- 'heap-sample-end': { parsers: [null, null],
+ 'heap-sample-end': { parsers: [parseString, parseString],
processor: this.processHeapSampleEnd },
- 'timer-event-start' : { parsers: [null, null, null],
+ 'timer-event-start' : { parsers: [parseString, parseString, parseString],
processor: this.advanceDistortion },
- 'timer-event-end' : { parsers: [null, null, null],
+ 'timer-event-end' : { parsers: [parseString, parseString, parseString],
processor: this.advanceDistortion },
// Ignored events.
'profiler': null,
@@ -159,7 +160,6 @@
this.stateFilter_ = stateFilter;
this.runtimeTimerFilter_ = runtimeTimerFilter;
this.sourceMap = sourceMap;
- this.deserializedEntriesNames_ = [];
var ticks = this.ticks_ =
{ total: 0, unaccounted: 0, excluded: 0, gc: 0 };
@@ -298,7 +298,6 @@
TickProcessor.prototype.processCodeCreation = function(
type, kind, timestamp, start, size, name, maybe_func) {
- name = this.deserializedEntriesNames_[start] || name;
if (maybe_func.length) {
var funcAddr = parseInt(maybe_func[0]);
var state = parseState(maybe_func[1]);
@@ -332,7 +331,7 @@
endPos, sourcePositions, inliningPositions, inlinedFunctions);
};
-TickProcessor.prototype.processCodeScript = function(script, url, source) {
+TickProcessor.prototype.processScriptSource = function(script, url, source) {
this.profile_.addScriptSource(script, url, source);
};
@@ -636,23 +635,44 @@
libName, libStart, libEnd, libASLRSlide, processorFunc) {
this.loadSymbols(libName);
- var prevEntry;
+ var lastUnknownSize;
+ var lastAdded;
+
+ function inRange(funcInfo, start, end) {
+ return funcInfo.start >= start && funcInfo.end <= end;
+ }
function addEntry(funcInfo) {
// Several functions can be mapped onto the same address. To avoid
// creating zero-sized entries, skip such duplicates.
// Also double-check that function belongs to the library address space.
- if (prevEntry && !prevEntry.end &&
- prevEntry.start < funcInfo.start &&
- prevEntry.start >= libStart && funcInfo.start <= libEnd) {
- processorFunc(prevEntry.name, prevEntry.start, funcInfo.start);
+
+ if (lastUnknownSize &&
+ lastUnknownSize.start < funcInfo.start) {
+ // Try to update lastUnknownSize based on new entries start position.
+ lastUnknownSize.end = funcInfo.start;
+ if ((!lastAdded || !inRange(lastUnknownSize, lastAdded.start,
+ lastAdded.end)) &&
+ inRange(lastUnknownSize, libStart, libEnd)) {
+ processorFunc(lastUnknownSize.name, lastUnknownSize.start,
+ lastUnknownSize.end);
+ lastAdded = lastUnknownSize;
+ }
}
- if (funcInfo.end &&
- (!prevEntry || prevEntry.start != funcInfo.start) &&
- funcInfo.start >= libStart && funcInfo.end <= libEnd) {
- processorFunc(funcInfo.name, funcInfo.start, funcInfo.end);
+ lastUnknownSize = undefined;
+
+ if (funcInfo.end) {
+ // Skip duplicates that have the same start address as the last added.
+ if ((!lastAdded || lastAdded.start != funcInfo.start) &&
+ inRange(funcInfo, libStart, libEnd)) {
+ processorFunc(funcInfo.name, funcInfo.start, funcInfo.end);
+ lastAdded = funcInfo;
+ }
+ } else {
+ // If a funcInfo doesn't have an end, try to match it up with then next
+ // entry.
+ lastUnknownSize = funcInfo;
}
- prevEntry = funcInfo;
}
while (true) {
@@ -686,11 +706,12 @@
};
-function UnixCppEntriesProvider(nmExec, targetRootFS) {
+function UnixCppEntriesProvider(nmExec, targetRootFS, apkEmbeddedLibrary) {
this.symbols = [];
this.parsePos = 0;
this.nmExec = nmExec;
this.targetRootFS = targetRootFS;
+ this.apkEmbeddedLibrary = apkEmbeddedLibrary;
this.FUNC_RE = /^([0-9a-fA-F]{8,16}) ([0-9a-fA-F]{8,16} )?[tTwW] (.*)$/;
};
inherits(UnixCppEntriesProvider, CppEntriesProvider);
@@ -698,7 +719,13 @@
UnixCppEntriesProvider.prototype.loadSymbols = function(libName) {
this.parsePos = 0;
- libName = this.targetRootFS + libName;
+ if (this.apkEmbeddedLibrary && libName.endsWith('.apk')) {
+ libName = this.apkEmbeddedLibrary;
+ }
+ if (this.targetRootFS) {
+ libName = libName.substring(libName.lastIndexOf('/') + 1);
+ libName = this.targetRootFS + libName;
+ }
try {
this.symbols = [
os.system(this.nmExec, ['-C', '-n', '-S', libName], -1, -1),
@@ -736,8 +763,8 @@
};
-function MacCppEntriesProvider(nmExec, targetRootFS) {
- UnixCppEntriesProvider.call(this, nmExec, targetRootFS);
+function MacCppEntriesProvider(nmExec, targetRootFS, apkEmbeddedLibrary) {
+ UnixCppEntriesProvider.call(this, nmExec, targetRootFS, apkEmbeddedLibrary);
// Note an empty group. It is required, as UnixCppEntriesProvider expects 3 groups.
this.FUNC_RE = /^([0-9a-fA-F]{8,16})() (.*)$/;
};
@@ -759,7 +786,8 @@
};
-function WindowsCppEntriesProvider(_ignored_nmExec, targetRootFS) {
+function WindowsCppEntriesProvider(_ignored_nmExec, targetRootFS,
+ _ignored_apkEmbeddedLibrary) {
this.targetRootFS = targetRootFS;
this.symbols = '';
this.parsePos = 0;
@@ -883,6 +911,8 @@
'Specify the \'nm\' executable to use (e.g. --nm=/my_dir/nm)'],
'--target': ['targetRootFS', '',
'Specify the target root directory for cross environment'],
+ '--apk-embedded-library': ['apkEmbeddedLibrary', '',
+ 'Specify the path of the embedded library for Android traces'],
'--range': ['range', 'auto,auto',
'Specify the range limit as [start],[end]'],
'--distortion': ['distortion', 0,
diff --git a/src/v8/tools/toolchain/BUILD.gn b/src/v8/tools/toolchain/BUILD.gn
new file mode 100644
index 0000000..b252c5e
--- /dev/null
+++ b/src/v8/tools/toolchain/BUILD.gn
@@ -0,0 +1,93 @@
+# 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.
+
+import("//build/toolchain/gcc_toolchain.gni")
+
+gcc_toolchain("mips-bundled") {
+ toolprefix = rebase_path("//tools/mips_toolchain/bin/mips-mti-linux-gnu-",
+ root_build_dir)
+ cc = "${toolprefix}gcc"
+ cxx = "${toolprefix}g++"
+
+ readelf = "${toolprefix}readelf"
+ nm = "${toolprefix}nm"
+ ar = "${toolprefix}ar"
+ ld = cxx
+
+ # Flag that sets endianness
+ extra_ldflags = "-EB"
+ extra_cppflags = "-EB"
+
+ toolchain_args = {
+ current_cpu = "mips"
+ current_os = "linux"
+ is_clang = false
+ }
+}
+
+gcc_toolchain("mips64-bundled") {
+ toolprefix = rebase_path("//tools/mips_toolchain/bin/mips-mti-linux-gnu-",
+ root_build_dir)
+ cc = "${toolprefix}gcc"
+ cxx = "${toolprefix}g++"
+
+ readelf = "${toolprefix}readelf"
+ nm = "${toolprefix}nm"
+ ar = "${toolprefix}ar"
+ ld = cxx
+
+ # Flag that sets endianness and ABI
+ extra_ldflags = "-EB -mabi=64"
+ extra_cppflags = "-EB -mabi=64"
+
+ toolchain_args = {
+ current_cpu = "mips64"
+ current_os = "linux"
+ is_clang = false
+ }
+}
+
+gcc_toolchain("mipsel-bundled") {
+ toolprefix = rebase_path("//tools/mips_toolchain/bin/mips-mti-linux-gnu-",
+ root_build_dir)
+ cc = "${toolprefix}gcc"
+ cxx = "${toolprefix}g++"
+
+ readelf = "${toolprefix}readelf"
+ nm = "${toolprefix}nm"
+ ar = "${toolprefix}ar"
+ ld = cxx
+
+ # Flag that sets endianness
+ extra_ldflags = "-EL"
+ extra_cppflags = "-EL"
+
+ toolchain_args = {
+ current_cpu = "mipsel"
+ current_os = "linux"
+ is_clang = false
+ }
+}
+
+gcc_toolchain("mips64el-bundled") {
+ toolprefix = rebase_path("//tools/mips_toolchain/bin/mips-mti-linux-gnu-",
+ root_build_dir)
+ cc = "${toolprefix}gcc"
+ cxx = "${toolprefix}g++"
+
+ readelf = "${toolprefix}readelf"
+ nm = "${toolprefix}nm"
+ ar = "${toolprefix}ar"
+ ld = cxx
+
+ # Flag that sets endianness and ABI
+ extra_ldflags = "-EL -mabi=64"
+ extra_cppflags = "-EL -mabi=64"
+
+ toolchain_args = {
+ current_cpu = "mips64el"
+ current_os = "linux"
+ is_clang = false
+ }
+}
diff --git a/src/v8/tools/torque/format-torque.py b/src/v8/tools/torque/format-torque.py
new file mode 100755
index 0000000..2150d7e
--- /dev/null
+++ b/src/v8/tools/torque/format-torque.py
@@ -0,0 +1,170 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Copyright 2014 the V8 project authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""This program either generates the parser files for Torque, generating
+the source and header files directly in V8's src directory."""
+
+# for py2/py3 compatibility
+from __future__ import print_function
+
+import subprocess
+import sys
+import re
+from subprocess import Popen, PIPE
+
+kPercentEscape = r'α'; # Unicode alpha
+
+def preprocess(input):
+ input = re.sub(r'(if\s+)constexpr(\s*\()', r'\1/*COxp*/\2', input)
+ input = re.sub(r'(\s+)operator\s*(\'[^\']+\')', r'\1/*_OPE \2*/', input)
+
+ # Mangle typeswitches to look like switch statements with the extra type
+ # information and syntax encoded in comments.
+ input = re.sub(r'(\s+)typeswitch\s*\(', r'\1/*_TYPE*/switch (', input)
+ input = re.sub(r'(\s+)case\s*\(\s*([^\:]+)\s*\)(\s*)\:\s*deferred',
+ r'\1case \2: /*_TSXDEFERRED_*/', input)
+ input = re.sub(r'(\s+)case\s*\(\s*([^\:]+)\s*\)(\s*)\:',
+ r'\1case \2: /*_TSX*/', input)
+ input = re.sub(r'(\s+)case\s*\(\s*([^\s]+)\s*\:\s*([^\:]+)\s*\)(\s*)\:\s*deferred',
+ r'\1case \3: /*_TSVDEFERRED_\2:*/', input)
+ input = re.sub(r'(\s+)case\s*\(\s*([^\s]+)\s*\:\s*([^\:]+)\s*\)(\s*)\:',
+ r'\1case \3: /*_TSV\2:*/', input)
+
+ # Add extra space around | operators to fix union types later.
+ while True:
+ old = input
+ input = re.sub(r'(\w+\s*)\|(\s*\w+)',
+ r'\1|/**/\2', input)
+ if old == input:
+ break;
+
+ input = re.sub(r'\bgenerates\s+\'([^\']+)\'\s*',
+ r' _GeNeRaTeS00_/*\1@*/', input)
+ input = re.sub(r'\bconstexpr\s+\'([^\']+)\'\s*',
+ r' _CoNsExP_/*\1@*/', input)
+ input = re.sub(r'\notherwise',
+ r'\n otherwise', input)
+ input = re.sub(r'(\n\s*\S[^\n]*\s)otherwise',
+ r'\1_OtheSaLi', input)
+ input = re.sub(r'@if\(', r'@iF(', input)
+ input = re.sub(r'@export', r'@eXpOrT', input)
+ input = re.sub(r'js-implicit[ \n]+', r'jS_iMpLiCiT_', input)
+
+ # Special handing of '%' for intrinsics, turn the percent
+ # into a unicode character so that it gets treated as part of the
+ # intrinsic's name if it's already adjacent to it.
+ input = re.sub(r'%([A-Za-z])', kPercentEscape + r'\1', input)
+
+ return input
+
+def postprocess(output):
+ output = re.sub(r'\/\*COxp\*\/', r'constexpr', output)
+ output = re.sub(r'(\S+)\s*: type([,>])', r'\1: type\2', output)
+ output = re.sub(r'(\n\s*)labels( [A-Z])', r'\1 labels\2', output)
+ output = re.sub(r'\/\*_OPE \'([^\']+)\'\*\/', r"operator '\1'", output)
+ output = re.sub(r'\/\*_TYPE\*\/(\s*)switch', r'typeswitch', output)
+ output = re.sub(r'case (\w+)\:\s*\/\*_TSXDEFERRED_\*\/',
+ r'case (\1): deferred', output)
+ output = re.sub(r'case (\w+)\:\s*\/\*_TSX\*\/',
+ r'case (\1):', output)
+ output = re.sub(r'case (\w+)\:\s*\/\*_TSVDEFERRED_([^\:]+)\:\*\/',
+ r'case (\2: \1): deferred', output)
+ output = re.sub(r'case (\w+)\:\s*\/\*_TSV([^\:]+)\:\*\/',
+ r'case (\2: \1):', output)
+ output = re.sub(r'\n_GeNeRaTeS00_\s*\/\*([^@]+)@\*\/',
+ r"\n generates '\1'", output)
+ output = re.sub(r'_GeNeRaTeS00_\s*\/\*([^@]+)@\*\/',
+ r"generates '\1'", output)
+ output = re.sub(r'_CoNsExP_\s*\/\*([^@]+)@\*\/',
+ r"constexpr '\1'", output)
+ output = re.sub(r'\n(\s+)otherwise',
+ r"\n\1 otherwise", output)
+ output = re.sub(r'\n(\s+)_OtheSaLi',
+ r"\n\1otherwise", output)
+ output = re.sub(r'_OtheSaLi',
+ r"otherwise", output)
+ output = re.sub(r'@iF\(', r'@if(', output)
+ output = re.sub(r'@eXpOrT',
+ r"@export", output)
+ output = re.sub(r'jS_iMpLiCiT_',
+ r"js-implicit ", output)
+
+ while True:
+ old = output
+ output = re.sub(r'(\w+)\s{0,1}\|\s{0,1}/\*\*/(\s*\w+)',
+ r'\1 |\2', output)
+ if old == output:
+ break;
+
+ output = re.sub(kPercentEscape, r'%', output)
+
+ return output
+
+def process(filename, lint, should_format):
+ with open(filename, 'r') as content_file:
+ content = content_file.read()
+
+ original_input = content
+
+ if sys.platform.startswith('win'):
+ p = Popen(['clang-format', '-assume-filename=.ts'], stdin=PIPE, stdout=PIPE, stderr=PIPE, shell=True)
+ else:
+ p = Popen(['clang-format', '-assume-filename=.ts'], stdin=PIPE, stdout=PIPE, stderr=PIPE)
+ output, err = p.communicate(preprocess(content))
+ output = postprocess(output)
+ rc = p.returncode
+ if (rc != 0):
+ print("error code " + str(rc) + " running clang-format. Exiting...")
+ sys.exit(rc);
+
+ if (output != original_input):
+ if lint:
+ print(filename + ' requires formatting', file=sys.stderr)
+
+ if should_format:
+ output_file = open(filename, 'w')
+ output_file.write(output);
+ output_file.close()
+
+def print_usage():
+ print('format-torque -i file1[, file2[, ...]]')
+ print(' format and overwrite input files')
+ print('format-torque -l file1[, file2[, ...]]')
+ print(' merely indicate which files need formatting')
+
+def Main():
+ if len(sys.argv) < 3:
+ print("error: at least 2 arguments required")
+ print_usage();
+ sys.exit(-1)
+
+ def is_option(arg):
+ return arg in ['-i', '-l', '-il']
+
+ should_format = lint = False
+ use_stdout = True
+
+ flag, files = sys.argv[1], sys.argv[2:]
+ if is_option(flag):
+ if '-i' == flag:
+ should_format = True
+ elif '-l' == flag:
+ lint = True
+ else:
+ lint = True
+ should_format = True
+ else:
+ print("error: -i and/or -l flags must be specified")
+ print_usage();
+ sys.exit(-1);
+
+ for filename in files:
+ process(filename, lint, should_format)
+
+ return 0
+
+if __name__ == '__main__':
+ sys.exit(Main());
diff --git a/src/v8/tools/torque/vim-torque/README.md b/src/v8/tools/torque/vim-torque/README.md
new file mode 100644
index 0000000..fbdef0f
--- /dev/null
+++ b/src/v8/tools/torque/vim-torque/README.md
@@ -0,0 +1,33 @@
+# V8 Torque syntax support for vim
+
+This plugin adds syntax highlighting support for the V8 Torque domain-specific
+language.
+
+## Installation
+
+Installation depends on your favorite plugin manager.
+
+**Pathogen:**
+
+Run
+
+```sh
+ln -s $V8/tools/torque/vim-torque ~/.vim/bundle/vim-torque
+# or ~/.config/nvim/bundle/vim-torque for Neovim
+```
+
+**Vundle:**
+
+Add this line to your `.vimrc` or `~/.config/nvim/init.vim`.
+
+```vim
+Plugin 'file:///path/to/v8/tools/torque/vim-torque'
+```
+
+**vim-plug:**
+
+Add this line to your `.vimrc` or `~/.config/nvim/init.vim`.
+
+```vim
+Plug '~/path/to/v8/tools/torque/vim-torque'
+```
diff --git a/src/v8/tools/torque/vim-torque/ftdetect/torque.vim b/src/v8/tools/torque/vim-torque/ftdetect/torque.vim
new file mode 100644
index 0000000..ead2c5e
--- /dev/null
+++ b/src/v8/tools/torque/vim-torque/ftdetect/torque.vim
@@ -0,0 +1 @@
+au BufRead,BufNewFile *.tq set filetype=torque
diff --git a/src/v8/tools/torque/vim-torque/syntax/torque.vim b/src/v8/tools/torque/vim-torque/syntax/torque.vim
new file mode 100644
index 0000000..592e870
--- /dev/null
+++ b/src/v8/tools/torque/vim-torque/syntax/torque.vim
@@ -0,0 +1,84 @@
+" 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.
+
+if !exists("main_syntax")
+ " quit when a syntax file was already loaded
+ if exists("b:current_syntax")
+ finish
+ endif
+ let main_syntax = 'torque'
+elseif exists("b:current_syntax") && b:current_syntax == "torque"
+ finish
+endif
+
+let s:cpo_save = &cpo
+set cpo&vim
+
+syn match torqueLineComment "\/\/.*" contains=@Spell
+syn region torqueComment start="/\*" end="\*/" contains=@Spell
+syn region torqueStringS start=+'+ skip=+\\\\\|\\'+ end=+'\|$+
+
+syn keyword torqueAssert assert check debug unreachable
+syn keyword torqueAtom True False Undefined TheHole Null
+syn keyword torqueBoolean true false
+syn keyword torqueBranch break continue goto
+syn keyword torqueConditional if else typeswitch otherwise
+syn match torqueConstant /\v<[A-Z][A-Z0-9_]+>/
+syn match torqueConstant /\v<k[A-Z][A-Za-z0-9]*>/
+syn keyword torqueFunction macro builtin runtime intrinsic
+syn keyword torqueKeyword cast convert from_constexpr min max unsafe_cast js-implicit implicit
+syn keyword torqueLabel case
+syn keyword torqueMatching try label catch
+syn keyword torqueModifier extern javascript constexpr transitioning transient weak export
+syn match torqueNumber /\v<[0-9]+(\.[0-9]*)?>/
+syn match torqueNumber /\v<0x[0-9a-fA-F]+>/
+syn keyword torqueOperator operator
+syn keyword torqueRel extends generates labels
+syn keyword torqueRepeat while for of
+syn keyword torqueStatement return tail
+syn keyword torqueStructure module struct type class
+syn keyword torqueVariable const let
+
+syn match torqueType /\v(\<)@<=([A-Za-z][0-9A-Za-z_]*)(>)@=/
+syn match torqueType /\v(:\s*(constexpr\s*)?)@<=([A-Za-z][0-9A-Za-z_]*)/
+" Include some common types also
+syn keyword torqueType Arguments void never
+syn keyword torqueType Tagged Smi HeapObject Object
+syn keyword torqueType int32 uint32 int64 intptr uintptr float32 float64
+syn keyword torqueType bool string
+syn keyword torqueType int31 RawPtr AbstractCode Code JSReceiver Context String
+syn keyword torqueType Oddball HeapNumber Number BigInt Numeric Boolean JSProxy
+syn keyword torqueType JSObject JSArray JSFunction JSBoundFunction Callable Map
+
+hi def link torqueAssert Statement
+hi def link torqueAtom Constant
+hi def link torqueBoolean Boolean
+hi def link torqueBranch Conditional
+hi def link torqueComment Comment
+hi def link torqueConditional Conditional
+hi def link torqueConstant Constant
+hi def link torqueFunction Function
+hi def link torqueKeyword Keyword
+hi def link torqueLabel Label
+hi def link torqueLineComment Comment
+hi def link torqueMatching Exception
+hi def link torqueModifier StorageClass
+hi def link torqueNumber Number
+hi def link torqueOperator Operator
+hi def link torqueRel StorageClass
+hi def link torqueRepeat Repeat
+hi def link torqueStatement Statement
+hi def link torqueStringS String
+hi def link torqueStructure Structure
+hi def link torqueType Type
+hi def link torqueVariable Identifier
+
+let b:current_syntax = "torque"
+if main_syntax == 'torque'
+ unlet main_syntax
+endif
+let &cpo = s:cpo_save
+unlet s:cpo_save
+
+" vim: set ts=8:
diff --git a/src/v8/tools/trace-maps-processor.py b/src/v8/tools/trace-maps-processor.py
index bf8c8a8..4a29eab 100755
--- a/src/v8/tools/trace-maps-processor.py
+++ b/src/v8/tools/trace-maps-processor.py
@@ -3,6 +3,9 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+# for py2/py3 compatibility
+from __future__ import print_function
+
import sys
@@ -169,4 +172,4 @@
reasons_list.append("%8d %s" % (reasons[r], r))
reasons_list.sort(reverse=True)
for r in reasons_list[:20]:
- print r
+ print(r)
diff --git a/src/v8/tools/tracing/proto-converter/.nvmrc b/src/v8/tools/tracing/proto-converter/.nvmrc
new file mode 100644
index 0000000..a7b32ad
--- /dev/null
+++ b/src/v8/tools/tracing/proto-converter/.nvmrc
@@ -0,0 +1 @@
+v11.9.0
diff --git a/src/v8/tools/tracing/proto-converter/package-lock.json b/src/v8/tools/tracing/proto-converter/package-lock.json
new file mode 100644
index 0000000..52e52b3
--- /dev/null
+++ b/src/v8/tools/tracing/proto-converter/package-lock.json
@@ -0,0 +1,123 @@
+{
+ "requires": true,
+ "lockfileVersion": 1,
+ "dependencies": {
+ "@protobufjs/aspromise": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
+ "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=",
+ "dev": true
+ },
+ "@protobufjs/base64": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
+ "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
+ "dev": true
+ },
+ "@protobufjs/codegen": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
+ "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==",
+ "dev": true
+ },
+ "@protobufjs/eventemitter": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
+ "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=",
+ "dev": true
+ },
+ "@protobufjs/fetch": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
+ "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=",
+ "dev": true,
+ "requires": {
+ "@protobufjs/aspromise": "^1.1.1",
+ "@protobufjs/inquire": "^1.1.0"
+ }
+ },
+ "@protobufjs/float": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
+ "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=",
+ "dev": true
+ },
+ "@protobufjs/inquire": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
+ "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=",
+ "dev": true
+ },
+ "@protobufjs/path": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
+ "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=",
+ "dev": true
+ },
+ "@protobufjs/pool": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
+ "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=",
+ "dev": true
+ },
+ "@protobufjs/utf8": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
+ "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=",
+ "dev": true
+ },
+ "@types/long": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.0.tgz",
+ "integrity": "sha512-1w52Nyx4Gq47uuu0EVcsHBxZFJgurQ+rTKS3qMHxR1GY2T8c2AJYd6vZoZ9q1rupaDjU0yT+Jc2XTyXkjeMA+Q==",
+ "dev": true
+ },
+ "@types/node": {
+ "version": "11.11.4",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.4.tgz",
+ "integrity": "sha512-02tIL+QIi/RW4E5xILdoAMjeJ9kYq5t5S2vciUdFPXv/ikFTb0zK8q9vXkg4+WAJuYXGiVT1H28AkD2C+IkXVw==",
+ "dev": true
+ },
+ "long": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
+ "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==",
+ "dev": true
+ },
+ "protobufjs": {
+ "version": "6.8.8",
+ "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.8.8.tgz",
+ "integrity": "sha512-AAmHtD5pXgZfi7GMpllpO3q1Xw1OYldr+dMUlAnffGTAhqkg72WdmSY71uKBF/JuyiKs8psYbtKrhi0ASCD8qw==",
+ "dev": true,
+ "requires": {
+ "@protobufjs/aspromise": "^1.1.2",
+ "@protobufjs/base64": "^1.1.2",
+ "@protobufjs/codegen": "^2.0.4",
+ "@protobufjs/eventemitter": "^1.1.0",
+ "@protobufjs/fetch": "^1.1.0",
+ "@protobufjs/float": "^1.0.2",
+ "@protobufjs/inquire": "^1.1.0",
+ "@protobufjs/path": "^1.1.2",
+ "@protobufjs/pool": "^1.1.0",
+ "@protobufjs/utf8": "^1.1.0",
+ "@types/long": "^4.0.0",
+ "@types/node": "^10.1.0",
+ "long": "^4.0.0"
+ },
+ "dependencies": {
+ "@types/node": {
+ "version": "10.14.1",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.1.tgz",
+ "integrity": "sha512-Rymt08vh1GaW4vYB6QP61/5m/CFLGnFZP++bJpWbiNxceNa6RBipDmb413jvtSf/R1gg5a/jQVl2jY4XVRscEA==",
+ "dev": true
+ }
+ }
+ },
+ "typescript": {
+ "version": "3.3.4000",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.3.4000.tgz",
+ "integrity": "sha512-jjOcCZvpkl2+z7JFn0yBOoLQyLoIkNZAs/fYJkUG6VKy6zLPHJGfQJYFHzibB6GJaF/8QrcECtlQ5cpvRHSMEA==",
+ "dev": true
+ }
+ }
+}
diff --git a/src/v8/tools/tracing/proto-converter/package.json b/src/v8/tools/tracing/proto-converter/package.json
new file mode 100644
index 0000000..41401a1
--- /dev/null
+++ b/src/v8/tools/tracing/proto-converter/package.json
@@ -0,0 +1,11 @@
+{
+ "private": true,
+ "scripts": {
+ "build": "tsc proto-to-json.ts"
+ },
+ "devDependencies": {
+ "@types/node": "^11.11.4",
+ "protobufjs": "^6.8.8",
+ "typescript": "^3.3.4000"
+ }
+}
diff --git a/src/v8/tools/tracing/proto-converter/proto-to-json.ts b/src/v8/tools/tracing/proto-converter/proto-to-json.ts
new file mode 100644
index 0000000..2427410
--- /dev/null
+++ b/src/v8/tools/tracing/proto-converter/proto-to-json.ts
@@ -0,0 +1,132 @@
+// Copyright 2019 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 * as fs from 'fs';
+import * as path from 'path';
+import { Root } from 'protobufjs';
+
+// Requirements: node 10.4.0+, npm
+
+// Setup:
+// (nvm is optional, you can also just install node manually)
+// $ nvm use
+// $ npm install
+// $ npm run build
+
+// Usage: node proto-to-json.js path_to_trace.proto input_file output_file
+
+// Converts a binary proto file to a 'Trace Event Format' compatible .json file
+// that can be used with chrome://tracing. Documentation of this format:
+// https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU
+
+// Attempts to reproduce the logic of the JSONTraceWriter in V8 in terms of the
+// JSON fields it will include/exclude based on the data present in the trace
+// event.
+
+// TODO(petermarshall): Replace with Array#flat once it lands in Node.js.
+const flatten = <T>(a: T[], b: T[]) => { a.push(...b); return a; }
+
+// Convert a string representing an int or uint (64 bit) to a Number or throw
+// if the value won't fit.
+function parseIntOrThrow(int: string) {
+ if (BigInt(int) > Number.MAX_SAFE_INTEGER) {
+ throw new Error("Loss of int precision");
+ }
+ return Number(int);
+}
+
+function uint64AsHexString(val : string) : string {
+ return "0x" + BigInt(val).toString(16);
+}
+
+function parseArgValue(arg: any) : any {
+ if (arg.jsonValue) {
+ return JSON.parse(arg.jsonValue);
+ }
+ if (typeof arg.stringValue !== 'undefined') {
+ return arg.stringValue;
+ }
+ if (typeof arg.uintValue !== 'undefined') {
+ return parseIntOrThrow(arg.uintValue);
+ }
+ if (typeof arg.intValue !== 'undefined') {
+ return parseIntOrThrow(arg.intValue);
+ }
+ if (typeof arg.boolValue !== 'undefined') {
+ return arg.boolValue;
+ }
+ if (typeof arg.doubleValue !== 'undefined') {
+ // Handle [-]Infinity and NaN which protobufjs outputs as strings here.
+ return typeof arg.doubleValue === 'string' ?
+ arg.doubleValue : Number(arg.doubleValue);
+ }
+ if (typeof arg.pointerValue !== 'undefined') {
+ return uint64AsHexString(arg.pointerValue);
+ }
+}
+
+// These come from
+// https://cs.chromium.org/chromium/src/base/trace_event/common/trace_event_common.h
+const TRACE_EVENT_FLAG_HAS_ID: number = 1 << 1;
+const TRACE_EVENT_FLAG_FLOW_IN: number = 1 << 8;
+const TRACE_EVENT_FLAG_FLOW_OUT: number = 1 << 9;
+
+async function main() {
+ const root = new Root();
+ const { resolvePath } = root;
+ const numDirectoriesToStrip = 2;
+ let initialOrigin: string|null;
+ root.resolvePath = (origin, target) => {
+ if (!origin) {
+ initialOrigin = target;
+ for (let i = 0; i <= numDirectoriesToStrip; i++) {
+ initialOrigin = path.dirname(initialOrigin);
+ }
+ return resolvePath(origin, target);
+ }
+ return path.resolve(initialOrigin!, target);
+ };
+ const traceProto = await root.load(process.argv[2]);
+ const Trace = traceProto.lookupType("Trace");
+ const payload = await fs.promises.readFile(process.argv[3]);
+ const msg = Trace.decode(payload).toJSON();
+ const output = {
+ traceEvents: msg.packet
+ .filter((packet: any) => !!packet.chromeEvents)
+ .map((packet: any) => packet.chromeEvents.traceEvents)
+ .map((traceEvents: any) => traceEvents.map((e: any) => {
+
+ const bind_id = (e.flags & (TRACE_EVENT_FLAG_FLOW_IN |
+ TRACE_EVENT_FLAG_FLOW_OUT)) ? e.bindId : undefined;
+ const scope = (e.flags & TRACE_EVENT_FLAG_HAS_ID) && e.scope ?
+ e.scope : undefined;
+
+ return {
+ pid: e.processId,
+ tid: e.threadId,
+ ts: parseIntOrThrow(e.timestamp),
+ tts: parseIntOrThrow(e.threadTimestamp),
+ ph: String.fromCodePoint(e.phase),
+ cat: e.categoryGroupName,
+ name: e.name,
+ dur: parseIntOrThrow(e.duration),
+ tdur: parseIntOrThrow(e.threadDuration),
+ bind_id: bind_id,
+ flow_in: e.flags & TRACE_EVENT_FLAG_FLOW_IN ? true : undefined,
+ flow_out: e.flags & TRACE_EVENT_FLAG_FLOW_OUT ? true : undefined,
+ scope: scope,
+ id: (e.flags & TRACE_EVENT_FLAG_HAS_ID) ?
+ uint64AsHexString(e.id) : undefined,
+ args: (e.args || []).reduce((js_args: any, proto_arg: any) => {
+ js_args[proto_arg.name] = parseArgValue(proto_arg);
+ return js_args;
+ }, {})
+ };
+ }))
+ .reduce(flatten, [])
+ };
+ await fs.promises.writeFile(process.argv[4], JSON.stringify(output, null, 2));
+}
+
+main().catch(console.error);
diff --git a/src/v8/tools/tracing/proto-converter/tsconfig.json b/src/v8/tools/tracing/proto-converter/tsconfig.json
new file mode 100644
index 0000000..defc4ef
--- /dev/null
+++ b/src/v8/tools/tracing/proto-converter/tsconfig.json
@@ -0,0 +1,12 @@
+{
+ "compilerOptions": {
+ "target": "ES2018",
+ "module": "commonjs",
+ "lib": ["es6","dom"],
+ "outDir": "lib",
+ "rootDir": "src",
+ "strict": true,
+ "esModuleInterop": true,
+ "resolveJsonModule": true
+ }
+}
diff --git a/src/v8/tools/try_perf.py b/src/v8/tools/try_perf.py
index cad836b..2c9c382 100755
--- a/src/v8/tools/try_perf.py
+++ b/src/v8/tools/try_perf.py
@@ -3,35 +3,39 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+# for py2/py3 compatibility
+from __future__ import print_function
+
import argparse
import os
import subprocess
import sys
BOTS = {
- '--arm32': 'v8_arm32_perf_try',
+ '--chromebook': 'v8_chromebook_perf_try',
'--linux32': 'v8_linux32_perf_try',
'--linux64': 'v8_linux64_perf_try',
'--linux64_atom': 'v8_linux64_atom_perf_try',
- '--linux64_haswell': 'v8_linux64_haswell_perf_try',
'--nexus5': 'v8_nexus5_perf_try',
'--nexus7': 'v8_nexus7_perf_try',
- '--nexus9': 'v8_nexus9_perf_try',
- '--nexus10': 'v8_nexus10_perf_try',
+ '--nokia1': 'v8_nokia1_perf_try',
+ '--odroid32': 'v8_odroid32_perf_try',
+ '--pixel2': 'v8_pixel2_perf_try',
}
DEFAULT_BOTS = [
- 'v8_arm32_perf_try',
+ 'v8_chromebook_perf_try',
'v8_linux32_perf_try',
- 'v8_linux64_haswell_perf_try',
- 'v8_nexus10_perf_try',
+ 'v8_linux64_perf_try',
]
PUBLIC_BENCHMARKS = [
'arewefastyet',
+ 'ares6',
+ 'blazor',
+ 'compile',
'embenchen',
'emscripten',
- 'compile',
'jetstream',
'jsbench',
'jstests',
@@ -46,6 +50,7 @@
'sunspider',
'unity',
'wasm',
+ 'web-tooling-benchmark',
]
V8_BASE = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
@@ -59,17 +64,28 @@
help='Revision (use full hash!) to use for the try job; '
'default: the revision will be determined by the '
'try server; see its waterfall for more info')
+ parser.add_argument('-v', '--verbose', action='store_true',
+ help='Print debug information')
+ parser.add_argument('-c', '--confidence-level', type=float,
+ help='Repeatedly runs each benchmark until specified '
+ 'confidence level is reached. The value is interpreted '
+ 'as the number of standard deviations from the mean that '
+ 'all values must lie within. Typical values are 1, 2 and '
+ '3 and correspond to 68%%, 95%% and 99.7%% probability '
+ 'that the measured value is within 0.1%% of the true '
+ 'value. Larger values result in more retries and thus '
+ 'longer runtime, but also provide more reliable results.')
for option in sorted(BOTS):
parser.add_argument(
option, dest='bots', action='append_const', const=BOTS[option],
help='Add %s trybot.' % BOTS[option])
options = parser.parse_args()
if not options.bots:
- print 'No trybots specified. Using default %s.' % ','.join(DEFAULT_BOTS)
+ print('No trybots specified. Using default %s.' % ','.join(DEFAULT_BOTS))
options.bots = DEFAULT_BOTS
if not options.benchmarks:
- print 'Please specify the benchmarks to run as arguments.'
+ print('Please specify the benchmarks to run as arguments.')
return 1
for benchmark in options.benchmarks:
@@ -77,7 +93,7 @@
print ('%s not found in our benchmark list. The respective trybot might '
'fail, unless you run something this script isn\'t aware of. '
'Available public benchmarks: %s' % (benchmark, PUBLIC_BENCHMARKS))
- print 'Proceed anyways? [Y/n] ',
+ print('Proceed anyways? [Y/n] ', end=' ')
answer = sys.stdin.readline().strip()
if answer != "" and answer != "Y" and answer != "y":
return 1
@@ -89,15 +105,20 @@
subprocess.check_output(
'update_depot_tools', shell=True, stderr=subprocess.STDOUT, cwd=V8_BASE)
- cmd = ['git cl try -m internal.client.v8']
+ cmd = ['git cl try', '-B', 'luci.v8-internal.try']
cmd += ['-b %s' % bot for bot in options.bots]
- if options.revision: cmd += ['-r %s' % options.revision]
+ if options.revision:
+ cmd.append('-r %s' % options.revision)
benchmarks = ['"%s"' % benchmark for benchmark in options.benchmarks]
- cmd += ['-p \'testfilter=[%s]\'' % ','.join(benchmarks)]
+ cmd.append('-p \'testfilter=[%s]\'' % ','.join(benchmarks))
if options.extra_flags:
- cmd += ['-p \'extra_flags="%s"\'' % options.extra_flags]
+ cmd.append('-p \'extra_flags="%s"\'' % options.extra_flags)
+ if options.confidence_level:
+ cmd.append('-p confidence_level=%f' % options.confidence_level)
+ if options.verbose:
+ cmd.append('-vv')
+ print('Running %s' % ' '.join(cmd))
subprocess.check_call(' '.join(cmd), shell=True, cwd=V8_BASE)
-
if __name__ == '__main__': # pragma: no cover
sys.exit(main())
diff --git a/src/v8/tools/turbolizer-perf.py b/src/v8/tools/turbolizer-perf.py
index c90a117..d35f538 100644
--- a/src/v8/tools/turbolizer-perf.py
+++ b/src/v8/tools/turbolizer-perf.py
@@ -2,6 +2,9 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+# for py2/py3 compatibility
+from __future__ import print_function
+
import os
import sys
import json
@@ -25,7 +28,7 @@
known_addrs.add(result.group(0))
def trace_end():
- print json.dumps(json_obj)
+ print(json.dumps(json_obj))
def process_event(param_dict):
addr = "0x%x" % int(param_dict['sample']['ip'])
diff --git a/src/v8/tools/turbolizer/README.md b/src/v8/tools/turbolizer/README.md
index d4010d8..c5ee729 100644
--- a/src/v8/tools/turbolizer/README.md
+++ b/src/v8/tools/turbolizer/README.md
@@ -8,12 +8,22 @@
Turbolizer consumes .json files that are generated per-function by d8 by passing
the '--trace-turbo' command-line flag.
-Host the turbolizer locally by starting a web server that serves the contents of
-the turbolizer directory, e.g.:
+Turbolizer is build using npm:
- cd src/tools/turbolizer
+ cd tools/turbolizer
+ npm i
+ npm run-script build
+
+Afterwards, turbolizer can be hosted locally by starting a web server that serve
+the contents of the turbolizer directory, e.g.:
+
python -m SimpleHTTPServer 8000
+To deploy to a directory that can be hosted the script `deploy` can be used. The
+following command will deploy to the directory /www/turbolizer:
+
+ npm run deploy -- /www/turbolizer
+
Optionally, profiling data generated by the perf tools in linux can be merged
with the .json files using the turbolizer-perf.py file included. The following
command is an example of using the perf script:
@@ -60,3 +70,11 @@
There are many options that can be added to the first command, for example '-e'
can be used to specify the counting of specific events (default: cycles), as
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.
diff --git a/src/v8/tools/turbolizer/deploy.sh b/src/v8/tools/turbolizer/deploy.sh
new file mode 100755
index 0000000..011c2f4
--- /dev/null
+++ b/src/v8/tools/turbolizer/deploy.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+
+DEST=$1
+
+if [ ! -d "$DEST" ]; then
+ echo -e "Destination \"$DEST\" is not a directory. Run\n\tnpm deploy -- [destination-directory]"
+ exit 1
+fi
+
+function copy() {
+ echo -n "."
+ cp "$@"
+}
+
+echo -n "Deploying..."
+copy *.png $DEST/
+copy *.css $DEST/
+copy index.html $DEST/
+copy info-view.html $DEST/
+copy -R build $DEST/
+copy -R img $DEST/
+echo "done!"
+
+echo "Deployed to $DEST/."
diff --git a/src/v8/tools/turbolizer/img/hide-selected-icon.png b/src/v8/tools/turbolizer/img/hide-selected-icon.png
new file mode 100644
index 0000000..207cdbb
--- /dev/null
+++ b/src/v8/tools/turbolizer/img/hide-selected-icon.png
Binary files differ
diff --git a/src/v8/tools/turbolizer/img/hide-unselected-icon.png b/src/v8/tools/turbolizer/img/hide-unselected-icon.png
new file mode 100644
index 0000000..15617b0
--- /dev/null
+++ b/src/v8/tools/turbolizer/img/hide-unselected-icon.png
Binary files differ
diff --git a/src/v8/tools/turbolizer/img/layout-icon.png b/src/v8/tools/turbolizer/img/layout-icon.png
new file mode 100644
index 0000000..95a517a
--- /dev/null
+++ b/src/v8/tools/turbolizer/img/layout-icon.png
Binary files differ
diff --git a/src/v8/tools/turbolizer/img/show-all-icon.png b/src/v8/tools/turbolizer/img/show-all-icon.png
new file mode 100644
index 0000000..50fc845
--- /dev/null
+++ b/src/v8/tools/turbolizer/img/show-all-icon.png
Binary files differ
diff --git a/src/v8/tools/turbolizer/img/show-control-icon.png b/src/v8/tools/turbolizer/img/show-control-icon.png
new file mode 100644
index 0000000..4238bee
--- /dev/null
+++ b/src/v8/tools/turbolizer/img/show-control-icon.png
Binary files differ
diff --git a/src/v8/tools/turbolizer/img/toggle-hide-dead-icon.png b/src/v8/tools/turbolizer/img/toggle-hide-dead-icon.png
new file mode 100644
index 0000000..ac72bb9
--- /dev/null
+++ b/src/v8/tools/turbolizer/img/toggle-hide-dead-icon.png
Binary files differ
diff --git a/src/v8/tools/turbolizer/img/toggle-types-icon.png b/src/v8/tools/turbolizer/img/toggle-types-icon.png
new file mode 100644
index 0000000..8fead8f
--- /dev/null
+++ b/src/v8/tools/turbolizer/img/toggle-types-icon.png
Binary files differ
diff --git a/src/v8/tools/turbolizer/img/zoom-selection-icon.png b/src/v8/tools/turbolizer/img/zoom-selection-icon.png
new file mode 100644
index 0000000..12dc3e3
--- /dev/null
+++ b/src/v8/tools/turbolizer/img/zoom-selection-icon.png
Binary files differ
diff --git a/src/v8/tools/turbolizer/index.html b/src/v8/tools/turbolizer/index.html
index 552e837..f970a6d 100644
--- a/src/v8/tools/turbolizer/index.html
+++ b/src/v8/tools/turbolizer/index.html
@@ -1,99 +1,46 @@
-<!DOCTYPE HTML>
+<!DOCTYPE html>
<html>
- <head>
- <title>Turbolizer</title>
- <link rel="stylesheet" href="turbo-visualizer.css" />
- </head>
- <body>
- <div id="left">
- <div id='source-text'>
- <pre id='source-text-pre'\>
- </div>
- </div>
- <div class="resizer-left"></div>
- <div id="middle" class="resizable-pane">
- <div id="graph-toolbox-anchor">
- <span id="graph-toolbox">
- <input id="layout" type="image" title="layout graph" src="layout-icon.png"
- alt="layout graph" class="button-input">
- <input id="show-all" type="image" title="show all nodes" src="expand-all.jpg"
- alt="show all nodes" class="button-input">
- <input id="hide-dead" type="image" title="only live nodes" src="live.png"
- alt="only live nodes" class="button-input">
- <input id="hide-unselected" type="image" title="hide unselected nodes"
- src="hide-unselected.png" alt="hide unselected nodes" class="button-input">
- <input id="hide-selected" type="image" title="hide selected nodes"
- src="hide-selected.png" alt="hide selected nodes" class="button-input">
- <input id="zoom-selection" type="image" title="zoom to selection"
- src="search.png" alt="zoom to selection" class="button-input">
- <input id="toggle-types" type="image" title="show/hide types"
- src="types.png" alt="show/hide types" class="button-input">
- <input id="search-input" type="text" title="search nodes for regex"
- alt="search node for regex" class="search-input"
- placeholder="find with regexp…">
- <select id="display-selector">
- <option disabled selected>(please open a file)</option>
- </select>
- </span>
- </div>
+<!--
+Copyright 2019 the V8 project authors. All rights reserved. Use of this source
+code is governed by a BSD-style license that can be found in the LICENSE file.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>V8 Turbolizer</title>
+ <link rel="stylesheet" href="turbo-visualizer.css">
+ <link rel="stylesheet" href="tabs.css">
+ <link rel="stylesheet" href="prettify.css">
+ <link rel="icon" type="image/png" href="turbolizer.png">
+</head>
- <div id="load-file">
- <input type="file" id="hidden-file-upload">
- <input id="upload" type="image" title="load graph" class="button-input"
- src="upload-icon.png" alt="upload graph">
- </div>
- <div id="empty" width="100%" height="100%"></div>
- <div id="graph" width="100%" height="100%"></div>
- <div id="schedule" width="100%">
- <pre id="schedule-text-pre" class='prettyprint prettyprinted'>
- <ul id="schedule-list" class='nolinenums noindent'>
- </ul>
- </pre>
- </div>
- <div id='text-placeholder' width="0px" height="0px" style="position: absolute; top:100000px;" ><svg><text text-anchor="right">
- <tspan white-space="inherit" id="text-measure"/>
- </text></svg></div>
+<body>
+ <div id="left" class="content"></div>
+ <div id="resizer-left" class="resizer"></div>
+ <div id="middle">
+
+ <div id="load-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 class="resizer-right"></div>
- <div id="right">
- <div id='disassembly'>
- <pre id='disassembly-text-pre' class='prettyprint prettyprinted'>
- <ul id='disassembly-list' class='nolinenums noindent'>
- </ul>
- </pre>
- </div>
- </div>
- <div id="source-collapse" class="collapse-pane">
- <input id="source-expand" type="image" title="show source"
- src="right-arrow.png" class="button-input invisible">
- <input id="source-shrink" type="image" title="hide source"
- src="left-arrow.png" class="button-input">
- </div>
- <div id="disassembly-collapse" class="collapse-pane">
- <input id="disassembly-expand" type="image" title="show disassembly"
- src="left-arrow.png" class="button-input invisible">
- <input id="disassembly-shrink" type="image" title="hide disassembly"
- src="right-arrow.png" class="button-input">
- </div>
- <script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js"></script>
- <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
- <script src="https://cdn.jsdelivr.net/filesaver.js/0.1/FileSaver.min.js"></script>
- <script src="monkey.js"></script>
- <script src="util.js"></script>
- <script src="lang-disassembly.js"></script>
- <script src="node.js"></script>
- <script src="edge.js"></script>
- <script src="selection.js"></script>
- <script src="selection-broker.js"></script>
- <script src="constants.js"></script>
- <script src="view.js"></script>
- <script src="text-view.js"></script>
- <script src="empty-view.js"></script>
- <script src="code-view.js"></script>
- <script src="graph-layout.js"></script>
- <script src="graph-view.js"></script>
- <script src="schedule-view.js"></script>
- <script src="disassembly-view.js"></script>
- <script src="turbo-visualizer.js"></script>
- </body>
+ </div>
+ <div id="resizer-right" class="resizer"></div>
+ <div id="right" class="content"></div>
+ <div id="source-collapse" class="collapse-pane">
+ <input id="source-expand" type="image" title="show source" src="right-arrow.png" class="button-input invisible">
+ <input id="source-shrink" type="image" title="hide source" src="left-arrow.png" class="button-input">
+ </div>
+ <div id="disassembly-collapse" class="collapse-pane">
+ <input id="disassembly-expand" type="image" title="show disassembly" src="left-arrow.png" class="button-input invisible">
+ <input id="disassembly-shrink" type="image" title="hide disassembly" src="right-arrow.png" class="button-input">
+ </div>
+ <div id="text-placeholder" width="0" height="0" style="position: absolute; top:100000px;">
+ <svg>
+ <text text-anchor="right">
+ <tspan white-space="inherit" id="text-measure">
+ </text>
+ </svg>
+ </div>
+ <script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js"></script>
+ <script src="build/turbolizer.js"></script>
+</body>
</html>
diff --git a/src/v8/tools/turbolizer/info-view.html b/src/v8/tools/turbolizer/info-view.html
new file mode 100644
index 0000000..b523e65
--- /dev/null
+++ b/src/v8/tools/turbolizer/info-view.html
@@ -0,0 +1,119 @@
+<div>This is view contains hints about available keyboard shortcuts.</div>
+<div class="info-topic" id="info-global">
+ <div class="info-topic-header">Global shortcuts</div>
+ <div class="info-topic-content">
+ <table>
+ <tr>
+ <td>CTRL+L</td>
+ <td>Open load file dialog.</td>
+ </tr>
+ <tr>
+ <td>CTRL+R</td>
+ <td>Reload turbolizer (Chrome shortcut)</td>
+ </tr>
+ </table>
+ </div>
+</div>
+<div class="info-topic" id="info-graph-view">
+ <div class="info-topic-header">Graph view</div>
+ <div class="info-topic-content">
+ <table>
+ <tr>
+ <td>r</td>
+ <td>Relayout graph</td>
+ </tr>
+ <tr>
+ <td>a</td>
+ <td>Select all nodes</td>
+ </tr>
+ <tr>
+ <td>/</td>
+ <td>Select search box</td>
+ </tr>
+ </table>
+ </div>
+</div>
+<div class="info-topic" id="info-graph-nodes">
+ <div class="info-topic-header">TurboFan graph nodes</div>
+ <div class="info-topic-content">
+ <div>The following commands transform node selections, i.e. each operation will be applied
+ to each node in the current selection and the union of the resulting nodes will become the
+ new selection.</div>
+ <table>
+ <tr>
+ <td>UP</td>
+ <td>Select all input nodes</td>
+ </tr>
+ <tr>
+ <td>DOWN</td>
+ <td>Select all output nodes</td>
+ </tr>
+ <tr>
+ <td>1-9</td>
+ <td>Select input node 1-9</td>
+ </tr>
+ <tr>
+ <td>CTRL+1-9</td>
+ <td>Toggle input edge 1-9</td>
+ </tr>
+ <tr>
+ <td>c</td>
+ <td>Select control output node</td>
+ </tr>
+ <tr>
+ <td>e</td>
+ <td>Select effect output node</td>
+ </tr>
+ <tr>
+ <td>i</td>
+ <td>Reveal node's input nodes</td>
+ </tr>
+ <tr>
+ <td>o</td>
+ <td>Reveal node's output nodes</td>
+ </tr>
+ <tr>
+ <td>s</td>
+ <td>Select node's origin node</td>
+ </tr>
+ <tr>
+ <td>/</td>
+ <td>Select search box</td>
+ </tr>
+ </table>
+ </div>
+</div>
+<div class="info-topic" id="info-graph-search">
+ <div class="info-topic-header">Graph search</div>
+ <div class="info-topic-content">
+ <table>
+ <tr>
+ <td>ENTER</td>
+ <td>Select nodes according to regular expression. Invisible nodes are included depending on the state of the
+ checkbox "only visible".</td>
+ </tr>
+ <tr>
+ <td>CTRL+ENTER</td>
+ <td>Select nodes according to regular expression, always including invisible nodes regardless of checkbox.</td>
+ </tr>
+ </table>
+ <div style="font-weight: bold">
+ Useful patterns
+ </div>
+ <table>
+ <tr>
+ <td>IfTrue</td>
+ <td>Select nodes which have 'IfTrue' in title or description.</td>
+ </tr>
+ <tr>
+ <td>^42:</td>
+ <td>Select exactly the node with id 14.</td>
+ </tr>
+ <tr>
+ <td>Origin: #42 </td>
+ <td>Select nodes which were created while node with id 42 was reduced. This is inaccurate if the node was
+ changed in-place.</td>
+ </tr>
+ </table>
+ </div>
+</div>
diff --git a/src/v8/tools/turbolizer/package-lock.json b/src/v8/tools/turbolizer/package-lock.json
new file mode 100644
index 0000000..9c8049f
--- /dev/null
+++ b/src/v8/tools/turbolizer/package-lock.json
@@ -0,0 +1,3568 @@
+{
+ "name": "turbolizer",
+ "version": "0.1.0",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "@koa/cors": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/@koa/cors/-/cors-2.2.2.tgz",
+ "integrity": "sha512-Ollvsy3wB8+7R9w6hPVzlj3wekF6nK+IHpHj7faSPVXCkahqCwNEPp9+0C4b51RDkdpHjevLEGLOKuVjqtXgSQ==",
+ "dev": true
+ },
+ "@types/d3": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/@types/d3/-/d3-5.5.0.tgz",
+ "integrity": "sha512-Bz9EAhWnaO93jLYSAT13blgzwP5Z0grO5THBOXSMeWHIIFHA7ntJSLpHSCr1kDtQunEZKCYT9OfE+4lYY/PwlA==",
+ "requires": {
+ "@types/d3-array": "*",
+ "@types/d3-axis": "*",
+ "@types/d3-brush": "*",
+ "@types/d3-chord": "*",
+ "@types/d3-collection": "*",
+ "@types/d3-color": "*",
+ "@types/d3-contour": "*",
+ "@types/d3-dispatch": "*",
+ "@types/d3-drag": "*",
+ "@types/d3-dsv": "*",
+ "@types/d3-ease": "*",
+ "@types/d3-fetch": "*",
+ "@types/d3-force": "*",
+ "@types/d3-format": "*",
+ "@types/d3-geo": "*",
+ "@types/d3-hierarchy": "*",
+ "@types/d3-interpolate": "*",
+ "@types/d3-path": "*",
+ "@types/d3-polygon": "*",
+ "@types/d3-quadtree": "*",
+ "@types/d3-random": "*",
+ "@types/d3-scale": "*",
+ "@types/d3-scale-chromatic": "*",
+ "@types/d3-selection": "*",
+ "@types/d3-shape": "*",
+ "@types/d3-time": "*",
+ "@types/d3-time-format": "*",
+ "@types/d3-timer": "*",
+ "@types/d3-transition": "*",
+ "@types/d3-voronoi": "*",
+ "@types/d3-zoom": "*"
+ }
+ },
+ "@types/d3-array": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-1.2.4.tgz",
+ "integrity": "sha512-3r1fOAAb+SGfcOGXty/LGvoP0ovMec4UtGNUyHOSzYyvSGpmt+eNMxLowol/3HryusevznSfcHZebEShXMwsZA=="
+ },
+ "@types/d3-axis": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-1.0.11.tgz",
+ "integrity": "sha512-cuigApCyCwYJxaQPghj+BqaxzbdRdT/lpZBMtF7EuEIJ61NMQ8yvGnqFvHCIgJEmUu2Wb2wiZqy9kiHi3Ddftg==",
+ "requires": {
+ "@types/d3-selection": "*"
+ }
+ },
+ "@types/d3-brush": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-1.0.9.tgz",
+ "integrity": "sha512-mAx8IVc0luUHfk51pl0UN1vzybnAzLMUsvIwLt3fbsqqPkSXr+Pu1AxOPPeyNc27LhHJnfH/LCV7Jlv+Yzqu1A==",
+ "requires": {
+ "@types/d3-selection": "*"
+ }
+ },
+ "@types/d3-chord": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-1.0.8.tgz",
+ "integrity": "sha512-F0ftYOo7FenAIxsRjXLt8vbij0NLDuVcL+xaGY7R9jUmF2Mrpj1T5XukBI9Cad+Ei7YSxEWREIO+CYcaKCl2qQ=="
+ },
+ "@types/d3-collection": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-collection/-/d3-collection-1.0.7.tgz",
+ "integrity": "sha512-vR3BT0GwHc5y93Jv6bxn3zoxP/vGu+GdXu/r1ApjbP9dLk9I2g6NiV7iP/QMQSuFZd0It0n/qWrfXHxCWwHIkg=="
+ },
+ "@types/d3-color": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-1.2.1.tgz",
+ "integrity": "sha512-xwb1tqvYNWllbHuhMFhiXk63Imf+QNq/dJdmbXmr2wQVnwGenCuj3/0IWJ9hdIFQIqzvhT7T37cvx93jtAsDbQ=="
+ },
+ "@types/d3-contour": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-1.3.0.tgz",
+ "integrity": "sha512-AUCUIjEnC5lCGBM9hS+MryRaFLIrPls4Rbv6ktqbd+TK/RXZPwOy9rtBWmGpbeXcSOYCJTUDwNJuEnmYPJRxHQ==",
+ "requires": {
+ "@types/d3-array": "*",
+ "@types/geojson": "*"
+ }
+ },
+ "@types/d3-dispatch": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-1.0.6.tgz",
+ "integrity": "sha512-xyWJQMr832vqhu6fD/YqX+MSFBWnkxasNhcStvlhqygXxj0cKqPft0wuGoH5TIq5ADXgP83qeNVa4R7bEYN3uA=="
+ },
+ "@types/d3-drag": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-1.2.2.tgz",
+ "integrity": "sha512-+UKFeaMVTfSQvMO0PTzOyLXSr7OZbF2Rx1iNVwo2XsyiOsd4MSuLyJKUwRmGn67044QpbNzr+VD6/8iBBLExWw==",
+ "requires": {
+ "@types/d3-selection": "*"
+ }
+ },
+ "@types/d3-dsv": {
+ "version": "1.0.34",
+ "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-1.0.34.tgz",
+ "integrity": "sha512-/grhPLPFJ17GxH18EB8OSOlqcsLahz1xlKb08cVUu3OP83wBPxfoX2otVvLJDTL6BEP0kyTNsA2SdGrRhWwSBQ=="
+ },
+ "@types/d3-ease": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-1.0.7.tgz",
+ "integrity": "sha1-k6MBhovp4VBh89RDQ7GrP4rLbwk="
+ },
+ "@types/d3-fetch": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-1.1.4.tgz",
+ "integrity": "sha512-POR6AHGEjUk8VjHhU2HfcKxVKnZUIhhHjU65greJs34NlfmWfaDxE+6+ABeMsRCAWa/DRTRNe+1ExuMPBwb7/Q==",
+ "requires": {
+ "@types/d3-dsv": "*"
+ }
+ },
+ "@types/d3-force": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-1.1.1.tgz",
+ "integrity": "sha512-ePkELuaFWY4yOuf+Bvx5Xd+ihFiYG4bdnW0BlvigovIm8Sob2t76e9RGO6lybQbv6AlW9Icn9HuZ9fmdzEoJyg=="
+ },
+ "@types/d3-format": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-1.3.0.tgz",
+ "integrity": "sha512-ZiY4j3iJvAdOwzwW24WjlZbUNvqOsnPAMfPBmdXqxj3uKJbrzBlRrdGl5uC89pZpFs9Dc92E81KcwG2uEgkIZA=="
+ },
+ "@types/d3-geo": {
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-1.10.3.tgz",
+ "integrity": "sha512-hfdaxM2L0wA9mDZrrSf2o+DyhEpnJYCiAN+lHFtpfZOVCQrYBA5g33sGRpUbAvjSMyO5jkHbftMWPEhuCMChSg==",
+ "requires": {
+ "@types/geojson": "*"
+ }
+ },
+ "@types/d3-hierarchy": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-1.1.5.tgz",
+ "integrity": "sha512-DKhqURrURt2c7MsF9sHiF2wrWf2+yZR4Q9oIG026t/ZY4VWoM0Yd7UonaR+rygyReWcFSEjKC/+5A27TgD8R8g=="
+ },
+ "@types/d3-interpolate": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-1.3.0.tgz",
+ "integrity": "sha512-Ng4ds7kPSvP/c3W3J5PPUQlgewif1tGBqCeh5lgY+UG82Y7H9zQ8c2gILsEFDLg7wRGOwnuKZ940Q/LSN14w9w==",
+ "requires": {
+ "@types/d3-color": "*"
+ }
+ },
+ "@types/d3-path": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-1.0.7.tgz",
+ "integrity": "sha512-U8dFRG+8WhkLJr2sxZ9Cw/5WeRgBnNqMxGdA1+Z0+ZG6tK0s75OQ4OXnxeyfKuh6E4wQPY8OAKr1+iNDx01BEQ=="
+ },
+ "@types/d3-polygon": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-1.0.6.tgz",
+ "integrity": "sha512-E6Kyodn9JThgLq20nxSbEce9ow5/ePgm9PX2EO6W1INIL4DayM7cFaiG10DStuamjYAd0X4rntW2q+GRjiIktw=="
+ },
+ "@types/d3-quadtree": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-1.0.6.tgz",
+ "integrity": "sha512-sphVuDdiSIaxLt9kQgebJW98pTktQ/xuN7Ysd8X68Rnjeg/q8+c36/ShlqU52qoKg9nob/JEHH1uQMdxURZidQ=="
+ },
+ "@types/d3-random": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-1.1.1.tgz",
+ "integrity": "sha512-jUPeBq1XKK9/5XasTvy5QAUwFeMsjma2yt/nP02yC2Tijovx7i/W5776U/HZugxc5SSmtpx4Z3g9KFVon0QrjQ=="
+ },
+ "@types/d3-scale": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-2.1.0.tgz",
+ "integrity": "sha512-vLzRDF5lRxZdCLUOvmw90pkiuSsZdgroBQaat0Ov7Z7OnO9iJsPSm/TZw3wW6m2z/NhIn1E4N0RLNfEi1k4kAA==",
+ "requires": {
+ "@types/d3-time": "*"
+ }
+ },
+ "@types/d3-scale-chromatic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-1.3.0.tgz",
+ "integrity": "sha512-JqQH5uu1kmdQEa6XSu7NYzQM71lL1YreBPS5o8SnmEDcBRKL6ooykXa8iFPPOEUiTah25ydi+cTrbsogBSMNSQ=="
+ },
+ "@types/d3-selection": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-1.3.4.tgz",
+ "integrity": "sha512-WQ6Ivy7VuUlZ/Grqc8493ZxC+y/fpvZLy5+8ELvmCr2hll8eJPUqC05l6fgRRA7kjqlpbH7lbmvY6pRKf6yzxw=="
+ },
+ "@types/d3-shape": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-1.2.7.tgz",
+ "integrity": "sha512-b2jpGcddOseeNxchaR1SNLqA5xZAbgKix3cXiFeuGeYIEAEUu91UbtelCxOHIUTbNURFnjcbkf4plRbejNzVaQ==",
+ "requires": {
+ "@types/d3-path": "*"
+ }
+ },
+ "@types/d3-time": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-1.0.9.tgz",
+ "integrity": "sha512-m+D4NbQdDlTVaO7QgXAnatR3IDxQYDMBtRhgSCi5rs9R1LPq1y7/2aqa1FJ2IWjFm1mOV63swDxonnCDlHgHMA=="
+ },
+ "@types/d3-time-format": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-2.1.0.tgz",
+ "integrity": "sha512-/myT3I7EwlukNOX2xVdMzb8FRgNzRMpsZddwst9Ld/VFe6LyJyRp0s32l/V9XoUzk+Gqu56F/oGk6507+8BxrA=="
+ },
+ "@types/d3-timer": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-1.0.8.tgz",
+ "integrity": "sha512-AKUgQ/nljUFcUO2P3gK24weVI5XwUTdJvjoh8gJ0yxT4aJ+d7t2Or3TB+k9dEYl14BAjoj32D0ky+YzQSVszfg=="
+ },
+ "@types/d3-transition": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-1.1.3.tgz",
+ "integrity": "sha512-1EukXNuVu/z2G1GZpZagzFJnie9C5zze17ox/vhTgGXNy46rYAm4UkhLLlUeeZ1ndq88k95SOeC8898RpKMLOQ==",
+ "requires": {
+ "@types/d3-selection": "*"
+ }
+ },
+ "@types/d3-voronoi": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/@types/d3-voronoi/-/d3-voronoi-1.1.8.tgz",
+ "integrity": "sha512-zqNhW7QsYQGlfOdrwPNPG3Wk64zUa4epKRurkJ/dVc6oeXrB+iTDt8sRZ0KZKOOXvvfa1dcdB0e45TZeLBiodQ=="
+ },
+ "@types/d3-zoom": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-1.7.3.tgz",
+ "integrity": "sha512-Tz7+z4+Id0MxERw/ozinC5QHJmGLARs9Mpi/7VVfiR+9AHcFGe9q+fjQa30/oPNY8WPuCh5p5uuXmBYAJ3y91Q==",
+ "requires": {
+ "@types/d3-interpolate": "*",
+ "@types/d3-selection": "*"
+ }
+ },
+ "@types/estree": {
+ "version": "0.0.39",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
+ "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw=="
+ },
+ "@types/geojson": {
+ "version": "7946.0.4",
+ "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.4.tgz",
+ "integrity": "sha512-MHmwBtCb7OCv1DSivz2UNJXPGU/1btAWRKlqJ2saEhVJkpkvqHMMaOpKg0v4sAbDWSQekHGvPVMM8nQ+Jen03Q=="
+ },
+ "@types/json5": {
+ "version": "0.0.29",
+ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
+ "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=",
+ "dev": true,
+ "optional": true
+ },
+ "@types/node": {
+ "version": "10.12.18",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.18.tgz",
+ "integrity": "sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ=="
+ },
+ "JSONStream": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz",
+ "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==",
+ "dev": true,
+ "requires": {
+ "jsonparse": "^1.2.0",
+ "through": ">=2.2.7 <3"
+ }
+ },
+ "accepts": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz",
+ "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=",
+ "dev": true,
+ "requires": {
+ "mime-types": "~2.1.18",
+ "negotiator": "0.6.1"
+ }
+ },
+ "ansi-escape-sequences": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-escape-sequences/-/ansi-escape-sequences-4.0.0.tgz",
+ "integrity": "sha512-v+0wW9Wezwsyb0uF4aBVCjmSqit3Ru7PZFziGF0o2KwTvN2zWfTi3BRLq9EkJFdg3eBbyERXGTntVpBxH1J68Q==",
+ "dev": true,
+ "requires": {
+ "array-back": "^2.0.0"
+ }
+ },
+ "ansi-regex": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+ "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "any-promise": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
+ "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=",
+ "dev": true
+ },
+ "argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dev": true,
+ "requires": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "argv-tools": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/argv-tools/-/argv-tools-0.1.1.tgz",
+ "integrity": "sha512-Cc0dBvx4dvrjjKpyDA6w8RlNAw8Su30NvZbWl/Tv9ZALEVlLVkWQiHMi84Q0xNfpVuSaiQbYkdmWK8g1PLGhKw==",
+ "dev": true,
+ "requires": {
+ "array-back": "^2.0.0",
+ "find-replace": "^2.0.1"
+ }
+ },
+ "arr-diff": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
+ "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA="
+ },
+ "arr-flatten": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz",
+ "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg=="
+ },
+ "arr-union": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz",
+ "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ="
+ },
+ "array-back": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz",
+ "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==",
+ "dev": true,
+ "requires": {
+ "typical": "^2.6.1"
+ },
+ "dependencies": {
+ "typical": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz",
+ "integrity": "sha1-XAgOXWYcu+OCWdLnCjxyU+hziB0=",
+ "dev": true
+ }
+ }
+ },
+ "array-unique": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
+ "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg="
+ },
+ "arrify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
+ "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=",
+ "dev": true
+ },
+ "assertion-error": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
+ "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
+ "dev": true
+ },
+ "assign-symbols": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
+ "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c="
+ },
+ "async-limiter": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
+ "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==",
+ "dev": true
+ },
+ "atob": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
+ "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg=="
+ },
+ "babel-code-frame": {
+ "version": "6.26.0",
+ "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
+ "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=",
+ "dev": true,
+ "requires": {
+ "chalk": "^1.1.3",
+ "esutils": "^2.0.2",
+ "js-tokens": "^3.0.2"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+ "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
+ "dev": true
+ },
+ "chalk": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^2.2.1",
+ "escape-string-regexp": "^1.0.2",
+ "has-ansi": "^2.0.0",
+ "strip-ansi": "^3.0.0",
+ "supports-color": "^2.0.0"
+ }
+ },
+ "supports-color": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+ "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+ "dev": true
+ }
+ }
+ },
+ "balanced-match": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
+ "dev": true
+ },
+ "base": {
+ "version": "0.11.2",
+ "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz",
+ "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==",
+ "requires": {
+ "cache-base": "^1.0.1",
+ "class-utils": "^0.3.5",
+ "component-emitter": "^1.2.1",
+ "define-property": "^1.0.0",
+ "isobject": "^3.0.1",
+ "mixin-deep": "^1.2.0",
+ "pascalcase": "^0.1.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+ "requires": {
+ "is-descriptor": "^1.0.0"
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ }
+ }
+ },
+ "basic-auth": {
+ "version": "1.1.0",
+ "resolved": "http://registry.npmjs.org/basic-auth/-/basic-auth-1.1.0.tgz",
+ "integrity": "sha1-RSIe5Cn37h5QNb4/UVM/HN/SmIQ=",
+ "dev": true
+ },
+ "batch": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
+ "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=",
+ "dev": true
+ },
+ "brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "requires": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "braces": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
+ "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
+ "requires": {
+ "arr-flatten": "^1.1.0",
+ "array-unique": "^0.3.2",
+ "extend-shallow": "^2.0.1",
+ "fill-range": "^4.0.0",
+ "isobject": "^3.0.1",
+ "repeat-element": "^1.1.2",
+ "snapdragon": "^0.8.1",
+ "snapdragon-node": "^2.0.1",
+ "split-string": "^3.0.2",
+ "to-regex": "^3.0.1"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "browser-stdout": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
+ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
+ "dev": true
+ },
+ "buffer-from": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
+ "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
+ "dev": true
+ },
+ "builtin-modules": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.0.0.tgz",
+ "integrity": "sha512-hMIeU4K2ilbXV6Uv93ZZ0Avg/M91RaKXucQ+4me2Do1txxBDyDZWCBa5bJSLqoNTRpXTLwEzIk1KmloenDDjhg=="
+ },
+ "byte-size": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/byte-size/-/byte-size-4.0.4.tgz",
+ "integrity": "sha512-82RPeneC6nqCdSwCX2hZUz3JPOvN5at/nTEw/CMf05Smu3Hrpo9Psb7LjN+k+XndNArG1EY8L4+BM3aTM4BCvw==",
+ "dev": true
+ },
+ "bytes": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
+ "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=",
+ "dev": true
+ },
+ "cache-base": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
+ "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==",
+ "requires": {
+ "collection-visit": "^1.0.0",
+ "component-emitter": "^1.2.1",
+ "get-value": "^2.0.6",
+ "has-value": "^1.0.0",
+ "isobject": "^3.0.1",
+ "set-value": "^2.0.0",
+ "to-object-path": "^0.3.0",
+ "union-value": "^1.0.0",
+ "unset-value": "^1.0.0"
+ }
+ },
+ "cache-content-type": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/cache-content-type/-/cache-content-type-1.0.1.tgz",
+ "integrity": "sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==",
+ "dev": true,
+ "requires": {
+ "mime-types": "^2.1.18",
+ "ylru": "^1.2.0"
+ }
+ },
+ "chai": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz",
+ "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==",
+ "dev": true,
+ "requires": {
+ "assertion-error": "^1.1.0",
+ "check-error": "^1.0.2",
+ "deep-eql": "^3.0.1",
+ "get-func-name": "^2.0.0",
+ "pathval": "^1.1.0",
+ "type-detect": "^4.0.5"
+ }
+ },
+ "chalk": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz",
+ "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "check-error": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
+ "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=",
+ "dev": true
+ },
+ "class-utils": {
+ "version": "0.3.6",
+ "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz",
+ "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==",
+ "requires": {
+ "arr-union": "^3.1.0",
+ "define-property": "^0.2.5",
+ "isobject": "^3.0.0",
+ "static-extend": "^0.1.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ }
+ }
+ },
+ "cli-commands": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/cli-commands/-/cli-commands-0.4.0.tgz",
+ "integrity": "sha512-zAvJlR7roeMgpUIhMDYATYL90vz+9ffuyPr0+qq4LzcZ0Jq+gM+H1KdYKxerc6U2nhitiDEx79YiJlXdrooEOA==",
+ "dev": true,
+ "requires": {
+ "command-line-args": "^5.0.2",
+ "command-line-commands": "^2.0.1"
+ }
+ },
+ "co": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
+ "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=",
+ "dev": true
+ },
+ "co-body": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/co-body/-/co-body-6.0.0.tgz",
+ "integrity": "sha512-9ZIcixguuuKIptnY8yemEOuhb71L/lLf+Rl5JfJEUiDNJk0e02MBt7BPxR2GEh5mw8dPthQYR4jPI/BnS1MQgw==",
+ "dev": true,
+ "requires": {
+ "inflation": "^2.0.0",
+ "qs": "^6.5.2",
+ "raw-body": "^2.3.3",
+ "type-is": "^1.6.16"
+ }
+ },
+ "collection-visit": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
+ "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=",
+ "requires": {
+ "map-visit": "^1.0.0",
+ "object-visit": "^1.0.0"
+ }
+ },
+ "color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dev": true,
+ "requires": {
+ "color-name": "1.1.3"
+ }
+ },
+ "color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+ "dev": true
+ },
+ "command-line-args": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.0.2.tgz",
+ "integrity": "sha512-/qPcbL8zpqg53x4rAaqMFlRV4opN3pbla7I7k9x8kyOBMQoGT6WltjN6sXZuxOXw6DgdK7Ad+ijYS5gjcr7vlA==",
+ "dev": true,
+ "requires": {
+ "argv-tools": "^0.1.1",
+ "array-back": "^2.0.0",
+ "find-replace": "^2.0.1",
+ "lodash.camelcase": "^4.3.0",
+ "typical": "^2.6.1"
+ },
+ "dependencies": {
+ "typical": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz",
+ "integrity": "sha1-XAgOXWYcu+OCWdLnCjxyU+hziB0=",
+ "dev": true
+ }
+ }
+ },
+ "command-line-commands": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/command-line-commands/-/command-line-commands-2.0.1.tgz",
+ "integrity": "sha512-m8c2p1DrNd2ruIAggxd/y6DgygQayf6r8RHwchhXryaLF8I6koYjoYroVP+emeROE9DXN5b9sP1Gh+WtvTTdtQ==",
+ "dev": true,
+ "requires": {
+ "array-back": "^2.0.0"
+ }
+ },
+ "command-line-usage": {
+ "version": "5.0.5",
+ "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-5.0.5.tgz",
+ "integrity": "sha512-d8NrGylA5oCXSbGoKz05FkehDAzSmIm4K03S5VDh4d5lZAtTWfc3D1RuETtuQCn8129nYfJfDdF7P/lwcz1BlA==",
+ "dev": true,
+ "requires": {
+ "array-back": "^2.0.0",
+ "chalk": "^2.4.1",
+ "table-layout": "^0.4.3",
+ "typical": "^2.6.1"
+ },
+ "dependencies": {
+ "typical": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz",
+ "integrity": "sha1-XAgOXWYcu+OCWdLnCjxyU+hziB0=",
+ "dev": true
+ }
+ }
+ },
+ "commander": {
+ "version": "2.15.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
+ "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag=="
+ },
+ "common-log-format": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/common-log-format/-/common-log-format-0.1.4.tgz",
+ "integrity": "sha512-BXcgq+wzr2htmBmnT7cL7YHzPAWketWbr4kozjoM9kWe4sk3+zMgjcH0HO+EddjDlEw2LZysqLpVRwbF318tDw==",
+ "dev": true
+ },
+ "component-emitter": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
+ "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY="
+ },
+ "compressible": {
+ "version": "2.0.15",
+ "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.15.tgz",
+ "integrity": "sha512-4aE67DL33dSW9gw4CI2H/yTxqHLNcxp0yS6jB+4h+wr3e43+1z7vm0HU9qXOH8j+qjKuL8+UtkOxYQSMq60Ylw==",
+ "dev": true,
+ "requires": {
+ "mime-db": ">= 1.36.0 < 2"
+ }
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+ "dev": true
+ },
+ "content-disposition": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
+ "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=",
+ "dev": true
+ },
+ "content-type": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
+ "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==",
+ "dev": true
+ },
+ "cookies": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.7.2.tgz",
+ "integrity": "sha512-J2JjH9T3PUNKPHknprxgCrCaZshIfxW2j49gq1E1CP5Micj1LppWAR2y9EHSQAzEiX84zOsScWNwUZ0b/ChlMw==",
+ "dev": true,
+ "requires": {
+ "depd": "~1.1.2",
+ "keygrip": "~1.0.2"
+ }
+ },
+ "copy-descriptor": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
+ "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40="
+ },
+ "copy-to": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/copy-to/-/copy-to-2.0.1.tgz",
+ "integrity": "sha1-JoD7uAaKSNCGVrYJgJK9r8kG9KU=",
+ "dev": true
+ },
+ "core-util-is": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
+ "dev": true
+ },
+ "d3": {
+ "version": "5.7.0",
+ "resolved": "https://registry.npmjs.org/d3/-/d3-5.7.0.tgz",
+ "integrity": "sha512-8KEIfx+dFm8PlbJN9PI0suazrZ41QcaAufsKE9PRcqYPWLngHIyWJZX96n6IQKePGgeSu0l7rtlueSSNq8Zc3g==",
+ "requires": {
+ "d3-array": "1",
+ "d3-axis": "1",
+ "d3-brush": "1",
+ "d3-chord": "1",
+ "d3-collection": "1",
+ "d3-color": "1",
+ "d3-contour": "1",
+ "d3-dispatch": "1",
+ "d3-drag": "1",
+ "d3-dsv": "1",
+ "d3-ease": "1",
+ "d3-fetch": "1",
+ "d3-force": "1",
+ "d3-format": "1",
+ "d3-geo": "1",
+ "d3-hierarchy": "1",
+ "d3-interpolate": "1",
+ "d3-path": "1",
+ "d3-polygon": "1",
+ "d3-quadtree": "1",
+ "d3-random": "1",
+ "d3-scale": "2",
+ "d3-scale-chromatic": "1",
+ "d3-selection": "1",
+ "d3-shape": "1",
+ "d3-time": "1",
+ "d3-time-format": "2",
+ "d3-timer": "1",
+ "d3-transition": "1",
+ "d3-voronoi": "1",
+ "d3-zoom": "1"
+ }
+ },
+ "d3-array": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz",
+ "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw=="
+ },
+ "d3-axis": {
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-1.0.12.tgz",
+ "integrity": "sha512-ejINPfPSNdGFKEOAtnBtdkpr24c4d4jsei6Lg98mxf424ivoDP2956/5HDpIAtmHo85lqT4pruy+zEgvRUBqaQ=="
+ },
+ "d3-brush": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-1.0.6.tgz",
+ "integrity": "sha512-lGSiF5SoSqO5/mYGD5FAeGKKS62JdA1EV7HPrU2b5rTX4qEJJtpjaGLJngjnkewQy7UnGstnFd3168wpf5z76w==",
+ "requires": {
+ "d3-dispatch": "1",
+ "d3-drag": "1",
+ "d3-interpolate": "1",
+ "d3-selection": "1",
+ "d3-transition": "1"
+ }
+ },
+ "d3-chord": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-1.0.6.tgz",
+ "integrity": "sha512-JXA2Dro1Fxw9rJe33Uv+Ckr5IrAa74TlfDEhE/jfLOaXegMQFQTAgAw9WnZL8+HxVBRXaRGCkrNU7pJeylRIuA==",
+ "requires": {
+ "d3-array": "1",
+ "d3-path": "1"
+ }
+ },
+ "d3-collection": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.7.tgz",
+ "integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A=="
+ },
+ "d3-color": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.2.3.tgz",
+ "integrity": "sha512-x37qq3ChOTLd26hnps36lexMRhNXEtVxZ4B25rL0DVdDsGQIJGB18S7y9XDwlDD6MD/ZBzITCf4JjGMM10TZkw=="
+ },
+ "d3-contour": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-1.3.2.tgz",
+ "integrity": "sha512-hoPp4K/rJCu0ladiH6zmJUEz6+u3lgR+GSm/QdM2BBvDraU39Vr7YdDCicJcxP1z8i9B/2dJLgDC1NcvlF8WCg==",
+ "requires": {
+ "d3-array": "^1.1.1"
+ }
+ },
+ "d3-dispatch": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.5.tgz",
+ "integrity": "sha512-vwKx+lAqB1UuCeklr6Jh1bvC4SZgbSqbkGBLClItFBIYH4vqDJCA7qfoy14lXmJdnBOdxndAMxjCbImJYW7e6g=="
+ },
+ "d3-drag": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-1.2.3.tgz",
+ "integrity": "sha512-8S3HWCAg+ilzjJsNtWW1Mutl74Nmzhb9yU6igspilaJzeZVFktmY6oO9xOh5TDk+BM2KrNFjttZNoJJmDnkjkg==",
+ "requires": {
+ "d3-dispatch": "1",
+ "d3-selection": "1"
+ }
+ },
+ "d3-dsv": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.0.10.tgz",
+ "integrity": "sha512-vqklfpxmtO2ZER3fq/B33R/BIz3A1PV0FaZRuFM8w6jLo7sUX1BZDh73fPlr0s327rzq4H6EN1q9U+eCBCSN8g==",
+ "requires": {
+ "commander": "2",
+ "iconv-lite": "0.4",
+ "rw": "1"
+ }
+ },
+ "d3-ease": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.5.tgz",
+ "integrity": "sha512-Ct1O//ly5y5lFM9YTdu+ygq7LleSgSE4oj7vUt9tPLHUi8VCV7QoizGpdWRWAwCO9LdYzIrQDg97+hGVdsSGPQ=="
+ },
+ "d3-fetch": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-1.1.2.tgz",
+ "integrity": "sha512-S2loaQCV/ZeyTyIF2oP8D1K9Z4QizUzW7cWeAOAS4U88qOt3Ucf6GsmgthuYSdyB2HyEm4CeGvkQxWsmInsIVA==",
+ "requires": {
+ "d3-dsv": "1"
+ }
+ },
+ "d3-force": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-1.1.2.tgz",
+ "integrity": "sha512-p1vcHAUF1qH7yR+e8ip7Bs61AHjLeKkIn8Z2gzwU2lwEf2wkSpWdjXG0axudTHsVFnYGlMkFaEsVy2l8tAg1Gw==",
+ "requires": {
+ "d3-collection": "1",
+ "d3-dispatch": "1",
+ "d3-quadtree": "1",
+ "d3-timer": "1"
+ }
+ },
+ "d3-format": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.3.2.tgz",
+ "integrity": "sha512-Z18Dprj96ExragQ0DeGi+SYPQ7pPfRMtUXtsg/ChVIKNBCzjO8XYJvRTC1usblx52lqge56V5ect+frYTQc8WQ=="
+ },
+ "d3-geo": {
+ "version": "1.11.3",
+ "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.11.3.tgz",
+ "integrity": "sha512-n30yN9qSKREvV2fxcrhmHUdXP9TNH7ZZj3C/qnaoU0cVf/Ea85+yT7HY7i8ySPwkwjCNYtmKqQFTvLFngfkItQ==",
+ "requires": {
+ "d3-array": "1"
+ }
+ },
+ "d3-hierarchy": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.8.tgz",
+ "integrity": "sha512-L+GHMSZNwTpiq4rt9GEsNcpLa4M96lXMR8M/nMG9p5hBE0jy6C+3hWtyZMenPQdwla249iJy7Nx0uKt3n+u9+w=="
+ },
+ "d3-interpolate": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.3.2.tgz",
+ "integrity": "sha512-NlNKGopqaz9qM1PXh9gBF1KSCVh+jSFErrSlD/4hybwoNX/gt1d8CDbDW+3i+5UOHhjC6s6nMvRxcuoMVNgL2w==",
+ "requires": {
+ "d3-color": "1"
+ }
+ },
+ "d3-path": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.7.tgz",
+ "integrity": "sha512-q0cW1RpvA5c5ma2rch62mX8AYaiLX0+bdaSM2wxSU9tXjU4DNvkx9qiUvjkuWCj3p22UO/hlPivujqMiR9PDzA=="
+ },
+ "d3-polygon": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-1.0.5.tgz",
+ "integrity": "sha512-RHhh1ZUJZfhgoqzWWuRhzQJvO7LavchhitSTHGu9oj6uuLFzYZVeBzaWTQ2qSO6bz2w55RMoOCf0MsLCDB6e0w=="
+ },
+ "d3-quadtree": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.5.tgz",
+ "integrity": "sha512-U2tjwDFbZ75JRAg8A+cqMvqPg1G3BE7UTJn3h8DHjY/pnsAfWdbJKgyfcy7zKjqGtLAmI0q8aDSeG1TVIKRaHQ=="
+ },
+ "d3-random": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-1.1.2.tgz",
+ "integrity": "sha512-6AK5BNpIFqP+cx/sreKzNjWbwZQCSUatxq+pPRmFIQaWuoD+NrbVWw7YWpHiXpCQ/NanKdtGDuB+VQcZDaEmYQ=="
+ },
+ "d3-scale": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-2.1.2.tgz",
+ "integrity": "sha512-bESpd64ylaKzCDzvULcmHKZTlzA/6DGSVwx7QSDj/EnX9cpSevsdiwdHFYI9ouo9tNBbV3v5xztHS2uFeOzh8Q==",
+ "requires": {
+ "d3-array": "^1.2.0",
+ "d3-collection": "1",
+ "d3-format": "1",
+ "d3-interpolate": "1",
+ "d3-time": "1",
+ "d3-time-format": "2"
+ }
+ },
+ "d3-scale-chromatic": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-1.3.3.tgz",
+ "integrity": "sha512-BWTipif1CimXcYfT02LKjAyItX5gKiwxuPRgr4xM58JwlLocWbjPLI7aMEjkcoOQXMkYsmNsvv3d2yl/OKuHHw==",
+ "requires": {
+ "d3-color": "1",
+ "d3-interpolate": "1"
+ }
+ },
+ "d3-selection": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.3.2.tgz",
+ "integrity": "sha512-OoXdv1nZ7h2aKMVg3kaUFbLLK5jXUFAMLD/Tu5JA96mjf8f2a9ZUESGY+C36t8R1WFeWk/e55hy54Ml2I62CRQ=="
+ },
+ "d3-shape": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.2.2.tgz",
+ "integrity": "sha512-hUGEozlKecFZ2bOSNt7ENex+4Tk9uc/m0TtTEHBvitCBxUNjhzm5hS2GrrVRD/ae4IylSmxGeqX5tWC2rASMlQ==",
+ "requires": {
+ "d3-path": "1"
+ }
+ },
+ "d3-time": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.0.10.tgz",
+ "integrity": "sha512-hF+NTLCaJHF/JqHN5hE8HVGAXPStEq6/omumPE/SxyHVrR7/qQxusFDo0t0c/44+sCGHthC7yNGFZIEgju0P8g=="
+ },
+ "d3-time-format": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.1.3.tgz",
+ "integrity": "sha512-6k0a2rZryzGm5Ihx+aFMuO1GgelgIz+7HhB4PH4OEndD5q2zGn1mDfRdNrulspOfR6JXkb2sThhDK41CSK85QA==",
+ "requires": {
+ "d3-time": "1"
+ }
+ },
+ "d3-timer": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.9.tgz",
+ "integrity": "sha512-rT34J5HnQUHhcLvhSB9GjCkN0Ddd5Y8nCwDBG2u6wQEeYxT/Lf51fTFFkldeib/sE/J0clIe0pnCfs6g/lRbyg=="
+ },
+ "d3-transition": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.1.3.tgz",
+ "integrity": "sha512-tEvo3qOXL6pZ1EzcXxFcPNxC/Ygivu5NoBY6mbzidATAeML86da+JfVIUzon3dNM6UX6zjDx+xbYDmMVtTSjuA==",
+ "requires": {
+ "d3-color": "1",
+ "d3-dispatch": "1",
+ "d3-ease": "1",
+ "d3-interpolate": "1",
+ "d3-selection": "^1.1.0",
+ "d3-timer": "1"
+ }
+ },
+ "d3-voronoi": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.4.tgz",
+ "integrity": "sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg=="
+ },
+ "d3-zoom": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-1.7.3.tgz",
+ "integrity": "sha512-xEBSwFx5Z9T3/VrwDkMt+mr0HCzv7XjpGURJ8lWmIC8wxe32L39eWHIasEe/e7Ox8MPU4p1hvH8PKN2olLzIBg==",
+ "requires": {
+ "d3-dispatch": "1",
+ "d3-drag": "1",
+ "d3-interpolate": "1",
+ "d3-selection": "1",
+ "d3-transition": "1"
+ }
+ },
+ "debug": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+ "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "decode-uri-component": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
+ "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU="
+ },
+ "deep-eql": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz",
+ "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==",
+ "dev": true,
+ "requires": {
+ "type-detect": "^4.0.0"
+ }
+ },
+ "deep-equal": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz",
+ "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=",
+ "dev": true
+ },
+ "deep-extend": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
+ "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
+ "dev": true
+ },
+ "deepmerge": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz",
+ "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==",
+ "dev": true,
+ "optional": true
+ },
+ "defer-promise": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/defer-promise/-/defer-promise-1.0.1.tgz",
+ "integrity": "sha1-HKb/7dvO8XFd16riXHYW+a4iky8=",
+ "dev": true
+ },
+ "define-property": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
+ "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
+ "requires": {
+ "is-descriptor": "^1.0.2",
+ "isobject": "^3.0.1"
+ },
+ "dependencies": {
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ }
+ }
+ },
+ "delegates": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
+ "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
+ "dev": true
+ },
+ "depd": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
+ "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=",
+ "dev": true
+ },
+ "destroy": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
+ "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=",
+ "dev": true
+ },
+ "diff": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
+ "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==",
+ "dev": true
+ },
+ "ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=",
+ "dev": true
+ },
+ "error-inject": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/error-inject/-/error-inject-1.0.0.tgz",
+ "integrity": "sha1-4rPZG1Su1nLzCdlQ0VSFD6EdTzc=",
+ "dev": true
+ },
+ "escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=",
+ "dev": true
+ },
+ "escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+ "dev": true
+ },
+ "esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "dev": true
+ },
+ "estree-walker": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.0.tgz",
+ "integrity": "sha512-peq1RfVAVzr3PU/jL31RaOjUKLoZJpObQWJJ+LgfcxDUifyLZ1RjPQZTl0pzj2uJ45b7A7XpyppXvxdEqzo4rw=="
+ },
+ "esutils": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
+ "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
+ "dev": true
+ },
+ "etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=",
+ "dev": true
+ },
+ "expand-brackets": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
+ "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=",
+ "requires": {
+ "debug": "^2.3.3",
+ "define-property": "^0.2.5",
+ "extend-shallow": "^2.0.1",
+ "posix-character-classes": "^0.1.0",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.1"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ },
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "extend-shallow": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
+ "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
+ "requires": {
+ "assign-symbols": "^1.0.0",
+ "is-extendable": "^1.0.1"
+ },
+ "dependencies": {
+ "is-extendable": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+ "requires": {
+ "is-plain-object": "^2.0.4"
+ }
+ }
+ }
+ },
+ "extglob": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
+ "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==",
+ "requires": {
+ "array-unique": "^0.3.2",
+ "define-property": "^1.0.0",
+ "expand-brackets": "^2.1.4",
+ "extend-shallow": "^2.0.1",
+ "fragment-cache": "^0.2.1",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+ "requires": {
+ "is-descriptor": "^1.0.0"
+ }
+ },
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ }
+ }
+ },
+ "fill-range": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
+ "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
+ "requires": {
+ "extend-shallow": "^2.0.1",
+ "is-number": "^3.0.0",
+ "repeat-string": "^1.6.1",
+ "to-regex-range": "^2.1.0"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "find-replace": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-2.0.1.tgz",
+ "integrity": "sha512-LzDo3Fpa30FLIBsh6DCDnMN1KW2g4QKkqKmejlImgWY67dDFPX/x9Kh/op/GK522DchQXEvDi/wD48HKW49XOQ==",
+ "dev": true,
+ "requires": {
+ "array-back": "^2.0.0",
+ "test-value": "^3.0.0"
+ }
+ },
+ "for-in": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
+ "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA="
+ },
+ "fragment-cache": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
+ "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=",
+ "requires": {
+ "map-cache": "^0.2.2"
+ }
+ },
+ "fresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+ "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=",
+ "dev": true
+ },
+ "fs-extra": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
+ "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ }
+ },
+ "fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+ "dev": true
+ },
+ "get-func-name": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
+ "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=",
+ "dev": true
+ },
+ "get-value": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
+ "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg="
+ },
+ "glob": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
+ "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
+ "dev": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "graceful-fs": {
+ "version": "4.1.15",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz",
+ "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA=="
+ },
+ "growl": {
+ "version": "1.10.5",
+ "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz",
+ "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==",
+ "dev": true
+ },
+ "has-ansi": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
+ "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^2.0.0"
+ }
+ },
+ "has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+ "dev": true
+ },
+ "has-value": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz",
+ "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=",
+ "requires": {
+ "get-value": "^2.0.6",
+ "has-values": "^1.0.0",
+ "isobject": "^3.0.0"
+ }
+ },
+ "has-values": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz",
+ "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=",
+ "requires": {
+ "is-number": "^3.0.0",
+ "kind-of": "^4.0.0"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
+ "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=",
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "he": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz",
+ "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=",
+ "dev": true
+ },
+ "http-assert": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.4.0.tgz",
+ "integrity": "sha512-tPVv62a6l3BbQoM/N5qo969l0OFxqpnQzNUPeYfTP6Spo4zkgWeDBD1D5thI7sDLg7jCCihXTLB0X8UtdyAy8A==",
+ "dev": true,
+ "requires": {
+ "deep-equal": "~1.0.1",
+ "http-errors": "~1.7.1"
+ }
+ },
+ "http-errors": {
+ "version": "1.7.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.1.tgz",
+ "integrity": "sha512-jWEUgtZWGSMba9I1N3gc1HmvpBUaNC9vDdA46yScAdp+C5rdEuKWUBLWTQpW9FwSWSbYYs++b6SDCxf9UEJzfw==",
+ "dev": true,
+ "requires": {
+ "depd": "~1.1.2",
+ "inherits": "2.0.3",
+ "setprototypeof": "1.1.0",
+ "statuses": ">= 1.5.0 < 2",
+ "toidentifier": "1.0.0"
+ }
+ },
+ "iconv-lite": {
+ "version": "0.4.23",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz",
+ "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==",
+ "requires": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ }
+ },
+ "inflation": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/inflation/-/inflation-2.0.0.tgz",
+ "integrity": "sha1-i0F+R8KPklpFEz2RTKH9OJEH8w8=",
+ "dev": true
+ },
+ "inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+ "dev": true,
+ "requires": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "inherits": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
+ "dev": true
+ },
+ "is-accessor-descriptor": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
+ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "is-buffer": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
+ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
+ },
+ "is-data-descriptor": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
+ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "is-descriptor": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
+ "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==",
+ "requires": {
+ "is-accessor-descriptor": "^0.1.6",
+ "is-data-descriptor": "^0.1.4",
+ "kind-of": "^5.0.0"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
+ "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw=="
+ }
+ }
+ },
+ "is-extendable": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
+ "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik="
+ },
+ "is-generator-function": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.7.tgz",
+ "integrity": "sha512-YZc5EwyO4f2kWCax7oegfuSr9mFz1ZvieNYBEjmukLxgXfBUbxAWGVF7GZf0zidYtoBl3WvC07YK0wT76a+Rtw==",
+ "dev": true
+ },
+ "is-module": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
+ "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE="
+ },
+ "is-number": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
+ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "is-plain-object": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
+ "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
+ "requires": {
+ "isobject": "^3.0.1"
+ }
+ },
+ "is-windows": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
+ "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA=="
+ },
+ "is-wsl": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz",
+ "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=",
+ "dev": true
+ },
+ "isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
+ },
+ "isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
+ },
+ "js-tokens": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
+ "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=",
+ "dev": true
+ },
+ "js-yaml": {
+ "version": "3.12.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.1.tgz",
+ "integrity": "sha512-um46hB9wNOKlwkHgiuyEVAybXBjwFUV0Z/RaHJblRd9DXltue9FTYvzCr9ErQrK9Adz5MU4gHWVaNUfdmrC8qA==",
+ "dev": true,
+ "requires": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ }
+ },
+ "json-stringify-safe": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=",
+ "dev": true
+ },
+ "json5": {
+ "version": "1.0.1",
+ "resolved": "http://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
+ "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "minimist": "^1.2.0"
+ }
+ },
+ "jsonfile": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+ "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
+ "requires": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "jsonparse": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz",
+ "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=",
+ "dev": true
+ },
+ "keygrip": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.0.3.tgz",
+ "integrity": "sha512-/PpesirAIfaklxUzp4Yb7xBper9MwP6hNRA6BGGUFCgbJ+BM5CKBtsoxinNXkLHAr+GXS1/lSlF2rP7cv5Fl+g==",
+ "dev": true
+ },
+ "kind-of": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
+ "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA=="
+ },
+ "koa": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/koa/-/koa-2.6.1.tgz",
+ "integrity": "sha512-n9R5Eex4y0drUeqFTeCIeXyz8wjr2AxBo2Cq8LvmiXbJl4yDA5KIrecMPkhnmgACZnPXMRyCLbJoyLmpM9aFAw==",
+ "dev": true,
+ "requires": {
+ "accepts": "^1.3.5",
+ "cache-content-type": "^1.0.0",
+ "content-disposition": "~0.5.2",
+ "content-type": "^1.0.4",
+ "cookies": "~0.7.1",
+ "debug": "~3.1.0",
+ "delegates": "^1.0.0",
+ "depd": "^1.1.2",
+ "destroy": "^1.0.4",
+ "error-inject": "^1.0.0",
+ "escape-html": "^1.0.3",
+ "fresh": "~0.5.2",
+ "http-assert": "^1.3.0",
+ "http-errors": "^1.6.3",
+ "is-generator-function": "^1.0.7",
+ "koa-compose": "^4.1.0",
+ "koa-convert": "^1.2.0",
+ "koa-is-json": "^1.0.0",
+ "on-finished": "^2.3.0",
+ "only": "~0.0.2",
+ "parseurl": "^1.3.2",
+ "statuses": "^1.5.0",
+ "type-is": "^1.6.16",
+ "vary": "^1.1.2"
+ }
+ },
+ "koa-bodyparser": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/koa-bodyparser/-/koa-bodyparser-4.2.1.tgz",
+ "integrity": "sha512-UIjPAlMZfNYDDe+4zBaOAUKYqkwAGcIU6r2ARf1UOXPAlfennQys5IiShaVeNf7KkVBlf88f2LeLvBFvKylttw==",
+ "dev": true,
+ "requires": {
+ "co-body": "^6.0.0",
+ "copy-to": "^2.0.1"
+ }
+ },
+ "koa-compose": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz",
+ "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==",
+ "dev": true
+ },
+ "koa-compress": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/koa-compress/-/koa-compress-2.0.0.tgz",
+ "integrity": "sha1-e36ykhuEd0a14SK6n1zYpnHo6jo=",
+ "dev": true,
+ "requires": {
+ "bytes": "^2.3.0",
+ "compressible": "^2.0.0",
+ "koa-is-json": "^1.0.0",
+ "statuses": "^1.0.0"
+ },
+ "dependencies": {
+ "bytes": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.5.0.tgz",
+ "integrity": "sha1-TJQj6i0lLCcMQbK97+/5u2tiwGo=",
+ "dev": true
+ }
+ }
+ },
+ "koa-conditional-get": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/koa-conditional-get/-/koa-conditional-get-2.0.0.tgz",
+ "integrity": "sha1-pD83I8HQFLcwo07Oit8wuTyCM/I=",
+ "dev": true
+ },
+ "koa-convert": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-1.2.0.tgz",
+ "integrity": "sha1-2kCHXfSd4FOQmNFwC1CCDOvNIdA=",
+ "dev": true,
+ "requires": {
+ "co": "^4.6.0",
+ "koa-compose": "^3.0.0"
+ },
+ "dependencies": {
+ "koa-compose": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-3.2.1.tgz",
+ "integrity": "sha1-qFzLQLfZhtjlo0Wzoazo6rz1Tec=",
+ "dev": true,
+ "requires": {
+ "any-promise": "^1.1.0"
+ }
+ }
+ }
+ },
+ "koa-etag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/koa-etag/-/koa-etag-3.0.0.tgz",
+ "integrity": "sha1-nvc4Ld1agqsN6xU0FckVg293HT8=",
+ "dev": true,
+ "requires": {
+ "etag": "^1.3.0",
+ "mz": "^2.1.0"
+ }
+ },
+ "koa-is-json": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/koa-is-json/-/koa-is-json-1.0.0.tgz",
+ "integrity": "sha1-JzwH7c3Ljfaiwat9We52SRRR7BQ=",
+ "dev": true
+ },
+ "koa-json": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/koa-json/-/koa-json-2.0.2.tgz",
+ "integrity": "sha1-Nq8U5uofXWRtfESihXAcb4Wk/eQ=",
+ "dev": true,
+ "requires": {
+ "koa-is-json": "1",
+ "streaming-json-stringify": "3"
+ }
+ },
+ "koa-mock-response": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/koa-mock-response/-/koa-mock-response-0.2.0.tgz",
+ "integrity": "sha512-HmybRN1a3WqcSFvf7tycu2YhBIEHeqzm8bwcsShNWGsTgP86coZOpdI8aqYm/1DFsAQMctnpdWrva4rDr1Pibg==",
+ "dev": true,
+ "requires": {
+ "array-back": "^2.0.0",
+ "path-to-regexp": "^1.7.0",
+ "typical": "^2.6.1"
+ },
+ "dependencies": {
+ "isarray": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+ "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
+ "dev": true
+ },
+ "path-to-regexp": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz",
+ "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=",
+ "dev": true,
+ "requires": {
+ "isarray": "0.0.1"
+ }
+ },
+ "typical": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz",
+ "integrity": "sha1-XAgOXWYcu+OCWdLnCjxyU+hziB0=",
+ "dev": true
+ }
+ }
+ },
+ "koa-morgan": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/koa-morgan/-/koa-morgan-1.0.1.tgz",
+ "integrity": "sha1-CAUuDODYOdPEMXi5CluzQkvvH5k=",
+ "dev": true,
+ "requires": {
+ "morgan": "^1.6.1"
+ }
+ },
+ "koa-range": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/koa-range/-/koa-range-0.3.0.tgz",
+ "integrity": "sha1-NYjjSWRzqDmhvSZNKkKx2FvX/qw=",
+ "dev": true,
+ "requires": {
+ "stream-slice": "^0.1.2"
+ }
+ },
+ "koa-rewrite-75lb": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/koa-rewrite-75lb/-/koa-rewrite-75lb-2.1.1.tgz",
+ "integrity": "sha512-i9ofDKLs0xNCb2PW7wKGFzBFX6+Ce3aKoZzNKPh0fkejeUOTWkkDqnjXrgqrJEP2ifX6WWsHp6VtGuXzSYLSWQ==",
+ "dev": true,
+ "requires": {
+ "path-to-regexp": "1.7.0"
+ },
+ "dependencies": {
+ "isarray": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+ "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
+ "dev": true
+ },
+ "path-to-regexp": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz",
+ "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=",
+ "dev": true,
+ "requires": {
+ "isarray": "0.0.1"
+ }
+ }
+ }
+ },
+ "koa-route": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/koa-route/-/koa-route-3.2.0.tgz",
+ "integrity": "sha1-dimLmaa8+p44yrb+XHmocz51i84=",
+ "dev": true,
+ "requires": {
+ "debug": "*",
+ "methods": "~1.1.0",
+ "path-to-regexp": "^1.2.0"
+ },
+ "dependencies": {
+ "isarray": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+ "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
+ "dev": true
+ },
+ "path-to-regexp": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz",
+ "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=",
+ "dev": true,
+ "requires": {
+ "isarray": "0.0.1"
+ }
+ }
+ }
+ },
+ "koa-send": {
+ "version": "4.1.3",
+ "resolved": "http://registry.npmjs.org/koa-send/-/koa-send-4.1.3.tgz",
+ "integrity": "sha512-3UetMBdaXSiw24qM2Mx5mKmxLKw5ZTPRjACjfhK6Haca55RKm9hr/uHDrkrxhSl5/S1CKI/RivZVIopiatZuTA==",
+ "dev": true,
+ "requires": {
+ "debug": "^2.6.3",
+ "http-errors": "^1.6.1",
+ "mz": "^2.6.0",
+ "resolve-path": "^1.4.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ }
+ }
+ },
+ "koa-static": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/koa-static/-/koa-static-4.0.3.tgz",
+ "integrity": "sha512-JGmxTuPWy4bH7bt6gD/OMWkhprawvRmzJSr8TWKmTL4N7+IMv3s0SedeQi5S4ilxM9Bo6ptkCyXj/7wf+VS5tg==",
+ "dev": true,
+ "requires": {
+ "debug": "^3.1.0",
+ "koa-send": "^4.1.3"
+ }
+ },
+ "load-module": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/load-module/-/load-module-1.0.0.tgz",
+ "integrity": "sha512-FmoAJI/RM4vmvIRk65g/SFCnGQC9BbALY3zy38Z0cMllNnra1+iCdxAf051LVymzE60/FweOo9or9XJiGgFshg==",
+ "dev": true,
+ "requires": {
+ "array-back": "^2.0.0"
+ }
+ },
+ "local-web-server": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/local-web-server/-/local-web-server-2.6.0.tgz",
+ "integrity": "sha512-m7Z5zlzZFxMyiK1W8xR5TJMh00Fy9z7Po8vilSQCpeU4LG2VMK667xCkASBUepFR9fPj6heUMBHu9P/TrwDqFw==",
+ "dev": true,
+ "requires": {
+ "lws": "^1.3.0",
+ "lws-basic-auth": "^0.1.1",
+ "lws-blacklist": "^0.3.0",
+ "lws-body-parser": "^0.2.4",
+ "lws-compress": "^0.2.1",
+ "lws-conditional-get": "^0.3.4",
+ "lws-cors": "^1.0.0",
+ "lws-index": "^0.4.0",
+ "lws-json": "^0.3.2",
+ "lws-log": "^0.3.2",
+ "lws-mime": "^0.2.2",
+ "lws-mock-response": "^0.5.1",
+ "lws-range": "^1.1.0",
+ "lws-request-monitor": "^0.1.5",
+ "lws-rewrite": "^0.4.1",
+ "lws-spa": "^0.3.0",
+ "lws-static": "^0.5.0",
+ "node-version-matches": "^1.0.0"
+ }
+ },
+ "lodash.assignwith": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/lodash.assignwith/-/lodash.assignwith-4.2.0.tgz",
+ "integrity": "sha1-EnqX8CrcQXUalU0ksN4X4QDgOOs=",
+ "dev": true
+ },
+ "lodash.camelcase": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
+ "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=",
+ "dev": true
+ },
+ "lodash.padend": {
+ "version": "4.6.1",
+ "resolved": "https://registry.npmjs.org/lodash.padend/-/lodash.padend-4.6.1.tgz",
+ "integrity": "sha1-U8y6BH0G4VjTEfRdpiX05J5vFm4=",
+ "dev": true
+ },
+ "lodash.pick": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz",
+ "integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=",
+ "dev": true
+ },
+ "lodash.throttle": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
+ "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=",
+ "dev": true
+ },
+ "lws": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/lws/-/lws-1.3.0.tgz",
+ "integrity": "sha512-2gOJzVtgjg4mA1cyWnzkICR/NLuMD24sbRSwQeVZeVkadp0VOKTlpmnjvA1tQpkb1TGrcOS+N+3vKMJST8tt2w==",
+ "dev": true,
+ "requires": {
+ "ansi-escape-sequences": "^4.0.0",
+ "array-back": "^2.0.0",
+ "byte-size": "^4.0.3",
+ "cli-commands": "^0.4.0",
+ "command-line-args": "^5.0.2",
+ "command-line-usage": "^5.0.5",
+ "koa": "^2.5.2",
+ "load-module": "^1.0.0",
+ "lodash.assignwith": "^4.2.0",
+ "node-version-matches": "^1.0.0",
+ "opn": "^5.3.0",
+ "reduce-flatten": "^2.0.0",
+ "typical": "^3.0.0",
+ "walk-back": "^3.0.0",
+ "ws": "^5.2.1"
+ }
+ },
+ "lws-basic-auth": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/lws-basic-auth/-/lws-basic-auth-0.1.1.tgz",
+ "integrity": "sha512-npPpqkOFzJzB9yJ2pGXmiYOswH+0n86ro75WhromeGuNo0GfE18ZLI/VCOVWmBbeXp2pcnPIMUAdkNSgukpAww==",
+ "dev": true,
+ "requires": {
+ "basic-auth": "^1.1.0"
+ }
+ },
+ "lws-blacklist": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/lws-blacklist/-/lws-blacklist-0.3.0.tgz",
+ "integrity": "sha512-ZA8dujYaZwRNMBhgP+oGsZi9tum44Ba6VHsA3JrV1JVrjZ8c65kLaO/41rLBqQDKP3SDPu7dLity4YLwe1FuNQ==",
+ "dev": true,
+ "requires": {
+ "array-back": "^2.0.0",
+ "path-to-regexp": "^2.2.0"
+ }
+ },
+ "lws-body-parser": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/lws-body-parser/-/lws-body-parser-0.2.4.tgz",
+ "integrity": "sha512-XKJzbzK97TUsewIPA5J2RpEk7kRoJcL+/Du6JlwzqIq84tWuXMfiT2a4Ncj12+tRWrdY2avV6d8uLhqlHLz1yg==",
+ "dev": true,
+ "requires": {
+ "koa-bodyparser": "^4.2.0"
+ }
+ },
+ "lws-compress": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/lws-compress/-/lws-compress-0.2.1.tgz",
+ "integrity": "sha512-14++1o6U8upi3DLx9J2O2sFELsijEJF9utoFxSH4Stoo9SdU2Cxw6BtqQTrb9SEA6O6IsApzstdMYnq8floLSg==",
+ "dev": true,
+ "requires": {
+ "koa-compress": "^2.0.0"
+ }
+ },
+ "lws-conditional-get": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/lws-conditional-get/-/lws-conditional-get-0.3.4.tgz",
+ "integrity": "sha512-6asZSfM747snhdz4xexRllm09pebz8pjYeg2d5khLR53D/OJznZWHsIqW0JGiScJObri2D7+H4z7yRLBjokT7g==",
+ "dev": true,
+ "requires": {
+ "koa-conditional-get": "^2.0.0",
+ "koa-etag": "^3.0.0"
+ }
+ },
+ "lws-cors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/lws-cors/-/lws-cors-1.0.0.tgz",
+ "integrity": "sha512-4C0m4lvYdAnpAa03tr9AqziB4d8SRPh4beQBuzPiefv7N9/tpVdrl9kgXrUe1hLHhISnVJ5MoOZuZ6wFeMiU4g==",
+ "dev": true,
+ "requires": {
+ "@koa/cors": "^2.2.1"
+ }
+ },
+ "lws-index": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/lws-index/-/lws-index-0.4.0.tgz",
+ "integrity": "sha512-k+mkqgMSzx1ipzVpaxsAJU4Qe7R1kp1B/u+qC+d1Y3l+auBz+bLcIxL4dYKfaxLqiz0IFwg1dZwGzVm/dd7FFw==",
+ "dev": true,
+ "requires": {
+ "serve-index-75lb": "^2.0.0"
+ }
+ },
+ "lws-json": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/lws-json/-/lws-json-0.3.2.tgz",
+ "integrity": "sha512-ElmCA8hi3GPMfxbtiI015PDHuJovhhcbXX/qTTTifXhopedAzIBzn/rF5dHZHE4k7HQDYfbiaPgPMbmpv9dMvQ==",
+ "dev": true,
+ "requires": {
+ "koa-json": "^2.0.2"
+ }
+ },
+ "lws-log": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/lws-log/-/lws-log-0.3.2.tgz",
+ "integrity": "sha512-DRp4bFl4a7hjwR/RjARjhFLEXs8pIeqKbUvojaAl1hhfRBuW2JsDxRSKC+ViQN06CW4Qypg3ZsztMMR8dRO8dA==",
+ "dev": true,
+ "requires": {
+ "koa-morgan": "^1.0.1",
+ "stream-log-stats": "^2.0.2"
+ }
+ },
+ "lws-mime": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/lws-mime/-/lws-mime-0.2.2.tgz",
+ "integrity": "sha512-cWBj9CuuSvvaqdYMPiXRid0QhzJmr+5gWAA96pEDOiW8tMCMoxl7CIgTpHXZwhJzCqdI84RZDVm+FswByATS5w==",
+ "dev": true
+ },
+ "lws-mock-response": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/lws-mock-response/-/lws-mock-response-0.5.1.tgz",
+ "integrity": "sha512-4R5Q1RmRglC0pqEwywrS5g62aKaLQsteMnShGmWU9aQ/737Bq0/3qbQ3mb8VbMk3lLzo3ZaNZ1DUsPgVvZaXNQ==",
+ "dev": true,
+ "requires": {
+ "array-back": "^2.0.0",
+ "koa-mock-response": "0.2.0",
+ "load-module": "^1.0.0",
+ "reduce-flatten": "^2.0.0"
+ }
+ },
+ "lws-range": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/lws-range/-/lws-range-1.1.0.tgz",
+ "integrity": "sha512-Mpx6FdO58Z4l6DAXlATsC2zm10QvyGYElQvFd7P1xqUSTPoYG0wAxfjlpqI+Qdb2O7W4Ah21yESVnPEwae3SIw==",
+ "dev": true,
+ "requires": {
+ "koa-range": "^0.3.0"
+ }
+ },
+ "lws-request-monitor": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/lws-request-monitor/-/lws-request-monitor-0.1.5.tgz",
+ "integrity": "sha512-u9eczHPowH17ftUjQ8ysutGDADNZdDD6k8wgFMzOB7/rRq1Is12lTYA4u8pfKZ8C2oyoy+HYsDSrOzTwespTlA==",
+ "dev": true,
+ "requires": {
+ "byte-size": "^4.0.2"
+ }
+ },
+ "lws-rewrite": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/lws-rewrite/-/lws-rewrite-0.4.1.tgz",
+ "integrity": "sha512-EHUdbqfdwc4Baa7iXOdG2y815WC040Cing1GwhM9VsBL7lHtZ7zl3EHzjWFv3styoO3qNqZ4W0xCey4hoo/aYg==",
+ "dev": true,
+ "requires": {
+ "array-back": "^2.0.0",
+ "koa-rewrite-75lb": "^2.1.1",
+ "koa-route": "^3.2.0",
+ "path-to-regexp": "^1.7.0",
+ "req-then": "^0.6.4",
+ "stream-read-all": "^0.1.2",
+ "typical": "^2.6.1"
+ },
+ "dependencies": {
+ "isarray": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+ "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
+ "dev": true
+ },
+ "path-to-regexp": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz",
+ "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=",
+ "dev": true,
+ "requires": {
+ "isarray": "0.0.1"
+ }
+ },
+ "typical": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz",
+ "integrity": "sha1-XAgOXWYcu+OCWdLnCjxyU+hziB0=",
+ "dev": true
+ }
+ }
+ },
+ "lws-spa": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/lws-spa/-/lws-spa-0.3.0.tgz",
+ "integrity": "sha512-8wxZl5dOI/CQsJ6oOG8Y7B4khjlQXfB7GlVkjYFPuOYM+JIw/QzMvezKjKweG0qGePmHJVHWa38+CyololV4aw==",
+ "dev": true,
+ "requires": {
+ "koa-route": "^3.2.0",
+ "koa-send": "^4.1.3"
+ }
+ },
+ "lws-static": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/lws-static/-/lws-static-0.5.0.tgz",
+ "integrity": "sha512-r3QIeJfBox/hSJLSL7TPhNSZsTKE0r4mWYHbGZ+DwrBcKbLt1ljsh5NAtmJpsqCcjYpyOuD/DlsZ0yQY9VI8bA==",
+ "dev": true,
+ "requires": {
+ "koa-static": "^4.0.2"
+ }
+ },
+ "make-error": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz",
+ "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==",
+ "dev": true
+ },
+ "map-cache": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz",
+ "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8="
+ },
+ "map-visit": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz",
+ "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=",
+ "requires": {
+ "object-visit": "^1.0.0"
+ }
+ },
+ "media-typer": {
+ "version": "0.3.0",
+ "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+ "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=",
+ "dev": true
+ },
+ "methods": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+ "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=",
+ "dev": true
+ },
+ "micromatch": {
+ "version": "3.1.10",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
+ "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
+ "requires": {
+ "arr-diff": "^4.0.0",
+ "array-unique": "^0.3.2",
+ "braces": "^2.3.1",
+ "define-property": "^2.0.2",
+ "extend-shallow": "^3.0.2",
+ "extglob": "^2.0.4",
+ "fragment-cache": "^0.2.1",
+ "kind-of": "^6.0.2",
+ "nanomatch": "^1.2.9",
+ "object.pick": "^1.3.0",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.2"
+ }
+ },
+ "mime-db": {
+ "version": "1.37.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz",
+ "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==",
+ "dev": true
+ },
+ "mime-types": {
+ "version": "2.1.21",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz",
+ "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==",
+ "dev": true,
+ "requires": {
+ "mime-db": "~1.37.0"
+ }
+ },
+ "minimatch": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+ "dev": true,
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ },
+ "minimist": {
+ "version": "1.2.0",
+ "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+ "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+ "dev": true
+ },
+ "mixin-deep": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz",
+ "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==",
+ "requires": {
+ "for-in": "^1.0.2",
+ "is-extendable": "^1.0.1"
+ },
+ "dependencies": {
+ "is-extendable": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+ "requires": {
+ "is-plain-object": "^2.0.4"
+ }
+ }
+ }
+ },
+ "mkdirp": {
+ "version": "0.5.1",
+ "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+ "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
+ "dev": true,
+ "requires": {
+ "minimist": "0.0.8"
+ },
+ "dependencies": {
+ "minimist": {
+ "version": "0.0.8",
+ "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+ "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
+ "dev": true
+ }
+ }
+ },
+ "mocha": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz",
+ "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==",
+ "dev": true,
+ "requires": {
+ "browser-stdout": "1.3.1",
+ "commander": "2.15.1",
+ "debug": "3.1.0",
+ "diff": "3.5.0",
+ "escape-string-regexp": "1.0.5",
+ "glob": "7.1.2",
+ "growl": "1.10.5",
+ "he": "1.1.1",
+ "minimatch": "3.0.4",
+ "mkdirp": "0.5.1",
+ "supports-color": "5.4.0"
+ }
+ },
+ "morgan": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.1.tgz",
+ "integrity": "sha512-HQStPIV4y3afTiCYVxirakhlCfGkI161c76kKFca7Fk1JusM//Qeo1ej2XaMniiNeaZklMVrh3vTtIzpzwbpmA==",
+ "dev": true,
+ "requires": {
+ "basic-auth": "~2.0.0",
+ "debug": "2.6.9",
+ "depd": "~1.1.2",
+ "on-finished": "~2.3.0",
+ "on-headers": "~1.0.1"
+ },
+ "dependencies": {
+ "basic-auth": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
+ "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "5.1.2"
+ }
+ },
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ }
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+ },
+ "mz": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
+ "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
+ "dev": true,
+ "requires": {
+ "any-promise": "^1.0.0",
+ "object-assign": "^4.0.1",
+ "thenify-all": "^1.0.0"
+ }
+ },
+ "nanomatch": {
+ "version": "1.2.13",
+ "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
+ "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==",
+ "requires": {
+ "arr-diff": "^4.0.0",
+ "array-unique": "^0.3.2",
+ "define-property": "^2.0.2",
+ "extend-shallow": "^3.0.2",
+ "fragment-cache": "^0.2.1",
+ "is-windows": "^1.0.2",
+ "kind-of": "^6.0.2",
+ "object.pick": "^1.3.0",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.1"
+ }
+ },
+ "negotiator": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
+ "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=",
+ "dev": true
+ },
+ "node-version-matches": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/node-version-matches/-/node-version-matches-1.0.0.tgz",
+ "integrity": "sha512-E1OQnAUB+BvEyNTXTWpUUMAWXYCa7yjiS64djOuTJEkm20yaQfNmWTfx/kvN6nC7fc0GQS182IaefOPxQvpxXg==",
+ "dev": true,
+ "requires": {
+ "semver": "^5.5.0"
+ }
+ },
+ "object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
+ "dev": true
+ },
+ "object-copy": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz",
+ "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=",
+ "requires": {
+ "copy-descriptor": "^0.1.0",
+ "define-property": "^0.2.5",
+ "kind-of": "^3.0.3"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ },
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "object-visit": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz",
+ "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=",
+ "requires": {
+ "isobject": "^3.0.0"
+ }
+ },
+ "object.pick": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz",
+ "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=",
+ "requires": {
+ "isobject": "^3.0.1"
+ }
+ },
+ "on-finished": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+ "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
+ "dev": true,
+ "requires": {
+ "ee-first": "1.1.1"
+ }
+ },
+ "on-headers": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz",
+ "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=",
+ "dev": true
+ },
+ "once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "dev": true,
+ "requires": {
+ "wrappy": "1"
+ }
+ },
+ "only": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz",
+ "integrity": "sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q=",
+ "dev": true
+ },
+ "opn": {
+ "version": "5.4.0",
+ "resolved": "https://registry.npmjs.org/opn/-/opn-5.4.0.tgz",
+ "integrity": "sha512-YF9MNdVy/0qvJvDtunAOzFw9iasOQHpVthTCvGzxt61Il64AYSGdK+rYwld7NAfk9qJ7dt+hymBNSc9LNYS+Sw==",
+ "dev": true,
+ "requires": {
+ "is-wsl": "^1.1.0"
+ }
+ },
+ "parseurl": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz",
+ "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=",
+ "dev": true
+ },
+ "pascalcase": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz",
+ "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ="
+ },
+ "path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+ "dev": true
+ },
+ "path-parse": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz",
+ "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME="
+ },
+ "path-to-regexp": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.4.0.tgz",
+ "integrity": "sha512-G6zHoVqC6GGTQkZwF4lkuEyMbVOjoBKAEybQUypI1WTkqinCOrq2x6U2+phkJ1XsEMTy4LjtwPI7HW+NVrRR2w==",
+ "dev": true
+ },
+ "pathval": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz",
+ "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=",
+ "dev": true
+ },
+ "posix-character-classes": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
+ "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs="
+ },
+ "process-nextick-args": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
+ "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==",
+ "dev": true
+ },
+ "qs": {
+ "version": "6.5.2",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
+ "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==",
+ "dev": true
+ },
+ "raw-body": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz",
+ "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==",
+ "dev": true,
+ "requires": {
+ "bytes": "3.0.0",
+ "http-errors": "1.6.3",
+ "iconv-lite": "0.4.23",
+ "unpipe": "1.0.0"
+ },
+ "dependencies": {
+ "http-errors": {
+ "version": "1.6.3",
+ "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
+ "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
+ "dev": true,
+ "requires": {
+ "depd": "~1.1.2",
+ "inherits": "2.0.3",
+ "setprototypeof": "1.1.0",
+ "statuses": ">= 1.4.0 < 2"
+ }
+ }
+ }
+ },
+ "readable-stream": {
+ "version": "2.3.6",
+ "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
+ "dev": true,
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "reduce-flatten": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-2.0.0.tgz",
+ "integrity": "sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==",
+ "dev": true
+ },
+ "regex-not": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
+ "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==",
+ "requires": {
+ "extend-shallow": "^3.0.2",
+ "safe-regex": "^1.1.0"
+ }
+ },
+ "repeat-element": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz",
+ "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g=="
+ },
+ "repeat-string": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
+ "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc="
+ },
+ "req-then": {
+ "version": "0.6.4",
+ "resolved": "https://registry.npmjs.org/req-then/-/req-then-0.6.4.tgz",
+ "integrity": "sha512-Uf7xsK1qPqPUetESHemNQ7nGtgOxngSFtlcAOOkx0lDAo+XRZpEA9QDrGBdyOfGq4b+a0z/D5gR2VJ+pp/dzBA==",
+ "dev": true,
+ "requires": {
+ "array-back": "^2.0.0",
+ "defer-promise": "^1.0.1",
+ "lodash.pick": "^4.4.0",
+ "stream-read-all": "^0.1.0",
+ "typical": "^2.6.1"
+ },
+ "dependencies": {
+ "typical": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz",
+ "integrity": "sha1-XAgOXWYcu+OCWdLnCjxyU+hziB0=",
+ "dev": true
+ }
+ }
+ },
+ "resolve": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz",
+ "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==",
+ "requires": {
+ "path-parse": "^1.0.5"
+ }
+ },
+ "resolve-path": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/resolve-path/-/resolve-path-1.4.0.tgz",
+ "integrity": "sha1-xL2p9e+y/OZSR4c6s2u02DT+Fvc=",
+ "dev": true,
+ "requires": {
+ "http-errors": "~1.6.2",
+ "path-is-absolute": "1.0.1"
+ },
+ "dependencies": {
+ "http-errors": {
+ "version": "1.6.3",
+ "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
+ "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
+ "dev": true,
+ "requires": {
+ "depd": "~1.1.2",
+ "inherits": "2.0.3",
+ "setprototypeof": "1.1.0",
+ "statuses": ">= 1.4.0 < 2"
+ }
+ }
+ }
+ },
+ "resolve-url": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
+ "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo="
+ },
+ "ret": {
+ "version": "0.1.15",
+ "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
+ "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg=="
+ },
+ "rollup": {
+ "version": "0.68.2",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-0.68.2.tgz",
+ "integrity": "sha512-WgjNCXYv7ZbtStIap1+tz4pd2zwz0XYN//OILwEY6dINIFLVizK1iWdu+ZtUURL/OKnp8Lv2w8FBds8YihzX7Q==",
+ "requires": {
+ "@types/estree": "0.0.39",
+ "@types/node": "*"
+ }
+ },
+ "rollup-plugin-node-resolve": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-4.0.0.tgz",
+ "integrity": "sha512-7Ni+/M5RPSUBfUaP9alwYQiIKnKeXCOHiqBpKUl9kwp3jX5ZJtgXAait1cne6pGEVUUztPD6skIKH9Kq9sNtfw==",
+ "requires": {
+ "builtin-modules": "^3.0.0",
+ "is-module": "^1.0.0",
+ "resolve": "^1.8.1"
+ }
+ },
+ "rollup-plugin-typescript2": {
+ "version": "0.20.1",
+ "resolved": "https://registry.npmjs.org/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.20.1.tgz",
+ "integrity": "sha512-uxA5JQNOfmJ9rsO0yJKTObb1t4nNYUexCg9zxhEKF+NzZwljYWdfgrA06UzA24cOk8fQjGEe7Q5+Vge2vFlnnw==",
+ "requires": {
+ "fs-extra": "7.0.1",
+ "resolve": "1.10.0",
+ "rollup-pluginutils": "2.4.1",
+ "tslib": "1.9.3"
+ },
+ "dependencies": {
+ "path-parse": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
+ "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw=="
+ },
+ "resolve": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz",
+ "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==",
+ "requires": {
+ "path-parse": "^1.0.6"
+ }
+ }
+ }
+ },
+ "rollup-pluginutils": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.4.1.tgz",
+ "integrity": "sha512-wesMQ9/172IJDIW/lYWm0vW0LiKe5Ekjws481R7z9WTRtmO59cqyM/2uUlxvf6yzm/fElFmHUobeQOYz46dZJw==",
+ "requires": {
+ "estree-walker": "^0.6.0",
+ "micromatch": "^3.1.10"
+ }
+ },
+ "rw": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz",
+ "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q="
+ },
+ "safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "dev": true
+ },
+ "safe-regex": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
+ "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
+ "requires": {
+ "ret": "~0.1.10"
+ }
+ },
+ "safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+ },
+ "semver": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
+ "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==",
+ "dev": true
+ },
+ "serve-index-75lb": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/serve-index-75lb/-/serve-index-75lb-2.0.1.tgz",
+ "integrity": "sha512-/d9r8bqJlFQcwy0a0nb1KnWAA+Mno+V+VaoKocdkbW5aXKRQd/+4bfnRhQRQr6uEoYwTRJ4xgztOyCJvWcpBpQ==",
+ "dev": true,
+ "requires": {
+ "accepts": "~1.3.4",
+ "batch": "0.6.1",
+ "debug": "2.6.9",
+ "escape-html": "~1.0.3",
+ "http-errors": "~1.6.2",
+ "mime-types": "~2.1.18",
+ "parseurl": "~1.3.2"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "http-errors": {
+ "version": "1.6.3",
+ "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
+ "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
+ "dev": true,
+ "requires": {
+ "depd": "~1.1.2",
+ "inherits": "2.0.3",
+ "setprototypeof": "1.1.0",
+ "statuses": ">= 1.4.0 < 2"
+ }
+ }
+ }
+ },
+ "set-value": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz",
+ "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==",
+ "requires": {
+ "extend-shallow": "^2.0.1",
+ "is-extendable": "^0.1.1",
+ "is-plain-object": "^2.0.3",
+ "split-string": "^3.0.1"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "setprototypeof": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz",
+ "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==",
+ "dev": true
+ },
+ "snapdragon": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
+ "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==",
+ "requires": {
+ "base": "^0.11.1",
+ "debug": "^2.2.0",
+ "define-property": "^0.2.5",
+ "extend-shallow": "^2.0.1",
+ "map-cache": "^0.2.2",
+ "source-map": "^0.5.6",
+ "source-map-resolve": "^0.5.0",
+ "use": "^3.1.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ },
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ },
+ "source-map": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
+ }
+ }
+ },
+ "snapdragon-node": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz",
+ "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==",
+ "requires": {
+ "define-property": "^1.0.0",
+ "isobject": "^3.0.0",
+ "snapdragon-util": "^3.0.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+ "requires": {
+ "is-descriptor": "^1.0.0"
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ }
+ }
+ },
+ "snapdragon-util": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz",
+ "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==",
+ "requires": {
+ "kind-of": "^3.2.0"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ },
+ "source-map-resolve": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz",
+ "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==",
+ "requires": {
+ "atob": "^2.1.1",
+ "decode-uri-component": "^0.2.0",
+ "resolve-url": "^0.2.1",
+ "source-map-url": "^0.4.0",
+ "urix": "^0.1.0"
+ }
+ },
+ "source-map-support": {
+ "version": "0.5.9",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.9.tgz",
+ "integrity": "sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA==",
+ "dev": true,
+ "requires": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ }
+ },
+ "source-map-url": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz",
+ "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM="
+ },
+ "split-string": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
+ "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==",
+ "requires": {
+ "extend-shallow": "^3.0.0"
+ }
+ },
+ "sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
+ "dev": true
+ },
+ "static-extend": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz",
+ "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=",
+ "requires": {
+ "define-property": "^0.2.5",
+ "object-copy": "^0.1.0"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ }
+ }
+ },
+ "statuses": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
+ "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=",
+ "dev": true
+ },
+ "stream-log-stats": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/stream-log-stats/-/stream-log-stats-2.0.2.tgz",
+ "integrity": "sha512-b1LccxXhMlOQQrzSqapQHyZ3UI00QTAv+8VecFgsJz//sGB5LFl/+mkFeWBVVI2/E4DlCT4sGgvLExB/VTVFfA==",
+ "dev": true,
+ "requires": {
+ "JSONStream": "^1.3.1",
+ "ansi-escape-sequences": "^3.0.0",
+ "byte-size": "^3.0.0",
+ "common-log-format": "~0.1.3",
+ "lodash.throttle": "^4.1.1",
+ "stream-via": "^1.0.3",
+ "table-layout": "~0.4.0"
+ },
+ "dependencies": {
+ "ansi-escape-sequences": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-escape-sequences/-/ansi-escape-sequences-3.0.0.tgz",
+ "integrity": "sha1-HBg5S2r5t2/5pjUJ+kl2af0s5T4=",
+ "dev": true,
+ "requires": {
+ "array-back": "^1.0.3"
+ }
+ },
+ "array-back": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/array-back/-/array-back-1.0.4.tgz",
+ "integrity": "sha1-ZEun8JX3/898Q7Xw3DnTwfA8Bjs=",
+ "dev": true,
+ "requires": {
+ "typical": "^2.6.0"
+ }
+ },
+ "byte-size": {
+ "version": "3.0.0",
+ "resolved": "http://registry.npmjs.org/byte-size/-/byte-size-3.0.0.tgz",
+ "integrity": "sha1-QG+eI2aqXav2NnLrKR17sJSV2nU=",
+ "dev": true
+ },
+ "typical": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz",
+ "integrity": "sha1-XAgOXWYcu+OCWdLnCjxyU+hziB0=",
+ "dev": true
+ }
+ }
+ },
+ "stream-read-all": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/stream-read-all/-/stream-read-all-0.1.2.tgz",
+ "integrity": "sha512-KX42xBg853m+KnwRtwCKT95ShopAbY/MNKs2dBQ0WkNeuJdqgQYRtGRbTlxdx0L6t979h3z/wMq2eMSAu7Tygw==",
+ "dev": true
+ },
+ "stream-slice": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/stream-slice/-/stream-slice-0.1.2.tgz",
+ "integrity": "sha1-LcT04bk2+xPz6zmi3vGTJ5jQeks=",
+ "dev": true
+ },
+ "stream-via": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/stream-via/-/stream-via-1.0.4.tgz",
+ "integrity": "sha512-DBp0lSvX5G9KGRDTkR/R+a29H+Wk2xItOF+MpZLLNDWbEV9tGPnqLPxHEYjmiz8xGtJHRIqmI+hCjmNzqoA4nQ==",
+ "dev": true
+ },
+ "streaming-json-stringify": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/streaming-json-stringify/-/streaming-json-stringify-3.1.0.tgz",
+ "integrity": "sha1-gCAEN6mTzDnE/gAmO3s7kDrIevU=",
+ "dev": true,
+ "requires": {
+ "json-stringify-safe": "5",
+ "readable-stream": "2"
+ }
+ },
+ "string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^2.0.0"
+ }
+ },
+ "strip-bom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+ "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
+ "dev": true,
+ "optional": true
+ },
+ "supports-color": {
+ "version": "5.4.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz",
+ "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ },
+ "table-layout": {
+ "version": "0.4.4",
+ "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-0.4.4.tgz",
+ "integrity": "sha512-uNaR3SRMJwfdp9OUr36eyEi6LLsbcTqTO/hfTsNviKsNeyMBPICJCC7QXRF3+07bAP6FRwA8rczJPBqXDc0CkQ==",
+ "dev": true,
+ "requires": {
+ "array-back": "^2.0.0",
+ "deep-extend": "~0.6.0",
+ "lodash.padend": "^4.6.1",
+ "typical": "^2.6.1",
+ "wordwrapjs": "^3.0.0"
+ },
+ "dependencies": {
+ "typical": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz",
+ "integrity": "sha1-XAgOXWYcu+OCWdLnCjxyU+hziB0=",
+ "dev": true
+ }
+ }
+ },
+ "test-value": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/test-value/-/test-value-3.0.0.tgz",
+ "integrity": "sha512-sVACdAWcZkSU9x7AOmJo5TqE+GyNJknHaHsMrR6ZnhjVlVN9Yx6FjHrsKZ3BjIpPCT68zYesPWkakrNupwfOTQ==",
+ "dev": true,
+ "requires": {
+ "array-back": "^2.0.0",
+ "typical": "^2.6.1"
+ },
+ "dependencies": {
+ "typical": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz",
+ "integrity": "sha1-XAgOXWYcu+OCWdLnCjxyU+hziB0=",
+ "dev": true
+ }
+ }
+ },
+ "thenify": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz",
+ "integrity": "sha1-5p44obq+lpsBCCB5eLn2K4hgSDk=",
+ "dev": true,
+ "requires": {
+ "any-promise": "^1.0.0"
+ }
+ },
+ "thenify-all": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
+ "integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=",
+ "dev": true,
+ "requires": {
+ "thenify": ">= 3.1.0 < 4"
+ }
+ },
+ "through": {
+ "version": "2.3.8",
+ "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz",
+ "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
+ "dev": true
+ },
+ "to-object-path": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz",
+ "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=",
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "to-regex": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz",
+ "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==",
+ "requires": {
+ "define-property": "^2.0.2",
+ "extend-shallow": "^3.0.2",
+ "regex-not": "^1.0.2",
+ "safe-regex": "^1.1.0"
+ }
+ },
+ "to-regex-range": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz",
+ "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=",
+ "requires": {
+ "is-number": "^3.0.0",
+ "repeat-string": "^1.6.1"
+ }
+ },
+ "toidentifier": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
+ "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==",
+ "dev": true
+ },
+ "ts-mocha": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ts-mocha/-/ts-mocha-2.0.0.tgz",
+ "integrity": "sha512-Rj6+vvwKtOTs5GsNO1jLl4DIXUGnyAg5HFt2Yb4SHIRN45clTJkHWpNdTxCSL0u+1oeavSYJah6d1PZ++Ju5pw==",
+ "dev": true,
+ "requires": {
+ "ts-node": "7.0.0",
+ "tsconfig-paths": "^3.5.0"
+ }
+ },
+ "ts-node": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-7.0.0.tgz",
+ "integrity": "sha512-klJsfswHP0FuOLsvBZ/zzCfUvakOSSxds78mVeK7I+qP76YWtxf16hEZsp3U+b0kIo82R5UatGFeblYMqabb2Q==",
+ "dev": true,
+ "requires": {
+ "arrify": "^1.0.0",
+ "buffer-from": "^1.1.0",
+ "diff": "^3.1.0",
+ "make-error": "^1.1.1",
+ "minimist": "^1.2.0",
+ "mkdirp": "^0.5.1",
+ "source-map-support": "^0.5.6",
+ "yn": "^2.0.0"
+ }
+ },
+ "tsconfig-paths": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.6.0.tgz",
+ "integrity": "sha512-mrqQIP2F4e03aMTCiPdedCIT300//+q0ET53o5WqqtQjmEICxP9yfz/sHTpPqXpssuJEzODsEzJaLRaf5J2X1g==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "@types/json5": "^0.0.29",
+ "deepmerge": "^2.0.1",
+ "json5": "^1.0.1",
+ "minimist": "^1.2.0",
+ "strip-bom": "^3.0.0"
+ }
+ },
+ "tslib": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz",
+ "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ=="
+ },
+ "tslint": {
+ "version": "5.12.0",
+ "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.12.0.tgz",
+ "integrity": "sha512-CKEcH1MHUBhoV43SA/Jmy1l24HJJgI0eyLbBNSRyFlsQvb9v6Zdq+Nz2vEOH00nC5SUx4SneJ59PZUS/ARcokQ==",
+ "dev": true,
+ "requires": {
+ "babel-code-frame": "^6.22.0",
+ "builtin-modules": "^1.1.1",
+ "chalk": "^2.3.0",
+ "commander": "^2.12.1",
+ "diff": "^3.2.0",
+ "glob": "^7.1.1",
+ "js-yaml": "^3.7.0",
+ "minimatch": "^3.0.4",
+ "resolve": "^1.3.2",
+ "semver": "^5.3.0",
+ "tslib": "^1.8.0",
+ "tsutils": "^2.27.2"
+ },
+ "dependencies": {
+ "builtin-modules": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz",
+ "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=",
+ "dev": true
+ }
+ }
+ },
+ "tsutils": {
+ "version": "2.29.0",
+ "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz",
+ "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==",
+ "dev": true,
+ "requires": {
+ "tslib": "^1.8.1"
+ }
+ },
+ "type-detect": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
+ "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
+ "dev": true
+ },
+ "type-is": {
+ "version": "1.6.16",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz",
+ "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==",
+ "dev": true,
+ "requires": {
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.18"
+ }
+ },
+ "typescript": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.2.tgz",
+ "integrity": "sha512-VCj5UiSyHBjwfYacmDuc/NOk4QQixbE+Wn7MFJuS0nRuPQbof132Pw4u53dm264O8LPc2MVsc7RJNml5szurkg==",
+ "dev": true
+ },
+ "typical": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/typical/-/typical-3.0.0.tgz",
+ "integrity": "sha512-2/pGDQD/q1iJWlrj357aEKGIlRvHirm81x04lsg51hreiohy2snAXoFc9dIHFWEx9LsfOVA5K7lUGM9rcUqwlQ==",
+ "dev": true
+ },
+ "union-value": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz",
+ "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=",
+ "requires": {
+ "arr-union": "^3.1.0",
+ "get-value": "^2.0.6",
+ "is-extendable": "^0.1.1",
+ "set-value": "^0.4.3"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ },
+ "set-value": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz",
+ "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=",
+ "requires": {
+ "extend-shallow": "^2.0.1",
+ "is-extendable": "^0.1.1",
+ "is-plain-object": "^2.0.1",
+ "to-object-path": "^0.3.0"
+ }
+ }
+ }
+ },
+ "universalify": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
+ },
+ "unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=",
+ "dev": true
+ },
+ "unset-value": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
+ "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=",
+ "requires": {
+ "has-value": "^0.3.1",
+ "isobject": "^3.0.0"
+ },
+ "dependencies": {
+ "has-value": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz",
+ "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=",
+ "requires": {
+ "get-value": "^2.0.3",
+ "has-values": "^0.1.4",
+ "isobject": "^2.0.0"
+ },
+ "dependencies": {
+ "isobject": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
+ "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
+ "requires": {
+ "isarray": "1.0.0"
+ }
+ }
+ }
+ },
+ "has-values": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz",
+ "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E="
+ }
+ }
+ },
+ "urix": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz",
+ "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI="
+ },
+ "use": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
+ "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ=="
+ },
+ "util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
+ "dev": true
+ },
+ "vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=",
+ "dev": true
+ },
+ "walk-back": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/walk-back/-/walk-back-3.0.0.tgz",
+ "integrity": "sha1-I1h4ejXakQMtrV6S+AsSNw2HlcU=",
+ "dev": true
+ },
+ "wordwrapjs": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-3.0.0.tgz",
+ "integrity": "sha512-mO8XtqyPvykVCsrwj5MlOVWvSnCdT+C+QVbm6blradR7JExAhbkZ7hZ9A+9NUtwzSqrlUo9a67ws0EiILrvRpw==",
+ "dev": true,
+ "requires": {
+ "reduce-flatten": "^1.0.1",
+ "typical": "^2.6.1"
+ },
+ "dependencies": {
+ "reduce-flatten": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-1.0.1.tgz",
+ "integrity": "sha1-JYx479FT3fk8tWEjf2EYTzaW4yc=",
+ "dev": true
+ },
+ "typical": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz",
+ "integrity": "sha1-XAgOXWYcu+OCWdLnCjxyU+hziB0=",
+ "dev": true
+ }
+ }
+ },
+ "wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+ "dev": true
+ },
+ "ws": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz",
+ "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==",
+ "dev": true,
+ "requires": {
+ "async-limiter": "~1.0.0"
+ }
+ },
+ "ylru": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.2.1.tgz",
+ "integrity": "sha512-faQrqNMzcPCHGVC2aaOINk13K+aaBDUPjGWl0teOXywElLjyVAB6Oe2jj62jHYtwsU49jXhScYbvPENK+6zAvQ==",
+ "dev": true
+ },
+ "yn": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz",
+ "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=",
+ "dev": true
+ }
+ }
+}
diff --git a/src/v8/tools/turbolizer/package.json b/src/v8/tools/turbolizer/package.json
new file mode 100644
index 0000000..2213fe3
--- /dev/null
+++ b/src/v8/tools/turbolizer/package.json
@@ -0,0 +1,34 @@
+{
+ "name": "turbolizer",
+ "version": "0.1.0",
+ "description": "Visualization tool for V8 TurboFan IR graphs",
+ "scripts": {
+ "build": "rollup -c",
+ "watch": "rollup -c -w",
+ "deploy": "./deploy.sh",
+ "test": "ts-mocha -p tsconfig.test.json test/**/*-test.ts",
+ "dev-server": "ws",
+ "presubmit": "tslint --project ./tslint.json --fix"
+ },
+ "author": "The V8 team",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3": "^5.5.0",
+ "d3": "^5.7.0",
+ "rollup": "^0.68.2",
+ "rollup-plugin-node-resolve": "^4.0.0",
+ "rollup-plugin-typescript2": "^0.20.1"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/v8/v8.git"
+ },
+ "devDependencies": {
+ "chai": "^4.2.0",
+ "local-web-server": "^2.6.0",
+ "mocha": "^5.2.0",
+ "ts-mocha": "^2.0.0",
+ "typescript": "^3.2.2",
+ "tslint": "^5.12.0"
+ }
+}
diff --git a/src/v8/tools/turbolizer/rollup.config.js b/src/v8/tools/turbolizer/rollup.config.js
new file mode 100644
index 0000000..05b69b8
--- /dev/null
+++ b/src/v8/tools/turbolizer/rollup.config.js
@@ -0,0 +1,32 @@
+// 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.
+
+import typescript from 'rollup-plugin-typescript2';
+import node from 'rollup-plugin-node-resolve';
+
+import path from 'path'
+
+const onwarn = warning => {
+ // Silence circular dependency warning for moment package
+ const node_modules = path.normalize('node_modules/');
+ if (warning.code === 'CIRCULAR_DEPENDENCY' &&
+ !warning.importer.indexOf(node_modules)) {
+ return
+ }
+
+ console.warn(`(!) ${warning.message}`)
+}
+
+export default {
+ input: "src/turbo-visualizer.ts",
+ plugins: [node(), typescript({
+ abortOnError: false
+ })],
+ output: {
+ file: "build/turbolizer.js",
+ format: "iife",
+ sourcemap: true
+ },
+ onwarn: onwarn
+};
diff --git a/src/v8/tools/turbolizer/src/code-view.ts b/src/v8/tools/turbolizer/src/code-view.ts
new file mode 100644
index 0000000..298f08b
--- /dev/null
+++ b/src/v8/tools/turbolizer/src/code-view.ts
@@ -0,0 +1,274 @@
+// Copyright 2015 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import { Source, SourceResolver, sourcePositionToStringKey } from "../src/source-resolver";
+import { SelectionBroker } from "../src/selection-broker";
+import { View } from "../src/view";
+import { MySelection } from "../src/selection";
+import { ViewElements } from "../src/util";
+import { SelectionHandler } from "./selection-handler";
+
+export enum CodeMode {
+ MAIN_SOURCE = "main function",
+ INLINED_SOURCE = "inlined function"
+}
+
+export class CodeView extends View {
+ broker: SelectionBroker;
+ source: Source;
+ sourceResolver: SourceResolver;
+ codeMode: CodeMode;
+ sourcePositionToHtmlElement: Map<string, HTMLElement>;
+ showAdditionalInliningPosition: boolean;
+ selectionHandler: SelectionHandler;
+ selection: MySelection;
+
+ createViewElement() {
+ const sourceContainer = document.createElement("div");
+ sourceContainer.classList.add("source-container");
+ return sourceContainer;
+ }
+
+ constructor(parent: HTMLElement, broker: SelectionBroker, sourceResolver: SourceResolver, sourceFunction: Source, codeMode: CodeMode) {
+ super(parent);
+ const view = this;
+ view.broker = broker;
+ view.sourceResolver = sourceResolver;
+ view.source = sourceFunction;
+ view.codeMode = codeMode;
+ this.sourcePositionToHtmlElement = new Map();
+ this.showAdditionalInliningPosition = false;
+
+ const selectionHandler = {
+ clear: function () {
+ view.selection.clear();
+ view.updateSelection();
+ broker.broadcastClear(this);
+ },
+ select: function (sourcePositions, selected) {
+ const locations = [];
+ for (const sourcePosition of sourcePositions) {
+ locations.push(sourcePosition);
+ sourceResolver.addInliningPositions(sourcePosition, locations);
+ }
+ if (locations.length == 0) return;
+ view.selection.select(locations, selected);
+ view.updateSelection();
+ broker.broadcastSourcePositionSelect(this, locations, selected);
+ },
+ brokeredSourcePositionSelect: function (locations, selected) {
+ const firstSelect = view.selection.isEmpty();
+ for (const location of locations) {
+ const translated = sourceResolver.translateToSourceId(view.source.sourceId, location);
+ if (!translated) continue;
+ view.selection.select([translated], selected);
+ }
+ view.updateSelection(firstSelect);
+ },
+ brokeredClear: function () {
+ view.selection.clear();
+ view.updateSelection();
+ },
+ };
+ view.selection = new MySelection(sourcePositionToStringKey);
+ broker.addSourcePositionHandler(selectionHandler);
+ this.selectionHandler = selectionHandler;
+ this.initializeCode();
+ }
+
+ addHtmlElementToSourcePosition(sourcePosition, element) {
+ const key = sourcePositionToStringKey(sourcePosition);
+ if (this.sourcePositionToHtmlElement.has(key)) {
+ console.log("Warning: duplicate source position", sourcePosition);
+ }
+ this.sourcePositionToHtmlElement.set(key, element);
+ }
+
+ getHtmlElementForSourcePosition(sourcePosition) {
+ const key = sourcePositionToStringKey(sourcePosition);
+ return this.sourcePositionToHtmlElement.get(key);
+ }
+
+ updateSelection(scrollIntoView: boolean = false): void {
+ const mkVisible = new ViewElements(this.divNode.parentNode as HTMLElement);
+ for (const [sp, el] of this.sourcePositionToHtmlElement.entries()) {
+ const isSelected = this.selection.isKeySelected(sp);
+ mkVisible.consider(el, isSelected);
+ el.classList.toggle("selected", isSelected);
+ }
+ mkVisible.apply(scrollIntoView);
+ }
+
+ getCodeHtmlElementName() {
+ return `source-pre-${this.source.sourceId}`;
+ }
+
+ getCodeHeaderHtmlElementName() {
+ return `source-pre-${this.source.sourceId}-header`;
+ }
+
+ getHtmlCodeLines(): NodeListOf<HTMLElement> {
+ const ordereList = this.divNode.querySelector(`#${this.getCodeHtmlElementName()} ol`);
+ return ordereList.childNodes as NodeListOf<HTMLElement>;
+ }
+
+ onSelectLine(lineNumber: number, doClear: boolean) {
+ if (doClear) {
+ this.selectionHandler.clear();
+ }
+ const positions = this.sourceResolver.linetoSourcePositions(lineNumber - 1);
+ if (positions !== undefined) {
+ this.selectionHandler.select(positions, undefined);
+ }
+ }
+
+ onSelectSourcePosition(sourcePosition, doClear: boolean) {
+ if (doClear) {
+ this.selectionHandler.clear();
+ }
+ this.selectionHandler.select([sourcePosition], undefined);
+ }
+
+ initializeCode() {
+ const view = this;
+ const source = this.source;
+ const sourceText = source.sourceText;
+ if (!sourceText) return;
+ const sourceContainer = view.divNode;
+ if (this.codeMode == CodeMode.MAIN_SOURCE) {
+ sourceContainer.classList.add("main-source");
+ } else {
+ sourceContainer.classList.add("inlined-source");
+ }
+ const codeHeader = document.createElement("div");
+ codeHeader.setAttribute("id", this.getCodeHeaderHtmlElementName());
+ codeHeader.classList.add("code-header");
+ const codeFileFunction = document.createElement("div");
+ codeFileFunction.classList.add("code-file-function");
+ codeFileFunction.innerHTML = `${source.sourceName}:${source.functionName}`;
+ codeHeader.appendChild(codeFileFunction);
+ const codeModeDiv = document.createElement("div");
+ codeModeDiv.classList.add("code-mode");
+ codeModeDiv.innerHTML = `${this.codeMode}`;
+ codeHeader.appendChild(codeModeDiv);
+ const clearDiv = document.createElement("div");
+ clearDiv.style.clear = "both";
+ codeHeader.appendChild(clearDiv);
+ sourceContainer.appendChild(codeHeader);
+ const codePre = document.createElement("pre");
+ codePre.setAttribute("id", this.getCodeHtmlElementName());
+ codePre.classList.add("prettyprint");
+ sourceContainer.appendChild(codePre);
+
+ codeHeader.onclick = function myFunction() {
+ if (codePre.style.display === "none") {
+ codePre.style.display = "block";
+ } else {
+ codePre.style.display = "none";
+ }
+ };
+ if (sourceText != "") {
+ codePre.classList.add("linenums");
+ codePre.textContent = sourceText;
+ try {
+ // Wrap in try to work when offline.
+ PR.prettyPrint(undefined, sourceContainer);
+ } catch (e) {
+ console.log(e);
+ }
+
+ view.divNode.onclick = function (e: MouseEvent) {
+ if (e.target instanceof Element && e.target.tagName == "DIV") {
+ const targetDiv = e.target as HTMLDivElement;
+ if (targetDiv.classList.contains("line-number")) {
+ e.stopPropagation();
+ view.onSelectLine(Number(targetDiv.dataset.lineNumber), !e.shiftKey);
+ }
+ } else {
+ view.selectionHandler.clear();
+ }
+ };
+
+ const base: number = source.startPosition;
+ let current = 0;
+ const lineListDiv = this.getHtmlCodeLines();
+ let newlineAdjust = 0;
+ for (let i = 0; i < lineListDiv.length; i++) {
+ // Line numbers are not zero-based.
+ const lineNumber = i + 1;
+ const currentLineElement = lineListDiv[i];
+ currentLineElement.id = "li" + i;
+ currentLineElement.dataset.lineNumber = "" + lineNumber;
+ const spans = currentLineElement.childNodes;
+ for (const currentSpan of spans) {
+ if (currentSpan instanceof HTMLSpanElement) {
+ const pos = base + current;
+ const end = pos + currentSpan.textContent.length;
+ current += currentSpan.textContent.length;
+ this.insertSourcePositions(currentSpan, lineNumber, pos, end, newlineAdjust);
+ newlineAdjust = 0;
+ }
+ }
+
+ this.insertLineNumber(currentLineElement, lineNumber);
+
+ while ((current < sourceText.length) &&
+ (sourceText[current] == '\n' || sourceText[current] == '\r')) {
+ ++current;
+ ++newlineAdjust;
+ }
+ }
+ }
+ }
+
+ insertSourcePositions(currentSpan, lineNumber, pos, end, adjust) {
+ const view = this;
+ const sps = this.sourceResolver.sourcePositionsInRange(this.source.sourceId, pos - adjust, end);
+ let offset = 0;
+ for (const sourcePosition of sps) {
+ this.sourceResolver.addAnyPositionToLine(lineNumber, sourcePosition);
+ const textnode = currentSpan.tagName == 'SPAN' ? currentSpan.lastChild : currentSpan;
+ if (!(textnode instanceof Text)) continue;
+ const splitLength = Math.max(0, sourcePosition.scriptOffset - pos - offset);
+ offset += splitLength;
+ const replacementNode = textnode.splitText(splitLength);
+ const span = document.createElement('span');
+ span.setAttribute("scriptOffset", sourcePosition.scriptOffset);
+ span.classList.add("source-position");
+ const marker = document.createElement('span');
+ marker.classList.add("marker");
+ span.appendChild(marker);
+ const inlining = this.sourceResolver.getInliningForPosition(sourcePosition);
+ if (inlining != undefined && view.showAdditionalInliningPosition) {
+ const sourceName = this.sourceResolver.getSourceName(inlining.sourceId);
+ const inliningMarker = document.createElement('span');
+ inliningMarker.classList.add("inlining-marker");
+ inliningMarker.setAttribute("data-descr", `${sourceName} was inlined here`);
+ span.appendChild(inliningMarker);
+ }
+ span.onclick = function (e) {
+ e.stopPropagation();
+ view.onSelectSourcePosition(sourcePosition, !e.shiftKey);
+ };
+ view.addHtmlElementToSourcePosition(sourcePosition, span);
+ textnode.parentNode.insertBefore(span, replacementNode);
+ }
+ }
+
+ insertLineNumber(lineElement: HTMLElement, lineNumber: number) {
+ const view = this;
+ const lineNumberElement = document.createElement("div");
+ lineNumberElement.classList.add("line-number");
+ lineNumberElement.dataset.lineNumber = `${lineNumber}`;
+ lineNumberElement.innerText = `${lineNumber}`;
+ lineElement.insertBefore(lineNumberElement, lineElement.firstChild);
+ // Don't add lines to source positions of not in backwardsCompatibility mode.
+ if (this.source.backwardsCompatibility === true) {
+ for (const sourcePosition of this.sourceResolver.linetoSourcePositions(lineNumber - 1)) {
+ view.addHtmlElementToSourcePosition(sourcePosition, lineElement);
+ }
+ }
+ }
+
+}
diff --git a/src/v8/tools/turbolizer/src/constants.ts b/src/v8/tools/turbolizer/src/constants.ts
new file mode 100644
index 0000000..1551d48
--- /dev/null
+++ b/src/v8/tools/turbolizer/src/constants.ts
@@ -0,0 +1,26 @@
+// Copyright 2014 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.
+
+export const MAX_RANK_SENTINEL = 0;
+export const GRAPH_MARGIN = 250;
+export const SOURCE_PANE_ID = 'left';
+export const SOURCE_COLLAPSE_ID = 'source-shrink';
+export const SOURCE_EXPAND_ID = 'source-expand';
+export const INTERMEDIATE_PANE_ID = 'middle';
+export const GRAPH_PANE_ID = 'graph';
+export const SCHEDULE_PANE_ID = 'schedule';
+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 COLLAPSE_PANE_BUTTON_VISIBLE = 'button-input';
+export const COLLAPSE_PANE_BUTTON_INVISIBLE = 'button-input-invisible';
+export const UNICODE_BLOCK = '▋';
+export const PROF_COLS = [
+ { perc: 0, col: { r: 255, g: 255, b: 255 } },
+ { perc: 0.5, col: { r: 255, g: 255, b: 128 } },
+ { perc: 5, col: { r: 255, g: 128, b: 0 } },
+ { perc: 15, col: { r: 255, g: 0, b: 0 } },
+ { perc: 100, col: { r: 0, g: 0, b: 0 } }
+];
diff --git a/src/v8/tools/turbolizer/src/disassembly-view.ts b/src/v8/tools/turbolizer/src/disassembly-view.ts
new file mode 100644
index 0000000..4b8fc6e
--- /dev/null
+++ b/src/v8/tools/turbolizer/src/disassembly-view.ts
@@ -0,0 +1,354 @@
+// Copyright 2015 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import { PROF_COLS, UNICODE_BLOCK } from "../src/constants";
+import { SelectionBroker } from "../src/selection-broker";
+import { TextView } from "../src/text-view";
+import { MySelection } from "./selection";
+import { anyToString, interpolate } from "./util";
+import { InstructionSelectionHandler } from "./selection-handler";
+
+const toolboxHTML = `<div id="disassembly-toolbox">
+<form>
+ <label><input id="show-instruction-address" type="checkbox" name="instruction-address">Show addresses</label>
+ <label><input id="show-instruction-binary" type="checkbox" name="instruction-binary">Show binary literal</label>
+</form>
+</div>`;
+
+export class DisassemblyView extends TextView {
+ SOURCE_POSITION_HEADER_REGEX: any;
+ addrEventCounts: any;
+ totalEventCounts: any;
+ maxEventCounts: any;
+ posLines: Array<any>;
+ instructionSelectionHandler: InstructionSelectionHandler;
+ offsetSelection: MySelection;
+ showInstructionAddressHandler: () => void;
+ showInstructionBinaryHandler: () => void;
+
+ createViewElement() {
+ const pane = document.createElement('div');
+ pane.setAttribute('id', "disassembly");
+ pane.innerHTML =
+ `<pre id='disassembly-text-pre' class='prettyprint prettyprinted'>
+ <ul id='disassembly-list' class='nolinenums noindent'>
+ </ul>
+ </pre>`;
+
+ return pane;
+ }
+
+ constructor(parentId, broker: SelectionBroker) {
+ super(parentId, broker);
+ const view = this;
+ const ADDRESS_STYLE = {
+ associateData: (text, fragment: HTMLElement) => {
+ const matches = text.match(/(?<address>0?x?[0-9a-fA-F]{8,16})(?<addressSpace>\s+)(?<offset>[0-9a-f]+)(?<offsetSpace>\s*)/);
+ const offset = Number.parseInt(matches.groups["offset"], 16);
+ const addressElement = document.createElement("SPAN");
+ addressElement.className = "instruction-address";
+ addressElement.innerText = matches.groups["address"];
+ const offsetElement = document.createElement("SPAN");
+ offsetElement.innerText = matches.groups["offset"];
+ fragment.appendChild(addressElement);
+ fragment.appendChild(document.createTextNode(matches.groups["addressSpace"]));
+ fragment.appendChild(offsetElement);
+ fragment.appendChild(document.createTextNode(matches.groups["offsetSpace"]));
+ fragment.classList.add('tag');
+
+ if (!Number.isNaN(offset)) {
+ const pcOffset = view.sourceResolver.getKeyPcOffset(offset);
+ fragment.dataset.pcOffset = `${pcOffset}`;
+ addressElement.classList.add('linkable-text');
+ offsetElement.classList.add('linkable-text');
+ }
+ }
+ };
+ const UNCLASSIFIED_STYLE = {
+ css: 'com'
+ };
+ const NUMBER_STYLE = {
+ css: ['instruction-binary', 'lit']
+ };
+ const COMMENT_STYLE = {
+ css: 'com'
+ };
+ const OPCODE_ARGS = {
+ associateData: function (text, fragment) {
+ fragment.innerHTML = text;
+ const replacer = (match, hexOffset) => {
+ const offset = Number.parseInt(hexOffset, 16);
+ const keyOffset = view.sourceResolver.getKeyPcOffset(offset);
+ return `<span class="tag linkable-text" data-pc-offset="${keyOffset}">${match}</span>`;
+ };
+ const html = text.replace(/<.0?x?([0-9a-fA-F]+)>/g, replacer);
+ fragment.innerHTML = html;
+ }
+ };
+ const OPCODE_STYLE = {
+ css: 'kwd'
+ };
+ const BLOCK_HEADER_STYLE = {
+ associateData: function (text, fragment) {
+ const matches = /\d+/.exec(text);
+ if (!matches) return;
+ const blockId = matches[0];
+ fragment.dataset.blockId = blockId;
+ fragment.innerHTML = text;
+ fragment.className = "com block";
+ }
+ };
+ const SOURCE_POSITION_HEADER_STYLE = {
+ css: 'com'
+ };
+ view.SOURCE_POSITION_HEADER_REGEX = /^\s*--[^<]*<.*(not inlined|inlined\((\d+)\)):(\d+)>\s*--/;
+ const patterns = [
+ [
+ [/^0?x?[0-9a-fA-F]{8,16}\s+[0-9a-f]+\s+/, ADDRESS_STYLE, 1],
+ [view.SOURCE_POSITION_HEADER_REGEX, SOURCE_POSITION_HEADER_STYLE, -1],
+ [/^\s+-- B\d+ start.*/, BLOCK_HEADER_STYLE, -1],
+ [/^.*/, UNCLASSIFIED_STYLE, -1]
+ ],
+ [
+ [/^\s*[0-9a-f]+\s+/, NUMBER_STYLE, 2],
+ [/^\s*[0-9a-f]+\s+[0-9a-f]+\s+/, NUMBER_STYLE, 2],
+ [/^.*/, null, -1]
+ ],
+ [
+ [/^REX.W \S+\s+/, OPCODE_STYLE, 3],
+ [/^\S+\s+/, OPCODE_STYLE, 3],
+ [/^\S+$/, OPCODE_STYLE, -1],
+ [/^.*/, null, -1]
+ ],
+ [
+ [/^\s+/, null],
+ [/^[^;]+$/, OPCODE_ARGS, -1],
+ [/^[^;]+/, OPCODE_ARGS, 4],
+ [/^;/, COMMENT_STYLE, 5]
+ ],
+ [
+ [/^.+$/, COMMENT_STYLE, -1]
+ ]
+ ];
+ view.setPatterns(patterns);
+
+ const linkHandler = (e: MouseEvent) => {
+ if (!(e.target instanceof HTMLElement)) return;
+ const offsetAsString = e.target.dataset.pcOffset ? e.target.dataset.pcOffset : e.target.parentElement.dataset.pcOffset;
+ const offset = Number.parseInt(offsetAsString, 10);
+ if ((typeof offsetAsString) != "undefined" && !Number.isNaN(offset)) {
+ view.offsetSelection.select([offset], true);
+ const nodes = view.sourceResolver.nodesForPCOffset(offset)[0];
+ if (nodes.length > 0) {
+ e.stopPropagation();
+ if (!e.shiftKey) {
+ view.selectionHandler.clear();
+ }
+ view.selectionHandler.select(nodes, true);
+ } else {
+ view.updateSelection();
+ }
+ }
+ return undefined;
+ };
+ view.divNode.addEventListener('click', linkHandler);
+
+ const linkHandlerBlock = e => {
+ const blockId = e.target.dataset.blockId;
+ if (typeof blockId != "undefined" && !Number.isNaN(blockId)) {
+ e.stopPropagation();
+ if (!e.shiftKey) {
+ view.selectionHandler.clear();
+ }
+ view.blockSelectionHandler.select([blockId], true);
+ }
+ };
+ view.divNode.addEventListener('click', linkHandlerBlock);
+
+ this.offsetSelection = new MySelection(anyToString);
+ const instructionSelectionHandler = {
+ clear: function () {
+ view.offsetSelection.clear();
+ view.updateSelection();
+ broker.broadcastClear(instructionSelectionHandler);
+ },
+ select: function (instructionIds, selected) {
+ view.offsetSelection.select(instructionIds, selected);
+ view.updateSelection();
+ broker.broadcastBlockSelect(instructionSelectionHandler, instructionIds, selected);
+ },
+ brokeredInstructionSelect: function (instructionIds, selected) {
+ const firstSelect = view.offsetSelection.isEmpty();
+ const keyPcOffsets = view.sourceResolver.instructionsToKeyPcOffsets(instructionIds);
+ view.offsetSelection.select(keyPcOffsets, selected);
+ view.updateSelection(firstSelect);
+ },
+ brokeredClear: function () {
+ view.offsetSelection.clear();
+ view.updateSelection();
+ }
+ };
+ this.instructionSelectionHandler = instructionSelectionHandler;
+ broker.addInstructionHandler(instructionSelectionHandler);
+
+ const toolbox = document.createElement("div");
+ toolbox.id = "toolbox-anchor";
+ toolbox.innerHTML = toolboxHTML;
+ view.divNode.insertBefore(toolbox, view.divNode.firstChild);
+ const instructionAddressInput: HTMLInputElement = view.divNode.querySelector("#show-instruction-address");
+ const lastShowInstructionAddress = window.sessionStorage.getItem("show-instruction-address");
+ instructionAddressInput.checked = lastShowInstructionAddress == 'true';
+ const showInstructionAddressHandler = () => {
+ window.sessionStorage.setItem("show-instruction-address", `${instructionAddressInput.checked}`);
+ for (const el of view.divNode.querySelectorAll(".instruction-address")) {
+ el.classList.toggle("invisible", !instructionAddressInput.checked);
+ }
+ };
+ instructionAddressInput.addEventListener("change", showInstructionAddressHandler);
+ this.showInstructionAddressHandler = showInstructionAddressHandler;
+
+ const instructionBinaryInput: HTMLInputElement = view.divNode.querySelector("#show-instruction-binary");
+ const lastShowInstructionBinary = window.sessionStorage.getItem("show-instruction-binary");
+ instructionBinaryInput.checked = lastShowInstructionBinary == 'true';
+ const showInstructionBinaryHandler = () => {
+ window.sessionStorage.setItem("show-instruction-binary", `${instructionBinaryInput.checked}`);
+ for (const el of view.divNode.querySelectorAll(".instruction-binary")) {
+ el.classList.toggle("invisible", !instructionBinaryInput.checked);
+ }
+ };
+ instructionBinaryInput.addEventListener("change", showInstructionBinaryHandler);
+ this.showInstructionBinaryHandler = showInstructionBinaryHandler;
+ }
+
+ updateSelection(scrollIntoView: boolean = false) {
+ super.updateSelection(scrollIntoView);
+ const keyPcOffsets = this.sourceResolver.nodesToKeyPcOffsets(this.selection.selectedKeys());
+ if (this.offsetSelection) {
+ for (const key of this.offsetSelection.selectedKeys()) {
+ keyPcOffsets.push(Number(key));
+ }
+ }
+ for (const keyPcOffset of keyPcOffsets) {
+ const elementsToSelect = this.divNode.querySelectorAll(`[data-pc-offset='${keyPcOffset}']`);
+ for (const el of elementsToSelect) {
+ el.classList.toggle("selected", true);
+ }
+ }
+ }
+
+ initializeCode(sourceText, sourcePosition: number = 0) {
+ const view = this;
+ view.addrEventCounts = null;
+ view.totalEventCounts = null;
+ view.maxEventCounts = null;
+ view.posLines = new Array();
+ // Comment lines for line 0 include sourcePosition already, only need to
+ // add sourcePosition for lines > 0.
+ view.posLines[0] = sourcePosition;
+ if (sourceText && sourceText != "") {
+ const base = sourcePosition;
+ let current = 0;
+ const sourceLines = sourceText.split("\n");
+ for (let i = 1; i < sourceLines.length; i++) {
+ // Add 1 for newline character that is split off.
+ current += sourceLines[i - 1].length + 1;
+ view.posLines[i] = base + current;
+ }
+ }
+ }
+
+ initializePerfProfile(eventCounts) {
+ const view = this;
+ if (eventCounts !== undefined) {
+ view.addrEventCounts = eventCounts;
+
+ view.totalEventCounts = {};
+ view.maxEventCounts = {};
+ for (const evName in view.addrEventCounts) {
+ if (view.addrEventCounts.hasOwnProperty(evName)) {
+ const keys = Object.keys(view.addrEventCounts[evName]);
+ const values = keys.map(key => view.addrEventCounts[evName][key]);
+ view.totalEventCounts[evName] = values.reduce((a, b) => a + b);
+ view.maxEventCounts[evName] = values.reduce((a, b) => Math.max(a, b));
+ }
+ }
+ } else {
+ view.addrEventCounts = null;
+ view.totalEventCounts = null;
+ view.maxEventCounts = null;
+ }
+ }
+
+ showContent(data): void {
+ console.time("disassembly-view");
+ super.initializeContent(data, null);
+ this.showInstructionAddressHandler();
+ this.showInstructionBinaryHandler();
+ console.timeEnd("disassembly-view");
+ }
+
+ // Shorten decimals and remove trailing zeroes for readability.
+ humanize(num) {
+ return num.toFixed(3).replace(/\.?0+$/, "") + "%";
+ }
+
+ processLine(line) {
+ const view = this;
+ let fragments = super.processLine(line);
+
+ // Add profiling data per instruction if available.
+ if (view.totalEventCounts) {
+ const matches = /^(0x[0-9a-fA-F]+)\s+\d+\s+[0-9a-fA-F]+/.exec(line);
+ if (matches) {
+ const newFragments = [];
+ for (const event in view.addrEventCounts) {
+ if (!view.addrEventCounts.hasOwnProperty(event)) continue;
+ const count = view.addrEventCounts[event][matches[1]];
+ let str = " ";
+ const cssCls = "prof";
+ if (count !== undefined) {
+ const perc = count / view.totalEventCounts[event] * 100;
+
+ let col = { r: 255, g: 255, b: 255 };
+ for (let i = 0; i < PROF_COLS.length; i++) {
+ if (perc === PROF_COLS[i].perc) {
+ col = PROF_COLS[i].col;
+ break;
+ } else if (perc > PROF_COLS[i].perc && perc < PROF_COLS[i + 1].perc) {
+ const col1 = PROF_COLS[i].col;
+ const col2 = PROF_COLS[i + 1].col;
+
+ const val = perc - PROF_COLS[i].perc;
+ const max = PROF_COLS[i + 1].perc - PROF_COLS[i].perc;
+
+ col.r = Math.round(interpolate(val, max, col1.r, col2.r));
+ col.g = Math.round(interpolate(val, max, col1.g, col2.g));
+ col.b = Math.round(interpolate(val, max, col1.b, col2.b));
+ break;
+ }
+ }
+
+ str = UNICODE_BLOCK;
+
+ const fragment = view.createFragment(str, cssCls);
+ fragment.title = event + ": " + view.humanize(perc) + " (" + count + ")";
+ fragment.style.color = "rgb(" + col.r + ", " + col.g + ", " + col.b + ")";
+
+ newFragments.push(fragment);
+ } else {
+ newFragments.push(view.createFragment(str, cssCls));
+ }
+ }
+ fragments = newFragments.concat(fragments);
+ }
+ }
+ return fragments;
+ }
+
+ detachSelection() { return null; }
+
+ public searchInputAction(searchInput: HTMLInputElement, e: Event, onlyVisible: boolean): void {
+ throw new Error("Method not implemented.");
+ }
+}
diff --git a/src/v8/tools/turbolizer/src/edge.ts b/src/v8/tools/turbolizer/src/edge.ts
new file mode 100644
index 0000000..30d265c
--- /dev/null
+++ b/src/v8/tools/turbolizer/src/edge.ts
@@ -0,0 +1,90 @@
+// Copyright 2014 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 { GNode, DEFAULT_NODE_BUBBLE_RADIUS } from "../src/node";
+import { Graph } from "./graph";
+
+export const MINIMUM_EDGE_SEPARATION = 20;
+
+export class Edge {
+ target: GNode;
+ source: GNode;
+ index: number;
+ type: string;
+ backEdgeNumber: number;
+ visible: boolean;
+
+ constructor(target: GNode, index: number, source: GNode, type: string) {
+ this.target = target;
+ this.source = source;
+ this.index = index;
+ this.type = type;
+ this.backEdgeNumber = 0;
+ this.visible = false;
+ }
+
+ stringID() {
+ return this.source.id + "," + this.index + "," + this.target.id;
+ }
+
+ isVisible() {
+ return this.visible && this.source.visible && this.target.visible;
+ }
+
+ getInputHorizontalPosition(graph: Graph, showTypes: boolean) {
+ if (this.backEdgeNumber > 0) {
+ return graph.maxGraphNodeX + this.backEdgeNumber * MINIMUM_EDGE_SEPARATION;
+ }
+ const source = this.source;
+ const target = this.target;
+ const index = this.index;
+ const inputX = target.x + target.getInputX(index);
+ const inputApproach = target.getInputApproach(this.index);
+ const outputApproach = source.getOutputApproach(showTypes);
+ if (inputApproach > outputApproach) {
+ return inputX;
+ } else {
+ const inputOffset = MINIMUM_EDGE_SEPARATION * (index + 1);
+ return (target.x < source.x)
+ ? (target.x + target.getTotalNodeWidth() + inputOffset)
+ : (target.x - inputOffset);
+ }
+ }
+
+ generatePath(graph: Graph, showTypes: boolean) {
+ const target = this.target;
+ const source = this.source;
+ const inputX = target.x + target.getInputX(this.index);
+ const arrowheadHeight = 7;
+ const inputY = target.y - 2 * DEFAULT_NODE_BUBBLE_RADIUS - arrowheadHeight;
+ const outputX = source.x + source.getOutputX();
+ const outputY = source.y + source.getNodeHeight(showTypes) + DEFAULT_NODE_BUBBLE_RADIUS;
+ let inputApproach = target.getInputApproach(this.index);
+ const outputApproach = source.getOutputApproach(showTypes);
+ const horizontalPos = this.getInputHorizontalPosition(graph, showTypes);
+
+ let result = "M" + outputX + "," + outputY +
+ "L" + outputX + "," + outputApproach +
+ "L" + horizontalPos + "," + outputApproach;
+
+ if (horizontalPos != inputX) {
+ result += "L" + horizontalPos + "," + inputApproach;
+ } else {
+ if (inputApproach < outputApproach) {
+ inputApproach = outputApproach;
+ }
+ }
+
+ result += "L" + inputX + "," + inputApproach +
+ "L" + inputX + "," + inputY;
+ return result;
+ }
+
+ isBackEdge() {
+ return this.target.hasBackEdges() && (this.target.rank < this.source.rank);
+ }
+
+}
+
+export const edgeToStr = (e: Edge) => e.stringID();
diff --git a/src/v8/tools/turbolizer/src/graph-layout.ts b/src/v8/tools/turbolizer/src/graph-layout.ts
new file mode 100644
index 0000000..3687c28
--- /dev/null
+++ b/src/v8/tools/turbolizer/src/graph-layout.ts
@@ -0,0 +1,461 @@
+// Copyright 2015 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import { MAX_RANK_SENTINEL } from "../src/constants";
+import { MINIMUM_EDGE_SEPARATION, Edge } from "../src/edge";
+import { NODE_INPUT_WIDTH, MINIMUM_NODE_OUTPUT_APPROACH, DEFAULT_NODE_BUBBLE_RADIUS, GNode } from "../src/node";
+import { Graph } from "./graph";
+
+const DEFAULT_NODE_ROW_SEPARATION = 130;
+const traceLayout = false;
+
+function newGraphOccupation(graph: Graph) {
+ const isSlotFilled = [];
+ let maxSlot = 0;
+ let minSlot = 0;
+ let nodeOccupation: Array<[number, number]> = [];
+
+ function slotToIndex(slot: number) {
+ if (slot >= 0) {
+ return slot * 2;
+ } else {
+ return slot * 2 + 1;
+ }
+ }
+
+ function positionToSlot(pos: number) {
+ return Math.floor(pos / NODE_INPUT_WIDTH);
+ }
+
+ function slotToLeftPosition(slot: number) {
+ return slot * NODE_INPUT_WIDTH;
+ }
+
+ function findSpace(pos: number, width: number, direction: number) {
+ const widthSlots = Math.floor((width + NODE_INPUT_WIDTH - 1) /
+ NODE_INPUT_WIDTH);
+ const currentSlot = positionToSlot(pos + width / 2);
+ let currentScanSlot = currentSlot;
+ let widthSlotsRemainingLeft = widthSlots;
+ let widthSlotsRemainingRight = widthSlots;
+ let slotsChecked = 0;
+ while (true) {
+ const mod = slotsChecked++ % 2;
+ currentScanSlot = currentSlot + (mod ? -1 : 1) * (slotsChecked >> 1);
+ if (!isSlotFilled[slotToIndex(currentScanSlot)]) {
+ if (mod) {
+ if (direction <= 0) --widthSlotsRemainingLeft;
+ } else {
+ if (direction >= 0) --widthSlotsRemainingRight;
+ }
+ if (widthSlotsRemainingLeft == 0 ||
+ widthSlotsRemainingRight == 0 ||
+ (widthSlotsRemainingLeft + widthSlotsRemainingRight) == widthSlots &&
+ (widthSlots == slotsChecked)) {
+ if (mod) {
+ return [currentScanSlot, widthSlots];
+ } else {
+ return [currentScanSlot - widthSlots + 1, widthSlots];
+ }
+ }
+ } else {
+ if (mod) {
+ widthSlotsRemainingLeft = widthSlots;
+ } else {
+ widthSlotsRemainingRight = widthSlots;
+ }
+ }
+ }
+ }
+
+ function setIndexRange(from: number, to: number, value: boolean) {
+ if (to < from) {
+ throw ("illegal slot range");
+ }
+ while (from <= to) {
+ if (from > maxSlot) {
+ maxSlot = from;
+ }
+ if (from < minSlot) {
+ minSlot = from;
+ }
+ isSlotFilled[slotToIndex(from++)] = value;
+ }
+ }
+
+ function occupySlotRange(from: number, to: number) {
+ if (traceLayout) {
+ console.log("Occupied [" + slotToLeftPosition(from) + " " + slotToLeftPosition(to + 1) + ")");
+ }
+ setIndexRange(from, to, true);
+ }
+
+ function clearSlotRange(from: number, to: number) {
+ if (traceLayout) {
+ console.log("Cleared [" + slotToLeftPosition(from) + " " + slotToLeftPosition(to + 1) + ")");
+ }
+ setIndexRange(from, to, false);
+ }
+
+ function occupyPositionRange(from: number, to: number) {
+ occupySlotRange(positionToSlot(from), positionToSlot(to - 1));
+ }
+
+ function clearPositionRange(from: number, to: number) {
+ clearSlotRange(positionToSlot(from), positionToSlot(to - 1));
+ }
+
+ function occupyPositionRangeWithMargin(from: number, to: number, margin: number) {
+ const fromMargin = from - Math.floor(margin);
+ const toMargin = to + Math.floor(margin);
+ occupyPositionRange(fromMargin, toMargin);
+ }
+
+ function clearPositionRangeWithMargin(from: number, to: number, margin: number) {
+ const fromMargin = from - Math.floor(margin);
+ const toMargin = to + Math.floor(margin);
+ clearPositionRange(fromMargin, toMargin);
+ }
+
+ const occupation = {
+ occupyNodeInputs: function (node: GNode, showTypes: boolean) {
+ for (let i = 0; i < node.inputs.length; ++i) {
+ if (node.inputs[i].isVisible()) {
+ const edge = node.inputs[i];
+ if (!edge.isBackEdge()) {
+ const horizontalPos = edge.getInputHorizontalPosition(graph, showTypes);
+ if (traceLayout) {
+ console.log("Occupying input " + i + " of " + node.id + " at " + horizontalPos);
+ }
+ occupyPositionRangeWithMargin(horizontalPos,
+ horizontalPos,
+ NODE_INPUT_WIDTH / 2);
+ }
+ }
+ }
+ },
+ occupyNode: function (node: GNode) {
+ const getPlacementHint = function (n: GNode) {
+ let pos = 0;
+ let direction = -1;
+ let outputEdges = 0;
+ let inputEdges = 0;
+ for (const outputEdge of n.outputs) {
+ if (outputEdge.isVisible()) {
+ const output = outputEdge.target;
+ for (let l = 0; l < output.inputs.length; ++l) {
+ if (output.rank > n.rank) {
+ const inputEdge = output.inputs[l];
+ if (inputEdge.isVisible()) {
+ ++inputEdges;
+ }
+ if (output.inputs[l].source == n) {
+ pos += output.x + output.getInputX(l) + NODE_INPUT_WIDTH / 2;
+ outputEdges++;
+ if (l >= (output.inputs.length / 2)) {
+ direction = 1;
+ }
+ }
+ }
+ }
+ }
+ }
+ if (outputEdges != 0) {
+ pos = pos / outputEdges;
+ }
+ if (outputEdges > 1 || inputEdges == 1) {
+ direction = 0;
+ }
+ return [direction, pos];
+ };
+ const width = node.getTotalNodeWidth();
+ const margin = MINIMUM_EDGE_SEPARATION;
+ const paddedWidth = width + 2 * margin;
+ const placementHint = getPlacementHint(node);
+ const x = placementHint[1] - paddedWidth + margin;
+ if (traceLayout) {
+ console.log("Node " + node.id + " placement hint [" + x + ", " + (x + paddedWidth) + ")");
+ }
+ const placement = findSpace(x, paddedWidth, placementHint[0]);
+ const firstSlot = placement[0];
+ const slotWidth = placement[1];
+ const endSlotExclusive = firstSlot + slotWidth - 1;
+ occupySlotRange(firstSlot, endSlotExclusive);
+ nodeOccupation.push([firstSlot, endSlotExclusive]);
+ if (placementHint[0] < 0) {
+ return slotToLeftPosition(firstSlot + slotWidth) - width - margin;
+ } else if (placementHint[0] > 0) {
+ return slotToLeftPosition(firstSlot) + margin;
+ } else {
+ return slotToLeftPosition(firstSlot + slotWidth / 2) - (width / 2);
+ }
+ },
+ clearOccupiedNodes: function () {
+ nodeOccupation.forEach(([firstSlot, endSlotExclusive]) => {
+ clearSlotRange(firstSlot, endSlotExclusive);
+ });
+ nodeOccupation = [];
+ },
+ clearNodeOutputs: function (source: GNode, showTypes: boolean) {
+ source.outputs.forEach(function (edge) {
+ if (edge.isVisible()) {
+ const target = edge.target;
+ for (const inputEdge of target.inputs) {
+ if (inputEdge.source === source) {
+ const horizontalPos = edge.getInputHorizontalPosition(graph, showTypes);
+ clearPositionRangeWithMargin(horizontalPos,
+ horizontalPos,
+ NODE_INPUT_WIDTH / 2);
+ }
+ }
+ }
+ });
+ },
+ print: function () {
+ let s = "";
+ for (let currentSlot = -40; currentSlot < 40; ++currentSlot) {
+ if (currentSlot != 0) {
+ s += " ";
+ } else {
+ s += "|";
+ }
+ }
+ console.log(s);
+ s = "";
+ for (let currentSlot2 = -40; currentSlot2 < 40; ++currentSlot2) {
+ if (isSlotFilled[slotToIndex(currentSlot2)]) {
+ s += "*";
+ } else {
+ s += " ";
+ }
+ }
+ console.log(s);
+ }
+ };
+ return occupation;
+}
+
+export function layoutNodeGraph(graph: Graph, showTypes: boolean): void {
+ // First determine the set of nodes that have no outputs. Those are the
+ // basis for bottom-up DFS to determine rank and node placement.
+
+ const start = performance.now();
+
+ const endNodesHasNoOutputs = [];
+ const startNodesHasNoInputs = [];
+ for (const n of graph.nodes()) {
+ endNodesHasNoOutputs[n.id] = true;
+ startNodesHasNoInputs[n.id] = true;
+ }
+ graph.forEachEdge((e: Edge) => {
+ endNodesHasNoOutputs[e.source.id] = false;
+ startNodesHasNoInputs[e.target.id] = false;
+ });
+
+ // Finialize the list of start and end nodes.
+ const endNodes: Array<GNode> = [];
+ const startNodes: Array<GNode> = [];
+ let visited: Array<boolean> = [];
+ const rank: Array<number> = [];
+ for (const n of graph.nodes()) {
+ if (endNodesHasNoOutputs[n.id]) {
+ endNodes.push(n);
+ }
+ if (startNodesHasNoInputs[n.id]) {
+ startNodes.push(n);
+ }
+ visited[n.id] = false;
+ rank[n.id] = -1;
+ n.rank = 0;
+ n.visitOrderWithinRank = 0;
+ n.outputApproach = MINIMUM_NODE_OUTPUT_APPROACH;
+ }
+
+ if (traceLayout) {
+ console.log(`layoutGraph init ${performance.now() - start}`);
+ }
+
+ let maxRank = 0;
+ visited = [];
+ let visitOrderWithinRank = 0;
+
+ const worklist: Array<GNode> = startNodes.slice();
+ while (worklist.length != 0) {
+ const n: GNode = worklist.pop();
+ let changed = false;
+ if (n.rank == MAX_RANK_SENTINEL) {
+ n.rank = 1;
+ changed = true;
+ }
+ let begin = 0;
+ let end = n.inputs.length;
+ if (n.nodeLabel.opcode == 'Phi' ||
+ n.nodeLabel.opcode == 'EffectPhi' ||
+ n.nodeLabel.opcode == 'InductionVariablePhi') {
+ // Keep with merge or loop node
+ begin = n.inputs.length - 1;
+ } else if (n.hasBackEdges()) {
+ end = 1;
+ }
+ for (let l = begin; l < end; ++l) {
+ const input = n.inputs[l].source;
+ if (input.visible && input.rank >= n.rank) {
+ n.rank = input.rank + 1;
+ changed = true;
+ }
+ }
+ if (changed) {
+ const hasBackEdges = n.hasBackEdges();
+ for (let l = n.outputs.length - 1; l >= 0; --l) {
+ if (hasBackEdges && (l != 0)) {
+ worklist.unshift(n.outputs[l].target);
+ } else {
+ worklist.push(n.outputs[l].target);
+ }
+ }
+ }
+ if (n.rank > maxRank) {
+ maxRank = n.rank;
+ }
+ }
+
+ if (traceLayout) {
+ console.log(`layoutGraph worklist ${performance.now() - start}`);
+ }
+
+ visited = [];
+ function dfsFindRankLate(n: GNode) {
+ if (visited[n.id]) return;
+ visited[n.id] = true;
+ const originalRank = n.rank;
+ let newRank = n.rank;
+ let isFirstInput = true;
+ for (const outputEdge of n.outputs) {
+ const output = outputEdge.target;
+ dfsFindRankLate(output);
+ const outputRank = output.rank;
+ if (output.visible && (isFirstInput || outputRank <= newRank) &&
+ (outputRank > originalRank)) {
+ newRank = outputRank - 1;
+ }
+ isFirstInput = false;
+ }
+ if (n.nodeLabel.opcode != "Start" && n.nodeLabel.opcode != "Phi" && n.nodeLabel.opcode != "EffectPhi" && n.nodeLabel.opcode != "InductionVariablePhi") {
+ n.rank = newRank;
+ }
+ }
+
+ startNodes.forEach(dfsFindRankLate);
+
+ visited = [];
+ function dfsRankOrder(n: GNode) {
+ if (visited[n.id]) return;
+ visited[n.id] = true;
+ for (const outputEdge of n.outputs) {
+ if (outputEdge.isVisible()) {
+ const output = outputEdge.target;
+ dfsRankOrder(output);
+ }
+ }
+ if (n.visitOrderWithinRank == 0) {
+ n.visitOrderWithinRank = ++visitOrderWithinRank;
+ }
+ }
+ startNodes.forEach(dfsRankOrder);
+
+ endNodes.forEach(function (n) {
+ n.rank = maxRank + 1;
+ });
+
+ const rankSets: Array<Array<GNode>> = [];
+ // Collect sets for each rank.
+ for (const n of graph.nodes()) {
+ n.y = n.rank * (DEFAULT_NODE_ROW_SEPARATION + n.getNodeHeight(showTypes) +
+ 2 * DEFAULT_NODE_BUBBLE_RADIUS);
+ if (n.visible) {
+ if (rankSets[n.rank] === undefined) {
+ rankSets[n.rank] = [n];
+ } else {
+ rankSets[n.rank].push(n);
+ }
+ }
+ }
+
+ // Iterate backwards from highest to lowest rank, placing nodes so that they
+ // spread out from the "center" as much as possible while still being
+ // compact and not overlapping live input lines.
+ const occupation = newGraphOccupation(graph);
+
+ rankSets.reverse().forEach(function (rankSet: Array<GNode>) {
+
+ for (const node of rankSet) {
+ occupation.clearNodeOutputs(node, showTypes);
+ }
+
+ if (traceLayout) {
+ console.log("After clearing outputs");
+ occupation.print();
+ }
+
+ let placedCount = 0;
+ rankSet = rankSet.sort((a: GNode, b: GNode) => {
+ if (a.visitOrderWithinRank < b.visitOrderWithinRank) {
+ return -1;
+ } else if (a.visitOrderWithinRank == b.visitOrderWithinRank) {
+ return 0;
+ } else {
+ return 1;
+ }
+ });
+
+ for (const nodeToPlace of rankSet) {
+ if (nodeToPlace.visible) {
+ nodeToPlace.x = occupation.occupyNode(nodeToPlace);
+ if (traceLayout) {
+ console.log("Node " + nodeToPlace.id + " is placed between [" + nodeToPlace.x + ", " + (nodeToPlace.x + nodeToPlace.getTotalNodeWidth()) + ")");
+ }
+ const staggeredFlooredI = Math.floor(placedCount++ % 3);
+ const delta = MINIMUM_EDGE_SEPARATION * staggeredFlooredI;
+ nodeToPlace.outputApproach += delta;
+ } else {
+ nodeToPlace.x = 0;
+ }
+ }
+
+ if (traceLayout) {
+ console.log("Before clearing nodes");
+ occupation.print();
+ }
+
+ occupation.clearOccupiedNodes();
+
+ if (traceLayout) {
+ console.log("After clearing nodes");
+ occupation.print();
+ }
+
+ for (const node of rankSet) {
+ occupation.occupyNodeInputs(node, showTypes);
+ }
+
+ if (traceLayout) {
+ console.log("After occupying inputs");
+ occupation.print();
+ }
+
+ if (traceLayout) {
+ console.log("After determining bounding box");
+ occupation.print();
+ }
+ });
+
+ graph.maxBackEdgeNumber = 0;
+ graph.forEachEdge((e: Edge) => {
+ if (e.isBackEdge()) {
+ e.backEdgeNumber = ++graph.maxBackEdgeNumber;
+ } else {
+ e.backEdgeNumber = 0;
+ }
+ });
+}
diff --git a/src/v8/tools/turbolizer/src/graph-view.ts b/src/v8/tools/turbolizer/src/graph-view.ts
new file mode 100644
index 0000000..07e0d7f
--- /dev/null
+++ b/src/v8/tools/turbolizer/src/graph-view.ts
@@ -0,0 +1,931 @@
+// Copyright 2015 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import * as d3 from "d3";
+import { layoutNodeGraph } from "../src/graph-layout";
+import { GNode, nodeToStr } from "../src/node";
+import { NODE_INPUT_WIDTH } from "../src/node";
+import { DEFAULT_NODE_BUBBLE_RADIUS } from "../src/node";
+import { Edge, edgeToStr } from "../src/edge";
+import { PhaseView } from "../src/view";
+import { MySelection } from "../src/selection";
+import { partial } from "../src/util";
+import { NodeSelectionHandler, ClearableHandler } from "./selection-handler";
+import { Graph } from "./graph";
+import { SelectionBroker } from "./selection-broker";
+
+function nodeToStringKey(n: GNode) {
+ return "" + n.id;
+}
+
+interface GraphState {
+ showTypes: boolean;
+ selection: MySelection;
+ mouseDownNode: any;
+ justDragged: boolean;
+ justScaleTransGraph: boolean;
+ hideDead: boolean;
+}
+
+export class GraphView extends PhaseView {
+ divElement: d3.Selection<any, any, any, any>;
+ svg: d3.Selection<any, any, any, any>;
+ showPhaseByName: (p: string, s: Set<any>) => void;
+ state: GraphState;
+ selectionHandler: NodeSelectionHandler & ClearableHandler;
+ graphElement: d3.Selection<any, any, any, any>;
+ visibleNodes: d3.Selection<any, GNode, any, any>;
+ visibleEdges: d3.Selection<any, Edge, any, any>;
+ drag: d3.DragBehavior<any, GNode, GNode>;
+ panZoom: d3.ZoomBehavior<SVGElement, any>;
+ visibleBubbles: d3.Selection<any, any, any, any>;
+ transitionTimout: number;
+ graph: Graph;
+ broker: SelectionBroker;
+ phaseName: string;
+ toolbox: HTMLElement;
+
+ createViewElement() {
+ const pane = document.createElement('div');
+ pane.setAttribute('id', "graph");
+ return pane;
+ }
+
+ constructor(idOrContainer: string | HTMLElement, broker: SelectionBroker,
+ showPhaseByName: (s: string) => void, toolbox: HTMLElement) {
+ super(idOrContainer);
+ const view = this;
+ this.broker = broker;
+ this.showPhaseByName = showPhaseByName;
+ this.divElement = d3.select(this.divNode);
+ this.phaseName = "";
+ this.toolbox = toolbox;
+ const svg = this.divElement.append("svg")
+ .attr('version', '2.0')
+ .attr("width", "100%")
+ .attr("height", "100%");
+ svg.on("click", function (d) {
+ view.selectionHandler.clear();
+ });
+ // Listen for key events. Note that the focus handler seems
+ // to be important even if it does nothing.
+ svg
+ .attr("focusable", false)
+ .on("focus", e => { })
+ .on("keydown", e => { view.svgKeyDown(); });
+
+ view.svg = svg;
+
+ this.state = {
+ selection: null,
+ mouseDownNode: null,
+ justDragged: false,
+ justScaleTransGraph: false,
+ showTypes: false,
+ hideDead: false
+ };
+
+ this.selectionHandler = {
+ clear: function () {
+ view.state.selection.clear();
+ broker.broadcastClear(this);
+ view.updateGraphVisibility();
+ },
+ select: function (nodes: Array<GNode>, selected: boolean) {
+ const locations = [];
+ for (const node of nodes) {
+ if (node.nodeLabel.sourcePosition) {
+ locations.push(node.nodeLabel.sourcePosition);
+ }
+ if (node.nodeLabel.origin && node.nodeLabel.origin.bytecodePosition) {
+ locations.push({ bytecodePosition: node.nodeLabel.origin.bytecodePosition });
+ }
+ }
+ view.state.selection.select(nodes, selected);
+ broker.broadcastSourcePositionSelect(this, locations, selected);
+ view.updateGraphVisibility();
+ },
+ brokeredNodeSelect: function (locations, selected: boolean) {
+ if (!view.graph) return;
+ const selection = view.graph.nodes(n => {
+ return locations.has(nodeToStringKey(n))
+ && (!view.state.hideDead || n.isLive());
+ });
+ view.state.selection.select(selection, selected);
+ // Update edge visibility based on selection.
+ for (const n of view.graph.nodes()) {
+ if (view.state.selection.isSelected(n)) {
+ n.visible = true;
+ n.inputs.forEach(e => {
+ e.visible = e.visible || view.state.selection.isSelected(e.source);
+ });
+ n.outputs.forEach(e => {
+ e.visible = e.visible || view.state.selection.isSelected(e.target);
+ });
+ }
+ }
+ view.updateGraphVisibility();
+ },
+ brokeredClear: function () {
+ view.state.selection.clear();
+ view.updateGraphVisibility();
+ }
+ };
+
+ view.state.selection = new MySelection(nodeToStringKey);
+
+ const defs = svg.append('svg:defs');
+ defs.append('svg:marker')
+ .attr('id', 'end-arrow')
+ .attr('viewBox', '0 -4 8 8')
+ .attr('refX', 2)
+ .attr('markerWidth', 2.5)
+ .attr('markerHeight', 2.5)
+ .attr('orient', 'auto')
+ .append('svg:path')
+ .attr('d', 'M0,-4L8,0L0,4');
+
+ this.graphElement = svg.append("g");
+ view.visibleEdges = this.graphElement.append("g");
+ view.visibleNodes = this.graphElement.append("g");
+
+ view.drag = d3.drag<any, GNode, GNode>()
+ .on("drag", function (d) {
+ d.x += d3.event.dx;
+ d.y += d3.event.dy;
+ view.updateGraphVisibility();
+ });
+
+ function zoomed() {
+ if (d3.event.shiftKey) return false;
+ view.graphElement.attr("transform", d3.event.transform);
+ return true;
+ }
+
+ const zoomSvg = d3.zoom<SVGElement, any>()
+ .scaleExtent([0.2, 40])
+ .on("zoom", zoomed)
+ .on("start", function () {
+ if (d3.event.shiftKey) return;
+ d3.select('body').style("cursor", "move");
+ })
+ .on("end", function () {
+ d3.select('body').style("cursor", "auto");
+ });
+
+ svg.call(zoomSvg).on("dblclick.zoom", null);
+
+ view.panZoom = zoomSvg;
+
+ }
+
+ getEdgeFrontier(nodes: Iterable<GNode>, inEdges: boolean,
+ edgeFilter: (e: Edge, i: number) => boolean) {
+ const frontier: Set<Edge> = new Set();
+ for (const n of nodes) {
+ const edges = inEdges ? n.inputs : n.outputs;
+ let edgeNumber = 0;
+ edges.forEach((edge: Edge) => {
+ if (edgeFilter == undefined || edgeFilter(edge, edgeNumber)) {
+ frontier.add(edge);
+ }
+ ++edgeNumber;
+ });
+ }
+ return frontier;
+ }
+
+ getNodeFrontier(nodes: Iterable<GNode>, inEdges: boolean,
+ edgeFilter: (e: Edge, i: number) => boolean) {
+ const view = this;
+ const frontier: Set<GNode> = new Set();
+ let newState = true;
+ const edgeFrontier = view.getEdgeFrontier(nodes, inEdges, edgeFilter);
+ // Control key toggles edges rather than just turning them on
+ if (d3.event.ctrlKey) {
+ edgeFrontier.forEach(function (edge: Edge) {
+ if (edge.visible) {
+ newState = false;
+ }
+ });
+ }
+ edgeFrontier.forEach(function (edge: Edge) {
+ edge.visible = newState;
+ if (newState) {
+ const node = inEdges ? edge.source : edge.target;
+ node.visible = true;
+ frontier.add(node);
+ }
+ });
+ view.updateGraphVisibility();
+ if (newState) {
+ return frontier;
+ } else {
+ return undefined;
+ }
+ }
+
+ initializeContent(data, rememberedSelection) {
+ this.show();
+ function createImgInput(id: string, title: string, onClick): HTMLElement {
+ const input = document.createElement("input");
+ input.setAttribute("id", id);
+ input.setAttribute("type", "image");
+ input.setAttribute("title", title);
+ input.setAttribute("src", `img/${id}-icon.png`);
+ input.className = "button-input graph-toolbox-item";
+ input.addEventListener("click", onClick);
+ return input;
+ }
+ this.toolbox.appendChild(createImgInput("layout", "layout graph",
+ partial(this.layoutAction, this)));
+ this.toolbox.appendChild(createImgInput("show-all", "show all nodes",
+ partial(this.showAllAction, this)));
+ this.toolbox.appendChild(createImgInput("show-control", "show only control nodes",
+ partial(this.showControlAction, this)));
+ this.toolbox.appendChild(createImgInput("toggle-hide-dead", "toggle hide dead nodes",
+ partial(this.toggleHideDead, this)));
+ this.toolbox.appendChild(createImgInput("hide-unselected", "hide unselected",
+ partial(this.hideUnselectedAction, this)));
+ this.toolbox.appendChild(createImgInput("hide-selected", "hide selected",
+ partial(this.hideSelectedAction, this)));
+ this.toolbox.appendChild(createImgInput("zoom-selection", "zoom selection",
+ partial(this.zoomSelectionAction, this)));
+ this.toolbox.appendChild(createImgInput("toggle-types", "toggle types",
+ partial(this.toggleTypesAction, this)));
+
+ this.phaseName = data.name;
+ this.createGraph(data.data, rememberedSelection);
+ this.broker.addNodeHandler(this.selectionHandler);
+
+ if (rememberedSelection != null && rememberedSelection.size > 0) {
+ this.attachSelection(rememberedSelection);
+ this.connectVisibleSelectedNodes();
+ this.viewSelection();
+ } else {
+ this.viewWholeGraph();
+ }
+ }
+
+ deleteContent() {
+ for (const item of this.toolbox.querySelectorAll(".graph-toolbox-item")) {
+ item.parentElement.removeChild(item);
+ }
+
+ for (const n of this.graph.nodes()) {
+ n.visible = false;
+ }
+ this.graph.forEachEdge((e: Edge) => {
+ e.visible = false;
+ });
+ this.updateGraphVisibility();
+ }
+
+ public hide(): void {
+ super.hide();
+ this.deleteContent();
+ }
+
+ createGraph(data, rememberedSelection) {
+ this.graph = new Graph(data);
+
+ this.showControlAction(this);
+
+ if (rememberedSelection != undefined) {
+ for (const n of this.graph.nodes()) {
+ n.visible = n.visible || rememberedSelection.has(nodeToStringKey(n));
+ }
+ }
+
+ this.graph.forEachEdge(e => e.visible = e.source.visible && e.target.visible);
+
+ this.layoutGraph();
+ this.updateGraphVisibility();
+ }
+
+ connectVisibleSelectedNodes() {
+ const view = this;
+ for (const n of view.state.selection) {
+ n.inputs.forEach(function (edge: Edge) {
+ if (edge.source.visible && edge.target.visible) {
+ edge.visible = true;
+ }
+ });
+ n.outputs.forEach(function (edge: Edge) {
+ if (edge.source.visible && edge.target.visible) {
+ edge.visible = true;
+ }
+ });
+ }
+ }
+
+ updateInputAndOutputBubbles() {
+ const view = this;
+ const g = this.graph;
+ const s = this.visibleBubbles;
+ s.classed("filledBubbleStyle", function (c) {
+ const components = this.id.split(',');
+ if (components[0] == "ib") {
+ const edge = g.nodeMap[components[3]].inputs[components[2]];
+ return edge.isVisible();
+ } else {
+ return g.nodeMap[components[1]].areAnyOutputsVisible() == 2;
+ }
+ }).classed("halfFilledBubbleStyle", function (c) {
+ const components = this.id.split(',');
+ if (components[0] == "ib") {
+ return false;
+ } else {
+ return g.nodeMap[components[1]].areAnyOutputsVisible() == 1;
+ }
+ }).classed("bubbleStyle", function (c) {
+ const components = this.id.split(',');
+ if (components[0] == "ib") {
+ const edge = g.nodeMap[components[3]].inputs[components[2]];
+ return !edge.isVisible();
+ } else {
+ return g.nodeMap[components[1]].areAnyOutputsVisible() == 0;
+ }
+ });
+ s.each(function (c) {
+ const components = this.id.split(',');
+ if (components[0] == "ob") {
+ const from = g.nodeMap[components[1]];
+ const x = from.getOutputX();
+ const y = from.getNodeHeight(view.state.showTypes) + DEFAULT_NODE_BUBBLE_RADIUS;
+ const transform = "translate(" + x + "," + y + ")";
+ this.setAttribute('transform', transform);
+ }
+ });
+ }
+
+ attachSelection(s) {
+ if (!(s instanceof Set)) return;
+ this.selectionHandler.clear();
+ const selected = [...this.graph.nodes(n =>
+ s.has(this.state.selection.stringKey(n)) && (!this.state.hideDead || n.isLive()))];
+ this.selectionHandler.select(selected, true);
+ }
+
+ detachSelection() {
+ return this.state.selection.detachSelection();
+ }
+
+ selectAllNodes() {
+ if (!d3.event.shiftKey) {
+ this.state.selection.clear();
+ }
+ const allVisibleNodes = [...this.graph.nodes(n => n.visible)];
+ this.state.selection.select(allVisibleNodes, true);
+ this.updateGraphVisibility();
+ }
+
+ layoutAction(graph: GraphView) {
+ graph.layoutGraph();
+ graph.updateGraphVisibility();
+ graph.viewWholeGraph();
+ }
+
+ showAllAction(view: GraphView) {
+ for (const n of view.graph.nodes()) {
+ n.visible = !view.state.hideDead || n.isLive();
+ }
+ view.graph.forEachEdge((e: Edge) => {
+ e.visible = e.source.visible || e.target.visible;
+ });
+ view.updateGraphVisibility();
+ view.viewWholeGraph();
+ }
+
+ showControlAction(view: GraphView) {
+ for (const n of view.graph.nodes()) {
+ n.visible = n.cfg && (!view.state.hideDead || n.isLive());
+ }
+ view.graph.forEachEdge((e: Edge) => {
+ e.visible = e.type == 'control' && e.source.visible && e.target.visible;
+ });
+ view.updateGraphVisibility();
+ view.viewWholeGraph();
+ }
+
+ toggleHideDead(view: GraphView) {
+ view.state.hideDead = !view.state.hideDead;
+ if (view.state.hideDead) {
+ view.hideDead();
+ } else {
+ view.showDead();
+ }
+ const element = document.getElementById('toggle-hide-dead');
+ element.classList.toggle('button-input-toggled', view.state.hideDead);
+ }
+
+ hideDead() {
+ for (const n of this.graph.nodes()) {
+ if (!n.isLive()) {
+ n.visible = false;
+ this.state.selection.select([n], false);
+ }
+ }
+ this.updateGraphVisibility();
+ }
+
+ showDead() {
+ for (const n of this.graph.nodes()) {
+ if (!n.isLive()) {
+ n.visible = true;
+ }
+ }
+ this.updateGraphVisibility();
+ }
+
+ hideUnselectedAction(view: GraphView) {
+ for (const n of view.graph.nodes()) {
+ if (!view.state.selection.isSelected(n)) {
+ n.visible = false;
+ }
+ }
+ view.updateGraphVisibility();
+ }
+
+ hideSelectedAction(view: GraphView) {
+ for (const n of view.graph.nodes()) {
+ if (view.state.selection.isSelected(n)) {
+ n.visible = false;
+ }
+ }
+ view.selectionHandler.clear();
+ }
+
+ zoomSelectionAction(view: GraphView) {
+ view.viewSelection();
+ }
+
+ toggleTypesAction(view: GraphView) {
+ view.toggleTypes();
+ }
+
+ searchInputAction(searchBar: HTMLInputElement, e: KeyboardEvent, onlyVisible: boolean) {
+ if (e.keyCode == 13) {
+ this.selectionHandler.clear();
+ const query = searchBar.value;
+ window.sessionStorage.setItem("lastSearch", query);
+ if (query.length == 0) return;
+
+ const reg = new RegExp(query);
+ const filterFunction = (n: GNode) => {
+ return (reg.exec(n.getDisplayLabel()) != null ||
+ (this.state.showTypes && reg.exec(n.getDisplayType())) ||
+ (reg.exec(n.getTitle())) ||
+ reg.exec(n.nodeLabel.opcode) != null);
+ };
+
+ const selection = [...this.graph.nodes(n => {
+ if ((e.ctrlKey || n.visible || !onlyVisible) && filterFunction(n)) {
+ if (e.ctrlKey || !onlyVisible) n.visible = true;
+ return true;
+ }
+ return false;
+ })];
+
+ this.selectionHandler.select(selection, true);
+ this.connectVisibleSelectedNodes();
+ this.updateGraphVisibility();
+ searchBar.blur();
+ this.viewSelection();
+ }
+ e.stopPropagation();
+ }
+
+ svgKeyDown() {
+ const view = this;
+ const state = this.state;
+
+ const showSelectionFrontierNodes = (inEdges: boolean, filter: (e: Edge, i: number) => boolean, doSelect: boolean) => {
+ const frontier = view.getNodeFrontier(state.selection, inEdges, filter);
+ if (frontier != undefined && frontier.size) {
+ if (doSelect) {
+ if (!d3.event.shiftKey) {
+ state.selection.clear();
+ }
+ state.selection.select([...frontier], true);
+ }
+ view.updateGraphVisibility();
+ }
+ };
+
+ let eventHandled = true; // unless the below switch defaults
+ switch (d3.event.keyCode) {
+ case 49:
+ case 50:
+ case 51:
+ case 52:
+ case 53:
+ case 54:
+ case 55:
+ case 56:
+ case 57:
+ // '1'-'9'
+ showSelectionFrontierNodes(true,
+ (edge: Edge, index: number) => index == (d3.event.keyCode - 49),
+ !d3.event.ctrlKey);
+ break;
+ case 97:
+ case 98:
+ case 99:
+ case 100:
+ case 101:
+ case 102:
+ case 103:
+ case 104:
+ case 105:
+ // 'numpad 1'-'numpad 9'
+ showSelectionFrontierNodes(true,
+ (edge, index) => index == (d3.event.keyCode - 97),
+ !d3.event.ctrlKey);
+ break;
+ case 67:
+ // 'c'
+ showSelectionFrontierNodes(d3.event.altKey,
+ (edge, index) => edge.type == 'control',
+ true);
+ break;
+ case 69:
+ // 'e'
+ showSelectionFrontierNodes(d3.event.altKey,
+ (edge, index) => edge.type == 'effect',
+ true);
+ break;
+ case 79:
+ // 'o'
+ showSelectionFrontierNodes(false, undefined, false);
+ break;
+ case 73:
+ // 'i'
+ if (!d3.event.ctrlKey && !d3.event.shiftKey) {
+ showSelectionFrontierNodes(true, undefined, false);
+ } else {
+ eventHandled = false;
+ }
+ break;
+ case 65:
+ // 'a'
+ view.selectAllNodes();
+ break;
+ case 38:
+ // UP
+ case 40: {
+ // DOWN
+ showSelectionFrontierNodes(d3.event.keyCode == 38, undefined, true);
+ break;
+ }
+ case 82:
+ // 'r'
+ if (!d3.event.ctrlKey && !d3.event.shiftKey) {
+ this.layoutAction(this);
+ } else {
+ eventHandled = false;
+ }
+ break;
+ case 83:
+ // 's'
+ view.selectOrigins();
+ break;
+ default:
+ eventHandled = false;
+ break;
+ }
+ if (eventHandled) {
+ d3.event.preventDefault();
+ }
+ }
+
+ layoutGraph() {
+ console.time("layoutGraph");
+ layoutNodeGraph(this.graph, this.state.showTypes);
+ const extent = this.graph.redetermineGraphBoundingBox(this.state.showTypes);
+ this.panZoom.translateExtent(extent);
+ this.minScale();
+ console.timeEnd("layoutGraph");
+ }
+
+ selectOrigins() {
+ const state = this.state;
+ const origins = [];
+ let phase = this.phaseName;
+ const selection = new Set<any>();
+ for (const n of state.selection) {
+ const origin = n.nodeLabel.origin;
+ if (origin) {
+ phase = origin.phase;
+ const node = this.graph.nodeMap[origin.nodeId];
+ if (phase === this.phaseName && node) {
+ origins.push(node);
+ } else {
+ selection.add(`${origin.nodeId}`);
+ }
+ }
+ }
+ // Only go through phase reselection if we actually need
+ // to display another phase.
+ if (selection.size > 0 && phase !== this.phaseName) {
+ this.showPhaseByName(phase, selection);
+ } else if (origins.length > 0) {
+ this.selectionHandler.clear();
+ this.selectionHandler.select(origins, true);
+ }
+ }
+
+ // call to propagate changes to graph
+ updateGraphVisibility() {
+ const view = this;
+ const graph = this.graph;
+ const state = this.state;
+ if (!graph) return;
+
+ const filteredEdges = [...graph.filteredEdges(function (e) {
+ return e.source.visible && e.target.visible;
+ })];
+ const selEdges = view.visibleEdges.selectAll<SVGPathElement, Edge>("path").data(filteredEdges, edgeToStr);
+
+ // remove old links
+ selEdges.exit().remove();
+
+ // add new paths
+ const newEdges = selEdges.enter()
+ .append('path');
+
+ newEdges.style('marker-end', 'url(#end-arrow)')
+ .attr("id", function (edge) { return "e," + edge.stringID(); })
+ .on("click", function (edge) {
+ d3.event.stopPropagation();
+ if (!d3.event.shiftKey) {
+ view.selectionHandler.clear();
+ }
+ view.selectionHandler.select([edge.source, edge.target], true);
+ })
+ .attr("adjacentToHover", "false")
+ .classed('value', function (e) {
+ return e.type == 'value' || e.type == 'context';
+ }).classed('control', function (e) {
+ return e.type == 'control';
+ }).classed('effect', function (e) {
+ return e.type == 'effect';
+ }).classed('frame-state', function (e) {
+ return e.type == 'frame-state';
+ }).attr('stroke-dasharray', function (e) {
+ if (e.type == 'frame-state') return "10,10";
+ return (e.type == 'effect') ? "5,5" : "";
+ });
+
+ const newAndOldEdges = newEdges.merge(selEdges);
+
+ newAndOldEdges.classed('hidden', e => !e.isVisible());
+
+ // select existing nodes
+ const filteredNodes = [...graph.nodes(n => n.visible)];
+ const allNodes = view.visibleNodes.selectAll<SVGGElement, GNode>("g");
+ const selNodes = allNodes.data(filteredNodes, nodeToStr);
+
+ // remove old nodes
+ selNodes.exit().remove();
+
+ // add new nodes
+ const newGs = selNodes.enter()
+ .append("g");
+
+ newGs.classed("turbonode", function (n) { return true; })
+ .classed("control", function (n) { return n.isControl(); })
+ .classed("live", function (n) { return n.isLive(); })
+ .classed("dead", function (n) { return !n.isLive(); })
+ .classed("javascript", function (n) { return n.isJavaScript(); })
+ .classed("input", function (n) { return n.isInput(); })
+ .classed("simplified", function (n) { return n.isSimplified(); })
+ .classed("machine", function (n) { return n.isMachine(); })
+ .on('mouseenter', function (node) {
+ const visibleEdges = view.visibleEdges.selectAll<SVGPathElement, Edge>('path');
+ const adjInputEdges = visibleEdges.filter(e => e.target === node);
+ const adjOutputEdges = visibleEdges.filter(e => e.source === node);
+ adjInputEdges.attr('relToHover', "input");
+ adjOutputEdges.attr('relToHover', "output");
+ const adjInputNodes = adjInputEdges.data().map(e => e.source);
+ const visibleNodes = view.visibleNodes.selectAll<SVGGElement, GNode>("g");
+ visibleNodes.data<GNode>(adjInputNodes, nodeToStr).attr('relToHover', "input");
+ const adjOutputNodes = adjOutputEdges.data().map(e => e.target);
+ visibleNodes.data<GNode>(adjOutputNodes, nodeToStr).attr('relToHover', "output");
+ view.updateGraphVisibility();
+ })
+ .on('mouseleave', function (node) {
+ const visibleEdges = view.visibleEdges.selectAll<SVGPathElement, Edge>('path');
+ const adjEdges = visibleEdges.filter(e => e.target === node || e.source === node);
+ adjEdges.attr('relToHover', "none");
+ const adjNodes = adjEdges.data().map(e => e.target).concat(adjEdges.data().map(e => e.source));
+ const visibleNodes = view.visibleNodes.selectAll<SVGPathElement, GNode>("g");
+ visibleNodes.data(adjNodes, nodeToStr).attr('relToHover', "none");
+ view.updateGraphVisibility();
+ })
+ .on("click", d => {
+ if (!d3.event.shiftKey) view.selectionHandler.clear();
+ view.selectionHandler.select([d], undefined);
+ d3.event.stopPropagation();
+ })
+ .call(view.drag);
+
+ newGs.append("rect")
+ .attr("rx", 10)
+ .attr("ry", 10)
+ .attr('width', function (d) {
+ return d.getTotalNodeWidth();
+ })
+ .attr('height', function (d) {
+ return d.getNodeHeight(view.state.showTypes);
+ });
+
+ function appendInputAndOutputBubbles(g, d) {
+ for (let i = 0; i < d.inputs.length; ++i) {
+ const x = d.getInputX(i);
+ const y = -DEFAULT_NODE_BUBBLE_RADIUS;
+ g.append('circle')
+ .classed("filledBubbleStyle", function (c) {
+ return d.inputs[i].isVisible();
+ })
+ .classed("bubbleStyle", function (c) {
+ return !d.inputs[i].isVisible();
+ })
+ .attr("id", "ib," + d.inputs[i].stringID())
+ .attr("r", DEFAULT_NODE_BUBBLE_RADIUS)
+ .attr("transform", function (d) {
+ return "translate(" + x + "," + y + ")";
+ })
+ .on("click", function (this: SVGCircleElement, d) {
+ const components = this.id.split(',');
+ const node = graph.nodeMap[components[3]];
+ const edge = node.inputs[components[2]];
+ const visible = !edge.isVisible();
+ node.setInputVisibility(components[2], visible);
+ d3.event.stopPropagation();
+ view.updateGraphVisibility();
+ });
+ }
+ if (d.outputs.length != 0) {
+ const x = d.getOutputX();
+ const y = d.getNodeHeight(view.state.showTypes) + DEFAULT_NODE_BUBBLE_RADIUS;
+ g.append('circle')
+ .classed("filledBubbleStyle", function (c) {
+ return d.areAnyOutputsVisible() == 2;
+ })
+ .classed("halFilledBubbleStyle", function (c) {
+ return d.areAnyOutputsVisible() == 1;
+ })
+ .classed("bubbleStyle", function (c) {
+ return d.areAnyOutputsVisible() == 0;
+ })
+ .attr("id", "ob," + d.id)
+ .attr("r", DEFAULT_NODE_BUBBLE_RADIUS)
+ .attr("transform", function (d) {
+ return "translate(" + x + "," + y + ")";
+ })
+ .on("click", function (d) {
+ d.setOutputVisibility(d.areAnyOutputsVisible() == 0);
+ d3.event.stopPropagation();
+ view.updateGraphVisibility();
+ });
+ }
+ }
+
+ newGs.each(function (d) {
+ appendInputAndOutputBubbles(d3.select(this), d);
+ });
+
+ newGs.each(function (d) {
+ d3.select(this).append("text")
+ .classed("label", true)
+ .attr("text-anchor", "right")
+ .attr("dx", 5)
+ .attr("dy", 5)
+ .append('tspan')
+ .text(function (l) {
+ return d.getDisplayLabel();
+ })
+ .append("title")
+ .text(function (l) {
+ return d.getTitle();
+ });
+ if (d.nodeLabel.type != undefined) {
+ d3.select(this).append("text")
+ .classed("label", true)
+ .classed("type", true)
+ .attr("text-anchor", "right")
+ .attr("dx", 5)
+ .attr("dy", d.labelbbox.height + 5)
+ .append('tspan')
+ .text(function (l) {
+ return d.getDisplayType();
+ })
+ .append("title")
+ .text(function (l) {
+ return d.getType();
+ });
+ }
+ });
+
+ const newAndOldNodes = newGs.merge(selNodes);
+
+ newAndOldNodes.select<SVGTextElement>('.type').each(function (d) {
+ this.setAttribute('visibility', view.state.showTypes ? 'visible' : 'hidden');
+ });
+
+ newAndOldNodes
+ .classed("selected", function (n) {
+ if (state.selection.isSelected(n)) return true;
+ return false;
+ })
+ .attr("transform", function (d) { return "translate(" + d.x + "," + d.y + ")"; })
+ .select('rect')
+ .attr('height', function (d) { return d.getNodeHeight(view.state.showTypes); });
+
+ view.visibleBubbles = d3.selectAll('circle');
+
+ view.updateInputAndOutputBubbles();
+
+ graph.maxGraphX = graph.maxGraphNodeX;
+ newAndOldEdges.attr("d", function (edge) {
+ return edge.generatePath(graph, view.state.showTypes);
+ });
+ }
+
+ getSvgViewDimensions() {
+ return [this.container.clientWidth, this.container.clientHeight];
+ }
+
+ getSvgExtent(): [[number, number], [number, number]] {
+ return [[0, 0], [this.container.clientWidth, this.container.clientHeight]];
+ }
+
+ minScale() {
+ const dimensions = this.getSvgViewDimensions();
+ const minXScale = dimensions[0] / (2 * this.graph.width);
+ const minYScale = dimensions[1] / (2 * this.graph.height);
+ const minScale = Math.min(minXScale, minYScale);
+ this.panZoom.scaleExtent([minScale, 40]);
+ return minScale;
+ }
+
+ onresize() {
+ const trans = d3.zoomTransform(this.svg.node());
+ const ctrans = this.panZoom.constrain()(trans, this.getSvgExtent(), this.panZoom.translateExtent());
+ this.panZoom.transform(this.svg, ctrans);
+ }
+
+ toggleTypes() {
+ const view = this;
+ view.state.showTypes = !view.state.showTypes;
+ const element = document.getElementById('toggle-types');
+ element.classList.toggle('button-input-toggled', view.state.showTypes);
+ view.updateGraphVisibility();
+ }
+
+ viewSelection() {
+ const view = this;
+ let minX;
+ let maxX;
+ let minY;
+ let maxY;
+ let hasSelection = false;
+ view.visibleNodes.selectAll<SVGGElement, GNode>("g").each(function (n) {
+ if (view.state.selection.isSelected(n)) {
+ hasSelection = true;
+ minX = minX ? Math.min(minX, n.x) : n.x;
+ maxX = maxX ? Math.max(maxX, n.x + n.getTotalNodeWidth()) :
+ n.x + n.getTotalNodeWidth();
+ minY = minY ? Math.min(minY, n.y) : n.y;
+ maxY = maxY ? Math.max(maxY, n.y + n.getNodeHeight(view.state.showTypes)) :
+ n.y + n.getNodeHeight(view.state.showTypes);
+ }
+ });
+ if (hasSelection) {
+ view.viewGraphRegion(minX - NODE_INPUT_WIDTH, minY - 60,
+ maxX + NODE_INPUT_WIDTH, maxY + 60);
+ }
+ }
+
+ viewGraphRegion(minX, minY, maxX, maxY) {
+ const [width, height] = this.getSvgViewDimensions();
+ const dx = maxX - minX;
+ const dy = maxY - minY;
+ const x = (minX + maxX) / 2;
+ const y = (minY + maxY) / 2;
+ const scale = Math.min(width / (1.1 * dx), height / (1.1 * dy));
+ this.svg
+ .transition().duration(300).call(this.panZoom.translateTo, x, y)
+ .transition().duration(300).call(this.panZoom.scaleTo, scale)
+ .transition().duration(300).call(this.panZoom.translateTo, x, y);
+ }
+
+ viewWholeGraph() {
+ this.panZoom.scaleTo(this.svg, 0);
+ this.panZoom.translateTo(this.svg,
+ this.graph.minGraphX + this.graph.width / 2,
+ this.graph.minGraphY + this.graph.height / 2);
+ }
+}
diff --git a/src/v8/tools/turbolizer/src/graph.ts b/src/v8/tools/turbolizer/src/graph.ts
new file mode 100644
index 0000000..0eb2e3e
--- /dev/null
+++ b/src/v8/tools/turbolizer/src/graph.ts
@@ -0,0 +1,107 @@
+import { GNode } from "./node";
+import { Edge, MINIMUM_EDGE_SEPARATION } from "./edge";
+
+export class Graph {
+ nodeMap: Array<GNode>;
+ minGraphX: number;
+ maxGraphX: number;
+ minGraphY: number;
+ maxGraphY: number;
+ maxGraphNodeX: number;
+ maxBackEdgeNumber: number;
+ width: number;
+ height: number;
+
+ constructor(data: any) {
+ this.nodeMap = [];
+
+ this.minGraphX = 0;
+ this.maxGraphX = 1;
+ this.minGraphY = 0;
+ this.maxGraphY = 1;
+ this.width = 1;
+ this.height = 1;
+
+ data.nodes.forEach((jsonNode: any) => {
+ this.nodeMap[jsonNode.id] = new GNode(jsonNode.nodeLabel);
+ });
+
+ data.edges.forEach((e: any) => {
+ const t = this.nodeMap[e.target];
+ const s = this.nodeMap[e.source];
+ const newEdge = new Edge(t, e.index, s, e.type);
+ t.inputs.push(newEdge);
+ s.outputs.push(newEdge);
+ if (e.type == 'control') {
+ // Every source of a control edge is a CFG node.
+ s.cfg = true;
+ }
+ });
+
+ }
+
+ *nodes(p = (n: GNode) => true) {
+ for (const node of this.nodeMap) {
+ if (!node || !p(node)) continue;
+ yield node;
+ }
+ }
+
+ *filteredEdges(p: (e: Edge) => boolean) {
+ for (const node of this.nodes()) {
+ for (const edge of node.inputs) {
+ if (p(edge)) yield edge;
+ }
+ }
+ }
+
+ forEachEdge(p: (e: Edge) => void) {
+ for (const node of this.nodeMap) {
+ if (!node) continue;
+ for (const edge of node.inputs) {
+ p(edge);
+ }
+ }
+ }
+
+ redetermineGraphBoundingBox(showTypes: boolean): [[number, number], [number, number]] {
+ this.minGraphX = 0;
+ this.maxGraphNodeX = 1;
+ this.maxGraphX = undefined; // see below
+ this.minGraphY = 0;
+ this.maxGraphY = 1;
+
+ for (const node of this.nodes()) {
+ if (!node.visible) {
+ continue;
+ }
+
+ if (node.x < this.minGraphX) {
+ this.minGraphX = node.x;
+ }
+ if ((node.x + node.getTotalNodeWidth()) > this.maxGraphNodeX) {
+ this.maxGraphNodeX = node.x + node.getTotalNodeWidth();
+ }
+ if ((node.y - 50) < this.minGraphY) {
+ this.minGraphY = node.y - 50;
+ }
+ if ((node.y + node.getNodeHeight(showTypes) + 50) > this.maxGraphY) {
+ this.maxGraphY = node.y + node.getNodeHeight(showTypes) + 50;
+ }
+ }
+
+ this.maxGraphX = this.maxGraphNodeX +
+ this.maxBackEdgeNumber * MINIMUM_EDGE_SEPARATION;
+
+ this.width = this.maxGraphX - this.minGraphX;
+ this.height = this.maxGraphY - this.minGraphY;
+
+ const extent: [[number, number], [number, number]] = [
+ [this.minGraphX - this.width / 2, this.minGraphY - this.height / 2],
+ [this.maxGraphX + this.width / 2, this.maxGraphY + this.height / 2]
+ ];
+
+ return extent;
+ }
+
+}
diff --git a/src/v8/tools/turbolizer/src/graphmultiview.ts b/src/v8/tools/turbolizer/src/graphmultiview.ts
new file mode 100644
index 0000000..43ec418
--- /dev/null
+++ b/src/v8/tools/turbolizer/src/graphmultiview.ts
@@ -0,0 +1,136 @@
+// 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.
+
+import { GraphView } from "../src/graph-view";
+import { ScheduleView } from "../src/schedule-view";
+import { SequenceView } from "../src/sequence-view";
+import { SourceResolver } from "../src/source-resolver";
+import { SelectionBroker } from "../src/selection-broker";
+import { View, PhaseView } from "../src/view";
+
+const multiviewID = "multiview";
+
+const toolboxHTML = `
+<div class="graph-toolbox">
+ <select id="phase-select">
+ <option disabled selected>(please open a file)</option>
+ </select>
+ <input id="search-input" type="text" title="search nodes for regex" alt="search node for regex" class="search-input"
+ placeholder="find with regexp…">
+ <label><input id="search-only-visible" type="checkbox" name="instruction-address" alt="Apply search to visible nodes only">only visible</label>
+</div>`;
+
+export class GraphMultiView extends View {
+ sourceResolver: SourceResolver;
+ selectionBroker: SelectionBroker;
+ graph: GraphView;
+ schedule: ScheduleView;
+ sequence: SequenceView;
+ selectMenu: HTMLSelectElement;
+ currentPhaseView: PhaseView;
+
+ createViewElement() {
+ const pane = document.createElement("div");
+ pane.setAttribute("id", multiviewID);
+ pane.className = "viewpane";
+ return pane;
+ }
+
+ constructor(id, selectionBroker, sourceResolver) {
+ super(id);
+ const view = this;
+ view.sourceResolver = sourceResolver;
+ view.selectionBroker = selectionBroker;
+ const toolbox = document.createElement("div");
+ toolbox.className = "toolbox-anchor";
+ toolbox.innerHTML = toolboxHTML;
+ view.divNode.appendChild(toolbox);
+ const searchInput = toolbox.querySelector("#search-input") as HTMLInputElement;
+ const onlyVisibleCheckbox = toolbox.querySelector("#search-only-visible") as HTMLInputElement;
+ searchInput.addEventListener("keyup", e => {
+ if (!view.currentPhaseView) return;
+ view.currentPhaseView.searchInputAction(searchInput, e, onlyVisibleCheckbox.checked);
+ });
+ view.divNode.addEventListener("keyup", (e: KeyboardEvent) => {
+ if (e.keyCode == 191) { // keyCode == '/'
+ searchInput.focus();
+ }
+ });
+ searchInput.setAttribute("value", window.sessionStorage.getItem("lastSearch") || "");
+ this.graph = new GraphView(this.divNode, selectionBroker, view.displayPhaseByName.bind(this),
+ toolbox.querySelector(".graph-toolbox"));
+ this.schedule = new ScheduleView(this.divNode, selectionBroker);
+ this.sequence = new SequenceView(this.divNode, selectionBroker);
+ this.selectMenu = toolbox.querySelector("#phase-select") as HTMLSelectElement;
+ }
+
+ initializeSelect() {
+ const view = this;
+ view.selectMenu.innerHTML = "";
+ view.sourceResolver.forEachPhase(phase => {
+ const optionElement = document.createElement("option");
+ let maxNodeId = "";
+ if (phase.type == "graph" && phase.highestNodeId != 0) {
+ maxNodeId = ` ${phase.highestNodeId}`;
+ }
+ optionElement.text = `${phase.name}${maxNodeId}`;
+ view.selectMenu.add(optionElement);
+ });
+ this.selectMenu.onchange = function (this: HTMLSelectElement) {
+ const phaseIndex = this.selectedIndex;
+ window.sessionStorage.setItem("lastSelectedPhase", phaseIndex.toString());
+ view.displayPhase(view.sourceResolver.getPhase(phaseIndex));
+ };
+ }
+
+ show() {
+ super.show();
+ this.initializeSelect();
+ const lastPhaseIndex = +window.sessionStorage.getItem("lastSelectedPhase");
+ const initialPhaseIndex = this.sourceResolver.repairPhaseId(lastPhaseIndex);
+ this.selectMenu.selectedIndex = initialPhaseIndex;
+ this.displayPhase(this.sourceResolver.getPhase(initialPhaseIndex));
+ }
+
+ displayPhase(phase, selection?: Set<any>) {
+ if (phase.type == "graph") {
+ this.displayPhaseView(this.graph, phase, selection);
+ } else if (phase.type == "schedule") {
+ this.displayPhaseView(this.schedule, phase, selection);
+ } else if (phase.type == "sequence") {
+ this.displayPhaseView(this.sequence, phase, selection);
+ }
+ }
+
+ displayPhaseView(view: PhaseView, data, selection?: Set<any>) {
+ const rememberedSelection = selection ? selection : this.hideCurrentPhase();
+ view.initializeContent(data, rememberedSelection);
+ this.divNode.classList.toggle("scrollable", view.isScrollable());
+ this.currentPhaseView = view;
+ }
+
+ displayPhaseByName(phaseName, selection?: Set<any>) {
+ const phaseId = this.sourceResolver.getPhaseIdByName(phaseName);
+ this.selectMenu.selectedIndex = phaseId;
+ this.displayPhase(this.sourceResolver.getPhase(phaseId), selection);
+ }
+
+ hideCurrentPhase() {
+ let rememberedSelection = null;
+ if (this.currentPhaseView != null) {
+ rememberedSelection = this.currentPhaseView.detachSelection();
+ this.currentPhaseView.hide();
+ this.currentPhaseView = null;
+ }
+ return rememberedSelection;
+ }
+
+ onresize() {
+ if (this.currentPhaseView) this.currentPhaseView.onresize();
+ }
+
+ detachSelection() {
+ return null;
+ }
+}
diff --git a/src/v8/tools/turbolizer/src/info-view.ts b/src/v8/tools/turbolizer/src/info-view.ts
new file mode 100644
index 0000000..3858536
--- /dev/null
+++ b/src/v8/tools/turbolizer/src/info-view.ts
@@ -0,0 +1,17 @@
+import { View } from "./view";
+
+export class InfoView extends View {
+
+ constructor(idOrContainer: HTMLElement | string) {
+ super(idOrContainer);
+ fetch("info-view.html")
+ .then(response => response.text())
+ .then(htmlText => this.divNode.innerHTML = htmlText);
+ }
+
+ createViewElement(): HTMLElement {
+ const infoContainer = document.createElement("div");
+ infoContainer.classList.add("info-container");
+ return infoContainer;
+ }
+}
diff --git a/src/v8/tools/turbolizer/src/node-label.ts b/src/v8/tools/turbolizer/src/node-label.ts
new file mode 100644
index 0000000..6e7d41d
--- /dev/null
+++ b/src/v8/tools/turbolizer/src/node-label.ts
@@ -0,0 +1,86 @@
+// Copyright 2019 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.
+
+function formatOrigin(origin) {
+ if (origin.nodeId) {
+ return `#${origin.nodeId} in phase ${origin.phase}/${origin.reducer}`;
+ }
+ if (origin.bytecodePosition) {
+ return `Bytecode line ${origin.bytecodePosition} in phase ${origin.phase}/${origin.reducer}`;
+ }
+ return "unknown origin";
+}
+
+export class NodeLabel {
+ id: number;
+ label: string;
+ title: string;
+ live: boolean;
+ properties: string;
+ sourcePosition: any;
+ origin: any;
+ opcode: string;
+ control: boolean;
+ opinfo: string;
+ type: string;
+ inplaceUpdatePhase: string;
+
+ constructor(id: number, label: string, title: string, live: boolean, properties: string, sourcePosition: any, origin: any, opcode: string, control: boolean, opinfo: string, type: string) {
+ this.id = id;
+ this.label = label;
+ this.title = title;
+ this.live = live;
+ this.properties = properties;
+ this.sourcePosition = sourcePosition;
+ this.origin = origin;
+ this.opcode = opcode;
+ this.control = control;
+ this.opinfo = opinfo;
+ this.type = type;
+ this.inplaceUpdatePhase = null;
+ }
+
+ equals(that?: NodeLabel) {
+ if (!that) return false;
+ if (this.id != that.id) return false;
+ if (this.label != that.label) return false;
+ if (this.title != that.title) return false;
+ if (this.live != that.live) return false;
+ if (this.properties != that.properties) return false;
+ if (this.opcode != that.opcode) return false;
+ if (this.control != that.control) return false;
+ if (this.opinfo != that.opinfo) return false;
+ if (this.type != that.type) return false;
+ return true;
+ }
+
+ getTitle() {
+ let propsString = "";
+ if (this.properties === "") {
+ propsString = "no properties";
+ } else {
+ propsString = "[" + this.properties + "]";
+ }
+ let title = this.title + "\n" + propsString + "\n" + this.opinfo;
+ if (this.origin) {
+ title += `\nOrigin: ${formatOrigin(this.origin)}`;
+ }
+ if (this.inplaceUpdatePhase) {
+ title += `\nInplace update in phase: ${this.inplaceUpdatePhase}`;
+ }
+ return title;
+ }
+
+ getDisplayLabel() {
+ const result = `${this.id}: ${this.label}`;
+ if (result.length > 40) {
+ return `${this.id}: ${this.opcode}`;
+ }
+ return result;
+ }
+
+ setInplaceUpdatePhase(name: string): any {
+ this.inplaceUpdatePhase = name;
+ }
+}
diff --git a/src/v8/tools/turbolizer/src/node.ts b/src/v8/tools/turbolizer/src/node.ts
new file mode 100644
index 0000000..02906d1
--- /dev/null
+++ b/src/v8/tools/turbolizer/src/node.ts
@@ -0,0 +1,180 @@
+// Copyright 2014 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 { MINIMUM_EDGE_SEPARATION, Edge } from "../src/edge";
+import { NodeLabel } from "./node-label";
+import { MAX_RANK_SENTINEL } from "./constants";
+import { alignUp, measureText } from "./util";
+
+export const DEFAULT_NODE_BUBBLE_RADIUS = 12;
+export const NODE_INPUT_WIDTH = 50;
+export const MINIMUM_NODE_OUTPUT_APPROACH = 15;
+const MINIMUM_NODE_INPUT_APPROACH = 15 + 2 * DEFAULT_NODE_BUBBLE_RADIUS;
+
+export class GNode {
+ id: number;
+ nodeLabel: NodeLabel;
+ displayLabel: string;
+ inputs: Array<Edge>;
+ outputs: Array<Edge>;
+ visible: boolean;
+ x: number;
+ y: number;
+ rank: number;
+ outputApproach: number;
+ cfg: boolean;
+ labelbbox: { width: number, height: number };
+ width: number;
+ normalheight: number;
+ visitOrderWithinRank: number;
+
+ constructor(nodeLabel: NodeLabel) {
+ this.id = nodeLabel.id;
+ this.nodeLabel = nodeLabel;
+ this.displayLabel = nodeLabel.getDisplayLabel();
+ this.inputs = [];
+ this.outputs = [];
+ this.visible = false;
+ this.x = 0;
+ this.y = 0;
+ this.rank = MAX_RANK_SENTINEL;
+ this.outputApproach = MINIMUM_NODE_OUTPUT_APPROACH;
+ // Every control node is a CFG node.
+ this.cfg = nodeLabel.control;
+ this.labelbbox = measureText(this.displayLabel);
+ const typebbox = measureText(this.getDisplayType());
+ const innerwidth = Math.max(this.labelbbox.width, typebbox.width);
+ this.width = alignUp(innerwidth + NODE_INPUT_WIDTH * 2,
+ NODE_INPUT_WIDTH);
+ const innerheight = Math.max(this.labelbbox.height, typebbox.height);
+ this.normalheight = innerheight + 20;
+ this.visitOrderWithinRank = 0;
+ }
+
+ isControl() {
+ return this.nodeLabel.control;
+ }
+ isInput() {
+ return this.nodeLabel.opcode == 'Parameter' || this.nodeLabel.opcode.endsWith('Constant');
+ }
+ isLive() {
+ return this.nodeLabel.live !== false;
+ }
+ isJavaScript() {
+ return this.nodeLabel.opcode.startsWith('JS');
+ }
+ isSimplified() {
+ if (this.isJavaScript()) return false;
+ const opcode = this.nodeLabel.opcode;
+ return opcode.endsWith('Phi') ||
+ opcode.startsWith('Boolean') ||
+ opcode.startsWith('Number') ||
+ opcode.startsWith('String') ||
+ opcode.startsWith('Change') ||
+ opcode.startsWith('Object') ||
+ opcode.startsWith('Reference') ||
+ opcode.startsWith('Any') ||
+ opcode.endsWith('ToNumber') ||
+ (opcode == 'AnyToBoolean') ||
+ (opcode.startsWith('Load') && opcode.length > 4) ||
+ (opcode.startsWith('Store') && opcode.length > 5);
+ }
+ isMachine() {
+ return !(this.isControl() || this.isInput() ||
+ this.isJavaScript() || this.isSimplified());
+ }
+ getTotalNodeWidth() {
+ const inputWidth = this.inputs.length * NODE_INPUT_WIDTH;
+ return Math.max(inputWidth, this.width);
+ }
+ getTitle() {
+ return this.nodeLabel.getTitle();
+ }
+ getDisplayLabel() {
+ return this.nodeLabel.getDisplayLabel();
+ }
+ getType() {
+ return this.nodeLabel.type;
+ }
+ getDisplayType() {
+ let typeString = this.nodeLabel.type;
+ if (typeString == undefined) return "";
+ if (typeString.length > 24) {
+ typeString = typeString.substr(0, 25) + "...";
+ }
+ return typeString;
+ }
+ deepestInputRank() {
+ let deepestRank = 0;
+ this.inputs.forEach(function (e) {
+ if (e.isVisible() && !e.isBackEdge()) {
+ if (e.source.rank > deepestRank) {
+ deepestRank = e.source.rank;
+ }
+ }
+ });
+ return deepestRank;
+ }
+ areAnyOutputsVisible() {
+ let visibleCount = 0;
+ this.outputs.forEach(function (e) { if (e.isVisible())++visibleCount; });
+ if (this.outputs.length == visibleCount) return 2;
+ if (visibleCount != 0) return 1;
+ return 0;
+ }
+ setOutputVisibility(v) {
+ let result = false;
+ this.outputs.forEach(function (e) {
+ e.visible = v;
+ if (v) {
+ if (!e.target.visible) {
+ e.target.visible = true;
+ result = true;
+ }
+ }
+ });
+ return result;
+ }
+ setInputVisibility(i, v) {
+ const edge = this.inputs[i];
+ edge.visible = v;
+ if (v) {
+ if (!edge.source.visible) {
+ edge.source.visible = true;
+ return true;
+ }
+ }
+ return false;
+ }
+ getInputApproach(index) {
+ return this.y - MINIMUM_NODE_INPUT_APPROACH -
+ (index % 4) * MINIMUM_EDGE_SEPARATION - DEFAULT_NODE_BUBBLE_RADIUS;
+ }
+ getNodeHeight(showTypes: boolean): number {
+ if (showTypes) {
+ return this.normalheight + this.labelbbox.height;
+ } else {
+ return this.normalheight;
+ }
+ }
+ getOutputApproach(showTypes: boolean) {
+ return this.y + this.outputApproach + this.getNodeHeight(showTypes) +
+ + DEFAULT_NODE_BUBBLE_RADIUS;
+ }
+ getInputX(index) {
+ const result = this.getTotalNodeWidth() - (NODE_INPUT_WIDTH / 2) +
+ (index - this.inputs.length + 1) * NODE_INPUT_WIDTH;
+ return result;
+ }
+ getOutputX() {
+ return this.getTotalNodeWidth() - (NODE_INPUT_WIDTH / 2);
+ }
+ hasBackEdges() {
+ return (this.nodeLabel.opcode == "Loop") ||
+ ((this.nodeLabel.opcode == "Phi" || this.nodeLabel.opcode == "EffectPhi" || this.nodeLabel.opcode == "InductionVariablePhi") &&
+ this.inputs[this.inputs.length - 1].source.nodeLabel.opcode == "Loop");
+ }
+}
+
+export const nodeToStr = (n: GNode) => "N" + n.id;
diff --git a/src/v8/tools/turbolizer/src/resizer.ts b/src/v8/tools/turbolizer/src/resizer.ts
new file mode 100644
index 0000000..ec2d68c
--- /dev/null
+++ b/src/v8/tools/turbolizer/src/resizer.ts
@@ -0,0 +1,199 @@
+// Copyright 2019 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 * as d3 from "d3";
+import * as C from "../src/constants";
+
+class Snapper {
+ resizer: Resizer;
+ sourceExpand: HTMLElement;
+ sourceCollapse: HTMLElement;
+ disassemblyExpand: HTMLElement;
+ disassemblyCollapse: HTMLElement;
+
+ constructor(resizer: Resizer) {
+ this.resizer = resizer;
+ this.sourceExpand = document.getElementById(C.SOURCE_EXPAND_ID);
+ this.sourceCollapse = document.getElementById(C.SOURCE_COLLAPSE_ID);
+ this.disassemblyExpand = document.getElementById(C.DISASSEMBLY_EXPAND_ID);
+ this.disassemblyCollapse = document.getElementById(C.DISASSEMBLY_COLLAPSE_ID);
+
+ document.getElementById("source-collapse").addEventListener("click", () => {
+ this.setSourceExpanded(!this.sourceExpand.classList.contains("invisible"));
+ this.resizer.updatePanes();
+ });
+ document.getElementById("disassembly-collapse").addEventListener("click", () => {
+ this.setDisassemblyExpanded(!this.disassemblyExpand.classList.contains("invisible"));
+ this.resizer.updatePanes();
+ });
+ }
+
+ restoreExpandedState(): void {
+ this.setSourceExpanded(this.getLastExpandedState("source", true));
+ this.setDisassemblyExpanded(this.getLastExpandedState("disassembly", false));
+ }
+
+ getLastExpandedState(type: string, defaultState: boolean): boolean {
+ const state = window.sessionStorage.getItem("expandedState-" + type);
+ if (state === null) return defaultState;
+ return state === 'true';
+ }
+
+ sourceExpandUpdate(newState: boolean): void {
+ window.sessionStorage.setItem("expandedState-source", `${newState}`);
+ this.sourceExpand.classList.toggle("invisible", newState);
+ this.sourceCollapse.classList.toggle("invisible", !newState);
+ }
+
+ setSourceExpanded(newState: boolean): void {
+ if (this.sourceExpand.classList.contains("invisible") === newState) return;
+ const resizer = this.resizer;
+ this.sourceExpandUpdate(newState);
+ if (newState) {
+ resizer.sepLeft = resizer.sepLeftSnap;
+ resizer.sepLeftSnap = 0;
+ } else {
+ resizer.sepLeftSnap = resizer.sepLeft;
+ resizer.sepLeft = 0;
+ }
+ }
+
+ disassemblyExpandUpdate(newState: boolean): void {
+ window.sessionStorage.setItem("expandedState-disassembly", `${newState}`);
+ this.disassemblyExpand.classList.toggle("invisible", newState);
+ this.disassemblyCollapse.classList.toggle("invisible", !newState);
+ }
+
+ setDisassemblyExpanded(newState: boolean): void {
+ if (this.disassemblyExpand.classList.contains("invisible") === newState) return;
+ const resizer = this.resizer;
+ this.disassemblyExpandUpdate(newState);
+ if (newState) {
+ resizer.sepRight = resizer.sepRightSnap;
+ resizer.sepRightSnap = resizer.clientWidth;
+ } else {
+ resizer.sepRightSnap = resizer.sepRight;
+ resizer.sepRight = resizer.clientWidth;
+ }
+ }
+
+ panesUpdated(): void {
+ this.sourceExpandUpdate(this.resizer.sepLeft > this.resizer.deadWidth);
+ this.disassemblyExpandUpdate(this.resizer.sepRight <
+ (this.resizer.clientWidth - this.resizer.deadWidth));
+ }
+}
+
+export class Resizer {
+ snapper: Snapper;
+ deadWidth: number;
+ clientWidth: number;
+ left: HTMLElement;
+ right: HTMLElement;
+ middle: HTMLElement;
+ sepLeft: number;
+ sepRight: number;
+ sepLeftSnap: number;
+ sepRightSnap: number;
+ sepWidthOffset: number;
+ panesUpdatedCallback: () => void;
+ resizerRight: d3.Selection<HTMLDivElement, any, any, any>;
+ resizerLeft: d3.Selection<HTMLDivElement, any, any, any>;
+
+ constructor(panesUpdatedCallback: () => void, deadWidth: number) {
+ const resizer = this;
+ resizer.panesUpdatedCallback = panesUpdatedCallback;
+ resizer.deadWidth = deadWidth;
+ resizer.left = document.getElementById(C.SOURCE_PANE_ID);
+ resizer.middle = document.getElementById(C.INTERMEDIATE_PANE_ID);
+ resizer.right = document.getElementById(C.GENERATED_PANE_ID);
+ resizer.resizerLeft = d3.select('#resizer-left');
+ resizer.resizerRight = d3.select('#resizer-right');
+ resizer.sepLeftSnap = 0;
+ resizer.sepRightSnap = 0;
+ // Offset to prevent resizers from sliding slightly over one another.
+ resizer.sepWidthOffset = 7;
+ this.updateWidths();
+
+ const dragResizeLeft = d3.drag()
+ .on('drag', function () {
+ const x = d3.mouse(this.parentElement)[0];
+ resizer.sepLeft = Math.min(Math.max(0, x), resizer.sepRight - resizer.sepWidthOffset);
+ resizer.updatePanes();
+ })
+ .on('start', function () {
+ resizer.resizerLeft.classed("dragged", true);
+ const x = d3.mouse(this.parentElement)[0];
+ if (x > deadWidth) {
+ resizer.sepLeftSnap = resizer.sepLeft;
+ }
+ })
+ .on('end', function () {
+ if (!resizer.isRightSnapped()) {
+ window.sessionStorage.setItem("source-pane-width", `${resizer.sepLeft / resizer.clientWidth}`);
+ }
+ resizer.resizerLeft.classed("dragged", false);
+ });
+ resizer.resizerLeft.call(dragResizeLeft);
+
+ const dragResizeRight = d3.drag()
+ .on('drag', function () {
+ const x = d3.mouse(this.parentElement)[0];
+ resizer.sepRight = Math.max(resizer.sepLeft + resizer.sepWidthOffset, Math.min(x, resizer.clientWidth));
+ resizer.updatePanes();
+ })
+ .on('start', function () {
+ resizer.resizerRight.classed("dragged", true);
+ const x = d3.mouse(this.parentElement)[0];
+ if (x < (resizer.clientWidth - deadWidth)) {
+ resizer.sepRightSnap = resizer.sepRight;
+ }
+ })
+ .on('end', function () {
+ if (!resizer.isRightSnapped()) {
+ console.log(`disassembly-pane-width ${resizer.sepRight}`);
+ window.sessionStorage.setItem("disassembly-pane-width", `${resizer.sepRight / resizer.clientWidth}`);
+ }
+ resizer.resizerRight.classed("dragged", false);
+ });
+ resizer.resizerRight.call(dragResizeRight);
+ window.onresize = function () {
+ resizer.updateWidths();
+ resizer.updatePanes();
+ };
+ resizer.snapper = new Snapper(resizer);
+ resizer.snapper.restoreExpandedState();
+ }
+
+ isLeftSnapped() {
+ return this.sepLeft === 0;
+ }
+
+ isRightSnapped() {
+ return this.sepRight >= this.clientWidth - 1;
+ }
+
+ updatePanes() {
+ const leftSnapped = this.isLeftSnapped();
+ const rightSnapped = this.isRightSnapped();
+ this.resizerLeft.classed("snapped", leftSnapped);
+ this.resizerRight.classed("snapped", rightSnapped);
+ this.left.style.width = this.sepLeft + 'px';
+ this.middle.style.width = (this.sepRight - this.sepLeft) + 'px';
+ this.right.style.width = (this.clientWidth - this.sepRight) + 'px';
+ this.resizerLeft.style('left', this.sepLeft + 'px');
+ this.resizerRight.style('right', (this.clientWidth - this.sepRight - 1) + 'px');
+
+ this.snapper.panesUpdated();
+ this.panesUpdatedCallback();
+ }
+
+ updateWidths() {
+ this.clientWidth = document.body.getBoundingClientRect().width;
+ const sepLeft = window.sessionStorage.getItem("source-pane-width");
+ this.sepLeft = this.clientWidth * (sepLeft ? Number.parseFloat(sepLeft) : (1 / 3));
+ const sepRight = window.sessionStorage.getItem("disassembly-pane-width");
+ this.sepRight = this.clientWidth * (sepRight ? Number.parseFloat(sepRight) : (2 / 3));
+ }
+}
diff --git a/src/v8/tools/turbolizer/src/schedule-view.ts b/src/v8/tools/turbolizer/src/schedule-view.ts
new file mode 100644
index 0000000..3da62ec
--- /dev/null
+++ b/src/v8/tools/turbolizer/src/schedule-view.ts
@@ -0,0 +1,187 @@
+// Copyright 2015 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import { Schedule, SourceResolver } from "../src/source-resolver";
+import { TextView } from "../src/text-view";
+
+export class ScheduleView extends TextView {
+ schedule: Schedule;
+ sourceResolver: SourceResolver;
+
+ createViewElement() {
+ const pane = document.createElement('div');
+ pane.setAttribute('id', "schedule");
+ return pane;
+ }
+
+ constructor(parentId, broker) {
+ super(parentId, broker);
+ this.sourceResolver = broker.sourceResolver;
+ }
+
+ attachSelection(s) {
+ const view = this;
+ if (!(s instanceof Set)) return;
+ view.selectionHandler.clear();
+ view.blockSelectionHandler.clear();
+ const selected = new Array();
+ for (const key of s) selected.push(key);
+ view.selectionHandler.select(selected, true);
+ }
+
+ detachSelection() {
+ this.blockSelection.clear();
+ return this.selection.detachSelection();
+ }
+
+ initializeContent(data, rememberedSelection) {
+ this.divNode.innerHTML = '';
+ this.schedule = data.schedule;
+ this.addBlocks(data.schedule.blocks);
+ this.attachSelection(rememberedSelection);
+ this.show();
+ }
+
+ createElementFromString(htmlString) {
+ const div = document.createElement('div');
+ div.innerHTML = htmlString.trim();
+ return div.firstChild;
+ }
+
+ elementForBlock(block) {
+ const view = this;
+ function createElement(tag: string, cls: string, content?: string) {
+ const el = document.createElement(tag);
+ el.className = cls;
+ if (content != undefined) el.innerHTML = content;
+ return el;
+ }
+
+ function mkNodeLinkHandler(nodeId) {
+ return function (e) {
+ e.stopPropagation();
+ if (!e.shiftKey) {
+ view.selectionHandler.clear();
+ }
+ view.selectionHandler.select([nodeId], true);
+ };
+ }
+
+ function getMarker(start, end) {
+ if (start != end) {
+ return ["⊙", `This node generated instructions in range [${start},${end}). ` +
+ `This is currently unreliable for constants.`];
+ }
+ if (start != -1) {
+ return ["·", `The instruction selector did not generate instructions ` +
+ `for this node, but processed the node at instruction ${start}. ` +
+ `This usually means that this node was folded into another node; ` +
+ `the highlighted machine code is a guess.`];
+ }
+ return ["", `This not is not in the final schedule.`];
+ }
+
+ function createElementForNode(node) {
+ const nodeEl = createElement("div", "node");
+
+ const [start, end] = view.sourceResolver.getInstruction(node.id);
+ const [marker, tooltip] = getMarker(start, end);
+ const instrMarker = createElement("div", "instr-marker com", marker);
+ instrMarker.setAttribute("title", tooltip);
+ instrMarker.onclick = mkNodeLinkHandler(node.id);
+ nodeEl.appendChild(instrMarker);
+
+ const nodeId = createElement("div", "node-id tag clickable", node.id);
+ nodeId.onclick = mkNodeLinkHandler(node.id);
+ view.addHtmlElementForNodeId(node.id, nodeId);
+ nodeEl.appendChild(nodeId);
+ const nodeLabel = createElement("div", "node-label", node.label);
+ nodeEl.appendChild(nodeLabel);
+ if (node.inputs.length > 0) {
+ const nodeParameters = createElement("div", "parameter-list comma-sep-list");
+ for (const param of node.inputs) {
+ const paramEl = createElement("div", "parameter tag clickable", param);
+ nodeParameters.appendChild(paramEl);
+ paramEl.onclick = mkNodeLinkHandler(param);
+ view.addHtmlElementForNodeId(param, paramEl);
+ }
+ nodeEl.appendChild(nodeParameters);
+ }
+
+ return nodeEl;
+ }
+
+ function mkBlockLinkHandler(blockId) {
+ return function (e) {
+ e.stopPropagation();
+ if (!e.shiftKey) {
+ view.blockSelectionHandler.clear();
+ }
+ view.blockSelectionHandler.select(["" + blockId], true);
+ };
+ }
+
+ const scheduleBlock = createElement("div", "schedule-block");
+ scheduleBlock.classList.toggle("deferred", block.isDeferred);
+
+ const [start, end] = view.sourceResolver.getInstructionRangeForBlock(block.id);
+ const instrMarker = createElement("div", "instr-marker com", "⊙");
+ instrMarker.setAttribute("title", `Instructions range for this block is [${start}, ${end})`);
+ instrMarker.onclick = mkBlockLinkHandler(block.id);
+ scheduleBlock.appendChild(instrMarker);
+
+ const blockId = createElement("div", "block-id com clickable", block.id);
+ blockId.onclick = mkBlockLinkHandler(block.id);
+ scheduleBlock.appendChild(blockId);
+ const blockPred = createElement("div", "predecessor-list block-list comma-sep-list");
+ for (const pred of block.pred) {
+ const predEl = createElement("div", "block-id com clickable", pred);
+ predEl.onclick = mkBlockLinkHandler(pred);
+ blockPred.appendChild(predEl);
+ }
+ if (block.pred.length) scheduleBlock.appendChild(blockPred);
+ const nodes = createElement("div", "nodes");
+ for (const node of block.nodes) {
+ nodes.appendChild(createElementForNode(node));
+ }
+ scheduleBlock.appendChild(nodes);
+ const blockSucc = createElement("div", "successor-list block-list comma-sep-list");
+ for (const succ of block.succ) {
+ const succEl = createElement("div", "block-id com clickable", succ);
+ succEl.onclick = mkBlockLinkHandler(succ);
+ blockSucc.appendChild(succEl);
+ }
+ if (block.succ.length) scheduleBlock.appendChild(blockSucc);
+ this.addHtmlElementForBlockId(block.id, scheduleBlock);
+ return scheduleBlock;
+ }
+
+ addBlocks(blocks) {
+ for (const block of blocks) {
+ const blockEl = this.elementForBlock(block);
+ this.divNode.appendChild(blockEl);
+ }
+ }
+
+ lineString(node) {
+ return `${node.id}: ${node.label}(${node.inputs.join(", ")})`;
+ }
+
+ searchInputAction(searchBar, e, onlyVisible) {
+ e.stopPropagation();
+ this.selectionHandler.clear();
+ const query = searchBar.value;
+ if (query.length == 0) return;
+ const select = [];
+ window.sessionStorage.setItem("lastSearch", query);
+ const reg = new RegExp(query);
+ for (const node of this.schedule.nodes) {
+ if (node === undefined) continue;
+ if (reg.exec(this.lineString(node)) != null) {
+ select.push(node.id);
+ }
+ }
+ this.selectionHandler.select(select, true);
+ }
+}
diff --git a/src/v8/tools/turbolizer/src/selection-broker.ts b/src/v8/tools/turbolizer/src/selection-broker.ts
new file mode 100644
index 0000000..7e0c0dd
--- /dev/null
+++ b/src/v8/tools/turbolizer/src/selection-broker.ts
@@ -0,0 +1,89 @@
+// Copyright 2015 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import { SourceResolver, sourcePositionValid } from "../src/source-resolver";
+import { ClearableHandler, SelectionHandler, NodeSelectionHandler, BlockSelectionHandler, InstructionSelectionHandler } from "../src/selection-handler";
+
+export class SelectionBroker {
+ sourceResolver: SourceResolver;
+ allHandlers: Array<ClearableHandler>;
+ sourcePositionHandlers: Array<SelectionHandler>;
+ nodeHandlers: Array<NodeSelectionHandler>;
+ blockHandlers: Array<BlockSelectionHandler>;
+ instructionHandlers: Array<InstructionSelectionHandler>;
+
+ constructor(sourceResolver) {
+ this.allHandlers = [];
+ this.sourcePositionHandlers = [];
+ this.nodeHandlers = [];
+ this.blockHandlers = [];
+ this.instructionHandlers = [];
+ this.sourceResolver = sourceResolver;
+ }
+
+ addSourcePositionHandler(handler: SelectionHandler & ClearableHandler) {
+ this.allHandlers.push(handler);
+ this.sourcePositionHandlers.push(handler);
+ }
+
+ addNodeHandler(handler: NodeSelectionHandler & ClearableHandler) {
+ this.allHandlers.push(handler);
+ this.nodeHandlers.push(handler);
+ }
+
+ addBlockHandler(handler: BlockSelectionHandler & ClearableHandler) {
+ this.allHandlers.push(handler);
+ this.blockHandlers.push(handler);
+ }
+
+ addInstructionHandler(handler: InstructionSelectionHandler & ClearableHandler) {
+ this.allHandlers.push(handler);
+ this.instructionHandlers.push(handler);
+ }
+
+ broadcastInstructionSelect(from, instructionOffsets, selected) {
+ for (const b of this.instructionHandlers) {
+ if (b != from) b.brokeredInstructionSelect(instructionOffsets, selected);
+ }
+ }
+
+ broadcastSourcePositionSelect(from, sourcePositions, selected) {
+ sourcePositions = sourcePositions.filter(l => {
+ if (!sourcePositionValid(l)) {
+ console.log("Warning: invalid source position");
+ return false;
+ }
+ return true;
+ });
+ for (const b of this.sourcePositionHandlers) {
+ if (b != from) b.brokeredSourcePositionSelect(sourcePositions, selected);
+ }
+ const nodes = this.sourceResolver.sourcePositionsToNodeIds(sourcePositions);
+ for (const b of this.nodeHandlers) {
+ if (b != from) b.brokeredNodeSelect(nodes, selected);
+ }
+ }
+
+ broadcastNodeSelect(from, nodes, selected) {
+ for (const b of this.nodeHandlers) {
+ if (b != from) b.brokeredNodeSelect(nodes, selected);
+ }
+ const sourcePositions = this.sourceResolver.nodeIdsToSourcePositions(nodes);
+ for (const b of this.sourcePositionHandlers) {
+ if (b != from) b.brokeredSourcePositionSelect(sourcePositions, selected);
+ }
+ }
+
+ broadcastBlockSelect(from, blocks, selected) {
+ for (const b of this.blockHandlers) {
+ if (b != from) b.brokeredBlockSelect(blocks, selected);
+ }
+ }
+
+ broadcastClear(from) {
+ this.allHandlers.forEach(function (b) {
+ if (b != from) b.brokeredClear();
+ });
+ }
+}
diff --git a/src/v8/tools/turbolizer/src/selection-handler.ts b/src/v8/tools/turbolizer/src/selection-handler.ts
new file mode 100644
index 0000000..a605149
--- /dev/null
+++ b/src/v8/tools/turbolizer/src/selection-handler.ts
@@ -0,0 +1,31 @@
+// 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.
+
+export interface ClearableHandler {
+ brokeredClear(): void;
+}
+
+export interface SelectionHandler {
+ clear(): void;
+ select(nodeIds: any, selected: any): void;
+ brokeredSourcePositionSelect(sourcePositions: any, selected: any): void;
+}
+
+export interface NodeSelectionHandler {
+ clear(): void;
+ select(nodeIds: any, selected: any): void;
+ brokeredNodeSelect(nodeIds: any, selected: any): void;
+}
+
+export interface BlockSelectionHandler {
+ clear(): void;
+ select(nodeIds: any, selected: any): void;
+ brokeredBlockSelect(blockIds: any, selected: any): void;
+}
+
+export interface InstructionSelectionHandler {
+ clear(): void;
+ select(instructionIds: any, selected: any): void;
+ brokeredInstructionSelect(instructionIds: any, selected: any): void;
+}
diff --git a/src/v8/tools/turbolizer/src/selection.ts b/src/v8/tools/turbolizer/src/selection.ts
new file mode 100644
index 0000000..90fe3bd
--- /dev/null
+++ b/src/v8/tools/turbolizer/src/selection.ts
@@ -0,0 +1,59 @@
+// Copyright 2015 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+export class MySelection {
+ selection: any;
+ stringKey: (o: any) => string;
+
+ constructor(stringKeyFnc) {
+ this.selection = new Map();
+ this.stringKey = stringKeyFnc;
+ }
+
+ isEmpty(): boolean {
+ return this.selection.size == 0;
+ }
+
+ clear(): void {
+ this.selection = new Map();
+ }
+
+ select(s: Iterable<any>, isSelected?: boolean) {
+ for (const i of s) {
+ if (!i) continue;
+ if (isSelected == undefined) {
+ isSelected = !this.selection.has(this.stringKey(i));
+ }
+ if (isSelected) {
+ this.selection.set(this.stringKey(i), i);
+ } else {
+ this.selection.delete(this.stringKey(i));
+ }
+ }
+ }
+
+ isSelected(i: any): boolean {
+ return this.selection.has(this.stringKey(i));
+ }
+
+ isKeySelected(key: string): boolean {
+ return this.selection.has(key);
+ }
+
+ selectedKeys() {
+ const result = new Set();
+ for (const i of this.selection.keys()) {
+ result.add(i);
+ }
+ return result;
+ }
+
+ detachSelection() {
+ const result = this.selectedKeys();
+ this.clear();
+ return result;
+ }
+
+ [Symbol.iterator]() { return this.selection.values(); }
+}
diff --git a/src/v8/tools/turbolizer/src/sequence-view.ts b/src/v8/tools/turbolizer/src/sequence-view.ts
new file mode 100644
index 0000000..e7691c6
--- /dev/null
+++ b/src/v8/tools/turbolizer/src/sequence-view.ts
@@ -0,0 +1,251 @@
+// 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.
+
+import { Sequence } from "../src/source-resolver";
+import { isIterable } from "../src/util";
+import { TextView } from "../src/text-view";
+
+export class SequenceView extends TextView {
+ sequence: Sequence;
+ searchInfo: Array<any>;
+
+ createViewElement() {
+ const pane = document.createElement('div');
+ pane.setAttribute('id', "sequence");
+ return pane;
+ }
+
+ constructor(parentId, broker) {
+ super(parentId, broker);
+ }
+
+ attachSelection(s) {
+ const view = this;
+ if (!(s instanceof Set)) return;
+ view.selectionHandler.clear();
+ view.blockSelectionHandler.clear();
+ const selected = new Array();
+ for (const key of s) selected.push(key);
+ view.selectionHandler.select(selected, true);
+ }
+
+ detachSelection() {
+ this.blockSelection.clear();
+ return this.selection.detachSelection();
+ }
+
+ initializeContent(data, rememberedSelection) {
+ this.divNode.innerHTML = '';
+ this.sequence = data.sequence;
+ this.searchInfo = [];
+ this.divNode.addEventListener('click', (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.addBlocks(this.sequence.blocks);
+ 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) {
+ e.stopPropagation();
+ if (!e.shiftKey) {
+ handler.clear();
+ }
+ handler.select(["" + id], true);
+ };
+ }
+
+ function mkBlockLinkHandler(blockId) {
+ return mkLinkHandler(blockId, view.blockSelectionHandler);
+ }
+
+ function mkOperandLinkHandler(text) {
+ return mkLinkHandler(text, view.selectionHandler);
+ }
+
+ function elementForOperand(operand, searchInfo) {
+ const text = operand.text;
+ const operandEl = createElement("div", ["parameter", "tag", "clickable", operand.type], text);
+ if (operand.tooltip) {
+ operandEl.setAttribute("title", operand.tooltip);
+ }
+ operandEl.onclick = mkOperandLinkHandler(text);
+ searchInfo.push(text);
+ view.addHtmlElementForNodeId(text, operandEl);
+ return operandEl;
+ }
+
+ function elementForInstruction(instruction, searchInfo) {
+ const instNodeEl = createElement("div", "instruction-node");
+
+ const instId = createElement("div", "instruction-id", instruction.id);
+ instId.classList.add("clickable");
+ instId.dataset.instructionId = instruction.id;
+ instNodeEl.appendChild(instId);
+
+ const instContentsEl = createElement("div", "instruction-contents");
+ instNodeEl.appendChild(instContentsEl);
+
+ // Print gap moves.
+ const gapEl = createElement("div", "gap", "gap");
+ let hasGaps = false;
+ for (const gap of instruction.gaps) {
+ const moves = createElement("div", ["comma-sep-list", "gap-move"]);
+ for (const move of gap) {
+ hasGaps = true;
+ const moveEl = createElement("div", "move");
+ const destinationEl = elementForOperand(move[0], searchInfo);
+ moveEl.appendChild(destinationEl);
+ const assignEl = createElement("div", "assign", "=");
+ moveEl.appendChild(assignEl);
+ const sourceEl = elementForOperand(move[1], searchInfo);
+ moveEl.appendChild(sourceEl);
+ moves.appendChild(moveEl);
+ }
+ gapEl.appendChild(moves);
+ }
+ if (hasGaps) {
+ instContentsEl.appendChild(gapEl);
+ }
+
+ const instEl = createElement("div", "instruction");
+ instContentsEl.appendChild(instEl);
+
+ if (instruction.outputs.length > 0) {
+ const outputs = createElement("div", ["comma-sep-list", "input-output-list"]);
+ for (const output of instruction.outputs) {
+ const outputEl = elementForOperand(output, searchInfo);
+ outputs.appendChild(outputEl);
+ }
+ instEl.appendChild(outputs);
+ const assignEl = createElement("div", "assign", "=");
+ instEl.appendChild(assignEl);
+ }
+
+ let 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;
+ }
+
+ searchInfo.push(text);
+ view.addHtmlElementForNodeId(text, instLabel);
+ instEl.appendChild(instLabel);
+
+ if (instruction.inputs.length > 0) {
+ const inputs = createElement("div", ["comma-sep-list", "input-output-list"]);
+ for (const input of instruction.inputs) {
+ const inputEl = elementForOperand(input, searchInfo);
+ inputs.appendChild(inputEl);
+ }
+ instEl.appendChild(inputs);
+ }
+
+ if (instruction.temps.length > 0) {
+ 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);
+ }
+ instEl.appendChild(temps);
+ }
+
+ return instNodeEl;
+ }
+
+ const sequenceBlock = createElement("div", "schedule-block");
+ sequenceBlock.classList.toggle("deferred", block.deferred);
+
+ 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"]);
+ for (const pred of block.predecessors) {
+ const predEl = createElement("div", ["block-id", "com", "clickable"], pred);
+ predEl.onclick = mkBlockLinkHandler(pred);
+ blockPred.appendChild(predEl);
+ }
+ if (block.predecessors.length > 0) sequenceBlock.appendChild(blockPred);
+ const phis = createElement("div", "phis");
+ sequenceBlock.appendChild(phis);
+
+ const phiLabel = createElement("div", "phi-label", "phi:");
+ phis.appendChild(phiLabel);
+
+ const phiContents = createElement("div", "phi-contents");
+ phis.appendChild(phiContents);
+
+ for (const phi of block.phis) {
+ const phiEl = createElement("div", "phi");
+ phiContents.appendChild(phiEl);
+
+ const outputEl = elementForOperand(phi.output, this.searchInfo);
+ phiEl.appendChild(outputEl);
+
+ const assignEl = createElement("div", "assign", "=");
+ phiEl.appendChild(assignEl);
+
+ for (const input of phi.operands) {
+ const inputEl = createElement("div", ["parameter", "tag", "clickable"], input);
+ phiEl.appendChild(inputEl);
+ }
+ }
+
+ const instructions = createElement("div", "instructions");
+ for (const instruction of block.instructions) {
+ instructions.appendChild(elementForInstruction(instruction, this.searchInfo));
+ }
+ sequenceBlock.appendChild(instructions);
+ 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);
+ succEl.onclick = mkBlockLinkHandler(succ);
+ blockSucc.appendChild(succEl);
+ }
+ if (block.successors.length > 0) sequenceBlock.appendChild(blockSucc);
+ this.addHtmlElementForBlockId(block.id, sequenceBlock);
+ return sequenceBlock;
+ }
+
+ addBlocks(blocks) {
+ for (const block of blocks) {
+ const blockEl = this.elementForBlock(block);
+ this.divNode.appendChild(blockEl);
+ }
+ }
+
+ searchInputAction(searchBar, e) {
+ e.stopPropagation();
+ this.selectionHandler.clear();
+ const query = searchBar.value;
+ if (query.length == 0) return;
+ const select = [];
+ window.sessionStorage.setItem("lastSearch", query);
+ const reg = new RegExp(query);
+ for (const item of this.searchInfo) {
+ if (reg.exec(item) != null) {
+ select.push(item);
+ }
+ }
+ this.selectionHandler.select(select, true);
+ }
+}
diff --git a/src/v8/tools/turbolizer/src/source-resolver.ts b/src/v8/tools/turbolizer/src/source-resolver.ts
new file mode 100644
index 0000000..67f9c08
--- /dev/null
+++ b/src/v8/tools/turbolizer/src/source-resolver.ts
@@ -0,0 +1,626 @@
+// 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.
+
+import { sortUnique, anyToString } from "../src/util";
+import { NodeLabel } from "./node-label";
+
+function sourcePositionLe(a, b) {
+ if (a.inliningId == b.inliningId) {
+ return a.scriptOffset - b.scriptOffset;
+ }
+ return a.inliningId - b.inliningId;
+}
+
+function sourcePositionEq(a, b) {
+ return a.inliningId == b.inliningId &&
+ a.scriptOffset == b.scriptOffset;
+}
+
+export function sourcePositionToStringKey(sourcePosition: AnyPosition): string {
+ if (!sourcePosition) return "undefined";
+ if ('inliningId' in sourcePosition && 'scriptOffset' in sourcePosition) {
+ return "SP:" + sourcePosition.inliningId + ":" + sourcePosition.scriptOffset;
+ }
+ if (sourcePosition.bytecodePosition) {
+ return "BCP:" + sourcePosition.bytecodePosition;
+ }
+ return "undefined";
+}
+
+export function sourcePositionValid(l) {
+ return (typeof l.scriptOffset !== undefined
+ && typeof l.inliningId !== undefined) || typeof l.bytecodePosition != undefined;
+}
+
+export interface SourcePosition {
+ scriptOffset: number;
+ inliningId: number;
+}
+
+interface TurboFanOrigin {
+ phase: string;
+ reducer: string;
+}
+
+export interface NodeOrigin {
+ nodeId: number;
+}
+
+interface BytecodePosition {
+ bytecodePosition: number;
+}
+
+export type Origin = NodeOrigin | BytecodePosition;
+export type TurboFanNodeOrigin = NodeOrigin & TurboFanOrigin;
+export type TurboFanBytecodeOrigin = BytecodePosition & TurboFanOrigin;
+
+type AnyPosition = SourcePosition | BytecodePosition;
+
+export interface Source {
+ sourcePositions: Array<SourcePosition>;
+ sourceName: string;
+ functionName: string;
+ sourceText: string;
+ sourceId: number;
+ startPosition?: number;
+ backwardsCompatibility: boolean;
+}
+interface Inlining {
+ inliningPosition: SourcePosition;
+ sourceId: number;
+}
+interface OtherPhase {
+ type: "disassembly" | "sequence" | "schedule";
+ name: string;
+ data: any;
+}
+
+interface InstructionsPhase {
+ type: "instructions";
+ name: string;
+ data: any;
+ instructionOffsetToPCOffset?: any;
+ blockIdtoInstructionRange?: any;
+ nodeIdToInstructionRange?: any;
+}
+
+interface GraphPhase {
+ type: "graph";
+ name: string;
+ data: any;
+ highestNodeId: number;
+ nodeLabelMap: Array<NodeLabel>;
+}
+
+type Phase = GraphPhase | InstructionsPhase | OtherPhase;
+
+export interface Schedule {
+ nodes: Array<any>;
+}
+
+export interface Sequence {
+ blocks: Array<any>;
+}
+
+export class SourceResolver {
+ nodePositionMap: Array<AnyPosition>;
+ sources: Array<Source>;
+ inlinings: Array<Inlining>;
+ inliningsMap: Map<string, Inlining>;
+ positionToNodes: Map<string, Array<string>>;
+ phases: Array<Phase>;
+ phaseNames: Map<string, number>;
+ disassemblyPhase: Phase;
+ lineToSourcePositions: Map<string, Array<AnyPosition>>;
+ nodeIdToInstructionRange: Array<[number, number]>;
+ blockIdToInstructionRange: Array<[number, number]>;
+ instructionToPCOffset: Array<number>;
+ pcOffsetToInstructions: Map<number, Array<number>>;
+ pcOffsets: Array<number>;
+
+ constructor() {
+ // Maps node ids to source positions.
+ this.nodePositionMap = [];
+ // Maps source ids to source objects.
+ this.sources = [];
+ // Maps inlining ids to inlining objects.
+ this.inlinings = [];
+ // Maps source position keys to inlinings.
+ this.inliningsMap = new Map();
+ // Maps source position keys to node ids.
+ this.positionToNodes = new Map();
+ // Maps phase ids to phases.
+ this.phases = [];
+ // Maps phase names to phaseIds.
+ this.phaseNames = new Map();
+ // The disassembly phase is stored separately.
+ this.disassemblyPhase = undefined;
+ // Maps line numbers to source positions
+ this.lineToSourcePositions = new Map();
+ // Maps node ids to instruction ranges.
+ this.nodeIdToInstructionRange = [];
+ // Maps block ids to instruction ranges.
+ this.blockIdToInstructionRange = [];
+ // Maps instruction numbers to PC offsets.
+ this.instructionToPCOffset = [];
+ // Maps PC offsets to instructions.
+ this.pcOffsetToInstructions = new Map();
+ this.pcOffsets = [];
+ }
+
+ setSources(sources, mainBackup) {
+ if (sources) {
+ for (const [sourceId, source] of Object.entries(sources)) {
+ this.sources[sourceId] = source;
+ this.sources[sourceId].sourcePositions = [];
+ }
+ }
+ // This is a fallback if the JSON is incomplete (e.g. due to compiler crash).
+ if (!this.sources[-1]) {
+ this.sources[-1] = mainBackup;
+ this.sources[-1].sourcePositions = [];
+ }
+ }
+
+ setInlinings(inlinings) {
+ if (inlinings) {
+ for (const [inliningId, inlining] of Object.entries<Inlining>(inlinings)) {
+ this.inlinings[inliningId] = inlining;
+ this.inliningsMap.set(sourcePositionToStringKey(inlining.inliningPosition), inlining);
+ }
+ }
+ // This is a default entry for the script itself that helps
+ // keep other code more uniform.
+ this.inlinings[-1] = { sourceId: -1, inliningPosition: null };
+ }
+
+ setNodePositionMap(map) {
+ if (!map) return;
+ if (typeof map[0] != 'object') {
+ const alternativeMap = {};
+ for (const [nodeId, scriptOffset] of Object.entries<number>(map)) {
+ alternativeMap[nodeId] = { scriptOffset: scriptOffset, inliningId: -1 };
+ }
+ map = alternativeMap;
+ }
+
+ for (const [nodeId, sourcePosition] of Object.entries<SourcePosition>(map)) {
+ if (sourcePosition == undefined) {
+ console.log("Warning: undefined source position ", sourcePosition, " for nodeId ", nodeId);
+ }
+ const inliningId = sourcePosition.inliningId;
+ const inlining = this.inlinings[inliningId];
+ if (inlining) {
+ const sourceId = inlining.sourceId;
+ this.sources[sourceId].sourcePositions.push(sourcePosition);
+ }
+ this.nodePositionMap[nodeId] = sourcePosition;
+ const key = sourcePositionToStringKey(sourcePosition);
+ if (!this.positionToNodes.has(key)) {
+ this.positionToNodes.set(key, []);
+ }
+ this.positionToNodes.get(key).push(nodeId);
+ }
+ for (const [, source] of Object.entries(this.sources)) {
+ source.sourcePositions = sortUnique(source.sourcePositions,
+ sourcePositionLe, sourcePositionEq);
+ }
+ }
+
+ sourcePositionsToNodeIds(sourcePositions) {
+ const nodeIds = new Set();
+ for (const sp of sourcePositions) {
+ const key = sourcePositionToStringKey(sp);
+ const nodeIdsForPosition = this.positionToNodes.get(key);
+ if (!nodeIdsForPosition) continue;
+ for (const nodeId of nodeIdsForPosition) {
+ nodeIds.add(nodeId);
+ }
+ }
+ return nodeIds;
+ }
+
+ nodeIdsToSourcePositions(nodeIds): Array<AnyPosition> {
+ const sourcePositions = new Map();
+ for (const nodeId of nodeIds) {
+ const sp = this.nodePositionMap[nodeId];
+ const key = sourcePositionToStringKey(sp);
+ sourcePositions.set(key, sp);
+ }
+ const sourcePositionArray = [];
+ for (const sp of sourcePositions.values()) {
+ sourcePositionArray.push(sp);
+ }
+ return sourcePositionArray;
+ }
+
+ forEachSource(f: (value: Source, index: number, array: Array<Source>) => void) {
+ this.sources.forEach(f);
+ }
+
+ translateToSourceId(sourceId: number, location?: SourcePosition) {
+ for (const position of this.getInlineStack(location)) {
+ const inlining = this.inlinings[position.inliningId];
+ if (!inlining) continue;
+ if (inlining.sourceId == sourceId) {
+ return position;
+ }
+ }
+ return location;
+ }
+
+ addInliningPositions(sourcePosition: AnyPosition, locations: Array<SourcePosition>) {
+ const inlining = this.inliningsMap.get(sourcePositionToStringKey(sourcePosition));
+ if (!inlining) return;
+ const sourceId = inlining.sourceId;
+ const source = this.sources[sourceId];
+ for (const sp of source.sourcePositions) {
+ locations.push(sp);
+ this.addInliningPositions(sp, locations);
+ }
+ }
+
+ getInliningForPosition(sourcePosition: AnyPosition) {
+ return this.inliningsMap.get(sourcePositionToStringKey(sourcePosition));
+ }
+
+ getSource(sourceId: number) {
+ return this.sources[sourceId];
+ }
+
+ getSourceName(sourceId: number) {
+ const source = this.sources[sourceId];
+ return `${source.sourceName}:${source.functionName}`;
+ }
+
+ sourcePositionFor(sourceId: number, scriptOffset: number) {
+ if (!this.sources[sourceId]) {
+ return null;
+ }
+ const list = this.sources[sourceId].sourcePositions;
+ for (let i = 0; i < list.length; i++) {
+ const sourcePosition = list[i];
+ const position = sourcePosition.scriptOffset;
+ const nextPosition = list[Math.min(i + 1, list.length - 1)].scriptOffset;
+ if ((position <= scriptOffset && scriptOffset < nextPosition)) {
+ return sourcePosition;
+ }
+ }
+ return null;
+ }
+
+ sourcePositionsInRange(sourceId: number, start: number, end: number) {
+ if (!this.sources[sourceId]) return [];
+ const res = [];
+ const list = this.sources[sourceId].sourcePositions;
+ for (const sourcePosition of list) {
+ if (start <= sourcePosition.scriptOffset && sourcePosition.scriptOffset < end) {
+ res.push(sourcePosition);
+ }
+ }
+ return res;
+ }
+
+ getInlineStack(sourcePosition?: SourcePosition) {
+ if (!sourcePosition) return [];
+
+ const inliningStack = [];
+ let cur = sourcePosition;
+ while (cur && cur.inliningId != -1) {
+ inliningStack.push(cur);
+ const inlining = this.inlinings[cur.inliningId];
+ if (!inlining) {
+ break;
+ }
+ cur = inlining.inliningPosition;
+ }
+ if (cur && cur.inliningId == -1) {
+ inliningStack.push(cur);
+ }
+ return inliningStack;
+ }
+
+ recordOrigins(phase: GraphPhase) {
+ if (phase.type != "graph") return;
+ for (const node of phase.data.nodes) {
+ phase.highestNodeId = Math.max(phase.highestNodeId, node.id);
+ if (node.origin != undefined &&
+ node.origin.bytecodePosition != undefined) {
+ const position = { bytecodePosition: node.origin.bytecodePosition };
+ this.nodePositionMap[node.id] = position;
+ const key = sourcePositionToStringKey(position);
+ if (!this.positionToNodes.has(key)) {
+ this.positionToNodes.set(key, []);
+ }
+ const A = this.positionToNodes.get(key);
+ if (!A.includes(node.id)) A.push(`${node.id}`);
+ }
+
+ // Backwards compatibility.
+ if (typeof node.pos === "number") {
+ node.sourcePosition = { scriptOffset: node.pos, inliningId: -1 };
+ }
+ }
+ }
+
+ readNodeIdToInstructionRange(nodeIdToInstructionRange) {
+ for (const [nodeId, range] of Object.entries<[number, number]>(nodeIdToInstructionRange)) {
+ this.nodeIdToInstructionRange[nodeId] = range;
+ }
+ }
+
+ readBlockIdToInstructionRange(blockIdToInstructionRange) {
+ for (const [blockId, range] of Object.entries<[number, number]>(blockIdToInstructionRange)) {
+ this.blockIdToInstructionRange[blockId] = range;
+ }
+ }
+
+ getInstruction(nodeId: number): [number, number] {
+ const X = this.nodeIdToInstructionRange[nodeId];
+ if (X === undefined) return [-1, -1];
+ return X;
+ }
+
+ getInstructionRangeForBlock(blockId: number): [number, number] {
+ const X = this.blockIdToInstructionRange[blockId];
+ if (X === undefined) return [-1, -1];
+ return X;
+ }
+
+ readInstructionOffsetToPCOffset(instructionToPCOffset) {
+ for (const [instruction, offset] of Object.entries<number>(instructionToPCOffset)) {
+ this.instructionToPCOffset[instruction] = offset;
+ if (!this.pcOffsetToInstructions.has(offset)) {
+ this.pcOffsetToInstructions.set(offset, []);
+ }
+ this.pcOffsetToInstructions.get(offset).push(Number(instruction));
+ }
+ this.pcOffsets = Array.from(this.pcOffsetToInstructions.keys()).sort((a, b) => b - a);
+ }
+
+ hasPCOffsets() {
+ return this.pcOffsetToInstructions.size > 0;
+ }
+
+ getKeyPcOffset(offset: number): number {
+ if (this.pcOffsets.length === 0) return -1;
+ for (const key of this.pcOffsets) {
+ if (key <= offset) {
+ return key;
+ }
+ }
+ return -1;
+ }
+
+ instructionRangeToKeyPcOffsets([start, end]: [number, number]) {
+ if (start == end) return [this.instructionToPCOffset[start]];
+ return this.instructionToPCOffset.slice(start, end);
+ }
+
+ instructionsToKeyPcOffsets(instructionIds: Iterable<number>) {
+ const keyPcOffsets = [];
+ for (const instructionId of instructionIds) {
+ keyPcOffsets.push(this.instructionToPCOffset[instructionId]);
+ }
+ return keyPcOffsets;
+ }
+
+ nodesToKeyPcOffsets(nodes) {
+ let offsets = [];
+ for (const node of nodes) {
+ const range = this.nodeIdToInstructionRange[node];
+ if (!range) continue;
+ offsets = offsets.concat(this.instructionRangeToKeyPcOffsets(range));
+ }
+ return offsets;
+ }
+
+ nodesForPCOffset(offset: number): [Array<string>, Array<string>] {
+ if (this.pcOffsets.length === 0) return [[], []];
+ for (const key of this.pcOffsets) {
+ if (key <= offset) {
+ const instrs = this.pcOffsetToInstructions.get(key);
+ const nodes = [];
+ const blocks = [];
+ for (const instr of instrs) {
+ for (const [nodeId, range] of this.nodeIdToInstructionRange.entries()) {
+ if (!range) continue;
+ const [start, end] = range;
+ if (start == end && instr == start) {
+ nodes.push("" + nodeId);
+ }
+ if (start <= instr && instr < end) {
+ nodes.push("" + nodeId);
+ }
+ }
+ }
+ return [nodes, blocks];
+ }
+ }
+ return [[], []];
+ }
+
+ parsePhases(phases) {
+ const nodeLabelMap = [];
+ for (const [, phase] of Object.entries<Phase>(phases)) {
+ switch (phase.type) {
+ case 'disassembly':
+ this.disassemblyPhase = phase;
+ break;
+ case 'schedule':
+ this.phaseNames.set(phase.name, this.phases.length);
+ this.phases.push(this.parseSchedule(phase));
+ break;
+ case 'sequence':
+ this.phaseNames.set(phase.name, this.phases.length);
+ this.phases.push(this.parseSequence(phase));
+ break;
+ case 'instructions':
+ if (phase.nodeIdToInstructionRange) {
+ this.readNodeIdToInstructionRange(phase.nodeIdToInstructionRange);
+ }
+ if (phase.blockIdtoInstructionRange) {
+ this.readBlockIdToInstructionRange(phase.blockIdtoInstructionRange);
+ }
+ if (phase.instructionOffsetToPCOffset) {
+ this.readInstructionOffsetToPCOffset(phase.instructionOffsetToPCOffset);
+ }
+ break;
+ case 'graph':
+ const graphPhase: GraphPhase = Object.assign(phase, { highestNodeId: 0 });
+ this.phaseNames.set(graphPhase.name, this.phases.length);
+ this.phases.push(graphPhase);
+ this.recordOrigins(graphPhase);
+ this.internNodeLabels(graphPhase, nodeLabelMap);
+ graphPhase.nodeLabelMap = nodeLabelMap.slice();
+ break;
+ default:
+ throw "Unsupported phase type";
+ }
+ }
+ }
+
+ internNodeLabels(phase: GraphPhase, nodeLabelMap: Array<NodeLabel>) {
+ for (const n of phase.data.nodes) {
+ const label = new NodeLabel(n.id, n.label, n.title, n.live,
+ n.properties, n.sourcePosition, n.origin, n.opcode, n.control,
+ n.opinfo, n.type);
+ const previous = nodeLabelMap[label.id];
+ if (!label.equals(previous)) {
+ if (previous != undefined) {
+ label.setInplaceUpdatePhase(phase.name);
+ }
+ nodeLabelMap[label.id] = label;
+ }
+ n.nodeLabel = nodeLabelMap[label.id];
+ }
+ }
+
+ repairPhaseId(anyPhaseId) {
+ return Math.max(0, Math.min(anyPhaseId | 0, this.phases.length - 1));
+ }
+
+ getPhase(phaseId: number) {
+ return this.phases[phaseId];
+ }
+
+ getPhaseIdByName(phaseName: string) {
+ return this.phaseNames.get(phaseName);
+ }
+
+ forEachPhase(f: (value: Phase, index: number, array: Array<Phase>) => void) {
+ this.phases.forEach(f);
+ }
+
+ addAnyPositionToLine(lineNumber: number | string, sourcePosition: AnyPosition) {
+ const lineNumberString = anyToString(lineNumber);
+ if (!this.lineToSourcePositions.has(lineNumberString)) {
+ this.lineToSourcePositions.set(lineNumberString, []);
+ }
+ const A = this.lineToSourcePositions.get(lineNumberString);
+ if (!A.includes(sourcePosition)) A.push(sourcePosition);
+ }
+
+ setSourceLineToBytecodePosition(sourceLineToBytecodePosition: Array<number> | undefined) {
+ if (!sourceLineToBytecodePosition) return;
+ sourceLineToBytecodePosition.forEach((pos, i) => {
+ this.addAnyPositionToLine(i, { bytecodePosition: pos });
+ });
+ }
+
+ linetoSourcePositions(lineNumber: number | string) {
+ const positions = this.lineToSourcePositions.get(anyToString(lineNumber));
+ if (positions === undefined) return [];
+ return positions;
+ }
+
+ parseSchedule(phase) {
+ function createNode(state: any, match) {
+ let inputs = [];
+ if (match.groups.args) {
+ const nodeIdsString = match.groups.args.replace(/\s/g, '');
+ const nodeIdStrings = nodeIdsString.split(',');
+ inputs = nodeIdStrings.map(n => Number.parseInt(n, 10));
+ }
+ const node = {
+ id: Number.parseInt(match.groups.id, 10),
+ label: match.groups.label,
+ inputs: inputs
+ };
+ if (match.groups.blocks) {
+ const nodeIdsString = match.groups.blocks.replace(/\s/g, '').replace(/B/g, '');
+ const nodeIdStrings = nodeIdsString.split(',');
+ const successors = nodeIdStrings.map(n => Number.parseInt(n, 10));
+ state.currentBlock.succ = successors;
+ }
+ state.nodes[node.id] = node;
+ state.currentBlock.nodes.push(node);
+ }
+ function createBlock(state, match) {
+ let predecessors = [];
+ if (match.groups.in) {
+ const blockIdsString = match.groups.in.replace(/\s/g, '').replace(/B/g, '');
+ const blockIdStrings = blockIdsString.split(',');
+ predecessors = blockIdStrings.map(n => Number.parseInt(n, 10));
+ }
+ const block = {
+ id: Number.parseInt(match.groups.id, 10),
+ isDeferred: match.groups.deferred != undefined,
+ pred: predecessors.sort(),
+ succ: [],
+ nodes: []
+ };
+ state.blocks[block.id] = block;
+ state.currentBlock = block;
+ }
+ function setGotoSuccessor(state, match) {
+ state.currentBlock.succ = [Number.parseInt(match.groups.successor.replace(/\s/g, ''), 10)];
+ }
+ const rules = [
+ {
+ lineRegexps:
+ [/^\s*(?<id>\d+):\ (?<label>.*)\((?<args>.*)\)$/,
+ /^\s*(?<id>\d+):\ (?<label>.*)\((?<args>.*)\)\ ->\ (?<blocks>.*)$/,
+ /^\s*(?<id>\d+):\ (?<label>.*)$/
+ ],
+ process: createNode
+ },
+ {
+ lineRegexps:
+ [/^\s*---\s*BLOCK\ B(?<id>\d+)\s*(?<deferred>\(deferred\))?(\ <-\ )?(?<in>[^-]*)?\ ---$/
+ ],
+ process: createBlock
+ },
+ {
+ lineRegexps:
+ [/^\s*Goto\s*->\s*B(?<successor>\d+)\s*$/
+ ],
+ process: setGotoSuccessor
+ }
+ ];
+
+ const lines = phase.data.split(/[\n]/);
+ const state = { currentBlock: undefined, blocks: [], nodes: [] };
+
+ nextLine:
+ for (const line of lines) {
+ for (const rule of rules) {
+ for (const lineRegexp of rule.lineRegexps) {
+ const match = line.match(lineRegexp);
+ if (match) {
+ rule.process(state, match);
+ continue nextLine;
+ }
+ }
+ }
+ console.log("Warning: unmatched schedule line \"" + line + "\"");
+ }
+ phase.schedule = state;
+ return phase;
+ }
+ parseSequence(phase) {
+ phase.sequence = { blocks: phase.blocks };
+ return phase;
+ }
+}
diff --git a/src/v8/tools/turbolizer/src/tabs.ts b/src/v8/tools/turbolizer/src/tabs.ts
new file mode 100644
index 0000000..0416b9e
--- /dev/null
+++ b/src/v8/tools/turbolizer/src/tabs.ts
@@ -0,0 +1,114 @@
+
+export class Tabs {
+ private container: HTMLElement;
+ private tabBar: HTMLElement;
+ private nextTabId: number;
+
+ private mkTabBar(container: HTMLElement) {
+ container.classList.add("nav-tabs-container");
+ this.tabBar = document.createElement("ul");
+ this.tabBar.id = `tab-bar-${container.id}`;
+ this.tabBar.className = "nav-tabs";
+ this.tabBar.ondrop = this.tabBarOnDrop.bind(this);
+ this.tabBar.ondragover = this.tabBarOnDragover.bind(this);
+ this.tabBar.onclick = this.tabBarOnClick.bind(this);
+
+ const defaultDiv = document.createElement("div");
+ defaultDiv.className = "tab-content tab-default";
+ defaultDiv.id = `tab-content-${container.id}-default`;
+ container.insertBefore(defaultDiv, container.firstChild);
+ container.insertBefore(this.tabBar, container.firstChild);
+ }
+
+ constructor(container: HTMLElement) {
+ this.container = container;
+ this.nextTabId = 0;
+ this.mkTabBar(container);
+ }
+
+ activateTab(tab: HTMLLIElement) {
+ if (typeof tab.dataset.divid !== "string") return;
+ for (const li of this.tabBar.querySelectorAll<HTMLLIElement>("li.active")) {
+ li.classList.remove("active");
+ this.showTab(li, false);
+ }
+ tab.classList.add("active");
+ this.showTab(tab, true);
+ }
+
+ clearTabsAndContent() {
+ for (const tab of this.tabBar.querySelectorAll(".nav-tabs > li")) {
+ if (!(tab instanceof HTMLLIElement)) continue;
+ if (tab.classList.contains("persistent-tab")) continue;
+ const tabDiv = document.getElementById(tab.dataset.divid);
+ tabDiv.parentNode.removeChild(tabDiv);
+ tab.parentNode.removeChild(tab);
+ }
+ }
+
+ private showTab(li: HTMLElement, show: boolean = true) {
+ const tabDiv = document.getElementById(li.dataset.divid);
+ tabDiv.style.display = show ? "block" : "none";
+ }
+
+ public addTab(caption: string): HTMLLIElement {
+ const newTab = document.createElement("li");
+ newTab.innerHTML = caption;
+ newTab.id = `tab-header-${this.container.id}-${this.nextTabId++}`;
+ const lastTab = this.tabBar.querySelector("li.last-tab");
+ this.tabBar.insertBefore(newTab, lastTab);
+ return newTab;
+ }
+
+ public addTabAndContent(caption: string): [HTMLLIElement, HTMLDivElement] {
+ const contentDiv = document.createElement("div");
+ contentDiv.className = "tab-content tab-default";
+ contentDiv.id = `tab-content-${this.container.id}-${this.nextTabId++}`;
+ contentDiv.style.display = "none";
+ this.container.appendChild(contentDiv);
+
+ const newTab = this.addTab(caption);
+ newTab.dataset.divid = contentDiv.id;
+ newTab.draggable = true;
+ newTab.ondragstart = this.tabOnDragStart.bind(this);
+ const lastTab = this.tabBar.querySelector("li.last-tab");
+ this.tabBar.insertBefore(newTab, lastTab);
+ return [newTab, contentDiv];
+ }
+
+ private moveTabDiv(tab: HTMLLIElement) {
+ const tabDiv = document.getElementById(tab.dataset.divid);
+ tabDiv.style.display = "none";
+ tab.classList.remove("active");
+ this.tabBar.parentNode.appendChild(tabDiv);
+ }
+
+ private tabBarOnDrop(e: DragEvent) {
+ if (!(e.target instanceof HTMLElement)) return;
+ e.preventDefault();
+ const tabId = e.dataTransfer.getData("text");
+ const tab = document.getElementById(tabId) as HTMLLIElement;
+ if (tab.parentNode != this.tabBar) {
+ this.moveTabDiv(tab);
+ }
+ const dropTab =
+ e.target.parentNode == this.tabBar
+ ? e.target : this.tabBar.querySelector("li.last-tab");
+ this.tabBar.insertBefore(tab, dropTab);
+ this.activateTab(tab);
+ }
+
+ private tabBarOnDragover(e) {
+ e.preventDefault();
+ }
+
+ private tabOnDragStart(e: DragEvent) {
+ if (!(e.target instanceof HTMLElement)) return;
+ e.dataTransfer.setData("text", e.target.id);
+ }
+
+ private tabBarOnClick(e: MouseEvent) {
+ const li = e.target as HTMLLIElement;
+ this.activateTab(li);
+ }
+}
diff --git a/src/v8/tools/turbolizer/src/text-view.ts b/src/v8/tools/turbolizer/src/text-view.ts
new file mode 100644
index 0000000..41a06ea
--- /dev/null
+++ b/src/v8/tools/turbolizer/src/text-view.ts
@@ -0,0 +1,252 @@
+// Copyright 2015 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import { PhaseView } from "../src/view";
+import { anyToString, ViewElements, isIterable } from "../src/util";
+import { MySelection } from "../src/selection";
+import { SourceResolver } from "./source-resolver";
+import { SelectionBroker } from "./selection-broker";
+import { NodeSelectionHandler, BlockSelectionHandler } from "./selection-handler";
+
+export abstract class TextView extends PhaseView {
+ selectionHandler: NodeSelectionHandler;
+ blockSelectionHandler: BlockSelectionHandler;
+ selection: MySelection;
+ blockSelection: MySelection;
+ textListNode: HTMLUListElement;
+ nodeIdToHtmlElementsMap: Map<string, Array<HTMLElement>>;
+ blockIdToHtmlElementsMap: Map<string, Array<HTMLElement>>;
+ blockIdtoNodeIds: Map<string, Array<string>>;
+ nodeIdToBlockId: Array<string>;
+ patterns: any;
+ sourceResolver: SourceResolver;
+ broker: SelectionBroker;
+
+ constructor(id, broker) {
+ super(id);
+ const view = this;
+ view.textListNode = view.divNode.getElementsByTagName('ul')[0];
+ view.patterns = null;
+ view.nodeIdToHtmlElementsMap = new Map();
+ view.blockIdToHtmlElementsMap = new Map();
+ view.blockIdtoNodeIds = new Map();
+ view.nodeIdToBlockId = [];
+ view.selection = new MySelection(anyToString);
+ view.blockSelection = new MySelection(anyToString);
+ view.broker = broker;
+ view.sourceResolver = broker.sourceResolver;
+ const selectionHandler = {
+ clear: function () {
+ view.selection.clear();
+ view.updateSelection();
+ broker.broadcastClear(selectionHandler);
+ },
+ select: function (nodeIds, selected) {
+ view.selection.select(nodeIds, selected);
+ view.updateSelection();
+ broker.broadcastNodeSelect(selectionHandler, view.selection.selectedKeys(), selected);
+ },
+ brokeredNodeSelect: function (nodeIds, selected) {
+ const firstSelect = view.blockSelection.isEmpty();
+ view.selection.select(nodeIds, selected);
+ view.updateSelection(firstSelect);
+ },
+ brokeredClear: function () {
+ view.selection.clear();
+ view.updateSelection();
+ }
+ };
+ this.selectionHandler = selectionHandler;
+ broker.addNodeHandler(selectionHandler);
+ view.divNode.addEventListener('click', e => {
+ if (!e.shiftKey) {
+ view.selectionHandler.clear();
+ }
+ e.stopPropagation();
+ });
+ const blockSelectionHandler = {
+ clear: function () {
+ view.blockSelection.clear();
+ view.updateSelection();
+ broker.broadcastClear(blockSelectionHandler);
+ },
+ select: function (blockIds, selected) {
+ view.blockSelection.select(blockIds, selected);
+ view.updateSelection();
+ broker.broadcastBlockSelect(blockSelectionHandler, blockIds, selected);
+ },
+ brokeredBlockSelect: function (blockIds, selected) {
+ const firstSelect = view.blockSelection.isEmpty();
+ view.blockSelection.select(blockIds, selected);
+ view.updateSelection(firstSelect);
+ },
+ brokeredClear: function () {
+ view.blockSelection.clear();
+ view.updateSelection();
+ }
+ };
+ this.blockSelectionHandler = blockSelectionHandler;
+ broker.addBlockHandler(blockSelectionHandler);
+ }
+
+ addHtmlElementForNodeId(anyNodeId: any, htmlElement: HTMLElement) {
+ const nodeId = anyToString(anyNodeId);
+ if (!this.nodeIdToHtmlElementsMap.has(nodeId)) {
+ this.nodeIdToHtmlElementsMap.set(nodeId, []);
+ }
+ this.nodeIdToHtmlElementsMap.get(nodeId).push(htmlElement);
+ }
+
+ addHtmlElementForBlockId(anyBlockId, htmlElement) {
+ const blockId = anyToString(anyBlockId);
+ if (!this.blockIdToHtmlElementsMap.has(blockId)) {
+ this.blockIdToHtmlElementsMap.set(blockId, []);
+ }
+ this.blockIdToHtmlElementsMap.get(blockId).push(htmlElement);
+ }
+
+ addNodeIdToBlockId(anyNodeId, anyBlockId) {
+ const blockId = anyToString(anyBlockId);
+ if (!this.blockIdtoNodeIds.has(blockId)) {
+ this.blockIdtoNodeIds.set(blockId, []);
+ }
+ this.blockIdtoNodeIds.get(blockId).push(anyToString(anyNodeId));
+ this.nodeIdToBlockId[anyNodeId] = blockId;
+ }
+
+ blockIdsForNodeIds(nodeIds) {
+ const blockIds = [];
+ for (const nodeId of nodeIds) {
+ const blockId = this.nodeIdToBlockId[nodeId];
+ if (blockId == undefined) continue;
+ blockIds.push(blockId);
+ }
+ return blockIds;
+ }
+
+ updateSelection(scrollIntoView: boolean = false) {
+ if (this.divNode.parentNode == null) return;
+ const mkVisible = new ViewElements(this.divNode.parentNode as HTMLElement);
+ const view = this;
+ for (const [blockId, elements] of this.blockIdToHtmlElementsMap.entries()) {
+ const isSelected = view.blockSelection.isSelected(blockId);
+ for (const element of elements) {
+ mkVisible.consider(element, isSelected);
+ element.classList.toggle("selected", isSelected);
+ }
+ }
+ const elementsToSelect = view.divNode.querySelectorAll(`[data-pc-offset]`);
+ for (const el of elementsToSelect) {
+ el.classList.toggle("selected", false);
+ }
+ for (const key of this.nodeIdToHtmlElementsMap.keys()) {
+ for (const element of this.nodeIdToHtmlElementsMap.get(key)) {
+ element.classList.toggle("selected", false);
+ }
+ }
+ for (const nodeId of view.selection.selectedKeys()) {
+ const elements = this.nodeIdToHtmlElementsMap.get(nodeId);
+ if (!elements) continue;
+ for (const element of elements) {
+ mkVisible.consider(element, true);
+ element.classList.toggle("selected", true);
+ }
+ }
+ mkVisible.apply(scrollIntoView);
+ }
+
+ setPatterns(patterns) {
+ this.patterns = patterns;
+ }
+
+ clearText() {
+ while (this.textListNode.firstChild) {
+ this.textListNode.removeChild(this.textListNode.firstChild);
+ }
+ }
+
+ createFragment(text, style) {
+ const fragment = document.createElement("SPAN");
+
+ if (typeof style.associateData == 'function') {
+ style.associateData(text, fragment);
+ } else {
+ if (style.css != undefined) {
+ const css = isIterable(style.css) ? style.css : [style.css];
+ for (const cls of css) {
+ fragment.classList.add(cls);
+ }
+ }
+ fragment.innerText = text;
+ }
+
+ return fragment;
+ }
+
+ processLine(line) {
+ const view = this;
+ const result = [];
+ let patternSet = 0;
+ while (true) {
+ const beforeLine = line;
+ for (const pattern of view.patterns[patternSet]) {
+ const matches = line.match(pattern[0]);
+ if (matches != null) {
+ if (matches[0] != '') {
+ const style = pattern[1] != null ? pattern[1] : {};
+ const text = matches[0];
+ if (text != '') {
+ const fragment = view.createFragment(matches[0], style);
+ result.push(fragment);
+ }
+ line = line.substr(matches[0].length);
+ }
+ let nextPatternSet = patternSet;
+ if (pattern.length > 2) {
+ nextPatternSet = pattern[2];
+ }
+ if (line == "") {
+ if (nextPatternSet != -1) {
+ throw ("illegal parsing state in text-view in patternSet" + patternSet);
+ }
+ return result;
+ }
+ patternSet = nextPatternSet;
+ break;
+ }
+ }
+ if (beforeLine == line) {
+ throw ("input not consumed in text-view in patternSet" + patternSet);
+ }
+ }
+ }
+
+ processText(text) {
+ const view = this;
+ const textLines = text.split(/[\n]/);
+ let lineNo = 0;
+ for (const line of textLines) {
+ const li = document.createElement("LI");
+ li.className = "nolinenums";
+ li.dataset.lineNo = "" + lineNo++;
+ const fragments = view.processLine(line);
+ for (const fragment of fragments) {
+ li.appendChild(fragment);
+ }
+ view.textListNode.appendChild(li);
+ }
+ }
+
+ initializeContent(data, rememberedSelection) {
+ this.clearText();
+ this.processText(data);
+ this.show();
+ }
+
+ public onresize(): void {}
+
+ isScrollable() {
+ return true;
+ }
+}
diff --git a/src/v8/tools/turbolizer/src/turbo-visualizer.ts b/src/v8/tools/turbolizer/src/turbo-visualizer.ts
new file mode 100644
index 0000000..87924b7
--- /dev/null
+++ b/src/v8/tools/turbolizer/src/turbo-visualizer.ts
@@ -0,0 +1,148 @@
+// Copyright 2017 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 { SourceResolver } from "../src/source-resolver";
+import { SelectionBroker } from "../src/selection-broker";
+import { DisassemblyView } from "../src/disassembly-view";
+import { GraphMultiView } from "../src/graphmultiview";
+import { CodeMode, CodeView } from "../src/code-view";
+import { Tabs } from "../src/tabs";
+import { Resizer } from "../src/resizer";
+import * as C from "../src/constants";
+import { InfoView } from "./info-view";
+
+window.onload = function () {
+ let multiview: GraphMultiView = null;
+ let disassemblyView: DisassemblyView = null;
+ let sourceViews: Array<CodeView> = [];
+ let selectionBroker: SelectionBroker = null;
+ let sourceResolver: SourceResolver = null;
+ const resizer = new Resizer(panesUpdatedCallback, 100);
+ const sourceTabsContainer = document.getElementById(C.SOURCE_PANE_ID);
+ const sourceTabs = new Tabs(sourceTabsContainer);
+ sourceTabs.addTab("+").classList.add("last-tab", "persistent-tab");
+ const disassemblyTabsContainer = document.getElementById(C.GENERATED_PANE_ID);
+ const disassemblyTabs = new Tabs(disassemblyTabsContainer);
+ disassemblyTabs.addTab("+").classList.add("last-tab", "persistent-tab");
+ const [infoTab, infoContainer] = sourceTabs.addTabAndContent("Info");
+ infoTab.classList.add("persistent-tab");
+ infoContainer.classList.add("viewpane", "scrollable");
+ const infoView = new InfoView(infoContainer);
+ infoView.show();
+ sourceTabs.activateTab(infoTab);
+
+ function panesUpdatedCallback() {
+ if (multiview) multiview.onresize();
+ }
+
+ function loadFile(txtRes: string) {
+ sourceTabs.clearTabsAndContent();
+ disassemblyTabs.clearTabsAndContent();
+ // If the JSON isn't properly terminated, assume compiler crashed and
+ // add best-guess empty termination
+ if (txtRes[txtRes.length - 2] == ',') {
+ txtRes += '{"name":"disassembly","type":"disassembly","data":""}]}';
+ }
+ try {
+ sourceViews.forEach(sv => sv.hide());
+ if (multiview) multiview.hide();
+ multiview = null;
+ if (disassemblyView) disassemblyView.hide();
+ sourceViews = [];
+ sourceResolver = new SourceResolver();
+ selectionBroker = new SelectionBroker(sourceResolver);
+
+ const jsonObj = JSON.parse(txtRes);
+
+ let fnc = null;
+ // Backwards compatibility.
+ if (typeof jsonObj.function == 'string') {
+ fnc = {
+ functionName: fnc,
+ sourceId: -1,
+ startPosition: jsonObj.sourcePosition,
+ endPosition: jsonObj.sourcePosition + jsonObj.source.length,
+ sourceText: jsonObj.source,
+ backwardsCompatibility: true
+ };
+ } else {
+ fnc = Object.assign(jsonObj.function, { backwardsCompatibility: false });
+ }
+
+ sourceResolver.setInlinings(jsonObj.inlinings);
+ sourceResolver.setSourceLineToBytecodePosition(jsonObj.sourceLineToBytecodePosition);
+ sourceResolver.setSources(jsonObj.sources, fnc);
+ sourceResolver.setNodePositionMap(jsonObj.nodePositions);
+ sourceResolver.parsePhases(jsonObj.phases);
+
+ const [sourceTab, sourceContainer] = sourceTabs.addTabAndContent("Source");
+ sourceContainer.classList.add("viewpane", "scrollable");
+ sourceTabs.activateTab(sourceTab);
+ const sourceView = new CodeView(sourceContainer, selectionBroker, sourceResolver, fnc, CodeMode.MAIN_SOURCE);
+ sourceView.show();
+ sourceViews.push(sourceView);
+
+ sourceResolver.forEachSource(source => {
+ const sourceView = new CodeView(sourceContainer, selectionBroker, sourceResolver, source, CodeMode.INLINED_SOURCE);
+ sourceView.show();
+ sourceViews.push(sourceView);
+ });
+
+ const [disassemblyTab, disassemblyContainer] = disassemblyTabs.addTabAndContent("Disassembly");
+ disassemblyContainer.classList.add("viewpane", "scrollable");
+ disassemblyTabs.activateTab(disassemblyTab);
+ disassemblyView = new DisassemblyView(disassemblyContainer, selectionBroker);
+ disassemblyView.initializeCode(fnc.sourceText);
+ if (sourceResolver.disassemblyPhase) {
+ disassemblyView.initializePerfProfile(jsonObj.eventCounts);
+ disassemblyView.showContent(sourceResolver.disassemblyPhase.data);
+ disassemblyView.show();
+ }
+
+ multiview = new GraphMultiView(C.INTERMEDIATE_PANE_ID, selectionBroker, sourceResolver);
+ multiview.show();
+ } catch (err) {
+ if (window.confirm("Error: Exception during load of TurboFan JSON file:\n" +
+ "error: " + err.message + "\nDo you want to clear session storage?")) {
+ window.sessionStorage.clear();
+ }
+ return;
+ }
+ }
+
+ function initializeUploadHandlers() {
+ // The <input> form #upload-helper with type file can't be a picture.
+ // We hence keep it hidden, and forward the click from the picture
+ // button #upload.
+ document.getElementById("upload").addEventListener("click", e => {
+ document.getElementById("upload-helper").click();
+ e.stopPropagation();
+ });
+ document.getElementById("upload-helper").addEventListener("change",
+ function (this: HTMLInputElement) {
+ const uploadFile = this.files && this.files[0];
+ if (uploadFile) {
+ const filereader = new FileReader();
+ filereader.onload = () => {
+ const txtRes = filereader.result;
+ if (typeof txtRes == 'string') {
+ loadFile(txtRes);
+ }
+ };
+ filereader.readAsText(uploadFile);
+ }
+ }
+ );
+ window.addEventListener("keydown", (e: KeyboardEvent) => {
+ if (e.keyCode == 76 && e.ctrlKey) { // CTRL + L
+ document.getElementById("upload-helper").click();
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ });
+ }
+
+ initializeUploadHandlers();
+ resizer.updatePanes();
+};
diff --git a/src/v8/tools/turbolizer/src/util.ts b/src/v8/tools/turbolizer/src/util.ts
new file mode 100644
index 0000000..d9c8dcd
--- /dev/null
+++ b/src/v8/tools/turbolizer/src/util.ts
@@ -0,0 +1,93 @@
+// Copyright 2015 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+export function anyToString(x: any): string {
+ return "" + x;
+}
+
+function computeScrollTop(container, element) {
+ const height = container.offsetHeight;
+ const margin = Math.floor(height / 4);
+ const pos = element.offsetTop;
+ const currentScrollTop = container.scrollTop;
+ if (pos < currentScrollTop + margin) {
+ return Math.max(0, pos - margin);
+ } else if (pos > (currentScrollTop + 3 * margin)) {
+ return Math.max(0, pos - 3 * margin);
+ }
+ return pos;
+}
+
+export class ViewElements {
+ container: HTMLElement;
+ scrollTop: number;
+
+ constructor(container: HTMLElement) {
+ this.container = container;
+ this.scrollTop = undefined;
+ }
+
+ consider(element, doConsider) {
+ if (!doConsider) return;
+ const newScrollTop = computeScrollTop(this.container, element);
+ if (isNaN(newScrollTop)) {
+ console.log("NOO");
+ }
+ if (this.scrollTop === undefined) {
+ this.scrollTop = newScrollTop;
+ } else {
+ this.scrollTop = Math.min(this.scrollTop, newScrollTop);
+ }
+ }
+
+ apply(doApply) {
+ if (!doApply || this.scrollTop === undefined) return;
+ this.container.scrollTop = this.scrollTop;
+ }
+}
+
+export function sortUnique<T>(arr: Array<T>, f: (a: T, b: T) => number, equal: (a: T, b: T) => boolean) {
+ if (arr.length == 0) return arr;
+ arr = arr.sort(f);
+ const ret = [arr[0]];
+ for (let i = 1; i < arr.length; i++) {
+ if (!equal(arr[i - 1], arr[i])) {
+ ret.push(arr[i]);
+ }
+ }
+ return ret;
+}
+
+// Partial application without binding the receiver
+export function partial(f: any, ...arguments1: Array<any>) {
+ return function (this: any, ...arguments2: Array<any>) {
+ f.apply(this, [...arguments1, ...arguments2]);
+ };
+}
+
+export function isIterable(obj: any): obj is Iterable<any> {
+ return obj != null && obj != undefined
+ && typeof obj != 'string' && typeof obj[Symbol.iterator] === 'function';
+}
+
+export function alignUp(raw: number, multiple: number): number {
+ return Math.floor((raw + multiple - 1) / multiple) * multiple;
+}
+
+export function measureText(text: string) {
+ const textMeasure = document.getElementById('text-measure');
+ if (textMeasure instanceof SVGTSpanElement) {
+ textMeasure.textContent = text;
+ return {
+ width: textMeasure.getBBox().width,
+ height: textMeasure.getBBox().height,
+ };
+ }
+ return { width: 0, height: 0 };
+}
+
+// Interpolate between the given start and end values by a fraction of val/max.
+export function interpolate(val: number, max: number, start: number, end: number) {
+ return start + (end - start) * (val / max);
+}
diff --git a/src/v8/tools/turbolizer/src/view.ts b/src/v8/tools/turbolizer/src/view.ts
new file mode 100644
index 0000000..a8bb125
--- /dev/null
+++ b/src/v8/tools/turbolizer/src/view.ts
@@ -0,0 +1,37 @@
+// Copyright 2015 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+export abstract class View {
+ protected container: HTMLElement;
+ protected divNode: HTMLElement;
+ protected abstract createViewElement(): HTMLElement;
+
+ constructor(idOrContainer: string | HTMLElement) {
+ this.container = typeof idOrContainer == "string" ? document.getElementById(idOrContainer) : idOrContainer;
+ this.divNode = this.createViewElement();
+ }
+
+ public show(): void {
+ this.container.appendChild(this.divNode);
+ }
+
+ public hide(): void {
+ this.container.removeChild(this.divNode);
+ }
+}
+
+export abstract class PhaseView extends View {
+ public abstract initializeContent(data: any, rememberedSelection: Set<any>): void;
+ public abstract detachSelection(): Set<string>;
+ public abstract onresize(): void;
+ public abstract searchInputAction(searchInput: HTMLInputElement, e: Event, onlyVisible: boolean): void;
+
+ constructor(idOrContainer: string | HTMLElement) {
+ super(idOrContainer);
+ }
+
+ public isScrollable(): boolean {
+ return false;
+ }
+}
diff --git a/src/v8/tools/turbolizer/tabs.css b/src/v8/tools/turbolizer/tabs.css
new file mode 100644
index 0000000..54dba72
--- /dev/null
+++ b/src/v8/tools/turbolizer/tabs.css
@@ -0,0 +1,55 @@
+.content {
+ display: grid;
+ grid-template-areas: "tabs" "window";
+ grid-template-columns: 1fr;
+ grid-template-rows: auto 1fr;
+ min-height: calc(100vh);
+}
+
+.nav-tabs-container {
+ grid-area: tabs;
+ padding: 0px;
+ background-color: #999999;
+ border-bottom: 4px solid #CCCCCC;
+}
+
+.tab-content {
+ grid-area: window;
+ background-color: white;
+ padding: 0px;
+ display:none;
+}
+
+.tab-content.tab-default {
+ display: block;
+}
+
+ul.nav-tabs {
+ padding: 0px;
+ margin: 0px;
+ overflow: auto;
+ display: table-row;
+ min-height: 2ex;
+}
+
+.nav-tabs li {
+ display: inline-block;
+ padding-left: 10px;
+ padding-right: 10px;
+ padding-top: 4px;
+ padding-bottom: 4px;
+ min-width: 20px;
+ text-decoration: none;
+ color: black;
+ text-align: center;
+ user-select: none;
+ cursor: pointer;
+}
+
+.nav-tabs li:hover {
+ background-color: #EEEEEE;
+}
+
+.nav-tabs li.active {
+ background-color: #CCCCCC;
+}
\ No newline at end of file
diff --git a/src/v8/tools/turbolizer/test/source-resolver-test.ts b/src/v8/tools/turbolizer/test/source-resolver-test.ts
new file mode 100644
index 0000000..38d6745
--- /dev/null
+++ b/src/v8/tools/turbolizer/test/source-resolver-test.ts
@@ -0,0 +1,10 @@
+import { SourceResolver } from '../src/source-resolver';
+import { expect } from 'chai';
+import { describe, it } from 'mocha';
+
+describe('SourceResolver', () => {
+ it('should be constructible', () => {
+ const a: SourceResolver = new SourceResolver();
+ expect(a.sources.length).to.equal(0);
+ });
+});
diff --git a/src/v8/tools/turbolizer/tsconfig.json b/src/v8/tools/turbolizer/tsconfig.json
new file mode 100644
index 0000000..cd036ac
--- /dev/null
+++ b/src/v8/tools/turbolizer/tsconfig.json
@@ -0,0 +1,39 @@
+{
+ "compilerOptions": {
+ "outDir": "build/",
+ "allowJs": false,
+ "target": "es2018",
+ "module": "es2015",
+ "sourceMap": true,
+ "experimentalDecorators": true,
+ "emitDecoratorMetadata": true,
+ "moduleResolution": "node",
+ "noUnusedLocals": true,
+ "noImplicitReturns": true,
+ "noImplicitThis": true,
+ "lib": ["dom", "es6", "dom.iterable", "scripthost", "es2018"]
+ },
+ "files": [
+ "src/util.ts",
+ "src/node.ts",
+ "src/edge.ts",
+ "src/graph.ts",
+ "src/node-label.ts",
+ "src/source-resolver.ts",
+ "src/selection.ts",
+ "src/selection-broker.ts",
+ "src/selection-handler.ts",
+ "src/constants.ts",
+ "src/view.ts",
+ "src/text-view.ts",
+ "src/code-view.ts",
+ "src/graph-layout.ts",
+ "src/graph-view.ts",
+ "src/schedule-view.ts",
+ "src/disassembly-view.ts",
+ "src/graphmultiview.ts",
+ "src/turbo-visualizer.ts",
+ "src/resizer.ts",
+ "src/info-view.ts"
+ ]
+}
diff --git a/src/v8/tools/turbolizer/tsconfig.test.json b/src/v8/tools/turbolizer/tsconfig.test.json
new file mode 100644
index 0000000..1b7a591
--- /dev/null
+++ b/src/v8/tools/turbolizer/tsconfig.test.json
@@ -0,0 +1,6 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "module": "commonjs"
+ }
+}
diff --git a/src/v8/tools/turbolizer/tsfmt.json b/src/v8/tools/turbolizer/tsfmt.json
new file mode 100644
index 0000000..2ff95b8
--- /dev/null
+++ b/src/v8/tools/turbolizer/tsfmt.json
@@ -0,0 +1,16 @@
+{
+ "tabSize": 2,
+ "indentSize": 2,
+ "convertTabsToSpaces": true,
+ "insertSpaceAfterCommaDelimiter": true,
+ "insertSpaceAfterSemicolonInForStatements": true,
+ "insertSpaceBeforeAndAfterBinaryOperators": true,
+ "insertSpaceAfterKeywordsInControlFlowStatements": true,
+ "insertSpaceAfterFunctionKeywordForAnonymousFunctions": true,
+ "insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis": false,
+ "insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets": false,
+ "insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces": false,
+ "insertSpaceBeforeFunctionParenthesis": false,
+ "placeOpenBraceOnNewLineForFunctions": false,
+ "placeOpenBraceOnNewLineForControlBlocks": false
+}
\ No newline at end of file
diff --git a/src/v8/tools/turbolizer/tslint.json b/src/v8/tools/turbolizer/tslint.json
new file mode 100644
index 0000000..e07e057
--- /dev/null
+++ b/src/v8/tools/turbolizer/tslint.json
@@ -0,0 +1,45 @@
+{
+ "defaultSeverity": "error",
+ "extends": "tslint:recommended",
+ "jsRules": {},
+ "rules": {
+ "curly": [true, "ignore-same-line"],
+ "quotemark": [false, "double", "avoid-escape", "avoid-template"],
+ "only-arrow-functions": [false],
+ "no-var-keyword": true,
+ "prefer-const": [true],
+ "max-line-length": [false, {
+ "limit": 80
+ }],
+ "ordered-imports": false,
+ "array-type": [true, "generic"],
+ "semicolon": true,
+ "member-access": false,
+ "object-literal-shorthand": false,
+ "object-literal-key-quotes": [true, "as-needed"],
+ "object-literal-sort-keys": false,
+ "space-before-function-paren": [true, {
+ "anonymous": "always"
+ }],
+ "triple-equals": false,
+ "no-string-throw": false,
+ "no-empty": [true, "allow-empty-catch", "allow-empty-functions"],
+ "trailing-comma": false,
+ "member-ordering": false,
+ "no-string-literal": false,
+ "arrow-parens": [true, "ban-single-arg-parens"],
+ "no-console": false,
+ "interface-name": false,
+ "no-bitwise": false,
+ "no-shadowed-variable": false,
+ "prefer-for-of": true,
+ "align": true,
+ "arrow-return-shorthand": true,
+ "max-classes-per-file": false,
+ "variable-name": true,
+ "forin": false,
+ "one-variable-per-declaration": true,
+ "no-consecutive-blank-lines": true
+ },
+ "rulesDirectory": []
+}
diff --git a/src/v8/tools/turbolizer/turbo-visualizer.css b/src/v8/tools/turbolizer/turbo-visualizer.css
index 7fd9c48..216ca13 100644
--- a/src/v8/tools/turbolizer/turbo-visualizer.css
+++ b/src/v8/tools/turbolizer/turbo-visualizer.css
@@ -1,121 +1,212 @@
.visible-transition {
- transition-delay: 0s;
- transition-duration: 1s;
- transition-property: all;
- transition-timing-function: ease;
+ transition-delay: 0s;
+ transition-duration: 1s;
+ transition-property: all;
+ transition-timing-function: ease;
}
.collapse-pane {
- background: #A0A0A0;
- bottom: 0;
- position: absolute;
- margin-bottom: 0.5em;
- margin-right: 0.5em;
- margin-left: 0.5em;
- border-radius: 5px;
- padding: 0.5em;
- z-index: 5;
- opacity: 0.7;
- cursor: pointer;
+ background: #A0A0A0;
+ bottom: 0;
+ position: absolute;
+ margin-bottom: 0.5em;
+ margin-right: 0.5em;
+ margin-left: 0.5em;
+ border-radius: 5px;
+ padding: 0.5em;
+ z-index: 20;
+ opacity: 0.7;
+ cursor: pointer;
}
.search-input {
- vertical-align: middle;
- width: 145px;
- opacity: 1;
+ vertical-align: middle;
+ width: 145px;
+ opacity: 1;
+ box-sizing: border-box;
+ height: 1.5em;
+}
+
+#phase-select {
+ box-sizing: border-box;
+ height: 1.5em;
+}
+
+#search-only-visible {
+ vertical-align: middle;
}
.button-input {
- vertical-align: middle;
- width: 24px;
- opacity: 0.4;
- cursor: pointer;
+ vertical-align: middle;
+ width: 24px;
+ opacity: 0.4;
+ cursor: pointer;
}
.button-input-toggled {
- border-radius: 5px;
- background-color: #505050;
+ border-radius: 5px;
+ background-color: #505050;
}
.button-input:focus {
- outline: none;
+ outline: none;
}
.invisible {
- display: none;
+ display: none;
}
-
.selected {
- background-color: #FFFF33;
+ background-color: #FFFF33;
}
-.prettyprint ol.linenums > li {
- list-style-type: decimal;
- !important
+.selected.block,
+.selected.block-id,
+.selected.schedule-block {
+ background-color: #AAFFAA;
+}
+
+ol.linenums {
+ -webkit-padding-start: 8px;
+}
+
+.line-number {
+ display: inline-block;
+ min-width: 3ex;
+ text-align: right;
+ color: #444444;
+ margin-right: 0.5ex;
+ padding-right: 0.5ex;
+ background: #EEEEEE;
+ /* font-size: 80%; */
+ user-select: none;
+ height: 120%;
+}
+
+.line-number:hover {
+ background-color: #CCCCCC;
+}
+
+.prettyprint ol.linenums>li.selected {
+ background-color: #FFFF33 !important;
+}
+
+li.selected .line-number {
+ background-color: #FFFF33;
+}
+
+.prettyprint ol.linenums>li {
+ list-style-type: decimal;
+ display: block;
+}
+
+.source-container {
+ border-bottom: 2px solid #AAAAAA;
+}
+
+.code-header {
+ background-color: #CCCCCC;
+ padding-left: 1em;
+ padding-right: 1em;
+ padding-top: 1ex;
+ padding-bottom: 1ex;
+ font-family: monospace;
+ user-select: none;
+}
+
+.main-source .code-header {
+ border-top: 2px solid #AAAAAA;
+ font-weight: bold;
+}
+
+.code-header .code-file-function {
+ font-family: monospace;
+ float: left;
+ user-select: text;
+}
+
+.code-header .code-mode {
+ float: right;
+ font-family: sans-serif;
+ font-size: small;
+}
+
+.info-container {
+ font-family: sans-serif;
+ font-size: small;
+}
+
+.info-topic {
+ border: 1px solid lightgray;
+ margin: 2px;
+}
+
+.info-topic-header {
+ background-color: lightgray;
+ padding: 1px;
+}
+
+.info-topic-content {
+ padding: 2px;
}
+html,
body {
- margin: 0;
- padding: 0;
- height: 100vh;
- width: 100vw;
- overflow:hidden;
- -webkit-touch-callout: none;
- -webkit-user-select: none;
- -khtml-user-select: none;
- -moz-user-select: none;
- -ms-user-select: none;
- user-select: none;
+ margin: 0;
+ padding: 0;
+ /*height: 99vh;
+ width: 99vw;*/
+ overflow: hidden;
}
p {
- text-align: center;
- overflow: overlay;
- position: relative;
+ text-align: center;
+ overflow: overlay;
+ position: relative;
}
marker {
- fill: #080808;
+ fill: #080808;
}
g rect {
- fill: #F0F0F0;
- stroke: #080808;
- stroke-width: 2px;
+ fill: #F0F0F0;
+ stroke: #080808;
+ stroke-width: 2px;
}
g.dead {
- opacity: .5;
+ opacity: .5;
}
g.unsorted rect {
- opacity: 0.5;
+ opacity: 0.5;
}
div.scrollable {
- overflow-y: _croll; overflow-x: hidden;
+ overflow-y: auto;
+ overflow-x: hidden;
}
g.turbonode[relToHover="input"] rect {
- stroke: #67e62c;
- stroke-width: 16px;
+ stroke: #67e62c;
+ stroke-width: 16px;
}
g.turbonode[relToHover="output"] rect {
- stroke: #d23b14;
- stroke-width: 16px;
+ stroke: #d23b14;
+ stroke-width: 16px;
}
path[relToHover="input"] {
- stroke: #67e62c;
- stroke-width: 16px;
+ stroke: #67e62c;
+ stroke-width: 16px;
}
path[relToHover="output"] {
- stroke: #d23b14;
- stroke-width: 16px;
+ stroke: #d23b14;
+ stroke-width: 16px;
}
@@ -125,82 +216,82 @@
}
g.control rect {
- fill: #EFCC00;
- stroke: #080808;
- stroke-width: 5px;
+ fill: #EFCC00;
+ stroke: #080808;
+ stroke-width: 5px;
}
g.javascript rect {
- fill: #DD7E6B;
+ fill: #DD7E6B;
}
g.simplified rect {
- fill: #3C78D8;
+ fill: #3C78D8;
}
g.machine rect {
- fill: #6AA84F;
+ fill: #6AA84F;
}
g.input rect {
- fill: #CFE2F3;
+ fill: #CFE2F3;
}
g.selected rect {
- fill: #FFFF33;
+ fill: #FFFF33;
}
circle.bubbleStyle {
- fill: #080808;
- fill-opacity: 0.0;
- stroke: #080808;
- stroke-width: 2px;
+ fill: #080808;
+ fill-opacity: 0.0;
+ stroke: #080808;
+ stroke-width: 2px;
}
circle.bubbleStyle:hover {
- stroke-width: 3px;
+ stroke-width: 3px;
}
circle.filledBubbleStyle {
- fill: #080808;
- stroke: #080808;
- stroke-width: 2px;
+ fill: #080808;
+ stroke: #080808;
+ stroke-width: 2px;
}
circle.filledBubbleStyle:hover {
- fill: #080808;
- stroke-width: 3px;
+ fill: #080808;
+ stroke-width: 3px;
}
circle.halfFilledBubbleStyle {
- fill: #808080;
- stroke: #101010;
- stroke-width: 2px;
+ fill: #808080;
+ stroke: #101010;
+ stroke-width: 2px;
}
circle.halfFilledBubbleStyle:hover {
- fill: #808080;
- stroke-width: 3px;
+ fill: #808080;
+ stroke-width: 3px;
}
path {
- fill: none;
- stroke: #080808;
- stroke-width: 4px;
- cursor: default;
+ fill: none;
+ stroke: #080808;
+ stroke-width: 4px;
+ cursor: default;
}
path:hover {
- stroke-width: 6px;
+ stroke-width: 6px;
}
path.hidden {
- fill: none;
- stroke-width: 0;
+ fill: none;
+ stroke-width: 0;
}
path.link.selected {
- stroke: #FFFF33;
+ stroke: #FFFF33;
}
pre.prettyprint {
@@ -213,11 +304,11 @@
li.L5,
li.L7,
li.L9 {
- background: none !important
+ background: none !important
}
li.nolinenums {
- list-style-type:none;
+ list-style-type: none;
}
ul.noindent {
@@ -226,136 +317,382 @@
-webkit-margin-after: 0px;
}
-input:hover, .collapse-pane:hover input {
- opacity: 1;
- cursor: pointer;
+input:hover,
+.collapse-pane:hover input {
+ opacity: 1;
+ cursor: pointer;
}
-span.linkable-text {
- text-decoration: underline;
+.linkable-text {
+ text-decoration: underline;
}
-span.linkable-text:hover {
- cursor: pointer;
- font-weight: bold;
+.linkable-text:hover {
+ cursor: pointer;
+ font-weight: bold;
}
#left {
- float: left; height: 100%; background-color: #FFFFFF;
+ float: left;
+ user-select: none;
}
#middle {
- float:left; height: 100%; background-color: #F8F8F8;
+ float: left;
+ background-color: #F8F8F8;
+ user-select: none;
}
#right {
- float: right; background-color: #FFFFFF;
+ float: right;
+}
+
+.viewpane {
+ height: 100vh;
+ background-color: #FFFFFF;
+}
+
+.multiview {
+ width: 100%;
}
#disassembly-collapse {
- right: 0;
+ right: 0;
}
#source-collapse {
- left: 0;
+ left: 0;
}
-#graph-toolbox-anchor {
- height: 0px;
+#graph {
+ width: 100%;
+ height: 100%;
}
-#graph-toolbox {
- position: relative;
- top: 1em;
- left: 25px;
- border: 2px solid #eee8d5;
- border-radius: 5px;
- padding: 0.7em;
- z-index: 5;
- background: rgba(100%, 100%, 100%, 0.7);
+.toolbox-anchor {
+ height: 0px;
}
-#disassembly-toolbox {
- position: relative;
- top: 1em;
- left: 0.7em;
- border: 2px solid #eee8d5;
- border-radius: 5px;
- padding: 0.7em;
- z-index: 5;
+.graph-toolbox {
+ position: relative;
+ border-bottom: 2px solid #eee8d5;
+ z-index: 5;
+ background: rgba(100%, 100%, 100%, 0.7);
+ box-sizing: border-box;
+ padding: 3px;
+ margin-left: 4px;
+ margin-right: 4px;
+}
+
+.disassembly-toolbox {
+ position: relative;
+ padding-bottom: 3px;
+ z-index: 5;
+ background: rgba(100%, 100%, 100%, 0.7);
+ padding-top: 3px;
+ box-sizing: border-box;
+ margin-left: 4px;
+ margin-right: 4px;
}
#load-file {
- position: absolute;
- top: 0;
- right: 0;
- margin-top: 0.5em;
- margin-right: 0.5em;
- z-index: 5;
- opacity: 0.7;
+ position: absolute;
+ top: 0;
+ right: 0;
+ margin-top: 0.5em;
+ margin-right: 0.5em;
+ z-index: 20;
+ opacity: 0.7;
}
#load-file input {
- background: #A0A0A0;
- border-radius: 5px;
- padding: 0.5em;
+ background: #A0A0A0;
+ border-radius: 5px;
+ padding: 0.5em;
}
-#hidden-file-upload {
- display: none;
+#upload-helper {
+ display: none;
}
.prof {
- cursor: default;
+ cursor: default;
}
tspan {
- font-size: 500%;
- font-family: sans-serif;
+ font-size: 500%;
+ font-family: sans-serif;
}
text {
- dominant-baseline: text-before-edge;
+ dominant-baseline: text-before-edge;
}
-.resizer-left {
- position:absolute;
- width: 4px;
- height:100%;
- background: #a0a0a0;
- cursor: pointer;
+.resizer {
+ position: absolute;
+ z-index: 10;
+ width: 4px;
+ height: 100%;
+ background: #a0a0a0;
+ cursor: pointer;
}
-.resizer-left.snapped {
- width: 12px;
+.resizer.snapped {
+ width: 12px;
}
-.resizer-left:hover {
- background: orange;
+.resizer.snapped:hover {
+ width: 12px;
+ margin-left: 0px;
}
-.resizer-left.dragged {
- background: orange;
+.resizer:hover,
+.resizer.dragged {
+ width: 10px;
+ margin-left: -4px;
+ background: orange;
}
-.resizer-right {
- position:absolute;
- width: 4px;
- height:100%;
- background: #a0a0a0;
- cursor: pointer;
+.source-position {
+ /* border-left: 1px solid #FF3333; */
+ width: 0;
+ display: inline-block;
}
-.resizer-right.snapped {
- width: 12px;
+.source-position .inlining-marker {
+ content: "";
+ position: relative;
+ display: inline-block;
+ top: -0.5ex;
+ margin-left: -4px;
+ margin-right: -4px;
+ border-width: 5px;
+ border-style: solid;
+ border-color: #555 transparent transparent transparent;
}
-.resizer-right:hover {
- background: orange;
+.source-position .marker {
+ content: "";
+ display: inline-block;
+ bottom: -1ex;
+ width: 0px;
+ margin-left: -4px;
+ margin-right: -4px;
+ margin-bottom: -1ex;
+ border-width: 5px;
+ border-style: solid;
+ border-color: transparent transparent #555 transparent;
}
-.resizer-right.dragged {
- background: orange;
-}
\ No newline at end of file
+.source-position.selected .marker {
+ border-color: transparent transparent #F00 transparent;
+}
+
+.source-position .inlining-marker:hover {
+ border-color: transparent transparent #AA5 transparent;
+}
+
+.source-position .inlining-marker[data-descr]:hover::after {
+ content: attr(data-descr);
+ position: absolute;
+ font-size: 10px;
+ z-index: 1;
+ background-color: #555;
+ color: #fff;
+ text-align: center;
+ border-radius: 6px;
+ padding: 6px;
+ top: 6px;
+ left: 50%;
+ margin-left: -80px;
+}
+
+#sequence {
+ font-family: monospace;
+ margin-top: 50px;
+}
+
+#schedule {
+ font-family: monospace;
+ margin-top: 50px;
+}
+
+.schedule-block {
+ margin: 5px;
+ background-color: white;
+ padding-left: 5px;
+}
+
+.schedule-block .block-id {
+ display: inline-block;
+ font-size: large;
+ text-decoration: underline;
+ padding-left: 1ex;
+}
+
+.schedule-block .block-id:hover {
+ font-weight: bold;
+}
+
+.schedule-block>.block-id::before {
+ content: "Block B";
+}
+
+.schedule-block.deferred>.block-id::after {
+ content: " (deferred)";
+}
+
+.schedule-block .block-list {
+ display: inline-block;
+}
+
+.schedule-block .block-list * {
+ display: inline-block;
+}
+
+.schedule-block .block-list .block-id {
+ padding-left: 1ex;
+}
+
+.schedule-block .block-list .block-id:before {
+ content: "B";
+}
+
+.schedule-block .predecessor-list::before {
+ display: inline-block;
+ content: " \2B05 ";
+ padding-left: 1ex;
+ padding-right: 1ex;
+}
+
+.schedule-block .successor-list::before {
+ display: inline-block;
+ content: " \2B95 ";
+ padding-left: 1ex;
+ padding-right: 1ex;
+}
+
+.schedule-block .nodes .node * {
+ display: inline-block;
+}
+
+.schedule-block .nodes .node .node-id {
+ padding-right: 1ex;
+ min-width: 5ex;
+ text-align: right;
+}
+
+.schedule-block .nodes .node .node-id:after {
+ content: ":";
+}
+
+.schedule-block .nodes .node .node-label {
+ user-select: text;
+}
+
+.schedule-block .nodes .node .parameter-list:before {
+ content: "(";
+}
+
+.schedule-block .nodes .node .parameter-list:after {
+ content: ")";
+}
+
+.schedule-block .instr-marker {
+ padding-right: .5ex;
+ padding-left: .5ex;
+ min-width: 1em;
+ background: #EEEEEE;
+ /* display: none; */
+}
+
+.schedule-block>.instr-marker {
+ display: inline;
+}
+
+.instruction * {
+ padding-right: .5ex;
+}
+
+.phi-label,
+.instruction-id {
+ display: inline-block;
+ padding-right: .5ex;
+ padding-left: .5ex;
+ min-width: 1ex;
+ vertical-align: top;
+}
+
+.instruction-id:after {
+ content: ":";
+}
+
+.instruction-node,
+.gap,
+.instruction {
+ display: block;
+}
+
+.phi-contents,
+.instruction-contents,
+.gap *,
+.instruction * {
+ display: inline-block;
+}
+
+.phi * {
+ padding-right: 1ex;
+ display: inline-block;
+}
+
+.gap .gap-move {
+ padding-left: .5ex;
+ padding-right: .5ex;
+}
+
+.gap>*:before {
+ content: "(";
+}
+
+.gap>*:after {
+ content: ")";
+}
+
+.parameter.constant {
+ outline: 1px dotted red;
+}
+
+.clickable:hover {
+ text-decoration: underline;
+}
+
+.clickable:hover {
+ font-weight: bold;
+}
+
+.comma-sep-list>* {
+ padding-right: 1ex;
+}
+
+.comma-sep-list>*:after {
+ content: ",";
+}
+
+.comma-sep-list>*:last-child:after {
+ content: "";
+}
+
+.comma-sep-list>*:last-child {
+ padding-right: 0ex;
+}
+
+.temps:before {
+ content: "temps: ";
+}
+
+.temps {
+ padding-left: .5ex;
+ outline: 1px dotted grey;
+}
diff --git a/src/v8/tools/turbolizer/turbolizer.png b/src/v8/tools/turbolizer/turbolizer.png
new file mode 100644
index 0000000..1af1a49
--- /dev/null
+++ b/src/v8/tools/turbolizer/turbolizer.png
Binary files differ
diff --git a/src/v8/tools/ubsan/blacklist.txt b/src/v8/tools/ubsan/blacklist.txt
new file mode 100644
index 0000000..0705adc
--- /dev/null
+++ b/src/v8/tools/ubsan/blacklist.txt
@@ -0,0 +1,11 @@
+#############################################################################
+# UBSan blacklist.
+
+# Bug 8735: PropertyCallbackInfo<void> vs PropertyCallbackInfo<T>.
+fun:*v8*internal*PropertyCallbackArguments*CallAccessorSetter*
+fun:*v8*internal*PropertyCallbackArguments*BasicCallNamedGetterCallback*
+fun:*v8*internal*InvokeAccessorGetterCallback*
+
+# Bug 8735: WeakCallbackInfo<void> vs. WeakCallbackInfo<T>.
+fun:*v8*internal*GlobalHandles*PendingPhantomCallback*Invoke*
+fun:*v8*internal*GlobalHandles*Node*PostGarbageCollectionProcessing*
diff --git a/src/v8/tools/unittests/__init__.py b/src/v8/tools/unittests/__init__.py
new file mode 100644
index 0000000..3841a86
--- /dev/null
+++ b/src/v8/tools/unittests/__init__.py
@@ -0,0 +1,4 @@
+#!/usr/bin/env python
+# 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/src/v8/tools/unittests/run_perf_test.py b/src/v8/tools/unittests/run_perf_test.py
index 07dd515..083d224 100755
--- a/src/v8/tools/unittests/run_perf_test.py
+++ b/src/v8/tools/unittests/run_perf_test.py
@@ -3,19 +3,22 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+# for py2/py3 compatibility
+from __future__ import print_function
+
from collections import namedtuple
-import coverage
import json
-from mock import DEFAULT
-from mock import MagicMock
import os
-from os import path, sys
import platform
import shutil
import subprocess
+import sys
import tempfile
import unittest
+import coverage
+import mock
+
# Requires python-coverage and python-mock. Native python coverage
# version >= 3.7.1 should be installed to get the best speed.
@@ -23,130 +26,135 @@
RUN_PERF = os.path.join(BASE_DIR, 'run_perf.py')
TEST_DATA = os.path.join(BASE_DIR, 'unittests', 'testdata')
-TEST_WORKSPACE = path.join(tempfile.gettempdir(), "test-v8-run-perf")
+TEST_WORKSPACE = os.path.join(tempfile.gettempdir(), 'test-v8-run-perf')
V8_JSON = {
- "path": ["."],
- "binary": "d7",
- "flags": ["--flag"],
- "main": "run.js",
- "run_count": 1,
- "results_regexp": "^%s: (.+)$",
- "tests": [
- {"name": "Richards"},
- {"name": "DeltaBlue"},
+ 'path': ['.'],
+ 'owners': ['username@chromium.org'],
+ 'binary': 'd7',
+ 'timeout': 60,
+ 'flags': ['--flag'],
+ 'main': 'run.js',
+ 'run_count': 1,
+ 'results_regexp': '^%s: (.+)$',
+ 'tests': [
+ {'name': 'Richards'},
+ {'name': 'DeltaBlue'},
]
}
V8_NESTED_SUITES_JSON = {
- "path": ["."],
- "flags": ["--flag"],
- "run_count": 1,
- "units": "score",
- "tests": [
- {"name": "Richards",
- "path": ["richards"],
- "binary": "d7",
- "main": "run.js",
- "resources": ["file1.js", "file2.js"],
- "run_count": 2,
- "results_regexp": "^Richards: (.+)$"},
- {"name": "Sub",
- "path": ["sub"],
- "tests": [
- {"name": "Leaf",
- "path": ["leaf"],
- "run_count_x64": 3,
- "units": "ms",
- "main": "run.js",
- "results_regexp": "^Simple: (.+) ms.$"},
+ 'path': ['.'],
+ 'owners': ['username@chromium.org'],
+ 'flags': ['--flag'],
+ 'run_count': 1,
+ 'units': 'score',
+ 'tests': [
+ {'name': 'Richards',
+ 'path': ['richards'],
+ 'binary': 'd7',
+ 'main': 'run.js',
+ 'resources': ['file1.js', 'file2.js'],
+ 'run_count': 2,
+ 'results_regexp': '^Richards: (.+)$'},
+ {'name': 'Sub',
+ 'path': ['sub'],
+ 'tests': [
+ {'name': 'Leaf',
+ 'path': ['leaf'],
+ 'run_count_x64': 3,
+ 'units': 'ms',
+ 'main': 'run.js',
+ 'results_regexp': '^Simple: (.+) ms.$'},
]
},
- {"name": "DeltaBlue",
- "path": ["delta_blue"],
- "main": "run.js",
- "flags": ["--flag2"],
- "results_regexp": "^DeltaBlue: (.+)$"},
- {"name": "ShouldntRun",
- "path": ["."],
- "archs": ["arm"],
- "main": "run.js"},
+ {'name': 'DeltaBlue',
+ 'path': ['delta_blue'],
+ 'main': 'run.js',
+ 'flags': ['--flag2'],
+ 'results_regexp': '^DeltaBlue: (.+)$'},
+ {'name': 'ShouldntRun',
+ 'path': ['.'],
+ 'archs': ['arm'],
+ 'main': 'run.js'},
]
}
V8_GENERIC_JSON = {
- "path": ["."],
- "binary": "cc",
- "flags": ["--flag"],
- "generic": True,
- "run_count": 1,
- "units": "ms",
+ 'path': ['.'],
+ 'owners': ['username@chromium.org'],
+ 'binary': 'cc',
+ 'flags': ['--flag'],
+ 'generic': True,
+ 'run_count': 1,
+ 'units': 'ms',
}
-Output = namedtuple("Output", "stdout, stderr, timed_out")
-
class PerfTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
- cls.base = path.dirname(path.dirname(path.abspath(__file__)))
- sys.path.append(cls.base)
+ sys.path.insert(0, BASE_DIR)
cls._cov = coverage.coverage(
- include=([os.path.join(cls.base, "run_perf.py")]))
+ include=([os.path.join(BASE_DIR, 'run_perf.py')]))
cls._cov.start()
import run_perf
from testrunner.local import command
- global command
- global run_perf
+ from testrunner.objects.output import Output, NULL_OUTPUT
+ global command, run_perf, Output, NULL_OUTPUT
@classmethod
def tearDownClass(cls):
cls._cov.stop()
- print ""
- print cls._cov.report()
+ print('')
+ print(cls._cov.report())
def setUp(self):
self.maxDiff = None
- if path.exists(TEST_WORKSPACE):
+ if os.path.exists(TEST_WORKSPACE):
shutil.rmtree(TEST_WORKSPACE)
os.makedirs(TEST_WORKSPACE)
def tearDown(self):
- if path.exists(TEST_WORKSPACE):
+ mock.patch.stopall()
+ if os.path.exists(TEST_WORKSPACE):
shutil.rmtree(TEST_WORKSPACE)
def _WriteTestInput(self, json_content):
- self._test_input = path.join(TEST_WORKSPACE, "test.json")
- with open(self._test_input, "w") as f:
+ self._test_input = os.path.join(TEST_WORKSPACE, 'test.json')
+ with open(self._test_input, 'w') as f:
f.write(json.dumps(json_content))
def _MockCommand(self, *args, **kwargs):
# Fake output for each test run.
test_outputs = [Output(stdout=arg,
- stderr=None,
- timed_out=kwargs.get("timed_out", False))
+ timed_out=kwargs.get('timed_out', False),
+ exit_code=kwargs.get('exit_code', 0),
+ duration=42)
for arg in args[1]]
def create_cmd(*args, **kwargs):
- cmd = MagicMock()
+ cmd = mock.MagicMock()
def execute(*args, **kwargs):
return test_outputs.pop()
- cmd.execute = MagicMock(side_effect=execute)
+ cmd.execute = mock.MagicMock(side_effect=execute)
return cmd
- command.Command = MagicMock(side_effect=create_cmd)
+ mock.patch.object(
+ run_perf.command, 'PosixCommand',
+ mock.MagicMock(side_effect=create_cmd)).start()
# Check that d8 is called from the correct cwd for each test run.
- dirs = [path.join(TEST_WORKSPACE, arg) for arg in args[0]]
+ dirs = [os.path.join(TEST_WORKSPACE, arg) for arg in args[0]]
def chdir(*args, **kwargs):
- self.assertEquals(dirs.pop(), args[0])
- os.chdir = MagicMock(side_effect=chdir)
+ self.assertEqual(dirs.pop(), args[0])
+ os.chdir = mock.MagicMock(side_effect=chdir)
- subprocess.check_call = MagicMock()
- platform.system = MagicMock(return_value='Linux')
+ subprocess.check_call = mock.MagicMock()
+ platform.system = mock.MagicMock(return_value='Linux')
def _CallMain(self, *args):
- self._test_output = path.join(TEST_WORKSPACE, "results.json")
+ self._test_output = os.path.join(TEST_WORKSPACE, 'results.json')
all_args=[
- "--json-test-results",
+ '--json-test-results',
self._test_output,
self._test_input,
]
@@ -158,18 +166,27 @@
return json.load(f)
def _VerifyResults(self, suite, units, traces, file_name=None):
- self.assertEquals([
- {"units": units,
- "graphs": [suite, trace["name"]],
- "results": trace["results"],
- "stddev": trace["stddev"]} for trace in traces],
- self._LoadResults(file_name)["traces"])
+ self.assertListEqual(sorted([
+ {'units': units,
+ 'graphs': [suite, trace['name']],
+ 'results': trace['results'],
+ 'stddev': trace['stddev']} for trace in traces]),
+ sorted(self._LoadResults(file_name)['traces']))
+
+ def _VerifyRunnableDurations(self, runs, timeout, file_name=None):
+ self.assertListEqual([
+ {
+ 'graphs': ['test'],
+ 'durations': [42] * runs,
+ 'timeout': timeout,
+ },
+ ], self._LoadResults(file_name)['runnables'])
def _VerifyErrors(self, errors):
- self.assertEquals(errors, self._LoadResults()["errors"])
+ self.assertListEqual(errors, self._LoadResults()['errors'])
def _VerifyMock(self, binary, *args, **kwargs):
- shell = path.join(path.dirname(self.base), binary)
+ shell = os.path.join(os.path.dirname(BASE_DIR), binary)
command.Command.assert_called_with(
cmd_prefix=[],
shell=shell,
@@ -177,381 +194,419 @@
timeout=kwargs.get('timeout', 60))
def _VerifyMockMultiple(self, *args, **kwargs):
- self.assertEquals(len(args), len(command.Command.call_args_list))
+ self.assertEqual(len(args), len(command.Command.call_args_list))
for arg, actual in zip(args, command.Command.call_args_list):
expected = {
'cmd_prefix': [],
- 'shell': path.join(path.dirname(self.base), arg[0]),
+ 'shell': os.path.join(os.path.dirname(BASE_DIR), arg[0]),
'args': list(arg[1:]),
'timeout': kwargs.get('timeout', 60)
}
- self.assertEquals((expected, ), actual)
+ self.assertTupleEqual((expected, ), actual)
def testOneRun(self):
self._WriteTestInput(V8_JSON)
- self._MockCommand(["."], ["x\nRichards: 1.234\nDeltaBlue: 10657567\ny\n"])
- self.assertEquals(0, self._CallMain())
- self._VerifyResults("test", "score", [
- {"name": "Richards", "results": ["1.234"], "stddev": ""},
- {"name": "DeltaBlue", "results": ["10657567.0"], "stddev": ""},
+ self._MockCommand(['.'], ['x\nRichards: 1.234\nDeltaBlue: 10657567\ny\n'])
+ self.assertEqual(0, self._CallMain())
+ self._VerifyResults('test', 'score', [
+ {'name': 'Richards', 'results': [1.234], 'stddev': ''},
+ {'name': 'DeltaBlue', 'results': [10657567.0], 'stddev': ''},
])
+ self._VerifyRunnableDurations(1, 60)
self._VerifyErrors([])
- self._VerifyMock(path.join("out", "x64.release", "d7"), "--flag", "run.js")
+ self._VerifyMock(
+ os.path.join('out', 'x64.release', 'd7'), '--flag', 'run.js')
def testOneRunWithTestFlags(self):
test_input = dict(V8_JSON)
- test_input["test_flags"] = ["2", "test_name"]
+ test_input['test_flags'] = ['2', 'test_name']
self._WriteTestInput(test_input)
- self._MockCommand(["."], ["Richards: 1.234\nDeltaBlue: 10657567"])
- self.assertEquals(0, self._CallMain())
- self._VerifyResults("test", "score", [
- {"name": "Richards", "results": ["1.234"], "stddev": ""},
- {"name": "DeltaBlue", "results": ["10657567.0"], "stddev": ""},
+ self._MockCommand(['.'], ['Richards: 1.234\nDeltaBlue: 10657567'])
+ self.assertEqual(0, self._CallMain())
+ self._VerifyResults('test', 'score', [
+ {'name': 'Richards', 'results': [1.234], 'stddev': ''},
+ {'name': 'DeltaBlue', 'results': [10657567.0], 'stddev': ''},
])
self._VerifyErrors([])
- self._VerifyMock(path.join("out", "x64.release", "d7"), "--flag", "run.js",
- "--", "2", "test_name")
+ self._VerifyMock(os.path.join(
+ 'out', 'x64.release', 'd7'), '--flag', 'run.js', '--', '2', 'test_name')
def testTwoRuns_Units_SuiteName(self):
test_input = dict(V8_JSON)
- test_input["run_count"] = 2
- test_input["name"] = "v8"
- test_input["units"] = "ms"
+ test_input['run_count'] = 2
+ test_input['name'] = 'v8'
+ test_input['units'] = 'ms'
self._WriteTestInput(test_input)
- self._MockCommand([".", "."],
- ["Richards: 100\nDeltaBlue: 200\n",
- "Richards: 50\nDeltaBlue: 300\n"])
- self.assertEquals(0, self._CallMain())
- self._VerifyResults("v8", "ms", [
- {"name": "Richards", "results": ["50.0", "100.0"], "stddev": ""},
- {"name": "DeltaBlue", "results": ["300.0", "200.0"], "stddev": ""},
+ self._MockCommand(['.', '.'],
+ ['Richards: 100\nDeltaBlue: 200\n',
+ 'Richards: 50\nDeltaBlue: 300\n'])
+ self.assertEqual(0, self._CallMain())
+ self._VerifyResults('v8', 'ms', [
+ {'name': 'Richards', 'results': [50.0, 100.0], 'stddev': ''},
+ {'name': 'DeltaBlue', 'results': [300.0, 200.0], 'stddev': ''},
])
self._VerifyErrors([])
- self._VerifyMock(path.join("out", "x64.release", "d7"), "--flag", "run.js")
+ self._VerifyMock(os.path.join(
+ 'out', 'x64.release', 'd7'), '--flag', 'run.js')
def testTwoRuns_SubRegexp(self):
test_input = dict(V8_JSON)
- test_input["run_count"] = 2
- del test_input["results_regexp"]
- test_input["tests"][0]["results_regexp"] = "^Richards: (.+)$"
- test_input["tests"][1]["results_regexp"] = "^DeltaBlue: (.+)$"
+ test_input['run_count'] = 2
+ del test_input['results_regexp']
+ test_input['tests'][0]['results_regexp'] = '^Richards: (.+)$'
+ test_input['tests'][1]['results_regexp'] = '^DeltaBlue: (.+)$'
self._WriteTestInput(test_input)
- self._MockCommand([".", "."],
- ["Richards: 100\nDeltaBlue: 200\n",
- "Richards: 50\nDeltaBlue: 300\n"])
- self.assertEquals(0, self._CallMain())
- self._VerifyResults("test", "score", [
- {"name": "Richards", "results": ["50.0", "100.0"], "stddev": ""},
- {"name": "DeltaBlue", "results": ["300.0", "200.0"], "stddev": ""},
+ self._MockCommand(['.', '.'],
+ ['Richards: 100\nDeltaBlue: 200\n',
+ 'Richards: 50\nDeltaBlue: 300\n'])
+ self.assertEqual(0, self._CallMain())
+ self._VerifyResults('test', 'score', [
+ {'name': 'Richards', 'results': [50.0, 100.0], 'stddev': ''},
+ {'name': 'DeltaBlue', 'results': [300.0, 200.0], 'stddev': ''},
])
self._VerifyErrors([])
- self._VerifyMock(path.join("out", "x64.release", "d7"), "--flag", "run.js")
+ self._VerifyMock(os.path.join(
+ 'out', 'x64.release', 'd7'), '--flag', 'run.js')
+
+ def testPerfectConfidenceRuns(self):
+ self._WriteTestInput(V8_JSON)
+ self._MockCommand(
+ ['.'], ['x\nRichards: 1.234\nDeltaBlue: 10657567\ny\n'] * 10)
+ self.assertEqual(0, self._CallMain('--confidence-level', '1'))
+ self._VerifyResults('test', 'score', [
+ {'name': 'Richards', 'results': [1.234] * 10, 'stddev': ''},
+ {'name': 'DeltaBlue', 'results': [10657567.0] * 10, 'stddev': ''},
+ ])
+ self._VerifyErrors([])
+ self._VerifyMock(os.path.join(
+ 'out', 'x64.release', 'd7'), '--flag', 'run.js')
+
+ def testNoisyConfidenceRuns(self):
+ self._WriteTestInput(V8_JSON)
+ self._MockCommand(
+ ['.'],
+ reversed([
+ # First 10 runs are mandatory. DeltaBlue is slightly noisy.
+ 'x\nRichards: 1.234\nDeltaBlue: 10757567\ny\n',
+ 'x\nRichards: 1.234\nDeltaBlue: 10557567\ny\n',
+ 'x\nRichards: 1.234\nDeltaBlue: 10657567\ny\n',
+ 'x\nRichards: 1.234\nDeltaBlue: 10657567\ny\n',
+ 'x\nRichards: 1.234\nDeltaBlue: 10657567\ny\n',
+ 'x\nRichards: 1.234\nDeltaBlue: 10657567\ny\n',
+ 'x\nRichards: 1.234\nDeltaBlue: 10657567\ny\n',
+ 'x\nRichards: 1.234\nDeltaBlue: 10657567\ny\n',
+ 'x\nRichards: 1.234\nDeltaBlue: 10657567\ny\n',
+ 'x\nRichards: 1.234\nDeltaBlue: 10657567\ny\n',
+ # Need 4 more runs for confidence in DeltaBlue results.
+ 'x\nRichards: 1.234\nDeltaBlue: 10657567\ny\n',
+ 'x\nRichards: 1.234\nDeltaBlue: 10657567\ny\n',
+ 'x\nRichards: 1.234\nDeltaBlue: 10657567\ny\n',
+ 'x\nRichards: 1.234\nDeltaBlue: 10657567\ny\n',
+ ]),
+ )
+ self.assertEqual(0, self._CallMain('--confidence-level', '1'))
+ self._VerifyResults('test', 'score', [
+ {'name': 'Richards', 'results': [1.234] * 14, 'stddev': ''},
+ {
+ 'name': 'DeltaBlue',
+ 'results': [10757567.0, 10557567.0] + [10657567.0] * 12,
+ 'stddev': '',
+ },
+ ])
+ self._VerifyErrors([])
+ self._VerifyMock(os.path.join(
+ 'out', 'x64.release', 'd7'), '--flag', 'run.js')
def testNestedSuite(self):
self._WriteTestInput(V8_NESTED_SUITES_JSON)
- self._MockCommand(["delta_blue", "sub/leaf", "richards"],
- ["DeltaBlue: 200\n",
- "Simple: 1 ms.\n",
- "Simple: 2 ms.\n",
- "Simple: 3 ms.\n",
- "Richards: 100\n",
- "Richards: 50\n"])
- self.assertEquals(0, self._CallMain())
- self.assertEquals([
- {"units": "score",
- "graphs": ["test", "Richards"],
- "results": ["50.0", "100.0"],
- "stddev": ""},
- {"units": "ms",
- "graphs": ["test", "Sub", "Leaf"],
- "results": ["3.0", "2.0", "1.0"],
- "stddev": ""},
- {"units": "score",
- "graphs": ["test", "DeltaBlue"],
- "results": ["200.0"],
- "stddev": ""},
- ], self._LoadResults()["traces"])
+ self._MockCommand(['delta_blue', 'sub/leaf', 'richards'],
+ ['DeltaBlue: 200\n',
+ 'Simple: 1 ms.\n',
+ 'Simple: 2 ms.\n',
+ 'Simple: 3 ms.\n',
+ 'Richards: 100\n',
+ 'Richards: 50\n'])
+ self.assertEqual(0, self._CallMain())
+ self.assertListEqual(sorted([
+ {'units': 'score',
+ 'graphs': ['test', 'Richards'],
+ 'results': [50.0, 100.0],
+ 'stddev': ''},
+ {'units': 'ms',
+ 'graphs': ['test', 'Sub', 'Leaf'],
+ 'results': [3.0, 2.0, 1.0],
+ 'stddev': ''},
+ {'units': 'score',
+ 'graphs': ['test', 'DeltaBlue'],
+ 'results': [200.0],
+ 'stddev': ''},
+ ]), sorted(self._LoadResults()['traces']))
self._VerifyErrors([])
self._VerifyMockMultiple(
- (path.join("out", "x64.release", "d7"), "--flag", "run.js"),
- (path.join("out", "x64.release", "d7"), "--flag", "run.js"),
- (path.join("out", "x64.release", "d8"), "--flag", "run.js"),
- (path.join("out", "x64.release", "d8"), "--flag", "run.js"),
- (path.join("out", "x64.release", "d8"), "--flag", "run.js"),
- (path.join("out", "x64.release", "d8"), "--flag", "--flag2", "run.js"))
+ (os.path.join('out', 'x64.release', 'd7'), '--flag', 'run.js'),
+ (os.path.join('out', 'x64.release', 'd7'), '--flag', 'run.js'),
+ (os.path.join('out', 'x64.release', 'd8'), '--flag', 'run.js'),
+ (os.path.join('out', 'x64.release', 'd8'), '--flag', 'run.js'),
+ (os.path.join('out', 'x64.release', 'd8'), '--flag', 'run.js'),
+ (os.path.join('out', 'x64.release', 'd8'),
+ '--flag', '--flag2', 'run.js'))
def testOneRunStdDevRegExp(self):
test_input = dict(V8_JSON)
- test_input["stddev_regexp"] = "^%s\-stddev: (.+)$"
+ test_input['stddev_regexp'] = '^%s\-stddev: (.+)$'
self._WriteTestInput(test_input)
- self._MockCommand(["."], ["Richards: 1.234\nRichards-stddev: 0.23\n"
- "DeltaBlue: 10657567\nDeltaBlue-stddev: 106\n"])
- self.assertEquals(0, self._CallMain())
- self._VerifyResults("test", "score", [
- {"name": "Richards", "results": ["1.234"], "stddev": "0.23"},
- {"name": "DeltaBlue", "results": ["10657567.0"], "stddev": "106"},
+ self._MockCommand(['.'], ['Richards: 1.234\nRichards-stddev: 0.23\n'
+ 'DeltaBlue: 10657567\nDeltaBlue-stddev: 106\n'])
+ self.assertEqual(0, self._CallMain())
+ self._VerifyResults('test', 'score', [
+ {'name': 'Richards', 'results': [1.234], 'stddev': '0.23'},
+ {'name': 'DeltaBlue', 'results': [10657567.0], 'stddev': '106'},
])
self._VerifyErrors([])
- self._VerifyMock(path.join("out", "x64.release", "d7"), "--flag", "run.js")
+ self._VerifyMock(
+ os.path.join('out', 'x64.release', 'd7'), '--flag', 'run.js')
def testTwoRunsStdDevRegExp(self):
test_input = dict(V8_JSON)
- test_input["stddev_regexp"] = "^%s\-stddev: (.+)$"
- test_input["run_count"] = 2
+ test_input['stddev_regexp'] = '^%s\-stddev: (.+)$'
+ test_input['run_count'] = 2
self._WriteTestInput(test_input)
- self._MockCommand(["."], ["Richards: 3\nRichards-stddev: 0.7\n"
- "DeltaBlue: 6\nDeltaBlue-boom: 0.9\n",
- "Richards: 2\nRichards-stddev: 0.5\n"
- "DeltaBlue: 5\nDeltaBlue-stddev: 0.8\n"])
- self.assertEquals(1, self._CallMain())
- self._VerifyResults("test", "score", [
- {"name": "Richards", "results": ["2.0", "3.0"], "stddev": "0.7"},
- {"name": "DeltaBlue", "results": ["5.0", "6.0"], "stddev": "0.8"},
+ self._MockCommand(['.'], ['Richards: 3\nRichards-stddev: 0.7\n'
+ 'DeltaBlue: 6\nDeltaBlue-boom: 0.9\n',
+ 'Richards: 2\nRichards-stddev: 0.5\n'
+ 'DeltaBlue: 5\nDeltaBlue-stddev: 0.8\n'])
+ self.assertEqual(1, self._CallMain())
+ self._VerifyResults('test', 'score', [
+ {'name': 'Richards', 'results': [2.0, 3.0], 'stddev': '0.7'},
+ {'name': 'DeltaBlue', 'results': [5.0, 6.0], 'stddev': '0.8'},
])
self._VerifyErrors(
- ["Test test/Richards should only run once since a stddev is provided "
- "by the test.",
- "Test test/DeltaBlue should only run once since a stddev is provided "
- "by the test.",
- "Regexp \"^DeltaBlue\-stddev: (.+)$\" didn't match for test "
- "test/DeltaBlue."])
- self._VerifyMock(path.join("out", "x64.release", "d7"), "--flag", "run.js")
+ ['Test test/Richards should only run once since a stddev is provided '
+ 'by the test.',
+ 'Test test/DeltaBlue should only run once since a stddev is provided '
+ 'by the test.',
+ 'Regexp "^DeltaBlue\-stddev: (.+)$" did not match for test '
+ 'test/DeltaBlue.'])
+ self._VerifyMock(
+ os.path.join('out', 'x64.release', 'd7'), '--flag', 'run.js')
def testBuildbot(self):
self._WriteTestInput(V8_JSON)
- self._MockCommand(["."], ["Richards: 1.234\nDeltaBlue: 10657567\n"])
- self.assertEquals(0, self._CallMain("--buildbot"))
- self._VerifyResults("test", "score", [
- {"name": "Richards", "results": ["1.234"], "stddev": ""},
- {"name": "DeltaBlue", "results": ["10657567.0"], "stddev": ""},
+ self._MockCommand(['.'], ['Richards: 1.234\nDeltaBlue: 10657567\n'])
+ mock.patch.object(
+ run_perf.Platform, 'ReadBuildConfig',
+ mock.MagicMock(return_value={'is_android': False})).start()
+ self.assertEqual(0, self._CallMain('--buildbot'))
+ self._VerifyResults('test', 'score', [
+ {'name': 'Richards', 'results': [1.234], 'stddev': ''},
+ {'name': 'DeltaBlue', 'results': [10657567.0], 'stddev': ''},
])
self._VerifyErrors([])
- self._VerifyMock(path.join("out", "Release", "d7"), "--flag", "run.js")
+ self._VerifyMock(os.path.join('out', 'Release', 'd7'), '--flag', 'run.js')
def testBuildbotWithTotal(self):
test_input = dict(V8_JSON)
- test_input["total"] = True
+ test_input['total'] = True
self._WriteTestInput(test_input)
- self._MockCommand(["."], ["Richards: 1.234\nDeltaBlue: 10657567\n"])
- self.assertEquals(0, self._CallMain("--buildbot"))
- self._VerifyResults("test", "score", [
- {"name": "Richards", "results": ["1.234"], "stddev": ""},
- {"name": "DeltaBlue", "results": ["10657567.0"], "stddev": ""},
- {"name": "Total", "results": ["3626.49109719"], "stddev": ""},
+ self._MockCommand(['.'], ['Richards: 1.234\nDeltaBlue: 10657567\n'])
+ mock.patch.object(
+ run_perf.Platform, 'ReadBuildConfig',
+ mock.MagicMock(return_value={'is_android': False})).start()
+ self.assertEqual(0, self._CallMain('--buildbot'))
+ self._VerifyResults('test', 'score', [
+ {'name': 'Richards', 'results': [1.234], 'stddev': ''},
+ {'name': 'DeltaBlue', 'results': [10657567.0], 'stddev': ''},
+ {'name': 'Total', 'results': [3626.491097190233], 'stddev': ''},
])
self._VerifyErrors([])
- self._VerifyMock(path.join("out", "Release", "d7"), "--flag", "run.js")
+ self._VerifyMock(os.path.join('out', 'Release', 'd7'), '--flag', 'run.js')
def testBuildbotWithTotalAndErrors(self):
test_input = dict(V8_JSON)
- test_input["total"] = True
+ test_input['total'] = True
self._WriteTestInput(test_input)
- self._MockCommand(["."], ["x\nRichards: bla\nDeltaBlue: 10657567\ny\n"])
- self.assertEquals(1, self._CallMain("--buildbot"))
- self._VerifyResults("test", "score", [
- {"name": "Richards", "results": [], "stddev": ""},
- {"name": "DeltaBlue", "results": ["10657567.0"], "stddev": ""},
+ self._MockCommand(['.'], ['x\nRichards: bla\nDeltaBlue: 10657567\ny\n'])
+ mock.patch.object(
+ run_perf.Platform, 'ReadBuildConfig',
+ mock.MagicMock(return_value={'is_android': False})).start()
+ self.assertEqual(1, self._CallMain('--buildbot'))
+ self._VerifyResults('test', 'score', [
+ {'name': 'DeltaBlue', 'results': [10657567.0], 'stddev': ''},
])
self._VerifyErrors(
- ["Regexp \"^Richards: (.+)$\" "
- "returned a non-numeric for test test/Richards.",
- "Not all traces have the same number of results."])
- self._VerifyMock(path.join("out", "Release", "d7"), "--flag", "run.js")
+ ['Regexp "^Richards: (.+)$" '
+ 'returned a non-numeric for test test/Richards.',
+ 'Not all traces have produced results. Can not compute total for '
+ 'test.'])
+ self._VerifyMock(os.path.join('out', 'Release', 'd7'), '--flag', 'run.js')
def testRegexpNoMatch(self):
self._WriteTestInput(V8_JSON)
- self._MockCommand(["."], ["x\nRichaards: 1.234\nDeltaBlue: 10657567\ny\n"])
- self.assertEquals(1, self._CallMain())
- self._VerifyResults("test", "score", [
- {"name": "Richards", "results": [], "stddev": ""},
- {"name": "DeltaBlue", "results": ["10657567.0"], "stddev": ""},
+ self._MockCommand(['.'], ['x\nRichaards: 1.234\nDeltaBlue: 10657567\ny\n'])
+ self.assertEqual(1, self._CallMain())
+ self._VerifyResults('test', 'score', [
+ {'name': 'DeltaBlue', 'results': [10657567.0], 'stddev': ''},
])
self._VerifyErrors(
- ["Regexp \"^Richards: (.+)$\" didn't match for test test/Richards."])
- self._VerifyMock(path.join("out", "x64.release", "d7"), "--flag", "run.js")
+ ['Regexp "^Richards: (.+)$" did not match for test test/Richards.'])
+ self._VerifyMock(
+ os.path.join('out', 'x64.release', 'd7'), '--flag', 'run.js')
- def testOneRunGeneric(self):
- test_input = dict(V8_GENERIC_JSON)
+ def testOneRunCrashed(self):
+ test_input = dict(V8_JSON)
+ test_input['retry_count'] = 1
self._WriteTestInput(test_input)
- self._MockCommand(["."], [
- "RESULT Infra: Constant1= 11 count\n"
- "RESULT Infra: Constant2= [10,5,10,15] count\n"
- "RESULT Infra: Constant3= {12,1.2} count\n"
- "RESULT Infra: Constant4= [10,5,error,15] count\n"])
- self.assertEquals(1, self._CallMain())
- self.assertEquals([
- {"units": "count",
- "graphs": ["test", "Infra", "Constant1"],
- "results": ["11.0"],
- "stddev": ""},
- {"units": "count",
- "graphs": ["test", "Infra", "Constant2"],
- "results": ["10.0", "5.0", "10.0", "15.0"],
- "stddev": ""},
- {"units": "count",
- "graphs": ["test", "Infra", "Constant3"],
- "results": ["12.0"],
- "stddev": "1.2"},
- {"units": "count",
- "graphs": ["test", "Infra", "Constant4"],
- "results": [],
- "stddev": ""},
- ], self._LoadResults()["traces"])
- self._VerifyErrors(["Found non-numeric in test/Infra/Constant4"])
- self._VerifyMock(path.join("out", "x64.release", "cc"), "--flag", "")
+ self._MockCommand(
+ ['.'], ['x\nRichards: 1.234\nDeltaBlue: 10657567\ny\n', ''],
+ exit_code=-1)
+ self.assertEqual(1, self._CallMain())
+ self._VerifyResults('test', 'score', [])
+ self._VerifyErrors([])
+ self._VerifyMock(
+ os.path.join('out', 'x64.release', 'd7'), '--flag', 'run.js')
def testOneRunTimingOut(self):
test_input = dict(V8_JSON)
- test_input["timeout"] = 70
+ test_input['timeout'] = 70
+ test_input['retry_count'] = 0
self._WriteTestInput(test_input)
- self._MockCommand(["."], [""], timed_out=True)
- self.assertEquals(1, self._CallMain())
- self._VerifyResults("test", "score", [
- {"name": "Richards", "results": [], "stddev": ""},
- {"name": "DeltaBlue", "results": [], "stddev": ""},
- ])
- self._VerifyErrors([
- "Regexp \"^Richards: (.+)$\" didn't match for test test/Richards.",
- "Regexp \"^DeltaBlue: (.+)$\" didn't match for test test/DeltaBlue.",
- ])
- self._VerifyMock(
- path.join("out", "x64.release", "d7"), "--flag", "run.js", timeout=70)
+ self._MockCommand(['.'], [''], timed_out=True)
+ self.assertEqual(1, self._CallMain())
+ self._VerifyResults('test', 'score', [])
+ self._VerifyErrors([])
+ self._VerifyMock(os.path.join('out', 'x64.release', 'd7'),
+ '--flag', 'run.js', timeout=70)
- # Simple test that mocks out the android platform. Testing the platform would
- # require lots of complicated mocks for the android tools.
def testAndroid(self):
self._WriteTestInput(V8_JSON)
- # FIXME(machenbach): This is not test-local!
- platform = run_perf.AndroidPlatform
- platform.PreExecution = MagicMock(return_value=None)
- platform.PostExecution = MagicMock(return_value=None)
- platform.PreTests = MagicMock(return_value=None)
- platform.Run = MagicMock(
- return_value=("Richards: 1.234\nDeltaBlue: 10657567\n", None))
- run_perf.AndroidPlatform = MagicMock(return_value=platform)
- self.assertEquals(
- 0, self._CallMain("--android-build-tools", "/some/dir",
- "--arch", "arm"))
- self._VerifyResults("test", "score", [
- {"name": "Richards", "results": ["1.234"], "stddev": ""},
- {"name": "DeltaBlue", "results": ["10657567.0"], "stddev": ""},
+ mock.patch('run_perf.AndroidPlatform.PreExecution').start()
+ mock.patch('run_perf.AndroidPlatform.PostExecution').start()
+ mock.patch('run_perf.AndroidPlatform.PreTests').start()
+ mock.patch(
+ 'run_perf.AndroidPlatform.Run',
+ return_value=(Output(stdout='Richards: 1.234\nDeltaBlue: 10657567\n'),
+ NULL_OUTPUT)).start()
+ mock.patch('testrunner.local.android._Driver', autospec=True).start()
+ mock.patch(
+ 'run_perf.Platform.ReadBuildConfig',
+ return_value={'is_android': True}).start()
+ self.assertEqual(0, self._CallMain('--arch', 'arm'))
+ self._VerifyResults('test', 'score', [
+ {'name': 'Richards', 'results': [1.234], 'stddev': ''},
+ {'name': 'DeltaBlue', 'results': [10657567.0], 'stddev': ''},
])
def testTwoRuns_Trybot(self):
test_input = dict(V8_JSON)
- test_input["run_count"] = 2
+ test_input['run_count'] = 2
self._WriteTestInput(test_input)
- self._MockCommand([".", ".", ".", "."],
- ["Richards: 100\nDeltaBlue: 200\n",
- "Richards: 200\nDeltaBlue: 20\n",
- "Richards: 50\nDeltaBlue: 200\n",
- "Richards: 100\nDeltaBlue: 20\n"])
- test_output_secondary = path.join(TEST_WORKSPACE, "results_secondary.json")
- self.assertEquals(0, self._CallMain(
- "--outdir-secondary", "out-secondary",
- "--json-test-results-secondary", test_output_secondary,
+ self._MockCommand(['.', '.', '.', '.'],
+ ['Richards: 100\nDeltaBlue: 200\n',
+ 'Richards: 200\nDeltaBlue: 20\n',
+ 'Richards: 50\nDeltaBlue: 200\n',
+ 'Richards: 100\nDeltaBlue: 20\n'])
+ test_output_secondary = os.path.join(
+ TEST_WORKSPACE, 'results_secondary.json')
+ self.assertEqual(0, self._CallMain(
+ '--outdir-secondary', 'out-secondary',
+ '--json-test-results-secondary', test_output_secondary,
))
- self._VerifyResults("test", "score", [
- {"name": "Richards", "results": ["100.0", "200.0"], "stddev": ""},
- {"name": "DeltaBlue", "results": ["20.0", "20.0"], "stddev": ""},
+ self._VerifyResults('test', 'score', [
+ {'name': 'Richards', 'results': [100.0, 200.0], 'stddev': ''},
+ {'name': 'DeltaBlue', 'results': [20.0, 20.0], 'stddev': ''},
])
- self._VerifyResults("test", "score", [
- {"name": "Richards", "results": ["50.0", "100.0"], "stddev": ""},
- {"name": "DeltaBlue", "results": ["200.0", "200.0"], "stddev": ""},
+ self._VerifyResults('test', 'score', [
+ {'name': 'Richards', 'results': [50.0, 100.0], 'stddev': ''},
+ {'name': 'DeltaBlue', 'results': [200.0, 200.0], 'stddev': ''},
], test_output_secondary)
+ self._VerifyRunnableDurations(2, 60, test_output_secondary)
self._VerifyErrors([])
self._VerifyMockMultiple(
- (path.join("out", "x64.release", "d7"), "--flag", "run.js"),
- (path.join("out-secondary", "x64.release", "d7"), "--flag", "run.js"),
- (path.join("out", "x64.release", "d7"), "--flag", "run.js"),
- (path.join("out-secondary", "x64.release", "d7"), "--flag", "run.js"),
+ (os.path.join('out', 'x64.release', 'd7'), '--flag', 'run.js'),
+ (os.path.join('out-secondary', 'x64.release', 'd7'),
+ '--flag', 'run.js'),
+ (os.path.join('out', 'x64.release', 'd7'), '--flag', 'run.js'),
+ (os.path.join('out-secondary', 'x64.release', 'd7'),
+ '--flag', 'run.js'),
)
def testWrongBinaryWithProf(self):
test_input = dict(V8_JSON)
self._WriteTestInput(test_input)
- self._MockCommand(["."], ["x\nRichards: 1.234\nDeltaBlue: 10657567\ny\n"])
- self.assertEquals(0, self._CallMain("--extra-flags=--prof"))
- self._VerifyResults("test", "score", [
- {"name": "Richards", "results": ["1.234"], "stddev": ""},
- {"name": "DeltaBlue", "results": ["10657567.0"], "stddev": ""},
+ self._MockCommand(['.'], ['x\nRichards: 1.234\nDeltaBlue: 10657567\ny\n'])
+ self.assertEqual(0, self._CallMain('--extra-flags=--prof'))
+ self._VerifyResults('test', 'score', [
+ {'name': 'Richards', 'results': [1.234], 'stddev': ''},
+ {'name': 'DeltaBlue', 'results': [10657567.0], 'stddev': ''},
])
self._VerifyErrors([])
- self._VerifyMock(path.join("out", "x64.release", "d7"),
- "--flag", "--prof", "run.js")
-
- def testUnzip(self):
- def Gen():
- for i in [1, 2, 3]:
- yield i, i + 1
- l, r = run_perf.Unzip(Gen())
- self.assertEquals([1, 2, 3], list(l()))
- self.assertEquals([2, 3, 4], list(r()))
+ self._VerifyMock(os.path.join('out', 'x64.release', 'd7'),
+ '--flag', '--prof', 'run.js')
#############################################################################
### System tests
def _RunPerf(self, mocked_d8, test_json):
- output_json = path.join(TEST_WORKSPACE, "output.json")
+ output_json = os.path.join(TEST_WORKSPACE, 'output.json')
args = [
- sys.executable, RUN_PERF,
- "--binary-override-path", os.path.join(TEST_DATA, mocked_d8),
- "--json-test-results", output_json,
+ os.sys.executable, RUN_PERF,
+ '--binary-override-path', os.path.join(TEST_DATA, mocked_d8),
+ '--json-test-results', output_json,
os.path.join(TEST_DATA, test_json),
]
subprocess.check_output(args)
return self._LoadResults(output_json)
def testNormal(self):
- results = self._RunPerf("d8_mocked1.py", "test1.json")
- self.assertEquals([], results['errors'])
- self.assertEquals([
+ results = self._RunPerf('d8_mocked1.py', 'test1.json')
+ self.assertListEqual([], results['errors'])
+ self.assertListEqual(sorted([
{
'units': 'score',
'graphs': ['test1', 'Richards'],
- 'results': [u'1.2', u'1.2'],
+ 'results': [1.2, 1.2],
'stddev': '',
},
{
'units': 'score',
'graphs': ['test1', 'DeltaBlue'],
- 'results': [u'2.1', u'2.1'],
+ 'results': [2.1, 2.1],
'stddev': '',
},
- ], results['traces'])
+ ]), sorted(results['traces']))
def testResultsProcessor(self):
- results = self._RunPerf("d8_mocked2.py", "test2.json")
- self.assertEquals([], results['errors'])
- self.assertEquals([
+ results = self._RunPerf('d8_mocked2.py', 'test2.json')
+ self.assertListEqual([], results['errors'])
+ self.assertListEqual([
{
'units': 'score',
'graphs': ['test2', 'Richards'],
- 'results': [u'1.2', u'1.2'],
+ 'results': [1.2, 1.2],
'stddev': '',
},
{
'units': 'score',
'graphs': ['test2', 'DeltaBlue'],
- 'results': [u'2.1', u'2.1'],
+ 'results': [2.1, 2.1],
'stddev': '',
},
], results['traces'])
def testResultsProcessorNested(self):
- results = self._RunPerf("d8_mocked2.py", "test3.json")
- self.assertEquals([], results['errors'])
- self.assertEquals([
+ results = self._RunPerf('d8_mocked2.py', 'test3.json')
+ self.assertListEqual([], results['errors'])
+ self.assertListEqual([
{
'units': 'score',
'graphs': ['test3', 'Octane', 'Richards'],
- 'results': [u'1.2'],
+ 'results': [1.2],
'stddev': '',
},
{
'units': 'score',
'graphs': ['test3', 'Octane', 'DeltaBlue'],
- 'results': [u'2.1'],
+ 'results': [2.1],
'stddev': '',
},
], results['traces'])
diff --git a/src/v8/tools/unittests/run_tests_test.py b/src/v8/tools/unittests/run_tests_test.py
index f4ff3fe..93b10f5 100755
--- a/src/v8/tools/unittests/run_tests_test.py
+++ b/src/v8/tools/unittests/run_tests_test.py
@@ -17,6 +17,9 @@
# TODO(machenbach): Coverage data from multiprocessing doesn't work.
# TODO(majeski): Add some tests for the fuzzers.
+# for py2/py3 compatibility
+from __future__ import print_function
+
import collections
import contextlib
import json
@@ -101,8 +104,9 @@
sys_args = ['--command-prefix', sys.executable] + list(args)
if kwargs.get('infra_staging', False):
sys_args.append('--infra-staging')
- code = standard_runner.StandardTestRunner(
- basedir=basedir).execute(sys_args)
+ else:
+ sys_args.append('--no-infra-staging')
+ code = standard_runner.StandardTestRunner(basedir=basedir).execute(sys_args)
return Result(stdout.getvalue(), stderr.getvalue(), code)
@@ -125,7 +129,7 @@
import coverage
if int(coverage.__version__.split('.')[0]) < 4:
cls._cov = None
- print 'Python coverage version >= 4 required.'
+ print('Python coverage version >= 4 required.')
raise ImportError()
cls._cov = coverage.Coverage(
source=([os.path.join(TOOLS_ROOT, 'testrunner')]),
@@ -141,19 +145,23 @@
cls._cov.exclude('assert False')
cls._cov.start()
except ImportError:
- print 'Running without python coverage.'
+ print('Running without python coverage.')
sys.path.append(TOOLS_ROOT)
global standard_runner
from testrunner import standard_runner
+ global num_fuzzer
+ from testrunner import num_fuzzer
+ from testrunner.local import command
from testrunner.local import pool
+ command.setup_testing()
pool.setup_testing()
@classmethod
def tearDownClass(cls):
if cls._cov:
cls._cov.stop()
- print ''
- print cls._cov.report(show_missing=True)
+ print('')
+ print(cls._cov.report(show_missing=True))
def testPass(self):
"""Test running only passing tests in two variants.
@@ -170,10 +178,10 @@
'sweet/bananas',
'sweet/raspberries',
)
- self.assertIn('Running 4 tests', result.stdout, result)
- self.assertIn('Done running sweet/bananas: pass', result.stdout, result)
- self.assertIn('Total time:', result.stderr, result)
- self.assertIn('sweet/bananas', result.stderr, result)
+ self.assertIn('Done running sweet/bananas default: pass', result.stdout, result)
+ # TODO(majeski): Implement for test processors
+ # self.assertIn('Total time:', result.stderr, result)
+ # self.assertIn('sweet/bananas', result.stderr, result)
self.assertEqual(0, result.returncode, result)
def testShardedProc(self):
@@ -186,19 +194,26 @@
'--variants=default,stress',
'--shard-count=2',
'--shard-run=%d' % shard,
- 'sweet/bananas',
+ 'sweet/blackberries',
'sweet/raspberries',
- infra_staging=True,
+ infra_staging=False,
)
# One of the shards gets one variant of each test.
- self.assertIn('Running 1 base tests', result.stdout, result)
self.assertIn('2 tests ran', result.stdout, result)
if shard == 1:
- self.assertIn('Done running sweet/bananas', result.stdout, result)
+ self.assertIn(
+ 'Done running sweet/raspberries default', result.stdout, result)
+ self.assertIn(
+ 'Done running sweet/raspberries stress', result.stdout, result)
+ self.assertEqual(0, result.returncode, result)
else:
- self.assertIn('Done running sweet/raspberries', result.stdout, result)
- self.assertEqual(0, result.returncode, result)
+ self.assertIn(
+ 'sweet/blackberries default: FAIL', result.stdout, result)
+ self.assertIn(
+ 'sweet/blackberries stress: FAIL', result.stdout, result)
+ self.assertEqual(1, result.returncode, result)
+ @unittest.skip("incompatible with test processors")
def testSharded(self):
"""Test running a particular shard."""
with temp_base() as basedir:
@@ -219,10 +234,7 @@
self.assertIn('Done running sweet/raspberries', result.stdout, result)
self.assertEqual(0, result.returncode, result)
- def testFailProc(self):
- self.testFail(infra_staging=True)
-
- def testFail(self, infra_staging=False):
+ def testFail(self):
"""Test running only failing tests in two variants."""
with temp_base() as basedir:
result = run_tests(
@@ -231,17 +243,13 @@
'--progress=verbose',
'--variants=default,stress',
'sweet/strawberries',
- infra_staging=infra_staging,
+ infra_staging=False,
)
- if not infra_staging:
- self.assertIn('Running 2 tests', result.stdout, result)
- else:
- self.assertIn('Running 1 base tests', result.stdout, result)
- self.assertIn('2 tests ran', result.stdout, result)
- self.assertIn('Done running sweet/strawberries: FAIL', result.stdout, result)
+ self.assertIn('Done running sweet/strawberries default: FAIL', result.stdout, result)
self.assertEqual(1, result.returncode, result)
- def check_cleaned_json_output(self, expected_results_name, actual_json):
+ def check_cleaned_json_output(
+ self, expected_results_name, actual_json, basedir):
# Check relevant properties of the json output.
with open(actual_json) as f:
json_output = json.load(f)[0]
@@ -254,6 +262,7 @@
data['duration'] = 1
data['command'] = ' '.join(
['/usr/bin/python'] + data['command'].split()[1:])
+ data['command'] = data['command'].replace(basedir + '/', '')
for data in json_output['slowest_tests']:
replace_variable_data(data)
for data in json_output['results']:
@@ -266,10 +275,7 @@
msg = None # Set to pretty_json for bootstrapping.
self.assertDictEqual(json_output, expected_test_results, msg)
- def testFailWithRerunAndJSONProc(self):
- self.testFailWithRerunAndJSON(infra_staging=True)
-
- def testFailWithRerunAndJSON(self, infra_staging=False):
+ def testFailWithRerunAndJSON(self):
"""Test re-running a failing test and output to json."""
with temp_base() as basedir:
json_path = os.path.join(basedir, 'out.json')
@@ -282,33 +288,23 @@
'--random-seed=123',
'--json-test-results', json_path,
'sweet/strawberries',
- infra_staging=infra_staging,
+ infra_staging=False,
)
- if not infra_staging:
- self.assertIn('Running 1 tests', result.stdout, result)
- else:
- self.assertIn('Running 1 base tests', result.stdout, result)
- self.assertIn('1 tests ran', result.stdout, result)
- self.assertIn('Done running sweet/strawberries: FAIL', result.stdout, result)
- if not infra_staging:
- # We run one test, which fails and gets re-run twice.
- self.assertIn('3 tests failed', result.stdout, result)
- else:
- # With test processors we don't count reruns as separated failures.
- # TODO(majeski): fix it?
- self.assertIn('1 tests failed', result.stdout, result)
+ self.assertIn('Done running sweet/strawberries default: FAIL', result.stdout, result)
+ # With test processors we don't count reruns as separated failures.
+ # TODO(majeski): fix it?
+ self.assertIn('1 tests failed', result.stdout, result)
self.assertEqual(0, result.returncode, result)
# TODO(majeski): Previously we only reported the variant flags in the
# flags field of the test result.
# After recent changes we report all flags, including the file names.
# This is redundant to the command. Needs investigation.
- self.check_cleaned_json_output('expected_test_results1.json', json_path)
+ self.maxDiff = None
+ self.check_cleaned_json_output(
+ 'expected_test_results1.json', json_path, basedir)
- def testFlakeWithRerunAndJSONProc(self):
- self.testFlakeWithRerunAndJSON(infra_staging=True)
-
- def testFlakeWithRerunAndJSON(self, infra_staging=False):
+ def testFlakeWithRerunAndJSON(self):
"""Test re-running a failing test and output to json."""
with temp_base(baseroot='testroot2') as basedir:
json_path = os.path.join(basedir, 'out.json')
@@ -321,20 +317,15 @@
'--random-seed=123',
'--json-test-results', json_path,
'sweet',
- infra_staging=infra_staging,
+ infra_staging=False,
)
- if not infra_staging:
- self.assertIn('Running 1 tests', result.stdout, result)
- self.assertIn(
- 'Done running sweet/bananaflakes: FAIL', result.stdout, result)
- self.assertIn('1 tests failed', result.stdout, result)
- else:
- self.assertIn('Running 1 base tests', result.stdout, result)
- self.assertIn(
- 'Done running sweet/bananaflakes: pass', result.stdout, result)
- self.assertIn('All tests succeeded', result.stdout, result)
+ self.assertIn(
+ 'Done running sweet/bananaflakes default: pass', result.stdout, result)
+ self.assertIn('All tests succeeded', result.stdout, result)
self.assertEqual(0, result.returncode, result)
- self.check_cleaned_json_output('expected_test_results2.json', json_path)
+ self.maxDiff = None
+ self.check_cleaned_json_output(
+ 'expected_test_results2.json', json_path, basedir)
def testAutoDetect(self):
"""Fake a build with several auto-detected options.
@@ -347,7 +338,9 @@
basedir, dcheck_always_on=True, is_asan=True, is_cfi=True,
is_msan=True, is_tsan=True, is_ubsan_vptr=True, target_cpu='x86',
v8_enable_i18n_support=False, v8_target_cpu='x86',
- v8_use_snapshot=False)
+ v8_use_snapshot=False, v8_enable_embedded_builtins=False,
+ v8_enable_verify_csa=False, v8_enable_lite_mode=False,
+ v8_enable_pointer_compression=False)
result = run_tests(
basedir,
'--mode=Release',
@@ -371,10 +364,7 @@
# TODO(machenbach): Test some more implications of the auto-detected
# options, e.g. that the right env variables are set.
- def testSkipsProc(self):
- self.testSkips(infra_staging=True)
-
- def testSkips(self, infra_staging=False):
+ def testSkips(self):
"""Test skipping tests in status file for a specific variant."""
with temp_base() as basedir:
result = run_tests(
@@ -383,19 +373,27 @@
'--progress=verbose',
'--variants=nooptimization',
'sweet/strawberries',
- infra_staging=infra_staging,
+ infra_staging=False,
)
- if not infra_staging:
- self.assertIn('Running 0 tests', result.stdout, result)
- else:
- self.assertIn('Running 1 base tests', result.stdout, result)
- self.assertIn('0 tests ran', result.stdout, result)
- self.assertEqual(0, result.returncode, result)
+ self.assertIn('0 tests ran', result.stdout, result)
+ self.assertEqual(2, result.returncode, result)
- def testDefaultProc(self):
- self.testDefault(infra_staging=True)
+ def testRunSkips(self):
+ """Inverse the above. Test parameter to keep running skipped tests."""
+ with temp_base() as basedir:
+ result = run_tests(
+ basedir,
+ '--mode=Release',
+ '--progress=verbose',
+ '--variants=nooptimization',
+ '--run-skipped',
+ 'sweet/strawberries',
+ )
+ self.assertIn('1 tests failed', result.stdout, result)
+ self.assertIn('1 tests ran', result.stdout, result)
+ self.assertEqual(1, result.returncode, result)
- def testDefault(self, infra_staging=False):
+ def testDefault(self):
"""Test using default test suites, though no tests are run since they don't
exist in a test setting.
"""
@@ -403,28 +401,17 @@
result = run_tests(
basedir,
'--mode=Release',
- infra_staging=infra_staging,
+ infra_staging=False,
)
- if not infra_staging:
- self.assertIn('Warning: no tests were run!', result.stdout, result)
- else:
- self.assertIn('Running 0 base tests', result.stdout, result)
- self.assertIn('0 tests ran', result.stdout, result)
- self.assertEqual(0, result.returncode, result)
+ self.assertIn('0 tests ran', result.stdout, result)
+ self.assertEqual(2, result.returncode, result)
def testNoBuildConfig(self):
"""Test failing run when build config is not found."""
with temp_base() as basedir:
result = run_tests(basedir)
self.assertIn('Failed to load build config', result.stdout, result)
- self.assertEqual(1, result.returncode, result)
-
- def testGNOption(self):
- """Test using gn option, but no gn build folder is found."""
- with temp_base() as basedir:
- # TODO(machenbach): This should fail gracefully.
- with self.assertRaises(OSError):
- run_tests(basedir, '--gn')
+ self.assertEqual(5, result.returncode, result)
def testInconsistentMode(self):
"""Test failing run when attempting to wrongly override the mode."""
@@ -433,7 +420,7 @@
result = run_tests(basedir, '--mode=Release')
self.assertIn('execution mode (release) for release is inconsistent '
'with build config (debug)', result.stdout, result)
- self.assertEqual(1, result.returncode, result)
+ self.assertEqual(5, result.returncode, result)
def testInconsistentArch(self):
"""Test failing run when attempting to wrongly override the arch."""
@@ -442,13 +429,13 @@
self.assertIn(
'--arch value (ia32) inconsistent with build config (x64).',
result.stdout, result)
- self.assertEqual(1, result.returncode, result)
+ self.assertEqual(5, result.returncode, result)
def testWrongVariant(self):
"""Test using a bogus variant."""
with temp_base() as basedir:
result = run_tests(basedir, '--mode=Release', '--variants=meh')
- self.assertEqual(1, result.returncode, result)
+ self.assertEqual(5, result.returncode, result)
def testModeFromBuildConfig(self):
"""Test auto-detection of mode from build config."""
@@ -457,6 +444,7 @@
self.assertIn('Running tests for x64.release', result.stdout, result)
self.assertEqual(0, result.returncode, result)
+ @unittest.skip("not available with test processors")
def testReport(self):
"""Test the report feature.
@@ -475,6 +463,7 @@
result.stdout, result)
self.assertEqual(1, result.returncode, result)
+ @unittest.skip("not available with test processors")
def testWarnUnusedRules(self):
"""Test the unused-rules feature."""
with temp_base() as basedir:
@@ -489,6 +478,7 @@
self.assertIn( 'Unused rule: regress/', result.stdout, result)
self.assertEqual(1, result.returncode, result)
+ @unittest.skip("not available with test processors")
def testCatNoSources(self):
"""Test printing sources, but the suite's tests have none available."""
with temp_base() as basedir:
@@ -503,10 +493,7 @@
self.assertIn('(no source available)', result.stdout, result)
self.assertEqual(0, result.returncode, result)
- def testPredictableProc(self):
- self.testPredictable(infra_staging=True)
-
- def testPredictable(self, infra_staging=False):
+ def testPredictable(self):
"""Test running a test in verify-predictable mode.
The test will fail because of missing allocation output. We verify that and
@@ -520,16 +507,13 @@
'--progress=verbose',
'--variants=default',
'sweet/bananas',
- infra_staging=infra_staging,
+ infra_staging=False,
)
- if not infra_staging:
- self.assertIn('Running 1 tests', result.stdout, result)
- else:
- self.assertIn('Running 1 base tests', result.stdout, result)
- self.assertIn('1 tests ran', result.stdout, result)
- self.assertIn('Done running sweet/bananas: FAIL', result.stdout, result)
+ self.assertIn('1 tests ran', result.stdout, result)
+ self.assertIn(
+ 'Done running sweet/bananas default: FAIL', result.stdout, result)
self.assertIn('Test had no allocation output', result.stdout, result)
- self.assertIn('--predictable --verify_predictable', result.stdout, result)
+ self.assertIn('--predictable --verify-predictable', result.stdout, result)
self.assertEqual(1, result.returncode, result)
def testSlowArch(self):
@@ -557,8 +541,9 @@
'--variants=default',
'--random-seed-stress-count=2',
'sweet/bananas',
+ infra_staging=False,
)
- self.assertIn('Running 2 tests', result.stdout, result)
+ self.assertIn('2 tests ran', result.stdout, result)
self.assertEqual(0, result.returncode, result)
def testRandomSeedStressWithSeed(self):
@@ -573,7 +558,7 @@
'--random-seed=123',
'sweet/strawberries',
)
- self.assertIn('Running 2 tests', result.stdout, result)
+ self.assertIn('2 tests ran', result.stdout, result)
# We use a failing test so that the command is printed and we can verify
# that the right random seed was passed.
self.assertIn('--random-seed=123', result.stdout, result)
@@ -598,7 +583,7 @@
)
# Both tests are either marked as running in only default or only
# slow variant.
- self.assertIn('Running 2 tests', result.stdout, result)
+ self.assertIn('2 tests ran', result.stdout, result)
self.assertEqual(0, result.returncode, result)
def testStatusFilePresubmit(self):
@@ -608,10 +593,7 @@
self.assertTrue(statusfile.PresubmitCheck(
os.path.join(basedir, 'test', 'sweet', 'sweet.status')))
- def testDotsProgressProc(self):
- self.testDotsProgress(infra_staging=True)
-
- def testDotsProgress(self, infra_staging=False):
+ def testDotsProgress(self):
with temp_base() as basedir:
result = run_tests(
basedir,
@@ -620,29 +602,19 @@
'sweet/cherries',
'sweet/bananas',
'--no-sorting', '-j1', # make results order deterministic
- infra_staging=infra_staging,
+ infra_staging=False,
)
- if not infra_staging:
- self.assertIn('Running 2 tests', result.stdout, result)
- else:
- self.assertIn('Running 2 base tests', result.stdout, result)
- self.assertIn('2 tests ran', result.stdout, result)
+ self.assertIn('2 tests ran', result.stdout, result)
self.assertIn('F.', result.stdout, result)
self.assertEqual(1, result.returncode, result)
- def testMonoProgressProc(self):
- self._testCompactProgress('mono', True)
-
def testMonoProgress(self):
- self._testCompactProgress('mono', False)
-
- def testColorProgressProc(self):
- self._testCompactProgress('color', True)
+ self._testCompactProgress('mono')
def testColorProgress(self):
- self._testCompactProgress('color', False)
+ self._testCompactProgress('color')
- def _testCompactProgress(self, name, infra_staging):
+ def _testCompactProgress(self, name):
with temp_base() as basedir:
result = run_tests(
basedir,
@@ -650,18 +622,51 @@
'--progress=%s' % name,
'sweet/cherries',
'sweet/bananas',
- infra_staging=infra_staging,
+ infra_staging=False,
)
if name == 'color':
- expected = ('\033[34m% 100\033[0m|'
+ expected = ('\033[34m% 28\033[0m|'
'\033[32m+ 1\033[0m|'
'\033[31m- 1\033[0m]: Done')
else:
- expected = '% 100|+ 1|- 1]: Done'
+ expected = '% 28|+ 1|- 1]: Done'
self.assertIn(expected, result.stdout)
self.assertIn('sweet/cherries', result.stdout)
self.assertIn('sweet/bananas', result.stdout)
self.assertEqual(1, result.returncode, result)
+ def testExitAfterNFailures(self):
+ with temp_base() as basedir:
+ result = run_tests(
+ basedir,
+ '--mode=Release',
+ '--progress=verbose',
+ '--exit-after-n-failures=2',
+ '-j1',
+ 'sweet/mangoes', # PASS
+ 'sweet/strawberries', # FAIL
+ 'sweet/blackberries', # FAIL
+ 'sweet/raspberries', # should not run
+ )
+ self.assertIn('sweet/mangoes default: pass', result.stdout, result)
+ self.assertIn('sweet/strawberries default: FAIL', result.stdout, result)
+ self.assertIn('Too many failures, exiting...', result.stdout, result)
+ self.assertIn('sweet/blackberries default: FAIL', result.stdout, result)
+ self.assertNotIn('Done running sweet/raspberries', result.stdout, result)
+ self.assertIn('2 tests failed', result.stdout, result)
+ self.assertIn('3 tests ran', result.stdout, result)
+ self.assertEqual(1, result.returncode, result)
+
+ def testNumFuzzer(self):
+ sys_args = ['--command-prefix', sys.executable, '--outdir', 'out/Release']
+
+ with temp_base() as basedir:
+ with capture() as (stdout, stderr):
+ code = num_fuzzer.NumFuzzer(basedir=basedir).execute(sys_args)
+ result = Result(stdout.getvalue(), stderr.getvalue(), code)
+
+ self.assertEqual(0, result.returncode, result)
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/src/v8/tools/unittests/testdata/d8_mocked1.py b/src/v8/tools/unittests/testdata/d8_mocked1.py
index 53405a6..ff330af 100644
--- a/src/v8/tools/unittests/testdata/d8_mocked1.py
+++ b/src/v8/tools/unittests/testdata/d8_mocked1.py
@@ -3,5 +3,8 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-print 'Richards: 1.2'
-print 'DeltaBlue: 2.1'
+# for py2/py3 compatibility
+from __future__ import print_function
+
+print('Richards: 1.2')
+print('DeltaBlue: 2.1')
diff --git a/src/v8/tools/unittests/testdata/d8_mocked2.py b/src/v8/tools/unittests/testdata/d8_mocked2.py
index 71a3d04..3630462 100644
--- a/src/v8/tools/unittests/testdata/d8_mocked2.py
+++ b/src/v8/tools/unittests/testdata/d8_mocked2.py
@@ -3,8 +3,11 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-print 'Richards1: 1'
-print 'DeltaBlue1: 1'
-print 'Richards2: 0.2'
-print 'DeltaBlue2: 1.0'
-print 'DeltaBlue3: 0.1'
+# for py2/py3 compatibility
+from __future__ import print_function
+
+print('Richards1: 1')
+print('DeltaBlue1: 1')
+print('Richards2: 0.2')
+print('DeltaBlue2: 1.0')
+print('DeltaBlue3: 0.1')
diff --git a/src/v8/tools/unittests/testdata/expected_test_results1.json b/src/v8/tools/unittests/testdata/expected_test_results1.json
index 172b87a..31fac89 100644
--- a/src/v8/tools/unittests/testdata/expected_test_results1.json
+++ b/src/v8/tools/unittests/testdata/expected_test_results1.json
@@ -1,107 +1,124 @@
{
- "arch": "x64",
- "duration_mean": 1,
- "mode": "release",
+ "arch": "x64",
+ "duration_mean": 1,
+ "mode": "release",
"results": [
{
- "command": "/usr/bin/python out/Release/d8_mocked.py --random-seed=123 strawberries --nohard-abort",
- "duration": 1,
- "exit_code": 1,
+ "command": "/usr/bin/python out/Release/d8_mocked.py --test strawberries --random-seed=123 --nohard-abort --testing-d8-test-runner",
+ "duration": 1,
+ "exit_code": 1,
"expected": [
"PASS"
- ],
+ ],
"flags": [
- "--random-seed=123",
- "strawberries",
- "--nohard-abort"
- ],
- "name": "sweet/strawberries",
- "random_seed": 123,
- "result": "FAIL",
- "run": 1,
- "stderr": "",
- "stdout": "--random-seed=123 strawberries --nohard-abort\n",
- "target_name": "d8_mocked.py",
- "variant": "default"
- },
+ "--test",
+ "strawberries",
+ "--random-seed=123",
+ "--nohard-abort",
+ "--testing-d8-test-runner"
+ ],
+ "framework_name": "standard_runner",
+ "name": "sweet/strawberries",
+ "random_seed": 123,
+ "result": "FAIL",
+ "run": 1,
+ "stderr": "",
+ "stdout": "--test strawberries --random-seed=123 --nohard-abort --testing-d8-test-runner\n",
+ "target_name": "d8_mocked.py",
+ "variant": "default",
+ "variant_flags": []
+ },
{
- "command": "/usr/bin/python out/Release/d8_mocked.py --random-seed=123 strawberries --nohard-abort",
- "duration": 1,
- "exit_code": 1,
+ "command": "/usr/bin/python out/Release/d8_mocked.py --test strawberries --random-seed=123 --nohard-abort --testing-d8-test-runner",
+ "duration": 1,
+ "exit_code": 1,
"expected": [
"PASS"
- ],
+ ],
"flags": [
- "--random-seed=123",
- "strawberries",
- "--nohard-abort"
- ],
- "name": "sweet/strawberries",
- "random_seed": 123,
- "result": "FAIL",
- "run": 2,
- "stderr": "",
- "stdout": "--random-seed=123 strawberries --nohard-abort\n",
- "target_name": "d8_mocked.py",
- "variant": "default"
- },
+ "--test",
+ "strawberries",
+ "--random-seed=123",
+ "--nohard-abort",
+ "--testing-d8-test-runner"
+ ],
+ "framework_name": "standard_runner",
+ "name": "sweet/strawberries",
+ "random_seed": 123,
+ "result": "FAIL",
+ "run": 2,
+ "stderr": "",
+ "stdout": "--test strawberries --random-seed=123 --nohard-abort --testing-d8-test-runner\n",
+ "target_name": "d8_mocked.py",
+ "variant": "default",
+ "variant_flags": []
+ },
{
- "command": "/usr/bin/python out/Release/d8_mocked.py --random-seed=123 strawberries --nohard-abort",
- "duration": 1,
- "exit_code": 1,
+ "command": "/usr/bin/python out/Release/d8_mocked.py --test strawberries --random-seed=123 --nohard-abort --testing-d8-test-runner",
+ "duration": 1,
+ "exit_code": 1,
"expected": [
"PASS"
- ],
+ ],
"flags": [
- "--random-seed=123",
- "strawberries",
- "--nohard-abort"
- ],
- "name": "sweet/strawberries",
- "random_seed": 123,
- "result": "FAIL",
- "run": 3,
- "stderr": "",
- "stdout": "--random-seed=123 strawberries --nohard-abort\n",
- "target_name": "d8_mocked.py",
- "variant": "default"
+ "--test",
+ "strawberries",
+ "--random-seed=123",
+ "--nohard-abort",
+ "--testing-d8-test-runner"
+ ],
+ "framework_name": "standard_runner",
+ "name": "sweet/strawberries",
+ "random_seed": 123,
+ "result": "FAIL",
+ "run": 3,
+ "stderr": "",
+ "stdout": "--test strawberries --random-seed=123 --nohard-abort --testing-d8-test-runner\n",
+ "target_name": "d8_mocked.py",
+ "variant": "default",
+ "variant_flags": []
}
- ],
+ ],
"slowest_tests": [
{
- "command": "/usr/bin/python out/Release/d8_mocked.py --random-seed=123 strawberries --nohard-abort",
- "duration": 1,
+ "command": "/usr/bin/python out/Release/d8_mocked.py --test strawberries --random-seed=123 --nohard-abort --testing-d8-test-runner",
+ "duration": 1,
"flags": [
- "--random-seed=123",
- "strawberries",
- "--nohard-abort"
- ],
- "marked_slow": true,
+ "--test",
+ "strawberries",
+ "--random-seed=123",
+ "--nohard-abort",
+ "--testing-d8-test-runner"
+ ],
+ "marked_slow": true,
"name": "sweet/strawberries"
- },
+ },
{
- "command": "/usr/bin/python out/Release/d8_mocked.py --random-seed=123 strawberries --nohard-abort",
- "duration": 1,
+ "command": "/usr/bin/python out/Release/d8_mocked.py --test strawberries --random-seed=123 --nohard-abort --testing-d8-test-runner",
+ "duration": 1,
"flags": [
- "--random-seed=123",
- "strawberries",
- "--nohard-abort"
- ],
- "marked_slow": true,
+ "--test",
+ "strawberries",
+ "--random-seed=123",
+ "--nohard-abort",
+ "--testing-d8-test-runner"
+ ],
+ "marked_slow": true,
"name": "sweet/strawberries"
- },
+ },
{
- "command": "/usr/bin/python out/Release/d8_mocked.py --random-seed=123 strawberries --nohard-abort",
- "duration": 1,
+ "command": "/usr/bin/python out/Release/d8_mocked.py --test strawberries --random-seed=123 --nohard-abort --testing-d8-test-runner",
+ "duration": 1,
"flags": [
- "--random-seed=123",
- "strawberries",
- "--nohard-abort"
- ],
- "marked_slow": true,
+ "--test",
+ "strawberries",
+ "--random-seed=123",
+ "--nohard-abort",
+ "--testing-d8-test-runner"
+ ],
+ "marked_slow": true,
"name": "sweet/strawberries"
}
- ],
+ ],
"test_total": 3
}
-
diff --git a/src/v8/tools/unittests/testdata/expected_test_results2.json b/src/v8/tools/unittests/testdata/expected_test_results2.json
index 7fcfe47..fd17972 100644
--- a/src/v8/tools/unittests/testdata/expected_test_results2.json
+++ b/src/v8/tools/unittests/testdata/expected_test_results2.json
@@ -1,74 +1,82 @@
{
- "arch": "x64",
- "duration_mean": 1,
- "mode": "release",
+ "arch": "x64",
+ "duration_mean": 1,
+ "mode": "release",
"results": [
{
- "command": "/usr/bin/python out/Release/d8_mocked.py --random-seed=123 bananaflakes --nohard-abort",
- "duration": 1,
- "exit_code": 1,
+ "command": "/usr/bin/python out/Release/d8_mocked.py bananaflakes --random-seed=123 --nohard-abort --testing-d8-test-runner",
+ "duration": 1,
+ "exit_code": 1,
"expected": [
"PASS"
- ],
+ ],
"flags": [
- "--random-seed=123",
- "bananaflakes",
- "--nohard-abort"
- ],
- "name": "sweet/bananaflakes",
- "random_seed": 123,
- "result": "FAIL",
- "run": 1,
- "stderr": "",
- "stdout": "--random-seed=123 bananaflakes --nohard-abort\n",
- "target_name": "d8_mocked.py",
- "variant": "default"
- },
+ "bananaflakes",
+ "--random-seed=123",
+ "--nohard-abort",
+ "--testing-d8-test-runner"
+ ],
+ "framework_name": "standard_runner",
+ "name": "sweet/bananaflakes",
+ "random_seed": 123,
+ "result": "FAIL",
+ "run": 1,
+ "stderr": "",
+ "stdout": "bananaflakes --random-seed=123 --nohard-abort --testing-d8-test-runner\n",
+ "target_name": "d8_mocked.py",
+ "variant": "default",
+ "variant_flags": []
+ },
{
- "command": "/usr/bin/python out/Release/d8_mocked.py --random-seed=123 bananaflakes --nohard-abort",
- "duration": 1,
- "exit_code": 0,
+ "command": "/usr/bin/python out/Release/d8_mocked.py bananaflakes --random-seed=123 --nohard-abort --testing-d8-test-runner",
+ "duration": 1,
+ "exit_code": 0,
"expected": [
"PASS"
- ],
+ ],
"flags": [
- "--random-seed=123",
- "bananaflakes",
- "--nohard-abort"
- ],
- "name": "sweet/bananaflakes",
- "random_seed": 123,
- "result": "PASS",
- "run": 2,
- "stderr": "",
- "stdout": "--random-seed=123 bananaflakes --nohard-abort\n",
- "target_name": "d8_mocked.py",
- "variant": "default"
+ "bananaflakes",
+ "--random-seed=123",
+ "--nohard-abort",
+ "--testing-d8-test-runner"
+ ],
+ "framework_name": "standard_runner",
+ "name": "sweet/bananaflakes",
+ "random_seed": 123,
+ "result": "PASS",
+ "run": 2,
+ "stderr": "",
+ "stdout": "bananaflakes --random-seed=123 --nohard-abort --testing-d8-test-runner\n",
+ "target_name": "d8_mocked.py",
+ "variant": "default",
+ "variant_flags": []
}
- ],
+ ],
"slowest_tests": [
{
- "command": "/usr/bin/python out/Release/d8_mocked.py --random-seed=123 bananaflakes --nohard-abort",
- "duration": 1,
+ "command": "/usr/bin/python out/Release/d8_mocked.py bananaflakes --random-seed=123 --nohard-abort --testing-d8-test-runner",
+ "duration": 1,
"flags": [
- "--random-seed=123",
- "bananaflakes",
- "--nohard-abort"
- ],
- "marked_slow": false,
+ "bananaflakes",
+ "--random-seed=123",
+ "--nohard-abort",
+ "--testing-d8-test-runner"
+ ],
+ "marked_slow": false,
"name": "sweet/bananaflakes"
- },
+ },
{
- "command": "/usr/bin/python out/Release/d8_mocked.py --random-seed=123 bananaflakes --nohard-abort",
- "duration": 1,
+ "command": "/usr/bin/python out/Release/d8_mocked.py bananaflakes --random-seed=123 --nohard-abort --testing-d8-test-runner",
+ "duration": 1,
"flags": [
- "--random-seed=123",
- "bananaflakes",
- "--nohard-abort"
- ],
- "marked_slow": false,
+ "bananaflakes",
+ "--random-seed=123",
+ "--nohard-abort",
+ "--testing-d8-test-runner"
+ ],
+ "marked_slow": false,
"name": "sweet/bananaflakes"
}
- ],
+ ],
"test_total": 2
}
diff --git a/src/v8/tools/unittests/testdata/predictable_mocked.py b/src/v8/tools/unittests/testdata/predictable_mocked.py
index cc332c2..b9e73f6 100644
--- a/src/v8/tools/unittests/testdata/predictable_mocked.py
+++ b/src/v8/tools/unittests/testdata/predictable_mocked.py
@@ -3,22 +3,25 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+# for py2/py3 compatibility
+from __future__ import print_function
+
import sys
assert len(sys.argv) == 3
if sys.argv[1] == 'equal':
# 1. Scenario: print equal allocation hashes.
- print '### Allocations = 9497, hash = 0xc322c6b0'
+ print('### Allocations = 9497, hash = 0xc322c6b0')
elif sys.argv[1] == 'differ':
# 2. Scenario: print different allocation hashes. This prints a different
# hash on the second run, based on the content of a semaphore file. This
# file is expected to be empty in the beginning.
with open(sys.argv[2]) as f:
if f.read():
- print '### Allocations = 9497, hash = 0xc322c6b0'
+ print('### Allocations = 9497, hash = 0xc322c6b0')
else:
- print '### Allocations = 9497, hash = 0xc322c6b1'
+ print('### Allocations = 9497, hash = 0xc322c6b1')
with open(sys.argv[2], 'w') as f:
f.write('something')
else:
diff --git a/src/v8/tools/unittests/testdata/results_processor.py b/src/v8/tools/unittests/testdata/results_processor.py
index 69c23e3..d8c5ad9 100644
--- a/src/v8/tools/unittests/testdata/results_processor.py
+++ b/src/v8/tools/unittests/testdata/results_processor.py
@@ -7,6 +7,9 @@
Fake results processor for testing that just sums some things up.
"""
+# for py2/py3 compatibility
+from __future__ import print_function
+
import fileinput
import re
@@ -21,5 +24,5 @@
if match:
deltablue += float(match.group(1))
-print 'Richards: %f' % richards
-print 'DeltaBlue: %f' % deltablue
+print('Richards: %f' % richards)
+print('DeltaBlue: %f' % deltablue)
diff --git a/src/v8/tools/unittests/testdata/test1.json b/src/v8/tools/unittests/testdata/test1.json
index 7fa1faa..939d6e2 100644
--- a/src/v8/tools/unittests/testdata/test1.json
+++ b/src/v8/tools/unittests/testdata/test1.json
@@ -1,5 +1,6 @@
{
"path": ["."],
+ "owners": ["username@chromium.org"],
"flags": [],
"main": "run.js",
"run_count": 2,
diff --git a/src/v8/tools/unittests/testdata/test2.json b/src/v8/tools/unittests/testdata/test2.json
index 79fed26..632c4e5 100644
--- a/src/v8/tools/unittests/testdata/test2.json
+++ b/src/v8/tools/unittests/testdata/test2.json
@@ -1,5 +1,6 @@
{
"path": ["."],
+ "owners": ["username@chromium.org"],
"flags": [],
"main": "run.js",
"run_count": 2,
diff --git a/src/v8/tools/unittests/testdata/test3.json b/src/v8/tools/unittests/testdata/test3.json
index 1b7ef96..3e871de 100644
--- a/src/v8/tools/unittests/testdata/test3.json
+++ b/src/v8/tools/unittests/testdata/test3.json
@@ -1,5 +1,6 @@
{
"path": ["."],
+ "owners": ["username@chromium.org"],
"flags": [],
"run_count": 1,
"results_processor": "results_processor.py",
diff --git a/src/v8/tools/unittests/testdata/testroot1/d8_mocked.py b/src/v8/tools/unittests/testdata/testroot1/d8_mocked.py
index c7ca55a..d67e030 100644
--- a/src/v8/tools/unittests/testdata/testroot1/d8_mocked.py
+++ b/src/v8/tools/unittests/testdata/testroot1/d8_mocked.py
@@ -6,10 +6,13 @@
Dummy d8 replacement. Just passes all test, except if 'berries' is in args.
"""
+# for py2/py3 compatibility
+from __future__ import print_function
+
import sys
args = ' '.join(sys.argv[1:])
-print args
+print(args)
# Let all berries fail.
if 'berries' in args:
sys.exit(1)
diff --git a/src/v8/tools/unittests/testdata/testroot1/test/sweet/sweet.status b/src/v8/tools/unittests/testdata/testroot1/test/sweet/sweet.status
index 7421463..d823cfd 100644
--- a/src/v8/tools/unittests/testdata/testroot1/test/sweet/sweet.status
+++ b/src/v8/tools/unittests/testdata/testroot1/test/sweet/sweet.status
@@ -6,6 +6,7 @@
[ALWAYS, {
'raspberries': FAIL,
'strawberries': [PASS, ['mode == release', SLOW], ['mode == debug', NO_VARIANTS]],
+ 'mangoes': [PASS, SLOW],
# Both cherries and apples are to test how PASS an FAIL from different
# sections are merged.
diff --git a/src/v8/tools/unittests/testdata/testroot1/test/sweet/testcfg.py b/src/v8/tools/unittests/testdata/testroot1/test/sweet/testcfg.py
index 115471a..a2dfc9d 100644
--- a/src/v8/tools/unittests/testdata/testroot1/test/sweet/testcfg.py
+++ b/src/v8/tools/unittests/testdata/testroot1/test/sweet/testcfg.py
@@ -9,23 +9,28 @@
from testrunner.local import testsuite
from testrunner.objects import testcase
+class TestLoader(testsuite.TestLoader):
+ def _list_test_filenames(self):
+ return [
+ 'bananas', 'apples', 'cherries', 'mangoes', 'strawberries',
+ 'blackberries', 'raspberries',
+ ]
+
+
class TestSuite(testsuite.TestSuite):
- def ListTests(self, context):
- return map(
- self._create_test,
- ['bananas', 'apples', 'cherries', 'strawberries', 'raspberries'],
- )
+ def _test_loader_class(self):
+ return TestLoader
def _test_class(self):
return TestCase
-class TestCase(testcase.TestCase):
+class TestCase(testcase.D8TestCase):
def get_shell(self):
return 'd8_mocked.py'
- def _get_files_params(self, ctx):
+ def _get_files_params(self):
return [self.name]
-def GetSuite(name, root):
- return TestSuite(name, root)
+def GetSuite(*args, **kwargs):
+ return TestSuite(*args, **kwargs)
diff --git a/src/v8/tools/unittests/testdata/testroot1/v8_build_config.json b/src/v8/tools/unittests/testdata/testroot1/v8_build_config.json
index c5e3ee3..0192fd8 100644
--- a/src/v8/tools/unittests/testdata/testroot1/v8_build_config.json
+++ b/src/v8/tools/unittests/testdata/testroot1/v8_build_config.json
@@ -1,10 +1,13 @@
{
"current_cpu": "x64",
"dcheck_always_on": false,
+ "is_android": false,
"is_asan": false,
"is_cfi": false,
+ "is_clang": true,
"is_component_build": false,
"is_debug": false,
+ "is_full_debug": false,
"is_gcov_coverage": false,
"is_ubsan_vptr": false,
"is_msan": false,
@@ -14,5 +17,9 @@
"v8_enable_i18n_support": true,
"v8_enable_verify_predictable": false,
"v8_target_cpu": "x64",
- "v8_use_snapshot": true
+ "v8_use_snapshot": true,
+ "v8_enable_embedded_builtins": false,
+ "v8_enable_verify_csa": false,
+ "v8_enable_lite_mode": false,
+ "v8_enable_pointer_compression": true
}
diff --git a/src/v8/tools/unittests/testdata/testroot2/d8_mocked.py b/src/v8/tools/unittests/testdata/testroot2/d8_mocked.py
index e66e299..48d6bce 100644
--- a/src/v8/tools/unittests/testdata/testroot2/d8_mocked.py
+++ b/src/v8/tools/unittests/testdata/testroot2/d8_mocked.py
@@ -6,12 +6,15 @@
Dummy d8 replacement for flaky tests.
"""
+# for py2/py3 compatibility
+from __future__ import print_function
+
import os
import sys
PATH = os.path.dirname(os.path.abspath(__file__))
-print ' '.join(sys.argv[1:])
+print(' '.join(sys.argv[1:]))
# Test files ending in 'flakes' should first fail then pass. We store state in
# a file side by side with the executable. No clean-up required as all tests
diff --git a/src/v8/tools/unittests/testdata/testroot2/test/sweet/testcfg.py b/src/v8/tools/unittests/testdata/testroot2/test/sweet/testcfg.py
index 9407769..3606cd3 100644
--- a/src/v8/tools/unittests/testdata/testroot2/test/sweet/testcfg.py
+++ b/src/v8/tools/unittests/testdata/testroot2/test/sweet/testcfg.py
@@ -9,12 +9,15 @@
from testrunner.local import testsuite
from testrunner.objects import testcase
+
+class TestLoader(testsuite.TestLoader):
+ def _list_test_filenames(self):
+ return ['bananaflakes']
+
+
class TestSuite(testsuite.TestSuite):
- def ListTests(self, context):
- return map(
- self._create_test,
- ['bananaflakes'],
- )
+ def _test_loader_class(self):
+ return TestLoader
def _test_class(self):
return TestCase
@@ -24,8 +27,8 @@
def get_shell(self):
return 'd8_mocked.py'
- def _get_files_params(self, ctx):
+ def _get_files_params(self):
return [self.name]
-def GetSuite(name, root):
- return TestSuite(name, root)
+def GetSuite(*args, **kwargs):
+ return TestSuite(*args, **kwargs)
diff --git a/src/v8/tools/unittests/testdata/testroot2/v8_build_config.json b/src/v8/tools/unittests/testdata/testroot2/v8_build_config.json
index c5e3ee3..f19c310 100644
--- a/src/v8/tools/unittests/testdata/testroot2/v8_build_config.json
+++ b/src/v8/tools/unittests/testdata/testroot2/v8_build_config.json
@@ -1,10 +1,13 @@
{
"current_cpu": "x64",
"dcheck_always_on": false,
+ "is_android": false,
"is_asan": false,
"is_cfi": false,
+ "is_clang": true,
"is_component_build": false,
"is_debug": false,
+ "is_full_debug": false,
"is_gcov_coverage": false,
"is_ubsan_vptr": false,
"is_msan": false,
@@ -14,5 +17,9 @@
"v8_enable_i18n_support": true,
"v8_enable_verify_predictable": false,
"v8_target_cpu": "x64",
- "v8_use_snapshot": true
+ "v8_use_snapshot": true,
+ "v8_enable_embedded_builtins": false,
+ "v8_enable_verify_csa": false,
+ "v8_enable_lite_mode": false,
+ "v8_enable_pointer_compression": false
}
diff --git a/src/v8/tools/unittests/v8_presubmit_test.py b/src/v8/tools/unittests/v8_presubmit_test.py
new file mode 100755
index 0000000..2c66d18
--- /dev/null
+++ b/src/v8/tools/unittests/v8_presubmit_test.py
@@ -0,0 +1,91 @@
+#!/usr/bin/env python
+# 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.
+
+import os
+import sys
+import tempfile
+import unittest
+
+# Configuring the path for the v8_presubmit module
+TOOLS_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+sys.path.append(TOOLS_ROOT)
+
+from v8_presubmit import FileContentsCache, CacheableSourceFileProcessor
+
+
+class FakeCachedProcessor(CacheableSourceFileProcessor):
+ def __init__(self, cache_file_path):
+ super(FakeCachedProcessor, self).__init__(
+ use_cache=True, cache_file_path=cache_file_path, file_type='.test')
+ def GetProcessorWorker(self):
+ return object
+ def GetProcessorScript(self):
+ return "echo", []
+ def DetectUnformattedFiles(_, cmd, worker, files):
+ raise NotImplementedError
+
+class FileContentsCacheTest(unittest.TestCase):
+ def setUp(self):
+ _, self.cache_file_path = tempfile.mkstemp()
+ cache = FileContentsCache(self.cache_file_path)
+ cache.Load()
+
+ def generate_file():
+ _, file_name = tempfile.mkstemp()
+ with open(file_name, "w") as f:
+ f.write(file_name)
+
+ return file_name
+
+ self.target_files = [generate_file() for _ in range(2)]
+ unchanged_files = cache.FilterUnchangedFiles(self.target_files)
+ self.assertEqual(len(unchanged_files), 2)
+ cache.Save()
+
+ def tearDown(self):
+ for file in [self.cache_file_path] + self.target_files:
+ os.remove(file)
+
+ def testCachesFiles(self):
+ cache = FileContentsCache(self.cache_file_path)
+ cache.Load()
+
+ changed_files = cache.FilterUnchangedFiles(self.target_files)
+ self.assertListEqual(changed_files, [])
+
+ modified_file = self.target_files[0]
+ with open(modified_file, "w") as f:
+ f.write("modification")
+
+ changed_files = cache.FilterUnchangedFiles(self.target_files)
+ self.assertListEqual(changed_files, [modified_file])
+
+ def testCacheableSourceFileProcessor(self):
+ class CachedProcessor(FakeCachedProcessor):
+ def DetectFilesToChange(_, files):
+ self.assertListEqual(files, [])
+ return []
+
+ cached_processor = CachedProcessor(cache_file_path=self.cache_file_path)
+ cached_processor.ProcessFiles(self.target_files)
+
+ def testCacheableSourceFileProcessorWithModifications(self):
+ modified_file = self.target_files[0]
+ with open(modified_file, "w") as f:
+ f.write("modification")
+
+ class CachedProcessor(FakeCachedProcessor):
+ def DetectFilesToChange(_, files):
+ self.assertListEqual(files, [modified_file])
+ return []
+
+ cached_processor = CachedProcessor(
+ cache_file_path=self.cache_file_path,
+ )
+ cached_processor.ProcessFiles(self.target_files)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/src/v8/tools/update-object-macros-undef.py b/src/v8/tools/update-object-macros-undef.py
new file mode 100755
index 0000000..866fdb6
--- /dev/null
+++ b/src/v8/tools/update-object-macros-undef.py
@@ -0,0 +1,47 @@
+#!/usr/bin/env python3
+
+# 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.
+# vim:fenc=utf-8:shiftwidth=2:tabstop=2:softtabstop=2:extandtab
+
+"""
+Generate object-macros-undef.h from object-macros.h.
+"""
+
+import os.path
+import re
+import sys
+
+INPUT = 'src/objects/object-macros.h'
+OUTPUT = 'src/objects/object-macros-undef.h'
+HEADER = """// Copyright 2016 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.
+
+// Generate this file using the {} script.
+
+// PRESUBMIT_INTENTIONALLY_MISSING_INCLUDE_GUARD
+
+""".format(os.path.basename(__file__))
+
+
+def main():
+ if not os.path.isfile(INPUT):
+ sys.exit("Input file {} does not exist; run this script in a v8 checkout."
+ .format(INPUT))
+ if not os.path.isfile(OUTPUT):
+ sys.exit("Output file {} does not exist; run this script in a v8 checkout."
+ .format(OUTPUT))
+ regexp = re.compile('^#define (\w+)')
+ seen = set()
+ with open(INPUT, 'r') as infile, open(OUTPUT, 'w') as outfile:
+ outfile.write(HEADER)
+ for line in infile:
+ match = regexp.match(line)
+ if match and match.group(1) not in seen:
+ seen.add(match.group(1))
+ outfile.write('#undef {}\n'.format(match.group(1)))
+
+if __name__ == "__main__":
+ main()
diff --git a/src/v8/tools/presubmit.py b/src/v8/tools/v8_presubmit.py
similarity index 71%
rename from src/v8/tools/presubmit.py
rename to src/v8/tools/v8_presubmit.py
index 9ac26dd..346fc9a 100755
--- a/src/v8/tools/presubmit.py
+++ b/src/v8/tools/v8_presubmit.py
@@ -27,10 +27,14 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+# for py2/py3 compatibility
+from __future__ import print_function
+
try:
import hashlib
md5er = hashlib.md5
-except ImportError, e:
+except ImportError as e:
import md5
md5er = md5.new
@@ -52,6 +56,7 @@
# Special LINT rules diverging from default and reason.
# build/header_guard: Our guards have the form "V8_FOO_H_", not "SRC_FOO_H_".
+# 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.
@@ -59,7 +64,8 @@
-build/header_guard
-build/include_what_you_use
-readability/fn_size
--runtime/references
+-readability/multiline_comment
+-whitespace/comments
""".split()
LINT_OUTPUT_PATTERN = re.compile(r'^.+[:(]\d+[:)]|^Done processing')
@@ -81,7 +87,7 @@
out_line = process.stderr.readline()
if out_line == '' and process.poll() != None:
if error_count == -1:
- print "Failed to process %s" % command.pop()
+ print("Failed to process %s" % command.pop())
return 1
break
m = LINT_OUTPUT_PATTERN.match(out_line)
@@ -97,6 +103,28 @@
' in your $PATH. Lint check skipped.')
process.kill()
+def TorqueLintWorker(command):
+ try:
+ process = subprocess.Popen(command, stderr=subprocess.PIPE)
+ process.wait()
+ out_lines = ""
+ error_count = 0
+ while True:
+ out_line = process.stderr.readline()
+ if out_line == '' and process.poll() != None:
+ break
+ out_lines += out_line
+ error_count += 1
+ sys.stdout.write(out_lines)
+ if error_count != 0:
+ sys.stdout.write(
+ "warning: formatting and overwriting unformatted Torque files\n")
+ return error_count
+ except KeyboardInterrupt:
+ process.kill()
+ except:
+ print('Error running format-torque.py')
+ process.kill()
class FileContentsCache(object):
@@ -204,17 +232,98 @@
return result
-class CppLintProcessor(SourceFileProcessor):
+class CacheableSourceFileProcessor(SourceFileProcessor):
+ """Utility class that allows caching ProcessFiles() method calls.
+
+ In order to use it, create a ProcessFilesWithoutCaching method that returns
+ the files requiring intervention after processing the source files.
+ """
+
+ def __init__(self, use_cache, cache_file_path, file_type):
+ self.use_cache = use_cache
+ self.cache_file_path = cache_file_path
+ self.file_type = file_type
+
+ def GetProcessorWorker(self):
+ """Expected to return the worker function to run the formatter."""
+ raise NotImplementedError
+
+ def GetProcessorScript(self):
+ """Expected to return a tuple
+ (path to the format processor script, list of arguments)."""
+ raise NotImplementedError
+
+ def GetProcessorCommand(self):
+ format_processor, options = self.GetProcessorScript()
+ if not format_processor:
+ print('Could not find the formatter for % files' % self.file_type)
+ sys.exit(1)
+
+ command = [sys.executable, format_processor]
+ command.extend(options)
+
+ return command
+
+ def ProcessFiles(self, files):
+ if self.use_cache:
+ cache = FileContentsCache(self.cache_file_path)
+ cache.Load()
+ files = cache.FilterUnchangedFiles(files)
+
+ if len(files) == 0:
+ print('No changes in %s files detected. Skipping check' % self.file_type)
+ return True
+
+ files_requiring_changes = self.DetectFilesToChange(files)
+ print (
+ 'Total %s files found that require formatting: %d' %
+ (self.file_type, len(files_requiring_changes)))
+ if self.use_cache:
+ for file in files_requiring_changes:
+ cache.RemoveFile(file)
+
+ cache.Save()
+
+ return files_requiring_changes == []
+
+ def DetectFilesToChange(self, files):
+ command = self.GetProcessorCommand()
+ worker = self.GetProcessorWorker()
+
+ commands = [command + [file] for file in files]
+ count = multiprocessing.cpu_count()
+ pool = multiprocessing.Pool(count)
+ try:
+ results = pool.map_async(worker, commands).get(timeout=240)
+ except KeyboardInterrupt:
+ print("\nCaught KeyboardInterrupt, terminating workers.")
+ pool.terminate()
+ pool.join()
+ sys.exit(1)
+
+ unformatted_files = []
+ for index, errors in enumerate(results):
+ if errors > 0:
+ unformatted_files.append(files[index])
+
+ return unformatted_files
+
+
+class CppLintProcessor(CacheableSourceFileProcessor):
"""
Lint files to check that they follow the google code style.
"""
+ def __init__(self, use_cache=True):
+ super(CppLintProcessor, self).__init__(
+ use_cache=use_cache, cache_file_path='.cpplint-cache', file_type='C/C++')
+
def IsRelevant(self, name):
return name.endswith('.cc') or name.endswith('.h')
def IgnoreDir(self, name):
return (super(CppLintProcessor, self).IgnoreDir(name)
- or (name == 'third_party'))
+ or (name == 'third_party'))
IGNORE_LINT = ['export-template.h', 'flag-definitions.h']
@@ -227,50 +336,50 @@
test_dirs = ['cctest', 'common', 'fuzzer', 'inspector', 'unittests']
return dirs + [join('test', dir) for dir in test_dirs]
- def GetCpplintScript(self, prio_path):
- for path in [prio_path] + os.environ["PATH"].split(os.pathsep):
+ def GetProcessorWorker(self):
+ return CppLintWorker
+
+ def GetProcessorScript(self):
+ filters = ','.join([n for n in LINT_RULES])
+ arguments = ['--filter', filters]
+ for path in [TOOLS_PATH] + os.environ["PATH"].split(os.pathsep):
path = path.strip('"')
- cpplint = os.path.join(path, "cpplint.py")
+ cpplint = os.path.join(path, 'cpplint.py')
if os.path.isfile(cpplint):
- return cpplint
+ return cpplint, arguments
- return None
+ return None, arguments
- def ProcessFiles(self, files):
- good_files_cache = FileContentsCache('.cpplint-cache')
- good_files_cache.Load()
- files = good_files_cache.FilterUnchangedFiles(files)
- if len(files) == 0:
- print 'No changes in files detected. Skipping cpplint check.'
- return True
- filters = ",".join([n for n in LINT_RULES])
- cpplint = self.GetCpplintScript(TOOLS_PATH)
- if cpplint is None:
- print('Could not find cpplint.py. Make sure '
- 'depot_tools is installed and in the path.')
- sys.exit(1)
+class TorqueLintProcessor(CacheableSourceFileProcessor):
+ """
+ Check .tq files to verify they follow the Torque style guide.
+ """
- command = [sys.executable, cpplint, '--filter', filters]
+ def __init__(self, use_cache=True):
+ super(TorqueLintProcessor, self).__init__(
+ use_cache=use_cache, cache_file_path='.torquelint-cache',
+ file_type='Torque')
- commands = [command + [file] for file in files]
- count = multiprocessing.cpu_count()
- pool = multiprocessing.Pool(count)
- try:
- results = pool.map_async(CppLintWorker, commands).get(999999)
- except KeyboardInterrupt:
- print "\nCaught KeyboardInterrupt, terminating workers."
- sys.exit(1)
+ def IsRelevant(self, name):
+ return name.endswith('.tq')
- for i in range(len(files)):
- if results[i] > 0:
- good_files_cache.RemoveFile(files[i])
+ def GetPathsToSearch(self):
+ dirs = ['third_party', 'src']
+ test_dirs = ['torque']
+ return dirs + [join('test', dir) for dir in test_dirs]
- total_errors = sum(results)
- print "Total errors found: %d" % total_errors
- good_files_cache.Save()
- return total_errors == 0
+ def GetProcessorWorker(self):
+ return TorqueLintWorker
+ def GetProcessorScript(self):
+ torque_tools = os.path.join(TOOLS_PATH, "torque")
+ torque_path = os.path.join(torque_tools, "format-torque.py")
+ arguments = ["-il"]
+ if os.path.isfile(torque_path):
+ return torque_path, arguments
+
+ return None, arguments
COPYRIGHT_HEADER_PATTERN = re.compile(
r'Copyright [\d-]*20[0-1][0-9] the V8 project authors. All rights reserved.')
@@ -280,8 +389,7 @@
Check that all files include a copyright notice and no trailing whitespaces.
"""
- RELEVANT_EXTENSIONS = ['.js', '.cc', '.h', '.py', '.c',
- '.status', '.gyp', '.gypi']
+ RELEVANT_EXTENSIONS = ['.js', '.cc', '.h', '.py', '.c', '.status', '.tq', '.g4']
def __init__(self):
self.runtime_function_call_pattern = self.CreateRuntimeFunctionCallMatcher()
@@ -295,7 +403,7 @@
m = pattern.match(line)
if m:
runtime_functions.append(m.group(1))
- if len(runtime_functions) < 500:
+ if len(runtime_functions) < 250:
print ("Runtime functions list is suspiciously short. "
"Consider updating the presubmit script.")
sys.exit(1)
@@ -331,26 +439,19 @@
def IgnoreDir(self, name):
return (super(SourceProcessor, self).IgnoreDir(name) or
- name in ('third_party', 'gyp', 'out', 'obj', 'DerivedSources'))
+ name in ('third_party', 'out', 'obj', 'DerivedSources'))
IGNORE_COPYRIGHTS = ['box2d.js',
'cpplint.py',
- 'check_injected_script_source.py',
'copy.js',
'corrections.js',
'crypto.js',
'daemon.py',
- 'debugger-script.js',
'earley-boyer.js',
'fannkuch.js',
'fasta.js',
- 'generate_protocol_externs.py',
'injected-script.cc',
'injected-script.h',
- 'injected-script-source.js',
- 'java-script-call-frame.cc',
- 'java-script-call-frame.h',
- 'jsmin.py',
'libraries.cc',
'libraries-empty.cc',
'lua_binarytrees.js',
@@ -361,14 +462,11 @@
'raytrace.js',
'regexp-pcre.js',
'resources-123.js',
- 'rjsmin.py',
'sqlite.js',
'sqlite-change-heap.js',
'sqlite-pointer-masking.js',
'sqlite-safe-heap.js',
'v8-debugger-script.h',
- 'v8-function-call.cc',
- 'v8-function-call.h',
'v8-inspector-impl.cc',
'v8-inspector-impl.h',
'v8-runtime-agent-impl.cc',
@@ -377,7 +475,10 @@
'zlib.js']
IGNORE_TABS = IGNORE_COPYRIGHTS + ['unicode-test.js', 'html-comments.js']
- IGNORE_COPYRIGHTS_DIRECTORY = "test/test262/local-tests"
+ IGNORE_COPYRIGHTS_DIRECTORIES = [
+ "test/test262/local-tests",
+ "test/mjsunit/wasm/bulk-memory-spec",
+ ]
def EndOfDeclaration(self, line):
return line == "}" or line == "};"
@@ -392,12 +493,13 @@
base = basename(name)
if not base in SourceProcessor.IGNORE_TABS:
if '\t' in contents:
- print "%s contains tabs" % name
+ print("%s contains tabs" % name)
result = False
if not base in SourceProcessor.IGNORE_COPYRIGHTS and \
- not SourceProcessor.IGNORE_COPYRIGHTS_DIRECTORY in name:
+ not any(ignore_dir in name for ignore_dir
+ in SourceProcessor.IGNORE_COPYRIGHTS_DIRECTORIES):
if not COPYRIGHT_HEADER_PATTERN.search(contents):
- print "%s is missing a correct copyright header." % name
+ print("%s is missing a correct copyright header." % name)
result = False
if ' \n' in contents or contents.endswith(' '):
line = 0
@@ -410,34 +512,35 @@
lines.append(str(line))
linenumbers = ', '.join(lines)
if len(lines) > 1:
- print "%s has trailing whitespaces in lines %s." % (name, linenumbers)
+ print("%s has trailing whitespaces in lines %s." % (name, linenumbers))
else:
- print "%s has trailing whitespaces in line %s." % (name, linenumbers)
+ print("%s has trailing whitespaces in line %s." % (name, linenumbers))
result = False
if not contents.endswith('\n') or contents.endswith('\n\n'):
- print "%s does not end with a single new line." % name
+ print("%s does not end with a single new line." % name)
result = False
# Sanitize flags for fuzzer.
- if "mjsunit" in name or "debugger" in name:
+ if (".js" in name or ".mjs" in name) and ("mjsunit" in name or "debugger" in name):
match = FLAGS_LINE.search(contents)
if match:
- print "%s Flags should use '-' (not '_')" % name
+ print("%s Flags should use '-' (not '_')" % name)
result = False
- if not "mjsunit/mjsunit.js" in name:
+ if (not "mjsunit/mjsunit.js" in name and
+ not "mjsunit/mjsunit_numfuzz.js" in name):
if ASSERT_OPTIMIZED_PATTERN.search(contents) and \
not FLAGS_ENABLE_OPT.search(contents):
- print "%s Flag --opt should be set if " \
- "assertOptimized() is used" % name
+ print("%s Flag --opt should be set if " \
+ "assertOptimized() is used" % name)
result = False
if ASSERT_UNOPTIMIZED_PATTERN.search(contents) and \
not FLAGS_NO_ALWAYS_OPT.search(contents):
- print "%s Flag --no-always-opt should be set if " \
- "assertUnoptimized() is used" % name
+ print("%s Flag --no-always-opt should be set if " \
+ "assertUnoptimized() is used" % name)
result = False
match = self.runtime_function_call_pattern.search(contents)
if match:
- print "%s has unexpected spaces in a runtime call '%s'" % (name, match.group(1))
+ print("%s has unexpected spaces in a runtime call '%s'" % (name, match.group(1)))
result = False
return result
@@ -448,12 +551,12 @@
try:
handle = open(file)
contents = handle.read()
- if not self.ProcessContents(file, contents):
+ if len(contents) > 0 and not self.ProcessContents(file, contents):
success = False
violations += 1
finally:
handle.close()
- print "Total violating files: %s" % violations
+ print("Total violating files: %s" % violations)
return success
def _CheckStatusFileForDuplicateKeys(filepath):
@@ -556,12 +659,16 @@
def PyTests(workspace):
result = True
for script in [
+ join(workspace, 'tools', 'clusterfuzz', 'v8_foozzie_test.py'),
join(workspace, 'tools', 'release', 'test_scripts.py'),
join(workspace, 'tools', 'unittests', 'run_tests_test.py'),
+ join(workspace, 'tools', 'unittests', 'run_perf_test.py'),
+ join(workspace, 'tools', 'testrunner', 'testproc', 'variant_unittest.py'),
]:
- print 'Running ' + script
+ print('Running ' + script)
result &= subprocess.call(
[sys.executable, script], stdout=subprocess.PIPE) == 0
+
return result
@@ -569,6 +676,9 @@
result = optparse.OptionParser()
result.add_option('--no-lint', help="Do not run cpplint", default=False,
action="store_true")
+ result.add_option('--no-linter-cache', help="Do not cache linter results",
+ default=False, action="store_true")
+
return result
@@ -577,17 +687,22 @@
parser = GetOptions()
(options, args) = parser.parse_args()
success = True
- print "Running checkdeps..."
+ print("Running checkdeps...")
success &= CheckDeps(workspace)
+ use_linter_cache = not options.no_linter_cache
if not options.no_lint:
- print "Running C++ lint check..."
- success &= CppLintProcessor().RunOnPath(workspace)
- print "Running copyright header, trailing whitespaces and " \
- "two empty lines between declarations check..."
+ print("Running C++ lint check...")
+ success &= CppLintProcessor(use_cache=use_linter_cache).RunOnPath(workspace)
+
+ print("Running Torque formatting check...")
+ success &= TorqueLintProcessor(use_cache=use_linter_cache).RunOnPath(
+ workspace)
+ print("Running copyright header, trailing whitespaces and " \
+ "two empty lines between declarations check...")
success &= SourceProcessor().RunOnPath(workspace)
- print "Running status-files check..."
+ print("Running status-files check...")
success &= StatusFilesProcessor().RunOnPath(workspace)
- print "Running python tests..."
+ print("Running python tests...")
success &= PyTests(workspace)
if success:
return 0
diff --git a/src/v8/tools/v8heapconst.py b/src/v8/tools/v8heapconst.py
index c96741a..c6c98c0 100644
--- a/src/v8/tools/v8heapconst.py
+++ b/src/v8/tools/v8heapconst.py
@@ -1,4 +1,4 @@
-# Copyright 2017 the V8 project authors. All rights reserved.
+# Copyright 2019 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.
@@ -11,324 +11,418 @@
2: "EXTERNAL_INTERNALIZED_STRING_TYPE",
8: "ONE_BYTE_INTERNALIZED_STRING_TYPE",
10: "EXTERNAL_ONE_BYTE_INTERNALIZED_STRING_TYPE",
- 18: "EXTERNAL_INTERNALIZED_STRING_WITH_ONE_BYTE_DATA_TYPE",
- 34: "SHORT_EXTERNAL_INTERNALIZED_STRING_TYPE",
- 42: "SHORT_EXTERNAL_ONE_BYTE_INTERNALIZED_STRING_TYPE",
- 50: "SHORT_EXTERNAL_INTERNALIZED_STRING_WITH_ONE_BYTE_DATA_TYPE",
- 64: "STRING_TYPE",
- 65: "CONS_STRING_TYPE",
- 66: "EXTERNAL_STRING_TYPE",
- 67: "SLICED_STRING_TYPE",
- 69: "THIN_STRING_TYPE",
- 72: "ONE_BYTE_STRING_TYPE",
- 73: "CONS_ONE_BYTE_STRING_TYPE",
- 74: "EXTERNAL_ONE_BYTE_STRING_TYPE",
- 75: "SLICED_ONE_BYTE_STRING_TYPE",
- 77: "THIN_ONE_BYTE_STRING_TYPE",
- 82: "EXTERNAL_STRING_WITH_ONE_BYTE_DATA_TYPE",
- 98: "SHORT_EXTERNAL_STRING_TYPE",
- 106: "SHORT_EXTERNAL_ONE_BYTE_STRING_TYPE",
- 114: "SHORT_EXTERNAL_STRING_WITH_ONE_BYTE_DATA_TYPE",
- 128: "SYMBOL_TYPE",
- 129: "HEAP_NUMBER_TYPE",
- 130: "BIGINT_TYPE",
- 131: "ODDBALL_TYPE",
- 132: "MAP_TYPE",
- 133: "CODE_TYPE",
- 134: "MUTABLE_HEAP_NUMBER_TYPE",
- 135: "FOREIGN_TYPE",
- 136: "BYTE_ARRAY_TYPE",
- 137: "BYTECODE_ARRAY_TYPE",
- 138: "FREE_SPACE_TYPE",
- 139: "FIXED_INT8_ARRAY_TYPE",
- 140: "FIXED_UINT8_ARRAY_TYPE",
- 141: "FIXED_INT16_ARRAY_TYPE",
- 142: "FIXED_UINT16_ARRAY_TYPE",
- 143: "FIXED_INT32_ARRAY_TYPE",
- 144: "FIXED_UINT32_ARRAY_TYPE",
- 145: "FIXED_FLOAT32_ARRAY_TYPE",
- 146: "FIXED_FLOAT64_ARRAY_TYPE",
- 147: "FIXED_UINT8_CLAMPED_ARRAY_TYPE",
- 148: "FIXED_DOUBLE_ARRAY_TYPE",
- 149: "FILLER_TYPE",
- 150: "ACCESS_CHECK_INFO_TYPE",
- 151: "ACCESSOR_INFO_TYPE",
- 152: "ACCESSOR_PAIR_TYPE",
- 153: "ALIASED_ARGUMENTS_ENTRY_TYPE",
- 154: "ALLOCATION_MEMENTO_TYPE",
- 155: "ALLOCATION_SITE_TYPE",
- 156: "ASYNC_GENERATOR_REQUEST_TYPE",
- 157: "CONTEXT_EXTENSION_TYPE",
- 158: "DEBUG_INFO_TYPE",
- 159: "FUNCTION_TEMPLATE_INFO_TYPE",
- 160: "INTERCEPTOR_INFO_TYPE",
- 161: "MODULE_INFO_ENTRY_TYPE",
- 162: "MODULE_TYPE",
- 163: "OBJECT_TEMPLATE_INFO_TYPE",
- 164: "PROMISE_REACTION_JOB_INFO_TYPE",
- 165: "PROMISE_RESOLVE_THENABLE_JOB_INFO_TYPE",
- 166: "PROTOTYPE_INFO_TYPE",
- 167: "SCRIPT_TYPE",
- 168: "STACK_FRAME_INFO_TYPE",
- 169: "TUPLE2_TYPE",
- 170: "TUPLE3_TYPE",
- 171: "FIXED_ARRAY_TYPE",
- 172: "DESCRIPTOR_ARRAY_TYPE",
- 173: "HASH_TABLE_TYPE",
- 174: "TRANSITION_ARRAY_TYPE",
- 175: "CELL_TYPE",
- 176: "CODE_DATA_CONTAINER_TYPE",
- 177: "FEEDBACK_VECTOR_TYPE",
- 178: "LOAD_HANDLER_TYPE",
- 179: "PROPERTY_ARRAY_TYPE",
- 180: "PROPERTY_CELL_TYPE",
- 181: "SHARED_FUNCTION_INFO_TYPE",
- 182: "SMALL_ORDERED_HASH_MAP_TYPE",
- 183: "SMALL_ORDERED_HASH_SET_TYPE",
- 184: "STORE_HANDLER_TYPE",
- 185: "WEAK_CELL_TYPE",
+ 18: "UNCACHED_EXTERNAL_INTERNALIZED_STRING_TYPE",
+ 26: "UNCACHED_EXTERNAL_ONE_BYTE_INTERNALIZED_STRING_TYPE",
+ 32: "STRING_TYPE",
+ 33: "CONS_STRING_TYPE",
+ 34: "EXTERNAL_STRING_TYPE",
+ 35: "SLICED_STRING_TYPE",
+ 37: "THIN_STRING_TYPE",
+ 40: "ONE_BYTE_STRING_TYPE",
+ 41: "CONS_ONE_BYTE_STRING_TYPE",
+ 42: "EXTERNAL_ONE_BYTE_STRING_TYPE",
+ 43: "SLICED_ONE_BYTE_STRING_TYPE",
+ 45: "THIN_ONE_BYTE_STRING_TYPE",
+ 50: "UNCACHED_EXTERNAL_STRING_TYPE",
+ 58: "UNCACHED_EXTERNAL_ONE_BYTE_STRING_TYPE",
+ 64: "SYMBOL_TYPE",
+ 65: "HEAP_NUMBER_TYPE",
+ 66: "BIGINT_TYPE",
+ 67: "ODDBALL_TYPE",
+ 68: "MAP_TYPE",
+ 69: "CODE_TYPE",
+ 70: "MUTABLE_HEAP_NUMBER_TYPE",
+ 71: "FOREIGN_TYPE",
+ 72: "BYTE_ARRAY_TYPE",
+ 73: "BYTECODE_ARRAY_TYPE",
+ 74: "FREE_SPACE_TYPE",
+ 75: "FIXED_DOUBLE_ARRAY_TYPE",
+ 76: "FEEDBACK_METADATA_TYPE",
+ 77: "FILLER_TYPE",
+ 78: "ACCESS_CHECK_INFO_TYPE",
+ 79: "ACCESSOR_INFO_TYPE",
+ 80: "ACCESSOR_PAIR_TYPE",
+ 81: "ALIASED_ARGUMENTS_ENTRY_TYPE",
+ 82: "ALLOCATION_MEMENTO_TYPE",
+ 83: "ARRAY_BOILERPLATE_DESCRIPTION_TYPE",
+ 84: "ASM_WASM_DATA_TYPE",
+ 85: "ASYNC_GENERATOR_REQUEST_TYPE",
+ 86: "CLASS_POSITIONS_TYPE",
+ 87: "DEBUG_INFO_TYPE",
+ 88: "ENUM_CACHE_TYPE",
+ 89: "FUNCTION_TEMPLATE_INFO_TYPE",
+ 90: "FUNCTION_TEMPLATE_RARE_DATA_TYPE",
+ 91: "INTERCEPTOR_INFO_TYPE",
+ 92: "INTERPRETER_DATA_TYPE",
+ 93: "OBJECT_TEMPLATE_INFO_TYPE",
+ 94: "PROMISE_CAPABILITY_TYPE",
+ 95: "PROMISE_REACTION_TYPE",
+ 96: "PROTOTYPE_INFO_TYPE",
+ 97: "SCRIPT_TYPE",
+ 98: "SOURCE_POSITION_TABLE_WITH_FRAME_CACHE_TYPE",
+ 99: "SOURCE_TEXT_MODULE_INFO_ENTRY_TYPE",
+ 100: "STACK_FRAME_INFO_TYPE",
+ 101: "STACK_TRACE_FRAME_TYPE",
+ 102: "TEMPLATE_OBJECT_DESCRIPTION_TYPE",
+ 103: "TUPLE2_TYPE",
+ 104: "TUPLE3_TYPE",
+ 105: "WASM_CAPI_FUNCTION_DATA_TYPE",
+ 106: "WASM_DEBUG_INFO_TYPE",
+ 107: "WASM_EXCEPTION_TAG_TYPE",
+ 108: "WASM_EXPORTED_FUNCTION_DATA_TYPE",
+ 109: "WASM_INDIRECT_FUNCTION_TABLE_TYPE",
+ 110: "WASM_JS_FUNCTION_DATA_TYPE",
+ 111: "CALLABLE_TASK_TYPE",
+ 112: "CALLBACK_TASK_TYPE",
+ 113: "PROMISE_FULFILL_REACTION_JOB_TASK_TYPE",
+ 114: "PROMISE_REJECT_REACTION_JOB_TASK_TYPE",
+ 115: "PROMISE_RESOLVE_THENABLE_JOB_TASK_TYPE",
+ 116: "FINALIZATION_GROUP_CLEANUP_JOB_TASK_TYPE",
+ 117: "INTERNAL_CLASS_TYPE",
+ 118: "SMI_PAIR_TYPE",
+ 119: "SMI_BOX_TYPE",
+ 120: "SORT_STATE_TYPE",
+ 121: "SOURCE_TEXT_MODULE_TYPE",
+ 122: "SYNTHETIC_MODULE_TYPE",
+ 123: "ALLOCATION_SITE_TYPE",
+ 124: "EMBEDDER_DATA_ARRAY_TYPE",
+ 125: "FIXED_ARRAY_TYPE",
+ 126: "OBJECT_BOILERPLATE_DESCRIPTION_TYPE",
+ 127: "CLOSURE_FEEDBACK_CELL_ARRAY_TYPE",
+ 128: "HASH_TABLE_TYPE",
+ 129: "ORDERED_HASH_MAP_TYPE",
+ 130: "ORDERED_HASH_SET_TYPE",
+ 131: "ORDERED_NAME_DICTIONARY_TYPE",
+ 132: "NAME_DICTIONARY_TYPE",
+ 133: "GLOBAL_DICTIONARY_TYPE",
+ 134: "NUMBER_DICTIONARY_TYPE",
+ 135: "SIMPLE_NUMBER_DICTIONARY_TYPE",
+ 136: "STRING_TABLE_TYPE",
+ 137: "EPHEMERON_HASH_TABLE_TYPE",
+ 138: "SCOPE_INFO_TYPE",
+ 139: "SCRIPT_CONTEXT_TABLE_TYPE",
+ 140: "AWAIT_CONTEXT_TYPE",
+ 141: "BLOCK_CONTEXT_TYPE",
+ 142: "CATCH_CONTEXT_TYPE",
+ 143: "DEBUG_EVALUATE_CONTEXT_TYPE",
+ 144: "EVAL_CONTEXT_TYPE",
+ 145: "FUNCTION_CONTEXT_TYPE",
+ 146: "MODULE_CONTEXT_TYPE",
+ 147: "NATIVE_CONTEXT_TYPE",
+ 148: "SCRIPT_CONTEXT_TYPE",
+ 149: "WITH_CONTEXT_TYPE",
+ 150: "WEAK_FIXED_ARRAY_TYPE",
+ 151: "TRANSITION_ARRAY_TYPE",
+ 152: "CALL_HANDLER_INFO_TYPE",
+ 153: "CELL_TYPE",
+ 154: "CODE_DATA_CONTAINER_TYPE",
+ 155: "DESCRIPTOR_ARRAY_TYPE",
+ 156: "FEEDBACK_CELL_TYPE",
+ 157: "FEEDBACK_VECTOR_TYPE",
+ 158: "LOAD_HANDLER_TYPE",
+ 159: "PREPARSE_DATA_TYPE",
+ 160: "PROPERTY_ARRAY_TYPE",
+ 161: "PROPERTY_CELL_TYPE",
+ 162: "SHARED_FUNCTION_INFO_TYPE",
+ 163: "SMALL_ORDERED_HASH_MAP_TYPE",
+ 164: "SMALL_ORDERED_HASH_SET_TYPE",
+ 165: "SMALL_ORDERED_NAME_DICTIONARY_TYPE",
+ 166: "STORE_HANDLER_TYPE",
+ 167: "UNCOMPILED_DATA_WITHOUT_PREPARSE_DATA_TYPE",
+ 168: "UNCOMPILED_DATA_WITH_PREPARSE_DATA_TYPE",
+ 169: "WEAK_ARRAY_LIST_TYPE",
+ 170: "WEAK_CELL_TYPE",
1024: "JS_PROXY_TYPE",
1025: "JS_GLOBAL_OBJECT_TYPE",
1026: "JS_GLOBAL_PROXY_TYPE",
1027: "JS_MODULE_NAMESPACE_TYPE",
1040: "JS_SPECIAL_API_OBJECT_TYPE",
- 1041: "JS_VALUE_TYPE",
+ 1041: "JS_PRIMITIVE_WRAPPER_TYPE",
1056: "JS_API_OBJECT_TYPE",
1057: "JS_OBJECT_TYPE",
1058: "JS_ARGUMENTS_TYPE",
1059: "JS_ARRAY_BUFFER_TYPE",
- 1060: "JS_ARRAY_TYPE",
- 1061: "JS_ASYNC_FROM_SYNC_ITERATOR_TYPE",
- 1062: "JS_ASYNC_GENERATOR_OBJECT_TYPE",
- 1063: "JS_CONTEXT_EXTENSION_OBJECT_TYPE",
- 1064: "JS_DATE_TYPE",
- 1065: "JS_ERROR_TYPE",
- 1066: "JS_GENERATOR_OBJECT_TYPE",
- 1067: "JS_MAP_TYPE",
- 1068: "JS_MAP_KEY_ITERATOR_TYPE",
- 1069: "JS_MAP_KEY_VALUE_ITERATOR_TYPE",
- 1070: "JS_MAP_VALUE_ITERATOR_TYPE",
- 1071: "JS_MESSAGE_OBJECT_TYPE",
- 1072: "JS_PROMISE_TYPE",
- 1073: "JS_REGEXP_TYPE",
- 1074: "JS_SET_TYPE",
- 1075: "JS_SET_KEY_VALUE_ITERATOR_TYPE",
- 1076: "JS_SET_VALUE_ITERATOR_TYPE",
- 1077: "JS_STRING_ITERATOR_TYPE",
- 1078: "JS_WEAK_MAP_TYPE",
- 1079: "JS_WEAK_SET_TYPE",
- 1080: "JS_TYPED_ARRAY_TYPE",
- 1081: "JS_DATA_VIEW_TYPE",
- 1082: "JS_TYPED_ARRAY_KEY_ITERATOR_TYPE",
- 1083: "JS_FAST_ARRAY_KEY_ITERATOR_TYPE",
- 1084: "JS_GENERIC_ARRAY_KEY_ITERATOR_TYPE",
- 1085: "JS_UINT8_ARRAY_KEY_VALUE_ITERATOR_TYPE",
- 1086: "JS_INT8_ARRAY_KEY_VALUE_ITERATOR_TYPE",
- 1087: "JS_UINT16_ARRAY_KEY_VALUE_ITERATOR_TYPE",
- 1088: "JS_INT16_ARRAY_KEY_VALUE_ITERATOR_TYPE",
- 1089: "JS_UINT32_ARRAY_KEY_VALUE_ITERATOR_TYPE",
- 1090: "JS_INT32_ARRAY_KEY_VALUE_ITERATOR_TYPE",
- 1091: "JS_FLOAT32_ARRAY_KEY_VALUE_ITERATOR_TYPE",
- 1092: "JS_FLOAT64_ARRAY_KEY_VALUE_ITERATOR_TYPE",
- 1093: "JS_UINT8_CLAMPED_ARRAY_KEY_VALUE_ITERATOR_TYPE",
- 1094: "JS_FAST_SMI_ARRAY_KEY_VALUE_ITERATOR_TYPE",
- 1095: "JS_FAST_HOLEY_SMI_ARRAY_KEY_VALUE_ITERATOR_TYPE",
- 1096: "JS_FAST_ARRAY_KEY_VALUE_ITERATOR_TYPE",
- 1097: "JS_FAST_HOLEY_ARRAY_KEY_VALUE_ITERATOR_TYPE",
- 1098: "JS_FAST_DOUBLE_ARRAY_KEY_VALUE_ITERATOR_TYPE",
- 1099: "JS_FAST_HOLEY_DOUBLE_ARRAY_KEY_VALUE_ITERATOR_TYPE",
- 1100: "JS_GENERIC_ARRAY_KEY_VALUE_ITERATOR_TYPE",
- 1101: "JS_UINT8_ARRAY_VALUE_ITERATOR_TYPE",
- 1102: "JS_INT8_ARRAY_VALUE_ITERATOR_TYPE",
- 1103: "JS_UINT16_ARRAY_VALUE_ITERATOR_TYPE",
- 1104: "JS_INT16_ARRAY_VALUE_ITERATOR_TYPE",
- 1105: "JS_UINT32_ARRAY_VALUE_ITERATOR_TYPE",
- 1106: "JS_INT32_ARRAY_VALUE_ITERATOR_TYPE",
- 1107: "JS_FLOAT32_ARRAY_VALUE_ITERATOR_TYPE",
- 1108: "JS_FLOAT64_ARRAY_VALUE_ITERATOR_TYPE",
- 1109: "JS_UINT8_CLAMPED_ARRAY_VALUE_ITERATOR_TYPE",
- 1110: "JS_FAST_SMI_ARRAY_VALUE_ITERATOR_TYPE",
- 1111: "JS_FAST_HOLEY_SMI_ARRAY_VALUE_ITERATOR_TYPE",
- 1112: "JS_FAST_ARRAY_VALUE_ITERATOR_TYPE",
- 1113: "JS_FAST_HOLEY_ARRAY_VALUE_ITERATOR_TYPE",
- 1114: "JS_FAST_DOUBLE_ARRAY_VALUE_ITERATOR_TYPE",
- 1115: "JS_FAST_HOLEY_DOUBLE_ARRAY_VALUE_ITERATOR_TYPE",
- 1116: "JS_GENERIC_ARRAY_VALUE_ITERATOR_TYPE",
- 1117: "WASM_INSTANCE_TYPE",
- 1118: "WASM_MEMORY_TYPE",
- 1119: "WASM_MODULE_TYPE",
- 1120: "WASM_TABLE_TYPE",
- 1121: "JS_BOUND_FUNCTION_TYPE",
- 1122: "JS_FUNCTION_TYPE",
+ 1060: "JS_ARRAY_ITERATOR_TYPE",
+ 1061: "JS_ARRAY_TYPE",
+ 1062: "JS_ASYNC_FROM_SYNC_ITERATOR_TYPE",
+ 1063: "JS_ASYNC_FUNCTION_OBJECT_TYPE",
+ 1064: "JS_ASYNC_GENERATOR_OBJECT_TYPE",
+ 1065: "JS_CONTEXT_EXTENSION_OBJECT_TYPE",
+ 1066: "JS_DATE_TYPE",
+ 1067: "JS_ERROR_TYPE",
+ 1068: "JS_GENERATOR_OBJECT_TYPE",
+ 1069: "JS_MAP_TYPE",
+ 1070: "JS_MAP_KEY_ITERATOR_TYPE",
+ 1071: "JS_MAP_KEY_VALUE_ITERATOR_TYPE",
+ 1072: "JS_MAP_VALUE_ITERATOR_TYPE",
+ 1073: "JS_MESSAGE_OBJECT_TYPE",
+ 1074: "JS_PROMISE_TYPE",
+ 1075: "JS_REGEXP_TYPE",
+ 1076: "JS_REGEXP_STRING_ITERATOR_TYPE",
+ 1077: "JS_SET_TYPE",
+ 1078: "JS_SET_KEY_VALUE_ITERATOR_TYPE",
+ 1079: "JS_SET_VALUE_ITERATOR_TYPE",
+ 1080: "JS_STRING_ITERATOR_TYPE",
+ 1081: "JS_WEAK_REF_TYPE",
+ 1082: "JS_FINALIZATION_GROUP_CLEANUP_ITERATOR_TYPE",
+ 1083: "JS_FINALIZATION_GROUP_TYPE",
+ 1084: "JS_WEAK_MAP_TYPE",
+ 1085: "JS_WEAK_SET_TYPE",
+ 1086: "JS_TYPED_ARRAY_TYPE",
+ 1087: "JS_DATA_VIEW_TYPE",
+ 1088: "JS_INTL_V8_BREAK_ITERATOR_TYPE",
+ 1089: "JS_INTL_COLLATOR_TYPE",
+ 1090: "JS_INTL_DATE_TIME_FORMAT_TYPE",
+ 1091: "JS_INTL_LIST_FORMAT_TYPE",
+ 1092: "JS_INTL_LOCALE_TYPE",
+ 1093: "JS_INTL_NUMBER_FORMAT_TYPE",
+ 1094: "JS_INTL_PLURAL_RULES_TYPE",
+ 1095: "JS_INTL_RELATIVE_TIME_FORMAT_TYPE",
+ 1096: "JS_INTL_SEGMENT_ITERATOR_TYPE",
+ 1097: "JS_INTL_SEGMENTER_TYPE",
+ 1098: "WASM_EXCEPTION_TYPE",
+ 1099: "WASM_GLOBAL_TYPE",
+ 1100: "WASM_INSTANCE_TYPE",
+ 1101: "WASM_MEMORY_TYPE",
+ 1102: "WASM_MODULE_TYPE",
+ 1103: "WASM_TABLE_TYPE",
+ 1104: "JS_BOUND_FUNCTION_TYPE",
+ 1105: "JS_FUNCTION_TYPE",
}
# List of known V8 maps.
KNOWN_MAPS = {
- 0x02201: (138, "FreeSpaceMap"),
- 0x02251: (132, "MetaMap"),
- 0x022a1: (131, "NullMap"),
- 0x022f1: (172, "DescriptorArrayMap"),
- 0x02341: (171, "FixedArrayMap"),
- 0x02391: (149, "OnePointerFillerMap"),
- 0x023e1: (149, "TwoPointerFillerMap"),
- 0x02431: (131, "UninitializedMap"),
- 0x02481: (8, "OneByteInternalizedStringMap"),
- 0x024d1: (131, "UndefinedMap"),
- 0x02521: (129, "HeapNumberMap"),
- 0x02571: (131, "TheHoleMap"),
- 0x025c1: (131, "BooleanMap"),
- 0x02611: (136, "ByteArrayMap"),
- 0x02661: (171, "FixedCOWArrayMap"),
- 0x026b1: (173, "HashTableMap"),
- 0x02701: (128, "SymbolMap"),
- 0x02751: (72, "OneByteStringMap"),
- 0x027a1: (171, "ScopeInfoMap"),
- 0x027f1: (181, "SharedFunctionInfoMap"),
- 0x02841: (133, "CodeMap"),
- 0x02891: (171, "FunctionContextMap"),
- 0x028e1: (175, "CellMap"),
- 0x02931: (185, "WeakCellMap"),
- 0x02981: (180, "GlobalPropertyCellMap"),
- 0x029d1: (135, "ForeignMap"),
- 0x02a21: (174, "TransitionArrayMap"),
- 0x02a71: (177, "FeedbackVectorMap"),
- 0x02ac1: (131, "ArgumentsMarkerMap"),
- 0x02b11: (131, "ExceptionMap"),
- 0x02b61: (131, "TerminationExceptionMap"),
- 0x02bb1: (131, "OptimizedOutMap"),
- 0x02c01: (131, "StaleRegisterMap"),
- 0x02c51: (171, "NativeContextMap"),
- 0x02ca1: (171, "ModuleContextMap"),
- 0x02cf1: (171, "EvalContextMap"),
- 0x02d41: (171, "ScriptContextMap"),
- 0x02d91: (171, "BlockContextMap"),
- 0x02de1: (171, "CatchContextMap"),
- 0x02e31: (171, "WithContextMap"),
- 0x02e81: (171, "DebugEvaluateContextMap"),
- 0x02ed1: (171, "ScriptContextTableMap"),
- 0x02f21: (171, "ArrayListMap"),
- 0x02f71: (148, "FixedDoubleArrayMap"),
- 0x02fc1: (134, "MutableHeapNumberMap"),
- 0x03011: (173, "OrderedHashMapMap"),
- 0x03061: (173, "OrderedHashSetMap"),
- 0x030b1: (173, "NameDictionaryMap"),
- 0x03101: (173, "GlobalDictionaryMap"),
- 0x03151: (173, "NumberDictionaryMap"),
- 0x031a1: (173, "StringTableMap"),
- 0x031f1: (173, "WeakHashTableMap"),
- 0x03241: (171, "SloppyArgumentsElementsMap"),
- 0x03291: (182, "SmallOrderedHashMapMap"),
- 0x032e1: (183, "SmallOrderedHashSetMap"),
- 0x03331: (176, "CodeDataContainerMap"),
- 0x03381: (1071, "JSMessageObjectMap"),
- 0x033d1: (1057, "ExternalMap"),
- 0x03421: (137, "BytecodeArrayMap"),
- 0x03471: (171, "ModuleInfoMap"),
- 0x034c1: (175, "NoClosuresCellMap"),
- 0x03511: (175, "OneClosureCellMap"),
- 0x03561: (175, "ManyClosuresCellMap"),
- 0x035b1: (179, "PropertyArrayMap"),
- 0x03601: (130, "BigIntMap"),
- 0x03651: (106, "NativeSourceStringMap"),
- 0x036a1: (64, "StringMap"),
- 0x036f1: (73, "ConsOneByteStringMap"),
- 0x03741: (65, "ConsStringMap"),
- 0x03791: (77, "ThinOneByteStringMap"),
- 0x037e1: (69, "ThinStringMap"),
- 0x03831: (67, "SlicedStringMap"),
- 0x03881: (75, "SlicedOneByteStringMap"),
- 0x038d1: (66, "ExternalStringMap"),
- 0x03921: (82, "ExternalStringWithOneByteDataMap"),
- 0x03971: (74, "ExternalOneByteStringMap"),
- 0x039c1: (98, "ShortExternalStringMap"),
- 0x03a11: (114, "ShortExternalStringWithOneByteDataMap"),
- 0x03a61: (0, "InternalizedStringMap"),
- 0x03ab1: (2, "ExternalInternalizedStringMap"),
- 0x03b01: (18, "ExternalInternalizedStringWithOneByteDataMap"),
- 0x03b51: (10, "ExternalOneByteInternalizedStringMap"),
- 0x03ba1: (34, "ShortExternalInternalizedStringMap"),
- 0x03bf1: (50, "ShortExternalInternalizedStringWithOneByteDataMap"),
- 0x03c41: (42, "ShortExternalOneByteInternalizedStringMap"),
- 0x03c91: (106, "ShortExternalOneByteStringMap"),
- 0x03ce1: (140, "FixedUint8ArrayMap"),
- 0x03d31: (139, "FixedInt8ArrayMap"),
- 0x03d81: (142, "FixedUint16ArrayMap"),
- 0x03dd1: (141, "FixedInt16ArrayMap"),
- 0x03e21: (144, "FixedUint32ArrayMap"),
- 0x03e71: (143, "FixedInt32ArrayMap"),
- 0x03ec1: (145, "FixedFloat32ArrayMap"),
- 0x03f11: (146, "FixedFloat64ArrayMap"),
- 0x03f61: (147, "FixedUint8ClampedArrayMap"),
- 0x03fb1: (169, "Tuple2Map"),
- 0x04001: (167, "ScriptMap"),
- 0x04051: (160, "InterceptorInfoMap"),
- 0x040a1: (151, "AccessorInfoMap"),
- 0x040f1: (150, "AccessCheckInfoMap"),
- 0x04141: (152, "AccessorPairMap"),
- 0x04191: (153, "AliasedArgumentsEntryMap"),
- 0x041e1: (154, "AllocationMementoMap"),
- 0x04231: (155, "AllocationSiteMap"),
- 0x04281: (156, "AsyncGeneratorRequestMap"),
- 0x042d1: (157, "ContextExtensionMap"),
- 0x04321: (158, "DebugInfoMap"),
- 0x04371: (159, "FunctionTemplateInfoMap"),
- 0x043c1: (161, "ModuleInfoEntryMap"),
- 0x04411: (162, "ModuleMap"),
- 0x04461: (163, "ObjectTemplateInfoMap"),
- 0x044b1: (164, "PromiseReactionJobInfoMap"),
- 0x04501: (165, "PromiseResolveThenableJobInfoMap"),
- 0x04551: (166, "PrototypeInfoMap"),
- 0x045a1: (168, "StackFrameInfoMap"),
- 0x045f1: (170, "Tuple3Map"),
+ ("read_only_space", 0x00111): (74, "FreeSpaceMap"),
+ ("read_only_space", 0x00161): (68, "MetaMap"),
+ ("read_only_space", 0x001e1): (67, "NullMap"),
+ ("read_only_space", 0x00249): (155, "DescriptorArrayMap"),
+ ("read_only_space", 0x002a9): (150, "WeakFixedArrayMap"),
+ ("read_only_space", 0x002f9): (77, "OnePointerFillerMap"),
+ ("read_only_space", 0x00349): (77, "TwoPointerFillerMap"),
+ ("read_only_space", 0x003c9): (67, "UninitializedMap"),
+ ("read_only_space", 0x00439): (8, "OneByteInternalizedStringMap"),
+ ("read_only_space", 0x004d9): (67, "UndefinedMap"),
+ ("read_only_space", 0x00539): (65, "HeapNumberMap"),
+ ("read_only_space", 0x005b9): (67, "TheHoleMap"),
+ ("read_only_space", 0x00661): (67, "BooleanMap"),
+ ("read_only_space", 0x00739): (72, "ByteArrayMap"),
+ ("read_only_space", 0x00789): (125, "FixedArrayMap"),
+ ("read_only_space", 0x007d9): (125, "FixedCOWArrayMap"),
+ ("read_only_space", 0x00829): (128, "HashTableMap"),
+ ("read_only_space", 0x00879): (64, "SymbolMap"),
+ ("read_only_space", 0x008c9): (40, "OneByteStringMap"),
+ ("read_only_space", 0x00919): (138, "ScopeInfoMap"),
+ ("read_only_space", 0x00969): (162, "SharedFunctionInfoMap"),
+ ("read_only_space", 0x009b9): (69, "CodeMap"),
+ ("read_only_space", 0x00a09): (145, "FunctionContextMap"),
+ ("read_only_space", 0x00a59): (153, "CellMap"),
+ ("read_only_space", 0x00aa9): (161, "GlobalPropertyCellMap"),
+ ("read_only_space", 0x00af9): (71, "ForeignMap"),
+ ("read_only_space", 0x00b49): (151, "TransitionArrayMap"),
+ ("read_only_space", 0x00b99): (157, "FeedbackVectorMap"),
+ ("read_only_space", 0x00c39): (67, "ArgumentsMarkerMap"),
+ ("read_only_space", 0x00cd9): (67, "ExceptionMap"),
+ ("read_only_space", 0x00d79): (67, "TerminationExceptionMap"),
+ ("read_only_space", 0x00e21): (67, "OptimizedOutMap"),
+ ("read_only_space", 0x00ec1): (67, "StaleRegisterMap"),
+ ("read_only_space", 0x00f31): (147, "NativeContextMap"),
+ ("read_only_space", 0x00f81): (146, "ModuleContextMap"),
+ ("read_only_space", 0x00fd1): (144, "EvalContextMap"),
+ ("read_only_space", 0x01021): (148, "ScriptContextMap"),
+ ("read_only_space", 0x01071): (140, "AwaitContextMap"),
+ ("read_only_space", 0x010c1): (141, "BlockContextMap"),
+ ("read_only_space", 0x01111): (142, "CatchContextMap"),
+ ("read_only_space", 0x01161): (149, "WithContextMap"),
+ ("read_only_space", 0x011b1): (143, "DebugEvaluateContextMap"),
+ ("read_only_space", 0x01201): (139, "ScriptContextTableMap"),
+ ("read_only_space", 0x01251): (127, "ClosureFeedbackCellArrayMap"),
+ ("read_only_space", 0x012a1): (76, "FeedbackMetadataArrayMap"),
+ ("read_only_space", 0x012f1): (125, "ArrayListMap"),
+ ("read_only_space", 0x01341): (66, "BigIntMap"),
+ ("read_only_space", 0x01391): (126, "ObjectBoilerplateDescriptionMap"),
+ ("read_only_space", 0x013e1): (73, "BytecodeArrayMap"),
+ ("read_only_space", 0x01431): (154, "CodeDataContainerMap"),
+ ("read_only_space", 0x01481): (75, "FixedDoubleArrayMap"),
+ ("read_only_space", 0x014d1): (133, "GlobalDictionaryMap"),
+ ("read_only_space", 0x01521): (156, "ManyClosuresCellMap"),
+ ("read_only_space", 0x01571): (125, "ModuleInfoMap"),
+ ("read_only_space", 0x015c1): (70, "MutableHeapNumberMap"),
+ ("read_only_space", 0x01611): (132, "NameDictionaryMap"),
+ ("read_only_space", 0x01661): (156, "NoClosuresCellMap"),
+ ("read_only_space", 0x016b1): (134, "NumberDictionaryMap"),
+ ("read_only_space", 0x01701): (156, "OneClosureCellMap"),
+ ("read_only_space", 0x01751): (129, "OrderedHashMapMap"),
+ ("read_only_space", 0x017a1): (130, "OrderedHashSetMap"),
+ ("read_only_space", 0x017f1): (131, "OrderedNameDictionaryMap"),
+ ("read_only_space", 0x01841): (159, "PreparseDataMap"),
+ ("read_only_space", 0x01891): (160, "PropertyArrayMap"),
+ ("read_only_space", 0x018e1): (152, "SideEffectCallHandlerInfoMap"),
+ ("read_only_space", 0x01931): (152, "SideEffectFreeCallHandlerInfoMap"),
+ ("read_only_space", 0x01981): (152, "NextCallSideEffectFreeCallHandlerInfoMap"),
+ ("read_only_space", 0x019d1): (135, "SimpleNumberDictionaryMap"),
+ ("read_only_space", 0x01a21): (125, "SloppyArgumentsElementsMap"),
+ ("read_only_space", 0x01a71): (163, "SmallOrderedHashMapMap"),
+ ("read_only_space", 0x01ac1): (164, "SmallOrderedHashSetMap"),
+ ("read_only_space", 0x01b11): (165, "SmallOrderedNameDictionaryMap"),
+ ("read_only_space", 0x01b61): (121, "SourceTextModuleMap"),
+ ("read_only_space", 0x01bb1): (136, "StringTableMap"),
+ ("read_only_space", 0x01c01): (122, "SyntheticModuleMap"),
+ ("read_only_space", 0x01c51): (167, "UncompiledDataWithoutPreparseDataMap"),
+ ("read_only_space", 0x01ca1): (168, "UncompiledDataWithPreparseDataMap"),
+ ("read_only_space", 0x01cf1): (169, "WeakArrayListMap"),
+ ("read_only_space", 0x01d41): (137, "EphemeronHashTableMap"),
+ ("read_only_space", 0x01d91): (124, "EmbedderDataArrayMap"),
+ ("read_only_space", 0x01de1): (170, "WeakCellMap"),
+ ("read_only_space", 0x01e31): (58, "NativeSourceStringMap"),
+ ("read_only_space", 0x01e81): (32, "StringMap"),
+ ("read_only_space", 0x01ed1): (41, "ConsOneByteStringMap"),
+ ("read_only_space", 0x01f21): (33, "ConsStringMap"),
+ ("read_only_space", 0x01f71): (45, "ThinOneByteStringMap"),
+ ("read_only_space", 0x01fc1): (37, "ThinStringMap"),
+ ("read_only_space", 0x02011): (35, "SlicedStringMap"),
+ ("read_only_space", 0x02061): (43, "SlicedOneByteStringMap"),
+ ("read_only_space", 0x020b1): (34, "ExternalStringMap"),
+ ("read_only_space", 0x02101): (42, "ExternalOneByteStringMap"),
+ ("read_only_space", 0x02151): (50, "UncachedExternalStringMap"),
+ ("read_only_space", 0x021a1): (0, "InternalizedStringMap"),
+ ("read_only_space", 0x021f1): (2, "ExternalInternalizedStringMap"),
+ ("read_only_space", 0x02241): (10, "ExternalOneByteInternalizedStringMap"),
+ ("read_only_space", 0x02291): (18, "UncachedExternalInternalizedStringMap"),
+ ("read_only_space", 0x022e1): (26, "UncachedExternalOneByteInternalizedStringMap"),
+ ("read_only_space", 0x02331): (58, "UncachedExternalOneByteStringMap"),
+ ("read_only_space", 0x02381): (67, "SelfReferenceMarkerMap"),
+ ("read_only_space", 0x023e9): (88, "EnumCacheMap"),
+ ("read_only_space", 0x02489): (83, "ArrayBoilerplateDescriptionMap"),
+ ("read_only_space", 0x02679): (91, "InterceptorInfoMap"),
+ ("read_only_space", 0x04e59): (78, "AccessCheckInfoMap"),
+ ("read_only_space", 0x04ea9): (79, "AccessorInfoMap"),
+ ("read_only_space", 0x04ef9): (80, "AccessorPairMap"),
+ ("read_only_space", 0x04f49): (81, "AliasedArgumentsEntryMap"),
+ ("read_only_space", 0x04f99): (82, "AllocationMementoMap"),
+ ("read_only_space", 0x04fe9): (84, "AsmWasmDataMap"),
+ ("read_only_space", 0x05039): (85, "AsyncGeneratorRequestMap"),
+ ("read_only_space", 0x05089): (86, "ClassPositionsMap"),
+ ("read_only_space", 0x050d9): (87, "DebugInfoMap"),
+ ("read_only_space", 0x05129): (89, "FunctionTemplateInfoMap"),
+ ("read_only_space", 0x05179): (90, "FunctionTemplateRareDataMap"),
+ ("read_only_space", 0x051c9): (92, "InterpreterDataMap"),
+ ("read_only_space", 0x05219): (93, "ObjectTemplateInfoMap"),
+ ("read_only_space", 0x05269): (94, "PromiseCapabilityMap"),
+ ("read_only_space", 0x052b9): (95, "PromiseReactionMap"),
+ ("read_only_space", 0x05309): (96, "PrototypeInfoMap"),
+ ("read_only_space", 0x05359): (97, "ScriptMap"),
+ ("read_only_space", 0x053a9): (98, "SourcePositionTableWithFrameCacheMap"),
+ ("read_only_space", 0x053f9): (99, "SourceTextModuleInfoEntryMap"),
+ ("read_only_space", 0x05449): (100, "StackFrameInfoMap"),
+ ("read_only_space", 0x05499): (101, "StackTraceFrameMap"),
+ ("read_only_space", 0x054e9): (102, "TemplateObjectDescriptionMap"),
+ ("read_only_space", 0x05539): (103, "Tuple2Map"),
+ ("read_only_space", 0x05589): (104, "Tuple3Map"),
+ ("read_only_space", 0x055d9): (105, "WasmCapiFunctionDataMap"),
+ ("read_only_space", 0x05629): (106, "WasmDebugInfoMap"),
+ ("read_only_space", 0x05679): (107, "WasmExceptionTagMap"),
+ ("read_only_space", 0x056c9): (108, "WasmExportedFunctionDataMap"),
+ ("read_only_space", 0x05719): (109, "WasmIndirectFunctionTableMap"),
+ ("read_only_space", 0x05769): (110, "WasmJSFunctionDataMap"),
+ ("read_only_space", 0x057b9): (111, "CallableTaskMap"),
+ ("read_only_space", 0x05809): (112, "CallbackTaskMap"),
+ ("read_only_space", 0x05859): (113, "PromiseFulfillReactionJobTaskMap"),
+ ("read_only_space", 0x058a9): (114, "PromiseRejectReactionJobTaskMap"),
+ ("read_only_space", 0x058f9): (115, "PromiseResolveThenableJobTaskMap"),
+ ("read_only_space", 0x05949): (116, "FinalizationGroupCleanupJobTaskMap"),
+ ("read_only_space", 0x05999): (117, "InternalClassMap"),
+ ("read_only_space", 0x059e9): (118, "SmiPairMap"),
+ ("read_only_space", 0x05a39): (119, "SmiBoxMap"),
+ ("read_only_space", 0x05a89): (120, "SortStateMap"),
+ ("read_only_space", 0x05ad9): (123, "AllocationSiteWithWeakNextMap"),
+ ("read_only_space", 0x05b29): (123, "AllocationSiteWithoutWeakNextMap"),
+ ("read_only_space", 0x05b79): (158, "LoadHandler1Map"),
+ ("read_only_space", 0x05bc9): (158, "LoadHandler2Map"),
+ ("read_only_space", 0x05c19): (158, "LoadHandler3Map"),
+ ("read_only_space", 0x05c69): (166, "StoreHandler0Map"),
+ ("read_only_space", 0x05cb9): (166, "StoreHandler1Map"),
+ ("read_only_space", 0x05d09): (166, "StoreHandler2Map"),
+ ("read_only_space", 0x05d59): (166, "StoreHandler3Map"),
+ ("map_space", 0x00111): (1057, "ExternalMap"),
+ ("map_space", 0x00161): (1073, "JSMessageObjectMap"),
}
# List of known V8 objects.
KNOWN_OBJECTS = {
- ("OLD_SPACE", 0x02201): "NullValue",
- ("OLD_SPACE", 0x02231): "EmptyDescriptorArray",
- ("OLD_SPACE", 0x02251): "EmptyFixedArray",
- ("OLD_SPACE", 0x02261): "UninitializedValue",
- ("OLD_SPACE", 0x022e1): "UndefinedValue",
- ("OLD_SPACE", 0x02311): "NanValue",
- ("OLD_SPACE", 0x02321): "TheHoleValue",
- ("OLD_SPACE", 0x02371): "HoleNanValue",
- ("OLD_SPACE", 0x02381): "TrueValue",
- ("OLD_SPACE", 0x023f1): "FalseValue",
- ("OLD_SPACE", 0x02441): "empty_string",
- ("OLD_SPACE", 0x02459): "EmptyScopeInfo",
- ("OLD_SPACE", 0x02469): "ArgumentsMarker",
- ("OLD_SPACE", 0x024c1): "Exception",
- ("OLD_SPACE", 0x02519): "TerminationException",
- ("OLD_SPACE", 0x02579): "OptimizedOut",
- ("OLD_SPACE", 0x025d1): "StaleRegister",
- ("OLD_SPACE", 0x02651): "EmptyByteArray",
- ("OLD_SPACE", 0x02661): "EmptyFixedUint8Array",
- ("OLD_SPACE", 0x02681): "EmptyFixedInt8Array",
- ("OLD_SPACE", 0x026a1): "EmptyFixedUint16Array",
- ("OLD_SPACE", 0x026c1): "EmptyFixedInt16Array",
- ("OLD_SPACE", 0x026e1): "EmptyFixedUint32Array",
- ("OLD_SPACE", 0x02701): "EmptyFixedInt32Array",
- ("OLD_SPACE", 0x02721): "EmptyFixedFloat32Array",
- ("OLD_SPACE", 0x02741): "EmptyFixedFloat64Array",
- ("OLD_SPACE", 0x02761): "EmptyFixedUint8ClampedArray",
- ("OLD_SPACE", 0x02781): "EmptyScript",
- ("OLD_SPACE", 0x02809): "UndefinedCell",
- ("OLD_SPACE", 0x02819): "EmptySloppyArgumentsElements",
- ("OLD_SPACE", 0x02839): "EmptySlowElementDictionary",
- ("OLD_SPACE", 0x02881): "EmptyOrderedHashMap",
- ("OLD_SPACE", 0x028a9): "EmptyOrderedHashSet",
- ("OLD_SPACE", 0x028d1): "EmptyPropertyCell",
- ("OLD_SPACE", 0x028f9): "EmptyWeakCell",
- ("OLD_SPACE", 0x02969): "NoElementsProtector",
- ("OLD_SPACE", 0x02991): "IsConcatSpreadableProtector",
- ("OLD_SPACE", 0x029a1): "SpeciesProtector",
- ("OLD_SPACE", 0x029c9): "StringLengthProtector",
- ("OLD_SPACE", 0x029d9): "FastArrayIterationProtector",
- ("OLD_SPACE", 0x029e9): "ArrayIteratorProtector",
- ("OLD_SPACE", 0x02a11): "ArrayBufferNeuteringProtector",
- ("OLD_SPACE", 0x02a39): "InfinityValue",
- ("OLD_SPACE", 0x02a49): "MinusZeroValue",
- ("OLD_SPACE", 0x02a59): "MinusInfinityValue",
+ ("read_only_space", 0x001b1): "NullValue",
+ ("read_only_space", 0x00231): "EmptyDescriptorArray",
+ ("read_only_space", 0x00299): "EmptyWeakFixedArray",
+ ("read_only_space", 0x00399): "UninitializedValue",
+ ("read_only_space", 0x004a9): "UndefinedValue",
+ ("read_only_space", 0x00529): "NanValue",
+ ("read_only_space", 0x00589): "TheHoleValue",
+ ("read_only_space", 0x00621): "HoleNanValue",
+ ("read_only_space", 0x00631): "TrueValue",
+ ("read_only_space", 0x006e1): "FalseValue",
+ ("read_only_space", 0x00729): "empty_string",
+ ("read_only_space", 0x00be9): "EmptyScopeInfo",
+ ("read_only_space", 0x00bf9): "EmptyFixedArray",
+ ("read_only_space", 0x00c09): "ArgumentsMarker",
+ ("read_only_space", 0x00ca9): "Exception",
+ ("read_only_space", 0x00d49): "TerminationException",
+ ("read_only_space", 0x00df1): "OptimizedOut",
+ ("read_only_space", 0x00e91): "StaleRegister",
+ ("read_only_space", 0x023d1): "EmptyEnumCache",
+ ("read_only_space", 0x02439): "EmptyPropertyArray",
+ ("read_only_space", 0x02449): "EmptyByteArray",
+ ("read_only_space", 0x02459): "EmptyObjectBoilerplateDescription",
+ ("read_only_space", 0x02471): "EmptyArrayBoilerplateDescription",
+ ("read_only_space", 0x024d9): "EmptyClosureFeedbackCellArray",
+ ("read_only_space", 0x024e9): "EmptySloppyArgumentsElements",
+ ("read_only_space", 0x02509): "EmptySlowElementDictionary",
+ ("read_only_space", 0x02551): "EmptyOrderedHashMap",
+ ("read_only_space", 0x02579): "EmptyOrderedHashSet",
+ ("read_only_space", 0x025a1): "EmptyFeedbackMetadata",
+ ("read_only_space", 0x025b1): "EmptyPropertyCell",
+ ("read_only_space", 0x025d9): "EmptyPropertyDictionary",
+ ("read_only_space", 0x02629): "NoOpInterceptorInfo",
+ ("read_only_space", 0x026c9): "EmptyWeakArrayList",
+ ("read_only_space", 0x026e1): "InfinityValue",
+ ("read_only_space", 0x026f1): "MinusZeroValue",
+ ("read_only_space", 0x02701): "MinusInfinityValue",
+ ("read_only_space", 0x02711): "SelfReferenceMarker",
+ ("read_only_space", 0x02769): "OffHeapTrampolineRelocationInfo",
+ ("read_only_space", 0x02781): "TrampolineTrivialCodeDataContainer",
+ ("read_only_space", 0x02799): "TrampolinePromiseRejectionCodeDataContainer",
+ ("read_only_space", 0x027b1): "HashSeed",
+ ("old_space", 0x00111): "ArgumentsIteratorAccessor",
+ ("old_space", 0x00181): "ArrayLengthAccessor",
+ ("old_space", 0x001f1): "BoundFunctionLengthAccessor",
+ ("old_space", 0x00261): "BoundFunctionNameAccessor",
+ ("old_space", 0x002d1): "ErrorStackAccessor",
+ ("old_space", 0x00341): "FunctionArgumentsAccessor",
+ ("old_space", 0x003b1): "FunctionCallerAccessor",
+ ("old_space", 0x00421): "FunctionNameAccessor",
+ ("old_space", 0x00491): "FunctionLengthAccessor",
+ ("old_space", 0x00501): "FunctionPrototypeAccessor",
+ ("old_space", 0x00571): "StringLengthAccessor",
+ ("old_space", 0x005e1): "InvalidPrototypeValidityCell",
+ ("old_space", 0x005f1): "EmptyScript",
+ ("old_space", 0x00671): "ManyClosuresCell",
+ ("old_space", 0x00689): "ArrayConstructorProtector",
+ ("old_space", 0x00699): "NoElementsProtector",
+ ("old_space", 0x006c1): "IsConcatSpreadableProtector",
+ ("old_space", 0x006d1): "ArraySpeciesProtector",
+ ("old_space", 0x006f9): "TypedArraySpeciesProtector",
+ ("old_space", 0x00721): "PromiseSpeciesProtector",
+ ("old_space", 0x00749): "StringLengthProtector",
+ ("old_space", 0x00759): "ArrayIteratorProtector",
+ ("old_space", 0x00781): "ArrayBufferDetachingProtector",
+ ("old_space", 0x007a9): "PromiseHookProtector",
+ ("old_space", 0x007d1): "PromiseResolveProtector",
+ ("old_space", 0x007e1): "MapIteratorProtector",
+ ("old_space", 0x00809): "PromiseThenProtector",
+ ("old_space", 0x00831): "SetIteratorProtector",
+ ("old_space", 0x00859): "StringIteratorProtector",
+ ("old_space", 0x00881): "SingleCharacterStringCache",
+ ("old_space", 0x01091): "StringSplitCache",
+ ("old_space", 0x018a1): "RegExpMultipleCache",
+ ("old_space", 0x020b1): "BuiltinsConstantsTable",
}
# List of known V8 Frame Markers.
@@ -339,14 +433,16 @@
"OPTIMIZED",
"WASM_COMPILED",
"WASM_TO_JS",
- "WASM_TO_WASM",
"JS_TO_WASM",
"WASM_INTERPRETER_ENTRY",
"C_WASM_ENTRY",
+ "WASM_EXIT",
+ "WASM_COMPILE_LAZY",
"INTERPRETED",
"STUB",
"BUILTIN_CONTINUATION",
"JAVA_SCRIPT_BUILTIN_CONTINUATION",
+ "JAVA_SCRIPT_BUILTIN_CONTINUATION_WITH_CATCH",
"INTERNAL",
"CONSTRUCT",
"ARGUMENTS_ADAPTOR",
diff --git a/src/v8/tools/valgrind/asan/dummy b/src/v8/tools/valgrind/asan/dummy
new file mode 100644
index 0000000..0e89814
--- /dev/null
+++ b/src/v8/tools/valgrind/asan/dummy
@@ -0,0 +1,2 @@
+# src/base has some more tools in this folder, which we don't use. But we need
+# to have the folder so that the data deps we inherit doesn't error out.
\ No newline at end of file
diff --git a/src/v8/tools/vim/ninja-build.vim b/src/v8/tools/vim/ninja-build.vim
index 3e9b894..7c88525 100644
--- a/src/v8/tools/vim/ninja-build.vim
+++ b/src/v8/tools/vim/ninja-build.vim
@@ -53,11 +53,8 @@
def compute_ninja_command_for_targets(targets='', configuration=None):
- flags = []
- if "use_goma=1" in os.getenv('GYP_DEFINES', '').split(' '):
- flags = ['-j', '512']
build_dir = path_to_build_dir(configuration);
- build_cmd = ' '.join(['ninja'] + flags + ['-C', build_dir, targets])
+ build_cmd = ' '.join(['autoninja', '-C', build_dir, targets])
vim.command('return "%s"' % build_cmd)
diff --git a/src/v8/tools/wasm-compilation-hints/inject-compilation-hints.py b/src/v8/tools/wasm-compilation-hints/inject-compilation-hints.py
new file mode 100755
index 0000000..fd4b65b
--- /dev/null
+++ b/src/v8/tools/wasm-compilation-hints/inject-compilation-hints.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+
+# Copyright 2019 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 argparse
+import io
+import sys
+
+from wasm import *
+
+FUNCTION_SECTION_ID = 3
+
+def parse_args():
+ parser = argparse.ArgumentParser(\
+ description="Inject compilation hints into a Wasm module.")
+ parser.add_argument("-i", "--in-wasm-file", \
+ type=str, \
+ help="original wasm module")
+ parser.add_argument("-o", "--out-wasm-file", \
+ type=str, \
+ help="wasm module with injected hints")
+ parser.add_argument("-x", "--hints-file", \
+ type=str, required=True, \
+ help="binary hints file to be injected as a custom section " + \
+ "'compilationHints'")
+ return parser.parse_args()
+
+if __name__ == "__main__":
+ args = parse_args()
+ in_wasm_file = args.in_wasm_file if args.in_wasm_file else sys.stdin.fileno()
+ out_wasm_file = args.out_wasm_file if args.out_wasm_file else sys.stdout.fileno()
+ hints_bs = open(args.hints_file, "rb").read()
+ with io.open(in_wasm_file, "rb") as fin:
+ with io.open(out_wasm_file, "wb") as fout:
+ magic_number, bs = read_magic_number(fin);
+ fout.write(bs)
+ version, bs = read_version(fin);
+ fout.write(bs)
+ num_declared_functions = None
+ while True:
+ id, bs = read_varuintN(fin)
+ fout.write(bs)
+ if id == None:
+ break
+ payload_length, bs = read_varuintN(fin)
+ fout.write(bs)
+
+ # Peek into function section for upcoming validity check.
+ if id == FUNCTION_SECTION_ID:
+ num_declared_functions, bs = peek_varuintN(fin)
+
+ bs = fin.read(payload_length)
+ fout.write(bs)
+
+ # Instert hint section after function section.
+ if id == FUNCTION_SECTION_ID:
+ assert len(hints_bs) == num_declared_functions, "unexpected number of hints"
+ write_compilation_hints_section(fout, hints_bs)
diff --git a/src/v8/tools/wasm-compilation-hints/wasm-objdump-compilation-hints.py b/src/v8/tools/wasm-compilation-hints/wasm-objdump-compilation-hints.py
new file mode 100755
index 0000000..a762bd7
--- /dev/null
+++ b/src/v8/tools/wasm-compilation-hints/wasm-objdump-compilation-hints.py
@@ -0,0 +1,47 @@
+#!/usr/bin/env python
+
+# Copyright 2019 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 argparse
+import io
+import sys
+
+from wasm import *
+
+def parse_args():
+ parser = argparse.ArgumentParser(\
+ description="Read compilation hints from Wasm module.")
+ parser.add_argument("in_wasm_file", \
+ type=str, \
+ help="wasm module")
+ return parser.parse_args()
+
+if __name__ == "__main__":
+ args = parse_args()
+ in_wasm_file = args.in_wasm_file if args.in_wasm_file else sys.stdin.fileno()
+ with io.open(in_wasm_file, "rb") as fin:
+ read_magic_number(fin);
+ read_version(fin);
+ while True:
+ id, bs = read_varuintN(fin)
+ if id == None:
+ break
+ payload_length, bs = read_varuintN(fin)
+ if id == CUSTOM_SECTION_ID:
+ section_name_length, section_name_length_bs = read_varuintN(fin)
+ section_name_bs = fin.read(section_name_length)
+ if section_name_bs == "compilationHints":
+ num_hints, bs = read_varuintN(fin)
+ print "Custom section compilationHints with", num_hints, "hints:"
+ for i in range(num_hints):
+ hint, bs = read_uint8(fin)
+ print i, hex(hint)
+ else:
+ remaining_length = payload_length \
+ - len(section_name_length_bs) \
+ - len(section_name_bs)
+ fin.read()
+ else:
+ fin.read(payload_length)
diff --git a/src/v8/tools/wasm-compilation-hints/wasm.py b/src/v8/tools/wasm-compilation-hints/wasm.py
new file mode 100644
index 0000000..ae3d084
--- /dev/null
+++ b/src/v8/tools/wasm-compilation-hints/wasm.py
@@ -0,0 +1,108 @@
+#!/usr/bin/env python
+
+# Copyright 2019 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 io
+import math
+import struct
+import sys
+
+CUSTOM_SECTION_ID = 0
+FUNCTION_SECTION_ID = 3
+
+def peek_uint8(fin):
+ bs = fin.peek(1)[:1]
+ if len(bs) != 1:
+ return None, bs
+ return ord(bs[0]), bs
+
+def read_uint8(fin):
+ value, bs = peek_uint8(fin)
+ fin.read(len(bs))
+ return value, bs
+
+def peek_uint32(fin):
+ bs = fin.peek(4)[:4]
+ if len(bs) != 4:
+ return None, bs
+ return ord(bs[0]) | ord(bs[1]) << 8 | ord(bs[2]) << 16 | ord(bs[3]) << 24, bs
+
+def read_uint32(fin):
+ value, bs = peek_uint32(fin)
+ fin.read(len(bs))
+ return value, bs
+
+def peek_varuintN(fin):
+ value = 0
+ shift = 0
+ n = 1
+ while True:
+ bs = fin.peek(n)[:n]
+ if len(bs) < n:
+ return None, bs
+ b = ord(bs[-1])
+ value |= (b & 0x7F) << shift;
+ if (b & 0x80) == 0x00:
+ return value, bs
+ shift += 7;
+ n += 1
+
+def read_varuintN(fin):
+ value, bs = peek_varuintN(fin)
+ fin.read(len(bs))
+ return value, bs
+
+def to_varuintN(value):
+ bs = ""
+ while True:
+ b = value & 0x7F
+ value >>= 7
+ if (value != 0x00):
+ b |= 0x80
+ bs += chr(b)
+ if value == 0x00:
+ return bs
+
+def write_varuintN(value, fout):
+ bs = to_varuintN(value)
+ fout.write(bs)
+ return bs
+
+def peek_magic_number(fin, expected_magic_number=0x6d736100):
+ magic_number, bs = peek_uint32(fin)
+ assert magic_number == expected_magic_number, "unexpected magic number"
+ return magic_number, bs
+
+def read_magic_number(fin, expected_magic_number=0x6d736100):
+ magic_number, bs = peek_magic_number(fin, expected_magic_number)
+ fin.read(len(bs))
+ return magic_number, bs
+
+def peek_version(fin, expected_version=1):
+ version, bs = peek_uint32(fin)
+ assert version == expected_version, "unexpected version"
+ return version, bs
+
+def read_version(fin, expected_version=1):
+ version, bs = peek_version(fin, expected_version)
+ fin.read(len(bs))
+ return version, bs
+
+def write_custom_section(fout, section_name_bs, payload_bs):
+ section_name_length_bs = to_varuintN(len(section_name_bs))
+ payload_length_bs = to_varuintN(len(section_name_bs) \
+ + len(section_name_length_bs) + len(payload_bs))
+ section_id_bs = to_varuintN(CUSTOM_SECTION_ID)
+ fout.write(section_id_bs)
+ fout.write(payload_length_bs)
+ fout.write(section_name_length_bs)
+ fout.write(section_name_bs)
+ fout.write(payload_bs)
+
+def write_compilation_hints_section(fout, hints_bs):
+ num_compilation_hints_bs = to_varuintN(len(hints_bs))
+ section_name_bs = b"compilationHints"
+ payload_bs = num_compilation_hints_bs + hints_bs
+ write_custom_section(fout, section_name_bs, payload_bs)
diff --git a/src/v8/tools/wasm/update-wasm-spec-tests.sh b/src/v8/tools/wasm/update-wasm-spec-tests.sh
index c4d18a3..d029ffe 100755
--- a/src/v8/tools/wasm/update-wasm-spec-tests.sh
+++ b/src/v8/tools/wasm/update-wasm-spec-tests.sh
@@ -13,33 +13,101 @@
# non-zero status, or zero if no command exited with a non-zero status
set -o pipefail
+log_and_run() {
+ echo ">>" $*
+ if ! $*; then
+ echo "sub-command failed: $*"
+ exit
+ fi
+}
+
+###############################################################################
+# Setup directories.
+###############################################################################
+
TOOLS_WASM_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
V8_DIR="${TOOLS_WASM_DIR}/../.."
SPEC_TEST_DIR=${V8_DIR}/test/wasm-spec-tests
+TMP_DIR=${SPEC_TEST_DIR}/tmp
-cd ${V8_DIR}
+log_and_run cd ${V8_DIR}
-rm -rf ${SPEC_TEST_DIR}/tests
-mkdir ${SPEC_TEST_DIR}/tests
+log_and_run rm -rf ${SPEC_TEST_DIR}/tests
+log_and_run mkdir ${SPEC_TEST_DIR}/tests
-rm -rf ${SPEC_TEST_DIR}/tmp
-mkdir ${SPEC_TEST_DIR}/tmp
+log_and_run mkdir ${SPEC_TEST_DIR}/tests/proposals
-./tools/dev/gm.py x64.release d8
+log_and_run rm -rf ${TMP_DIR}
+log_and_run mkdir ${TMP_DIR}
-cd ${V8_DIR}/test/wasm-js/interpreter
-make clean all
+###############################################################################
+# Generate the spec tests.
+###############################################################################
-cd ${V8_DIR}/test/wasm-js/test/core
+log_and_run cd ${V8_DIR}/test/wasm-js/data/interpreter
+# The next step requires that ocaml is installed. See the README.md in
+# ${V8_DIR}/test/wasm-js/data/interpreter/.
+log_and_run make clean opt
+log_and_run cd ${V8_DIR}/test/wasm-js/data/test/core
+log_and_run cp *.wast ${SPEC_TEST_DIR}/tests/
-./run.py --wasm ${V8_DIR}/test/wasm-js/interpreter/wasm --js ${V8_DIR}/out/x64.release/d8 --out ${SPEC_TEST_DIR}/tmp
-cp ${SPEC_TEST_DIR}/tmp/*.js ${SPEC_TEST_DIR}/tests/
-rm -rf ${SPEC_TEST_DIR}/tmp
+log_and_run ./run.py --wasm ${V8_DIR}/test/wasm-js/data/interpreter/wasm --out ${TMP_DIR}
+log_and_run cp ${TMP_DIR}/*.js ${SPEC_TEST_DIR}/tests/
+
+###############################################################################
+# Generate the proposal tests.
+###############################################################################
+
+repos='bulk-memory-operations reference-types'
+
+for repo in ${repos}; do
+ echo "Process ${repo}"
+ log_and_run cd ${TMP_DIR}
+ log_and_run git clone https://github.com/WebAssembly/${repo}
+ # Compile the spec interpreter to generate the .js test cases later.
+ log_and_run cd ${repo}/interpreter
+ log_and_run make clean opt
+ log_and_run cd ../test/core
+ log_and_run mkdir ${SPEC_TEST_DIR}/tests/proposals/${repo}
+
+ # Iterate over all proposal tests. Those which differ from the spec tests are
+ # copied to the output directory and converted to .js tests.
+ for abs_filename in ${TMP_DIR}/${repo}/test/core/*.wast; do
+ rel_filename="$(basename -- $abs_filename)"
+ test_name=${rel_filename%.wast}
+ spec_filename=${V8_DIR}/test/wasm-js/data/test/core/${rel_filename}
+ if [ ! -f "$spec_filename" ] || ! cmp -s $abs_filename $spec_filename ; then
+ log_and_run cp ${rel_filename} ${SPEC_TEST_DIR}/tests/proposals/${repo}/
+ 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}/
+done
+
+###############################################################################
+# Report and cleanup.
+###############################################################################
cd ${SPEC_TEST_DIR}
echo
echo "The following files will get uploaded:"
-ls tests
+ls -R tests
echo
-upload_to_google_storage.py -a -b v8-wasm-spec-tests tests
+
+log_and_run rm -rf ${TMP_DIR}
+
+###############################################################################
+# Upload all spec tests.
+###############################################################################
+
+echo "****************************************************************************"
+echo "* For the following command you first have to authenticate with google cloud"
+echo "* storage. For that you have to execute"
+echo "*"
+echo "* > gsutil.py config"
+echo "*"
+echo "* When the script asks you for your project-id, use 0."
+echo "****************************************************************************"
+log_and_run cd ${SPEC_TEST_DIR}
+log_and_run upload_to_google_storage.py -a -b v8-wasm-spec-tests tests
diff --git a/src/v8/tools/wasm/wasm-import-profiler-end.js b/src/v8/tools/wasm/wasm-import-profiler-end.js
new file mode 100644
index 0000000..5b5eedd
--- /dev/null
+++ b/src/v8/tools/wasm/wasm-import-profiler-end.js
@@ -0,0 +1,6 @@
+// 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.
+
+// Code to run at shutdown: print out the profiles for all instances.
+if (typeof WebAssembly.dumpAllProfiles == "function") WebAssembly.dumpAllProfiles();
diff --git a/src/v8/tools/wasm/wasm-import-profiler.js b/src/v8/tools/wasm/wasm-import-profiler.js
new file mode 100644
index 0000000..cfbb3fb
--- /dev/null
+++ b/src/v8/tools/wasm/wasm-import-profiler.js
@@ -0,0 +1,131 @@
+// 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.
+
+(() => {
+ let all_profiles = [];
+ let instanceMap = new WeakMap();
+ let instanceCounter = 0;
+
+ function instrument(imports, profile) {
+ let orig_imports = imports;
+ return new Proxy(imports, {
+ get: (obj, module_name) => {
+ let orig_module = orig_imports[module_name];
+ return new Proxy(orig_module, {
+ get: (obj, item_name) => {
+ let orig_func = orig_module[item_name];
+ let item = orig_func;
+ if (typeof orig_func == "function") {
+ var full_name = module_name + "." + item_name;
+ print("instrumented " + full_name);
+ profile[full_name] = {name: full_name, count: 0, total: 0};
+ item = function profiled_func(...args) {
+ var before = performance.now();
+ var result = orig_func(...args);
+ var delta = performance.now() - before;
+ var data = profile[full_name];
+ data.count++;
+ data.total += delta;
+ return result;
+ }
+ }
+ return item;
+ }
+ })
+ }
+ });
+ }
+
+ function dumpProfile(profile) {
+ let array = [];
+ for (let key in profile) {
+ if (key == "instanceNum") continue;
+ let data = profile[key];
+ if (data.count == 0) continue;
+ array.push(data);
+ }
+ print(`--- Import profile for instance ${profile.instanceNum} ---`);
+ if (array.length == 0) return;
+ array.sort((a, b) => b.total - a.total);
+ for (let data of array) {
+ print(`${padl(data.name, 30)}: ${padr(data.count, 10)} ${padp(data.total, 10)}ms`);
+ }
+ }
+
+ function padl(s, len) {
+ s = s.toString();
+ while (s.length < len) s = s + " ";
+ return s;
+ }
+ function padr(s, len) {
+ s = s.toString();
+ while (s.length < len) s = " " + s;
+ return s;
+ }
+ function padp(s, len) {
+ s = s.toString();
+ var i = s.indexOf(".");
+ if (i == -1) i = s.length;
+ while (i++ < len) s = " " + s;
+ return s;
+ }
+
+ // patch: WebAssembly.instantiate (async)
+ let orig_instantiate = WebAssembly.instantiate;
+ WebAssembly.instantiate = (m, imports, ...args) => {
+ let profile = {};
+ let promise = orig_instantiate(m, instrument(imports, profile), ...args);
+ promise.then((instance) => {
+ instanceMap.set(instance, profile);
+ all_profiles.push(profile);
+ profile.instanceNum = instanceCounter++;
+ });
+ return promise;
+ }
+
+ // patch: new WebAssembly.Instance (sync)
+ let orig_new_instance = WebAssembly.Instance;
+ WebAssembly.Instance = new Proxy(orig_new_instance, {
+ construct: (target, args) => {
+ let profile = {};
+ args[1] = instrument(args[1], profile);
+ let instance = new orig_new_instance(...args);
+ instanceMap.set(instance, profile);
+ all_profiles.push(profile);
+ profile.instanceNum = instanceCounter++;
+ return instance;
+ }
+ });
+
+ // expose: WebAssembly.dumpProfile(instance)
+ WebAssembly.dumpProfile = (instance) => {
+ let profile = instanceMap.get(instance);
+ if (profile === undefined) return;
+ dumpProfile(profile);
+ }
+ // expose: WebAssembly.clearProfile(instance)
+ WebAssembly.clearProfile = (instance) => {
+ let profile = instanceMap.get(instance);
+ if (profile === undefined) return;
+ for (let key in profile) {
+ if (key == "instanceNum") continue;
+ let data = p[key];
+ data.count = 0;
+ data.total = 0;
+ }
+ }
+ // expose: WebAssembly.dumpAllProfiles()
+ WebAssembly.dumpAllProfiles = () => {
+ for (let profile of all_profiles) dumpProfile(profile);
+ }
+ // expose: WebAssembly.getProfile(instance)
+ // returns: {
+ // func_name1: {name: func_name1, count: <num>, total: <num>}
+ // func_name2: {name: func_name1, count: <num>, total: <num>}
+ // ...
+ // }
+ WebAssembly.getProfile = (instance) => {
+ return instanceMap.get(instance);
+ }
+})();
diff --git a/src/v8/tools/whitespace.txt b/src/v8/tools/whitespace.txt
index 83f0066..9a80a32 100644
--- a/src/v8/tools/whitespace.txt
+++ b/src/v8/tools/whitespace.txt
@@ -6,5 +6,5 @@
"I'm so deoptimized today!"
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.......................
+The autoroller bought a round of Himbeerbrause. Suddenly.....
+The bartender starts to shake the bottles..........
diff --git a/src/v8/tools/windbg.js b/src/v8/tools/windbg.js
new file mode 100644
index 0000000..3df14f4
--- /dev/null
+++ b/src/v8/tools/windbg.js
@@ -0,0 +1,420 @@
+// Copyright 2019 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/*=============================================================================
+ This is a convenience script for debugging with WinDbg (akin to gdbinit)
+ It can be loaded into WinDbg with: .scriptload full_path\windbg.js
+
+ To printout the help message below into the debugger's command window:
+ !help
+=============================================================================*/
+
+function help() {
+ print("--------------------------------------------------------------------");
+ print(" LIVE debugging only");
+ print("--------------------------------------------------------------------");
+ print(" !jlh(\"local_handle_var_name\")");
+ print(" prints object held by the handle");
+ print(" e.g. !jlh(\"key\") or !jlh(\"this->receiver_\")");
+ print(" !job(address_or_taggedint)");
+ print(" prints object at the address, e.g. !job(0x235cb869f9)");
+ print(" !jobs(start_address, count)");
+ print(" prints 'count' objects from a continuous range of Object");
+ print(" pointers, e.g. !jobs(0x5f7270, 42)");
+ print(" !jst() or !jst");
+ print(" prints javascript stack (output goes into the console)");
+ print(" !jsbp() or !jsbp");
+ print(" sets bp in v8::internal::Execution::Call");
+ print("");
+ print("--------------------------------------------------------------------");
+ print(" Managed heap");
+ print("--------------------------------------------------------------------");
+ print(" !set_iso(isolate_address)");
+ print(" call this function before using !mem or other heap routines");
+ print(" !mem or !mem(\"space1[ space2 ...]\")");
+ print(" prints memory chunks from the 'space' owned by the heap in the");
+ print(" isolate set by !set_iso; valid values for 'space' are:");
+ print(" new, old, map, code, lo [large], nlo [newlarge], ro [readonly]");
+ print(" if no 'space' specified prints memory chunks for all spaces,");
+ print(" e.g. !mem(\"code\"), !mem(\"ro new old\")");
+ print(" !where(address)");
+ print(" prints name of the space and address of the MemoryChunk the");
+ print(" 'address' is from, e.g. !where(0x235cb869f9)");
+ print("");
+ print("--------------------------------------------------------------------");
+ print(" To run any function from this script (live or postmortem):");
+ print("");
+ print(" dx @$scriptContents.function_name(args)");
+ print(" e.g. dx @$scriptContents.pointer_size()");
+ print(" e.g. dx @$scriptContents.module_name(\"v8_for_test\")");
+ print("--------------------------------------------------------------------");
+}
+
+/*=============================================================================
+ Output
+=============================================================================*/
+function print(s) {
+ host.diagnostics.debugLog(s + "\n");
+}
+
+function print_filtered(obj, filter) {
+ for (let line of obj) {
+ if (!filter || line.indexOf(filter) != -1) {
+ print(line);
+ }
+ }
+}
+
+function inspect(s) {
+ for (var k of Reflect.ownKeys(s)) {
+ print(k + " => " + Reflect.get(s, k));
+ }
+}
+
+
+/*=============================================================================
+ Utils (postmortem and live)
+=============================================================================*/
+function cast(address, type_name) {
+ return host.createTypedObject(address, module_name(), type_name);
+}
+
+// Failed to figure out how to get pointer size from the debugger's data model,
+// so we parse it out from sizeof(void*) output.
+function pointer_size() {
+ let ctl = host.namespace.Debugger.Utility.Control;
+ let sizeof = ctl.ExecuteCommand("?? sizeof(void*)");
+ let output = "";
+ for (output of sizeof) {} // unsigned int64 8
+ return parseInt(output.trim().split(" ").pop());
+}
+
+function poi(address) {
+ try {
+ // readMemoryValues throws if cannot read from 'address'.
+ return host.memory.readMemoryValues(address, 1, pointer_size())[0];
+ }
+ catch (e){}
+}
+
+function get_register(name) {
+ return host.namespace.Debugger.State.DebuggerVariables.curthread
+ .Registers.User[name];
+}
+
+// In debug builds v8 code is compiled into v8.dll, and in release builds
+// the code is compiled directly into the executable. If you are debugging some
+// other embedder, invoke module_name explicitly from the debugger and provide
+// the module name to use.
+const known_exes = ["d8", "unittests", "mksnapshot", "chrome", "chromium"];
+let module_name_cache;
+function module_name(use_this_module) {
+ if (use_this_module) {
+ module_name_cache = use_this_module;
+ }
+
+ if (!module_name_cache) {
+ let v8 = host.namespace.Debugger.State.DebuggerVariables.curprocess
+ .Modules.Where(
+ function(m) {
+ return m.Name.indexOf("\\v8.dll") !== -1;
+ });
+
+ if (v8) {
+ module_name_cache = "v8";
+ }
+ else {
+ for (let exe_name in known_exes) {
+ let exe = host.namespace.Debugger.State.DebuggerVariables.curprocess
+ .Modules.Where(
+ function(m) {
+ return m.Name.indexOf(`\\${exe_name}.exe`) !== -1;
+ });
+ if (exe) {
+ module_name_cache = exe_name;
+ break;
+ }
+ }
+ }
+ }
+ return module_name_cache;
+};
+
+function make_call(fn) {
+ // .call resets current frame to the top one, so have to manually remember
+ // and restore it after making the call.
+ let curframe = host.namespace.Debugger.State.DebuggerVariables.curframe;
+ let ctl = host.namespace.Debugger.Utility.Control;
+ let output = ctl.ExecuteCommand(`.call ${fn};g`);
+ curframe.SwitchTo();
+ return output;
+}
+
+// Skips the meta output about the .call invocation.
+function make_call_and_print_return(fn) {
+ let output = make_call(fn);
+ let print_line = false;
+ for (let line of output) {
+ if (print_line) {
+ print(line);
+ break;
+ }
+ if (line.includes(".call returns")) {
+ print_line = true;
+ }
+ }
+}
+
+
+/*=============================================================================
+ Wrappers around V8's printing functions and other utils for live-debugging
+=============================================================================*/
+
+/*-----------------------------------------------------------------------------
+ 'address' should be an int (so in hex must include '0x' prefix).
+-----------------------------------------------------------------------------*/
+function print_object(address) {
+ let output = make_call(`_v8_internal_Print_Object(${address})`);
+
+ // skip the first few lines with meta info of .call command
+ let skip_line = true;
+ for (let line of output) {
+ if (!skip_line) {
+ print(line);
+ continue;
+ }
+ if (line.includes("deadlocks and corruption of the debuggee")) {
+ skip_line = false;
+ }
+ }
+}
+
+/*-----------------------------------------------------------------------------
+ 'handle_to_object' should be a name of a Handle which can be a local
+ variable or it can be a member variable like "this->receiver_".
+-----------------------------------------------------------------------------*/
+function print_object_from_handle(handle_to_object) {
+ let handle = host.evaluateExpression(handle_to_object);
+ let location = handle.location_;
+ let pobj = poi(location.address);
+ print_object(pobj);
+}
+
+/*-----------------------------------------------------------------------------
+ 'start_address' should be an int (so in hex must include '0x' prefix), it can
+ point at any continuous memory that contains Object pointers.
+-----------------------------------------------------------------------------*/
+function print_objects_array(start_address, count) {
+ let ctl = host.namespace.Debugger.Utility.Control;
+ let psize = pointer_size();
+ let addr_int = start_address;
+ for (let i = 0; i < count; i++) {
+ const addr_hex = `0x${addr_int.toString(16)}`;
+
+ // TODO: Tried using createPointerObject but it throws unknown exception
+ // from ChakraCore. Why?
+ //let obj = host.createPointerObject(addr_hex, module, "void*");
+
+ let output = ctl.ExecuteCommand(`dp ${addr_hex} l1`);
+ let item = "";
+ for (item of output) {} // 005f7270 34604101
+ let deref = `0x${item.split(" ").pop()}`;
+ print(`${addr_hex} -> ${deref}`);
+ print_object(deref);
+
+ addr_int += psize;
+ }
+}
+
+function print_js_stack() {
+ make_call("_v8_internal_Print_StackTrace()");
+}
+
+function set_user_js_bp() {
+ let ctl = host.namespace.Debugger.Utility.Control;
+ ctl.ExecuteCommand(`bp ${module_name()}!v8::internal::Execution::Call`)
+}
+
+
+/*=============================================================================
+ Managed heap related functions (live and post-mortem debugging)
+=============================================================================*/
+let isolate_address = 0;
+function set_isolate_address(addr) {
+ isolate_address = addr;
+}
+
+/*-----------------------------------------------------------------------------
+ Memory in each Space is organized into a linked list of memory chunks
+-----------------------------------------------------------------------------*/
+const NEVER_EVACUATE = 1 << 7; // see src\heap\spaces.h
+
+function print_memory_chunk_list(space_type, front, top, age_mark) {
+ let alloc_pos = top ? ` (allocating at: ${top})` : "";
+ let age_mark_pos = age_mark ? ` (age_mark at: ${top})` : "";
+ print(`${space_type}${alloc_pos}${age_mark_pos}:`);
+ if (front.isNull) {
+ print("<empty>\n");
+ return;
+ }
+
+ let cur = front;
+ while (!cur.isNull) {
+ let imm = cur.flags_ & NEVER_EVACUATE ? "*" : " ";
+ let addr = `0x${cur.address.toString(16)}`;
+ let area =
+ `0x${cur.area_start_.toString(16)} - 0x${cur.area_end_.toString(16)}`;
+ let dt = `dt ${addr} ${module_name()}!v8::internal::MemoryChunk`;
+ print(`${imm} ${addr}:\t ${area} (0x${cur.size_.toString(16)}) : ${dt}`);
+ cur = cur.list_node_.next_;
+ }
+ print("");
+}
+
+const space_tags =
+ ['old', 'new_to', 'new_from', 'ro', 'map', 'code', 'lo', 'nlo'];
+
+function get_chunks_space(space_tag, front, chunks) {
+ let cur = front;
+ while (!cur.isNull) {
+ chunks.push({
+ 'address':cur.address,
+ 'area_start_':cur.area_start_,
+ 'area_end_':cur.area_end_,
+ 'space':space_tag});
+ cur = cur.list_node_.next_;
+ }
+}
+
+function get_chunks() {
+ let iso = cast(isolate_address, "v8::internal::Isolate");
+ let h = iso.heap_;
+
+ let chunks = [];
+ get_chunks_space('old', h.old_space_.memory_chunk_list_.front_, chunks);
+ get_chunks_space('new_to',
+ h.new_space_.to_space_.memory_chunk_list_.front_, chunks);
+ get_chunks_space('new_from',
+ h.new_space_.from_space_.memory_chunk_list_.front_, chunks);
+ get_chunks_space('ro', h.read_only_space_.memory_chunk_list_.front_, chunks);
+ get_chunks_space('map', h.map_space_.memory_chunk_list_.front_, chunks);
+ get_chunks_space('code', h.code_space_.memory_chunk_list_.front_, chunks);
+ get_chunks_space('lo', h.lo_space_.memory_chunk_list_.front_, chunks);
+ get_chunks_space('nlo', h.new_lo_space_.memory_chunk_list_.front_, chunks);
+
+ return chunks;
+}
+
+function find_chunk(address) {
+ // if 'address' is greater than Number.MAX_SAFE_INTEGER, comparison ops on it
+ // throw "Error: 64 bit value loses precision on conversion to number"
+ try {
+ let chunks = get_chunks(isolate_address);
+ for (let c of chunks) {
+ let chunk = cast(c.address, "v8::internal::MemoryChunk");
+ if (address >= chunk.area_start_ && address < chunk.area_end_) {
+ return c;
+ }
+ }
+ }
+ catch (e) { }
+ return undefined;
+}
+
+/*-----------------------------------------------------------------------------
+ Print memory chunks from spaces in the current Heap
+ 'isolate_address' should be an int (so in hex must include '0x' prefix).
+ 'space': space separated string containing "all", "old", "new", "map",
+ "code", "ro [readonly]", "lo [large]", "nlo [newlarge]"
+-----------------------------------------------------------------------------*/
+function print_memory(space = "all") {
+ if (isolate_address == 0) {
+ print("Please call !set_iso(isolate_address) first.");
+ return;
+ }
+
+ let iso = cast(isolate_address, "v8::internal::Isolate");
+ let h = iso.heap_;
+ print(`Heap at ${h.targetLocation}`);
+
+ let st = space.toLowerCase().split(" ");
+
+ print("Im address:\t object area start - end (size)");
+ if (st.includes("all") || st.includes("old")) {
+ print_memory_chunk_list("OldSpace",
+ h.old_space_.memory_chunk_list_.front_,
+ h.old_space_.allocation_info_.top_);
+ }
+ if (st.includes("all") || st.includes("new")) {
+ // new space doesn't use the chunk list from its base class but from
+ // the to/from semi-spaces it points to
+ print_memory_chunk_list("NewSpace_To",
+ h.new_space_.to_space_.memory_chunk_list_.front_,
+ h.new_space_.allocation_info_.top_,
+ h.new_space_.to_space_.age_mark_);
+ print_memory_chunk_list("NewSpace_From",
+ h.new_space_.from_space_.memory_chunk_list_.front_);
+ }
+ if (st.includes("all") || st.includes("map")) {
+ print_memory_chunk_list("MapSpace",
+ h.map_space_.memory_chunk_list_.front_,
+ h.map_space_.allocation_info_.top_);
+ }
+ if (st.includes("all") || st.includes("code")) {
+ print_memory_chunk_list("CodeSpace",
+ h.code_space_.memory_chunk_list_.front_,
+ h.code_space_.allocation_info_.top_);
+ }
+ if (st.includes("all") || st.includes("large") || st.includes("lo")) {
+ print_memory_chunk_list("LargeObjectSpace",
+ h.lo_space_.memory_chunk_list_.front_);
+ }
+ if (st.includes("all") || st.includes("newlarge") || st.includes("nlo")) {
+ print_memory_chunk_list("NewLargeObjectSpace",
+ h.new_lo_space_.memory_chunk_list_.front_);
+ }
+ if (st.includes("all") || st.includes("readonly") || st.includes("ro")) {
+ print_memory_chunk_list("ReadOnlySpace",
+ h.read_only_space_.memory_chunk_list_.front_);
+ }
+}
+
+/*-----------------------------------------------------------------------------
+ 'isolate_address' and 'address' should be ints (so in hex must include '0x'
+ prefix).
+-----------------------------------------------------------------------------*/
+function print_owning_space(address) {
+ if (isolate_address == 0) {
+ print("Please call !set_iso(isolate_address) first.");
+ return;
+ }
+
+ let c = find_chunk(address);
+ let addr = `0x${address.toString(16)}`;
+ if (c) {
+ print(`${addr} is in ${c.space} (chunk: 0x${c.address.toString(16)})`);
+ }
+ else {
+ print(`Address ${addr} is not in managed heap`);
+ }
+}
+
+/*=============================================================================
+ Initialize short aliased names for the most common commands
+=============================================================================*/
+function initializeScript() {
+ return [
+ new host.functionAlias(help, "help"),
+ new host.functionAlias(print_object_from_handle, "jlh"),
+ new host.functionAlias(print_object, "job"),
+ new host.functionAlias(print_objects_array, "jobs"),
+ new host.functionAlias(print_js_stack, "jst"),
+
+ new host.functionAlias(set_isolate_address, "set_iso"),
+ new host.functionAlias(print_memory, "mem"),
+ new host.functionAlias(print_owning_space, "where"),
+
+ new host.functionAlias(set_user_js_bp, "jsbp"),
+ ]
+}