Skip to content

[clang] add -fimplicit-constexpr flag #136436

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

hanickadot
Copy link
Contributor

Same as GCC's flag of the same name. If a function (constructor / destructor) doesn't have a constexpr nor consteval specifier. It will implicitly add constexpr.

This change doesn't touch variables. Only functions.

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:driver 'clang' and 'clang++' user-facing binaries. Not 'clang-cl' clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Apr 19, 2025
@llvmbot
Copy link
Member

llvmbot commented Apr 19, 2025

@llvm/pr-subscribers-clang

@llvm/pr-subscribers-clang-driver

Author: Hana Dusíková (hanickadot)

Changes

Same as GCC's flag of the same name. If a function (constructor / destructor) doesn't have a constexpr nor consteval specifier. It will implicitly add constexpr.

This change doesn't touch variables. Only functions.


Full diff: https://github.com/llvm/llvm-project/pull/136436.diff

7 Files Affected:

  • (modified) clang/include/clang/Basic/LangOptions.def (+1)
  • (modified) clang/include/clang/Driver/Options.td (+6)
  • (modified) clang/include/clang/Sema/Sema.h (+1)
  • (modified) clang/lib/Driver/ToolChains/Clang.cpp (+2)
  • (modified) clang/lib/Sema/CMakeLists.txt (+1)
  • (modified) clang/lib/Sema/SemaDecl.cpp (+10)
  • (added) clang/test/Sema/implicit-constexpr.cpp (+51)
diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def
index 930c1c06d1a76..97527f7a0d1f3 100644
--- a/clang/include/clang/Basic/LangOptions.def
+++ b/clang/include/clang/Basic/LangOptions.def
@@ -413,6 +413,7 @@ BENIGN_LANGOPT(ArrowDepth, 32, 256,
                "maximum number of operator->s to follow")
 BENIGN_LANGOPT(InstantiationDepth, 32, 1024,
                "maximum template instantiation depth")
+LANGOPT(ImplicitConstexpr, 1, 0, "evaluate everything as it is a constexpr enabled")
 BENIGN_LANGOPT(ConstexprCallDepth, 32, 512,
                "maximum constexpr call depth")
 BENIGN_LANGOPT(ConstexprStepLimit, 32, 1048576,
diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td
index 919c1c643d080..877235147a044 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -1991,6 +1991,12 @@ defm constant_cfstrings : BoolFOption<"constant-cfstrings",
           "Disable creation of CodeFoundation-type constant strings">,
   PosFlag<SetFalse>>;
 def fconstant_string_class_EQ : Joined<["-"], "fconstant-string-class=">, Group<f_Group>;
+def fimplicit_constexpr
+    : Joined<["-"], "fimplicit-constexpr">,
+      Group<f_Group>,
+      Visibility<[ClangOption, CC1Option]>,
+      HelpText<"All function declarations will be implicitly constexpr.">,
+      MarshallingInfoFlag<LangOpts<"ImplicitConstexpr">>;
 def fconstexpr_depth_EQ : Joined<["-"], "fconstexpr-depth=">, Group<f_Group>,
   Visibility<[ClangOption, CC1Option]>,
   HelpText<"Set the maximum depth of recursive constexpr function calls">,
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 96d81e618494a..603a44bbba910 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -2826,6 +2826,7 @@ class Sema final : public SemaBase {
   bool buildCoroutineParameterMoves(SourceLocation Loc);
   VarDecl *buildCoroutinePromise(SourceLocation Loc);
   void CheckCompletedCoroutineBody(FunctionDecl *FD, Stmt *&Body);
+  void AnalyseCoroutineBody(const CoroutineBodyStmt *);
 
   // As a clang extension, enforces that a non-coroutine function must be marked
   // with [[clang::coro_wrapper]] if it returns a type marked with
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index f2f5231933c88..27baf602e2703 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -6612,6 +6612,8 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
   Args.AddLastArg(CmdArgs, options::OPT_fconstexpr_depth_EQ);
   Args.AddLastArg(CmdArgs, options::OPT_fconstexpr_steps_EQ);
 
+  Args.AddLastArg(CmdArgs, options::OPT_fimplicit_constexpr);
+
   Args.AddLastArg(CmdArgs, options::OPT_fexperimental_library);
 
   if (Args.hasArg(options::OPT_fexperimental_new_constant_interpreter))
diff --git a/clang/lib/Sema/CMakeLists.txt b/clang/lib/Sema/CMakeLists.txt
index 4b87004e4b8ea..c0e916440a5fd 100644
--- a/clang/lib/Sema/CMakeLists.txt
+++ b/clang/lib/Sema/CMakeLists.txt
@@ -46,6 +46,7 @@ add_clang_library(clangSema
   SemaConcept.cpp
   SemaConsumer.cpp
   SemaCoroutine.cpp
+  SemaCoroutineFlow.cpp
   SemaCUDA.cpp
   SemaDirectX.cpp
   SemaDecl.cpp
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 46933c5c43168..1d4328db4dc41 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -10163,6 +10163,16 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
     }
 
     ConstexprSpecKind ConstexprKind = D.getDeclSpec().getConstexprSpecifier();
+
+    // Clang's option -fimplicit-constexpr will make functions without constexpr
+    // or consteval specifier implicitly constexpr (compatibility with GCC.)
+    if (ConstexprKind == ConstexprSpecKind::Unspecified &&
+        getLangOpts().ImplicitConstexpr) {
+
+      NewFD->setConstexprKind(ConstexprSpecKind::Constexpr);
+      ConstexprKind = ConstexprSpecKind::Constexpr;
+    }
+
     if (ConstexprKind != ConstexprSpecKind::Unspecified) {
       // C++11 [dcl.constexpr]p2: constexpr functions and constexpr constructors
       // are implicitly inline.
diff --git a/clang/test/Sema/implicit-constexpr.cpp b/clang/test/Sema/implicit-constexpr.cpp
new file mode 100644
index 0000000000000..4b1314c342361
--- /dev/null
+++ b/clang/test/Sema/implicit-constexpr.cpp
@@ -0,0 +1,51 @@
+// RUN: %clang_cc1 -verify=normal,both -std=c++23 %s
+// RUN: %clang_cc1 -verify=implicit,both -fimplicit-constexpr -std=c++23 %s
+
+bool function_test() { // normal-note+ {{declared here}}
+	return true;
+}
+
+// const variables should work as before
+const auto cvar = function_test();
+
+constexpr auto constexpr_var = function_test(); // normal-error {{constexpr variable 'constexpr_var' must be initialized by a constant expression}}
+// normal-note@-1 {{non-constexpr function 'function_test' cannot be used in a constant expression}}
+
+static_assert(function_test()); // normal-error {{static assertion expression is not an integral constant expression}}
+// normal-note@-1 {{non-constexpr function 'function_test' cannot be used in a constant expression}}
+
+
+struct full_nonconstexpr_type {
+	full_nonconstexpr_type() { } // normal-note {{declared here}}
+  ~full_nonconstexpr_type() { }
+};
+
+constexpr bool constructor_destructor_test() {
+  full_nonconstexpr_type var{}; // normal-note {{non-constexpr constructor 'full_nonconstexpr_type' cannot be used in a constant expression}}
+  return true;
+}
+
+static_assert(constructor_destructor_test()); // normal-error {{static assertion expression is not an integral constant expression}}
+// normal-note@-1 {{in call to 'constructor_destructor_test()'}}
+
+
+struct only_destructor_type {
+  constexpr only_destructor_type() { }
+  ~only_destructor_type() { } // normal-note {{declared here}}
+};
+
+constexpr bool destructor_test() {
+  only_destructor_type var{}; // normal-note {{non-constexpr function '~only_destructor_type' cannot be used in a constant expression}}
+  return true;
+}
+
+static_assert(destructor_test()); // normal-error {{static assertion expression is not an integral constant expression}}
+// normal-note@-1 {{in call to 'destructor_test()'}}
+
+
+bool undefined_test(); // both-note {{declared here}}
+
+static_assert(undefined_test()); // both-error {{static assertion expression is not an integral constant expression}}
+// normal-note@-1 {{non-constexpr function 'undefined_test' cannot be used in a constant expressio}}
+// implicit-note@-2 {{undefined function 'undefined_test' cannot be used in a constant expression}}
+

@hanickadot hanickadot force-pushed the feature/implicit-constexpr-flag branch from 697ec9a to b8865c8 Compare April 19, 2025 15:10
@philnik777
Copy link
Contributor

IMO this kind of flag requires an RFC. This is a non-conforming extension and I don't see any rationale for why we'd want to add it.

@hanickadot
Copy link
Contributor Author

My motivation is get better ability to experiment with constexpr-ification of stdlib and other libraries. This proved very useful in GCC. Where quickly check something for constexpr-ness is much easier.

Copy link
Member

@Sirraide Sirraide left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this unfortunately isn’t this simple: I can think of a few problems this runs into and I the solutions I’ve managed to think of are:

First, we could use Sema::CheckConstexprFunctionDefinition() (see also the call to that function in Sema::BuildLambda(), which handles making lambdas implicitly constexpr) to make sure a function is even allowed to be constexpr; however, that needs to happen after we parse the function body.

This, however, might have some unfortunate consequences for function declarations without a body; it would be a bit janky to have to remove the constexpr specifier again once we see the definition and realise it can’t actually be constexpr, but not making it constexpr if there is no body runs into problems with code like:

int foo();
int bar() { return foo(); }
int foo() { return 3; }
static_assert(bar() == 3);

When we encounter bar() here, we can only make it implicitly constexpr if we tread foo() as constexpr as well even though we haven’t seen its body yet and hence can’t actually check whether it can actually be constexpr. GCC accepts this code if we declare foo() and bar() as inline.

An alternative approach might be to just not make functions constexpr in the AST and also never call CheckConstexprFunctionDefinition() for such functions in this mode and instead just allow calling functions that are not declared constexpr if the lang opt is set. That however runs into the problem that the constant evaluator needs to be able to diagnose any invalid constructs it runs into w/o crashing, and I’m not sure it’s currently set up for that given that CheckConstexprFunctionDefinitions() rejects e.g. gotos before we ever get to the constant evaluator. So that definitely requires more tests for all things we might run into there.

@@ -6612,6 +6612,8 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
Args.AddLastArg(CmdArgs, options::OPT_fconstexpr_depth_EQ);
Args.AddLastArg(CmdArgs, options::OPT_fconstexpr_steps_EQ);

Args.AddLastArg(CmdArgs, options::OPT_fimplicit_constexpr);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should issue a driver diagnostic somewhere if someone tries to pass -fimplicit-constexpr if we’re not compiling C++. And we’d need tests for that too then.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added check for c++, but it would be goot to enable for 14+ only (but not sure how to do it in this file, will look at it tomorrow)

@Sirraide
Copy link
Member

Oh and one more thing: this is currently missing a release note.

@Sirraide
Copy link
Member

IMO this kind of flag requires an RFC. This is a non-conforming extension and I don't see any rationale for why we'd want to add it.

I feel like GCC compatibility might be an argument? Imo it depends a bit on how difficult it is to implement the flag, but an RFC to discuss the exact behaviour of it might be warranted (e.g. do we require inline or not—unless we want to just do the exact same thing as GCC).

@Sirraide
Copy link
Member

Oh yeah, and one more thing I forgot about earlier: this definitely need some tests involving templates to make sure that instantiations are also treated as implicitly constexpr.

@Sirraide
Copy link
Member

IMO this kind of flag requires an RFC. This is a non-conforming extension and I don't see any rationale for why we'd want to add it.

I feel like GCC compatibility might be an argument? Imo it depends a bit on how difficult it is to implement the flag, but an RFC to discuss the exact behaviour of it might be warranted (e.g. do we require inline or not—unless we want to just do the exact same thing as GCC).

CC @AaronBallman regarding whether this needs an RFC

@hanickadot hanickadot force-pushed the feature/implicit-constexpr-flag branch from 3853a24 to f37eb75 Compare April 19, 2025 22:24
@hanickadot hanickadot force-pushed the feature/implicit-constexpr-flag branch from 426c4f7 to a486edc Compare April 19, 2025 22:34
@tbaederr
Copy link
Contributor

That however runs into the problem that the constant evaluator needs to be able to diagnose any invalid constructs it runs into w/o crashing, and I’m not sure it’s currently set up for that given that CheckConstexprFunctionDefinitions() rejects e.g. gotos before we ever get to the constant evaluator.

This at least shouldn't be a problem - the constant evaluator already needs to do that when e.g. the goto is only conditionally reachable: https://godbolt.org/z/TdYz4zP5c.

@cor3ntin
Copy link
Contributor

cor3ntin commented Apr 20, 2025

@Sirraide we could do the check at the point of call - and cache the result, which would require storing some state in FunctionDecl. Or just not care. GCC is explicit about not supporting this scenario https://compiler-explorer.com/z/Mdva4f1a1 - and i don't see the value in diverging from GCC

I do agree we should call CheckConstexprFunctionDefinition on implicitly constexpr functions

We also should be careful about the whole consteval propagation thing, which would be affected by this feature.
I think @philnik777, this probably needs an RFC, but it does seem reasonable to me to follow in GCC's footsteps here.

Comment on lines +782 to +784

if (LangOpts.ImplicitConstexpr) // same value as GCC
Builder.defineMacro("__cpp_implicit_constexpr", "20211111");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't usually define macros for non-standard feature. Instead users should use __has_extension(cxx_implicit_constexpr)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is to mirror GCC's behavior, but I'm happy to remove it

@AaronBallman
Copy link
Collaborator

IMO this kind of flag requires an RFC.

+1, this sort of extension requires an RFC. Please be sure to mention how the RFC addresses our usual criteria for new extensions, too.

@llvmbot llvmbot added the clang:bytecode Issues for the clang bytecode constexpr interpreter label Apr 21, 2025
@Sirraide
Copy link
Member

An RFC has been posted on Discourse: https://discourse.llvm.org/t/rfc-fimplicit-constexpr/85963

@shafik shafik self-requested a review April 21, 2025 20:01
@hanickadot hanickadot force-pushed the feature/implicit-constexpr-flag branch from e5bc297 to f2d5bfa Compare April 21, 2025 21:05
@hanickadot hanickadot force-pushed the feature/implicit-constexpr-flag branch from f2d5bfa to 5f388d0 Compare April 21, 2025 21:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:bytecode Issues for the clang bytecode constexpr interpreter clang:driver 'clang' and 'clang++' user-facing binaries. Not 'clang-cl' clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants