batch relocations to reduce contention on global reference list

This commit is contained in:
Nikita Smith
2026-05-18 18:11:48 -07:00
parent 2683206ba2
commit 8c58086585
2 changed files with 157 additions and 165 deletions
+76 -84
View File
@@ -1813,7 +1813,7 @@ lnk_link_inputs(TP_Context *tp,
is_addr_import = hash_map_search_stringf_raw(&imports_hm, "__imp_%S", member_ref->link_symbol->name);
}
if (is_thunk_import != 0 || is_addr_import != 0) {
lnk_invalid_path("duplicate import member queue detected");
lnk_log(LNK_Log_Debug, "duplicate import %S member queue detected", member_ref->link_symbol->name);
break;
}
}
@@ -2379,62 +2379,53 @@ lnk_link_image(TP_Context *tp, TP_Arena *arena, LNK_Config *config, LNK_Inputer
}
internal void
lnk_reloc_refs_list_push_node(LNK_RelocRefsList *list, LNK_RelocRefsNode *node)
lnk_reloc_ref_batch_list_push_node(LNK_RelocRefsBatchList *list, LNK_RelocRefsBatch *node)
{
LNK_RelocRefsPointer old_head = list->head;
LNK_RelocRefsBatchPointer old_head = list->head;
node->next = old_head.node;
list->head = (LNK_RelocRefsPointer){ .node = node, .tag = old_head.tag + 1 };
list->head = (LNK_RelocRefsBatchPointer){ .node = node, .tag = old_head.tag + 1 };
}
internal LNK_RelocRefsNode *
lnk_reloc_refs_list_pop_node(LNK_RelocRefsList *list)
internal void
lnk_reloc_ref_batch_list_push(Arena *arena, LNK_RelocRefsBatchList *list, LNK_RelocRefs v)
{
LNK_RelocRefsPointer old_head = list->head;
LNK_RelocRefsBatch *batch = list->head.node;
if (list->head.node == 0 || list->head.node->count >= ArrayCount(list->head.node->v)) {
batch = push_array(arena, LNK_RelocRefsBatch, 1);
lnk_reloc_ref_batch_list_push_node(list, batch);
}
batch->v[batch->count++] = v;
}
internal LNK_RelocRefsBatch *
lnk_reloc_ref_batch_list_pop(LNK_RelocRefsBatchList *list)
{
LNK_RelocRefsBatchPointer old_head = list->head;
if (old_head.node) {
list->head = (LNK_RelocRefsPointer){ .node = old_head.node->next, .tag = old_head.tag + 1};
list->head = (LNK_RelocRefsBatchPointer){ .node = old_head.node->next, .tag = old_head.tag + 1};
}
return old_head.node;
}
internal LNK_RelocRefsNode *
lnk_reloc_refs_list_push(Arena *arena, LNK_RelocRefsList *list, LNK_RelocRefs *v)
internal LNK_RelocRefsBatch *
lnk_reloc_ref_batch_list_pop_atomic(LNK_RelocRefsBatchList *list)
{
LNK_RelocRefsNode *node = push_array(arena, LNK_RelocRefsNode, 1);
node->v = v;
lnk_reloc_refs_list_push_node(list, node);
return node;
}
internal LNK_RelocRefsNode *
lnk_reloc_refs_list_pop_node_atomic(LNK_RelocRefsList *list)
{
LNK_RelocRefsPointer old_head = { .node = ins_atomic_ptr_eval(&list->head.node), .tag = ins_atomic_u64_eval(&list->head.tag) };
LNK_RelocRefsBatchPointer old_head = { .node = ins_atomic_ptr_eval(&list->head.node), .tag = ins_atomic_u64_eval(&list->head.tag) };
for (;;) {
if (old_head.node == 0) { break; }
LNK_RelocRefsPointer new_head = { .node = old_head.node->next, .tag = old_head.tag + 1 };
LNK_RelocRefsBatchPointer new_head = { .node = old_head.node->next, .tag = old_head.tag + 1 };
if (ins_atomic_u128_eval_cond_assign(&list->head, &new_head, &old_head)) { break; }
}
return old_head.node;
}
internal void
lnk_reloc_refs_list_push_node_atomic(LNK_RelocRefsList *list, LNK_RelocRefsNode *node)
lnk_reloc_ref_batch_list_concat_in_place_atomic(LNK_RelocRefsBatchList *list, LNK_RelocRefsBatch *first, LNK_RelocRefsBatch *last)
{
LNK_RelocRefsPointer old_head = { .node = ins_atomic_ptr_eval(&list->head.node), .tag = ins_atomic_u64_eval(&list->head.tag) };
for (;;) {
node->next = old_head.node;
LNK_RelocRefsPointer new_head = { .node = node, .tag = old_head.tag + 1 };
if (ins_atomic_u128_eval_cond_assign(&list->head, &new_head, &old_head)) { break; }
}
}
internal void
lnk_reloc_refs_list_concat_in_place(LNK_RelocRefsList *list, LNK_RelocRefsNode *first, LNK_RelocRefsNode *last)
{
LNK_RelocRefsPointer old_head = { .node = ins_atomic_ptr_eval(&list->head.node), .tag = ins_atomic_u64_eval(&list->head.tag) };
LNK_RelocRefsBatchPointer old_head = { .node = ins_atomic_ptr_eval(&list->head.node), .tag = ins_atomic_u64_eval(&list->head.tag) };
for (;;) {
last->next = old_head.node;
LNK_RelocRefsPointer new_head = { .node = first, .tag = old_head.tag + 1 };
LNK_RelocRefsBatchPointer new_head = { .node = first, .tag = old_head.tag + 1 };
if (ins_atomic_u128_eval_cond_assign(&list->head, &new_head, &old_head)) { break; }
}
}
@@ -2454,11 +2445,10 @@ THREAD_POOL_TASK_FUNC(lnk_walk_relocs_and_mark_ref_sections_task)
U8 **is_live = 0;
U64 *active_thread_count = 0;
LNK_RelocRefsList *ref_queue = 0;
LNK_RelocRefsList free_list = {0};
LNK_RelocRefsBatchList *global_batch_list = 0;
if (task_id == 0) {
active_thread_count = push_array(scratch.arena, U64, 1);
ref_queue = push_array(scratch.arena, LNK_RelocRefsList, 1);
global_batch_list = push_array(scratch.arena, LNK_RelocRefsBatchList, 1);
// alloc live flags for each section
is_live = push_array_no_zero(scratch.arena, U8 *, objs.count);
@@ -2490,13 +2480,13 @@ THREAD_POOL_TASK_FUNC(lnk_walk_relocs_and_mark_ref_sections_task)
LNK_Symbol *root = lnk_symbol_table_search(symtab, root_n->v.name);
LNK_ObjSymbolRef root_ref = lnk_ref_from_symbol(root);
LNK_RelocRefs *r = push_array(scratch.arena, LNK_RelocRefs, 1);
r->obj = root_ref.obj;
r->relocs.count = 1;
r->relocs.v = push_array(scratch.arena, COFF_Reloc, 1);
r->relocs.v[0].isymbol = root_ref.symbol_idx;
LNK_RelocRefs r = {0};
r.obj = root_ref.obj;
r.relocs.count = 1;
r.relocs.v = push_array(scratch.arena, COFF_Reloc, 1);
r.relocs.v[0].isymbol = root_ref.symbol_idx;
lnk_reloc_refs_list_push(scratch.arena, ref_queue, r);
lnk_reloc_ref_batch_list_push(scratch.arena, global_batch_list, r);
}
// push task for every non-COMDAT section
@@ -2514,41 +2504,41 @@ THREAD_POOL_TASK_FUNC(lnk_walk_relocs_and_mark_ref_sections_task)
// divide relocs and push task for each reloc block
COFF_RelocArray relocs = lnk_coff_reloc_info_from_section_number(obj, section_number);
U64 new_task_count = CeilIntegerDiv(relocs.count, LNK_RELOCS_PER_TASK);
LNK_RelocRefs *new_tasks = push_array(scratch.arena, LNK_RelocRefs, new_task_count);
U64 relocs_per_batch = 1000;
U64 new_task_count = CeilIntegerDiv(relocs.count, relocs_per_batch);
for EachIndex(new_task_idx, new_task_count) {
LNK_RelocRefs *r = new_tasks + new_task_idx;
r->obj = obj;
r->relocs.count = Min(LNK_RELOCS_PER_TASK, relocs.count - (new_task_idx * LNK_RELOCS_PER_TASK));
r->relocs.v = relocs.v + (new_task_idx * LNK_RELOCS_PER_TASK);
lnk_reloc_refs_list_push(scratch.arena, ref_queue, r);
LNK_RelocRefs r = {0};
r.obj = obj;
r.relocs.count = Min(relocs_per_batch, relocs.count - (new_task_idx * relocs_per_batch));
r.relocs.v = relocs.v + (new_task_idx * relocs_per_batch);
lnk_reloc_ref_batch_list_push(scratch.arena, global_batch_list, r);
}
}
}
}
}
tp_broadcast(&is_live);
tp_broadcast(&ref_queue);
tp_broadcast(&global_batch_list);
tp_broadcast(&active_thread_count);
LNK_RelocRefsBatchList free_list = {0};
for (;;) {
// update active thread count
ins_atomic_u32_inc_eval(active_thread_count);
for (;;) {
// pop head node
LNK_RelocRefsNode *node = lnk_reloc_refs_list_pop_node_atomic(ref_queue);
if (!node) { break; }
// pop batch
LNK_RelocRefsBatch *batch = lnk_reloc_ref_batch_list_pop_atomic(global_batch_list);
if (!batch) { break; }
LNK_RelocRefs *reloc_refs = node->v;
LNK_RelocRefsNode *first_node = 0, *last_node = 0;
for EachIndex(reloc_idx, reloc_refs->relocs.count) {
COFF_Reloc *reloc = &reloc_refs->relocs.v[reloc_idx];
// walk batch relocations
LNK_RelocRefsBatch *first_batch = 0, *last_batch = 0;
for EachIndex(i, batch->count) {
for EachIndex(reloc_idx, batch->v[i].relocs.count) {
COFF_Reloc *reloc = &batch->v[i].relocs.v[reloc_idx];
// reloc -> symbol
LNK_ObjSymbolRef ref_symbol = (LNK_ObjSymbolRef){ .obj = reloc_refs->obj, .symbol_idx = reloc->isymbol };
LNK_ObjSymbolRef ref_symbol = (LNK_ObjSymbolRef){ .obj = batch->v[i].obj, .symbol_idx = reloc->isymbol };
{
Temp temp = temp_begin(scratch2.arena);
HashMap seen_hm = {0};
@@ -2559,8 +2549,8 @@ THREAD_POOL_TASK_FUNC(lnk_walk_relocs_and_mark_ref_sections_task)
if (hash_map_search_u64_u64(&seen_hm, symbol_key) == 0) {
hash_map_push_u64_u64(temp.arena, &seen_hm, symbol_key, 1);
} else {
COFF_ParsedSymbol reloc_parsed = lnk_parsed_symbol_from_coff_symbol_idx(reloc_refs->obj, reloc->isymbol);
lnk_error_obj(LNK_Warning_CyclicSymbol, reloc_refs->obj, "symbol %S forms a cyclic chain (/OPT:REF)", reloc_parsed.name);
COFF_ParsedSymbol reloc_parsed = lnk_parsed_symbol_from_coff_symbol_idx(batch->v[i].obj, reloc->isymbol);
lnk_error_obj(LNK_Warning_CyclicSymbol, batch->v[i].obj, "symbol %S forms a cyclic chain (/OPT:REF)", reloc_parsed.name);
MemoryZeroStruct(&ref_symbol);
break;
}
@@ -2620,52 +2610,54 @@ THREAD_POOL_TASK_FUNC(lnk_walk_relocs_and_mark_ref_sections_task)
if (section_header->flags & COFF_SectionFlag_LnkInfo) { continue; }
if (section_header->flags & LNK_SECTION_FLAG_DEBUG) { continue; }
// on first section visit set live flag and queue new section to walk
// on first section visit, set live flag and enqueue section
U8 was_visited = ins_atomic_u8_eval_assign(&is_live[ref_symbol.obj->input_idx][section_number], 1);
if (was_visited) { continue; }
LNK_RelocRefsNode *node;
LNK_RelocRefs refs = {0};
refs.obj = ref_symbol.obj;
refs.relocs = lnk_coff_reloc_info_from_section_number(ref_symbol.obj, section_number);
// get a batch node
LNK_RelocRefsBatch *batch = last_batch;
if (last_batch == 0 || last_batch->count >= ArrayCount(last_batch->v)) {
if (free_list.head.node) {
node = lnk_reloc_refs_list_pop_node(&free_list);
batch = lnk_reloc_ref_batch_list_pop(&free_list);
MemoryZeroStruct(batch);
} else {
node = push_array(scratch.arena, LNK_RelocRefsNode, 1);
node->v = push_array(scratch.arena, LNK_RelocRefs, 1);
batch = push_array(scratch.arena, LNK_RelocRefsBatch, 1);
}
SLLQueuePush(first_batch, last_batch, batch);
}
node->v->obj = ref_symbol.obj;
node->v->relocs = lnk_coff_reloc_info_from_section_number(ref_symbol.obj, section_number);
batch->v[batch->count++] = refs;
if (first_node == 0) {
first_node = node, last_node = node;
} else {
node->next = first_node;
first_node = node;
}
} while (stack);
temp_end(temp);
}
}
// free walk node
lnk_reloc_refs_list_push_node(&free_list, node);
}
// queue new walks
if (first_node && last_node) {
lnk_reloc_refs_list_concat_in_place(ref_queue, first_node, last_node);
if (first_batch && last_batch) {
lnk_reloc_ref_batch_list_concat_in_place_atomic(global_batch_list, first_batch, last_batch);
}
// put batch on the free list
lnk_reloc_ref_batch_list_push_node(&free_list, batch);
}
// are all threads done walking?
{
U32 c = ins_atomic_u32_dec_eval(active_thread_count);
if (c == 0 && ins_atomic_ptr_eval(&ref_queue->head.node) == 0) {
if (c == 0 && ins_atomic_ptr_eval(&global_batch_list->head.node) == 0) {
break;
}
}
// comprehensive solution to the waiting problem
for (; ins_atomic_ptr_eval(&ref_queue->head.node) == 0; ) {
for (; ins_atomic_ptr_eval(&global_batch_list->head.node) == 0; ) {
// was signaled to exit?
if (ins_atomic_u32_eval(active_thread_count) == 0) { goto exit; }
}
+13 -13
View File
@@ -140,9 +140,7 @@ typedef struct LNK_CommonBlockContrib
} u;
} LNK_CommonBlockContrib;
// --- Ref ---------------------------------------------------------------------
#define LNK_RELOCS_PER_TASK 0x1000
// --- Reloc Ref ---------------------------------------------------------------
typedef struct LNK_RelocRefs
{
@@ -150,25 +148,27 @@ typedef struct LNK_RelocRefs
COFF_RelocArray relocs;
} LNK_RelocRefs;
typedef struct LNK_RelocRefsNode
typedef struct LNK_RelocRefsBatch LNK_RelocRefsBatch;
struct LNK_RelocRefsBatch
{
LNK_RelocRefs *v;
struct LNK_RelocRefsNode *next;
} LNK_RelocRefsNode;
LNK_RelocRefsBatch *next;
U64 count;
LNK_RelocRefs v[64];
};
typedef union LNK_RelocRefsPointer
typedef union
{
struct {
LNK_RelocRefsNode *node;
LNK_RelocRefsBatch *node;
U64 tag;
};
U64 v[2];
} LNK_RelocRefsPointer;
} LNK_RelocRefsBatchPointer;
typedef struct AlignType(16) LNK_RelocRefsList
typedef struct AlignType(16)
{
LNK_RelocRefsPointer head;
} LNK_RelocRefsList;
LNK_RelocRefsBatchPointer head;
} LNK_RelocRefsBatchList;
// --- Base Reloc --------------------------------------------------------------