[ocaml-ctypes] Bigarray typing problem
Jeremy Yallop
yallop at gmail.com
Thu Dec 5 11:08:38 GMT 2013
Hi Daniel,
On 4 December 2013 18:13, Daniel Bünzli <daniel.buenzli at erratique.ch> wrote:
> Suppose I have a C function like this that takes a 256 element array prototyped like this:
>
> void write (Uint16 *a);
>
> I would like to expose it as
>
> val write : (int, Bigarray.int16_unsigned_elt, Bigarray.c_layout) Bigarray.Array1.t -> unit
>
> tried something along
>
> let write = foreign "write" (ptr uint16_t @-> returning void)
> let write a = write (bigarray_start array1 a)
>
> But it fails on the second line with (for the parenthised expression) :
>
> Error: This expression has type int Ctypes.ptr
> but an expression was expected of type Unsigned.uint16 Ctypes.ptr
Right: this is by design, although I'm not certain that it's the best
possible design to use. The intention is to have the ctypes Bigarray
interface imitate the design of Bigarray itself, with a distinction
between storage behaviour and read/write type. For instance, using
the type in question,
((int, Bigarray.int16_unsigned_elt, Bigarray.c_layout) Bigarray.Array1.t)
the Bigarray modules stores the elements as 16-bit integers, but
exposes them (through indexing functions etc.) as ints. Ctypes does
the same: when you ask for a pointer into the bigarray you get back a
pointer that gives you an int when you dereference it, but behaves as
a pointer to 16-bit integers for purposes of arithmetic etc.:
# let ba = Array1.create int16_unsigned c_layout 5;;
val ba : (int, int16_unsigned_elt, c_layout) Array1.t = <abstr>
# let p = bigarray_start array1 ba;;
val p : int ptr = (int16_t*) 0xe5cb40
The type of p indicates that dereferencing gives you an int, but the
way the value of p is printed indicates that it's treated as a pointer
into an array of 16-bit integers. (There's a bug relating to
signedness -- the value should be "(uint16_t*) 0xe5cb40", but that's a
separate issue. I'll note it on the issue tracker.). You can see
that pointer arithmetic (for example) behaves correctly by accessing
bigarray elements through both the array and the pointer:
# for i = 0 to 4 do ba.{i} <- i done;;
- : unit = ()
# for i = 0 to 4 do Printf.printf "%d " !@(p +@ i) done;;
0 1 2 3 4 - : unit = ()
Similarly,
# sizeof (reference_type p);;
- : int = 2
> It seems to me that bigarray_start has the wrong type, it should map to a pointer of the *storage* type of the bigarray, not the type used to read/write the bigarray.
That would be a reasonable approach as well. I suppose it depends on
whether you want a bigarray-like interface or simply an interface that
accesses bigarray-managed memory.
I've tried changing the interface so that bigarray_start etc. return
pointers to storage types rather than to read/write types. You can
find it on the bigarray-kinds branch on my repository:
https://github.com/yallop/ocaml-ctypes/commit/0d15800310
(It appears to work, but it's only lightly tested and not polished.
It may be possible to simplify the types.)
The main changes are:
* there's now a family of bigarray_kind values -- ba_float32,
ba_float64, etc. -- that correspond to the kind values in the Bigarray
module. These should be used in place of Bigarray.kind values in the
ctypes Bigarray interface.
* the type of bigarray_class now includes an extra field for the
ctypes equivalent of the storage type
* bigarray_start and array_of_bigarray now take an additional
argument of type bigarray_kind to indicate the storage type of the
bigarray
With these changes in place you have the behaviour you were expecting:
# let write = foreign "write" (ptr uint16_t @-> returning void);;
val write : Unsigned.uint16 ptr -> unit = <fun>
# let write a = write (bigarray_start array1 ba_int16_unsigned a);;
val write : (int, int16_unsigned_elt, c_layout) Array1.t -> unit = <fun>
As I said, I'm not sure which interface is preferable overall.
Perhaps it's worth providing both.
Jeremy.
More information about the Ctypes
mailing list