Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Factor out IPythonBuffer from IBufferProtocol #808

Merged
merged 38 commits into from
May 22, 2020
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
9b28c32
Factor out IPythonBuffer from IBufferProtocol
BCSharp Apr 27, 2020
c7a3d66
Remove IPythonBuffer.SetSlice(...)
BCSharp Apr 29, 2020
c16769a
Introduce IPythonBuffer.AsSpan()/AsReadOnlySpan()
BCSharp Apr 30, 2020
5965bd2
Redesign MemoryView
BCSharp May 6, 2020
fd07f00
Merge remote-tracking branch 'ironpython3/master' into buffer_protocol
BCSharp May 6, 2020
4df9e7f
Fix compile errors in re
BCSharp May 6, 2020
f9bd595
Move IPythonBuffer from Bytes to BytesView
BCSharp May 6, 2020
613735f
Properly dispose IPythonBuffer created during conversion to BytesLike
BCSharp May 6, 2020
7282c9c
Add ArrayDataView
slozier May 7, 2020
87e55b6
Synchronise disposal of ArrayDataView
BCSharp May 8, 2020
ebe9e6a
Support optional byteorder mark in memoryview casts
BCSharp May 9, 2020
02c42c7
Thread-safe buffer export management of ArrayData
BCSharp May 10, 2020
28488f3
Fix bytearray extended slice assignment
BCSharp May 10, 2020
8c1e57d
Add/enable some bytearray tests
BCSharp May 10, 2020
a0da749
Workaround for HTTPResponse.client.readinto
BCSharp May 10, 2020
28fec90
Merge remote-tracking branch 'ironpython3/master' into buffer_protocol
BCSharp May 10, 2020
4b59a9c
Support conversion from Memory/ReadOnlyMemory to IBufferProtocol
BCSharp May 12, 2020
fea55d8
Merge remote-tracking branch 'ironpython3/master' into buffer_protocol
BCSharp May 12, 2020
c2c63af
Use IBufferProtocol in BytesIO
slozier May 12, 2020
8988491
Convert [BytesLike]ReadOnlyMemory<byte> to IBufferProtocol
BCSharp May 15, 2020
ea91608
Accept byte[] in BytesIO.writelines
slozier May 15, 2020
b5bab39
Adjust rich comparisons in memoryview
slozier May 15, 2020
bbb2457
Merge branch 'master' into buffer_protocol
slozier May 15, 2020
d1ab07c
Fix compile errors
slozier May 15, 2020
78093d6
Remove obsolete code
BCSharp May 16, 2020
533255b
Remove IPythonBuffer.ToBytes(int, int?)
BCSharp May 16, 2020
de4b45c
Finish implementing support for unformatted MemoryView exports
BCSharp May 17, 2020
5a6cc1a
Increase test coverage of codecs and fix bugs
BCSharp May 17, 2020
ee0cff3
Optimize _codecs.escape_encode
BCSharp May 18, 2020
61ffbdd
Let array.array use ArrayDataView
BCSharp May 19, 2020
39a497a
Eliminate some of switches on typecode in array.array
BCSharp May 19, 2020
a73fa3e
Make ArrayData field in ByteArray readonly
BCSharp May 20, 2020
6a6841d
Prevent overflow bugs of ArrayData size
BCSharp May 20, 2020
3611b2e
Implement a finalizer in MemoryView
BCSharp May 20, 2020
a66f5b9
Merge branch 'master' into buffer_protocol
slozier May 21, 2020
589cc93
Enable test_array buffer test
slozier May 21, 2020
7a29b5a
Replace Marshal.SizeOf with Unsafe.SizeOf
slozier May 21, 2020
824fefe
Update after review
BCSharp May 22, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -51,16 +51,15 @@ private static void AnalyzeSymbol(SymbolAnalysisContext context) {
var bytesLikeAttributeSymbol = context.Compilation.GetTypeByMetadataName("IronPython.Runtime.BytesLikeAttribute");

var byteType = context.Compilation.GetTypeByMetadataName("System.Byte");
var readOnlyMemoryType = context.Compilation.GetTypeByMetadataName("System.ReadOnlyMemory`1");
var ireadOnlyListType = context.Compilation.GetTypeByMetadataName("System.Collections.Generic.IReadOnlyList`1");
var ilistType = context.Compilation.GetTypeByMetadataName("System.Collections.Generic.IList`1");
var readOnlyMemoryOfByteType = readOnlyMemoryType.Construct(byteType);
var ireadOnlyListOfByteType = ireadOnlyListType.Construct(byteType);
var ilistOfByteType = ilistType.Construct(byteType);
var ibufferProtocolType = context.Compilation.GetTypeByMetadataName("IronPython.Runtime.IBufferProtocol");

foreach (IParameterSymbol parameterSymbol in methodSymbol.Parameters) {
if (parameterSymbol.GetAttributes().Any(x => x.AttributeClass.Equals(bytesLikeAttributeSymbol))
&& !parameterSymbol.Type.Equals(readOnlyMemoryOfByteType)
&& !parameterSymbol.Type.Equals(ibufferProtocolType)
&& !parameterSymbol.Type.Equals(ireadOnlyListOfByteType) && !parameterSymbol.Type.Equals(ilistOfByteType)) {
var diagnostic = Diagnostic.Create(Rule3, parameterSymbol.Locations[0], parameterSymbol.Name, parameterSymbol.Type.MetadataName);
context.ReportDiagnostic(diagnostic);
Expand Down
1 change: 1 addition & 0 deletions Src/IronPython.Modules/IronPython.Modules.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<BaseAddress>885063680</BaseAddress>
<CodeAnalysisRuleSet>..\..\IronPython.ruleset</CodeAnalysisRuleSet>
<DocumentationFile>$(OutputPath)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<ItemGroup Condition=" '$(IsFullFramework)' == 'true' ">
Expand Down
191 changes: 111 additions & 80 deletions Src/IronPython.Modules/_codecs.cs

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions Src/IronPython.Modules/_ctypes/Array.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ namespace IronPython.Modules {
/// Provides support for interop with native code from Python code.
/// </summary>
public static partial class CTypes {

[PythonType("Array")]
public abstract class _Array : CData {

Expand Down Expand Up @@ -127,7 +127,7 @@ private INativeType ElementType {
}
}


internal override PythonTuple GetBufferInfo() {
INativeType elemType = ElementType;
int dimensions = 1;
Expand Down Expand Up @@ -179,6 +179,8 @@ public override IList<BigInteger> GetShape(int start, int? end) {
return shape;
}

// TODO: if this.NativeType.ElementType is ArrayType, suboffsets need to be reported

#endregion
}
}
Expand Down
56 changes: 29 additions & 27 deletions Src/IronPython.Modules/_ctypes/CData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using IronPython.Runtime;
using IronPython.Runtime.Types;
using System.Collections.Generic;
using System.Linq;

namespace IronPython.Modules {
/// <summary>
Expand All @@ -35,7 +36,7 @@ public static partial class CTypes {
/// Base class for all ctypes interop types.
/// </summary>
[PythonType("_CData"), PythonHidden]
public abstract class CData : IBufferProtocol {
public abstract class CData : IBufferProtocol, IPythonBuffer {
internal MemoryHolder _memHolder;

// members: __setstate__, __reduce__ _b_needsfree_ __ctypes_from_outparam__ __hash__ _objects _b_base_ __doc__
Expand Down Expand Up @@ -95,17 +96,21 @@ internal virtual PythonTuple GetBufferInfo() {

#region IBufferProtocol Members

object IBufferProtocol.GetItem(int index) {
return Bytes.Make(GetBytes(index, NativeType.Size));
IPythonBuffer IBufferProtocol.GetBuffer(BufferFlags flags) {
return this;
}

void IBufferProtocol.SetItem(int index, object value) {
throw new NotImplementedException();
}
void IDisposable.Dispose() { }

void IBufferProtocol.SetSlice(Slice index, object value) {
throw new NotImplementedException();
}
object IPythonBuffer.Object => this;

unsafe ReadOnlySpan<byte> IPythonBuffer.AsReadOnlySpan()
=> new Span<byte>(_memHolder.UnsafeAddress.ToPointer(), _memHolder.Size);

unsafe Span<byte> IPythonBuffer.AsSpan()
=> new Span<byte>(_memHolder.UnsafeAddress.ToPointer(), _memHolder.Size);

int IPythonBuffer.Offset => 0;

public virtual int ItemCount {
[PythonHidden]
Expand All @@ -114,46 +119,43 @@ public virtual int ItemCount {
}
}

string IBufferProtocol.Format {
string IPythonBuffer.Format {
get { return NativeType.TypeFormat; }
}

// TODO: change sig
public virtual BigInteger ItemSize {
[PythonHidden]
get { return this.NativeType.Size; }
}

BigInteger IBufferProtocol.NumberDimensions {
get { return 0; }
int IPythonBuffer.ItemSize => (int)this.ItemSize;

int IPythonBuffer.NumOfDims {
get {
return GetShape(0, null)?.Count ?? (ItemCount > 1 ? 1 : 0);
}
}

bool IBufferProtocol.ReadOnly {
bool IPythonBuffer.IsReadOnly {
get { return false; }
}

// TODO: change sig
[PythonHidden]
public virtual IList<BigInteger> GetShape(int start, int? end) {
return null;
}

PythonTuple IBufferProtocol.Strides {
get { return null; }
}

PythonTuple IBufferProtocol.SubOffsets {
get { return null; }
}
IReadOnlyList<int> IPythonBuffer.Shape => GetShape(0, null)?.Select(big => (int)big).ToArray(); // TODO: remove using Linq when done with sig change

Bytes IBufferProtocol.ToBytes(int start, int? end) {
return Bytes.Make(GetBytes(start, NativeType.Size));
}

PythonList IBufferProtocol.ToList(int start, int? end) {
return new PythonList(((IBufferProtocol)this).ToBytes(start, end));
IReadOnlyList<int> IPythonBuffer.Strides {
get { return null; }
}

ReadOnlyMemory<byte> IBufferProtocol.ToMemory() {
return GetBytes(0, NativeType.Size);
IReadOnlyList<int> IPythonBuffer.SubOffsets {
get { return null; }
}

#endregion
Expand Down
10 changes: 5 additions & 5 deletions Src/IronPython.Modules/_operator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -352,14 +352,14 @@ public static bool _compare_digest(object a, object b) {
string aStr = a as string;
string bStr = b as string;
return CompareBytes(aStr.MakeByteArray(), bStr.MakeByteArray());
} else if(a is IBufferProtocol && b is IBufferProtocol) {
IBufferProtocol aBuf = a as IBufferProtocol;
IBufferProtocol bBuf = b as IBufferProtocol;
if(aBuf.NumberDimensions > 1 || bBuf.NumberDimensions > 1) {
} else if(a is IBufferProtocol abp && b is IBufferProtocol bbp) {
using IPythonBuffer aBuf = abp.GetBuffer();
using IPythonBuffer bBuf = bbp.GetBuffer();
if(aBuf.NumOfDims > 1 || bBuf.NumOfDims > 1) {
throw PythonOps.BufferError("Buffer must be single dimension");
}

return CompareBytes(aBuf.ToBytes(0, null), bBuf.ToBytes(0, null));
return CompareBytes(aBuf.AsReadOnlySpan().ToArray(), bBuf.AsReadOnlySpan().ToArray());
}
throw PythonOps.TypeError("unsupported operand types(s) or combination of types: '{0}' and '{1}", PythonOps.GetPythonTypeName(a), PythonOps.GetPythonTypeName(b));
}
Expand Down
7 changes: 5 additions & 2 deletions Src/IronPython.Modules/_ssl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,11 @@ public void load_verify_locations(CodeContext context, [DefaultParameterValue(nu
var cabuf = cadata as IBufferProtocol;
if (cabuf != null) {
int pos = 0;
byte[] contents = cabuf.ToBytes(0, null).ToArray();
while(pos < contents.Length) {
byte[] contents;
using (IPythonBuffer buf = cabuf.GetBuffer()) {
contents = buf.AsReadOnlySpan().ToArray();
}
while (pos < contents.Length) {
byte[] curr = new byte[contents.Length - pos];
Array.Copy(contents, pos, curr, 0, contents.Length - pos);
var cert = new X509Certificate2(curr);
Expand Down
7 changes: 5 additions & 2 deletions Src/IronPython.Modules/binascii.cs
Original file line number Diff line number Diff line change
Expand Up @@ -680,8 +680,11 @@ private static byte[] tobytes(this string s) {
return ret;
}

private static IList<byte> tobytes(this IBufferProtocol buffer)
=> buffer as IList<byte> ?? buffer.ToBytes(0, null);
private static IList<byte> tobytes(this IBufferProtocol buffer) {
if (buffer is IList<byte> list) return list;

return new Bytes(buffer);
}

#endregion
}
Expand Down
12 changes: 9 additions & 3 deletions Src/IronPython.Modules/re.cs
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,9 @@ private string ValidateString(object @string) {
if (pattern is Bytes) {
switch (@string) {
case IBufferProtocol bufferProtocol:
str = bufferProtocol.ToBytes(0, null).MakeString();
using (IPythonBuffer buf = bufferProtocol.GetBuffer()) {
str = buf.AsReadOnlySpan().MakeString();
}
break;
case IList<byte> b:
str = b.MakeString();
Expand Down Expand Up @@ -494,7 +496,9 @@ private string ValidateReplacement(object repl) {
if (pattern is Bytes) {
switch (repl) {
case IBufferProtocol bufferProtocol:
str = bufferProtocol.ToBytes(0, null).MakeString();
using (IPythonBuffer buf = bufferProtocol.GetBuffer()) {
str = buf.AsReadOnlySpan().MakeString();
}
break;
case IList<byte> b:
str = b.MakeString();
Expand Down Expand Up @@ -1231,7 +1235,9 @@ private static string ValidateArguments(object pattern, object @string) {
if (pattern is Bytes) {
switch (@string) {
case IBufferProtocol bufferProtocol:
str = bufferProtocol.ToBytes(0, null).MakeString();
using (IPythonBuffer buf = bufferProtocol.GetBuffer()) {
str = buf.AsReadOnlySpan().MakeString();
}
break;
case IList<byte> b:
str = b.MakeString();
Expand Down
84 changes: 15 additions & 69 deletions Src/IronPython/Modules/_bytesio.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public BytesIO(CodeContext/*!*/ context, object initial_bytes=null)
: base(context) {
}

public void __init__(object initial_bytes=null) {
public void __init__(IBufferProtocol initial_bytes=null) {
if (Object.ReferenceEquals(_data, null)) {
_data = new byte[DEFAULT_BUF_SIZE];
}
Expand Down Expand Up @@ -94,8 +94,8 @@ public Bytes getvalue() {

public MemoryView getbuffer() {
_checkClosed();

return new MemoryView(new Bytes(_data), 0, _length, 1, "B", PythonTuple.MakeTuple(_length), readonlyView: true);
// TODO: MemoryView constructor should accept Memory/ReadOnlyMemory
return (MemoryView)new MemoryView(new Bytes(_data))[new Slice(0, _length, 1)];
}

[Documentation("isatty() -> False\n\n"
Expand Down Expand Up @@ -355,7 +355,12 @@ public override bool writable(CodeContext/*!*/ context) {
)]
public override BigInteger write(CodeContext/*!*/ context, object bytes) {
_checkClosed();
if (bytes is IBufferProtocol bufferProtocol) return DoWrite(bufferProtocol);
throw PythonOps.TypeError("a bytes-like object is required, not '{0}'", PythonTypeOps.GetName(bytes));
}

public BigInteger write(CodeContext/*!*/ context, IBufferProtocol bytes) {
_checkClosed();
return DoWrite(bytes);
}

Expand Down Expand Up @@ -418,83 +423,24 @@ DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject(Expression parameter)

#region Private implementation details

private int DoWrite(byte[] bytes) {
private int DoWrite(IBufferProtocol bufferProtocol) {
using var buffer = bufferProtocol.GetBuffer();
var bytes = buffer.AsReadOnlySpan();

if (bytes.Length == 0) {
return 0;
}

EnsureSizeSetLength(_pos + bytes.Length);
Array.Copy(bytes, 0, _data, _pos, bytes.Length);
bytes.CopyTo(_data.AsSpan(_pos, bytes.Length));

_pos += bytes.Length;
return bytes.Length;
}

private int DoWrite(ICollection<byte> bytes) {
int nbytes = bytes.Count;
if (nbytes == 0) {
return 0;
}

EnsureSizeSetLength(_pos + nbytes);
bytes.CopyTo(_data, _pos);

_pos += nbytes;
return nbytes;
}

private int DoWrite(string bytes) {
// CLR strings are natively Unicode (UTF-16 LE, to be precise).
// In 2.x, io.BytesIO.write() takes "bytes or bytearray" as a parameter.
// On 2.x "bytes" is an alias for str, so str types are accepted by BytesIO.write().
// When given a unicode object, 2.x BytesIO.write() complains:
// TypeError: 'unicode' does not have the buffer interface

// We will accept CLR strings, but only if the data in it is in Latin 1 (iso-8859-1)
// encoding (i.e. ord(c) for all c is within 0-255.

// Alternatively, we could support strings containing any Unicode character by ignoring
// any 0x00 bytes, but as CPython doesn't support that it is unlikely that we will need to.

int nbytes = bytes.Length;
if (nbytes == 0) {
return 0;
}

byte[] _raw_string = new byte[nbytes];
for (int i = 0; i < nbytes; i++) {
int ord = (int)bytes[i];
if(ord < 256) {
_raw_string[i] = (byte)ord;
} else {
// A character outside the range 0x00-0xFF is present in the original string.
// Ejecting, emulating the cPython 2.x behavior when it enounters "unicode".
// This should keep the unittest gods at bay.
throw PythonOps.TypeError("'unicode' does not have the buffer interface");
}
}

return DoWrite(_raw_string);
}

private int DoWrite(object bytes) {
switch (bytes) {
case byte[] b:
return DoWrite(b);
case Bytes b:
return DoWrite(b.UnsafeByteArray);
case ArrayModule.array a:
return DoWrite(a.ToByteArray()); // as byte[]
case ICollection<byte> c:
return DoWrite(c);
case string s:
// TODO Remove this when we move to 3.x
return DoWrite(s);
case MemoryView mv:
return DoWrite(mv.tobytes().UnsafeByteArray);
}

throw PythonOps.TypeError("expected a readable buffer object");
if (bytes is IBufferProtocol bufferProtocol) return DoWrite(bufferProtocol);
return DoWrite(Converter.Convert<IBufferProtocol>(bytes));
}

private void EnsureSize(int size) {
Expand Down
11 changes: 5 additions & 6 deletions Src/IronPython/Modules/_io.cs
Original file line number Diff line number Diff line change
Expand Up @@ -421,12 +421,11 @@ public override object read(CodeContext/*!*/ context, object size=null) {
ByteArray arr = new ByteArray(new byte[sizeInt]);
sizeInt = (int)readinto(context, arr);

var res = arr.UnsafeByteList;
if (sizeInt < res.Count) {
res.RemoveRange(sizeInt, res.Count - sizeInt);
}

return new Bytes(res);
// cannot use arr.UnsafeByteList.RemoveRange(sizeInt, res.Count - sizeInt)
// because HTTPResponse.client.readinto may wrap `arr` in `memoryview`
// and never release it
using var buf = ((IBufferProtocol)arr).GetBuffer();
return Bytes.Make(buf.AsReadOnlySpan().Slice(0, sizeInt).ToArray());
}

public Bytes readall(CodeContext/*!*/ context) {
Expand Down
Loading