// Copyright (c) 2025 Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) internal U8 lnk_code_align_byte_from_machine(COFF_MachineType machine) { U8 align_byte = 0; switch (machine) { case COFF_MachineType_X64: case COFF_MachineType_X86: { align_byte = 0xCC; } break; default: { lnk_not_implemented("TODO: set align value for machine %S", coff_string_from_machine_type(machine)); } break; } return align_byte; } internal U16 lnk_default_align_from_machine(COFF_MachineType machine) { U16 align = 0; switch (machine) { case COFF_MachineType_Unknown: break; case COFF_MachineType_X64: { align = 16; } break; default: { NotImplemented; } break; } return align; } internal LNK_SectionContrib * lnk_section_contrib_chunk_push(LNK_SectionContribChunk *chunk, U64 count) { Assert(chunk->count + count <= chunk->cap); LNK_SectionContrib *result = chunk->v[chunk->count]; chunk->count += count; return result; } internal LNK_SectionContribChunk * lnk_section_contrib_chunk_list_push_chunk(Arena *arena, LNK_SectionContribChunkList *list, U64 cap) { LNK_SectionContribChunk *chunk = push_array(arena, LNK_SectionContribChunk, 1); chunk->count = 0; chunk->cap = cap; chunk->v = push_array(arena, LNK_SectionContrib *, cap); chunk->v2 = push_array(arena, LNK_SectionContrib, cap); for (U64 i = 0; i < cap; i += 1) { chunk->v[i] = &chunk->v2[i]; } SLLQueuePush(list->first, list->last, chunk); list->chunk_count += 1; return chunk; } internal void lnk_section_contrib_chunk_list_concat_in_place(LNK_SectionContribChunkList *list, LNK_SectionContribChunkList *to_concat) { if (list->chunk_count == 0) { *list = *to_concat; } else { list->last->next = to_concat->first; list->last = to_concat->last; list->chunk_count += to_concat->chunk_count; } } internal LNK_SectionArray lnk_section_array_from_list(Arena *arena, LNK_SectionList list) { LNK_SectionArray result; result.count = 0; result.v = push_array_no_zero(arena, LNK_Section *, list.count); for (LNK_SectionNode *node = list.first; node != 0; node = node->next) { result.v[result.count] = &node->data; result.count += 1; } return result; } internal U64 lnk_voff_from_section_contrib(COFF_SectionHeader **image_section_table, LNK_SectionContrib *sc) { COFF_SectionHeader *sect_header = image_section_table[sc->u.sect_idx+1]; U64 voff = sect_header->voff + sc->u.off; return voff; } internal U64 lnk_foff_from_section_contrib(COFF_SectionHeader **image_section_table, LNK_SectionContrib *sc) { COFF_SectionHeader *sect_header = image_section_table[sc->u.sect_idx+1]; U64 foff = sect_header->foff + sc->u.off; return foff; } internal U64 lnk_fopl_from_section_contrib(COFF_SectionHeader **image_section_table, LNK_SectionContrib *sc) { U64 foff = lnk_foff_from_section_contrib(image_section_table, sc); return foff + sc->u.size; } internal LNK_SectionContrib * lnk_get_first_section_contrib(LNK_Section *sect) { if (sect->contribs.chunk_count > 0) { if (sect->contribs.first->count > 0) { return sect->contribs.first->v[0]; } } return 0; } internal LNK_SectionContrib * lnk_get_last_section_contrib(LNK_Section *sect) { if (sect->contribs.chunk_count > 0) { if (sect->contribs.last->count > 0) { return sect->contribs.last->v[sect->contribs.last->count-1]; } } return 0; } internal U64 lnk_get_section_contrib_size(LNK_Section *sect) { LNK_SectionContrib *first_sc = lnk_get_first_section_contrib(sect); LNK_SectionContrib *last_sc = lnk_get_last_section_contrib(sect); U64 size = (last_sc->u.off - first_sc->u.off) + last_sc->u.size; return size; } internal U64 lnk_get_first_section_contrib_voff(COFF_SectionHeader **image_section_table, LNK_Section *sect) { LNK_SectionContrib *sc = lnk_get_first_section_contrib(sect); return lnk_voff_from_section_contrib(image_section_table, sc); } internal LNK_SectionTable * lnk_section_table_alloc(void) { ProfBeginFunction(); Arena *arena = arena_alloc(); LNK_SectionTable *sectab = push_array(arena, LNK_SectionTable, 1); sectab->arena = arena; sectab->sect_ht = hash_table_init(arena, 256); ProfEnd(); return sectab; } internal void lnk_section_table_release(LNK_SectionTable **st_ptr) { ProfBeginFunction(); LNK_SectionTable *sectab = *st_ptr; arena_release(sectab->arena); *st_ptr = 0; ProfEnd(); } internal String8 lnk_make_name_with_flags(Arena *arena, String8 name, COFF_SectionFlags flags) { Temp scratch = scratch_begin(&arena, 1); String8List l = {0}; str8_list_push(scratch.arena, &l, name); str8_list_push(scratch.arena, &l, str8_struct(&flags)); String8 name_with_flags = str8_list_join(arena, &l, 0); scratch_end(scratch); return name_with_flags; } internal LNK_Section * lnk_section_table_push(LNK_SectionTable *sectab, String8 name, COFF_SectionFlags flags) { ProfBeginFunction(); LNK_SectionNode *sect_node = push_array(sectab->arena, LNK_SectionNode, 1); LNK_Section *sect = §_node->data; sect->id = sectab->id_max++; sect->name = push_str8_copy(sectab->arena, name); sect->flags = flags; LNK_SectionList *sect_list = §ab->list; SLLQueuePush(sect_list->first, sect_list->last, sect_node); sect_list->count += 1; String8 name_with_flags = lnk_make_name_with_flags(sectab->arena, name, flags); hash_table_push_string_raw(sectab->arena, sectab->sect_ht, name_with_flags, sect); ProfEnd(); return sect; } internal LNK_SectionNode * lnk_section_table_remove(LNK_SectionTable *sectab, String8 name) { ProfBeginFunction(); // find node LNK_SectionNode *node; for (node = sectab->list.first; node != 0; node = node->next) { if (str8_match(node->data.name, name, 0)) { break; } } // remove node { LNK_SectionList *list = §ab->list; if (list->count > 0) { if (list->first == node) { list->first = list->first->next; list->count -= 1; if (list->last == node) { list->last = 0; } } else { for (LNK_SectionNode *curr = list->first, *prev = 0; curr != 0; prev = curr, curr = curr->next) { if (curr == node) { prev->next = curr->next; list->count -= 1; if (list->last == curr) { list->last = prev; } break; } } } } } ProfEnd(); return node; } internal LNK_Section * lnk_section_table_search(LNK_SectionTable *sectab, String8 full_or_partial_name, COFF_SectionFlags flags) { Temp scratch = scratch_begin(0,0); String8 name = {0}; String8 postfix = {0}; coff_parse_section_name(full_or_partial_name, &name, &postfix); String8 name_with_flags = lnk_make_name_with_flags(scratch.arena, name, flags); LNK_Section *section= 0; hash_table_search_string_raw(sectab->sect_ht, name_with_flags, §ion); scratch_end(scratch); return section; } internal LNK_SectionArray lnk_section_table_search_many(Arena *arena, LNK_SectionTable *sectab, String8 full_or_partial_name) { String8 name = {0}; String8 postfix = {0}; coff_parse_section_name(full_or_partial_name, &name, &postfix); U64 match_count = 0; for (LNK_SectionNode *sect_n = sectab->list.first; sect_n != 0; sect_n = sect_n->next) { if (str8_match(sect_n->data.name, name, 0)) { match_count += 1; } } LNK_SectionArray result = {0}; if (match_count > 0) { result.count = 0; result.v = push_array(arena, LNK_Section *, match_count); for (LNK_SectionNode *sect_n = sectab->list.first; sect_n != 0; sect_n = sect_n->next) { if (str8_match(sect_n->data.name, name, 0)) { result.v[result.count++] = §_n->data; } } } return result; } internal void lnk_section_table_merge(LNK_SectionTable *sectab, LNK_MergeDirectiveList merge_list) { ProfBeginFunction(); Temp scratch = scratch_begin(0, 0); LNK_Section **src_dst = push_array(scratch.arena, LNK_Section *, sectab->id_max); for (LNK_MergeDirectiveNode *merge_node = merge_list.first; merge_node != 0; merge_node = merge_node->next) { LNK_MergeDirective *merge = &merge_node->data; // guard against illegal merges { local_persist String8 illegal_merge_sections[] = { str8_lit_comp(".rsrc"), str8_lit_comp(".reloc"), }; for (U64 i = 0; i < ArrayCount(illegal_merge_sections); i += 1) { if (str8_match(merge->src, illegal_merge_sections[i], 0)) { lnk_error(LNK_Error_IllegalSectionMerge, "illegal to merge %S with %S", illegal_merge_sections[i], merge->dst); } if (str8_match(merge->dst, illegal_merge_sections[i], 0)) { lnk_error(LNK_Error_IllegalSectionMerge, "illegal to merge %S with %S", merge->src, illegal_merge_sections[i]); } } } // guard against circular merges { if (str8_match(merge_node->data.dst, merge_node->data.src, 0)) { lnk_error(LNK_Error_CircularMerge, "detected circular /MERGE:%S=%S", merge_node->data.src, merge_node->data.dst); } for (LNK_SectionNode *sect_n = sectab->merge_list.first; sect_n != 0; sect_n = sect_n->next) { if (str8_match(sect_n->data.name, merge_node->data.dst, 0) || str8_match(sect_n->data.name, merge_node->data.src, 0)) { lnk_error(LNK_Error_CircularMerge, "detected circular /MERGE:%S=%S", merge_node->data.src, merge_node->data.dst); } } } // are we trying to merge section that was already merged? LNK_Section *merge_sect = 0; hash_table_search_string_raw(sectab->sect_ht, merge->src, &merge_sect); if (merge_sect && merge_sect->is_merged) { LNK_Section *dst = src_dst[merge_sect->id]; B32 is_ambiguous_merge = !str8_match(dst->name, merge->dst, 0); if (is_ambiguous_merge) { lnk_error(LNK_Warning_AmbiguousMerge, "Detected ambiguous section merge:"); lnk_supplement_error("%S => %S (Merged)", merge_sect->name, dst->name); lnk_supplement_error("%S => %S", merge_sect->name, merge->dst); } continue; } // find source seciton LNK_SectionArray src_matches = lnk_section_table_search_many(scratch.arena, sectab, merge->src); if (src_matches.count == 0) { continue; } LNK_Section *dst; { LNK_SectionArray dst_matches = lnk_section_table_search_many(scratch.arena, sectab, merge->dst); if (dst_matches.count > 1) { lnk_error(LNK_Warning_AmbiguousMerge, "unable to merge %S=%S, too many dest sections (%llu)", merge->src, merge->dst, dst_matches.count); continue; } // handle case where destination section doesn't exist if (dst_matches.count == 0) { dst = lnk_section_table_push(sectab, merge->dst, src_matches.v[0]->flags); } else { dst = dst_matches.v[0]; } } for (U64 src_idx = 0; src_idx < src_matches.count; src_idx += 1) { LNK_Section *src = src_matches.v[src_idx]; // update map src_dst[src->id] = dst; // merge section with destination lnk_section_contrib_chunk_list_concat_in_place(&dst->contribs, &src->contribs); src->is_merged = 1; src->merge_id = dst->id; // remove from output section list LNK_SectionNode *merge_node = lnk_section_table_remove(sectab, src->name); // move node to the merge list SLLQueuePush(sectab->merge_list.first, sectab->merge_list.last, merge_node); sectab->merge_list.count += 1; } } scratch_end(scratch); ProfEnd(); } internal LNK_SectionArray lnk_section_table_get_output_sections(Arena *arena, LNK_SectionTable *sectab) { LNK_SectionArray result = {0}; result.count = 0; result.v = push_array(arena, LNK_Section *, sectab->list.count); for (LNK_SectionNode *sect_node = sectab->list.first; sect_node != 0; sect_node = sect_node->next) { Assert(result.count < sectab->list.count); result.v[result.count] = §_node->data; result.count += 1; } U64 unused_entry_count = sectab->list.count - result.count; arena_pop(arena, unused_entry_count * sizeof(result.v[0])); return result; } internal void lnk_finalize_section_layout(LNK_SectionTable *sectab, LNK_Section *sect, U64 file_align) { U64 cursor = 0; for (LNK_SectionContribChunk *sc_chunk = sect->contribs.first; sc_chunk != 0; sc_chunk = sc_chunk->next) { for (U64 sc_idx = 0; sc_idx < sc_chunk->count; sc_idx += 1) { LNK_SectionContrib *sc = sc_chunk->v[sc_idx]; cursor = AlignPow2(cursor, sc->align); // store section contribution start offset U64 sc_off = cursor; // compute contrib size U64 sc_size = 0; for (String8Node *data_n = sc->data_list; data_n != 0; data_n = data_n->next) { sc_size += data_n->string.size; } cursor += sc_size; // assign offset and size sc->u.off = sc_off; sc->u.size = sc_size; } } if (~sect->flags & COFF_SectionFlag_CntUninitializedData) { sect->fsize = AlignPow2(cursor, file_align); } sect->vsize = cursor; } internal void lnk_assign_section_index(LNK_Section *sect, U64 sect_idx) { sect->sect_idx = sect_idx; // assign section indices to contribs for (LNK_SectionContribChunk *sc_chunk = sect->contribs.first; sc_chunk != 0; sc_chunk = sc_chunk->next) { for (U64 sc_idx = 0; sc_idx < sc_chunk->count; sc_idx += 1) { sc_chunk->v[sc_idx]->u.sect_idx = sect_idx; } } } internal void lnk_assign_section_virtual_space(LNK_Section *sect, U64 sect_align, U64 *voff_cursor) { sect->voff = *voff_cursor; *voff_cursor += sect->vsize; *voff_cursor = AlignPow2(*voff_cursor, sect_align); } internal void lnk_assign_section_file_space(LNK_Section *sect, U64 *foff_cursor) { if (~sect->flags & COFF_SectionFlag_CntUninitializedData) { sect->foff = *foff_cursor; *foff_cursor += sect->fsize; } } internal LNK_Section * lnk_finalized_section_from_id(LNK_SectionTable *sectab, U64 id) { for (LNK_SectionNode *sect_n = sectab->list.first; sect_n != 0; sect_n = sect_n->next) { if (sect_n->data.id == id) { return §_n->data; } } for (LNK_SectionNode *sect_n = sectab->merge_list.first; sect_n != 0; sect_n = sect_n->next) { if (sect_n->data.id == id) { return lnk_finalized_section_from_id(sectab, sect_n->data.merge_id); } } return 0; }