Skip to content

Commit 3d82770

Browse files
mmhaandykaylor
authored andcommitted
[CIR] Implement __builtin_object_size and __builtin_dynamic_object_size (llvm#166191)
* Add cir.objsize operation to CIR dialect * Add lowering for cir.objsize operation to LLVM dialect * Add codegen for __builtin_object_size and __builtin_dynamic_object_size Note that this does not support the pass_object_size attribute yet. --------- Co-authored-by: Andy Kaylor <[email protected]>
1 parent 7894cfe commit 3d82770

File tree

9 files changed

+1540
-0
lines changed

9 files changed

+1540
-0
lines changed

clang/include/clang/CIR/Dialect/IR/CIROps.td

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4089,6 +4089,57 @@ def CIR_PrefetchOp : CIR_Op<"prefetch"> {
40894089
}];
40904090
}
40914091

4092+
//===----------------------------------------------------------------------===//
4093+
// ObjSizeOp
4094+
//===----------------------------------------------------------------------===//
4095+
4096+
def CIR_ObjSizeOp : CIR_Op<"objsize", [Pure]> {
4097+
let summary = "Implements the llvm.objsize builtin";
4098+
let description = [{
4099+
The `cir.objsize` operation is designed to provide information to the
4100+
optimizer to determine whether a) an operation (like memcpy) will
4101+
overflow a buffer that corresponds to an object, or b) that a runtime
4102+
check for overflow isn’t necessary. An object in this context means an
4103+
allocation of a specific class, structure, array, or other object.
4104+
4105+
When the `min` attribute is present, the operation returns the minimum
4106+
guaranteed accessible size. When absent (max mode), it returns the maximum
4107+
possible object size. Corresponds to `llvm.objectsize`'s `min` argument.
4108+
4109+
The `dynamic` attribute determines if the value should be evaluated at
4110+
runtime. Corresponds to `llvm.objectsize`'s `dynamic` argument.
4111+
4112+
The `nullunknown` attribute controls how null pointers are handled. When
4113+
present, null pointers are treated as having unknown size. When absent,
4114+
null pointers are treated as having 0 size (in min mode) or -1 size
4115+
(in max mode). Corresponds to `llvm.objectsize`'s `nullunknown` argument.
4116+
4117+
Example:
4118+
4119+
```mlir
4120+
%size = cir.objsize min %ptr : !cir.ptr<i32> -> i64
4121+
%dsize = cir.objsize max dynamic %ptr : !cir.ptr<i32> -> i64
4122+
%nsize = cir.objsize min nullunknown %ptr : !cir.ptr<i32> -> i64
4123+
```
4124+
}];
4125+
4126+
let arguments = (ins
4127+
CIR_PointerType:$ptr,
4128+
UnitAttr:$min,
4129+
UnitAttr:$nullunknown,
4130+
UnitAttr:$dynamic
4131+
);
4132+
4133+
let results = (outs CIR_AnyFundamentalIntType:$result);
4134+
4135+
let assemblyFormat = [{
4136+
(`min` $min^) : (`max`)?
4137+
(`nullunknown` $nullunknown^)?
4138+
(`dynamic` $dynamic^)?
4139+
$ptr `:` qualified(type($ptr)) `->` qualified(type($result)) attr-dict
4140+
}];
4141+
}
4142+
40924143
//===----------------------------------------------------------------------===//
40934144
// PtrDiffOp
40944145
//===----------------------------------------------------------------------===//

clang/include/clang/CIR/MissingFeatures.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ struct MissingFeatures {
215215
static bool builtinCallMathErrno() { return false; }
216216
static bool builtinCheckKind() { return false; }
217217
static bool cgCapturedStmtInfo() { return false; }
218+
static bool countedBySize() { return false; }
218219
static bool cgFPOptionsRAII() { return false; }
219220
static bool checkBitfieldClipping() { return false; }
220221
static bool cirgenABIInfo() { return false; }

clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,19 @@ RValue CIRGenFunction::emitBuiltinExpr(const GlobalDecl &gd, unsigned builtinID,
481481
return emitCall(e->getCallee()->getType(), CIRGenCallee::forDirect(fnOp), e,
482482
returnValue);
483483
}
484+
case Builtin::BI__builtin_dynamic_object_size:
485+
case Builtin::BI__builtin_object_size: {
486+
unsigned type =
487+
e->getArg(1)->EvaluateKnownConstInt(getContext()).getZExtValue();
488+
auto resType = mlir::cast<cir::IntType>(convertType(e->getType()));
489+
490+
// We pass this builtin onto the optimizer so that it can figure out the
491+
// object size in more complex cases.
492+
bool isDynamic = builtinID == Builtin::BI__builtin_dynamic_object_size;
493+
return RValue::get(emitBuiltinObjectSize(e->getArg(0), type, resType,
494+
/*EmittedE=*/nullptr, isDynamic));
495+
}
496+
484497
case Builtin::BI__builtin_prefetch: {
485498
auto evaluateOperandAsInt = [&](const Expr *arg) {
486499
Expr::EvalResult res;
@@ -663,3 +676,42 @@ mlir::Value CIRGenFunction::emitVAArg(VAArgExpr *ve) {
663676
mlir::Value vaList = emitVAListRef(ve->getSubExpr()).getPointer();
664677
return cir::VAArgOp::create(builder, loc, type, vaList);
665678
}
679+
680+
mlir::Value CIRGenFunction::emitBuiltinObjectSize(const Expr *e, unsigned type,
681+
cir::IntType resType,
682+
mlir::Value emittedE,
683+
bool isDynamic) {
684+
assert(!cir::MissingFeatures::opCallImplicitObjectSizeArgs());
685+
686+
// LLVM can't handle type=3 appropriately, and __builtin_object_size shouldn't
687+
// evaluate e for side-effects. In either case, just like original LLVM
688+
// lowering, we shouldn't lower to `cir.objsize` but to a constant instead.
689+
if (type == 3 || (!emittedE && e->HasSideEffects(getContext())))
690+
return builder.getConstInt(getLoc(e->getSourceRange()), resType,
691+
(type & 2) ? 0 : -1);
692+
693+
mlir::Value ptr = emittedE ? emittedE : emitScalarExpr(e);
694+
assert(mlir::isa<cir::PointerType>(ptr.getType()) &&
695+
"Non-pointer passed to __builtin_object_size?");
696+
697+
assert(!cir::MissingFeatures::countedBySize());
698+
699+
// Extract the min/max mode from type. CIR only supports type 0
700+
// (max, whole object) and type 2 (min, whole object), not type 1 or 3
701+
// (closest subobject variants).
702+
const bool min = ((type & 2) != 0);
703+
// For GCC compatibility, __builtin_object_size treats NULL as unknown size.
704+
auto op =
705+
cir::ObjSizeOp::create(builder, getLoc(e->getSourceRange()), resType, ptr,
706+
min, /*nullUnknown=*/true, isDynamic);
707+
return op.getResult();
708+
}
709+
710+
mlir::Value CIRGenFunction::evaluateOrEmitBuiltinObjectSize(
711+
const Expr *e, unsigned type, cir::IntType resType, mlir::Value emittedE,
712+
bool isDynamic) {
713+
uint64_t objectSize;
714+
if (!e->tryEvaluateObjectSize(objectSize, getContext(), type))
715+
return emitBuiltinObjectSize(e, type, resType, emittedE, isDynamic);
716+
return builder.getConstInt(getLoc(e->getSourceRange()), resType, objectSize);
717+
}

clang/lib/CIR/CodeGen/CIRGenFunction.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1307,6 +1307,28 @@ class CIRGenFunction : public CIRGenTypeCache {
13071307
RValue emitBuiltinExpr(const clang::GlobalDecl &gd, unsigned builtinID,
13081308
const clang::CallExpr *e, ReturnValueSlot returnValue);
13091309

1310+
/// Returns a Value corresponding to the size of the given expression by
1311+
/// emitting a `cir.objsize` operation.
1312+
///
1313+
/// \param e The expression whose object size to compute
1314+
/// \param type Determines the semantics of the object size computation.
1315+
/// The type parameter is a 2-bit value where:
1316+
/// bit 0 (type & 1): 0 = whole object, 1 = closest subobject
1317+
/// bit 1 (type & 2): 0 = maximum size, 2 = minimum size
1318+
/// \param resType The result type for the size value
1319+
/// \param emittedE Optional pre-emitted pointer value. If non-null, we'll
1320+
/// call `cir.objsize` on this value rather than emitting e.
1321+
/// \param isDynamic If true, allows runtime evaluation via dynamic mode
1322+
mlir::Value emitBuiltinObjectSize(const clang::Expr *e, unsigned type,
1323+
cir::IntType resType, mlir::Value emittedE,
1324+
bool isDynamic);
1325+
1326+
mlir::Value evaluateOrEmitBuiltinObjectSize(const clang::Expr *e,
1327+
unsigned type,
1328+
cir::IntType resType,
1329+
mlir::Value emittedE,
1330+
bool isDynamic);
1331+
13101332
RValue emitCall(const CIRGenFunctionInfo &funcInfo,
13111333
const CIRGenCallee &callee, ReturnValueSlot returnValue,
13121334
const CallArgList &args, cir::CIRCallOpInterface *callOp,

clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2832,6 +2832,29 @@ static void collectUnreachable(mlir::Operation *parent,
28322832
}
28332833
}
28342834

2835+
mlir::LogicalResult CIRToLLVMObjSizeOpLowering::matchAndRewrite(
2836+
cir::ObjSizeOp op, OpAdaptor adaptor,
2837+
mlir::ConversionPatternRewriter &rewriter) const {
2838+
mlir::Type llvmResTy = getTypeConverter()->convertType(op.getType());
2839+
mlir::Location loc = op->getLoc();
2840+
2841+
mlir::IntegerType i1Ty = rewriter.getI1Type();
2842+
2843+
auto i1Val = [&rewriter, &loc, &i1Ty](bool val) {
2844+
return mlir::LLVM::ConstantOp::create(rewriter, loc, i1Ty, val);
2845+
};
2846+
2847+
replaceOpWithCallLLVMIntrinsicOp(rewriter, op, "llvm.objectsize", llvmResTy,
2848+
{
2849+
adaptor.getPtr(),
2850+
i1Val(op.getMin()),
2851+
i1Val(op.getNullunknown()),
2852+
i1Val(op.getDynamic()),
2853+
});
2854+
2855+
return mlir::LogicalResult::success();
2856+
}
2857+
28352858
void ConvertCIRToLLVMPass::processCIRAttrs(mlir::ModuleOp module) {
28362859
// Lower the module attributes to LLVM equivalents.
28372860
if (mlir::Attribute tripleAttr =

0 commit comments

Comments
 (0)