-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
move bounds checks to constructor #32
base: main
Are you sure you want to change the base?
move bounds checks to constructor #32
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an interesting idea. I'm not sure whether I'm sold on it.
src/blob.jl
Outdated
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should allow null pointers if their size is also set to 0?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sounds good to me (I also remove the @Assert since it caused in alloc)
src/blob.jl
Outdated
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
@@ -32,7 +56,7 @@ function Base.pointer(blob::Blob{T}) where T | |||
convert(Ptr{T}, getfield(blob, :base) + getfield(blob, :offset)) | |||
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 <[email protected]>
… into rb-blobs-nobounds
end | ||
end | ||
|
||
@noinline function _bounds_check( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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