[lld][COFF] Add optimization remarks options to lld-link (#205390) Add support for emitting optimization remarks during LTO in the COFF linker, matching the existing ELF linker functionality. The following options are added: -opt-remarks-filename -opt-remarks-passes -opt-remarks-format -opt-remarks-with-hotness -opt-remarks-hotness-threshold These options are forwarded to the LTO backend via lto::Config and allow users to capture optimization decisions (e.g., inlining) as structured YAML output, optionally filtered by pass name or profile hotness threshold. Assisted by Claude. GitOrigin-RevId: a037d2ad7312adc58664358893c20927efae22cf
diff --git a/COFF/Config.h b/COFF/Config.h index f22d580..7546da2 100644 --- a/COFF/Config.h +++ b/COFF/Config.h
@@ -21,6 +21,7 @@ #include "llvm/Support/VirtualFileSystem.h" #include <cstdint> #include <map> +#include <optional> #include <string> namespace lld::coff { @@ -357,6 +358,11 @@ EmitKind emit = EmitKind::Obj; bool allowDuplicateWeak = false; BuildIDHash buildIDHash = BuildIDHash::None; + llvm::StringRef optRemarksFilename; + llvm::StringRef optRemarksPasses; + llvm::StringRef optRemarksFormat; + bool optRemarksWithHotness = false; + std::optional<uint64_t> optRemarksHotnessThreshold = 0; }; struct COFFSyncStream : SyncStream {
diff --git a/COFF/Driver.cpp b/COFF/Driver.cpp index 6583e43..ecbb5e9 100644 --- a/COFF/Driver.cpp +++ b/COFF/Driver.cpp
@@ -33,6 +33,7 @@ #include "llvm/Option/Arg.h" #include "llvm/Option/ArgList.h" #include "llvm/Option/Option.h" +#include "llvm/Remarks/HotnessThresholdParser.h" #include "llvm/Support/BinaryStreamReader.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Debug.h" @@ -2327,6 +2328,19 @@ if (args.hasFlag(OPT_prefetch_inputs, OPT_prefetch_inputs_no, false)) config->prefetchInputs = true; + config->optRemarksFilename = args.getLastArgValue(OPT_opt_remarks_filename); + config->optRemarksPasses = args.getLastArgValue(OPT_opt_remarks_passes); + config->optRemarksFormat = args.getLastArgValue(OPT_opt_remarks_format); + config->optRemarksWithHotness = args.hasArg(OPT_opt_remarks_with_hotness); + if (auto *arg = args.getLastArg(OPT_opt_remarks_hotness_threshold)) { + auto resultOrErr = remarks::parseHotnessThresholdOption(arg->getValue()); + if (!resultOrErr) + Err(ctx) << arg->getSpelling() << ": invalid argument '" + << arg->getValue() << "', only integer or 'auto' is supported"; + else + config->optRemarksHotnessThreshold = *resultOrErr; + } + if (errCount(ctx)) return;
diff --git a/COFF/LTO.cpp b/COFF/LTO.cpp index 71413b0..f85a260 100644 --- a/COFF/LTO.cpp +++ b/COFF/LTO.cpp
@@ -95,6 +95,11 @@ c.SampleProfile = ctx.config.ltoSampleProfileName; c.TimeTraceEnabled = ctx.config.timeTraceEnabled; c.TimeTraceGranularity = ctx.config.timeTraceGranularity; + c.RemarksFilename = ctx.config.optRemarksFilename; + c.RemarksPasses = ctx.config.optRemarksPasses; + c.RemarksWithHotness = ctx.config.optRemarksWithHotness; + c.RemarksHotnessThreshold = ctx.config.optRemarksHotnessThreshold; + c.RemarksFormat = ctx.config.optRemarksFormat; if (ctx.config.emit == EmitKind::LLVM) { c.PreCodeGenModuleHook = [this](size_t task, const Module &m) {
diff --git a/COFF/Options.td b/COFF/Options.td index bc1902d..eb05b2f 100644 --- a/COFF/Options.td +++ b/COFF/Options.td
@@ -360,6 +360,18 @@ "possible, to improve link times", "Do not prefetch input files (default)">; +def opt_remarks_filename: P<"opt-remarks-filename", + "YAML output file for optimization remarks">; +def opt_remarks_passes: P<"opt-remarks-passes", + "Regex for the passes that need to be serialized to the output file">; +def opt_remarks_format: P<"opt-remarks-format", + "The format used for serializing remarks (default: YAML)">; +def opt_remarks_with_hotness: F<"opt-remarks-with-hotness">, + HelpText<"Include hotness information in the optimization remarks file">; +def opt_remarks_hotness_threshold: P<"opt-remarks-hotness-threshold", + "Minimum profile count required for an optimization remark to be output. " + "Use 'auto' to apply the threshold from profile summary.">; + // Flags for debugging def lldmap : F<"lldmap">; def lldmap_file : P_priv<"lldmap">;
diff --git a/test/COFF/lto-opt-remarks.ll b/test/COFF/lto-opt-remarks.ll new file mode 100644 index 0000000..c70e180 --- /dev/null +++ b/test/COFF/lto-opt-remarks.ll
@@ -0,0 +1,87 @@ +; REQUIRES: x86 +; RUN: llvm-as %s -o %t.obj + +; RUN: rm -f %t.yaml %t.pass.yaml %t.hot.yaml %t.t300.yaml %t.t301.yaml +; RUN: lld-link -opt-remarks-filename:%t.yaml %t.obj -entry:main -nodefaultlib \ +; RUN: -out:%t.exe -force:unresolved +; RUN: cat %t.yaml | FileCheck %s -check-prefix=YAML + +; RUN: lld-link -opt-remarks-filename:%t.pass.yaml -opt-remarks-passes:inline \ +; RUN: %t.obj -entry:main -nodefaultlib -out:%t.exe -force:unresolved +; RUN: cat %t.pass.yaml | FileCheck %s -check-prefix=YAML-PASSES + +; RUN: lld-link -opt-remarks-with-hotness -opt-remarks-filename:%t.hot.yaml \ +; RUN: %t.obj -entry:main -nodefaultlib -out:%t.exe -force:unresolved +; RUN: cat %t.hot.yaml | FileCheck %s -check-prefix=YAML-HOT + +; RUN: lld-link -opt-remarks-with-hotness \ +; RUN: -opt-remarks-hotness-threshold:300 \ +; RUN: -opt-remarks-filename:%t.t300.yaml %t.obj -entry:main -nodefaultlib \ +; RUN: -out:%t.exe -force:unresolved +; RUN: FileCheck %s -check-prefix=YAML-HOT < %t.t300.yaml + +; RUN: lld-link -opt-remarks-with-hotness \ +; RUN: -opt-remarks-hotness-threshold:301 \ +; RUN: -opt-remarks-filename:%t.t301.yaml %t.obj -entry:main -nodefaultlib \ +; RUN: -out:%t.exe -force:unresolved +; RUN: count 0 < %t.t301.yaml + +; RUN: lld-link -opt-remarks-filename:%t.yaml -opt-remarks-format:yaml \ +; RUN: %t.obj -entry:main -nodefaultlib -out:%t.exe -force:unresolved +; RUN: FileCheck %s -check-prefix=YAML < %t.yaml + +; YAML: --- !Passed +; YAML-NEXT: Pass: inline +; YAML-NEXT: Name: Inlined +; YAML-NEXT: Function: main +; YAML-NEXT: Args: +; YAML-NEXT: - String: '''' +; YAML-NEXT: - Callee: tinkywinky +; YAML-NEXT: - String: ''' inlined into ''' +; YAML-NEXT: - Caller: main +; YAML-NEXT: - String: '''' +; YAML-NEXT: - String: ' with ' +; YAML-NEXT: - String: '(cost=' +; YAML-NEXT: - Cost: +; YAML-NEXT: - String: ', threshold=' +; YAML-NEXT: - Threshold: +; YAML-NEXT: - String: ')' +; YAML-NEXT: ... + +; YAML-HOT: --- !Passed +; YAML-HOT-NEXT: Pass: inline +; YAML-HOT-NEXT: Name: Inlined +; YAML-HOT-NEXT: Function: main +; YAML-HOT-NEXT: Hotness: 300 +; YAML-HOT-NEXT: Args: +; YAML-HOT-NEXT: - String: '''' +; YAML-HOT-NEXT: - Callee: tinkywinky +; YAML-HOT-NEXT: - String: ''' inlined into ''' +; YAML-HOT-NEXT: - Caller: main +; YAML-HOT-NEXT: - String: '''' +; YAML-HOT-NEXT: - String: ' with ' +; YAML-HOT-NEXT: - String: '(cost=' +; YAML-HOT-NEXT: - Cost: +; YAML-HOT-NEXT: - String: ', threshold=' +; YAML-HOT-NEXT: - Threshold: +; YAML-HOT-NEXT: - String: ')' +; YAML-HOT-NEXT: ... + +; YAML-PASSES: Pass: inline + +target datalayout = "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-pc-windows-msvc19.14.26433" + +declare i32 @patatino() + +define i32 @tinkywinky() { + %a = call i32 @patatino() + ret i32 %a +} + +define i32 @main() !prof !0 { + %i = call i32 @tinkywinky() + ret i32 %i +} + +!0 = !{!"function_entry_count", i64 300}