diff --git a/clang/docs/MisNoInline.rst b/clang/docs/MisNoInline.rst new file mode 100644 --- /dev/null +++ b/clang/docs/MisNoInline.rst @@ -0,0 +1,67 @@ +=================== +Misnoinline +=================== +.. contents:: + +.. toctree:: + :maxdepth: 1 + +When developers use noinline attribute with a function, i.e., through use of +``__attribute__((noinline))``, they are trying to communicate that the +function should not be inlined by the compiler. These can be due to various +number of reasons to mark a function noinline. If a function is small, +not critical to the performance of your code and would be called less often, +especially in cases of error handling, it makes sense to noinline a function. +These annotations, however, can be incorrect for a variety of reasons: +changes to the code base may make these functions hot, the developer +mis-annotated them, or perhaps they assumed something incorrectly when they +wrote the annotation. Regardless of why, it is useful to detect these +situations so that the user can rethink and remove the attribute as necessary. + +MisNoInline diagnostics are intended to help developers identify and address +these situations, by comparing function hotness of noinline function and +checking if they indeed were candidates to become inline. Whenever percentile values +are breached, a warning is emitted from backend. Details on how the flags operate +in the LLVM backed can be found in LLVM's documentation. + +By default, MisNoInline checking is quite linient. It checks on default threshold percentile +of 99%. Because this may emit a lot of warnings, MisNoInline diagnostics are not enabled by +default, and support an additional flag to decrease the noinline percentile threshold and filter +hotter functions. The ``-fdiagnostics-misnoinline-percentile-threshold=N`` accepts +function entry percentile coldness upto N before emitting a warning. So passing +``-fdiagnostics-misnoinline-percentile-threshold=50`` will not report diagnostic messages +if the noinline function's hotness is greater than 50% as per PSI analysis. + +MisNoInline diagnostics are also available in the form of optimization remarks, +which can be serialized and processed through the ``opt-viewer.py`` +scripts in LLVM. + +.. option:: -Rpass=misnoinline + + Enables optimization remarks for misnoinline when profiling data conflicts with + use of ``noinline`` attribute. + + +.. option:: -Wmisnoinline + + Enables misnoinline warnings when profiling data conflicts with use of + ``noinline`` attribute. + +.. option:: -fdiagnostics-misnoinline-percentile-threshold=N + + Relaxes misnoinline checking to not emit warning for functions colder than N%. + +LLVM supports 4 types of profile formats: Frontend, IR, CS-IR, and +Sampling. MisNoInline Diagnostics are compatible with all Profiling formats. + ++----------------+--------------------------------------------------------------------------------------+ +| Profile Type | Description | ++================+======================================================================================+ +| Frontend | Profiling instrumentation added during compilation by the frontend, i.e. ``clang`` | ++----------------+--------------------------------------------------------------------------------------+ +| IR | Profiling instrumentation added during by the LLVM backend | ++----------------+--------------------------------------------------------------------------------------+ +| CS-IR | Context Sensitive IR based profiles | ++----------------+--------------------------------------------------------------------------------------+ +| Sampling | Profiles collected through sampling with external tools, such as ``perf`` on Linux | ++----------------+--------------------------------------------------------------------------------------+ diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -101,6 +101,8 @@ - Clang will now print more information about failed static assertions. In particular, simple static assertion expressions are evaluated to their compile-time value and printed out if the assertion fails. +- ``-Wmisnoinline`` warns when the noinline attribute attached to the function + may look wrong compared to profiling data. Non-comprehensive list of changes in this release ------------------------------------------------- diff --git a/clang/include/clang/Basic/CodeGenOptions.h b/clang/include/clang/Basic/CodeGenOptions.h --- a/clang/include/clang/Basic/CodeGenOptions.h +++ b/clang/include/clang/Basic/CodeGenOptions.h @@ -426,6 +426,10 @@ /// values in order to be included in misexpect diagnostics. Optional DiagnosticsMisExpectTolerance = 0; + /// The minimum function hotness percentile after which we emit + /// a warning to be included in misnoinline diagnostics. + Optional DiagnosticsNoInlinePercentileThreshold = 990000; + public: // Define accessors/mutators for code generation options of enumeration type. #define CODEGENOPT(Name, Bits, Default) diff --git a/clang/include/clang/Basic/CodeGenOptions.def b/clang/include/clang/Basic/CodeGenOptions.def --- a/clang/include/clang/Basic/CodeGenOptions.def +++ b/clang/include/clang/Basic/CodeGenOptions.def @@ -182,6 +182,7 @@ ///< enabled. CODEGENOPT(NoWarn , 1, 0) ///< Set when -Wa,--no-warn is enabled. CODEGENOPT(MisExpect , 1, 0) ///< Set when -Wmisexpect is enabled +CODEGENOPT(MisNoInline , 1, 0) ///< Set when -Wmisnoinline is enabled CODEGENOPT(EnableSegmentedStacks , 1, 0) ///< Set when -fsplit-stack is enabled. CODEGENOPT(NoInlineLineTables, 1, 0) ///< Whether debug info should contain ///< inline line tables. diff --git a/clang/include/clang/Basic/DiagnosticDriverKinds.td b/clang/include/clang/Basic/DiagnosticDriverKinds.td --- a/clang/include/clang/Basic/DiagnosticDriverKinds.td +++ b/clang/include/clang/Basic/DiagnosticDriverKinds.td @@ -148,6 +148,8 @@ "invalid argument in '%0', only integer or 'auto' is supported">; def err_drv_invalid_diagnotics_misexpect_tolerance : Error< "invalid argument in '%0', only integers are supported">; +def err_drv_invalid_diagnotics_misnoinline_threshold : Error< + "invalid argument in '%0' only integers are supported">; def err_drv_missing_argument : Error< "argument to '%0' is missing (expected %1 value%s1)">; def err_drv_invalid_Xarch_argument_with_args : Error< diff --git a/clang/include/clang/Basic/DiagnosticFrontendKinds.td b/clang/include/clang/Basic/DiagnosticFrontendKinds.td --- a/clang/include/clang/Basic/DiagnosticFrontendKinds.td +++ b/clang/include/clang/Basic/DiagnosticFrontendKinds.td @@ -320,6 +320,10 @@ "Annotation was correct on %0 of profiled executions.">, BackendInfo, InGroup; +def warn_profile_data_misnoinline : Warning< + "noinline attribute marking for %0:%1 may hurt performance.">, + BackendInfo, + InGroup; } // end of instrumentation issue category } diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -1272,6 +1272,7 @@ def ProfileInstrOutOfDate : DiagGroup<"profile-instr-out-of-date">; def ProfileInstrUnprofiled : DiagGroup<"profile-instr-unprofiled">; def MisExpect : DiagGroup<"misexpect">; +def MisNoInline : DiagGroup<"misnoinline">; // AddressSanitizer frontend instrumentation remarks. def SanitizeAddressRemarks : DiagGroup<"sanitize-address">; diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -1483,6 +1483,9 @@ def fdiagnostics_misexpect_tolerance_EQ : Joined<["-"], "fdiagnostics-misexpect-tolerance=">, Group, Flags<[CC1Option]>, MetaVarName<"">, HelpText<"Prevent misexpect diagnostics from being output if the profile counts are within N% of the expected. ">; +def fdiagnostics_misnoinline_percentile_EQ : Joined<["-"], "fdiagnostics-misnoinline-percentile-threshold=">, + Group, Flags<[CC1Option]>, MetaVarName<"">, + HelpText<"Prevent misnoinline diagnostics from being output if the function is colder than N% of total entry counts. ">; defm diagnostics_show_option : BoolFOption<"diagnostics-show-option", DiagnosticOpts<"ShowOptionNames">, DefaultTrue, NegFlag, PosFlag>; diff --git a/clang/lib/CodeGen/BackendUtil.cpp b/clang/lib/CodeGen/BackendUtil.cpp --- a/clang/lib/CodeGen/BackendUtil.cpp +++ b/clang/lib/CodeGen/BackendUtil.cpp @@ -479,7 +479,7 @@ Options.MCOptions.Argv0 = CodeGenOpts.Argv0; Options.MCOptions.CommandLineArgs = CodeGenOpts.CommandLineArgs; Options.MisExpect = CodeGenOpts.MisExpect; - + Options.MisNoInline = CodeGenOpts.MisNoInline; return true; } diff --git a/clang/lib/CodeGen/CodeGenAction.cpp b/clang/lib/CodeGen/CodeGenAction.cpp --- a/clang/lib/CodeGen/CodeGenAction.cpp +++ b/clang/lib/CodeGen/CodeGenAction.cpp @@ -351,6 +351,13 @@ CodeGenOpts.DiagnosticsMisExpectTolerance); } + if (CodeGenOpts.MisNoInline) + Ctx.setMisNoInlineWarningRequested(true); + + if (CodeGenOpts.DiagnosticsNoInlinePercentileThreshold) + Ctx.setDiagnosticsMisNoInlinePercentileThreshold( + CodeGenOpts.DiagnosticsNoInlinePercentileThreshold); + // Link each LinkModule into our module. if (LinkInModules()) return; @@ -451,9 +458,10 @@ void OptimizationFailureHandler( const llvm::DiagnosticInfoOptimizationFailure &D); void DontCallDiagHandler(const DiagnosticInfoDontCall &D); - /// Specialized handler for misexpect warnings. - /// Note that misexpect remarks are emitted through ORE + /// Specialized handler for misexpect and misnoinline warnings. + /// Note that misexpect/misnoinline remarks are emitted through ORE void MisExpectDiagHandler(const llvm::DiagnosticInfoMisExpect &D); + void MisNoInlineDiagHandler(const llvm::DiagnosticInfoMisNoInline &D); }; void BackendConsumer::anchor() {} @@ -854,6 +862,25 @@ << Filename << Line << Column; } +void BackendConsumer::MisNoInlineDiagHandler( + const llvm::DiagnosticInfoMisNoInline &D) { + StringRef Filename; + unsigned Line, Column; + bool BadDebugInfo = false; + FullSourceLoc Loc = + getBestLocationFromDebugLoc(D, BadDebugInfo, Filename, Line, Column); + Diags.Report(Loc, diag::warn_profile_data_misnoinline) + << D.getFunction().getParent()->getName() + << llvm::demangle(D.getFunction().getName().str()); + if (BadDebugInfo) + // If we were not able to translate the file:line:col information + // back to a SourceLocation, at least emit a note stating that + // we could not translate this location. This can happen in the + // case of #line directives. + Diags.Report(Loc, diag::note_fe_backend_invalid_loc) + << Filename << Line << Column; +} + /// This function is invoked when the backend needs /// to report something to the user. void BackendConsumer::DiagnosticHandlerImpl(const DiagnosticInfo &DI) { @@ -931,6 +958,9 @@ case llvm::DK_MisExpect: MisExpectDiagHandler(cast(DI)); return; + case llvm::DK_MisNoInline: + MisNoInlineDiagHandler(cast(DI)); + return; default: // Plugin IDs are not bound to any value as they are set dynamically. ComputeDiagRemarkID(Severity, backend_plugin, DiagID); diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -120,6 +120,14 @@ "Not an integer: %s", Arg.data()); return Val; } +// Parse misnoinline count value. +// Valid option values are integers in the range [0, infinity) +inline Optional parseCountOption(StringRef Arg) { + int64_t Val; + if (Arg.getAsInteger(10, Val)) + return Optional(); + return Val; +} //===----------------------------------------------------------------------===// // Initialization. @@ -1553,6 +1561,9 @@ GenerateArg(Args, OPT_fdiagnostics_misexpect_tolerance_EQ, Twine(*Opts.DiagnosticsMisExpectTolerance), SA); + GenerateArg(Args, OPT_fdiagnostics_misnoinline_percentile_EQ, + Twine(*Opts.DiagnosticsNoInlinePercentileThreshold), SA); + for (StringRef Sanitizer : serializeSanitizerKinds(Opts.SanitizeRecover)) GenerateArg(Args, OPT_fsanitize_recover_EQ, Sanitizer, SA); @@ -2010,6 +2021,24 @@ } } + if (auto *arg = Args.getLastArg( + options::OPT_fdiagnostics_misnoinline_percentile_EQ)) { + auto ResultOrErr = parseCountOption(arg->getValue()); + + if (!ResultOrErr) { + Diags.Report(diag::err_drv_invalid_diagnotics_misnoinline_threshold) + << "-fdiagnostics-misnoinline-percentile-threshold="; + } else { + Opts.DiagnosticsNoInlinePercentileThreshold = *ResultOrErr; + if ((!Opts.DiagnosticsNoInlinePercentileThreshold || + Opts.MisNoInline == 1) && + !UsingProfile) { + Diags.Report(diag::warn_drv_diagnostics_misexpect_requires_pgo) + << "-fdiagnostics-misnoinline-percentile-threshold="; + } + } + } + // If the user requested to use a sample profile for PGO, then the // backend will need to track source location information so the profile // can be incorporated into the IR. @@ -4482,6 +4511,11 @@ !Diags.isIgnored(diag::warn_profile_data_misexpect, SourceLocation())) { Res.getCodeGenOpts().MisExpect = true; } + if (Warning == "misnoinline" && + !Diags.isIgnored(diag::warn_profile_data_misnoinline, + SourceLocation())) { + Res.getCodeGenOpts().MisNoInline = true; + } } if (LangOpts.CUDA) { diff --git a/clang/test/Misc/Inputs/MisNoInline.proftext b/clang/test/Misc/Inputs/MisNoInline.proftext new file mode 100644 --- /dev/null +++ b/clang/test/Misc/Inputs/MisNoInline.proftext @@ -0,0 +1,21 @@ +_Z4foo1ii:2000:2000 + 0: 2000 + 1.1: 2000 + 1.2: 2000 + 2: 2000 + 3: 2000 + 5: 2000 +_Z4foo2ii:200:200 + 0: 200 + 1.1: 200 + 1.2: 200 + 2: 200 + 3: 200 + 5: 200 +main:1:1 + 0: 1 + 1: 1 + 2: 1 + 3: 1 + 4: 1 + 6: 1 diff --git a/clang/test/Misc/MisNoInline.cpp b/clang/test/Misc/MisNoInline.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Misc/MisNoInline.cpp @@ -0,0 +1,29 @@ +//Test that misnoinline emits correct warning + +// Test that without passing -fdiagnostics-misnoinline-count-threshold flag warning are emitted with percentile cuttof at 99% +// This would mean both foo1 and foo2 would emit warning +// RUN: %clang_cc1 %s -O2 -o - -emit-llvm -fprofile-sample-use=%S/Inputs/MisNoInline.proftext -verify=emit -Wmisnoinline + +void printf(); +__attribute__((noinline)) long foo1(int x, int y) { + while (x != y) { + printf(); + y++; + } + return y; +} +__attribute__((noinline)) long foo2(int x, int y) { + while (x != y) { + printf(); + y++; + } + return y; +} +int main() { + int x = 5678; + int y = 1234; + x += foo1(x, y); // emit-warning-re {{noinline attribute marking for {{.*}}MisNoInline.cpp:foo1(int, int) may hurt performance.}} + x += foo2(x, y); // emit-warning-re {{noinline attribute marking for {{.*}}MisNoInline.cpp:foo2(int, int) may hurt performance.}} + + return x; +} diff --git a/clang/test/Misc/MisNoInline_LowThreshold.cpp b/clang/test/Misc/MisNoInline_LowThreshold.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Misc/MisNoInline_LowThreshold.cpp @@ -0,0 +1,28 @@ +//Test that misnoinline emits correct warning + +// Test that with -fdiagnostics-misnoinline-percentile-threshold set to 90% only foo1 emits warning as per profiling data +// RUN: %clang_cc1 %s -O2 -o - -emit-llvm -fprofile-sample-use=%S/Inputs/MisNoInline.proftext -verify=emit -Wmisnoinline -fdiagnostics-misnoinline-percentile-threshold=900000 + +void printf(); +__attribute__((noinline)) long foo1(int x, int y) { + while (x != y) { + printf(); + y++; + } + return y; +} +__attribute__((noinline)) long foo2(int x, int y) { + while (x != y) { + printf(); + y++; + } + return y; +} +int main() { + int x = 5678; + int y = 1234; + x += foo1(x, y); // emit-warning-re {{noinline attribute marking for {{.*}}MisNoInline_LowThreshold.cpp:foo1(int, int) may hurt performance.}} + x += foo2(x, y); + + return x; +} diff --git a/clang/test/Misc/MisNoInline_PragmaIgnore.cpp b/clang/test/Misc/MisNoInline_PragmaIgnore.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Misc/MisNoInline_PragmaIgnore.cpp @@ -0,0 +1,34 @@ +// Test that misnoinline dosent emit warning when it is pragma ignored + +// RUN: %clang_cc1 %s -O2 -o - -emit-llvm -fprofile-sample-use=%S/Inputs/MisNoInline.proftext -verify=noemit -Wmisnoinline + +// RUN: %clang_cc1 %s -O2 -o - -emit-llvm -fprofile-sample-use=%S/Inputs/MisNoInline.proftext -verify=noemit -Wmisnoinline -fdiagnostics-misnoinline-percentile-threshold=900000 + +//noemit-no-diagnostics +void printf(); +__attribute__((noinline)) long foo1(int x, int y) { + while (x != y) { + printf(); + y++; + } + return y; +} +__attribute__((noinline)) long foo2(int x, int y) { + while (x != y) { + printf(); + y++; + } + return y; +} + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wmisnoinline" +int main() { + int x = 5678; + int y = 1234; + x += foo1(x, y); + x += foo2(x, y); + + return x; +} +#pragma clang diagnostic pop diff --git a/llvm/docs/MisNoInline.rst b/llvm/docs/MisNoInline.rst new file mode 100644 --- /dev/null +++ b/llvm/docs/MisNoInline.rst @@ -0,0 +1,69 @@ +=================== +Misnoexpect +=================== +.. contents:: + +.. toctree:: + :maxdepth: 1 + +When developers use noinline attribute with a function, i.e., through use of +``__attribute__((noinline))``, they are trying to communicate that the +function should not be inlined by the compiler. These can be due to various +number of reasons to mark a function noinline. If a function is small, +not critical to the performance of your code and would be called less often, +especially in cases of error handling, it makes sense to noinline a function. +These annotations, however, can be incorrect for a variety of reasons: +changes to the code base may make these functions hot, the developer +mis-annotated them, or perhaps they assumed something incorrectly when they +wrote the annotation. Regardless of why, it is useful to detect these +situations so that the user can rethink and remove the attribute as necessary. + +The MisNoInline checks in the LLVM backend follow a simple procedure: if the function entry +entry count exceeds a certain percentile threshold and the cost of inlining the noinline marked +function indicates a success, the MisNoInline flags will emit a diagnostic to the user to reconsider +their stance on the function marking. + +The most natural place to perform these checks is in the CGSCC Inliner pass specifically +where we check the cost of inlining the function. The profile pass has also been run +up to this point, so information about BFI/PSI is available for further analysis. + + +Instead of stopping our analysis on whether a function needs to be inlined +in InlinerCost, if we hit a noinline marked function we continue and see if by cost it is +feasible to inline a function. If so, we do one more check if the function is percentile wise +hotter than user provided value (If user doesn't provide the value it's 99% by default), and go +ahead and emit the warning. + +The diagnostics are also available in the form of optimization remarks, +which can be serialized and processed through the ``opt-viewer.py`` +scripts in LLVM. + +.. option:: -pass-remarks=misnoinline + + Enables optimization remarks for misnoinline when profiling data conflicts with + use of ``noinline`` function attribute. + + +.. option:: pgo-warn-misnoinline + + Enables misnoinline warnings when profiling data conflicts with use of + ``noinline`` function attribute. + +.. option:: misnoinline-percent + + Relaxes misnoinline checking to not emit warnings for functions colder than Nth percentile. + +LLVM supports 4 types of profile formats: Frontend, IR, CS-IR, and +Sampling. MisNoInline Diagnostics are compatible with all profiling formats. + ++----------------+--------------------------------------------------------------------------------------+ +| Profile Type | Description | ++================+======================================================================================+ +| Frontend | Profiling instrumentation added during compilation by the frontend, i.e. ``clang`` | ++----------------+--------------------------------------------------------------------------------------+ +| IR | Profiling instrumentation added during by the LLVM backend | ++----------------+--------------------------------------------------------------------------------------+ +| CS-IR | Context Sensitive IR based profiles | ++----------------+--------------------------------------------------------------------------------------+ +| Sampling | Profiles collected through sampling with external tools, such as ``perf`` on Linux | ++----------------+--------------------------------------------------------------------------------------+ diff --git a/llvm/include/llvm/Analysis/InlineCost.h b/llvm/include/llvm/Analysis/InlineCost.h --- a/llvm/include/llvm/Analysis/InlineCost.h +++ b/llvm/include/llvm/Analysis/InlineCost.h @@ -101,6 +101,8 @@ /// The cost-benefit pair computed by cost-benefit analysis. Optional CostBenefit = None; + bool IsNoInline = false; + // Trivial constructor, interesting logic in the factory functions below. InlineCost(int Cost, int Threshold, const char *Reason = nullptr, Optional CostBenefit = None) @@ -159,12 +161,17 @@ /// Only valid if the cost is of the variable kind. Returns a negative /// value if the cost is too high to inline. int getCostDelta() const { return Threshold - getCost(); } + + // Set cost is due to function being marked noinline + void setIsNoInline() { IsNoInline = true; } + bool isNoInline() const { return IsNoInline; } }; /// InlineResult is basically true or false. For false results the message /// describes a reason. class InlineResult { const char *Message = nullptr; + bool IsNoInline = false; InlineResult(const char *Message = nullptr) : Message(Message) {} public: @@ -178,6 +185,8 @@ "getFailureReason should only be called in failure cases"); return Message; } + bool isNoInline() const { return IsNoInline; } + void setIsNoInline() { IsNoInline = true; } }; /// Thresholds to tune inline cost analysis. The inline cost analysis decides @@ -292,6 +301,16 @@ CallBase &Call, Function *Callee, TargetTransformInfo &CalleeTTI, function_ref GetTLI); +// Check if a no inline function would have been inlined based on cost decision +InlineCost isInlineViableFromCostAnalysis( + CallBase &Call, Function *Callee, const InlineParams &Params, + TargetTransformInfo &CalleeTTI, + function_ref GetAssumptionCache, + function_ref GetTLI, + function_ref GetBFI = nullptr, + ProfileSummaryInfo *PSI = nullptr, + OptimizationRemarkEmitter *ORE = nullptr); + /// Get the cost estimate ignoring thresholds. This is similar to getInlineCost /// when passed InlineParams::ComputeFullInlineCost, or a non-null ORE. It /// uses default InlineParams otherwise. diff --git a/llvm/include/llvm/IR/DiagnosticInfo.h b/llvm/include/llvm/IR/DiagnosticInfo.h --- a/llvm/include/llvm/IR/DiagnosticInfo.h +++ b/llvm/include/llvm/IR/DiagnosticInfo.h @@ -86,6 +86,7 @@ DK_SrcMgr, DK_DontCall, DK_MisExpect, + DK_MisNoInline, DK_FirstPluginKind // Must be last value to work with // getNextAvailablePluginDiagnosticKind }; @@ -397,7 +398,7 @@ /// Return the absolute path tot the file. std::string getAbsolutePath() const; - + const Function &getFunction() const { return Fn; } DiagnosticLocation getLocation() const { return Loc; } @@ -1052,6 +1053,28 @@ const Twine &Msg; }; +/// Diagnostic information for MisNoInline analysis. +/// Diagnostic information for ISel fallback path. +class DiagnosticInfoMisNoInline : public DiagnosticInfoWithLocationBase { + /// The function that is concerned by this diagnostic. + /// const Twine &Msg; +private: + const Function &Fn; + const Twine &Msg; + +public: + DiagnosticInfoMisNoInline(const Instruction *Inst, const Function &Fn, + Twine &Msg); + const Function &getFunction() const { return Fn; } + + void print(DiagnosticPrinter &DP) const override; + + static bool classof(const DiagnosticInfo *DI) { + return DI->getKind() == DK_MisNoInline; + } + const Twine &getMsg() const { return Msg; } +}; + static DiagnosticSeverity getDiagnosticSeverity(SourceMgr::DiagKind DK) { switch (DK) { case llvm::SourceMgr::DK_Error: diff --git a/llvm/include/llvm/IR/LLVMContext.h b/llvm/include/llvm/IR/LLVMContext.h --- a/llvm/include/llvm/IR/LLVMContext.h +++ b/llvm/include/llvm/IR/LLVMContext.h @@ -207,6 +207,14 @@ void setDiagnosticsMisExpectTolerance(Optional Tolerance); uint32_t getDiagnosticsMisExpectTolerance() const; + /// Return if a code noinline diagnostic needs to be emitted + bool getMisNoInlineWarningRequested() const; + /// Set noinline diagnostic + void setMisNoInlineWarningRequested(bool Requested); + // Get/set for NoInline count threshold + void + setDiagnosticsMisNoInlinePercentileThreshold(Optional Threshold); + uint64_t getDiagnosticsMisNoInlinePercentileThreshold() const; /// Return the minimum hotness value a diagnostic would need in order /// to be included in optimization diagnostics. /// diff --git a/llvm/include/llvm/Target/TargetOptions.h b/llvm/include/llvm/Target/TargetOptions.h --- a/llvm/include/llvm/Target/TargetOptions.h +++ b/llvm/include/llvm/Target/TargetOptions.h @@ -144,7 +144,7 @@ ValueTrackingVariableLocations(false), ForceDwarfFrameSection(false), XRayOmitFunctionIndex(false), DebugStrictDwarf(false), Hotpatch(false), PPCGenScalarMASSEntries(false), JMCInstrument(false), - EnableCFIFixup(false), MisExpect(false), + EnableCFIFixup(false), MisExpect(false), MisNoInline(false), FPDenormalMode(DenormalMode::IEEE, DenormalMode::IEEE) {} /// DisableFramePointerElim - This returns true if frame pointer elimination @@ -364,6 +364,10 @@ /// By default, it is set to false unsigned MisExpect : 1; + /// When set to true, enable MisNoInline Diagnostics + /// By default, it is set to false + unsigned MisNoInline : 1; + /// Name of the stack usage file (i.e., .su file) if user passes /// -fstack-usage. If empty, it can be implied that -fstack-usage is not /// passed on the command line. diff --git a/llvm/lib/Analysis/InlineAdvisor.cpp b/llvm/lib/Analysis/InlineAdvisor.cpp --- a/llvm/lib/Analysis/InlineAdvisor.cpp +++ b/llvm/lib/Analysis/InlineAdvisor.cpp @@ -28,6 +28,7 @@ using namespace llvm; #define DEBUG_TYPE "inline" +#define DEBUG_TYPE_WARN "misnoinline" #ifdef LLVM_HAVE_TF_AOT_INLINERSIZEMODEL #define LLVM_HAVE_TF_AOT #endif @@ -61,6 +62,20 @@ cl::desc("If true, annotate inline advisor remarks " "with LTO and pass information.")); +// Command line option to enable/disable the warning when profile data suggests +// a mismatch with the use of the noinline attribute +static cl::opt PGOWarnMisNoInline( + "pgo-warn-misnoinline", cl::init(false), cl::Hidden, + cl::desc("Use this option to turn on/off " + "warnings about usage of noinline function attribute " + "which may hurt performance.")); + +// Command line opt option to noinline count threshold +static cl::opt MisNoInlinePercent( + "misnoinline-percent", + cl::desc("Prevents emiting diagnostics when function is colder" + "that N percentile of total entry counts.")); + extern cl::opt InlinerFunctionImportStats; namespace { @@ -128,6 +143,64 @@ Advisor->getAnnotatedInlinePassName()); } +static bool canEmitNoInlineWarning(CallBase *CB, InlineCost &IC, + FunctionAnalysisManager &FAM) { + Function &Callee = *CB->getCalledFunction(); + LLVMContext &Ctx = Callee.getContext(); + const char *reason = IC.getReason(); + + // Check if pass remarks, Wmisnoinline or pgo-warn-misnoinline said anything + // about warning + if (!OptimizationRemark(DEBUG_TYPE_WARN, "misnoinline", CB).isEnabled() && + !PGOWarnMisNoInline && !Ctx.getMisNoInlineWarningRequested()) + return false; + // If Callee has no profile data bail out + if (!Callee.hasProfileData()) + return false; + // Failiure reason is due to noinline callee/call site attribute + if (reason == nullptr) + return false; + if (!IC.isNoInline()) + return false; + uint64_t Percent_Threshold; + if (MisNoInlinePercent) + Percent_Threshold = MisNoInlinePercent; + else + Percent_Threshold = Ctx.getDiagnosticsMisNoInlinePercentileThreshold(); + ProfileSummaryInfo *PSI = + FAM.getResult(Callee) + .getCachedResult(*Callee.getParent()); + if (!PSI) + return false; + BlockFrequencyInfo &CalleeBFI = FAM.getResult(Callee); + if (!PSI->isFunctionHotInCallGraphNthPercentile(Percent_Threshold, &Callee, + CalleeBFI)) + return false; + // we have checked all conditions we are safe to + // check for cost and emit warning + return true; +} + +// Emit a noinline diagnostic if: +// 1. Function entry is hotter than the supplied thrshold in terms of total +// entry count +// 2. The noinline function could have been inlined by cost metric. This part is +// checked when normal inlining fails due to noinline attribute +static void emitNoInlineWarning(Function *Callee, Function *Caller, + CallBase *CB) { + llvm::LLVMContext &Ctx = Caller->getContext(); + auto RemStr = + formatv("Marking {0} noinline while calling in {1} " + "may hurt performance as per inline cost/hotness analysis", + Callee->getName(), Caller->getName()); + Twine Msg(RemStr); + if (PGOWarnMisNoInline || Ctx.getMisNoInlineWarningRequested()) + Ctx.diagnose(DiagnosticInfoMisNoInline(CB, *Callee, Msg)); + OptimizationRemarkEmitter ORE(CB->getParent()->getParent()); + ORE.emit(OptimizationRemark(DEBUG_TYPE_WARN, "misnoinline", CB) + << RemStr.str()); +} + llvm::Optional static getDefaultInlineAdvice( CallBase &CB, FunctionAnalysisManager &FAM, const InlineParams &Params) { Function &Caller = *CB.getCaller(); @@ -153,9 +226,23 @@ bool RemarksEnabled = Callee.getContext().getDiagHandlerPtr()->isMissedOptRemarkEnabled( DEBUG_TYPE); - return getInlineCost(CB, Params, CalleeTTI, GetAssumptionCache, GetTLI, - GetBFI, PSI, RemarksEnabled ? &ORE : nullptr); + InlineCost IC = + getInlineCost(CB, Params, CalleeTTI, GetAssumptionCache, GetTLI, GetBFI, + PSI, RemarksEnabled ? &ORE : nullptr); + // Check if we should emit a warning for noinline function + if (LLVM_UNLIKELY(canEmitNoInlineWarning(&CB, IC, FAM))) { + Function &Callee = *CB.getCalledFunction(); + auto &CalleeTTI = FAM.getResult(Callee); + auto TempIC = llvm::isInlineViableFromCostAnalysis( + CB, &Callee, Params, CalleeTTI, GetAssumptionCache, GetTLI, GetBFI, + PSI); + + if (TempIC.isAlways() || TempIC) + emitNoInlineWarning(&Callee, &Caller, &CB); + } + return IC; }; + return llvm::shouldInline( CB, GetInlineCost, ORE, Params.EnableDeferral.value_or(EnableInlineDeferral)); diff --git a/llvm/lib/Analysis/InlineCost.cpp b/llvm/lib/Analysis/InlineCost.cpp --- a/llvm/lib/Analysis/InlineCost.cpp +++ b/llvm/lib/Analysis/InlineCost.cpp @@ -2919,33 +2919,29 @@ return InlineResult::failure("interposable"); // Don't inline functions marked noinline. - if (Callee->hasFnAttribute(Attribute::NoInline)) - return InlineResult::failure("noinline function attribute"); + if (Callee->hasFnAttribute(Attribute::NoInline)) { + auto IR = InlineResult::failure("noinline function attribute"); + IR.setIsNoInline(); + return IR; + } // Don't inline call sites marked noinline. - if (Call.isNoInline()) - return InlineResult::failure("noinline call site attribute"); + if (Call.isNoInline()) { + auto IR = InlineResult::failure("noinline call site attribute"); + IR.setIsNoInline(); + return IR; + } return None; } -InlineCost llvm::getInlineCost( +InlineCost llvm::isInlineViableFromCostAnalysis( CallBase &Call, Function *Callee, const InlineParams &Params, TargetTransformInfo &CalleeTTI, function_ref GetAssumptionCache, function_ref GetTLI, function_ref GetBFI, ProfileSummaryInfo *PSI, OptimizationRemarkEmitter *ORE) { - - auto UserDecision = - llvm::getAttributeBasedInliningDecision(Call, Callee, CalleeTTI, GetTLI); - - if (UserDecision) { - if (UserDecision->isSuccess()) - return llvm::InlineCost::getAlways("always inline attribute"); - return llvm::InlineCost::getNever(UserDecision->getFailureReason()); - } - LLVM_DEBUG(llvm::dbgs() << " Analyzing call of " << Callee->getName() << "... (caller:" << Call.getCaller()->getName() << ")\n"); @@ -2976,6 +2972,29 @@ : InlineCost::getNever(ShouldInline.getFailureReason()); } +InlineCost llvm::getInlineCost( + CallBase &Call, Function *Callee, const InlineParams &Params, + TargetTransformInfo &CalleeTTI, + function_ref GetAssumptionCache, + function_ref GetTLI, + function_ref GetBFI, + ProfileSummaryInfo *PSI, OptimizationRemarkEmitter *ORE) { + + auto UserDecision = + llvm::getAttributeBasedInliningDecision(Call, Callee, CalleeTTI, GetTLI); + if (UserDecision) { + if (UserDecision->isSuccess()) + return llvm::InlineCost::getAlways("always inline attribute"); + auto IC = llvm::InlineCost::getNever(UserDecision->getFailureReason()); + if (UserDecision->isNoInline()) + IC.setIsNoInline(); + return IC; + } + return llvm::isInlineViableFromCostAnalysis(Call, Callee, Params, CalleeTTI, + GetAssumptionCache, GetTLI, + GetBFI, PSI, ORE); +} + InlineResult llvm::isInlineViable(Function &F) { bool ReturnsTwice = F.hasFnAttribute(Attribute::ReturnsTwice); for (BasicBlock &BB : F) { diff --git a/llvm/lib/IR/DiagnosticInfo.cpp b/llvm/lib/IR/DiagnosticInfo.cpp --- a/llvm/lib/IR/DiagnosticInfo.cpp +++ b/llvm/lib/IR/DiagnosticInfo.cpp @@ -404,6 +404,19 @@ DP << getLocationStr() << ": " << getMsg(); } +DiagnosticInfoMisNoInline::DiagnosticInfoMisNoInline(const Instruction *Inst, + const Function &Fn, + Twine &Msg) + : DiagnosticInfoWithLocationBase(DK_MisNoInline, DS_Warning, + *Inst->getParent()->getParent(), + Inst->getDebugLoc()), + Fn(Fn), Msg(Msg) {} + +void DiagnosticInfoMisNoInline::print(DiagnosticPrinter &DP) const { + DP << "Module" + << ": " << getFunction().getParent()->getName() << " " << getMsg(); +} + void OptimizationRemarkAnalysisFPCommute::anchor() {} void OptimizationRemarkAnalysisAliasing::anchor() {} diff --git a/llvm/lib/IR/LLVMContext.cpp b/llvm/lib/IR/LLVMContext.cpp --- a/llvm/lib/IR/LLVMContext.cpp +++ b/llvm/lib/IR/LLVMContext.cpp @@ -155,6 +155,19 @@ return pImpl->DiagnosticsMisExpectTolerance.value_or(0); } +void LLVMContext::setMisNoInlineWarningRequested(bool Requested) { + pImpl->MisNoInlineWarningRequested = Requested; +} +bool LLVMContext::getMisNoInlineWarningRequested() const { + return pImpl->MisNoInlineWarningRequested; +} +void LLVMContext::setDiagnosticsMisNoInlinePercentileThreshold( + Optional Count) { + pImpl->DiagnosticsMisNoInlinePercentile = Count; +} +uint64_t LLVMContext::getDiagnosticsMisNoInlinePercentileThreshold() const { + return pImpl->DiagnosticsMisNoInlinePercentile.value_or(0); +} bool LLVMContext::isDiagnosticsHotnessThresholdSetFromPSI() const { return !pImpl->DiagnosticsHotnessThreshold.has_value(); } diff --git a/llvm/lib/IR/LLVMContextImpl.h b/llvm/lib/IR/LLVMContextImpl.h --- a/llvm/lib/IR/LLVMContextImpl.h +++ b/llvm/lib/IR/LLVMContextImpl.h @@ -1391,6 +1391,11 @@ Optional DiagnosticsMisExpectTolerance = 0; bool MisExpectWarningRequested = false; + /// The percentile of hotness of a function + // when emiting MisNoInline diagnostics + Optional DiagnosticsMisNoInlinePercentile = 990000; + bool MisNoInlineWarningRequested = false; + /// The specialized remark streamer used by LLVM's OptimizationRemarkEmitter. std::unique_ptr LLVMRS; diff --git a/llvm/test/Transforms/PGOProfile/Inputs/MisNoInline.proftext b/llvm/test/Transforms/PGOProfile/Inputs/MisNoInline.proftext new file mode 100644 --- /dev/null +++ b/llvm/test/Transforms/PGOProfile/Inputs/MisNoInline.proftext @@ -0,0 +1,21 @@ +_Z4foo1ii:2000:2000 + 0: 2000 + 1.1: 2000 + 1.2: 2000 + 2: 2000 + 3: 2000 + 5: 2000 +_Z4foo2ii:200:200 + 0: 200 + 1.1: 200 + 1.2: 200 + 2: 200 + 3: 200 + 5: 200 +main:1:1 + 0: 1 + 1: 1 + 2: 1 + 3: 1 + 4: 1 + 6: 1 diff --git a/llvm/test/Transforms/PGOProfile/MisNoInline.ll b/llvm/test/Transforms/PGOProfile/MisNoInline.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Transforms/PGOProfile/MisNoInline.ll @@ -0,0 +1,216 @@ +; RUN: opt < %s -passes=sample-profile,inline -sample-profile-file=%S/Inputs/MisNoInline.proftext -pgo-warn-misnoinline -S 2>&1 | FileCheck %s -check-prefix=WARNING +; RUN: opt < %s -passes=sample-profile,inline -sample-profile-file=%S/Inputs/MisNoInline.proftext -pgo-warn-misnoinline -misnoinline-percent=900000 -S 2>&1 | FileCheck %s -check-prefix=WARNING_LOW +; RUN: opt < %s -passes=sample-profile,inline -sample-profile-file=%S/Inputs/MisNoInline.proftext -pass-remarks=misnoinline -S 2>&1 | FileCheck %s --check-prefix=REMARK + +; WARNING-DAG: warning: Module: {{.*}} Marking _Z4foo1ii noinline while calling in main may hurt performance as per inline cost/hotness analysis +; WARNING-DAG: warning: Module: {{.*}} Marking _Z4foo2ii noinline while calling in main may hurt performance as per inline cost/hotness analysis + +; WARNING_LOW-DAG: warning: Module: {{.*}} Marking _Z4foo1ii noinline while calling in main may hurt performance as per inline cost/hotness analysis +; WARNING_LOW-NOT: warning: Module: {{.*}} Marking _Z4foo2ii noinline while calling in main may hurt performance as per inline cost/hotness analysis + +; REMARK-NOT: warning: Module: {{.*}} Marking _Z4foo1ii noinline while calling in main may hurt performance as per inline cost/hotness analysis +; REMARK-NOT: warning: Module: {{.*}} Marking _Z4foo2ii noinline while calling in main may hurt performance as per inline cost/hotness analysis +; REMARK-DAG: remark: MisNoInline.cpp:28:8: Marking _Z4foo1ii noinline while calling in main may hurt performance as per inline cost/hotness analysis +; REMARK-DAG: remark: MisNoInline.cpp:29:8: Marking _Z4foo2ii noinline while calling in main may hurt performance as per inline cost/hotness analysis + +; The source code used for the LLVM IR that follows. +; void printf(); +; __attribute__((noinline)) long foo1(int x, int y) { +; while (x != y) { +; printf(); +; y++; +; } +; return y; +; } +; __attribute__((noinline)) long foo2(int x, int y) { +; while (x != y) { +; printf(); +; y++; +; } +; return y; +; } +; int main() { +; int x = 5678; +; int y = 1234; +; x += foo1(x, y); +; x += foo2(x, y); +; +; return x; +; } + +; Function Attrs: mustprogress noinline optnone uwtable +define dso_local noundef i64 @_Z4foo1ii(i32 noundef %x, i32 noundef %y) #0 !dbg !8 { +entry: + %x.addr = alloca i32, align 4 + %y.addr = alloca i32, align 4 + store i32 %x, ptr %x.addr, align 4 + call void @llvm.dbg.declare(metadata ptr %x.addr, metadata !14, metadata !DIExpression()), !dbg !15 + store i32 %y, ptr %y.addr, align 4 + call void @llvm.dbg.declare(metadata ptr %y.addr, metadata !16, metadata !DIExpression()), !dbg !17 + br label %while.cond, !dbg !18 + +while.cond: ; preds = %while.body, %entry + %0 = load i32, ptr %x.addr, align 4, !dbg !19 + %1 = load i32, ptr %y.addr, align 4, !dbg !21 + %cmp = icmp ne i32 %0, %1, !dbg !22 + br i1 %cmp, label %while.body, label %while.end, !dbg !23 + +while.body: ; preds = %while.cond + call void @_Z6printfv(), !dbg !24 + %2 = load i32, ptr %y.addr, align 4, !dbg !26 + %inc = add nsw i32 %2, 1, !dbg !26 + store i32 %inc, ptr %y.addr, align 4, !dbg !26 + br label %while.cond, !dbg !27, !llvm.loop !29 + +while.end: ; preds = %while.cond + %3 = load i32, ptr %y.addr, align 4, !dbg !32 + %conv = sext i32 %3 to i64, !dbg !32 + ret i64 %conv, !dbg !33 +} + +; Function Attrs: nocallback nofree nosync nounwind readnone speculatable willreturn +declare void @llvm.dbg.declare(metadata, metadata, metadata) #1 + +declare dso_local void @_Z6printfv() #2 + +; Function Attrs: mustprogress noinline optnone uwtable +define dso_local noundef i64 @_Z4foo2ii(i32 noundef %x, i32 noundef %y) #0 !dbg !34 { +entry: + %x.addr = alloca i32, align 4 + %y.addr = alloca i32, align 4 + store i32 %x, ptr %x.addr, align 4 + call void @llvm.dbg.declare(metadata ptr %x.addr, metadata !35, metadata !DIExpression()), !dbg !36 + store i32 %y, ptr %y.addr, align 4 + call void @llvm.dbg.declare(metadata ptr %y.addr, metadata !37, metadata !DIExpression()), !dbg !38 + br label %while.cond, !dbg !39 + +while.cond: ; preds = %while.body, %entry + %0 = load i32, ptr %x.addr, align 4, !dbg !40 + %1 = load i32, ptr %y.addr, align 4, !dbg !42 + %cmp = icmp ne i32 %0, %1, !dbg !43 + br i1 %cmp, label %while.body, label %while.end, !dbg !44 + +while.body: ; preds = %while.cond + call void @_Z6printfv(), !dbg !45 + %2 = load i32, ptr %y.addr, align 4, !dbg !47 + %inc = add nsw i32 %2, 1, !dbg !47 + store i32 %inc, ptr %y.addr, align 4, !dbg !47 + br label %while.cond, !dbg !48, !llvm.loop !50 + +while.end: ; preds = %while.cond + %3 = load i32, ptr %y.addr, align 4, !dbg !52 + %conv = sext i32 %3 to i64, !dbg !52 + ret i64 %conv, !dbg !53 +} + +; Function Attrs: mustprogress noinline norecurse uwtable +define dso_local noundef i32 @main() #3 !dbg !54 { +entry: + %retval = alloca i32, align 4 + %x = alloca i32, align 4 + %y = alloca i32, align 4 + store i32 0, ptr %retval, align 4 + call void @llvm.dbg.declare(metadata ptr %x, metadata !57, metadata !DIExpression()), !dbg !58 + store i32 5678, ptr %x, align 4, !dbg !58 + call void @llvm.dbg.declare(metadata ptr %y, metadata !59, metadata !DIExpression()), !dbg !60 + store i32 1234, ptr %y, align 4, !dbg !60 + %0 = load i32, ptr %x, align 4, !dbg !61 + %1 = load i32, ptr %y, align 4, !dbg !62 + %call = call noundef i64 @_Z4foo1ii(i32 noundef %0, i32 noundef %1), !dbg !63 + %2 = load i32, ptr %x, align 4, !dbg !64 + %conv = sext i32 %2 to i64, !dbg !64 + %add = add nsw i64 %conv, %call, !dbg !64 + %conv1 = trunc i64 %add to i32, !dbg !64 + store i32 %conv1, ptr %x, align 4, !dbg !64 + %3 = load i32, ptr %x, align 4, !dbg !65 + %4 = load i32, ptr %y, align 4, !dbg !66 + %call2 = call noundef i64 @_Z4foo2ii(i32 noundef %3, i32 noundef %4), !dbg !67 + %5 = load i32, ptr %x, align 4, !dbg !68 + %conv3 = sext i32 %5 to i64, !dbg !68 + %add4 = add nsw i64 %conv3, %call2, !dbg !68 + %conv5 = trunc i64 %add4 to i32, !dbg !68 + store i32 %conv5, ptr %x, align 4, !dbg !68 + %6 = load i32, ptr %x, align 4, !dbg !69 + ret i32 %6, !dbg !70 +} + +attributes #0 = { mustprogress noinline uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" "use-sample-profile"} +attributes #1 = { nocallback nofree nosync nounwind readnone speculatable willreturn } +attributes #2 = { "frame-pointer"="all" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } +attributes #3 = { mustprogress noinline norecurse uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" "use-sample-profile"} + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!2, !3, !4, !5, !6} +!llvm.ident = !{!7} + +!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang version 15.0.0", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, debugInfoForProfiling: true, nameTableKind: None) +!1 = !DIFile(filename: "MisNoInline.cpp", directory: ".") +!2 = !{i32 7, !"Dwarf Version", i32 5} +!3 = !{i32 2, !"Debug Info Version", i32 3} +!4 = !{i32 1, !"wchar_size", i32 4} +!5 = !{i32 7, !"uwtable", i32 2} +!6 = !{i32 7, !"frame-pointer", i32 2} +!7 = !{!"clang version 15.0.0"} +!8 = distinct !DISubprogram(name: "foo1", linkageName: "_Z4foo1ii", scope: !1, file: !1, line: 11, type: !9, scopeLine: 11, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !13) +!9 = !DISubroutineType(types: !10) +!10 = !{!11, !12, !12} +!11 = !DIBasicType(name: "long", size: 64, encoding: DW_ATE_signed) +!12 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +!13 = !{} +!14 = !DILocalVariable(name: "x", arg: 1, scope: !8, file: !1, line: 11, type: !12) +!15 = !DILocation(line: 11, column: 41, scope: !8) +!16 = !DILocalVariable(name: "y", arg: 2, scope: !8, file: !1, line: 11, type: !12) +!17 = !DILocation(line: 11, column: 48, scope: !8) +!18 = !DILocation(line: 12, column: 3, scope: !8) +!19 = !DILocation(line: 12, column: 10, scope: !20) +!20 = !DILexicalBlockFile(scope: !8, file: !1, discriminator: 2) +!21 = !DILocation(line: 12, column: 15, scope: !20) +!22 = !DILocation(line: 12, column: 12, scope: !20) +!23 = !DILocation(line: 12, column: 3, scope: !20) +!24 = !DILocation(line: 13, column: 5, scope: !25) +!25 = distinct !DILexicalBlock(scope: !8, file: !1, line: 12, column: 18) +!26 = !DILocation(line: 14, column: 6, scope: !25) +!27 = !DILocation(line: 12, column: 3, scope: !28) +!28 = !DILexicalBlockFile(scope: !8, file: !1, discriminator: 4) +!29 = distinct !{!29, !18, !30, !31} +!30 = !DILocation(line: 15, column: 3, scope: !8) +!31 = !{!"llvm.loop.mustprogress"} +!32 = !DILocation(line: 16, column: 10, scope: !8) +!33 = !DILocation(line: 16, column: 3, scope: !8) +!34 = distinct !DISubprogram(name: "foo2", linkageName: "_Z4foo2ii", scope: !1, file: !1, line: 18, type: !9, scopeLine: 18, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !13) +!35 = !DILocalVariable(name: "x", arg: 1, scope: !34, file: !1, line: 18, type: !12) +!36 = !DILocation(line: 18, column: 41, scope: !34) +!37 = !DILocalVariable(name: "y", arg: 2, scope: !34, file: !1, line: 18, type: !12) +!38 = !DILocation(line: 18, column: 48, scope: !34) +!39 = !DILocation(line: 19, column: 3, scope: !34) +!40 = !DILocation(line: 19, column: 10, scope: !41) +!41 = !DILexicalBlockFile(scope: !34, file: !1, discriminator: 2) +!42 = !DILocation(line: 19, column: 15, scope: !41) +!43 = !DILocation(line: 19, column: 12, scope: !41) +!44 = !DILocation(line: 19, column: 3, scope: !41) +!45 = !DILocation(line: 20, column: 5, scope: !46) +!46 = distinct !DILexicalBlock(scope: !34, file: !1, line: 19, column: 18) +!47 = !DILocation(line: 21, column: 6, scope: !46) +!48 = !DILocation(line: 19, column: 3, scope: !49) +!49 = !DILexicalBlockFile(scope: !34, file: !1, discriminator: 4) +!50 = distinct !{!50, !39, !51, !31} +!51 = !DILocation(line: 22, column: 3, scope: !34) +!52 = !DILocation(line: 23, column: 10, scope: !34) +!53 = !DILocation(line: 23, column: 3, scope: !34) +!54 = distinct !DISubprogram(name: "main", scope: !1, file: !1, line: 25, type: !55, scopeLine: 25, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !13) +!55 = !DISubroutineType(types: !56) +!56 = !{!12} +!57 = !DILocalVariable(name: "x", scope: !54, file: !1, line: 26, type: !12) +!58 = !DILocation(line: 26, column: 7, scope: !54) +!59 = !DILocalVariable(name: "y", scope: !54, file: !1, line: 27, type: !12) +!60 = !DILocation(line: 27, column: 7, scope: !54) +!61 = !DILocation(line: 28, column: 13, scope: !54) +!62 = !DILocation(line: 28, column: 16, scope: !54) +!63 = !DILocation(line: 28, column: 8, scope: !54) +!64 = !DILocation(line: 28, column: 5, scope: !54) +!65 = !DILocation(line: 29, column: 13, scope: !54) +!66 = !DILocation(line: 29, column: 16, scope: !54) +!67 = !DILocation(line: 29, column: 8, scope: !54) +!68 = !DILocation(line: 29, column: 5, scope: !54) +!69 = !DILocation(line: 31, column: 10, scope: !54) +!70 = !DILocation(line: 31, column: 3, scope: !54)