2021-01-17 13:44:34 -05:00
using System ;
2021-03-06 09:43:55 -05:00
using System.Collections.Generic ;
2021-01-17 13:44:34 -05:00
namespace Ryujinx.Memory.Range
{
/// <summary>
/// Sequence of physical memory regions that a single non-contiguous virtual memory region maps to.
/// </summary>
2022-12-05 08:47:39 -05:00
public readonly struct MultiRange : IEquatable < MultiRange >
2021-01-17 13:44:34 -05:00
{
2023-03-14 16:08:44 -04:00
private const ulong InvalidAddress = ulong . MaxValue ;
2021-01-17 13:44:34 -05:00
private readonly MemoryRange _singleRange ;
private readonly MemoryRange [ ] _ranges ;
private bool HasSingleRange = > _ranges = = null ;
/// <summary>
/// Total of physical sub-ranges on the virtual memory region.
/// </summary>
public int Count = > HasSingleRange ? 1 : _ranges . Length ;
/// <summary>
/// Creates a new multi-range with a single physical region.
/// </summary>
/// <param name="address">Start address of the region</param>
/// <param name="size">Size of the region in bytes</param>
public MultiRange ( ulong address , ulong size )
{
_singleRange = new MemoryRange ( address , size ) ;
_ranges = null ;
}
/// <summary>
/// Creates a new multi-range with multiple physical regions.
/// </summary>
/// <param name="ranges">Array of physical regions</param>
/// <exception cref="ArgumentNullException"><paramref name="ranges"/> is null</exception>
public MultiRange ( MemoryRange [ ] ranges )
{
_singleRange = MemoryRange . Empty ;
_ranges = ranges ? ? throw new ArgumentNullException ( nameof ( ranges ) ) ;
}
2021-03-06 09:43:55 -05:00
/// <summary>
/// Gets a slice of the multi-range.
/// </summary>
/// <param name="offset">Offset of the slice into the multi-range in bytes</param>
/// <param name="size">Size of the slice in bytes</param>
/// <returns>A new multi-range representing the given slice of this one</returns>
2023-03-19 12:31:35 -04:00
public MultiRange Slice ( ulong offset , ulong size )
2021-03-06 09:43:55 -05:00
{
if ( HasSingleRange )
{
if ( _singleRange . Size - offset < size )
{
throw new ArgumentOutOfRangeException ( nameof ( size ) ) ;
}
return new MultiRange ( _singleRange . Address + offset , size ) ;
}
else
{
var ranges = new List < MemoryRange > ( ) ;
foreach ( MemoryRange range in _ranges )
{
if ( ( long ) offset < = 0 )
{
ranges . Add ( new MemoryRange ( range . Address , Math . Min ( size , range . Size ) ) ) ;
size - = range . Size ;
}
else if ( offset < range . Size )
{
ulong sliceSize = Math . Min ( size , range . Size - offset ) ;
2023-03-14 16:08:44 -04:00
if ( range . Address = = InvalidAddress )
{
ranges . Add ( new MemoryRange ( range . Address , sliceSize ) ) ;
}
else
{
ranges . Add ( new MemoryRange ( range . Address + offset , sliceSize ) ) ;
}
2021-03-06 09:43:55 -05:00
size - = sliceSize ;
}
if ( ( long ) size < = 0 )
{
break ;
}
offset - = range . Size ;
}
return new MultiRange ( ranges . ToArray ( ) ) ;
}
}
2021-01-17 13:44:34 -05:00
/// <summary>
/// Gets the physical region at the specified index.
/// </summary>
/// <param name="index">Index of the physical region</param>
/// <returns>Region at the index specified</returns>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is invalid</exception>
public MemoryRange GetSubRange ( int index )
{
if ( HasSingleRange )
{
if ( index ! = 0 )
{
throw new ArgumentOutOfRangeException ( nameof ( index ) ) ;
}
return _singleRange ;
}
else
{
if ( ( uint ) index > = _ranges . Length )
{
throw new ArgumentOutOfRangeException ( nameof ( index ) ) ;
}
return _ranges [ index ] ;
}
}
/// <summary>
/// Gets the physical region at the specified index, without explicit bounds checking.
/// </summary>
/// <param name="index">Index of the physical region</param>
/// <returns>Region at the index specified</returns>
private MemoryRange GetSubRangeUnchecked ( int index )
{
return HasSingleRange ? _singleRange : _ranges [ index ] ;
}
/// <summary>
/// Check if two multi-ranges overlap with each other.
/// </summary>
/// <param name="other">Other multi-range to check for overlap</param>
/// <returns>True if any sub-range overlaps, false otherwise</returns>
public bool OverlapsWith ( MultiRange other )
{
if ( HasSingleRange & & other . HasSingleRange )
{
return _singleRange . OverlapsWith ( other . _singleRange ) ;
}
else
{
for ( int i = 0 ; i < Count ; i + + )
{
MemoryRange currentRange = GetSubRangeUnchecked ( i ) ;
for ( int j = 0 ; j < other . Count ; j + + )
{
if ( currentRange . OverlapsWith ( other . GetSubRangeUnchecked ( j ) ) )
{
return true ;
}
}
}
}
return false ;
}
/// <summary>
/// Checks if a given multi-range is fully contained inside another.
/// </summary>
/// <param name="other">Multi-range to be checked</param>
/// <returns>True if all the sub-ranges on <paramref name="other"/> are contained inside the multi-range, with the same order, false otherwise</returns>
public bool Contains ( MultiRange other )
{
return FindOffset ( other ) > = 0 ;
}
/// <summary>
/// Calculates the offset of a given multi-range inside another, when the multi-range is fully contained
/// inside the other multi-range, otherwise returns -1.
/// </summary>
/// <param name="other">Multi-range that should be fully contained inside this one</param>
/// <returns>Offset in bytes if fully contained, otherwise -1</returns>
public int FindOffset ( MultiRange other )
{
int thisCount = Count ;
int otherCount = other . Count ;
if ( thisCount = = 1 & & otherCount = = 1 )
{
MemoryRange otherFirstRange = other . GetSubRangeUnchecked ( 0 ) ;
MemoryRange currentFirstRange = GetSubRangeUnchecked ( 0 ) ;
if ( otherFirstRange . Address > = currentFirstRange . Address & &
otherFirstRange . EndAddress < = currentFirstRange . EndAddress )
{
return ( int ) ( otherFirstRange . Address - currentFirstRange . Address ) ;
}
}
else if ( thisCount > = otherCount )
{
ulong baseOffset = 0 ;
MemoryRange otherFirstRange = other . GetSubRangeUnchecked ( 0 ) ;
MemoryRange otherLastRange = other . GetSubRangeUnchecked ( otherCount - 1 ) ;
for ( int i = 0 ; i < ( thisCount - otherCount ) + 1 ; baseOffset + = GetSubRangeUnchecked ( i ) . Size , i + + )
{
MemoryRange currentFirstRange = GetSubRangeUnchecked ( i ) ;
MemoryRange currentLastRange = GetSubRangeUnchecked ( i + otherCount - 1 ) ;
if ( otherCount > 1 )
{
if ( otherFirstRange . Address < currentFirstRange . Address | |
otherFirstRange . EndAddress ! = currentFirstRange . EndAddress )
{
continue ;
}
if ( otherLastRange . Address ! = currentLastRange . Address | |
otherLastRange . EndAddress > currentLastRange . EndAddress )
{
continue ;
}
bool fullMatch = true ;
for ( int j = 1 ; j < otherCount - 1 ; j + + )
{
if ( ! GetSubRangeUnchecked ( i + j ) . Equals ( other . GetSubRangeUnchecked ( j ) ) )
{
fullMatch = false ;
break ;
}
}
if ( ! fullMatch )
{
continue ;
}
}
else if ( currentFirstRange . Address > otherFirstRange . Address | |
currentFirstRange . EndAddress < otherFirstRange . EndAddress )
{
continue ;
}
return ( int ) ( baseOffset + ( otherFirstRange . Address - currentFirstRange . Address ) ) ;
}
}
return - 1 ;
}
/// <summary>
/// Gets the total size of all sub-ranges in bytes.
/// </summary>
/// <returns>Total size in bytes</returns>
public ulong GetSize ( )
{
2022-01-09 11:28:48 -05:00
if ( HasSingleRange )
{
return _singleRange . Size ;
}
2021-01-17 13:44:34 -05:00
ulong sum = 0 ;
foreach ( MemoryRange range in _ranges )
{
sum + = range . Size ;
}
return sum ;
}
public override bool Equals ( object obj )
{
return obj is MultiRange other & & Equals ( other ) ;
}
public bool Equals ( MultiRange other )
{
if ( HasSingleRange & & other . HasSingleRange )
{
return _singleRange . Equals ( other . _singleRange ) ;
}
int thisCount = Count ;
if ( thisCount ! = other . Count )
{
return false ;
}
for ( int i = 0 ; i < thisCount ; i + + )
{
if ( ! GetSubRangeUnchecked ( i ) . Equals ( other . GetSubRangeUnchecked ( i ) ) )
{
return false ;
}
}
return true ;
}
public override int GetHashCode ( )
{
if ( HasSingleRange )
{
return _singleRange . GetHashCode ( ) ;
}
HashCode hash = new HashCode ( ) ;
foreach ( MemoryRange range in _ranges )
{
hash . Add ( range ) ;
}
return hash . ToHashCode ( ) ;
}
}
}