[llvm] [Draft] Integrate EmitC-translated MLGO models (PR #209829)

ioana ghiban via llvm-commits llvm-commits at lists.llvm.org
Thu Jul 16 02:28:23 PDT 2026


https://github.com/ioghiban updated https://github.com/llvm/llvm-project/pull/209829

>From f1714dbc43381a6f02ef39cdc6adf703d0238245 Mon Sep 17 00:00:00 2001
From: Ioana Ghiban <ioana.ghiban at arm.com>
Date: Mon, 13 Jul 2026 17:08:13 +0200
Subject: [PATCH 1/2] Integrate EmitC translated model

---
 .../llvm/Analysis/EmitCInlinerSizeModel.h     |  82 ++++++
 .../llvm/CodeGen/EmitCRegAllocEvictModel.h    |  98 +++++++
 llvm/lib/Analysis/CMakeLists.txt              |  51 ++--
 llvm/lib/Analysis/EmitCInlinerSizeModel.cpp   | 250 ++++++++++++++++++
 llvm/lib/Analysis/MLInlineAdvisor.cpp         |   5 +-
 llvm/lib/CodeGen/CMakeLists.txt               |  17 +-
 llvm/lib/CodeGen/EmitCRegAllocEvictModel.cpp  | 209 +++++++++++++++
 llvm/lib/CodeGen/MLRegAllocEvictAdvisor.cpp   |   7 +-
 .../MLRegAlloc/default-eviction-advisor.ll    |   1 +
 llvm/test/lit.cfg.py                          |   3 +
 llvm/test/lit.site.cfg.py.in                  |   1 +
 11 files changed, 700 insertions(+), 24 deletions(-)
 create mode 100644 llvm/include/llvm/Analysis/EmitCInlinerSizeModel.h
 create mode 100644 llvm/include/llvm/CodeGen/EmitCRegAllocEvictModel.h
 create mode 100644 llvm/lib/Analysis/EmitCInlinerSizeModel.cpp
 create mode 100644 llvm/lib/CodeGen/EmitCRegAllocEvictModel.cpp

diff --git a/llvm/include/llvm/Analysis/EmitCInlinerSizeModel.h b/llvm/include/llvm/Analysis/EmitCInlinerSizeModel.h
new file mode 100644
index 0000000000000..63e298e4ce9fa
--- /dev/null
+++ b/llvm/include/llvm/Analysis/EmitCInlinerSizeModel.h
@@ -0,0 +1,82 @@
+//===- EmitCInlinerSizeModel.h - EmitC inliner model wrapper ----*- 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
+//
+//===----------------------------------------------------------------------===//
+//
+/// Declares the wrapper around the EmitC-translated MLGO inliner model.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIB_ANALYSIS_EMITCINLINERSIZEMODEL_H
+#define LLVM_LIB_ANALYSIS_EMITCINLINERSIZEMODEL_H
+
+#include <array>
+#include <cstdint>
+#include <string>
+
+namespace llvm {
+
+class EmitCInlinerSizeModel final {
+public:
+  int LookupArgIndex(const std::string &Name);
+  int LookupResultIndex(const std::string &Name);
+  void *arg_data(int Index);
+  void *result_data(int Index);
+  void Run();
+
+private:
+  enum ArgIndex : int {
+    DeadBlocks = 0,
+    CaseClusterPenalty,
+    SroaSavings,
+    JumpTablePenalty,
+    CallsiteHeight,
+    CalleeBasicBlockCount,
+    CallArgumentSetup,
+    LoweredCallArgSetup,
+    SimplifiedInstructions,
+    NrCtantParams,
+    IsMultipleBlocks,
+    LoadElimination,
+    EdgeCount,
+    CallerUsers,
+    CallerConditionallyExecutedBlocks,
+    ConstantOffsetPtrArgs,
+    CallsiteCost,
+    CallerBasicBlockCount,
+    LoadRelativeIntrinsic,
+    IndirectCallPenalty,
+    CostEstimate,
+    Threshold,
+    NestedInlineCostEstimate,
+    UnsimplifiedCommonInstructions,
+    SroaLosses,
+    NumLoops,
+    SwitchPenalty,
+    CalleeUsers,
+    NodeCount,
+    ConstantArgs,
+    LastCallToStaticBonus,
+    ColdCCPenalty,
+    CalleeConditionallyExecutedBlocks,
+    CallPenalty,
+    NestedInlines,
+
+    NumArgs
+  };
+
+  std::array<std::array<int64_t, 1>, NumArgs> Inputs{};
+  std::array<int64_t, 1> Result{};
+
+  std::array<int64_t, 1> DummyInliningDefault{};
+  std::array<int32_t, 1> DummyStepType{};
+  std::array<float, 1> DummyDiscount{};
+  std::array<float, 1> DummyReward{};
+};
+
+} // namespace llvm
+
+#endif
diff --git a/llvm/include/llvm/CodeGen/EmitCRegAllocEvictModel.h b/llvm/include/llvm/CodeGen/EmitCRegAllocEvictModel.h
new file mode 100644
index 0000000000000..41a19ec62522e
--- /dev/null
+++ b/llvm/include/llvm/CodeGen/EmitCRegAllocEvictModel.h
@@ -0,0 +1,98 @@
+//===- EmitCRegAllocEvictModel.h - EmitC regalloc model wrapper -*- 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
+//
+//===----------------------------------------------------------------------===//
+//
+/// \file
+/// Declares the wrapper around the EmitC-translated MLGO regalloc eviction
+/// model.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CODEGEN_EMITCREGALLOCEVICTMODEL_H
+#define LLVM_CODEGEN_EMITCREGALLOCEVICTMODEL_H
+
+#include <cmath>
+#include <cstddef>
+#include <cstdint>
+#include <string>
+
+namespace llvm {
+
+class EmitCRegAllocEvictModel final {
+public:
+  int LookupArgIndex(const std::string &Name);
+  int LookupResultIndex(const std::string &Name);
+  void *arg_data(int Index);
+  void *result_data(int Index);
+  void Run();
+
+private:
+  static constexpr std::size_t InterferenceCount = 33;
+
+  using F32InterferenceTensor = float[1][InterferenceCount];
+  using I64InterferenceTensor = int64_t[1][InterferenceCount];
+  using F32Scalar = float[1];
+  using I32Scalar = int32_t[1];
+  using I64Scalar = int64_t[1];
+
+  enum ArgIndex : int {
+    Mask = 0,
+    IsFree,
+    NrUrgent,
+    NrBrokenHints,
+    IsHint,
+    IsLocal,
+    NrRematerializable,
+    NrDefsAndUses,
+    WeighedReadsByMax,
+    WeighedWritesByMax,
+    WeighedReadWritesByMax,
+    WeighedIndvarsByMax,
+    HintWeightsByMax,
+    StartBBFreqByMax,
+    EndBBFreqByMax,
+    HottestBBFreqByMax,
+    LiverangeSize,
+    UseDefDensity,
+    MaxStage,
+    MinStage,
+    Progress,
+
+    NumArgs
+  };
+
+  I64InterferenceTensor MaskInput{};
+  I64InterferenceTensor IsFreeInput{};
+  F32InterferenceTensor NrUrgentInput{};
+  F32InterferenceTensor NrBrokenHintsInput{};
+  I64InterferenceTensor IsHintInput{};
+  I64InterferenceTensor IsLocalInput{};
+  F32InterferenceTensor NrRematerializableInput{};
+  F32InterferenceTensor NrDefsAndUsesInput{};
+  F32InterferenceTensor WeighedReadsByMaxInput{};
+  F32InterferenceTensor WeighedWritesByMaxInput{};
+  F32InterferenceTensor WeighedReadWritesByMaxInput{};
+  F32InterferenceTensor WeighedIndvarsByMaxInput{};
+  F32InterferenceTensor HintWeightsByMaxInput{};
+  F32InterferenceTensor StartBBFreqByMaxInput{};
+  F32InterferenceTensor EndBBFreqByMaxInput{};
+  F32InterferenceTensor HottestBBFreqByMaxInput{};
+  F32InterferenceTensor LiverangeSizeInput{};
+  F32InterferenceTensor UseDefDensityInput{};
+  I64InterferenceTensor MaxStageInput{};
+  I64InterferenceTensor MinStageInput{};
+  F32Scalar ProgressInput{};
+
+  I32Scalar DummyStepType{};
+  F32Scalar DummyDiscount{};
+  F32Scalar DummyReward{};
+  I64Scalar Result{};
+};
+
+} // namespace llvm
+
+#endif
diff --git a/llvm/lib/Analysis/CMakeLists.txt b/llvm/lib/Analysis/CMakeLists.txt
index f3586c66cb056..474d96df9b619 100644
--- a/llvm/lib/Analysis/CMakeLists.txt
+++ b/llvm/lib/Analysis/CMakeLists.txt
@@ -1,26 +1,31 @@
-if (DEFINED LLVM_HAVE_TF_AOT OR LLVM_HAVE_TFLITE)
-  include(TensorFlowCompile)
-  set(LLVM_INLINER_MODEL_PATH_DEFAULT "models/inliner-Oz")
+option(LLVM_USE_EMITC_INLINER_MODEL
+  "Use the in-tree EmitC inliner model instead of TensorFlow AOT"
+  OFF)
 
-  set(LLVM_INLINER_MODEL_CURRENT_URL "<UNSPECIFIED>" CACHE STRING "URL to download the LLVM inliner model")
+if (LLVM_HAVE_TFLITE)
+  list(APPEND MLLinkDeps
+    tensorflow-lite::tensorflow-lite)
+endif()
 
-  if (DEFINED LLVM_HAVE_TF_AOT)
-    tf_find_and_compile(
-      ${LLVM_INLINER_MODEL_PATH}
-      ${LLVM_INLINER_MODEL_CURRENT_URL}
-      ${LLVM_INLINER_MODEL_PATH_DEFAULT}
-      "models/gen-inline-oz-test-model.py"
-      serve
-      action
-      InlinerSizeModel
-      llvm::InlinerSizeModel
-    )
-  endif()
+if (LLVM_USE_EMITC_INLINER_MODEL)
+  list(APPEND LLVM_COMPILE_DEFINITIONS
+    LLVM_HAVE_EMITC_INLINERSIZEMODEL)
+elseif (DEFINED LLVM_HAVE_TF_AOT)
+  include(TensorFlowCompile)
+  set(LLVM_INLINER_MODEL_PATH_DEFAULT "models/inliner-Oz")
+  set(LLVM_INLINER_MODEL_CURRENT_URL "<UNSPECIFIED>" CACHE STRING
+      "URL to download the LLVM inliner model")
 
-  if (LLVM_HAVE_TFLITE)
-    list(APPEND MLLinkDeps
-      tensorflow-lite::tensorflow-lite)
-  endif()
+  tf_find_and_compile(
+    ${LLVM_INLINER_MODEL_PATH}
+    ${LLVM_INLINER_MODEL_CURRENT_URL}
+    ${LLVM_INLINER_MODEL_PATH_DEFAULT}
+    "models/gen-inline-oz-test-model.py"
+    serve
+    action
+    InlinerSizeModel
+    llvm::InlinerSizeModel
+  )
 endif()
 
 # The implementation of ConstantFolding.cpp relies on the use of math functions
@@ -77,6 +82,7 @@ add_llvm_component_library(LLVMAnalysis
   DominanceFrontier.cpp
   DXILResource.cpp
   DXILMetadataAnalysis.cpp
+  EmitCInlinerSizeModel.cpp
   EphemeralValuesCache.cpp
   FloatingPointPredicateUtils.cpp
   FunctionPropertiesAnalysis.cpp
@@ -187,6 +193,11 @@ add_llvm_component_library(LLVMAnalysis
   TargetParser
   )
 
+if (LLVM_USE_EMITC_INLINER_MODEL)
+  target_compile_definitions(LLVMAnalysis
+    PRIVATE LLVM_HAVE_EMITC_INLINERSIZEMODEL)
+endif()
+
 include(CheckCXXSymbolExists)
 check_cxx_symbol_exists(logf128 math.h HAS_LOGF128)
 if(HAS_LOGF128)
diff --git a/llvm/lib/Analysis/EmitCInlinerSizeModel.cpp b/llvm/lib/Analysis/EmitCInlinerSizeModel.cpp
new file mode 100644
index 0000000000000..a8e0094737bd6
--- /dev/null
+++ b/llvm/lib/Analysis/EmitCInlinerSizeModel.cpp
@@ -0,0 +1,250 @@
+//===- EmitCInlinerSizeModel.cpp - EmitC inliner model wrapper ------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+/// This file implements the wrapper around the EmitC-translated MLGO inliner
+/// model.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Analysis/EmitCInlinerSizeModel.h"
+
+#include "llvm/ADT/StringSwitch.h"
+#include "llvm/Support/ErrorHandling.h"
+
+#include <math.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <type_traits>
+
+#if defined(__clang__)
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wmissing-braces"
+#endif
+
+namespace llvm::emitc_inliner_model {
+#define main action
+#include "llvm/Analysis/EmitCInlinerSizeModel.inc"
+#undef main
+} // namespace llvm::emitc_inliner_model
+
+#if defined(__clang__)
+#pragma clang diagnostic pop
+#endif
+
+using namespace llvm;
+
+namespace {
+template <typename T> inline constexpr bool AlwaysFalse = false;
+using I64Ptr = int64_t *;
+using I32Ptr = int32_t *;
+using F32Ptr = float *;
+
+using InlinerProductionActionTy =
+    int64_t (*)(I64Ptr, I64Ptr, I64Ptr, I64Ptr, I64Ptr, I64Ptr, I64Ptr, I64Ptr,
+                I64Ptr, I64Ptr, I64Ptr, I64Ptr, I64Ptr, I64Ptr, I64Ptr, I64Ptr,
+                I64Ptr, I64Ptr, I64Ptr, I64Ptr, I64Ptr, I64Ptr, I64Ptr, I64Ptr,
+                I64Ptr, I64Ptr, I64Ptr, I32Ptr, I64Ptr, I64Ptr, I64Ptr, I64Ptr,
+                I64Ptr, I64Ptr, I64Ptr, I64Ptr, F32Ptr, I64Ptr, F32Ptr);
+using InlinerMockActionTy = int64_t (*)(I64Ptr, I64Ptr, I64Ptr, I64Ptr, I64Ptr,
+                                        I64Ptr, I64Ptr, I64Ptr, I64Ptr, I64Ptr,
+                                        I64Ptr, I64Ptr, I64Ptr, I64Ptr, I64Ptr,
+                                        I64Ptr, I64Ptr, I64Ptr, I64Ptr, I64Ptr,
+                                        I64Ptr, I64Ptr, I64Ptr, I64Ptr, I64Ptr,
+                                        I64Ptr, I64Ptr, I32Ptr, I64Ptr, I64Ptr,
+                                        I64Ptr, I64Ptr, I64Ptr, I64Ptr, I64Ptr,
+                                        I64Ptr, I64Ptr, F32Ptr, I64Ptr, F32Ptr);
+
+struct InlinerRunInputs {
+  I64Ptr deadBlocks;
+  I64Ptr caseClusterPenalty;
+  I64Ptr sroaSavings;
+  I64Ptr jumpTablePenalty;
+  I64Ptr callsiteHeight;
+  I64Ptr calleeBasicBlockCount;
+  I64Ptr callArgumentSetup;
+  I64Ptr loweredCallArgSetup;
+  I64Ptr simplifiedInstructions;
+  I64Ptr nrCtantParams;
+  I64Ptr isMultipleBlocks;
+  I64Ptr loadElimination;
+  I64Ptr edgeCount;
+  I64Ptr callerUsers;
+  I64Ptr callerConditionallyExecutedBlocks;
+  I64Ptr constantOffsetPtrArgs;
+  I64Ptr callsiteCost;
+  I64Ptr callerBasicBlockCount;
+  I64Ptr loadRelativeIntrinsic;
+  I64Ptr indirectCallPenalty;
+  I64Ptr costEstimate;
+  I64Ptr threshold;
+  I64Ptr nestedInlineCostEstimate;
+  I64Ptr unsimplifiedCommonInstructions;
+  I64Ptr sroaLosses;
+  I64Ptr numLoops;
+  I64Ptr switchPenalty;
+  I64Ptr calleeUsers;
+  I64Ptr nodeCount;
+  I64Ptr constantArgs;
+  I64Ptr lastCallToStaticBonus;
+  I64Ptr coldCCPenalty;
+  I64Ptr calleeConditionallyExecutedBlocks;
+  I64Ptr callPenalty;
+  I64Ptr nestedInlines;
+  I32Ptr dummyStepType;
+  F32Ptr dummyDiscount;
+  F32Ptr dummyReward;
+  I64Ptr dummyInliningDefault;
+};
+
+template <typename ActionTy>
+int64_t runEmitCInlinerAction(const InlinerRunInputs &I) {
+  if constexpr (std::is_same_v<ActionTy, InlinerProductionActionTy>) {
+    return static_cast<ActionTy>(emitc_inliner_model::action)(
+        I.callsiteCost, I.isMultipleBlocks, I.callerConditionallyExecutedBlocks,
+        I.dummyInliningDefault, I.coldCCPenalty,
+        I.calleeConditionallyExecutedBlocks, I.calleeUsers,
+        I.calleeBasicBlockCount, I.nrCtantParams, I.loadRelativeIntrinsic,
+        I.jumpTablePenalty, I.unsimplifiedCommonInstructions,
+        I.indirectCallPenalty, I.loadElimination, I.callPenalty, I.costEstimate,
+        I.caseClusterPenalty, I.nodeCount, I.callArgumentSetup, I.sroaSavings,
+        I.loweredCallArgSetup, I.threshold, I.deadBlocks, I.constantArgs,
+        I.sroaLosses, I.simplifiedInstructions, I.numLoops, I.dummyStepType,
+        I.edgeCount, I.nestedInlines, I.callerBasicBlockCount,
+        I.lastCallToStaticBonus, I.nestedInlineCostEstimate, I.callsiteHeight,
+        I.constantOffsetPtrArgs, I.switchPenalty, I.dummyDiscount,
+        I.callerUsers, I.dummyReward);
+  } else if constexpr (std::is_same_v<ActionTy, InlinerMockActionTy>) {
+    return static_cast<ActionTy>(emitc_inliner_model::action)(
+        I.callerBasicBlockCount, I.callerConditionallyExecutedBlocks,
+        I.callerUsers, I.calleeBasicBlockCount,
+        I.calleeConditionallyExecutedBlocks, I.calleeUsers, I.nrCtantParams,
+        I.nodeCount, I.edgeCount, I.callsiteHeight, I.costEstimate,
+        I.sroaSavings, I.sroaLosses, I.loadElimination, I.callPenalty,
+        I.callArgumentSetup, I.loadRelativeIntrinsic, I.loweredCallArgSetup,
+        I.indirectCallPenalty, I.jumpTablePenalty, I.caseClusterPenalty,
+        I.switchPenalty, I.unsimplifiedCommonInstructions, I.numLoops,
+        I.deadBlocks, I.simplifiedInstructions, I.constantArgs, I.dummyStepType,
+        I.constantOffsetPtrArgs, I.callsiteCost, I.coldCCPenalty,
+        I.lastCallToStaticBonus, I.isMultipleBlocks, I.nestedInlines,
+        I.nestedInlineCostEstimate, I.threshold, I.dummyInliningDefault,
+        I.dummyDiscount, I.callerUsers, I.dummyReward);
+  } else {
+    static_assert(AlwaysFalse<ActionTy>,
+                  "Unsupported EmitC inliner model signature");
+  }
+}
+} // namespace
+
+int EmitCInlinerSizeModel::LookupArgIndex(const std::string &Name) {
+  return StringSwitch<int>(Name)
+      .Case("feed_dead_blocks", DeadBlocks)
+      .Case("feed_case_cluster_penalty", CaseClusterPenalty)
+      .Case("feed_sroa_savings", SroaSavings)
+      .Case("feed_jump_table_penalty", JumpTablePenalty)
+      .Case("feed_callsite_height", CallsiteHeight)
+      .Case("feed_callee_basic_block_count", CalleeBasicBlockCount)
+      .Case("feed_call_argument_setup", CallArgumentSetup)
+      .Case("feed_lowered_call_arg_setup", LoweredCallArgSetup)
+      .Case("feed_simplified_instructions", SimplifiedInstructions)
+      .Case("feed_nr_ctant_params", NrCtantParams)
+      .Case("feed_is_multiple_blocks", IsMultipleBlocks)
+      .Case("feed_load_elimination", LoadElimination)
+      .Case("feed_edge_count", EdgeCount)
+      .Case("feed_caller_users", CallerUsers)
+      .Case("feed_caller_conditionally_executed_blocks",
+            CallerConditionallyExecutedBlocks)
+      .Case("feed_constant_offset_ptr_args", ConstantOffsetPtrArgs)
+      .Case("feed_callsite_cost", CallsiteCost)
+      .Case("feed_caller_basic_block_count", CallerBasicBlockCount)
+      .Case("feed_load_relative_intrinsic", LoadRelativeIntrinsic)
+      .Case("feed_indirect_call_penalty", IndirectCallPenalty)
+      .Case("feed_cost_estimate", CostEstimate)
+      .Case("feed_threshold", Threshold)
+      .Case("feed_nested_inline_cost_estimate", NestedInlineCostEstimate)
+      .Case("feed_unsimplified_common_instructions",
+            UnsimplifiedCommonInstructions)
+      .Case("feed_sroa_losses", SroaLosses)
+      .Case("feed_num_loops", NumLoops)
+      .Case("feed_switch_penalty", SwitchPenalty)
+      .Case("feed_callee_users", CalleeUsers)
+      .Case("feed_node_count", NodeCount)
+      .Case("feed_constant_args", ConstantArgs)
+      .Case("feed_last_call_to_static_bonus", LastCallToStaticBonus)
+      .Case("feed_cold_cc_penalty", ColdCCPenalty)
+      .Case("feed_callee_conditionally_executed_blocks",
+            CalleeConditionallyExecutedBlocks)
+      .Case("feed_call_penalty", CallPenalty)
+      .Case("feed_nested_inlines", NestedInlines)
+      .Default(-1);
+}
+
+int EmitCInlinerSizeModel::LookupResultIndex(const std::string &Name) {
+  return Name == "fetch_inlining_decision" ? 0 : -1;
+}
+
+void *EmitCInlinerSizeModel::arg_data(int Index) {
+  if (Index < 0 || Index >= NumArgs)
+    llvm_unreachable("invalid EmitC inliner input index");
+  return Inputs[Index].data();
+}
+
+void *EmitCInlinerSizeModel::result_data(int Index) {
+  if (Index != 0)
+    llvm_unreachable("invalid EmitC inliner result index");
+  return Result.data();
+}
+
+void EmitCInlinerSizeModel::Run() {
+  using ActionTy = decltype(&emitc_inliner_model::action);
+  InlinerRunInputs I{};
+  I.deadBlocks = Inputs[DeadBlocks].data();
+  I.caseClusterPenalty = Inputs[CaseClusterPenalty].data();
+  I.sroaSavings = Inputs[SroaSavings].data();
+  I.jumpTablePenalty = Inputs[JumpTablePenalty].data();
+  I.callsiteHeight = Inputs[CallsiteHeight].data();
+  I.calleeBasicBlockCount = Inputs[CalleeBasicBlockCount].data();
+  I.callArgumentSetup = Inputs[CallArgumentSetup].data();
+  I.loweredCallArgSetup = Inputs[LoweredCallArgSetup].data();
+  I.simplifiedInstructions = Inputs[SimplifiedInstructions].data();
+  I.nrCtantParams = Inputs[NrCtantParams].data();
+  I.isMultipleBlocks = Inputs[IsMultipleBlocks].data();
+  I.loadElimination = Inputs[LoadElimination].data();
+  I.edgeCount = Inputs[EdgeCount].data();
+  I.callerUsers = Inputs[CallerUsers].data();
+  I.callerConditionallyExecutedBlocks =
+      Inputs[CallerConditionallyExecutedBlocks].data();
+  I.constantOffsetPtrArgs = Inputs[ConstantOffsetPtrArgs].data();
+  I.callsiteCost = Inputs[CallsiteCost].data();
+  I.callerBasicBlockCount = Inputs[CallerBasicBlockCount].data();
+  I.loadRelativeIntrinsic = Inputs[LoadRelativeIntrinsic].data();
+  I.indirectCallPenalty = Inputs[IndirectCallPenalty].data();
+  I.costEstimate = Inputs[CostEstimate].data();
+  I.threshold = Inputs[Threshold].data();
+  I.nestedInlineCostEstimate = Inputs[NestedInlineCostEstimate].data();
+  I.unsimplifiedCommonInstructions =
+      Inputs[UnsimplifiedCommonInstructions].data();
+  I.sroaLosses = Inputs[SroaLosses].data();
+  I.numLoops = Inputs[NumLoops].data();
+  I.switchPenalty = Inputs[SwitchPenalty].data();
+  I.calleeUsers = Inputs[CalleeUsers].data();
+  I.nodeCount = Inputs[NodeCount].data();
+  I.constantArgs = Inputs[ConstantArgs].data();
+  I.lastCallToStaticBonus = Inputs[LastCallToStaticBonus].data();
+  I.coldCCPenalty = Inputs[ColdCCPenalty].data();
+  I.calleeConditionallyExecutedBlocks =
+      Inputs[CalleeConditionallyExecutedBlocks].data();
+  I.callPenalty = Inputs[CallPenalty].data();
+  I.nestedInlines = Inputs[NestedInlines].data();
+  I.dummyStepType = DummyStepType.data();
+  I.dummyDiscount = DummyDiscount.data();
+  I.dummyReward = DummyReward.data();
+  I.dummyInliningDefault = DummyInliningDefault.data();
+  Result[0] = runEmitCInlinerAction<ActionTy>(I);
+}
diff --git a/llvm/lib/Analysis/MLInlineAdvisor.cpp b/llvm/lib/Analysis/MLInlineAdvisor.cpp
index 9a5ae2ae26799..e698f066b9336 100644
--- a/llvm/lib/Analysis/MLInlineAdvisor.cpp
+++ b/llvm/lib/Analysis/MLInlineAdvisor.cpp
@@ -64,7 +64,10 @@ static cl::opt<std::string> ModelSelector("ml-inliner-model-selector",
 static cl::opt<bool> StopImmediatelyForTest("ml-inliner-stop-immediately",
                                             cl::Hidden);
 
-#if defined(LLVM_HAVE_TF_AOT_INLINERSIZEMODEL)
+#if defined(LLVM_HAVE_EMITC_INLINERSIZEMODEL)
+#include "llvm/Analysis/EmitCInlinerSizeModel.h"
+using CompiledModelType = llvm::EmitCInlinerSizeModel;
+#elif defined(LLVM_HAVE_TF_AOT_INLINERSIZEMODEL)
 // codegen-ed file
 #include "InlinerSizeModel.h" // NOLINT
 using CompiledModelType = llvm::InlinerSizeModel;
diff --git a/llvm/lib/CodeGen/CMakeLists.txt b/llvm/lib/CodeGen/CMakeLists.txt
index c572128b023c1..bf05ca6d7cb19 100644
--- a/llvm/lib/CodeGen/CMakeLists.txt
+++ b/llvm/lib/CodeGen/CMakeLists.txt
@@ -1,10 +1,19 @@
+option(LLVM_USE_EMITC_REGALLOC_EVICT_MODEL
+  "Use the in-tree EmitC regalloc eviction model instead of TensorFlow AOT"
+  OFF)
+
+if (LLVM_USE_EMITC_REGALLOC_EVICT_MODEL)
+  list(APPEND LLVM_COMPILE_DEFINITIONS
+    LLVM_HAVE_EMITC_REGALLOCEVICTMODEL)
+endif()
+
 if (DEFINED LLVM_HAVE_TF_AOT OR LLVM_HAVE_TFLITE)
   include(TensorFlowCompile)
   set(LLVM_RAEVICT_MODEL_PATH_DEFAULT "models/regalloc-eviction")
 
   set(LLVM_RAEVICT_MODEL_CURRENT_URL "<UNSPECIFIED>" CACHE STRING "URL to download the LLVM register allocator eviction model")
 
-  if (DEFINED LLVM_HAVE_TF_AOT)
+  if (DEFINED LLVM_HAVE_TF_AOT AND NOT LLVM_USE_EMITC_REGALLOC_EVICT_MODEL)
     tf_find_and_compile(
       ${LLVM_RAEVICT_MODEL_PATH}
       ${LLVM_RAEVICT_MODEL_CURRENT_URL}
@@ -53,6 +62,7 @@ add_llvm_component_library(LLVMCodeGen
   DroppedVariableStatsMIR.cpp
   DwarfEHPrepare.cpp
   EarlyIfConversion.cpp
+  EmitCRegAllocEvictModel.cpp
   EdgeBundles.cpp
   EHContGuardTargets.cpp
   ExecutionDomainFix.cpp
@@ -295,6 +305,11 @@ add_llvm_component_library(LLVMCodeGen
   TransformUtils
   )
 
+if (LLVM_USE_EMITC_REGALLOC_EVICT_MODEL)
+  target_compile_definitions(LLVMCodeGen
+    PRIVATE LLVM_HAVE_EMITC_REGALLOCEVICTMODEL)
+endif()
+
 add_subdirectory(SelectionDAG)
 add_subdirectory(AsmPrinter)
 add_subdirectory(MIRParser)
diff --git a/llvm/lib/CodeGen/EmitCRegAllocEvictModel.cpp b/llvm/lib/CodeGen/EmitCRegAllocEvictModel.cpp
new file mode 100644
index 0000000000000..120268ec1f389
--- /dev/null
+++ b/llvm/lib/CodeGen/EmitCRegAllocEvictModel.cpp
@@ -0,0 +1,209 @@
+//===- EmitCRegAllocEvictModel.cpp - EmitC regalloc model wrapper ---------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+/// This file implements the wrapper around the EmitC-translated MLGO
+/// regalloc eviction model.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/CodeGen/EmitCRegAllocEvictModel.h"
+
+#include "llvm/ADT/StringSwitch.h"
+#include "llvm/Support/ErrorHandling.h"
+
+#include <stddef.h>
+#include <stdint.h>
+#include <type_traits>
+
+namespace llvm::emitc_regalloc_evict_model {
+#define main action
+#include "llvm/CodeGen/EmitCRegAllocEvictModel.inc"
+#undef main
+} // namespace llvm::emitc_regalloc_evict_model
+
+using namespace llvm;
+
+namespace {
+template <typename T> inline constexpr bool AlwaysFalse = false;
+constexpr std::size_t EmitCRegAllocInterferenceCount = 33;
+using F32TensorPtr = float (*)[EmitCRegAllocInterferenceCount];
+using I64TensorPtr = int64_t (*)[EmitCRegAllocInterferenceCount];
+using F32ScalarPtr = float *;
+using I32ScalarPtr = int32_t *;
+using I64ScalarPtr = int64_t *;
+
+using RegAllocProductionActionTy = int64_t (*)(
+    F32TensorPtr, F32TensorPtr, I64TensorPtr, F32TensorPtr, F32TensorPtr,
+    F32TensorPtr, F32ScalarPtr, F32TensorPtr, F32TensorPtr, F32TensorPtr,
+    I64TensorPtr, I64TensorPtr, F32TensorPtr, F32TensorPtr, I32ScalarPtr,
+    F32TensorPtr, I64TensorPtr, F32TensorPtr, I64TensorPtr, F32TensorPtr,
+    F32ScalarPtr, F32TensorPtr, I64TensorPtr, F32ScalarPtr);
+using RegAllocMaskOnlyActionTy = int64_t (*)(I64ScalarPtr);
+
+struct RegAllocRunInputs {
+  F32TensorPtr liverangeSize;
+  F32TensorPtr hintWeightsByMax;
+  I64TensorPtr isFree;
+  F32TensorPtr weighedReadsByMax;
+  F32TensorPtr weighedReadWritesByMax;
+  F32TensorPtr nrBrokenHints;
+  F32ScalarPtr progress;
+  F32TensorPtr hottestBBFreqByMax;
+  F32TensorPtr useDefDensity;
+  F32TensorPtr startBBFreqByMax;
+  I64TensorPtr maxStage;
+  I64TensorPtr isHint;
+  F32TensorPtr nrRematerializable;
+  F32TensorPtr weighedWritesByMax;
+  I32ScalarPtr dummyStepType;
+  F32TensorPtr nrUrgent;
+  I64TensorPtr mask;
+  F32TensorPtr nrDefsAndUses;
+  I64TensorPtr isLocal;
+  F32TensorPtr endBBFreqByMax;
+  F32ScalarPtr dummyDiscount;
+  F32TensorPtr weighedIndvarsByMax;
+  I64TensorPtr minStage;
+  F32ScalarPtr dummyReward;
+  I64ScalarPtr maskFlat;
+};
+
+template <typename ActionTy>
+int64_t runEmitCRegAllocAction(const RegAllocRunInputs &I) {
+  if constexpr (std::is_same_v<ActionTy, RegAllocProductionActionTy>) {
+    return static_cast<ActionTy>(emitc_regalloc_evict_model::action)(
+        I.liverangeSize, I.hintWeightsByMax, I.isFree, I.weighedReadsByMax,
+        I.weighedReadWritesByMax, I.nrBrokenHints, I.progress,
+        I.hottestBBFreqByMax, I.useDefDensity, I.startBBFreqByMax, I.maxStage,
+        I.isHint, I.nrRematerializable, I.weighedWritesByMax, I.dummyStepType,
+        I.nrUrgent, I.mask, I.nrDefsAndUses, I.isLocal, I.endBBFreqByMax,
+        I.dummyDiscount, I.weighedIndvarsByMax, I.minStage, I.dummyReward);
+  } else if constexpr (std::is_same_v<ActionTy, RegAllocMaskOnlyActionTy>) {
+    return static_cast<ActionTy>(emitc_regalloc_evict_model::action)(
+        I.maskFlat);
+  } else {
+    static_assert(AlwaysFalse<ActionTy>,
+                  "Unsupported EmitC regalloc eviction model signature");
+  }
+}
+} // namespace
+
+int EmitCRegAllocEvictModel::LookupArgIndex(const std::string &Name) {
+  return StringSwitch<int>(Name)
+      .Case("feed_mask", Mask)
+      .Case("feed_is_free", IsFree)
+      .Case("feed_nr_urgent", NrUrgent)
+      .Case("feed_nr_broken_hints", NrBrokenHints)
+      .Case("feed_is_hint", IsHint)
+      .Case("feed_is_local", IsLocal)
+      .Case("feed_nr_rematerializable", NrRematerializable)
+      .Case("feed_nr_defs_and_uses", NrDefsAndUses)
+      .Case("feed_weighed_reads_by_max", WeighedReadsByMax)
+      .Case("feed_weighed_writes_by_max", WeighedWritesByMax)
+      .Case("feed_weighed_read_writes_by_max", WeighedReadWritesByMax)
+      .Case("feed_weighed_indvars_by_max", WeighedIndvarsByMax)
+      .Case("feed_hint_weights_by_max", HintWeightsByMax)
+      .Case("feed_start_bb_freq_by_max", StartBBFreqByMax)
+      .Case("feed_end_bb_freq_by_max", EndBBFreqByMax)
+      .Case("feed_hottest_bb_freq_by_max", HottestBBFreqByMax)
+      .Case("feed_liverange_size", LiverangeSize)
+      .Case("feed_use_def_density", UseDefDensity)
+      .Case("feed_max_stage", MaxStage)
+      .Case("feed_min_stage", MinStage)
+      .Case("feed_progress", Progress)
+      .Default(-1);
+}
+
+int EmitCRegAllocEvictModel::LookupResultIndex(const std::string &Name) {
+  return Name == "fetch_index_to_evict" ? 0 : -1;
+}
+
+void *EmitCRegAllocEvictModel::arg_data(int Index) {
+  switch (Index) {
+  case Mask:
+    return MaskInput;
+  case IsFree:
+    return IsFreeInput;
+  case NrUrgent:
+    return NrUrgentInput;
+  case NrBrokenHints:
+    return NrBrokenHintsInput;
+  case IsHint:
+    return IsHintInput;
+  case IsLocal:
+    return IsLocalInput;
+  case NrRematerializable:
+    return NrRematerializableInput;
+  case NrDefsAndUses:
+    return NrDefsAndUsesInput;
+  case WeighedReadsByMax:
+    return WeighedReadsByMaxInput;
+  case WeighedWritesByMax:
+    return WeighedWritesByMaxInput;
+  case WeighedReadWritesByMax:
+    return WeighedReadWritesByMaxInput;
+  case WeighedIndvarsByMax:
+    return WeighedIndvarsByMaxInput;
+  case HintWeightsByMax:
+    return HintWeightsByMaxInput;
+  case StartBBFreqByMax:
+    return StartBBFreqByMaxInput;
+  case EndBBFreqByMax:
+    return EndBBFreqByMaxInput;
+  case HottestBBFreqByMax:
+    return HottestBBFreqByMaxInput;
+  case LiverangeSize:
+    return LiverangeSizeInput;
+  case UseDefDensity:
+    return UseDefDensityInput;
+  case MaxStage:
+    return MaxStageInput;
+  case MinStage:
+    return MinStageInput;
+  case Progress:
+    return ProgressInput;
+  }
+  llvm_unreachable("invalid EmitC regalloc eviction input index");
+}
+
+void *EmitCRegAllocEvictModel::result_data(int Index) {
+  if (Index != 0)
+    llvm_unreachable("invalid EmitC regalloc eviction result index");
+  return Result;
+}
+
+void EmitCRegAllocEvictModel::Run() {
+  using ActionTy = decltype(&emitc_regalloc_evict_model::action);
+  RegAllocRunInputs I{};
+  I.liverangeSize = LiverangeSizeInput;
+  I.hintWeightsByMax = HintWeightsByMaxInput;
+  I.isFree = IsFreeInput;
+  I.weighedReadsByMax = WeighedReadsByMaxInput;
+  I.weighedReadWritesByMax = WeighedReadWritesByMaxInput;
+  I.nrBrokenHints = NrBrokenHintsInput;
+  I.progress = ProgressInput;
+  I.hottestBBFreqByMax = HottestBBFreqByMaxInput;
+  I.useDefDensity = UseDefDensityInput;
+  I.startBBFreqByMax = StartBBFreqByMaxInput;
+  I.maxStage = MaxStageInput;
+  I.isHint = IsHintInput;
+  I.nrRematerializable = NrRematerializableInput;
+  I.weighedWritesByMax = WeighedWritesByMaxInput;
+  I.dummyStepType = DummyStepType;
+  I.nrUrgent = NrUrgentInput;
+  I.mask = MaskInput;
+  I.nrDefsAndUses = NrDefsAndUsesInput;
+  I.isLocal = IsLocalInput;
+  I.endBBFreqByMax = EndBBFreqByMaxInput;
+  I.dummyDiscount = DummyDiscount;
+  I.weighedIndvarsByMax = WeighedIndvarsByMaxInput;
+  I.minStage = MinStageInput;
+  I.dummyReward = DummyReward;
+  I.maskFlat = MaskInput[0];
+  Result[0] = runEmitCRegAllocAction<ActionTy>(I);
+}
diff --git a/llvm/lib/CodeGen/MLRegAllocEvictAdvisor.cpp b/llvm/lib/CodeGen/MLRegAllocEvictAdvisor.cpp
index 23dc6fbd6e500..03ba2da4e945e 100644
--- a/llvm/lib/CodeGen/MLRegAllocEvictAdvisor.cpp
+++ b/llvm/lib/CodeGen/MLRegAllocEvictAdvisor.cpp
@@ -47,8 +47,11 @@ using namespace llvm;
 
 #define DEBUG_TYPE "ml-regalloc"
 
-// Generated header in release (AOT) mode
-#if defined(LLVM_HAVE_TF_AOT_REGALLOCEVICTMODEL)
+// Generated header in release (AOT / EmitC) mode
+#if defined(LLVM_HAVE_EMITC_REGALLOCEVICTMODEL)
+#include "llvm/CodeGen/EmitCRegAllocEvictModel.h"
+using CompiledModelType = llvm::EmitCRegAllocEvictModel;
+#elif defined(LLVM_HAVE_TF_AOT_REGALLOCEVICTMODEL)
 #include "RegAllocEvictModel.h"
 using CompiledModelType = RegAllocEvictModel;
 #else
diff --git a/llvm/test/CodeGen/MLRegAlloc/default-eviction-advisor.ll b/llvm/test/CodeGen/MLRegAlloc/default-eviction-advisor.ll
index 881a80c41361c..fc9d244a617a9 100644
--- a/llvm/test/CodeGen/MLRegAlloc/default-eviction-advisor.ll
+++ b/llvm/test/CodeGen/MLRegAlloc/default-eviction-advisor.ll
@@ -2,6 +2,7 @@
 ; trying to use ML-driven advisor.
 ; REQUIRES: !have_tf_aot
 ; REQUIRES: !have_tflite
+; REQUIRES: !have_emitc_raevict_model
 ; REQUIRES: default_triple
 ; RUN: not llc -O2 -regalloc-enable-advisor=development < %s 2>&1 | FileCheck %s
 ; RUN: not llc -O2 -regalloc-enable-advisor=release < %s 2>&1 | FileCheck %s
diff --git a/llvm/test/lit.cfg.py b/llvm/test/lit.cfg.py
index cd028963dd59e..796d258515e5b 100644
--- a/llvm/test/lit.cfg.py
+++ b/llvm/test/lit.cfg.py
@@ -597,6 +597,9 @@ def enable_ptxas(ptxas_executable):
 if config.have_tflite:
     config.available_features.add("have_tflite")
 
+if config.have_emitc_raevict_model:
+    config.available_features.add("have_emitc_raevict_model")
+
 if config.llvm_inliner_model_autogenerated:
     config.available_features.add("llvm_inliner_model_autogenerated")
 
diff --git a/llvm/test/lit.site.cfg.py.in b/llvm/test/lit.site.cfg.py.in
index 64679c2f64034..8c60b0d1a5d1b 100644
--- a/llvm/test/lit.site.cfg.py.in
+++ b/llvm/test/lit.site.cfg.py.in
@@ -57,6 +57,7 @@ config.linked_bye_extension = @LLVM_BYE_LINK_INTO_TOOLS@
 config.linked_exampleirtransforms_extension = @LLVM_EXAMPLEIRTRANSFORMS_LINK_INTO_TOOLS@
 config.have_tf_aot = @LLVM_HAVE_TF_AOT@
 config.have_tflite = @LLVM_HAVE_TFLITE@
+config.have_emitc_raevict_model = @LLVM_USE_EMITC_REGALLOC_EVICT_MODEL@
 config.enable_profcheck = @LLVM_ENABLE_PROFCHECK@
 config.llvm_inliner_model_autogenerated = @LLVM_INLINER_MODEL_AUTOGENERATED@
 config.llvm_raevict_model_autogenerated = @LLVM_RAEVICT_MODEL_AUTOGENERATED@

>From 33bdfa86d8a408260351bb220b2fa0944549f75d Mon Sep 17 00:00:00 2001
From: Ioana Ghiban <ioana.ghiban at arm.com>
Date: Thu, 16 Jul 2026 11:26:09 +0200
Subject: [PATCH 2/2] Mark model wrapper implementations optional

---
 llvm/lib/Analysis/CMakeLists.txt | 13 ++++++++++---
 llvm/lib/CodeGen/CMakeLists.txt  | 14 +++++++++++---
 2 files changed, 21 insertions(+), 6 deletions(-)

diff --git a/llvm/lib/Analysis/CMakeLists.txt b/llvm/lib/Analysis/CMakeLists.txt
index 474d96df9b619..50f10c58e6892 100644
--- a/llvm/lib/Analysis/CMakeLists.txt
+++ b/llvm/lib/Analysis/CMakeLists.txt
@@ -2,14 +2,21 @@ option(LLVM_USE_EMITC_INLINER_MODEL
   "Use the in-tree EmitC inliner model instead of TensorFlow AOT"
   OFF)
 
+# The EmitC wrapper is only built when the in-tree EmitC model is enabled.
+# Mark it optional so LLVM's source audit does not require it in default builds.
+list(APPEND LLVM_OPTIONAL_SOURCES
+  EmitCInlinerSizeModel.cpp)
+
 if (LLVM_HAVE_TFLITE)
   list(APPEND MLLinkDeps
     tensorflow-lite::tensorflow-lite)
 endif()
 
+# Only compile the EmitC inliner wrapper when the EmitC-backed release model
+# is selected. Otherwise the generated .inc file may not exist.
 if (LLVM_USE_EMITC_INLINER_MODEL)
-  list(APPEND LLVM_COMPILE_DEFINITIONS
-    LLVM_HAVE_EMITC_INLINERSIZEMODEL)
+  list(APPEND LLVMAnalysisOptionalSources
+    EmitCInlinerSizeModel.cpp)
 elseif (DEFINED LLVM_HAVE_TF_AOT)
   include(TensorFlowCompile)
   set(LLVM_INLINER_MODEL_PATH_DEFAULT "models/inliner-Oz")
@@ -82,7 +89,6 @@ add_llvm_component_library(LLVMAnalysis
   DominanceFrontier.cpp
   DXILResource.cpp
   DXILMetadataAnalysis.cpp
-  EmitCInlinerSizeModel.cpp
   EphemeralValuesCache.cpp
   FloatingPointPredicateUtils.cpp
   FunctionPropertiesAnalysis.cpp
@@ -170,6 +176,7 @@ add_llvm_component_library(LLVMAnalysis
   ValueLatticeUtils.cpp
   ValueTracking.cpp
   VectorUtils.cpp
+  ${LLVMAnalysisOptionalSources}
   ${GeneratedMLSources}
 
   ADDITIONAL_HEADER_DIRS
diff --git a/llvm/lib/CodeGen/CMakeLists.txt b/llvm/lib/CodeGen/CMakeLists.txt
index bf05ca6d7cb19..72496671b3d68 100644
--- a/llvm/lib/CodeGen/CMakeLists.txt
+++ b/llvm/lib/CodeGen/CMakeLists.txt
@@ -2,9 +2,17 @@ option(LLVM_USE_EMITC_REGALLOC_EVICT_MODEL
   "Use the in-tree EmitC regalloc eviction model instead of TensorFlow AOT"
   OFF)
 
+# The EmitC wrapper is only built when the in-tree EmitC regalloc model is
+# enabled. Mark it optional so LLVM's source audit accepts its absence from the
+# default target source list.
+list(APPEND LLVM_OPTIONAL_SOURCES
+  EmitCRegAllocEvictModel.cpp)
+
+# Only compile the EmitC regalloc wrapper when the EmitC-backed release model
+# is selected. Otherwise the generated .inc file may not exist.
 if (LLVM_USE_EMITC_REGALLOC_EVICT_MODEL)
-  list(APPEND LLVM_COMPILE_DEFINITIONS
-    LLVM_HAVE_EMITC_REGALLOCEVICTMODEL)
+  list(APPEND LLVMCodeGenOptionalSources
+    EmitCRegAllocEvictModel.cpp)
 endif()
 
 if (DEFINED LLVM_HAVE_TF_AOT OR LLVM_HAVE_TFLITE)
@@ -62,7 +70,6 @@ add_llvm_component_library(LLVMCodeGen
   DroppedVariableStatsMIR.cpp
   DwarfEHPrepare.cpp
   EarlyIfConversion.cpp
-  EmitCRegAllocEvictModel.cpp
   EdgeBundles.cpp
   EHContGuardTargets.cpp
   ExecutionDomainFix.cpp
@@ -268,6 +275,7 @@ add_llvm_component_library(LLVMCodeGen
   WindowsSecureHotPatching.cpp
   WinEHPrepare.cpp
   XRayInstrumentation.cpp
+  ${LLVMCodeGenOptionalSources}
   ${GeneratedMLSources}
 
   LiveDebugValues/LiveDebugValues.cpp



More information about the llvm-commits mailing list