Skip to content

Commit e5c3665

Browse files
jdenny-ornlaaryanshukla
authored andcommitted
[LinkerWrapper] Extend with usual pass options (llvm#96704)
The goal of this patch is to enable utilizing LLVM plugin passes and remarks for GPU offload code at link time. Specifically, this patch extends clang-linker-wrapper's `--offload-opt` (and consequently `-mllvm`) to accept the various LLVM pass options that tools like opt usually accept. Those options include `--passes`, `--load-pass-plugin`, and various remarks options. Unlike many other LLVM options that are inherited from linked code by clang-linker-wrapper (e.g., `-pass-remarks` is already implemented in `llvm/lib/IR/DiagnosticHandler.cpp`), these options are implemented separately as needed by each tool (e.g., opt, llc). Fortunately, this patch is able to handle most of the implementation by passing the option values to `lto::Config`. For testing plugin support, this patch uses the simple `Bye` plugin from LLVM core, but that requires several small Clang test suite config extensions.
1 parent 5f69734 commit e5c3665

File tree

8 files changed

+183
-2
lines changed

8 files changed

+183
-2
lines changed

clang/test/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ llvm_canonicalize_cmake_booleans(
1111
CLANG_SPAWN_CC1
1212
CLANG_ENABLE_CIR
1313
ENABLE_BACKTRACES
14+
LLVM_BUILD_EXAMPLES
15+
LLVM_BYE_LINK_INTO_TOOLS
16+
LLVM_ENABLE_PLUGINS
1417
LLVM_ENABLE_ZLIB
1518
LLVM_ENABLE_ZSTD
1619
LLVM_ENABLE_PER_TARGET_RUNTIME_DIR
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Check that these simple command lines for listing LLVM options are supported,
2+
// as claimed by 'clang-linker-wrapper --help'.
3+
4+
// RUN: clang-linker-wrapper -mllvm --help 2>&1 | FileCheck %s
5+
// RUN: clang-linker-wrapper --offload-opt=--help 2>&1 | FileCheck %s
6+
7+
// Look for a few options supported only after -mllvm and --offload-opt.
8+
// CHECK: OPTIONS:
9+
// CHECK-DAG: --passes=<string>
10+
// CHECK-DAG: --load-pass-plugin=<string>
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Check various clang-linker-wrapper pass options after -offload-opt.
2+
3+
// REQUIRES: llvm-plugins, llvm-examples
4+
// REQUIRES: x86-registered-target
5+
// REQUIRES: amdgpu-registered-target
6+
7+
// Setup.
8+
// RUN: mkdir -p %t
9+
// RUN: %clang -cc1 -emit-llvm-bc -o %t/host-x86_64-unknown-linux-gnu.bc \
10+
// RUN: -disable-O0-optnone -triple=x86_64-unknown-linux-gnu %s
11+
// RUN: %clang -cc1 -emit-llvm-bc -o %t/openmp-amdgcn-amd-amdhsa.bc \
12+
// RUN: -disable-O0-optnone -triple=amdgcn-amd-amdhsa %s
13+
// RUN: opt %t/openmp-amdgcn-amd-amdhsa.bc -o %t/openmp-amdgcn-amd-amdhsa.bc \
14+
// RUN: -passes=forceattrs -force-remove-attribute=f:noinline
15+
// RUN: clang-offload-packager -o %t/openmp-x86_64-unknown-linux-gnu.out \
16+
// RUN: --image=file=%t/openmp-amdgcn-amd-amdhsa.bc,triple=amdgcn-amd-amdhsa
17+
// RUN: %clang -cc1 -S -o %t/host-x86_64-unknown-linux-gnu.s \
18+
// RUN: -fopenmp -fopenmp-targets=amdgcn-amd-amdhsa \
19+
// RUN: -fembed-offload-object=%t/openmp-x86_64-unknown-linux-gnu.out \
20+
// RUN: %t/host-x86_64-unknown-linux-gnu.bc
21+
// RUN: %clang -cc1as -o %t/host-x86_64-unknown-linux-gnu.o \
22+
// RUN: -triple x86_64-unknown-linux-gnu -filetype obj -target-cpu x86-64 \
23+
// RUN: %t/host-x86_64-unknown-linux-gnu.s
24+
25+
// Check plugin, -passes, and no remarks.
26+
// RUN: clang-linker-wrapper -o a.out --embed-bitcode \
27+
// RUN: --linker-path=/usr/bin/true %t/host-x86_64-unknown-linux-gnu.o \
28+
// RUN: %offload-opt-loadbye --offload-opt=-wave-goodbye \
29+
// RUN: --offload-opt=-passes="function(goodbye),module(inline)" 2>&1 | \
30+
// RUN: FileCheck -match-full-lines -check-prefixes=OUT %s
31+
32+
// Check plugin, -p, and remarks.
33+
// RUN: clang-linker-wrapper -o a.out --embed-bitcode \
34+
// RUN: --linker-path=/usr/bin/true %t/host-x86_64-unknown-linux-gnu.o \
35+
// RUN: %offload-opt-loadbye --offload-opt=-wave-goodbye \
36+
// RUN: --offload-opt=-p="function(goodbye),module(inline)" \
37+
// RUN: --offload-opt=-pass-remarks=inline \
38+
// RUN: --offload-opt=-pass-remarks-output=%t/remarks.yml \
39+
// RUN: --offload-opt=-pass-remarks-filter=inline \
40+
// RUN: --offload-opt=-pass-remarks-format=yaml 2>&1 | \
41+
// RUN: FileCheck -match-full-lines -check-prefixes=OUT,REM %s
42+
// RUN: FileCheck -input-file=%t/remarks.yml -match-full-lines \
43+
// RUN: -check-prefixes=YML %s
44+
45+
// Check handling of bad plugin.
46+
// RUN: not clang-linker-wrapper \
47+
// RUN: --offload-opt=-load-pass-plugin=%t/nonexistent.so 2>&1 | \
48+
// RUN: FileCheck -match-full-lines -check-prefixes=BAD-PLUGIN %s
49+
50+
// OUT-NOT: {{.}}
51+
// OUT: Bye: f
52+
// OUT-NEXT: Bye: test
53+
// REM-NEXT: remark: {{.*}} 'f' inlined into 'test' {{.*}}
54+
// OUT-NOT: {{.}}
55+
56+
// YML-NOT: {{.}}
57+
// YML: --- !Passed
58+
// YML-NEXT: Pass: inline
59+
// YML-NEXT: Name: Inlined
60+
// YML-NEXT: Function: test
61+
// YML-NEXT: Args:
62+
// YML: - Callee: f
63+
// YML: - Caller: test
64+
// YML: ...
65+
// YML-NOT: {{.}}
66+
67+
// BAD-PLUGIN-NOT: {{.}}
68+
// BAD-PLUGIN: {{.*}}Could not load library {{.*}}nonexistent.so{{.*}}
69+
// BAD-PLUGIN-NOT: {{.}}
70+
71+
void f() {}
72+
void test() { f(); }

clang/test/lit.cfg.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,15 @@
109109
if config.clang_examples:
110110
config.available_features.add("examples")
111111

112+
if config.llvm_examples:
113+
config.available_features.add("llvm-examples")
114+
115+
if config.llvm_linked_bye_extension:
116+
config.substitutions.append(("%offload-opt-loadbye", ""))
117+
else:
118+
loadbye = f"-load-pass-plugin={config.llvm_shlib_dir}/Bye{config.llvm_shlib_ext}"
119+
config.substitutions.append(("%offload-opt-loadbye", f"--offload-opt={loadbye}"))
120+
112121

113122
def have_host_jit_feature_support(feature_name):
114123
clang_repl_exe = lit.util.which("clang-repl", config.clang_tools_dir)
@@ -213,6 +222,9 @@ def have_host_clang_repl_cuda():
213222
if config.has_plugins and config.llvm_plugin_ext:
214223
config.available_features.add("plugins")
215224

225+
if config.llvm_has_plugins and config.llvm_plugin_ext:
226+
config.available_features.add("llvm-plugins")
227+
216228
if config.clang_default_pie_on_linux:
217229
config.available_features.add("default-pie-on-linux")
218230

clang/test/lit.site.cfg.py.in

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ config.llvm_obj_root = path(r"@LLVM_BINARY_DIR@")
77
config.llvm_tools_dir = lit_config.substitute(path(r"@LLVM_TOOLS_DIR@"))
88
config.llvm_libs_dir = lit_config.substitute(path(r"@LLVM_LIBS_DIR@"))
99
config.llvm_shlib_dir = lit_config.substitute(path(r"@SHLIBDIR@"))
10+
config.llvm_shlib_ext = "@SHLIBEXT@"
1011
config.llvm_plugin_ext = "@LLVM_PLUGIN_EXT@"
1112
config.lit_tools_dir = path(r"@LLVM_LIT_TOOLS_DIR@")
1213
config.errc_messages = "@LLVM_LIT_ERRC_MESSAGES@"
@@ -39,7 +40,10 @@ config.python_executable = "@Python3_EXECUTABLE@"
3940
config.use_z3_solver = lit_config.params.get('USE_Z3_SOLVER', "@USE_Z3_SOLVER@")
4041
config.has_plugins = @CLANG_PLUGIN_SUPPORT@
4142
config.clang_vendor_uti = "@CLANG_VENDOR_UTI@"
43+
config.llvm_examples = @LLVM_BUILD_EXAMPLES@
44+
config.llvm_linked_bye_extension = @LLVM_BYE_LINK_INTO_TOOLS@
4245
config.llvm_external_lit = path(r"@LLVM_EXTERNAL_LIT@")
46+
config.llvm_has_plugins = @LLVM_ENABLE_PLUGINS@
4347
config.standalone_build = @CLANG_BUILT_STANDALONE@
4448
config.ppc_linux_default_ieeelongdouble = @PPC_LINUX_DEFAULT_IEEELONGDOUBLE@
4549
config.have_llvm_driver = @LLVM_TOOL_LLVM_DRIVER_BUILD@

clang/tools/clang-linker-wrapper/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,5 @@ target_link_libraries(clang-linker-wrapper
4141
PRIVATE
4242
${CLANG_LINKER_WRAPPER_LIB_DEPS}
4343
)
44+
45+
export_executable_symbols_for_plugins(clang-linker-wrapper)

clang/tools/clang-linker-wrapper/ClangLinkerWrapper.cpp

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
#include "llvm/Option/ArgList.h"
3838
#include "llvm/Option/OptTable.h"
3939
#include "llvm/Option/Option.h"
40+
#include "llvm/Passes/PassPlugin.h"
41+
#include "llvm/Remarks/HotnessThresholdParser.h"
4042
#include "llvm/Support/CommandLine.h"
4143
#include "llvm/Support/Errc.h"
4244
#include "llvm/Support/FileOutputBuffer.h"
@@ -62,6 +64,54 @@ using namespace llvm;
6264
using namespace llvm::opt;
6365
using namespace llvm::object;
6466

67+
// Various tools (e.g., llc and opt) duplicate this series of declarations for
68+
// options related to passes and remarks.
69+
70+
static cl::opt<bool> RemarksWithHotness(
71+
"pass-remarks-with-hotness",
72+
cl::desc("With PGO, include profile count in optimization remarks"),
73+
cl::Hidden);
74+
75+
static cl::opt<std::optional<uint64_t>, false, remarks::HotnessThresholdParser>
76+
RemarksHotnessThreshold(
77+
"pass-remarks-hotness-threshold",
78+
cl::desc("Minimum profile count required for "
79+
"an optimization remark to be output. "
80+
"Use 'auto' to apply the threshold from profile summary."),
81+
cl::value_desc("N or 'auto'"), cl::init(0), cl::Hidden);
82+
83+
static cl::opt<std::string>
84+
RemarksFilename("pass-remarks-output",
85+
cl::desc("Output filename for pass remarks"),
86+
cl::value_desc("filename"));
87+
88+
static cl::opt<std::string>
89+
RemarksPasses("pass-remarks-filter",
90+
cl::desc("Only record optimization remarks from passes whose "
91+
"names match the given regular expression"),
92+
cl::value_desc("regex"));
93+
94+
static cl::opt<std::string> RemarksFormat(
95+
"pass-remarks-format",
96+
cl::desc("The format used for serializing remarks (default: YAML)"),
97+
cl::value_desc("format"), cl::init("yaml"));
98+
99+
static cl::list<std::string>
100+
PassPlugins("load-pass-plugin",
101+
cl::desc("Load passes from plugin library"));
102+
103+
static cl::opt<std::string> PassPipeline(
104+
"passes",
105+
cl::desc(
106+
"A textual description of the pass pipeline. To have analysis passes "
107+
"available before a certain pass, add 'require<foo-analysis>'. "
108+
"'-passes' overrides the pass pipeline (but not all effects) from "
109+
"specifying '--opt-level=O?' (O2 is the default) to "
110+
"clang-linker-wrapper. Be sure to include the corresponding "
111+
"'default<O?>' in '-passes'."));
112+
static cl::alias PassPipeline2("p", cl::aliasopt(PassPipeline),
113+
cl::desc("Alias for -passes"));
114+
65115
/// Path of the current binary.
66116
static const char *LinkerExecutable;
67117

@@ -628,6 +678,12 @@ std::unique_ptr<lto::LTO> createLTO(
628678
Conf.CPU = Arch.str();
629679
Conf.Options = codegen::InitTargetOptionsFromCodeGenFlags(Triple);
630680

681+
Conf.RemarksFilename = RemarksFilename;
682+
Conf.RemarksPasses = RemarksPasses;
683+
Conf.RemarksWithHotness = RemarksWithHotness;
684+
Conf.RemarksHotnessThreshold = RemarksHotnessThreshold;
685+
Conf.RemarksFormat = RemarksFormat;
686+
631687
StringRef OptLevel = Args.getLastArgValue(OPT_opt_level, "O2");
632688
Conf.MAttrs = Features;
633689
std::optional<CodeGenOptLevel> CGOptLevelOrNone =
@@ -637,6 +693,17 @@ std::unique_ptr<lto::LTO> createLTO(
637693
Conf.OptLevel = OptLevel[1] - '0';
638694
Conf.DefaultTriple = Triple.getTriple();
639695

696+
// TODO: Should we complain about combining --opt-level and -passes, as opt
697+
// does? That might be too limiting in clang-linker-wrapper, so for now we
698+
// just warn in the help entry for -passes that the default<O?> corresponding
699+
// to --opt-level=O? should be included there. The problem is that
700+
// --opt-level produces effects in clang-linker-wrapper beyond what -passes
701+
// appears to be able to achieve, so rejecting the combination of --opt-level
702+
// and -passes would apparently make it impossible to combine those effects
703+
// with a custom pass pipeline.
704+
Conf.OptPipeline = PassPipeline;
705+
Conf.PassPlugins = PassPlugins;
706+
640707
LTOError = false;
641708
Conf.DiagHandler = diagnosticHandler;
642709

@@ -1660,6 +1727,13 @@ int main(int Argc, char **Argv) {
16601727
NewArgv.push_back(Arg->getValue());
16611728
for (const opt::Arg *Arg : Args.filtered(OPT_offload_opt_eq_minus))
16621729
NewArgv.push_back(Args.MakeArgString(StringRef("-") + Arg->getValue()));
1730+
SmallVector<PassPlugin, 1> PluginList;
1731+
PassPlugins.setCallback([&](const std::string &PluginPath) {
1732+
auto Plugin = PassPlugin::Load(PluginPath);
1733+
if (!Plugin)
1734+
report_fatal_error(Plugin.takeError(), /*gen_crash_diag=*/false);
1735+
PluginList.emplace_back(Plugin.get());
1736+
});
16631737
cl::ParseCommandLineOptions(NewArgv.size(), &NewArgv[0]);
16641738

16651739
Verbose = Args.hasArg(OPT_verbose);

clang/tools/clang-linker-wrapper/LinkerWrapperOpts.td

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,13 @@ def linker_arg_EQ : Joined<["--"], "linker-arg=">,
9494

9595
// Arguments for the LLVM backend.
9696
def mllvm : Separate<["-"], "mllvm">, Flags<[WrapperOnlyOption]>,
97-
MetaVarName<"<arg>">, HelpText<"Arguments passed to the LLVM invocation">;
97+
MetaVarName<"<arg>">,
98+
HelpText<"Arguments passed to LLVM, including Clang invocations, for which "
99+
"the '-mllvm' prefix is preserved. Use '-mllvm --help' for a list "
100+
"of options.">;
98101
def offload_opt_eq_minus : Joined<["--", "-"], "offload-opt=-">, Flags<[HelpHidden, WrapperOnlyOption]>,
99-
HelpText<"Options passed to LLVM">;
102+
HelpText<"Options passed to LLVM, not including the Clang invocation. Use "
103+
"'--offload-opt=--help' for a list of options.">;
100104

101105
// Standard linker flags also used by the linker wrapper.
102106
def sysroot_EQ : Joined<["--"], "sysroot=">, HelpText<"Set the system root">;

0 commit comments

Comments
 (0)