Files
raddebugger/src/linker/pdb_ext/pdb_builder.c
T

3189 lines
102 KiB
C

// Copyright (c) Epic Games Tools
// Licensed under the MIT license (https://opensource.org/license/mit/)
////////////////////////////////
internal U64
pdb_hash_table_compute_load_factor(U64 count)
{
// PDB/include/map.h:cdrLoadMax()
U64 load_factor = count * 2/3 + 1;
return load_factor;
}
internal void
pdb_hash_table_alloc(PDB_HashTable *ht, U32 max)
{
ProfBeginFunction();
ht->arena = arena_alloc();
ht->bucket_arr = push_array(ht->arena, PDB_HashTableBucket, max);
ht->present_bits = bit_array_init32(ht->arena, max);
ht->deleted_bits = bit_array_init32(ht->arena, max);
ht->max = max;
ht->count = 0;
ProfEnd();
}
internal void
pdb_hash_table_release(PDB_HashTable *ht)
{
ProfBeginFunction();
arena_release(ht->arena);
MemoryZeroStruct(ht);
ProfEnd();
}
internal PDB_HashTableParseError
pdb_hash_table_from_data(PDB_HashTable *ht,
String8 data,
B32 has_local_data,
PDB_HashTableUnpackFunc *unpack_func,
void *unpack_ud,
U64 *read_bytes_out)
{
ProfBeginFunction();
PDB_HashTableParseError error = PDB_HashTableParseError_OK;
U64 cursor = 0;
U32 local_data_size = 0;
String8 local_data = str8(0,0);
U32 count = 0;
U32 max = 0;
U32Array present_bits = {0};
U32Array deleted_bits = {0};
do {
error = PDB_HashTableParseError_OUT_OF_BYTES;
if (has_local_data) {
if (cursor + sizeof(local_data_size) > data.size) {
break;
}
cursor += str8_deserial_read_struct(data, cursor, &local_data_size);
if (cursor + local_data_size > data.size) {
break;
}
cursor += str8_deserial_read_block(data, cursor, local_data_size, &local_data);
}
if (cursor + sizeof(count) > data.size) {
break;
}
cursor += str8_deserial_read_struct(data, cursor, &count);
if (cursor + sizeof(max) > data.size) {
break;
}
cursor += str8_deserial_read_struct(data, cursor, &max);
cursor += pdb_read_bit_vector_string(data, cursor, &present_bits);
cursor += pdb_read_bit_vector_string(data, cursor, &deleted_bits);
error = PDB_HashTableParseError_OK;
} while(0);
if (error == PDB_HashTableParseError_OK) {
U64 load_factor = pdb_hash_table_compute_load_factor(max);
B32 is_count_ok = count < max;
B32 is_load_factor_ok = count < load_factor;
B32 is_present_bits_ok = present_bits.count <= AlignPow2(max, 32);
B32 is_deleted_bits_ok = deleted_bits.count <= AlignPow2(max, 32);
if (is_count_ok && is_load_factor_ok && is_present_bits_ok && is_deleted_bits_ok) {
Arena *arena = arena_alloc();
PDB_HashTableBucket *bucket_arr = push_array_no_zero(arena, PDB_HashTableBucket, max);
U32Array present_bits_new = bit_array_init32(arena, max);
U32Array deleted_bits_new = bit_array_init32(arena, max);
MemoryCopyTyped(&present_bits_new.v[0], &present_bits.v[0], present_bits.count);
MemoryCopyTyped(&deleted_bits_new.v[0], &deleted_bits.v[0], deleted_bits.count);
// unpack buckets
U64 read_count = 0;
for (U64 bucket_idx = 0; bucket_idx < max; bucket_idx += 1) {
if (bit_array_is_bit_set(present_bits_new, bucket_idx)) {
if (bit_array_is_bit_set(deleted_bits_new, bucket_idx)) {
error = PDB_HashTableParseError_CORRUPTED;
break;
}
if (read_count >= count) {
error = PDB_HashTableParseError_CORRUPTED;
break;
}
String8 key;
String8 value;
B32 has_unpack_failed = unpack_func(unpack_ud, local_data, data, &cursor, &key, &value);
if (has_unpack_failed) {
error = PDB_HashTableParseError_CORRUPTED;
break;
}
bucket_arr[bucket_idx].key = key;
bucket_arr[bucket_idx].value = value;
read_count += 1;
}
}
if (error == PDB_HashTableParseError_OK) {
ht->arena = arena;
ht->bucket_arr = bucket_arr;
ht->present_bits = present_bits_new;
ht->deleted_bits = deleted_bits_new;
ht->count = count;
ht->max = max;
if (read_bytes_out) {
// TBH data format should tell parser upfront size of the hash table
*read_bytes_out = cursor;
}
} else {
arena_release(arena);
}
} else {
error = PDB_HashTableParseError_CORRUPTED;
}
}
ProfEnd();
return error;
}
internal int
pdb_hash_table_bucket_is_before(void *raw_a, void *raw_b)
{
PDB_HashTableBucket *a = *(PDB_HashTableBucket **)raw_a, *b = *(PDB_HashTableBucket **)raw_b;
return a->insert_idx < b->insert_idx;
}
internal String8
pdb_data_from_hash_table(Arena *arena, PDB_HashTable *ht, PDB_HashTablePackFunc *pack_func, void *pack_ud)
{
ProfBeginFunction();
Temp scratch = scratch_begin(&arena, 1);
String8List kv_srl = {0}; str8_serial_begin(scratch.arena, &kv_srl);
PDB_HashTableBucket **buckets = pdb_hash_table_get_present_buckets(scratch.arena, ht);
for EachIndex(i, ht->count) { pack_func(scratch.arena, &kv_srl, buckets[i], buckets[i]->key, buckets[i]->value, pack_ud); }
// compute count of present words that are needed
U64 present_word_count = 0;
U64 present_msb = bit_array_scan_right_to_left32(ht->present_bits, 0, ht->present_bits.count*32, 1);
if (present_msb < ht->present_bits.count*32) { present_word_count = present_msb / 32 + 1; }
// compute count of deleted words that are needed
U64 deleted_word_count = 0;
U64 deleted_msb = bit_array_scan_right_to_left32(ht->deleted_bits, 0, ht->deleted_bits.count*32, 1);
if (deleted_msb < ht->deleted_bits.count*32) { deleted_word_count = deleted_msb / 32 + 1; }
// write hash table
String8List ht_srl = {0}; str8_serial_begin(scratch.arena, &ht_srl);
str8_serial_push_u32 (scratch.arena, &ht_srl, ht->count);
str8_serial_push_u32 (scratch.arena, &ht_srl, ht->max);
str8_serial_push_u32 (scratch.arena, &ht_srl, present_word_count);
str8_serial_push_array(scratch.arena, &ht_srl, &ht->present_bits.v[0], present_word_count);
str8_serial_push_u32 (scratch.arena, &ht_srl, deleted_word_count);
str8_serial_push_array(scratch.arena, &ht_srl, &ht->deleted_bits.v[0], deleted_word_count);
str8_list_concat_in_place(&ht_srl, &kv_srl);
String8 result = str8_serial_end(arena, &ht_srl);
scratch_end(scratch);
ProfEnd();
return result;
}
internal void
pdb_hash_table_grow(PDB_HashTable *ht, U64 new_capacity)
{
ProfBeginFunction();
PDB_HashTable new_ht;
pdb_hash_table_alloc(&new_ht, new_capacity);
for (U32 i = 0; i < ht->max; ++i) {
if (bit_array_is_bit_set(ht->present_bits, i)) {
PDB_HashTableBucket *bucket = &ht->bucket_arr[i];
PDB_HashTableBucket *is_set = pdb_hash_table_try_set(&new_ht, bucket->key, bucket->value);
is_set->insert_idx = bucket->insert_idx;
Assert(is_set);
}
}
pdb_hash_table_release(ht);
*ht = new_ht;
ProfEnd();
}
internal U32
pdb_hash_table_hash(String8 key)
{
return (U16)pdb_hash_v1(key);
}
internal PDB_HashTableBucket *
pdb_hash_table_try_set(PDB_HashTable *ht, String8 key, String8 value)
{
ProfBeginFunction();
PDB_HashTableBucket *is_set = 0;
U32 best_ibucket = pdb_hash_table_hash(key) % ht->max;
U32 ibucket = best_ibucket;
do {
B32 is_present = pdb_hash_table_is_present(ht, ibucket);
if ( ! is_present) {
PDB_HashTableBucket *bucket = &ht->bucket_arr[ibucket];
bucket->key = push_str8_copy(ht->arena, key);
bucket->value = push_str8_copy(ht->arena, value);
bucket->insert_idx = ht->count;
bit_array_set_bit32(ht->present_bits, ibucket, 1);
bit_array_set_bit32(ht->deleted_bits, ibucket, 0);
ht->count += 1;
is_set = bucket;
break;
}
ibucket = (ibucket + 1) % ht->max;
} while (ibucket != best_ibucket);
ProfEnd();
return is_set;
}
internal void
pdb_hash_table_set(PDB_HashTable *ht, String8 key, String8 value)
{
ProfBeginFunction();
// should resize?
U64 load_factor = pdb_hash_table_compute_load_factor(ht->max);
if (ht->count + 1 >= load_factor) {
pdb_hash_table_grow(ht, load_factor * 2);
}
// set new item
PDB_HashTableBucket *is_set = pdb_hash_table_try_set(ht, key, value);
AssertAlways(is_set);
ProfEnd();
}
internal B32
pdb_hash_table_get(PDB_HashTable *ht, String8 key, String8 *value_out)
{
ProfBeginFunction();
B32 is_get_ok = 0;
U32 best_ibucket = pdb_hash_table_hash(key) % ht->max;
U32 ibucket = best_ibucket;
do {
B32 is_present = pdb_hash_table_is_present(ht, ibucket);
if (is_present) {
PDB_HashTableBucket *bucket = &ht->bucket_arr[ibucket];
B32 is_match = str8_match(bucket->key, key, 0);
if (is_match) {
*value_out = bucket->value;
is_get_ok = 1;
break;
}
} else {
break;
}
ibucket = (ibucket + 1) % ht->max;
} while (ibucket != best_ibucket);
ProfEnd();
return is_get_ok;
}
internal void
pdb_hash_table_delete(PDB_HashTable *ht, String8 key)
{
ProfBeginFunction();
U32 best_ibucket = pdb_hash_table_hash(key) % ht->max;
U32 ibucket = best_ibucket;
do {
B32 is_present = pdb_hash_table_is_present(ht, ibucket);
if (!is_present) {
break;
}
PDB_HashTableBucket *bucket = &ht->bucket_arr[ibucket];
int cmp = MemoryCompare(key.str, bucket->key.str, key.size);
if (cmp == 0) {
bit_array_set_bit32(ht->present_bits, ibucket, 0);
bit_array_set_bit32(ht->deleted_bits, ibucket, 1);
ht->count -= 1;
break;
}
ibucket = (ibucket + 1) % ht->max;
} while (ibucket != best_ibucket);
ProfEnd();
}
internal B32
pdb_hash_table_is_present(PDB_HashTable *ht, U32 k)
{
Assert(k < ht->max);
return bit_array_is_bit_set(ht->present_bits, k);
}
internal B32
pdb_hash_table_is_deleted(PDB_HashTable *ht, U32 k)
{
Assert(k < ht->max);
return bit_array_is_bit_set(ht->deleted_bits, k);
}
internal PDB_HashTableBucket **
pdb_hash_table_get_present_buckets(Arena *arena, PDB_HashTable *ht)
{
U64 result_count = 0;
PDB_HashTableBucket **result = push_array(arena, PDB_HashTableBucket *, ht->count);
for EachIndex(bucket_idx, ht->max) {
if (bit_array_is_bit_set(ht->present_bits, bucket_idx)) {
PDB_HashTableBucket *bucket = &ht->bucket_arr[bucket_idx];
Assert(result_count < ht->count);
result[result_count++] = bucket;
}
}
return result;
}
internal void
pdb_hash_table_get_present_keys_and_values(Arena *arena, PDB_HashTable *ht, String8Array *keys_out, String8Array *values_out)
{
*keys_out = str8_array_reserve(arena, ht->count);
*values_out = str8_array_reserve(arena, ht->count);
for (U64 bucket_idx = 0; bucket_idx < ht->max; bucket_idx += 1) {
if (bit_array_is_bit_set(ht->present_bits, bucket_idx)) {
PDB_HashTableBucket *bucket = &ht->bucket_arr[bucket_idx];
Assert(keys_out->count < ht->count);
keys_out->v[keys_out->count++] = bucket->key;
values_out->v[values_out->count++] = bucket->value;
}
}
}
////////////////////////////////
internal
PDB_HASH_TABLE_UNPACK_FUNC(pdb_named_stream_ht_unpack)
{
Assert(!ud);
U32 key_data_offset = max_U32;
*key_value_cursor += str8_deserial_read_struct(key_value_data, *key_value_cursor, &key_data_offset);
U8 *cstr_ptr = local_data.str + key_data_offset;
U8 *cstr_opl = local_data.str + local_data.size;
String8 stream_name = str8_cstring_capped(cstr_ptr, cstr_opl);
// NOTE: stream number is U16 but in the reference they cast to U32
String8 stream_number = {0};
*key_value_cursor += str8_deserial_read_block(key_value_data, *key_value_cursor, sizeof(U32), &stream_number);
*key_out = stream_name;
*value_out = stream_number;
return 0;
}
internal
PDB_HASH_TABLE_UNPACK_FUNC(pdb_hash_adj_ht_unpack)
{
Assert(local_data.size == 0);
if (*key_value_cursor + sizeof(PDB_StringOffset) > key_value_data.size){
return 1;
}
PDB_StringOffset string_offset = 0;
*key_value_cursor += str8_deserial_read_struct(key_value_data, *key_value_cursor, &string_offset);
if (*key_value_cursor + sizeof(CV_TypeIndex) > key_value_data.size) {
return 1;
}
String8 type_index = {0};
*key_value_cursor += str8_deserial_read_block(key_value_data, *key_value_cursor, sizeof(CV_TypeIndex), &type_index);
PDB_StringTable *strtab = (PDB_StringTable*)ud;
String8 type_name = pdb_strtab_string_from_offset(strtab, string_offset);
*key_out = type_name;
*value_out = type_index;
return 0;
}
internal
PDB_HASH_TABLE_UNPACK_FUNC(pdb_src_header_block_ht_unpack)
{
if (*key_value_cursor + sizeof(PDB_StringOffset) > key_value_data.size) {
return 1;
}
PDB_StringOffset path_offset = 0;
*key_value_cursor += str8_deserial_read_struct(key_value_data, *key_value_cursor, &path_offset);
if (path_offset + sizeof(PDB_SrcHeaderBlockEntry) > key_value_data.size) {
return 1;
}
String8 src_header_block_entry = {0};
*key_value_cursor += str8_deserial_read_block(key_value_data, *key_value_cursor, sizeof(PDB_SrcHeaderBlockEntry), &src_header_block_entry);
PDB_StringTable *strtab = (PDB_StringTable*)ud;
String8 path = pdb_strtab_string_from_offset(strtab, path_offset);
*key_out = path;
*value_out = src_header_block_entry;
return 0;
}
internal
PDB_HASH_TABLE_PACK_FUNC(pdb_named_stream_ht_pack)
{
Assert(!ud);
Assert(value.size == sizeof(U32));
str8_serial_push_u32(arena, key_value_srl, bucket->key_offset);
str8_serial_push_string(arena, key_value_srl, value);
}
internal
PDB_HASH_TABLE_PACK_FUNC(pdb_hash_adj_ht_pack)
{
Assert(value.size == sizeof(CV_TypeIndex));
PDB_StringTable *strtab = ud;
PDB_StringIndex string_idx = PDB_INVALID_STRING_INDEX;
B32 is_found = pdb_strtab_search(strtab, key, &string_idx);
Assert(is_found);
PDB_StringOffset type_name_offset = pdb_strtab_string_to_offset(strtab, string_idx);
str8_serial_push_struct(arena, key_value_srl, &type_name_offset);
str8_serial_push_string(arena, key_value_srl, value);
}
internal
PDB_HASH_TABLE_PACK_FUNC(pdb_src_header_block_ht_pack)
{
Assert(value.size == sizeof(PDB_SrcHeaderBlockEntry));
PDB_StringTable *strtab = ud;
PDB_StringIndex path_idx = 0;
B32 is_found = pdb_strtab_search(strtab, key, &path_idx);
Assert(is_found);
PDB_StringOffset path_offset = pdb_strtab_string_to_offset(strtab, path_idx);
str8_serial_push_struct(arena, key_value_srl, &path_offset);
str8_serial_push_string(arena, key_value_srl, value);
}
////////////////////////////////
internal PDB_HashTableParseError
pdb_hash_adj_hash_table_from_data(PDB_HashTable *ht, String8 data, PDB_StringTable *strtab, U64 *read_bytes_out)
{
return pdb_hash_table_from_data(ht, data, 0, pdb_hash_adj_ht_unpack, strtab, read_bytes_out);
}
internal PDB_HashTableParseError
pdb_src_header_block_ht_from_data(PDB_HashTable *ht, String8 data, PDB_StringTable *strtab, U64 *read_bytes_out)
{
return pdb_hash_table_from_data(ht, data, 0, pdb_src_header_block_ht_unpack, strtab, read_bytes_out);
}
internal PDB_HashTableParseError
pdb_named_stream_ht_from_data(PDB_HashTable *ht, String8 data, U64 *read_bytes_out)
{
return pdb_hash_table_from_data(ht, data, 1, pdb_named_stream_ht_unpack, 0, read_bytes_out);
}
internal String8
pdb_data_from_hash_adj_hash_table(Arena *arena, PDB_HashTable *ht, PDB_StringTable *strtab)
{
return pdb_data_from_hash_table(arena, ht, pdb_hash_adj_ht_pack, strtab);
}
internal String8
pdb_data_from_src_header_block_ht(Arena *arena, PDB_HashTable *ht, PDB_StringTable *strtab)
{
return pdb_data_from_hash_table(arena, ht, pdb_src_header_block_ht_pack, strtab);
}
internal String8
pdb_data_from_named_stream_ht(Arena *arena, PDB_HashTable *ht)
{
Temp scratch = scratch_begin(&arena, 1);
// serialize names (layout must be in insert order)
String8List key_data_srl = {0}; str8_serial_begin(scratch.arena, &key_data_srl);
{
PDB_HashTableBucket **buckets = pdb_hash_table_get_present_buckets(scratch.arena, ht);
radsort(buckets, ht->count, pdb_hash_table_bucket_is_before);
for EachIndex(i, ht->count) {
buckets[i]->key_offset = key_data_srl.total_size;
str8_serial_push_cstr(scratch.arena, &key_data_srl, buckets[i]->key);
}
}
// serialize hash table
String8 ht_data = pdb_data_from_hash_table(arena, ht, pdb_named_stream_ht_pack, 0);
// put names and hash table together
String8List srl = {0}; str8_serial_begin(scratch.arena, &srl);
str8_serial_push_u32(scratch.arena, &srl, safe_cast_u32(key_data_srl.total_size));
str8_serial_push_data_list(scratch.arena, &srl, key_data_srl.first);
str8_serial_push_string(scratch.arena, &srl, ht_data);
String8 result = str8_serial_end(arena, &srl);
scratch_end(scratch);
return result;
}
////////////////////////////////
internal void
pdb_strtab_alloc(PDB_StringTable *strtab, U32 max)
{
ProfBeginFunction();
U64 bucket_max = (U64)((F64)max * 1.3);
bucket_max += 1; // reserve space for null string
strtab->arena = arena_alloc();
strtab->version = 1;
strtab->size = 0;
strtab->bucket_count = 0;
strtab->bucket_max = bucket_max;
strtab->ibucket_array = push_array(strtab->arena, U32, strtab->bucket_max);
MemorySet(strtab->ibucket_array, 0xff, sizeof(strtab->ibucket_array[0]) * strtab->bucket_max);
strtab->bucket_array = push_array(strtab->arena, PDB_StringTableBucket *, strtab->bucket_max);
// string table always has a null for first entry
pdb_strtab_add(strtab, str8_lit(""));
ProfEnd();
}
internal void
pdb_strtab_build(PDB_StringTable *strtab, MSF_Context *msf, MSF_StreamNumber sn)
{
ProfBeginFunction();
Temp scratch = scratch_begin(0,0);
// serialize bucket data
U8 *string_buffer = push_array_no_zero(scratch.arena, U8, strtab->size);
U32 *bucket_offset_arr = push_array(scratch.arena, U32, strtab->bucket_max);
for (U32 bucket_idx = 0; bucket_idx < strtab->bucket_max; bucket_idx += 1) {
PDB_StringTableBucket *bucket = strtab->bucket_array[bucket_idx];
if (bucket) {
// store string offset
Assert(bucket->offset + bucket->data.size <= strtab->size);
bucket_offset_arr[bucket_idx] = bucket->offset;
// write c string at bucket offset
U8 *str_ptr = string_buffer + bucket->offset;
MemoryCopy(str_ptr, bucket->data.str, bucket->data.size);
str_ptr[bucket->data.size] = '\0';
}
}
// fill out header
PDB_StringTableHeader header;
header.magic = PDB_StringTableHeader_MAGIC;
header.version = strtab->version;
// reserve memory for entire string table
MSF_UInt reserve_size = sizeof(header)
+ sizeof(strtab->size)
+ strtab->size
+ sizeof(bucket_offset_arr[0]) * strtab->bucket_max
+ sizeof(strtab->bucket_count);
msf_stream_reserve(msf, sn, reserve_size);
// write out string table
msf_stream_write_struct(msf, sn, &header);
msf_stream_write_struct(msf, sn, &strtab->size);
msf_stream_write_array (msf, sn, string_buffer, strtab->size);
msf_stream_write_struct(msf, sn, &strtab->bucket_max);
msf_stream_write_array (msf, sn, bucket_offset_arr, strtab->bucket_max);
msf_stream_write_u32(msf, sn, strtab->bucket_count - 1); // 1 for null
scratch_end(scratch);
ProfEnd();
}
internal void
pdb_strtab_release(PDB_StringTable *strtab)
{
ProfBeginFunction();
arena_release(strtab->arena);
MemoryZeroStruct(strtab);
ProfEnd();
}
internal U32
pdb_strtab_get_serialized_size(PDB_StringTable *strtab)
{
U32 result = 0;
result += sizeof(PDB_StringTableHeader);
result += sizeof(U32); // strtab size
result += strtab->size;
result += sizeof(U32); // bucket count
result += sizeof(U32) * strtab->bucket_max;
result += sizeof(U32); // string count
return result;
}
internal U32
pdb_strtab_hash(PDB_StringTable *strtab, String8 string)
{
U32 hash = 0;
switch (strtab->version) {
case 1: hash = pdb_hash_v1(string); break;
default: NotImplemented; break;
}
U32 ibucket = hash % strtab->bucket_max;
return ibucket;
}
internal B32
pdb_strtab_add_(PDB_StringTable *strtab, U64 hash, PDB_StringTableBucket *bucket)
{
U64 best_bucket_idx = hash;
U64 bucket_idx = best_bucket_idx;
do {
if (strtab->bucket_array[bucket_idx] == 0) {
strtab->ibucket_array[bucket->istr] = bucket_idx;
strtab->bucket_array[bucket_idx] = bucket;
strtab->size += bucket->data.size + /* null: */ 1;
return 1;
}
bucket_idx = (bucket_idx + 1) % strtab->bucket_max;
} while (best_bucket_idx != bucket_idx);
return 0;
}
internal void
pdb_strtab_add_cv_string_hash_table(PDB_StringTable *strtab, CV_StringHashTable string_ht)
{
ProfBeginFunction();
// reserve enough slots for new strings
pdb_strtab_grow(strtab, string_ht.total_insert_count);
// upfront push buckets
PDB_StringTableBucket *buckets = push_array_no_zero(strtab->arena, PDB_StringTableBucket, string_ht.total_insert_count);
U64 base_offset = strtab->size;
// proceed to fill out buckets & add them to the string table
for (U64 bucket_idx = 0, string_idx = 0; bucket_idx < string_ht.bucket_cap; ++bucket_idx) {
if (string_ht.buckets[bucket_idx] != 0) {
PDB_StringTableBucket *dst = &buckets[string_idx++];
dst->data = string_ht.buckets[bucket_idx]->string;
dst->offset = base_offset + string_ht.buckets[bucket_idx]->u.offset;
dst->istr = strtab->bucket_count++;
// TODO: precompute hashes in parallel
U64 hash = pdb_strtab_hash(strtab, dst->data);
B32 was_added = pdb_strtab_add_(strtab, hash, dst);
Assert(was_added);
}
}
ProfEnd();
}
internal B32
pdb_strtab_try_add(PDB_StringTable *strtab, String8 string, PDB_StringIndex *index_out)
{
PDB_StringTableBucket *bucket = push_array(strtab->arena, PDB_StringTableBucket, 1);
bucket->data = push_str8_copy(strtab->arena, string);
bucket->offset = strtab->size;
bucket->istr = (PDB_StringIndex)strtab->bucket_count++;
U32 hash = pdb_strtab_hash(strtab, string);
B32 was_added = pdb_strtab_add_(strtab, hash, bucket);
*index_out = bucket->istr;
return was_added;
}
internal void
pdb_strtab_grow(PDB_StringTable *strtab, U64 new_max)
{
ProfBeginFunction();
PDB_StringTable new_strtab;
pdb_strtab_alloc(&new_strtab, new_max);
// start with 1 because null bucket is already added during string table alloc
for (PDB_StringIndex istr = 1; istr < strtab->bucket_max; ++istr) {
U32 ibucket = strtab->ibucket_array[istr];
B32 is_bucket_null = ibucket >= strtab->bucket_max;
if (is_bucket_null) {
continue;
}
PDB_StringTableBucket *bucket = strtab->bucket_array[ibucket];
PDB_StringIndex new_istr;
B32 is_bucket_pushed = pdb_strtab_try_add(&new_strtab, bucket->data, &new_istr);
Assert(is_bucket_pushed);
Assert(new_istr == istr);
U32 new_ibucket = new_strtab.ibucket_array[new_istr];
PDB_StringTableBucket *new_bucket = new_strtab.bucket_array[new_ibucket];
Assert(new_bucket->offset == bucket->offset);
}
*strtab = new_strtab;
ProfEnd();
}
internal PDB_StringIndex
pdb_strtab_add(PDB_StringTable *strtab, String8 string)
{
PDB_StringIndex index = 0;
B32 is_pushed = pdb_strtab_try_add(strtab, string, &index);
if (!is_pushed) {
// increase number of slots in the hash table
pdb_strtab_grow(strtab, strtab->bucket_max * 2);
// now we have enough slots for the new string
is_pushed = pdb_strtab_try_add(strtab, string, &index);
AssertAlways(is_pushed);
}
return index;
}
internal B32
pdb_strtab_search(PDB_StringTable *strtab, String8 string, PDB_StringIndex *index_out)
{
B32 is_found = 0;
U32 best_ibucket = pdb_strtab_hash(strtab, string);
U32 ibucket = best_ibucket;
do {
PDB_StringTableBucket *bucket = strtab->bucket_array[ibucket];
if (bucket == NULL) {
break;
}
if (str8_match(bucket->data, string, 0)) {
*index_out = bucket->istr;
is_found = 1;
break;
}
ibucket = (ibucket + 1) % strtab->bucket_max;
} while (ibucket != best_ibucket);
return is_found;
}
internal String8
pdb_strtab_string_from_offset(PDB_StringTable *strtab, PDB_StringOffset offset)
{
String8 string = str8(0,0);
for (U32 ibucket = 0; ibucket < strtab->bucket_max; ++ibucket) {
PDB_StringTableBucket *bucket = strtab->bucket_array[ibucket];
if (bucket) {
if (bucket->offset == offset) {
string = bucket->data;
break;
}
}
}
return string;
}
internal PDB_StringOffset
pdb_strtab_string_to_offset(PDB_StringTable *strtab, PDB_StringIndex stridx)
{
Assert(stridx < strtab->bucket_max);
U32 ibucket = strtab->ibucket_array[stridx];
PDB_StringOffset offset = strtab->bucket_array[ibucket]->offset;
return offset;
}
////////////////////////////////
internal B32
pdb_extract_type_server_info(String8 raw_msf, MSF_RawStreamTable *st, MSF_StreamNumber sn, Rng1U64 *ti_range_out, Rng1U64 *leaf_range_out)
{
Temp scratch = scratch_begin(0,0);
B32 is_ok = 0;
String8 version_data = msf_data_from_stream_number_ex(scratch.arena, raw_msf, st, sn, r1u64(0, sizeof(PDB_TpiVersion)), 8);
PDB_TpiVersion *version = str8_deserial_get_raw_ptr(version_data, 0, sizeof(*version));
if (version) {
switch (*version) {
case PDB_TpiVersion_IMPV80: {
String8 header_size_data = msf_data_from_stream_number_ex(scratch.arena, raw_msf, st, sn, r1u64(sizeof(*version), sizeof(*version) + sizeof(U32)), 8);
U32 *header_size = str8_deserial_get_raw_ptr(header_size_data, 0, sizeof(*header_size));
if (header_size && *header_size >= sizeof(PDB_TpiVersion) + sizeof(U32)) {
String8 header_data = msf_data_from_stream_number_ex(scratch.arena, raw_msf, st, sn, r1u64(0, *header_size), 8);
PDB_TpiHeader *header = str8_deserial_get_raw_ptr(header_data, 0, sizeof(*header));
if (*header_size + header->leaf_data_size <= st->streams[sn].size) {
*ti_range_out = r1u64(header->ti_lo, header->ti_hi);
*leaf_range_out = r1u64(*header_size, *header_size + header->leaf_data_size);
is_ok = 1;
} else {
NotImplemented; // TODO: handle error
}
} else {
NotImplemented; // TODO: handle error
}
} break;
default: break;
}
}
scratch_end(scratch);
return is_ok;
}
internal PDB_TypeServer *
pdb_type_server_alloc(U64 bucket_cap)
{
ProfBeginFunction();
AssertAlways(0x1000 <= bucket_cap && bucket_cap <= 0x40000);
Arena *arena = arena_alloc();
PDB_TypeServer *ts = push_array(arena, PDB_TypeServer, 1);
ts->arena = arena;
ts->hash_sn = MSF_INVALID_STREAM_NUMBER;
ts->ti_lo = CV_MinComplexTypeIndex;
ts->bucket_cap = bucket_cap;
ts->buckets = push_array(arena, PDB_TypeBucket *, ts->bucket_cap);
pdb_hash_table_alloc(&ts->hash_adj, 32);
ProfEnd();
return ts;
}
internal
THREAD_POOL_TASK_FUNC(pdb_write_type_to_bucket_map_32_task)
{
PDB_WriteTypeToBucketMap *task = raw_task;
U64 bucket_idx = task_id;
U32 bucket_idx32 = safe_cast_u32(bucket_idx);
PDB_TypeServer *ts = task->ts;
PDB_TypeBucket *head = ts->buckets[bucket_idx];
for (PDB_TypeBucket *bucket = head; bucket != 0; bucket = bucket->next) {
Assert(bucket->type_index >= ts->ti_lo);
Assert(bucket->type_index - ts->ti_lo < ts->leaf_list.node_count);
CV_TypeIndex type_idx = bucket->type_index - ts->ti_lo;
Assert(task->map[type_idx] == 0);
task->map[type_idx] = bucket_idx32;
}
}
internal PDB_TypeHashStreamInfo
pdb_type_hash_stream_build(TP_Context *tp,
PDB_TypeServer *ts,
PDB_StringTable *strtab,
MSF_Context *msf,
PDB_TpiOffHint *hint_arr,
U64 hint_count)
{
ProfBeginFunction();
Temp scratch = scratch_begin(0,0);
// write (type index -> bucket index) map
//
// zero-out entire map so non-UDTs type indices, that are NOT in the hash table,
// map to zero offset
U32 *type_to_bucket_map = push_array(scratch.arena, U32, ts->leaf_list.node_count);
{
ProfBegin("Bucket Map");
PDB_WriteTypeToBucketMap type_to_bucket_task;
type_to_bucket_task.ts = ts;
type_to_bucket_task.map = type_to_bucket_map;
tp_for_parallel(tp, 0, ts->bucket_cap, pdb_write_type_to_bucket_map_32_task, &type_to_bucket_task);
ProfEnd();
}
ProfBegin("MSF Write");
// write data to stream
if (ts->hash_sn == MSF_INVALID_STREAM_NUMBER) {
ts->hash_sn = msf_stream_alloc(msf);
}
msf_stream_seek_start(msf, ts->hash_sn);
PDB_OffsetSize hash_vals;
hash_vals.off = msf_stream_get_pos(msf, ts->hash_sn);
hash_vals.size = sizeof(type_to_bucket_map[0]) * ts->leaf_list.node_count;
msf_stream_write(msf, ts->hash_sn, &type_to_bucket_map[0], hash_vals.size);
PDB_OffsetSize hint_offs;
hint_offs.off = msf_stream_get_pos(msf, ts->hash_sn);
hint_offs.size = sizeof(hint_arr[0]) * hint_count;
msf_stream_write(msf, ts->hash_sn, &hint_arr[0], hint_offs.size);
PDB_OffsetSize hash_adj = {0};
if (ts->hash_adj.count) {
// write bucket adjust info
String8 hash_adj_data = pdb_data_from_hash_adj_hash_table(scratch.arena, &ts->hash_adj, strtab);
hash_adj.off = msf_stream_get_pos(msf, ts->hash_sn);
hash_adj.size = hash_adj_data.size;
msf_stream_write_string(msf, ts->hash_sn, hash_adj_data);
}
ProfEnd();
// fill out result
PDB_TypeHashStreamInfo result;
result.hash_vals = hash_vals;
result.ti_offs = hint_offs;
result.hash_adj = hash_adj;
scratch_end(scratch);
ProfEnd();
return result;
}
internal
THREAD_POOL_TASK_FUNC(pdb_write_types_task)
{
ProfBeginFunction();
PDB_WriteTypesTask *task = raw_task;
String8Node *node = task->lf_arr[task_id];
Rng1U64 range = task->lf_range_arr[task_id];
U64 cursor = task->lf_cursor_arr[task_id];
for (U64 lf_idx = range.min; lf_idx < range.max; node = node->next, lf_idx += 1) {
if (lf_idx % PDB_TYPE_HINT_STEP == 0) {
U64 off_idx = lf_idx / PDB_TYPE_HINT_STEP;
Assert(off_idx < task->hint_count);
Assert(cursor < PDB_TYPE_OFFSET_MAX);
task->hint_arr[off_idx].itype = task->ti_lo + lf_idx;
task->hint_arr[off_idx].off = (PDB_TypeOffset)cursor;
}
// copy leaf data
MemoryCopy(task->lf_buf + cursor, node->string.str, node->string.size);
cursor += node->string.size;
}
ProfEnd();
}
internal void
pdb_type_server_build(TP_Context *tp, PDB_TypeServer *ts, PDB_StringTable *strtab, MSF_Context *msf, MSF_StreamNumber sn)
{
ProfBeginFunction();
Temp scratch = scratch_begin(0,0);
ProfBeginDynamic("Prepare Buffers [Leaf Count: %llu]", ts->leaf_list.node_count);
U64 hint_count = CeilIntegerDiv(ts->leaf_list.node_count, PDB_TYPE_HINT_STEP);
PDB_TpiOffHint *hint_arr = push_array_no_zero(scratch.arena, PDB_TpiOffHint, hint_count);
String8Node **lf_arr = push_array_no_zero(scratch.arena, String8Node *, tp->worker_count);
U64 *lf_cursor_arr = push_array_no_zero(scratch.arena, U64, tp->worker_count);
Rng1U64 *lf_range_arr = tp_divide_work(scratch.arena, ts->leaf_list.node_count, tp->worker_count);
U64 lf_buf_size = 0;
U64 lf_node_idx = 0;
U64 lf_arr_idx = 0;
for (String8Node *lf = ts->leaf_list.first; lf != 0; lf = lf->next) {
if (lf_node_idx == lf_range_arr[lf_arr_idx].min) { // :thread_pool_dummy_range
lf_cursor_arr[lf_arr_idx] = lf_buf_size;
lf_arr[lf_arr_idx] = lf;
lf_arr_idx += 1;
}
lf_buf_size += lf->string.size;
lf_node_idx += 1;
}
ProfEnd();
ProfBegin("Write Type Data & Hints");
PDB_WriteTypesTask write_types_task;
write_types_task.ti_lo = ts->ti_lo;
write_types_task.ti_hi = ts->ti_lo + ts->leaf_list.node_count;
write_types_task.hint_count = hint_count;
write_types_task.hint_arr = hint_arr;
write_types_task.lf_arr = lf_arr;
write_types_task.lf_range_arr = lf_range_arr;
write_types_task.lf_cursor_arr = lf_cursor_arr;
write_types_task.lf_buf = push_array_no_zero(scratch.arena, U8, lf_buf_size);
write_types_task.lf_buf_size = lf_buf_size;
tp_for_parallel(tp, 0, tp->worker_count, pdb_write_types_task, &write_types_task);
ProfEnd();
// build type lookup accelerator
PDB_TypeHashStreamInfo hash_stream_info = pdb_type_hash_stream_build(tp, ts, strtab, msf, hint_arr, hint_count);
// fill out header
PDB_TpiHeader header;
header.version = PDB_TpiVersion_IMPV80;
header.header_size = sizeof(header);
header.ti_lo = ts->ti_lo;
header.ti_hi = ts->ti_lo + ts->leaf_list.node_count;
header.leaf_data_size = safe_cast_u32(lf_buf_size);
header.hash_sn = ts->hash_sn;
header.hash_sn_aux = MSF_INVALID_STREAM_NUMBER;
header.hash_key_size = sizeof(U32);
header.hash_bucket_count = ts->bucket_cap;
header.hash_vals = hash_stream_info.hash_vals;
header.itype_offs = hash_stream_info.ti_offs;
header.hash_adj = hash_stream_info.hash_adj;
// write type server to stream
ProfBegin("MSF Commit");
msf_stream_seek_start(msf, sn);
msf_stream_write_struct(msf, sn, &header);
msf_stream_write_parallel(tp, msf, sn, write_types_task.lf_buf, lf_buf_size);
ProfEnd();
scratch_end(scratch);
ProfEnd();
}
internal void
pdb_type_server_release(PDB_TypeServer **ts_ptr)
{
ProfBeginFunction();
arena_release((*ts_ptr)->arena);
*ts_ptr = 0;
ProfEnd();
}
internal String8Node *
pdb_type_server_make_leaf(PDB_TypeServer *ts, CV_LeafKind kind, String8 data)
{
ProfBeginFunction();
String8 leaf = cv_make_leaf(ts->arena, kind, data, PDB_LEAF_ALIGN);
String8Node *node = str8_list_push(ts->arena, &ts->leaf_list, leaf);
ProfEnd();
return node;
}
internal U32
pdb_type_server_hash(String8 data)
{
U32 hash = pdb_hash_v1(data);
return hash;
}
internal PDB_TypeBucket *
pdb_type_server_push_udt_arr(PDB_TypeServer *ts, U64 count, U32 *hash_arr, String8 *raw_leaf_arr)
{
// check if type server already contains this leaf and if so move
// it to the head of bucket list.
#if 0
B32 is_udt = pdb_is_udt(kind);
if (is_udt) {
PDB_UDTInfo udt_info = pdb_get_udt_info(kind, data);
U32 udt_hash = pdb_hash_udt(udt_info, data) % ts->bucket_count;
U64 match_count = 0;
for (PDB_TypeBucket *curr = ts->bucket_table[udt_hash], *prev = NULL;
curr != NULL;
prev = curr, curr = curr->next) {
if (curr->leaf->kind == kind) {
PDB_UDTInfo this_udt_info = pdb_get_udt_info(curr->leaf->kind, curr->leaf->data);
if (str8_match(udt_info.name, this_udt_info.name)) {
B32 is_data_match = curr->leaf->data.size == data.size &&
MemoryCompare(curr->leaf->data.str, data.str, data.size) == 0;
if (is_data_match) {
B32 is_not_head = (match_count > 0);
if (is_not_head) {
// move bucket to head
prev->next = curr->next;
curr->next = ts->bucket_table[udt_hash];
ts->bucket_table[udt_hash] = curr;
// update hash adjust
pdb_hash_table_delete(&ts->hash_adj, udt_info.name);
pdb_hash_table_set(&ts->hash_adj, udt_info.name, str8((U8*)&curr->leaf->type_index, sizeof(curr->leaf->type_index)));
}
return curr->leaf;
}
match_count += 1;
}
}
}
}
#endif
PDB_TypeBucket *bucket_arr = push_array_no_zero(ts->arena, PDB_TypeBucket, count);
for (U64 leaf_idx = 0; leaf_idx < count; leaf_idx += 1) {
U32 hash = hash_arr[leaf_idx];
String8 raw_leaf = raw_leaf_arr[leaf_idx];
CV_Leaf leaf = cv_leaf_from_string(raw_leaf);
// make sure we push a complete UDT
Assert(cv_is_udt(leaf.kind));
Assert(!(cv_get_udt_info(leaf.kind, leaf.data).props & CV_TypeProp_FwdRef));
PDB_TypeBucket *bucket = &bucket_arr[leaf_idx];
bucket->next = 0;
bucket->raw_leaf = raw_leaf;
bucket->type_index = ts->ti_lo + ts->leaf_list.node_count + leaf_idx;
U32 bucket_idx = hash % ts->bucket_cap;
SLLStackPush(ts->buckets[bucket_idx], bucket);
}
return bucket_arr;
}
internal PDB_TypeBucket *
pdb_type_server_push_udt(PDB_TypeServer *ts, U32 hash, String8 raw_leaf)
{
return pdb_type_server_push_udt_arr(ts, 1, &hash, &raw_leaf);
}
internal void
pdb_type_server_push(PDB_TypeServer *ts, String8 raw_leaf)
{
ProfBeginFunction();
CV_Leaf leaf;
cv_read_leaf(raw_leaf, 0, 1, &leaf);
if (cv_is_udt(leaf.kind)) {
CV_UDTInfo udt_info = cv_get_udt_info(leaf.kind, leaf.data);
B32 is_complete = !(udt_info.props & CV_TypeProp_FwdRef);
if (is_complete) {
U32 hash = pdb_hash_udt(udt_info, leaf.data);
pdb_type_server_push_udt(ts, hash, raw_leaf);
}
}
ProfEnd();
}
internal
THREAD_POOL_TASK_FUNC(pdb_count_udt_task)
{
PDB_PushLeafTask *task = raw_task;
for EachInRange(leaf_idx, task->ranges[task_id]) {
CV_Leaf leaf;
cv_read_leaf(str8(task->leaf_arr[leaf_idx], max_U64), 0, 1, &leaf);
if (cv_is_udt(leaf.kind)) {
CV_UDTInfo udt_info = cv_get_udt_info(leaf.kind, leaf.data);
if (~udt_info.props & CV_TypeProp_FwdRef) {
task->udt_counts[task_id] += 1;
}
}
}
}
internal
THREAD_POOL_TASK_FUNC(pdb_push_udt_leaf_task)
{
PDB_PushLeafTask *task = raw_task;
PDB_TypeServer *type_server = task->type_server;
U64 bucket_cursor = task->udt_offsets[task_id];
PDB_TypeBucket *new_buckets = task->udt_buckets;
U64 type_ht_cap = type_server->bucket_cap;
PDB_TypeBucket **type_ht_buckets = type_server->buckets;
U64 base_type_index = type_server->ti_lo + type_server->leaf_list.node_count;
for EachInRange(leaf_idx, task->ranges[task_id]) {
CV_Leaf leaf;
cv_read_leaf(str8(task->leaf_arr[leaf_idx], max_U64), 0, 1, &leaf);
if (cv_is_udt(leaf.kind)) {
CV_UDTInfo udt_info = cv_get_udt_info(leaf.kind, leaf.data);
if (~udt_info.props & CV_TypeProp_FwdRef) {
// hash udt and compute bucket index
U32 hash = pdb_hash_udt(udt_info, leaf.data);
U32 bucket_idx = hash % type_ht_cap;
// fill out & insert bucket
PDB_TypeBucket *bucket = &new_buckets[bucket_cursor++];
bucket->raw_leaf = leaf.data;
bucket->type_index = base_type_index + leaf_idx;
bucket->next = ins_atomic_ptr_eval_assign(&type_ht_buckets[bucket_idx], bucket);
}
}
}
}
typedef struct
{
Rng1U64 *ranges;
U8 **leaf_arr;
String8List *lists;
String8Node *nodes;
} PDB_String8ListFromLeafArray;
internal
THREAD_POOL_TASK_FUNC(pdb_str8_list_from_leaf_array_task)
{
PDB_String8ListFromLeafArray *task = raw_task;
for EachInRange(leaf_idx, task->ranges[task_id]) {
String8Node *node = &task->nodes[leaf_idx];
node->string = cv_raw_leaf_from_ptr(task->leaf_arr[leaf_idx]);
str8_list_push_node(&task->lists[task_id], node);
}
}
internal void
pdb_type_server_push_parallel(TP_Context *tp, PDB_TypeServer *type_server, U64 leaf_count, U8 **leaf_arr)
{
ProfBeginFunction();
Temp scratch = scratch_begin(0, 0);
PDB_PushLeafTask task = {0};
task.leaf_count = leaf_count;
task.leaf_arr = leaf_arr;
task.type_server = type_server;
task.ranges = tp_divide_work(scratch.arena, leaf_count, tp->worker_count);
ProfBegin("Count UDT");
task.udt_counts = push_array(scratch.arena, U64, tp->worker_count);
tp_for_parallel(tp, 0, tp->worker_count, pdb_count_udt_task, &task);
ProfEnd();
ProfBegin("Push UDT Leaves");
U64 total_udt_count = sum_array_u64(tp->worker_count, task.udt_counts);
task.udt_offsets = offsets_from_counts_array_u64(scratch.arena, task.udt_counts, tp->worker_count);
task.udt_buckets = push_array_no_zero(type_server->arena, PDB_TypeBucket, total_udt_count);
tp_for_parallel(tp, 0, tp->worker_count, pdb_push_udt_leaf_task, &task);
ProfEnd();
ProfBegin("Append New Leaves");
{
PDB_String8ListFromLeafArray task = {0};
task.leaf_arr = leaf_arr;
task.ranges = tp_divide_work(scratch.arena, leaf_count, tp->worker_count);
task.lists = push_array(scratch.arena, String8List, tp->worker_count);
task.nodes = push_array_no_zero(type_server->arena, String8Node, leaf_count);
tp_for_parallel(tp, 0, tp->worker_count, pdb_str8_list_from_leaf_array_task, &task);
// concat output lists
String8List list = {0};
for EachIndex(task_id, tp->worker_count) { str8_list_concat_in_place(&type_server->leaf_list, &task.lists[task_id]); }
}
ProfEnd();
scratch_end(scratch);
ProfEnd();
}
#if 0
internal CV_LeafNode *
pdb_type_server_leaf_from_string(PDB_TypeServer *ts, String8 string)
{
ProfBeginFunction();
U32 hash = pdb_hash_v1(string);
U32 bucket_idx = hash % ts->bucket_count;
PDB_TypeBucket *head_bucket = ts->bucket_table[bucket_idx];
CV_LeafNode *result = 0;
for (PDB_TypeBucket *i = head_bucket; i != 0; i = i->next) {
CV_LeafNode *leaf = i->leaf_node;
String8 leaf_name = cv_get_leaf_name(leaf->data.kind, leaf->data.data);
if (str8_match(leaf_name, string, 0)) {
result = leaf;
break;
}
}
ProfEnd();
return result;
}
#endif
////////////////////////////////
#if 0
internal PDB_TypeIndexMap *
pdb_load_types_from_leaf_list(PDB_TypeServer **type_server_arr, CV_LeafList leaf_list)
{
ProfBeginFunction();
Temp scratch = scratch_begin(0, 0);
// 1. redistribute leaves in parallel
CV_LeafList leaf_list_arr[CV_TypeIndexSource_COUNT] = {0};
for (CV_LeafNode *curr = leaf_list.first, *next = 0; curr != 0; curr = next) {
next = curr->next;
curr->next = 0;
CV_TypeIndexSource ti_source = cv_type_index_source_from_leaf_kind(curr->data.kind);
CV_LeafList *list = &leaf_list_arr[ti_source];
SLLQueuePush(list->first, list->last, curr);
list->count += 1;
}
// 2. reserve type leafs on main thread
PDB_TypeLeaf *leaf_arr_arr[CV_TypeIndexSource_COUNT];
for (U64 source_idx = 0; source_idx < ArrayCount(leaf_list_arr); source_idx += 1) {
PDB_TypeServer *type_server = type_server_arr[source_idx];
CV_LeafList input_leaf_list = leaf_list_arr[source_idx];
PDB_TypeLeaf *leaf_arr = pdb_type_server_reserve(type_server, input_leaf_list.count);
leaf_arr_arr[source_idx] = leaf_arr;
}
// 3. populate type index map in parallel
PDB_TypeIndexMap *ti_map = pdb_type_index_map_alloc();
for (U64 source_idx = 0; source_idx < ArrayCount(leaf_list_arr); source_idx += 1) {
CV_LeafList input_leaf_list = leaf_list_arr[source_idx];
PDB_TypeLeaf *leaf_arr = leaf_arr_arr[source_idx];
for (U64 leaf_idx = 0; leaf_idx < input_leaf_list.count; leaf_idx += 1) {
CV_TypeIndex external_ti = ti_map->min_itype[source_idx] + leaf_idx;
CV_TypeIndex internal_ti = leaf_arr[leaf_idx].type_index;
pdb_type_index_map_add(ti_map, (CV_TypeIndexSource)source_idx, external_ti, internal_ti);
}
}
// 4. patch type indices in parallel
for (U64 source_idx = 0; source_idx < ArrayCount(leaf_list_arr); source_idx += 1) {
CV_LeafList list = leaf_list_arr[source_idx];
for (CV_LeafNode *node = list.first; node != 0; node = node->next) {
Temp temp = temp_begin(scratch.arena);
// get offsets for type indices in data blob
CV_Leaf *leaf = &node->data;
CV_TypeIndexInfoList ti_info_list = cv_get_leaf_type_index_offsets(temp.arena, leaf->kind, leaf->data);
for (CV_TypeIndexInfo *ti_info = ti_info_list.first; ti_info != 0; ti_info = ti_info->next) {
Assert(ti_info->offset + sizeof(CV_TypeIndex) <= leaf->data.size);
CV_TypeIndex *ti_ptr = (CV_TypeIndex *)(leaf->data.str + ti_info->offset);
CV_TypeIndex external_ti = *ti_ptr;
B32 is_complex_type = external_ti >= ti_map->min_itype[ti_info->source];
if (is_complex_type) {
// search external type index
CV_TypeIndex internal_tpi_idx = pdb_type_index_map_search(ti_map, CV_TypeIndexSource_TPI, external_ti);
CV_TypeIndex internal_ipi_idx = pdb_type_index_map_search(ti_map, CV_TypeIndexSource_IPI, external_ti);
// error checks
if (internal_tpi_idx == 0 && internal_ipi_idx == 0) {
lnk_invalid_path("unable to find match for external type index 0x%X", external_ti);
continue;
}
if (internal_tpi_idx != 0 && internal_ipi_idx != 0) {
lnk_invalid_path("both TPI and IPI matched for external type index 0x%X", external_ti);
continue;
}
// rewrite index
CV_TypeIndex internal_ti = internal_tpi_idx ? internal_tpi_idx : internal_ipi_idx;
*ti_ptr = internal_ti;
}
}
temp_end(temp);
}
}
// 5. push types to hash table on main thread
for (U64 source_idx = 0; source_idx < ArrayCount(leaf_list_arr); source_idx += 1) {
PDB_TypeServer *type_server = type_server_arr[source_idx];
CV_LeafList list = leaf_list_arr[source_idx];
PDB_TypeLeaf *leaf_arr = leaf_arr_arr[source_idx];
U64 leaf_idx = 0;
for (CV_LeafNode *node = list.first; node != 0; node = node->next, leaf_idx += 1) {
CV_Leaf *external_leaf = &node->data;
// move patched type data
PDB_TypeLeaf *internal_leaf = leaf_arr + leaf_idx;
internal_leaf->kind = external_leaf->kind;
internal_leaf->data = push_str8_copy(type_server->arena, external_leaf->data);
// push leaf to type server
pdb_type_server_push_(type_server, internal_leaf);
}
}
scratch_end(scratch);
ProfEnd();
return ti_map;
}
#endif
////////////////////////////////
internal PDB_InfoContext *
pdb_info_alloc(U32 age, COFF_TimeStamp time_stamp, Guid guid)
{
ProfBeginFunction();
Arena *arena = arena_alloc();
PDB_InfoContext *info = push_array(arena, PDB_InfoContext, 1);
info->arena = arena;
info->flags = PDB_FeatureFlag_HAS_ID_STREAM;
info->time_stamp = time_stamp;
info->age = age;
info->guid = guid;
pdb_strtab_alloc(&info->strtab, 0x3fff);
pdb_hash_table_alloc(&info->named_stream_ht, 1);
pdb_hash_table_alloc(&info->src_header_block_ht, 8);
ProfEnd();
return info;
}
internal void
pdb_info_parse_from_data(String8 data, PDB_InfoParse *parse_out)
{
PDB_InfoVersion version = 0;
str8_deserial_read_struct(data, 0, &version);
switch (version) {
case PDB_InfoVersion_VC70: {
U64 cursor = 0;
// read header
PDB_InfoHeaderV70 header;
cursor += str8_deserial_read_struct(data, cursor, &header);
parse_out->version = version;
parse_out->time_stamp = header.time_stamp;
parse_out->age = header.age;
parse_out->guid = header.guid;
parse_out->extra_info = str8_skip(data, cursor);
} break;
case PDB_InfoVersion_VC2:
case PDB_InfoVersion_VC4:
case PDB_InfoVersion_VC41:
case PDB_InfoVersion_VC50:
case PDB_InfoVersion_VC98:
case PDB_InfoVersion_VC70_DEP:
case PDB_InfoVersion_VC80:
case PDB_InfoVersion_VC110:
case PDB_InfoVersion_VC140: {
NotImplemented;
} break;
default: Assert(!"invalid info stream version"); break;
}
}
internal void
pdb_info_build_src_header_block(PDB_InfoContext *info, MSF_Context *msf)
{
Temp scratch = scratch_begin(0,0);
// was stream allocated?
MSF_StreamNumber src_header_block_sn = pdb_find_named_stream(&info->named_stream_ht, PDB_SRC_HEADER_BLOCK_STREAM_NAME);
if (src_header_block_sn == MSF_INVALID_STREAM_NUMBER) {
src_header_block_sn = pdb_push_named_stream(&info->named_stream_ht, msf, PDB_SRC_HEADER_BLOCK_STREAM_NAME);
}
// build the hash table
String8 hash_table_data = pdb_data_from_src_header_block_ht(scratch.arena, &info->src_header_block_ht, &info->strtab);
AssertAlways(hash_table_data.size);
// compute stream size
U64 src_header_stream_size = 0;
src_header_stream_size += sizeof(PDB_SrcHeaderBlockHeader);
src_header_stream_size += hash_table_data.size;
// fill out header
PDB_SrcHeaderBlockHeader src_header;
src_header.version = PDB_SRC_HEADER_BLOCK_MAGIC_V1;
src_header.stream_size = src_header_stream_size;
src_header.file_time = 0;
src_header.age = 0;
MemoryZeroStruct(&src_header.pad);
// write to stream
B32 is_header_written = msf_stream_write_struct(msf, src_header_block_sn, &src_header);
B32 is_hash_table_written = msf_stream_write_string(msf, src_header_block_sn, hash_table_data);
AssertAlways(is_header_written);
AssertAlways(is_hash_table_written);
AssertAlways(msf_stream_get_size(msf, src_header_block_sn) == src_header.stream_size);
scratch_end(scratch);
}
internal void
pdb_info_build_link_info(PDB_InfoContext *info, MSF_Context *msf)
{
MSF_StreamNumber linkinfo_sn = pdb_find_named_stream(&info->named_stream_ht, PDB_LINK_INFO_STREAM_NAME);
if (linkinfo_sn == MSF_INVALID_STREAM_NUMBER) {
linkinfo_sn = pdb_push_named_stream(&info->named_stream_ht, msf, PDB_LINK_INFO_STREAM_NAME);
}
// TODO: populate LINKINFO
}
internal void
pdb_info_build_names(PDB_InfoContext *info, MSF_Context *msf)
{
MSF_StreamNumber strtab_sn = pdb_find_named_stream(&info->named_stream_ht, PDB_NAMES_STREAM_NAME);
if (strtab_sn == MSF_INVALID_STREAM_NUMBER) {
strtab_sn = pdb_push_named_stream(&info->named_stream_ht, msf, PDB_NAMES_STREAM_NAME);
}
pdb_strtab_build(&info->strtab, msf, strtab_sn);
}
internal void
pdb_info_build(PDB_InfoContext *info, MSF_Context *msf, MSF_StreamNumber sn)
{
ProfBeginFunction();
Temp scratch = scratch_begin(0,0);
// finalize named streams
if (info->src_header_block_ht.count) {
pdb_info_build_src_header_block(info, msf);
}
pdb_info_build_link_info(info, msf);
if (info->strtab.bucket_count > 1) {
pdb_info_build_names(info, msf);
}
// serialize named streams hash table
String8 named_stream_ht_data = pdb_data_from_named_stream_ht(scratch.arena, &info->named_stream_ht);
// fill out header
PDB_InfoHeaderV70 header;
header.version = PDB_InfoVersion_VC70;
header.time_stamp = info->time_stamp;
header.age = info->age;
header.guid = info->guid;
// layout info stream
String8List info_srl = {0};
str8_serial_begin(scratch.arena, &info_srl);
str8_serial_push_struct(scratch.arena, &info_srl, &header);
str8_serial_push_string(scratch.arena, &info_srl, named_stream_ht_data);
str8_serial_push_u32(scratch.arena, &info_srl, 0);
if (info->flags & PDB_FeatureFlag_HAS_ID_STREAM) {
str8_serial_push_u32(scratch.arena, &info_srl, PDB_FeatureSig_VC140);
}
if (info->flags & PDB_FeatureFlag_NO_TYPE_MERGE) {
str8_serial_push_u32(scratch.arena, &info_srl, PDB_FeatureSig_NO_TYPE_MERGE);
}
if (info->flags & PDB_FeatureFlag_MINIMAL_DBG_INFO) {
str8_serial_push_u32(scratch.arena, &info_srl, PDB_FeatureSig_MINIMAL_DEBUG_INFO);
}
// write info to MSF
msf_stream_seek_start(msf, sn);
msf_stream_resize(msf, sn, info_srl.total_size);
msf_stream_write_list(msf, sn, info_srl);
scratch_end(scratch);
ProfEnd();
}
internal void
pdb_info_release(PDB_InfoContext **info_ptr)
{
ProfBeginFunction();
arena_release((*info_ptr)->arena);
*info_ptr = NULL;
ProfEnd();
}
internal MSF_StreamNumber
pdb_push_named_stream(PDB_HashTable *named_stream_ht, MSF_Context *msf, String8 name)
{
ProfBeginFunction();
MSF_StreamNumber sn = msf_stream_alloc(msf);
U32 sn32 = (U32)sn;
pdb_hash_table_set(named_stream_ht, name, str8_struct(&sn32));
ProfEnd();
return sn;
}
internal MSF_StreamNumber
pdb_find_named_stream(PDB_HashTable *named_stream_ht, String8 name)
{
ProfBeginFunction();
MSF_StreamNumber result = MSF_INVALID_STREAM_NUMBER;
String8 value;
if (pdb_hash_table_get(named_stream_ht, name, &value)) {
Assert(value.size == sizeof(U32));
result = *(MSF_StreamNumber*)value.str;
}
ProfEnd();
return result;
}
internal PDB_SrcError
pdb_add_src(PDB_InfoContext *info, MSF_Context *msf, String8 file_path, String8 file_data, PDB_SrcCompType comp)
{
Temp scratch = scratch_begin(0,0);
PDB_SrcError error_status = PDB_SrcError_UNKNOWN;
if (comp == PDB_SrcComp_NULL) {
// process path so it passes VS validity checks
String8 virt_path = file_path;
String8 work_dir = os_get_current_path(scratch.arena);
virt_path = path_absolute_dst_from_relative_dst_src(scratch.arena, virt_path, work_dir);
virt_path = lower_from_str8(scratch.arena, virt_path);
virt_path = path_convert_slashes(scratch.arena, virt_path, PathStyle_UnixAbsolute);
String8 dummy_value;
B32 is_virt_path_present = pdb_hash_table_get(&info->src_header_block_ht, virt_path, &dummy_value);
if (!is_virt_path_present) {
String8 stream_name = push_str8f(scratch.arena, "/src/files/%S", virt_path);
MSF_StreamNumber sn = pdb_find_named_stream(&info->named_stream_ht, stream_name);
B32 is_name_free = (sn == MSF_INVALID_STREAM_NUMBER);
if (is_name_free) {
sn = pdb_push_named_stream(&info->named_stream_ht, msf, stream_name);
B32 is_file_data_written = msf_stream_write_string(msf, sn, file_data);
if (is_file_data_written) {
// add command line path
PDB_StringIndex file_path_stridx;
if (!pdb_strtab_search(&info->strtab, file_path, &file_path_stridx)) {
file_path_stridx = pdb_strtab_add(&info->strtab, file_path);
}
// add virtual path
PDB_StringIndex virt_path_stridx;
if (!pdb_strtab_search(&info->strtab, virt_path, &virt_path_stridx)) {
virt_path_stridx = pdb_strtab_add(&info->strtab, virt_path);
}
// string indices -> offsets
PDB_StringOffset file_path_stroff = pdb_strtab_string_to_offset(&info->strtab, file_path_stridx);
PDB_StringOffset virt_path_stroff = pdb_strtab_string_to_offset(&info->strtab, virt_path_stridx);
// fill out entry
PDB_SrcHeaderBlockEntry entry;
entry.size = sizeof(entry);
entry.version = PDB_SRC_HEADER_BLOCK_MAGIC_V1;
entry.file_crc = pdb_crc32_from_string(file_data);
entry.file_size = file_data.size;
entry.file_path = file_path_stroff;
entry.obj = 0; // null string offset
entry.virt_path = virt_path_stroff;
entry.comp = comp;
entry.flags = 0;
MemorySet(&entry.pad[0], 0, sizeof(entry.pad));
MemorySet(&entry.reserved[0], 0, sizeof(entry.reserved));
// add to hash table { path, entry }
String8 key = virt_path;
String8 val = str8_struct(&entry);
pdb_hash_table_set(&info->src_header_block_ht, key, val);
error_status = PDB_SrcError_OK;
} else {
error_status = PDB_SrcError_UNABLE_TO_WRITE_DATA;
}
} else {
error_status = PDB_SrcError_DUPLICATE_NAME_STREAM;
}
} else {
error_status = PDB_SrcError_DUPLICATE_ENTRY;
}
} else {
error_status = PDB_SrcError_UNSUPPORTED_COMPRESSION;
}
scratch_end(scratch);
return error_status;
}
////////////////////////////////
internal PDB_GsiContext *
gsi_alloc(void)
{
ProfBeginFunction();
Arena *arena = arena_alloc();
PDB_GsiContext *gsi = push_array(arena, PDB_GsiContext, 1);
gsi->arena = arena;
gsi->word_size = PDB_GSI_V70_WORD_SIZE;
gsi->symbol_align = PDB_GSI_V70_SYMBOL_ALIGN;
gsi->bucket_count = PDB_GSI_V70_BUCKET_COUNT;
gsi->bucket_arr = push_array(arena, CV_SymbolList, gsi->bucket_count);
ProfEnd();
return gsi;
}
internal void
gsi_release(PDB_GsiContext *gsi)
{
ProfBeginFunction();
arena_release(gsi->arena);
ProfEnd();
}
internal void
gsi_write_build_result(TP_Context *tp,
PDB_GsiBuildResult build,
MSF_Context *msf,
MSF_StreamNumber gsi_sn,
MSF_StreamNumber symbols_sn)
{
ProfBeginFunction();
U64 hash_record_arr_size = sizeof(build.hash_record_arr[0]) * build.hash_record_count;
U64 bitmap_size = sizeof(build.bitmap[0]) * build.bitmap_count;
U64 compressed_bucket_arr_size = sizeof(build.compressed_bucket_arr[0]) * build.compressed_bucket_count;
U64 gsi_size = sizeof(build.header) + hash_record_arr_size + bitmap_size + compressed_bucket_arr_size;
ProfBeginV("Reserve %M for GSI hash table", gsi_size);
msf_stream_reserve(msf, gsi_sn, gsi_size);
ProfEnd();
ProfBeginV("Reserve %M for symbols", build.symbol_data.size);
msf_stream_reserve(msf, symbols_sn, build.symbol_data.size);
ProfEnd();
ProfBegin("Write GSI header");
msf_stream_write_struct(msf, gsi_sn, &build.header);
ProfEnd();
ProfBegin("Write hash records [%M]", hash_record_arr_size);
msf_stream_write_parallel(tp, msf, gsi_sn, &build.hash_record_arr[0], hash_record_arr_size);
ProfEnd();
ProfBeginV("Write bucket bitmap [%M]", bitmap_size);
msf_stream_write(msf, gsi_sn, &build.bitmap[0], bitmap_size);
ProfEnd();
ProfBegin("Write buckets [%M]", compressed_bucket_arr_size);
msf_stream_write(msf, gsi_sn, &build.compressed_bucket_arr[0], compressed_bucket_arr_size);
ProfEnd();
ProfBegin("Write symbols [%M]", build.symbol_data.size);
msf_stream_write_string_parallel(tp, msf, symbols_sn, build.symbol_data);
ProfEnd();
ProfEnd();
}
internal int
gsi_hash_record_compar_is_before(void *raw_a, void *raw_b)
{
PDB_GsiSortRecord *a = raw_a;
PDB_GsiSortRecord *b = raw_b;
int is_before;
if (a->name.size != b->name.size) {
is_before = a->name.size < b->name.size;
} else {
int cmp = str8_compar_ignore_case(&a->name, &b->name);
if (cmp == 0) {
cmp = u64_compar(&a->offset, &b->offset);
}
is_before = cmp < 0;
}
return is_before;
}
internal int
psi_addr_map_compar_is_before(void *raw_a, void *raw_b)
{
PDB_GsiSortRecord *a = raw_a;
PDB_GsiSortRecord *b = raw_b;
int is_before;
if (a->isect_off.isect != b->isect_off.isect) {
is_before = a->isect_off.isect < b->isect_off.isect;
} else if (a->isect_off.off != b->isect_off.off) {
is_before = a->isect_off.off < b->isect_off.off;
} else {
is_before = str8_compar_case_sensitive(&a->name, &b->name);
}
return is_before;
}
internal void
gsi_record_sort_by_name(PDB_GsiSortRecord *arr, U64 count)
{
ProfBeginFunction();
radsort(arr, count, gsi_hash_record_compar_is_before);
ProfEnd();
}
internal void
gsi_record_sort_by_sc(PDB_GsiSortRecord *arr, U64 count)
{
ProfBeginFunction();
radsort(arr, count, psi_addr_map_compar_is_before);
ProfEnd();
}
internal
THREAD_POOL_TASK_FUNC(gsi_size_buckets_task)
{
U64 bucket_idx = task_id;
PDB_GsiSerializeSymbolsTask *task = raw_task;
CV_SymbolList *bucket_list = &task->bucket_arr[bucket_idx];
for (CV_SymbolNode *node = bucket_list->first; node != 0; node = node->next) {
task->bucket_size_arr[bucket_idx] += cv_size_from_symbol(&node->data, task->symbol_align);
}
}
force_inline int
gsi_symbol_is_before(void *raw_a, void *raw_b)
{
CV_Symbol *a = *(CV_Symbol **)raw_a;
CV_Symbol *b = *(CV_Symbol **)raw_b;
String8 a_name = cv_name_from_symbol(a->kind, a->data);
String8 b_name = cv_name_from_symbol(b->kind, b->data);
int is_before;
if (a_name.size != b_name.size) {
is_before = a_name.size < b_name.size;
} else {
int cmp = str8_compar_ignore_case(&a_name, &b_name);
if (cmp == 0) {
cmp = u64_compar(&a->offset, &b->offset);
}
is_before = cmp < 0;
}
return is_before;
}
internal int
gsi_pub_symbol_is_before(void *raw_a, void *raw_b)
{
CV_Symbol *a = *(CV_Symbol **)raw_a;
CV_Symbol *b = *(CV_Symbol **)raw_b;
String8 a_name = cv_name_from_symbol(a->kind, a->data);
String8 b_name = cv_name_from_symbol(b->kind, b->data);
int is_before;
if (a_name.size != b_name.size) {
is_before = a_name.size < b_name.size;
} else {
int cmp = str8_compar_ignore_case(&a_name, &b_name);
if (cmp == 0) {
CV_SymPub32 *a_sym = (CV_SymPub32 *)a->data.str;
CV_SymPub32 *b_sym = (CV_SymPub32 *)b->data.str;
cmp = u16_compar(&a_sym->sec, &b_sym->sec);
if (cmp == 0) {
cmp = u32_compar(&a_sym->off, &b_sym->off);
}
}
is_before = cmp < 0;
}
return is_before;
}
internal
THREAD_POOL_TASK_FUNC(gsi_serialize_pub32)
{
Temp scratch = scratch_begin(&arena, 1);
U64 bucket_idx = task_id;
PDB_GsiSerializeSymbolsTask *task = raw_task;
CV_SymbolList bucket = task->bucket_arr[bucket_idx];
CV_Symbol **symbol_arr = push_array(scratch.arena, CV_Symbol *, bucket.count);
{
U64 i = 0;
for EachNode(n, CV_SymbolNode, bucket.first) { symbol_arr[i++] = &n->data; }
}
// sort symbols within bucket
radsort(symbol_arr, bucket.count, gsi_pub_symbol_is_before);
PDB_GsiSortRecord *sort_record_arr = task->sort_record_arr_arr[bucket_idx];
U64 buffer_size = task->bucket_size_arr[bucket_idx];
U64 buffer_base = task->bucket_off_arr[bucket_idx];
U8 *buffer = task->buffer + buffer_base;
U64 sort_idx = 0;
U64 buffer_cursor = 0;
for EachIndex(i, bucket.count) {
Assert(symbol_arr[i]->kind == CV_SymKind_PUB32);
CV_SymPub32 *pub32 = (CV_SymPub32 *)symbol_arr[i]->data.str;
U8 *str_ptr = (U8 *)(pub32 + 1);
U64 str_size = symbol_arr[i]->data.size - sizeof(*pub32);
String8 name = str8(str_ptr, str_size);
// init sort record
PDB_GsiSortRecord *sr = &sort_record_arr[sort_idx];
sr->isect_off = isect_off(pub32->sec, pub32->off);
sr->name = name;
sr->offset = task->symbol_data_base + buffer_base + buffer_cursor;
// serialize symbol
U64 serial_size = cv_write_symbol(buffer, buffer_cursor, buffer_size, symbol_arr[i], task->symbol_align);
// advance
sort_idx += 1;
buffer_cursor += serial_size;
}
Assert(sort_idx == bucket.count);
Assert(buffer_cursor == buffer_size);
scratch_end(scratch);
}
internal
THREAD_POOL_TASK_FUNC(gsi_serialize_symbols_task)
{
Temp scratch = scratch_begin(&arena, 1);
U64 bucket_idx = task_id;
PDB_GsiSerializeSymbolsTask *task = raw_task;
CV_SymbolList bucket = task->bucket_arr[bucket_idx];
CV_Symbol **symbol_arr = push_array(scratch.arena, CV_Symbol *, bucket.count);
{
U64 i = 0;
for EachNode(n, CV_SymbolNode, bucket.first) { symbol_arr[i++] = &n->data; }
}
// sort symbols within bucket
radsort(symbol_arr, bucket.count, gsi_symbol_is_before);
// symbol -> GSI sort record
{
PDB_GsiSortRecord *sort_record_arr = task->sort_record_arr_arr[bucket_idx];
U64 buffer_size = task->bucket_size_arr[bucket_idx];
U64 buffer_base = task->bucket_off_arr[bucket_idx];
U8 *buffer = task->buffer + buffer_base;
U64 sort_idx = 0;
U64 buffer_cursor = 0;
for EachIndex(i, bucket.count) {
// init sort record
PDB_GsiSortRecord *sr = &sort_record_arr[sort_idx];
sr->name = cv_name_from_symbol(symbol_arr[i]->kind, symbol_arr[i]->data);
sr->offset = task->symbol_data_base + buffer_base + buffer_cursor;
sort_idx += 1;
// serialize symbol
U64 serial_size = cv_write_symbol(buffer, buffer_cursor, buffer_size, symbol_arr[i], task->symbol_align);
buffer_cursor += serial_size;
}
Assert(sort_idx == bucket.count);
Assert(buffer_cursor == buffer_size);
}
scratch_end(scratch);
}
internal PDB_GsiBuildResult
gsi_build_ex(TP_Context *tp, Arena *arena, PDB_GsiContext *gsi, U64 symbol_data_base, B32 is_pub32, U64 msf_page_size)
{
ProfBeginFunction();
Temp scratch = scratch_begin(&arena,1);
ProfBegin("Serialize & Sort Symbols");
PDB_GsiSerializeSymbolsTask serial_task = {0};
serial_task.symbol_data_base = symbol_data_base;
serial_task.symbol_align = gsi->symbol_align;
serial_task.bucket_arr = gsi->bucket_arr;
serial_task.bucket_size_arr = push_array(scratch.arena, U64, gsi->bucket_count);
// estimate each bucket size
tp_for_parallel(tp, 0, gsi->bucket_count, gsi_size_buckets_task, &serial_task);
// prepare serial buffer
U64 buffer_size = sum_array_u64(gsi->bucket_count, serial_task.bucket_size_arr);
serial_task.buffer = push_array_no_zero(arena, U8, buffer_size);
serial_task.bucket_off_arr = push_array_copy_u64(scratch.arena, serial_task.bucket_size_arr, gsi->bucket_count);
u64_array_counts_to_offsets(gsi->bucket_count, serial_task.bucket_off_arr);
// prepare GSI records
serial_task.sort_record_arr_arr = push_array_no_zero(scratch.arena, PDB_GsiSortRecord *, gsi->bucket_count);
serial_task.sort_record_arr = push_array_no_zero(arena, PDB_GsiSortRecord, gsi->symbol_count);
for (U64 bucket_idx = 0, cursor = 0; bucket_idx < gsi->bucket_count; bucket_idx += 1) {
serial_task.sort_record_arr_arr[bucket_idx] = serial_task.sort_record_arr + cursor;
cursor += gsi->bucket_arr[bucket_idx].count;
}
// fill out sort records & serialize symbols
TP_TaskFunc *serial_func = is_pub32 ? gsi_serialize_pub32 : gsi_serialize_symbols_task;
tp_for_parallel(tp, 0, gsi->bucket_count, serial_func, &serial_task);
ProfEnd();
U64 bitmap_count = (gsi->bucket_count / gsi->word_size) + 1; // ms-pdb allocates extra bucket and funnels free buckets there
U64 compressed_offset_count = 0;
U64 hash_record_count = gsi->symbol_count;
U32 *bitmap = push_array(arena, U32, bitmap_count);
U32 *compressed_offset_arr = push_array_no_zero(arena, U32, gsi->bucket_count);
PDB_GsiHashRecord *hash_record_arr = push_array_no_zero(arena, PDB_GsiHashRecord, hash_record_count);
ProfBegin("Write Bitmap & Record Offsets");
for (U64 bucket_idx = 0, hash_idx = 0; bucket_idx < gsi->bucket_count; bucket_idx += 1) {
// set bit for each occupied bucket
CV_SymbolList bucket_list = gsi->bucket_arr[bucket_idx];
if (bucket_list.count) {
U64 word_idx = bucket_idx / gsi->word_size;
Assert(word_idx < bitmap_count);
bitmap[word_idx] |= 1u << (bucket_idx % gsi->word_size);
compressed_offset_arr[compressed_offset_count] = hash_idx * sizeof(PDB_GsiHashRecordOffsetCalc); // store in-memory offset for first bucket
compressed_offset_count += 1;
}
// write out sorted hash records
PDB_GsiSortRecord *sort_record_arr = serial_task.sort_record_arr_arr[bucket_idx];
for (U64 sr_idx = 0; sr_idx < gsi->bucket_arr[bucket_idx].count; sr_idx += 1, hash_idx += 1) {
PDB_GsiHashRecord *hr = &hash_record_arr[hash_idx];
hr->symbol_off = sort_record_arr[sr_idx].offset + 1; // off-by-one because 0 is reserved for null
hr->cref = 1;
}
}
ProfEnd();
// fill out header
PDB_GsiHeader header;
header.signature = PDB_GsiSignature_Basic;
header.version = PDB_GsiVersion_V70;
header.hash_record_arr_size = sizeof(hash_record_arr[0]) * hash_record_count;
header.bucket_data_size = sizeof(bitmap[0]) * bitmap_count + sizeof(compressed_offset_arr[0]) * compressed_offset_count;
// fill out result
PDB_GsiBuildResult result;
result.header = header;
result.hash_record_count = hash_record_count;
result.hash_record_arr = hash_record_arr;
result.sort_record_arr = serial_task.sort_record_arr;
result.bitmap_count = bitmap_count;
result.bitmap = bitmap;
result.compressed_bucket_count = compressed_offset_count;
result.compressed_bucket_arr = compressed_offset_arr;
result.total_hash_size = sizeof(header) + header.hash_record_arr_size + header.bucket_data_size;
result.symbol_data = str8(serial_task.buffer, buffer_size);
scratch_end(scratch);
ProfEnd();
return result;
}
internal void
gsi_build(TP_Context *tp, PDB_GsiContext *gsi, MSF_Context *msf, MSF_StreamNumber sn, MSF_StreamNumber symbols_sn)
{
ProfBeginFunction();
Temp scratch = scratch_begin(0,0);
U64 symbol_data_base = msf_stream_get_pos(msf, symbols_sn);
PDB_GsiBuildResult build = gsi_build_ex(tp, scratch.arena, gsi, symbol_data_base, /* is_pub32: */ 0, msf->page_size);
gsi_write_build_result(tp, build, msf, sn, symbols_sn);
scratch_end(scratch);
ProfEnd();
}
internal U32
gsi_hash(PDB_GsiContext *gsi, String8 input)
{ (void)gsi;
U32 hash = pdb_hash_v1(input);
return hash;
}
internal void
gsi_push_(PDB_GsiContext *gsi, U32 hash, CV_SymbolNode *node)
{
U64 bucket_idx = hash % gsi->bucket_count;
CV_SymbolList *list = &gsi->bucket_arr[bucket_idx];
cv_symbol_list_push_node(list, node);
gsi->symbol_count += 1;
}
internal CV_SymbolNode *
gsi_push(PDB_GsiContext *gsi, CV_Symbol *symbol)
{
String8 name = cv_name_from_symbol(symbol->kind, symbol->data);
U32 hash = gsi_hash(gsi, name);
CV_SymbolNode *node = push_array_no_zero(gsi->arena, CV_SymbolNode, 1);
node->next = 0;
node->prev = 0;
node->data = *symbol;
gsi_push_(gsi, hash, node);
return node;
}
internal
THREAD_POOL_TASK_FUNC(gsi_symbol_hasher_task)
{
ProfBeginFunction();
GSI_SymbolHasherTask *task = raw_task;
Rng1U64 range = task->ranges[task_id];
for (U64 symbol_idx = range.min; symbol_idx < range.max; ++symbol_idx) {
CV_SymbolNode *symbol = task->symbols[symbol_idx];
String8 name = cv_name_from_symbol(symbol->data.kind, symbol->data.data);
task->hashes[symbol_idx] = gsi_hash(task->gsi, name);
}
ProfEnd();
}
internal void
gsi_push_many_arr(TP_Context *tp, PDB_GsiContext *gsi, U64 count, CV_SymbolNode **symbols)
{
ProfBeginFunction();
Temp scratch = scratch_begin(0, 0);
ProfBegin("Hash UDT Names");
GSI_SymbolHasherTask task = {0};
task.gsi = gsi;
task.ranges = tp_divide_work(scratch.arena, count, tp->worker_count);
task.symbols = symbols;
task.hashes = push_array_no_zero(scratch.arena, U32, count);
tp_for_parallel(tp, 0, tp->worker_count, gsi_symbol_hasher_task, &task);
ProfEnd();
for (U64 i = 0; i < count; ++i) {
gsi_push_(gsi, task.hashes[i], symbols[i]);
}
scratch_end(scratch);
ProfEnd();
}
internal void
gsi_push_many_list(PDB_GsiContext *gsi, U64 count, U32 *hash_arr, CV_SymbolList *list)
{
Assert(count == list->count);
U64 hash_idx = 0;
for (CV_SymbolNode *curr = list->first, *next = 0; curr != 0; curr = next, ++hash_idx) {
next = curr->next;
curr->prev = 0;
curr->next = 0;
gsi_push_(gsi, hash_arr[hash_idx], curr);
}
MemoryZeroStruct(list);
}
internal CV_SymbolNode *
gsi_search(PDB_GsiContext *gsi, CV_Symbol *symbol)
{
String8 name = cv_name_from_symbol(symbol->kind, symbol->data);
U32 hash = gsi_hash(gsi, name);
U64 ibucket = hash % gsi->bucket_count;
CV_SymbolList bucket_list = gsi->bucket_arr[ibucket];
for (CV_SymbolNode *node = bucket_list.first; node != 0; node = node->next) {
String8 that_name = cv_name_from_symbol(node->data.kind, node->data.data);
if (str8_match(name, that_name, 0)) {
return node;
}
}
return NULL;
}
////////////////////////////////
internal PDB_PsiContext *
psi_alloc(void)
{
ProfBeginFunction();
Arena *arena = arena_alloc();
PDB_PsiContext *psi = push_array(arena, PDB_PsiContext, 1);
psi->arena = arena;
psi->gsi = gsi_alloc();
ProfEnd();
return psi;
}
internal void
psi_build(TP_Context *tp, PDB_PsiContext *psi, MSF_Context *msf, MSF_StreamNumber sn, MSF_StreamNumber symbols_sn)
{
ProfBeginFunction();
Temp scratch = scratch_begin(0,0);
U64 symbol_data_base = msf_stream_get_pos(msf, symbols_sn);
PDB_GsiBuildResult gsi_build = gsi_build_ex(tp, scratch.arena, psi->gsi, symbol_data_base, /* is_pub32: */ 1, msf->page_size);
ProfBegin("Address Map");
ProfBegin("Sort");
gsi_record_sort_by_sc(gsi_build.sort_record_arr, gsi_build.hash_record_count);
ProfEnd();
ProfBegin("Offset Fill");
U64 addr_map_count = gsi_build.hash_record_count;
U64 addr_map_size = addr_map_count * sizeof(U32);
U32 *addr_map = push_array_no_zero(scratch.arena, U32, addr_map_count);
for (U64 i = 0; i < addr_map_count; i += 1) {
addr_map[i] = gsi_build.sort_record_arr[i].offset;
}
ProfEnd();
ProfEnd();
PDB_PsiHeader header;
header.sym_hash_size = gsi_build.total_hash_size;
header.addr_map_size = addr_map_size;
header.thunk_count = 0;
header.thunk_size = 0;
header.isec_thunk_table = 0;
header.padding = 0;
header.sec_thunk_table_off = 0;
header.sec_count = 0;
ProfBegin("MSF Write");
msf_stream_write_struct(msf, sn, &header);
gsi_write_build_result(tp, gsi_build, msf, sn, symbols_sn);
msf_stream_write_array(msf, sn, &addr_map[0], addr_map_count);
ProfEnd();
scratch_end(scratch);
ProfEnd();
}
internal void
psi_release(PDB_PsiContext *psi)
{
ProfBeginFunction();
gsi_release(psi->gsi);
arena_release(psi->arena);
ProfEnd();
}
internal CV_SymbolNode *
psi_push(PDB_PsiContext *psi, CV_Pub32Flags flags, U32 offset, U16 isect, String8 name)
{
CV_Symbol pub = cv_make_pub32(psi->arena, flags, offset, isect, name);
CV_SymbolNode *node = gsi_push(psi->gsi, &pub);
return node;
}
////////////////////////////////
internal void
dbi_sec_contrib_list_push_node(PDB_DbiSectionContribList *list, PDB_DbiSectionContribNode *node)
{
node->next = 0;
SLLQueuePush(list->first, list->last, node);
list->count += 1;
}
internal PDB_DbiSectionContribNode *
dbi_sec_contrib_list_push(Arena *arena, PDB_DbiSectionContribList *list)
{
PDB_DbiSectionContribNode *node = push_array_no_zero(arena, PDB_DbiSectionContribNode, 1);
node->next = 0;
dbi_sec_contrib_list_push_node(list, node);
return node;
}
internal void
dbi_sec_list_concat_arr(PDB_DbiSectionContribList *list, U64 count, PDB_DbiSectionContribList *to_concat)
{
SLLConcatInPlaceArray(list, to_concat, count);
}
internal PDB_DbiContext *
dbi_alloc(COFF_MachineType machine, U32 age)
{
ProfBeginFunction();
Arena *arena = arena_alloc();
PDB_DbiContext *dbi = push_array(arena, PDB_DbiContext, 1);
dbi->arena = arena;
dbi->age = age;
dbi->machine = machine;
dbi->globals_sn = MSF_INVALID_STREAM_NUMBER;
dbi->publics_sn = MSF_INVALID_STREAM_NUMBER;
dbi->symbols_sn = MSF_INVALID_STREAM_NUMBER;
pdb_strtab_alloc(&dbi->ec_names, 8);
for (U64 istream = 0; istream < ArrayCount(dbi->dbg_streams); istream += 1) {
dbi->dbg_streams[istream] = MSF_INVALID_STREAM_NUMBER;
}
ProfEnd();
return dbi;
}
internal String8List *
dbi_open_file_info(Arena *arena, MSF_Context *msf, MSF_StreamNumber sn, PDB_DbiHeader *dbi_header)
{
ProfBeginFunction();
Temp scratch = scratch_begin(&arena, 1);
MSF_UInt file_info_pos = sizeof(PDB_DbiHeader) +
dbi_header->module_info_size +
dbi_header->sec_con_size +
dbi_header->sec_map_size;
msf_stream_seek(msf, sn, file_info_pos);
U16 mod_count = msf_stream_read_u16(msf, sn);
U16 total_file_count16 = msf_stream_read_u16(msf, sn);
CV_ModIndex *imod_array = push_array(scratch.arena, CV_ModIndex, mod_count);
msf_stream_read_array(msf, sn, &imod_array[0], mod_count);
U16 *mod_file_count = push_array(scratch.arena, U16, mod_count);
msf_stream_read_array(msf, sn, &mod_file_count[0], mod_count);
U64 total_file_count = 0;
for (U16 imod = 0; imod < mod_count; imod += 1) {
total_file_count += mod_file_count[imod];
}
U32 *file_name_offset_array = push_array(scratch.arena, U32, total_file_count);
msf_stream_read_array(msf, sn, &file_name_offset_array[0], total_file_count);
U64 file_name_buffer_offset = sizeof(mod_count) +
sizeof(total_file_count16) +
sizeof(imod_array[0]) * mod_count +
sizeof(mod_file_count[0]) * mod_count +
sizeof(file_name_offset_array[0]) * total_file_count;
Assert(dbi_header->file_info_size >= file_name_buffer_offset);
U64 file_name_buffer_size = dbi_header->file_info_size - file_name_buffer_offset;
char *file_name_buffer = push_array(arena, char, file_name_buffer_size + 1);
msf_stream_read_array(msf, sn, &file_name_buffer[0], file_name_buffer_size);
String8List *file_info = push_array(arena, String8List, mod_count + 1);
U32 *file_name_offset_ptr = &file_name_offset_array[0];
for (U64 mod_idx = 0; mod_idx < mod_count; ++mod_idx) {
String8List *file_list = &file_info[mod_idx];
U16 file_count = mod_file_count[mod_idx];
for (U16 ifile = 0; ifile < file_count; ifile += 1, file_name_offset_ptr += 1) {
Assert(*file_name_offset_ptr <= file_name_buffer_size);
String8 file_path = str8_cstring(file_name_buffer + *file_name_offset_ptr);
str8_list_push(arena, file_list, file_path);
}
}
scratch_end(scratch);
ProfEnd();
return file_info;
}
internal PDB_DbiModuleList
dbi_open_module_info(Arena *arena, MSF_Context *msf, MSF_StreamNumber sn, PDB_DbiHeader *dbi_header, String8List *file_info)
{
ProfBeginFunction();
PDB_DbiModuleList list = {0};
MSF_UInt module_info_pos = sizeof(PDB_DbiHeader);
msf_stream_seek(msf, sn, module_info_pos);
MSF_UInt module_info_opl = module_info_pos + dbi_header->module_info_size;
while (msf_stream_get_pos(msf, sn) < module_info_opl) {
PDB_DbiCompUnitHeader header = {0};
msf_stream_read_struct(msf, sn, &header);
String8 obj_path = msf_stream_read_string(arena, msf, sn);
String8 lib_path = msf_stream_read_string(arena, msf, sn);
msf_stream_align(msf, sn, PDB_MODULE_ALIGN);
String8List source_file_list = {0};
if (header.contribution.base.mod != CV_ModIndex_Invalid) {
source_file_list = file_info[header.contribution.base.mod];
}
PDB_DbiModule *mod = push_array(arena, PDB_DbiModule, 1);
mod->next = 0;
mod->sn = header.sn;
mod->imod = header.contribution.base.mod;
mod->sym_data_size = header.symbols_size;
mod->c11_data_size = header.c11_lines_size;
mod->c13_data_size = header.c13_lines_size;
mod->source_file_list = source_file_list;
mod->obj_path = obj_path;
mod->lib_path = lib_path;
mod->first_sc = header.contribution;
SLLQueuePush(list.first, list.last, mod);
list.count += 1;
}
ProfEnd();
return list;
}
internal PDB_DbiSectionContribList
dbi_open_sec_contrib(Arena *arena, MSF_Context *msf, MSF_StreamNumber sn, PDB_DbiHeader *dbi_header)
{
ProfBeginFunction();
PDB_DbiSectionContribList sec_contrib = {0};
if (dbi_header->sec_con_size > sizeof(PDB_DbiSectionContrib)) {
Temp scratch = scratch_begin(&arena, 1);
// seek to start of section contrib info
MSF_UInt sec_con_pos = sizeof(PDB_DbiHeader) + dbi_header->module_info_size;
msf_stream_seek(msf, sn, sec_con_pos);
// read header
PDB_DbiSectionContribVersion version = 0;
msf_stream_read_struct(msf, sn, &version);
// parse contrib items
switch (version) {
case PDB_DbiSectionContribVersion_1: {
U64 contrib_count = dbi_header->sec_con_size / sizeof(PDB_DbiSectionContrib);
PDB_DbiSectionContrib *src_contrib_array = push_array(scratch.arena, PDB_DbiSectionContrib, contrib_count);
MSF_UInt sec_con_read = msf_stream_read_array(msf, sn, &src_contrib_array[0], contrib_count);
Assert(sec_con_read == sizeof(src_contrib_array[0]) * contrib_count);
PDB_DbiSectionContribNode *dst_contrib_array = push_array_no_zero(arena, PDB_DbiSectionContribNode, contrib_count);
for (U64 icontrib = 0; icontrib < contrib_count; icontrib += 1) {
dst_contrib_array[icontrib].next = 0;
dst_contrib_array[icontrib].data = src_contrib_array[icontrib];
dbi_sec_contrib_list_push_node(&sec_contrib, &dst_contrib_array[icontrib]);
}
} break;
case PDB_DbiSectionContribVersion_2: {
NotImplemented;
} break;
default: Assert(!"unknown section contrib version"); break;
}
// have we exhausted sec-con bytes?
Assert(sec_con_pos + dbi_header->sec_con_size == msf_stream_get_pos(msf, sn));
scratch_end(scratch);
}
ProfEnd();
return sec_contrib;
}
internal void
dbi_build_section_header_stream(PDB_DbiContext *dbi, MSF_Context *msf, MSF_StreamNumber sn)
{
ProfBeginFunction();
U64 header_arr_size = sizeof(dbi->section_list.first->data) * dbi->section_list.count;
msf_stream_resize(msf, sn, header_arr_size);
msf_stream_seek(msf, sn, 0);
for (PDB_DbiSectionNode *i = dbi->section_list.first; i; i = i->next) {
msf_stream_write_struct(msf, sn, &i->data);
}
ProfEnd();
}
internal
THREAD_POOL_TASK_FUNC(dbi_build_file_info_assign_file_offsets_task)
{
ProfBeginFunction();
PDB_DbiBuildFileInfoTask *task = raw_task;
PDB_DbiModule *mod = task->mod_arr[task_id];
task->imod_arr[mod->imod] = mod->imod;
if (mod->imod != CV_ModIndex_Invalid) {
// assign source file count
task->source_file_name_count_arr[mod->imod] = safe_cast_u16x(mod->source_file_list.node_count);
// assign source file offsets
U64 source_file_idx = 0;
for (String8Node *string_n = mod->source_file_list.first; string_n != 0; string_n = string_n->next, ++source_file_idx) {
CV_StringBucket *string_bucket = cv_string_hash_table_lookup(task->string_ht, string_n->string);
task->source_file_name_offset_arr[mod->imod][source_file_idx] = safe_cast_u32(string_bucket->u.offset);
}
} else {
// module was deleted don't create source file info
task->source_file_name_count_arr[mod->imod] = 0;
}
ProfEnd();
}
internal String8List
dbi_build_file_info(Arena *arena, TP_Context *tp, PDB_DbiModuleList mod_list, CV_StringHashTable string_ht)
{
ProfBeginFunction();
Temp scratch = scratch_begin(&arena, 1);
U64 total_source_file_count = 0;
U64 mod_arr_count = 0;
PDB_DbiModule **mod_arr = push_array_no_zero(scratch.arena, PDB_DbiModule *, mod_list.count);
for (PDB_DbiModule *mod = mod_list.first; mod != 0; mod = mod->next) {
mod_arr[mod_arr_count++] = mod;
if (mod->imod != CV_ModIndex_Invalid) {
total_source_file_count += mod->source_file_list.node_count;
}
}
U32 **source_file_name_offsets_arr = push_array_no_zero(scratch.arena, U32 *, mod_list.count);
U32 *source_file_name_offsets = push_array_no_zero(arena, U32, total_source_file_count);
for (U64 mod_idx = 0, cursor = 0; mod_idx < mod_list.count; ++mod_idx) {
if (mod_arr[mod_idx]->imod != CV_ModIndex_Invalid) {
source_file_name_offsets_arr[mod_idx] = source_file_name_offsets + cursor;
cursor += mod_arr[mod_idx]->source_file_list.node_count;
} else {
source_file_name_offsets_arr[mod_idx] = 0;
}
}
U16 total_source_file_count16 = Min(max_U16, total_source_file_count);
U16 mod_count16 = Min(max_U16, mod_list.count);
PDB_DbiBuildFileInfoTask task = {0};
task.string_ht = string_ht;
task.mod_arr = mod_arr;
task.imod_arr = push_array_no_zero(arena, U16, mod_count16);
task.source_file_name_count_arr = push_array_no_zero(arena, U16, mod_list.count);
task.source_file_name_offset_arr = source_file_name_offsets_arr;
tp_for_parallel(tp, 0, mod_arr_count, dbi_build_file_info_assign_file_offsets_task, &task);
// pack strings
String8 string_buffer = cv_pack_string_hash_table(arena, tp, string_ht);
// layout file info sections
String8List file_info_srl = {0};
str8_serial_begin(arena, &file_info_srl);
str8_serial_push_u16(arena, &file_info_srl, mod_count16);
str8_serial_push_u16(arena, &file_info_srl, total_source_file_count16);
str8_list_push(arena, &file_info_srl, str8_array(task.imod_arr, mod_count16));
str8_list_push(arena, &file_info_srl, str8_array(task.source_file_name_count_arr, mod_list.count));
str8_list_push(arena, &file_info_srl, str8_array(source_file_name_offsets, total_source_file_count));
str8_list_push(arena, &file_info_srl, string_buffer);
str8_serial_push_align(arena, &file_info_srl, sizeof(U32));
scratch_end(scratch);
ProfEnd();
return file_info_srl;
}
internal String8List
dbi_build_module_info(Arena *arena, PDB_DbiContext *dbi, MSF_Context *msf)
{
ProfBeginFunction();
String8List module_info_list = {0};
str8_serial_begin(arena, &module_info_list);
for (PDB_DbiModule *mod = dbi->module_list.first; mod != 0; mod = mod->next) {
// fill out header
PDB_DbiCompUnitHeader *header = push_array(arena, PDB_DbiCompUnitHeader, 1);
header->contribution = mod->first_sc;
// we don't use these flags right now
// U16 is_written : 1
// U16 unused : 7
// U16 tsm_index : 8 ; index into type server map
header->flags = 0;
header->sn = mod->sn;
header->symbols_size = mod->sym_data_size;
header->c11_lines_size = mod->c11_data_size;
header->c13_lines_size = mod->c13_data_size;
header->num_contrib_files = Min(max_U16, mod->source_file_list.node_count);
header->file_names_offset = 0; // TODO: fill out the offset
// TODO: generate EC info
header->src_file = 0;
header->pdb_file = 0;
// push module info
str8_serial_push_struct(arena, &module_info_list, header);
str8_serial_push_cstr(arena, &module_info_list, mod->obj_path);
str8_serial_push_cstr(arena, &module_info_list, mod->lib_path);
str8_serial_push_align(arena, &module_info_list, PDB_MODULE_ALIGN);
}
ProfEnd();
return module_info_list;
}
#if 0
int
dbi_sc_compar(const PDB_DbiSectionContrib *a, const PDB_DbiSectionContrib *b)
{
#if 0
int cmp = 0;
if (a->base.sec == b->base.sec) {
if (a->base.sec_off < b->base.sec_off) {
cmp = -1;
} else if (a->base.sec_off > b->base.sec_off) {
cmp = +1;
}
} else if (a->base.sec < b->base.sec) {
cmp = -1;
} else {
cmp = +1;
}
#else
#define MAKE_SORTER(x) (((U64)(x)->base.sec << 32) | (U64)(x)->base.sec_off)
U64 l = MAKE_SORTER(a);
U64 r = MAKE_SORTER(b);
int cmp = l < r ? -1 : l > r ? + 1 : 0;
#undef MAKE_SORTER
#endif
return cmp;
}
#endif
internal void
lnk_radix_sort_dbi_sc_array(PDB_DbiSectionContrib *arr, U64 sc_count, U64 sect_count)
{
ProfBeginFunction();
#if 1
// faster but uses more memory
# define RADIX_BIT_COUNT 16
# define RADIX_MAX 2
#else
// slower but uses less memory
# define RADIX_BIT_COUNT 8
# define RADIX_MAX 4
#endif
Temp scratch = scratch_begin(0,0);
PDB_DbiSectionContrib *temp_arr = push_array_no_zero(scratch.arena, PDB_DbiSectionContrib, sc_count);
PDB_DbiSectionContrib *src_arr = arr;
PDB_DbiSectionContrib *dst_arr = temp_arr;
ProfBegin("Count Memzero");
U32 count_8lo[256]; MemoryZeroArray(count_8lo);
U32 count_8hi[256]; MemoryZeroArray(count_8hi);
U32 count_16[1 << 16]; MemoryZeroArray(count_16);
U32 *count_arr = push_array(scratch.arena, U32, sect_count + 1);
ProfEnd();
ProfBegin("Histogram");
for (U64 i = 0; i < sc_count; i += 1) {
PDB_DbiSectionContrib *sc = src_arr + i;
count_arr[sc->base.sec] += 1;
U64 digit_8lo = (sc->base.sec_off >> 0) % ArrayCount(count_8lo);
U64 digit_8hi = (sc->base.sec_off >> 8) % ArrayCount(count_8hi);
U64 digit_16 = (sc->base.sec_off >> 16) % ArrayCount(count_16);
count_8lo[digit_8lo] += 1;
count_8hi[digit_8hi] += 1;
count_16[digit_16] += 1;
}
ProfEnd();
//
// sort on section offset
//
ProfBegin("Offsets");
U32 offset_8lo = 0;
U32 offset_8hi = 0;
for (U64 i = 1; i <= ArrayCount(count_8lo); i += 1) {
U32 current_8lo = count_8lo[i - 1];
U32 current_8hi = count_8hi[i - 1];
count_8lo[i - 1] = offset_8lo;
count_8hi[i - 1] = offset_8hi;
offset_8lo += current_8lo;
offset_8hi += current_8hi;
}
U32 offset_16 = 0;
for (U64 i = 1; i <= ArrayCount(count_16); i += 1) {
U32 current_16 = count_16[i - 1];
count_16[i - 1] = offset_16;
offset_16 += current_16;
}
ProfEnd();
count_8lo[0] = 0;
count_8hi[0] = 0;
count_16[0] = 0;
ProfBegin("Order 8 Lo");
for (U64 i = 0; i < sc_count; i += 1) {
PDB_DbiSectionContrib *sc = &src_arr[i];
U64 digit = (sc->base.sec_off >> 0) % ArrayCount(count_8lo);
dst_arr[count_8lo[digit]++] = *sc;
}
ProfEnd();
ProfBegin("Order 8 Hi");
for (U64 i = 0; i < sc_count; i += 1) {
PDB_DbiSectionContrib *sc = &dst_arr[i];
U64 digit = (sc->base.sec_off >> 8) % ArrayCount(count_8hi);
src_arr[count_8hi[digit]++] = *sc;
}
ProfEnd();
ProfBegin("Order 16");
for (U64 i = 0; i < sc_count; i += 1) {
PDB_DbiSectionContrib *sc = &src_arr[i];
U64 digit = (sc->base.sec_off >> 16) % ArrayCount(count_16);
dst_arr[count_16[digit]++] = *sc;
}
ProfEnd();
//
// sort on section index
//
ProfBegin("Section Indices");
U32 offset = 0;
for (U64 i = 1; i <= sect_count; i += 1) {
U32 current = count_arr[i - 1];
count_arr[i - 1] = offset;
offset += current;
}
count_arr[0] = 0;
for (U64 i = 0; i < sc_count; i += 1) {
PDB_DbiSectionContrib *sc = dst_arr + i;
src_arr[count_arr[sc->base.sec]++] = *sc;
}
ProfEnd();
#if 0
for (U64 i = 1; i < sc_count; i += 1) {
U64 a = ((U64)arr[i - 1].base.sec << 32) | arr[i - 1].base.sec_off;
U64 b = ((U64)arr[i ].base.sec << 32) | arr[i ].base.sec_off;
Assert(a <= b);
}
#endif
scratch_end(scratch);
#undef RADIX_BIT_COUNT
#undef RADIX_MAX
ProfEnd();
}
internal String8List
dbi_build_sec_con(Arena *arena, PDB_DbiContext *dbi)
{
ProfBeginFunction();
PDB_DbiSectionContribVersion *version = push_array(arena, PDB_DbiSectionContribVersion, 1);
*version = PDB_DbiSectionContribVersion_1;
// push section contribs V1
ProfBegin("Push sect contribs [Count %llu]", dbi->sec_contrib_list.count);
PDB_DbiSectionContrib *sc_array = push_array_no_zero(arena, PDB_DbiSectionContrib, dbi->sec_contrib_list.count);
PDB_DbiSectionContrib *dst = &sc_array[0];
for (PDB_DbiSectionContribNode *src = dbi->sec_contrib_list.first; src != 0; src = src->next, dst += 1) {
*dst = src->data;
}
ProfEnd();
// sort section contribs so they are binary searchable
lnk_radix_sort_dbi_sc_array(sc_array, dbi->sec_contrib_list.count, dbi->section_list.count + 1);
// push section contrib info
ProfBegin("List Push");
String8List sec_con_list = {0};
str8_list_push(arena, &sec_con_list, str8((U8*)version, sizeof(*version)));
str8_list_push(arena, &sec_con_list, str8((U8*)sc_array, sizeof(sc_array[0])*dbi->sec_contrib_list.count));
ProfEnd();
ProfEnd();
return sec_con_list;
}
internal String8List
dbi_build_sec_map(Arena *arena, PDB_DbiContext *dbi)
{
ProfBeginFunction();
U64 entry_count = dbi->section_list.count + 1;
PDB_DbiSecMapEntry *entry_array = push_array(arena, PDB_DbiSecMapEntry, entry_count);
U64 isect = 0;
for (PDB_DbiSectionNode *sect = dbi->section_list.first; sect; sect = sect->next, ++isect) {
PDB_DbiSecMapEntry *s = &entry_array[isect];
COFF_SectionHeader *section_header = &sect->data;
if (section_header->flags & COFF_SectionFlag_MemRead) {
s->flags |= PDB_DbiOMF_READ;
}
if (section_header->flags & COFF_SectionFlag_MemWrite) {
s->flags |= PDB_DbiOMF_WRITE;
}
if (section_header->flags & COFF_SectionFlag_MemExecute) {
s->flags |= PDB_DbiOMF_EXEC;
}
if (~section_header->flags & COFF_SectionFlag_Mem16Bit) {
s->flags |= PDB_DbiOMF_IS_32BIT_ADDR;
}
s->flags |= PDB_DbiOMF_IS_SELECTOR; // always set
s->sec_size = section_header->vsize;
s->frame = isect + 1;
s->sec_name = max_U16;
s->class_name = max_U16;
}
// init last entry
{
PDB_DbiSecMapEntry *s = &entry_array[entry_count - 1];
s->flags = PDB_DbiOMF_IS_32BIT_ADDR | PDB_DbiOMF_IS_ABS_ADDR;
s->sec_size = max_U32;
s->frame = isect + 1;
s->sec_name = max_U16;
s->class_name = max_U16;
}
// init header
PDB_DbiSecMapHeader *header = push_array(arena, PDB_DbiSecMapHeader, 1);
header->section_count = entry_count;
header->segment_count = entry_count;
// push section map info
String8List sec_map_list = {0};
str8_list_push(arena, &sec_map_list, str8((U8*)header, sizeof(*header)));
str8_list_push(arena, &sec_map_list, str8((U8*)entry_array, sizeof(entry_array[0])*entry_count));
ProfEnd();
return sec_map_list;
}
internal String8List
dbi_build_dbg_header(Arena *arena, PDB_DbiContext *dbi, MSF_Context *msf)
{
ProfBeginFunction();
if (dbi->dbg_streams[PDB_DbiStream_SECTION_HEADER] == MSF_INVALID_STREAM_NUMBER) {
dbi->dbg_streams[PDB_DbiStream_SECTION_HEADER] = msf_stream_alloc(msf);
}
dbi_build_section_header_stream(dbi, msf, dbi->dbg_streams[PDB_DbiStream_SECTION_HEADER]);
String8List dbg_header_srl = {0};
str8_serial_begin(arena, &dbg_header_srl);
str8_serial_push_array(arena, &dbg_header_srl, dbi->dbg_streams, ArrayCount(dbi->dbg_streams));
ProfEnd();
return dbg_header_srl;
}
internal void
dbi_build(TP_Context *tp, PDB_DbiContext *dbi, MSF_Context *msf, MSF_StreamNumber dbi_sn, CV_StringHashTable string_ht, B32 is_stripped)
{
ProfBeginFunction();
Temp scratch = scratch_begin(0, 0);
ProfBegin("Build");
String8List module_info_list = dbi_build_module_info(scratch.arena, dbi, msf);
String8List sec_con_list = dbi_build_sec_con(scratch.arena, dbi);
String8List sec_map_list = dbi_build_sec_map(scratch.arena, dbi);
String8List file_info_list = dbi_build_file_info(scratch.arena, tp, dbi->module_list, string_ht);
String8List dbg_header_list = dbi_build_dbg_header(scratch.arena, dbi, msf);
String8List tsm_list = {0}; // TODO: TSM
ProfEnd();
PDB_DbiHeader header = {0};
header.sig = PDB_DbiHeaderSignature_V1;
header.version = PDB_DbiVersion_70;
header.age = dbi->age;
header.gsi_sn = dbi->globals_sn;
header.build_number = PDB_DbiMakeBuildNumber(14, 11);
header.psi_sn = dbi->publics_sn;
header.pdb_version = 0;
header.sym_sn = dbi->symbols_sn;
header.pdb_version2 = 0;
header.module_info_size = module_info_list.total_size;
header.sec_con_size = sec_con_list.total_size;
header.sec_map_size = sec_map_list.total_size;
header.file_info_size = file_info_list.total_size;
header.tsm_size = tsm_list.total_size;
header.mfc_index = 0;
header.dbg_header_size = dbg_header_list.total_size;
header.ec_info_size = pdb_strtab_get_serialized_size(&dbi->ec_names);
header.flags = 0;
header.machine = dbi->machine;
header.reserved = 0;
if (is_stripped) {
header.flags |= PDB_DbiHeaderFlag_Stripped;
}
ProfBegin("MSF Write");
U64 dbi_stream_size = sizeof(header) +
module_info_list.total_size +
sec_con_list.total_size +
sec_map_list.total_size +
file_info_list.total_size +
tsm_list.total_size +
dbg_header_list.total_size;
msf_stream_resize(msf, dbi_sn, dbi_stream_size);
msf_stream_seek_start(msf, dbi_sn);
msf_stream_write(msf, dbi_sn, &header, sizeof(header));
msf_stream_write_list(msf, dbi_sn, module_info_list);
msf_stream_write_list(msf, dbi_sn, sec_con_list);
msf_stream_write_list(msf, dbi_sn, sec_map_list);
msf_stream_write_list(msf, dbi_sn, file_info_list);
msf_stream_write_list(msf, dbi_sn, tsm_list);
pdb_strtab_build(&dbi->ec_names, msf, dbi_sn);
msf_stream_write_list(msf, dbi_sn, dbg_header_list);
ProfEnd();
ProfEnd();
scratch_end(scratch);
}
internal void
dbi_release(PDB_DbiContext *dbi)
{
ProfBeginFunction();
arena_release(dbi->arena);
ProfEnd();
}
internal PDB_DbiModule *
dbi_push_module(PDB_DbiContext *dbi, String8 obj_path, String8 lib_path)
{
// init module
PDB_DbiModule *mod = push_array(dbi->arena, PDB_DbiModule, 1);
mod->imod = safe_cast_u32(dbi->module_list.count);
mod->sn = MSF_INVALID_STREAM_NUMBER;
mod->obj_path = push_str8_copy(dbi->arena, obj_path);
mod->lib_path = push_str8_copy(dbi->arena, lib_path.size > 0 ? lib_path : obj_path);
// push to list
SLLQueuePush(dbi->module_list.first, dbi->module_list.last, mod);
dbi->module_list.count += 1;
return mod;
}
internal void
dbi_module_push_section_contrib(PDB_DbiContext *dbi,
PDB_DbiModule *mod,
ISectOff isect_off,
U32 size,
U32 data_crc,
U32 reloc_crc,
COFF_SectionFlags flags)
{
ProfBeginFunction();
PDB_DbiSectionContrib sc;
sc.base.sec = safe_cast_u16(isect_off.isect);
sc.base.sec_off = isect_off.off;
sc.base.size = size;
sc.base.flags = flags;
sc.base.mod = mod->imod;
sc.data_crc = data_crc;
sc.reloc_crc = reloc_crc;
PDB_DbiSectionContribNode *node = push_array_no_zero(dbi->arena, PDB_DbiSectionContribNode, 1);
node->data = sc;
dbi_sec_contrib_list_push_node(&dbi->sec_contrib_list, node);
// Mod1::fUpdateSecContrib
if (mod->first_sc.base.mod == 0) {
if (flags & COFF_SectionFlag_CntCode) {
mod->first_sc = sc;
}
}
ProfEnd();
}
internal String8
dbi_module_read_symbol_data(Arena *arena, MSF_Context *msf, PDB_DbiModule *mod)
{
String8 symbol_data = str8(0,0);
if (mod->sn != MSF_INVALID_STREAM_NUMBER) {
B32 is_seek_ok = msf_stream_seek(msf, mod->sn, 0);
if (is_seek_ok) {
symbol_data = msf_stream_read_block(arena, msf, mod->sn, mod->sym_data_size);
}
}
return symbol_data;
}
internal String8
dbi_module_read_c11_data(Arena *arena, MSF_Context *msf, PDB_DbiModule *mod)
{
String8 c11_data = str8(0,0);
if (mod->sn != MSF_INVALID_STREAM_NUMBER) {
MSF_UInt c11_data_pos = mod->sym_data_size;
B32 is_seek_ok = msf_stream_seek(msf, mod->sn, c11_data_pos);
if (is_seek_ok) {
c11_data = msf_stream_read_block(arena, msf, mod->sn, mod->c13_data_size);
}
}
return c11_data;
}
internal String8
dbi_module_read_c13_data(Arena *arena, MSF_Context *msf, PDB_DbiModule *mod)
{
String8 c13_data = str8(0,0);
if (mod->sn != MSF_INVALID_STREAM_NUMBER) {
MSF_UInt c13_data_pos = mod->sym_data_size + mod->c11_data_size;
B32 is_seek_ok = msf_stream_seek(msf, mod->sn, c13_data_pos);
if (is_seek_ok) {
c13_data = msf_stream_read_block(arena, msf, mod->sn, mod->c13_data_size);
}
}
return c13_data;
}
internal void
dbi_push_section(PDB_DbiContext *dbi, COFF_SectionHeader *hdr)
{
ProfBeginFunction();
PDB_DbiSectionNode *n = push_array(dbi->arena, PDB_DbiSectionNode, 1);
n->data = *hdr;
n->next = 0;
SLLQueuePush(dbi->section_list.first, dbi->section_list.last, n);
dbi->section_list.count += 1;
ProfEnd();
}
////////////////////////////////
internal MSF_Context *
pdb_alloc_msf(Arena *arena, U64 page_size)
{
ProfBeginFunction();
MSF_Context *msf = msf_alloc_(arena, page_size, MSF_DEFAULT_FPM);
MSF_StreamNumber null_sn = msf_stream_alloc(msf);
MSF_StreamNumber info_sn = msf_stream_alloc(msf);
MSF_StreamNumber tpi_sn = msf_stream_alloc(msf);
MSF_StreamNumber dbi_sn = msf_stream_alloc(msf);
MSF_StreamNumber ipi_sn = msf_stream_alloc(msf);
Assert(null_sn == 0);
Assert(info_sn == PDB_FixedStream_Info);
Assert(dbi_sn == PDB_FixedStream_Dbi);
Assert(tpi_sn == PDB_FixedStream_Tpi);
Assert(ipi_sn == PDB_FixedStream_Ipi);
ProfEnd();
return msf;
}
internal PDB_Context *
pdb_alloc_(Arena *arena, U64 page_size, COFF_MachineType machine, COFF_TimeStamp time_stamp, U32 age, Guid guid)
{
ProfBeginFunction();
PDB_Context *pdb = push_array(arena, PDB_Context, 1);
pdb->arena = arena;
pdb->msf = pdb_alloc_msf(arena, page_size);
pdb->info = pdb_info_alloc(age, time_stamp, guid);
pdb->dbi = dbi_alloc(machine, age);
pdb->gsi = gsi_alloc();
pdb->psi = psi_alloc();
pdb->type_servers[CV_TypeIndexSource_NULL] = push_array(arena, PDB_TypeServer, 1);
for (U64 i = CV_TypeIndexSource_NULL + 1; i < ArrayCount(pdb->type_servers); ++i) {
pdb->type_servers[i] = pdb_type_server_alloc(PDB_TYPE_SERVER_HASH_BUCKET_COUNT_CURRENT);
}
ProfEnd();
return pdb;
}
internal PDB_Context *
pdb_alloc(U64 page_size, COFF_MachineType machine, COFF_TimeStamp time_stamp, U32 age, Guid guid)
{
return pdb_alloc_(arena_alloc(.name = "PDB"), page_size, machine, time_stamp, age, guid);
}
internal void
pdb_release(PDB_Context *pdb)
{
ProfBeginFunction();
dbi_release(pdb->dbi);
gsi_release(pdb->gsi);
for (U64 i = 1; i < ArrayCount(pdb->type_servers); ++i) { pdb_type_server_release(&pdb->type_servers[i]); }
arena_release(pdb->arena);
ProfEnd();
}
internal void
pdb_set_machine(PDB_Context *pdb, COFF_MachineType machine)
{
pdb->dbi->machine = machine;
}
internal void
pdb_set_guid(PDB_Context *pdb, Guid guid)
{
pdb->info->guid = guid;
}
internal void
pdb_set_time_stamp(PDB_Context *pdb, COFF_TimeStamp time_stamp)
{
pdb->info->time_stamp = time_stamp;
}
internal void
pdb_set_age(PDB_Context *pdb, U32 age)
{
pdb->dbi->age = age;
pdb->info->age = age;
}
internal COFF_MachineType
pdb_get_machine(PDB_Context *pdb)
{
return pdb->dbi->machine;
}
internal COFF_TimeStamp
pdb_get_time_stamp(PDB_Context *pdb)
{
return pdb->info->time_stamp;
}
internal U32
pdb_get_age(PDB_Context *pdb)
{
return pdb->info->age;
}
internal Guid
pdb_get_guid(PDB_Context *pdb)
{
return pdb->info->guid;
}
internal void
pdb_build(TP_Context *tp, TP_Arena *pool_temp, PDB_Context *pdb, CV_StringHashTable string_ht, B32 build_gsi, B32 is_stripped)
{
ProfBeginFunction();
PDB_InfoContext *info = pdb->info;
PDB_StringTable *strtab = &info->strtab;
PDB_DbiContext *dbi = pdb->dbi;
PDB_TypeServer *tpi = pdb->type_servers[CV_TypeIndexSource_TPI];
PDB_TypeServer *ipi = pdb->type_servers[CV_TypeIndexSource_IPI];
pdb_type_server_build(tp, tpi, strtab, pdb->msf, PDB_FixedStream_Tpi);
if (info->flags & PDB_FeatureFlag_HAS_ID_STREAM) {
pdb_type_server_build(tp, ipi, strtab, pdb->msf, PDB_FixedStream_Ipi);
}
if (build_gsi) {
if (dbi->globals_sn == MSF_INVALID_STREAM_NUMBER) {
dbi->globals_sn = msf_stream_alloc(pdb->msf);
}
if (dbi->publics_sn == MSF_INVALID_STREAM_NUMBER) {
dbi->publics_sn = msf_stream_alloc(pdb->msf);
}
if (dbi->symbols_sn == MSF_INVALID_STREAM_NUMBER) {
dbi->symbols_sn = msf_stream_alloc(pdb->msf);
}
psi_build(tp, pdb->psi, pdb->msf, dbi->publics_sn, dbi->symbols_sn);
gsi_build(tp, pdb->gsi, pdb->msf, dbi->globals_sn, dbi->symbols_sn);
}
dbi_build(tp, pdb->dbi, pdb->msf, PDB_FixedStream_Dbi, string_ht, is_stripped);
pdb_info_build(pdb->info, pdb->msf, PDB_FixedStream_Info);
ProfEnd();
}
////////////////////////////////
internal String8
pdb_string_from_src_error(PDB_SrcError error)
{
switch (error) {
case PDB_SrcError_OK: return str8_lit("OK");
case PDB_SrcError_DUPLICATE_NAME_STREAM: return str8_lit("DUPLICATE_NAME_STREAM");
case PDB_SrcError_DUPLICATE_ENTRY: return str8_lit("DUPLICATE_ENTRY");
case PDB_SrcError_UNABLE_TO_WRITE_DATA: return str8_lit("UNABLE_TO_WRITE_DATA");
case PDB_SrcError_UNSUPPORTED_COMPRESSION: return str8_lit("UNSUPPORTED_COMPRESSION");
case PDB_SrcError_UNKNOWN: return str8_lit("UNKNOWN");
}
return str8(0,0);
}