Skip to content

[lld][WebAssembly] Drop llvm_cov sections when llvm_prf segments are discarded#172023

Open
Spxg wants to merge 3 commits into
llvm:mainfrom
Spxg:w/prf
Open

[lld][WebAssembly] Drop llvm_cov sections when llvm_prf segments are discarded#172023
Spxg wants to merge 3 commits into
llvm:mainfrom
Spxg:w/prf

Conversation

@Spxg

@Spxg Spxg commented Dec 12, 2025

Copy link
Copy Markdown
Contributor

While enabling coverage instrumentation for a Rust program, I encountered llvm-cov exiting with an error: "malformed instrumentation profile data: function name is empty". I found that the Rust linker defaults to using --gc-sections. The issue is resolved by setting --no-gc-sections.

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.

However, WASM stores __llvm_covfun in CustomSection, 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.

`__llvm_prf_names` -> `hash(name) to name` map, PrfMap eg.
`__llvm_covfun` -> iterate -> PrfMap.looksUp(hashValue) -> malformed if not found

This PR makes __llvm_covfun and __llvm_covmap will be discarded if the object is not live.

resolve #192833

@github-actions

Copy link
Copy Markdown

Thank you for submitting a Pull Request (PR) to the LLVM Project!

This PR will be automatically labeled and the relevant teams will be notified.

If you wish to, you can add reviewers by using the "Reviewers" section on this page.

If this is not working for you, it is probably because you do not have write permissions for the repository. In which case you can instead tag reviewers by name in a comment by using @ followed by their GitHub username.

If you have received no comments on your PR for a week, you can request a review by "ping"ing the PR by adding a comment “Ping”. The common courtesy "ping" rate is once a week. Please remember that you are asking for valuable time from other developers.

If you have further questions, they may be answered by the LLVM GitHub User Guide.

You can also ask questions in a comment on this PR, on the LLVM Discord or on the forums.

@llvmbot

llvmbot commented Dec 12, 2025

Copy link
Copy Markdown
Member

@llvm/pr-subscribers-llvm-binary-utilities
@llvm/pr-subscribers-lld

@llvm/pr-subscribers-backend-webassembly

Author: None (Spxg)

Changes

While enabling coverage instrumentation for a Rust program, I encountered llvm-cov exiting with an error: "malformed instrumentation profile data: function name is empty". I found that the Rust linker defaults to using --gc-sections. The issue is resolved by setting --no-gc-sections.

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.

However, WASM stores __llvm_covfun in CustomSection, while __llvm_prf_names is stored in the DATA section. The former cannot be GC, whereas the latter may be GC, causing llvm-cov execution to fail.

This flag records whether it is an InstrProfSegment so that it can be enqueued during GC.


Full diff: https://github.com/llvm/llvm-project/pull/172023.diff

4 Files Affected:

  • (modified) lld/wasm/InputChunks.h (+3)
  • (modified) lld/wasm/InputFiles.cpp (+17)
  • (modified) lld/wasm/MarkLive.cpp (+12-1)
  • (modified) llvm/include/llvm/BinaryFormat/Wasm.h (+1)
diff --git a/lld/wasm/InputChunks.h b/lld/wasm/InputChunks.h
index 1fe78d76631f1..7cc49da1064fc 100644
--- a/lld/wasm/InputChunks.h
+++ b/lld/wasm/InputChunks.h
@@ -83,6 +83,9 @@ class InputChunk {
 
   bool isTLS() const { return flags & llvm::wasm::WASM_SEG_FLAG_TLS; }
   bool isRetained() const { return flags & llvm::wasm::WASM_SEG_FLAG_RETAIN; }
+  bool isInstrProfSegment() const {
+    return flags & llvm::wasm::WASM_SEG_FLAG_PRF;
+  }
 
   ObjFile *file;
   OutputSection *outputSec = nullptr;
diff --git a/lld/wasm/InputFiles.cpp b/lld/wasm/InputFiles.cpp
index 387b5eb10ba2f..fedaa4b737e07 100644
--- a/lld/wasm/InputFiles.cpp
+++ b/lld/wasm/InputFiles.cpp
@@ -577,6 +577,23 @@ void ObjFile::parse(bool ignoreComdats) {
     if (!seg->isTLS() &&
         (seg->name.starts_with(".tdata") || seg->name.starts_with(".tbss")))
       seg->flags |= WASM_SEG_FLAG_TLS;
+
+    // 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.
+    //
+    // However, WASM stores `__llvm_covfun` in `CustomSection`, while
+    // `__llvm_prf_names` is stored in the `DATA` section. The former cannot be
+    // GC, whereas the latter may be GC, causing `llvm-cov` execution to fail.
+    //
+    // This flag records whether it is an `InstrProfSegment` so that it can be
+    // enqueued during GC.
+    if (seg->name == getInstrProfSectionName(IPSK_name, Triple::Wasm) ||
+        seg->name == getInstrProfSectionName(IPSK_cnts, Triple::Wasm) ||
+        seg->name == getInstrProfSectionName(IPSK_data, Triple::Wasm)) {
+      seg->flags |= WASM_SEG_FLAG_PRF;
+    }
+
     segments.emplace_back(seg);
   }
   setRelocs(segments, dataSection);
diff --git a/lld/wasm/MarkLive.cpp b/lld/wasm/MarkLive.cpp
index 2b2cf19f14b30..c51214b6920cf 100644
--- a/lld/wasm/MarkLive.cpp
+++ b/lld/wasm/MarkLive.cpp
@@ -43,6 +43,7 @@ class MarkLive {
   void enqueue(InputChunk *chunk);
   void enqueueInitFunctions(const ObjFile *sym);
   void enqueueRetainedSegments(const ObjFile *file);
+  void enqueueInstrProfSegments(const ObjFile *file);
   void mark();
   bool isCallCtorsLive();
 
@@ -104,6 +105,13 @@ void MarkLive::enqueueRetainedSegments(const ObjFile *file) {
       enqueue(chunk);
 }
 
+// Mark instr profile segments.
+void MarkLive::enqueueInstrProfSegments(const ObjFile *file) {
+  for (InputChunk *chunk : file->segments)
+    if (chunk->isInstrProfSegment())
+      enqueue(chunk);
+}
+
 void MarkLive::run() {
   // Add GC root symbols.
   if (!ctx.arg.entry.empty())
@@ -117,7 +125,9 @@ void MarkLive::run() {
   if (ctx.sym.callDtors)
     enqueue(ctx.sym.callDtors);
 
-  for (const ObjFile *obj : ctx.objectFiles)
+  for (const ObjFile *obj : ctx.objectFiles) {
+    // Enqueue instr profile segments
+    enqueueInstrProfSegments(obj);
     if (obj->isLive()) {
       // Enqueue constructors in objects explicitly live from the command-line.
       enqueueInitFunctions(obj);
@@ -125,6 +135,7 @@ void MarkLive::run() {
       // command-line.
       enqueueRetainedSegments(obj);
     }
+  }
 
   mark();
 
diff --git a/llvm/include/llvm/BinaryFormat/Wasm.h b/llvm/include/llvm/BinaryFormat/Wasm.h
index cf90a43d0d7e8..1f76330a29efb 100644
--- a/llvm/include/llvm/BinaryFormat/Wasm.h
+++ b/llvm/include/llvm/BinaryFormat/Wasm.h
@@ -229,6 +229,7 @@ enum WasmSegmentFlag : unsigned {
   WASM_SEG_FLAG_STRINGS = 0x1,
   WASM_SEG_FLAG_TLS = 0x2,
   WASM_SEG_FLAG_RETAIN = 0x4,
+  WASM_SEG_FLAG_PRF = 0x8,
 };
 
 // Kinds of tag attributes.

@llvmbot

llvmbot commented Dec 12, 2025

Copy link
Copy Markdown
Member

@llvm/pr-subscribers-lld-wasm

Author: None (Spxg)

Changes

While enabling coverage instrumentation for a Rust program, I encountered llvm-cov exiting with an error: "malformed instrumentation profile data: function name is empty". I found that the Rust linker defaults to using --gc-sections. The issue is resolved by setting --no-gc-sections.

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.

However, WASM stores __llvm_covfun in CustomSection, while __llvm_prf_names is stored in the DATA section. The former cannot be GC, whereas the latter may be GC, causing llvm-cov execution to fail.

This flag records whether it is an InstrProfSegment so that it can be enqueued during GC.


Full diff: https://github.com/llvm/llvm-project/pull/172023.diff

4 Files Affected:

  • (modified) lld/wasm/InputChunks.h (+3)
  • (modified) lld/wasm/InputFiles.cpp (+17)
  • (modified) lld/wasm/MarkLive.cpp (+12-1)
  • (modified) llvm/include/llvm/BinaryFormat/Wasm.h (+1)
diff --git a/lld/wasm/InputChunks.h b/lld/wasm/InputChunks.h
index 1fe78d76631f1..7cc49da1064fc 100644
--- a/lld/wasm/InputChunks.h
+++ b/lld/wasm/InputChunks.h
@@ -83,6 +83,9 @@ class InputChunk {
 
   bool isTLS() const { return flags & llvm::wasm::WASM_SEG_FLAG_TLS; }
   bool isRetained() const { return flags & llvm::wasm::WASM_SEG_FLAG_RETAIN; }
+  bool isInstrProfSegment() const {
+    return flags & llvm::wasm::WASM_SEG_FLAG_PRF;
+  }
 
   ObjFile *file;
   OutputSection *outputSec = nullptr;
diff --git a/lld/wasm/InputFiles.cpp b/lld/wasm/InputFiles.cpp
index 387b5eb10ba2f..fedaa4b737e07 100644
--- a/lld/wasm/InputFiles.cpp
+++ b/lld/wasm/InputFiles.cpp
@@ -577,6 +577,23 @@ void ObjFile::parse(bool ignoreComdats) {
     if (!seg->isTLS() &&
         (seg->name.starts_with(".tdata") || seg->name.starts_with(".tbss")))
       seg->flags |= WASM_SEG_FLAG_TLS;
+
+    // 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.
+    //
+    // However, WASM stores `__llvm_covfun` in `CustomSection`, while
+    // `__llvm_prf_names` is stored in the `DATA` section. The former cannot be
+    // GC, whereas the latter may be GC, causing `llvm-cov` execution to fail.
+    //
+    // This flag records whether it is an `InstrProfSegment` so that it can be
+    // enqueued during GC.
+    if (seg->name == getInstrProfSectionName(IPSK_name, Triple::Wasm) ||
+        seg->name == getInstrProfSectionName(IPSK_cnts, Triple::Wasm) ||
+        seg->name == getInstrProfSectionName(IPSK_data, Triple::Wasm)) {
+      seg->flags |= WASM_SEG_FLAG_PRF;
+    }
+
     segments.emplace_back(seg);
   }
   setRelocs(segments, dataSection);
diff --git a/lld/wasm/MarkLive.cpp b/lld/wasm/MarkLive.cpp
index 2b2cf19f14b30..c51214b6920cf 100644
--- a/lld/wasm/MarkLive.cpp
+++ b/lld/wasm/MarkLive.cpp
@@ -43,6 +43,7 @@ class MarkLive {
   void enqueue(InputChunk *chunk);
   void enqueueInitFunctions(const ObjFile *sym);
   void enqueueRetainedSegments(const ObjFile *file);
+  void enqueueInstrProfSegments(const ObjFile *file);
   void mark();
   bool isCallCtorsLive();
 
@@ -104,6 +105,13 @@ void MarkLive::enqueueRetainedSegments(const ObjFile *file) {
       enqueue(chunk);
 }
 
+// Mark instr profile segments.
+void MarkLive::enqueueInstrProfSegments(const ObjFile *file) {
+  for (InputChunk *chunk : file->segments)
+    if (chunk->isInstrProfSegment())
+      enqueue(chunk);
+}
+
 void MarkLive::run() {
   // Add GC root symbols.
   if (!ctx.arg.entry.empty())
@@ -117,7 +125,9 @@ void MarkLive::run() {
   if (ctx.sym.callDtors)
     enqueue(ctx.sym.callDtors);
 
-  for (const ObjFile *obj : ctx.objectFiles)
+  for (const ObjFile *obj : ctx.objectFiles) {
+    // Enqueue instr profile segments
+    enqueueInstrProfSegments(obj);
     if (obj->isLive()) {
       // Enqueue constructors in objects explicitly live from the command-line.
       enqueueInitFunctions(obj);
@@ -125,6 +135,7 @@ void MarkLive::run() {
       // command-line.
       enqueueRetainedSegments(obj);
     }
+  }
 
   mark();
 
diff --git a/llvm/include/llvm/BinaryFormat/Wasm.h b/llvm/include/llvm/BinaryFormat/Wasm.h
index cf90a43d0d7e8..1f76330a29efb 100644
--- a/llvm/include/llvm/BinaryFormat/Wasm.h
+++ b/llvm/include/llvm/BinaryFormat/Wasm.h
@@ -229,6 +229,7 @@ enum WasmSegmentFlag : unsigned {
   WASM_SEG_FLAG_STRINGS = 0x1,
   WASM_SEG_FLAG_TLS = 0x2,
   WASM_SEG_FLAG_RETAIN = 0x4,
+  WASM_SEG_FLAG_PRF = 0x8,
 };
 
 // Kinds of tag attributes.

Comment thread llvm/include/llvm/BinaryFormat/Wasm.h
Comment thread llvm/include/llvm/BinaryFormat/Wasm.h Outdated
Comment thread lld/wasm/MarkLive.cpp Outdated
Comment thread llvm/include/llvm/BinaryFormat/Wasm.h
@Spxg Spxg changed the title [Coverage][WebAssembly] Keep InstrProf segments during gc [Coverage][WebAssembly] Discard InstrProf sections if object not live Dec 13, 2025
@Spxg Spxg changed the title [Coverage][WebAssembly] Discard InstrProf sections if object not live [Coverage][WebAssembly] Remove InstrProf sections if object not live Dec 13, 2025
@Spxg Spxg force-pushed the w/prf branch 2 times, most recently from 5ca22ad to dc84939 Compare December 13, 2025 03:05
@Spxg Spxg marked this pull request as draft December 13, 2025 03:15
@Spxg Spxg marked this pull request as ready for review December 13, 2025 03:34
@Spxg Spxg force-pushed the w/prf branch 2 times, most recently from c94ec45 to cba9c10 Compare December 13, 2025 04:00
@Spxg Spxg changed the title [Coverage][WebAssembly] Remove InstrProf sections if object not live [Coverage][WebAssembly] Discard InstrProf sections if object not live Dec 13, 2025
Comment thread lld/wasm/Writer.cpp
@Spxg Spxg marked this pull request as draft December 16, 2025 03:50
@sbc100

sbc100 commented Dec 17, 2025

Copy link
Copy Markdown
Contributor

OK, this change looks good now. Can we add a test though perhaps?

@Spxg Spxg force-pushed the w/prf branch 2 times, most recently from d65c4a8 to 5127a43 Compare December 18, 2025 14:05
@Spxg Spxg marked this pull request as ready for review December 18, 2025 14:07
@Spxg

Spxg commented Dec 18, 2025

Copy link
Copy Markdown
Contributor Author

OK, this change looks good now. Can we add a test though perhaps?

Done! minimal example:

// instr-prof.c
int __llvm_profile_runtime = 0;
// malformed-prf1.c
extern void bar();
void foo() { bar(); }
// malformed-prf2.c
void bar() {}
clang -fprofile-instr-generate -fcoverage-mapping instr-prof.c malformed-prf1.c malformed-prf2.c -c -target wasm32-unknown-unknown
wasm-ld instr-prof.o malformed-prf1.o --start-lib malformed-prf2.o --end-lib --gc-sections --no-entry -o x.wasm
llvm-cov export --object x.wasm --empty-profile
# error: failed to load coverage: 'x.wasm': malformed instrumentation profile data: function name is empty
# error: could not load coverage information

Comment thread lld/wasm/InputFiles.h Outdated
Comment thread lld/test/wasm/Inputs/instr-prof.ll Outdated
Comment thread lld/test/wasm/malformed-prf.test Outdated
@Spxg Spxg changed the title [Coverage][WebAssembly] Discard InstrProf sections if object not live [Coverage][WebAssembly] Discard __llvm_covfun and __llvm_covmap if __llvm_prf_names was discarded Dec 19, 2025
@Spxg Spxg marked this pull request as draft December 19, 2025 14:33
@Spxg Spxg force-pushed the w/prf branch 7 times, most recently from 258ab97 to 3112c24 Compare December 20, 2025 03:09
@Spxg Spxg changed the title [Coverage][WebAssembly] Discard __llvm_covfun and __llvm_covmap if __llvm_prf_names was discarded [lld][WebAssembly] Drop llvm_cov sections when llvm_prf segments are discarded Dec 20, 2025
@Spxg Spxg marked this pull request as ready for review December 20, 2025 03:12
@github-actions

Copy link
Copy Markdown

🐧 Linux x64 Test Results

  • 3750 tests passed
  • 69 tests skipped
  • 1 test failed

Failed Tests

(click on a test name to see its output)

lld

lld.wasm/malformed-prf.ll
Exit Code: 127

Command Output (stdout):
--
# RUN: at line 1
/home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/llc -filetype=obj /home/gha/actions-runner/_work/llvm-project/llvm-project/lld/test/wasm/malformed-prf.ll -o /home/gha/actions-runner/_work/llvm-project/llvm-project/build/tools/lld/test/wasm/Output/malformed-prf.ll.tmp.instr-prof.o
# executed command: /home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/llc -filetype=obj /home/gha/actions-runner/_work/llvm-project/llvm-project/lld/test/wasm/malformed-prf.ll -o /home/gha/actions-runner/_work/llvm-project/llvm-project/build/tools/lld/test/wasm/Output/malformed-prf.ll.tmp.instr-prof.o
# note: command had no output on stdout or stderr
# RUN: at line 2
/home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/llc -filetype=obj /home/gha/actions-runner/_work/llvm-project/llvm-project/lld/test/wasm/Inputs/malformed-prf1.ll -o /home/gha/actions-runner/_work/llvm-project/llvm-project/build/tools/lld/test/wasm/Output/malformed-prf.ll.tmp.malformed-prf1.o
# executed command: /home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/llc -filetype=obj /home/gha/actions-runner/_work/llvm-project/llvm-project/lld/test/wasm/Inputs/malformed-prf1.ll -o /home/gha/actions-runner/_work/llvm-project/llvm-project/build/tools/lld/test/wasm/Output/malformed-prf.ll.tmp.malformed-prf1.o
# note: command had no output on stdout or stderr
# RUN: at line 3
/home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/llc -filetype=obj /home/gha/actions-runner/_work/llvm-project/llvm-project/lld/test/wasm/Inputs/malformed-prf2.ll -o /home/gha/actions-runner/_work/llvm-project/llvm-project/build/tools/lld/test/wasm/Output/malformed-prf.ll.tmp.malformed-prf2.o
# executed command: /home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/llc -filetype=obj /home/gha/actions-runner/_work/llvm-project/llvm-project/lld/test/wasm/Inputs/malformed-prf2.ll -o /home/gha/actions-runner/_work/llvm-project/llvm-project/build/tools/lld/test/wasm/Output/malformed-prf.ll.tmp.malformed-prf2.o
# note: command had no output on stdout or stderr
# RUN: at line 4
/home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/wasm-ld -o /home/gha/actions-runner/_work/llvm-project/llvm-project/build/tools/lld/test/wasm/Output/malformed-prf.ll.tmp.wasm /home/gha/actions-runner/_work/llvm-project/llvm-project/build/tools/lld/test/wasm/Output/malformed-prf.ll.tmp.instr-prof.o /home/gha/actions-runner/_work/llvm-project/llvm-project/build/tools/lld/test/wasm/Output/malformed-prf.ll.tmp.malformed-prf1.o --start-lib /home/gha/actions-runner/_work/llvm-project/llvm-project/build/tools/lld/test/wasm/Output/malformed-prf.ll.tmp.malformed-prf2.o --end-lib --gc-sections --no-entry
# executed command: /home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/wasm-ld -o /home/gha/actions-runner/_work/llvm-project/llvm-project/build/tools/lld/test/wasm/Output/malformed-prf.ll.tmp.wasm /home/gha/actions-runner/_work/llvm-project/llvm-project/build/tools/lld/test/wasm/Output/malformed-prf.ll.tmp.instr-prof.o /home/gha/actions-runner/_work/llvm-project/llvm-project/build/tools/lld/test/wasm/Output/malformed-prf.ll.tmp.malformed-prf1.o --start-lib /home/gha/actions-runner/_work/llvm-project/llvm-project/build/tools/lld/test/wasm/Output/malformed-prf.ll.tmp.malformed-prf2.o --end-lib --gc-sections --no-entry
# note: command had no output on stdout or stderr
# RUN: at line 5
llvm-cov export --object /home/gha/actions-runner/_work/llvm-project/llvm-project/build/tools/lld/test/wasm/Output/malformed-prf.ll.tmp.wasm --empty-profile
# executed command: llvm-cov export --object /home/gha/actions-runner/_work/llvm-project/llvm-project/build/tools/lld/test/wasm/Output/malformed-prf.ll.tmp.wasm --empty-profile
# .---command stderr------------
# | 'llvm-cov': command not found
# `-----------------------------
# error: command failed with exit status: 127

--

If these failures are unrelated to your changes (for example tests are broken or flaky at HEAD), please open an issue at https://github.com/llvm/llvm-project/issues and add the infrastructure label.

@github-actions

Copy link
Copy Markdown

🪟 Windows x64 Test Results

  • 3105 tests passed
  • 61 tests skipped
  • 1 test failed

Failed Tests

(click on a test name to see its output)

lld

lld.wasm/malformed-prf.ll
Exit Code: 127

Command Output (stdout):
--
# RUN: at line 1
c:\_work\llvm-project\llvm-project\build\bin\llc.exe -filetype=obj C:\_work\llvm-project\llvm-project\lld\test\wasm\malformed-prf.ll -o C:\_work\llvm-project\llvm-project\build\tools\lld\test\wasm\Output\malformed-prf.ll.tmp.instr-prof.o
# executed command: 'c:\_work\llvm-project\llvm-project\build\bin\llc.exe' -filetype=obj 'C:\_work\llvm-project\llvm-project\lld\test\wasm\malformed-prf.ll' -o 'C:\_work\llvm-project\llvm-project\build\tools\lld\test\wasm\Output\malformed-prf.ll.tmp.instr-prof.o'
# note: command had no output on stdout or stderr
# RUN: at line 2
c:\_work\llvm-project\llvm-project\build\bin\llc.exe -filetype=obj C:\_work\llvm-project\llvm-project\lld\test\wasm/Inputs/malformed-prf1.ll -o C:\_work\llvm-project\llvm-project\build\tools\lld\test\wasm\Output\malformed-prf.ll.tmp.malformed-prf1.o
# executed command: 'c:\_work\llvm-project\llvm-project\build\bin\llc.exe' -filetype=obj 'C:\_work\llvm-project\llvm-project\lld\test\wasm/Inputs/malformed-prf1.ll' -o 'C:\_work\llvm-project\llvm-project\build\tools\lld\test\wasm\Output\malformed-prf.ll.tmp.malformed-prf1.o'
# note: command had no output on stdout or stderr
# RUN: at line 3
c:\_work\llvm-project\llvm-project\build\bin\llc.exe -filetype=obj C:\_work\llvm-project\llvm-project\lld\test\wasm/Inputs/malformed-prf2.ll -o C:\_work\llvm-project\llvm-project\build\tools\lld\test\wasm\Output\malformed-prf.ll.tmp.malformed-prf2.o
# executed command: 'c:\_work\llvm-project\llvm-project\build\bin\llc.exe' -filetype=obj 'C:\_work\llvm-project\llvm-project\lld\test\wasm/Inputs/malformed-prf2.ll' -o 'C:\_work\llvm-project\llvm-project\build\tools\lld\test\wasm\Output\malformed-prf.ll.tmp.malformed-prf2.o'
# note: command had no output on stdout or stderr
# RUN: at line 4
c:\_work\llvm-project\llvm-project\build\bin\wasm-ld.exe -o C:\_work\llvm-project\llvm-project\build\tools\lld\test\wasm\Output\malformed-prf.ll.tmp.wasm C:\_work\llvm-project\llvm-project\build\tools\lld\test\wasm\Output\malformed-prf.ll.tmp.instr-prof.o C:\_work\llvm-project\llvm-project\build\tools\lld\test\wasm\Output\malformed-prf.ll.tmp.malformed-prf1.o --start-lib C:\_work\llvm-project\llvm-project\build\tools\lld\test\wasm\Output\malformed-prf.ll.tmp.malformed-prf2.o --end-lib --gc-sections --no-entry
# executed command: 'c:\_work\llvm-project\llvm-project\build\bin\wasm-ld.exe' -o 'C:\_work\llvm-project\llvm-project\build\tools\lld\test\wasm\Output\malformed-prf.ll.tmp.wasm' 'C:\_work\llvm-project\llvm-project\build\tools\lld\test\wasm\Output\malformed-prf.ll.tmp.instr-prof.o' 'C:\_work\llvm-project\llvm-project\build\tools\lld\test\wasm\Output\malformed-prf.ll.tmp.malformed-prf1.o' --start-lib 'C:\_work\llvm-project\llvm-project\build\tools\lld\test\wasm\Output\malformed-prf.ll.tmp.malformed-prf2.o' --end-lib --gc-sections --no-entry
# note: command had no output on stdout or stderr
# RUN: at line 5
llvm-cov export --object C:\_work\llvm-project\llvm-project\build\tools\lld\test\wasm\Output\malformed-prf.ll.tmp.wasm --empty-profile
# executed command: llvm-cov export --object 'C:\_work\llvm-project\llvm-project\build\tools\lld\test\wasm\Output\malformed-prf.ll.tmp.wasm' --empty-profile
# .---command stderr------------
# | 'llvm-cov': command not found
# `-----------------------------
# error: command failed with exit status: 127

--

If these failures are unrelated to your changes (for example tests are broken or flaky at HEAD), please open an issue at https://github.com/llvm/llvm-project/issues and add the infrastructure label.

@sbc100 sbc100 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems a little unfortunate to have to hack wasm-ld to support these llvm-specifics.

I wonder if we can find a way to avoid this in long term? For now it seems like this is needed to fix the bug in question though, so lgtm.

Comment thread lld/test/wasm/malformed-prf.ll Outdated

!0 = !{i32 2, !"EnableValueProfiling", i32 0}
!1 = !{i32 1, !"wchar_size", i32 4}
!2 = !{!"clang version 21.1.6"}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we minify these .ll files by removing all the superfluous attributes?

@Spxg Spxg Dec 20, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I generated it via clang -S -emit-llvm, I think we shouldn't make any changes, what makes me curious is why people don't use .c for testing, it's smaller and clearer.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason is that we want to keep the system-under-test as small as possible. If we used clang -S -emit-llvm then changed to clang could effect lld tests, which would make them way less stable, and make test a failure way more involved to debug.

Even using llvm ir here is kind sub-optimal because it means the tests depends on large parts of llvm includes instruction selelction. Tests written in .s format depend only the MC layer and are far more stable/simple.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your answers! Could you trigger the CI task to run again? It seems the last run failed due to the missing llvm-cov command.

Comment thread lld/test/wasm/malformed-prf.ll Outdated
@MaskRay

MaskRay commented Dec 21, 2025

Copy link
Copy Markdown
Member

Special-casing LLVM code coverage sections feels like the wrong direction. Instead, we should use section metadata to explicitly encode dependency relationships (and propose adding this to the wasm binary format if it's currently lacking). For a solid reference, I recommend looking into Mach-O live_support, which handles similar linker garbage collection challenges. https://maskray.me/blog/2021-02-28-linker-garbage-collection

@Spxg

Spxg commented Apr 18, 2026

Copy link
Copy Markdown
Contributor Author

Special-casing LLVM code coverage sections feels like the wrong direction. Instead, we should use section metadata to explicitly encode dependency relationships

That sounds like a long-term solution. It’s probably not this PR can address. @sbc100, what do you think?

guybedford added a commit to guybedford/llvm-project that referenced this pull request May 28, 2026
…stom sections

wasm-ld silently drops user-defined wasm custom sections (wasm-bindgen
descriptors, wit-bindgen component metadata, coverage / sanitizer
sections, __llvm_prf_*, etc.) from archive members that have no
externally-referenced symbol.

Custom sections have no symbol-table entries by construction, so
symbol-driven archive extraction cannot reach them: the archive
member stays lazy and the section is silently dropped along with it.
This affects builds with or without --gc-sections, since the bug is in
archive lazy-extraction, not GC.

The fix teaches ObjFile::parseLazy to scan top-level sections for
WASM_SEC_CUSTOM entries, ignoring custom sections that the linker
synthesizes or owns (linking, name, producers, target_features,
reloc.*, .llvmbc, .llvmcmd, .debug_*, dylink*). If a user custom
section is present, the member is eagerly extracted
(lazy = false; markLive(); addFile(this)).

This generalizes the case-specific approach in llvm#172023 (which added
WASM_SEG_FLAG_PRF for __llvm_prf_* sections), addressing the comment
by @sbc100 on that PR asking for a less special-cased solution. The
PRF special case could later be retired in favor of this general
mechanism.

Tested end-to-end with the wasm-bindgen raytrace-parallel example,
which previously failed with `error: import of __wbg_get_* doesn't
have an adapter listed` and now builds successfully.

Fixes llvm#200083.

Note: this PR was authored by an LLM (Claude/OpenCode) under human
direction, with human review of the diagnosis, implementation, and
tests before submission.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Malformed instrumentation profile data can occur in some WebAssembly binaries

4 participants