1
0
mirror of https://github.com/RPCS3/llvm-mirror.git synced 2024-10-18 10:32:48 +02:00

[RS4GC] Introduce intrinsics to get base ptr and offset

There can be a need for some optimizations to get (base, offset)
for any GC pointer. The base can be calculated by generating
needed instructions as it is done by the
RewriteStatepointsForGC::findBasePointer() function. The offset
can be calculated in the same way. Though to not expose the base
calculation and to make the offset calculation as simple as
ptrtoint(derived_ptr) - ptrtoint(base_ptr), which is illegal
outside RS4GC, this patch introduces 2 intrinsics:

 @llvm.experimental.gc.get.pointer.base(%derived_ptr)
 @llvm.experimental.gc.get.pointer.offset(%derived_ptr)

These intrinsics are inlined by RS4GC along with generation of
statepoint sequences.

With these new intrinsics the GC parseable lowering for atomic
memcpy intrinsics (6ec2c5e402a724ba99bce82a9cac7a3006d660f4)
could be implemented as a separate pass.

Reviewed By: reames
Differential Revision: https://reviews.llvm.org/D100445
This commit is contained in:
Yevgeny Rouban 2021-05-27 09:01:55 +07:00
parent 044ed9b7c9
commit 0a4cf978a7
8 changed files with 361 additions and 10 deletions

View File

@ -12250,6 +12250,88 @@ A ``gc.relocate`` is modeled as a ``readnone`` pure function. It has no
side effects since it is just a way to extract information about work
done during the actual call modeled by the ``gc.statepoint``.
.. _gc.get.pointer.base:
'llvm.experimental.gc.get.pointer.base' Intrinsic
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Syntax:
"""""""
::
declare <pointer type>
@llvm.experimental.gc.get.pointer.base(
<pointer type> readnone nocapture %derived_ptr)
nounwind readnone willreturn
Overview:
"""""""""
``gc.get.pointer.base`` for a derived pointer returns its base pointer.
Operands:
"""""""""
The only argument is a pointer which is based on some object with
an unknown offset from the base of said object.
Semantics:
""""""""""
This intrinsic is used in the abstract machine model for GC to represent
the base pointer for an arbitrary derived pointer.
This intrinsic is inlined by the :ref:`RewriteStatepointsForGC` pass by
replacing all uses of this callsite with the offset of a derived pointer from
its base pointer value. The replacement is done as part of the lowering to the
explicit statepoint model.
The return pointer type must be the same as the type of the parameter.
'llvm.experimental.gc.get.pointer.offset' Intrinsic
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Syntax:
"""""""
::
declare i64
@llvm.experimental.gc.get.pointer.offset(
<pointer type> readnone nocapture %derived_ptr)
nounwind readnone willreturn
Overview:
"""""""""
``gc.get.pointer.offset`` for a derived pointer returns the offset from its
base pointer.
Operands:
"""""""""
The only argument is a pointer which is based on some object with
an unknown offset from the base of said object.
Semantics:
""""""""""
This intrinsic is used in the abstract machine model for GC to represent
the offset of an arbitrary derived pointer from its base pointer.
This intrinsic is inlined by the :ref:`RewriteStatepointsForGC` pass by
replacing all uses of this callsite with the offset of a derived pointer from
its base pointer value. The replacement is done as part of the lowering to the
explicit statepoint model.
Basically this call calculates difference between the derived pointer and its
base pointer (see :ref:`gc.get.pointer.base`) both ptrtoint casted. But
this cast done outside the :ref:`RewriteStatepointsForGC` pass could result
in the pointers lost for further lowering from the abstract model to the
explicit physical one.
Code Generator Intrinsics
-------------------------

View File

@ -430,6 +430,13 @@ strategy-specific lowering is not present, and all GC transitions are emitted as
as single no-op before and after the call instruction. These no-ops are often
removed by the backend during dead machine instruction elimination.
Before the abstract machine model is lowered to the explicit statepoint model
of relocations by the :ref:`RewriteStatepointsForGC` pass it is possible for
any derived pointer to get its base pointer and offset from the base pointer
by using the ``gc.get.pointer.base`` and the ``gc.get.pointer.offset``
intrinsics respectively. These intrinsics are inlined by the
:ref:`RewriteStatepointsForGC` pass and must not be used after this pass.
.. _statepoint-stackmap-format:
@ -620,12 +627,16 @@ RewriteStatepointsForGC intrinsic lowering
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
As a part of lowering to the explicit model of relocations
RewriteStatepointsForGC performs GC specific lowering for
'``llvm.memcpy.element.unordered.atomic.*``',
'``llvm.memmove.element.unordered.atomic.*``' intrinsics.
RewriteStatepointsForGC performs GC specific lowering for the following
intrinsics:
There are two possible lowerings for these copy operations: GC leaf lowering
and GC parseable lowering. If a call is explicitly marked with
* ``gc.get.pointer.base``
* ``gc.get.pointer.offset``
* ``llvm.memcpy.element.unordered.atomic.*``
* ``llvm.memmove.element.unordered.atomic.*``
There are two possible lowerings for the memcpy and memmove operations:
GC leaf lowering and GC parseable lowering. If a call is explicitly marked with
"gc-leaf-function" attribute the call is lowered to a GC leaf call to
'``__llvm_memcpy_element_unordered_atomic_*``' or
'``__llvm_memmove_element_unordered_atomic_*``' symbol. Such a call can not

View File

@ -850,6 +850,14 @@ public:
Type *ResultType,
const Twine &Name = "");
/// Create a call to the experimental.gc.pointer.base intrinsic to get the
/// base pointer for the specified derived pointer.
CallInst *CreateGCGetPointerBase(Value *DerivedPtr, const Twine &Name = "");
/// Create a call to the experimental.gc.get.pointer.offset intrinsic to get
/// the offset of the specified derived pointer from its base.
CallInst *CreateGCGetPointerOffset(Value *DerivedPtr, const Twine &Name = "");
/// Create a call to llvm.vscale, multiplied by \p Scaling. The type of VScale
/// will be the same type as that of \p Scaling.
Value *CreateVScale(Constant *Scaling, const Twine &Name = "");

View File

@ -1206,6 +1206,14 @@ def int_experimental_gc_relocate : Intrinsic<[llvm_any_ty],
[IntrNoMem, ImmArg<ArgIndex<1>>,
ImmArg<ArgIndex<2>>]>;
def int_experimental_gc_get_pointer_base : Intrinsic<[llvm_anyptr_ty],
[llvm_anyptr_ty], [IntrNoMem, IntrWillReturn,
ReadNone<ArgIndex<0>>, NoCapture<ArgIndex<0>>]>;
def int_experimental_gc_get_pointer_offset : Intrinsic<[llvm_i64_ty],
[llvm_anyptr_ty], [IntrNoMem, IntrWillReturn,
ReadNone<ArgIndex<0>>, NoCapture<ArgIndex<0>>]>;
//===------------------------ Coroutine Intrinsics ---------------===//
// These are documented in docs/Coroutines.rst

View File

@ -780,6 +780,24 @@ CallInst *IRBuilderBase::CreateGCRelocate(Instruction *Statepoint,
return createCallHelper(FnGCRelocate, Args, this, Name);
}
CallInst *IRBuilderBase::CreateGCGetPointerBase(Value *DerivedPtr,
const Twine &Name) {
Module *M = BB->getParent()->getParent();
Type *PtrTy = DerivedPtr->getType();
Function *FnGCFindBase = Intrinsic::getDeclaration(
M, Intrinsic::experimental_gc_get_pointer_base, {PtrTy, PtrTy});
return createCallHelper(FnGCFindBase, {DerivedPtr}, this, Name);
}
CallInst *IRBuilderBase::CreateGCGetPointerOffset(Value *DerivedPtr,
const Twine &Name) {
Module *M = BB->getParent()->getParent();
Type *PtrTy = DerivedPtr->getType();
Function *FnGCGetOffset = Intrinsic::getDeclaration(
M, Intrinsic::experimental_gc_get_pointer_offset, {PtrTy});
return createCallHelper(FnGCGetOffset, {DerivedPtr}, this, Name);
}
CallInst *IRBuilderBase::CreateUnaryIntrinsic(Intrinsic::ID ID, Value *V,
Instruction *FMFSource,
const Twine &Name) {

View File

@ -2617,6 +2617,29 @@ void Verifier::visitFunction(const Function &F) {
Assert(false, "Invalid user of intrinsic instruction!", U);
}
// Check intrinsics' signatures.
switch (F.getIntrinsicID()) {
case Intrinsic::experimental_gc_get_pointer_base: {
FunctionType *FT = F.getFunctionType();
Assert(FT->getNumParams() == 1, "wrong number of parameters", F);
Assert(isa<PointerType>(F.getReturnType()),
"gc.get.pointer.base must return a pointer", F);
Assert(FT->getParamType(0) == F.getReturnType(),
"gc.get.pointer.base operand and result must be of the same type",
F);
break;
}
case Intrinsic::experimental_gc_get_pointer_offset: {
FunctionType *FT = F.getFunctionType();
Assert(FT->getNumParams() == 1, "wrong number of parameters", F);
Assert(isa<PointerType>(FT->getParamType(0)),
"gc.get.pointer.offset operand must be a pointer", F);
Assert(F.getReturnType()->isIntegerTy(),
"gc.get.pointer.offset must return integer", F);
break;
}
}
auto *N = F.getSubprogram();
HasDebugInfo = (N != nullptr);
if (!HasDebugInfo)

View File

@ -562,6 +562,8 @@ static BaseDefiningValueResult findBaseDefiningValue(Value *I) {
// implications much.
llvm_unreachable(
"interaction with the gcroot mechanism is not supported");
case Intrinsic::experimental_gc_get_pointer_base:
return findBaseDefiningValue(II->getOperand(0));
}
}
// We assume that functions in the source language only return base
@ -594,6 +596,11 @@ static BaseDefiningValueResult findBaseDefiningValue(Value *I) {
assert(!isa<InsertValueInst>(I) &&
"Base pointer for a struct is meaningless");
// This value might have been generated by findBasePointer() called when
// substituting gc.get.pointer.base() intrinsic.
bool IsKnownBase =
isa<Instruction>(I) && cast<Instruction>(I)->getMetadata("is_base_value");
// An extractelement produces a base result exactly when it's input does.
// We may need to insert a parallel instruction to extract the appropriate
// element out of the base vector corresponding to the input. Given this,
@ -602,7 +609,7 @@ static BaseDefiningValueResult findBaseDefiningValue(Value *I) {
// Note: There a lot of obvious peephole cases here. This are deliberately
// handled after the main base pointer inference algorithm to make writing
// test cases to exercise that code easier.
return BaseDefiningValueResult(I, false);
return BaseDefiningValueResult(I, IsKnownBase);
// The last two cases here don't return a base pointer. Instead, they
// return a value which dynamically selects from among several base
@ -610,7 +617,7 @@ static BaseDefiningValueResult findBaseDefiningValue(Value *I) {
// the caller to resolve these.
assert((isa<SelectInst>(I) || isa<PHINode>(I)) &&
"missing instruction case in findBaseDefiningValing");
return BaseDefiningValueResult(I, false);
return BaseDefiningValueResult(I, IsKnownBase);
}
/// Returns the base defining value for this value.
@ -2384,6 +2391,56 @@ static void rematerializeLiveValues(CallBase *Call,
}
}
static bool inlineGetBaseAndOffset(Function &F,
SmallVectorImpl<CallInst *> &Intrinsics) {
DefiningValueMapTy DVCache;
auto &Context = F.getContext();
auto &DL = F.getParent()->getDataLayout();
bool Changed = false;
for (auto *Callsite : Intrinsics)
switch (Callsite->getIntrinsicID()) {
case Intrinsic::experimental_gc_get_pointer_base: {
Changed = true;
Value *Base = findBasePointer(Callsite->getOperand(0), DVCache);
assert(!DVCache.count(Callsite));
auto *BaseBC = IRBuilder<>(Callsite).CreateBitCast(
Base, Callsite->getType(), suffixed_name_or(Base, ".cast", ""));
if (BaseBC != Base)
DVCache[BaseBC] = Base;
Callsite->replaceAllUsesWith(BaseBC);
if (!BaseBC->hasName())
BaseBC->takeName(Callsite);
Callsite->eraseFromParent();
break;
}
case Intrinsic::experimental_gc_get_pointer_offset: {
Changed = true;
Value *Derived = Callsite->getOperand(0);
Value *Base = findBasePointer(Derived, DVCache);
assert(!DVCache.count(Callsite));
unsigned AddressSpace = Derived->getType()->getPointerAddressSpace();
unsigned IntPtrSize = DL.getPointerSizeInBits(AddressSpace);
IRBuilder<> Builder(Callsite);
Value *BaseInt =
Builder.CreatePtrToInt(Base, Type::getIntNTy(Context, IntPtrSize),
suffixed_name_or(Base, ".int", ""));
Value *DerivedInt =
Builder.CreatePtrToInt(Derived, Type::getIntNTy(Context, IntPtrSize),
suffixed_name_or(Derived, ".int", ""));
Value *Offset = Builder.CreateSub(DerivedInt, BaseInt);
Callsite->replaceAllUsesWith(Offset);
Offset->takeName(Callsite);
Callsite->eraseFromParent();
break;
}
default:
llvm_unreachable("Unknown intrinsic");
}
return Changed;
}
static bool insertParsePoints(Function &F, DominatorTree &DT,
TargetTransformInfo &TTI,
SmallVectorImpl<CallBase *> &ToUpdate) {
@ -2442,7 +2499,6 @@ static bool insertParsePoints(Function &F, DominatorTree &DT,
// insertion of base phis and selects. This ensures that we don't insert
// large numbers of duplicate base_phis.
DefiningValueMapTy DVCache;
for (size_t i = 0; i < Records.size(); i++) {
PartiallyConstructedSafepointRecord &info = Records[i];
findBasePointers(DT, DVCache, ToUpdate[i], info);
@ -2785,6 +2841,7 @@ bool RewriteStatepointsForGC::runOnFunction(Function &F, DominatorTree &DT,
// consider those in reachable code since we need to ask dominance queries
// when rewriting. We'll delete the unreachable ones in a moment.
SmallVector<CallBase *, 64> ParsePointNeeded;
SmallVector<CallInst *, 64> Intrinsics;
for (Instruction &I : instructions(F)) {
// TODO: only the ones with the flag set!
if (NeedsRewrite(I)) {
@ -2796,10 +2853,14 @@ bool RewriteStatepointsForGC::runOnFunction(Function &F, DominatorTree &DT,
"no unreachable blocks expected");
ParsePointNeeded.push_back(cast<CallBase>(&I));
}
if (auto *CI = dyn_cast<CallInst>(&I))
if (CI->getIntrinsicID() == Intrinsic::experimental_gc_get_pointer_base ||
CI->getIntrinsicID() == Intrinsic::experimental_gc_get_pointer_offset)
Intrinsics.emplace_back(CI);
}
// Return early if no work to do.
if (ParsePointNeeded.empty())
if (ParsePointNeeded.empty() && Intrinsics.empty())
return MadeChange;
// As a prepass, go ahead and aggressively destroy single entry phi nodes.
@ -2868,7 +2929,13 @@ bool RewriteStatepointsForGC::runOnFunction(Function &F, DominatorTree &DT,
}
}
MadeChange |= insertParsePoints(F, DT, TTI, ParsePointNeeded);
if (!Intrinsics.empty())
// Inline @gc.get.pointer.base() and @gc.get.pointer.offset() before finding
// live references.
MadeChange |= inlineGetBaseAndOffset(F, Intrinsics);
if (!ParsePointNeeded.empty())
MadeChange |= insertParsePoints(F, DT, TTI, ParsePointNeeded);
return MadeChange;
}

View File

@ -0,0 +1,134 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature
; Use instcombine to cleanup offset computation.
; Use gvn to remove duplicate computation.
; RUN: opt -passes=rewrite-statepoints-for-gc,gvn,instcombine -S < %s | FileCheck %s
target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128-p1:64:64"
target triple = "x86_64-apple-macosx10.11.0"
declare i8 addrspace(1)* @llvm.experimental.gc.get.pointer.base.p1i8.p1i8(i8 addrspace(1)* readnone nocapture) nounwind readnone willreturn
declare i8 addrspace(1)* addrspace(1)* @llvm.experimental.gc.get.pointer.base.p1p1i8.p1p1i8(i8 addrspace(1)* addrspace(1)* readnone nocapture) nounwind readnone willreturn
declare i64 @llvm.experimental.gc.get.pointer.offset.p1i8(i8 addrspace(1)* readnone nocapture) nounwind readnone willreturn
declare i64 @llvm.experimental.gc.get.pointer.offset.p1p1i8(i8 addrspace(1)* addrspace(1)* readnone nocapture) nounwind readnone willreturn
declare void @foo() readonly
define i8 addrspace(1)* addrspace(1)* @test_simple(i8 addrspace(1)* %obj1, i8 addrspace(1)* %obj2, i32 %len, i1 %c) gc "statepoint-example" {
; CHECK-LABEL: define {{[^@]+}}@test_simple
; CHECK-SAME: (i8 addrspace(1)* [[OBJ1:%.*]], i8 addrspace(1)* [[OBJ2:%.*]], i32 [[LEN:%.*]], i1 [[C:%.*]]) gc "statepoint-example" {
; CHECK-NEXT: entry:
; CHECK-NEXT: [[OBJ1_12:%.*]] = getelementptr inbounds i8, i8 addrspace(1)* [[OBJ1]], i64 12
; CHECK-NEXT: [[OBJ2_16:%.*]] = getelementptr inbounds i8, i8 addrspace(1)* [[OBJ2]], i64 16
; CHECK-NEXT: [[OBJ_X_BASE1:%.*]] = select i1 [[C]], i8 addrspace(1)* [[OBJ1]], i8 addrspace(1)* [[OBJ2]], !is_base_value !0
; CHECK-NEXT: [[OBJ_X:%.*]] = select i1 [[C]], i8 addrspace(1)* [[OBJ1_12]], i8 addrspace(1)* [[OBJ2_16]]
; CHECK-NEXT: [[OBJ_Y_BASE:%.*]] = select i1 [[C]], i8 addrspace(1)* [[OBJ2]], i8 addrspace(1)* [[OBJ1]]
; CHECK-NEXT: [[OBJ_Y:%.*]] = select i1 [[C]], i8 addrspace(1)* [[OBJ2_16]], i8 addrspace(1)* [[OBJ1_12]]
; CHECK-NEXT: [[OBJ_YA:%.*]] = bitcast i8 addrspace(1)* [[OBJ_Y]] to i8 addrspace(1)* addrspace(1)*
; CHECK-NEXT: [[OBJ_X_BASE1_INT:%.*]] = ptrtoint i8 addrspace(1)* [[OBJ_X_BASE1]] to i64
; CHECK-NEXT: [[OBJ_X_INT:%.*]] = ptrtoint i8 addrspace(1)* [[OBJ_X]] to i64
; CHECK-NEXT: [[OBJ_X_OFFSET:%.*]] = sub i64 [[OBJ_X_INT]], [[OBJ_X_BASE1_INT]]
; CHECK-NEXT: [[OBJ_Y_BASE_CAST:%.*]] = bitcast i8 addrspace(1)* [[OBJ_Y_BASE]] to i8 addrspace(1)* addrspace(1)*
; CHECK-NEXT: [[OBJ_Y_BASE_INT:%.*]] = ptrtoint i8 addrspace(1)* [[OBJ_Y_BASE]] to i64
; CHECK-NEXT: [[OBJ_YA_INT:%.*]] = ptrtoint i8 addrspace(1)* [[OBJ_Y]] to i64
; CHECK-NEXT: [[OBJ_YA_OFFSET:%.*]] = sub i64 [[OBJ_YA_INT]], [[OBJ_Y_BASE_INT]]
; CHECK-NEXT: [[STATEPOINT_TOKEN:%.*]] = call token (i64, i32, void ()*, i32, i32, ...) @llvm.experimental.gc.statepoint.p0f_isVoidf(i64 2882400000, i32 0, void ()* nonnull @foo, i32 0, i32 0, i32 0, i32 0) [ "deopt"(i8 addrspace(1)* [[OBJ_X_BASE1]], i64 [[OBJ_X_OFFSET]], i8 addrspace(1)* [[OBJ_X_BASE1]], i64 [[OBJ_X_OFFSET]], i8 addrspace(1)* addrspace(1)* [[OBJ_Y_BASE_CAST]], i64 [[OBJ_YA_OFFSET]]), "gc-live"(i8 addrspace(1)* addrspace(1)* [[OBJ_YA]], i8 addrspace(1)* [[OBJ_Y_BASE]]) ]
; CHECK-NEXT: [[OBJ_YA_RELOCATED:%.*]] = call coldcc i8 addrspace(1)* @llvm.experimental.gc.relocate.p1i8(token [[STATEPOINT_TOKEN]], i32 1, i32 0)
; CHECK-NEXT: [[OBJ_YA_RELOCATED_CASTED:%.*]] = bitcast i8 addrspace(1)* [[OBJ_YA_RELOCATED]] to i8 addrspace(1)* addrspace(1)*
; CHECK-NEXT: ret i8 addrspace(1)* addrspace(1)* [[OBJ_YA_RELOCATED_CASTED]]
;
entry:
%obj1.12 = getelementptr inbounds i8, i8 addrspace(1)* %obj1, i64 12
%obj2.16 = getelementptr inbounds i8, i8 addrspace(1)* %obj2, i64 16
%obj.x = select i1 %c, i8 addrspace(1)* %obj1.12, i8 addrspace(1)* %obj2.16
%obj.y = select i1 %c, i8 addrspace(1)* %obj2.16, i8 addrspace(1)* %obj1.12
%obj.ya = bitcast i8 addrspace(1)* %obj.y to i8 addrspace(1)* addrspace(1)*
%obj.x.base = call i8 addrspace(1)* @llvm.experimental.gc.get.pointer.base.p1i8.p1i8(i8 addrspace(1)* %obj.x)
%obj.x.offset = call i64 @llvm.experimental.gc.get.pointer.offset.p1i8(i8 addrspace(1)* %obj.x)
%obj.x.base2 = call i8 addrspace(1)* @llvm.experimental.gc.get.pointer.base.p1i8.p1i8(i8 addrspace(1)* %obj.x)
%obj.x.offset2 = call i64 @llvm.experimental.gc.get.pointer.offset.p1i8(i8 addrspace(1)* %obj.x)
%obj.ya.base = call i8 addrspace(1)* addrspace(1)* @llvm.experimental.gc.get.pointer.base.p1p1i8.p1p1i8(i8 addrspace(1)* addrspace(1)* %obj.ya)
%obj.ya.offset = call i64 @llvm.experimental.gc.get.pointer.offset.p1p1i8(i8 addrspace(1)* addrspace(1)* %obj.ya)
call void @foo() readonly [
"deopt"(i8 addrspace(1)* %obj.x.base, i64 %obj.x.offset, i8 addrspace(1)* %obj.x.base2, i64 %obj.x.offset2, i8 addrspace(1)* addrspace(1)* %obj.ya.base, i64 %obj.ya.offset) ]
ret i8 addrspace(1)* addrspace(1)* %obj.ya
}
define void @test_base_of_base(i8 addrspace(1)* %obj1, i8 addrspace(1)* %obj2, i32 %len, i1 %c) gc "statepoint-example" {
; CHECK-LABEL: define {{[^@]+}}@test_base_of_base
; CHECK-SAME: (i8 addrspace(1)* [[OBJ1:%.*]], i8 addrspace(1)* [[OBJ2:%.*]], i32 [[LEN:%.*]], i1 [[C:%.*]]) gc "statepoint-example" {
; CHECK-NEXT: entry:
; CHECK-NEXT: ret void
;
entry:
%obj1.12 = getelementptr inbounds i8, i8 addrspace(1)* %obj1, i64 12
%obj2.16 = getelementptr inbounds i8, i8 addrspace(1)* %obj2, i64 16
%obj.x = select i1 %c, i8 addrspace(1)* %obj1.12, i8 addrspace(1)* %obj2.16
%obj.x.base = call i8 addrspace(1)* @llvm.experimental.gc.get.pointer.base.p1i8.p1i8(i8 addrspace(1)* %obj.x)
%obj.x.base_of_base = call i8 addrspace(1)* @llvm.experimental.gc.get.pointer.base.p1i8.p1i8(i8 addrspace(1)* %obj.x.base)
ret void
}
define i8 addrspace(1)* @test_chained(i8 addrspace(1)* %obj1, i8 addrspace(1)* %obj2, i32 %len, i1 %c) gc "statepoint-example" {
; CHECK-LABEL: define {{[^@]+}}@test_chained
; CHECK-SAME: (i8 addrspace(1)* [[OBJ1:%.*]], i8 addrspace(1)* [[OBJ2:%.*]], i32 [[LEN:%.*]], i1 [[C:%.*]]) gc "statepoint-example" {
; CHECK-NEXT: entry:
; CHECK-NEXT: [[OBJ_X_BASE1:%.*]] = select i1 [[C]], i8 addrspace(1)* [[OBJ1]], i8 addrspace(1)* [[OBJ2]], !is_base_value !0
; CHECK-NEXT: [[OBJ_X_BASE_GEP:%.*]] = getelementptr inbounds i8, i8 addrspace(1)* [[OBJ_X_BASE1]], i64 8
; CHECK-NEXT: [[OBJ_X_BASE_OF_BASE_GEP:%.*]] = getelementptr inbounds i8, i8 addrspace(1)* [[OBJ_X_BASE1]], i64 20
; CHECK-NEXT: [[OBJ_X_BASE_OF_BASE_OF_BASE_GEP:%.*]] = getelementptr inbounds i8, i8 addrspace(1)* [[OBJ_X_BASE1]], i64 24
; CHECK-NEXT: [[STATEPOINT_TOKEN:%.*]] = call token (i64, i32, void ()*, i32, i32, ...) @llvm.experimental.gc.statepoint.p0f_isVoidf(i64 2882400000, i32 0, void ()* nonnull @foo, i32 0, i32 0, i32 0, i32 0) [ "deopt"(i8 addrspace(1)* [[OBJ_X_BASE1]], i8 addrspace(1)* [[OBJ_X_BASE1]], i8 addrspace(1)* [[OBJ_X_BASE1]], i8 addrspace(1)* [[OBJ_X_BASE_GEP]], i8 addrspace(1)* [[OBJ_X_BASE_OF_BASE_GEP]], i8 addrspace(1)* [[OBJ_X_BASE_OF_BASE_OF_BASE_GEP]], i8 addrspace(1)* [[OBJ_X_BASE1]], i8 addrspace(1)* [[OBJ_X_BASE1]], i8 addrspace(1)* [[OBJ_X_BASE1]], i64 0, i64 0, i64 0, i64 8, i64 20, i64 24, i64 0, i64 0, i64 0), "gc-live"(i8 addrspace(1)* [[OBJ_X_BASE1]]) ]
; CHECK-NEXT: [[OBJ_X_BASE1_RELOCATED:%.*]] = call coldcc i8 addrspace(1)* @llvm.experimental.gc.relocate.p1i8(token [[STATEPOINT_TOKEN]], i32 0, i32 0)
; CHECK-NEXT: ret i8 addrspace(1)* [[OBJ_X_BASE1_RELOCATED]]
;
entry:
%obj1.12 = getelementptr inbounds i8, i8 addrspace(1)* %obj1, i64 12
%obj2.16 = getelementptr inbounds i8, i8 addrspace(1)* %obj2, i64 16
%obj.x = select i1 %c, i8 addrspace(1)* %obj1.12, i8 addrspace(1)* %obj2.16
%obj.x.base = call i8 addrspace(1)* @llvm.experimental.gc.get.pointer.base.p1i8.p1i8(i8 addrspace(1)* %obj.x)
%obj.x.base_of_base = call i8 addrspace(1)* @llvm.experimental.gc.get.pointer.base.p1i8.p1i8(i8 addrspace(1)* %obj.x.base)
%obj.x.base_of_base_of_base = call i8 addrspace(1)* @llvm.experimental.gc.get.pointer.base.p1i8.p1i8(i8 addrspace(1)* %obj.x.base_of_base)
%obj.x.base_gep = getelementptr inbounds i8, i8 addrspace(1)* %obj.x.base, i64 8
%obj.x.base_of_base_gep = getelementptr inbounds i8, i8 addrspace(1)* %obj.x.base_of_base, i64 20
%obj.x.base_of_base_of_base_gep = getelementptr inbounds i8, i8 addrspace(1)* %obj.x.base_of_base_of_base, i64 24
%obj.x.base_gep_base = call i8 addrspace(1)* @llvm.experimental.gc.get.pointer.base.p1i8.p1i8(i8 addrspace(1)* %obj.x.base_gep)
%obj.x.base_of_base_gep_base = call i8 addrspace(1)* @llvm.experimental.gc.get.pointer.base.p1i8.p1i8(i8 addrspace(1)* %obj.x.base_of_base_gep)
%obj.x.base_of_base_of_base_gep_base = call i8 addrspace(1)* @llvm.experimental.gc.get.pointer.base.p1i8.p1i8(i8 addrspace(1)* %obj.x.base_of_base_of_base_gep)
%obj.x.base_offset = call i64 @llvm.experimental.gc.get.pointer.offset.p1i8(i8 addrspace(1)* %obj.x.base)
%obj.x.base_of_base_offset = call i64 @llvm.experimental.gc.get.pointer.offset.p1i8(i8 addrspace(1)* %obj.x.base_of_base)
%obj.x.base_of_base_of_base_offset = call i64 @llvm.experimental.gc.get.pointer.offset.p1i8(i8 addrspace(1)* %obj.x.base_of_base_of_base)
%obj.x.base_gep_offset = call i64 @llvm.experimental.gc.get.pointer.offset.p1i8(i8 addrspace(1)* %obj.x.base_gep)
%obj.x.base_of_base_gep_offset = call i64 @llvm.experimental.gc.get.pointer.offset.p1i8(i8 addrspace(1)* %obj.x.base_of_base_gep)
%obj.x.base_of_base_of_base_gep_offset = call i64 @llvm.experimental.gc.get.pointer.offset.p1i8(i8 addrspace(1)* %obj.x.base_of_base_of_base_gep)
%obj.x.base_gep_base_offset = call i64 @llvm.experimental.gc.get.pointer.offset.p1i8(i8 addrspace(1)* %obj.x.base_gep_base)
%obj.x.base_of_base_gep_base_offset = call i64 @llvm.experimental.gc.get.pointer.offset.p1i8(i8 addrspace(1)* %obj.x.base_of_base_gep_base)
%obj.x.base_of_base_of_base_gep_base_offset = call i64 @llvm.experimental.gc.get.pointer.offset.p1i8(i8 addrspace(1)* %obj.x.base_of_base_of_base_gep_base)
call void @foo() readonly [
"deopt"(
i8 addrspace(1)* %obj.x.base,
i8 addrspace(1)* %obj.x.base_of_base_of_base,
i8 addrspace(1)* %obj.x.base_of_base,
i8 addrspace(1)* %obj.x.base_gep,
i8 addrspace(1)* %obj.x.base_of_base_gep,
i8 addrspace(1)* %obj.x.base_of_base_of_base_gep,
i8 addrspace(1)* %obj.x.base_gep_base,
i8 addrspace(1)* %obj.x.base_of_base_gep_base,
i8 addrspace(1)* %obj.x.base_of_base_of_base_gep_base,
i64 %obj.x.base_offset,
i64 %obj.x.base_of_base_offset,
i64 %obj.x.base_of_base_of_base_offset,
i64 %obj.x.base_gep_offset,
i64 %obj.x.base_of_base_gep_offset,
i64 %obj.x.base_of_base_of_base_gep_offset,
i64 %obj.x.base_gep_base_offset,
i64 %obj.x.base_of_base_gep_base_offset,
i64 %obj.x.base_of_base_of_base_gep_base_offset) ]
ret i8 addrspace(1)* %obj.x.base_of_base
}