/* -*- 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 /* 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; size += member_size; } else have_error = TRUE; } if (have_error) field->offset = -1; } 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); }