Index: docs/SourceLevelDebugging.rst =================================================================== --- docs/SourceLevelDebugging.rst +++ docs/SourceLevelDebugging.rst @@ -588,6 +588,12 @@ Name the source variable name. Context and line indicate where the variable was defined. Type descriptor defines the declared type of the variable. +The ``OpPiece`` operator is used for (typically larger aggregate) +variables that are fragmented across several addresses. It takes two +i32 arguments, an offset and a size to describe which piece of the +variable is at this address. + + .. _format_common_intrinsics: Debugger intrinsic functions Index: include/llvm/IR/DIBuilder.h =================================================================== --- include/llvm/IR/DIBuilder.h +++ include/llvm/IR/DIBuilder.h @@ -86,7 +86,7 @@ public: explicit DIBuilder(Module &M); - enum ComplexAddrKind { OpPlus=1, OpDeref }; + enum ComplexAddrKind { OpPlus=1, OpDeref, OpPiece }; enum DebugEmissionKind { FullDebug=1, LineTablesOnly }; /// finalize - Construct any deferred debug info descriptors. @@ -541,6 +541,16 @@ DITypeRef 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 OffsetInBytes Offset of the piece in bytes. + /// @param SizeInBytes Size of the piece in bytes. + DIVariable createVariablePiece(DIVariable Variable, + unsigned OffsetInBytes, + unsigned SizeInBytes); + /// 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/IR/DebugInfo.h =================================================================== --- include/llvm/IR/DebugInfo.h +++ include/llvm/IR/DebugInfo.h @@ -704,6 +704,17 @@ /// 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; + /// getPieceSize - Return the size of this piece in bytes. + uint64_t getPieceSize() const; + + /// Return the size reported by the variable's type. + unsigned getSizeInBits(const DITypeIdentifierMap &Map); + void printExtendedName(raw_ostream &OS) const; }; @@ -821,6 +832,9 @@ /// cleanseInlinedVariable - Remove inlined scope from the variable. DIVariable cleanseInlinedVariable(MDNode *DV, LLVMContext &VMContext); +/// getEntireVariable - Remove OpPiece exprs from the variable. +DIVariable getEntireVariable(DIVariable DV); + /// Construct DITypeIdentifierMap by going through retained types of each CU. DITypeIdentifierMap generateDITypeIdentifierMap(const NamedMDNode *CU_Nodes); Index: lib/CodeGen/AsmPrinter/AsmPrinter.cpp =================================================================== --- lib/CodeGen/AsmPrinter/AsmPrinter.cpp +++ lib/CodeGen/AsmPrinter/AsmPrinter.cpp @@ -641,7 +641,11 @@ if (!Name.empty()) OS << Name << ":"; } - OS << V.getName() << " <- "; + OS << V.getName(); + if (V.isVariablePiece()) + OS << " [piece offset=" << V.getPieceOffset() + << " size="<getOperand(0).isReg() && MI->getOperand(1).isImm(); Index: lib/CodeGen/AsmPrinter/DwarfDebug.h =================================================================== --- lib/CodeGen/AsmPrinter/DwarfDebug.h +++ lib/CodeGen/AsmPrinter/DwarfDebug.h @@ -90,7 +90,7 @@ StringRef getName() const { return Var.getName(); } DbgVariable *getAbstractVariable() const { return AbsVar; } const MachineInstr *getMInsn() const { return MInsn; } - void setMInsn(const MachineInstr *M) { MInsn = M; } + void setMInsn(const MachineInstr *M); int getFrameIndex() const { return FrameIndex; } void setFrameIndex(int FI) { FrameIndex = FI; } // Translate tag to proper Dwarf tag. Index: lib/CodeGen/AsmPrinter/DwarfDebug.cpp =================================================================== --- lib/CodeGen/AsmPrinter/DwarfDebug.cpp +++ lib/CodeGen/AsmPrinter/DwarfDebug.cpp @@ -107,6 +107,15 @@ //===----------------------------------------------------------------------===// +void DbgVariable::setMInsn(const MachineInstr *M) { + MInsn = M; + // Update Var from MInsn. The DBG_VALUE may describe a specific + // piece of the variable in its complex expression. + assert(Var.getName() == MInsn->getDebugVariable().getName() + && "variable and variable in DBG_VALUE do not match"); + Var = MInsn->getDebugVariable(); +} + /// resolve - Look in the DwarfDebug map for the MDNode that /// corresponds to the reference. template T DbgVariable::resolve(DIRef Ref) const { @@ -1142,8 +1151,12 @@ // register-indirect address. if (!MI->getOperand(1).isImm()) MLoc.set(MI->getOperand(0).getReg()); - else + else { + // Based on the assumption that a pointer always fits into a + // register, an indirect piece should not occur. + assert(!DIVariable(Var).isVariablePiece() && "indirect piece"); MLoc.set(MI->getOperand(0).getReg(), MI->getOperand(1).getImm()); + } return DebugLocEntry::Value(Var, MLoc); } if (MI->getOperand(0).isImm()) @@ -1156,6 +1169,42 @@ llvm_unreachable("Unexpected 3 operand DBG_VALUE instruction!"); } +/// Determine whether two variable pieces overlap. +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::iterator HistoryIter; + +/// The range of a variable piece is truncated if its value is +/// clobbered, or by an overlapping piece. Return an iterator to the +/// history entry that ends the range of DbgVal. +static HistoryIter +getEndOfRange(HistoryIter DbgVal, HistoryIter End) { + DIVariable DV0 = (*DbgVal)->getDebugVariable(); + if (!DV0.isVariablePiece()) + return DbgVal+1; + + for (HistoryIter I=DbgVal+1; I != End; ++I) { + // Ignore nullptr entries. + if (!*I) + continue; + + // Clobber. + if (!(*I)->isDebugValue()) + return I; + + // Non-piece or overlapping piece? + DIVariable DVI = (*I)->getDebugVariable(); + if (!DVI.isVariablePiece() || piecesOverlap(DV0, DVI)) + return I; + } + return End; +} + + // Find variables for each lexical scope. void DwarfDebug::collectVariableInfo(SmallPtrSet &Processed) { @@ -1189,7 +1238,7 @@ if (!Scope) continue; - Processed.insert(DV); + Processed.insert(getEntireVariable(DV)); assert(MInsn->isDebugValue() && "History must begin with debug value"); DbgVariable *AbsVar = findAbstractVariable(DV, MInsn->getDebugLoc()); DbgVariable *RegVar = new DbgVariable(DV, AbsVar, this); @@ -1213,11 +1262,18 @@ LocList.Label = Asm->GetTempSymbol("debug_loc", DotDebugLocEntries.size() - 1); SmallVector &DebugLoc = LocList.List; - for (SmallVectorImpl::const_iterator - HI = History.begin(), - HE = History.end(); - HI != HE; ++HI) { + + // Build the location list for all DBG_VALUEs in the function that + // describe the same variable. + bool IsNonOverlappingPiece = false; + for (HistoryIter HI = History.begin(), HE = History.end(); HI != HE; ++HI) { const MachineInstr *Begin = *HI; + // Skip over end markers that we already processed. + if (!Begin) + continue; + + DEBUG(dbgs()<< "Range for " << *Begin->getDebugVariable() + << "beginning at " << *Begin <<"\n"); assert(Begin->isDebugValue() && "Invalid History entry"); // Check if DBG_VALUE is truncating a range. @@ -1227,14 +1283,15 @@ // Compute the range for a register location. const MCSymbol *FLabel = getLabelBeforeInsn(Begin); - const MCSymbol *SLabel = nullptr; - - if (HI + 1 == HE) + const MCSymbol *SLabel; + // Skip over other non-overlapping pieces to find the end of the range. + HistoryIter EndOfRange = getEndOfRange(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()) @@ -1243,14 +1300,30 @@ // End is a normal instruction clobbering the range. SLabel = getLabelAfterInsn(End); assert(SLabel && "Forgot label after clobber instruction"); - ++HI; + // Mark the end as processed. + *EndOfRange = 0; } } - // The value is valid until the next DBG_VALUE or clobber. - DebugLocEntry Loc(FLabel, SLabel, getDebugLocValue(Begin), TheCU); - if (DebugLoc.empty() || !DebugLoc.back().Merge(Loc)) - DebugLoc.push_back(std::move(Loc)); + if (IsNonOverlappingPiece && + FLabel == DebugLoc.back().getBeginSym() && + SLabel == DebugLoc.back().getEndSym() + ) { + // If this is a piece of the same variable with a + // non-overlapping range, add it to the previous Loc. + DEBUG(DIVariable Prev(DebugLoc.back().getValues().back().getVariable()); + DIVariable Cur(Begin->getDebugVariable()); + assert((Prev.getName() == Cur.getName()) && + "pieces do not belong to the same variable"); + ); + DebugLoc.back().addValue(getDebugLocValue(Begin)); + } else { + // The value is valid until the next DBG_VALUE or clobber. + DebugLocEntry Loc(FLabel, SLabel, getDebugLocValue(Begin), TheCU); + if (DebugLoc.empty() || !DebugLoc.back().Merge(Loc)) + DebugLoc.push_back(std::move(Loc)); + } + IsNonOverlappingPiece = EndOfRange != HI+1; } } @@ -1427,22 +1500,40 @@ assert(MI->getNumOperands() > 1 && "Invalid machine instruction!"); // Keep track of user variables. - const MDNode *Var = MI->getDebugVariable(); + DIVariable Var = MI->getDebugVariable(); // Variable is in a register, we need to check for clobbers. if (isDbgValueInDefinedReg(MI)) LiveUserVar[MI->getOperand(0).getReg()] = Var; - // Check the history of this variable. - SmallVectorImpl &History = DbgValues[Var]; - if (History.empty()) { - UserVariables.push_back(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); + SmallVectorImpl &History = DbgValues[EntireVar]; + + bool AtFnBegin = false; + if (History.empty()) + AtFnBegin = true; + else { + const MachineInstr *Prev = History.back(); + if (Prev->isDebugValue() && + Var.isVariablePiece() && + LabelsBeforeInsn[Prev] == FunctionBeginSym && + !piecesOverlap(Prev->getDebugVariable(), Var)) + AtFnBegin = true; + } + + if (AtFnBegin) { // The first mention of a function argument gets the FunctionBeginSym // label, so arguments are visible when breaking at function entry. DIVariable DV(Var); if (DV.isVariable() && DV.getTag() == dwarf::DW_TAG_arg_variable && getDISubprogram(DV.getContext()).describes(MF->getFunction())) LabelsBeforeInsn[MI] = FunctionBeginSym; + } + + if (History.empty()) { + UserVariables.push_back(EntireVar); } else { // We have seen this variable before. Try to coalesce DBG_VALUEs. const MachineInstr *Prev = History.back(); @@ -1501,7 +1592,8 @@ LiveUserVar[Reg] = nullptr; // Was MD last defined by a DBG_VALUE referring to Reg? - DbgValueHistoryMap::iterator HistI = DbgValues.find(Var); + DIVariable EntireVar = getEntireVariable(DIVariable(Var)); + DbgValueHistoryMap::iterator HistI = DbgValues.find(EntireVar); if (HistI == DbgValues.end()) continue; SmallVectorImpl &History = HistI->second; @@ -2048,12 +2140,61 @@ Holder.emitStrings(Asm->getObjFileLowering().getDwarfStrSection()); } +/// Emits an optimal (=sorted) sequence of DW_OP_pieces. +void emitDWARFPieces(AsmPrinter *Asm, ByteStreamer &Streamer, + const DITypeIdentifierMap &Map, + ArrayRef Values) { + typedef DebugLocEntry::Value Piece; + SmallVector Pieces(Values.begin(), Values.end()); + assert(std::all_of(Pieces.begin(), Pieces.end(), [](Piece &P) { + return DIVariable(P.getVariable()).isVariablePiece(); + })); + + // Sort the pieces so they can be emitted using DW_OP_piece. + std::sort(Pieces.begin(), Pieces.end(), [](const Piece &A, const Piece &B) { + DIVariable VarA(A.getVariable()); + DIVariable VarB(B.getVariable()); + return VarA.getPieceOffset() < VarB.getPieceOffset(); + }); + + bool Consecutive = true; + unsigned Offset = 0; + for (auto Piece : Pieces) { + DIVariable Var(Piece.getVariable()); + unsigned PieceOffset = Var.getPieceOffset(); + unsigned PieceSize = Var.getPieceSize(); + if (PieceOffset != Offset) + Consecutive = false; + + Offset += PieceSize; + + // Assuming 8 bits per byte. + assert(!Var.isIndirect() && "indirect address for piece"); +#ifndef NDEBUG + unsigned VarSize = Var.getSizeInBits(Map); + assert(PieceSize+PieceOffset <= VarSize/8 + && "piece is larger than or outside of variable"); + assert(PieceSize*8 != VarSize + && "piece covers entire variable"); +#endif + if (Piece.isLocation()) + Asm->EmitDwarfRegOpPiece(Streamer, + Piece.getLoc(), + PieceSize*8, + Consecutive ? 0 : PieceOffset*8); + } +}; + + void DwarfDebug::emitDebugLocEntry(ByteStreamer &Streamer, const DebugLocEntry &Entry) { - assert(Entry.getValues().size() == 1 && - "multi-value entries are not supported yet."); const DebugLocEntry::Value Value = Entry.getValues()[0]; DIVariable DV(Value.getVariable()); + if (DV.isVariablePiece()) + // Emit all pieces that belong to the same variable and range. + return emitDWARFPieces(Asm, Streamer, TypeIdentifierMap, Entry.getValues()); + + // Regular entry. if (Value.isInt()) { DIBasicType BTy(resolve(DV.getType())); if (BTy.Verify() && (BTy.getEncoding() == dwarf::DW_ATE_signed || Index: lib/IR/DIBuilder.cpp =================================================================== --- lib/IR/DIBuilder.cpp +++ lib/IR/DIBuilder.cpp @@ -1047,6 +1047,21 @@ Elts.push_back(Constant::getNullValue(Type::getInt32Ty(VMContext))); Elts.append(Addr.begin(), Addr.end()); +/// createVariablePiece - Create a descriptor to describe one part +/// of aggregate variable that is fragmented across multiple Values. +DIVariable DIBuilder::createVariablePiece(DIVariable Variable, + unsigned OffsetInBytes, + unsigned SizeInBytes) { + assert(SizeInBytes > 0 && "zero-size piece"); + SmallVector Elts; + for (unsigned i = 0; i < Variable->getNumOperands(); ++i) + Elts.push_back(Variable->getOperand(i)); + + SmallVector Expr; + Expr.push_back(ConstantInt::get(Type::getInt32Ty(VMContext), OpPiece)); + Expr.push_back(ConstantInt::get(Type::getInt32Ty(VMContext), OffsetInBytes)); + Expr.push_back(ConstantInt::get(Type::getInt32Ty(VMContext), SizeInBytes)); + Elts.push_back(MDNode::get(VMContext, Expr)); return DIVariable(MDNode::get(VMContext, Elts)); } Index: lib/IR/DebugInfo.cpp =================================================================== --- lib/IR/DebugInfo.cpp +++ lib/IR/DebugInfo.cpp @@ -12,13 +12,14 @@ // //===----------------------------------------------------------------------===// -#include "llvm/IR/DebugInfo.h" #include "LLVMContextImpl.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/SmallString.h" #include "llvm/Analysis/ValueTracking.h" #include "llvm/IR/Constants.h" +#include "llvm/IR/DIBuilder.h" +#include "llvm/IR/DebugInfo.h" #include "llvm/IR/DerivedTypes.h" #include "llvm/IR/Instructions.h" #include "llvm/IR/IntrinsicInst.h" @@ -145,6 +146,36 @@ /// 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); +} + +/// Return the size reported by the variable's type. +unsigned DIVariable::getSizeInBits(const DITypeIdentifierMap &Map) { + DIType Ty = getType().resolve(Map); + // Follow derived types until we reach a type that + // reports back a size. + while (Ty.isDerivedType() && !Ty.isCompositeType()) { + DIDerivedType DT(&*Ty); + Ty = DT.getTypeDerivedFrom().resolve(Map); + } + assert(Ty.getSizeInBits() && "type with size 0"); + return Ty.getSizeInBits(); +} + + + + //===----------------------------------------------------------------------===// // Predicates //===----------------------------------------------------------------------===// @@ -910,6 +941,20 @@ return DIVariable(MDNode::get(VMContext, Elts)); } + +/// getEntireVariable - Remove OpPiece exprs from the variable. +DIVariable llvm::getEntireVariable(DIVariable DV) { + if (!DV.isVariablePiece()) + return DV; + + SmallVector Elts; + for (unsigned i = 0; i < 8; ++i) + Elts.push_back(DV->getOperand(i)); + + return DIVariable(MDNode::get(DV->getContext(), Elts)); +} + + /// getDISubprogram - Find subprogram that is enclosing this scope. DISubprogram llvm::getDISubprogram(const MDNode *Scope) { DIDescriptor D(Scope); @@ -1393,6 +1438,10 @@ OS << " [" << Res << ']'; OS << " [line " << getLineNumber() << ']'; + + if (isVariablePiece()) + OS << " [piece, size " << getPieceSize() + << ", offset " << getPieceOffset() << ']'; } void DIObjCProperty::printInternal(raw_ostream &OS) const { Index: test/DebugInfo/X86/pieces-1.ll =================================================================== --- /dev/null +++ test/DebugInfo/X86/pieces-1.ll @@ -0,0 +1,83 @@ +; RUN: llc %s -filetype=obj -o %t.o +; RUN: llvm-dwarfdump -debug-dump=loc %t.o | FileCheck --check-prefix=CHECK-DWARF %s +; +; rdar://problem/15928306 +; +; Test that we can emit debug info for aggregate values that are split +; up across multiple registers by SROA. +; +; // Compile with -O1. +; typedef struct { long int a; int b;} S; +; +; int foo(S s) { +; return s.b; +; } +; +; +; CHECK-DWARF: .debug_loc contents: +; +; 0x0000000000000000 - 0x0000000000000008: rdi, piece 0x00000008 +; CHECK-DWARF: Beginning address offset: 0x0000000000000000 +; CHECK-DWARF: Ending address offset: [[END:.*]] +; CHECK-DWARF: Location description: 55 93 08 +; +; 0x0000000000000000 - 0x0000000000000006: rsi, bit-piece 32 64 +; CHECK-DWARF: Beginning address offset: 0x0000000000000000 +; CHECK-DWARF: Ending address offset: [[LTMP3:.*]] +; CHECK-DWARF: Location description: 54 9d 20 40 +; +; 0x0000000000000006 - 0x0000000000000008: rax, bit-piece 32 64 +; CHECK-DWARF: Beginning address offset: [[LTMP3:.*]] +; CHECK-DWARF: Ending address offset: [[END]] +; CHECK-DWARF: Location description: 50 9d 20 40 +; +target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-apple-macosx10.9.0" + +; Function Attrs: nounwind ssp uwtable +define i32 @foo(i64 %s.coerce0, i32 %s.coerce1) #0 { +entry: + call void @llvm.dbg.value(metadata !{i64 %s.coerce0}, i64 0, metadata !20), !dbg !21 + call void @llvm.dbg.value(metadata !{i32 %s.coerce1}, i64 0, metadata !22), !dbg !21 + ret i32 %s.coerce1, !dbg !23 +} + +; 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 !""} +!1 = metadata !{metadata !"pieces.c", metadata !""} +!2 = metadata !{} +!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 4} +!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, metadata !24} ; [ DW_TAG_arg_variable ] [s] [line 3] [piece, size 8, offset 0] +!21 = metadata !{i32 3, i32 0, metadata !4, null} +!22 = metadata !{i32 786689, metadata !4, metadata !"s", metadata !5, i32 16777219, metadata !9, i32 0, i32 0, metadata !25} ; [ DW_TAG_arg_variable ] [s] [line 3] [piece, size 4, offset 8] +!23 = metadata !{i32 4, i32 0, metadata !4, null} +!24 = metadata !{i64 3, i64 0, i64 8} +!25 = metadata !{i64 3, i64 8, i64 4} \ No newline at end of file