Thanks for the elaborated discussion, it clarified a lot to me, but some questions remain.
I understood (simplified): using pointers should always be protected by a surrounding GC.@preserve.
Am I right that this code from Julia 1.0.5, iobuffer.jl line 462 ff is an incorrect use, because the pointer p is defined outside the @preserve block instead of inside it?
function occursin(delim::UInt8, buf::IOBuffer)
p = pointer(buf.data, buf.ptr)
q = GC.@preserve buf ccall(:memchr,Ptr{UInt8},(Ptr{UInt8},Int32,Csize_t),p,delim,bytesavailable(buf))
return q != C_NULL
end
My suggestion for a correction would be simply
function occursin(delim::UInt8, buf::IOBuffer)
q = GC.@preserve buf ccall(:memchr,Ptr{UInt8},(Ptr{UInt8},Int32,Csize_t),pointer(buf.data, buf.ptr),delim,bytesavailable(buf))
return q != C_NULL
end
IOBuffer allocates its working memory as a String and wraps it in a UInt8 vector:
# allocate Vector{UInt8}s for IOBuffer storage that can efficiently become Strings
StringVector(n::Integer) = unsafe_wrap(Vector{UInt8}, _string_n(n))
Using the String instance directly in IOBuffer (instead of the UInt8 vector wrapper) would
spare one indirection. However, all String code unit accesses need a @preserve guard, see
codeunit definition in string.jl:
@inline function codeunit(s::String, i::Integer)
@boundscheck checkbounds(s, i)
GC.@preserve s unsafe_load(pointer(s, i))
end
If we compare the current IOBuffer imlementation with the possible alternative to use
the String instance directly with @preserve wrapped pointer accesses:
does wrapping in an UInt8 vector yields better performance, despite the added indirection,
because @preserve is avoided and compiler can do better optimizations, or is the UInt8 vector wrap
due to better code reuse with GenericIOBuffer and the rich array API for the price of a small performance penalty, because the internal array element access code has to do similar things @preserve does?