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

[flang] AliasAnalysis: distinguish addr of arg vs. addr in arg #87723

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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions flang/include/flang/Optimizer/Analysis/AliasAnalysis.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ struct AliasAnalysis {
/// Represents memory allocated outside of a function
/// and passed to the function via host association tuple.
HostAssoc,
/// Represents direct memory access whose source cannot be further
/// determined
/// Memory address retrieved from a box, perhaps from a global or
/// an argument.
Direct,
/// Represents memory allocated by unknown means and
/// with the memory address defined by a memory reading
Expand Down Expand Up @@ -93,6 +93,9 @@ struct AliasAnalysis {

/// Return the memory source of a value.
Source getSource(mlir::Value);

/// Return true if `v` is a dummy argument.
static bool isDummyArgument(mlir::Value v);
};

inline llvm::raw_ostream &operator<<(llvm::raw_ostream &os,
Expand Down
54 changes: 35 additions & 19 deletions flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,6 @@ using namespace mlir;
// AliasAnalysis: alias
//===----------------------------------------------------------------------===//

static bool isDummyArgument(mlir::Value v) {
auto blockArg{v.dyn_cast<mlir::BlockArgument>()};
if (!blockArg)
return false;

return blockArg.getOwner()->isEntryBlock();
}

/// Temporary function to skip through all the no op operations
/// TODO: Generalize support of fir.load
static mlir::Value getOriginalDef(mlir::Value v) {
Expand Down Expand Up @@ -88,21 +80,34 @@ AliasResult AliasAnalysis::alias(Value lhs, Value rhs) {
auto lhsSrc = getSource(lhs);
auto rhsSrc = getSource(rhs);
bool approximateSource = lhsSrc.approximateSource || rhsSrc.approximateSource;
LLVM_DEBUG(llvm::dbgs() << "AliasAnalysis::alias\n";
llvm::dbgs() << " lhs: " << lhs << "\n";
llvm::dbgs() << " lhsSrc: " << lhsSrc << "\n";
llvm::dbgs() << " rhs: " << rhs << "\n";
llvm::dbgs() << " rhsSrc: " << rhsSrc << "\n";
llvm::dbgs() << "\n";);
LLVM_DEBUG(
llvm::dbgs() << "AliasAnalysis::alias\n";
llvm::dbgs() << " lhs: " << lhs << "\n";
llvm::dbgs() << " lhsSrc: " << lhsSrc << "\n";
llvm::dbgs() << " lhsSrc kind: " << EnumToString(lhsSrc.kind) << "\n";
llvm::dbgs() << " lhsSrc pointer: "
<< lhsSrc.attributes.test(Attribute::Pointer) << "\n";
llvm::dbgs() << " lhsSrc target: "
<< lhsSrc.attributes.test(Attribute::Target) << "\n";
llvm::dbgs() << " rhs: " << rhs << "\n";
llvm::dbgs() << " rhsSrc: " << rhsSrc << "\n";
llvm::dbgs() << " rhsSrc kind: " << EnumToString(rhsSrc.kind) << "\n";
llvm::dbgs() << " rhsSrc pointer: "
<< rhsSrc.attributes.test(Attribute::Pointer) << "\n";
llvm::dbgs() << " rhsSrc target: "
<< rhsSrc.attributes.test(Attribute::Target) << "\n";
llvm::dbgs() << "\n";);

// Indirect case currently not handled. Conservatively assume
// it aliases with everything
if (lhsSrc.kind > SourceKind::Direct || rhsSrc.kind > SourceKind::Direct) {
return AliasResult::MayAlias;
}

// SourceKind::Direct is set for the addresses wrapped in a global boxes.
// ie: fir.global @_QMpointersEp : !fir.box<!fir.ptr<f32>>
// SourceKind::Direct is set for the addresses wrapped in a box, perhaps from
// a global or an argument.
// e.g.: fir.global @_QMpointersEp : !fir.box<!fir.ptr<f32>>
// e.g.: %arg0: !fir.ref<!fir.box<!fir.ptr<f32>>>
// Though nothing is known about them, they would only alias with targets or
// pointers
bool directSourceToNonTargetOrPointer = false;
Expand Down Expand Up @@ -399,19 +404,30 @@ AliasAnalysis::Source AliasAnalysis::getSource(mlir::Value v) {
if (!defOp && type == SourceKind::Unknown)
// Check if the memory source is coming through a dummy argument.
if (isDummyArgument(v)) {
type = SourceKind::Argument;
ty = v.getType();
if (fir::valueHasFirAttribute(v, fir::getTargetAttrName()))
attributes.set(Attribute::Target);

if (Source::isPointerReference(ty))
attributes.set(Attribute::Pointer);
if (followBoxAddr && fir::isa_ref_type(ty))
Copy link
Contributor

Choose a reason for hiding this comment

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

I believe, this might be an attempt at handling INTENT(IN). With INTENT(IN) the box is passed by value so there is no !fir.ref type. Could we check for that instead?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'm not sure I understand your meaning. Given the following:

subroutine test(p)
  real, pointer, intent(in) :: p
end subroutine test

flang-new generates:

func.func @_QPtest(%arg0: !fir.ref<!fir.box<!fir.ptr<f32>>> {fir.bindc_name = "p"}) {
  %0:2 = hlfir.declare %arg0 {fortran_attrs = #fir.var_attrs<intent_in, pointer>, uniq_name = "_QFtestEp"} : (!fir.ref<!fir.box<!fir.ptr<f32>>>) -> (!fir.ref<!fir.box<!fir.ptr<f32>>>, !fir.ref<!fir.box<!fir.ptr<f32>>>)
  return
}

If I drop the intent(in), the only difference is that the intent_in attribute is lost.

Would you please provide an example of the case you have in mind? Thanks.

Copy link
Collaborator Author

@jdenny-ornl jdenny-ornl Apr 25, 2024

Choose a reason for hiding this comment

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

If I remove the fir::isa_ref_type(ty) check, several tests fail. For example, in the function test3 in flang/test/Analysis/AliasAnalysis/alias-analysis-host-assoc.fir, y(1) is then analyzed as a Direct instead of an Argument. It has type !fir.box<!fir.array<?xi32>> and is declared as integer, target :: y(:). Is that the information you're looking for?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

After further thought, maybe there is a cleaner way to write this check:

  • Declare a new followBoxAddrLoad variable local to AliasAnalysis::getSource, and set it to true right before the return in the fir::LoadOp case.
  • For the dummy arg check above, replace followBoxAddr && fir::isa_ref_type(ty) with followBoxAddrLoad.
  • For the fir::AddrOfOp check, replace followBoxAddr && mlir::isa<fir::BaseBoxType>(fir::unwrapRefType(ty)) with followBoxAddrLoad.

I tried that, and check-flang still succeeded. What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

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

I am in the process of compiling your changes and will give them a better look today. What you describe though sounds really good. I will give it a shot.

Copy link
Collaborator Author

@jdenny-ornl jdenny-ornl May 1, 2024

Choose a reason for hiding this comment

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

That test doesn't deal with a fortran pointer, so this patch leaves it as SourceKind::Argument

fir::isa_ref_type(ty) does not mean fortran POINTER. fir::isPointerType(ty) does.

Right, when I wrote that comment, I somehow forgot that I generalized beyond pointers. See my previous comment.

What additional benefits do you mean?

Boxes are also passed by value. You would want to distinguish between them and their data as well. In this case followBoxAddrLoad would not be set.

While the cases I'm trying to address so far specifically involve the fir::LoadOp case in AliasAnalysis::getSource, I'd appreciate any fortran example that does not. My understanding of relevant use cases is probably too narrow. [edit: Oops, you just posted one while I was writing this.]

Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if it will be more clear to only keep the first four source kinds and move Direct into the attributes. We are looking for the source entity here, so the source kind should represent this source entity. Whatever happens during the use-def chain walk should not affect the kind of the source entity that we end up finding. The Direct, in this case, would mean that the reference value passed to getSource is a result of one or more dereferences of the original source. I just find it easier to think about it this way, though, you may know cases where it can complicate the implementation.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I was due for an example of box, passed by value:

subroutine test(p)
  real [,TARGET] :: p(:)
end subroutine test
func.func @_QPtest(%arg0: !fir.box<!fir.array<?xf32>> {fir.bindc_name = "p", fir.target})

I am not the expert here (that would be @jeanPerier ), but the rationale was to communicate in FIR that the callee does not modify the box and that therefore, to the caller, the box is a shallow constant. They do end up being passed by reference in codegen but show as values in FIR.

Thanks for the example. That helps. However, I'm struggling with the idea of performing a FIR alias analysis on something (the box in this example) that FIR represents as a value not an address. If indeed it never makes sense to do that, then it seems it wouldn't matter whether we distinguish this box from its content as only the content is a candidate for alias analysis.

I wonder if it will be more clear to only keep the first four source kinds and move Direct into the attributes. We are looking for the source entity here, so the source kind should represent this source entity. Whatever happens during the use-def chain walk should not affect the kind of the source entity that we end up finding. The Direct, in this case, would mean that the reference value passed to getSource is a result of one or more dereferences of the original source. I just find it easier to think about it this way, though, you may know cases where it can complicate the implementation.

Might make sense. However, I've been assuming that, once you extract an address from a box, then you no longer have a global, dummy arg, local, or whatever the box is, so I thought it was ok to just call it a Direct. Maybe that's wrong? On that point, does anyone object to this patch's changes to flang/test/Transforms/tbaa2.fir? There, this patch causes the address extracted from a dummy argument allocatable to be labeled SourceKind::Direct and not SourceKind::Argument. Is that problematic?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm struggling with the idea of performing a FIR alias analysis on something (the box in this example) that FIR represents as a value not an address

Right, we could do some ground work before adding further enhancements to enforce that we start from a memory reference always. I wanted to check with @jeanPerier before we do that, in case he might need to make adjustments in lowering.

Let's note that when we are at the LLVM level, the box is an address and it becomes a legitimate question to ask. I will have to dig some to see how we handle this case.

What I would be struggling with is that following the address of a POINTER takes you to a SourceKind::Direct but following the address of a TARGET takes you to a SourceKind::Argument.

I wonder if it will be more clear to only keep the first four source kinds and move Direct into the attributes

Yes, then we would get consistent source kinds for target and pointers and we should get the same attribute, that I would hope we would have renamed to something other than "Direct" then. That would require some buy-in from @tblah as it would require changes in runOnAliasInterface though it should not cause any change in the results.

Copy link
Contributor

Choose a reason for hiding this comment

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

Talking to Jean, reminded me that we are already handling the query on a box value as a query on the data.
bool followBoxAddr{mlir::isa<fir::BaseBoxType>(v.getType())};
We initialize followBoxAddr to true if we are starting from a box.

Though this is a moot point for this discussion since you are interested on a distinction between references.

I am working on a proposal to eliminate SourceKind::Direct while still retaining the information it was capturing. You still will be able to distinguish between data and box. I will write up an RFC tomorrow and will send out a WIP PR.

type = SourceKind::Direct;
else
type = SourceKind::Argument;
}

if (type == SourceKind::Global || type == SourceKind::Direct)
if (global)
return {global, type, ty, attributes, approximateSource};

return {v, type, ty, attributes, approximateSource};
}

bool AliasAnalysis::isDummyArgument(mlir::Value v) {
auto blockArg{v.dyn_cast<mlir::BlockArgument>()};
if (!blockArg)
return false;
auto *owner{blockArg.getOwner()};
return owner->isEntryBlock() &&
mlir::isa<mlir::FunctionOpInterface>(owner->getParentOp());
}

} // namespace fir
17 changes: 15 additions & 2 deletions flang/lib/Optimizer/Transforms/AddAliasTags.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -170,9 +170,22 @@ void AddAliasTagsPass::runOnAliasInterface(fir::FirAliasTagOpInterface op,
LLVM_DEBUG(llvm::dbgs().indent(2) << "Found reference to direct " << name
<< " at " << *op << "\n");
tag = state.getFuncTree(func).directDataTree.getTag(name);
} else if (fir::AliasAnalysis::isDummyArgument(
source.u.get<mlir::Value>())) {
std::string name = getFuncArgName(source.u.get<mlir::Value>());
if (!name.empty()) {
LLVM_DEBUG(llvm::dbgs().indent(2)
<< "Found reference to direct from dummy argument " << name
<< " at " << *op << "\n");
tag = state.getFuncTree(func).directDataTree.getTag(name);
} else {
LLVM_DEBUG(llvm::dbgs().indent(2)
<< "WARN: for direct, couldn't find a name for dummy "
<< "argument " << *op << "\n");
}
} else {
// SourceKind::Direct is likely to be extended to cases which are not a
// SymbolRefAttr in the future
// SourceKind::Direct is likely to be extended to more cases in the
// future
LLVM_DEBUG(llvm::dbgs().indent(2) << "Can't get name for direct "
<< source << " at " << *op << "\n");
}
Expand Down
2 changes: 1 addition & 1 deletion flang/test/Analysis/AliasAnalysis/alias-analysis-2.fir
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
// pointer arguments
// CHECK-DAG: arg2.addr#0 <-> func.region0#0: MayAlias
// CHECK-DAG: arg2.addr#0 <-> func.region0#1: MayAlias
// CHECK-DAG: arg2.addr#0 <-> func.region0#2: MustAlias
// CHECK-DAG: arg2.addr#0 <-> func.region0#2: MayAlias
// CHECK-DAG: boxp1.addr#0 <-> arg2.addr#0: MayAlias

func.func @_QFPtest(%arg0: !fir.ref<f32> {fir.bindc_name = "v1", fir.target}, %arg1: !fir.ref<f32> {fir.bindc_name = "v2", fir.target}, %arg2: !fir.ref<!fir.box<!fir.ptr<f32>>> ) attributes {test.ptr = "func"} {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Check aliasing with the address passed via a pointer dummy argument.

// Use --mlir-disable-threading so that the AA queries are serialized
// as well as its diagnostic output.
// RUN: fir-opt %s \
// RUN: -pass-pipeline='builtin.module(func.func(test-fir-alias-analysis))' \
// RUN: --mlir-disable-threading 2>&1 | FileCheck -match-full-lines %s

// subroutine test(p0, p1, arr, t_arr, alloc, t_alloc)
// real, pointer :: p0, p1
// real :: arr(:)
// real, target :: t_arr(:)
// real, allocatable :: alloc
// real, allocatable, target :: t_alloc
// real, target :: t
// real :: v
// v = p0
// v = p1
// v = arr(1)
// v = t_arr(1)
// v = alloc
// v = t_alloc
// end subroutine test

// CHECK-LABEL: Testing : "_QPtest"

// The address in a pointer can alias the address in another pointer or the
// address of a target but not the address of other variables.
// CHECK-DAG: t.addr#0 <-> p0.tgt_addr#0: MayAlias
// CHECK-DAG: t.addr#0 <-> p1.tgt_addr#0: MayAlias
// CHECK-DAG: v.addr#0 <-> p0.tgt_addr#0: NoAlias
// CHECK-DAG: v.addr#0 <-> p1.tgt_addr#0: NoAlias
// CHECK-DAG: p0.tgt_addr#0 <-> p1.tgt_addr#0: MayAlias

// Determining whether the address *in* a pointer can alias the address *of* a
// pointer is not yet handled. In the past, when it was the same pointer, that
// relationship was mistakenly determined to be MustAlias.
// CHECK-DAG: p0.tgt_addr#0 <-> func.region0#0: MayAlias
// CHECK-DAG: p0.tgt_addr#0 <-> func.region0#1: MayAlias
// CHECK-DAG: p1.tgt_addr#0 <-> func.region0#0: MayAlias
// CHECK-DAG: p1.tgt_addr#0 <-> func.region0#1: MayAlias

// For some cases, AliasAnalysis analyzes hlfir.designate like fir.box_addr, so
// make sure it doesn't mistakenly see arr(1).addr as an address that was loaded
// from a pointer and that could alias something. However, t_arr is a target.
// CHECK-DAG: p0.tgt_addr#0 <-> arr(1).addr#0: NoAlias
// CHECK-DAG: p0.tgt_addr#0 <-> t_arr(1).addr#0: MayAlias

// Like a pointer, an allocatable contains an address, but an allocatable is not
// a pointer and so cannot alias pointers. However, t_alloc is a target.
// CHECK-DAG: p0.tgt_addr#0 <-> alloc.tgt_addr#0: NoAlias
// CHECK-DAG: p0.tgt_addr#0 <-> t_alloc.tgt_addr#0: MayAlias

// The address *in* an allocatable cannot alias the address *of* that
// allocatable.
// CHECK-DAG: alloc.addr#0 <-> alloc.tgt_addr#0: NoAlias

func.func @_QPtest(%arg0: !fir.ref<!fir.box<!fir.ptr<f32>>> {fir.bindc_name = "p0"}, %arg1: !fir.ref<!fir.box<!fir.ptr<f32>>> {fir.bindc_name = "p1"}, %arg2: !fir.box<!fir.array<?xf32>> {fir.bindc_name = "arr"}, %arg3: !fir.box<!fir.array<?xf32>> {fir.bindc_name = "t_arr", fir.target}, %arg4: !fir.ref<!fir.box<!fir.heap<f32>>> {fir.bindc_name = "alloc"}, %arg5: !fir.ref<!fir.box<!fir.heap<f32>>> {fir.bindc_name = "t_alloc", fir.target}) attributes {test.ptr="func"} {
%0:2 = hlfir.declare %arg4 {fortran_attrs = #fir.var_attrs<allocatable>, uniq_name = "_QFtestEalloc", test.ptr="alloc.addr"} : (!fir.ref<!fir.box<!fir.heap<f32>>>) -> (!fir.ref<!fir.box<!fir.heap<f32>>>, !fir.ref<!fir.box<!fir.heap<f32>>>)
%1:2 = hlfir.declare %arg2 {uniq_name = "_QFtestEarr"} : (!fir.box<!fir.array<?xf32>>) -> (!fir.box<!fir.array<?xf32>>, !fir.box<!fir.array<?xf32>>)
%2:2 = hlfir.declare %arg0 {fortran_attrs = #fir.var_attrs<pointer>, uniq_name = "_QFtestEp0"} : (!fir.ref<!fir.box<!fir.ptr<f32>>>) -> (!fir.ref<!fir.box<!fir.ptr<f32>>>, !fir.ref<!fir.box<!fir.ptr<f32>>>)
%3:2 = hlfir.declare %arg1 {fortran_attrs = #fir.var_attrs<pointer>, uniq_name = "_QFtestEp1"} : (!fir.ref<!fir.box<!fir.ptr<f32>>>) -> (!fir.ref<!fir.box<!fir.ptr<f32>>>, !fir.ref<!fir.box<!fir.ptr<f32>>>)
%4 = fir.alloca f32 {bindc_name = "t", fir.target, uniq_name = "_QFtestEt"}
%5:2 = hlfir.declare %4 {fortran_attrs = #fir.var_attrs<target>, uniq_name = "_QFtestEt", test.ptr="t.addr"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
%6:2 = hlfir.declare %arg5 {fortran_attrs = #fir.var_attrs<allocatable, target>, uniq_name = "_QFtestEt_alloc"} : (!fir.ref<!fir.box<!fir.heap<f32>>>) -> (!fir.ref<!fir.box<!fir.heap<f32>>>, !fir.ref<!fir.box<!fir.heap<f32>>>)
%7:2 = hlfir.declare %arg3 {fortran_attrs = #fir.var_attrs<target>, uniq_name = "_QFtestEt_arr"} : (!fir.box<!fir.array<?xf32>>) -> (!fir.box<!fir.array<?xf32>>, !fir.box<!fir.array<?xf32>>)
%8 = fir.alloca f32 {bindc_name = "v", uniq_name = "_QFtestEv"}
%9:2 = hlfir.declare %8 {uniq_name = "_QFtestEv", test.ptr="v.addr"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
%10 = fir.load %2#0 : !fir.ref<!fir.box<!fir.ptr<f32>>>
%11 = fir.box_addr %10 {test.ptr="p0.tgt_addr"} : (!fir.box<!fir.ptr<f32>>) -> !fir.ptr<f32>
%12 = fir.load %11 : !fir.ptr<f32>
hlfir.assign %12 to %9#0 : f32, !fir.ref<f32>
%13 = fir.load %3#0 : !fir.ref<!fir.box<!fir.ptr<f32>>>
%14 = fir.box_addr %13 {test.ptr="p1.tgt_addr"} : (!fir.box<!fir.ptr<f32>>) -> !fir.ptr<f32>
%15 = fir.load %14 : !fir.ptr<f32>
hlfir.assign %15 to %9#0 : f32, !fir.ref<f32>
%c1 = arith.constant 1 : index
%16 = hlfir.designate %1#0 (%c1) {test.ptr="arr(1).addr"} : (!fir.box<!fir.array<?xf32>>, index) -> !fir.ref<f32>
%17 = fir.load %16 : !fir.ref<f32>
hlfir.assign %17 to %9#0 : f32, !fir.ref<f32>
%c1_0 = arith.constant 1 : index
%18 = hlfir.designate %7#0 (%c1_0) {test.ptr="t_arr(1).addr"} : (!fir.box<!fir.array<?xf32>>, index) -> !fir.ref<f32>
%19 = fir.load %18 : !fir.ref<f32>
hlfir.assign %19 to %9#0 : f32, !fir.ref<f32>
%20 = fir.load %0#0 : !fir.ref<!fir.box<!fir.heap<f32>>>
%21 = fir.box_addr %20 {test.ptr="alloc.tgt_addr"} : (!fir.box<!fir.heap<f32>>) -> !fir.heap<f32>
%22 = fir.load %21 : !fir.heap<f32>
hlfir.assign %22 to %9#0 : f32, !fir.ref<f32>
%23 = fir.load %6#0 : !fir.ref<!fir.box<!fir.heap<f32>>>
%24 = fir.box_addr %23 {test.ptr="t_alloc.tgt_addr"} : (!fir.box<!fir.heap<f32>>) -> !fir.heap<f32>
%25 = fir.load %24 : !fir.heap<f32>
hlfir.assign %25 to %9#0 : f32, !fir.ref<f32>
return
}
4 changes: 2 additions & 2 deletions flang/test/Transforms/tbaa2.fir
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,14 @@
// CHECK: #[[ANY_ACCESS:.+]] = #llvm.tbaa_type_desc<id = "any access", members = {<#[[ROOT]], 0>}>
// CHECK: #[[ANY_DATA:.+]] = #llvm.tbaa_type_desc<id = "any data access", members = {<#[[ANY_ACCESS]], 0>}>
// CHECK: #[[ANY_GLBL:.+]] = #llvm.tbaa_type_desc<id = "global data", members = {<#[[ANY_DATA]], 0>}>
// CHECK: #[[ANY_ARG:.+]] = #llvm.tbaa_type_desc<id = "dummy arg data", members = {<#[[ANY_DATA]], 0>}>
// CHECK: #[[ANY_DIRECT:.+]] = #llvm.tbaa_type_desc<id = "direct data", members = {<#[[ANY_DATA]], 0>}>
// CHECK: #[[ANY_ARG:.+]] = #llvm.tbaa_type_desc<id = "dummy arg data", members = {<#[[ANY_DATA]], 0>}>
// CHECK: #[[GLBL_ZSTART:.+]] = #llvm.tbaa_type_desc<id = "global data/_QMmodEzstart", members = {<#[[ANY_GLBL]], 0>}>
// CHECK: #[[GLBL_ZSTOP:.+]] = #llvm.tbaa_type_desc<id = "global data/_QMmodEzstop", members = {<#[[ANY_GLBL]], 0>}>
// CHECK: #[[GLBL_YSTART:.+]] = #llvm.tbaa_type_desc<id = "global data/_QMmodEystart", members = {<#[[ANY_GLBL]], 0>}>
// CHECK: #[[GLBL_YSTOP:.+]] = #llvm.tbaa_type_desc<id = "global data/_QMmodEystop", members = {<#[[ANY_GLBL]], 0>}>
// CHECK: #[[GLBL_XSTART:.+]] = #llvm.tbaa_type_desc<id = "global data/_QMmodExstart", members = {<#[[ANY_GLBL]], 0>}>
// CHECK: #[[ARG_LOW:.+]] = #llvm.tbaa_type_desc<id = "dummy arg data/_QMmodFcalleeElow", members = {<#[[ANY_ARG]], 0>}>
// CHECK: #[[ARG_LOW:.+]] = #llvm.tbaa_type_desc<id = "direct data/_QMmodFcalleeElow", members = {<#[[ANY_DIRECT]], 0>}>
// CHECK: #[[DIRECT_A:.+]] = #llvm.tbaa_type_desc<id = "direct data/_QMmodEa", members = {<#[[ANY_DIRECT]], 0>}>
// CHECK: #[[DIRECT_B:.+]] = #llvm.tbaa_type_desc<id = "direct data/_QMmodEb", members = {<#[[ANY_DIRECT]], 0>}>
// CHECK: #[[ARG_Z:.+]] = #llvm.tbaa_type_desc<id = "dummy arg data/_QMmodFcalleeEz", members = {<#[[ANY_ARG]], 0>}>
Expand Down
Loading