diff --git a/llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.h b/llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.h --- a/llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.h +++ b/llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.h @@ -108,6 +108,10 @@ void finishNonUnitTypeDIE(DIE& D, const DICompositeType *CTy) override; + /// Add info for Wasm-global-based relocation. + void addWasmRelocBaseGlobal(DIELoc *Loc, StringRef GlobalName, + uint64_t GlobalIndex); + public: DwarfCompileUnit(unsigned UID, const DICompileUnit *Node, AsmPrinter *A, DwarfDebug *DW, DwarfFile *DWU, diff --git a/llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.cpp b/llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.cpp --- a/llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.cpp +++ b/llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.cpp @@ -278,7 +278,16 @@ : FormAndOp{dwarf::DW_FORM_data8, dwarf::DW_OP_const8u}; }; if (Global->isThreadLocal()) { - if (Asm->TM.useEmulatedTLS()) { + if (Asm->TM.getTargetTriple().isWasm()) { + // FIXME This is not guaranteed, but in practice, in static linking, + // if present, __tls_base's index is 1. This doesn't hold for dynamic + // linking, so TLS variables used in dynamic linking won't have + // correct debug info for now. See + // https://github.com/llvm/llvm-project/blob/19afbfe33156d211fa959dadeea46cd17b9c723c/lld/wasm/Driver.cpp#L786-L823 + addWasmRelocBaseGlobal(Loc, "__tls_base", 1); + addOpAddress(*Loc, Sym); + addUInt(*Loc, dwarf::DW_FORM_data1, dwarf::DW_OP_plus); + } else if (Asm->TM.useEmulatedTLS()) { // TODO: add debug info for emulated thread local mode. } else { // FIXME: Make this work with -gsplit-dwarf. @@ -301,6 +310,14 @@ DD->useGNUTLSOpcode() ? dwarf::DW_OP_GNU_push_tls_address : dwarf::DW_OP_form_tls_address); } + } else if (Asm->TM.getTargetTriple().isWasm() && + Asm->TM.getRelocationModel() == Reloc::PIC_) { + // FIXME This is not guaranteed, but in practice, if present, + // __memory_base's index is 1. See + // https://github.com/llvm/llvm-project/blob/19afbfe33156d211fa959dadeea46cd17b9c723c/lld/wasm/Driver.cpp#L786-L823 + addWasmRelocBaseGlobal(Loc, "__memory_base", 1); + addOpAddress(*Loc, Sym); + addUInt(*Loc, dwarf::DW_FORM_data1, dwarf::DW_OP_plus); } else if ((Asm->TM.getRelocationModel() == Reloc::RWPI || Asm->TM.getRelocationModel() == Reloc::ROPI_RWPI) && !Asm->getObjFileLowering() @@ -449,6 +466,39 @@ return ContextCU->updateSubprogramScopeDIEImpl(SP, SPDie); } +// Add info for Wasm-global-based relocation. +// 'GlobalIndex' is used for split dwarf, which currently relies on a few +// assumptions that are not guaranteed in a formal way but work in practice. +void DwarfCompileUnit::addWasmRelocBaseGlobal(DIELoc *Loc, StringRef GlobalName, + uint64_t GlobalIndex) { + // FIXME: duplicated from Target/WebAssembly/WebAssembly.h + // don't want to depend on target specific headers in this code? + const unsigned TI_GLOBAL_RELOC = 3; + unsigned PointerSize = Asm->getDataLayout().getPointerSize(); + auto *Sym = cast(Asm->GetExternalSymbolSymbol(GlobalName)); + // FIXME: this repeats what WebAssemblyMCInstLower:: + // GetExternalSymbolSymbol does, since if there's no code that + // refers to this symbol, we have to set it here. + Sym->setType(wasm::WASM_SYMBOL_TYPE_GLOBAL); + Sym->setGlobalType(wasm::WasmGlobalType{ + static_cast(PointerSize == 4 ? wasm::WASM_TYPE_I32 + : wasm::WASM_TYPE_I64), + true}); + addUInt(*Loc, dwarf::DW_FORM_data1, dwarf::DW_OP_WASM_location); + addSInt(*Loc, dwarf::DW_FORM_sdata, TI_GLOBAL_RELOC); + if (!isDwoUnit()) { + addLabel(*Loc, dwarf::DW_FORM_data4, Sym); + } else { + // FIXME: when writing dwo, we need to avoid relocations. Probably + // the "right" solution is to treat globals the way func and data + // symbols are (with entries in .debug_addr). + // For now we hardcode the indices in the callsites. Global indices are not + // fixed, but in practice a few are fixed; for example, __stack_pointer is + // always index 0. + addUInt(*Loc, dwarf::DW_FORM_data4, GlobalIndex); + } +} + DIE &DwarfCompileUnit::updateSubprogramScopeDIEImpl(const DISubprogram *SP, DIE *SPDie) { SmallVector BB_List; @@ -485,35 +535,14 @@ } case TargetFrameLowering::DwarfFrameBase::WasmFrameBase: { // FIXME: duplicated from Target/WebAssembly/WebAssembly.h - // don't want to depend on target specific headers in this code? const unsigned TI_GLOBAL_RELOC = 3; if (FrameBase.Location.WasmLoc.Kind == TI_GLOBAL_RELOC) { // These need to be relocatable. - assert(FrameBase.Location.WasmLoc.Index == 0); // Only SP so far. - auto SPSym = cast( - Asm->GetExternalSymbolSymbol("__stack_pointer")); - // FIXME: this repeats what WebAssemblyMCInstLower:: - // GetExternalSymbolSymbol does, since if there's no code that - // refers to this symbol, we have to set it here. - SPSym->setType(wasm::WASM_SYMBOL_TYPE_GLOBAL); - SPSym->setGlobalType(wasm::WasmGlobalType{ - uint8_t(Asm->getSubtargetInfo().getTargetTriple().getArch() == - Triple::wasm64 - ? wasm::WASM_TYPE_I64 - : wasm::WASM_TYPE_I32), - true}); DIELoc *Loc = new (DIEValueAllocator) DIELoc; - addUInt(*Loc, dwarf::DW_FORM_data1, dwarf::DW_OP_WASM_location); - addSInt(*Loc, dwarf::DW_FORM_sdata, TI_GLOBAL_RELOC); - if (!isDwoUnit()) { - addLabel(*Loc, dwarf::DW_FORM_data4, SPSym); - } else { - // FIXME: when writing dwo, we need to avoid relocations. Probably - // the "right" solution is to treat globals the way func and data - // symbols are (with entries in .debug_addr). - // For now, since we only ever use index 0, this should work as-is. - addUInt(*Loc, dwarf::DW_FORM_data4, FrameBase.Location.WasmLoc.Index); - } + assert(FrameBase.Location.WasmLoc.Index == 0); // Only SP so far. + // For now, since we only ever use index 0, this should work as-is. + addWasmRelocBaseGlobal(Loc, "__stack_pointer", + FrameBase.Location.WasmLoc.Index); addUInt(*Loc, dwarf::DW_FORM_data1, dwarf::DW_OP_stack_value); addBlock(*SPDie, dwarf::DW_AT_frame_base, Loc); } else { diff --git a/llvm/test/DebugInfo/WebAssembly/tls_pic_globals.ll b/llvm/test/DebugInfo/WebAssembly/tls_pic_globals.ll new file mode 100644 --- /dev/null +++ b/llvm/test/DebugInfo/WebAssembly/tls_pic_globals.ll @@ -0,0 +1,182 @@ +; Non-PIC tests +; RUN: sed -e 's/\[\[TLS_MODE\]\]/(localexec)/' %s | llc -filetype=obj -mattr=+bulk-memory,atomics - -o %t.localexec.o +; RUN: sed -e 's/\[\[TLS_MODE\]\]//' %s | llc -filetype=obj -mattr=+bulk-memory,atomics - -o %t.generaldynamic.o +; RUN: llvm-dwarfdump %t.localexec.o | FileCheck %s --check-prefixes=CHECK,NOPIC +; RUN: llvm-dwarfdump %t.generaldynamic.o | FileCheck %s --check-prefixes=CHECK,NOPIC +; RUN: llvm-readobj -r %t.localexec.o | FileCheck %s --check-prefixes=RELOCS-NOSPLIT +; RUN: llvm-readobj -r %t.generaldynamic.o | FileCheck %s --check-prefixes=RELOCS-NOSPLIT + +; PIC tests +; RUN: sed -e 's/\[\[TLS_MODE\]\]/(localexec)/' %s | llc -filetype=obj -mattr=+bulk-memory,atomics -relocation-model=pic - -o %t.localexec.pic.o +; RUN: sed -e 's/\[\[TLS_MODE\]\]//' %s | llc -filetype=obj -mattr=+bulk-memory,atomics -relocation-model=pic - -o %t.generaldynamic.pic.o +; RUN: llvm-dwarfdump %t.localexec.pic.o | FileCheck %s --check-prefixes=CHECK,PIC +; RUN: llvm-dwarfdump %t.generaldynamic.pic.o | FileCheck %s --check-prefixes=CHECK,PIC +; RUN: llvm-readobj -r %t.localexec.pic.o | FileCheck %s --check-prefixes=RELOCS-NOSPLIT,RELOCS-PIC-NOSPLIT +; RUN: llvm-readobj -r %t.generaldynamic.pic.o | FileCheck %s --check-prefixes=RELOCS-NOSPLIT,RELOCS-PIC-NOSPLIT + +; Non-PIC + split DWARF tests +; RUN: sed -e 's/\[\[TLS_MODE\]\]/(localexec)/' %s | llc -filetype=obj -mattr=+bulk-memory,atomics -split-dwarf-file=%t.localexec.split.dwo -split-dwarf-output=%t.localexec.split.dwo - -o %t.localexec.split.o +; RUN: sed -e 's/\[\[TLS_MODE\]\]//' %s | llc -filetype=obj -mattr=+bulk-memory,atomics -split-dwarf-file=%t.generaldynamic.split.dwo -split-dwarf-output=%t.generaldynamic.split.dwo - -o %t.generaldynamic.split.o +; RUN: llvm-dwarfdump %t.localexec.split.dwo | FileCheck %s --check-prefixes=CHECK,NOPIC +; RUN: llvm-dwarfdump %t.generaldynamic.split.dwo | FileCheck %s --check-prefixes=CHECK,NOPIC +; RUN: llvm-readobj -r %t.localexec.split.dwo | FileCheck %s --check-prefixes=RELOCS-SPLIT +; RUN: llvm-readobj -r %t.generaldynamic.split.dwo | FileCheck %s --check-prefixes=RELOCS-SPLIT + +; PIC + split DWARF tests +; RUN: sed -e 's/\[\[TLS_MODE\]\]/(localexec)/' %s | llc -filetype=obj -mattr=+bulk-memory,atomics -relocation-model=pic -split-dwarf-file=%t.localexec.pic.split.dwo -split-dwarf-output=%t.localexec.pic.split.dwo - -o %t.localexec.pic.split.o +; RUN: sed -e 's/\[\[TLS_MODE\]\]//' %s | llc -filetype=obj -mattr=+bulk-memory,atomics -relocation-model=pic -split-dwarf-file=%t.generaldynamic.pic.split.dwo -split-dwarf-output=%t.generaldynamic.pic.split.dwo - -o %t.generaldynamic.pic.split.o +; RUN: llvm-dwarfdump %t.localexec.pic.split.dwo | FileCheck %s --check-prefixes=CHECK,PIC +; RUN: llvm-dwarfdump %t.generaldynamic.pic.split.dwo | FileCheck %s --check-prefixes=CHECK,PIC +; RUN: llvm-readobj -r %t.localexec.pic.split.dwo | FileCheck %s --check-prefixes=RELOCS-SPLIT +; RUN: llvm-readobj -r %t.generaldynamic.pic.split.dwo | FileCheck %s --check-prefixes=RELOCS-SPLIT + +; This test is generated and from the following C code, after which some +; unnecessary debug info is removed. + +; int external_var0 = 111; +; int external_var1 = 222; +; static int internal_var0 = 333; +; static int internal_var1 = 444; +; _Thread_local int external_tls_var0 = 555; +; _Thread_local int external_tls_var1 = 666; +; _Thread_local int internal_tls_var0 = 777; +; _Thread_local int internal_tls_var1 = 888; +; +; void foo(int, int, int, int, int, int, int, int); +; +; void test_tls_pic_globals() { +; foo(external_var0, external_var1, internal_var0, internal_var1, +; external_tls_var0, external_tls_var1, internal_tls_var0, +; internal_tls_var1); +; } + +target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-f128:64-n32:64-S128-ni:1:10:20" +target triple = "wasm32-unknown-emscripten" + +@external_var0 = global i32 111, align 4, !dbg !0 +@external_var1 = global i32 222, align 4, !dbg !5 +@internal_var0 = internal global i32 333, align 4, !dbg !8 +@internal_var1 = internal global i32 444, align 4, !dbg !10 +@external_tls_var0 = thread_local[[TLS_MODE]] global i32 555, align 4, !dbg !12 +@external_tls_var1 = thread_local[[TLS_MODE]] global i32 666, align 4, !dbg !14 +@internal_tls_var0 = internal thread_local[[TLS_MODE]] global i32 777, align 4, !dbg !16 +@internal_tls_var1 = internal thread_local[[TLS_MODE]] global i32 888, align 4, !dbg !18 + +define void @foo(i32, i32, i32, i32, i32, i32, i32, i32) { + ret void +} + +define void @test_tls_pic_globals() !dbg !24 { +entry: + %0 = load i32, ptr @external_var0, align 4 + %1 = load i32, ptr @external_var1, align 4 + %2 = load i32, ptr @internal_var0, align 4 + %3 = load i32, ptr @internal_var1, align 4 + %4 = call align 4 ptr @llvm.threadlocal.address.p0(ptr align 4 @external_tls_var0) + %5 = load i32, ptr %4, align 4 + %6 = call align 4 ptr @llvm.threadlocal.address.p0(ptr align 4 @external_tls_var1) + %7 = load i32, ptr %6, align 4 + %8 = call align 4 ptr @llvm.threadlocal.address.p0(ptr align 4 @internal_tls_var0) + %9 = load i32, ptr %8, align 4 + %10 = call align 4 ptr @llvm.threadlocal.address.p0(ptr align 4 @internal_tls_var1) + %11 = load i32, ptr %10, align 4 + call void @foo(i32 %0, i32 %1, i32 %2, i32 %3, i32 %5, i32 %7, i32 %9, i32 %11) + ret void +} + +; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) +declare nonnull ptr @llvm.threadlocal.address.p0(ptr nonnull) #0 + +attributes #0 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } + +!llvm.dbg.cu = !{!2} +!llvm.module.flags = !{!20, !21, !22, !23} + +!0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression()) +!1 = distinct !DIGlobalVariable(name: "external_var0", scope: !2, file: !3, line: 4, type: !7, isLocal: false, isDefinition: true) +!2 = distinct !DICompileUnit(language: DW_LANG_C11, file: !3, isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, globals: !4) +!3 = !DIFile(filename: "test.c", directory: "") +!4 = !{!0, !5, !8, !10, !12, !14, !16, !18} +!5 = !DIGlobalVariableExpression(var: !6, expr: !DIExpression()) +!6 = distinct !DIGlobalVariable(name: "external_var1", scope: !2, file: !3, line: 4, type: !7, isLocal: false, isDefinition: true) +!7 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +!8 = !DIGlobalVariableExpression(var: !9, expr: !DIExpression()) +!9 = distinct !DIGlobalVariable(name: "internal_var0", scope: !2, file: !3, line: 6, type: !7, isLocal: true, isDefinition: true) +!10 = !DIGlobalVariableExpression(var: !11, expr: !DIExpression()) +!11 = distinct !DIGlobalVariable(name: "internal_var1", scope: !2, file: !3, line: 7, type: !7, isLocal: true, isDefinition: true) +!12 = !DIGlobalVariableExpression(var: !13, expr: !DIExpression()) +!13 = distinct !DIGlobalVariable(name: "external_tls_var0", scope: !2, file: !3, line: 8, type: !7, isLocal: false, isDefinition: true) +!14 = !DIGlobalVariableExpression(var: !15, expr: !DIExpression()) +!15 = distinct !DIGlobalVariable(name: "external_tls_var1", scope: !2, file: !3, line: 9, type: !7, isLocal: false, isDefinition: true) +!16 = !DIGlobalVariableExpression(var: !17, expr: !DIExpression()) +!17 = distinct !DIGlobalVariable(name: "internal_tls_var0", scope: !2, file: !3, line: 9, type: !7, isLocal: true, isDefinition: true) +!18 = !DIGlobalVariableExpression(var: !19, expr: !DIExpression()) +!19 = distinct !DIGlobalVariable(name: "internal_tls_var1", scope: !2, file: !3, line: 9, type: !7, isLocal: true, isDefinition: true) +!20 = !{i32 7, !"Dwarf Version", i32 5} +!21 = !{i32 2, !"Debug Info Version", i32 3} +!22 = !{i32 1, !"wchar_size", i32 4} +!23 = !{i32 8, !"PIC Level", i32 2} +!24 = distinct !DISubprogram(name: "main", scope: !3, file: !3, line: 16, type: !25, spFlags: DISPFlagDefinition, unit: !2) +!25 = !DISubroutineType(types: !26) +!26 = !{null} + +; Tests if TLS variables and global variables in PIC mode have a correct debug +; info location. TLS variables should have their location relative to __tls_base +; global, and global variables in PIC objects should have their location +; relative to __memory_base global. + +; CHECK: DW_TAG_variable +; CHECK-NEXT: DW_AT_name ("external_var0") +; CHECK: DW_AT_external (true) +; NOPIC: DW_AT_location (DW_OP_addrx 0x0) +; PIC: DW_AT_location (DW_OP_WASM_location 0x3 0x{{[0-9]+}}, DW_OP_addrx 0x0, DW_OP_plus) + +; CHECK: DW_TAG_variable +; CHECK-NEXT: DW_AT_name ("external_var1") +; CHECK: DW_AT_external (true) +; NOPIC: DW_AT_location (DW_OP_addrx 0x1) +; PIC: DW_AT_location (DW_OP_WASM_location 0x3 0x{{[0-9]+}}, DW_OP_addrx 0x1, DW_OP_plus) + +; CHECK: DW_TAG_variable +; CHECK-NEXT: DW_AT_name ("internal_var0") +; NOPIC: DW_AT_location (DW_OP_addrx 0x2) +; PIC: DW_AT_location (DW_OP_WASM_location 0x3 0x{{[0-9]+}}, DW_OP_addrx 0x2, DW_OP_plus) + +; CHECK: DW_TAG_variable +; CHECK-NEXT: DW_AT_name ("internal_var1") +; NOPIC: DW_AT_location (DW_OP_addrx 0x3) +; PIC: DW_AT_location (DW_OP_WASM_location 0x3 0x{{[0-9]+}}, DW_OP_addrx 0x3, DW_OP_plus) + +; CHECK: DW_TAG_variable +; CHECK-NEXT: DW_AT_name ("external_tls_var0") +; CHECK: DW_AT_external (true) +; CHECK: DW_AT_location (DW_OP_WASM_location 0x3 0x{{[0-9]+}}, DW_OP_addrx 0x4, DW_OP_plus) + +; CHECK: DW_TAG_variable +; CHECK-NEXT: DW_AT_name ("external_tls_var1") +; CHECK: DW_AT_external (true) +; CHECK: DW_AT_location (DW_OP_WASM_location 0x3 0x{{[0-9]+}}, DW_OP_addrx 0x5, DW_OP_plus) + +; CHECK: DW_TAG_variable +; CHECK-NEXT: DW_AT_name ("internal_tls_var0") +; CHECK: DW_AT_location (DW_OP_WASM_location 0x3 0x{{[0-9]+}}, DW_OP_addrx 0x6, DW_OP_plus) + +; CHECK: DW_TAG_variable +; CHECK-NEXT: DW_AT_name ("internal_tls_var1") +; CHECK: DW_AT_location (DW_OP_WASM_location 0x3 0x{{[0-9]+}}, DW_OP_addrx 0x7, DW_OP_plus) + +; In non-split DWARF, .debug_info section contains relocations referring to +; __stack_pointer, __tls_base, and __memory_base (if they use them) + +; RELOCS-NOSPLIT: Relocations [ +; RELOCS-NOSPLIT: Section (8) .debug_info { +; RELOCS-NOSPLIT-DAG: 0x{{.*}} R_WASM_GLOBAL_INDEX_I32 __tls_base +; RELOCS-NOSPLIT-DAG: 0x{{.*}} R_WASM_GLOBAL_INDEX_I32 __stack_pointer +; RELOCS-PIC-NOSPLIT-DAG: 0x{{.*}} R_WASM_GLOBAL_INDEX_I32 __memory_base +; RELOCS-NOSPLIT: } +; RELOCS-NOSPLIT: Section (9) .debug_str_offsets { + +; In split DWARF, there should be no relocations in .dwo files. + +; RELOCS-SPLIT: Relocations [ +; RELOCS-SPLIT-NEXT: ]