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_roel...@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() //===----------------------------------------------------------------------===// _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits