2828import datetime
2929import fnmatch
3030import glob
31+ import gzip
3132import itertools
3233import os
3334import pathlib
4142from pathlib import Path
4243from textwrap import dedent
4344
44- from typing import cast
45+ from typing import cast , Union
4546
4647import downstream_tests
4748import mx_graalpython_benchmark
4849import mx_urlrewrites
4950
50- if sys .version_info [0 ] < 3 :
51- raise RuntimeError ("The build scripts are no longer compatible with Python 2" )
52-
5351import tempfile
5452from argparse import ArgumentParser
5553from dataclasses import dataclass
@@ -274,9 +272,104 @@ def libpythonvm_build_args():
274272 build_args += bytecode_dsl_build_args ()
275273 if mx_sdk_vm_ng .is_nativeimage_ee () and mx .get_os () == 'linux' and 'NATIVE_IMAGE_AUXILIARY_ENGINE_CACHE' not in os .environ :
276274 build_args += ['--gc=G1' , '-H:-ProtectionKeys' ]
275+ if not os .environ .get ("GRAALPY_PGO_PROFILE" ) and mx .suite ('graalpython-enterprise' , fatalIfMissing = False ):
276+ cmd = mx .command_function ('python-get-latest-profile' , fatalIfMissing = False )
277+ if cmd :
278+ profile = None
279+ try :
280+ profile = cmd ([])
281+ except BaseException :
282+ pass
283+ if profile and os .path .exists (profile ):
284+ mx .log (f"Using PGO profile { profile } " )
285+ build_args += [
286+ f"--pgo={ profile } " ,
287+ "-H:+UnlockExperimentalVMOptions" ,
288+ "-H:+PGOPrintProfileQuality" ,
289+ "-H:-UnlockExperimentalVMOptions" ,
290+ ]
291+ else :
292+ mx .log (f"Not using any PGO profile" )
277293 return build_args
278294
279295
296+ def graalpy_native_pgo_build_and_test (_ ):
297+ """
298+ Builds a PGO-instrumented GraalPy native standalone, runs the unittests to generate a profile,
299+ then builds a PGO-optimized GraalPy native standalone with the collected profile.
300+ The profile file will be named 'default.iprof' in native image build directory.
301+ """
302+ with set_env (GRAALPY_PGO_PROFILE = "" ):
303+ mx .log (mx .colorize ("[PGO] Building PGO-instrumented native image" , color = "yellow" ))
304+ build_home = graalpy_standalone_home ('native' , enterprise = True , build = True )
305+ instrumented_home = build_home + "_PGO_INSTRUMENTED"
306+ shutil .rmtree (instrumented_home , ignore_errors = True )
307+ shutil .copytree (build_home , instrumented_home , symlinks = True , ignore_dangling_symlinks = True )
308+ instrumented_launcher = os .path .join (instrumented_home , 'bin' , _graalpy_launcher ())
309+
310+ mx .log (mx .colorize (f"[PGO] Instrumented build complete: { instrumented_home } " , color = "yellow" ))
311+
312+ mx .log (mx .colorize (f"[PGO] Running graalpytest with instrumented binary: { instrumented_launcher } " , color = "yellow" ))
313+ with tempfile .TemporaryDirectory () as d :
314+ with set_env (
315+ GRAALPYTEST_ALLOW_NO_JAVA_ASSERTIONS = "true" ,
316+ GRAAL_PYTHON_VM_ARGS = "\v " .join ([
317+ f"--vm.XX:ProfilesDumpFile={ os .path .join (d , '$UUID$.iprof' )} " ,
318+ f"--vm.XX:ProfilesLCOVFile={ os .path .join (d , '$UUID$.info' )} " ,
319+ ]),
320+ GRAALPY_HOME = instrumented_home ,
321+ ):
322+ graalpytest (["--python" , instrumented_launcher , "test_venv.py" ])
323+ mx .command_function ('benchmark' )(["meso-small:*" , "--" , "--python-vm" , "graalpython" , "--python-vm-config" , "custom" ])
324+
325+ iprof_path = Path (SUITE .dir ) / 'default.iprof'
326+ lcov_path = Path (SUITE .dir ) / 'default.lcov'
327+
328+ run ([
329+ os .path .join (
330+ graalvm_jdk (enterprise = True ),
331+ "bin" ,
332+ f"native-image-configure{ '.exe' if mx .is_windows () else '' } " ,
333+ ),
334+ "merge-pgo-profiles" ,
335+ f"--input-dir={ d } " ,
336+ f"--output-file={ iprof_path } "
337+ ])
338+ run ([
339+ "/usr/bin/env" ,
340+ "lcov" ,
341+ "-o" , str (lcov_path ),
342+ * itertools .chain .from_iterable ([
343+ ["-a" , f .absolute ().as_posix ()] for f in Path (d ).glob ("*.info" )
344+ ])
345+ ], nonZeroIsFatal = False )
346+ run ([
347+ "/usr/bin/env" ,
348+ "genhtml" ,
349+ "--source-directory" , str (Path (SUITE .dir ) / "com.oracle.graal.python" / "src" ),
350+ "--source-directory" , str (Path (SUITE .dir ) / "com.oracle.graal.python.pegparser" / "src" ),
351+ "--source-directory" , str (Path (SUITE .get_output_root ()) / "com.oracle.graal.python" / "src_gen" ),
352+ "--include" , "com/oracle/graal/python" ,
353+ "--keep-going" ,
354+ "-o" , "lcov_html" ,
355+ str (lcov_path ),
356+ ], nonZeroIsFatal = False )
357+
358+ if not os .path .isfile (iprof_path ):
359+ mx .abort (f"[PGO] Could not find profile file at expected location: { iprof_path } " )
360+
361+ with set_env (GRAALPY_PGO_PROFILE = str (iprof_path )):
362+ mx .log (mx .colorize ("[PGO] Building optimized native image with collected profile" , color = "yellow" ))
363+ native_bin = graalpy_standalone ('native' , enterprise = True , build = True )
364+
365+ mx .log (mx .colorize (f"[PGO] Optimized PGO build complete: { native_bin } " , color = "yellow" ))
366+
367+ iprof_gz_path = str (iprof_path ) + '.gz'
368+ with open (iprof_path , 'rb' ) as f_in , gzip .open (iprof_gz_path , 'wb' ) as f_out :
369+ shutil .copyfileobj (f_in , f_out )
370+ mx .log (mx .colorize (f"[PGO] Gzipped profile at: { iprof_gz_path } " , color = "yellow" ))
371+
372+
280373def full_python (args , env = None ):
281374 """Run python from standalone build (unless kwargs are given). Does not build GraalPython sources automatically."""
282375
@@ -666,6 +759,19 @@ def graalpy_standalone_home(standalone_type, enterprise=False, dev=False, build=
666759 if BUILD_NATIVE_IMAGE_WITH_ASSERTIONS :
667760 mx_args .append ("--extra-image-builder-argument=-ea" )
668761
762+ pgo_profile = os .environ .get ("GRAALPY_PGO_PROFILE" )
763+ if pgo_profile is not None :
764+ if not enterprise or standalone_type != "native" :
765+ mx .abort ("PGO is only supported on enterprise NI" )
766+ if pgo_profile :
767+ mx_args .append (f"--extra-image-builder-argument=--pgo={ pgo_profile } " )
768+ mx_args .append (f"--extra-image-builder-argument=-H:+UnlockExperimentalVMOptions" )
769+ mx_args .append (f"--extra-image-builder-argument=-H:+PGOPrintProfileQuality" )
770+ else :
771+ mx_args .append (f"--extra-image-builder-argument=--pgo-instrument" )
772+ mx_args .append (f"--extra-image-builder-argument=-H:+UnlockExperimentalVMOptions" )
773+ mx_args .append (f"--extra-image-builder-argument=-H:+ProfilingLCOV" )
774+
669775 if mx_gate .get_jacoco_agent_args () or (build and not DISABLE_REBUILD ):
670776 mx_build_args = mx_args
671777 if BYTECODE_DSL_INTERPRETER :
@@ -908,7 +1014,9 @@ def graalpytest(args):
9081014 python_binary = graalpy_standalone_native ()
9091015 elif 'graalpy' in os .path .basename (python_binary ) or 'mxbuild' in python_binary :
9101016 is_graalpy = True
911- gp_args = ["--vm.ea" , "--vm.esa" , "--experimental-options=true" , "--python.EnableDebuggingBuiltins" ]
1017+ gp_args = ["--experimental-options=true" , "--python.EnableDebuggingBuiltins" ]
1018+ if env .get ("GRAALPYTEST_ALLOW_NO_JAVA_ASSERTIONS" ) != "true" :
1019+ gp_args += ["--vm.ea" , "--vm.esa" ]
9121020 mx .log (f"Executable seems to be GraalPy, prepending arguments: { gp_args } " )
9131021 python_args += gp_args
9141022 if is_graalpy and BYTECODE_DSL_INTERPRETER :
@@ -963,7 +1071,7 @@ def is_included(path):
9631071
9641072def run_python_unittests (python_binary , args = None , paths = None , exclude = None , env = None ,
9651073 use_pytest = False , cwd = None , lock = None , out = None , err = None , nonZeroIsFatal = True , timeout = None ,
966- report = False , parallel = None , runner_args = None ):
1074+ report : Union [ Task , bool , None ] = False , parallel = None , runner_args = None ):
9671075 if lock :
9681076 lock .acquire ()
9691077
@@ -1039,7 +1147,7 @@ def run_python_unittests(python_binary, args=None, paths=None, exclude=None, env
10391147 return result
10401148
10411149
1042- def run_hpy_unittests (python_binary , args = None , env = None , nonZeroIsFatal = True , timeout = None , report = False ):
1150+ def run_hpy_unittests (python_binary , args = None , env = None , nonZeroIsFatal = True , timeout = None , report : Union [ Task , bool , None ] = False ):
10431151 t0 = time .time ()
10441152 result = downstream_tests .downstream_test_hpy (python_binary , args = args , env = env , check = nonZeroIsFatal , timeout = timeout )
10451153 if report :
@@ -1051,7 +1159,7 @@ def run_hpy_unittests(python_binary, args=None, env=None, nonZeroIsFatal=True, t
10511159
10521160
10531161def run_tagged_unittests (python_binary , env = None , cwd = None , nonZeroIsFatal = True , checkIfWithGraalPythonEE = False ,
1054- report = False , parallel = 8 , exclude = None , paths = ()):
1162+ report : Union [ Task , bool , None ] = False , parallel = 8 , exclude = None , paths = ()):
10551163
10561164 if checkIfWithGraalPythonEE :
10571165 mx .run ([python_binary , "-c" , "import sys; print(sys.version)" ])
@@ -1323,13 +1431,13 @@ def graalpython_gate_runner(args, tasks):
13231431 if task :
13241432 run_mx ([
13251433 "--dy" , "graalpython,/substratevm" ,
1326- "-p" , os .path .join (mx .suite ("truffle" ), ".." , "vm" ),
1434+ "-p" , os .path .join (mx .suite ("truffle" ). dir , ".." , "vm" ),
13271435 "--native-images=" ,
13281436 "build" ,
13291437 ], env = {** os .environ , ** LATEST_JAVA_HOME })
13301438 run_mx ([
13311439 "--dy" , "graalpython,/substratevm" ,
1332- "-p" , os .path .join (mx .suite ("truffle" ), ".." , "vm" ),
1440+ "-p" , os .path .join (mx .suite ("truffle" ). dir , ".." , "vm" ),
13331441 "--native-images=" ,
13341442 "gate" , "svm-truffle-tck-python" ,
13351443 ])
@@ -2554,4 +2662,5 @@ def run_downstream_test(args):
25542662 'graalpy-jmh' : [graalpy_jmh , '' ],
25552663 'deploy-local-maven-repo' : [deploy_local_maven_repo_wrapper , '' ],
25562664 'downstream-test' : [run_downstream_test , '' ],
2665+ 'python-native-pgo' : [graalpy_native_pgo_build_and_test , 'Build PGO-instrumented native image, run tests, then build PGO-optimized native image' ],
25572666})
0 commit comments