[llvm] [Support] Accept -no-<name> to negate cl::opt<bool> (PR #224978)

Fangrui Song via llvm-commits llvm-commits at lists.llvm.org
Mon Sep 21 01:22:39 PDT 2026


https://github.com/MaskRay updated https://github.com/llvm/llvm-project/pull/224978

>From 15aceb56828e410fe7898cf5dd386aa9849c8f47 Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Sun, 20 Sep 2026 16:55:19 -0700
Subject: [PATCH 1/3] [Support] Accept -no-<name> to negate cl::opt<bool>

As a longstanding quirk, a boolean option is negated with -<name>=false
or -<name>=0. Most command line libraries spell negation as -no-<name>.
Accept -no-<name> by resolving at lookup time when no option named
no-<name> is registered. This lets tests and scripts move to the
spelling both parsers understand.

In the future, we shall ban `-<name>=false` and `-<name>=0` for boolean
options. See https://discourse.llvm.org/t/91877

LLM-aided
---
 llvm/docs/CommandLine.md                   |  4 ++-
 llvm/include/llvm/Support/CommandLine.h    |  8 +++++
 llvm/lib/Support/CommandLine.cpp           | 12 ++++++-
 llvm/unittests/Support/CommandLineTest.cpp | 37 ++++++++++++++++++++++
 4 files changed, 59 insertions(+), 2 deletions(-)

diff --git a/llvm/docs/CommandLine.md b/llvm/docs/CommandLine.md
index bf5123fdcfa10..4dce7cb5e4b24 100644
--- a/llvm/docs/CommandLine.md
+++ b/llvm/docs/CommandLine.md
@@ -221,13 +221,15 @@ literally into the content of the string variable... we obviously cannot do that
 in the boolean case, however, so we must use a smarter parser.  In the case of
 the boolean parser, it allows no options (in which case it assigns the value of
 true to the variable), or it allows the values "`true`" or "`false`" to be
-specified, allowing any of the following inputs:
+specified; a boolean option can also be negated with a `no-` prefix, allowing
+any of the following inputs:
 
 ```
 compiler -f          # No value, 'Force' == true
 compiler -f=true     # Value specified, 'Force' == true
 compiler -f=TRUE     # Value specified, 'Force' == true
 compiler -f=FALSE    # Value specified, 'Force' == false
+compiler -no-f       # Negated, 'Force' == false
 ```
 
 ... you get the idea.  The {ref}`bool parser <bool parser>` just turns the string values into
diff --git a/llvm/include/llvm/Support/CommandLine.h b/llvm/include/llvm/Support/CommandLine.h
index d74958ba4f463..622eb597c2625 100644
--- a/llvm/include/llvm/Support/CommandLine.h
+++ b/llvm/include/llvm/Support/CommandLine.h
@@ -292,6 +292,9 @@ class LLVM_ABI Option {
     return Value ? ((enum ValueExpected)Value) : getValueExpectedFlagDefault();
   }
 
+  // Whether -no-<ArgStr> is accepted and sets the option to false.
+  virtual bool isNegatable() const { return false; }
+
   inline enum OptionHidden getOptionHiddenFlag() const {
     return (enum OptionHidden)HiddenFlag;
   }
@@ -1481,6 +1484,11 @@ class opt
     return Parser.getValueExpectedFlagDefault();
   }
 
+  bool isNegatable() const override {
+    return std::is_same_v<DataType, bool> ||
+           std::is_same_v<DataType, boolOrDefault>;
+  }
+
   void getExtraOptionNames(SmallVectorImpl<StringRef> &OptionNames) override {
     return Parser.getExtraOptionNames(OptionNames);
   }
diff --git a/llvm/lib/Support/CommandLine.cpp b/llvm/lib/Support/CommandLine.cpp
index f363190bca1ce..cd9341e3e5706 100644
--- a/llvm/lib/Support/CommandLine.cpp
+++ b/llvm/lib/Support/CommandLine.cpp
@@ -561,7 +561,17 @@ Option *CommandLineParser::LookupOption(SubCommand &Sub, StringRef &Arg,
   // If we have an equals sign, remember the value.
   if (EqualPos == StringRef::npos) {
     // Look up the option.
-    return Sub.OptionsMap.lookup(Arg);
+    if (Option *O = Sub.OptionsMap.lookup(Arg))
+      return O;
+    // -no-<name> negates the boolean option <name>.
+    StringRef Name = Arg;
+    if (!Name.consume_front("no-"))
+      return nullptr;
+    Option *O = Sub.OptionsMap.lookup(Name);
+    if (!O || !O->isNegatable())
+      return nullptr;
+    Value = "false";
+    return O;
   }
 
   // If the argument before the = is a valid option name and the option allows
diff --git a/llvm/unittests/Support/CommandLineTest.cpp b/llvm/unittests/Support/CommandLineTest.cpp
index 956d5b97c2703..c45ed339a93c1 100644
--- a/llvm/unittests/Support/CommandLineTest.cpp
+++ b/llvm/unittests/Support/CommandLineTest.cpp
@@ -2187,6 +2187,43 @@ TEST(CommandLineTest, ConsumeAfterTwoPositionals) {
   EXPECT_TRUE(Errs.empty());
 }
 
+TEST(CommandLineTest, BoolNegation) {
+  cl::ResetCommandLineParser();
+
+  StackOption<bool> OptFlag("flag", cl::init(true));
+  StackOption<cl::boolOrDefault> OptBod("bod");
+  StackOption<std::string> OptStr("str");
+  // A registered no-<name> takes precedence over negating <name>.
+  StackOption<bool> OptExplicit("explicit", cl::init(true));
+  StackOption<bool> OptNoExplicit("no-explicit");
+
+  const char *args1[] = {"prog", "-no-flag", "--no-bod", "-no-explicit"};
+  EXPECT_TRUE(
+      cl::ParseCommandLineOptions(4, args1, StringRef(), &llvm::nulls()));
+  EXPECT_FALSE(OptFlag);
+  EXPECT_EQ(cl::boolOrDefault::BOU_FALSE, OptBod);
+  EXPECT_TRUE(OptExplicit);
+  EXPECT_TRUE(OptNoExplicit);
+  cl::ResetAllOptionOccurrences();
+
+  // The last occurrence wins.
+  const char *args2[] = {"prog", "-no-flag", "-flag"};
+  EXPECT_TRUE(
+      cl::ParseCommandLineOptions(3, args2, StringRef(), &llvm::nulls()));
+  EXPECT_TRUE(OptFlag);
+  cl::ResetAllOptionOccurrences();
+
+  // Only boolean options are negatable, and the negated form takes no value.
+  const char *args3[] = {"prog", "-no-str"};
+  EXPECT_FALSE(
+      cl::ParseCommandLineOptions(2, args3, StringRef(), &llvm::nulls()));
+  cl::ResetAllOptionOccurrences();
+
+  const char *args4[] = {"prog", "-no-flag=false"};
+  EXPECT_FALSE(
+      cl::ParseCommandLineOptions(2, args4, StringRef(), &llvm::nulls()));
+}
+
 TEST(CommandLineTest, ConsumeOptionalString) {
   cl::ResetCommandLineParser();
 

>From 7761ec6e06e33e9da2b63cb54155f451e68fea21 Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Sun, 20 Sep 2026 17:45:19 -0700
Subject: [PATCH 2/3] update a few lit tests for demonstration

---
 llvm/lib/CodeGen/StackSlotColoring.cpp             | 7 +++----
 llvm/lib/Target/X86/X86CallFrameOptimization.cpp   | 9 ++++-----
 llvm/test/Transforms/InstCombine/fputs-opt-size.ll | 2 +-
 llvm/test/Transforms/SimplifyCFG/switch_undef.ll   | 2 +-
 4 files changed, 9 insertions(+), 11 deletions(-)

diff --git a/llvm/lib/CodeGen/StackSlotColoring.cpp b/llvm/lib/CodeGen/StackSlotColoring.cpp
index 4bc74814d6fc4..41131ac3680ee 100644
--- a/llvm/lib/CodeGen/StackSlotColoring.cpp
+++ b/llvm/lib/CodeGen/StackSlotColoring.cpp
@@ -50,9 +50,8 @@ using namespace llvm;
 #define DEBUG_TYPE "stack-slot-coloring"
 
 static cl::opt<bool>
-DisableSharing("no-stack-slot-sharing",
-             cl::init(false), cl::Hidden,
-             cl::desc("Suppress slot sharing during stack coloring"));
+    EnableSharing("stack-slot-sharing", cl::init(true), cl::Hidden,
+                  cl::desc("Share stack slots during stack coloring"));
 
 static cl::opt<int> DCELimit("ssc-dce-limit", cl::init(-1), cl::Hidden);
 
@@ -312,7 +311,7 @@ int StackSlotColoring::ColorSlot(LiveInterval *li) {
   int FI = li->reg().stackSlotIndex();
   uint8_t StackID = MFI->getStackID(FI);
 
-  if (!DisableSharing) {
+  if (EnableSharing) {
 
     // Check if it's possible to reuse any of the used colors.
     Color = UsedColors[StackID].find_first();
diff --git a/llvm/lib/Target/X86/X86CallFrameOptimization.cpp b/llvm/lib/Target/X86/X86CallFrameOptimization.cpp
index 4966ec2fe8dc9..2ef586a76963b 100644
--- a/llvm/lib/Target/X86/X86CallFrameOptimization.cpp
+++ b/llvm/lib/Target/X86/X86CallFrameOptimization.cpp
@@ -52,10 +52,9 @@ using namespace llvm;
 
 #define DEBUG_TYPE "x86-cf-opt"
 
-static cl::opt<bool>
-    NoX86CFOpt("no-x86-call-frame-opt",
-               cl::desc("Avoid optimizing x86 call frames for size"),
-               cl::init(false), cl::Hidden);
+static cl::opt<bool> X86CFOpt("x86-call-frame-opt",
+                              cl::desc("Optimize x86 call frames for size"),
+                              cl::init(true), cl::Hidden);
 
 namespace {
 
@@ -145,7 +144,7 @@ INITIALIZE_PASS(X86CallFrameOptimizationLegacy, DEBUG_TYPE,
 // Also returns false in cases where it's potentially legal, but
 // we don't even want to try.
 bool X86CallFrameOptimizationImpl::isLegal(MachineFunction &MF) {
-  if (NoX86CFOpt.getValue())
+  if (!X86CFOpt)
     return false;
 
   // We can't encode multiple DW_CFA_GNU_args_size or DW_CFA_def_cfa_offset
diff --git a/llvm/test/Transforms/InstCombine/fputs-opt-size.ll b/llvm/test/Transforms/InstCombine/fputs-opt-size.ll
index 2584320178c9a..92b7277837176 100644
--- a/llvm/test/Transforms/InstCombine/fputs-opt-size.ll
+++ b/llvm/test/Transforms/InstCombine/fputs-opt-size.ll
@@ -3,7 +3,7 @@
 ;
 ; RUN: opt < %s -passes=instcombine -S | FileCheck %s
 ; RUN: opt < %s -passes='require<profile-summary>,function(instcombine)' -pgso -S | FileCheck %s -check-prefix=PGSO
-; RUN: opt < %s -passes=instcombine -pgso=false -S | FileCheck %s -check-prefix=NPGSO
+; RUN: opt < %s -passes=instcombine -no-pgso -S | FileCheck %s -check-prefix=NPGSO
 
 %struct._IO_FILE = type { i32, ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr, i32, i32, i32, i16, i8, [1 x i8], ptr, i64, ptr, ptr, ptr, ptr, i32, i32, [40 x i8] }
 %struct._IO_marker = type { ptr, ptr, i32 }
diff --git a/llvm/test/Transforms/SimplifyCFG/switch_undef.ll b/llvm/test/Transforms/SimplifyCFG/switch_undef.ll
index 4de5ea948ed27..0a64e51123886 100644
--- a/llvm/test/Transforms/SimplifyCFG/switch_undef.ll
+++ b/llvm/test/Transforms/SimplifyCFG/switch_undef.ll
@@ -1,5 +1,5 @@
 ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
-; RUN: opt %s -keep-loops=false -switch-to-lookup=true -passes=simplifycfg -simplifycfg-require-and-preserve-domtree=1 -S | FileCheck %s
+; RUN: opt %s -no-keep-loops -switch-to-lookup=true -passes=simplifycfg -simplifycfg-require-and-preserve-domtree=1 -S | FileCheck %s
 ; RUN: opt %s -passes='simplifycfg<no-keep-loops;switch-to-lookup>' -S | FileCheck %s
 
 define void @f6() #0 {

>From fd838d5133947dbca128ccf8f31b1973bca3fc8b Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Mon, 21 Sep 2026 01:22:25 -0700
Subject: [PATCH 3/3] remove redundant noLeadingLines from split-file

---
 llvm/test/tools/split-file/help.test | 2 +-
 llvm/utils/split-file/split-file.cpp | 4 ----
 2 files changed, 1 insertion(+), 5 deletions(-)

diff --git a/llvm/test/tools/split-file/help.test b/llvm/test/tools/split-file/help.test
index 27c450aeac3a7..511a4343293d2 100644
--- a/llvm/test/tools/split-file/help.test
+++ b/llvm/test/tools/split-file/help.test
@@ -3,4 +3,4 @@ CHECK: OVERVIEW: Split input {{.*}}
 CHECK: USAGE: split-file [options] filename directory
 CHECK: Generic Options:
 CHECK: split-file Options:
-CHECK:   --no-leading-lines
+CHECK:   --leading-lines
diff --git a/llvm/utils/split-file/split-file.cpp b/llvm/utils/split-file/split-file.cpp
index c7e9ff4b2b682..7ccfde4cff883 100644
--- a/llvm/utils/split-file/split-file.cpp
+++ b/llvm/utils/split-file/split-file.cpp
@@ -39,10 +39,6 @@ static cl::opt<bool> leadingLines("leading-lines",
                                     cl::desc("Preserve line numbers"),
                                     cl::cat(cat));
 
-static cl::opt<bool> noLeadingLines("no-leading-lines",
-                                    cl::desc("Don't preserve line numbers (default)"),
-                                    cl::cat(cat));
-
 static StringRef toolName;
 static int errorCount;
 



More information about the llvm-commits mailing list