Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions clang/include/clang/CIR/Dialect/IR/CIROps.td
Original file line number Diff line number Diff line change
Expand Up @@ -4089,6 +4089,57 @@ def CIR_PrefetchOp : CIR_Op<"prefetch"> {
}];
}

//===----------------------------------------------------------------------===//
// ObjSizeOp
//===----------------------------------------------------------------------===//

def CIR_ObjSizeOp : CIR_Op<"objsize", [Pure]> {
let summary = "Implements the llvm.objsize builtin";
let description = [{
The `cir.objsize` operation is designed to provide information to the
optimizer to determine whether a) an operation (like memcpy) will
overflow a buffer that corresponds to an object, or b) that a runtime
check for overflow isn’t necessary. An object in this context means an
allocation of a specific class, structure, array, or other object.

When the `min` attribute is present, the operation returns the minimum
guaranteed accessible size. When absent (max mode), it returns the maximum
possible object size. Corresponds to `llvm.objectsize`'s `min` argument.

The `dynamic` attribute determines if the value should be evaluated at
runtime. Corresponds to `llvm.objectsize`'s `dynamic` argument.

The `nullunknown` attribute controls how null pointers are handled. When
present, null pointers are treated as having unknown size. When absent,
null pointers are treated as having 0 size (in min mode) or -1 size
(in max mode). Corresponds to `llvm.objectsize`'s `nullunknown` argument.

Example:

```mlir
%size = cir.objsize min %ptr : !cir.ptr<i32> -> i64
%dsize = cir.objsize max dynamic %ptr : !cir.ptr<i32> -> i64
%nsize = cir.objsize min nullunknown %ptr : !cir.ptr<i32> -> i64
```
}];

let arguments = (ins
CIR_PointerType:$ptr,
UnitAttr:$min,
UnitAttr:$nullunknown,
UnitAttr:$dynamic
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we have an attribute corresponding to the nullunknown argument to llvm.objectsize?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No strong opinions here, but since clang hardcodes this to true I don't see the need to expose this parameter. If we need it in the future it's easy to add it as an optional attribute later on

Copy link
Contributor

@andykaylor andykaylor Nov 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clang hard codes it to true when handling the builtin, but there's another place (EmitTypeCheck) where it sets it to false.

But you're right that we can add it later.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I didn't spot that. This is just for the sanitizer and it will take us some time until this gets relevant, but I added that attribute now regardless.

);

let results = (outs CIR_AnyFundamentalIntType:$result);

let assemblyFormat = [{
(`min` $min^) : (`max`)?
(`nullunknown` $nullunknown^)?
(`dynamic` $dynamic^)?
$ptr `:` qualified(type($ptr)) `->` qualified(type($result)) attr-dict
}];
}

//===----------------------------------------------------------------------===//
// PtrDiffOp
//===----------------------------------------------------------------------===//
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/CIR/MissingFeatures.h
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ struct MissingFeatures {
static bool builtinCallMathErrno() { return false; }
static bool builtinCheckKind() { return false; }
static bool cgCapturedStmtInfo() { return false; }
static bool countedBySize() { return false; }
static bool cgFPOptionsRAII() { return false; }
static bool checkBitfieldClipping() { return false; }
static bool cirgenABIInfo() { return false; }
Expand Down
52 changes: 52 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,19 @@ RValue CIRGenFunction::emitBuiltinExpr(const GlobalDecl &gd, unsigned builtinID,
return emitCall(e->getCallee()->getType(), CIRGenCallee::forDirect(fnOp), e,
returnValue);
}
case Builtin::BI__builtin_dynamic_object_size:
case Builtin::BI__builtin_object_size: {
unsigned type =
e->getArg(1)->EvaluateKnownConstInt(getContext()).getZExtValue();
auto resType = mlir::cast<cir::IntType>(convertType(e->getType()));

// We pass this builtin onto the optimizer so that it can figure out the
// object size in more complex cases.
bool isDynamic = builtinID == Builtin::BI__builtin_dynamic_object_size;
return RValue::get(emitBuiltinObjectSize(e->getArg(0), type, resType,
/*EmittedE=*/nullptr, isDynamic));
}

case Builtin::BI__builtin_prefetch: {
auto evaluateOperandAsInt = [&](const Expr *arg) {
Expr::EvalResult res;
Expand Down Expand Up @@ -641,3 +654,42 @@ mlir::Value CIRGenFunction::emitVAArg(VAArgExpr *ve) {
mlir::Value vaList = emitVAListRef(ve->getSubExpr()).getPointer();
return cir::VAArgOp::create(builder, loc, type, vaList);
}

mlir::Value CIRGenFunction::emitBuiltinObjectSize(const Expr *e, unsigned type,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A comment explaining what type means would be helpful. In particular, the comments below about "type=3" are rather meaningless without a more general explanation of this parameter.

cir::IntType resType,
mlir::Value emittedE,
bool isDynamic) {
assert(!cir::MissingFeatures::opCallImplicitObjectSizeArgs());

// LLVM can't handle type=3 appropriately, and __builtin_object_size shouldn't
// evaluate e for side-effects. In either case, just like original LLVM
// lowering, we shouldn't lower to `cir.objsize` but to a constant instead.
if (type == 3 || (!emittedE && e->HasSideEffects(getContext())))
return builder.getConstInt(getLoc(e->getSourceRange()), resType,
(type & 2) ? 0 : -1);

mlir::Value ptr = emittedE ? emittedE : emitScalarExpr(e);
assert(mlir::isa<cir::PointerType>(ptr.getType()) &&
"Non-pointer passed to __builtin_object_size?");

assert(!cir::MissingFeatures::countedBySize());

// Extract the min/max mode from type. CIR only supports type 0
// (max, whole object) and type 2 (min, whole object), not type 1 or 3
// (closest subobject variants).
const bool min = ((type & 2) != 0);
// For GCC compatibility, __builtin_object_size treats NULL as unknown size.
auto op =
cir::ObjSizeOp::create(builder, getLoc(e->getSourceRange()), resType, ptr,
min, /*nullUnknown=*/true, isDynamic);
return op.getResult();
}

mlir::Value CIRGenFunction::evaluateOrEmitBuiltinObjectSize(
const Expr *e, unsigned type, cir::IntType resType, mlir::Value emittedE,
bool isDynamic) {
uint64_t objectSize;
if (!e->tryEvaluateObjectSize(objectSize, getContext(), type))
return emitBuiltinObjectSize(e, type, resType, emittedE, isDynamic);
return builder.getConstInt(getLoc(e->getSourceRange()), resType, objectSize);
}
22 changes: 22 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -1304,6 +1304,28 @@ class CIRGenFunction : public CIRGenTypeCache {
RValue emitBuiltinExpr(const clang::GlobalDecl &gd, unsigned builtinID,
const clang::CallExpr *e, ReturnValueSlot returnValue);

/// Returns a Value corresponding to the size of the given expression by
/// emitting a `cir.objsize` operation.
///
/// \param e The expression whose object size to compute
/// \param type Determines the semantics of the object size computation.
/// The type parameter is a 2-bit value where:
/// bit 0 (type & 1): 0 = whole object, 1 = closest subobject
/// bit 1 (type & 2): 0 = maximum size, 2 = minimum size
/// \param resType The result type for the size value
/// \param emittedE Optional pre-emitted pointer value. If non-null, we'll
/// call `cir.objsize` on this value rather than emitting e.
/// \param isDynamic If true, allows runtime evaluation via dynamic mode
mlir::Value emitBuiltinObjectSize(const clang::Expr *e, unsigned type,
cir::IntType resType, mlir::Value emittedE,
bool isDynamic);

mlir::Value evaluateOrEmitBuiltinObjectSize(const clang::Expr *e,
unsigned type,
cir::IntType resType,
mlir::Value emittedE,
bool isDynamic);

RValue emitCall(const CIRGenFunctionInfo &funcInfo,
const CIRGenCallee &callee, ReturnValueSlot returnValue,
const CallArgList &args, cir::CIRCallOpInterface *callOp,
Expand Down
23 changes: 23 additions & 0 deletions clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2816,6 +2816,29 @@ static void collectUnreachable(mlir::Operation *parent,
}
}

mlir::LogicalResult CIRToLLVMObjSizeOpLowering::matchAndRewrite(
cir::ObjSizeOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
mlir::Type llvmResTy = getTypeConverter()->convertType(op.getType());
mlir::Location loc = op->getLoc();

mlir::IntegerType i1Ty = rewriter.getI1Type();

auto i1Val = [&rewriter, &loc, &i1Ty](bool val) {
return mlir::LLVM::ConstantOp::create(rewriter, loc, i1Ty, val);
};

replaceOpWithCallLLVMIntrinsicOp(rewriter, op, "llvm.objectsize", llvmResTy,
{
adaptor.getPtr(),
i1Val(op.getMin()),
i1Val(op.getNullunknown()),
i1Val(op.getDynamic()),
});

return mlir::LogicalResult::success();
}

void ConvertCIRToLLVMPass::processCIRAttrs(mlir::ModuleOp module) {
// Lower the module attributes to LLVM equivalents.
if (mlir::Attribute tripleAttr =
Expand Down
Loading