| /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- |
| * GObject introspection: Compute structure offsets |
| * |
| * Copyright (C) 2008 Red Hat, Inc. |
| * |
| * SPDX-License-Identifier: LGPL-2.1-or-later |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the |
| * Free Software Foundation, Inc., 59 Temple Place - Suite 330, |
| * Boston, MA 02111-1307, USA. |
| */ |
| |
| #include "girffi.h" |
| |
| #include "girnode-private.h" |
| |
| #include <string.h> |
| |
| /* The C standard specifies that an enumeration can be any char or any signed |
| * or unsigned integer type capable of representing all the values of the |
| * enumeration. We use test enumerations to figure out what choices the |
| * compiler makes. (Ignoring > 32 bit enumerations) |
| */ |
| |
| typedef enum { |
| ENUM_1 = 1 /* compiler could use int8, uint8, int16, uint16, int32, uint32 */ |
| } Enum1; |
| |
| typedef enum { |
| ENUM_2 = 128 /* compiler could use uint8, int16, uint16, int32, uint32 */ |
| } Enum2; |
| |
| typedef enum { |
| ENUM_3 = 257 /* compiler could use int16, uint16, int32, uint32 */ |
| } Enum3; |
| |
| typedef enum { |
| ENUM_4 = G_MAXSHORT + 1 /* compiler could use uint16, int32, uint32 */ |
| } Enum4; |
| |
| typedef enum { |
| ENUM_5 = G_MAXUSHORT + 1 /* compiler could use int32, uint32 */ |
| } Enum5; |
| |
| typedef enum { |
| ENUM_6 = ((unsigned int)G_MAXINT) + 1 /* compiler could use uint32 */ |
| } Enum6; |
| |
| typedef enum { |
| ENUM_7 = -1 /* compiler could use int8, int16, int32 */ |
| } Enum7; |
| |
| typedef enum { |
| ENUM_8 = -129 /* compiler could use int16, int32 */ |
| } Enum8; |
| |
| typedef enum { |
| ENUM_9 = G_MINSHORT - 1 /* compiler could use int32 */ |
| } Enum9; |
| |
| static void |
| compute_enum_storage_type (GIIrNodeEnum *enum_node) |
| { |
| GList *l; |
| int64_t max_value = 0; |
| int64_t min_value = 0; |
| int width; |
| gboolean signed_type; |
| |
| if (enum_node->storage_type != GI_TYPE_TAG_VOID) /* already done */ |
| return; |
| |
| for (l = enum_node->values; l; l = l->next) |
| { |
| GIIrNodeValue *value = l->data; |
| if (value->value > max_value) |
| max_value = value->value; |
| if (value->value < min_value) |
| min_value = value->value; |
| } |
| |
| if (min_value < 0) |
| { |
| signed_type = TRUE; |
| |
| if (min_value > -128 && max_value <= 127) |
| width = sizeof(Enum7); |
| else if (min_value >= G_MINSHORT && max_value <= G_MAXSHORT) |
| width = sizeof(Enum8); |
| else |
| width = sizeof(Enum9); |
| } |
| else |
| { |
| if (max_value <= 127) |
| { |
| width = sizeof (Enum1); |
| signed_type = (int64_t)(Enum1)(-1) < 0; |
| } |
| else if (max_value <= 255) |
| { |
| width = sizeof (Enum2); |
| signed_type = (int64_t)(Enum2)(-1) < 0; |
| } |
| else if (max_value <= G_MAXSHORT) |
| { |
| width = sizeof (Enum3); |
| signed_type = (int64_t)(Enum3)(-1) < 0; |
| } |
| else if (max_value <= G_MAXUSHORT) |
| { |
| width = sizeof (Enum4); |
| signed_type = (int64_t)(Enum4)(-1) < 0; |
| } |
| else if (max_value <= G_MAXINT) |
| { |
| width = sizeof (Enum5); |
| signed_type = (int64_t)(Enum5)(-1) < 0; |
| } |
| else |
| { |
| width = sizeof (Enum6); |
| signed_type = (int64_t)(Enum6)(-1) < 0; |
| } |
| } |
| |
| if (width == 1) |
| enum_node->storage_type = signed_type ? GI_TYPE_TAG_INT8 : GI_TYPE_TAG_UINT8; |
| else if (width == 2) |
| enum_node->storage_type = signed_type ? GI_TYPE_TAG_INT16 : GI_TYPE_TAG_UINT16; |
| else if (width == 4) |
| enum_node->storage_type = signed_type ? GI_TYPE_TAG_INT32 : GI_TYPE_TAG_UINT32; |
| else if (width == 8) |
| enum_node->storage_type = signed_type ? GI_TYPE_TAG_INT64 : GI_TYPE_TAG_UINT64; |
| else |
| g_error ("Unexpected enum width %d", width); |
| } |
| |
| static gboolean |
| get_enum_size_alignment (GIIrNodeEnum *enum_node, |
| size_t *size, |
| size_t *alignment) |
| { |
| ffi_type *type_ffi; |
| |
| compute_enum_storage_type (enum_node); |
| |
| switch (enum_node->storage_type) |
| { |
| case GI_TYPE_TAG_INT8: |
| case GI_TYPE_TAG_UINT8: |
| type_ffi = &ffi_type_uint8; |
| break; |
| case GI_TYPE_TAG_INT16: |
| case GI_TYPE_TAG_UINT16: |
| type_ffi = &ffi_type_uint16; |
| break; |
| case GI_TYPE_TAG_INT32: |
| case GI_TYPE_TAG_UINT32: |
| type_ffi = &ffi_type_uint32; |
| break; |
| case GI_TYPE_TAG_INT64: |
| case GI_TYPE_TAG_UINT64: |
| type_ffi = &ffi_type_uint64; |
| break; |
| default: |
| g_error ("Unexpected enum storage type %s", |
| gi_type_tag_to_string (enum_node->storage_type)); |
| } |
| |
| *size = type_ffi->size; |
| *alignment = type_ffi->alignment; |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| get_interface_size_alignment (GIIrTypelibBuild *build, |
| GIIrNodeType *type, |
| size_t *size, |
| size_t *alignment, |
| const char *who) |
| { |
| GIIrNode *iface; |
| |
| iface = gi_ir_find_node (build, ((GIIrNode*)type)->module, type->giinterface); |
| if (!iface) |
| { |
| gi_ir_module_fatal (build, 0, "Can't resolve type '%s' for %s", type->giinterface, who); |
| *size = 0; |
| *alignment = 0; |
| return FALSE; |
| } |
| |
| gi_ir_node_compute_offsets (build, iface); |
| |
| switch (iface->type) |
| { |
| case GI_IR_NODE_BOXED: |
| { |
| GIIrNodeBoxed *boxed = (GIIrNodeBoxed *)iface; |
| *size = boxed->size; |
| *alignment = boxed->alignment; |
| break; |
| } |
| case GI_IR_NODE_STRUCT: |
| { |
| GIIrNodeStruct *struct_ = (GIIrNodeStruct *)iface; |
| *size = struct_->size; |
| *alignment = struct_->alignment; |
| break; |
| } |
| case GI_IR_NODE_OBJECT: |
| case GI_IR_NODE_INTERFACE: |
| { |
| GIIrNodeInterface *interface = (GIIrNodeInterface *)iface; |
| *size = interface->size; |
| *alignment = interface->alignment; |
| break; |
| } |
| case GI_IR_NODE_UNION: |
| { |
| GIIrNodeUnion *union_ = (GIIrNodeUnion *)iface; |
| *size = union_->size; |
| *alignment = union_->alignment; |
| break; |
| } |
| case GI_IR_NODE_ENUM: |
| case GI_IR_NODE_FLAGS: |
| { |
| return get_enum_size_alignment ((GIIrNodeEnum *)iface, |
| size, alignment); |
| } |
| case GI_IR_NODE_CALLBACK: |
| { |
| *size = ffi_type_pointer.size; |
| *alignment = ffi_type_pointer.alignment; |
| break; |
| } |
| default: |
| { |
| g_warning ("%s has is not a pointer and is of type %s", |
| who, |
| gi_ir_node_type_to_string (iface->type)); |
| *size = 0; |
| *alignment = 0; |
| return FALSE; |
| } |
| } |
| |
| return *alignment > 0; |
| } |
| |
| static gboolean |
| get_type_size_alignment (GIIrTypelibBuild *build, |
| GIIrNodeType *type, |
| size_t *size, |
| size_t *alignment, |
| const char *who) |
| { |
| ffi_type *type_ffi; |
| |
| if (type->is_pointer) |
| { |
| type_ffi = &ffi_type_pointer; |
| } |
| else if (type->tag == GI_TYPE_TAG_ARRAY) |
| { |
| size_t elt_size; |
| size_t elt_alignment; |
| |
| if (!type->has_size |
| || !get_type_size_alignment(build, type->parameter_type1, |
| &elt_size, &elt_alignment, who)) |
| { |
| *size = 0; |
| *alignment = 0; |
| return FALSE; |
| } |
| |
| *size = type->size * elt_size; |
| *alignment = elt_alignment; |
| |
| return TRUE; |
| } |
| else |
| { |
| if (type->tag == GI_TYPE_TAG_INTERFACE) |
| { |
| return get_interface_size_alignment (build, type, size, alignment, who); |
| } |
| else |
| { |
| type_ffi = gi_type_tag_get_ffi_type (type->tag, type->is_pointer); |
| |
| if (type_ffi == &ffi_type_void) |
| { |
| g_warning ("%s has void type", who); |
| *size = 0; |
| *alignment = 0; |
| return FALSE; |
| } |
| else if (type_ffi == &ffi_type_pointer) |
| { |
| g_warning ("%s has is not a pointer and is of type %s", |
| who, |
| gi_type_tag_to_string (type->tag)); |
| *size = 0; |
| *alignment = 0; |
| return FALSE; |
| } |
| } |
| } |
| |
| g_assert (type_ffi); |
| *size = type_ffi->size; |
| *alignment = type_ffi->alignment; |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| get_field_size_alignment (GIIrTypelibBuild *build, |
| GIIrNodeField *field, |
| GIIrNode *parent_node, |
| size_t *size, |
| size_t *alignment) |
| { |
| GIIrModule *module = build->module; |
| char *who; |
| gboolean success; |
| |
| who = g_strdup_printf ("field %s.%s.%s", module->name, parent_node->name, ((GIIrNode *)field)->name); |
| |
| if (field->callback) |
| { |
| *size = ffi_type_pointer.size; |
| *alignment = ffi_type_pointer.alignment; |
| success = TRUE; |
| } |
| else |
| success = get_type_size_alignment (build, field->type, size, alignment, who); |
| g_free (who); |
| |
| return success; |
| } |
| |
| #define GI_ALIGN(n, align) (((n) + (align) - 1) & ~((align) - 1)) |
| |
| static gboolean |
| compute_struct_field_offsets (GIIrTypelibBuild *build, |
| GIIrNode *node, |
| GList *members, |
| size_t *size_out, |
| size_t *alignment_out, |
| GIIrOffsetsState *offsets_state_out) |
| { |
| size_t size = 0; |
| size_t alignment = 1; |
| GList *l; |
| gboolean have_error = FALSE; |
| |
| *offsets_state_out = GI_IR_OFFSETS_IN_PROGRESS; /* mark to detect recursion */ |
| |
| for (l = members; l; l = l->next) |
| { |
| GIIrNode *member = (GIIrNode *)l->data; |
| |
| if (member->type == GI_IR_NODE_FIELD) |
| { |
| GIIrNodeField *field = (GIIrNodeField *)member; |
| |
| if (!have_error) |
| { |
| size_t member_size; |
| size_t member_alignment; |
| |
| if (get_field_size_alignment (build, field, node, |
| &member_size, &member_alignment)) |
| { |
| size = GI_ALIGN (size, member_alignment); |
| alignment = MAX (alignment, member_alignment); |
| field->offset = size; |
| field->offset_state = GI_IR_OFFSETS_COMPUTED; |
| size += member_size; |
| } |
| else |
| have_error = TRUE; |
| } |
| |
| if (have_error) |
| { |
| field->offset = 0; |
| field->offset_state = GI_IR_OFFSETS_FAILED; |
| } |
| } |
| else if (member->type == GI_IR_NODE_CALLBACK) |
| { |
| size = GI_ALIGN (size, ffi_type_pointer.alignment); |
| alignment = MAX (alignment, ffi_type_pointer.alignment); |
| size += ffi_type_pointer.size; |
| } |
| } |
| |
| /* Structs are tail-padded out to a multiple of their alignment */ |
| size = GI_ALIGN (size, alignment); |
| |
| if (!have_error) |
| { |
| *size_out = size; |
| *alignment_out = alignment; |
| *offsets_state_out = GI_IR_OFFSETS_COMPUTED; |
| } |
| else |
| { |
| *size_out = 0; |
| *alignment_out = 0; |
| *offsets_state_out = GI_IR_OFFSETS_FAILED; |
| } |
| |
| return !have_error; |
| } |
| |
| static gboolean |
| compute_union_field_offsets (GIIrTypelibBuild *build, |
| GIIrNode *node, |
| GList *members, |
| size_t *size_out, |
| size_t *alignment_out, |
| GIIrOffsetsState *offsets_state_out) |
| { |
| size_t size = 0; |
| size_t alignment = 1; |
| GList *l; |
| gboolean have_error = FALSE; |
| |
| *offsets_state_out = GI_IR_OFFSETS_IN_PROGRESS; /* mark to detect recursion */ |
| |
| for (l = members; l; l = l->next) |
| { |
| GIIrNode *member = (GIIrNode *)l->data; |
| |
| if (member->type == GI_IR_NODE_FIELD) |
| { |
| GIIrNodeField *field = (GIIrNodeField *)member; |
| |
| if (!have_error) |
| { |
| size_t member_size; |
| size_t member_alignment; |
| |
| if (get_field_size_alignment (build,field, node, |
| &member_size, &member_alignment)) |
| { |
| size = MAX (size, member_size); |
| alignment = MAX (alignment, member_alignment); |
| } |
| else |
| have_error = TRUE; |
| } |
| } |
| } |
| |
| /* Unions are tail-padded out to a multiple of their alignment */ |
| size = GI_ALIGN (size, alignment); |
| |
| if (!have_error) |
| { |
| *size_out = size; |
| *alignment_out = alignment; |
| *offsets_state_out = GI_IR_OFFSETS_COMPUTED; |
| } |
| else |
| { |
| *size_out = 0; |
| *alignment_out = 0; |
| *offsets_state_out = GI_IR_OFFSETS_FAILED; |
| } |
| |
| return !have_error; |
| } |
| |
| static gboolean |
| check_needs_computation (GIIrTypelibBuild *build, |
| GIIrNode *node, |
| GIIrOffsetsState offsets_state) |
| { |
| GIIrModule *module = build->module; |
| |
| if (offsets_state == GI_IR_OFFSETS_IN_PROGRESS) |
| { |
| g_warning ("Recursion encountered when computing the size of %s.%s", |
| module->name, node->name); |
| } |
| |
| return offsets_state == GI_IR_OFFSETS_UNKNOWN; |
| } |
| |
| /* |
| * gi_ir_node_compute_offsets: |
| * @build: Current typelib build |
| * @node: a #GIIrNode |
| * |
| * If a node is a a structure or union, makes sure that the field |
| * offsets have been computed, and also computes the overall size and |
| * alignment for the type. |
| * |
| * Since: 2.80 |
| */ |
| void |
| gi_ir_node_compute_offsets (GIIrTypelibBuild *build, |
| GIIrNode *node) |
| { |
| gboolean appended_stack; |
| |
| if (build->stack) |
| appended_stack = node != (GIIrNode*)build->stack->data; |
| else |
| appended_stack = TRUE; |
| if (appended_stack) |
| build->stack = g_list_prepend (build->stack, node); |
| |
| switch (node->type) |
| { |
| case GI_IR_NODE_BOXED: |
| { |
| GIIrNodeBoxed *boxed = (GIIrNodeBoxed *)node; |
| |
| if (!check_needs_computation (build, node, boxed->offsets_state)) |
| return; |
| |
| compute_struct_field_offsets (build, node, boxed->members, |
| &boxed->size, &boxed->alignment, &boxed->offsets_state); |
| break; |
| } |
| case GI_IR_NODE_STRUCT: |
| { |
| GIIrNodeStruct *struct_ = (GIIrNodeStruct *)node; |
| |
| if (!check_needs_computation (build, node, struct_->offsets_state)) |
| return; |
| |
| compute_struct_field_offsets (build, node, struct_->members, |
| &struct_->size, &struct_->alignment, &struct_->offsets_state); |
| break; |
| } |
| case GI_IR_NODE_OBJECT: |
| case GI_IR_NODE_INTERFACE: |
| { |
| GIIrNodeInterface *iface = (GIIrNodeInterface *)node; |
| |
| if (!check_needs_computation (build, node, iface->offsets_state)) |
| return; |
| |
| compute_struct_field_offsets (build, node, iface->members, |
| &iface->size, &iface->alignment, &iface->offsets_state); |
| break; |
| } |
| case GI_IR_NODE_UNION: |
| { |
| GIIrNodeUnion *union_ = (GIIrNodeUnion *)node; |
| |
| if (!check_needs_computation (build, node, union_->offsets_state)) |
| return; |
| |
| compute_union_field_offsets (build, (GIIrNode*)union_, union_->members, |
| &union_->size, &union_->alignment, &union_->offsets_state); |
| break; |
| } |
| case GI_IR_NODE_ENUM: |
| case GI_IR_NODE_FLAGS: |
| { |
| GIIrNodeEnum *enum_ = (GIIrNodeEnum *)node; |
| |
| if (enum_->storage_type != GI_TYPE_TAG_VOID) /* already done */ |
| return; |
| |
| compute_enum_storage_type (enum_); |
| |
| break; |
| } |
| default: |
| break; |
| } |
| |
| if (appended_stack) |
| build->stack = g_list_delete_link (build->stack, build->stack); |
| } |