// Copyright (c) 2024 Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) //////////////////////////////// internal void lnk_error_obj(LNK_ErrorCode code, LNK_Obj *obj, char *fmt, ...) { Temp scratch = scratch_begin(0, 0); va_list args; va_start(args, fmt); String8 text = push_str8fv(scratch.arena, fmt, args); if (obj->lib_path.size) { lnk_error(code, "%S(%S): %S", obj->lib_path, obj->path, text); } else { lnk_error(code, "%S: %S", obj->path, text); } va_end(args); scratch_end(scratch); } //////////////////////////////// internal void lnk_input_obj_list_push_node(LNK_InputObjList *list, LNK_InputObj *node) { SLLQueuePush(list->first, list->last, node); ++list->count; } internal LNK_InputObj * lnk_input_obj_list_push(Arena *arena, LNK_InputObjList *list) { LNK_InputObj *node = push_array(arena, LNK_InputObj, 1); lnk_input_obj_list_push_node(list, node); return node; } internal LNK_InputObj ** lnk_array_from_input_obj_list(Arena *arena, LNK_InputObjList list) { LNK_InputObj **result = push_array_no_zero(arena, LNK_InputObj *, list.count); U64 i = 0; for (LNK_InputObj *n = list.first; n != 0; n = n->next, ++i) { Assert(i < list.count); result[i] = n; } return result; } internal void lnk_input_obj_list_concat_in_place(LNK_InputObjList *list, LNK_InputObjList *to_concat) { SLLConcatInPlace(list, to_concat); } internal int lnk_input_obj_compar(const void *raw_a, const void *raw_b) { const LNK_InputObj **a = (const LNK_InputObj **) raw_a; const LNK_InputObj **b = (const LNK_InputObj **) raw_b; int cmp = str8_compar_case_sensitive(&(*a)->path, &(*b)->path); return cmp; } internal int lnk_input_obj_compar_is_before(void *raw_a, void *raw_b) { LNK_InputObj **a = raw_a; LNK_InputObj **b = raw_b; int cmp = str8_compar_case_sensitive(&(*a)->path, &(*b)->path); int is_before = cmp < 0; return is_before; } internal LNK_InputObjList lnk_list_from_input_obj_arr(LNK_InputObj **arr, U64 count) { LNK_InputObjList list = {0}; for (U64 i = 0; i < count; ++i) { SLLQueuePush(list.first, list.last, arr[i]); ++list.count; } return list; } internal LNK_InputObjList lnk_input_obj_list_from_string_list(Arena *arena, String8List list) { LNK_InputObjList input_list = {0}; for (String8Node *path = list.first; path != 0; path = path->next) { LNK_InputObj *input = lnk_input_obj_list_push(arena, &input_list); input->is_thin = 1; input->dedup_id = path->string; input->path = path->string; } return input_list; } //////////////////////////////// internal LNK_Obj ** lnk_obj_arr_from_list(Arena *arena, LNK_ObjList list) { LNK_Obj **arr = push_array_no_zero(arena, LNK_Obj *, list.count); U64 idx = 0; for (LNK_ObjNode *node = list.first; node != 0; node = node->next, ++idx) { arr[idx] = &node->data; } return arr; } internal LNK_ObjNodeArray lnk_obj_list_reserve(Arena *arena, LNK_ObjList *list, U64 count) { LNK_ObjNodeArray arr = {0}; if (count) { arr.count = count; arr.v = push_array(arena, LNK_ObjNode, count); for (LNK_ObjNode *ptr = arr.v, *opl = arr.v + arr.count; ptr < opl; ++ptr) { SLLQueuePush(list->first, list->last, ptr); } list->count += count; } else { MemoryZeroStruct(&arr); } return arr; } internal LNK_ChunkList lnk_obj_search_chunks(Arena *arena, LNK_Obj *obj, String8 name, String8 postfix, B32 collect_discarded) { LNK_ChunkList list = {0}; for (U64 sect_idx = 0; sect_idx < obj->chunk_count; ++sect_idx) { String8 obj_sect_name = obj->sect_name_arr[sect_idx]; String8 obj_sect_sort = obj->sect_sort_arr[sect_idx]; B32 is_match = str8_match(obj_sect_name, name, 0) && str8_match(obj_sect_sort, postfix, 0); if (is_match) { LNK_ChunkPtr chunk = obj->chunk_arr[sect_idx]; if (!collect_discarded && lnk_chunk_is_discarded(chunk)) { continue; } LNK_ChunkNode *node = push_array_no_zero(arena, LNK_ChunkNode, 1); node->next = 0; node->data = chunk; SLLQueuePush(list.first, list.last, node); ++list.count; } } return list; } internal THREAD_POOL_TASK_FUNC(lnk_collect_obj_chunks_task) { U64 obj_idx = task_id; LNK_CollectObjChunksTaskData *task = raw_task; LNK_Obj *obj = task->obj_arr[obj_idx]; LNK_ChunkList *list_ptr = &task->list_arr[obj_idx]; *list_ptr = lnk_obj_search_chunks(arena, obj, task->name, task->postfix, task->collect_discarded); } internal LNK_ChunkList * lnk_collect_obj_chunks(TP_Context *tp, TP_Arena *arena, U64 obj_count, LNK_Obj **obj_arr, String8 name, String8 postfix, B32 collect_discarded) { LNK_CollectObjChunksTaskData task_data = {0}; task_data.obj_arr = obj_arr; task_data.name = name; task_data.postfix = postfix; task_data.list_arr = push_array_no_zero(arena->v[0], LNK_ChunkList, obj_count); task_data.collect_discarded = collect_discarded; tp_for_parallel(tp, arena, obj_count, lnk_collect_obj_chunks_task, &task_data); return task_data.list_arr; } internal THREAD_POOL_TASK_FUNC(lnk_symbol_collector) { LNK_SymbolCollector *task = raw_task; Rng1U64 range = task->range_arr[task_id]; LNK_SymbolList *list = &task->out_arr[task_id]; for (U64 obj_idx = range.min; obj_idx < range.max; ++obj_idx) { LNK_Obj *obj = &task->in_arr.v[obj_idx].data; for (LNK_SymbolNode *node = obj->symbol_list.first; node != 0; node = node->next) { if (node->data->type == task->type) { lnk_symbol_list_push(arena, list, node->data); } } } } internal LNK_SymbolList lnk_run_symbol_collector(TP_Context *tp, TP_Arena *arena, LNK_ObjNodeArray arr, LNK_SymbolType symbol_type) { ProfBeginFunction(); Temp scratch = scratch_begin(0,0); LNK_SymbolCollector task_data; task_data.type = symbol_type; task_data.range_arr = tp_divide_work(scratch.arena, arr.count, tp->worker_count); task_data.in_arr = arr; task_data.out_arr = push_array(scratch.arena, LNK_SymbolList, tp->worker_count); tp_for_parallel(tp, arena, tp->worker_count, lnk_symbol_collector, &task_data); LNK_SymbolList list = {0}; for (U64 ithread = 0; ithread < tp->worker_count; ++ithread) { lnk_symbol_list_concat_in_place(&list, &task_data.out_arr[ithread]); } scratch_end(scratch); ProfEnd(); return list; } internal THREAD_POOL_TASK_FUNC(lnk_default_lib_collector) { LNK_DefaultLibCollector *task = raw_task; Rng1U64 range = task->range_arr[task_id]; String8List *result = &task->out_arr[task_id]; for (U64 obj_idx = range.min; obj_idx < range.max; obj_idx += 1) { LNK_Obj *obj = &task->in_arr.v[obj_idx].data; String8List list = lnk_parse_default_lib_directive(arena, &obj->directive_info.v[LNK_Directive_DefaultLib]); str8_list_concat_in_place(result, &list); } } internal LNK_InputLibList lnk_collect_default_lib_obj_arr(TP_Context *tp, TP_Arena *arena, LNK_ObjNodeArray arr) { Temp scratch = scratch_begin(0,0); LNK_DefaultLibCollector task_data; task_data.range_arr = tp_divide_work(scratch.arena, arr.count, tp->worker_count); task_data.in_arr = arr; task_data.out_arr = push_array(scratch.arena, LNK_InputLibList, tp->worker_count); tp_for_parallel(tp, arena, tp->worker_count, lnk_default_lib_collector, &task_data); String8List result = str8_list_arr_concat(task_data.out_arr, tp->worker_count); scratch_end(scratch); return result; } internal THREAD_POOL_TASK_FUNC(lnk_manifest_dependency_collector) { LNK_ManifestDependencyCollector *task = raw_task; Rng1U64 range = task->range_arr[task_id]; String8List *list = &task->out_arr[task_id]; LNK_ObjNode *obj_ptr = &task->in_arr[range.min]; LNK_ObjNode *obj_opl = &task->in_arr[range.max]; for (; obj_ptr < obj_opl; obj_ptr += 1) { LNK_Obj *obj = &obj_ptr->data; LNK_DirectiveList *dirs = &obj->directive_info.v[LNK_Directive_ManifestDependency]; for (LNK_Directive *dir = dirs->first; dir != 0; dir = dir->next) { String8List dep = str8_list_copy(arena, &dir->value_list); str8_list_concat_in_place(list, &dep); } } } internal String8List lnk_collect_manifest_dependency_list(TP_Context *tp, TP_Arena *arena, LNK_ObjNodeArray obj_node_arr) { Temp scratch = scratch_begin(arena->v, arena->count); LNK_ManifestDependencyCollector task_data = {0}; task_data.in_arr = obj_node_arr.v; task_data.out_arr = push_array(scratch.arena, String8List, tp->worker_count); task_data.range_arr = tp_divide_work(scratch.arena, obj_node_arr.count, tp->worker_count); tp_for_parallel(tp, arena, tp->worker_count, lnk_manifest_dependency_collector, &task_data); String8List result = str8_list_arr_concat(task_data.out_arr, tp->worker_count); scratch_end(scratch); return result; } internal void lnk_sect_defn_list_push_node(LNK_SectDefnList *list, LNK_SectDefn *node) { SLLQueuePush(list->first, list->last, node); ++list->count; } internal LNK_SectDefn * lnk_sect_defn_list_push(Arena *arena, LNK_SectDefnList *list, LNK_Obj *obj, String8 name, U64 idx, COFF_SectionFlags flags) { LNK_SectDefn *node = push_array_no_zero(arena, LNK_SectDefn, 1); node->next = 0; node->obj = obj; node->name = name; node->idx = idx; node->flags = flags; lnk_sect_defn_list_push_node(list, node); return node; } internal void lnk_sect_defn_list_concat_in_place(LNK_SectDefnList *list, LNK_SectDefnList *to_concat) { SLLConcatInPlace(list, to_concat); } internal void lnk_sect_defn_list_concat_in_place_arr(LNK_SectDefnList *list, LNK_SectDefnList *to_concat_arr, U64 count) { SLLConcatInPlaceArray(list, to_concat_arr, count); } internal THREAD_POOL_TASK_FUNC(lnk_obj_initer) { Temp scratch = scratch_begin(&arena, 1); LNK_ObjIniter *task = raw_task; LNK_InputObj *input = task->inputs[task_id]; U64 obj_idx = task->obj_id_base + task_id; LNK_ObjNode *obj_node = task->obj_node_arr + task_id; LNK_Obj *obj = &obj_node->data; // cache path, we need it for error reports and debug stuff String8 cached_path = push_str8_copy(arena, input->path); String8 cached_lib_path = push_str8_copy(arena, input->lib_path); // parse coff obj COFF_HeaderInfo coff_info = coff_header_info_from_data(input->data); COFF_SectionHeader *coff_sect_arr = (COFF_SectionHeader *)(input->data.str + coff_info.section_array_off); void *coff_symbols = input->data.str + coff_info.symbol_off; // handle machines we dont support if (coff_info.machine != COFF_MachineType_UNKNOWN && coff_info.machine != COFF_MachineType_X64) { lnk_error(LNK_Error_UnsupportedMachine, "%S: %S machine is supported", input->path, coff_string_from_machine_type(coff_info.machine)); } // :function_pad_min U64 function_pad_min; if (task->function_pad_min) { function_pad_min = *task->function_pad_min; } else { function_pad_min = lnk_get_default_function_pad_min(coff_info.machine); } U64 chunk_count = 0; chunk_count += coff_info.section_count_no_null; chunk_count += 1; // :common_block String8 *sect_name_arr = push_array_no_zero(arena, String8, chunk_count); String8 *sect_sort_arr = push_array_no_zero(arena, String8, chunk_count); LNK_Chunk *chunk_arr = push_array_no_zero(arena, LNK_Chunk, chunk_count); // init section name and postfix array for (U64 sect_idx = 0; sect_idx < coff_info.section_count_no_null; sect_idx += 1) { COFF_SectionHeader *coff_sect = &coff_sect_arr[sect_idx]; // read name String8 sect_name = coff_name_from_section_header(coff_sect, input->data, coff_info.string_table_off); // parse section name String8 name, postfix; coff_parse_section_name(sect_name, &name, &postfix); // fill out sect_name_arr[sect_idx] = name; sect_sort_arr[sect_idx] = postfix; } // :common_block U64 common_block_idx = chunk_count - 1; sect_name_arr[common_block_idx] = str8_lit(".bss"); sect_sort_arr[common_block_idx] = str8_lit("~"); for (U64 sect_idx = 0; sect_idx < coff_info.section_count_no_null; sect_idx += 1) { COFF_SectionHeader *coff_sect = &coff_sect_arr[sect_idx]; String8 data; if (coff_sect->flags & COFF_SectionFlag_CNT_UNINITIALIZED_DATA) { data = str8(0, coff_sect->fsize); } else { data = str8(input->data.str + coff_sect->foff, coff_sect->fsize); } LNK_Chunk *chunk = &chunk_arr[sect_idx]; chunk->ref = lnk_chunk_ref(0,0); // :chunk_ref_assign chunk->align = coff_align_size_from_section_flags(coff_sect->flags); chunk->min_size = 0; chunk->is_discarded = !!(coff_sect->flags & COFF_SectionFlag_LNK_REMOVE); chunk->sort_chunk = 1; chunk->type = LNK_Chunk_Leaf; chunk->sort_idx = sect_sort_arr[sect_idx]; chunk->input_idx = LNK_MakeChunkInputIdx(obj_idx, sect_idx); chunk->flags = coff_sect->flags; chunk->associate = 0; chunk->u.leaf = data; lnk_chunk_set_debugf(arena, chunk, "%S: name: %S, obj_idx: 0x%llX isect: 0x%llX", cached_path, sect_name_arr[sect_idx], obj_idx, sect_idx); } // :common_block LNK_Chunk *master_common_block = &chunk_arr[common_block_idx]; master_common_block->ref = lnk_chunk_ref(0,0); // :chunk_ref_assign master_common_block->align = 1; master_common_block->is_discarded = 0; master_common_block->sort_chunk = 0; master_common_block->type = LNK_Chunk_List; master_common_block->sort_idx = sect_sort_arr[common_block_idx]; master_common_block->input_idx = LNK_MakeChunkInputIdx(obj_idx, common_block_idx); master_common_block->flags = LNK_BSS_SECTION_FLAGS; master_common_block->associate = 0; master_common_block->u.list = push_array(arena, LNK_ChunkList, 1); lnk_chunk_set_debugf(arena, master_common_block, "%S: master common block", cached_path); LNK_ChunkPtr *chunk_ptr_arr = push_array_no_zero(arena, LNK_ChunkPtr, chunk_count); for (U64 i = 0; i < chunk_count; ++i) { chunk_ptr_arr[i] = &chunk_arr[i]; } // convert from coff B32 is_big_obj = coff_info.type == COFF_DataType_BIG_OBJ; LNK_SymbolArray symbol_arr = lnk_symbol_array_from_coff(arena, input->data, obj, cached_path, is_big_obj, function_pad_min, coff_info.string_table_off, coff_info.section_count_no_null, coff_sect_arr, coff_info.symbol_count, coff_symbols, chunk_ptr_arr, master_common_block); LNK_SymbolList symbol_list = lnk_symbol_list_from_array(arena, symbol_arr); LNK_RelocList *reloc_list_arr = lnk_reloc_list_array_from_coff(arena, coff_info.machine, input->data, coff_info.section_count_no_null, coff_sect_arr, chunk_ptr_arr, symbol_arr); // fill out obj obj->data = input->data; obj->path = cached_path; obj->lib_path = cached_lib_path; obj->input_idx = obj_idx; obj->machine = coff_info.machine; obj->chunk_count = chunk_count; obj->sect_count = coff_info.section_count_no_null; obj->sect_name_arr = sect_name_arr; obj->sect_sort_arr = sect_sort_arr; obj->chunk_arr = chunk_ptr_arr; obj->symbol_list = symbol_list; obj->sect_reloc_list_arr = reloc_list_arr; obj->directive_info = lnk_init_directives(arena, cached_path, coff_info.section_count_no_null, sect_name_arr, chunk_arr); // parse exports LNK_ExportParseList export_parse = {0}; for (LNK_Directive *dir = obj->directive_info.v[LNK_Directive_Export].first; dir != 0; dir = dir->next) { lnk_parse_export_direcive(arena, &obj->export_parse, dir->value_list, obj); } // push /export symbols for (LNK_ExportParse *exp = export_parse.first; exp != 0; exp = exp->next) { LNK_Symbol *symbol = lnk_make_undefined_symbol(arena, exp->name, LNK_SymbolScopeFlag_Main); lnk_symbol_list_push(arena, &obj->symbol_list, symbol); } // push /include symbols for (LNK_Directive *dir = obj->directive_info.v[LNK_Directive_Include].first; dir != 0; dir = dir->next) { str8_list_concat_in_place(&obj->include_symbol_list, &dir->value_list); } // parse /alternatename for (LNK_Directive *dir = obj->directive_info.v[LNK_Directive_AlternateName].first; dir != 0; dir = dir->next) { String8 *invalid_string = lnk_parse_alt_name_directive_list(arena, dir->value_list, &obj->alt_name_list); if (invalid_string != 0) { lnk_error_obj(LNK_Error_Cmdl, obj, "invalid syntax \"%S\", expected format \"FROM=TO\"", *invalid_string); } } scratch_end(scratch); } internal THREAD_POOL_TASK_FUNC(lnk_obj_new_sect_scanner) { LNK_ObjNewSectScanner *task = raw_task; Rng1U64 range = task->range_arr[task_id]; HashTable *ht = hash_table_init(arena, 128); for (U64 obj_idx = range.min; obj_idx < range.max; obj_idx += 1) { LNK_Obj *obj = &task->obj_node_arr[obj_idx].data; for (U64 chunk_idx = 0; chunk_idx < obj->chunk_count; chunk_idx += 1) { String8 sect_name = obj->sect_name_arr[chunk_idx]; COFF_SectionFlags sect_flags = obj->chunk_arr[chunk_idx]->flags & ~COFF_SectionFlags_LNK_FLAGS; KeyValuePair *is_present = hash_table_search_string(ht, sect_name); if (is_present) { if (lnk_is_error_code_active(LNK_Warning_SectionFlagsConflict)) { LNK_SectDefn *defn = is_present->value_raw; if (defn->flags != sect_flags) { lnk_sect_defn_list_push(arena, &task->defn_arr[task_id], obj, sect_name, chunk_idx, sect_flags); } } } else { LNK_SectDefn *defn = lnk_sect_defn_list_push(arena, &task->defn_arr[task_id], obj, sect_name, chunk_idx, sect_flags); hash_table_push_string_raw(arena, ht, sect_name, defn); } } } } LNK_CHUNK_VISITOR_SIG(lnk_chunk_get_count_cb) { U64 *counter = (U64 *)ud; *counter += 1; return 0; } internal THREAD_POOL_TASK_FUNC(lnk_chunk_counter) { U64 obj_idx = task_id; LNK_ChunkCounter *task = raw_task; LNK_Obj *obj = &task->obj_arr[obj_idx].data; for (U64 chunk_idx = 0; chunk_idx < obj->chunk_count; chunk_idx += 1) { String8 name = obj->sect_name_arr[chunk_idx]; LNK_Chunk *chunk = obj->chunk_arr[chunk_idx]; LNK_Section *sect = lnk_section_table_search(task->st, name); U64 count = 0; lnk_visit_chunks(0, chunk, lnk_chunk_get_count_cb, &count); task->chunk_count_arr_arr[sect->id][obj_idx] += count; } } internal LNK_CHUNK_VISITOR_SIG(lnk_chunk_ref_assign) { LNK_ChunkRefAssign *ctx = ud; // alloc chunk id U64 chunk_id = ctx->chunk_id_arr_arr[sect_id][ctx->obj_idx]; ctx->chunk_id_arr_arr[sect_id][ctx->obj_idx] += 1; // set chunk ref chunk->ref = lnk_chunk_ref(sect_id, chunk_id); // keep visiting chunks return 0; } internal THREAD_POOL_TASK_FUNC(lnk_chunk_ref_assigner) { LNK_ChunkRefAssigner *task = raw_task; Rng1U64 range = task->range_arr[task_id]; for (U64 obj_idx = range.min; obj_idx < range.max; obj_idx += 1) { LNK_Obj *obj = &task->obj_arr[obj_idx].data; for (U64 chunk_idx = 0; chunk_idx < obj->chunk_count; chunk_idx += 1) { String8 name = obj->sect_name_arr[chunk_idx]; String8 sort = obj->sect_sort_arr[chunk_idx]; LNK_Chunk *chunk = obj->chunk_arr[chunk_idx]; // :find_chunk_section LNK_Section *sect = lnk_section_table_search(task->st, name); Assert(sect); // :chunk_ref_assign LNK_ChunkRefAssign ctx; ctx.cman = sect->cman; ctx.chunk_id_arr_arr = task->chunk_id_arr_arr; ctx.obj_idx = obj_idx; lnk_visit_chunks(sect->id, chunk, lnk_chunk_ref_assign, &ctx); // push to section chunk list LNK_ChunkList **chunk_list_arr_arr = sort.size ? task->chunk_list_arr_arr : task->nosort_chunk_list_arr_arr; lnk_chunk_list_push(arena, &chunk_list_arr_arr[sect->id][task_id], chunk); } } } internal LNK_ObjNodeArray lnk_obj_list_push_parallel(TP_Context *tp, TP_Arena *arena, LNK_ObjList *obj_list, LNK_SectionTable *st, U64 *function_pad_min, U64 input_count, LNK_InputObj **inputs) { ProfBeginFunction(); Temp scratch = scratch_begin(arena->v, arena->count); U64 obj_id_base = obj_list->count; LNK_ObjNodeArray obj_arr = lnk_obj_list_reserve(arena->v[0], obj_list, input_count); ProfBegin("Obj Initer"); { LNK_ObjIniter task = {0}; task.inputs = inputs; task.obj_id_base = obj_id_base; task.obj_node_arr = obj_arr.v; task.function_pad_min = function_pad_min; tp_for_parallel(tp, arena, input_count, lnk_obj_initer, &task); } ProfEnd(); if (st) { ProfBegin("Section Table Update"); { TP_Temp temp = tp_temp_begin(arena); LNK_ObjNewSectScanner task; task.range_arr = tp_divide_work(arena->v[0], obj_arr.count, tp->worker_count); task.obj_node_arr = obj_arr.v; task.defn_arr = push_array(arena->v[0], LNK_SectDefnList, tp->worker_count); task.conf_arr = push_array(arena->v[0], LNK_SectDefnList, tp->worker_count); tp_for_parallel(tp, arena, tp->worker_count, lnk_obj_new_sect_scanner, &task); LNK_SectDefnList defn_list = {0}; LNK_SectDefnList conf_list = {0}; lnk_sect_defn_list_concat_in_place_arr(&defn_list, task.defn_arr, tp->worker_count); lnk_sect_defn_list_concat_in_place_arr(&conf_list, task.conf_arr, tp->worker_count); HashTable *ht = hash_table_init(arena->v[0], 128); for (LNK_SectionNode *sect_node = st->list.first; sect_node != 0; sect_node = sect_node->next) { LNK_Section *sect = §_node->data; hash_table_push_string_u64(arena->v[0], ht, sect->name, sect->flags); } LNK_SectDefnList new_list = {0}; for (LNK_SectDefn *curr = defn_list.first, *next; curr != 0; curr = next) { next = curr->next; curr->next = 0; KeyValuePair *is_present = hash_table_search_string(ht, curr->name); if (is_present) { if (lnk_is_error_code_active(LNK_Warning_SectionFlagsConflict)) { COFF_SectionFlags flags = is_present->value_u64; if (flags != curr->flags) { lnk_sect_defn_list_push_node(&conf_list, curr); } else { // section is present or is in new_list } } } else { lnk_sect_defn_list_push_node(&new_list, curr); hash_table_push_string_u64(arena->v[0], ht, curr->name, curr->flags); } } for (LNK_SectDefn *defn = conf_list.first; defn != 0; defn = defn->next) { KeyValuePair *is_present = hash_table_search_string(ht, defn->name); if (!is_present) { InvalidPath; } U64 sect_number = (defn->idx + 1); COFF_SectionFlags expected_flags = is_present->value_u64; String8 expected_flags_str = coff_string_from_section_flags(scratch.arena, expected_flags); String8 current_flags_str = coff_string_from_section_flags(scratch.arena, defn->flags); lnk_error_obj(LNK_Warning_SectionFlagsConflict, defn->obj, "detected section flags conflict in %S(No. %X); expected {%S} but got {%S}", defn->name, sect_number, expected_flags_str, current_flags_str); } // push new sections for :find_chunk_section for (LNK_SectDefn *curr = new_list.first; curr != 0; curr = curr->next) { lnk_section_table_push(st, curr->name, curr->flags & ~COFF_SectionFlags_LNK_FLAGS); } tp_temp_end(temp); } ProfEnd(); ProfBegin("Count Chunks Per Section"); U64 **chunk_id_arr_arr; { U64 **chunk_count_arr_arr = push_array_no_zero(scratch.arena, U64 *, st->id_max); for (U64 sect_idx = 0; sect_idx < st->id_max; sect_idx += 1) { chunk_count_arr_arr[sect_idx] = push_array(scratch.arena, U64, obj_arr.count); } LNK_ChunkCounter task; task.st = st; task.obj_arr = obj_arr.v; task.chunk_count_arr_arr = chunk_count_arr_arr; tp_for_parallel(tp, 0, obj_arr.count, lnk_chunk_counter, &task); chunk_id_arr_arr = chunk_count_arr_arr; for (U64 sect_idx = 1; sect_idx < st->id_max; sect_idx += 1) { LNK_Section *sect = lnk_section_table_search_id(st, sect_idx); if (!sect) continue; for (U64 obj_idx = 0; obj_idx < obj_arr.count; obj_idx += 1) { U64 chunk_id_base = sect->cman->total_chunk_count; sect->cman->total_chunk_count += chunk_count_arr_arr[sect_idx][obj_idx]; chunk_id_arr_arr[sect_idx][obj_idx] = chunk_id_base; } } } ProfEnd(); ProfBegin("Assign Chunk Refs"); { LNK_ChunkRefAssigner task; task.st = st; task.range_arr = tp_divide_work(scratch.arena, obj_arr.count, tp->worker_count); task.chunk_id_arr_arr = chunk_id_arr_arr; task.obj_arr = obj_arr.v; task.nosort_chunk_list_arr_arr = lnk_make_chunk_list_arr_arr(scratch.arena, st->id_max, tp->worker_count); task.chunk_list_arr_arr = lnk_make_chunk_list_arr_arr(scratch.arena, st->id_max, tp->worker_count); tp_for_parallel(tp, arena, tp->worker_count, lnk_chunk_ref_assigner, &task); // merge chunks for (LNK_SectionNode *sect_node = st->list.first; sect_node != 0; sect_node = sect_node->next) { LNK_Section *sect = §_node->data; lnk_chunk_list_concat_in_place_arr(sect->nosort_chunk->u.list, task.nosort_chunk_list_arr_arr[sect->id], tp->worker_count); lnk_chunk_list_concat_in_place_arr(sect->root->u.list, task.chunk_list_arr_arr[sect->id], tp->worker_count); } } ProfEnd(); } ProfEnd(); scratch_end(scratch); return obj_arr; } internal LNK_SymbolArray lnk_symbol_array_from_coff(Arena *arena, String8 coff_data, LNK_Obj *obj, String8 obj_path, B32 is_big_obj, U64 function_pad_min, U64 string_table_off, U64 sect_count, COFF_SectionHeader *coff_sect_arr, U64 coff_symbol_count, void *coff_symbols, LNK_ChunkPtr *chunk_ptr_arr, LNK_Chunk *master_common_block) { LNK_SymbolArray symbol_array; symbol_array.count = coff_symbol_count; symbol_array.v = push_array(arena, LNK_Symbol, symbol_array.count); for (U64 symbol_idx = 0; symbol_idx < coff_symbol_count; ++symbol_idx) { String8 name; U32 section_number; U32 value; COFF_SymStorageClass storage_class; COFF_SymbolType type; U64 aux_symbol_count; void *aux_symbols; if (is_big_obj) { COFF_Symbol32 *coff_symbol32 = (COFF_Symbol32 *)coff_symbols + symbol_idx; name = coff_read_symbol_name(coff_data, string_table_off, &coff_symbol32->name); section_number = coff_symbol32->section_number; value = coff_symbol32->value; storage_class = coff_symbol32->storage_class; type = coff_symbol32->type; aux_symbol_count = coff_symbol32->aux_symbol_count; aux_symbols = coff_symbol32 + 1; } else { COFF_Symbol16 *coff_symbol16 = (COFF_Symbol16 *)coff_symbols + symbol_idx; name = coff_read_symbol_name(coff_data, string_table_off, &coff_symbol16->name); section_number = coff_symbol16->section_number; value = coff_symbol16->value; storage_class = coff_symbol16->storage_class; type = coff_symbol16->type; aux_symbol_count = coff_symbol16->aux_symbol_count; aux_symbols = coff_symbol16 + 1; // promote special section numbers to 32 bit if (section_number == COFF_SYMBOL_DEBUG_SECTION_16) { section_number = COFF_SYMBOL_DEBUG_SECTION; } else if (section_number == COFF_SYMBOL_ABS_SECTION_16) { section_number = COFF_SYMBOL_ABS_SECTION; } } if (symbol_idx + aux_symbol_count + 1 > coff_symbol_count) { lnk_error(LNK_Error_IllData, "%S: symbol %S has out of bounds aux symbol count %u", obj_path, name, aux_symbol_count); } COFF_SymbolValueInterpType interp = coff_interp_symbol(section_number, value, storage_class); switch (interp) { case COFF_SymbolValueInterp_REGULAR: { if (section_number == 0 || section_number > sect_count) { lnk_error(LNK_Error_IllData, "%S: out ouf bounds section index in symbol \"%S (%u)\"", obj_path, name, section_number); break; } COFF_SectionHeader *coff_sect_header = &coff_sect_arr[section_number-1]; if (value > coff_sect_header->fsize) { lnk_error(LNK_Error_IllData, "%S: out of bounds section offset in symbol \"%S (%u)\"", obj_path, name, value); break; } LNK_DefinedSymbolVisibility visibility = LNK_DefinedSymbolVisibility_Static; if (storage_class == COFF_SymStorageClass_EXTERNAL) { visibility = LNK_DefinedSymbolVisibility_Extern; } LNK_DefinedSymbolFlags flags = 0; if (COFF_SymbolType_IsFunc(type)) { flags |= LNK_DefinedSymbolFlag_IsFunc; } LNK_Chunk *chunk = chunk_ptr_arr[section_number-1]; U64 offset = value; COFF_ComdatSelectType selection = COFF_ComdatSelectType_ANY; U64 check_sum = 0; if (coff_sect_header->flags & COFF_SectionFlag_LNK_COMDAT) { B32 has_static_def = value == 0 && type.u.lsb == COFF_SymType_NULL && storage_class == COFF_SymStorageClass_STATIC && aux_symbol_count == 1; if (has_static_def) { COFF_SymbolSecDef *secdef = aux_symbols; selection = secdef->selection; check_sum = secdef->check_sum; if (secdef->selection == COFF_ComdatSelectType_ASSOCIATIVE) { U32 secdef_number = secdef->number_lo; if (is_big_obj) { secdef_number |= (U32)secdef->number_hi << 16; } if (secdef_number == 0 || secdef_number > sect_count) { lnk_error(LNK_Error_IllData, "%S: symbol %u has out of bounds section definition number %u", name, symbol_idx, secdef_number); } LNK_Chunk *head_chunk = chunk_ptr_arr[secdef_number-1]; LNK_Chunk *associate_chunk = chunk_ptr_arr[section_number-1]; lnk_chunk_associate(arena, head_chunk, associate_chunk); } } } if (function_pad_min) { if ((flags & LNK_DefinedSymbolFlag_IsFunc)) { if (offset > 0) { // convert leaf to list // // there is no way to know up front how many splits we have, // so lazily convert chunks when see two or more functions // in a section if (chunk->type == LNK_Chunk_Leaf) { // make a list chunk LNK_Chunk *chunk_list = push_array(arena, LNK_Chunk, 1); chunk_list->type = LNK_Chunk_List; chunk_list->align = chunk->align; chunk_list->is_discarded = chunk->is_discarded; chunk_list->sort_idx = chunk->sort_idx; chunk_list->input_idx = chunk->input_idx; chunk_list->flags = chunk->flags; chunk_list->associate = chunk->associate; chunk_list->u.list = push_array(arena, LNK_ChunkList, 1); lnk_chunk_set_debugf(arena, chunk_list, "%S: function chunk list for %S", obj_path, name); // reset chunk properties chunk->align = 1; chunk->min_size = function_pad_min; chunk->sort_chunk = 0; chunk->sort_idx = str8_zero(); chunk->input_idx = 0; chunk->associate = 0; // push leaf to list lnk_chunk_list_push(arena, chunk_list->u.list, chunk); // set list as target chunk chunk = chunk_list; // set list chunk to be head of this section chunk_ptr_arr[section_number-1] = chunk_list; } // find chunk that is near symbol U64 cursor = 0; LNK_ChunkNode *current = chunk->u.list->last; for (LNK_ChunkNode *c = chunk->u.list->first; c != 0; c = c->next) { Assert(c->data->type == LNK_Chunk_Leaf); if (cursor + c->data->u.leaf.size > value) { current = c; break; } cursor += c->data->u.leaf.size; } if (cursor < value) { // bifurcate chunk at symbol offset U64 split_pos = value - cursor; Rng1U64 left_data_range = rng_1u64(0, split_pos); Rng1U64 right_data_range = rng_1u64(left_data_range.max, current->data->u.leaf.size); String8 left_data = str8_substr(current->data->u.leaf, left_data_range); String8 right_data = str8_substr(current->data->u.leaf, right_data_range); // create new chunk for new part LNK_Chunk *split_chunk = push_array(arena, LNK_Chunk, 1); split_chunk->align = 1; split_chunk->is_discarded = current->data->is_discarded; split_chunk->type = LNK_Chunk_Leaf; split_chunk->flags = current->data->flags; split_chunk->u.leaf = right_data; lnk_chunk_set_debugf(arena, chunk, "%S: chunk split on function %S", obj_path, name); LNK_ChunkNode *split_node = push_array(arena, LNK_ChunkNode, 1); split_node->data = split_chunk; // update split chunk data current->data->u.leaf = left_data; // insert split chunk after current chunk split_node->next = current->next; current->next = split_node; chunk->u.list->count += 1; // point symbol to new split chunk chunk = split_chunk; offset = 0; } else { // chunk was already split at the offset chunk = current->data; offset = value - cursor; } } chunk->min_size = function_pad_min; } // if symbol points to bifurcated section then we have an alias symbol (e.g. memmove is an alias to memcpy) // and we need to find chunk that is near symbol if (chunk->type == LNK_Chunk_List) { U64 cursor = 0; for (LNK_ChunkNode *c = chunk->u.list->first; c != 0; c = c->next) { if (cursor + c->data->u.leaf.size > value) { chunk = c->data; offset = offset - cursor; break; } cursor += c->data->u.leaf.size; } } } Assert(chunk->type == LNK_Chunk_Leaf); LNK_Symbol *symbol = &symbol_array.v[symbol_idx]; lnk_init_defined_symbol_chunk(symbol, name, visibility, flags, chunk, offset, selection, check_sum); symbol->obj = obj; } break; case COFF_SymbolValueInterp_WEAK: { if (aux_symbol_count == 0) { lnk_error(LNK_Error_IllData, "%S: Weak symbol \"%S (%u)\" must at least one aux symbol", obj_path, name, symbol_idx); } COFF_SymbolWeakExt *weak_ext = aux_symbols; if (weak_ext->tag_index >= coff_symbol_count) { lnk_error(LNK_Error_IllData, "%S: Weak symbol \"%S (%u)\" points to out of bounds symbol", obj_path, name, symbol_idx); break; } LNK_Symbol *symbol = &symbol_array.v[symbol_idx]; LNK_Symbol *fallback_symbol = &symbol_array.v[weak_ext->tag_index]; lnk_init_weak_symbol(symbol, name, weak_ext->characteristics, fallback_symbol); symbol->obj = obj; fallback_symbol->obj = obj; } break; case COFF_SymbolValueInterp_UNDEFINED: { LNK_Symbol *symbol = &symbol_array.v[symbol_idx]; lnk_init_undefined_symbol(symbol, name, LNK_SymbolScopeFlag_Main); symbol->obj = obj; } break; case COFF_SymbolValueInterp_COMMON: { // :common_block // // TODO: sort chunks on size to reduce bss usage LNK_Chunk *chunk = push_array(arena, LNK_Chunk, 1); chunk->align = Min(32, u64_up_to_pow2(value)); // link.exe caps align at 32 bytes chunk->type = LNK_Chunk_Leaf; chunk->flags = master_common_block->flags; chunk->u.leaf = str8(0, value); lnk_chunk_set_debugf(arena, chunk, "%S: common block %S", obj_path, name); lnk_chunk_list_push(arena, master_common_block->u.list, chunk); LNK_DefinedSymbolFlags flags = 0; if (COFF_SymbolType_IsFunc(type)) { flags |= LNK_DefinedSymbolFlag_IsFunc; } LNK_Symbol *symbol = &symbol_array.v[symbol_idx]; lnk_init_defined_symbol_chunk(symbol, name, LNK_DefinedSymbolVisibility_Extern, flags, chunk, 0, COFF_ComdatSelectType_LARGEST, 0); symbol->obj = obj; } break; case COFF_SymbolValueInterp_ABS: { // Never code or data, synthetic symbol. COFF spec says bits in value are used // as flags in symbol @feat.00, other symbols like @comp.id and @vol.md are undocumented. // LLVM uses undocumented mask 0x4800 on @feat.00 to tell if object was compiled with /guard:cf. LNK_DefinedSymbolVisibility visibility = LNK_DefinedSymbolVisibility_Static; if (storage_class == COFF_SymStorageClass_EXTERNAL) { visibility = LNK_DefinedSymbolVisibility_Extern; } LNK_Symbol *symbol = &symbol_array.v[symbol_idx]; lnk_init_defined_symbol_va(symbol, name, visibility, 0, value); symbol->obj = obj; } break; case COFF_SymbolValueInterp_DEBUG: break; } // skip aux symbols symbol_idx += aux_symbol_count; } return symbol_array; } internal LNK_RelocList * lnk_reloc_list_array_from_coff(Arena *arena, COFF_MachineType machine, String8 coff_data, U64 sect_count, COFF_SectionHeader *coff_sect_arr, LNK_ChunkPtr *chunk_ptr_arr, LNK_SymbolArray symbol_array) { LNK_RelocList *reloc_list_arr = push_array_no_zero(arena, LNK_RelocList, sect_count); for (U64 sect_idx = 0; sect_idx < sect_count; ++sect_idx) { COFF_SectionHeader *coff_header = &coff_sect_arr[sect_idx]; COFF_RelocInfo coff_reloc_info = coff_reloc_info_from_section_header(coff_data, coff_header); COFF_Reloc *coff_reloc_v = (COFF_Reloc *)(coff_data.str + coff_reloc_info.array_off); LNK_Chunk *sect_chunk = chunk_ptr_arr[sect_idx]; reloc_list_arr[sect_idx] = lnk_reloc_list_from_coff_reloc_array(arena, machine, sect_chunk, symbol_array, coff_reloc_v, coff_reloc_info.count); } return reloc_list_arr; } internal LNK_DirectiveInfo lnk_init_directives(Arena *arena, String8 obj_path, U64 chunk_count, String8 *sect_name_arr, LNK_Chunk *chunk_arr) { LNK_DirectiveInfo directive_info = {0}; for (U64 chunk_idx = 0; chunk_idx < chunk_count; chunk_idx += 1) { String8 sect_name = sect_name_arr[chunk_idx]; LNK_Chunk *sect_chunk = &chunk_arr[chunk_idx]; Assert(sect_chunk->type == LNK_Chunk_Leaf); if (!str8_match(sect_name, str8_lit(".drectve"), 0)) { continue; } if (sect_chunk->u.leaf.size < 3) { lnk_error(LNK_Warning_IllData, "%S: can't parse %S", obj_path, sect_name); continue; } if (~sect_chunk->flags & COFF_SectionFlag_LNK_INFO) { lnk_error(LNK_Warning_IllData, "%S: %S missing COFF_SectionFlag_LNK_INFO.", obj_path, sect_name); } // TODO: warn if section has relocations lnk_parse_directives(arena, &directive_info, sect_chunk->u.leaf, obj_path); int bad_vs = 0; (void)bad_vs; } return directive_info; } internal MSCRT_FeatFlags lnk_obj_get_features(LNK_Obj *obj) { MSCRT_FeatFlags result = 0; LNK_Symbol *sym = lnk_symbol_list_search(obj->symbol_list, str8_lit("@feat.00"), 0); if (sym) { Assert(LNK_Symbol_IsDefined(sym->type)); Assert(sym->u.defined.value_type == LNK_DefinedSymbolValue_VA); result = sym->u.defined.u.va; } return result; } internal U32 lnk_obj_get_comp_id(LNK_Obj *obj) { U32 result = 0; LNK_Symbol *sym = lnk_symbol_list_search(obj->symbol_list, str8_lit("@comp.id"), 0); if (sym) { Assert(LNK_Symbol_IsDefined(sym->type)); Assert(sym->u.defined.value_type == LNK_DefinedSymbolValue_VA); result = sym->u.defined.u.va; } return result; } internal U32 lnk_obj_get_vol_md(LNK_Obj *obj) { U32 result = 0; LNK_Symbol *sym = lnk_symbol_list_search(obj->symbol_list, str8_lit("@vol.md"), 0); if (sym) { Assert(LNK_Symbol_IsDefined(sym->type)); Assert(sym->u.defined.value_type == LNK_DefinedSymbolValue_VA); result = sym->u.defined.u.va; } return result; }