From 28485b60e2c459eea2ea063ec58542cb2a4d8866 Mon Sep 17 00:00:00 2001 From: kuba Date: Fri, 17 Jul 2026 03:17:58 +0200 Subject: [PATCH] CPU: avoid continuation emitter closure allocations (#306) --- .../Cpu/Native/DirectExecutionBackend.cs | 134 +++++++++++------- 1 file changed, 80 insertions(+), 54 deletions(-) diff --git a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs index 399c5fb..6ebbb9c 100644 --- a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs +++ b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs @@ -5127,64 +5127,51 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I BindTlsBase(context); byte* ptr2 = (byte*)ptr; ulong hostRspSlot = (ulong)hostRspStorage; - int offset = 0; + var emitter = new NativeCodeEmitter(ptr2); - void Emit(byte value) => ptr2[offset++] = value; - void EmitU64(ulong value) - { - *(ulong*)(ptr2 + offset) = value; - offset += sizeof(ulong); - } - void EmitMovR64Imm(byte rex, byte opcode, ulong value) - { - Emit(rex); - Emit(opcode); - EmitU64(value); - } - - Emit(0x53); // push rbx - Emit(0x55); // push rbp - Emit(0x57); // push rdi - Emit(0x56); // push rsi - Emit(0x41); Emit(0x54); // push r12 - Emit(0x41); Emit(0x55); // push r13 - Emit(0x41); Emit(0x56); // push r14 - Emit(0x41); Emit(0x57); // push r15 - EmitHostNonvolatileXmmSave(ptr2, ref offset); + emitter.Emit(0x53); // push rbx + emitter.Emit(0x55); // push rbp + emitter.Emit(0x57); // push rdi + emitter.Emit(0x56); // push rsi + emitter.Emit(0x41); emitter.Emit(0x54); // push r12 + emitter.Emit(0x41); emitter.Emit(0x55); // push r13 + emitter.Emit(0x41); emitter.Emit(0x56); // push r14 + emitter.Emit(0x41); emitter.Emit(0x57); // push r15 + EmitHostNonvolatileXmmSave(ptr2, ref emitter.Offset); // Restore the fiber's floating-point control environment before // abandoning the host stack. This path is used when a blocked guest // continuation migrates to another managed worker. - Emit(0x48); Emit(0x83); Emit(0xEC); Emit(0x08); // sub rsp,8 - Emit(0xC7); Emit(0x04); Emit(0x24); // mov dword [rsp],imm32 - *(uint*)(ptr2 + offset) = context.Mxcsr; offset += sizeof(uint); - Emit(0x0F); Emit(0xAE); Emit(0x14); Emit(0x24); // ldmxcsr [rsp] - Emit(0x66); Emit(0xC7); Emit(0x04); Emit(0x24); // mov word [rsp],imm16 - *(ushort*)(ptr2 + offset) = context.FpuControlWord; offset += sizeof(ushort); - Emit(0xD9); Emit(0x2C); Emit(0x24); // fldcw [rsp] - Emit(0x48); Emit(0x83); Emit(0xC4); Emit(0x08); // add rsp,8 - EmitMovR64Imm(0x49, 0xBA, hostRspSlot); // mov r10, hostRspSlot - Emit(0x49); Emit(0x89); Emit(0x22); // mov [r10], rsp - EmitMovR64Imm(0x48, 0xB8, context[CpuRegister.Rsp]); // mov rax, guest rsp - Emit(0x48); Emit(0x89); Emit(0xC4); // mov rsp, rax - Emit(0x48); Emit(0x83); Emit(0xEC); Emit(0x08); // reserve transfer slot - EmitMovR64Imm(0x48, 0xB8, entryPoint); // mov rax, entryPoint - Emit(0x48); Emit(0x89); Emit(0x04); Emit(0x24); // mov [rsp],rax - EmitMovR64Imm(0x48, 0xBB, context[CpuRegister.Rbx]); // mov rbx, imm64 - EmitMovR64Imm(0x48, 0xBD, context[CpuRegister.Rbp]); // mov rbp, imm64 - EmitMovR64Imm(0x48, 0xBF, context[CpuRegister.Rdi]); // mov rdi, imm64 - EmitMovR64Imm(0x48, 0xBE, context[CpuRegister.Rsi]); // mov rsi, imm64 - EmitMovR64Imm(0x48, 0xBA, context[CpuRegister.Rdx]); // mov rdx, imm64 - EmitMovR64Imm(0x48, 0xB9, context[CpuRegister.Rcx]); // mov rcx, imm64 - EmitMovR64Imm(0x49, 0xB8, context[CpuRegister.R8]); // mov r8, imm64 - EmitMovR64Imm(0x49, 0xB9, context[CpuRegister.R9]); // mov r9, imm64 - EmitMovR64Imm(0x49, 0xBA, context[CpuRegister.R10]); // mov r10, imm64 - EmitMovR64Imm(0x49, 0xBC, context[CpuRegister.R12]); // mov r12, imm64 - EmitMovR64Imm(0x49, 0xBD, context[CpuRegister.R13]); // mov r13, imm64 - EmitMovR64Imm(0x49, 0xBE, context[CpuRegister.R14]); // mov r14, imm64 - EmitMovR64Imm(0x49, 0xBF, context[CpuRegister.R15]); // mov r15, imm64 - EmitMovR64Imm(0x49, 0xBB, context[CpuRegister.R11]); // mov r11, imm64 - EmitMovR64Imm(0x48, 0xB8, context[CpuRegister.Rax]); // mov rax, imm64 - Emit(0xC3); // ret through the synthetic transfer slot + emitter.Emit(0x48); emitter.Emit(0x83); emitter.Emit(0xEC); emitter.Emit(0x08); // sub rsp,8 + emitter.Emit(0xC7); emitter.Emit(0x04); emitter.Emit(0x24); // mov dword [rsp],imm32 + emitter.Emit(context.Mxcsr); + emitter.Emit(0x0F); emitter.Emit(0xAE); emitter.Emit(0x14); emitter.Emit(0x24); // ldmxcsr [rsp] + emitter.Emit(0x66); emitter.Emit(0xC7); emitter.Emit(0x04); emitter.Emit(0x24); // mov word [rsp],imm16 + emitter.Emit(context.FpuControlWord); + emitter.Emit(0xD9); emitter.Emit(0x2C); emitter.Emit(0x24); // fldcw [rsp] + emitter.Emit(0x48); emitter.Emit(0x83); emitter.Emit(0xC4); emitter.Emit(0x08); // add rsp,8 + emitter.EmitMovR64Immediate(0x49, 0xBA, hostRspSlot); // mov r10, hostRspSlot + emitter.Emit(0x49); emitter.Emit(0x89); emitter.Emit(0x22); // mov [r10], rsp + emitter.EmitMovR64Immediate(0x48, 0xB8, context[CpuRegister.Rsp]); // mov rax, guest rsp + emitter.Emit(0x48); emitter.Emit(0x89); emitter.Emit(0xC4); // mov rsp, rax + emitter.Emit(0x48); emitter.Emit(0x83); emitter.Emit(0xEC); emitter.Emit(0x08); // reserve transfer slot + emitter.EmitMovR64Immediate(0x48, 0xB8, entryPoint); // mov rax, entryPoint + emitter.Emit(0x48); emitter.Emit(0x89); emitter.Emit(0x04); emitter.Emit(0x24); // mov [rsp],rax + emitter.EmitMovR64Immediate(0x48, 0xBB, context[CpuRegister.Rbx]); // mov rbx, imm64 + emitter.EmitMovR64Immediate(0x48, 0xBD, context[CpuRegister.Rbp]); // mov rbp, imm64 + emitter.EmitMovR64Immediate(0x48, 0xBF, context[CpuRegister.Rdi]); // mov rdi, imm64 + emitter.EmitMovR64Immediate(0x48, 0xBE, context[CpuRegister.Rsi]); // mov rsi, imm64 + emitter.EmitMovR64Immediate(0x48, 0xBA, context[CpuRegister.Rdx]); // mov rdx, imm64 + emitter.EmitMovR64Immediate(0x48, 0xB9, context[CpuRegister.Rcx]); // mov rcx, imm64 + emitter.EmitMovR64Immediate(0x49, 0xB8, context[CpuRegister.R8]); // mov r8, imm64 + emitter.EmitMovR64Immediate(0x49, 0xB9, context[CpuRegister.R9]); // mov r9, imm64 + emitter.EmitMovR64Immediate(0x49, 0xBA, context[CpuRegister.R10]); // mov r10, imm64 + emitter.EmitMovR64Immediate(0x49, 0xBC, context[CpuRegister.R12]); // mov r12, imm64 + emitter.EmitMovR64Immediate(0x49, 0xBD, context[CpuRegister.R13]); // mov r13, imm64 + emitter.EmitMovR64Immediate(0x49, 0xBE, context[CpuRegister.R14]); // mov r14, imm64 + emitter.EmitMovR64Immediate(0x49, 0xBF, context[CpuRegister.R15]); // mov r15, imm64 + emitter.EmitMovR64Immediate(0x49, 0xBB, context[CpuRegister.R11]); // mov r11, imm64 + emitter.EmitMovR64Immediate(0x48, 0xB8, context[CpuRegister.Rax]); // mov rax, imm64 + emitter.Emit(0xC3); // ret through the synthetic transfer slot ActiveEntryReturnSentinelRip = (ulong)_guestReturnStub; if (returnSlotAddress == 0 || !context.TryWriteUInt64(returnSlotAddress, (ulong)_guestReturnStub)) { @@ -5248,6 +5235,45 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I } } + // The continuation trampoline is rebuilt on every blocked-thread resume. + // Keep its tiny writer on the stack: capturing local emit functions create a + // managed display-class allocation on this extremely hot path. + private unsafe ref struct NativeCodeEmitter(byte* code) + { + private readonly byte* _code = code; + public int Offset; + + public void Emit(byte value) + { + _code[Offset++] = value; + } + + public void Emit(ushort value) + { + *(ushort*)(_code + Offset) = value; + Offset += sizeof(ushort); + } + + public void Emit(uint value) + { + *(uint*)(_code + Offset) = value; + Offset += sizeof(uint); + } + + private void Emit(ulong value) + { + *(ulong*)(_code + Offset) = value; + Offset += sizeof(ulong); + } + + public void EmitMovR64Immediate(byte rex, byte opcode, ulong value) + { + Emit(rex); + Emit(opcode); + Emit(value); + } + } + private static ulong AlignDown(ulong value, ulong alignment) { if (alignment == 0)