diff --git a/CHANGELOG.md b/CHANGELOG.md index bcad398..bafd462 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,30 @@ Any new features, or breaking changes, will be written in this file. Bugfixes, internal refactors, documentation improvements and style changes will not be mentioned here, because they do not impact how the package is to be used. +## 0.4.0 +### Breaking changes +* Removed the `Unsafe` trait type: + - Instead of `MutableMemoryView(::Unsafe, ::MemoryView)`, use + `unsafe_from_parts(::MemoryRef, ::Int)` + - Using the inner constructor `MemoryView{T, M}(::Unsafe, ::MemoryRef{T}, ::Int)` + was never documented API and is now removed. + +* `Matrix` and other `Array` types with a different dimensionality than 1 is now + `NotMemory`, since it is not equal to its own memory view, due to shape mismatch. + +* Out of bounds access now throws a `LightBoundsError` from the LightBoundsErrors + package, instead of `Base.BoundsError`. + This improves codegen slightly, as it enables escape analysis of the array, + and outlines error paths slightly more aggressively. + +* `MemoryView(::SubArray)` now accepts fewer subarray types. However, it is unlikely + that any instance that is now no longer accepted worked previously, so it is + unlikely to be breaking in practice. + +## Other +* `parentindices` now works correctly for zero-sized structs. +* `Base.memoryref(::MemoryView)` obtains the `MemoryRef` in a `MemoryView`. + ## 0.3.5 * Add method `MemoryKind{::Type{<:MemoryView}}` * Add package extension for LibDeflate.jl diff --git a/docs/src/interfaces.md b/docs/src/interfaces.md index cdd7756..724d8e0 100644 --- a/docs/src/interfaces.md +++ b/docs/src/interfaces.md @@ -34,14 +34,14 @@ For a type `T`, `MemoryKind(T)` returns one of two types: they are not heap allocated, and `String`, which _are_ backed by memory, but which are semantically different from an `AbstractVector` containing its bytes. * `IsMemory{M}()` where `M` is a concrete subtype of `MemoryView`, if instances of `T` _are_ equivalent to their own memory. - Examples include `Array`s and `Codeunits{String}`. For these objects, it's the case that `x == MemoryView(x)`. + Examples include `Vector`s and `Codeunits{String}`. For these objects, it's the case that `x == MemoryView(x)`. ```jldoctest julia> MemoryKind(Vector{Union{Int32, UInt32}}) IsMemory{MutableMemoryView{Union{Int32, UInt32}}}() -julia> MemoryKind(Matrix{String}) -IsMemory{MutableMemoryView{String}}() +julia> MemoryKind(Matrix{String}) # dimension mismatch, not equal +NotMemory() julia> MemoryKind(SubString{String}) NotMemory() @@ -63,7 +63,7 @@ my_hash(x) = my_hash(MemoryKind(typeof(x)), x) my_hash(::IsMemory{<:MemoryView{UInt8}}, x) = my_hash(ImmutableMemoryView(x)) # IsMemory with eltype other than UInt8 can't use the fast low-level function -my_hash(T::IsMemory, x) = my_hash(NotMemory(), x) +my_hash(::IsMemory, x) = my_hash(NotMemory(), x) function my_hash(::NotMemory, x) # fallback implementation diff --git a/src/MemoryViews.jl b/src/MemoryViews.jl index 1f49ec6..60219ce 100644 --- a/src/MemoryViews.jl +++ b/src/MemoryViews.jl @@ -8,23 +8,16 @@ export MemoryView, NotMemory, inner, split_each, - unsafe_from_parts + unsafe_from_parts, + split_first, + split_last, + split_at, + split_unaligned public Mutable, Immutable, DelimitedIterator using LightBoundsErrors: checkbounds_lightboundserror, throw_lightboundserror -""" - Unsafe - -Trait object used to dispatch to unsafe methods. -The `MemoryViews.unsafe` instance is the singleton instance of this type. -""" -struct Unsafe end - -"Singleton instance of the trait type `Unsafe`" -const unsafe = Unsafe() - """ Trait struct, only used in the mutability parameter of `MemoryView` """ @@ -89,7 +82,7 @@ struct MemoryView{T, M <: Union{Mutable, Immutable}} <: DenseVector{T} ref::MemoryRef{T} len::Int - function MemoryView{T, M}(::Unsafe, ref::MemoryRef{T}, len::Int) where {T, M} + global function unsafe_new_memoryview(::Type{M}, ref::MemoryRef{T}, len::Int) where {M, T} (M === Mutable || M === Immutable) || error("Parameter M must be Mutable or Immutable") return new{T, M}(ref, len) @@ -108,8 +101,10 @@ Create a mutable memory view from its parts. * `len` is not negative * All indices `i in 1:len` are valid for `ref` (i.e. `memoryref(ref, i)` would not throw) -* If `ref` is derived from immutable memory, the returned memory view must not - be mutated. The caller should immediately convert it to an immutable view. +* If `ref` is derived from immutable memory, it is the caller's responsibility + to ensure that mutating the memory does not result in undefined behaviour. + For example, `ref` may be derived from a `String`, and mutating `String`s in + Julia may result in undefined behaviour. # Examples ```jldoctest @@ -124,28 +119,24 @@ julia> view = unsafe_from_parts(ref, 3) 3 ``` """ -function unsafe_from_parts(ref::MemoryRef{T}, len::Int) where {T} - return MemoryView{T, Mutable}(unsafe, ref, len) +function unsafe_from_parts(ref::MemoryRef, len::Int) + return unsafe_new_memoryview(Mutable, ref, len) end +""" + Base.memoryref(x::MemoryView{T})::MemoryRef{T} + +Get the `MemoryRef` of `x`. This reference is guaranteed to be inbounds, +except if `x` is empty, where it may point to one element past the end. +""" +Base.memoryref(x::MemoryView) = x.ref + _get_mutability(::MemoryView{T, M}) where {T, M} = M # Mutable mem views can turn into immutable ones, but not vice versa ImmutableMemoryView(x) = ImmutableMemoryView(MemoryView(x)::MemoryView) -function ImmutableMemoryView(x::MutableMemoryView{T}) where {T} - return ImmutableMemoryView{T}(unsafe, x.ref, x.len) -end -ImmutableMemoryView(x::ImmutableMemoryView) = x - -""" - MutableMemoryView(::Unsafe, x::MemoryView) - -Convert a memory view into a mutable memory view. -Note that it may cause undefined behaviour, if supposedly immutable data -is observed to be mutated. -""" -function MutableMemoryView(::Unsafe, x::MemoryView{T}) where {T} - return MutableMemoryView{T}(unsafe, x.ref, x.len) +function ImmutableMemoryView(x::MemoryView) + return unsafe_new_memoryview(Immutable, x.ref, x.len) end # Constructors that allows users to specify eltype explicitly, e.g. @@ -220,7 +211,6 @@ MemoryKind(::Type{T}) where {T <: MemoryView} = IsMemory(T) include("construction.jl") include("basic.jl") -include("experimental.jl") include("delimited.jl") include("base_arrays.jl") include("io.jl") diff --git a/src/base_arrays.jl b/src/base_arrays.jl index 4f15c6b..d1b5e44 100644 --- a/src/base_arrays.jl +++ b/src/base_arrays.jl @@ -14,7 +14,6 @@ function Base.Memory{T}(x::MemoryView{T}) where {T} end end - function Base.copyto!(A::Union{Memory{T}, Array{T}}, mem::MemoryView{T}) where {T} copyto!(MemoryView(A), mem) return A diff --git a/src/basic.jl b/src/basic.jl index c9bce60..bb4e944 100644 --- a/src/basic.jl +++ b/src/basic.jl @@ -6,10 +6,9 @@ function Base.setindex!(v::MutableMemoryView{T}, x, i::Int) where {T} return v end -# In v1.12, this `parent` method was added. Before 1.12, we load this undocumented -# field. This is safe-ish because it's very unlikely changes to the layout of -# `MemoryRef` will be backported -if VERSION < v"1.12" +# The parent method for memoryref was added in 1.12. In versions before that, +# it can be accessed by reaching into internals. +@static if VERSION < v"1.12" Base.parent(v::MemoryView) = v.ref.mem else Base.parent(v::MemoryView) = parent(v.ref) @@ -25,13 +24,11 @@ end # Base.memoryindex exists in Julia 1.13 onwards. @static if VERSION < v"1.13" - # Note: For zero-sized elements, this always returns 1:x.len, which may not be - # the correct indices. However, the result is indistinguishable from the "correct" - # result, so it doesn't matter function Base.parentindices(x::MemoryView) elz = Base.elsize(x) return if iszero(elz) - (1:(x.len),) + offset = Int(x.ref.ptr_or_offset) + ((1 + offset):(x.len + offset),) else byte_offset = pointer(x.ref) - pointer(x.ref.mem) elem_offset = div(byte_offset % UInt, elz % UInt) % Int @@ -45,10 +42,10 @@ else end end -function Base.copy(x::MemoryView) +function Base.copy(x::MemoryView{T, M}) where {T, M} isempty(x) && return x newmem = @inbounds x.ref.mem[only(parentindices(x))] - return typeof(x)(unsafe, memoryref(newmem), x.len) + return unsafe_new_memoryview(M, memoryref(newmem), x.len) end function Base.checkbounds(v::MemoryView, is...) @@ -70,12 +67,13 @@ function Base.similar(::MemoryView{T1, M}, ::Type{T2}, dims::Tuple{Int}) where { end function Base.empty(::MemoryView{T1, M}, ::Type{T2}) where {T1, T2, M} - return MemoryView{T2, M}(unsafe, memoryref(Memory{T2}()), 0) + return unsafe_new_memoryview(M, memoryref(Memory{T2}()), 0) end -Base.empty(T::Type{<:MemoryView{E}}) where {E} = T(unsafe, memoryref(Memory{E}()), 0) +Base.empty(::Type{<:MemoryView{E, M}}) where {E, M} = unsafe_new_memoryview(M, memoryref(Memory{E}()), 0) Base.pointer(x::MemoryView{T}) where {T} = Ptr{T}(pointer(x.ref)) Base.unsafe_convert(::Type{Ptr{T}}, v::MemoryView{T}) where {T} = pointer(v) +Base.cconvert(::Type{<:Ptr{T}}, v::MemoryView{T}) where {T} = v.ref Base.elsize(::Type{<:MemoryView{T}}) where {T} = Base.elsize(Memory{T}) Base.sizeof(x::MemoryView) = Base.elsize(typeof(x)) * length(x) Base.strides(::MemoryView) = (1,) @@ -109,28 +107,28 @@ function Base.mightalias(a::KNOWN_MEM_BACKED, b::MemoryView) return Base.mightalias(ImmutableMemoryView(a), b) end -function Base.getindex(v::MemoryView, idx::AbstractUnitRange) +function Base.getindex(v::MemoryView{T, M}, idx::AbstractUnitRange) where {T, M} # This branch is necessary, because the memoryref can't point out of bounds. # So if the user gives an empty slice that is out of bounds, the boundscheck # may pass, but the memoryref construction will be OOB. - isempty(idx) && return typeof(v)(unsafe, memoryref(v.ref.mem), 0) + isempty(idx) && return unsafe_new_memoryview(M, memoryref(v.ref.mem), 0) @boundscheck checkbounds(v, idx) newref = @inbounds memoryref(v.ref, Int(first(idx))::Int) - return typeof(v)(unsafe, newref, Int(length(idx))::Int) + return unsafe_new_memoryview(M, newref, Int(length(idx))::Int) end -function Base.getindex(v::MemoryView, idx::UnitRange{UInt}) - isempty(idx) && return typeof(v)(unsafe, memoryref(v.ref.mem), 0) +function Base.getindex(v::MemoryView{T, M}, idx::UnitRange{UInt}) where {T, M} + isempty(idx) && return unsafe_new_memoryview(M, v.ref, 0) @boundscheck checkbounds(v, idx) newref = @inbounds memoryref(v.ref, first(idx) % Int) - return typeof(v)(unsafe, newref, length(idx) % Int) + return unsafe_new_memoryview(M, newref, length(idx) % Int) end # Faster method, because we don't need to create a new memoryref, and also don't # need to handle the empty case. -function Base.getindex(v::MemoryView, idx::Base.OneTo) +function Base.getindex(v::MemoryView{T, M}, idx::Base.OneTo) where {T, M} @boundscheck checkbounds(v, idx) - return typeof(v)(unsafe, v.ref, last(idx)) + return unsafe_new_memoryview(M, v.ref, last(idx)) end Base.getindex(v::MemoryView, ::Colon) = v @@ -138,35 +136,35 @@ Base.@propagate_inbounds Base.view(v::MemoryView, idx::AbstractUnitRange) = v[id # Efficient way to get `mem[1:include_last]`. # include_last must be in 0:length(mem) -function truncate(mem::MemoryView, include_last::Integer) +function truncate(mem::MemoryView{T, M}, include_last::Integer) where {T, M} lst = Int(include_last)::Int @boundscheck if (lst % UInt) > length(mem) % UInt throw_lightboundserror(mem, lst) end - return typeof(mem)(unsafe, mem.ref, lst) + return unsafe_new_memoryview(M, mem.ref, lst) end # Efficient way to get `mem[from:end]`. # From must be in 1:length(mem). -function truncate_start_nonempty(mem::MemoryView, from::Integer) +function truncate_start_nonempty(mem::MemoryView{T, M}, from::Integer) where {T, M} frm = Int(from)::Int @boundscheck if ((frm - 1) % UInt) ≥ length(mem) % UInt throw_lightboundserror(mem, frm) end newref = @inbounds memoryref(mem.ref, frm) - return typeof(mem)(unsafe, newref, length(mem) - frm + 1) + return unsafe_new_memoryview(M, newref, length(mem) - frm + 1) end # Efficient way to get `mem[from:end]`. # From must be in 1:length(mem)+1. -function truncate_start(mem::MemoryView, from::Integer) +function truncate_start(mem::MemoryView{T, M}, from::Integer) where {T, M} frm = Int(from)::Int @boundscheck if ((frm - 1) % UInt) > length(mem) % UInt throw_lightboundserror(mem, frm) end frm == 1 && return mem newref = @inbounds memoryref(mem.ref, frm - (from == length(mem) + 1)) - return typeof(mem)(unsafe, newref, length(mem) - frm + 1) + return unsafe_new_memoryview(M, newref, length(mem) - frm + 1) end function Base.unsafe_copyto!(dst::MutableMemoryView{T}, src::MemoryView{T}) where {T} @@ -403,3 +401,124 @@ function Base.iterate(x::ReverseMemoryView, state = length(x)) iszero(state) && return nothing return (@inbounds(x.mem[state]), state - 1) end + +""" + split_first(v::MemoryView{T}) -> Tuple{T, MemoryView{T}} + +Return the first element of `v` and all other elements as a new memory view. + +This function will throw a `LightBoundsError` if `v` is empty. + +See also: [`split_last`](@ref) + +# Examples +```jldoctest +julia> v = MemoryView([0x01, 0x02, 0x03]); + +julia> split_first(v) +(0x01, UInt8[0x02, 0x03]) + +julia> split_first(v[1:1]) +(0x01, UInt8[]) + +julia> split_first(v[1:0]) +ERROR: LightBoundsErrors.LightBoundsError: out-of-bounds indexing: `collection[1]`, where: +[...] +``` +""" +function split_first(v::MemoryView) + @boundscheck checkbounds(v, 1) + return (@inbounds(v[1]), @inbounds(truncate_start(v, 2))) +end + +""" + split_last(v::MemoryView{T}) -> Tuple{T, MemoryView{T}} + +Return the last element of `v` and all other elements as a new memory view. + +This function will throw a `LightBoundsError` if `v` is empty. + +See also: [`split_first`](@ref) + +# Examples +```jldoctest +julia> v = MemoryView([0x01, 0x02, 0x03]); + +julia> split_last(v) +(0x03, UInt8[0x01, 0x02]) + +julia> split_last(v[1:1]) +(0x01, UInt8[]) + +julia> split_last(v[1:0]) +ERROR: LightBoundsErrors.LightBoundsError: out-of-bounds indexing: `collection[1]`, where: +[...] +``` +""" +function split_last(v::MemoryView) + @boundscheck checkbounds(v, 1) + return (@inbounds(v[end]), @inbounds(truncate(v, length(v) - 1))) +end + +""" + split_at(v::T, i::Int) -> Tuple{T, T} where {T <: MemoryView} + +Split a memory view into two at an index. + +The first will contain all indices in `1:i-1`, the second `i:end`. +This function will throw a `LightBoundsError` if `i` is not in `1:end+1`. + +# Examples +```jldoctest +julia> split_at(MemoryView([1,2,3,4,5]), 2) +([1], [2, 3, 4, 5]) + +julia> split_at(MemoryView(Int8[1, 2, 3]), 4) +(Int8[1, 2, 3], Int8[]) +``` +""" +function split_at(v::MemoryView, i::Int) + @boundscheck if i ∉ 1:(lastindex(v) + 1) + throw_lightboundserror(v, i) + end + return (@inbounds(truncate(v, i - 1)), @inbounds(truncate_start(v, i))) +end + +""" + split_unaligned(v::T, ::Val{A}) -> Tuple{T, T} where {T <: MemoryView} + +Split memory view `v` into two views `a` and `b`, where `a` is the smallest prefix of `v` +that guarantees the starting memory address of `b` is is aligned to the integer value `A`. +`A` must be a normal bit-integer, and a power of two in the range 1:64. + +If `v` is empty or already aligned, `a` will be empty. +If no elements of `v` is aligned, `b` will be empty and `a` will be equal to `v`. +The element type of `v` must be a bitstype. + +!!! warning + When using this function, make sure to `GC.@preserve v`, to make sure Julia + does not move `v` in memory. + +# Examples: +``` +julia> split_unaligned(MemoryView(Int16[1, 2, 3]), Val(8)) +(Int16[], Int16[1, 2, 3]) + +julia> split_unaligned(MemoryView(collect(0x01:0x20))[6:13], Val(8)) +(UInt8[0x06, 0x07, 0x08], UInt8[0x09, 0x0a, 0x0b, 0x0c, 0x0d]) +``` +""" +function split_unaligned(v::MemoryView{T, M}, ::Val{A}) where {A, T, M} + isbitstype(eltype(v)) || error("Alignment can only be computed for views of bitstypes") + A isa Bits || error("Invalid alignment") + in(A, (1, 2, 4, 8, 16, 32, 64)) || error("Invalid alignment") + alignment = A % UInt + mask = alignment - 1 + sz = Base.elsize(v) + # Early return here to avoid division by zero: Size sz is statically known, + # this will be compiled away + iszero(sz) && return (unsafe_new_memoryview(M, v.ref, 0), v) + unaligned_bytes = ((alignment - (UInt(pointer(v)) & mask)) & mask) + n_elements = min(length(v), div(unaligned_bytes, sz % UInt) % Int) + return @inbounds split_at(v, n_elements + 1) +end diff --git a/src/construction.jl b/src/construction.jl index fd6a366..7d274cf 100644 --- a/src/construction.jl +++ b/src/construction.jl @@ -1,10 +1,12 @@ MemoryView(v::MemoryView) = v # Array and Memory -MemoryKind(::Type{<:Array{T}}) where {T} = IsMemory(MutableMemoryView{T}) +# Array with more than 1 dimension is not equal to the view, since they have different axes +MemoryKind(::Type{<:Array{T}}) where {T} = NotMemory() +MemoryKind(::Type{<:Vector{T}}) where {T} = IsMemory(MutableMemoryView{T}) MemoryKind(::Type{<:Memory{T}}) where {T} = IsMemory(MutableMemoryView{T}) -MemoryView(A::Memory{T}) where {T} = MutableMemoryView{T}(unsafe, memoryref(A), length(A)) -MemoryView(A::Array{T}) where {T} = MutableMemoryView{T}(unsafe, Base.cconvert(Ptr, A), length(A)) +MemoryView(A::Memory{T}) where {T} = unsafe_new_memoryview(Mutable, memoryref(A), length(A)) +MemoryView(A::Array{T}) where {T} = unsafe_new_memoryview(Mutable, Base.cconvert(Ptr, A), length(A)) # Strings MemoryView(s::String) = ImmutableMemoryView(unsafe_wrap(Memory{UInt8}, s)) @@ -19,7 +21,7 @@ function MemoryView(s::SubString{String}) memview = MemoryView(parent(s)) isempty(memview) && return memview newref = @inbounds memoryref(memview.ref, s.offset + 1) - return ImmutableMemoryView{UInt8}(unsafe, newref, ncodeunits(s)) + return unsafe_new_memoryview(Immutable, newref, ncodeunits(s)) end # CodeUnits are semantically IsMemory, but only if the underlying string diff --git a/src/experimental.jl b/src/experimental.jl deleted file mode 100644 index 1ed86a6..0000000 --- a/src/experimental.jl +++ /dev/null @@ -1,123 +0,0 @@ -# API which I'm not sure should be kept - -export split_first, split_last, split_at, split_unaligned - -""" - split_first(v::MemoryView{T}) -> Tuple{T, MemoryView{T}} - -Return the first element of `v` and all other elements as a new memory view. - -This function will throw a `LightBoundsError` if `v` is empty. - -See also: [`split_last`](@ref) - -# Examples -```jldoctest -julia> v = MemoryView([0x01, 0x02, 0x03]); - -julia> split_first(v) -(0x01, UInt8[0x02, 0x03]) - -julia> split_first(v[1:1]) -(0x01, UInt8[]) - -julia> split_first(v[1:0]) -ERROR: LightBoundsErrors.LightBoundsError: out-of-bounds indexing: `collection[1]`, where: -* `typeof(collection) == MutableMemoryView{UInt8}` -* `axes(collection) == (Base.OneTo(0),)` -[...] -``` -""" -function split_first(v::MemoryView) - @boundscheck checkbounds(v, 1) - return (@inbounds(v[1]), @inbounds(truncate_start(v, 2))) -end - -""" - split_last(v::MemoryView{T}) -> Tuple{T, MemoryView{T}} - -Return the last element of `v` and all other elements as a new memory view. - -This function will throw a `LightBoundsError` if `v` is empty. - -See also: [`split_first`](@ref) - -# Examples -```jldoctest -julia> v = MemoryView([0x01, 0x02, 0x03]); - -julia> split_last(v) -(0x03, UInt8[0x01, 0x02]) - -julia> split_last(v[1:1]) -(0x01, UInt8[]) - -julia> split_last(v[1:0]) -ERROR: LightBoundsErrors.LightBoundsError: out-of-bounds indexing: `collection[1]`, where: -* `typeof(collection) == MutableMemoryView{UInt8}` -* `axes(collection) == (Base.OneTo(0),)` -[...] -``` -""" -function split_last(v::MemoryView) - @boundscheck checkbounds(v, 1) - return (@inbounds(v[end]), @inbounds(truncate(v, length(v) - 1))) -end - -""" - split_at(v::T, i::Int) -> Tuple{T, T} where {T <: MemoryView} - -Split a memory view into two at an index. - -The first will contain all indices in `1:i-1`, the second `i:end`. -This function will throw a `LightBoundsError` if `i` is not in `1:end+1`. - -# Examples -```jldoctest -julia> split_at(MemoryView([1,2,3,4,5]), 2) -([1], [2, 3, 4, 5]) - -julia> split_at(MemoryView(Int8[1, 2, 3]), 4) -(Int8[1, 2, 3], Int8[]) -``` -""" -function split_at(v::MemoryView, i::Int) - @boundscheck checkbounds_lightboundserror(1:(lastindex(v) + 1), i) - return (@inbounds(truncate(v, i - 1)), @inbounds(truncate_start(v, i))) -end - -""" - split_unaligned(v::T, ::Val{A}) -> Tuple{T, T} where {T <: MemoryView} - -Split memory view `v` into two views `a` and `b`, where `a` is the smallest prefix of `v` -that guarantees the starting memory address of `b` is is aligned to the integer value `A`. -`A` must be a normal bit-integer, and a power of two in the range 1:64. - -If `v` is empty or already aligned, `a` will be empty. -If no elements of `v` is aligned, `b` will be empty and `a` will be equal to `v`. -The element type of `v` must be a bitstype. - - -# Examples: -``` -julia> split_unaligned(MemoryView(Int16[1, 2, 3]), Val(8)) -(Int16[], Int16[1, 2, 3]) - -julia> split_unaligned(MemoryView(collect(0x01:0x20))[6:13], Val(8)) -(UInt8[0x06, 0x07, 0x08], UInt8[0x09, 0x0a, 0x0b, 0x0c, 0x0d]) -``` -""" -function split_unaligned(v::MemoryView, ::Val{A}) where {A} - isbitstype(eltype(v)) || error("Alignment can only be computed for views of bitstypes") - A isa Bits || error("Invalid alignment") - in(A, (1, 2, 4, 8, 16, 32, 64)) || error("Invalid alignment") - alignment = A % UInt - mask = alignment - 1 - sz = Base.elsize(v) - # Early return here to avoid division by zero: Size sz is statically known, - # this will be compiled away - iszero(sz) && return (typeof(v)(unsafe, v.ref, 0), v) - unaligned_bytes = ((alignment - (UInt(pointer(v)) & mask)) & mask) - n_elements = min(length(v), div(unaligned_bytes, sz % UInt) % Int) - return @inbounds split_at(v, n_elements + 1) -end diff --git a/test/runtests.jl b/test/runtests.jl index 164b3b8..789c6f4 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -42,7 +42,7 @@ MUT_BACKINGS = Any[ @testset "Unsafe mutability" begin v = [1.0, 2.0, 3.0] m = ImmutableMemoryView(v) - m2 = MutableMemoryView(MemoryViews.unsafe, m) + m2 = unsafe_from_parts(m.ref, 3) m2[2] = 5.0 @test v == [1.0, 5.0, 3.0] end @@ -324,6 +324,20 @@ end @test pointer(v, 3) == pointer(mem, 2) end +@testset "memoryref" begin + mem = MemoryView([10, 20, 30])[2:3] + ref = memoryref(mem) + @test ref === mem.ref + @test memoryref(ref, 1)[] === 20 + @test memoryref(ref, 2)[] === 30 + + imm = MemoryView("abc")[2:3] + ref = memoryref(imm) + @test ref === imm.ref + @test memoryref(ref, 1)[] === UInt8('b') + @test memoryref(ref, 2)[] === UInt8('c') +end + @testset "Misc functions" begin @testset "Copying" begin # Immutable @@ -352,6 +366,13 @@ end mem = MemoryView(view(Vector{String}(undef, 10), 5:7)) @test parentindices(mem) == (5:7,) + + mem = MemoryView(Vector{Nothing}(undef, 10))[4:7] + @test parentindices(mem) == (4:7,) + + struct ZeroSized end + mem = MemoryView(Vector{ZeroSized}(undef, 8))[3:5] + @test parentindices(mem) == (3:5,) end @testset "Similar and empty" begin @@ -471,6 +492,8 @@ end @test split_at(mem, 2) == (mem[1:1], mem[2:end]) @test split_at(mem, lastindex(mem)) == (mem[1:(end - 1)], mem[end:end]) @test split_at(mem, lastindex(mem) + 1) == (mem[1:end], mem[1:0]) + @test_throws LightBoundsError split_at(mem, 0) + @test_throws LightBoundsError split_at(mem, lastindex(mem) + 2) mem = mem[2:2] @test split_first(mem) == (mem[1], mem[2:end]) @test split_last(mem) == (mem[end], mem[1:(end - 1)]) @@ -484,6 +507,8 @@ end (v1, v2) = split_at(mem, 1) @test v1 == v2 @test isempty(v1) + @test_throws LightBoundsError split_at(mem, 0) + @test_throws LightBoundsError split_at(mem, 2) end @testset "Split unaligned" begin @@ -737,6 +762,9 @@ end @test unsafe_from_parts(ref, 2) == [1, 3] @test isempty(unsafe_from_parts(ref, 0)) @test mem isa MutableMemoryView{Int} + + v2 = MemoryView([3, 1, 4]) + @test Base.cconvert(Ptr{Int}, v2) === v2.ref end @testset "MemoryKind" begin @@ -745,7 +773,7 @@ end IsMemory(ImmutableMemoryView{UInt8}) @test MemoryKind(typeof(view(Memory{String}(undef, 3), Base.OneTo(2)))) == IsMemory(MutableMemoryView{String}) - @test MemoryKind(Matrix{Nothing}) == IsMemory(MutableMemoryView{Nothing}) + @test MemoryKind(Matrix{Nothing}) == NotMemory() @test MemoryKind(Memory{Int32}) == IsMemory(MutableMemoryView{Int32}) @test MemoryKind(typeof(view([1], 1:1))) == IsMemory(MutableMemoryView{Int})