// Copyright (c) 2025 Epic Games Tools
// Licensed under the MIT license (https://opensource.org/license/mit/)

////////////////////////////////

internal LNK_Symbol *
lnk_make_defined_symbol(Arena *arena, String8 name, struct LNK_Obj *obj, U32 symbol_idx)
{
  LNK_Symbol *symbol = push_array(arena, LNK_Symbol, 1);
  symbol->name = name;
  symbol->type = LNK_Symbol_Defined;
  symbol->u.defined.obj = obj;
  symbol->u.defined.symbol_idx = symbol_idx;
  return symbol;
}

internal LNK_Symbol *
lnk_make_lib_symbol(Arena *arena, String8 name, struct LNK_Lib *lib, U64 member_offset)
{
  LNK_Symbol *symbol = push_array(arena, LNK_Symbol, 1);
  symbol->name = name;
  symbol->type = LNK_Symbol_Lib;
  symbol->u.lib.lib = lib;
  symbol->u.lib.member_offset = member_offset;
  return symbol;
}

internal LNK_Symbol *
lnk_make_undefined_symbol(Arena *arena, String8 name, struct LNK_Obj *obj)
{
  LNK_Symbol *symbol = push_array(arena, LNK_Symbol, 1);
  symbol->name = name;
  symbol->type = LNK_Symbol_Undefined;
  symbol->u.undef.obj = obj;
  return symbol;
}

internal LNK_Symbol *
lnk_make_import_symbol(Arena *arena, String8 name, String8 import_header)
{
  LNK_Symbol *symbol = push_array(arena, LNK_Symbol, 1);
  symbol->name = name;
  symbol->type = LNK_Symbol_Import;
  symbol->u.import.import_header = import_header;
  return symbol;
}

////////////////////////////////

internal void
lnk_symbol_list_push_node(LNK_SymbolList *list, LNK_SymbolNode *node)
{
  SLLQueuePush(list->first, list->last, node);
  list->count += 1;
}

internal LNK_SymbolNode *
lnk_symbol_list_push(Arena *arena, LNK_SymbolList *list, LNK_Symbol *symbol)
{
  LNK_SymbolNode *node = push_array(arena, LNK_SymbolNode, 1);
  node->data           = symbol;
  lnk_symbol_list_push_node(list, node);
  return node;
}

internal void
lnk_symbol_list_concat_in_place(LNK_SymbolList *list, LNK_SymbolList *to_concat)
{
  SLLConcatInPlace(list, to_concat);
}

internal LNK_SymbolList
lnk_symbol_list_from_array(Arena *arena, LNK_SymbolArray arr)
{
  LNK_SymbolList list = {0};
  LNK_SymbolNode *node_arr = push_array_no_zero(arena, LNK_SymbolNode, arr.count);
  for (U64 i = 0; i < arr.count; i += 1) {
    LNK_SymbolNode *node = &node_arr[i];
    node->next           = 0;
    node->data           = &arr.v[i];
    lnk_symbol_list_push_node(&list, node);
  }
  return list;
}

internal LNK_SymbolNodeArray
lnk_symbol_node_array_from_list(Arena *arena, LNK_SymbolList list)
{
  LNK_SymbolNodeArray result = {0};
  result.count               = 0;
  result.v                   = push_array_no_zero(arena, LNK_SymbolNode *, list.count);
  for (LNK_SymbolNode *i = list.first; i != 0; i = i->next, ++result.count) {
    result.v[result.count] = i;
  }
  return result;
}

internal LNK_SymbolArray
lnk_symbol_array_from_list(Arena *arena, LNK_SymbolList list)
{
  LNK_SymbolArray arr = {0};
  arr.count           = 0;
  arr.v               = push_array_no_zero(arena, LNK_Symbol, list.count);
  for (LNK_SymbolNode *node = list.first; node != 0; node = node->next) {
    arr.v[arr.count++] = *node->data;
  }
  return arr;
}

////////////////////////////////

internal ISectOff
lnk_sc_from_symbol(LNK_Symbol *symbol)
{
  COFF_ParsedSymbol parsed_symbol = lnk_parsed_symbol_from_coff_symbol_idx(symbol->u.defined.obj, symbol->u.defined.symbol_idx);

  ISectOff sc = {0};
  sc.isect    = parsed_symbol.section_number;
  sc.off      = parsed_symbol.value;

  return sc;
}

internal U64
lnk_isect_from_symbol(LNK_Symbol *symbol)
{
  return lnk_sc_from_symbol(symbol).isect;
}

internal U64
lnk_sect_off_from_symbol(LNK_Symbol *symbol)
{
  return lnk_sc_from_symbol(symbol).off;
}

internal U64
lnk_virt_off_from_symbol(COFF_SectionHeader **section_table, LNK_Symbol *symbol)
{
  ISectOff sc   = lnk_sc_from_symbol(symbol);
  U64      voff = section_table[sc.isect]->voff + sc.off;
  return voff;
}

internal U64
lnk_file_off_from_symbol(COFF_SectionHeader **section_table, LNK_Symbol *symbol)
{
  ISectOff sc   = lnk_sc_from_symbol(symbol);
  U64      foff = section_table[sc.isect]->foff + sc.off;
  return foff;
}

////////////////////////////////

internal LNK_SymbolHashTrie *
lnk_symbol_hash_trie_chunk_list_push(Arena *arena, LNK_SymbolHashTrieChunkList *list, U64 cap)
{
  if (list->last == 0 || list->last->count >= list->last->cap) {
    LNK_SymbolHashTrieChunk *chunk = push_array(arena, LNK_SymbolHashTrieChunk, 1);
    chunk->cap                     = cap;
    chunk->v                       = push_array_no_zero(arena, LNK_SymbolHashTrie, cap);
    SLLQueuePush(list->first, list->last, chunk);
    ++list->count;
  }

  LNK_SymbolHashTrie *result = &list->last->v[list->last->count++];
  return result;
}

internal void
lnk_error_multiply_defined_symbol(LNK_Symbol *dst, LNK_Symbol *src)
{
  lnk_error_obj(LNK_Error_MultiplyDefinedSymbol, dst->u.defined.obj, "symbol \"%S\" (No. %#x) is multiply defined in %S (No. %#x)", dst->name, dst->u.defined.symbol_idx, src->u.defined.obj->path, src->u.defined.symbol_idx);
}

internal B32
lnk_can_replace_symbol(LNK_Symbol *dst, LNK_Symbol *src)
{
  Assert(src->type != LNK_Symbol_Undefined);
  Assert(dst != src);
  Assert(str8_match(dst->name, src->name, 0));

  B32 can_replace = 0;


  // lib vs lib
  if (dst->type == LNK_Symbol_Lib && src->type == LNK_Symbol_Lib) {
    // link.exe picks symbol from lib that is discovered first
    can_replace = src->u.lib.lib->input_idx < dst->u.lib.lib->input_idx;
  }
  else if (dst->type == LNK_Symbol_Import) { 
    AssertAlways(src->type != LNK_Symbol_Import);
    can_replace = 1;
  }
  // defined vs defined
  else if (dst->type == LNK_Symbol_Defined && src->type == LNK_Symbol_Defined) {
    LNK_Obj *dst_obj = dst->u.defined.obj;
    LNK_Obj *src_obj = src->u.defined.obj;

    COFF_ParsedSymbol dst_parsed = lnk_parsed_symbol_from_coff_symbol_idx(dst->u.defined.obj, dst->u.defined.symbol_idx);
    COFF_ParsedSymbol src_parsed = lnk_parsed_symbol_from_coff_symbol_idx(src->u.defined.obj, src->u.defined.symbol_idx);

    COFF_SymbolValueInterpType dst_interp = coff_interp_symbol(dst_parsed.section_number, dst_parsed.value, dst_parsed.storage_class);
    COFF_SymbolValueInterpType src_interp = coff_interp_symbol(src_parsed.section_number, src_parsed.value, src_parsed.storage_class);

    if (dst_interp == COFF_SymbolValueInterp_Regular && src_interp == COFF_SymbolValueInterp_Abs) {
      lnk_error_multiply_defined_symbol(dst, src);
    }
    // abs vs regular
    else if ((dst_interp == COFF_SymbolValueInterp_Abs && src_interp == COFF_SymbolValueInterp_Regular) ||
             (dst_interp == COFF_SymbolValueInterp_Regular && src_interp == COFF_SymbolValueInterp_Abs)) {
      lnk_error_multiply_defined_symbol(dst, src);
    }
    // abs vs common
    else if (dst_interp == COFF_SymbolValueInterp_Abs && src_interp == COFF_SymbolValueInterp_Common) {
      if (dst->u.defined.obj->input_idx < src->u.defined.obj->input_idx) {
        can_replace = 1;
      } else {
        lnk_error_multiply_defined_symbol(dst, src);
      }
    }
    // common vs abs
    else if (dst_interp == COFF_SymbolValueInterp_Common && src_interp == COFF_SymbolValueInterp_Abs) {
      if (dst->u.defined.obj->input_idx < src->u.defined.obj->input_idx) {
        lnk_error_multiply_defined_symbol(dst, src);
      }
    }
    // weak vs weak
    else if (dst_interp == COFF_SymbolValueInterp_Weak && src_interp == COFF_SymbolValueInterp_Weak) {
    }
    // weak vs abs
    else if (dst_interp == COFF_SymbolValueInterp_Weak && src_interp == COFF_SymbolValueInterp_Abs) {
      can_replace = 1;
    }
    // abs vs weak
    else if (dst_interp == COFF_SymbolValueInterp_Abs && src_interp == COFF_SymbolValueInterp_Weak) {
      can_replace = 0;
    }
    // weak vs (regular, common, abs)
    else if (dst_interp == COFF_SymbolValueInterp_Weak && (src_interp == COFF_SymbolValueInterp_Regular || src_interp == COFF_SymbolValueInterp_Common || src_interp == COFF_SymbolValueInterp_Abs)) {
      can_replace = 1;
    }
    // (regular, common) vs (regular, common)
    else if ((dst_interp == COFF_SymbolValueInterp_Regular || dst_interp == COFF_SymbolValueInterp_Common) && (src_interp == COFF_SymbolValueInterp_Regular || src_interp == COFF_SymbolValueInterp_Common)) {
      B32                   dst_is_comdat = 0;
      COFF_ComdatSelectType dst_select;
      U32                   dst_section_length;
      U32                   dst_check_sum;
      if (dst_interp == COFF_SymbolValueInterp_Regular) {
        U32 dst_comdat_symbol_idx = dst_obj->comdats[dst_parsed.section_number-1];
        if (dst_comdat_symbol_idx != max_U32) {
          COFF_ParsedSymbol secdef = lnk_parsed_symbol_from_coff_symbol_idx(dst_obj, dst_comdat_symbol_idx);
          coff_parse_secdef(secdef, dst_obj->header.is_big_obj, &dst_select, 0, &dst_section_length, &dst_check_sum);
          dst_is_comdat = 1;
        }
      } else if (dst_interp == COFF_SymbolValueInterp_Common) {
        dst_select         = COFF_ComdatSelect_Largest;
        dst_section_length = dst_parsed.value;
        dst_check_sum      = 0;
        dst_is_comdat      = 1;
      }

      B32                   src_is_comdat = 0;
      COFF_ComdatSelectType src_select;
      U32                   src_section_length;
      U32                   src_check_sum;
      if (src_interp == COFF_SymbolValueInterp_Regular) {
        U32 src_comdat_symbol_idx = src_obj->comdats[src_parsed.section_number-1];
        if (src_comdat_symbol_idx != max_U32) {
          COFF_ParsedSymbol secdef = lnk_parsed_symbol_from_coff_symbol_idx(src_obj, src_comdat_symbol_idx);
          coff_parse_secdef(secdef, src_obj->header.is_big_obj, &src_select, 0, &src_section_length, &src_check_sum);
          src_is_comdat = 1;
        }
      } else if (src_interp == COFF_SymbolValueInterp_Common) {
        src_select         = COFF_ComdatSelect_Largest;
        src_section_length = src_parsed.value;
        src_check_sum      = 0;
        src_is_comdat      = 1;
      }

      // regular non-comdat vs communal
      if (dst_interp == COFF_SymbolValueInterp_Regular && !dst_is_comdat &&
          src_interp == COFF_SymbolValueInterp_Common) {
        can_replace = 0;
      }
      // communal vs regular non-comdat
      else if (dst_interp == COFF_SymbolValueInterp_Common && 
               src_interp == COFF_SymbolValueInterp_Regular && !src_is_comdat) {
        can_replace = 1;
      }
      // handle COMDATs
      else if (dst_is_comdat && src_is_comdat) {
        // handle objs compiled with /GR- and /GR
        if ((src_select == COFF_ComdatSelect_Any && dst_select == COFF_ComdatSelect_Largest)) {
          src_select = COFF_ComdatSelect_Largest;
        }
        if (src_select == COFF_ComdatSelect_Largest && dst_select == COFF_ComdatSelect_Any) {
          dst_select = COFF_ComdatSelect_Largest;
        }

        if (src_select == dst_select) {
          switch (src_select) {
          case COFF_ComdatSelect_Null:
          case COFF_ComdatSelect_Any: {
            if (src_section_length == dst_section_length) {
              can_replace = src_obj->input_idx < dst_obj->input_idx;
            } else {
              // both COMDATs are valid but to get smaller exe pick smallest
              can_replace = src_section_length < dst_section_length;
            }
          } break;
          case COFF_ComdatSelect_NoDuplicates: {
            lnk_error_multiply_defined_symbol(dst, src);
          } break;
          case COFF_ComdatSelect_SameSize: {
            if (dst_section_length == src_section_length) {
              can_replace = src_obj->input_idx < dst_obj->input_idx;
            } else {
              lnk_error_multiply_defined_symbol(dst, src);
            }
          } break;
          case COFF_ComdatSelect_ExactMatch: {
            COFF_SectionHeader *dst_section_table = (COFF_SectionHeader *)str8_substr(dst_obj->data, dst_obj->header.section_table_range).str;
            COFF_SectionHeader *src_section_table = (COFF_SectionHeader *)str8_substr(src_obj->data, src_obj->header.section_table_range).str;
            COFF_SectionHeader *dst_sect_header = &dst_section_table[dst_parsed.section_number-1];
            COFF_SectionHeader *src_sect_header = &src_section_table[src_parsed.section_number-1];
            String8 dst_data = str8_substr(dst_obj->data, rng_1u64(dst_sect_header->foff, dst_sect_header->foff + dst_sect_header->fsize));
            String8 src_data = str8_substr(src_obj->data, rng_1u64(src_sect_header->foff, src_sect_header->foff + src_sect_header->fsize));
            B32 is_exact_match = 0;
            if (dst_check_sum != 0 && src_check_sum != 0) {
              is_exact_match = dst_check_sum == src_check_sum && str8_match(dst_data, src_data, 0);
            } else {
              is_exact_match = str8_match(dst_data, src_data, 0);
            }

            if (is_exact_match) {
              can_replace = src_obj->input_idx < dst_obj->input_idx;
            } else {
              lnk_error_multiply_defined_symbol(dst, src);
            }
          } break;
          case COFF_ComdatSelect_Largest: {
            if (dst_section_length == src_section_length) {
              can_replace = src_obj->input_idx < dst_obj->input_idx;
            } else {
              can_replace = dst_section_length < src_section_length;
            }
          } break;
          case COFF_ComdatSelect_Associative: {
            // ignore
          } break;
          default: { InvalidPath; }
          }
        } else {
          String8 src_select_str = coff_string_from_comdat_select_type(src_select);
          String8 dst_select_str = coff_string_from_comdat_select_type(dst_select);
          lnk_error_obj(LNK_Warning_UnresolvedComdat, src_obj,
                        "%S: COMDAT selection conflict detected, current selection %S, leader selection %S from %S", 
                        src->name, src_select_str, dst_select_str, dst_obj);
        }
      } else {
        lnk_error_multiply_defined_symbol(dst, src);
      }
    } else {
      lnk_error(LNK_Error_InvalidPath, "unable to find a suitable replacement logic for symbol combination");
    }
  } else {
    lnk_error(LNK_Error_InvalidPath, "unable to find a suitable replacement logic for symbol combination");
  }

  return can_replace;
}

internal void
lnk_on_symbol_replace(LNK_Symbol *dst, LNK_Symbol *src)
{
  Assert(dst != src);
  if (dst->type == LNK_Symbol_Lib && src->type == LNK_Symbol_Lib) {
    dst->u.lib = src->u.lib;
  } else if (dst->type == LNK_Symbol_Defined && src->type == LNK_Symbol_Defined) {
    COFF_ParsedSymbol dst_parsed = lnk_parsed_symbol_from_coff_symbol_idx(dst->u.defined.obj, dst->u.defined.symbol_idx);
    COFF_ParsedSymbol src_parsed = lnk_parsed_symbol_from_coff_symbol_idx(src->u.defined.obj, src->u.defined.symbol_idx);
    COFF_SymbolValueInterpType dst_interp = coff_interp_symbol(dst_parsed.section_number, dst_parsed.value, dst_parsed.storage_class);
    COFF_SymbolValueInterpType src_interp = coff_interp_symbol(src_parsed.section_number, src_parsed.value, src_parsed.storage_class);
    if (dst_interp == COFF_SymbolValueInterp_Regular) {
      COFF_SectionHeader *dst_sect = lnk_coff_section_header_from_section_number(dst->u.defined.obj, dst_parsed.section_number);
      dst_sect->flags |= COFF_SectionFlag_LnkRemove;
      dst->u.defined = src->u.defined;
    }
    if (src_interp == COFF_SymbolValueInterp_Regular) {
      COFF_SectionHeader *src_sect = lnk_coff_section_header_from_section_number(src->u.defined.obj, src_parsed.section_number);
      AssertAlways(~src_sect->flags & COFF_SectionFlag_LnkRemove);
    }
  } else {
    InvalidPath;
  }
}

internal void
lnk_symbol_hash_trie_insert_or_replace(Arena                        *arena,
                                       LNK_SymbolHashTrieChunkList  *chunks,
                                       LNK_SymbolHashTrie          **trie,
                                       U64                           hash,
                                       LNK_Symbol                   *symbol)
{
  LNK_SymbolHashTrie **curr_trie_ptr = trie;
  for (U64 h = hash; ; h <<= 2) {
    // load current pointer
    LNK_SymbolHashTrie *curr_trie = ins_atomic_ptr_eval(curr_trie_ptr);

    if (curr_trie == 0) {
      // init node
      LNK_SymbolHashTrie *new_trie = lnk_symbol_hash_trie_chunk_list_push(arena, chunks, 512);
      new_trie->name               = &symbol->name;
      new_trie->symbol             = symbol;
      MemoryZeroArray(new_trie->child);

      // try to insert new node
      LNK_SymbolHashTrie *cmp = ins_atomic_ptr_eval_cond_assign(curr_trie_ptr, new_trie, curr_trie);

      // was symbol inserted?
      if (cmp == curr_trie) {
        break;
      }

      // rollback chunk list push
      --chunks->last->count;

      // retry insert with trie node from another thread
      curr_trie = cmp;
    }

    // load current symbol
    String8 *curr_name = ins_atomic_ptr_eval(&curr_trie->name);

    if (curr_name && str8_match(*curr_name, symbol->name, 0)) {
      for (LNK_Symbol *src = symbol;;) {
        // try replacing current symbol with zero, otherwise loop back and retry
        LNK_Symbol *leader = ins_atomic_ptr_eval_assign(&curr_trie->symbol, 0);

        // apply replacement
        if (leader) {
          if (lnk_can_replace_symbol(leader, src)) {
            // discard leader
            lnk_on_symbol_replace(leader, src);
            leader = src;
          } else {
            // discard source
            lnk_on_symbol_replace(src, leader);
            src = leader;
          }
        } else {
          leader = src;
        }

        // try replacing symbol, if another thread has already taken the slot, rerun replacement loop again
        LNK_Symbol *was_replaced = ins_atomic_ptr_eval_cond_assign(&curr_trie->symbol, leader, 0);

        // symbol replaced, exit
        if (was_replaced == 0) {
          goto exit;
        }
      }
    }

    // pick child and descend
    curr_trie_ptr = curr_trie->child + (h >> 62);
  }
  exit:;
}

internal LNK_SymbolHashTrie *
lnk_symbol_hash_trie_search(LNK_SymbolHashTrie *trie, U64 hash, String8 name)
{
  LNK_SymbolHashTrie  *result   = 0;
  LNK_SymbolHashTrie **curr_ptr = &trie;
  for (U64 h = hash; ; h <<= 2) {
    LNK_SymbolHashTrie *curr = ins_atomic_ptr_eval(curr_ptr);
    if (curr == 0) {
      break;
    }
    if (curr->symbol) {
      if (str8_match(curr->symbol->name, name, 0)) {
        result = curr;
        break;
      }
    }
    curr_ptr = curr->child + (h >> 62);
  }
  return result;
}

internal void
lnk_symbol_hash_trie_remove(LNK_SymbolHashTrie *trie)
{
  ins_atomic_ptr_eval_assign(&trie->name,   0);
  ins_atomic_ptr_eval_assign(&trie->symbol, 0);
}

////////////////////////////////

internal U64
lnk_symbol_hash(String8 string)
{
  XXH3_state_t hasher; XXH3_64bits_reset(&hasher);
  XXH3_64bits_update(&hasher, &string.size, sizeof(string.size));
  XXH3_64bits_update(&hasher, string.str, string.size);
  XXH64_hash_t result = XXH3_64bits_digest(&hasher);
  return result;
}

internal LNK_SymbolTable *
lnk_symbol_table_init(TP_Arena *arena)
{
  LNK_SymbolTable *symtab = push_array(arena->v[0], LNK_SymbolTable, 1);
  symtab->arena           = arena;
  for (U64 i = 0; i < LNK_SymbolScope_Count; ++i) {
    symtab->chunk_lists[i] = push_array(arena->v[0], LNK_SymbolHashTrieChunkList, arena->count);
  }
  symtab->alt_names = hash_table_init(arena->v[0], 1024);
  return symtab;
}

internal LNK_Symbol *
lnk_symbol_table_search_hash(LNK_SymbolTable *symtab, LNK_SymbolScope scope, U64 hash, String8 name)
{
  LNK_SymbolHashTrie *trie = lnk_symbol_hash_trie_search(symtab->scopes[scope], hash, name);
  if (trie == 0) {
    String8 alt_name = {0};
    if (hash_table_search_string_string(symtab->alt_names, name, &alt_name)) {
      U64 alt_hash = lnk_symbol_hash(alt_name);
      trie = lnk_symbol_hash_trie_search(symtab->scopes[scope], alt_hash, alt_name);
    }
  }
  return trie ? trie->symbol : 0;
}

internal LNK_Symbol *
lnk_symbol_table_search(LNK_SymbolTable *symtab, LNK_SymbolScope scope, String8 name)
{
  U64 hash = lnk_symbol_hash(name);
  return lnk_symbol_table_search_hash(symtab, scope, hash, name);
}

internal LNK_Symbol *
lnk_symbol_table_searchf(LNK_SymbolTable *symtab, LNK_SymbolScope scope, char *fmt, ...)
{
  Temp scratch = scratch_begin(0, 0);
  
  va_list args;
  va_start(args, fmt);
  String8 name = push_str8fv(scratch.arena, fmt, args);
  va_end(args);
  
  LNK_Symbol *symbol = lnk_symbol_table_search(symtab, scope, name);

  scratch_end(scratch);
  return symbol;
}

internal void
lnk_symbol_table_push_(LNK_SymbolTable *symtab, Arena *arena, U64 worker_id, LNK_SymbolScope scope, U64 hash, LNK_Symbol *symbol)
{
  lnk_symbol_hash_trie_insert_or_replace(arena, &symtab->chunk_lists[scope][worker_id], &symtab->scopes[scope], hash, symbol);
}

internal void
lnk_symbol_table_push_hash(LNK_SymbolTable *symtab, U64 hash, LNK_Symbol *symbol)
{
  switch (symbol->type) {
  case LNK_Symbol_Null: break;
  case LNK_Symbol_Defined: { lnk_symbol_table_push_(symtab, symtab->arena->v[0], 0, LNK_SymbolScope_Defined, hash, symbol); } break;
  case LNK_Symbol_Import:  { lnk_symbol_table_push_(symtab, symtab->arena->v[0], 0, LNK_SymbolScope_Import,  hash, symbol); } break;
  case LNK_Symbol_Lib:     { lnk_symbol_table_push_(symtab, symtab->arena->v[0], 0, LNK_SymbolScope_Lib,     hash, symbol); } break;
  default: { InvalidPath; } break;
  }
}

internal void
lnk_symbol_table_push(LNK_SymbolTable *symtab, LNK_Symbol *symbol)
{
  U64 hash = lnk_symbol_hash(symbol->name);
  lnk_symbol_table_push_hash(symtab, hash, symbol);
}

internal void
lnk_symbol_table_remove(LNK_SymbolTable *symtab, LNK_SymbolScope scope, String8 name)
{
  U64                 hash = lnk_symbol_hash(name);
  LNK_SymbolHashTrie *trie = lnk_symbol_hash_trie_search(symtab->scopes[scope], hash, name);
  if (trie) {
    lnk_symbol_hash_trie_remove(trie);
  }
}

internal void
lnk_symbol_table_push_alt_name(LNK_SymbolTable *symtab, LNK_Obj *obj, String8 from, String8 to)
{
  String8 to_extant;
  if (hash_table_search_string_string(symtab->alt_names, from, &to_extant)) {
    if (!str8_match(to_extant, to, 0)) {
      lnk_error_obj(LNK_Error_AlternateNameConflict, obj, "conflicting alternative name: existing '%S=%S' vs. new '%S=%S'", from, to_extant, from, to);
    }
  } else {
    hash_table_push_string_string(symtab->arena->v[0], symtab->alt_names, from, to);
  }
}

