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

////////////////////////////////
//~ Dwarf Decode Helpers

static U64
dwarf_leb128_decode_U64(U8 *ptr, U8 *opl){
  U64 r = 0;
  switch (opl - ptr){
    case 10: r |= ((U64)(ptr[9]&0x7F) << 63);
    case  9: r |= ((U64)(ptr[8]&0x7F) << 56);
    case  8: r |= ((U64)(ptr[7]&0x7F) << 49);
    case  7: r |= ((U64)(ptr[6]&0x7F) << 42);
    case  6: r |= ((U64)(ptr[5]&0x7F) << 35);
    case  5: r |= ((U64)(ptr[4]&0x7F) << 28);
    case  4: r |= ((U64)(ptr[3]&0x7F) << 21);
    case  3: r |= ((U64)(ptr[2]&0x7F) << 14);
    case  2: r |= ((U64)(ptr[1]&0x7F) <<  7);
    case  1: r |= ((U64)(ptr[0]&0x7F)      );
    case  0: default: break;
  }
  return(r);
}

static S64
dwarf_leb128_decode_S64(U8 *ptr, U8 *opl){
  U64 u = dwarf_leb128_decode_U32(ptr, opl);
  U64 s = (U64)(opl - ptr)*7;
  B32 neg = ((u & (1llu << s)) != 0);
  if (neg){
    switch (opl - ptr){
      case 9: u |= ~0x7FFFFFFFFFFFFFFFllu; break;
      case 8: u |= ~0x00FFFFFFFFFFFFFFllu; break;
      case 7: u |= ~  0x01FFFFFFFFFFFFllu; break;
      case 6: u |= ~    0x03FFFFFFFFFFllu; break;
      case 5: u |= ~      0x07FFFFFFFFllu; break;
      case 4: u |= ~        0x0FFFFFFFllu; break;
      case 3: u |= ~          0x1FFFFFllu; break;
      case 2: u |= ~            0x3FFFllu; break;
      case 1: u |= ~              0x7Fllu; break;
    }
  }
  S64 r = (S64)(u);
  return(r);
}

static U32
dwarf_leb128_decode_U32(U8 *ptr, U8 *opl){
  U32 r = 0;
  switch (opl - ptr){
    case 5: r |= ((U32)(ptr[4]&0x7F) << 28);
    case 4: r |= ((U32)(ptr[3]&0x7F) << 21);
    case 3: r |= ((U32)(ptr[2]&0x7F) << 14);
    case 2: r |= ((U32)(ptr[1]&0x7F) <<  7);
    case 1: r |= ((U32)(ptr[0]&0x7F)      );
    case 0: default: break;
  }
  return(r);
}


////////////////////////////////
//~ Dwarf Parser Functions

static DWARF_Parsed*
dwarf_parsed_from_elf(Arena *arena, ELF_Parsed *elf){
  DWARF_Parsed *result = 0;
  
  if (elf != 0){
    //- extract debug info
    U32 debug_section_idx[DWARF_SectionCode_COUNT] = {0};
    String8 debug_section_name[DWARF_SectionCode_COUNT] = {0};
    String8 debug_data[DWARF_SectionCode_COUNT] = {0};
    for (U64 i = 1; i < DWARF_SectionCode_COUNT; i += 1){
      DWARF_SectionNameRow *row = dwarf_section_name_table + i;
      U32 idx = 0;
      for (U32 j = 0; idx == 0 && j < DWARF_SECTION_NAME_VARIANT_COUNT; j += 1){
        idx = elf_section_idx_from_name(elf, row->name[j]);
      }
      debug_section_idx[i] = idx;
      debug_section_name[i] = elf_section_name_from_idx(elf, idx);
      debug_data[i] = elf_section_data_from_idx(elf, idx);
    }
    
    //- fill result
    {
      result = push_array(arena, DWARF_Parsed, 1);
      result->elf = elf;
      MemoryCopyArray(result->debug_section_idx, debug_section_idx);
      MemoryCopyArray(result->debug_section_name, debug_section_name);
      MemoryCopyArray(result->debug_data, debug_data);
    }
  }
  
  return(result);
}

static DWARF_IndexParsed*
dwarf_index_from_data(Arena *arena, String8 data){
  DWARF_IndexParsed *result = 0;
  // TODO(allen): 
  return(result);
}

static DWARF_SupParsed*
dwarf_sup_from_data(Arena *arena, String8 data){
  DWARF_SupParsed *result = 0;
  // TODO(allen): 
  return(result);
}

static DWARF_InfoParsed*
dwarf_info_from_data(Arena *arena, String8 data){
  // supported version numbers: 4,5
  
  
  // empty unit list
  DWARF_InfoUnit *first = 0;
  DWARF_InfoUnit *last = 0;
  U64 count = 0;
  
  // whole section loop
  U64 unit_idx = 0;
  U8 *ptr = data.str;
  U8 *opl = data.str + data.size;
  for (;ptr < opl; unit_idx += 1){
    
    // remember header offset
    U64 hdr_off = (ptr - data.str);
    
    // initial length
    U8 *unit_opl = 0;
    B32 is_64bit = 0;
    dwarf__initial_length(data, &ptr, &unit_opl, &is_64bit);
    
    // version
    U8 version = MemoryConsume(U16, ptr, unit_opl);
    
    // rest of header depends on version
    U64 abbrev_off = 0;
    U8  address_size = 0;
    U8  unit_type = 0;
    U64 unit_dwo_id = 0;
    U64 unit_type_signature = 0;
    U64 unit_type_offset = 0;
    switch (version){
      case 4:
      {
        // abbrev_off
        if (is_64bit){
          abbrev_off = MemoryConsume(U64, ptr, unit_opl);
        }
        else{
          abbrev_off = MemoryConsume(U32, ptr, unit_opl);
        }
        
        // address_size
        address_size = MemoryConsume(U8, ptr, unit_opl);
      }break;
      
      case 5:
      {
        // unit_type
        unit_type = (DWARF_UnitType)MemoryConsume(U8, ptr, unit_opl);
        
        // address_size
        address_size = MemoryConsume(U8, ptr, unit_opl);
        
        // abbrev_off
        if (is_64bit){
          abbrev_off = MemoryConsume(U64, ptr, unit_opl);
        }
        else{
          abbrev_off = MemoryConsume(U32, ptr, unit_opl);
        }
        
        // rest of header depends on unit_type
        switch (unit_type){
          case DWARF_UnitType_skeleton: case DWARF_UnitType_split_compile:
          {
            unit_dwo_id = MemoryConsume(U64, ptr, unit_opl);
          }break;
          case DWARF_UnitType_type: case DWARF_UnitType_split_type:
          {
            unit_type_signature = MemoryConsume(U64, ptr, unit_opl);
            if (is_64bit){
              unit_type_offset = MemoryConsume(U64, ptr, unit_opl);
            }
            else{
              unit_type_offset = MemoryConsume(U32, ptr, unit_opl);
            }
          }break;
        }
      }break;
    }
    
    // offset size
    U8 offset_size = is_64bit?8:4;
    
    // unit offsets
    U64 base_off = (ptr - data.str);
    U64 opl_off = (unit_opl - data.str);
    
    // emit unit
    DWARF_InfoUnit *unit = push_array(arena, DWARF_InfoUnit, 1);
    SLLQueuePush(first, last, unit);
    count += 1;
    
    unit->hdr_off      = hdr_off;
    unit->base_off     = base_off;
    unit->opl_off      = opl_off;
    
    unit->offset_size  = offset_size;
    unit->version      = version;
    unit->unit_type    = unit_type;
    unit->address_size = address_size;
    unit->abbrev_off   = abbrev_off;
    
    switch (unit_type){
      case DWARF_UnitType_skeleton: case DWARF_UnitType_split_compile:
      {
        unit->dwo_id = unit_dwo_id;
      }break;
      case DWARF_UnitType_type: case DWARF_UnitType_split_type:
      {
        unit->type_signature = unit_type_signature;
        unit->type_offset = unit_type_offset;
      }break;
    }
    
    // advance to end of unit
    ptr = unit_opl;
  }
  
  // fill result
  DWARF_InfoParsed *result = push_array(arena, DWARF_InfoParsed, 1);
  result->unit_first = first;
  result->unit_last = last;
  result->unit_count = count;
  return(result);
}

static DWARF_PubNamesParsed*
dwarf_pubnames_from_data(Arena *arena, String8 data){
  // supported version numbers: 2
  
  
  // empty unit list
  DWARF_PubNamesUnit *first = 0;
  DWARF_PubNamesUnit *last = 0;
  U64 count = 0;
  
  // whole section loop
  U64 unit_idx = 0;
  U8 *ptr = data.str;
  U8 *opl = data.str + data.size;
  for (;ptr < opl; unit_idx += 1){
    
    // remember header offset
    U64 hdr_off = (ptr - data.str);
    
    // initial length
    U8 *unit_opl = 0;
    B32 is_64bit = 0;
    dwarf__initial_length(data, &ptr, &unit_opl, &is_64bit);
    
    // version
    U8  version = MemoryConsume(U16, ptr, unit_opl);
    
    // info_off
    U64 info_off = 0;
    if (is_64bit){
      info_off = MemoryConsume(U64, ptr, unit_opl);
    }
    else{
      info_off = MemoryConsume(U32, ptr, unit_opl);
    }
    
    // info_length
    U64 info_length = 0;
    if (is_64bit){
      info_length = MemoryConsume(U64, ptr, unit_opl);
    }
    else{
      info_length = MemoryConsume(U32, ptr, unit_opl);
    }
    
    // offset size
    U8 offset_size = is_64bit?8:4;
    
    // unit offsets
    U64 base_off = (ptr - data.str);
    U64 opl_off = (unit_opl - data.str);
    
    // emit unit
    DWARF_PubNamesUnit *unit = push_array(arena, DWARF_PubNamesUnit, 1);
    SLLQueuePush(first, last, unit);
    count += 1;
    
    unit->hdr_off  = hdr_off;
    unit->base_off = base_off;
    unit->opl_off  = opl_off;
    
    unit->offset_size = offset_size;
    unit->version = version;
    unit->info_off = info_off;
    unit->info_length = info_length;
    
    // advance to end of unit
    ptr = unit_opl;
  }
  
  // fill result
  DWARF_PubNamesParsed *result = push_array(arena, DWARF_PubNamesParsed, 1);
  result->unit_first = first;
  result->unit_last  = last;
  result->unit_count = count;
  return(result);
}

static DWARF_NamesParsed*
dwarf_names_from_data(Arena *arena, String8 data){
  // supported version numbers: 5
  
  
  // empty unit list
  DWARF_NamesUnit *first = 0;
  DWARF_NamesUnit *last = 0;
  U64 count = 0;
  
  // whole section loop
  U64 unit_idx = 0;
  U8 *ptr = data.str;
  U8 *opl = data.str + data.size;
  for (;ptr < opl; unit_idx += 1){
    
    // remember header offset
    U64 hdr_off = (ptr - data.str);
    
    // initial length
    U8 *unit_opl = 0;
    B32 is_64bit = 0;
    dwarf__initial_length(data, &ptr, &unit_opl, &is_64bit);
    
    // version
    U8 version = MemoryConsume(U16, ptr, unit_opl);
    
    // *padding*
    MemoryConsume(U16, ptr, unit_opl);
    
    // comp_unit_count
    U32 comp_unit_count = MemoryConsume(U32, ptr, unit_opl);
    
    // local_type_unit_count
    U32 local_type_unit_count = MemoryConsume(U32, ptr, unit_opl);
    
    // foreign_type_unit_count
    U32 foreign_type_unit_count = MemoryConsume(U32, ptr, unit_opl);
    
    // bucket_count
    U32 bucket_count = MemoryConsume(U32, ptr, unit_opl);
    
    // name_count
    U32 name_count = MemoryConsume(U32, ptr, unit_opl);
    
    // abbrev_table_size
    U32 abbrev_table_size = MemoryConsume(U32, ptr, unit_opl);
    
    // augmentation_string_size
    U32 augmentation_string_size = MemoryConsume(U32, ptr, unit_opl);
    
    // augmentation_string
    U8 *augmentation_string = ptr;
    {
      U8 *ptr_raw = ptr + augmentation_string_size;
      ptr = ClampTop(ptr_raw, unit_opl);
    }
    
    // offset size
    U8 offset_size = is_64bit?8:4;
    
    // unit offsets
    U64 base_off = (ptr - data.str);
    U64 opl_off = (unit_opl - data.str);
    
    // emit unit
    DWARF_NamesUnit *unit = push_array(arena, DWARF_NamesUnit, 1);
    SLLQueuePush(first, last, unit);
    count += 1;
    
    unit->hdr_off  = hdr_off;
    unit->base_off = base_off;
    unit->opl_off  = opl_off;
    
    unit->version = version;
    unit->comp_unit_count = comp_unit_count;
    unit->local_type_unit_count = local_type_unit_count;
    unit->foreign_type_unit_count = foreign_type_unit_count;
    unit->bucket_count = bucket_count;
    unit->name_count = name_count;
    unit->abbrev_table_size = abbrev_table_size;
    unit->augmentation_string = str8_cstring_capped(augmentation_string, unit_opl);
    
    // advance to end of unit
    ptr = unit_opl;
  }
  
  // fill result
  DWARF_NamesParsed *result = push_array(arena, DWARF_NamesParsed, 1);
  result->unit_first = first;
  result->unit_last  = last;
  result->unit_count = count;
  return(result);
}

static DWARF_ArangesParsed*
dwarf_aranges_from_data(Arena *arena, String8 data){
  // supported version numbers: 2
  
  
  // empty unit list
  DWARF_ArangesUnit *first = 0;
  DWARF_ArangesUnit *last = 0;
  U64 count = 0;
  
  // whole section loop
  U64 unit_idx = 0;
  U8 *ptr = data.str;
  U8 *opl = data.str + data.size;
  for (;ptr < opl; unit_idx += 1){
    
    // remember header offset
    U64 hdr_off = (ptr - data.str);
    
    // initial length
    U8 *unit_opl = 0;
    B32 is_64bit = 0;
    dwarf__initial_length(data, &ptr, &unit_opl, &is_64bit);
    
    // version
    U8 version = MemoryConsume(U16, ptr, unit_opl);
    
    // info_off
    U64 info_off = 0;
    if (is_64bit){
      info_off = MemoryConsume(U64, ptr, unit_opl);
    }
    else{
      info_off = MemoryConsume(U32, ptr, unit_opl);
    }
    
    // address_size
    U8 address_size = MemoryConsume(U8, ptr, unit_opl);
    
    // segment_selector_size
    U8 segment_selector_size = MemoryConsume(U8, ptr, unit_opl);
    
    // offset size
    U8 offset_size = is_64bit?8:4;
    
    // unit offsets
    U64 base_off = (ptr - data.str);
    U64 opl_off = (unit_opl - data.str);
    
    // emit unit
    DWARF_ArangesUnit *unit = push_array(arena, DWARF_ArangesUnit, 1);
    SLLQueuePush(first, last, unit);
    count += 1;
    
    unit->hdr_off  = hdr_off;
    unit->base_off = base_off;
    unit->opl_off  = opl_off;
    
    unit->version = version;
    unit->address_size = address_size;
    unit->segment_selector_size = segment_selector_size;
    unit->offset_size = offset_size;
    unit->info_off = info_off;
    
    // advance to end of unit
    ptr = unit_opl;
  }
  
  // fill result
  DWARF_ArangesParsed *result = push_array(arena, DWARF_ArangesParsed, 1);
  result->unit_first = first;
  result->unit_last  = last;
  result->unit_count = count;
  return(result);
}

static DWARF_LineParsed*
dwarf_line_from_data(Arena *arena, String8 data){
  // supported version numbers: 4, 5
  
  
  // empty unit list
  DWARF_LineUnit *first = 0;
  DWARF_LineUnit *last = 0;
  U64 count = 0;
  
  // whole section loop
  U64 unit_idx = 0;
  U8 *ptr = data.str;
  U8 *opl = data.str + data.size;
  for (;ptr < opl; unit_idx += 1){
    
    // remember header offset
    U64 hdr_off = (ptr - data.str);
    
    // initial length
    U8 *unit_opl = 0;
    B32 is_64bit = 0;
    dwarf__initial_length(data, &ptr, &unit_opl, &is_64bit);
    
    // version
    U8 version = MemoryConsume(U16, ptr, unit_opl);
    
    // offset size
    U8 offset_size = is_64bit?8:4;
    
    // rest of header depends on version
    U8  minimum_instruction_length = 0;
    U8  maximum_operations_per_instruction = 0;
    U8  default_is_stmt = 0;
    S8  line_base = 0;
    U8  line_range = 0;
    U8  opcode_base = 0;
    U8 *standard_opcode_lengths = 0;
    // v4
    String8List include_directories = {0};
    DWARF_V4LineFileNamesList file_names = {0};
    // v5
    U8  address_size = 0;
    U8  segment_selector_size = 0;
    U8  directory_entry_format_count = 0;
    DWARF_V5LinePathEntryFormat *directory_entry_format = 0;
    U64 directories_count = 0;
    U8  file_name_entry_format_count = 0;
    DWARF_V5LinePathEntryFormat *file_name_entry_format = 0;
    U64 file_names_count = 0;
    
    switch (version){
      case 4:
      {
        // header_length
        U64 header_length = 0;
        if (is_64bit){
          header_length = MemoryConsume(U64, ptr, unit_opl);
        }
        else{
          header_length = MemoryConsume(U32, ptr, unit_opl);
        }
        
        // header opl
        U8 *header_opl_raw = ptr + header_length;
        U8 *header_opl = ClampTop(header_opl_raw, unit_opl);
        
        // minimum_instruction_length
        minimum_instruction_length = MemoryConsume(U8, ptr, header_opl);
        
        // maximum_operations_per_instruction
        maximum_operations_per_instruction = MemoryConsume(U8, ptr, header_opl);
        
        // default_is_stmt
        default_is_stmt = MemoryConsume(U8, ptr, header_opl);
        
        // line_base
        line_base = MemoryConsume(S8, ptr, header_opl);
        
        // line_range
        line_range = MemoryConsume(U8, ptr, header_opl);
        
        // opcode_base
        opcode_base = MemoryConsume(U8, ptr, header_opl);
        
        // standard_opcode_lengths
        if (opcode_base > 1){
          standard_opcode_lengths = ptr;
          ptr += opcode_base - 1;
        }
        
        // include_directories
        for (;ptr < header_opl;){
          // null byte ends entries
          if (*ptr == 0){
            ptr += 1;
            break;
          }
          
          // extract dir range
          U8 *dir_first = ptr;
          for (;ptr < header_opl && *ptr != 0;) ptr += 1;
          U8 *dir_opl = ptr;
          if (ptr < header_opl){
            ptr += 1;
          }
          
          // attach dir to list
          String8 dir = str8_range(dir_first, dir_opl);
          str8_list_push(arena, &include_directories, dir);
        }
        
        // file_names
        for (;ptr < header_opl;){
          // null byte ends entries
          if (*ptr == 0){
            ptr += 1;
            break;
          }
          
          // extract file_name range
          U8 *file_name_first = ptr;
          for (;ptr < header_opl && *ptr != 0;) ptr += 1;
          U8 *file_name_opl = ptr;
          if (ptr < header_opl){
            ptr += 1;
          }
          
          // extract include directory index
          U64 include_directory_idx = 0;
          DWARF_LEB128_DECODE_ADV(U64, include_directory_idx, ptr, header_opl);
          
          // extract last modified time
          U64 last_modified_time = 0;
          DWARF_LEB128_DECODE_ADV(U64, last_modified_time, ptr, header_opl);
          
          // extract file size
          U64 file_size = 0;
          DWARF_LEB128_DECODE_ADV(U64, file_size, ptr, header_opl);
          
          // emit file name entry
          DWARF_V4LineFileNamesEntry *entry = push_array(arena, DWARF_V4LineFileNamesEntry, 1);
          SLLQueuePush(file_names.first, file_names.last, entry);
          file_names.count += 1;
          entry->file_name = str8_range(file_name_first, file_name_opl);
          entry->include_directory_idx = include_directory_idx;
          entry->last_modified_time = last_modified_time;
          entry->file_size = file_size;
        }
        
        ptr = header_opl;
      }break;
      
      case 5:
      {
        // address_size
        address_size = MemoryConsume(U8, ptr, unit_opl);
        
        // segment_selector_size
        segment_selector_size = MemoryConsume(U8, ptr, unit_opl);
        
        // header_length
        U64 header_length = 0;
        if (is_64bit){
          header_length = MemoryConsume(U64, ptr, unit_opl);
        }
        else{
          header_length = MemoryConsume(U32, ptr, unit_opl);
        }
        
        // header opl
        U8 *header_opl_raw = ptr + header_length;
        U8 *header_opl = ClampTop(header_opl_raw, unit_opl);
        
        // minimum_instruction_length
        minimum_instruction_length = MemoryConsume(U8, ptr, header_opl);
        
        // maximum_operations_per_instruction
        maximum_operations_per_instruction = MemoryConsume(U8, ptr, header_opl);
        
        // default_is_stmt
        default_is_stmt = MemoryConsume(U8, ptr, header_opl);
        
        // line_base
        line_base = MemoryConsume(S8, ptr, header_opl);
        
        // line_range
        line_range = MemoryConsume(U8, ptr, header_opl);
        
        // opcode_base
        opcode_base = MemoryConsume(U8, ptr, header_opl);
        
        // standard_opcode_lengths
        if (opcode_base > 1){
          standard_opcode_lengths = ptr;
          ptr += opcode_base - 1;
        }
        
        // directory_entry_format_count
        directory_entry_format_count = MemoryConsume(U8, ptr, header_opl);
        
        // directory_entry_format
        {
          directory_entry_format = push_array(arena, DWARF_V5LinePathEntryFormat,
                                              directory_entry_format_count);
          DWARF_V5LinePathEntryFormat *entry = directory_entry_format;
          DWARF_V5LinePathEntryFormat *entry_opl =
            directory_entry_format + directory_entry_format_count;
          for (;entry < entry_opl && ptr < header_opl; entry += 1){
            DWARF_LEB128_DECODE_ADV(U64, entry->content_type, ptr, header_opl);
            DWARF_LEB128_DECODE_ADV(U64, entry->form, ptr, header_opl);
          }
        }
        
        // directories_count
        DWARF_LEB128_DECODE_ADV(U64, directories_count, ptr, header_opl);
        
        // directories
        DWARF_V5Directory *directories = push_array(arena, DWARF_V5Directory, directories_count);
        dwarf__line_v5_directories(address_size, offset_size,
                                   directory_entry_format, directory_entry_format_count,
                                   directories, directories_count,
                                   &ptr, header_opl);
        
        // file_name_entry_format_count
        file_name_entry_format_count = MemoryConsume(U8, ptr, header_opl);
        
        // file_name_entry_format
        {
          file_name_entry_format = push_array(arena, DWARF_V5LinePathEntryFormat,
                                              file_name_entry_format_count);
          DWARF_V5LinePathEntryFormat *entry = file_name_entry_format;
          for (;ptr < header_opl; entry += 1){
            DWARF_LEB128_DECODE_ADV(U64, entry->content_type, ptr, header_opl);
            DWARF_LEB128_DECODE_ADV(U64, entry->form, ptr, header_opl);
          }
        }
        
        // file_names_count
        DWARF_LEB128_DECODE_ADV(U64, file_names_count, ptr, header_opl);
        
        // file_names
        DWARF_V5Directory *file_names = push_array(arena, DWARF_V5Directory, file_names_count);
        dwarf__line_v5_directories(address_size, offset_size,
                                   directory_entry_format, directory_entry_format_count,
                                   file_names, file_names_count,
                                   &ptr, header_opl);
      }break;
    }
    
    // unit offsets
    U64 base_off = (ptr - data.str);
    U64 opl_off = (unit_opl - data.str);
    
    // emit unit
    DWARF_LineUnit *unit = push_array(arena, DWARF_LineUnit, 1);
    SLLQueuePush(first, last, unit);
    count += 1;
    
    unit->hdr_off  = hdr_off;
    unit->base_off = base_off;
    unit->opl_off  = opl_off;
    
    unit->version = version;
    
    // advance to end of unit
    ptr = unit_opl;
  }
  
  // fill result
  DWARF_LineParsed *result = push_array(arena, DWARF_LineParsed, 1);
  result->unit_first = first;
  result->unit_last  = last;
  result->unit_count = count;
  return(result);
}

static DWARF_MacInfoParsed*
dwarf_mac_info_from_data(Arena *arena, String8 data){
  DWARF_MacInfoParsed *result = 0;
  // TODO(allen): 
  return(result);
}

static DWARF_MacroParsed*
dwarf_macro_from_data(Arena *arena, String8 data){
  DWARF_MacroParsed *result = 0;
  // TODO(allen): 
  return(result);
}

static DWARF_FrameParsed*
dwarf_frame_from_data(Arena *arena, String8 data){
  DWARF_FrameParsed *result = 0;
  // TODO(allen): 
  return(result);
}

static DWARF_RangesParsed*
dwarf_ranges_from_data(Arena *arena, String8 data){
  DWARF_RangesParsed *result = 0;
  // TODO(allen): 
  return(result);
}

static DWARF_StrOffsetsParsed*
dwarf_str_offsets_from_data(Arena *arena, String8 data){
  DWARF_StrOffsetsParsed *result = 0;
  // TODO(allen): 
  return(result);
}

static DWARF_AddrParsed*
dwarf_addr_from_data(Arena *arena, String8 data){
  // supported version numbers: 5
  
  
  // addr unit list
  DWARF_AddrUnit *first = 0;
  DWARF_AddrUnit *last = 0;
  U64 count = 0;
  
  // whole section loop
  U64 unit_idx  = 0;
  U8 *ptr = data.str;
  U8 *opl = data.str + data.size;
  for (;ptr < opl; unit_idx += 1){
    
    U64 hdr_off = (ptr - data.str);
    
    // initial length
    U8 *unit_opl = 0;
    B32 is_64bit = 0;
    dwarf__initial_length(data, &ptr, &unit_opl, &is_64bit);
    
    // version
    U8 version = MemoryConsume(U16, ptr, unit_opl);
    
    // address size
    U8 address_size = MemoryConsume(U8, ptr, unit_opl);
    
    // segment selector size
    U8 segment_selector_size = MemoryConsume(U8, ptr, unit_opl);
    
    // offset size
    U32 offset_size = is_64bit?8:4;
    
    // unit offsets
    U64 base_off = (ptr - data.str);
    U64 opl_off = (unit_opl - data.str);
    
    // emit addr unit
    DWARF_AddrUnit *unit = push_array(arena, DWARF_AddrUnit, 1);
    SLLQueuePush(first, last, unit);
    count += 1;
    
    unit->hdr_off  = hdr_off;
    unit->base_off = base_off;
    unit->opl_off  = opl_off;
    
    unit->offset_size = offset_size;
    unit->dwarf_version = version;
    unit->address_size = address_size;
    unit->segment_selector_size = segment_selector_size;
    
    // advance to next unit
    ptr = unit_opl;
  }
  
  // fill result
  DWARF_AddrParsed *result = push_array(arena, DWARF_AddrParsed, 1);
  result->unit_first = first;
  result->unit_last = last;
  result->unit_count = count;
  return(result);
}

static DWARF_RngListsParsed*
dwarf_rng_lists_from_data(Arena *arena, String8 data){
  DWARF_RngListsParsed *result = 0;
  // TODO(allen): 
  return(result);
}

static DWARF_LocListsParsed*
dwarf_loc_lists_from_data(Arena *arena, String8 data){
  DWARF_LocListsParsed *result = 0;
  // TODO(allen): 
  return(result);
}


// parse helpers

static void
dwarf__initial_length(String8 data, U8 **ptr_inout, U8 **unit_opl_out, B32 *is_64bit_out){
  U8 *unit_opl = 0;
  B32 is_64bit = 0;
  
  U8 *opl = data.str + data.size;
  U8 *ptr = *ptr_inout;
  {
    U64 length = 0;
    U32 m = MemoryConsume(U32, ptr, opl);
    if (m == 0xFFFFFFFF){
      is_64bit = 1;
      length = MemoryConsume(U64, ptr, opl);
    }
    else{
      length = ClampTop(m, 0xFFFFFFF0);
    }
    if (length > 0){
      U64 unit_opl_off_raw = (ptr - data.str) + length;
      U64 unit_opl_off = ClampTop(unit_opl_off_raw, data.size);
      unit_opl = data.str + unit_opl_off;
    }
    else{
      unit_opl = ptr;
    }
  }
  
  *ptr_inout = ptr;
  *unit_opl_out = unit_opl;
  *is_64bit_out = is_64bit;
}

static void
dwarf__line_v5_directories(U64 address_size, U64 offset_size,
                           DWARF_V5LinePathEntryFormat *format, U64 format_count,
                           DWARF_V5Directory *directories_out, U64 dir_count,
                           U8 **ptr_io, U8 *opl){
  
  U8 *ptr = *ptr_io;
  
  DWARF_V5Directory *directory_ptr = directories_out;
  for (U32 i = 0; i < dir_count; i += 1, directory_ptr += 1){
    DWARF_V5LinePathEntryFormat *fmt = format;
    for (U32 j = 0; j < format_count; j += 1){
      
      // form decode
      DWARF_FormDecodeRules rules =
        dwarf_form_decode_rule(fmt->form, address_size, offset_size);
      
      // execute decoding
      DWARF_FormDecoded decoded = dwarf_form_decode(&rules, &ptr, opl, 0, 0);
      
      // store to correct field
      U64 *target = 0;
      switch (fmt->content_type){
        case DWARF_LineEntryFormat_path:
        {
          if (decoded.dataptr != 0){
            directory_ptr->path_str = str8(decoded.dataptr, decoded.val);
          }
          else{
            directory_ptr->path_off = decoded.val;
            directory_ptr->path_sec_form = fmt->form;
          }
        }break;
        
        case DWARF_LineEntryFormat_directory_index:
        {
          target = &directory_ptr->directory_index;
        }goto v5_directory_u64;
        
        case DWARF_LineEntryFormat_timestamp:
        {
          target = &directory_ptr->timestamp;
        }goto v5_directory_u64;
        
        case DWARF_LineEntryFormat_size:
        {
          target = &directory_ptr->size;
        }goto v5_directory_u64;
        
        v5_directory_u64:
        {
          if (decoded.dataptr != 0){
            U64 size = ClampTop(decoded.val, 8);
            MemoryCopy(target, decoded.dataptr, size);
          }
          else{
            *target = decoded.val;
          }
        }break;
        
        case DWARF_LineEntryFormat_MD5:
        {
          if (decoded.dataptr != 0){
            U64 size = ClampTop(decoded.val, 16);
            MemoryCopy(directory_ptr->md5_checksum, decoded.dataptr, size);
          }
        }break;
      }
    }
  }
  
  *ptr_io = ptr;
}


// debug sections

static String8
dwarf_name_from_debug_section(DWARF_Parsed *dwarf, DWARF_SectionCode sec_code){
  String8 result = str8_lit("invalid_debug_section");
  if (sec_code < DWARF_SectionCode_COUNT){
    if (dwarf->debug_section_idx[sec_code] != 0){
      result = dwarf->debug_section_name[sec_code];
    }
  }
  return(result);
}


// abbrev functions

static DWARF_AbbrevUnit*
dwarf_abbrev_unit_from_offset(DWARF_AbbrevParsed *abbrev, U64 offset){
  DWARF_AbbrevUnit *result = 0;
  for (DWARF_AbbrevUnit *unit = abbrev->unit_first;
       unit != 0;
       unit = unit->next){
    if (unit->offset == offset){
      result = unit;
      break;
    }
  }
  return(result);
}

static DWARF_AbbrevDecl*
dwarf_abbrev_decl_from_code(DWARF_AbbrevUnit *unit, U32 abbrev_code){
  DWARF_AbbrevDecl *result = 0;
  for (DWARF_AbbrevDecl *decl = unit->first;
       decl != 0;
       decl = decl->next){
    if (decl->abbrev_code == abbrev_code){
      result = decl;
      break;
    }
  }
  return(result);
}

// attribute decoding functions

static DWARF_AttributeClassFlags
dwarf_attribute_class_from_form(DWARF_AttributeForm form){
  DWARF_AttributeClassFlags result = 0;
  switch (form){
#define X(N,C,f) case C: result = DWARF_AttributeClassFlag_##f; break;
    DWARF_AttributeFormXList(X)
#undef X
  }
  return(result);
}

static DWARF_AttributeClassFlags
dwarf_attribute_class_from_name(DWARF_AttributeName name){
  DWARF_AttributeClassFlags result = 0;
  switch (name){
#define X(N,C,f1,f2,f3,f4) case C: result = 0\
|DWARF_AttributeClassFlag_##f1\
|DWARF_AttributeClassFlag_##f2\
|DWARF_AttributeClassFlag_##f3\
|DWARF_AttributeClassFlag_##f4\
;break;
    DWARF_AttributeNameXList(X)
#undef X
  }
  return(result);
}

// form decoding functions

static DWARF_FormDecodeRules
dwarf_form_decode_rule(DWARF_AttributeForm form, U64 address_size, U64 offset_size){
  DWARF_FormDecodeRules result = {0};
  switch (form){
    case DWARF_AttributeForm_addr: result.size = address_size; break;
    case DWARF_AttributeForm_addrx: result.uleb128 = 1; break;
    case DWARF_AttributeForm_addrx1: result.size = 1; break;
    case DWARF_AttributeForm_addrx2: result.size = 2; break;
    case DWARF_AttributeForm_addrx3: result.size = 3; break;
    case DWARF_AttributeForm_addrx4: result.size = 4; break;
    
    case DWARF_AttributeForm_sec_offset: result.size = offset_size; break;
    
    case DWARF_AttributeForm_block1: result.size = 1; result.block = 1; break;
    case DWARF_AttributeForm_block2: result.size = 2; result.block = 1; break;
    case DWARF_AttributeForm_block4: result.size = 4; result.block = 1; break;
    case DWARF_AttributeForm_block: result.uleb128 = 1; result.block = 1; break;
    
    case DWARF_AttributeForm_data1: result.size = 1; break;
    case DWARF_AttributeForm_data2: result.size = 2; break;
    case DWARF_AttributeForm_data4: result.size = 4; break;
    case DWARF_AttributeForm_data8: result.size = 8; break;
    case DWARF_AttributeForm_data16: result.size = 16; break;
    
    case DWARF_AttributeForm_sdata: result.sleb128 = 1; break;
    case DWARF_AttributeForm_udata: result.uleb128 = 1; break;
    
    case DWARF_AttributeForm_implicit_const: result.in_abbrev = 1; break;
    
    case DWARF_AttributeForm_exprloc: result.uleb128 = 1; result.block = 1; break;
    
    case DWARF_AttributeForm_flag: result.size = 1; break;
    case DWARF_AttributeForm_flag_present: result.auto_1 = 1; break;
    
    case DWARF_AttributeForm_loclistx: result.uleb128 = 1; break;
    case DWARF_AttributeForm_rnglistx: result.uleb128 = 1; break;
    
    case DWARF_AttributeForm_ref1: result.size = 1; break;
    case DWARF_AttributeForm_ref2: result.size = 2; break;
    case DWARF_AttributeForm_ref4: result.size = 4; break;
    case DWARF_AttributeForm_ref8: result.size = 8; break;
    case DWARF_AttributeForm_ref_udata: result.uleb128 = 1; break;
    
    case DWARF_AttributeForm_ref_addr: result.size = offset_size; break;
    
    case DWARF_AttributeForm_ref_sig8: result.size = 8; break;
    
    case DWARF_AttributeForm_ref_sup4: result.size = 4; break;
    case DWARF_AttributeForm_ref_sup8: result.size = 8; break;
    
    case DWARF_AttributeForm_string: result.null_terminated = 1; break;
    
    case DWARF_AttributeForm_strp: result.size = offset_size; break;
    case DWARF_AttributeForm_line_strp: result.size = offset_size; break;
    case DWARF_AttributeForm_strp_sup: result.size = offset_size; break;
    
    case DWARF_AttributeForm_strx: result.uleb128 = 1; break;
    case DWARF_AttributeForm_strx1: result.size = 1; break;
    case DWARF_AttributeForm_strx2: result.size = 2; break;
    case DWARF_AttributeForm_strx3: result.size = 3; break;
    case DWARF_AttributeForm_strx4: result.size = 4; break;
  }
  
  return(result);
}

static DWARF_FormDecoded
dwarf_form_decode(DWARF_FormDecodeRules *rules, U8 **ptr_io, U8 *opl,
                  DWARF_AbbrevDecl *abbrev_decl, U32 attrib_i){
  
  // local copy of ptr
  U8 *ptr = *ptr_io;
  
  // apply rules
  U64 val = 0;
  U8 *dataptr = 0;
  
  B32 success = 1;
  if (rules->size > 0){
    if (ptr + rules->size <= opl){
      MemoryCopy(&val, ptr, rules->size);
      ptr += rules->size;
    }
    else{
      success = 0;
    }
  }
  else if (rules->uleb128 || rules->sleb128){
    U8 *val_ptr = ptr;
    DWARF_LEB128_ADV(ptr, opl, success);
    if (success){
      if (rules->uleb128){
        val = dwarf_leb128_decode_U64(val_ptr, ptr);
      }
      else{
        val = (U64)dwarf_leb128_decode_S64(val_ptr, ptr);
      }
    }
  }
  else if (rules->in_abbrev){
    if (abbrev_decl != 0){
      if (abbrev_decl->implicit_const != 0){
        val = (U64)abbrev_decl->implicit_const[attrib_i];
      }
    }
    else{
      success = 0;
    }
  }
  else if (rules->auto_1){
    val = 1;
  }
  if (rules->block){
    dataptr = ptr;
    ptr += val;
  }
  else if (rules->null_terminated){
    dataptr = ptr;
    for (;ptr < opl && *ptr != 0;) ptr += 1;
    val = (U64)(ptr - dataptr);
    if (ptr < opl){
      ptr += 1;
    }
  }
  
  // store out ptr
  *ptr_io = ptr;
  
  // fill result
  DWARF_FormDecoded result = {0};
  result.val = val;
  result.dataptr = dataptr;
  result.error = !success;
  return(result);
}


// string functions

static String8
dwarf_string_from_unit_type(DWARF_UnitType type){
  String8 result = str8_lit("unrecognized_type");
  switch (type){
#define X(N,C) case C: result = str8_lit(#N); break;
    DWARF_UnitTypeXList(X)
#undef X
  }
  return(result);
}

static String8
dwarf_string_from_tag(DWARF_Tag tag){
  String8 result = str8_lit("unrecognized_tag");
  switch (tag){
#define X(N,C) case C: result = str8_lit(#N); break;
    DWARF_TagXList(X)
#undef X
  }
  return(result);
}

static String8
dwarf_string_from_attribute_name(DWARF_AttributeName name){
  String8 result = str8_lit("unrecognized_attribute_name");
  switch (name){
#define X(N,C,f1,f2,f3,f4) case C: result = str8_lit(#N); break;
    DWARF_AttributeNameXList(X)
#undef X
  }
  return(result);
}

static String8
dwarf_string_from_attribute_form(DWARF_AttributeForm form){
  String8 result = str8_lit("unrecognized_attribute_form");
  switch (form){
#define X(N,C,k) case C: result = str8_lit(#N); break;
    DWARF_AttributeFormXList(X)
#undef X
  }
  return(result);
}

static String8
dwarf_string_from_line_std_op(DWARF_LineStdOp op){
  String8 result = str8_lit("unrecognized_line_std_op");
  switch (op){
#define X(N,C) case C: result = str8_lit(#N); break;
    DWARF_LineStdOpXList(X)
#undef X
  }
  return(result);
}

static String8
dwarf_string_from_line_ext_op(DWARF_LineExtOp op){
  String8 result = str8_lit("unrecognized_line_ext_op");
  switch (op){
#define X(N,C) case C: result = str8_lit(#N); break;
    DWARF_LineExtOpXList(X)
#undef X
  }
  return(result);
}

static String8
dwarf_string_from_line_entry_format(DWARF_LineEntryFormat format){
  String8 result = str8_lit("unrecognized_line_entry_format");
  switch (format){
#define X(N,C) case C: result = str8_lit(#N); break;
    DWARF_LineEntryFormatXList(X)
#undef X
  }
  return(result);
}

static String8
dwarf_string_from_section_code(DWARF_SectionCode sec_code){
  String8 result = str8_lit("unrecognized_section_code");
  switch (sec_code){
#define X(Nc,Vf,N0,N1,N2) case DWARF_SectionCode_##Nc: result = str8_lit(#Nc); break;
    DWARF_SectionNameXList(X,0,0)
#undef X
  }
  return(result);
}





#if 0
static DWARF_InfoParsed*
dwarf_info_from_data(Arena *arena, String8 data, DWARF_InfoParams *params,
                     DWARF_AbbrevParsed *abbrev){
  
  // unit index range to extract
  U64 unit_idx_min = params->unit_idx_min;
  U64 unit_idx_max = params->unit_idx_max;
  
  // empty unit list
  DWARF_InfoUnit *unit_first = 0;
  DWARF_InfoUnit *unit_last = 0;
  U64 unit_count = 0;
  B32 decoding_error = 0;
  
  // whole section loop
  U64 unit_idx = 0;
  U8 *ptr = data.str;
  U8 *opl = data.str + data.size;
  for (;ptr < opl; unit_idx += 1){
    
    // early escape on unit idx
    if (unit_idx > unit_idx_max){
      break;
    }
    
    // determine whether to full parse this unit
    B32 full_parse = (unit_idx_min <= unit_idx);
    
    // header fields
    U8 *unit_opl = 0;
    B32 is_64bit = 0;
    U16 version = 0;
    U64 abbrev_offset = 0;
    U32 address_size = 0;
    DWARF_UnitType unit_type = DWARF_UnitType_null;
    U64 unit_dwo_id = 0;
    U64 unit_type_signature = 0;
    U64 unit_type_offset = 0;
    
    // initial length
    dwarf__initial_length(&ptr, opl, &unit_opl, &is_64bit);
    
    // if this is not a full parse we may use
    //  unit_opl to skip to the next unit now
    if (full_parse){
      
      // version (part of header)
      version = MemoryConsume(U16, ptr, unit_opl);
      
      // rest of header depends on version
      switch (version){
        case 4:
        {
          // abbrev_offset (part of header)
          if (is_64bit){
            abbrev_offset = MemoryConsume(U64, ptr, unit_opl);
          }
          else{
            abbrev_offset = MemoryConsume(U32, ptr, unit_opl);
          }
          
          // address_size (part of header)
          address_size = MemoryConsume(U8, ptr, unit_opl);
        }break;
        
        case 5:
        {
          // unit_type (part of header)
          unit_type = (DWARF_UnitType)MemoryConsume(U8, ptr, unit_opl);
          
          // address_size (part of header)
          address_size = MemoryConsume(U8, ptr, unit_opl);
          
          // abbrev_offset (part of header)
          if (is_64bit){
            abbrev_offset = MemoryConsume(U64, ptr, unit_opl);
          }
          else{
            abbrev_offset = MemoryConsume(U32, ptr, unit_opl);
          }
          
          // rest of header depends on unit_type
          switch (unit_type){
            case DWARF_UnitType_skeleton:
            case DWARF_UnitType_split_compile:
            {
              unit_dwo_id = MemoryConsume(U64, ptr, unit_opl);
            }break;
            case DWARF_UnitType_type:
            case DWARF_UnitType_split_type:
            {
              unit_type_signature = MemoryConsume(U64, ptr, unit_opl);
              if (is_64bit){
                unit_type_offset = MemoryConsume(U64, ptr, unit_opl);
              }
              else{
                unit_type_offset = MemoryConsume(U32, ptr, unit_opl);
              }
            }break;
          }
        }break;
      }
      
      // offset size
      U32 offset_size = is_64bit?8:4;
      
      // find matching abbrev unit
      DWARF_AbbrevUnit *abbrev_unit = dwarf_abbrev_unit_from_offset(abbrev, abbrev_offset);
      if (abbrev_unit == 0){
        // TODO: preserve error info
        decoding_error = 1;
      }
      
      // consume info entries
      DWARF_InfoEntry *entry_root = 0;
      U64 entry_count = 0;
      
      DWARF_InfoEntry *entry_consptr = 0;
      if (abbrev_unit != 0){
        for (;ptr < unit_opl;){
          B32 success = 1;
          
          // mark beginning of entry
          U8 *entry_start_ptr = ptr;
          
          // extract abbrev code
          U8 *abbrev_code_ptr = ptr;
          DWARF_LEB128_ADV(ptr, unit_opl, success);
          if (!success){
            // TODO: preserve error info
            decoding_error = 1;
            goto exit_unit_loop;
          }
          
          U32 abbrev_code = dwarf_leb128_decode_U32(abbrev_code_ptr, ptr);
          
          // null abbrev code means pop
          if (abbrev_code == 0){
            if (entry_consptr == 0){
              goto exit_unit_loop;
            }
            else{
              entry_consptr = entry_consptr->parent;
              goto skip_entry;
            }
          }
          
          // get abbrev decl
          DWARF_AbbrevDecl *abbrev_decl = dwarf_abbrev_decl_from_code(abbrev_unit, abbrev_code);
          if (abbrev_decl == 0){
            // TODO: preserve error info
            decoding_error = 1;
            goto exit_unit_loop;
          }
          
          // allocate entry
          U32 attrib_count = abbrev_decl->attrib_count;
          DWARF_InfoEntry *entry = push_array(arena, DWARF_InfoEntry, 1);
          DWARF_InfoAttribVal *attrib_vals =
            push_array_no_zero(arena, DWARF_InfoAttribVal, attrib_count);
          
          // save entry offset
          entry->info_offset = (U64)(entry_start_ptr - data.str);
          
          // set root at beginning
          if (entry_root == 0){
            entry_root = entry;
          }
          
          // attribute loop
          DWARF_AbbrevAttribSpec *attrib_spec = abbrev_decl->attrib_specs;
          DWARF_InfoAttribVal *attrib_val = attrib_vals;
          for (U32 i = 0; i < attrib_count; i += 1, attrib_spec += 1, attrib_val += 1){
            
            // determine decoding rules
            U32 size = 0;
            B8 uleb128 = 0;
            B8 sleb128 = 0;
            B8 in_abbrev = 0;
            B8 auto_1 = 0;
            B8 block = 0;
            B8 null_terminated = 0;
            {
              DWARF_AttributeForm form = attrib_spec->form;
              switch (form){
                case DWARF_AttributeForm_addr: size = address_size; break;
                case DWARF_AttributeForm_addrx: uleb128 = 1; break;
                case DWARF_AttributeForm_addrx1: size = 1; break;
                case DWARF_AttributeForm_addrx2: size = 2; break;
                case DWARF_AttributeForm_addrx3: size = 3; break;
                case DWARF_AttributeForm_addrx4: size = 4; break;
                
                case DWARF_AttributeForm_sec_offset: size = offset_size; break;
                
                case DWARF_AttributeForm_block1: size = 1; block = 1; break;
                case DWARF_AttributeForm_block2: size = 2; block = 1; break;
                case DWARF_AttributeForm_block4: size = 4; block = 1; break;
                case DWARF_AttributeForm_block: uleb128 = 1; block = 1; break;
                
                case DWARF_AttributeForm_data1: size = 1; break;
                case DWARF_AttributeForm_data2: size = 2; break;
                case DWARF_AttributeForm_data4: size = 4; break;
                case DWARF_AttributeForm_data8: size = 8; break;
                case DWARF_AttributeForm_data16: size = 16; break;
                
                case DWARF_AttributeForm_sdata: sleb128 = 1; break;
                case DWARF_AttributeForm_udata: uleb128 = 1; break;
                
                case DWARF_AttributeForm_implicit_const: in_abbrev = 1; break;
                
                case DWARF_AttributeForm_exprloc: uleb128 = 1; block = 1; break;
                
                case DWARF_AttributeForm_flag: size = 1; break;
                case DWARF_AttributeForm_flag_present: auto_1 = 1; break;
                
                case DWARF_AttributeForm_loclistx: uleb128 = 1; break;
                case DWARF_AttributeForm_rnglistx: uleb128 = 1; break;
                
                case DWARF_AttributeForm_ref1: size = 1; break;
                case DWARF_AttributeForm_ref2: size = 2; break;
                case DWARF_AttributeForm_ref4: size = 4; break;
                case DWARF_AttributeForm_ref8: size = 8; break;
                case DWARF_AttributeForm_ref_udata: uleb128 = 1; break;
                
                case DWARF_AttributeForm_ref_addr: size = offset_size; break;
                
                case DWARF_AttributeForm_ref_sig8: size = 8; break;
                
                case DWARF_AttributeForm_ref_sup4: size = 4; break;
                case DWARF_AttributeForm_ref_sup8: size = 8; break;
                
                case DWARF_AttributeForm_string: null_terminated = 1; break;
                
                case DWARF_AttributeForm_strp: size = offset_size; break;
                case DWARF_AttributeForm_line_strp: size = offset_size; break;
                case DWARF_AttributeForm_strp_sup: size = offset_size; break;
                
                case DWARF_AttributeForm_strx: uleb128 = 1; break;
                case DWARF_AttributeForm_strx1: size = 1; break;
                case DWARF_AttributeForm_strx2: size = 2; break;
                case DWARF_AttributeForm_strx3: size = 3; break;
                case DWARF_AttributeForm_strx4: size = 4; break;
              }
            }
            
            // execute decoding rules
            U64 val = 0;
            U8 *dataptr = 0;
            {
              if (size > 0){
                if (ptr + size <= unit_opl){
                  MemoryCopy(&val, ptr, size);
                  ptr += size;
                }
                else{
                  // TODO: preserve error info
                  decoding_error = 1;
                  goto exit_unit_loop;
                }
              }
              else if (uleb128 || sleb128){
                U8 *val_ptr = ptr;
                DWARF_LEB128_ADV(ptr, unit_opl, success);
                if (!success){
                  // TODO: preserve error info
                  decoding_error = 1;
                  goto exit_unit_loop;
                }
                else{
                  if (uleb128){
                    val = dwarf_leb128_decode_U64(val_ptr, ptr);
                  }
                  else{
                    val = (U64)dwarf_leb128_decode_S64(val_ptr, ptr);
                  }
                }
              }
              else if (in_abbrev){
                if (abbrev_decl->implicit_const != 0){
                  val = (U64)abbrev_decl->implicit_const[i];
                }
              }
              else if (auto_1){
                val = 1;
              }
              if (block){
                dataptr = ptr;
                ptr += val;
              }
              else if (null_terminated){
                dataptr = ptr;
                for (;ptr < unit_opl && *ptr != 0;) ptr += 1;
                val = (U64)(ptr - dataptr);
              }
            }
            
            // save attribute
            attrib_val->val = val;
            attrib_val->dataptr = dataptr;
          }
          
          // emit entry
          if (entry_consptr != 0){
            SLLQueuePush_N(entry_consptr->first_child, entry_consptr->last_child,
                           entry, next_sibling);
            entry_consptr->child_count += 1;
            entry->parent = entry_consptr;
          }
          entry_count += 1;
          entry->abbrev_decl = abbrev_decl;
          entry->attrib_vals = attrib_vals;
          
          // move consptr down if has children
          if (abbrev_decl->has_children){
            entry_consptr = entry;
          }
          
          skip_entry:;
        }
      }
      exit_unit_loop:;
      
      // TODO: notice errors, emit them, and exit loop here
      if (decoding_error){
        break;
      }
      
      // extract root attributes
      U64 language = 0;
      U64 str_offsets_base = 0;
      U64 line_info_offset = 0;
      U64 vbase = 0;
      U64 addr_base = 0;
      U64 rnglists_base = 0;
      U64 loclists_base = 0;
      if (entry_root != 0){
        
        // pull out attributes
        DWARF_AbbrevDecl *root_abbrev_decl = entry_root->abbrev_decl;
        DWARF_AbbrevAttribSpec *attrib_specs = root_abbrev_decl->attrib_specs;
        DWARF_InfoAttribVal *attrib_vals = entry_root->attrib_vals;
        U32 attrib_count = root_abbrev_decl->attrib_count;
        
        // examine each attribute
        DWARF_AbbrevAttribSpec *attrib_spec = attrib_specs;
        DWARF_InfoAttribVal *attrib_val = attrib_vals;
        for (U32 i = 0; i < attrib_count; i += 1, attrib_spec += 1, attrib_val += 1){
          
          // determine if there is a root attribute to extract here
          U64 *target_u64 = 0;
          switch (attrib_spec->name){
            case DWARF_AttributeName_language:         target_u64 = &language;         break;
            case DWARF_AttributeName_str_offsets_base: target_u64 = &str_offsets_base; break;
            case DWARF_AttributeName_stmt_list:        target_u64 = &line_info_offset; break;
            case DWARF_AttributeName_low_pc:           target_u64 = &vbase;            break;
            case DWARF_AttributeName_addr_base:        target_u64 = &addr_base;        break;
            case DWARF_AttributeName_rnglists_base:    target_u64 = &rnglists_base;    break;
            case DWARF_AttributeName_loclists_base:    target_u64 = &loclists_base;    break;
          }
          
          // set target from attrib value
          if (target_u64 != 0){
            *target_u64 = attrib_val->val;
          }
        }
      }
      
      // allocate unit
      DWARF_InfoUnit *unit = push_array(arena, DWARF_InfoUnit, 1);
      
      // fill & emit unit
      SLLQueuePush(unit_first, unit_last, unit);
      unit_count += 1;
      // [header]
      unit->dwarf_version = version;
      unit->offset_size   = offset_size;
      unit->address_size  = address_size;
      // [root attributes]
      unit->language = (DWARF_Language)language;
      unit->str_offsets_base = str_offsets_base;
      unit->line_info_offset = line_info_offset;
      unit->vbase = vbase;
      unit->addr_base = addr_base;
      unit->rnglists_base = rnglists_base;
      unit->loclists_base = loclists_base;
      // [entries]
      unit->entry_root = entry_root;
      unit->entry_count = entry_count;
      
    }
    
    // set ptr to end of this unit
    ptr = unit_opl;
  }
  
  // fill result
  DWARF_InfoParsed *result = push_array(arena, DWARF_InfoParsed, 1);
  result->unit_first = unit_first;
  result->unit_last = unit_last;
  result->unit_count = unit_count;
  result->decoding_error = decoding_error;
  return(result);
}

static DWARF_AbbrevParsed*
dwarf_abbrev_from_data(Arena *arena, String8 data, DWARF_AbbrevParams *params){
  /* .debug_abbrev
  ** Layout
  **  List(Tag)
  **  Tag       = { id:ULEB128, tag:ULEB128, has_children:B8, ListNullTerminated(Attribute) }
  **  Attribute = { name:ULEB128, form:ULEB128, (val:SLEB128)? }
  */
  
  // unit index range to extract
  U64 unit_idx_min = params->unit_idx_min;
  U64 unit_idx_max = params->unit_idx_max;
  
  // empty unit list
  DWARF_AbbrevUnit *unit_first = 0;
  DWARF_AbbrevUnit *unit_last = 0;
  U64 unit_count = 0;
  B32 decoding_error = 0;
  
  // whole section loop
  U64 unit_idx = 0;
  U8 *ptr = data.str;
  U8 *opl = data.str + data.size;
  for (;ptr < opl; unit_idx += 1){
    
    // early escape on unit idx
    if (unit_idx > unit_idx_max){
      break;
    }
    
    // determine whether to full parse this unit
    B32 full_parse = (unit_idx_min <= unit_idx);
    
    // save unit offset
    U64 abbrev_unit_offset = (U64)(ptr - data.str);
    
    // allocate unit
    DWARF_AbbrevUnit *unit = push_array(arena, DWARF_AbbrevUnit, 1);
    
    // empty abbrev list
    DWARF_AbbrevDecl *abbrev_first = 0;
    DWARF_AbbrevDecl *abbrev_last = 0;
    U64 abbrev_count = 0;
    
    // abbrev decl loop
    for (;ptr < opl;){
      B32 success = 1;
      
      // mark abbrev_code field
      U8 *abbrev_code_ptr = ptr;
      DWARF_LEB128_ADV(ptr, opl, success);
      
      // null abbrev code means end of unit
      if (success && *abbrev_code_ptr == 0){
        break;
      }
      
      // mark tag
      U8 *tag_ptr = ptr;
      DWARF_LEB128_ADV(ptr, opl, success);
      U8 *end_tag_ptr = ptr;
      
      // extract has_children
      B8 has_children = 0;
      if (ptr < opl){
        has_children = *ptr;
        ptr += 1;
      }
      else{
        success = 0;
      }
      
      // count attributes
      U8 *attrib_start_ptr = ptr;
      U32 attrib_count = 0;
      B32 has_implicit_const = 0;
      if (success){
        for (;;){
          // decode normal attribute layout
          U8 *attrib_name = ptr;
          DWARF_LEB128_ADV(ptr, opl, success);
          U8 *attrib_form = ptr;
          DWARF_LEB128_ADV(ptr, opl, success);
          
          // handle special case implicit_const
          if (success && *attrib_form == (U8)DWARF_AttributeForm_implicit_const){
            DWARF_LEB128_ADV(ptr, opl, success);
            has_implicit_const = 1;
          }
          
          // termination conditions
          if (ptr == opl ||
              (*attrib_name == 0 && *attrib_form == 0)){
            break;
          }
          
          // increment
          attrib_count += 1;
        }
      }
      
      // build the abbreviation declaration
      if (full_parse && success){
        
        // allocate abbrev
        DWARF_AbbrevDecl *abbrev = push_array(arena, DWARF_AbbrevDecl, 1);
        DWARF_AbbrevAttribSpec *attribs =
          push_array_no_zero(arena, DWARF_AbbrevAttribSpec, attrib_count);
        U64 *implicit_const = 0;
        if (has_implicit_const){
          implicit_const = push_array(arena, U64, attrib_count);
        }
        
        // extract abbrev fields
        U32 abbrev_code = dwarf_leb128_decode_U32(abbrev_code_ptr, tag_ptr);
        U32 tag = dwarf_leb128_decode_U32(tag_ptr, end_tag_ptr);
        
        U8 *attrib_ptr = attrib_start_ptr;
        DWARF_AbbrevAttribSpec *attrib = attribs;
        for (U32 i = 0; i < attrib_count; i += 1, attrib += 1){
          // mark attribute fields
          U8 *attrib_name = attrib_ptr;
          DWARF_LEB128_ADV_NOCAP(attrib_ptr);
          U8 *attrib_form = attrib_ptr;
          DWARF_LEB128_ADV_NOCAP(attrib_ptr);
          
          // extract attribute fields
          U32 name = dwarf_leb128_decode_U32(attrib_name, attrib_form);
          U32 form = dwarf_leb128_decode_U32(attrib_form, attrib_ptr);
          
          // fill attribute spec
          attrib->name = (DWARF_AttributeName)name;
          attrib->form = (DWARF_AttributeForm)form;
          
          // handle special case implicit_const
          if (form == DWARF_AttributeForm_implicit_const){
            U8 *attrib_value = attrib_ptr;
            DWARF_LEB128_ADV_NOCAP(attrib_ptr);
            S64 value = dwarf_leb128_decode_S64(attrib_form, attrib_ptr);
            implicit_const[i] = value;
          }
        }
        
        // fill abbreviation
        SLLQueuePush(abbrev_first, abbrev_last, abbrev);
        abbrev_count += 1;
        abbrev->abbrev_code = abbrev_code;
        abbrev->tag = (DWARF_Tag)tag;
        abbrev->has_children = has_children;
        abbrev->attrib_count = attrib_count;
        abbrev->attrib_specs = attribs;
        abbrev->implicit_const = implicit_const;
      }
      
      // handle failure
      if (!success){
        // TODO: emit error message
        decoding_error = 1;
        goto done_parse;
      }
    }
    
    // fill unit
    if (full_parse){
      SLLQueuePush(unit_first, unit_last, unit);
      unit_count += 1;
      unit->offset = abbrev_unit_offset;
      unit->first  = abbrev_first;
      unit->last   = abbrev_last;
      unit->count  = abbrev_count;
    }
  }
  
  done_parse:;
  
  // fill result
  DWARF_AbbrevParsed *result = push_array(arena, DWARF_AbbrevParsed, 1);
  result->unit_first = unit_first;
  result->unit_last  = unit_last;
  result->unit_count = unit_count;
  result->decoding_error = decoding_error;
  return(result);
}
#endif
