// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
#nullable enable
using System;
using System.Buffers;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using IronPython.Runtime.Operations;
using IronPython.Runtime.Types;
namespace IronPython.Runtime {
internal interface ArrayData : IList {
Type StorageType { get; }
bool CanStore([NotNullWhen(true)]object? item);
int CountItems(object? item);
IntPtr GetAddress();
void AddRange(ArrayData value);
void InsertRange(int index, int count, ArrayData value);
void RemoveSlice(Slice slice);
ArrayData Multiply(int count);
new bool Remove(object? item);
void Reverse();
Span AsByteSpan();
IPythonBuffer GetBuffer(object owner, string format, BufferFlags flags);
}
internal class ArrayData : ArrayData, IList, IReadOnlyList where T : struct {
private T[] _items;
private int _size;
private GCHandle? _dataHandle;
private static readonly T[] empty = Array.Empty();
public ArrayData() : this(0) { }
public ArrayData(int capacity) {
GC.SuppressFinalize(this);
_items = capacity == 0 ? empty : new T[capacity];
}
public ArrayData(IEnumerable collection) : this(collection is ICollection c ? c.Count : collection is IReadOnlyCollection rc ? rc.Count : 0) {
AddRange(collection);
}
internal ArrayData(ReadOnlySpan data) {
GC.SuppressFinalize(this);
_items = data.ToArray();
_size = _items.Length;
}
~ArrayData() {
Debug.Assert(_dataHandle.HasValue);
_dataHandle?.Free();
}
public int Count => _size;
public T[] Data => _items;
bool IList.IsFixedSize => false;
bool ICollection.IsReadOnly => false;
bool IList.IsReadOnly => false;
bool ICollection.IsSynchronized => false;
Type ArrayData.StorageType => typeof(T);
object ICollection.SyncRoot => this;
public T this[int index] {
get => _items[index];
set => _items[index] = value;
}
[NotNull]
object? IList.this[int index] {
get => _items[index];
set => _items[index] = GetValue(value);
}
public void Add(T item) {
lock (this) {
CheckBuffer();
EnsureSize(_size + 1L);
_items[_size++] = item;
}
}
int IList.Add(object? item) {
Add(GetValue(item));
return _size - 1;
}
public void AddRange(IPythonBuffer data) {
Debug.Assert(data.NumBytes() % Unsafe.SizeOf() == 0);
int delta = data.NumBytes() / Unsafe.SizeOf();
lock (this) {
CheckBuffer();
EnsureSize((long)_size + delta);
data.CopyTo(MemoryMarshal.AsBytes(_items.AsSpan(_size)));
_size += delta;
}
}
public void AddRange(IEnumerable collection) {
if (collection is ICollection c) {
lock (this) {
CheckBuffer();
EnsureSize((long)_size + c.Count);
c.CopyTo(_items, _size);
_size += c.Count;
}
} else {
foreach (var x in collection) {
Add(x);
}
}
}
void ArrayData.AddRange(ArrayData value)
=> AddRange((ArrayData)value);
bool ArrayData.CanStore([NotNullWhen(true)]object? item)
=> TryConvert(item, out _);
public void Clear() {
lock (this) {
CheckBuffer();
_size = 0;
}
}
public bool Contains(T item)
=> _size != 0 && IndexOf(item) != -1;
bool IList.Contains([NotNullWhen(true)]object? item)
=> TryConvert(item, out T value) && Contains(value);
public void CopyTo(T[] array, int arrayIndex)
=> Array.Copy(_items, 0, array, arrayIndex, _size);
void ICollection.CopyTo(Array array, int index)
=> Array.Copy(_items, 0, array, index, _size);
int ArrayData.CountItems(object? item)
=> TryConvert(item, out T value) ? this.Count(x => x.Equals(value)) : 0;
private void EnsureSize(long size) {
if (size > int.MaxValue) throw PythonOps.MemoryError();
const int IndexOverflow = 0x7FF00000; // https://docs.microsoft.com/en-us/dotnet/api/system.array?view=netcore-3.1#remarks
if (_items.Length < size) {
var length = _items.Length;
if (length == 0) length = 8;
while (length < size && length <= IndexOverflow / 2) {
length *= 2;
}
if (length < size) length = (int)size;
Array.Resize(ref _items, length);
if (_dataHandle != null) {
_dataHandle.Value.Free();
_dataHandle = null;
GC.SuppressFinalize(this);
}
}
}
IntPtr ArrayData.GetAddress() {
// slightly evil to pin our data array but it's only used in rare
// interop cases. If this becomes a problem we can move the allocation
// onto the unmanaged heap if we have full trust via a different subclass
// of ArrayData.
if (!_dataHandle.HasValue) {
_dataHandle = GCHandle.Alloc(_items, GCHandleType.Pinned);
GC.ReRegisterForFinalize(this);
}
return _dataHandle.Value.AddrOfPinnedObject();
}
public IEnumerator GetEnumerator()
=> _items.Take(_size).GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
private static T GetValue(object? value) {
if (TryConvert(value, out T v)) {
return v;
}
if (value != null && typeof(T).IsPrimitive && typeof(T) != typeof(char))
throw PythonOps.OverflowError("couldn't convert {1} to {0}",
DynamicHelpers.GetPythonTypeFromType(typeof(T)).Name,
PythonOps.GetPythonTypeName(value));
throw PythonOps.TypeError("expected {0}, got {1}",
DynamicHelpers.GetPythonTypeFromType(typeof(T)).Name,
PythonOps.GetPythonTypeName(value));
}
public int IndexOf(T item)
=> Array.IndexOf(_items, item, 0, _size);
int IList.IndexOf(object? item)
=> TryConvert(item, out T value) ? IndexOf(value) : -1;
public void Insert(int index, T item) {
lock (this) {
CheckBuffer();
EnsureSize(_size + 1L);
if (index < _size) {
Array.Copy(_items, index, _items, index + 1, _size - index);
}
_items[index] = item;
_size++;
}
}
void IList.Insert(int index, object? item)
=> Insert(index, GetValue(item));
public void InsertRange(int index, int count, IList value) {
// The caller has to ensure that value does not use this ArrayData as its data backing
Debug.Assert(index >= 0);
Debug.Assert(count >= 0);
Debug.Assert(index + count <= _size);
int delta = value.Count - count;
if (delta != 0) {
lock (this) {
CheckBuffer();
EnsureSize((long)_size + delta);
if (index + count < _size) {
Array.Copy(_items, index + count, _items, index + value.Count, _size - index - count);
}
_size += delta;
}
}
value.CopyTo(_items, index);
}
void ArrayData.InsertRange(int index, int count, ArrayData value)
=> InsertRange(index, count, (ArrayData)value);
public void InPlaceMultiply(int count) {
long newSize = (long)_size * count;
if (newSize > int.MaxValue) throw PythonOps.MemoryError();
if (newSize < 0) newSize = 0;
if (newSize == _size) return;
long block = _size;
long pos = _size;
lock (this) {
CheckBuffer();
EnsureSize(newSize);
_size = (int)newSize;
}
while (pos < _size) {
Array.Copy(_items, 0, _items, pos, Math.Min(block, _size - pos));
pos += block;
block *= 2;
}
}
ArrayData ArrayData.Multiply(int count) {
var res = new ArrayData(count * _size);
CopyTo(res._items, 0);
res._size = _size;
res.InPlaceMultiply(count);
return res;
}
public bool Remove(T item) {
int index = IndexOf(item);
if (index >= 0) {
RemoveAt(index);
return true;
}
return false;
}
bool ArrayData.Remove(object? item)
=> TryConvert(item, out T value) && Remove(value);
void IList.Remove(object? item) {
if (TryConvert(item, out T value))
Remove(value);
}
public void RemoveAt(int index) {
lock (this) {
CheckBuffer();
_size--;
}
if (index < _size) {
Array.Copy(_items, index + 1, _items, index, _size - index);
}
}
public void RemoveRange(int index, int count) {
if (count > 0) {
lock (this) {
CheckBuffer();
_size -= count;
}
if (index < _size) {
Array.Copy(_items, index + count, _items, index, _size - index);
}
}
}
public void RemoveSlice(Slice slice) {
slice.Indices(_size, out int start, out int stop, out int step);
if (step > 0 && (start >= stop)) return;
if (step < 0 && (start <= stop)) return;
lock (this) {
CheckBuffer();
if (step == 1) {
RemoveRange(start, stop - start);
return;
} else if (step == -1) {
RemoveRange(stop + 1, start - stop);
return;
} else if (step < 0) {
// normalize start/stop for positive step case
int count = PythonOps.GetSliceCount(start, stop, step);
stop = start + 1;
start += (count - 1) * step;
step = -step; // can overflow, OK
}
int curr, skip, move;
// skip: the next position we should skip
// curr: the next position we should fill in data
// move: the next position we will check
curr = skip = move = start;
while (move < stop) {
if (move != skip) {
_items[curr++] = _items[move];
} else {
skip += step; // can overflow, OK
}
move++;
}
RemoveRange(curr, stop - curr);
}
}
public void Reverse()
=> Array.Reverse(_items, 0, _size);
public Span AsByteSpan()
=> MemoryMarshal.AsBytes(_items.AsSpan(0, _size));
private static bool TryConvert([NotNullWhen(true)]object? value, out T result) {
if (value is null) {
result = default;
return false;
}
if (value is T res) {
result = res;
return true;
}
try {
result = Converter.Convert(value);
return true;
} catch {
result = default;
return false;
}
}
private int _bufferCount = 0;
public IPythonBuffer GetBuffer(object owner, string format, BufferFlags flags) {
return new ArrayDataView(owner, format, this, flags);
}
private void CheckBuffer() {
if (_bufferCount > 0) throw PythonOps.BufferError("Existing exports of data: object cannot be re-sized");
}
private sealed class ArrayDataView : IPythonBuffer {
private readonly BufferFlags _flags;
private readonly ArrayData _arrayData;
private readonly string _format;
public ArrayDataView(object owner, string format, ArrayData arrayData, BufferFlags flags) {
Object = owner;
_format = format;
_arrayData = arrayData;
_flags = flags;
lock (_arrayData) {
_arrayData._bufferCount++;
}
}
private bool _disposed = false;
public void Dispose() {
lock (_arrayData) {
if (_disposed) return;
_arrayData._bufferCount--;
_disposed = true;
}
}
public object Object { get; }
public bool IsReadOnly => false;
public ReadOnlySpan AsReadOnlySpan() => _arrayData.AsByteSpan();
public Span AsSpan() => _arrayData.AsByteSpan();
public MemoryHandle Pin() => _arrayData._items.AsMemory().Pin();
public int Offset => 0;
public string? Format => _flags.HasFlag(BufferFlags.Format)? _format : null;
public int ItemCount => _arrayData.Count;
public int ItemSize => Unsafe.SizeOf();
public int NumOfDims => 1;
public IReadOnlyList? Shape => null;
public IReadOnlyList? Strides => null;
public IReadOnlyList? SubOffsets => null;
}
}
}