Skip to content

Commit a5e6a00

Browse files
committed
[CIR] Add support for SourceLocExpr (__builtin_LINE, etc.)
This patch implements SourceLocExpr code generation, which enables support for the following builtin functions: - __builtin_LINE - __builtin_FILE - __builtin_FUNCTION - __builtin_COLUMN These builtins are evaluated at compile-time and emit constant values or global string literals. The implementation follows the traditional CodeGen approach of using ConstantEmitter to emit abstract constants from evaluated APValues. A key challenge was handling synthetic StringLiterals created during constant evaluation (for __builtin_FILE and __builtin_FUNCTION). These synthetic literals don't have valid source locations, which caused assertion failures when creating global string constants. The fix adds a check in getGlobalForStringLiteral to use UnknownLoc when the StringLiteral has an invalid source range. This feature unblocks: - std::source_location (C++20) - Logging and debugging macros that use source location builtins - Assertion/diagnostic frameworks Test coverage includes: - Basic __builtin_LINE, __builtin_FILE, __builtin_FUNCTION, __builtin_COLUMN - Usage in global variable initializers - Default function arguments - Lambda expressions - Combined usage of multiple source location builtins ghstack-source-id: 1958776 Pull-Request: #1988
1 parent 96a0551 commit a5e6a00

File tree

3 files changed

+142
-2
lines changed

3 files changed

+142
-2
lines changed

clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
#include "Address.h"
14+
#include "CIRGenCstEmitter.h"
1415
#include "CIRGenFunction.h"
1516
#include "CIRGenModule.h"
1617
#include "CIRGenOpenMPRuntime.h"
@@ -705,7 +706,17 @@ class ScalarExprEmitter : public StmtVisitor<ScalarExprEmitter, mlir::Value> {
705706
mlir::Value VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *E) {
706707
return emitLoadOfLValue(E);
707708
}
708-
mlir::Value VisitSourceLocExpr(SourceLocExpr *E) { llvm_unreachable("NYI"); }
709+
mlir::Value VisitSourceLocExpr(SourceLocExpr *E) {
710+
auto &Ctx = CGF.getContext();
711+
APValue Evaluated =
712+
E->EvaluateInContext(Ctx, CGF.CurSourceLocExprScope.getDefaultExpr());
713+
auto C = ConstantEmitter(CGF).emitAbstract(E->getLocation(), Evaluated,
714+
E->getType());
715+
mlir::TypedAttr typedAttr = mlir::dyn_cast_or_null<mlir::TypedAttr>(C);
716+
assert(typedAttr && "SourceLocExpr must produce a typed constant");
717+
return cir::ConstantOp::create(Builder, CGF.getLoc(E->getExprLoc()),
718+
typedAttr);
719+
}
709720
mlir::Value VisitCXXDefaultArgExpr(CXXDefaultArgExpr *DAE) {
710721
CIRGenFunction::CXXDefaultArgExprScope Scope(CGF, DAE);
711722
return Visit(DAE->getExpr());

clang/lib/CIR/CodeGen/CIRGenModule.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1845,7 +1845,10 @@ cir::GlobalOp CIRGenModule::getGlobalForStringLiteral(const StringLiteral *s,
18451845
// Unlike LLVM IR, CIR doesn't automatically unique names for globals, so
18461846
// we need to do that explicitly.
18471847
std::string uniqueName = getUniqueGlobalName(globalVariableName.str());
1848-
auto loc = getLoc(s->getSourceRange());
1848+
// Synthetic string literals (e.g., from SourceLocExpr) may not have valid
1849+
// source locations. Use unknown location in those cases.
1850+
auto loc = s->getBeginLoc().isValid() ? getLoc(s->getSourceRange())
1851+
: builder.getUnknownLoc();
18491852
auto typedC = llvm::dyn_cast<mlir::TypedAttr>(c);
18501853
if (!typedC)
18511854
llvm_unreachable("this should never be untyped at this point");
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fclangir -emit-cir %s -o %t.cir
2+
// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
3+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fclangir -emit-llvm %s -o %t.ll
4+
// RUN: FileCheck --check-prefix=LLVM --input-file=%t.ll %s
5+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -emit-llvm %s -o %t.og.ll
6+
// RUN: FileCheck --check-prefix=OGCG --input-file=%t.og.ll %s
7+
8+
// Test in global context
9+
#line 100 "test_file.cpp"
10+
int global_line = __builtin_LINE();
11+
// CIR: cir.global external @global_line = #cir.int<100> : !s32i
12+
// LLVM: @global_line{{.*}} = global i32 100
13+
// OGCG: @global_line{{.*}} = global i32 100
14+
15+
#line 15 "clang/test/CIR/CodeGen/source-loc-expr.cpp"
16+
// Test __builtin_LINE
17+
int test_builtin_LINE() {
18+
// CIR-LABEL: cir.func {{.*}}@{{.*}}test_builtin_LINE
19+
// CIR: %{{.*}} = cir.const #cir.int<25> : !u32i
20+
21+
// LLVM-LABEL: @{{.*}}test_builtin_LINE
22+
// LLVM: store i32 25
23+
24+
// OGCG-LABEL: @{{.*}}test_builtin_LINE
25+
// OGCG: ret i32 25
26+
return __builtin_LINE();
27+
}
28+
29+
// Test __builtin_FILE
30+
const char* test_builtin_FILE() {
31+
// CIR-LABEL: cir.func {{.*}}@{{.*}}test_builtin_FILE
32+
// CIR: %{{.*}} = cir.const #cir.global_view<@".str{{.*}}"> : !cir.ptr<!s8i>
33+
34+
// LLVM-LABEL: @{{.*}}test_builtin_FILE
35+
// LLVM: store ptr @.str
36+
37+
// OGCG-LABEL: @{{.*}}test_builtin_FILE
38+
// OGCG: ret ptr @.str
39+
return __builtin_FILE();
40+
}
41+
42+
// Test __builtin_FUNCTION
43+
const char* test_builtin_FUNCTION() {
44+
// CIR-LABEL: cir.func {{.*}}@{{.*}}test_builtin_FUNCTION
45+
// CIR: %{{.*}} = cir.const #cir.global_view<@".str{{.*}}"> : !cir.ptr<!s8i>
46+
47+
// LLVM-LABEL: @{{.*}}test_builtin_FUNCTION
48+
// LLVM: store ptr @.str
49+
50+
// OGCG-LABEL: @{{.*}}test_builtin_FUNCTION
51+
// OGCG: ret ptr @.str
52+
return __builtin_FUNCTION();
53+
}
54+
55+
// Test __builtin_COLUMN
56+
int test_builtin_COLUMN() {
57+
// CIR-LABEL: cir.func {{.*}}@{{.*}}test_builtin_COLUMN
58+
// The column number is the position of '__builtin_COLUMN'
59+
// CIR: %{{.*}} = cir.const #cir.int<10> : !u32i
60+
61+
// LLVM-LABEL: @{{.*}}test_builtin_COLUMN
62+
// LLVM: store i32 10
63+
64+
// OGCG-LABEL: @{{.*}}test_builtin_COLUMN
65+
// OGCG: ret i32 10
66+
return __builtin_COLUMN();
67+
}
68+
69+
// Test default argument
70+
int get_line(int l = __builtin_LINE()) {
71+
return l;
72+
}
73+
74+
void test_default_arg() {
75+
// CIR-LABEL: cir.func {{.*}}@{{.*}}test_default_arg
76+
// The LINE should be from the call site, not the default argument definition
77+
#line 111
78+
int x = get_line();
79+
// CIR: %{{.*}} = cir.const #cir.int<111> : !u32i
80+
// CIR: %{{.*}} = cir.call @{{.*}}get_line{{.*}}({{.*}}) :
81+
82+
// LLVM-LABEL: @{{.*}}test_default_arg
83+
// LLVM: call{{.*}} i32 @{{.*}}get_line{{.*}}(i32 111)
84+
85+
// OGCG-LABEL: @{{.*}}test_default_arg
86+
// OGCG: call{{.*}} i32 @{{.*}}get_line{{.*}}(i32 {{.*}} 111)
87+
}
88+
89+
#line 200 "lambda-test.cpp"
90+
// Test in lambda (this tests that source location correctly captures context)
91+
void test_in_lambda() {
92+
// CIR-LABEL: cir.func {{.*}}@{{.*}}test_in_lambda
93+
auto lambda = []() {
94+
return __builtin_LINE();
95+
};
96+
int x = lambda();
97+
98+
// LLVM-LABEL: @{{.*}}test_in_lambda
99+
// LLVM: call{{.*}} i32 @{{.*}}
100+
101+
// OGCG-LABEL: @{{.*}}test_in_lambda
102+
// OGCG: call{{.*}} i32 @{{.*}}
103+
}
104+
105+
#line 214 "combined-test.cpp"
106+
// Test multiple builtins in one expression
107+
void test_combined() {
108+
// CIR-LABEL: cir.func {{.*}}@{{.*}}test_combined
109+
const char* file = __builtin_FILE();
110+
int line = __builtin_LINE();
111+
const char* func = __builtin_FUNCTION();
112+
// All should produce constants
113+
// CIR: cir.const
114+
// CIR: cir.const
115+
// CIR: cir.const
116+
117+
// LLVM-LABEL: @{{.*}}test_combined
118+
// LLVM: store ptr @.str
119+
// LLVM: store i32 218
120+
// LLVM: store ptr @.str
121+
122+
// OGCG-LABEL: @{{.*}}test_combined
123+
// OGCG: store ptr @.str
124+
// OGCG: store i32 218
125+
// OGCG: store ptr @.str
126+
}

0 commit comments

Comments
 (0)