diff --git a/src/Compilers/Core/Portable/CodeAnalysis.csproj b/src/Compilers/Core/Portable/CodeAnalysis.csproj index ded873e82ff5db62d4201a643704504f3b121906..86b2d06ccb289f4b0ccc650365ff41012af73b44 100644 --- a/src/Compilers/Core/Portable/CodeAnalysis.csproj +++ b/src/Compilers/Core/Portable/CodeAnalysis.csproj @@ -334,6 +334,7 @@ + diff --git a/src/Compilers/Core/Portable/PEWriter/ComMemoryStream.cs b/src/Compilers/Core/Portable/PEWriter/ComMemoryStream.cs index 11046a2dd6bf7f6fb00f04d4242971389e807ec2..71cfc31565301d741a0dce70cc8305d7c0fb5667 100644 --- a/src/Compilers/Core/Portable/PEWriter/ComMemoryStream.cs +++ b/src/Compilers/Core/Portable/PEWriter/ComMemoryStream.cs @@ -2,21 +2,55 @@ using System.IO; using System.Collections.Generic; using System.Runtime.InteropServices.ComTypes; +using System.Runtime.InteropServices; namespace Roslyn.Utilities { - internal class ComMemoryStream : IStream + /// + /// A COM IStream implementation over memory. Supports just enough for DiaSymReader's PDB writing. + /// Also tuned for performance: + /// 1. SetSize (and Seek beyond the length) is very fast and doesn't re-allocate the underlying memory. + /// 2. Write is optimized to avoid copying. + /// 3. Doesn't use contiguous memory. + /// + internal class ComMemoryStream : IUnsafeComStream { -#if DEBUG - private const int ChunkSize = 509; // Small prime number for debugging chunking -#else private const int ChunkSize = 32768; -#endif private List _chunks = new List(); private int _position; private int _length; - public unsafe void Read(byte[] pv, int cb, IntPtr pcbRead) + public void CopyTo(Stream stream) + { + // If the target stream allows seeking set its length upfront. + // When writing to a large file, it helps to give a hint to the OS how big the file is going to be. + if (stream.CanSeek) + { + stream.SetLength(stream.Position + _length); + } + + int chunkIndex = 0; + for (int cb = _length; cb > 0;) + { + int bytesToCopy = Math.Min(ChunkSize, cb); + if (chunkIndex < _chunks.Count) + { + stream.Write(_chunks[chunkIndex++], 0, bytesToCopy); + } + else + { + // Fill remaining space with zero bytes + for (int i = 0; i < bytesToCopy; i++) + { + stream.WriteByte(0); + } + } + + cb -= bytesToCopy; + } + } + + unsafe void IUnsafeComStream.Read(byte[] pv, int cb, IntPtr pcbRead) { int chunkIndex = _position / ChunkSize; int chunkOffset = _position % ChunkSize; @@ -76,7 +110,7 @@ private int SetPosition(int newPos) return newPos; } - public unsafe void Seek(long dlibMove, int origin, IntPtr plibNewPosition) + unsafe void IUnsafeComStream.Seek(long dlibMove, int origin, IntPtr plibNewPosition) { int newPosition; @@ -104,12 +138,12 @@ public unsafe void Seek(long dlibMove, int origin, IntPtr plibNewPosition) } } - public void SetSize(long libNewSize) + void IUnsafeComStream.SetSize(long libNewSize) { _length = (int)libNewSize; } - public void Stat(out STATSTG pstatstg, int grfStatFlag) + void IUnsafeComStream.Stat(out STATSTG pstatstg, int grfStatFlag) { pstatstg = new STATSTG() { @@ -117,7 +151,7 @@ public void Stat(out STATSTG pstatstg, int grfStatFlag) }; } - public unsafe void Write(byte[] pv, int cb, IntPtr pcbWritten) + unsafe void IUnsafeComStream.Write(IntPtr pv, int cb, IntPtr pcbWritten) { int chunkIndex = _position / ChunkSize; int chunkOffset = _position % ChunkSize; @@ -135,7 +169,7 @@ public unsafe void Write(byte[] pv, int cb, IntPtr pcbWritten) _chunks.Add(new byte[ChunkSize]); } - Array.Copy(pv, bytesWritten, _chunks[chunkIndex], chunkOffset, bytesToCopy); + Marshal.Copy(pv + bytesWritten, _chunks[chunkIndex], chunkOffset, bytesToCopy); bytesWritten += bytesToCopy; _position += bytesToCopy; cb -= bytesToCopy; @@ -159,65 +193,34 @@ public unsafe void Write(byte[] pv, int cb, IntPtr pcbWritten) } } - public void CopyTo(Stream stream) - { - int size = _length; - - // If the target stream allows seeking set its length upfront. - // When writing to a large file, it helps to give a hint to the OS how big the file is going to be. - if (stream.CanSeek) - { - stream.SetLength(stream.Position + size); - } - - int chunkIndex = 0; - for (int cb = size; cb > 0;) - { - int bytesToCopy = Math.Min(ChunkSize, cb); - if (chunkIndex < _chunks.Count) - { - stream.Write(_chunks[chunkIndex++], 0, bytesToCopy); - } - else - { - // Fill remaining space with zero bytes - for (int i = 0; i < bytesToCopy; i++) - { - stream.WriteByte(0); - } - } - - cb -= bytesToCopy; - } - } - - public void Commit(int grfCommitFlags) + void IUnsafeComStream.Commit(int grfCommitFlags) { } - public void Clone(out IStream ppstm) + void IUnsafeComStream.Clone(out IStream ppstm) { throw new NotSupportedException(); } - public void CopyTo(IStream pstm, long cb, IntPtr pcbRead, IntPtr pcbWritten) + void IUnsafeComStream.CopyTo(IStream pstm, long cb, IntPtr pcbRead, IntPtr pcbWritten) { throw new NotSupportedException(); } - public void LockRegion(long libOffset, long cb, int lockType) + void IUnsafeComStream.LockRegion(long libOffset, long cb, int lockType) { throw new NotSupportedException(); } - public void Revert() + void IUnsafeComStream.Revert() { throw new NotSupportedException(); } - public void UnlockRegion(long libOffset, long cb, int lockType) + void IUnsafeComStream.UnlockRegion(long libOffset, long cb, int lockType) { throw new NotSupportedException(); } } } + diff --git a/src/Compilers/Core/Portable/PEWriter/IUnsafeComStream.cs b/src/Compilers/Core/Portable/PEWriter/IUnsafeComStream.cs new file mode 100644 index 0000000000000000000000000000000000000000..df20e1eebe3de5edafeb507bccc6535e588757f2 --- /dev/null +++ b/src/Compilers/Core/Portable/PEWriter/IUnsafeComStream.cs @@ -0,0 +1,31 @@ +using System; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; + +namespace Roslyn.Utilities +{ + /// + /// This is a re-definition of COM's IStream interface. The important change is that + /// the Write method takes an instead of a byte[] to avoid the + /// allocation cost when called from native code. + /// + [Guid("0000000c-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), ComImport] + internal interface IUnsafeComStream + { + // ISequentialStream portion + void Read([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1), Out] byte[] pv, int cb, IntPtr pcbRead); + void Write(IntPtr pv, int cb, IntPtr pcbWritten); + + // IStream portion + void Seek(long dlibMove, int dwOrigin, IntPtr plibNewPosition); + void SetSize(long libNewSize); + void CopyTo(IStream pstm, long cb, IntPtr pcbRead, IntPtr pcbWritten); + void Commit(int grfCommitFlags); + void Revert(); + void LockRegion(long libOffset, long cb, int dwLockType); + void UnlockRegion(long libOffset, long cb, int dwLockType); + void Stat(out STATSTG pstatstg, int grfStatFlag); + void Clone(out IStream ppstm); + } + +}