Skip to content

Commit 55d6f65

Browse files
committed
[GR-75206] [GR-75115] Add GraalPy native-image PolyBench guest VM
PullRequest: graalpython/4444
2 parents da03314 + 5f115a2 commit 55d6f65

3 files changed

Lines changed: 652 additions & 12 deletions

File tree

graalpython/com.oracle.graal.python.benchmarks/python/harness.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -527,7 +527,7 @@ def run_benchmark(args):
527527

528528
if GRAALPYTHON:
529529
print(f"### using bytecode DSL interpreter: {__graalpython__.is_bytecode_dsl_interpreter}")
530-
print(f"### using forced uncached interpreter: {getattr(__graalpython__, "is_forced_uncached_interpreter", False)}")
530+
print(f"### using forced uncached interpreter: {getattr(__graalpython__, 'is_forced_uncached_interpreter', False)}")
531531

532532
BenchRunner(bench_file, bench_args=bench_args, iterations=iterations, warmup=warmup, warmup_runs=warmup_runs, startup=startup, live_results=live_results, self_measurement=self_measurement).run()
533533

mx.graalpython/mx_graalpython.py

Lines changed: 169 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
from __future__ import print_function
2626

27+
import ast
2728
import contextlib
2829
import datetime
2930
import glob
@@ -109,6 +110,8 @@ def get_boolean_env(name, default=False):
109110
os.environ["GRAALPY_VERSION"] = GRAAL_VERSION
110111

111112
MAIN_BRANCH = 'master'
113+
GRAALPY_PGO_PROFILE_ARTIFACT_GROUP = "graalpy"
114+
GRAALPY_PGO_PROFILE_ARTIFACT_PREFIX = "pgo-"
112115

113116
GRAALPYTHON_MAIN_CLASS = "com.oracle.graal.python.shell.GraalPythonMain"
114117

@@ -293,6 +296,98 @@ def _is_overridden_native_image_arg(prefix):
293296
return any(arg.startswith(prefix) for arg in extras)
294297

295298

299+
def _normalize_branch_name(branch):
300+
if not branch:
301+
return ""
302+
branch = branch.strip()
303+
for prefix in ("refs/heads/", "origin/"):
304+
if branch.startswith(prefix):
305+
return branch[len(prefix):]
306+
return branch
307+
308+
309+
def _graalpython_target_import_commit(target_branch):
310+
"""Return the GraalPy commit imported by the target branch's VM suite.
311+
312+
Product PGO profiles are produced by the GraalPy post-merge profile job and
313+
keyed by GraalPy commit. In linked cross-repo PR gates, CI can auto-bump the
314+
VM suite's GraalPy import to the GraalPython PR merge commit, which normally
315+
has no post-merge product profile. For feature branches, product-ee therefore
316+
uses the GraalPy import from the target VM branch instead, bypassing that
317+
CI-generated import bump.
318+
319+
Prefer origin/<target>. Some CI workspaces only have the PR merge commit; in
320+
that case HEAD^1 is the target-side parent and still contains the target
321+
branch import. Local workspaces can fall back to the checked-out VM suite.
322+
The suite file is a literal dict, so parse it instead of executing suite.py.
323+
"""
324+
vm_suite = mx.suite("vm", fatalIfMissing=False)
325+
if not vm_suite or not vm_suite.vc:
326+
mx.warn("Cannot resolve target-branch GraalPy import: mx suite 'vm' is not available")
327+
return None
328+
329+
target_branch = _normalize_branch_name(target_branch)
330+
suite_path = "vm/mx.vm/suite.py"
331+
332+
# Some CI checkouts do not keep origin/<target>. In PR merge checkouts, the
333+
# first parent is the target branch side of the merge and therefore still
334+
# gives us the target branch's imported GraalPy commit without fetching.
335+
suite_text = None
336+
refs = [f"origin/{target_branch}"]
337+
parents = vm_suite.vc.git_command(
338+
vm_suite.vc_dir,
339+
["rev-list", "--parents", "-n", "1", "HEAD"],
340+
abortOnError=False,
341+
)
342+
if parents and len(parents.split()) > 2:
343+
refs.append("HEAD^1")
344+
for ref in refs:
345+
if ref:
346+
suite_text = vm_suite.vc.git_command(
347+
vm_suite.vc_dir,
348+
["show", f"{ref}:{suite_path}"],
349+
abortOnError=False,
350+
)
351+
if suite_text:
352+
break
353+
354+
if not suite_text:
355+
suite_file = os.path.join(vm_suite.dir, f"mx.{vm_suite.name}", "suite.py")
356+
try:
357+
with open(suite_file, encoding="utf-8") as f:
358+
suite_text = f.read()
359+
except OSError:
360+
mx.warn(f"Cannot read {suite_path} to resolve the GraalPy profile source")
361+
return None
362+
363+
try:
364+
suite_node = next(
365+
node.value
366+
for node in ast.parse(suite_text, filename=suite_path).body
367+
if isinstance(node, ast.Assign)
368+
and any(isinstance(target, ast.Name) and target.id == "suite" for target in node.targets)
369+
)
370+
suite = ast.literal_eval(suite_node)
371+
imports = suite.get("imports", {}).get("suites", [])
372+
matches = []
373+
for imported_suite in imports:
374+
version = imported_suite.get("version")
375+
if (
376+
imported_suite.get("name") == "graalpython"
377+
and isinstance(version, str)
378+
and re.match(r"^[0-9a-f]{40}$", version)
379+
):
380+
matches.append(version)
381+
except (StopIteration, SyntaxError, ValueError, TypeError) as e:
382+
mx.warn(f"Cannot evaluate {suite_path} to resolve the GraalPy profile source: {e}")
383+
return None
384+
385+
if len(matches) == 1:
386+
return matches[0]
387+
mx.warn(f"Expected exactly one graalpython import in target vm suite, found {len(matches)}")
388+
return None
389+
390+
296391
def github_ci_build_args():
297392
# Determine memory and parallelism for GitHub CI builds
298393
# Use 90% of available memory up to 14GB, but at least 8GB
@@ -347,15 +442,57 @@ def libpythonvm_build_args():
347442
build_args += ['-H:-ProtectionKeys']
348443

349444
profile = None
445+
require_profile = get_boolean_env("GRAALPY_REQUIRE_PGO_PROFILE")
350446
if (
351447
"GRAALPY_PGO_PROFILE" not in os.environ
352448
and mx.suite('graalpython-enterprise', fatalIfMissing=False)
353449
and mx_sdk_vm_ng.get_bootstrap_graalvm_version() >= mx.VersionSpec("25.0")
354450
and not _is_overridden_native_image_arg("--pgo")
355451
):
356452
vc = SUITE.vc
357-
commit = str(vc.tip(SUITE.dir)).strip()
358-
branch = str(vc.active_branch(SUITE.dir, abortOnError=False) or 'master').strip()
453+
source_commit = str(vc.tip(SUITE.dir)).strip()
454+
source_branch = _normalize_branch_name(
455+
os.environ.get("FROM_BRANCH") or vc.active_branch(SUITE.dir, abortOnError=False) or 'master'
456+
)
457+
target_branch = _normalize_branch_name(os.environ.get("TO_BRANCH"))
458+
profile_source_commit = source_commit
459+
profile_source_branch = source_branch
460+
profile_source_reason = "current GraalPy commit"
461+
override = os.environ.get("GRAALPY_PGO_PROFILE_SOURCE_COMMIT")
462+
if override:
463+
profile_source_commit = override.strip()
464+
if not re.match(r"^[0-9a-f]{40}$", profile_source_commit):
465+
mx.abort(f"GRAALPY_PGO_PROFILE_SOURCE_COMMIT must be a 40-character lowercase git commit, got: {override}")
466+
profile_source_reason = "GRAALPY_PGO_PROFILE_SOURCE_COMMIT"
467+
elif (
468+
target_branch
469+
and source_branch
470+
and source_branch != target_branch
471+
and not (source_branch == MAIN_BRANCH or source_branch.startswith(("release/", "cpu/")))
472+
):
473+
# Feature branch merge commits can import a fresh GraalPy revision that
474+
# has no released-product profile yet. Use the target branch's imported
475+
# GraalPy commit so product-ee consumes the profile that already belongs
476+
# to the baseline product launcher.
477+
target_import_commit = _graalpython_target_import_commit(target_branch)
478+
if target_import_commit:
479+
profile_source_commit = target_import_commit
480+
profile_source_branch = target_branch
481+
profile_source_reason = f"target branch import from {target_branch}"
482+
elif require_profile:
483+
mx.abort("\n".join([
484+
"Could not resolve the GraalPy PGO profile source commit from the target branch import.",
485+
f"GraalPy source commit: {source_commit}",
486+
f"Source branch: {source_branch or '<unknown>'}",
487+
f"Target branch: {target_branch or '<unknown>'}",
488+
"Expected to read vm/mx.vm/suite.py from the target branch and find the graalpython import version.",
489+
"Set GRAALPY_PGO_PROFILE_SOURCE_COMMIT=<40-character-commit> to override this lookup for debugging.",
490+
]))
491+
else:
492+
mx.warn("Falling back to the current GraalPy commit for PGO profile lookup because the target branch import could not be resolved")
493+
artifact_name = f"{GRAALPY_PGO_PROFILE_ARTIFACT_GROUP}/{GRAALPY_PGO_PROFILE_ARTIFACT_PREFIX}{profile_source_commit}"
494+
mx.log(f"GraalPy source commit for PGO profile lookup: {source_commit}")
495+
mx.log(f"GraalPy PGO profile source commit: {profile_source_commit} ({profile_source_reason})")
359496

360497
if script := os.environ.get("ARTIFACT_DOWNLOAD_SCRIPT"):
361498
# This is always available in the GraalPy CI
@@ -364,29 +501,50 @@ def libpythonvm_build_args():
364501
[
365502
sys.executable,
366503
script,
367-
f"graalpy/pgo-{commit}",
504+
artifact_name,
368505
profile,
369506
],
370507
nonZeroIsFatal=False,
371508
)
372-
else:
509+
elif not require_profile:
373510
# Locally, we try to get a reasonable profile
374511
get_profile = mx.command_function('python-get-latest-profile', fatalIfMissing=False)
375512
if get_profile:
376-
for b in [branch, "master"]:
513+
seen_branches = set()
514+
for b in [profile_source_branch, source_branch, MAIN_BRANCH]:
515+
b = _normalize_branch_name(b)
516+
if not b or b in seen_branches:
517+
continue
518+
seen_branches.add(b)
377519
if not profile:
378520
try:
379521
profile = get_profile(["--branch", b])
380522
except BaseException:
381523
pass
382524

383-
if CI and (not profile or not os.path.isfile(profile)):
525+
profile_missing = not profile or not os.path.isfile(profile)
526+
if require_profile and profile_missing:
527+
mx.abort("\n".join([
528+
"GRAALPY_REQUIRE_PGO_PROFILE is set, but no CI generated GraalPy PGO profile was found.",
529+
f"GraalPy source commit: {source_commit}",
530+
f"Source branch: {source_branch or '<unknown>'}",
531+
f"Target branch: {target_branch or '<unset>'}",
532+
f"PGO profile source commit: {profile_source_commit} ({profile_source_reason})",
533+
f"Expected artifact: {artifact_name}",
534+
"The product profile configuration does not fall back to benchmark-local PGO.",
535+
"Run the GraalPy CI job python-pgo-profile-post_merge-linux-amd64-jdk-latest for the PGO profile source commit, then retry the product-ee benchmark.",
536+
]))
537+
538+
if CI and profile_missing:
384539
mx.log("No profile in CI job")
385540
# When running on a release branch or attempting to merge into
386-
# a release branch, make sure we can use a PGO profile, and
387-
# when running in the CI on a bench runner, ensure a PGO profile
541+
# a release/CPU branch, make sure we can use a PGO profile, and
542+
# when running in the CI on a bench runner, ensure a PGO profile.
388543
if (
389-
any(b.startswith("release/") for b in [branch, os.environ.get("TO_BRANCH", "")])
544+
any(
545+
_normalize_branch_name(b).startswith(("release/", "cpu/"))
546+
for b in [source_branch, target_branch]
547+
)
390548
or ("bench" in os.environ.get('BUILD_NAME', ''))
391549
):
392550
mx.warn("PGO profile must exist for benchmarking and release, creating one now...")
@@ -505,8 +663,8 @@ def graalpy_native_pgo_build_and_test(args=None):
505663
sys.executable,
506664
script,
507665
iprof_gz_path,
508-
f"pgo-{commit}",
509-
"graalpy",
666+
f"{GRAALPY_PGO_PROFILE_ARTIFACT_PREFIX}{commit}",
667+
GRAALPY_PGO_PROFILE_ARTIFACT_GROUP,
510668
"--lifecycle",
511669
"cache",
512670
"--artifact-repo-key",

0 commit comments

Comments
 (0)