Commit 4db43bfa authored by Camillo Bruni's avatar Camillo Bruni Committed by V8 LUCI CQ

[tools] Improve gcmole part I: command line

- Convert gcmole to python3-ish code
  - use local Path implementation for future full migration
- Use optparse and explicit arguments for gcmole
  - Add explicit directories flags
  - Use backwards compatible env vars as fallbacks
- Add gn target v8_gcmole_files to avoid issues with missing or
  incompatible generated files

Drive-by-fixes for running gcmole without ignored files:
- Disable gcmole in Isolate::UnwindAndFindHandle
- Partially disable gcmole in V8HeapExplorer::AddEntry

Bug: v8:10009
Change-Id: I5b4d1c6554db300190226361b6c518419109ff3d
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3497320Reviewed-by: 's avatarLiviu Rau <liviurau@chromium.org>
Reviewed-by: 's avatarMaya Lekova <mslekova@chromium.org>
Commit-Queue: Camillo Bruni <cbruni@chromium.org>
Cr-Commit-Position: refs/heads/main@{#79348}
parent 07945511
......@@ -60,7 +60,7 @@
"type": "script",
},
"run-gcmole": {
"label": "//tools/gcmole:v8_run_gcmole",
"label": "//tools/gcmole:v8_gcmole_files",
"type": "script",
},
"run-num-fuzzer": {
......
......@@ -11,7 +11,7 @@ group("gn_all") {
data_deps = [
":v8_check_static_initializers",
"debug_helper:v8_debug_helper",
"gcmole:v8_run_gcmole",
"gcmole:v8_gcmole_files",
"jsfunfuzz:v8_jsfunfuzz",
]
......
......@@ -4,11 +4,13 @@
import("../../gni/v8.gni")
group("v8_run_gcmole") {
group("v8_gcmole_files") {
testonly = true
data_deps = [
"../../:v8_dump_build_config",
"../../:v8_generated_cc_files",
]
data = [
"GCMOLE.gn",
"gcmole.py",
"gcmole-test.cc",
"gcmole-tools/",
......@@ -36,8 +38,6 @@ group("v8_run_gcmole") {
"$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.
......
action("gcmole") {
sources = [
### gcmole(all) ###
"tools/gcmole/gcmole-test.cc",
]
}
#!/usr/bin/env python
#!/usr/bin/env python3
# Copyright 2020 the V8 project authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
......@@ -6,58 +6,126 @@
# This is main driver for gcmole tool. See README for more details.
# Usage: CLANG_BIN=clang-bin-dir python tools/gcmole/gcmole.py [arm|arm64|ia32|x64]
# for py2/py3 compatibility
from __future__ import print_function
from multiprocessing import cpu_count
import collections
import difflib
from multiprocessing import cpu_count
import optparse
import os
import re
import subprocess
import sys
import threading
import json
if sys.version_info.major > 2:
from pathlib import Path
import queue
else:
import Queue as queue
default_open = open
def open(path, *args, **kwargs):
return default_open(str(path), *args, **kwargs)
class Path(object):
def __init__(self, path, *args):
if args:
self._path = os.path.join(str(path), *args)
else:
self._path = str(path)
def __div__(self, other):
return Path(self._path, str(other))
def __str__(self):
return self._path
def resolve(self):
return Path(os.path.abspath(self._path))
@property
def parent(self):
return Path(os.path.dirname(self._path))
@property
def parents(self):
current = self
parents = []
while current._path != "" and current._path != "/":
current = current.parent
parents.append(current)
return parents
def is_file(self):
return os.path.isfile(self._path)
def is_dir(self):
return os.path.isdir(self._path)
def exists(self):
return os.path.exists(self._path)
ArchCfg = collections.namedtuple("ArchCfg",
["triple", "arch_define", "arch_options"])
def mkdir(self, parents=False, exist_ok=False):
if parents and not self.parent.exists():
self.parent.mkdir(parents=True, exist_ok=True)
if exist_ok and self.exists():
return
os.mkdir(self._path)
ArchCfg = collections.namedtuple(
"ArchCfg", ["name", "cpu", "triple", "arch_define", "arch_options"])
ARCHITECTURES = {
"ia32":
ArchCfg(
name="ia32",
cpu="x86",
triple="i586-unknown-linux",
arch_define="V8_TARGET_ARCH_IA32",
arch_options=["-m32"],
),
"arm":
ArchCfg(
name="arm",
cpu="arm",
triple="i586-unknown-linux",
arch_define="V8_TARGET_ARCH_ARM",
arch_options=["-m32"],
),
"x64":
ArchCfg(
name="x64",
cpu="x64",
triple="x86_64-unknown-linux",
arch_define="V8_TARGET_ARCH_X64",
arch_options=[]),
arch_options=[],
),
"arm64":
ArchCfg(
name="arm64",
cpu="arm64",
triple="x86_64-unknown-linux",
arch_define="V8_TARGET_ARCH_ARM64",
arch_options=[],
),
}
ARCHITECTURES['x86'] = ARCHITECTURES['ia32']
def log(format, *args):
print(format.format(*args))
def log(format, *args, **kwargs):
mark = ("#", "=", "-", ".")[kwargs.get("level", 0)]
print(mark * 2, str(format).format(*list(map(str, args))))
def fatal(format, *args):
log(format, *args)
def fatal(format):
log(format)
sys.exit(1)
......@@ -65,26 +133,27 @@ def fatal(format, *args):
# Clang invocation
def MakeClangCommandLine(plugin, plugin_args, arch_cfg, clang_bin_dir,
clang_plugins_dir):
def make_clang_command_line(plugin, plugin_args, options):
arch_cfg = ARCHITECTURES[options.v8_target_cpu]
prefixed_plugin_args = []
if plugin_args:
for arg in plugin_args:
prefixed_plugin_args += [
"-Xclang",
"-plugin-arg-{}".format(plugin),
"-plugin-arg-" + plugin,
"-Xclang",
arg,
]
log("Using generated files in {}", options.v8_build_dir / 'gen')
icu_src_dir = options.v8_root_dir / 'third_party/icu/source'
return ([
os.path.join(clang_bin_dir, "clang++"),
options.clang_bin_dir / "clang++",
"-std=c++14",
"-c",
"-Xclang",
"-load",
"-Xclang",
os.path.join(clang_plugins_dir, "libgcmole.so"),
options.clang_plugins_dir / "libgcmole.so",
"-Xclang",
"-plugin",
"-Xclang",
......@@ -100,36 +169,37 @@ def MakeClangCommandLine(plugin, plugin_args, arch_cfg, clang_bin_dir,
"-DENABLE_DEBUGGER_SUPPORT",
"-DV8_INTL_SUPPORT",
"-DV8_ENABLE_WEBASSEMBLY",
"-I./",
"-Iinclude/",
"-Iout/build/gen",
"-Ithird_party/icu/source/common",
"-Ithird_party/icu/source/i18n",
"-I{}".format(options.v8_root_dir),
"-I{}".format(options.v8_root_dir / 'include'),
"-I{}".format(options.v8_build_dir / 'gen'),
"-I{}".format(icu_src_dir / 'common'),
"-I{}".format(icu_src_dir / 'i18n'),
] + arch_cfg.arch_options)
def InvokeClangPluginForFile(filename, cmd_line, verbose):
def invoke_clang_plugin_for_file(filename, cmd_line, verbose):
args = cmd_line + [filename]
args = list(map(str, args))
if verbose:
print("popen ", " ".join(cmd_line + [filename]))
p = subprocess.Popen(
cmd_line + [filename], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
print("popen ", " ".join(args))
p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = p.communicate()
return p.returncode, stdout, stderr
return p.returncode, stdout.decode("utf-8"), stderr.decode("utf-8")
def InvokeClangPluginForFilesInQueue(i, input_queue, output_queue, cancel_event,
cmd_line, verbose):
def invoke_clang_plugin_for_files_in_queue(i, input_queue, output_queue,
cancel_event, cmd_line, verbose):
success = False
try:
while not cancel_event.is_set():
filename = input_queue.get_nowait()
ret, stdout, stderr = InvokeClangPluginForFile(filename, cmd_line,
verbose)
output_queue.put_nowait((filename, ret, stdout.decode('utf-8'), stderr.decode('utf-8')))
ret, stdout, stderr = invoke_clang_plugin_for_file(
filename, cmd_line, verbose)
output_queue.put_nowait((filename, ret, stdout, stderr))
if ret != 0:
break
except KeyboardInterrupt:
log("-- [{}] Interrupting", i)
log("[{}] Interrupting", i, level=1)
except queue.Empty:
success = True
finally:
......@@ -138,30 +208,21 @@ def InvokeClangPluginForFilesInQueue(i, input_queue, output_queue, cancel_event,
output_queue.put_nowait(success)
def InvokeClangPluginForEachFile(
filenames,
plugin,
plugin_args,
arch_cfg,
flags,
clang_bin_dir,
clang_plugins_dir,
):
cmd_line = MakeClangCommandLine(plugin, plugin_args, arch_cfg, clang_bin_dir,
clang_plugins_dir)
verbose = flags["verbose"]
if flags["sequential"]:
log("** Sequential execution.")
def invoke_clang_plugin_for_each_file(filenames, plugin, plugin_args, options):
cmd_line = make_clang_command_line(plugin, plugin_args, options)
verbose = options.verbose
if options.sequential:
log("Sequential execution.")
for filename in filenames:
log("-- {}", filename)
returncode, stdout, stderr = InvokeClangPluginForFile(
log(filename, level=1)
returncode, stdout, stderr = invoke_clang_plugin_for_file(
filename, cmd_line, verbose)
if returncode != 0:
sys.stderr.write(stderr)
sys.exit(returncode)
yield filename, stdout, stderr
else:
log("** Parallel execution.")
log("Parallel execution.")
cpus = cpu_count()
input_queue = queue.Queue()
output_queue = queue.Queue()
......@@ -175,7 +236,7 @@ def InvokeClangPluginForEachFile(
for i in range(min(len(filenames), cpus)):
threads.append(
threading.Thread(
target=InvokeClangPluginForFilesInQueue,
target=invoke_clang_plugin_for_files_in_queue,
args=(i, input_queue, output_queue, cancel_event, cmd_line,
verbose)))
......@@ -192,7 +253,7 @@ def InvokeClangPluginForEachFile(
else:
break
filename, returncode, stdout, stderr = output
log("-- {}", filename)
log(filename, level=1)
if returncode != 0:
sys.stderr.write(stderr)
sys.exit(returncode)
......@@ -207,31 +268,30 @@ def InvokeClangPluginForEachFile(
# -----------------------------------------------------------------------------
def ParseGNFile(for_test):
result = {}
def parse_gn_file(options, for_test):
if for_test:
gn_files = [("tools/gcmole/GCMOLE.gn", re.compile('"([^"]*?\.cc)"'), "")]
else:
gn_files = [
("BUILD.gn", re.compile('"([^"]*?\.cc)"'), ""),
("test/cctest/BUILD.gn", re.compile('"(test-[^"]*?\.cc)"'),
"test/cctest/"),
]
return {"all": [options.v8_root_dir / "tools/gcmole/gcmole-test.cc"]}
result = {}
gn_files = [
("BUILD.gn", re.compile('"([^"]*?\.cc)"'), ""),
("test/cctest/BUILD.gn", re.compile('"(test-[^"]*?\.cc)"'),
Path("test/cctest/")),
]
for filename, pattern, prefix in gn_files:
with open(filename) as gn_file:
path = options.v8_root_dir / filename
with open(path) as gn_file:
gn = gn_file.read()
for condition, sources in re.findall("### gcmole\((.*?)\) ###(.*?)\]", gn,
re.MULTILINE | re.DOTALL):
if condition not in result:
result[condition] = []
for file in pattern.findall(sources):
result[condition].append(prefix + file)
result[condition].append(options.v8_root_dir / prefix / file)
return result
def EvaluateCondition(cond, props):
def evaluate_condition(cond, props):
if cond == "all":
return True
......@@ -245,34 +305,19 @@ def EvaluateCondition(cond, props):
return props[p] == v
def BuildFileList(sources, props):
ret = []
for condition, files in sources.items():
if EvaluateCondition(condition, props):
ret += files
return ret
gn_sources = ParseGNFile(for_test=False)
gn_test_sources = ParseGNFile(for_test=True)
def FilesForArch(arch):
return BuildFileList(gn_sources, {
def build_file_list(options, for_test):
sources = parse_gn_file(options, for_test)
props = {
"os": "linux",
"arch": arch,
"arch": options.v8_target_cpu,
"mode": "debug",
"simulator": ""
})
def FilesForTest(arch):
return BuildFileList(gn_test_sources, {
"os": "linux",
"arch": arch,
"mode": "debug",
"simulator": ""
})
}
ret = []
for condition, files in list(sources.items()):
if evaluate_condition(condition, props):
ret += files
return ret
# -----------------------------------------------------------------------------
......@@ -308,19 +353,19 @@ ALLOWLIST = [
GC_PATTERN = ",.*Collect.*Garbage"
SAFEPOINT_PATTERN = ",SafepointSlowPath"
ALLOWLIST_PATTERN = "|".join("(?:%s)" % p for p in ALLOWLIST)
ALLOWLIST_PATTERN = "|".join("(?:{})".format(p) for p in ALLOWLIST)
def MergeRegexp(pattern_dict):
return re.compile("|".join(
"(?P<%s>%s)" % (key, value) for (key, value) in pattern_dict.items()))
def merge_regexp(pattern_dict):
return re.compile("|".join("(?P<{}>{})".format(key, value)
for (key, value) in list(pattern_dict.items())))
IS_SPECIAL_WITHOUT_ALLOW_LIST = MergeRegexp({
IS_SPECIAL_WITHOUT_ALLOW_LIST = merge_regexp({
"gc": GC_PATTERN,
"safepoint": SAFEPOINT_PATTERN
})
IS_SPECIAL_WITH_ALLOW_LIST = MergeRegexp({
IS_SPECIAL_WITH_ALLOW_LIST = merge_regexp({
"gc": GC_PATTERN,
"safepoint": SAFEPOINT_PATTERN,
"allow": ALLOWLIST_PATTERN
......@@ -329,81 +374,83 @@ IS_SPECIAL_WITH_ALLOW_LIST = MergeRegexp({
class GCSuspectsCollector:
def __init__(self, flags):
def __init__(self, options):
self.gc = {}
self.gc_caused = collections.defaultdict(lambda: [])
self.gc_caused = collections.defaultdict(lambda: set())
self.funcs = {}
self.current_caller = None
self.allowlist = flags["allowlist"]
self.allowlist = options.allowlist
self.is_special = IS_SPECIAL_WITH_ALLOW_LIST if self.allowlist else IS_SPECIAL_WITHOUT_ALLOW_LIST
def AddCause(self, name, cause):
self.gc_caused[name].append(cause)
def add_cause(self, name, cause):
self.gc_caused[name].add(cause)
def Parse(self, lines):
def parse(self, lines):
for funcname in lines:
if not funcname:
continue
if funcname[0] != "\t":
self.Resolve(funcname)
self.resolve(funcname)
self.current_caller = funcname
else:
name = funcname[1:]
callers_for_name = self.Resolve(name)
callers_for_name = self.resolve(name)
callers_for_name.add(self.current_caller)
def Resolve(self, name):
def resolve(self, name):
if name not in self.funcs:
self.funcs[name] = set()
m = self.is_special.search(name)
if m:
if m.group("gc"):
self.gc[name] = True
self.AddCause(name, "<GC>")
self.add_cause(name, "<GC>")
elif m.group("safepoint"):
self.gc[name] = True
self.AddCause(name, "<Safepoint>")
self.add_cause(name, "<Safepoint>")
elif m.group("allow"):
self.gc[name] = False
return self.funcs[name]
def Propagate(self):
log("** Propagating GC information")
def propagate(self):
log("Propagating GC information")
def mark(funcname, callers):
for caller in callers:
if caller not in self.gc:
self.gc[caller] = True
mark(caller, self.funcs[caller])
self.add_cause(caller, funcname)
self.AddCause(caller, funcname)
for funcname, callers in self.funcs.items():
for funcname, callers in list(self.funcs.items()):
if self.gc.get(funcname, False):
mark(funcname, callers)
def GenerateGCSuspects(arch, files, arch_cfg, flags, clang_bin_dir,
clang_plugins_dir):
def generate_gc_suspects(files, options):
# Reset the global state.
collector = GCSuspectsCollector(flags)
collector = GCSuspectsCollector(options)
log("** Building GC Suspects for {}", arch)
for filename, stdout, stderr in InvokeClangPluginForEachFile(
files, "dump-callees", [], arch_cfg, flags, clang_bin_dir,
clang_plugins_dir):
collector.Parse(stdout.splitlines())
log("Building GC Suspects for {}", options.v8_target_cpu)
for _, stdout, _ in invoke_clang_plugin_for_each_file(files, "dump-callees",
[], options):
collector.parse(stdout.splitlines())
collector.propagate()
# TODO(cbruni): remove once gcmole.cc is migrated
write_gc_suspects(collector, options.v8_root_dir)
write_gc_suspects(collector, options.out_dir)
log("GCSuspects generated for {}", options.v8_target_cpu)
collector.Propagate()
with open("gcsuspects", "w") as out:
def write_gc_suspects(collector, dst):
with open(dst / "gcsuspects", "w") as out:
for name, value in collector.gc.items():
if value:
out.write(name + "\n")
with open("gccauses", "w") as out:
with open(dst / "gccauses", "w") as out:
out.write("GC = {\n")
for name, causes in collector.gc_caused.items():
out.write(" '{}': [\n".format(name))
......@@ -412,50 +459,35 @@ def GenerateGCSuspects(arch, files, arch_cfg, flags, clang_bin_dir,
out.write(" ],\n")
out.write("}\n")
log("** GCSuspects generated for {}", arch)
# ------------------------------------------------------------------------------
# Analysis
def CheckCorrectnessForArch(arch, for_test, flags, clang_bin_dir,
clang_plugins_dir):
if for_test:
files = FilesForTest(arch)
else:
files = FilesForArch(arch)
arch_cfg = ARCHITECTURES[arch]
def check_correctness_for_arch(options, for_test):
files = build_file_list(options, for_test)
if not flags["reuse_gcsuspects"]:
GenerateGCSuspects(arch, files, arch_cfg, flags, clang_bin_dir,
clang_plugins_dir)
if not options.reuse_gcsuspects:
generate_gc_suspects(files, options)
else:
log("** Reusing GCSuspects for {}", arch)
log("Reusing GCSuspects for {}", options.v8_target_cpu)
processed_files = 0
errors_found = False
output = ""
log(
"** Searching for evaluation order problems{} for {}",
" and dead variables" if flags["dead_vars"] else "",
arch,
)
log("Searching for evaluation order problems " +
(' and dead variables' if options.dead_vars else '') + "for" +
options.v8_target_cpu)
plugin_args = []
if flags["dead_vars"]:
if options.dead_vars:
plugin_args.append("--dead-vars")
if flags["verbose_trace"]:
if options.verbose:
plugin_args.append("--verbose")
for filename, stdout, stderr in InvokeClangPluginForEachFile(
files,
"find-problems",
plugin_args,
arch_cfg,
flags,
clang_bin_dir,
clang_plugins_dir,
):
if options.verbose_trace:
plugin_args.append("--verbose-trace")
for _, _, stderr in invoke_clang_plugin_for_each_file(files, "find-problems",
plugin_args, options):
processed_files = processed_files + 1
if not errors_found:
errors_found = re.search("^[^:]+:\d+:\d+: (warning|error)", stderr,
......@@ -465,112 +497,281 @@ def CheckCorrectnessForArch(arch, for_test, flags, clang_bin_dir,
else:
sys.stdout.write(stderr)
log(
"** Done processing {} files. {}",
processed_files,
"Errors found" if errors_found else "No errors found",
)
log("Done processing {} files.", processed_files)
log("Errors found" if errors_found else "## No errors found")
return errors_found, output
def TestRun(flags, clang_bin_dir, clang_plugins_dir):
log("** Test Run")
errors_found, output = CheckCorrectnessForArch("x64", True, flags,
clang_bin_dir,
clang_plugins_dir)
def test_run(options):
if not options.test_run:
return True
log("Test Run")
errors_found, output = check_correctness_for_arch(options, True)
if not errors_found:
log("** Test file should produce errors, but none were found. Output:")
log(output)
log("Test file should produce errors, but none were found. Output:")
print(output)
return False
filename = "tools/gcmole/test-expectations.txt"
with open(filename) as exp_file:
new_file = options.out_dir / "test-expectations-gen.txt"
with open(new_file, "w") as f:
f.write(output)
log("Wrote test-results: {}", new_file)
expected_file = options.v8_root_dir / "tools/gcmole/test-expectations.txt"
with open(expected_file) as exp_file:
expectations = exp_file.read()
if output != expectations:
log("** Output mismatch from running tests. Please run them manually.")
diff_file = options.out_dir / "test_output.diff"
print("#" * 79)
log("Output mismatch from running tests.")
log("Please run gcmole manually with --test-run --verbose.")
log("Expected: " + expected_file)
log("New: " + new_file)
log("*Diff:* " + diff_file)
print("#" * 79)
for line in difflib.unified_diff(
expectations.splitlines(),
output.splitlines(),
fromfile=filename,
tofile="output",
fromfile=str(new_file),
tofile=str(diff_file),
lineterm="",
):
log("{}", line)
print(line)
log("------")
log("--- Full output ---")
log(output)
log("------")
print("#" * 79)
log("Full output")
log("Expected: " + expected_file)
log("Diff: " + diff_file)
log("*New:* " + new_file)
print("#" * 79)
print(output)
print("#" * 79)
return False
log("** Tests ran successfully")
log("Tests ran successfully")
return True
# =============================================================================
def relative_parents(path, level=0):
return Path(os.path.relpath(str(path.resolve().parents[level])))
def main(args):
DIR = os.path.dirname(args[0])
clang_bin_dir = os.getenv("CLANG_BIN")
clang_plugins_dir = os.getenv("CLANG_PLUGINS")
if not clang_bin_dir or clang_bin_dir == "":
fatal("CLANG_BIN not set")
if not clang_plugins_dir or clang_plugins_dir == "":
clang_plugins_dir = DIR
flags = {
#: not build gcsuspects file and reuse previously generated one.
"reuse_gcsuspects": False,
#:n't use parallel python runner.
"sequential": False,
# Print commands to console before executing them.
"verbose": False,
# Perform dead variable analysis.
"dead_vars": True,
# Enable verbose tracing from the plugin itself.
"verbose_trace": False,
# When building gcsuspects allowlist certain functions as if they can be
# causing GC. Currently used to reduce number of false positives in dead
# variables analysis. See TODO for ALLOWLIST
"allowlist": True,
}
pos_args = []
flag_regexp = re.compile("^--(no[-_]?)?([\w\-_]+)$")
for arg in args[1:]:
m = flag_regexp.match(arg)
if m:
no, flag = m.groups()
flag = flag.replace("-", "_")
if flag in flags:
flags[flag] = no is None
else:
fatal("Unknown flag: {}", flag)
# Print arguments for better debugging on the bots
# Get a clean parent path relative to PWD
gcmole_dir = relative_parents(Path(args[0]))
parser = optparse.OptionParser()
archs = list(ARCHITECTURES.keys())
parser.add_option(
"--v8-target-cpu",
type="choice",
choices=archs,
help="Tested CPU architecture. Choices: {}".format(archs),
metavar="CPU")
default_clang_bin_dir = gcmole_dir / 'gcmole-tools/bin'
parser.add_option(
"--clang-bin-dir",
metavar="DIR",
help="Build dir of the custom clang version for gcmole." + \
"Default: env['CLANG_DIR'] or '{}'".format(default_clang_bin_dir))
parser.add_option(
"--clang-plugins-dir",
metavar="DIR",
help="Containing dir for libgcmole.so."
"Default: env['CLANG_PLUGINS'] or '{}'".format(gcmole_dir))
default_root_dir = relative_parents(gcmole_dir, 1)
parser.add_option(
"--v8-root-dir",
metavar="DIR",
default=default_root_dir,
help="V8 checkout directory. Default: '{}'".format(default_root_dir))
parser.add_option(
"--v8-build-dir",
metavar="BUILD_DIR",
help="GN build dir for v8. Default: 'out/CPU.Release'. "
"Config must match cpu specified by --v8-target-cpu")
parser.add_option(
"--out-dir",
metavar="DIR",
help="Output location for gcsuspect and gcauses file."
"Default: BUILD_DIR/gen/tools/gcmole")
parser.add_option(
"--is-bot",
action="store_true",
default=False,
help="Flag for setting build bot specific settings.")
group = optparse.OptionGroup(parser, "GCMOLE options")
group.add_option(
"--reuse-gcsuspects",
action="store_true",
default=False,
help="Don't build gcsuspects file and reuse previously generated one.")
group.add_option(
"--sequential",
action="store_true",
default=False,
help="Don't use parallel python runner.")
group.add_option(
"--verbose",
action="store_true",
default=False,
help="Print commands to console before executing them.")
group.add_option(
"--no-dead-vars",
action="store_false",
dest="dead_vars",
default=True,
help="Don't perform dead variable analysis.")
group.add_option(
"--verbose-trace",
action="store_true",
default=False,
help="Enable verbose tracing from the plugin itself."
"This can be useful to debug finding dead variable.")
group.add_option(
"--no-allowlist",
action="store_true",
default=True,
dest="allowlist",
help="""When building gcsuspects allowlist certain functions as if they can be
causing GC. Currently used to reduce number of false positives in dead
variables analysis. See TODO for ALLOWLIST in gcmole.py""")
group.add_option(
"--test-run",
action="store_true",
default=False,
help="Test gcmole on tools/gcmole/gcmole-test.cc")
parser.add_option_group(group)
(options, args) = parser.parse_args()
if not options.v8_target_cpu:
# Backwards compatibility
if len(args[0]) > 0 and args[0] in archs:
options.v8_target_cpu = args[0]
log("Using --v8-target-cpu={}", options.v8_target_cpu)
else:
pos_args.append(arg)
parser.error("Missing --v8-target-cpu option")
archs = pos_args if len(pos_args) > 0 else ["ia32", "arm", "x64", "arm64"]
options.is_bot = False
verify_and_convert_dirs(parser, options, gcmole_dir, default_clang_bin_dir)
verify_clang_plugin(parser, options)
prepare_gcmole_files(options)
verify_build_config(parser, options)
any_errors_found = False
if not TestRun(flags, clang_bin_dir, clang_plugins_dir):
if not test_run(options):
any_errors_found = True
else:
for arch in archs:
if not ARCHITECTURES[arch]:
fatal("Unknown arch: {}", arch)
errors_found, output = CheckCorrectnessForArch(arch, False, flags,
clang_bin_dir,
clang_plugins_dir)
any_errors_found = any_errors_found or errors_found
errors_found, output = check_correctness_for_arch(options, False)
any_errors_found = any_errors_found or errors_found
sys.exit(1 if any_errors_found else 0)
def verify_and_convert_dirs(parser, options, gcmole_dir, default_clang_bin_dir):
# Verify options for setting directors and convert the input strings to Path
# objects.
options.v8_root_dir = Path(options.v8_root_dir)
if not options.clang_bin_dir:
if os.getenv("CLANG_BIN"):
options.clang_bin_dir = Path(os.getenv("CLANG_BIN"))
options.is_bot = True
else:
options.clang_bin_dir = default_clang_bin_dir
if not (options.clang_bin_dir / 'clang++').exists():
options.clang_bin_dir = Path(gcmole_dir,
"tools/gcmole/bootstrap/build/bin")
log("Using --clang-bin-dir={}", options.clang_bin_dir)
else:
options.clang_bin_dir = Path(options.clang_bin_dir)
if not options.clang_plugins_dir:
if os.getenv("CLANG_PLUGINS"):
options.clang_plugins_dir = Path(os.getenv("CLANG_PLUGINS"))
else:
options.clang_plugins_dir = gcmole_dir.resolve()
log("Using --clang-plugins-dir={}", options.clang_plugins_dir)
else:
options.clang_plugins_dir = Path(options.clang_plugins_dir)
if not options.v8_build_dir:
config = ARCHITECTURES[options.v8_target_cpu]
options.v8_build_dir = options.v8_root_dir / ('out/%s.release' %
config.name)
# Fallback for build bots.
if not options.v8_build_dir.exists() and os.getenv("CLANG_BIN"):
options.v8_build_dir = options.v8_root_dir / 'out/build'
log("Using --v8-build-dir={}", options.v8_build_dir)
else:
options.v8_build_dir = Path(options.v8_build_dir)
if not options.out_dir:
options.out_dir = options.v8_build_dir / 'gen/tools/gcmole'
if options.v8_build_dir.exists():
options.out_dir.mkdir(parents=True, exist_ok=True)
else:
options.out_dir = Path(options.out_dir)
for flag in [
"--v8-root-dir", "--v8-build-dir", "--clang-bin-dir",
"--clang-plugins-dir", "--out-dir"
]:
dir = getattr(options, parser.get_option(flag).dest)
if not dir.is_dir():
parser.error("{}='{}' does not exist!".format(flag, dir))
def verify_clang_plugin(parser, options):
libgcmole_path = options.clang_plugins_dir / "libgcmole.so"
if not libgcmole_path.is_file():
parser.error("'{}' does not exist. Please build gcmole first.".format(
libgcmole_path))
clang_path = options.clang_bin_dir / "clang++"
if not clang_path.is_file():
parser.error(
"'{}' does not exist. Please build gcmole first.".format(clang_path))
def prepare_gcmole_files(options):
cmd = [
"ninja", "-C", options.v8_build_dir, "v8_gcmole_files",
"v8_dump_build_config"
]
cmd = list(map(str, cmd))
log("Preparing files: {}", " ".join(cmd))
try:
subprocess.check_call(cmd)
except:
# Ignore ninja task errors on the bots
if options.is_bot:
log("Ninja command failed, ignoring errors.")
else:
raise
def verify_build_config(parser, options):
if options.is_bot:
#TODO(cbruni): Fix, currently not supported on the bots
return
config_file = options.v8_build_dir / 'v8_build_config.json'
with open(config_file) as f:
config = json.load(f)
found_cpu = None
for key in ('v8_target_cpu', 'target_cpu', 'current_cpu'):
found_cpu = config.get('v8_target_cpu')
if found_cpu == options.v8_target_cpu:
return
parser.error("Build dir '{}' config doesn't match request cpu. {}: {}".format(
options.v8_build_dir, options.v8_target_cpu, found_cpu))
if __name__ == "__main__":
main(sys.argv)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment