Skip to content
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

Open
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

robertbuessow
Copy link

@robertbuessow robertbuessow commented Oct 28, 2024

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

@robertbuessow robertbuessow marked this pull request as ready for review October 29, 2024 17:14
Copy link
Contributor

@kuszmaul kuszmaul left a 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/bit_vector.jl Outdated Show resolved Hide resolved
.vscode/settings.json Outdated Show resolved Hide resolved
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)"
Copy link
Contributor

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.

Copy link
Contributor

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?

Copy link
Author

@robertbuessow robertbuessow Oct 30, 2024

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
Comment on lines 36 to 37
Base.@propagate_inbounds function Blob(ref::Base.RefValue{T}) where T
Blob{T}(pointer_from_objref(ref), 0, sizeof(T))
Copy link
Contributor

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.

Suggested change
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))

Copy link
Author

@robertbuessow robertbuessow Oct 30, 2024

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.

src/blob.jl Show resolved Hide resolved
@@ -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
Copy link
Contributor

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?

Copy link
Author

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.

end
end

@noinline function _bounds_check(
Copy link
Contributor

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.

Copy link
Author

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))
Copy link
Contributor

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))

Copy link
Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants