/* Basic data types for Objective C.
  Copyright (C) 1998-2024 Free Software Foundation, Inc.
  Contributed by Ovidiu Predescu.

This file is part of GCC.

GCC is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.

GCC 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 General Public License for more details.

Under Section 7 of GPL version 3, you are granted additional
permissions described in the GCC Runtime Library Exception, version
3.1, as published by the Free Software Foundation.

You should have received a copy of the GNU General Public License and
a copy of the GCC Runtime Library Exception along with this program;
see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
<http://www.gnu.org/licenses/>.  */

#include "objc-private/common.h"
#include "objc/objc.h"

#if OBJC_WITH_GC

#include "tconfig.h"
#include <assert.h>
#include <ctype.h> /* For isdigit.  */
#include <string.h>
#include <stdlib.h>
#include "objc/runtime.h"
#include "objc-private/module-abi-8.h"

#include <gc/gc.h>
#include <limits.h>

/* gc_typed.h uses the following but doesn't declare them */
typedef GC_word word;
typedef GC_signed_word signed_word;
#define BITS_PER_WORD (CHAR_BIT * sizeof (word))

#include <gc/gc_typed.h>

/* The following functions set up in `mask` the corresponding pointers.
  The offset is incremented with the size of the type.  */

#define ROUND(V, A) \
 ({ typeof (V) __v = (V); typeof (A) __a = (A); \
    __a * ((__v+__a - 1)/__a); })

#define SET_BIT_FOR_OFFSET(mask, offset) \
 GC_set_bit (mask, offset / sizeof (void *))

/* Some prototypes */
static void
__objc_gc_setup_struct (GC_bitmap mask, const char *type, int offset);
static void
__objc_gc_setup_union (GC_bitmap mask, const char *type, int offset);


static void
__objc_gc_setup_array (GC_bitmap mask, const char *type, int offset)
{
 int i, len = atoi (type + 1);

 while (isdigit (*++type))
   /* do nothing */;           /* skip the size of the array */

 switch (*type) {
 case _C_ARY_B:
   for (i = 0; i < len; i++)
     __objc_gc_setup_array (mask, type, offset);
   break;

 case _C_STRUCT_B:
   for (i = 0; i < len; i++)
     __objc_gc_setup_struct (mask, type, offset);
   break;

 case _C_UNION_B:
   for (i = 0; i < len; i++)
     __objc_gc_setup_union (mask, type, offset);
   break;

 default:
   break;
 }
}

static void
__objc_gc_setup_struct (GC_bitmap mask, const char *type, int offset)
{
 struct objc_struct_layout layout;
 unsigned int position;
 const char *mtype;

 objc_layout_structure (type, &layout);

 while (objc_layout_structure_next_member (&layout))
   {
     BOOL gc_invisible = NO;

     objc_layout_structure_get_info (&layout, &position, NULL, &mtype);

     /* Skip the variable name */
     if (*mtype == '"')
       {
         for (mtype++; *mtype++ != '"';)
           /* do nothing */;
       }

     if (*mtype == _C_GCINVISIBLE)
       {
         gc_invisible = YES;
         mtype++;
       }

     /* Add to position the offset of this structure */
     position += offset;

     switch (*mtype) {
     case _C_ID:
     case _C_CLASS:
     case _C_SEL:
     case _C_PTR:
     case _C_CHARPTR:
     case _C_ATOM:
       if (! gc_invisible)
         SET_BIT_FOR_OFFSET (mask, position);
       break;

     case _C_ARY_B:
       __objc_gc_setup_array (mask, mtype, position);
       break;

     case _C_STRUCT_B:
       __objc_gc_setup_struct (mask, mtype, position);
       break;

     case _C_UNION_B:
       __objc_gc_setup_union (mask, mtype, position);
       break;

     default:
       break;
     }
   }
}

static void
__objc_gc_setup_union (GC_bitmap mask, const char *type, int offset)
{
 /* Sub-optimal, quick implementation: assume the union is made of
    pointers, set up the mask accordingly. */

 int i, size, align;

 /* Skip the variable name */
 if (*type == '"')
   {
     for (type++; *type++ != '"';)
       /* do nothing */;
   }

 size = objc_sizeof_type (type);
 align = objc_alignof_type (type);

 offset = ROUND (offset, align);
 for (i = 0; i < size; i += sizeof (void *))
   {
     SET_BIT_FOR_OFFSET (mask, offset);
     offset += sizeof (void *);
   }
}


/* Iterates over the types in the structure that represents the class
  encoding and sets the bits in mask according to each ivar type.  */
static void
__objc_gc_type_description_from_type (GC_bitmap mask, const char *type)
{
 struct objc_struct_layout layout;
 unsigned int offset, align;
 const char *ivar_type;

 objc_layout_structure (type, &layout);

 while (objc_layout_structure_next_member (&layout))
   {
     BOOL gc_invisible = NO;

     objc_layout_structure_get_info (&layout, &offset, &align, &ivar_type);

     /* Skip the variable name */
     if (*ivar_type == '"')
       {
         for (ivar_type++; *ivar_type++ != '"';)
           /* do nothing */;
       }

     if (*ivar_type == _C_GCINVISIBLE)
       {
         gc_invisible = YES;
         ivar_type++;
       }

     switch (*ivar_type) {
     case _C_ID:
     case _C_CLASS:
     case _C_SEL:
     case _C_PTR:
     case _C_CHARPTR:
       if (! gc_invisible)
         SET_BIT_FOR_OFFSET (mask, offset);
       break;

     case _C_ARY_B:
       __objc_gc_setup_array (mask, ivar_type, offset);
       break;

     case _C_STRUCT_B:
       __objc_gc_setup_struct (mask, ivar_type, offset);
       break;

     case _C_UNION_B:
       __objc_gc_setup_union (mask, ivar_type, offset);
       break;

     default:
       break;
     }
   }
}

/* Computes in *type the full type encoding of this class including
  its super classes. '*size' gives the total number of bytes allocated
  into *type, '*current' the number of bytes used so far by the
  encoding. */
static void
__objc_class_structure_encoding (Class class, char **type, int *size,
                                int *current)
{
 int i, ivar_count;
 struct objc_ivar_list *ivars;

 if (! class)
   {
     strcat (*type, "{");
     (*current)++;
     return;
   }

 /* Add the type encodings of the super classes */
 __objc_class_structure_encoding (class->super_class, type, size, current);

 ivars = class->ivars;
 if (! ivars)
   return;

 ivar_count = ivars->ivar_count;

 for (i = 0; i < ivar_count; i++)
   {
     struct objc_ivar *ivar = &(ivars->ivar_list[i]);
     const char *ivar_type = ivar->ivar_type;
     int len = strlen (ivar_type);

     if (*current + len + 1 >= *size)
       {
         /* Increase the size of the encoding string so that it
            contains this ivar's type. */
         *size = ROUND (*current + len + 1, 10);
         *type = objc_realloc (*type, *size);
       }
     strcat (*type + *current, ivar_type);
     *current += len;
   }
}


/* Allocates the memory that will hold the type description for class
  and calls the __objc_class_structure_encoding that generates this
  value. */
void
__objc_generate_gc_type_description (Class class)
{
 GC_bitmap mask;
 int bits_no, size;
 int type_size = 10, current;
 char *class_structure_type;

 if (! CLS_ISCLASS (class))
   return;

 /* We have to create a mask in which each bit counts for a pointer member.
    We take into consideration all the non-pointer instance variables and we
    round them up to the alignment. */

 /* The number of bits in the mask is the size of an instance in bytes divided
    by the size of a pointer. */
 bits_no = (ROUND (class_getInstanceSize (class), sizeof (void *))
            / sizeof (void *));
 size = ROUND (bits_no, BITS_PER_WORD) / BITS_PER_WORD;
 mask = objc_atomic_malloc (size * sizeof (int));
 memset (mask, 0, size * sizeof (int));

 class_structure_type = objc_atomic_malloc (type_size);
 *class_structure_type = current = 0;
 __objc_class_structure_encoding (class, &class_structure_type,
                                  &type_size, &current);
 if (current + 1 == type_size)
   class_structure_type = objc_realloc (class_structure_type, ++type_size);
 strcat (class_structure_type + current, "}");
#ifdef DEBUG
 printf ("type description for '%s' is %s\n", class->name, class_structure_type);
#endif

 __objc_gc_type_description_from_type (mask, class_structure_type);
 objc_free (class_structure_type);

#ifdef DEBUG
 printf ("  mask for '%s', type '%s' (bits %d, mask size %d) is:",
         class_structure_type, class->name, bits_no, size);
 {
   int i;
   for (i = 0; i < size; i++)
     printf (" %lx", mask[i]);
 }
 puts ("");
#endif

 class->gc_object_type = (void *) GC_make_descriptor (mask, bits_no);
}


/* Returns YES if type denotes a pointer type, NO otherwise */
static inline BOOL
__objc_ivar_pointer (const char *type)
{
 type = objc_skip_type_qualifiers (type);

 return (*type == _C_ID
         || *type == _C_CLASS
         || *type == _C_SEL
         || *type == _C_PTR
         || *type == _C_CHARPTR
         || *type == _C_ATOM);
}


/* Mark the instance variable whose name is given by ivarname as a
  weak pointer (a pointer hidden to the garbage collector) if
  gc_invisible is true. If gc_invisible is false it unmarks the
  instance variable and makes it a normal pointer, visible to the
  garbage collector.

  This operation only makes sense on instance variables that are
  pointers.  */
void
class_ivar_set_gcinvisible (Class class, const char *ivarname,
                           BOOL gc_invisible)
{
 int i, ivar_count;
 struct objc_ivar_list *ivars;

 if (! class || ! ivarname)
   return;

 ivars = class->ivars;
 if (! ivars)
   return;

 ivar_count = ivars->ivar_count;

 for (i = 0; i < ivar_count; i++)
   {
     struct objc_ivar *ivar = &(ivars->ivar_list[i]);
     const char *type;

     if (! ivar->ivar_name || strcmp (ivar->ivar_name, ivarname))
       continue;

     assert (ivar->ivar_type);
     type = ivar->ivar_type;

     /* Skip the variable name */
     if (*type == '"')
       {
         for (type++; *type++ != '"';)
           /* do nothing */;
       }

     if (*type == _C_GCINVISIBLE)
       {
         char *new_type;
         size_t len;

         if (gc_invisible || ! __objc_ivar_pointer (type))
           return;     /* The type of the variable already matches the
                          requested gc_invisible type */

         /* The variable is gc_invisible so we make it gc visible.  */
         new_type = objc_atomic_malloc (strlen(ivar->ivar_type));
         len = (type - ivar->ivar_type);
         memcpy (new_type, ivar->ivar_type, len);
         new_type[len] = 0;
         strcat (new_type, type + 1);
         ivar->ivar_type = new_type;
       }
     else
       {
         char *new_type;
         size_t len;

         if (! gc_invisible || ! __objc_ivar_pointer (type))
           return;     /* The type of the variable already matches the
                          requested gc_invisible type */

         /* The variable is gc visible so we make it gc_invisible.  */
         new_type = objc_malloc (strlen(ivar->ivar_type) + 2);

         /* Copy the variable name.  */
         len = (type - ivar->ivar_type);
         memcpy (new_type, ivar->ivar_type, len);
         /* Add '!'.  */
         new_type[len++] = _C_GCINVISIBLE;
         /* Copy the original types.  */
         strcpy (new_type + len, type);

         ivar->ivar_type = new_type;
       }

     __objc_generate_gc_type_description (class);
     return;
   }

 /* Search the instance variable in the superclasses */
 class_ivar_set_gcinvisible (class->super_class, ivarname, gc_invisible);
}

#else /* !OBJC_WITH_GC */

void
__objc_generate_gc_type_description (Class class __attribute__ ((__unused__)))
{
}

void class_ivar_set_gcinvisible (Class class __attribute__ ((__unused__)),
                                const char *ivarname __attribute__ ((__unused__)),
                                BOOL gc_invisible __attribute__ ((__unused__)))
{
}

#endif /* OBJC_WITH_GC */