[llvm] [llvm-profgen] Add support for ETM trace decoding (PR #191584)
via llvm-commits
llvm-commits at lists.llvm.org
Mon Apr 13 20:09:30 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/2] [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 e5156129838948..350efef89f7e78 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 a47c5a48d51b81..cfa9e812f9ee80 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 00000000000000..6824b4a9c108b1
--- /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 00000000000000..31bdef63e02058
--- /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 00000000000000..0de28a01f07c54
--- /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 c628a80ff368d8..ec7d3d52b82150 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 8d13bbb2bdb143..e42af2d8a6330f 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 1dc59321fd91f9..f0631301c3a0aa 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 2a4c7594d3a93a..15b2aced0995eb 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 a437c57b8c0d70..9fc42a1036ed1b 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 0ab6c6f87382d4..5b75b2498fb889 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/2] [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 00000000000000..10f039311e4d24
--- /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 c2366dc0ae03e8..6259f5af897bf6 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 00000000000000..7b854220f9f695
--- /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 e42af2d8a6330f..8d13bbb2bdb143 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 f0631301c3a0aa..be567a96dea12c 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 15b2aced0995eb..ca07a44cd83bdc 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
More information about the llvm-commits
mailing list