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

Peter Smith via llvm-commits llvm-commits at lists.llvm.org
Tue Apr 14 13:55:14 PDT 2026


================
@@ -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;
----------------
smithp35 wrote:

If build attributes have been read to work out the profile, then it should be possible to derive the architecture version too. For example Cortex M3 is Arm v7M and uses ETM 3.5.

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


More information about the llvm-commits mailing list