Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get a `Ptr` to an element of an `NTuple`?

Tags:

julia

Say I have a tuple of Cchar like

str = ('f', 'o', 'o', '\0', '\0')

and I want to convert it to a more traditional string. If str were a Vector, I could create Ptr and do all sorts of things with that. I've tried various ways of passing str to methods of pointer, Ptr, Ref, and unsafe_string without success since those normally work on arrays rather than tuples. Any suggestions?

Note: what I really have is a C struct that looks like

typedef struct foo {
    char str[FOO_STR_MAX_SZ];
    ...
} foo_t;

which Clang.jl wrapped as

struct foo_t
    str :: NTuple{FOO_STR_MAX_SZ, UInt8}
    ...
end

I also played around with NTuple of Cchar (ie, Int8) instead of UInt8, and I tried to use SVector instead of NTuple as well. But I still couldn't find a way to generate a Ptr from the str field. Am I missing something?

like image 427
Nicu Stiurca Avatar asked Feb 26 '19 15:02

Nicu Stiurca


2 Answers

Since you asked the question, I think collecting it to an array a = collect(x.str) is not the answer you are expecting...

You could use ccall(:jl_value_ptr, Ptr{Cvoid}, (Any,), a) to get the pointer of a even if a is immutable. However, blindly using it will produce some confusing results:

julia> struct foo_t
         str::NTuple{2, UInt8}
       end

julia> a = foo_t((2, 3))
foo_t((0x02, 0x03))

julia> ccall(:jl_value_ptr, Ptr{Cvoid}, (Any,), a.str)
Ptr{Nothing} @0x00007f4302c4f670

julia> ccall(:jl_value_ptr, Ptr{Cvoid}, (Any,), a.str)
Ptr{Nothing} @0x00007f4302cc47e0

We got two different pointers from the same object! The reason is that since NTuple is immutable, the compiler will do many "optimizations" for it, for example, coping it every time you use it. This is why getting pointers from immutable objects is explicitly forbidden in the source code:

function pointer_from_objref(@nospecialize(x))
    @_inline_meta
    typeof(x).mutable || error("pointer_from_objref cannot be used on immutable objects")
    ccall(:jl_value_ptr, Ptr{Cvoid}, (Any,), x)
end

However, there are several workarounds for it. First, since the expression a.str copies the tuple, you can avoid this expressoin and calculate the address of it directly using the address of a and fieldoffset(typeof(a), 1). (1 means str is the first field of foo_t)

julia> p = Ptr{UInt8}(ccall(:jl_value_ptr, Ptr{UInt8}, (Any,), a)) + fieldoffset(typeof(a), 1)
Ptr{UInt8} @0x00007f4304901df0

julia> p2 = Ptr{UInt8}(ccall(:jl_value_ptr, Ptr{UInt8}, (Any,), a)) + fieldoffset(typeof(a), 1)
Ptr{UInt8} @0x00007f4304901df0

julia> p === p2
true

julia> unsafe_store!(p, 5)
Ptr{UInt8} @0x00007f4304901df0

julia> a
foo_t((0x05, 0x03))

It now works. However, there are still caveats: when you try to wrap the code in a function, it became wrong again:

julia> mut!(a) = unsafe_store!(Ptr{UInt8}(ccall(:jl_value_ptr, Ptr{UInt8}, (Any,), a)) + fieldoffset(typeof(a), 1), 8)
mut! (generic function with 1 method)

julia> mut!(a)
Ptr{UInt8} @0x00007f42ec560294

julia> a
foo_t((0x05, 0x03))

a is not changed because, well, foo_t itself is also immutable and will be copied to mut!, so the change made within the function will not be visible outside. To solve this, we need to wrap a in a mutable object to give it a stable address in the heap. Base.RefValue can be used for this purpose:

julia> b = Base.RefValue(a)
Base.RefValue{foo_t}(foo_t((0x05, 0x03)))

julia> mut!(b) = unsafe_store!(Ptr{UInt8}(ccall(:jl_value_ptr, Ptr{UInt8}, (Any,), b)) + fieldoffset(typeof(b), 1) + fieldoffset(typeof(a), 1), 8)
mut! (generic function with 1 method)

julia> mut!(b)
Ptr{UInt8} @0x00007f43057b3820

julia> b
Base.RefValue{foo_t}(foo_t((0x08, 0x03)))

julia> b[]
foo_t((0x08, 0x03))
like image 100
张实唯 Avatar answered Nov 10 '22 17:11

张实唯


As explained by @张实唯, str is a constant array which is stack-allocated, so you need to use pointer arithmetics to access the field. There is a package called Blobs.jl for this kinda purpose. As for the mutability, you could also use Setfield.jl for convenience.

BTW, Clang.jl do support generating mutable structs via ctx.options["is_struct_mutable"] = true.

like image 45
Gnimuc Avatar answered Nov 10 '22 16:11

Gnimuc