diff --git a/lld/test/CMakeLists.txt b/lld/test/CMakeLists.txt index 1bd3ad7e1765b..d39dd75c0efa8 100644 --- a/lld/test/CMakeLists.txt +++ b/lld/test/CMakeLists.txt @@ -51,6 +51,7 @@ if (NOT LLD_BUILT_STANDALONE) llvm-bcanalyzer llvm-cgdata llvm-config + llvm-cov llvm-cvtres llvm-dis llvm-dlltool diff --git a/lld/test/lit.cfg.py b/lld/test/lit.cfg.py index 39c3d0aa36bfb..ed1fa6613ab76 100644 --- a/lld/test/lit.cfg.py +++ b/lld/test/lit.cfg.py @@ -53,6 +53,7 @@ "llc", "llvm-as", "llvm-cgdata", + "llvm-cov", "llvm-mc", "llvm-nm", "llvm-objdump", diff --git a/lld/test/wasm/Inputs/malformed-prf1.ll b/lld/test/wasm/Inputs/malformed-prf1.ll new file mode 100644 index 0000000000000..9151f922dec23 --- /dev/null +++ b/lld/test/wasm/Inputs/malformed-prf1.ll @@ -0,0 +1,48 @@ +; ModuleID = 'malformed-prf1.c' +source_filename = "malformed-prf1.c" +target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-i128:128-n32:64-S128-ni:1:10:20" +target triple = "wasm32-unknown-unknown" + +$__llvm_profile_runtime_user = comdat any + +$__covrec_5CF8C24CDB18BDACu = comdat any + +@__covrec_5CF8C24CDB18BDACu = linkonce_odr hidden constant <{ i64, i32, i64, i64, [9 x i8] }> <{ i64 6699318081062747564, i32 9, i64 0, i64 -8255375002284483420, [9 x i8] c"\01\01\00\01\01\03\0C\02\02" }>, section "__llvm_covfun", comdat, align 8 +@__llvm_coverage_mapping = private constant { { i32, i32, i32, i32 }, [76 x i8] } { { i32, i32, i32, i32 } { i32 0, i32 76, i32 0, i32 6 }, [76 x i8] c"\02EIx\DA\0D\CA\D1\09\800\0C\05\C0\15\\D\1F\E2\14\8E\11\DA\94\22MS\92T\D7\B7\9F\07w\A1\AA0f%\F3\0E\E3\A1h\ED\95}\98>\9Cb!#\D8\03\1F\B9\E0\EEc\86oB\AD\A8\09\E7\D5\CAy\A4\1F\85E\19\B0" }, section "__llvm_covmap", align 8 +@__llvm_profile_runtime = external hidden global i32 +@__profc_foo = private global [1 x i64] zeroinitializer, section "__llvm_prf_cnts", align 8 +@__profd_foo = private global { i64, i64, i32, i32, ptr, ptr, i32, [3 x i16], i32 } { i64 6699318081062747564, i64 0, i32 sub (i32 ptrtoint (ptr @__profc_foo to i32), i32 ptrtoint (ptr @__profd_foo to i32)), i32 0, ptr null, ptr null, i32 1, [3 x i16] zeroinitializer, i32 0 }, section "__llvm_prf_data", align 8 +@__llvm_prf_nm = private constant [13 x i8] c"\03\0Bx\DAK\CB\CF\07\00\02\82\01E", section "__llvm_prf_names", align 1 +@llvm.used = appending global [5 x ptr] [ptr @__covrec_5CF8C24CDB18BDACu, ptr @__llvm_coverage_mapping, ptr @__llvm_profile_runtime_user, ptr @__profd_foo, ptr @__llvm_prf_nm], section "llvm.metadata" + +; Function Attrs: noinline nounwind optnone +define hidden void @foo() #0 { + %1 = load i64, ptr @__profc_foo, align 8 + %2 = add i64 %1, 1 + store i64 %2, ptr @__profc_foo, align 8 + call void @bar() + ret void +} + +; Function Attrs: nounwind +declare void @llvm.instrprof.increment(ptr, i64, i32, i32) #1 + +declare void @bar(...) #2 + +; Function Attrs: noinline +define linkonce_odr hidden i32 @__llvm_profile_runtime_user() #3 comdat { + %1 = load i32, ptr @__llvm_profile_runtime, align 4 + ret i32 %1 +} + +attributes #0 = { noinline nounwind optnone "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="generic" "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+multivalue,+mutable-globals,+nontrapping-fptoint,+reference-types,+sign-ext" } +attributes #1 = { nounwind } +attributes #2 = { "no-prototype" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="generic" "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+multivalue,+mutable-globals,+nontrapping-fptoint,+reference-types,+sign-ext" } +attributes #3 = { noinline } + +!llvm.module.flags = !{!0, !1} +!llvm.ident = !{!2} + +!0 = !{i32 2, !"EnableValueProfiling", i32 0} +!1 = !{i32 1, !"wchar_size", i32 4} +!2 = !{!"clang version 21.1.6"} diff --git a/lld/test/wasm/Inputs/malformed-prf2.ll b/lld/test/wasm/Inputs/malformed-prf2.ll new file mode 100644 index 0000000000000..9831e17c7c862 --- /dev/null +++ b/lld/test/wasm/Inputs/malformed-prf2.ll @@ -0,0 +1,44 @@ +; ModuleID = 'malformed-prf2.c' +source_filename = "malformed-prf2.c" +target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-i128:128-n32:64-S128-ni:1:10:20" +target triple = "wasm32-unknown-unknown" + +$__llvm_profile_runtime_user = comdat any + +$__covrec_E413754A191DB537u = comdat any + +@__covrec_E413754A191DB537u = linkonce_odr hidden constant <{ i64, i32, i64, i64, [9 x i8] }> <{ i64 -2012135647395072713, i32 9, i64 0, i64 -3975376370493289617, [9 x i8] c"\01\01\00\01\01\01\0C\00\0E" }>, section "__llvm_covfun", comdat, align 8 +@__llvm_coverage_mapping = private constant { { i32, i32, i32, i32 }, [76 x i8] } { { i32, i32, i32, i32 } { i32 0, i32 76, i32 0, i32 6 }, [76 x i8] c"\02EIx\DA\0D\CA\D1\09\800\0C\05\C0\15\\D\1F\E8\14\8E\11\DA\94\22MS\92T\D7\B7\9F\07w\A1\AA0f%\F3\0E\E3\A1h\ED\95}\98>\9Cb!#\D8\03\1F\B9\E0\EEc\86oB\AD\A8\09\E7\D5\CAy\A4\1F\85H\19\B1" }, section "__llvm_covmap", align 8 +@__llvm_profile_runtime = external hidden global i32 +@__profc_bar = private global [1 x i64] zeroinitializer, section "__llvm_prf_cnts", align 8 +@__profd_bar = private global { i64, i64, i32, i32, ptr, ptr, i32, [3 x i16], i32 } { i64 -2012135647395072713, i64 0, i32 sub (i32 ptrtoint (ptr @__profc_bar to i32), i32 ptrtoint (ptr @__profd_bar to i32)), i32 0, ptr null, ptr null, i32 1, [3 x i16] zeroinitializer, i32 0 }, section "__llvm_prf_data", align 8 +@__llvm_prf_nm = private constant [13 x i8] c"\03\0Bx\DAKJ,\02\00\02]\016", section "__llvm_prf_names", align 1 +@llvm.used = appending global [5 x ptr] [ptr @__covrec_E413754A191DB537u, ptr @__llvm_coverage_mapping, ptr @__llvm_profile_runtime_user, ptr @__profd_bar, ptr @__llvm_prf_nm], section "llvm.metadata" + +; Function Attrs: noinline nounwind optnone +define hidden void @bar() #0 { + %1 = load i64, ptr @__profc_bar, align 8 + %2 = add i64 %1, 1 + store i64 %2, ptr @__profc_bar, align 8 + ret void +} + +; Function Attrs: nounwind +declare void @llvm.instrprof.increment(ptr, i64, i32, i32) #1 + +; Function Attrs: noinline +define linkonce_odr hidden i32 @__llvm_profile_runtime_user() #2 comdat { + %1 = load i32, ptr @__llvm_profile_runtime, align 4 + ret i32 %1 +} + +attributes #0 = { noinline nounwind optnone "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="generic" "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+multivalue,+mutable-globals,+nontrapping-fptoint,+reference-types,+sign-ext" } +attributes #1 = { nounwind } +attributes #2 = { noinline } + +!llvm.module.flags = !{!0, !1} +!llvm.ident = !{!2} + +!0 = !{i32 2, !"EnableValueProfiling", i32 0} +!1 = !{i32 1, !"wchar_size", i32 4} +!2 = !{!"clang version 21.1.6"} diff --git a/lld/test/wasm/custom-section-align.s b/lld/test/wasm/custom-section-align.s index 0e46177f4cdb7..6ea49df962638 100644 --- a/lld/test/wasm/custom-section-align.s +++ b/lld/test/wasm/custom-section-align.s @@ -1,5 +1,5 @@ # RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown -o %t.o %s -# RUN: wasm-ld --no-entry %t.o -o %t.wasm +# RUN: wasm-ld --no-entry --no-gc-sections %t.o -o %t.wasm # RUN: obj2yaml %t.wasm | FileCheck %s # Check that "__llvm_covfun" custom section is aligned to 8 bytes. diff --git a/lld/test/wasm/malformed-prf.ll b/lld/test/wasm/malformed-prf.ll new file mode 100644 index 0000000000000..d3a50c927ca73 --- /dev/null +++ b/lld/test/wasm/malformed-prf.ll @@ -0,0 +1,27 @@ +; RUN: llc -filetype=obj %s -o %t.o +; RUN: llc -filetype=obj %p/Inputs/malformed-prf1.ll -o %t.malformed-prf1.o +; RUN: llc -filetype=obj %p/Inputs/malformed-prf2.ll -o %t.malformed-prf2.o +; RUN: wasm-ld -o %t.wasm %t.o %t.malformed-prf1.o --start-lib %t.malformed-prf2.o --end-lib --gc-sections --no-entry +; RUN: llvm-cov export --object %t.wasm --empty-profile + +; Every covfun record holds a hash of its symbol name, and llvm-cov will exit fatally if +; it can't resolve that hash back to an entry in the binary's `__llvm_prf_names` linker section. +; +; WASM stores `__llvm_covfun` in custom section, while `__llvm_prf_names` is stored in the DATA section. +; The former not be GC, whereas the latter may be GC, causing llvm-cov execution to fail. +; +; Now, __llvm_covfun and __llvm_covmap will be dropped if __llvm_prf_names is discarded. + +; ModuleID = 'instr-prof.c' +source_filename = "instr-prof.c" +target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-i128:128-n32:64-S128-ni:1:10:20" +target triple = "wasm32-unknown-unknown" + +@__llvm_profile_runtime = hidden global i32 0, align 4 + +!llvm.module.flags = !{!0, !1} +!llvm.ident = !{!2} + +!0 = !{i32 2, !"EnableValueProfiling", i32 0} +!1 = !{i32 1, !"wchar_size", i32 4} +!2 = !{!"clang version 21.1.6"} diff --git a/lld/wasm/InputFiles.h b/lld/wasm/InputFiles.h index fd7fcb13f4426..e0e44f00cdc72 100644 --- a/lld/wasm/InputFiles.h +++ b/lld/wasm/InputFiles.h @@ -121,6 +121,8 @@ class ObjFile : public WasmFileBase { // Maps input type indices to output type indices std::vector typeMap; std::vector typeIsUsed; + // Check if the __llvm_prf_names segment is retained + bool prfSegmentsRetained = false; // Maps function indices to table indices std::vector tableEntries; std::vector tableEntriesRel; diff --git a/lld/wasm/Writer.cpp b/lld/wasm/Writer.cpp index 9a5b56fc52e2f..9639759e08c0d 100644 --- a/lld/wasm/Writer.cpp +++ b/lld/wasm/Writer.cpp @@ -26,6 +26,7 @@ #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringMap.h" #include "llvm/BinaryFormat/Wasm.h" +#include "llvm/ProfileData/InstrProf.h" #include "llvm/Support/FileOutputBuffer.h" #include "llvm/Support/FormatVariadic.h" #include "llvm/Support/Parallel.h" @@ -153,6 +154,12 @@ void Writer::calculateCustomSections() { // Strip debug section in that option was specified. if (stripDebug && name.starts_with(".debug_")) continue; + // Drop `__llvm_covfun` and `__llvm_covmap` sections if + // `__llvm_prf_*` segments are discarded. + if (ctx.arg.gcSections && !file->prfSegmentsRetained && + (name == getInstrProfSectionName(IPSK_covfun, Triple::Wasm) || + name == getInstrProfSectionName(IPSK_covmap, Triple::Wasm))) + continue; // Otherwise include custom sections by default and concatenate their // contents. customSectionMapping[name].push_back(section); @@ -1035,6 +1042,8 @@ void Writer::createOutputSegments() { if (!segment->live) continue; StringRef name = getOutputDataSegmentName(*segment); + if (name == getInstrProfSectionName(IPSK_name, Triple::Wasm)) + file->prfSegmentsRetained = true; OutputSegment *s = nullptr; // When running in relocatable mode we can't merge segments that are part // of comdat groups since the ultimate linker needs to be able exclude or