misc.c raw

   1  /*
   2   * Copyright 1988, 1989 Hans-J. Boehm, Alan J. Demers
   3   * Copyright (c) 1991-1994 by Xerox Corporation.  All rights reserved.
   4   * Copyright (c) 1999-2001 by Hewlett-Packard Company. All rights reserved.
   5   * Copyright (c) 2008-2022 Ivan Maidanski
   6   *
   7   * THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED
   8   * OR IMPLIED.  ANY USE IS AT YOUR OWN RISK.
   9   *
  10   * Permission is hereby granted to use or copy this program
  11   * for any purpose, provided the above notices are retained on all copies.
  12   * Permission to modify the code and to distribute modified code is granted,
  13   * provided the above notices are retained, and a notice that the code was
  14   * modified is included with the above copyright notice.
  15   */
  16  
  17  #include "private/gc_pmark.h"
  18  
  19  #include <limits.h>
  20  #include <stdarg.h>
  21  
  22  #if defined(SOLARIS) && defined(THREADS)
  23  #  include <sys/syscall.h>
  24  #endif
  25  
  26  #if defined(UNIX_LIKE) || defined(CYGWIN32) || defined(SYMBIAN) \
  27      || (defined(CONSOLE_LOG) && defined(MSWIN32))
  28  #  include <fcntl.h>
  29  #  include <sys/stat.h>
  30  #endif
  31  
  32  #if defined(CONSOLE_LOG) && defined(MSWIN32) && !defined(__GNUC__)
  33  #  include <io.h>
  34  #endif
  35  
  36  #ifdef NONSTOP
  37  #  include <floss.h>
  38  #endif
  39  
  40  #ifdef THREADS
  41  #  if defined(SN_TARGET_PSP2)
  42  GC_INNER WapiMutex GC_allocate_ml_PSP2 = { 0, NULL };
  43  #  elif defined(GC_DEFN_ALLOCATE_ML) && !defined(USE_RWLOCK) \
  44        || defined(SN_TARGET_PS3)
  45  #    include <pthread.h>
  46  GC_INNER pthread_mutex_t GC_allocate_ml;
  47  #  else
  48  /*
  49   * For other platforms with threads, the allocator lock and, possibly,
  50   * `GC_lock_holder` are defined in the thread support code.
  51   */
  52  #  endif
  53  #endif /* THREADS */
  54  
  55  #ifdef DYNAMIC_LOADING
  56  /*
  57   * We need to register the main data segment.  Returns `TRUE` unless
  58   * this is done implicitly as part of dynamic library registration.
  59   */
  60  #  define GC_REGISTER_MAIN_STATIC_DATA() GC_register_main_static_data()
  61  #elif defined(GC_DONT_REGISTER_MAIN_STATIC_DATA)
  62  #  define GC_REGISTER_MAIN_STATIC_DATA() FALSE
  63  #else
  64  /*
  65   * Do not unnecessarily call `GC_register_main_static_data()` in case
  66   * `dyn_load.c` file is not linked in.
  67   */
  68  #  define GC_REGISTER_MAIN_STATIC_DATA() TRUE
  69  #endif
  70  
  71  #ifdef NEED_CANCEL_DISABLE_COUNT
  72  __thread unsigned char GC_cancel_disable_count = 0;
  73  #endif
  74  
  75  struct _GC_arrays GC_arrays /* `= { 0 }` */;
  76  
  77  GC_INNER unsigned GC_n_mark_procs = GC_RESERVED_MARK_PROCS;
  78  
  79  GC_INNER unsigned GC_n_kinds = GC_N_KINDS_INITIAL_VALUE;
  80  
  81  ptr_t GC_stackbottom = 0;
  82  
  83  #if defined(E2K) && defined(THREADS) || defined(IA64)
  84  GC_INNER ptr_t GC_register_stackbottom = NULL;
  85  #endif
  86  
  87  int GC_dont_gc = FALSE;
  88  
  89  int GC_dont_precollect = FALSE;
  90  
  91  GC_bool GC_quiet = 0; /*< used also in `msvc_dbg.c` file */
  92  
  93  #if !defined(NO_CLOCK) || !defined(SMALL_CONFIG)
  94  GC_INNER int GC_print_stats = 0;
  95  #endif
  96  
  97  #ifdef MAKE_BACK_GRAPH
  98  #  ifdef GC_PRINT_BACK_HEIGHT
  99  GC_INNER GC_bool GC_print_back_height = TRUE;
 100  #  else
 101  GC_INNER GC_bool GC_print_back_height = FALSE;
 102  #  endif
 103  #endif
 104  
 105  #ifndef NO_DEBUGGING
 106  #  ifdef GC_DUMP_REGULARLY
 107  GC_INNER GC_bool GC_dump_regularly = TRUE;
 108  #  else
 109  GC_INNER GC_bool GC_dump_regularly = FALSE;
 110  #  endif
 111  #  ifndef NO_CLOCK
 112  /* The time that the collector was initialized at. */
 113  STATIC CLOCK_TYPE GC_init_time;
 114  #  endif
 115  #endif /* !NO_DEBUGGING */
 116  
 117  #ifdef KEEP_BACK_PTRS
 118  GC_INNER long GC_backtraces = 0;
 119  #endif
 120  
 121  #ifdef FIND_LEAK
 122  int GC_find_leak = 1;
 123  #else
 124  int GC_find_leak = 0;
 125  #endif
 126  
 127  #if !defined(NO_FIND_LEAK) && !defined(SHORT_DBG_HDRS)
 128  #  ifdef GC_FINDLEAK_DELAY_FREE
 129  GC_INNER GC_bool GC_findleak_delay_free = TRUE;
 130  #  else
 131  GC_INNER GC_bool GC_findleak_delay_free = FALSE;
 132  #  endif
 133  #endif /* !NO_FIND_LEAK && !SHORT_DBG_HDRS */
 134  
 135  #ifdef ALL_INTERIOR_POINTERS
 136  int GC_all_interior_pointers = 1;
 137  #else
 138  int GC_all_interior_pointers = 0;
 139  #endif
 140  
 141  #ifdef FINALIZE_ON_DEMAND
 142  int GC_finalize_on_demand = 1;
 143  #else
 144  int GC_finalize_on_demand = 0;
 145  #endif
 146  
 147  #ifdef JAVA_FINALIZATION
 148  int GC_java_finalization = 1;
 149  #else
 150  int GC_java_finalization = 0;
 151  #endif
 152  
 153  /* All accesses to it should be synchronized to avoid data race. */
 154  GC_finalizer_notifier_proc GC_finalizer_notifier
 155      = (GC_finalizer_notifier_proc)0;
 156  
 157  #ifdef GC_FORCE_UNMAP_ON_GCOLLECT
 158  GC_INNER GC_bool GC_force_unmap_on_gcollect = TRUE;
 159  #else
 160  GC_INNER GC_bool GC_force_unmap_on_gcollect = FALSE;
 161  #endif
 162  
 163  #ifndef GC_LARGE_ALLOC_WARN_INTERVAL
 164  #  define GC_LARGE_ALLOC_WARN_INTERVAL 5
 165  #endif
 166  
 167  #ifndef NO_BLACK_LISTING
 168  GC_INNER long GC_large_alloc_warn_interval = GC_LARGE_ALLOC_WARN_INTERVAL;
 169  #endif
 170  
 171  STATIC void *GC_CALLBACK
 172  GC_default_oom_fn(size_t bytes_requested)
 173  {
 174    UNUSED_ARG(bytes_requested);
 175    return NULL;
 176  }
 177  
 178  /* All accesses to it should be synchronized to avoid data race. */
 179  GC_oom_func GC_oom_fn = GC_default_oom_fn;
 180  
 181  #ifdef CAN_HANDLE_FORK
 182  #  ifdef HANDLE_FORK
 183  GC_INNER int GC_handle_fork = 1;
 184  #  else
 185  GC_INNER int GC_handle_fork = FALSE;
 186  #  endif
 187  
 188  #elif !defined(HAVE_NO_FORK)
 189  GC_API void GC_CALL
 190  GC_atfork_prepare(void)
 191  {
 192  #  ifdef THREADS
 193    ABORT("fork() handling unsupported");
 194  #  endif
 195  }
 196  
 197  GC_API void GC_CALL
 198  GC_atfork_parent(void)
 199  {
 200    /* Empty. */
 201  }
 202  
 203  GC_API void GC_CALL
 204  GC_atfork_child(void)
 205  {
 206    /* Empty. */
 207  }
 208  #endif /* !CAN_HANDLE_FORK && !HAVE_NO_FORK */
 209  
 210  GC_API void GC_CALL
 211  GC_set_handle_fork(int value)
 212  {
 213  #ifdef CAN_HANDLE_FORK
 214    if (!GC_is_initialized) {
 215      /* Map all negative values except for -1 to a positive one. */
 216      GC_handle_fork = value >= -1 ? value : 1;
 217    }
 218  #elif defined(THREADS) || (defined(DARWIN) && defined(MPROTECT_VDB))
 219    if (!GC_is_initialized && value) {
 220  #  ifndef SMALL_CONFIG
 221      /* Initialize `GC_manual_vdb` and `GC_stderr`. */
 222      GC_init();
 223  #    ifndef THREADS
 224      if (GC_manual_vdb)
 225        return;
 226  #    endif
 227  #  endif
 228      ABORT("fork() handling unsupported");
 229    }
 230  #else
 231    /* No at-fork handler is needed in the single-threaded mode. */
 232    UNUSED_ARG(value);
 233  #endif
 234  }
 235  
 236  /*
 237   * Set things up so that `GC_size_map[i] >= granules(i)`, but not too
 238   * much bigger and so that `GC_size_map` contains relatively few
 239   * distinct entries.  This was originally stolen from Russ Atkinson's
 240   * Cedar quantization algorithm (but we precompute it).
 241   */
 242  STATIC void
 243  GC_init_size_map(void)
 244  {
 245    size_t i = 1;
 246  
 247    /* Map size 0 to something bigger; this avoids problems at lower levels. */
 248    GC_size_map[0] = 1;
 249  
 250    for (; i <= GRANULES_TO_BYTES(GC_TINY_FREELISTS - 1) - EXTRA_BYTES; i++) {
 251      GC_size_map[i] = ALLOC_REQUEST_GRANS(i);
 252  #ifndef _MSC_VER
 253      /* Seems to tickle bug in VC++ 2008 for x86_64. */
 254      GC_ASSERT(GC_size_map[i] < GC_TINY_FREELISTS);
 255  #endif
 256    }
 257    /* We leave the rest of the array to be filled in on demand. */
 258  }
 259  
 260  /*
 261   * The following is a gross hack to deal with a problem that can occur
 262   * on machines that are sloppy about stack frame sizes, notably SPARC.
 263   * Bogus pointers may be written to the stack and not cleared for
 264   * a LONG time, because they always fall into holes in stack frames
 265   * that are not written.  We partially address this by clearing
 266   * sections of the stack whenever we get control.
 267   */
 268  
 269  #ifndef SMALL_CLEAR_SIZE
 270  /* Clear this many words of the stack every time. */
 271  #  define SMALL_CLEAR_SIZE 256
 272  #endif
 273  
 274  #if defined(ALWAYS_SMALL_CLEAR_STACK) || defined(STACK_NOT_SCANNED)
 275  GC_API void *GC_CALL
 276  GC_clear_stack(void *arg)
 277  {
 278  #  ifndef STACK_NOT_SCANNED
 279    volatile ptr_t dummy[SMALL_CLEAR_SIZE];
 280  
 281    BZERO(CAST_AWAY_VOLATILE_PVOID(dummy), sizeof(dummy));
 282  #  endif
 283    return arg;
 284  }
 285  #else
 286  
 287  #  ifdef THREADS
 288  /* Clear this much sometimes. */
 289  #    define BIG_CLEAR_SIZE 2048
 290  #  else
 291  /* `GC_gc_no` value when we last did this. */
 292  STATIC word GC_stack_last_cleared = 0;
 293  
 294  STATIC word GC_bytes_allocd_at_reset = 0;
 295  
 296  /*
 297   * Coolest stack pointer value from which we have already cleared
 298   * the stack.
 299   */
 300  STATIC ptr_t GC_min_sp = NULL;
 301  
 302  /*
 303   * The "hottest" stack pointer value we have seen recently.
 304   * Degrades over time.
 305   */
 306  STATIC ptr_t GC_high_water = NULL;
 307  
 308  #    define DEGRADE_RATE 50
 309  #  endif
 310  
 311  #  if defined(__APPLE_CC__) && !GC_CLANG_PREREQ(6, 0)
 312  #    define CLEARSTACK_LIMIT_MODIFIER volatile /*< to workaround some bug */
 313  #  else
 314  #    define CLEARSTACK_LIMIT_MODIFIER /*< empty */
 315  #  endif
 316  
 317  EXTERN_C_BEGIN
 318  void *GC_clear_stack_inner(void *, CLEARSTACK_LIMIT_MODIFIER ptr_t);
 319  EXTERN_C_END
 320  
 321  #  ifndef ASM_CLEAR_CODE
 322  /*
 323   * Clear the stack up to about `limit`.  Return `arg`.  This function is
 324   * not `static` because it could also be erroneously defined in `.S` file,
 325   * so this error would be caught by the linker.
 326   */
 327  void *
 328  GC_clear_stack_inner(void *arg, CLEARSTACK_LIMIT_MODIFIER ptr_t limit)
 329  {
 330  #    define CLEAR_SIZE 213 /*< granularity */
 331    volatile ptr_t dummy[CLEAR_SIZE];
 332  
 333    BZERO(CAST_AWAY_VOLATILE_PVOID(dummy), sizeof(dummy));
 334    if (HOTTER_THAN((/* no volatile */ ptr_t)limit, GC_approx_sp())) {
 335      (void)GC_clear_stack_inner(arg, limit);
 336    }
 337    /*
 338     * Make sure the recursive call is not a tail call, and the `bzero` call
 339     * is not recognized as dead code.
 340     */
 341  #    if defined(CPPCHECK)
 342    GC_noop1(ADDR(dummy[0]));
 343  #    else
 344    GC_noop1(COVERT_DATAFLOW(ADDR(dummy)));
 345  #    endif
 346    return arg;
 347  }
 348  #  endif /* !ASM_CLEAR_CODE */
 349  
 350  #  ifdef THREADS
 351  /* Used to occasionally clear a bigger chunk. */
 352  /* TODO: Should be more random than it is... */
 353  static unsigned
 354  next_random_no(void)
 355  {
 356  #    ifdef AO_HAVE_fetch_and_add1
 357    static volatile AO_t random_no;
 358  
 359    return (unsigned)AO_fetch_and_add1(&random_no) % 13;
 360  #    else
 361    static unsigned random_no = 0;
 362  
 363    return (random_no++) % 13;
 364  #    endif
 365  }
 366  #  endif /* THREADS */
 367  
 368  GC_API void *GC_CALL
 369  GC_clear_stack(void *arg)
 370  {
 371    /* Note: this is hotter than the actual stack pointer. */
 372    ptr_t sp = GC_approx_sp();
 373  #  ifdef THREADS
 374    volatile ptr_t dummy[SMALL_CLEAR_SIZE];
 375  #  endif
 376  
 377    /*
 378     * Extra bytes we clear every time.  This clears our own activation
 379     * record, and should cause more frequent clearing near the cold end
 380     * of the stack, a good thing.
 381     */
 382  #  define SLOP 400
 383  
 384    /*
 385     * We make `GC_high_water` this much hotter than we really saw it,
 386     * to cover for the GC noise above our current frame.
 387     */
 388  #  define GC_SLOP 4000
 389  
 390    /*
 391     * We restart the clearing process after this many bytes of allocation.
 392     * Otherwise very heavily recursive programs with sparse stacks may
 393     * result in heaps that grow almost without bounds.  As the heap gets
 394     * larger, collection frequency decreases, thus clearing frequency
 395     * would decrease, thus more junk remains accessible, thus the heap
 396     * gets larger...
 397     */
 398  #  define CLEAR_THRESHOLD 100000
 399  
 400  #  ifdef THREADS
 401    if (next_random_no() == 0) {
 402      ptr_t limit = sp;
 403  
 404      MAKE_HOTTER(limit, BIG_CLEAR_SIZE * sizeof(ptr_t));
 405      /*
 406       * Make it sufficiently aligned for assembly implementations
 407       * of `GC_clear_stack_inner`.
 408       */
 409      limit = PTR_ALIGN_DOWN(limit, 0x10);
 410      return GC_clear_stack_inner(arg, limit);
 411    }
 412    BZERO(CAST_AWAY_VOLATILE_PVOID(dummy), sizeof(dummy));
 413  #  else
 414    if (GC_gc_no != GC_stack_last_cleared) {
 415      /* Start things over, so we clear the entire stack again. */
 416      if (UNLIKELY(NULL == GC_high_water))
 417        GC_high_water = (ptr_t)GC_stackbottom;
 418      GC_min_sp = GC_high_water;
 419      GC_stack_last_cleared = GC_gc_no;
 420      GC_bytes_allocd_at_reset = GC_bytes_allocd;
 421    }
 422    /* Adjust `GC_high_water`. */
 423    GC_ASSERT(GC_high_water != NULL);
 424    MAKE_COOLER(GC_high_water, PTRS_TO_BYTES(DEGRADE_RATE) + GC_SLOP);
 425    if (HOTTER_THAN(sp, GC_high_water))
 426      GC_high_water = sp;
 427    MAKE_HOTTER(GC_high_water, GC_SLOP);
 428    {
 429      ptr_t limit = GC_min_sp;
 430  
 431      MAKE_HOTTER(limit, SLOP);
 432      if (HOTTER_THAN(limit, sp)) {
 433        limit = PTR_ALIGN_DOWN(limit, 0x10);
 434        GC_min_sp = sp;
 435        return GC_clear_stack_inner(arg, limit);
 436      }
 437    }
 438    if (GC_bytes_allocd - GC_bytes_allocd_at_reset > CLEAR_THRESHOLD) {
 439      /* Restart clearing process, but limit how much clearing we do. */
 440      GC_min_sp = sp;
 441      MAKE_HOTTER(GC_min_sp, CLEAR_THRESHOLD / 4);
 442      if (HOTTER_THAN(GC_min_sp, GC_high_water))
 443        GC_min_sp = GC_high_water;
 444      GC_bytes_allocd_at_reset = GC_bytes_allocd;
 445    }
 446  #  endif
 447    return arg;
 448  }
 449  
 450  #endif /* !ALWAYS_SMALL_CLEAR_STACK && !STACK_NOT_SCANNED */
 451  
 452  GC_API void *GC_CALL
 453  GC_base(void *p)
 454  {
 455    ptr_t r = (ptr_t)p;
 456    struct hblk *h;
 457    bottom_index *bi;
 458    hdr *hhdr;
 459    ptr_t limit;
 460    size_t sz;
 461  
 462    if (UNLIKELY(!GC_is_initialized))
 463      return NULL;
 464    h = HBLKPTR(r);
 465    GET_BI(r, bi);
 466    hhdr = HDR_FROM_BI(bi, r);
 467    if (NULL == hhdr)
 468      return NULL;
 469  
 470    /*
 471     * If it is a pointer to the middle of a large object, then move it
 472     * to the beginning.
 473     */
 474    if (IS_FORWARDING_ADDR_OR_NIL(hhdr)) {
 475      h = GC_find_starting_hblk(h, &hhdr);
 476      r = (ptr_t)h;
 477    }
 478    if (HBLK_IS_FREE(hhdr))
 479      return NULL;
 480  
 481    /* Make sure `r` points to the beginning of the object. */
 482    r = PTR_ALIGN_DOWN(r, sizeof(ptr_t));
 483  
 484    sz = hhdr->hb_sz;
 485    r -= HBLKDISPL(r) % sz;
 486    limit = r + sz;
 487    if ((ADDR_LT((ptr_t)(h + 1), limit) && sz <= HBLKSIZE)
 488        || ADDR_GE((ptr_t)p, limit))
 489      return NULL;
 490  
 491    return r;
 492  }
 493  
 494  GC_API int GC_CALL
 495  GC_is_heap_ptr(const void *p)
 496  {
 497    bottom_index *bi;
 498  
 499    GC_ASSERT(GC_is_initialized);
 500    GET_BI(p, bi);
 501    return HDR_FROM_BI(bi, p) != 0;
 502  }
 503  
 504  GC_API size_t GC_CALL
 505  GC_size(const void *p)
 506  {
 507    const hdr *hhdr;
 508  
 509    /* Accept `NULL` for compatibility with `malloc_usable_size()`. */
 510    if (UNLIKELY(NULL == p))
 511      return 0;
 512  
 513    hhdr = HDR(p);
 514    return hhdr->hb_sz;
 515  }
 516  
 517  /*
 518   * These getters remain unsynchronized for compatibility (since some clients
 519   * could call some of them from a GC callback holding the allocator lock).
 520   */
 521  
 522  GC_API size_t GC_CALL
 523  GC_get_heap_size(void)
 524  {
 525    /*
 526     * Ignore the memory space returned to OS (i.e. count only the space
 527     * owned by the garbage collector).
 528     */
 529    return (size_t)(GC_heapsize - GC_unmapped_bytes);
 530  }
 531  
 532  GC_API size_t GC_CALL
 533  GC_get_obtained_from_os_bytes(void)
 534  {
 535    return (size_t)GC_our_mem_bytes;
 536  }
 537  
 538  GC_API size_t GC_CALL
 539  GC_get_free_bytes(void)
 540  {
 541    /* Ignore the memory space returned to OS. */
 542    return (size_t)(GC_large_free_bytes - GC_unmapped_bytes);
 543  }
 544  
 545  GC_API size_t GC_CALL
 546  GC_get_unmapped_bytes(void)
 547  {
 548    return (size_t)GC_unmapped_bytes;
 549  }
 550  
 551  GC_API size_t GC_CALL
 552  GC_get_bytes_since_gc(void)
 553  {
 554    return (size_t)GC_bytes_allocd;
 555  }
 556  
 557  GC_API size_t GC_CALL
 558  GC_get_total_bytes(void)
 559  {
 560    return (size_t)(GC_bytes_allocd + GC_bytes_allocd_before_gc);
 561  }
 562  
 563  #ifndef GC_GET_HEAP_USAGE_NOT_NEEDED
 564  
 565  GC_API size_t GC_CALL
 566  GC_get_size_map_at(int i)
 567  {
 568    if ((unsigned)i > MAXOBJBYTES)
 569      return GC_SIZE_MAX;
 570    return GRANULES_TO_BYTES(GC_size_map[i]);
 571  }
 572  
 573  GC_API void GC_CALL
 574  GC_get_heap_usage_safe(GC_word *pheap_size, GC_word *pfree_bytes,
 575                         GC_word *punmapped_bytes, GC_word *pbytes_since_gc,
 576                         GC_word *ptotal_bytes)
 577  {
 578    READER_LOCK();
 579    if (pheap_size != NULL)
 580      *pheap_size = GC_heapsize - GC_unmapped_bytes;
 581    if (pfree_bytes != NULL)
 582      *pfree_bytes = GC_large_free_bytes - GC_unmapped_bytes;
 583    if (punmapped_bytes != NULL)
 584      *punmapped_bytes = GC_unmapped_bytes;
 585    if (pbytes_since_gc != NULL)
 586      *pbytes_since_gc = GC_bytes_allocd;
 587    if (ptotal_bytes != NULL)
 588      *ptotal_bytes = GC_bytes_allocd + GC_bytes_allocd_before_gc;
 589    READER_UNLOCK();
 590  }
 591  
 592  GC_INNER word GC_reclaimed_bytes_before_gc = 0;
 593  
 594  /* Fill in GC statistics provided the destination is of enough size. */
 595  static void
 596  fill_prof_stats(struct GC_prof_stats_s *pstats)
 597  {
 598    pstats->heapsize_full = GC_heapsize;
 599    pstats->free_bytes_full = GC_large_free_bytes;
 600    pstats->unmapped_bytes = GC_unmapped_bytes;
 601    pstats->bytes_allocd_since_gc = GC_bytes_allocd;
 602    pstats->allocd_bytes_before_gc = GC_bytes_allocd_before_gc;
 603    pstats->non_gc_bytes = GC_non_gc_bytes;
 604    pstats->gc_no = GC_gc_no; /*< could be -1 */
 605  #  ifdef PARALLEL_MARK
 606    pstats->markers_m1 = (word)((GC_signed_word)GC_markers_m1);
 607  #  else
 608    /* A single marker. */
 609    pstats->markers_m1 = 0;
 610  #  endif
 611    pstats->bytes_reclaimed_since_gc
 612        = GC_bytes_found > 0 ? (word)GC_bytes_found : 0;
 613    pstats->reclaimed_bytes_before_gc = GC_reclaimed_bytes_before_gc;
 614    pstats->expl_freed_bytes_since_gc = GC_bytes_freed; /*< since gc-7.7 */
 615    pstats->obtained_from_os_bytes = GC_our_mem_bytes;  /*< since gc-8.2 */
 616  }
 617  
 618  #  include <string.h> /*< for `memset()` */
 619  
 620  GC_API size_t GC_CALL
 621  GC_get_prof_stats(struct GC_prof_stats_s *pstats, size_t stats_sz)
 622  {
 623    struct GC_prof_stats_s stats;
 624  
 625    READER_LOCK();
 626    fill_prof_stats(stats_sz >= sizeof(stats) ? pstats : &stats);
 627    READER_UNLOCK();
 628  
 629    if (stats_sz == sizeof(stats)) {
 630      return sizeof(stats);
 631    } else if (stats_sz > sizeof(stats)) {
 632      /* Fill in the remaining part with -1. */
 633      memset((char *)pstats + sizeof(stats), 0xff, stats_sz - sizeof(stats));
 634      return sizeof(stats);
 635    } else {
 636      if (LIKELY(stats_sz > 0))
 637        BCOPY(&stats, pstats, stats_sz);
 638      return stats_sz;
 639    }
 640  }
 641  
 642  #  ifdef THREADS
 643  GC_API size_t GC_CALL
 644  GC_get_prof_stats_unsafe(struct GC_prof_stats_s *pstats, size_t stats_sz)
 645  {
 646    struct GC_prof_stats_s stats;
 647  
 648    if (stats_sz >= sizeof(stats)) {
 649      fill_prof_stats(pstats);
 650      if (stats_sz > sizeof(stats))
 651        memset((char *)pstats + sizeof(stats), 0xff, stats_sz - sizeof(stats));
 652      return sizeof(stats);
 653    } else {
 654      if (LIKELY(stats_sz > 0)) {
 655        fill_prof_stats(&stats);
 656        BCOPY(&stats, pstats, stats_sz);
 657      }
 658      return stats_sz;
 659    }
 660  }
 661  #  endif /* THREADS */
 662  
 663  #endif /* !GC_GET_HEAP_USAGE_NOT_NEEDED */
 664  
 665  #if defined(THREADS) && !defined(SIGNAL_BASED_STOP_WORLD)
 666  /* The collector does not use signals to suspend and restart threads. */
 667  
 668  GC_API void GC_CALL
 669  GC_set_suspend_signal(int sig)
 670  {
 671    UNUSED_ARG(sig);
 672  }
 673  
 674  GC_API void GC_CALL
 675  GC_set_thr_restart_signal(int sig)
 676  {
 677    UNUSED_ARG(sig);
 678  }
 679  
 680  GC_API int GC_CALL
 681  GC_get_suspend_signal(void)
 682  {
 683    return -1;
 684  }
 685  
 686  GC_API int GC_CALL
 687  GC_get_thr_restart_signal(void)
 688  {
 689    return -1;
 690  }
 691  #endif /* THREADS && !SIGNAL_BASED_STOP_WORLD */
 692  
 693  #if !defined(_MAX_PATH) && defined(ANY_MSWIN)
 694  #  define _MAX_PATH MAX_PATH
 695  #endif
 696  
 697  #ifdef GC_READ_ENV_FILE
 698  /* This works for Win32/WinCE for now.  Really useful only for WinCE. */
 699  
 700  /*
 701   * The content of the `.gc.env` file with CR and LF replaced to '\0'.
 702   * `NULL` if the file is missing or empty.  Otherwise, always ends with '\0'
 703   * (designating the end of the file).
 704   */
 705  STATIC char *GC_envfile_content = NULL;
 706  
 707  /* Length of `GC_envfile_content` (if non-`NULL`). */
 708  STATIC unsigned GC_envfile_length = 0;
 709  
 710  #  ifndef GC_ENVFILE_MAXLEN
 711  #    define GC_ENVFILE_MAXLEN 0x4000
 712  #  endif
 713  
 714  #  define GC_ENV_FILE_EXT ".gc.env"
 715  
 716  /* The routine initializes `GC_envfile_content` from the `.gc.env` file. */
 717  STATIC void
 718  GC_envfile_init(void)
 719  {
 720  #  ifdef ANY_MSWIN
 721    HANDLE hFile;
 722    char *content;
 723    unsigned ofs;
 724    unsigned len;
 725    DWORD nBytesRead;
 726    TCHAR path[_MAX_PATH + 0x10]; /*< buffer for file path with extension */
 727    size_t bytes_to_get;
 728  
 729    GC_ASSERT(I_HOLD_LOCK());
 730    len = (unsigned)GetModuleFileName(NULL /* `hModule` */, path, _MAX_PATH + 1);
 731    /* If `GetModuleFileName()` failed, then len is 0. */
 732    if (len > 4 && path[len - 4] == (TCHAR)'.') {
 733      /* Strip the executable file extension. */
 734      len -= 4;
 735    }
 736    BCOPY(TEXT(GC_ENV_FILE_EXT), &path[len], sizeof(TEXT(GC_ENV_FILE_EXT)));
 737    hFile = CreateFile(path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
 738                       NULL /* `lpSecurityAttributes` */, OPEN_EXISTING,
 739                       FILE_ATTRIBUTE_NORMAL, NULL /* `hTemplateFile` */);
 740    if (hFile == INVALID_HANDLE_VALUE) {
 741      /* The file is absent or the operation failed. */
 742      return;
 743    }
 744    len = (unsigned)GetFileSize(hFile, NULL);
 745    if (len <= 1 || len >= GC_ENVFILE_MAXLEN) {
 746      CloseHandle(hFile);
 747      /* Invalid file length - ignoring the file content. */
 748      return;
 749    }
 750    /*
 751     * At this execution point, `GC_setpagesize()` and `GC_init_win32()`
 752     * must already be called (for `GET_MEM()` to work correctly).
 753     */
 754    GC_ASSERT(GC_page_size != 0);
 755    bytes_to_get = ROUNDUP_PAGESIZE_IF_MMAP((size_t)len + 1);
 756    content = GC_os_get_mem(bytes_to_get);
 757    if (content == NULL) {
 758      CloseHandle(hFile);
 759      /* An allocation failure. */
 760      return;
 761    }
 762    ofs = 0;
 763    nBytesRead = (DWORD)-1L;
 764    /* Last `ReadFile()` call should clear `nBytesRead` on success. */
 765    while (ReadFile(hFile, content + ofs, len - ofs + 1, &nBytesRead,
 766                    NULL /* `lpOverlapped` */)
 767           && nBytesRead != 0) {
 768      if ((ofs += nBytesRead) > len)
 769        break;
 770    }
 771    CloseHandle(hFile);
 772    if (ofs != len || nBytesRead != 0) {
 773      /* TODO: Recycle content. */
 774      /* Read operation has failed - ignoring the file content. */
 775      return;
 776    }
 777    content[ofs] = '\0';
 778    while (ofs-- > 0) {
 779      if (content[ofs] == '\r' || content[ofs] == '\n')
 780        content[ofs] = '\0';
 781    }
 782    GC_ASSERT(NULL == GC_envfile_content);
 783    GC_envfile_length = len + 1;
 784    GC_envfile_content = content;
 785  #  endif
 786  }
 787  
 788  GC_INNER char *
 789  GC_envfile_getenv(const char *name)
 790  {
 791    char *p;
 792    const char *end_of_content;
 793    size_t namelen;
 794  
 795  #  ifndef NO_GETENV
 796    /* Try the standard `getenv()` first. */
 797    p = getenv(name);
 798    if (p != NULL)
 799      return *p != '\0' ? p : NULL;
 800  #  endif
 801    p = GC_envfile_content;
 802    if (NULL == p) {
 803      /* The `.gc.env` file is absent (or empty). */
 804      return NULL;
 805    }
 806    namelen = strlen(name);
 807    if (0 == namelen) {
 808      /* A sanity check. */
 809      return NULL;
 810    }
 811    for (end_of_content = p + GC_envfile_length;
 812         ADDR_LT((ptr_t)p, (ptr_t)end_of_content); p += strlen(p) + 1) {
 813      if (strncmp(p, name, namelen) == 0 && *(p += namelen) == '=') {
 814        /* The match is found; skip "=". */
 815        p++;
 816        return *p != '\0' ? p : NULL;
 817      }
 818      /* If not matching then skip to the next line. */
 819    }
 820    GC_ASSERT(p == end_of_content);
 821    /* No match is found. */
 822    return NULL;
 823  }
 824  #endif /* GC_READ_ENV_FILE */
 825  
 826  GC_INNER GC_bool GC_is_initialized = FALSE;
 827  
 828  GC_API int GC_CALL
 829  GC_is_init_called(void)
 830  {
 831    return (int)GC_is_initialized;
 832  }
 833  
 834  #if defined(GC_WIN32_THREADS) \
 835      && ((defined(MSWIN32) && !defined(CONSOLE_LOG)) || defined(MSWINCE))
 836  GC_INNER CRITICAL_SECTION GC_write_cs;
 837  #endif
 838  
 839  #ifndef DONT_USE_ATEXIT
 840  #  if !defined(SMALL_CONFIG)
 841  /*
 842   * A dedicated variable to avoid a garbage collection on abort.
 843   * `GC_find_leak` cannot be used for this purpose as otherwise
 844   * TSan finds a data race (between `GC_default_on_abort` and, e.g.,
 845   * `GC_finish_collection`).
 846   */
 847  static GC_bool skip_gc_atexit = FALSE;
 848  #  else
 849  #    define skip_gc_atexit FALSE
 850  #  endif
 851  
 852  STATIC void
 853  GC_exit_check(void)
 854  {
 855    if (GC_find_leak && !skip_gc_atexit) {
 856  #  ifdef THREADS
 857      /*
 858       * Check that the thread executing at-exit functions is the same as
 859       * the one performed the GC initialization, otherwise the latter
 860       * thread might already be dead but still registered and this, as
 861       * a consequence, might cause a signal delivery fail when suspending
 862       * the threads on platforms that do not guarantee `ESRCH` returned
 863       * if the signal is not delivered.  It should also prevent
 864       * "Collecting from unknown thread" abort in `GC_push_all_stacks()`.
 865       */
 866      if (!GC_is_main_thread() || !GC_thread_is_registered())
 867        return;
 868  #  endif
 869      GC_gcollect();
 870    }
 871  }
 872  #endif /* !DONT_USE_ATEXIT */
 873  
 874  #if defined(UNIX_LIKE) && !defined(NO_DEBUGGING)
 875  static void
 876  looping_handler(int sig)
 877  {
 878    GC_err_printf("Caught signal %d: looping in handler\n", sig);
 879    for (;;) {
 880      /* Empty. */
 881    }
 882  }
 883  
 884  static GC_bool installed_looping_handler = FALSE;
 885  
 886  static void
 887  maybe_install_looping_handler(void)
 888  {
 889    /*
 890     * Install looping handler before the write fault handler,
 891     * so we handle write faults correctly.
 892     */
 893    if (!installed_looping_handler && GETENV("GC_LOOP_ON_ABORT") != NULL) {
 894      GC_set_and_save_fault_handler(looping_handler);
 895      installed_looping_handler = TRUE;
 896    }
 897  }
 898  
 899  #else /* !UNIX_LIKE */
 900  #  define maybe_install_looping_handler()
 901  #endif
 902  
 903  #define GC_DEFAULT_STDERR_FD 2
 904  #ifdef KOS
 905  #  define GC_DEFAULT_STDOUT_FD GC_DEFAULT_STDERR_FD
 906  #else
 907  #  define GC_DEFAULT_STDOUT_FD 1
 908  #endif
 909  
 910  #if !defined(OS2) && !defined(GC_ANDROID_LOG) && !defined(NN_PLATFORM_CTR) \
 911      && !defined(NINTENDO_SWITCH)                                           \
 912      && (!defined(MSWIN32) || defined(CONSOLE_LOG)) && !defined(MSWINCE)
 913  STATIC int GC_stdout = GC_DEFAULT_STDOUT_FD;
 914  STATIC int GC_stderr = GC_DEFAULT_STDERR_FD;
 915  STATIC int GC_log = GC_DEFAULT_STDERR_FD;
 916  
 917  #  ifndef MSWIN32
 918  GC_API void GC_CALL
 919  GC_set_log_fd(int fd)
 920  {
 921    GC_log = fd;
 922  }
 923  #  endif
 924  #endif
 925  
 926  #ifdef MSGBOX_ON_ERROR
 927  STATIC void
 928  GC_win32_MessageBoxA(const char *msg, const char *caption, unsigned flags)
 929  {
 930  #  ifndef DONT_USE_USER32_DLL
 931    /* Use static binding to `user32.dll` file. */
 932    (void)MessageBoxA(NULL, msg, caption, flags);
 933  #  else
 934    /* This simplifies linking - resolve `MessageBoxA()` at run-time. */
 935    HINSTANCE hU32 = LoadLibrary(TEXT("user32.dll"));
 936    if (hU32) {
 937      FARPROC pfn = GetProcAddress(hU32, "MessageBoxA");
 938      if (pfn)
 939        (void)(*(int(WINAPI *)(HWND, LPCSTR, LPCSTR, UINT))(GC_funcptr_uint)pfn)(
 940            NULL /* `hWnd` */, msg, caption, flags);
 941      (void)FreeLibrary(hU32);
 942    }
 943  #  endif
 944  }
 945  #endif /* MSGBOX_ON_ERROR */
 946  
 947  #if defined(THREADS) && defined(UNIX_LIKE) && !defined(NO_GETCONTEXT)
 948  static void
 949  callee_saves_pushed_dummy_fn(ptr_t data, void *context)
 950  {
 951    UNUSED_ARG(data);
 952    UNUSED_ARG(context);
 953  }
 954  #endif
 955  
 956  #ifdef MANUAL_VDB
 957  static GC_bool manual_vdb_allowed = TRUE;
 958  #else
 959  static GC_bool manual_vdb_allowed = FALSE;
 960  #endif
 961  
 962  GC_API void GC_CALL
 963  GC_set_manual_vdb_allowed(int value)
 964  {
 965    manual_vdb_allowed = (GC_bool)value;
 966  }
 967  
 968  GC_API int GC_CALL
 969  GC_get_manual_vdb_allowed(void)
 970  {
 971    return (int)manual_vdb_allowed;
 972  }
 973  
 974  GC_API unsigned GC_CALL
 975  GC_get_supported_vdbs(void)
 976  {
 977  #ifdef GC_DISABLE_INCREMENTAL
 978    return GC_VDB_NONE;
 979  #else
 980  #  if defined(CPPCHECK)
 981    /* Workaround a warning about redundant `| 0`. */
 982    volatile unsigned zero = 0;
 983  #  endif
 984    return
 985  #  if defined(CPPCHECK)
 986        zero
 987  #  else
 988        0
 989  #  endif
 990  #  ifndef NO_MANUAL_VDB
 991        | GC_VDB_MANUAL
 992  #  endif
 993  #  ifdef DEFAULT_VDB
 994        | GC_VDB_DEFAULT
 995  #  endif
 996  #  ifdef MPROTECT_VDB
 997        | GC_VDB_MPROTECT
 998  #  endif
 999  #  ifdef GWW_VDB
1000        | GC_VDB_GWW
1001  #  endif
1002  #  ifdef PROC_VDB
1003        | GC_VDB_PROC
1004  #  endif
1005  #  ifdef SOFT_VDB
1006        | GC_VDB_SOFT
1007  #  endif
1008        ;
1009  #endif
1010  }
1011  
1012  #ifndef GC_DISABLE_INCREMENTAL
1013  static void
1014  set_incremental_mode_on(void)
1015  {
1016    GC_ASSERT(I_HOLD_LOCK());
1017  #  ifndef NO_MANUAL_VDB
1018    if (manual_vdb_allowed) {
1019      GC_manual_vdb = TRUE;
1020      GC_incremental = TRUE;
1021    } else
1022  #  endif
1023    /* else */ {
1024      /*
1025       * For `GWW_VDB` on Win32, this needs to happen before any heap memory
1026       * is allocated.
1027       */
1028      GC_incremental = GC_dirty_init();
1029    }
1030  }
1031  #endif /* !GC_DISABLE_INCREMENTAL */
1032  
1033  STATIC word
1034  GC_parse_mem_size_arg(const char *str)
1035  {
1036    word result;
1037    char *endptr;
1038    char ch;
1039  
1040    if ('\0' == *str)
1041      return GC_WORD_MAX; /*< bad value */
1042    result = (word)STRTOULL(str, &endptr, 10);
1043    ch = *endptr;
1044    if (ch != '\0') {
1045      if (*(endptr + 1) != '\0')
1046        return GC_WORD_MAX;
1047      /* Allow "k", "M" or "G" suffix. */
1048      switch (ch) {
1049      case 'K':
1050      case 'k':
1051        result <<= 10;
1052        break;
1053  #if CPP_WORDSZ >= 32
1054      case 'M':
1055      case 'm':
1056        result <<= 20;
1057        break;
1058      case 'G':
1059      case 'g':
1060        result <<= 30;
1061        break;
1062  #endif
1063      default:
1064        result = GC_WORD_MAX;
1065      }
1066    }
1067    return result;
1068  }
1069  
1070  #define GC_LOG_STD_NAME "gc.log"
1071  
1072  GC_API void GC_CALL
1073  GC_init(void)
1074  {
1075    word initial_heap_sz;
1076    IF_CANCEL(int cancel_state;)
1077  
1078    if (LIKELY(GC_is_initialized))
1079      return;
1080  #ifdef REDIRECT_MALLOC
1081    {
1082      static GC_bool init_started = FALSE;
1083      if (init_started)
1084        ABORT("Redirected malloc() called during GC init");
1085      init_started = TRUE;
1086    }
1087  #endif
1088  
1089  #if defined(GC_INITIAL_HEAP_SIZE) && !defined(CPPCHECK)
1090    initial_heap_sz = GC_INITIAL_HEAP_SIZE;
1091  #else
1092    initial_heap_sz = MINHINCR * HBLKSIZE;
1093  #endif
1094  
1095    DISABLE_CANCEL(cancel_state);
1096    /*
1097     * Note that although we are nominally called with the allocator lock
1098     * held, now it is only really acquired once a second thread is created.
1099     * And the initialization code needs to run before then.  Thus we really
1100     * do not hold any locks, and can safely initialize them here.
1101     */
1102  #ifdef THREADS
1103  #  ifndef GC_ALWAYS_MULTITHREADED
1104    GC_ASSERT(!GC_need_to_lock);
1105  #  endif
1106    {
1107  #  if !defined(GC_BUILTIN_ATOMIC) && defined(HP_PA) \
1108        && (defined(USE_SPIN_LOCK) || defined(NEED_FAULT_HANDLER_LOCK))
1109      AO_TS_t ts_init = AO_TS_INITIALIZER;
1110  
1111      /* Arrays can only be initialized when declared. */
1112  #    ifdef USE_SPIN_LOCK
1113      BCOPY(&ts_init, (/* no volatile */ void *)&GC_allocate_lock,
1114            sizeof(GC_allocate_lock));
1115  #    endif
1116  #    ifdef NEED_FAULT_HANDLER_LOCK
1117      BCOPY(&ts_init, (/* no volatile */ void *)&GC_fault_handler_lock,
1118            sizeof(GC_fault_handler_lock));
1119  #    endif
1120  #  else
1121  #    ifdef USE_SPIN_LOCK
1122      GC_allocate_lock = AO_TS_INITIALIZER;
1123  #    endif
1124  #    ifdef NEED_FAULT_HANDLER_LOCK
1125      GC_fault_handler_lock = AO_TS_INITIALIZER;
1126  #    endif
1127  #  endif
1128    }
1129  #  ifdef SN_TARGET_PS3
1130    {
1131      pthread_mutexattr_t mattr;
1132  
1133      if (pthread_mutexattr_init(&mattr) != 0)
1134        ABORT("pthread_mutexattr_init failed");
1135      if (pthread_mutex_init(&GC_allocate_ml, &mattr) != 0)
1136        ABORT("pthread_mutex_init failed");
1137      (void)pthread_mutexattr_destroy(&mattr);
1138    }
1139  #  endif
1140  #endif /* THREADS */
1141  #if defined(GC_WIN32_THREADS) && !defined(GC_PTHREADS)
1142  #  ifndef SPIN_COUNT
1143  #    define SPIN_COUNT 4000
1144  #  endif
1145  #  ifdef USE_RWLOCK
1146    /* TODO: Probably use `SRWLOCK_INIT` instead. */
1147    InitializeSRWLock(&GC_allocate_ml);
1148  #  elif defined(MSWINRT_FLAVOR)
1149    InitializeCriticalSectionAndSpinCount(&GC_allocate_ml, SPIN_COUNT);
1150  #  else
1151    {
1152  #    ifndef MSWINCE
1153      FARPROC pfn = 0;
1154      HMODULE hK32 = GetModuleHandle(TEXT("kernel32.dll"));
1155      if (hK32)
1156        pfn = GetProcAddress(hK32, "InitializeCriticalSectionAndSpinCount");
1157      if (pfn) {
1158        (*(BOOL(WINAPI *)(LPCRITICAL_SECTION, DWORD))(GC_funcptr_uint)pfn)(
1159            &GC_allocate_ml, SPIN_COUNT);
1160      } else
1161  #    endif /* !MSWINCE */
1162        /* else */ InitializeCriticalSection(&GC_allocate_ml);
1163    }
1164  #  endif
1165  #endif /* GC_WIN32_THREADS && !GC_PTHREADS */
1166  #if defined(GC_WIN32_THREADS) \
1167      && ((defined(MSWIN32) && !defined(CONSOLE_LOG)) || defined(MSWINCE))
1168    InitializeCriticalSection(&GC_write_cs);
1169  #endif
1170  #if defined(GC_ASSERTIONS) && defined(GC_ALWAYS_MULTITHREADED)
1171    /* Just to set `GC_lock_holder`. */
1172    LOCK();
1173  #endif
1174  #ifdef DYNAMIC_POINTER_MASK
1175    if (0 == GC_pointer_mask)
1176      GC_pointer_mask = GC_WORD_MAX;
1177  #endif
1178    GC_setpagesize();
1179  #ifdef MSWIN32
1180    GC_init_win32();
1181  #endif
1182  #ifdef GC_READ_ENV_FILE
1183    GC_envfile_init();
1184  #endif
1185  #if !defined(NO_CLOCK) || !defined(SMALL_CONFIG)
1186  #  ifdef GC_PRINT_VERBOSE_STATS
1187    /*
1188     * This is useful for debugging and profiling on platforms with
1189     * missing `getenv()` (like WinCE).
1190     */
1191    GC_print_stats = VERBOSE;
1192  #  else
1193    if (GETENV("GC_PRINT_VERBOSE_STATS") != NULL) {
1194      GC_print_stats = VERBOSE;
1195    } else if (GETENV("GC_PRINT_STATS") != NULL) {
1196      GC_print_stats = 1;
1197    }
1198  #  endif
1199  #endif
1200  #if ((defined(UNIX_LIKE) && !defined(GC_ANDROID_LOG))                   \
1201       || (defined(CONSOLE_LOG) && defined(MSWIN32)) || defined(CYGWIN32) \
1202       || defined(SYMBIAN))                                               \
1203      && !defined(SMALL_CONFIG)
1204    {
1205      const char *fname = TRUSTED_STRING(GETENV("GC_LOG_FILE"));
1206  #  ifdef GC_LOG_TO_FILE_ALWAYS
1207      if (NULL == fname)
1208        fname = GC_LOG_STD_NAME;
1209  #  else
1210      if (fname != NULL)
1211  #  endif
1212      {
1213  #  if defined(_MSC_VER)
1214        int log_d = _open(fname, O_CREAT | O_WRONLY | O_APPEND);
1215  #  else
1216        int log_d = open(fname, O_CREAT | O_WRONLY | O_APPEND, 0644);
1217  #  endif
1218        if (log_d < 0) {
1219          GC_err_printf("Failed to open %s as log file\n", fname);
1220        } else {
1221          const char *str;
1222          GC_log = log_d;
1223          str = GETENV("GC_ONLY_LOG_TO_FILE");
1224  #  ifdef GC_ONLY_LOG_TO_FILE
1225          /*
1226           * The similar environment variable set to "0"
1227           * overrides the effect of the macro defined.
1228           */
1229          if (str != NULL && str[0] == '0' && str[1] == '\0')
1230  #  else
1231          /*
1232           * Otherwise setting the environment variable to anything other
1233           * than "0" will prevent from redirecting `stdout` and `stderr`
1234           * to the collector log file.
1235           */
1236          if (str == NULL || (str[0] == '0' && str[1] == '\0'))
1237  #  endif
1238          {
1239            GC_stdout = log_d;
1240            GC_stderr = log_d;
1241          }
1242        }
1243      }
1244    }
1245  #endif
1246  #if !defined(NO_DEBUGGING) && !defined(GC_DUMP_REGULARLY)
1247    if (GETENV("GC_DUMP_REGULARLY") != NULL) {
1248      GC_dump_regularly = TRUE;
1249    }
1250  #endif
1251  #ifdef KEEP_BACK_PTRS
1252    {
1253      const char *str = GETENV("GC_BACKTRACES");
1254  
1255      if (str != NULL) {
1256        GC_backtraces = atol(str);
1257        if (str[0] == '\0')
1258          GC_backtraces = 1;
1259      }
1260    }
1261  #endif
1262  #ifndef NO_FIND_LEAK
1263    if (GETENV("GC_FIND_LEAK") != NULL) {
1264      GC_find_leak = 1;
1265    }
1266  #  ifndef SHORT_DBG_HDRS
1267    if (GETENV("GC_FINDLEAK_DELAY_FREE") != NULL) {
1268      GC_findleak_delay_free = TRUE;
1269    }
1270  #  endif
1271  #endif
1272    if (GETENV("GC_ALL_INTERIOR_POINTERS") != NULL) {
1273      GC_all_interior_pointers = 1;
1274    }
1275    if (GETENV("GC_DONT_GC") != NULL) {
1276  #if defined(LINT2) \
1277      && !(defined(GC_ASSERTIONS) && defined(GC_ALWAYS_MULTITHREADED))
1278      GC_disable();
1279  #else
1280      GC_dont_gc = 1;
1281  #endif
1282    }
1283  #if !defined(SMALL_CONFIG) && !defined(GC_PRINT_BACK_HEIGHT)
1284    if (GETENV("GC_PRINT_BACK_HEIGHT") != NULL) {
1285  #  ifdef MAKE_BACK_GRAPH
1286      GC_print_back_height = TRUE;
1287  #  else
1288      GC_err_printf("Back height is not available!\n");
1289  #  endif
1290    }
1291  #endif
1292    {
1293      const char *str = GETENV("GC_TRACE");
1294  
1295      if (str != NULL) {
1296  #ifndef ENABLE_TRACE
1297        WARN("Tracing not enabled: Ignoring GC_TRACE value\n", 0);
1298  #else
1299        ptr_t p = MAKE_CPTR(STRTOULL(str, NULL, 16));
1300  
1301        if (ADDR(p) < 0x1000)
1302          WARN("Unlikely trace address: %p\n", p);
1303        GC_trace_ptr = p;
1304  #endif
1305      }
1306    }
1307  #ifdef GC_COLLECT_AT_MALLOC
1308    {
1309      const char *str = GETENV("GC_COLLECT_AT_MALLOC");
1310  
1311      if (str != NULL) {
1312        size_t min_lb = (size_t)STRTOULL(str, NULL, 10);
1313  
1314        if (min_lb > 0)
1315          GC_dbg_collect_at_malloc_min_lb = min_lb;
1316      }
1317    }
1318  #endif
1319  #if !defined(GC_DISABLE_INCREMENTAL) && !defined(NO_CLOCK)
1320    {
1321      const char *str = GETENV("GC_PAUSE_TIME_TARGET");
1322  
1323      if (str != NULL) {
1324        long time_limit = atol(str);
1325  
1326        if (time_limit > 0) {
1327          GC_time_limit = (unsigned long)time_limit;
1328        }
1329      }
1330    }
1331  #endif
1332  #ifndef SMALL_CONFIG
1333    {
1334      const char *str = GETENV("GC_FULL_FREQUENCY");
1335  
1336      if (str != NULL) {
1337        int full_freq = atoi(str);
1338  
1339        if (full_freq > 0)
1340          GC_full_freq = full_freq;
1341      }
1342    }
1343  #endif
1344  #ifndef NO_BLACK_LISTING
1345    {
1346      char const *str = GETENV("GC_LARGE_ALLOC_WARN_INTERVAL");
1347  
1348      if (str != NULL) {
1349        long interval = atol(str);
1350  
1351        if (interval <= 0) {
1352          WARN("GC_LARGE_ALLOC_WARN_INTERVAL environment variable has"
1353               " bad value - ignoring\n",
1354               0);
1355        } else {
1356          GC_large_alloc_warn_interval = interval;
1357        }
1358      }
1359    }
1360  #endif
1361    {
1362      const char *str = GETENV("GC_FREE_SPACE_DIVISOR");
1363  
1364      if (str != NULL) {
1365        int space_divisor = atoi(str);
1366  
1367        if (space_divisor > 0)
1368          GC_free_space_divisor = (unsigned)space_divisor;
1369      }
1370    }
1371  #ifdef USE_MUNMAP
1372    {
1373      const char *str = GETENV("GC_UNMAP_THRESHOLD");
1374  
1375      if (str != NULL) {
1376        if (str[0] == '0' && str[1] == '\0') {
1377          /* "0" is used to disable unmapping. */
1378          GC_unmap_threshold = 0;
1379        } else {
1380          int unmap_threshold = atoi(str);
1381  
1382          if (unmap_threshold > 0)
1383            GC_unmap_threshold = (unsigned)unmap_threshold;
1384        }
1385      }
1386    }
1387    {
1388      const char *str = GETENV("GC_FORCE_UNMAP_ON_GCOLLECT");
1389  
1390      if (str != NULL) {
1391        if (str[0] == '0' && str[1] == '\0') {
1392          /* "0" is used to turn off the mode. */
1393          GC_force_unmap_on_gcollect = FALSE;
1394        } else {
1395          GC_force_unmap_on_gcollect = TRUE;
1396        }
1397      }
1398    }
1399    {
1400      const char *str = GETENV("GC_USE_ENTIRE_HEAP");
1401  
1402      if (str != NULL) {
1403        if (str[0] == '0' && str[1] == '\0') {
1404          /* "0" is used to turn off the mode. */
1405          GC_use_entire_heap = FALSE;
1406        } else {
1407          GC_use_entire_heap = TRUE;
1408        }
1409      }
1410    }
1411  #endif
1412  #if !defined(NO_DEBUGGING) && !defined(NO_CLOCK)
1413    GET_TIME(GC_init_time);
1414  #endif
1415    maybe_install_looping_handler();
1416  #if ALIGNMENT > GC_DS_TAGS
1417    /* Adjust normal object descriptor for extra allocation. */
1418    if (EXTRA_BYTES != 0)
1419      GC_obj_kinds[NORMAL].ok_descriptor
1420          = ((~(word)ALIGNMENT) + 1) | GC_DS_LENGTH;
1421  #endif
1422    GC_exclude_static_roots_inner(beginGC_arrays, endGC_arrays);
1423    GC_exclude_static_roots_inner(beginGC_obj_kinds, endGC_obj_kinds);
1424  #ifdef SEPARATE_GLOBALS
1425    GC_exclude_static_roots_inner(beginGC_objfreelist, endGC_objfreelist);
1426    GC_exclude_static_roots_inner(beginGC_aobjfreelist, endGC_aobjfreelist);
1427  #endif
1428  #if defined(USE_PROC_FOR_LIBRARIES) && defined(LINUX) && defined(THREADS)
1429    /*
1430     * TODO: `USE_PROC_FOR_LIBRARIES` with LinuxThreads performs poorly!
1431     * If thread stacks are cached, they tend to be scanned in entirety
1432     * as part of the root set.  This will grow them to maximum size, and
1433     * is generally not desirable.
1434     */
1435  #endif
1436  #if !defined(THREADS) || !(defined(SN_TARGET_PS3) || defined(SN_TARGET_PSP2))
1437    if (NULL == GC_stackbottom) {
1438      GC_stackbottom = GC_get_main_stack_base();
1439  #  if (defined(LINUX) || defined(HPUX)) && defined(IA64)
1440      GC_register_stackbottom = GC_get_register_stack_base();
1441  #  endif
1442    } else {
1443  #  if (defined(LINUX) || defined(HPUX)) && defined(IA64)
1444      if (NULL == GC_register_stackbottom) {
1445        WARN("GC_register_stackbottom should be set with GC_stackbottom\n", 0);
1446        /*
1447         * The following may fail, since we may rely on alignment properties
1448         * that may not hold with `GC_stackbottom` value set by client.
1449         */
1450        GC_register_stackbottom = GC_get_register_stack_base();
1451      }
1452  #  endif
1453    }
1454  #endif
1455  #if !defined(CPPCHECK)
1456    GC_STATIC_ASSERT(sizeof(size_t) <= sizeof(ptrdiff_t));
1457  #  ifdef AO_HAVE_store
1458    /*
1459     * As of now, `hb_descr`, `mse_descr` and `hb_marks[i]` might be treated
1460     * as variables of `word` type but might be accessed atomically.
1461     */
1462    GC_STATIC_ASSERT(sizeof(AO_t) == sizeof(word));
1463  #  endif
1464    GC_STATIC_ASSERT(sizeof(ptrdiff_t) == sizeof(word));
1465    GC_STATIC_ASSERT(sizeof(GC_signed_word) == sizeof(word));
1466    GC_STATIC_ASSERT(sizeof(word) * 8 == CPP_WORDSZ);
1467    GC_STATIC_ASSERT(sizeof(ptr_t) * 8 == CPP_PTRSZ);
1468    GC_STATIC_ASSERT(sizeof(ptr_t) == sizeof(GC_uintptr_t));
1469    GC_STATIC_ASSERT(sizeof(GC_oom_func) == sizeof(GC_funcptr_uint));
1470  #  ifdef FUNCPTR_IS_DATAPTR
1471    GC_STATIC_ASSERT(sizeof(ptr_t) == sizeof(GC_funcptr_uint));
1472  #  endif
1473    GC_STATIC_ASSERT((word)(-1) > (word)0); /*< `word` should be unsigned */
1474    /*
1475     * We no longer check for `(void *)-1 > NULL` since all pointers
1476     * are explicitly cast to `word` in every less/greater comparison.
1477     */
1478    GC_STATIC_ASSERT((GC_signed_word)(-1) < (GC_signed_word)0);
1479  #endif
1480    GC_STATIC_ASSERT(sizeof(struct hblk) == HBLKSIZE);
1481  #ifndef THREADS
1482    GC_ASSERT(!HOTTER_THAN(GC_stackbottom, GC_approx_sp()));
1483  #endif
1484    GC_init_headers();
1485  #ifdef SEARCH_FOR_DATA_START
1486    /*
1487     * For `MPROTECT_VDB`, the temporary fault handler should be installed
1488     * first, before the write fault one in `GC_dirty_init`.
1489     */
1490    if (GC_REGISTER_MAIN_STATIC_DATA())
1491      GC_init_linux_data_start();
1492  #endif
1493  #ifndef GC_DISABLE_INCREMENTAL
1494    if (GC_incremental || GETENV("GC_ENABLE_INCREMENTAL") != NULL) {
1495      set_incremental_mode_on();
1496      GC_ASSERT(0 == GC_bytes_allocd);
1497    }
1498  #endif
1499  
1500    /*
1501     * Add the initial guess of root sets.  Do this first, since `sbrk(0)`
1502     * might be used.
1503     */
1504    if (GC_REGISTER_MAIN_STATIC_DATA())
1505      GC_register_data_segments();
1506  
1507    GC_bl_init();
1508    GC_mark_init();
1509    {
1510      const char *str = GETENV("GC_INITIAL_HEAP_SIZE");
1511  
1512      if (str != NULL) {
1513        word value = GC_parse_mem_size_arg(str);
1514  
1515        if (GC_WORD_MAX == value) {
1516          WARN("Bad initial heap size %s - ignoring\n", str);
1517        } else {
1518          initial_heap_sz = value;
1519        }
1520      }
1521    }
1522    {
1523      const char *str = GETENV("GC_MAXIMUM_HEAP_SIZE");
1524  
1525      if (str != NULL) {
1526        word max_heap_sz = GC_parse_mem_size_arg(str);
1527  
1528        if (max_heap_sz < initial_heap_sz || GC_WORD_MAX == max_heap_sz) {
1529          WARN("Bad maximum heap size %s - ignoring\n", str);
1530        } else {
1531          if (0 == GC_max_retries)
1532            GC_max_retries = 2;
1533          GC_set_max_heap_size(max_heap_sz);
1534        }
1535      }
1536    }
1537    if (initial_heap_sz != 0) {
1538      if (!GC_expand_hp_inner(divHBLKSZ(initial_heap_sz))) {
1539        GC_err_printf("Can't start up: not enough memory\n");
1540        EXIT();
1541      } else {
1542        GC_requested_heapsize += initial_heap_sz;
1543      }
1544    }
1545    if (GC_all_interior_pointers)
1546      GC_initialize_offsets();
1547    GC_register_displacement_inner(0);
1548  #ifdef REDIR_MALLOC_AND_LINUXTHREADS
1549    if (!GC_all_interior_pointers) {
1550      /* TLS ABI uses "pointer-sized" offsets for `dtv`. */
1551      GC_register_displacement_inner(sizeof(void *));
1552    }
1553  #endif
1554    GC_init_size_map();
1555    GC_is_initialized = TRUE;
1556  #ifdef THREADS
1557  #  if defined(LINT2) \
1558        && !(defined(GC_ASSERTIONS) && defined(GC_ALWAYS_MULTITHREADED))
1559    LOCK();
1560    GC_thr_init();
1561    UNLOCK();
1562  #  else
1563    GC_thr_init();
1564  #  endif
1565  #endif
1566    COND_DUMP;
1567    /* Get black list set up and/or the incremental GC started. */
1568    if (!GC_dont_precollect || GC_incremental) {
1569  #if defined(DYNAMIC_LOADING) && defined(DARWIN)
1570      GC_ASSERT(0 == GC_bytes_allocd);
1571  #endif
1572      GC_gcollect_inner();
1573    }
1574  #if defined(GC_ASSERTIONS) && defined(GC_ALWAYS_MULTITHREADED)
1575    UNLOCK();
1576  #endif
1577  #if defined(THREADS) && defined(UNIX_LIKE) && !defined(NO_GETCONTEXT)
1578    /* Ensure `getcontext_works` is set to avoid potential data race. */
1579    if (GC_dont_gc || GC_dont_precollect)
1580      GC_with_callee_saves_pushed(callee_saves_pushed_dummy_fn, NULL);
1581  #endif
1582  #ifndef DONT_USE_ATEXIT
1583    if (GC_find_leak) {
1584      /*
1585       * This is to give us at least one chance to detect leaks.
1586       * This may report some very benign leaks, but...
1587       */
1588      atexit(GC_exit_check);
1589    }
1590  #endif
1591    /*
1592     * The rest of this again assumes we do not really hold the allocator
1593     * lock.
1594     */
1595  
1596  #ifdef THREADS
1597    /* Initialize thread-local allocation. */
1598    GC_init_parallel();
1599  #endif
1600  
1601  #if defined(DYNAMIC_LOADING) && defined(DARWIN)
1602    /*
1603     * This must be called *without* the allocator lock held and before
1604     * any threads are created.
1605     */
1606    GC_init_dyld();
1607  #endif
1608    RESTORE_CANCEL(cancel_state);
1609    /*
1610     * It is not safe to allocate any object till completion of `GC_init`
1611     * (in particular by `GC_thr_init`), i.e. before `GC_init_dyld()` call
1612     * and initialization of the incremental mode (if any).
1613     */
1614  #if defined(GWW_VDB) && !defined(KEEP_BACK_PTRS)
1615    GC_ASSERT(GC_bytes_allocd + GC_bytes_allocd_before_gc == 0);
1616  #endif
1617  }
1618  
1619  GC_API void GC_CALL
1620  GC_enable_incremental(void)
1621  {
1622  #if !defined(GC_DISABLE_INCREMENTAL) && !defined(KEEP_BACK_PTRS)
1623    /*
1624     * If we are keeping back pointers, the collector itself dirties all pages
1625     * on which objects have been marked, making the incremental collection
1626     * pointless.
1627     */
1628    if (!GC_find_leak_inner && NULL == GETENV("GC_DISABLE_INCREMENTAL")) {
1629      LOCK();
1630      if (!GC_incremental) {
1631        GC_setpagesize();
1632        /* TODO: Should we skip enabling incremental if win32s? */
1633  
1634        /* Install the looping handler before write fault handler! */
1635        maybe_install_looping_handler();
1636        if (!GC_is_initialized) {
1637          /* Indicate the intention to turn it on. */
1638          GC_incremental = TRUE;
1639          UNLOCK();
1640          GC_init();
1641          LOCK();
1642        } else {
1643          set_incremental_mode_on();
1644        }
1645        /* Cannot easily do it if `GC_dont_gc`. */
1646        if (GC_incremental && !GC_dont_gc) {
1647          IF_CANCEL(int cancel_state;)
1648  
1649          DISABLE_CANCEL(cancel_state);
1650          if (GC_bytes_allocd > 0) {
1651            /* There may be unmarked reachable objects. */
1652            GC_gcollect_inner();
1653          } else {
1654            /*
1655             * We are OK in assuming everything is clean since nothing can
1656             * point to an unmarked object.
1657             */
1658  #  ifdef CHECKSUMS
1659            GC_read_dirty(FALSE);
1660  #  else
1661            GC_read_dirty(TRUE);
1662  #  endif
1663          }
1664          RESTORE_CANCEL(cancel_state);
1665        }
1666      }
1667      UNLOCK();
1668      return;
1669    }
1670  #endif
1671    GC_init();
1672  }
1673  
1674  GC_API void GC_CALL
1675  GC_start_mark_threads(void)
1676  {
1677  #ifdef PARALLEL_MARK
1678    IF_CANCEL(int cancel_state;)
1679  
1680    DISABLE_CANCEL(cancel_state);
1681    LOCK();
1682    GC_start_mark_threads_inner();
1683    UNLOCK();
1684    RESTORE_CANCEL(cancel_state);
1685  #else
1686    /* No action since parallel markers are disabled (or no POSIX `fork`). */
1687    GC_ASSERT(I_DONT_HOLD_LOCK());
1688  #endif
1689  }
1690  
1691  GC_API void GC_CALL
1692  GC_deinit(void)
1693  {
1694    if (GC_is_initialized) {
1695      /* Prevent duplicate resource close. */
1696      GC_is_initialized = FALSE;
1697      GC_bytes_allocd = 0;
1698      GC_bytes_allocd_before_gc = 0;
1699  #if defined(GC_WIN32_THREADS) && (defined(MSWIN32) || defined(MSWINCE))
1700  #  if !defined(CONSOLE_LOG) || defined(MSWINCE)
1701      DeleteCriticalSection(&GC_write_cs);
1702  #  endif
1703  #  if !defined(GC_PTHREADS) && !defined(USE_RWLOCK)
1704      DeleteCriticalSection(&GC_allocate_ml);
1705  #  endif
1706  #endif
1707    }
1708  }
1709  
1710  #if (defined(MSWIN32) && !defined(CONSOLE_LOG)) || defined(MSWINCE)
1711  
1712  STATIC HANDLE GC_log = 0;
1713  
1714  #  ifdef THREADS
1715  #    if defined(PARALLEL_MARK) && !defined(GC_ALWAYS_MULTITHREADED)
1716  #      define IF_NEED_TO_LOCK(x)            \
1717          if (GC_parallel || GC_need_to_lock) \
1718          x
1719  #    else
1720  #      define IF_NEED_TO_LOCK(x) \
1721          if (GC_need_to_lock)     \
1722          x
1723  #    endif
1724  #  else
1725  #    define IF_NEED_TO_LOCK(x)
1726  #  endif /* !THREADS */
1727  
1728  #  ifdef MSWINRT_FLAVOR
1729  #    include <windows.storage.h>
1730  
1731  /*
1732   * This API function is defined in platform `roapi.h` file, but we cannot
1733   * include it here since it does not compile in C.
1734   */
1735  DECLSPEC_IMPORT HRESULT WINAPI
1736  RoGetActivationFactory(HSTRING activatableClassId, REFIID iid, void **factory);
1737  
1738  static GC_bool
1739  getWinRTLogPath(wchar_t *buf, size_t bufLen)
1740  {
1741    static const GUID kIID_IApplicationDataStatics
1742        = { 0x5612147B, 0xE843, 0x45E3, 0x94, 0xD8, 0x06,
1743            0x16,       0x9E,   0x3C,   0x8E, 0x17 };
1744    static const GUID kIID_IStorageItem
1745        = { 0x4207A996, 0xCA2F, 0x42F7, 0xBD, 0xE8, 0x8B,
1746            0x10,       0x45,   0x7A,   0x7F, 0x30 };
1747    GC_bool result = FALSE;
1748    HSTRING_HEADER appDataClassNameHeader;
1749    HSTRING appDataClassName;
1750    __x_ABI_CWindows_CStorage_CIApplicationDataStatics *appDataStatics = 0;
1751  
1752    GC_ASSERT(bufLen > 0);
1753    if (SUCCEEDED(WindowsCreateStringReference(
1754            RuntimeClass_Windows_Storage_ApplicationData,
1755            (sizeof(RuntimeClass_Windows_Storage_ApplicationData) - 1)
1756                / sizeof(wchar_t),
1757            &appDataClassNameHeader, &appDataClassName))
1758        && SUCCEEDED(RoGetActivationFactory(
1759            appDataClassName, &kIID_IApplicationDataStatics, &appDataStatics))) {
1760      __x_ABI_CWindows_CStorage_CIApplicationData *appData = NULL;
1761      __x_ABI_CWindows_CStorage_CIStorageFolder *tempFolder = NULL;
1762      __x_ABI_CWindows_CStorage_CIStorageItem *tempFolderItem = NULL;
1763      HSTRING tempPath = NULL;
1764  
1765      if (SUCCEEDED(
1766              appDataStatics->lpVtbl->get_Current(appDataStatics, &appData))
1767          && SUCCEEDED(
1768              appData->lpVtbl->get_TemporaryFolder(appData, &tempFolder))
1769          && SUCCEEDED(tempFolder->lpVtbl->QueryInterface(
1770              tempFolder, &kIID_IStorageItem, &tempFolderItem))
1771          && SUCCEEDED(
1772              tempFolderItem->lpVtbl->get_Path(tempFolderItem, &tempPath))) {
1773        UINT32 tempPathLen;
1774        const wchar_t *tempPathBuf
1775            = WindowsGetStringRawBuffer(tempPath, &tempPathLen);
1776  
1777        buf[0] = '\0';
1778        if (wcsncat_s(buf, bufLen, tempPathBuf, tempPathLen) == 0
1779            && wcscat_s(buf, bufLen, L"\\") == 0
1780            && wcscat_s(buf, bufLen, TEXT(GC_LOG_STD_NAME)) == 0)
1781          result = TRUE;
1782        WindowsDeleteString(tempPath);
1783      }
1784  
1785      if (tempFolderItem != NULL)
1786        tempFolderItem->lpVtbl->Release(tempFolderItem);
1787      if (tempFolder != NULL)
1788        tempFolder->lpVtbl->Release(tempFolder);
1789      if (appData != NULL)
1790        appData->lpVtbl->Release(appData);
1791      appDataStatics->lpVtbl->Release(appDataStatics);
1792    }
1793    return result;
1794  }
1795  #  endif /* MSWINRT_FLAVOR */
1796  
1797  STATIC HANDLE
1798  GC_CreateLogFile(void)
1799  {
1800    HANDLE hFile;
1801  #  ifdef MSWINRT_FLAVOR
1802    TCHAR pathBuf[_MAX_PATH + 0x10]; /*< buffer for file path plus extension */
1803  
1804    hFile = INVALID_HANDLE_VALUE;
1805    if (getWinRTLogPath(pathBuf, _MAX_PATH + 1)) {
1806      CREATEFILE2_EXTENDED_PARAMETERS extParams;
1807  
1808      BZERO(&extParams, sizeof(extParams));
1809      extParams.dwSize = sizeof(extParams);
1810      extParams.dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
1811      extParams.dwFileFlags
1812          = GC_print_stats == VERBOSE ? 0 : FILE_FLAG_WRITE_THROUGH;
1813      hFile = CreateFile2(pathBuf, GENERIC_WRITE, FILE_SHARE_READ, CREATE_ALWAYS,
1814                          &extParams);
1815    }
1816  
1817  #  else
1818    TCHAR *logPath;
1819  #    if defined(NO_GETENV_WIN32) && defined(CPPCHECK)
1820  #      define appendToFile FALSE
1821  #    else
1822    BOOL appendToFile = FALSE;
1823  #    endif
1824  #    if !defined(NO_GETENV_WIN32) || !defined(OLD_WIN32_LOG_FILE)
1825    TCHAR pathBuf[_MAX_PATH + 0x10]; /*< buffer for file path plus extension */
1826  
1827    logPath = pathBuf;
1828  #    endif
1829  
1830    /* Use `GetEnvironmentVariable` instead of `GETENV` for Unicode support. */
1831  #    ifndef NO_GETENV_WIN32
1832    if (GetEnvironmentVariable(TEXT("GC_LOG_FILE"), pathBuf, _MAX_PATH + 1) - 1U
1833        < (DWORD)_MAX_PATH) {
1834      appendToFile = TRUE;
1835    } else
1836  #    endif
1837    /* else */ {
1838      /* Environment var not found or its value too long. */
1839  #    ifdef OLD_WIN32_LOG_FILE
1840      logPath = TEXT(GC_LOG_STD_NAME);
1841  #    else
1842      int len
1843          = (int)GetModuleFileName(NULL /* `hModule` */, pathBuf, _MAX_PATH + 1);
1844      /* If `GetModuleFileName()` has failed, then len is 0. */
1845      if (len > 4 && pathBuf[len - 4] == (TCHAR)'.') {
1846        /* Strip the executable file extension. */
1847        len -= 4;
1848      }
1849      BCOPY(TEXT(".") TEXT(GC_LOG_STD_NAME), &pathBuf[len],
1850            sizeof(TEXT(".") TEXT(GC_LOG_STD_NAME)));
1851  #    endif
1852    }
1853  
1854    hFile = CreateFile(logPath, GENERIC_WRITE, FILE_SHARE_READ,
1855                       NULL /* `lpSecurityAttributes` */,
1856                       appendToFile ? OPEN_ALWAYS : CREATE_ALWAYS,
1857                       GC_print_stats == VERBOSE
1858                           ? FILE_ATTRIBUTE_NORMAL
1859                           :
1860                           /* immediately flush writes unless very verbose */
1861                           FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH,
1862                       NULL /* `hTemplateFile` */);
1863  
1864  #    ifndef NO_GETENV_WIN32
1865    if (appendToFile && hFile != INVALID_HANDLE_VALUE) {
1866      LONG posHigh = 0;
1867      /* Seek to the file end (ignoring any error). */
1868      (void)SetFilePointer(hFile, 0, &posHigh, FILE_END);
1869    }
1870  #    endif
1871  #    undef appendToFile
1872  #  endif
1873    return hFile;
1874  }
1875  
1876  STATIC int
1877  GC_write(const char *buf, size_t len)
1878  {
1879    BOOL res;
1880    DWORD written;
1881  #  if defined(THREADS) && defined(GC_ASSERTIONS)
1882    /* This is to prevent infinite recursion at abort. */
1883    static GC_bool inside_write = FALSE;
1884    if (inside_write)
1885      return -1;
1886  #  endif
1887  
1888    if (0 == len)
1889      return 0;
1890    IF_NEED_TO_LOCK(EnterCriticalSection(&GC_write_cs));
1891  #  if defined(THREADS) && defined(GC_ASSERTIONS)
1892    if (GC_write_disabled) {
1893      inside_write = TRUE;
1894      ABORT("Assertion failure: GC_write called with write_disabled");
1895    }
1896  #  endif
1897    if (0 == GC_log) {
1898      GC_log = GC_CreateLogFile();
1899    }
1900    if (GC_log == INVALID_HANDLE_VALUE) {
1901      IF_NEED_TO_LOCK(LeaveCriticalSection(&GC_write_cs));
1902  #  ifdef NO_DEBUGGING
1903      /*
1904       * Ignore open log failure (e.g., it might be caused by read-only folder
1905       * of the client application).
1906       */
1907      return 0;
1908  #  else
1909      return -1;
1910  #  endif
1911    }
1912    res = WriteFile(GC_log, buf, (DWORD)len, &written, NULL);
1913  #  if defined(_MSC_VER) && defined(_DEBUG) && !defined(NO_CRT) \
1914        && !defined(NO_CRTDBGREPORT)
1915  #    ifdef MSWINCE
1916    /* There is no `CrtDbgReport()` in WinCE. */
1917    {
1918      WCHAR wbuf[1024];
1919  
1920      /* Always use Unicode variant of `OutputDebugString()`. */
1921      wbuf[MultiByteToWideChar(CP_ACP, 0 /* `dwFlags` */, buf, len, wbuf,
1922                               sizeof(wbuf) / sizeof(wbuf[0]) - 1)]
1923          = 0;
1924      OutputDebugStringW(wbuf);
1925    }
1926  #    else
1927    _CrtDbgReport(_CRT_WARN, NULL, 0, NULL, "%.*s", len, buf);
1928  #    endif
1929  #  endif
1930    IF_NEED_TO_LOCK(LeaveCriticalSection(&GC_write_cs));
1931    return res ? (int)written : -1;
1932  }
1933  
1934  /* TODO: This is pretty ugly... */
1935  #  define WRITE(f, buf, len) GC_write(buf, len)
1936  
1937  #elif defined(OS2)
1938  STATIC FILE *GC_stdout = NULL;
1939  STATIC FILE *GC_stderr = NULL;
1940  STATIC FILE *GC_log = NULL;
1941  
1942  /* Initialize `GC_log` (and the friends) passed to `GC_write()`. */
1943  STATIC void
1944  GC_set_files(void)
1945  {
1946    if (GC_stdout == NULL) {
1947      GC_stdout = stdout;
1948    }
1949    if (GC_stderr == NULL) {
1950      GC_stderr = stderr;
1951    }
1952    if (GC_log == NULL) {
1953      GC_log = stderr;
1954    }
1955  }
1956  
1957  GC_INLINE int
1958  GC_write(FILE *f, const char *buf, size_t len)
1959  {
1960    int res = fwrite(buf, 1, len, f);
1961    fflush(f);
1962    return res;
1963  }
1964  
1965  #  define WRITE(f, buf, len) (GC_set_files(), GC_write(f, buf, len))
1966  
1967  #elif defined(GC_ANDROID_LOG)
1968  
1969  #  include <android/log.h>
1970  
1971  #  ifndef GC_ANDROID_LOG_TAG
1972  #    define GC_ANDROID_LOG_TAG "BDWGC"
1973  #  endif
1974  
1975  #  define GC_stdout ANDROID_LOG_DEBUG
1976  #  define GC_stderr ANDROID_LOG_ERROR
1977  #  define GC_log GC_stdout
1978  
1979  #  define WRITE(level, buf, unused_len) \
1980      __android_log_write(level, GC_ANDROID_LOG_TAG, buf)
1981  
1982  #elif defined(NN_PLATFORM_CTR)
1983  int n3ds_log_write(const char *text, int length);
1984  #  define WRITE(level, buf, len) n3ds_log_write(buf, len)
1985  
1986  #elif defined(NINTENDO_SWITCH)
1987  int switch_log_write(const char *text, int length);
1988  #  define WRITE(level, buf, len) switch_log_write(buf, len)
1989  
1990  #else
1991  
1992  #  if !defined(ECOS) && !defined(NOSYS) && !defined(PLATFORM_WRITE) \
1993        && !defined(SN_TARGET_PSP2)
1994  #    include <errno.h>
1995  #  endif
1996  
1997  STATIC int
1998  GC_write(int fd, const char *buf, size_t len)
1999  {
2000  #  if defined(ECOS) || defined(PLATFORM_WRITE) || defined(SN_TARGET_PSP2) \
2001        || defined(NOSYS)
2002    UNUSED_ARG(fd);
2003  #    ifdef ECOS
2004    /* FIXME: This seems to be defined nowhere at present. */
2005    /* `_Jv_diag_write(buf, len);` */
2006  #    else
2007    /* No writing. */
2008  #    endif
2009    UNUSED_ARG(buf);
2010    return (int)len;
2011  #  else
2012    size_t bytes_written = 0;
2013    IF_CANCEL(int cancel_state;)
2014  
2015    DISABLE_CANCEL(cancel_state);
2016    while (bytes_written < len) {
2017      int result;
2018  
2019  #    if defined(SOLARIS) && defined(THREADS)
2020      result = syscall(SYS_write, fd, buf + bytes_written, len - bytes_written);
2021  #    elif defined(_MSC_VER)
2022      result = _write(fd, buf + bytes_written, (unsigned)(len - bytes_written));
2023  #    else
2024      result = (int)write(fd, buf + bytes_written, len - bytes_written);
2025  #    endif
2026      if (result < 0) {
2027        if (EAGAIN == errno) {
2028          /* Resource is temporarily unavailable. */
2029          continue;
2030        }
2031        RESTORE_CANCEL(cancel_state);
2032        return -1;
2033      }
2034  #    ifdef LINT2
2035      if ((unsigned)result > len - bytes_written)
2036        ABORT("write() result cannot be bigger than requested length");
2037  #    endif
2038      bytes_written += (unsigned)result;
2039    }
2040    RESTORE_CANCEL(cancel_state);
2041    return (int)bytes_written;
2042  #  endif
2043  }
2044  
2045  #  define WRITE(f, buf, len) GC_write(f, buf, len)
2046  #endif /* !MSWINCE && !OS2 && !GC_ANDROID_LOG */
2047  
2048  #ifndef GC_DISABLE_SNPRINTF
2049  #  define BUFSZ 1024
2050  
2051  #  if defined(DJGPP) || defined(__STRICT_ANSI__)
2052  /* `vsnprintf` is missing in DJGPP (v2.0.3). */
2053  #    define GC_VSNPRINTF(buf, bufsz, format, args) vsprintf(buf, format, args)
2054  #  elif defined(_MSC_VER)
2055  #    ifdef MSWINCE
2056  /* `_vsnprintf` is deprecated in WinCE. */
2057  #      define GC_VSNPRINTF StringCchVPrintfA
2058  #    else
2059  #      define GC_VSNPRINTF _vsnprintf
2060  #    endif
2061  #  else
2062  #    define GC_VSNPRINTF vsnprintf
2063  #  endif
2064  
2065  /*
2066   * A variant of `printf` that is unlikely to call `malloc`, and is thus
2067   * safer to call from the collector in case `malloc` has been bound to
2068   * `GC_malloc`.  Floating-point arguments and formats should be avoided,
2069   * since the conversion is more likely to allocate memory.
2070   * Assumes that no more than `BUFSZ - 1` characters are written at once.
2071   */
2072  #  define GC_PRINTF_FILLBUF(buf, format)                      \
2073      do {                                                      \
2074        va_list args;                                           \
2075        va_start(args, format);                                 \
2076        (buf)[sizeof(buf) - 1] = 0x15; /*< guard */             \
2077        (void)GC_VSNPRINTF(buf, sizeof(buf) - 1, format, args); \
2078        va_end(args);                                           \
2079        if ((buf)[sizeof(buf) - 1] != 0x15)                     \
2080          ABORT("GC_printf clobbered stack");                   \
2081      } while (0)
2082  
2083  #  define DECL_BUF_AND_PRINTF_TO(buf, format) \
2084      char buf[BUFSZ + 1];                      \
2085      GC_PRINTF_FILLBUF(buf, format)
2086  #else
2087  /*
2088   * At most, when `vsnprintf()` is unavailable, we could only print the
2089   * format string as is, not handling the format specifiers (if any), thus
2090   * skipping the rest of the `printf` arguments.
2091   */
2092  #  define DECL_BUF_AND_PRINTF_TO(buf, format) const char *buf = (format)
2093  #endif /* GC_DISABLE_SNPRINTF */
2094  
2095  void
2096  GC_printf(const char *format, ...)
2097  {
2098    if (!GC_quiet) {
2099      DECL_BUF_AND_PRINTF_TO(buf, format);
2100  #ifdef NACL
2101      (void)WRITE(GC_stdout, buf, strlen(buf));
2102      /* Ignore errors silently. */
2103  #else
2104      if (WRITE(GC_stdout, buf, strlen(buf)) < 0
2105  #  if defined(CYGWIN32) || (defined(CONSOLE_LOG) && defined(MSWIN32))
2106          && GC_stdout != GC_DEFAULT_STDOUT_FD
2107  #  endif
2108      ) {
2109        ABORT("write to stdout failed");
2110      }
2111  #endif
2112    }
2113  }
2114  
2115  void
2116  GC_err_printf(const char *format, ...)
2117  {
2118    DECL_BUF_AND_PRINTF_TO(buf, format);
2119    GC_err_puts(buf);
2120  }
2121  
2122  void
2123  GC_log_printf(const char *format, ...)
2124  {
2125    DECL_BUF_AND_PRINTF_TO(buf, format);
2126  #ifdef NACL
2127    (void)WRITE(GC_log, buf, strlen(buf));
2128  #else
2129    if (WRITE(GC_log, buf, strlen(buf)) < 0
2130  #  if defined(CYGWIN32) || (defined(CONSOLE_LOG) && defined(MSWIN32))
2131        && GC_log != GC_DEFAULT_STDERR_FD
2132  #  endif
2133    ) {
2134      ABORT("write to GC log failed");
2135    }
2136  #endif
2137  }
2138  
2139  #ifndef GC_ANDROID_LOG
2140  
2141  #  define GC_warn_printf GC_err_printf
2142  
2143  #else
2144  
2145  GC_INNER void
2146  GC_info_log_printf(const char *format, ...)
2147  {
2148    DECL_BUF_AND_PRINTF_TO(buf, format);
2149    (void)WRITE(ANDROID_LOG_INFO, buf, 0 /* unused */);
2150  }
2151  
2152  GC_INNER void
2153  GC_verbose_log_printf(const char *format, ...)
2154  {
2155    DECL_BUF_AND_PRINTF_TO(buf, format);
2156    /* Note: write errors are ignored. */
2157    (void)WRITE(ANDROID_LOG_VERBOSE, buf, 0);
2158  }
2159  
2160  STATIC void
2161  GC_warn_printf(const char *format, ...)
2162  {
2163    DECL_BUF_AND_PRINTF_TO(buf, format);
2164    (void)WRITE(ANDROID_LOG_WARN, buf, 0);
2165  }
2166  
2167  #endif /* GC_ANDROID_LOG */
2168  
2169  void
2170  GC_err_puts(const char *s)
2171  {
2172    /* Note: write errors are ignored. */
2173    (void)WRITE(GC_stderr, s, strlen(s));
2174  }
2175  
2176  STATIC void GC_CALLBACK
2177  GC_default_warn_proc(const char *msg, GC_uintptr_t arg)
2178  {
2179    /* TODO: Add assertion on argument to comply with `msg` (format). */
2180    GC_warn_printf(msg, arg);
2181  }
2182  
2183  GC_INNER GC_warn_proc GC_current_warn_proc = GC_default_warn_proc;
2184  
2185  GC_API void GC_CALLBACK
2186  GC_ignore_warn_proc(const char *msg, GC_uintptr_t arg)
2187  {
2188    if (GC_print_stats) {
2189      /* Do not ignore warnings if stats printing is on. */
2190      GC_default_warn_proc(msg, arg);
2191    }
2192  }
2193  
2194  GC_API void GC_CALL
2195  GC_set_warn_proc(GC_warn_proc p)
2196  {
2197    GC_ASSERT(NONNULL_ARG_NOT_NULL(p));
2198    LOCK();
2199    GC_current_warn_proc = p;
2200    UNLOCK();
2201  }
2202  
2203  GC_API GC_warn_proc GC_CALL
2204  GC_get_warn_proc(void)
2205  {
2206    GC_warn_proc result;
2207  
2208    READER_LOCK();
2209    result = GC_current_warn_proc;
2210    READER_UNLOCK();
2211    return result;
2212  }
2213  
2214  /*
2215   * Print (or display) a message before abnormal exit (including abort).
2216   * Invoked from `ABORT(msg)` macro (where `msg` is non-`NULL`) and from
2217   * `EXIT()` macro (`msg` is `NULL` in that case).
2218   */
2219  STATIC void GC_CALLBACK
2220  GC_default_on_abort(const char *msg)
2221  {
2222  #if !defined(SMALL_CONFIG)
2223  #  ifndef DONT_USE_ATEXIT
2224    /* Disable at-exit garbage collection. */
2225    skip_gc_atexit = TRUE;
2226  #  endif
2227  
2228    if (msg != NULL) {
2229  #  ifdef MSGBOX_ON_ERROR
2230      GC_win32_MessageBoxA(msg, "Fatal error in GC", MB_ICONERROR | MB_OK);
2231      /* Also duplicate `msg` to the collector log file. */
2232  #  endif
2233  
2234  #  ifndef GC_ANDROID_LOG
2235      /*
2236       * Avoid calling `GC_err_printf()` here, as `GC_on_abort()` could
2237       * be called from it.  Note 1: this is not an atomic output.
2238       * Note 2: possible write errors are ignored.
2239       */
2240  #    if defined(GC_WIN32_THREADS) && defined(GC_ASSERTIONS) \
2241          && ((defined(MSWIN32) && !defined(CONSOLE_LOG)) || defined(MSWINCE))
2242      if (!GC_write_disabled)
2243  #    endif
2244      {
2245        if (WRITE(GC_stderr, msg, strlen(msg)) >= 0)
2246          (void)WRITE(GC_stderr, "\n", 1);
2247      }
2248  #  else
2249      __android_log_assert("*" /* `cond` */, GC_ANDROID_LOG_TAG, "%s\n", msg);
2250  #  endif
2251  #  if defined(HAIKU) && !defined(DONT_CALL_DEBUGGER)
2252      /*
2253       * This will cause the crash reason to appear in any debug reports
2254       * generated (by the default system application crash dialog).
2255       */
2256      debugger(msg);
2257  #  endif
2258    }
2259  
2260  #  if !defined(NO_DEBUGGING) && !defined(GC_ANDROID_LOG)
2261    if (GETENV("GC_LOOP_ON_ABORT") != NULL) {
2262      /*
2263       * In many cases it is easier to debug a running process.
2264       * It is arguably nicer to sleep, but that makes it harder to look
2265       * at the thread if the debugger does not know much about threads.
2266       */
2267      for (;;) {
2268        /* Empty. */
2269      }
2270    }
2271  #  endif
2272  #else
2273    UNUSED_ARG(msg);
2274  #endif
2275  }
2276  
2277  #ifndef SMALL_CONFIG
2278  GC_abort_func GC_on_abort = GC_default_on_abort;
2279  #endif
2280  
2281  GC_API void GC_CALL
2282  GC_set_abort_func(GC_abort_func fn)
2283  {
2284    GC_ASSERT(NONNULL_ARG_NOT_NULL(fn));
2285    LOCK();
2286  #ifndef SMALL_CONFIG
2287    GC_on_abort = fn;
2288  #else
2289    UNUSED_ARG(fn);
2290  #endif
2291    UNLOCK();
2292  }
2293  
2294  GC_API GC_abort_func GC_CALL
2295  GC_get_abort_func(void)
2296  {
2297    GC_abort_func fn;
2298  
2299    READER_LOCK();
2300  #ifndef SMALL_CONFIG
2301    fn = GC_on_abort;
2302    GC_ASSERT(fn != 0);
2303  #else
2304    fn = GC_default_on_abort;
2305  #endif
2306    READER_UNLOCK();
2307    return fn;
2308  }
2309  
2310  #if defined(NEED_SNPRINTF_SLDS) /* && GC_DISABLE_SNPRINTF */
2311  GC_INNER void
2312  GC_snprintf_s_ld_s(char *buf, size_t buf_sz, const char *prefix, long lv,
2313                     const char *suffix)
2314  {
2315    size_t len = strlen(prefix);
2316  
2317    GC_ASSERT(buf_sz > 0);
2318    /* Copy the prefix. */
2319    if (UNLIKELY(len >= buf_sz))
2320      len = buf_sz - 1;
2321    BCOPY(prefix, buf, len);
2322    buf += len;
2323    buf_sz -= len;
2324  
2325    /* Handle sign of the number. */
2326    if (lv >= 0) {
2327      lv = -lv;
2328    } else if (LIKELY(buf_sz > 1)) {
2329      *(buf++) = '-';
2330      buf_sz--;
2331    }
2332  
2333    /* Convert the decimal number to string.  (A trivial implementation.) */
2334    {
2335      char num_buf[20];
2336      size_t pos = sizeof(num_buf);
2337  
2338      do {
2339        long r = lv / 10;
2340  
2341        if (UNLIKELY(0 == pos))
2342          break; /*< overflow */
2343        num_buf[--pos] = (char)(r * 10 - lv + '0');
2344        lv = r;
2345      } while (lv < 0);
2346      len = sizeof(num_buf) - pos;
2347      if (UNLIKELY(len >= buf_sz))
2348        len = buf_sz - 1;
2349      BCOPY(&num_buf[pos], buf, len);
2350    }
2351    buf += len;
2352    buf_sz -= len;
2353  
2354    /* Copy the suffix (if any). */
2355    len = strlen(suffix);
2356    if (len > 0) {
2357      if (UNLIKELY(len >= buf_sz))
2358        len = buf_sz - 1;
2359      BCOPY(suffix, buf, len);
2360      buf += len;
2361    }
2362    *buf = '\0';
2363  }
2364  #endif /* NEED_SNPRINTF_SLDS */
2365  
2366  GC_API void GC_CALL
2367  GC_enable(void)
2368  {
2369    LOCK();
2370    /* Ensure no counter underflow. */
2371    GC_ASSERT(GC_dont_gc != 0);
2372    GC_dont_gc--;
2373    if (!GC_dont_gc && GC_heapsize > GC_heapsize_on_gc_disable)
2374      WARN("Heap grown by %" WARN_PRIuPTR " KiB while GC was disabled\n",
2375           (GC_heapsize - GC_heapsize_on_gc_disable) >> 10);
2376    UNLOCK();
2377  }
2378  
2379  GC_API void GC_CALL
2380  GC_disable(void)
2381  {
2382    LOCK();
2383    if (!GC_dont_gc)
2384      GC_heapsize_on_gc_disable = GC_heapsize;
2385    GC_dont_gc++;
2386    UNLOCK();
2387  }
2388  
2389  GC_API int GC_CALL
2390  GC_is_disabled(void)
2391  {
2392    return GC_dont_gc != 0;
2393  }
2394  
2395  /* Helper procedures for new kind creation. */
2396  
2397  GC_API void **GC_CALL
2398  GC_new_free_list_inner(void)
2399  {
2400    void *result;
2401  
2402    GC_ASSERT(I_HOLD_LOCK());
2403    result = GC_INTERNAL_MALLOC((MAXOBJGRANULES + 1) * sizeof(ptr_t), PTRFREE);
2404    if (NULL == result)
2405      ABORT("Failed to allocate free list for new kind");
2406    BZERO(result, (MAXOBJGRANULES + 1) * sizeof(ptr_t));
2407    return (void **)result;
2408  }
2409  
2410  GC_API void **GC_CALL
2411  GC_new_free_list(void)
2412  {
2413    void **result;
2414  
2415    LOCK();
2416    result = GC_new_free_list_inner();
2417    UNLOCK();
2418    return result;
2419  }
2420  
2421  GC_API unsigned GC_CALL
2422  GC_new_kind_inner(void **fl, GC_word descr, int adjust, int clear)
2423  {
2424    unsigned result = GC_n_kinds;
2425  
2426    GC_ASSERT(NONNULL_ARG_NOT_NULL(fl));
2427    GC_ASSERT(!adjust || 1 == adjust);
2428    /*
2429     * If an object is not needed to be cleared (when moved to the free list),
2430     * then its descriptor should be zero to denote a pointer-free object
2431     * (and, as a consequence, the size of the object should not be added to
2432     * the descriptor template).
2433     */
2434    GC_ASSERT(1 == clear || (0 == descr && !adjust && !clear));
2435    if (result < MAXOBJKINDS) {
2436      GC_ASSERT(result > 0);
2437      GC_n_kinds++;
2438      GC_obj_kinds[result].ok_freelist = fl;
2439      GC_obj_kinds[result].ok_reclaim_list = 0;
2440      GC_obj_kinds[result].ok_descriptor = descr;
2441      GC_obj_kinds[result].ok_relocate_descr = (GC_bool)adjust;
2442      GC_obj_kinds[result].ok_init = (GC_bool)clear;
2443  #ifdef ENABLE_DISCLAIM
2444      GC_obj_kinds[result].ok_mark_unconditionally = FALSE;
2445      GC_obj_kinds[result].ok_disclaim_proc = 0;
2446  #endif
2447    } else {
2448      ABORT("Too many kinds");
2449    }
2450    return result;
2451  }
2452  
2453  GC_API unsigned GC_CALL
2454  GC_new_kind(void **fl, GC_word descr, int adjust, int clear)
2455  {
2456    unsigned result;
2457  
2458    LOCK();
2459    result = GC_new_kind_inner(fl, descr, adjust, clear);
2460    UNLOCK();
2461    return result;
2462  }
2463  
2464  GC_API unsigned GC_CALL
2465  GC_new_proc_inner(GC_mark_proc proc)
2466  {
2467    unsigned result = GC_n_mark_procs;
2468  
2469    if (result < GC_MAX_MARK_PROCS) {
2470      GC_n_mark_procs++;
2471      GC_mark_procs[result] = proc;
2472    } else {
2473      ABORT("Too many mark procedures");
2474    }
2475    return result;
2476  }
2477  
2478  GC_API unsigned GC_CALL
2479  GC_new_proc(GC_mark_proc proc)
2480  {
2481    unsigned result;
2482  
2483    LOCK();
2484    result = GC_new_proc_inner(proc);
2485    UNLOCK();
2486    return result;
2487  }
2488  
2489  GC_API void *GC_CALL
2490  GC_call_with_alloc_lock(GC_fn_type fn, void *client_data)
2491  {
2492    void *result;
2493  
2494    LOCK();
2495    result = fn(client_data);
2496    UNLOCK();
2497    return result;
2498  }
2499  
2500  #ifdef THREADS
2501  GC_API void GC_CALL
2502  GC_alloc_lock(void)
2503  {
2504    LOCK();
2505  }
2506  
2507  GC_API void GC_CALL
2508  GC_alloc_unlock(void)
2509  {
2510    UNLOCK();
2511  }
2512  
2513  GC_API void *GC_CALL
2514  GC_call_with_reader_lock(GC_fn_type fn, void *client_data, int release)
2515  {
2516    void *result;
2517  
2518    READER_LOCK();
2519    result = fn(client_data);
2520  #  ifdef HAS_REAL_READER_LOCK
2521    if (release) {
2522      READER_UNLOCK_RELEASE();
2523  #    ifdef LINT2
2524      GC_noop1((unsigned)release);
2525  #    endif
2526      return result;
2527    }
2528  #  else
2529    UNUSED_ARG(release);
2530  #  endif
2531    READER_UNLOCK();
2532    return result;
2533  }
2534  #endif /* THREADS */
2535  
2536  GC_ATTR_NOINLINE
2537  GC_API void *GC_CALL
2538  GC_call_with_stack_base(GC_stack_base_func fn, void *arg)
2539  {
2540    struct GC_stack_base base;
2541    void *result;
2542  
2543    STORE_APPROX_SP_TO(*(volatile ptr_t *)&base.mem_base);
2544  #ifdef IA64
2545    base.reg_base = GC_save_regs_in_stack();
2546    /*
2547     * TODO: Unnecessarily flushes register stack, but that probably
2548     * does not hurt.
2549     */
2550  #elif defined(E2K)
2551    {
2552      unsigned long long sz_ull;
2553  
2554      GET_PROCEDURE_STACK_SIZE_INNER(&sz_ull);
2555      base.reg_base = NUMERIC_TO_VPTR(sz_ull);
2556    }
2557  #endif
2558    result = (*(GC_stack_base_func volatile *)&fn)(&base, arg);
2559    /*
2560     * Strongly discourage the compiler from treating the above as
2561     * a tail call.
2562     */
2563    GC_noop1(COVERT_DATAFLOW(ADDR(&base)));
2564    return result;
2565  }
2566  
2567  #ifndef THREADS
2568  
2569  GC_INNER ptr_t GC_blocked_sp = NULL;
2570  
2571  #  ifdef IA64
2572  STATIC ptr_t GC_blocked_register_sp = NULL;
2573  #  endif
2574  
2575  GC_INNER struct GC_traced_stack_sect_s *GC_traced_stack_sect = NULL;
2576  
2577  /* This is nearly the same as in `pthread_support.c` file. */
2578  GC_ATTR_NOINLINE
2579  GC_API void *GC_CALL
2580  GC_call_with_gc_active(GC_fn_type fn, void *client_data)
2581  {
2582    struct GC_traced_stack_sect_s stacksect;
2583    GC_ASSERT(GC_is_initialized);
2584  
2585    /*
2586     * Adjust our stack bottom pointer (this could happen if
2587     * `GC_get_main_stack_base()` is unimplemented or broken for
2588     * the platform).  Note: `stacksect` variable is reused here.
2589     */
2590    STORE_APPROX_SP_TO(*(volatile ptr_t *)&stacksect.saved_stack_ptr);
2591    if (HOTTER_THAN(GC_stackbottom, stacksect.saved_stack_ptr))
2592      GC_stackbottom = stacksect.saved_stack_ptr;
2593  
2594    if (GC_blocked_sp == NULL) {
2595      /* We are not inside `GC_do_blocking()` - do nothing more. */
2596      client_data = (*(GC_fn_type volatile *)&fn)(client_data);
2597      /* Prevent treating the above as a tail call. */
2598      GC_noop1(COVERT_DATAFLOW(ADDR(&stacksect)));
2599      return client_data; /*< result */
2600    }
2601  
2602    /* Setup new "stack section". */
2603    stacksect.saved_stack_ptr = GC_blocked_sp;
2604  #  ifdef IA64
2605    /* This is the same as in `GC_call_with_stack_base()`. */
2606    stacksect.backing_store_end = GC_save_regs_in_stack();
2607    /* Unnecessarily flushes register stack, but that probably does not hurt. */
2608    stacksect.saved_backing_store_ptr = GC_blocked_register_sp;
2609  #  endif
2610    stacksect.prev = GC_traced_stack_sect;
2611    GC_blocked_sp = NULL;
2612    GC_traced_stack_sect = &stacksect;
2613  
2614    client_data = (*(GC_fn_type volatile *)&fn)(client_data);
2615    GC_ASSERT(GC_blocked_sp == NULL);
2616    GC_ASSERT(GC_traced_stack_sect == &stacksect);
2617  
2618  #  if defined(CPPCHECK)
2619    GC_noop1_ptr(GC_traced_stack_sect);
2620    GC_noop1_ptr(GC_blocked_sp);
2621  #  endif
2622    /* Restore original "stack section". */
2623    GC_traced_stack_sect = stacksect.prev;
2624  #  ifdef IA64
2625    GC_blocked_register_sp = stacksect.saved_backing_store_ptr;
2626  #  endif
2627    GC_blocked_sp = stacksect.saved_stack_ptr;
2628  
2629    return client_data; /*< result */
2630  }
2631  
2632  /* This is nearly the same as in `pthread_support.c` file. */
2633  STATIC void
2634  GC_do_blocking_inner(ptr_t data, void *context)
2635  {
2636    UNUSED_ARG(context);
2637    GC_ASSERT(GC_is_initialized);
2638    GC_ASSERT(GC_blocked_sp == NULL);
2639  #  ifdef SPARC
2640    GC_blocked_sp = GC_save_regs_in_stack();
2641  #  else
2642    GC_blocked_sp = GC_approx_sp();
2643  #    ifdef IA64
2644    GC_blocked_register_sp = GC_save_regs_in_stack();
2645  #    endif
2646  #  endif
2647  
2648    ((struct blocking_data *)data)->client_data /*< result */
2649        = ((struct blocking_data *)data)
2650              ->fn(((struct blocking_data *)data)->client_data);
2651  
2652    GC_ASSERT(GC_blocked_sp != NULL);
2653  #  if defined(CPPCHECK)
2654    GC_noop1_ptr(GC_blocked_sp);
2655  #  endif
2656    GC_blocked_sp = NULL;
2657  }
2658  
2659  GC_API void GC_CALL
2660  GC_set_stackbottom(void *gc_thread_handle, const struct GC_stack_base *sb)
2661  {
2662    GC_ASSERT(sb->mem_base != NULL);
2663    GC_ASSERT(NULL == gc_thread_handle || &GC_stackbottom == gc_thread_handle);
2664    GC_ASSERT(NULL == GC_blocked_sp
2665              && NULL == GC_traced_stack_sect); /*< for now */
2666    UNUSED_ARG(gc_thread_handle);
2667  
2668    GC_stackbottom = (char *)sb->mem_base;
2669  #  ifdef IA64
2670    GC_register_stackbottom = (ptr_t)sb->reg_base;
2671  #  endif
2672  }
2673  
2674  GC_API void *GC_CALL
2675  GC_get_my_stackbottom(struct GC_stack_base *sb)
2676  {
2677    GC_ASSERT(GC_is_initialized);
2678    sb->mem_base = GC_stackbottom;
2679  #  ifdef IA64
2680    sb->reg_base = GC_register_stackbottom;
2681  #  elif defined(E2K)
2682    sb->reg_base = NULL;
2683  #  endif
2684    return &GC_stackbottom; /*< `gc_thread_handle` */
2685  }
2686  
2687  #endif /* !THREADS */
2688  
2689  GC_API void *GC_CALL
2690  GC_do_blocking(GC_fn_type fn, void *client_data)
2691  {
2692    struct blocking_data my_data;
2693  
2694    my_data.fn = fn;
2695    my_data.client_data = client_data;
2696    GC_with_callee_saves_pushed(GC_do_blocking_inner, (ptr_t)(&my_data));
2697    return my_data.client_data; /*< result */
2698  }
2699  
2700  #if !defined(NO_DEBUGGING)
2701  GC_API void GC_CALL
2702  GC_dump(void)
2703  {
2704    READER_LOCK();
2705    GC_dump_named(NULL);
2706    READER_UNLOCK();
2707  }
2708  
2709  GC_API void GC_CALL
2710  GC_dump_named(const char *name)
2711  {
2712  #  ifndef NO_CLOCK
2713    CLOCK_TYPE current_time;
2714  
2715    GET_TIME(current_time);
2716  #  endif
2717    if (name != NULL) {
2718      GC_printf("\n***GC Dump %s\n", name);
2719    } else {
2720      GC_printf("\n***GC Dump collection #%lu\n", (unsigned long)GC_gc_no);
2721    }
2722  #  ifndef NO_CLOCK
2723    /* Note that the time is wrapped in ~49 days if `sizeof(long) == 4`. */
2724    GC_printf("Time since GC init: %lu ms\n",
2725              MS_TIME_DIFF(current_time, GC_init_time));
2726  #  endif
2727  
2728    GC_printf("\n***Static roots:\n");
2729    GC_print_static_roots();
2730    GC_printf("\n***Heap sections:\n");
2731    GC_print_heap_sects();
2732    GC_printf("\n***Free blocks:\n");
2733    GC_print_hblkfreelist();
2734    GC_printf("\n***Blocks in use:\n");
2735    GC_print_block_list();
2736  #  ifndef GC_NO_FINALIZATION
2737    GC_dump_finalization();
2738  #  endif
2739  }
2740  #endif /* !NO_DEBUGGING */
2741  
2742  GC_API GC_word GC_CALL
2743  GC_get_memory_use(void)
2744  {
2745    word bytes;
2746  
2747    READER_LOCK();
2748    GC_ASSERT(GC_heapsize >= GC_large_free_bytes);
2749    bytes = GC_heapsize - GC_large_free_bytes;
2750    READER_UNLOCK();
2751    return bytes;
2752  }
2753  
2754  /* Getter functions for the public read-only variables. */
2755  
2756  GC_API GC_word GC_CALL
2757  GC_get_gc_no(void)
2758  {
2759    return GC_gc_no;
2760  }
2761  
2762  #ifndef PARALLEL_MARK
2763  GC_API void GC_CALL
2764  GC_set_markers_count(unsigned markers)
2765  {
2766    UNUSED_ARG(markers);
2767  }
2768  #endif
2769  
2770  GC_API int GC_CALL
2771  GC_get_parallel(void)
2772  {
2773  #ifdef THREADS
2774    return GC_parallel;
2775  #else
2776    return 0;
2777  #endif
2778  }
2779  
2780  /*
2781   * Setter and getter functions for the public R/W function variables.
2782   * These functions are synchronized (like `GC_set_warn_proc()` and
2783   * `GC_get_warn_proc()`).
2784   */
2785  
2786  GC_API void GC_CALL
2787  GC_set_oom_fn(GC_oom_func fn)
2788  {
2789    GC_ASSERT(NONNULL_ARG_NOT_NULL(fn));
2790    LOCK();
2791    GC_oom_fn = fn;
2792    UNLOCK();
2793  }
2794  
2795  GC_API GC_oom_func GC_CALL
2796  GC_get_oom_fn(void)
2797  {
2798    GC_oom_func fn;
2799  
2800    READER_LOCK();
2801    fn = GC_oom_fn;
2802    READER_UNLOCK();
2803    return fn;
2804  }
2805  
2806  GC_API void GC_CALL
2807  GC_set_on_heap_resize(GC_on_heap_resize_proc fn)
2808  {
2809    /* `fn` may be 0 (means no event notifier). */
2810    LOCK();
2811    GC_on_heap_resize = fn;
2812    UNLOCK();
2813  }
2814  
2815  GC_API GC_on_heap_resize_proc GC_CALL
2816  GC_get_on_heap_resize(void)
2817  {
2818    GC_on_heap_resize_proc fn;
2819  
2820    READER_LOCK();
2821    fn = GC_on_heap_resize;
2822    READER_UNLOCK();
2823    return fn;
2824  }
2825  
2826  GC_API void GC_CALL
2827  GC_set_finalizer_notifier(GC_finalizer_notifier_proc fn)
2828  {
2829    /* `fn` may be 0 (means no finalizer notifier). */
2830    LOCK();
2831    GC_finalizer_notifier = fn;
2832    UNLOCK();
2833  }
2834  
2835  GC_API GC_finalizer_notifier_proc GC_CALL
2836  GC_get_finalizer_notifier(void)
2837  {
2838    GC_finalizer_notifier_proc fn;
2839  
2840    READER_LOCK();
2841    fn = GC_finalizer_notifier;
2842    READER_UNLOCK();
2843    return fn;
2844  }
2845  
2846  /*
2847   * Setter and getter functions for the public numeric R/W variables.
2848   * It is safe to call these functions even before `GC_INIT()`.
2849   * These functions are unsynchronized and, if called after `GC_INIT()`,
2850   * should be typically invoked inside the context of
2851   * `GC_call_with_alloc_lock()` (or `GC_call_with_reader_lock()` in case
2852   * of the getters) to prevent data race (unless it is guaranteed the
2853   * collector is not multi-threaded at that execution point).
2854   */
2855  
2856  GC_API void GC_CALL
2857  GC_set_find_leak(int value)
2858  {
2859    /* `value` is of boolean type. */
2860  #ifdef NO_FIND_LEAK
2861    if (value)
2862      ABORT("Find-leak mode is unsupported");
2863  #else
2864    GC_find_leak = value;
2865  #endif
2866  }
2867  
2868  GC_API int GC_CALL
2869  GC_get_find_leak(void)
2870  {
2871    return GC_find_leak_inner;
2872  }
2873  
2874  GC_API void GC_CALL
2875  GC_set_all_interior_pointers(int value)
2876  {
2877    GC_all_interior_pointers = value ? 1 : 0;
2878    if (GC_is_initialized) {
2879      /*
2880       * It is not recommended to change `GC_all_interior_pointers` value
2881       * after the collector is initialized but it seems it could work
2882       * correctly even after switching the mode.
2883       */
2884      LOCK();
2885      /* Note: this resets manual offsets as well. */
2886      GC_initialize_offsets();
2887  #ifndef NO_BLACK_LISTING
2888      if (!GC_all_interior_pointers)
2889        GC_bl_init_no_interiors();
2890  #endif
2891      UNLOCK();
2892    }
2893  }
2894  
2895  GC_API int GC_CALL
2896  GC_get_all_interior_pointers(void)
2897  {
2898    return GC_all_interior_pointers;
2899  }
2900  
2901  GC_API void GC_CALL
2902  GC_set_finalize_on_demand(int value)
2903  {
2904    /* Note: -1 was used to retrieve old value in gc-7.2. */
2905    GC_ASSERT(value != -1);
2906    /* `value` is of boolean type. */
2907    GC_finalize_on_demand = value;
2908  }
2909  
2910  GC_API int GC_CALL
2911  GC_get_finalize_on_demand(void)
2912  {
2913    return GC_finalize_on_demand;
2914  }
2915  
2916  GC_API void GC_CALL
2917  GC_set_java_finalization(int value)
2918  {
2919    /* Note: -1 was used to retrieve old value in gc-7.2. */
2920    GC_ASSERT(value != -1);
2921    /* `value` is of boolean type. */
2922    GC_java_finalization = value;
2923  }
2924  
2925  GC_API int GC_CALL
2926  GC_get_java_finalization(void)
2927  {
2928    return GC_java_finalization;
2929  }
2930  
2931  GC_API void GC_CALL
2932  GC_set_dont_expand(int value)
2933  {
2934    /* Note: -1 was used to retrieve old value in gc-7.2. */
2935    GC_ASSERT(value != -1);
2936    /* `value` is of boolean type. */
2937    GC_dont_expand = value;
2938  }
2939  
2940  GC_API int GC_CALL
2941  GC_get_dont_expand(void)
2942  {
2943    return GC_dont_expand;
2944  }
2945  
2946  GC_API void GC_CALL
2947  GC_set_no_dls(int value)
2948  {
2949    /* Note: -1 was used to retrieve old value in gc-7.2. */
2950    GC_ASSERT(value != -1);
2951    /* `value` is of boolean type. */
2952    GC_no_dls = value;
2953  }
2954  
2955  GC_API int GC_CALL
2956  GC_get_no_dls(void)
2957  {
2958    return GC_no_dls;
2959  }
2960  
2961  GC_API void GC_CALL
2962  GC_set_non_gc_bytes(GC_word value)
2963  {
2964    GC_non_gc_bytes = value;
2965  }
2966  
2967  GC_API GC_word GC_CALL
2968  GC_get_non_gc_bytes(void)
2969  {
2970    return GC_non_gc_bytes;
2971  }
2972  
2973  GC_API void GC_CALL
2974  GC_set_free_space_divisor(GC_word value)
2975  {
2976    GC_ASSERT(value > 0);
2977    GC_free_space_divisor = value;
2978  }
2979  
2980  GC_API GC_word GC_CALL
2981  GC_get_free_space_divisor(void)
2982  {
2983    return GC_free_space_divisor;
2984  }
2985  
2986  GC_API void GC_CALL
2987  GC_set_max_retries(GC_word value)
2988  {
2989    /* Note: -1 was used to retrieve old value in gc-7.2. */
2990    GC_ASSERT((GC_signed_word)value != -1);
2991    GC_max_retries = value;
2992  }
2993  
2994  GC_API GC_word GC_CALL
2995  GC_get_max_retries(void)
2996  {
2997    return GC_max_retries;
2998  }
2999  
3000  GC_API void GC_CALL
3001  GC_set_dont_precollect(int value)
3002  {
3003    /* Note: -1 was used to retrieve old value in gc-7.2. */
3004    GC_ASSERT(value != -1);
3005    /* `value` is of boolean type. */
3006    GC_dont_precollect = value;
3007  }
3008  
3009  GC_API int GC_CALL
3010  GC_get_dont_precollect(void)
3011  {
3012    return GC_dont_precollect;
3013  }
3014  
3015  GC_API void GC_CALL
3016  GC_set_full_freq(int value)
3017  {
3018    GC_ASSERT(value >= 0);
3019    GC_full_freq = value;
3020  }
3021  
3022  GC_API int GC_CALL
3023  GC_get_full_freq(void)
3024  {
3025    return GC_full_freq;
3026  }
3027  
3028  GC_API void GC_CALL
3029  GC_set_time_limit(unsigned long value)
3030  {
3031    /* Note: -1 was used to retrieve old value in gc-7.2. */
3032    GC_ASSERT((long)value != -1L);
3033    GC_time_limit = value;
3034  }
3035  
3036  GC_API unsigned long GC_CALL
3037  GC_get_time_limit(void)
3038  {
3039    return GC_time_limit;
3040  }
3041  
3042  GC_API void GC_CALL
3043  GC_set_force_unmap_on_gcollect(int value)
3044  {
3045    GC_force_unmap_on_gcollect = (GC_bool)value;
3046  }
3047  
3048  GC_API int GC_CALL
3049  GC_get_force_unmap_on_gcollect(void)
3050  {
3051    return (int)GC_force_unmap_on_gcollect;
3052  }
3053  
3054  GC_API GC_OOM_ABORT_THROW_ATTRIBUTE void GC_CALL
3055  GC_abort_on_oom(void)
3056  {
3057    GC_err_printf("Insufficient memory for the allocation\n");
3058    EXIT();
3059  }
3060  
3061  GC_API size_t GC_CALL
3062  GC_get_hblk_size(void)
3063  {
3064    return (size_t)HBLKSIZE;
3065  }
3066