Skip to content
Closed
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
38 changes: 6 additions & 32 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -1,32 +1,6 @@
<!--
Copyright (C) 2026 SharpEmu Emulator Project
SPDX-License-Identifier: GPL-2.0-or-later
-->

## 2026-07-25 - Replace reference records with readonly structs for Dictionary keys
**Context:** Metal Video Presenter / PSO Cache (`PipelineKey`)
**Learning:** Using `sealed record` for Dictionary keys causes implicit heap allocations on every lookup (due to it being a class reference type). Since PSO caching lookups happen per-frame or per-draw, this creates significant GC pressure.
**Action:** Replace `sealed record` with `readonly struct` implementing `IEquatable<T>` and explicitly overriding `GetHashCode()` with `HashCode.Combine()` to eliminate allocations in hot dictionary lookups.

## 2024-05-30 - [Kernel String Reading Optimization]
**Context:** src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs
**Learning:** Guest string reading operations (TryCompareStrings, Strchr, Strrchr, Memchr, TryCompareStringsCaseInsensitive) read character by character in loops, making a new TryReadCompat call (which grabs locks and searches trees in VirtualMemory) for every single byte. This creates huge overhead for long strings, which could be reduced with chunked reads or reading the whole thing using the existing CString functions.
**Action:** Use TryReadCString or chunked reads instead of 1-byte read loops.

## 2026-07-26 - Testing Roslyn Incremental Generator Equality Models

**Context:** When working with C# Source Generators, `IIncrementalGenerator` caching relies heavily on the `Equals` and `GetHashCode` implementations of intermediate data models. If these methods are incomplete or flawed, Roslyn will incorrectly cache or regenerate output, leading to performance issues or stale state. In the context of `SysAbiExportGenerator`, the `ExportModel` is a private, nested type that was largely untested for equality logic.

**Learning:** To guarantee 100% line and branch coverage on these critical internal data models, the most direct and reliable approach is to use reflection within standard xUnit tests. This circumvents the need to construct complex `CSharpCompilation` trees and invoke `CSharpGeneratorDriver` incrementally just to trigger `IEquatable` calls, reducing test brittleness and focusing on structural equality.

**Action:** Created `ExportModelEqualityTests` in `SysAbiExportGeneratorTests.cs` using `GetNestedType` and `Activator.CreateInstance` to thoroughly test all branches of `ExportModel` equality logic, improving code reliability for the generator pipeline.

## 2024-05-18 - Optimize PhysicalVirtualMemory FindRegion Bottleneck
**Context:** `src/SharpEmu.Core/Memory/PhysicalVirtualMemory.cs`
**Learning:** The `List<T>` indexer inside the `FindRegion` binary search hot path incurred bounds-checking and method-call overhead. Accessing it via `CollectionsMarshal.AsSpan` yielded a measurable decrease in execution time on millions of iterations.
**Action:** Use `CollectionsMarshal.AsSpan` for hot path reads of `List<T>` instead of indexers when zero-allocation and bounds-checking elision is needed.

## 2026-08-26 - Direct Span Access in Virtual Memory Search
**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.
<!-- SPDX-License-Identifier: GPL-2.0-or-later -->
<!-- Copyright (C) 2026 SharpEmu Emulator Project -->
## 2026-09-14 - Scoped Dotnet Format Execution
**Context:** src/SharpEmu.Core/Memory/VirtualMemory.cs
**Learning:** Running `dotnet format` globally modifies dozens of unrelated files, breaking the strict `< 80 lines` boundary rule and causing the PR review to fail for excessive scope.
**Action:** Always scope code formatting commands specifically to the modified project and file, e.g., `dotnet format src/SharpEmu.Core/SharpEmu.Core.csproj --include src/SharpEmu.Core/Memory/VirtualMemory.cs --verify-no-changes`.
18 changes: 9 additions & 9 deletions src/SharpEmu.Core/Memory/VirtualMemory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,10 @@ public bool TryWrite(ulong virtualAddress, ReadOnlySpan<byte> source)
return true;
}

/// <summary>
/// Validates a virtual memory range for specified protection requirements.
/// </summary>
/// <remarks>Performance optimization: Replaced while loop with a standard for loop over Span to enable JIT compiler array bounds check elision.</remarks>
private bool TryValidateRange(
ulong virtualAddress,
int length,
Expand All @@ -142,15 +146,10 @@ private bool TryValidateRange(
var span = System.Runtime.InteropServices.CollectionsMarshal.AsSpan(_regions);
var currentAddress = virtualAddress;
var remaining = length;
var currentIndex = regionIndex;
while (true)
{
if (currentIndex >= span.Length)
{
return false;
}

ref var region = ref span[currentIndex];
for (var i = regionIndex; i < span.Length; i++)
{
ref var region = ref span[i];
if (currentAddress < region.Region.VirtualAddress ||
currentAddress >= region.EndAddress ||
(region.Region.Protection & requiredProtection) == 0)
Expand All @@ -172,8 +171,9 @@ private bool TryValidateRange(
}

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

return false;
}

private int FindContainingRegionIndex(ulong virtualAddress)
Expand Down
27 changes: 15 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,31 @@ 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));

var diffPreferLle = Create("TypeA", "MethodA", SysAbiExportShape.HandlerShape.ContextOnly, "uint", "libA", "nidA", "expA", 1, true);
Assert.False(baseModel.Equals(diffPreferLle));
}
}
Loading