From dba1b202e3c0ac7d5395e092bafb1b6e162a6d7e Mon Sep 17 00:00:00 2001 From: kuba Date: Thu, 1 Oct 2026 17:43:12 +0200 Subject: [PATCH] [Fiber] Avoid formatting unused names and disabled traces --- src/SharpEmu.Libs/Fiber/FiberExports.cs | 42 ++++++++++-------- .../Fiber/FiberSwitchLoopTests.cs | 43 +++++++++++++++++++ 2 files changed, 66 insertions(+), 19 deletions(-) diff --git a/src/SharpEmu.Libs/Fiber/FiberExports.cs b/src/SharpEmu.Libs/Fiber/FiberExports.cs index 93e5c9c..1411464 100644 --- a/src/SharpEmu.Libs/Fiber/FiberExports.cs +++ b/src/SharpEmu.Libs/Fiber/FiberExports.cs @@ -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 _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 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( diff --git a/tests/SharpEmu.Libs.Tests/Fiber/FiberSwitchLoopTests.cs b/tests/SharpEmu.Libs.Tests/Fiber/FiberSwitchLoopTests.cs index 605357d..6816326 100644 --- a/tests/SharpEmu.Libs.Tests/Fiber/FiberSwitchLoopTests.cs +++ b/tests/SharpEmu.Libs.Tests/Fiber/FiberSwitchLoopTests.cs @@ -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() {