[clang] clang: __builtin_VARIABLE_NAME (PR #86756)

Jon Roelofs via cfe-commits cfe-commits at lists.llvm.org
Tue Mar 26 19:09:44 PDT 2024


https://github.com/jroelofs created https://github.com/llvm/llvm-project/pull/86756

Some C++ Embedded DSLs make use of `std::source_location` as a form of crude reflection.  Until now, they didn't have a great way to reflect variable names into their IRs.  With `__buitlin_SOURCE_LOCATION()`, that is as simple as adding a default argument expression to a constructor:

```
struct Variable {
  const char *Name;
  Variable(const char *Name = __builtin_SOURCE_LOCATION()) : Name(Name) {}
};

Variable foo;
// foo.Name == "foo"
```

>From dc4a313b793b4215c8a68bea0b4b8e70f8a7806b Mon Sep 17 00:00:00 2001
From: Jon Roelofs <jonathan_roelofs at apple.com>
Date: Tue, 26 Mar 2024 16:54:12 -0700
Subject: [PATCH] clang: __builtin_VARIABLE_NAME

Some C++ Embedded DSLs make use of std::source_location as a form of crude
reflection.  Until now, they didn't have a great way to reflect variable names
into their IRs.  With __buitlin_SOURCE_LOCATION(), that is as simple as adding
a default argument expression to a constructor.

struct Variable {
  const char *Name;
  Variable(const char *Name = __builtin_SOURCE_LOCATION()) : Name(Name) {}
};

Variable foo;
// foo.Name == "foo"
---
 clang/include/clang/AST/Expr.h           |  7 ++-
 clang/include/clang/Basic/TokenKinds.def |  1 +
 clang/lib/AST/Expr.cpp                   | 33 +++++++++++
 clang/lib/Parse/ParseExpr.cpp            |  6 ++
 clang/lib/Sema/SemaExpr.cpp              |  1 +
 clang/test/CodeGenCXX/variable-name.cpp  | 75 ++++++++++++++++++++++++
 clang/test/SemaCXX/source_location.cpp   | 39 ++++++++++++
 7 files changed, 160 insertions(+), 2 deletions(-)
 create mode 100644 clang/test/CodeGenCXX/variable-name.cpp

diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h
index 6e153ebe024b42..1d0e741ddc983b 100644
--- a/clang/include/clang/AST/Expr.h
+++ b/clang/include/clang/AST/Expr.h
@@ -4725,12 +4725,13 @@ enum class SourceLocIdentKind {
   FileName,
   Line,
   Column,
-  SourceLocStruct
+  SourceLocStruct,
+  VariableName,
 };
 
 /// Represents a function call to one of __builtin_LINE(), __builtin_COLUMN(),
 /// __builtin_FUNCTION(), __builtin_FUNCSIG(), __builtin_FILE(),
-/// __builtin_FILE_NAME() or __builtin_source_location().
+/// __builtin_FILE_NAME(), __builtin_source_location() or __builtin_VARIABLE_NAME().
 class SourceLocExpr final : public Expr {
   SourceLocation BuiltinLoc, RParenLoc;
   DeclContext *ParentContext;
@@ -4762,6 +4763,7 @@ class SourceLocExpr final : public Expr {
     case SourceLocIdentKind::Function:
     case SourceLocIdentKind::FuncSig:
     case SourceLocIdentKind::SourceLocStruct:
+    case SourceLocIdentKind::VariableName:
       return false;
     case SourceLocIdentKind::Line:
     case SourceLocIdentKind::Column:
@@ -4796,6 +4798,7 @@ class SourceLocExpr final : public Expr {
     case SourceLocIdentKind::Function:
     case SourceLocIdentKind::FuncSig:
     case SourceLocIdentKind::SourceLocStruct:
+    case SourceLocIdentKind::VariableName:
       return true;
     default:
       return false;
diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def
index 3a96f8a4d22bd1..23e617e1640886 100644
--- a/clang/include/clang/Basic/TokenKinds.def
+++ b/clang/include/clang/Basic/TokenKinds.def
@@ -448,6 +448,7 @@ KEYWORD(__builtin_FUNCSIG           , KEYMS)
 KEYWORD(__builtin_LINE              , KEYALL)
 KEYWORD(__builtin_COLUMN            , KEYALL)
 KEYWORD(__builtin_source_location   , KEYCXX)
+KEYWORD(__builtin_VARIABLE_NAME     , KEYCXX)
 
 // __builtin_types_compatible_p is a GNU C extension that we handle like a C++
 // type trait.
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index 6221ebd5c9b4e9..96954df5a9817b 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -24,6 +24,7 @@
 #include "clang/AST/IgnoreExpr.h"
 #include "clang/AST/Mangle.h"
 #include "clang/AST/RecordLayout.h"
+#include "clang/AST/RecursiveASTVisitor.h"
 #include "clang/AST/StmtVisitor.h"
 #include "clang/Basic/Builtins.h"
 #include "clang/Basic/CharInfo.h"
@@ -2262,6 +2263,8 @@ StringRef SourceLocExpr::getBuiltinStr() const {
     return "__builtin_FILE";
   case SourceLocIdentKind::FileName:
     return "__builtin_FILE_NAME";
+  case SourceLocIdentKind::VariableName:
+    return "__builtin_VARIABLE_NAME";
   case SourceLocIdentKind::Function:
     return "__builtin_FUNCTION";
   case SourceLocIdentKind::FuncSig:
@@ -2305,6 +2308,36 @@ APValue SourceLocExpr::EvaluateInContext(const ASTContext &Ctx,
   };
 
   switch (getIdentKind()) {
+  case SourceLocIdentKind::VariableName: {
+    // __builtin_VARIABLE_NAME() is a Clang-specific extension that expands to
+    // the name of the variable being defined in a CXXDefaultArgExpr.
+
+    // FIXME: The AST doesn't have upward edges, so we can't easily traverse up
+    // from the CXXDefaultArgExpr to find it.  Unfortunately, this means we need
+    // to do a linear scan of (up to) the entire FunctionDecl.
+    struct FindVarDecl : public RecursiveASTVisitor<FindVarDecl> {
+      const Expr *ToFind;
+      const VarDecl *Found = nullptr;
+      bool TraverseVarDecl(VarDecl *D) {
+        if (const auto *CE = dyn_cast_or_null<CXXConstructExpr>(D->getInit())) {
+          if (llvm::is_contained(CE->arguments(), ToFind)) {
+            Found = D;
+            return false;
+          }
+        }
+        return RecursiveASTVisitor::TraverseVarDecl(D);
+      }
+    };
+
+    if (isa<Decl>(Context)) {
+      FindVarDecl FVD;
+      FVD.ToFind = DefaultExpr;
+      FVD.TraverseDecl(const_cast<Decl*>(cast<Decl>(Context)));
+      if (FVD.Found)
+        return MakeStringLiteral(FVD.Found->getQualifiedNameAsString());
+    }
+    return MakeStringLiteral("");
+  }
   case SourceLocIdentKind::FileName: {
     // __builtin_FILE_NAME() is a Clang-specific extension that expands to the
     // the last part of __builtin_FILE().
diff --git a/clang/lib/Parse/ParseExpr.cpp b/clang/lib/Parse/ParseExpr.cpp
index ae23cb432c4391..bca64d854037bb 100644
--- a/clang/lib/Parse/ParseExpr.cpp
+++ b/clang/lib/Parse/ParseExpr.cpp
@@ -814,6 +814,7 @@ class CastExpressionIdValidator final : public CorrectionCandidateCallback {
 ///                                     assign-expr ')'
 /// [GNU]   '__builtin_FILE' '(' ')'
 /// [CLANG] '__builtin_FILE_NAME' '(' ')'
+/// [CLANG] '__builtin_VARIABLE_NAME' '(' ')'
 /// [GNU]   '__builtin_FUNCTION' '(' ')'
 /// [MS]    '__builtin_FUNCSIG' '(' ')'
 /// [GNU]   '__builtin_LINE' '(' ')'
@@ -1365,6 +1366,7 @@ ExprResult Parser::ParseCastExpression(CastParseKind ParseKind,
   case tok::kw___builtin_COLUMN:
   case tok::kw___builtin_FILE:
   case tok::kw___builtin_FILE_NAME:
+  case tok::kw___builtin_VARIABLE_NAME:
   case tok::kw___builtin_FUNCTION:
   case tok::kw___builtin_FUNCSIG:
   case tok::kw___builtin_LINE:
@@ -2638,6 +2640,7 @@ ExprResult Parser::ParseUnaryExprOrTypeTraitExpression() {
 /// [GNU]   '__builtin_types_compatible_p' '(' type-name ',' type-name ')'
 /// [GNU]   '__builtin_FILE' '(' ')'
 /// [CLANG] '__builtin_FILE_NAME' '(' ')'
+/// [CLANG] '__builtin_VARIABLE_NAME' '(' ')'
 /// [GNU]   '__builtin_FUNCTION' '(' ')'
 /// [MS]    '__builtin_FUNCSIG' '(' ')'
 /// [GNU]   '__builtin_LINE' '(' ')'
@@ -2875,6 +2878,7 @@ ExprResult Parser::ParseBuiltinPrimaryExpression() {
   case tok::kw___builtin_COLUMN:
   case tok::kw___builtin_FILE:
   case tok::kw___builtin_FILE_NAME:
+  case tok::kw___builtin_VARIABLE_NAME:
   case tok::kw___builtin_FUNCTION:
   case tok::kw___builtin_FUNCSIG:
   case tok::kw___builtin_LINE:
@@ -2891,6 +2895,8 @@ ExprResult Parser::ParseBuiltinPrimaryExpression() {
         return SourceLocIdentKind::File;
       case tok::kw___builtin_FILE_NAME:
         return SourceLocIdentKind::FileName;
+      case tok::kw___builtin_VARIABLE_NAME:
+        return SourceLocIdentKind::VariableName;
       case tok::kw___builtin_FUNCTION:
         return SourceLocIdentKind::Function;
       case tok::kw___builtin_FUNCSIG:
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 091fc3e4836b63..393cbfd61a9757 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -17575,6 +17575,7 @@ ExprResult Sema::ActOnSourceLocExpr(SourceLocIdentKind Kind,
   switch (Kind) {
   case SourceLocIdentKind::File:
   case SourceLocIdentKind::FileName:
+  case SourceLocIdentKind::VariableName:
   case SourceLocIdentKind::Function:
   case SourceLocIdentKind::FuncSig: {
     QualType ArrTy = Context.getStringLiteralArrayType(Context.CharTy, 0);
diff --git a/clang/test/CodeGenCXX/variable-name.cpp b/clang/test/CodeGenCXX/variable-name.cpp
new file mode 100644
index 00000000000000..d1c4807a3c5a96
--- /dev/null
+++ b/clang/test/CodeGenCXX/variable-name.cpp
@@ -0,0 +1,75 @@
+// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --check-globals all --include-generated-funcs --version 4
+// RUN: %clang_cc1 -triple arm64-apple-macosx -emit-llvm -o - %s | FileCheck %s
+
+struct DSLKind {
+  const char *name;
+  constexpr DSLKind(const char *name = __builtin_VARIABLE_NAME()) : name(name) {}
+};
+
+const char *check_local() {
+    DSLKind halide;
+    return halide.name;
+}
+
+namespace dsls {
+    DSLKind spirit;
+}
+
+const char *check_global() {
+    return dsls::spirit.name;
+}
+
+//.
+// CHECK: @.str = private unnamed_addr constant [7 x i8] c"halide\00", align 1
+// CHECK: @.str.1 = private unnamed_addr constant [13 x i8] c"dsls::spirit\00", align 1
+// CHECK: @_ZN4dsls6spiritE = global %struct.DSLKind { ptr @.str.1 }, align 8
+//.
+// CHECK-LABEL: define noundef ptr @_Z11check_localv(
+// CHECK-SAME: ) #[[ATTR0:[0-9]+]] {
+// CHECK-NEXT:  entry:
+// CHECK-NEXT:    [[HALIDE:%.*]] = alloca [[STRUCT_DSLKIND:%.*]], align 8
+// CHECK-NEXT:    [[CALL:%.*]] = call noundef ptr @_ZN7DSLKindC1EPKc(ptr noundef nonnull align 8 dereferenceable(8) [[HALIDE]], ptr noundef @.str)
+// CHECK-NEXT:    [[NAME:%.*]] = getelementptr inbounds [[STRUCT_DSLKIND]], ptr [[HALIDE]], i32 0, i32 0
+// CHECK-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[NAME]], align 8
+// CHECK-NEXT:    ret ptr [[TMP0]]
+//
+//
+// CHECK-LABEL: define linkonce_odr noundef ptr @_ZN7DSLKindC1EPKc(
+// CHECK-SAME: ptr noundef nonnull returned align 8 dereferenceable(8) [[THIS:%.*]], ptr noundef [[NAME:%.*]]) unnamed_addr #[[ATTR0]] align 2 {
+// CHECK-NEXT:  entry:
+// CHECK-NEXT:    [[THIS_ADDR:%.*]] = alloca ptr, align 8
+// CHECK-NEXT:    [[NAME_ADDR:%.*]] = alloca ptr, align 8
+// CHECK-NEXT:    store ptr [[THIS]], ptr [[THIS_ADDR]], align 8
+// CHECK-NEXT:    store ptr [[NAME]], ptr [[NAME_ADDR]], align 8
+// CHECK-NEXT:    [[THIS1:%.*]] = load ptr, ptr [[THIS_ADDR]], align 8
+// CHECK-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[NAME_ADDR]], align 8
+// CHECK-NEXT:    [[CALL:%.*]] = call noundef ptr @_ZN7DSLKindC2EPKc(ptr noundef nonnull align 8 dereferenceable(8) [[THIS1]], ptr noundef [[TMP0]])
+// CHECK-NEXT:    ret ptr [[THIS1]]
+//
+//
+// CHECK-LABEL: define noundef ptr @_Z12check_globalv(
+// CHECK-SAME: ) #[[ATTR0]] {
+// CHECK-NEXT:  entry:
+// CHECK-NEXT:    [[TMP0:%.*]] = load ptr, ptr @_ZN4dsls6spiritE, align 8
+// CHECK-NEXT:    ret ptr [[TMP0]]
+//
+//
+// CHECK-LABEL: define linkonce_odr noundef ptr @_ZN7DSLKindC2EPKc(
+// CHECK-SAME: ptr noundef nonnull returned align 8 dereferenceable(8) [[THIS:%.*]], ptr noundef [[NAME:%.*]]) unnamed_addr #[[ATTR0]] align 2 {
+// CHECK-NEXT:  entry:
+// CHECK-NEXT:    [[THIS_ADDR:%.*]] = alloca ptr, align 8
+// CHECK-NEXT:    [[NAME_ADDR:%.*]] = alloca ptr, align 8
+// CHECK-NEXT:    store ptr [[THIS]], ptr [[THIS_ADDR]], align 8
+// CHECK-NEXT:    store ptr [[NAME]], ptr [[NAME_ADDR]], align 8
+// CHECK-NEXT:    [[THIS1:%.*]] = load ptr, ptr [[THIS_ADDR]], align 8
+// CHECK-NEXT:    [[NAME2:%.*]] = getelementptr inbounds [[STRUCT_DSLKIND:%.*]], ptr [[THIS1]], i32 0, i32 0
+// CHECK-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[NAME_ADDR]], align 8
+// CHECK-NEXT:    store ptr [[TMP0]], ptr [[NAME2]], align 8
+// CHECK-NEXT:    ret ptr [[THIS1]]
+//
+//.
+// CHECK: attributes #[[ATTR0]] = { mustprogress noinline nounwind optnone "no-trapping-math"="true" "stack-protector-buffer-size"="8" }
+//.
+// CHECK: [[META0:![0-9]+]] = !{i32 1, !"wchar_size", i32 4}
+// CHECK: [[META1:![0-9]+]] = !{!"{{.*}}clang version {{.*}}"}
+//.
diff --git a/clang/test/SemaCXX/source_location.cpp b/clang/test/SemaCXX/source_location.cpp
index 63157cfacdd98b..8b1896a060bb49 100644
--- a/clang/test/SemaCXX/source_location.cpp
+++ b/clang/test/SemaCXX/source_location.cpp
@@ -94,6 +94,7 @@ static_assert(is_same<decltype(__builtin_LINE()), unsigned>);
 static_assert(is_same<decltype(__builtin_COLUMN()), unsigned>);
 static_assert(is_same<decltype(__builtin_FILE()), const char *>);
 static_assert(is_same<decltype(__builtin_FILE_NAME()), const char *>);
+static_assert(is_same<decltype(__builtin_VARIABLE_NAME()), const char *>);
 static_assert(is_same<decltype(__builtin_FUNCTION()), const char *>);
 #ifdef MS
 static_assert(is_same<decltype(__builtin_FUNCSIG()), const char *>);
@@ -105,6 +106,7 @@ static_assert(noexcept(__builtin_LINE()));
 static_assert(noexcept(__builtin_COLUMN()));
 static_assert(noexcept(__builtin_FILE()));
 static_assert(noexcept(__builtin_FILE_NAME()));
+static_assert(noexcept(__builtin_VARIABLE_NAME()));
 static_assert(noexcept(__builtin_FUNCTION()));
 #ifdef MS
 static_assert(noexcept(__builtin_FUNCSIG()));
@@ -411,6 +413,43 @@ void test_aggr_class() {
 
 } // namespace test_file_name
 
+//===----------------------------------------------------------------------===//
+//                            __builtin_VARIABLE_NAME()
+//===----------------------------------------------------------------------===//
+
+namespace test_variable_name {
+
+struct Class {
+  const char *Actual;
+  constexpr Class(const char *N = __builtin_VARIABLE_NAME()) : Actual(N) {}
+};
+
+constexpr Class halide;
+
+constexpr void test_named_decl() {
+  // FIXME: I don't understand why this doesn't work. Maybe there is something I
+  // don't understand about constexpr?
+  //static_assert(is_equal(halide.Actual, "test_variable_name::halide"), "");
+}
+
+constexpr void test_anonymous() {
+  static_assert(is_equal(Class().Actual, ""));
+}
+
+constexpr const char *check_function(const char *Name = __builtin_VARIABLE_NAME()) {
+  return Name;
+}
+
+constexpr void test_function() {
+  static_assert(is_equal(check_function(), ""), "");
+}
+
+constexpr void test_expr() {
+  static_assert(is_equal(__builtin_VARIABLE_NAME(), ""), "");
+}
+
+} // namespace test_variable_name
+
 //===----------------------------------------------------------------------===//
 //                            __builtin_FUNCTION()
 //===----------------------------------------------------------------------===//



More information about the cfe-commits mailing list