//
// Copyright (c) 2010-2024 Antmicro
// Copyright (c) 2011-2015 Realtime Embedded
//
// This file is licensed under the MIT License.
// Full license text is available in 'licenses/MIT.txt'.
//
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Antmicro.Renode.Core;
using Antmicro.Renode.Exceptions;
namespace Antmicro.Renode.Core
{
public struct Range
{
public Range(ulong startAddress, ulong size)
{
StartAddress = startAddress;
EndAddress = startAddress + size - 1;
// Check if overflow occurred.
CreationAssert(EndAddress >= StartAddress, startAddress, () => $"size is too large: 0x{size:X}");
}
///
/// Checks if this Range can be expanded by range so that
/// this.Contains(address) || range.Contains(address) equals
/// this.Expand(range).Contains(address) for any address.
///
public bool CanBeExpandedBy(Range range)
{
return Intersects(range) || IsAdjacentTo(range);
}
public bool Contains(ulong address)
{
return address >= StartAddress && address <= EndAddress;
}
public bool Contains(long address)
{
// Range operates on positive numbers
// so it cannot contain a negative number.
if(address < 0)
{
return false;
}
return Contains((ulong)address);
}
public bool Contains(Range range)
{
return range.StartAddress >= StartAddress && range.EndAddress <= EndAddress;
}
///
/// range has to overlap or be adjacent to this Range
/// which can be tested with CanBeExpandedBy(range).
/// An ArgumentException is thrown if the condition isn't satisfied.
///
public Range Expand(Range range)
{
if(!CanBeExpandedBy(range))
{
throw new ArgumentException($"{this} can't be expanded by {range}.");
}
var startAddress = Math.Min(StartAddress, range.StartAddress);
var endAddress = Math.Max(EndAddress, range.EndAddress);
return startAddress.To(endAddress);
}
/// Intersection if ranges overlap, null otherwise.
public Range? Intersect(Range range)
{
var startAddress = Math.Max(StartAddress, range.StartAddress);
var endAddress = Math.Min(EndAddress, range.EndAddress);
if(startAddress > endAddress)
{
return null;
}
return new Range(startAddress, endAddress - startAddress + 1);
}
public bool IsAdjacentTo(Range range)
{
return (StartAddress != 0x0 && StartAddress == range.EndAddress + 1)
|| (EndAddress != ulong.MaxValue && EndAddress == range.StartAddress - 1);
}
public List Subtract(Range sub)
{
// If the subtracted range does not intersect this range, return this range
if(!sub.Intersects(this))
{
return new List { this };
}
// If the subtracted range contains this range, return an empty list
if(sub.Contains(this))
{
return new List { };
}
// If the subtracted range contains the start of this range,
// return a range from the end of the subtracted range to the end of this range
if(sub.Contains(StartAddress))
{
return new List { new Range(sub.EndAddress + 1, EndAddress - sub.EndAddress) };
}
// If the subtracted range contains the end of this range,
// return a range from the start of this range to the start of the subtracted range
if(sub.Contains(EndAddress))
{
return new List { new Range(StartAddress, sub.StartAddress - StartAddress) };
}
// If the subtracted range is contained within this range, return two ranges:
// one from the start of this range to the start of the subtracted range, and
// one from the end of the subtracted range to the end of this range.
// We probably don't need to check this because it's the only possibility left
if(this.Contains(sub))
{
return new List
{
new Range(StartAddress, sub.StartAddress - StartAddress),
new Range(sub.EndAddress + 1, EndAddress - sub.EndAddress)
};
}
throw new Exception("Unreachable");
}
public bool Intersects(Range range)
{
return Intersect(range).HasValue;
}
public ulong StartAddress
{
get;
private set;
}
public ulong EndAddress
{
get;
private set;
}
public ulong Size
{
get
{
return EndAddress - StartAddress + 1;
}
}
public Range ShiftBy(long shiftValue)
{
return new Range(checked(shiftValue >= 0 ? StartAddress + (ulong)shiftValue : StartAddress - (ulong)(-shiftValue)), Size);
}
public Range MoveToZero()
{
return new Range(0, Size);
}
public override string ToString()
{
return string.Format("<0x{0:X8}, 0x{1:X8}>", StartAddress, EndAddress);
}
public override bool Equals(object obj)
{
if(obj == null)
{
return false;
}
if(obj.GetType() != typeof(Range))
{
return false;
}
var other = (Range)obj;
return this == other;
}
public override int GetHashCode()
{
unchecked
{
return 7 * StartAddress.GetHashCode() ^ 31 * EndAddress.GetHashCode();
}
}
public static bool operator==(Range range, Range other)
{
return range.StartAddress == other.StartAddress && range.EndAddress == other.EndAddress;
}
public static bool operator!=(Range range, Range other)
{
return !(range == other);
}
public static Range operator+(Range range, long addend)
{
return range.ShiftBy(addend);
}
public static Range operator-(Range range, long minuend)
{
return range.ShiftBy(-minuend);
}
internal static void CreationAssert(bool condition, ulong startAddress, Func reason)
{
if(!condition)
{
throw new RecoverableException($"Could not create range at 0x{startAddress:X}; {reason()}");
}
}
}
public interface IReadOnlyMinimalRangesCollection : IEnumerable
{
bool ContainsOverlappingRange(Range range);
bool ContainsWholeRange(Range range);
bool ContainsPoint(ulong point);
}
public class MinimalRangesCollection : IReadOnlyMinimalRangesCollection, ICoalescable
{
public MinimalRangesCollection(IEnumerable rangeEnumerable = null)
{
if(rangeEnumerable != null)
{
AddAll(rangeEnumerable);
}
}
///
/// Adds a range expanding any elements, if possible. Therefore ranges overlapping,
/// and adjacent to the range are removed from the collection and re-added as a
/// single expanded Range.
///
public void Add(Range range)
{
// ToArray is necessary because ranges gets modified in the code block.
foreach(var expandableRange in ranges.Where(collectionRange => collectionRange.CanBeExpandedBy(range)).ToArray())
{
ranges.Remove(expandableRange);
range = range.Expand(expandableRange);
}
ranges.Add(range);
}
public void AddAll(IEnumerable source)
{
foreach(var range in source)
{
Add(range);
}
}
public void Coalesce(MinimalRangesCollection source)
{
AddAll(source.AsEnumerable());
}
public void Clear()
{
ranges.Clear();
}
public bool ContainsOverlappingRange(Range range)
{
return ranges.Any(existingRange => existingRange.Intersects(range));
}
public bool ContainsWholeRange(Range range)
{
// Ranges are merged when added to the collection which means it's only possible if one of elements contains this whole range.
return ranges.Any(existingRange => existingRange.Contains(range));
}
public bool ContainsPoint(ulong point)
{
return ranges.Any(existingRange => existingRange.Contains(point));
}
public IEnumerator GetEnumerator()
{
return ranges.GetEnumerator();
}
public bool Remove(Range range)
{
return ranges.SubtractAll(range);
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)ranges).GetEnumerator();
}
public int Count => ranges.Count;
private readonly HashSet ranges = new HashSet();
}
public static class RangeExtensions
{
///
/// Subtracts the given range from each overlapping element in ranges.
///
/// True if range was subtracted from any element in ranges.
public static bool SubtractAll(this ICollection ranges, Range range)
{
// ToArray is necessary because ranges gets modified in the code block.
var overlappingRanges = ranges.Where(collectionRange => collectionRange.Intersects(range)).ToArray();
foreach(var overlappingRange in overlappingRanges)
{
ranges.Remove(overlappingRange);
foreach(var remainingRange in overlappingRange.Subtract(range))
{
ranges.Add(remainingRange);
}
}
return overlappingRanges.Any();
}
public static Range By(this ulong startAddress, ulong size)
{
return new Range(startAddress, size);
}
public static Range By(this long startAddress, ulong size)
{
return new Range(checked((ulong)startAddress), size);
}
public static Range By(this long startAddress, long size)
{
return new Range(checked((ulong)startAddress), checked((ulong)size));
}
public static Range By(this int startAddress, ulong size)
{
return new Range(checked((ulong)startAddress), size);
}
public static Range By(this int startAddress, long size)
{
return new Range(checked((ulong)startAddress), checked((ulong)size));
}
public static Range By(this uint startAddress, ulong size)
{
return new Range(startAddress, size);
}
public static Range By(this uint startAddress, long size)
{
return new Range(startAddress, checked((ulong)size));
}
/// The returned `Range` contains endAddress.
public static Range To(this int startAddress, ulong endAddress)
{
return checked((ulong)startAddress).To(endAddress);
}
/// The returned `Range` contains endAddress.
public static Range To(this long startAddress, ulong endAddress)
{
return checked((ulong)startAddress).To(endAddress);
}
/// The returned `Range` contains endAddress.
public static Range To(this ulong startAddress, ulong endAddress)
{
// Moving these to a `Range` constructor would be much better but we can't have
// a second constructor with two `ulong` arguments.
Range.CreationAssert(
startAddress != 0 || endAddress != ulong.MaxValue,
startAddress, () => "<0, 2^64-1> ranges aren't currently supported"
);
// The constructor would also throw but the message would be that size is too large
// if `startAddress` is higher than `endAddress` which can be misleading.
Range.CreationAssert(
startAddress <= endAddress,
startAddress, () => "the start address can't be higher than the end address"
);
return new Range(startAddress, checked(endAddress - startAddress + 1));
}
}
}