Merge safe fiber allocation reduction

This commit is contained in:
kuba 2026-10-01 18:31:18 +02:00
commit a1f5e6ebef
2 changed files with 66 additions and 19 deletions

View File

@ -49,6 +49,7 @@ public static class FiberExports
private const int FiberMagicEndOffset = 104;
private static int _contextSizeCheck;
private static readonly bool TraceEnabled = Environment.GetEnvironmentVariable("SHARPEMU_LOG_FIBER") == "1";
private static readonly object _fiberGate = new();
private static readonly ConcurrentDictionary<ulong, FiberContinuation> _continuations = new();
@ -287,9 +288,10 @@ public static class FiberExports
_ = GuestThreadExecution.EnterFiber(0);
GuestThreadExecution.RequestCurrentContextTransfer(transferTarget);
TraceFiber(
$"return-to-thread fiber=0x{fiberAddress:X16} " +
$"resume=0x{transferTarget.Rip:X16} rsp=0x{transferTarget.Rsp:X16} arg=0x{returnArgument:X16}");
if (TraceEnabled)
TraceFiber(
$"return-to-thread fiber=0x{fiberAddress:X16} " +
$"resume=0x{transferTarget.Rip:X16} rsp=0x{transferTarget.Rsp:X16} arg=0x{returnArgument:X16}");
return SetReturn(ctx, 0);
}
@ -335,7 +337,7 @@ public static class FiberExports
return SetReturn(ctx, FiberErrorInvalid);
}
if (!TryReadFiberFields(ctx, fiber, out var fields))
if (!TryReadFiberFields(ctx, fiber, out var fields, readName: true))
{
return SetReturn(ctx, FiberErrorInvalid);
}
@ -521,9 +523,10 @@ public static class FiberExports
_stackRanges[fiber] = new FiberStackRange(contextAddress, contextSize);
}
TraceFiber(
$"init fiber=0x{fiber:X16} entry=0x{entry:X16} ctx=0x{contextAddress:X16} " +
$"size=0x{contextSize:X} flags=0x{flags:X} build=0x{buildVersion:X8} name='{name}'");
if (TraceEnabled)
TraceFiber(
$"init fiber=0x{fiber:X16} entry=0x{entry:X16} ctx=0x{contextAddress:X16} " +
$"size=0x{contextSize:X} flags=0x{flags:X} build=0x{buildVersion:X8} name='{name}'");
return SetReturn(ctx, 0);
}
@ -583,7 +586,8 @@ public static class FiberExports
}
if (fields.State != FiberStateIdle)
{
TraceFiber($"run-state-error reason={reason} fiber=0x{fiber:X16} state=0x{fields.State:X8}");
if (TraceEnabled)
TraceFiber($"run-state-error reason={reason} fiber=0x{fiber:X16} state=0x{fields.State:X8}");
return SetReturn(ctx, FiberErrorState);
}
@ -670,9 +674,10 @@ public static class FiberExports
_ = GuestThreadExecution.EnterFiber(fiber);
GuestThreadExecution.RequestCurrentContextTransfer(transferTarget);
TraceFiber(
$"transfer reason={reason} from=0x{previousFiber:X16} to=0x{fiber:X16} resume={resumed} " +
$"rip=0x{transferTarget.Rip:X16} rsp=0x{transferTarget.Rsp:X16} arg=0x{argOnRun:X16}");
if (TraceEnabled)
TraceFiber(
$"transfer reason={reason} from=0x{previousFiber:X16} to=0x{fiber:X16} resume={resumed} " +
$"rip=0x{transferTarget.Rip:X16} rsp=0x{transferTarget.Rsp:X16} arg=0x{argOnRun:X16}");
return SetReturn(ctx, 0);
}
@ -971,7 +976,7 @@ public static class FiberExports
return true;
}
private static bool TryReadFiberFields(CpuContext ctx, ulong fiber, out FiberFields fields)
private static bool TryReadFiberFields(CpuContext ctx, ulong fiber, out FiberFields fields, bool readName = false)
{
fields = default;
if (!TryReadUInt32(ctx, fiber + FiberStateOffset, out var state) ||
@ -980,7 +985,7 @@ public static class FiberExports
!TryReadUInt64(ctx, fiber + FiberContextAddressOffset, out var contextAddress) ||
!TryReadUInt64(ctx, fiber + FiberContextSizeOffset, out var contextSize) ||
!TryReadUInt32(ctx, fiber + FiberFlagsOffset, out var flags) ||
!TryReadInlineName(ctx, fiber + FiberNameOffset, out var name))
!TryReadInlineName(ctx, fiber + FiberNameOffset, out var name, decode: readName))
{
return false;
}
@ -1065,7 +1070,7 @@ public static class FiberExports
return ctx.Memory.TryWrite(address, buffer);
}
private static bool TryReadInlineName(CpuContext ctx, ulong address, out string value)
private static bool TryReadInlineName(CpuContext ctx, ulong address, out string value, bool decode)
{
Span<byte> buffer = stackalloc byte[MaxNameLength + 1];
if (!ctx.Memory.TryRead(address, buffer))
@ -1080,7 +1085,9 @@ public static class FiberExports
length = buffer.Length;
}
value = Encoding.UTF8.GetString(buffer[..length]);
// A switch validates the same bytes but does not need a managed name.
// Only sceFiberGetInfo exposes the decoded name to its caller.
value = decode ? Encoding.UTF8.GetString(buffer[..length]) : string.Empty;
return true;
}
@ -1117,10 +1124,7 @@ public static class FiberExports
private static void TraceFiber(string message)
{
if (string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_FIBER"), "1", StringComparison.Ordinal))
{
Console.Error.WriteLine($"[LOADER][TRACE] fiber.{message}");
}
Console.Error.WriteLine($"[LOADER][TRACE] fiber.{message}");
}
private readonly record struct FiberFields(

View File

@ -81,6 +81,49 @@ public sealed class FiberSwitchLoopTests : IDisposable
FiberExports.ResetRuntimeState();
}
[Fact]
public void WarmSwitchLoop_BoundsAllocationAndPreservesResumeArguments()
{
var memory = new FakeCpuMemory(Base, RegionSize);
var context = new CpuContext(memory, Generation.Gen5);
InitializeFiber(memory, context, FiberA, NameA, EntryA, ArgOnInitializeA, ContextA);
InitializeFiber(memory, context, FiberB, NameB, EntryB, ArgOnInitializeB, ContextB);
GuestThreadExecution.EnterImportCallFrame(RootReturnRip, RootResumeRsp, RootReturnSlot);
context[CpuRegister.Rdi] = FiberA;
context[CpuRegister.Rdx] = ArgSlotRoot;
Assert.Equal(0, FiberExports.FiberRun(context));
Assert.True(GuestThreadExecution.TryConsumeCurrentContextTransfer(out _));
void SwitchOnce(int index)
{
var toB = (index & 1) == 0;
GuestThreadExecution.EnterImportCallFrame(toB ? ResumeRipA : ResumeRipB,
toB ? ResumeRspA : ResumeRspB, toB ? ReturnSlotA : ReturnSlotB);
context[CpuRegister.Rdi] = toB ? FiberB : FiberA;
context[CpuRegister.Rsi] = (ulong)index;
context[CpuRegister.Rdx] = toB ? ArgSlotA : ArgSlotB;
if (FiberExports.FiberSwitch(context) != 0 ||
!GuestThreadExecution.TryConsumeCurrentContextTransfer(out _))
throw new InvalidOperationException("Fiber switch failed.");
}
for (var i = 0; i < 2000; i++) SwitchOnce(i);
const int count = 20000;
var allocated = GC.GetAllocatedBytesForCurrentThread();
var start = System.Diagnostics.Stopwatch.GetTimestamp();
for (var i = 0; i < count; i++) SwitchOnce(i);
var elapsed = System.Diagnostics.Stopwatch.GetElapsedTime(start);
allocated = GC.GetAllocatedBytesForCurrentThread() - allocated;
Console.WriteLine($"Fiber switches: bytes/call={allocated / (double)count:F1}, us/call={elapsed.TotalMicroseconds / count:F3}");
// Previously 912 bytes per switch with tracing disabled. Keep room for
// continuation storage, but do not format names or discarded traces.
if (Environment.GetEnvironmentVariable("SHARPEMU_LOG_FIBER") != "1")
Assert.True(allocated < count * 600L, $"Allocated {allocated} bytes for {count} switches.");
Assert.Equal((ulong)(count - 1), ReadUInt64(memory, ArgSlotA));
Assert.Equal((ulong)(count - 2), ReadUInt64(memory, ArgSlotB));
Assert.Equal(FiberA, GuestThreadExecution.CurrentFiberAddress);
}
[Fact]
public void RepeatedSwitch_RestoresEachFiberOwnResumePoint()
{