From 8d41d25e69ff7419f7b11c8d105884fa2edea925 Mon Sep 17 00:00:00 2001 From: Kristoffer Carlsson Date: Wed, 31 Jul 2024 12:54:51 +0200 Subject: [PATCH] Revert changing `wrap` on `Memory` to `view` and make `wrap` non-public. (#54927) Fixes https://github.com/JuliaLang/julia/issues/54768 --- base/array.jl | 53 ++++++++++++++++++++++++++++++++++++++++++ base/genericmemory.jl | 25 -------------------- base/iobuffer.jl | 12 ++++++---- base/strings/string.jl | 5 +--- test/arrayops.jl | 53 ++++++++++++++---------------------------- test/core.jl | 3 --- 6 files changed, 79 insertions(+), 72 deletions(-) diff --git a/base/array.jl b/base/array.jl index 32c543ff12638..008a52abb952e 100644 --- a/base/array.jl +++ b/base/array.jl @@ -3064,3 +3064,56 @@ 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(LazyString( + "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/genericmemory.jl b/base/genericmemory.jl index 32c15a22e0db1..6df6c880f74a8 100644 --- a/base/genericmemory.jl +++ b/base/genericmemory.jl @@ -316,31 +316,6 @@ function indcopy(sz::Dims, I::GenericMemory) dst, src end -# Wrapping a memory region in an Array -@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 - - """ - 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 = (Int(length(inds)),) - $(Expr(:new, :(Array{T, 1}), :ref, :dims)) - end -end -view(m::GenericMemory, inds::Colon) = view(m, eachindex(m)) - # get, set(once), modify, swap and replace at index, atomically function getindex_atomic(mem::GenericMemory, order::Symbol, i::Int) memref = memoryref(mem, i) diff --git a/base/iobuffer.jl b/base/iobuffer.jl index 04a694a4fec15..c0c2731eec08b 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)::Vector{UInt8} +StringVector(n::Integer) = wrap(Array, StringMemory(n)) # IOBuffers behave like Files. They are typically readable and writable. They are seekable. (They can be appendable). @@ -466,7 +466,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) + data = wrap(Array, memoryref(io.data, io.offset + 1), nbytes) else data = copyto!(StringVector(nbytes), 1, io.data, io.offset + 1, nbytes) end @@ -475,7 +475,7 @@ function take!(io::IOBuffer) if nbytes == 0 data = StringVector(0) elseif io.writable - data = view(io.data, io.ptr:io.ptr+nbytes-1) + data = wrap(Array, memoryref(io.data, io.ptr), nbytes) else data = read!(io, data) end @@ -501,7 +501,11 @@ 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) = view(io.data, io.offset+1:io.size) +_unsafe_take!(io::IOBuffer) = + wrap(Array, io.size == io.offset ? + memoryref(Memory{UInt8}()) : + memoryref(io.data, io.offset + 1), + io.size - io.offset) function write(to::IO, from::GenericIOBuffer) written::Int = bytesavailable(from) diff --git a/base/strings/string.jl b/base/strings/string.jl index 7917f463771b2..89e2ff288c3d7 100644 --- a/base/strings/string.jl +++ b/base/strings/string.jl @@ -115,10 +115,7 @@ 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) -function unsafe_wrap(::Type{Vector{UInt8}}, s::String) - mem = unsafe_wrap(Memory{UInt8}, s) - view(mem, eachindex(mem)) -end +unsafe_wrap(::Type{Vector{UInt8}}, s::String) = wrap(Array, unsafe_wrap(Memory{UInt8}, s)) Vector{UInt8}(s::CodeUnits{UInt8,String}) = copyto!(Vector{UInt8}(undef, length(s)), s) Vector{UInt8}(s::String) = Vector{UInt8}(codeunits(s)) diff --git a/test/arrayops.jl b/test/arrayops.jl index f4bb2dc7372f8..e407ad7233bc2 100644 --- a/test/arrayops.jl +++ b/test/arrayops.jl @@ -3219,42 +3219,23 @@ end end 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 @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(view(mem, :))::Vector{Int} == mem - @test @inferred(reshape(mem, 5, 2))::Matrix{Int} == reshape(11:20, 5, 2) - - # 53990 - @test @inferred(view(mem, unsigned(1):10))::Vector{Int} == 11:20 - - 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 @inferred(view(empty_mem, 1:0))::Vector{Module} == [] - @test @inferred(view(empty_mem, 10:3))::Vector{Module} == [] - @test @inferred(view(empty_mem, :))::Vector{Module} == empty_mem - @test isempty(@inferred(reshape(empty_mem, 0, 7, 1))::Array{Module, 3}) - - offset_inds = OffsetArrays.IdOffsetRange(values=3:6, indices=53:56) - @test @inferred(view(collect(mem), offset_inds)) == view(mem, offset_inds) +@testset "Wrapping Memory into Arrays" begin + mem = Memory{Int}(undef, 10) .= 1 + memref = memoryref(mem) + @test_throws DimensionMismatch Base.wrap(Array, mem, (10, 10)) + @test Base.wrap(Array, mem, (5,)) == ones(Int, 5) + @test Base.wrap(Array, mem, 2) == ones(Int, 2) + @test Base.wrap(Array, memref, 10) == ones(Int, 10) + @test Base.wrap(Array, memref, (2,2,2)) == ones(Int,2,2,2) + @test Base.wrap(Array, mem, (5, 2)) == ones(Int, 5, 2) + + memref2 = memoryref(mem, 3) + @test Base.wrap(Array, memref2, (5,)) == ones(Int, 5) + @test Base.wrap(Array, memref2, 2) == ones(Int, 2) + @test Base.wrap(Array, memref2, (2,2,2)) == ones(Int,2,2,2) + @test Base.wrap(Array, memref2, (3, 2)) == ones(Int, 3, 2) + @test_throws DimensionMismatch Base.wrap(Array, memref2, 9) + @test_throws DimensionMismatch Base.wrap(Array, memref2, 10) end @testset "Memory size" begin diff --git a/test/core.jl b/test/core.jl index 3a3f6e6d1b6cd..e765d5a2ab7d7 100644 --- a/test/core.jl +++ b/test/core.jl @@ -5532,9 +5532,6 @@ let a = Base.StringVector(2^17) @test sizeof(c) == 0 end -# issue #53990 / https://github.com/JuliaLang/julia/pull/53896#discussion_r1555087951 -@test Base.StringVector(UInt64(2)) isa Vector{UInt8} - @test_throws ArgumentError eltype(Bottom) # issue #16424, re-evaluating type definitions