Skip to content
Merged
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
67 changes: 65 additions & 2 deletions Fiberish.Core/Memory/ProcessAddressSpaceSync.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,31 @@ internal static class ProcessAddressSpaceSync
private static readonly Dictionary<IntPtr, (VMAManager AddressSpace, Engine Engine)> AddressSpaceByEngineState = [];
[ThreadStatic] private static Stack<EngineSnapshotBuffer>? SnapshotBufferPool;

internal sealed class WindowsHostMappedResizeScope
{
public static readonly WindowsHostMappedResizeScope Empty = new([]);

private readonly AddressSpaceResizeScope[] _plans;

internal WindowsHostMappedResizeScope(AddressSpaceResizeScope[] plans)
{
_plans = plans;
}

internal bool HasMappings => _plans.Length > 0;

internal void Restore(long newSize)
{
foreach (var plan in _plans)
plan.AddressSpace.RestoreHostMappedFileViewsAfterResize(plan.Plan, newSize, plan.Engines);
}
}

internal readonly record struct AddressSpaceResizeScope(
VMAManager AddressSpace,
VMAManager.WindowsHostMappedResizePlan Plan,
Engine[] Engines);

private static Process? ResolveProcess(Engine engine, Process? process)
{
return process ?? (engine.Owner as FiberTask)?.Process;
Expand Down Expand Up @@ -380,7 +405,7 @@ internal static void SyncMappedFile(VMAManager vmaManager, Engine engine, LinuxF
FillAddressSpaceEngineSnapshot(target, snapshot.Engines, snapshot.SeenStates, fallback);
if (snapshot.Engines.Count == 0)
{
target.SyncMappedFile(file, Array.Empty<Engine>());
target.SyncMappedFile(file, [engine]);
continue;
}

Expand All @@ -403,6 +428,44 @@ internal static void NotifyFileContentChanged(VMAManager vmaManager, Engine engi
}
}

internal static WindowsHostMappedResizeScope SuspendHostMappedFileViewsForResize(Inode inode,
in FileMutationContext context)
{
if (!OperatingSystem.IsWindows())
return WindowsHostMappedResizeScope.Empty;

var targets = inode.SnapshotMappedAddressSpaces();
if (targets.Length == 0)
{
if (!context.HasLiveAddressSpace)
return WindowsHostMappedResizeScope.Empty;

using var snapshot = RentEngineSnapshot();
FillAddressSpaceEngineSnapshot(context.AddressSpace!, snapshot.Engines, snapshot.SeenStates, context.Engine);
Engine[] fallbackEngines = snapshot.Engines.Count == 0 ? [context.Engine!] : [.. snapshot.Engines];
var fallbackPlan = context.AddressSpace!.SuspendHostMappedFileViewsForResize(inode, fallbackEngines);
return fallbackPlan.GuestPages.Length == 0
? WindowsHostMappedResizeScope.Empty
: new WindowsHostMappedResizeScope([new AddressSpaceResizeScope(context.AddressSpace!, fallbackPlan, fallbackEngines)]);
}

List<AddressSpaceResizeScope>? plans = null;
foreach (var target in targets)
{
var fallback = ReferenceEquals(target, context.AddressSpace) ? context.Engine : null;
using var snapshot = RentEngineSnapshot();
FillAddressSpaceEngineSnapshot(target, snapshot.Engines, snapshot.SeenStates, fallback);
Engine[] engines = snapshot.Engines.Count == 0 ? [] : [.. snapshot.Engines];
var plan = target.SuspendHostMappedFileViewsForResize(inode, engines);
if (plan.GuestPages.Length == 0)
continue;

(plans ??= []).Add(new AddressSpaceResizeScope(target, plan, engines));
}

return plans == null ? WindowsHostMappedResizeScope.Empty : new WindowsHostMappedResizeScope([.. plans]);
}

internal static void NotifyInodeTruncated(Inode inode, long newSize)
{
NotifyInodeTruncated(inode, newSize, default);
Expand Down Expand Up @@ -472,7 +535,7 @@ internal static void SyncAllMappedSharedFiles(VMAManager vmaManager, Engine engi
using var scope = EnterAddressSpaceScope(engine, process);
using var snapshot = RentEngineSnapshot();
FillAddressSpaceEngineSnapshot(vmaManager, snapshot.Engines, snapshot.SeenStates, engine);
vmaManager.SyncAllMappedSharedFiles(snapshot.Engines);
vmaManager.SyncAllMappedSharedFiles(snapshot.Engines.Count == 0 ? [engine] : snapshot.Engines);
}

private static void PublishCodeCacheInvalidation(VMAManager vmaManager, Engine engine, uint addr, uint len)
Expand Down
151 changes: 151 additions & 0 deletions Fiberish.Core/Memory/VMAManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1072,6 +1072,153 @@ public void OnFileTruncate(Inode inode, long newSize, IReadOnlyList<Engine> engi
privateObject.TruncateFilePrivatePagesToSize(newSize);
}

internal WindowsHostMappedResizePlan SuspendHostMappedFileViewsForResize(Inode inode, IReadOnlyList<Engine> engines)
{
if (!OperatingSystem.IsWindows())
return WindowsHostMappedResizePlan.Empty;

var guestPages = new List<uint>();
var seenGuestPages = new HashSet<uint>();
var invalidateRanges = new List<NativeRange>();
Dictionary<MappingBackedInode, HashSet<uint>>? retireByInode = null;
if (inode is MappingBackedInode mappingBackedInode)
{
foreach (var record in mappingBackedInode.SnapshotMappingPageRecords())
{
if (record.BackingKind != FilePageBackingKind.HostMappedWindow)
continue;

retireByInode ??= [];
if (!retireByInode.TryGetValue(mappingBackedInode, out var retiredPages))
{
retiredPages = [];
retireByInode[mappingBackedInode] = retiredPages;
}

retiredPages.Add(record.PageIndex);
}
}

foreach (var pageAddr in PageMapping.SnapshotMappedPages())
{
if (!PageMapping.TryGetBinding(pageAddr, out var bindingNullable) || !bindingNullable.HasValue)
continue;

var binding = bindingNullable.Value;
if (binding.OwnerKind != MappedPageOwnerKind.AddressSpace)
continue;

var vma = FindVmArea(pageAddr);
if (vma == null || !VmaReferencesInode(vma, inode))
continue;

if (!ReferenceEquals(binding.Mapping, vma.VmMapping))
continue;

var mappingInode = ResolveCurrentFileMappingBacking(vma.FileMapping);
if (mappingInode == null)
continue;

var pageIndex = GetVmaPageIndex(vma, pageAddr);
if (!mappingInode.TryGetMappingPageRecord(pageIndex, out var record) ||
record.BackingKind != FilePageBackingKind.HostMappedWindow)
continue;

if (!seenGuestPages.Add(pageAddr))
continue;

guestPages.Add(pageAddr);
invalidateRanges.Add(new NativeRange(pageAddr, LinuxConstants.PageSize));
retireByInode ??= [];
if (!retireByInode.TryGetValue(mappingInode, out var retiredPages))
{
retiredPages = [];
retireByInode[mappingInode] = retiredPages;
}

retiredPages.Add(pageIndex);
}

if (guestPages.Count == 0 && (retireByInode == null || retireByInode.Count == 0))
return WindowsHostMappedResizePlan.Empty;

if (invalidateRanges.Count > 0)
{
guestPages.Sort();
MergeRangesInPlace(invalidateRanges);
var sequence = BumpCodeCacheSequence();
foreach (var range in invalidateRanges)
RecordCodeCacheResetRange(sequence, range.Start, range.Length);

if (engines.Count > 0)
{
var primary = GetPrimarySharedMmuEngine(engines, nameof(SuspendHostMappedFileViewsForResize))!;
foreach (var range in invalidateRanges)
TearDownNativeMappings(primary, range.Start, range.Length, false, true, true);
primary.AddressSpaceCodeCacheSequenceSeen = sequence;
}
}

if (retireByInode != null)
{
foreach (var entry in retireByInode)
{
var mapping = entry.Key.AcquireMappingRef();
try
{
foreach (var pageIndex in entry.Value)
{
if (!entry.Key.TryGetMappingPageRecord(pageIndex, out var record) ||
record.BackingKind != FilePageBackingKind.HostMappedWindow)
continue;

var removedResidentPages = mapping.RemovePagesInRange(pageIndex, pageIndex + 1);
if (removedResidentPages == 0)
{
if (entry.Key.TryGetMappingPageRecord(pageIndex, out record) &&
record.BackingKind == FilePageBackingKind.HostMappedWindow)
entry.Key.ReleaseInstalledMappingPage(record);
}
else if (entry.Key.TryGetMappingPageRecord(pageIndex, out record) &&
record.BackingKind == FilePageBackingKind.HostMappedWindow)
{
entry.Key.ReleaseInstalledMappingPage(record);
}
}
}
finally
{
mapping.Release();
}
}
}

return new WindowsHostMappedResizePlan([.. guestPages]);
}

internal void RestoreHostMappedFileViewsAfterResize(WindowsHostMappedResizePlan plan, long newSize,
IReadOnlyList<Engine> engines)
{
if (!OperatingSystem.IsWindows() || plan.GuestPages.Length == 0)
return;

if (engines.Count == 0)
return;

var primary = GetPrimarySharedMmuEngine(engines, nameof(RestoreHostMappedFileViewsAfterResize))!;
foreach (var guestPageStart in plan.GuestPages)
{
if (newSize <= 0 || (ulong)guestPageStart >= (ulong)newSize)
continue;

if (HandleFaultDetailed(guestPageStart, false, primary) != FaultResult.Handled)
{
throw new InvalidOperationException(
$"Failed to restore host-mapped file page after truncate at 0x{guestPageStart:X8}.");
}
}
}

public void UnmapMappingRange(Inode inode, long start, long len, bool evenCows, IReadOnlyList<Engine> engines)
{
if (len <= 0) return;
Expand Down Expand Up @@ -2849,4 +2996,8 @@ public void LogVmAreas()
internal readonly record struct NativeRange(uint Start, uint Length);

private readonly record struct CodeCacheResetEntry(long Sequence, NativeRange Range);
internal readonly record struct WindowsHostMappedResizePlan(uint[] GuestPages)
{
public static WindowsHostMappedResizePlan Empty => new([]);
}
}
73 changes: 63 additions & 10 deletions Fiberish.Core/Memory/WindowedMappedFilePageBackend.cs
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ public bool TryFlushPage(long filePageIndex)
out var flushLength))
return false;

return Flush(window.Mapping.RawPtr + (nint)flushOffset, flushLength);
return Flush(window.Mapping.Ptr + (nint)flushOffset, flushLength);
}
}

Expand All @@ -324,9 +324,9 @@ public bool TryFlushAllActiveWritableWindows()
continue;
if (window.AccessMode == WindowAccessMode.ReadOnly)
continue;
if (window.Mapping.RawPtr == 0 || window.Mapping.FlushLength <= 0)
if (window.Mapping.Ptr == 0 || window.Mapping.FlushLength <= 0)
continue;
if (!Flush(window.Mapping.RawPtr, window.Mapping.FlushLength))
if (!Flush(window.Mapping.Ptr, window.Mapping.FlushLength))
return false;
}
}
Expand Down Expand Up @@ -502,35 +502,50 @@ private bool TryAcquireFromActiveWindowLocked(long windowId, long filePageIndex,
if (mappedLength <= 0)
return null;

var handle = GetOrOpenCachedHandleLocked(writable);
if (handle == null)
return null;
var access = writable ? FileAccess.ReadWrite : FileAccess.Read;
using var transientHandle = File.OpenHandle(
_path,
FileMode.Open,
access,
FileShare.ReadWrite | FileShare.Delete);
return CreateWindowsWindowCore(transientHandle, windowStart, mappedLength, logicalLength, writable);
}

private static WindowMapping? CreateWindowsWindowCore(SafeFileHandle handle, long windowStart, long mappedLength,
long logicalLength, bool writable)
{
var protect = writable ? PageReadWrite : PageReadOnly;
var mappingHandle = CreateFileMapping(handle.DangerousGetHandle(), 0, protect, 0, 0, null);
if (mappingHandle == 0)
return null;

try
{
SplitUInt64(windowStart, out var offsetHigh, out var offsetLow);
var viewAlignment = GetWindowsViewAlignment();
var alignedWindowStart = AlignDown(windowStart, viewAlignment);
var viewOffset = windowStart - alignedWindowStart;
if (viewOffset < 0)
return null;

var viewLength = checked(mappedLength + viewOffset);
SplitUInt64(alignedWindowStart, out var offsetHigh, out var offsetLow);
var desiredAccess = writable ? FileMapWrite : FileMapRead;
var mapped = MapViewOfFile(
mappingHandle,
desiredAccess,
offsetHigh,
offsetLow,
checked((nuint)mappedLength));
checked((nuint)viewLength));
if (mapped == 0)
return null;

return new WindowMapping
{
RawPtr = mapped,
Ptr = mapped,
Ptr = mapped + (nint)viewOffset,
Length = logicalLength,
FlushLength = mappedLength,
MappedLength = mappedLength
MappedLength = viewLength
};
}
finally
Expand Down Expand Up @@ -861,6 +876,9 @@ private static partial nint MapViewOfFile(
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool CloseHandle(nint hObject);

[LibraryImport("kernel32.dll")]
private static partial void GetSystemInfo(out SYSTEM_INFO lpSystemInfo);

private static readonly nint MmapFailed = new(-1);
private const int ProtRead = 0x1;
private const int ProtWrite = 0x2;
Expand All @@ -869,6 +887,7 @@ private static partial nint MapViewOfFile(
private const uint PageReadWrite = 0x04;
private const uint FileMapWrite = 0x0002;
private const uint FileMapRead = 0x0004;
private static readonly int WindowsViewAlignment = GetWindowsAllocationGranularity();

private enum WindowAccessMode
{
Expand Down Expand Up @@ -901,4 +920,38 @@ private struct Window
}

private readonly record struct PageLease(long PageIndex, long WindowId);

private static int GetWindowsViewAlignment()
{
if (!OperatingSystem.IsWindows())
return LinuxConstants.PageSize;

return WindowsViewAlignment > 0 ? WindowsViewAlignment : LinuxConstants.PageSize;
}

private static int GetWindowsAllocationGranularity()
{
if (!OperatingSystem.IsWindows())
return LinuxConstants.PageSize;

GetSystemInfo(out var systemInfo);
var granularity = checked((int)systemInfo.dwAllocationGranularity);
return granularity > 0 ? granularity : LinuxConstants.PageSize;
}

[StructLayout(LayoutKind.Sequential)]
private struct SYSTEM_INFO
{
public ushort wProcessorArchitecture;
public ushort wReserved;
public uint dwPageSize;
public nint lpMinimumApplicationAddress;
public nint lpMaximumApplicationAddress;
public nint dwActiveProcessorMask;
public uint dwNumberOfProcessors;
public uint dwProcessorType;
public uint dwAllocationGranularity;
public ushort wProcessorLevel;
public ushort wProcessorRevision;
}
}
Loading
Loading