https://github.com/Marsian13 created https://github.com/llvm/llvm-project/pull/233395
C rejects taking the address of a register variable, but C++ skipped that check, so for a global register variable (declared with an asm label) CodeGen received an lvalue with no address, which triggered assertions (or invalid IR without assertions). - Diagnose '&sp' for global register variables in C++, as in C. - Treat global register variables as non-referenceable glvalues, so a 'const T&' binds to a temporary holding the register's value, as for bit-fields. - Diagnose binding a non-const reference to one. Ordinary 'register' locals are unchanged: their address can still be taken in C++ (c++03 7.1.1p3). Fixes #233283 Testing: added clang/test/SemaCXX/global-register-var-address.cpp and clang/test/CodeGenCXX/global-register-var-reference.cpp. The Sema, SemaCXX, CodeGen and CodeGenCXX test directories pass locally (X86 target only). AI assistance: I used an AI assistant (Claude) to help me understand the code. >From a9e0800569b2dc99394db2c23629583a1588abe1 Mon Sep 17 00:00:00 2001 From: Marsian13 <[email protected]> Date: Mon, 19 Oct 2026 03:15:41 +0530 Subject: [PATCH] [clang] Diagnose and handle global register variables in C++ C rejects taking the address of a register variable, but C++ skipped that check, so for a global register variable (declared with an asm label) CodeGen received an lvalue with no address, which triggered assertions (or invalid IR without assertions). - Diagnose '&sp' for global register variables in C++, as in C. - Treat global register variables as non-referenceable glvalues, so a 'const T&' binds to a temporary holding the register's value, as for bit-fields. - Diagnose binding a non-const reference to one. Ordinary 'register' locals are unchanged: their address can still be taken in C++ (c++03 7.1.1p3). Fixes #233283 --- clang/docs/ReleaseNotes.md | 5 ++ .../clang/Basic/DiagnosticSemaKinds.td | 2 + clang/include/clang/Sema/Initialization.h | 3 + clang/lib/Sema/SemaExpr.cpp | 2 +- clang/lib/Sema/SemaInit.cpp | 25 ++++++++- .../global-register-var-reference.cpp | 16 ++++++ .../SemaCXX/global-register-var-address.cpp | 55 +++++++++++++++++++ 7 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 clang/test/CodeGenCXX/global-register-var-reference.cpp create mode 100644 clang/test/SemaCXX/global-register-var-address.cpp diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index 7705b0e61bf4bf..287c2e49ecb691 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -1030,6 +1030,11 @@ features cannot lower the translation-unit ABI level; whose parameter type uses pack indexing, and makes such valid matches succeed instead of being rejected. (#GH232220) +- Clang no longer crashes when taking the address of, or binding a reference + to, a global register variable. Taking the address is now diagnosed as in C, + binding a `const` reference uses a temporary holding the register's value, + and binding a non-const reference is diagnosed. (#GH233283) + #### Bug Fixes to AST Handling - Fixed a non-deterministic ordering of unused local typedefs that made diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 3e8ba5bf88a42b..8ed766294cc5bf 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -2562,6 +2562,8 @@ def err_reference_bind_to_vector_element : Error< "%select{non-const|volatile}0 reference cannot bind to vector element">; def err_reference_bind_to_matrix_element : Error< "%select{non-const|volatile}0 reference cannot bind to matrix element">; +def err_reference_bind_to_global_register_variable : Error< + "%select{non-const|volatile}0 reference cannot bind to global register variable">; def err_reference_var_requires_init : Error< "declaration of reference variable %0 requires an initializer">; def err_reference_without_init : Error< diff --git a/clang/include/clang/Sema/Initialization.h b/clang/include/clang/Sema/Initialization.h index 2c2df1f03747b3..31adaad1d65184 100644 --- a/clang/include/clang/Sema/Initialization.h +++ b/clang/include/clang/Sema/Initialization.h @@ -1091,6 +1091,9 @@ class InitializationSequence { /// Non-const lvalue reference binding to a matrix element. FK_NonConstLValueReferenceBindingToMatrixElement, + /// Non-const lvalue reference binding to a global register variable. + FK_NonConstLValueReferenceBindingToRegisterVariable, + /// Non-const lvalue reference binding to an lvalue of unrelated /// type. FK_NonConstLValueReferenceBindingToUnrelated, diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 2e9a3b73e5c658..0035488909459f 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -15374,7 +15374,7 @@ QualType Sema::CheckAddressOfOperand(ExprResult &OrigOp, SourceLocation OpLoc) { // in C++ it is not error to take address of a register // variable (c++03 7.1.1P3) if (vd->getStorageClass() == SC_Register && - !getLangOpts().CPlusPlus) { + (!getLangOpts().CPlusPlus || vd->hasGlobalStorage())) { AddressOfError = AO_Register_Variable; } } else if (isa<MSPropertyDecl>(dcl)) { diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp index 1cea790da1d27f..9249f024178a0a 100644 --- a/clang/lib/Sema/SemaInit.cpp +++ b/clang/lib/Sema/SemaInit.cpp @@ -4024,6 +4024,7 @@ bool InitializationSequence::isAmbiguous() const { case FK_NonConstLValueReferenceBindingToBitfield: case FK_NonConstLValueReferenceBindingToVectorElement: case FK_NonConstLValueReferenceBindingToMatrixElement: + case FK_NonConstLValueReferenceBindingToRegisterVariable: case FK_NonConstLValueReferenceBindingToUnrelated: case FK_RValueReferenceBindingToLValue: case FK_ReferenceAddrspaceMismatchTemporary: @@ -5490,9 +5491,18 @@ static void TryReferenceInitialization(Sema &S, const InitializedEntity &Entity, /// Determine whether an expression is a non-referenceable glvalue (one to /// which a reference can never bind). Attempting to bind a reference to /// such a glvalue will always create a temporary. +/// Determine whether an expression refers to a global register variable, +/// which lives in a hardware register and has no address to bind to. +static bool refersToGlobalRegisterVar(Expr *E) { + if (const auto *DRE = dyn_cast<DeclRefExpr>(E->IgnoreParens())) + if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl())) + return VD->getStorageClass() == SC_Register && VD->hasGlobalStorage(); + return false; +} + static bool isNonReferenceableGLValue(Expr *E) { return E->refersToBitField() || E->refersToVectorElement() || - E->refersToMatrixElement(); + E->refersToMatrixElement() || refersToGlobalRegisterVar(E); } /// Reference initialization without resolving overloaded functions. @@ -5626,6 +5636,9 @@ static void TryReferenceInitializationCore(Sema &S, else if (Initializer->refersToMatrixElement()) FK = InitializationSequence:: FK_NonConstLValueReferenceBindingToMatrixElement; + else if (refersToGlobalRegisterVar(Initializer)) + FK = InitializationSequence:: + FK_NonConstLValueReferenceBindingToRegisterVariable; else llvm_unreachable("unexpected kind of compatible initializer"); break; @@ -9244,6 +9257,12 @@ bool InitializationSequence::Diagnose(Sema &S, << DestType.isVolatileQualified() << Args[0]->getSourceRange(); break; + case FK_NonConstLValueReferenceBindingToRegisterVariable: + S.Diag(Kind.getLocation(), + diag::err_reference_bind_to_global_register_variable) + << DestType.isVolatileQualified() << Args[0]->getSourceRange(); + break; + case FK_RValueReferenceBindingToLValue: S.Diag(Kind.getLocation(), diag::err_lvalue_to_rvalue_ref) << DestType.getNonReferenceType() << OnlyArg->getType() @@ -9637,6 +9656,10 @@ void InitializationSequence::dump(raw_ostream &OS) const { OS << "non-const lvalue reference bound to matrix element"; break; + case FK_NonConstLValueReferenceBindingToRegisterVariable: + OS << "non-const lvalue reference bound to global register variable"; + break; + case FK_NonConstLValueReferenceBindingToUnrelated: OS << "non-const lvalue reference bound to unrelated type"; break; diff --git a/clang/test/CodeGenCXX/global-register-var-reference.cpp b/clang/test/CodeGenCXX/global-register-var-reference.cpp new file mode 100644 index 00000000000000..8d17194f1c3be5 --- /dev/null +++ b/clang/test/CodeGenCXX/global-register-var-reference.cpp @@ -0,0 +1,16 @@ +// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -emit-llvm -o - %s | FileCheck %s + +register unsigned long sp asm("rsp"); + +template <class T> unsigned long f(const T &x) { return x; } + +// A const reference to a global register variable binds to a temporary that +// holds the register's value. +// CHECK-LABEL: define {{.*}} @_Z1gv( +// CHECK: [[TMP:%.*]] = alloca i64 +// CHECK: [[VAL:%.*]] = call i64 @llvm.read_register.i64(metadata ![[REG:[0-9]+]]) +// CHECK-NEXT: store i64 [[VAL]], ptr [[TMP]] +// CHECK: call {{.*}} @_Z1fImEmRKT_(ptr {{.*}} [[TMP]]) +unsigned long g() { return f(sp); } + +// CHECK: ![[REG]] = !{!"rsp"} diff --git a/clang/test/SemaCXX/global-register-var-address.cpp b/clang/test/SemaCXX/global-register-var-address.cpp new file mode 100644 index 00000000000000..5171a7ca1003e9 --- /dev/null +++ b/clang/test/SemaCXX/global-register-var-address.cpp @@ -0,0 +1,55 @@ +// RUN: %clang_cc1 -std=c++20 -Wno-register -triple x86_64-unknown-linux-gnu -fsyntax-only -verify %s + +// A global register variable (GNU extension) has no memory address, so C++ +// should reject taking its address, as C does. +register unsigned long sp asm("rsp"); + +unsigned long *take_address() { + return &sp; // expected-error {{address of register variable requested}} +} + +// Reading the value is fine, only taking the address is rejected. +unsigned long read_value() { + return sp; +} + +void bind_reference() { + unsigned long &ref = sp; // expected-error {{non-const reference cannot bind to global register variable}} + (void)ref; +} + +// Taking the address of an ordinary register variable stays valid in C++ +// (c++03 7.1.1p3), so the check must not apply to it. +void local_register_ok() { + register int x = 0; + int *q = &x; + (void)q; +} + +// A non-const reference can't bind to a global register variable: there is +// no object to refer to. +unsigned long &bad_return() { + return sp; // expected-error {{non-const reference cannot bind to global register variable}} +} + +void takes_ref(unsigned long &x); // expected-note {{passing argument to parameter 'x' here}} +void bad_call() { + takes_ref(sp); // expected-error {{non-const reference cannot bind to global register variable}} +} + +// A const reference binds to a temporary copy of the register's value. +void const_ref_ok() { + const unsigned long &r = sp; + (void)r; +} + +// Parentheses around the variable don't change the result. +void paren_ref() { + unsigned long &r = (sp); // expected-error {{non-const reference cannot bind to global register variable}} + (void)r; +} + +void paren_const_ref_ok() { + const unsigned long &r = (sp); + (void)r; +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
