Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Clang] Correct __builtin_dynamic_object_size for subobject types #78526

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 6 additions & 1 deletion clang/lib/CodeGen/CGBuiltin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1103,12 +1103,17 @@ CodeGenFunction::emitBuiltinObjectSize(const Expr *E, unsigned Type,
Function *F =
CGM.getIntrinsic(Intrinsic::objectsize, {ResType, Ptr->getType()});

// If the least significant bit is clear, objects are whole variables. If
// it's set, a closest surrounding subobject is considered the object a
// pointer points to.
Value *WholeObj = Builder.getInt1((Type & 1) == 0);

// LLVM only supports 0 and 2, make sure that we pass along that as a boolean.
Value *Min = Builder.getInt1((Type & 2) != 0);
// For GCC compatibility, __builtin_object_size treat NULL as unknown size.
Value *NullIsUnknown = Builder.getTrue();
Value *Dynamic = Builder.getInt1(IsDynamic);
return Builder.CreateCall(F, {Ptr, Min, NullIsUnknown, Dynamic});
return Builder.CreateCall(F, {Ptr, WholeObj, Min, NullIsUnknown, Dynamic});
}

namespace {
Expand Down
4 changes: 3 additions & 1 deletion clang/lib/CodeGen/CGExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -744,11 +744,13 @@ void CodeGenFunction::EmitTypeCheck(TypeCheckKind TCK, SourceLocation Loc,
// FIXME: Get object address space
llvm::Type *Tys[2] = { IntPtrTy, Int8PtrTy };
llvm::Function *F = CGM.getIntrinsic(llvm::Intrinsic::objectsize, Tys);
llvm::Value *WholeObj = Builder.getTrue();
llvm::Value *Min = Builder.getFalse();
llvm::Value *NullIsUnknown = Builder.getFalse();
llvm::Value *Dynamic = Builder.getFalse();
llvm::Value *LargeEnough = Builder.CreateICmpUGE(
Builder.CreateCall(F, {Ptr, Min, NullIsUnknown, Dynamic}), Size);
Builder.CreateCall(F, {Ptr, WholeObj, Min, NullIsUnknown, Dynamic}),
Size);
Checks.push_back(std::make_pair(LargeEnough, SanitizerKind::ObjectSize));
}
}
Expand Down
2 changes: 1 addition & 1 deletion clang/test/CodeGen/catch-undef-behavior.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
void foo(void) {
union { int i; } u;

// CHECK-COMMON: %[[SIZE:.*]] = call i64 @llvm.objectsize.i64.p0(ptr %[[PTR:.*]], i1 false, i1 false, i1 false)
// CHECK-COMMON: %[[SIZE:.*]] = call i64 @llvm.objectsize.i64.p0(ptr %[[PTR:.*]], i1 true, i1 false, i1 false, i1 false)
// CHECK-COMMON-NEXT: %[[OK:.*]] = icmp uge i64 %[[SIZE]], 4

// CHECK-UBSAN: br i1 %[[OK]], {{.*}} !prof ![[WEIGHT_MD:.*]], !nosanitize
Expand Down
6 changes: 3 additions & 3 deletions clang/test/CodeGen/pass-object-size.c
Original file line number Diff line number Diff line change
Expand Up @@ -85,16 +85,16 @@ void test1(unsigned long sz) {

char *ptr = (char *)malloc(sz);

// CHECK: [[REG:%.*]] = call i64 @llvm.objectsize.i64.p0({{.*}}, i1 false, i1 true, i1 true)
// CHECK: [[REG:%.*]] = call i64 @llvm.objectsize.i64.p0({{.*}}, i1 true, i1 false, i1 true, i1 true)
// CHECK: call i32 @DynamicObjectSize0(ptr noundef %{{.*}}, i64 noundef [[REG]])
gi = DynamicObjectSize0(ptr);

// CHECK: [[WITH_OFFSET:%.*]] = getelementptr
// CHECK: [[REG:%.*]] = call i64 @llvm.objectsize.i64.p0(ptr [[WITH_OFFSET]], i1 false, i1 true, i1 true)
// CHECK: [[REG:%.*]] = call i64 @llvm.objectsize.i64.p0(ptr [[WITH_OFFSET]], i1 true, i1 false, i1 true, i1 true)
// CHECK: call i32 @DynamicObjectSize0(ptr noundef {{.*}}, i64 noundef [[REG]])
gi = DynamicObjectSize0(ptr+10);

// CHECK: [[REG:%.*]] = call i64 @llvm.objectsize.i64.p0({{.*}}, i1 true, i1 true, i1 true)
// CHECK: [[REG:%.*]] = call i64 @llvm.objectsize.i64.p0({{.*}}, i1 true, i1 true, i1 true, i1 true)
// CHECK: call i32 @DynamicObjectSize2(ptr noundef {{.*}}, i64 noundef [[REG]])
gi = DynamicObjectSize2(ptr);
}
Expand Down
41 changes: 28 additions & 13 deletions llvm/docs/LangRef.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26520,8 +26520,8 @@ Syntax:

::

declare i32 @llvm.objectsize.i32(ptr <object>, i1 <min>, i1 <nullunknown>, i1 <dynamic>)
declare i64 @llvm.objectsize.i64(ptr <object>, i1 <min>, i1 <nullunknown>, i1 <dynamic>)
declare i32 @llvm.objectsize.i32(ptr <object>, i1 <wholeobj>, i1 <min>, i1 <nullunknown>, i1 <dynamic>)
declare i64 @llvm.objectsize.i64(ptr <object>, i1 <wholeobj>, i1 <min>, i1 <nullunknown>, i1 <dynamic>)

Overview:
"""""""""
Expand All @@ -26535,18 +26535,33 @@ class, structure, array, or other object.
Arguments:
""""""""""

The ``llvm.objectsize`` intrinsic takes four arguments. The first argument is a
pointer to or into the ``object``. The second argument determines whether
``llvm.objectsize`` returns 0 (if true) or -1 (if false) when the object size is
unknown. The third argument controls how ``llvm.objectsize`` acts when ``null``
in address space 0 is used as its pointer argument. If it's ``false``,
``llvm.objectsize`` reports 0 bytes available when given ``null``. Otherwise, if
the ``null`` is in a non-zero address space or if ``true`` is given for the
third argument of ``llvm.objectsize``, we assume its size is unknown. The fourth
argument to ``llvm.objectsize`` determines if the value should be evaluated at
runtime.
The ``llvm.objectsize`` intrinsic takes five arguments:

- The first argument is a pointer to or into the ``object``.

- The second argument controls which size ``llvm.objectsize`` returns:

- If it's ``false``, ``llvm.objectsize`` returns the size of the closest
surrounding subobject.
- If it's ``true``, ``llvm.objectsize`` returns the size of the whole object.

- The third argument controls which value to return when the size is unknown:

- If it's ``false``, ``llvm.objectsize`` returns ``-1``.
- If it's ``true``, ``llvm.objectsize`` returns ``0``.

- The fourth argument controls how ``llvm.objectsize`` acts when ``null`` in
address space 0 is used as its pointer argument:

- If it's ``false``, ``llvm.objectsize`` reports 0 bytes available when given
``null``.
- If it's ``true``, or the ``null`` pointer is in a non-zero address space,
the size is assumed to be unknown.

- The fifth argument to ``llvm.objectsize`` determines if the value should be
evaluated at runtime.

The second, third, and fourth arguments only accept constants.
The second, third, fourth, and fifth arguments accept only constants.

Semantics:
""""""""""
Expand Down
9 changes: 9 additions & 0 deletions llvm/include/llvm/Analysis/MemoryBuiltins.h
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,11 @@ struct ObjectSizeOpts {
/// Whether to round the result up to the alignment of allocas, byval
/// arguments, and global variables.
bool RoundToAlign = false;
/// If this is true, return the whole size of the object. Otherwise, return
/// the size of the closest surrounding subobject.
/// FIXME: The default before being added was to return the whole size of the
/// object. Review if this is the correct default.
bool WholeObjectSize = true;
/// If this is true, null pointers in address space 0 will be treated as
/// though they can't be evaluated. Otherwise, null is always considered to
/// point to a 0 byte region of memory.
Expand Down Expand Up @@ -231,6 +236,7 @@ class ObjectSizeOffsetVisitor
APInt Zero;
SmallDenseMap<Instruction *, SizeOffsetAPInt, 8> SeenInsts;
unsigned InstructionsVisited;
const StructType *AllocaTy = nullptr;

APInt align(APInt Size, MaybeAlign Align);

Expand All @@ -242,6 +248,8 @@ class ObjectSizeOffsetVisitor

SizeOffsetAPInt compute(Value *V);

const StructType *getAllocaType() const { return AllocaTy; }

// These are "private", except they can't actually be made private. Only
// compute() should be used by external users.
SizeOffsetAPInt visitAllocaInst(AllocaInst &I);
Expand Down Expand Up @@ -313,6 +321,7 @@ class ObjectSizeOffsetEvaluator
PtrSetTy SeenVals;
ObjectSizeOpts EvalOpts;
SmallPtrSet<Instruction *, 8> InsertedInstructions;
const StructType *AllocaTy = nullptr;

SizeOffsetValue compute_(Value *V);

Expand Down
2 changes: 1 addition & 1 deletion llvm/include/llvm/IR/Intrinsics.td
Original file line number Diff line number Diff line change
Expand Up @@ -1071,7 +1071,7 @@ def int_maximum : DefaultAttrsIntrinsic<[llvm_anyfloat_ty],

// Internal interface for object size checking
def int_objectsize : DefaultAttrsIntrinsic<[llvm_anyint_ty],
[llvm_anyptr_ty, llvm_i1_ty,
[llvm_anyptr_ty, llvm_i1_ty, llvm_i1_ty,
llvm_i1_ty, llvm_i1_ty],
[IntrNoMem, IntrSpeculatable, IntrWillReturn,
ImmArg<ArgIndex<1>>, ImmArg<ArgIndex<2>>,
Expand Down
35 changes: 31 additions & 4 deletions llvm/lib/Analysis/MemoryBuiltins.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -615,23 +615,26 @@ Value *llvm::lowerObjectSizeCall(
assert(ObjectSize->getIntrinsicID() == Intrinsic::objectsize &&
"ObjectSize must be a call to llvm.objectsize!");

bool MaxVal = cast<ConstantInt>(ObjectSize->getArgOperand(1))->isZero();
ObjectSizeOpts EvalOptions;
EvalOptions.AA = AA;

EvalOptions.WholeObjectSize =
cast<ConstantInt>(ObjectSize->getArgOperand(1))->isOne();

// Unless we have to fold this to something, try to be as accurate as
// possible.
bool MaxVal = cast<ConstantInt>(ObjectSize->getArgOperand(2))->isZero();
if (MustSucceed)
EvalOptions.EvalMode =
MaxVal ? ObjectSizeOpts::Mode::Max : ObjectSizeOpts::Mode::Min;
else
EvalOptions.EvalMode = ObjectSizeOpts::Mode::ExactSizeFromOffset;

EvalOptions.NullIsUnknownSize =
cast<ConstantInt>(ObjectSize->getArgOperand(2))->isOne();
cast<ConstantInt>(ObjectSize->getArgOperand(3))->isOne();

auto *ResultType = cast<IntegerType>(ObjectSize->getType());
bool StaticOnly = cast<ConstantInt>(ObjectSize->getArgOperand(3))->isZero();
bool StaticOnly = cast<ConstantInt>(ObjectSize->getArgOperand(4))->isZero();
if (StaticOnly) {
// FIXME: Does it make sense to just return a failure value if the size won't
// fit in the output and `!MustSucceed`?
Expand Down Expand Up @@ -726,6 +729,25 @@ SizeOffsetAPInt ObjectSizeOffsetVisitor::computeImpl(Value *V) {
if (!IndexTypeSizeChanged && Offset.isZero())
return SOT;

if (!Options.WholeObjectSize && AllocaTy) {
// At this point, SOT.Size is the size of the whole struct. However, we
// want the size of the sub-object.
const StructLayout &SL =
*DL.getStructLayout(const_cast<StructType *>(AllocaTy));

unsigned Idx = SL.getElementContainingOffset(Offset.getLimitedValue());

// Get the size of the sub-object.
TypeSize ElemSize = DL.getTypeAllocSize(AllocaTy->getTypeAtIndex(Idx));
APInt Size(InitialIntTyBits, ElemSize.getKnownMinValue());

// Adjust the offset to reflect the sub-object's offset.
TypeSize ElemOffset = SL.getElementOffset(Idx);
Offset -= ElemOffset.getKnownMinValue();

SOT = SizeOffsetAPInt(Size, Offset);
}

// We stripped an address space cast that changed the index type size or we
// accumulated some constant offset (or both). Readjust the bit width to match
// the argument index type size and apply the offset, as required.
Expand Down Expand Up @@ -781,9 +803,12 @@ SizeOffsetAPInt ObjectSizeOffsetVisitor::visitAllocaInst(AllocaInst &I) {
TypeSize ElemSize = DL.getTypeAllocSize(I.getAllocatedType());
if (ElemSize.isScalable() && Options.EvalMode != ObjectSizeOpts::Mode::Min)
return ObjectSizeOffsetVisitor::unknown();

APInt Size(IntTyBits, ElemSize.getKnownMinValue());
if (!I.isArrayAllocation())
if (!I.isArrayAllocation()) {
AllocaTy = dyn_cast<StructType>(I.getAllocatedType());
return SizeOffsetAPInt(align(Size, I.getAlign()), Zero);
}

Value *ArraySize = I.getArraySize();
if (const ConstantInt *C = dyn_cast<ConstantInt>(ArraySize)) {
Expand Down Expand Up @@ -1086,6 +1111,8 @@ SizeOffsetValue ObjectSizeOffsetEvaluator::compute(Value *V) {
SizeOffsetValue ObjectSizeOffsetEvaluator::compute_(Value *V) {
ObjectSizeOffsetVisitor Visitor(DL, TLI, Context, EvalOpts);
SizeOffsetAPInt Const = Visitor.compute(V);
AllocaTy = Visitor.getAllocaType();

if (Const.bothKnown())
return SizeOffsetValue(ConstantInt::get(Context, Const.Size),
ConstantInt::get(Context, Const.Offset));
Expand Down
43 changes: 37 additions & 6 deletions llvm/lib/IR/AutoUpgrade.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4393,12 +4393,43 @@ void llvm::UpgradeIntrinsicCall(CallBase *CI, Function *NewFn) {
break;

case Intrinsic::objectsize: {
Value *NullIsUnknownSize =
CI->arg_size() == 2 ? Builder.getFalse() : CI->getArgOperand(2);
Value *Dynamic =
CI->arg_size() < 4 ? Builder.getFalse() : CI->getArgOperand(3);
NewCall = Builder.CreateCall(
NewFn, {CI->getArgOperand(0), CI->getArgOperand(1), NullIsUnknownSize, Dynamic});
// The default behavior before the addition of the '<wholeobj>' argument
// was to return the size of the whole object.
Value *WholeObj = nullptr;
Value *UnknownVal = nullptr;
Value *NullIsUnknownSize = nullptr;
Value *Dynamic = nullptr;

switch (CI->arg_size()) {
case 2:
WholeObj = Builder.getTrue();
UnknownVal = CI->getArgOperand(1);
NullIsUnknownSize = Builder.getFalse();
Dynamic = Builder.getFalse();
break;
case 3:
WholeObj = Builder.getTrue();
UnknownVal = CI->getArgOperand(1);
NullIsUnknownSize = CI->getArgOperand(2);
Dynamic = Builder.getFalse();
break;
case 4:
WholeObj = Builder.getTrue();
UnknownVal = CI->getArgOperand(1);
NullIsUnknownSize = CI->getArgOperand(2);
Dynamic = CI->getArgOperand(3);
break;
case 5:
WholeObj = CI->getArgOperand(1);
UnknownVal = CI->getArgOperand(2);
NullIsUnknownSize = CI->getArgOperand(3);
Dynamic = CI->getArgOperand(4);
break;
}

NewCall =
Builder.CreateCall(NewFn, {CI->getArgOperand(0), WholeObj, UnknownVal,
NullIsUnknownSize, Dynamic});
break;
}

Expand Down
4 changes: 2 additions & 2 deletions llvm/lib/Target/AMDGPU/AMDGPUPromoteAlloca.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1486,8 +1486,8 @@ bool AMDGPUPromoteAllocaImpl::tryPromoteAllocaToLDS(AllocaInst &I,
PointerType::get(Context, AMDGPUAS::LOCAL_ADDRESS)});

CallInst *NewCall = Builder.CreateCall(
ObjectSize,
{Src, Intr->getOperand(1), Intr->getOperand(2), Intr->getOperand(3)});
ObjectSize, {Src, Intr->getOperand(1), Intr->getOperand(2),
Intr->getOperand(3), Intr->getOperand(4)});
Intr->replaceAllUsesWith(NewCall);
Intr->eraseFromParent();
continue;
Expand Down
8 changes: 4 additions & 4 deletions llvm/test/Analysis/CostModel/X86/free-intrinsics.ll
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ define i32 @trivially_free() {
; CHECK-SIZE-NEXT: Cost Model: Found an estimated cost of 0 for instruction: %a4 = call i1 @llvm.is.constant.i32(i32 undef)
; CHECK-SIZE-NEXT: Cost Model: Found an estimated cost of 0 for instruction: call void @llvm.lifetime.start.p0(i64 1, ptr undef)
; CHECK-SIZE-NEXT: Cost Model: Found an estimated cost of 0 for instruction: call void @llvm.lifetime.end.p0(i64 1, ptr undef)
; CHECK-SIZE-NEXT: Cost Model: Found an estimated cost of 0 for instruction: %a5 = call i64 @llvm.objectsize.i64.p0(ptr undef, i1 true, i1 true, i1 true)
; CHECK-SIZE-NEXT: Cost Model: Found an estimated cost of 0 for instruction: %a5 = call i64 @llvm.objectsize.i64.p0(ptr undef, i1 true, i1 true, i1 true, i1 true)
; CHECK-SIZE-NEXT: Cost Model: Found an estimated cost of 0 for instruction: %a6 = call ptr @llvm.ptr.annotation.p0.p0(ptr undef, ptr undef, ptr undef, i32 undef, ptr undef)
; CHECK-SIZE-NEXT: Cost Model: Found an estimated cost of 0 for instruction: call void @llvm.var.annotation.p0.p0(ptr undef, ptr undef, ptr undef, i32 undef, ptr undef)
; CHECK-SIZE-NEXT: Cost Model: Found an estimated cost of 1 for instruction: ret i32 undef
Expand All @@ -38,7 +38,7 @@ define i32 @trivially_free() {
; CHECK-THROUGHPUT-NEXT: Cost Model: Found an estimated cost of 0 for instruction: %a4 = call i1 @llvm.is.constant.i32(i32 undef)
; CHECK-THROUGHPUT-NEXT: Cost Model: Found an estimated cost of 0 for instruction: call void @llvm.lifetime.start.p0(i64 1, ptr undef)
; CHECK-THROUGHPUT-NEXT: Cost Model: Found an estimated cost of 0 for instruction: call void @llvm.lifetime.end.p0(i64 1, ptr undef)
; CHECK-THROUGHPUT-NEXT: Cost Model: Found an estimated cost of 0 for instruction: %a5 = call i64 @llvm.objectsize.i64.p0(ptr undef, i1 true, i1 true, i1 true)
; CHECK-THROUGHPUT-NEXT: Cost Model: Found an estimated cost of 0 for instruction: %a5 = call i64 @llvm.objectsize.i64.p0(ptr undef, i1 true, i1 true, i1 true, i1 true)
; CHECK-THROUGHPUT-NEXT: Cost Model: Found an estimated cost of 0 for instruction: %a6 = call ptr @llvm.ptr.annotation.p0.p0(ptr undef, ptr undef, ptr undef, i32 undef, ptr undef)
; CHECK-THROUGHPUT-NEXT: Cost Model: Found an estimated cost of 0 for instruction: call void @llvm.var.annotation.p0.p0(ptr undef, ptr undef, ptr undef, i32 undef, ptr undef)
; CHECK-THROUGHPUT-NEXT: Cost Model: Found an estimated cost of 0 for instruction: ret i32 undef
Expand All @@ -58,7 +58,7 @@ define i32 @trivially_free() {
%a4 = call i1 @llvm.is.constant.i32(i32 undef)
call void @llvm.lifetime.start.p0(i64 1, ptr undef)
call void @llvm.lifetime.end.p0(i64 1, ptr undef)
%a5 = call i64 @llvm.objectsize.i64.p0(ptr undef, i1 1, i1 1, i1 1)
%a5 = call i64 @llvm.objectsize.i64.p0(ptr undef, i1 1, i1 1, i1 1, i1 1)
%a6 = call ptr @llvm.ptr.annotation.p0(ptr undef, ptr undef, ptr undef, i32 undef, ptr undef)
call void @llvm.var.annotation(ptr undef, ptr undef, ptr undef, i32 undef, ptr undef)
ret i32 undef
Expand All @@ -79,7 +79,7 @@ declare ptr @llvm.strip.invariant.group.p0(ptr)
declare i1 @llvm.is.constant.i32(i32)
declare void @llvm.lifetime.start.p0(i64, ptr)
declare void @llvm.lifetime.end.p0(i64, ptr)
declare i64 @llvm.objectsize.i64.p0(ptr, i1, i1, i1)
declare i64 @llvm.objectsize.i64.p0(ptr, i1, i1, i1, i1)
declare ptr @llvm.ptr.annotation.p0(ptr, ptr, ptr, i32, ptr)
declare void @llvm.var.annotation(ptr, ptr, ptr, i32, ptr)

Expand Down