28
28
import datetime
29
29
import fnmatch
30
30
import glob
31
+ import gzip
31
32
import itertools
32
33
import os
33
34
import pathlib
41
42
from pathlib import Path
42
43
from textwrap import dedent
43
44
44
- from typing import cast
45
+ from typing import cast , Union
45
46
46
47
import downstream_tests
47
48
import mx_graalpython_benchmark
48
49
import mx_urlrewrites
49
50
50
- if sys .version_info [0 ] < 3 :
51
- raise RuntimeError ("The build scripts are no longer compatible with Python 2" )
52
-
53
51
import tempfile
54
52
from argparse import ArgumentParser
55
53
from dataclasses import dataclass
@@ -274,9 +272,104 @@ def libpythonvm_build_args():
274
272
build_args += bytecode_dsl_build_args ()
275
273
if mx_sdk_vm_ng .is_nativeimage_ee () and mx .get_os () == 'linux' and 'NATIVE_IMAGE_AUXILIARY_ENGINE_CACHE' not in os .environ :
276
274
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" )
277
293
return build_args
278
294
279
295
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
+
280
373
def full_python (args , env = None ):
281
374
"""Run python from standalone build (unless kwargs are given). Does not build GraalPython sources automatically."""
282
375
@@ -666,6 +759,19 @@ def graalpy_standalone_home(standalone_type, enterprise=False, dev=False, build=
666
759
if BUILD_NATIVE_IMAGE_WITH_ASSERTIONS :
667
760
mx_args .append ("--extra-image-builder-argument=-ea" )
668
761
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
+
669
775
if mx_gate .get_jacoco_agent_args () or (build and not DISABLE_REBUILD ):
670
776
mx_build_args = mx_args
671
777
if BYTECODE_DSL_INTERPRETER :
@@ -908,7 +1014,9 @@ def graalpytest(args):
908
1014
python_binary = graalpy_standalone_native ()
909
1015
elif 'graalpy' in os .path .basename (python_binary ) or 'mxbuild' in python_binary :
910
1016
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" ]
912
1020
mx .log (f"Executable seems to be GraalPy, prepending arguments: { gp_args } " )
913
1021
python_args += gp_args
914
1022
if is_graalpy and BYTECODE_DSL_INTERPRETER :
@@ -963,7 +1071,7 @@ def is_included(path):
963
1071
964
1072
def run_python_unittests (python_binary , args = None , paths = None , exclude = None , env = None ,
965
1073
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 ):
967
1075
if lock :
968
1076
lock .acquire ()
969
1077
@@ -1039,7 +1147,7 @@ def run_python_unittests(python_binary, args=None, paths=None, exclude=None, env
1039
1147
return result
1040
1148
1041
1149
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 ):
1043
1151
t0 = time .time ()
1044
1152
result = downstream_tests .downstream_test_hpy (python_binary , args = args , env = env , check = nonZeroIsFatal , timeout = timeout )
1045
1153
if report :
@@ -1051,7 +1159,7 @@ def run_hpy_unittests(python_binary, args=None, env=None, nonZeroIsFatal=True, t
1051
1159
1052
1160
1053
1161
def 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 = ()):
1055
1163
1056
1164
if checkIfWithGraalPythonEE :
1057
1165
mx .run ([python_binary , "-c" , "import sys; print(sys.version)" ])
@@ -1323,13 +1431,13 @@ def graalpython_gate_runner(args, tasks):
1323
1431
if task :
1324
1432
run_mx ([
1325
1433
"--dy" , "graalpython,/substratevm" ,
1326
- "-p" , os .path .join (mx .suite ("truffle" ), ".." , "vm" ),
1434
+ "-p" , os .path .join (mx .suite ("truffle" ). dir , ".." , "vm" ),
1327
1435
"--native-images=" ,
1328
1436
"build" ,
1329
1437
], env = {** os .environ , ** LATEST_JAVA_HOME })
1330
1438
run_mx ([
1331
1439
"--dy" , "graalpython,/substratevm" ,
1332
- "-p" , os .path .join (mx .suite ("truffle" ), ".." , "vm" ),
1440
+ "-p" , os .path .join (mx .suite ("truffle" ). dir , ".." , "vm" ),
1333
1441
"--native-images=" ,
1334
1442
"gate" , "svm-truffle-tck-python" ,
1335
1443
])
@@ -2554,4 +2662,5 @@ def run_downstream_test(args):
2554
2662
'graalpy-jmh' : [graalpy_jmh , '' ],
2555
2663
'deploy-local-maven-repo' : [deploy_local_maven_repo_wrapper , '' ],
2556
2664
'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' ],
2557
2666
})
0 commit comments