Skip to content

Commit

Permalink
Revert changing wrap on Memory to view and make wrap non-publ…
Browse files Browse the repository at this point in the history
…ic. (#54927)

Fixes #54768
  • Loading branch information
KristofferC authored Jul 31, 2024
1 parent f225f84 commit 8d41d25
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 72 deletions.
53 changes: 53 additions & 0 deletions base/array.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
25 changes: 0 additions & 25 deletions base/genericmemory.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
12 changes: 8 additions & 4 deletions base/iobuffer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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).

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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)
Expand Down
5 changes: 1 addition & 4 deletions base/strings/string.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
53 changes: 17 additions & 36 deletions test/arrayops.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 0 additions & 3 deletions test/core.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 8d41d25

Please sign in to comment.