diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp --- a/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp +++ b/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp @@ -209,6 +209,7 @@ #include "WebAssembly.h" #include "llvm/IR/CallSite.h" +#include "llvm/IR/DebugInfoMetadata.h" #include "llvm/IR/Dominators.h" #include "llvm/IR/IRBuilder.h" #include "llvm/Support/CommandLine.h" @@ -883,6 +884,27 @@ return Changed; } +// This tries to get debug info from the instruction before which a new +// instruction will be inserted, and if there's no debug info in that +// instruction, tries to get the info instead from the previous instruction (if +// any). If none of these has debug info and a DISubprogram is provided, it +// creates a dummy debug info with the first line of the function, because IR +// verifier requires all inlinable callsites should have debug info when both a +// caller and callee have DISubprogram. If none of these conditions are met, +// returns empty info. +static DebugLoc getOrCreateDebugLoc(const Instruction *InsertBefore, + DISubprogram *SP) { + assert(InsertBefore); + if (InsertBefore->getDebugLoc()) + return InsertBefore->getDebugLoc(); + const Instruction *Prev = InsertBefore->getPrevNode(); + if (Prev && Prev->getDebugLoc()) + return Prev->getDebugLoc(); + if (SP) + return DILocation::get(SP->getContext(), SP->getLine(), 1, SP); + return DebugLoc(); +} + bool WebAssemblyLowerEmscriptenEHSjLj::runSjLjOnFunction(Function &F) { Module &M = *F.getParent(); LLVMContext &C = F.getContext(); @@ -900,7 +922,7 @@ // this instruction to a constant 4, because this value will be used in // SSAUpdater.AddAvailableValue(...) later. BasicBlock &EntryBB = F.getEntryBlock(); - DebugLoc FirstDL = EntryBB.begin()->getDebugLoc(); + DebugLoc FirstDL = getOrCreateDebugLoc(&*EntryBB.begin(), F.getSubprogram()); BinaryOperator *SetjmpTableSize = BinaryOperator::Create( Instruction::Add, IRB.getInt32(4), IRB.getInt32(0), "setjmpTableSize", &*EntryBB.getFirstInsertionPt()); @@ -1076,13 +1098,14 @@ for (BasicBlock &BB : F) { Instruction *TI = BB.getTerminator(); if (isa(TI)) { + DebugLoc DL = getOrCreateDebugLoc(TI, F.getSubprogram()); auto *Free = CallInst::CreateFree(SetjmpTable, TI); - Free->setDebugLoc(TI->getDebugLoc()); + Free->setDebugLoc(DL); // CallInst::CreateFree may create a bitcast instruction if its argument // types mismatch. We need to set the debug loc for the bitcast too. if (auto *FreeCallI = dyn_cast(Free)) { if (auto *BitCastI = dyn_cast(FreeCallI->getArgOperand(0))) - BitCastI->setDebugLoc(TI->getDebugLoc()); + BitCastI->setDebugLoc(DL); } } } diff --git a/llvm/test/CodeGen/WebAssembly/lower-em-sjlj-debuginfo.ll b/llvm/test/CodeGen/WebAssembly/lower-em-sjlj-debuginfo.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/WebAssembly/lower-em-sjlj-debuginfo.ll @@ -0,0 +1,87 @@ +; RUN: opt < %s -wasm-lower-em-ehsjlj -S | FileCheck %s + +target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" +target triple = "wasm32-unknown-unknown" + +%struct.__jmp_buf_tag = type { [6 x i32], i32, [32 x i32] } + +; Basic debug info test. All existing instructions have debug info and inserted +; 'malloc' and 'free' calls take debug info from the next instruction. +define void @setjmp_debug_info0() !dbg !3 { +; CHECK-LABEL: @setjmp_debug_info0 +entry: + %buf = alloca [1 x %struct.__jmp_buf_tag], align 16, !dbg !4 + %arraydecay = getelementptr inbounds [1 x %struct.__jmp_buf_tag], [1 x %struct.__jmp_buf_tag]* %buf, i32 0, i32 0, !dbg !5 + %call = call i32 @setjmp(%struct.__jmp_buf_tag* %arraydecay) #0, !dbg !6 + call void @foo(), !dbg !7 + ret void, !dbg !8 +; CHECK: entry: + ; CHECK-NEXT: call i8* @malloc(i32 40), !dbg ![[DL0:.*]] + ; CHECK-NEXT: bitcast {{.*}}, !dbg ![[DL0]] + ; CHECK: alloca {{.*}}, !dbg ![[DL0]] + ; CHECK: call i32* @saveSetjmp{{.*}}, !dbg ![[DL1:.*]] + ; CHECK-NEXT: call i32 @getTempRet0{{.*}}, !dbg ![[DL1]] + ; CHECK-NEXT: br {{.*}}, !dbg ![[DL2:.*]] + +; CHECK: entry.split: + ; CHECK: call {{.*}} void @__invoke_void{{.*}}, !dbg ![[DL2]] + +; CHECK: entry.split.split: + ; CHECK-NEXT: bitcast {{.*}}, !dbg ![[DL3:.*]] + ; CHECK-NEXT: call void @free{{.*}}, !dbg ![[DL3]] + +; CHECK: if.then1: + ; CHECK: call i32 @testSetjmp{{.*}}, !dbg ![[DL2]] + +; CHECK: if.end: + ; CHECK: call i32 @getTempRet0{{.*}}, !dbg ![[DL2]] + +; CHECK: if.then2: + ; CHECK: call void @emscripten_longjmp{{.*}}, !dbg ![[DL2]] + +; CHECK: if.end2: + ; CHECK: call void @setTempRet0{{.*}}, !dbg ![[DL2]] +} + +; No instruction has debug info but the current function (setjmp_debug_info2) +; and the called function (malloc / free) have DISubprograms, so the newly +; generated calls should have debug info attached. We don't have an instruction +; to take debug info from, so we create dummy debug info. +define void @setjmp_debug_info1() !dbg !9 { +; CHECK-LABEL: @setjmp_debug_info1 +entry: + %buf = alloca [1 x %struct.__jmp_buf_tag], align 16 + %arraydecay = getelementptr inbounds [1 x %struct.__jmp_buf_tag], [1 x %struct.__jmp_buf_tag]* %buf, i32 0, i32 0 + %call = call i32 @setjmp(%struct.__jmp_buf_tag* %arraydecay) #0 + call void @foo() + ret void + ; CHECK: call i8* @malloc(i32 40), !dbg ![[DL_DUMMY:.*]] + ; CHECK: call void @free{{.*}}, !dbg ![[DL_DUMMY]] +} + +; Note that these functions have DISubprograms. +declare !dbg !10 i8* @malloc(i32) +declare !dbg !11 void @free(i8*) + +declare void @foo() +; Function Attrs: returns_twice +declare i32 @setjmp(%struct.__jmp_buf_tag*) #0 + +!llvm.dbg.cu = !{!2} +!llvm.module.flags = !{!0} + +!0 = !{i32 2, !"Debug Info Version", i32 3} +!1 = !DIFile(filename: "lower-em-sjlj.c", directory: "test") +!2 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1) +!3 = distinct !DISubprogram(name: "setjmp_debug_info0", unit:!2, file: !1, line: 1) +!4 = !DILocation(line:2, scope: !3) +!5 = !DILocation(line:3, scope: !3) +!6 = !DILocation(line:4, scope: !3) +!7 = !DILocation(line:5, scope: !3) +!8 = !DILocation(line:6, scope: !3) +!9 = distinct !DISubprogram(name: "setjmp_debug_info1", unit:!2, file: !1, line: 50) +!10 = !DISubprogram(name: "malloc", file: !1, line: 10, isDefinition: false) +!11 = !DISubprogram(name: "free", file: !1, line: 20, isDefinition: false) + +; Dummy debug info generated +; CHECK: ![[DL_DUMMY]] = !DILocation(line: 50, column: 1, scope: !9) diff --git a/llvm/test/CodeGen/WebAssembly/lower-em-sjlj.ll b/llvm/test/CodeGen/WebAssembly/lower-em-sjlj.ll --- a/llvm/test/CodeGen/WebAssembly/lower-em-sjlj.ll +++ b/llvm/test/CodeGen/WebAssembly/lower-em-sjlj.ll @@ -246,43 +246,6 @@ ; CHECK: %var[[VARNO]] = phi i32 [ %var, %for.inc ] } -; Debug info test -define void @setjmp_debug_info() !dbg !3 { -; CHECK-LABEL: @setjmp_debug_info -entry: - %buf = alloca [1 x %struct.__jmp_buf_tag], align 16, !dbg !4 - %arraydecay = getelementptr inbounds [1 x %struct.__jmp_buf_tag], [1 x %struct.__jmp_buf_tag]* %buf, i32 0, i32 0, !dbg !5 - %call = call i32 @setjmp(%struct.__jmp_buf_tag* %arraydecay) #0, !dbg !6 - call void @foo(), !dbg !7 - ret void, !dbg !8 -; CHECK: entry: - ; CHECK-NEXT: call i8* @malloc(i32 40), !dbg ![[DL0:.*]] - ; CHECK-NEXT: bitcast {{.*}}, !dbg ![[DL0]] - ; CHECK: alloca {{.*}}, !dbg ![[DL0]] - ; CHECK: call i32* @saveSetjmp{{.*}}, !dbg ![[DL1:.*]] - ; CHECK-NEXT: call i32 @getTempRet0{{.*}}, !dbg ![[DL1]] - ; CHECK-NEXT: br {{.*}}, !dbg ![[DL2:.*]] - -; CHECK: entry.split: - ; CHECK: call {{.*}} void @__invoke_void{{.*}}, !dbg ![[DL2]] - -; CHECK: entry.split.split: - ; CHECK-NEXT: bitcast {{.*}}, !dbg ![[DL3:.*]] - ; CHECK-NEXT: call void @free{{.*}}, !dbg ![[DL3]] - -; CHECK: if.then1: - ; CHECK: call i32 @testSetjmp{{.*}}, !dbg ![[DL2]] - -; CHECK: if.end: - ; CHECK: call i32 @getTempRet0{{.*}}, !dbg ![[DL2]] - -; CHECK: if.then2: - ; CHECK: call void @emscripten_longjmp{{.*}}, !dbg ![[DL2]] - -; CHECK: if.end2: - ; CHECK: call void @setTempRet0{{.*}}, !dbg ![[DL2]] -} - declare void @foo() ; Function Attrs: returns_twice declare i32 @setjmp(%struct.__jmp_buf_tag*) #0