[llvm] [llvm-profgen] Add support for ETM trace decoding (PR #191584)

via llvm-commits llvm-commits at lists.llvm.org
Wed Apr 22 09:52:06 PDT 2026


https://github.com/gulfemsavrun updated https://github.com/llvm/llvm-project/pull/191584

>From 93768f1e3a611d21243ee96520347b1c555ccbe9 Mon Sep 17 00:00:00 2001
From: Gulfem Savrun Yeniceri <gulfem at google.com>
Date: Tue, 10 Feb 2026 17:49:11 -0800
Subject: [PATCH 1/6] [llvm-profgen] Add support for ETM trace decoding

This patch introduces ETMReader to llvm-profgen,
enabling the reconstruction of execution profiles from
ETM formatted trace data.

- Integrate OpenCSD (CoreSight Decoding Library) as an
  optional dependency via the LLVM_ENABLE_OPENCSD flag.
- Implement ETMReader, which uses hardware configuration
  and ELF memory mapping to decode instruction traces.
- Add the --etm command-line option to specify raw
  trace inputs.

The implementation targets microcontroller-class (Cortex-M)
devices based on the binary's target triple.

RFC: https://discourse.llvm.org/t/rfc-add-etm-trace-support-to-llvm-profgen/90525
---
 llvm/docs/CommandGuide/llvm-profgen.rst       |   5 +
 llvm/docs/ReleaseNotes.md                     |   2 +
 .../llvm-profgen/Inputs/etm-opencsd.yaml      |  48 ++++
 llvm/test/tools/llvm-profgen/etm-arch.test    |  81 +++++++
 llvm/test/tools/llvm-profgen/etm-opencsd.test |  15 ++
 llvm/test/tools/llvm-profgen/lit.local.cfg    |  10 +
 llvm/tools/llvm-profgen/CMakeLists.txt        |  20 ++
 llvm/tools/llvm-profgen/PerfReader.cpp        | 209 ++++++++++++++++++
 llvm/tools/llvm-profgen/PerfReader.h          |  31 ++-
 llvm/tools/llvm-profgen/ProfiledBinary.h      |   1 +
 llvm/tools/llvm-profgen/llvm-profgen.cpp      |  25 ++-
 11 files changed, 439 insertions(+), 8 deletions(-)
 create mode 100644 llvm/test/tools/llvm-profgen/Inputs/etm-opencsd.yaml
 create mode 100644 llvm/test/tools/llvm-profgen/etm-arch.test
 create mode 100644 llvm/test/tools/llvm-profgen/etm-opencsd.test

diff --git a/llvm/docs/CommandGuide/llvm-profgen.rst b/llvm/docs/CommandGuide/llvm-profgen.rst
index e515612983894..350efef89f7e7 100644
--- a/llvm/docs/CommandGuide/llvm-profgen.rst
+++ b/llvm/docs/CommandGuide/llvm-profgen.rst
@@ -24,6 +24,11 @@ At least one of the following commands are required:
   Path of perf-script trace created by Linux perf tool with `script`
   command(the raw perf.data should be profiled with -b).
 
+.. option:: --etm=<string>
+
+  Path of the ETM trace file created by ARM CoreSight trace tools.
+  Requires the OpenCSD library to be enabled during the build.
+
 .. option:: --binary=<string[,string,...]>
 
   Path of the input profiled binary files.
diff --git a/llvm/docs/ReleaseNotes.md b/llvm/docs/ReleaseNotes.md
index a47c5a48d51b8..cfa9e812f9ee8 100644
--- a/llvm/docs/ReleaseNotes.md
+++ b/llvm/docs/ReleaseNotes.md
@@ -238,6 +238,8 @@ Changes to the Debug Info
 Changes to the LLVM tools
 -------------------------
 
+* `llvm-profgen` now supports ETM trace decoding using the OpenCSD library for Cortex-M targets.
+
 * `llvm-objcopy` no longer corrupts the symbol table when `--update-section` is called for ELF files.
 * `FileCheck` option `-check-prefix` now accepts a comma-separated list of
   prefixes, making it an alias of the existing `-check-prefixes` option.
diff --git a/llvm/test/tools/llvm-profgen/Inputs/etm-opencsd.yaml b/llvm/test/tools/llvm-profgen/Inputs/etm-opencsd.yaml
new file mode 100644
index 0000000000000..6824b4a9c108b
--- /dev/null
+++ b/llvm/test/tools/llvm-profgen/Inputs/etm-opencsd.yaml
@@ -0,0 +1,48 @@
+# This file contains binary data extracted from the OpenCSD repository sample tests
+# (specifically from decoder/tests/snapshots/armv8_1m_branches/).
+# It is used to verify ETM trace decoding in llvm-profgen.
+--- !ELF
+FileHeader:
+  Class:           ELFCLASS32
+  Data:            ELFDATA2LSB
+  Type:            ET_DYN
+  Machine:         EM_ARM
+  Flags:           [ EF_ARM_EABI_VER5 ]
+ProgramHeaders:
+  - Type:            PT_LOAD
+    Flags:           [ PF_X, PF_R ]
+    VAddr:           0x1E008
+    FirstSec:        .text1
+    LastSec:         .text1
+  - Type:            PT_LOAD
+    Flags:           [ PF_X, PF_R ]
+    VAddr:           0x4F9A02
+    FirstSec:        .text2
+    LastSec:         .text2
+Sections:
+  - Name:            .text1
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_ALLOC, SHF_EXECINSTR ]
+    Address:         0x1E008
+    AddressAlign:    0x4
+    Content:         2de9f04f8db08142984651eab08706922ab147ea0002b2f1ff3f40f3bf80002fcde9020100f1af8000f08b8016984ff0000a4fea88093a4600f1040b202000eb4810059704900698043008900de000bf079a0af1080a059f0bf1200b0498083a0899ba45014408916bda012a10460792d8bf012008284ff00802a7eb0a0150eab28c082951eab28001293bdb06990024089e01eb8a02169901eb8a03ffe704eb0a0707fb08f193ed002a02eb810191ed001a1a9921ee021a91ed000a44f003c011e000bf19990134644507fb01f1189f4e4407eb810191ed002aa0ee012a81ed002a0fd0dbe73546594600bfb5ec012ab1ec013a22ee032a31ee021a0ff00bc0e0e700bfbaf1000fa2d00afb08f1069a09ab199e02eb8101169acde90b180afb06f10992189a02eb81071a990baa91ed000a1799cde900760a915146dff09cf186e7dde902318b421add08fb01f0069a199e02eb8002581a169bcde90b2806fb01f20993189b03eb82071a9a09ab92ed000a179acde900760a920baadff07cf10db0bde8f08f4ef669304ef6fc12c0f27700a621c0f2770251f218f64ef669304ef20772c0f27700b221c0f2770251f20df6d4d4
+  - Name:            .text2
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_ALLOC, SHF_EXECINSTR ]
+    Address:         0x4F9A02
+    AddressAlign:    0x4
+    Content:         002851d096f85c00002864d030462a46234606f5956800f0f2f996ed080a96f82c0020ee009a01286ed1b7eec80a06f594792a464846234602f067f8d8e906104a1e70f100020fdbd8e9083253ea020708d0eb1a64eb0202c91a904104da484602f079f9c8e9085496f8541096f8a40401293ad1002847d098ed000bb5ee400bf1ee10fa40d098ed041b21ee011b81ee000b98ed021b31ee400bb7eec91a80fe010bb7eec09b2fe006f5a4602a4623460af038fe002800f0a080b7eec80a4ef622214ff46d70c2f6942121228ded000b92f18dfb91e0d6ed010a30462a462346b0ee480a00f0e3f887e098ed020b9fed6b1b002801fe000bb7eec91a80fe010bb7eec00b09fe009ad6e928324ff07e5096ed1a0a0390012078ee400a8df80a00002153ea020040f2011706f1680004910591adf8087015d0eb1a64eb020283eae27382eae277b3ebe27367ebe27245f60107cff64707db1b72f10d0228bf8df80a1001e08df80a1003a902aab0ee490a00f029fa96f8e804002804bf96f8f8045feac0703fd030462a46234600f039fe98bb002706f264404121c6f8a874c6e9b077c6f8ac74c6f8b074c6f8b474c6f8b874c6f8bc74c6f85874c6f85c74a6f8607438f7ebf506f1c800faf7c3fed6f808052a46234686f85c70016b0131016306f5aa600af09efd38b142f6491140f6c440caf62f2100226ae706b0bdec048b01b0bde8f0832846214670f75efd41ec180bd8e9120170f758fd96ed120a41ec110bb7eec00a9fed1f2ba0ee021bb4ee481bf1ee10faa6d896f84400012186f8f81401280bd100204df2e111c9e90000c8f2762140f6aa60002292f1c8fa96f8450001280ed1306f40f6f931c3f249010022c8f85800012086f8040540f6ea6092f1b5fa44f6976140f65260c8f26911002292f1acfa76e700bf00bf00bf00000000000000000000000065cdcd412de9f04397b080ed1a0a20eea00a0646d0f8d0048ded090a96ed020a96ed031a00216f4620ee000a21ee011ac6e91b1038462421984691468ded0a0a8ded0b1a38f755f54ff0030e09a800bf50f8041b47f8101b0ff007c0002069468df858000ca809f0a1fb9df85800002865d09df85900f8b906f174042421204638f737f5002001210caa00bf0130134640f001e0274600bf53f80c5b47f8045b0ff007c00131032804f10c0402f10402ecd1012086f8980001204a4686f85c0030464346c6e9189800f046fda0bb002406f264404121c6f8a844c6e9b044c6f8ac44c6f8b044c6f8b444c6f8b844c6f8bc44c6f85844c6f85c44a6f8604438f7f8f406f1c800faf7d0fdd6f808054a46434686f85c40016b0131016306f5aa600af0abfc40b142f6491140f6c440caf62f21002292f106fa17b0bde8f0834ef6fb304ef6bf22c0f27700c121c0f2770275f1eaff2de9f0412ded048b96b00446d0e9181016461d468a1a70eb030223dab6eb010865eb00074ef20140c5f20b40b8eb000077f10200c4e918652ddb04f5a26032462b460af070fc002800f0a98045f6ca2140f64b10c9f283311122cde9008792f1c7f99ce004f5a26032462b460af05bfc002800f09480d4e9180342f6ab71c2f604215222029040f6f200cde90065039392f1aef983e0204600f0f6fa204620ee008a00f06dfb204632462b4620ee009a00f0e2fb20ee000a8ded108a8ded119a8ded120a4ff0030e39a107ad80ef500000202a4687ef504f91ed003f40f6ff7123fe400f35fe016fedee101a71fe4d0fa2ec037f01300ff00dc84046394670f7e5fb41ec100b9fed2e1b40f6ff7110aa20ee010b05f10c0704f16808b7eec00b8ded0a0aedee101aa16a10ee100a71fe4d0f92ed001f31ee600e0b9104a904f17402284671fe4d0f81ed001f19f732f694ed1a0a6c340021022313aa05eb81003e4690ed001a43f001e0204620ee011ab0ec013a96ed002a0c3622ee032a31ee021a0ff00bc802eb810001310437032980ed001ae2d113aa07ca88e8070016b0bdec048bbde8f08100bf00bf00bf00bf0000000001000000020000000300000095d626e80b2e113e2de9f0472ded068b9cb0b1eec0aab0ee409a15460f46b0ee608a04464ff0000a012600f10c08cdf808a08df80c60b4ee4aaaf1ee10fab0ee4a0a5ad60df11c098df820608df86c600df1080c04ae1aab404639464a468ded070a8ded1a0acde9006c01f091ffb4ee4aaaf1ee10fa8df864a043d6012004ae8df86c001aaa304639464346cdf800908ded1aaa07f0b6fb4ff0030e30469fed1d0a00bf15f8011bb0ee401a012908bf90ed001aa0ec011a0ff00dc040f6ff71edee101a18ee100a71fe4d0f96ed001f31ee600e31fe4d8f89ed001f99ed001f94ed003f00ef420d71fe4d0f84ed001f1cb0bdec068bbde8f087b0ee490a70f785fb9fe7b0ee490a70f780fbb0ee40aab4e700bf000000002de9f04381b02ded088b88b0b0ee608ab0ee409a9846154606460af0efff002848d0b0ee480a0af0ffff002842d096f8d80406f59c69199f012802bfc9e90058002086f8d80496f8e8043946189c012802bfc9e90458002086f8e80420460af0fdff00281cbfc6e92858c6e92a4796f85c00d0b330462a464346fff755fe96ed1a0a96ed1c1a96f82e10002030ee410a9fedad1a06f16807012981ee001a0690b1ee412a8ded071a8ded052a29d128ee08aa40e006f5a6602a4643460af0c7fa002800f0ec80b7eec90ab7eec81a49f2f47140f2fc408ded000bc3f27661a2228ded021b92f118f8d9e0b0ee490a0af05bfbd6ed040a30462a464346fff76cfdcde096ed091a96f82d0021ee01aa012811d1b0ee40ba06f1c800b0ee490afaf7c3fb96f8e100012803d196ed3a0a80fe0aaab0ee4b0a0af015fbd6e92810012279ee400a8df8122040f20112adf8102051ea000312d0691a68eb000081eae07180eae072b1ebe07162ebe07045f60102cff64702891a70f10d0002d300208df8120005a904aa3846b0ee4a0afff7b0fe96f8f804002848d196ed130ad6f8fc04b4ee408af1ee10fa9cbf0130c6f8fc04316d884201da002030e096f84400012186f8f81401280bd100204df2e111c9e90000c8f2762140f6aa60002291f19cff96f8450001280ed1306f40f6f931c3f249010022c9f82000012086f8040540f6ea6091f189ff48f2370140f6aa50c4f2f171002291f180ff96f8f80496f8e814002908bf5feac0703fd030462a46434600f07cfa98bb002406f264404121c6f8a844c6e9b044c6f8ac44c6f8b044c6f8b444c6f8b844c6f8bc44c6f85844c6f85c44a6f8604438f72ef206f1c800faf706fbd6f808052a46434686f85c40016b0131016306f5aa600af0e1f938b142f6491140f6c440caf62f21002222e708b0bdec088b01b0bde8f0832846414670f7a1f941ec180bd9e9040170f79bf996ed120a41ec110bb7eec00a9fed1d2ba0ee021bb4ee481bf1ee10faa6d896f84400012186f8f81401280bd100204df2e111c9e90000c8f2762140f6aa60002291f10bff96f8450001280ed1306f40f6f931c3f249010022c9f82000012086f8040540f6ea6091f1f8fe44f6976140f65260c8f26911002291f1effe76e700bf00bf0000000065cdcd413e15044690f85c007047d4d410b50446702290ed1a0a90f8040594f84510c0074ff0700018bf4ff4a060002950ea1280204490ed001a30ee410a0af0eff994ed2e1a30ee410a10bd80b590ed1a0a0af0e5f980bd90f8461090f8f80460ea010000f001007047d4d42de9f04f81b02ded028b90b090f8981000f1740890ed0e8a059009b3032406ae00274ff07e590df13c0a4ff0010b45460320424603210323c6e90077b760cdf83c90cde9005bcde9026bcdf810a023f797f5
+  - Name:            .ARM.attributes
+    Type:            SHT_ARM_ATTRIBUTES
+    Content:         4140000000616561626900013600000043322E30390005636F727465782D6D3300060A074D080009020E0011011204140115001703180119011A021E0622012601
+Symbols:
+  - Name:            foo
+    Type:            STT_FUNC
+    Section:         .text1
+    Value:           0x1E008
+    Size:            436
+  - Name:            bar
+    Type:            STT_FUNC
+    Section:         .text2
+    Value:           0x4F9A02
+    Size:            2776
diff --git a/llvm/test/tools/llvm-profgen/etm-arch.test b/llvm/test/tools/llvm-profgen/etm-arch.test
new file mode 100644
index 0000000000000..31bdef63e0205
--- /dev/null
+++ b/llvm/test/tools/llvm-profgen/etm-arch.test
@@ -0,0 +1,81 @@
+; REQUIRES: opencsd
+; RUN: split-file %s %t
+; RUN: printf '\x80' > %t.etm
+
+; Test 1: ARM binary with Cortex-M attributes should pass.
+; RUN: yaml2obj %t/arm-valid.yaml -o %t.arm.valid.bin
+; RUN: llvm-profgen --etm=%t.etm --binary=%t.arm.valid.bin --output=%t.out 2>&1 | FileCheck %s --check-prefix=ARM-VALID
+
+; Test 2: ARM binary without Cortex-M attributes should fail.
+; RUN: yaml2obj %t/arm-no-attr.yaml -o %t.arm.noattr.bin
+; RUN: not llvm-profgen --etm=%t.etm --binary=%t.arm.noattr.bin --output=%t.out 2>&1 | FileCheck %s --check-prefix=ARM-NOT-VALID
+
+; Test 3: Non-ARM binary (X86) should fail.
+; RUN: yaml2obj %t/x86.yaml -o %t.x86.bin
+; RUN: not llvm-profgen --etm=%t.etm --binary=%t.x86.bin --output=%t.out 2>&1 | FileCheck %s --check-prefix=X86
+
+; ARM-VALID-NOT: OpenCSD: Unsupported processor architecture
+
+; ARM-NOT-VALID: OpenCSD: Unsupported processor architecture. Only microcontroller-class (Cortex-M) is currently supported.
+
+; X86: OpenCSD: Unsupported processor architecture. Only microcontroller-class (Cortex-M) is currently supported.
+
+;--- arm-valid.yaml
+--- !ELF
+FileHeader:
+  Class:           ELFCLASS32
+  Data:            ELFDATA2LSB
+  Type:            ET_DYN
+  Machine:         EM_ARM
+ProgramHeaders:
+  - Type:            PT_LOAD
+    Flags:           [ PF_X, PF_R ]
+    VAddr:           0x1000
+Sections:
+  - Name:            .text
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_ALLOC, SHF_EXECINSTR ]
+    Address:         0x1000
+    AddressAlign:    0x4
+    Content:         C3
+  - Name:            .ARM.attributes
+    Type:            SHT_ARM_ATTRIBUTES
+    Content:         4140000000616561626900013600000043322E30390005636F727465782D6D3300060A074D080009020E0011011204140115001703180119011A021E0622012601
+
+;--- arm-no-attr.yaml
+--- !ELF
+FileHeader:
+  Class:           ELFCLASS32
+  Data:            ELFDATA2LSB
+  Type:            ET_DYN
+  Machine:         EM_ARM
+ProgramHeaders:
+  - Type:            PT_LOAD
+    Flags:           [ PF_X, PF_R ]
+    VAddr:           0x1000
+Sections:
+  - Name:            .text
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_ALLOC, SHF_EXECINSTR ]
+    Address:         0x1000
+    AddressAlign:    0x4
+    Content:         C3
+
+;--- x86.yaml
+--- !ELF
+FileHeader:
+  Class:           ELFCLASS64
+  Data:            ELFDATA2LSB
+  Type:            ET_DYN
+  Machine:         EM_X86_64
+ProgramHeaders:
+  - Type:            PT_LOAD
+    Flags:           [ PF_X, PF_R ]
+    VAddr:           0x1000
+Sections:
+  - Name:            .text
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_ALLOC, SHF_EXECINSTR ]
+    Address:         0x1000
+    AddressAlign:    0x4
+    Content:         C3
diff --git a/llvm/test/tools/llvm-profgen/etm-opencsd.test b/llvm/test/tools/llvm-profgen/etm-opencsd.test
new file mode 100644
index 0000000000000..0de28a01f07c5
--- /dev/null
+++ b/llvm/test/tools/llvm-profgen/etm-opencsd.test
@@ -0,0 +1,15 @@
+; REQUIRES: opencsd
+; RUN: yaml2obj %S/Inputs/etm-opencsd.yaml -o %t.bin
+
+; Verify ETM trace decoding using OpenCSD with inlined trace data.
+; RUN: printf "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x01\x00\x83\x01\x9a\x4f\x00\x00\x96\x81\x9a\xfc\xfe\xfa\xf8\xfd\xfd\xd6\xfd\xfd\xc1" > %t.trace
+
+; Run the generator to produce the output file.
+; RUN: llvm-profgen --etm=%t.trace --binary=%t.bin --output=%t.prof --format=text
+; RUN: ls %t.prof
+
+; Verify error handling when no synchronization header (0x80) is found.
+; RUN: printf "\x00\x00\x00" > %t.nosync
+; RUN: not llvm-profgen --etm=%t.nosync --binary=%t.bin --output=%t2.prof 2>&1 | FileCheck %s --check-prefix=NOSYNC
+
+; NOSYNC: error: No synchronization header (0x80) found in the bitstream.
diff --git a/llvm/test/tools/llvm-profgen/lit.local.cfg b/llvm/test/tools/llvm-profgen/lit.local.cfg
index c628a80ff368d..ec7d3d52b8215 100644
--- a/llvm/test/tools/llvm-profgen/lit.local.cfg
+++ b/llvm/test/tools/llvm-profgen/lit.local.cfg
@@ -5,3 +5,13 @@ config.suffixes = [".test", ".ll", ".s", ".yaml"]
 
 if not "X86" in config.root.targets:
     config.unsupported = True
+
+llvm_profgen = lit.util.which('llvm-profgen')
+if llvm_profgen:
+    try:
+        result = subprocess.run([llvm_profgen, '--etm=/dev/null'], capture_output=True, text=True)
+        if "OpenCSD support was not found" not in result.stderr:
+            config.available_features.add('opencsd')
+    except Exception as e:
+        pass
+
diff --git a/llvm/tools/llvm-profgen/CMakeLists.txt b/llvm/tools/llvm-profgen/CMakeLists.txt
index 8d13bbb2bdb14..e42af2d8a6330 100644
--- a/llvm/tools/llvm-profgen/CMakeLists.txt
+++ b/llvm/tools/llvm-profgen/CMakeLists.txt
@@ -25,3 +25,23 @@ add_llvm_tool(llvm-profgen
   ProfileGenerator.cpp
   MissingFrameInferrer.cpp
   )
+
+set(LLVM_ENABLE_OPENCSD "AUTO" CACHE STRING "Enable OpenCSD support in llvm-profgen")
+
+if(NOT LLVM_ENABLE_OPENCSD STREQUAL "OFF")
+  find_library(OPENCSD_LIB NAMES opencsd_c_api opencsd)
+  find_path(OPENCSD_INCLUDE NAMES opencsd/ocsd_if_types.h)
+
+  if(OPENCSD_LIB AND OPENCSD_INCLUDE)
+    target_compile_definitions(llvm-profgen PRIVATE HAVE_OPENCSD)
+    target_include_directories(llvm-profgen PRIVATE ${OPENCSD_INCLUDE})
+    target_link_libraries(llvm-profgen PRIVATE ${OPENCSD_LIB})
+    message(STATUS "llvm-profgen: OpenCSD support enabled.")
+  else()
+    if(LLVM_ENABLE_OPENCSD STREQUAL "ON")
+      message(FATAL_ERROR "OpenCSD enabled but library or headers not found.")
+    else()
+      message(STATUS "llvm-profgen: OpenCSD not found; ETM decoding support disabled.")
+    endif()
+  endif()
+endif()
diff --git a/llvm/tools/llvm-profgen/PerfReader.cpp b/llvm/tools/llvm-profgen/PerfReader.cpp
index 1dc59321fd91f..f0631301c3a0a 100644
--- a/llvm/tools/llvm-profgen/PerfReader.cpp
+++ b/llvm/tools/llvm-profgen/PerfReader.cpp
@@ -16,6 +16,7 @@
 #include "llvm/Support/MemoryBuffer.h"
 #include "llvm/Support/Process.h"
 #include "llvm/Support/ToolOutputFile.h"
+#include "llvm/TargetParser/Triple.h"
 
 #define DEBUG_TYPE "perf-reader"
 
@@ -354,6 +355,11 @@ PerfReaderBase::create(ProfiledBinary *Binary, PerfInputFile &PerfInput,
     return PerfReader;
   }
 
+  if (PerfInput.Format == PerfFormat::ETMFormat) {
+    PerfReader.reset(new ETMReader(Binary, PerfInput.InputFile));
+    return PerfReader;
+  }
+
   // For perf data input, we need to convert them into perf script first.
   // If this is a kernel perf file, there is no need for retrieving PIDs.
   if (PerfInput.Format == PerfFormat::PerfData)
@@ -1369,5 +1375,208 @@ void PerfScriptReader::parsePerfTraces() {
 
 SmallVector<CleanupInstaller, 2> PerfScriptReader::TempFileCleanups;
 
+void ETMReader::recordProcessedRange(uint64_t Start, uint64_t End,
+                                     uint64_t Count) {
+  if (!SampleCounters.empty()) {
+    auto &Counter = SampleCounters.begin()->second;
+    Counter.recordRangeCount(Start, End, Count);
+  }
+}
+
+#ifdef HAVE_OPENCSD
+ocsd_datapath_resp_t ETMReader::processTrace(
+    const void *PContext, const ocsd_trc_index_t /*IndexSOP*/,
+    const uint8_t /*TrcChanID*/, const ocsd_generic_trace_elem *Element) {
+  auto *Reader = static_cast<ETMReader *>(const_cast<void *>(PContext));
+  if (!Reader || !Element)
+    return OCSD_RESP_FATAL_SYS_ERR;
+
+  // Process instruction ranges reconstructed by the decoder.
+  if (Element->elem_type == OCSD_GEN_TRC_ELEM_INSTR_RANGE) {
+    uint64_t Start = Element->st_addr;
+    uint64_t End = Element->en_addr;
+
+    // OpenCSD ranges are exclusive at the end [Start, End).
+    // llvm-profgen range counters expect inclusive bounds [Start, End].
+    // Adjust the exclusive end address provided by OpenCSD to include
+    // the last executed instruction within the reported range.
+    if (End > Start)
+      Reader->recordProcessedRange(Start, End - 1, 1);
+  }
+
+  return OCSD_RESP_CONT;
+}
+
+// Iterate through the ELF program headers to collect all executable LOAD
+// segments. These are registered as a single transaction to the OpenCSD memory
+// manager to prevent overlap/collision errors between different memory
+// regions.
+void ETMReader::mapELFSegments(dcd_tree_handle_t DcdTree,
+                               ProfiledBinary &Binary) {
+  std::string ExePath = Binary.getPath().str();
+
+  auto BinaryOrErr = object::createBinary(Binary.getPath());
+  if (!BinaryOrErr)
+    exitWithError("Failed to open binary for ETM mapping", Binary.getPath());
+
+  object::Binary &SourceBin = *BinaryOrErr.get().getBinary();
+  auto *ElfBase = dyn_cast<object::ELFObjectFileBase>(&SourceBin);
+  if (!ElfBase)
+    return;
+
+  std::vector<ocsd_file_mem_region_t> Regions;
+  auto ProcessHeaders = [&](const auto &ElfFile) {
+    auto ProgramHeaders = ElfFile.program_headers();
+    if (!ProgramHeaders)
+      return;
+
+    for (const auto &Phdr : *ProgramHeaders) {
+      // Only map segments that are loadable and executable (code sections).
+      if (Phdr.p_type == llvm::ELF::PT_LOAD &&
+          (Phdr.p_flags & llvm::ELF::PF_X)) {
+        ocsd_file_mem_region_t Region;
+        memset(&Region, 0, sizeof(Region));
+        Region.start_address = (uint64_t)Phdr.p_vaddr;
+        Region.file_offset = (uint64_t)Phdr.p_offset;
+        Region.region_size = (uint64_t)Phdr.p_filesz;
+        Regions.push_back(Region);
+      }
+    }
+  };
+
+  if (auto *O = dyn_cast<object::ELF32LEObjectFile>(ElfBase))
+    ProcessHeaders(O->getELFFile());
+  else if (auto *O = dyn_cast<object::ELF64LEObjectFile>(ElfBase))
+    ProcessHeaders(O->getELFFile());
+  else if (auto *O = dyn_cast<object::ELF32BEObjectFile>(ElfBase))
+    ProcessHeaders(O->getELFFile());
+  else if (auto *O = dyn_cast<object::ELF64BEObjectFile>(ElfBase))
+    ProcessHeaders(O->getELFFile());
+
+  if (!Regions.empty()) {
+    // Single transaction mapping to avoid overlap errors.
+    if (ocsd_dt_add_binfile_region_mem_acc(
+            DcdTree, Regions.data(), (uint32_t)Regions.size(),
+            OCSD_MEM_SPACE_ANY, ExePath.c_str()) != 0) {
+      exitWithError("OpenCSD: Failed to map ELF executable segments.");
+    }
+  }
+}
+
+// Configure the decoder for microcontroller-class targets based on the binary's
+// architecture.
+void ETMReader::setupDecoder(dcd_tree_handle_t DcdTree,
+                             ProfiledBinary &Binary) {
+  ocsd_etmv4_cfg Config;
+  memset(&Config, 0, sizeof(Config));
+
+  const Triple &BinaryTriple = Binary.getTriple();
+  // Identify microcontroller-class hardware (Cortex-M) by checking the
+  // architecture family or the instruction set mode in the target triple.
+  if (BinaryTriple.isArmMClass() || BinaryTriple.isThumb()) {
+    Config.arch_ver = ARCH_V8;
+    Config.core_prof = profile_CortexM;
+  } else {
+    // Application-class (Cortex-A) support might be added in the future.
+    exitWithError(
+        "OpenCSD: Unsupported processor architecture. "
+        "Only microcontroller-class (Cortex-M) is currently supported.");
+  }
+
+  // Manually associate this decoder with Trace ID 0x10 to enable immediate
+  // stream processing without waiting for hardware synchronization packets.
+  uint8_t CSID = 0x10;
+  Config.reg_traceidr = CSID;
+
+  // Enable full instruction-level decoding to reconstruct the complete program
+  // flow from raw trace packets and the provided binary.
+  uint32_t Flags = OCSD_CREATE_FLG_FULL_DECODER | OCSD_OPFLG_CHK_RANGE_CONTINUE;
+
+  if (ocsd_dt_create_decoder(DcdTree, OCSD_BUILTIN_DCD_ETMV4I, Flags,
+                             (void *)&Config, &CSID) != 0)
+    exitWithError("OpenCSD: Failed to initialize the instruction decoder.");
+}
+
+void ETMReader::parsePerfTraces() {
+  auto BufferOrErr = MemoryBuffer::getFile(PerfTraceFile);
+  if (std::error_code EC = BufferOrErr.getError())
+    exitWithError("Could not open ETM trace file: " + EC.message());
+
+  ArrayRef<uint8_t> Data(
+      reinterpret_cast<const uint8_t *>((*BufferOrErr)->getBufferStart()),
+      (*BufferOrErr)->getBufferSize());
+
+  // There is no context for ETM instruction traces.
+  // Initialize the SampleCounters map with a single empty context key
+  // to aggregate all instruction hits into a global bucket.
+  auto Key = std::make_shared<StringBasedCtxKey>();
+  SampleCounters.emplace(Hashable<ContextKey>(Key), SampleCounter());
+
+  // The protocol utilizes a 0x80 byte as an initial synchronization header.
+  // Perform a manual search for this sync point to discard any leading
+  // padding or truncated packets before decoding begins.
+  size_t StartIdx = 0;
+  while (StartIdx < Data.size() && Data[StartIdx] != 0x80)
+    StartIdx++;
+  if (StartIdx >= Data.size())
+    exitWithError("No synchronization header (0x80) found in the bitstream.");
+  ArrayRef<uint8_t> TraceSlice = Data.slice(StartIdx);
+
+  dcd_tree_handle_t DcdTree = ocsd_create_dcd_tree(OCSD_TRC_SRC_SINGLE, 0);
+  if (!DcdTree)
+    exitWithError("Failed to create OpenCSD decoder tree.");
+
+  // Configure and initialize the instruction-level decoder.
+  setupDecoder(DcdTree, *Binary);
+
+  // Extract and map executable segments from the ELF binary.
+  mapELFSegments(DcdTree, *Binary);
+
+  // Register the high-level packet callback. The 'processTrace' function
+  // will be invoked for every decoded instruction range.
+  ocsd_dt_set_gen_elem_outfn(DcdTree, processTrace, this);
+
+  // Initial reset to prime the decoder.
+  ocsd_dt_process_data(DcdTree, OCSD_OP_RESET, 0, 0, nullptr, nullptr);
+
+  const uint8_t *DataPtr = TraceSlice.data();
+  uint32_t TotalSize = TraceSlice.size();
+  uint32_t Processed = 0;
+
+  // Core Decoding Loop.
+  while (Processed < TotalSize) {
+    uint32_t Consumed = 0;
+    uint32_t Remaining = TotalSize - Processed;
+    ocsd_datapath_resp_t Response =
+        ocsd_dt_process_data(DcdTree, OCSD_OP_DATA, Processed, Remaining,
+                             DataPtr + Processed, &Consumed);
+
+    if (Response == OCSD_RESP_WAIT) {
+      // Decoder buffers are full; flush to drain internal states.
+      ocsd_dt_process_data(DcdTree, OCSD_OP_FLUSH, 0, 0, nullptr, nullptr);
+    } else if (Consumed == 0 && Processed < TotalSize) {
+      // Decoder stalled; skip byte and reset to find next sync point.
+      Processed++;
+      ocsd_dt_process_data(DcdTree, OCSD_OP_RESET, 0, 0, nullptr, nullptr);
+    } else {
+      // Successfully consumed bytes of the bitstream.
+      Processed += Consumed;
+    }
+
+    if (Response >= OCSD_RESP_FATAL_INVALID_DATA)
+      break;
+  }
+
+  // Finalize the decoding session by flushing the EOT (End of Trace) marker.
+  ocsd_dt_process_data(DcdTree, OCSD_OP_EOT, 0, 0, nullptr, nullptr);
+  // Deallocate the decoder tree resources.
+  ocsd_destroy_dcd_tree(DcdTree);
+}
+#else
+void ETMReader::parsePerfTraces() {
+  exitWithError("OpenCSD support was not found or enabled.");
+}
+#endif
+
 } // end namespace sampleprof
 } // end namespace llvm
diff --git a/llvm/tools/llvm-profgen/PerfReader.h b/llvm/tools/llvm-profgen/PerfReader.h
index 2a4c7594d3a93..15b2aced0995e 100644
--- a/llvm/tools/llvm-profgen/PerfReader.h
+++ b/llvm/tools/llvm-profgen/PerfReader.h
@@ -18,6 +18,10 @@
 #include <fstream>
 #include <map>
 
+#ifdef HAVE_OPENCSD
+#include "opencsd/c_api/opencsd_c_api.h"
+#endif
+
 namespace llvm {
 
 class CleanupInstaller;
@@ -63,7 +67,7 @@ enum PerfFormat {
   PerfData = 1,            // Raw linux perf.data.
   PerfScript = 2,          // Perf script create by `perf script` command.
   UnsymbolizedProfile = 3, // Unsymbolized profile generated by llvm-profgen.
-
+  ETMFormat = 4,           // Raw ETM format.
 };
 
 // The type of perfscript content.
@@ -748,6 +752,31 @@ class UnsymbolizedProfileReader : public PerfReaderBase {
   std::unordered_set<std::string> ContextStrSet;
 };
 
+class ETMReader : public PerfReaderBase {
+public:
+  ETMReader(ProfiledBinary *Binary, StringRef TraceFile)
+      : PerfReaderBase(Binary, TraceFile) {}
+  void parsePerfTraces() override;
+
+private:
+  void recordProcessedRange(uint64_t Start, uint64_t End, uint64_t Count);
+
+#ifdef HAVE_OPENCSD
+  // Trace processing and Callback handling.
+  static ocsd_datapath_resp_t
+  processTrace(const void *PContext, const ocsd_trc_index_t /*IndexSOP*/,
+               const uint8_t /*TrcChanID*/,
+               const ocsd_generic_trace_elem *Element);
+
+  // Initialize the decoder for microcontroller-class targets.
+  void setupDecoder(dcd_tree_handle_t DcdTree, ProfiledBinary &Binary);
+
+  // Identify and register executable ELF segments with the OpenCSD decoder
+  // to provide opcode access for different memory regions.
+  void mapELFSegments(dcd_tree_handle_t DcdTree, ProfiledBinary &Binary);
+#endif
+};
+
 } // end namespace sampleprof
 } // end namespace llvm
 
diff --git a/llvm/tools/llvm-profgen/ProfiledBinary.h b/llvm/tools/llvm-profgen/ProfiledBinary.h
index a437c57b8c0d7..9fc42a1036ed1 100644
--- a/llvm/tools/llvm-profgen/ProfiledBinary.h
+++ b/llvm/tools/llvm-profgen/ProfiledBinary.h
@@ -431,6 +431,7 @@ class ProfiledBinary {
 
   StringRef getPath() const { return Path; }
   StringRef getName() const { return llvm::sys::path::filename(Path); }
+  const Triple &getTriple() const { return TheTriple; }
   uint64_t getBaseAddress() const { return BaseAddress; }
   void setBaseAddress(uint64_t Address) { BaseAddress = Address; }
 
diff --git a/llvm/tools/llvm-profgen/llvm-profgen.cpp b/llvm/tools/llvm-profgen/llvm-profgen.cpp
index 0ab6c6f87382d..5b75b2498fb88 100644
--- a/llvm/tools/llvm-profgen/llvm-profgen.cpp
+++ b/llvm/tools/llvm-profgen/llvm-profgen.cpp
@@ -79,6 +79,10 @@ static cl::opt<std::string> DataAccessProfileFilename(
              "-D`) consisting of memory access events."),
     cl::cat(ProfGenCategory));
 
+static cl::opt<std::string> ETMPath("etm", cl::value_desc("etm"),
+                                    cl::desc("Path of raw ETM trace file"),
+                                    cl::cat(ProfGenCategory));
+
 // Validate the command line input.
 static void validateCommandLine() {
   // Allow the missing perfscript if we only use to show binary disassembly.
@@ -89,15 +93,18 @@ static void validateCommandLine() {
     bool HasUnsymbolizedProfile =
         UnsymbolizedProfFilename.getNumOccurrences() > 0;
     bool HasSampleProfile = SampleProfFilename.getNumOccurrences() > 0;
-    uint16_t S =
-        HasPerfData + HasPerfScript + HasUnsymbolizedProfile + HasSampleProfile;
+    bool HasEtm = ETMPath.getNumOccurrences() > 0;
+    uint16_t S = HasPerfData + HasPerfScript + HasUnsymbolizedProfile +
+                 HasSampleProfile + HasEtm;
     if (S != 1) {
       std::string Msg =
-          S > 1
-              ? "`--perfscript`, `--perfdata` and `--unsymbolized-profile` "
-                "cannot be used together."
-              : "Perf input file is missing, please use one of `--perfscript`, "
-                "`--perfdata` and `--unsymbolized-profile` for the input.";
+          S > 1 ? "Only one of `--perfscript`, `--perfdata`, "
+                  "`--unsymbolized-profile`, "
+                  "`--sample-profile` or `--etm` can be used."
+                : "Perf input file is missing. Please provide one of "
+                  "`--perfscript`, "
+                  "`--perfdata`, `--unsymbolized-profile`, `--sample-profile`, "
+                  "`--etm`.";
       exitWithError(Msg);
     }
 
@@ -112,6 +119,7 @@ static void validateCommandLine() {
     CheckFileExists(HasPerfScript, PerfScriptFilename);
     CheckFileExists(HasUnsymbolizedProfile, UnsymbolizedProfFilename);
     CheckFileExists(HasSampleProfile, SampleProfFilename);
+    CheckFileExists(HasEtm, ETMPath);
   }
 
   if (!llvm::sys::fs::exists(BinaryPath)) {
@@ -139,6 +147,9 @@ static PerfInputFile getPerfInputFile() {
   } else if (UnsymbolizedProfFilename.getNumOccurrences()) {
     File.InputFile = UnsymbolizedProfFilename;
     File.Format = PerfFormat::UnsymbolizedProfile;
+  } else if (ETMPath.getNumOccurrences()) {
+    File.InputFile = ETMPath;
+    File.Format = PerfFormat::ETMFormat;
   }
   return File;
 }

>From 7a14d67ed95ce3b4a38bdc00246c8a33e773d211 Mon Sep 17 00:00:00 2001
From: Gulfem Savrun Yeniceri <gulfem at google.com>
Date: Mon, 13 Apr 2026 19:56:06 -0700
Subject: [PATCH 2/6] [llvm-profgen] Move ETM decoding logic to a library

This patch refactors the ETM parsing logic from llvm-profgen
into a new ETMTraceDecoder component within lib/ProfileData.
By providing a shared decoder, other tools like BOLT can
leverage this library to parse raw ETM traces directly.
---
 .../llvm/ProfileData/ETMTraceDecoder.h        |  39 ++++
 llvm/lib/ProfileData/CMakeLists.txt           |  21 ++
 llvm/lib/ProfileData/ETMTraceDecoder.cpp      | 220 ++++++++++++++++++
 llvm/tools/llvm-profgen/CMakeLists.txt        |  20 --
 llvm/tools/llvm-profgen/PerfReader.cpp        | 179 ++------------
 llvm/tools/llvm-profgen/PerfReader.h          |  21 --
 6 files changed, 295 insertions(+), 205 deletions(-)
 create mode 100644 llvm/include/llvm/ProfileData/ETMTraceDecoder.h
 create mode 100644 llvm/lib/ProfileData/ETMTraceDecoder.cpp

diff --git a/llvm/include/llvm/ProfileData/ETMTraceDecoder.h b/llvm/include/llvm/ProfileData/ETMTraceDecoder.h
new file mode 100644
index 0000000000000..10f039311e4d2
--- /dev/null
+++ b/llvm/include/llvm/ProfileData/ETMTraceDecoder.h
@@ -0,0 +1,39 @@
+//===-- ETMTraceDecoder.h - ETM Trace Decoder -------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_PROFILEDATA_ETMTRACEDECODER_H
+#define LLVM_PROFILEDATA_ETMTRACEDECODER_H
+
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+#include <cstdint>
+#include <memory>
+
+namespace llvm {
+
+class ETMDecoder {
+public:
+  virtual ~ETMDecoder() = default;
+
+  class Callback {
+  public:
+    virtual ~Callback() = default;
+    virtual void processInstructionRange(uint64_t Start, uint64_t End) = 0;
+  };
+
+  static Expected<std::unique_ptr<ETMDecoder>> create(StringRef BinaryPath);
+
+  virtual Error processTrace(ArrayRef<uint8_t> TraceData,
+                             Callback &TraceCallback) = 0;
+};
+
+} // namespace llvm
+
+#endif // LLVM_PROFILEDATA_ETMTRACEDECODER_H
diff --git a/llvm/lib/ProfileData/CMakeLists.txt b/llvm/lib/ProfileData/CMakeLists.txt
index c2366dc0ae03e..6259f5af897bf 100644
--- a/llvm/lib/ProfileData/CMakeLists.txt
+++ b/llvm/lib/ProfileData/CMakeLists.txt
@@ -1,5 +1,6 @@
 add_llvm_component_library(LLVMProfileData
   DataAccessProf.cpp
+  ETMTraceDecoder.cpp
   GCOV.cpp
   IndexedMemProfData.cpp
   InstrProf.cpp
@@ -39,4 +40,24 @@ add_llvm_component_library(LLVMProfileData
   TargetParser
   )
 
+set(LLVM_ENABLE_OPENCSD "AUTO" CACHE STRING "Enable OpenCSD support in LLVMProfileData")
+
+if(NOT LLVM_ENABLE_OPENCSD STREQUAL "OFF")
+  find_library(OPENCSD_LIB NAMES opencsd_c_api opencsd)
+  find_path(OPENCSD_INCLUDE NAMES opencsd/ocsd_if_types.h)
+
+  if(OPENCSD_LIB AND OPENCSD_INCLUDE)
+    target_compile_definitions(LLVMProfileData PRIVATE HAVE_OPENCSD)
+    target_include_directories(LLVMProfileData PRIVATE ${OPENCSD_INCLUDE})
+    target_link_libraries(LLVMProfileData PRIVATE ${OPENCSD_LIB})
+    message(STATUS "LLVMProfileData: OpenCSD support enabled.")
+  else()
+    if(LLVM_ENABLE_OPENCSD STREQUAL "ON")
+      message(FATAL_ERROR "OpenCSD enabled but library or headers not found.")
+    else()
+      message(STATUS "LLVMProfileData: OpenCSD not found; ETM decoding support disabled.")
+    endif()
+  endif()
+endif()
+
 add_subdirectory(Coverage)
diff --git a/llvm/lib/ProfileData/ETMTraceDecoder.cpp b/llvm/lib/ProfileData/ETMTraceDecoder.cpp
new file mode 100644
index 0000000000000..7b854220f9f69
--- /dev/null
+++ b/llvm/lib/ProfileData/ETMTraceDecoder.cpp
@@ -0,0 +1,220 @@
+//===-- ETMTraceDecoder.cpp - ETM Trace Decoder -----------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/ProfileData/ETMTraceDecoder.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Object/ELFObjectFile.h"
+#include "llvm/Object/ObjectFile.h"
+#include "llvm/Support/Error.h"
+
+#ifdef HAVE_OPENCSD
+#include "opencsd/c_api/opencsd_c_api.h"
+
+namespace llvm {
+
+namespace {
+class ETMDecoderImpl : public ETMDecoder {
+  dcd_tree_handle_t DcdTree = 0;
+  std::string BinaryPath;
+  Triple TargetTriple;
+
+  // Trace processing and Callback handling.
+  static ocsd_datapath_resp_t
+  processTrace(const void *PContext, const ocsd_trc_index_t /*IndexSOP*/,
+               const uint8_t /*TrcChanID*/,
+               const ocsd_generic_trace_elem *Element) {
+    auto *Decoder = static_cast<ETMDecoderImpl *>(const_cast<void *>(PContext));
+    if (!Decoder || !Element)
+      return OCSD_RESP_FATAL_SYS_ERR;
+
+    // Process instruction ranges reconstructed by the decoder.
+    if (Element->elem_type == OCSD_GEN_TRC_ELEM_INSTR_RANGE) {
+      uint64_t Start = Element->st_addr;
+      uint64_t End = Element->en_addr;
+      if (End > Start) {
+        // OpenCSD ranges are exclusive at the end [Start, End).
+        // llvm-profgen range counters expect inclusive bounds [Start, End].
+        // Adjust the exclusive end address provided by OpenCSD to include
+        // the last executed instruction within the reported range.
+        Decoder->CurrentCallback->processInstructionRange(Start, End - 1);
+      }
+    }
+    return OCSD_RESP_CONT;
+  }
+
+  Callback *CurrentCallback = nullptr;
+
+  // Iterate through the ELF program headers to collect all executable LOAD
+  // segments. These are registered as a single transaction to the OpenCSD
+  // memory manager to prevent overlap/collision errors between different
+  // memory regions.
+  Error mapELFSegments(dcd_tree_handle_t DcdTree, object::Binary &SourceBin) {
+    SmallVector<ocsd_file_mem_region_t, 4> Regions;
+    auto ProcessHeaders = [&](const auto &ElfFile) {
+      auto ProgramHeaders = ElfFile.program_headers();
+      if (!ProgramHeaders)
+        return;
+
+      for (const auto &Phdr : *ProgramHeaders) {
+        if (Phdr.p_type == llvm::ELF::PT_LOAD &&
+            (Phdr.p_flags & llvm::ELF::PF_X)) {
+          ocsd_file_mem_region_t Region{};
+          Region.start_address = (uint64_t)Phdr.p_vaddr;
+          Region.file_offset = (uint64_t)Phdr.p_offset;
+          Region.region_size = (uint64_t)Phdr.p_filesz;
+          Regions.push_back(Region);
+        }
+      }
+    };
+
+    if (auto *O = dyn_cast<object::ELF32LEObjectFile>(&SourceBin))
+      ProcessHeaders(O->getELFFile());
+    else if (auto *O = dyn_cast<object::ELF64LEObjectFile>(&SourceBin))
+      ProcessHeaders(O->getELFFile());
+    else if (auto *O = dyn_cast<object::ELF32BEObjectFile>(&SourceBin))
+      ProcessHeaders(O->getELFFile());
+    else if (auto *O = dyn_cast<object::ELF64BEObjectFile>(&SourceBin))
+      ProcessHeaders(O->getELFFile());
+
+    if (!Regions.empty()) {
+      if (ocsd_dt_add_binfile_region_mem_acc(
+              DcdTree, Regions.data(), (uint32_t)Regions.size(),
+              OCSD_MEM_SPACE_ANY, BinaryPath.c_str()) != 0) {
+        return createStringError(
+            inconvertibleErrorCode(),
+            "OpenCSD: Failed to map ELF executable segments.");
+      }
+    }
+    return Error::success();
+  }
+
+public:
+  ETMDecoderImpl(StringRef BinaryPath) : BinaryPath(BinaryPath.str()) {}
+  ~ETMDecoderImpl() override {
+    if (DcdTree)
+      // Deallocate the decoder tree resources.
+      ocsd_destroy_dcd_tree(DcdTree);
+  }
+
+  /// Initialize the decoder by auto-detecting the target architecture and
+  /// configuring the OpenCSD decoder.
+  Error initialize() {
+    auto BinaryOrErr = object::createBinary(BinaryPath);
+    if (!BinaryOrErr)
+      return BinaryOrErr.takeError();
+
+    object::Binary &SourceBin = *BinaryOrErr.get().getBinary();
+    auto *ElfBase = dyn_cast<object::ELFObjectFileBase>(&SourceBin);
+    if (!ElfBase)
+      return createStringError(inconvertibleErrorCode(),
+                               "OpenCSD: Unsupported binary format. Only ELF "
+                               "is currently supported.");
+
+    TargetTriple = ElfBase->makeTriple();
+
+    DcdTree = ocsd_create_dcd_tree(OCSD_TRC_SRC_SINGLE, 0);
+    if (!DcdTree)
+      return createStringError(inconvertibleErrorCode(),
+                               "Failed to create OpenCSD decoder tree.");
+
+    // Configure and initialize the instruction-level decoder.
+    ocsd_etmv4_cfg Config{};
+    // Initialize the decoder for microcontroller-class targets.
+    if (TargetTriple.isArmMClass() || TargetTriple.isThumb()) {
+      Config.arch_ver = ARCH_V8;
+      Config.core_prof = profile_CortexM;
+    } else {
+      return createStringError(
+          inconvertibleErrorCode(),
+          "OpenCSD: Unsupported processor architecture. Only "
+          "microcontroller-class (Cortex-M) is currently supported.");
+    }
+    uint8_t CSID = 0x10;
+    Config.reg_traceidr = CSID;
+    uint32_t Flags =
+        OCSD_CREATE_FLG_FULL_DECODER | OCSD_OPFLG_CHK_RANGE_CONTINUE;
+    if (ocsd_dt_create_decoder(DcdTree, OCSD_BUILTIN_DCD_ETMV4I, Flags,
+                               (void *)&Config, &CSID) != 0)
+      return createStringError(
+          inconvertibleErrorCode(),
+          "OpenCSD: Failed to initialize the instruction decoder.");
+
+    // Extract and map executable segments from the ELF binary.
+    if (Error E = mapELFSegments(DcdTree, SourceBin))
+      return E;
+
+    // Register the high-level packet callback. The 'processTrace' function
+    // will be invoked for every decoded instruction range.
+    ocsd_dt_set_gen_elem_outfn(DcdTree, processTrace, this);
+    return Error::success();
+  }
+
+  Error processTrace(ArrayRef<uint8_t> TraceData,
+                     Callback &TraceCallback) override {
+    CurrentCallback = &TraceCallback;
+    // Initial reset to prime the decoder.
+    ocsd_dt_process_data(DcdTree, OCSD_OP_RESET, 0, 0, nullptr, nullptr);
+
+    const uint8_t *DataPtr = TraceData.data();
+    uint32_t TotalSize = TraceData.size();
+    uint32_t Processed = 0;
+
+    // Core Decoding Loop.
+    while (Processed < TotalSize) {
+      uint32_t Consumed = 0;
+      uint32_t Remaining = TotalSize - Processed;
+      ocsd_datapath_resp_t Response =
+          ocsd_dt_process_data(DcdTree, OCSD_OP_DATA, Processed, Remaining,
+                               DataPtr + Processed, &Consumed);
+
+      if (Response == OCSD_RESP_WAIT) {
+        // Decoder buffers are full; flush to drain internal states.
+        ocsd_dt_process_data(DcdTree, OCSD_OP_FLUSH, 0, 0, nullptr, nullptr);
+      } else if (Consumed == 0 && Processed < TotalSize) {
+        // Decoder stalled; skip byte and reset to find next sync point.
+        Processed++;
+        ocsd_dt_process_data(DcdTree, OCSD_OP_RESET, 0, 0, nullptr, nullptr);
+      } else {
+        // Successfully consumed bytes of the bitstream.
+        Processed += Consumed;
+      }
+
+      if (Response >= OCSD_RESP_FATAL_INVALID_DATA)
+        return createStringError(inconvertibleErrorCode(),
+                                 "OpenCSD: Fatal decoding error.");
+    }
+
+    // Finalize the decoding session by flushing the EOT (End of Trace) marker.
+    ocsd_dt_process_data(DcdTree, OCSD_OP_EOT, 0, 0, nullptr, nullptr);
+    return Error::success();
+  }
+};
+} // namespace
+
+Expected<std::unique_ptr<ETMDecoder>> ETMDecoder::create(StringRef BinaryPath) {
+  auto Decoder =
+      std::unique_ptr<ETMDecoderImpl>(new ETMDecoderImpl(BinaryPath));
+  if (Error E = Decoder->initialize())
+    return std::move(E);
+  return std::unique_ptr<ETMDecoder>(std::move(Decoder));
+}
+
+} // namespace llvm
+
+#else // !HAVE_OPENCSD
+
+namespace llvm {
+
+Expected<std::unique_ptr<ETMDecoder>> ETMDecoder::create(StringRef BinaryPath) {
+  return createStringError(inconvertibleErrorCode(),
+                           "OpenCSD support was not found or enabled.");
+}
+
+} // namespace llvm
+
+#endif // HAVE_OPENCSD
diff --git a/llvm/tools/llvm-profgen/CMakeLists.txt b/llvm/tools/llvm-profgen/CMakeLists.txt
index e42af2d8a6330..8d13bbb2bdb14 100644
--- a/llvm/tools/llvm-profgen/CMakeLists.txt
+++ b/llvm/tools/llvm-profgen/CMakeLists.txt
@@ -25,23 +25,3 @@ add_llvm_tool(llvm-profgen
   ProfileGenerator.cpp
   MissingFrameInferrer.cpp
   )
-
-set(LLVM_ENABLE_OPENCSD "AUTO" CACHE STRING "Enable OpenCSD support in llvm-profgen")
-
-if(NOT LLVM_ENABLE_OPENCSD STREQUAL "OFF")
-  find_library(OPENCSD_LIB NAMES opencsd_c_api opencsd)
-  find_path(OPENCSD_INCLUDE NAMES opencsd/ocsd_if_types.h)
-
-  if(OPENCSD_LIB AND OPENCSD_INCLUDE)
-    target_compile_definitions(llvm-profgen PRIVATE HAVE_OPENCSD)
-    target_include_directories(llvm-profgen PRIVATE ${OPENCSD_INCLUDE})
-    target_link_libraries(llvm-profgen PRIVATE ${OPENCSD_LIB})
-    message(STATUS "llvm-profgen: OpenCSD support enabled.")
-  else()
-    if(LLVM_ENABLE_OPENCSD STREQUAL "ON")
-      message(FATAL_ERROR "OpenCSD enabled but library or headers not found.")
-    else()
-      message(STATUS "llvm-profgen: OpenCSD not found; ETM decoding support disabled.")
-    endif()
-  endif()
-endif()
diff --git a/llvm/tools/llvm-profgen/PerfReader.cpp b/llvm/tools/llvm-profgen/PerfReader.cpp
index f0631301c3a0a..be567a96dea12 100644
--- a/llvm/tools/llvm-profgen/PerfReader.cpp
+++ b/llvm/tools/llvm-profgen/PerfReader.cpp
@@ -11,6 +11,7 @@
 #include "ProfileGenerator.h"
 #include "llvm/ADT/SmallString.h"
 #include "llvm/DebugInfo/Symbolize/SymbolizableModule.h"
+#include "llvm/ProfileData/ETMTraceDecoder.h"
 #include "llvm/Support/FileSystem.h"
 #include "llvm/Support/LineIterator.h"
 #include "llvm/Support/MemoryBuffer.h"
@@ -1383,119 +1384,15 @@ void ETMReader::recordProcessedRange(uint64_t Start, uint64_t End,
   }
 }
 
-#ifdef HAVE_OPENCSD
-ocsd_datapath_resp_t ETMReader::processTrace(
-    const void *PContext, const ocsd_trc_index_t /*IndexSOP*/,
-    const uint8_t /*TrcChanID*/, const ocsd_generic_trace_elem *Element) {
-  auto *Reader = static_cast<ETMReader *>(const_cast<void *>(PContext));
-  if (!Reader || !Element)
-    return OCSD_RESP_FATAL_SYS_ERR;
-
-  // Process instruction ranges reconstructed by the decoder.
-  if (Element->elem_type == OCSD_GEN_TRC_ELEM_INSTR_RANGE) {
-    uint64_t Start = Element->st_addr;
-    uint64_t End = Element->en_addr;
-
-    // OpenCSD ranges are exclusive at the end [Start, End).
-    // llvm-profgen range counters expect inclusive bounds [Start, End].
-    // Adjust the exclusive end address provided by OpenCSD to include
-    // the last executed instruction within the reported range.
-    if (End > Start)
-      Reader->recordProcessedRange(Start, End - 1, 1);
-  }
-
-  return OCSD_RESP_CONT;
-}
-
-// Iterate through the ELF program headers to collect all executable LOAD
-// segments. These are registered as a single transaction to the OpenCSD memory
-// manager to prevent overlap/collision errors between different memory
-// regions.
-void ETMReader::mapELFSegments(dcd_tree_handle_t DcdTree,
-                               ProfiledBinary &Binary) {
-  std::string ExePath = Binary.getPath().str();
-
-  auto BinaryOrErr = object::createBinary(Binary.getPath());
-  if (!BinaryOrErr)
-    exitWithError("Failed to open binary for ETM mapping", Binary.getPath());
-
-  object::Binary &SourceBin = *BinaryOrErr.get().getBinary();
-  auto *ElfBase = dyn_cast<object::ELFObjectFileBase>(&SourceBin);
-  if (!ElfBase)
-    return;
-
-  std::vector<ocsd_file_mem_region_t> Regions;
-  auto ProcessHeaders = [&](const auto &ElfFile) {
-    auto ProgramHeaders = ElfFile.program_headers();
-    if (!ProgramHeaders)
-      return;
+class ETMCallback : public ETMDecoder::Callback {
+  ETMReader *Reader;
 
-    for (const auto &Phdr : *ProgramHeaders) {
-      // Only map segments that are loadable and executable (code sections).
-      if (Phdr.p_type == llvm::ELF::PT_LOAD &&
-          (Phdr.p_flags & llvm::ELF::PF_X)) {
-        ocsd_file_mem_region_t Region;
-        memset(&Region, 0, sizeof(Region));
-        Region.start_address = (uint64_t)Phdr.p_vaddr;
-        Region.file_offset = (uint64_t)Phdr.p_offset;
-        Region.region_size = (uint64_t)Phdr.p_filesz;
-        Regions.push_back(Region);
-      }
-    }
-  };
-
-  if (auto *O = dyn_cast<object::ELF32LEObjectFile>(ElfBase))
-    ProcessHeaders(O->getELFFile());
-  else if (auto *O = dyn_cast<object::ELF64LEObjectFile>(ElfBase))
-    ProcessHeaders(O->getELFFile());
-  else if (auto *O = dyn_cast<object::ELF32BEObjectFile>(ElfBase))
-    ProcessHeaders(O->getELFFile());
-  else if (auto *O = dyn_cast<object::ELF64BEObjectFile>(ElfBase))
-    ProcessHeaders(O->getELFFile());
-
-  if (!Regions.empty()) {
-    // Single transaction mapping to avoid overlap errors.
-    if (ocsd_dt_add_binfile_region_mem_acc(
-            DcdTree, Regions.data(), (uint32_t)Regions.size(),
-            OCSD_MEM_SPACE_ANY, ExePath.c_str()) != 0) {
-      exitWithError("OpenCSD: Failed to map ELF executable segments.");
-    }
+public:
+  ETMCallback(ETMReader *R) : Reader(R) {}
+  void processInstructionRange(uint64_t Start, uint64_t End) override {
+    Reader->recordProcessedRange(Start, End, 1);
   }
-}
-
-// Configure the decoder for microcontroller-class targets based on the binary's
-// architecture.
-void ETMReader::setupDecoder(dcd_tree_handle_t DcdTree,
-                             ProfiledBinary &Binary) {
-  ocsd_etmv4_cfg Config;
-  memset(&Config, 0, sizeof(Config));
-
-  const Triple &BinaryTriple = Binary.getTriple();
-  // Identify microcontroller-class hardware (Cortex-M) by checking the
-  // architecture family or the instruction set mode in the target triple.
-  if (BinaryTriple.isArmMClass() || BinaryTriple.isThumb()) {
-    Config.arch_ver = ARCH_V8;
-    Config.core_prof = profile_CortexM;
-  } else {
-    // Application-class (Cortex-A) support might be added in the future.
-    exitWithError(
-        "OpenCSD: Unsupported processor architecture. "
-        "Only microcontroller-class (Cortex-M) is currently supported.");
-  }
-
-  // Manually associate this decoder with Trace ID 0x10 to enable immediate
-  // stream processing without waiting for hardware synchronization packets.
-  uint8_t CSID = 0x10;
-  Config.reg_traceidr = CSID;
-
-  // Enable full instruction-level decoding to reconstruct the complete program
-  // flow from raw trace packets and the provided binary.
-  uint32_t Flags = OCSD_CREATE_FLG_FULL_DECODER | OCSD_OPFLG_CHK_RANGE_CONTINUE;
-
-  if (ocsd_dt_create_decoder(DcdTree, OCSD_BUILTIN_DCD_ETMV4I, Flags,
-                             (void *)&Config, &CSID) != 0)
-    exitWithError("OpenCSD: Failed to initialize the instruction decoder.");
-}
+};
 
 void ETMReader::parsePerfTraces() {
   auto BufferOrErr = MemoryBuffer::getFile(PerfTraceFile);
@@ -1522,61 +1419,15 @@ void ETMReader::parsePerfTraces() {
     exitWithError("No synchronization header (0x80) found in the bitstream.");
   ArrayRef<uint8_t> TraceSlice = Data.slice(StartIdx);
 
-  dcd_tree_handle_t DcdTree = ocsd_create_dcd_tree(OCSD_TRC_SRC_SINGLE, 0);
-  if (!DcdTree)
-    exitWithError("Failed to create OpenCSD decoder tree.");
-
-  // Configure and initialize the instruction-level decoder.
-  setupDecoder(DcdTree, *Binary);
-
-  // Extract and map executable segments from the ELF binary.
-  mapELFSegments(DcdTree, *Binary);
-
-  // Register the high-level packet callback. The 'processTrace' function
-  // will be invoked for every decoded instruction range.
-  ocsd_dt_set_gen_elem_outfn(DcdTree, processTrace, this);
-
-  // Initial reset to prime the decoder.
-  ocsd_dt_process_data(DcdTree, OCSD_OP_RESET, 0, 0, nullptr, nullptr);
-
-  const uint8_t *DataPtr = TraceSlice.data();
-  uint32_t TotalSize = TraceSlice.size();
-  uint32_t Processed = 0;
-
-  // Core Decoding Loop.
-  while (Processed < TotalSize) {
-    uint32_t Consumed = 0;
-    uint32_t Remaining = TotalSize - Processed;
-    ocsd_datapath_resp_t Response =
-        ocsd_dt_process_data(DcdTree, OCSD_OP_DATA, Processed, Remaining,
-                             DataPtr + Processed, &Consumed);
-
-    if (Response == OCSD_RESP_WAIT) {
-      // Decoder buffers are full; flush to drain internal states.
-      ocsd_dt_process_data(DcdTree, OCSD_OP_FLUSH, 0, 0, nullptr, nullptr);
-    } else if (Consumed == 0 && Processed < TotalSize) {
-      // Decoder stalled; skip byte and reset to find next sync point.
-      Processed++;
-      ocsd_dt_process_data(DcdTree, OCSD_OP_RESET, 0, 0, nullptr, nullptr);
-    } else {
-      // Successfully consumed bytes of the bitstream.
-      Processed += Consumed;
-    }
-
-    if (Response >= OCSD_RESP_FATAL_INVALID_DATA)
-      break;
-  }
+  auto DecoderOrErr = ETMDecoder::create(Binary->getPath());
+  if (!DecoderOrErr)
+    exitWithError(toString(DecoderOrErr.takeError()));
+  auto Decoder = std::move(*DecoderOrErr);
 
-  // Finalize the decoding session by flushing the EOT (End of Trace) marker.
-  ocsd_dt_process_data(DcdTree, OCSD_OP_EOT, 0, 0, nullptr, nullptr);
-  // Deallocate the decoder tree resources.
-  ocsd_destroy_dcd_tree(DcdTree);
+  ETMCallback CB(this);
+  if (Error E = Decoder->processTrace(TraceSlice, CB))
+    exitWithError(toString(std::move(E)));
 }
-#else
-void ETMReader::parsePerfTraces() {
-  exitWithError("OpenCSD support was not found or enabled.");
-}
-#endif
 
 } // end namespace sampleprof
 } // end namespace llvm
diff --git a/llvm/tools/llvm-profgen/PerfReader.h b/llvm/tools/llvm-profgen/PerfReader.h
index 15b2aced0995e..ca07a44cd83bd 100644
--- a/llvm/tools/llvm-profgen/PerfReader.h
+++ b/llvm/tools/llvm-profgen/PerfReader.h
@@ -18,10 +18,6 @@
 #include <fstream>
 #include <map>
 
-#ifdef HAVE_OPENCSD
-#include "opencsd/c_api/opencsd_c_api.h"
-#endif
-
 namespace llvm {
 
 class CleanupInstaller;
@@ -757,24 +753,7 @@ class ETMReader : public PerfReaderBase {
   ETMReader(ProfiledBinary *Binary, StringRef TraceFile)
       : PerfReaderBase(Binary, TraceFile) {}
   void parsePerfTraces() override;
-
-private:
   void recordProcessedRange(uint64_t Start, uint64_t End, uint64_t Count);
-
-#ifdef HAVE_OPENCSD
-  // Trace processing and Callback handling.
-  static ocsd_datapath_resp_t
-  processTrace(const void *PContext, const ocsd_trc_index_t /*IndexSOP*/,
-               const uint8_t /*TrcChanID*/,
-               const ocsd_generic_trace_elem *Element);
-
-  // Initialize the decoder for microcontroller-class targets.
-  void setupDecoder(dcd_tree_handle_t DcdTree, ProfiledBinary &Binary);
-
-  // Identify and register executable ELF segments with the OpenCSD decoder
-  // to provide opcode access for different memory regions.
-  void mapELFSegments(dcd_tree_handle_t DcdTree, ProfiledBinary &Binary);
-#endif
 };
 
 } // end namespace sampleprof

>From fc319126f66bf993368b84b6fb3083d73f2425f0 Mon Sep 17 00:00:00 2001
From: Gulfem Savrun Yeniceri <gulfem at google.com>
Date: Tue, 14 Apr 2026 13:49:24 -0700
Subject: [PATCH 3/6] [llvm-profgen] Decouple ETMReader from PerfReaderBase

Remove ETMReader's inheritance from PerfReaderBase, as ETM
traces are handled differently from perf traces and do not
fit well in the same hierarchy.

Generalize naming to reflect that ETM data is accurate trace
data rather than samples, and to make the input handling
infrastructure less tied to linux perf.

Other cleanups:
- Remove unused getTriple.
- Add comment about CoreSight Trace ID (CSID) usage in ETMTraceDecoder.
- Add assert in ETMReader::recordProcessedRange to ensure Counters is
not empty, and remove redundant if check.
---
 llvm/lib/ProfileData/ETMTraceDecoder.cpp |  4 ++
 llvm/tools/llvm-profgen/PerfReader.cpp   | 50 +++++++++++-------------
 llvm/tools/llvm-profgen/PerfReader.h     | 24 +++++++-----
 llvm/tools/llvm-profgen/ProfiledBinary.h |  1 -
 llvm/tools/llvm-profgen/llvm-profgen.cpp | 31 +++++++++++----
 5 files changed, 64 insertions(+), 46 deletions(-)

diff --git a/llvm/lib/ProfileData/ETMTraceDecoder.cpp b/llvm/lib/ProfileData/ETMTraceDecoder.cpp
index 7b854220f9f69..785923cb836ec 100644
--- a/llvm/lib/ProfileData/ETMTraceDecoder.cpp
+++ b/llvm/lib/ProfileData/ETMTraceDecoder.cpp
@@ -134,6 +134,10 @@ class ETMDecoderImpl : public ETMDecoder {
           "OpenCSD: Unsupported processor architecture. Only "
           "microcontroller-class (Cortex-M) is currently supported.");
     }
+
+    // The CoreSight Trace ID (CSID) is a hardware-assigned 7-bit identifier
+    // used to route trace data. 0x10 is the most common default value for the
+    // ETM.
     uint8_t CSID = 0x10;
     Config.reg_traceidr = CSID;
     uint32_t Flags =
diff --git a/llvm/tools/llvm-profgen/PerfReader.cpp b/llvm/tools/llvm-profgen/PerfReader.cpp
index be567a96dea12..b36880d873ade 100644
--- a/llvm/tools/llvm-profgen/PerfReader.cpp
+++ b/llvm/tools/llvm-profgen/PerfReader.cpp
@@ -346,37 +346,32 @@ bool VirtualUnwinder::unwind(const PerfSample *Sample, uint64_t Repeat) {
 }
 
 std::unique_ptr<PerfReaderBase>
-PerfReaderBase::create(ProfiledBinary *Binary, PerfInputFile &PerfInput,
+PerfReaderBase::create(ProfiledBinary *Binary, InputFile &Input,
                        std::optional<int32_t> PIDFilter) {
   std::unique_ptr<PerfReaderBase> PerfReader;
 
-  if (PerfInput.Format == PerfFormat::UnsymbolizedProfile) {
+  if (Input.Format == InputFormat::UnsymbolizedProfile) {
     PerfReader.reset(
-        new UnsymbolizedProfileReader(Binary, PerfInput.InputFile));
-    return PerfReader;
-  }
-
-  if (PerfInput.Format == PerfFormat::ETMFormat) {
-    PerfReader.reset(new ETMReader(Binary, PerfInput.InputFile));
+        new UnsymbolizedProfileReader(Binary, Input.InputFile));
     return PerfReader;
   }
 
   // For perf data input, we need to convert them into perf script first.
   // If this is a kernel perf file, there is no need for retrieving PIDs.
-  if (PerfInput.Format == PerfFormat::PerfData)
-    PerfInput = PerfScriptReader::convertPerfDataToTrace(
-        Binary, Binary->isKernel(), PerfInput, PIDFilter);
+  if (Input.Format == InputFormat::PerfData)
+    Input = PerfScriptReader::convertPerfDataToTrace(
+        Binary, Binary->isKernel(), Input, PIDFilter);
 
-  assert((PerfInput.Format == PerfFormat::PerfScript) &&
+  assert((Input.Format == InputFormat::PerfScript) &&
          "Should be a perfscript!");
 
-  PerfInput.Content =
-      PerfScriptReader::checkPerfScriptType(PerfInput.InputFile);
-  if (PerfInput.Content == PerfContent::LBRStack) {
+  Input.Content =
+      PerfScriptReader::checkPerfScriptType(Input.InputFile);
+  if (Input.Content == PerfContent::LBRStack) {
     PerfReader.reset(
-        new HybridPerfReader(Binary, PerfInput.InputFile, PIDFilter));
-  } else if (PerfInput.Content == PerfContent::LBR) {
-    PerfReader.reset(new LBRPerfReader(Binary, PerfInput.InputFile, PIDFilter));
+        new HybridPerfReader(Binary, Input.InputFile, PIDFilter));
+  } else if (Input.Content == PerfContent::LBR) {
+    PerfReader.reset(new LBRPerfReader(Binary, Input.InputFile, PIDFilter));
   } else {
     exitWithError("Unsupported perfscript!");
   }
@@ -455,9 +450,9 @@ Error PerfReaderBase::parseDataAccessPerfTraces(
   return Error::success();
 }
 
-PerfInputFile
+InputFile
 PerfScriptReader::convertPerfDataToTrace(ProfiledBinary *Binary, bool SkipPID,
-                                         PerfInputFile &File,
+                                         InputFile &File,
                                          std::optional<int32_t> PIDFilter) {
   StringRef PerfData = File.InputFile;
   // Run perf script to retrieve PIDs matching binary we're interested in.
@@ -521,7 +516,7 @@ PerfScriptReader::convertPerfDataToTrace(ProfiledBinary *Binary, bool SkipPID,
   }
   sys::ExecuteAndWait(PerfPath, ScriptSampleArgs, std::nullopt, Redirects);
 
-  return {std::string(PerfTraceFile), PerfFormat::PerfScript,
+  return {std::string(PerfTraceFile), InputFormat::PerfScript,
           PerfContent::UnknownContent};
 }
 
@@ -1378,10 +1373,9 @@ SmallVector<CleanupInstaller, 2> PerfScriptReader::TempFileCleanups;
 
 void ETMReader::recordProcessedRange(uint64_t Start, uint64_t End,
                                      uint64_t Count) {
-  if (!SampleCounters.empty()) {
-    auto &Counter = SampleCounters.begin()->second;
-    Counter.recordRangeCount(Start, End, Count);
-  }
+  assert(!Counters.empty() && "Counters should not be empty!");
+  auto &Counter = Counters.begin()->second;
+  Counter.recordRangeCount(Start, End, Count);
 }
 
 class ETMCallback : public ETMDecoder::Callback {
@@ -1394,8 +1388,8 @@ class ETMCallback : public ETMDecoder::Callback {
   }
 };
 
-void ETMReader::parsePerfTraces() {
-  auto BufferOrErr = MemoryBuffer::getFile(PerfTraceFile);
+void ETMReader::parseETMTraces() {
+  auto BufferOrErr = MemoryBuffer::getFile(TraceFile);
   if (std::error_code EC = BufferOrErr.getError())
     exitWithError("Could not open ETM trace file: " + EC.message());
 
@@ -1407,7 +1401,7 @@ void ETMReader::parsePerfTraces() {
   // Initialize the SampleCounters map with a single empty context key
   // to aggregate all instruction hits into a global bucket.
   auto Key = std::make_shared<StringBasedCtxKey>();
-  SampleCounters.emplace(Hashable<ContextKey>(Key), SampleCounter());
+  Counters.emplace(Hashable<ContextKey>(Key), SampleCounter());
 
   // The protocol utilizes a 0x80 byte as an initial synchronization header.
   // Perform a manual search for this sync point to discard any leading
diff --git a/llvm/tools/llvm-profgen/PerfReader.h b/llvm/tools/llvm-profgen/PerfReader.h
index ca07a44cd83bd..aeae84647c797 100644
--- a/llvm/tools/llvm-profgen/PerfReader.h
+++ b/llvm/tools/llvm-profgen/PerfReader.h
@@ -58,7 +58,7 @@ class TraceStream {
 };
 
 // The type of input format.
-enum PerfFormat {
+enum InputFormat {
   UnknownFormat = 0,
   PerfData = 1,            // Raw linux perf.data.
   PerfScript = 2,          // Perf script create by `perf script` command.
@@ -73,9 +73,9 @@ enum PerfContent {
   LBRStack = 2, // Hybrid sample including call stack and LBR stack.
 };
 
-struct PerfInputFile {
+struct InputFile {
   std::string InputFile;
-  PerfFormat Format = PerfFormat::UnknownFormat;
+  InputFormat Format = InputFormat::UnknownFormat;
   PerfContent Content = PerfContent::UnknownContent;
 };
 
@@ -573,7 +573,7 @@ class PerfReaderBase {
   };
   virtual ~PerfReaderBase() = default;
   static std::unique_ptr<PerfReaderBase>
-  create(ProfiledBinary *Binary, PerfInputFile &PerfInput,
+  create(ProfiledBinary *Binary, InputFile &Input,
          std::optional<int32_t> PIDFilter);
 
   // Entry of the reader to parse multiple perf traces
@@ -620,8 +620,8 @@ class PerfScriptReader : public PerfReaderBase {
                                         MMapEvent &MMap);
 
   // Generate perf script from perf data
-  static PerfInputFile convertPerfDataToTrace(ProfiledBinary *Binary,
-                                              bool SkipPID, PerfInputFile &File,
+  static InputFile convertPerfDataToTrace(ProfiledBinary *Binary,
+                                              bool SkipPID, InputFile &File,
                                               std::optional<int32_t> PIDFilter);
   // Extract perf script type by peaking at the input
   static PerfContent checkPerfScriptType(StringRef FileName);
@@ -748,12 +748,18 @@ class UnsymbolizedProfileReader : public PerfReaderBase {
   std::unordered_set<std::string> ContextStrSet;
 };
 
-class ETMReader : public PerfReaderBase {
+class ETMReader {
 public:
   ETMReader(ProfiledBinary *Binary, StringRef TraceFile)
-      : PerfReaderBase(Binary, TraceFile) {}
-  void parsePerfTraces() override;
+      : Binary(Binary), TraceFile(TraceFile) {}
+  void parseETMTraces();
   void recordProcessedRange(uint64_t Start, uint64_t End, uint64_t Count);
+  const ContextSampleCounterMap &getTraceCounters() const { return Counters; }
+
+private:
+  ProfiledBinary *Binary = nullptr;
+  StringRef TraceFile;
+  ContextSampleCounterMap Counters;
 };
 
 } // end namespace sampleprof
diff --git a/llvm/tools/llvm-profgen/ProfiledBinary.h b/llvm/tools/llvm-profgen/ProfiledBinary.h
index 9fc42a1036ed1..a437c57b8c0d7 100644
--- a/llvm/tools/llvm-profgen/ProfiledBinary.h
+++ b/llvm/tools/llvm-profgen/ProfiledBinary.h
@@ -431,7 +431,6 @@ class ProfiledBinary {
 
   StringRef getPath() const { return Path; }
   StringRef getName() const { return llvm::sys::path::filename(Path); }
-  const Triple &getTriple() const { return TheTriple; }
   uint64_t getBaseAddress() const { return BaseAddress; }
   void setBaseAddress(uint64_t Address) { BaseAddress = Address; }
 
diff --git a/llvm/tools/llvm-profgen/llvm-profgen.cpp b/llvm/tools/llvm-profgen/llvm-profgen.cpp
index 5b75b2498fb88..095ae5f8d09dd 100644
--- a/llvm/tools/llvm-profgen/llvm-profgen.cpp
+++ b/llvm/tools/llvm-profgen/llvm-profgen.cpp
@@ -136,20 +136,20 @@ static void validateCommandLine() {
   }
 }
 
-static PerfInputFile getPerfInputFile() {
-  PerfInputFile File;
+static InputFile getInputFile() {
+  InputFile File;
   if (PerfDataFilename.getNumOccurrences()) {
     File.InputFile = PerfDataFilename;
-    File.Format = PerfFormat::PerfData;
+    File.Format = InputFormat::PerfData;
   } else if (PerfScriptFilename.getNumOccurrences()) {
     File.InputFile = PerfScriptFilename;
-    File.Format = PerfFormat::PerfScript;
+    File.Format = InputFormat::PerfScript;
   } else if (UnsymbolizedProfFilename.getNumOccurrences()) {
     File.InputFile = UnsymbolizedProfFilename;
-    File.Format = PerfFormat::UnsymbolizedProfile;
+    File.Format = InputFormat::UnsymbolizedProfile;
   } else if (ETMPath.getNumOccurrences()) {
     File.InputFile = ETMPath;
-    File.Format = PerfFormat::ETMFormat;
+    File.Format = InputFormat::ETMFormat;
   }
   return File;
 }
@@ -193,9 +193,24 @@ int main(int argc, const char *argv[]) {
     std::optional<uint32_t> PIDFilter;
     if (ProcessId.getNumOccurrences())
       PIDFilter = ProcessId;
-    PerfInputFile PerfFile = getPerfInputFile();
+    InputFile File = getInputFile();
+    if (File.Format == InputFormat::ETMFormat) {
+      ETMReader Reader(Binary.get(), File.InputFile);
+      Reader.parseETMTraces();
+
+      if (SkipSymbolization)
+        return EXIT_SUCCESS;
+
+      std::unique_ptr<ProfileGeneratorBase> Generator =
+          ProfileGeneratorBase::create(Binary.get(), &Reader.getTraceCounters(),
+                                       false);
+      Generator->generateProfile();
+      Generator->write();
+      return EXIT_SUCCESS;
+    }
+
     std::unique_ptr<PerfReaderBase> Reader =
-        PerfReaderBase::create(Binary.get(), PerfFile, PIDFilter);
+        PerfReaderBase::create(Binary.get(), File, PIDFilter);
     // Parse perf events and samples
     Reader->parsePerfTraces();
 

>From a5b1feb68759b83251dd14bba7fa8abccfb407a4 Mon Sep 17 00:00:00 2001
From: Gulfem Savrun Yeniceri <gulfem at google.com>
Date: Tue, 14 Apr 2026 16:41:14 -0700
Subject: [PATCH 4/6] [llvm-profgen] Fix clang-format style issues

---
 llvm/tools/llvm-profgen/PerfReader.cpp | 13 +++++--------
 llvm/tools/llvm-profgen/PerfReader.h   |  6 +++---
 2 files changed, 8 insertions(+), 11 deletions(-)

diff --git a/llvm/tools/llvm-profgen/PerfReader.cpp b/llvm/tools/llvm-profgen/PerfReader.cpp
index b36880d873ade..543ebd63ed6cb 100644
--- a/llvm/tools/llvm-profgen/PerfReader.cpp
+++ b/llvm/tools/llvm-profgen/PerfReader.cpp
@@ -351,25 +351,22 @@ PerfReaderBase::create(ProfiledBinary *Binary, InputFile &Input,
   std::unique_ptr<PerfReaderBase> PerfReader;
 
   if (Input.Format == InputFormat::UnsymbolizedProfile) {
-    PerfReader.reset(
-        new UnsymbolizedProfileReader(Binary, Input.InputFile));
+    PerfReader.reset(new UnsymbolizedProfileReader(Binary, Input.InputFile));
     return PerfReader;
   }
 
   // For perf data input, we need to convert them into perf script first.
   // If this is a kernel perf file, there is no need for retrieving PIDs.
   if (Input.Format == InputFormat::PerfData)
-    Input = PerfScriptReader::convertPerfDataToTrace(
-        Binary, Binary->isKernel(), Input, PIDFilter);
+    Input = PerfScriptReader::convertPerfDataToTrace(Binary, Binary->isKernel(),
+                                                     Input, PIDFilter);
 
   assert((Input.Format == InputFormat::PerfScript) &&
          "Should be a perfscript!");
 
-  Input.Content =
-      PerfScriptReader::checkPerfScriptType(Input.InputFile);
+  Input.Content = PerfScriptReader::checkPerfScriptType(Input.InputFile);
   if (Input.Content == PerfContent::LBRStack) {
-    PerfReader.reset(
-        new HybridPerfReader(Binary, Input.InputFile, PIDFilter));
+    PerfReader.reset(new HybridPerfReader(Binary, Input.InputFile, PIDFilter));
   } else if (Input.Content == PerfContent::LBR) {
     PerfReader.reset(new LBRPerfReader(Binary, Input.InputFile, PIDFilter));
   } else {
diff --git a/llvm/tools/llvm-profgen/PerfReader.h b/llvm/tools/llvm-profgen/PerfReader.h
index aeae84647c797..7a38d599e515e 100644
--- a/llvm/tools/llvm-profgen/PerfReader.h
+++ b/llvm/tools/llvm-profgen/PerfReader.h
@@ -620,9 +620,9 @@ class PerfScriptReader : public PerfReaderBase {
                                         MMapEvent &MMap);
 
   // Generate perf script from perf data
-  static InputFile convertPerfDataToTrace(ProfiledBinary *Binary,
-                                              bool SkipPID, InputFile &File,
-                                              std::optional<int32_t> PIDFilter);
+  static InputFile convertPerfDataToTrace(ProfiledBinary *Binary, bool SkipPID,
+                                          InputFile &File,
+                                          std::optional<int32_t> PIDFilter);
   // Extract perf script type by peaking at the input
   static PerfContent checkPerfScriptType(StringRef FileName);
 

>From 9409bfe4a5d9012727640436f2d41d17658928bb Mon Sep 17 00:00:00 2001
From: Gulfem Savrun Yeniceri <gulfem at google.com>
Date: Wed, 15 Apr 2026 16:35:44 -0700
Subject: [PATCH 5/6] [llvm-profgen] Support triple override

1) Add --target-triple flag for manual arch overrides.
2) Reuse binary in ETMDecoder and improve ARM detection.
---
 .../llvm/ProfileData/ETMTraceDecoder.h        |  8 ++-
 llvm/lib/ProfileData/ETMTraceDecoder.cpp      | 67 +++++++++----------
 llvm/tools/llvm-profgen/PerfReader.cpp        |  4 +-
 llvm/tools/llvm-profgen/ProfiledBinary.cpp    |  8 ++-
 llvm/tools/llvm-profgen/ProfiledBinary.h      | 15 +++--
 llvm/tools/llvm-profgen/llvm-profgen.cpp      |  7 ++
 6 files changed, 65 insertions(+), 44 deletions(-)

diff --git a/llvm/include/llvm/ProfileData/ETMTraceDecoder.h b/llvm/include/llvm/ProfileData/ETMTraceDecoder.h
index 10f039311e4d2..2bc5183ce073b 100644
--- a/llvm/include/llvm/ProfileData/ETMTraceDecoder.h
+++ b/llvm/include/llvm/ProfileData/ETMTraceDecoder.h
@@ -18,6 +18,11 @@
 
 namespace llvm {
 
+class Triple;
+namespace object {
+class Binary;
+}
+
 class ETMDecoder {
 public:
   virtual ~ETMDecoder() = default;
@@ -28,7 +33,8 @@ class ETMDecoder {
     virtual void processInstructionRange(uint64_t Start, uint64_t End) = 0;
   };
 
-  static Expected<std::unique_ptr<ETMDecoder>> create(StringRef BinaryPath);
+  static Expected<std::unique_ptr<ETMDecoder>>
+  create(const object::Binary &Binary, const Triple &TargetTriple);
 
   virtual Error processTrace(ArrayRef<uint8_t> TraceData,
                              Callback &TraceCallback) = 0;
diff --git a/llvm/lib/ProfileData/ETMTraceDecoder.cpp b/llvm/lib/ProfileData/ETMTraceDecoder.cpp
index 785923cb836ec..644fcd462678a 100644
--- a/llvm/lib/ProfileData/ETMTraceDecoder.cpp
+++ b/llvm/lib/ProfileData/ETMTraceDecoder.cpp
@@ -11,6 +11,7 @@
 #include "llvm/Object/ELFObjectFile.h"
 #include "llvm/Object/ObjectFile.h"
 #include "llvm/Support/Error.h"
+#include "llvm/TargetParser/ARMTargetParser.h"
 
 #ifdef HAVE_OPENCSD
 #include "opencsd/c_api/opencsd_c_api.h"
@@ -20,8 +21,8 @@ namespace llvm {
 namespace {
 class ETMDecoderImpl : public ETMDecoder {
   dcd_tree_handle_t DcdTree = 0;
-  std::string BinaryPath;
-  Triple TargetTriple;
+  const object::Binary &Binary;
+  const Triple &TargetTriple;
 
   // Trace processing and Callback handling.
   static ocsd_datapath_resp_t
@@ -53,7 +54,8 @@ class ETMDecoderImpl : public ETMDecoder {
   // segments. These are registered as a single transaction to the OpenCSD
   // memory manager to prevent overlap/collision errors between different
   // memory regions.
-  Error mapELFSegments(dcd_tree_handle_t DcdTree, object::Binary &SourceBin) {
+  Error mapELFSegments(dcd_tree_handle_t DcdTree,
+                       const object::Binary &SourceBin) {
     SmallVector<ocsd_file_mem_region_t, 4> Regions;
     auto ProcessHeaders = [&](const auto &ElfFile) {
       auto ProgramHeaders = ElfFile.program_headers();
@@ -82,9 +84,10 @@ class ETMDecoderImpl : public ETMDecoder {
       ProcessHeaders(O->getELFFile());
 
     if (!Regions.empty()) {
+      std::string Path = SourceBin.getFileName().str();
       if (ocsd_dt_add_binfile_region_mem_acc(
               DcdTree, Regions.data(), (uint32_t)Regions.size(),
-              OCSD_MEM_SPACE_ANY, BinaryPath.c_str()) != 0) {
+              OCSD_MEM_SPACE_ANY, Path.c_str()) != 0) {
         return createStringError(
             inconvertibleErrorCode(),
             "OpenCSD: Failed to map ELF executable segments.");
@@ -94,46 +97,41 @@ class ETMDecoderImpl : public ETMDecoder {
   }
 
 public:
-  ETMDecoderImpl(StringRef BinaryPath) : BinaryPath(BinaryPath.str()) {}
+  ETMDecoderImpl(const object::Binary &Binary, const Triple &Triple)
+      : Binary(Binary), TargetTriple(Triple) {}
+
   ~ETMDecoderImpl() override {
     if (DcdTree)
       // Deallocate the decoder tree resources.
       ocsd_destroy_dcd_tree(DcdTree);
   }
 
-  /// Initialize the decoder by auto-detecting the target architecture and
-  /// configuring the OpenCSD decoder.
+  // Initialize the decoder by auto-detecting the target architecture and
+  // configuring the OpenCSD decoder.
   Error initialize() {
-    auto BinaryOrErr = object::createBinary(BinaryPath);
-    if (!BinaryOrErr)
-      return BinaryOrErr.takeError();
-
-    object::Binary &SourceBin = *BinaryOrErr.get().getBinary();
-    auto *ElfBase = dyn_cast<object::ELFObjectFileBase>(&SourceBin);
-    if (!ElfBase)
-      return createStringError(inconvertibleErrorCode(),
-                               "OpenCSD: Unsupported binary format. Only ELF "
-                               "is currently supported.");
-
-    TargetTriple = ElfBase->makeTriple();
-
     DcdTree = ocsd_create_dcd_tree(OCSD_TRC_SRC_SINGLE, 0);
     if (!DcdTree)
       return createStringError(inconvertibleErrorCode(),
                                "Failed to create OpenCSD decoder tree.");
 
     // Configure and initialize the instruction-level decoder.
-    ocsd_etmv4_cfg Config{};
-    // Initialize the decoder for microcontroller-class targets.
-    if (TargetTriple.isArmMClass() || TargetTriple.isThumb()) {
-      Config.arch_ver = ARCH_V8;
-      Config.core_prof = profile_CortexM;
-    } else {
+    ocsd_arch_version_t ArchVer = ARCH_UNKNOWN;
+    if (TargetTriple.isArmMClass()) {
+      if (ARM::parseArchVersion(TargetTriple.getArchName()) >= 8)
+        ArchVer = ARCH_V8;
+      else
+        ArchVer = ARCH_V7;
+    }
+    if (ArchVer == ARCH_UNKNOWN)
       return createStringError(
           inconvertibleErrorCode(),
           "OpenCSD: Unsupported processor architecture. Only "
           "microcontroller-class (Cortex-M) is currently supported.");
-    }
+
+    ocsd_etmv4_cfg Config{};
+    // Initialize the decoder for microcontroller-class targets.
+    Config.arch_ver = ArchVer;
+    Config.core_prof = profile_CortexM;
 
     // The CoreSight Trace ID (CSID) is a hardware-assigned 7-bit identifier
     // used to route trace data. 0x10 is the most common default value for the
@@ -149,7 +147,7 @@ class ETMDecoderImpl : public ETMDecoder {
           "OpenCSD: Failed to initialize the instruction decoder.");
 
     // Extract and map executable segments from the ELF binary.
-    if (Error E = mapELFSegments(DcdTree, SourceBin))
+    if (Error E = mapELFSegments(DcdTree, Binary))
       return E;
 
     // Register the high-level packet callback. The 'processTrace' function
@@ -200,9 +198,9 @@ class ETMDecoderImpl : public ETMDecoder {
 };
 } // namespace
 
-Expected<std::unique_ptr<ETMDecoder>> ETMDecoder::create(StringRef BinaryPath) {
-  auto Decoder =
-      std::unique_ptr<ETMDecoderImpl>(new ETMDecoderImpl(BinaryPath));
+Expected<std::unique_ptr<ETMDecoder>>
+ETMDecoder::create(const object::Binary &Binary, const Triple &Triple) {
+  auto Decoder = std::make_unique<ETMDecoderImpl>(Binary, Triple);
   if (Error E = Decoder->initialize())
     return std::move(E);
   return std::unique_ptr<ETMDecoder>(std::move(Decoder));
@@ -214,9 +212,10 @@ Expected<std::unique_ptr<ETMDecoder>> ETMDecoder::create(StringRef BinaryPath) {
 
 namespace llvm {
 
-Expected<std::unique_ptr<ETMDecoder>> ETMDecoder::create(StringRef BinaryPath) {
-  return createStringError(inconvertibleErrorCode(),
-                           "OpenCSD support was not found or enabled.");
+Expected<std::unique_ptr<ETMDecoder>>
+ETMDecoder::create(const object::Binary & /*Binary*/,
+                   const Triple & /*Triple*/) {
+  return createStringError(inconvertibleErrorCode(), "OpenCSD not enabled.");
 }
 
 } // namespace llvm
diff --git a/llvm/tools/llvm-profgen/PerfReader.cpp b/llvm/tools/llvm-profgen/PerfReader.cpp
index 543ebd63ed6cb..bfdb41caa5cb9 100644
--- a/llvm/tools/llvm-profgen/PerfReader.cpp
+++ b/llvm/tools/llvm-profgen/PerfReader.cpp
@@ -1410,7 +1410,9 @@ void ETMReader::parseETMTraces() {
     exitWithError("No synchronization header (0x80) found in the bitstream.");
   ArrayRef<uint8_t> TraceSlice = Data.slice(StartIdx);
 
-  auto DecoderOrErr = ETMDecoder::create(Binary->getPath());
+  auto DecoderOrErr =
+      ETMDecoder::create(Binary->getBinary(), Binary->getTriple());
+
   if (!DecoderOrErr)
     exitWithError(toString(DecoderOrErr.takeError()));
   auto Decoder = std::move(*DecoderOrErr);
diff --git a/llvm/tools/llvm-profgen/ProfiledBinary.cpp b/llvm/tools/llvm-profgen/ProfiledBinary.cpp
index 88832ba65efea..5c45b8f66aa97 100644
--- a/llvm/tools/llvm-profgen/ProfiledBinary.cpp
+++ b/llvm/tools/llvm-profgen/ProfiledBinary.cpp
@@ -195,7 +195,6 @@ ProfiledBinary::ProfiledBinary(const StringRef ExeBinPath,
   SymbolizerPath = DebugBinPath.empty() ? ExeBinPath : DebugBinPath;
   if (InferMissingFrames)
     MissingContextInferrer = std::make_unique<MissingFrameInferrer>(this);
-  load();
 }
 
 ProfiledBinary::~ProfiledBinary() = default;
@@ -228,9 +227,9 @@ void ProfiledBinary::warnNoFuncEntry() {
                      "inconsistent name from symbol table and dwarf info.");
 }
 
-void ProfiledBinary::load() {
+void ProfiledBinary::load(StringRef TripleStr) {
   // Attempt to open the binary.
-  OwningBinary<Binary> OBinary = unwrapOrError(createBinary(Path), Path);
+  OBinary = unwrapOrError(createBinary(Path), Path);
   Binary &ExeBinary = *OBinary.getBinary();
 
   IsCOFF = isa<COFFObjectFile>(&ExeBinary);
@@ -240,6 +239,9 @@ void ProfiledBinary::load() {
   auto *Obj = cast<ObjectFile>(&ExeBinary);
   TheTriple = Obj->makeTriple();
 
+  if (!TripleStr.empty())
+    TheTriple = Triple(TripleStr);
+
   LLVM_DEBUG(dbgs() << "Loading " << Path << "\n");
 
   // Mark the binary as a kernel image;
diff --git a/llvm/tools/llvm-profgen/ProfiledBinary.h b/llvm/tools/llvm-profgen/ProfiledBinary.h
index a437c57b8c0d7..1e4cc5d650ea6 100644
--- a/llvm/tools/llvm-profgen/ProfiledBinary.h
+++ b/llvm/tools/llvm-profgen/ProfiledBinary.h
@@ -200,6 +200,8 @@ struct MMapEvent {
 };
 
 class ProfiledBinary {
+  // The executable binary file.
+  object::OwningBinary<object::Binary> OBinary;
   // Absolute path of the executable binary.
   std::string Path;
   // Path of the debug info binary.
@@ -410,6 +412,11 @@ class ProfiledBinary {
   SampleContextFrameVector symbolize(const InstructionPointer &IP,
                                      bool UseCanonicalFnName = false,
                                      bool UseProbeDiscriminator = false);
+
+public:
+  ProfiledBinary(const StringRef ExeBinPath, const StringRef DebugBinPath);
+  ~ProfiledBinary();
+
   /// Decode the interesting parts of the binary and build internal data
   /// structures. On high level, the parts of interest are:
   ///   1. Text sections, including the main code section and the PLT
@@ -417,11 +424,7 @@ class ProfiledBinary {
   ///   2. The .debug_line section, used by Dwarf-based profile generation.
   ///   3. Pseudo probe related sections, used by probe-based profile
   ///   generation.
-  void load();
-
-public:
-  ProfiledBinary(const StringRef ExeBinPath, const StringRef DebugBinPath);
-  ~ProfiledBinary();
+  void load(StringRef TripleStr = "");
 
   /// Symbolize an address and return the symbol name. The returned StringRef is
   /// owned by this ProfiledBinary object.
@@ -431,6 +434,8 @@ class ProfiledBinary {
 
   StringRef getPath() const { return Path; }
   StringRef getName() const { return llvm::sys::path::filename(Path); }
+  const Triple &getTriple() const { return TheTriple; }
+  const object::Binary &getBinary() const { return *OBinary.getBinary(); }
   uint64_t getBaseAddress() const { return BaseAddress; }
   void setBaseAddress(uint64_t Address) { BaseAddress = Address; }
 
diff --git a/llvm/tools/llvm-profgen/llvm-profgen.cpp b/llvm/tools/llvm-profgen/llvm-profgen.cpp
index 095ae5f8d09dd..e333c88a3a22d 100644
--- a/llvm/tools/llvm-profgen/llvm-profgen.cpp
+++ b/llvm/tools/llvm-profgen/llvm-profgen.cpp
@@ -83,6 +83,11 @@ static cl::opt<std::string> ETMPath("etm", cl::value_desc("etm"),
                                     cl::desc("Path of raw ETM trace file"),
                                     cl::cat(ProfGenCategory));
 
+static cl::opt<std::string>
+    TargetTriple("target-triple", cl::value_desc("triple"),
+                 cl::desc("Override the target triple for the binary"),
+                 cl::cat(ProfGenCategory));
+
 // Validate the command line input.
 static void validateCommandLine() {
   // Allow the missing perfscript if we only use to show binary disassembly.
@@ -171,6 +176,8 @@ int main(int argc, const char *argv[]) {
   // Load symbols and disassemble the code of a given binary.
   std::unique_ptr<ProfiledBinary> Binary =
       std::make_unique<ProfiledBinary>(BinaryPath, DebugBinPath);
+  Binary->load(TargetTriple);
+
   if (ShowDisassemblyOnly)
     return EXIT_SUCCESS;
 

>From 77e0bd831b464bd3a029137c6276c766e9d04360 Mon Sep 17 00:00:00 2001
From: Gulfem Savrun Yeniceri <gulfem at google.com>
Date: Tue, 21 Apr 2026 11:03:29 -0700
Subject: [PATCH 6/6] [llvm-profgen] Abstract ETM configuration

1) Abstract ETM configuration into HardwareTraceConfig
2) Add explicit error handling for cores with no ETM support
3) Deduplicate profile generation code
---
 llvm/lib/ProfileData/ETMTraceDecoder.cpp | 69 ++++++++++++++++--------
 llvm/tools/llvm-profgen/PerfReader.h     |  2 +-
 llvm/tools/llvm-profgen/llvm-profgen.cpp | 66 +++++++++++------------
 3 files changed, 78 insertions(+), 59 deletions(-)

diff --git a/llvm/lib/ProfileData/ETMTraceDecoder.cpp b/llvm/lib/ProfileData/ETMTraceDecoder.cpp
index 644fcd462678a..6c9166b619b1d 100644
--- a/llvm/lib/ProfileData/ETMTraceDecoder.cpp
+++ b/llvm/lib/ProfileData/ETMTraceDecoder.cpp
@@ -19,6 +19,48 @@
 namespace llvm {
 
 namespace {
+
+class HardwareTraceConfig {
+public:
+  virtual ~HardwareTraceConfig() = default;
+};
+
+class ETMTraceConfig : public HardwareTraceConfig {
+public:
+  ocsd_etmv4_cfg Cfg{};
+  uint8_t CSID = 0x10;
+
+  ETMTraceConfig(const Triple &TargetTriple) {
+    ocsd_arch_version_t ArchVer = ARCH_UNKNOWN;
+    if (TargetTriple.isArmMClass()) {
+      unsigned ArchVersion = ARM::parseArchVersion(TargetTriple.getArchName());
+      if (ArchVersion >= 8)
+        ArchVer = ARCH_V8;
+      else if (ArchVersion == 7)
+        ArchVer = ARCH_V7;
+      else
+        ArchVer = ARCH_UNKNOWN; // For version 6 (Cortex-M0) and others.
+    }
+    // Initialize the decoder for microcontroller-class targets.
+    Cfg.arch_ver = ArchVer;
+    Cfg.core_prof = profile_CortexM;
+
+    // The CoreSight Trace ID (CSID) is a hardware-assigned 7-bit identifier
+    // used to route trace data. 0x10 is the most common default value for the
+    // ETM.
+    Cfg.reg_traceidr = CSID;
+  }
+
+  Error validate() const {
+    if (Cfg.arch_ver == ARCH_UNKNOWN)
+      return createStringError(
+          inconvertibleErrorCode(),
+          "OpenCSD: Unsupported processor architecture. Only Arm M-profile "
+          "(Cortex-M) with ETM support is currently supported.");
+    return Error::success();
+  }
+};
+
 class ETMDecoderImpl : public ETMDecoder {
   dcd_tree_handle_t DcdTree = 0;
   const object::Binary &Binary;
@@ -115,33 +157,14 @@ class ETMDecoderImpl : public ETMDecoder {
                                "Failed to create OpenCSD decoder tree.");
 
     // Configure and initialize the instruction-level decoder.
-    ocsd_arch_version_t ArchVer = ARCH_UNKNOWN;
-    if (TargetTriple.isArmMClass()) {
-      if (ARM::parseArchVersion(TargetTriple.getArchName()) >= 8)
-        ArchVer = ARCH_V8;
-      else
-        ArchVer = ARCH_V7;
-    }
-    if (ArchVer == ARCH_UNKNOWN)
-      return createStringError(
-          inconvertibleErrorCode(),
-          "OpenCSD: Unsupported processor architecture. Only "
-          "microcontroller-class (Cortex-M) is currently supported.");
-
-    ocsd_etmv4_cfg Config{};
-    // Initialize the decoder for microcontroller-class targets.
-    Config.arch_ver = ArchVer;
-    Config.core_prof = profile_CortexM;
+    ETMTraceConfig Config(TargetTriple);
+    if (Error E = Config.validate())
+      return E;
 
-    // The CoreSight Trace ID (CSID) is a hardware-assigned 7-bit identifier
-    // used to route trace data. 0x10 is the most common default value for the
-    // ETM.
-    uint8_t CSID = 0x10;
-    Config.reg_traceidr = CSID;
     uint32_t Flags =
         OCSD_CREATE_FLG_FULL_DECODER | OCSD_OPFLG_CHK_RANGE_CONTINUE;
     if (ocsd_dt_create_decoder(DcdTree, OCSD_BUILTIN_DCD_ETMV4I, Flags,
-                               (void *)&Config, &CSID) != 0)
+                               (void *)&Config.Cfg, &Config.CSID) != 0)
       return createStringError(
           inconvertibleErrorCode(),
           "OpenCSD: Failed to initialize the instruction decoder.");
diff --git a/llvm/tools/llvm-profgen/PerfReader.h b/llvm/tools/llvm-profgen/PerfReader.h
index 7a38d599e515e..fb28a5a3c7319 100644
--- a/llvm/tools/llvm-profgen/PerfReader.h
+++ b/llvm/tools/llvm-profgen/PerfReader.h
@@ -754,7 +754,7 @@ class ETMReader {
       : Binary(Binary), TraceFile(TraceFile) {}
   void parseETMTraces();
   void recordProcessedRange(uint64_t Start, uint64_t End, uint64_t Count);
-  const ContextSampleCounterMap &getTraceCounters() const { return Counters; }
+  const ContextSampleCounterMap &getSampleCounters() const { return Counters; }
 
 private:
   ProfiledBinary *Binary = nullptr;
diff --git a/llvm/tools/llvm-profgen/llvm-profgen.cpp b/llvm/tools/llvm-profgen/llvm-profgen.cpp
index e333c88a3a22d..8ad2dd01a1e8e 100644
--- a/llvm/tools/llvm-profgen/llvm-profgen.cpp
+++ b/llvm/tools/llvm-profgen/llvm-profgen.cpp
@@ -201,49 +201,45 @@ int main(int argc, const char *argv[]) {
     if (ProcessId.getNumOccurrences())
       PIDFilter = ProcessId;
     InputFile File = getInputFile();
-    if (File.Format == InputFormat::ETMFormat) {
-      ETMReader Reader(Binary.get(), File.InputFile);
-      Reader.parseETMTraces();
-
-      if (SkipSymbolization)
-        return EXIT_SUCCESS;
-
-      std::unique_ptr<ProfileGeneratorBase> Generator =
-          ProfileGeneratorBase::create(Binary.get(), &Reader.getTraceCounters(),
-                                       false);
-      Generator->generateProfile();
-      Generator->write();
-      return EXIT_SUCCESS;
-    }
+    const ContextSampleCounterMap *Counters = nullptr;
+    bool ProfileIsCS = false;
+    std::unique_ptr<ETMReader> EtmReader;
+    std::unique_ptr<PerfReaderBase> PerfReader;
 
-    std::unique_ptr<PerfReaderBase> Reader =
-        PerfReaderBase::create(Binary.get(), File, PIDFilter);
-    // Parse perf events and samples
-    Reader->parsePerfTraces();
-
-    if (!DataAccessProfileFilename.empty()) {
-      if (Reader->profileIsCS() || Binary->usePseudoProbes()) {
-        exitWithError("Symbolizing vtables from data access profiles is not "
-                      "yet supported for context-sensitive perf traces or "
-                      "when pseudo-probe based mapping is enabled. ");
-      }
-      // Parse the data access perf traces into <ip, data-addr> pairs, symbolize
-      // the data-addr to data-symbol. If the data-addr is a vtable, increment
-      // counters for the <ip, data-symbol> pair.
-      if (Error E = Reader->parseDataAccessPerfTraces(DataAccessProfileFilename,
-                                                      PIDFilter)) {
-        handleAllErrors(std::move(E), [&](const StringError &SE) {
-          exitWithError(SE.getMessage());
-        });
+    if (File.Format == InputFormat::ETMFormat) {
+      EtmReader = std::make_unique<ETMReader>(Binary.get(), File.InputFile);
+      EtmReader->parseETMTraces();
+      Counters = &EtmReader->getSampleCounters();
+    } else {
+      PerfReader = PerfReaderBase::create(Binary.get(), File, PIDFilter);
+      // Parse perf events and samples
+      PerfReader->parsePerfTraces();
+
+      if (!DataAccessProfileFilename.empty()) {
+        if (PerfReader->profileIsCS() || Binary->usePseudoProbes()) {
+          exitWithError("Symbolizing vtables from data access profiles is not "
+                        "yet supported for context-sensitive perf traces or "
+                        "when pseudo-probe based mapping is enabled. ");
+        }
+        // Parse the data access perf traces into <ip, data-addr> pairs, symbolize
+        // the data-addr to data-symbol. If the data-addr is a vtable, increment
+        // counters for the <ip, data-symbol> pair.
+        if (Error E = PerfReader->parseDataAccessPerfTraces(DataAccessProfileFilename,
+                                                        PIDFilter)) {
+          handleAllErrors(std::move(E), [&](const StringError &SE) {
+            exitWithError(SE.getMessage());
+          });
+        }
       }
+      Counters = &PerfReader->getSampleCounters();
+      ProfileIsCS = PerfReader->profileIsCS();
     }
 
     if (SkipSymbolization)
       return EXIT_SUCCESS;
 
     std::unique_ptr<ProfileGeneratorBase> Generator =
-        ProfileGeneratorBase::create(Binary.get(), &Reader->getSampleCounters(),
-                                     Reader->profileIsCS());
+        ProfileGeneratorBase::create(Binary.get(), Counters, ProfileIsCS);
     Generator->generateProfile();
     Generator->write();
   }



More information about the llvm-commits mailing list