pybind11 icon indicating copy to clipboard operation
pybind11 copied to clipboard

PYBIND11_NUMPY_DTYPE for union struct member - cannot compute offset of bit field

Open asherungar opened this issue 5 years ago • 6 comments

Hi,

I want to bind an array of union, say I have:

typedef union A_t {
  uint32_t full;
  struct {
     uint32_t x:16;
     uint32_t y:16;
  }f;
} A_t;

union B_t {
   struct {
       A_t  regs[10];
   } r ;
} B_t;

if I want to create a pybind11::array of that union I'd have to declare its DTYPE:

PYBIND11_NUMPY_DTYPE(A_t, full, f.x, f.y)

py::class_<B_t>(m, "B_t")
        .def(py::init<>())
        .def_property("regs", [](B_t &self) -> pybind11::array {
            auto dtype = py::dtype(pybind11::format_descriptor<A_t>::format());
            auto base = py::array(dtype, {10}, {sizeof(A_t)});
            return py::array(
                dtype, {10}, {sizeof(A_t)}, self.r.regs, base);
        }, [](B_t& ) {})

but I'm getting error: error: cannot compute offset of bit-field 'x'.

Any suggestions on how to do that?

asherungar avatar Apr 13 '20 21:04 asherungar

Interesting example. I can't weigh in on whether or not this can be done with pybind11::array, but I would expect that if it were possible, it would be very difficult to implement safely. The memory layout of bitfields are not standardized as far as I know and may be different in different compilers and architectures (endianness problems are a real pain with bitfields!) There are probably some compiler-specific pragmas that need to be set in order for it to work reliably.

The reason it is not working is because there is some code that needs to take the address of one of the bitfields for some reason or another, and that is not allowed in C++ (and probably not in C either.) I'm assuming the address of .x and .y are needed for this to work:

PYBIND11_NUMPY_DTYPE(A_t, full, f.x, f.y);

If it is possible in your case to not use bitfields, I would recommend that. For instance, why not use a uint16_t for x and y? Don't forget to force the packing correctly (I just use 1 all the time for stuff like this) using whatever pragma your compiler needs for it.

Maybe try this instead:

#pragma pack(push,1)
typedef union A_t {
  uint32_t full;
  struct {
      uint16_t x;
      uint16_t y;
  }f;
} A_t;
#pragma pack(pop)

If it doesn't work for your real code (e.g. because you have non-byte aligned bitfield elements,) then I think you will need a much more sophisticated solution.

Good luck!

carlsonmark avatar Apr 13 '20 22:04 carlsonmark

sadly i cannot change the union structures.

asherungar avatar Apr 14 '20 07:04 asherungar

what if i "help" it? add a binding for A_t and then use it to return an array of that binded type?

py::class_<A_t>(m, "A_t")
        .def(py::init<>())
        .def_property("x",
            [](A_t& self) -> int32_t
            {
                return self.f.x;
            },
            [](A_t& self, const int32_t value )
            {
                self.f.x = value;
            })
       .def_property("y",
            [](A_t& self) -> int32_t
            {
                return self.f.y;
            },
            [](A_t& self, const int32_t value )
            {
                self.f.y = value;
            })
    ;

asherungar avatar Apr 14 '20 10:04 asherungar

That's definitely worth a try. Let me know how it goes.

carlsonmark avatar Apr 14 '20 16:04 carlsonmark

I meant I dont know how to do that :) it was an idea but I need help with implementing..

asherungar avatar Apr 14 '20 16:04 asherungar

what if i "help" it? add a binding for A_t and then use it to return an array of that binded type?

py::class_<A_t>(m, "A_t")
        .def(py::init<>())
        .def_property("x",
            [](A_t& self) -> int32_t
            {
                return self.f.x;
            },
            [](A_t& self, const int32_t value )
            {
                self.f.x = value;
            })
       .def_property("y",
            [](A_t& self) -> int32_t
            {
                return self.f.y;
            },
            [](A_t& self, const int32_t value )
            {
                self.f.y = value;
            })
    ;

It work,

Delay-n-days avatar Sep 06 '24 06:09 Delay-n-days