Skip to content

Commit fd05202

Browse files
committed
[CIR] Implement non-ODR-use constant expression handling
This patch implements support for non-ODR-use constant expressions (NOUR_Constant) in ClangIR, which allows variables to be referenced without constituting an odr-use when used in constant contexts.
1 parent 2807a3e commit fd05202

File tree

5 files changed

+221
-32
lines changed

5 files changed

+221
-32
lines changed

clang/lib/CIR/CodeGen/CIRGenDecl.cpp

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,69 @@ CIRGenModule::getOrCreateStaticVarDecl(const VarDecl &D,
552552
return GV;
553553
}
554554

555+
Address CIRGenModule::createUnnamedGlobalFrom(const VarDecl &D,
556+
mlir::Attribute Constant,
557+
CharUnits Align) {
558+
auto functionName = [&](const DeclContext *DC) -> std::string {
559+
if (const auto *FD = dyn_cast<FunctionDecl>(DC)) {
560+
if (const auto *CC = dyn_cast<CXXConstructorDecl>(FD))
561+
return CC->getNameAsString();
562+
if (const auto *CD = dyn_cast<CXXDestructorDecl>(FD))
563+
return CD->getNameAsString();
564+
return std::string(getMangledName(FD));
565+
} else if (const auto *OM = dyn_cast<ObjCMethodDecl>(DC)) {
566+
return OM->getNameAsString();
567+
} else if (isa<BlockDecl>(DC)) {
568+
return "<block>";
569+
} else if (isa<CapturedDecl>(DC)) {
570+
return "<captured>";
571+
} else {
572+
llvm_unreachable("expected a function or method");
573+
}
574+
};
575+
576+
// Form a simple per-variable cache of these values in case we find we
577+
// want to reuse them.
578+
cir::GlobalOp &cacheEntry = initializerConstants[&D];
579+
if (!cacheEntry || cacheEntry.getInitialValue() != Constant) {
580+
auto ty = mlir::cast<mlir::TypedAttr>(Constant).getType();
581+
bool isConstant = true;
582+
mlir::ptr::MemorySpaceAttrInterface addrSpace =
583+
cir::toCIRLangAddressSpaceAttr(&getMLIRContext(),
584+
getGlobalVarAddressSpace(&D));
585+
586+
std::string name;
587+
if (D.hasGlobalStorage())
588+
name = getMangledName(&D).str() + ".const";
589+
else if (const DeclContext *DC = D.getParentFunctionOrMethod())
590+
name = ("__const." + functionName(DC) + "." + D.getName()).str();
591+
else
592+
llvm_unreachable("local variable has no parent function or method");
593+
594+
cir::GlobalOp gv = builder.createVersionedGlobal(
595+
getModule(), getLoc(D.getLocation()), name, ty, isConstant,
596+
cir::GlobalLinkageKind::PrivateLinkage, addrSpace);
597+
// TODO(cir): infer visibility from linkage in global op builder.
598+
gv.setVisibility(getMLIRVisibilityFromCIRLinkage(
599+
cir::GlobalLinkageKind::PrivateLinkage));
600+
gv.setInitialValueAttr(Constant);
601+
gv.setAlignment(Align.getAsAlign().value());
602+
// TODO(cir): Set unnamed address attribute when available in CIR
603+
604+
cacheEntry = gv;
605+
} else if (cacheEntry.getAlignment() < Align.getQuantity()) {
606+
cacheEntry.setAlignment(Align.getAsAlign().value());
607+
}
608+
609+
// Create a GetGlobalOp to get a pointer to the global
610+
mlir::Type eltTy = mlir::cast<mlir::TypedAttr>(Constant).getType();
611+
auto ptrTy = builder.getPointerTo(cacheEntry.getSymType(),
612+
cacheEntry.getAddrSpaceAttr());
613+
mlir::Value globalPtr = cir::GetGlobalOp::create(
614+
builder, getLoc(D.getLocation()), ptrTy, cacheEntry.getSymName());
615+
return Address(globalPtr, eltTy, Align);
616+
}
617+
555618
/// Add the initializer for 'D' to the global variable that has already been
556619
/// created for it. If the initializer has a different type than GV does, this
557620
/// may free GV and return a different one. Otherwise it just returns GV.

clang/lib/CIR/CodeGen/CIRGenExpr.cpp

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1028,6 +1028,56 @@ static LValue emitFunctionDeclLValue(CIRGenFunction &CGF, const Expr *E,
10281028
AlignmentSource::Decl);
10291029
}
10301030

1031+
// TODO: can be a shared AST helper with traditional codegen
1032+
static bool canEmitSpuriousReferenceToVariable(CIRGenFunction &CGF,
1033+
const DeclRefExpr *E,
1034+
const VarDecl *VD) {
1035+
// For a variable declared in an enclosing scope, do not emit a spurious
1036+
// reference even if we have a capture, as that will emit an unwarranted
1037+
// reference to our capture state, and will likely generate worse code than
1038+
// emitting a local copy.
1039+
if (E->refersToEnclosingVariableOrCapture())
1040+
return false;
1041+
1042+
// For a local declaration declared in this function, we can always reference
1043+
// it even if we don't have an odr-use.
1044+
if (VD->hasLocalStorage()) {
1045+
return VD->getDeclContext() ==
1046+
dyn_cast_or_null<DeclContext>(CGF.CurCodeDecl);
1047+
}
1048+
1049+
// For a global declaration, we can emit a reference to it if we know
1050+
// for sure that we are able to emit a definition of it.
1051+
VD = VD->getDefinition(CGF.getContext());
1052+
if (!VD)
1053+
return false;
1054+
1055+
// Don't emit a spurious reference if it might be to a variable that only
1056+
// exists on a different device / target.
1057+
// FIXME: This is unnecessarily broad. Check whether this would actually be a
1058+
// cross-target reference.
1059+
if (CGF.getLangOpts().OpenMP || CGF.getLangOpts().CUDA ||
1060+
CGF.getLangOpts().OpenCL) {
1061+
return false;
1062+
}
1063+
1064+
// We can emit a spurious reference only if the linkage implies that we'll
1065+
// be emitting a non-interposable symbol that will be retained until link
1066+
// time.
1067+
switch (CGF.CGM.getCIRLinkageVarDefinition(
1068+
VD, /*IsConstant=*/VD->getType().isConstantStorage(
1069+
CGF.getContext(), /*ExcludeCtor=*/false, /*ExcludeDtor=*/false))) {
1070+
case cir::GlobalLinkageKind::ExternalLinkage:
1071+
case cir::GlobalLinkageKind::LinkOnceODRLinkage:
1072+
case cir::GlobalLinkageKind::WeakODRLinkage:
1073+
case cir::GlobalLinkageKind::InternalLinkage:
1074+
case cir::GlobalLinkageKind::PrivateLinkage:
1075+
return true;
1076+
default:
1077+
return false;
1078+
}
1079+
}
1080+
10311081
LValue CIRGenFunction::emitDeclRefLValue(const DeclRefExpr *E) {
10321082
const NamedDecl *ND = E->getDecl();
10331083
QualType T = E->getType();
@@ -1041,7 +1091,43 @@ LValue CIRGenFunction::emitDeclRefLValue(const DeclRefExpr *E) {
10411091
!VD->isLocalVarDecl())
10421092
llvm_unreachable("NYI");
10431093

1044-
assert(E->isNonOdrUse() != NOUR_Constant && "not implemented");
1094+
// If this DeclRefExpr does not constitute an odr-use of the variable,
1095+
// we're not permitted to emit a reference to it in general, and it might
1096+
// not be captured if capture would be necessary for a use. Emit the
1097+
// constant value directly instead.
1098+
if (E->isNonOdrUse() == NOUR_Constant &&
1099+
(VD->getType()->isReferenceType() ||
1100+
!canEmitSpuriousReferenceToVariable(*this, E, VD))) {
1101+
VD->getAnyInitializer(VD);
1102+
mlir::Attribute val = ConstantEmitter(*this).emitAbstract(
1103+
E->getLocation(), *VD->evaluateValue(), VD->getType());
1104+
assert(val && "failed to emit constant expression");
1105+
1106+
Address addr = Address::invalid();
1107+
if (!VD->getType()->isReferenceType()) {
1108+
// Spill the constant value to a global.
1109+
addr = CGM.createUnnamedGlobalFrom(*VD, val,
1110+
getContext().getDeclAlign(VD));
1111+
mlir::Type varTy = getTypes().convertTypeForMem(VD->getType());
1112+
auto ptrTy = mlir::cast<cir::PointerType>(addr.getPointer().getType());
1113+
if (ptrTy.getPointee() != varTy) {
1114+
addr = addr.withElementType(getBuilder(), varTy);
1115+
}
1116+
} else {
1117+
// Should we be using the alignment of the constant pointer we emitted?
1118+
CharUnits alignment =
1119+
CGM.getNaturalTypeAlignment(E->getType(),
1120+
/* BaseInfo= */ nullptr,
1121+
/* TBAAInfo= */ nullptr,
1122+
/* forPointeeType= */ true);
1123+
mlir::Value ptrVal = getBuilder().getConstant(
1124+
getLoc(E->getExprLoc()), mlir::cast<mlir::TypedAttr>(val));
1125+
addr = makeNaturalAddressForPointer(ptrVal, T, alignment);
1126+
}
1127+
return makeAddrLValue(addr, T, AlignmentSource::Decl);
1128+
}
1129+
1130+
// FIXME(ogcg): Handle other kinds of non-odr-use DeclRefExprs.
10451131

10461132
// Check for captured variables.
10471133
if (E->refersToEnclosingVariableOrCapture()) {

clang/lib/CIR/CodeGen/CIRGenModule.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,8 @@ class CIRGenModule : public CIRGenTypeCache {
237237
void HandleCXXStaticMemberVarInstantiation(VarDecl *VD);
238238

239239
llvm::DenseMap<const Decl *, cir::GlobalOp> StaticLocalDeclMap;
240+
llvm::DenseMap<const Decl *, cir::GlobalOp> staticLocalDeclGuardMap;
241+
llvm::DenseMap<const VarDecl *, cir::GlobalOp> initializerConstants;
240242
llvm::DenseMap<llvm::StringRef, mlir::Value> Globals;
241243
mlir::Operation *getGlobalValue(llvm::StringRef Ref);
242244
mlir::Value getGlobalValue(const clang::Decl *D);
@@ -259,6 +261,9 @@ class CIRGenModule : public CIRGenTypeCache {
259261
cir::GlobalOp getOrCreateStaticVarDecl(const VarDecl &D,
260262
cir::GlobalLinkageKind Linkage);
261263

264+
Address createUnnamedGlobalFrom(const VarDecl &D, mlir::Attribute Constant,
265+
CharUnits Align);
266+
262267
cir::GlobalOp getOrCreateCIRGlobal(const VarDecl *D, mlir::Type Ty,
263268
ForDefinition_t IsForDefinition);
264269

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
2+
// RUN: FileCheck %s --input-file=%t.cir --check-prefix=CIR
3+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t.ll
4+
// RUN: FileCheck %s --input-file=%t.ll --check-prefix=LLVM
5+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ogcg.ll
6+
// RUN: FileCheck %s --input-file=%t.ogcg.ll --check-prefix=OGCG
7+
//
8+
// Test non-ODR-use constant expressions
9+
10+
namespace llvm {
11+
template<typename ValueTy> class StringMapEntry {};
12+
template<typename ValueTy> class StringMapIterBase {
13+
public:
14+
StringMapEntry<ValueTy>& operator*() const;
15+
StringMapIterBase& operator++();
16+
friend bool operator!=(const StringMapIterBase& LHS, const StringMapIterBase& RHS);
17+
};
18+
template<typename ValueTy> class StringMap {
19+
public:
20+
StringMapIterBase<ValueTy> begin();
21+
StringMapIterBase<ValueTy> end();
22+
};
23+
struct EmptyStringSetTag {};
24+
template<class AllocatorTy = int> class StringSet : public StringMap<EmptyStringSetTag> {};
25+
}
26+
27+
namespace clang {
28+
// Static variable that will be referenced without ODR-use in range-for
29+
static llvm::StringSet<> BuiltinClasses;
30+
31+
void EmitBuiltins() {
32+
// This range-for iterates over BuiltinClasses without constituting an ODR-use
33+
// because it's used in an unevaluated context for the range-for desugaring
34+
for (const auto &Entry : BuiltinClasses) {
35+
}
36+
}
37+
}
38+
39+
// CIR: cir.global "private" internal dso_local @_ZN5clangL14BuiltinClassesE
40+
// CIR: cir.func {{.*}}@_ZN5clang12EmitBuiltinsEv()
41+
// CIR: %{{.*}} = cir.const #cir.global_view<@_ZN5clangL14BuiltinClassesE>
42+
43+
// LLVM: @_ZN5clangL14BuiltinClassesE = internal global
44+
// LLVM: define {{.*}}@_ZN5clang12EmitBuiltinsEv()
45+
// LLVM: %{{.*}} = alloca ptr
46+
// LLVM: store ptr @_ZN5clangL14BuiltinClassesE
47+
48+
// OGCG: @_ZN5clangL14BuiltinClassesE = internal global
49+
// OGCG: define {{.*}}@_ZN5clang12EmitBuiltinsEv()
50+
// OGCG: %{{.*}} = alloca ptr
51+
// OGCG: store ptr @_ZN5clangL14BuiltinClassesE
52+
53+
// Test non-reference type NOUR_Constant (local constexpr in lambda)
54+
struct A { int x, y[2]; int arr[3]; };
55+
// CIR-DAG: @__const._Z1fi.a
56+
// LLVM-DAG: @__const._Z1fi.a
57+
// OGCG-DAG: @__const._Z1fi.a
58+
int f(int i) {
59+
constexpr A a = {1, 2, 3, 4, 5, 6};
60+
return [] (int n, int A::*p) {
61+
return (n >= 0 ? a.arr[n] : (n == -1 ? a.*p : a.y[2 - n]));
62+
}(i, &A::x);
63+
}
64+
// CIR: cir.get_global @__const._Z1fi.a
65+
// LLVM: getelementptr {{.*}} @__const._Z1fi.a
66+
// OGCG: getelementptr inbounds {{.*}} @__const._Z1fi.a

clang/test/CIR/crashes/non-odr-use-constant.cpp

Lines changed: 0 additions & 31 deletions
This file was deleted.

0 commit comments

Comments
 (0)