Skip to content

Commit 74cabdb

Browse files
authored
[CIR] Add basic support for operator new (#145802)
This adds the code to handle operator new expressions in ClangIR.
1 parent 3e9fd49 commit 74cabdb

File tree

7 files changed

+377
-0
lines changed

7 files changed

+377
-0
lines changed

clang/include/clang/CIR/MissingFeatures.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,9 @@ struct MissingFeatures {
110110
static bool opCallLandingPad() { return false; }
111111
static bool opCallContinueBlock() { return false; }
112112

113+
// CXXNewExpr
114+
static bool exprNewNullCheck() { return false; }
115+
113116
// FnInfoOpts -- This is used to track whether calls are chain calls or
114117
// instance methods. Classic codegen uses chain call to track and extra free
115118
// register for x86 and uses instance method as a condition for a thunk
@@ -170,6 +173,7 @@ struct MissingFeatures {
170173
static bool armComputeVolatileBitfields() { return false; }
171174
static bool asmLabelAttr() { return false; }
172175
static bool astVarDeclInterface() { return false; }
176+
static bool attributeBuiltin() { return false; }
173177
static bool attributeNoBuiltin() { return false; }
174178
static bool bitfields() { return false; }
175179
static bool builtinCall() { return false; }

clang/lib/CIR/CodeGen/CIRGenCXXExpr.cpp

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,3 +197,195 @@ RValue CIRGenFunction::emitCXXMemberOrOperatorCall(
197197
assert(!cir::MissingFeatures::opCallMustTail());
198198
return emitCall(fnInfo, callee, returnValue, args, nullptr, loc);
199199
}
200+
201+
static mlir::Value emitCXXNewAllocSize(CIRGenFunction &cgf, const CXXNewExpr *e,
202+
unsigned minElements,
203+
mlir::Value &numElements,
204+
mlir::Value &sizeWithoutCookie) {
205+
QualType type = e->getAllocatedType();
206+
mlir::Location loc = cgf.getLoc(e->getSourceRange());
207+
208+
if (!e->isArray()) {
209+
CharUnits typeSize = cgf.getContext().getTypeSizeInChars(type);
210+
sizeWithoutCookie = cgf.getBuilder().getConstant(
211+
loc, cir::IntAttr::get(cgf.SizeTy, typeSize.getQuantity()));
212+
return sizeWithoutCookie;
213+
}
214+
215+
cgf.cgm.errorNYI(e->getSourceRange(), "emitCXXNewAllocSize: array");
216+
return {};
217+
}
218+
219+
static void storeAnyExprIntoOneUnit(CIRGenFunction &cgf, const Expr *init,
220+
QualType allocType, Address newPtr,
221+
AggValueSlot::Overlap_t mayOverlap) {
222+
// FIXME: Refactor with emitExprAsInit.
223+
switch (cgf.getEvaluationKind(allocType)) {
224+
case cir::TEK_Scalar:
225+
cgf.emitScalarInit(init, cgf.getLoc(init->getSourceRange()),
226+
cgf.makeAddrLValue(newPtr, allocType), false);
227+
return;
228+
case cir::TEK_Complex:
229+
cgf.cgm.errorNYI(init->getSourceRange(),
230+
"storeAnyExprIntoOneUnit: complex");
231+
return;
232+
case cir::TEK_Aggregate: {
233+
assert(!cir::MissingFeatures::aggValueSlotGC());
234+
assert(!cir::MissingFeatures::sanitizers());
235+
AggValueSlot slot = AggValueSlot::forAddr(
236+
newPtr, allocType.getQualifiers(), AggValueSlot::IsDestructed,
237+
AggValueSlot::IsNotAliased, mayOverlap, AggValueSlot::IsNotZeroed);
238+
cgf.emitAggExpr(init, slot);
239+
return;
240+
}
241+
}
242+
llvm_unreachable("bad evaluation kind");
243+
}
244+
245+
static void emitNewInitializer(CIRGenFunction &cgf, const CXXNewExpr *e,
246+
QualType elementType, mlir::Type elementTy,
247+
Address newPtr, mlir::Value numElements,
248+
mlir::Value allocSizeWithoutCookie) {
249+
assert(!cir::MissingFeatures::generateDebugInfo());
250+
if (e->isArray()) {
251+
cgf.cgm.errorNYI(e->getSourceRange(), "emitNewInitializer: array");
252+
} else if (const Expr *init = e->getInitializer()) {
253+
storeAnyExprIntoOneUnit(cgf, init, e->getAllocatedType(), newPtr,
254+
AggValueSlot::DoesNotOverlap);
255+
}
256+
}
257+
258+
/// Emit a call to an operator new or operator delete function, as implicitly
259+
/// created by new-expressions and delete-expressions.
260+
static RValue emitNewDeleteCall(CIRGenFunction &cgf,
261+
const FunctionDecl *calleeDecl,
262+
const FunctionProtoType *calleeType,
263+
const CallArgList &args) {
264+
cir::CIRCallOpInterface callOrTryCall;
265+
cir::FuncOp calleePtr = cgf.cgm.getAddrOfFunction(calleeDecl);
266+
CIRGenCallee callee =
267+
CIRGenCallee::forDirect(calleePtr, GlobalDecl(calleeDecl));
268+
RValue rv =
269+
cgf.emitCall(cgf.cgm.getTypes().arrangeFreeFunctionCall(args, calleeType),
270+
callee, ReturnValueSlot(), args, &callOrTryCall);
271+
272+
/// C++1y [expr.new]p10:
273+
/// [In a new-expression,] an implementation is allowed to omit a call
274+
/// to a replaceable global allocation function.
275+
///
276+
/// We model such elidable calls with the 'builtin' attribute.
277+
assert(!cir::MissingFeatures::attributeBuiltin());
278+
return rv;
279+
}
280+
281+
mlir::Value CIRGenFunction::emitCXXNewExpr(const CXXNewExpr *e) {
282+
// The element type being allocated.
283+
QualType allocType = getContext().getBaseElementType(e->getAllocatedType());
284+
285+
// 1. Build a call to the allocation function.
286+
FunctionDecl *allocator = e->getOperatorNew();
287+
288+
// If there is a brace-initializer, cannot allocate fewer elements than inits.
289+
unsigned minElements = 0;
290+
if (e->isArray() && e->hasInitializer()) {
291+
cgm.errorNYI(e->getSourceRange(), "emitCXXNewExpr: array initializer");
292+
}
293+
294+
mlir::Value numElements = nullptr;
295+
mlir::Value allocSizeWithoutCookie = nullptr;
296+
mlir::Value allocSize = emitCXXNewAllocSize(
297+
*this, e, minElements, numElements, allocSizeWithoutCookie);
298+
CharUnits allocAlign = getContext().getTypeAlignInChars(allocType);
299+
300+
// Emit the allocation call.
301+
Address allocation = Address::invalid();
302+
CallArgList allocatorArgs;
303+
if (allocator->isReservedGlobalPlacementOperator()) {
304+
cgm.errorNYI(e->getSourceRange(),
305+
"emitCXXNewExpr: reserved global placement operator");
306+
} else {
307+
const FunctionProtoType *allocatorType =
308+
allocator->getType()->castAs<FunctionProtoType>();
309+
unsigned paramsToSkip = 0;
310+
311+
// The allocation size is the first argument.
312+
QualType sizeType = getContext().getSizeType();
313+
allocatorArgs.add(RValue::get(allocSize), sizeType);
314+
++paramsToSkip;
315+
316+
if (allocSize != allocSizeWithoutCookie) {
317+
CharUnits cookieAlign = getSizeAlign(); // FIXME: Ask the ABI.
318+
allocAlign = std::max(allocAlign, cookieAlign);
319+
}
320+
321+
// The allocation alignment may be passed as the second argument.
322+
if (e->passAlignment()) {
323+
cgm.errorNYI(e->getSourceRange(), "emitCXXNewExpr: pass alignment");
324+
}
325+
326+
// FIXME: Why do we not pass a CalleeDecl here?
327+
emitCallArgs(allocatorArgs, allocatorType, e->placement_arguments(),
328+
AbstractCallee(), paramsToSkip);
329+
RValue rv =
330+
emitNewDeleteCall(*this, allocator, allocatorType, allocatorArgs);
331+
332+
// Set !heapallocsite metadata on the call to operator new.
333+
assert(!cir::MissingFeatures::generateDebugInfo());
334+
335+
// If this was a call to a global replaceable allocation function that does
336+
// not take an alignment argument, the allocator is known to produce storage
337+
// that's suitably aligned for any object that fits, up to a known
338+
// threshold. Otherwise assume it's suitably aligned for the allocated type.
339+
CharUnits allocationAlign = allocAlign;
340+
if (!e->passAlignment() &&
341+
allocator->isReplaceableGlobalAllocationFunction()) {
342+
const TargetInfo &target = cgm.getASTContext().getTargetInfo();
343+
unsigned allocatorAlign = llvm::bit_floor(std::min<uint64_t>(
344+
target.getNewAlign(), getContext().getTypeSize(allocType)));
345+
allocationAlign = std::max(
346+
allocationAlign, getContext().toCharUnitsFromBits(allocatorAlign));
347+
}
348+
349+
mlir::Value allocPtr = rv.getValue();
350+
allocation = Address(
351+
allocPtr, mlir::cast<cir::PointerType>(allocPtr.getType()).getPointee(),
352+
allocationAlign);
353+
}
354+
355+
// Emit a null check on the allocation result if the allocation
356+
// function is allowed to return null (because it has a non-throwing
357+
// exception spec or is the reserved placement new) and we have an
358+
// interesting initializer will be running sanitizers on the initialization.
359+
bool nullCheck = e->shouldNullCheckAllocation() &&
360+
(!allocType.isPODType(getContext()) || e->hasInitializer());
361+
assert(!cir::MissingFeatures::exprNewNullCheck());
362+
if (nullCheck)
363+
cgm.errorNYI(e->getSourceRange(), "emitCXXNewExpr: null check");
364+
365+
// If there's an operator delete, enter a cleanup to call it if an
366+
// exception is thrown.
367+
if (e->getOperatorDelete() &&
368+
!e->getOperatorDelete()->isReservedGlobalPlacementOperator())
369+
cgm.errorNYI(e->getSourceRange(), "emitCXXNewExpr: operator delete");
370+
371+
if (allocSize != allocSizeWithoutCookie)
372+
cgm.errorNYI(e->getSourceRange(), "emitCXXNewExpr: array with cookies");
373+
374+
mlir::Type elementTy = convertTypeForMem(allocType);
375+
Address result = builder.createElementBitCast(getLoc(e->getSourceRange()),
376+
allocation, elementTy);
377+
378+
// Passing pointer through launder.invariant.group to avoid propagation of
379+
// vptrs information which may be included in previous type.
380+
// To not break LTO with different optimizations levels, we do it regardless
381+
// of optimization level.
382+
if (cgm.getCodeGenOpts().StrictVTablePointers &&
383+
allocator->isReservedGlobalPlacementOperator())
384+
cgm.errorNYI(e->getSourceRange(), "emitCXXNewExpr: strict vtable pointers");
385+
386+
assert(!cir::MissingFeatures::sanitizers());
387+
388+
emitNewInitializer(*this, e, allocType, elementTy, result, numElements,
389+
allocSizeWithoutCookie);
390+
return result.getPointer();
391+
}

clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,10 @@ class ScalarExprEmitter : public StmtVisitor<ScalarExprEmitter, mlir::Value> {
609609

610610
mlir::Value VisitCXXThisExpr(CXXThisExpr *te) { return cgf.loadCXXThis(); }
611611

612+
mlir::Value VisitCXXNewExpr(const CXXNewExpr *e) {
613+
return cgf.emitCXXNewExpr(e);
614+
}
615+
612616
/// Emit a conversion from the specified type to the specified destination
613617
/// type, both of which are CIR scalar types.
614618
/// TODO: do we need ScalarConversionOpts here? Should be done in another

clang/lib/CIR/CodeGen/CIRGenFunction.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -770,6 +770,15 @@ class CIRGenFunction : public CIRGenTypeCache {
770770
const CIRGenCallee &callee, ReturnValueSlot returnValue,
771771
const CallArgList &args, cir::CIRCallOpInterface *callOp,
772772
mlir::Location loc);
773+
RValue emitCall(const CIRGenFunctionInfo &funcInfo,
774+
const CIRGenCallee &callee, ReturnValueSlot returnValue,
775+
const CallArgList &args,
776+
cir::CIRCallOpInterface *callOrTryCall = nullptr) {
777+
assert(currSrcLoc && "source location must have been set");
778+
return emitCall(funcInfo, callee, returnValue, args, callOrTryCall,
779+
*currSrcLoc);
780+
}
781+
773782
RValue emitCall(clang::QualType calleeTy, const CIRGenCallee &callee,
774783
const clang::CallExpr *e, ReturnValueSlot returnValue);
775784
void emitCallArg(CallArgList &args, const clang::Expr *e,
@@ -836,6 +845,8 @@ class CIRGenFunction : public CIRGenTypeCache {
836845
clang::NestedNameSpecifier *qualifier, bool isArrow,
837846
const clang::Expr *base);
838847

848+
mlir::Value emitCXXNewExpr(const CXXNewExpr *e);
849+
839850
RValue emitCXXOperatorMemberCallExpr(const CXXOperatorCallExpr *e,
840851
const CXXMethodDecl *md,
841852
ReturnValueSlot returnValue);

clang/lib/CIR/CodeGen/CIRGenModule.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ CIRGenModule::CIRGenModule(mlir::MLIRContext &mlirContext,
9595
// TODO(CIR): Should be updated once TypeSizeInfoAttr is upstreamed
9696
const unsigned sizeTypeSize =
9797
astContext.getTypeSize(astContext.getSignedSizeType());
98+
SizeAlignInBytes = astContext.toCharUnitsFromBits(sizeTypeSize).getQuantity();
9899
// In CIRGenTypeCache, UIntPtrTy and SizeType are fields of the same union
99100
UIntPtrTy =
100101
cir::IntType::get(&getMLIRContext(), sizeTypeSize, /*isSigned=*/false);

clang/lib/CIR/CodeGen/CIRGenTypeCache.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,13 @@ struct CIRGenTypeCache {
6666
unsigned char PointerSizeInBytes;
6767
};
6868

69+
/// The alignment of size_t.
70+
unsigned char SizeAlignInBytes;
71+
72+
clang::CharUnits getSizeAlign() const {
73+
return clang::CharUnits::fromQuantity(SizeAlignInBytes);
74+
}
75+
6976
clang::CharUnits getPointerAlign() const {
7077
return clang::CharUnits::fromQuantity(PointerAlignInBytes);
7178
}

0 commit comments

Comments
 (0)