diff --git a/.jules/bolt.md b/.jules/bolt.md index e86e382be..6720a80d1 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -30,3 +30,12 @@ **Context:** `src/SharpEmu.Core/Memory/VirtualMemory.cs` (`FindInsertionIndex`) **Learning:** Standard C# `List` accesses inside high-frequency binary searches introduce unnecessary overhead via indexer property access and bounds checking. The same optimization pattern recently used in `PhysicalVirtualMemory.cs` (commit 980b47b) applies directly to `VirtualMemory.cs`. Bypassing this via `CollectionsMarshal.AsSpan(list)` completely elides these checks, turning the operation into direct O(1) span memory access. **Action:** When optimizing binary search loops or hot paths over `List`, immediately refactor to use `CollectionsMarshal.AsSpan()` to access elements and `span.Length` for bounds, alongside the `>>> 1` operator for division. +## 2026-09-13 - Bounds Check Elision in Guest MMU Read/Write Loops +**Context:** Guest MMU Address Translation / `VirtualMemory.cs` +**Learning:** The C# JIT compiler cannot elide array bounds checks for `Span` or arrays when using a `while` loop with a manually managed index. This introduces silent branching overhead in memory-intensive hot paths (like `TryValidateRange`, `CopyFromRegions`). +**Action:** Always replace `while` loops iterating over sequential buffers with standard `for (var i = start; i < span.Length; i++)` loops to guarantee RyuJIT bounds check elimination in high-frequency emulation paths. + +## 2026-09-13 - SysAbiExportGenerator.ExportModel Constructor Signature Updates +**Context:** Unit Tests / `SysAbiExportGeneratorTests.cs` +**Learning:** When internal models like `ExportModel` are modified with new parameters (e.g., adding `bool preferLle`), reflection-based unit tests creating instances via `Activator.CreateInstance` will fail with `MissingMethodException` if the test arguments are not updated to match the new constructor signature. +**Action:** Always verify and update reflection-based test factories whenever internal constructor signatures change to maintain test suite stability. diff --git a/src/SharpEmu.Core/Memory/VirtualMemory.cs b/src/SharpEmu.Core/Memory/VirtualMemory.cs index 7496dc42f..72e574a4b 100644 --- a/src/SharpEmu.Core/Memory/VirtualMemory.cs +++ b/src/SharpEmu.Core/Memory/VirtualMemory.cs @@ -127,6 +127,7 @@ public bool TryWrite(ulong virtualAddress, ReadOnlySpan source) return true; } + /// Performance optimization: Uses a standard for-loop to allow the JIT compiler to elide array bounds checks during hot-path sequential span access. private bool TryValidateRange( ulong virtualAddress, int length, @@ -142,15 +143,9 @@ private bool TryValidateRange( var span = System.Runtime.InteropServices.CollectionsMarshal.AsSpan(_regions); var currentAddress = virtualAddress; var remaining = length; - var currentIndex = regionIndex; - while (true) + for (var i = regionIndex; i < span.Length; i++) { - if (currentIndex >= span.Length) - { - return false; - } - - ref var region = ref span[currentIndex]; + ref var region = ref span[i]; if (currentAddress < region.Region.VirtualAddress || currentAddress >= region.EndAddress || (region.Region.Protection & requiredProtection) == 0) @@ -172,8 +167,9 @@ private bool TryValidateRange( } currentAddress += (ulong)chunkLength; - currentIndex++; } + + return false; } private int FindContainingRegionIndex(ulong virtualAddress) @@ -192,14 +188,15 @@ private int FindContainingRegionIndex(ulong virtualAddress) : -1; } + /// Performance optimization: Uses a standard for-loop to allow the JIT compiler to elide array bounds checks during hot-path sequential span access. private void CopyFromRegions(ulong virtualAddress, Span destination, int regionIndex) { var span = System.Runtime.InteropServices.CollectionsMarshal.AsSpan(_regions); var copied = 0; var currentAddress = virtualAddress; - while (copied < destination.Length) + for (var i = regionIndex; i < span.Length && copied < destination.Length; i++) { - ref var region = ref span[regionIndex++]; + ref var region = ref span[i]; var regionOffset = checked((int)(currentAddress - region.Region.VirtualAddress)); var chunkLength = Math.Min(destination.Length - copied, region.BackingMemory.Length - regionOffset); region.BackingMemory.AsSpan(regionOffset, chunkLength).CopyTo(destination[copied..]); @@ -208,14 +205,15 @@ private void CopyFromRegions(ulong virtualAddress, Span destination, int r } } + /// Performance optimization: Uses a standard for-loop to allow the JIT compiler to elide array bounds checks during hot-path sequential span access. private void CopyToRegions(ulong virtualAddress, ReadOnlySpan source, int regionIndex) { var span = System.Runtime.InteropServices.CollectionsMarshal.AsSpan(_regions); var copied = 0; var currentAddress = virtualAddress; - while (copied < source.Length) + for (var i = regionIndex; i < span.Length && copied < source.Length; i++) { - ref var region = ref span[regionIndex++]; + ref var region = ref span[i]; var regionOffset = checked((int)(currentAddress - region.Region.VirtualAddress)); var chunkLength = Math.Min(source.Length - copied, region.BackingMemory.Length - regionOffset); source.Slice(copied, chunkLength).CopyTo(region.BackingMemory.AsSpan(regionOffset, chunkLength)); diff --git a/tests/SharpEmu.SourceGenerators.Tests/SysAbiExportGeneratorTests.cs b/tests/SharpEmu.SourceGenerators.Tests/SysAbiExportGeneratorTests.cs index 93a2658f7..fd448c44f 100644 --- a/tests/SharpEmu.SourceGenerators.Tests/SysAbiExportGeneratorTests.cs +++ b/tests/SharpEmu.SourceGenerators.Tests/SysAbiExportGeneratorTests.cs @@ -191,15 +191,15 @@ public void ExportModelEqualityTests() var modelType = generatorType.GetNestedType("ExportModel", System.Reflection.BindingFlags.NonPublic); Assert.NotNull(modelType); - object Create(string containingType, string methodName, SysAbiExportShape.HandlerShape shape, string typedParameterKinds, string libraryName, string nid, string exportName, int target) + object Create(string containingType, string methodName, SysAbiExportShape.HandlerShape shape, string typedParameterKinds, string libraryName, string nid, string exportName, int target, bool preferLle) { - return Activator.CreateInstance(modelType, containingType, methodName, shape, typedParameterKinds, libraryName, nid, exportName, target)!; + return Activator.CreateInstance(modelType, containingType, methodName, shape, typedParameterKinds, libraryName, nid, exportName, target, preferLle)!; } - var baseModel = Create("TypeA", "MethodA", SysAbiExportShape.HandlerShape.ContextOnly, "uint", "libA", "nidA", "expA", 1); + var baseModel = Create("TypeA", "MethodA", SysAbiExportShape.HandlerShape.ContextOnly, "uint", "libA", "nidA", "expA", 1, false); // Equals(object? obj) and Equals(ExportModel? other) identical - var identical = Create("TypeA", "MethodA", SysAbiExportShape.HandlerShape.ContextOnly, "uint", "libA", "nidA", "expA", 1); + var identical = Create("TypeA", "MethodA", SysAbiExportShape.HandlerShape.ContextOnly, "uint", "libA", "nidA", "expA", 1, false); Assert.True(baseModel.Equals(identical)); Assert.True(baseModel.Equals((object)identical)); @@ -214,28 +214,28 @@ object Create(string containingType, string methodName, SysAbiExportShape.Handle Assert.False(baseModel.Equals(new object())); // Different fields - var diffContainingType = Create("TypeB", "MethodA", SysAbiExportShape.HandlerShape.ContextOnly, "uint", "libA", "nidA", "expA", 1); + var diffContainingType = Create("TypeB", "MethodA", SysAbiExportShape.HandlerShape.ContextOnly, "uint", "libA", "nidA", "expA", 1, false); Assert.False(baseModel.Equals(diffContainingType)); - var diffMethodName = Create("TypeA", "MethodB", SysAbiExportShape.HandlerShape.ContextOnly, "uint", "libA", "nidA", "expA", 1); + var diffMethodName = Create("TypeA", "MethodB", SysAbiExportShape.HandlerShape.ContextOnly, "uint", "libA", "nidA", "expA", 1, false); Assert.False(baseModel.Equals(diffMethodName)); - var diffShape = Create("TypeA", "MethodA", SysAbiExportShape.HandlerShape.Parameterless, "uint", "libA", "nidA", "expA", 1); + var diffShape = Create("TypeA", "MethodA", SysAbiExportShape.HandlerShape.Parameterless, "uint", "libA", "nidA", "expA", 1, false); Assert.False(baseModel.Equals(diffShape)); - var diffTypedKinds = Create("TypeA", "MethodA", SysAbiExportShape.HandlerShape.ContextOnly, "int", "libA", "nidA", "expA", 1); + var diffTypedKinds = Create("TypeA", "MethodA", SysAbiExportShape.HandlerShape.ContextOnly, "int", "libA", "nidA", "expA", 1, false); Assert.False(baseModel.Equals(diffTypedKinds)); - var diffLibrary = Create("TypeA", "MethodA", SysAbiExportShape.HandlerShape.ContextOnly, "uint", "libB", "nidA", "expA", 1); + var diffLibrary = Create("TypeA", "MethodA", SysAbiExportShape.HandlerShape.ContextOnly, "uint", "libB", "nidA", "expA", 1, false); Assert.False(baseModel.Equals(diffLibrary)); - var diffNid = Create("TypeA", "MethodA", SysAbiExportShape.HandlerShape.ContextOnly, "uint", "libA", "nidB", "expA", 1); + var diffNid = Create("TypeA", "MethodA", SysAbiExportShape.HandlerShape.ContextOnly, "uint", "libA", "nidB", "expA", 1, false); Assert.False(baseModel.Equals(diffNid)); - var diffExportName = Create("TypeA", "MethodA", SysAbiExportShape.HandlerShape.ContextOnly, "uint", "libA", "nidA", "expB", 1); + var diffExportName = Create("TypeA", "MethodA", SysAbiExportShape.HandlerShape.ContextOnly, "uint", "libA", "nidA", "expB", 1, false); Assert.False(baseModel.Equals(diffExportName)); - var diffTarget = Create("TypeA", "MethodA", SysAbiExportShape.HandlerShape.ContextOnly, "uint", "libA", "nidA", "expA", 2); + var diffTarget = Create("TypeA", "MethodA", SysAbiExportShape.HandlerShape.ContextOnly, "uint", "libA", "nidA", "expA", 2, false); Assert.False(baseModel.Equals(diffTarget)); } }