Skip to content

Commit

Permalink
Fixed LOH byte array allocations in the IDE from metadata (dotnet#7971)
Browse files Browse the repository at this point in the history
* Fixed LOH byte array allocations in the IDE. Unified memory handling for IL and FSharp metadata.

* minor cleanup

* minor cleanup

* rename EmitBytes to EmitByteMemory

* Fixed ByteArrayMemory

* fixing build

* Fixing build again

* Better ReadInt32/ReadUInt16

* ILResourceLocation.Local should use a ReadOnlyByteMemory

* Able to have a ReadOnlyStream

* Using OutOfRangeException according to feedback
  • Loading branch information
TIHan authored and nosami committed Feb 22, 2021
1 parent 9f26d11 commit d071e26
Show file tree
Hide file tree
Showing 15 changed files with 449 additions and 168 deletions.
302 changes: 296 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,286 @@ 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 length <= 0 || length > bytes.Length then
raise (ArgumentOutOfRangeException("length"))

if offset < 0 || (offset + length) > bytes.Length then
raise (ArgumentOutOfRangeException("offset"))

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
raise (ArgumentOutOfRangeException("i"))

do
if length <= 0 then
raise (ArgumentOutOfRangeException("length"))

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()

[<MethodImpl(MethodImplOptions.AggressiveInlining)>]
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 +321,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 +391,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

0 comments on commit d071e26

Please sign in to comment.