implement /OPT:ICF

Add identical COMDAT folding using a color-refinement over foldable
read-only sections. Hash section contents, relocation targets, and target
colors until partitions stabilize, then redirect folded COMDAT symlinks
and discard duplicate sections.

Honor .llvm_addrsig via /LLVM_ADDRSIG so address-significant sections
are not folded, keep separate color space for code, unwind info, and
MSVC vftables, and update COMDAT symbol/section mapping after folding.

Add linker tests covering function folding, relocation-sensitive folds,
alignment, color-space separation, symlink chains, .llvm_addrsig,
pdata/xdata, and C++ ICF cases.
This commit is contained in:
Nikita Smith
2026-07-27 14:47:08 -07:00
committed by Ryan Fleury
parent 1ea9fae835
commit d34e1a7802
11 changed files with 1869 additions and 153 deletions
+660 -103
View File
@@ -171,6 +171,8 @@ lnk_make_default_cmd_line(Arena *arena, LNK_CmdLine user_cmd_line)
"/RAD_SORT_IMPORTS", "/RAD_SORT_IMPORTS",
(char*)str8f(scratch.arena, "/RAD_MT_PATH:%s", LNK_MANIFEST_MERGE_TOOL_NAME).str, (char*)str8f(scratch.arena, "/RAD_MT_PATH:%s", LNK_MANIFEST_MERGE_TOOL_NAME).str,
(char*)str8f(scratch.arena, "/RAD_DATA_DIR_COUNT:%u", PE_DataDirectoryIndex_COUNT).str, (char*)str8f(scratch.arena, "/RAD_DATA_DIR_COUNT:%u", PE_DataDirectoryIndex_COUNT).str,
"/LLVM_ADDRSIG",
}; };
char *push_opts[] = { char *push_opts[] = {
@@ -234,11 +236,10 @@ lnk_make_default_cmd_line(Arena *arena, LNK_CmdLine user_cmd_line)
} }
// when /FORCE is specified on the command line, do not stop on these errors // when /FORCE is specified on the command line, do not stop on these errors
#if 0 if (lnk_cmd_line_has_switch(user_cmd_line, LNK_CmdSwitch_Force)) {
if (lnk_cmd_line_has_switch(cmd_line, LNK_CmdSwitch_Force)) {
g_error_mode_arr[LNK_Error_UnresolvedSymbol] = LNK_ErrorMode_Continue; g_error_mode_arr[LNK_Error_UnresolvedSymbol] = LNK_ErrorMode_Continue;
g_error_mode_arr[LNK_Error_RelocationAgainstRemovedSection] = LNK_ErrorMode_Continue;
} }
#endif
#undef DefaultOpt #undef DefaultOpt
#undef PushOpt #undef PushOpt
@@ -2397,19 +2398,31 @@ lnk_link_image(TP_Context *tp, TP_Arena *arena, LNK_Config *config, LNK_Inputer
scratch_end(debug_scratch); scratch_end(debug_scratch);
} }
// TODO: /FORCE if (unresolved_symbols_count && !config->force) {
if (unresolved_symbols_count) {
lnk_exit(LNK_Error_UnresolvedSymbol); lnk_exit(LNK_Error_UnresolvedSymbol);
} }
ProfEnd(); ProfEnd();
} }
// {
// discard COMDAT sections that are not referenced LNK_Obj **objs = 0;
//
if (config->opt_ref == LNK_SwitchState_Yes) { //
lnk_opt_ref(tp, symtab, config, link->objs); // discard COMDAT sections that are not referenced
//
if (config->opt_ref == LNK_SwitchState_Yes) {
if (objs == 0) { objs = lnk_array_from_obj_list(scratch.arena, link->objs); }
lnk_opt_ref(tp, symtab, config, objs, link->objs.count);
}
//
// fold duplicate sections
//
if (config->opt_icf == LNK_SwitchState_Yes) {
if (objs == 0) { objs = lnk_array_from_obj_list(scratch.arena, link->objs); }
lnk_opt_icf(tp, symtab, config, objs, link->objs.count);
}
} }
// //
@@ -2503,38 +2516,128 @@ lnk_reloc_ref_batch_list_concat_in_place_atomic(LNK_RelocRefsBatchList *list, LN
} }
} }
internal U32Array *
lnk_obj_indices_from_section_counts(Arena *arena, U64 worker_count, LNK_Obj **objs, U64 objs_count)
{
Temp scratch = scratch_begin(&arena, 1);
U64 *worker_section_counts = push_array(scratch.arena, U64, worker_count);
U64 *worker_obj_counts = push_array(scratch.arena, U64, worker_count);
U32Array *obj_indices = push_array(arena, U32Array, worker_count);
for EachIndex(obj_idx, objs_count) {
U64 min_worker_idx = 0;
for (U64 worker_idx = 1; worker_idx < worker_count; worker_idx += 1) {
if (worker_section_counts[worker_idx] < worker_section_counts[min_worker_idx]) {
min_worker_idx = worker_idx;
}
}
worker_section_counts[min_worker_idx] += objs[obj_idx]->header.section_count_no_null;
worker_obj_counts[min_worker_idx] += 1;
}
for EachIndex(worker_idx, worker_count) {
obj_indices[worker_idx].v = push_array_no_zero(arena, U32, worker_obj_counts[worker_idx]);
}
MemoryZero(worker_section_counts, sizeof(worker_section_counts[0])*worker_count);
MemoryZero(worker_obj_counts, sizeof(worker_obj_counts[0])*worker_count);
for EachIndex(obj_idx, objs_count) {
U64 min_worker_idx = 0;
for (U64 worker_idx = 1; worker_idx < worker_count; worker_idx += 1) {
if (worker_section_counts[worker_idx] < worker_section_counts[min_worker_idx]) {
min_worker_idx = worker_idx;
}
}
U32Array *worker_obj_indices = &obj_indices[min_worker_idx];
worker_obj_indices->v[worker_obj_counts[min_worker_idx]++] = (U32)obj_idx;
worker_obj_indices->count += 1;
worker_section_counts[min_worker_idx] += objs[obj_idx]->header.section_count_no_null;
}
scratch_end(scratch);
return obj_indices;
}
internal B32
lnk_resolve_reloc_target_symbol(Arena *arena, LNK_SymbolTable *symtab, LNK_ObjSymbolRef symbol, String8 pass_name, LNK_ObjSymbolRef *resolved_symbol_out)
{
B32 is_resolved = 1;
Temp temp = temp_begin(arena);
HashMap seen_hm = {0};
B32 keep_walking = 1;
LNK_ObjSymbolRef result = symbol;
do {
// detect cyclic chains
U64 symbol_key = ((U64)result.obj->input_idx << 32ull) | (U64)result.symbol_idx;
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 symbol_parsed = lnk_parsed_symbol_from_coff_symbol_idx(symbol.obj, symbol.symbol_idx);
lnk_error_obj(LNK_Warning_CyclicSymbol, symbol.obj, "symbol %S forms a cyclic chain (%S)", symbol_parsed.name, pass_name);
MemoryZeroStruct(&result);
is_resolved = 0;
break;
}
// unpack symbol
COFF_ParsedSymbol result_parsed = lnk_parsed_symbol_from_coff_symbol_idx(result.obj, result.symbol_idx);
COFF_SymbolValueInterpType result_interp = coff_interp_from_parsed_symbol(result_parsed);
// resolve symbol
LNK_ObjSymbolRef next_ref = {0};
if (lnk_resolve_symbol(symtab, result, &next_ref)) {
keep_walking = (result_interp == COFF_SymbolValueInterp_Weak || result_interp == COFF_SymbolValueInterp_Undefined);
result = next_ref;
} else {
keep_walking = 0;
}
} while (keep_walking);
if (resolved_symbol_out) {
*resolved_symbol_out = result;
}
temp_end(temp);
return is_resolved;
}
internal internal
THREAD_POOL_TASK_FUNC(lnk_walk_relocs_and_mark_ref_sections_task) THREAD_POOL_TASK_FUNC(lnk_opt_ref_task)
{ {
ProfBeginFunction(); ProfBeginFunction();
Temp scratch = scratch_begin(0,0); Temp scratch = scratch_begin(0,0);
Temp scratch2 = scratch_begin(&scratch.arena, 1); Temp scratch2 = scratch_begin(&scratch.arena, 1);
LNK_OptRefTask *task = raw_task; LNK_OptTask *task = raw_task;
LNK_SymbolTable *symtab = task->symtab; LNK_SymbolTable *symtab = task->symtab;
LNK_Config *config = task->config; LNK_Config *config = task->config;
LNK_ObjList objs = task->objs; LNK_Obj **objs = task->objs;
U64 objs_count = task->objs_count;
U8 **is_live = 0; U8 **is_live = 0;
U64 *active_thread_count = 0; U64 *active_thread_count = 0;
LNK_RelocRefsBatchList *global_batch_list = 0; LNK_RelocRefsBatchList *global_batch_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);
global_batch_list = push_array(scratch.arena, LNK_RelocRefsBatchList, 1); global_batch_list = push_array(scratch.arena, LNK_RelocRefsBatchList, 1);
// alloc live flags and set live status on every non-COMDAT section // alloc live flags and set live status on every non-COMDAT section
is_live = push_array_no_zero(scratch.arena, U8 *, objs.count); is_live = push_array_no_zero(scratch.arena, U8 *, objs_count);
{ {
U64 obj_idx = 0; for EachIndex(obj_idx, objs_count) {
for EachNode(n, LNK_ObjNode, task->objs.first) { LNK_Obj *obj = objs[obj_idx];
is_live[obj_idx] = push_array(scratch.arena, U8, n->data.header.section_count_no_null + 1);
for EachIndex(sect_idx, n->data.header.section_count_no_null) { is_live[obj_idx] = push_array(scratch.arena, U8, obj->header.section_count_no_null + 1);
is_live[obj_idx][sect_idx + 1] = !(n->data.section_flags[sect_idx] & COFF_SectionFlag_LnkCOMDAT);
for EachIndex(sect_idx, obj->header.section_count_no_null) {
is_live[obj_idx][sect_idx + 1] = !(obj->section_flags[sect_idx] & COFF_SectionFlag_LnkCOMDAT);
} }
obj_idx += 1;
} }
} }
@@ -2561,8 +2664,9 @@ THREAD_POOL_TASK_FUNC(lnk_walk_relocs_and_mark_ref_sections_task)
} }
// push task for every non-COMDAT section // push task for every non-COMDAT section
for EachNode(obj_n, LNK_ObjNode, objs.first) { for EachIndex(obj_idx, objs_count) {
LNK_Obj *obj = &obj_n->data; LNK_Obj *obj = objs[obj_idx];
for EachIndex(sect_idx, obj->header.section_count_no_null) { for EachIndex(sect_idx, obj->header.section_count_no_null) {
U32 section_number = sect_idx+1; U32 section_number = sect_idx+1;
COFF_SectionFlags section_flags = obj->section_flags[sect_idx]; COFF_SectionFlags section_flags = obj->section_flags[sect_idx];
@@ -2610,37 +2714,7 @@ THREAD_POOL_TASK_FUNC(lnk_walk_relocs_and_mark_ref_sections_task)
// reloc -> symbol // reloc -> symbol
LNK_ObjSymbolRef ref_symbol = (LNK_ObjSymbolRef){ .obj = batch->v[i].obj, .symbol_idx = reloc->isymbol }; LNK_ObjSymbolRef ref_symbol = (LNK_ObjSymbolRef){ .obj = batch->v[i].obj, .symbol_idx = reloc->isymbol };
{ lnk_resolve_reloc_target_symbol(scratch2.arena, symtab, ref_symbol, str8_lit("/OPT:REF"), &ref_symbol);
Temp temp = temp_begin(scratch2.arena);
HashMap seen_hm = {0};
B32 keep_walking = 1;
do {
// detect cyclic chains
U64 symbol_key = ((U64)ref_symbol.obj->input_idx << 32ull) | (U64)ref_symbol.symbol_idx;
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(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;
}
// unpack symbol
COFF_ParsedSymbol ref_parsed = lnk_parsed_symbol_from_coff_symbol_idx(ref_symbol.obj, ref_symbol.symbol_idx);
COFF_SymbolValueInterpType ref_interp = coff_interp_from_parsed_symbol(ref_parsed);
// resolve symbol
LNK_ObjSymbolRef next_ref = {0};
if (lnk_resolve_symbol(symtab, ref_symbol, &next_ref)) {
keep_walking = (ref_interp == COFF_SymbolValueInterp_Weak || ref_interp == COFF_SymbolValueInterp_Undefined);
ref_symbol = next_ref;
} else {
keep_walking = 0;
}
} while (keep_walking);
temp_end(temp);
}
// skip unresolved symbol // skip unresolved symbol
if (ref_symbol.obj == 0) { continue; } if (ref_symbol.obj == 0) { continue; }
@@ -2674,7 +2748,7 @@ THREAD_POOL_TASK_FUNC(lnk_walk_relocs_and_mark_ref_sections_task)
SLLStackPush(stack, stack_n); SLLStackPush(stack, stack_n);
} }
COFF_SectionFlags section_flags = ref_symbol.obj->section_flags[section_number-1]; COFF_SectionFlags section_flags = ref_symbol.obj->section_flags[section_number-1];
// on first section visit, set live flag and enqueue section // 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);
@@ -2740,8 +2814,8 @@ THREAD_POOL_TASK_FUNC(lnk_walk_relocs_and_mark_ref_sections_task)
if (task_id == 0) { if (task_id == 0) {
ProfBegin("Remove Unreachable Sections"); ProfBegin("Remove Unreachable Sections");
for EachNode(obj_n, LNK_ObjNode, objs.first) { for EachIndex(obj_idx, objs_count) {
LNK_Obj *obj = &obj_n->data; LNK_Obj *obj = objs[obj_idx];
for EachIndex(sect_idx, obj->header.section_count_no_null) { for EachIndex(sect_idx, obj->header.section_count_no_null) {
U32 section_number = sect_idx+1; U32 section_number = sect_idx+1;
COFF_SectionHeader *section_header = lnk_coff_section_header_from_section_number(obj, section_number); COFF_SectionHeader *section_header = lnk_coff_section_header_from_section_number(obj, section_number);
@@ -2756,8 +2830,8 @@ THREAD_POOL_TASK_FUNC(lnk_walk_relocs_and_mark_ref_sections_task)
enum { Stat_Null, Stat_Code, Stat_Data, Stat_Debug, Stat_Count }; enum { Stat_Null, Stat_Code, Stat_Data, Stat_Debug, Stat_Count };
Stat stats[Stat_Count] = {0}; Stat stats[Stat_Count] = {0};
for EachNode(obj_n, LNK_ObjNode, objs.first) { for EachIndex(obj_idx, objs_count) {
LNK_Obj *obj = &obj_n->data; LNK_Obj *obj = objs[obj_idx];
for EachIndex(sect_idx, obj->header.section_count_no_null) { for EachIndex(sect_idx, obj->header.section_count_no_null) {
U32 section_number = sect_idx+1; U32 section_number = sect_idx+1;
@@ -2813,14 +2887,503 @@ THREAD_POOL_TASK_FUNC(lnk_walk_relocs_and_mark_ref_sections_task)
} }
internal void internal void
lnk_opt_ref(TP_Context *tp, LNK_SymbolTable *symtab, LNK_Config *config, LNK_ObjList objs) lnk_opt_ref(TP_Context *tp, LNK_SymbolTable *symtab, LNK_Config *config, LNK_Obj **objs, U64 objs_count)
{ {
ProfScope("Mark Live Sections") Temp scratch = scratch_begin(0,0);
tp_for_parallel(tp, U32Array *obj_indices = lnk_obj_indices_from_section_counts(scratch.arena, tp->worker_count, objs, objs_count);
0,
tp->worker_count, ProfScope("/OPT:REF")
lnk_walk_relocs_and_mark_ref_sections_task, {
&(LNK_OptRefTask){ .symtab = symtab, .config = config, .objs = objs }); LNK_OptTask task = { .symtab = symtab, .config = config, .objs = objs, .objs_count = objs_count, .obj_indices = obj_indices };
tp_for_parallel(tp, 0, tp->worker_count, lnk_opt_ref_task, &task);
}
scratch_end(scratch);
}
typedef enum LNK_ICF_ColorSpace
{
LNK_ICF_ColorSpace_Null,
LNK_ICF_ColorSpace_Code,
LNK_ICF_ColorSpace_Unwind,
LNK_ICF_ColorSpace_VFTable,
LNK_ICF_ColorSpace_COUNT,
} LNK_ICF_ColorSpace;
internal String8
lnk_string_from_icf_color_space(LNK_ICF_ColorSpace color_space)
{
String8 result = str8_lit("Unknown");
switch (color_space) {
case LNK_ICF_ColorSpace_Null: { result = str8_lit("Null"); } break;
case LNK_ICF_ColorSpace_Code: { result = str8_lit("Code"); } break;
case LNK_ICF_ColorSpace_Unwind: { result = str8_lit("Unwind"); } break;
case LNK_ICF_ColorSpace_VFTable: { result = str8_lit("VFTables"); } break;
case LNK_ICF_ColorSpace_COUNT: { result = str8_lit("Unknown"); } break;
}
return result;
}
internal LNK_ICF_ColorSpace
lnk_icf_color_space_from_section(LNK_Obj *obj, U32 sect_idx)
{
LNK_ICF_ColorSpace result = LNK_ICF_ColorSpace_Null;
// fold read-only COMDAT sections
COFF_SectionFlags expected_flags = COFF_SectionFlag_LnkCOMDAT | COFF_SectionFlag_MemRead;
COFF_SectionFlags exclude_flags = COFF_SectionFlag_LnkRemove | COFF_SectionFlag_MemWrite | LNK_SECTION_FLAG_NOICF;
if ((obj->section_flags[sect_idx] & expected_flags) == expected_flags && (obj->section_flags[sect_idx] & exclude_flags) == 0) {
// fold code
if (obj->section_flags[sect_idx] & COFF_SectionFlag_CntCode) {
result = LNK_ICF_ColorSpace_Code;
}
// fold data
else if (obj->section_flags[sect_idx] & COFF_SectionFlag_CntInitializedData) {
COFF_SectionHeader *section_header = lnk_coff_section_header_from_section_number(obj, sect_idx + 1);
String8 section_name = str8_cstring_capped(section_header->name, section_header->name + sizeof(section_header->name));
// fold unwind info
if (str8_match(section_name, str8_lit(".xdata"), 0) || str8_match(section_name, str8_lit(".pdata"), 0)) {
result = LNK_ICF_ColorSpace_Unwind;
} else {
// fold MSVC vftables separately from other read-only data
LNK_ObjSymbolRef symlink_ref = {0};
if (lnk_obj_get_comdat_symlink(obj, sect_idx + 1, &symlink_ref)) {
COFF_ParsedSymbol symlink_symbol = lnk_parsed_symbol_from_coff_symbol_idx(symlink_ref.obj, symlink_ref.symbol_idx);
if (str8_starts_with(symlink_symbol.name, str8_lit("??_7"))) {
result = LNK_ICF_ColorSpace_VFTable;
}
}
}
}
}
return result;
}
internal
THREAD_POOL_TASK_FUNC(lnk_opt_icf_task)
{
ProfBeginFunction();
Temp scratch = scratch_begin(&arena,1);
//
// OPT uses a color-refinement algorithm for folding duplicate sections.
// If a color group contains multiple distinct hashes, the group is split
// and a new refinement pass is run. By default, the algorithm loops until
// partitions stabilize. Equivalence is established by comparing cryptographic
// 128-bit hashes; in theory, the chance of collisions are near the birthday
// paradox with BLAKE3 and the other downside it is susceptible to adversarial
// inputs.
//
LNK_OptTask *task = raw_task;
LNK_Obj **objs = task->objs;
if (task_id == 0) {
lnk_log(LNK_Log_Debug, "/OPT:ICF:");
}
if (task->config->llvm_addrsig == LNK_SwitchState_Yes) {
// .llvm_addrsig is an array of ULEB128 symbol indices, which mark sections
// whose addresses are significant
for EachIndex(i, task->obj_indices[task_id].count) {
U64 obj_idx = task->obj_indices[task_id].v[i];
LNK_Obj *obj = task->objs[obj_idx];
if (obj->llvm_addrsig_sect_idx >= obj->header.section_count_no_null) { continue; }
String8 symbol_table = lnk_coff_symbol_table_from_obj(obj);
String8 string_table = lnk_coff_string_table_from_obj(obj);
COFF_SectionHeader *section_header = lnk_coff_section_header_from_section_number(obj, obj->llvm_addrsig_sect_idx + 1);
String8 section_data = str8_substr(obj->data, r1u64s(section_header->foff, section_header->fsize));
// parse symbol indices and mark selected sections with NOICF flag
for (U64 off = 0; off < section_data.size;) {
U64 symbol_off = off;
U64 symbol_idx = 0;
off += str8_deserial_read_uleb128(section_data, off, &symbol_idx);
if (symbol_off == off) { break; }
if (symbol_idx < obj->header.symbol_count) {
LNK_ObjSymbolRef target_ref = { .obj = obj, .symbol_idx = symbol_idx };
B32 is_symbol_found = lnk_resolve_reloc_target_symbol(scratch.arena, task->symtab, target_ref, str8_lit("/OPT:ICF"), &target_ref);
if (is_symbol_found) {
COFF_ParsedSymbol symbol = coff_parse_symbol(target_ref.obj->header, string_table, symbol_table, target_ref.symbol_idx);
if (coff_interp_from_parsed_symbol(symbol) == COFF_SymbolValueInterp_Regular) {
obj->section_flags[symbol.section_number - 1] |= LNK_SECTION_FLAG_NOICF;
} else {
lnk_error_obj(LNK_Error_IllData, obj, ".llvm_addrsig: skip symbol 0x%x at offset 0x%x; symbol index must address a section-based symbol\n", symbol_idx, symbol_off);
}
}
} else {
lnk_error_obj(LNK_Error_IllData, obj, ".llvm_addrsig: contains out of bounds symbol index 0x%x at offset 0x%x\n", symbol_idx, symbol_off);
}
}
}
barrier_wait(tp->barrier);
}
//
// step 1: fill out color map and contributions
//
// alloc total section counter
U64 *contrib_counts = 0;
if (task_id == 0) {
contrib_counts = push_array(scratch.arena, U64, task->objs_count);
}
tp_broadcast(&contrib_counts);
// count contributions
for EachIndex(i, task->obj_indices[task_id].count) {
U64 obj_idx = task->obj_indices[task_id].v[i];
LNK_Obj *obj = objs[obj_idx];
for EachIndex(sect_idx, obj->header.section_count_no_null) {
if (lnk_icf_color_space_from_section(obj, sect_idx)) {
contrib_counts[obj_idx] += 1;
}
}
}
barrier_wait(tp->barrier);
typedef struct {
struct {
U128 hash;
U64 old_color;
} key;
U32 obj_idx;
U32 sect_idx;
} Contrib;
U64 contrib_count = sum_array_u64(task->objs_count, contrib_counts);
U64 *contrib_offsets = 0;
U64 **color_map = 0;
Contrib *contribs = 0;
U32 *is_part_stable = 0;
U64 *next_color = 0;
if (task_id == 0) {
contrib_offsets = offsets_from_counts_array_u64(scratch.arena, contrib_counts, task->objs_count);
color_map = push_array(scratch.arena, U64 *, task->objs_count);
for EachIndex(obj_idx, task->objs_count) {
LNK_Obj *obj = objs[obj_idx];
color_map[obj_idx] = push_array(scratch.arena, U64, obj->header.section_count_no_null);
}
contribs = push_array(scratch.arena, Contrib, contrib_count);
is_part_stable = push_array(scratch.arena, U32, 1);
next_color = push_array(scratch.arena, U64, 1);
*next_color = LNK_ICF_ColorSpace_COUNT;
lnk_log(LNK_Log_Debug, " Contrib count: %S", str8_from_count(scratch.arena, contrib_count));
}
tp_broadcast(&contrib_offsets);
tp_broadcast(&color_map);
tp_broadcast(&contribs);
tp_broadcast(&is_part_stable);
tp_broadcast(&next_color);
for EachIndex(i, task->obj_indices[task_id].count) {
U64 obj_idx = task->obj_indices[task_id].v[i];
LNK_Obj *obj = objs[obj_idx];
U64 cursor = 0;
for EachIndex(sect_idx, obj->header.section_count_no_null) {
LNK_ICF_ColorSpace color_space = lnk_icf_color_space_from_section(obj, sect_idx);
if (color_space == LNK_ICF_ColorSpace_Null) {
// assign unique color to sections that are not foldable
color_map[obj_idx][sect_idx] = ins_atomic_u64_inc_eval(next_color);
}
else {
// compute contribution index
U64 contrib_idx = contrib_offsets[obj_idx] + cursor++;
// seed foldable sections with a content-derived color
COFF_SectionHeader *section_header = lnk_coff_section_header_from_section_number(obj, sect_idx + 1);
String8 section_data = str8_substr(obj->data, r1u64s(section_header->foff, section_header->fsize));
XXH3_state_t hasher;
XXH3_64bits_reset(&hasher);
XXH3_64bits_update(&hasher, &color_space, sizeof(color_space));
XXH3_64bits_update(&hasher, section_data.str, section_data.size);
// fill out contribution
contribs[contrib_idx] = (Contrib){
.obj_idx = safe_cast_u32(obj->input_idx),
.sect_idx = safe_cast_u32(sect_idx),
};
// assign content-derived starting color value
color_map[obj_idx][sect_idx] = XXH3_64bits_digest(&hasher) | (1ull << 63);
}
}
}
barrier_wait(tp->barrier);
//
// step 2: refine equivalence classes
//
U64 iter_count = 0;
for (;; iter_count += 1) {
barrier_wait(tp->barrier);
// reset color status tracker
if (task_id == 0) {
*is_part_stable = 1;
}
barrier_wait(tp->barrier);
// compute colored hashes
for EachIndex(i, task->obj_indices[task_id].count) {
U64 obj_idx = task->obj_indices[task_id].v[i];
LNK_Obj *obj = objs[obj_idx];
COFF_SectionHeader *section_table = lnk_coff_section_table_from_obj(obj);
U64 contrib_cursor = 0;
for EachIndex(sect_idx, obj->header.section_count_no_null) {
LNK_ICF_ColorSpace color_space = lnk_icf_color_space_from_section(obj, sect_idx);
if (color_space == LNK_ICF_ColorSpace_Null) { continue; }
// parse relocations
COFF_SectionHeader *section_header = section_table + sect_idx;
COFF_RelocArray relocs = lnk_coff_relocs_from_section_header(obj, section_header);
U64 contrib_idx = contrib_offsets[obj_idx] + contrib_cursor;
contrib_cursor += 1;
contribs[contrib_idx].key.old_color = color_map[obj_idx][sect_idx];
// parse section data
String8 section_data = str8_substr(obj->data, r1u64s(section_header->foff, section_header->fsize));
blake3_hasher hasher; blake3_hasher_init(&hasher);
// mix section non-recursive properties
blake3_hasher_update(&hasher, section_data.str, section_data.size);
blake3_hasher_update(&hasher, &relocs.count, sizeof(relocs.count));
blake3_hasher_update(&hasher, &color_space, sizeof(color_space));
for EachIndex(reloc_idx, relocs.count) {
COFF_Reloc *r = &relocs.v[reloc_idx];
// resolve symbol referenced by the relocation
LNK_ObjSymbolRef target_ref = { .obj = obj, .symbol_idx = r->isymbol };
B32 is_symbol_found = lnk_resolve_reloc_target_symbol(scratch.arena, task->symtab, target_ref, str8_lit("/OPT:ICF"), &target_ref);
COFF_SymbolValueInterpType target_interp;
U64 target_id;
U32 target_value;
if (is_symbol_found) {
// parse COFF symbol and interpret the target symbol kind
COFF_ParsedSymbol target_symbol = lnk_parsed_symbol_from_coff_symbol_idx(target_ref.obj, target_ref.symbol_idx);
target_interp = coff_interp_from_parsed_symbol(target_symbol);
switch (target_interp) {
case COFF_SymbolValueInterp_Regular: {
// color relocation with the referenced section
target_id = color_map[target_ref.obj->input_idx][target_symbol.section_number - 1];
target_value = target_symbol.value;
} break;
default: {
// color relocation with unique symbol ref
target_id = Compose64Bit(target_ref.obj->input_idx, target_ref.symbol_idx);
target_value = target_symbol.value;
} break;
}
} else {
// color relocation with unique symbol ref
target_interp = max_U32;
target_id = Compose64Bit(obj_idx, r->isymbol);
target_value = 0;
}
// mix relocation properties
blake3_hasher_update(&hasher, &r->apply_off, sizeof(r->apply_off));
blake3_hasher_update(&hasher, &r->type, sizeof(r->type));
blake3_hasher_update(&hasher, &target_interp, sizeof(target_interp));
blake3_hasher_update(&hasher, &target_id, sizeof(target_id));
blake3_hasher_update(&hasher, &target_value, sizeof(target_value));
}
// finalize section hash
U128 hash;
blake3_hasher_finalize(&hasher, (U8*)&hash, sizeof(hash));
// update contribution hash and color
contribs[contrib_idx].key.hash = hash;
}
}
barrier_wait(tp->barrier);
// group sections by (color, hash) and handle color splits
if (task_id == 0) {
Temp temp = temp_begin(scratch.arena);
HashMap new_color_hm = {0};
HashMap old_color_hm = {0};
U64 start_next_color = *next_color;
for EachIndex(contrib_idx, contrib_count) {
Contrib *contrib = &contribs[contrib_idx];
U64 *new_color_ptr = hash_map_search_string_u64(&new_color_hm, str8_struct(&contrib->key));
if (new_color_ptr == 0) {
U64 color;
if (hash_map_search_u64_u64(&old_color_hm, contrib->key.old_color) == 0) {
color = contrib->key.old_color;
hash_map_push_u64_u64(temp.arena, &old_color_hm, contrib->key.old_color, color);
} else {
// generate a new color for the hash
color = ++*next_color;
*is_part_stable = 0;
}
new_color_ptr = &hash_map_push_string_u64(temp.arena, &new_color_hm, str8_struct(&contrib->key), color)->v.value.value_u64;
}
color_map[contrib->obj_idx][contrib->sect_idx] = *new_color_ptr;
}
U64 split_count = *next_color - start_next_color;
lnk_log(LNK_Log_Debug, " Pass %llu found %S splits", iter_count, str8_from_count(scratch.arena, split_count));
temp_end(temp);
}
barrier_wait(tp->barrier);
// stop iterating when partitions stabilize
if (*is_part_stable) { break; }
}
barrier_wait(tp->barrier);
//
// step 3: flag folded sections for removal
//
if (task_id == 0) {
HashMap leader_hm = { 0 };
for EachIndex(contrib_idx, contrib_count) {
Contrib *contrib = &contribs[contrib_idx];
U64 color = color_map[contrib->obj_idx][contrib->sect_idx];
if (hash_map_search_u64_raw(&leader_hm, color) == 0) {
hash_map_push_u64_raw(scratch.arena, &leader_hm, color, contrib);
}
}
typedef struct { U64 count; U64 size; } FoldStats;
FoldStats fold_stats[LNK_ICF_ColorSpace_COUNT] = {0};
for EachIndex(contrib_idx, contrib_count) {
Contrib *contrib = &contribs[contrib_idx];
Contrib *leader = hash_map_search_u64_raw(&leader_hm, color_map[contrib->obj_idx][contrib->sect_idx]);
if (leader == 0 || leader == contrib) { continue; }
LNK_Obj *contrib_obj = objs[contrib->obj_idx];
LNK_Obj *leader_obj = objs[leader->obj_idx];
// update fold stats
if (lnk_get_log_status(LNK_Log_Debug)) {
COFF_SectionHeader *section_header = lnk_coff_section_header_from_section_number(contrib_obj, contrib->sect_idx + 1);
LNK_ICF_ColorSpace color_space = lnk_icf_color_space_from_section(contrib_obj, contrib->sect_idx);
fold_stats[color_space].count += 1;
fold_stats[color_space].size += section_header->fsize;
}
// update leader with largest alignment
U64 leader_align = coff_align_size_from_section_flags(leader_obj->section_flags[leader->sect_idx]);
U64 contrib_align = coff_align_size_from_section_flags(contrib_obj->section_flags[contrib->sect_idx]);
if (leader_align < contrib_align) {
leader_obj->section_flags[leader->sect_idx] &= ~(COFF_SectionFlag_AlignMask << COFF_SectionFlag_AlignShift);
leader_obj->section_flags[leader->sect_idx] |= coff_section_flag_from_align_size(contrib_align);
}
// update COMDAT leader symbol link
Assert(leader_obj->comdats[leader->sect_idx] != max_U32);
LNK_ObjSymbolRef leader_symlink = { leader_obj, leader_obj->comdats[leader->sect_idx] };
contrib_obj->symlinks[contrib->sect_idx + 1] = leader_symlink;
// discard folded section
contrib_obj->section_flags[contrib->sect_idx] |= COFF_SectionFlag_LnkRemove;
#if LNK_PARANOID
String8 section_name = lnk_obj_section_name_from_section_number(contrib_obj, contrib->sect_idx+1);
String8 leader_name = lnk_obj_section_name_from_section_number(leader_obj, leader->sect_idx+1);
lnk_log(LNK_Log_Debug, "fold %.*s[SECT%X \"%.*s\"] ==> %.*s[SECT%X \"%.*s\"]", str8_varg(lnk_loc_from_obj(scratch.arena, contrib_obj)), contrib->sect_idx+1, str8_varg(section_name), str8_varg(lnk_loc_from_obj(scratch.arena, leader_obj)), leader->sect_idx+1, str8_varg(leader_name));
#endif
}
if (lnk_get_log_status(LNK_Log_Debug)) {
U64 total_count = 0, total_size = 0;
for (LNK_ICF_ColorSpace color_space = (LNK_ICF_ColorSpace)(LNK_ICF_ColorSpace_Null + 1); color_space < LNK_ICF_ColorSpace_COUNT; color_space += 1) {
lnk_log(LNK_Log_Debug, " %-8S: %M, %.*s sections", lnk_string_from_icf_color_space(color_space), fold_stats[color_space].size, str8_varg(str8_from_count(scratch.arena, fold_stats[color_space].count)));
total_count += fold_stats[color_space].count;
total_size += fold_stats[color_space].size;
}
lnk_log(LNK_Log_Debug, " %-8s: %M, %.*s sections", "Total", total_size, str8_varg(str8_from_count(scratch.arena, total_count)));
}
}
barrier_wait(tp->barrier);
//
// step 4: flatten COMDAT symlink chains so subsequent passes can assume symlinks are single hop
//
for EachIndex(i, task->obj_indices[task_id].count) {
U64 obj_idx = task->obj_indices[task_id].v[i];
LNK_Obj *obj = objs[obj_idx];
for EachIndex(sect_idx, obj->header.section_count_no_null) {
U64 section_number = sect_idx + 1;
LNK_ObjSymbolRef symlink_ref = {0};
if (!lnk_obj_get_comdat_symlink(obj, section_number, &symlink_ref)) { continue; }
{
Temp temp = temp_begin(scratch.arena);
HashMap seen_hm = {0};
U64 hop_count = 0;
U64 hop_cap = 1024;
for(; hop_count < hop_cap; hop_count += 1) {
COFF_ParsedSymbol symlink_parsed = lnk_parsed_symbol_from_coff_symbol_idx(symlink_ref.obj, symlink_ref.symbol_idx);
LNK_ObjSymbolRef next_symlink_ref = {0};
if (!lnk_obj_get_comdat_symlink(symlink_ref.obj, symlink_parsed.section_number, &next_symlink_ref)) { break; }
if (MemoryMatchStruct(&next_symlink_ref, &symlink_ref)) { break; }
if (hash_map_search_string_u64(&seen_hm, str8_struct(&next_symlink_ref)) != 0) {
lnk_error_obj(LNK_Error_IllData, obj, "recursive COMDAT symlink in SECT%X", section_number);
MemoryZeroStruct(&symlink_ref);
break;
}
symlink_ref = next_symlink_ref;
hash_map_push_string_u64(temp.arena, &seen_hm, str8_copy(temp.arena, str8_struct(&symlink_ref)), 1);
}
if (hop_count >= hop_cap) {
lnk_error_obj(LNK_Error_IllData, obj, "failed to flatten symlink for SECT%X; max number of hops reached", section_number);
MemoryZeroStruct(&symlink_ref);
}
temp_end(temp);
}
obj->symlinks[section_number] = symlink_ref;
}
}
barrier_wait(tp->barrier);
scratch_end(scratch);
ProfEnd();
}
internal void
lnk_opt_icf(TP_Context *tp, LNK_SymbolTable *symtab, LNK_Config *config, LNK_Obj **objs, U64 objs_count)
{
Temp scratch = scratch_begin(0,0);
U32Array *obj_indices = lnk_obj_indices_from_section_counts(scratch.arena, tp->worker_count, objs, objs_count);
ProfScope("/OPT:ICF")
{
LNK_OptTask task = { .symtab = symtab, .config = config, .objs = objs, .objs_count = objs_count, .obj_indices = obj_indices };
tp_for_parallel(tp, 0, tp->worker_count, lnk_opt_icf_task, &task);
}
scratch_end(scratch);
} }
internal internal
@@ -2935,11 +3498,10 @@ THREAD_POOL_TASK_FUNC(lnk_set_comdat_leaders_contribs_task)
if (~obj->section_flags[sect_idx] & COFF_SectionFlag_LnkCOMDAT) { continue; } if (~obj->section_flags[sect_idx] & COFF_SectionFlag_LnkCOMDAT) { continue; }
LNK_Symbol *symlink = lnk_obj_get_comdat_symlink(obj, section_number); LNK_ObjSymbolRef symlink_ref = {0};
if (symlink == 0) { continue; } if ( ! lnk_obj_get_comdat_symlink(obj, section_number, &symlink_ref)) { continue; }
COFF_ParsedSymbol symlink_parsed = lnk_parsed_from_symbol(symlink); COFF_ParsedSymbol symlink_parsed = lnk_parsed_symbol_from_coff_symbol_idx(symlink_ref.obj, symlink_ref.symbol_idx);
LNK_ObjSymbolRef symlink_ref = lnk_ref_from_symbol(symlink);
task->sect_map[obj_idx][sect_idx] = task->sect_map[symlink_ref.obj->input_idx][symlink_parsed.section_number - 1]; task->sect_map[obj_idx][sect_idx] = task->sect_map[symlink_ref.obj->input_idx][symlink_parsed.section_number - 1];
} }
ProfEnd(); ProfEnd();
@@ -2981,35 +3543,30 @@ THREAD_POOL_TASK_FUNC(lnk_patch_comdat_leaders_task)
symbol = lnk_parsed_symbol_from_coff_symbol_idx(obj, symbol_idx); symbol = lnk_parsed_symbol_from_coff_symbol_idx(obj, symbol_idx);
COFF_SymbolValueInterpType interp = coff_interp_symbol(symbol.section_number, symbol.value, symbol.storage_class); COFF_SymbolValueInterpType interp = coff_interp_symbol(symbol.section_number, symbol.value, symbol.storage_class);
if (interp == COFF_SymbolValueInterp_Regular) { if (interp != COFF_SymbolValueInterp_Regular) { continue; }
LNK_Symbol *symlink = lnk_obj_get_comdat_symlink(obj, symbol.section_number);
if (symlink) {
LNK_ObjSymbolRef symlink_ref = lnk_ref_from_symbol(symlink);
if (symlink_ref.obj != obj) {
U32 section_number;
U32 value;
if (symbol.storage_class == COFF_SymStorageClass_External) {
// COMDAT leader may be at a different offset, so update this symbol with leader's offset
COFF_ParsedSymbol parsed_symlink = lnk_parsed_from_symbol(symlink);
section_number = symbol.section_number;
value = parsed_symlink.value;
} else {
// COMDAT section may have static symbols which are now invalid to relocate against
section_number = lnk_obj_get_removed_section_number(obj);
value = max_U32;
task->u.patch_symtabs.was_symbol_patched[obj_idx][symbol_idx] = 1;
}
if (obj->header.is_big_obj) { LNK_ObjSymbolRef symlink_ref = {0};
COFF_Symbol32 *symbol32 = symbol.raw_symbol; if ( ! lnk_obj_get_comdat_symlink(obj, symbol.section_number, &symlink_ref)) { continue; }
symbol32->section_number = section_number;
symbol32->value = value; COFF_ParsedSymbol parsed_symlink = lnk_parsed_symbol_from_coff_symbol_idx(symlink_ref.obj, symlink_ref.symbol_idx);
} else { if (symlink_ref.obj == obj && parsed_symlink.section_number == symbol.section_number) { continue; }
COFF_Symbol16 *symbol16 = symbol.raw_symbol;
symbol16->section_number = (U16)section_number; B32 is_static_comdat_leader = symbol.storage_class == COFF_SymStorageClass_Static &&
symbol16->value = value; obj->comdats[symbol.section_number-1] == symbol_idx;
}
} if (symbol.storage_class == COFF_SymStorageClass_External || is_static_comdat_leader) {
// COMDAT leader may be at a different offset, so update this symbol with leader's offset
U32 section_number = symbol.section_number;
U32 value = parsed_symlink.value;
if (obj->header.is_big_obj) {
COFF_Symbol32 *symbol32 = symbol.raw_symbol;
symbol32->section_number = section_number;
symbol32->value = value;
} else {
COFF_Symbol16 *symbol16 = symbol.raw_symbol;
symbol16->section_number = (U16)section_number;
symbol16->value = value;
} }
} }
} }
+9 -5
View File
@@ -74,6 +74,7 @@ typedef struct LNK_Inputer
#define LNK_NULL_SYMBOL "*** RAD_NULL_SYMBOL ***" #define LNK_NULL_SYMBOL "*** RAD_NULL_SYMBOL ***"
#define LNK_SECTION_FLAG_DEBUG (1 << 0) #define LNK_SECTION_FLAG_DEBUG (1 << 0)
#define LNK_SECTION_FLAG_NOICF (1 << 1)
typedef U8 LNK_LibMemberFlags; typedef U8 LNK_LibMemberFlags;
enum enum
@@ -215,10 +216,12 @@ typedef struct
typedef struct typedef struct
{ {
LNK_SymbolTable *symtab; LNK_SymbolTable *symtab;
LNK_Config *config; LNK_Config *config;
LNK_ObjList objs; LNK_Obj **objs;
} LNK_OptRefTask; U64 objs_count;
U32Array *obj_indices;
} LNK_OptTask;
typedef struct typedef struct
{ {
@@ -390,7 +393,8 @@ internal LNK_LinkResult lnk_link_image (TP_Context *tp, TP_Arena *arena, LNK_Con
// --- Optimizations ----------------------------------------------------------- // --- Optimizations -----------------------------------------------------------
internal void lnk_opt_ref(TP_Context *tp, LNK_SymbolTable *symtab, LNK_Config *config, LNK_ObjList objs); internal void lnk_opt_ref(TP_Context *tp, LNK_SymbolTable *symtab, LNK_Config *config, LNK_Obj **objs, U64 objs_count);
internal void lnk_opt_icf(TP_Context *tp, LNK_SymbolTable *symtab, LNK_Config *config, LNK_Obj **objs, U64 objs_count);
// --- Win32 Image ------------------------------------------------------------- // --- Win32 Image -------------------------------------------------------------
+8
View File
@@ -24,6 +24,7 @@ global read_only LNK_CmdSwitch g_cmd_switch_map[] =
{ LNK_CmdSwitch_FailIfMismatch, 1, "FAILIFMISMATCH", "{id=value}", "Fails to link if same ids have conflicting values." }, { LNK_CmdSwitch_FailIfMismatch, 1, "FAILIFMISMATCH", "{id=value}", "Fails to link if same ids have conflicting values." },
{ LNK_CmdSwitch_FileAlign, 0, "FILEALIGN", ":#", "Set section alignment in the file." }, { LNK_CmdSwitch_FileAlign, 0, "FILEALIGN", ":#", "Set section alignment in the file." },
{ LNK_CmdSwitch_Fixed, 0, "FIXED", "[:NO]", "Load the image at the default base address." }, { LNK_CmdSwitch_Fixed, 0, "FIXED", "[:NO]", "Load the image at the default base address." },
{ LNK_CmdSwitch_Force, 0, "FORCE", "", "Force image output despite errors." },
{ LNK_CmdSwitch_FunctionPadMin, 0, "FUNCTIONPADMIN", ":#", "Minimum function byte size." }, { LNK_CmdSwitch_FunctionPadMin, 0, "FUNCTIONPADMIN", ":#", "Minimum function byte size." },
{ LNK_CmdSwitch_Heap, 0, "HEAP", "RESERVE[,COMMIT]", "Set reserve and commit size for the heap." }, { LNK_CmdSwitch_Heap, 0, "HEAP", "RESERVE[,COMMIT]", "Set reserve and commit size for the heap." },
{ LNK_CmdSwitch_HighEntropyVa, 0, "HIGHENTROPYVA", "[:NO]", "Indicate that image supports full 64-bit address space ASLR." }, { LNK_CmdSwitch_HighEntropyVa, 0, "HIGHENTROPYVA", "[:NO]", "Indicate that image supports full 64-bit address space ASLR." },
@@ -106,6 +107,8 @@ global read_only LNK_CmdSwitch g_cmd_switch_map[] =
{ LNK_CmdSwitch_RadTypeServer, 0, "RAD_TYPE_SERVER", ":FILENAME", "Merge types and store them in the specified file. The filename must have the .rrt extension." }, { LNK_CmdSwitch_RadTypeServer, 0, "RAD_TYPE_SERVER", ":FILENAME", "Merge types and store them in the specified file. The filename must have the .rrt extension." },
{ LNK_CmdSwitch_LLVM_AddrSig, 0, "LLVM_ADDRSIG", "[:NO]", "Use .llvm_addrsig to guide ICF." },
{ LNK_CmdSwitch_Help, 0, "HELP", "", "" }, { LNK_CmdSwitch_Help, 0, "HELP", "", "" },
{ LNK_CmdSwitch_Help, 0, "?", "", "" }, { LNK_CmdSwitch_Help, 0, "?", "", "" },
}; };
@@ -2179,6 +2182,10 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List
lnk_error_cmd_switch(LNK_Error_Cmdl, obj, cmd_switch, "missing type server file path"); lnk_error_cmd_switch(LNK_Error_Cmdl, obj, cmd_switch, "missing type server file path");
} }
} break; } break;
case LNK_CmdSwitch_LLVM_AddrSig: {
lnk_cmd_switch_parse_flag(obj, cmd_switch, value_strings, &config->llvm_addrsig);
} break;
} }
scratch_end(scratch); scratch_end(scratch);
@@ -2208,6 +2215,7 @@ lnk_config_init(LNK_CmdLine cmd_line)
config->arena = arena; config->arena = arena;
config->raw_cmd_line = str8_list_copy(arena, &cmd_line.raw_cmd_line); config->raw_cmd_line = str8_list_copy(arena, &cmd_line.raw_cmd_line);
config->work_dir = get_current_path(arena); config->work_dir = get_current_path(arena);
config->force = lnk_cmd_line_has_switch(cmd_line, LNK_CmdSwitch_Force);
// apply command line switches // apply command line switches
for EachNode(cmd, LNK_CmdOption, cmd_line.first_option) { for EachNode(cmd, LNK_CmdOption, cmd_line.first_option) {
+4
View File
@@ -143,6 +143,8 @@ typedef enum
LNK_CmdSwitch_RadTypeServer, LNK_CmdSwitch_RadTypeServer,
LNK_CmdSwitch_RadTypeServer_MatchObj, LNK_CmdSwitch_RadTypeServer_MatchObj,
LNK_CmdSwitch_LLVM_AddrSig,
LNK_CmdSwitch_Help, LNK_CmdSwitch_Help,
LNK_CmdSwitch_Count LNK_CmdSwitch_Count
@@ -316,6 +318,7 @@ typedef struct LNK_Config
U64 function_pad_min; U64 function_pad_min;
U64 *manifest_resource_id; U64 *manifest_resource_id;
B32 no_default_libs; B32 no_default_libs;
B32 force;
LNK_SwitchState infer_asan_libs; LNK_SwitchState infer_asan_libs;
Version link_ver; Version link_ver;
Version os_ver; Version os_ver;
@@ -387,6 +390,7 @@ typedef struct LNK_Config
String8 type_server_name; String8 type_server_name;
LNK_SwitchState type_server; LNK_SwitchState type_server;
LNK_SwitchState sort_imports; LNK_SwitchState sort_imports;
LNK_SwitchState llvm_addrsig;
} LNK_Config; } LNK_Config;
// --- MSVC Error Codes -------------------------------------------------------- // --- MSVC Error Codes --------------------------------------------------------
+1
View File
@@ -24,6 +24,7 @@ typedef enum
LNK_Log_Count LNK_Log_Count
} LNK_LogType; } LNK_LogType;
// TODO: factor into an xlist with explicitly defined error levels and warnings
typedef enum typedef enum
{ {
LNK_Error_Null, LNK_Error_Null,
+38 -11
View File
@@ -100,7 +100,7 @@ THREAD_POOL_TASK_FUNC(lnk_obj_initer)
COFF_SectionFlags *section_flags = push_array_no_zero(arena, COFF_SectionFlags, header.section_count_no_null); COFF_SectionFlags *section_flags = push_array_no_zero(arena, COFF_SectionFlags, header.section_count_no_null);
for (U64 sect_idx = 0; sect_idx < header.section_count_no_null; sect_idx += 1) { for (U64 sect_idx = 0; sect_idx < header.section_count_no_null; sect_idx += 1) {
COFF_SectionHeader *coff_sect_header = &coff_section_table[sect_idx]; COFF_SectionHeader *coff_sect_header = &coff_section_table[sect_idx];
section_flags[sect_idx] = coff_sect_header->flags; section_flags[sect_idx] = coff_sect_header->flags & ~3; // linker reserves low 2 bits for internal flags
String8 sect_name = coff_name_from_section_header(raw_coff_string_table, coff_sect_header); String8 sect_name = coff_name_from_section_header(raw_coff_string_table, coff_sect_header);
if (~section_flags[sect_idx] & COFF_SectionFlag_CntUninitializedData) { if (~section_flags[sect_idx] & COFF_SectionFlag_CntUninitializedData) {
if (coff_sect_header->fsize > 0) { if (coff_sect_header->fsize > 0) {
@@ -344,6 +344,7 @@ THREAD_POOL_TASK_FUNC(lnk_obj_initer)
obj->debug_t_sect_idx = ~0; obj->debug_t_sect_idx = ~0;
obj->debug_p_sect_idx = ~0; obj->debug_p_sect_idx = ~0;
obj->debug_h_sect_idx = ~0; obj->debug_h_sect_idx = ~0;
obj->llvm_addrsig_sect_idx = ~0;
} }
internal internal
@@ -362,6 +363,19 @@ THREAD_POOL_TASK_FUNC(lnk_obj_find_debug_t)
} }
} }
internal
THREAD_POOL_TASK_FUNC(lnk_obj_find_llvm_addrsig)
{
LNK_Obj *obj = &((LNK_ObjNode *)raw_task)[task_id].data;
for EachIndex(sect_idx, obj->header.section_count_no_null) {
String8 section_name = lnk_obj_section_name_from_sect_idx(obj, sect_idx);
if (str8_match(section_name, str8_lit(".llvm_addrsig"), 0)) {
obj->llvm_addrsig_sect_idx = sect_idx;
break;
}
}
}
internal LNK_ObjNode * internal LNK_ObjNode *
lnk_obj_from_input_many(TP_Context *tp, TP_Arena *arena, LNK_Config *config, U64 inputs_count, LNK_Input **inputs) lnk_obj_from_input_many(TP_Context *tp, TP_Arena *arena, LNK_Config *config, U64 inputs_count, LNK_Input **inputs)
{ {
@@ -369,10 +383,12 @@ lnk_obj_from_input_many(TP_Context *tp, TP_Arena *arena, LNK_Config *config, U64
if (inputs_count) { if (inputs_count) {
objs = push_array(arena->v[0], LNK_ObjNode, inputs_count); objs = push_array(arena->v[0], LNK_ObjNode, inputs_count);
tp_for_parallel(tp, arena, inputs_count, lnk_obj_initer, &(LNK_ObjIniter){ .inputs = inputs, .objs = objs, .machine = config->machine }); tp_for_parallel(tp, arena, inputs_count, lnk_obj_initer, &(LNK_ObjIniter){ .inputs = inputs, .objs = objs, .machine = config->machine });
if (lnk_do_debug_info(config)) { if (lnk_do_debug_info(config)) {
tp_for_parallel(tp, arena, inputs_count, lnk_obj_find_debug_t, objs); tp_for_parallel(tp, arena, inputs_count, lnk_obj_find_debug_t, objs);
} }
if (config->opt_icf == LNK_SwitchState_Yes) {
tp_for_parallel(tp, arena, inputs_count, lnk_obj_find_llvm_addrsig, objs);
}
} }
return objs; return objs;
} }
@@ -451,19 +467,26 @@ THREAD_POOL_TASK_FUNC(lnk_input_coff_symbol_table)
} }
} }
internal LNK_SymbolHashTrie ** internal LNK_ObjSymbolRef *
lnk_symlinks_from_obj(Arena *arena, LNK_SymbolTable *symtab, LNK_Obj *obj) lnk_symlinks_from_obj(Arena *arena, LNK_SymbolTable *symtab, LNK_Obj *obj)
{ {
LNK_SymbolHashTrie **symlinks = push_array(arena, LNK_SymbolHashTrie *, obj->header.section_count_no_null+1); LNK_ObjSymbolRef *symlinks = push_array(arena, LNK_ObjSymbolRef, obj->header.section_count_no_null+1);
COFF_ParsedSymbol symbol; COFF_ParsedSymbol symbol;
for (U64 symbol_idx = 0; symbol_idx < obj->header.symbol_count; symbol_idx += (1 + symbol.aux_symbol_count)) { for (U64 symbol_idx = 0; symbol_idx < obj->header.symbol_count; symbol_idx += (1 + symbol.aux_symbol_count)) {
symbol = lnk_parsed_symbol_from_coff_symbol_idx(obj, symbol_idx); symbol = lnk_parsed_symbol_from_coff_symbol_idx(obj, symbol_idx);
COFF_SymbolValueInterpType interp = coff_interp_symbol(symbol.section_number, symbol.value, symbol.storage_class); COFF_SymbolValueInterpType interp = coff_interp_symbol(symbol.section_number, symbol.value, symbol.storage_class);
if (interp == COFF_SymbolValueInterp_Regular && symbol.aux_symbol_count == 0 && symbol.storage_class == COFF_SymStorageClass_External) { if (interp == COFF_SymbolValueInterp_Regular) {
LNK_ObjSection section = lnk_obj_section_from_section_number(obj, symbol.section_number); LNK_ObjSection section = lnk_obj_section_from_section_number(obj, symbol.section_number);
if (*section.flags & COFF_SectionFlag_LnkCOMDAT) { if (*section.flags & COFF_SectionFlag_LnkCOMDAT) {
if (symlinks[symbol.section_number] == 0 || symbol.value == 0) { if (symbol.aux_symbol_count == 0 && symbol.storage_class == COFF_SymStorageClass_External) {
symlinks[symbol.section_number] = lnk_symbol_table_search_(symtab, symbol.name); if (symlinks[symbol.section_number].obj == 0 || symbol.value == 0) {
LNK_SymbolHashTrie *link_symbol = lnk_symbol_table_search_(symtab, symbol.name);
if (link_symbol) {
symlinks[symbol.section_number] = lnk_ref_from_symbol(link_symbol->symbol);
}
}
} else if (symlinks[symbol.section_number].obj == 0 && symbol.storage_class == COFF_SymStorageClass_Static && symbol.aux_symbol_count > 0) {
symlinks[symbol.section_number] = (LNK_ObjSymbolRef){ obj, symbol_idx };
} }
} }
} }
@@ -543,11 +566,15 @@ lnk_obj_get_removed_section_number(LNK_Obj *obj)
return obj->header.is_big_obj ? LNK_REMOVED_SECTION_NUMBER_32 : LNK_REMOVED_SECTION_NUMBER_16; return obj->header.is_big_obj ? LNK_REMOVED_SECTION_NUMBER_32 : LNK_REMOVED_SECTION_NUMBER_16;
} }
internal LNK_Symbol * internal B32
lnk_obj_get_comdat_symlink(LNK_Obj *obj, U64 section_number) lnk_obj_get_comdat_symlink(LNK_Obj *obj, U64 section_number, LNK_ObjSymbolRef *symlink_out)
{ {
LNK_SymbolHashTrie *symlink = obj->symlinks[section_number]; LNK_ObjSymbolRef symlink = obj->symlinks[section_number];
return symlink ? symlink->symbol : 0; B32 is_valid = symlink.obj != 0;
if (is_valid && symlink_out) {
*symlink_out = symlink;
}
return is_valid;
} }
internal COFF_SectionHeader * internal COFF_SectionHeader *
+5 -2
View File
@@ -22,7 +22,7 @@ typedef struct LNK_Obj
// COMDAT // COMDAT
U32 *comdats; U32 *comdats;
U32Node **associated_sections; U32Node **associated_sections;
LNK_SymbolHashTrie **symlinks; LNK_ObjSymbolRef *symlinks;
// link // link
struct LNK_LibMemberRef *link_member; struct LNK_LibMemberRef *link_member;
@@ -33,6 +33,9 @@ typedef struct LNK_Obj
U32 debug_p_sect_idx; U32 debug_p_sect_idx;
U32 debug_h_sect_idx; U32 debug_h_sect_idx;
// ICF
U32 llvm_addrsig_sect_idx;
// @type_server // @type_server
Rng1U64 ti_range; Rng1U64 ti_range;
CV_TypeIndex *ti_map; CV_TypeIndex *ti_map;
@@ -139,7 +142,7 @@ internal U32 lnk_obj_get_vol_md(LNK_Obj *obj);
internal struct LNK_Lib * lnk_obj_get_lib(LNK_Obj *obj); internal struct LNK_Lib * lnk_obj_get_lib(LNK_Obj *obj);
internal String8 lnk_obj_get_lib_path(LNK_Obj *obj); internal String8 lnk_obj_get_lib_path(LNK_Obj *obj);
internal U32 lnk_obj_get_removed_section_number(LNK_Obj *obj); internal U32 lnk_obj_get_removed_section_number(LNK_Obj *obj);
internal LNK_Symbol * lnk_obj_get_comdat_symlink(LNK_Obj *obj, U64 section_number); internal B32 lnk_obj_get_comdat_symlink(LNK_Obj *obj, U64 section_number, LNK_ObjSymbolRef *symlink_out);
// --- Symbol & Section Helpers ------------------------------------------------ // --- Symbol & Section Helpers ------------------------------------------------
+2 -2
View File
@@ -717,8 +717,8 @@ lnk_resolve_symbol(LNK_SymbolTable *symtab, LNK_ObjSymbolRef symbol, LNK_ObjSymb
COFF_SymbolValueInterpType symbol_interp = coff_interp_symbol(symbol_parsed.section_number, symbol_parsed.value, symbol_parsed.storage_class); COFF_SymbolValueInterpType symbol_interp = coff_interp_symbol(symbol_parsed.section_number, symbol_parsed.value, symbol_parsed.storage_class);
switch (symbol_interp) { switch (symbol_interp) {
case COFF_SymbolValueInterp_Regular: { case COFF_SymbolValueInterp_Regular: {
LNK_Symbol *symlink = lnk_obj_get_comdat_symlink(symbol.obj, symbol_parsed.section_number); LNK_ObjSymbolRef symlink = {0};
*symbol_out = symlink ? lnk_ref_from_symbol(symlink) : symbol; *symbol_out = lnk_obj_get_comdat_symlink(symbol.obj, symbol_parsed.section_number, &symlink) ? symlink : symbol;
} break; } break;
case COFF_SymbolValueInterp_Weak: { case COFF_SymbolValueInterp_Weak: {
LNK_Symbol *defn = lnk_symbol_table_search(symtab, symbol_parsed.name); LNK_Symbol *defn = lnk_symbol_table_search(symtab, symbol_parsed.name);
File diff suppressed because it is too large Load Diff
+6 -6
View File
@@ -4,7 +4,7 @@
internal void internal void
tp_run_tasks(TP_Context *pool, TP_Worker *worker) tp_run_tasks(TP_Context *pool, TP_Worker *worker)
{ {
barrier_wait(pool->barrier); barrier_wait(pool->run_barrier);
for (;;) { for (;;) {
S64 task_left = ins_atomic_u64_dec_eval(&pool->task_left); S64 task_left = ins_atomic_u64_dec_eval(&pool->task_left);
@@ -19,14 +19,11 @@ tp_run_tasks(TP_Context *pool, TP_Worker *worker)
U64 task_id = pool->task_count - (task_left+1); U64 task_id = pool->task_count - (task_left+1);
pool->task_func(arena, worker->id, task_id, pool->task_data, pool); pool->task_func(arena, worker->id, task_id, pool->task_data, pool);
// cache task count so we dont touch pool memory after atomic inc
U64 task_count = pool->task_count;
// update task done count // update task done count
ins_atomic_u64_inc_eval(&pool->task_done); ins_atomic_u64_inc_eval(&pool->task_done);
} }
barrier_wait(pool->barrier); barrier_wait(pool->run_barrier);
} }
internal void internal void
@@ -74,6 +71,7 @@ tp_alloc(Arena *arena, U32 worker_count, U32 max_worker_count, String8 name)
// init pool // init pool
TP_Context *pool = push_array(arena, TP_Context, 1); TP_Context *pool = push_array(arena, TP_Context, 1);
pool->exec_semaphore = exec_semaphore; pool->exec_semaphore = exec_semaphore;
pool->run_barrier = barrier_alloc(worker_count);
pool->barrier = barrier_alloc(worker_count); pool->barrier = barrier_alloc(worker_count);
pool->is_live = 1; pool->is_live = 1;
pool->worker_count = worker_count; pool->worker_count = worker_count;
@@ -113,6 +111,7 @@ tp_release(TP_Context *pool)
if (is_shared) { if (is_shared) {
semaphore_release(pool->exec_semaphore); semaphore_release(pool->exec_semaphore);
} }
barrier_release(pool->run_barrier);
barrier_release(pool->barrier); barrier_release(pool->barrier);
MemoryZeroStruct(pool); MemoryZeroStruct(pool);
@@ -194,13 +193,14 @@ tp_for_parallel(TP_Context *pool, TP_Arena *task_arena, U64 task_count, TP_TaskF
// if we are in shared mode -> ping // if we are in shared mode -> ping
if (*pool->exec_semaphore.u64) { if (*pool->exec_semaphore.u64) {
U64 drop_count64 = Min(task_count, pool->worker_count); U64 drop_count64 = pool->worker_count - 1;
U32 drop_count = safe_cast_u32(drop_count64); U32 drop_count = safe_cast_u32(drop_count64);
semaphore_drop_count(pool->exec_semaphore, drop_count); semaphore_drop_count(pool->exec_semaphore, drop_count);
} }
// run tasks on main worker // run tasks on main worker
tp_run_tasks(pool, pool->worker_arr); tp_run_tasks(pool, pool->worker_arr);
Assert(pool->task_done == task_count);
} }
} }
+1 -1
View File
@@ -32,6 +32,7 @@ typedef struct TP_Context
Semaphore exec_semaphore; Semaphore exec_semaphore;
Semaphore task_semaphore; Semaphore task_semaphore;
Semaphore main_semaphore; Semaphore main_semaphore;
Barrier run_barrier;
Barrier barrier; Barrier barrier;
void *broadcast; void *broadcast;
U64 broadcast_size; U64 broadcast_size;
@@ -58,4 +59,3 @@ internal void tp_temp_end(TP_Temp temp);
internal void tp_for_parallel(TP_Context *pool, TP_Arena *arena, U64 task_count, TP_TaskFunc *task_func, void *task_data); internal void tp_for_parallel(TP_Context *pool, TP_Arena *arena, U64 task_count, TP_TaskFunc *task_func, void *task_data);
internal Rng1U64 * tp_divide_work(Arena *arena, U64 item_count, U32 worker_count); internal Rng1U64 * tp_divide_work(Arena *arena, U64 item_count, U32 worker_count);
#define tp_broadcast(p) tp_broadcast_(tp, task_id, p, sizeof(*p)) #define tp_broadcast(p) tp_broadcast_(tp, task_id, p, sizeof(*p))