From cb1434524c3f4c627a1da4a454bf45b5a18b18a0 Mon Sep 17 00:00:00 2001 From: Lilith Orion Hafner Date: Fri, 29 Mar 2024 10:47:38 -0500 Subject: [PATCH 01/13] make reshape and view on Memory produce Arrays --- base/genericmemory.jl | 19 +++++++++++++++++++ test/arrayops.jl | 28 ++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/base/genericmemory.jl b/base/genericmemory.jl index b5f519a0f854d..5482b57040404 100644 --- a/base/genericmemory.jl +++ b/base/genericmemory.jl @@ -288,3 +288,22 @@ function indcopy(sz::Dims, I::GenericMemory) src = eltype(I)[I[i][_findin(I[i], i < n ? (1:sz[i]) : (1:s))] for i = 1:n] dst, src end + +# Wrapping a memory region in an Array + +# TODO: delete wrap +# TODO: maybe delete jl_genericmemory_slice + +@eval function reshape(m::GenericMemory{M, T}, dims::Vararg{Int, N}) where {M, T, N} + len = Core.checked_dims(dims...) + length(m) == len || throw(DimensionMismatch("parent has $(length(m)) elements, which is incompatible with size $(dims)")) + ref = MemoryRef(m) + $(Expr(:new, :(Array{T, N}), :ref, :dims)) +end + +@eval function view(m::GenericMemory{M, T}, inds::AbstractUnitRange) where {M, T} + @boundscheck checkbounds(m, inds) + ref = @inbounds MemoryRef(m, first(inds)) # @inbounds is needed to allow view(Memory{T}(undef, 0), 1:0) + dims = (length(inds),) + $(Expr(:new, :(Array{T, 1}), :ref, :dims)) +end diff --git a/test/arrayops.jl b/test/arrayops.jl index 566dd44b8dcd9..5d468ad199a57 100644 --- a/test/arrayops.jl +++ b/test/arrayops.jl @@ -3206,6 +3206,34 @@ end @test_throws DimensionMismatch wrap(Array, memref2, 10) end +@testset "Wrapping Memory into Arrays with view and reshape" begin + mem::Memory{Int} = Memory{Int}(undef, 10) .= 11:20 + + @test_throws DimensionMismatch reshape(mem, 10, 10) + @test_throws DimensionMismatch reshape(mem, 5) + @test_throws BoundsError view(mem, 1:10, 1:10) + @test_throws BoundsError view(mem, 1:11) + @test_throws BoundsError view(mem, 3:11) + @test_throws BoundsError view(mem, 0:4) + + @test view(mem, 1:5)::Vector{Int} == 11:15 + @test view(mem, 1:2)::Vector{Int} == 11:12 + @test view(mem, 1:10)::Vector{Int} == 11:20 + @test view(mem, 3:8)::Vector{Int} == 13:18 + @test reshape(mem, 5, 2)::Matrix{Int} == reshape(11:20, 5, 2) + + empty_mem = Memory{Module}(undef, 0) + @test_throws BoundsError view(empty_mem, 0:1) + @test_throws BoundsError view(empty_mem, 1:2) + @test_throws DimensionMismatch reshape(empty_mem, 1) + @test_throws DimensionMismatch reshape(empty_mem, 1, 2, 3) + @test_throws ArgumentError reshape(empty_mem, 2^16, 2^16, 2^16, 2^16) + + @test view(empty_mem, 1:0)::Vector{Module} == [] + @test view(empty_mem, 10:3)::Vector{Module} == [] + @test isempty(reshape(empty_mem, 0, 7, 1)::Array{Module, 3}) +end + @testset "Memory size" begin len = 5 mem = Memory{Int}(undef, len) From 873500957b3149c8a6ba20d0c9172438807dd421 Mon Sep 17 00:00:00 2001 From: Lilith Orion Hafner Date: Fri, 29 Mar 2024 11:35:24 -0500 Subject: [PATCH 02/13] delete wrap --- HISTORY.md | 1 - base/array.jl | 51 ------------------------------------------ base/exports.jl | 1 - base/genericmemory.jl | 1 - base/iobuffer.jl | 12 ++++------ base/strings/string.jl | 5 ++++- doc/src/base/arrays.md | 1 - test/arrayops.jl | 19 ---------------- 8 files changed, 8 insertions(+), 83 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 3eaec3c6d0774..55bbfa1335a0e 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -86,7 +86,6 @@ New library functions * `copyuntil(out, io, delim)` and `copyline(out, io)` copy data into an `out::IO` stream ([#48273]). * `eachrsplit(string, pattern)` iterates split substrings right to left. * `Sys.username()` can be used to return the current user's username ([#51897]). -* `wrap(Array, m::Union{MemoryRef{T}, Memory{T}}, dims)` is the safe counterpart to `unsafe_wrap` ([#52049]). * `GC.logging_enabled()` can be used to test whether GC logging has been enabled via `GC.enable_logging` ([#51647]). * `IdSet` is now exported from Base and considered public ([#53262]). diff --git a/base/array.jl b/base/array.jl index ef91077dd6e96..7676b380923ee 100644 --- a/base/array.jl +++ b/base/array.jl @@ -3080,54 +3080,3 @@ intersect(r::AbstractRange, v::AbstractVector) = intersect(v, r) _getindex(v, i) end end - -""" - wrap(Array, m::Union{Memory{T}, MemoryRef{T}}, dims) - -Create an array of size `dims` using `m` as the underlying memory. This can be thought of as a safe version -of [`unsafe_wrap`](@ref) utilizing `Memory` or `MemoryRef` instead of raw pointers. -""" -function wrap end - -# validity checking for _wrap calls, separate from allocation of Array so that it can be more likely to inline into the caller -function _wrap(ref::MemoryRef{T}, dims::NTuple{N, Int}) where {T, N} - mem = ref.mem - mem_len = length(mem) + 1 - memoryrefoffset(ref) - len = Core.checked_dims(dims...) - @boundscheck mem_len >= len || invalid_wrap_err(mem_len, dims, len) - if N != 1 && !(ref === GenericMemoryRef(mem) && len === mem_len) - mem = ccall(:jl_genericmemory_slice, Memory{T}, (Any, Ptr{Cvoid}, Int), mem, ref.ptr_or_offset, len) - ref = MemoryRef(mem) - end - return ref -end - -@noinline invalid_wrap_err(len, dims, proddims) = throw(DimensionMismatch( - "Attempted to wrap a MemoryRef of length $len with an Array of size dims=$dims, which is invalid because prod(dims) = $proddims > $len, so that the array would have more elements than the underlying memory can store.")) - -@eval @propagate_inbounds function wrap(::Type{Array}, m::MemoryRef{T}, dims::NTuple{N, Integer}) where {T, N} - dims = convert(Dims, dims) - ref = _wrap(m, dims) - $(Expr(:new, :(Array{T, N}), :ref, :dims)) -end - -@eval @propagate_inbounds function wrap(::Type{Array}, m::Memory{T}, dims::NTuple{N, Integer}) where {T, N} - dims = convert(Dims, dims) - ref = _wrap(MemoryRef(m), dims) - $(Expr(:new, :(Array{T, N}), :ref, :dims)) -end -@eval @propagate_inbounds function wrap(::Type{Array}, m::MemoryRef{T}, l::Integer) where {T} - dims = (Int(l),) - ref = _wrap(m, dims) - $(Expr(:new, :(Array{T, 1}), :ref, :dims)) -end -@eval @propagate_inbounds function wrap(::Type{Array}, m::Memory{T}, l::Integer) where {T} - dims = (Int(l),) - ref = _wrap(MemoryRef(m), (l,)) - $(Expr(:new, :(Array{T, 1}), :ref, :dims)) -end -@eval @propagate_inbounds function wrap(::Type{Array}, m::Memory{T}) where {T} - ref = MemoryRef(m) - dims = (length(m),) - $(Expr(:new, :(Array{T, 1}), :ref, :dims)) -end diff --git a/base/exports.jl b/base/exports.jl index 3044f9d162d41..fc2ee86a8d0d4 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -460,7 +460,6 @@ export vcat, vec, view, - wrap, zeros, # search, find, match and related functions diff --git a/base/genericmemory.jl b/base/genericmemory.jl index 5482b57040404..168a9f5a7b620 100644 --- a/base/genericmemory.jl +++ b/base/genericmemory.jl @@ -291,7 +291,6 @@ end # Wrapping a memory region in an Array -# TODO: delete wrap # TODO: maybe delete jl_genericmemory_slice @eval function reshape(m::GenericMemory{M, T}, dims::Vararg{Int, N}) where {M, T, N} diff --git a/base/iobuffer.jl b/base/iobuffer.jl index dadb13e1f1e6a..da8a8e351c72e 100644 --- a/base/iobuffer.jl +++ b/base/iobuffer.jl @@ -42,7 +42,7 @@ end # allocate Vector{UInt8}s for IOBuffer storage that can efficiently become Strings StringMemory(n::Integer) = unsafe_wrap(Memory{UInt8}, _string_n(n)) -StringVector(n::Integer) = wrap(Array, StringMemory(n)) +StringVector(n::Integer) = view(StringMemory(n), 1:n) # IOBuffers behave like Files. They are typically readable and writable. They are seekable. (They can be appendable). @@ -456,7 +456,7 @@ function take!(io::IOBuffer) if nbytes == 0 || io.reinit data = StringVector(0) elseif io.writable - data = wrap(Array, MemoryRef(io.data, io.offset + 1), nbytes) + data = view(io.data, io.offset+1:nbytes+io.offset+1) else data = copyto!(StringVector(io.size), 1, io.data, io.offset + 1, nbytes) end @@ -465,7 +465,7 @@ function take!(io::IOBuffer) if nbytes == 0 data = StringVector(0) elseif io.writable - data = wrap(Array, MemoryRef(io.data, io.ptr), nbytes) + data = view(io.data, io.ptr:io.ptr+nbytes) else data = read!(io, data) end @@ -491,11 +491,7 @@ state. This should only be used internally for performance-critical It might save an allocation compared to `take!` (if the compiler elides the Array allocation), as well as omits some checks. """ -_unsafe_take!(io::IOBuffer) = - wrap(Array, io.size == io.offset ? - MemoryRef(Memory{UInt8}()) : - MemoryRef(io.data, io.offset + 1), - io.size - io.offset) +_unsafe_take!(io::IOBuffer) = view(io.data, io.offset+1:io.size) function write(to::IO, from::GenericIOBuffer) written::Int = bytesavailable(from) diff --git a/base/strings/string.jl b/base/strings/string.jl index b2afce897a937..d091baeb6c663 100644 --- a/base/strings/string.jl +++ b/base/strings/string.jl @@ -117,7 +117,10 @@ String(s::AbstractString) = print_to_string(s) @assume_effects :total String(s::Symbol) = unsafe_string(unsafe_convert(Ptr{UInt8}, s)) unsafe_wrap(::Type{Memory{UInt8}}, s::String) = ccall(:jl_string_to_genericmemory, Ref{Memory{UInt8}}, (Any,), s) -unsafe_wrap(::Type{Vector{UInt8}}, s::String) = wrap(Array, unsafe_wrap(Memory{UInt8}, s)) +function unsafe_wrap(::Type{Vector{UInt8}}, s::String) + mem = unsafe_wrap(Memory{UInt8}, s) + view(mem, eachindex(mem)) +end Vector{UInt8}(s::CodeUnits{UInt8,String}) = copyto!(Vector{UInt8}(undef, length(s)), s) Vector{UInt8}(s::String) = Vector{UInt8}(codeunits(s)) diff --git a/doc/src/base/arrays.md b/doc/src/base/arrays.md index f7ea23ad3b556..20e8e81614b9e 100644 --- a/doc/src/base/arrays.md +++ b/doc/src/base/arrays.md @@ -139,7 +139,6 @@ Base.reshape Base.dropdims Base.vec Base.SubArray -Base.wrap ``` ## Concatenation and permutation diff --git a/test/arrayops.jl b/test/arrayops.jl index 5d468ad199a57..3cf8ef22365d7 100644 --- a/test/arrayops.jl +++ b/test/arrayops.jl @@ -3187,25 +3187,6 @@ end end end -@testset "Wrapping Memory into Arrays" begin - mem = Memory{Int}(undef, 10) .= 1 - memref = MemoryRef(mem) - @test_throws DimensionMismatch wrap(Array, mem, (10, 10)) - @test wrap(Array, mem, (5,)) == ones(Int, 5) - @test wrap(Array, mem, 2) == ones(Int, 2) - @test wrap(Array, memref, 10) == ones(Int, 10) - @test wrap(Array, memref, (2,2,2)) == ones(Int,2,2,2) - @test wrap(Array, mem, (5, 2)) == ones(Int, 5, 2) - - memref2 = MemoryRef(mem, 3) - @test wrap(Array, memref2, (5,)) == ones(Int, 5) - @test wrap(Array, memref2, 2) == ones(Int, 2) - @test wrap(Array, memref2, (2,2,2)) == ones(Int,2,2,2) - @test wrap(Array, memref2, (3, 2)) == ones(Int, 3, 2) - @test_throws DimensionMismatch wrap(Array, memref2, 9) - @test_throws DimensionMismatch wrap(Array, memref2, 10) -end - @testset "Wrapping Memory into Arrays with view and reshape" begin mem::Memory{Int} = Memory{Int}(undef, 10) .= 11:20 From c2b2a27aaef7bc9f55441c34a261888d8ffa8c07 Mon Sep 17 00:00:00 2001 From: Lilith Orion Hafner Date: Fri, 29 Mar 2024 11:35:31 -0500 Subject: [PATCH 03/13] add another test --- test/arrayops.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/arrayops.jl b/test/arrayops.jl index 3cf8ef22365d7..9adc1cecc8b32 100644 --- a/test/arrayops.jl +++ b/test/arrayops.jl @@ -3201,6 +3201,8 @@ end @test view(mem, 1:2)::Vector{Int} == 11:12 @test view(mem, 1:10)::Vector{Int} == 11:20 @test view(mem, 3:8)::Vector{Int} == 13:18 + @test view(mem, 20:19)::Vector{Int} == [] + @test view(mem, -5:-7)::Vector{Int} == [] @test reshape(mem, 5, 2)::Matrix{Int} == reshape(11:20, 5, 2) empty_mem = Memory{Module}(undef, 0) From 664335c5a19c93b3976a7f5c586dc5843ad6530e Mon Sep 17 00:00:00 2001 From: Lilith Orion Hafner Date: Fri, 29 Mar 2024 11:36:44 -0500 Subject: [PATCH 04/13] accept that we need to keep jl_genericmemory_slice because we still have reshape(view(::Memory, ...), ...) --- base/genericmemory.jl | 3 --- 1 file changed, 3 deletions(-) diff --git a/base/genericmemory.jl b/base/genericmemory.jl index 168a9f5a7b620..87dc3fa754409 100644 --- a/base/genericmemory.jl +++ b/base/genericmemory.jl @@ -290,9 +290,6 @@ function indcopy(sz::Dims, I::GenericMemory) end # Wrapping a memory region in an Array - -# TODO: maybe delete jl_genericmemory_slice - @eval function reshape(m::GenericMemory{M, T}, dims::Vararg{Int, N}) where {M, T, N} len = Core.checked_dims(dims...) length(m) == len || throw(DimensionMismatch("parent has $(length(m)) elements, which is incompatible with size $(dims)")) From bc6f772ba81f002f622b20eb6353786eb47d2449 Mon Sep 17 00:00:00 2001 From: Lilith Orion Hafner Date: Sat, 30 Mar 2024 09:27:10 -0500 Subject: [PATCH 05/13] replace `@inbounds` with branch --- base/genericmemory.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/base/genericmemory.jl b/base/genericmemory.jl index 87dc3fa754409..f29beccc461c6 100644 --- a/base/genericmemory.jl +++ b/base/genericmemory.jl @@ -298,8 +298,9 @@ end end @eval function view(m::GenericMemory{M, T}, inds::AbstractUnitRange) where {M, T} + isempty(inds) && return T[] # needed to allow view(Memory{T}(undef, 0), 2:1) @boundscheck checkbounds(m, inds) - ref = @inbounds MemoryRef(m, first(inds)) # @inbounds is needed to allow view(Memory{T}(undef, 0), 1:0) + ref = MemoryRef(m, first(inds)) # @inbounds here is not safe on view(Memory{T}(undef, 0), 2:1) dims = (length(inds),) $(Expr(:new, :(Array{T, 1}), :ref, :dims)) end From 91542bc7a12e8f28f665aa16cad829818971df36 Mon Sep 17 00:00:00 2001 From: Lilith Orion Hafner Date: Sat, 30 Mar 2024 09:48:55 -0500 Subject: [PATCH 06/13] restrict to known non-offset-indexed inds --- base/genericmemory.jl | 4 ++-- test/arrayops.jl | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/base/genericmemory.jl b/base/genericmemory.jl index f29beccc461c6..fe4dda089d515 100644 --- a/base/genericmemory.jl +++ b/base/genericmemory.jl @@ -297,8 +297,8 @@ end $(Expr(:new, :(Array{T, N}), :ref, :dims)) end -@eval function view(m::GenericMemory{M, T}, inds::AbstractUnitRange) where {M, T} - isempty(inds) && return T[] # needed to allow view(Memory{T}(undef, 0), 2:1) +@eval function view(m::GenericMemory{M, T}, inds::Union{UnitRange, OneTo}) where {M, T} + !(inds isa OneTo) && isempty(inds) && return T[] # needed to allow view(Memory{T}(undef, 0), 2:1) @boundscheck checkbounds(m, inds) ref = MemoryRef(m, first(inds)) # @inbounds here is not safe on view(Memory{T}(undef, 0), 2:1) dims = (length(inds),) diff --git a/test/arrayops.jl b/test/arrayops.jl index 9adc1cecc8b32..70e8dfa1c493d 100644 --- a/test/arrayops.jl +++ b/test/arrayops.jl @@ -3215,6 +3215,9 @@ end @test view(empty_mem, 1:0)::Vector{Module} == [] @test view(empty_mem, 10:3)::Vector{Module} == [] @test isempty(reshape(empty_mem, 0, 7, 1)::Array{Module, 3}) + + offset_inds = OffsetArrays.IdOffsetRange(values=3:6, indices=53:56) + @test view(collect(mem), offset_inds) == view(mem, offset_inds) end @testset "Memory size" begin From 64142808249c3ed88cdf6d10d937d2f74be817b5 Mon Sep 17 00:00:00 2001 From: Lilith Orion Hafner Date: Mon, 1 Apr 2024 20:32:07 +0000 Subject: [PATCH 07/13] Apply suggestions from code review Thanks @cafaxo --- base/iobuffer.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/iobuffer.jl b/base/iobuffer.jl index da8a8e351c72e..7cf0d80c185e5 100644 --- a/base/iobuffer.jl +++ b/base/iobuffer.jl @@ -456,7 +456,7 @@ function take!(io::IOBuffer) if nbytes == 0 || io.reinit data = StringVector(0) elseif io.writable - data = view(io.data, io.offset+1:nbytes+io.offset+1) + data = view(io.data, io.offset+1:nbytes+io.offset) else data = copyto!(StringVector(io.size), 1, io.data, io.offset + 1, nbytes) end @@ -465,7 +465,7 @@ function take!(io::IOBuffer) if nbytes == 0 data = StringVector(0) elseif io.writable - data = view(io.data, io.ptr:io.ptr+nbytes) + data = view(io.data, io.ptr:io.ptr+nbytes-1) else data = read!(io, data) end From 9a713f41e011bf7582d16855d0d40dbf8ab02ed0 Mon Sep 17 00:00:00 2001 From: Lilith Orion Hafner Date: Tue, 2 Apr 2024 13:12:23 -0500 Subject: [PATCH 08/13] Update base/genericmemory.jl Co-authored-by: Jameson Nash --- base/genericmemory.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/genericmemory.jl b/base/genericmemory.jl index fe4dda089d515..bd0a7d9252255 100644 --- a/base/genericmemory.jl +++ b/base/genericmemory.jl @@ -298,7 +298,7 @@ end end @eval function view(m::GenericMemory{M, T}, inds::Union{UnitRange, OneTo}) where {M, T} - !(inds isa OneTo) && isempty(inds) && return T[] # needed to allow view(Memory{T}(undef, 0), 2:1) + isempty(inds) && return T[] # needed to allow view(Memory{T}(undef, 0), 2:1) @boundscheck checkbounds(m, inds) ref = MemoryRef(m, first(inds)) # @inbounds here is not safe on view(Memory{T}(undef, 0), 2:1) dims = (length(inds),) From f16ce690a33a104b75f6c997bf9aadcfa505e5b0 Mon Sep 17 00:00:00 2001 From: Lilith Orion Hafner Date: Tue, 2 Apr 2024 13:13:41 -0500 Subject: [PATCH 09/13] Update comment --- base/genericmemory.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/genericmemory.jl b/base/genericmemory.jl index bd0a7d9252255..5bf07c829ae2d 100644 --- a/base/genericmemory.jl +++ b/base/genericmemory.jl @@ -300,7 +300,7 @@ end @eval function view(m::GenericMemory{M, T}, inds::Union{UnitRange, OneTo}) where {M, T} isempty(inds) && return T[] # needed to allow view(Memory{T}(undef, 0), 2:1) @boundscheck checkbounds(m, inds) - ref = MemoryRef(m, first(inds)) # @inbounds here is not safe on view(Memory{T}(undef, 0), 2:1) + ref = MemoryRef(m, first(inds)) # @inbounds would be safe here but does not help performance. dims = (length(inds),) $(Expr(:new, :(Array{T, 1}), :ref, :dims)) end From c1586aaab1a8114d22d35e1e3753ec7987e5e298 Mon Sep 17 00:00:00 2001 From: Lilith Orion Hafner Date: Thu, 4 Apr 2024 13:53:40 -0500 Subject: [PATCH 10/13] add specific method docstring with warning --- base/genericmemory.jl | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/base/genericmemory.jl b/base/genericmemory.jl index 5bf07c829ae2d..09e54f53937fa 100644 --- a/base/genericmemory.jl +++ b/base/genericmemory.jl @@ -297,6 +297,12 @@ end $(Expr(:new, :(Array{T, N}), :ref, :dims)) end +""" + view(m::GenericMemory{M, T}, inds::Union{UnitRange, OneTo}) + +Create a vector `v::Vector{T}` backed by the specified indicies of `m`. It is only safe to +resize `v` if `m` is subseqently not used. +""" @eval function view(m::GenericMemory{M, T}, inds::Union{UnitRange, OneTo}) where {M, T} isempty(inds) && return T[] # needed to allow view(Memory{T}(undef, 0), 2:1) @boundscheck checkbounds(m, inds) From fb7cf4fb92f419fe559ae19fe6f5fe265d8381d7 Mon Sep 17 00:00:00 2001 From: Lilith Orion Hafner Date: Fri, 5 Apr 2024 11:42:54 +0000 Subject: [PATCH 11/13] Fix `@eval`/docstring interactions --- base/genericmemory.jl | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/base/genericmemory.jl b/base/genericmemory.jl index 09e54f53937fa..a6f002b9c6175 100644 --- a/base/genericmemory.jl +++ b/base/genericmemory.jl @@ -290,23 +290,25 @@ function indcopy(sz::Dims, I::GenericMemory) end # Wrapping a memory region in an Array -@eval function reshape(m::GenericMemory{M, T}, dims::Vararg{Int, N}) where {M, T, N} - len = Core.checked_dims(dims...) - length(m) == len || throw(DimensionMismatch("parent has $(length(m)) elements, which is incompatible with size $(dims)")) - ref = MemoryRef(m) - $(Expr(:new, :(Array{T, N}), :ref, :dims)) -end - -""" - view(m::GenericMemory{M, T}, inds::Union{UnitRange, OneTo}) +@eval begin # @eval for the Array construction. Block for the docstring. + function reshape(m::GenericMemory{M, T}, dims::Vararg{Int, N}) where {M, T, N} + len = Core.checked_dims(dims...) + length(m) == len || throw(DimensionMismatch("parent has $(length(m)) elements, which is incompatible with size $(dims)")) + ref = MemoryRef(m) + $(Expr(:new, :(Array{T, N}), :ref, :dims)) + end -Create a vector `v::Vector{T}` backed by the specified indicies of `m`. It is only safe to -resize `v` if `m` is subseqently not used. -""" -@eval function view(m::GenericMemory{M, T}, inds::Union{UnitRange, OneTo}) where {M, T} - isempty(inds) && return T[] # needed to allow view(Memory{T}(undef, 0), 2:1) - @boundscheck checkbounds(m, inds) - ref = MemoryRef(m, first(inds)) # @inbounds would be safe here but does not help performance. - dims = (length(inds),) - $(Expr(:new, :(Array{T, 1}), :ref, :dims)) + """ + view(m::GenericMemory{M, T}, inds::Union{UnitRange, OneTo}) + + Create a vector `v::Vector{T}` backed by the specified indices of `m`. It is only safe to + resize `v` if `m` is subseqently not used. + """ + function view(m::GenericMemory{M, T}, inds::Union{UnitRange, OneTo}) where {M, T} + isempty(inds) && return T[] # needed to allow view(Memory{T}(undef, 0), 2:1) + @boundscheck checkbounds(m, inds) + ref = MemoryRef(m, first(inds)) # @inbounds would be safe here but does not help performance. + dims = (length(inds),) + $(Expr(:new, :(Array{T, 1}), :ref, :dims)) + end end From 384f197b8cafba1698ca14a59aaff7dbc12098e9 Mon Sep 17 00:00:00 2001 From: Lilith Orion Hafner Date: Fri, 5 Apr 2024 15:27:19 -0500 Subject: [PATCH 12/13] add type annotation --- base/iobuffer.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/iobuffer.jl b/base/iobuffer.jl index 7cf0d80c185e5..109eaa96a3de4 100644 --- a/base/iobuffer.jl +++ b/base/iobuffer.jl @@ -42,7 +42,7 @@ end # allocate Vector{UInt8}s for IOBuffer storage that can efficiently become Strings StringMemory(n::Integer) = unsafe_wrap(Memory{UInt8}, _string_n(n)) -StringVector(n::Integer) = view(StringMemory(n), 1:n) +StringVector(n::Integer) = view(StringMemory(n), 1:n)::Vector{UInt8} # IOBuffers behave like Files. They are typically readable and writable. They are seekable. (They can be appendable). From 700ec3e6fa03210bf9ff03904901d13b09019fa6 Mon Sep 17 00:00:00 2001 From: Lilith Orion Hafner Date: Fri, 5 Apr 2024 16:06:42 -0500 Subject: [PATCH 13/13] add inference tests --- test/arrayops.jl | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/test/arrayops.jl b/test/arrayops.jl index 70e8dfa1c493d..65a9ecbd5e2a2 100644 --- a/test/arrayops.jl +++ b/test/arrayops.jl @@ -3197,13 +3197,13 @@ end @test_throws BoundsError view(mem, 3:11) @test_throws BoundsError view(mem, 0:4) - @test view(mem, 1:5)::Vector{Int} == 11:15 - @test view(mem, 1:2)::Vector{Int} == 11:12 - @test view(mem, 1:10)::Vector{Int} == 11:20 - @test view(mem, 3:8)::Vector{Int} == 13:18 - @test view(mem, 20:19)::Vector{Int} == [] - @test view(mem, -5:-7)::Vector{Int} == [] - @test reshape(mem, 5, 2)::Matrix{Int} == reshape(11:20, 5, 2) + @test @inferred(view(mem, 1:5))::Vector{Int} == 11:15 + @test @inferred(view(mem, 1:2))::Vector{Int} == 11:12 + @test @inferred(view(mem, 1:10))::Vector{Int} == 11:20 + @test @inferred(view(mem, 3:8))::Vector{Int} == 13:18 + @test @inferred(view(mem, 20:19))::Vector{Int} == [] + @test @inferred(view(mem, -5:-7))::Vector{Int} == [] + @test @inferred(reshape(mem, 5, 2))::Matrix{Int} == reshape(11:20, 5, 2) empty_mem = Memory{Module}(undef, 0) @test_throws BoundsError view(empty_mem, 0:1) @@ -3212,12 +3212,12 @@ end @test_throws DimensionMismatch reshape(empty_mem, 1, 2, 3) @test_throws ArgumentError reshape(empty_mem, 2^16, 2^16, 2^16, 2^16) - @test view(empty_mem, 1:0)::Vector{Module} == [] - @test view(empty_mem, 10:3)::Vector{Module} == [] - @test isempty(reshape(empty_mem, 0, 7, 1)::Array{Module, 3}) + @test @inferred(view(empty_mem, 1:0))::Vector{Module} == [] + @test @inferred(view(empty_mem, 10:3))::Vector{Module} == [] + @test isempty(@inferred(reshape(empty_mem, 0, 7, 1))::Array{Module, 3}) offset_inds = OffsetArrays.IdOffsetRange(values=3:6, indices=53:56) - @test view(collect(mem), offset_inds) == view(mem, offset_inds) + @test @inferred(view(collect(mem), offset_inds)) == view(mem, offset_inds) end @testset "Memory size" begin