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

Fixed LOH byte array allocations in the IDE from metadata #7971

Merged
merged 12 commits into from
Dec 16, 2019
306 changes: 300 additions & 6 deletions src/absil/bytes.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@
/// Byte arrays
namespace FSharp.Compiler.AbstractIL.Internal

open System
open System.IO
open System.IO.MemoryMappedFiles
open System.Runtime.InteropServices
open System.Runtime.CompilerServices
open FSharp.NativeInterop

#nowarn "9"

module internal Bytes =
let b0 n = (n &&& 0xFF)
Expand All @@ -26,10 +33,290 @@ module internal Bytes =
Array.append (System.Text.Encoding.UTF8.GetBytes s) (ofInt32Array [| 0x0 |])

let stringAsUnicodeNullTerminated (s:string) =
Array.append (System.Text.Encoding.Unicode.GetBytes s) (ofInt32Array [| 0x0;0x0 |])
Array.append (System.Text.Encoding.Unicode.GetBytes s) (ofInt32Array [| 0x0;0x0 |])

[<AbstractClass>]
type ByteMemory () =

abstract Item: int -> byte with get, set

abstract Length: int

abstract ReadBytes: pos: int * count: int -> byte[]

abstract ReadInt32: pos: int -> int

abstract ReadUInt16: pos: int -> uint16

abstract ReadUtf8String: pos: int * count: int -> string

abstract Slice: pos: int * count: int -> ByteMemory

abstract CopyTo: Stream -> unit

abstract Copy: srcOffset: int * dest: byte[] * destOffset: int * count: int -> unit

abstract ToArray: unit -> byte[]

abstract AsStream: unit -> Stream

abstract AsReadOnlyStream: unit -> Stream

[<Sealed>]
type ByteArrayMemory(bytes: byte[], offset, length) =
inherit ByteMemory()

do
if offset < 0 then
failwith "offset is less than zero"

if length <= 0 then
failwith "length is less than or equal to zero"
cartermp marked this conversation as resolved.
Show resolved Hide resolved

if (offset + length) > bytes.Length then
failwith "span larger than byte array"
cartermp marked this conversation as resolved.
Show resolved Hide resolved

override _.Item
with get i =
bytes.[offset + i]
and set i v =
bytes.[offset + i] <- v

override _.Length = length

override _.ReadBytes(pos, count) =
Array.sub bytes (offset + pos) count

override _.ReadInt32 pos =
let finalOffset = offset + pos
(uint32 bytes.[finalOffset]) |||
((uint32 bytes.[finalOffset + 1]) <<< 8) |||
((uint32 bytes.[finalOffset + 2]) <<< 16) |||
((uint32 bytes.[finalOffset + 3]) <<< 24)
|> int

override _.ReadUInt16 pos =
let finalOffset = offset + pos
(uint16 bytes.[finalOffset]) |||
((uint16 bytes.[finalOffset + 1]) <<< 8)

override _.ReadUtf8String(pos, count) =
System.Text.Encoding.UTF8.GetString(bytes, offset + pos, count)

override _.Slice(pos, count) =
ByteArrayMemory(bytes, offset + pos, count) :> ByteMemory

override _.CopyTo stream =
stream.Write(bytes, offset, length)

override _.Copy(srcOffset, dest, destOffset, count) =
Array.blit bytes (offset + srcOffset) dest destOffset count

override _.ToArray() =
Array.sub bytes offset length

override _.AsStream() =
new MemoryStream(bytes, offset, length) :> Stream

override _.AsReadOnlyStream() =
new MemoryStream(bytes, offset, length, false) :> Stream

[<Sealed>]
type RawByteMemory(addr: nativeptr<byte>, length: int, hold: obj) =
inherit ByteMemory ()

let check i =
if i < 0 || i >= length then
failwith "out of range"
cartermp marked this conversation as resolved.
Show resolved Hide resolved

do
if length <= 0 then
failwith "length is less than or equal to zero"
cartermp marked this conversation as resolved.
Show resolved Hide resolved

override _.Item
with get i =
check i
NativePtr.add addr i
|> NativePtr.read
and set i v =
check i
NativePtr.set addr i v

override _.Length = length

override _.ReadUtf8String(pos, count) =
check pos
check (pos + count - 1)
System.Text.Encoding.UTF8.GetString(NativePtr.add addr pos, count)

override _.ReadBytes(pos, count) =
check pos
check (pos + count - 1)
let res = Bytes.zeroCreate count
Marshal.Copy(NativePtr.toNativeInt addr + nativeint pos, res, 0, count)
res

override _.ReadInt32 pos =
check pos
check (pos + 3)
Marshal.ReadInt32(NativePtr.toNativeInt addr + nativeint pos)

override _.ReadUInt16 pos =
check pos
check (pos + 1)
uint16(Marshal.ReadInt16(NativePtr.toNativeInt addr + nativeint pos))

override _.Slice(pos, count) =
check pos
check (pos + count - 1)
RawByteMemory(NativePtr.add addr pos, count, hold) :> ByteMemory

override x.CopyTo stream =
use stream2 = x.AsStream()
stream2.CopyTo stream

override x.Copy(srcOffset, dest, destOffset, count) =
check srcOffset
Marshal.Copy(NativePtr.toNativeInt addr + nativeint srcOffset, dest, destOffset, count)

override _.ToArray() =
let res = Array.zeroCreate<byte> length
Marshal.Copy(NativePtr.toNativeInt addr, res, 0, res.Length)
res

override _.AsStream() =
new UnmanagedMemoryStream(addr, int64 length) :> Stream

override _.AsReadOnlyStream() =
new UnmanagedMemoryStream(addr, int64 length, int64 length, FileAccess.Read) :> Stream

[<Struct;NoEquality;NoComparison>]
type ReadOnlyByteMemory(bytes: ByteMemory) =

member _.Item with [<MethodImpl(MethodImplOptions.AggressiveInlining)>] get i = bytes.[i]

member _.Length with [<MethodImpl(MethodImplOptions.AggressiveInlining)>] get () = bytes.Length

[<MethodImpl(MethodImplOptions.AggressiveInlining)>]
member _.ReadBytes(pos, count) = bytes.ReadBytes(pos, count)

[<MethodImpl(MethodImplOptions.AggressiveInlining)>]
member _.ReadInt32 pos = bytes.ReadInt32 pos

[<MethodImpl(MethodImplOptions.AggressiveInlining)>]
member _.ReadUInt16 pos = bytes.ReadUInt16 pos

[<MethodImpl(MethodImplOptions.AggressiveInlining)>]
member _.ReadUtf8String(pos, count) = bytes.ReadUtf8String(pos, count)

[<MethodImpl(MethodImplOptions.AggressiveInlining)>]
member _.Slice(pos, count) = bytes.Slice(pos, count) |> ReadOnlyByteMemory

[<MethodImpl(MethodImplOptions.AggressiveInlining)>]
member _.CopyTo stream = bytes.CopyTo stream

[<MethodImpl(MethodImplOptions.AggressiveInlining)>]
member _.Copy(srcOffset, dest, destOffset, count) = bytes.Copy(srcOffset, dest, destOffset, count)

[<MethodImpl(MethodImplOptions.AggressiveInlining)>]
member _.ToArray() = bytes.ToArray()

member _.AsStream() = bytes.AsReadOnlyStream()

type ByteMemory with

member x.AsReadOnly() = ReadOnlyByteMemory x

static member CreateMemoryMappedFile(bytes: ReadOnlyByteMemory) =
let length = int64 bytes.Length
let mmf =
let mmf =
MemoryMappedFile.CreateNew(
null,
length,
MemoryMappedFileAccess.ReadWrite,
MemoryMappedFileOptions.None,
HandleInheritability.None)
use stream = mmf.CreateViewStream(0L, length, MemoryMappedFileAccess.ReadWrite)
bytes.CopyTo stream
mmf

let accessor = mmf.CreateViewAccessor(0L, length, MemoryMappedFileAccess.ReadWrite)

let safeHolder =
{ new obj() with
override x.Finalize() =
(x :?> IDisposable).Dispose()
interface IDisposable with
member x.Dispose() =
GC.SuppressFinalize x
accessor.Dispose()
mmf.Dispose() }
RawByteMemory.FromUnsafePointer(accessor.SafeMemoryMappedViewHandle.DangerousGetHandle(), int length, safeHolder)

static member FromFile(path, access, ?canShadowCopy: bool) =
let canShadowCopy = defaultArg canShadowCopy false

let memoryMappedFileAccess =
match access with
| FileAccess.Read -> MemoryMappedFileAccess.Read
| FileAccess.Write -> MemoryMappedFileAccess.Write
| _ -> MemoryMappedFileAccess.ReadWrite

let mmf, accessor, length =
let fileStream = File.Open(path, FileMode.Open, access, FileShare.Read)
let length = fileStream.Length
let mmf =
if canShadowCopy then
let mmf =
MemoryMappedFile.CreateNew(
null,
length,
MemoryMappedFileAccess.ReadWrite,
MemoryMappedFileOptions.None,
HandleInheritability.None)
use stream = mmf.CreateViewStream(0L, length, MemoryMappedFileAccess.ReadWrite)
fileStream.CopyTo(stream)
fileStream.Dispose()
mmf
else
MemoryMappedFile.CreateFromFile(
fileStream,
null,
length,
memoryMappedFileAccess,
HandleInheritability.None,
leaveOpen=false)
mmf, mmf.CreateViewAccessor(0L, length, memoryMappedFileAccess), length

match access with
| FileAccess.Read when not accessor.CanRead -> failwith "Cannot read file"
| FileAccess.Write when not accessor.CanWrite -> failwith "Cannot write file"
| _ when not accessor.CanRead || not accessor.CanWrite -> failwith "Cannot read or write file"
| _ -> ()

let safeHolder =
{ new obj() with
override x.Finalize() =
(x :?> IDisposable).Dispose()
interface IDisposable with
member x.Dispose() =
GC.SuppressFinalize x
accessor.Dispose()
mmf.Dispose() }
RawByteMemory.FromUnsafePointer(accessor.SafeMemoryMappedViewHandle.DangerousGetHandle(), int length, safeHolder)

static member FromUnsafePointer(addr, length, hold: obj) =
RawByteMemory(NativePtr.ofNativeInt addr, length, hold) :> ByteMemory

static member FromArray(bytes, offset, length) =
ByteArrayMemory(bytes, offset, length) :> ByteMemory

static member FromArray bytes =
ByteArrayMemory.FromArray(bytes, 0, bytes.Length)

type internal ByteStream =
{ bytes: byte[]
{ bytes: ReadOnlyByteMemory
mutable pos: int
max: int }
member b.ReadByte() =
Expand All @@ -38,18 +325,18 @@ type internal ByteStream =
b.pos <- b.pos + 1
res
member b.ReadUtf8String n =
let res = System.Text.Encoding.UTF8.GetString(b.bytes,b.pos,n)
let res = b.bytes.ReadUtf8String(b.pos,n)
b.pos <- b.pos + n; res

static member FromBytes (b:byte[],n,len) =
static member FromBytes (b: ReadOnlyByteMemory,n,len) =
if n < 0 || (n+len) > b.Length then failwith "FromBytes"
{ bytes = b; pos = n; max = n+len }

member b.ReadBytes n =
if b.pos + n > b.max then failwith "ReadBytes: end of stream"
let res = Bytes.sub b.bytes b.pos n
let res = b.bytes.Slice(b.pos, n)
b.pos <- b.pos + n
res
res

member b.Position = b.pos
#if LAZY_UNPICKLE
Expand Down Expand Up @@ -108,6 +395,13 @@ type internal ByteBuffer =
Bytes.blit i 0 buf.bbArray buf.bbCurrent n
buf.bbCurrent <- newSize

member buf.EmitByteMemory (i:ReadOnlyByteMemory) =
let n = i.Length
let newSize = buf.bbCurrent + n
buf.Ensure newSize
i.Copy(0, buf.bbArray, buf.bbCurrent, n)
buf.bbCurrent <- newSize

member buf.EmitInt32AsUInt16 n =
let newSize = buf.bbCurrent + 2
buf.Ensure newSize
Expand Down
Loading