diff options
author | Victor "Nate" Graf <nategraf1@gmail.com> | 2017-08-08 11:14:42 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-08-08 11:14:42 -0700 |
commit | 488d562052e61dde32ff0593835acc2713e9a0d1 (patch) | |
tree | a7214b175fcadc9826bfb7bb672d47dbe5205d73 | |
parent | d0e76ed19b8578caf746b3a4faa324c521d6420e (diff) | |
download | coreclr-488d562052e61dde32ff0593835acc2713e9a0d1.tar.gz coreclr-488d562052e61dde32ff0593835acc2713e9a0d1.tar.bz2 coreclr-488d562052e61dde32ff0593835acc2713e9a0d1.zip |
Add build-time check to enforce PGO compilation for applicable DLL files (#13258)
* Added PGO execution verification script
Added a script which can help verify whether or not PGO was used
to compile PE files passed to it
* Added target to enforce PGO compilation
* Remove OS checking for enforcepgo
-rw-r--r-- | build.cmd | 15 | ||||
-rw-r--r-- | src/build.proj | 14 | ||||
-rw-r--r-- | src/scripts/pgocheck.py | 77 |
3 files changed, 106 insertions, 0 deletions
@@ -80,6 +80,7 @@ set __BuildStandaloneGCOnly="-DFEATURE_STANDALONE_GC_ONLY=0" set __PgoInstrument=0 set __PgoOptimize=1 +set __EnforcePgo=0 set __IbcTuning= REM __PassThroughArgs is a set of things that will be passed through to nested calls to build.cmd @@ -140,6 +141,7 @@ if /i "%1" == "skiprestoreoptdata" (set __RestoreOptData=0&set processedArgs=!p if /i "%1" == "usenmakemakefiles" (set __NMakeMakefiles=1&set __ConfigureOnly=1&set __BuildNative=1&set __BuildNativeCoreLib=0&set __BuildCoreLib=0&set __BuildTests=0&set __BuildPackages=0&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) if /i "%1" == "pgoinstrument" (set __PgoInstrument=1&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) if /i "%1" == "nopgooptimize" (set __PgoOptimize=0&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) +if /i "%1" == "enforcepgo" (set __EnforcePgo=1&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) if /i "%1" == "ibcinstrument" (set __IbcTuning=/Tuning&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) if /i "%1" == "toolset_dir" (set __ToolsetDir=%2&set __PassThroughArgs=%__PassThroughArgs% %2&set processedArgs=!processedArgs! %1 %2&shift&shift&goto Arg_Loop) if /i "%1" == "buildstandalonegc" ( @@ -193,6 +195,17 @@ if %__BuildTypeRelease%==1 set __BuildType=Release set __RunArgs=-BuildOS=%__BuildOS% -BuildType=%__BuildType% -BuildArch=%__BuildArch% +if %__EnforcePgo%==1 ( + if %__BuildArchArm%==1 ( + echo Error: enforcepgo cannot be used with arm architecture + goto Usage + ) + if %__BuildArchArm64%==1 ( + echo Error: enforcepgo cannot be used with arm64 architecture + goto Usage + ) +) + :: Set the remaining variables based upon the determined build configuration set "__BinDir=%__RootBinDir%\Product\%__BuildOS%.%__BuildArch%.%__BuildType%" set "__IntermediatesDir=%__RootBinDir%\obj\%__BuildOS%.%__BuildArch%.%__BuildType%" @@ -204,6 +217,7 @@ set "__TestIntermediatesDir=%__RootBinDir%\tests\obj\%__BuildOS%.%__BuildArch%.% set "__CrossComponentBinDir=%__BinDir%" set "__CrossCompIntermediatesDir=%__IntermediatesDir%\crossgen" + if NOT "%__CrossArch%" == "" set __CrossComponentBinDir=%__CrossComponentBinDir%\%__CrossArch% set "__CrossGenCoreLibLog=%__LogsDir%\CrossgenCoreLib_%__BuildOS%__%__BuildArch%__%__BuildType%.log" set "__CrossgenExe=%__CrossComponentBinDir%\crossgen.exe" @@ -626,6 +640,7 @@ echo respectively^). echo add nativemscorlib to go further and build the native image for designated mscorlib. echo toolset_dir ^<dir^> : set the toolset directory -- Arm64 use only. Required for Arm64 builds. echo nopgooptimize: do not use profile guided optimizations. +echo enforcepgo: verify after the build that PGO was used for key DLLs, and fail the build if not echo pgoinstrument: generate instrumented code for profile guided optimization enabled binaries. echo ibcinstrument: generate IBC-tuning-enabled native images when invoking crossgen. echo configureonly: skip all builds; only run CMake ^(default: CMake and builds are run^) diff --git a/src/build.proj b/src/build.proj index 7962d27e99..b59b00c00f 100644 --- a/src/build.proj +++ b/src/build.proj @@ -38,4 +38,18 @@ DestinationFolder="$(BinDir)PDB" /> </Target> + <PropertyGroup> + <RunEnforcePGO Condition="$(__EnforcePgo) == '1'">true</RunEnforcePGO> + <RunEnforcePGO Condition="$(__BuildArch) == 'arm' OR $(__BuildArch) == 'arm64'">false</RunEnforcePGO> + </PropertyGroup> + + <Target Name="EnforcePGO" Condition="$(RunEnforcePGO) == 'true'" AfterTargets="Build"> + <ItemGroup> + <PGOEnforcedFiles Include="$(BinDir)coreclr.dll" /> + <PGOEnforcedFiles Include="$(BinDir)clrjit.dll" /> + </ItemGroup> + + <Message Text="Checking if the following DLLs are properly compiled with PGO" Importance="High" /> + <Exec Command="python $(MSBuildThisFileDirectory)scripts\pgocheck.py @(PGOEnforcedFiles)" /> + </Target> </Project> diff --git a/src/scripts/pgocheck.py b/src/scripts/pgocheck.py new file mode 100644 index 0000000000..d408e6eaba --- /dev/null +++ b/src/scripts/pgocheck.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python +# +## Licensed to the .NET Foundation under one or more agreements. +## The .NET Foundation licenses this file to you under the MIT license. +## See the LICENSE file in the project root for more information. +# +## +# Title :pgocheck.py +# +# A script to check whether or not a particular portable executable +# (e.g. EXE, DLL) was compiled using PGO technology +# +################################################################################ + +from glob import glob +import sys +import re +import subprocess +import argparse + +# This pattern matches the line which specifies if PGO, LTCG, or similar techologies were used for compilation +# coffgrp matches the literal string. It uniquely identifies within the field in question +# (?:\s+[0-9A-F]+){4} matches 4 hex valued fields without capturing them +# \((\S*)\) captures the text identifier from the dump output, letting us know the technology +pgo_pattern_str = r'coffgrp(?:\s+[0-9A-F]+){4}\s+\((\S*)\)' +pgo_pattern = re.compile(pgo_pattern_str) + +def was_compiled_with_pgo(filename): + headers = subprocess.check_output(["link", "/dump", "/headers", filename]) + + match = pgo_pattern.search(headers) + + result = False + tech = "UNKNOWN" + if match: + result = match.group(1) == 'PGU' + tech = match.group(1) + + return result, tech + +if __name__ == "__main__": + from sys import stdout, stderr + + parser = argparse.ArgumentParser(description="Check if the given PE files were compiled with PGO. Fails if the files were not.") + parser.add_argument('files', metavar='file', nargs='+', help="the files to check for PGO flags") + parser.add_argument('--negative', action='store_true', help="fail on PGO flags found") + parser.add_argument('--quiet', action='store_true', help="don't output; just return a code") + + args = parser.parse_args() + # Divide up filenames which are separated by semicolons as well as the ones by spaces. Avoid duplicates + filenames = set() + for token in args.files: + unexpanded_filenames = token.split(';') + # Provide support for Unix-style filename expansion (i.e. with * and ?) + for unexpanded_filename in unexpanded_filenames: + expanded_filenames = glob(unexpanded_filename) + if unexpanded_filename and not expanded_filenames: + stderr.write("ERROR: Could not find file(s) {0}\n".format(unexpanded_filename)) + exit(2) + filenames.update(expanded_filenames) + + success = True + for filename in filenames: + result, tech = was_compiled_with_pgo(filename) + success = success and result + + if not args.quiet: + status = "compiled with PGO" if result else "NOT compiled with PGO" + sys.stdout.write("{0}: {1} ({2})\n".format(filename, status, tech)) + + if not success: + if not args.quiet: + if not args.negative: + stderr.write("ERROR: The files listed above must be compiled with PGO\n") + else: + stderr.write("ERROR: The files listed above must NOT be compiled with PGO\n") + exit(1)
\ No newline at end of file |