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

Peter Smith via llvm-commits llvm-commits at lists.llvm.org
Thu Apr 16 11:54:15 PDT 2026


================
@@ -0,0 +1,223 @@
+//===-- 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 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 different
+  // 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:
+  ETMDecoderImpl(const object::Binary &Binary, const Triple &Triple)
+      : Binary(Binary), TargetTriple(Triple) {}
+
+  ~ETMDecoderImpl() override {
+    if (DcdTree)
+      // Deallocate the decoder tree resources.
+      ocsd_destroy_dcd_tree(DcdTree);
+  }
+
+  // Initialize the decoder by auto-detecting the target architecture and
+  // configuring the OpenCSD decoder.
+  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.
+    ocsd_arch_version_t ArchVer = ARCH_UNKNOWN;
+    if (TargetTriple.isArmMClass()) {
+      if (ARM::parseArchVersion(TargetTriple.getArchName()) >= 8)
+        ArchVer = ARCH_V8;
+      else
+        ArchVer = ARCH_V7;
+    }
----------------
smithp35 wrote:

The Cortex-M0 and Cortex-M0+ which are Arm v6-m do not have ETM support, they have some basic tracing support called MTB which isn't supported by OpenCSD.

I suggest looking for a triple getArchName() of 6 and using `ARCH_UNKNOWN`. Then in the error message you could alter the message text to:
```
Only Arm M-profile (Cortex-M) with ETM support is currently supported.
```
Apologies for the Arm pedantry, using profile instead of class.

MTB reference:
https://developer.arm.com/documentation/ddi0486/b/introduction/about-the-coresight-mtb-m0-

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


More information about the llvm-commits mailing list