diff --git a/ARMeilleure/CodeGen/CompiledFunction.cs b/ARMeilleure/CodeGen/CompiledFunction.cs
index 61e89c2401..ab5e88ebb2 100644
--- a/ARMeilleure/CodeGen/CompiledFunction.cs
+++ b/ARMeilleure/CodeGen/CompiledFunction.cs
@@ -1,17 +1,56 @@
+using ARMeilleure.CodeGen.Linking;
 using ARMeilleure.CodeGen.Unwinding;
+using ARMeilleure.Translation.Cache;
+using System;
+using System.Runtime.InteropServices;
 
 namespace ARMeilleure.CodeGen
 {
-    struct CompiledFunction
+    /// <summary>
+    /// Represents a compiled function.
+    /// </summary>
+    readonly struct CompiledFunction
     {
+        /// <summary>
+        /// Gets the machine code of the <see cref="CompiledFunction"/>.
+        /// </summary>
         public byte[] Code { get; }
 
+        /// <summary>
+        /// Gets the <see cref="Unwinding.UnwindInfo"/> of the <see cref="CompiledFunction"/>.
+        /// </summary>
         public UnwindInfo UnwindInfo { get; }
 
-        public CompiledFunction(byte[] code, UnwindInfo unwindInfo)
+        /// <summary>
+        /// Gets the <see cref="Linking.RelocInfo"/> of the <see cref="CompiledFunction"/>.
+        /// </summary>
+        public RelocInfo RelocInfo { get; }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="CompiledFunction"/> struct with the specified machine code,
+        /// unwind info and relocation info.
+        /// </summary>
+        /// <param name="code">Machine code</param>
+        /// <param name="unwindInfo">Unwind info</param>
+        /// <param name="relocInfo">Relocation info</param>
+        internal CompiledFunction(byte[] code, UnwindInfo unwindInfo, RelocInfo relocInfo)
         {
             Code       = code;
             UnwindInfo = unwindInfo;
+            RelocInfo  = relocInfo;
+        }
+
+        /// <summary>
+        /// Maps the <see cref="CompiledFunction"/> onto the <see cref="JitCache"/> and returns a delegate of type
+        /// <typeparamref name="T"/> pointing to the mapped function.
+        /// </summary>
+        /// <typeparam name="T">Type of delegate</typeparam>
+        /// <returns>A delegate of type <typeparamref name="T"/> pointing to the mapped function</returns>
+        public T Map<T>()
+        {
+            IntPtr codePtr = JitCache.Map(this);
+
+            return Marshal.GetDelegateForFunctionPointer<T>(codePtr);
         }
     }
 }
\ No newline at end of file
diff --git a/ARMeilleure/CodeGen/Linking/RelocEntry.cs b/ARMeilleure/CodeGen/Linking/RelocEntry.cs
new file mode 100644
index 0000000000..a27bfded2c
--- /dev/null
+++ b/ARMeilleure/CodeGen/Linking/RelocEntry.cs
@@ -0,0 +1,38 @@
+namespace ARMeilleure.CodeGen.Linking
+{
+    /// <summary>
+    /// Represents a relocation.
+    /// </summary>
+    readonly struct RelocEntry
+    {
+        public const int Stride = 13; // Bytes.
+
+        /// <summary>
+        /// Gets the position of the relocation.
+        /// </summary>
+        public int Position { get; }
+
+        /// <summary>
+        /// Gets the <see cref="Symbol"/> of the relocation.
+        /// </summary>
+        public Symbol Symbol { get; }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="RelocEntry"/> struct with the specified position and
+        /// <see cref="Symbol"/>.
+        /// </summary>
+        /// <param name="position">Position of relocation</param>
+        /// <param name="symbol">Symbol of relocation</param>
+        public RelocEntry(int position, Symbol symbol)
+        {
+            Position = position;
+            Symbol = symbol;
+        }
+
+        /// <inheritdoc/>
+        public override string ToString()
+        {
+            return $"({nameof(Position)} = {Position}, {nameof(Symbol)} = {Symbol})";
+        }
+    }
+}
\ No newline at end of file
diff --git a/ARMeilleure/CodeGen/Linking/RelocInfo.cs b/ARMeilleure/CodeGen/Linking/RelocInfo.cs
new file mode 100644
index 0000000000..922b8bfeb2
--- /dev/null
+++ b/ARMeilleure/CodeGen/Linking/RelocInfo.cs
@@ -0,0 +1,32 @@
+using System;
+
+namespace ARMeilleure.CodeGen.Linking
+{
+    /// <summary>
+    /// Represents relocation information about a <see cref="CompiledFunction"/>.
+    /// </summary>
+    readonly struct RelocInfo
+    {
+        /// <summary>
+        /// Gets an empty <see cref="RelocInfo"/>.
+        /// </summary>
+        public static RelocInfo Empty { get; } = new RelocInfo(null);
+
+        private readonly RelocEntry[] _entries;
+
+        /// <summary>
+        /// Gets the set of <see cref="RelocEntry"/>.
+        /// </summary>
+        public ReadOnlySpan<RelocEntry> Entries => _entries ?? Array.Empty<RelocEntry>();
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="RelocInfo"/> struct with the specified set of
+        /// <see cref="RelocEntry"/>.
+        /// </summary>
+        /// <param name="entries">Set of <see cref="RelocInfo"/> to use</param>
+        public RelocInfo(RelocEntry[] entries)
+        {
+            _entries = entries ?? Array.Empty<RelocEntry>();
+        }
+    }
+}
\ No newline at end of file
diff --git a/ARMeilleure/Translation/PTC/Symbol.cs b/ARMeilleure/CodeGen/Linking/Symbol.cs
similarity index 97%
rename from ARMeilleure/Translation/PTC/Symbol.cs
rename to ARMeilleure/CodeGen/Linking/Symbol.cs
index f9d67742bc..fa47ee23b9 100644
--- a/ARMeilleure/Translation/PTC/Symbol.cs
+++ b/ARMeilleure/CodeGen/Linking/Symbol.cs
@@ -1,11 +1,11 @@
 using System;
 
-namespace ARMeilleure.Translation.PTC
+namespace ARMeilleure.CodeGen.Linking
 {
     /// <summary>
     /// Represents a symbol.
     /// </summary>
-    struct Symbol
+    readonly struct Symbol
     {
         private readonly ulong _value;
 
diff --git a/ARMeilleure/Translation/PTC/SymbolType.cs b/ARMeilleure/CodeGen/Linking/SymbolType.cs
similarity index 67%
rename from ARMeilleure/Translation/PTC/SymbolType.cs
rename to ARMeilleure/CodeGen/Linking/SymbolType.cs
index cd7b6c1c6a..b05b696927 100644
--- a/ARMeilleure/Translation/PTC/SymbolType.cs
+++ b/ARMeilleure/CodeGen/Linking/SymbolType.cs
@@ -1,4 +1,4 @@
-namespace ARMeilleure.Translation.PTC
+namespace ARMeilleure.CodeGen.Linking
 {
     /// <summary>
     /// Types of <see cref="Symbol"/>.
@@ -11,17 +11,17 @@
         None,
 
         /// <summary>
-        /// Refers to an entry in <see cref="Delegates"/>.
+        /// Refers to an entry in <see cref="Translation.Delegates"/>.
         /// </summary>
         DelegateTable,
 
         /// <summary>
-        /// Refers to an entry in <see cref="Translator.FunctionTable"/>.
+        /// Refers to an entry in <see cref="Translation.Translator.FunctionTable"/>.
         /// </summary>
         FunctionTable,
 
         /// <summary>
-        /// Refers to a special symbol which is handled by <see cref="Ptc.PatchCode"/>.
+        /// Refers to a special symbol which is handled by <see cref="Translation.PTC.Ptc.PatchCode"/>.
         /// </summary>
         Special
     }
diff --git a/ARMeilleure/CodeGen/X86/Assembler.cs b/ARMeilleure/CodeGen/X86/Assembler.cs
index 044f604726..9560875662 100644
--- a/ARMeilleure/CodeGen/X86/Assembler.cs
+++ b/ARMeilleure/CodeGen/X86/Assembler.cs
@@ -1,6 +1,7 @@
+using ARMeilleure.CodeGen.Linking;
 using ARMeilleure.IntermediateRepresentation;
-using ARMeilleure.Translation.PTC;
 using System;
+using System.Collections.Generic;
 using System.Diagnostics;
 using System.IO;
 
@@ -61,12 +62,12 @@ namespace ARMeilleure.CodeGen.X86
             }
         }
 
-        private static InstructionInfo[] _instTable;
+        private readonly static InstructionInfo[] _instTable;
 
-        private Stream _stream;
+        private readonly Stream _stream;
 
-        private PtcInfo _ptcInfo;
-        private bool    _ptcDisabled;
+        public List<RelocEntry> Relocs { get; }
+        public bool HasRelocs => Relocs != null;
 
         static Assembler()
         {
@@ -294,12 +295,10 @@ namespace ARMeilleure.CodeGen.X86
             _instTable[(int)inst] = info;
         }
 
-        public Assembler(Stream stream, PtcInfo ptcInfo = null)
+        public Assembler(Stream stream, bool relocatable)
         {
             _stream = stream;
-
-            _ptcInfo     = ptcInfo;
-            _ptcDisabled = ptcInfo == null;
+            Relocs = relocatable ? new List<RelocEntry>() : null;
         }
 
         public void Add(Operand dest, Operand source, OperandType type)
@@ -498,7 +497,7 @@ namespace ARMeilleure.CodeGen.X86
 
         public void Jcc(X86Condition condition, long offset)
         {
-            if (_ptcDisabled && ConstFitsOnS8(offset))
+            if (!HasRelocs && ConstFitsOnS8(offset))
             {
                 WriteByte((byte)(0x70 | (int)condition));
 
@@ -519,7 +518,7 @@ namespace ARMeilleure.CodeGen.X86
 
         public void Jmp(long offset)
         {
-            if (_ptcDisabled && ConstFitsOnS8(offset))
+            if (!HasRelocs && ConstFitsOnS8(offset))
             {
                 WriteByte(0xeb);
 
@@ -980,9 +979,9 @@ namespace ARMeilleure.CodeGen.X86
 
                         WriteByte((byte)(info.OpRImm64 + (dest.GetRegister().Index & 0b111)));
 
-                        if (_ptcInfo != default && source.Relocatable)
+                        if (HasRelocs && source.Relocatable)
                         {
-                            _ptcInfo.WriteRelocEntry(new RelocEntry((int)_stream.Position, source.Symbol));
+                            Relocs.Add(new RelocEntry((int)_stream.Position, source.Symbol));
                         }
 
                         WriteUInt64(imm);
@@ -1396,9 +1395,9 @@ namespace ARMeilleure.CodeGen.X86
             return ConstFitsOnS32(value);
         }
 
-        public static int GetJccLength(long offset, bool ptcDisabled = true)
+        public static int GetJccLength(long offset, bool relocatable = false)
         {
-            if (ptcDisabled && ConstFitsOnS8(offset < 0 ? offset - 2 : offset))
+            if (!relocatable && ConstFitsOnS8(offset < 0 ? offset - 2 : offset))
             {
                 return 2;
             }
@@ -1412,9 +1411,9 @@ namespace ARMeilleure.CodeGen.X86
             }
         }
 
-        public static int GetJmpLength(long offset, bool ptcDisabled = true)
+        public static int GetJmpLength(long offset, bool relocatable = false)
         {
-            if (ptcDisabled && ConstFitsOnS8(offset < 0 ? offset - 2 : offset))
+            if (!relocatable && ConstFitsOnS8(offset < 0 ? offset - 2 : offset))
             {
                 return 2;
             }
diff --git a/ARMeilleure/CodeGen/X86/CodeGenContext.cs b/ARMeilleure/CodeGen/X86/CodeGenContext.cs
index fa726f2f86..7e96dd85a7 100644
--- a/ARMeilleure/CodeGen/X86/CodeGenContext.cs
+++ b/ARMeilleure/CodeGen/X86/CodeGenContext.cs
@@ -1,7 +1,7 @@
+using ARMeilleure.CodeGen.Linking;
 using ARMeilleure.CodeGen.RegisterAllocators;
 using ARMeilleure.Common;
 using ARMeilleure.IntermediateRepresentation;
-using ARMeilleure.Translation.PTC;
 using System;
 using System.Collections.Generic;
 using System.Diagnostics;
@@ -13,10 +13,8 @@ namespace ARMeilleure.CodeGen.X86
     {
         private const int ReservedBytesForJump = 1;
 
-        private Stream _stream;
-
-        private PtcInfo _ptcInfo;
-        private bool    _ptcDisabled;
+        private readonly Stream _stream;
+        private readonly bool _relocatable;
 
         public int StreamOffset => (int)_stream.Length;
 
@@ -27,22 +25,17 @@ namespace ARMeilleure.CodeGen.X86
         public BasicBlock CurrBlock { get; private set; }
 
         public int CallArgsRegionSize { get; }
-        public int XmmSaveRegionSize  { get; }
+        public int XmmSaveRegionSize { get; }
 
-        private long[] _blockOffsets;
+        private readonly long[] _blockOffsets;
 
         private struct Jump
         {
             public bool IsConditional { get; }
-
             public X86Condition Condition { get; }
-
             public BasicBlock Target { get; }
-
             public long JumpPosition { get; }
-
             public long RelativeOffset { get; set; }
-
             public int InstSize { get; set; }
 
             public Jump(BasicBlock target, long jumpPosition, int instSize = 0)
@@ -70,33 +63,26 @@ namespace ARMeilleure.CodeGen.X86
             }
         }
 
-        private List<Jump> _jumps;
+        private readonly List<Jump> _jumps;
 
         private X86Condition _jNearCondition;
-
         private long _jNearPosition;
-        private int  _jNearLength;
+        private int _jNearLength;
 
-        public CodeGenContext(Stream stream, AllocationResult allocResult, int maxCallArgs, int blocksCount, PtcInfo ptcInfo = null)
+        public CodeGenContext(Stream stream, AllocationResult allocResult, int maxCallArgs, int blocksCount, bool relocatable)
         {
             _stream = stream;
-
-            AllocResult = allocResult;
-
-            Assembler = new Assembler(stream, ptcInfo);
-
-            CallArgsRegionSize = GetCallArgsRegionSize(allocResult, maxCallArgs, out int xmmSaveRegionSize);
-            XmmSaveRegionSize  = xmmSaveRegionSize;
-
+            _relocatable = relocatable;
             _blockOffsets = new long[blocksCount];
-
             _jumps = new List<Jump>();
 
-            _ptcInfo     = ptcInfo;
-            _ptcDisabled = ptcInfo == null;
+            AllocResult = allocResult;
+            Assembler = new Assembler(stream, relocatable);
+            CallArgsRegionSize = GetCallArgsRegionSize(allocResult, maxCallArgs, out int xmmSaveRegionSize);
+            XmmSaveRegionSize  = xmmSaveRegionSize;
         }
 
-        private int GetCallArgsRegionSize(AllocationResult allocResult, int maxCallArgs, out int xmmSaveRegionSize)
+        private static int GetCallArgsRegionSize(AllocationResult allocResult, int maxCallArgs, out int xmmSaveRegionSize)
         {
             // We need to add 8 bytes to the total size, as the call to this
             // function already pushed 8 bytes (the return address).
@@ -144,7 +130,7 @@ namespace ARMeilleure.CodeGen.X86
 
         public void JumpTo(BasicBlock target)
         {
-            if (_ptcDisabled)
+            if (!_relocatable)
             {
                 _jumps.Add(new Jump(target, _stream.Position));
 
@@ -160,7 +146,7 @@ namespace ARMeilleure.CodeGen.X86
 
         public void JumpTo(X86Condition condition, BasicBlock target)
         {
-            if (_ptcDisabled)
+            if (!_relocatable)
             {
                 _jumps.Add(new Jump(condition, target, _stream.Position));
 
@@ -178,7 +164,7 @@ namespace ARMeilleure.CodeGen.X86
         {
             _jNearCondition = condition;
             _jNearPosition  = _stream.Position;
-            _jNearLength    = Assembler.GetJccLength(0, _ptcDisabled);
+            _jNearLength    = Assembler.GetJccLength(0, _relocatable);
 
             _stream.Seek(_jNearLength, SeekOrigin.Current);
         }
@@ -191,7 +177,7 @@ namespace ARMeilleure.CodeGen.X86
 
             long offset = currentPosition - (_jNearPosition + _jNearLength);
 
-            Debug.Assert(_jNearLength == Assembler.GetJccLength(offset, _ptcDisabled), "Relative offset doesn't fit on near jump.");
+            Debug.Assert(_jNearLength == Assembler.GetJccLength(offset, _relocatable), "Relative offset doesn't fit on near jump.");
 
             Assembler.Jcc(_jNearCondition, offset);
 
@@ -206,7 +192,7 @@ namespace ARMeilleure.CodeGen.X86
             }
         }
 
-        public byte[] GetCode()
+        public (byte[], RelocInfo) GetCode()
         {
             // Write jump relative offsets.
             bool modified;
@@ -223,7 +209,7 @@ namespace ARMeilleure.CodeGen.X86
 
                     long offset = jumpTarget - jump.JumpPosition;
 
-                    if (_ptcDisabled)
+                    if (!_relocatable)
                     {
                         if (offset < 0)
                         {
@@ -300,7 +286,7 @@ namespace ARMeilleure.CodeGen.X86
 
             using (MemoryStream codeStream = new MemoryStream())
             {
-                Assembler assembler = new Assembler(codeStream, _ptcInfo);
+                Assembler assembler = new Assembler(codeStream, _relocatable);
 
                 for (int index = 0; index < _jumps.Count; index++)
                 {
@@ -309,7 +295,7 @@ namespace ARMeilleure.CodeGen.X86
                     Span<byte> buffer = new byte[jump.JumpPosition - _stream.Position];
 
                     _stream.Read(buffer);
-                    _stream.Seek(_ptcDisabled ? ReservedBytesForJump : jump.InstSize, SeekOrigin.Current);
+                    _stream.Seek(!_relocatable ? ReservedBytesForJump : jump.InstSize, SeekOrigin.Current);
 
                     codeStream.Write(buffer);
 
@@ -325,7 +311,12 @@ namespace ARMeilleure.CodeGen.X86
 
                 _stream.CopyTo(codeStream);
 
-                return codeStream.ToArray();
+                var code = codeStream.ToArray();
+                var relocInfo = Assembler.HasRelocs
+                    ? new RelocInfo(Assembler.Relocs.ToArray())
+                    : RelocInfo.Empty;
+
+                return (code, relocInfo);
             }
         }
     }
diff --git a/ARMeilleure/CodeGen/X86/CodeGenerator.cs b/ARMeilleure/CodeGen/X86/CodeGenerator.cs
index 5818eb2eeb..924c113c4f 100644
--- a/ARMeilleure/CodeGen/X86/CodeGenerator.cs
+++ b/ARMeilleure/CodeGen/X86/CodeGenerator.cs
@@ -1,3 +1,4 @@
+using ARMeilleure.CodeGen.Linking;
 using ARMeilleure.CodeGen.Optimizations;
 using ARMeilleure.CodeGen.RegisterAllocators;
 using ARMeilleure.CodeGen.Unwinding;
@@ -5,7 +6,6 @@ using ARMeilleure.Common;
 using ARMeilleure.Diagnostics;
 using ARMeilleure.IntermediateRepresentation;
 using ARMeilleure.Translation;
-using ARMeilleure.Translation.PTC;
 using System;
 using System.Collections.Generic;
 using System.Diagnostics;
@@ -91,7 +91,7 @@ namespace ARMeilleure.CodeGen.X86
             _instTable[(int)inst] = func;
         }
 
-        public static CompiledFunction Generate(CompilerContext cctx, PtcInfo ptcInfo = null)
+        public static CompiledFunction Generate(CompilerContext cctx)
         {
             ControlFlowGraph cfg = cctx.Cfg;
 
@@ -149,53 +149,47 @@ namespace ARMeilleure.CodeGen.X86
 
             Logger.StartPass(PassName.CodeGeneration);
 
-            using (MemoryStream stream = new MemoryStream())
+            bool relocatable = (cctx.Options & CompilerOptions.Relocatable) != 0;
+
+            using MemoryStream stream = new();
+
+            CodeGenContext context = new(stream, allocResult, maxCallArgs, cfg.Blocks.Count, relocatable);
+
+            UnwindInfo unwindInfo = WritePrologue(context);
+
+            for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext)
             {
-                CodeGenContext context = new CodeGenContext(stream, allocResult, maxCallArgs, cfg.Blocks.Count, ptcInfo);
+                context.EnterBlock(block);
 
-                UnwindInfo unwindInfo = WritePrologue(context);
-
-                ptcInfo?.WriteUnwindInfo(unwindInfo);
-
-                for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext)
+                for (Operation node = block.Operations.First; node != default; node = node.ListNext)
                 {
-                    context.EnterBlock(block);
-
-                    for (Operation node = block.Operations.First; node != default; node = node.ListNext)
-                    {
-                        GenerateOperation(context, node);
-                    }
-
-                    if (block.SuccessorsCount == 0)
-                    {
-                        // The only blocks which can have 0 successors are exit blocks.
-                        Operation last = block.Operations.Last;
-
-                        Debug.Assert(last.Instruction == Instruction.Tailcall ||
-                                     last.Instruction == Instruction.Return);
-                    }
-                    else
-                    {
-                        BasicBlock succ = block.GetSuccessor(0);
-
-                        if (succ != block.ListNext)
-                        {
-                            context.JumpTo(succ);
-                        }
-                    }
+                    GenerateOperation(context, node);
                 }
 
-                byte[] code = context.GetCode();
-
-                if (ptcInfo != null)
+                if (block.SuccessorsCount == 0)
                 {
-                    ptcInfo.Code = code;
+                    // The only blocks which can have 0 successors are exit blocks.
+                    Operation last = block.Operations.Last;
+
+                    Debug.Assert(last.Instruction == Instruction.Tailcall ||
+                                 last.Instruction == Instruction.Return);
                 }
+                else
+                {
+                    BasicBlock succ = block.GetSuccessor(0);
 
-                Logger.EndPass(PassName.CodeGeneration);
-
-                return new CompiledFunction(code, unwindInfo);
+                    if (succ != block.ListNext)
+                    {
+                        context.JumpTo(succ);
+                    }
+                }
             }
+
+            (byte[] code, RelocInfo relocInfo) = context.GetCode();
+
+            Logger.EndPass(PassName.CodeGeneration);
+
+            return new CompiledFunction(code, unwindInfo, relocInfo);
         }
 
         private static void GenerateOperation(CodeGenContext context, Operation operation)
diff --git a/ARMeilleure/Instructions/InstEmitFlowHelper.cs b/ARMeilleure/Instructions/InstEmitFlowHelper.cs
index 2d73745316..8ce704b102 100644
--- a/ARMeilleure/Instructions/InstEmitFlowHelper.cs
+++ b/ARMeilleure/Instructions/InstEmitFlowHelper.cs
@@ -1,8 +1,8 @@
+using ARMeilleure.CodeGen.Linking;
 using ARMeilleure.Decoders;
 using ARMeilleure.IntermediateRepresentation;
 using ARMeilleure.State;
 using ARMeilleure.Translation;
-using ARMeilleure.Translation.Cache;
 using ARMeilleure.Translation.PTC;
 
 using static ARMeilleure.Instructions.InstEmitHelper;
diff --git a/ARMeilleure/IntermediateRepresentation/Operand.cs b/ARMeilleure/IntermediateRepresentation/Operand.cs
index 2e0b649c05..ff3354f2fd 100644
--- a/ARMeilleure/IntermediateRepresentation/Operand.cs
+++ b/ARMeilleure/IntermediateRepresentation/Operand.cs
@@ -1,5 +1,5 @@
+using ARMeilleure.CodeGen.Linking;
 using ARMeilleure.Common;
-using ARMeilleure.Translation.PTC;
 using System;
 using System.Collections.Generic;
 using System.Diagnostics;
diff --git a/ARMeilleure/Signal/NativeSignalHandler.cs b/ARMeilleure/Signal/NativeSignalHandler.cs
index 18d19b82fb..2716896994 100644
--- a/ARMeilleure/Signal/NativeSignalHandler.cs
+++ b/ARMeilleure/Signal/NativeSignalHandler.cs
@@ -261,7 +261,7 @@ namespace ARMeilleure.Signal
 
             OperandType[] argTypes = new OperandType[] { OperandType.I32, OperandType.I64, OperandType.I64 };
 
-            return Compiler.Compile<UnixExceptionHandler>(cfg, argTypes, OperandType.None, CompilerOptions.HighCq);
+            return Compiler.Compile(cfg, argTypes, OperandType.None, CompilerOptions.HighCq).Map<UnixExceptionHandler>();
         }
 
         private static VectoredExceptionHandler GenerateWindowsSignalHandler(IntPtr signalStructPtr)
@@ -315,7 +315,7 @@ namespace ARMeilleure.Signal
 
             OperandType[] argTypes = new OperandType[] { OperandType.I64 };
 
-            return Compiler.Compile<VectoredExceptionHandler>(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq);
+            return Compiler.Compile(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq).Map<VectoredExceptionHandler>();
         }
     }
 }
diff --git a/ARMeilleure/Translation/ArmEmitterContext.cs b/ARMeilleure/Translation/ArmEmitterContext.cs
index 563775ee98..6d02728ca4 100644
--- a/ARMeilleure/Translation/ArmEmitterContext.cs
+++ b/ARMeilleure/Translation/ArmEmitterContext.cs
@@ -1,3 +1,4 @@
+using ARMeilleure.CodeGen.Linking;
 using ARMeilleure.Common;
 using ARMeilleure.Decoders;
 using ARMeilleure.Diagnostics;
diff --git a/ARMeilleure/Translation/Compiler.cs b/ARMeilleure/Translation/Compiler.cs
index 812144a13c..9e4cdb243f 100644
--- a/ARMeilleure/Translation/Compiler.cs
+++ b/ARMeilleure/Translation/Compiler.cs
@@ -2,35 +2,16 @@ using ARMeilleure.CodeGen;
 using ARMeilleure.CodeGen.X86;
 using ARMeilleure.Diagnostics;
 using ARMeilleure.IntermediateRepresentation;
-using ARMeilleure.Translation.Cache;
-using ARMeilleure.Translation.PTC;
-using System;
-using System.Runtime.InteropServices;
 
 namespace ARMeilleure.Translation
 {
     static class Compiler
     {
-        public static T Compile<T>(
-            ControlFlowGraph cfg,
-            OperandType[]    argTypes,
-            OperandType      retType,
-            CompilerOptions  options,
-            PtcInfo          ptcInfo = null)
-        {
-            CompiledFunction func = Compile(cfg, argTypes, retType, options, ptcInfo);
-
-            IntPtr codePtr = JitCache.Map(func);
-
-            return Marshal.GetDelegateForFunctionPointer<T>(codePtr);
-        }
-
         public static CompiledFunction Compile(
             ControlFlowGraph cfg,
             OperandType[]    argTypes,
             OperandType      retType,
-            CompilerOptions  options,
-            PtcInfo          ptcInfo = null)
+            CompilerOptions  options)
         {
             Logger.StartPass(PassName.Dominance);
 
@@ -57,7 +38,7 @@ namespace ARMeilleure.Translation
 
             CompilerContext cctx = new(cfg, argTypes, retType, options);
 
-            return CodeGenerator.Generate(cctx, ptcInfo);
+            return CodeGenerator.Generate(cctx);
         }
     }
 }
\ No newline at end of file
diff --git a/ARMeilleure/Translation/CompilerOptions.cs b/ARMeilleure/Translation/CompilerOptions.cs
index 53998ec6f3..0a07ed4ab9 100644
--- a/ARMeilleure/Translation/CompilerOptions.cs
+++ b/ARMeilleure/Translation/CompilerOptions.cs
@@ -5,10 +5,11 @@ namespace ARMeilleure.Translation
     [Flags]
     enum CompilerOptions
     {
-        None     = 0,
-        SsaForm  = 1 << 0,
-        Optimize = 1 << 1,
-        Lsra     = 1 << 2,
+        None        = 0,
+        SsaForm     = 1 << 0,
+        Optimize    = 1 << 1,
+        Lsra        = 1 << 2,
+        Relocatable = 1 << 3,
 
         MediumCq = SsaForm | Optimize,
         HighCq   = SsaForm | Optimize | Lsra
diff --git a/ARMeilleure/Translation/PTC/Ptc.cs b/ARMeilleure/Translation/PTC/Ptc.cs
index 8d4f971bdb..ad2871d079 100644
--- a/ARMeilleure/Translation/PTC/Ptc.cs
+++ b/ARMeilleure/Translation/PTC/Ptc.cs
@@ -1,9 +1,9 @@
 using ARMeilleure.CodeGen;
+using ARMeilleure.CodeGen.Linking;
 using ARMeilleure.CodeGen.Unwinding;
 using ARMeilleure.CodeGen.X86;
 using ARMeilleure.Common;
 using ARMeilleure.Memory;
-using ARMeilleure.Translation.Cache;
 using Ryujinx.Common;
 using Ryujinx.Common.Configuration;
 using Ryujinx.Common.Logging;
@@ -727,15 +727,10 @@ namespace ARMeilleure.Translation.PTC
             UnwindInfo unwindInfo,
             bool highCq)
         {
-            CompiledFunction cFunc = new CompiledFunction(code, unwindInfo);
+            var cFunc = new CompiledFunction(code, unwindInfo, RelocInfo.Empty);
+            var gFunc = cFunc.Map<GuestFunction>();
 
-            IntPtr codePtr = JitCache.Map(cFunc);
-
-            GuestFunction gFunc = Marshal.GetDelegateForFunctionPointer<GuestFunction>(codePtr);
-
-            TranslatedFunction tFunc = new TranslatedFunction(gFunc, callCounter, guestSize, highCq);
-
-            return tFunc;
+            return new TranslatedFunction(gFunc, callCounter, guestSize, highCq);
         }
 
         private static void UpdateInfo(InfoEntry infoEntry)
@@ -889,10 +884,14 @@ namespace ARMeilleure.Translation.PTC
             return XXHash128.ComputeHash(memory.GetSpan(address, checked((int)(guestSize))));
         }
 
-        internal static void WriteInfoCodeRelocUnwindInfo(ulong address, ulong guestSize, Hash128 hash, bool highCq, PtcInfo ptcInfo)
+        internal static void WriteCompiledFunction(ulong address, ulong guestSize, Hash128 hash, bool highCq, CompiledFunction compiledFunc)
         {
             lock (_lock)
             {
+                byte[] code = compiledFunc.Code;
+                RelocInfo relocInfo = compiledFunc.RelocInfo;
+                UnwindInfo unwindInfo = compiledFunc.UnwindInfo;
+
                 InfoEntry infoEntry = new InfoEntry();
 
                 infoEntry.Address = address;
@@ -900,18 +899,37 @@ namespace ARMeilleure.Translation.PTC
                 infoEntry.Hash = hash;
                 infoEntry.HighCq = highCq;
                 infoEntry.Stubbed = false;
-                infoEntry.CodeLength = ptcInfo.Code.Length;
-                infoEntry.RelocEntriesCount = ptcInfo.RelocEntriesCount;
+                infoEntry.CodeLength = code.Length;
+                infoEntry.RelocEntriesCount = relocInfo.Entries.Length;
 
                 SerializeStructure(_infosStream, infoEntry);
 
-                WriteCode(ptcInfo.Code.AsSpan());
+                WriteCode(code.AsSpan());
 
                 // WriteReloc.
-                ptcInfo.RelocStream.WriteTo(_relocsStream);
+                using var relocInfoWriter = new BinaryWriter(_relocsStream, EncodingCache.UTF8NoBOM, true);
+
+                foreach (RelocEntry entry in relocInfo.Entries)
+                {
+                    relocInfoWriter.Write(entry.Position);
+                    relocInfoWriter.Write((byte)entry.Symbol.Type);
+                    relocInfoWriter.Write(entry.Symbol.Value);
+                }
 
                 // WriteUnwindInfo.
-                ptcInfo.UnwindInfoStream.WriteTo(_unwindInfosStream);
+                using var unwindInfoWriter = new BinaryWriter(_unwindInfosStream, EncodingCache.UTF8NoBOM, true);
+
+                unwindInfoWriter.Write(unwindInfo.PushEntries.Length);
+
+                foreach (UnwindPushEntry unwindPushEntry in unwindInfo.PushEntries)
+                {
+                    unwindInfoWriter.Write((int)unwindPushEntry.PseudoOp);
+                    unwindInfoWriter.Write(unwindPushEntry.PrologOffset);
+                    unwindInfoWriter.Write(unwindPushEntry.RegIndex);
+                    unwindInfoWriter.Write(unwindPushEntry.StackOffsetOrAllocSize);
+                }
+
+                unwindInfoWriter.Write(unwindInfo.PrologSize);
             }
         }
 
diff --git a/ARMeilleure/Translation/PTC/PtcInfo.cs b/ARMeilleure/Translation/PTC/PtcInfo.cs
deleted file mode 100644
index b28b2dc1f5..0000000000
--- a/ARMeilleure/Translation/PTC/PtcInfo.cs
+++ /dev/null
@@ -1,63 +0,0 @@
-using ARMeilleure.CodeGen.Unwinding;
-using System;
-using System.IO;
-
-namespace ARMeilleure.Translation.PTC
-{
-    class PtcInfo : IDisposable
-    {
-        private readonly BinaryWriter _relocWriter;
-        private readonly BinaryWriter _unwindInfoWriter;
-
-        public byte[] Code { get; set; }
-
-        public MemoryStream RelocStream      { get; }
-        public MemoryStream UnwindInfoStream { get; }
-
-        public int RelocEntriesCount { get; private set; }
-
-        public PtcInfo()
-        {
-            RelocStream      = new MemoryStream();
-            UnwindInfoStream = new MemoryStream();
-
-            _relocWriter      = new BinaryWriter(RelocStream,      EncodingCache.UTF8NoBOM, true);
-            _unwindInfoWriter = new BinaryWriter(UnwindInfoStream, EncodingCache.UTF8NoBOM, true);
-
-            RelocEntriesCount = 0;
-        }
-
-        public void WriteRelocEntry(RelocEntry relocEntry)
-        {
-            _relocWriter.Write((int)relocEntry.Position);
-            _relocWriter.Write((byte)relocEntry.Symbol.Type);
-            _relocWriter.Write((ulong)relocEntry.Symbol.Value);
-
-            RelocEntriesCount++;
-        }
-
-        public void WriteUnwindInfo(UnwindInfo unwindInfo)
-        {
-            _unwindInfoWriter.Write((int)unwindInfo.PushEntries.Length);
-
-            foreach (UnwindPushEntry unwindPushEntry in unwindInfo.PushEntries)
-            {
-                _unwindInfoWriter.Write((int)unwindPushEntry.PseudoOp);
-                _unwindInfoWriter.Write((int)unwindPushEntry.PrologOffset);
-                _unwindInfoWriter.Write((int)unwindPushEntry.RegIndex);
-                _unwindInfoWriter.Write((int)unwindPushEntry.StackOffsetOrAllocSize);
-            }
-
-            _unwindInfoWriter.Write((int)unwindInfo.PrologSize);
-        }
-
-        public void Dispose()
-        {
-            _relocWriter.Dispose();
-            _unwindInfoWriter.Dispose();
-
-            RelocStream.Dispose();
-            UnwindInfoStream.Dispose();
-        }
-    }
-}
\ No newline at end of file
diff --git a/ARMeilleure/Translation/PTC/RelocEntry.cs b/ARMeilleure/Translation/PTC/RelocEntry.cs
deleted file mode 100644
index 545612d019..0000000000
--- a/ARMeilleure/Translation/PTC/RelocEntry.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-namespace ARMeilleure.Translation.PTC
-{
-    struct RelocEntry
-    {
-        public const int Stride = 13; // Bytes.
-
-        public int Position;
-        public Symbol Symbol;
-
-        public RelocEntry(int position, Symbol symbol)
-        {
-            Position = position;
-            Symbol = symbol;
-        }
-
-        public override string ToString()
-        {
-            return $"({nameof(Position)} = {Position}, {nameof(Symbol)} = {Symbol})";
-        }
-    }
-}
\ No newline at end of file
diff --git a/ARMeilleure/Translation/Translator.cs b/ARMeilleure/Translation/Translator.cs
index 03ed4c5eb1..9974fb2d83 100644
--- a/ARMeilleure/Translation/Translator.cs
+++ b/ARMeilleure/Translation/Translator.cs
@@ -1,3 +1,4 @@
+using ARMeilleure.CodeGen;
 using ARMeilleure.Common;
 using ARMeilleure.Decoders;
 using ARMeilleure.Diagnostics;
@@ -279,32 +280,30 @@ namespace ARMeilleure.Translation
 
             Logger.EndPass(PassName.RegisterUsage);
 
-            OperandType[] argTypes = new OperandType[] { OperandType.I64 };
+            var retType = OperandType.I64;
+            var argTypes = new OperandType[] { OperandType.I64 };
 
-            CompilerOptions options = highCq ? CompilerOptions.HighCq : CompilerOptions.None;
+            var options = highCq ? CompilerOptions.HighCq : CompilerOptions.None;
 
-            GuestFunction func;
-
-            if (!context.HasPtc)
+            if (context.HasPtc)
             {
-                func = Compiler.Compile<GuestFunction>(cfg, argTypes, OperandType.I64, options);
+                options |= CompilerOptions.Relocatable;
             }
-            else
+
+            CompiledFunction compiledFunc = Compiler.Compile(cfg, argTypes, retType, options);
+
+            if (context.HasPtc)
             {
-                using PtcInfo ptcInfo = new PtcInfo();
-
-                func = Compiler.Compile<GuestFunction>(cfg, argTypes, OperandType.I64, options, ptcInfo);
-
                 Hash128 hash = Ptc.ComputeHash(Memory, address, funcSize);
 
-                Ptc.WriteInfoCodeRelocUnwindInfo(address, funcSize, hash, highCq, ptcInfo);
+                Ptc.WriteCompiledFunction(address, funcSize, hash, highCq, compiledFunc);
             }
 
-            var result = new TranslatedFunction(func, counter, funcSize, highCq);
+            GuestFunction func = compiledFunc.Map<GuestFunction>();
 
             Allocators.ResetAll();
 
-            return result;
+            return new TranslatedFunction(func, counter, funcSize, highCq);
         }
 
         private struct Range
diff --git a/ARMeilleure/Translation/TranslatorStubs.cs b/ARMeilleure/Translation/TranslatorStubs.cs
index 48fa3a9478..4ad6c2f233 100644
--- a/ARMeilleure/Translation/TranslatorStubs.cs
+++ b/ARMeilleure/Translation/TranslatorStubs.cs
@@ -178,7 +178,7 @@ namespace ARMeilleure.Translation
             var retType = OperandType.I64;
             var argTypes = new[] { OperandType.I64 };
 
-            var func = Compiler.Compile<GuestFunction>(cfg, argTypes, retType, CompilerOptions.HighCq);
+            var func = Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq).Map<GuestFunction>();
 
             return Marshal.GetFunctionPointerForDelegate(func);
         }
@@ -204,7 +204,7 @@ namespace ARMeilleure.Translation
             var retType = OperandType.I64;
             var argTypes = new[] { OperandType.I64 };
 
-            var func = Compiler.Compile<GuestFunction>(cfg, argTypes, retType, CompilerOptions.HighCq);
+            var func = Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq).Map<GuestFunction>();
 
             return Marshal.GetFunctionPointerForDelegate(func);
         }
@@ -242,7 +242,7 @@ namespace ARMeilleure.Translation
             var retType = OperandType.None;
             var argTypes = new[] { OperandType.I64, OperandType.I64 };
 
-            return Compiler.Compile<DispatcherFunction>(cfg, argTypes, retType, CompilerOptions.HighCq);
+            return Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq).Map<DispatcherFunction>();
         }
     }
 }