Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,12 @@
**Context:** `src/SharpEmu.Core/Memory/VirtualMemory.cs` (`FindInsertionIndex`)
**Learning:** Standard C# `List<T>` 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<T>`, 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<T>` 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.
24 changes: 11 additions & 13 deletions src/SharpEmu.Core/Memory/VirtualMemory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ public bool TryWrite(ulong virtualAddress, ReadOnlySpan<byte> source)
return true;
}

/// <remarks>Performance optimization: Uses a standard for-loop to allow the JIT compiler to elide array bounds checks during hot-path sequential span access.</remarks>
private bool TryValidateRange(
ulong virtualAddress,
int length,
Expand All @@ -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)
Expand All @@ -172,8 +167,9 @@ private bool TryValidateRange(
}

currentAddress += (ulong)chunkLength;
currentIndex++;
}

return false;
}

private int FindContainingRegionIndex(ulong virtualAddress)
Expand All @@ -192,14 +188,15 @@ private int FindContainingRegionIndex(ulong virtualAddress)
: -1;
}

/// <remarks>Performance optimization: Uses a standard for-loop to allow the JIT compiler to elide array bounds checks during hot-path sequential span access.</remarks>
private void CopyFromRegions(ulong virtualAddress, Span<byte> 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..]);
Expand All @@ -208,14 +205,15 @@ private void CopyFromRegions(ulong virtualAddress, Span<byte> destination, int r
}
}

/// <remarks>Performance optimization: Uses a standard for-loop to allow the JIT compiler to elide array bounds checks during hot-path sequential span access.</remarks>
private void CopyToRegions(ulong virtualAddress, ReadOnlySpan<byte> 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));
Expand Down
24 changes: 12 additions & 12 deletions tests/SharpEmu.SourceGenerators.Tests/SysAbiExportGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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));

Expand All @@ -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));
}
}
Loading