diff --git a/Source/Core/Common/CodeBlock.h b/Source/Core/Common/CodeBlock.h
index 28a4919e66..9c3da1058b 100644
--- a/Source/Core/Common/CodeBlock.h
+++ b/Source/Core/Common/CodeBlock.h
@@ -5,6 +5,7 @@
 #pragma once
 
 #include <cstddef>
+#include <vector>
 
 #include "Common/Assert.h"
 #include "Common/CommonTypes.h"
@@ -27,12 +28,13 @@ private:
 
 protected:
   u8* region = nullptr;
+  // Size of region we can use.
   size_t region_size = 0;
-  size_t parent_region_size = 0;
+  // Original size of the region we allocated.
+  size_t total_region_size = 0;
 
-  bool m_has_child = false;
   bool m_is_child = false;
-  CodeBlock* m_child = nullptr;
+  std::vector<CodeBlock*> m_children;
 
 public:
   virtual ~CodeBlock()
@@ -42,16 +44,17 @@ public:
   }
 
   // Call this before you generate any code.
-  virtual void AllocCodeSpace(size_t size, bool need_low = true)
+  void AllocCodeSpace(size_t size, bool need_low = true)
   {
     region_size = size;
-    region = static_cast<u8*>(Common::AllocateExecutableMemory(region_size, need_low));
+    total_region_size = size;
+    region = static_cast<u8*>(Common::AllocateExecutableMemory(total_region_size, need_low));
     T::SetCodePtr(region);
   }
 
   // Always clear code space with breakpoints, so that if someone accidentally executes
   // uninitialized, it just breaks into the debugger.
-  virtual void ClearCodeSpace()
+  void ClearCodeSpace()
   {
     PoisonMemory();
     ResetCodePtr();
@@ -60,14 +63,16 @@ public:
   // Call this when shutting down. Don't rely on the destructor, even though it'll do the job.
   void FreeCodeSpace()
   {
-    Common::FreeMemoryPages(region, region_size);
+    _assert_(!m_is_child);
+    Common::FreeMemoryPages(region, total_region_size);
     region = nullptr;
     region_size = 0;
-    parent_region_size = 0;
-    if (m_has_child)
+    total_region_size = 0;
+    for (CodeBlock* child : m_children)
     {
-      m_child->region = nullptr;
-      m_child->region_size = 0;
+      child->region = nullptr;
+      child->region_size = 0;
+      child->total_region_size = 0;
     }
   }
 
@@ -78,7 +83,8 @@ public:
   void ResetCodePtr() { T::SetCodePtr(region); }
   size_t GetSpaceLeft() const
   {
-    return (m_has_child ? parent_region_size : region_size) - (T::GetCodePtr() - region);
+    _assert_(static_cast<size_t>(T::GetCodePtr() - region) < region_size);
+    return region_size - (T::GetCodePtr() - region);
   }
 
   bool IsAlmostFull() const
@@ -86,16 +92,23 @@ public:
     // This should be bigger than the biggest block ever.
     return GetSpaceLeft() < 0x10000;
   }
-  void AddChildCodeSpace(CodeBlock* child, size_t size)
+
+  bool HasChildren() const { return region_size != total_region_size; }
+  u8* AllocChildCodeSpace(size_t child_size)
   {
-    _assert_msg_(DYNA_REC, !m_has_child, "Already have a child! Can't have another!");
-    m_child = child;
-    m_has_child = true;
-    m_child->m_is_child = true;
-    u8* child_region = region + region_size - size;
-    m_child->region = child_region;
-    m_child->region_size = size;
-    m_child->ResetCodePtr();
-    parent_region_size = region_size - size;
+    _assert_msg_(DYNA_REG, child_size < GetSpaceLeft(), "Insufficient space for child allocation.");
+    u8* child_region = region + region_size - child_size;
+    region_size -= child_size;
+    return child_region;
+  }
+  void AddChildCodeSpace(CodeBlock* child, size_t child_size)
+  {
+    u8* child_region = AllocChildCodeSpace(child_size);
+    child->m_is_child = true;
+    child->region = child_region;
+    child->region_size = child_size;
+    child->total_region_size = child_size;
+    child->ResetCodePtr();
+    m_children.emplace_back(child);
   }
 };
diff --git a/Source/Core/Core/PowerPC/Jit64/Jit.cpp b/Source/Core/Core/PowerPC/Jit64/Jit.cpp
index 74ca3ffd96..6d506b3d06 100644
--- a/Source/Core/Core/PowerPC/Jit64/Jit.cpp
+++ b/Source/Core/Core/PowerPC/Jit64/Jit.cpp
@@ -229,8 +229,15 @@ void Jit64::Init()
   gpr.SetEmitter(this);
   fpr.SetEmitter(this);
 
-  trampolines.Init(jo.memcheck ? TRAMPOLINE_CODE_SIZE_MMU : TRAMPOLINE_CODE_SIZE);
-  AllocCodeSpace(CODE_SIZE);
+  const size_t routines_size = asm_routines.CODE_SIZE;
+  const size_t trampolines_size = jo.memcheck ? TRAMPOLINE_CODE_SIZE_MMU : TRAMPOLINE_CODE_SIZE;
+  const size_t farcode_size = jo.memcheck ? FARCODE_SIZE_MMU : FARCODE_SIZE;
+  const size_t constpool_size = m_const_pool.CONST_POOL_SIZE;
+  AllocCodeSpace(CODE_SIZE + routines_size + trampolines_size + farcode_size + constpool_size);
+  AddChildCodeSpace(&asm_routines, routines_size);
+  AddChildCodeSpace(&trampolines, trampolines_size);
+  AddChildCodeSpace(&m_far_code, farcode_size);
+  m_const_pool.Init(AllocChildCodeSpace(constpool_size), constpool_size);
 
   // BLR optimization has the same consequences as block linking, as well as
   // depending on the fault handler to be safe in the event of excessive BL.
@@ -248,7 +255,7 @@ void Jit64::Init()
   // important: do this *after* generating the global asm routines, because we can't use farcode in
   // them.
   // it'll crash because the farcode functions get cleared on JIT clears.
-  m_far_code.Init(jo.memcheck ? FARCODE_SIZE_MMU : FARCODE_SIZE);
+  m_far_code.Init();
   Clear();
 
   code_block.m_stats = &js.st;
@@ -262,6 +269,7 @@ void Jit64::ClearCache()
   blocks.Clear();
   trampolines.ClearCodeSpace();
   m_far_code.ClearCodeSpace();
+  m_const_pool.Clear();
   ClearCodeSpace();
   Clear();
   UpdateMemoryOptions();
@@ -273,9 +281,8 @@ void Jit64::Shutdown()
   FreeCodeSpace();
 
   blocks.Shutdown();
-  trampolines.Shutdown();
-  asm_routines.Shutdown();
   m_far_code.Shutdown();
+  m_const_pool.Shutdown();
 }
 
 void Jit64::FallBackToInterpreter(UGeckoInstruction inst)
diff --git a/Source/Core/Core/PowerPC/Jit64/JitAsm.cpp b/Source/Core/Core/PowerPC/Jit64/JitAsm.cpp
index 1239570dbd..cb2d12156d 100644
--- a/Source/Core/Core/PowerPC/Jit64/JitAsm.cpp
+++ b/Source/Core/Core/PowerPC/Jit64/JitAsm.cpp
@@ -2,7 +2,7 @@
 // Licensed under GPLv2+
 // Refer to the license.txt file included.
 
-#include "Core/PowerPC/Jit64/Jit.h"
+#include "Core/PowerPC/Jit64/JitAsm.h"
 #include "Common/CommonTypes.h"
 #include "Common/JitRegister.h"
 #include "Common/x64ABI.h"
@@ -11,7 +11,7 @@
 #include "Core/CoreTiming.h"
 #include "Core/HW/CPU.h"
 #include "Core/HW/Memmap.h"
-#include "Core/PowerPC/Jit64/JitAsm.h"
+#include "Core/PowerPC/Jit64/Jit.h"
 #include "Core/PowerPC/Jit64Common/Jit64PowerPCState.h"
 #include "Core/PowerPC/PowerPC.h"
 
@@ -20,6 +20,14 @@ using namespace Gen;
 // Not PowerPC state.  Can't put in 'this' because it's out of range...
 static void* s_saved_rsp;
 
+void Jit64AsmRoutineManager::Init(u8* stack_top)
+{
+  m_const_pool.Init(AllocChildCodeSpace(4096), 4096);
+  m_stack_top = stack_top;
+  Generate();
+  WriteProtect();
+}
+
 // PLAN: no more block numbers - crazy opcodes just contain offset within
 // dynarec buffer
 // At this offset - 4, there is an int specifying the block number.
diff --git a/Source/Core/Core/PowerPC/Jit64/JitAsm.h b/Source/Core/Core/PowerPC/Jit64/JitAsm.h
index 2cdac46b45..6aaad10031 100644
--- a/Source/Core/Core/PowerPC/Jit64/JitAsm.h
+++ b/Source/Core/Core/PowerPC/Jit64/JitAsm.h
@@ -34,16 +34,11 @@ private:
   u8* m_stack_top;
 
 public:
-  void Init(u8* stack_top)
-  {
-    m_stack_top = stack_top;
-    // NOTE: When making large additions to the AsmCommon code, you might
-    // want to ensure this number is big enough.
-    AllocCodeSpace(16384);
-    Generate();
-    WriteProtect();
-  }
+  // NOTE: When making large additions to the AsmCommon code, you might
+  // want to ensure this number is big enough.
+  static constexpr size_t CODE_SIZE = 16384;
+
+  void Init(u8* stack_top);
 
-  void Shutdown() { FreeCodeSpace(); }
   void ResetStack(Gen::X64CodeBlock& emitter);
 };
diff --git a/Source/Core/Core/PowerPC/Jit64Common/ConstantPool.cpp b/Source/Core/Core/PowerPC/Jit64Common/ConstantPool.cpp
index 20c3f46802..cd8da238a0 100644
--- a/Source/Core/Core/PowerPC/Jit64Common/ConstantPool.cpp
+++ b/Source/Core/Core/PowerPC/Jit64Common/ConstantPool.cpp
@@ -10,21 +10,31 @@
 #include "Common/x64Emitter.h"
 #include "Core/PowerPC/Jit64Common/ConstantPool.h"
 
-ConstantPool::ConstantPool(Gen::X64CodeBlock* parent) : m_parent(parent)
-{
-}
+ConstantPool::ConstantPool() = default;
 
 ConstantPool::~ConstantPool() = default;
 
-void ConstantPool::AllocCodeSpace()
+void ConstantPool::Init(void* memory, size_t size)
 {
-  _assert_(!m_current_ptr);
-  Init();
+  m_region = memory;
+  m_region_size = size;
+  Clear();
 }
 
-void ConstantPool::ClearCodeSpace()
+void ConstantPool::Clear()
 {
-  Init();
+  m_current_ptr = m_region;
+  m_remaining_size = m_region_size;
+  m_const_info.clear();
+}
+
+void ConstantPool::Shutdown()
+{
+  m_region = nullptr;
+  m_region_size = 0;
+  m_current_ptr = nullptr;
+  m_remaining_size = 0;
+  m_const_info.clear();
 }
 
 Gen::OpArg ConstantPool::GetConstantOpArg(const void* value, size_t element_size,
@@ -51,17 +61,3 @@ Gen::OpArg ConstantPool::GetConstantOpArg(const void* value, size_t element_size
   u8* location = static_cast<u8*>(info.m_location);
   return Gen::M(location + element_size * index);
 }
-
-void ConstantPool::Init()
-{
-  // If execution happens to run to the start of the constant pool, halt.
-  m_parent->INT3();
-  m_parent->AlignCode16();
-
-  // Reserve a block of memory CONST_POOL_SIZE in size.
-  m_current_ptr = m_parent->GetWritableCodePtr();
-  m_parent->ReserveCodeSpace(CONST_POOL_SIZE);
-
-  m_remaining_size = CONST_POOL_SIZE;
-  m_const_info.clear();
-}
diff --git a/Source/Core/Core/PowerPC/Jit64Common/ConstantPool.h b/Source/Core/Core/PowerPC/Jit64Common/ConstantPool.h
index eba5cbec0f..732a41af9d 100644
--- a/Source/Core/Core/PowerPC/Jit64Common/ConstantPool.h
+++ b/Source/Core/Core/PowerPC/Jit64Common/ConstantPool.h
@@ -22,13 +22,12 @@ public:
   static constexpr size_t CONST_POOL_SIZE = 1024 * 32;
   static constexpr size_t ALIGNMENT = 16;
 
-  explicit ConstantPool(Gen::X64CodeBlock* parent);
+  ConstantPool();
   ~ConstantPool();
 
-  // ConstantPool reserves CONST_POOL_SIZE bytes from parent, and uses
-  // that space to store its constants.
-  void AllocCodeSpace();
-  void ClearCodeSpace();
+  void Init(void* memory, size_t size);
+  void Clear();
+  void Shutdown();
 
   // Copies the value into the pool if it doesn't exist. Returns a pointer
   // to existing values if they were already copied. Pointer equality is
@@ -37,16 +36,15 @@ public:
                               size_t index);
 
 private:
-  void Init();
-
   struct ConstantInfo
   {
     void* m_location;
     size_t m_size;
   };
 
-  Gen::X64CodeBlock* m_parent;
+  void* m_region = nullptr;
+  size_t m_region_size = 0;
   void* m_current_ptr = nullptr;
-  size_t m_remaining_size = CONST_POOL_SIZE;
+  size_t m_remaining_size = 0;
   std::map<const void*, ConstantInfo> m_const_info;
 };
diff --git a/Source/Core/Core/PowerPC/Jit64Common/EmuCodeBlock.cpp b/Source/Core/Core/PowerPC/Jit64Common/EmuCodeBlock.cpp
index 3a35c8dfa7..7408765a68 100644
--- a/Source/Core/Core/PowerPC/Jit64Common/EmuCodeBlock.cpp
+++ b/Source/Core/Core/PowerPC/Jit64Common/EmuCodeBlock.cpp
@@ -40,18 +40,6 @@ OpArg FixImmediate(int access_size, OpArg arg)
 }
 }  // Anonymous namespace
 
-void EmuCodeBlock::ClearCodeSpace()
-{
-  X64CodeBlock::ClearCodeSpace();
-  m_const_pool.ClearCodeSpace();
-}
-
-void EmuCodeBlock::AllocCodeSpace(size_t size, bool need_low)
-{
-  X64CodeBlock::AllocCodeSpace(size + ConstantPool::CONST_POOL_SIZE, need_low);
-  m_const_pool.AllocCodeSpace();
-}
-
 void EmuCodeBlock::MemoryExceptionCheck()
 {
   // TODO: We really should untangle the trampolines, exception handlers and
diff --git a/Source/Core/Core/PowerPC/Jit64Common/EmuCodeBlock.h b/Source/Core/Core/PowerPC/Jit64Common/EmuCodeBlock.h
index 08bafb49d7..244494a31e 100644
--- a/Source/Core/Core/PowerPC/Jit64Common/EmuCodeBlock.h
+++ b/Source/Core/Core/PowerPC/Jit64Common/EmuCodeBlock.h
@@ -23,9 +23,6 @@ class Mapping;
 class EmuCodeBlock : public Gen::X64CodeBlock
 {
 public:
-  void ClearCodeSpace() override;
-  void AllocCodeSpace(size_t size, bool need_low = true) override;
-
   void MemoryExceptionCheck();
 
   // Simple functions to switch between near and far code emitting
@@ -121,7 +118,7 @@ public:
   void Clear();
 
 protected:
-  ConstantPool m_const_pool{this};
+  ConstantPool m_const_pool;
   FarCodeCache m_far_code;
   u8* m_near_code;  // Backed up when we switch to far code.
 
diff --git a/Source/Core/Core/PowerPC/Jit64Common/FarCodeCache.cpp b/Source/Core/Core/PowerPC/Jit64Common/FarCodeCache.cpp
index 72909f6a6a..7efdd6f331 100644
--- a/Source/Core/Core/PowerPC/Jit64Common/FarCodeCache.cpp
+++ b/Source/Core/Core/PowerPC/Jit64Common/FarCodeCache.cpp
@@ -4,15 +4,13 @@
 
 #include "Core/PowerPC/Jit64Common/FarCodeCache.h"
 
-void FarCodeCache::Init(size_t size)
+void FarCodeCache::Init()
 {
-  AllocCodeSpace(size);
   m_enabled = true;
 }
 
 void FarCodeCache::Shutdown()
 {
-  FreeCodeSpace();
   m_enabled = false;
 }
 
diff --git a/Source/Core/Core/PowerPC/Jit64Common/FarCodeCache.h b/Source/Core/Core/PowerPC/Jit64Common/FarCodeCache.h
index bc82045870..9cbbf2c052 100644
--- a/Source/Core/Core/PowerPC/Jit64Common/FarCodeCache.h
+++ b/Source/Core/Core/PowerPC/Jit64Common/FarCodeCache.h
@@ -17,7 +17,7 @@ constexpr size_t FARCODE_SIZE_MMU = 1024 * 1024 * 48;
 class FarCodeCache : public Gen::X64CodeBlock
 {
 public:
-  void Init(size_t size);
+  void Init();
   void Shutdown();
 
   bool Enabled() const;
diff --git a/Source/Core/Core/PowerPC/Jit64Common/TrampolineCache.cpp b/Source/Core/Core/PowerPC/Jit64Common/TrampolineCache.cpp
index 5fa7c2d911..25848a1a24 100644
--- a/Source/Core/Core/PowerPC/Jit64Common/TrampolineCache.cpp
+++ b/Source/Core/Core/PowerPC/Jit64Common/TrampolineCache.cpp
@@ -23,21 +23,11 @@
 
 using namespace Gen;
 
-void TrampolineCache::Init(size_t size)
-{
-  AllocCodeSpace(size);
-}
-
 void TrampolineCache::ClearCodeSpace()
 {
   X64CodeBlock::ClearCodeSpace();
 }
 
-void TrampolineCache::Shutdown()
-{
-  FreeCodeSpace();
-}
-
 const u8* TrampolineCache::GenerateTrampoline(const TrampolineInfo& info)
 {
   if (info.read)
diff --git a/Source/Core/Core/PowerPC/Jit64Common/TrampolineCache.h b/Source/Core/Core/PowerPC/Jit64Common/TrampolineCache.h
index 769c4b4c84..9fc9a0b940 100644
--- a/Source/Core/Core/PowerPC/Jit64Common/TrampolineCache.h
+++ b/Source/Core/Core/PowerPC/Jit64Common/TrampolineCache.h
@@ -24,8 +24,6 @@ class TrampolineCache : public EmuCodeBlock
   const u8* GenerateWriteTrampoline(const TrampolineInfo& info);
 
 public:
-  void Init(size_t size);
-  void Shutdown();
   const u8* GenerateTrampoline(const TrampolineInfo& info);
   void ClearCodeSpace();
 };
diff --git a/Source/Core/Core/PowerPC/Jit64IL/JitIL.cpp b/Source/Core/Core/PowerPC/Jit64IL/JitIL.cpp
index 9a9730efc0..c2200791f2 100644
--- a/Source/Core/Core/PowerPC/Jit64IL/JitIL.cpp
+++ b/Source/Core/Core/PowerPC/Jit64IL/JitIL.cpp
@@ -262,12 +262,19 @@ void JitIL::Init()
   jo.accurateSinglePrecision = false;
   UpdateMemoryOptions();
 
-  trampolines.Init(jo.memcheck ? TRAMPOLINE_CODE_SIZE_MMU : TRAMPOLINE_CODE_SIZE);
-  AllocCodeSpace(CODE_SIZE);
+  const size_t routines_size = asm_routines.CODE_SIZE;
+  const size_t trampolines_size = jo.memcheck ? TRAMPOLINE_CODE_SIZE_MMU : TRAMPOLINE_CODE_SIZE;
+  const size_t farcode_size = jo.memcheck ? FARCODE_SIZE_MMU : FARCODE_SIZE;
+  const size_t constpool_size = m_const_pool.CONST_POOL_SIZE;
+  AllocCodeSpace(CODE_SIZE + routines_size + trampolines_size + farcode_size + constpool_size);
+  AddChildCodeSpace(&asm_routines, routines_size);
+  AddChildCodeSpace(&trampolines, trampolines_size);
+  AddChildCodeSpace(&m_far_code, farcode_size);
+  m_const_pool.Init(AllocChildCodeSpace(constpool_size), constpool_size);
+
   blocks.Init();
   asm_routines.Init(nullptr);
-
-  m_far_code.Init(jo.memcheck ? FARCODE_SIZE_MMU : FARCODE_SIZE);
+  m_far_code.Init();
   Clear();
 
   code_block.m_stats = &js.st;
@@ -299,8 +306,6 @@ void JitIL::Shutdown()
   FreeCodeSpace();
 
   blocks.Shutdown();
-  trampolines.Shutdown();
-  asm_routines.Shutdown();
   m_far_code.Shutdown();
 }