From 8456c3a789285079ad35d146e487436b5a27b027 Mon Sep 17 00:00:00 2001 From: Amara Emerson Date: Fri, 15 Jan 2021 22:51:54 -0800 Subject: [PATCH 1/2] AArch64: fix regression introduced by fcmp immediate selection. Forgot to check if the predicate is safe to commutate operands. --- .../GISel/AArch64InstructionSelector.cpp | 25 ++++++++++++------ .../AArch64/GlobalISel/select-fcmp.mir | 26 +++++++++++++++++++ 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/llvm/lib/Target/AArch64/GISel/AArch64InstructionSelector.cpp b/llvm/lib/Target/AArch64/GISel/AArch64InstructionSelector.cpp index b24fad35e32bbd..0021456a596d4d 100644 --- a/llvm/lib/Target/AArch64/GISel/AArch64InstructionSelector.cpp +++ b/llvm/lib/Target/AArch64/GISel/AArch64InstructionSelector.cpp @@ -34,6 +34,7 @@ #include "llvm/CodeGen/MachineRegisterInfo.h" #include "llvm/CodeGen/TargetOpcodes.h" #include "llvm/IR/Constants.h" +#include "llvm/IR/Instructions.h" #include "llvm/IR/PatternMatch.h" #include "llvm/IR/Type.h" #include "llvm/IR/IntrinsicsAArch64.h" @@ -177,8 +178,10 @@ class AArch64InstructionSelector : public InstructionSelector { MachineIRBuilder &MIRBuilder) const; /// Emit a floating point comparison between \p LHS and \p RHS. + /// \p Pred if given is the intended predicate to use. MachineInstr *emitFPCompare(Register LHS, Register RHS, - MachineIRBuilder &MIRBuilder) const; + MachineIRBuilder &MIRBuilder, + Optional = None) const; MachineInstr *emitInstr(unsigned Opcode, std::initializer_list DstOps, @@ -1483,11 +1486,11 @@ bool AArch64InstructionSelector::selectCompareBranchFedByFCmp( assert(I.getOpcode() == TargetOpcode::G_BRCOND); // Unfortunately, the mapping of LLVM FP CC's onto AArch64 CC's isn't // totally clean. Some of them require two branches to implement. - emitFPCompare(FCmp.getOperand(2).getReg(), FCmp.getOperand(3).getReg(), MIB); + auto Pred = (CmpInst::Predicate)FCmp.getOperand(1).getPredicate(); + emitFPCompare(FCmp.getOperand(2).getReg(), FCmp.getOperand(3).getReg(), MIB, + Pred); AArch64CC::CondCode CC1, CC2; - changeFCMPPredToAArch64CC( - static_cast(FCmp.getOperand(1).getPredicate()), CC1, - CC2); + changeFCMPPredToAArch64CC(static_cast(Pred), CC1, CC2); MachineBasicBlock *DestMBB = I.getOperand(1).getMBB(); MIB.buildInstr(AArch64::Bcc, {}, {}).addImm(CC1).addMBB(DestMBB); if (CC2 != AArch64CC::AL) @@ -3090,7 +3093,7 @@ bool AArch64InstructionSelector::select(MachineInstr &I) { CmpInst::Predicate Pred = static_cast(I.getOperand(1).getPredicate()); if (!emitFPCompare(I.getOperand(2).getReg(), I.getOperand(3).getReg(), - MIRBuilder) || + MIRBuilder, Pred) || !emitCSetForFCmp(I.getOperand(0).getReg(), Pred, MIRBuilder)) return false; I.eraseFromParent(); @@ -4211,7 +4214,8 @@ MachineInstr *AArch64InstructionSelector::emitCSetForFCmp( MachineInstr * AArch64InstructionSelector::emitFPCompare(Register LHS, Register RHS, - MachineIRBuilder &MIRBuilder) const { + MachineIRBuilder &MIRBuilder, + Optional Pred) const { MachineRegisterInfo &MRI = *MIRBuilder.getMRI(); LLT Ty = MRI.getType(LHS); if (Ty.isVector()) @@ -4224,7 +4228,12 @@ AArch64InstructionSelector::emitFPCompare(Register LHS, Register RHS, // to explicitly materialize a constant. const ConstantFP *FPImm = getConstantFPVRegVal(RHS, MRI); bool ShouldUseImm = FPImm && (FPImm->isZero() && !FPImm->isNegative()); - if (!ShouldUseImm) { + + auto IsEqualityPred = [](CmpInst::Predicate P) { + return P == CmpInst::FCMP_OEQ || P == CmpInst::FCMP_ONE || + P == CmpInst::FCMP_UEQ || P == CmpInst::FCMP_UNE; + }; + if (!ShouldUseImm && Pred && IsEqualityPred(*Pred)) { // Try commutating the operands. const ConstantFP *LHSImm = getConstantFPVRegVal(LHS, MRI); if (LHSImm && (LHSImm->isZero() && !LHSImm->isNegative())) { diff --git a/llvm/test/CodeGen/AArch64/GlobalISel/select-fcmp.mir b/llvm/test/CodeGen/AArch64/GlobalISel/select-fcmp.mir index c12cd3343c7e6b..cde785a6a4465b 100644 --- a/llvm/test/CodeGen/AArch64/GlobalISel/select-fcmp.mir +++ b/llvm/test/CodeGen/AArch64/GlobalISel/select-fcmp.mir @@ -134,3 +134,29 @@ body: | RET_ReallyLR implicit $s0 ... +--- +name: zero_lhs_not_commutative_pred +alignment: 4 +legalized: true +regBankSelected: true +tracksRegLiveness: true +body: | + bb.1: + liveins: $s0, $s1 + + ; CHECK-LABEL: name: zero_lhs_not_commutative_pred + ; CHECK: liveins: $s0, $s1 + ; CHECK: [[COPY:%[0-9]+]]:fpr32 = COPY $s0 + ; CHECK: [[FMOVS0_:%[0-9]+]]:fpr32 = FMOVS0 + ; CHECK: FCMPSrr [[FMOVS0_]], [[COPY]], implicit-def $nzcv + ; CHECK: [[CSINCWr:%[0-9]+]]:gpr32 = CSINCWr $wzr, $wzr, 5, implicit $nzcv + ; CHECK: $s0 = COPY [[CSINCWr]] + ; CHECK: RET_ReallyLR implicit $s0 + %0:fpr(s32) = COPY $s0 + %1:fpr(s32) = COPY $s1 + %2:fpr(s32) = G_FCONSTANT float 0.000000e+00 + %3:gpr(s32) = G_FCMP floatpred(olt), %2(s32), %0 + $s0 = COPY %3(s32) + RET_ReallyLR implicit $s0 + +... From 668827b6485664dbcf6caa2756fe2f6579ab1885 Mon Sep 17 00:00:00 2001 From: Jeroen Dobbelaere Date: Sat, 16 Jan 2021 09:14:18 +0100 Subject: [PATCH 2/2] Introduce llvm.noalias.decl intrinsic The ``llvm.experimental.noalias.scope.decl`` intrinsic identifies where a noalias scope is declared. When the intrinsic is duplicated, a decision must also be made about the scope: depending on the reason of the duplication, the scope might need to be duplicated as well. Reviewed By: nikic, jdoerfert Differential Revision: https://reviews.llvm.org/D93039 --- llvm/docs/LangRef.rst | 76 ++++++++++++++++ llvm/include/llvm/IR/IRBuilder.h | 7 ++ llvm/include/llvm/IR/Intrinsics.h | 3 + llvm/include/llvm/IR/Intrinsics.td | 10 +++ llvm/lib/CodeGen/IntrinsicLowering.cpp | 1 + llvm/lib/CodeGen/SelectionDAG/FastISel.cpp | 2 + .../SelectionDAG/SelectionDAGBuilder.cpp | 5 +- llvm/lib/IR/IRBuilder.cpp | 7 ++ llvm/lib/IR/Verifier.cpp | 86 +++++++++++++++++++ llvm/test/Verifier/noalias_scope_decl.ll | 61 +++++++++++++ 10 files changed, 257 insertions(+), 1 deletion(-) create mode 100644 llvm/test/Verifier/noalias_scope_decl.ll diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst index a6f6e8281a7203..ccf1feb420eb5c 100644 --- a/llvm/docs/LangRef.rst +++ b/llvm/docs/LangRef.rst @@ -19603,6 +19603,82 @@ Semantics: This function returns the same values as the libm ``trunc`` functions would and handles error conditions in the same way. +.. _int_experimental_noalias_scope_decl: + +'``llvm.experimental.noalias.scope.decl``' Intrinsic +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Syntax: +""""""" + + +:: + + declare void @llvm.experimental.noalias.scope.decl(metadata !id.scope.list) + +Overview: +""""""""" + +The ``llvm.experimental.noalias.scope.decl`` intrinsic identifies where a +noalias scope is declared. When the intrinsic is duplicated, a decision must +also be made about the scope: depending on the reason of the duplication, +the scope might need to be duplicated as well. + + +Arguments: +"""""""""" + +The ``!id.scope.list`` argument is metadata that is a list of ``noalias`` +metadata references. The format is identical to that required for ``noalias`` +metadata. This list must have exactly one element. + +Semantics: +"""""""""" + +The ``llvm.experimental.noalias.scope.decl`` intrinsic identifies where a +noalias scope is declared. When the intrinsic is duplicated, a decision must +also be made about the scope: depending on the reason of the duplication, +the scope might need to be duplicated as well. + +For example, when the intrinsic is used inside a loop body, and that loop is +unrolled, the associated noalias scope must also be duplicated. Otherwise, the +noalias property it signifies would spill across loop iterations, whereas it +was only valid within a single iteration. + +.. code-block:: llvm + + ; This examples shows two possible positions for noalias.decl and how they impact the semantics: + ; If it is outside the loop (Version 1), then %a and %b are noalias across *all* iterations. + ; If it is inside the loop (Version 2), then %a and %b are noalias only within *one* iteration. + declare void @decl_in_loop(i8* %a.base, i8* %b.base) { + entry: + ; call void @llvm.experimental.noalias.scope.decl(metadata !2) ; Version 1: noalias decl outside loop + br label %loop + + loop: + %a = phi i8* [ %a.base, %entry ], [ %a.inc, %loop ] + %b = phi i8* [ %b.base, %entry ], [ %b.inc, %loop ] + ; call void @llvm.experimental.noalias.scope.decl(metadata !2) ; Version 2: noalias decl inside loop + %val = load i8, i8* %a, !alias.scope !2 + store i8 %val, i8* %b, !noalias !2 + %a.inc = getelementptr inbounds i8, i8* %a, i64 1 + %b.inc = getelementptr inbounds i8, i8* %b, i64 1 + %cond = call i1 @cond() + br i1 %cond, label %loop, label %exit + + exit: + ret void + } + + !0 = !{!0} ; domain + !1 = !{!1, !0} ; scope + !2 = !{!1} ; scope list + +Multiple calls to `@llvm.experimental.noalias.scope.decl` for the same scope +are possible, but one should never dominate another. Violations are pointed out +by the verifier as they indicate a problem in either a transformation pass or +the input. + Floating Point Environment Manipulation intrinsics -------------------------------------------------- diff --git a/llvm/include/llvm/IR/IRBuilder.h b/llvm/include/llvm/IR/IRBuilder.h index c9074abe88c2d8..9cefc9aa764c08 100644 --- a/llvm/include/llvm/IR/IRBuilder.h +++ b/llvm/include/llvm/IR/IRBuilder.h @@ -858,6 +858,13 @@ class IRBuilderBase { CallInst *CreateAssumption(Value *Cond, ArrayRef OpBundles = llvm::None); + /// Create a llvm.experimental.noalias.scope.decl intrinsic call. + Instruction *CreateNoAliasScopeDeclaration(Value *Scope); + Instruction *CreateNoAliasScopeDeclaration(MDNode *ScopeTag) { + return CreateNoAliasScopeDeclaration( + MetadataAsValue::get(Context, ScopeTag)); + } + /// Create a call to the experimental.gc.statepoint intrinsic to /// start a new statepoint sequence. CallInst *CreateGCStatepointCall(uint64_t ID, uint32_t NumPatchBytes, diff --git a/llvm/include/llvm/IR/Intrinsics.h b/llvm/include/llvm/IR/Intrinsics.h index 08f64be87b1476..f9b6c098a3f27f 100644 --- a/llvm/include/llvm/IR/Intrinsics.h +++ b/llvm/include/llvm/IR/Intrinsics.h @@ -34,6 +34,9 @@ class AttributeList; /// function known by LLVM. The enum values are returned by /// Function::getIntrinsicID(). namespace Intrinsic { + // Abstraction for the arguments of the noalias intrinsics + static const int NoAliasScopeDeclScopeArg = 0; + // Intrinsic ID type. This is an opaque typedef to facilitate splitting up // the enum into target-specific enums. typedef unsigned ID; diff --git a/llvm/include/llvm/IR/Intrinsics.td b/llvm/include/llvm/IR/Intrinsics.td index 538039550363c7..b2bfc6e6f9e6a3 100644 --- a/llvm/include/llvm/IR/Intrinsics.td +++ b/llvm/include/llvm/IR/Intrinsics.td @@ -539,6 +539,16 @@ def int_readcyclecounter : DefaultAttrsIntrinsic<[llvm_i64_ty]>; def int_assume : DefaultAttrsIntrinsic<[], [llvm_i1_ty], [IntrWillReturn, NoUndef>]>; +// 'llvm.experimental.noalias.scope.decl' intrinsic: Inserted at the location of +// noalias scope declaration. Makes it possible to identify that a noalias scope +// is only valid inside the body of a loop. +// +// Purpose of the different arguments: +// - arg0: id.scope: metadata representing the scope declaration. +def int_experimental_noalias_scope_decl + : DefaultAttrsIntrinsic<[], [llvm_metadata_ty], + [IntrInaccessibleMemOnly]>; // blocks LICM and some more + // Stack Protector Intrinsic - The stackprotector intrinsic writes the stack // guard to the correct place on the stack frame. def int_stackprotector : DefaultAttrsIntrinsic<[], [llvm_ptr_ty, llvm_ptrptr_ty], []>; diff --git a/llvm/lib/CodeGen/IntrinsicLowering.cpp b/llvm/lib/CodeGen/IntrinsicLowering.cpp index e37c21e765977b..55089d3b90d0ef 100644 --- a/llvm/lib/CodeGen/IntrinsicLowering.cpp +++ b/llvm/lib/CodeGen/IntrinsicLowering.cpp @@ -329,6 +329,7 @@ void IntrinsicLowering::LowerIntrinsicCall(CallInst *CI) { break; case Intrinsic::assume: + case Intrinsic::experimental_noalias_scope_decl: case Intrinsic::var_annotation: break; // Strip out these intrinsics diff --git a/llvm/lib/CodeGen/SelectionDAG/FastISel.cpp b/llvm/lib/CodeGen/SelectionDAG/FastISel.cpp index c018f164716924..62f7f3d98ba6d6 100644 --- a/llvm/lib/CodeGen/SelectionDAG/FastISel.cpp +++ b/llvm/lib/CodeGen/SelectionDAG/FastISel.cpp @@ -1252,6 +1252,8 @@ bool FastISel::selectIntrinsicCall(const IntrinsicInst *II) { case Intrinsic::sideeffect: // Neither does the assume intrinsic; it's also OK not to codegen its operand. case Intrinsic::assume: + // Neither does the llvm.experimental.noalias.scope.decl intrinsic + case Intrinsic::experimental_noalias_scope_decl: return true; case Intrinsic::dbg_declare: { const DbgDeclareInst *DI = cast(II); diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp index 20e13b361cf89c..529f3c6fd8e2e9 100644 --- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp +++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp @@ -6466,10 +6466,13 @@ void SelectionDAGBuilder::visitIntrinsicCall(const CallInst &I, // Drop the intrinsic, but forward the value setValue(&I, getValue(I.getOperand(0))); return; + case Intrinsic::assume: + case Intrinsic::experimental_noalias_scope_decl: case Intrinsic::var_annotation: case Intrinsic::sideeffect: - // Discard annotate attributes, assumptions, and artificial side-effects. + // Discard annotate attributes, noalias scope declarations, assumptions, and + // artificial side-effects. return; case Intrinsic::codeview_annotation: { diff --git a/llvm/lib/IR/IRBuilder.cpp b/llvm/lib/IR/IRBuilder.cpp index e3aa9b3cf1f6fb..7e76a6c2a055a8 100644 --- a/llvm/lib/IR/IRBuilder.cpp +++ b/llvm/lib/IR/IRBuilder.cpp @@ -452,6 +452,13 @@ IRBuilderBase::CreateAssumption(Value *Cond, return createCallHelper(FnAssume, Ops, this, "", nullptr, OpBundles); } +Instruction *IRBuilderBase::CreateNoAliasScopeDeclaration(Value *Scope) { + Module *M = BB->getModule(); + auto *FnIntrinsic = Intrinsic::getDeclaration( + M, Intrinsic::experimental_noalias_scope_decl, {}); + return createCallHelper(FnIntrinsic, {Scope}, this); +} + /// Create a call to a Masked Load intrinsic. /// \p Ptr - base pointer for the load /// \p Alignment - alignment of the source location diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp index 2e12ed61606310..bdf36e0cd3bf70 100644 --- a/llvm/lib/IR/Verifier.cpp +++ b/llvm/lib/IR/Verifier.cpp @@ -115,6 +115,11 @@ using namespace llvm; +static cl::opt VerifyNoAliasScopeDomination( + "verify-noalias-scope-decl-dom", cl::Hidden, cl::init(false), + cl::desc("Ensure that llvm.experimental.noalias.scope.decl for identical " + "scopes are not dominating")); + namespace llvm { struct VerifierSupport { @@ -313,6 +318,8 @@ class Verifier : public InstVisitor, VerifierSupport { TBAAVerifier TBAAVerifyHelper; + SmallVector NoAliasScopeDecls; + void checkAtomicMemAccessSize(Type *Ty, const Instruction *I); public: @@ -360,6 +367,8 @@ class Verifier : public InstVisitor, VerifierSupport { LandingPadResultTy = nullptr; SawFrameEscape = false; SiblingFuncletInfo.clear(); + verifyNoAliasScopeDecl(); + NoAliasScopeDecls.clear(); return !Broken; } @@ -536,6 +545,9 @@ class Verifier : public InstVisitor, VerifierSupport { /// Verify all-or-nothing property of DIFile source attribute within a CU. void verifySourceDebugInfo(const DICompileUnit &U, const DIFile &F); + + /// Verify the llvm.experimental.noalias.scope.decl declarations + void verifyNoAliasScopeDecl(); }; } // end anonymous namespace @@ -5163,6 +5175,10 @@ void Verifier::visitIntrinsicCall(Intrinsic::ID ID, CallBase &Call) { &Call); break; } + case Intrinsic::experimental_noalias_scope_decl: { + NoAliasScopeDecls.push_back(cast(&Call)); + break; + } }; } @@ -5513,6 +5529,76 @@ void Verifier::verifySourceDebugInfo(const DICompileUnit &U, const DIFile &F) { "inconsistent use of embedded source"); } +void Verifier::verifyNoAliasScopeDecl() { + if (NoAliasScopeDecls.empty()) + return; + + // only a single scope must be declared at a time. + for (auto *II : NoAliasScopeDecls) { + assert(II->getIntrinsicID() == Intrinsic::experimental_noalias_scope_decl && + "Not a llvm.experimental.noalias.scope.decl ?"); + const auto *ScopeListMV = dyn_cast( + II->getOperand(Intrinsic::NoAliasScopeDeclScopeArg)); + Assert(ScopeListMV != nullptr, + "llvm.experimental.noalias.scope.decl must have a MetadataAsValue " + "argument", + II); + + const auto *ScopeListMD = dyn_cast(ScopeListMV->getMetadata()); + Assert(ScopeListMD != nullptr, "!id.scope.list must point to an MDNode", + II); + Assert(ScopeListMD->getNumOperands() == 1, + "!id.scope.list must point to a list with a single scope", II); + } + + // Only check the domination rule when requested. Once all passes have been + // adapted this option can go away. + if (!VerifyNoAliasScopeDomination) + return; + + // Now sort the intrinsics based on the scope MDNode so that declarations of + // the same scopes are next to each other. + auto GetScope = [](IntrinsicInst *II) { + const auto *ScopeListMV = cast( + II->getOperand(Intrinsic::NoAliasScopeDeclScopeArg)); + return &cast(ScopeListMV->getMetadata())->getOperand(0); + }; + + // We are sorting on MDNode pointers here. For valid input IR this is ok. + // TODO: Sort on Metadata ID to avoid non-deterministic error messages. + auto Compare = [GetScope](IntrinsicInst *Lhs, IntrinsicInst *Rhs) { + return GetScope(Lhs) < GetScope(Rhs); + }; + + llvm::sort(NoAliasScopeDecls, Compare); + + // Go over the intrinsics and check that for the same scope, they are not + // dominating each other. + auto ItCurrent = NoAliasScopeDecls.begin(); + while (ItCurrent != NoAliasScopeDecls.end()) { + auto CurScope = GetScope(*ItCurrent); + auto ItNext = ItCurrent; + do { + ++ItNext; + } while (ItNext != NoAliasScopeDecls.end() && + GetScope(*ItNext) == CurScope); + + // [ItCurrent, ItNext[ represents the declarations for the same scope. + // Ensure they are not dominating each other + for (auto *I : llvm::make_range(ItCurrent, ItNext)) { + for (auto *J : llvm::make_range(ItCurrent, ItNext)) { + if (I != J) { + Assert(!DT.dominates(I, J), + "llvm.experimental.noalias.scope.decl dominates another one " + "with the same scope", + I); + } + } + } + ItCurrent = ItNext; + } +} + //===----------------------------------------------------------------------===// // Implement the public interfaces to this file... //===----------------------------------------------------------------------===// diff --git a/llvm/test/Verifier/noalias_scope_decl.ll b/llvm/test/Verifier/noalias_scope_decl.ll new file mode 100644 index 00000000000000..6fbb9ffa660801 --- /dev/null +++ b/llvm/test/Verifier/noalias_scope_decl.ll @@ -0,0 +1,61 @@ +; RUN: not llvm-as -disable-output --verify-noalias-scope-decl-dom < %s 2>&1 | FileCheck %s + +define void @test_single_scope01() nounwind ssp { + tail call void @llvm.experimental.noalias.scope.decl(metadata !2) + ret void +} + +define void @test_single_scope02() nounwind ssp { + tail call void @llvm.experimental.noalias.scope.decl(metadata !5) + ret void +} +; CHECK: !id.scope.list must point to a list with a single scope +; CHECK-NEXT: tail call void @llvm.experimental.noalias.scope.decl(metadata !5) + +define void @test_single_scope03() nounwind ssp { + tail call void @llvm.experimental.noalias.scope.decl(metadata !"test") + ret void +} +; CHECK-NEXT: !id.scope.list must point to an MDNode +; CHECK-NEXT: tail call void @llvm.experimental.noalias.scope.decl(metadata !"test") + +define void @test_dom01() nounwind ssp { + tail call void @llvm.experimental.noalias.scope.decl(metadata !2) + tail call void @llvm.experimental.noalias.scope.decl(metadata !8) + ret void +} + +define void @test_dom02() nounwind ssp { + tail call void @llvm.experimental.noalias.scope.decl(metadata !2) + tail call void @llvm.experimental.noalias.scope.decl(metadata !6) + ret void +} +; CHECK-NEXT: llvm.experimental.noalias.scope.decl dominates another one with the same scope +; CHECK-NEXT: tail call void @llvm.experimental.noalias.scope.decl(metadata !2) + +define void @test_dom03() nounwind ssp { + tail call void @llvm.experimental.noalias.scope.decl(metadata !2) + tail call void @llvm.experimental.noalias.scope.decl(metadata !2) + ret void +} +; CHECK-NEXT: llvm.experimental.noalias.scope.decl dominates another one with the same scope +; CHECK-NEXT: tail call void @llvm.experimental.noalias.scope.decl(metadata !2) + +; CHECK-NOT: llvm.experimental.noalias.scope.decl + +; Function Attrs: inaccessiblememonly nounwind +declare void @llvm.experimental.noalias.scope.decl(metadata) #1 + +attributes #1 = { inaccessiblememonly nounwind } +!llvm.module.flags = !{!0} +!llvm.ident = !{!1} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{!"clang"} +!2 = !{!3} +!3 = distinct !{!3, !4, !"test: pA"} +!4 = distinct !{!4, !"test"} +!5 = !{!3, !3} +!6 = !{!3} +!7 = distinct !{!7, !4, !"test: pB"} +!8 = !{!7}