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);
+ }
+
+}