[llvm] [CodeGen] Add a FunctionSplittingMode target option (PR #226575)
Rahman Lavaee via llvm-commits
llvm-commits at lists.llvm.org
Tue Sep 29 11:58:05 PDT 2026
https://github.com/rlavaee updated https://github.com/llvm/llvm-project/pull/226575
>From 87569405a7c61ae2e7748c2c574b46127636c7d0 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] [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 ++---
.../Generic/machine-function-splitter.ll | 14 +++-
.../CodeGen/X86/function-splitting-mode.ll | 68 +++++++++++++++++++
8 files changed, 158 insertions(+), 11 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 25eaf5c32d743..0a64aee495441 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 1a5d44570430f..3a63063b0f137 100644
--- a/llvm/include/llvm/CodeGen/CommandFlags.h
+++ b/llvm/include/llvm/CodeGen/CommandFlags.h
@@ -91,6 +91,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 7ebc0e0e9ed1e..b3c7a5d9f689d 100644
--- a/llvm/include/llvm/Target/TargetOptions.h
+++ b/llvm/include/llvm/Target/TargetOptions.h
@@ -50,6 +50,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
@@ -232,6 +247,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 9972d431aae8b..2fba0503534a7 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 1bebecda62166..fac84780d669d 100644
--- a/llvm/lib/CodeGen/CommandFlags.cpp
+++ b/llvm/lib/CodeGen/CommandFlags.cpp
@@ -92,6 +92,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)
@@ -439,6 +440,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."),
@@ -551,6 +567,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 ac9dc278dafcf..4fa8e16ef932d 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"
@@ -263,12 +264,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,
@@ -1270,8 +1265,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()) {
@@ -1294,8 +1291,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/Generic/machine-function-splitter.ll b/llvm/test/CodeGen/Generic/machine-function-splitter.ll
index d798b2875645b..d0cf1ebc2351e 100644
--- a/llvm/test/CodeGen/Generic/machine-function-splitter.ll
+++ b/llvm/test/CodeGen/Generic/machine-function-splitter.ll
@@ -15,7 +15,9 @@
; RUN: echo 'ffoo22' >> %t
; RUN: echo 'c0 1' >> %t
; RUN: echo 'c2' >> %t
-; RUN: llc < %s -mtriple=x86_64-unknown-linux-gnu -basic-block-sections=%t -split-machine-functions | FileCheck %s --check-prefixes=MFS-BBSECTIONS
+; RUN: llc < %s -mtriple=x86_64-unknown-linux-gnu -basic-block-sections=%t -split-machine-functions | FileCheck %s --check-prefixes=MFS-BBSECTIONS,MFS-BBSECTIONS-ALL
+; RUN: llc < %s -mtriple=x86_64-unknown-linux-gnu -basic-block-sections=%t -function-splitting=all | FileCheck %s --check-prefixes=MFS-BBSECTIONS,MFS-BBSECTIONS-ALL
+; RUN: llc < %s -mtriple=x86_64-unknown-linux-gnu -basic-block-sections=%t -function-splitting=bbsections | FileCheck %s --check-prefixes=MFS-BBSECTIONS,MFS-BBSECTIONS-ONLY
; RUN: llc < %s -mtriple=aarch64-unknown-linux-gnu -aarch64-min-jump-table-entries=4 -enable-split-machine-functions | FileCheck %s -check-prefixes=MFS-DEFAULTS,MFS-DEFAULTS-AARCH64
; RUN: llc < %s -mtriple=aarch64-unknown-linux-gnu -aarch64-min-jump-table-entries=4 -enable-split-machine-functions -mfs-psi-cutoff=0 -mfs-count-threshold=2000 | FileCheck %s --dump-input=always -check-prefixes=MFS-OPTS1,MFS-OPTS1-AARCH64
@@ -38,6 +40,16 @@ define void @foo1(i1 zeroext %0) nounwind !prof !14 !section_prefix !15 {
; MFS-DEFAULTS-X86-NEXT: callq baz
; MFS-DEFAULTS-AARCH64-NOT: bl bar
; MFS-DEFAULTS-AARCH64-NEXT: bl baz
+;; Check that with a basic-block-sections profile, a function outside the
+;; profile is still split using PGO with -function-splitting=all (and the
+;; legacy -split-machine-functions).
+; MFS-BBSECTIONS-ALL-LABEL: foo1:
+; MFS-BBSECTIONS-ALL: .section .text.split.foo1
+; MFS-BBSECTIONS-ALL-NEXT: foo1.cold:
+;; Check that with -function-splitting=bbsections, a function outside the
+;; basic-block-sections profile is not split, even though it has PGO data.
+; MFS-BBSECTIONS-ONLY-LABEL: foo1:
+; MFS-BBSECTIONS-ONLY-NOT: foo1.cold:
br i1 %0, label %2, label %4, !prof !17
2: ; preds = %1
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 0000000000000..07519c1579031
--- /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}
More information about the llvm-commits
mailing list