forked from IronLanguages/ironpython3
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMemoryHolder.cs
More file actions
365 lines (313 loc) · 13.1 KB
/
MemoryHolder.cs
File metadata and controls
365 lines (313 loc) · 13.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
// 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
#if FEATURE_CTYPES
#pragma warning disable SYSLIB0004 // The Constrained Execution Region (CER) feature is not supported in .NET 5.0.
using System;
using System.Buffers;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Threading;
using IronPython.Runtime;
namespace IronPython.Modules {
/// <summary>
/// A wrapper around allocated memory to ensure it gets released and isn't accessed
/// when it could be finalized.
/// </summary>
internal sealed class MemoryHolder : CriticalFinalizerObject, IDisposable {
private readonly IntPtr _data;
private readonly bool _ownsData;
private readonly int _size;
private readonly IPythonBuffer? _buffer;
private readonly MemoryHandle _handle;
private readonly MemoryHolder? _parent;
private bool _disposeRequested;
private bool _disposed;
private int _numChildren;
private PythonDictionary? _objects;
/// <summary>
/// Creates a new MemoryHolder and allocates a buffer of the specified size.
/// The buffer will be released on a call to <see cref="Dispose"/> or by the finalizer.
/// </summary>
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
public MemoryHolder(int size) {
RuntimeHelpers.PrepareConstrainedRegions();
try {
} finally {
_size = size;
_data = NativeFunctions.Calloc(new IntPtr(size));
if (_data == IntPtr.Zero) {
GC.SuppressFinalize(this);
throw new OutOfMemoryException();
}
_ownsData = true;
}
}
/// <summary>
/// Creates a new MemoryHolder at the specified address which is not tracked
/// by us and we will never free.
/// Therefore no need to call <see cref="Dispose"/>.
/// </summary>
public MemoryHolder(IntPtr data, int size) {
GC.SuppressFinalize(this);
_data = data;
_size = size;
}
/// <summary>
/// Creates a new MemoryHolder at the specified address which will keep alive the
/// parent memory holder.
/// </summary>
public MemoryHolder(IntPtr data, int size, MemoryHolder parent) {
GC.SuppressFinalize(this);
_data = data;
_parent = parent;
parent._numChildren++;
_objects = parent._objects;
_size = size;
}
/// <summary>
/// Creates a new MemoryHolder from a Python buffer
/// that will be disposed on a call to <see cref="Dispose"/> or by the finalizer.
/// </summary>
public MemoryHolder(IPythonBuffer buffer, int offset, int size) {
if (buffer.IsReadOnly) throw new ArgumentException("Buffer must be writable.");
if (!buffer.IsCContiguous()) throw new ArgumentException("Buffer must be c-contiguous.");
if (size < 0) throw new ArgumentOutOfRangeException(nameof(size), size, "Non-negative number required.");
if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset), offset, "Non-negative number required.");
int bufSize = buffer.NumBytes();
ReadOnlySpan<byte> memblock = buffer.AsSpan();
if (memblock.Length != bufSize) new ArgumentException("Invalid buffer.");
if (size > bufSize - offset) throw new ArgumentException("Requested memory block exceeds buffer boundaries.");
_buffer = buffer;
_handle = buffer.Pin();
unsafe { _data = (IntPtr)_handle.Pointer + offset; }
_size = size;
}
/// <summary>
/// Gets the address of the held memory. The caller should ensure the MemoryHolder
/// is always alive and not disposed as long as the address will continue to be accessed.
/// </summary>
public IntPtr UnsafeAddress => !_disposed ? _data : IntPtr.Zero;
public int Size => _size;
/// <summary>
/// Gets a list of objects which need to be kept alive for this MemoryHolder to be
/// remain valid.
/// </summary>
public PythonDictionary? Objects {
get => _objects;
set => _objects = value;
}
internal PythonDictionary EnsureObjects() {
if (_objects is null) {
Interlocked.CompareExchange(ref _objects, new PythonDictionary(), null);
}
return _objects;
}
/// <summary>
/// Used to track the lifetime of objects when one memory region depends upon
/// another memory region. For example if you have an array of objects that
/// each have an element which has it's own lifetime the array needs to keep
/// the individual elements alive.
///
/// The keys used here match CPython's keys as tested by CPython's test_ctypes.
/// Typically they are a string which is the array index, "ffffffff" when
/// from_buffer is used, or when it's a simple type there's just a string
/// instead of the full dictionary - we store that under the key "str".
/// </summary>
internal void AddObject(object key, object value) {
EnsureObjects()[key] = value;
}
private short Swap(short val) {
return (short)((((ushort)val & 0xFF00) >> 8) | (((ushort)val & 0x00FF) << 8));
}
private int Swap(int val) {
// swap adjacent 16-bit blocks
val = (int)(((uint)val >> 16) | ((uint)val << 16));
// swap adjacent 8-bit blocks
return (int)((((uint)val & 0xFF00FF00) >> 8) | (((uint)val & 0x00FF00FF) << 8));
}
private long Swap(long val) {
// swap adjacent 32-bit blocks
val = (long)(((ulong)val >> 32) | ((ulong)val << 32));
// swap adjacent 16-bit blocks
val = (long)((((ulong)val & 0xFFFF0000FFFF0000) >> 16) | (((ulong)val & 0x0000FFFF0000FFFF) << 16));
// swap adjacent 8-bit blocks
return (long)((((ulong)val & 0xFF00FF00FF00FF00) >> 8) | (((ulong)val & 0x00FF00FF00FF00FF) << 8));
}
public byte ReadByte(int offset) {
byte res = Marshal.ReadByte(_data, offset);
GC.KeepAlive(this);
return res;
}
public short ReadInt16(int offset, bool swap=false) {
short res = Marshal.ReadInt16(_data, offset);
GC.KeepAlive(this);
if(swap) res = Swap(res);
return res;
}
public int ReadInt32(int offset, bool swap=false) {
int res = Marshal.ReadInt32(_data, offset);
GC.KeepAlive(this);
if(swap) res = Swap(res);
return res;
}
public long ReadInt64(int offset, bool swap=false) {
long res = Marshal.ReadInt64(_data, offset);
GC.KeepAlive(this);
if(swap) res = Swap(res);
return res;
}
public IntPtr ReadIntPtr(int offset) {
IntPtr res = Marshal.ReadIntPtr(_data, offset);
GC.KeepAlive(this);
return res;
}
public MemoryHolder ReadMemoryHolder(int offset) {
IntPtr res = Marshal.ReadIntPtr(_data, offset);
return new MemoryHolder(res, IntPtr.Size, this);
}
internal Bytes? ReadBytes(int offset) {
try {
return ReadBytes(_data, offset);
} finally {
GC.KeepAlive(this);
}
}
internal string? ReadUnicodeString(int offset) {
try {
return Marshal.PtrToStringUni(_data.Add(offset));
} finally {
GC.KeepAlive(this);
}
}
internal Bytes ReadBytes(int offset, int length) {
try {
return ReadBytes(_data, offset, length);
} finally {
GC.KeepAlive(this);
}
}
internal static Bytes ReadBytes(IntPtr addr, int offset, int length) {
// instead of Marshal.PtrToStringAnsi we do this because
// ptrToStringAnsi gives special treatment to values >= 128.
var buffer = new byte[length];
if (checked(offset + length) < int.MaxValue) {
for (int i = 0; i < length; i++) {
buffer[i] = Marshal.ReadByte(addr, offset + i);
}
}
return Bytes.Make(buffer);
}
internal static Bytes? ReadBytes(IntPtr addr, int offset) {
if (addr == IntPtr.Zero) return null;
// instead of Marshal.PtrToStringAnsi we do this because
// ptrToStringAnsi gives special treatment to values >= 128.
MemoryStream res = new MemoryStream();
byte b;
while((b = Marshal.ReadByte(addr, offset++)) != 0) {
res.WriteByte(b);
}
return Bytes.Make(res.ToArray());
}
internal string ReadUnicodeString(int offset, int length) {
try {
return Marshal.PtrToStringUni(_data.Add(offset), length);
} finally {
GC.KeepAlive(this);
}
}
public void WriteByte(int offset, byte value) {
Marshal.WriteByte(_data, offset, value);
GC.KeepAlive(this);
}
public void WriteInt16(int offset, short value, bool swap=false) {
Marshal.WriteInt16(_data, offset, swap ? Swap(value) : value);
GC.KeepAlive(this);
}
public void WriteInt32(int offset, int value, bool swap=false) {
Marshal.WriteInt32(_data, offset, swap ? Swap(value) : value);
GC.KeepAlive(this);
}
public void WriteInt64(int offset, long value, bool swap=false) {
Marshal.WriteInt64(_data, offset, swap ? Swap(value) : value);
GC.KeepAlive(this);
}
public void WriteIntPtr(int offset, IntPtr value) {
Marshal.WriteIntPtr(_data, offset, value);
GC.KeepAlive(this);
}
public void WriteIntPtr(int offset, MemoryHolder address) {
Marshal.WriteIntPtr(_data, offset, address.UnsafeAddress);
GC.KeepAlive(this);
GC.KeepAlive(address);
}
/// <summary>
/// Copies the data in data into this MemoryHolder.
/// </summary>
public void CopyFrom(IntPtr source, IntPtr size) {
NativeFunctions.MemCopy(_data, source, size);
GC.KeepAlive(this);
}
internal void WriteUnicodeString(int offset, string value) {
// TODO: There's gotta be a better way to do this
for (int i = 0; i < value.Length; i++) {
WriteInt16(checked(offset + i * 2), (short)value[i]);
}
}
internal void WriteSpan(int offset, ReadOnlySpan<byte> value) {
unsafe {
var dest = new Span<byte>((void*)_data, _size).Slice(offset);
value.CopyTo(dest);
}
GC.KeepAlive(this);
}
public MemoryHolder GetSubBlock(int offset) {
// No GC.KeepAlive here because the new MemoryHolder holds onto the previous one.
return new MemoryHolder(_data.Add(offset), _size - offset, this);
}
/// <summary>
/// Copies memory from one location to another keeping the associated memory holders alive during the
/// operation.
/// </summary>
public void CopyTo(MemoryHolder/*!*/ destAddress, int writeOffset, int size) {
NativeFunctions.MemCopy(destAddress._data.Add(writeOffset), _data, new IntPtr(size));
GC.KeepAlive(destAddress);
GC.KeepAlive(this);
}
private void ReleaseChild() {
Debug.Assert(_numChildren >= 0);
_numChildren--;
if (_disposeRequested) Dispose();
}
public void Dispose() {
_disposeRequested = true;
if (!_disposed && _numChildren == 0) {
_disposed = true;
if (_ownsData) {
Marshal.FreeHGlobal(_data);
}
_handle.Dispose();
_buffer?.Dispose();
_parent?.ReleaseChild();
GC.SuppressFinalize(this);
}
}
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
~MemoryHolder() {
if (!_disposed) {
_disposed = true;
if (_ownsData) {
Marshal.FreeHGlobal(_data);
}
try { _handle.Dispose(); } catch { /*ignore*/ }
try { _buffer?.Dispose(); } catch { /*ignore*/ }
try { _parent?.ReleaseChild(); } catch { /*ignore*/ }
}
}
}
}
#endif