Would a pointer-esque api be ok if it were memory-safe? Something like:
struct SafePointer{T}
base::Ptr{Void} # start of the region
len::UInt64 # length of the region
offset::UInt64 # offset to the T
function SafePointer{T}(base::Ptr{Void}, len::UInt64, offset::UInt64}
@assert 0 <= offset <= len - sizeof(T)
new(base, len, offset)
end
end
function +(sp::SafePointer{T}, i::UInt64) where {T}
SafePointer(sp.base, sp.len, offset+i)
end
We use something similar for implementing disk-backed betrees. It handles most of the pointer arithmetic and does bounds checks:
julia> struct Foo
x::Int64
y::PagedVector{Float32}
end
julia> paged = Paged{Foo}(p)
Pageds.Paged{Foo}(Ptr{Void} @0x00000000032d9fe0)
julia> @v paged.y
3-element Pageds.PagedVector{Float32}:
0.0
0.0
0.0
julia> @v paged.y[2]
0.0f0
julia> @v paged.y[4]
ERROR: BoundsError: attempt to access 3-element Pageds.PagedVector{Float32} at index [4]
julia> y2 = @a paged.y[2] # internal pointer
Pageds.Paged{Float32}(Ptr{Void} @0x00000000032d9ffc)
julia> @v y2
0.0f0
julia> @v y2 = 7
Ptr{Float32} @0x00000000032d9ffc
julia> @v paged.y
3-element Pageds.PagedVector{Float32}:
0.0
7.0
0.0
It all boils down to pointer operations which optimize really well:
julia> f(paged, x) = @v paged.y[2] = x
julia> @code_lowered f(paged, 3)
CodeInfo(:(begin
nothing
return (Pageds.unsafe_store!)((Pageds.get_address)((Pageds.get_address)(paged, Val{:y}), 2), x)
end))
julia> @code_warntype f(paged, 3)
Variables:
#self# <optimized out>
paged::Pageds.Paged{Foo}
x::Int64
Body:
begin
$(Expr(:inbounds, false))
# meta: location /home/jamie/raicode/src/Pageds/Pageds.jl get_address 95
# meta: location /home/jamie/raicode/src/Pageds/Pageds.jl # line 99:
# meta: location /home/jamie/raicode/src/Pageds/Pageds.jl Type 18
goto 7
7:
# meta: pop location
SSAValue(0) = $(Expr(:new, Pageds.Paged{Pageds.PagedVector{Float32}}, :((Base.bitcast)(Ptr{Void}, (Base.add_int)((Base.bitcast)(UInt64, (Core.getfield)(paged, :ptr)::Ptr{Void}), 0x0000000000000008)::UInt64))))
# meta: pop location
# meta: pop location
$(Expr(:inbounds, :pop))
SSAValue(1) = $(Expr(:invoke, MethodInstance for get_address(::Pageds.Paged{Pageds.PagedVector{Float32}}, ::Int64), :(Pageds.get_address), SSAValue(0), 2))
$(Expr(:inbounds, false))
# meta: location /home/jamie/raicode/src/Pageds/Pageds.jl unsafe_store! 142
# meta: location /home/jamie/raicode/src/Pageds/Pageds.jl unsafe_store! 122
# meta: location /home/jamie/raicode/src/Pageds/Pageds.jl # line 126:
SSAValue(2) = (Base.pointerset)((Base.bitcast)(Ptr{Float32}, (Core.getfield)(SSAValue(1), :ptr)::Ptr{Void}), (Base.sitofp)(Float32, x::Int64)::Float32, 1, 1)::Ptr{Float32}
# meta: pop location
# meta: pop location
# meta: pop location
$(Expr(:inbounds, :pop))
return SSAValue(2)
end::Ptr{Float32}
julia> @code_native f(paged, 3)
.text
Filename: REPL[27]
pushq %rbp
movq %rsp, %rbp
pushq %rbx
pushq %rax
movq %rsi, %rbx
Source line: 99
movq (%rdi), %rax
addq $8, %rax
movq %rax, -16(%rbp)
Source line: 1
movabsq $get_address, %rax
leaq -16(%rbp), %rdi
movl $2, %esi
callq *%rax
Source line: 126
xorps %xmm0, %xmm0
cvtsi2ssq %rbx, %xmm0
movss %xmm0, (%rax)
Source line: 1
addq $8, %rsp
popq %rbx
popq %rbp
retq
nopl (%rax)