[flang-commits] [clang] [flang] [llvm] Turn -fsplit-machine-functions into a mode option (PR #226566)

Rahman Lavaee via flang-commits flang-commits at lists.llvm.org
Fri Sep 25 11:52:33 PDT 2026


https://github.com/rlavaee created https://github.com/llvm/llvm-project/pull/226566

None

>From cfafeeced28dc9d9dfd98cd4f9e2c3cc25e56343 Mon Sep 17 00:00:00 2001
From: Rahman Lavaee <rahmanl at google.com>
Date: Thu, 27 Aug 2026 16:59:24 +0000
Subject: [PATCH 1/3] [llvm-profgen] Parse hex address suffixes in perf scripts

DSO/library names (e.g. ` [unknown]`, ` [libc.so.6]`) are sometimes appended to call stack addresses in perf scripts. This causes `parseAddress` to fail because `getAsInteger` expects the entire input string to be the integer representation.

Fix this by using `take_while` to strip trailing whitespace/non-hex suffixes from the address token inside `parseAddress`. To prevent LBR lines from being mistakenly consumed as call stack entries in `checkPerfScriptType`'s detection loop, we add an explicit check to stop when an LBR line is encountered.

Added a new test case `callstack-dso-suffix.test` verifying correct parsing with different types of suffixes.
---
 .../Inputs/callstack-dso-suffix.perfscript    | 23 ++++++++++++
 .../X86/callstack-dso-suffix.test             | 36 +++++++++++++++++++
 llvm/tools/llvm-profgen/PerfReader.cpp        |  3 ++
 3 files changed, 62 insertions(+)
 create mode 100644 llvm/test/tools/llvm-profgen/X86/Inputs/callstack-dso-suffix.perfscript
 create mode 100644 llvm/test/tools/llvm-profgen/X86/callstack-dso-suffix.test

diff --git a/llvm/test/tools/llvm-profgen/X86/Inputs/callstack-dso-suffix.perfscript b/llvm/test/tools/llvm-profgen/X86/Inputs/callstack-dso-suffix.perfscript
new file mode 100644
index 00000000000000..f101edf6282bf0
--- /dev/null
+++ b/llvm/test/tools/llvm-profgen/X86/Inputs/callstack-dso-suffix.perfscript
@@ -0,0 +1,23 @@
+PERF_RECORD_MMAP2 2854748/2854748: [0x400000(0x1000) @ 0 00:1d 123291722 526021]: r-xp /home/noinline-cs-noprobe.perfbin
+
+	          4005dc [unknown]
+	          400634 [libc.so.6]
+	          400684
+	    7f68c5788793 [unknown]
+ 0x4005c8/0x4005dc/P/-/-/0  0x40062f/0x4005b0/P/-/-/0  0x400645/0x4005ff/P/-/-/0  0x400637/0x400645/P/-/-/0  0x4005e9/0x400634/P/-/-/0  0x4005d7/0x4005e5/P/-/-/0  0x40062f/0x4005b0/P/-/-/0  0x400645/0x4005ff/P/-/-/0  0x400637/0x400645/P/-/-/0  0x4005e9/0x400634/P/-/-/0  0x4005d7/0x4005e5/P/-/-/0  0x40062f/0x4005b0/P/-/-/0  0x400645/0x4005ff/P/-/-/0  0x400637/0x400645/P/-/-/0  0x4005e9/0x400634/P/-/-/0  0x4005c8/0x4005dc/P/-/-/0
+
+// Test for leaf frame ending up in prolog
+	          4005b0 [libc.so.6]
+	          400684
+	    7f68c5788793 [unknown]
+ 0x40062f/0x4005b0/P/-/-/0  0x400645/0x4005ff/P/-/-/0  0x400637/0x400645/P/-/-/0  0x4005e9/0x400634/P/-/-/0  0x4005c8/0x4005dc/P/-/-/0  0x40062f/0x4005b0/P/-/-/0  0x400645/0x4005ff/P/-/-/0  0x400637/0x400645/P/-/-/0  0x4005e9/0x400634/P/-/-/0  0x4005d7/0x4005e5/P/-/-/0  0x40062f/0x4005b0/P/-/-/0  0x400645/0x4005ff/P/-/-/0  0x400637/0x400645/P/-/-/0  0x4005e9/0x400634/P/-/-/0  0x4005d7/0x4005e5/P/-/-/0  0x40062f/0x4005b0/P/-/-/0
+
+// Call stack:
+//	          4005b0	-> 	start addr of bar
+//	          400684	->	address in main
+// LBR Entry:				    |   Source				|	Target
+// 0x40062f/0x4005b0/P/-/-/0	|   callq	-132 <bar>  |   start addr of bar
+// 0x400645/0x4005ff/P/-/-/0    |	jmp	-75 <foo+0xf>	|	movl	-8(%rbp), %eax
+// 0x400637/0x400645/P/-/-/0 	|	jmp	9 <foo+0x55>	|	jmp	-75 <foo+0xf>
+// 0x4005e9/0x400634/P/-/-/0	|	(bar)retq			|	next addr of [callq	-132 <bar>]
+// 0x4005d7/0x4005e5/P/-/-/0 	|	jmp	9 <bar+0x35>	|	movl	-4(%rbp), %eax
diff --git a/llvm/test/tools/llvm-profgen/X86/callstack-dso-suffix.test b/llvm/test/tools/llvm-profgen/X86/callstack-dso-suffix.test
new file mode 100644
index 00000000000000..1e073d1432d220
--- /dev/null
+++ b/llvm/test/tools/llvm-profgen/X86/callstack-dso-suffix.test
@@ -0,0 +1,36 @@
+;; The test fails on Windows. Fix it before removing the following requirement.
+; REQUIRES: x86_64-linux
+; RUN: llvm-profgen --format=text --perfscript=%S/Inputs/callstack-dso-suffix.perfscript --binary=%S/Inputs/noinline-cs-noprobe.perfbin --output=%t --skip-symbolization --profile-summary-cold-count=0
+; RUN: FileCheck %s --input-file %t --check-prefix=CHECK-UNWINDER
+; RUN: llvm-profgen --format=text --unsymbolized-profile=%t --binary=%S/Inputs/noinline-cs-noprobe.perfbin --output=%t1 --profile-summary-cold-count=0 --csspgo-preinliner=0 --gen-cs-nested-profile=0
+; RUN: FileCheck %s --input-file %t1
+
+; CHECK-UNWINDER:      [main:1 @ foo]
+; CHECK-UNWINDER-NEXT:   3
+; CHECK-UNWINDER-NEXT:   5ff-62f:3
+; CHECK-UNWINDER-NEXT:   634-637:3
+; CHECK-UNWINDER-NEXT:   645-645:3
+; CHECK-UNWINDER-NEXT:   3
+; CHECK-UNWINDER-NEXT:   62f->5b0:3
+; CHECK-UNWINDER-NEXT:   637->645:3
+; CHECK-UNWINDER-NEXT:   645->5ff:3
+; CHECK-UNWINDER-NEXT: [main:1 @ foo:3 @ bar]
+; CHECK-UNWINDER-NEXT:   4
+; CHECK-UNWINDER-NEXT:   5b0-5c8:1
+; CHECK-UNWINDER-NEXT:   5b0-5d7:2
+; CHECK-UNWINDER-NEXT:   5dc-5e9:1
+; CHECK-UNWINDER-NEXT:   5e5-5e9:2
+; CHECK-UNWINDER-NEXT:   3
+; CHECK-UNWINDER-NEXT:   5c8->5dc:2
+; CHECK-UNWINDER-NEXT:   5d7->5e5:2
+; CHECK-UNWINDER-NEXT:   5e9->634:3
+
+; CHECK:[main:1 @ foo]:54:0
+; CHECK: 2: 3
+; CHECK: 3: 3 bar:3
+; CHECK:[main:1 @ foo:3 @ bar]:50:3
+; CHECK: 0: 3
+; CHECK: 1: 3
+; CHECK: 2: 2
+; CHECK: 4: 1
+; CHECK: 5: 3
diff --git a/llvm/tools/llvm-profgen/PerfReader.cpp b/llvm/tools/llvm-profgen/PerfReader.cpp
index c4fc4fb25166fb..daabb5183d440e 100644
--- a/llvm/tools/llvm-profgen/PerfReader.cpp
+++ b/llvm/tools/llvm-profgen/PerfReader.cpp
@@ -6,6 +6,7 @@
 //
 //===----------------------------------------------------------------------===//
 #include "PerfReader.h"
+#include <cctype>
 #include "ErrorHandling.h"
 #include "Options.h"
 #include "ProfileGenerator.h"
@@ -708,6 +709,7 @@ void HybridPerfReader::unwindSamples() {
 
 /// Parse a hex address from \p Str.
 static bool parseAddress(StringRef Str, uint64_t &Addr, bool HasPrefix) {
+  Str = Str.take_while([](char C) { return !isspace(C); });
   if (Str.consume_front("0x") != HasPrefix)
     return true;
   return Str.getAsInteger(16, Addr);
@@ -1264,6 +1266,7 @@ PerfContent PerfScriptReader::checkPerfScriptType(StringRef FileName) {
     // Detect sample with call stack
     int32_t Count = 0;
     while (!TraceIt.isAtEoF() &&
+           !isLBRSample(TraceIt.getCurrentLine(), false) &&
            !parseAddress(TraceIt.getCurrentLine().ltrim(), FrameAddr, false)) {
       Count++;
       TraceIt.advance();

>From 1e2f529ab0861309474136f3682086fe7b87f91f Mon Sep 17 00:00:00 2001
From: Rahman Lavaee <rahmanl at google.com>
Date: Sat, 19 Sep 2026 18:56:47 +0000
Subject: [PATCH 2/3] [CodeGen] Add a FunctionSplittingMode target option

Introduce llvm::FunctionSplittingMode {Default, None, BBSectionsOnly, All} and
TargetOptions::FunctionSplitting, exposed through a new
-function-splitting={none,bbsections,all} codegen flag, together with the
resolveFunctionSplittingMode() helper which resolves it against the legacy
-split-machine-functions / -enable-split-machine-functions options and the
basic block sections mode.

The hidden -enable-split-machine-functions cl::opt moves from
TargetPassConfig.cpp to BasicBlockSections.cpp (declared in
BasicBlockSectionUtils.h, mirroring BBSectionsColdTextPrefix) so that both can
share it, and TargetPassConfig schedules MachineFunctionSplitter when the
resolved mode is 'all'.

This is behavior preserving for every existing command line: the resolved mode
is 'all' exactly when one of the legacy options is set, since FunctionSplitting
stays 'Default' unless the new flag is passed.

This is the first step towards merging MachineFunctionSplitter into
BasicBlockSections, where 'none' (layout only) and 'bbsections' will gain their
full per-function meaning.
---
 .../llvm/CodeGen/BasicBlockSectionUtils.h     | 13 ++++
 llvm/include/llvm/CodeGen/CommandFlags.h      |  2 +
 llvm/include/llvm/Target/TargetOptions.h      | 18 +++++
 llvm/lib/CodeGen/BasicBlockSections.cpp       | 21 ++++++
 llvm/lib/CodeGen/CommandFlags.cpp             | 17 +++++
 llvm/lib/CodeGen/TargetPassConfig.cpp         | 16 ++---
 .../CodeGen/X86/function-splitting-mode.ll    | 68 +++++++++++++++++++
 7 files changed, 145 insertions(+), 10 deletions(-)
 create mode 100644 llvm/test/CodeGen/X86/function-splitting-mode.ll

diff --git a/llvm/include/llvm/CodeGen/BasicBlockSectionUtils.h b/llvm/include/llvm/CodeGen/BasicBlockSectionUtils.h
index 25eaf5c32d743d..0a64aee4954413 100644
--- a/llvm/include/llvm/CodeGen/BasicBlockSectionUtils.h
+++ b/llvm/include/llvm/CodeGen/BasicBlockSectionUtils.h
@@ -11,13 +11,26 @@
 
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/Support/CommandLine.h"
+#include "llvm/Target/TargetOptions.h"
 
 namespace llvm {
 
 extern LLVM_ABI cl::opt<std::string> BBSectionsColdTextPrefix;
 
+/// Legacy option which enables late function splitting for every function with
+/// profile data. Superseded by \p FunctionSplittingMode, which it feeds into
+/// via resolveFunctionSplittingMode().
+extern LLVM_ABI cl::opt<bool> EnableMachineFunctionSplitter;
+
 class MachineFunction;
 class MachineBasicBlock;
+class TargetMachine;
+
+/// Returns the effective late function splitting mode, resolving the explicit
+/// options against the basic block sections mode. Never returns
+/// FunctionSplittingMode::Default.
+LLVM_ABI FunctionSplittingMode
+resolveFunctionSplittingMode(const TargetMachine &TM);
 
 using MachineBasicBlockComparator =
     function_ref<bool(const MachineBasicBlock &, const MachineBasicBlock &)>;
diff --git a/llvm/include/llvm/CodeGen/CommandFlags.h b/llvm/include/llvm/CodeGen/CommandFlags.h
index c882a33b84a5bc..15f2dfb0ecbb9b 100644
--- a/llvm/include/llvm/CodeGen/CommandFlags.h
+++ b/llvm/include/llvm/CodeGen/CommandFlags.h
@@ -103,6 +103,8 @@ LLVM_ABI bool getXCOFFTracebackTable();
 
 LLVM_ABI std::string getBBSections();
 
+LLVM_ABI llvm::FunctionSplittingMode getFunctionSplitting();
+
 LLVM_ABI unsigned getTLSSize();
 
 LLVM_ABI bool getEmulatedTLS();
diff --git a/llvm/include/llvm/Target/TargetOptions.h b/llvm/include/llvm/Target/TargetOptions.h
index f6c862e99b98fc..58723bb27d5352 100644
--- a/llvm/include/llvm/Target/TargetOptions.h
+++ b/llvm/include/llvm/Target/TargetOptions.h
@@ -65,6 +65,21 @@ enum class BasicBlockSection {
   None    // Do not use Basic Block Sections.
 };
 
+/// Late function splitting mode. Decides which functions are eligible to have
+/// their cold blocks moved into a separate section.
+enum class FunctionSplittingMode {
+  Default,        // Not specified: infer from the basic block sections and the
+                  // legacy machine function splitter options.
+  None,           // Hard off. Never create a cold section, even for functions
+                  // which have a basic block sections profile. Such functions
+                  // are still laid out using the profile, but are emitted as a
+                  // single contiguous section.
+  BBSectionsOnly, // Split only functions which have a basic block sections
+                  // profile.
+  All             // Split functions using the basic block sections profile
+                  // where it is available, and PGO/AutoFDO elsewhere.
+};
+
 /// Identify a debugger for "tuning" the debug info.
 ///
 /// The "debugger tuning" concept allows us to present a more intuitive
@@ -287,6 +302,9 @@ class TargetOptions {
   /// Emit basic blocks into separate sections.
   BasicBlockSection BBSections = BasicBlockSection::None;
 
+  /// Which functions are eligible for late function splitting.
+  FunctionSplittingMode FunctionSplitting = FunctionSplittingMode::Default;
+
   /// Memory Buffer that contains information on sampled basic blocks and used
   /// to selectively generate basic block sections.
   std::shared_ptr<MemoryBuffer> BBSectionsFuncListBuf;
diff --git a/llvm/lib/CodeGen/BasicBlockSections.cpp b/llvm/lib/CodeGen/BasicBlockSections.cpp
index 9972d431aae8b0..2fba0503534a74 100644
--- a/llvm/lib/CodeGen/BasicBlockSections.cpp
+++ b/llvm/lib/CodeGen/BasicBlockSections.cpp
@@ -96,6 +96,27 @@ cl::opt<std::string> llvm::BBSectionsColdTextPrefix(
     cl::desc("The text prefix to use for cold basic block clusters"),
     cl::init(".text.split."), cl::Hidden);
 
+/// Enable the machine function splitter pass. This is the legacy spelling of
+/// -function-splitting=all and is kept for backwards compatibility.
+cl::opt<bool> llvm::EnableMachineFunctionSplitter(
+    "enable-split-machine-functions", cl::Hidden,
+    cl::desc("Split out cold blocks from machine functions based on profile "
+             "information."));
+
+FunctionSplittingMode
+llvm::resolveFunctionSplittingMode(const TargetMachine &TM) {
+  // An explicitly requested mode always wins.
+  if (TM.Options.FunctionSplitting != FunctionSplittingMode::Default)
+    return TM.Options.FunctionSplitting;
+  // The legacy machine function splitter options request splitting everywhere.
+  if (TM.Options.EnableMachineFunctionSplitter || EnableMachineFunctionSplitter)
+    return FunctionSplittingMode::All;
+  // A basic block sections profile splits the functions it covers.
+  if (TM.getBBSectionsType() == BasicBlockSection::List)
+    return FunctionSplittingMode::BBSectionsOnly;
+  return FunctionSplittingMode::None;
+}
+
 static cl::opt<bool> BBSectionsDetectSourceDrift(
     "bbsections-detect-source-drift",
     cl::desc("This checks if there is a fdo instr. profile hash "
diff --git a/llvm/lib/CodeGen/CommandFlags.cpp b/llvm/lib/CodeGen/CommandFlags.cpp
index db149dd9382035..c122030f0dd922 100644
--- a/llvm/lib/CodeGen/CommandFlags.cpp
+++ b/llvm/lib/CodeGen/CommandFlags.cpp
@@ -98,6 +98,7 @@ CGOPT(bool, IgnoreXCOFFVisibility)
 CGOPT(bool, XCOFFTracebackTable)
 CGOPT(bool, EnableBBAddrMap)
 CGOPT(std::string, BBSections)
+CGOPT(FunctionSplittingMode, FunctionSplitting)
 CGOPT(unsigned, TLSSize)
 CGOPT_EXP(bool, EmulatedTLS)
 CGOPT_EXP(bool, EnableTLSDESC)
@@ -490,6 +491,21 @@ codegen::RegisterCodeGenFlags::RegisterCodeGenFlags() {
       cl::init(false));
   CGBINDOPT(EnableMachineFunctionSplitter);
 
+  static cl::opt<FunctionSplittingMode> FunctionSplitting(
+      "function-splitting",
+      cl::desc("Which functions are eligible for late function splitting"),
+      cl::init(FunctionSplittingMode::Default),
+      cl::values(
+          clEnumValN(FunctionSplittingMode::None, "none",
+                     "Do not split any function"),
+          clEnumValN(FunctionSplittingMode::BBSectionsOnly, "bbsections",
+                     "Only split functions which have a basic block sections "
+                     "profile"),
+          clEnumValN(FunctionSplittingMode::All, "all",
+                     "Split functions using the basic block sections profile "
+                     "where it is available, and PGO/AutoFDO elsewhere")));
+  CGBINDOPT(FunctionSplitting);
+
   static cl::opt<bool> EnableStaticDataPartitioning(
       "partition-static-data-sections",
       cl::desc("Partition data sections using profile information."),
@@ -614,6 +630,7 @@ codegen::InitTargetOptionsFromCodeGenFlags(const Triple &TheTriple) {
   Options.VecLib = getVectorLibrary();
   Options.EmitStackSizeSection = getEnableStackSizeSection();
   Options.EnableMachineFunctionSplitter = getEnableMachineFunctionSplitter();
+  Options.FunctionSplitting = getFunctionSplitting();
   Options.EnableStaticDataPartitioning = getEnableStaticDataPartitioning();
   Options.EmitAddrsig = getEnableAddrsig();
   Options.EmitCallGraphSection = getEnableCallGraphSection();
diff --git a/llvm/lib/CodeGen/TargetPassConfig.cpp b/llvm/lib/CodeGen/TargetPassConfig.cpp
index 06abd8c378ac89..6a0a3a6876141e 100644
--- a/llvm/lib/CodeGen/TargetPassConfig.cpp
+++ b/llvm/lib/CodeGen/TargetPassConfig.cpp
@@ -20,6 +20,7 @@
 #include "llvm/Analysis/ScopedNoAliasAA.h"
 #include "llvm/Analysis/TargetTransformInfo.h"
 #include "llvm/Analysis/TypeBasedAliasAnalysis.h"
+#include "llvm/CodeGen/BasicBlockSectionUtils.h"
 #include "llvm/CodeGen/BasicBlockSectionsProfileReader.h"
 #include "llvm/CodeGen/CSEConfigBase.h"
 #include "llvm/CodeGen/CodeGenTargetMachineImpl.h"
@@ -249,12 +250,6 @@ static cl::opt<std::string>
                   cl::desc("Stop compilation before a specific pass"),
                   cl::value_desc("pass-name"), cl::init(""), cl::Hidden);
 
-/// Enable the machine function splitter pass.
-static cl::opt<bool> EnableMachineFunctionSplitter(
-    "enable-split-machine-functions", cl::Hidden,
-    cl::desc("Split out cold blocks from machine functions based on profile "
-             "information."));
-
 /// Disable the expand reductions pass for testing.
 static cl::opt<bool> DisableExpandReductions(
     "disable-expand-reductions", cl::init(false), cl::Hidden,
@@ -1252,8 +1247,10 @@ void TargetPassConfig::addMachinePasses() {
     addPass(createMIRAddFSDiscriminatorsPass(
         sampleprof::FSDiscriminatorPass::PassLast));
 
-  if (TM->Options.EnableMachineFunctionSplitter ||
-      EnableMachineFunctionSplitter || SplitStaticData ||
+  const bool SplitFunctions =
+      resolveFunctionSplittingMode(*TM) == FunctionSplittingMode::All;
+
+  if (SplitFunctions || SplitStaticData ||
       TM->Options.EnableStaticDataPartitioning) {
     const std::string ProfileFile = getFSProfileFile(TM);
     if (!ProfileFile.empty()) {
@@ -1276,8 +1273,7 @@ void TargetPassConfig::addMachinePasses() {
   // feature takes precedence. This means functions eligible for
   // basic-block-sections optimizations (`=all`, or `=list=` with function
   // included in the list profile) will get that optimization instead.
-  if (TM->Options.EnableMachineFunctionSplitter ||
-      EnableMachineFunctionSplitter)
+  if (SplitFunctions)
     addPass(createMachineFunctionSplitterPass());
 
   if (SplitStaticData || TM->Options.EnableStaticDataPartitioning) {
diff --git a/llvm/test/CodeGen/X86/function-splitting-mode.ll b/llvm/test/CodeGen/X86/function-splitting-mode.ll
new file mode 100644
index 00000000000000..07519c1579031a
--- /dev/null
+++ b/llvm/test/CodeGen/X86/function-splitting-mode.ll
@@ -0,0 +1,68 @@
+;; Tests for the -function-splitting= late function splitting mode.
+; REQUIRES: x86-registered-target
+
+;; The legacy spellings and -function-splitting=all are equivalent.
+; RUN: llc < %s -mtriple=x86_64-unknown-linux-gnu -split-machine-functions | FileCheck %s --check-prefix=SPLIT
+; RUN: llc < %s -mtriple=x86_64-unknown-linux-gnu -enable-split-machine-functions | FileCheck %s --check-prefix=SPLIT
+; RUN: llc < %s -mtriple=x86_64-unknown-linux-gnu -function-splitting=all | FileCheck %s --check-prefix=SPLIT
+
+;; A function which only has PGO data is not split unless the mode is 'all'.
+; RUN: llc < %s -mtriple=x86_64-unknown-linux-gnu | FileCheck %s --check-prefix=NOSPLIT
+; RUN: llc < %s -mtriple=x86_64-unknown-linux-gnu -function-splitting=none | FileCheck %s --check-prefix=NOSPLIT
+; RUN: llc < %s -mtriple=x86_64-unknown-linux-gnu -function-splitting=bbsections | FileCheck %s --check-prefix=NOSPLIT
+
+;; An explicit mode overrides the legacy options.
+; RUN: llc < %s -mtriple=x86_64-unknown-linux-gnu -split-machine-functions -function-splitting=none | FileCheck %s --check-prefix=NOSPLIT
+; RUN: llc < %s -mtriple=x86_64-unknown-linux-gnu -enable-split-machine-functions -function-splitting=bbsections | FileCheck %s --check-prefix=NOSPLIT
+
+;; Invalid modes are rejected.
+; RUN: not llc < %s -mtriple=x86_64-unknown-linux-gnu -function-splitting=bogus 2>&1 | FileCheck %s --check-prefix=ERR
+; ERR: for the --function-splitting option: Cannot find option named 'bogus'!
+
+define void @foo(i1 zeroext %0) nounwind !prof !14 !section_prefix !15 {
+; SPLIT-LABEL:   foo
+; SPLIT:         .section        .text.split.foo
+; SPLIT-NEXT:    foo.cold:
+; SPLIT-NOT:     callq   bar
+; SPLIT-NEXT:    callq   baz
+;
+; NOSPLIT-LABEL: foo
+; NOSPLIT-NOT:   .section        .text.split.foo
+; NOSPLIT-NOT:   foo.cold:
+  br i1 %0, label %2, label %4, !prof !17
+
+2:                                                ; preds = %1
+  %3 = call i32 @bar()
+  br label %6
+
+4:                                                ; preds = %1
+  %5 = call i32 @baz()
+  br label %6
+
+6:                                                ; preds = %4, %2
+  %7 = tail call i32 @qux()
+  ret void
+}
+
+declare i32 @bar()
+declare i32 @baz()
+declare i32 @qux()
+
+!llvm.module.flags = !{!0}
+!0 = !{i32 1, !"ProfileSummary", !1}
+!1 = !{!2, !3, !4, !5, !6, !7, !8, !9}
+!2 = !{!"ProfileFormat", !"InstrProf"}
+!3 = !{!"TotalCount", i64 10000}
+!4 = !{!"MaxCount", i64 10}
+!5 = !{!"MaxInternalCount", i64 1}
+!6 = !{!"MaxFunctionCount", i64 1000}
+!7 = !{!"NumCounts", i64 3}
+!8 = !{!"NumFunctions", i64 5}
+!9 = !{!"DetailedSummary", !10}
+!10 = !{!11, !12, !13}
+!11 = !{i32 10000, i64 100, i32 1}
+!12 = !{i32 999900, i64 100, i32 1}
+!13 = !{i32 999999, i64 1, i32 2}
+!14 = !{!"function_entry_count", i64 7000}
+!15 = !{!"function_section_prefix", !"hot"}
+!17 = !{!"branch_weights", i32 7000, i32 0}

>From d8aca10f63856b86092cb53e3f26deca92763a5b Mon Sep 17 00:00:00 2001
From: Rahman Lavaee <rahmanl at google.com>
Date: Sat, 19 Sep 2026 18:57:00 +0000
Subject: [PATCH 3/3] [clang][flang] Turn -fsplit-machine-functions into a mode
 option

Replace the BoolFOption with -fsplit-machine-functions={none,bbsections,all},
following the -flto pattern: the bare -fsplit-machine-functions and
-fno-split-machine-functions spellings become aliases of the '=' form with
AliasArgs<["all"]> and AliasArgs<["none"]> respectively.

This buys a tri-state (absent / explicitly off / explicitly on) which the
backend needs in order to honor -fno-split-machine-functions together with
-fbasic-block-sections=list=, plus last-wins precedence between the spellings,
while keeping every existing command line working.

Because Option::accept() rewrites an aliased Arg to carry the unaliased option,
matches(OPT_fsplit_machine_functions) and hasArg(OPT_fsplit_machine_functions)
silently stop working. All readers are therefore updated to query
OPT_fsplit_machine_functions_EQ and switch on the value: the LTO -plugin-opt=
forwarding, tools::addSplitMachineFunctionsArgs() and flang's
CompilerInvocation.

CodeGenOpts.SplitMachineFunctions becomes the FunctionSplitting enum, which now
feeds TargetOptions::FunctionSplitting, and flang's TargetOptions carries the
mode as well.

Driver behavior is unchanged except that -cc1/-fc1 now receive the explicit
mode (e.g. "-fsplit-machine-functions=all"), and that disabling splitting is
accepted on targets without splitting support. Forwarding the new modes through
LTO (-plugin-opt=-function-splitting=) is left for a follow-up; for now only
'all' maps to the legacy -plugin-opt=-split-machine-functions.
---
 clang/include/clang/Basic/CodeGenOptions.def  |  3 +-
 clang/include/clang/Basic/CodeGenOptions.h    |  1 +
 clang/include/clang/Options/Options.td        | 20 +++++++++----
 clang/lib/CodeGen/BackendUtil.cpp             |  2 +-
 clang/lib/Driver/ToolChains/CommonArgs.cpp    | 29 ++++++++++---------
 clang/test/Driver/fsplit-machine-functions.c  | 26 +++++++++++++++--
 .../test/Frontend/fsplit-machine-functions.c  | 11 +++++++
 flang/include/flang/Frontend/TargetOptions.h  |  6 ++--
 flang/lib/Frontend/CompilerInstance.cpp       |  2 +-
 flang/lib/Frontend/CompilerInvocation.cpp     | 10 +++++--
 .../test/Driver/fsplit-machine-functions.f90  |  4 +--
 11 files changed, 85 insertions(+), 29 deletions(-)
 create mode 100644 clang/test/Frontend/fsplit-machine-functions.c

diff --git a/clang/include/clang/Basic/CodeGenOptions.def b/clang/include/clang/Basic/CodeGenOptions.def
index bf3e61f2f036fd..a6d5c643d9d973 100644
--- a/clang/include/clang/Basic/CodeGenOptions.def
+++ b/clang/include/clang/Basic/CodeGenOptions.def
@@ -207,7 +207,8 @@ CODEGENOPT(HIPCorrectlyRoundedDivSqrt, 1, 1, Benign) ///< -fno-hip-fp32-correctl
 CODEGENOPT(DisableBlockSignatureString, 1, 0, Benign) ///< Set when -fdisable-block-signature-string is enabled.
 CODEGENOPT(HIPSaveKernelArgName, 1, 0, Benign) ///< Set when -fhip-kernel-arg-name is enabled.
 CODEGENOPT(UniqueInternalLinkageNames, 1, 0, Benign) ///< Internal Linkage symbols get unique names.
-CODEGENOPT(SplitMachineFunctions, 1, 0, Benign) ///< Split machine functions using profile information.
+ENUM_CODEGENOPT(FunctionSplitting, FunctionSplittingMode, 2,
+                FunctionSplittingMode::Default, Benign) ///< Late function splitting mode.
 CODEGENOPT(PartitionStaticDataSections, 1, 0, Benign) /// < Partition static data sections using profile information.
 CODEGENOPT(PPCUseFullRegisterNames, 1, 0, Benign) ///< Print full register names in assembly
 CODEGENOPT(X86RelaxRelocations, 1, 1, Benign) ///< -Wa,-mrelax-relocations={yes,no}
diff --git a/clang/include/clang/Basic/CodeGenOptions.h b/clang/include/clang/Basic/CodeGenOptions.h
index 17f367bc02607f..9649205ee33ffe 100644
--- a/clang/include/clang/Basic/CodeGenOptions.h
+++ b/clang/include/clang/Basic/CodeGenOptions.h
@@ -78,6 +78,7 @@ class CodeGenOptionsBase {
       llvm::AsanDetectStackUseAfterReturnMode;
   using AsanDtorKind = llvm::AsanDtorKind;
   using VectorLibrary = llvm::driver::VectorLibrary;
+  using FunctionSplittingMode = llvm::FunctionSplittingMode;
   using ZeroCallUsedRegsKind = llvm::ZeroCallUsedRegs::ZeroCallUsedRegsKind;
   using WinX64EHUnwindMode = llvm::WinX64EHUnwindMode;
   using ControlFlowGuardMechanism = llvm::ControlFlowGuardMechanism;
diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td
index d1d33e9bb6a95e..5acb64694938df 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -5069,11 +5069,21 @@ defm separate_named_sections : BoolFOption<"separate-named-sections",
           "Use separate unique sections for named sections (ELF Only)">,
   NegFlag<SetFalse>>;
 
-defm split_machine_functions: BoolFOption<"split-machine-functions",
-  CodeGenOpts<"SplitMachineFunctions">, DefaultFalse,
-  PosFlag<SetTrue, [], [ClangOption, CC1Option, FlangOption, FC1Option], "Enable">,
-  NegFlag<SetFalse, [], [ClangOption, FlangOption], "Disable">,
-  BothFlags<[], [ClangOption], " late function splitting using profile information (x86 and aarch64 ELF)">>;
+def fsplit_machine_functions_EQ : Joined<["-"], "fsplit-machine-functions=">,
+  Visibility<[ClangOption, CC1Option, FlangOption, FC1Option]>, Group<f_Group>,
+  HelpText<"Late function splitting mode (x86 and aarch64 ELF)">,
+  Values<"none,bbsections,all">,
+  NormalizedValuesScope<"llvm::FunctionSplittingMode">,
+  NormalizedValues<["None", "BBSectionsOnly", "All"]>,
+  MarshallingInfoEnum<CodeGenOpts<"FunctionSplitting">, "Default">;
+def fsplit_machine_functions : Flag<["-"], "fsplit-machine-functions">,
+  Visibility<[ClangOption, CC1Option, FlangOption, FC1Option]>, Group<f_Group>,
+  Alias<fsplit_machine_functions_EQ>, AliasArgs<["all"]>,
+  HelpText<"Enable late function splitting using profile information (x86 and aarch64 ELF)">;
+def fno_split_machine_functions : Flag<["-"], "fno-split-machine-functions">,
+  Visibility<[ClangOption, FlangOption]>, Group<f_Group>,
+  Alias<fsplit_machine_functions_EQ>, AliasArgs<["none"]>,
+  HelpText<"Disable late function splitting using profile information (x86 and aarch64 ELF)">;
 
 defm partition_static_data_sections: BoolFOption<"partition-static-data-sections",
   CodeGenOpts<"PartitionStaticDataSections">, DefaultFalse,
diff --git a/clang/lib/CodeGen/BackendUtil.cpp b/clang/lib/CodeGen/BackendUtil.cpp
index 6aa6bc1bd41e8c..e0ca4739675d86 100644
--- a/clang/lib/CodeGen/BackendUtil.cpp
+++ b/clang/lib/CodeGen/BackendUtil.cpp
@@ -449,7 +449,7 @@ static bool initTargetOptions(const CompilerInstance &CI,
     Options.BBSectionsFuncListBuf = std::move(*MBOrErr);
   }
 
-  Options.EnableMachineFunctionSplitter = CodeGenOpts.SplitMachineFunctions;
+  Options.FunctionSplitting = CodeGenOpts.getFunctionSplitting();
   Options.EnableStaticDataPartitioning =
       CodeGenOpts.PartitionStaticDataSections;
   Options.FunctionSections = CodeGenOpts.FunctionSections;
diff --git a/clang/lib/Driver/ToolChains/CommonArgs.cpp b/clang/lib/Driver/ToolChains/CommonArgs.cpp
index 74e27bf8b9cdec..fd1d432a3e3d50 100644
--- a/clang/lib/Driver/ToolChains/CommonArgs.cpp
+++ b/clang/lib/Driver/ToolChains/CommonArgs.cpp
@@ -1301,9 +1301,11 @@ void tools::addLTOOptions(const ToolChain &ToolChain, const ArgList &Args,
   }
 
   // Pass an option to enable split machine functions.
-  if (auto *A = Args.getLastArg(options::OPT_fsplit_machine_functions,
-                                options::OPT_fno_split_machine_functions)) {
-    if (A->getOption().matches(options::OPT_fsplit_machine_functions))
+  // TODO: Forward the mode itself (-plugin-opt=-function-splitting=<mode>)
+  // once the LTO plumbing for the new modes is in place. Until then only the
+  // legacy "split everywhere" behavior can be requested through LTO.
+  if (auto *A = Args.getLastArg(options::OPT_fsplit_machine_functions_EQ)) {
+    if (StringRef(A->getValue()) == "all")
       CmdArgs.push_back(Args.MakeArgString(Twine(PluginOptPrefix) +
                                            "-split-machine-functions"));
   }
@@ -3090,17 +3092,18 @@ void tools::addSplitMachineFunctionsArgs(const Driver &D,
                                          const llvm::opt::ArgList &Args,
                                          llvm::opt::ArgStringList &CmdArgs,
                                          const llvm::Triple &Triple) {
-  if (Arg *A = Args.getLastArg(options::OPT_fsplit_machine_functions,
-                               options::OPT_fno_split_machine_functions)) {
-    if (!A->getOption().matches(options::OPT_fno_split_machine_functions)) {
-      // This codegen pass is only available on x86 and AArch64 ELF targets.
-      if ((Triple.isX86() || Triple.isAArch64()) && Triple.isOSBinFormatELF())
-        A->render(Args, CmdArgs);
-      else
-        D.Diag(diag::err_drv_unsupported_opt_for_target)
-            << A->getAsString(Args) << Triple.getTriple();
-    }
+  Arg *A = Args.getLastArg(options::OPT_fsplit_machine_functions_EQ);
+  if (!A)
+    return;
+  // Disabling splitting is always supported, everything else is a codegen pass
+  // which is only available on x86 and AArch64 ELF targets.
+  if (StringRef(A->getValue()) != "none" &&
+      !((Triple.isX86() || Triple.isAArch64()) && Triple.isOSBinFormatELF())) {
+    D.Diag(diag::err_drv_unsupported_opt_for_target)
+        << A->getAsString(Args) << Triple.getTriple();
+    return;
   }
+  A->render(Args, CmdArgs);
 }
 
 void tools::addOpenMPDeviceRTL(const Driver &D,
diff --git a/clang/test/Driver/fsplit-machine-functions.c b/clang/test/Driver/fsplit-machine-functions.c
index 153aafba8fbd00..6f16ab36ea879d 100644
--- a/clang/test/Driver/fsplit-machine-functions.c
+++ b/clang/test/Driver/fsplit-machine-functions.c
@@ -5,11 +5,29 @@
 // RUN: %clang -### --target=aarch64 -fprofile-use=%t/default.profdata -fsplit-machine-functions %s 2>&1 | FileCheck %s --check-prefixes=CHECK,LTO-NEG
 // RUN: %clang -### --target=x86_64 -fprofile-use=%t/default.profdata -fsplit-machine-functions -fno-split-machine-functions %s -c 2>&1 | FileCheck -check-prefix=NEG %s
 
-// CHECK:      "-fsplit-machine-functions"
+// CHECK:      "-fsplit-machine-functions=all"
 // CHECK-SAME: "-fprofile-instrument-use=llvm"
 // CHECK-SAME: "-fprofile-instrument-use-path={{.*}}default.profdata"
 
-// NEG-NOT:    "-fsplit-machine-functions"
+// NEG-NOT:    "-fsplit-machine-functions=all"
+// NEG:        "-fsplit-machine-functions=none"
+
+/// The mode spellings are forwarded to -cc1 verbatim and the last one wins.
+// RUN: %clang -### -c --target=x86_64 -fsplit-machine-functions=bbsections %s 2>&1 | FileCheck %s --check-prefix=BBSECTIONS
+// RUN: %clang -### -c --target=x86_64 -fsplit-machine-functions -fsplit-machine-functions=bbsections %s 2>&1 | FileCheck %s --check-prefix=BBSECTIONS
+// BBSECTIONS: "-fsplit-machine-functions=bbsections"
+
+// RUN: %clang -### -c --target=x86_64 -fsplit-machine-functions=none %s 2>&1 | FileCheck %s --check-prefix=NONE
+// RUN: %clang -### -c --target=x86_64 -fsplit-machine-functions -fno-split-machine-functions %s 2>&1 | FileCheck %s --check-prefix=NONE
+// NONE: "-fsplit-machine-functions=none"
+
+// RUN: %clang -### -c --target=x86_64 -fno-split-machine-functions -fsplit-machine-functions %s 2>&1 | FileCheck %s --check-prefix=ALL
+// RUN: %clang -### -c --target=x86_64 -fsplit-machine-functions=all %s 2>&1 | FileCheck %s --check-prefix=ALL
+// ALL: "-fsplit-machine-functions=all"
+
+/// Nothing is forwarded when the option is absent.
+// RUN: %clang -### -c --target=x86_64 %s 2>&1 | FileCheck %s --check-prefix=ABSENT
+// ABSENT-NOT: "-fsplit-machine-functions
 
 // RUN: %clang -### --target=x86_64-linux -flto -fsplit-machine-functions %s 2>&1 | FileCheck %s --check-prefix=LTO
 // RUN: %clang -### --target=x86_64-linux -flto -fsplit-machine-functions -fno-split-machine-functions %s 2>&1 | FileCheck %s --check-prefix=LTO-NEG
@@ -18,6 +36,10 @@
 // LTO-NEG-NOT: "-plugin-opt=-split-machine-functions"
 
 // RUN: not %clang -### -c --target=arm-unknown-linux -fsplit-machine-functions %s 2>&1 | FileCheck %s --check-prefix=ERR
+// RUN: not %clang -### -c --target=arm-unknown-linux -fsplit-machine-functions=bbsections %s 2>&1 | FileCheck %s --check-prefix=ERR-BBSECTIONS
 // ERR: error: unsupported option '-fsplit-machine-functions' for target
+// ERR-BBSECTIONS: error: unsupported option '-fsplit-machine-functions=bbsections' for target
 
+/// Disabling splitting is supported on every target.
 // RUN: %clang -### --target=arm-unknown-linux -fsplit-machine-functions -fno-split-machine-functions %s
+// RUN: %clang -### -c --target=arm-unknown-linux -fno-split-machine-functions %s
diff --git a/clang/test/Frontend/fsplit-machine-functions.c b/clang/test/Frontend/fsplit-machine-functions.c
new file mode 100644
index 00000000000000..c6420a4d0c814e
--- /dev/null
+++ b/clang/test/Frontend/fsplit-machine-functions.c
@@ -0,0 +1,11 @@
+/// -cc1 accepts every late function splitting mode and rejects unknown ones.
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fsplit-machine-functions=none -emit-llvm -o /dev/null %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fsplit-machine-functions=bbsections -emit-llvm -o /dev/null %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fsplit-machine-functions=all -emit-llvm -o /dev/null %s
+/// The legacy spelling is an alias for '=all'.
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fsplit-machine-functions -emit-llvm -o /dev/null %s
+
+// RUN: not %clang_cc1 -triple x86_64-unknown-linux-gnu -fsplit-machine-functions=bogus -emit-llvm -o /dev/null %s 2>&1 | FileCheck %s
+// CHECK: invalid value 'bogus' in '-fsplit-machine-functions=bogus'
+
+void f(void) {}
diff --git a/flang/include/flang/Frontend/TargetOptions.h b/flang/include/flang/Frontend/TargetOptions.h
index fc7a2a3089f150..43b4342ebe8bab 100644
--- a/flang/include/flang/Frontend/TargetOptions.h
+++ b/flang/include/flang/Frontend/TargetOptions.h
@@ -18,6 +18,7 @@
 #ifndef FORTRAN_FRONTEND_TARGETOPTIONS_H
 #define FORTRAN_FRONTEND_TARGETOPTIONS_H
 
+#include "llvm/Target/TargetOptions.h"
 #include <string>
 #include <vector>
 
@@ -54,8 +55,9 @@ class TargetOptions {
   /// Print verbose assembly
   bool asmVerbose = false;
 
-  /// Enable splitting of machine functions using profile information.
-  bool SplitMachineFunctions = false;
+  /// Late function splitting mode.
+  llvm::FunctionSplittingMode functionSplitting =
+      llvm::FunctionSplittingMode::Default;
 
   /// Atomic control options
   bool atomicIgnoreDenormalMode = false;
diff --git a/flang/lib/Frontend/CompilerInstance.cpp b/flang/lib/Frontend/CompilerInstance.cpp
index 2416d3b5934813..1a35d1a1da5891 100644
--- a/flang/lib/Frontend/CompilerInstance.cpp
+++ b/flang/lib/Frontend/CompilerInstance.cpp
@@ -381,7 +381,7 @@ bool CompilerInstance::setUpTargetMachine() {
 
   llvm::TargetOptions tOpts = llvm::TargetOptions();
   tOpts.EnableAIXExtendedAltivecABI = targetOpts.EnableAIXExtendedAltivecABI;
-  tOpts.EnableMachineFunctionSplitter = targetOpts.SplitMachineFunctions;
+  tOpts.FunctionSplitting = targetOpts.functionSplitting;
   tOpts.VecLib = convertDriverVectorLibraryToVectorLibrary(CGOpts.getVecLib());
   tOpts.DisableIntegratedAS = CGOpts.DisableIntegratedAS;
   tOpts.FunctionSections = CGOpts.FunctionSections;
diff --git a/flang/lib/Frontend/CompilerInvocation.cpp b/flang/lib/Frontend/CompilerInvocation.cpp
index 6c89570920c461..44810ce4b63c1b 100644
--- a/flang/lib/Frontend/CompilerInvocation.cpp
+++ b/flang/lib/Frontend/CompilerInvocation.cpp
@@ -618,8 +618,14 @@ static void parseTargetArgs(TargetOptions &opts, llvm::opt::ArgList &args) {
     }
   }
 
-  opts.SplitMachineFunctions =
-      args.hasArg(clang::options::OPT_fsplit_machine_functions);
+  if (const llvm::opt::Arg *a =
+          args.getLastArg(clang::options::OPT_fsplit_machine_functions_EQ))
+    opts.functionSplitting =
+        llvm::StringSwitch<llvm::FunctionSplittingMode>(a->getValue())
+            .Case("none", llvm::FunctionSplittingMode::None)
+            .Case("bbsections", llvm::FunctionSplittingMode::BBSectionsOnly)
+            .Case("all", llvm::FunctionSplittingMode::All)
+            .Default(llvm::FunctionSplittingMode::Default);
 
   opts.asmVerbose = args.hasFlag(clang::options::OPT_fverbose_asm,
                                  clang::options::OPT_fno_verbose_asm, false);
diff --git a/flang/test/Driver/fsplit-machine-functions.f90 b/flang/test/Driver/fsplit-machine-functions.f90
index 2a0ffe61811fc2..f2de6e1d67e2ca 100644
--- a/flang/test/Driver/fsplit-machine-functions.f90
+++ b/flang/test/Driver/fsplit-machine-functions.f90
@@ -8,7 +8,7 @@
 ! RUN: %if arm-registered-target %{ not %flang -### --target=arm-unknown-linux -fsplit-machine-functions %s 2>&1 | FileCheck %s --check-prefix=UNSUPPORTED-OPT %}
 ! RUN: %if arm-registered-target %{ %flang -### --target=arm-unknown-linux -fno-split-machine-functions %s 2>&1 | FileCheck %s --check-prefix=NO-SPLIT-MACHINE-FUNCTIONS %}
 
-! SPLIT-MACHINE-FUNCTIONS: "-fsplit-machine-functions"
-! NO-SPLIT-MACHINE-FUNCTIONS-NOT: "-fsplit-machine-functions"
+! SPLIT-MACHINE-FUNCTIONS: "-fsplit-machine-functions=all"
+! NO-SPLIT-MACHINE-FUNCTIONS-NOT: "-fsplit-machine-functions=all"
 ! UNSUPPORTED-OPT: error: unsupported option '-fsplit-machine-functions' for target
 



More information about the flang-commits mailing list