[llvm] 69c38be - Reapply "Reland "[llvm-profgen] Add support for ETM trace decoding"" … (#194730)

via llvm-commits llvm-commits at lists.llvm.org
Tue Apr 28 20:46:57 PDT 2026


Author: gulfemsavrun
Date: 2026-04-28T20:46:51-07:00
New Revision: 69c38be839913749fb34b681fe05a053f222e311

URL: https://github.com/llvm/llvm-project/commit/69c38be839913749fb34b681fe05a053f222e311
DIFF: https://github.com/llvm/llvm-project/commit/69c38be839913749fb34b681fe05a053f222e311.diff

LOG: Reapply "Reland "[llvm-profgen] Add support for ETM trace decoding"" … (#194730)

…(#194695)

This relands the original commit
e3bd61890e68303a33fdd33fbdd9abeda1037450 (#191584).

The original change was reverted in the following commits:

1) ec9d7d18bdfe21c30c94c02f14f3613f7b69a17b (#194087) 
2) c26ae41c8765fdd073c71061f21a83a161598901 (#194695)

This reland incorporates the following fixes:

1) Change LLVM_ENABLE_OPENCSD to default to OFF (opt-in).

2) Parse the OpenCSD version from ocsd_if_version.h and gate OpenCSD
support on a minimum version of 1.5.4 to avoid compilation errors.

3) Refactor the test feature detection to use a
configure-time LLVM_HAVE_OPENCSD variable instead of runtime checks in
lit.local.cfg.

Added: 
    llvm/include/llvm/ProfileData/ETMTraceDecoder.h
    llvm/lib/ProfileData/ETMTraceDecoder.cpp
    llvm/test/tools/llvm-profgen/Inputs/etm-opencsd.yaml
    llvm/test/tools/llvm-profgen/etm-arch.test
    llvm/test/tools/llvm-profgen/etm-opencsd.test

Modified: 
    llvm/docs/CommandGuide/llvm-profgen.rst
    llvm/docs/ReleaseNotes.md
    llvm/lib/ProfileData/CMakeLists.txt
    llvm/test/CMakeLists.txt
    llvm/test/lit.cfg.py
    llvm/test/lit.site.cfg.py.in
    llvm/tools/llvm-profgen/PerfReader.cpp
    llvm/tools/llvm-profgen/PerfReader.h
    llvm/tools/llvm-profgen/ProfiledBinary.cpp
    llvm/tools/llvm-profgen/ProfiledBinary.h
    llvm/tools/llvm-profgen/llvm-profgen.cpp

Removed: 
    


################################################################################
diff  --git a/llvm/docs/CommandGuide/llvm-profgen.rst b/llvm/docs/CommandGuide/llvm-profgen.rst
index 90ad729ec10b2..006cdfecf4f88 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 version 1.5.4 or higher to be enabled during the build.
+
 .. option:: --perfdata=<perfdata>, --pd
 
    Path of raw perf data created by Linux perf tool (it should be profiled
@@ -41,7 +46,7 @@ At least one of the following commands are required:
 .. note::
 
    Only one of ``--perfscript``, ``--perfdata``, ``--unsymbolized-profile``,
-   or ``--llvm-sample-profile`` may be specified at a time.  
+   ``--llvm-sample-profile``, or ``--etm`` may be specified at a time.
 
 .. option:: --binary=<string[,string,...]>
 
@@ -79,6 +84,11 @@ OPTIONS
     
    Path of debug info binary. ``llvm-profgen`` will load the DWARF info from
    it instead of the executable binary.
+
+.. option:: --target-triple=<triple>
+
+   Override the target triple for the binary. This is useful for ETM trace
+   decoding to specify the correct Arm M-profile target.
   
 .. option:: --pid
 

diff  --git a/llvm/docs/ReleaseNotes.md b/llvm/docs/ReleaseNotes.md
index d540cbf31211e..fc0114342e38e 100644
--- a/llvm/docs/ReleaseNotes.md
+++ b/llvm/docs/ReleaseNotes.md
@@ -245,6 +245,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. OpenCSD version 1.5.4 or higher is required.
+
 * `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/include/llvm/ProfileData/ETMTraceDecoder.h b/llvm/include/llvm/ProfileData/ETMTraceDecoder.h
new file mode 100644
index 0000000000000..86599fa2fb6d2
--- /dev/null
+++ b/llvm/include/llvm/ProfileData/ETMTraceDecoder.h
@@ -0,0 +1,46 @@
+//===-- 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 Triple;
+namespace object {
+class Binary;
+}
+
+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(const object::Binary &Binary, const Triple &TargetTriple,
+         uint8_t TraceID = 0x10);
+
+  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..08e5e760252e1 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,60 @@ add_llvm_component_library(LLVMProfileData
   TargetParser
   )
 
+set(LLVM_ENABLE_OPENCSD "OFF" CACHE STRING "Enable OpenCSD support in LLVMProfileData")
+
+set(LLVM_HAVE_OPENCSD OFF CACHE BOOL "Is OpenCSD available" FORCE)
+
+# Minimum OpenCSD release that exposes every symbol referenced by
+# ETMTraceDecoder.cpp. OCSD_OPFLG_CHK_RANGE_CONTINUE was introduced in
+# OpenCSD 1.5.4; older releases (e.g. the 1.2.0 shipped by Debian/Ubuntu)
+# build the OpenCSD library but cannot satisfy our use of the C API.
+set(LLVM_OPENCSD_MIN_VERSION "1.5.4")
+
+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)
+
+  set(OPENCSD_VERSION "")
+  set(OPENCSD_VERSION_OK OFF)
+  if(OPENCSD_LIB AND OPENCSD_INCLUDE)
+    # The header exposes an OCSD_VER_STRING macro of the form "M.m.p"
+    # which we extract for a robust version comparison.
+    file(READ "${OPENCSD_INCLUDE}/opencsd/ocsd_if_version.h" _opencsd_ver_h)
+    string(REGEX MATCH "OCSD_VER_STRING[ \t]+\"([0-9]+\\.[0-9]+\\.[0-9]+)\""
+                 _ver_match "${_opencsd_ver_h}")
+    set(OPENCSD_VERSION "${CMAKE_MATCH_1}")
+    if(OPENCSD_VERSION AND
+       NOT OPENCSD_VERSION VERSION_LESS LLVM_OPENCSD_MIN_VERSION)
+      set(OPENCSD_VERSION_OK ON)
+    endif()
+  endif()
+
+  if(OPENCSD_LIB AND OPENCSD_INCLUDE AND OPENCSD_VERSION_OK)
+    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 (found ${OPENCSD_VERSION}).")
+    set(LLVM_HAVE_OPENCSD ON CACHE BOOL "Is OpenCSD available" FORCE)
+  elseif(OPENCSD_LIB AND OPENCSD_INCLUDE)
+    if(LLVM_ENABLE_OPENCSD STREQUAL "ON")
+      message(FATAL_ERROR
+        "OpenCSD found at ${OPENCSD_LIB} but its version "
+        "(${OPENCSD_VERSION}) is older than the required "
+        "${LLVM_OPENCSD_MIN_VERSION}. Install a newer OpenCSD or "
+        "configure with -DLLVM_ENABLE_OPENCSD=OFF.")
+    else()
+      message(STATUS
+        "LLVMProfileData: OpenCSD ${OPENCSD_VERSION} is older than "
+        "${LLVM_OPENCSD_MIN_VERSION}; ETM decoding support disabled.")
+    endif()
+  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..3d6a9846ac25c
--- /dev/null
+++ b/llvm/lib/ProfileData/ETMTraceDecoder.cpp
@@ -0,0 +1,251 @@
+//===-- 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"
+#include "llvm/TargetParser/ARMTargetParser.h"
+
+#ifdef HAVE_OPENCSD
+#include "opencsd/c_api/opencsd_c_api.h"
+
+namespace llvm {
+
+namespace {
+
+class HardwareTraceConfig {
+public:
+  virtual ~HardwareTraceConfig() = default;
+};
+
+class ETMTraceConfig : public HardwareTraceConfig {
+public:
+  ocsd_etmv4_cfg Cfg{};
+  uint8_t TraceID;
+
+  ETMTraceConfig(const Triple &TargetTriple, uint8_t TraceID)
+      : TraceID(TraceID) {
+    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
+        // For version 6 (Cortex-M0) and others.
+        ArchVer = ARCH_UNKNOWN;
+    }
+    // Initialize the decoder for Arm M-profile 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.
+    Cfg.reg_traceidr = TraceID;
+  }
+
+  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;
+  const 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 
diff erent
+  // memory regions.
+  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();
+      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()) {
+      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, Path.c_str()) != 0) {
+        return createStringError(
+            inconvertibleErrorCode(),
+            "OpenCSD: Failed to map ELF executable segments.");
+      }
+    }
+    return Error::success();
+  }
+
+public:
+  uint8_t TraceID;
+
+  ETMDecoderImpl(const object::Binary &Binary, const Triple &Triple,
+                 uint8_t TraceID)
+      : Binary(Binary), TargetTriple(Triple), TraceID(TraceID) {}
+
+  ~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() {
+    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.
+    ETMTraceConfig Config(TargetTriple, TraceID);
+    if (Error E = Config.validate())
+      return E;
+
+    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.Cfg, &Config.TraceID) != 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, Binary))
+      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(const object::Binary &Binary, const Triple &Triple,
+                   uint8_t TraceID) {
+  auto Decoder = std::make_unique<ETMDecoderImpl>(Binary, Triple, TraceID);
+  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(const object::Binary & /*Binary*/, const Triple & /*Triple*/,
+                   uint8_t /*TraceID*/) {
+  return createStringError(inconvertibleErrorCode(), "OpenCSD not enabled.");
+}
+
+} // namespace llvm
+
+#endif // HAVE_OPENCSD

diff  --git a/llvm/test/CMakeLists.txt b/llvm/test/CMakeLists.txt
index a027cd754135c..54094b02604f9 100644
--- a/llvm/test/CMakeLists.txt
+++ b/llvm/test/CMakeLists.txt
@@ -31,6 +31,7 @@ llvm_canonicalize_cmake_booleans(
   LLVM_APPEND_VC_REV
   LLVM_HAS_LOGF128
   LLVM_ENABLE_ONDISK_CAS
+  LLVM_HAVE_OPENCSD
   )
 
 configure_lit_site_cfg(

diff  --git a/llvm/test/lit.cfg.py b/llvm/test/lit.cfg.py
index 704151e0228e4..e44079aa5cd26 100644
--- a/llvm/test/lit.cfg.py
+++ b/llvm/test/lit.cfg.py
@@ -589,6 +589,9 @@ def enable_ptxas(ptxas_executable):
 if config.have_tf_aot:
     config.available_features.add("have_tf_aot")
 
+if getattr(config, "have_opencsd", False):
+    config.available_features.add("opencsd")
+
 if config.have_tflite:
     config.available_features.add("have_tflite")
 

diff  --git a/llvm/test/lit.site.cfg.py.in b/llvm/test/lit.site.cfg.py.in
index 6fe48f21cc685..944779c0d7f2f 100644
--- a/llvm/test/lit.site.cfg.py.in
+++ b/llvm/test/lit.site.cfg.py.in
@@ -68,6 +68,7 @@ config.have_vc_rev = @LLVM_APPEND_VC_REV@
 config.force_vc_rev = "@LLVM_FORCE_VC_REVISION@"
 config.has_logf128 = @LLVM_HAS_LOGF128@
 config.have_ondisk_cas = @LLVM_ENABLE_ONDISK_CAS@
+config.have_opencsd = @LLVM_HAVE_OPENCSD@
 
 import lit.llvm
 lit.llvm.initialize(lit_config, config)

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..418a91fec265c
--- /dev/null
+++ b/llvm/test/tools/llvm-profgen/etm-arch.test
@@ -0,0 +1,81 @@
+; REQUIRES: opencsd, arm-registered-target
+; 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 Arm M-profile (Cortex-M) with ETM support is currently supported.
+
+; X86: OpenCSD: Unsupported processor architecture. Only Arm M-profile (Cortex-M) with ETM support 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..5148455684a88
--- /dev/null
+++ b/llvm/test/tools/llvm-profgen/etm-opencsd.test
@@ -0,0 +1,19 @@
+; REQUIRES: opencsd, arm-registered-target
+; 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 target-triple override works.
+; RUN: llvm-profgen --etm=%t.trace --binary=%t.bin --output=%t2.prof --format=text --target-triple=thumbv7m-none-eabi
+; RUN: ls %t2.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=%t3.prof 2>&1 | FileCheck %s --check-prefix=NOSYNC
+
+; NOSYNC: error: No synchronization header (0x80) found in the bitstream.

diff  --git a/llvm/tools/llvm-profgen/PerfReader.cpp b/llvm/tools/llvm-profgen/PerfReader.cpp
index 7c51c08afdbe0..101530b24cdba 100644
--- a/llvm/tools/llvm-profgen/PerfReader.cpp
+++ b/llvm/tools/llvm-profgen/PerfReader.cpp
@@ -11,12 +11,14 @@
 #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"
 #include "llvm/Support/Process.h"
 #include "llvm/Support/Timer.h"
 #include "llvm/Support/ToolOutputFile.h"
+#include "llvm/TargetParser/Triple.h"
 
 #define DEBUG_TYPE "perf-reader"
 
@@ -351,32 +353,31 @@ 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));
+        new UnsymbolizedProfileReader(Binary, Input.InputFilePath));
     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.InputFilePath);
+  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.InputFilePath, PIDFilter));
+  } else if (Input.Content == PerfContent::LBR) {
+    PerfReader.reset(new LBRPerfReader(Binary, Input.InputFilePath, PIDFilter));
   } else {
     exitWithError("Unsupported perfscript!");
   }
@@ -455,11 +456,11 @@ 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;
+  StringRef PerfData = File.InputFilePath;
   // Run perf script to retrieve PIDs matching binary we're interested in.
   auto PerfExecutable = sys::Process::FindInEnvPath("PATH", "perf");
   if (!PerfExecutable) {
@@ -521,7 +522,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};
 }
 
@@ -1423,5 +1424,59 @@ void PerfScriptReader::parsePerfTraces() {
 
 SmallVector<CleanupInstaller, 2> PerfScriptReader::TempFileCleanups;
 
+void ETMReader::recordProcessedRange(uint64_t Start, uint64_t End,
+                                     uint64_t Count) {
+  assert(!Counters.empty() && "Counters should not be empty!");
+  auto &Counter = Counters.begin()->second;
+  Counter.recordRangeCount(Start, End, Count);
+}
+
+class ETMCallback : public ETMDecoder::Callback {
+  ETMReader *Reader;
+
+public:
+  ETMCallback(ETMReader *R) : Reader(R) {}
+  void processInstructionRange(uint64_t Start, uint64_t End) override {
+    Reader->recordProcessedRange(Start, End, 1);
+  }
+};
+
+void ETMReader::parseETMTraces() {
+  auto BufferOrErr = MemoryBuffer::getFile(TraceFile);
+  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>();
+  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
+  // 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);
+
+  auto DecoderOrErr = ETMDecoder::create(
+      Binary->getBinary(), Binary->getTriple(), static_cast<uint8_t>(TraceID));
+
+  if (!DecoderOrErr)
+    exitWithError(toString(DecoderOrErr.takeError()));
+  auto Decoder = std::move(*DecoderOrErr);
+
+  ETMCallback CB(this);
+  if (Error E = Decoder->processTrace(TraceSlice, CB))
+    exitWithError(toString(std::move(E)));
+}
+
 } // end namespace sampleprof
 } // end namespace llvm

diff  --git a/llvm/tools/llvm-profgen/PerfReader.h b/llvm/tools/llvm-profgen/PerfReader.h
index 9011e0b644261..d7b527e1fe549 100644
--- a/llvm/tools/llvm-profgen/PerfReader.h
+++ b/llvm/tools/llvm-profgen/PerfReader.h
@@ -58,12 +58,12 @@ 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.
   UnsymbolizedProfile = 3, // Unsymbolized profile generated by llvm-profgen.
-
+  ETMFormat = 4,           // Raw ETM format.
 };
 
 // The type of perfscript content.
@@ -73,9 +73,9 @@ enum PerfContent {
   LBRStack = 2, // Hybrid sample including call stack and LBR stack.
 };
 
-struct PerfInputFile {
-  std::string InputFile;
-  PerfFormat Format = PerfFormat::UnknownFormat;
+struct InputFile {
+  std::string InputFilePath;
+  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,9 +620,9 @@ class PerfScriptReader : public PerfReaderBase {
                                         MMapEvent &MMap);
 
   // Generate perf script from perf data
-  static PerfInputFile convertPerfDataToTrace(ProfiledBinary *Binary,
-                                              bool SkipPID, PerfInputFile &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);
 
@@ -750,6 +750,21 @@ class UnsymbolizedProfileReader : public PerfReaderBase {
   std::unordered_set<std::string> ContextStrSet;
 };
 
+class ETMReader {
+public:
+  ETMReader(ProfiledBinary *Binary, StringRef TraceFile, uint8_t TraceID)
+      : Binary(Binary), TraceFile(TraceFile), TraceID(TraceID) {}
+  void parseETMTraces();
+  void recordProcessedRange(uint64_t Start, uint64_t End, uint64_t Count);
+  const ContextSampleCounterMap &getSampleCounters() const { return Counters; }
+
+private:
+  ProfiledBinary *Binary = nullptr;
+  StringRef TraceFile;
+  uint8_t TraceID;
+  ContextSampleCounterMap Counters;
+};
+
 } // end namespace sampleprof
 } // end namespace llvm
 

diff  --git a/llvm/tools/llvm-profgen/ProfiledBinary.cpp b/llvm/tools/llvm-profgen/ProfiledBinary.cpp
index 6d355b6141e2c..fb7dd1d01ba93 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);
@@ -238,7 +237,10 @@ void ProfiledBinary::load() {
     exitWithError("not a valid ELF/COFF image", Path);
 
   auto *Obj = cast<ObjectFile>(&ExeBinary);
-  TheTriple = Obj->makeTriple();
+  if (!TripleStr.empty())
+    TheTriple = Triple(TripleStr);
+  else
+    TheTriple = Obj->makeTriple();
 
   LLVM_DEBUG(dbgs() << "Loading " << Path << "\n");
 

diff  --git a/llvm/tools/llvm-profgen/ProfiledBinary.h b/llvm/tools/llvm-profgen/ProfiledBinary.h
index af8d2dbb8eb06..4a2c4cec8b49f 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.
@@ -414,6 +416,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
@@ -421,11 +428,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.
@@ -435,6 +438,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 821e3e4613c7c..cba5f1cd3d878 100644
--- a/llvm/tools/llvm-profgen/llvm-profgen.cpp
+++ b/llvm/tools/llvm-profgen/llvm-profgen.cpp
@@ -86,6 +86,20 @@ 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));
+
+static cl::opt<unsigned> ETMTraceID(
+    "etm-trace-id", cl::init(0x10),
+    cl::desc("CoreSight Trace ID (CSID) used to route ETM trace data."),
+    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.
@@ -96,15 +110,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);
     }
 
@@ -119,6 +136,7 @@ static void validateCommandLine() {
     CheckFileExists(HasPerfScript, PerfScriptFilename);
     CheckFileExists(HasUnsymbolizedProfile, UnsymbolizedProfFilename);
     CheckFileExists(HasSampleProfile, SampleProfFilename);
+    CheckFileExists(HasEtm, ETMPath);
   }
 
   if (!llvm::sys::fs::exists(BinaryPath)) {
@@ -135,17 +153,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.InputFilePath = PerfDataFilename;
+    File.Format = InputFormat::PerfData;
   } else if (PerfScriptFilename.getNumOccurrences()) {
-    File.InputFile = PerfScriptFilename;
-    File.Format = PerfFormat::PerfScript;
+    File.InputFilePath = PerfScriptFilename;
+    File.Format = InputFormat::PerfScript;
   } else if (UnsymbolizedProfFilename.getNumOccurrences()) {
-    File.InputFile = UnsymbolizedProfFilename;
-    File.Format = PerfFormat::UnsymbolizedProfile;
+    File.InputFilePath = UnsymbolizedProfFilename;
+    File.Format = InputFormat::UnsymbolizedProfile;
+  } else if (ETMPath.getNumOccurrences()) {
+    File.InputFilePath = ETMPath;
+    File.Format = InputFormat::ETMFormat;
   }
   return File;
 }
@@ -167,6 +188,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;
 
@@ -189,35 +212,47 @@ int main(int argc, const char *argv[]) {
     std::optional<uint32_t> PIDFilter;
     if (ProcessId.getNumOccurrences())
       PIDFilter = ProcessId;
-    PerfInputFile PerfFile = getPerfInputFile();
-    std::unique_ptr<PerfReaderBase> Reader =
-        PerfReaderBase::create(Binary.get(), PerfFile, 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());
-        });
+    InputFile File = getInputFile();
+    const ContextSampleCounterMap *Counters = nullptr;
+    bool ProfileIsCS = false;
+    std::unique_ptr<ETMReader> EtmReader;
+    std::unique_ptr<PerfReaderBase> PerfReader;
+
+    if (File.Format == InputFormat::ETMFormat) {
+      EtmReader = std::make_unique<ETMReader>(Binary.get(), File.InputFilePath,
+                                              static_cast<uint8_t>(ETMTraceID));
+      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