Upload Kmake

This commit is contained in:
Gorochu
2026-05-26 23:36:42 -07:00
parent ba051b2f74
commit 555ec72358
41615 changed files with 13344630 additions and 1 deletions

View File

@ -0,0 +1,37 @@
#!/usr/bin/env python3
# Copyright 2023 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.
"""
Compare builtins hashes from two architectures and enforce their equality.
Bail out with an error and details on the difference otherwise.
"""
import difflib
import sys
assert len(sys.argv) > 3
file1 = sys.argv[1]
file2 = sys.argv[2]
report = sys.argv[3]
with open(file1) as f1, open(file2) as f2:
diff = list(difflib.unified_diff(
f1.read().splitlines(),
f2.read().splitlines(),
fromfile=file1,
tofile=file2,
lineterm='',
))
if diff:
print('Detected incompatible builtins hashes:')
for line in diff:
print(line)
sys.exit(1)
with open(report, 'w') as f:
f.write('No builtins incompatibilities detected.')

136
deps/v8/tools/builtins-pgo/combine_hints.py vendored Executable file
View File

@ -0,0 +1,136 @@
#!/usr/bin/env python3
# Copyright 2022 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 combines the branch hints for profile-guided optimization
produced by get_hints.py. The hints can simply be concatenated in priority order
instead of using this script if the earliest seen hint is to be used.
Usage: combine_hints.py combine-option N output_file hints_file_1 weight_1 hints_file_2 weight_2 ...
where weights_n is the integer weight applied to the hints in hints_file_n
and combine-option N is one of the below:
diff N: Only use the hint when the weighted sum of the hints in one
direction is equal to or greater than the weighted sum of hints
in the opposite direction by at least N.
agreed N: Only use the hint if every file containing this branch agrees
and the weighted sum of these hints is at least N.
Using diff num_input_files and using a weight of 1 for every hints_file will
give the strict intersection of all files.
"""
import argparse
import sys
PARSER = argparse.ArgumentParser(
description="A script that combines the hints produced by get_hints.py",
epilog="Example:\n\tcombine_hints.py combine-option N output_file hints_file_1 2 hints_file_2 1 ...\""
)
PARSER.add_argument(
'combine_option',
choices=['diff', 'agreed'],
help="The combine option dictates how the hints will be combined, diff \
only uses the hint if the positive/negative hints outweigh the \
negative/positive hints by N, while agreed only uses the hint if \
the weighted sum of hints in one direction matches or exceeds N and \
no conflicting hints are found.")
PARSER.add_argument(
'weight_threshold',
type=int,
help="The threshold value which the hint's weight must match or exceed \
to be used.")
PARSER.add_argument(
'output_file',
help="The file which the hints and builtin hashes are written to")
PARSER.add_argument(
'hint_files_and_weights',
nargs=argparse.REMAINDER,
help="The hint files produced by get_hints.py along with their weights")
ARGS = vars(PARSER.parse_args())
BRANCH_HINT_MARKER = "block_hint"
BUILTIN_HASH_MARKER = "builtin_hash"
must_agree = ARGS['combine_option'] == "agreed"
weight_threshold = max(1, ARGS['weight_threshold'])
hint_args = ARGS['hint_files_and_weights']
hint_files_and_weights = zip(hint_args[0::2], hint_args[1::2])
def add_branch_hints(hint_file, weight, branch_hints, builtin_hashes):
try:
with open(hint_file, "r") as f:
for line in f.readlines():
fields = line.split(',')
if fields[0] == BRANCH_HINT_MARKER:
builtin_name = fields[1]
true_block_id = int(fields[2])
false_block_id = int(fields[3])
key = (builtin_name, true_block_id, false_block_id)
delta = weight if (int(fields[4]) > 0) else -weight
if key not in branch_hints:
if must_agree:
# The boolean value records whether or not any conflicts have been
# found for this branch.
initial_hint = (False, 0)
else:
initial_hint = 0
branch_hints[key] = initial_hint
if must_agree:
(has_conflicts, count) = branch_hints[key]
if not has_conflicts:
if abs(delta) + abs(count) == abs(delta + count):
branch_hints[key] = (False, count + delta)
else:
branch_hints[key] = (True, 0)
else:
branch_hints[key] += delta
elif fields[0] == BUILTIN_HASH_MARKER:
builtin_name = fields[1]
builtin_hash = int(fields[2])
if builtin_name in builtin_hashes:
if builtin_hashes[builtin_name] != builtin_hash:
print("Builtin hashes {} and {} for {} do not match.".format(
builtin_hashes[builtin_name], builtin_hash, builtin_name))
sys.exit(1)
else:
builtin_hashes[builtin_name] = builtin_hash
except IOError as e:
print("Cannot read from {}. {}.".format(hint_file, e.strerror))
sys.exit(1)
def write_hints_to_output(output_file, branch_hints, builtin_hashes):
try:
with open(output_file, "w") as f:
for key in branch_hints:
if must_agree:
(has_conflicts, count) = branch_hints[key]
if has_conflicts:
count = 0
else:
count = branch_hints[key]
if abs(count) >= abs(weight_threshold):
hint = 1 if count > 0 else 0
f.write("{},{},{},{},{}\n".format(BRANCH_HINT_MARKER, key[0], key[1],
key[2], hint))
for builtin_name in builtin_hashes:
f.write("{},{},{}\n".format(BUILTIN_HASH_MARKER, builtin_name,
builtin_hashes[builtin_name]))
except IOError as e:
print("Cannot write to {}. {}.".format(output_file, e.strerror))
sys.exit(1)
branch_hints = {}
builtin_hashes = {}
for (hint_file, weight) in hint_files_and_weights:
add_branch_hints(hint_file, int(weight), branch_hints, builtin_hashes)
write_hints_to_output(ARGS['output_file'], branch_hints, builtin_hashes)

View File

@ -0,0 +1,278 @@
#!/usr/bin/env python3
# Copyright 2023 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.
"""
Download PGO profiles for V8 builtins. The version is pulled from V8's version
file (include/v8-version.h).
See argparse documentation for usage details.
"""
import argparse
from functools import cached_property
import json
import os
import pathlib
import re
import sys
FILE = pathlib.Path(os.path.abspath(__file__))
V8_DIR = FILE.parents[2]
PGO_PROFILE_DIR = V8_DIR / 'tools/builtins-pgo/profiles'
PGO_PROFILE_BUCKET = 'chromium-v8-builtins-pgo'
CHROMIUM_DEPS_V8_REVISION = r"'v8_revision': '([0-9a-f]{40})',"
DEPOT_TOOLS_DEFAULT_PATH = V8_DIR / 'third_party/depot_tools'
VERSION_FILE = V8_DIR / 'include/v8-version.h'
VERSION_RE = r"""#define V8_MAJOR_VERSION (\d+)
#define V8_MINOR_VERSION (\d+)
#define V8_BUILD_NUMBER (\d+)
#define V8_PATCH_LEVEL (\d+)"""
class ProfileDownloader:
def __init__(self, cmd_args=None):
self.args = self._parse_args(cmd_args)
self._import_gsutil()
def run(self):
if self.args.action == 'download':
self._download()
sys.exit(0)
if self.args.action == 'validate':
self._validate()
sys.exit(0)
raise AssertionError(f'Invalid action: {args.action}')
def _parse_args(self, cmd_args):
parser = argparse.ArgumentParser(
description=(
f'Download PGO profiles for V8 builtins generated for the version '
f'defined in {VERSION_FILE}. If the current checkout has no '
f'version (i.e. build and patch level are 0 in {VERSION_FILE}), no '
f'profiles exist and the script returns without errors.'),
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog='\n'.join([
f'examples:', f' {FILE.name} download',
f' {FILE.name} validate --bucket=chromium-v8-builtins-pgo-staging',
f'', f'return codes:',
f' 0 - profiles successfully downloaded or validated',
f' 1 - unexpected error, see stdout',
f' 2 - invalid arguments specified, see {FILE.name} --help',
f' 3 - invalid path to depot_tools provided'
f' 4 - gsutil was unable to retrieve data from the bucket'
f' 5 - profiles have been generated for a different revision'
f' 6 - chromium DEPS file found without v8 revision'
f' 7 - no chromium DEPS file found'
]),
)
parser.add_argument(
'action',
choices=['download', 'validate'],
help=(
'download or validate profiles for the currently checked out '
'version'
),
)
parser.add_argument(
'--version',
help=('download (or validate) profiles for this version (e.g. '
'11.0.226.0 or 11.0.226.2), defaults to the version in v8\'s '
'version file'),
)
parser.add_argument(
'--depot-tools',
help=('path to depot tools, defaults to V8\'s version in '
f'{DEPOT_TOOLS_DEFAULT_PATH}.'),
type=pathlib.Path,
default=DEPOT_TOOLS_DEFAULT_PATH,
)
parser.add_argument(
'--force',
help='force download, overwriting existing profiles',
action='store_true',
)
parser.add_argument(
'--quiet',
help='run silently, still display errors',
action='store_true',
)
parser.add_argument(
'--check-v8-revision',
help='validate profiles are built for chromium\'s V8 revision',
action='store_true',
)
return parser.parse_args(cmd_args)
def _import_gsutil(self):
abs_depot_tools_path = os.path.abspath(self.args.depot_tools)
file = os.path.join(abs_depot_tools_path, 'download_from_google_storage.py')
if not pathlib.Path(file).is_file():
message = f'{file} does not exist; check --depot-tools path.'
self._fail(3, message)
# Put this path at the beginning of the PATH to give it priority.
sys.path.insert(0, abs_depot_tools_path)
globals()['gcs_download'] = __import__('download_from_google_storage')
@cached_property
def version(self):
if self.args.version:
return self.args.version
with VERSION_FILE.open() as f:
version_tuple = re.search(VERSION_RE, f.read()).groups(0)
version = '.'.join(version_tuple)
if version_tuple[2] == version_tuple[3] == '0':
self._log(f'The version file specifies {version}, which has no profiles.')
sys.exit(0)
return version
@cached_property
def _remote_profile_path(self):
return f'{PGO_PROFILE_BUCKET}/by-version/{self.version}'
@cached_property
def _meta_json_path(self):
return PGO_PROFILE_DIR / 'meta.json'
@cached_property
def _v8_revision(self):
"""If this script is executed within a chromium checkout, return the V8
revision defined in chromium. Otherwise return None."""
chromium_deps_file = V8_DIR.parent / 'DEPS'
if not chromium_deps_file.is_file():
message = (
f'File {chromium_deps_file} not found. Verify the parent directory '
f'of V8 is a valid chromium checkout.'
)
self._fail(7, message)
with chromium_deps_file.open() as f:
chromum_deps = f.read()
match = re.search(CHROMIUM_DEPS_V8_REVISION, chromum_deps)
if not match:
message = (
f'No V8 revision can be found in {chromium_deps_file}. Verify this '
f'is a valid chromium DEPS file including a v8 version entry.'
)
self._fail(6, message)
return match.group(1)
def _download(self):
if self._require_download():
# Wipe profiles directory.
for file in PGO_PROFILE_DIR.glob('*'):
if file.name.startswith('.'):
continue
file.unlink()
# Download new profiles.
path = self._remote_profile_path
cmd = ['cp', '-R', f'gs://{path}/*', str(PGO_PROFILE_DIR)]
failure_hint = f'https://storage.googleapis.com/{path} does not exist.'
self._call_gsutil(cmd, failure_hint)
# Validate profile revision matches the current V8 revision.
if self.args.check_v8_revision:
with self._meta_json_path.open() as meta_json_file:
meta_json = json.load(meta_json_file)
if meta_json['revision'] != self._v8_revision:
message = (
f'V8 Builtins PGO profiles have been built for '
f'{meta_json["revision"]}, but this chromium checkout uses '
f'{self._v8_revision} in its DEPS file. Invalid profiles might '
f'cause the build to fail or result in performance regressions. '
f'Select a V8 revision which has up-to-date profiles or build with '
f'pgo disabled.'
)
self._fail(5, message)
def _require_download(self):
if self.args.force:
return True
if not self._meta_json_path.is_file():
return True
with self._meta_json_path.open() as meta_json_file:
try:
meta_json = json.load(meta_json_file)
except json.decoder.JSONDecodeError:
return True
if meta_json['version'] != self.version:
return True
self._log('Profiles already downloaded, use --force to overwrite.')
return False
def _validate(self):
meta_json = f'{self._remote_profile_path}/meta.json'
cmd = ['stat', f'gs://{meta_json}']
failure_hint = (
f'https://storage.googleapis.com/{meta_json} does not exist. This '
f'error might be transient. Creating PGO data takes ~20 min after a '
f'merge to a release branch. You can follow current PGO creation at '
f'https://ci.chromium.org/ui/p/v8/builders/ci-hp/PGO%20Builder and '
f'retry the release builder when it\'s ready.')
self._call_gsutil(cmd, failure_hint)
def _call_gsutil(self, cmd, failure_hint):
# Load gsutil from depot tools, and execute command
gsutil = gcs_download.Gsutil(gcs_download.GSUTIL_DEFAULT_PATH)
returncode, stdout, stderr = gsutil.check_call(*cmd)
if returncode != 0:
self._print_error(['gsutil', *cmd], returncode, stdout, stderr, failure_hint)
sys.exit(4)
def _print_error(self, cmd, returncode, stdout, stderr, failure_hint):
message = [
'The following command did not succeed:',
f' $ {" ".join(cmd)}',
]
sections = [
('return code', str(returncode)),
('stdout', stdout.strip()),
('stderr', stderr.strip()),
('hint', failure_hint),
]
for label, output in sections:
if not output:
continue
message += [f'{label}:', " " + "\n ".join(output.split("\n"))]
print('\n'.join(message), file=sys.stderr)
def _log(self, message):
if self.args.quiet:
return
print(message)
def _fail(self, returncode, message):
print(message, file=sys.stderr)
sys.exit(returncode)
if __name__ == '__main__':
downloader = ProfileDownloader()
downloader.run()

View File

@ -0,0 +1,184 @@
#!/usr/bin/env python3
# Copyright 2023 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 contextlib
import io
import json
import os
import pathlib
import unittest
from tempfile import TemporaryDirectory
from unittest.mock import patch, mock_open
from download_profiles import ProfileDownloader
class BaseTestCase(unittest.TestCase):
def setUp(self):
self.chromium_dir = TemporaryDirectory()
self.chromium_path = pathlib.Path(self.chromium_dir.name)
os.makedirs(self.profiles_path)
patch('download_profiles.PGO_PROFILE_DIR', self.profiles_path).start()
patch('download_profiles.V8_DIR', self.chromium_path / 'v8').start()
patch('download_profiles.VERSION_FILE', self.version_h_path).start()
def tearDown(self):
patch.stopall()
self.chromium_dir.cleanup()
def add_version_h_file(self, major, minor, build=0, patch=0):
self.version_h_path.parents[0].mkdir(parents=True, exist_ok=True)
with self.version_h_path.open('w') as f:
f.write(
f'#define V8_MAJOR_VERSION {major}\n'
f'#define V8_MINOR_VERSION {minor}\n'
f'#define V8_BUILD_NUMBER {build}\n'
f'#define V8_PATCH_LEVEL {patch}\n'
)
@property
def v8_path(self):
return self.chromium_path / 'v8'
@property
def profiles_path(self):
return self.v8_path / 'tools/builtins-pgo/profiles'
@property
def version_h_path(self):
return self.v8_path / 'include/v8-version.h'
class TestDownloadProfiles(BaseTestCase):
def _test_cmd(self, cmd, exitcode):
out = io.StringIO()
err = io.StringIO()
with self.assertRaises(SystemExit) as se, \
contextlib.redirect_stdout(out), \
contextlib.redirect_stderr(err):
downloader = ProfileDownloader(cmd)
# Note: This loads the version file before running the downloader to
# simplify patching.
downloader.version
downloader.run()
self.assertEqual(se.exception.code, exitcode)
return out.getvalue(), err.getvalue()
def test_validate_profiles(self):
out, err = self._test_cmd(['validate', '--version', '11.1.0.0'], 0)
self.assertEqual(len(out), 0)
self.assertEqual(len(err), 0)
def test_download_profiles(self):
out, err = self._test_cmd(['download', '--version', '11.1.0.0'], 0)
self.assertEqual(len(out), 0)
self.assertEqual(len(err), 0)
self.assertTrue(any(
f.name.endswith('.profile') for f in self.profiles_path.glob('*')))
with (self.profiles_path / 'meta.json').open() as f:
self.assertEqual(json.load(f)['version'], '11.1.0.0')
# A second download should not be started as profiles exist already
with patch('download_profiles.ProfileDownloader._call_gsutil') as gsutil:
out, err = self._test_cmd(['download', '--version', '11.1.0.0'], 0)
self.assertEqual(out,
'Profiles already downloaded, use --force to overwrite.\n')
gsutil.assert_not_called()
# A forced download should always trigger
with patch('download_profiles.ProfileDownloader._call_gsutil') as gsutil:
cmd = ['download', '--version', '11.1.0.0', '--force']
out, err = self._test_cmd(cmd, 0)
self.assertEqual(len(out), 0)
self.assertEqual(len(err), 0)
gsutil.assert_called_once()
def test_arg_quiet(self):
self.add_version_h_file(11, 9)
out, err = self._test_cmd(['download'], 0)
self.assertGreater(len(out), 0)
out, err = self._test_cmd(['download', '--quiet'], 0)
self.assertEqual(len(out), 0)
def test_invalid_args(self):
out, err = self._test_cmd(['invalid-action'], 2)
self.assertEqual(len(out), 0)
self.assertGreater(len(err), 0)
def test_invalid_depot_tools_path(self):
out, err = self._test_cmd(
['validate', '--depot-tools', '/no-depot-tools-path'], 3)
self.assertEqual(len(out), 0)
self.assertGreater(len(err), 0)
def test_missing_profiles(self):
out, err = self._test_cmd(['download', '--version', '0.0.0.42'], 4)
self.assertEqual(len(out), 0)
self.assertGreater(len(err), 0)
def test_chromium_deps_valid_v8_revision(self):
with (self.chromium_path / 'DEPS').open('w') as f:
f.write("'v8_revision': '778cc4ae9ebb1973e900d4a56a16e6415492ab1d',")
cmd = ['download', '--version', '11.1.0.0', '--check-v8-revision']
self._test_cmd(cmd, 0)
def test_chromium_deps_no_file(self):
cmd = ['download', '--version', '11.1.0.0', '--check-v8-revision']
self._test_cmd(cmd, 7)
def test_chromium_deps_no_v8_revision(self):
with (self.chromium_path / 'DEPS').open('w') as f:
pass
cmd = ['download', '--version', '11.1.0.0', '--check-v8-revision']
self._test_cmd(cmd, 6)
def test_chromium_deps_invalid_v8_revision(self):
with (self.chromium_path / 'DEPS').open('w') as f:
f.write("'v8_revision': 'deadbeefdeadbeefdeadbeefdeadbeefdeadbeef',")
cmd = ['download', '--version', '11.1.0.0', '--check-v8-revision']
self._test_cmd(cmd, 5)
class TestRetrieveVersion(BaseTestCase):
def test_retrieve_valid_version(self):
self.add_version_h_file(11, 4, 1)
downloader = ProfileDownloader(['download'])
self.assertEqual(downloader.version, '11.4.1.0')
def test_retrieve_parameter_version(self):
downloader = ProfileDownloader(['download', '--version', '11.1.1.42'])
self.assertEqual(downloader.version, '11.1.1.42')
def test_retrieve_untagged_version(self):
self.add_version_h_file(11, 4)
out = io.StringIO()
with contextlib.redirect_stdout(out), self.assertRaises(SystemExit) as se:
downloader = ProfileDownloader(['download'])
downloader.version
self.assertEqual(se.exception.code, 0)
self.assertEqual(out.getvalue(),
'The version file specifies 11.4.0.0, which has no profiles.\n')
if __name__ == '__main__':
unittest.main()

113
deps/v8/tools/builtins-pgo/generate.py vendored Executable file
View File

@ -0,0 +1,113 @@
#!/usr/bin/env python3
# Copyright 2022 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 subprocess
import argparse
from pathlib import Path
parser = argparse.ArgumentParser(
description='Generate builtin PGO profiles. ' +
'The script has to be run from the root of a V8 checkout and updates the profiles in `tools/builtins-pgo/profiles`.'
)
parser.add_argument(
'v8_target_cpu', help='target cpu to build the profile for: x64 or arm64')
parser.add_argument(
'--target-cpu',
default=None,
help='target cpu for V8 binary (for simulator builds), by default it\'s equal to `v8_target_cpu`'
)
parser.add_argument(
'--clang',
default=True,
help='Use clang for building V8 binaries. Using other compiler helps to get profiles for Windows/gcc. See crbug.com/v8/13647.',
action=argparse.BooleanOptionalAction)
parser.add_argument(
'--use-qemu',
default=False,
help='Use qemu for running cross-compiled V8 binary.',
action=argparse.BooleanOptionalAction)
parser.add_argument(
'benchmark_path',
help='path to benchmark runner .js file, usually JetStream2\'s `cli.js`',
type=Path)
parser.add_argument(
'--out-path',
default=Path("out"),
help='directory to be used for building V8, by default `./out`',
type=Path)
args = parser.parse_args()
if args.target_cpu is None:
args.target_cpu = args.v8_target_cpu
def run(cmd, **kwargs):
print(f"# CMD: {cmd} {kwargs}")
return subprocess.run(cmd, **kwargs, check=True)
def build_d8(path, gn_args):
if not path.exists():
path.mkdir(parents=True, exist_ok=True)
with (path / "args.gn").open("w") as f:
f.write(gn_args)
run(["gn", "gen", path])
run(["autoninja", "-C", path, "d8"])
return (path / "d8").absolute()
tools_pgo_dir = Path(__file__).parent
v8_path = tools_pgo_dir.parent.parent
if not args.benchmark_path.is_file() or args.benchmark_path.suffix != ".js":
print(f"Invalid benchmark argument: {args.benchmark_path}")
exit(1)
cmd_prefix = []
if args.use_qemu:
if args.v8_target_cpu == "arm":
cmd_prefix = ["qemu-arm", "-L", "/usr/arm-linux-gnueabihf/"]
elif args.v8_target_cpu == "arm64":
cmd_prefix = ["qemu-aarch64", "-L", "/usr/aarch64-linux-gnu/"]
else:
print(f"{args.v8_target_cpu} binaries can't be run with qemu")
exit(1)
GN_ARGS_TEMPLATE_CLANG = f"""\
is_debug = false
is_clang = true
target_cpu = "{args.target_cpu}"
v8_target_cpu = "{args.v8_target_cpu}"
v8_enable_builtins_profiling = true
"""
GN_ARGS_TEMPLATE_NO_CLANG = f"""\
is_debug = false
is_clang = false
use_custom_libcxx = false
target_cpu = "{args.target_cpu}"
v8_target_cpu = "{args.v8_target_cpu}"
v8_enable_builtins_profiling = true
"""
GN_ARGS_TEMPLATE = GN_ARGS_TEMPLATE_CLANG if args.clang else GN_ARGS_TEMPLATE_NO_CLANG
for arch, gn_args in [(args.v8_target_cpu, GN_ARGS_TEMPLATE)]:
# TODO(crbug.com/v8/13647): remove profile suffixes once CSA is fixed.
suffix = "" if args.clang else "-rl"
build_dir = args.out_path / f"{arch}{suffix}.release.generate_builtin_pgo_profile"
d8_path = build_d8(build_dir, gn_args)
benchmark_dir = args.benchmark_path.parent
benchmark_file = args.benchmark_path.name
log_path = (build_dir / "v8.builtins.pgo").absolute()
cmd = cmd_prefix + [
d8_path, f"--turbo-profiling-output={log_path}", benchmark_file
]
run(cmd, cwd=benchmark_dir)
get_hints_path = tools_pgo_dir / "get_hints.py"
profile_path = tools_pgo_dir / "profiles" / f"{arch}{suffix}.profile"
run([get_hints_path, log_path, profile_path])

191
deps/v8/tools/builtins-pgo/get_hints.py vendored Executable file
View File

@ -0,0 +1,191 @@
#!/usr/bin/env python3
# Copyright 2022 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 generates the branch hints for profile-guided optimization of
the builtins in the following format:
block_hint,<builtin_name>,<basic_block_id_for_true_destination>,<basic_block_id_for_false_destination>,<hint>
where hint is an integer representation of the expected boolean result of the
branch condition. The expected boolean result is generated for a specific given
branch by comparing the counts of the two destination basic blocks. V8's
control flow graph is always in edge-split form, guaranteeing that each
destination block only has a single predecessor, and thus guaranteeing that the
execution counts of these basic blocks are equal to how many times the branch
condition is true or false.
Usage: get_hints.py [--min MIN] [--ratio RATIO] log_file output_file
where:
1. log_file is the file produced after running v8 with the
--turbo-profiling-output=log_file flag after building with
v8_enable_builtins_profiling = true.
2. output_file is the file which the hints and builtin hashes are written
to.
3. --min MIN provides the minimum count at which a basic block will be taken
as a valid destination of a hinted branch decision.
4. --ratio RATIO provides the ratio at which, when compared to the
alternative destination's count, a branch destination's count is
considered sufficient to require a branch hint to be produced.
"""
import argparse
import sys
PARSER = argparse.ArgumentParser(
description="A script that generates the branch hints for profile-guided \
optimization",
epilog="Example:\n\tget_hints.py --min n1 --ratio n2 branches_file log_file output_file\""
)
PARSER.add_argument(
'--min',
type=int,
default=1000,
help="The minimum count at which a basic block will be taken as a valid \
destination of a hinted branch decision")
PARSER.add_argument(
'--ratio',
type=int,
default=40,
help="The ratio at which, when compared to the alternative destination's \
count,a branch destination's count is considered sufficient to \
require a branch hint to be produced")
PARSER.add_argument(
'log_file',
help="The v8.log file produced after running v8 with the --turbo-profiling-output=log_file flag after building with v8_enable_builtins_profiling = true"
)
PARSER.add_argument(
'output_file',
help="The file which the hints and builtin hashes are written to")
ARGS = vars(PARSER.parse_args())
BLOCK_COUNT_MARKER = "block"
BRANCH_HINT_MARKER = "block_hint"
BUILTIN_HASH_MARKER = "builtin_hash"
NORMALIZED_BLOCK_COUNT_MARKER = "block_count"
NORMALIZED_BUILTIN_COUNT_MARKER = "builtin_count"
MAX_NORMALIZED_COUNT = 10000
def parse_log_file(log_file):
block_counts = {}
branches = []
builtin_hashes = {}
max_execution_count = 0
try:
with open(log_file, "r") as f:
for line in f.readlines():
fields = line.split('\t')
if fields[0] == BLOCK_COUNT_MARKER:
builtin_name = fields[1]
block_id = int(fields[2])
count = float(fields[3])
if block_id == 0 and count > max_execution_count:
max_execution_count = count
if builtin_name not in block_counts:
block_counts[builtin_name] = []
while len(block_counts[builtin_name]) <= block_id:
block_counts[builtin_name].append(0)
block_counts[builtin_name][block_id] += count
elif fields[0] == BUILTIN_HASH_MARKER:
builtin_name = fields[1]
builtin_hash = int(fields[2])
if builtin_name in builtin_hashes:
old_hash = builtin_hashes[builtin_name]
assert old_hash == builtin_hash, (
"Merged PGO file contains multiple incompatible builtin "
"versions: {old_hash} != {builtin_hash}")
else:
builtin_hashes[builtin_name] = builtin_hash
elif fields[0] == BRANCH_HINT_MARKER:
builtin_name = fields[1]
true_block_id = int(fields[2])
false_block_id = int(fields[3])
branches.append((builtin_name, true_block_id, false_block_id))
except IOError as e:
print(f"Cannot read from {log_file}. {e.strerror}.")
sys.exit(1)
return [block_counts, branches, builtin_hashes, max_execution_count]
def get_branch_hints(block_counts, branches, min_count, threshold_ratio):
branch_hints = {}
for (builtin_name, true_block_id, false_block_id) in branches:
if builtin_name in block_counts:
true_block_count = 0
false_block_count = 0
if true_block_id < len(block_counts[builtin_name]):
true_block_count = block_counts[builtin_name][true_block_id]
if false_block_id < len(block_counts[builtin_name]):
false_block_count = block_counts[builtin_name][false_block_id]
hint = -1
if (true_block_count >= min_count) and (true_block_count / threshold_ratio
>= false_block_count):
hint = 1
elif (false_block_count >= min_count) and (
false_block_count / threshold_ratio >= true_block_count):
hint = 0
if hint >= 0:
branch_hints[(builtin_name, true_block_id, false_block_id)] = hint
return branch_hints
def normalize_count(block_counts, max_count):
normalized_block_counts = {}
for builtin in block_counts:
for block, count in enumerate(block_counts[builtin]):
block_count_normalized = int(count * MAX_NORMALIZED_COUNT / max_count)
if builtin not in normalized_block_counts:
normalized_block_counts[builtin] = {}
normalized_block_counts[builtin][block] = block_count_normalized
return normalized_block_counts
def write_hints_to_output(output_file, branch_hints, builtin_hashes,
block_counts):
try:
with open(output_file, "w") as f:
for key in branch_hints:
f.write("{},{},{},{},{}\n".format(BRANCH_HINT_MARKER, key[0], key[1],
key[2], branch_hints[key]))
# Put the NORMALIZED_BUILTIN_COUNT_MARKER before NORMALIZED_BLOCK_COUNT_MARKER,
# because we will use them to calculate call probability.
# The normalized count of a builtin/block means how much frequently the
# builtin/block was executed.
# We will take it as "density" of the builtin/block in post process, hence
# the execution time could be "density * size".
for builtin_name in block_counts.keys():
f.write("{},{},{}\n".format(NORMALIZED_BUILTIN_COUNT_MARKER,
builtin_name,
block_counts[builtin_name][0]))
for builtin_name in block_counts:
for block_id in block_counts[builtin_name]:
f.write("{},{},{},{}\n".format(NORMALIZED_BLOCK_COUNT_MARKER,
builtin_name, block_id,
block_counts[builtin_name][block_id]))
for builtin_name in builtin_hashes:
f.write("{},{},{}\n".format(BUILTIN_HASH_MARKER, builtin_name,
builtin_hashes[builtin_name]))
except IOError as e:
print(f"Cannot read from {output_file}. {e.strerror}.")
sys.exit(1)
[block_counts, branches, builtin_hashes,
max_count] = parse_log_file(ARGS['log_file'])
branch_hints = get_branch_hints(block_counts, branches, ARGS['min'],
ARGS['ratio'])
block_counts = normalize_count(block_counts, max_count)
write_hints_to_output(ARGS['output_file'], branch_hints, builtin_hashes,
block_counts)

84
deps/v8/tools/builtins-pgo/profile_only.py vendored Executable file
View File

@ -0,0 +1,84 @@
#!/usr/bin/env python3
# Copyright 2023 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 pathlib import Path
import argparse
import subprocess
import sys
def main():
args = parse_arguments()
run_benchmark(args.benchmark_path, args.d8_path, args.output_dir)
run_get_hints(args.output_dir, args.profile_name)
def parse_arguments():
parser = argparse.ArgumentParser(
description=('Generate builtin PGO profiles. '
'The script is designed to run in swarming context where '
'the isolate aready contains the instrumented binary.'))
parser.add_argument(
'--profile-name',
default='pgo',
help='target cpu to build the profile for: x64 or arm64')
parser.add_argument(
'--benchmark_path',
default=Path('./JetStream2/cli.js'),
help='path to benchmark runner .js file, usually JetStream2\'s `cli.js`',
type=Path)
parser.add_argument(
'--d8-path',
default=Path('./out/build/d8'),
help=('path to the d8 executable, by default `./out/build/d8` in '
'swarming context'),
type=Path)
parser.add_argument('--output-dir', type=Path)
return parser.parse_args()
def run_benchmark(benchmark_path, d8_path, output_dir):
root_dir = tools_pgo_dir().parent.parent
benchmark_dir = (root_dir / benchmark_path).parent.absolute()
assert benchmark_dir.exists(), "Could not find benchmark path!"
benchmark_file = benchmark_path.name
d8_path_abs = (root_dir / d8_path).absolute()
assert d8_path_abs.exists(), "Could not find d8 path!"
log_path = benchmark_log_path(output_dir)
cmd = [d8_path_abs, f"--turbo-profiling-output={log_path}", benchmark_file]
run(cmd, cwd=benchmark_dir)
assert log_path.exists(), "Could not find benchmark logs path!"
def tools_pgo_dir():
return Path(__file__).parent
def benchmark_log_path(output_dir):
return (output_dir / "v8.builtins.pgo").absolute()
def run_get_hints(output_dir, profile_name):
get_hints_path = (tools_pgo_dir() / "get_hints.py").absolute()
assert get_hints_path.exists(), "Could not find get_hints.py script path!"
profile_path = (output_dir / f"{profile_name}.profile").absolute()
run([
sys.executable, '-u', get_hints_path,
benchmark_log_path(output_dir), profile_path
])
assert profile_path.exists(), "Could not find profile path!"
def run(cmd, **kwargs):
print(f"# CMD: {cmd} {kwargs}")
subprocess.run(cmd, **kwargs, check=True)
if __name__ == "__main__": # pragma: no cover
sys.exit(main())

View File