Skip to content

[clang-cl] Add support for [[msvc::constexpr]] C++11 attribute #71300

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

Merged
merged 4 commits into from
Dec 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,8 @@ Non-comprehensive list of changes in this release
except that it returns the size of a type ignoring tail padding.
* ``__builtin_classify_type()`` now classifies ``_BitInt`` values as the return value ``18``
and vector types as return value ``19``, to match GCC 14's behavior.
* The default value of `_MSC_VER` was raised from 1920 to 1933.
* Since MSVC 19.33 added undocumented attribute ``[[msvc::constexpr]]``, this release adds the attribute as well.

* Added ``#pragma clang fp reciprocal``.

Expand Down
4 changes: 2 additions & 2 deletions clang/docs/UsersManual.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3359,8 +3359,8 @@ default for Windows targets.

For compatibility with existing code that compiles with MSVC, clang defines the
``_MSC_VER`` and ``_MSC_FULL_VER`` macros. When on Windows, these default to
either the same value as the currently installed version of cl.exe, or ``1920``
and ``192000000`` (respectively). The ``-fms-compatibility-version=`` flag
either the same value as the currently installed version of cl.exe, or ``1933``
and ``193300000`` (respectively). The ``-fms-compatibility-version=`` flag
overrides these values. It accepts a dotted version tuple, such as 19.00.23506.
Changing the MSVC compatibility version makes clang behave more like that
version of MSVC. For example, ``-fms-compatibility-version=19`` will enable
Expand Down
8 changes: 8 additions & 0 deletions clang/include/clang/Basic/Attr.td
Original file line number Diff line number Diff line change
Expand Up @@ -3646,6 +3646,14 @@ def : MutualExclusions<[Owner, Pointer]>;

// Microsoft-related attributes

def MSConstexpr : InheritableAttr {
let LangOpts = [MicrosoftExt];
let Spellings = [CXX11<"msvc", "constexpr">];
let Subjects = SubjectList<[Function, ReturnStmt], ErrorDiag,
"functions and return statements">;
let Documentation = [MSConstexprDocs];
}

def MSNoVTable : InheritableAttr, TargetSpecificAttr<TargetMicrosoftCXXABI> {
let Spellings = [Declspec<"novtable">];
let Subjects = SubjectList<[CXXRecord]>;
Expand Down
15 changes: 15 additions & 0 deletions clang/include/clang/Basic/AttrDocs.td
Original file line number Diff line number Diff line change
Expand Up @@ -3657,6 +3657,21 @@ an error:
}];
}

def MSConstexprDocs : Documentation {
let Category = DocCatStmt;
let Content = [{
The ``[[msvc::constexpr]]`` attribute can be applied only to a function
definition or a ``return`` statement. It does not impact function declarations.
A ``[[msvc::constexpr]]`` function cannot be ``constexpr`` or ``consteval``.
A ``[[msvc::constexpr]]`` function is treated as if it were a ``constexpr`` function
when it is evaluated in a constant context of ``[[msvc::constexpr]] return`` statement.
Otherwise, it is treated as a regular function.

Semantics of this attribute are enabled only under MSVC compatibility
(``-fms-compatibility-version``) 19.33 and later.
}];
}

def MSNoVTableDocs : Documentation {
let Category = DocCatDecl;
let Content = [{
Expand Down
2 changes: 2 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -2884,6 +2884,8 @@ def warn_cxx11_compat_constexpr_body_multiple_return : Warning<
InGroup<CXXPre14Compat>, DefaultIgnore;
def note_constexpr_body_previous_return : Note<
"previous return statement is here">;
def err_ms_constexpr_cannot_be_applied : Error<
"attribute 'msvc::constexpr' cannot be applied to the %select{constexpr|consteval|virtual}0 function %1">;

// C++20 function try blocks in constexpr
def ext_constexpr_function_try_block_cxx20 : ExtWarn<
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/Basic/LangOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ class LangOptions : public LangOptionsBase {
MSVC2019 = 1920,
MSVC2019_5 = 1925,
MSVC2019_8 = 1928,
MSVC2022_3 = 1933,
};

enum SYCLMajorVersion {
Expand Down
34 changes: 28 additions & 6 deletions clang/lib/AST/ExprConstant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,10 @@ namespace {
return false;
}

/// Whether we're in a context where [[msvc::constexpr]] evaluation is
/// permitted. See MSConstexprDocs for description of permitted contexts.
bool CanEvalMSConstexpr = false;

private:
APValue &createLocal(APValue::LValueBase Base, const void *Key, QualType T,
ScopeKind Scope);
Expand Down Expand Up @@ -674,6 +678,19 @@ namespace {
private:
llvm::TimeTraceScope TimeScope;
};

/// RAII object used to change the current ability of
/// [[msvc::constexpr]] evaulation.
struct MSConstexprContextRAII {
CallStackFrame &Frame;
bool OldValue;
explicit MSConstexprContextRAII(CallStackFrame &Frame, bool Value)
: Frame(Frame), OldValue(Frame.CanEvalMSConstexpr) {
Frame.CanEvalMSConstexpr = Value;
}

~MSConstexprContextRAII() { Frame.CanEvalMSConstexpr = OldValue; }
};
}

static bool HandleDestruction(EvalInfo &Info, const Expr *E,
Expand Down Expand Up @@ -5546,11 +5563,14 @@ static EvalStmtResult EvaluateStmt(StmtResult &Result, EvalInfo &Info,
case Stmt::LabelStmtClass:
return EvaluateStmt(Result, Info, cast<LabelStmt>(S)->getSubStmt(), Case);

case Stmt::AttributedStmtClass:
// As a general principle, C++11 attributes can be ignored without
// any semantic impact.
return EvaluateStmt(Result, Info, cast<AttributedStmt>(S)->getSubStmt(),
Case);
case Stmt::AttributedStmtClass: {
const auto *AS = cast<AttributedStmt>(S);
const auto *SS = AS->getSubStmt();
MSConstexprContextRAII ConstexprContext(
*Info.CurrentCall, hasSpecificAttr<MSConstexprAttr>(AS->getAttrs()) &&
isa<ReturnStmt>(SS));
return EvaluateStmt(Result, Info, SS, Case);
}

case Stmt::CaseStmtClass:
case Stmt::DefaultStmtClass:
Expand Down Expand Up @@ -5621,7 +5641,9 @@ static bool CheckConstexprFunction(EvalInfo &Info, SourceLocation CallLoc,
}

// Can we evaluate this function call?
if (Definition && Definition->isConstexpr() && Body)
if (Definition && Body &&
(Definition->isConstexpr() || Info.CurrentCall->CanEvalMSConstexpr &&
Definition->hasAttr<MSConstexprAttr>()))
return true;

if (Info.getLangOpts().CPlusPlus11) {
Expand Down
3 changes: 3 additions & 0 deletions clang/lib/Basic/Targets/OSTargets.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,9 @@ static void addVisualCDefines(const LangOptions &Opts, MacroBuilder &Builder) {
else if (Opts.CPlusPlus14)
Builder.defineMacro("_MSVC_LANG", "201402L");
}

if (Opts.isCompatibleWithMSVC(LangOptions::MSVC2022_3))
Builder.defineMacro("_MSVC_CONSTEXPR_ATTRIBUTE");
}

if (Opts.MicrosoftExt) {
Expand Down
4 changes: 2 additions & 2 deletions clang/lib/Driver/ToolChains/MSVC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -787,11 +787,11 @@ VersionTuple MSVCToolChain::computeMSVCVersion(const Driver *D,
if (MSVT.empty() &&
Args.hasFlag(options::OPT_fms_extensions, options::OPT_fno_ms_extensions,
IsWindowsMSVC)) {
// -fms-compatibility-version=19.20 is default, aka 2019, 16.x
// -fms-compatibility-version=19.33 is default, aka 2022, 17.3
// NOTE: when changing this value, also update
// clang/docs/CommandGuide/clang.rst and clang/docs/UsersManual.rst
// accordingly.
MSVT = VersionTuple(19, 20);
MSVT = VersionTuple(19, 33);
}
return MSVT;
}
Expand Down
4 changes: 3 additions & 1 deletion clang/lib/Sema/SemaDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16221,7 +16221,9 @@ Decl *Sema::ActOnFinishFunctionBody(Decl *dcl, Stmt *Body,
ActivePolicy = &WP;
}

if (!IsInstantiation && FD && FD->isConstexpr() && !FD->isInvalidDecl() &&
if (!IsInstantiation && FD &&
(FD->isConstexpr() || FD->hasAttr<MSConstexprAttr>()) &&
!FD->isInvalidDecl() &&
!CheckConstexprFunctionDefinition(FD, CheckConstexprKind::Diagnose))
FD->setInvalidDecl();

Expand Down
25 changes: 25 additions & 0 deletions clang/lib/Sema/SemaDeclAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7372,6 +7372,28 @@ static void handleDeclspecThreadAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
D->addAttr(::new (S.Context) ThreadAttr(S.Context, AL));
}

static void handleMSConstexprAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
if (!S.getLangOpts().isCompatibleWithMSVC(LangOptions::MSVC2022_3)) {
S.Diag(AL.getLoc(), diag::warn_unknown_attribute_ignored)
<< AL << AL.getRange();
return;
}
auto *FD = cast<FunctionDecl>(D);
if (FD->isConstexprSpecified() || FD->isConsteval()) {
S.Diag(AL.getLoc(), diag::err_ms_constexpr_cannot_be_applied)
<< FD->isConsteval() << FD;
return;
}
if (auto *MD = dyn_cast<CXXMethodDecl>(FD)) {
if (!S.getLangOpts().CPlusPlus20 && MD->isVirtual()) {
S.Diag(AL.getLoc(), diag::err_ms_constexpr_cannot_be_applied)
<< /*virtual*/ 2 << MD;
return;
}
}
D->addAttr(::new (S.Context) MSConstexprAttr(S.Context, AL));
}

static void handleAbiTagAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
SmallVector<StringRef, 4> Tags;
for (unsigned I = 0, E = AL.getNumArgs(); I != E; ++I) {
Expand Down Expand Up @@ -9477,6 +9499,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
case ParsedAttr::AT_Thread:
handleDeclspecThreadAttr(S, D, AL);
break;
case ParsedAttr::AT_MSConstexpr:
handleMSConstexprAttr(S, D, AL);
break;

// HLSL attributes:
case ParsedAttr::AT_HLSLNumThreads:
Expand Down
12 changes: 12 additions & 0 deletions clang/lib/Sema/SemaStmtAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,16 @@ static void CheckForDuplicateCodeAlignAttrs(Sema &S,
}
}

static Attr *handleMSConstexprAttr(Sema &S, Stmt *St, const ParsedAttr &A,
SourceRange Range) {
if (!S.getLangOpts().isCompatibleWithMSVC(LangOptions::MSVC2022_3)) {
S.Diag(A.getLoc(), diag::warn_unknown_attribute_ignored)
<< A << A.getRange();
return nullptr;
}
return ::new (S.Context) MSConstexprAttr(S.Context, A);
}

#define WANT_STMT_MERGE_LOGIC
#include "clang/Sema/AttrParsedAttrImpl.inc"
#undef WANT_STMT_MERGE_LOGIC
Expand Down Expand Up @@ -600,6 +610,8 @@ static Attr *ProcessStmtAttribute(Sema &S, Stmt *St, const ParsedAttr &A,
return handleUnlikely(S, St, A, Range);
case ParsedAttr::AT_CodeAlign:
return handleCodeAlignAttr(S, St, A);
case ParsedAttr::AT_MSConstexpr:
return handleMSConstexprAttr(S, St, A, Range);
default:
// N.B., ClangAttrEmitter.cpp emits a diagnostic helper that ensures a
// declaration attribute is not written on a statement, but this code is
Expand Down
28 changes: 28 additions & 0 deletions clang/test/AST/ms-constexpr.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// RUN: %clang_cc1 -fms-compatibility -fms-compatibility-version=19.33 -std=c++20 -ast-dump -verify %s | FileCheck %s
// expected-no-diagnostics

// CHECK: used f1 'bool ()'
// CHECK: MSConstexprAttr 0x{{[0-9a-f]+}} <col:3, col:9>
[[msvc::constexpr]] bool f1() { return true; }

// CHECK: used constexpr f2 'bool ()'
// CHECK-NEXT: CompoundStmt 0x{{[0-9a-f]+}} <col:21, col:56>
// CHECK-NEXT: AttributedStmt 0x{{[0-9a-f]+}} <col:23, col:53>
// CHECK-NEXT: MSConstexprAttr 0x{{[0-9a-f]+}} <col:25, col:31>
// CHECK-NEXT: ReturnStmt 0x{{[0-9a-f]+}} <col:43, col:53>
constexpr bool f2() { [[msvc::constexpr]] return f1(); }
static_assert(f2());

struct S1 {
// CHECK: used vm 'bool ()' virtual
// CHECK: MSConstexprAttr 0x{{[0-9a-f]+}} <col:7, col:13>
[[msvc::constexpr]] virtual bool vm() { return true; }

// CHECK: used constexpr cm 'bool ()'
// CHECK-NEXT: CompoundStmt 0x{{[0-9a-f]+}} <col:25, col:60>
// CHECK-NEXT: AttributedStmt 0x{{[0-9a-f]+}} <col:27, col:57>
// CHECK-NEXT: MSConstexprAttr 0x{{[0-9a-f]+}} <col:29, col:35>
// CHECK-NEXT: ReturnStmt 0x{{[0-9a-f]+}} <col:47, col:57>
constexpr bool cm() { [[msvc::constexpr]] return vm(); }
};
static_assert(S1{}.cm());
4 changes: 2 additions & 2 deletions clang/test/Driver/cl-options.c
Original file line number Diff line number Diff line change
Expand Up @@ -747,7 +747,7 @@

// Validate that the default triple is used when run an empty tools dir is specified
// RUN: %clang_cl -vctoolsdir "" -### -- %s 2>&1 | FileCheck %s --check-prefix VCTOOLSDIR
// VCTOOLSDIR: "-triple" "{{[a-zA-Z0-9_-]*}}-pc-windows-msvc19.20.0"
// VCTOOLSDIR: "-triple" "{{[a-zA-Z0-9_-]*}}-pc-windows-msvc19.33.0"

// Validate that built-in include paths are based on the supplied path
// RUN: %clang_cl --target=aarch64-pc-windows-msvc -vctoolsdir "/fake" -winsdkdir "/foo" -winsdkversion 10.0.12345.0 -### -- %s 2>&1 | FileCheck %s --check-prefix FAKEDIR
Expand Down Expand Up @@ -787,7 +787,7 @@

// RUN: %clang_cl -vctoolsdir "" /arm64EC /c -### -- %s 2>&1 | FileCheck --check-prefix=ARM64EC %s
// ARM64EC-NOT: /arm64EC has been overridden by specified target
// ARM64EC: "-triple" "arm64ec-pc-windows-msvc19.20.0"
// ARM64EC: "-triple" "arm64ec-pc-windows-msvc19.33.0"

// RUN: %clang_cl -vctoolsdir "" /arm64EC /c -target x86_64-pc-windows-msvc -### -- %s 2>&1 | FileCheck --check-prefix=ARM64EC_OVERRIDE %s
// ARM64EC_OVERRIDE: warning: /arm64EC has been overridden by specified target: x86_64-pc-windows-msvc; option ignored
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
// CHECK-NEXT: LoaderUninitialized (SubjectMatchRule_variable_is_global)
// CHECK-NEXT: Lockable (SubjectMatchRule_record)
// CHECK-NEXT: MIGServerRoutine (SubjectMatchRule_function, SubjectMatchRule_objc_method, SubjectMatchRule_block)
// CHECK-NEXT: MSConstexpr (SubjectMatchRule_function)
// CHECK-NEXT: MSStruct (SubjectMatchRule_record)
// CHECK-NEXT: MaybeUndef (SubjectMatchRule_variable_is_parameter)
// CHECK-NEXT: MicroMips (SubjectMatchRule_function)
Expand Down
52 changes: 52 additions & 0 deletions clang/test/SemaCXX/ms-constexpr-invalid.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// RUN: %clang_cc1 -fms-compatibility -fms-compatibility-version=19.33 -std=c++20 -verify %s
// RUN: %clang_cc1 -fms-compatibility -fms-compatibility-version=19.33 -std=c++17 -verify %s

// Check explicitly invalid code

void runtime() {} // expected-note {{declared here}}

[[msvc::constexpr]] void f0() { runtime(); } // expected-error {{constexpr function never produces a constant expression}} \
// expected-note {{non-constexpr function 'runtime' cannot be used in a constant expression}}
[[msvc::constexpr]] constexpr void f1() {} // expected-error {{attribute 'msvc::constexpr' cannot be applied to the constexpr function 'f1'}}
#if __cplusplus >= 202202L
[[msvc::constexpr]] consteval void f2() {} // expected-error {{attribute 'msvc::constexpr' cannot be applied to the consteval function 'f1'}}
#endif

struct B1 {};
struct D1 : virtual B1 { // expected-note {{virtual base class declared here}}
[[msvc::constexpr]] D1() {} // expected-error {{constexpr constructor not allowed in struct with virtual base class}}
};

struct [[msvc::constexpr]] S2{}; // expected-error {{'constexpr' attribute only applies to functions and return statements}}

// Check invalid code mixed with valid code

[[msvc::constexpr]] int f4(int x) { return x > 1 ? 1 + f4(x / 2) : 0; } // expected-note {{non-constexpr function 'f4' cannot be used in a constant expression}} \
// expected-note {{declared here}} \
// expected-note {{declared here}} \
// expected-note {{declared here}}
constexpr bool f5() { [[msvc::constexpr]] return f4(32) == 5; } // expected-note {{in call to 'f4(32)'}}
static_assert(f5()); // expected-error {{static assertion expression is not an integral constant expression}} \
// expected-note {{in call to 'f5()'}}

int f6(int x) { [[msvc::constexpr]] return x > 1 ? 1 + f6(x / 2) : 0; } // expected-note {{declared here}} \
// expected-note {{declared here}}
constexpr bool f7() { [[msvc::constexpr]] return f6(32) == 5; } // expected-error {{constexpr function never produces a constant expression}} \
// expected-note {{non-constexpr function 'f6' cannot be used in a constant expression}} \
// expected-note {{non-constexpr function 'f6' cannot be used in a constant expression}}
static_assert(f7()); // expected-error {{static assertion expression is not an integral constant expression}} \
// expected-note {{in call to 'f7()'}}

constexpr bool f8() { // expected-error {{constexpr function never produces a constant expression}}
[[msvc::constexpr]] f4(32); // expected-error {{'constexpr' attribute only applies to functions and return statements}} \
// expected-note {{non-constexpr function 'f4' cannot be used in a constant expression}} \
// expected-note {{non-constexpr function 'f4' cannot be used in a constant expression}}
[[msvc::constexpr]] int i5 = f4(32); // expected-error {{'constexpr' attribute only applies to functions and return statements}}
return i5 == 5;
}
static_assert(f8()); // expected-error {{static assertion expression is not an integral constant expression}} \
// expected-note {{in call to 'f8()'}}

#if __cplusplus == 201702L
struct S1 { [[msvc::constexpr]] virtual bool vm() const { return true; } }; // expected-error {{attribute 'msvc::constexpr' ignored, it only applies to function definitions and return statements}}
#endif
16 changes: 16 additions & 0 deletions clang/test/SemaCXX/ms-constexpr-new.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// RUN: %clang_cc1 -fms-compatibility -fms-compatibility-version=19.33 -std=c++20 -verify=supported %s
// RUN: %clang_cc1 -fms-compatibility -fms-compatibility-version=19.32 -std=c++20 -verify=unsupported %s
// supported-no-diagnostics

[[nodiscard]]
[[msvc::constexpr]] // unsupported-warning {{unknown attribute 'constexpr' ignored}}
inline void* __cdecl operator new(decltype(sizeof(void*)), void* p) noexcept { return p; }

namespace std {
constexpr int* construct_at(int* p, int v) {
[[msvc::constexpr]] return ::new (p) int(v); // unsupported-warning {{unknown attribute 'constexpr' ignored}}
}
}

constexpr bool check_construct_at() { int x; return *std::construct_at(&x, 42) == 42; }
static_assert(check_construct_at());
37 changes: 37 additions & 0 deletions clang/test/SemaCXX/ms-constexpr.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// RUN: %clang_cc1 -fms-compatibility -fms-compatibility-version=19.33 -std=c++20 -verify %s

[[msvc::constexpr]] int log2(int x) { [[msvc::constexpr]] return x > 1 ? 1 + log2(x / 2) : 0; }
constexpr bool test_log2() { [[msvc::constexpr]] return log2(32) == 5; }
static_assert(test_log2());

[[msvc::constexpr]] int get_value(int x)
{
switch (x)
{
case 42: return 1337;
default:
if (x < 0) [[msvc::constexpr]] return log2(-x);
else return x;
}
}

constexpr bool test_complex_expr() {
[[msvc::constexpr]] return get_value(get_value(42) - 1337 + get_value(-32) - 5 + (get_value(1) ? get_value(0) : get_value(2))) == get_value(0);
}
static_assert(test_complex_expr());

constexpr bool get_constexpr_true() { return true; }
[[msvc::constexpr]] bool get_msconstexpr_true() { return get_constexpr_true(); }
constexpr bool test_get_msconstexpr_true() { [[msvc::constexpr]] return get_msconstexpr_true(); }
static_assert(test_get_msconstexpr_true());

// TODO (#72149): Add support for [[msvc::constexpr]] constructor; this code is valid for MSVC.
struct S2 {
[[msvc::constexpr]] S2() {}
[[msvc::constexpr]] bool value() { return true; }
static constexpr bool check() { [[msvc::constexpr]] return S2{}.value(); } // expected-error {{constexpr function never produces a constant expression}} \
// expected-note {{non-literal type 'S2' cannot be used in a constant expression}} \
// expected-note {{non-literal type 'S2' cannot be used in a constant expression}}
};
static_assert(S2::check()); // expected-error {{static assertion expression is not an integral constant expression}} \
// expected-note {{in call to 'check()'}}