text: // This file is a part of Julia. License is MIT: https://julialang.org/license
  
  /*
    saving and restoring system images
  */
  #include <stdlib.h>
  #include <string.h>
  
  #include "julia.h"
  #include "julia_internal.h"
  #include "builtin_proto.h"
  
  #ifndef _OS_WINDOWS_
  #include <dlfcn.h>
  #endif
  
  #ifndef _COMPILER_MICROSOFT_
  #include "valgrind.h"
  #else
  #define RUNNING_ON_VALGRIND 0
  #endif
  #include "julia_assert.h"
  
  #ifdef __cplusplus
  extern "C" {
  #endif
  
  // TODO: put WeakRefs on the weak_refs list during deserialization
  // TODO: handle finalizers
  
  // hash of definitions for predefined tagged object
  static htable_t ser_tag;
  // array of definitions for the predefined tagged object types
  // (reverse of ser_tag)
  static jl_value_t *deser_tag[256];
  // hash of some common symbols, encoded as CommonSym_tag plus 1 byte
  static htable_t common_symbol_tag;
  static jl_value_t *deser_symbols[256];
  
  // table of all objects that have been deserialized, indexed by pos
  // (the order in the serializer stream) in MODE_MODULE, the low
  // bit is reserved for flagging certain entries and pos is
  // left shift by 1
  // (not used in MODE_IR)
  static htable_t backref_table;
  static int backref_table_numel;
  static arraylist_t backref_list;
  
  // list of (jl_value_t **loc, size_t pos) entries
  // for anything that was flagged by the deserializer for later
  // type-rewriting of some sort
  // (not used in MODE_IR)
  static arraylist_t flagref_list;
  
  // list of (size_t pos, (void *f)(jl_value_t*)) entries
  // for the serializer to mark values in need of rework by function f
  // during deserialization later
  // (not used in MODE_IR)
  static arraylist_t reinit_list;
  
  // list of stuff that is being serialized
  // (only used by the incremental serializer in MODE_MODULE)
  // This is not quite globally rooted, but we take care to only
  // ever assigned rooted values here.
  static jl_array_t *serializer_worklist JL_GLOBALLY_ROOTED;
  
  // inverse of backedges tree
  // (only used by the incremental serializer in MODE_MODULE)
  htable_t edges_map;
  
  #define TAG_SYMBOL              2
  #define TAG_SSAVALUE            3
  #define TAG_DATATYPE            4
  #define TAG_SLOTNUMBER          5
  #define TAG_SVEC                6
  #define TAG_ARRAY               7
  #define TAG_NULL                8
  #define TAG_EXPR                9
  #define TAG_PHINODE            10
  #define TAG_PHICNODE           11
  #define TAG_LONG_SYMBOL        12
  #define TAG_LONG_SVEC          13
  #define TAG_LONG_EXPR          14
  #define TAG_LONG_PHINODE       15
  #define TAG_LONG_PHICNODE      16
  #define TAG_METHODROOT         17
  #define TAG_STRING             18
  #define TAG_SHORT_INT64        19
  #define TAG_SHORT_GENERAL      20
  #define TAG_TYPEMAP_ENTRY      21
  #define TAG_ARRAY1D            22
  #define TAG_SINGLETON          23
  #define TAG_MODULE             24
  #define TAG_TVAR               25
  #define TAG_METHOD_INSTANCE    26
  #define TAG_METHOD             27
  #define TAG_CODE_INSTANCE      28
  #define TAG_COMMONSYM          29
  #define TAG_NEARBYGLOBAL       30
  #define TAG_GLOBALREF          31
  #define TAG_CORE               32
  #define TAG_BASE               33
  #define TAG_BITYPENAME         34
  #define TAG_NEARBYMODULE       35
  #define TAG_INT32              36
  #define TAG_INT64              37
  #define TAG_UINT8              38
  #define TAG_VECTORTY           39
  #define TAG_PTRTY              40
  #define TAG_LONG_SSAVALUE      41
  #define TAG_LONG_METHODROOT    42
  #define TAG_SHORTER_INT64      43
  #define TAG_SHORT_INT32        44
  #define TAG_CALL1              45
  #define TAG_CALL2              46
  #define TAG_LINEINFO           47
  #define TAG_SHORT_BACKREF      48
  #define TAG_BACKREF            49
  #define TAG_UNIONALL           50
  #define TAG_GOTONODE           51
  #define TAG_QUOTENODE          52
  #define TAG_GENERAL            53
  
  #define LAST_TAG 53
  
  typedef enum _DUMP_MODES {
      // not in the serializer at all, or
      // something is seriously wrong
      MODE_INVALID = 0,
  
      // compressing / decompressing a CodeInfo
      MODE_IR,
  
      // jl_restore_new_module
      // restoring a single module from disk for integration
      // into the currently running system image / environment
      MODE_MODULE
  } DUMP_MODES;
  
  typedef struct {
      ios_t *s;
      DUMP_MODES mode;
      // method we're compressing for in MODE_IR
      jl_method_t *method;
      jl_ptls_t ptls;
      jl_array_t *loaded_modules_array;
  } jl_serializer_state;
  
  static jl_value_t *jl_idtable_type = NULL;
  static jl_typename_t *jl_idtable_typename = NULL;
  static jl_value_t *jl_bigint_type = NULL;
  static int gmp_limb_size = 0;
  static arraylist_t builtin_typenames;
  
  #define write_uint8(s, n) ios_putc((n), (s))
  #define read_uint8(s) ((uint8_t)ios_getc(s))
  #define write_int8(s, n) write_uint8(s, n)
  #define read_int8(s) read_uint8(s)
  
  /* read and write in host byte order */
  
  static void write_int32(ios_t *s, int32_t i) JL_NOTSAFEPOINT
  {
      ios_write(s, (char*)&i, 4);
  }
  
  static int32_t read_int32(ios_t *s) JL_NOTSAFEPOINT
  {
      int32_t x = 0;
      ios_read(s, (char*)&x, 4);
      return x;
  }
  
  static void write_uint64(ios_t *s, uint64_t i) JL_NOTSAFEPOINT
  {
      ios_write(s, (char*)&i, 8);
  }
  
  static uint64_t read_uint64(ios_t *s) JL_NOTSAFEPOINT
  {
      uint64_t x = 0;
      ios_read(s, (char*)&x, 8);
      return x;
  }
  
  static void write_int64(ios_t *s, int64_t i) JL_NOTSAFEPOINT
  {
      ios_write(s, (char*)&i, 8);
  }
  
  static void write_uint16(ios_t *s, uint16_t i) JL_NOTSAFEPOINT
  {
      ios_write(s, (char*)&i, 2);
  }
  
  static uint16_t read_uint16(ios_t *s) JL_NOTSAFEPOINT
  {
      int16_t x = 0;
      ios_read(s, (char*)&x, 2);
      return x;
  }
  
  static void write_float64(ios_t *s, double x) JL_NOTSAFEPOINT
  {
      write_uint64(s, *((uint64_t*)&x));
  }
  
  //#ifdef _P64
  //#define write_ulong(s, x) (write_uint64((s), (x)))
  //#define read_ulong(s, x) (read_uint64((s), (x)))
  //#else
  //#define write_ulong(s, x) (write_uint32((s), (x)))
  //#define read_ulong(s, x) (read_uint32((s), (x)))
  //#endif
  
  // --- serialize ---
  
  #define jl_serialize_value(s, v) jl_serialize_value_((s), (jl_value_t*)(v), 0)
  static void jl_serialize_value_(jl_serializer_state *s, jl_value_t *v, int as_literal) JL_GC_DISABLED;
  static jl_value_t *jl_deserialize_value(jl_serializer_state *s, jl_value_t **loc) JL_GC_DISABLED;
  
  static int module_in_worklist(jl_module_t *mod) JL_NOTSAFEPOINT
  {
      int i, l = jl_array_len(serializer_worklist);
      for (i = 0; i < l; i++) {
          jl_module_t *workmod = (jl_module_t*)jl_array_ptr_ref(serializer_worklist, i);
          if (jl_is_module(workmod) && jl_is_submodule(mod, workmod))
              return 1;
      }
      return 0;
  }
  
  // compute whether a type references something internal to worklist
  // and thus could not have existed before deserialize
  // and thus does not need delayed unique-ing
  static int type_in_worklist(jl_datatype_t *dt) JL_NOTSAFEPOINT
  {
      if (module_in_worklist(dt->name->module))
          return 1;
      int i, l = jl_svec_len(dt->parameters);
      for (i = 0; i < l; i++) {
          jl_value_t *p = jl_unwrap_unionall(jl_tparam(dt, i));
          if (type_in_worklist((jl_datatype_t*)(jl_is_datatype(p) ? p : jl_typeof(p))))
              return 1;
      }
      return 0;
  }
  
  static int type_recursively_external(jl_datatype_t *dt);
  
  static int type_parameter_recursively_external(jl_value_t *p0) JL_NOTSAFEPOINT
  {
      jl_datatype_t *p = (jl_datatype_t*)p0;
      while (jl_is_unionall(p)) {
          if (!type_parameter_recursively_external(((jl_unionall_t*)p)->var->lb))
              return 0;
          if (!type_parameter_recursively_external(((jl_unionall_t*)p)->var->ub))
              return 0;
          p = (jl_datatype_t*)((jl_unionall_t*)p)->body;
      }
      if (!jl_is_datatype(p) || p->uid == 0)
          return 0;
      if (module_in_worklist(p->name->module))
          return 0;
      if (p->name->wrapper != (jl_value_t*)p0) {
          if (!type_recursively_external(p))
              return 0;
      }
      return 1;
  }
  
  // returns true if all of the parameters are tag 6 or 7
  static int type_recursively_external(jl_datatype_t *dt) JL_NOTSAFEPOINT
  {
      if (dt->uid == 0)
          return 0;
      if (jl_svec_len(dt->parameters) == 0)
          return 1;
  
      int i, l = jl_svec_len(dt->parameters);
      for (i = 0; i < l; i++) {
          if (!type_parameter_recursively_external(jl_tparam(dt, i)))
              return 0;
      }
      return 1;
  }
  
  
  static void jl_serialize_datatype(jl_serializer_state *s, jl_datatype_t *dt) JL_GC_DISABLED
  {
      int tag = 0;
      int internal = module_in_worklist(dt->name->module);
      if (!internal && jl_unwrap_unionall(dt->name->wrapper) == (jl_value_t*)dt) {
          tag = 6; // external primary type
      }
      else if (dt->uid == 0) {
          tag = 0; // normal struct
      }
      else if (internal) {
          if (jl_unwrap_unionall(dt->name->wrapper) == (jl_value_t*)dt) // comes up often since functions create types
              tag = 5; // internal, and not in the typename cache (just needs uid reassigned)
          else
              tag = 10; // anything else that's internal (just needs uid reassigned and possibly recaching)
      }
      else if (type_recursively_external(dt)) {
          tag = 7; // external type that can be immediately recreated (with apply_type)
      }
      else if (type_in_worklist(dt)) {
          tag = 10; // external, but definitely new (still needs uid and caching, but not full unique-ing)
      }
      else {
          // this'll need a uid and unique-ing later
          // flag this in the backref table as special
          uintptr_t *bp = (uintptr_t*)ptrhash_bp(&backref_table, dt);
          assert(*bp != (uintptr_t)HT_NOTFOUND);
          *bp |= 1;
          tag = 10;
      }
  
      if (strncmp(jl_symbol_name(dt->name->name), "#kw#", 4) == 0 && !internal && tag != 0) {
          /* XXX: yuck, this is horrible, but the auto-generated kw types from the serializer isn't a real type, so we *must* be very careful */
          assert(tag == 6); // other struct types should never exist
          tag = 9;
          if (jl_type_type_mt->kwsorter != NULL && dt == (jl_datatype_t*)jl_typeof(jl_type_type_mt->kwsorter)) {
              dt = jl_datatype_type; // any representative member with this MethodTable
          }
          else if (jl_nonfunction_mt->kwsorter != NULL && dt == (jl_datatype_t*)jl_typeof(jl_nonfunction_mt->kwsorter)) {
              dt = jl_symbol_type; // any representative member with this MethodTable
          }
          else {
              // search for the representative member of this MethodTable
              jl_methtable_t *mt = dt->name->mt;
              size_t l = strlen(jl_symbol_name(mt->name));
              char *prefixed;
              prefixed = (char*)malloc(l + 2);
              prefixed[0] = '#';
              strcpy(&prefixed[1], jl_symbol_name(mt->name));
              jl_sym_t *tname = jl_symbol(prefixed);
              free(prefixed);
              jl_value_t *primarydt = jl_get_global(mt->module, tname);
              if (!primarydt)
                  primarydt = jl_get_global(mt->module, mt->name);
              primarydt = jl_unwrap_unionall(primarydt);
              assert(jl_is_datatype(primarydt));
              assert(primarydt == (jl_value_t*)jl_any_type || jl_typeof(((jl_datatype_t*)primarydt)->name->mt->kwsorter) == (jl_value_t*)dt);
              dt = (jl_datatype_t*)primarydt;
          }
      }
  
      write_uint8(s->s, TAG_DATATYPE);
      write_uint8(s->s, tag);
      if (tag == 6 || tag == 7) {
          // for tag==6, copy its typevars in case there are references to them elsewhere
          jl_serialize_value(s, dt->name);
          jl_serialize_value(s, dt->parameters);
          return;
      }
      if (tag == 9) {
          jl_serialize_value(s, dt);
          return;
      }
  
      write_int32(s->s, dt->size);
      int has_instance = (dt->instance != NULL);
      int has_layout = (dt->layout != NULL);
      write_uint8(s->s, dt->abstract | (dt->mutabl << 1) | (has_layout << 2) | (has_instance << 3));
      write_uint8(s->s, dt->hasfreetypevars
              | (dt->isconcretetype << 1)
              | (dt->isdispatchtuple << 2)
              | (dt->isbitstype << 3)
              | (dt->zeroinit << 4)
              | (dt->isinlinealloc << 5)
              | (dt->has_concrete_subtype << 6));
      if (!dt->abstract) {
          write_uint16(s->s, dt->ninitialized);
      }
  
      if (has_layout) {
          uint8_t layout = 0;
          if (dt->layout == ((jl_datatype_t*)jl_unwrap_unionall((jl_value_t*)jl_array_type))->layout) {
              layout = 1;
          }
          else if (dt->layout == jl_void_type->layout) {
              layout = 2;
          }
          else if (dt->layout == ((jl_datatype_t*)jl_unwrap_unionall((jl_value_t*)jl_pointer_type))->layout) {
              layout = 3;
          }
          write_uint8(s->s, layout);
          if (layout == 0) {
              uint32_t nf = dt->layout->nfields;
              write_int32(s->s, nf);
              uint32_t alignment = ((uint32_t*)dt->layout)[1];
              write_int32(s->s, alignment);
              if (dt->layout->npointers && nf)
                  write_int32(s->s, ((uint32_t*)dt->layout)[-1]);
              size_t fieldsize = jl_fielddesc_size(dt->layout->fielddesc_type);
              ios_write(s->s, (char*)(&dt->layout[1]), nf * fieldsize);
          }
      }
  
      if (has_instance)
          jl_serialize_value(s, dt->instance);
      jl_serialize_value(s, dt->name);
      jl_serialize_value(s, dt->names);
      jl_serialize_value(s, dt->parameters);
      jl_serialize_value(s, dt->super);
      jl_serialize_value(s, dt->types);
  }
  
  static void jl_serialize_module(jl_serializer_state *s, jl_module_t *m)
  {
      write_uint8(s->s, TAG_MODULE);
      jl_serialize_value(s, m->name);
      size_t i;
      if (!module_in_worklist(m)) {
          if (m == m->parent) {
              // top-level module
              write_int8(s->s, 2);
              int j = 0;
              for (i = 0; i < jl_array_len(s->loaded_modules_array); i++) {
                  jl_module_t *mi = (jl_module_t*)jl_array_ptr_ref(s->loaded_modules_array, i);
                  if (!module_in_worklist(mi)) {
                      if (m == mi) {
                          write_int32(s->s, j);
                          return;
                      }
                      j++;
                  }
              }
              assert(0 && "top level module not found in modules array");
          }
          else {
              write_int8(s->s, 1);
              jl_serialize_value(s, m->parent);
          }
          return;
      }
      write_int8(s->s, 0);
      jl_serialize_value(s, m->parent);
      void **table = m->bindings.table;
      for(i=1; i < m->bindings.size; i+=2) {
          if (table[i] != HT_NOTFOUND) {
              jl_binding_t *b = (jl_binding_t*)table[i];
              if (b->owner == m || m != jl_main_module) {
                  jl_serialize_value(s, b->name);
                  jl_serialize_value(s, b->value);
                  jl_serialize_value(s, b->globalref);
                  jl_serialize_value(s, b->owner);
                  write_int8(s->s, (b->deprecated<<3) | (b->constp<<2) | (b->exportp<<1) | (b->imported));
              }
          }
      }
      jl_serialize_value(s, NULL);
      if (m == jl_main_module) {
          write_int32(s->s, 1);
          jl_serialize_value(s, (jl_value_t*)jl_core_module);
      }
      else {
          write_int32(s->s, m->usings.len);
          for(i=0; i < m->usings.len; i++) {
              jl_serialize_value(s, (jl_value_t*)m->usings.items[i]);
          }
      }
      write_uint8(s->s, m->istopmod);
      write_uint64(s->s, m->uuid.hi);
      write_uint64(s->s, m->uuid.lo);
      write_uint64(s->s, m->build_id);
      write_int32(s->s, m->counter);
      write_int32(s->s, m->nospecialize);
  }
  
  static int is_ast_node(jl_value_t *v)
  {
      // TODO: this accidentally copies QuoteNode(Expr(...)) and QuoteNode(svec(...))
      return jl_is_slot(v) || jl_is_ssavalue(v) ||
          jl_is_uniontype(v) || jl_is_expr(v) || jl_is_newvarnode(v) ||
          jl_is_svec(v) || jl_is_tuple(v) || ((jl_datatype_t*)jl_typeof(v))->instance ||
          jl_is_int32(v) || jl_is_int64(v) || jl_is_bool(v) || jl_is_uint8(v) ||
          jl_is_quotenode(v) || jl_is_gotonode(v) || jl_is_linenode(v) || jl_is_globalref(v) ||
          jl_is_phinode(v) || jl_is_phicnode(v) || jl_is_upsilonnode(v) || jl_is_pinode(v) ||
          jl_typeis(v, jl_lineinfonode_type);
  }
  
  static int literal_val_id(jl_serializer_state *s, jl_value_t *v) JL_GC_DISABLED
  {
      jl_array_t *rs = s->method->roots;
      int i, l = jl_array_len(rs);
      if (jl_is_symbol(v) || jl_is_concrete_type(v)) {
          for (i = 0; i < l; i++) {
              if (jl_array_ptr_ref(rs, i) == v)
                  return i;
          }
      }
      else {
          for (i = 0; i < l; i++) {
              if (jl_egal(jl_array_ptr_ref(rs, i), v))
                  return i;
          }
      }
      jl_array_ptr_1d_push(rs, v);
      return jl_array_len(rs) - 1;
  }
  
  static void jl_serialize_value_(jl_serializer_state *s, jl_value_t *v, int as_literal) JL_GC_DISABLED
  {
      if (v == NULL) {
          write_uint8(s->s, TAG_NULL);
          return;
      }
  
      void *tag = ptrhash_get(&ser_tag, v);
      if (tag != HT_NOTFOUND) {
          uint8_t t8 = (intptr_t)tag;
          if (t8 <= LAST_TAG)
              write_uint8(s->s, 0);
          write_uint8(s->s, t8);
          return;
      }
      if (jl_is_symbol(v)) {
          void *idx = ptrhash_get(&common_symbol_tag, v);
          if (idx != HT_NOTFOUND) {
              write_uint8(s->s, TAG_COMMONSYM);
              write_uint8(s->s, (uint8_t)(size_t)idx);
              return;
          }
      }
      else if (v == (jl_value_t*)jl_core_module) {
          write_uint8(s->s, TAG_CORE);
          return;
      }
      else if (v == (jl_value_t*)jl_base_module) {
          write_uint8(s->s, TAG_BASE);
          return;
      }
  
      if (s->mode == MODE_IR) {
          if (v == (jl_value_t*)s->method->module) {
              write_uint8(s->s, TAG_NEARBYMODULE);
              return;
          }
          else if (jl_is_datatype(v) && ((jl_datatype_t*)v)->name == jl_array_typename &&
                   jl_is_long(jl_tparam1(v)) && jl_unbox_long(jl_tparam1(v)) == 1 &&
                   !((jl_datatype_t*)v)->hasfreetypevars) {
              write_uint8(s->s, TAG_VECTORTY);
              jl_serialize_value(s, jl_tparam0(v));
              return;
          }
          else if (jl_is_datatype(v) && ((jl_datatype_t*)v)->name == jl_pointer_typename &&
                   !((jl_datatype_t*)v)->hasfreetypevars) {
              write_uint8(s->s, TAG_PTRTY);
              jl_serialize_value(s, jl_tparam0(v));
              return;
          }
          else if (!as_literal && !is_ast_node(v)) {
              int id = literal_val_id(s, v);
              assert(id >= 0);
              if (id < 256) {
                  write_uint8(s->s, TAG_METHODROOT);
                  write_uint8(s->s, id);
              }
              else {
                  assert(id <= UINT16_MAX);
                  write_uint8(s->s, TAG_LONG_METHODROOT);
                  write_uint16(s->s, id);
              }
              return;
          }
      }
      else if (jl_typeis(v, jl_string_type) && jl_string_len(v) == 0) {
          jl_serialize_value(s, jl_an_empty_string);
          return;
      }
      else if (!jl_is_uint8(v)) {
          void **bp = ptrhash_bp(&backref_table, v);
          if (*bp != HT_NOTFOUND) {
              uintptr_t pos = (char*)*bp - (char*)HT_NOTFOUND - 1;
              if (pos < 65536) {
                  write_uint8(s->s, TAG_SHORT_BACKREF);
                  write_uint16(s->s, pos);
              }
              else {
                  write_uint8(s->s, TAG_BACKREF);
                  write_int32(s->s, pos);
              }
              return;
          }
          intptr_t pos = backref_table_numel++;
          if (((jl_datatype_t*)(jl_typeof(v)))->name == jl_idtable_typename) {
              // will need to rehash this, later (after types are fully constructed)
              arraylist_push(&reinit_list, (void*)pos);
              arraylist_push(&reinit_list, (void*)1);
          }
          if (jl_is_module(v)) {
              jl_module_t *m = (jl_module_t*)v;
              if (module_in_worklist(m) && !module_in_worklist(m->parent)) {
                  // will need to reinsert this into parent bindings, later (in case of any errors during reinsert)
                  arraylist_push(&reinit_list, (void*)pos);
                  arraylist_push(&reinit_list, (void*)2);
              }
          }
          // TypeMapLevels need to be rehashed
          if (jl_is_mtable(v)) {
              arraylist_push(&reinit_list, (void*)pos);
              arraylist_push(&reinit_list, (void*)3);
          }
          if (jl_is_method(v) && jl_typeof(((jl_method_t*)v)->specializations) == (jl_value_t*)jl_typemap_level_type) {
              arraylist_push(&reinit_list, (void*)pos);
              arraylist_push(&reinit_list, (void*)4);
          }
          pos <<= 1;
          ptrhash_put(&backref_table, v, (char*)HT_NOTFOUND + pos + 1);
      }
  
      size_t i;
      if (jl_is_svec(v)) {
          size_t l = jl_svec_len(v);
          if (l <= 255) {
              write_uint8(s->s, TAG_SVEC);
              write_uint8(s->s, (uint8_t)l);
          }
          else {
              write_uint8(s->s, TAG_LONG_SVEC);
              write_int32(s->s, l);
          }
          for (i = 0; i < l; i++) {
              jl_serialize_value(s, jl_svecref(v, i));
          }
      }
      else if (jl_is_symbol(v)) {
          size_t l = strlen(jl_symbol_name((jl_sym_t*)v));
          if (l <= 255) {
              write_uint8(s->s, TAG_SYMBOL);
              write_uint8(s->s, (uint8_t)l);
          }
          else {
              write_uint8(s->s, TAG_LONG_SYMBOL);
              write_int32(s->s, l);
          }
          ios_write(s->s, jl_symbol_name((jl_sym_t*)v), l);
      }
      else if (jl_is_globalref(v)) {
          if (s->mode == MODE_IR && jl_globalref_mod(v) == s->method->module) {
              write_uint8(s->s, TAG_NEARBYGLOBAL);
              jl_serialize_value(s, jl_globalref_name(v));
          }
          else {
              write_uint8(s->s, TAG_GLOBALREF);
              jl_serialize_value(s, jl_globalref_mod(v));
              jl_serialize_value(s, jl_globalref_name(v));
          }
      }
      else if (jl_is_ssavalue(v) && ((jl_ssavalue_t*)v)->id < 256 && ((jl_ssavalue_t*)v)->id >= 0) {
          write_uint8(s->s, TAG_SSAVALUE);
          write_uint8(s->s, ((jl_ssavalue_t*)v)->id);
      }
      else if (jl_is_ssavalue(v) && ((jl_ssavalue_t*)v)->id <= UINT16_MAX && ((jl_ssavalue_t*)v)->id >= 0) {
          write_uint8(s->s, TAG_LONG_SSAVALUE);
          write_uint16(s->s, ((jl_ssavalue_t*)v)->id);
      }
      else if (jl_typeis(v, jl_slotnumber_type) && jl_slot_number(v) <= UINT16_MAX && jl_slot_number(v) >= 0) {
          write_uint8(s->s, TAG_SLOTNUMBER);
          write_uint16(s->s, jl_slot_number(v));
      }
      else if (jl_is_array(v)) {
          jl_array_t *ar = (jl_array_t*)v;
          int isunion = jl_is_uniontype(jl_tparam0(jl_typeof(ar)));
          if (ar->flags.ndims == 1 && ar->elsize <= 0x3f) {
              write_uint8(s->s, TAG_ARRAY1D);
              write_uint8(s->s, (ar->flags.ptrarray<<7) | (isunion << 6) | (ar->elsize & 0x3f));
          }
          else {
              write_uint8(s->s, TAG_ARRAY);
              write_uint16(s->s, ar->flags.ndims);
              write_uint16(s->s, (ar->flags.ptrarray << 15) | (isunion << 14) | (ar->elsize & 0x3fff));
          }
          for (i = 0; i < ar->flags.ndims; i++)
              jl_serialize_value(s, jl_box_long(jl_array_dim(ar,i)));
          jl_serialize_value(s, jl_typeof(ar));
          if (!ar->flags.ptrarray) {
              ios_write(s->s, (char*)jl_array_data(ar), jl_array_len(ar) * ar->elsize);
              if (jl_array_isbitsunion(ar))
                  ios_write(s->s, jl_array_typetagdata(ar), jl_array_len(ar));
          }
          else {
              for (i = 0; i < jl_array_len(ar); i++) {
                  jl_serialize_value(s, jl_array_ptr_ref(v, i));
              }
          }
      }
      else if (jl_is_expr(v)) {
          jl_expr_t *e = (jl_expr_t*)v;
          size_t l = jl_array_len(e->args);
          if (e->head == call_sym) {
              if (l == 2) {
                  write_uint8(s->s, TAG_CALL1);
                  jl_serialize_value(s, jl_exprarg(e, 0));
                  jl_serialize_value(s, jl_exprarg(e, 1));
                  return;
              }
              else if (l == 3) {
                  write_uint8(s->s, TAG_CALL2);
                  jl_serialize_value(s, jl_exprarg(e, 0));
                  jl_serialize_value(s, jl_exprarg(e, 1));
                  jl_serialize_value(s, jl_exprarg(e, 2));
                  return;
              }
          }
          if (l <= 255) {
              write_uint8(s->s, TAG_EXPR);
              write_uint8(s->s, (uint8_t)l);
          }
          else {
              write_uint8(s->s, TAG_LONG_EXPR);
              write_int32(s->s, l);
          }
          jl_serialize_value(s, e->head);
          for (i = 0; i < l; i++) {
              jl_serialize_value(s, jl_exprarg(e, i));
          }
      }
      else if (jl_is_phinode(v)) {
          jl_array_t *edges = (jl_array_t*)jl_fieldref_noalloc(v, 0);
          jl_array_t *values = (jl_array_t*)jl_fieldref_noalloc(v, 1);
          size_t l = jl_array_len(edges);
          if (l <= 255 && jl_array_len(values) == l) {
              write_uint8(s->s, TAG_PHINODE);
              write_uint8(s->s, (uint8_t)l);
          }
          else {
              write_uint8(s->s, TAG_LONG_PHINODE);
              write_int32(s->s, l);
              write_int32(s->s, jl_array_len(values));
          }
          for (i = 0; i < l; i++) {
              jl_serialize_value(s, jl_array_ptr_ref(edges, i));
          }
          l = jl_array_len(values);
          for (i = 0; i < l; i++) {
              jl_serialize_value(s, jl_array_ptr_ref(values, i));
          }
      }
      else if (jl_is_phicnode(v)) {
          jl_array_t *values = (jl_array_t*)jl_fieldref_noalloc(v, 0);
          size_t l = jl_array_len(values);
          if (l <= 255) {
              write_uint8(s->s, TAG_PHICNODE);
              write_uint8(s->s, (uint8_t)l);
          }
          else {
              write_uint8(s->s, TAG_LONG_PHICNODE);
              write_int32(s->s, l);
          }
          for (i = 0; i < l; i++) {
              jl_serialize_value(s, jl_array_ptr_ref(values, i));
          }
      }
      else if (jl_is_gotonode(v)) {
          write_uint8(s->s, TAG_GOTONODE);
          jl_serialize_value(s, jl_get_nth_field(v, 0));
      }
      else if (jl_is_quotenode(v)) {
          write_uint8(s->s, TAG_QUOTENODE);
          jl_serialize_value(s, jl_get_nth_field(v, 0));
      }
      else if (jl_is_datatype(v)) {
          jl_serialize_datatype(s, (jl_datatype_t*)v);
      }
      else if (jl_is_unionall(v)) {
          write_uint8(s->s, TAG_UNIONALL);
          jl_datatype_t *d = (jl_datatype_t*)jl_unwrap_unionall(v);
          if (jl_is_datatype(d) && d->name->wrapper == v &&
              !module_in_worklist(d->name->module)) {
              write_uint8(s->s, 1);
              jl_serialize_value(s, d->name->module);
              jl_serialize_value(s, d->name->name);
          }
          else {
              write_uint8(s->s, 0);
              jl_serialize_value(s, ((jl_unionall_t*)v)->var);
              jl_serialize_value(s, ((jl_unionall_t*)v)->body);
          }
      }
      else if (jl_is_typevar(v)) {
          write_uint8(s->s, TAG_TVAR);
          jl_serialize_value(s, ((jl_tvar_t*)v)->name);
          jl_serialize_value(s, ((jl_tvar_t*)v)->lb);
          jl_serialize_value(s, ((jl_tvar_t*)v)->ub);
      }
      else if (jl_is_method(v)) {
          write_uint8(s->s, TAG_METHOD);
          jl_method_t *m = (jl_method_t*)v;
          int internal = 1;
          int external_mt = 0;
          internal = module_in_worklist(m->module);
          if (!internal) {
              // flag this in the backref table as special
              uintptr_t *bp = (uintptr_t*)ptrhash_bp(&backref_table, v);
              assert(*bp != (uintptr_t)HT_NOTFOUND);
              *bp |= 1;
          }
          jl_serialize_value(s, (jl_value_t*)m->sig);
          write_uint8(s->s, internal);
          if (!internal)
              return;
          jl_methtable_t *mt = jl_method_table_for((jl_value_t*)m->sig);
          assert((jl_value_t*)mt != jl_nothing);
          external_mt = !module_in_worklist(mt->module);
          jl_serialize_value(s, m->specializations);
          jl_serialize_value(s, (jl_value_t*)m->name);
          jl_serialize_value(s, (jl_value_t*)m->file);
          write_int32(s->s, m->line);
          if (external_mt) {
              jl_serialize_value(s, jl_nothing);
              jl_serialize_value(s, jl_nothing);
          }
          else {
              jl_serialize_value(s, (jl_value_t*)m->ambig);
              jl_serialize_value(s, (jl_value_t*)m->resorted);
          }
          write_int32(s->s, m->called);
          write_int32(s->s, m->nargs);
          write_int32(s->s, m->nospecialize);
          write_int8(s->s, m->isva);
          write_int8(s->s, m->pure);
          jl_serialize_value(s, (jl_value_t*)m->module);
          jl_serialize_value(s, (jl_value_t*)m->slot_syms);
          jl_serialize_value(s, (jl_value_t*)m->roots);
          jl_serialize_value(s, (jl_value_t*)m->source);
          jl_serialize_value(s, (jl_value_t*)m->unspecialized);
          jl_serialize_value(s, (jl_value_t*)m->generator);
          jl_serialize_value(s, (jl_value_t*)m->invokes);
      }
      else if (jl_is_method_instance(v)) {
          write_uint8(s->s, TAG_METHOD_INSTANCE);
          jl_method_instance_t *mi = (jl_method_instance_t*)v;
          int internal = 0;
          if (!jl_is_method(mi->def.method))
              internal = 1;
          else if (module_in_worklist(mi->def.method->module))
              internal = 2;
          write_uint8(s->s, internal);
          if (!internal) {
              // also flag this in the backref table as special
              uintptr_t *bp = (uintptr_t*)ptrhash_bp(&backref_table, v);
              assert(*bp != (uintptr_t)HT_NOTFOUND);
              *bp |= 1;
          }
          if (internal == 1)
              jl_serialize_value(s, (jl_value_t*)mi->uninferred);
          jl_serialize_value(s, (jl_value_t*)mi->specTypes);
          if (internal)
              jl_serialize_value(s, mi->def.value);
          else
              jl_serialize_value(s, (jl_value_t*)mi->def.method->sig);
          if (!internal)
              return;
          jl_serialize_value(s, (jl_value_t*)mi->sparam_vals);
          jl_array_t *backedges = mi->backedges;
          if (s->mode == MODE_MODULE && backedges) {
              // filter backedges to only contain pointers
              // to items that we will actually store (internal == 2)
              size_t ins, i, l = jl_array_len(backedges);
              jl_method_instance_t **b_edges = (jl_method_instance_t**)jl_array_data(backedges);
              for (ins = i = 0; i < l; i++) {
                  jl_method_instance_t *backedge = b_edges[i];
                  if (module_in_worklist(backedge->def.method->module)) {
                      b_edges[ins++] = backedge;
                  }
              }
              if (ins != l)
                  jl_array_del_end(backedges, l - ins);
              if (ins == 0)
                  backedges = NULL;
          }
          jl_serialize_value(s, (jl_value_t*)backedges);
          jl_serialize_value(s, (jl_value_t*)mi->cache);
      }
      else if (jl_is_code_instance(v)) {
          write_uint8(s->s, TAG_CODE_INSTANCE);
          jl_code_instance_t *codeinst = (jl_code_instance_t*)v;
          int validate = 0;
          if (codeinst->max_world == ~(size_t)0)
              validate = 1; // can check on deserialize if this cache entry is still valid
          int flags = validate << 0;
          if (codeinst->invoke == jl_fptr_const_return)
              flags |= 1 << 2;
          write_uint8(s->s, flags);
          jl_serialize_value(s, (jl_value_t*)codeinst->def);
          if (validate || codeinst->min_world == 0) {
              jl_serialize_value(s, codeinst->inferred);
              jl_serialize_value(s, codeinst->rettype_const);
              jl_serialize_value(s, codeinst->rettype);
          }
          else {
              // skip storing useless data
              jl_serialize_value(s, NULL);
              jl_serialize_value(s, NULL);
              jl_serialize_value(s, jl_any_type);
          }
          jl_serialize_value(s, codeinst->next);
      }
      else if (jl_typeis(v, jl_module_type)) {
          jl_serialize_module(s, (jl_module_t*)v);
      }
      else if (jl_typeis(v, jl_task_type)) {
          jl_error("Task cannot be serialized");
      }
      else if (jl_typeis(v, jl_string_type)) {
          write_uint8(s->s, TAG_STRING);
          write_int32(s->s, jl_string_len(v));
          ios_write(s->s, jl_string_data(v), jl_string_len(v));
      }
      else if (jl_typeis(v, jl_typemap_entry_type)) {
          write_uint8(s->s, TAG_TYPEMAP_ENTRY);
          size_t n = 0;
          jl_typemap_entry_t *te = (jl_typemap_entry_t*)v;
          while ((jl_value_t*)te != jl_nothing) {
              n++;
              te = te->next;
          }
          write_int32(s->s, n);
          te = (jl_typemap_entry_t*)v;
          size_t i, nf = jl_datatype_nfields(jl_typemap_entry_type);
          while ((jl_value_t*)te != jl_nothing) {
              for (i = 1; i < nf; i++) {
                  if (jl_field_size(jl_typemap_entry_type, i) > 0) {
                      jl_serialize_value(s, jl_get_nth_field((jl_value_t*)te, i));
                      if (!jl_field_isptr(jl_typemap_entry_type, i))
                          write_int8(s->s, 0); // union-selector byte
                  }
              }
              te = te->next;
          }
      }
      else if (jl_typeis(v, jl_int64_type)) {
          void *data = jl_data_ptr(v);
          if (*(int64_t*)data >= INT16_MIN && *(int64_t*)data <= INT16_MAX) {
              write_uint8(s->s, TAG_SHORTER_INT64);
              write_uint16(s->s, (uint16_t)*(int64_t*)data);
          }
          else if (*(int64_t*)data >= S32_MIN && *(int64_t*)data <= S32_MAX) {
              write_uint8(s->s, TAG_SHORT_INT64);
              write_int32(s->s, (int32_t)*(int64_t*)data);
          }
          else {
              write_uint8(s->s, TAG_INT64);
              write_int64(s->s, *(int64_t*)data);
          }
      }
      else if (jl_typeis(v, jl_int32_type)) {
          void *data = jl_data_ptr(v);
          if (*(int32_t*)data >= INT16_MIN && *(int32_t*)data <= INT16_MAX) {
              write_uint8(s->s, TAG_SHORT_INT32);
              write_uint16(s->s, (uint16_t)*(int32_t*)data);
          }
          else {
              write_uint8(s->s, TAG_INT32);
              write_int32(s->s, *(int32_t*)data);
          }
      }
      else if (jl_typeis(v, jl_uint8_type)) {
          write_uint8(s->s, TAG_UINT8);
          write_int8(s->s, *(int8_t*)jl_data_ptr(v));
      }
      else if (jl_typeis(v, jl_lineinfonode_type)) {
          write_uint8(s->s, TAG_LINEINFO);
          for (i = 0; i < jl_datatype_nfields(jl_lineinfonode_type); i++)
              jl_serialize_value(s, jl_get_nth_field(v, i));
      }
      else if (jl_bigint_type && jl_typeis(v, jl_bigint_type)) {
          write_uint8(s->s, TAG_SHORT_GENERAL);
          write_uint8(s->s, jl_datatype_size(jl_bigint_type));
          jl_serialize_value(s, jl_bigint_type);
          jl_value_t *sizefield = jl_get_nth_field(v, 1);
          jl_serialize_value(s, sizefield);
          void *data = jl_unbox_voidpointer(jl_get_nth_field(v, 2));
          int32_t sz = jl_unbox_int32(sizefield);
          size_t nb = (sz == 0 ? 1 : (sz < 0 ? -sz : sz)) * gmp_limb_size;
          ios_write(s->s, (char*)data, nb);
      }
      else {
          jl_datatype_t *t = (jl_datatype_t*)jl_typeof(v);
          void *data = jl_data_ptr(v);
          {
              if (v == t->instance) {
                  if (s->mode == MODE_MODULE && !type_in_worklist(t)) {
                      // also flag this in the backref table as special
                      // if it might not be unique (is external)
                      uintptr_t *bp = (uintptr_t*)ptrhash_bp(&backref_table, v);
                      assert(*bp != (uintptr_t)HT_NOTFOUND);
                      *bp |= 1;
                  }
                  write_uint8(s->s, TAG_SINGLETON);
                  jl_serialize_value(s, t);
                  return;
              }
              assert(!t->instance && "detected singleton construction corruption");
  
              if (t == jl_typename_type) {
                  void *bttag = ptrhash_get(&ser_tag, ((jl_typename_t*)t)->wrapper);
                  if (bttag != HT_NOTFOUND) {
                      write_uint8(s->s, TAG_BITYPENAME);
                      write_uint8(s->s, (uint8_t)(intptr_t)bttag);
                      return;
                  }
              }
              if (t->size <= 255) {
                  write_uint8(s->s, TAG_SHORT_GENERAL);
                  write_uint8(s->s, t->size);
              }
              else {
                  write_uint8(s->s, TAG_GENERAL);
                  write_int32(s->s, t->size);
              }
              jl_serialize_value(s, t);
              if (t == jl_typename_type) {
                  jl_typename_t *tn = (jl_typename_t*)v;
                  int internal = module_in_worklist(tn->module);
                  write_uint8(s->s, internal);
                  jl_serialize_value(s, tn->module);
                  jl_serialize_value(s, tn->name);
                  if (internal) {
                      jl_serialize_value(s, tn->names);
                      jl_serialize_value(s, tn->wrapper);
                      jl_serialize_value(s, tn->mt);
                      ios_write(s->s, (char*)&tn->hash, sizeof(tn->hash));
                  }
                  return;
              }
              if (t == jl_typemap_level_type) {
                  // perform some compression on the typemap levels
                  // (which will need to be rehashed during deserialization anyhow)
                  jl_typemap_level_t *node = (jl_typemap_level_t*)v;
                  assert( // make sure this type has the expected ordering
                      offsetof(jl_typemap_level_t, arg1) == 0 * sizeof(jl_value_t*) &&
                      offsetof(jl_typemap_level_t, targ) == 2 * sizeof(jl_value_t*) &&
                      offsetof(jl_typemap_level_t, linear) == 4 * sizeof(jl_value_t*) &&
                      offsetof(jl_typemap_level_t, any) == 5 * sizeof(jl_value_t*) &&
                      offsetof(jl_typemap_level_t, key) == 6 * sizeof(jl_value_t*) &&
                      sizeof(jl_typemap_level_t) == 7 * sizeof(jl_value_t*));
                  jl_serialize_value(s, jl_nothing);
                  jl_serialize_value(s, node->arg1.values);
                  jl_serialize_value(s, jl_nothing);
                  jl_serialize_value(s, node->targ.values);
                  jl_serialize_value(s, node->linear);
                  jl_serialize_value(s, node->any);
                  jl_serialize_value(s, node->key);
                  return;
              }
              size_t nf = jl_datatype_nfields(t);
              if (nf == 0 && jl_datatype_size(t) > 0) {
                  if (t->name == jl_pointer_typename && jl_unbox_voidpointer(v) != (void*)-1) {
                      // normalize most pointers to NULL, to help catch memory errors
                      // but permit MAP_FAILED / INVALID_HANDLE to be stored unchanged
                      write_int32(s->s, 0);
  #ifdef _P64
                      write_int32(s->s, 0);
  #endif
                  }
                  else {
                      ios_write(s->s, (char*)data, jl_datatype_size(t));
                  }
              }
              else {
                  size_t i;
                  for (i = 0; i < nf; i++) {
                      size_t offs = jl_field_offset(t, i);
                      size_t fsz = jl_field_size(t, i);
                      if (fsz > 0) {
                          jl_serialize_value(s, jl_get_nth_field(v, i));
                          if (!jl_field_isptr(t, i)) {
                              uint8_t sel = 0;
                              if (jl_is_uniontype(jl_field_type(t, i))) {
                                  sel = ((uint8_t*)v)[offs + fsz - 1] + 1;
                              }
                              write_int8(s->s, sel);
                          }
                      }
                  }
              }
          }
      }
  }
  
  static void jl_collect_missing_backedges_to_mod(jl_methtable_t *mt)
  {
      jl_array_t *backedges = mt->backedges;
      if (backedges) {
          size_t i, l = jl_array_len(backedges);
          for (i = 1; i < l; i += 2) {
              jl_method_instance_t *caller = (jl_method_instance_t*)jl_array_ptr_ref(backedges, i);
              jl_value_t *missing_callee = jl_array_ptr_ref(backedges, i - 1);
              jl_array_t **edges = (jl_array_t**)ptrhash_bp(&edges_map, (void*)caller);
              if (*edges == HT_NOTFOUND)
                  *edges = jl_alloc_vec_any(0);
              jl_array_ptr_1d_push(*edges, missing_callee);
          }
      }
  }
  
  // the intent of this function is to invert the backedges tree
  // for anything that points to a method not part of the worklist
  static void collect_backedges(jl_method_instance_t *callee) JL_GC_DISABLED
  {
      jl_array_t *backedges = callee->backedges;
      if (backedges) {
          size_t i, l = jl_array_len(backedges);
          for (i = 0; i < l; i++) {
              jl_method_instance_t *caller = (jl_method_instance_t*)jl_array_ptr_ref(backedges, i);
              jl_array_t **edges = (jl_array_t**)ptrhash_bp(&edges_map, caller);
              if (*edges == HT_NOTFOUND)
                  *edges = jl_alloc_vec_any(0);
              jl_array_ptr_1d_push(*edges, (jl_value_t*)callee);
          }
      }
  }
  
  
  static int jl_collect_backedges_to_mod(jl_typemap_entry_t *ml, void *closure) JL_GC_DISABLED
  {
      (void)(jl_array_t*)closure;
      jl_method_instance_t *callee = ml->func.linfo;
      collect_backedges(callee);
      return 1;
  }
  
  static int jl_collect_methcache_from_mod(jl_typemap_entry_t *ml, void *closure) JL_GC_DISABLED
  {
      jl_array_t *s = (jl_array_t*)closure;
      jl_method_t *m = ml->func.method;
      if (module_in_worklist(m->module)) {
          jl_array_ptr_1d_push(s, (jl_value_t*)m);
          jl_array_ptr_1d_push(s, (jl_value_t*)ml->simplesig);
      }
      else {
          jl_typemap_visitor(m->specializations, jl_collect_backedges_to_mod, closure);
      }
      return 1;
  }
  
  static void jl_collect_methtable_from_mod(jl_array_t *s, jl_methtable_t *mt) JL_GC_DISABLED
  {
      jl_typemap_visitor(mt->defs, jl_collect_methcache_from_mod, (void*)s);
  }
  
  static void jl_collect_lambdas_from_mod(jl_array_t *s, jl_module_t *m) JL_GC_DISABLED
  {
      if (module_in_worklist(m))
          return;
      size_t i;
      void **table = m->bindings.table;
      for (i = 1; i < m->bindings.size; i += 2) {
          if (table[i] != HT_NOTFOUND) {
              jl_binding_t *b = (jl_binding_t*)table[i];
              if (b->owner == m && b->value && b->constp) {
                  jl_value_t *bv = jl_unwrap_unionall(b->value);
                  if (jl_is_datatype(bv)) {
                      jl_typename_t *tn = ((jl_datatype_t*)bv)->name;
                      if (tn->module == m && tn->name == b->name && tn->wrapper == b->value) {
                          jl_methtable_t *mt = tn->mt;
                          if (mt != NULL &&
                                  (jl_value_t*)mt != jl_nothing &&
                                  (mt != jl_type_type_mt && mt != jl_nonfunction_mt)) {
                              jl_collect_methtable_from_mod(s, mt);
                              jl_collect_missing_backedges_to_mod(mt);
                          }
                      }
                  }
                  else if (jl_is_module(b->value)) {
                      jl_module_t *child = (jl_module_t*)b->value;
                      if (child != m && child->parent == m && child->name == b->name) {
                          // this is the original/primary binding for the submodule
                          jl_collect_lambdas_from_mod(s, (jl_module_t*)b->value);
                      }
                  }
              }
          }
      }
  }
  
  // flatten the backedge map reachable from caller into callees
  static void jl_collect_backedges_to(jl_method_instance_t *caller, htable_t *all_callees) JL_GC_DISABLED
  {
      jl_array_t **pcallees = (jl_array_t**)ptrhash_bp(&edges_map, (void*)caller),
                  *callees = *pcallees;
      if (callees != HT_NOTFOUND) {
          *pcallees = (jl_array_t*) HT_NOTFOUND;
          size_t i, l = jl_array_len(callees);
          for (i = 0; i < l; i++) {
              jl_value_t *c = jl_array_ptr_ref(callees, i);
              ptrhash_put(all_callees, c, c);
              if (jl_is_method_instance(c)) {
                  jl_collect_backedges_to((jl_method_instance_t*)c, all_callees);
              }
          }
      }
  }
  
  static void jl_collect_backedges(jl_array_t *s)
  {
      htable_t all_callees;
      htable_new(&all_callees, 0);
      size_t i;
      void **table = edges_map.table;
      for (i = 0; i < edges_map.size; i += 2) {
          jl_method_instance_t *caller = (jl_method_instance_t*)table[i];
          jl_array_t *callees = (jl_array_t*)table[i + 1];
          if (callees != HT_NOTFOUND && module_in_worklist(caller->def.method->module)) {
              size_t i, l = jl_array_len(callees);
              for (i = 0; i < l; i++) {
                  jl_value_t *c = jl_array_ptr_ref(callees, i);
                  ptrhash_put(&all_callees, c, c);
                  if (jl_is_method_instance(c)) {
                      jl_collect_backedges_to((jl_method_instance_t*)c, &all_callees);
                  }
              }
              jl_array_del_end(callees, l);
              void **pc = all_callees.table;
              size_t j;
              for(j = 0; j < all_callees.size; j += 2) {
                  if (pc[j+1] != HT_NOTFOUND)
                      jl_array_ptr_1d_push(callees, (jl_value_t*)pc[j]);
              }
              htable_reset(&all_callees, 100);
              jl_array_ptr_1d_push(s, (jl_value_t*)caller);
              jl_array_ptr_1d_push(s, (jl_value_t*)callees);
          }
      }
      htable_free(&all_callees);
  }
  
  // serialize information about all loaded modules
  static void write_mod_list(ios_t *s, jl_array_t *a)
  {
      size_t i;
      size_t len = jl_array_len(a);
      for (i = 0; i < len; i++) {
          jl_module_t *m = (jl_module_t*)jl_array_ptr_ref(a, i);
          assert(jl_is_module(m));
          if (!module_in_worklist(m)) {
              const char *modname = jl_symbol_name(m->name);
              size_t l = strlen(modname);
              write_int32(s, l);
              ios_write(s, modname, l);
              write_uint64(s, m->uuid.hi);
              write_uint64(s, m->uuid.lo);
              write_uint64(s, m->build_id);
          }
      }
      write_int32(s, 0);
  }
  
  // "magic" string and version header of .ji file
  static const int JI_FORMAT_VERSION = 9;
  static const char JI_MAGIC[] = "\373jli\r\n\032\n"; // based on PNG signature
  static const uint16_t BOM = 0xFEFF; // byte-order marker
  static void write_header(ios_t *s)
  {
      ios_write(s, JI_MAGIC, strlen(JI_MAGIC));
      write_uint16(s, JI_FORMAT_VERSION);
      ios_write(s, (char *) &BOM, 2);
      write_uint8(s, sizeof(void*));
      ios_write(s, JL_BUILD_UNAME, strlen(JL_BUILD_UNAME)+1);
      ios_write(s, JL_BUILD_ARCH, strlen(JL_BUILD_ARCH)+1);
      ios_write(s, JULIA_VERSION_STRING, strlen(JULIA_VERSION_STRING)+1);
      const char *branch = jl_git_branch(), *commit = jl_git_commit();
      ios_write(s, branch, strlen(branch)+1);
      ios_write(s, commit, strlen(commit)+1);
  }
  
  // serialize information about the result of deserializing this file
  static void write_work_list(ios_t *s)
  {
      int i, l = jl_array_len(serializer_worklist);
      for (i = 0; i < l; i++) {
          jl_module_t *workmod = (jl_module_t*)jl_array_ptr_ref(serializer_worklist, i);
          if (workmod->parent == jl_main_module || workmod->parent == workmod) {
              size_t l = strlen(jl_symbol_name(workmod->name));
              write_int32(s, l);
              ios_write(s, jl_symbol_name(workmod->name), l);
              write_uint64(s, workmod->uuid.hi);
              write_uint64(s, workmod->uuid.lo);
              write_uint64(s, workmod->build_id);
          }
      }
      write_int32(s, 0);
  }
  
  static void write_module_path(ios_t *s, jl_module_t *depmod) JL_NOTSAFEPOINT
  {
      if (depmod->parent == jl_main_module || depmod->parent == depmod)
          return;
      const char *mname = jl_symbol_name(depmod->name);
      size_t slen = strlen(mname);
      write_module_path(s, depmod->parent);
      write_int32(s, slen);
      ios_write(s, mname, slen);
  }
  
  // serialize the global _require_dependencies array of pathnames that
  // are include dependencies
  static int64_t write_dependency_list(ios_t *s, jl_array_t **udepsp, jl_array_t *mod_array)
  {
      int64_t initial_pos = 0;
      int64_t pos = 0;
      static jl_array_t *deps = NULL;
      if (!deps)
          deps = (jl_array_t*)jl_get_global(jl_base_module, jl_symbol("_require_dependencies"));
  
      // unique(deps) to eliminate duplicates while preserving order:
      // we preserve order so that the topmost included .jl file comes first
      static jl_value_t *unique_func = NULL;
      if (!unique_func)
          unique_func = jl_get_global(jl_base_module, jl_symbol("unique"));
      jl_value_t *uniqargs[2] = {unique_func, (jl_value_t*)deps};
      size_t last_age = jl_get_ptls_states()->world_age;
      jl_get_ptls_states()->world_age = jl_world_counter;
      jl_array_t *udeps = (*udepsp = deps && unique_func ? (jl_array_t*)jl_apply(uniqargs, 2) : NULL);
      jl_get_ptls_states()->world_age = last_age;
  
      // write a placeholder for total size so that we can quickly seek past all of the
      // dependencies if we don't need them
      initial_pos = ios_pos(s);
      write_uint64(s, 0);
      if (udeps) {
          size_t i, l = jl_array_len(udeps);
          for (i = 0; i < l; i++) {
              jl_value_t *deptuple = jl_array_ptr_ref(udeps, i);
              jl_value_t *dep = jl_fieldref(deptuple, 1);              // file abspath
              size_t slen = jl_string_len(dep);
              write_int32(s, slen);
              ios_write(s, jl_string_data(dep), slen);
              write_float64(s, jl_unbox_float64(jl_fieldref(deptuple, 2)));  // mtime
              jl_module_t *depmod = (jl_module_t*)jl_fieldref(deptuple, 0);  // evaluating module
              jl_module_t *depmod_top = depmod;
              while (depmod_top->parent != jl_main_module && depmod_top->parent != depmod_top)
                  depmod_top = depmod_top->parent;
              unsigned provides = 0;
              size_t j, lj = jl_array_len(serializer_worklist);
              for (j = 0; j < lj; j++) {
                  jl_module_t *workmod = (jl_module_t*)jl_array_ptr_ref(serializer_worklist, j);
                  if (workmod->parent == jl_main_module || workmod->parent == workmod) {
                      ++provides;
                      if (workmod == depmod_top) {
                          write_int32(s, provides);
                          write_module_path(s, depmod);
                          break;
                      }
                  }
              }
              write_int32(s, 0);
          }
          write_int32(s, 0); // terminator, for ease of reading
          // write a dummy file position to indicate the beginning of the source-text
          pos = ios_pos(s);
          ios_seek(s, initial_pos);
          write_uint64(s, pos - initial_pos);
          ios_seek(s, pos);
          write_int64(s, 0);
      }
      return pos;
  }
  
  // --- deserialize ---
  
  static jl_value_t *jl_deserialize_datatype(jl_serializer_state *s, int pos, jl_value_t **loc) JL_GC_DISABLED
  {
      int tag = read_uint8(s->s);
      if (tag == 6 || tag == 7) {
          jl_typename_t *name = (jl_typename_t*)jl_deserialize_value(s, NULL);
          jl_value_t *dtv = name->wrapper;
          jl_svec_t *parameters = (jl_svec_t*)jl_deserialize_value(s, NULL);
          dtv = jl_apply_type(dtv, jl_svec_data(parameters), jl_svec_len(parameters));
          backref_list.items[pos] = dtv;
          return dtv;
      }
      if (tag == 9) {
          jl_datatype_t *primarydt = (jl_datatype_t*)jl_deserialize_value(s, NULL);
          jl_value_t *dtv = jl_typeof(jl_get_kwsorter((jl_value_t*)primarydt));
          backref_list.items[pos] = dtv;
          return dtv;
      }
      size_t size = read_int32(s->s);
      uint8_t flags = read_uint8(s->s);
      uint8_t memflags = read_uint8(s->s);
      jl_datatype_t *dt = NULL;
      if (tag == 0 || tag == 5 || tag == 10)
          dt = jl_new_uninitialized_datatype();
      else {
          assert(0 && "corrupt deserialization state");
          abort();
      }
      assert(s->method == NULL && s->mode != MODE_IR && "no new data-types expected during MODE_IR");
      assert(pos == backref_list.len - 1 && "nothing should have been deserialized since assigning pos");
      backref_list.items[pos] = dt;
      dt->size = size;
      dt->struct_decl = NULL;
      dt->instance = NULL;
      dt->ditype = NULL;
      dt->abstract = flags & 1;
      dt->mutabl = (flags >> 1) & 1;
      int has_layout = (flags >> 2) & 1;
      int has_instance = (flags >> 3) & 1;
      dt->hasfreetypevars = memflags & 1;
      dt->isconcretetype = (memflags >> 1) & 1;
      dt->isdispatchtuple = (memflags >> 2) & 1;
      dt->isbitstype = (memflags >> 3) & 1;
      dt->zeroinit = (memflags >> 4) & 1;
      dt->isinlinealloc = (memflags >> 5) & 1;
      dt->has_concrete_subtype = (memflags >> 6) & 1;
      dt->types = NULL;
      dt->parameters = NULL;
      dt->name = NULL;
      dt->super = NULL;
      dt->layout = NULL;
      if (!dt->abstract) {
          dt->ninitialized = read_uint16(s->s);
          dt->uid = 0;
      }
      else {
          dt->ninitialized = 0;
          dt->uid = 0;
      }
  
      if (has_layout) {
          uint8_t layout = read_uint8(s->s);
          if (layout == 1) {
              dt->layout = ((jl_datatype_t*)jl_unwrap_unionall((jl_value_t*)jl_array_type))->layout;
          }
          else if (layout == 2) {
              dt->layout = jl_void_type->layout;
          }
          else if (layout == 3) {
              dt->layout = ((jl_datatype_t*)jl_unwrap_unionall((jl_value_t*)jl_pointer_type))->layout;
          }
          else {
              assert(layout == 0);
              uint32_t nf = read_int32(s->s);
              uint32_t alignment = read_int32(s->s);
              union {
                  struct {
                      uint32_t nf;
                      uint32_t alignment;
                  } buffer;
                  jl_datatype_layout_t layout;
              } header;
              header.buffer.nf = nf;
              header.buffer.alignment = alignment;
              int has_padding = header.layout.npointers && nf;
              uint8_t fielddesc_type = header.layout.fielddesc_type;
              size_t fielddesc_size = nf > 0 ? jl_fielddesc_size(fielddesc_type) : 0;
              jl_datatype_layout_t *layout = (jl_datatype_layout_t*)jl_gc_perm_alloc(
                      sizeof(jl_datatype_layout_t) + nf * fielddesc_size +
                      (has_padding ? sizeof(uint32_t) : 0), 0, 4, 0);
              if (has_padding) {
                  layout = (jl_datatype_layout_t*)(((char*)layout) + sizeof(uint32_t));
                  jl_datatype_layout_n_nonptr(layout) = read_int32(s->s);
              }
              *layout = header.layout;
              ios_read(s->s, (char*)&layout[1], nf * fielddesc_size);
              dt->layout = layout;
          }
      }
  
      if (tag == 5) {
          dt->uid = jl_assign_type_uid();
      }
      else if (tag == 10) {
          assert(pos > 0);
          arraylist_push(&flagref_list, loc == HT_NOTFOUND ? NULL : loc);
          arraylist_push(&flagref_list, (void*)(uintptr_t)pos);
          dt->uid = -1; // mark that this type needs a new uid
      }
  
      if (has_instance) {
          assert(dt->uid != 0 && "there shouldn't be an instance on a type with uid = 0");
          dt->instance = jl_deserialize_value(s, &dt->instance);
          jl_gc_wb(dt, dt->instance);
      }
      dt->name = (jl_typename_t*)jl_deserialize_value(s, (jl_value_t**)&dt->name);
      jl_gc_wb(dt, dt->name);
      dt->names = (jl_svec_t*)jl_deserialize_value(s, (jl_value_t**)&dt->names);
      jl_gc_wb(dt, dt->names);
      dt->parameters = (jl_svec_t*)jl_deserialize_value(s, (jl_value_t**)&dt->parameters);
      jl_gc_wb(dt, dt->parameters);
      dt->super = (jl_datatype_t*)jl_deserialize_value(s, (jl_value_t**)&dt->super);
      jl_gc_wb(dt, dt->super);
      dt->types = (jl_svec_t*)jl_deserialize_value(s, (jl_value_t**)&dt->types);
      if (dt->types) jl_gc_wb(dt, dt->types);
  
      return (jl_value_t*)dt;
  }
  
  static jl_value_t *jl_deserialize_value_svec(jl_serializer_state *s, uint8_t tag) JL_GC_DISABLED
  {
      int usetable = (s->mode != MODE_IR);
      size_t i, len;
      if (tag == TAG_SVEC)
          len = read_uint8(s->s);
      else
          len = read_int32(s->s);
      jl_svec_t *sv = jl_alloc_svec_uninit(len);
      if (usetable)
          arraylist_push(&backref_list, (jl_value_t*)sv);
      jl_value_t **data = jl_svec_data(sv);
      for (i = 0; i < len; i++) {
          data[i] = jl_deserialize_value(s, &data[i]);
      }
      return (jl_value_t*)sv;
  }
  
  static jl_value_t *jl_deserialize_value_symbol(jl_serializer_state *s, uint8_t tag) JL_GC_DISABLED
  {
      int usetable = (s->mode != MODE_IR);
      size_t len;
      if (tag == TAG_SYMBOL)
          len = read_uint8(s->s);
      else
          len = read_int32(s->s);
      char *name = (char*)(len >= 256 ? malloc(len + 1) : alloca(len + 1));
      ios_read(s->s, name, len);
      name[len] = '\0';
      jl_value_t *sym = (jl_value_t*)jl_symbol(name);
      if (len >= 256)
          free(name);
      if (usetable)
          arraylist_push(&backref_list, sym);
      return sym;
  }
  
  static jl_value_t *jl_deserialize_value_array(jl_serializer_state *s, uint8_t tag) JL_GC_DISABLED
  {
      int usetable = (s->mode != MODE_IR);
      int16_t i, ndims;
      int isunboxed, isunion, elsize;
      if (tag == TAG_ARRAY1D) {
          ndims = 1;
          elsize = read_uint8(s->s);
          isunboxed = !(elsize >> 7);
          isunion = elsize >> 6;
          elsize = elsize & 0x3f;
      }
      else {
          ndims = read_uint16(s->s);
          elsize = read_uint16(s->s);
          isunboxed = !(elsize >> 15);
          isunion = elsize >> 14;
          elsize = elsize & 0x3fff;
      }
      uintptr_t pos = backref_list.len;
      if (usetable)
          arraylist_push(&backref_list, NULL);
      size_t *dims = (size_t*)alloca(ndims * sizeof(size_t));
      for (i = 0; i < ndims; i++) {
          dims[i] = jl_unbox_long(jl_deserialize_value(s, NULL));
      }
      jl_array_t *a = jl_new_array_for_deserialization(
              (jl_value_t*)NULL, ndims, dims, isunboxed, isunion, elsize);
      if (usetable)
          backref_list.items[pos] = a;
      jl_value_t *aty = jl_deserialize_value(s, &jl_astaggedvalue(a)->type);
      jl_set_typeof(a, aty);
      if (!a->flags.ptrarray) {
          size_t extra = jl_array_isbitsunion(a) ? jl_array_len(a) : 0;
          size_t tot = jl_array_len(a) * a->elsize + extra;
          ios_read(s->s, (char*)jl_array_data(a), tot);
      }
      else {
          jl_value_t **data = (jl_value_t**)jl_array_data(a);
          size_t i, numel = jl_array_len(a);
          for (i = 0; i < numel; i++) {
              data[i] = jl_deserialize_value(s, &data[i]);
              if (data[i])
                  jl_gc_wb(a, data[i]);
          }
      }
      return (jl_value_t*)a;
  }
  
  static jl_value_t *jl_deserialize_value_expr(jl_serializer_state *s, uint8_t tag) JL_GC_DISABLED
  {
      int usetable = (s->mode != MODE_IR);
      size_t i, len;
      jl_sym_t *head = NULL;
      if (tag == TAG_EXPR) {
          len = read_uint8(s->s);
      }
      else if (tag == TAG_CALL1) {
          len = 2;
          head = call_sym;
      }
      else if (tag == TAG_CALL2) {
          len = 3;
          head = call_sym;
      }
      else {
          len = read_int32(s->s);
      }
      int pos = backref_list.len;
      if (usetable)
          arraylist_push(&backref_list, NULL);
      if (head == NULL)
          head = (jl_sym_t*)jl_deserialize_value(s, NULL);
      jl_expr_t *e = jl_exprn(head, len);
      if (usetable)
          backref_list.items[pos] = e;
      jl_value_t **data = (jl_value_t**)(e->args->data);
      for (i = 0; i < len; i++) {
          data[i] = jl_deserialize_value(s, &data[i]);
      }
      return (jl_value_t*)e;
  }
  
  static jl_value_t *jl_deserialize_value_phi(jl_serializer_state *s, uint8_t tag) JL_GC_DISABLED
  {
      int usetable = (s->mode != MODE_IR);
      size_t i, len_e, len_v;
      if (tag == TAG_PHINODE) {
          len_e = len_v = read_uint8(s->s);
      }
      else {
          len_e = read_int32(s->s);
          len_v = read_int32(s->s);
      }
      jl_array_t *e = jl_alloc_vec_any(len_e);
      jl_array_t *v = jl_alloc_vec_any(len_v);
      jl_value_t *phi = jl_new_struct(jl_phinode_type, e, v);
      if (usetable)
          arraylist_push(&backref_list, phi);
      jl_value_t **data_e = (jl_value_t**)(e->data);
      for (i = 0; i < len_e; i++) {
          data_e[i] = jl_deserialize_value(s, &data_e[i]);
      }
      jl_value_t **data_v = (jl_value_t**)(v->data);
      for (i = 0; i < len_v; i++) {
          data_v[i] = jl_deserialize_value(s, &data_v[i]);
      }
      return phi;
  }
  
  static jl_value_t *jl_deserialize_value_phic(jl_serializer_state *s, uint8_t tag) JL_GC_DISABLED
  {
      int usetable = (s->mode != MODE_IR);
      size_t i, len;
      if (tag == TAG_PHICNODE)
          len = read_uint8(s->s);
      else
          len = read_int32(s->s);
      jl_array_t *v = jl_alloc_vec_any(len);
      jl_value_t *phic = jl_new_struct(jl_phicnode_type, v);
      if (usetable)
          arraylist_push(&backref_list, phic);
      jl_value_t **data = (jl_value_t**)(v->data);
      for (i = 0; i < len; i++) {
          data[i] = jl_deserialize_value(s, &data[i]);
      }
      return phic;
  }
  
  static jl_value_t *jl_deserialize_value_method(jl_serializer_state *s, jl_value_t **loc) JL_GC_DISABLED
  {
      int usetable = (s->mode != MODE_IR);
      jl_method_t *m =
          (jl_method_t*)jl_gc_alloc(s->ptls, sizeof(jl_method_t),
                                    jl_method_type);
      memset(m, 0, sizeof(jl_method_t));
      uintptr_t pos = backref_list.len;
      if (usetable)
          arraylist_push(&backref_list, m);
      m->sig = (jl_value_t*)jl_deserialize_value(s, (jl_value_t**)&m->sig);
      jl_gc_wb(m, m->sig);
      int internal = read_uint8(s->s);
      if (!internal) {
          assert(loc != NULL && loc != HT_NOTFOUND);
          arraylist_push(&flagref_list, loc);
          arraylist_push(&flagref_list, (void*)pos);
          return (jl_value_t*)m;
      }
      m->specializations = jl_deserialize_value(s, (jl_value_t**)&m->specializations);
      jl_gc_wb(m, m->specializations);
      m->name = (jl_sym_t*)jl_deserialize_value(s, NULL);
      jl_gc_wb(m, m->name);
      m->file = (jl_sym_t*)jl_deserialize_value(s, NULL);
      m->line = read_int32(s->s);
      m->primary_world = jl_world_counter;
      m->deleted_world = ~(size_t)0;
      m->ambig = jl_deserialize_value(s, (jl_value_t**)&m->ambig);
      jl_gc_wb(m, m->ambig);
      m->resorted = jl_deserialize_value(s, (jl_value_t**)&m->resorted);
      jl_gc_wb(m, m->resorted);
      m->called = read_int32(s->s);
      m->nargs = read_int32(s->s);
      m->nospecialize = read_int32(s->s);
      m->isva = read_int8(s->s);
      m->pure = read_int8(s->s);
      m->module = (jl_module_t*)jl_deserialize_value(s, (jl_value_t**)&m->module);
      jl_gc_wb(m, m->module);
      m->slot_syms = jl_deserialize_value(s, (jl_value_t**)&m->slot_syms);
      jl_gc_wb(m, m->slot_syms);
      m->roots = (jl_array_t*)jl_deserialize_value(s, (jl_value_t**)&m->roots);
      if (m->roots)
          jl_gc_wb(m, m->roots);
      m->source = jl_deserialize_value(s, &m->source);
      if (m->source)
          jl_gc_wb(m, m->source);
      m->unspecialized = (jl_method_instance_t*)jl_deserialize_value(s, (jl_value_t**)&m->unspecialized);
      if (m->unspecialized)
          jl_gc_wb(m, m->unspecialized);
      m->generator = jl_deserialize_value(s, (jl_value_t**)&m->generator);
      if (m->generator)
          jl_gc_wb(m, m->generator);
      m->invokes = jl_deserialize_value(s, (jl_value_t**)&m->invokes);
      jl_gc_wb(m, m->invokes);
      JL_MUTEX_INIT(&m->writelock);
      return (jl_value_t*)m;
  }
  
  static jl_value_t *jl_deserialize_value_method_instance(jl_serializer_state *s, jl_value_t **loc) JL_GC_DISABLED
  {
      int usetable = (s->mode != MODE_IR);
      jl_method_instance_t *mi =
          (jl_method_instance_t*)jl_gc_alloc(s->ptls, sizeof(jl_method_instance_t),
                                         jl_method_instance_type);
      memset(mi, 0, sizeof(jl_method_instance_t));
      uintptr_t pos = backref_list.len;
      if (usetable)
          arraylist_push(&backref_list, mi);
      int internal = read_uint8(s->s);
      mi->specTypes = (jl_value_t*)jl_deserialize_value(s, (jl_value_t**)&mi->specTypes);
      jl_gc_wb(mi, mi->specTypes);
      mi->def.value = jl_deserialize_value(s, &mi->def.value);
      jl_gc_wb(mi, mi->def.value);
  
      if (!internal) {
          assert(loc != NULL && loc != HT_NOTFOUND);
          arraylist_push(&flagref_list, loc);
          arraylist_push(&flagref_list, (void*)pos);
          return (jl_value_t*)mi;
      }
  
      if (internal == 1) {
          mi->uninferred = jl_deserialize_value(s, &mi->uninferred);
          jl_gc_wb(mi, mi->uninferred);
      }
      mi->sparam_vals = (jl_svec_t*)jl_deserialize_value(s, (jl_value_t**)&mi->sparam_vals);
      jl_gc_wb(mi, mi->sparam_vals);
      mi->backedges = (jl_array_t*)jl_deserialize_value(s, (jl_value_t**)&mi->backedges);
      if (mi->backedges)
          jl_gc_wb(mi, mi->backedges);
      mi->cache = (jl_code_instance_t*)jl_deserialize_value(s, (jl_value_t**)&mi->cache);
      if (mi->cache)
          jl_gc_wb(mi, mi->cache);
      return (jl_value_t*)mi;
  }
  
  static jl_value_t *jl_deserialize_value_code_instance(jl_serializer_state *s, jl_value_t **loc) JL_GC_DISABLED
  {
      int usetable = (s->mode != MODE_IR);
      jl_code_instance_t *codeinst =
          (jl_code_instance_t*)jl_gc_alloc(s->ptls, sizeof(jl_code_instance_t), jl_code_instance_type);
      memset(codeinst, 0, sizeof(jl_code_instance_t));
      if (usetable)
          arraylist_push(&backref_list, codeinst);
      int flags = read_uint8(s->s);
      int validate = (flags >> 0) & 3;
      int constret = (flags >> 2) & 1;
      codeinst->def = (jl_method_instance_t*)jl_deserialize_value(s, (jl_value_t**)&codeinst->def);
      jl_gc_wb(codeinst, codeinst->def);
      codeinst->inferred = jl_deserialize_value(s, &codeinst->inferred);
      jl_gc_wb(codeinst, codeinst->inferred);
      codeinst->rettype_const = jl_deserialize_value(s, &codeinst->rettype_const);
      if (codeinst->rettype_const)
          jl_gc_wb(codeinst, codeinst->rettype_const);
      codeinst->rettype = jl_deserialize_value(s, &codeinst->rettype);
      jl_gc_wb(codeinst, codeinst->rettype);
      if (constret)
          codeinst->invoke = jl_fptr_const_return;
      codeinst->next = (jl_code_instance_t*)jl_deserialize_value(s, (jl_value_t**)&codeinst->next);
      jl_gc_wb(codeinst, codeinst->next);
      if (validate)
          codeinst->min_world = jl_world_counter;
      return (jl_value_t*)codeinst;
  }
  
  static jl_value_t *jl_deserialize_value_module(jl_serializer_state *s) JL_GC_DISABLED
  {
      int usetable = (s->mode != MODE_IR);
      uintptr_t pos = backref_list.len;
      if (usetable)
          arraylist_push(&backref_list, NULL);
      jl_sym_t *mname = (jl_sym_t*)jl_deserialize_value(s, NULL);
      int ref_only = read_uint8(s->s);
      if (ref_only) {
          jl_value_t *m_ref;
          if (ref_only == 1)
              m_ref = jl_get_global((jl_module_t*)jl_deserialize_value(s, NULL), mname);
          else
              m_ref = jl_array_ptr_ref(s->loaded_modules_array, read_int32(s->s));
          if (usetable)
              backref_list.items[pos] = m_ref;
          return m_ref;
      }
      jl_module_t *m = jl_new_module(mname);
      if (usetable)
          backref_list.items[pos] = m;
      m->parent = (jl_module_t*)jl_deserialize_value(s, (jl_value_t**)&m->parent);
      jl_gc_wb(m, m->parent);
  
      while (1) {
          jl_sym_t *name = (jl_sym_t*)jl_deserialize_value(s, NULL);
          if (name == NULL)
              break;
          jl_binding_t *b = jl_get_binding_wr(m, name, 1);
          b->value = jl_deserialize_value(s, &b->value);
          jl_gc_wb_buf(m, b, sizeof(jl_binding_t));
          if (b->value != NULL) jl_gc_wb(m, b->value);
          b->globalref = jl_deserialize_value(s, &b->globalref);
          if (b->globalref != NULL) jl_gc_wb(m, b->globalref);
          b->owner = (jl_module_t*)jl_deserialize_value(s, (jl_value_t**)&b->owner);
          if (b->owner != NULL) jl_gc_wb(m, b->owner);
          int8_t flags = read_int8(s->s);
          b->deprecated = (flags>>3) & 1;
          b->constp = (flags>>2) & 1;
          b->exportp = (flags>>1) & 1;
          b->imported = (flags) & 1;
      }
      size_t i = m->usings.len;
      size_t ni = read_int32(s->s);
      arraylist_grow(&m->usings, ni);
      ni += i;
      while (i < ni) {
          m->usings.items[i] = jl_deserialize_value(s, (jl_value_t**)&m->usings.items[i]);
          i++;
      }
      m->istopmod = read_uint8(s->s);
      m->uuid.hi = read_uint64(s->s);
      m->uuid.lo = read_uint64(s->s);
      m->build_id = read_uint64(s->s);
      m->counter = read_int32(s->s);
      m->nospecialize = read_int32(s->s);
      m->primary_world = jl_world_counter;
      return (jl_value_t*)m;
  }
  
  static jl_value_t *jl_deserialize_value_globalref(jl_serializer_state *s) JL_GC_DISABLED
  {
      int usetable = (s->mode != MODE_IR);
      if (usetable) {
          jl_value_t *v = jl_new_struct_uninit(jl_globalref_type);
          arraylist_push(&backref_list, v);
          jl_value_t **data = jl_data_ptr(v);
          data[0] = jl_deserialize_value(s, &data[0]);
          data[1] = jl_deserialize_value(s, &data[1]);
          return v;
      }
      else {
          jl_value_t *mod = jl_deserialize_value(s, NULL);
          jl_value_t *var = jl_deserialize_value(s, NULL);
          return jl_module_globalref((jl_module_t*)mod, (jl_sym_t*)var);
      }
  }
  
  static jl_value_t *jl_deserialize_value_singleton(jl_serializer_state *s, jl_value_t **loc) JL_GC_DISABLED
  {
      if (s->mode == MODE_IR) {
          jl_datatype_t *dt = (jl_datatype_t*)jl_deserialize_value(s, NULL);
          return dt->instance;
      }
      jl_value_t *v = (jl_value_t*)jl_gc_alloc(s->ptls, 0, NULL);
      uintptr_t pos = backref_list.len;
      arraylist_push(&backref_list, (void*)v);
      if (s->mode == MODE_MODULE) {
          // TODO: optimize the case where the value can easily be obtained
          // from an external module (tag == 6) as dt->instance
          assert(loc != HT_NOTFOUND);
          // if loc == NULL, then the caller can't provide the address where the instance will be
          // stored. this happens if a field might store a 0-size value, but the field itself is
          // not 0 size, e.g. `::Union{Int,Nothing}`
          if (loc != NULL) {
              arraylist_push(&flagref_list, loc);
              arraylist_push(&flagref_list, (void*)pos);
          }
      }
      jl_datatype_t *dt = (jl_datatype_t*)jl_deserialize_value(s, (jl_value_t**)HT_NOTFOUND); // no loc, since if dt is replaced, then dt->instance would be also
      jl_set_typeof(v, dt);
      return v;
  }
  
  static void jl_deserialize_struct(jl_serializer_state *s, jl_value_t *v, size_t startfield) JL_GC_DISABLED
  {
      jl_datatype_t *dt = (jl_datatype_t*)jl_typeof(v);
      size_t i, nf = jl_datatype_nfields(dt);
      char *data = (char*)jl_data_ptr(v);
      for (i = startfield; i < nf; i++) {
          size_t offs = jl_field_offset(dt, i);
          size_t fsz = jl_field_size(dt, i);
          jl_value_t **fld = (jl_value_t**)(data + offs);
          if (fsz > 0) {
              if (jl_field_isptr(dt, i)) {
                  *fld = jl_deserialize_value(s, fld);
              }
              else {
                  jl_value_t *fldval = jl_deserialize_value(s, NULL);
                  jl_assign_bits((char*)fld, fldval);
                  uint8_t union_selector = read_uint8(s->s);
                  if (union_selector) {
                      uint8_t *psel = (uint8_t*)fld + fsz - 1;
                      *psel = union_selector - 1;
                  }
              }
          }
      }
      if (dt == jl_typemap_entry_type) {
          jl_typemap_entry_t *entry = (jl_typemap_entry_t*)v;
          if (entry->max_world == ~(size_t)0) {
              if (entry->min_world > 1) {
                  // update world validity to reflect current state of the counter
                  entry->min_world = jl_world_counter;
              }
          }
          else {
              // garbage entry - delete it :(
              entry->min_world = 1;
              entry->max_world = 0;
          }
      }
  }
  
  static jl_value_t *jl_deserialize_typemap_entry(jl_serializer_state *s) JL_GC_DISABLED
  {
      int N = read_int32(s->s); int n = N;
      jl_value_t *te = jl_nothing;
      jl_value_t **pn = &te;
      while (n > 0) {
          jl_value_t *v = jl_gc_alloc(s->ptls, jl_datatype_size(jl_typemap_entry_type), jl_typemap_entry_type);
          if (n == N && s->mode != MODE_IR)
              arraylist_push(&backref_list, v);
          jl_typemap_entry_t* te = (jl_typemap_entry_t*)v;
          te->next = (jl_typemap_entry_t*)jl_nothing; // `next` is the first field
          jl_deserialize_struct(s, v, 1);
          *pn = v;
          pn = (jl_value_t**)&te->next;
          n--;
      }
      return te;
  }
  
  static jl_value_t *jl_deserialize_value_any(jl_serializer_state *s, uint8_t tag, jl_value_t **loc) JL_GC_DISABLED
  {
      int usetable = (s->mode != MODE_IR);
      int32_t sz = (tag == TAG_SHORT_GENERAL ? read_uint8(s->s) : read_int32(s->s));
      jl_value_t *v = jl_gc_alloc(s->ptls, sz, NULL);
      jl_set_typeof(v, (void*)(intptr_t)0x50);
      uintptr_t pos = backref_list.len;
      if (usetable)
          arraylist_push(&backref_list, v);
      jl_datatype_t *dt = (jl_datatype_t*)jl_deserialize_value(s, &jl_astaggedvalue(v)->type);
      assert(s->mode == MODE_IR || sz != 0 || loc);
      if (s->mode == MODE_MODULE && dt == jl_typename_type) {
          int internal = read_uint8(s->s);
          jl_typename_t *tn;
          if (internal) {
              tn = (jl_typename_t*)jl_gc_alloc(
                      s->ptls, sizeof(jl_typename_t), jl_typename_type);
              memset(tn, 0, sizeof(jl_typename_t));
              tn->cache = jl_emptysvec; // the cache is refilled later (tag 5)
              tn->linearcache = jl_emptysvec; // the cache is refilled later (tag 5)
              if (usetable)
                  backref_list.items[pos] = tn;
          }
          jl_module_t *m = (jl_module_t*)jl_deserialize_value(s, NULL);
          jl_sym_t *sym = (jl_sym_t*)jl_deserialize_value(s, NULL);
          if (internal) {
              tn->module = m;
              tn->name = sym;
              tn->names = (jl_svec_t*)jl_deserialize_value(s, (jl_value_t**)&tn->names);
              jl_gc_wb(tn, tn->names);
              tn->wrapper = jl_deserialize_value(s, &tn->wrapper);
              jl_gc_wb(tn, tn->wrapper);
              tn->mt = (jl_methtable_t*)jl_deserialize_value(s, (jl_value_t**)&tn->mt);
              jl_gc_wb(tn, tn->mt);
              ios_read(s->s, (char*)&tn->hash, sizeof(tn->hash));
          }
          else {
              jl_datatype_t *dt = (jl_datatype_t*)jl_unwrap_unionall(jl_get_global(m, sym));
              assert(jl_is_datatype(dt));
              tn = dt->name;
              if (usetable)
                  backref_list.items[pos] = tn;
          }
          return (jl_value_t*)tn;
      }
      jl_set_typeof(v, dt);
      if (jl_datatype_nfields(dt) == 0 && jl_datatype_size(dt) > 0) {
          int nby = jl_datatype_size(dt);
          ios_read(s->s, (char*)jl_data_ptr(v), nby);
      }
      else if ((jl_value_t*)dt == jl_bigint_type) {
          jl_value_t *sizefield = jl_deserialize_value(s, NULL);
          int32_t sz = jl_unbox_int32(sizefield);
          int32_t nw = (sz == 0 ? 1 : (sz < 0 ? -sz : sz));
          size_t nb = nw * gmp_limb_size;
          void *buf = jl_gc_counted_malloc(nb);
          ios_read(s->s, (char*)buf, nb);
          jl_set_nth_field(v, 0, jl_box_int32(nw));
          jl_set_nth_field(v, 1, sizefield);
          jl_set_nth_field(v, 2, jl_box_voidpointer(buf));
      }
      else {
          jl_deserialize_struct(s, v, 0);
      }
      return v;
  }
  
  static jl_value_t *jl_deserialize_value(jl_serializer_state *s, jl_value_t **loc) JL_GC_DISABLED
  {
      assert(!ios_eof(s->s));
      jl_value_t *v;
      size_t i, n;
      uintptr_t pos;
      uint8_t tag = read_uint8(s->s);
      if (tag > LAST_TAG)
          return deser_tag[tag];
      int usetable = (s->mode != MODE_IR);
      switch (tag) {
      case TAG_NULL: return NULL;
      case 0:
          tag = read_uint8(s->s);
          return deser_tag[tag];
      case TAG_BACKREF: JL_FALLTHROUGH; case TAG_SHORT_BACKREF:
          assert(s->method == NULL && s->mode != MODE_IR);
          uintptr_t offs = (tag == TAG_BACKREF) ? read_int32(s->s) : read_uint16(s->s);
          int isflagref = 0;
          isflagref = !!(offs & 1);
          offs >>= 1;
          // assert(offs >= 0); // offs is unsigned so this is always true
          assert(offs < backref_list.len);
          jl_value_t *bp = (jl_value_t*)backref_list.items[offs];
          assert(bp);
          if (isflagref && loc != HT_NOTFOUND) {
              if (loc != NULL) {
                  // as in jl_deserialize_value_singleton, the caller won't have a place to
                  // store this reference given a field type like Union{Int,Nothing}
                  arraylist_push(&flagref_list, loc);
                  arraylist_push(&flagref_list, (void*)(uintptr_t)-1);
              }
          }
          return (jl_value_t*)bp;
      case TAG_METHODROOT:
          return jl_array_ptr_ref(s->method->roots, read_uint8(s->s));
      case TAG_LONG_METHODROOT:
          return jl_array_ptr_ref(s->method->roots, read_uint16(s->s));
      case TAG_SVEC: JL_FALLTHROUGH; case TAG_LONG_SVEC:
          return jl_deserialize_value_svec(s, tag);
      case TAG_COMMONSYM:
          return deser_symbols[read_uint8(s->s)];
      case TAG_SYMBOL: JL_FALLTHROUGH; case TAG_LONG_SYMBOL:
          return jl_deserialize_value_symbol(s, tag);
      case TAG_SSAVALUE:
          v = jl_box_ssavalue(read_uint8(s->s));
          if (usetable)
              arraylist_push(&backref_list, v);
          return v;
      case TAG_LONG_SSAVALUE:
          v = jl_box_ssavalue(read_uint16(s->s));
          if (usetable)
              arraylist_push(&backref_list, v);
          return v;
      case TAG_SLOTNUMBER:
          v = jl_box_slotnumber(read_uint16(s->s));
          if (usetable)
              arraylist_push(&backref_list, v);
          return v;
      case TAG_ARRAY: JL_FALLTHROUGH; case TAG_ARRAY1D:
          return jl_deserialize_value_array(s, tag);
      case TAG_EXPR:      JL_FALLTHROUGH;
      case TAG_LONG_EXPR: JL_FALLTHROUGH;
      case TAG_CALL1:     JL_FALLTHROUGH;
      case TAG_CALL2:
          return jl_deserialize_value_expr(s, tag);
      case TAG_PHINODE: JL_FALLTHROUGH; case TAG_LONG_PHINODE:
          return jl_deserialize_value_phi(s, tag);
      case TAG_PHICNODE: JL_FALLTHROUGH; case TAG_LONG_PHICNODE:
          return jl_deserialize_value_phic(s, tag);
      case TAG_GOTONODE: JL_FALLTHROUGH; case TAG_QUOTENODE:
          v = jl_new_struct_uninit(tag == TAG_GOTONODE ? jl_gotonode_type : jl_quotenode_type);
          if (usetable)
              arraylist_push(&backref_list, v);
          jl_set_nth_field(v, 0, jl_deserialize_value(s, NULL));
          return v;
      case TAG_UNIONALL:
          pos = backref_list.len;
          if (usetable)
              arraylist_push(&backref_list, NULL);
          if (read_uint8(s->s)) {
              jl_module_t *m = (jl_module_t*)jl_deserialize_value(s, NULL);
              jl_sym_t *sym = (jl_sym_t*)jl_deserialize_value(s, NULL);
              jl_value_t *v = jl_get_global(m, sym);
              assert(jl_is_unionall(v));
              if (usetable)
                  backref_list.items[pos] = v;
              return v;
          }
          v = jl_gc_alloc(s->ptls, sizeof(jl_unionall_t), jl_unionall_type);
          if (usetable)
              backref_list.items[pos] = v;
          ((jl_unionall_t*)v)->var = (jl_tvar_t*)jl_deserialize_value(s, (jl_value_t**)&((jl_unionall_t*)v)->var);
          jl_gc_wb(v, ((jl_unionall_t*)v)->var);
          ((jl_unionall_t*)v)->body = jl_deserialize_value(s, &((jl_unionall_t*)v)->body);
          jl_gc_wb(v, ((jl_unionall_t*)v)->body);
          return v;
      case TAG_TVAR:
          v = jl_gc_alloc(s->ptls, sizeof(jl_tvar_t), jl_tvar_type);
          jl_tvar_t *tv = (jl_tvar_t*)v;
          if (usetable)
              arraylist_push(&backref_list, tv);
          tv->name = (jl_sym_t*)jl_deserialize_value(s, NULL);
          jl_gc_wb(tv, tv->name);
          tv->lb = jl_deserialize_value(s, &tv->lb);
          jl_gc_wb(tv, tv->lb);
          tv->ub = jl_deserialize_value(s, &tv->ub);
          jl_gc_wb(tv, tv->ub);
          return (jl_value_t*)tv;
      case TAG_METHOD:
          return jl_deserialize_value_method(s, loc);
      case TAG_METHOD_INSTANCE:
          return jl_deserialize_value_method_instance(s, loc);
      case TAG_CODE_INSTANCE:
          return jl_deserialize_value_code_instance(s, loc);
      case TAG_MODULE:
          return jl_deserialize_value_module(s);
      case TAG_SHORTER_INT64:
          v = jl_box_int64((int16_t)read_uint16(s->s));
          if (usetable)
              arraylist_push(&backref_list, v);
          return v;
      case TAG_SHORT_INT64:
          v = jl_box_int64(read_int32(s->s));
          if (usetable)
              arraylist_push(&backref_list, v);
          return v;
      case TAG_INT64:
          v = jl_box_int64((int64_t)read_uint64(s->s));
          if (usetable)
              arraylist_push(&backref_list, v);
          return v;
      case TAG_SHORT_INT32:
          v = jl_box_int32((int16_t)read_uint16(s->s));
          if (usetable)
              arraylist_push(&backref_list, v);
          return v;
      case TAG_INT32:
          v = jl_box_int32(read_int32(s->s));
          if (usetable)
              arraylist_push(&backref_list, v);
          return v;
      case TAG_UINT8:
          return jl_box_uint8(read_uint8(s->s));
      case TAG_NEARBYGLOBAL:
          assert(s->method != NULL);
          v = jl_deserialize_value(s, NULL);
          return jl_module_globalref(s->method->module, (jl_sym_t*)v);
      case TAG_NEARBYMODULE:
          assert(s->method != NULL);
          return (jl_value_t*)s->method->module;
      case TAG_GLOBALREF:
          return jl_deserialize_value_globalref(s);
      case TAG_SINGLETON:
          return jl_deserialize_value_singleton(s, loc);
      case TAG_CORE:
          return (jl_value_t*)jl_core_module;
      case TAG_BASE:
          return (jl_value_t*)jl_base_module;
      case TAG_VECTORTY:
          v = jl_deserialize_value(s, NULL);
          return jl_apply_type2((jl_value_t*)jl_array_type, v, jl_box_long(1));
      case TAG_PTRTY:
          v = jl_deserialize_value(s, NULL);
          return jl_apply_type1((jl_value_t*)jl_pointer_type, v);
      case TAG_BITYPENAME:
          v = deser_tag[read_uint8(s->s)];
          jl_datatype_t *dt = (jl_datatype_t*)jl_unwrap_unionall(v);
          return (jl_value_t*)dt->name;
      case TAG_STRING:
          n = read_int32(s->s);
          v = jl_alloc_string(n);
          if (usetable)
              arraylist_push(&backref_list, v);
          ios_read(s->s, jl_string_data(v), n);
          return v;
      case TAG_TYPEMAP_ENTRY:
          return jl_deserialize_typemap_entry(s);
      case TAG_LINEINFO:
          v = jl_new_struct_uninit(jl_lineinfonode_type);
          if (usetable)
              arraylist_push(&backref_list, v);
          for (i = 0; i < jl_datatype_nfields(jl_lineinfonode_type); i++) {
              size_t offs = jl_field_offset(jl_lineinfonode_type, i);
              jl_set_nth_field(v, i, jl_deserialize_value(s, (jl_value_t**)((char*)v + offs)));
          }
          return v;
      case TAG_DATATYPE:
          pos = backref_list.len;
          if (usetable)
              arraylist_push(&backref_list, NULL);
          return jl_deserialize_datatype(s, pos, loc);
      default:
          assert(tag == TAG_GENERAL || tag == TAG_SHORT_GENERAL);
          return jl_deserialize_value_any(s, tag, loc);
      }
  }
  
  static void jl_insert_methods(jl_array_t *list)
  {
      size_t i, l = jl_array_len(list);
      for (i = 0; i < l; i += 2) {
          jl_method_t *meth = (jl_method_t*)jl_array_ptr_ref(list, i);
          jl_tupletype_t *simpletype = (jl_tupletype_t*)jl_array_ptr_ref(list, i + 1);
          assert(jl_is_method(meth));
          jl_methtable_t *mt = jl_method_table_for((jl_value_t*)meth->sig);
          assert((jl_value_t*)mt != jl_nothing);
          jl_method_table_insert(mt, meth, simpletype);
      }
  }
  
  static size_t lowerbound_dependent_world_set(size_t world, arraylist_t *dependent_worlds) JL_NOTSAFEPOINT
  {
      size_t i, l = dependent_worlds->len;
      if (world <= (size_t)dependent_worlds->items[l - 1])
          return world;
      for (i = 0; i < l; i++) {
          size_t depworld = (size_t)dependent_worlds->items[i];
          if (depworld <= world)
              return depworld;
      }
      abort(); // unreachable
  }
  
  extern int JL_DEBUG_METHOD_INVALIDATION;
  
  static void jl_insert_backedges(jl_array_t *list, arraylist_t *dependent_worlds)
  {
      size_t i, l = jl_array_len(list);
      for (i = 0; i < l; i += 2) {
          jl_method_instance_t *caller = (jl_method_instance_t*)jl_array_ptr_ref(list, i);
          assert(jl_is_method_instance(caller) && jl_is_method(caller->def.method));
          assert(caller->def.method->primary_world == jl_world_counter); // caller should be new
          jl_array_t *callees = (jl_array_t*)jl_array_ptr_ref(list, i + 1);
          assert(jl_is_array(callees));
          int valid = 1;
          size_t j;
          for (j = 0; valid && j < jl_array_len(callees); j++) {
              jl_value_t *callee = jl_array_ptr_ref(callees, j);
              jl_method_instance_t *callee_mi = (jl_method_instance_t*)callee;
              jl_value_t *sig;
              if (jl_is_method_instance(callee)) {
                  sig = callee_mi->specTypes;
                  assert(!module_in_worklist(callee_mi->def.method->module));
                  // TODO: short-circuit, so we don't do a huge amount of excess work
              }
              else {
                  sig = callee;
              }
              // verify that this backedge doesn't intersect with any new methods
              size_t min_valid = 0;
              size_t max_valid = ~(size_t)0;
              jl_value_t *matches = jl_matching_methods((jl_tupletype_t*)sig, -1, 1, 0, &min_valid, &max_valid);
              if (matches == jl_false)
                  valid = 0;
              size_t k;
              for (k = 0; valid && k < jl_array_len(matches); k++) {
                  jl_method_t *m = (jl_method_t*)jl_svecref(jl_array_ptr_ref(matches, k), 2);
                  int wasactive = (lowerbound_dependent_world_set(m->primary_world, dependent_worlds) == m->primary_world);
                  int nowactive = (m->deleted_world == ~(size_t)0);
                  if (wasactive != nowactive) {
                      // intersection has a new method or a method was deleted--this is now probably no good, just invalidate everything now
                      valid = 0;
                  }
              }
          }
          if (valid) {
              // if this callee is still valid, add all the backedges
              for (j = 0; j < jl_array_len(callees); j++) {
                  jl_value_t *callee = jl_array_ptr_ref(callees, j);
                  if (jl_is_method_instance(callee)) {
                      jl_method_instance_add_backedge((jl_method_instance_t*)callee, caller);
                  }
                  else {
                      jl_methtable_t *mt = jl_method_table_for(callee);
                      assert((jl_value_t*)mt != jl_nothing);
                      jl_method_table_add_backedge(mt, callee, (jl_value_t*)caller);
                  }
              }
              // then enable it
              jl_code_instance_t *codeinst = caller->cache;
              while (codeinst) {
                  if (codeinst->min_world > 0)
                      codeinst->max_world = ~(size_t)0;
                  codeinst = codeinst->next;
              }
          }
          else {
              if (JL_DEBUG_METHOD_INVALIDATION) {
                  jl_static_show(JL_STDOUT, (jl_value_t*)caller);
                  jl_uv_puts(JL_STDOUT, "<<<\n", 4);
              }
          }
      }
  }
  
  
  static int size_isgreater(const void *a, const void *b)
  {
      return *(size_t*)b - *(size_t*)a;
  }
  
  static jl_value_t *read_verify_mod_list(ios_t *s, arraylist_t *dependent_worlds, jl_array_t *mod_list)
  {
      if (!jl_main_module->build_id) {
          return jl_get_exceptionf(jl_errorexception_type,
                  "Main module uuid state is invalid for module deserialization.");
      }
      size_t i, l = jl_array_len(mod_list);
      for (i = 0; ; i++) {
          size_t len = read_int32(s);
          if (len == 0 && i == l)
              return NULL; // success
          if (len == 0 || i == l)
              return jl_get_exceptionf(jl_errorexception_type, "Wrong number of entries in module list.");
          char *name = (char*)alloca(len + 1);
          ios_read(s, name, len);
          name[len] = '\0';
          jl_uuid_t uuid;
          uuid.hi = read_uint64(s);
          uuid.lo = read_uint64(s);
          uint64_t build_id = read_uint64(s);
          jl_sym_t *sym = jl_symbol_n(name, len);
          jl_module_t *m = (jl_module_t*)jl_array_ptr_ref(mod_list, i);
          if (!m || !jl_is_module(m) || m->uuid.hi != uuid.hi || m->uuid.lo != uuid.lo || m->name != sym || m->build_id != build_id) {
              return jl_get_exceptionf(jl_errorexception_type,
                  "Invalid input in module list: expected %s.", name);
          }
          if (m->primary_world > jl_main_module->primary_world)
              arraylist_push(dependent_worlds, (void*)m->primary_world);
      }
  }
  
  static int readstr_verify(ios_t *s, const char *str)
  {
      size_t i, len = strlen(str);
      for (i = 0; i < len; ++i)
          if ((char)read_uint8(s) != str[i])
              return 0;
      return 1;
  }
  
  JL_DLLEXPORT int jl_read_verify_header(ios_t *s)
  {
      uint16_t bom;
      return (readstr_verify(s, JI_MAGIC) &&
              read_uint16(s) == JI_FORMAT_VERSION &&
              ios_read(s, (char *) &bom, 2) == 2 && bom == BOM &&
              read_uint8(s) == sizeof(void*) &&
              readstr_verify(s, JL_BUILD_UNAME) && !read_uint8(s) &&
              readstr_verify(s, JL_BUILD_ARCH) && !read_uint8(s) &&
              readstr_verify(s, JULIA_VERSION_STRING) && !read_uint8(s) &&
              readstr_verify(s, jl_git_branch()) && !read_uint8(s) &&
              readstr_verify(s, jl_git_commit()) && !read_uint8(s));
  }
  
  static void jl_finalize_serializer(jl_serializer_state *s)
  {
      size_t i, l;
      // save module initialization order
      if (jl_module_init_order != NULL) {
          l = jl_array_len(jl_module_init_order);
          for (i = 0; i < l; i++) {
              // verify that all these modules were saved
              assert(ptrhash_get(&backref_table, jl_array_ptr_ref(jl_module_init_order, i)) != HT_NOTFOUND);
          }
      }
      jl_serialize_value(s, jl_module_init_order);
  
      // record list of reinitialization functions
      l = reinit_list.len;
      for (i = 0; i < l; i += 2) {
          write_int32(s->s, (int)((uintptr_t) reinit_list.items[i]));
          write_int32(s->s, (int)((uintptr_t) reinit_list.items[i+1]));
      }
      write_int32(s->s, -1);
  }
  
  void jl_typemap_rehash(jl_typemap_t *ml, int8_t offs);
  static void jl_reinit_item(jl_value_t *v, int how, arraylist_t *tracee_list)
  {
      JL_TRY {
          switch (how) {
              case 1: { // rehash IdDict
                  jl_array_t **a = (jl_array_t**)v;
                  // Assume *a don't need a write barrier
                  *a = jl_idtable_rehash(*a, jl_array_len(*a));
                  jl_gc_wb(v, *a);
                  break;
              }
              case 2: { // reinsert module v into parent (const)
                  jl_module_t *mod = (jl_module_t*)v;
                  if (mod->parent == mod) // top level modules handled by loader
                      break;
                  jl_binding_t *b = jl_get_binding_wr(mod->parent, mod->name, 1);
                  jl_declare_constant(b); // this can throw
                  if (b->value != NULL) {
                      if (!jl_is_module(b->value)) {
                          jl_errorf("Invalid redefinition of constant %s.",
                                    jl_symbol_name(mod->name)); // this also throws
                      }
                      if (jl_generating_output() && jl_options.incremental) {
                          jl_errorf("Cannot replace module %s during incremental precompile.", jl_symbol_name(mod->name));
                      }
                      jl_printf(JL_STDERR, "WARNING: replacing module %s.\n", jl_symbol_name(mod->name));
                  }
                  b->value = v;
                  jl_gc_wb_binding(b, v);
                  break;
              }
              case 3: { // rehash MethodTable
                  jl_methtable_t *mt = (jl_methtable_t*)v;
                  jl_typemap_rehash(mt->defs, 0);
                  // TODO: consider reverting this when we can split on Type{...} better
                  jl_typemap_rehash(mt->cache, mt->offs);
                  if (tracee_list)
                      arraylist_push(tracee_list, mt);
                  break;
              }
              case 4: { // rehash specializations tfunc
                  jl_method_t *m = (jl_method_t*)v;
                  jl_typemap_rehash(m->specializations, 0);
                  break;
              }
              default:
                  assert(0 && "corrupt deserialization state");
                  abort();
          }
      }
      JL_CATCH {
          jl_printf(JL_STDERR, "WARNING: error while reinitializing value ");
          jl_static_show(JL_STDERR, v);
          jl_printf(JL_STDERR, ":\n");
          jl_static_show(JL_STDERR, jl_current_exception());
          jl_printf(JL_STDERR, "\n");
      }
  }
  
  static jl_array_t *jl_finalize_deserializer(jl_serializer_state *s, arraylist_t *tracee_list)
  {
      jl_array_t *init_order = (jl_array_t*)jl_deserialize_value(s, NULL);
  
      // run reinitialization functions
      int pos = read_int32(s->s);
      while (pos != -1) {
          jl_reinit_item((jl_value_t*)backref_list.items[pos], read_int32(s->s), tracee_list);
          pos = read_int32(s->s);
      }
      return init_order;
  }
  
  JL_DLLEXPORT void jl_init_restored_modules(jl_array_t *init_order)
  {
      if (!init_order)
          return;
      int i, l = jl_array_len(init_order);
      for (i = 0; i < l; i++) {
          jl_value_t *mod = jl_array_ptr_ref(init_order, i);
          if (!jl_generating_output() || jl_options.incremental) {
              jl_module_run_initializer((jl_module_t*)mod);
          }
          else {
              if (jl_module_init_order == NULL)
                  jl_module_init_order = jl_alloc_vec_any(0);
              jl_array_ptr_1d_push(jl_module_init_order, mod);
          }
      }
  }
  
  
  // --- entry points ---
  
  JL_DLLEXPORT jl_array_t *jl_compress_ast(jl_method_t *m, jl_code_info_t *code)
  {
      JL_TIMING(AST_COMPRESS);
      JL_LOCK(&m->writelock); // protect the roots array (Might GC)
      assert(jl_is_method(m));
      assert(jl_is_code_info(code));
      ios_t dest;
      ios_mem(&dest, 0);
      int en = jl_gc_enable(0); // Might GC
      size_t i;
  
      if (m->roots == NULL) {
          m->roots = jl_alloc_vec_any(0);
          jl_gc_wb(m, m->roots);
      }
      jl_serializer_state s = {
          &dest, MODE_IR,
          m,
          jl_get_ptls_states(),
          NULL
      };
  
      uint8_t flags = (code->inferred << 3)
                    | (code->inlineable << 2)
                    | (code->propagate_inbounds << 1)
                    | (code->pure << 0);
      write_uint8(s.s, flags);
  
      size_t nslots = jl_array_len(code->slotflags);
      assert(nslots >= m->nargs && nslots < INT32_MAX); // required by generated functions
      write_int32(s.s, nslots);
      ios_write(s.s, (char*)jl_array_data(code->slotflags), nslots);
  
      // N.B.: The layout of everything before this point is explicitly referenced
      // by the various jl_ast_ accessors. Make sure to adjust those if you change
      // the data layout.
  
      for (i = 0; i < 6; i++) {
          int copy = 1;
          if (i == 1) { // skip codelocs
              assert(jl_field_offset(jl_code_info_type, i) == offsetof(jl_code_info_t, codelocs));
              continue;
          }
          if (i == 4) { // don't copy contents of method_for_inference_limit_heuristics field
              assert(jl_field_offset(jl_code_info_type, i) == offsetof(jl_code_info_t, method_for_inference_limit_heuristics));
              copy = 0;
          }
          jl_serialize_value_(&s, jl_get_nth_field((jl_value_t*)code, i), copy);
      }
  
      if (m->generator)
          // can't optimize generated functions
          jl_serialize_value_(&s, (jl_value_t*)jl_compress_argnames(code->slotnames), 1);
      else
          jl_serialize_value(&s, jl_nothing);
  
      size_t nstmt = jl_array_len(code->code);
      assert(nstmt == jl_array_len(code->codelocs));
      if (jl_array_len(code->linetable) < 256) {
          for (i = 0; i < nstmt; i++) {
              write_uint8(s.s, ((int32_t*)jl_array_data(code->codelocs))[i]);
          }
      }
      else if (jl_array_len(code->linetable) < 65536) {
          for (i = 0; i < nstmt; i++) {
              write_uint16(s.s, ((int32_t*)jl_array_data(code->codelocs))[i]);
          }
      }
      else {
          ios_write(s.s, (char*)jl_array_data(code->codelocs), nstmt * sizeof(int32_t));
      }
  
      ios_flush(s.s);
      jl_array_t *v = jl_take_buffer(&dest);
      ios_close(s.s);
      if (jl_array_len(m->roots) == 0) {
          m->roots = NULL;
      }
      JL_GC_PUSH1(&v);
      jl_gc_enable(en);
      JL_UNLOCK(&m->writelock); // Might GC
      JL_GC_POP();
      return v;
  }
  
  JL_DLLEXPORT jl_code_info_t *jl_uncompress_ast(jl_method_t *m, jl_code_instance_t *metadata, jl_array_t *data)
  {
      if (jl_is_code_info(data))
          return (jl_code_info_t*)data;
      JL_TIMING(AST_UNCOMPRESS);
      JL_LOCK(&m->writelock); // protect the roots array (Might GC)
      assert(jl_is_method(m));
      assert(jl_typeis(data, jl_array_uint8_type));
      size_t i;
      ios_t src;
      ios_mem(&src, 0);
      ios_setbuf(&src, (char*)data->data, jl_array_len(data), 0);
      src.size = jl_array_len(data);
      int en = jl_gc_enable(0); // Might GC
      jl_serializer_state s = {
          &src, MODE_IR,
          m,
          jl_get_ptls_states(),
          NULL
      };
  
      jl_code_info_t *code = jl_new_code_info_uninit();
      uint8_t flags = read_uint8(s.s);
      code->inferred = !!(flags & (1 << 3));
      code->inlineable = !!(flags & (1 << 2));
      code->propagate_inbounds = !!(flags & (1 << 1));
      code->pure = !!(flags & (1 << 0));
  
      size_t nslots = read_int32(&src);
      code->slotflags = jl_alloc_array_1d(jl_array_uint8_type, nslots);
      ios_read(s.s, (char*)jl_array_data(code->slotflags), nslots);
  
      for (i = 0; i < 6; i++) {
          if (i == 1)  // skip codelocs
              continue;
          assert(jl_field_isptr(jl_code_info_type, i));
          jl_value_t **fld = (jl_value_t**)((char*)jl_data_ptr(code) + jl_field_offset(jl_code_info_type, i));
          *fld = jl_deserialize_value(&s, fld);
      }
  
      jl_value_t *slotnames = jl_deserialize_value(&s, NULL);
      if (!jl_is_string(slotnames))
          slotnames = m->slot_syms;
      code->slotnames = jl_uncompress_argnames(slotnames);
  
      size_t nstmt = jl_array_len(code->code);
      code->codelocs = (jl_value_t*)jl_alloc_array_1d(jl_array_int32_type, nstmt);
      if (jl_array_len(code->linetable) < 256) {
          for (i = 0; i < nstmt; i++) {
              ((int32_t*)jl_array_data(code->codelocs))[i] = read_uint8(s.s);
          }
      }
      else if (jl_array_len(code->linetable) < 65536) {
          for (i = 0; i < nstmt; i++) {
              ((int32_t*)jl_array_data(code->codelocs))[i] = read_uint16(s.s);
          }
      }
      else {
          ios_read(s.s, (char*)jl_array_data(code->codelocs), nstmt * sizeof(int32_t));
      }
  
      assert(ios_getc(s.s) == -1);
      ios_close(s.s);
      JL_GC_PUSH1(&code);
      jl_gc_enable(en);
      JL_UNLOCK(&m->writelock); // Might GC
      JL_GC_POP();
      if (metadata) {
          code->min_world = metadata->min_world;
          code->max_world = metadata->max_world;
          code->rettype = metadata->rettype;
          code->parent = metadata->def;
      }
      return code;
  }
  
  JL_DLLEXPORT uint8_t jl_ast_flag_inferred(jl_array_t *data)
  {
      if (jl_is_code_info(data))
          return ((jl_code_info_t*)data)->inferred;
      assert(jl_typeis(data, jl_array_uint8_type));
      uint8_t flags = ((uint8_t*)data->data)[0];
      return !!(flags & (1 << 3));
  }
  
  JL_DLLEXPORT uint8_t jl_ast_flag_inlineable(jl_array_t *data)
  {
      if (jl_is_code_info(data))
          return ((jl_code_info_t*)data)->inlineable;
      assert(jl_typeis(data, jl_array_uint8_type));
      uint8_t flags = ((uint8_t*)data->data)[0];
      return !!(flags & (1 << 2));
  }
  
  JL_DLLEXPORT uint8_t jl_ast_flag_pure(jl_array_t *data)
  {
      if (jl_is_code_info(data))
          return ((jl_code_info_t*)data)->pure;
      assert(jl_typeis(data, jl_array_uint8_type));
      uint8_t flags = ((uint8_t*)data->data)[0];
      return !!(flags & (1 << 0));
  }
  
  JL_DLLEXPORT jl_value_t *jl_compress_argnames(jl_array_t *syms)
  {
      size_t nsyms = jl_array_len(syms);
      size_t i, len = 0;
      for (i = 0; i < nsyms; i++) {
          jl_sym_t *name = (jl_sym_t*)jl_array_ptr_ref(syms, i);
          assert(jl_is_symbol(name));
          char *namestr = jl_symbol_name(name);
          size_t namelen = strlen(namestr) + 1;
          len += namelen;
      }
      jl_value_t *str = jl_alloc_string(len);
      len = 0;
      for (i = 0; i < nsyms; i++) {
          jl_sym_t *name = (jl_sym_t*)jl_array_ptr_ref(syms, i);
          assert(jl_is_symbol(name));
          char *namestr = jl_symbol_name(name);
          size_t namelen = strlen(namestr) + 1; // include nul-byte
          assert(len + namelen <= jl_string_len(str));
          memcpy(jl_string_data(str) + len, namestr, namelen);
          len += namelen;
      }
      assert(len == jl_string_len(str));
      return str;
  }
  
  JL_DLLEXPORT ssize_t jl_ast_nslots(jl_array_t *data)
  {
      if (jl_is_code_info(data)) {
          jl_code_info_t *func = (jl_code_info_t*)data;
          return jl_array_len(func->slotnames);
      }
      else {
          assert(jl_typeis(data, jl_array_uint8_type));
          int nslots = jl_load_unaligned_i32((char*)data->data + 1);
          return nslots;
      }
  }
  
  JL_DLLEXPORT uint8_t jl_ast_slotflag(jl_array_t *data, size_t i)
  {
      assert(i < jl_ast_nslots(data));
      if (jl_is_code_info(data))
          return ((uint8_t*)((jl_code_info_t*)data)->slotflags->data)[i];
      assert(jl_typeis(data, jl_array_uint8_type));
      return ((uint8_t*)data->data)[1 + sizeof(int32_t) + i];
  }
  
  JL_DLLEXPORT jl_array_t *jl_uncompress_argnames(jl_value_t *syms)
  {
      assert(jl_is_string(syms));
      char *namestr;
      namestr = jl_string_data(syms);
      size_t remaining = jl_string_len(syms);
      size_t i, len = 0;
      while (remaining) {
          size_t namelen = strlen(namestr);
          len += 1;
          namestr += namelen + 1;
          remaining -= namelen + 1;
      }
      namestr = jl_string_data(syms);
      jl_array_t *names = jl_alloc_array_1d(jl_array_symbol_type, len);
      JL_GC_PUSH1(&names);
      for (i = 0; i < len; i++) {
          size_t namelen = strlen(namestr);
          jl_sym_t *name = jl_symbol_n(namestr, namelen);
          jl_array_ptr_set(names, i, name);
          namestr += namelen + 1;
      }
      JL_GC_POP();
      return names;
  }
  
  JL_DLLEXPORT jl_value_t *jl_uncompress_argname_n(jl_value_t *syms, size_t i)
  {
      assert(jl_is_string(syms));
      char *namestr = jl_string_data(syms);
      size_t remaining = jl_string_len(syms);
      while (remaining) {
          size_t namelen = strlen(namestr);
          if (i-- == 0) {
              jl_sym_t *name = jl_symbol_n(namestr, namelen);
              return (jl_value_t*)name;
          }
          namestr += namelen + 1;
          remaining -= namelen + 1;
      }
      return jl_nothing;
  }
  
  
  JL_DLLEXPORT int jl_save_incremental(const char *fname, jl_array_t *worklist)
  {
      JL_TIMING(SAVE_MODULE);
      char *tmpfname = strcat(strcpy((char *) alloca(strlen(fname)+8), fname), ".XXXXXX");
      ios_t f;
      jl_array_t *mod_array = NULL, *udeps = NULL;
      if (ios_mkstemp(&f, tmpfname) == NULL) {
          jl_printf(JL_STDERR, "Cannot open cache file \"%s\" for writing.\n", tmpfname);
          return 1;
      }
      JL_GC_PUSH2(&mod_array, &udeps);
      mod_array = jl_get_loaded_modules();
  
      serializer_worklist = worklist;
      write_header(&f);
      // write description on contents
      write_work_list(&f);
      // write binary blob from caller
      int64_t srctextpos = write_dependency_list(&f, &udeps, mod_array);
      // write description of requirements for loading
      // this can return errors during deserialize,
      // best to keep it early (before any actual initialization)
      write_mod_list(&f, mod_array);
  
      arraylist_new(&reinit_list, 0);
      htable_new(&edges_map, 0);
      htable_new(&backref_table, 5000);
      ptrhash_put(&backref_table, jl_main_module, (char*)HT_NOTFOUND + 1);
      backref_table_numel = 1;
      jl_idtable_type = jl_base_module ? jl_get_global(jl_base_module, jl_symbol("IdDict")) : NULL;
      jl_idtable_typename = jl_base_module ? ((jl_datatype_t*)jl_unwrap_unionall((jl_value_t*)jl_idtable_type))->name : NULL;
      jl_bigint_type = jl_base_module ? jl_get_global(jl_base_module, jl_symbol("BigInt")) : NULL;
      if (jl_bigint_type) {
          gmp_limb_size = jl_unbox_long(jl_get_global((jl_module_t*)jl_get_global(jl_base_module, jl_symbol("GMP")),
                                                      jl_symbol("BITS_PER_LIMB"))) / 8;
      }
  
      int en = jl_gc_enable(0); // edges map is not gc-safe
      jl_array_t *lambdas = jl_alloc_vec_any(0);
      jl_array_t *edges = jl_alloc_vec_any(0);
  
      size_t i;
      size_t len = jl_array_len(mod_array);
      for (i = 0; i < len; i++) {
          jl_module_t *m = (jl_module_t*)jl_array_ptr_ref(mod_array, i);
          assert(jl_is_module(m));
          if (m->parent == m) // some toplevel modules (really just Base) aren't actually
              jl_collect_lambdas_from_mod(lambdas, m);
      }
      jl_collect_methtable_from_mod(lambdas, jl_type_type_mt);
      jl_collect_missing_backedges_to_mod(jl_type_type_mt);
      jl_collect_methtable_from_mod(lambdas, jl_nonfunction_mt);
      jl_collect_missing_backedges_to_mod(jl_nonfunction_mt);
  
      jl_collect_backedges(edges);
  
      jl_serializer_state s = {
          &f, MODE_MODULE,
          NULL,
          jl_get_ptls_states(),
          mod_array
      };
      jl_serialize_value(&s, worklist);
      jl_serialize_value(&s, lambdas);
      jl_serialize_value(&s, edges);
      jl_finalize_serializer(&s);
      serializer_worklist = NULL;
  
      jl_gc_enable(en);
      htable_reset(&edges_map, 0);
      htable_reset(&backref_table, 0);
      arraylist_free(&reinit_list);
  
      // Write the source-text for the dependent files
      if (udeps) {
          // Go back and update the source-text position to point to the current position
          int64_t posfile = ios_pos(&f);
          ios_seek(&f, srctextpos);
          write_int64(&f, posfile);
          ios_seek_end(&f);
          // Each source-text file is written as
          //   int32: length of abspath
          //   char*: abspath
          //   uint64: length of src text
          //   char*: src text
          // At the end we write int32(0) as a terminal sentinel.
          len = jl_array_len(udeps);
          ios_t srctext;
          for (i = 0; i < len; i++) {
              jl_value_t *deptuple = jl_array_ptr_ref(udeps, i);
              jl_value_t *depmod = jl_fieldref(deptuple, 0);  // module
              // Dependencies declared with `include_dependency` are excluded
              // because these may not be Julia code (and could be huge)
              if (depmod != (jl_value_t*)jl_main_module) {
                  jl_value_t *dep = jl_fieldref(deptuple, 1);  // file abspath
                  const char *depstr = jl_string_data(dep);
                  if (!depstr[0])
                      continue;
                  ios_t *srctp = ios_file(&srctext, depstr, 1, 0, 0, 0);
                  if (!srctp) {
                      jl_printf(JL_STDERR, "WARNING: could not cache source text for \"%s\".\n",
                                jl_string_data(dep));
                      continue;
                  }
                  size_t slen = jl_string_len(dep);
                  write_int32(&f, slen);
                  ios_write(&f, depstr, slen);
                  posfile = ios_pos(&f);
                  write_uint64(&f, 0);   // placeholder for length of this file in bytes
                  uint64_t filelen = (uint64_t) ios_copyall(&f, &srctext);
                  ios_close(&srctext);
                  ios_seek(&f, posfile);
                  write_uint64(&f, filelen);
                  ios_seek_end(&f);
              }
          }
      }
      write_int32(&f, 0); // mark the end of the source text
      ios_close(&f);
  
      JL_GC_POP();
      if (jl_fs_rename(tmpfname, fname) < 0) {
          jl_printf(JL_STDERR, "Cannot write cache file \"%s\".\n", fname);
          return 1;
      }
  
      return 0;
  }
  
  #ifndef JL_NDEBUG
  // skip the performance optimizations of jl_types_equal and just use subtyping directly
  // one of these types is invalid - that's why we're doing the recache type operation
  static int jl_invalid_types_equal(jl_datatype_t *a, jl_datatype_t *b)
  {
      return jl_subtype((jl_value_t*)a, (jl_value_t*)b) && jl_subtype((jl_value_t*)b, (jl_value_t*)a);
  }
  #endif
  
  static jl_datatype_t *jl_recache_type(jl_datatype_t *dt, size_t start, jl_value_t *v) JL_GC_DISABLED
  {
      if (v == NULL)
          v = dt->instance; // the instance before unique'ing
      jl_svec_t *tt = dt->parameters;
      if (dt->uid == 0 || dt->uid == -1) {
          // recache all type parameters
          size_t i, l = jl_svec_len(tt);
          for (i = 0; i < l; i++) {
              jl_datatype_t *p = (jl_datatype_t*)jl_svecref(tt, i);
              if (jl_is_datatype(p)) {
                  if (p->uid == -1 || p->uid == 0) {
                      jl_datatype_t *cachep = jl_recache_type(p, start, NULL);
                      if (p != cachep) {
                          assert(jl_invalid_types_equal(p, cachep));
                          jl_svecset(tt, i, cachep);
                      }
                  }
              }
              else {
                  jl_datatype_t *tp = (jl_datatype_t*)jl_typeof(p);
                  assert(tp->uid != 0);
                  if (tp->uid == -1) {
                      tp = jl_recache_type(tp, start, NULL);
                  }
                  if (tp->instance && (jl_value_t*)p != tp->instance)
                      jl_svecset(tt, i, tp->instance);
              }
          }
      }
  
      jl_datatype_t *t; // the type after unique'ing
      if (dt->uid == 0) {
          return dt;
      }
      else if (dt->uid == -1) {
          if (jl_svec_len(tt) == 0) { // jl_cache_type doesn't work if length(parameters) == 0
              dt->uid = jl_assign_type_uid();
              t = dt;
          }
          else {
              dt->uid = 0;
              t = (jl_datatype_t*)jl_cache_type_(dt);
              assert(jl_invalid_types_equal(t, dt));
          }
      }
      else {
          t = dt;
      }
      assert(t->uid != 0);
      if (t == dt && v == NULL)
          return t;
      // delete / replace any other usages of this type in the backref list
      // with the newly constructed object
      size_t i = start;
      while (i < flagref_list.len) {
          jl_value_t **loc = (jl_value_t**)flagref_list.items[i + 0];
          int offs = (int)(intptr_t)flagref_list.items[i + 1];
          jl_value_t *o = loc ? *loc : (jl_value_t*)backref_list.items[offs];
          if ((jl_value_t*)dt == o) {
              if (t != dt) {
                  if (loc)
                      *loc = (jl_value_t*)t;
                  if (offs > 0)
                      backref_list.items[offs] = t;
              }
          }
          else if (v == o) {
              if (t->instance != v) {
                  assert(loc);
                  *loc = t->instance;
                  if (offs > 0)
                      backref_list.items[offs] = t->instance;
              }
          }
          else {
              i += 2;
              continue;
          }
          // delete this item from the flagref list, so it won't be re-encountered later
          flagref_list.len -= 2;
          if (i >= flagref_list.len)
              break;
          flagref_list.items[i + 0] = flagref_list.items[flagref_list.len + 0];
          flagref_list.items[i + 1] = flagref_list.items[flagref_list.len + 1];
      }
      return t;
  }
  
  static void jl_recache_types(void) JL_GC_DISABLED
  {
      size_t i = 0;
      while (i < flagref_list.len) {
          jl_value_t **loc = (jl_value_t**)flagref_list.items[i + 0];
          int offs = (int)(intptr_t)flagref_list.items[i + 1];
          jl_value_t *o = loc ? *loc : (jl_value_t*)backref_list.items[offs];
          if (jl_is_method(o) || jl_is_method_instance(o)) {
              i += 2;
          }
          else {
              jl_value_t *v;
              jl_datatype_t *dt, *t;
              if (jl_is_datatype(o)) {
                  dt = (jl_datatype_t*)o;
                  v = dt->instance;
                  t = dt->uid == -1 ? jl_recache_type(dt, i + 2, NULL) : dt;
              }
              else {
                  dt = (jl_datatype_t*)jl_typeof(o);
                  v = o;
                  assert(dt->instance);
                  t = jl_recache_type(dt, i + 2, v);
              }
              assert(dt);
              if (t != dt) {
                  assert(!type_in_worklist(t));
                  jl_set_typeof(dt, (void*)(intptr_t)0x10); // invalidate the old datatype to help catch errors
                  if ((jl_value_t*)dt == o) {
                      if (loc)
                          *loc = (jl_value_t*)t;
                      if (offs > 0)
                          backref_list.items[offs] = t;
                  }
              }
              if (t->instance != v) {
                  jl_set_typeof(v, (void*)(intptr_t)0x20); // invalidate the old value to help catch errors
                  if (v == o) {
                      assert(loc);
                      *loc = t->instance;
                      if (offs > 0)
                          backref_list.items[offs] = t->instance;
                  }
              }
              // delete this item from the flagref list, so it won't be re-encountered later
              flagref_list.len -= 2;
              if (i >= flagref_list.len)
                  break;
              flagref_list.items[i + 0] = flagref_list.items[flagref_list.len + 0];
              flagref_list.items[i + 1] = flagref_list.items[flagref_list.len + 1];
          }
      }
  }
  
  static void jl_update_backref_list(jl_value_t *old, jl_value_t *_new, size_t start)
  {
      // update the backref list
      size_t i = start;
      while (i < flagref_list.len) {
          jl_value_t **loc = (jl_value_t**)flagref_list.items[i + 0];
          int offs = (int)(intptr_t)flagref_list.items[i + 1];
          jl_value_t *v = loc ? *loc : (jl_value_t*)backref_list.items[offs];
          if ((jl_value_t*)v == old) { // same item, update this entry
              if (loc)
                  *loc = (jl_value_t*)_new;
              if (offs > 0)
                  backref_list.items[offs] = _new;
              // delete this item from the flagref list, so it won't be re-encountered later
              flagref_list.len -= 2;
              if (i >= flagref_list.len)
                  break;
              flagref_list.items[i + 0] = flagref_list.items[flagref_list.len + 0];
              flagref_list.items[i + 1] = flagref_list.items[flagref_list.len + 1];
          }
          else {
              i += 2;
          }
      }
  }
  
  // repeatedly look up older methods until we come to one that existed
  // at the time this module was serialized (e.g. ignoring deletion)
  static jl_method_t *jl_lookup_method_worldset(jl_methtable_t *mt, jl_datatype_t *sig, arraylist_t *dependent_worlds)
  {
      size_t world = jl_world_counter;
      jl_typemap_entry_t *entry;
      while (1) {
          entry = jl_typemap_assoc_by_type(
              mt->defs, (jl_value_t*)sig, NULL, /*subtype*/0, /*offs*/0, world, /*max_world_mask*/(~(size_t)0) >> 1);
          assert(entry);
          jl_method_t *_new = (jl_method_t*)entry->func.value;
          world = lowerbound_dependent_world_set(_new->primary_world, dependent_worlds);
          if (world == _new->primary_world) {
              return _new;
          }
      }
  }
  
  static jl_method_t *jl_recache_method(jl_method_t *m, size_t start, arraylist_t *dependent_worlds)
  {
      jl_datatype_t *sig = (jl_datatype_t*)m->sig;
      jl_methtable_t *mt = jl_method_table_for((jl_value_t*)m->sig);
      assert((jl_value_t*)mt != jl_nothing);
      jl_set_typeof(m, (void*)(intptr_t)0x30); // invalidate the old value to help catch errors
      jl_method_t *_new = jl_lookup_method_worldset(mt, sig, dependent_worlds);
      jl_update_backref_list((jl_value_t*)m, (jl_value_t*)_new, start);
      return _new;
  }
  
  static jl_method_instance_t *jl_recache_method_instance(jl_method_instance_t *mi, size_t start, arraylist_t *dependent_worlds)
  {
      jl_datatype_t *sig = (jl_datatype_t*)mi->def.value;
      assert(jl_is_datatype(sig) || jl_is_unionall(sig));
      jl_methtable_t *mt = jl_method_table_for((jl_value_t*)sig);
      assert((jl_value_t*)mt != jl_nothing);
      jl_method_t *m = jl_lookup_method_worldset(mt, sig, dependent_worlds);
      jl_datatype_t *argtypes = (jl_datatype_t*)mi->specTypes;
      jl_set_typeof(mi, (void*)(intptr_t)0x40); // invalidate the old value to help catch errors
      jl_svec_t *env = jl_emptysvec;
      jl_value_t *ti = jl_type_intersection_env((jl_value_t*)argtypes, (jl_value_t*)m->sig, &env);
      //assert(ti != jl_bottom_type); (void)ti;
      if (ti == jl_bottom_type)
          env = jl_emptysvec; // the intersection may fail now if the type system had made an incorrect subtype env in the past
      jl_method_instance_t *_new = jl_specializations_get_linfo(m, (jl_value_t*)argtypes, env);
      jl_update_backref_list((jl_value_t*)mi, (jl_value_t*)_new, start);
      return _new;
  }
  
  static void jl_recache_other(arraylist_t *dependent_worlds)
  {
      size_t i = 0;
      while (i < flagref_list.len) {
          jl_value_t **loc = (jl_value_t**)flagref_list.items[i + 0];
          int offs = (int)(intptr_t)flagref_list.items[i + 1];
          jl_value_t *_new, *o = loc ? *loc : (jl_value_t*)backref_list.items[offs];
          i += 2;
          if (jl_is_method(o)) {
              // lookup the real Method based on the placeholder sig
              _new = (jl_value_t*)jl_recache_method((jl_method_t*)o, i, dependent_worlds);
          }
          else if (jl_is_method_instance(o)) {
              // lookup the real MethodInstance based on the placeholder specTypes
              _new = (jl_value_t*)jl_recache_method_instance((jl_method_instance_t*)o, i, dependent_worlds);
          }
          else {
              abort();
          }
          if (loc)
              *loc = _new;
          if (offs > 0)
              backref_list.items[offs] = _new;
      }
  }
  
  extern tracer_cb jl_newmeth_tracer;
  static int trace_method(jl_typemap_entry_t *entry, void *closure)
  {
      jl_call_tracer(jl_newmeth_tracer, (jl_value_t*)entry->func.method);
      return 1;
  }
  
  static jl_value_t *_jl_restore_incremental(ios_t *f, jl_array_t *mod_array)
  {
      JL_TIMING(LOAD_MODULE);
      jl_ptls_t ptls = jl_get_ptls_states();
      if (ios_eof(f) || !jl_read_verify_header(f)) {
          ios_close(f);
          return jl_get_exceptionf(jl_errorexception_type,
                  "Precompile file header verification checks failed.");
      }
      { // skip past the mod list
          size_t len;
          while ((len = read_int32(f)))
              ios_skip(f, len + 3 * sizeof(uint64_t));
      }
      { // skip past the dependency list
          size_t deplen = read_uint64(f);
          ios_skip(f, deplen);
      }
  
      jl_bigint_type = jl_base_module ? jl_get_global(jl_base_module, jl_symbol("BigInt")) : NULL;
      if (jl_bigint_type) {
          gmp_limb_size = jl_unbox_long(jl_get_global((jl_module_t*)jl_get_global(jl_base_module, jl_symbol("GMP")),
                                                      jl_symbol("BITS_PER_LIMB"))) / 8;
      }
  
      // list of world counters of incremental dependencies
      arraylist_t dependent_worlds;
      arraylist_new(&dependent_worlds, 0);
  
      // verify that the system state is valid
      jl_value_t *verify_fail = read_verify_mod_list(f, &dependent_worlds, mod_array);
      if (verify_fail) {
          arraylist_free(&dependent_worlds);
          ios_close(f);
          return verify_fail;
      }
  
      // prepare to deserialize
      int en = jl_gc_enable(0);
      jl_gc_enable_finalizers(ptls, 0);
      ++jl_world_counter; // reserve a world age for the deserialization
  
      arraylist_new(&backref_list, 4000);
      arraylist_push(&backref_list, jl_main_module);
      arraylist_new(&flagref_list, 0);
      arraylist_push(&dependent_worlds, (void*)jl_world_counter);
      arraylist_push(&dependent_worlds, (void*)jl_main_module->primary_world);
      qsort(dependent_worlds.items, dependent_worlds.len, sizeof(size_t), size_isgreater);
  
      jl_serializer_state s = {
          f, MODE_MODULE,
          NULL,
          ptls,
          mod_array
      };
      jl_array_t *restored = (jl_array_t*)jl_deserialize_value(&s, (jl_value_t**)&restored);
      serializer_worklist = restored;
  
      // get list of external generic functions
      jl_value_t *external_methods = jl_deserialize_value(&s, &external_methods);
      jl_value_t *external_backedges = jl_deserialize_value(&s, &external_backedges);
  
      arraylist_t *tracee_list = NULL;
      if (jl_newmeth_tracer)
          tracee_list = arraylist_new((arraylist_t*)malloc(sizeof(arraylist_t)), 0);
  
      // at this point, the AST is fully reconstructed, but still completely disconnected
      // now all of the interconnects will be created
      jl_recache_types(); // make all of the types identities correct
      jl_insert_methods((jl_array_t*)external_methods); // hook up methods of external generic functions (needs to be after recache types)
      jl_recache_other(&dependent_worlds); // make all of the other objects identities correct (needs to be after insert methods)
      jl_array_t *init_order = jl_finalize_deserializer(&s, tracee_list); // done with f and s (needs to be after recache)
  
      JL_GC_PUSH3(&init_order, &restored, &external_backedges);
      jl_gc_enable(en); // subtyping can allocate a lot, not valid before recache-other
  
      jl_insert_backedges((jl_array_t*)external_backedges, &dependent_worlds); // restore external backedges (needs to be last)
  
      serializer_worklist = NULL;
      arraylist_free(&flagref_list);
      arraylist_free(&backref_list);
      arraylist_free(&dependent_worlds);
      ios_close(f);
  
      jl_gc_enable_finalizers(ptls, 1); // make sure we don't run any Julia code concurrently before this point
      if (tracee_list) {
          jl_methtable_t *mt;
          while ((mt = (jl_methtable_t*)arraylist_pop(tracee_list)) != NULL) {
              JL_GC_PROMISE_ROOTED(mt);
              jl_typemap_visitor(mt->defs, trace_method, NULL);
          }
          arraylist_free(tracee_list);
          free(tracee_list);
      }
      jl_value_t *ret = (jl_value_t*)jl_svec(2, restored, init_order);
      JL_GC_POP();
  
      return (jl_value_t*)ret;
  }
  
  JL_DLLEXPORT jl_value_t *jl_restore_incremental_from_buf(const char *buf, size_t sz, jl_array_t *mod_array)
  {
      ios_t f;
      ios_static_buffer(&f, (char*)buf, sz);
      return _jl_restore_incremental(&f, mod_array);
  }
  
  JL_DLLEXPORT jl_value_t *jl_restore_incremental(const char *fname, jl_array_t *mod_array)
  {
      ios_t f;
      if (ios_file(&f, fname, 1, 0, 0, 0) == NULL) {
          return jl_get_exceptionf(jl_errorexception_type,
              "Cache file \"%s\" not found.\n", fname);
      }
      return _jl_restore_incremental(&f, mod_array);
  }
  
  // --- init ---
  
  void jl_init_serializer(void)
  {
      jl_ptls_t ptls = jl_get_ptls_states();
      htable_new(&ser_tag, 0);
      htable_new(&common_symbol_tag, 0);
      htable_new(&backref_table, 0);
  
      void *vals[] = { jl_emptysvec, jl_emptytuple, jl_false, jl_true, jl_nothing, jl_any_type,
                       call_sym, invoke_sym, goto_ifnot_sym, return_sym, jl_symbol("tuple"),
                       unreachable_sym, jl_an_empty_string,
  
                       // empirical list of very common symbols
                       #include "common_symbols1.inc"
  
                       jl_box_int32(0), jl_box_int32(1), jl_box_int32(2),
                       jl_box_int32(3), jl_box_int32(4), jl_box_int32(5),
                       jl_box_int32(6), jl_box_int32(7), jl_box_int32(8),
                       jl_box_int32(9), jl_box_int32(10), jl_box_int32(11),
                       jl_box_int32(12), jl_box_int32(13), jl_box_int32(14),
                       jl_box_int32(15), jl_box_int32(16), jl_box_int32(17),
                       jl_box_int32(18), jl_box_int32(19), jl_box_int32(20),
                       jl_box_int32(21),
  
                       jl_box_int64(0), jl_box_int64(1), jl_box_int64(2),
                       jl_box_int64(3), jl_box_int64(4), jl_box_int64(5),
                       jl_box_int64(6), jl_box_int64(7), jl_box_int64(8),
                       jl_box_int64(9), jl_box_int64(10), jl_box_int64(11),
                       jl_box_int64(12), jl_box_int64(13), jl_box_int64(14),
                       jl_box_int64(15), jl_box_int64(16), jl_box_int64(17),
                       jl_box_int64(18), jl_box_int64(19), jl_box_int64(20),
                       jl_box_int64(21), jl_box_int64(22),
  
                       jl_bool_type, jl_linenumbernode_type, jl_pinode_type,
                       jl_upsilonnode_type, jl_type_type, jl_bottom_type, jl_ref_type,
                       jl_pointer_type, jl_vararg_type, jl_abstractarray_type, jl_void_type,
                       jl_densearray_type, jl_function_type, jl_typename_type,
                       jl_builtin_type, jl_task_type, jl_uniontype_type, jl_typetype_type,
                       jl_array_any_type, jl_intrinsic_type,
                       jl_abstractslot_type, jl_methtable_type, jl_typemap_level_type,
                       jl_voidpointer_type, jl_newvarnode_type, jl_abstractstring_type,
                       jl_array_symbol_type, jl_anytuple_type, jl_tparam0(jl_anytuple_type),
                       jl_emptytuple_type, jl_array_uint8_type, jl_code_info_type,
                       jl_typeofbottom_type, jl_typeofbottom_type->super,
                       jl_namedtuple_type, jl_array_int32_type,
                       jl_typedslot_type, jl_uint32_type, jl_uint64_type,
                       jl_type_type_mt, jl_nonfunction_mt,
  
                       ptls->root_task,
  
                       NULL };
  
      // more common symbols, less common than those above. will get 2-byte encodings.
      void *common_symbols[] = {
          #include "common_symbols2.inc"
          NULL
      };
  
      deser_tag[TAG_SYMBOL] = (jl_value_t*)jl_symbol_type;
      deser_tag[TAG_SSAVALUE] = (jl_value_t*)jl_ssavalue_type;
      deser_tag[TAG_DATATYPE] = (jl_value_t*)jl_datatype_type;
      deser_tag[TAG_SLOTNUMBER] = (jl_value_t*)jl_slotnumber_type;
      deser_tag[TAG_SVEC] = (jl_value_t*)jl_simplevector_type;
      deser_tag[TAG_ARRAY] = (jl_value_t*)jl_array_type;
      deser_tag[TAG_EXPR] = (jl_value_t*)jl_expr_type;
      deser_tag[TAG_PHINODE] = (jl_value_t*)jl_phinode_type;
      deser_tag[TAG_PHICNODE] = (jl_value_t*)jl_phicnode_type;
      deser_tag[TAG_STRING] = (jl_value_t*)jl_string_type;
      deser_tag[TAG_TYPEMAP_ENTRY] = (jl_value_t*)jl_typemap_entry_type;
      deser_tag[TAG_MODULE] = (jl_value_t*)jl_module_type;
      deser_tag[TAG_TVAR] = (jl_value_t*)jl_tvar_type;
      deser_tag[TAG_METHOD_INSTANCE] = (jl_value_t*)jl_method_instance_type;
      deser_tag[TAG_METHOD] = (jl_value_t*)jl_method_type;
      deser_tag[TAG_CODE_INSTANCE] = (jl_value_t*)jl_code_instance_type;
      deser_tag[TAG_GLOBALREF] = (jl_value_t*)jl_globalref_type;
      deser_tag[TAG_INT32] = (jl_value_t*)jl_int32_type;
      deser_tag[TAG_INT64] = (jl_value_t*)jl_int64_type;
      deser_tag[TAG_UINT8] = (jl_value_t*)jl_uint8_type;
      deser_tag[TAG_LINEINFO] = (jl_value_t*)jl_lineinfonode_type;
      deser_tag[TAG_UNIONALL] = (jl_value_t*)jl_unionall_type;
      deser_tag[TAG_GOTONODE] = (jl_value_t*)jl_gotonode_type;
      deser_tag[TAG_QUOTENODE] = (jl_value_t*)jl_quotenode_type;
  
      intptr_t i = 0;
      while (vals[i] != NULL) {
          deser_tag[LAST_TAG+1+i] = (jl_value_t*)vals[i];
          i += 1;
      }
      assert(LAST_TAG+1+i < 256);
  
      for (i = 2; i < 256; i++) {
          if (deser_tag[i])
              ptrhash_put(&ser_tag, deser_tag[i], (void*)i);
      }
  
      i = 2;
      while (common_symbols[i-2] != NULL) {
          ptrhash_put(&common_symbol_tag, common_symbols[i-2], (void*)i);
          deser_symbols[i] = (jl_value_t*)common_symbols[i-2];
          i += 1;
      }
      assert(i <= 256);
  
      arraylist_new(&builtin_typenames, 0);
      arraylist_push(&builtin_typenames, jl_array_typename);
      arraylist_push(&builtin_typenames, ((jl_datatype_t*)jl_ref_type->body)->name);
      arraylist_push(&builtin_typenames, jl_pointer_typename);
      arraylist_push(&builtin_typenames, jl_type_typename);
      arraylist_push(&builtin_typenames, ((jl_datatype_t*)jl_unwrap_unionall((jl_value_t*)jl_abstractarray_type))->name);
      arraylist_push(&builtin_typenames, ((jl_datatype_t*)jl_unwrap_unionall((jl_value_t*)jl_densearray_type))->name);
      arraylist_push(&builtin_typenames, jl_tuple_typename);
      arraylist_push(&builtin_typenames, jl_vararg_typename);
      arraylist_push(&builtin_typenames, jl_namedtuple_typename);
  }
  
  #ifdef __cplusplus
  }
  #endif
  