move bounds checks to constructor#32
Conversation
kuszmaul
left a comment
There was a problem hiding this comment.
This is an interesting idea. I'm not sure whether I'm sold on it.
| if offset < 0 || offset + self_size(T) > limit | ||
| throw(InvalidBlobError(Blob{T}, base, offset, limit, 1)) | ||
| end | ||
| @assert base != Ptr{Nothing}(0) "Null pointer dereference in $(T)" |
There was a problem hiding this comment.
This assert against the null pointer seems out of place. There's no way to protect against bad pointers, and may be the user wants to make such a blob.
There was a problem hiding this comment.
Maybe we should allow null pointers if their size is also set to 0?
There was a problem hiding this comment.
sounds good to me (I also remove the @Assert since it caused in alloc)
| Base.@propagate_inbounds function Blob(ref::Base.RefValue{T}) where T | ||
| Blob{T}(pointer_from_objref(ref), 0, sizeof(T)) |
There was a problem hiding this comment.
This @propagate_bounds seems unneeded and possibly wrong. We know that there are no bounds errors here, right? Also, this is going to be a problem if self_size != sizeof so maybe it should be self_size rather than sizeof. Or maybe there should be an error.
| Base.@propagate_inbounds function Blob(ref::Base.RefValue{T}) where T | |
| Blob{T}(pointer_from_objref(ref), 0, sizeof(T)) | |
| function Blob(ref::Base.RefValue{T}) where T | |
| @assert self_size(T) == sizeof(T) | |
| @inbounds Blob{T}(pointer_from_objref(ref), 0, self_size(T)) |
There was a problem hiding this comment.
That @Assert actually breaks the test. Basically this operation only works if the memory layout of Julia structs and Blobs are the same. That's not always the case because of alignment. The best would be to get rid of the constructor altogether. Otherwise, the @Assert is probably good enough, assuming that Julia only takes more memory due to alignment.
| end | ||
|
|
||
| function Base.:+(blob::Blob{T}, offset::Integer) where T | ||
| Base.@propagate_inbounds function Base.:+(blob::Blob{T}, offset::Integer) where T |
There was a problem hiding this comment.
It's traditional in the C world to allow people to construct a pointer that goes just off the end of an array. (But not to access that pointer)
This code prohibits the construction of the pointer. Is that where we want to go?
There was a problem hiding this comment.
You can created a Blob{Nothing}(<someptr>, 0, 0). That actually happening. I don't think a Blob is an array so don't see a need for something else.
Co-authored-by: Bradley C. Kuszmaul <kuszmaul@gmail.com>
… into rb-blobs-nobounds
| end | ||
| end | ||
|
|
||
| @noinline function _bounds_check( |
There was a problem hiding this comment.
What's the rationale for making this @noinline? Julia doesn't do it for checkbounds(::AbstractArray, I...), for example.
There was a problem hiding this comment.
The reason for the large amount of generate llvm code was the bounds check (building the strings etc). Therefore, I want to avoid that this is getting inlined into the specialized function.
| offset::Int64, | ||
| limit::Int64, | ||
| self_size_T::Int64, | ||
| @nospecialize(T::DataType)) |
There was a problem hiding this comment.
Does this @nospecialize actually do anything?
Julia doesn't specialize arguments that look like t::Type. (It does specialize arguments like ::Type{T}) where T.)
julia> foo(base::Int64, t::DataType) = base+sizeof(t)
foo (generic function with 1 method)
julia> Base.specializations(@which foo(3,Int))
Base.MethodSpecializations(MethodInstance for foo(::Int64, ::DataType))
julia> Base.specializations(@which foo(3,UInt8))
Base.MethodSpecializations(MethodInstance for foo(::Int64, ::DataType))
julia> bar(base::Int64, ::Type{T}) where T = base+sizeof(T)
bar (generic function with 1 method)
julia> Base.specializations(@which bar(3,Int))
Base.MethodSpecializations(MethodInstance for bar(::Int64, ::Type{Int64}))
julia> Base.specializations(@which bar(3,UInt8))
Base.MethodSpecializations(svec(MethodInstance for bar(::Int64, ::Type{Int64}), MethodInstance for bar(::Int64, ::Type{UInt8}), nothing, nothing, nothing, nothing, nothing))
There was a problem hiding this comment.
No, good catch. I added it because I was surprised that there is still more code generated by the constructor (I believe). But it didn't change anything for the reason that you point out.
I moved the bounds check from getindex to the Blobs constructors. We now guarantee that the constructed blob is valid (has enough memory for the data), so we don't need boundschecks whenever we deref. This gets rid of all the boundscheck code.
RAICode needs some fixes to work with this, i.e. where we created blobs with offset > limit. See: https://github.com/RelationalAI/raicode/pull/21062