diff --git a/subprojects/helper-to-tcg/meson.build b/subprojects/helper-to-tcg/meson.build
index af593ccdfe..7bb93ce005 100644
--- a/subprojects/helper-to-tcg/meson.build
+++ b/subprojects/helper-to-tcg/meson.build
@@ -41,6 +41,7 @@ if version_major < 10 or version_major > 14
 endif
 
 sources = [
+    'passes/llvm-compat.cpp',
 ]
 
 clang = bindir / 'clang'
diff --git a/subprojects/helper-to-tcg/passes/llvm-compat.cpp b/subprojects/helper-to-tcg/passes/llvm-compat.cpp
new file mode 100644
index 0000000000..c5d9d28078
--- /dev/null
+++ b/subprojects/helper-to-tcg/passes/llvm-compat.cpp
@@ -0,0 +1,162 @@
+//
+//  Copyright(c) 2024 rev.ng Labs Srl. All Rights Reserved.
+//
+//  This program is free software; you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation; either version 2 of the License, or
+//  (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program; if not, see <http://www.gnu.org/licenses/>.
+//
+
+#include "llvm-compat.h"
+
+#if LLVM_VERSION_MAJOR > 10
+#include <llvm/CodeGen/CommandFlags.h>
+#else
+#include <llvm/CodeGen/CommandFlags.inc>
+#endif
+
+#include <string>
+
+// Static variables required by LLVM
+//
+// Defining RegisterCodeGenFlags with static duration registers extra
+// codegen commandline flags for specifying the target arch.
+#if LLVM_VERSION_MAJOR > 10
+static llvm::codegen::RegisterCodeGenFlags CGF;
+#endif
+static llvm::ExitOnError ExitOnErr;
+
+namespace compat
+{
+
+using namespace llvm;
+
+#if LLVM_VERSION_MAJOR > 10
+llvm::TargetMachine *getTargetMachine(llvm::Triple &TheTriple)
+{
+    const TargetOptions Options{};
+    std::string Error;
+    const Target *TheTarget = llvm::TargetRegistry::lookupTarget(
+        llvm::codegen::getMArch(), TheTriple, Error);
+    // Some modules don't specify a triple, and this is okay.
+    if (!TheTarget) {
+        return nullptr;
+    }
+
+    return TheTarget->createTargetMachine(
+        TheTriple.getTriple(), llvm::codegen::getCPUStr(),
+        llvm::codegen::getFeaturesStr(), Options,
+        llvm::codegen::getExplicitRelocModel(),
+        llvm::codegen::getExplicitCodeModel(), llvm::CodeGenOpt::Aggressive);
+}
+#else
+llvm::TargetMachine *getTargetMachine(llvm::Triple &TheTriple)
+{
+    const TargetOptions Options{};
+    std::string Error;
+    const Target *TheTarget =
+        llvm::TargetRegistry::lookupTarget(MArch, TheTriple, Error);
+    // Some modules don't specify a triple, and this is okay.
+    if (!TheTarget) {
+        return nullptr;
+    }
+
+    return TheTarget->createTargetMachine(
+        TheTriple.getTriple(), getCPUStr(), getFeaturesStr(), Options,
+        getRelocModel(), getCodeModel(), llvm::CodeGenOpt::Aggressive);
+}
+#endif
+
+//
+// LLVM 11 and below does not define the UnifyFunctionExitNodes pass
+// for the new pass manager.  Copy over the definition from LLVM and use it
+// for 11 and below.
+//
+#if LLVM_VERSION_MAJOR <= 11
+static bool unifyReturnBlocks(Function &F)
+{
+    std::vector<BasicBlock *> ReturningBlocks;
+
+    for (BasicBlock &I : F)
+        if (isa<ReturnInst>(I.getTerminator()))
+            ReturningBlocks.push_back(&I);
+
+    if (ReturningBlocks.size() <= 1)
+        return false;
+
+    // Insert a new basic block into the function, add PHI nodes (if the
+    // function returns values), and convert all of the return instructions into
+    // unconditional branches.
+    BasicBlock *NewRetBlock =
+        BasicBlock::Create(F.getContext(), "UnifiedReturnBlock", &F);
+
+    PHINode *PN = nullptr;
+    if (F.getReturnType()->isVoidTy()) {
+        ReturnInst::Create(F.getContext(), nullptr, NewRetBlock);
+    } else {
+        // If the function doesn't return void... add a PHI node to the block...
+        PN = PHINode::Create(F.getReturnType(), ReturningBlocks.size(),
+                             "UnifiedRetVal");
+        NewRetBlock->getInstList().push_back(PN);
+        ReturnInst::Create(F.getContext(), PN, NewRetBlock);
+    }
+
+    // Loop over all of the blocks, replacing the return instruction with an
+    // unconditional branch.
+    for (BasicBlock *BB : ReturningBlocks) {
+        // Add an incoming element to the PHI node for every return instruction
+        // that is merging into this new block...
+        if (PN)
+            PN->addIncoming(BB->getTerminator()->getOperand(0), BB);
+
+        BB->getInstList().pop_back(); // Remove the return insn
+        BranchInst::Create(NewRetBlock, BB);
+    }
+
+    return true;
+}
+
+static bool unifyUnreachableBlocks(Function &F)
+{
+    std::vector<BasicBlock *> UnreachableBlocks;
+
+    for (BasicBlock &I : F)
+        if (isa<UnreachableInst>(I.getTerminator()))
+            UnreachableBlocks.push_back(&I);
+
+    if (UnreachableBlocks.size() <= 1)
+        return false;
+
+    BasicBlock *UnreachableBlock =
+        BasicBlock::Create(F.getContext(), "UnifiedUnreachableBlock", &F);
+    new UnreachableInst(F.getContext(), UnreachableBlock);
+
+    for (BasicBlock *BB : UnreachableBlocks) {
+        BB->getInstList().pop_back(); // Remove the unreachable inst.
+        BranchInst::Create(UnreachableBlock, BB);
+    }
+
+    return true;
+}
+
+llvm::PreservedAnalyses
+UnifyFunctionExitNodesPass::run(llvm::Function &F,
+                                llvm::FunctionAnalysisManager &AM)
+{
+    bool Changed = false;
+    Changed |= unifyUnreachableBlocks(F);
+    Changed |= unifyReturnBlocks(F);
+    return Changed ? PreservedAnalyses() : llvm::PreservedAnalyses::all();
+}
+
+#endif
+
+} // namespace compat
diff --git a/subprojects/helper-to-tcg/passes/llvm-compat.h b/subprojects/helper-to-tcg/passes/llvm-compat.h
new file mode 100644
index 0000000000..e983ad660e
--- /dev/null
+++ b/subprojects/helper-to-tcg/passes/llvm-compat.h
@@ -0,0 +1,143 @@
+//
+//  Copyright(c) 2024 rev.ng Labs Srl. All Rights Reserved.
+//
+//  This program is free software; you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation; either version 2 of the License, or
+//  (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program; if not, see <http://www.gnu.org/licenses/>.
+//
+
+#pragma once
+
+//
+// The purpose of this file is to both collect and hide most api-specific
+// changes of LLVM [10,14]. Hopefully making it easier to keep track of the
+// changes necessary to support our targeted versions.
+//
+// Note some #ifdefs still remain throughout the codebase for larger codeblocks
+// that are specific enough such that pulling them here would be more cumbersome
+// than it's worth.
+//
+
+#include <llvm/IR/Module.h>
+#include <llvm/IR/PassManager.h>
+
+#if LLVM_VERSION_MAJOR > 11
+#include <llvm/Transforms/Utils/UnifyFunctionExitNodes.h>
+#endif
+
+#if LLVM_VERSION_MAJOR >= 14
+#include <llvm/MC/TargetRegistry.h>
+#else
+#include <llvm/Support/TargetRegistry.h>
+#endif
+
+#include <llvm/IR/DerivedTypes.h>
+#include <llvm/IR/PatternMatch.h>
+#include <llvm/Passes/OptimizationLevel.h>
+#include <llvm/Passes/PassBuilder.h>
+#include <llvm/Support/FileSystem.h>
+
+#include <stdint.h>
+
+namespace compat
+{
+
+#if LLVM_VERSION_MAJOR == 14 || LLVM_VERSION_MAJOR == 13
+constexpr auto OpenFlags = llvm::sys::fs::OF_TextWithCRLF;
+#else
+constexpr auto OpenFlags = llvm::sys::fs::OF_Text;
+#endif
+
+#if LLVM_VERSION_MAJOR == 14
+using OptimizationLevel = llvm::OptimizationLevel;
+#else
+using OptimizationLevel = llvm::PassBuilder::OptimizationLevel;
+#endif
+
+#if LLVM_VERSION_MAJOR > 11
+constexpr auto LTOPhase = llvm::ThinOrFullLTOPhase::None;
+#else
+constexpr auto LTOPhase = llvm::PassBuilder::ThinLTOPhase::None;
+#endif
+
+inline llvm::PassBuilder createPassBuilder(llvm::TargetMachine *TM,
+                                           llvm::PipelineTuningOptions &PTO)
+{
+#if LLVM_VERSION_MAJOR == 14 || LLVM_VERSION_MAJOR == 13
+    return llvm::PassBuilder(TM, PTO, llvm::None);
+#elif LLVM_VERSION_MAJOR == 12
+    return llvm::PassBuilder(TM, nullptr, PTO);
+#else
+    return llvm::PassBuilder(TM, PTO);
+#endif
+}
+
+// Wrapper to convert Function- to Module analysis manager
+template <typename T>
+inline const typename T::Result *
+getModuleAnalysisManagerProxyResult(llvm::FunctionAnalysisManager &FAM,
+                                    llvm::Function &F)
+{
+#if LLVM_VERSION_MAJOR > 10
+    auto &MAMProxy = FAM.getResult<llvm::ModuleAnalysisManagerFunctionProxy>(F);
+    return MAMProxy.getCachedResult<T>(*F.getParent());
+#else
+    auto &MAMProxy =
+        FAM.getResult<llvm::ModuleAnalysisManagerFunctionProxy>(F).getManager();
+    return MAMProxy.getCachedResult<T>(*F.getParent());
+#endif
+}
+
+llvm::TargetMachine *getTargetMachine(llvm::Triple &TheTriple);
+
+//
+// LLVM 11 and below does not define the UnifyFunctionExitNodes pass
+// for the new pass manager.  Copy over the definition and use it for
+// 11 and below.
+//
+#if LLVM_VERSION_MAJOR > 11
+using llvm::UnifyFunctionExitNodesPass;
+#else
+class UnifyFunctionExitNodesPass
+    : public llvm::PassInfoMixin<UnifyFunctionExitNodesPass>
+{
+  public:
+    llvm::PreservedAnalyses run(llvm::Function &F,
+                                llvm::FunctionAnalysisManager &AM);
+};
+#endif
+
+inline uint32_t getVectorElementCount(llvm::VectorType *VecTy)
+{
+    auto ElementCount = VecTy->getElementCount();
+#if LLVM_VERSION_MAJOR > 11
+    return ElementCount.getFixedValue();
+#else
+    return ElementCount.Min;
+#endif
+}
+
+//
+// PatternMatch
+//
+
+#if LLVM_VERSION_MAJOR > 10
+#define compat_m_InsertElt llvm::PatternMatch::m_InsertElt
+#define compat_m_Shuffle llvm::PatternMatch::m_Shuffle
+#define compat_m_ZeroMask llvm::PatternMatch::m_ZeroMask
+#else
+#define compat_m_InsertElt llvm::PatternMatch::m_InsertElement
+#define compat_m_Shuffle llvm::PatternMatch::m_ShuffleVector
+#define compat_m_ZeroMask llvm::PatternMatch::m_Zero
+#endif
+
+} // namespace compat
