1 //
2 // Copyright (c) 2010-2024 Antmicro
3 // Copyright (c) 2011-2015 Realtime Embedded
4 //
5 // This file is licensed under the MIT License.
6 // Full license text is available in 'licenses/MIT.txt'.
7 //
8 using System;
9 using System.Collections;
10 using System.Collections.Generic;
11 using System.Linq;
12 using Antmicro.Renode.Core;
13 using Antmicro.Renode.Exceptions;
14 
15 namespace Antmicro.Renode.Core
16 {
17     public struct Range
18     {
RangeAntmicro.Renode.Core.Range19         public Range(ulong startAddress, ulong size)
20         {
21             StartAddress = startAddress;
22             EndAddress = startAddress + size - 1;
23 
24             // Check if overflow occurred.
25             CreationAssert(EndAddress >= StartAddress, startAddress, () => $"size is too large: 0x{size:X}");
26         }
27 
28         /// <summary>
29         /// Checks if this <c>Range</c> can be expanded by <c>range</c> so that
30         /// <c>this.Contains(address) || range.Contains(address)</c> equals
31         /// <c>this.Expand(range).Contains(address)</c> for any address.
32         /// </summary>
CanBeExpandedByAntmicro.Renode.Core.Range33         public bool CanBeExpandedBy(Range range)
34         {
35             return Intersects(range) || IsAdjacentTo(range);
36         }
37 
ContainsAntmicro.Renode.Core.Range38         public bool Contains(ulong address)
39         {
40             return address >= StartAddress && address <= EndAddress;
41         }
42 
ContainsAntmicro.Renode.Core.Range43         public bool Contains(long address)
44         {
45             // Range operates on positive numbers
46             // so it cannot contain a negative number.
47             if(address < 0)
48             {
49                 return false;
50             }
51 
52             return Contains((ulong)address);
53         }
54 
ContainsAntmicro.Renode.Core.Range55         public bool Contains(Range range)
56         {
57             return range.StartAddress >= StartAddress && range.EndAddress <= EndAddress;
58         }
59 
60         /// <param name="range">
61         /// <c>range</c> has to overlap or be adjacent to this <c>Range</c>
62         /// which can be tested with <c>CanBeExpandedBy(range)</c>.
63         /// An <c>ArgumentException</c> is thrown if the condition isn't satisfied.
64         /// </param>
ExpandAntmicro.Renode.Core.Range65         public Range Expand(Range range)
66         {
67             if(!CanBeExpandedBy(range))
68             {
69                 throw new ArgumentException($"{this} can't be expanded by {range}.");
70             }
71             var startAddress = Math.Min(StartAddress, range.StartAddress);
72             var endAddress = Math.Max(EndAddress, range.EndAddress);
73             return startAddress.To(endAddress);
74         }
75 
76         /// <returns>Intersection if ranges overlap, <c>null</c> otherwise.</returns>
IntersectAntmicro.Renode.Core.Range77         public Range? Intersect(Range range)
78         {
79             var startAddress = Math.Max(StartAddress, range.StartAddress);
80             var endAddress = Math.Min(EndAddress, range.EndAddress);
81             if(startAddress > endAddress)
82             {
83                 return null;
84             }
85             return new Range(startAddress, endAddress - startAddress + 1);
86         }
87 
IsAdjacentToAntmicro.Renode.Core.Range88         public bool IsAdjacentTo(Range range)
89         {
90             return (StartAddress != 0x0 && StartAddress == range.EndAddress + 1)
91                     || (EndAddress != ulong.MaxValue && EndAddress == range.StartAddress - 1);
92         }
93 
SubtractAntmicro.Renode.Core.Range94         public List<Range> Subtract(Range sub)
95         {
96             // If the subtracted range does not intersect this range, return this range
97             if(!sub.Intersects(this))
98             {
99                 return new List<Range> { this };
100             }
101 
102             // If the subtracted range contains this range, return an empty list
103             if(sub.Contains(this))
104             {
105                 return new List<Range> { };
106             }
107 
108             // If the subtracted range contains the start of this range,
109             // return a range from the end of the subtracted range to the end of this range
110             if(sub.Contains(StartAddress))
111             {
112                 return new List<Range> { new Range(sub.EndAddress + 1, EndAddress - sub.EndAddress) };
113             }
114 
115             // If the subtracted range contains the end of this range,
116             // return a range from the start of this range to the start of the subtracted range
117             if(sub.Contains(EndAddress))
118             {
119                 return new List<Range> { new Range(StartAddress, sub.StartAddress - StartAddress) };
120             }
121 
122             // If the subtracted range is contained within this range, return two ranges:
123             // one from the start of this range to the start of the subtracted range, and
124             // one from the end of the subtracted range to the end of this range.
125             // We probably don't need to check this because it's the only possibility left
126             if(this.Contains(sub))
127             {
128                 return new List<Range>
129                 {
130                     new Range(StartAddress, sub.StartAddress - StartAddress),
131                     new Range(sub.EndAddress + 1, EndAddress - sub.EndAddress)
132                 };
133             }
134 
135             throw new Exception("Unreachable");
136         }
137 
IntersectsAntmicro.Renode.Core.Range138         public bool Intersects(Range range)
139         {
140             return Intersect(range).HasValue;
141         }
142 
143         public ulong StartAddress
144         {
145             get;
146             private set;
147         }
148 
149         public ulong EndAddress
150         {
151             get;
152             private set;
153         }
154 
155         public ulong Size
156         {
157             get
158             {
159                 return EndAddress - StartAddress + 1;
160             }
161         }
162 
ShiftByAntmicro.Renode.Core.Range163         public Range ShiftBy(long shiftValue)
164         {
165             return new Range(checked(shiftValue >= 0 ? StartAddress + (ulong)shiftValue : StartAddress - (ulong)(-shiftValue)), Size);
166         }
167 
MoveToZeroAntmicro.Renode.Core.Range168         public Range MoveToZero()
169         {
170             return new Range(0, Size);
171         }
172 
ToStringAntmicro.Renode.Core.Range173         public override string ToString()
174         {
175             return string.Format("<0x{0:X8}, 0x{1:X8}>", StartAddress, EndAddress);
176         }
177 
EqualsAntmicro.Renode.Core.Range178         public override bool Equals(object obj)
179         {
180             if(obj == null)
181             {
182                 return false;
183             }
184             if(obj.GetType() != typeof(Range))
185             {
186                 return false;
187             }
188             var other = (Range)obj;
189             return this == other;
190         }
191 
GetHashCodeAntmicro.Renode.Core.Range192         public override int GetHashCode()
193         {
194             unchecked
195             {
196                 return 7 * StartAddress.GetHashCode() ^ 31 * EndAddress.GetHashCode();
197             }
198         }
199 
operator ==Antmicro.Renode.Core.Range200         public static bool operator==(Range range, Range other)
201         {
202             return range.StartAddress == other.StartAddress && range.EndAddress == other.EndAddress;
203         }
204 
operator !=Antmicro.Renode.Core.Range205         public static bool operator!=(Range range, Range other)
206         {
207             return !(range == other);
208         }
209 
operator +Antmicro.Renode.Core.Range210         public static Range operator+(Range range, long addend)
211         {
212             return range.ShiftBy(addend);
213         }
214 
operator -Antmicro.Renode.Core.Range215         public static Range operator-(Range range, long minuend)
216         {
217             return range.ShiftBy(-minuend);
218         }
219 
CreationAssertAntmicro.Renode.Core.Range220         internal static void CreationAssert(bool condition, ulong startAddress, Func<string> reason)
221         {
222             if(!condition)
223             {
224                 throw new RecoverableException($"Could not create range at 0x{startAddress:X}; {reason()}");
225             }
226         }
227     }
228 
229     public interface IReadOnlyMinimalRangesCollection : IEnumerable<Range>
230     {
ContainsOverlappingRange(Range range)231         bool ContainsOverlappingRange(Range range);
ContainsWholeRange(Range range)232         bool ContainsWholeRange(Range range);
ContainsPoint(ulong point)233         bool ContainsPoint(ulong point);
234     }
235 
236     public class MinimalRangesCollection : IReadOnlyMinimalRangesCollection, ICoalescable<MinimalRangesCollection>
237     {
MinimalRangesCollection(IEnumerable<Range> rangeEnumerable = null)238         public MinimalRangesCollection(IEnumerable<Range> rangeEnumerable = null)
239         {
240             if(rangeEnumerable != null)
241             {
242                 AddAll(rangeEnumerable);
243             }
244         }
245 
246         /// <summary>
247         /// Adds a <c>range</c> expanding any elements, if possible. Therefore ranges overlapping,
248         /// and adjacent to the <c>range</c> are removed from the collection and re-added as a
249         /// single expanded <c>Range</c>.
250         /// </summary>
Add(Range range)251         public void Add(Range range)
252         {
253             // ToArray is necessary because ranges gets modified in the code block.
254             foreach(var expandableRange in ranges.Where(collectionRange => collectionRange.CanBeExpandedBy(range)).ToArray())
255             {
256                 ranges.Remove(expandableRange);
257                 range = range.Expand(expandableRange);
258             }
259             ranges.Add(range);
260         }
261 
AddAll(IEnumerable<Range> source)262         public void AddAll(IEnumerable<Range> source)
263         {
264             foreach(var range in source)
265             {
266                 Add(range);
267             }
268         }
269 
Coalesce(MinimalRangesCollection source)270         public void Coalesce(MinimalRangesCollection source)
271         {
272             AddAll(source.AsEnumerable());
273         }
274 
Clear()275         public void Clear()
276         {
277             ranges.Clear();
278         }
279 
ContainsOverlappingRange(Range range)280         public bool ContainsOverlappingRange(Range range)
281         {
282             return ranges.Any(existingRange => existingRange.Intersects(range));
283         }
284 
ContainsWholeRange(Range range)285         public bool ContainsWholeRange(Range range)
286         {
287             // Ranges are merged when added to the collection which means it's only possible if one of elements contains this whole range.
288             return ranges.Any(existingRange => existingRange.Contains(range));
289         }
290 
ContainsPoint(ulong point)291         public bool ContainsPoint(ulong point)
292         {
293             return ranges.Any(existingRange => existingRange.Contains(point));
294         }
295 
GetEnumerator()296         public IEnumerator GetEnumerator()
297         {
298             return ranges.GetEnumerator();
299         }
300 
Remove(Range range)301         public bool Remove(Range range)
302         {
303             return ranges.SubtractAll(range);
304         }
305 
GetEnumerator()306         IEnumerator<Range> IEnumerable<Range>.GetEnumerator()
307         {
308             return ((IEnumerable<Range>)ranges).GetEnumerator();
309         }
310 
311         public int Count => ranges.Count;
312 
313         private readonly HashSet<Range> ranges = new HashSet<Range>();
314     }
315 
316     public static class RangeExtensions
317     {
318         /// <summary>
319         /// Subtracts the given <c>range</c> from each overlapping element in <c>ranges</c>.
320         /// </summary>
321         /// <returns>True if <c>range</c> was subtracted from any element in <c>ranges</c>.</returns>
SubtractAll(this ICollection<Range> ranges, Range range)322         public static bool SubtractAll(this ICollection<Range> ranges, Range range)
323         {
324             // ToArray is necessary because ranges gets modified in the code block.
325             var overlappingRanges = ranges.Where(collectionRange => collectionRange.Intersects(range)).ToArray();
326             foreach(var overlappingRange in overlappingRanges)
327             {
328                 ranges.Remove(overlappingRange);
329                 foreach(var remainingRange in overlappingRange.Subtract(range))
330                 {
331                     ranges.Add(remainingRange);
332                 }
333             }
334             return overlappingRanges.Any();
335         }
336 
By(this ulong startAddress, ulong size)337         public static Range By(this ulong startAddress, ulong size)
338         {
339             return new Range(startAddress, size);
340         }
341 
By(this long startAddress, ulong size)342         public static Range By(this long startAddress, ulong size)
343         {
344             return new Range(checked((ulong)startAddress), size);
345         }
346 
By(this long startAddress, long size)347         public static Range By(this long startAddress, long size)
348         {
349             return new Range(checked((ulong)startAddress), checked((ulong)size));
350         }
351 
By(this int startAddress, ulong size)352         public static Range By(this int startAddress, ulong size)
353         {
354             return new Range(checked((ulong)startAddress), size);
355         }
356 
By(this int startAddress, long size)357         public static Range By(this int startAddress, long size)
358         {
359             return new Range(checked((ulong)startAddress), checked((ulong)size));
360         }
361 
By(this uint startAddress, ulong size)362         public static Range By(this uint startAddress, ulong size)
363         {
364             return new Range(startAddress, size);
365         }
366 
By(this uint startAddress, long size)367         public static Range By(this uint startAddress, long size)
368         {
369             return new Range(startAddress, checked((ulong)size));
370         }
371 
372         /// <remarks>The returned `Range` contains <c>endAddress</c>.</remarks>
To(this int startAddress, ulong endAddress)373         public static Range To(this int startAddress, ulong endAddress)
374         {
375             return checked((ulong)startAddress).To(endAddress);
376         }
377 
378         /// <remarks>The returned `Range` contains <c>endAddress</c>.</remarks>
To(this long startAddress, ulong endAddress)379         public static Range To(this long startAddress, ulong endAddress)
380         {
381             return checked((ulong)startAddress).To(endAddress);
382         }
383 
384         /// <remarks>The returned `Range` contains <c>endAddress</c>.</remarks>
To(this ulong startAddress, ulong endAddress)385         public static Range To(this ulong startAddress, ulong endAddress)
386         {
387             // Moving these to a `Range` constructor would be much better but we can't have
388             // a second constructor with two `ulong` arguments.
389             Range.CreationAssert(
390                 startAddress != 0 || endAddress != ulong.MaxValue,
391                 startAddress, () => "<0, 2^64-1> ranges aren't currently supported"
392             );
393 
394             // The constructor would also throw but the message would be that size is too large
395             // if `startAddress` is higher than `endAddress` which can be misleading.
396             Range.CreationAssert(
397                 startAddress <= endAddress,
398                 startAddress, () => "the start address can't be higher than the end address"
399             );
400             return new Range(startAddress, checked(endAddress - startAddress + 1));
401         }
402     }
403 }
404 
405