Commit 9de24a3ff31c7446de29c1adcd9276fd7ba93897

Anthony Green 2016-03-14T13:54:53

Merge pull request #212 from tromey/struct-layout add ffi_get_struct_offsets

diff --git a/doc/libffi.texi b/doc/libffi.texi
index 5c9fddd..94b7a9e 100644
--- a/doc/libffi.texi
+++ b/doc/libffi.texi
@@ -440,7 +440,8 @@ on the chosen ABI.
 
 @item
 The size and alignment of a new structure type will not be set by
-@code{libffi} until it has been passed to @code{ffi_prep_cif}.
+@code{libffi} until it has been passed to @code{ffi_prep_cif} or
+@code{ffi_get_struct_offsets}.
 
 @item
 A structure type cannot be shared across ABIs.  Instead each ABI needs
@@ -448,8 +449,9 @@ its own copy of the structure type.
 @end itemize
 
 So, before examining these fields, it is safest to pass the
-@code{ffi_type} object to @code{ffi_prep_cif} first.  This function
-will do all the needed setup.
+@code{ffi_type} object to @code{ffi_prep_cif} or
+@code{ffi_get_struct_offsets} first.  This function will do all the
+needed setup.
 
 @example
 ffi_type *desired_type;
@@ -463,6 +465,28 @@ if (ffi_prep_cif (&cif, desired_abi, 0, desired_type, NULL) == FFI_OK)
   @}
 @end example
 
+@code{libffi} also provides a way to get the offsets of the members of
+a structure.
+
+@findex ffi_get_struct_offsets
+@defun ffi_status ffi_get_struct_offsets (ffi_abi abi, ffi_type *struct_type, size_t *offsets)
+Compute the offset of each element of the given structure type.
+@var{abi} is the ABI to use; this is needed because in some cases the
+layout depends on the ABI.
+
+@var{sizes} is an out parameter.  The caller is responsible for
+providing enough space for all the results to be written -- one
+element per element type in @var{struct_type}.  If @var{sizes} is
+@code{NULL}, then the type will be laid out but not otherwise
+modified.  This can be useful for accessing the type's size or layout,
+as mentioned above.
+
+This function returns @code{FFI_OK} on success; @code{FFI_BAD_ABI} if
+@var{abi} is invalid; or @code{FFI_BAD_TYPEDEF} if @var{struct_type}
+is invalid in some way.  Note that only @code{FFI_STRUCT} types are
+valid here.
+@end defun
+
 @node Arrays Unions Enums
 @subsection Arrays, Unions, and Enumerations
 
diff --git a/include/ffi.h.in b/include/ffi.h.in
index cd3358f..9e65277 100644
--- a/include/ffi.h.in
+++ b/include/ffi.h.in
@@ -458,6 +458,9 @@ void ffi_call(ffi_cif *cif,
 	      void *rvalue,
 	      void **avalue);
 
+ffi_status ffi_get_struct_offsets (ffi_abi abi, ffi_type *struct_type,
+				   size_t *offsets);
+
 /* Useful for eliminating compiler warnings */
 #define FFI_FN(f) ((void (*)(void))f)
 
diff --git a/src/prep_cif.c b/src/prep_cif.c
index 5881ceb..43f0487 100644
--- a/src/prep_cif.c
+++ b/src/prep_cif.c
@@ -34,7 +34,7 @@
 /* Perform machine independent initialization of aggregate type
    specifications. */
 
-static ffi_status initialize_aggregate(ffi_type *arg)
+static ffi_status initialize_aggregate(ffi_type *arg, size_t *offsets)
 {
   ffi_type **ptr;
 
@@ -52,13 +52,15 @@ static ffi_status initialize_aggregate(ffi_type *arg)
   while ((*ptr) != NULL)
     {
       if (UNLIKELY(((*ptr)->size == 0)
-		    && (initialize_aggregate((*ptr)) != FFI_OK)))
+		    && (initialize_aggregate((*ptr), NULL) != FFI_OK)))
 	return FFI_BAD_TYPEDEF;
 
       /* Perform a sanity check on the argument type */
       FFI_ASSERT_VALID_TYPE(*ptr);
 
       arg->size = ALIGN(arg->size, (*ptr)->alignment);
+      if (offsets)
+	*offsets++ = arg->size;
       arg->size += (*ptr)->size;
 
       arg->alignment = (arg->alignment > (*ptr)->alignment) ?
@@ -133,7 +135,8 @@ ffi_status FFI_HIDDEN ffi_prep_cif_core(ffi_cif *cif, ffi_abi abi,
 #endif
 
   /* Initialize the return type if necessary */
-  if ((cif->rtype->size == 0) && (initialize_aggregate(cif->rtype) != FFI_OK))
+  if ((cif->rtype->size == 0)
+      && (initialize_aggregate(cif->rtype, NULL) != FFI_OK))
     return FFI_BAD_TYPEDEF;
 
 #ifndef FFI_TARGET_HAS_COMPLEX_TYPE
@@ -164,7 +167,8 @@ ffi_status FFI_HIDDEN ffi_prep_cif_core(ffi_cif *cif, ffi_abi abi,
     {
 
       /* Initialize any uninitialized aggregate type definitions */
-      if (((*ptr)->size == 0) && (initialize_aggregate((*ptr)) != FFI_OK))
+      if (((*ptr)->size == 0)
+	  && (initialize_aggregate((*ptr), NULL) != FFI_OK))
 	return FFI_BAD_TYPEDEF;
 
 #ifndef FFI_TARGET_HAS_COMPLEX_TYPE
@@ -240,3 +244,18 @@ ffi_prep_closure (ffi_closure* closure,
 }
 
 #endif
+
+ffi_status
+ffi_get_struct_offsets (ffi_abi abi, ffi_type *struct_type, size_t *offsets)
+{
+  if (! (abi > FFI_FIRST_ABI && abi < FFI_LAST_ABI))
+    return FFI_BAD_ABI;
+  if (struct_type->type != FFI_TYPE_STRUCT)
+    return FFI_BAD_TYPEDEF;
+
+#if HAVE_LONG_DOUBLE_VARIANT
+  ffi_prep_types (abi);
+#endif
+
+  return initialize_aggregate(struct_type, offsets);
+}
diff --git a/testsuite/libffi.call/offsets.c b/testsuite/libffi.call/offsets.c
new file mode 100644
index 0000000..23d88b3
--- /dev/null
+++ b/testsuite/libffi.call/offsets.c
@@ -0,0 +1,46 @@
+/* Area:		Struct layout
+   Purpose:		Test ffi_get_struct_offsets
+   Limitations:		none.
+   PR:			none.
+   Originator: 		Tom Tromey. */
+
+/* { dg-do run } */
+#include "ffitest.h"
+#include <stddef.h>
+
+struct test_1
+{
+  char c;
+  float f;
+  char c2;
+  int i;
+};
+
+int
+main (void)
+{
+  ffi_type test_1_type;
+  ffi_type *test_1_elements[5];
+  size_t test_1_offsets[4];
+
+  test_1_elements[0] = &ffi_type_schar;
+  test_1_elements[1] = &ffi_type_float;
+  test_1_elements[2] = &ffi_type_schar;
+  test_1_elements[3] = &ffi_type_sint;
+  test_1_elements[4] = NULL;
+
+  test_1_type.size = 0;
+  test_1_type.alignment = 0;
+  test_1_type.type = FFI_TYPE_STRUCT;
+  test_1_type.elements = test_1_elements;
+
+  CHECK (ffi_get_struct_offsets (FFI_DEFAULT_ABI, &test_1_type, test_1_offsets)
+	 == FFI_OK);
+  CHECK (test_1_type.size == sizeof (struct test_1));
+  CHECK (offsetof (struct test_1, c) == test_1_offsets[0]);
+  CHECK (offsetof (struct test_1, f) == test_1_offsets[1]);
+  CHECK (offsetof (struct test_1, c2) == test_1_offsets[2]);
+  CHECK (offsetof (struct test_1, i) == test_1_offsets[3]);
+
+  return 0;
+}