Skip to content

Commit 85265a9

Browse files
authored
[CIR] Upstream support for variable length arrays (#163297)
This adds the code needed to emit alloca operations for variable length array local variables and the necessary calls to stacksave and stackrestore to adjust the local stack as the array variables go in an out of scope.
1 parent 2d23a60 commit 85265a9

File tree

9 files changed

+470
-28
lines changed

9 files changed

+470
-28
lines changed

clang/lib/CIR/CodeGen/CIRGenBuilder.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,14 @@ class CIRGenBuilderTy : public cir::CIRBaseBuilderTy {
519519
return createGlobal(module, loc, uniqueName, type, isConstant, linkage);
520520
}
521521

522+
cir::StackSaveOp createStackSave(mlir::Location loc, mlir::Type ty) {
523+
return cir::StackSaveOp::create(*this, loc, ty);
524+
}
525+
526+
cir::StackRestoreOp createStackRestore(mlir::Location loc, mlir::Value v) {
527+
return cir::StackRestoreOp::create(*this, loc, v);
528+
}
529+
522530
mlir::Value createSetBitfield(mlir::Location loc, mlir::Type resultType,
523531
Address dstAddr, mlir::Type storageType,
524532
mlir::Value src, const CIRGenBitFieldInfo &info,

clang/lib/CIR/CodeGen/CIRGenDecl.cpp

Lines changed: 72 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -44,38 +44,70 @@ CIRGenFunction::emitAutoVarAlloca(const VarDecl &d,
4444

4545
// If the type is variably-modified, emit all the VLA sizes for it.
4646
if (ty->isVariablyModifiedType())
47-
cgm.errorNYI(d.getSourceRange(), "emitAutoVarDecl: variably modified type");
47+
emitVariablyModifiedType(ty);
4848

4949
assert(!cir::MissingFeatures::openMP());
5050

5151
Address address = Address::invalid();
52-
if (!ty->isConstantSizeType())
53-
cgm.errorNYI(d.getSourceRange(), "emitAutoVarDecl: non-constant size type");
54-
55-
// A normal fixed sized variable becomes an alloca in the entry block,
56-
// unless:
57-
// - it's an NRVO variable.
58-
// - we are compiling OpenMP and it's an OpenMP local variable.
59-
if (nrvo) {
60-
// The named return value optimization: allocate this variable in the
61-
// return slot, so that we can elide the copy when returning this
62-
// variable (C++0x [class.copy]p34).
63-
address = returnValue;
64-
65-
if (const RecordDecl *rd = ty->getAsRecordDecl()) {
66-
if (const auto *cxxrd = dyn_cast<CXXRecordDecl>(rd);
67-
(cxxrd && !cxxrd->hasTrivialDestructor()) ||
68-
rd->isNonTrivialToPrimitiveDestroy())
69-
cgm.errorNYI(d.getSourceRange(), "emitAutoVarAlloca: set NRVO flag");
52+
if (ty->isConstantSizeType()) {
53+
// A normal fixed sized variable becomes an alloca in the entry block,
54+
// unless:
55+
// - it's an NRVO variable.
56+
// - we are compiling OpenMP and it's an OpenMP local variable.
57+
if (nrvo) {
58+
// The named return value optimization: allocate this variable in the
59+
// return slot, so that we can elide the copy when returning this
60+
// variable (C++0x [class.copy]p34).
61+
address = returnValue;
62+
63+
if (const RecordDecl *rd = ty->getAsRecordDecl()) {
64+
if (const auto *cxxrd = dyn_cast<CXXRecordDecl>(rd);
65+
(cxxrd && !cxxrd->hasTrivialDestructor()) ||
66+
rd->isNonTrivialToPrimitiveDestroy())
67+
cgm.errorNYI(d.getSourceRange(), "emitAutoVarAlloca: set NRVO flag");
68+
}
69+
} else {
70+
// A normal fixed sized variable becomes an alloca in the entry block,
71+
mlir::Type allocaTy = convertTypeForMem(ty);
72+
// Create the temp alloca and declare variable using it.
73+
address = createTempAlloca(allocaTy, alignment, loc, d.getName(),
74+
/*arraySize=*/nullptr, /*alloca=*/nullptr, ip);
75+
declare(address.getPointer(), &d, ty, getLoc(d.getSourceRange()),
76+
alignment);
7077
}
7178
} else {
72-
// A normal fixed sized variable becomes an alloca in the entry block,
73-
mlir::Type allocaTy = convertTypeForMem(ty);
74-
// Create the temp alloca and declare variable using it.
75-
address = createTempAlloca(allocaTy, alignment, loc, d.getName(),
76-
/*arraySize=*/nullptr, /*alloca=*/nullptr, ip);
77-
declare(address.getPointer(), &d, ty, getLoc(d.getSourceRange()),
78-
alignment);
79+
// Non-constant size type
80+
assert(!cir::MissingFeatures::openMP());
81+
if (!didCallStackSave) {
82+
// Save the stack.
83+
cir::PointerType defaultTy = AllocaInt8PtrTy;
84+
CharUnits align = CharUnits::fromQuantity(
85+
cgm.getDataLayout().getAlignment(defaultTy, false));
86+
Address stack = createTempAlloca(defaultTy, align, loc, "saved_stack");
87+
88+
mlir::Value v = builder.createStackSave(loc, defaultTy);
89+
assert(v.getType() == AllocaInt8PtrTy);
90+
builder.createStore(loc, v, stack);
91+
92+
didCallStackSave = true;
93+
94+
// Push a cleanup block and restore the stack there.
95+
// FIXME: in general circumstances, this should be an EH cleanup.
96+
pushStackRestore(NormalCleanup, stack);
97+
}
98+
99+
VlaSizePair vlaSize = getVLASize(ty);
100+
mlir::Type memTy = convertTypeForMem(vlaSize.type);
101+
102+
// Allocate memory for the array.
103+
address =
104+
createTempAlloca(memTy, alignment, loc, d.getName(), vlaSize.numElts,
105+
/*alloca=*/nullptr, builder.saveInsertionPoint());
106+
107+
// If we have debug info enabled, properly describe the VLA dimensions for
108+
// this type by registering the vla size expression for each of the
109+
// dimensions.
110+
assert(!cir::MissingFeatures::generateDebugInfo());
79111
}
80112

81113
emission.addr = address;
@@ -696,6 +728,16 @@ struct DestroyObject final : EHScopeStack::Cleanup {
696728
cgf.emitDestroy(addr, type, destroyer);
697729
}
698730
};
731+
732+
struct CallStackRestore final : EHScopeStack::Cleanup {
733+
Address stack;
734+
CallStackRestore(Address stack) : stack(stack) {}
735+
void emit(CIRGenFunction &cgf) override {
736+
mlir::Location loc = stack.getPointer().getLoc();
737+
mlir::Value v = cgf.getBuilder().createLoad(loc, stack);
738+
cgf.getBuilder().createStackRestore(loc, v);
739+
}
740+
};
699741
} // namespace
700742

701743
void CIRGenFunction::pushDestroy(CleanupKind cleanupKind, Address addr,
@@ -805,6 +847,10 @@ CIRGenFunction::getDestroyer(QualType::DestructionKind kind) {
805847
llvm_unreachable("Unknown DestructionKind");
806848
}
807849

850+
void CIRGenFunction::pushStackRestore(CleanupKind kind, Address spMem) {
851+
ehStack.pushCleanup<CallStackRestore>(kind, spMem);
852+
}
853+
808854
/// Enter a destroy cleanup for the given local variable.
809855
void CIRGenFunction::emitAutoVarTypeCleanup(
810856
const CIRGenFunction::AutoVarEmission &emission,

clang/lib/CIR/CodeGen/CIRGenExpr.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2068,7 +2068,7 @@ mlir::Value CIRGenFunction::emitAlloca(StringRef name, mlir::Type ty,
20682068
mlir::OpBuilder::InsertionGuard guard(builder);
20692069
builder.restoreInsertionPoint(ip);
20702070
addr = builder.createAlloca(loc, /*addr type*/ localVarPtrTy,
2071-
/*var type*/ ty, name, alignIntAttr);
2071+
/*var type*/ ty, name, alignIntAttr, arraySize);
20722072
assert(!cir::MissingFeatures::astVarDeclInterface());
20732073
}
20742074
return addr;

clang/lib/CIR/CodeGen/CIRGenFunction.cpp

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,8 @@ void CIRGenFunction::startFunction(GlobalDecl gd, QualType returnType,
410410
curFn = fn;
411411

412412
const Decl *d = gd.getDecl();
413+
414+
didCallStackSave = false;
413415
curCodeDecl = d;
414416
const auto *fd = dyn_cast_or_null<FunctionDecl>(d);
415417
curFuncDecl = d->getNonClosureContext();
@@ -1006,6 +1008,41 @@ mlir::Value CIRGenFunction::emitAlignmentAssumption(
10061008
offsetValue);
10071009
}
10081010

1011+
CIRGenFunction::VlaSizePair CIRGenFunction::getVLASize(QualType type) {
1012+
const VariableArrayType *vla =
1013+
cgm.getASTContext().getAsVariableArrayType(type);
1014+
assert(vla && "type was not a variable array type!");
1015+
return getVLASize(vla);
1016+
}
1017+
1018+
CIRGenFunction::VlaSizePair
1019+
CIRGenFunction::getVLASize(const VariableArrayType *type) {
1020+
// The number of elements so far; always size_t.
1021+
mlir::Value numElements;
1022+
1023+
QualType elementType;
1024+
do {
1025+
elementType = type->getElementType();
1026+
mlir::Value vlaSize = vlaSizeMap[type->getSizeExpr()];
1027+
assert(vlaSize && "no size for VLA!");
1028+
assert(vlaSize.getType() == SizeTy);
1029+
1030+
if (!numElements) {
1031+
numElements = vlaSize;
1032+
} else {
1033+
// It's undefined behavior if this wraps around, so mark it that way.
1034+
// FIXME: Teach -fsanitize=undefined to trap this.
1035+
1036+
numElements =
1037+
builder.createMul(numElements.getLoc(), numElements, vlaSize,
1038+
cir::OverflowBehavior::NoUnsignedWrap);
1039+
}
1040+
} while ((type = getContext().getAsVariableArrayType(elementType)));
1041+
1042+
assert(numElements && "Undefined elements number");
1043+
return {numElements, elementType};
1044+
}
1045+
10091046
// TODO(cir): Most of this function can be shared between CIRGen
10101047
// and traditional LLVM codegen
10111048
void CIRGenFunction::emitVariablyModifiedType(QualType type) {
@@ -1086,7 +1123,26 @@ void CIRGenFunction::emitVariablyModifiedType(QualType type) {
10861123
break;
10871124

10881125
case Type::VariableArray: {
1089-
cgm.errorNYI("CIRGenFunction::emitVariablyModifiedType VLA");
1126+
// Losing element qualification here is fine.
1127+
const VariableArrayType *vat = cast<clang::VariableArrayType>(ty);
1128+
1129+
// Unknown size indication requires no size computation.
1130+
// Otherwise, evaluate and record it.
1131+
if (const Expr *sizeExpr = vat->getSizeExpr()) {
1132+
// It's possible that we might have emitted this already,
1133+
// e.g. with a typedef and a pointer to it.
1134+
mlir::Value &entry = vlaSizeMap[sizeExpr];
1135+
if (!entry) {
1136+
mlir::Value size = emitScalarExpr(sizeExpr);
1137+
assert(!cir::MissingFeatures::sanitizers());
1138+
1139+
// Always zexting here would be wrong if it weren't
1140+
// undefined behavior to have a negative bound.
1141+
// FIXME: What about when size's type is larger than size_t?
1142+
entry = builder.createIntCast(size, SizeTy);
1143+
}
1144+
}
1145+
type = vat->getElementType();
10901146
break;
10911147
}
10921148

clang/lib/CIR/CodeGen/CIRGenFunction.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,10 @@ class CIRGenFunction : public CIRGenTypeCache {
149149
using SymTableTy = llvm::ScopedHashTable<const clang::Decl *, mlir::Value>;
150150
SymTableTy symbolTable;
151151

152+
/// Whether a cir.stacksave operation has been added. Used to avoid
153+
/// inserting cir.stacksave for multiple VLAs in the same scope.
154+
bool didCallStackSave = false;
155+
152156
/// Whether or not a Microsoft-style asm block has been processed within
153157
/// this fuction. These can potentially set the return value.
154158
bool sawAsmBlock = false;
@@ -188,6 +192,14 @@ class CIRGenFunction : public CIRGenTypeCache {
188192
llvm::DenseMap<const OpaqueValueExpr *, LValue> opaqueLValues;
189193
llvm::DenseMap<const OpaqueValueExpr *, RValue> opaqueRValues;
190194

195+
// This keeps track of the associated size for each VLA type.
196+
// We track this by the size expression rather than the type itself because
197+
// in certain situations, like a const qualifier applied to an VLA typedef,
198+
// multiple VLA types can share the same size expression.
199+
// FIXME: Maybe this could be a stack of maps that is pushed/popped as we
200+
// enter/leave scopes.
201+
llvm::DenseMap<const Expr *, mlir::Value> vlaSizeMap;
202+
191203
public:
192204
/// A non-RAII class containing all the information about a bound
193205
/// opaque value. OpaqueValueMapping, below, is a RAII wrapper for
@@ -436,6 +448,20 @@ class CIRGenFunction : public CIRGenTypeCache {
436448
}
437449
};
438450

451+
struct VlaSizePair {
452+
mlir::Value numElts;
453+
QualType type;
454+
455+
VlaSizePair(mlir::Value num, QualType ty) : numElts(num), type(ty) {}
456+
};
457+
458+
/// Returns an MLIR::Value+QualType pair that corresponds to the size,
459+
/// in non-variably-sized elements, of a variable length array type,
460+
/// plus that largest non-variably-sized element type. Assumes that
461+
/// the type has already been emitted with emitVariablyModifiedType.
462+
VlaSizePair getVLASize(const VariableArrayType *type);
463+
VlaSizePair getVLASize(QualType type);
464+
439465
void finishFunction(SourceLocation endLoc);
440466

441467
/// Determine whether the given initializer is trivial in the sense
@@ -583,6 +609,8 @@ class CIRGenFunction : public CIRGenTypeCache {
583609
return needsEHCleanup(kind) ? NormalAndEHCleanup : NormalCleanup;
584610
}
585611

612+
void pushStackRestore(CleanupKind kind, Address spMem);
613+
586614
/// Set the address of a local variable.
587615
void setAddrOfLocalVar(const clang::VarDecl *vd, Address addr) {
588616
assert(!localDeclMap.count(vd) && "Decl already exists in LocalDeclMap!");
@@ -854,6 +882,7 @@ class CIRGenFunction : public CIRGenTypeCache {
854882

855883
protected:
856884
bool performCleanup;
885+
bool oldDidCallStackSave;
857886

858887
private:
859888
RunCleanupsScope(const RunCleanupsScope &) = delete;
@@ -867,6 +896,8 @@ class CIRGenFunction : public CIRGenTypeCache {
867896
explicit RunCleanupsScope(CIRGenFunction &cgf)
868897
: performCleanup(true), cgf(cgf) {
869898
cleanupStackDepth = cgf.ehStack.stable_begin();
899+
oldDidCallStackSave = cgf.didCallStackSave;
900+
cgf.didCallStackSave = false;
870901
oldCleanupStackDepth = cgf.currentCleanupStackDepth;
871902
cgf.currentCleanupStackDepth = cleanupStackDepth;
872903
}
@@ -883,6 +914,7 @@ class CIRGenFunction : public CIRGenTypeCache {
883914
assert(performCleanup && "Already forced cleanup");
884915
{
885916
mlir::OpBuilder::InsertionGuard guard(cgf.getBuilder());
917+
cgf.didCallStackSave = oldDidCallStackSave;
886918
cgf.popCleanupBlocks(cleanupStackDepth);
887919
performCleanup = false;
888920
cgf.currentCleanupStackDepth = oldCleanupStackDepth;

clang/lib/CIR/CodeGen/CIRGenModule.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ CIRGenModule::CIRGenModule(mlir::MLIRContext &mlirContext,
8888
FP80Ty = cir::FP80Type::get(&getMLIRContext());
8989
FP128Ty = cir::FP128Type::get(&getMLIRContext());
9090

91+
AllocaInt8PtrTy = cir::PointerType::get(UInt8Ty, cirAllocaAddressSpace);
92+
9193
PointerAlignInBytes =
9294
astContext
9395
.toCharUnitsFromBits(

clang/lib/CIR/CodeGen/CIRGenTypeCache.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ struct CIRGenTypeCache {
6565
cir::PointerType VoidPtrTy;
6666
cir::PointerType UInt8PtrTy;
6767

68+
/// void* in alloca address space
69+
cir::PointerType AllocaInt8PtrTy;
70+
6871
/// The size and alignment of a pointer into the generic address space.
6972
union {
7073
unsigned char PointerAlignInBytes;

clang/lib/CIR/CodeGen/CIRGenTypes.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,16 @@ mlir::Type CIRGenTypes::convertType(QualType type) {
420420
break;
421421
}
422422

423+
case Type::VariableArray: {
424+
const VariableArrayType *a = cast<VariableArrayType>(ty);
425+
if (a->getIndexTypeCVRQualifiers() != 0)
426+
cgm.errorNYI(SourceLocation(), "non trivial array types", type);
427+
// VLAs resolve to the innermost element type; this matches
428+
// the return of alloca, and there isn't any obviously better choice.
429+
resultType = convertTypeForMem(a->getElementType());
430+
break;
431+
}
432+
423433
case Type::IncompleteArray: {
424434
const IncompleteArrayType *arrTy = cast<IncompleteArrayType>(ty);
425435
if (arrTy->getIndexTypeCVRQualifiers() != 0)

0 commit comments

Comments
 (0)