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); 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) { 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; break;
} }
} }
@@ -2379,62 +2379,53 @@ lnk_link_image(TP_Context *tp, TP_Arena *arena, LNK_Config *config, LNK_Inputer
} }
internal void 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; 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 * internal void
lnk_reloc_refs_list_pop_node(LNK_RelocRefsList *list) 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) { 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; return old_head.node;
} }
internal LNK_RelocRefsNode * internal LNK_RelocRefsBatch *
lnk_reloc_refs_list_push(Arena *arena, LNK_RelocRefsList *list, LNK_RelocRefs *v) lnk_reloc_ref_batch_list_pop_atomic(LNK_RelocRefsBatchList *list)
{ {
LNK_RelocRefsNode *node = push_array(arena, LNK_RelocRefsNode, 1); LNK_RelocRefsBatchPointer old_head = { .node = ins_atomic_ptr_eval(&list->head.node), .tag = ins_atomic_u64_eval(&list->head.tag) };
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) };
for (;;) { for (;;) {
if (old_head.node == 0) { break; } 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; } if (ins_atomic_u128_eval_cond_assign(&list->head, &new_head, &old_head)) { break; }
} }
return old_head.node; return old_head.node;
} }
internal void 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) }; LNK_RelocRefsBatchPointer 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) };
for (;;) { for (;;) {
last->next = old_head.node; 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; } 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; U8 **is_live = 0;
U64 *active_thread_count = 0; U64 *active_thread_count = 0;
LNK_RelocRefsList *ref_queue = 0; LNK_RelocRefsBatchList *global_batch_list = 0;
LNK_RelocRefsList free_list = {0};
if (task_id == 0) { if (task_id == 0) {
active_thread_count = push_array(scratch.arena, U64, 1); 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 // alloc live flags for each section
is_live = push_array_no_zero(scratch.arena, U8 *, objs.count); 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_Symbol *root = lnk_symbol_table_search(symtab, root_n->v.name);
LNK_ObjSymbolRef root_ref = lnk_ref_from_symbol(root); LNK_ObjSymbolRef root_ref = lnk_ref_from_symbol(root);
LNK_RelocRefs *r = push_array(scratch.arena, LNK_RelocRefs, 1); LNK_RelocRefs r = {0};
r->obj = root_ref.obj; r.obj = root_ref.obj;
r->relocs.count = 1; r.relocs.count = 1;
r->relocs.v = push_array(scratch.arena, COFF_Reloc, 1); r.relocs.v = push_array(scratch.arena, COFF_Reloc, 1);
r->relocs.v[0].isymbol = root_ref.symbol_idx; 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 // 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 // divide relocs and push task for each reloc block
COFF_RelocArray relocs = lnk_coff_reloc_info_from_section_number(obj, section_number); COFF_RelocArray relocs = lnk_coff_reloc_info_from_section_number(obj, section_number);
U64 new_task_count = CeilIntegerDiv(relocs.count, LNK_RELOCS_PER_TASK); U64 relocs_per_batch = 1000;
LNK_RelocRefs *new_tasks = push_array(scratch.arena, LNK_RelocRefs, new_task_count); U64 new_task_count = CeilIntegerDiv(relocs.count, relocs_per_batch);
for EachIndex(new_task_idx, new_task_count) { for EachIndex(new_task_idx, new_task_count) {
LNK_RelocRefs *r = new_tasks + new_task_idx; LNK_RelocRefs r = {0};
r->obj = obj; r.obj = obj;
r->relocs.count = Min(LNK_RELOCS_PER_TASK, relocs.count - (new_task_idx * LNK_RELOCS_PER_TASK)); r.relocs.count = Min(relocs_per_batch, relocs.count - (new_task_idx * relocs_per_batch));
r->relocs.v = relocs.v + (new_task_idx * LNK_RELOCS_PER_TASK); r.relocs.v = relocs.v + (new_task_idx * relocs_per_batch);
lnk_reloc_ref_batch_list_push(scratch.arena, global_batch_list, r);
lnk_reloc_refs_list_push(scratch.arena, ref_queue, r);
} }
} }
} }
} }
} }
tp_broadcast(&is_live); tp_broadcast(&is_live);
tp_broadcast(&ref_queue); tp_broadcast(&global_batch_list);
tp_broadcast(&active_thread_count); tp_broadcast(&active_thread_count);
LNK_RelocRefsBatchList free_list = {0};
for (;;) { for (;;) {
// update active thread count // update active thread count
ins_atomic_u32_inc_eval(active_thread_count); ins_atomic_u32_inc_eval(active_thread_count);
for (;;) { for (;;) {
// pop head node // pop batch
LNK_RelocRefsNode *node = lnk_reloc_refs_list_pop_node_atomic(ref_queue); LNK_RelocRefsBatch *batch = lnk_reloc_ref_batch_list_pop_atomic(global_batch_list);
if (!node) { break; } if (!batch) { break; }
LNK_RelocRefs *reloc_refs = node->v; // walk batch relocations
LNK_RelocRefsNode *first_node = 0, *last_node = 0; LNK_RelocRefsBatch *first_batch = 0, *last_batch = 0;
for EachIndex(i, batch->count) {
for EachIndex(reloc_idx, reloc_refs->relocs.count) { for EachIndex(reloc_idx, batch->v[i].relocs.count) {
COFF_Reloc *reloc = &reloc_refs->relocs.v[reloc_idx]; COFF_Reloc *reloc = &batch->v[i].relocs.v[reloc_idx];
// reloc -> symbol // 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); Temp temp = temp_begin(scratch2.arena);
HashMap seen_hm = {0}; 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) { if (hash_map_search_u64_u64(&seen_hm, symbol_key) == 0) {
hash_map_push_u64_u64(temp.arena, &seen_hm, symbol_key, 1); hash_map_push_u64_u64(temp.arena, &seen_hm, symbol_key, 1);
} else { } else {
COFF_ParsedSymbol reloc_parsed = lnk_parsed_symbol_from_coff_symbol_idx(reloc_refs->obj, reloc->isymbol); COFF_ParsedSymbol reloc_parsed = lnk_parsed_symbol_from_coff_symbol_idx(batch->v[i].obj, reloc->isymbol);
lnk_error_obj(LNK_Warning_CyclicSymbol, reloc_refs->obj, "symbol %S forms a cyclic chain (/OPT:REF)", reloc_parsed.name); lnk_error_obj(LNK_Warning_CyclicSymbol, batch->v[i].obj, "symbol %S forms a cyclic chain (/OPT:REF)", reloc_parsed.name);
MemoryZeroStruct(&ref_symbol); MemoryZeroStruct(&ref_symbol);
break; 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 & COFF_SectionFlag_LnkInfo) { continue; }
if (section_header->flags & LNK_SECTION_FLAG_DEBUG) { 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); U8 was_visited = ins_atomic_u8_eval_assign(&is_live[ref_symbol.obj->input_idx][section_number], 1);
if (was_visited) { continue; } 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) { 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 { } else {
node = push_array(scratch.arena, LNK_RelocRefsNode, 1); batch = push_array(scratch.arena, LNK_RelocRefsBatch, 1);
node->v = push_array(scratch.arena, LNK_RelocRefs, 1); }
SLLQueuePush(first_batch, last_batch, batch);
} }
node->v->obj = ref_symbol.obj; batch->v[batch->count++] = refs;
node->v->relocs = lnk_coff_reloc_info_from_section_number(ref_symbol.obj, section_number);
if (first_node == 0) {
first_node = node, last_node = node;
} else {
node->next = first_node;
first_node = node;
}
} while (stack); } while (stack);
temp_end(temp); temp_end(temp);
} }
} }
}
// free walk node
lnk_reloc_refs_list_push_node(&free_list, node);
// queue new walks // queue new walks
if (first_node && last_node) { if (first_batch && last_batch) {
lnk_reloc_refs_list_concat_in_place(ref_queue, first_node, last_node); 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? // are all threads done walking?
{ {
U32 c = ins_atomic_u32_dec_eval(active_thread_count); 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; break;
} }
} }
// comprehensive solution to the waiting problem // 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? // was signaled to exit?
if (ins_atomic_u32_eval(active_thread_count) == 0) { goto 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; } u;
} LNK_CommonBlockContrib; } LNK_CommonBlockContrib;
// --- Ref --------------------------------------------------------------------- // --- Reloc Ref ---------------------------------------------------------------
#define LNK_RELOCS_PER_TASK 0x1000
typedef struct LNK_RelocRefs typedef struct LNK_RelocRefs
{ {
@@ -150,25 +148,27 @@ typedef struct LNK_RelocRefs
COFF_RelocArray relocs; COFF_RelocArray relocs;
} LNK_RelocRefs; } LNK_RelocRefs;
typedef struct LNK_RelocRefsNode typedef struct LNK_RelocRefsBatch LNK_RelocRefsBatch;
struct LNK_RelocRefsBatch
{ {
LNK_RelocRefs *v; LNK_RelocRefsBatch *next;
struct LNK_RelocRefsNode *next; U64 count;
} LNK_RelocRefsNode; LNK_RelocRefs v[64];
};
typedef union LNK_RelocRefsPointer typedef union
{ {
struct { struct {
LNK_RelocRefsNode *node; LNK_RelocRefsBatch *node;
U64 tag; U64 tag;
}; };
U64 v[2]; U64 v[2];
} LNK_RelocRefsPointer; } LNK_RelocRefsBatchPointer;
typedef struct AlignType(16) LNK_RelocRefsList typedef struct AlignType(16)
{ {
LNK_RelocRefsPointer head; LNK_RelocRefsBatchPointer head;
} LNK_RelocRefsList; } LNK_RelocRefsBatchList;
// --- Base Reloc -------------------------------------------------------------- // --- Base Reloc --------------------------------------------------------------