Index: include/llvm/DIBuilder.h =================================================================== --- include/llvm/DIBuilder.h +++ include/llvm/DIBuilder.h @@ -85,7 +85,7 @@ public: explicit DIBuilder(Module &M); - enum ComplexAddrKind { OpPlus=1, OpDeref }; + enum ComplexAddrKind { OpPlus=1, OpDeref, OpPiece }; /// finalize - Construct any deferred debug info descriptors. void finalize(); @@ -537,6 +537,15 @@ DIType Ty, ArrayRef Addr, unsigned ArgNo = 0); + /// createVariablePiece - Create a descriptor to describe one part + /// of aggregate variable that is fragmented across multiple Values. + /// + /// @param Variable Variable that is partially represented by this. + /// @param Offset Offset of the piece in bytes. + /// @param Size Size of the piece in bytes. + DIVariable createVariablePiece(DIVariable Variable, unsigned Offset, + unsigned Size); + /// createFunction - Create a new descriptor for the specified subprogram. /// See comments in DISubprogram for descriptions of these fields. /// @param Scope Function scope. Index: include/llvm/DebugInfo.h =================================================================== --- include/llvm/DebugInfo.h +++ include/llvm/DebugInfo.h @@ -676,6 +676,14 @@ /// information for an inlined function arguments. bool isInlinedFnArgument(const Function *CurFn); + /// isVariablePiece - Return whether this is a piece of an aggregate + /// variable. + bool isVariablePiece() const; + /// getPieceOffset - Return the offset of this piece in bytes. + uint64_t getPieceOffset() const; + /// getPieceOffset - Return the offset of this piece in bytes. + uint64_t getPieceSize() const; + void printExtendedName(raw_ostream &OS) const; }; @@ -770,6 +778,9 @@ /// cleanseInlinedVariable - Remove inlined scope from the variable. DIVariable cleanseInlinedVariable(MDNode *DV, LLVMContext &VMContext); +/// getEntireVariable - Remove OpPiece exprs from the variable. +DIVariable getEntireVariable(const MDNode *DV, LLVMContext &VMContext); + /// Construct DITypeIdentifierMap by going through retained types of each CU. DITypeIdentifierMap generateDITypeIdentifierMap(const NamedMDNode *CU_Nodes); Index: lib/CodeGen/AsmPrinter/DwarfDebug.cpp =================================================================== --- lib/CodeGen/AsmPrinter/DwarfDebug.cpp +++ lib/CodeGen/AsmPrinter/DwarfDebug.cpp @@ -1301,6 +1301,39 @@ llvm_unreachable("Unexpected 3 operand DBG_VALUE instruction!"); } +static DIVariable getDbgVar(const MachineInstr *MI) { + const MDNode *Var = MI->getOperand(MI->getNumOperands() - 1).getMetadata(); + return DIVariable(Var); +} + +static bool piecesOverlap(DIVariable P1, DIVariable P2) { + assert(P1.isVariablePiece() && P1.isVariablePiece() && "must be a piece"); + return (P1.getPieceOffset() < P2.getPieceOffset() + P2.getPieceSize() && + P2.getPieceOffset() < P1.getPieceOffset() + P1.getPieceSize()); +} + +typedef SmallVectorImpl::const_iterator HistoryIter; + +/// The range of a variable piece is truncated if its value is +/// clobbered, or by an overlapping piece. +static HistoryIter +nextOverlappingHistroyEntry(HistoryIter DbgVal, HistoryIter End) { + DIVariable DV0 = getDbgVar(*DbgVal); + if (!DV0.isVariablePiece()) + return DbgVal+1; + + for (HistoryIter I=DbgVal+1; I != End; ++I) { + if (!(*I)->isDebugValue()) + return I; + + // Clobber or overlap? + DIVariable DVI = getDbgVar(*I); + if (!DVI.isVariablePiece() || piecesOverlap(DV0, DVI)) + return I; + } + return End; +} + // Find variables for each lexical scope. void DwarfDebug::collectVariableInfo(SmallPtrSet &Processed) { @@ -1355,8 +1388,9 @@ // Handle multiple DBG_VALUE instructions describing one variable. RegVar->setDotDebugLocOffset(DotDebugLocEntries.size()); - for (SmallVectorImpl::const_iterator - HI = History.begin(), + bool ShareFLabel = false; + const MCSymbol *SLabel, *FLabel = 0; + for (HistoryIter HI = History.begin(), HE = History.end(); HI != HE; ++HI) { const MachineInstr *Begin = *HI; @@ -1368,15 +1402,17 @@ continue; // Compute the range for a register location. - const MCSymbol *FLabel = getLabelBeforeInsn(Begin); - const MCSymbol *SLabel = 0; + if (!ShareFLabel) + FLabel = getLabelBeforeInsn(Begin); + SLabel = 0; - if (HI + 1 == HE) + HistoryIter EndOfRange = nextOverlappingHistroyEntry(HI, HE); + if (EndOfRange == HE) // If Begin is the last instruction in History then its value is valid // until the end of the function. SLabel = FunctionEndSym; else { - const MachineInstr *End = HI[1]; + const MachineInstr *End = *EndOfRange; DEBUG(dbgs() << "DotDebugLoc Pair:\n" << "\t" << *Begin << "\t" << *End << "\n"); if (End->isDebugValue()) @@ -1392,6 +1428,7 @@ // The value is valid until the next DBG_VALUE or clobber. DotDebugLocEntries.push_back( getDebugLocEntry(Asm, FLabel, SLabel, Begin)); + ShareFLabel = EndOfRange != HI+1; } DotDebugLocEntries.push_back(DotDebugLocEntry()); } @@ -1592,10 +1629,13 @@ if (isDbgValueInDefinedReg(MI)) LiveUserVar[MI->getOperand(0).getReg()] = Var; - // Check the history of this variable. - SmallVectorImpl &History = DbgValues[Var]; + // Check the history of this variable. A variable may be fragmented + // into multiple pieces, but they still share one history. + DIVariable EntireVar = getEntireVariable(Var, Var->getContext()); + SmallVectorImpl &History = DbgValues[EntireVar]; + if (History.empty()) { - UserVariables.push_back(Var); + UserVariables.push_back(EntireVar); // The first mention of a function argument gets the FunctionBeginSym // label, so arguments are visible when breaking at function entry. DIVariable DV(Var); @@ -1615,6 +1655,15 @@ History.pop_back(); } + if (EntireVar.isVariablePiece()) { + DIVariable PrevVar = getDbgVar(Prev); + DIVariable CurVar = DIVariable(Var); + if (!piecesOverlap(PrevVar, CurVar)) { + History.push_back(MI); + continue; + } + } + // Terminate old register assignments that don't reach MI; MachineFunction::const_iterator PrevMBB = Prev->getParent(); if (PrevMBB != I && (!AtBlockEntry || llvm::next(PrevMBB) != I) && @@ -2649,8 +2698,45 @@ Asm->EmitDwarfRegOp(Loc, DV.isIndirect()); else { // Complex address entry. + if (DV.isVariablePiece()) { + bool Consecutive = true; + unsigned Offset = 0; + // Collect all the pieces belonging to this variable. + for (; I!=E; ++I) { + DIVariable Var(I->getVariable()); + if (Offset > 0 && + !(Var.isVariable() && + Var.isVariablePiece() && + Var.getName() == DV.getName() && + I->getBeginSym() == Entry.getBeginSym() && + I->getEndSym() == Entry.getEndSym())) { + --I; + break; + } + + if (Var.getPieceOffset() != Offset) + Consecutive = false; + + Offset += Var.getPieceSize(); + + assert(!Var.isIndirect() && "indirect address for piece"); + Asm->EmitDwarfRegOp(I->getLoc(), false); + if (Consecutive) { + Asm->OutStreamer.AddComment("DW_OP_piece"); + Asm->EmitInt8(dwarf::DW_OP_piece); + Asm->EmitInt8(Var.getPieceSize()); + } else { + Asm->OutStreamer.AddComment("DW_OP_bit_piece"); + Asm->EmitInt8(dwarf::DW_OP_bit_piece); + Asm->EmitInt8(Var.getPieceSize()*8); + Asm->EmitInt8(Var.getPieceOffset()*8); + } + } + + } else { unsigned N = DV.getNumAddrElements(); unsigned i = 0; + if (N >= 2 && DV.getAddrElement(0) == DIBuilder::OpPlus) { if (Loc.getOffset()) { i = 2; @@ -2683,6 +2769,7 @@ } else llvm_unreachable("unknown Opcode found in complex address"); } + } // FIXME: weird indentation here to make the diff more readable. } } // else ... ignore constant fp. There is not any good way to Index: lib/IR/DIBuilder.cpp =================================================================== --- lib/IR/DIBuilder.cpp +++ lib/IR/DIBuilder.cpp @@ -1042,6 +1042,20 @@ return DIVariable(MDNode::get(VMContext, Elts)); } +/// createVariablePiece - Create a descriptor to describe one part +/// of aggregate variable that is fragmented across multiple Values. +DIVariable DIBuilder::createVariablePiece(DIVariable Variable, unsigned Offset, + unsigned Size) { + SmallVector Elts; + for (unsigned i = 0; i < Variable->getNumOperands(); ++i) + Elts.push_back(Variable->getOperand(i)); + + Elts.push_back(ConstantInt::get(Type::getInt32Ty(VMContext), OpPiece)); + Elts.push_back(ConstantInt::get(Type::getInt32Ty(VMContext), Offset)); + Elts.push_back(ConstantInt::get(Type::getInt32Ty(VMContext), Size)); + return DIVariable(MDNode::get(VMContext, Elts)); +} + /// createFunction - Create a new descriptor for the specified function. /// FIXME: this is added for dragonegg. Once we update dragonegg /// to call resolve function, this will be removed. Index: lib/IR/DebugInfo.cpp =================================================================== --- lib/IR/DebugInfo.cpp +++ lib/IR/DebugInfo.cpp @@ -17,6 +17,7 @@ #include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/SmallString.h" #include "llvm/Analysis/ValueTracking.h" +#include "llvm/DIBuilder.h" #include "llvm/IR/Constants.h" #include "llvm/IR/DerivedTypes.h" #include "llvm/IR/Instructions.h" @@ -143,6 +144,21 @@ /// getInlinedAt - If this variable is inlined then return inline location. MDNode *DIVariable::getInlinedAt() const { return getNodeField(DbgNode, 7); } +bool DIVariable::isVariablePiece() const { + return hasComplexAddress() && getAddrElement(0) == DIBuilder::OpPiece; +} + +uint64_t DIVariable::getPieceOffset() const { + assert(isVariablePiece()); + return getAddrElement(1); +} +uint64_t DIVariable::getPieceSize() const { + assert(isVariablePiece()); + return getAddrElement(2); +} + + + //===----------------------------------------------------------------------===// // Predicates //===----------------------------------------------------------------------===// @@ -876,6 +892,21 @@ return DIVariable(MDNode::get(VMContext, Elts)); } + +/// getEntireVariable - Remove OpPiece exprs from the variable. +DIVariable llvm::getEntireVariable(const MDNode *MD, LLVMContext &VMContext) { + DIVariable DV(MD); + if (!DV.isVariablePiece()) + return DV; + + SmallVector Elts; + for (unsigned i = 0; i < 9; ++i) + Elts.push_back(DV->getOperand(i)); + + return DIVariable(MDNode::get(VMContext, Elts)); +} + + /// getDISubprogram - Find subprogram that is enclosing this scope. DISubprogram llvm::getDISubprogram(const MDNode *Scope) { DIDescriptor D(Scope); Index: lib/Transforms/Scalar/SROA.cpp =================================================================== --- lib/Transforms/Scalar/SROA.cpp +++ lib/Transforms/Scalar/SROA.cpp @@ -248,6 +248,9 @@ void dump() const; #endif + /// \brief Return the dbg.declare intrinsic describing the alloca. + DbgDeclareInst *getDbgDeclare() const { return DbgDeclare; } + private: template class BuilderBase; class SliceBuilder; @@ -291,6 +294,9 @@ /// want to swap this particular input for undef to simplify the use lists of /// the alloca. SmallVector DeadOperands; + + /// \brief The dbg.declare intrinsic describing the alloca. + DbgDeclareInst *DbgDeclare; }; } @@ -417,6 +423,11 @@ if (!IsOffsetKnown) return PI.setAborted(&SI); + // Make a best effort to find a dbg.declare intrinsic describing + // the alloca by peeking at the next instruction. + if (DbgDeclareInst *DDI=dyn_cast_or_null(SI.getNextNode())) + S.DbgDeclare = DDI; + uint64_t Size = DL.getTypeStoreSize(ValOp->getType()); // If this memory access can be shown to *statically* extend outside the @@ -674,7 +685,9 @@ #if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP) AI(AI), #endif - PointerEscapingInstr(0) { + PointerEscapingInstr(0), + DbgDeclare(0) +{ SliceBuilder PB(DL, AI, *this); SliceBuilder::PtrInfo PtrI = PB.visitPtr(AI); if (PtrI.isEscaped() || PtrI.isAborted()) { @@ -930,9 +943,10 @@ bool rewritePartition(AllocaInst &AI, AllocaSlices &S, AllocaSlices::iterator B, AllocaSlices::iterator E, int64_t BeginOffset, int64_t EndOffset, - ArrayRef SplitUses); - bool splitAlloca(AllocaInst &AI, AllocaSlices &S); - bool runOnAlloca(AllocaInst &AI); + ArrayRef SplitUses, + DIBuilder &DIB); + bool splitAlloca(AllocaInst &AI, AllocaSlices &S, DIBuilder &DIB); + bool runOnAlloca(AllocaInst &AI, DIBuilder &DIB); void clobberUse(Use &U); void deleteDeadInstructions(SmallPtrSet &DeletedAllocas); bool promoteAllocas(Function &F); @@ -1961,11 +1975,13 @@ // Utility IR builder, whose name prefix is setup for each visited use, and // the insertion point is set to point to the user. IRBuilderTy IRB; + DIBuilder &DIB; public: AllocaSliceRewriter(const DataLayout &DL, AllocaSlices &S, SROA &Pass, AllocaInst &OldAI, AllocaInst &NewAI, uint64_t NewBeginOffset, uint64_t NewEndOffset, + DIBuilder &DIB, bool IsVectorPromotable = false, bool IsIntegerPromotable = false) : DL(DL), S(S), Pass(Pass), OldAI(OldAI), NewAI(NewAI), @@ -1981,7 +1997,8 @@ : 0), BeginOffset(), EndOffset(), IsSplittable(), IsSplit(), OldUse(), OldPtr(), IsUsedByRewrittenSpeculatableInstructions(false), - IRB(NewAI.getContext(), ConstantFolder()) { + IRB(NewAI.getContext(), ConstantFolder()), + DIB(DIB) { if (VecTy) { assert((DL.getTypeSizeInBits(ElementTy) % 8) == 0 && "Only multiple-of-8 sized vector elements are viable"); @@ -2215,7 +2232,14 @@ V = convertValue(DL, IRB, V, NewAllocaTy); StoreInst *Store = IRB.CreateAlignedStore(V, &NewAI, NewAI.getAlignment()); Pass.DeadInsts.insert(&SI); - (void)Store; + + // Split up the debug information. + if (S.getDbgDeclare()) { + MDNode *MD = S.getDbgDeclare()->getVariable(); + DIVariable Piece = DIB.createVariablePiece(DIVariable(MD), BeginOffset, + EndOffset - BeginOffset); + DIB.insertDbgValueIntrinsic(V, 0, Piece, Store); + } DEBUG(dbgs() << " to: " << *Store << "\n"); return true; } @@ -3070,7 +3094,8 @@ bool SROA::rewritePartition(AllocaInst &AI, AllocaSlices &S, AllocaSlices::iterator B, AllocaSlices::iterator E, int64_t BeginOffset, int64_t EndOffset, - ArrayRef SplitUses) { + ArrayRef SplitUses, + DIBuilder &DIB) { assert(BeginOffset < EndOffset); uint64_t SliceSize = EndOffset - BeginOffset; @@ -3142,7 +3167,7 @@ unsigned NumUses = 0; AllocaSliceRewriter Rewriter(*DL, S, *this, AI, *NewAI, BeginOffset, - EndOffset, IsVectorPromotable, + EndOffset, DIB, IsVectorPromotable, IsIntegerPromotable); bool Promotable = true; for (ArrayRef::const_iterator SUI = SplitUses.begin(), @@ -3235,7 +3260,7 @@ /// \brief Walks the slices of an alloca and form partitions based on them, /// rewriting each of their uses. -bool SROA::splitAlloca(AllocaInst &AI, AllocaSlices &S) { +bool SROA::splitAlloca(AllocaInst &AI, AllocaSlices &S, DIBuilder &DIB) { if (S.begin() == S.end()) return false; @@ -3284,8 +3309,8 @@ // we'll have to rewrite uses and erase old split uses. if (BeginOffset < MaxEndOffset) { // Rewrite a sequence of overlapping slices. - Changed |= - rewritePartition(AI, S, SI, SJ, BeginOffset, MaxEndOffset, SplitUses); + Changed |= rewritePartition(AI, S, SI, SJ, BeginOffset, MaxEndOffset, + SplitUses, DIB); ++NumPartitions; removeFinishedSplitUses(SplitUses, MaxSplitUseEndOffset, MaxEndOffset); @@ -3325,7 +3350,7 @@ SJ == SE ? MaxSplitUseEndOffset : SJ->beginOffset(); Changed |= rewritePartition(AI, S, SJ, SJ, MaxEndOffset, PostSplitEndOffset, - SplitUses); + SplitUses, DIB); ++NumPartitions; if (SJ == SE) @@ -3365,7 +3390,7 @@ /// This analyzes the alloca to ensure we can reason about it, builds /// the slices of the alloca, and then hands it off to be split and /// rewritten as needed. -bool SROA::runOnAlloca(AllocaInst &AI) { +bool SROA::runOnAlloca(AllocaInst &AI, DIBuilder &DIB) { DEBUG(dbgs() << "SROA alloca: " << AI << "\n"); ++NumAllocasAnalyzed; @@ -3420,7 +3445,7 @@ if (S.begin() == S.end()) return Changed; - Changed |= splitAlloca(AI, S); + Changed |= splitAlloca(AI, S, DIB); DEBUG(dbgs() << " Speculating PHIs\n"); while (!SpeculatablePHIs.empty()) @@ -3594,9 +3619,11 @@ // the list of promotable allocas. SmallPtrSet DeletedAllocas; + DIBuilder DIB(*F.getParent()); + do { while (!Worklist.empty()) { - Changed |= runOnAlloca(*Worklist.pop_back_val()); + Changed |= runOnAlloca(*Worklist.pop_back_val(), DIB); deleteDeadInstructions(DeletedAllocas); // Remove the deleted allocas from various lists so that we don't try to Index: test/DebugInfo/pieces.ll =================================================================== --- /dev/null +++ test/DebugInfo/pieces.ll @@ -0,0 +1,81 @@ +; RUN: opt -sroa -verify -S -asm-verbose -o %t.ll %s +; RUN: cat %t.ll | FileCheck --check-prefix=CHECK-OPT %s +; RUN: llc %t.ll -filetype=obj -o %t.o +; RUN: llvm-dwarfdump -debug-dump=loc %t.o | FileCheck --check-prefix=CHECK-DWARF %s +; +;typedef struct { long int a; int b;} S; +; +;int foo(S s) { +; return s.b; +;} +; +target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-apple-macosx10.9.0" + +%struct.S = type { i64, i32 } + +; Function Attrs: nounwind readnone ssp uwtable +define i32 @foo(i64 %s.coerce0, i32 %s.coerce1) #0 { +entry: + %s = alloca %struct.S, align 8 + %0 = bitcast %struct.S* %s to { i64, i32 }* + %1 = getelementptr { i64, i32 }* %0, i32 0, i32 0 + store i64 %s.coerce0, i64* %1 + %2 = getelementptr { i64, i32 }* %0, i32 0, i32 1 + store i32 %s.coerce1, i32* %2 + call void @llvm.dbg.declare(metadata !{%struct.S* %s}, metadata !16), !dbg !23 + %b = getelementptr inbounds %struct.S* %s, i32 0, i32 1, !dbg !23 + %3 = load i32* %b, align 4, !dbg !21, !tbaa !24 + ret i32 %3, !dbg !24 +} + +; CHECK-OPT: define i32 @foo(i64 %s.coerce0, i32 %s.coerce1) #0 { +; CHECK-OPT: call void @llvm.dbg.value(metadata !{i64 %s.coerce0}, i64 0, metadata ![[PIECE1:.*]]) +; CHECK-OPT: call void @llvm.dbg.value(metadata !{i32 %s.coerce1}, i64 0, metadata ![[PIECE2:.*]]) +; CHECK-OPT: ret i32 %s.coerce1 +; CHECK-OPT: ![[PIECE1]] = {{.*}} i32 3, i32 0, i32 8} ; [ DW_TAG_arg_variable ] [s] [line 3] +; CHECK-OPT: ![[PIECE2]] = {{.*}} i32 3, i32 8, i32 4} ; [ DW_TAG_arg_variable ] [s] [line 3] + +; rdi, piece 0x00000008 +; CHECK-DWARF: Location description: 55 93 08 +; rsi, bit-piece 32 64 +; CHECK-DWARF: Location description: 54 9d 20 40 + +; Function Attrs: nounwind readnone +declare void @llvm.dbg.declare(metadata, metadata) #1 + +; Function Attrs: nounwind readnone +declare void @llvm.dbg.value(metadata, i64, metadata) #1 + +attributes #0 = { nounwind ssp uwtable "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" } +attributes #1 = { nounwind readnone } + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!17, !18} +!llvm.ident = !{!19} + +!0 = metadata !{i32 786449, metadata !1, i32 12, metadata !"clang version 3.5 ", i1 true, metadata !"", i32 0, metadata !2, metadata !2, metadata !3, metadata !2, metadata !2, metadata !""} ; [ DW_TAG_compile_unit ] [pieces.c] [DW_LANG_C99] +!1 = metadata !{metadata !"pieces.c", metadata !""} +!2 = metadata !{i32 0} +!3 = metadata !{metadata !4} +!4 = metadata !{i32 786478, metadata !1, metadata !5, metadata !"foo", metadata !"foo", metadata !"", i32 3, metadata !6, i1 false, i1 true, i32 0, i32 0, null, i32 256, i1 true, i32 (i64, i32)* @foo, null, null, metadata !15, i32 3} ; [ DW_TAG_subprogram ] [line 3] [def] [foo] +!5 = metadata !{i32 786473, metadata !1} ; [ DW_TAG_file_type ] [pieces.c] +!6 = metadata !{i32 786453, i32 0, null, metadata !"", i32 0, i64 0, i64 0, i64 0, i32 0, null, metadata !7, i32 0, null, null, null} ; [ DW_TAG_subroutine_type ] [line 0, size 0, align 0, offset 0] [from ] +!7 = metadata !{metadata !8, metadata !9} +!8 = metadata !{i32 786468, null, null, metadata !"int", i32 0, i64 32, i64 32, i64 0, i32 0, i32 5} ; [ DW_TAG_base_type ] [int] [line 0, size 32, align 32, offset 0, enc DW_ATE_signed] +!9 = metadata !{i32 786454, metadata !1, null, metadata !"S", i32 1, i64 0, i64 0, i64 0, i32 0, metadata !10} ; [ DW_TAG_typedef ] [S] [line 1, size 0, align 0, offset 0] [from ] +!10 = metadata !{i32 786451, metadata !1, null, metadata !"", i32 1, i64 128, i64 64, i32 0, i32 0, null, metadata !11, i32 0, null, null, null} ; [ DW_TAG_structure_type ] [line 1, size 128, align 64, offset 0] [def] [from ] +!11 = metadata !{metadata !12, metadata !14} +!12 = metadata !{i32 786445, metadata !1, metadata !10, metadata !"a", i32 1, i64 64, i64 64, i64 0, i32 0, metadata !13} ; [ DW_TAG_member ] [a] [line 1, size 64, align 64, offset 0] [from long int] +!13 = metadata !{i32 786468, null, null, metadata !"long int", i32 0, i64 64, i64 64, i64 0, i32 0, i32 5} ; [ DW_TAG_base_type ] [long int] [line 0, size 64, align 64, offset 0, enc DW_ATE_signed] +!14 = metadata !{i32 786445, metadata !1, metadata !10, metadata !"b", i32 1, i64 32, i64 32, i64 64, i32 0, metadata !8} ; [ DW_TAG_member ] [b] [line 1, size 32, align 32, offset 64] [from int] +!15 = metadata !{metadata !16} +!16 = metadata !{i32 786689, metadata !4, metadata !"s", metadata !5, i32 16777219, metadata !9, i32 0, i32 0} ; [ DW_TAG_arg_variable ] [s] [line 3] +!17 = metadata !{i32 2, metadata !"Dwarf Version", i32 2} +!18 = metadata !{i32 1, metadata !"Debug Info Version", i32 1} +!19 = metadata !{metadata !"clang version 3.5 "} +!20 = metadata !{i32 786689, metadata !4, metadata !"s", metadata !5, i32 16777219, metadata !9, i32 0, i32 0, i32 3, i32 0, i32 8} ; [ DW_TAG_arg_variable ] [s] [line 3] +!21 = metadata !{i32 786689, metadata !4, metadata !"s", metadata !5, i32 16777219, metadata !9, i32 0, i32 0, i32 3, i32 8, i32 4} ; [ DW_TAG_arg_variable ] [s] [line 3] +!22 = metadata !{%struct.S* undef} +!23 = metadata !{i32 3, i32 0, metadata !4, null} +!24 = metadata !{i32 4, i32 0, metadata !4, null}