[llvm] 88dd82f - [opt-viewer] Fix demangler usage with multiprocessing (#228388)

via llvm-commits llvm-commits at lists.llvm.org
Sat Oct 3 13:22:10 PDT 2026


Author: Ömer Sinan Ağacan
Date: 2026-10-03T22:22:02+02:00
New Revision: 88dd82f800f46c3ec4c16932a705091434e17ae0

URL: https://github.com/llvm/llvm-project/commit/88dd82f800f46c3ec4c16932a705091434e17ae0
DIFF: https://github.com/llvm/llvm-project/commit/88dd82f800f46c3ec4c16932a705091434e17ae0.diff

LOG: [opt-viewer] Fix demangler usage with multiprocessing (#228388)

Because of how `multiprocessing.Pool` creates new processes on macOS,
parallel `_render_file`s create their own demangler processes.

However the CLI flag `--demangler` also does not set an inherited state
that's used by the subprocesses, so the new demanglers are created from
the default command, which is `c++filt -n`.

This also means that the semaphore in class `Remark` is not useful on
macOS. It's also not properly cleaned up, causing warnings on exit:

    .../python3.9/multiprocessing/resource_tracker.py:216: UserWarning:
    resource_tracker: There appear to be 36 leaked semaphore objects to
    clean up at shutdown

With this patch we now pass the `--demangler` value to the `Remark`
class so the subprocesses can make use of it. We also remove the
semaphore and make each subprocess have their own demangler process.
This simplifies the code (no need for locking) as the demangler
processes are cheap to start and keep running and don't need to be
shared.

To observe the bug on macOS, generate inlining remarks with

-fsave-optimization-record -foptimization-record-passes=inline
-gline-tables-only

then render as HTML:

    python3 <llvm checkout>/llvm/tools/opt-viewer/opt-viewer.py \
      --demangler 'sed -l s/^/[CUSTOM]/' -s <source dir> \
      -o html <remark file dir>

In the output, you'll see that the index.html has the demangled symbols
starting with '[CUSTOM]', but if you click on the pages the demangled
symbols don't have the same prefix, because the `--demangler` command is
not used in the subprocesses.

The new demangling implementation should work on all platforms as it no
longer relies on process state inheriting behavior.

Added: 
    

Modified: 
    llvm/tools/opt-viewer/opt-viewer.py
    llvm/tools/opt-viewer/optrecord.py

Removed: 
    


################################################################################
diff  --git a/llvm/tools/opt-viewer/opt-viewer.py b/llvm/tools/opt-viewer/opt-viewer.py
index 6fb8a8dc795f1..51995fb85296a 100755
--- a/llvm/tools/opt-viewer/opt-viewer.py
+++ b/llvm/tools/opt-viewer/opt-viewer.py
@@ -286,9 +286,10 @@ def render(self, all_remarks):
         )
 
 
-def _render_file(source_dir, output_dir, ctx, no_highlight, entry, filter_):
+def _render_file(source_dir, output_dir, ctx, no_highlight, demangler, entry, filter_):
     global context
     context = ctx
+    optrecord.Remark.demangler_cmd = demangler
     filename, remarks = entry
     SourceFileRenderer(source_dir, output_dir, filename, no_highlight).render(remarks)
 
@@ -322,6 +323,7 @@ def generate_report(
     max_hottest_remarks_on_index,
     num_jobs,
     should_print_progress,
+    demangler,
 ):
     try:
         os.makedirs(output_dir)
@@ -369,7 +371,7 @@ def generate_report(
     )
 
     _render_file_bound = functools.partial(
-        _render_file, source_dir, output_dir, context, no_highlight
+        _render_file, source_dir, output_dir, context, no_highlight, demangler
     )
     if should_print_progress:
         print("Rendering HTML files...")
@@ -424,8 +426,8 @@ def main():
     )
     parser.add_argument(
         "--demangler",
-        help="Set the demangler to be used (defaults to %s)"
-        % optrecord.Remark.default_demangler,
+        default=optrecord.Remark.default_demangler,
+        help="Set the demangler to be used (defaults to %(default)s)",
     )
 
     parser.add_argument(
@@ -440,8 +442,7 @@ def main():
     args = parser.parse_args()
 
     print_progress = not args.no_progress_indicator
-    if args.demangler:
-        optrecord.Remark.set_demangler(args.demangler)
+    optrecord.Remark.demangler_cmd = args.demangler
 
     files = optrecord.find_opt_files(*args.yaml_dirs_or_files)
     if not files:
@@ -464,6 +465,7 @@ def main():
         args.max_hottest_remarks_on_index,
         args.jobs,
         print_progress,
+        args.demangler,
     )
 
 

diff  --git a/llvm/tools/opt-viewer/optrecord.py b/llvm/tools/opt-viewer/optrecord.py
index 94197e12456fb..ef70a743f0b2b 100644
--- a/llvm/tools/opt-viewer/optrecord.py
+++ b/llvm/tools/opt-viewer/optrecord.py
@@ -16,7 +16,6 @@
 from collections import defaultdict
 import fnmatch
 import functools
-from multiprocessing import Lock
 import os, os.path
 import subprocess
 from sys import intern
@@ -46,23 +45,25 @@ class Remark(yaml.YAMLObject):
     yaml_loader = Loader
 
     default_demangler = "c++filt -n"
+    demangler_cmd = default_demangler
     demangler_proc = None
-    demangler_lock = Lock()
-
-    @classmethod
-    def set_demangler(cls, demangler):
-        cls.demangler_proc = subprocess.Popen(
-            demangler.split(), stdin=subprocess.PIPE, stdout=subprocess.PIPE
-        )
+    demangler_owner_pid = None
 
     @classmethod
     def demangle(cls, name):
-        with cls.demangler_lock:
-            if not cls.demangler_proc:
-                cls.set_demangler(cls.default_demangler)
-            cls.demangler_proc.stdin.write((name + "\n").encode("utf-8"))
-            cls.demangler_proc.stdin.flush()
-            return cls.demangler_proc.stdout.readline().rstrip().decode("utf-8")
+        # Start a demangler on first use in each process. Workers created by
+        # `fork` inherit the parent's class state, including its pipes, so
+        # compare against the current pid rather than just checking for None.
+        if cls.demangler_owner_pid != os.getpid():
+            cls.demangler_proc = subprocess.Popen(
+                cls.demangler_cmd.split(),
+                stdin=subprocess.PIPE,
+                stdout=subprocess.PIPE,
+            )
+            cls.demangler_owner_pid = os.getpid()
+        cls.demangler_proc.stdin.write((name + "\n").encode("utf-8"))
+        cls.demangler_proc.stdin.flush()
+        return cls.demangler_proc.stdout.readline().rstrip().decode("utf-8")
 
     # Intern all strings since we have lot of duplication across filenames,
     # remark text.


        


More information about the llvm-commits mailing list