[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