diff --git a/CHANGELOG.md b/CHANGELOG.md index 748e1e5b..e69dd338 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +# v0.9.28-alpha + +## Linker Changes +- Implemented `/OPT:ICF` identical COMDAT folding. +- Added DEF, and SECTION configuration support. +- Improved COMDAT handling for weak symbols, ICF, and zero-sized COMDAT + anchors, and stopped emitting empty marker sections that could produce invalid + PE images. +- Fixed import/export edge cases, including import data-directory bounds and + archive member handling for `__imp_*` symbols. +- Accepted duplicate identical weak search aliases without reporting multiply + defined symbols. +- Add support for UTF-16 encoded response files. +- Fixed DBI section-contribution image-section range mapping. +- Improved debug logging for `/OPT:REF` liveness stats and unresolved symbols + referenced by `.llvm_addrsig`. + # v0.9.27-alpha ## Debugger Changes diff --git a/README.md b/README.md index 64b7ce02..37093768 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ debugger itself, and is intended as a technical overview of the project. The debugger's README, which includes usage instructions and tips, can be found packaged along with debugger releases, or within the `build` folder after a local copy has been built. You can find pre-built release binaries -[here](https://github.com/EpicGamesExt/raddebugger/releases)._ +[here](https://github.com/EpicGames/raddebugger/releases)._ The RAD Debugger is a native, user-mode, multi-process, graphical debugger. It currently only supports local-machine Windows x64 debugging with PDBs, with @@ -14,7 +14,7 @@ support native Linux debugging and DWARF debug info. The debugger is currently in *ALPHA*. In order to get the debugger bullet-proof, it'd greatly help out if you submitted the issues you find -[here](https://github.com/EpicGamesExt/raddebugger/issues), along with any +[here](https://github.com/EpicGames/raddebugger/issues), along with any information you can gather, like dump files (along with the build you used), instructions to reproduce, test executables, and so on. diff --git a/project.4coder b/project.4coder index 850ffd88..f205a915 100644 --- a/project.4coder +++ b/project.4coder @@ -49,7 +49,7 @@ commands = // .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, // .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg debug telemetry", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, // .f1 = { .win = "raddbg_stable --ipc kill_all && build radbin", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, - .f1 = { .win = "raddbg_stable --ipc kill_all && build radbin no_meta debug telemetry", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, + .f1 = { .win = "raddbg_stable --ipc kill_all && build torture && build ryan_scratch && pushd build && torture eval2/* && ryan_scratch", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, //- rjf: [raddbg wsl] // .f1 = { .win = "wsl ./build.sh raddbg", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, diff --git a/project.raddbg b/project.raddbg index d81b9d31..73a545e7 100644 --- a/project.raddbg +++ b/project.raddbg @@ -25,7 +25,7 @@ target: { executable: "build/torture.exe" working_directory: build - arguments: "raddbg/*" + arguments: "eval2/*" } target: { @@ -47,13 +47,29 @@ target: { executable: "build/radbin.exe" working_directory: build - arguments: "--breakpad fat.so --capture" + arguments: "--rdi fat.so --capture" +} +target: +{ + executable: "build/radbin.exe" + working_directory: build + arguments: "--rdi fat.debug --capture" } target: { executable: "build/radbin.exe" working_directory: build arguments: "--rdi raddbg" - enabled: 1 } watch_pin: expression: tag_hash_slots +target: +{ + executable: "build/ryan_scratch.exe" + working_directory: build + enabled: 1 +} +target: +{ + executable: "../../Program Files/SuperTux/bin/supertux2.exe" + working_directory: "../../Program Files/SuperTux/bin" +} diff --git a/src/artifact_cache/artifact_cache.c b/src/artifact_cache/artifact_cache.c index 7a5ddec8..b9b0db58 100644 --- a/src/artifact_cache/artifact_cache.c +++ b/src/artifact_cache/artifact_cache.c @@ -391,12 +391,12 @@ ac_async_tick(void) } // rjf: compute val - B32 retry = 0; + AC_Status status = AC_Status_Good; U64 gen = r->gen; - AC_Artifact val = r->create(r->key, r->cancel_signal, &retry, &gen); + AC_Artifact val = r->create(r->key, r->cancel_signal, &status, &gen); // rjf: retry? -> resubmit request - if(retry && lane_idx() == 0 && !ins_atomic_u32_eval(r->cancel_signal)) + if(status == AC_Status_NeedRetry && lane_idx() == 0 && !ins_atomic_u32_eval(r->cancel_signal)) { AC_RequestBatch *batch = &ac_shared->req_batches[task_idx]; MutexScope(batch->mutex) @@ -412,7 +412,7 @@ ac_async_tick(void) // rjf: create function -> cache AC_Cache *cache = 0; - if(!retry && lane_idx() == 0) + if(status != AC_Status_NeedRetry && lane_idx() == 0) { U64 cache_hash = u64_hash_from_str8(str8_struct(&r->create)); U64 cache_slot_idx = cache_hash%ac_shared->cache_slots_count; @@ -431,7 +431,7 @@ ac_async_tick(void) } // rjf: write value into cache - if(!retry && lane_idx() == 0) + if(status != AC_Status_NeedRetry && lane_idx() == 0) { U64 hash = u64_hash_from_str8(r->key); U64 slot_idx = hash%cache->slots_count; @@ -443,8 +443,10 @@ ac_async_tick(void) { if(str8_match(n->key, r->key, 0)) { + B32 got_new_value = (status == AC_Status_Good); + // rjf: eliminate existing values, if any, if they do not match the current - if(cache->destroy != 0 && !MemoryMatchStruct(&n->val, &val) && ins_atomic_u64_eval(&n->completion_count) > 0) for(;;) + if(got_new_value && cache->destroy != 0 && !MemoryMatchStruct(&n->val, &val) && ins_atomic_u64_eval(&n->completion_count) > 0) for(;;) { if(access_pt_is_expired(&n->access_pt, .time = 0, .update_idxs = 0)) { @@ -456,8 +458,11 @@ ac_async_tick(void) } // rjf: write new value + if(got_new_value) + { + n->val = val; + } n->last_completed_gen = gen; - n->val = val; ins_atomic_u64_dec_eval(&n->working_count); ins_atomic_u64_inc_eval(&n->completion_count); } @@ -510,15 +515,15 @@ ac_async_tick(void) LaneCtx lane_ctx_restore = lane_ctx(thin_lane_ctx); // rjf: compute val - B32 retry = 0; + AC_Status status = AC_Status_Good; U64 gen = r->gen; - AC_Artifact val = r->create(r->key, r->cancel_signal, &retry, &gen); + AC_Artifact val = r->create(r->key, r->cancel_signal, &status, &gen); // rjf: restore wide lane ctx lane_ctx(lane_ctx_restore); // rjf: retry? -> resubmit request - if(retry && !ins_atomic_u32_eval(r->cancel_signal)) + if(status == AC_Status_NeedRetry && !ins_atomic_u32_eval(r->cancel_signal)) { AC_RequestBatch *batch = &ac_shared->req_batches[task_idx]; MutexScope(batch->mutex) @@ -534,7 +539,7 @@ ac_async_tick(void) // rjf: create function -> cache AC_Cache *cache = 0; - if(!retry) + if(status != AC_Status_NeedRetry) { U64 cache_hash = u64_hash_from_str8(str8_struct(&r->create)); U64 cache_slot_idx = cache_hash%ac_shared->cache_slots_count; @@ -553,7 +558,7 @@ ac_async_tick(void) } // rjf: write value into cache - if(!retry) + if(status != AC_Status_NeedRetry) { U64 hash = u64_hash_from_str8(r->key); U64 slot_idx = hash%cache->slots_count; @@ -565,8 +570,10 @@ ac_async_tick(void) { if(str8_match(n->key, r->key, 0)) { + B32 got_new_value = (status == AC_Status_Good); + // rjf: eliminate existing values, if any, if they do not match the current - if(cache->destroy != 0 && !MemoryMatchStruct(&n->val, &val) && ins_atomic_u64_eval(&n->completion_count) > 0) for(;;) + if(got_new_value && cache->destroy != 0 && !MemoryMatchStruct(&n->val, &val) && ins_atomic_u64_eval(&n->completion_count) > 0) for(;;) { if(access_pt_is_expired(&n->access_pt, .time = 0, .update_idxs = 0)) { @@ -578,8 +585,11 @@ ac_async_tick(void) } // rjf: store + if(got_new_value) + { + n->val = val; + } n->last_completed_gen = gen; - n->val = val; ins_atomic_u64_dec_eval(&n->working_count); ins_atomic_u64_inc_eval(&n->completion_count); } diff --git a/src/artifact_cache/artifact_cache.h b/src/artifact_cache/artifact_cache.h index 2bd082bb..3acc5182 100644 --- a/src/artifact_cache/artifact_cache.h +++ b/src/artifact_cache/artifact_cache.h @@ -4,6 +4,17 @@ #ifndef ARTIFACT_CACHE_H #define ARTIFACT_CACHE_H +//////////////////////////////// +//~ rjf: Artifact Computation Statuses + +typedef enum AC_Status +{ + AC_Status_Good, + AC_Status_NeedRetry, + AC_Status_Failed, +} +AC_Status; + //////////////////////////////// //~ rjf: Artifact Handle Type @@ -16,7 +27,7 @@ struct AC_Artifact //////////////////////////////// //~ rjf: Artifact Computation Function Types -typedef AC_Artifact AC_CreateFunctionType(String8 key, B32 *cancel_signal, B32 *retry_out, U64 *gen_out); +typedef AC_Artifact AC_CreateFunctionType(String8 key, B32 *cancel_signal, AC_Status *status_out, U64 *gen_out); typedef void AC_DestroyFunctionType(AC_Artifact artifact); typedef U32 AC_Flags; @@ -168,6 +179,9 @@ internal AC_Artifact ac_artifact_from_key_(Access *access, String8 key, AC_Artif //////////////////////////////// //~ rjf: Asynchronous Tick +#if !defined(NEED_ASYNC) +# define NEED_ASYNC 1 +#endif internal void ac_async_tick(void); //////////////////////////////// diff --git a/src/base/base_arena.c b/src/base/base_arena.c index f765f700..881944b3 100644 --- a/src/base/base_arena.c +++ b/src/base/base_arena.c @@ -28,6 +28,8 @@ global ArenaTableNode *free_arena_table_node = 0; internal Arena * arena_alloc_(ArenaParams *params) { + ProfBeginFunction(); + U64 reserve_size = params->reserve_size; U64 commit_size = params->commit_size; @@ -133,12 +135,15 @@ arena_alloc_(ArenaParams *params) } #endif + ProfEnd(); return arena; } internal void arena_release(Arena *arena) { + ProfBeginFunction(); + #if PROFILE_TELEMETRY { Arena *base_arena = arena; @@ -171,6 +176,8 @@ arena_release(Arena *arena) AsanUnpoisonMemoryRegion(n, n->cmt); release_memory(n, n->res); } + + ProfEnd(); } //- rjf: arena push/pop core functions diff --git a/src/base/base_context_cracking.h b/src/base/base_context_cracking.h index 76011874..9fdf366c 100644 --- a/src/base/base_context_cracking.h +++ b/src/base/base_context_cracking.h @@ -187,7 +187,7 @@ #endif #if !defined(BUILD_ISSUES_LINK_STRING_LITERAL) -# define BUILD_ISSUES_LINK_STRING_LITERAL "https://github.com/EpicGamesExt/raddebugger/issues" +# define BUILD_ISSUES_LINK_STRING_LITERAL "https://github.com/EpicGames/raddebugger/issues" #endif #define BUILD_TITLE_STRING_LITERAL BUILD_TITLE " (" BUILD_VERSION_STRING_LITERAL " " BUILD_RELEASE_PHASE_STRING_LITERAL ") - " __DATE__ "" BUILD_GIT_HASH_STRING_LITERAL_APPEND BUILD_MODE_STRING_LITERAL_APPEND diff --git a/src/base/base_entry_point.c b/src/base/base_entry_point.c index a08a50b5..ddf34406 100644 --- a/src/base/base_entry_point.c +++ b/src/base/base_entry_point.c @@ -101,8 +101,8 @@ main_thread_base_entry_point(int arguments_count, char **arguments) rd_init(&cmdline); #endif -#if !NO_ASYNC //- rjf: launch async threads +#if NEED_ASYNC Thread *async_threads = 0; U64 lane_broadcast_val = 0; { @@ -137,9 +137,10 @@ main_thread_base_entry_point(int arguments_count, char **arguments) //- rjf: call into entry point entry_point(&cmdline); -#if !NO_ASYNC //- rjf: join async threads +#if NEED_ASYNC ins_atomic_u32_inc_eval(&global_async_exit); + ins_atomic_u32_inc_eval(&async_loop_again); cond_var_broadcast(async_tick_start_cond_var); for EachIndex(idx, async_threads_count) { diff --git a/src/base/base_memory_map.c b/src/base/base_memory_map.c index 23e37ea7..e774ca81 100644 --- a/src/base/base_memory_map.c +++ b/src/base/base_memory_map.c @@ -18,19 +18,39 @@ memory_map_read(MemoryMap *map, Rng1U64 range, void *dst) { U64 dst_vaddr = range.min; { - for(MemoryMapRangeNode *n = map->first_range; n != 0; n = n->next) + for(;;) { - if(contains_1u64(n->v.vaddr_range, dst_vaddr)) + U64 start_dst_vaddr = dst_vaddr; + B32 found = 0; + for(MemoryMapRangeNode *n = map->first_range; n != 0; n = n->next) { - U64 src_off = dst_vaddr - n->v.vaddr_range.min; - U64 num_bytes_possible = n->v.vaddr_range.max - dst_vaddr; - U64 num_bytes_needed = range.max - dst_vaddr; - U64 num_bytes_to_read = Min(num_bytes_needed, num_bytes_possible); - MemoryCopy((U8 *)dst + (dst_vaddr - range.min), (U8 *)n->v.base + src_off, num_bytes_to_read); - dst_vaddr += num_bytes_to_read; + if(contains_1u64(n->v.vaddr_range, dst_vaddr)) + { + U64 src_off = dst_vaddr - n->v.vaddr_range.min; + U64 num_bytes_possible = n->v.vaddr_range.max - dst_vaddr; + U64 num_bytes_needed = range.max - dst_vaddr; + U64 num_bytes_to_read = Min(num_bytes_needed, num_bytes_possible); + MemoryCopy((U8 *)dst + (dst_vaddr - range.min), (U8 *)n->v.base + src_off, num_bytes_to_read); + dst_vaddr += num_bytes_to_read; + found = 1; + } + } + if(!found || start_dst_vaddr == dst_vaddr) + { + break; } } } U64 bytes_read = (dst_vaddr - range.min); return bytes_read; } + +internal String8 +memory_map_data_from_range(Arena *arena, MemoryMap *map, Rng1U64 range) +{ + String8 result = {0}; + result.size = dim_1u64(range); + result.str = push_array(arena, U8, result.size); + memory_map_read(map, range, result.str); + return result; +} diff --git a/src/base/base_memory_map.h b/src/base/base_memory_map.h index 40a5e4fd..b33f2802 100644 --- a/src/base/base_memory_map.h +++ b/src/base/base_memory_map.h @@ -34,5 +34,6 @@ struct MemoryMap internal void memory_map_push(Arena *arena, MemoryMap *map, Rng1U64 vaddr_range, void *data); internal U64 memory_map_read(MemoryMap *map, Rng1U64 range, void *dst); #define memory_map_read_struct(map, vaddr, ptr) memory_map_read((map), r1u64((vaddr), (vaddr)+sizeof(*(ptr))), (ptr)) +internal String8 memory_map_data_from_range(Arena *arena, MemoryMap *map, Rng1U64 range); #endif // BASE_MEMORY_MAP_H diff --git a/src/base/base_ring.c b/src/base/base_ring.c index d43f0955..41f26095 100644 --- a/src/base/base_ring.c +++ b/src/base/base_ring.c @@ -46,18 +46,22 @@ ring_try_read(Ring *ring, U64 size, void *ptr) internal GuardedRing * guarded_ring_alloc(Arena *arena, U64 size) { + ProfBeginFunction(); GuardedRing *gr = push_array(arena, GuardedRing, 1); gr->ring = make_ring(arena, size); gr->mutex = mutex_alloc(); gr->cv = cond_var_alloc(); + ProfEnd(); return gr; } internal void guarded_ring_release(GuardedRing *ring) { + ProfBeginFunction(); mutex_release(ring->mutex); cond_var_release(ring->cv); + ProfEnd(); } internal RingGuard diff --git a/src/codeview/codeview_parse.c b/src/codeview/codeview_parse.c index 1a44b83e..583666fb 100644 --- a/src/codeview/codeview_parse.c +++ b/src/codeview/codeview_parse.c @@ -1025,63 +1025,77 @@ internal U64 cv_name_offset_from_symbol(CV_SymKind kind, String8 data) { U64 offset = data.size; - switch (kind) { - case CV_SymKind_COMPILE: break; - case CV_SymKind_OBJNAME: break; - case CV_SymKind_THUNK32: { + switch(kind) + { + case CV_SymKind_COMPILE:{}break; + case CV_SymKind_OBJNAME:{}break; + case CV_SymKind_THUNK32: + { offset = sizeof(CV_SymThunk32); - } break; - case CV_SymKind_LABEL32: { + }break; + case CV_SymKind_LABEL32: + { offset = sizeof(CV_SymLabel32); - } break; - case CV_SymKind_REGISTER: { + }break; + case CV_SymKind_REGISTER: + { offset = sizeof(CV_SymRegister); - } break; - case CV_SymKind_CONSTANT: { + }break; + case CV_SymKind_CONSTANT: + { offset = sizeof(CV_SymConstant); CV_NumericParsed size; offset += cv_read_numeric(data, offset, &size); - } break; - case CV_SymKind_UDT: { + }break; + case CV_SymKind_UDT: + { offset = sizeof(CV_SymUDT); - } break; - case CV_SymKind_BPREL32: { + }break; + case CV_SymKind_BPREL32: + { offset = sizeof(CV_SymBPRel32); - } break; + }break; case CV_SymKind_LDATA32: - case CV_SymKind_GDATA32: { + case CV_SymKind_GDATA32: + { offset = sizeof(CV_SymData32); - } break; - case CV_SymKind_PUB32: { + }break; + case CV_SymKind_PUB32: + { offset = sizeof(CV_SymPub32); - } break; + }break; case CV_SymKind_LPROC32: case CV_SymKind_GPROC32: case CV_SymKind_LPROC32_ID: - case CV_SymKind_GPROC32_ID: { + case CV_SymKind_GPROC32_ID: + { offset = sizeof(CV_SymProc32); - } break; - case CV_SymKind_REGREL32: { + }break; + case CV_SymKind_REGREL32: + { offset = sizeof(CV_SymRegrel32); - } break; + }break; case CV_SymKind_LTHREAD32: - case CV_SymKind_GTHREAD32: { + case CV_SymKind_GTHREAD32: + { offset = sizeof(CV_SymData32); - } break; + }break; case CV_SymKind_COMPILE2: break; - case CV_SymKind_LOCALSLOT: { + case CV_SymKind_LOCALSLOT: + { offset = sizeof(CV_SymSlot); - } break; + }break; case CV_SymKind_PROCREF: case CV_SymKind_LPROCREF: - case CV_SymKind_DATAREF: { + case CV_SymKind_DATAREF: + { offset = sizeof(CV_SymRef2); - } break; + }break; case CV_SymKind_TRAMPOLINE: break; - case CV_SymKind_LOCAL: { + case CV_SymKind_LOCAL: + { offset = sizeof(CV_SymLocal); - } break; - default: InvalidPath; + }break; } return offset; } diff --git a/src/coff/coff.h b/src/coff/coff.h index 5459f97c..5f379ee5 100644 --- a/src/coff/coff.h +++ b/src/coff/coff.h @@ -162,6 +162,7 @@ enum }; #define COFF_SectionFlags_ExtractAlign(f) (COFF_SectionAlign)(((f) >> COFF_SectionFlag_AlignShift) & COFF_SectionFlag_AlignMask) #define COFF_SectionFlags_LnkFlags ((COFF_SectionFlag_AlignMask << COFF_SectionFlag_AlignShift) | COFF_SectionFlag_LnkCOMDAT | COFF_SectionFlag_LnkInfo | COFF_SectionFlag_LnkOther | COFF_SectionFlag_LnkRemove | COFF_SectionFlag_LnkNRelocOvfl) +#define COFF_SectionFlags_Reserved 3 typedef struct COFF_SectionHeader COFF_SectionHeader; struct COFF_SectionHeader diff --git a/src/coff/coff_parse.c b/src/coff/coff_parse.c index 1db0b582..c2d129f7 100644 --- a/src/coff/coff_parse.c +++ b/src/coff/coff_parse.c @@ -166,13 +166,20 @@ coff_section_header_array_from_name(Arena *arena, String8 string_table, COFF_Sec } -internal COFF_ParsedSymbol -coff_parse_symbol32(String8 string_table, COFF_Symbol32 *sym32) -{ - COFF_ParsedSymbol result = {0}; - result.name = coff_read_symbol_name(string_table, &sym32->name); - result.value = sym32->value; - result.section_number = sym32->section_number; +internal COFF_ParsedSymbol +coff_parse_symbol32(String8 string_table, COFF_Symbol32 *sym32) +{ + COFF_ParsedSymbol result = coff_parse_symbol32_no_name(sym32); + result.name = coff_read_symbol_name(string_table, &sym32->name); + return result; +} + +internal force_inline COFF_ParsedSymbol +coff_parse_symbol32_no_name(COFF_Symbol32 *sym32) +{ + COFF_ParsedSymbol result = {0}; + result.value = sym32->value; + result.section_number = sym32->section_number; result.type = sym32->type; result.storage_class = sym32->storage_class; result.aux_symbol_count = sym32->aux_symbol_count; @@ -180,12 +187,19 @@ coff_parse_symbol32(String8 string_table, COFF_Symbol32 *sym32) return result; } -internal COFF_ParsedSymbol -coff_parse_symbol16(String8 string_table, COFF_Symbol16 *sym16) -{ - COFF_ParsedSymbol result = {0}; - result.name = coff_read_symbol_name(string_table, &sym16->name); - result.value = sym16->value; +internal COFF_ParsedSymbol +coff_parse_symbol16(String8 string_table, COFF_Symbol16 *sym16) +{ + COFF_ParsedSymbol result = coff_parse_symbol16_no_name(sym16); + result.name = coff_read_symbol_name(string_table, &sym16->name); + return result; +} + +internal force_inline COFF_ParsedSymbol +coff_parse_symbol16_no_name(COFF_Symbol16 *sym16) +{ + COFF_ParsedSymbol result = {0}; + result.value = sym16->value; if (sym16->section_number == COFF_Symbol_DebugSection16) { result.section_number = COFF_Symbol_DebugSection32; } else if (sym16->section_number == COFF_Symbol_AbsSection16) { @@ -200,8 +214,8 @@ coff_parse_symbol16(String8 string_table, COFF_Symbol16 *sym16) return result; } -internal COFF_ParsedSymbol -coff_parse_symbol(COFF_FileHeaderInfo header, String8 string_table, String8 symbol_table, U32 symbol_idx) +internal COFF_ParsedSymbol +coff_parse_symbol(COFF_FileHeaderInfo header, String8 string_table, String8 symbol_table, U32 symbol_idx) { COFF_ParsedSymbol symbol; if (header.is_big_obj) { @@ -209,8 +223,20 @@ coff_parse_symbol(COFF_FileHeaderInfo header, String8 string_table, String8 symb } else { symbol = coff_parse_symbol16(string_table, (COFF_Symbol16 *)symbol_table.str + symbol_idx); } - return symbol; -} + return symbol; +} + +internal force_inline COFF_ParsedSymbol +coff_parse_symbol_no_name(COFF_FileHeaderInfo header, String8 symbol_table, U32 symbol_idx) +{ + COFF_ParsedSymbol symbol; + if (header.is_big_obj) { + symbol = coff_parse_symbol32_no_name((COFF_Symbol32 *)symbol_table.str + symbol_idx); + } else { + symbol = coff_parse_symbol16_no_name((COFF_Symbol16 *)symbol_table.str + symbol_idx); + } + return symbol; +} internal COFF_Symbol32Array coff_symbol_array_from_data_16(Arena *arena, String8 raw_coff, U64 symbol_array_off, U64 symbol_count) diff --git a/src/coff/coff_parse.h b/src/coff/coff_parse.h index edcb2952..ac377eae 100644 --- a/src/coff/coff_parse.h +++ b/src/coff/coff_parse.h @@ -259,9 +259,12 @@ internal String8 coff_name_from_section_header (String8 str //////////////////////////////// // Symbol -internal COFF_ParsedSymbol coff_parse_symbol32(String8 string_table, COFF_Symbol32 *sym32); -internal COFF_ParsedSymbol coff_parse_symbol16(String8 string_table, COFF_Symbol16 *sym16); -internal COFF_ParsedSymbol coff_parse_symbol (COFF_FileHeaderInfo header, String8 string_table, String8 symbol_table, U32 symbol_idx); +internal COFF_ParsedSymbol coff_parse_symbol32(String8 string_table, COFF_Symbol32 *sym32); +internal COFF_ParsedSymbol coff_parse_symbol16(String8 string_table, COFF_Symbol16 *sym16); +internal COFF_ParsedSymbol coff_parse_symbol (COFF_FileHeaderInfo header, String8 string_table, String8 symbol_table, U32 symbol_idx); +internal force_inline COFF_ParsedSymbol coff_parse_symbol32_no_name(COFF_Symbol32 *sym32); +internal force_inline COFF_ParsedSymbol coff_parse_symbol16_no_name(COFF_Symbol16 *sym16); +internal force_inline COFF_ParsedSymbol coff_parse_symbol_no_name (COFF_FileHeaderInfo header, String8 symbol_table, U32 symbol_idx); internal COFF_Symbol32Array coff_symbol_array_from_data_16(Arena *arena, String8 data, U64 symbol_array_off, U64 symbol_count); internal COFF_Symbol32Array coff_symbol_array_from_data_32(Arena *arena, String8 data, U64 symbol_array_off, U64 symbol_count); diff --git a/src/com_shim/com_shim_main.c b/src/com_shim/com_shim_main.c index 6f9c5aba..5003eb3a 100644 --- a/src/com_shim/com_shim_main.c +++ b/src/com_shim/com_shim_main.c @@ -1,7 +1,6 @@ // Copyright (c) Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) -#define NO_ASYNC 1 #define BUILD_CONSOLE_INTERFACE 1 #define BUILD_TITLE "The RAD Debugger (Command Line Launcher)" #include "base/base_inc.h" diff --git a/src/content/content.h b/src/content/content.h index 90f44cda..a702a5bb 100644 --- a/src/content/content.h +++ b/src/content/content.h @@ -238,6 +238,9 @@ internal String8 c_data_from_hash(Access *access, U128 hash); //////////////////////////////// //~ rjf: Asynchronous Tick +#if !defined(NEED_ASYNC) +# define NEED_ASYNC 1 +#endif internal void c_async_tick(void); #endif // CONTENT_H diff --git a/src/dbg_engine/dbg_engine_ctrl.c b/src/dbg_engine/dbg_engine_ctrl.c index 943f7ad8..f33189d3 100644 --- a/src/dbg_engine/dbg_engine_ctrl.c +++ b/src/dbg_engine/dbg_engine_ctrl.c @@ -1225,9 +1225,26 @@ d_modules_from_dbgi_key(Arena *arena, DI_Key dbgi_key) return list; } +internal D_Entity * +d_process_from_id(U64 id) +{ + // TODO(rjf): @multimachine need machine ID here? + D_Entity *process = &d_entity_nil; + D_EntityArray processes = d_entity_array_from_kind(D_EntityKind_Process); + for EachIndex(idx, processes.count) + { + if(processes.v[idx]->id == id) + { + process = processes.v[idx]; + } + } + return process; +} + internal D_Entity * d_thread_from_id(U64 id) { + // TODO(rjf): @multimachine need machine ID here? D_Entity *thread = &d_entity_nil; D_EntityArray threads = d_entity_array_from_kind(D_EntityKind_Thread); for EachIndex(idx, threads.count) @@ -1781,9 +1798,10 @@ d_unwind_from_thread(Arena *arena, D_Handle thread, U64 endt_us) D_Unwind unwind = { .flags = D_UnwindFlag_Error }; ////////////////////////////// - //- rjf: grab run state pre-unwind computing + //- rjf: grab generations pre-unwind computing // - U64 run_gen = d_run_gen(); + U64 mem_gen = d_mem_gen(); + U64 reg_gen = d_reg_gen(); ////////////////////////////// //- rjf: unpack args @@ -1889,19 +1907,16 @@ d_unwind_from_thread(Arena *arena, D_Handle thread, U64 endt_us) // stale. if it can't be read, the unwind fails. if(step.status == UWND_StepStatus_FailedMemoryRead) { - D_ProcessMemorySlice slice = d_process_memory_slice_from_vaddr_range(scratch.arena, process_entity->handle, step.missed_read_vaddr_range, 1, endt_us); - String8 data = slice.data; - if(slice.stale) + U64 size_desired = dim_1u64(step.missed_read_vaddr_range); + U8 *data = push_array(scratch.arena, U8, size_desired); + U64 size = d_process_read(process_entity->handle, step.missed_read_vaddr_range, data); + if(size == size_desired) { - unwind.flags |= D_UnwindFlag_Stale; - } - else if(data.size < dim_1u64(step.missed_read_vaddr_range)) - { - unwind.flags |= D_UnwindFlag_Error; + memory_map_push(scratch.arena, &memory_map, step.missed_read_vaddr_range, data); } else { - memory_map_push(scratch.arena, &memory_map, step.missed_read_vaddr_range, data.str); + unwind.flags |= D_UnwindFlag_Error; } } @@ -1949,6 +1964,18 @@ d_unwind_from_thread(Arena *arena, D_Handle thread, U64 endt_us) } } + //- rjf: if we failed, but our generations changed, mark the 'stale' bit + if(unwind.flags & D_UnwindFlag_Error) + { + U64 post_reg_gen = d_reg_gen(); + U64 post_mem_gen = d_mem_gen(); + if(post_reg_gen != reg_gen || + post_mem_gen != mem_gen) + { + unwind.flags |= D_UnwindFlag_Stale; + } + } + //- rjf: bake frames list into result array { unwind.frames.count = frame_node_count; @@ -2806,7 +2833,14 @@ d_ctrl_thread__module_close(D_Handle process, D_Handle module, U64 base_vaddr) arena_release(node->arena); break; } - cond_var_wait_rw_w(stripe->cv, stripe->rw_mutex, max_U64); + if(node) + { + cond_var_wait_rw_w(stripe->cv, stripe->rw_mutex, max_U64); + } + else + { + break; + } } } @@ -5626,7 +5660,7 @@ d_data_from_process_vaddr_range(Arena *arena, D_Handle process, Rng1U64 vaddr_ra //- rjf: process memory artifact cache internal AC_Artifact -d_memory_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out, U64 *gen_out) +d_memory_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, U64 *gen_out) { AC_Artifact artifact = {0}; { @@ -5768,7 +5802,7 @@ d_memory_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out, U64 *g //- rjf: retry on mem gen "tearing", and if the range is non-empty if(pre_read_mem_gen != post_read_mem_gen && range_size != 0) { - retry_out[0] = 1; + status_out[0] = AC_Status_NeedRetry; } //- rjf: bundle content key as artifact @@ -5981,7 +6015,7 @@ d_process_memory_read(D_Handle process, Rng1U64 range, B32 *is_stale_out, void * //~ rjf: TLS Address Artifact Cache Hooks / Lookups internal AC_Artifact -d_tls_vaddr_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out, U64 *gen_out) +d_tls_vaddr_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, U64 *gen_out) { // rjf: unpack key D_Handle thread = {0}; @@ -6008,7 +6042,7 @@ d_tls_vaddr_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out, U64 // rjf: if not successful -> retry if(!success) { - retry_out[0] = 1; + status_out[0] = AC_Status_NeedRetry; } // rjf: package as artifact @@ -6017,11 +6051,6 @@ d_tls_vaddr_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out, U64 return artifact; } -internal void d_tls_vaddr_artifact_destroy(AC_Artifact artifact) -{ - // NOTE(rjf): no-op -} - internal U64 d_cached_tls_vaddr_from_thread_module(D_Handle thread_handle, D_Handle module_handle, U64 endt_us, B32 *stale_out) { @@ -6029,7 +6058,7 @@ d_cached_tls_vaddr_from_thread_module(D_Handle thread_handle, D_Handle module_ha Access *access = access_open(); D_Handle key_data[] = {thread_handle, module_handle}; String8 key = str8((U8 *)&key_data[0], sizeof(key_data)); - AC_Artifact artifact = ac_artifact_from_key(access, key, d_tls_vaddr_artifact_create, d_tls_vaddr_artifact_destroy, endt_us, .stale_out = stale_out); + AC_Artifact artifact = ac_artifact_from_key(access, key, d_tls_vaddr_artifact_create, 0, endt_us, .stale_out = stale_out); result = artifact.u64[0]; access_close(access); return result; @@ -6039,7 +6068,7 @@ d_cached_tls_vaddr_from_thread_module(D_Handle thread_handle, D_Handle module_ha //~ rjf: Call Stack Artifact Cache Hooks / Lookups internal AC_Artifact -d_call_stack_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out, U64 *gen_out) +d_call_stack_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, U64 *gen_out) { AC_Artifact artifact = {0}; { @@ -6168,14 +6197,19 @@ d_call_stack_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out, U6 pre_reg_gen = d_reg_gen(); pre_mem_gen = d_mem_gen(); unwind = d_unwind_from_thread(arena, thread_handle, now_time_us()+100); - if(!(unwind.flags & D_UnwindFlag_Stale)) + if(unwind.flags & D_UnwindFlag_Stale) { - good = 1; - call_stack[0] = d_call_stack_from_unwind(arena, process, &unwind); + retry = 1; + } + else if(unwind.flags & D_UnwindFlag_Error) + { + good = 0; + retry = 0; } else { - retry = 1; + good = 1; + call_stack[0] = d_call_stack_from_unwind(arena, process, &unwind); } post_reg_gen = d_reg_gen(); post_mem_gen = d_mem_gen(); @@ -6204,8 +6238,19 @@ d_call_stack_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out, U6 artifact.u64[1] = (U64)call_stack; } - //- rjf: mark retry - retry_out[0] = retry; + //- rjf: mark status + if(retry) + { + status_out[0] = AC_Status_NeedRetry; + } + else if(good) + { + status_out[0] = AC_Status_Good; + } + else + { + status_out[0] = AC_Status_Failed; + } scratch_end(scratch); } @@ -6243,7 +6288,7 @@ d_call_stack_from_thread(Access *access, D_Handle thread_handle, B32 high_priori //~ rjf: Call Stack Tree Artifact Cache Hooks / Lookups internal AC_Artifact -d_call_stack_tree_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out, U64 *gen_out) +d_call_stack_tree_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, U64 *gen_out) { Temp scratch = scratch_begin(0, 0); Access *access = access_open(); @@ -6354,7 +6399,7 @@ d_call_stack_tree_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_ou //- rjf: retry on stale if(stale) { - retry_out[0] = 1; + status_out[0] = AC_Status_NeedRetry; } access_close(access); diff --git a/src/dbg_engine/dbg_engine_ctrl.h b/src/dbg_engine/dbg_engine_ctrl.h index 47a8a750..4fe1ab95 100644 --- a/src/dbg_engine/dbg_engine_ctrl.h +++ b/src/dbg_engine/dbg_engine_ctrl.h @@ -519,6 +519,7 @@ internal void d_entity_equip_string(D_EntityCtxRWStore *store, D_Entity *entity, internal D_EntityCtxLookupAccel *d_thread_entity_ctx_lookup_accel(void); internal D_EntityArray d_entity_array_from_kind(D_EntityKind kind); internal D_EntityList d_modules_from_dbgi_key(Arena *arena, DI_Key dbgi_key); +internal D_Entity *d_process_from_id(U64 id); internal D_Entity *d_thread_from_id(U64 id); //- rjf: applying events to entity caches @@ -640,7 +641,7 @@ internal String8 d_data_from_process_vaddr_range(Arena *arena, D_Handle process, #define d_process_write_struct(process, vaddr, ptr) d_process_write((process), r1u64((vaddr), (vaddr)+(sizeof(*ptr))), (ptr)) //- rjf: process memory artifact cache -internal AC_Artifact d_memory_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out, U64 *gen_out); +internal AC_Artifact d_memory_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, U64 *gen_out); internal void d_memory_artifact_destroy(AC_Artifact artifact); internal C_Key d_key_from_process_vaddr_range(D_Handle process, Rng1U64 vaddr_range, B32 zero_terminated, B32 wait_for_fresh, U64 endt_us, B32 *out_is_stale); @@ -652,21 +653,20 @@ internal B32 d_process_memory_read(D_Handle process, Rng1U64 range, B32 *is_stal //////////////////////////////// //~ rjf: TLS Address Artifact Cache Hooks / Lookups -internal AC_Artifact d_tls_vaddr_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out, U64 *gen_out); -internal void d_tls_vaddr_artifact_destroy(AC_Artifact artifact); +internal AC_Artifact d_tls_vaddr_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, U64 *gen_out); internal U64 d_cached_tls_vaddr_from_thread_module(D_Handle thread_handle, D_Handle module_handle, U64 endt_us, B32 *stale_out); //////////////////////////////// //~ rjf: Call Stack Artifact Cache Hooks / Lookups -internal AC_Artifact d_call_stack_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out, U64 *gen_out); +internal AC_Artifact d_call_stack_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, U64 *gen_out); internal void d_call_stack_artifact_destroy(AC_Artifact artifact); internal D_CallStack d_call_stack_from_thread(Access *access, D_Handle thread_handle, B32 high_priority, U64 endt_us); //////////////////////////////// //~ rjf: Call Stack Tree Artifact Cache Hooks / Lookups -internal AC_Artifact d_call_stack_tree_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out, U64 *gen_out); +internal AC_Artifact d_call_stack_tree_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, U64 *gen_out); internal void d_call_stack_tree_artifact_destroy(AC_Artifact artifact); internal D_CallStackTree d_call_stack_tree(Access *access, U64 endt_us); diff --git a/src/dbg_engine/dbg_engine_user.c b/src/dbg_engine/dbg_engine_user.c index bafedc75..9a923aa1 100644 --- a/src/dbg_engine/dbg_engine_user.c +++ b/src/dbg_engine/dbg_engine_user.c @@ -2004,7 +2004,7 @@ d_tick(Arena *arena, D_TargetArray *targets, D_BreakpointArray *breakpoints, D_P if(params->file_path.size != 0) { run_extra_bps.v[0].file_path = params->file_path; - run_extra_bps.v[0].pt = params->cursor; + run_extra_bps.v[0].pt = txt_pt((S64)params->line_num, (S64)params->column_num); } else if(params->vaddr != 0) { diff --git a/src/dbg_engine/dbg_engine_user.h b/src/dbg_engine/dbg_engine_user.h index 2a81e8a8..eb63a750 100644 --- a/src/dbg_engine/dbg_engine_user.h +++ b/src/dbg_engine/dbg_engine_user.h @@ -65,7 +65,8 @@ struct D_CmdParams D_Handle entity; String8 string; String8 file_path; - TxtPt cursor; + U64 line_num; + U64 column_num; U64 vaddr; B32 prefer_disasm; U32 pid; diff --git a/src/dbg_info/dbg_info.c b/src/dbg_info/dbg_info.c index fbbe8f63..df265907 100644 --- a/src/dbg_info/dbg_info.c +++ b/src/dbg_info/dbg_info.c @@ -1152,7 +1152,7 @@ di_conversion_completion_signal_receiver_thread_entry_point(void *p) //~ rjf: Search Artifact Cache Hooks / Lookups internal AC_Artifact -di_search_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out, U64 *gen_out) +di_search_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, U64 *gen_out) { ProfBeginFunction(); Access *access = access_open(); @@ -1569,7 +1569,7 @@ di_search_item_array_from_target_query(Access *access, RDI_SectionKind target, S //~ rjf: Match Artifact Cache Hooks / Lookups internal AC_Artifact -di_match_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out, U64 *gen_out) +di_match_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, U64 *gen_out) { ProfBeginFunction(); Temp scratch = scratch_begin(0, 0); diff --git a/src/dbg_info/dbg_info.h b/src/dbg_info/dbg_info.h index 85fb9a03..56c88acc 100644 --- a/src/dbg_info/dbg_info.h +++ b/src/dbg_info/dbg_info.h @@ -352,6 +352,9 @@ internal DI_EventList di_get_events(Arena *arena); //////////////////////////////// //~ rjf: Asynchronous Tick +#if !defined(NEED_ASYNC) +# define NEED_ASYNC 1 +#endif internal void di_async_tick(void); //////////////////////////////// @@ -363,14 +366,14 @@ internal void di_conversion_completion_signal_receiver_thread_entry_point(void * //////////////////////////////// //~ rjf: Search Artifact Cache Hooks / Lookups -internal AC_Artifact di_search_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out, U64 *gen_out); +internal AC_Artifact di_search_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, U64 *gen_out); internal void di_search_artifact_destroy(AC_Artifact artifact); internal DI_SearchItemArray di_search_item_array_from_target_query(Access *access, RDI_SectionKind target, String8 query, U64 endt_us, B32 *stale_out); //////////////////////////////// //~ rjf: Match Artifact Cache Hooks / Lookups -internal AC_Artifact di_match_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out, U64 *gen_out); +internal AC_Artifact di_match_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, U64 *gen_out); internal DI_Match di_match_from_string(String8 string, U32 match_index, U32 unit_idx, B32 allow_other_dbgis, DI_Key preferred_dbgi_key, U64 endt_us); #endif // DBG_INFO_H diff --git a/src/disasm/disasm.c b/src/disasm/disasm.c index 58532b5e..21033b6c 100644 --- a/src/disasm/disasm.c +++ b/src/disasm/disasm.c @@ -146,7 +146,7 @@ struct DASM_Artifact }; internal AC_Artifact -dasm_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out, U64 *gen_out) +dasm_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, U64 *gen_out) { DASM_Artifact *artifact = 0; if(lane_idx() == 0) @@ -338,7 +338,7 @@ dasm_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out, U64 *gen_o //- rjf: if stale, retry if(stale) { - retry_out[0] = 1; + status_out[0] = AC_Status_NeedRetry; } //- rjf: mark dependency on data hash diff --git a/src/disasm/disasm.h b/src/disasm/disasm.h index 816e9c19..87840257 100644 --- a/src/disasm/disasm.h +++ b/src/disasm/disasm.h @@ -201,7 +201,7 @@ internal U64 dasm_line_array_code_off_from_idx(DASM_LineArray *array, U64 idx); //////////////////////////////// //~ rjf: Artifact Cache Hooks / Lookups -internal AC_Artifact dasm_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out, U64 *gen_out); +internal AC_Artifact dasm_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, U64 *gen_out); internal void dasm_artifact_destroy(AC_Artifact artifact); internal DASM_Info dasm_info_from_hash_params(Access *access, U128 hash, DASM_Params *params); internal DASM_Info dasm_info_from_key_params(Access *access, C_Key key, DASM_Params *params, U128 *hash_out); diff --git a/src/eval/eval_core.c b/src/eval/eval_core.c index c8d19db4..e46fcf29 100644 --- a/src/eval/eval_core.c +++ b/src/eval/eval_core.c @@ -709,8 +709,7 @@ e_oplist_from_location(Arena *arena, RDI_Parsed *rdi, RDI_Location loc) ARCH_RegCode reg_code = arch_reg_code_from_rdi(arch, rdi_regcode); Rng1U16 reg_rng = arch_info->reg_code_rng_table[reg_code]; U64 byte_size = (U64)dim_1u16(reg_rng); - U64 byte_pos = reg_rng.min; - e_oplist_push_op(arena, &result, RDI_EvalOp_RegRead, e_value_u64(RDI_EncodeRegReadParam(rdi_regcode, byte_size, byte_pos))); + e_oplist_push_op(arena, &result, RDI_EvalOp_RegRead, e_value_u64(RDI_EncodeRegReadParam(rdi_regcode, byte_size, 0))); }break; //- rjf: space offsets diff --git a/src/eval/eval_ir.c b/src/eval/eval_ir.c index ab1974f8..a7b8fe02 100644 --- a/src/eval/eval_ir.c +++ b/src/eval/eval_ir.c @@ -1874,6 +1874,7 @@ e_push_irtree_and_type_from_expr(Arena *arena, E_IRTreeAndType *root_parent, E_I U64 ip_voff = e_base_ctx->thread_ip_voff; { mapped_location = rdi_location_from_location_voff(rdi, local->location, ip_voff); + mapped_bytecode_mode = E_Mode_Null; if(mapped_location != 0) { got_location_block = 1; diff --git a/src/eval/eval_types.c b/src/eval/eval_types.c index d1ca382f..de41c9a3 100644 --- a/src/eval/eval_types.c +++ b/src/eval/eval_types.c @@ -2579,7 +2579,7 @@ E_TYPE_EXPAND_RANGE_FUNCTION_DEF(array) //~ rjf: (Built-In Type Hooks) `list` lens internal AC_Artifact -e_list_gather_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out, U64 *gen_out) +e_list_gather_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, U64 *gen_out) { Temp scratch = scratch_begin(0, 0); @@ -2684,7 +2684,7 @@ e_list_gather_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out, U //- rjf: retry if(retry) { - retry_out[0] = 1; + status_out[0] = AC_Status_NeedRetry; } //- rjf: flatten diff --git a/src/eval2/eval2.c b/src/eval2/eval2.c index 9ee5e7be..99d18520 100644 --- a/src/eval2/eval2.c +++ b/src/eval2/eval2.c @@ -116,6 +116,62 @@ e2_expr_from_name(E2_ExprMap *map, String8 name) return expr; } +//////////////////////////////// +//~ rjf: Identifier Map Helpers + +internal void +e2_identifier_map_push(Arena *arena, E2_IdentifierMap *map, String8 name, E2_IRNode *irtree) +{ + if(map->slots_count == 0) + { + map->slots_count = 8; + map->slots = push_array(arena, E2_IdentifierMapNode *, map->slots_count); + } + U64 hash = u64_hash_from_str8(name); + U64 slot_idx = hash%map->slots_count; + E2_IdentifierMapNode *node = 0; + for(E2_IdentifierMapNode *n = map->slots[slot_idx]; n != 0; n = n->next) + { + if(str8_match(n->name, name, 0)) + { + node = n; + break; + } + } + if(node == 0) + { + node = push_array(arena, E2_IdentifierMapNode, 1); + node->name = str8_copy(arena, name); + node->irtree = irtree; + SLLStackPush(map->slots[slot_idx], node); + } +} + +internal E2_IRNode * +e2_irtree_from_identifier(E2_IdentifierMap *map, String8 name) +{ + E2_IRNode *irtree = &e2_irnode_nil; + if(map->slots_count != 0) + { + U64 hash = u64_hash_from_str8(name); + U64 slot_idx = hash%map->slots_count; + E2_IdentifierMapNode *node = 0; + for(E2_IdentifierMapNode *n = map->slots[slot_idx]; n != 0; n = n->next) + { + if(str8_match(n->name, name, 0)) + { + node = n; + break; + } + } + if(node != 0) + { + irtree = node->irtree; + } + } + return irtree; +} + //////////////////////////////// //~ rjf: Messages @@ -163,6 +219,165 @@ e2_dbgi_from_num(U32 num) return result; } +//////////////////////////////// +//~ rjf: Constructed Types + +internal E2_ConsTypeMap * +e2_cons_type_map_alloc(void) +{ + Arena *arena = arena_alloc(); + E2_ConsTypeMap *map = push_array(arena, E2_ConsTypeMap, 1); + map->arena = arena; + map->table_start_arena_pos = arena_pos(arena); + return map; +} + +internal void +e2_select_cons_type_map(E2_ConsTypeMap *map) +{ + arena_pop_to(map->arena, map->table_start_arena_pos); + map->slots_count = 256; + map->content_slots = push_array(map->arena, E2_ConsTypeSlot, map->slots_count); + map->id_slots = push_array(map->arena, E2_ConsTypeSlot, map->slots_count); + e2_cons_type_map = map; +} + +internal B32 +e2_cons_type_params_match(E2_ConsTypeParams *a, E2_ConsTypeParams *b) +{ + B32 result = (a->arch == b->arch && + a->kind == b->kind && + str8_match(a->name, b->name, 0) && + e2_type_key_match(a->direct, b->direct) && + a->count == b->count && + a->depth == b->depth && + a->args == 0 && b->args == 0); + return result; +} + +internal E2_ConsTypeNode * +e2_cons_type_node_from_params(E2_ConsTypeParams *params) +{ + E2_ConsTypeMap *map = e2_cons_type_map; + + // rjf: params -> hash + U64 hash = 0; + { + hash = u64_hash_from_seed_str8(hash, str8_struct(¶ms->arch)); + hash = u64_hash_from_seed_str8(hash, str8_struct(¶ms->kind)); + hash = u64_hash_from_seed_str8(hash, str8_struct(¶ms->name)); + hash = u64_hash_from_seed_str8(hash, str8_struct(¶ms->direct)); + hash = u64_hash_from_seed_str8(hash, str8_struct(¶ms->count)); + hash = u64_hash_from_seed_str8(hash, str8_struct(¶ms->depth)); + if(params->param_types != 0) + { + for EachIndex(idx, params->count) + { + hash = u64_hash_from_seed_str8(hash, str8_struct(¶ms->param_types[idx])); + } + } + } + + // rjf: hash -> content slot + U64 content_slot_idx = hash%map->slots_count; + E2_ConsTypeSlot *content_slot = &map->content_slots[content_slot_idx]; + + // rjf: parameters -> existing node + E2_ConsTypeNode *node = 0; + for(E2_ConsTypeNode *n = content_slot->first; n != 0; n = n->content_next) + { + if(e2_cons_type_params_match(&n->params, params)) + { + node = n; + break; + } + } + + // rjf: if node wasn't found -> push into the map + if(node == 0) + { + // rjf: form node ID + map->id_gen += 1; + U64 id = map->id_gen; + + // rjf: build the node + node = push_array(map->arena, E2_ConsTypeNode, 1); + SLLQueuePush_N(content_slot->first, content_slot->last, node, content_next); + node->id = id; + MemoryCopyStruct(&node->params, params); + node->params.name = str8_copy(map->arena, node->params.name); + if(node->params.param_types != 0) + { + node->params.param_types = push_array(map->arena, E2_TypeKey, node->params.count); + MemoryCopy(node->params.param_types, params->param_types, sizeof(params->param_types[0])*params->count); + } + // TODO(rjf): double check once we're doing type expression + // arguments that we don't need a deep copy of the args here. + switch(node->params.kind) + { + default: + { + node->byte_size = e2_byte_size_from_type_key(node->params.direct); + }break; + case E2_TypeKind_Ptr: + case E2_TypeKind_Function: + case E2_TypeKind_Method: + case E2_TypeKind_MemberPtr: + case E2_TypeKind_RRef: + case E2_TypeKind_LRef: + { + node->byte_size = byte_size_from_arch(node->params.arch); + }break; + case E2_TypeKind_Array: + { + node->byte_size = node->params.count * e2_byte_size_from_type_key(node->params.direct); + }break; + } + + // rjf: insert node into the ID slots + { + U64 id_hash = u64_hash_from_str8(str8_struct(&id)); + U64 id_slot_idx = id_hash%map->slots_count; + E2_ConsTypeSlot *id_slot = &map->id_slots[id_slot_idx]; + SLLQueuePush_N(id_slot->first, id_slot->last, node, id_next); + } + } + + return node; +} + +internal E2_ConsTypeNode * +e2_cons_type_node_from_id(U64 id) +{ + E2_ConsTypeMap *map = e2_cons_type_map; + U64 id_hash = u64_hash_from_str8(str8_struct(&id)); + U64 id_slot_idx = id_hash%map->slots_count; + E2_ConsTypeSlot *id_slot = &map->id_slots[id_slot_idx]; + E2_ConsTypeNode *node = &e2_cons_type_node_nil; + for(E2_ConsTypeNode *n = id_slot->first; n != 0; n = n->id_next) + { + if(n->id == id) + { + node = n; + break; + } + } + return node; +} + +internal E2_TypeKey +e2_type_key_from_cons_type_node(E2_ConsTypeNode *node) +{ + E2_TypeKey key = + { + .kind = E2_TypeKeyKind_Cons, + .u32[0] = node->params.kind, + .u32[1] = (U32)((node->id&0xffffffff00000000ull) >> 32), + .u32[2] = node->id&0xffffffffull, + }; + return key; +} + //////////////////////////////// //~ rjf: Type Keys @@ -371,8 +586,8 @@ e2_type_key_reg(Arch arch, ARCH_RegCode reg_code) internal E2_TypeKey e2_type_key_cons_(E2_ConsTypeParams *params) { - E2_TypeKey result = {E2_TypeKeyKind_Null}; - // TODO(rjf) + E2_ConsTypeNode *node = e2_cons_type_node_from_params(params); + E2_TypeKey result = e2_type_key_from_cons_type_node(node); return result; } @@ -428,11 +643,20 @@ e2_byte_size_from_type_key(E2_TypeKey k) }break; case E2_TypeKeyKind_Cons: { - // TODO(rjf) + U64 id = e2_cons_type_id_from_key(k); + E2_ConsTypeNode *node = e2_cons_type_node_from_id(id); + result = node->byte_size; }break; case E2_TypeKeyKind_Reg: { - // TODO(rjf) + Arch arch = (Arch)k.u32[0]; + ARCH_Info *arch_info = arch_info_from_arch(arch); + ARCH_RegCode regcode = k.u32[1]; + if(regcode < arch_info->reg_code_count) + { + Rng1U16 regrng = arch_info->reg_code_rng_table[regcode]; + result = (U64)dim_1u16(regrng); + } }break; } return result; @@ -468,6 +692,17 @@ e2_dbgi_type_idx_from_key(E2_TypeKey k) return result; } +internal U64 +e2_cons_type_id_from_key(E2_TypeKey k) +{ + U64 result = 0; + if(k.kind == E2_TypeKeyKind_Cons) + { + result = ((U64)k.u32[1] << 32) | k.u32[2]; + } + return result; +} + internal U64 e2_shift_from_type_key(E2_TypeKey k) { @@ -485,10 +720,7 @@ e2_shift_from_type_key(E2_TypeKey k) RDI_TypeNode *type_node = rdi_element_from_name_idx(dbgi->rdi, TypeNodes, dbgi_type_idx); result = type_node->bitfield.off; }break; - case E2_TypeKeyKind_Cons: - { - // TODO(rjf) - }break; + case E2_TypeKeyKind_Cons:{}break; case E2_TypeKeyKind_Reg:{}break; } } @@ -512,9 +744,35 @@ e2_mask_count_from_type_key(E2_TypeKey k) RDI_TypeNode *type_node = rdi_element_from_name_idx(dbgi->rdi, TypeNodes, dbgi_type_idx); result = type_node->bitfield.size; }break; + case E2_TypeKeyKind_Cons:{}break; + case E2_TypeKeyKind_Reg:{}break; + } + } + return result; +} + +internal U64 +e2_array_count_from_type_key(E2_TypeKey k) +{ + U64 result = 0; + if(e2_type_kind_from_key(k) == E2_TypeKind_Array) + { + switch(k.kind) + { + case E2_TypeKeyKind_Null:{}break; + case E2_TypeKeyKind_Basic:{}break; + case E2_TypeKeyKind_DbgInfo: + { + E2_DbgInfo *dbgi = e2_dbgi_from_type_key(k); + U32 dbgi_type_idx = e2_dbgi_type_idx_from_key(k); + RDI_TypeNode *type_node = rdi_element_from_name_idx(dbgi->rdi, TypeNodes, dbgi_type_idx); + result = type_node->constructed.count; + }break; case E2_TypeKeyKind_Cons: { - // TODO(rjf) + U64 id = e2_cons_type_id_from_key(k); + E2_ConsTypeNode *node = e2_cons_type_node_from_id(id); + result = node->params.count; }break; case E2_TypeKeyKind_Reg:{}break; } @@ -539,16 +797,318 @@ e2_arch_from_type_key(E2_TypeKey k) }break; case E2_TypeKeyKind_Cons: { - // TODO(rjf) + U64 id = e2_cons_type_id_from_key(k); + E2_ConsTypeNode *node = e2_cons_type_node_from_id(id); + arch = node->params.arch; }break; case E2_TypeKeyKind_Reg: { - arch = k.u32[0]; + arch = (Arch)k.u32[0]; }break; } return arch; } +internal String8 +e2_name_from_type_key(Arena *arena, E2_TypeKey k) +{ + String8 result = {0}; + switch(k.kind) + { + case E2_TypeKeyKind_Null:{}break; + case E2_TypeKeyKind_Basic: + { + E2_TypeKind kind = e2_type_kind_from_key(k); + if(E2_TypeKind_FirstBasic <= kind && kind <= E2_TypeKind_LastBasic) + { + result = e2_type_kind_basic_string_table[kind]; + } + }break; + case E2_TypeKeyKind_DbgInfo: + { + E2_DbgInfo *dbgi = e2_dbgi_from_type_key(k); + U32 type_idx = e2_dbgi_type_idx_from_key(k); + RDI_TypeNode *type_node = rdi_element_from_name_idx(dbgi->rdi, TypeNodes, type_idx); + result = fully_qualified_str8_from_rdi_type(arena, dbgi->rdi, type_node); + }break; + case E2_TypeKeyKind_Cons: + { + U64 id = e2_cons_type_id_from_key(k); + E2_ConsTypeNode *node = e2_cons_type_node_from_id(id); + result = node->params.name; + }break; + case E2_TypeKeyKind_Reg: + { + U64 byte_size = e2_byte_size_from_type_key(k); + result = str8f(arena, "register_%I64u", byte_size*8); + }break; + } + return result; +} + +//- rjf: type key -> string + +internal String8 +e2_string_from_type_key(Arena *arena, E2_TypeKey key) +{ + String8 result = {0}; + { + typedef struct Task Task; + struct Task + { + Task *next; + E2_TypeKey key; + U32 prec; + B32 did_direct; + }; + Task start_task = {0, key}; + Task *top_task = &start_task; + Task *free_task = 0; + Temp scratch = scratch_begin(&arena, 1); + String8List lhs = {0}; + String8List rhs = {0}; + for(;top_task != 0;) + { + //- rjf: unpack type + E2_TypeKey key = top_task->key; + E2_TypeKind kind = e2_type_kind_from_key(key); + E2_TypeKey direct = e2_type_key_direct(key); + String8 name = e2_name_from_type_key(scratch.arena, key); + U64 prec = top_task->prec; + B32 did_direct = top_task->did_direct; + + //- rjf: push string for this task + U32 next_prec = 0; + switch(kind) + { + default: + { + String8 keyword = {0}; + if(E2_TypeKind_FirstIncomplete <= kind && kind <= E2_TypeKind_LastIncomplete) + { + switch(kind) + { + default:{}break; + case E2_TypeKind_IncompleteStruct:{keyword = s("struct");}break; + case E2_TypeKind_IncompleteUnion: {keyword = s("union");}break; + case E2_TypeKind_IncompleteClass: {keyword = s("class");}break; + case E2_TypeKind_IncompleteEnum: {keyword = s("enum");}break; + } + } + if(keyword.size != 0) + { + str8_list_pushf(scratch.arena, &lhs, "%S ", keyword); + } + str8_list_push(scratch.arena, &lhs, name); + }break; + case E2_TypeKind_Ptr: + if(!did_direct) + { + next_prec = 1; + } + else if(did_direct) + { + str8_list_push(scratch.arena, &lhs, s("*")); + }break; + case E2_TypeKind_LRef: + if(did_direct) + { + str8_list_push(scratch.arena, &lhs, s("&")); + }break; + case E2_TypeKind_RRef: + if(did_direct) + { + str8_list_push(scratch.arena, &lhs, s("&&")); + }break; + case E2_TypeKind_Array: + if(!did_direct) + { + next_prec = 2; + } + else + { + U64 count = e2_array_count_from_type_key(key); + if(prec == 1) + { + str8_list_push(scratch.arena, &lhs, s("(")); + str8_list_push(scratch.arena, &rhs, s(")")); + } + str8_list_pushf(scratch.arena, &rhs, "[%I64u]", count); + }break; + case E2_TypeKind_Function: + if(!did_direct) + { + next_prec = 2; + } + else + { + U64 count = e2_array_count_from_type_key(key); + if(prec == 1) + { + str8_list_push(scratch.arena, &lhs, s("(")); + str8_list_push(scratch.arena, &rhs, s(")")); + } + // TODO(rjf): params here + str8_list_pushf(scratch.arena, &rhs, "(...)"); + }break; + case E2_TypeKind_Bitfield: + if(did_direct) + { + U64 mask_count = e2_mask_count_from_type_key(key); + str8_list_pushf(scratch.arena, &rhs, ": %I64u", mask_count); + }break; + case E2_TypeKind_Variadic: + { + str8_list_push(scratch.arena, &lhs, s("...")); + }break; + } + + //- rjf: did direct, or we don't have a direct key? -> we're done with this type, pop + if(did_direct || e2_type_key_match(direct, e2_type_key_zero())) + { + Task *popped = top_task; + SLLStackPop(top_task); + SLLStackPush(free_task, popped); + } + + //- rjf: didn't do direct? -> push new task for direct type + else + { + top_task->did_direct = 1; + Task *t = free_task; + if(t != 0) + { + SLLStackPop(free_task); + } + else + { + t = push_array_no_zero(scratch.arena, Task, 1); + } + MemoryZeroStruct(t); + SLLStackPush(top_task, t); + t->key = direct; + t->prec = next_prec; + } + } + String8List parts = {0}; + str8_list_concat_in_place(&parts, &lhs); + str8_list_concat_in_place(&parts, &rhs); + result = str8_list_join(arena, &parts, 0); + scratch_end(scratch); + } + return result; +} + +//- rjf: type deep matches + +internal B32 +e2_type_deep_match(E2_TypeKey l, E2_TypeKey r) +{ + B32 result = e2_type_key_match(l, r); + if(!result) + { + Temp scratch = scratch_begin(0, 0); + typedef struct Task Task; + struct Task + { + Task *next; + E2_TypeKey k; + U64 member_num; + }; + Task *free_task = 0; + Task l_start_task = {0, l}; + Task r_start_task = {0, r}; + Task *l_top = &l_start_task; + Task *r_top = &r_start_task; + U64 l_hash = 0; + U64 r_hash = 0; + result = 1; + for(;l_top != 0 || r_top != 0;) + { + // rjf: mismatched task structures -> immediately mark as a non-match & break + if((l_top && !r_top) || (!l_top && r_top)) + { + result = 0; + break; + } + + // rjf: mismatched hashes -> immediately mark as a non-match & break + if(l_hash != r_hash) + { + result = 0; + break; + } + + // rjf: do next step of hash + Task **task_tops[] = {&l_top, &r_top}; + E2_TypeKey keys[] = {l_top->k, r_top->k}; + U64 *hash_ptrs[] = {&l_hash, &r_hash}; + for EachElement(key_idx, keys) + { + Task *top = task_tops[key_idx][0]; + E2_TypeKey key = keys[key_idx]; + E2_TypeKind kind = e2_type_kind_from_key(key); + + // rjf: have not advanced to members -> mix in kind / byte size + if(top->member_num == 0) + { + U64 byte_size = e2_byte_size_from_type_key(key); + hash_ptrs[key_idx][0] = u64_hash_from_seed_str8(hash_ptrs[key_idx][0], str8_struct(&kind)); + hash_ptrs[key_idx][0] = u64_hash_from_seed_str8(hash_ptrs[key_idx][0], str8_struct(&byte_size)); + } + + // rjf: advance to next member + B32 has_next_member = 0; + if(kind == E2_TypeKind_Struct || + kind == E2_TypeKind_Union || + kind == E2_TypeKind_Class || + kind == E2_TypeKind_Enum) + { + U32 dbgi_num = e2_dbgi_num_from_type_key(key); + U32 type_idx = e2_dbgi_type_idx_from_key(key); + E2_DbgInfo *dbgi = e2_dbgi_from_num(dbgi_num); + RDI_TypeNode *type_node = rdi_element_from_name_idx(dbgi->rdi, TypeNodes, type_idx); + String8 name = str8_from_rdi_string_idx(dbgi->rdi, type_node->user_defined.name_string_idx); + hash_ptrs[key_idx][0] = u64_hash_from_seed_str8(hash_ptrs[key_idx][0], name); + RDI_UDT *udt = rdi_element_from_name_idx(dbgi->rdi, UDTs, type_node->user_defined.udt_idx); + U64 next_member_num = top->member_num + 1; + if(next_member_num <= udt->member_count) + { + has_next_member = 1; + RDI_Member *member = rdi_element_from_name_idx(dbgi->rdi, Members, next_member_num-1); + RDI_TypeNode *member_type_node = rdi_element_from_name_idx(dbgi->rdi, TypeNodes, member->type_idx); + String8 member_name = str8_from_rdi_string_idx(dbgi->rdi, member->name_string_idx); + hash_ptrs[key_idx][0] = u64_hash_from_seed_str8(hash_ptrs[key_idx][0], str8_struct(&member->off)); + hash_ptrs[key_idx][0] = u64_hash_from_seed_str8(hash_ptrs[key_idx][0], member_name); + Task *member_task = free_task; + if(member_task != 0) + { + SLLStackPop(free_task); + } + else + { + member_task = push_array_no_zero(scratch.arena, Task, 1); + } + MemoryZeroStruct(&member_task); + member_task->k = e2_type_key_dbgi(e2_type_kind_from_rdi(member_type_node->kind), dbgi_num, member->type_idx); + SLLStackPush(task_tops[key_idx][0], member_task); + } + } + + // rjf: if we don't have a subsequent member, pop this task + if(!has_next_member) + { + Task *popped = task_tops[key_idx][0]; + SLLStackPop(task_tops[key_idx][0]); + SLLStackPush(free_task, popped); + } + } + } + scratch_end(scratch); + } + return result; +} + //- rjf: type graph traversal primitives internal E2_TypeKey @@ -582,7 +1142,9 @@ e2_type_key_direct(E2_TypeKey k) }break; case E2_TypeKeyKind_Cons: { - // TODO(rjf) + U64 id = e2_cons_type_id_from_key(k); + E2_ConsTypeNode *node = e2_cons_type_node_from_id(id); + result = node->params.direct; }break; case E2_TypeKeyKind_Reg:{}break; } @@ -608,10 +1170,7 @@ e2_type_key_owner(E2_TypeKey k) U32 owner_type_idx = type_node->constructed.owner_type_idx; result = e2_type_key_dbgi(e2_type_kind_from_rdi(type_node->kind), dbgi_num, owner_type_idx); }break; - case E2_TypeKeyKind_Cons: - { - // TODO(rjf) - }break; + case E2_TypeKeyKind_Cons:{}break; case E2_TypeKeyKind_Reg:{}break; } } @@ -719,17 +1278,59 @@ e2_coerced_type_key_from_operands(E2_TypeKey lhs, E2_TypeKey rhs) //~ rjf: Expression Constructors internal E2_Expr * -e2_expr(Arena *arena) +e2_expr(Arena *arena, E2_ExprKind kind) { E2_Expr *expr = push_array(arena, E2_Expr, 1); MemoryCopyStruct(expr, &e2_expr_nil); + expr->kind = kind; return expr; } -internal E2_Expr * -e2_expr_const_u64_or_smaller(Arena *arena, U64 u) +internal void +e2_expr_push_child_node(E2_Expr *parent, E2_ExprNode *node) { - E2_Expr *e = e2_expr(arena); + SLLQueuePush(parent->first_child, parent->last_child, node); + parent->child_count += 1; +} + +internal void +e2_expr_push_child(Arena *arena, E2_Expr *parent, E2_Expr *expr) +{ + E2_ExprNode *n = push_array(arena, E2_ExprNode, 1); + n->v = expr; + e2_expr_push_child_node(parent, n); +} + +internal void +e2_expr_push_child_node_front(E2_Expr *parent, E2_ExprNode *node) +{ + SLLQueuePushFront(parent->first_child, parent->last_child, node); + parent->child_count += 1; +} + +internal void +e2_expr_push_child_front(Arena *arena, E2_Expr *parent, E2_Expr *expr) +{ + E2_ExprNode *n = push_array(arena, E2_ExprNode, 1); + n->v = expr; + e2_expr_push_child_node_front(parent, n); +} + +//////////////////////////////// +//~ rjf: Expression Constructors + +internal E2_IRNode * +e2_irnode(Arena *arena) +{ + E2_IRNode *expr = push_array(arena, E2_IRNode, 1); + MemoryCopyStruct(expr, &e2_irnode_nil); + return expr; +} + +internal E2_IRNode * +e2_irnode_const_u64_or_smaller(Arena *arena, U64 u) +{ + E2_IRNode *e = e2_irnode(arena); { e->mode = E2_Mode_Value; e->val.u64 = u; @@ -757,10 +1358,10 @@ e2_expr_const_u64_or_smaller(Arena *arena, U64 u) return e; } -internal E2_Expr * -e2_expr_const_f32(Arena *arena, F32 f32) +internal E2_IRNode * +e2_irnode_const_f32(Arena *arena, F32 f32) { - E2_Expr *e = e2_expr(arena); + E2_IRNode *e = e2_irnode(arena); e->mode = E2_Mode_Value; e->val.f32 = f32; e->op = RDI_EvalOp_ConstU32; @@ -768,10 +1369,10 @@ e2_expr_const_f32(Arena *arena, F32 f32) return e; } -internal E2_Expr * -e2_expr_const_f64(Arena *arena, F64 f64) +internal E2_IRNode * +e2_irnode_const_f64(Arena *arena, F64 f64) { - E2_Expr *e = e2_expr(arena); + E2_IRNode *e = e2_irnode(arena); e->mode = E2_Mode_Value; e->val.f64 = f64; e->op = RDI_EvalOp_ConstU64; @@ -779,45 +1380,50 @@ e2_expr_const_f64(Arena *arena, F64 f64) return e; } -internal E2_Expr * -e2_expr_unary_op(Arena *arena, E2_TypeKey type_key, RDI_EvalOp op, E2_Expr *operand) +internal E2_IRNode * +e2_irnode_unary_op(Arena *arena, E2_TypeKey type_key, RDI_EvalOp op, E2_IRNode *operand) { - E2_Expr *e = e2_expr(arena); + E2_IRNode *e = e2_irnode(arena); e->type_key = type_key; e->op = op; e->mode = E2_Mode_Value; e->val.u512.u8[0] = e2_type_group_from_kind(e2_type_kind_from_key(type_key)); e->val.u512.u8[1] = 8*e2_byte_size_from_type_key(type_key); - e2_expr_push_child(e, operand); + e2_irnode_push_child(arena, e, operand); return e; } -internal E2_Expr * -e2_expr_binary_op(Arena *arena, E2_TypeKey type_key, RDI_EvalOp op, E2_Expr *lhs, E2_Expr *rhs) +internal E2_IRNode * +e2_irnode_binary_op(Arena *arena, E2_TypeKey type_key, RDI_EvalOp op, E2_IRNode *lhs, E2_IRNode *rhs) { - E2_Expr *e = e2_expr(arena); + E2_IRNode *e = e2_irnode(arena); + E2_TypeKey arith_type_key = type_key; + if(RDI_EvalOp_FirstLogical <= op && op <= RDI_EvalOp_LastLogical) + { + arith_type_key = lhs->type_key; + } e->type_key = type_key; e->op = op; e->mode = E2_Mode_Value; - e->val.u512.u8[0] = e2_type_group_from_kind(e2_type_kind_from_key(type_key)); - e->val.u512.u8[1] = 8*e2_byte_size_from_type_key(type_key); - e2_expr_push_child(e, lhs); - e2_expr_push_child(e, rhs); + e->val.u512.u8[0] = e2_type_group_from_kind(e2_type_kind_from_key(arith_type_key)); + e->val.u512.u8[1] = 8*e2_byte_size_from_type_key(arith_type_key); + e2_irnode_push_child(arena, e, lhs); + e2_irnode_push_child(arena, e, rhs); return e; } -internal E2_Expr * -e2_expr_resolve_to_value(Arena *arena, E2_Expr *expr) +internal E2_IRNode * +e2_irnode_resolve_to_value(Arena *arena, E2_IRNode *expr) { - E2_Expr *result = expr; + E2_IRNode *result = expr; // rjf: address evaluations of arrays -> create value of pointer to first element if(expr->mode == E2_Mode_Address && e2_type_kind_from_key(e2_type_key_undecorate(expr->type_key)) == E2_TypeKind_Array) { - result = e2_expr(arena); + result = e2_irnode(arena); result->type_key = e2_type_key_direct(e2_type_key_undecorate(expr->type_key)); result->mode = E2_Mode_Value; - e2_expr_push_child(result, expr); + e2_irnode_push_child(arena, result, expr); } // rjf: address evaluations -> read value from space @@ -825,12 +1431,12 @@ e2_expr_resolve_to_value(Arena *arena, E2_Expr *expr) { U64 memread_byte_size = e2_byte_size_from_type_key(expr->type_key); memread_byte_size = Min(64, memread_byte_size); - E2_Expr *memread_expr = e2_expr(arena); + E2_IRNode *memread_expr = e2_irnode(arena); memread_expr->op = RDI_EvalOp_MemRead; memread_expr->mode = E2_Mode_Value; memread_expr->type_key = expr->type_key; memread_expr->val.u64 = memread_byte_size; - e2_expr_push_child(memread_expr, expr); + e2_irnode_push_child(arena, memread_expr, expr); result = memread_expr; } @@ -848,36 +1454,36 @@ e2_expr_resolve_to_value(Arena *arena, E2_Expr *expr) } // rjf: mask(shift(expr, count), bitmask) - E2_Expr *shift_expr = e2_expr_binary_op(arena, e2_type_key_basic(E2_TypeKind_U64), RDI_EvalOp_RShift, result, e2_expr_const_u64_or_smaller(arena, shift)); - E2_Expr *mask_expr = e2_expr_binary_op(arena, e2_type_key_direct(core_type_key), RDI_EvalOp_BitAnd, shift_expr, e2_expr_const_u64_or_smaller(arena, valid_bits_mask)); + E2_IRNode *shift_expr = e2_irnode_binary_op(arena, e2_type_key_basic(E2_TypeKind_U64), RDI_EvalOp_RShift, result, e2_irnode_const_u64_or_smaller(arena, shift)); + E2_IRNode *mask_expr = e2_irnode_binary_op(arena, e2_type_key_direct(core_type_key), RDI_EvalOp_BitAnd, shift_expr, e2_irnode_const_u64_or_smaller(arena, valid_bits_mask)); result = mask_expr; } return result; } -internal E2_Expr * -e2_expr_truncate(Arena *arena, E2_Expr *expr, E2_TypeKey dst_type_key) +internal E2_IRNode * +e2_irnode_truncate(Arena *arena, E2_IRNode *expr, E2_TypeKey dst_type_key) { - E2_Expr *result = expr; + E2_IRNode *result = expr; E2_TypeKind dst_type_kind = e2_type_kind_from_key(dst_type_key); U64 dst_type_byte_size = e2_byte_size_from_type_key(dst_type_key); B32 dst_type_is_signed = e2_type_kind_is_signed(dst_type_kind); if(dst_type_byte_size < 64) { - result = e2_expr(arena); + result = e2_irnode(arena); result->type_key = dst_type_key; result->op = dst_type_is_signed ? RDI_EvalOp_TruncSigned : RDI_EvalOp_Trunc; result->val.u64 = dst_type_byte_size*8; - e2_expr_push_child(result, expr); + e2_irnode_push_child(arena, result, expr); } return result; } -internal E2_Expr * -e2_expr_convert_if_possible(Arena *arena, E2_Expr *expr, E2_TypeKey dst_type_key) +internal E2_IRNode * +e2_irnode_convert_if_possible(Arena *arena, E2_IRNode *expr, E2_TypeKey dst_type_key) { - E2_Expr *result = expr; + E2_IRNode *result = expr; { // rjf: unpack src / dst types E2_TypeKey src_type_key = expr->type_key; @@ -892,35 +1498,60 @@ e2_expr_convert_if_possible(Arena *arena, E2_Expr *expr, E2_TypeKey dst_type_key RDI_EvalConversionKind conversion_kind = rdi_eval_conversion_kind_from_typegroups(src_type_group, dst_type_group); if(conversion_kind == RDI_EvalConversionKind_Legal) { - result = e2_expr(arena); + result = e2_irnode(arena); result->mode = E2_Mode_Value; result->type_key = dst_type_key; result->op = RDI_EvalOp_Convert; result->val.u64 = src_type_group | (dst_type_group << 8); - e2_expr_push_child(result, expr); + e2_irnode_push_child(arena, result, expr); + } + + // rjf: no-op from src -> dst + if(conversion_kind == RDI_EvalConversionKind_Noop) + { + result->type_key = dst_type_key; } // rjf: if shrinking integer sizes, truncate if(dst_byte_size < src_byte_size && e2_type_kind_is_integer(dst_type_kind)) { - result = e2_expr_truncate(arena, expr, dst_type_key); + result = e2_irnode_truncate(arena, expr, dst_type_key); } } return result; } -internal void -e2_expr_push_child(E2_Expr *parent, E2_Expr *expr) +internal E2_IRNode * +e2_irnode_type(Arena *arena, E2_TypeKey type_key) { - SLLQueuePush_NZ(&e2_expr_nil, parent->first, parent->last, expr, next); + E2_IRNode *expr = e2_irnode(arena); + expr->type_key = type_key; + expr->mode = E2_Mode_Type; + return expr; +} + +internal void +e2_irnode_push_child_node(E2_IRNode *parent, E2_IRNodePtrNode *node) +{ + SLLQueuePush(parent->first_child, parent->last_child, node); + parent->child_count += 1; +} + +internal void +e2_irnode_push_child(Arena *arena, E2_IRNode *parent, E2_IRNode *expr) +{ + E2_IRNodePtrNode *n = push_array(arena, E2_IRNodePtrNode, 1); + n->v = expr; + e2_irnode_push_child_node(parent, n); } //////////////////////////////// //~ rjf: String -> Expression internal E2_Token -e2_token_from_string_off(String8 string, U64 start_off) +e2_token_from_string_off(E2_LangKind lang, String8 string, U64 start_off) { + E2_LangInfo *lang_info = &e2_lang_kind_info_table[lang]; E2_Token token = {E2_TokenKind_Null}; B32 identifier_tick_mode = 0; B32 comment_is_explicit_ender = 0; @@ -1059,9 +1690,9 @@ e2_token_from_string_off(String8 string, U64 start_off) token.range.max = off; String8 token_string = str8_substr(string, token.range); U64 biggest_match_size = 0; - for EachNonZeroEnumVal(E2_OpKind, k) + for EachIndex(idx, lang_info->expr_kind_parse_infos_count) { - String8 op_symbol = (e2_op_kind_info_table[k].pre.size != 0) ? e2_op_kind_info_table[k].pre : e2_op_kind_info_table[k].sep; + String8 op_symbol = (lang_info->expr_kind_parse_infos[idx].pre.size != 0) ? lang_info->expr_kind_parse_infos[idx].pre : lang_info->expr_kind_parse_infos[idx].sep; if(biggest_match_size < op_symbol.size && str8_match(op_symbol, str8_prefix(token_string, op_symbol.size), 0)) { biggest_match_size = op_symbol.size; @@ -1106,9 +1737,9 @@ e2_token_from_string_off(String8 string, U64 start_off) } internal U64 -e2_read_token(String8 string, U64 off, E2_Token *token_out) +e2_read_token(E2_LangKind lang, String8 string, U64 off, E2_Token *token_out) { - E2_Token token = e2_token_from_string_off(string, off); + E2_Token token = e2_token_from_string_off(lang, string, off); if(token_out != 0) { token_out[0] = token; @@ -1118,14 +1749,14 @@ e2_read_token(String8 string, U64 off, E2_Token *token_out) } internal B32 -e2_try_token(String8 string, E2_TokenKind kind, String8 expected_string, U64 *off_out, E2_Token *token_out) +e2_try_token(E2_LangKind lang, String8 string, E2_TokenKind kind, String8 expected_string, U64 *off_out, E2_Token *token_out) { B32 result = 0; U64 off = off_out[0]; for(;;) { E2_Token next_token = {E2_TokenKind_Null}; - off += e2_read_token(string, off, &next_token); + off += e2_read_token(lang, string, off, &next_token); if(next_token.kind == kind && (expected_string.size == 0 || str8_match(str8_substr(string, next_token.range), expected_string, 0))) { result = 1; @@ -1148,11 +1779,22 @@ e2_try_token(String8 string, E2_TokenKind kind, String8 expected_string, U64 *of } internal E2_Parse -e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E2_Expr *access_result, String8 string) +e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, E2_LangKind lang, String8 string) { U64 off = state->string_off; E2_Parse parse = {E2_ParseStatus_Error, .expr = &e2_expr_nil, .params_expr = &e2_expr_nil}; - E2_Expr *next_access_result = access_result; + E2_LangInfo *lang_info = &e2_lang_kind_info_table[lang]; + + //- rjf: set up initial parsing task + if(state->top_task == 0) + { + state->top_task = &state->start_task; + state->top_task->src_range = r1u64(0, string.size); + state->top_task->child_count_target = max_U64; + state->top_task->max_precedence = max_S64; + state->top_task->expected_splitter = s(","); + state->top_task->splitter_min_child_count = 0; + } //- rjf: parse & attach to top parsing task - if we don't have a top task // then it is just the result @@ -1160,15 +1802,17 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E for(B32 done = 0; !done;) { U64 start_off = off; - S64 max_precedence = state->top_task ? state->top_task->max_precedence : max_S64; - B32 need_new_expr = (expr == &e2_expr_nil && !state->caller_info_completes_task); + E2_ParseTask *start_task = state->top_task; + S64 max_precedence = state->top_task->max_precedence; + B32 type_ancestors = state->top_task->do_type_ancestors; + B32 need_new_expr = (expr == &e2_expr_nil && !type_ancestors && !state->caller_info_completes_task); E2_Token token = {0}; //- rjf: skip leading whitespace / comments for(;;) { E2_Token next_token = {0}; - U64 next_off = off + e2_read_token(string, off, &next_token); + U64 next_off = off + e2_read_token(lang, string, off, &next_token); if(next_token.kind == E2_TokenKind_Whitespace || next_token.kind == E2_TokenKind_Comment) { @@ -1181,7 +1825,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E } //- rjf: nested sub-expressions - if(need_new_expr && e2_try_token(string, E2_TokenKind_Symbol, s("("), &off, &token)) + if(need_new_expr && e2_try_token(lang, string, E2_TokenKind_Symbol, s("("), &off, &token)) { E2_ParseTask *task = state->free_task; if(task != 0) @@ -1201,28 +1845,35 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E } //- rjf: symbols (possible prefix unaries, *or* unexpected) - else if(need_new_expr && e2_try_token(string, E2_TokenKind_Symbol, s(""), &off, &token)) + else if(need_new_expr && e2_try_token(lang, string, E2_TokenKind_Symbol, s(""), &off, &token)) { + String8 token_string = str8_substr(string, token.range); + // rjf: string -> operator kind - E2_OpKind op_kind = E2_OpKind_Null; + B32 unexpected = !str8_match(token_string, state->top_task->expected_closer, 0); + E2_ExprKind expr_kind = E2_ExprKind_Null; String8 closer = {0}; + S64 precedence = 0; { - String8 token_string = str8_substr(string, token.range); - for EachNonZeroEnumVal(E2_OpKind, k) + for EachIndex(idx, lang_info->expr_kind_parse_infos_count) { - if(e2_op_kind_info_table[k].parse_kind == E2_OpParseKind_UnaryPrefix && - e2_op_kind_info_table[k].precedence <= max_precedence && - str8_match(token_string, e2_op_kind_info_table[k].pre, 0)) + if(lang_info->expr_kind_parse_infos[idx].parse_kind == E2_ExprParseKind_Prefix && + str8_match(token_string, lang_info->expr_kind_parse_infos[idx].pre, 0)) { - op_kind = k; - closer = e2_op_kind_info_table[k].post; - break; + unexpected = 0; + if(lang_info->expr_kind_parse_infos[idx].precedence <= max_precedence) + { + expr_kind = lang_info->expr_kind_parse_infos[idx].expr_kind; + closer = lang_info->expr_kind_parse_infos[idx].post; + precedence = lang_info->expr_kind_parse_infos[idx].precedence; + break; + } } } } // rjf: push task for operand - if(op_kind != E2_OpKind_Null) + if(expr_kind != E2_ExprKind_Null) { E2_ParseTask *task = state->free_task; if(task != 0) @@ -1234,71 +1885,189 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E task = push_array_no_zero(arena, E2_ParseTask, 1); } MemoryZeroStruct(task); - task->src_range = token.range; - task->child_count_target = 1; - task->op_kind = op_kind; - task->max_precedence = e2_op_kind_info_table[op_kind].precedence; - task->expected_closer = closer; + task->src_range = token.range; + task->child_count_target = e2_expr_kind_target_operand_count_table[expr_kind]; + task->expr_kind = expr_kind; + task->max_precedence = precedence; + task->expected_closer = closer; SLLStackPush(state->top_task, task); } // rjf: report unexpected symbols - if(op_kind == E2_OpKind_Null) + if(expr_kind == E2_ExprKind_Null && unexpected) { - String8 token_string = str8_substr(string, token.range); e2_msgf(arena, &parse.msgs, token.range, "Unexpected `%S`.", token_string); } } //- rjf: identifiers with an active dot op kind -> member access - else if(need_new_expr && state->top_task != 0 && state->top_task->op_kind == E2_OpKind_Dot && e2_try_token(string, E2_TokenKind_Identifier, s(""), &off, &token)) + else if(need_new_expr && state->top_task->expr_kind == E2_ExprKind_Dot && e2_try_token(lang, string, E2_TokenKind_Identifier, s(""), &off, &token)) { - // rjf: if we have a caller-provided access result, use that as our new expression - if(next_access_result != &e2_expr_nil) - { - expr = next_access_result; - next_access_result = &e2_expr_nil; - } - - // rjf: if we don't, ask for it - reset the offset to before this token - else - { - done = 1; - parse.status = E2_ParseStatus_MemberAccess; - parse.expr = state->top_task->first_child ? state->top_task->first_child->v : &e2_expr_nil; - parse.member_name = str8_substr(string, token.range); - state->last_requested_member_name = parse.member_name; - off = start_off; - } + expr = e2_expr(arena, E2_ExprKind_Dot); + expr->first_child = state->top_task->first_child; + expr->last_child = state->top_task->last_child; + expr->child_count = state->top_task->child_count; + expr->src_range = state->top_task->src_range; + expr->string = str8_substr(string, token.range); + E2_ParseTask *task = state->top_task; + SLLStackPop(state->top_task); + SLLStackPush(state->free_task, task); } - //- rjf: standalone identifiers (also could be operator keyword) - else if(need_new_expr && e2_try_token(string, E2_TokenKind_Identifier, s(""), &off, &token)) + //- rjf: standalone identifiers (also could be definition or operator keyword) + else if(need_new_expr && e2_try_token(lang, string, E2_TokenKind_Identifier, s(""), &off, &token)) { - String8 identifier = str8_substr(string, token.range); B32 identifier_mapped = 0; + B32 definitions_are_allowed = (state->top_task->expr_kind != E2_ExprKind_Macro); + + // rjf: pick the last identifier as the target identifier, collect the rest as qualifiers + String8List qualifiers = {0}; + String8 identifier = {0}; + { + E2_Token last_identifier_token = token; + for(;;) + { + U64 next_off_maybe = off; + E2_Token ext_identifier_token = {E2_TokenKind_Null}; + if(e2_try_token(lang, string, E2_TokenKind_Symbol, s("!"), &next_off_maybe, 0) || + (e2_try_token(lang, string, E2_TokenKind_Symbol, s("."), &next_off_maybe, 0) && + e2_try_token(lang, string, E2_TokenKind_Identifier, s(""), &next_off_maybe, &ext_identifier_token) && + e2_try_token(lang, string, E2_TokenKind_Symbol, s("!"), &next_off_maybe, 0))) + { + Rng1U64 qualifier_range = last_identifier_token.range; + if(dim_1u64(ext_identifier_token.range) > 0) + { + qualifier_range = union_1u64(qualifier_range, ext_identifier_token.range); + } + String8 qualifier = str8_substr(string, qualifier_range); + str8_list_push(arena, &qualifiers, qualifier); + if(!e2_try_token(lang, string, E2_TokenKind_Identifier, s(""), &next_off_maybe, &last_identifier_token)) + { + e2_msgf(arena, &parse.msgs, r1u64(off, off), "Expected identifier after qualifier `%S!`.", qualifier); + } + off = next_off_maybe; + } + else + { + identifier = str8_substr(string, last_identifier_token.range); + break; + } + } + } + + // rjf: first try to resolve as a definition + if(qualifiers.node_count == 0 && definitions_are_allowed && !identifier_mapped && e2_try_token(lang, string, E2_TokenKind_Symbol, s("="), &off, 0)) + { + identifier_mapped = 1; + E2_ParseTask *task = state->free_task; + if(task != 0) + { + SLLStackPop(state->free_task); + } + else + { + task = push_array_no_zero(arena, E2_ParseTask, 1); + } + MemoryZeroStruct(task); + task->src_range = token.range; + task->child_count_target = e2_expr_kind_target_operand_count_table[E2_ExprKind_Define]; + task->expr_kind = E2_ExprKind_Define; + task->max_precedence = max_S64; + task->identifier = identifier; + SLLStackPush(state->top_task, task); + } + + // rjf: try to resolve it as a macro definition + if(qualifiers.node_count == 0 && definitions_are_allowed && !identifier_mapped) + { + U64 macro_off = off; + if(e2_try_token(lang, string, E2_TokenKind_Symbol, s("("), ¯o_off, 0)) + { + U64 post_macro_paren_off = macro_off; + B32 looks_like_macro_def = 1; + U64 macro_arg_count = 0; + for(;macro_off < string.size;) + { + if(!e2_try_token(lang, string, E2_TokenKind_Identifier, s(""), ¯o_off, 0)) + { + looks_like_macro_def = 0; + break; + } + macro_arg_count += 1; + e2_try_token(lang, string, E2_TokenKind_Symbol, s(","), ¯o_off, 0); + if(e2_try_token(lang, string, E2_TokenKind_Symbol, s(")"), ¯o_off, 0)) + { + break; + } + } + if(looks_like_macro_def && e2_try_token(lang, string, E2_TokenKind_Symbol, s("="), ¯o_off, 0)) + { + // rjf: push task to fill macro definition + identifier_mapped = 1; + E2_ParseTask *task = state->free_task; + if(task != 0) + { + SLLStackPop(state->free_task); + } + else + { + task = push_array_no_zero(arena, E2_ParseTask, 1); + } + MemoryZeroStruct(task); + task->src_range = token.range; + task->child_count_target = e2_expr_kind_target_operand_count_table[E2_ExprKind_Macro]; + task->expr_kind = E2_ExprKind_Macro; + task->max_precedence = max_S64; + task->identifier = identifier; + SLLStackPush(state->top_task, task); + + // rjf: gather argument names + macro_off = post_macro_paren_off; + for(;macro_off < string.size;) + { + E2_Token next_token = {0}; + if(!e2_try_token(lang, string, E2_TokenKind_Identifier, s(""), ¯o_off, &next_token)) + { + break; + } + str8_list_push(arena, &task->macro_arg_names, str8_substr(string, next_token.range)); + e2_try_token(lang, string, E2_TokenKind_Symbol, s(","), ¯o_off, 0); + if(e2_try_token(lang, string, E2_TokenKind_Symbol, s(")"), ¯o_off, 0)) + { + break; + } + } + e2_try_token(lang, string, E2_TokenKind_Symbol, s("="), ¯o_off, 0); + + // rjf: advance offset to past '=' + off = macro_off; + } + } + } // rjf: first try to resolve as an operator name - if(!identifier_mapped) + if(qualifiers.node_count == 0 && !identifier_mapped) { // rjf: string -> op kind - E2_OpKind op_kind = E2_OpKind_Null; + E2_ExprKind expr_kind = E2_ExprKind_Null; + S64 precedence = 0; { String8 token_string = str8_substr(string, token.range); - for EachNonZeroEnumVal(E2_OpKind, k) + for EachIndex(idx, lang_info->expr_kind_parse_infos_count) { - if(e2_op_kind_info_table[k].parse_kind == E2_OpParseKind_UnaryPrefix && - e2_op_kind_info_table[k].precedence <= max_precedence && - str8_match(token_string, str8_skip_chop_whitespace(e2_op_kind_info_table[k].pre), 0)) + if(lang_info->expr_kind_parse_infos[idx].parse_kind == E2_ExprParseKind_Prefix && + lang_info->expr_kind_parse_infos[idx].precedence <= max_precedence && + str8_match(token_string, str8_skip_chop_whitespace(lang_info->expr_kind_parse_infos[idx].pre), 0)) { - op_kind = k; + expr_kind = lang_info->expr_kind_parse_infos[idx].expr_kind; + precedence = lang_info->expr_kind_parse_infos[idx].precedence; break; } } } // rjf: push task for operand - if(op_kind != E2_OpKind_Null) + if(expr_kind != E2_ExprKind_Null) { identifier_mapped = 1; E2_ParseTask *task = state->free_task; @@ -1312,77 +2081,159 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E } MemoryZeroStruct(task); task->src_range = token.range; - task->child_count_target = 1; - task->op_kind = op_kind; - task->max_precedence = e2_op_kind_info_table[op_kind].precedence; + task->child_count_target = e2_expr_kind_target_operand_count_table[expr_kind]; + task->expr_kind = expr_kind; + task->max_precedence = precedence; SLLStackPush(state->top_task, task); } } - // rjf: try to resolve via caller-provided named expressions - if(!identifier_mapped) + // rjf: try to resolve it as a macro argument + if(qualifiers.node_count == 0 && !identifier_mapped && state->top_task->expr_kind == E2_ExprKind_Macro) { - expr = e2_expr_from_name(expr_map, identifier); - identifier_mapped = (expr != &e2_expr_nil); + U64 macro_arg_num = 0; + { + U64 num = 1; + for(String8Node *n = state->top_task->macro_arg_names.first; n != 0; n = n->next, num += 1) + { + if(str8_match(n->string, identifier, 0)) + { + macro_arg_num = num; + break; + } + } + } + if(macro_arg_num != 0) + { + identifier_mapped = 1; + expr = e2_expr(arena, E2_ExprKind_MacroArg); + expr->macro_arg_num = (U32)macro_arg_num; + expr->string = identifier; + } } - // rjf: couldn't map -> ask caller to resolve it + // rjf: build this as a leaf identifier expression - ask the caller if it's a type if(!identifier_mapped) { - done = 1; - parse.status = E2_ParseStatus_MissedIdentifierResolution; - parse.missed_identifier = identifier; - off = start_off; + if(state->caller_request_count == 0) + { + done = 1; + parse.status = E2_ParseStatus_CheckIdentifierIsType; + parse.identifier = identifier; + parse.qualifiers = qualifiers; + off = start_off; + } + else + { + identifier_mapped = 1; + expr = e2_expr(arena, identifier_is_type ? E2_ExprKind_TypeIdentifier : E2_ExprKind_Identifier); + expr->string = identifier; + expr->qualifiers = qualifiers; + state->caller_request_count = 0; + } } } //- rjf: leaf numerics - else if(need_new_expr && e2_try_token(string, E2_TokenKind_Numeric, s(""), &off, &token)) + else if(need_new_expr && e2_try_token(lang, string, E2_TokenKind_Numeric, s(""), &off, &token)) { - U64 u64_val = 0; - String8 numeric_string = str8_substr(string, token.range); - U64 dot_pos = str8_find_needle(numeric_string, 0, s("."), 0); - B32 f_suffix = str8_match(str8_postfix(numeric_string, 1), s("f"), 0); - U64 colon_pos = str8_find_needle(numeric_string, 0, s(":"), 0); - if(dot_pos < numeric_string.size && f_suffix) + expr = e2_expr(arena, E2_ExprKind_Numeric); + expr->string = str8_substr(string, token.range); + } + + //- rjf: leaf string literals + else if(need_new_expr && e2_try_token(lang, string, E2_TokenKind_StringLiteral, s(""), &off, &token)) + { + String8 token_string = str8_substr(string, token.range); + String8 unquoted_string = str8_skip(str8_chop(token_string, 1), 1); + String8 raw_string = raw_from_escaped_str8(arena, unquoted_string); + expr = e2_expr(arena, E2_ExprKind_StringLiteral); + expr->string = raw_string; + } + + //- rjf: leaf character literals + else if(need_new_expr && e2_try_token(lang, string, E2_TokenKind_CharLiteral, s(""), &off, &token)) + { + String8 token_string = str8_substr(string, token.range); + String8 unquoted_string = str8_skip(str8_chop(token_string, 1), 1); + String8 raw_string = raw_from_escaped_str8(arena, unquoted_string); + expr = e2_expr(arena, E2_ExprKind_CharLiteral); + expr->string = raw_string; + } + + //- rjf: parse left-hand-side type operators + if(!state->caller_info_completes_task && (expr != &e2_expr_nil || state->top_task->do_type_ancestors)) + { + U64 trailing_symbol_off = off; + if(e2_try_token(lang, string, E2_TokenKind_Symbol, s(""), &trailing_symbol_off, &token)) { - expr = e2_expr_const_f32(arena, (F32)f64_from_str8(numeric_string)); - } - else if(dot_pos < numeric_string.size && !f_suffix) - { - expr = e2_expr_const_f64(arena, f64_from_str8(numeric_string)); - } - else if(colon_pos < numeric_string.size) - { - Temp scratch = scratch_begin(&arena, 1); - String8List parts = str8_split(scratch.arena, numeric_string, (U8 *)":", 1, 0); - U64 u64_idx = 0; - expr = e2_expr(arena); - for EachNode(n, String8Node, parts.first) + B32 parent_looking_for_type = (state->top_task->expr_kind != E2_ExprKind_Null && + state->top_task->child_count == 0 && + e2_expr_kind_is_first_operand_type_maybe_table[state->top_task->expr_kind]); + B32 is_expr_type = (e2_expr_kind_is_type_expr_table[expr->kind] || parent_looking_for_type); + if(is_expr_type || state->top_task->do_type_ancestors) { - if(u64_idx >= ArrayCount(expr->val.u512.u64)) + // rjf: token string -> operator kind + E2_ExprKind expr_kind = E2_ExprKind_Null; { - break; + String8 token_string = str8_substr(string, token.range); + for EachIndex(idx, lang_info->expr_kind_parse_infos_count) + { + E2_ExprKindParseInfo *op_info = &lang_info->expr_kind_parse_infos[idx]; + if(e2_expr_kind_is_type_expr_table[op_info->expr_kind] && + op_info->sep.size == 0 && + str8_match(op_info->post, token_string, 0)) + { + expr_kind = op_info->expr_kind; + break; + } + } } - try_u64_from_str8_c_rules(n->string, &expr->val.u512.u64[u64_idx]); - u64_idx += 1; - } - switch(u64_idx) - { - case 1:{expr->op = RDI_EvalOp_ConstU64; expr->type_key = e2_type_key_basic(E2_TypeKind_U64);}break; - case 2:{expr->op = RDI_EvalOp_ConstU128; expr->type_key = e2_type_key_basic(E2_TypeKind_U128);}break; - case 4:{expr->op = RDI_EvalOp_ConstU256; expr->type_key = e2_type_key_basic(E2_TypeKind_U256);}break; - case 8:{expr->op = RDI_EvalOp_ConstU512; expr->type_key = e2_type_key_basic(E2_TypeKind_U512);}break; - default: + + // rjf: got an expression kind -> build new operator node + if(expr_kind != E2_ExprKind_Null) { - e2_msgf(arena, &parse.msgs, token.range, "Invalid number of numeric portions specified (%I64u; must be 2, 4, or 8).", u64_idx); - }break; + E2_Expr *op_expr = e2_expr(arena, expr_kind); + if(expr != &e2_expr_nil) + { + e2_expr_push_child(arena, op_expr, expr); + } + expr = op_expr; + off = trailing_symbol_off; + } } - scratch_end(scratch); } - else if(try_u64_from_str8_c_rules(numeric_string, &u64_val)) + } + + //- rjf: if we're parsing a type, and we see a C-style type info recursion (to + // express ancestors of the current type), then we need to generate a task to + // descend and parse the ancestor type + if(!state->caller_info_completes_task && expr != &e2_expr_nil && (state->top_task->next_type_ancestor == 0 || state->top_task->next_type_ancestor == &e2_expr_nil)) + { + B32 parent_looking_for_type = (state->top_task->expr_kind != E2_ExprKind_Null && + state->top_task->child_count == 0 && + e2_expr_kind_is_first_operand_type_maybe_table[state->top_task->expr_kind]); + B32 is_expr_type = (e2_expr_kind_is_type_expr_table[expr->kind] || parent_looking_for_type); + if(is_expr_type && e2_try_token(lang, string, E2_TokenKind_Symbol, s("("), &off, &token)) { - expr = e2_expr_const_u64_or_smaller(arena, u64_val); + E2_ParseTask *task = state->free_task; + if(task != 0) + { + SLLStackPop(state->free_task); + } + else + { + task = push_array_no_zero(arena, E2_ParseTask, 1); + } + MemoryZeroStruct(task); + task->src_range = token.range; + task->expected_closer = s(")"); + task->child_count_target = 1; + task->max_precedence = max_S64; + task->do_type_ancestors = 1; + task->type_lhs = expr; + SLLStackPush(state->top_task, task); + expr = &e2_expr_nil; } } @@ -1390,38 +2241,42 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E if(!state->caller_info_completes_task && expr != &e2_expr_nil) { U64 trailing_symbol_off = off; - if(e2_try_token(string, E2_TokenKind_Symbol, s(""), &trailing_symbol_off, &token)) + if(e2_try_token(lang, string, E2_TokenKind_Symbol, s(""), &trailing_symbol_off, &token) || + e2_try_token(lang, string, E2_TokenKind_Identifier, s(""), &trailing_symbol_off, &token)) { + // rjf: determine if the expression is a type + B32 expr_is_type = e2_expr_kind_is_type_expr_table[expr->kind]; + // rjf: token string -> operator kind - E2_OpKind op_kind = E2_OpKind_Null; + E2_ExprKind expr_kind = E2_ExprKind_Null; U64 child_count_target = 2; String8 splitter = {0}; String8 closer = {0}; B32 splitter_is_required = 0; + S64 precedence = 0; + B32 reverse_children = 0; { String8 token_string = str8_substr(string, token.range); - for EachNonZeroEnumVal(E2_OpKind, k) + for EachIndex(idx, lang_info->expr_kind_parse_infos_count) { - E2_OpInfo *op_info = &e2_op_kind_info_table[k]; - if(op_info->precedence <= max_precedence && str8_match(token_string, op_info->sep, 0)) + E2_ExprKindParseInfo *expr_kind_info = &lang_info->expr_kind_parse_infos[idx]; + if(expr_kind_info->precedence <= max_precedence && + str8_match(token_string, expr_kind_info->sep, 0) && + (!expr_is_type || e2_expr_kind_allow_type_operands_table[expr_kind_info->expr_kind])) { - switch(op_info->parse_kind) - { - default:{}break; - case E2_OpParseKind_Binary: {child_count_target = 2;}break; - case E2_OpParseKind_Ternary: {child_count_target = 3; splitter_is_required = 1;}break; - case E2_OpParseKind_Call: {child_count_target = max_U64;}break; - } - splitter = op_info->chain; - closer = op_info->post; - op_kind = k; + splitter = expr_kind_info->chain; + closer = expr_kind_info->post; + expr_kind = expr_kind_info->expr_kind; + precedence = expr_kind_info->precedence; + reverse_children = expr_kind_info->reverse_children; + child_count_target = e2_expr_kind_target_operand_count_table[expr_kind]; break; } } } - // rjf: non-null operator kind -> build binary expression node, push task to fill other child - if(op_kind != E2_OpKind_Null) + // rjf: non-null operator kind -> push task to fill other children of this operator node + if(expr_kind != E2_ExprKind_Null) { E2_ParseTask *task = state->free_task; if(task != 0) @@ -1436,9 +2291,11 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E task->src_range = token.range; task->child_count = 0; task->child_count_target = child_count_target; - task->op_kind = op_kind; - task->max_precedence = e2_op_kind_info_table[op_kind].precedence; + task->expr_kind = expr_kind; + task->reverse_children = reverse_children; + task->max_precedence = precedence; task->expected_splitter = splitter; + task->splitter_min_child_count = 2; task->expected_closer = closer; task->splitter_is_required = splitter_is_required; SLLStackPush(state->top_task, task); @@ -1447,453 +2304,211 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E } } - //- rjf: attach formed expressions to parent task exprs; pop tasks when they're done. - // if we have no parent task, then we just fill the result, and we're done with the parse. + //- rjf: attach formed expressions to parent task; pop task if done. if(state->caller_info_completes_task || expr != &e2_expr_nil) { - //- rjf: pop finished expressions - for(;state->top_task != 0 && !done;) + //- rjf: if this task has a type ancestor: our finished expression is + // actually a descendant of that. we want to push our finished expression + // as a child, and then use the next type ancestor as the root expression + // which we push to our current task. + if(state->top_task->next_type_ancestor != 0 && state->top_task->next_type_ancestor != &e2_expr_nil) { - //- rjf: gather finished expression to current task; increase task child count - if(!state->caller_info_completes_task) + E2_Expr *type_descendant = expr; + expr = state->top_task->next_type_ancestor; + state->top_task->next_type_ancestor = &e2_expr_nil; + e2_expr_push_child_front(arena, expr, type_descendant); + } + + //- rjf: gather finished expression to current task; increase task child count + if(!state->caller_info_completes_task) + { + E2_ExprNode *n = push_array(arena, E2_ExprNode, 1); + if(state->top_task->reverse_children) { - E2_ExprNode *n = push_array(arena, E2_ExprNode, 1); - SLLQueuePush(state->top_task->first_child, state->top_task->last_child, n); - n->v = expr; - state->top_task->child_count += 1; + SLLQueuePushFront(state->top_task->first_child, state->top_task->last_child, n); } - - //- rjf: consume expected splitters - if(!state->caller_info_completes_task && - state->top_task->child_count > 1 && - state->top_task->expected_splitter.size != 0 && - state->top_task->child_count < state->top_task->child_count_target) - { - if(!e2_try_token(string, E2_TokenKind_Symbol, state->top_task->expected_splitter, &off, 0) && state->top_task->splitter_is_required) - { - e2_msgf(arena, &parse.msgs, r1u64(off, off), "Expected `%S`.", state->top_task->expected_splitter); - } - } - - //- rjf: consume expected closers - can terminate a child list early - B32 closer_found = 0; - if(!state->caller_info_completes_task && state->top_task->expected_closer.size != 0) - { - closer_found = e2_try_token(string, E2_TokenKind_Symbol, state->top_task->expected_closer, &off, 0); - } - - //- rjf: caller-provided info completes a task, *or* closer found, - // *or* task child count hits limit -> complete this subtree - if(state->caller_info_completes_task || closer_found || state->top_task->child_count >= state->top_task->child_count_target) - { - B32 completed_with_caller_info = state->caller_info_completes_task; - state->caller_info_completes_task = 0; - E2_ParseTask *completed_task = state->top_task; - - //- rjf: produced finished expression tree, given all children - E2_Expr *finished_root = &e2_expr_nil; - E2_Expr *lhs = completed_task->first_child ? completed_task->first_child->v : &e2_expr_nil; - E2_Expr *rhs = completed_task->last_child ? completed_task->last_child->v : &e2_expr_nil; - E2_Expr *mhs = completed_task->child_count == 3 ? lhs->next : &e2_expr_nil; - { - RDI_EvalOp op = RDI_EvalOp_Stop; - E2_TypeKey dst_type_key = {E2_TypeKeyKind_Null}; - switch(completed_task->op_kind) - { - //- rjf: no op -> just a sub-expression - default: - { - if(lhs != rhs) - { - finished_root = e2_expr(arena); - for EachNode(n, E2_ExprNode, completed_task->first_child) - { - e2_expr_push_child(finished_root, n->v); - } - } - else - { - finished_root = lhs; - } - }break; - - //- rjf: member accesses -> at this point we should have a resolved access - // expression submitted to us by the user (the parser reports it first) - // stored as the second child in the list. we just want to use the - // second expression - the initial child was just the accessed evaluation. - case E2_OpKind_Dot: - { - finished_root = rhs; - }break; - - //- rjf: indexes - case E2_OpKind_Index: - { - E2_TypeKey lhs_type_key = e2_type_key_undecorate(lhs->type_key); - E2_TypeKind lhs_type_kind = e2_type_kind_from_key(lhs_type_key); - - // rjf: array/pointer *address* indexes - if(lhs_type_kind == E2_TypeKind_Ptr || lhs_type_kind == E2_TypeKind_Array) - { - E2_TypeKey element_type_key = e2_type_key_direct(lhs_type_key); - U64 element_byte_size = e2_byte_size_from_type_key(element_type_key); - E2_Expr *base_addr_value_expr = e2_expr_resolve_to_value(arena, lhs); - E2_Expr *index_value_expr = e2_expr_resolve_to_value(arena, rhs); - E2_Expr *offset_value_expr = index_value_expr; - if(element_byte_size != 1) - { - E2_Expr *element_size_value_expr = e2_expr_const_u64_or_smaller(arena, element_byte_size); - offset_value_expr = e2_expr_binary_op(arena, e2_type_key_basic(E2_TypeKind_U64), RDI_EvalOp_Mul, index_value_expr, element_size_value_expr); - } - E2_Expr *element_addr_value_expr = e2_expr_binary_op(arena, e2_type_key_basic(E2_TypeKind_U64), RDI_EvalOp_Add, base_addr_value_expr, offset_value_expr); - element_addr_value_expr->type_key = element_type_key; - element_addr_value_expr->mode = E2_Mode_Address; - finished_root = element_addr_value_expr; - } - - // rjf: fallback case: ask the caller for fancy indexing operations - else - { - if(next_access_result != &e2_expr_nil) - { - finished_root = next_access_result; - next_access_result = &e2_expr_nil; - } - else - { - done = 1; - parse.status = E2_ParseStatus_IndexAccess; - parse.expr = lhs; - parse.params_expr = rhs; - state->caller_info_completes_task = 1; - } - } - }break; - - //- rjf: calls - case E2_OpKind_Call: - { - // TODO(rjf) - }break; - - //- rjf: sizeof - case E2_OpKind_SizeOf: - { - E2_TypeKey rhs_type_key = e2_type_key_unwrap(rhs->type_key, E2_TypeUnwrapFlag_AllDecorative & ~E2_TypeUnwrapFlag_Enums); - U64 rhs_size = e2_byte_size_from_type_key(rhs_type_key); - finished_root = e2_expr_const_u64_or_smaller(arena, rhs_size); - finished_root->type_key = e2_type_key_basic(E2_TypeKind_U64); - }break; - - //- rjf: dereferences - case E2_OpKind_Deref: - case E2_OpKind_DerefAsm: - { - // rjf: unpack operand - E2_TypeKey rhs_type_key = e2_type_key_unwrap(rhs->type_key, E2_TypeUnwrapFlag_AllDecorative & ~E2_TypeUnwrapFlag_Enums); - E2_TypeKind rhs_type_kind = e2_type_kind_from_key(rhs_type_key); - E2_TypeKey dereferenced_type = e2_type_key_unwrap(rhs->type_key, E2_TypeUnwrapFlag_All & ~E2_TypeUnwrapFlag_Enums); - U64 dereferenced_type_size = e2_byte_size_from_type_key(dereferenced_type); - - // rjf: collect info about malformed situations - B32 malformed = 0; - if(dereferenced_type_size == 0 && e2_type_kind_is_ptr_or_ref(rhs_type_kind)) - { - malformed = 1; - e2_msgf(arena, &parse.msgs, completed_task->src_range, "Cannot dereference pointers to zero-sized types."); - } - else if(dereferenced_type_size == 0 && rhs_type_kind == E2_TypeKind_Array) - { - malformed = 1; - e2_msgf(arena, &parse.msgs, completed_task->src_range, "Cannot dereference arrays of zero-sized types."); - } - else if(!e2_type_kind_is_ptr_or_ref(rhs_type_kind) && rhs_type_kind != E2_TypeKind_Array && - completed_task->op_kind != E2_OpKind_DerefAsm) - { - malformed = 1; - e2_msgf(arena, &parse.msgs, completed_task->src_range, "Cannot dereference this type."); - } - - // rjf: asm-style deref -> go to u64 if the address expression isn't - // an array or pointer - // - // TODO(rjf): probably we should get an absolute-fallback-architecture here, - // which ultimately is sourced by the selected thread, to choose an address-sized - // integer type. - // - if(!e2_type_kind_is_ptr_or_ref(rhs_type_kind) && rhs_type_kind != E2_TypeKind_Array && - completed_task->op_kind == E2_OpKind_DerefAsm) - { - dereferenced_type = e2_type_key_basic(E2_TypeKind_U64); - } - - // rjf: not malformed -> equip info - if(!malformed) - { - B32 is_only_type_eval = (rhs->mode == E2_Mode_Type); - E2_Expr *addr_value_tree = e2_expr_resolve_to_value(arena, rhs); - addr_value_tree->type_key = dereferenced_type; - if(is_only_type_eval) - { - addr_value_tree->mode = E2_Mode_Type; - } - else - { - addr_value_tree->mode = E2_Mode_Address; - } - finished_root = addr_value_tree; - } - }break; - - //- rjf: address-of - case E2_OpKind_Address: - { - // rjf: determine if malformed - B32 malformed = 0; - if(rhs->mode != E2_Mode_Address) - { - malformed = 1; - e2_msgf(arena, &parse.msgs, completed_task->src_range, "Cannot take an address of a value; it does not exist in memory."); - } - - // rjf: generate - if(!malformed) - { - Arch arch = e2_arch_from_type_key(rhs->type_key); - finished_root = rhs; - finished_root->mode = E2_Mode_Value; - finished_root->type_key = e2_type_key_cons_ptr(arch, rhs->type_key); - } - }break; - - //- rjf: positive (no-op, just take the right-hand-side) - case E2_OpKind_Pos: - { - finished_root = rhs; - }break; - - //- rjf: unary ops - case E2_OpKind_Neg: {op = RDI_EvalOp_Neg;}goto unary_op; - case E2_OpKind_LogNot: {op = RDI_EvalOp_LogNot;}goto unary_op; - case E2_OpKind_BitNot: {op = RDI_EvalOp_BitNot;}goto unary_op; - unary_op:; - { - // rjf: unpack operand - E2_TypeKey rhs_type_key = e2_type_key_unwrap(rhs->type_key, E2_TypeUnwrapFlag_AllDecorative & ~E2_TypeUnwrapFlag_Enums); - E2_TypeKind rhs_type_kind = e2_type_kind_from_key(rhs_type_key); - RDI_EvalTypeGroup rhs_type_group = e2_type_group_from_kind(rhs_type_kind); - - // rjf: determine if malformed - B32 malformed = 0; - if(!rdi_eval_op_typegroup_are_compatible(op, rhs_type_group)) - { - malformed = 1; - e2_msgf(arena, &parse.msgs, completed_task->src_range, "Cannot use this operator on this type."); - } - - // rjf: generate - if(!malformed) - { - E2_TypeKey dst_type = rhs_type_key; - if(RDI_EvalOp_FirstLogical <= op && op <= RDI_EvalOp_LastLogical) - { - dst_type = e2_type_key_basic(E2_TypeKind_Bool); - } - else if(rhs_type_kind == E2_TypeKind_Bool || - rhs_type_kind == E2_TypeKind_S8 || - rhs_type_kind == E2_TypeKind_S16 || - rhs_type_kind == E2_TypeKind_U8 || - rhs_type_kind == E2_TypeKind_U16) - { - dst_type = e2_type_key_basic(E2_TypeKind_S32); - } - E2_Expr *operand = e2_expr_resolve_to_value(arena, rhs); - E2_Expr *operand__converted = e2_expr_convert_if_possible(arena, operand, dst_type); - finished_root = e2_expr_unary_op(arena, dst_type, op, operand__converted); - } - }break; - - //- rjf: binary ops - case E2_OpKind_Mul: {op = RDI_EvalOp_Mul;}goto binary_op; - case E2_OpKind_Div: {op = RDI_EvalOp_Div;}goto binary_op; - case E2_OpKind_Mod: {op = RDI_EvalOp_Mod;}goto binary_op; - case E2_OpKind_Add: {op = RDI_EvalOp_Add;}goto binary_op; - case E2_OpKind_Sub: {op = RDI_EvalOp_Sub;}goto binary_op; - case E2_OpKind_LShift:{op = RDI_EvalOp_LShift;}goto binary_op; - case E2_OpKind_RShift:{op = RDI_EvalOp_RShift;}goto binary_op; - case E2_OpKind_Less: {op = RDI_EvalOp_Less;}goto binary_op; - case E2_OpKind_LtEq: {op = RDI_EvalOp_LsEq;}goto binary_op; - case E2_OpKind_Grtr: {op = RDI_EvalOp_Grtr;}goto binary_op; - case E2_OpKind_GrEq: {op = RDI_EvalOp_GrEq;}goto binary_op; - case E2_OpKind_EqEq: {op = RDI_EvalOp_EqEq;}goto binary_op; - case E2_OpKind_NtEq: {op = RDI_EvalOp_NtEq;}goto binary_op; - case E2_OpKind_BitAnd:{op = RDI_EvalOp_BitAnd;}goto binary_op; - case E2_OpKind_BitXor:{op = RDI_EvalOp_BitXor;}goto binary_op; - case E2_OpKind_BitOr: {op = RDI_EvalOp_BitOr;}goto binary_op; - case E2_OpKind_LogAnd:{op = RDI_EvalOp_LogAnd;}goto binary_op; - case E2_OpKind_LogOr: {op = RDI_EvalOp_LogOr;}goto binary_op; - binary_op:; - { - // rjf: resolve lhs / rhs to values - E2_Expr *lhs_value = e2_expr_resolve_to_value(arena, lhs); - E2_Expr *rhs_value = e2_expr_resolve_to_value(arena, rhs); - - // rjf: unpack resolved lhs/rhs - E2_TypeKey lhs_type_key = e2_type_key_undecorate(lhs_value->type_key); - E2_TypeKey rhs_type_key = e2_type_key_undecorate(rhs_value->type_key); - E2_TypeKind lhs_type_kind = e2_type_kind_from_key(lhs_type_key); - E2_TypeKind rhs_type_kind = e2_type_kind_from_key(rhs_type_key); - RDI_EvalTypeGroup lhs_type_group = e2_type_group_from_kind(lhs_type_kind); - RDI_EvalTypeGroup rhs_type_group = e2_type_group_from_kind(rhs_type_kind); - U64 lhs_type_size = e2_byte_size_from_type_key(lhs_type_key); - U64 rhs_type_size = e2_byte_size_from_type_key(rhs_type_key); - - // rjf: determine kind of arithmetic - typedef enum ArithKind - { - ArithKind_Normal, - ArithKind_PtrAdd, - ArithKind_PtrSub, - ArithKind_PtrArrayCompare, - ArithKind_TypeCompare, - } - ArithKind; - ArithKind arith_kind = ArithKind_Normal; - { - if(lhs_value->mode == E2_Mode_Type || rhs_value->mode == E2_Mode_Type) - { - arith_kind = ArithKind_TypeCompare; - } - else if((op == RDI_EvalOp_Add || op == RDI_EvalOp_Sub) && - ((e2_type_kind_is_ptr_or_ref(lhs_type_kind) && e2_type_kind_is_integer(rhs_type_kind)) || - (e2_type_kind_is_ptr_or_ref(rhs_type_kind) && e2_type_kind_is_integer(lhs_type_kind)))) - { - arith_kind = ArithKind_PtrAdd; - } - else if(op == RDI_EvalOp_Sub && e2_type_kind_is_ptr_or_ref(lhs_type_kind) && e2_type_kind_is_ptr_or_ref(rhs_type_kind)) - { - arith_kind = ArithKind_PtrSub; - } - else if((op == RDI_EvalOp_EqEq || op == RDI_EvalOp_NtEq) && - ((e2_type_kind_is_ptr_or_ref(lhs_type_kind) && rhs_type_kind == E2_TypeKind_Array) || - (e2_type_kind_is_ptr_or_ref(rhs_type_kind) && lhs_type_kind == E2_TypeKind_Array))) - { - arith_kind = ArithKind_PtrArrayCompare; - } - } - - // rjf: generate - switch(arith_kind) - { - //- rjf: normal arithmetic - case ArithKind_Normal: - { - // rjf: check malformation - B32 malformed = 0; - if(!rdi_eval_op_typegroup_are_compatible(op, lhs_type_group) || - !rdi_eval_op_typegroup_are_compatible(op, rhs_type_group)) - { - malformed = 1; - e2_msgf(arena, &parse.msgs, completed_task->src_range, "Cannot use this operator on this type."); - } - - // rjf: generate - if(!malformed) - { - E2_TypeKey dst_type_key = lhs_type_key; - if(RDI_EvalOp_FirstLogical <= op && op <= RDI_EvalOp_LastLogical) - { - dst_type_key = e2_type_key_basic(E2_TypeKind_Bool); - } - else - { - dst_type_key = e2_coerced_type_key_from_operands(lhs_type_key, rhs_type_key); - } - E2_Expr *lhs_converted = e2_expr_convert_if_possible(arena, lhs_value, dst_type_key); - E2_Expr *rhs_converted = e2_expr_convert_if_possible(arena, rhs_value, dst_type_key); - finished_root = e2_expr_binary_op(arena, dst_type_key, op, lhs_converted, rhs_converted); - } - }break; - - //- rjf: pointer-add arithmetic - case ArithKind_PtrAdd: - { - - }break; - - //- rjf: pointer-sub arithmetic - case ArithKind_PtrSub: - { - - }break; - - //- rjf: pointer-array comparisons - case ArithKind_PtrArrayCompare: - { - - }break; - - //- rjf: type comparisons - case ArithKind_TypeCompare: - { - - }break; - } - }break; - - //- rjf: definitions (TODO(rjf): tricky with the 'user resolves expressions' model - let's figure it out once basics are in) - case E2_OpKind_Define:{}break; - - //- rjf: conditionals - case E2_OpKind_Cond: - { - // TODO(rjf) - }break; - } - } - - //- rjf: report missing closers - if(!completed_with_caller_info && completed_task->expected_closer.size != 0 && !closer_found) - { - e2_msgf(arena, &parse.msgs, r1u64(off, off), "Expected `%S`.", completed_task->expected_closer); - } - - //- rjf: work on the parent of the finished expression next - expr = finished_root; - - //- rjf: release task - if(!done) - { - SLLStackPop(state->top_task); - SLLStackPush(state->free_task, completed_task); - } - } - - //- rjf: if the top task is not done -> break & continue - reset expression, because we need to parse another. else { - expr = &e2_expr_nil; - break; + SLLQueuePush(state->top_task->first_child, state->top_task->last_child, n); + } + n->v = expr; + state->top_task->child_count += 1; + } + + //- rjf: consume expected splitters + if(!state->caller_info_completes_task && + state->top_task->child_count >= state->top_task->splitter_min_child_count && + state->top_task->expected_splitter.size != 0 && + state->top_task->child_count < state->top_task->child_count_target) + { + if(!e2_try_token(lang, string, E2_TokenKind_Symbol, state->top_task->expected_splitter, &off, 0) && state->top_task->splitter_is_required) + { + e2_msgf(arena, &parse.msgs, r1u64(off, off), "Expected `%S`.", state->top_task->expected_splitter); } } - //- rjf: for the last finished expression, if we do not have any further tasks, - // this is our result. - if(state->top_task == 0 && off >= string.size) + //- rjf: consume expected closers - can terminate a child list early + B32 closer_found = 0; + if(!state->caller_info_completes_task && state->top_task->expected_closer.size != 0) { - parse.expr = expr; - if(parse.status == E2_ParseStatus_Error) + closer_found = e2_try_token(lang, string, E2_TokenKind_Symbol, state->top_task->expected_closer, &off, 0); + } + + //- rjf: determine if the task above a parenthesized sub-expression is expecting a type + B32 parent_looking_for_type = 0; + if(!state->top_task->do_type_ancestors && + state->top_task->next != 0 && + state->top_task->next->expr_kind != E2_ExprKind_Null && + state->top_task->next->child_count == 0 && + e2_expr_kind_is_first_operand_type_maybe_table[state->top_task->next->expr_kind]) + { + parent_looking_for_type = 1; + } + + //- rjf: determine if first child of a parenthesized sub-expression is a type + B32 parenthesized_child_is_type = 0; + if(!state->top_task->do_type_ancestors && !parent_looking_for_type && closer_found && state->top_task->child_count == 1) + { + E2_Expr *expr = state->top_task->first_child->v; + parenthesized_child_is_type = 1; + for(E2_Expr *e = expr, *next_e; e != &e2_expr_nil && parenthesized_child_is_type; e = next_e) { - parse.status = E2_ParseStatus_Good; + next_e = &e2_expr_nil; + if(e->kind != E2_ExprKind_TypeIdentifier && + e->kind != E2_ExprKind_Ptr) + { + parenthesized_child_is_type = 0; + } + if(parenthesized_child_is_type && e->child_count == 1) + { + next_e = e->first_child->v; + } + else if(e->child_count > 1) + { + parenthesized_child_is_type = 0; + } } - break; + } + + //- rjf: closer found, completed child count == 1 & completed child is a + // type evaluation - then this is cast. push a task to fill the operand + B32 is_cast = parenthesized_child_is_type; + if(!parent_looking_for_type && is_cast) + { + // rjf: pop the sub-task for the type expression + E2_Expr *type_expr = state->top_task->first_child->v; + { + E2_ParseTask *completed_task = state->top_task; + SLLStackPop(state->top_task); + SLLStackPush(state->free_task, completed_task); + } + + // rjf: push a new task for the castee operand + { + E2_ParseTask *task = state->free_task; + if(task != 0) + { + SLLStackPop(state->free_task); + } + else + { + task = push_array_no_zero(arena, E2_ParseTask, 1); + } + MemoryZeroStruct(task); + task->src_range = type_expr->src_range; + task->child_count = 1; + task->child_count_target = e2_expr_kind_target_operand_count_table[E2_ExprKind_Cast]; + task->expr_kind = E2_ExprKind_Cast; + task->max_precedence = 1; + SLLStackPush(state->top_task, task); + expr = &e2_expr_nil; + E2_ExprNode *child_n = push_array(arena, E2_ExprNode, 1); + SLLQueuePush(task->first_child, task->last_child, child_n); + child_n->v = type_expr; + } + } + + //- rjf: caller-provided info completes a task, *or* closer found, + // *or* task child count hits limit -> complete this subtree + else if(state->caller_info_completes_task || closer_found || state->top_task->child_count >= state->top_task->child_count_target) + { + B32 completed_with_caller_info = state->caller_info_completes_task; + state->caller_info_completes_task = 0; + E2_ParseTask *completed_task = state->top_task; + + //- rjf: produced finished expression tree, given all children + E2_Expr *finished_root = &e2_expr_nil; + { + if(completed_task->expr_kind == E2_ExprKind_Null && completed_task->child_count == 1) + { + finished_root = completed_task->first_child->v; + } + else + { + finished_root = e2_expr(arena, completed_task->expr_kind); + finished_root->first_child = completed_task->first_child; + finished_root->last_child = completed_task->last_child; + finished_root->child_count = completed_task->child_count; + finished_root->src_range = completed_task->src_range; + } + } + + //- rjf: report missing closers + if(!completed_with_caller_info && completed_task->expected_closer.size != 0 && !closer_found) + { + e2_msgf(arena, &parse.msgs, r1u64(off, off), "Expected `%S`.", completed_task->expected_closer); + } + + //- rjf: if this task parsed type ancestors: + // + // we need to pop this task, and return to working on the original left-hand-side + // type descendant. but, we need to remember the parsed type ancestors, so that + // we can apply them to the type once the right-hand-side has been parsed. + // + // otherwise (normal case): work on the parent of the finished expression next, + // using the finished expression as the completed child. + // + if(completed_task->do_type_ancestors) + { + expr = completed_task->type_lhs; + completed_task->next->next_type_ancestor = finished_root; + } + else + { + expr = finished_root; + } + + //- rjf: release task + if(!done) + { + SLLStackPop(state->top_task); + SLLStackPush(state->free_task, completed_task); + } + } + + //- rjf: if the top task is not done -> continue parsing. + // + // if we have an active op kind, reset expression, because we need to parse another. + // if we don't, we need to look for extensions of our current expression instead. + // + else if(state->top_task->expr_kind != E2_ExprKind_Null) + { + expr = &e2_expr_nil; } } - //- rjf: we're always done if there's nothing left to parse - if(off >= string.size) + //- rjf: we're done if we couldn't make any more progress. + if(start_task == state->top_task && (off >= string.size || (off == start_off && !e2_parse_status_is_caller_request(parse.status)))) { done = 1; + if(expr != &e2_expr_nil) + { + parse.expr = expr; + } + if(parse.status == E2_ParseStatus_Error) + { + parse.status = E2_ParseStatus_Good; + } } } @@ -1903,9 +2518,9 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E { if(state->caller_request_count > 0 && state->last_caller_request_string_off == state->string_off) { - if(parse.status == E2_ParseStatus_MissedIdentifierResolution) + if(parse.status == E2_ParseStatus_CheckIdentifierIsType) { - e2_msgf(arena, &parse.msgs, r1u64(state->string_off, state->string_off + parse.missed_identifier.size), "`%S` couldn't be resolved.", parse.missed_identifier); + e2_msgf(arena, &parse.msgs, r1u64(state->string_off, state->string_off + parse.identifier.size), "`%S` couldn't be resolved.", parse.identifier); } parse.status = E2_ParseStatus_Error; } @@ -1916,23 +2531,47 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E //- rjf: report incomplete tasks if(off >= string.size && !e2_parse_status_is_caller_request(parse.status)) { - for(E2_ParseTask *t = state->top_task; t != 0; t = t->next) + for(E2_ParseTask *t = state->top_task; t != 0 && t != &state->start_task; t = t->next) { - if(t->child_count == 1 && t->child_count_target == 2 && t->op_kind == E2_OpKind_Dot) + String8 symbol = {0}; + if(t->expr_kind != E2_ExprKind_Null) + { + for EachIndex(idx, lang_info->expr_kind_parse_infos_count) + { + if(lang_info->expr_kind_parse_infos[idx].expr_kind == t->expr_kind) + { + U64 target_operand_count = e2_expr_kind_target_operand_count_table[t->expr_kind]; + if(target_operand_count == 2) + { + symbol = lang_info->expr_kind_parse_infos[idx].sep; + } + else if(target_operand_count == 1) + { + symbol = lang_info->expr_kind_parse_infos[idx].pre; + } + break; + } + } + } + if(t->child_count == 1 && t->child_count_target == 2 && t->expr_kind == E2_ExprKind_Dot) { e2_msgf(arena, &parse.msgs, t->src_range, "Couldn't access member `%S`.", state->last_requested_member_name); } - else if(t->child_count == 2 && t->child_count_target == 2 && t->op_kind == E2_OpKind_Index) + else if(t->child_count == 2 && t->child_count_target == 2 && t->expr_kind == E2_ExprKind_Index) { e2_msgf(arena, &parse.msgs, t->src_range, "Couldn't index into this type."); } - else if(t->child_count == 1 && t->child_count_target == 2 && t->op_kind != E2_OpKind_Null) + else if(t->child_count >= 1 && t->expr_kind == E2_ExprKind_Call) { - e2_msgf(arena, &parse.msgs, t->src_range, "Expected expression after binary operator `%S`.", e2_op_kind_info_table[t->op_kind].sep); + e2_msgf(arena, &parse.msgs, t->src_range, "Couldn't call into this type."); } - else if(t->child_count == 0 && t->child_count_target == 1 && t->op_kind != E2_OpKind_Null) + else if(t->child_count == 1 && t->child_count_target == 2 && t->expr_kind != E2_ExprKind_Null && symbol.size != 0) { - e2_msgf(arena, &parse.msgs, t->src_range, "Expected expression after unary operator `%S`.", e2_op_kind_info_table[t->op_kind].pre); + e2_msgf(arena, &parse.msgs, t->src_range, "Expected expression after binary operator `%S`.", symbol); + } + else if(t->child_count == 0 && t->child_count_target == 1 && t->expr_kind != E2_ExprKind_Null && symbol.size != 0) + { + e2_msgf(arena, &parse.msgs, t->src_range, "Expected expression after unary operator `%S`.", symbol); } else { @@ -1950,10 +2589,906 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E } //////////////////////////////// -//~ rjf: Expression -> Bytecode +//~ rjf: Expression -> IR Tree + +internal E2_Compile +e2_compile_from_expr(Arena *arena, E2_CompileState *state, E2_IRNode *resolve_result, E2_Val compile_time_eval_result, E2_Expr *expr) +{ + E2_Compile compile = {E2_CompileStatus_Error}; + { + E2_IRNode *next_resolve_result = resolve_result; + + //- rjf: generate initial task + if(state->top_task == 0) + { + E2_CompileTask *task = push_array(arena, E2_CompileTask, 1); + SLLStackPush(state->top_task, task); + task->expr = expr; + } + + //- rjf: make progress on the compile task stack + for(B32 done = 0; !done && state->top_task != 0;) + { + E2_CompileTask *task = state->top_task; + E2_Expr *e = task->expr; + + //- rjf: try to push a task for the next child, if we have one + B32 new_child_task = 0; + { + E2_ExprNode *next_child_node = 0; + if(task->last_compiled_child_node != 0) + { + next_child_node = state->top_task->last_compiled_child_node->next; + } + else if(task->irtree_child_count == 0) + { + next_child_node = e->first_child; + } + if(next_child_node != 0) + { + new_child_task = 1; + task->last_compiled_child_node = next_child_node; + E2_Expr *next_child = next_child_node->v; + E2_CompileTask *new_task = state->free_task; + if(new_task != 0) + { + SLLStackPop(state->free_task); + } + else + { + new_task = push_array_no_zero(arena, E2_CompileTask, 1); + } + MemoryZeroStruct(new_task); + new_task->expr = next_child; + SLLStackPush(state->top_task, new_task); + } + } + + //- rjf: if all children are complete -> compile this node + E2_IRNode *finished_root = &e2_irnode_nil; + if(!new_child_task) + { + E2_IRNode *lhs = task->first_irtree_child ? task->first_irtree_child->v : &e2_irnode_nil; + E2_IRNode *rhs = task->last_irtree_child ? task->last_irtree_child->v : &e2_irnode_nil; + E2_IRNode *mhs = task->irtree_child_count == 3 ? task->first_irtree_child->next->v : &e2_irnode_nil; + { + RDI_EvalOp op = RDI_EvalOp_Stop; + E2_TypeKey dst_type_key = {E2_TypeKeyKind_Null}; + switch(e->kind) + { + //- rjf: no op -> just a sub-expression + default: + { + if(lhs != rhs) + { + finished_root = e2_irnode(arena); + finished_root->first_child = task->first_irtree_child; + finished_root->last_child = task->last_irtree_child; + finished_root->child_count = task->irtree_child_count; + } + else + { + finished_root = lhs; + } + }break; + + //- rjf: leaf identifiers + case E2_ExprKind_Identifier: + case E2_ExprKind_TypeIdentifier: + { + if(next_resolve_result != &e2_irnode_nil) + { + finished_root = next_resolve_result; + next_resolve_result = &e2_irnode_nil; + state->caller_request_count = 0; + } + else + { + done = 1; + compile.status = E2_CompileStatus_MissedIdentifierResolution; + compile.identifier = e->string; + compile.qualifiers = e->qualifiers; + } + }break; + + //- rjf: pointer type operators + case E2_ExprKind_Ptr: + { + Arch arch = state->selected_ctx.arch; + E2_TypeKey ptee_type_key = lhs->type_key; + E2_TypeKey ptr_type_key = e2_type_key_cons_ptr(arch, ptee_type_key); + finished_root = e2_irnode(arena); + finished_root->type_key = ptr_type_key; + }break; + + //- rjf: array type operator + case E2_ExprKind_Array: + { + if(state->caller_request_count == 0) + { + done = 1; + compile.status = E2_CompileStatus_CompileTimeEval; + compile.irtree = rhs; + } + else + { + U64 array_count = compile_time_eval_result.u64; + E2_TypeKey element_type_key = lhs->type_key; + E2_TypeKey array_type_key = e2_type_key_cons_array(element_type_key, array_count); + finished_root = e2_irnode(arena); + finished_root->type_key = array_type_key; + } + }break; + + //- rjf: function type operator + case E2_ExprKind_Function: + { + Temp scratch = scratch_begin(&arena, 1); + U64 params_count = 0; + E2_TypeKey *param_types = 0; + if(task->irtree_child_count != 0) + { + params_count = task->irtree_child_count-1; + param_types = push_array(scratch.arena, E2_TypeKey, params_count); + U64 idx = 0; + for(E2_IRNodePtrNode *n = task->first_irtree_child->next; n != 0; n = n->next, idx += 1) + { + param_types[idx] = n->v->type_key; + } + } + E2_TypeKey fn_type_key = e2_type_key_cons(E2_TypeKind_Function, .direct = lhs->type_key, .count = params_count, .param_types = param_types); + finished_root = e2_irnode(arena); + finished_root->type_key = fn_type_key; + scratch_end(scratch); + }break; + + //- rjf: const type operator + case E2_ExprKind_Const: + { + E2_TypeKey const_type_key = e2_type_key_cons(E2_TypeKind_Modifier, .flags = E2_TypeFlag_Const, .direct = lhs->type_key); + finished_root = e2_irnode(arena); + finished_root->type_key = const_type_key; + }break; + + //- rjf: volatile type operator + case E2_ExprKind_Volatile: + { + E2_TypeKey volatile_type_key = e2_type_key_cons(E2_TypeKind_Modifier, .flags = E2_TypeFlag_Volatile, .direct = lhs->type_key); + finished_root = e2_irnode(arena); + finished_root->type_key = volatile_type_key; + }break; + + //- rjf: unsigned type operator + case E2_ExprKind_Unsigned: + { + E2_TypeKey unsigned_type_key = lhs->type_key; + E2_TypeKey int_type_key = e2_type_key_undecorate(lhs->type_key); + E2_TypeKind int_type_kind = e2_type_kind_from_key(int_type_key); + switch(int_type_kind) + { + default:{}break; + case E2_TypeKind_S8: {unsigned_type_key = e2_type_key_basic(E2_TypeKind_U8);}break; + case E2_TypeKind_S16: {unsigned_type_key = e2_type_key_basic(E2_TypeKind_U16);}break; + case E2_TypeKind_S32: {unsigned_type_key = e2_type_key_basic(E2_TypeKind_U32);}break; + case E2_TypeKind_S64: {unsigned_type_key = e2_type_key_basic(E2_TypeKind_U64);}break; + case E2_TypeKind_S128:{unsigned_type_key = e2_type_key_basic(E2_TypeKind_U128);}break; + case E2_TypeKind_S256:{unsigned_type_key = e2_type_key_basic(E2_TypeKind_U256);}break; + case E2_TypeKind_S512:{unsigned_type_key = e2_type_key_basic(E2_TypeKind_U512);}break; + } + finished_root = e2_irnode(arena); + finished_root->type_key = unsigned_type_key; + }break; + + //- rjf: signed type operator + case E2_ExprKind_Signed: + { + E2_TypeKey signed_type_key = lhs->type_key; + E2_TypeKey int_type_key = e2_type_key_undecorate(lhs->type_key); + E2_TypeKind int_type_kind = e2_type_kind_from_key(int_type_key); + switch(int_type_kind) + { + default:{}break; + case E2_TypeKind_U8: {signed_type_key = e2_type_key_basic(E2_TypeKind_S8);}break; + case E2_TypeKind_U16: {signed_type_key = e2_type_key_basic(E2_TypeKind_S16);}break; + case E2_TypeKind_U32: {signed_type_key = e2_type_key_basic(E2_TypeKind_S32);}break; + case E2_TypeKind_U64: {signed_type_key = e2_type_key_basic(E2_TypeKind_S64);}break; + case E2_TypeKind_U128:{signed_type_key = e2_type_key_basic(E2_TypeKind_S128);}break; + case E2_TypeKind_U256:{signed_type_key = e2_type_key_basic(E2_TypeKind_S256);}break; + case E2_TypeKind_U512:{signed_type_key = e2_type_key_basic(E2_TypeKind_S512);}break; + } + finished_root = e2_irnode(arena); + finished_root->type_key = signed_type_key; + }break; + + //- rjf: leaf numerics + case E2_ExprKind_Numeric: + { + U64 u64_val = 0; + String8 numeric_string = e->string; + U64 dot_pos = str8_find_needle(numeric_string, 0, s("."), 0); + B32 f_suffix = str8_match(str8_postfix(numeric_string, 1), s("f"), 0); + U64 colon_pos = str8_find_needle(numeric_string, 0, s(":"), 0); + if(dot_pos < numeric_string.size && f_suffix) + { + finished_root = e2_irnode_const_f32(arena, (F32)f64_from_str8(numeric_string)); + } + else if(dot_pos < numeric_string.size && !f_suffix) + { + finished_root = e2_irnode_const_f64(arena, f64_from_str8(numeric_string)); + } + else if(colon_pos < numeric_string.size) + { + Temp scratch = scratch_begin(&arena, 1); + String8List parts = str8_split(scratch.arena, numeric_string, (U8 *)":", 1, 0); + U64 u64_idx = 0; + finished_root = e2_irnode(arena); + for EachNode(n, String8Node, parts.first) + { + if(u64_idx >= ArrayCount(finished_root->val.u512.u64)) + { + break; + } + try_u64_from_str8_c_rules(n->string, &finished_root->val.u512.u64[u64_idx]); + u64_idx += 1; + } + switch(u64_idx) + { + case 1:{finished_root->op = RDI_EvalOp_ConstU64; finished_root->type_key = e2_type_key_basic(E2_TypeKind_U64);}break; + case 2:{finished_root->op = RDI_EvalOp_ConstU128; finished_root->type_key = e2_type_key_basic(E2_TypeKind_U128);}break; + case 4:{finished_root->op = RDI_EvalOp_ConstU256; finished_root->type_key = e2_type_key_basic(E2_TypeKind_U256);}break; + case 8:{finished_root->op = RDI_EvalOp_ConstU512; finished_root->type_key = e2_type_key_basic(E2_TypeKind_U512);}break; + default: + { + e2_msgf(arena, &compile.msgs, e->src_range, "Invalid number of numeric portions specified (%I64u; must be 2, 4, or 8).", u64_idx); + }break; + } + scratch_end(scratch); + } + else if(try_u64_from_str8_c_rules(numeric_string, &u64_val)) + { + finished_root = e2_irnode_const_u64_or_smaller(arena, u64_val); + } + }break; + + //- rjf: string literals + case E2_ExprKind_StringLiteral: + { + finished_root = e2_irnode(arena); + finished_root->op = RDI_EvalOp_ConstString; + finished_root->string = e->string; + finished_root->type_key = e2_type_key_cons_array(e2_type_key_basic(E2_TypeKind_UChar8), e->string.size); + finished_root->val.u64 = e->string.size; + finished_root->mode = E2_Mode_Value; + }break; + + //- rjf: character literals + case E2_ExprKind_CharLiteral: + { + UnicodeDecode decode = utf8_decode(e->string.str, e->string.size); + finished_root = e2_irnode_const_u64_or_smaller(arena, (U64)decode.codepoint); + }break; + + //- rjf: member accesses -> at this point we should have a resolved access + // expression submitted to us by the user (the parser reports it first) + // stored as the second child in the list. we just want to use the + // second expression - the initial child was just the accessed evaluation. + case E2_ExprKind_Dot: + { + if(next_resolve_result != &e2_irnode_nil) + { + finished_root = next_resolve_result; + next_resolve_result = &e2_irnode_nil; + state->caller_request_count = 0; + } + else + { + done = 1; + compile.status = E2_CompileStatus_MemberAccess; + compile.irtree = lhs; + compile.member_name = e->string; + finished_root = rhs; + } + }break; + + //- rjf: indexes + case E2_ExprKind_Index: + { + E2_TypeKey lhs_type_key = e2_type_key_undecorate(lhs->type_key); + E2_TypeKind lhs_type_kind = e2_type_kind_from_key(lhs_type_key); + + // rjf: array/pointer *address* indexes + if(lhs_type_kind == E2_TypeKind_Ptr || lhs_type_kind == E2_TypeKind_Array) + { + E2_TypeKey element_type_key = e2_type_key_direct(lhs_type_key); + U64 element_byte_size = e2_byte_size_from_type_key(element_type_key); + E2_IRNode *base_addr_value_ir = e2_irnode_resolve_to_value(arena, lhs); + E2_IRNode *index_value_ir = e2_irnode_resolve_to_value(arena, rhs); + E2_IRNode *offset_value_ir = index_value_ir; + if(element_byte_size != 1) + { + E2_IRNode *element_size_value_ir = e2_irnode_const_u64_or_smaller(arena, element_byte_size); + offset_value_ir = e2_irnode_binary_op(arena, e2_type_key_basic(E2_TypeKind_U64), RDI_EvalOp_Mul, index_value_ir, element_size_value_ir); + } + E2_IRNode *element_addr_value_ir = e2_irnode_binary_op(arena, e2_type_key_basic(E2_TypeKind_U64), RDI_EvalOp_Add, base_addr_value_ir, offset_value_ir); + element_addr_value_ir->type_key = element_type_key; + element_addr_value_ir->mode = E2_Mode_Address; + finished_root = element_addr_value_ir; + } + + // rjf: fallback case: ask the caller for fancy indexing operations + else + { + if(next_resolve_result != &e2_irnode_nil) + { + finished_root = next_resolve_result; + next_resolve_result = &e2_irnode_nil; + state->caller_request_count = 0; + } + else + { + done = 1; + compile.status = E2_CompileStatus_IndexAccess; + compile.irtree = lhs; + compile.params_irtree = rhs; + } + } + }break; + + //- rjf: calls + case E2_ExprKind_Call: + { + // rjf: calls of types redirect to casts + if(lhs->mode == E2_Mode_Type) + { + goto cast; + } + + // rjf: fallback => ask the caller to resolve the call + else + { + if(next_resolve_result != &e2_irnode_nil) + { + finished_root = next_resolve_result; + next_resolve_result = &e2_irnode_nil; + state->caller_request_count = 0; + } + else + { + E2_IRNode *params_irtree = e2_irnode(arena); + params_irtree->first_child = task->first_irtree_child->next; + params_irtree->last_child = task->last_irtree_child; + params_irtree->child_count = task->irtree_child_count-1; + done = 1; + compile.status = E2_CompileStatus_Call; + compile.irtree = lhs; + compile.params_irtree = params_irtree; + } + } + }break; + + //- rjf: sizeof + case E2_ExprKind_SizeOf: + { + E2_TypeKey rhs_type_key = e2_type_key_unwrap(rhs->type_key, E2_TypeUnwrapFlag_AllDecorative & ~E2_TypeUnwrapFlag_Enums); + U64 rhs_size = e2_byte_size_from_type_key(rhs_type_key); + finished_root = e2_irnode_const_u64_or_smaller(arena, rhs_size); + finished_root->type_key = e2_type_key_basic(E2_TypeKind_U64); + }break; + + //- rjf: typeof + case E2_ExprKind_TypeOf: + { + E2_TypeKey rhs_type_key = e2_type_key_unwrap(rhs->type_key, E2_TypeUnwrapFlag_AllDecorative & ~E2_TypeUnwrapFlag_Enums); + finished_root = e2_irnode(arena); + finished_root->mode = E2_Mode_Type; + finished_root->type_key = rhs_type_key; + }break; + + //- rjf: casts + case E2_ExprKind_Cast: + cast:; + { + E2_IRNode *cast_type_ir = lhs; + E2_TypeKey cast_type_key = cast_type_ir->type_key; + if(e2_type_key_match(e2_type_key_zero(), cast_type_key)) + { + e2_msgf(arena, &compile.msgs, e->src_range, "Cannot cast to this type."); + } + E2_IRNode *castee_ir = rhs; + finished_root = e2_irnode_convert_if_possible(arena, castee_ir, cast_type_key); + }break; + + //- rjf: dereferences + case E2_ExprKind_Deref: + case E2_ExprKind_DerefAsm: + { + // rjf: unpack operand + E2_TypeKey rhs_type_key = e2_type_key_unwrap(rhs->type_key, E2_TypeUnwrapFlag_AllDecorative & ~E2_TypeUnwrapFlag_Enums); + E2_TypeKind rhs_type_kind = e2_type_kind_from_key(rhs_type_key); + E2_TypeKey dereferenced_type = e2_type_key_unwrap(rhs->type_key, E2_TypeUnwrapFlag_All & ~E2_TypeUnwrapFlag_Enums); + U64 dereferenced_type_size = e2_byte_size_from_type_key(dereferenced_type); + + // rjf: collect info about malformed situations + B32 malformed = 0; + if(dereferenced_type_size == 0 && e2_type_kind_is_ptr_or_ref(rhs_type_kind)) + { + malformed = 1; + e2_msgf(arena, &compile.msgs, e->src_range, "Cannot dereference pointers to zero-sized types."); + } + else if(dereferenced_type_size == 0 && rhs_type_kind == E2_TypeKind_Array) + { + malformed = 1; + e2_msgf(arena, &compile.msgs, e->src_range, "Cannot dereference arrays of zero-sized types."); + } + else if(!e2_type_kind_is_ptr_or_ref(rhs_type_kind) && rhs_type_kind != E2_TypeKind_Array && + e->kind != E2_ExprKind_DerefAsm) + { + malformed = 1; + e2_msgf(arena, &compile.msgs, e->src_range, "Cannot dereference this type."); + } + + // rjf: asm-style deref -> go to u64 if the address expression isn't + // an array or pointer + // + // TODO(rjf): probably we should get an absolute-fallback-architecture here, + // which ultimately is sourced by the selected thread, to choose an address-sized + // integer type. + // + if(!e2_type_kind_is_ptr_or_ref(rhs_type_kind) && rhs_type_kind != E2_TypeKind_Array && + e->kind == E2_ExprKind_DerefAsm) + { + dereferenced_type = e2_type_key_basic(E2_TypeKind_U64); + } + + // rjf: not malformed -> equip info + if(!malformed) + { + B32 is_only_type_eval = (rhs->mode == E2_Mode_Type); + E2_IRNode *addr_value_tree = e2_irnode_resolve_to_value(arena, rhs); + addr_value_tree->type_key = dereferenced_type; + if(is_only_type_eval) + { + addr_value_tree->mode = E2_Mode_Type; + } + else + { + addr_value_tree->mode = E2_Mode_Address; + } + finished_root = addr_value_tree; + } + }break; + + //- rjf: address-of + case E2_ExprKind_Address: + { + // rjf: determine if malformed + B32 malformed = 0; + if(rhs->mode != E2_Mode_Address) + { + malformed = 1; + e2_msgf(arena, &compile.msgs, e->src_range, "Cannot take an address of a value; it does not exist in memory."); + } + + // rjf: generate + if(!malformed) + { + Arch arch = e2_arch_from_type_key(rhs->type_key); + finished_root = rhs; + finished_root->mode = E2_Mode_Value; + finished_root->type_key = e2_type_key_cons_ptr(arch, rhs->type_key); + } + }break; + + //- rjf: positive (no-op, just take the right-hand-side) + case E2_ExprKind_Pos: + { + finished_root = rhs; + }break; + + //- rjf: unary ops + case E2_ExprKind_Neg: {op = RDI_EvalOp_Neg;}goto unary_op; + case E2_ExprKind_LogNot: {op = RDI_EvalOp_LogNot;}goto unary_op; + case E2_ExprKind_BitNot: {op = RDI_EvalOp_BitNot;}goto unary_op; + unary_op:; + { + // rjf: unpack operand + E2_TypeKey rhs_type_key = e2_type_key_unwrap(rhs->type_key, E2_TypeUnwrapFlag_AllDecorative & ~E2_TypeUnwrapFlag_Enums); + E2_TypeKind rhs_type_kind = e2_type_kind_from_key(rhs_type_key); + RDI_EvalTypeGroup rhs_type_group = e2_type_group_from_kind(rhs_type_kind); + + // rjf: determine if malformed + B32 malformed = 0; + if(!rdi_eval_op_typegroup_are_compatible(op, rhs_type_group)) + { + malformed = 1; + e2_msgf(arena, &compile.msgs, e->src_range, "Cannot use this operator on this type."); + } + + // rjf: generate + if(!malformed) + { + E2_TypeKey dst_type = rhs_type_key; + if(RDI_EvalOp_FirstLogical <= op && op <= RDI_EvalOp_LastLogical) + { + dst_type = e2_type_key_basic(E2_TypeKind_Bool); + } + else if(rhs_type_kind == E2_TypeKind_Bool || + rhs_type_kind == E2_TypeKind_S8 || + rhs_type_kind == E2_TypeKind_S16 || + rhs_type_kind == E2_TypeKind_U8 || + rhs_type_kind == E2_TypeKind_U16) + { + dst_type = e2_type_key_basic(E2_TypeKind_S32); + } + E2_IRNode *operand = e2_irnode_resolve_to_value(arena, rhs); + E2_IRNode *operand__converted = e2_irnode_convert_if_possible(arena, operand, dst_type); + finished_root = e2_irnode_unary_op(arena, dst_type, op, operand__converted); + } + }break; + + //- rjf: binary ops + case E2_ExprKind_Mul: {op = RDI_EvalOp_Mul;}goto binary_op; + case E2_ExprKind_Div: {op = RDI_EvalOp_Div;}goto binary_op; + case E2_ExprKind_Mod: {op = RDI_EvalOp_Mod;}goto binary_op; + case E2_ExprKind_Add: {op = RDI_EvalOp_Add;}goto binary_op; + case E2_ExprKind_Sub: {op = RDI_EvalOp_Sub;}goto binary_op; + case E2_ExprKind_LShift:{op = RDI_EvalOp_LShift;}goto binary_op; + case E2_ExprKind_RShift:{op = RDI_EvalOp_RShift;}goto binary_op; + case E2_ExprKind_Less: {op = RDI_EvalOp_Less;}goto binary_op; + case E2_ExprKind_LtEq: {op = RDI_EvalOp_LsEq;}goto binary_op; + case E2_ExprKind_Grtr: {op = RDI_EvalOp_Grtr;}goto binary_op; + case E2_ExprKind_GrEq: {op = RDI_EvalOp_GrEq;}goto binary_op; + case E2_ExprKind_EqEq: {op = RDI_EvalOp_EqEq;}goto binary_op; + case E2_ExprKind_NtEq: {op = RDI_EvalOp_NtEq;}goto binary_op; + case E2_ExprKind_BitAnd:{op = RDI_EvalOp_BitAnd;}goto binary_op; + case E2_ExprKind_BitXor:{op = RDI_EvalOp_BitXor;}goto binary_op; + case E2_ExprKind_BitOr: {op = RDI_EvalOp_BitOr;}goto binary_op; + case E2_ExprKind_LogAnd:{op = RDI_EvalOp_LogAnd;}goto binary_op; + case E2_ExprKind_LogOr: {op = RDI_EvalOp_LogOr;}goto binary_op; + binary_op:; + { + // rjf: resolve lhs / rhs to values + E2_IRNode *lhs_value = e2_irnode_resolve_to_value(arena, lhs); + E2_IRNode *rhs_value = e2_irnode_resolve_to_value(arena, rhs); + + // rjf: unpack resolved lhs/rhs + E2_TypeKey lhs_type_key = e2_type_key_undecorate(lhs_value->type_key); + E2_TypeKey rhs_type_key = e2_type_key_undecorate(rhs_value->type_key); + E2_TypeKind lhs_type_kind = e2_type_kind_from_key(lhs_type_key); + E2_TypeKind rhs_type_kind = e2_type_kind_from_key(rhs_type_key); + RDI_EvalTypeGroup lhs_type_group = e2_type_group_from_kind(lhs_type_kind); + RDI_EvalTypeGroup rhs_type_group = e2_type_group_from_kind(rhs_type_kind); + U64 lhs_type_size = e2_byte_size_from_type_key(lhs_type_key); + U64 rhs_type_size = e2_byte_size_from_type_key(rhs_type_key); + + // rjf: determine kind of arithmetic + typedef enum ArithKind + { + ArithKind_Normal, + ArithKind_PtrAdd, + ArithKind_PtrSub, + ArithKind_PtrArrayCompare, + ArithKind_TypeCompare, + } + ArithKind; + ArithKind arith_kind = ArithKind_Normal; + { + if(lhs_value->mode == E2_Mode_Type || rhs_value->mode == E2_Mode_Type) + { + arith_kind = ArithKind_TypeCompare; + } + else if((op == RDI_EvalOp_Add || op == RDI_EvalOp_Sub) && + ((e2_type_kind_is_ptr_or_ref(lhs_type_kind) && e2_type_kind_is_integer(rhs_type_kind)) || + (e2_type_kind_is_ptr_or_ref(rhs_type_kind) && e2_type_kind_is_integer(lhs_type_kind)))) + { + arith_kind = ArithKind_PtrAdd; + } + else if(op == RDI_EvalOp_Sub && e2_type_kind_is_ptr_or_ref(lhs_type_kind) && e2_type_kind_is_ptr_or_ref(rhs_type_kind)) + { + arith_kind = ArithKind_PtrSub; + } + else if((op == RDI_EvalOp_EqEq || op == RDI_EvalOp_NtEq) && + ((e2_type_kind_is_ptr_or_ref(lhs_type_kind) && rhs_type_kind == E2_TypeKind_Array) || + (e2_type_kind_is_ptr_or_ref(rhs_type_kind) && lhs_type_kind == E2_TypeKind_Array))) + { + arith_kind = ArithKind_PtrArrayCompare; + } + } + + // rjf: generate + switch(arith_kind) + { + //- rjf: normal arithmetic + case ArithKind_Normal: + { + // rjf: check malformation + B32 malformed = 0; + if(!rdi_eval_op_typegroup_are_compatible(op, lhs_type_group) || + !rdi_eval_op_typegroup_are_compatible(op, rhs_type_group)) + { + malformed = 1; + e2_msgf(arena, &compile.msgs, e->src_range, "Cannot use this operator on this type."); + } + + // rjf: generate + if(!malformed) + { + E2_TypeKey dst_type_key = lhs_type_key; + E2_IRNode *lhs_converted = lhs_value; + E2_IRNode *rhs_converted = rhs_value; + if(RDI_EvalOp_FirstLogical <= op && op <= RDI_EvalOp_LastLogical) + { + dst_type_key = e2_type_key_basic(E2_TypeKind_Bool); + E2_TypeKey coerced_type_key = e2_coerced_type_key_from_operands(lhs_type_key, rhs_type_key); + lhs_converted = e2_irnode_convert_if_possible(arena, lhs_value, coerced_type_key); + rhs_converted = e2_irnode_convert_if_possible(arena, rhs_value, coerced_type_key); + } + else + { + dst_type_key = e2_coerced_type_key_from_operands(lhs_type_key, rhs_type_key); + lhs_converted = e2_irnode_convert_if_possible(arena, lhs_value, dst_type_key); + rhs_converted = e2_irnode_convert_if_possible(arena, rhs_value, dst_type_key); + } + finished_root = e2_irnode_binary_op(arena, dst_type_key, op, lhs_converted, rhs_converted); + } + }break; + + //- rjf: pointer-add arithmetic + case ArithKind_PtrAdd: + { + // rjf: l/r -> ptr + int + E2_IRNode *ptr_ir = lhs; + E2_IRNode *int_ir = rhs; + if(e2_type_kind_is_ptr_or_ref(rhs_type_kind)) + { + ptr_ir = rhs; + int_ir = lhs; + } + + // rjf: unpack ptee type + E2_TypeKey ptee_type_key = e2_type_key_direct(e2_type_key_undecorate(ptr_ir->type_key)); + U64 ptee_byte_size = e2_byte_size_from_type_key(ptee_type_key); + + // rjf: form tree + E2_IRNode *base_addr_ir = e2_irnode_resolve_to_value(arena, ptr_ir); + E2_IRNode *index_ir = e2_irnode_resolve_to_value(arena, int_ir); + E2_IRNode *offset_ir = index_ir; + if(ptee_byte_size > 1) + { + E2_IRNode *size_ir = e2_irnode_const_u64_or_smaller(arena, ptee_byte_size); + offset_ir = e2_irnode_binary_op(arena, e2_type_key_basic(E2_TypeKind_U64), RDI_EvalOp_Mul, index_ir, size_ir); + } + E2_IRNode *addr_value_ir = e2_irnode_binary_op(arena, e2_type_key_basic(E2_TypeKind_U64), RDI_EvalOp_Add, base_addr_ir, offset_ir); + addr_value_ir->mode = E2_Mode_Address; + addr_value_ir->type_key = ptr_ir->type_key; + finished_root = addr_value_ir; + }break; + + //- rjf: pointer-sub arithmetic + case ArithKind_PtrSub: + { + E2_IRNode *lhs_addr_value_ir = e2_irnode_resolve_to_value(arena, lhs); + E2_IRNode *rhs_addr_value_ir = e2_irnode_resolve_to_value(arena, rhs); + E2_IRNode *sub_ir = e2_irnode_binary_op(arena, e2_type_key_basic(E2_TypeKind_U64), RDI_EvalOp_Sub, lhs_addr_value_ir, rhs_addr_value_ir); + E2_IRNode *sub_divided_ir = sub_ir; + E2_TypeKey lhs_ptr_type = e2_type_key_undecorate(lhs->type_key); + E2_TypeKey rhs_ptr_type = e2_type_key_undecorate(rhs->type_key); + E2_TypeKey lhs_ptee_type = e2_type_key_direct(lhs_ptr_type); + E2_TypeKey rhs_ptee_type = e2_type_key_direct(rhs_ptr_type); + if(e2_type_deep_match(lhs_ptee_type, rhs_ptee_type)) + { + U64 ptee_byte_size = e2_byte_size_from_type_key(lhs_ptee_type); + if(ptee_byte_size != 0) + { + sub_divided_ir = e2_irnode_binary_op(arena, e2_type_key_basic(E2_TypeKind_U64), RDI_EvalOp_Div, sub_ir, e2_irnode_const_u64_or_smaller(arena, ptee_byte_size)); + } + } + finished_root = sub_divided_ir; + }break; + + //- rjf: pointer-array comparisons + case ArithKind_PtrArrayCompare: + { + // TODO(rjf) + }break; + + //- rjf: type comparisons + case ArithKind_TypeCompare: + { + if(e->kind == E2_ExprKind_EqEq || + e->kind == E2_ExprKind_NtEq) + { + E2_TypeKey lhs_type_key = e2_type_key_unwrap(lhs->type_key, E2_TypeUnwrapFlag_Meta); + E2_TypeKey rhs_type_key = e2_type_key_unwrap(rhs->type_key, E2_TypeUnwrapFlag_Meta); + B32 types_match = e2_type_deep_match(lhs_type_key, rhs_type_key); + B32 result = types_match; + if(e->kind == E2_ExprKind_NtEq) + { + result = !result; + } + finished_root = e2_irnode_const_u64_or_smaller(arena, (U64)result); + finished_root->type_key = e2_type_key_basic(E2_TypeKind_Bool); + } + else + { + e2_msgf(arena, &compile.msgs, e->src_range, "Cannot use this operator on types."); + } + }break; + } + }break; + + //- rjf: definitions + case E2_ExprKind_Define: + { + if(state->caller_request_count > 0) + { + finished_root = lhs; + } + else + { + done = 1; + compile.status = E2_CompileStatus_NewIdentifierDefinition; + compile.irtree = lhs; + compile.identifier = e->string; + } + }break; + + //- rjf: conditionals + case E2_ExprKind_Cond: + { + // rjf: unpack operands + E2_IRNode *condition_ir = lhs; + E2_IRNode *pass_ir = mhs; + E2_IRNode *fail_ir = rhs; + E2_TypeKey type_key = e2_coerced_type_key_from_operands(pass_ir->type_key, fail_ir->type_key); + + // rjf: determine if the mhs/rhs types force us to do compile-time evaluation of the condition + B32 compile_time_cond = 0; + { + RDI_EvalTypeGroup pass_type_group = e2_type_group_from_kind(e2_type_kind_from_key(pass_ir->type_key)); + RDI_EvalTypeGroup fail_type_group = e2_type_group_from_kind(e2_type_kind_from_key(fail_ir->type_key)); + RDI_EvalConversionKind conversion_kind = rdi_eval_conversion_kind_from_typegroups(fail_type_group, pass_type_group); + if(conversion_kind != RDI_EvalConversionKind_Legal && + conversion_kind != RDI_EvalConversionKind_Noop) + { + compile_time_cond = 1; + } + if(pass_ir->mode != fail_ir->mode) + { + compile_time_cond = 1; + } + } + + // rjf: compile-time conditional -> ask caller to evaluate conditional + if(compile_time_cond) + { + if(state->caller_request_count == 0) + { + done = 1; + compile.status = E2_CompileStatus_CompileTimeEval; + compile.irtree = lhs; + } + else + { + if(compile_time_eval_result.u64 == 0) + { + finished_root = fail_ir; + } + else + { + finished_root = pass_ir; + } + } + } + + // rjf: runtime conditional -> build opcodes for evaluating condition, jumping, etc. + else + { + // rjf: convert pass/fail to coerced type + pass_ir = e2_irnode_convert_if_possible(arena, pass_ir, type_key); + fail_ir = e2_irnode_convert_if_possible(arena, fail_ir, type_key); + + // rjf: form conditional tree + E2_IRNode *cond_root = e2_irnode(arena); + E2_IRNode *cjump_ir = e2_irnode(arena); + E2_IRNode *jump_ir = e2_irnode(arena); + e2_irnode_push_child(arena, cond_root, condition_ir); + e2_irnode_push_child(arena, cond_root, cjump_ir); + e2_irnode_push_child(arena, cond_root, fail_ir); + e2_irnode_push_child(arena, cond_root, jump_ir); + e2_irnode_push_child(arena, cond_root, pass_ir); + + // rjf: compute # of bytes for pass + U64 pass_ir_byte_count = 0; + { + Temp scratch = scratch_begin(&arena, 1); + String8 pass_bytecode = e2_bytecode_from_irnode(scratch.arena, pass_ir); + pass_ir_byte_count = pass_bytecode.size; + scratch_end(scratch); + } + + // rjf: configure unconditional jump (fail -> end) + jump_ir->op = RDI_EvalOp_Skip; + jump_ir->val.u64 = pass_ir_byte_count; + + // rjf: compute # of bytes for fail + U64 fail_ir_byte_count = 0; + { + Temp scratch = scratch_begin(&arena, 1); + String8 fail_bytecode = e2_bytecode_from_irnode(scratch.arena, fail_ir); + fail_ir_byte_count = fail_bytecode.size; + scratch_end(scratch); + } + + // rjf: compute # of bytes for unconditional jump + U64 jump_ir_byte_count = 0; + { + Temp scratch = scratch_begin(&arena, 1); + String8 jump_bytecode = e2_bytecode_from_irnode(scratch.arena, jump_ir); + jump_ir_byte_count = jump_bytecode.size; + scratch_end(scratch); + } + + // rjf: configure conditional jump + cjump_ir->op = RDI_EvalOp_Cond; + cjump_ir->val.u64 = fail_ir_byte_count + jump_ir_byte_count; + + // rjf: fill conditional root type + cond_root->type_key = type_key; + + // rjf: take result + finished_root = cond_root; + finished_root->mode = pass_ir->mode; + } + }break; + } + } + } + + //- rjf: if this task is done, pop - if there is a parent, push this task's + // IR tree as a child - otherwise this is our final compilation result + if(!done && !new_child_task) + { + SLLStackPop(state->top_task); + SLLStackPush(state->free_task, task); + if(state->top_task != 0) + { + E2_IRNodePtrNode *n = push_array(arena, E2_IRNodePtrNode, 1); + SLLQueuePush(state->top_task->first_irtree_child, state->top_task->last_irtree_child, n); + n->v = finished_root; + state->top_task->irtree_child_count += 1; + } + else + { + compile.status = E2_CompileStatus_Good; + compile.irtree = finished_root; + } + } + } + + //- rjf: asking caller for info? -> if we already did this at this task, + // the user couldn't provide the info, so we just fail out. + if(e2_compile_status_is_caller_request(compile.status)) + { + if(state->caller_request_count > 0) + { + if(compile.status == E2_CompileStatus_MissedIdentifierResolution) + { + e2_msgf(arena, &compile.msgs, state->top_task->expr->src_range, "`%S` couldn't be resolved.", compile.identifier); + } + compile.status = E2_CompileStatus_Error; + } + state->caller_request_count += 1; + } + } + return compile; +} + +//////////////////////////////// +//~ rjf: IR Tree -> Bytecode internal String8 -e2_bytecode_from_expr(Arena *arena, E2_Expr *expr) +e2_bytecode_from_irnode(Arena *arena, E2_IRNode *irnode) { String8 result = {0}; { @@ -1963,26 +3498,35 @@ e2_bytecode_from_expr(Arena *arena, E2_Expr *expr) struct Task { Task *next; - E2_Expr *e; - E2_Expr *last_pushed_child; + E2_IRNode *ir; + E2_IRNodePtrNode *last_pushed_child_node; U64 pushed_child_count; }; - Task start_task = {0, expr, &e2_expr_nil}; + Task start_task = {0, irnode}; Task *top_task = &start_task; Task *free_task = 0; for(Task *t = top_task; t != 0; t = top_task) { - E2_Expr *e = t->e; + E2_IRNode *ir = t->ir; //- rjf: unpack this op - U16 ctrlbits = rdi_eval_op_ctrlbits_table[e->op]; - U64 child_count = RDI_POPN_FROM_CTRLBITS(ctrlbits); + U16 ctrlbits = 0; + U64 child_count = 0; + if(0 < ir->op && ir->op < RDI_EvalOp_COUNT) + { + ctrlbits = rdi_eval_op_ctrlbits_table[ir->op]; + child_count = RDI_POPN_FROM_CTRLBITS(ctrlbits); + } + else + { + child_count = ir->child_count; + } //- rjf: push the next child task if we can if(t->pushed_child_count < child_count) { t->pushed_child_count += 1; - E2_Expr *next_child = (t->last_pushed_child == &e2_expr_nil ? e->first : t->last_pushed_child->next); + E2_IRNodePtrNode *next_child_node = (t->last_pushed_child_node == 0 ? ir->first_child : t->last_pushed_child_node->next); Task *child_task = free_task; if(child_task != 0) { @@ -1994,26 +3538,42 @@ e2_bytecode_from_expr(Arena *arena, E2_Expr *expr) } MemoryZeroStruct(child_task); SLLStackPush(top_task, child_task); - child_task->e = next_child; - child_task->last_pushed_child = &e2_expr_nil; + child_task->ir = next_child_node ? next_child_node->v : &e2_irnode_nil; + child_task->last_pushed_child_node = 0; child_task->pushed_child_count = 0; - t->last_pushed_child = next_child; + t->last_pushed_child_node = next_child_node; } //- rjf: did push of all children -> push this expr's op, pop off stack else { - U16 ctrlbits = 0; - if(e->op < RDI_EvalOp_COUNT) + if(ir->op != 0) { - ctrlbits = rdi_eval_op_ctrlbits_table[e->op]; + U16 ctrlbits = 0; + if(ir->op < RDI_EvalOp_COUNT) + { + ctrlbits = rdi_eval_op_ctrlbits_table[ir->op]; + } + else switch(ir->op) + { + default:{}break; + case E2_EvalOp_SetCtxID: + case E2_EvalOp_LeafBytecode: + { + ctrlbits = RDI_EVAL_CTRLBITS(8, 0, 0); + }break; + } + U64 decode_byte_count = RDI_DECODEN_FROM_CTRLBITS(ctrlbits); + U64 op_size = 1 + decode_byte_count; + U8 *op_buffer = push_array(scratch.arena, U8, op_size); + op_buffer[0] = ir->op; + MemoryCopy(op_buffer+1, &ir->val, decode_byte_count); + str8_list_push(scratch.arena, &strings, str8(op_buffer, op_size)); + if(ir->string.size != 0) + { + str8_list_push(scratch.arena, &strings, ir->string); + } } - U64 decode_byte_count = RDI_DECODEN_FROM_CTRLBITS(ctrlbits); - U64 op_size = 1 + decode_byte_count; - U8 *op_buffer = push_array(scratch.arena, U8, op_size); - op_buffer[0] = e->op; - MemoryCopy(op_buffer+1, &e->val, decode_byte_count); - str8_list_push(scratch.arena, &strings, str8(op_buffer, op_size)); SLLStackPop(top_task); SLLStackPush(free_task, t); } @@ -2088,7 +3648,11 @@ e2_interp_from_bytecode(Arena *arena, E2_InterpState *state, E2_SpaceMap *space_ U64 ctx_base_addr = 0; switch(op) { - default:{}break; + default: + { + good = 0; + interp.status = E2_InterpStatus_UnsupportedOp; + }break; case E2_EvalOp_SetCtxID: { diff --git a/src/eval2/eval2.h b/src/eval2/eval2.h index f920d69e..c8736d19 100644 --- a/src/eval2/eval2.h +++ b/src/eval2/eval2.h @@ -5,28 +5,15 @@ #define EVAL2_H //////////////////////////////// -//~ rjf: Operator Info Tables +//~ rjf: Expression Parse Info Table Types -typedef enum E2_OpParseKind +typedef enum E2_ExprParseKind { - E2_OpParseKind_Null, - E2_OpParseKind_UnaryPrefix, - E2_OpParseKind_Binary, - E2_OpParseKind_Ternary, - E2_OpParseKind_Call, + E2_ExprParseKind_Null, + E2_ExprParseKind_Prefix, + E2_ExprParseKind_Postfix, } -E2_OpParseKind; - -typedef struct E2_OpInfo E2_OpInfo; -struct E2_OpInfo -{ - E2_OpParseKind parse_kind; - S64 precedence; - String8 pre; - String8 sep; - String8 post; - String8 chain; -}; +E2_ExprParseKind; //////////////////////////////// //~ rjf: Generated Code @@ -39,6 +26,7 @@ struct E2_OpInfo enum { E2_EvalOp_SetCtxID = RDI_EvalOp_COUNT, + E2_EvalOp_LeafBytecode, }; //////////////////////////////// @@ -53,18 +41,42 @@ typedef enum E2_ParseStatus E2_ParseStatus_LastTerminal = E2_ParseStatus_Error, //- rjf: caller-provided info - E2_ParseStatus_MissedIdentifierResolution, - E2_ParseStatus_MemberAccess, - E2_ParseStatus_IndexAccess, - E2_ParseStatus_Call, - E2_ParseStatus_FirstCallerRequest = E2_ParseStatus_MissedIdentifierResolution, - E2_ParseStatus_LastCallerRequest = E2_ParseStatus_Call, + E2_ParseStatus_CheckIdentifierIsType, + E2_ParseStatus_FirstCallerRequest = E2_ParseStatus_CheckIdentifierIsType, + E2_ParseStatus_LastCallerRequest = E2_ParseStatus_CheckIdentifierIsType, } E2_ParseStatus; #define e2_parse_status_is_terminal(s) (E2_ParseStatus_FirstTerminal <= (s) && (s) <= E2_ParseStatus_LastTerminal) #define e2_parse_status_is_caller_request(s) (E2_ParseStatus_FirstCallerRequest <= (s) && (s) <= E2_ParseStatus_LastCallerRequest) +//////////////////////////////// +//~ rjf: Compiler Statuses + +typedef enum E2_CompileStatus +{ + //- rjf: terminals + E2_CompileStatus_Good, + E2_CompileStatus_Error, + E2_CompileStatus_FirstTerminal = E2_CompileStatus_Good, + E2_CompileStatus_LastTerminal = E2_CompileStatus_Error, + + //- rjf: caller-provided info + E2_CompileStatus_MissedIdentifierResolution, + E2_CompileStatus_NewIdentifierDefinition, + E2_CompileStatus_NewCtxID, + E2_CompileStatus_MemberAccess, + E2_CompileStatus_IndexAccess, + E2_CompileStatus_Call, + E2_CompileStatus_CompileTimeEval, + E2_CompileStatus_FirstCallerRequest = E2_CompileStatus_MissedIdentifierResolution, + E2_CompileStatus_LastCallerRequest = E2_CompileStatus_CompileTimeEval, +} +E2_CompileStatus; + +#define e2_compile_status_is_terminal(s) (E2_CompileStatus_FirstTerminal <= (s) && (s) <= E2_CompileStatus_LastTerminal) +#define e2_compile_status_is_caller_request(s) (E2_CompileStatus_FirstCallerRequest <= (s) && (s) <= E2_CompileStatus_LastCallerRequest) + //////////////////////////////// //~ rjf: Interpretation Statuses @@ -133,69 +145,57 @@ struct E2_TypeKeyList }; //////////////////////////////// -//~ rjf: Unpacked Type Info - -typedef enum E2_MemberKind -{ - E2_MemberKind_Null, - E2_MemberKind_DataField, - E2_MemberKind_StaticData, - E2_MemberKind_Method, - E2_MemberKind_StaticMethod, - E2_MemberKind_VirtualMethod, - E2_MemberKind_VTablePtr, - E2_MemberKind_Base, - E2_MemberKind_VirtualBase, - E2_MemberKind_NestedType, - E2_MemberKind_Padding, - E2_MemberKind_COUNT -} -E2_MemberKind; +//~ rjf: Constructed Type Cache Types typedef U32 E2_TypeFlags; enum { E2_TypeFlag_Const = (1<<0), E2_TypeFlag_Volatile = (1<<1), - E2_TypeFlag_Restrict = (1<<2), }; -typedef struct E2_EnumVal E2_EnumVal; -struct E2_EnumVal -{ - String8 name; - U64 val; -}; - -typedef struct E2_Member E2_Member; -struct E2_Member -{ - E2_MemberKind kind; - E2_TypeKey type_key; - String8 name; - U64 off; - E2_TypeKeyList inheritees; -}; - -typedef struct E2_Type E2_Type; -struct E2_Type +typedef struct E2_ConsTypeParams E2_ConsTypeParams; +struct E2_ConsTypeParams { + Arch arch; E2_TypeKind kind; E2_TypeFlags flags; String8 name; - U64 byte_size; + E2_TypeKey direct; U64 count; U64 depth; - U32 off; - Arch arch; - E2_TypeKey direct_type_key; - E2_TypeKey owner_type_key; - E2_TypeKey *param_type_keys; - E2_Member *members; - E2_EnumVal *enum_vals; + E2_TypeKey *param_types; struct E2_Expr **args; }; +typedef struct E2_ConsTypeNode E2_ConsTypeNode; +struct E2_ConsTypeNode +{ + E2_ConsTypeNode *id_next; + E2_ConsTypeNode *content_next; + U64 id; + E2_ConsTypeParams params; + U64 byte_size; +}; + +typedef struct E2_ConsTypeSlot E2_ConsTypeSlot; +struct E2_ConsTypeSlot +{ + E2_ConsTypeNode *first; + E2_ConsTypeNode *last; +}; + +typedef struct E2_ConsTypeMap E2_ConsTypeMap; +struct E2_ConsTypeMap +{ + Arena *arena; + U64 id_gen; + U64 table_start_arena_pos; + U64 slots_count; + E2_ConsTypeSlot *content_slots; + E2_ConsTypeSlot *id_slots; +}; + //////////////////////////////// //~ rjf: Evaluation Values @@ -309,25 +309,24 @@ struct E2_Token //////////////////////////////// //~ rjf: Expression Tree Building -typedef struct E2_Expr E2_Expr; -struct E2_Expr -{ - E2_Expr *first; - E2_Expr *last; - E2_Expr *next; - Rng1U64 src_range; - String8 string; - RDI_EvalOp op; - E2_TypeKey type_key; - E2_Mode mode; - E2_Val val; -}; - typedef struct E2_ExprNode E2_ExprNode; struct E2_ExprNode { E2_ExprNode *next; - E2_Expr *v; + struct E2_Expr *v; +}; + +typedef struct E2_Expr E2_Expr; +struct E2_Expr +{ + E2_ExprNode *first_child; + E2_ExprNode *last_child; + U64 child_count; + E2_ExprKind kind; + U32 macro_arg_num; + Rng1U64 src_range; + String8 string; + String8List qualifiers; }; typedef struct E2_ExprMapNode E2_ExprMapNode; @@ -352,19 +351,27 @@ struct E2_ParseTask Rng1U64 src_range; E2_ExprNode *first_child; E2_ExprNode *last_child; + E2_Expr *next_type_ancestor; U64 child_count; U64 child_count_target; + B32 reverse_children; S64 max_precedence; - E2_OpKind op_kind; + E2_ExprKind expr_kind; + String8 identifier; String8 expected_closer; String8 expected_splitter; + U64 splitter_min_child_count; B32 splitter_is_required; + String8List macro_arg_names; + B32 do_type_ancestors; + E2_Expr *type_lhs; }; typedef struct E2_ParseState E2_ParseState; struct E2_ParseState { U64 string_off; + E2_ParseTask start_task; E2_ParseTask *top_task; E2_ParseTask *free_task; U64 last_caller_request_string_off; @@ -393,13 +400,84 @@ typedef struct E2_Parse E2_Parse; struct E2_Parse { E2_ParseStatus status; - String8 missed_identifier; + String8 identifier; + String8List qualifiers; E2_Expr *expr; String8 member_name; E2_Expr *params_expr; E2_MsgList msgs; }; +//////////////////////////////// +//~ rjf: IR Tree Building + +typedef struct E2_IRNodePtrNode E2_IRNodePtrNode; +struct E2_IRNodePtrNode +{ + E2_IRNodePtrNode *next; + struct E2_IRNode *v; +}; + +typedef struct E2_IRNode E2_IRNode; +struct E2_IRNode +{ + E2_IRNodePtrNode *first_child; + E2_IRNodePtrNode *last_child; + U64 child_count; + RDI_EvalOp op; + String8 string; + E2_TypeKey type_key; + E2_Mode mode; + E2_Val val; +}; + +typedef struct E2_IdentifierMapNode E2_IdentifierMapNode; +struct E2_IdentifierMapNode +{ + E2_IdentifierMapNode *next; + String8 name; + E2_IRNode *irtree; +}; + +typedef struct E2_IdentifierMap E2_IdentifierMap; +struct E2_IdentifierMap +{ + E2_IdentifierMapNode **slots; + U64 slots_count; +}; + +typedef struct E2_CompileTask E2_CompileTask; +struct E2_CompileTask +{ + E2_CompileTask *next; + E2_Expr *expr; + E2_ExprNode *last_compiled_child_node; + E2_IRNodePtrNode *first_irtree_child; + E2_IRNodePtrNode *last_irtree_child; + U64 irtree_child_count; +}; + +typedef struct E2_CompileState E2_CompileState; +struct E2_CompileState +{ + E2_CompileTask *top_task; + E2_CompileTask *free_task; + U64 caller_request_count; + E2_Ctx selected_ctx; +}; + +typedef struct E2_Compile E2_Compile; +struct E2_Compile +{ + E2_CompileStatus status; + String8 identifier; + String8List qualifiers; + E2_IRNode *irtree; + String8 member_name; + E2_IRNode *params_irtree; + E2_MsgList msgs; +}; + //////////////////////////////// //~ rjf: Interpretation @@ -452,8 +530,11 @@ struct E2_Interp //~ rjf: Globals thread_static E2_Assets *e2_assets = 0; +thread_static E2_ConsTypeMap *e2_cons_type_map = 0; +read_only global E2_ConsTypeNode e2_cons_type_node_nil = {&e2_cons_type_node_nil, &e2_cons_type_node_nil}; read_only global E2_DbgInfo e2_dbg_info_nil = {{0}, &rdi_parsed_nil}; -read_only global E2_Expr e2_expr_nil = {&e2_expr_nil, &e2_expr_nil, &e2_expr_nil}; +read_only global E2_Expr e2_expr_nil = {0}; +read_only global E2_IRNode e2_irnode_nil = {0}; //////////////////////////////// //~ rjf: Space -> Memory Map Helpers @@ -467,6 +548,12 @@ internal U64 e2_space_map_read(E2_SpaceMap *map, E2_SpaceID space_id, Rng1U64 ad internal void e2_expr_map_push(Arena *arena, E2_ExprMap *map, String8 name, E2_Expr *expr); internal E2_Expr *e2_expr_from_name(E2_ExprMap *map, String8 name); +//////////////////////////////// +//~ rjf: Identifier Map Helpers + +internal void e2_identifier_map_push(Arena *arena, E2_IdentifierMap *map, String8 name, E2_IRNode *irtree); +internal E2_IRNode *e2_irtree_from_identifier(E2_IdentifierMap *map, String8 name); + //////////////////////////////// //~ rjf: Messages @@ -479,6 +566,16 @@ internal E2_Msg *e2_msgf(Arena *arena, E2_MsgList *msgs, Rng1U64 src_range, char internal void e2_select_assets(E2_Assets *assets); internal E2_DbgInfo *e2_dbgi_from_num(U32 num); +//////////////////////////////// +//~ rjf: Constructed Types + +internal E2_ConsTypeMap *e2_cons_type_map_alloc(void); +internal void e2_select_cons_type_map(E2_ConsTypeMap *map); +internal B32 e2_cons_type_params_match(E2_ConsTypeParams *a, E2_ConsTypeParams *b); +internal E2_ConsTypeNode *e2_cons_type_node_from_params(E2_ConsTypeParams *params); +internal E2_ConsTypeNode *e2_cons_type_node_from_id(U64 id); +internal E2_TypeKey e2_type_key_from_cons_type_node(E2_ConsTypeNode *node); + //////////////////////////////// //~ rjf: Type Keys @@ -498,24 +595,12 @@ internal E2_TypeKey e2_type_key_dbgi(E2_TypeKind kind, U32 dbg_info_num, U32 typ internal E2_TypeKey e2_type_key_reg(Arch arch, ARCH_RegCode reg_code); //- rjf: constructed type constructor -typedef struct E2_ConsTypeParams E2_ConsTypeParams; -struct E2_ConsTypeParams -{ - Arch arch; - E2_TypeKind kind; - E2_TypeFlags flags; - String8 name; - E2_TypeKey direct_type_key; - U64 count; - U64 depth; - E2_Expr **args; -}; internal E2_TypeKey e2_type_key_cons_(E2_ConsTypeParams *params); #define e2_type_key_cons(k, ...) e2_type_key_cons_(&(E2_ConsTypeParams){.kind = (k), __VA_ARGS__}) //- rjf: constructed type constructor helpers -#define e2_type_key_cons_array(element_type_key, count_, ...) e2_type_key_cons(E2_TypeKind_Array, .direct_type_key = (element_type_key), .count = (count_), __VA_ARGS__) -#define e2_type_key_cons_ptr(arch, ptee_type_key, ...) e2_type_key_cons(E2_TypeKind_Ptr, .direct_type_key = (ptee_type_key), __VA_ARGS__) +#define e2_type_key_cons_array(element_type_key, count_, ...) e2_type_key_cons(E2_TypeKind_Array, .direct = (element_type_key), .count = (count_), __VA_ARGS__) +#define e2_type_key_cons_ptr(arch, ptee_type_key, ...) e2_type_key_cons(E2_TypeKind_Ptr, .direct = (ptee_type_key), __VA_ARGS__) //- rjf: basic type key type functions internal B32 e2_type_key_match(E2_TypeKey a, E2_TypeKey b); @@ -526,9 +611,18 @@ internal U64 e2_byte_size_from_type_key(E2_TypeKey k); internal U32 e2_dbgi_num_from_type_key(E2_TypeKey k); internal E2_DbgInfo *e2_dbgi_from_type_key(E2_TypeKey k); internal U32 e2_dbgi_type_idx_from_key(E2_TypeKey k); +internal U64 e2_cons_type_id_from_key(E2_TypeKey k); internal U64 e2_shift_from_type_key(E2_TypeKey k); internal U64 e2_mask_count_from_type_key(E2_TypeKey k); +internal U64 e2_array_count_from_type_key(E2_TypeKey k); internal Arch e2_arch_from_type_key(E2_TypeKey k); +internal String8 e2_name_from_type_key(Arena *arena, E2_TypeKey k); + +//- rjf: type key -> string +internal String8 e2_string_from_type_key(Arena *arena, E2_TypeKey key); + +//- rjf: type deep matches +internal B32 e2_type_deep_match(E2_TypeKey l, E2_TypeKey r); //- rjf: type graph traversal primitives internal E2_TypeKey e2_type_key_direct(E2_TypeKey k); @@ -557,29 +651,45 @@ internal E2_TypeKey e2_coerced_type_key_from_operands(E2_TypeKey lhs, E2_TypeKey //////////////////////////////// //~ rjf: Expression Constructors -internal E2_Expr *e2_expr(Arena *arena); -internal E2_Expr *e2_expr_const_u64_or_smaller(Arena *arena, U64 u); -internal E2_Expr *e2_expr_const_f32(Arena *arena, F32 f32); -internal E2_Expr *e2_expr_const_f64(Arena *arena, F64 f64); -internal E2_Expr *e2_expr_unary_op(Arena *arena, E2_TypeKey type_key, RDI_EvalOp op, E2_Expr *operand); -internal E2_Expr *e2_expr_binary_op(Arena *arena, E2_TypeKey type_key, RDI_EvalOp op, E2_Expr *lhs, E2_Expr *rhs); -internal E2_Expr *e2_expr_resolve_to_value(Arena *arena, E2_Expr *expr); -internal E2_Expr *e2_expr_truncate(Arena *arena, E2_Expr *expr, E2_TypeKey dst_type_key); -internal E2_Expr *e2_expr_convert_if_possible(Arena *arena, E2_Expr *expr, E2_TypeKey dst_type_key); -internal void e2_expr_push_child(E2_Expr *parent, E2_Expr *expr); +internal E2_Expr *e2_expr(Arena *arena, E2_ExprKind kind); +internal void e2_expr_push_child_node(E2_Expr *parent, E2_ExprNode *node); +internal void e2_expr_push_child(Arena *arena, E2_Expr *parent, E2_Expr *expr); +internal void e2_expr_push_child_node_front(E2_Expr *parent, E2_ExprNode *node); +internal void e2_expr_push_child_front(Arena *arena, E2_Expr *parent, E2_Expr *expr); + +//////////////////////////////// +//~ rjf: IR Tree Constructors + +internal E2_IRNode *e2_irnode(Arena *arena); +internal E2_IRNode *e2_irnode_const_u64_or_smaller(Arena *arena, U64 u); +internal E2_IRNode *e2_irnode_const_f32(Arena *arena, F32 f32); +internal E2_IRNode *e2_irnode_const_f64(Arena *arena, F64 f64); +internal E2_IRNode *e2_irnode_unary_op(Arena *arena, E2_TypeKey type_key, RDI_EvalOp op, E2_IRNode *operand); +internal E2_IRNode *e2_irnode_binary_op(Arena *arena, E2_TypeKey type_key, RDI_EvalOp op, E2_IRNode *lhs, E2_IRNode *rhs); +internal E2_IRNode *e2_irnode_resolve_to_value(Arena *arena, E2_IRNode *expr); +internal E2_IRNode *e2_irnode_truncate(Arena *arena, E2_IRNode *expr, E2_TypeKey dst_type_key); +internal E2_IRNode *e2_irnode_convert_if_possible(Arena *arena, E2_IRNode *expr, E2_TypeKey dst_type_key); +internal E2_IRNode *e2_irnode_type(Arena *arena, E2_TypeKey type_key); +internal void e2_irnode_push_child_node(E2_IRNode *parent, E2_IRNodePtrNode *node); +internal void e2_irnode_push_child(Arena *arena, E2_IRNode *parent, E2_IRNode *expr); //////////////////////////////// //~ rjf: String -> Expression -internal E2_Token e2_token_from_string_off(String8 string, U64 start_off); -internal U64 e2_read_token(String8 string, U64 off, E2_Token *token_out); -internal B32 e2_try_token(String8 string, E2_TokenKind kind, String8 expected_string, U64 *off_out, E2_Token *token_out); -internal E2_Parse e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E2_Expr *access_result, String8 string); +internal E2_Token e2_token_from_string_off(E2_LangKind lang, String8 string, U64 start_off); +internal U64 e2_read_token(E2_LangKind lang, String8 string, U64 off, E2_Token *token_out); +internal B32 e2_try_token(E2_LangKind lang, String8 string, E2_TokenKind kind, String8 expected_string, U64 *off_out, E2_Token *token_out); +internal E2_Parse e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, E2_LangKind lang, String8 string); //////////////////////////////// -//~ rjf: Expression -> Bytecode +//~ rjf: Expression -> IR Tree -internal String8 e2_bytecode_from_expr(Arena *arena, E2_Expr *expr); +internal E2_Compile e2_compile_from_expr(Arena *arena, E2_CompileState *state, E2_IRNode *resolve_result, E2_Val compile_time_eval_result, E2_Expr *expr); + +//////////////////////////////// +//~ rjf: IR Tree -> Bytecode + +internal String8 e2_bytecode_from_irnode(Arena *arena, E2_IRNode *irnode); //////////////////////////////// //~ rjf: Bytecode -> Result diff --git a/src/eval2/eval2.mdesk b/src/eval2/eval2.mdesk index 2f07c727..70d13964 100644 --- a/src/eval2/eval2.mdesk +++ b/src/eval2/eval2.mdesk @@ -1,53 +1,179 @@ // Copyright (c) Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) -@table(name parse_kind precedence pre sep pos chain) -E2_OpTable: +@table(name allow_type_operands is_type_expression is_first_operand_type_maybe target_operand_count) +E2_ExprKindTable: { - {Dot Binary 1 "" "." "" "" } - {Index Binary 1 "" "[" "]" "" } - {Call Call 1 "" "(" ")" ","} - {DerefAsm UnaryPrefix 1 "[" "" "]" "" } - {SizeOf UnaryPrefix 1 "sizeof " "" "" "" } - {Deref UnaryPrefix 2 "*" "" "" "" } - {Address UnaryPrefix 2 "&" "" "" "" } - {Pos UnaryPrefix 2 "+" "" "" "" } - {Neg UnaryPrefix 2 "-" "" "" "" } - {LogNot UnaryPrefix 2 "!" "" "" "" } - {BitNot UnaryPrefix 2 "~" "" "" "" } - {Mul Binary 3 "" "*" "" "" } - {Div Binary 3 "" "/" "" "" } - {Mod Binary 3 "" "%" "" "" } - {Add Binary 4 "" "+" "" "" } - {Sub Binary 4 "" "-" "" "" } - {LShift Binary 5 "" "<<" "" "" } - {RShift Binary 5 "" ">>" "" "" } - {Less Binary 6 "" "<" "" "" } - {LtEq Binary 6 "" "<=" "" "" } - {Grtr Binary 6 "" ">" "" "" } - {GrEq Binary 6 "" ">=" "" "" } - {EqEq Binary 7 "" "==" "" "" } - {NtEq Binary 7 "" "!=" "" "" } - {BitAnd Binary 8 "" "&" "" "" } - {BitXor Binary 9 "" "^" "" "" } - {BitOr Binary 10 "" "|" "" "" } - {LogAnd Binary 11 "" "&&" "" "" } - {LogOr Binary 12 "" "||" "" "" } - {Define Binary 13 "" "=" "" "" } - {Cond Ternary 14 "" "?" "" ":"} + //- rjf: leaves + {Identifier 0 0 0 0 } + {MacroArg 0 0 0 0 } + {TypeIdentifier 0 1 0 0 } + {Numeric 0 0 0 0 } + {StringLiteral 0 0 0 0 } + {CharLiteral 0 0 0 0 } + + //- rjf: type operators + {Ptr 1 1 1 1 } + {Array 1 1 1 2 } + {Function 1 1 1 0xffffffffffffffffull} + {Const 1 1 1 1 } + {Volatile 1 1 1 1 } + {Unsigned 1 1 1 1 } + {Signed 1 1 1 1 } + + //- rjf: operators + {Dot 1 0 0 1 } + {Index 0 0 0 2 } + {Call 0 0 0 0xffffffffffffffffull} + {DerefAsm 1 0 0 1 } + {SizeOf 1 0 1 1 } + {TypeOf 1 1 1 1 } + {Cast 1 0 1 2 } + {Deref 1 0 0 1 } + {Address 1 0 0 1 } + {Pos 1 0 0 1 } + {Neg 1 0 0 1 } + {LogNot 1 0 0 1 } + {BitNot 1 0 0 1 } + {Mul 0 0 0 2 } + {Div 1 0 0 2 } + {Mod 1 0 0 2 } + {Add 1 0 0 2 } + {Sub 1 0 0 2 } + {LShift 1 0 0 2 } + {RShift 1 0 0 2 } + {Less 1 0 0 2 } + {LtEq 1 0 0 2 } + {Grtr 1 0 0 2 } + {GrEq 1 0 0 2 } + {EqEq 1 0 0 2 } + {NtEq 1 0 0 2 } + {BitAnd 1 0 0 2 } + {BitXor 1 0 0 2 } + {BitOr 1 0 0 2 } + {LogAnd 1 0 0 2 } + {LogOr 1 0 0 2 } + {Define 1 0 0 1 } + {Macro 1 0 0 1 } + {Cond 1 0 0 3 } } -@enum E2_OpKind: +@enum E2_ExprKind: { Null, - @expand(E2_OpTable a) `$(a.name)`, + @expand(E2_ExprKindTable a) `$(a.name)`, COUNT, } -@data(E2_OpInfo) e2_op_kind_info_table: +@data(B8) e2_expr_kind_allow_type_operands_table: { - `{0}`, - @expand(E2_OpTable a) `{E2_OpParseKind_$(a.parse_kind), $(a.precedence), str8_lit_comp("$(a.pre)"), str8_lit_comp("$(a.sep)"), str8_lit_comp("$(a.pos)"), str8_lit_comp("$(a.chain)")}` + 0, + @expand(E2_ExprKindTable a) `$(a.allow_type_operands)` +} + +@data(B8) e2_expr_kind_is_type_expr_table: +{ + 0, + @expand(E2_ExprKindTable a) `$(a.is_type_expression)` +} + +@data(B8) e2_expr_kind_is_first_operand_type_maybe_table: +{ + 0, + @expand(E2_ExprKindTable a) `$(a.is_first_operand_type_maybe)` +} + +@data(U64) e2_expr_kind_target_operand_count_table: +{ + 0, + @expand(E2_ExprKindTable a) `$(a.target_operand_count)` +} + +@struct E2_ExprKindParseInfo: +{ + `E2_ExprKind expr_kind`; + `E2_ExprParseKind parse_kind`; + `S64 precedence`; + `B8 reverse_children`; + `String8 pre`; + `String8 sep`; + `String8 post`; + `String8 chain`; +} + +@table(name name_lower) +E2_LangKindTable: +{ + {CLike clike} +} + +@enum E2_LangKind: +{ + @expand(E2_LangKindTable a) `$(a.name)` +} + +@struct E2_LangInfo: +{ + `U64 expr_kind_parse_infos_count`; + `E2_ExprKindParseInfo *expr_kind_parse_infos`; +} + +@data(E2_LangInfo) e2_lang_kind_info_table: +{ + @expand(E2_LangKindTable a) `{ArrayCount(e2_expr_kind_parse_info_table__$(a.name_lower)), (e2_expr_kind_parse_info_table__$(a.name_lower))}` +} + +@table(name parse_kind precedence pre sep pos chain reverse_children) +E2_ExprKindParseInfoTable_CLike: +{ + {Dot Postfix 1 "" "." "" "" 0} + {Index Postfix 1 "" "[" "]" "" 0} + {Call Postfix 1 "" "(" ")" "," 0} + {Function Postfix 1 "" "(" ")" "," 0} + {DerefAsm Prefix 1 "[" "" "]" "" 0} + {Unsigned Prefix 1 "unsigned " "" "" "" 0} + {Signed Prefix 1 "signed " "" "" "" 0} + {Volatile Prefix 1 "volatile " "" "" "" 0} + {Const Prefix 1 "const " "" "" "" 0} + {SizeOf Prefix 1 "sizeof " "" "" "" 0} + {TypeOf Prefix 1 "typeof " "" "" "" 0} + {SizeOf Prefix 1 "size_of " "" "" "" 0} + {TypeOf Prefix 1 "type_of " "" "" "" 0} + {Cast Prefix 1 "" "" "" "" 0} + {Deref Prefix 2 "*" "" "" "" 0} + {Address Prefix 2 "&" "" "" "" 0} + {Pos Prefix 2 "+" "" "" "" 0} + {Neg Prefix 2 "-" "" "" "" 0} + {LogNot Prefix 2 "!" "" "" "" 0} + {BitNot Prefix 2 "~" "" "" "" 0} + {Cast Prefix 1 "cast " "" "" "" 0} + {Cast Postfix 1 "" "as" "" "" 1} + {Ptr Postfix 1 "" "" "*" "" 0} + {Array Postfix 1 "" "[" "]" "" 0} + {Mul Postfix 3 "" "*" "" "" 0} + {Div Postfix 3 "" "/" "" "" 0} + {Mod Postfix 3 "" "%" "" "" 0} + {Add Postfix 4 "" "+" "" "" 0} + {Sub Postfix 4 "" "-" "" "" 0} + {LShift Postfix 5 "" "<<" "" "" 0} + {RShift Postfix 5 "" ">>" "" "" 0} + {Less Postfix 6 "" "<" "" "" 0} + {LtEq Postfix 6 "" "<=" "" "" 0} + {Grtr Postfix 6 "" ">" "" "" 0} + {GrEq Postfix 6 "" ">=" "" "" 0} + {EqEq Postfix 7 "" "==" "" "" 0} + {NtEq Postfix 7 "" "!=" "" "" 0} + {BitAnd Postfix 8 "" "&" "" "" 0} + {BitXor Postfix 9 "" "^" "" "" 0} + {BitOr Postfix 10 "" "|" "" "" 0} + {LogAnd Postfix 11 "" "&&" "" "" 0} + {LogOr Postfix 12 "" "||" "" "" 0} + {Cond Postfix 14 "" "?" "" ":" 0} +} + +@data(E2_ExprKindParseInfo) e2_expr_kind_parse_info_table__clike: +{ + @expand(E2_ExprKindParseInfoTable_CLike a) `{E2_ExprKind_$(a.name), E2_ExprParseKind_$(a.parse_kind), $(a.precedence), $(a.reverse_children), str8_lit_comp("$(a.pre)"), str8_lit_comp("$(a.sep)"), str8_lit_comp("$(a.pos)"), str8_lit_comp("$(a.chain)")}` } @table(name basic_string basic_byte_size) diff --git a/src/eval2/generated/eval2.meta.c b/src/eval2/generated/eval2.meta.c index 0b8b63b1..7affddae 100644 --- a/src/eval2/generated/eval2.meta.c +++ b/src/eval2/generated/eval2.meta.c @@ -4,40 +4,264 @@ //- GENERATED CODE C_LINKAGE_BEGIN -E2_OpInfo e2_op_kind_info_table[32] = +B8 e2_expr_kind_allow_type_operands_table[48] = { -{0}, -{E2_OpParseKind_Binary, 1, str8_lit_comp(""), str8_lit_comp("."), str8_lit_comp(""), str8_lit_comp("")}, -{E2_OpParseKind_Binary, 1, str8_lit_comp(""), str8_lit_comp("["), str8_lit_comp("]"), str8_lit_comp("")}, -{E2_OpParseKind_Call, 1, str8_lit_comp(""), str8_lit_comp("("), str8_lit_comp(")"), str8_lit_comp(",")}, -{E2_OpParseKind_UnaryPrefix, 1, str8_lit_comp("["), str8_lit_comp(""), str8_lit_comp("]"), str8_lit_comp("")}, -{E2_OpParseKind_UnaryPrefix, 1, str8_lit_comp("sizeof "), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, -{E2_OpParseKind_UnaryPrefix, 2, str8_lit_comp("*"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, -{E2_OpParseKind_UnaryPrefix, 2, str8_lit_comp("&"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, -{E2_OpParseKind_UnaryPrefix, 2, str8_lit_comp("+"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, -{E2_OpParseKind_UnaryPrefix, 2, str8_lit_comp("-"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, -{E2_OpParseKind_UnaryPrefix, 2, str8_lit_comp("!"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, -{E2_OpParseKind_UnaryPrefix, 2, str8_lit_comp("~"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, -{E2_OpParseKind_Binary, 3, str8_lit_comp(""), str8_lit_comp("*"), str8_lit_comp(""), str8_lit_comp("")}, -{E2_OpParseKind_Binary, 3, str8_lit_comp(""), str8_lit_comp("/"), str8_lit_comp(""), str8_lit_comp("")}, -{E2_OpParseKind_Binary, 3, str8_lit_comp(""), str8_lit_comp("%"), str8_lit_comp(""), str8_lit_comp("")}, -{E2_OpParseKind_Binary, 4, str8_lit_comp(""), str8_lit_comp("+"), str8_lit_comp(""), str8_lit_comp("")}, -{E2_OpParseKind_Binary, 4, str8_lit_comp(""), str8_lit_comp("-"), str8_lit_comp(""), str8_lit_comp("")}, -{E2_OpParseKind_Binary, 5, str8_lit_comp(""), str8_lit_comp("<<"), str8_lit_comp(""), str8_lit_comp("")}, -{E2_OpParseKind_Binary, 5, str8_lit_comp(""), str8_lit_comp(">>"), str8_lit_comp(""), str8_lit_comp("")}, -{E2_OpParseKind_Binary, 6, str8_lit_comp(""), str8_lit_comp("<"), str8_lit_comp(""), str8_lit_comp("")}, -{E2_OpParseKind_Binary, 6, str8_lit_comp(""), str8_lit_comp("<="), str8_lit_comp(""), str8_lit_comp("")}, -{E2_OpParseKind_Binary, 6, str8_lit_comp(""), str8_lit_comp(">"), str8_lit_comp(""), str8_lit_comp("")}, -{E2_OpParseKind_Binary, 6, str8_lit_comp(""), str8_lit_comp(">="), str8_lit_comp(""), str8_lit_comp("")}, -{E2_OpParseKind_Binary, 7, str8_lit_comp(""), str8_lit_comp("=="), str8_lit_comp(""), str8_lit_comp("")}, -{E2_OpParseKind_Binary, 7, str8_lit_comp(""), str8_lit_comp("!="), str8_lit_comp(""), str8_lit_comp("")}, -{E2_OpParseKind_Binary, 8, str8_lit_comp(""), str8_lit_comp("&"), str8_lit_comp(""), str8_lit_comp("")}, -{E2_OpParseKind_Binary, 9, str8_lit_comp(""), str8_lit_comp("^"), str8_lit_comp(""), str8_lit_comp("")}, -{E2_OpParseKind_Binary, 10, str8_lit_comp(""), str8_lit_comp("|"), str8_lit_comp(""), str8_lit_comp("")}, -{E2_OpParseKind_Binary, 11, str8_lit_comp(""), str8_lit_comp("&&"), str8_lit_comp(""), str8_lit_comp("")}, -{E2_OpParseKind_Binary, 12, str8_lit_comp(""), str8_lit_comp("||"), str8_lit_comp(""), str8_lit_comp("")}, -{E2_OpParseKind_Binary, 13, str8_lit_comp(""), str8_lit_comp("="), str8_lit_comp(""), str8_lit_comp("")}, -{E2_OpParseKind_Ternary, 14, str8_lit_comp(""), str8_lit_comp("?"), str8_lit_comp(""), str8_lit_comp(":")}, +0, +0, +0, +0, +0, +0, +0, +1, +1, +1, +1, +1, +1, +1, +1, +0, +0, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, +0, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, +}; + +B8 e2_expr_kind_is_type_expr_table[48] = +{ +0, +0, +0, +1, +0, +0, +0, +1, +1, +1, +1, +1, +1, +1, +0, +0, +0, +0, +0, +1, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +}; + +B8 e2_expr_kind_is_first_operand_type_maybe_table[48] = +{ +0, +0, +0, +0, +0, +0, +0, +1, +1, +1, +1, +1, +1, +1, +0, +0, +0, +0, +1, +1, +1, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +}; + +U64 e2_expr_kind_target_operand_count_table[48] = +{ +0, +0, +0, +0, +0, +0, +0, +1, +2, +0xffffffffffffffffull, +1, +1, +1, +1, +1, +2, +0xffffffffffffffffull, +1, +1, +1, +2, +1, +1, +1, +1, +1, +1, +2, +2, +2, +2, +2, +2, +2, +2, +2, +2, +2, +2, +2, +2, +2, +2, +2, +2, +1, +1, +3, +}; + +E2_LangInfo e2_lang_kind_info_table[1] = +{ +{ArrayCount(e2_expr_kind_parse_info_table__clike), (e2_expr_kind_parse_info_table__clike)}, +}; + +E2_ExprKindParseInfo e2_expr_kind_parse_info_table__clike[43] = +{ +{E2_ExprKind_Dot, E2_ExprParseKind_Postfix, 1, 0, str8_lit_comp(""), str8_lit_comp("."), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_Index, E2_ExprParseKind_Postfix, 1, 0, str8_lit_comp(""), str8_lit_comp("["), str8_lit_comp("]"), str8_lit_comp("")}, +{E2_ExprKind_Call, E2_ExprParseKind_Postfix, 1, 0, str8_lit_comp(""), str8_lit_comp("("), str8_lit_comp(")"), str8_lit_comp(",")}, +{E2_ExprKind_Function, E2_ExprParseKind_Postfix, 1, 0, str8_lit_comp(""), str8_lit_comp("("), str8_lit_comp(")"), str8_lit_comp(",")}, +{E2_ExprKind_DerefAsm, E2_ExprParseKind_Prefix, 1, 0, str8_lit_comp("["), str8_lit_comp(""), str8_lit_comp("]"), str8_lit_comp("")}, +{E2_ExprKind_Unsigned, E2_ExprParseKind_Prefix, 1, 0, str8_lit_comp("unsigned "), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_Signed, E2_ExprParseKind_Prefix, 1, 0, str8_lit_comp("signed "), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_Volatile, E2_ExprParseKind_Prefix, 1, 0, str8_lit_comp("volatile "), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_Const, E2_ExprParseKind_Prefix, 1, 0, str8_lit_comp("const "), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_SizeOf, E2_ExprParseKind_Prefix, 1, 0, str8_lit_comp("sizeof "), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_TypeOf, E2_ExprParseKind_Prefix, 1, 0, str8_lit_comp("typeof "), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_SizeOf, E2_ExprParseKind_Prefix, 1, 0, str8_lit_comp("size_of "), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_TypeOf, E2_ExprParseKind_Prefix, 1, 0, str8_lit_comp("type_of "), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_Cast, E2_ExprParseKind_Prefix, 1, 0, str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_Deref, E2_ExprParseKind_Prefix, 2, 0, str8_lit_comp("*"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_Address, E2_ExprParseKind_Prefix, 2, 0, str8_lit_comp("&"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_Pos, E2_ExprParseKind_Prefix, 2, 0, str8_lit_comp("+"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_Neg, E2_ExprParseKind_Prefix, 2, 0, str8_lit_comp("-"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_LogNot, E2_ExprParseKind_Prefix, 2, 0, str8_lit_comp("!"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_BitNot, E2_ExprParseKind_Prefix, 2, 0, str8_lit_comp("~"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_Cast, E2_ExprParseKind_Prefix, 1, 0, str8_lit_comp("cast "), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_Cast, E2_ExprParseKind_Postfix, 1, 1, str8_lit_comp(""), str8_lit_comp("as"), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_Ptr, E2_ExprParseKind_Postfix, 1, 0, str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("*"), str8_lit_comp("")}, +{E2_ExprKind_Array, E2_ExprParseKind_Postfix, 1, 0, str8_lit_comp(""), str8_lit_comp("["), str8_lit_comp("]"), str8_lit_comp("")}, +{E2_ExprKind_Mul, E2_ExprParseKind_Postfix, 3, 0, str8_lit_comp(""), str8_lit_comp("*"), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_Div, E2_ExprParseKind_Postfix, 3, 0, str8_lit_comp(""), str8_lit_comp("/"), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_Mod, E2_ExprParseKind_Postfix, 3, 0, str8_lit_comp(""), str8_lit_comp("%"), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_Add, E2_ExprParseKind_Postfix, 4, 0, str8_lit_comp(""), str8_lit_comp("+"), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_Sub, E2_ExprParseKind_Postfix, 4, 0, str8_lit_comp(""), str8_lit_comp("-"), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_LShift, E2_ExprParseKind_Postfix, 5, 0, str8_lit_comp(""), str8_lit_comp("<<"), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_RShift, E2_ExprParseKind_Postfix, 5, 0, str8_lit_comp(""), str8_lit_comp(">>"), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_Less, E2_ExprParseKind_Postfix, 6, 0, str8_lit_comp(""), str8_lit_comp("<"), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_LtEq, E2_ExprParseKind_Postfix, 6, 0, str8_lit_comp(""), str8_lit_comp("<="), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_Grtr, E2_ExprParseKind_Postfix, 6, 0, str8_lit_comp(""), str8_lit_comp(">"), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_GrEq, E2_ExprParseKind_Postfix, 6, 0, str8_lit_comp(""), str8_lit_comp(">="), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_EqEq, E2_ExprParseKind_Postfix, 7, 0, str8_lit_comp(""), str8_lit_comp("=="), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_NtEq, E2_ExprParseKind_Postfix, 7, 0, str8_lit_comp(""), str8_lit_comp("!="), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_BitAnd, E2_ExprParseKind_Postfix, 8, 0, str8_lit_comp(""), str8_lit_comp("&"), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_BitXor, E2_ExprParseKind_Postfix, 9, 0, str8_lit_comp(""), str8_lit_comp("^"), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_BitOr, E2_ExprParseKind_Postfix, 10, 0, str8_lit_comp(""), str8_lit_comp("|"), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_LogAnd, E2_ExprParseKind_Postfix, 11, 0, str8_lit_comp(""), str8_lit_comp("&&"), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_LogOr, E2_ExprParseKind_Postfix, 12, 0, str8_lit_comp(""), str8_lit_comp("||"), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_Cond, E2_ExprParseKind_Postfix, 14, 0, str8_lit_comp(""), str8_lit_comp("?"), str8_lit_comp(""), str8_lit_comp(":")}, }; U8 e2_type_kind_basic_byte_size_table[61] = diff --git a/src/eval2/generated/eval2.meta.h b/src/eval2/generated/eval2.meta.h index eedb460f..2bc7ea5b 100644 --- a/src/eval2/generated/eval2.meta.h +++ b/src/eval2/generated/eval2.meta.h @@ -6,42 +6,63 @@ #ifndef EVAL2_META_H #define EVAL2_META_H -typedef enum E2_OpKind +typedef enum E2_ExprKind { -E2_OpKind_Null, -E2_OpKind_Dot, -E2_OpKind_Index, -E2_OpKind_Call, -E2_OpKind_DerefAsm, -E2_OpKind_SizeOf, -E2_OpKind_Deref, -E2_OpKind_Address, -E2_OpKind_Pos, -E2_OpKind_Neg, -E2_OpKind_LogNot, -E2_OpKind_BitNot, -E2_OpKind_Mul, -E2_OpKind_Div, -E2_OpKind_Mod, -E2_OpKind_Add, -E2_OpKind_Sub, -E2_OpKind_LShift, -E2_OpKind_RShift, -E2_OpKind_Less, -E2_OpKind_LtEq, -E2_OpKind_Grtr, -E2_OpKind_GrEq, -E2_OpKind_EqEq, -E2_OpKind_NtEq, -E2_OpKind_BitAnd, -E2_OpKind_BitXor, -E2_OpKind_BitOr, -E2_OpKind_LogAnd, -E2_OpKind_LogOr, -E2_OpKind_Define, -E2_OpKind_Cond, -E2_OpKind_COUNT, -} E2_OpKind; +E2_ExprKind_Null, +E2_ExprKind_Identifier, +E2_ExprKind_MacroArg, +E2_ExprKind_TypeIdentifier, +E2_ExprKind_Numeric, +E2_ExprKind_StringLiteral, +E2_ExprKind_CharLiteral, +E2_ExprKind_Ptr, +E2_ExprKind_Array, +E2_ExprKind_Function, +E2_ExprKind_Const, +E2_ExprKind_Volatile, +E2_ExprKind_Unsigned, +E2_ExprKind_Signed, +E2_ExprKind_Dot, +E2_ExprKind_Index, +E2_ExprKind_Call, +E2_ExprKind_DerefAsm, +E2_ExprKind_SizeOf, +E2_ExprKind_TypeOf, +E2_ExprKind_Cast, +E2_ExprKind_Deref, +E2_ExprKind_Address, +E2_ExprKind_Pos, +E2_ExprKind_Neg, +E2_ExprKind_LogNot, +E2_ExprKind_BitNot, +E2_ExprKind_Mul, +E2_ExprKind_Div, +E2_ExprKind_Mod, +E2_ExprKind_Add, +E2_ExprKind_Sub, +E2_ExprKind_LShift, +E2_ExprKind_RShift, +E2_ExprKind_Less, +E2_ExprKind_LtEq, +E2_ExprKind_Grtr, +E2_ExprKind_GrEq, +E2_ExprKind_EqEq, +E2_ExprKind_NtEq, +E2_ExprKind_BitAnd, +E2_ExprKind_BitXor, +E2_ExprKind_BitOr, +E2_ExprKind_LogAnd, +E2_ExprKind_LogOr, +E2_ExprKind_Define, +E2_ExprKind_Macro, +E2_ExprKind_Cond, +E2_ExprKind_COUNT, +} E2_ExprKind; + +typedef enum E2_LangKind +{ +E2_LangKind_CLike, +} E2_LangKind; typedef enum E2_TypeKind { @@ -121,8 +142,33 @@ E2_TypeKind_FirstMeta = E2_TypeKind_MetaExpr, E2_TypeKind_LastMeta = E2_TypeKind_MetaDescription, } E2_TypeKind; +typedef struct E2_ExprKindParseInfo E2_ExprKindParseInfo; +struct E2_ExprKindParseInfo +{ +E2_ExprKind expr_kind; +E2_ExprParseKind parse_kind; +S64 precedence; +B8 reverse_children; +String8 pre; +String8 sep; +String8 post; +String8 chain; +}; + +typedef struct E2_LangInfo E2_LangInfo; +struct E2_LangInfo +{ +U64 expr_kind_parse_infos_count; +E2_ExprKindParseInfo *expr_kind_parse_infos; +}; + C_LINKAGE_BEGIN -extern E2_OpInfo e2_op_kind_info_table[32]; +extern B8 e2_expr_kind_allow_type_operands_table[48]; +extern B8 e2_expr_kind_is_type_expr_table[48]; +extern B8 e2_expr_kind_is_first_operand_type_maybe_table[48]; +extern U64 e2_expr_kind_target_operand_count_table[48]; +extern E2_LangInfo e2_lang_kind_info_table[1]; +extern E2_ExprKindParseInfo e2_expr_kind_parse_info_table__clike[43]; extern U8 e2_type_kind_basic_byte_size_table[61]; extern String8 e2_type_kind_basic_string_table[61]; diff --git a/src/eval2/tests/eval2_tests.c b/src/eval2/tests/eval2_tests.c new file mode 100644 index 00000000..4de538d7 --- /dev/null +++ b/src/eval2/tests/eval2_tests.c @@ -0,0 +1,191 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +Test(eval2_regressions) +{ + E2_ConsTypeMap *cons_types = e2_cons_type_map_alloc(); + e2_select_cons_type_map(cons_types); + String8 strings[] = + { + s("int32(*)(int32, int32)"), + s("int32 *(*)(int32, int32)"), + s("int32 **(*)(int32, int32)"), + s("123(1, 2, 3)"), + s("int32 (*) [100]"), + s("(3 * 4) + 2"), + s("cast (float32) 123"), + s("int32 *"), + s("3 * 4 + 2"), + s("int32[100]"), + s("3 * 4"), + s("foo"), + s("foo.bar"), + s("[123]"), + s("123[456]"), + s("2 + (3 * 4)"), + s("int32 == int32"), + s("123, 456"), + s("222.f"), + s("123 as float32"), + s("cast float32 123"), + s("(int32 *)123"), + s("bar(a, b) = a + b"), + s("foo = 123"), + s("1 > 2"), + s("1 ? 123 : 456"), + s("0 ? 123 : 456"), + s("1 ? \"Test\" : 888"), + s("'a'"), + s("'b'"), + s("(float32)222"), + s("(float64)222"), + s("222.0"), + s("123"), + s("123 + 456"), + s("!1"), + s("!0"), + s("1 + 1 + 1"), + s("40 / 0"), + s("123 | 456"), + s("123 + "), + s("-123"), + s("sizeof 123"), + }; + for EachElement(idx, strings) + { + Temp scratch = scratch_begin(0, 0); + String8List msgs = {0}; + + // rjf: string -> expr + E2_Expr *expr = &e2_expr_nil; + { + E2_ParseState state = {0}; + B32 identifier_is_type = 0; + for(;;) + { + E2_Parse parse = e2_parse_from_string(scratch.arena, &state, identifier_is_type, E2_LangKind_CLike, strings[idx]); + identifier_is_type = 0; + expr = parse.expr; + for EachNode(n, E2_Msg, parse.msgs.first) + { + str8_list_push(scratch.arena, &msgs, n->string); + } + if(parse.status == E2_ParseStatus_CheckIdentifierIsType) + { + identifier_is_type = identifier_is_type || str8_match(parse.identifier, s("int32"), 0); + identifier_is_type = identifier_is_type || str8_match(parse.identifier, s("float32"), 0); + identifier_is_type = identifier_is_type || str8_match(parse.identifier, s("float64"), 0); + } + if(e2_parse_status_is_terminal(parse.status)) + { + break; + } + } + } + + // rjf: expr -> ir tree + E2_IRNode *irtree = &e2_irnode_nil; + { + E2_IRNode *resolve_result = &e2_irnode_nil; + E2_Val compile_time_eval_result = {0}; + E2_CompileState state = {0}; + for(;;) + { + E2_Compile compile = e2_compile_from_expr(scratch.arena, &state, resolve_result, compile_time_eval_result, expr); + irtree = compile.irtree; + resolve_result = &e2_irnode_nil; + if(compile.status == E2_CompileStatus_MissedIdentifierResolution) + { + E2_IRNode *irnode = &e2_irnode_nil; + if(str8_match(compile.identifier, s("float32"), 0)) + { + irnode = e2_irnode_type(scratch.arena, e2_type_key_basic(E2_TypeKind_F32)); + } + else if(str8_match(compile.identifier, s("float64"), 0)) + { + irnode = e2_irnode_type(scratch.arena, e2_type_key_basic(E2_TypeKind_F64)); + } + else if(str8_match(compile.identifier, s("int32"), 0)) + { + irnode = e2_irnode_type(scratch.arena, e2_type_key_basic(E2_TypeKind_S32)); + } + else if(str8_match(compile.identifier, s("int64"), 0)) + { + irnode = e2_irnode_type(scratch.arena, e2_type_key_basic(E2_TypeKind_S64)); + } + else + { + irnode = e2_irnode_const_u64_or_smaller(scratch.arena, 123); + } + resolve_result = irnode; + } + if(compile.status == E2_CompileStatus_MemberAccess) + { + resolve_result = e2_irnode_const_u64_or_smaller(scratch.arena, 456); + } + if(compile.status == E2_CompileStatus_IndexAccess) + { + resolve_result = e2_irnode_const_u64_or_smaller(scratch.arena, 111); + } + if(compile.status == E2_CompileStatus_Call) + { + resolve_result = e2_irnode_const_u64_or_smaller(scratch.arena, 123456); + } + if(compile.status == E2_CompileStatus_CompileTimeEval) + { + String8 bytecode = e2_bytecode_from_irnode(scratch.arena, irtree); + E2_InterpState interp_state = {0}; + E2_SpaceMap space_map = {0}; + E2_Interp interp = e2_interp_from_bytecode(scratch.arena, &interp_state, &space_map, bytecode); + compile_time_eval_result = interp.val; + } + if(e2_compile_status_is_terminal(compile.status)) + { + break; + } + } + } + + // rjf: ir tree -> bytecode + String8 bytecode = e2_bytecode_from_irnode(scratch.arena, irtree); + + // rjf: bytecode -> value + E2_Val val = {0}; + { + E2_InterpState state = {0}; + E2_SpaceMap space_map = {0}; + for(;;) + { + E2_Interp interp = e2_interp_from_bytecode(scratch.arena, &state, &space_map, bytecode); + val = interp.val; + if(e2_interp_status_is_terminal(interp.status)) + { + if(interp.status != E2_InterpStatus_Good) + { + str8_list_pushf(scratch.arena, &msgs, "Interpretation error (%i).", interp.status); + } + break; + } + } + } + + // rjf: message string list -> string + StringJoin join = {.sep = s(" ")}; + String8 msgs_string = str8_list_join(scratch.arena, &msgs, &join); + + // rjf: log + String8 log = str8f(scratch.arena, "%S -> %I64d (f32: %f) (f64: %f) *%s* (type: %S) %s%S\n", + strings[idx], + val.s64, + val.f32, + val.f64, + irtree->mode == E2_Mode_Type ? "type" : + irtree->mode == E2_Mode_Value ? "value" : + "address", + e2_string_from_type_key(scratch.arena, irtree->type_key), + msgs_string.size != 0 ? " // " : "", msgs_string); + log_info(log); + + scratch_end(scratch); + } +} diff --git a/src/eval_visualization/eval_visualization_core.c b/src/eval_visualization/eval_visualization_core.c index 23cd64e3..35605bec 100644 --- a/src/eval_visualization/eval_visualization_core.c +++ b/src/eval_visualization/eval_visualization_core.c @@ -208,8 +208,6 @@ ev_view_alloc(void) view->arena = arena; view->expand_slots_count = 256; view->expand_slots = push_array(arena, EV_ExpandSlot, view->expand_slots_count); - view->key_view_rule_slots_count = 256; - view->key_view_rule_slots = push_array(arena, EV_KeyViewRuleSlot, view->key_view_rule_slots_count); return view; } @@ -246,36 +244,6 @@ ev_expansion_from_key(EV_View *view, EV_Key key) return (node != 0 && node->expanded); } -internal String8 -ev_view_rule_from_key(EV_View *view, EV_Key key) -{ - String8 result = {0}; - - //- rjf: key -> hash * slot idx * slot - U64 hash = ev_hash_from_key(key); - U64 slot_idx = hash%view->key_view_rule_slots_count; - EV_KeyViewRuleSlot *slot = &view->key_view_rule_slots[slot_idx]; - - //- rjf: slot -> existing node - EV_KeyViewRuleNode *existing_node = 0; - for(EV_KeyViewRuleNode *n = slot->first; n != 0; n = n->hash_next) - { - if(ev_key_match(n->key, key)) - { - existing_node = n; - break; - } - } - - //- rjf: node -> result - if(existing_node != 0) - { - result = str8(existing_node->buffer, existing_node->buffer_string_size); - } - - return result; -} - internal void ev_key_set_expansion(EV_View *view, EV_Key parent_key, EV_Key key, B32 expanded) { @@ -348,44 +316,6 @@ ev_key_set_expansion(EV_View *view, EV_Key parent_key, EV_Key key, B32 expanded) } } -internal void -ev_key_set_view_rule(EV_View *view, EV_Key key, String8 view_rule_string) -{ - //- rjf: key -> hash * slot idx * slot - U64 hash = ev_hash_from_key(key); - U64 slot_idx = hash%view->key_view_rule_slots_count; - EV_KeyViewRuleSlot *slot = &view->key_view_rule_slots[slot_idx]; - - //- rjf: slot -> existing node - EV_KeyViewRuleNode *existing_node = 0; - for(EV_KeyViewRuleNode *n = slot->first; n != 0; n = n->hash_next) - { - if(ev_key_match(n->key, key)) - { - existing_node = n; - break; - } - } - - //- rjf: existing node * new node -> node - EV_KeyViewRuleNode *node = existing_node; - if(node == 0) - { - node = push_array(view->arena, EV_KeyViewRuleNode, 1); - DLLPushBack_NP(slot->first, slot->last, node, hash_next, hash_prev); - node->key = key; - node->buffer_cap = 512; - node->buffer = push_array(view->arena, U8, node->buffer_cap); - } - - //- rjf: mutate node - if(node != 0) - { - node->buffer_string_size = ClampTop(view_rule_string.size, node->buffer_cap); - MemoryCopy(node->buffer, view_rule_string.str, node->buffer_string_size); - } -} - //////////////////////////////// //~ rjf: View Rule Info Table Building / Selection / Lookups diff --git a/src/eval_visualization/eval_visualization_core.h b/src/eval_visualization/eval_visualization_core.h index d3a4d35b..552aa534 100644 --- a/src/eval_visualization/eval_visualization_core.h +++ b/src/eval_visualization/eval_visualization_core.h @@ -40,26 +40,6 @@ struct EV_ExpandSlot EV_ExpandNode *last; }; -//- rjf: hash table for view rules - -typedef struct EV_KeyViewRuleNode EV_KeyViewRuleNode; -struct EV_KeyViewRuleNode -{ - EV_KeyViewRuleNode *hash_next; - EV_KeyViewRuleNode *hash_prev; - EV_Key key; - U8 *buffer; - U64 buffer_cap; - U64 buffer_string_size; -}; - -typedef struct EV_KeyViewRuleSlot EV_KeyViewRuleSlot; -struct EV_KeyViewRuleSlot -{ - EV_KeyViewRuleNode *first; - EV_KeyViewRuleNode *last; -}; - //- rjf: view state bundle typedef struct EV_View EV_View; @@ -69,9 +49,6 @@ struct EV_View EV_ExpandSlot *expand_slots; U64 expand_slots_count; EV_ExpandNode *free_expand_node; - EV_KeyViewRuleSlot *key_view_rule_slots; - U64 key_view_rule_slots_count; - EV_KeyViewRuleNode *free_key_view_rule_node; }; //////////////////////////////// @@ -328,9 +305,7 @@ internal void ev_view_release(EV_View *view); //- rjf: lookups / mutations internal EV_ExpandNode *ev_expand_node_from_key(EV_View *view, EV_Key key); internal B32 ev_expansion_from_key(EV_View *view, EV_Key key); -internal String8 ev_view_rule_from_key(EV_View *view, EV_Key key); internal void ev_key_set_expansion(EV_View *view, EV_Key parent_key, EV_Key key, B32 expanded); -internal void ev_key_set_view_rule(EV_View *view, EV_Key key, String8 view_rule_string); //////////////////////////////// //~ rjf: View Rule Info Table Building / Selection / Lookups diff --git a/src/file_stream/file_stream.c b/src/file_stream/file_stream.c index 26533c17..5917c920 100644 --- a/src/file_stream/file_stream.c +++ b/src/file_stream/file_stream.c @@ -32,7 +32,7 @@ fs_change_gen(void) //~ rjf: Cache Interaction internal AC_Artifact -fs_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out, U64 *gen_out) +fs_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, U64 *gen_out) { ProfBeginFunction(); Temp scratch = scratch_begin(0, 0); @@ -146,7 +146,7 @@ fs_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out, U64 *gen_out (file_handle_is_valid || pre_props.flags & FilePropertyFlag_IsFolder)); if(!read_good) { - retry_out[0] = 1; + status_out[0] = AC_Status_NeedRetry; ProfScope("abort") { arena_release(data_arena); diff --git a/src/file_stream/file_stream.h b/src/file_stream/file_stream.h index 579f68cc..56e09f66 100644 --- a/src/file_stream/file_stream.h +++ b/src/file_stream/file_stream.h @@ -55,7 +55,7 @@ internal U64 fs_change_gen(void); //////////////////////////////// //~ rjf: Artifact Cache Hooks / Accessing API -internal AC_Artifact fs_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out, U64 *gen_out); +internal AC_Artifact fs_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, U64 *gen_out); internal void fs_artifact_destroy(AC_Artifact artifact); internal C_Key fs_key_from_path_range(String8 path, Rng1U64 range, U64 endt_us); @@ -66,6 +66,9 @@ internal U128 fs_hash_from_path_range(String8 path, Rng1U64 range, U64 endt_us); //////////////////////////////// //~ rjf: Asynchronous Tick +#if !defined(NEED_ASYNC) +# define NEED_ASYNC 1 +#endif internal void fs_async_tick(void); #endif // FILE_STREAM_H diff --git a/src/http/http.h b/src/http/http.h index c7d570b8..dba2f37e 100644 --- a/src/http/http.h +++ b/src/http/http.h @@ -161,6 +161,9 @@ internal HTTP_StatusKind http_status_kind_from_code(HTTP_StatusCode code); //////////////////////////////////////////////////////////////// //~ rjf: @per_os_impl Top-Level Layer Calls +#if !defined(NEED_ASYNC) +# define NEED_ASYNC 1 +#endif internal void http_init(void); internal void http_async_tick(void); diff --git a/src/lib_raddbg_markup/raddbg_markup.h b/src/lib_raddbg_markup/raddbg_markup.h index a12f64b8..715f8754 100644 --- a/src/lib_raddbg_markup/raddbg_markup.h +++ b/src/lib_raddbg_markup/raddbg_markup.h @@ -20,43 +20,45 @@ //~ Usage Macros #if !defined(RADDBG_MARKUP_STUBS) -# define raddbg_is_attached(...) raddbg_is_attached__impl() -# define raddbg_thread_id(...) raddbg_thread_id__impl() -# define raddbg_thread_name(...) raddbg_thread_name__impl(raddbg_thread_id(), __VA_ARGS__) -# define raddbg_thread_id_name(id, ...) raddbg_thread_name__impl((id), __VA_ARGS__) -# define raddbg_thread_color_u32(u32) raddbg_thread_color__impl(raddbg_thread_id(), (u32)) -# define raddbg_thread_color_rgba(r, g, b, a) raddbg_thread_color__impl(raddbg_thread_id(), ((unsigned int)((r)*255) << 24) | ((unsigned int)((g)*255) << 16) | ((unsigned int)((b)*255) << 8) | ((unsigned int)(a)*255)) -# define raddbg_thread_id_color_u32(id, u32) raddbg_thread_color__impl((id), (u32)) -# define raddbg_thread_id_color_rgba(id, r, g, b, a) raddbg_thread_color__impl((id), ((unsigned int)((r)*255) << 24) | ((unsigned int)((g)*255) << 16) | ((unsigned int)((b)*255) << 8) | ((unsigned int)(a)*255)) -# define raddbg_break(...) raddbg_break__impl() -# define raddbg_break_if(expr, ...) ((expr) ? raddbg_break__impl() : (void)0) -# define raddbg_watch(fmt, ...) raddbg_watch__impl((fmt), __VA_ARGS__) -# define raddbg_pin(expr, ...) /* NOTE(rjf): inspected by debugger ui - does not change program execution */ -# define raddbg_log(...) raddbg_log__impl(__VA_ARGS__) -# define raddbg_entry_point(...) raddbg_exe_data char raddbg_gen_data_id()[] = ("entry_point: \"" #__VA_ARGS__ "\"") -# define raddbg_type_view(type, ...) raddbg_exe_data char raddbg_gen_data_id()[] = ("type_view: {type: ```" #type "```, expr: ```" #__VA_ARGS__ "```}") -# define raddbg_add_breakpoint(ptr, size, r, w, x) raddbg_add_or_remove_breakpoint__impl((ptr), (1), (size), (r), (w), (x)) -# define raddbg_remove_breakpoint(ptr, size, r, w, x) raddbg_add_or_remove_breakpoint__impl((ptr), (0), (size), (r), (w), (x)) -# define raddbg_annotate_vaddr_range(ptr, size, ...) raddbg_annotate_vaddr_range__impl((ptr), (size), __VA_ARGS__) +# define raddbg_is_attached(...) raddbg_is_attached__impl() +# define raddbg_thread_id(...) raddbg_thread_id__impl() +# define raddbg_thread_name(...) raddbg_thread_name__impl(raddbg_thread_id(), __VA_ARGS__) +# define raddbg_thread_id_name(id, ...) raddbg_thread_name__impl((id), __VA_ARGS__) +# define raddbg_thread_color_u32(u32) raddbg_thread_color__impl(raddbg_thread_id(), (u32)) +# define raddbg_thread_color_rgba(r, g, b, a) raddbg_thread_color__impl(raddbg_thread_id(), ((unsigned int)((r)*255) << 24) | ((unsigned int)((g)*255) << 16) | ((unsigned int)((b)*255) << 8) | ((unsigned int)(a)*255)) +# define raddbg_thread_id_color_u32(id, u32) raddbg_thread_color__impl((id), (u32)) +# define raddbg_thread_id_color_rgba(id, r, g, b, a) raddbg_thread_color__impl((id), ((unsigned int)((r)*255) << 24) | ((unsigned int)((g)*255) << 16) | ((unsigned int)((b)*255) << 8) | ((unsigned int)(a)*255)) +# define raddbg_break(...) raddbg_break__impl() +# define raddbg_break_if(expr, ...) ((expr) ? raddbg_break__impl() : (void)0) +# define raddbg_watch(fmt, ...) raddbg_watch__impl((fmt), __VA_ARGS__) +# define raddbg_pin(expr, ...) /* NOTE(rjf): inspected by debugger ui - does not change program execution */ +# define raddbg_log(...) raddbg_log__impl(__VA_ARGS__) +# define raddbg_entry_point(...) raddbg_exe_data char raddbg_gen_data_id()[] = ("entry_point: \"" #__VA_ARGS__ "\"") +# define raddbg_type_view(type, ...) raddbg_exe_data char raddbg_gen_data_id()[] = ("type_view: {type: ```" #type "```, expr: ```" #__VA_ARGS__ "```}") +# define raddbg_add_breakpoint(ptr, size, r, w, x) raddbg_add_or_remove_breakpoint__impl((ptr), (1), (size), (r), (w), (x)) +# define raddbg_remove_breakpoint(ptr, size, r, w, x) raddbg_add_or_remove_breakpoint__impl((ptr), (0), (size), (r), (w), (x)) +# define raddbg_annotate_vaddr_range(ptr, size, ...) raddbg_annotate_vaddr_range__impl((ptr), (size), __VA_ARGS__) +# define raddbg_load_module(ptr, size, name, dbg_path) raddbg_load_module__impl((ptr), (size), (name), (dbg_path)) #else -# define raddbg_is_attached(...) (0) -# define raddbg_thread_id(...) ((void)0) -# define raddbg_thread_name(fmt, ...) ((void)0) -# define raddbg_thread_id_name(id, fmt, ...) ((void)0) -# define raddbg_thread_color_u32(u32) ((void)0) -# define raddbg_thread_color_rgba(r, g, b, a) ((void)0) -# define raddbg_thread_id_color_u32(id, u32) ((void)0) -# define raddbg_thread_id_color_rgba(id, r, g, b, a) ((void)0) -# define raddbg_break(...) ((void)0) -# define raddbg_break_if(expr, ...) ((void)expr) -# define raddbg_watch(fmt, ...) ((void)0) +# define raddbg_is_attached(...) (0) +# define raddbg_thread_id(...) ((void)0) +# define raddbg_thread_name(fmt, ...) ((void)0) +# define raddbg_thread_id_name(id, fmt, ...) ((void)0) +# define raddbg_thread_color_u32(u32) ((void)0) +# define raddbg_thread_color_rgba(r, g, b, a) ((void)0) +# define raddbg_thread_id_color_u32(id, u32) ((void)0) +# define raddbg_thread_id_color_rgba(id, r, g, b, a) ((void)0) +# define raddbg_break(...) ((void)0) +# define raddbg_break_if(expr, ...) ((void)expr) +# define raddbg_watch(fmt, ...) ((void)0) # define raddbg_pin(expr, ...) -# define raddbg_log(fmt, ...) ((void)0) -# define raddbg_entry_point(...) struct raddbg_gen_data_id(){int __unused__;} -# define raddbg_type_view(type, ...) struct raddbg_gen_data_id(){int __unused__;} -# define raddbg_add_breakpoint(ptr, size, r, w, x) ((void)0) -# define raddbg_remove_breakpoint(ptr, size, r, w, x) ((void)0) -# define raddbg_annotate_vaddr_range(ptr, size, ...) ((void)0) +# define raddbg_log(fmt, ...) ((void)0) +# define raddbg_entry_point(...) struct raddbg_gen_data_id(){int __unused__;} +# define raddbg_type_view(type, ...) struct raddbg_gen_data_id(){int __unused__;} +# define raddbg_add_breakpoint(ptr, size, r, w, x) ((void)0) +# define raddbg_remove_breakpoint(ptr, size, r, w, x) ((void)0) +# define raddbg_annotate_vaddr_range(ptr, size, ...) ((void)0) +# define raddbg_load_module(ptr, size, name, dbg_path) ((void)0) #endif //////////////////////////////// @@ -466,6 +468,39 @@ raddbg_annotate_vaddr_range__impl(void *ptr, unsigned __int64 size, char *fmt, . } } +void +raddbg_load_module__impl(void *ptr, unsigned __int64 size, char *name, char *dbg_path) +{ + if(raddbg_is_attached()) + { +#pragma pack(push, 8) + typedef struct RADDBG_LoadModuleInfo RADDBG_LoadModuleInfo; + struct RADDBG_LoadModuleInfo + { + unsigned __int64 vaddr; + unsigned __int64 size; + unsigned __int64 name_vaddr; + unsigned __int64 dbg_path_vaddr; + }; +#pragma pack(pop) + RADDBG_LoadModuleInfo info; + info.vaddr = (unsigned __int64)ptr; + info.size = size; + info.name_vaddr = (unsigned __int64)name; + info.dbg_path_vaddr = (unsigned __int64)dbg_path; +#pragma warning(push) +#pragma warning(disable: 6320 6322) + __try + { + RaiseException(0x00524157u, 0, sizeof(info) / sizeof(void *), (const ULONG_PTR *)&info); + } + __except(1) + { + } +#pragma warning(pop) + } +} + #endif // defined(RADDBG_MARKUP_IMPLEMENTATION) #endif // defined(_WIN32) && !defined(RADDBG_MARKUP_STUBS) diff --git a/src/lib_rdi/rdi.c b/src/lib_rdi/rdi.c index 84f35ba9..f7294023 100644 --- a/src/lib_rdi/rdi.c +++ b/src/lib_rdi/rdi.c @@ -78,7 +78,7 @@ RDI_EVAL_CTRLBITS(8, 0, 1), RDI_EVAL_CTRLBITS(16, 0, 1), RDI_EVAL_CTRLBITS(32, 0, 1), RDI_EVAL_CTRLBITS(64, 0, 1), -RDI_EVAL_CTRLBITS(1, 0, 1), +RDI_EVAL_CTRLBITS(8, 0, 1), RDI_EVAL_CTRLBITS(2, 1, 1), RDI_EVAL_CTRLBITS(2, 1, 1), RDI_EVAL_CTRLBITS(2, 2, 1), @@ -92,15 +92,15 @@ RDI_EVAL_CTRLBITS(2, 2, 1), RDI_EVAL_CTRLBITS(2, 2, 1), RDI_EVAL_CTRLBITS(2, 2, 1), RDI_EVAL_CTRLBITS(2, 1, 1), -RDI_EVAL_CTRLBITS(1, 2, 1), -RDI_EVAL_CTRLBITS(1, 2, 1), -RDI_EVAL_CTRLBITS(1, 1, 1), -RDI_EVAL_CTRLBITS(1, 2, 1), -RDI_EVAL_CTRLBITS(1, 2, 1), -RDI_EVAL_CTRLBITS(1, 2, 1), -RDI_EVAL_CTRLBITS(1, 2, 1), -RDI_EVAL_CTRLBITS(1, 2, 1), -RDI_EVAL_CTRLBITS(1, 2, 1), +RDI_EVAL_CTRLBITS(2, 2, 1), +RDI_EVAL_CTRLBITS(2, 2, 1), +RDI_EVAL_CTRLBITS(2, 1, 1), +RDI_EVAL_CTRLBITS(2, 2, 1), +RDI_EVAL_CTRLBITS(2, 2, 1), +RDI_EVAL_CTRLBITS(2, 2, 1), +RDI_EVAL_CTRLBITS(2, 2, 1), +RDI_EVAL_CTRLBITS(2, 2, 1), +RDI_EVAL_CTRLBITS(2, 2, 1), RDI_EVAL_CTRLBITS(1, 1, 1), RDI_EVAL_CTRLBITS(1, 1, 1), RDI_EVAL_CTRLBITS(2, 1, 1), diff --git a/src/lib_rdi_make/rdi_make.c b/src/lib_rdi_make/rdi_make.c index 86f9970d..fe187d22 100644 --- a/src/lib_rdi_make/rdi_make.c +++ b/src/lib_rdi_make/rdi_make.c @@ -1807,27 +1807,10 @@ rdim_bake_name_map_insert(RDIM_Arena *arena, RDIM_BakeNameMapTopology *map_topol { slot = map->slots[slot_idx] = rdim_push_array(arena, RDIM_BakeNameChunkList, 1); } - RDI_S32 is_duplicate = 0; - for(RDIM_BakeNameChunkNode *n = slot->first; n != 0; n = n->next) - { - for(RDI_U64 n_idx = 0; n_idx < n->count; n_idx += 1) - { - if(rdim_str8_match(n->v[n_idx].string, string, 0) && - n->v[n_idx].idx == idx) - { - is_duplicate = 1; - goto break_all; - } - } - } - break_all:; - if(!is_duplicate) - { - RDIM_BakeName *bstr = rdim_bake_name_chunk_list_push(arena, slot, chunk_cap); - bstr->string = string; - bstr->idx = idx; - bstr->hash = hash; - } + RDIM_BakeName *bstr = rdim_bake_name_chunk_list_push(arena, slot, chunk_cap); + bstr->string = string; + bstr->idx = idx; + bstr->hash = hash; } } diff --git a/src/linker/base_ext/base_arrays.c b/src/linker/base_ext/base_arrays.c index 834f4540..06a80f71 100644 --- a/src/linker/base_ext/base_arrays.c +++ b/src/linker/base_ext/base_arrays.c @@ -34,6 +34,23 @@ void_list_push(Arena *arena, VoidList *list, void *v) return n; } +internal void +u32_list_push_node(U32List *list, U32Node *n) +{ + SLLQueuePush(list->first, list->last, n); + list->count += 1; +} + +internal U32Node * +u32_list_push(Arena *arena, U32List *list, U32 data) +{ + U32Node *n = push_array(arena, U32Node, 1); + n->next = 0; + n->data = data; + u32_list_push_node(list, n); + return n; +} + internal void u64_list_push_node(U64List *list, U64Node *n) { @@ -264,4 +281,3 @@ s64_array_from_list(Arena *arena, S64List *list) for EachNode(n, S64Node, list->first) { result.v[result.count++] = n->v; } return result; } - diff --git a/src/linker/base_ext/base_arrays.h b/src/linker/base_ext/base_arrays.h index 97350685..ec4894dd 100644 --- a/src/linker/base_ext/base_arrays.h +++ b/src/linker/base_ext/base_arrays.h @@ -9,6 +9,7 @@ typedef struct U64Node { U64 data; struct U64Node *next; } U64Node; typedef struct S64Node { S64 v; struct S64Node *next; } S64Node; typedef struct VoidList { U64 count; VoidNode *first, *last; } VoidList; +typedef struct U32List { U64 count; U32Node *first, *last; } U32List; typedef struct U64List { U64 count; U64Node *first, *last; } U64List; typedef struct S64List { U64 count; S64Node *first, *last; } S64List; @@ -20,6 +21,9 @@ internal U64 void_list_count_nodes (VoidNode *head); internal void void_node_concat (VoidNode **head, VoidNode *node); internal void void_node_concat_atomic(VoidNode **head, VoidNode *node); +internal void u32_list_push_node (U32List *list, U32Node *n); +internal U32Node * u32_list_push (Arena *arena, U32List *list, U32 v); + internal void u64_list_push_node (U64List *list, U64Node *n); internal U64Node * u64_list_push (Arena *arena, U64List *list, U64 v); internal void u64_list_concat_in_place(U64List *list, U64List *to_concat); @@ -44,4 +48,3 @@ internal void s64_list_push_node (S64List *list, S64Node *n); internal S64Node * s64_list_push (Arena *arena, S64List *list, S64 v); internal void s64_list_concat_in_place(S64List *list, S64List *to_concat); internal S64Array s64_array_from_list (Arena *arena, S64List *list); - diff --git a/src/linker/lnk.c b/src/linker/lnk.c index 2850b08b..be253f18 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -8,6 +8,7 @@ #define ARENA_FREE_LIST 1 #define NO_ASYNC 1 +#define NO_WIN32_RIO 1 // --- Code Base --------------------------------------------------------------- @@ -102,6 +103,7 @@ #include "lnk_log.h" #include "lnk_timer.h" +#include "lnk_hasher.h" #include "lnk_io.h" #include "lnk_cmd_line.h" #include "lnk_config.h" @@ -115,6 +117,7 @@ #include "lnk_log.c" #include "lnk_timer.c" +#include "lnk_hasher.c" #include "lnk_io.c" #include "lnk_cmd_line.c" #include "lnk_config.c" @@ -149,7 +152,6 @@ lnk_make_default_cmd_line(Arena *arena, LNK_CmdLine user_cmd_line) "/RAD_BOOT_MODE:LINKER", //"/RAD_BUILD_EXP", "/RAD_BUILD_IMPLIB", - "/RAD_TPYE_HASH_ALG:BLAKE3", "/RAD_AGE:1", "/RAD_CHECK_UNUSED_DELAY_LOAD_DLL", "/RAD_DO_MERGE", @@ -171,6 +173,18 @@ lnk_make_default_cmd_line(Arena *arena, LNK_CmdLine user_cmd_line) "/RAD_SORT_IMPORTS", (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, + + // Set BLAKE3 as the default to match the LLVM default. + // + // When hash kinds conflict, radlink discards any .debug$H sections + // whose hash kind does not match the selected default. + "/RAD_DEBUG_TYPE_HASH:BLAKE3", + + // Use LLVM significant addresses hints for the /OPT:ICF. + "/LLVM_ADDRSIG", + + // By default keep full type names, override when TPI/IPI streams overflow. + "/RAD_PDB_HASH_TYPE_NAMES:NONE", }; char *push_opts[] = { @@ -234,11 +248,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 -#if 0 - if (lnk_cmd_line_has_switch(cmd_line, LNK_CmdSwitch_Force)) { + if (lnk_cmd_line_has_switch(user_cmd_line, LNK_CmdSwitch_Force)) { g_error_mode_arr[LNK_Error_UnresolvedSymbol] = LNK_ErrorMode_Continue; + g_error_mode_arr[LNK_Error_RelocationAgainstRemovedSection] = LNK_ErrorMode_Continue; } -#endif #undef DefaultOpt #undef PushOpt @@ -464,7 +477,7 @@ lnk_manifest_from_inputs(Arena *arena, if (input_manifest_path_list.node_count > 0) { ProfBegin("Merge Manifests"); - + String8 linker_manifest = lnk_make_linker_manifest(scratch.arena, manifest_uac, manifest_level, manifest_ui_access, unique_deps); // write linker manifest to temp file @@ -1210,8 +1223,10 @@ lnk_inputer_flush(Arena *arena, TP_Context *tp, LNK_Inputer *inputer, LNK_IO_Fla ProfBegin("Load Inputs From Disk"); String8Array thin_input_datas = lnk_read_data_from_file_path_parallel(tp, inputer->arena, io_flags, thin_input_paths); + B32 is_mapped = !!(io_flags & (LNK_IO_Flags_MemoryMapFilesReadWrite|LNK_IO_Flags_MemoryMapFilesReadOnly)); for EachIndex(thin_input_idx, thin_inputs_count) { thin_inputs[thin_input_idx]->has_disk_read_failed = thin_input_datas.v[thin_input_idx].size == 0; + thin_inputs[thin_input_idx]->owns_file_map = is_mapped && !thin_inputs[thin_input_idx]->has_disk_read_failed; thin_inputs[thin_input_idx]->data = thin_input_datas.v[thin_input_idx]; } ProfEnd(); @@ -1233,6 +1248,48 @@ lnk_inputer_flush(Arena *arena, TP_Context *tp, LNK_Inputer *inputer, LNK_IO_Fla return result; } +internal +THREAD_POOL_TASK_FUNC(lnk_release_file_map_task) +{ + LNK_Input **mapped_inputs = raw_task; + LNK_Input *input = mapped_inputs[task_id]; + file_map_view_close((FileMap){0}, input->data.str, r1u64(0, input->data.size)); + input->data = str8_zero(); + input->owns_file_map = 0; +} + +internal void +lnk_inputer_release_file_maps(TP_Context *tp, LNK_Inputer *inputer) +{ + Temp scratch = scratch_begin(0, 0); + + U64 mapped_input_count = 0; + for EachNode(input, LNK_Input, inputer->objs.first) { + mapped_input_count += input->owns_file_map; + } + for EachNode(input, LNK_Input, inputer->libs.first) { + mapped_input_count += input->owns_file_map; + } + + LNK_Input **mapped_inputs = push_array_no_zero(scratch.arena, LNK_Input *, mapped_input_count); + U64 mapped_input_idx = 0; + for EachNode(input, LNK_Input, inputer->objs.first) { + if (input->owns_file_map) { + mapped_inputs[mapped_input_idx++] = input; + } + } + for EachNode(input, LNK_Input, inputer->libs.first) { + if (input->owns_file_map) { + mapped_inputs[mapped_input_idx++] = input; + } + } + Assert(mapped_input_idx == mapped_input_count); + + tp_for_parallel(tp, 0, mapped_input_count, lnk_release_file_map_task, mapped_inputs); + + scratch_end(scratch); +} + internal void lnk_lib_member_ref_list_push_node(LNK_LibMemberRefList *list, LNK_LibMemberRef *node) { @@ -1570,6 +1627,11 @@ lnk_queue_lib_member(Arena *arena, LNK_LibMemberInfo *member_infos, U32 member_idx) { + U32 member_offset = memory_read32(lib->member_offsets + member_idx); + COFF_ArchiveMember member_info = coff_archive_member_from_offset(lib->data, member_offset); + COFF_DataType member_type = coff_data_type_from_data(member_info.data); + B32 is_import_member = (member_type == COFF_DataType_Import); + // associate link symbol to lib member for (LNK_Symbol *leader = link_symbol;;) { LNK_Symbol *slot = ins_atomic_ptr_eval_assign(&member_infos[member_idx].link, 0); @@ -1605,14 +1667,14 @@ lnk_queue_lib_member(Arena *arena, LNK_LibMemberRef *is_queued_import = is_thunk_import ? is_thunk_import : is_addr_import ? is_addr_import : 0; - if (is_queued_import) { + if (is_import_member && is_queued_import) { // do not queue second import member link -> flag member and continue U8 flag = str8_starts_with(link_symbol->name, str8_lit("__imp_")) ? LNK_LibMemberFlag_LinkedImp : LNK_LibMemberFlag_LinkedRegular; LNK_LibMemberInfo *import_member_infos = hash_map_search_raw_raw(&lib_member_info_hm, is_queued_import->lib); ins_atomic_u8_or(&import_member_infos[is_queued_import->member_idx].flags, flag); } else { B32 do_queue; - if (str8_starts_with(link_symbol->name, str8_lit("__imp_"))) { + if (is_import_member && str8_starts_with(link_symbol->name, str8_lit("__imp_"))) { U8 member_flags = ins_atomic_u8_or(&member_infos[member_idx].flags, LNK_LibMemberFlag_LinkedImp); do_queue = !(member_flags & LNK_LibMemberFlag_LinkedImp); } else { @@ -1641,43 +1703,62 @@ THREAD_POOL_TASK_FUNC(lnk_search_lib_task) LNK_LibMemberInfo *lib_member_infos = task->lib_member_infos; LNK_LibMemberRefList *member_ref_list = &task->member_ref_lists[task_id]; - for EachNode(c, LNK_SymbolHashTrieChunk, symtab->search_chunks[task_id].first) { - for EachIndex(i, c->count) { - LNK_Symbol *symbol = c->v[i].symbol; + LNK_SymbolHashTrieChunk *start_chunk = task->reset_search_cursor ? 0 : lib->search_cursor_chunks[task_id]; + U64 start_idx = task->reset_search_cursor ? 0 : lib->search_cursor_indices[task_id]; + LNK_SymbolHashTrieChunk *end_chunk = symtab->search_chunks[task_id].last; + U64 end_count = end_chunk ? end_chunk->count : 0; - LNK_ObjSymbolRef symbol_ref = lnk_ref_from_symbol(symbol); - COFF_ParsedSymbol symbol_parsed = lnk_parsed_from_symbol(symbol); - COFF_SymbolValueInterpType symbol_interp = coff_interp_from_parsed_symbol(symbol_parsed); - if (symbol_interp == COFF_SymbolValueInterp_Undefined) { + for EachNode(c, LNK_SymbolHashTrieChunk, start_chunk ? start_chunk : symtab->search_chunks[task_id].first) { + U64 i_begin = (c == start_chunk) ? start_idx : 0; + U64 i_end = (c == end_chunk) ? end_count : c->count; + for (U64 i = i_begin; i < i_end; i += 1) { + LNK_Symbol *symbol = c->v[i].symbol; + LNK_SymbolSearchType search_type = lnk_search_type_from_symbol(symbol); + + if (search_type == LNK_SymbolSearch_Undefined || search_type == LNK_SymbolSearch_WeakLibrary) { U32 member_idx; if (lnk_search_lib(lib, symbol->name, &member_idx)) { lnk_queue_lib_member(arena, task->imports_hm, task->link->lib_member_infos_hm, member_ref_list, symbol, lib, lib_member_infos, member_idx); } - } else if (symbol_interp == COFF_SymbolValueInterp_Weak) { - COFF_SymbolWeakExt *weak_ext = coff_parse_weak_tag(symbol_parsed, symbol_ref.obj->header.is_big_obj); - if (weak_ext->characteristics == COFF_WeakExt_SearchLibrary) { - U32 member_idx; - if (lnk_search_lib(lib, symbol->name, &member_idx)) { - lnk_queue_lib_member(arena, task->imports_hm, task->link->lib_member_infos_hm, member_ref_list, symbol, lib, lib_member_infos, member_idx); - } - } else if (weak_ext->characteristics == COFF_WeakExt_AntiDependency) { - if (search_anti_deps) { - LNK_ObjSymbolRef dep_symbol = {0}; - if (lnk_resolve_weak_symbol(symtab, symbol_ref, &dep_symbol)) { - COFF_ParsedSymbol dep_parsed = lnk_parsed_symbol_from_coff_symbol_idx(dep_symbol.obj, dep_symbol.symbol_idx); - COFF_SymbolValueInterpType dep_interp = coff_interp_from_parsed_symbol(dep_parsed); - if (dep_interp == COFF_SymbolValueInterp_Weak) { - U32 member_idx; - if (lnk_search_lib(lib, symbol_parsed.name, &member_idx)) { - lnk_queue_lib_member(arena, task->imports_hm, task->link->lib_member_infos_hm, member_ref_list, symbol, lib, lib_member_infos, member_idx); - } - } + } else if (search_type == LNK_SymbolSearch_WeakAntiDependency && search_anti_deps) { + LNK_ObjSymbolRef symbol_ref = lnk_ref_from_symbol(symbol); + LNK_ObjSymbolRef dep_symbol = {0}; + if (lnk_resolve_weak_symbol(symtab, symbol_ref, &dep_symbol)) { + COFF_ParsedSymbol dep_parsed = lnk_parsed_symbol_from_coff_symbol_idx_no_name(dep_symbol.obj, dep_symbol.symbol_idx); + COFF_SymbolValueInterpType dep_interp = coff_interp_from_parsed_symbol(dep_parsed); + if (dep_interp == COFF_SymbolValueInterp_Weak) { + U32 member_idx; + if (lnk_search_lib(lib, symbol->name, &member_idx)) { + lnk_queue_lib_member(arena, task->imports_hm, task->link->lib_member_infos_hm, member_ref_list, symbol, lib, lib_member_infos, member_idx); } } } } } } + + // cache search cursors + lib->search_cursor_chunks[task_id] = end_chunk; + lib->search_cursor_indices[task_id] = end_count; +} + +internal U64 +lnk_search_lib_task_work_count(LNK_SearchLibTask *task, U64 task_id) +{ + LNK_Lib *lib = task->lib; + LNK_SymbolTable *symtab = task->symtab; + LNK_SymbolHashTrieChunk *start_chunk = task->reset_search_cursor ? 0 : lib->search_cursor_chunks[task_id]; + U64 start_idx = task->reset_search_cursor ? 0 : lib->search_cursor_indices[task_id]; + LNK_SymbolHashTrieChunk *end_chunk = symtab->search_chunks[task_id].last; + U64 end_count = end_chunk ? end_chunk->count : 0; + + U64 work_count = 0; + for EachNode(c, LNK_SymbolHashTrieChunk, start_chunk ? start_chunk : symtab->search_chunks[task_id].first) { + U64 i_begin = (c == start_chunk) ? start_idx : 0; + U64 i_end = (c == end_chunk) ? end_count : c->count; + work_count += i_end - i_begin; + } + return work_count; } internal LNK_Lib * @@ -1730,6 +1811,8 @@ lnk_link_inputs(TP_Context *tp, LNK_LibMemberRefList *member_ref_lists = push_array(scratch.arena, LNK_LibMemberRefList, tp->worker_count); B32 search_anti_deps = 0; for (U64 resolved_members_count = 0; ; resolved_members_count = 0) { + ProfBegin("Search Pass"); + lnk_load_inputs(tp, arena, config, inputer, symtab, link); for EachNode(lib_n, LNK_LibNode, link->libs.first) { @@ -1813,19 +1896,48 @@ lnk_link_inputs(TP_Context *tp, for EachIndex(member_idx, lib->member_count) { lnk_queue_lib_member(arena->v[0], &imports_hm, link->lib_member_infos_hm, &member_ref_lists[0], null_symbol, lib, lib_member_infos, member_idx); } - } else { - // search symbols in lib + } else { // search symbols in lib MemoryZeroTyped(member_ref_lists, tp->worker_count); + + // anti-dep mode changes which weak symbols can resolve from this lib + B32 reset_search_cursor = lib->search_cursor_chunks != 0 && lib->searched_anti_deps != search_anti_deps; + + // lazy alloc cursors for tracking searched symbols + if (lib->search_cursor_chunks == 0) { + lib->search_cursor_chunks = push_array(link->arena, LNK_SymbolHashTrieChunk *, tp->worker_count); + lib->search_cursor_indices = push_array(link->arena, U64, tp->worker_count); + } + LNK_SearchLibTask search_task = { - .search_anti_deps = search_anti_deps, - .link = link, - .imports_hm = &imports_hm, - .lib = lib, - .symtab = symtab, - .lib_member_infos = lib_member_infos, - .member_ref_lists = member_ref_lists + .search_anti_deps = search_anti_deps, + .reset_search_cursor = reset_search_cursor, + .link = link, + .imports_hm = &imports_hm, + .lib = lib, + .symtab = symtab, + .lib_member_infos = lib_member_infos, + .member_ref_lists = member_ref_lists }; - tp_for_parallel(tp, arena, tp->worker_count, lnk_search_lib_task, &search_task); + enum { serial_work_limit = 16384 }; + U64 search_work_count = 0; + for EachIndex(task_id, tp->worker_count) { + search_work_count += lnk_search_lib_task_work_count(&search_task, task_id); + if (search_work_count > serial_work_limit) { + break; + } + } + if (search_work_count <= serial_work_limit) { + // thread pool barrier waits dominate small library searches, + // search small tasks on the main thread + for EachIndex(task_id, tp->worker_count) { + lnk_search_lib_task(arena->v[0], 0, task_id, &search_task, tp); + } + } else { + tp_for_parallel(tp, arena, tp->worker_count, lnk_search_lib_task, &search_task); + } + + // cache last search mode, if the mode changes then skipped weak anti-dependency must be searched again + lib->searched_anti_deps = search_anti_deps; } LNK_LibMemberRefList queued_members = {0}; @@ -1902,7 +2014,7 @@ lnk_link_inputs(TP_Context *tp, // replace the import symbol with a stub, which is later replaced with the real import symbol once import obj is ready. member_ref->link_symbol->first_ref = import_stub->first_ref; - member_ref->link_symbol->last_ref = import_stub->last_ref; + member_ref->link_symbol->last_ref_and_search_type = import_stub->last_ref_and_search_type; // push import member for import obj generation lnk_lib_member_ref_list_push_node(&link->imports, member_ref); @@ -1972,6 +2084,7 @@ lnk_link_inputs(TP_Context *tp, resolved_members_count = lnk_inputer_has_items(inputer); } + ProfEnd(); if (resolved_members_count == 0) { break; } } @@ -2002,12 +2115,13 @@ lnk_link_image(TP_Context *tp, TP_Arena *arena, LNK_Config *config, LNK_Inputer lnk_inputer_push_lib_thin(inputer, config, LNK_InputSource_CmdLine, (*link->last_cmd_lib)->string); } + if (config->guard_flags != LNK_Guard_None) { + lnk_include_symbol(config, str8_lit(MSCRT_LOAD_CONFIG_SYMBOL_NAME), 0); + } + // link inputer lnk_link_inputs(tp, arena, config, inputer, symtab, link); - // TODO: need to figure out under what condition to include load config - //lnk_include_symbol(config, str8_lit(MSCRT_LOAD_CONFIG_SYMBOL_NAME), 0); - { ProfBegin("Push Linker Symbols"); String8 linker_symbols_obj = lnk_make_linker_obj(arena->v[0], config); @@ -2265,6 +2379,14 @@ lnk_link_image(TP_Context *tp, TP_Arena *arena, LNK_Config *config, LNK_Inputer // lnk_replace_weak_with_default_symbols(tp, symtab); + // + // assign COMDAT leaders + // + { + LNK_Obj **objs = lnk_array_from_obj_list(scratch.arena, link->objs); + lnk_assign_comdat_symlinks(tp, arena, symtab, link->objs.count, objs); + } + // // was entry point resolved? // @@ -2397,19 +2519,31 @@ lnk_link_image(TP_Context *tp, TP_Arena *arena, LNK_Config *config, LNK_Inputer scratch_end(debug_scratch); } - // TODO: /FORCE - if (unresolved_symbols_count) { + if (unresolved_symbols_count && !config->force) { lnk_exit(LNK_Error_UnresolvedSymbol); } ProfEnd(); } - // - // discard COMDAT sections that are not referenced - // - if (config->opt_ref == LNK_SwitchState_Yes) { - lnk_opt_ref(tp, symtab, config, link->objs); + { + LNK_Obj **objs = 0; + + // + // 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 +2637,127 @@ 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) +{ + Temp temp = temp_begin(arena); + B32 is_resolved = 1; + HashMap seen_hm = {0}; + LNK_ObjSymbolRef result = symbol; + for (;;) { + // unpack symbol + COFF_ParsedSymbol result_parsed = lnk_parsed_symbol_from_coff_symbol_idx_no_name(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)) { + break; + } + if (result_interp != COFF_SymbolValueInterp_Weak && result_interp != COFF_SymbolValueInterp_Undefined) { + result = next_ref; + break; + } + + // most relocations resolve in one step; only allocate cycle tracking for 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) { + 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; + } + hash_map_push_u64_u64(temp.arena, &seen_hm, symbol_key, 1); + result = next_ref; + } + + if (resolved_symbol_out) { + *resolved_symbol_out = result; + } + + temp_end(temp); + return is_resolved; +} + internal -THREAD_POOL_TASK_FUNC(lnk_walk_relocs_and_mark_ref_sections_task) +THREAD_POOL_TASK_FUNC(lnk_opt_ref_task) { ProfBeginFunction(); Temp scratch = scratch_begin(0,0); Temp scratch2 = scratch_begin(&scratch.arena, 1); - LNK_OptRefTask *task = raw_task; - LNK_SymbolTable *symtab = task->symtab; - LNK_Config *config = task->config; - LNK_ObjList objs = task->objs; + LNK_OptTask *task = raw_task; + LNK_SymbolTable *symtab = task->symtab; + LNK_Config *config = task->config; + LNK_Obj **objs = task->objs; + U64 objs_count = task->objs_count; - U8 **is_live = 0; - U64 *active_thread_count = 0; + U8 **is_live = 0; + U64 *active_thread_count = 0; LNK_RelocRefsBatchList *global_batch_list = 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); // 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 EachNode(n, LNK_ObjNode, task->objs.first) { - is_live[obj_idx] = push_array(scratch.arena, U8, n->data.header.section_count_no_null + 1); + for EachIndex(obj_idx, objs_count) { + LNK_Obj *obj = objs[obj_idx]; - for EachIndex(sect_idx, n->data.header.section_count_no_null) { - is_live[obj_idx][sect_idx + 1] = !(n->data.section_flags[sect_idx] & COFF_SectionFlag_LnkCOMDAT); + is_live[obj_idx] = push_array(scratch.arena, U8, obj->header.section_count_no_null + 1); + + 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 +2784,9 @@ THREAD_POOL_TASK_FUNC(lnk_walk_relocs_and_mark_ref_sections_task) } // push task for every non-COMDAT section - for EachNode(obj_n, LNK_ObjNode, objs.first) { - LNK_Obj *obj = &obj_n->data; + for EachIndex(obj_idx, objs_count) { + LNK_Obj *obj = objs[obj_idx]; + for EachIndex(sect_idx, obj->header.section_count_no_null) { U32 section_number = sect_idx+1; COFF_SectionFlags section_flags = obj->section_flags[sect_idx]; @@ -2610,71 +2834,27 @@ THREAD_POOL_TASK_FUNC(lnk_walk_relocs_and_mark_ref_sections_task) // reloc -> symbol 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}; - 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); - } + lnk_resolve_reloc_target_symbol(scratch2.arena, symtab, ref_symbol, str8_lit("/OPT:REF"), &ref_symbol); // skip unresolved symbol if (ref_symbol.obj == 0) { continue; } // unpack resolved symbol - COFF_ParsedSymbol ref_parsed = lnk_parsed_symbol_from_coff_symbol_idx(ref_symbol.obj, ref_symbol.symbol_idx); + COFF_ParsedSymbol ref_parsed = lnk_parsed_symbol_from_coff_symbol_idx_no_name(ref_symbol.obj, ref_symbol.symbol_idx); COFF_SymbolValueInterpType ref_interp = coff_interp_from_parsed_symbol(ref_parsed); if (ref_interp == COFF_SymbolValueInterp_Regular) { Temp temp = temp_begin(scratch2.arena); - HashMap visited_sections_hm = {0}; - U32Node *stack = push_array(temp.arena, U32Node, 1); - stack->data = ref_parsed.section_number; - do { - U32 section_number = stack->data; - SLLStackPop(stack); + U32List associated_sections = lnk_obj_collect_associated_sections(temp.arena, ref_symbol.obj, ref_parsed.section_number, 0); - // is section number valid? - if (section_number == 0 || section_number > ref_symbol.obj->header.section_count_no_null) { continue; } + // visit root section + u32_list_push(temp.arena, &associated_sections, ref_parsed.section_number); - // detect cyclic associative sections - if (hash_map_search_u64_u64(&visited_sections_hm, section_number)) { continue; } - hash_map_push_u64_u64(temp.arena, &visited_sections_hm, section_number, 1); + for EachNode(section_n, U32Node, associated_sections.first) { + U32 section_number = section_n->data; - // push associated section - for EachNode(associated_n, U32Node, ref_symbol.obj->associated_sections[section_number]) { - if (hash_map_search_u64_u64(&visited_sections_hm, associated_n->data)) { continue; } - U32Node *stack_n = push_array(temp.arena, U32Node, 1); - stack_n->data = associated_n->data; - 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 U8 was_visited = ins_atomic_u8_eval_assign(&is_live[ref_symbol.obj->input_idx][section_number], 1); @@ -2702,8 +2882,7 @@ THREAD_POOL_TASK_FUNC(lnk_walk_relocs_and_mark_ref_sections_task) } batch->v[batch->count++] = refs; - - } while (stack); + } temp_end(temp); } @@ -2740,46 +2919,65 @@ THREAD_POOL_TASK_FUNC(lnk_walk_relocs_and_mark_ref_sections_task) if (task_id == 0) { ProfBegin("Remove Unreachable Sections"); - typedef struct { U64 vsize; U64 fsize; U64 section_count; } Stat; - enum { Stat_Null, Stat_Code, Stat_Data, Stat_Debug, Stat_Count }; - Stat stats[Stat_Count] = {0}; - - for EachNode(obj_n, LNK_ObjNode, objs.first) { - LNK_Obj *obj = &obj_n->data; - + for EachIndex(obj_idx, objs_count) { + LNK_Obj *obj = objs[obj_idx]; for EachIndex(sect_idx, obj->header.section_count_no_null) { - U32 section_number = sect_idx+1; - if (is_live[obj->input_idx][section_number]) { continue; } - + U32 section_number = sect_idx+1; COFF_SectionHeader *section_header = lnk_coff_section_header_from_section_number(obj, section_number); - obj->section_flags[sect_idx] |= COFF_SectionFlag_LnkRemove; - COFF_SectionFlags section_flags = obj->section_flags[sect_idx]; - - U64 stat_kind = Stat_Null; - if (section_flags & LNK_SECTION_FLAG_DEBUG) { stat_kind = Stat_Debug; } - else if (section_flags & COFF_SectionFlag_CntCode) { stat_kind = Stat_Code; } - else { stat_kind = Stat_Data; } - - if (section_flags & COFF_SectionFlag_CntUninitializedData) { - stats[stat_kind].vsize += section_header->vsize; - } else { - stats[stat_kind].fsize += section_header->fsize; + if ( ! is_live[obj->input_idx][section_number]) { + obj->section_flags[sect_idx] |= COFF_SectionFlag_LnkRemove; } - stats[stat_kind].section_count += 1; } } if (lnk_get_log_status(LNK_Log_Debug)) { + typedef struct { U64 vsize; U64 fsize; U64 section_count; U64 live_count; U64 live_fsize; U64 live_vsize; } Stat; + enum { Stat_Null, Stat_Code, Stat_Data, Stat_Debug, Stat_Count }; + Stat stats[Stat_Count] = {0}; + + for EachIndex(obj_idx, objs_count) { + LNK_Obj *obj = objs[obj_idx]; + + for EachIndex(sect_idx, obj->header.section_count_no_null) { + U32 section_number = sect_idx+1; + COFF_SectionHeader *section_header = lnk_coff_section_header_from_section_number(obj, section_number); + + U64 stat_kind = Stat_Null; + if (obj->section_flags[sect_idx] & LNK_SECTION_FLAG_DEBUG) { stat_kind = Stat_Debug; } + else if (obj->section_flags[sect_idx] & COFF_SectionFlag_CntCode) { stat_kind = Stat_Code; } + else { stat_kind = Stat_Data; } + + if (is_live[obj->input_idx][section_number]) { + stats[stat_kind].live_count += 1; + if (obj->section_flags[sect_idx] & COFF_SectionFlag_CntUninitializedData) { + stats[stat_kind].live_vsize += section_header->vsize; + } else { + stats[stat_kind].live_fsize += section_header->fsize; + } + } else { + if (obj->section_flags[sect_idx] & COFF_SectionFlag_CntUninitializedData) { + stats[stat_kind].vsize += section_header->vsize; + } else { + stats[stat_kind].fsize += section_header->fsize; + } + stats[stat_kind].section_count += 1; + } + } + } + U64 total_fsize = 0, total_section_count = 0; + U64 total_fsize_live = 0, total_section_count_live = 0; for EachElement(i, stats) { - total_fsize += stats[i].fsize; - total_section_count += stats[i].section_count; + total_fsize += stats[i].fsize; + total_section_count += stats[i].section_count; + total_fsize_live += stats[i].live_fsize; + total_section_count_live += stats[i].live_count; } String8List stat_list = {0}; - str8_list_pushf(scratch.arena, &stat_list, "Code : %M, %S sections", stats[Stat_Code].fsize, str8_from_count(scratch.arena, stats[Stat_Code].section_count )); - str8_list_pushf(scratch.arena, &stat_list, "Data : %M, %S sections", stats[Stat_Data].fsize, str8_from_count(scratch.arena, stats[Stat_Data].section_count )); - str8_list_pushf(scratch.arena, &stat_list, "Debug: %M, %S sections", stats[Stat_Debug].fsize, str8_from_count(scratch.arena, stats[Stat_Debug].section_count)); - str8_list_pushf(scratch.arena, &stat_list, "Total: %M, %S sections", total_fsize, str8_from_count(scratch.arena, total_section_count)); + str8_list_pushf(scratch.arena, &stat_list, "Code : removed %M, %S sections; live %M, %S sections", stats[Stat_Code].fsize, str8_from_count(scratch.arena, stats[Stat_Code].section_count ), stats[Stat_Code].live_fsize, str8_from_count(scratch.arena, stats[Stat_Code].live_count)); + str8_list_pushf(scratch.arena, &stat_list, "Data : removed %M, %S sections; live %M, %S sections", stats[Stat_Data].fsize, str8_from_count(scratch.arena, stats[Stat_Data].section_count ), stats[Stat_Data].live_fsize, str8_from_count(scratch.arena, stats[Stat_Data].live_count)); + str8_list_pushf(scratch.arena, &stat_list, "Debug: removed %M, %S sections; live %M, %S sections", stats[Stat_Debug].fsize, str8_from_count(scratch.arena, stats[Stat_Debug].section_count), stats[Stat_Debug].live_fsize, str8_from_count(scratch.arena, stats[Stat_Debug].live_count)); + str8_list_pushf(scratch.arena, &stat_list, "Total: removed %M, %S sections; live %M, %S sections", total_fsize, str8_from_count(scratch.arena, total_section_count), total_fsize_live, str8_from_count(scratch.arena, total_section_count_live)); String8 stat_str = str8_list_join(scratch.arena, &stat_list, &(StringJoin){.pre = str8_lit(" "), .sep = str8_lit("\n ")}); lnk_log(LNK_Log_Debug, "/OPT:REF Stats:\n%S", stat_str); } @@ -2794,39 +2992,850 @@ THREAD_POOL_TASK_FUNC(lnk_walk_relocs_and_mark_ref_sections_task) } 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") - tp_for_parallel(tp, - 0, - tp->worker_count, - lnk_walk_relocs_and_mark_ref_sections_task, - &(LNK_OptRefTask){ .symtab = symtab, .config = config, .objs = objs }); + ProfBegin("/OPT:REF"); + Temp scratch = scratch_begin(0,0); + U32Array *obj_indices = lnk_obj_indices_from_section_counts(scratch.arena, tp->worker_count, objs, objs_count); + 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); + ProfEnd(); +} + +typedef enum LNK_ICF_ColorSpace +{ + LNK_ICF_ColorSpace_Null, + LNK_ICF_ColorSpace_Code, + LNK_ICF_ColorSpace_Unwind, + LNK_ICF_ColorSpace_VFTable, + LNK_ICF_ColorSpace_ConstData, // string literals, float consts, const tables (/Gw and /GF) + 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_ConstData: { result = str8_lit("ConstData"); } 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; + + // * section flags filter * + 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) { + goto exit; + } + + if (obj->section_flags[sect_idx] & COFF_SectionFlag_CntCode) { + result = LNK_ICF_ColorSpace_Code; + goto exit; + } + + if (obj->section_flags[sect_idx] & COFF_SectionFlag_CntInitializedData) { + U64 section_number = sect_idx + 1; + COFF_SectionHeader *section_header = lnk_coff_section_header_from_section_number(obj, section_number); + String8 section_name = str8_cstring_capped(section_header->name, section_header->name + sizeof(section_header->name)); + + // + // * include unwind info metadata * + // + if (str8_match(section_name, str8_lit(".xdata"), 0) || str8_match(section_name, str8_lit(".pdata"), 0)) { + result = LNK_ICF_ColorSpace_Unwind; + goto exit; + } + + // query COMDAT symlink that is associated with the section number + // because the properties are stored in the symbol table + LNK_ObjSymbolRef comdat_ref = {0}; + if (lnk_obj_get_comdat_symlink(obj, section_number, &comdat_ref)) { + + // load COMDAT symbol name + String8 comdat_name = lnk_symbol_name_from_coff_symbol_idx(comdat_ref.obj, comdat_ref.symbol_idx); + + // + // * include virtual function tables * + // + if (str8_starts_with(comdat_name, str8_lit(MSCRT_VFTABLE_SYMBOL_PREFIX))) { + result = LNK_ICF_ColorSpace_VFTable; + goto exit; + } + + // + // * include COMDATs * + // + COFF_ComdatSelectType select = COFF_ComdatSelect_Null; + if (lnk_try_comdat_props_from_section_number(obj, section_number, &select, 0, 0, 0)) { + // TODO: ref linkers exclude C++ virtual base tables, not sure why, + // are there tools that assume vbptr is unique for _some_reason_? + if (str8_starts_with(comdat_name, str8_lit(MSCRT_VBTABLE_SYMBOL_PREFIX))) { + goto exit; + } + + // following selections are not included in ICF + // 1. NoDuplicate: selection requires a unique address + // 2. Associative: breaks COMDAT ownership model + if (select == COFF_ComdatSelect_Any || + select == COFF_ComdatSelect_SameSize || + select == COFF_ComdatSelect_ExactMatch || + select == COFF_ComdatSelect_Largest) { + result = LNK_ICF_ColorSpace_ConstData; + goto exit; + } + } + } + } + + exit:; + return result; +} + +internal void +lnk_icf_atomic_min_u64(U64 *dst, U64 value) +{ + // preserve stable leaders despite concurrent insertion + for (U64 old_value = ins_atomic_u64_eval(dst); value < old_value;) { + U64 observed = ins_atomic_u64_eval_cond_assign(dst, value, old_value); + if (observed == old_value) { break; } + old_value = observed; + } +} + +// NOTE: 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 round 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 +THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) +{ + ProfBeginFunction(); + + typedef struct { U128 hash; U64 old_color; } ColorKey; + + // only target colors vary between rounds, so cache non-recursive relocation data + // and rehash target colors each round + typedef struct { + U64 *color; + U64 static_id; + U32 value; + COFF_SymbolValueInterpType interp; + } RelocTarget; + + typedef struct { + ColorKey key; + U128 static_hash; + RelocTarget **reloc_targets; + U64 reloc_count; + U64 color_slot_idx; + U32 obj_idx; + U32 sect_idx; + LNK_ICF_ColorSpace color_space; + } Contrib; + + // reuse both tables without clearing them between refinement rounds + typedef struct { + U64 state; // generation << 2 | (0 = empty, 1 = initializing, 2 = ready) + ColorKey key; + U64 first_contrib_idx; + U64 old_color_slot_idx; + U64 color; + } ColorHashSlot; + + typedef struct { ColorHashSlot *slots; U64 slots_count; } ColorHashTable; + + typedef struct { + U64 state; // generation << 2 | (0 = empty, 1 = initializing, 2 = ready) + U64 old_color; + U64 first_contrib_idx; + } OldColorHashSlot; + + typedef struct { OldColorHashSlot *slots; U64 slots_count; } OldColorHashTable; + + Temp scratch = scratch_begin(&arena,1); + Temp scratch2 = scratch_begin(&scratch.arena,1); // retain relocation metadata through temporary associated-section traversals + + LNK_OptTask *task = raw_task; + LNK_Obj **objs = task->objs; + + if (task->config->llvm_addrsig == LNK_SwitchState_Yes) { + ProfBegin("Flag significant sections"); + // .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, + // the selected symbols maybe undefined/weak which need to be resolved + 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 = lnk_parsed_symbol_from_coff_symbol_idx_no_name(target_ref.obj, target_ref.symbol_idx); + if (coff_interp_from_parsed_symbol(symbol) == COFF_SymbolValueInterp_Regular) { + target_ref.obj->section_flags[symbol.section_number - 1] |= LNK_SECTION_FLAG_NOICF; + } + } else { + COFF_ParsedSymbol original_symbol = lnk_parsed_symbol_from_coff_symbol_idx_no_name(obj, symbol_idx); + if (coff_interp_from_parsed_symbol(original_symbol) == COFF_SymbolValueInterp_Regular) { + obj->section_flags[original_symbol.section_number - 1] |= LNK_SECTION_FLAG_NOICF; + } else { + lnk_log(LNK_Log_Debug, "%S: .llvm_addrsig: contains an unresolved symbol index 0x%x at offset 0x%x", lnk_loc_from_obj(scratch.arena, obj), 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); + } + } + } + ProfEnd(); + 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); + + ProfBegin("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; + } + } + } + ProfEnd(); + barrier_wait(tp->barrier); + + U64 contrib_count = sum_array_u64(task->objs_count, contrib_counts); + U64 *noncontrib_offsets = 0; + U64 *contrib_offsets = 0; + U64 **color_map = 0; + Contrib *contribs = 0; + Rng1U64 *contrib_ranges = 0; + U64 *split_counts = 0; + U64 *split_offsets = 0; + U32 *is_part_stable = 0; + U64 *next_color = 0; + if (task_id == 0) { + ProfBegin("Init"); + + noncontrib_offsets = push_array(scratch.arena, U64, task->objs_count); + U64 noncontrib_count = 0; + for EachIndex(obj_idx, task->objs_count) { + noncontrib_offsets[obj_idx] = noncontrib_count; + noncontrib_count += objs[obj_idx]->header.section_count_no_null - contrib_counts[obj_idx]; + } + + 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); + } + + contrib_offsets = offsets_from_counts_array_u64(scratch.arena, contrib_counts, task->objs_count); + contribs = push_array(scratch.arena, Contrib, contrib_count); + contrib_ranges = tp_divide_work(scratch.arena, contrib_count, tp->worker_count); + split_counts = push_array(scratch.arena, U64, tp->worker_count); + split_offsets = push_array(scratch.arena, U64, tp->worker_count + 1); + is_part_stable = push_array(scratch.arena, U32, 1); + next_color = push_array(scratch.arena, U64, 1); + *next_color = LNK_ICF_ColorSpace_COUNT + noncontrib_count; + + lnk_log(LNK_Log_Debug, " Contrib count: %S", str8_from_count(scratch.arena, contrib_count)); + + ProfEnd(); + } + tp_broadcast(&contrib_offsets); + tp_broadcast(&noncontrib_offsets); + tp_broadcast(&color_map); + tp_broadcast(&contribs); + tp_broadcast(&contrib_ranges); + tp_broadcast(&split_counts); + tp_broadcast(&split_offsets); + tp_broadcast(&is_part_stable); + tp_broadcast(&next_color); + + ProfBegin("Compute Hashes"); + HashMap reloc_target_hm = {0}; // cache source-symbol resolution so refinement only reads colors and 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]; + U64 cursor = 0; + U64 noncontrib_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 colors in object order to avoid a contended atomic allocator + color_map[obj_idx][sect_idx] = LNK_ICF_ColorSpace_COUNT + noncontrib_offsets[obj_idx] + noncontrib_cursor++; + continue; + } + + // compute contribution index + U64 contrib_idx = contrib_offsets[obj_idx] + cursor++; + Contrib *contrib = &contribs[contrib_idx]; + *contrib = (Contrib){ + .obj_idx = safe_cast_u32(obj->input_idx), + .sect_idx = safe_cast_u32(sect_idx), + .color_space = color_space, + }; + + Temp temp = temp_begin(scratch.arena); + // include associative children in their parent COMDAT's identity + COFF_SectionFlags associated_filter = COFF_SectionFlag_LnkRemove | COFF_SectionFlag_LnkInfo | COFF_SectionFlag_MemDiscardable | LNK_SECTION_FLAG_DEBUG; + U32List associated_sections = lnk_obj_collect_associated_sections(temp.arena, obj, sect_idx + 1, associated_filter); + u32_list_push(temp.arena, &associated_sections, sect_idx + 1); + + for EachNode(associated_n, U32Node, associated_sections.first) { + COFF_SectionHeader *associated_header = lnk_coff_section_header_from_section_number(obj, associated_n->data); + COFF_RelocArray associated_relocs = lnk_coff_relocs_from_section_header(obj, associated_header); + contrib->reloc_count += associated_relocs.count; + } + if (contrib->reloc_count) { + contrib->reloc_targets = push_array(scratch2.arena, RelocTarget *, contrib->reloc_count); + } + + blake3_hasher hasher; blake3_hasher_init(&hasher); + blake3_hasher_update(&hasher, &color_space, sizeof(color_space)); + + U64 reloc_cursor = 0; + for EachNode(associated_n, U32Node, associated_sections.first) { + COFF_SectionHeader *associated_header = lnk_coff_section_header_from_section_number(obj, associated_n->data); + String8 associated_data = str8_substr(obj->data, r1u64s(associated_header->foff, associated_header->fsize)); + COFF_RelocArray associated_relocs = lnk_coff_relocs_from_section_header(obj, associated_header); + + blake3_hasher_update(&hasher, associated_data.str, associated_data.size); + blake3_hasher_update(&hasher, &associated_relocs.count, sizeof(associated_relocs.count)); + + for EachIndex(reloc_idx, associated_relocs.count) { + COFF_Reloc *r = &associated_relocs.v[reloc_idx]; + + U64 reloc_key = Compose64Bit(obj->input_idx, r->isymbol); + RelocTarget *target = hash_map_search_u64_raw(&reloc_target_hm, reloc_key); + if (target == 0) { + target = push_array(scratch2.arena, RelocTarget, 1); + *target = (RelocTarget){0}; + + LNK_ObjSymbolRef target_ref = { .obj = obj, .symbol_idx = r->isymbol }; + B32 is_symbol_found = lnk_resolve_reloc_target_symbol(scratch2.arena, task->symtab, target_ref, str8_lit("/OPT:ICF"), &target_ref); + if (is_symbol_found) { + COFF_ParsedSymbol target_symbol = lnk_parsed_symbol_from_coff_symbol_idx_no_name(target_ref.obj, target_ref.symbol_idx); + target->interp = coff_interp_from_parsed_symbol(target_symbol); + target->value = target_symbol.value; + + switch (target->interp) { + case COFF_SymbolValueInterp_Regular: { + LNK_Obj *target_obj = target_ref.obj; + U32 target_sect = target_symbol.section_number; + + // use the selected COMDAT leader so equivalent targets hash alike + if (target_sect != 0 && target_sect <= target_obj->header.section_count_no_null && + target_obj->section_flags[target_sect - 1] & COFF_SectionFlag_LnkCOMDAT) { + LNK_ObjSymbolRef leader_ref = {0}; + if (lnk_obj_get_comdat_symlink(target_obj, target_sect, &leader_ref)) { + COFF_ParsedSymbol leader_symbol = lnk_parsed_symbol_from_coff_symbol_idx_no_name(leader_ref.obj, leader_ref.symbol_idx); + if (leader_symbol.section_number != 0 && leader_symbol.section_number <= leader_ref.obj->header.section_count_no_null) { + target_obj = leader_ref.obj; + target_sect = leader_symbol.section_number; + } + } + } + + target->color = &color_map[target_obj->input_idx][target_sect - 1]; + } break; + default: { + target->static_id = Compose64Bit(target_ref.obj->input_idx, target_ref.symbol_idx); + } break; + } + } else { + target->interp = max_U32; + target->static_id = Compose64Bit(obj_idx, r->isymbol); + } + + hash_map_push_u64_raw(scratch2.arena, &reloc_target_hm, reloc_key, target); + } + + contrib->reloc_targets[reloc_cursor++] = target; + 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->value, sizeof(target->value)); + } + } + Assert(reloc_cursor == contrib->reloc_count); + blake3_hasher_finalize(&hasher, (U8 *)&contrib->static_hash, sizeof(contrib->static_hash)); + + // seed foldable sections with their immutable content and relocation shape + color_map[obj_idx][sect_idx] = hash_map_hasher(str8_struct(&contrib->static_hash)) | (1ull << 63); + temp_end(temp); + } + } + ProfEnd(); + barrier_wait(tp->barrier); + + ColorHashTable color_table = {0}; + OldColorHashTable old_color_table = {0}; + U64 *table_generation = 0; + if (task_id == 0) { + ProfBegin("Alloc hash tables"); + color_table.slots_count = u64_up_to_pow2(Max(2, contrib_count*2)); + color_table.slots = push_array(scratch.arena, ColorHashSlot, color_table.slots_count); + old_color_table.slots_count = color_table.slots_count; + old_color_table.slots = push_array(scratch.arena, OldColorHashSlot, old_color_table.slots_count); + table_generation = push_array_no_zero(scratch.arena, U64, 1); + *table_generation = 0; + ProfEnd(); + } + tp_broadcast(&color_table); + tp_broadcast(&old_color_table); + tp_broadcast(&table_generation); + + // + // step 2: refine equivalence classes + // + + U64 iter_count = 0; + for (;; iter_count += 1) { + ProfBegin("Round #%llu", iter_count); + + barrier_wait(tp->barrier); + + if (task_id == 0) { + // reset color status tracker + *is_part_stable = 1; + + // update hash tables generations + Assert(*table_generation < (max_U64 >> 2)); + *table_generation += 1; + } + barrier_wait(tp->barrier); + + // unpack the table generation + U64 table_generation_value = *table_generation; + U64 initializing_state = (table_generation_value << 2) | 1; + U64 ready_state = (table_generation_value << 2) | 2; + + ProfBegin("Compute colored hashes"); + for EachInRange(contrib_idx, contrib_ranges[task_id]) { + Contrib *contrib = &contribs[contrib_idx]; + contrib->key.old_color = color_map[contrib->obj_idx][contrib->sect_idx]; + + blake3_hasher hasher; blake3_hasher_init(&hasher); + blake3_hasher_update(&hasher, &contrib->static_hash, sizeof(contrib->static_hash)); + for EachIndex(reloc_idx, contrib->reloc_count) { + RelocTarget *target = contrib->reloc_targets[reloc_idx]; + U64 target_id = target->color ? *target->color : target->static_id; + blake3_hasher_update(&hasher, &target_id, sizeof(target_id)); + } + U128 hash; + blake3_hasher_finalize(&hasher, (U8 *)&hash, sizeof(hash)); + + // insert the colored hash into the concurrent table + contrib->key.hash = hash; + + Assert(color_table.slots_count > 0 && (color_table.slots_count & (color_table.slots_count - 1)) == 0); + U64 table_hash = hash_map_hasher(str8_struct(&contrib->key)); + U64 color_slot_idx = table_hash & (color_table.slots_count - 1); + for (;;) { + ColorHashSlot *color_slot = &color_table.slots[color_slot_idx]; + U64 state = ins_atomic_u64_eval(&color_slot->state); + + if ((state >> 2) != table_generation_value) { + if (ins_atomic_u64_eval_cond_assign(&color_slot->state, initializing_state, state) == state) { + color_slot->key = contrib->key; + color_slot->first_contrib_idx = contrib_idx; + contrib->color_slot_idx = color_slot_idx; + ins_atomic_u64_eval_assign(&color_slot->state, ready_state); + break; + } + continue; + } + + if (state == initializing_state) { + do { state = ins_atomic_u64_eval(&color_slot->state); } while (state == initializing_state); + continue; + } + + Assert(state == ready_state); + + if (color_slot->key.old_color == contrib->key.old_color && u128_match(color_slot->key.hash, contrib->key.hash)) { + lnk_icf_atomic_min_u64(&color_slot->first_contrib_idx, contrib_idx); + contrib->color_slot_idx = color_slot_idx; + break; + } + + color_slot_idx = (color_slot_idx + 1) & (color_table.slots_count - 1); + } + } + ProfEnd(); + barrier_wait(tp->barrier); + + // publish one old-color record per color group + ProfBegin("Index color groups"); + Rng1U64 contrib_range = contrib_ranges[task_id]; + for EachInRange(contrib_idx, contrib_range) { + Contrib *contrib = &contribs[contrib_idx]; + ColorHashSlot *slot = &color_table.slots[contrib->color_slot_idx]; + if (ins_atomic_u64_eval(&slot->first_contrib_idx) == contrib_idx) { + Assert(old_color_table.slots_count > 0 && (old_color_table.slots_count & (old_color_table.slots_count - 1)) == 0); + U64 old_color = slot->key.old_color; + U64 table_hash = hash_map_hasher(str8_struct(&old_color)); + U64 old_slot_idx = table_hash & (old_color_table.slots_count - 1); + for (;;) { + OldColorHashSlot *old_color_slot = &old_color_table.slots[old_slot_idx]; + U64 state = ins_atomic_u64_eval(&old_color_slot->state); + + if ((state >> 2) != table_generation_value) { + if (ins_atomic_u64_eval_cond_assign(&old_color_slot->state, initializing_state, state) == state) { + old_color_slot->old_color = old_color; + old_color_slot->first_contrib_idx = contrib_idx; + ins_atomic_u64_eval_assign(&old_color_slot->state, ready_state); + break; + } + continue; + } + + if (state == initializing_state) { + do { state = ins_atomic_u64_eval(&old_color_slot->state); } while (state == initializing_state); + continue; + } + + Assert(state == ready_state); + + if (old_color_slot->old_color == old_color) { + lnk_icf_atomic_min_u64(&old_color_slot->first_contrib_idx, contrib_idx); + break; + } + + old_slot_idx = (old_slot_idx + 1) & (old_color_table.slots_count - 1); + } + slot->old_color_slot_idx = old_slot_idx; + } + } + ProfEnd(); + barrier_wait(tp->barrier); + + // count split groups in deterministic contribution order + ProfBegin("Count color splits"); + U64 split_count = 0; + for EachInRange(contrib_idx, contrib_range) { + Contrib *contrib = &contribs[contrib_idx]; + ColorHashSlot *color_slot = &color_table.slots[contrib->color_slot_idx]; + if (ins_atomic_u64_eval(&color_slot->first_contrib_idx) != contrib_idx) { continue; } + + OldColorHashSlot *old_color_slot = &old_color_table.slots[color_slot->old_color_slot_idx]; + if (ins_atomic_u64_eval(&old_color_slot->first_contrib_idx) != contrib_idx) { + split_count += 1; + } + } + split_counts[task_id] = split_count; + ProfEnd(); + barrier_wait(tp->barrier); + + // assign deterministic color ranges with a small serial prefix sum + if (task_id == 0) { + ProfBegin("Prefix color splits"); + + U64 start_next_color = *next_color; + U64 total_split_count = 0; + for EachIndex(worker_id, tp->worker_count) { + split_offsets[worker_id] = total_split_count; + total_split_count += split_counts[worker_id]; + } + split_offsets[tp->worker_count] = start_next_color; + + *next_color += total_split_count; + *is_part_stable = (total_split_count == 0); + + lnk_log(LNK_Log_Debug, " Round %llu found %S splits", iter_count, str8_from_count(scratch.arena, total_split_count)); + + ProfEnd(); + } + barrier_wait(tp->barrier); + + // assign old and split colors in deterministic contribution order + ProfBegin("Assign colors"); + U64 next_split_color = split_offsets[tp->worker_count] + split_offsets[task_id]; + for EachInRange(contrib_idx, contrib_range) { + Contrib *contrib = &contribs[contrib_idx]; + ColorHashSlot *color_slot = &color_table.slots[contrib->color_slot_idx]; + if (ins_atomic_u64_eval(&color_slot->first_contrib_idx) != contrib_idx) { continue; } + + OldColorHashSlot *old_color_slot = &old_color_table.slots[color_slot->old_color_slot_idx]; + if (ins_atomic_u64_eval(&old_color_slot->first_contrib_idx) == contrib_idx) { + color_slot->color = color_slot->key.old_color; + } else { + color_slot->color = ++next_split_color; + } + } + ProfEnd(); + barrier_wait(tp->barrier); + + // update colors for this worker's contributions + ProfBegin("Update color map"); + for EachIndex(i, task->obj_indices[task_id].count) { + U64 obj_idx = task->obj_indices[task_id].v[i]; + Rng1U64 obj_contrib_range = r1u64(contrib_offsets[obj_idx], contrib_offsets[obj_idx] + contrib_counts[obj_idx]); + for EachInRange(contrib_idx, obj_contrib_range) { + Contrib *contrib = &contribs[contrib_idx]; + color_map[contrib->obj_idx][contrib->sect_idx] = color_table.slots[contrib->color_slot_idx].color; + } + } + ProfEnd(); + barrier_wait(tp->barrier); + + ProfEnd(); // round prof + + // stop iterating when partitions stabilize + if (*is_part_stable) { break; } + } + barrier_wait(tp->barrier); + + // + // step 3: flag folded sections for removal + // + + typedef struct { U64 count; U64 size; } FoldStats; + FoldStats *fold_stats = 0; + if (task_id == 0 && lnk_get_log_status(LNK_Log_Debug)) { + fold_stats = push_array(scratch.arena, FoldStats, tp->worker_count * LNK_ICF_ColorSpace_COUNT); + } + tp_broadcast(&fold_stats); + + ProfBegin("Flag Folds"); + FoldStats *local_fold_stats = fold_stats ? fold_stats + (task_id * LNK_ICF_ColorSpace_COUNT) : 0; + for EachInRange(contrib_idx, contrib_ranges[task_id]) { + Contrib *contrib = &contribs[contrib_idx]; + ColorHashSlot *color_slot = &color_table.slots[contrib->color_slot_idx]; + Contrib *leader = &contribs[ins_atomic_u64_eval(&color_slot->first_contrib_idx)]; + if (leader == contrib) { continue; } + + LNK_Obj *contrib_obj = objs[contrib->obj_idx]; + LNK_Obj *leader_obj = objs[leader->obj_idx]; + + if (local_fold_stats) { + COFF_SectionHeader *section_header = lnk_coff_section_header_from_section_number(contrib_obj, contrib->sect_idx + 1); + LNK_ICF_ColorSpace color_space = contrib->color_space; + local_fold_stats[color_space].count += 1; + local_fold_stats[color_space].size += section_header->fsize; + } + + U64 contrib_align = coff_align_size_from_section_flags(contrib_obj->section_flags[contrib->sect_idx]); + COFF_SectionFlags *leader_flags = &leader_obj->section_flags[leader->sect_idx]; + for (COFF_SectionFlags old_flags = ins_atomic_u32_eval((U32 *)leader_flags);;) { + U64 leader_align = coff_align_size_from_section_flags(old_flags); + if (leader_align >= contrib_align) { break; } + + COFF_SectionFlags new_flags = old_flags; + new_flags &= ~(COFF_SectionFlag_AlignMask << COFF_SectionFlag_AlignShift); + new_flags |= coff_section_flag_from_align_size(contrib_align); + COFF_SectionFlags observed = ins_atomic_u32_eval_cond_assign((U32 *)leader_flags, new_flags, old_flags); + if (observed == old_flags) { break; } + old_flags = observed; + } + + Assert(leader_obj->comdats[leader->sect_idx] != max_U32); + contrib_obj->symlinks[contrib->sect_idx + 1] = (LNK_ObjSymbolRef){ leader_obj, leader_obj->comdats[leader->sect_idx] }; + 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 + } + ProfEnd(); + barrier_wait(tp->barrier); + + if (task_id == 0 && fold_stats) { + FoldStats total_stats[LNK_ICF_ColorSpace_COUNT] = {0}; + for EachIndex(worker_id, tp->worker_count) { + FoldStats *worker_stats = fold_stats + (worker_id * LNK_ICF_ColorSpace_COUNT); + for (LNK_ICF_ColorSpace color_space = (LNK_ICF_ColorSpace)(LNK_ICF_ColorSpace_Null + 1); color_space < LNK_ICF_ColorSpace_COUNT; color_space += 1) { + total_stats[color_space].count += worker_stats[color_space].count; + total_stats[color_space].size += worker_stats[color_space].size; + } + } + + 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), total_stats[color_space].size, str8_varg(str8_from_count(scratch.arena, total_stats[color_space].count))); + total_count += total_stats[color_space].count; + total_size += total_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 + // + + ProfBegin("Flatten COMDAT Symbol Links"); + 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_no_name(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; + } + } + ProfEnd(); + barrier_wait(tp->barrier); + + scratch_end(scratch2); + scratch_end(scratch); + ProfEnd(); +} + +internal void +lnk_opt_icf(TP_Context *tp, LNK_SymbolTable *symtab, LNK_Config *config, LNK_Obj **objs, U64 objs_count) +{ + ProfBegin("/OPT:ICF"); + Temp scratch = scratch_begin(0,0); + + lnk_log(LNK_Log_Debug, "/OPT:ICF:"); + U32Array *obj_indices = lnk_obj_indices_from_section_counts(scratch.arena, tp->worker_count, objs, objs_count); + 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); + ProfEnd(); +} + +internal int +lnk_section_definition_is_before(void *raw_a, void *raw_b) +{ + LNK_SectionDefinition **a = raw_a, **b = raw_b; + U64 input_idx_a = Compose64Bit((*a)->obj->input_idx, (*a)->obj_sect_idx); + U64 input_idx_b = Compose64Bit((*b)->obj->input_idx, (*b)->obj_sect_idx); + return u64_compar_is_before(&input_idx_a, &input_idx_b); +} + +internal B32 +lnk_should_gather_section(LNK_Obj *obj, U64 sect_idx, COFF_SectionHeader *sect_header) +{ + COFF_SectionFlags sect_flags = obj->section_flags[sect_idx]; + + // removed sections were eliminated before image layout + if (sect_flags & COFF_SectionFlag_LnkRemove) { + return 0; + } + + // linker-info sections carry metadata but are not copied to the image + if (sect_flags & COFF_SectionFlag_LnkInfo) { + return 0; + } + + // empty COMDATs with symlinks can still anchor symbols at offset zero + if (sect_header->fsize == 0) { + if (~sect_flags & COFF_SectionFlag_LnkCOMDAT) { + return 0; + } + + LNK_ObjSymbolRef symlink_ref = {0}; + if (!lnk_obj_get_comdat_symlink(obj, sect_idx + 1, &symlink_ref)) { + return 0; + } + + // gather only COMDAT leaders + AssertAlways(symlink_ref.obj == obj); + } + + return 1; } internal -THREAD_POOL_TASK_FUNC(lnk_gather_section_definitions_task) +THREAD_POOL_TASK_FUNC(lnk_gather_sections_task) { Temp scratch = scratch_begin(&arena, 1); - LNK_BuildImageTask *task = raw_task; - U64 obj_idx = task_id; + LNK_BuildImageTask *task = raw_task; + Rng1U64 range = task->u.gather_sects.ranges[task_id]; + HashTable *sect_defn_ht = hash_table_init(arena, 128); + task->u.gather_sects.defns[task_id] = sect_defn_ht; - HashTable *sect_defn_ht = task->u.gather_sects.defns[worker_id]; - LNK_Obj *obj = task->objs[obj_idx]; - COFF_SectionHeader *section_table = (COFF_SectionHeader *)str8_substr(obj->data, obj->header.section_table_range).str; - String8 string_table = str8_substr(obj->data, obj->header.string_table_range); + ProfBegin("Gather Section Definitions"); + for EachInRange(obj_idx, range) { + LNK_Obj *obj = task->objs[obj_idx]; + COFF_SectionHeader *section_table = (COFF_SectionHeader *)str8_substr(obj->data, obj->header.section_table_range).str; + String8 string_table = str8_substr(obj->data, obj->header.string_table_range); - for (U64 sect_idx = 0; sect_idx < obj->header.section_count_no_null; sect_idx += 1) { - COFF_SectionHeader *sect_header = §ion_table[sect_idx]; - COFF_SectionFlags sect_flags = obj->section_flags[sect_idx]; + for EachIndex(sect_idx, obj->header.section_count_no_null) { + COFF_SectionHeader *sect_header = §ion_table[sect_idx]; + + if ( ! lnk_should_gather_section(obj, sect_idx, sect_header)) { continue; } - if (~sect_flags & COFF_SectionFlag_LnkRemove && ~sect_flags & COFF_SectionFlag_LnkInfo && sect_header->fsize > 0) { Temp temp = temp_begin(scratch.arena); // was section defined? + COFF_SectionFlags image_sect_flags = obj->section_flags[sect_idx] & ~(COFF_SectionFlags_LnkFlags | COFF_SectionFlags_Reserved); String8 sect_name = coff_name_from_section_header(string_table, sect_header); - String8 sect_name_with_flags = lnk_make_name_with_flags(temp.arena, sect_name, sect_flags & ~COFF_SectionFlags_LnkFlags); + image_sect_flags = lnk_apply_section_directives_to_flags(task->config, sect_name, image_sect_flags); + String8 sect_name_with_flags = lnk_make_name_with_flags(temp.arena, sect_name, image_sect_flags); LNK_SectionDefinition *sect_defn = hash_table_search_string_raw(sect_defn_ht, sect_name_with_flags); // push new section definition @@ -2835,7 +3844,7 @@ THREAD_POOL_TASK_FUNC(lnk_gather_section_definitions_task) sect_defn->name = sect_name; sect_defn->obj = obj; sect_defn->obj_sect_idx = sect_idx; - sect_defn->flags = sect_flags & ~COFF_SectionFlags_LnkFlags; + sect_defn->flags = image_sect_flags; sect_name_with_flags = push_str8_copy(arena, sect_name_with_flags); hash_table_push_string_raw(arena, sect_defn_ht, sect_name_with_flags, sect_defn); @@ -2847,34 +3856,121 @@ THREAD_POOL_TASK_FUNC(lnk_gather_section_definitions_task) temp_end(temp); } } + ProfEnd(); - scratch_end(scratch); -} + barrier_wait(tp->barrier); -internal -THREAD_POOL_TASK_FUNC(lnk_gather_section_contribs_task) -{ - Temp scratch = scratch_begin(&arena, 1); + if (task_id == 0) { + Arena *main_arena = task->u.gather_sects.arena; + LNK_Config *config = task->config; + LNK_SectionTable *sectab = task->sectab; - LNK_BuildImageTask *task = raw_task; - U64 obj_idx = task_id; + ProfBegin("Merge Section Definitions Hash Tables"); + for (U64 worker_idx = 1; worker_idx < tp->worker_count; worker_idx += 1) { + U64 sect_defns_count = task->u.gather_sects.defns[worker_idx]->count; + LNK_SectionDefinition **sect_defns = values_from_hash_table_raw(main_arena, task->u.gather_sects.defns[worker_idx]); + radsort(sect_defns, sect_defns_count, lnk_section_definition_is_before); - LNK_Obj *obj = task->objs[obj_idx]; - COFF_SectionHeader *section_table = (COFF_SectionHeader *)str8_substr(obj->data, obj->header.section_table_range).str; - String8 string_table = str8_substr(obj->data, obj->header.string_table_range); + for EachIndex(defn_idx, sect_defns_count) { + LNK_SectionDefinition *defn = sect_defns[defn_idx]; + String8 name_with_flags = lnk_make_name_with_flags(main_arena, defn->name, defn->flags); + LNK_SectionDefinition *main_defn = hash_table_search_string_raw(task->u.gather_sects.defns[0], name_with_flags); + if (main_defn == 0) { + main_defn = sect_defns[defn_idx]; + hash_table_push_string_raw(main_arena, task->u.gather_sects.defns[0], name_with_flags, main_defn); + } else { + if (lnk_section_definition_is_before(§_defns[defn_idx], &main_defn)) { + main_defn->obj = sect_defns[defn_idx]->obj; + main_defn->obj_sect_idx = sect_defns[defn_idx]->obj_sect_idx; + } + main_defn->contribs_count += sect_defns[defn_idx]->contribs_count; + } + } + } + U64 sect_defns_count = task->u.gather_sects.defns[0]->count; + LNK_SectionDefinition **sect_defns = values_from_hash_table_raw(main_arena, task->u.gather_sects.defns[0]); + ProfEnd(); + + ProfBegin("Sort Sections Definitions"); + radsort(sect_defns, sect_defns_count, lnk_section_definition_is_before); + ProfEnd(); + + ProfBegin("Push Sections And Reserve Section Contrib Memory"); + task->contribs_ht = hash_table_init(sectab->arena, sect_defns_count); + for EachIndex(defn_idx, sect_defns_count) { + LNK_SectionDefinition *sect_defn = sect_defns[defn_idx]; + + // parse section name + String8 sect_name, sort_idx; + coff_parse_section_name(sect_defn->name, §_name, &sort_idx); + + // do not create definitions for sections that are removed from the image + if (lnk_is_section_removed(config, sect_name)) { continue; } + + // warn about conflicting section flags + for EachNode(sect_n, LNK_SectionNode, sectab->list.first) { + if (str8_match(sect_n->data.name, sect_name, 0) && sect_n->data.flags != sect_defn->flags) { + LNK_Obj *obj = sect_defn->obj; + U32 sect_number = sect_defn->obj_sect_idx + 1; + COFF_SectionHeader *sect_header = lnk_coff_section_header_from_section_number(obj, sect_number); + String8 sect_name = coff_name_from_section_header(str8_substr(obj->data, obj->header.string_table_range), sect_header); + String8 expected_flags_str = coff_string_from_section_flags(main_arena, sect_n->data.flags); + String8 current_flags_str = coff_string_from_section_flags(main_arena, sect_defn->flags); + lnk_error_obj(LNK_Warning_SectionFlagsConflict, sect_defn->obj, "detected section flags conflict in %S(No. %X); expected {%S} but got {%S}", sect_name, sect_number, expected_flags_str, current_flags_str); + } + } + + { + ProfBeginV("Reserve Section Contrib Chunks [%S]", sect_defn->name); + + LNK_Section *sect = lnk_section_table_search(sectab, sect_name, sect_defn->flags); + if (!sect) { + sect = lnk_section_table_push(sectab, sect_name, sect_defn->flags); + } + + String8 defn_name_with_flags = lnk_make_name_with_flags(sectab->arena, sect_defn->name, sect_defn->flags); + LNK_SectionContribChunk *contrib_chunk = hash_table_search_string_raw(task->contribs_ht, defn_name_with_flags); + if (!contrib_chunk) { + contrib_chunk = lnk_section_contrib_chunk_list_push_chunk(main_arena, §->contribs, sect_defn->contribs_count, sort_idx); + hash_table_push_string_raw(sectab->arena, task->contribs_ht, defn_name_with_flags, contrib_chunk); + } + + ProfEnd(); + } + } + ProfEnd(); + + ProfBegin("Alloc Section Map"); + task->sect_map = push_array(main_arena, LNK_SectionContrib **, task->objs_count); + for EachIndex(obj_idx, task->objs_count) { task->sect_map[obj_idx] = push_array(main_arena, LNK_SectionContrib *, task->objs[obj_idx]->header.section_count_no_null); } + ProfEnd(); + } + + barrier_wait(tp->barrier); + + ProfBegin("Gather Section Contribs"); + for EachInRange(obj_idx, range) { + LNK_Obj *obj = task->objs[obj_idx]; + COFF_SectionHeader *section_table = (COFF_SectionHeader *)str8_substr(obj->data, obj->header.section_table_range).str; + String8 string_table = str8_substr(obj->data, obj->header.string_table_range); + + ProfBeginV("Gather Section Contribs [%S]", obj->path); + for EachIndex(sect_idx, obj->header.section_count_no_null) { + LNK_SectionContrib *sc = task->null_sc; + COFF_SectionHeader *sect_header = §ion_table[sect_idx]; + COFF_SectionFlags sect_flags = obj->section_flags[sect_idx]; + task->sect_map[obj_idx][sect_idx] = sc; + + if ( ! lnk_should_gather_section(obj, sect_idx, sect_header)) { continue; } - ProfBeginV("Gather Section Contribs [%S]", obj->path); - for (U64 sect_idx = 0; sect_idx < obj->header.section_count_no_null; sect_idx += 1) { - LNK_SectionContrib *sc = task->null_sc; - COFF_SectionHeader *sect_header = §ion_table[sect_idx]; - COFF_SectionFlags sect_flags = obj->section_flags[sect_idx]; - if (~sect_flags & COFF_SectionFlag_LnkRemove && ~sect_flags & COFF_SectionFlag_LnkInfo && sect_header->fsize > 0) { LNK_SectionContribChunk *sc_chunk = 0; { Temp temp = temp_begin(scratch.arena); - String8 sect_name = coff_name_from_section_header(string_table, sect_header); - String8 sect_name_with_flags = lnk_make_name_with_flags(temp.arena, sect_name, sect_flags & ~COFF_SectionFlags_LnkFlags); - sc_chunk = hash_table_search_string_raw(task->contribs_ht, sect_name_with_flags); + COFF_SectionFlags sect_flags_clean = sect_flags & ~(COFF_SectionFlags_LnkFlags | COFF_SectionFlags_Reserved); + String8 sect_name = coff_name_from_section_header(string_table, sect_header); + sect_flags_clean = lnk_apply_section_directives_to_flags(task->config, sect_name, sect_flags_clean); + String8 sect_key = lnk_make_name_with_flags(temp.arena, sect_name, sect_flags_clean); + sc_chunk = hash_table_search_string_raw(task->contribs_ht, sect_key); temp_end(temp); } @@ -2895,8 +3991,9 @@ THREAD_POOL_TASK_FUNC(lnk_gather_section_contribs_task) sc->u.obj_idx = obj_idx; sc->u.obj_sect_idx = sect_idx; } + task->sect_map[obj_idx][sect_idx] = sc; } - task->sect_map[obj_idx][sect_idx] = sc; + ProfEnd(); } ProfEnd(); @@ -2916,11 +4013,10 @@ THREAD_POOL_TASK_FUNC(lnk_set_comdat_leaders_contribs_task) if (~obj->section_flags[sect_idx] & COFF_SectionFlag_LnkCOMDAT) { continue; } - LNK_Symbol *symlink = lnk_obj_get_comdat_symlink(obj, section_number); - if (symlink == 0) { continue; } + LNK_ObjSymbolRef symlink_ref = {0}; + if ( ! lnk_obj_get_comdat_symlink(obj, section_number, &symlink_ref)) { continue; } - COFF_ParsedSymbol symlink_parsed = lnk_parsed_from_symbol(symlink); - LNK_ObjSymbolRef symlink_ref = lnk_ref_from_symbol(symlink); + COFF_ParsedSymbol symlink_parsed = lnk_parsed_symbol_from_coff_symbol_idx_no_name(symlink_ref.obj, symlink_ref.symbol_idx); task->sect_map[obj_idx][sect_idx] = task->sect_map[symlink_ref.obj->input_idx][symlink_parsed.section_number - 1]; } ProfEnd(); @@ -2935,7 +4031,7 @@ THREAD_POOL_TASK_FUNC(lnk_flag_debug_symbols_task) COFF_ParsedSymbol symbol; 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_no_name(obj, symbol_idx); COFF_SymbolValueInterpType interp = coff_interp_symbol(symbol.section_number, symbol.value, symbol.storage_class); if (interp == COFF_SymbolValueInterp_Regular) { if (obj->section_flags[symbol.section_number-1] & LNK_SECTION_FLAG_DEBUG) { @@ -2948,57 +4044,67 @@ THREAD_POOL_TASK_FUNC(lnk_flag_debug_symbols_task) internal THREAD_POOL_TASK_FUNC(lnk_patch_comdat_leaders_task) { - Temp scratch = scratch_begin(&arena, 1); - LNK_BuildImageTask *task = raw_task; U64 obj_idx = task_id; LNK_Obj *obj = task->objs[obj_idx]; - ProfBeginV("%S", obj->path); - - ProfBegin("Patch COMDAT Offsets"); + ProfBeginV("Patch COMDAT Offsets in %S", obj->path); COFF_ParsedSymbol symbol; 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_no_name(obj, symbol_idx); - COFF_SymbolValueInterpType interp = coff_interp_symbol(symbol.section_number, symbol.value, symbol.storage_class); - if (interp == COFF_SymbolValueInterp_Regular) { - 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; - } + COFF_SymbolValueInterpType interp = coff_interp_from_parsed_symbol(symbol); + if (interp != COFF_SymbolValueInterp_Regular) { continue; } - 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; - } + LNK_ObjSymbolRef symlink_ref = {0}; + if ( ! lnk_obj_get_comdat_symlink(obj, symbol.section_number, &symlink_ref)) { continue; } + + COFF_ParsedSymbol leader_symbol = lnk_parsed_symbol_from_coff_symbol_idx_no_name(symlink_ref.obj, symlink_ref.symbol_idx); + if (symlink_ref.obj == obj && leader_symbol.section_number == symbol.section_number) { continue; } + + B32 is_external = symbol.storage_class == COFF_SymStorageClass_External; + B32 is_same_obj = symlink_ref.obj == obj; + + U32 section_number = symbol.section_number; + U32 value = symbol.value; + B32 should_patch = 0; + + if (is_same_obj) { + B32 is_static_comdat_leader = symbol.storage_class == COFF_SymStorageClass_Static && obj->comdats[symbol.section_number-1] == symbol_idx; + if (is_external || is_static_comdat_leader) { + section_number = leader_symbol.section_number; + value = leader_symbol.value; + + // ICF folds sections by linking to the leader section definition; preserve + // public symbol offsets inside identical folded sections + if (is_external && leader_symbol.storage_class == COFF_SymStorageClass_Static && leader_symbol.aux_symbol_count > 0) { + value = symbol.value; } + + should_patch = 1; + } + } else { + String8 symbol_name = lnk_symbol_name_from_coff_symbol_idx(obj, symbol_idx); + String8 leader_name = lnk_symbol_name_from_coff_symbol_idx(symlink_ref.obj, symlink_ref.symbol_idx); + if (is_external && str8_match(symbol_name, leader_name, 0)) { + value = leader_symbol.value; + should_patch = 1; + } + } + + if (should_patch) { + 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 = safe_cast_u16(section_number); + symbol16->value = value; } } } ProfEnd(); - - ProfEnd(); - - scratch_end(scratch); } internal int @@ -3010,11 +4116,115 @@ lnk_section_contrib_ptr_is_before(void *raw_a, void *raw_b) return u64_compar_is_before(&input_idx_a, &input_idx_b); } +#define LNK_SORT_CONTRIBS_RADIX_BITS 8 +#define LNK_SORT_CONTRIBS_RADIX_SIZE (1 << LNK_SORT_CONTRIBS_RADIX_BITS) +#define LNK_SORT_CONTRIBS_RADIX_MIN (64u*1024u) + +typedef struct LNK_SortContribsRadixTask +{ + Rng1U64 *ranges; + U64 *keys_src; + U32 *indices_src; + U64 *keys_dst; + U32 *indices_dst; + U32 *hist; + U64 shift; +} LNK_SortContribsRadixTask; + +internal +THREAD_POOL_TASK_FUNC(lnk_sort_contribs_radix_hist_task) +{ + LNK_SortContribsRadixTask *task = raw_task; + U32 *hist = task->hist + (U64)task_id * LNK_SORT_CONTRIBS_RADIX_SIZE; + for EachInRange(i, task->ranges[task_id]) { + hist[(task->keys_src[i] >> task->shift) & (LNK_SORT_CONTRIBS_RADIX_SIZE - 1)] += 1; + } +} + +internal +THREAD_POOL_TASK_FUNC(lnk_sort_contribs_radix_scatter_task) +{ + LNK_SortContribsRadixTask *task = raw_task; + U32 *hist = task->hist + (U64)task_id * LNK_SORT_CONTRIBS_RADIX_SIZE; + for EachInRange(i, task->ranges[task_id]) { + U64 digit = (task->keys_src[i] >> task->shift) & (LNK_SORT_CONTRIBS_RADIX_SIZE - 1); + U32 dst_idx = hist[digit]++; + task->keys_dst[dst_idx] = task->keys_src[i]; + task->indices_dst[dst_idx] = task->indices_src[i]; + } +} + +internal void +lnk_sort_contribs_chunk_radix(TP_Context *tp, Arena *arena, LNK_SectionContribChunk *chunk) +{ + ProfBeginFunction(); + + Temp scratch = scratch_begin(&arena, 1); + + U64 count = chunk->count; + U64 worker_count = tp->worker_count; + + U64 *keys = push_array_no_zero(scratch.arena, U64, count); + U32 *indices = push_array_no_zero(scratch.arena, U32, count); + U64 max_key = 0; + for EachIndex(i, count) { + U64 key = Compose64Bit(chunk->v[i]->u.obj_idx, chunk->v[i]->u.obj_sect_idx); + keys[i] = key; + indices[i] = (U32)i; + max_key = Max(max_key, key); + } + + U64 significant_pass_count = (64 - clz64(max_key) + LNK_SORT_CONTRIBS_RADIX_BITS - 1) / LNK_SORT_CONTRIBS_RADIX_BITS; + U64 pass_count = significant_pass_count + (significant_pass_count & 1); + U64 *keys_buffer = push_array_no_zero(scratch.arena, U64, count); + U32 *indices_buffer = push_array_no_zero(scratch.arena, U32, count); + U32 *hist = push_array_no_zero(scratch.arena, U32, worker_count * LNK_SORT_CONTRIBS_RADIX_SIZE); + Rng1U64 *ranges = tp_divide_work(scratch.arena, count, worker_count); + + U64 *keys_src = keys, *keys_dst = keys_buffer; + U32 *indices_src = indices, *indices_dst = indices_buffer; + LNK_SortContribsRadixTask task = { .ranges = ranges, .hist = hist }; + for EachIndex(pass, pass_count) { + task.keys_src = keys_src; + task.indices_src = indices_src; + task.keys_dst = keys_dst; + task.indices_dst = indices_dst; + task.shift = pass * LNK_SORT_CONTRIBS_RADIX_BITS; + + MemoryZero(hist, sizeof(U32) * worker_count * LNK_SORT_CONTRIBS_RADIX_SIZE); + tp_for_parallel(tp, 0, worker_count, lnk_sort_contribs_radix_hist_task, &task); + + U64 offset = 0; + for EachIndex(digit, LNK_SORT_CONTRIBS_RADIX_SIZE) { + for EachIndex(worker_idx, worker_count) { + U32 *slot = &hist[worker_idx * LNK_SORT_CONTRIBS_RADIX_SIZE + digit]; + U32 count = *slot; + *slot = (U32)offset; + offset += count; + } + } + tp_for_parallel(tp, 0, worker_count, lnk_sort_contribs_radix_scatter_task, &task); + + Swap(U64 *, keys_src, keys_dst); + Swap(U32 *, indices_src, indices_dst); + } + + LNK_SectionContrib **sorted = push_array_no_zero(scratch.arena, LNK_SectionContrib *, count); + for EachIndex(i, count) { + sorted[i] = chunk->v[indices[i]]; + } + MemoryCopy(chunk->v, sorted, count * sizeof(*sorted)); + + scratch_end(scratch); + ProfEnd(); +} + internal THREAD_POOL_TASK_FUNC(lnk_sort_contribs_task) { LNK_BuildImageTask *task = raw_task; LNK_SectionContribChunk *chunk = task->u.sort_contribs.chunks[task_id]; + if (chunk->count >= LNK_SORT_CONTRIBS_RADIX_MIN) { return; } ProfBeginV("[%llu]", chunk->count); radsort(chunk->v, chunk->count, lnk_section_contrib_ptr_is_before); ProfEnd(); @@ -3079,10 +4289,10 @@ THREAD_POOL_TASK_FUNC(lnk_patch_common_block_symbols_task) ProfBeginV("Patch Common Block Symbols [%S]", obj->path); COFF_ParsedSymbol symbol; 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_no_name(obj, symbol_idx); COFF_SymbolValueInterpType interp = coff_interp_symbol(symbol.section_number, symbol.value, symbol.storage_class); if (interp == COFF_SymbolValueInterp_Common) { - LNK_Symbol *defn = lnk_symbol_table_search(task->symtab, symbol.name); + LNK_Symbol *defn = lnk_symbol_table_search(task->symtab, lnk_symbol_name_from_coff_symbol_idx(obj, symbol_idx)); COFF_ParsedSymbol defn_parsed = lnk_parsed_from_symbol(defn); Assert(lnk_interp_from_symbol(defn) == COFF_SymbolValueInterp_Regular); if (defn) { @@ -3113,16 +4323,12 @@ THREAD_POOL_TASK_FUNC(lnk_patch_regular_symbols_task) ProfBeginV("Patch Regular Symbols [%S]", obj->path); COFF_ParsedSymbol symbol; 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_no_name(obj, symbol_idx); - if (task->u.patch_symtabs.was_symbol_patched[obj_idx][symbol_idx]) { - continue; - } + if (task->u.patch_symtabs.was_symbol_patched[obj_idx][symbol_idx]) { continue; } COFF_SymbolValueInterpType interp = coff_interp_symbol(symbol.section_number, symbol.value, symbol.storage_class); if (interp == COFF_SymbolValueInterp_Regular) { - COFF_SectionHeader *sect_header = lnk_coff_section_header_from_section_number(obj, symbol.section_number); - LNK_SectionContrib *sc = task->sect_map[obj_idx][symbol.section_number-1]; U32 section_number; U32 value; @@ -3155,7 +4361,7 @@ lnk_patch_obj_symtab(LNK_SymbolTable *symtab, LNK_Obj *obj, B8 *was_symbol_patch COFF_ParsedSymbol fixup_dst; for (U64 symbol_idx = 0; symbol_idx < obj->header.symbol_count; symbol_idx += (1 + fixup_dst.aux_symbol_count)) { - fixup_dst = lnk_parsed_symbol_from_coff_symbol_idx(obj, symbol_idx); + fixup_dst = lnk_parsed_symbol_from_coff_symbol_idx_no_name(obj, symbol_idx); if (was_symbol_patched[symbol_idx]) { continue; } COFF_SymbolValueInterpType fixup_dst_type = coff_interp_symbol(fixup_dst.section_number, fixup_dst.value, fixup_dst.storage_class); @@ -3165,7 +4371,7 @@ lnk_patch_obj_symtab(LNK_SymbolTable *symtab, LNK_Obj *obj, B8 *was_symbol_patch LNK_ObjSymbolRef fixup_symbol = {0}; B32 is_resolved = lnk_resolve_symbol(symtab, symbol_to_resolve, &fixup_symbol); if (is_resolved) { - COFF_ParsedSymbol fixup_src = lnk_parsed_symbol_from_coff_symbol_idx(fixup_symbol.obj, fixup_symbol.symbol_idx); + COFF_ParsedSymbol fixup_src = lnk_parsed_symbol_from_coff_symbol_idx_no_name(fixup_symbol.obj, fixup_symbol.symbol_idx); COFF_SymbolValueInterpType fixup_type = coff_interp_symbol(fixup_src.section_number, fixup_src.value, fixup_src.storage_class); B32 was_fixup_removed = fixup_src.section_number == lnk_obj_get_removed_section_number(fixup_symbol.obj); @@ -3313,13 +4519,14 @@ THREAD_POOL_TASK_FUNC(lnk_obj_reloc_patcher) U32 symbol_secoff = 0; S64 symbol_voff = 0; { - COFF_ParsedSymbol symbol = lnk_parsed_symbol_from_coff_symbol_idx(obj, reloc->isymbol); + COFF_ParsedSymbol symbol = lnk_parsed_symbol_from_coff_symbol_idx_no_name(obj, reloc->isymbol); COFF_SymbolValueInterpType interp = coff_interp_from_parsed_symbol(symbol); if (interp == COFF_SymbolValueInterp_Regular) { if (symbol.section_number == lnk_obj_get_removed_section_number(obj)) { if (~section_flags & LNK_SECTION_FLAG_DEBUG) { String8 sect_name = coff_name_from_section_header(string_table, §ion_table[sect_idx]); - lnk_error_obj(LNK_Error_RelocationAgainstRemovedSection, obj, "relocating against symbol that is in a removed section (symbol: %S, reloc-section: %S 0x%llx, reloc-index: 0x%llx)", symbol.name, sect_name, sect_idx+1, reloc_idx); + String8 symbol_name = lnk_symbol_name_from_coff_symbol_idx(obj, reloc->isymbol); + lnk_error_obj(LNK_Error_RelocationAgainstRemovedSection, obj, "relocating against symbol that is in a removed section (symbol: %S, reloc-section: %S 0x%llx, reloc-index: 0x%llx)", symbol_name, sect_name, sect_idx+1, reloc_idx); } continue; } @@ -3330,7 +4537,7 @@ THREAD_POOL_TASK_FUNC(lnk_obj_reloc_patcher) // There aren't enough bits in COFF symbol to store full image base address, // so we special case __ImageBase. A better solution would be to add // a 64-bit symbol format to COFF. - if (str8_match(symbol.name, str8_lit("__ImageBase"), 0)) { + if (str8_match(lnk_symbol_name_from_coff_symbol_idx(obj, reloc->isymbol), str8_lit("__ImageBase"), 0)) { symbol.value = task->image_base; } symbol_secnum = 0; @@ -3370,15 +4577,6 @@ THREAD_POOL_TASK_FUNC(lnk_obj_reloc_patcher) ProfEnd(); } -internal int -lnk_section_definition_is_before(void *raw_a, void *raw_b) -{ - LNK_SectionDefinition **a = raw_a, **b = raw_b; - U64 input_idx_a = Compose64Bit((*a)->obj->input_idx, (*a)->obj_sect_idx); - U64 input_idx_b = Compose64Bit((*b)->obj->input_idx, (*b)->obj_sect_idx); - return u64_compar_is_before(&input_idx_a, &input_idx_b); -} - internal THREAD_POOL_TASK_FUNC(lnk_count_common_block_contribs_task) { @@ -3428,7 +4626,7 @@ THREAD_POOL_TASK_FUNC(lnk_flag_hotpatch_contribs_task) if (obj->hotpatch) { COFF_ParsedSymbol symbol; 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_no_name(obj, symbol_idx); COFF_SymbolValueInterpType interp = coff_interp_symbol(symbol.section_number, symbol.value, symbol.storage_class); if (interp == COFF_SymbolValueInterp_Regular && COFF_SymbolType_IsFunc(symbol.type)) { LNK_SectionContrib *sc = task->sect_map[obj_idx][symbol.section_number-1]; @@ -3843,7 +5041,7 @@ THREAD_POOL_TASK_FUNC(lnk_patch_section_symbols_task) ProfBegin("Patch Section Symbols [%S]", obj->path); COFF_ParsedSymbol symbol; 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_no_name(obj, symbol_idx); COFF_SymbolValueInterpType interp = coff_interp_symbol(symbol.section_number, symbol.value, symbol.storage_class); if (interp == COFF_SymbolValueInterp_Undefined) { if (symbol.storage_class == COFF_SymStorageClass_Section) { @@ -3866,7 +5064,8 @@ THREAD_POOL_TASK_FUNC(lnk_patch_section_symbols_task) } if (!is_referenced) { continue; } - LNK_Section *sect = lnk_section_table_search(task->sectab, symbol.name, symbol.value); + String8 symbol_name = lnk_symbol_name_from_coff_symbol_idx(obj, symbol_idx); + LNK_Section *sect = lnk_section_table_search(task->sectab, symbol_name, symbol.value); if (sect && (~sect->flags & COFF_SectionFlag_LnkRemove)) { if (~sect->flags & COFF_SectionFlag_MemDiscardable) { LNK_SectionContrib *first_sc = lnk_get_first_section_contrib(sect); @@ -3882,7 +5081,7 @@ THREAD_POOL_TASK_FUNC(lnk_patch_section_symbols_task) symbol16->storage_class = COFF_SymStorageClass_Static; } } else { - lnk_error_obj(LNK_Error_SectRefsDiscardedMemory, obj, "symbol %S (No. 0x%llx) references section with discard flag", symbol.name, symbol_idx); + lnk_error_obj(LNK_Error_SectRefsDiscardedMemory, obj, "symbol %S (No. 0x%llx) references section with discard flag", symbol_name, symbol_idx); } } else { U64 fallback_voff = 0; @@ -3910,7 +5109,7 @@ THREAD_POOL_TASK_FUNC(lnk_patch_section_symbols_task) symbol16->storage_class = COFF_SymStorageClass_Static; } - lnk_error_obj(LNK_Warning_UndefinedSectionSymbol, obj, "undefined section symbol %S (No. 0x%llx) refers to an image section that doesn't exist; patching to %#llx", symbol.name, symbol_idx, fallback_voff); + lnk_error_obj(LNK_Warning_UndefinedSectionSymbol, obj, "undefined section symbol %S (No. 0x%llx) refers to an image section that doesn't exist; patching to %#llx", symbol_name, symbol_idx, fallback_voff); } } } @@ -3936,7 +5135,7 @@ THREAD_POOL_TASK_FUNC(lnk_gather_base_reloc_pages_task) for EachIndex(reloc_idx, relocs.count) { COFF_Reloc *r = &relocs.v[reloc_idx]; - COFF_ParsedSymbol symbol = lnk_parsed_symbol_from_coff_symbol_idx(obj, r->isymbol); + COFF_ParsedSymbol symbol = lnk_parsed_symbol_from_coff_symbol_idx_no_name(obj, r->isymbol); COFF_SymbolValueInterpType symbol_interp = coff_interp_from_parsed_symbol(symbol); if (symbol_interp == COFF_SymbolValueInterp_Abs) { continue; } @@ -3964,7 +5163,7 @@ THREAD_POOL_TASK_FUNC(lnk_gather_base_reloc_pages_task) switch (is_addr) { case 4: { if (task->is_large_addr_aware) { - lnk_error_obj(LNK_Error_LargeAddrAwareRequired, obj, "found out of range ADDR32 relocation for '%S', link with /LARGEADDRESSAWARE:NO", symbol.name); + lnk_error_obj(LNK_Error_LargeAddrAwareRequired, obj, "found out of range ADDR32 relocation for '%S', link with /LARGEADDRESSAWARE:NO", lnk_symbol_name_from_coff_symbol_idx(obj, r->isymbol)); } else { u64_list_push(arena, page->v.entries_addr32, reloc_voff); } @@ -4412,6 +5611,22 @@ lnk_build_win32_header(Arena *arena, LNK_SymbolTable *symtab, LNK_Config *config return result; } +internal LNK_Section * +lnk_image_section_table_push(LNK_Config *config, LNK_SectionTable *sectab, String8 name, COFF_SectionFlags flags) +{ + flags = lnk_apply_section_directives_to_flags(config, name, flags); + LNK_Section *sect = lnk_section_table_push(sectab, name, flags); + return sect; +} + +internal LNK_Section * +lnk_image_section_table_search(LNK_Config *config, LNK_SectionTable *sectab, String8 name, COFF_SectionFlags flags) +{ + flags = lnk_apply_section_directives_to_flags(config, name, flags); + LNK_Section *sect = lnk_section_table_search(sectab, name, flags); + return sect; +} + internal LNK_ImageContext lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolTable *symtab, U64 objs_count, LNK_Obj **objs) { @@ -4424,14 +5639,15 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT // init section table // LNK_SectionTable *sectab = lnk_section_table_alloc(); - lnk_section_table_push(sectab, str8_lit(".text" ), PE_TEXT_SECTION_FLAGS ); - lnk_section_table_push(sectab, str8_lit(".rdata"), PE_RDATA_SECTION_FLAGS); - lnk_section_table_push(sectab, str8_lit(".data" ), PE_DATA_SECTION_FLAGS ); - lnk_section_table_push(sectab, str8_lit(".bss" ), PE_BSS_SECTION_FLAGS ); - lnk_section_table_push(sectab, str8_lit(".pdata"), PE_PDATA_SECTION_FLAGS); - LNK_Section *common_block_sect = lnk_section_table_search(sectab, str8_lit(".bss"), PE_BSS_SECTION_FLAGS); + lnk_image_section_table_push(config, sectab, str8_lit(".text" ), PE_TEXT_SECTION_FLAGS ); + lnk_image_section_table_push(config, sectab, str8_lit(".rdata"), PE_RDATA_SECTION_FLAGS); + lnk_image_section_table_push(config, sectab, str8_lit(".data" ), PE_DATA_SECTION_FLAGS ); + lnk_image_section_table_push(config, sectab, str8_lit(".bss" ), PE_BSS_SECTION_FLAGS ); + lnk_image_section_table_push(config, sectab, str8_lit(".pdata"), PE_PDATA_SECTION_FLAGS); + LNK_Section *common_block_sect = lnk_image_section_table_search(config, sectab, str8_lit(".bss"), PE_BSS_SECTION_FLAGS); LNK_BuildImageTask task = { + .config = config, .symtab = symtab, .sectab = sectab, .objs_count = objs_count, @@ -4442,130 +5658,42 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT .null_sc = push_array(arena->v[0], LNK_SectionContrib, 1), }; - { - ProfBegin("Define And Count Sections"); - TP_Temp temp = tp_temp_begin(arena); - - ProfBegin("Init Hash Tables For Gathering Section Definitions"); - task.u.gather_sects.defns = push_array(arena->v[0], HashTable *, tp->worker_count); - for EachIndex(worker_id, tp->worker_count) { task.u.gather_sects.defns[worker_id] = hash_table_init(arena->v[0], 128); } - ProfEnd(); - - tp_for_parallel_prof(tp, arena, objs_count, lnk_gather_section_definitions_task, &task, "Gather Section Definitions"); - - ProfBegin("Merge Section Definitions Hash Tables"); - for (U64 worker_idx = 1; worker_idx < tp->worker_count; worker_idx += 1) { - U64 sect_defns_count = task.u.gather_sects.defns[worker_idx]->count; - LNK_SectionDefinition **sect_defns = values_from_hash_table_raw(arena->v[0], task.u.gather_sects.defns[worker_idx]); - radsort(sect_defns, sect_defns_count, lnk_section_definition_is_before); - - for EachIndex(defn_idx, sect_defns_count) { - LNK_SectionDefinition *defn = sect_defns[defn_idx]; - String8 name_with_flags = lnk_make_name_with_flags(arena->v[0], defn->name, defn->flags); - LNK_SectionDefinition *main_defn = hash_table_search_string_raw(task.u.gather_sects.defns[0], name_with_flags); - if (main_defn == 0) { - main_defn = sect_defns[defn_idx]; - hash_table_push_string_raw(arena->v[0], task.u.gather_sects.defns[0], name_with_flags, main_defn); - } else { - if (lnk_section_definition_is_before(§_defns[defn_idx], &main_defn)) { - main_defn->obj = sect_defns[defn_idx]->obj; - main_defn->obj_sect_idx = sect_defns[defn_idx]->obj_sect_idx; - } - main_defn->contribs_count += sect_defns[defn_idx]->contribs_count; - } - } - } - U64 sect_defns_count = task.u.gather_sects.defns[0]->count; - LNK_SectionDefinition **sect_defns = values_from_hash_table_raw(arena->v[0], task.u.gather_sects.defns[0]); - ProfEnd(); - - ProfBegin("Sort Sections Definitions"); - radsort(sect_defns, sect_defns_count, lnk_section_definition_is_before); - ProfEnd(); - - ProfBegin("Push Sections And Reserve Section Contrib Memory"); - task.contribs_ht = hash_table_init(sectab->arena, sect_defns_count); - for EachIndex(defn_idx, sect_defns_count) { - LNK_SectionDefinition *sect_defn = sect_defns[defn_idx]; - - // parse section name - String8 sect_name, sort_idx; - coff_parse_section_name(sect_defn->name, §_name, &sort_idx); - - // do not create definitions for sections that are removed from the image - if (lnk_is_section_removed(config, sect_name)) { continue; } - - // warn about conflicting section flags - for (LNK_SectionNode *sect_n = sectab->list.first; sect_n != 0; sect_n = sect_n->next) { - if (str8_match(sect_n->data.name, sect_name, 0) && sect_n->data.flags != sect_defn->flags) { - LNK_Obj *obj = sect_defn->obj; - U32 sect_number = sect_defn->obj_sect_idx + 1; - COFF_SectionHeader *sect_header = lnk_coff_section_header_from_section_number(obj, sect_number); - String8 sect_name = coff_name_from_section_header(str8_substr(obj->data, obj->header.string_table_range), sect_header); - String8 expected_flags_str = coff_string_from_section_flags(arena->v[0], sect_n->data.flags); - String8 current_flags_str = coff_string_from_section_flags(arena->v[0], sect_defn->flags); - lnk_error_obj(LNK_Warning_SectionFlagsConflict, sect_defn->obj, "detected section flags conflict in %S(No. %X); expected {%S} but got {%S}", sect_name, sect_number, expected_flags_str, current_flags_str); - } - } - - { - ProfBeginV("Reserve Section Contrib Chunks [%S]", sect_defn->name); - - LNK_Section *sect = lnk_section_table_search(sectab, sect_name, sect_defn->flags); - if (!sect) { - sect = lnk_section_table_push(sectab, sect_name, sect_defn->flags); - } - - String8 defn_name_with_flags = lnk_make_name_with_flags(sectab->arena, sect_defn->name, sect_defn->flags); - LNK_SectionContribChunk *contrib_chunk = hash_table_search_string_raw(task.contribs_ht, defn_name_with_flags); - if (!contrib_chunk) { - contrib_chunk = lnk_section_contrib_chunk_list_push_chunk(arena->v[0], §->contribs, sect_defn->contribs_count, sort_idx); - hash_table_push_string_raw(sectab->arena, task.contribs_ht, defn_name_with_flags, contrib_chunk); - } - - ProfEnd(); - } - } - ProfEnd(); - - tp_temp_end(temp); - ProfEnd(); - } - U64 expected_image_header_size; { - ProfBegin("Alloc Section Map"); - task.sect_map = push_array(scratch.arena, LNK_SectionContrib **, objs_count); - for EachIndex(obj_idx, objs_count) { task.sect_map[obj_idx] = push_array(scratch.arena, LNK_SectionContrib *, objs[obj_idx]->header.section_count_no_null); } - ProfEnd(); - - tp_for_parallel_prof(tp, 0, objs_count, lnk_gather_section_contribs_task, &task, "Gather Section Contribs"); + ProfScope("Gather Sections") + { + TP_Temp temp = tp_temp_begin(arena); + task.u.gather_sects.arena = arena->v[0]; + task.u.gather_sects.ranges = tp_divide_work(arena->v[0], objs_count, tp->worker_count); + task.u.gather_sects.defns = push_array(arena->v[0], HashTable *, tp->worker_count); + tp_for_parallel_prof(tp, arena, tp->worker_count, lnk_gather_sections_task, &task, "Gather Sections"); + tp_temp_end(temp); + } // ensure determinism by sorting section contribs in chunks by input index + ProfScope("Sort Section Contribs") { - ProfBegin("Sort Section Contribs"); - U64 total_chunk_count = 0; - { - for (LNK_SectionNode *sect_n = sectab->list.first; sect_n != 0; sect_n = sect_n->next) { - total_chunk_count += sect_n->data.contribs.chunk_count; - } + for EachNode(sect_n, LNK_SectionNode, sectab->list.first) { + total_chunk_count += sect_n->data.contribs.chunk_count; } - { - U64 cursor = 0; - task.u.sort_contribs.chunks = push_array(scratch.arena, LNK_SectionContribChunk *, total_chunk_count); - for (LNK_SectionNode *sect_n = sectab->list.first; sect_n != 0; sect_n = sect_n->next) { - for (LNK_SectionContribChunk *chunk_n = sect_n->data.contribs.first; chunk_n != 0; chunk_n = chunk_n->next) { - task.u.sort_contribs.chunks[cursor++] = chunk_n; - } + U64 cursor = 0; + task.u.sort_contribs.chunks = push_array(scratch.arena, LNK_SectionContribChunk *, total_chunk_count); + for EachNode(sect_n, LNK_SectionNode, sectab->list.first) { + for EachNode(chunk_n, LNK_SectionContribChunk, sect_n->data.contribs.first) { + task.u.sort_contribs.chunks[cursor++] = chunk_n; } - Assert(cursor == total_chunk_count); } + Assert(cursor == total_chunk_count); + for EachIndex(chunk_idx, total_chunk_count) { + LNK_SectionContribChunk *chunk = task.u.sort_contribs.chunks[chunk_idx]; + if (chunk->count >= LNK_SORT_CONTRIBS_RADIX_MIN) { + lnk_sort_contribs_chunk_radix(tp, scratch.arena, chunk); + } + } tp_for_parallel(tp, 0, total_chunk_count, lnk_sort_contribs_task, &task); - - ProfEnd(); } tp_for_parallel_prof(tp, 0, objs_count, lnk_set_comdat_leaders_contribs_task, &task, "Update Section Map With COMDAT Leader Contribs"); @@ -4650,30 +5778,30 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT } // assign contribs offsets, sizes, and section indices - for (LNK_SectionNode *sect_n = sectab->list.first; sect_n != 0; sect_n = sect_n->next) { + for EachNode(sect_n, LNK_SectionNode, sectab->list.first) { lnk_finalize_section_layout(§_n->data, config->file_align, config->function_pad_min); } // remove empty sections { String8List empty_sect_list = {0}; - for (LNK_SectionNode *sect_n = sectab->list.first; sect_n != 0; sect_n = sect_n->next) { - if (sect_n->data.vsize == 0) { + for EachNode(sect_n, LNK_SectionNode, sectab->list.first) { + if (sect_n->data.vsize == 0 && sect_n->data.contribs.chunk_count == 0) { str8_list_push(scratch.arena, &empty_sect_list, sect_n->data.name); } } - for (String8Node *name_n = empty_sect_list.first; name_n != 0; name_n = name_n->next) { + for EachNode(name_n, String8Node, empty_sect_list.first) { lnk_section_table_purge(sectab, name_n->string); } } // assign section indices to sections - for (LNK_SectionNode *sect_n = sectab->list.first; sect_n != 0; sect_n = sect_n->next) { + for EachNode(sect_n, LNK_SectionNode, sectab->list.first) { lnk_assign_section_index(§_n->data, sectab->next_sect_idx++); } // assing layout offsets and sizes to merged sections - for (LNK_SectionNode *sect_n = sectab->merge_list.first; sect_n != 0; sect_n = sect_n->next) { + for EachNode(sect_n, LNK_SectionNode, sectab->merge_list.first) { LNK_Section *sect = §_n->data; LNK_SectionContrib *first_sc = lnk_get_first_section_contrib(sect); LNK_SectionContrib *last_sc = lnk_get_last_section_contrib(sect); @@ -4729,7 +5857,7 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT if (~config->flags & LNK_ConfigFlag_Fixed) { String8 base_relocs_data = lnk_build_base_relocs(tp, arena, config, objs_count, objs); if (base_relocs_data.size) { - LNK_Section *reloc = lnk_section_table_push(sectab, str8_lit(".reloc"), PE_RELOC_SECTION_FLAGS); + LNK_Section *reloc = lnk_image_section_table_push(config, sectab, str8_lit(".reloc"), PE_RELOC_SECTION_FLAGS); LNK_SectionContribChunk *first_sc_chunk = lnk_section_contrib_chunk_list_push_chunk(sectab->arena, &reloc->contribs, 1, str8_zero()); LNK_SectionContrib *sc = lnk_section_contrib_chunk_push(first_sc_chunk, 1); sc->first_data_node.string = base_relocs_data; @@ -4861,6 +5989,7 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT U32 load_config_size = 0; if (sizeof(load_config_size) <= load_config_data.size) { + MemoryCopyStruct(&load_config_size, load_config_data.str); // TODO: load config PE_DataDirectory *load_config_dir = pe_data_directory_from_idx(image_data, pe, PE_DataDirectoryIndex_LOAD_CONFIG); load_config_dir->virt_off = lnk_voff_from_symbol(image_section_table, load_config_symbol); load_config_dir->virt_size = load_config_size; @@ -4872,7 +6001,7 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT // patch exceptions { - LNK_Section *pdata_sect = lnk_section_table_search(sectab, str8_lit(".pdata"), PE_PDATA_SECTION_FLAGS); + LNK_Section *pdata_sect = lnk_image_section_table_search(config, sectab, str8_lit(".pdata"), PE_PDATA_SECTION_FLAGS); if (pdata_sect) { String8 raw_pdata = str8_substr(image_data, rng_1u64(pdata_sect->foff, pdata_sect->foff + pdata_sect->vsize)); pe_pdata_sort(config->machine, raw_pdata); @@ -4885,7 +6014,7 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT // patch export { - LNK_Section *edata_sect = lnk_section_table_search(sectab, str8_lit(".edata"), PE_EDATA_SECTION_FLAGS); + LNK_Section *edata_sect = lnk_image_section_table_search(config, sectab, str8_lit(".edata"), PE_EDATA_SECTION_FLAGS); if (edata_sect) { PE_DataDirectory *export_dir = pe_data_directory_from_idx(image_data, pe, PE_DataDirectoryIndex_EXPORT); LNK_SectionContrib *edata_first_contrib = lnk_get_first_section_contrib(edata_sect); @@ -4897,7 +6026,7 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT // patch base relocs { - LNK_Section *reloc_sect = lnk_section_table_search(sectab, str8_lit(".reloc"), PE_RELOC_SECTION_FLAGS); + LNK_Section *reloc_sect = lnk_image_section_table_search(config, sectab, str8_lit(".reloc"), PE_RELOC_SECTION_FLAGS); if (reloc_sect) { PE_DataDirectory *reloc_dir = pe_data_directory_from_idx(image_data, pe, PE_DataDirectoryIndex_BASE_RELOC); reloc_dir->virt_off = lnk_get_first_section_contrib_voff(image_section_table, reloc_sect); @@ -4907,7 +6036,7 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT // patch import and import addr { - LNK_Section *idata_sect = lnk_section_table_search(sectab, str8_lit(".idata"), PE_IDATA_SECTION_FLAGS); + LNK_Section *idata_sect = lnk_image_section_table_search(config, sectab, str8_lit(".idata"), PE_IDATA_SECTION_FLAGS); LNK_Symbol *null_import_desc = lnk_symbol_table_searchf(symtab, "__NULL_IMPORT_DESCRIPTOR"); LNK_Symbol *null_thunk_data = lnk_symbol_table_searchf(symtab, "\x7f%S_NULL_THUNK_DATA", lnk_get_image_name(config)); if (idata_sect && null_import_desc && null_thunk_data) { @@ -4915,21 +6044,21 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT LNK_SectionContrib *idata_first_contrib = lnk_get_first_section_contrib(idata_sect); PE_DataDirectory *import_dir = pe_data_directory_from_idx(image_data, pe, PE_DataDirectoryIndex_IMPORT); import_dir->virt_off = image_section_table[idata_first_contrib->u.sect_idx + 1]->voff + idata_first_contrib->u.off; - import_dir->virt_size = null_import_desc_parsed.value - idata_first_contrib->u.off; + import_dir->virt_size = null_import_desc_parsed.value - idata_first_contrib->u.off + sizeof(PE_ImportEntry); COFF_ParsedSymbol null_thunk_data_parsed = lnk_parsed_from_symbol(null_thunk_data); U64 null_thunk_data_voff = image_section_table[null_thunk_data_parsed.section_number]->voff + null_thunk_data_parsed.value; U64 first_import_foff = image_section_table[idata_first_contrib->u.sect_idx+1]->foff + idata_first_contrib->u.off; PE_ImportEntry *first_import = str8_deserial_get_raw_ptr(image_data, first_import_foff, sizeof(*first_import)); PE_DataDirectory *import_addr_dir = pe_data_directory_from_idx(image_data, pe, PE_DataDirectoryIndex_IMPORT_ADDR); - import_addr_dir->virt_off = lnk_get_first_section_contrib_voff(image_section_table, idata_sect); + import_addr_dir->virt_off = first_import->import_addr_table_voff; import_addr_dir->virt_size = null_thunk_data_voff - first_import->import_addr_table_voff /* null */ + coff_word_size_from_machine(config->machine); } } // patch delay imports { - LNK_Section *didat_sect = lnk_section_table_search(sectab, str8_lit(".didat"), PE_IDATA_SECTION_FLAGS); + LNK_Section *didat_sect = lnk_image_section_table_search(config, sectab, str8_lit(".didat"), PE_IDATA_SECTION_FLAGS); LNK_Symbol *null_import_desc = lnk_symbol_table_search(symtab, str8_lit("__NULL_DELAY_IMPORT_DESCRIPTOR")); LNK_Symbol *last_null_thunk = lnk_symbol_table_searchf(symtab,"\x7f%S_NULL_THUNK_DATA_DLA", lnk_get_image_name(config)); if (didat_sect && null_import_desc && last_null_thunk) { @@ -4949,7 +6078,7 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT // find max align in .tls U64 tls_align = 0; - LNK_Section *tls_sect = lnk_section_table_search(sectab, str8_lit(".tls"), PE_TLS_SECTION_FLAGS); + LNK_Section *tls_sect = lnk_image_section_table_search(config, sectab, str8_lit(".tls"), PE_TLS_SECTION_FLAGS); for (LNK_SectionContribChunk *sc_chunk = tls_sect->contribs.first; sc_chunk != 0; sc_chunk = sc_chunk->next) { for EachIndex (sc_idx, sc_chunk->count) { Assert(IsPow2(sc_chunk->v[sc_idx]->align)); @@ -4979,7 +6108,7 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT // patch debug { - LNK_Section *debug_dir_sect = lnk_section_table_search(sectab, str8_lit(".RAD_LINK_PE_DEBUG_DIR"), PE_RDATA_SECTION_FLAGS); + LNK_Section *debug_dir_sect = lnk_image_section_table_search(config, sectab, str8_lit(".RAD_LINK_PE_DEBUG_DIR"), PE_RDATA_SECTION_FLAGS); if (debug_dir_sect) { // patch directory PE_DataDirectory *debug_dir = pe_data_directory_from_idx(image_data, pe, PE_DataDirectoryIndex_DEBUG); @@ -5006,7 +6135,7 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT // patch resources { - LNK_Section *rsrc_sect = lnk_section_table_search(sectab, str8_lit(".rsrc"), PE_RSRC_SECTION_FLAGS); + LNK_Section *rsrc_sect = lnk_image_section_table_search(config, sectab, str8_lit(".rsrc"), PE_RSRC_SECTION_FLAGS); if (rsrc_sect) { PE_DataDirectory *rsrc_dir = pe_data_directory_from_idx(image_data, pe, PE_DataDirectoryIndex_RESOURCES); rsrc_dir->virt_off = lnk_get_first_section_contrib_voff(image_section_table, rsrc_sect); @@ -5420,6 +6549,11 @@ lnk_run_linker(TP_Context *tp, TP_Arena *arena, LNK_Config *config) image_write_ctx->data = image_ctx.image_data; Thread image_write_thread = thread_launch(lnk_write_thread, image_write_ctx); + LNK_BackgroundFileWriter background_file_writer = {0}; + LNK_PdbWriter pdb_writer = { .file_writer = &background_file_writer }; + Temp pdb_huge_temp = temp_begin(lnk_get_huge_arena()); + lnk_background_file_writer_begin(pdb_writer.file_writer); + // // RAD Map // @@ -5464,12 +6598,11 @@ lnk_run_linker(TP_Context *tp, TP_Arena *arena, LNK_Config *config) // TODO: Parallel debug info builds are currently blocked by the patch // strings in $$FILE_CHECKSUM step in `lnk_process_c13_data_task`. if (config->debug_mode == LNK_DebugMode_Full || config->rad_debug == LNK_SwitchState_Yes) { - Temp huge_arena_temp = temp_begin(lnk_get_huge_arena()); - - String8List pdb_data = {0}; + LNK_FileArtifact pdb_artifact = {0}; { lnk_timer_begin(LNK_Timer_Pdb); - if (config->pdb_hash_type_names != LNK_TypeNameHashMode_Null && config->pdb_hash_type_names != LNK_TypeNameHashMode_None) { + + if (config->pdb_hash_type_names != LNK_TypeNameHashMode_None) { lnk_replace_type_names_with_hashes(tp, arena, cv_types.count[CV_TypeIndexSource_TPI], @@ -5478,17 +6611,18 @@ lnk_run_linker(TP_Context *tp, TP_Arena *arena, LNK_Config *config) config->pdb_hash_type_name_length, config->pdb_hash_type_name_map); } - pdb_data = lnk_build_pdb(tp, arena, image_ctx.image_data, config, symtab, &cv, cv_types, LNK_PDB_BuilderFlag_All); - if (config->debug_mode == LNK_DebugMode_Full) { - lnk_write_data_list_to_file_path(config->pdb_name, config->temp_pdb_name, pdb_data); - } + + pdb_writer.output_path = config->debug_mode == LNK_DebugMode_Full ? config->pdb_name : str8_zero(); + pdb_writer.temp_output_path = config->debug_mode == LNK_DebugMode_Full ? config->temp_pdb_name : str8_zero(); + pdb_artifact = lnk_build_pdb(tp, arena, image_ctx.image_data, config, symtab, &cv, cv_types, pdb_writer, LNK_PDB_BuilderFlag_All); + lnk_timer_end(LNK_Timer_Pdb); } if (config->rad_debug == LNK_SwitchState_Yes) { lnk_timer_begin(LNK_Timer_Rdi); - LNK_P2R p2r = { .config = config, .pdb_data = str8_list_join(lnk_get_huge_arena(), &pdb_data, 0), .image_data = image_ctx.image_data }; + LNK_P2R p2r = { .config = config, .pdb_data = lnk_data_from_file_artifact(lnk_get_huge_arena(), &pdb_artifact), .image_data = image_ctx.image_data }; tp_for_parallel(tp, arena, tp->worker_count, lnk_p2r_worker, &p2r); String8List rdi_blobs = rdim_file_blobs_from_section_bundle(scratch.arena, &p2r.bake_results.section_bundle); @@ -5496,8 +6630,6 @@ lnk_run_linker(TP_Context *tp, TP_Arena *arena, LNK_Config *config) lnk_timer_end(LNK_Timer_Rdi); } - - temp_end(huge_arena_temp); } // @@ -5565,14 +6697,25 @@ lnk_run_linker(TP_Context *tp, TP_Arena *arena, LNK_Config *config) stripped_cv.debug_s_arr = debug_s_arr; stripped_cv.symbol_input_ranges = push_array(scratch.arena, Rng1U64, tp->worker_count); - String8List pdb_data = lnk_build_pdb(tp, arena, image_ctx.image_data, config, symtab, &stripped_cv, (LNK_MergedTypes){0}, LNK_PDB_BuilderFlag_All); - lnk_write_data_list_to_file_path(config->pdb_stripped_name, str8f(scratch.arena, "%S.tmp", config->pdb_stripped_name), pdb_data); + LNK_FileArtifact pdb_artifact = lnk_build_pdb(tp, arena, image_ctx.image_data, config, symtab, &stripped_cv, (LNK_MergedTypes){0}, (LNK_PdbWriter){0}, LNK_PDB_BuilderFlag_All); + lnk_write_data_list_to_file_path(config->pdb_stripped_name, str8f(scratch.arena, "%S.tmp", config->pdb_stripped_name), pdb_artifact.data); } lnk_timer_end(LNK_Timer_Debug); ProfEnd(); } +#if OS_WINDOWS + // for unexplained reasons, file mappings on Windows cause slow process exit times + ProfBegin("Release Input File Maps"); + lnk_inputer_release_file_maps(tp, inputer); + ProfEnd(); +#endif + + // PDB output borrows pages from the huge arena, so drain after map release + lnk_background_file_writer_end(pdb_writer.file_writer); + temp_end(pdb_huge_temp); + // wait for the thread to finish writing image to disk thread_join(image_write_thread, -1); @@ -5750,7 +6893,7 @@ lnk_run_type_server(TP_Context *tp, TP_Arena *arena, LNK_Config *config) include_objs = u64_array_from_list(scratch.arena, &include_obj_list); } - LNK_RRT rrt = {0}; + LNK_RRT rrt = { .debug_types_hash = config->debug_types_hash }; ProfScope("Pack Type Data & Data Ranges") { LNK_RRTTypeDataSerializer task = { &cv_types, &rrt.type_data_raw, rrt.type_data_ranges }; diff --git a/src/linker/lnk.h b/src/linker/lnk.h index d6388d0a..7d27c247 100644 --- a/src/linker/lnk.h +++ b/src/linker/lnk.h @@ -34,6 +34,7 @@ typedef struct LNK_Input String8 data; B32 disallow; B32 is_thin; + B32 owns_file_map; B32 has_disk_read_failed; B32 exclude_from_debug_info; LNK_LibMemberRef *link_member; @@ -74,6 +75,7 @@ typedef struct LNK_Inputer #define LNK_NULL_SYMBOL "*** RAD_NULL_SYMBOL ***" #define LNK_SECTION_FLAG_DEBUG (1 << 0) +#define LNK_SECTION_FLAG_NOICF (1 << 1) typedef U8 LNK_LibMemberFlags; enum @@ -204,6 +206,7 @@ typedef struct LNK_BaseRelocPageArray typedef struct { B32 search_anti_deps; + B32 reset_search_cursor; LNK_Link *link; HashMap *imports_hm; LNK_SymbolTable *symtab; @@ -215,10 +218,12 @@ typedef struct typedef struct { - LNK_SymbolTable *symtab; - LNK_Config *config; - LNK_ObjList objs; -} LNK_OptRefTask; + LNK_SymbolTable *symtab; + LNK_Config *config; + LNK_Obj **objs; + U64 objs_count; + U32Array *obj_indices; +} LNK_OptTask; typedef struct { @@ -258,6 +263,7 @@ typedef struct LNK_ImageFillNode typedef struct { + LNK_Config *config; LNK_SymbolTable *symtab; LNK_SectionTable *sectab; U64 objs_count; @@ -271,6 +277,8 @@ typedef struct LNK_SectionArray image_sects; union { struct { + Arena *arena; + Rng1U64 *ranges; HashTable **defns; } gather_sects; struct { @@ -374,6 +382,7 @@ internal LNK_Input * lnk_inputer_push_lib_thin(LNK_Inputer *inputer, LNK_Config internal B32 lnk_inputer_has_items(LNK_Inputer *inputer); internal LNK_InputPtrArray lnk_inputer_flush(Arena *arena, TP_Context *tp, LNK_Inputer *inputer, LNK_IO_Flags io_flags, LNK_InputList *all_inputs, LNK_InputList *new_inputs); +internal void lnk_inputer_release_file_maps(TP_Context *tp, LNK_Inputer *inputer); // --- Link Context ------------------------------------------------------------ @@ -390,7 +399,8 @@ internal LNK_LinkResult lnk_link_image (TP_Context *tp, TP_Arena *arena, LNK_Con // --- 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 ------------------------------------------------------------- @@ -403,4 +413,3 @@ internal LNK_ImageContext lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_C internal void lnk_log_link_stats(LNK_ObjList obj_list, LNK_LibList *lib_index, LNK_SectionTable *sectab); internal void lnk_log_timers(void); - diff --git a/src/linker/lnk_config.c b/src/linker/lnk_config.c index ac624c0e..eb08ca3b 100644 --- a/src/linker/lnk_config.c +++ b/src/linker/lnk_config.c @@ -14,6 +14,7 @@ global read_only LNK_CmdSwitch g_cmd_switch_map[] = { LNK_CmdSwitch_Brepro, 0, "BREPRO", "", "No support." }, { LNK_CmdSwitch_Debug, 0, "DEBUG", "[:{FULL|NONE}]", "Controls debug info level." }, { LNK_CmdSwitch_DefaultLib, 1, "DEFAULTLIB", ":LIBNAME", "Set default library." }, + { LNK_CmdSwitch_Def, 1, "DEF", ":FILENAME", "Read exports from a module-definition file." }, { LNK_CmdSwitch_Delay, 0, "DELAY", ":{NOBIND|UNLOAD}", "Controls emission of unload and bind tables." }, { LNK_CmdSwitch_DelayLoad, 0, "DELAYLOAD", ":DLL", "Delay load DLL." }, { LNK_CmdSwitch_Dll, 0, "DLL", "", "Link to a DLL." }, @@ -24,7 +25,10 @@ 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_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_Force, 0, "FORCE", "", "Force image output despite errors." }, { LNK_CmdSwitch_FunctionPadMin, 0, "FUNCTIONPADMIN", ":#", "Minimum function byte size." }, + { LNK_CmdSwitch_Guard, 0, "GUARD", ":{CF|NO|LONGJMP|EHCONT}", "Controls Control Flow Guard metadata." }, + { LNK_CmdSwitch_GuardSym, 1, "GUARDSYM", ":SYMBOL,S", "MSVC guard symbol directive." }, { 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_Ignore, 0, "IGNORE", ":#", "Ignore a warning." }, @@ -55,6 +59,7 @@ global read_only LNK_CmdSwitch g_cmd_switch_map[] = { LNK_CmdSwitch_PdbPageSize, 0, "PDBPAGESIZE", ":#", "Page size must be power of two." }, { LNK_CmdSwitch_PdbStripped, 0, "PDBSTRIPPED", ":FILENAME", "Create a stripped PDB containing public symbols, a section map, and a list of object files." }, { LNK_CmdSwitch_Release, 1, "RELEASE", "", "Write image checksum." }, + { LNK_CmdSwitch_Section, 1, "SECTION", ":NAME,ATTRS", "Set output section attributes." }, { LNK_CmdSwitch_Stack, 1, "STACK", ":RESERVE[,COMMIT]", "Set reserve and commit size for the stack." }, { LNK_CmdSwitch_SubSystem, 1, "SUBSYSTEM", ":{CONSOLE|NATIVE|WINDOWS}[,#[.##]]", "Set subsystem for the image." }, { LNK_CmdSwitch_TsAware, 0, "TSAWARE", "[:NO]", "Image is terminal server aware." }, @@ -97,7 +102,7 @@ global read_only LNK_CmdSwitch g_cmd_switch_map[] = { LNK_CmdSwitch_Rad_ImageAltPath, 0, "RAD_IMAGEALTPATH", ":FILENAME", "Alternative name for the image" }, { LNK_CmdSwitch_Rad_WriteTempFiles, 0, "RAD_WRITE_TEMP_FILES", "[:NO]", "When speicifed linker writes image and debug info to temporary files and renames after link is done." }, { LNK_CmdSwitch_Rad_TimeStamp, 0, "RAD_TIME_STAMP", ":#", "Time stamp embeded in EXE and PDB." }, - { LNK_CmdSwitch_Rad_TypeHashAlg, 0, "RAD_TPYE_HASH_ALG", ":{BLAKE3}", "Sets hashing algorithm for type merging." }, + { LNK_CmdSwitch_Rad_DebugTypeHash, 0, "RAD_DEBUG_TYPE_HASH", ":{BLAKE3|XXHASH}", "Sets hashing algorithm for debug type merging." }, { LNK_CmdSwitch_Rad_UnresolvedSymbolLimit, 0, "RAD_UNRESOLVED_SYMBOL_LIMIT", ":#", "Limits number of unresolved symbol errors linker reports." }, { LNK_CmdSwitch_Rad_UnresolvedSymbolRefLimit, 0, "RAD_UNRESOLVED_SYMBOL_REF_LIMIT", ":#", "Limit number of unresolved symbol references linker reports." }, { LNK_CmdSwitch_Rad_Version, 0, "RAD_VERSION", "", "Print version and exit." }, @@ -106,6 +111,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_LLVM_AddrSig, 0, "LLVM_ADDRSIG", "[:NO]", "Use .llvm_addrsig to guide ICF." }, + { LNK_CmdSwitch_Help, 0, "HELP", "", "" }, { LNK_CmdSwitch_Help, 0, "?", "", "" }, }; @@ -708,6 +715,55 @@ lnk_parse_export_directive(Arena *arena, String8 directive, LNK_Obj *obj, PE_Exp return is_parsed; } +internal String8 +lnk_text_file_string_from_data(Arena *arena, String8 data) +{ + String8 result = data; + + if (data.size >= 2 && data.str[0] == 0xff && data.str[1] == 0xfe) { + // decode UTF-16LE BOM + result = str8_from_16(arena, str16((U16 *)(data.str + 2), (data.size - 2) / sizeof(U16))); + } else if (data.size >= 3 && data.str[0] == 0xef && data.str[1] == 0xbb && data.str[2] == 0xbf) { + // strip UTF-8 BOM + result = str8_skip(data, 3); + } + + return result; +} + +internal void +lnk_push_export_to_config(LNK_Config *config, LNK_Obj *obj, PE_ExportParse export_parse) +{ + // lookup existing export + String8 export_name = pe_name_from_export_parse(&export_parse); + PE_ExportParseNode *exp_n = hash_map_search_string_raw(&config->export_ht, export_name); + + if (exp_n == 0) { + // push new export + if (!export_parse.is_forwarder) { + lnk_include_symbol(config, export_parse.name, 0); + } + + exp_n = pe_export_parse_list_push(config->arena, &config->export_symbol_list, export_parse); + hash_map_push_string_raw(config->arena, &config->export_ht, export_name, exp_n); + } else { + // merge duplicate export + PE_ExportParse *extant_export = &exp_n->data; + B32 alias_conflict = extant_export->alias.size && + export_parse.alias.size && + !str8_match(extant_export->alias, export_parse.alias, 0); + B32 ordinal_conflict = extant_export->ordinal != export_parse.ordinal; + + if (alias_conflict || ordinal_conflict) { + lnk_error_obj(LNK_Error_IllExport, obj, "ambiguous symbol export %S", export_parse.name); + } + + if (!alias_conflict && !ordinal_conflict && extant_export->alias.size == 0 && export_parse.alias.size != 0) { + extant_export->alias = export_parse.alias; + } + } +} + internal B32 lnk_parse_merge_directive(String8 string, LNK_Obj *obj, LNK_MergeDirective *out) { @@ -725,6 +781,43 @@ lnk_parse_merge_directive(String8 string, LNK_Obj *obj, LNK_MergeDirective *out) return is_parse_ok; } +typedef struct LNK_SectionDirectiveAttr +{ + U8 code; + COFF_SectionFlags flag; + B32 is_mem_attr; + B32 negated_sets; +} LNK_SectionDirectiveAttr; + +global read_only LNK_SectionDirectiveAttr g_section_directive_attr_map[] = +{ + { 'D', COFF_SectionFlag_MemDiscardable, 0, 0 }, + { 'E', COFF_SectionFlag_MemExecute, 1, 0 }, + { 'K', COFF_SectionFlag_MemNotCached, 0, 1 }, + { 'P', COFF_SectionFlag_MemNotPaged, 0, 1 }, + { 'R', COFF_SectionFlag_MemRead, 1, 0 }, + { 'S', COFF_SectionFlag_MemShared, 0, 0 }, + { 'W', COFF_SectionFlag_MemWrite, 1, 0 }, +}; + +typedef struct LNK_GuardOption +{ + String8 name; + LNK_GuardFlags set_flags; + LNK_GuardFlags clear_flags; +} LNK_GuardOption; + +global read_only LNK_GuardOption g_guard_option_table[] = +{ + { str8_lit_comp("cf"), LNK_Guard_Cf, 0 }, + { str8_lit_comp("nocf"), 0, LNK_Guard_Cf }, + { str8_lit_comp("longjmp"), LNK_Guard_LongJmp, 0 }, + { str8_lit_comp("nolongjmp"), 0, LNK_Guard_LongJmp }, + { str8_lit_comp("ehcont"), LNK_Guard_EhCont, 0 }, + { str8_lit_comp("noehcont"), 0, LNK_Guard_EhCont }, + { str8_lit_comp("no"), 0, LNK_Guard_All }, +}; + internal LNK_AltNameNode * lnk_alt_name_list_push(Arena *arena, LNK_AltNameList *list, LNK_AltName v) { @@ -745,6 +838,24 @@ lnk_merge_directive_list_push(Arena *arena, LNK_MergeDirectiveList *list, LNK_Me return node; } +internal COFF_SectionFlags +lnk_apply_section_directives_to_flags(LNK_Config *config, String8 full_section_name, COFF_SectionFlags flags) +{ + String8 section_name = {0}; + String8 sort_idx = {0}; + coff_parse_section_name(full_section_name, §ion_name, &sort_idx); + + for EachNode(dir_n, LNK_SectionDirectiveNode, config->section_list.first) { + LNK_SectionDirective *dir = &dir_n->v; + if (str8_match(dir->name, section_name, 0)) { + flags &= ~dir->clear_flags; + flags |= dir->set_flags; + } + } + + return flags; +} + internal String8 lnk_get_image_name(LNK_Config *config) { @@ -1088,6 +1199,7 @@ lnk_unwrap_rsp(Arena *arena, String8List arg_list) if (file_path_exists(name)) { // read rsp from disk String8 file = lnk_read_data_from_file_path(scratch.arena, 0, name); + file = lnk_text_file_string_from_data(scratch.arena, file); // parse rsp String8List rsp_args = lnk_arg_list_parse_windows_rules(scratch.arena, file); @@ -1132,6 +1244,8 @@ lnk_apply_write_temp_files(Arena *arena, LNK_Config *config) } } +internal void lnk_apply_def_file_to_config(LNK_Config *config, String8 path, LNK_Obj *obj); + internal void lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List value_strings, LNK_Obj *obj) { @@ -1250,6 +1364,13 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List } } break; + case LNK_CmdSwitch_Def: { + String8 path = {0}; + if (lnk_cmd_switch_parse_string(obj, cmd_switch, value_strings, &path)) { + lnk_apply_def_file_to_config(config, path, obj); + } + } break; + case LNK_CmdSwitch_Delay: { if (value_strings.node_count == 0 || value_strings.node_count > 1) { lnk_error_cmd_switch_invalid_param_count(LNK_Error_Cmdl, obj, cmd_switch); @@ -1297,42 +1418,7 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List case LNK_CmdSwitch_Export: { PE_ExportParse export_parse = {0}; if (lnk_parse_export_directive_ex(config->arena, value_strings, obj, &export_parse)) { - String8 export_name = pe_name_from_export_parse(&export_parse); - PE_ExportParseNode *exp_n = hash_map_search_string_raw(&config->export_ht, export_name); - - if (exp_n == 0) { - // make sure export is defined - if (!export_parse.is_forwarder) { - lnk_include_symbol(config, export_parse.name, 0); - } - - // push new export - exp_n = pe_export_parse_list_push(config->arena, &config->export_symbol_list, export_parse); - - hash_map_push_string_raw(config->arena, &config->export_ht, export_name, exp_n); - } else { - B32 is_ambiguous = 1; - PE_ExportParse *extant_export = &exp_n->data; - - if (extant_export->alias.size && export_parse.alias.size && !str8_match(extant_export->alias, export_parse.alias, 0)) { - goto report; - } - - if (extant_export->ordinal != export_parse.ordinal) { - goto report; - } - - is_ambiguous = 0; - - if (extant_export->alias.size == 0 && export_parse.alias.size != 0) { - extant_export->alias = export_parse.alias; - } - - report:; - if (is_ambiguous) { - lnk_error_obj(LNK_Error_IllExport, obj, "ambiguous symbol export %S", export_parse.name); - } - } + lnk_push_export_to_config(config, obj, export_parse); } } break; @@ -1384,6 +1470,25 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List config->do_function_pad_min = LNK_SwitchState_Yes; } break; + case LNK_CmdSwitch_Guard: { + for EachNode(n, String8Node, value_strings.first) { + LNK_GuardOption *option = 0; + for EachElement(i, g_guard_option_table) { + if (str8_matchi(g_guard_option_table[i].name, n->string)) { + option = &g_guard_option_table[i]; + break; + } + } + + if (option != 0) { + config->guard_flags &= ~option->clear_flags; + config->guard_flags |= option->set_flags; + } else { + lnk_error_cmd_switch(LNK_Error_Cmdl, obj, cmd_switch, "unknown option \"%S\"", n->string); + } + } + } break; + case LNK_CmdSwitch_Heap: { Rng1U64 reserve_commit; reserve_commit.v[0] = config->heap_reserve; @@ -1728,6 +1833,101 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List } } break; + case LNK_CmdSwitch_Section: { + LNK_SectionDirective section_dir = {0}; + B32 is_parse_ok = 1; + + if (value_strings.node_count < 2) { + lnk_error_cmd_switch(LNK_Error_Cmdl, obj, LNK_CmdSwitch_Section, "expected section name and attributes"); + is_parse_ok = 0; + } else { + section_dir.name = value_strings.first->string; + + B32 has_attr = 0; + B32 has_mem_attr = 0; + for (String8Node *param_n = value_strings.first->next; param_n != 0; param_n = param_n->next) { + String8 param = param_n->string; + + if (str8_match_lit("ALIGN=", param, StringMatchFlag_CaseInsensitive|StringMatchFlag_RightSideSloppy)) { + String8 align_string = str8_skip(param, sizeof("ALIGN=") - 1); + U64 align = 0; + if (try_u64_from_str8_c_rules(align_string, &align)) { + COFF_SectionFlags align_flag = coff_section_flag_from_align_size(align); + if (align_flag) { + COFF_SectionFlags align_mask = (COFF_SectionFlag_AlignMask << COFF_SectionFlag_AlignShift); + section_dir.clear_flags |= align_mask; + section_dir.set_flags &= ~align_mask; + section_dir.set_flags |= align_flag; + section_dir.clear_flags &= ~align_flag; + } else { + lnk_error_cmd_switch(LNK_Error_Cmdl, obj, LNK_CmdSwitch_Section, "invalid alignment \"%S\"", align_string); + is_parse_ok = 0; + } + } else { + lnk_error_cmd_switch(LNK_Error_Cmdl, obj, LNK_CmdSwitch_Section, "unable to parse alignment \"%S\"", align_string); + is_parse_ok = 0; + } + continue; + } + + B32 negate = 0; + for EachIndex(i, param.size) { + U8 c = upper_from_char(param.str[i]); + if (c == '!') { + negate = 1; + continue; + } + + LNK_SectionDirectiveAttr *attr = 0; + for EachElement(attr_idx, g_section_directive_attr_map) { + if (g_section_directive_attr_map[attr_idx].code == c) { + attr = &g_section_directive_attr_map[attr_idx]; + break; + } + } + + if (attr == 0) { + lnk_error_cmd_switch(LNK_Error_Cmdl, obj, LNK_CmdSwitch_Section, "unknown section attribute '%c' in \"%S\"", c, param); + is_parse_ok = 0; + } else { + has_attr = 1; + has_mem_attr = has_mem_attr || attr->is_mem_attr; + + COFF_SectionFlags flags = (negate == attr->negated_sets) ? attr->flag : 0; + section_dir.clear_flags |= attr->flag; + section_dir.set_flags &= ~attr->flag; + section_dir.set_flags |= flags; + section_dir.clear_flags &= ~flags; + } + + negate = 0; + } + + if (negate) { + lnk_error_cmd_switch(LNK_Error_Cmdl, obj, LNK_CmdSwitch_Section, "dangling '!' in \"%S\"", param); + is_parse_ok = 0; + } + } + + if (has_attr) { + section_dir.clear_flags |= COFF_SectionFlag_MemDiscardable|COFF_SectionFlag_MemNotCached|COFF_SectionFlag_MemNotPaged|COFF_SectionFlag_MemShared; + if (has_mem_attr) { + section_dir.clear_flags |= COFF_SectionFlag_MemExecute|COFF_SectionFlag_MemRead|COFF_SectionFlag_MemWrite; + } + section_dir.clear_flags &= ~section_dir.set_flags; + } + } + + if (is_parse_ok) { + section_dir.name = push_str8_copy(config->arena, section_dir.name); + + LNK_SectionDirectiveNode *node = push_array_no_zero(config->arena, LNK_SectionDirectiveNode, 1); + node->v = section_dir; + SLLQueuePush(config->section_list.first, config->section_list.last, node); + config->section_list.count += 1; + } + } break; + case LNK_CmdSwitch_Stack: { Rng1U64 reserve_commit; reserve_commit.v[0] = config->stack_reserve; @@ -2125,11 +2325,13 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List lnk_cmd_switch_parse_u32(obj, cmd_switch, value_strings, &config->time_stamp, 0); } break; - case LNK_CmdSwitch_Rad_TypeHashAlg: { + case LNK_CmdSwitch_Rad_DebugTypeHash: { String8 alg = {0}; if (lnk_cmd_switch_parse_string(obj, cmd_switch, value_strings, &alg)) { if (str8_match(alg, str8_lit("BLAKE3"), StringMatchFlag_CaseInsensitive)) { - config->type_hash_alg = LLVM_GHashAlg_BLAKE3; + config->debug_types_hash = LNK_HashKind_BLAKE3; + } else if (str8_match(alg, str8_lit("XXHASH"), StringMatchFlag_CaseInsensitive)) { + config->debug_types_hash = LNK_HashKind_XXHash; } else { lnk_error_cmd_switch(LNK_Error_Cmdl, obj, cmd_switch, "unknown hash alg: %S", alg); } @@ -2179,6 +2381,335 @@ 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"); } } break; + + case LNK_CmdSwitch_LLVM_AddrSig: { + lnk_cmd_switch_parse_flag(obj, cmd_switch, value_strings, &config->llvm_addrsig); + } break; + } + + scratch_end(scratch); +} + +typedef enum LNK_DefFileStmt +{ + LNK_DefFileStmt_Null, + LNK_DefFileStmt_Description, + LNK_DefFileStmt_Exports, + LNK_DefFileStmt_HeapSize, + LNK_DefFileStmt_Imports, + LNK_DefFileStmt_Library, + LNK_DefFileStmt_Name, + LNK_DefFileStmt_Sections, + LNK_DefFileStmt_Segments, + LNK_DefFileStmt_StackSize, + LNK_DefFileStmt_Stub, + LNK_DefFileStmt_Version, +} LNK_DefFileStmt; + +global read_only struct +{ + String8 name; + LNK_DefFileStmt stmt; +} g_def_file_stmt_map[] = +{ + { str8_lit_comp("DESCRIPTION"), LNK_DefFileStmt_Description }, + { str8_lit_comp("EXPORTS"), LNK_DefFileStmt_Exports }, + { str8_lit_comp("HEAPSIZE"), LNK_DefFileStmt_HeapSize }, + { str8_lit_comp("IMPORTS"), LNK_DefFileStmt_Imports }, + { str8_lit_comp("LIBRARY"), LNK_DefFileStmt_Library }, + { str8_lit_comp("NAME"), LNK_DefFileStmt_Name }, + { str8_lit_comp("SECTIONS"), LNK_DefFileStmt_Sections }, + { str8_lit_comp("SEGMENTS"), LNK_DefFileStmt_Segments }, + { str8_lit_comp("STACKSIZE"), LNK_DefFileStmt_StackSize }, + { str8_lit_comp("STUB"), LNK_DefFileStmt_Stub }, + { str8_lit_comp("VERSION"), LNK_DefFileStmt_Version }, +}; + +typedef struct LNK_DefFileLineNode LNK_DefFileLineNode; +struct LNK_DefFileLineNode +{ + LNK_DefFileStmt stmt; + String8 line; + B32 is_keyword; + LNK_DefFileLineNode *next; +}; + +typedef struct LNK_DefFileLineList LNK_DefFileLineList; +struct LNK_DefFileLineList +{ + U64 count; + LNK_DefFileLineNode *first; + LNK_DefFileLineNode *last; +}; + +internal String8List +lnk_def_file_tokenize(Arena *arena, String8 line) +{ + // tokenize with windows quoting rules + line = push_str8_copy(arena, line); + for EachIndex(i, line.size) { + if (line.str[i] == '\t') { + line.str[i] = ' '; + } + } + + String8List tokens = lnk_arg_list_parse_windows_rules(arena, line); + + String8List result = {0}; + for (String8Node *token_n = tokens.first; token_n != 0; token_n = token_n->next) { + String8 token = token_n->string; + String8Node *next_n = token_n->next; + if (token.size == 1 && token.str[0] == '@' && next_n != 0) { + str8_list_push(arena, &result, str8_cat(arena, token, next_n->string)); + token_n = next_n; + } else { + str8_list_push(arena, &result, token); + } + } + return result; +} + +internal void +lnk_apply_def_file_to_config(LNK_Config *config, String8 path, LNK_Obj *obj) +{ + Temp scratch = scratch_begin(&config->arena, 1); + + // load DEF file + String8 raw_file = lnk_read_data_from_file_path(scratch.arena, config->io_flags, path); + String8 file = lnk_text_file_string_from_data(scratch.arena, raw_file); + + // parse & collect normalized DEF lines + LNK_DefFileLineList def_lines = {0}; + LNK_DefFileStmt active_stmt = LNK_DefFileStmt_Null; + String8 rest = file; + while (rest.size != 0) { + String8 line = str8_chop_line(&rest); + + // strip comments outside quotes + B32 in_quote = 0; + for EachIndex(i, line.size) { + U8 c = line.str[i]; + if (in_quote && c == '\\') { + i += 1; + continue; + } + if (c == '"') { + in_quote = !in_quote; + } else if (c == ';' && !in_quote) { + line = str8_prefix(line, i); + break; + } + } + + line = str8_skip_chop_whitespace(line); + if (line.size == 0) { continue; } + + // parse statement keyword + String8 tail = line; + LNK_DefFileStmt stmt = LNK_DefFileStmt_Null; + B32 is_keyword = 0; + if (line.size != 0 && line.str[0] != '"') { + U64 keyword_opl = 0; + for (; keyword_opl < line.size; keyword_opl += 1) { + U8 c = line.str[keyword_opl]; + if (char_is_space(c) || c == ':' || c == '=') { + break; + } + } + + String8 keyword = str8_prefix(line, keyword_opl); + for EachElement(i, g_def_file_stmt_map) { + if (str8_matchi(g_def_file_stmt_map[i].name, keyword)) { + stmt = g_def_file_stmt_map[i].stmt; + break; + } + } + + if (stmt != LNK_DefFileStmt_Null) { + U64 tail_pos = keyword_opl; + while (tail_pos < line.size && char_is_space(line.str[tail_pos])) { + tail_pos += 1; + } + if (tail_pos < line.size && (line.str[tail_pos] == ':' || line.str[tail_pos] == '=')) { + tail_pos += 1; + } + tail = str8_skip_chop_whitespace(str8_skip(line, tail_pos)); + } + } + + if (stmt != LNK_DefFileStmt_Null) { + B32 is_multiline = stmt == LNK_DefFileStmt_Exports || + stmt == LNK_DefFileStmt_Sections || + stmt == LNK_DefFileStmt_Imports || + stmt == LNK_DefFileStmt_Segments; + active_stmt = is_multiline ? stmt : LNK_DefFileStmt_Null; + is_keyword = 1; + line = tail; + } else { + stmt = active_stmt; + } + + LNK_DefFileLineNode *node = push_array(scratch.arena, LNK_DefFileLineNode, 1); + node->stmt = stmt; + node->line = line; + node->is_keyword = is_keyword; + SLLQueuePush(def_lines.first, def_lines.last, node); + def_lines.count += 1; + } + + // apply collected DEF lines to config + for EachNode(def_line, LNK_DefFileLineNode, def_lines.first) { + String8 line = def_line->line; + LNK_DefFileStmt stmt = def_line->stmt; + + switch (stmt) { + case LNK_DefFileStmt_Description: { + lnk_error_cmd_switch(LNK_Warning_Cmdl, obj, LNK_CmdSwitch_Def, "DESCRIPTION is ignored in DEF files"); + } break; + + case LNK_DefFileStmt_Imports: { + if (def_line->is_keyword) { + lnk_error_cmd_switch(LNK_Warning_Cmdl, obj, LNK_CmdSwitch_Def, "IMPORTS is ignored in DEF files"); + } else { + lnk_log(LNK_Log_Debug, "%S: unsupported old-style import specifications", path); + } + } break; + + case LNK_DefFileStmt_Stub: { + lnk_error_cmd_switch(LNK_Warning_Cmdl, obj, LNK_CmdSwitch_Def, "STUB is ignored in DEF files"); + } break; + + case LNK_DefFileStmt_Segments: { + lnk_error_cmd_switch(LNK_Warning_Cmdl, obj, LNK_CmdSwitch_Def, "SEGMENTS is ignored in DEF files"); + } break; + + case LNK_DefFileStmt_Exports: { + String8List tokens = lnk_def_file_tokenize(scratch.arena, line); + if (tokens.node_count != 0) { + PE_ExportParse export_parse = {0}; + if (lnk_parse_export_directive_ex(config->arena, tokens, obj, &export_parse)) { + export_parse.obj_path = path; + lnk_push_export_to_config(config, obj, export_parse); + } + } + } break; + + case LNK_DefFileStmt_Library: + case LNK_DefFileStmt_Name: { + String8List tokens = lnk_def_file_tokenize(scratch.arena, line); + String8 name = {0}; + + for EachNode(token_n, String8Node, tokens.first) { + String8 token = token_n->string; + String8 base_value = {0}; + B32 has_base = 0; + + U64 sep_pos = str8_find_needle(token, 0, str8_lit("="), 0); + if (sep_pos < token.size) { + String8 key = str8_prefix(token, sep_pos); + if (str8_matchi(key, str8_lit("BASE"))) { + base_value = str8_skip(token, sep_pos + 1); + has_base = 1; + } + } else if (str8_matchi(token, str8_lit("BASE")) || + str8_match_lit("BASE:", token, StringMatchFlag_CaseInsensitive|StringMatchFlag_RightSideSloppy)) { + lnk_error_cmd_switch(LNK_Error_Cmdl, obj, LNK_CmdSwitch_Def, "syntax error in DEF file BASE specification"); + } + + if (has_base) { + String8List values = str8_split_by_string_chars(scratch.arena, base_value, str8_lit(","), 0); + lnk_apply_cmd_option_to_config(config, str8_lit("base"), values, obj); + } else if (name.size == 0) { + name = token; + } else { + lnk_error_cmd_switch(LNK_Warning_Cmdl, obj, LNK_CmdSwitch_Def, "ignoring unexpected DEF token \"%S\"", token); + } + } + + if (stmt == LNK_DefFileStmt_Library) { + config->file_characteristics |= PE_ImageFileCharacteristic_FILE_DLL; + } + + if (name.size != 0) { + String8 image_name = name; + if (stmt == LNK_DefFileStmt_Library) { + // set DLL import name + String8 file_name = str8_skip_last_slash(name); + B32 has_ext = (str8_skip_last_dot(file_name).size != file_name.size); + if (!has_ext) { + image_name = path_replace_file_extension(scratch.arena, name, str8_lit("dll")); + } + if (config->image_alt_path.size == 0) { + config->image_alt_path = push_str8_copy(config->arena, image_name); + } + } + + if (config->out_path.size == 0) { + config->out_path = push_str8_copy(config->arena, image_name); + } + } + } break; + + case LNK_DefFileStmt_Sections: { + String8List tokens = lnk_def_file_tokenize(scratch.arena, line); + if (tokens.node_count != 0) { + B32 is_parse_ok = 1; + String8List section_values = {0}; + str8_list_push(scratch.arena, §ion_values, tokens.first->string); + + for (String8Node *token_n = tokens.first->next; token_n != 0; token_n = token_n->next) { + String8 token = token_n->string; + if (str8_matchi(token, str8_lit("CLASS"))) { + if (token_n->next != 0) { + token_n = token_n->next; + } + continue; + } + + String8 attr = {0}; + if (str8_matchi(token, str8_lit("EXECUTE"))) { + attr = str8_lit("E"); + } else if (str8_matchi(token, str8_lit("READ"))) { + attr = str8_lit("R"); + } else if (str8_matchi(token, str8_lit("SHARED"))) { + attr = str8_lit("S"); + } else if (str8_matchi(token, str8_lit("WRITE"))) { + attr = str8_lit("W"); + } else { + lnk_error_cmd_switch(LNK_Error_Cmdl, obj, LNK_CmdSwitch_Def, "unknown DEF SECTIONS specifier \"%S\"", token); + is_parse_ok = 0; + } + + if (attr.size != 0) { + str8_list_push(scratch.arena, §ion_values, attr); + } + } + + if (is_parse_ok) { + lnk_apply_cmd_option_to_config(config, str8_lit("section"), section_values, obj); + } + } + } break; + + case LNK_DefFileStmt_StackSize: { + String8List values = str8_split_by_string_chars(scratch.arena, line, str8_lit(" \t,"), 0); + lnk_apply_cmd_option_to_config(config, str8_lit("stack"), values, obj); + } break; + + case LNK_DefFileStmt_Version: { + String8List values = str8_split_by_string_chars(scratch.arena, line, str8_lit(" \t"), 0); + lnk_apply_cmd_option_to_config(config, str8_lit("version"), values, obj); + } break; + + case LNK_DefFileStmt_HeapSize: { + String8List values = str8_split_by_string_chars(scratch.arena, line, str8_lit(" \t,"), 0); + lnk_apply_cmd_option_to_config(config, str8_lit("heap"), values, obj); + } break; + + default: { + lnk_error_cmd_switch(LNK_Warning_Cmdl, obj, LNK_CmdSwitch_Def, "ignoring unrecognized DEF statement \"%S\"", line); + } break; + } } scratch_end(scratch); @@ -2208,6 +2739,7 @@ lnk_config_init(LNK_CmdLine cmd_line) config->arena = arena; config->raw_cmd_line = str8_list_copy(arena, &cmd_line.raw_cmd_line); config->work_dir = get_current_path(arena); + config->force = lnk_cmd_line_has_switch(cmd_line, LNK_CmdSwitch_Force); // apply command line switches for EachNode(cmd, LNK_CmdOption, cmd_line.first_option) { @@ -2333,13 +2865,15 @@ lnk_config_init(LNK_CmdLine cmd_line) config->dll_characteristics |= PE_DllCharacteristic_DYNAMIC_BASE; } - // set flag for /guard + // TODO: Set GUARD_CF only after emitting the Guard CF function tables. + // Marking the image without valid load-config tables makes CFG-enabled + // executables crash on indirect calls. if (config->guard_flags != LNK_Guard_None) { - config->dll_characteristics |= PE_DllCharacteristic_GUARD_CF; + // config->dll_characteristics |= PE_DllCharacteristic_GUARD_CF; } // handle empty /OUT - if (!lnk_cmd_line_has_switch(cmd_line, LNK_CmdSwitch_Out)) { + if (!config->out_path.size) { String8 name = str8_list_first(&config->input_list[LNK_Input_Obj]); String8 ext = (config->file_characteristics & PE_ImageFileCharacteristic_FILE_DLL) ? str8_lit("dll") : str8_lit("exe"); config->out_path = path_replace_file_extension(scratch.arena, name, ext); @@ -2422,4 +2956,3 @@ lnk_config_init(LNK_CmdLine cmd_line) ProfEnd(); return config; } - diff --git a/src/linker/lnk_config.h b/src/linker/lnk_config.h index a781d664..9fd5057f 100644 --- a/src/linker/lnk_config.h +++ b/src/linker/lnk_config.h @@ -47,6 +47,7 @@ typedef enum LNK_CmdSwitch_Brepro, LNK_CmdSwitch_Debug, LNK_CmdSwitch_DefaultLib, + LNK_CmdSwitch_Def, LNK_CmdSwitch_Delay, LNK_CmdSwitch_DelayLoad, LNK_CmdSwitch_DisallowLib, @@ -59,6 +60,8 @@ typedef enum LNK_CmdSwitch_Fixed, LNK_CmdSwitch_Force, LNK_CmdSwitch_FunctionPadMin, + LNK_CmdSwitch_Guard, + LNK_CmdSwitch_GuardSym, LNK_CmdSwitch_Heap, LNK_CmdSwitch_HighEntropyVa, LNK_CmdSwitch_Ignore, @@ -88,6 +91,7 @@ typedef enum LNK_CmdSwitch_PdbPageSize, LNK_CmdSwitch_PdbStripped, LNK_CmdSwitch_Release, + LNK_CmdSwitch_Section, LNK_CmdSwitch_Stack, LNK_CmdSwitch_SubSystem, LNK_CmdSwitch_Time, @@ -132,7 +136,7 @@ typedef enum LNK_CmdSwitch_Rad_SharedThreadPoolMaxWorkers, LNK_CmdSwitch_Rad_SortImports, LNK_CmdSwitch_Rad_TimeStamp, - LNK_CmdSwitch_Rad_TypeHashAlg, + LNK_CmdSwitch_Rad_DebugTypeHash, LNK_CmdSwitch_Rad_UnresolvedSymbolLimit, LNK_CmdSwitch_Rad_UnresolvedSymbolRefLimit, LNK_CmdSwitch_Rad_Version, @@ -143,6 +147,8 @@ typedef enum LNK_CmdSwitch_RadTypeServer, LNK_CmdSwitch_RadTypeServer_MatchObj, + LNK_CmdSwitch_LLVM_AddrSig, + LNK_CmdSwitch_Help, LNK_CmdSwitch_Count @@ -265,6 +271,26 @@ typedef struct LNK_MergeDirectiveList LNK_MergeDirectiveNode *last; } LNK_MergeDirectiveList; +typedef struct LNK_SectionDirective +{ + String8 name; + COFF_SectionFlags set_flags; + COFF_SectionFlags clear_flags; +} LNK_SectionDirective; + +typedef struct LNK_SectionDirectiveNode +{ + struct LNK_SectionDirectiveNode *next; + LNK_SectionDirective v; +} LNK_SectionDirectiveNode; + +typedef struct LNK_SectionDirectiveList +{ + U64 count; + LNK_SectionDirectiveNode *first; + LNK_SectionDirectiveNode *last; +} LNK_SectionDirectiveList; + typedef enum { LNK_DebugInfoGuid_Null, @@ -279,7 +305,6 @@ typedef enum LNK_TypeNameHashMode_Full, } LNK_TypeNameHashMode; - typedef struct LNK_Config { Arena *arena; @@ -316,6 +341,7 @@ typedef struct LNK_Config U64 function_pad_min; U64 *manifest_resource_id; B32 no_default_libs; + B32 force; LNK_SwitchState infer_asan_libs; Version link_ver; Version os_ver; @@ -360,6 +386,7 @@ typedef struct LNK_Config LNK_IncludeSymbolList include_symbol_list; LNK_AltNameList alt_name_list; LNK_MergeDirectiveList merge_list; + LNK_SectionDirectiveList section_list; U64 data_dir_count; B32 build_imp_lib; B32 build_exp; @@ -383,10 +410,11 @@ typedef struct LNK_Config U64 unresolved_symbol_limit; U64 unresolved_symbol_ref_limit; LNK_SwitchState map_lines_for_unresolved_symbols; - LLVM_GHashAlg type_hash_alg; + LNK_HashKind debug_types_hash; String8 type_server_name; LNK_SwitchState type_server; LNK_SwitchState sort_imports; + LNK_SwitchState llvm_addrsig; } LNK_Config; // --- MSVC Error Codes -------------------------------------------------------- @@ -571,6 +599,8 @@ internal B32 lnk_is_thread_pool_shared(LNK_Config *config); internal B32 lnk_is_section_removed (LNK_Config *config, String8 section_name); internal B32 lnk_is_dll_delay_load (LNK_Config *config, String8 dll_name); +internal COFF_SectionFlags lnk_apply_section_directives_to_flags(LNK_Config *config, String8 full_section_name, COFF_SectionFlags flags); + internal String8 lnk_get_lib_name (String8 path); internal void lnk_push_disallow_lib(LNK_Config *config, String8 path); internal B32 lnk_is_lib_disallowed(LNK_Config *config, String8 path); @@ -585,4 +615,3 @@ internal void lnk_apply_cmd_option_to_config(LNK_Config *config, String8 name, S internal void lnk_config_pushf(LNK_Config *config, char *fmt, ...); internal LNK_Config * lnk_config_init(LNK_CmdLine cmd_line); - diff --git a/src/linker/lnk_debug_info.c b/src/linker/lnk_debug_info.c index fe125133..e217d057 100644 --- a/src/linker/lnk_debug_info.c +++ b/src/linker/lnk_debug_info.c @@ -57,6 +57,18 @@ THREAD_POOL_TASK_FUNC(lnk_parse_debug_s_task) } } +internal int +lnk_symbol_input_task_is_before(void *raw_a, void *raw_b) +{ + LNK_SymbolInputTask *a = raw_a, *b = raw_b; + + if (a->weight == b->weight) { + return a->input_range.min < b->input_range.min; + } + + return a->weight > b->weight; +} + internal THREAD_POOL_TASK_FUNC(lnk_parse_debug_h_task) { @@ -95,11 +107,11 @@ THREAD_POOL_TASK_FUNC(lnk_parse_debug_h_task) } // validate hashing algorithm - if (ghash.hash_alg != task->config->type_hash_alg) { + if (lnk_hash_kind_from_llvm(ghash.hash_alg) != task->config->debug_types_hash) { lnk_error_obj(LNK_Warning_GHash, task->obj_arr[obj_idx], - "mismatched .debug$H hash algorithm: got %S, expected %S", + "mismatched .debug$H hash algorithm: got %S, expected %S; types will be rehashed", llvm_string_from_ghash_alg(ghash.hash_alg), - llvm_string_from_ghash_alg(task->config->type_hash_alg)); + lnk_string_hash_kind(task->config->debug_types_hash)); goto exit; } @@ -308,42 +320,45 @@ lnk_string_list_from_rrt(Arena *arena, LNK_RRT *rrt) str8_list_push(arena, &rrt_data, g_rrt_magic); // (2) version - str8_list_push(arena, &rrt_data, str8_struct(&g_rrt_version)); + str8_list_push(arena, &rrt_data, str8_struct(push_u64(arena, g_rrt_version))); - // (3) type data ranges + // (3) debug types hash + str8_list_push(arena, &rrt_data, str8_struct(&rrt->debug_types_hash)); + + // (4) type data ranges str8_list_push(arena, &rrt_data, str8_array_fixed(rrt->type_data_ranges)); - // (4) type data + // (5) type data str8_list_push(arena, &rrt_data, rrt->type_data_raw); - // (5) type index ranges + // (6) type index ranges str8_list_push(arena, &rrt_data, str8_array_fixed(rrt->ti_ranges)); - // (6) type hashes size + // (7) type hashes size U64 total_hash_count = 0; for EachIndex(i, CV_TypeIndexSource_COUNT) { total_hash_count += dim_1u64(rrt->ti_ranges[i]); } U64 type_hashes_size = sizeof(**rrt->type_hashes_unpacked) * total_hash_count; str8_list_push(arena, &rrt_data, str8_struct(push_u64(arena, type_hashes_size))); - // (7) type hashes + // (8) type hashes for EachIndex(i, CV_TypeIndexSource_COUNT) { U64 type_count = dim_1u64(rrt->ti_ranges[i]); str8_list_push(arena, &rrt_data, str8_array(rrt->type_hashes_unpacked[i], type_count)); } - // (8) object count + // (9) object count str8_list_push(arena, &rrt_data, str8_struct(&rrt->obj_count)); - // (9) per object type index ranges + // (10) per object type index ranges str8_list_push(arena, &rrt_data, str8_array(rrt->obj_ti_ranges, rrt->obj_count)); - // (10) per object time stamps + // (11) per object time stamps str8_list_push(arena, &rrt_data, str8_array(rrt->obj_time_stamps, rrt->obj_count)); - // (11) per object leaf counts + // (12) per object leaf counts str8_list_push(arena, &rrt_data, str8_array(rrt->obj_leaf_counts, rrt->obj_count)); - // (12) per object file reverse lookup table for type indices + // (13) per object file reverse lookup table for type indices for EachIndex(obj_idx, rrt->obj_count) { CV_TypeIndex *obj_ti_map = rrt->obj_ti_maps[obj_idx]; U64 obj_ti_count = rrt->obj_leaf_counts[obj_idx]; @@ -351,16 +366,16 @@ lnk_string_list_from_rrt(Arena *arena, LNK_RRT *rrt) str8_list_push(arena, &rrt_data, str8_array(obj_ti_map, obj_ti_count)); } - // (13) object file paths size + // (14) object file paths size str8_list_push(arena, &rrt_data, str8_struct(push_u64(arena, obj_paths.size))); - // (14) object file paths block + // (15) object file paths block str8_list_push(arena, &rrt_data, obj_paths); - // (15) PCH type index ranges + // (16) PCH type index ranges str8_list_push(arena, &rrt_data, str8_array(rrt->obj_pch_ti_ranges, rrt->obj_count)); - // (16) PCH object indices + // (17) PCH object indices str8_list_push(arena, &rrt_data, str8_array(rrt->obj_pch_indices, rrt->obj_count)); ProfEnd(); @@ -392,11 +407,27 @@ lnk_rrt_from_string(Arena *arena, String8 rrt_data, String8 path, LNK_RRT *rrt_o // match version if (version != g_rrt_version) { - lnk_error(LNK_Error_IllData, "ERROR: %S: RRT version mismatch, got %llu, expected %llu", path, version, g_rrt_version); + lnk_error(LNK_Error_IllData, "ERROR: %S: RRT version mismatch, got %llu, expected 2 or %llu", path, version, g_rrt_version); goto exit; } - // (3) type data ranges + // (3) debug types hash + LNK_HashKind debug_types_hash = LNK_HashKind_BLAKE3; + if (version == g_rrt_version) { + U64 debug_types_hash_size = str8_deserial_read_struct(rrt_data, cursor, &debug_types_hash); + if (debug_types_hash_size != sizeof(debug_types_hash)) { + lnk_error(LNK_Error_IllData, "ERROR: %S: RRT file does not contain enough bytes to read the debug types hash", path); + goto exit; + } + cursor += debug_types_hash_size; + + if (debug_types_hash != LNK_HashKind_BLAKE3 && debug_types_hash != LNK_HashKind_XXHash) { + lnk_error(LNK_Error_IllData, "ERROR: %S: RRT file has invalid debug types hash %u", path, debug_types_hash); + goto exit; + } + } + + // (4) type data ranges Rng1U64 type_data_ranges[CV_TypeIndexSource_COUNT] = {0}; U64 type_data_ranges_size = str8_deserial_read_array(rrt_data, cursor, type_data_ranges, ArrayCount(type_data_ranges)); if (type_data_ranges_size != sizeof(type_data_ranges)) { @@ -409,7 +440,7 @@ lnk_rrt_from_string(Arena *arena, String8 rrt_data, String8 path, LNK_RRT *rrt_o U64 total_type_data_size = 0; for EachElement(i, type_data_ranges) total_type_data_size += dim_1u64(type_data_ranges[i]); - // (4) type data + // (5) type data String8 type_data_raw = {0}; U64 type_data_raw_size = str8_deserial_read_block(rrt_data, cursor, total_type_data_size, &type_data_raw); if (type_data_raw_size != total_type_data_size) { @@ -418,7 +449,7 @@ lnk_rrt_from_string(Arena *arena, String8 rrt_data, String8 path, LNK_RRT *rrt_o } cursor += type_data_raw_size; - // (5) type index ranges + // (6) type index ranges Rng1U64 ti_ranges[CV_TypeIndexSource_COUNT] = {0}; U64 ti_ranges_size = str8_deserial_read_array(rrt_data, cursor, ti_ranges, ArrayCount(ti_ranges)); if (ti_ranges_size != sizeof(ti_ranges)) { @@ -427,7 +458,7 @@ lnk_rrt_from_string(Arena *arena, String8 rrt_data, String8 path, LNK_RRT *rrt_o } cursor += ti_ranges_size; - // (6) type hashes size + // (7) type hashes size U64 type_hashes_size = 0; U64 type_hashes_size_size = str8_deserial_read_struct(rrt_data, cursor, &type_hashes_size); if (type_hashes_size_size != sizeof(type_hashes_size)) { @@ -447,7 +478,7 @@ lnk_rrt_from_string(Arena *arena, String8 rrt_data, String8 path, LNK_RRT *rrt_o } } - // (7) type hashes + // (8) type hashes String8 type_hashes = {0}; U64 type_hashes_read_size = str8_deserial_read_block(rrt_data, cursor, type_hashes_size, &type_hashes); if (type_hashes_read_size != type_hashes_size) { @@ -467,7 +498,7 @@ lnk_rrt_from_string(Arena *arena, String8 rrt_data, String8 path, LNK_RRT *rrt_o } } - // (8) object count + // (9) object count U64 obj_count = 0; U64 obj_count_size = str8_deserial_read_struct(rrt_data, cursor, &obj_count); if (obj_count_size == 0) { @@ -476,7 +507,7 @@ lnk_rrt_from_string(Arena *arena, String8 rrt_data, String8 path, LNK_RRT *rrt_o } cursor += obj_count_size; - // (9) per object type index ranges + // (10) per object type index ranges Rng1U64 *obj_ti_ranges = str8_deserial_get_raw_ptr(rrt_data, cursor, sizeof(*obj_ti_ranges) * obj_count); if (obj_ti_ranges == 0) { lnk_error(LNK_Error_IllData, "ERROR: %S: RRT file is missing the object type index ranges", path); @@ -484,7 +515,7 @@ lnk_rrt_from_string(Arena *arena, String8 rrt_data, String8 path, LNK_RRT *rrt_o } cursor += sizeof(*obj_ti_ranges) * obj_count; - // (10) last observed time stamp of the object files + // (11) last observed time stamp of the object files U64 *obj_time_stamps = str8_deserial_get_raw_ptr(rrt_data, cursor, sizeof(*obj_time_stamps) * obj_count); if (obj_time_stamps == 0) { lnk_error(LNK_Error_IllData, "ERROR: %S: RRT file is missing the object timestamps", path); @@ -492,7 +523,7 @@ lnk_rrt_from_string(Arena *arena, String8 rrt_data, String8 path, LNK_RRT *rrt_o } cursor += sizeof(*obj_time_stamps) * obj_count; - // (11) per object leaf counts + // (12) per object leaf counts U64 *obj_leaf_counts = str8_deserial_get_raw_ptr(rrt_data, cursor, sizeof(*obj_leaf_counts) * obj_count); if (obj_leaf_counts == 0) { lnk_error(LNK_Error_IllData, "ERROR: %S: RRT file is missing the object leaf counts", path); @@ -500,7 +531,7 @@ lnk_rrt_from_string(Arena *arena, String8 rrt_data, String8 path, LNK_RRT *rrt_o } cursor += sizeof(*obj_leaf_counts) * obj_count; - // (12) per object file reverse lookup table for type indices + // (13) per object file reverse lookup table for type indices CV_TypeIndex **obj_ti_maps = push_array(arena, CV_TypeIndex *, obj_count); for EachIndex(obj_idx, obj_count) { U64 obj_ti_count = obj_leaf_counts[obj_idx]; @@ -512,7 +543,7 @@ lnk_rrt_from_string(Arena *arena, String8 rrt_data, String8 path, LNK_RRT *rrt_o cursor += obj_ti_count * sizeof(*obj_ti_maps[obj_idx]); } - // (13) object file paths size + // (14) object file paths size U64 obj_file_paths_size = 0; U64 obj_file_paths_size_size = str8_deserial_read_struct(rrt_data, cursor, &obj_file_paths_size); if (obj_file_paths_size_size == 0) { @@ -521,7 +552,7 @@ lnk_rrt_from_string(Arena *arena, String8 rrt_data, String8 path, LNK_RRT *rrt_o } cursor += obj_file_paths_size_size; - // (14) object file paths block + // (15) object file paths block String8 obj_file_paths_block = {0}; U64 obj_file_paths_block_size = str8_deserial_read_block(rrt_data, cursor, obj_file_paths_size, &obj_file_paths_block); if (obj_file_paths_block_size != obj_file_paths_size) { @@ -530,7 +561,7 @@ lnk_rrt_from_string(Arena *arena, String8 rrt_data, String8 path, LNK_RRT *rrt_o } cursor += obj_file_paths_block_size; - // (15) PCH type index ranges + // (16) PCH type index ranges Rng1U64 *obj_pch_ti_ranges = str8_deserial_get_raw_ptr(rrt_data, cursor, obj_count * sizeof(*obj_pch_ti_ranges)); if (obj_pch_ti_ranges == 0) { lnk_error(LNK_Error_IllData, "ERROR: %S: RRT file is too small to read object PCH type index ranges"); @@ -572,6 +603,7 @@ lnk_rrt_from_string(Arena *arena, String8 rrt_data, String8 path, LNK_RRT *rrt_o // fill out result if (rrt_out) { rrt_out->path = path; + rrt_out->debug_types_hash = debug_types_hash; rrt_out->type_data_raw = type_data_raw; rrt_out->type_hashes = type_hashes; MemoryCopyArray(rrt_out->type_data_ranges, type_data_ranges); @@ -925,7 +957,7 @@ lnk_make_code_view_input(TP_Context *tp, TP_Arena *tp_arena, LNK_Config *config, // wire RRT hashes to type servers .debug$H for EachIndex(ts_idx, ts_arr.count) { LNK_TypeServer *ts = &ts_arr.v[ts_idx]; - if (ts->rrt) { + if (ts->rrt && ts->rrt->debug_types_hash == config->debug_types_hash) { U64 ts_obj_idx = input.ts_obj_range.min + ts_idx; CV_DebugT *debug_t = &input.debug_t_arr[ts_obj_idx]; CV_DebugH *debug_h = &input.debug_h_arr[ts_obj_idx]; @@ -1096,6 +1128,7 @@ lnk_make_code_view_input(TP_Context *tp, TP_Arena *tp_arena, LNK_Config *config, } ProfBegin("Make Ranges"); + U64 total_input_size = 0; for EachIndex(i, input.symbol_input_count) { total_input_size += input.symbol_inputs[i].raw_symbols.size; } @@ -1112,7 +1145,35 @@ lnk_make_code_view_input(TP_Context *tp, TP_Arena *tp_arena, LNK_Config *config, } input.symbol_input_ranges[i] = r1u64(begin, cursor); } + ProfEnd(); + + if (input.symbol_input_count) { + ProfBegin("Balance Symbol Inputs"); + + U64 task_cap = Min(input.symbol_input_count, tp->worker_count * 16); + U64 task_weight = Max(1, CeilIntegerDiv(total_input_size, task_cap)); + + input.symbol_patch_task = push_array_no_zero(tp_arena->v[0], LNK_SymbolInputTask, task_cap); + + cursor = 0; + while (cursor < input.symbol_input_count) { + U64 begin = cursor; + U64 weight = 0; + do { + weight += input.symbol_inputs[cursor++].raw_symbols.size; + } while (cursor < input.symbol_input_count && weight < task_weight); + + Assert(input.symbol_patch_task_count < task_cap); + LNK_SymbolInputTask *task = &input.symbol_patch_task[input.symbol_patch_task_count++]; + task->input_range = r1u64(begin, cursor); + task->weight = weight; + } + + radsort(input.symbol_patch_task, input.symbol_patch_task_count, lnk_symbol_input_task_is_before); + + ProfEnd(); + } } ProfEnd(); @@ -1207,7 +1268,8 @@ lnk_hash_cv_leaf(LNK_CodeViewInput *input, LNK_LeafRef leaf_ref, CV_TypeIndexInf CV_TypeIndex curr_ti = cv_ti_from_leaf_idx(debug_t, curr_ti_source, leaf_ref.leaf_idx); // init hasher - blake3_hasher hasher; blake3_hasher_init(&hasher); + LNK_Hasher hasher; + lnk_hasher_init(&hasher, input->config->debug_types_hash); // hash bytes around indices { @@ -1215,14 +1277,14 @@ lnk_hash_cv_leaf(LNK_CodeViewInput *input, LNK_LeafRef leaf_ref, CV_TypeIndexInf for EachNode(ti_info, CV_TypeIndexInfo, ti_info_list.first) { U8 *bytes = leaf.data.str + last_ti_off; U64 size = ti_info->offset - last_ti_off; - blake3_hasher_update(&hasher, bytes, size); + lnk_hasher_update(&hasher, bytes, size); last_ti_off = ti_info->offset + sizeof(CV_TypeIndex); } Assert(leaf.data.size >= last_ti_off); U8 *bytes = leaf.data.str + last_ti_off; U64 size = leaf.data.size - last_ti_off; - blake3_hasher_update(&hasher, bytes, size); + lnk_hasher_update(&hasher, bytes, size); } // mix-in sub leaf hashes @@ -1232,7 +1294,7 @@ lnk_hash_cv_leaf(LNK_CodeViewInput *input, LNK_LeafRef leaf_ref, CV_TypeIndexInf // simple indices are stable across compile units if (sub_ti < debug_t->ti_ranges[sub_ti_n->source].min) { - blake3_hasher_update(&hasher, &sub_ti, sizeof(sub_ti)); + lnk_hasher_update_struct(&hasher, &sub_ti); continue; } @@ -1244,7 +1306,7 @@ lnk_hash_cv_leaf(LNK_CodeViewInput *input, LNK_LeafRef leaf_ref, CV_TypeIndexInf memory_write16(leaf_header + OffsetOf(CV_LeafHeader, size), sizeof(CV_LeafKind)); // reset hasher - blake3_hasher_init(&hasher); + lnk_hasher_init(&hasher, input->config->debug_types_hash); // log error Temp scratch = scratch_begin(0,0); @@ -1266,7 +1328,7 @@ lnk_hash_cv_leaf(LNK_CodeViewInput *input, LNK_LeafRef leaf_ref, CV_TypeIndexInf memory_write16(leaf_header + OffsetOf(CV_LeafHeader, size), sizeof(CV_LeafKind)); // reset hasher - blake3_hasher_init(&hasher); + lnk_hasher_init(&hasher, input->config->debug_types_hash); // log error Temp scratch = scratch_begin(0,0); @@ -1283,20 +1345,21 @@ lnk_hash_cv_leaf(LNK_CodeViewInput *input, LNK_LeafRef leaf_ref, CV_TypeIndexInf U64 sub_hash = input->debug_h_arr[sub_ref.obj_idx].v[sub_ref.leaf_idx]; // mix-in sub-type hash - blake3_hasher_update(&hasher, &sub_hash, sizeof(sub_hash)); + lnk_hasher_update_struct(&hasher, &sub_hash); } // hash leaf header - CV_LeafHeader *leaf_header = cv_debug_t_get_leaf_header(debug_t, leaf_ref.leaf_idx); - blake3_hasher_update(&hasher, leaf_header, sizeof(*leaf_header)); + CV_LeafHeader *leaf_header_ptr = cv_debug_t_get_leaf_header(debug_t, leaf_ref.leaf_idx); + lnk_hasher_update_struct(&hasher, leaf_header_ptr); - U64 hash; - blake3_hasher_finalize(&hasher, (U8 *) &hash, sizeof(hash)); + // finalize the type hash + U64 hash = lnk_hasher_digest(&hasher); Assert(hash != 0); Assert(input->debug_h_arr[leaf_ref.obj_idx].v[leaf_ref.leaf_idx] == 0 || input->debug_h_arr[leaf_ref.obj_idx].v[leaf_ref.leaf_idx] == 1); input->debug_h_arr[leaf_ref.obj_idx].v[leaf_ref.leaf_idx] = hash; + return hash; } @@ -1370,29 +1433,21 @@ lnk_hash_cv_leaf_deep(Arena *arena, temp_end(temp); } -internal LNK_LeafRef * -lnk_leaf_hash_table_search(LNK_LeafHashTable *ht, LNK_CodeViewInput *input, LNK_LeafRef leaf_ref) +internal CV_TypeIndex +lnk_assigned_ti_hash_search(LNK_AssignedTiHash *ht, LNK_CodeViewInput *input, LNK_LeafRef leaf_ref) { - LNK_LeafRef *match = 0; - - CV_DebugT *debug_t = &input->debug_t_arr[leaf_ref.obj_idx]; - CV_DebugH *debug_h = &input->debug_h_arr[leaf_ref.obj_idx]; - U64 hash = debug_h->v[leaf_ref.leaf_idx]; - U64 best_bucket_idx = hash % ht->cap; - U64 bucket_idx = best_bucket_idx; + CV_DebugH *debug_h = &input->debug_h_arr[leaf_ref.obj_idx]; + U64 hash = debug_h->v[leaf_ref.leaf_idx]; + U64 best_idx = hash % ht->cap; + U64 idx = best_idx; do { - LNK_LeafRef *bucket = ht->bucket_arr[bucket_idx]; - if (bucket == 0) { break; } + CV_TypeIndex ti = ht->ti_arr[idx]; + if (ti == 0) { break; } + if (ht->hash_arr[idx] == hash) { return ti; } + idx = (idx + 1) == ht->cap ? 0 : (idx + 1); + } while (idx != best_idx); - if (lnk_match_leaf_ref(input, *bucket, leaf_ref)) { - match = bucket; - break; - } - - bucket_idx = (bucket_idx + 1) == ht->cap ? 0 : (bucket_idx + 1); - } while (bucket_idx != best_bucket_idx); - - return match; + return 0; } internal @@ -1731,10 +1786,7 @@ internal void lnk_leaf_ref_array_sort(TP_Context *tp, LNK_CodeViewInput *input, LNK_LeafRefArray arr, U64 debug_t_count) { Temp scratch = scratch_begin(0,0); - ProfBeginDynamic("Leaf Sort [Leaf Count: %.*s]", str8_varg(str8_from_count(scratch.arena, arr.count))); - - scratch_end(scratch); ProfEnd(); } @@ -1747,54 +1799,35 @@ THREAD_POOL_TASK_FUNC(lnk_assign_type_indices_task) CV_TypeIndexSource ti_source = task->ti_source; LNK_LeafRefArray unique_leaf_refs = task->unique_leaf_refs_arr[ti_source]; CV_TypeIndex min_type_index = task->min_type_indices[ti_source]; - U64 assigned_type_cap = task->assigned_type_caps[ti_source]; - CV_TypeIndex *assigned_type_ht = task->assigned_type_hts[ti_source]; + LNK_AssignedTiHash *assigned = &task->assigned_ti_arr[ti_source]; + CV_DebugH *debug_h_arr = task->input->debug_h_arr; for EachInRange(i, task->ranges[task_id]) { LNK_LeafRef *leaf_ref = unique_leaf_refs.v[i]; CV_TypeIndex type_index = min_type_index + i; - U64 hash = u64_hash_from_str8(str8_struct(leaf_ref)); - U64 best_idx = hash % assigned_type_cap; + U64 hash = debug_h_arr[leaf_ref->obj_idx].v[leaf_ref->leaf_idx]; + U64 best_idx = hash % assigned->cap; U64 idx = best_idx; B32 is_inserted = 0; do { - CV_TypeIndex curr_type_index = assigned_type_ht[idx]; + CV_TypeIndex curr_type_index = assigned->ti_arr[idx]; if (curr_type_index == 0) { - CV_TypeIndex cmp_type_index = ins_atomic_u32_eval_cond_assign(&assigned_type_ht[idx], type_index, curr_type_index); + CV_TypeIndex cmp_type_index = ins_atomic_u32_eval_cond_assign(&assigned->ti_arr[idx], type_index, curr_type_index); if (cmp_type_index == curr_type_index) { + assigned->hash_arr[idx] = hash; is_inserted = 1; break; } } // advance - idx = (idx + 1) == assigned_type_cap ? 0 : (idx + 1); + idx = (idx + 1) == assigned->cap ? 0 : (idx + 1); } while (idx != best_idx); Assert(is_inserted); } } -internal CV_TypeIndex -lnk_assigned_type_ht_search(U64 cap, CV_TypeIndex *ht, CV_TypeIndex min_type_index, LNK_LeafRefArray unique_leaf_refs, LNK_LeafRef *v, U64 hash) -{ - U64 best_idx = hash % cap; - U64 idx = best_idx; - do { - CV_TypeIndex type_index = ht[idx]; - if (type_index < min_type_index) { break; } - - U64 leaf_idx = type_index - min_type_index; - LNK_LeafRef *compar = unique_leaf_refs.v[leaf_idx]; - if (MemoryMatchStruct(compar,v)) { return type_index; } - - idx = (idx + 1) == cap ? 0 : (idx + 1); - } while(idx != best_idx); - - InvalidPath; - return 0; -} - internal void lnk_fixup_cv_type_indices(LNK_MergeTypes *ctx, U32 obj_idx, String8 data, CV_TypeIndexInfoList ti_info_list) { @@ -1805,26 +1838,15 @@ lnk_fixup_cv_type_indices(LNK_MergeTypes *ctx, U32 obj_idx, String8 data, CV_Typ // skip basic types if (ti < ctx->input->min_type_indices[n->source]) { continue; } - CV_TypeIndex final_ti = 0; - LNK_LeafRef leaf_ref = lnk_leaf_ref_from_ti(ctx->input, obj_idx, n->source, ti); - LNK_LeafHashTable *leaf_ht = &ctx->leaf_ht_arr[n->source]; - LNK_LeafRef *final_leaf = lnk_leaf_hash_table_search(leaf_ht, ctx->input, leaf_ref); - if (final_leaf) { - U64 final_hash = u64_hash_from_str8(str8_struct(final_leaf)); - final_ti = lnk_assigned_type_ht_search(ctx->assigned_type_caps [n->source], - ctx->assigned_type_hts [n->source], - ctx->min_type_indices [n->source], - ctx->unique_leaf_refs_arr[n->source], - final_leaf, - final_hash); - } -#if BUILD_DEBUG - else { + LNK_LeafRef leaf_ref = lnk_leaf_ref_from_ti(ctx->input, obj_idx, n->source, ti); + CV_TypeIndex final_ti = lnk_assigned_ti_hash_search(&ctx->assigned_ti_arr[n->source], ctx->input, leaf_ref); + memory_write32(ti_ptr, final_ti); + +#if LNK_PARANOID + if (final_ti == 0) { lnk_error_obj(LNK_Error_InvalidTypeIndex, ctx->input->obj_arr[obj_idx], "no itype 0x%x", ti); } #endif - - memory_write32(ti_ptr, final_ti); } } @@ -1833,11 +1855,11 @@ THREAD_POOL_TASK_FUNC(lnk_cv_patcher_symbols_task) { ProfBeginFunction(); LNK_MergeTypes *task = raw_task; - Rng1U64 range = task->input->symbol_input_ranges[task_id]; + Rng1U64 range = task->input->symbol_patch_task[task_id].input_range; for EachInRange(i, range) { LNK_SymbolInput symbols = task->input->symbol_inputs[i]; for (U64 cursor = 0; cursor + sizeof(CV_SymbolHeader) <= symbols.raw_symbols.size; ) { - Temp temp = temp_begin(task->fixed_arenas[task_id]); + Temp temp = temp_begin(task->fixed_arenas[worker_id]); CV_Symbol symbol = {0}; TryReadBreak(cv_read_symbol(symbols.raw_symbols, cursor, CV_SymbolAlign, &symbol), cursor); @@ -1986,25 +2008,11 @@ THREAD_POOL_TASK_FUNC(lnk_build_obj_ti_map) CV_TypeIndex *obj_ti_map = task->obj_ti_batch + task->obj_ti_map_offsets[obj_idx]; for EachIndex(leaf_idx, debug_t->count) { - CV_Leaf leaf = cv_debug_t_get_leaf(debug_t, leaf_idx); - CV_TypeIndexSource source = cv_type_index_source_from_leaf_kind(leaf.kind); - LNK_LeafRef leaf_ref = { obj_idx, leaf_idx }; - LNK_LeafHashTable *leaf_ht = &task->leaf_ht_arr[source]; - LNK_LeafRef *final_leaf = lnk_leaf_hash_table_search(leaf_ht, input, leaf_ref); - - if (final_leaf) { - U64 final_hash = u64_hash_from_str8(str8_struct(final_leaf)); - CV_TypeIndex final_ti = lnk_assigned_type_ht_search(task->assigned_type_caps [source], - task->assigned_type_hts [source], - task->min_type_indices [source], - task->unique_leaf_refs_arr[source], - final_leaf, - final_hash); - - obj_ti_map[leaf_idx] = final_ti; - } else { - obj_ti_map[leaf_idx] = 0; - } + CV_Leaf leaf = cv_debug_t_get_leaf(debug_t, leaf_idx); + CV_TypeIndexSource source = cv_type_index_source_from_leaf_kind(leaf.kind); + LNK_LeafRef leaf_ref = { obj_idx, leaf_idx }; + LNK_AssignedTiHash *assigned = &task->assigned_ti_arr[source]; + obj_ti_map[leaf_idx] = lnk_assigned_ti_hash_search(assigned, input, leaf_ref); } task->result.obj_ti_maps[obj_idx] = obj_ti_map; @@ -2245,18 +2253,23 @@ lnk_merge_types(TP_Context *tp, TP_Arena *tp_temp, LNK_CodeViewInput *input, LNK { ProfBegin("Assign type indices"); for EachIndex(ti_source, CV_TypeIndexSource_COUNT) { - task.ti_source = ti_source; - task.assigned_type_caps[ti_source] = (task.unique_leaf_refs_arr[ti_source].count * 13) / 10; - task.assigned_type_hts [ti_source] = push_array(scratch.arena, CV_TypeIndex, task.assigned_type_caps[ti_source]); - task.min_type_indices [ti_source] = CV_MinComplexTypeIndex; - task.ranges = tp_divide_work(scratch.arena, task.unique_leaf_refs_arr[ti_source].count, tp->worker_count); + task.ti_source = ti_source; + task.assigned_ti_arr[ti_source].cap = ((task.unique_leaf_refs_arr[ti_source].count * 13) / 10); + task.assigned_ti_arr[ti_source].ti_arr = push_array(scratch.arena, CV_TypeIndex, task.assigned_ti_arr[ti_source].cap); + + // unique extraction is complete, so the dedup bucket slots can back the + // direct hash table without increasing peak memory + Assert(task.assigned_ti_arr[ti_source].cap <= task.leaf_ht_arr[ti_source].cap); + task.assigned_ti_arr[ti_source].hash_arr = (U64 *)task.leaf_ht_arr[ti_source].bucket_arr; + + task.min_type_indices[ti_source] = CV_MinComplexTypeIndex; + task.ranges = tp_divide_work(scratch.arena, task.unique_leaf_refs_arr[ti_source].count, tp->worker_count); tp_for_parallel_prof(tp, 0, tp->worker_count, lnk_assign_type_indices_task, &task, "Assign Type Indices"); } ProfEnd(); if (~merge_flags & LNK_MergeTypeFlag_SkipSymbolTypeFixup) { - task.ranges = tp_divide_work(scratch.arena, input->symbol_input_count, tp->worker_count); - tp_for_parallel_prof(tp, 0, tp->worker_count, lnk_cv_patcher_symbols_task, &task, "Fixup Symbol Type Indices"); + tp_for_parallel_prof(tp, 0, input->symbol_patch_task_count, lnk_cv_patcher_symbols_task, &task, "Fixup Symbol Type Indices"); task.ranges = 0; task.debug_s_arr = input->debug_s_arr; @@ -3029,7 +3042,7 @@ THREAD_POOL_TASK_FUNC(lnk_write_pdb_modules) String8 name = str8_cstring_capped(string_table.str + header.name_off, string_table.str + string_table.size); CV_StringBucket *bucket = cv_string_hash_table_lookup(task->string_ht, name); - U64 name_off = task->pdb->info->strtab.size + bucket->u.offset; + U64 name_off = task->string_table_base_offset + bucket->u.offset; // update name offset { @@ -3067,73 +3080,192 @@ THREAD_POOL_TASK_FUNC(lnk_push_dbi_sec_contrib_task) PDB_DbiModule *mod = task->mod_arr [obj_idx]; LNK_Obj *obj = task->cv->obj_arr[obj_idx]; - PDB_DbiSectionContribNode *sc_arr = push_array_no_zero(arena, PDB_DbiSectionContribNode, obj->header.section_count_no_null); - U64 sc_count = 0; - + PDB_DbiSCNode *sc_arr = push_array_no_zero(arena, PDB_DbiSCNode, obj->header.section_count_no_null); + U64 sc_count = 0; + for EachIndex(sect_idx, obj->header.section_count_no_null) { - LNK_ObjSection section = lnk_obj_section_from_sect_idx(obj, sect_idx); - if (*section.flags & COFF_SectionFlag_LnkInfo) { continue; } - if (*section.flags & COFF_SectionFlag_LnkRemove) { continue; } - if (*section.flags & LNK_SECTION_FLAG_DEBUG) { continue; } + // filter by section flags + if (obj->section_flags[sect_idx] & (COFF_SectionFlag_LnkInfo | COFF_SectionFlag_LnkRemove | LNK_SECTION_FLAG_DEBUG)) { continue; } + // skip unwind info for the section contribution String8 section_name = lnk_obj_section_name_from_sect_idx(obj, sect_idx); if (str8_match(section_name, str8_lit(".pdata"), 0)) { continue; } - U64 sect_number; - String8 sect_data; - U32 sect_off; - U32 data_crc; - if (*section.flags & COFF_SectionFlag_CntUninitializedData) { - if (dim_1u64(section.vrange) == 0) { continue; } + // load section and determine its type + LNK_ObjSection section = lnk_obj_section_from_sect_idx(obj, sect_idx); + B32 is_virt = !!(*section.flags & COFF_SectionFlag_CntUninitializedData); - U64 search_result = rng1u64_array_num_from_value__binary_search(&task->image_section_virt_ranges, section.vrange.min); - sect_number = search_result-1; - Assert(sect_number < task->image_section_virt_ranges.count); - - sect_data = str8_zero(); - sect_off = section.vrange.min - task->image_section_virt_ranges.v[sect_number].min; - data_crc = 0; - } else { - if (dim_1u64(section.frange) == 0) { continue; } + // pick section range + Rng1U64 section_range = is_virt ? section.vrange : section.frange; + if (dim_1u64(section_range) == 0) { continue; } - U64 search_result = rng1u64_array_num_from_value__binary_search(&task->image_section_file_ranges, section.frange.min); - sect_number = search_result-1; - Assert(sect_number < task->image_section_file_ranges.count); + // map the SC offset to the image section range that contains it + Rng1U64Array *image_ranges = is_virt ? &task->image_section_virt_ranges : &task->image_section_file_ranges; + U64 search_result = rng1u64_array_num_from_value__binary_search(image_ranges, section_range.min); - sect_data = str8_substr(task->image_data, section.frange); - sect_off = section.frange.min - task->image_section_file_ranges.v[sect_number].min; - data_crc = update_crc32(0, sect_data.str, sect_data.size); + // log & skip SC offsets that failed to map + if (search_result == 0) { + Temp scratch = scratch_begin(0,0); + lnk_log(LNK_Log_Debug, "%S: failed to map section offset 0x%llx into the linked image; skipping this section", lnk_loc_from_obj(scratch.arena, obj), section_range.min); + scratch_end(scratch); + continue; } - // fill out SC - PDB_DbiSectionContribNode *sc = sc_arr + sc_count++; - sc->data.base.sec = (U16)sect_number; - sc->data.base.pad0 = 0; - sc->data.base.sec_off = sect_off; - sc->data.base.size = dim_1u64(section.vrange); - sc->data.base.flags = *section.flags; - sc->data.base.mod = mod->imod; - sc->data.base.pad1 = 0; - sc->data.data_crc = 0; - sc->data.reloc_crc = 0; + // unpack image range index + U64 range_idx = search_result - 1; + // fill out & push section contribution + PDB_DbiSCNode *sc = sc_arr + sc_count++; + sc->data.base.sec = (U16)(is_virt ? range_idx : task->image_section_file_section_numbers[range_idx]); + sc->data.base.pad0 = 0; + sc->data.base.sec_off = section_range.min - image_ranges->v[range_idx].min; + sc->data.base.size = dim_1u64(section_range); + sc->data.base.flags = *section.flags; + sc->data.base.mod = mod->imod; + sc->data.base.pad1 = 0; + sc->data.data_crc = is_virt ? 0 : crc32_from_string(str8_substr(task->image_data, section_range)); + sc->data.reloc_crc = 0; dbi_sec_contrib_list_push_node(&task->sc_list[obj_idx], sc); } - // Mod1::fUpdateSecContrib - if (sc_count > 0) { - for (U64 sc_idx = 0; sc_idx < sc_count; ++sc_idx) { - if (sc_arr[sc_idx].data.base.flags & COFF_SectionFlag_CntCode) { - mod->first_sc = sc_arr[sc_idx].data; - break; - } + // find first code section contribution for the Mod1::fUpdateSecContrib + for EachIndex(i, sc_count) { + if (sc_arr[i].data.base.flags & COFF_SectionFlag_CntCode) { + mod->first_sc = sc_arr[i].data; + break; } } } -internal String8List -lnk_build_pdb(TP_Context *tp, TP_Arena *tp_arena, String8 image_data, LNK_Config *config, LNK_SymbolTable *symtab, LNK_CodeViewInput *cv, LNK_MergedTypes cv_types, LNK_PDB_BuilderFlags builder_flags) +typedef struct +{ + LNK_BackgroundFileWriter *writer; + LNK_BackgroundFile *file; + MSF_StreamNumber *sealed_streams; + U64 sealed_stream_count; + U64 sealed_stream_cap; +} LNK_PdbOutput; + +typedef struct LNK_MsfPageCursor +{ + MSF_PageDataNode *node; + U64 node_idx; + U64 pages_per_node; +} LNK_MsfPageCursor; + +internal U8 * +lnk_msf_data_from_pn(LNK_MsfPageCursor *cursor, MSF_Context *msf, MSF_PageNumber pn) +{ + U64 node_idx = pn / cursor->pages_per_node; + if (node_idx < cursor->node_idx) { + cursor->node = msf->page_data_list.first; + cursor->node_idx = 0; + } + while (cursor->node_idx < node_idx) { + cursor->node = cursor->node->next; + cursor->node_idx += 1; + } + Assert(cursor->node != 0); + return cursor->node->data + (pn % cursor->pages_per_node) * msf->page_size; +} + +internal void +lnk_pdb_output_enqueue_stream(LNK_PdbOutput *output, MSF_Context *msf, MSF_StreamNumber sn) +{ + if (sn == MSF_INVALID_STREAM_NUMBER) { return; } + Assert(output->sealed_stream_count < output->sealed_stream_cap); + output->sealed_streams[output->sealed_stream_count++] = sn; + + MSF_Stream *stream = msf_find_stream(msf, sn); + Assert(stream != 0); + if (stream->page_list.count == 0) { return; } + + LNK_MsfPageCursor cursor = { + .node = msf->page_data_list.first, + .pages_per_node = msf_get_data_node_size(msf->page_size) / msf->page_size, + }; + MSF_PageNumber run_first_pn = 0; + MSF_PageNumber run_last_pn = 0; + U8 *run_data = 0; + U64 run_size = 0; + for EachNode(page, MSF_PageNode, stream->page_list.first) { + U8 *page_data = lnk_msf_data_from_pn(&cursor, msf, page->pn); + if (run_data != 0 && page->pn == run_last_pn + 1 && page_data == run_data + run_size) { + run_last_pn = page->pn; + run_size += msf->page_size; + } else { + if (run_data != 0) { + lnk_background_file_writer_enqueue(output->writer, output->file, (U64)run_first_pn * msf->page_size, str8(run_data, run_size)); + } + run_first_pn = run_last_pn = page->pn; + run_data = page_data; + run_size = msf->page_size; + } + } + if (run_data != 0) { + lnk_background_file_writer_enqueue(output->writer, output->file, (U64)run_first_pn * msf->page_size, str8(run_data, run_size)); + } +} + +internal void +lnk_pdb_output_finalize_stream(void *user_data, MSF_Context *msf, MSF_StreamNumber sn) +{ + lnk_pdb_output_enqueue_stream(user_data, msf, sn); +} + +internal void +lnk_pdb_output_enqueue_remaining(LNK_PdbOutput *output, MSF_Context *msf) +{ + Temp scratch = scratch_begin(0,0); + U64 save_size = msf_get_save_size(msf); + U64 page_count = CeilIntegerDiv(save_size, msf->page_size); + U8 *is_written = push_array(scratch.arena, U8, page_count); + + for EachIndex(i, output->sealed_stream_count) { + MSF_Stream *stream = msf_find_stream(msf, output->sealed_streams[i]); + Assert(stream != 0); + for EachNode(page, MSF_PageNode, stream->page_list.first) { + Assert(page->pn < page_count); + is_written[page->pn] = 1; + } + } + + LNK_MsfPageCursor cursor = { + .node = msf->page_data_list.first, + .pages_per_node = msf_get_data_node_size(msf->page_size) / msf->page_size, + }; + U64 run_first_pn = 0; + U8 *run_data = 0; + U64 run_size = 0; + for EachIndex(pn, page_count) { + U8 *page_data = lnk_msf_data_from_pn(&cursor, msf, pn); + U64 page_size = Min(msf->page_size, save_size - pn * msf->page_size); + if (!is_written[pn]) { + if (run_data != 0 && page_data == run_data + run_size) { + run_size += page_size; + } else { + if (run_data != 0) { + lnk_background_file_writer_enqueue(output->writer, output->file, run_first_pn * msf->page_size, str8(run_data, run_size)); + } + run_first_pn = pn; + run_data = page_data; + run_size = page_size; + } + } else if (run_data != 0) { + lnk_background_file_writer_enqueue(output->writer, output->file, run_first_pn * msf->page_size, str8(run_data, run_size)); + run_data = 0; + run_size = 0; + } + } + if (run_data != 0) { + lnk_background_file_writer_enqueue(output->writer, output->file, run_first_pn * msf->page_size, str8(run_data, run_size)); + } + scratch_end(scratch); +} + +internal LNK_FileArtifact +lnk_build_pdb(TP_Context *tp, TP_Arena *tp_arena, String8 image_data, LNK_Config *config, LNK_SymbolTable *symtab, LNK_CodeViewInput *cv, LNK_MergedTypes cv_types, LNK_PdbWriter writer, LNK_PDB_BuilderFlags builder_flags) { ProfBeginFunction(); Temp scratch = scratch_begin(tp_arena->v, tp_arena->count); @@ -3143,19 +3275,38 @@ lnk_build_pdb(TP_Context *tp, TP_Arena *tp_arena, String8 image_data, LNK_Config } LNK_BuildPdb task = { - .image_data = image_data, - .symtab = symtab, - .cv = cv, - .pdb = pdb_alloc_(lnk_get_huge_arena(), config->pdb_page_size, config->machine, config->time_stamp, config->age, config->guid), - .mod_arr = push_array(scratch.arena, PDB_DbiModule *, cv->obj_count), - .pe = pe_bin_info_from_data(scratch.arena, image_data), - .image_section_table = coff_section_table_from_data(scratch.arena, image_data, task.pe.section_table_range), - .image_section_table_count = task.pe.section_count+1, - .image_section_virt_ranges.count = task.image_section_table_count, - .image_section_virt_ranges.v = push_array(scratch.arena, Rng1U64, task.image_section_table_count), - .image_section_file_ranges.v = push_array(scratch.arena, Rng1U64, task.image_section_table_count), + .image_data = image_data, + .symtab = symtab, + .cv = cv, + .pdb = pdb_alloc_(lnk_get_huge_arena(), config->pdb_page_size, config->machine, config->time_stamp, config->age, config->guid), + .mod_arr = push_array(scratch.arena, PDB_DbiModule *, cv->obj_count), + .pe = pe_bin_info_from_data(scratch.arena, image_data), + .image_section_table = coff_section_table_from_data(scratch.arena, image_data, task.pe.section_table_range), + .image_section_table_count = task.pe.section_count+1, + .image_section_virt_ranges.count = task.image_section_table_count, + .image_section_virt_ranges.v = push_array(scratch.arena, Rng1U64, task.image_section_table_count), + .image_section_file_ranges.v = push_array(scratch.arena, Rng1U64, task.image_section_table_count), + .image_section_file_section_numbers = push_array(scratch.arena, U64, task.image_section_table_count), }; + LNK_PdbOutput output = {0}; + LNK_PdbOutput *output_ptr = 0; + if (writer.output_path.size > 0) { + output.writer = writer.file_writer; + output.file = lnk_background_file_writer_begin_file(output.writer, writer.output_path, writer.temp_output_path); + if (output.file != 0) { + output.sealed_stream_cap = cv->obj_count + 128; + output.sealed_streams = push_array_no_zero(scratch.arena, MSF_StreamNumber, output.sealed_stream_cap); + output_ptr = &output; + } + } + + PDB_BuildHooks build_hooks = {0}; + if (output_ptr != 0) { + build_hooks.stream_finalize = lnk_pdb_output_finalize_stream; + build_hooks.user_data = output_ptr; + } + // set min type indices for EachElement(ti_source, cv_types.min_type_indices) { task.pdb->type_servers[ti_source]->ti_lo = cv_types.min_type_indices[ti_source]; } @@ -3183,24 +3334,32 @@ lnk_build_pdb(TP_Context *tp, TP_Arena *tp_arena, String8 image_data, LNK_Config cv_string_hash_table_assign_buffer_offsets(tp, task.string_ht); ProfEnd(); - if (builder_flags & LNK_PDB_BuilderFlag_Modules) { - ProfScope ("Alloc Modules") - for EachIndex(obj_idx, cv->obj_count) - task.mod_arr[obj_idx] = dbi_push_module(task.pdb->dbi, cv->obj_arr[obj_idx]->path, lnk_obj_get_lib_path(cv->obj_arr[obj_idx])); - - ProfScope("Move Global Symbols") - tp_for_parallel(tp, 0, tp->worker_count, lnk_move_global_symbols_to_gsi, &task); - - ProfScope("Build GSI and PSI") - pdb_build_gsi_psi(tp, task.pdb); - - ProfScope("Write Modules") - tp_for_parallel(tp, 0, tp->worker_count, lnk_write_pdb_modules, &task); - } - + task.string_table_base_offset = task.pdb->info->strtab.size; ProfBegin("Add string tables"); pdb_strtab_add_cv_string_hash_table(&task.pdb->info->strtab, task.string_ht); ProfEnd(); + pdb_build_types(tp, task.pdb, &build_hooks); + + if (builder_flags & LNK_PDB_BuilderFlag_Modules) { + ProfScope ("Alloc Modules") + for EachIndex(obj_idx, cv->obj_count) { + task.mod_arr[obj_idx] = dbi_push_module(task.pdb->dbi, cv->obj_arr[obj_idx]->path, lnk_obj_get_lib_path(cv->obj_arr[obj_idx])); + } + + ProfScope("Write Modules") tp_for_parallel(tp, 0, tp->worker_count, lnk_write_pdb_modules, &task); + if (output_ptr != 0) { + for EachIndex(obj_idx, cv->obj_count) { + lnk_pdb_output_enqueue_stream(output_ptr, task.pdb->msf, task.mod_arr[obj_idx]->sn); + } + } + ProfScope("Move Global Symbols") tp_for_parallel(tp, 0, tp->worker_count, lnk_move_global_symbols_to_gsi, &task); + ProfScope("Build GSI and PSI") pdb_build_gsi_psi(tp, task.pdb); + if (output_ptr != 0) { + lnk_pdb_output_enqueue_stream(output_ptr, task.pdb->msf, task.pdb->dbi->publics_sn); + lnk_pdb_output_enqueue_stream(output_ptr, task.pdb->msf, task.pdb->dbi->globals_sn); + lnk_pdb_output_enqueue_stream(output_ptr, task.pdb->msf, task.pdb->dbi->symbols_sn); + } + } if (builder_flags & LNK_PDB_BuilderFlag_SC) { ProfBegin("Build Section Contrib Map"); @@ -3213,13 +3372,17 @@ lnk_build_pdb(TP_Context *tp, TP_Arena *tp_arena, String8 image_data, LNK_Config for EachIndex(i, task.image_section_table_count) { COFF_SectionHeader *sect_header = task.image_section_table[i]; + if (~sect_header->flags & COFF_SectionFlag_CntUninitializedData) { - task.image_section_file_ranges.v[task.image_section_file_ranges.count++] = rng_1u64(sect_header->foff, sect_header->foff + sect_header->fsize); + U64 section_file_idx = task.image_section_file_ranges.count++; + task.image_section_file_ranges.v[section_file_idx] = r1u64s(sect_header->foff, sect_header->fsize); + task.image_section_file_section_numbers[section_file_idx] = i; } + task.image_section_virt_ranges.v[i] = rng_1u64(sect_header->voff, sect_header->voff + sect_header->vsize); } - task.sc_list = push_array(scratch.arena, PDB_DbiSectionContribList, cv->obj_count); + task.sc_list = push_array(scratch.arena, PDB_DbiSCList, cv->obj_count); tp_for_parallel(tp, tp_arena, cv->obj_count, lnk_push_dbi_sec_contrib_task, &task); dbi_sec_list_concat_arr(&task.pdb->dbi->sec_contrib_list, cv->obj_count, task.sc_list); } @@ -3258,17 +3421,25 @@ lnk_build_pdb(TP_Context *tp, TP_Arena *tp_arena, String8 image_data, LNK_Config ProfEnd(); } - pdb_build(tp, tp_arena, task.pdb, task.string_ht, 0, cv->is_stripped); + pdb_build_dbi_info(tp, task.pdb, task.string_ht, 0, cv->is_stripped, &build_hooks); MSF_Error msf_err = msf_build(task.pdb->msf); if (msf_err != MSF_Error_OK) { lnk_error(LNK_Error_UnableToSerializeMsf, "unable to serialize MSF: %s", msf_error_to_string(msf_err)); } + if (output_ptr != 0) { + lnk_pdb_output_enqueue_remaining(output_ptr, task.pdb->msf); + } + ProfBegin("Get Page Nodes"); - String8List page_data_list = msf_get_page_data_nodes(tp_arena->v[0], task.pdb->msf); + LNK_FileArtifact artifact = { .data = msf_get_page_data_nodes(tp_arena->v[0], task.pdb->msf) }; ProfEnd(); - + + if (output_ptr != 0) { + lnk_background_file_writer_end_file(output_ptr->writer, output_ptr->file, artifact.data.total_size); + } + // NOTE: linker is about to exit so we can skip memory release // and let windows free memory since it does this faster #if 0 @@ -3279,5 +3450,5 @@ lnk_build_pdb(TP_Context *tp, TP_Arena *tp_arena, String8 image_data, LNK_Config scratch_end(scratch); ProfEnd(); - return page_data_list; + return artifact; } diff --git a/src/linker/lnk_debug_info.h b/src/linker/lnk_debug_info.h index dc094e0f..28364855 100644 --- a/src/linker/lnk_debug_info.h +++ b/src/linker/lnk_debug_info.h @@ -7,11 +7,12 @@ // RRT global read_only String8 g_rrt_magic = str8_lit_comp("RAD-TYPE-SERVER\0"); -global read_only U64 g_rrt_version = 2; +global read_only U64 g_rrt_version = 3; typedef struct LNK_RRT { String8 path; + LNK_HashKind debug_types_hash; String8 type_data_raw; union { @@ -68,6 +69,12 @@ typedef struct LNK_SymbolInput String8 raw_symbols; } LNK_SymbolInput; +typedef struct LNK_SymbolInputTask +{ + Rng1U64 input_range; + U64 weight; +} LNK_SymbolInputTask; + typedef struct { LNK_Config *config; @@ -95,9 +102,11 @@ typedef struct B32 *is_type_server_discarded; // [ts_arr.count] CV_TypeIndex min_type_indices[CV_TypeIndexSource_COUNT]; - U64 symbol_input_count; - LNK_SymbolInput *symbol_inputs; // [symbol_input_count] - Rng1U64 *symbol_input_ranges; // [worker_count] + U64 symbol_input_count; + LNK_SymbolInput *symbol_inputs; // [symbol_input_count] + Rng1U64 *symbol_input_ranges; // [worker_count] + U64 symbol_patch_task_count; // + LNK_SymbolInputTask *symbol_patch_task; // [symbol_patch_task_count] } LNK_CodeViewInput; typedef struct @@ -119,6 +128,13 @@ typedef struct LNK_LeafRef **bucket_arr; } LNK_LeafHashTable; +typedef struct +{ + U64 cap; + CV_TypeIndex *ti_arr; + U64 *hash_arr; +} LNK_AssignedTiHash; + typedef struct LNK_LeafRange { struct LNK_LeafRange *next; @@ -150,6 +166,7 @@ typedef struct LNK_CodeViewInput *input; CV_DebugS *debug_s_arr; LNK_LeafHashTable leaf_ht_arr[CV_TypeIndexSource_COUNT]; + LNK_AssignedTiHash assigned_ti_arr[CV_TypeIndexSource_COUNT]; Arena **fixed_arenas; CV_TypeIndexSource ti_source; U32Array indices; @@ -173,11 +190,8 @@ typedef struct LNK_LeafRef **src; U64 pass_idx; - // assign type indices - U64 assigned_type_caps [CV_TypeIndexSource_COUNT]; - CV_TypeIndex *assigned_type_hts [CV_TypeIndexSource_COUNT]; - CV_TypeIndex min_type_indices [CV_TypeIndexSource_COUNT]; - LNK_LeafRefArray unique_leaf_refs_arr[CV_TypeIndexSource_COUNT]; + CV_TypeIndex min_type_indices [CV_TypeIndexSource_COUNT]; + LNK_LeafRefArray unique_leaf_refs_arr[CV_TypeIndexSource_COUNT]; U64 *obj_ti_map_counts; U64 *obj_ti_map_offsets; @@ -209,20 +223,29 @@ typedef struct LNK_CodeViewInput *cv; PDB_Context *pdb; CV_StringHashTable string_ht; + U64 string_table_base_offset; PDB_DbiModule **mod_arr; // [obj_count] U32Array *obj_indices; // [obj_count] U64 symbol_count; // push DBI SC Map - PE_BinInfo pe; - COFF_SectionHeader **image_section_table; - U64 image_section_table_count; - Rng1U64Array image_section_file_ranges; - Rng1U64Array image_section_virt_ranges; - PDB_DbiSectionContribList *sc_list; // [obj_count] + PE_BinInfo pe; + COFF_SectionHeader **image_section_table; + U64 image_section_table_count; + Rng1U64Array image_section_virt_ranges; + Rng1U64Array image_section_file_ranges; + U64 *image_section_file_section_numbers; + PDB_DbiSCList *sc_list; // [obj_count] } LNK_BuildPdb; +typedef struct +{ + LNK_BackgroundFileWriter *file_writer; + String8 output_path; + String8 temp_output_path; +} LNK_PdbWriter; + typedef struct { U64 leaf_count; @@ -251,11 +274,11 @@ internal B32 lnk_match_leaf_ref (LNK_CodeViewInput internal U64 lnk_hash_cv_leaf (LNK_CodeViewInput *input, LNK_LeafRef leaf_ref, CV_TypeIndexInfoList ti_info_list, B32 discard_cycles); internal void lnk_hash_cv_leaf_deep (Arena *arena, LNK_CodeViewInput *input, LNK_LeafRef leaf_ref, CV_TypeIndexInfoList ti_info_list); internal LNK_LeafRef * lnk_leaf_hash_table_insert_or_update(LNK_LeafHashTable *leaf_ht, LNK_CodeViewInput *input, CV_DebugH *hashes, U64 hash, LNK_LeafRef *new_bucket); -internal LNK_LeafRef * lnk_leaf_hash_table_search (LNK_LeafHashTable *ht, LNK_CodeViewInput *input, LNK_LeafRef leaf_ref); +internal CV_TypeIndex lnk_assigned_ti_hash_search (LNK_AssignedTiHash *ht, LNK_CodeViewInput *input, LNK_LeafRef leaf_ref); internal LNK_MergedTypes lnk_merge_types (TP_Context *tp, TP_Arena *tp_temp, LNK_CodeViewInput *input, LNK_MergeTypeFlags merge_flags); internal void lnk_replace_type_names_with_hashes (TP_Context *tp, TP_Arena *arena, U64 leaf_count, U8 **leaf_arr, LNK_TypeNameHashMode mode, U64 hash_length, String8 map_name); //////////////////////////////// // PDB -internal String8List lnk_build_pdb(TP_Context *tp, TP_Arena *tp_arena, String8 image_data, LNK_Config *config, LNK_SymbolTable *symtab, LNK_CodeViewInput *cv, LNK_MergedTypes cv_types, LNK_PDB_BuilderFlags builder_flags); +internal LNK_FileArtifact lnk_build_pdb(TP_Context *tp, TP_Arena *tp_arena, String8 image_data, LNK_Config *config, LNK_SymbolTable *symtab, LNK_CodeViewInput *cv, LNK_MergedTypes cv_types, LNK_PdbWriter writer, LNK_PDB_BuilderFlags builder_flags); diff --git a/src/linker/lnk_hasher.c b/src/linker/lnk_hasher.c new file mode 100644 index 00000000..d11467b1 --- /dev/null +++ b/src/linker/lnk_hasher.c @@ -0,0 +1,60 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +internal void +lnk_hasher_init(LNK_Hasher *hasher, LNK_HashKind kind) +{ + hasher->kind = kind; + switch (kind) { + case LNK_HashKind_BLAKE3: blake3_hasher_init(&hasher->u.blake3); break; + case LNK_HashKind_XXHash: XXH3_64bits_reset(&hasher->u.xxhash); break; + default: InvalidPath; break; + } +} + +internal void +lnk_hasher_update(LNK_Hasher *hasher, void *data, U64 size) +{ + switch (hasher->kind) { + case LNK_HashKind_BLAKE3: blake3_hasher_update(&hasher->u.blake3, data, size); break; + case LNK_HashKind_XXHash: XXH3_64bits_update(&hasher->u.xxhash, data, size); break; + default: InvalidPath; break; + } +} + +internal U64 +lnk_hasher_digest(LNK_Hasher *hasher) +{ + U64 hash = 0; + switch (hasher->kind) { + case LNK_HashKind_BLAKE3: blake3_hasher_finalize(&hasher->u.blake3, (U8 *)&hash, sizeof(hash)); break; + case LNK_HashKind_XXHash: hash = XXH3_64bits_digest(&hasher->u.xxhash); break; + default: InvalidPath; break; + } + return hash; +} + +internal String8 +lnk_string_hash_kind(LNK_HashKind hash_kind) +{ +#define X(NAME) case LNK_HashKind_##NAME: return str8_lit(Stringify(NAME)); + switch (hash_kind) { + LNK_HashKind_XList + } +#undef X + return str8_zero(); +} + +internal LNK_HashKind +lnk_hash_kind_from_llvm(LLVM_GHashAlgEnum v) +{ + switch (v) { + case LLVM_GHashAlg_SHA1: + case LLVM_GHashAlg_SHA1_8: + return LNK_HashKind_Null; + case LLVM_GHashAlg_BLAKE3: + return LNK_HashKind_BLAKE3; + } + return LNK_HashKind_Null; +} + diff --git a/src/linker/lnk_hasher.h b/src/linker/lnk_hasher.h new file mode 100644 index 00000000..a07aa321 --- /dev/null +++ b/src/linker/lnk_hasher.h @@ -0,0 +1,37 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#pragma once + +#define LNK_HashKind_XList \ + X(Null) \ + X(BLAKE3) \ + X(XXHash) + +typedef enum +{ +#define X(NAME) LNK_HashKind_##NAME, + LNK_HashKind_XList +#undef X +} LNK_HashKind; + +typedef struct LNK_Hasher +{ + LNK_HashKind kind; + union + { + blake3_hasher blake3; + XXH3_state_t xxhash; + } u; +} LNK_Hasher; + +internal void lnk_hasher_init (LNK_Hasher *hasher, LNK_HashKind kind); +internal void lnk_hasher_update(LNK_Hasher *hasher, void *data, U64 size); +#define lnk_hasher_update_struct(hasher, ptr) lnk_hasher_update(hasher, ptr, sizeof(*ptr)) +internal U64 lnk_hasher_digest(LNK_Hasher *hasher); + +internal String8 lnk_string_hash_kind(LNK_HashKind hash_kind); + +// LLVM extension +internal LNK_HashKind lnk_hash_kind_from_llvm(LLVM_GHashAlgEnum v); + diff --git a/src/linker/lnk_io.c b/src/linker/lnk_io.c index 8af41797..8e522dfb 100644 --- a/src/linker/lnk_io.c +++ b/src/linker/lnk_io.c @@ -10,9 +10,11 @@ lnk_open_file_read(char *path, uint64_t path_size, void *handle_buffer, uint64_t shared_function int lnk_open_file_write(char *path, uint64_t path_size, void *handle_buffer, uint64_t handle_buffer_max) { + ProfBeginFunction(); File handle = file_open(AccessFlag_Write, str8((U8*)path, path_size)); Assert(sizeof(handle) <= handle_buffer_max); MemoryCopy(handle_buffer, &handle, sizeof(handle)); + ProfEnd(); return !file_match(handle, file_zero()); } @@ -84,6 +86,7 @@ lnk_find_first_file(Arena *arena, String8List dir_list, String8 path) internal File lnk_file_open_with_rename_permissions(String8 path) { + ProfBeginFunction(); File file_handle = file_zero(); #if OS_WINDOWS Temp scratch = scratch_begin(0,0); @@ -106,6 +109,7 @@ lnk_file_open_with_rename_permissions(String8 path) #else file_handle = file_open(AccessFlag_Read|AccessFlag_Write, path); #endif + ProfEnd(); return file_handle; } @@ -401,3 +405,230 @@ lnk_write_data_to_file_path(String8 path, String8 temp_path, String8 data) lnk_write_data_list_to_file_path(path, temp_path, data_list); scratch_end(scratch); } + +internal String8 +lnk_data_from_file_artifact(Arena *arena, LNK_FileArtifact *artifact) +{ + return str8_list_join(arena, &artifact->data, 0); +} + +// --- Background Writer ------------------------------------------------------- + +struct LNK_BackgroundFile +{ + LNK_BackgroundFile *next; + String8 path; + String8 temp_path; + String8 open_path; + File file; + U64 bytes_written; + B32 open_with_rename; + B32 is_open; + B32 is_finished; + B32 is_complete; + B32 write_failed; +}; + +typedef enum +{ + LNK_BackgroundFileJobKind_Write, + LNK_BackgroundFileJobKind_EndFile, + LNK_BackgroundFileJobKind_EndWriter, +} LNK_BackgroundFileJobKind; + +typedef struct +{ + LNK_BackgroundFileJobKind kind; + LNK_BackgroundFile *file; + U64 file_off; + U64 expected_byte_count; + String8 data; +} LNK_BackgroundFileWriteJob; + +internal void +lnk_background_file_writer_end_file_on_thread(LNK_BackgroundFile *file, U64 expected_byte_count) +{ + B32 is_complete = !file->write_failed && file->bytes_written == expected_byte_count; + if (is_complete && file->open_with_rename) { + if (!lnk_file_set_delete_on_close(file->file, 0)) { + lnk_error(LNK_Error_IO, "failed to update file disposition on %S", file->open_path); + is_complete = 0; + } else if (!lnk_file_rename(file->file, file->path)) { + lnk_error(LNK_Error_IO, "failed to rename %S -> %S", file->temp_path, file->path); + is_complete = 0; + } + } + + lnk_close_file(&file->file); + file->is_open = 0; + file->is_complete = is_complete; + + if (!is_complete) { + lnk_error(LNK_Error_IO, "incomplete write, %M written, expected %M, file %S", file->bytes_written, expected_byte_count, file->path); + } else if (lnk_get_log_status(LNK_Log_IO_Write)) { + lnk_log(LNK_Log_IO_Write, "File \"%S\" %M written", file->path, expected_byte_count); + } +} + +internal void +lnk_background_file_writer_thread(void *raw_writer) +{ + ProfBeginFunction(); + set_thread_namef("Background File Writer"); + + LNK_BackgroundFileWriter *writer = raw_writer; + for (;;) { + LNK_BackgroundFileWriteJob job = {0}; + RingGuard guard = guarded_ring_open(writer->queue); + B32 is_read = guarded_ring_read_struct_or_wait(&guard, &job, max_U64); + guarded_ring_close(&guard); + Assert(is_read); + + if (job.kind == LNK_BackgroundFileJobKind_EndWriter) { break; } + + if (job.kind == LNK_BackgroundFileJobKind_Write) { + U64 write_size = lnk_write_file(&job.file->file, job.file_off, job.data.str, job.data.size); + if (write_size != job.data.size) { + job.file->write_failed = 1; + } + job.file->bytes_written += write_size; + } else if (job.kind == LNK_BackgroundFileJobKind_EndFile) { + lnk_background_file_writer_end_file_on_thread(job.file, job.expected_byte_count); + } + } + ProfEnd(); +} + +internal void +lnk_background_file_writer_begin(LNK_BackgroundFileWriter *writer) +{ + ProfBegin("Background File Writer Begin"); + + writer->queue_arena = arena_alloc(.reserve_size = MB(2), .commit_size = MB(2), .name = "BACKGROUND_FILE_WRITE_QUEUE"); + writer->queue = guarded_ring_alloc(writer->queue_arena, MB(1)); + ProfEnd(); +} + +internal LNK_BackgroundFile * +lnk_background_file_writer_begin_file(LNK_BackgroundFileWriter *writer, String8 path, String8 temp_path) +{ + ProfBegin("Background File Writer Begin File"); + + if (!writer->is_running) { + writer->thread = thread_launch(lnk_background_file_writer_thread, writer); + writer->is_running = writer->thread.u64[0] != 0; + if (!writer->is_running) { + lnk_error(LNK_Error_IO, "failed to start background file writer"); + ProfEnd(); + return 0; + } + } + + LNK_BackgroundFile *file = push_array(writer->queue_arena, LNK_BackgroundFile, 1); + + file->path = path; + file->temp_path = temp_path; + file->open_with_rename = (temp_path.size > 0); + + if (file->open_with_rename) { + file->file = lnk_file_open_with_rename_permissions(temp_path); + file->open_path = temp_path; + } else { + lnk_open_file_write((char *)path.str, path.size, &file->file, sizeof(file->file)); + file->open_path = path; + } + + file->is_open = !file_match(file->file, file_zero()); + if (!file->is_open) { + lnk_error(LNK_Error_NoAccess, "don't have access to write to %S", path); + goto exit; + } + + if (file->open_with_rename && !lnk_file_set_delete_on_close(file->file, 1)) { + lnk_error(LNK_Error_IO, "failed to update file disposition on %S", file->open_path); + lnk_close_file(&file->file); + file->is_open = 0; + goto exit; + } + + SLLQueuePush(writer->file_first, writer->file_last, file); + + exit:; + ProfEnd(); + return file->is_open ? file : 0; +} + +internal void +lnk_background_file_writer_enqueue(LNK_BackgroundFileWriter *writer, LNK_BackgroundFile *file, U64 file_off, String8 data) +{ + ProfBegin("Background File Writer Enqueue"); + + Assert(writer->is_running); + Assert(file->is_open && !file->is_finished); + + if (data.size > 0) { + LNK_BackgroundFileWriteJob job = { + .kind = LNK_BackgroundFileJobKind_Write, + .file = file, + .file_off = file_off, + .data = data, + }; + RingGuard guard = guarded_ring_open(writer->queue); + B32 is_written = guarded_ring_write_struct_or_wait(&guard, &job, max_U64); + guarded_ring_close(&guard); + Assert(is_written); + } + + ProfEnd(); +} + +internal void +lnk_background_file_writer_end_file(LNK_BackgroundFileWriter *writer, LNK_BackgroundFile *file, U64 expected_byte_count) +{ + ProfBegin("Background File Writer End File"); + + Assert(writer->is_running); + Assert(file->is_open && !file->is_finished); + file->is_finished = 1; + + LNK_BackgroundFileWriteJob job = { + .kind = LNK_BackgroundFileJobKind_EndFile, + .file = file, + .expected_byte_count = expected_byte_count, + }; + RingGuard guard = guarded_ring_open(writer->queue); + B32 is_written = guarded_ring_write_struct_or_wait(&guard, &job, max_U64); + guarded_ring_close(&guard); + Assert(is_written); + + ProfEnd(); +} + +internal void +lnk_background_file_writer_end(LNK_BackgroundFileWriter *writer) +{ + ProfBegin("Background File Writer End"); + + if (writer->is_running) { + LNK_BackgroundFileWriteJob job = { .kind = LNK_BackgroundFileJobKind_EndWriter }; + RingGuard guard = guarded_ring_open(writer->queue); + B32 is_written = guarded_ring_write_struct_or_wait(&guard, &job, max_U64); + guarded_ring_close(&guard); + Assert(is_written); + thread_join(writer->thread, -1); + } + + for EachNode(file, LNK_BackgroundFile, writer->file_first) { + if (file->is_open) { + lnk_error(LNK_Error_IO, "unfinished background write, file %S", file->path); + lnk_close_file(&file->file); + file->is_open = 0; + } + } + + guarded_ring_release(writer->queue); + arena_release(writer->queue_arena); + writer->is_running = 0; + + ProfEnd(); +} diff --git a/src/linker/lnk_io.h b/src/linker/lnk_io.h index ffa35a62..dc9932b3 100644 --- a/src/linker/lnk_io.h +++ b/src/linker/lnk_io.h @@ -18,6 +18,23 @@ typedef struct U8 *buffer; } LNK_DiskReader; +typedef struct +{ + String8List data; +} LNK_FileArtifact; + +typedef struct LNK_BackgroundFile LNK_BackgroundFile; + +typedef struct +{ + Arena *queue_arena; + GuardedRing *queue; + Thread thread; + LNK_BackgroundFile *file_first; + LNK_BackgroundFile *file_last; + B32 is_running; +} LNK_BackgroundFileWriter; + // --- Shared File API --------------------------------------------------------- shared_function int lnk_open_file_read(char *path, uint64_t path_size, void *handle_buffer, uint64_t handle_buffer_max); @@ -38,4 +55,12 @@ internal String8Array lnk_read_data_from_file_path_parallel(TP_Context *tp, Aren internal void lnk_write_data_list_to_file_path(String8 path, String8 temp_path, String8List list); internal void lnk_write_data_to_file_path(String8 path, String8 temp_path, String8 data); +internal String8 lnk_data_from_file_artifact(Arena *arena, LNK_FileArtifact *artifact); +// --- Background Writer ------------------------------------------------------- + +internal void lnk_background_file_writer_begin (LNK_BackgroundFileWriter *writer); +internal LNK_BackgroundFile *lnk_background_file_writer_begin_file(LNK_BackgroundFileWriter *writer, String8 path, String8 temp_path); +internal void lnk_background_file_writer_enqueue (LNK_BackgroundFileWriter *writer, LNK_BackgroundFile *file, U64 file_off, String8 data); +internal void lnk_background_file_writer_end_file (LNK_BackgroundFileWriter *writer, LNK_BackgroundFile *file, U64 expected_byte_count); +internal void lnk_background_file_writer_end (LNK_BackgroundFileWriter *writer); diff --git a/src/linker/lnk_lib.h b/src/linker/lnk_lib.h index eed5e2de..6db16e29 100644 --- a/src/linker/lnk_lib.h +++ b/src/linker/lnk_lib.h @@ -15,6 +15,10 @@ typedef struct LNK_Lib String8Array symbol_names; String8 long_names; U64 input_idx; + + struct LNK_SymbolHashTrieChunk **search_cursor_chunks; + U64 *search_cursor_indices; + B32 searched_anti_deps; } LNK_Lib; typedef struct LNK_LibNode diff --git a/src/linker/lnk_log.c b/src/linker/lnk_log.c index c15abb0e..5dce5875 100644 --- a/src/linker/lnk_log.c +++ b/src/linker/lnk_log.c @@ -1,6 +1,11 @@ // Copyright (c) Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) +#if OS_WINDOWS +#include // _O_BINARY +#include // _setmode +#endif + static Mutex g_log_mutex; static B32 g_log_status [LNK_Log_Count]; static LNK_ErrorMode g_error_mode_arr [LNK_Error_Count]; @@ -36,7 +41,14 @@ lnk_log_begin(void) for (int i = LNK_Error_StopFirst; i < LNK_Error_StopLast; ++i) { g_error_mode_arr[i] = LNK_ErrorMode_Stop; } for (int i = LNK_Error_ContinueFirst; i < LNK_Error_ContinueLast; ++i) { g_error_mode_arr[i] = LNK_ErrorMode_Continue; } for (int i = LNK_Warning_First; i < LNK_Warning_Last; ++i) { g_error_mode_arr[i] = LNK_ErrorMode_Warn; } + g_log_mutex = mutex_alloc(); + + // ninja mangles CRLF in captured linker output, so force LF-only output +#if OS_WINDOWS + _setmode(_fileno(stdout), _O_BINARY); + _setmode(_fileno(stderr), _O_BINARY); +#endif } internal void diff --git a/src/linker/lnk_log.h b/src/linker/lnk_log.h index 8c087009..b10c836b 100644 --- a/src/linker/lnk_log.h +++ b/src/linker/lnk_log.h @@ -24,6 +24,7 @@ typedef enum LNK_Log_Count } LNK_LogType; +// TODO: factor into an xlist with explicitly defined error levels and warnings typedef enum { LNK_Error_Null, diff --git a/src/linker/lnk_obj.c b/src/linker/lnk_obj.c index c2f9649e..73bd5366 100644 --- a/src/linker/lnk_obj.c +++ b/src/linker/lnk_obj.c @@ -94,14 +94,44 @@ THREAD_POOL_TASK_FUNC(lnk_obj_initer) } // - // error check section headers + // section table pass + // - error check headers fields + // - collect section header flags + // - find debug info and meta-data leaders // - COFF_SectionHeader *coff_section_table = (COFF_SectionHeader *)raw_coff_section_table.str; - COFF_SectionFlags *section_flags = push_array_no_zero(arena, COFF_SectionFlags, header.section_count_no_null); + COFF_SectionHeader *coff_section_table = (COFF_SectionHeader *)raw_coff_section_table.str; + COFF_SectionFlags *section_flags = push_array_no_zero(arena, COFF_SectionFlags, header.section_count_no_null); + U32 debug_t_sect_idx = ~0; + U32 debug_p_sect_idx = ~0; + U32 debug_h_sect_idx = ~0; + U32 llvm_addrsig_sect_idx = ~0; 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]; - 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); + + if (str8_starts_with(sect_name, str8_lit(".debug$"))) { + section_flags[sect_idx] |= LNK_SECTION_FLAG_DEBUG; + } + if (str8_ends_with(sect_name, str8_lit("$fo$"), 0) || + str8_ends_with(sect_name, str8_lit("$fo_rvas$"), 0) || + str8_ends_with(sect_name, str8_lit("$fo_bdd$"), 0)) { + section_flags[sect_idx] |= COFF_SectionFlag_LnkInfo; + } + if (task->find_debug_t) { + if (str8_match(sect_name, str8_lit(".debug$T"), 0)) { + debug_t_sect_idx = sect_idx; + } else if (str8_match(sect_name, str8_lit(".debug$P"), 0)) { + debug_p_sect_idx = sect_idx; + } else if (str8_match(sect_name, str8_lit(".debug$H"), 0)) { + debug_h_sect_idx = sect_idx; + } + } + if (task->find_llvm_addrsig && llvm_addrsig_sect_idx == max_U32 && + str8_match(sect_name, str8_lit(".llvm_addrsig"), 0)) { + llvm_addrsig_sect_idx = sect_idx; + } + if (~section_flags[sect_idx] & COFF_SectionFlag_CntUninitializedData) { if (coff_sect_header->fsize > 0) { Rng1U64 sect_range = rng_1u64(coff_sect_header->foff, coff_sect_header->foff + coff_sect_header->fsize); @@ -125,13 +155,38 @@ THREAD_POOL_TASK_FUNC(lnk_obj_initer) } // - // error check symbol table + // error check symbol table and cache name lengths for primary symbols // + U64 primary_symbol_count = 0; + { + COFF_ParsedSymbol symbol; + for (U64 symbol_idx = 0; symbol_idx < header.symbol_count; symbol_idx += 1 + symbol.aux_symbol_count) { + symbol = coff_parse_symbol_no_name(header, raw_coff_symbol_table, symbol_idx); + primary_symbol_count += 1; + } + } + + U64 name_block_count = CeilIntegerDiv(header.symbol_count, 64); + LNK_SymbolNameCache symbol_name_cache = { + .masks = push_array(arena, U64, name_block_count), + .block_bases = push_array_no_zero(arena, U32, name_block_count), + .name_sizes = push_array_no_zero(arena, U32, primary_symbol_count), + }; { COFF_SectionHeader *section_table = (COFF_SectionHeader *)str8_substr(input->data, header.section_table_range).str; + U64 next_block = 0; + U32 name_count = 0; COFF_ParsedSymbol symbol; for (U64 symbol_idx = 0; symbol_idx < header.symbol_count; symbol_idx += (1 + symbol.aux_symbol_count)) { symbol = coff_parse_symbol(header, raw_coff_string_table, raw_coff_symbol_table, symbol_idx); + U64 block_idx = symbol_idx >> 6; + while (next_block <= block_idx) { + symbol_name_cache.block_bases[next_block++] = name_count; + } + if (symbol.name.size) { + symbol_name_cache.masks[block_idx] |= 1ull << (symbol_idx & 63); + symbol_name_cache.name_sizes[name_count++] = safe_cast_u32(symbol.name.size); + } COFF_SymbolValueInterpType interp = coff_interp_symbol(symbol.section_number, symbol.value, symbol.storage_class); if (interp == COFF_SymbolValueInterp_Regular) { if (symbol.section_number == 0 || symbol.section_number > header.section_count_no_null) { @@ -149,6 +204,9 @@ THREAD_POOL_TASK_FUNC(lnk_obj_initer) } } } + while (next_block < name_block_count) { + symbol_name_cache.block_bases[next_block++] = name_count; + } } // @@ -161,7 +219,7 @@ THREAD_POOL_TASK_FUNC(lnk_obj_initer) COFF_ParsedSymbol symbol; for (U64 symbol_idx = 0; symbol_idx < header.symbol_count; symbol_idx += (1 + symbol.aux_symbol_count)) { - symbol = coff_parse_symbol(header, raw_coff_string_table, raw_coff_symbol_table, symbol_idx); + symbol = coff_parse_symbol_no_name(header, raw_coff_symbol_table, symbol_idx); COFF_SymbolValueInterpType interp = coff_interp_symbol(symbol.section_number, symbol.value, symbol.storage_class); if (interp == COFF_SymbolValueInterp_Regular) { @@ -208,7 +266,7 @@ THREAD_POOL_TASK_FUNC(lnk_obj_initer) } // extract COMDAT info for current section - COFF_ParsedSymbol symbol = coff_parse_symbol(header, raw_coff_string_table, raw_coff_symbol_table, symbol_idx); + COFF_ParsedSymbol symbol = coff_parse_symbol_no_name(header, raw_coff_symbol_table, symbol_idx); COFF_ComdatSelectType select = COFF_ComdatSelect_Null; U32 section_number = 0; coff_parse_secdef(symbol, header.is_big_obj, &select, §ion_number, 0, 0); @@ -247,7 +305,7 @@ THREAD_POOL_TASK_FUNC(lnk_obj_initer) { COFF_ParsedSymbol symbol; for (U32 symbol_idx = 0; symbol_idx < header.symbol_count; symbol_idx += (1 + symbol.aux_symbol_count)) { - symbol = coff_parse_symbol(header, raw_coff_string_table, raw_coff_symbol_table, symbol_idx); + symbol = coff_parse_symbol_no_name(header, raw_coff_symbol_table, symbol_idx); COFF_SymbolValueInterpType interp = coff_interp_from_parsed_symbol(symbol); if (interp == COFF_SymbolValueInterp_Regular && symbol.storage_class == COFF_SymStorageClass_Static && symbol.aux_symbol_count > 0) { COFF_ComdatSelectType selection = COFF_ComdatSelect_Null; @@ -262,28 +320,6 @@ THREAD_POOL_TASK_FUNC(lnk_obj_initer) } } - // - // mark sections - // - { - for EachIndex(sect_idx, header.section_count_no_null) { - COFF_SectionHeader *sect_header = &coff_section_table[sect_idx]; - String8 sect_name = coff_name_from_section_header(raw_coff_string_table, sect_header); - - // debug info - if (str8_starts_with(sect_name, str8_lit(".debug$"))) { - section_flags[sect_idx] |= LNK_SECTION_FLAG_DEBUG; - } - - // function overrides - if (str8_ends_with(sect_name, str8_lit("$fo$"), 0) || - str8_ends_with(sect_name, str8_lit("$fo_rvas$"), 0) || - str8_ends_with(sect_name, str8_lit("$fo_bdd$"), 0)) { - section_flags[sect_idx] |= COFF_SectionFlag_LnkInfo; - } - } - } - B8 hotpatch = 0; if (header.machine == COFF_MachineType_X64) { hotpatch = 1; @@ -335,31 +371,17 @@ THREAD_POOL_TASK_FUNC(lnk_obj_initer) obj->path = push_str8_copy(arena, input->path); obj->header = header; obj->section_flags = section_flags; + obj->symbol_name_cache = symbol_name_cache; obj->comdats = comdats; obj->exclude_from_debug_info = input->exclude_from_debug_info; obj->hotpatch = hotpatch; obj->associated_sections = associated_sections; obj->self = &task->objs[task_id]; obj->link_member = input->link_member; - obj->debug_t_sect_idx = ~0; - obj->debug_p_sect_idx = ~0; - obj->debug_h_sect_idx = ~0; -} - -internal -THREAD_POOL_TASK_FUNC(lnk_obj_find_debug_t) -{ - 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(".debug$T"), 0)) { - obj->debug_t_sect_idx = sect_idx; - } else if (str8_match(section_name, str8_lit(".debug$P"), 0)) { - obj->debug_p_sect_idx = sect_idx; - } else if (str8_match(section_name, str8_lit(".debug$H"), 0)) { - obj->debug_h_sect_idx = sect_idx; - } - } + obj->debug_t_sect_idx = debug_t_sect_idx; + obj->debug_p_sect_idx = debug_p_sect_idx; + obj->debug_h_sect_idx = debug_h_sect_idx; + obj->llvm_addrsig_sect_idx = llvm_addrsig_sect_idx; } internal LNK_ObjNode * @@ -368,11 +390,14 @@ lnk_obj_from_input_many(TP_Context *tp, TP_Arena *arena, LNK_Config *config, U64 LNK_ObjNode *objs = 0; if (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 }); - - if (lnk_do_debug_info(config)) { - tp_for_parallel(tp, arena, inputs_count, lnk_obj_find_debug_t, objs); - } + LNK_ObjIniter task = { + .inputs = inputs, + .objs = objs, + .machine = config->machine, + .find_debug_t = lnk_do_debug_info(config), + .find_llvm_addrsig = config->opt_icf == LNK_SwitchState_Yes, + }; + tp_for_parallel(tp, arena, inputs_count, lnk_obj_initer, &task); } return objs; } @@ -410,8 +435,9 @@ THREAD_POOL_TASK_FUNC(lnk_input_coff_symbol_table) LNK_Obj *obj = task->objs[task_id]; COFF_ParsedSymbol symbol = {0}; 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_no_name(obj, symbol_idx); COFF_SymbolValueInterpType interp = coff_interp_from_parsed_symbol(symbol); + LNK_SymbolSearchType search_type = lnk_symbol_search_type_from_coff(obj, symbol, interp); switch (interp) { case COFF_SymbolValueInterp_Regular: { if (symbol.storage_class == COFF_SymStorageClass_External) { @@ -419,27 +445,27 @@ THREAD_POOL_TASK_FUNC(lnk_input_coff_symbol_table) if (*section.flags & COFF_SectionFlag_LnkRemove) { break; } - LNK_Symbol *defn = lnk_make_symbol(arena, symbol.name, obj, symbol_idx); + LNK_Symbol *defn = lnk_make_symbol(arena, lnk_symbol_name_from_coff_symbol_idx(obj, symbol_idx), obj, symbol_idx, search_type); lnk_symbol_table_push_(task->symtab, arena, worker_id, defn); } } break; case COFF_SymbolValueInterp_Weak: { - LNK_Symbol *defn = lnk_make_symbol(arena, symbol.name, obj, symbol_idx); + LNK_Symbol *defn = lnk_make_symbol(arena, lnk_symbol_name_from_coff_symbol_idx(obj, symbol_idx), obj, symbol_idx, search_type); lnk_symbol_table_push_(task->symtab, arena, worker_id, defn); } break; case COFF_SymbolValueInterp_Undefined: { if (symbol.storage_class == COFF_SymStorageClass_External) { - LNK_Symbol *defn = lnk_make_symbol(arena, symbol.name, obj, symbol_idx); + LNK_Symbol *defn = lnk_make_symbol(arena, lnk_symbol_name_from_coff_symbol_idx(obj, symbol_idx), obj, symbol_idx, search_type); lnk_symbol_table_push_(task->symtab, arena, worker_id, defn); } } break; case COFF_SymbolValueInterp_Common: { - LNK_Symbol *defn = lnk_make_symbol(arena, symbol.name, obj, symbol_idx); + LNK_Symbol *defn = lnk_make_symbol(arena, lnk_symbol_name_from_coff_symbol_idx(obj, symbol_idx), obj, symbol_idx, search_type); lnk_symbol_table_push_(task->symtab, arena, worker_id, defn); } break; case COFF_SymbolValueInterp_Abs: { if (symbol.storage_class == COFF_SymStorageClass_External) { - LNK_Symbol *defn = lnk_make_symbol(arena, symbol.name, obj, symbol_idx); + LNK_Symbol *defn = lnk_make_symbol(arena, lnk_symbol_name_from_coff_symbol_idx(obj, symbol_idx), obj, symbol_idx, search_type); lnk_symbol_table_push_(task->symtab, arena, worker_id, defn); } } break; @@ -451,26 +477,96 @@ 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_SymbolHashTrie **symlinks = push_array(arena, LNK_SymbolHashTrie *, obj->header.section_count_no_null+1); - COFF_ParsedSymbol symbol; + LNK_ObjSymbolRef *symlinks = push_array(arena, LNK_ObjSymbolRef, obj->header.section_count_no_null + 1); + COFF_ParsedSymbol symbol = {0}; 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); - 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) { - LNK_ObjSection section = lnk_obj_section_from_section_number(obj, symbol.section_number); - if (*section.flags & COFF_SectionFlag_LnkCOMDAT) { - if (symlinks[symbol.section_number] == 0 || symbol.value == 0) { - symlinks[symbol.section_number] = lnk_symbol_table_search_(symtab, symbol.name); + symbol = lnk_parsed_symbol_from_coff_symbol_idx_no_name(obj, symbol_idx); + COFF_SymbolValueInterpType interp = coff_interp_from_parsed_symbol(symbol); + if (interp != COFF_SymbolValueInterp_Regular) { continue; } + + COFF_SectionFlags section_flags = obj->section_flags[symbol.section_number - 1]; + if (~section_flags & COFF_SectionFlag_LnkCOMDAT) { continue; } + + LNK_ObjSymbolRef *symlink = &symlinks[symbol.section_number]; + + // external symbols + if (symbol.storage_class == COFF_SymStorageClass_External && symbol.aux_symbol_count == 0) { + String8 symbol_name = lnk_symbol_name_from_coff_symbol_idx(obj, symbol_idx); + B32 can_set_symlink = (symlink->obj == 0 || symbol.value == 0); + if (!can_set_symlink && symlink->obj == obj) { + COFF_ParsedSymbol leader = lnk_parsed_symbol_from_coff_symbol_idx_no_name(symlink->obj, symlink->symbol_idx); + B32 leader_is_same_section = leader.section_number == symbol.section_number; + B32 leader_is_static_anchor = (leader_is_same_section && leader.storage_class == COFF_SymStorageClass_Static && leader.aux_symbol_count == 0); + B32 leader_is_vftable = str8_starts_with(lnk_symbol_name_from_coff_symbol_idx(symlink->obj, symlink->symbol_idx), str8_lit(MSCRT_VFTABLE_SYMBOL_PREFIX)); + B32 current_is_vftable = str8_starts_with(symbol_name, str8_lit(MSCRT_VFTABLE_SYMBOL_PREFIX)); + + // prefer public symbols to local static anchors; prefer vftable public + // symbols to other public symbols so ICF keeps vftables in their own color space + can_set_symlink = (leader_is_static_anchor || (leader_is_same_section && current_is_vftable && !leader_is_vftable)); + } + + if (can_set_symlink) { + LNK_SymbolHashTrie *link_symbol = lnk_symbol_table_search_(symtab, symbol_name); + if (link_symbol) { + *symlink = lnk_ref_from_symbol(link_symbol->symbol); + } + } + } + // static symbols + else if (symbol.storage_class == COFF_SymStorageClass_Static) { + if (symbol.aux_symbol_count == 0) { + if (symlink->obj == 0) { + *symlink = (LNK_ObjSymbolRef){ obj, symbol_idx }; } } } } + return symlinks; } +internal U32List +lnk_obj_collect_associated_sections(Arena *arena, LNK_Obj *obj, U32 root_section, COFF_SectionFlags skip_flags) +{ + Temp scratch = scratch_begin(&arena, 1); + + // track each child before enqueueing it because COFF associations can cycle + HashMap seen_hm = {0}; + U32List queue = {0}; + + U32Node root_n = { root_section }; + u32_list_push_node(&queue, &root_n); + hash_map_push_u64_u64(scratch.arena, &seen_hm, root_section, 1); + + // walk the complete descendant chain because associated COMDATs can nest + for EachNode(parent_n, U32Node, queue.first) { + for EachNode(associated_n, U32Node, obj->associated_sections[parent_n->data]) { + U32 child_section = associated_n->data; + + if (child_section == 0) { continue; } + if (hash_map_search_u64_u64(&seen_hm, child_section)) { continue; } + if (obj->section_flags[child_section - 1] & skip_flags) { continue; } + + hash_map_push_u64_u64(scratch.arena, &seen_hm, child_section, 1); + u32_list_push(arena, &queue, child_section); + } + } + + // return only child sections so callers choose whether the root participates + U32List result = {0}; + if (queue.count > 1) { + result.first = queue.first->next; + result.last = queue.last; + result.count = queue.count - 1; + } + + scratch_end(scratch); + return result; +} + internal THREAD_POOL_TASK_FUNC(lnk_assign_comdat_symlinks_task) { @@ -479,13 +575,21 @@ THREAD_POOL_TASK_FUNC(lnk_assign_comdat_symlinks_task) obj->symlinks = lnk_symlinks_from_obj(arena, task->symtab, obj); } +internal void +lnk_assign_comdat_symlinks(TP_Context *tp, TP_Arena *arena, LNK_SymbolTable *symtab, U64 objs_count, LNK_Obj **objs) +{ + ProfBeginFunction(); + LNK_InputCoffSymbolTable task = { .symtab = symtab, .objs = objs }; + tp_for_parallel(tp, arena, objs_count, lnk_assign_comdat_symlinks_task, &task); + ProfEnd(); +} + internal void lnk_push_obj_symbols(TP_Context *tp, TP_Arena *arena, LNK_SymbolTable *symtab, U64 objs_count, LNK_Obj **objs) { ProfBeginFunction(); LNK_InputCoffSymbolTable task = { .symtab = symtab, .objs = objs }; tp_for_parallel(tp, arena, objs_count, lnk_input_coff_symbol_table, &task); - tp_for_parallel(tp, arena, objs_count, lnk_assign_comdat_symlinks_task, &task); ProfEnd(); } @@ -543,11 +647,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; } -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) { - LNK_SymbolHashTrie *symlink = obj->symlinks[section_number]; - return symlink ? symlink->symbol : 0; + LNK_ObjSymbolRef symlink = obj->symlinks[section_number]; + B32 is_valid = symlink.obj != 0; + if (is_valid && symlink_out) { + *symlink_out = symlink; + } + return is_valid; } internal COFF_SectionHeader * @@ -644,7 +752,7 @@ lnk_try_comdat_props_from_section_number(LNK_Obj *obj, U32 section_number, COFF_ Assert(section_number > 0); U32 symbol_idx = obj->comdats[section_number-1]; if (symbol_idx != max_U32) { - COFF_ParsedSymbol secdef = lnk_parsed_symbol_from_coff_symbol_idx(obj, symbol_idx); + COFF_ParsedSymbol secdef = lnk_parsed_symbol_from_coff_symbol_idx_no_name(obj, symbol_idx); coff_parse_secdef(secdef, obj->header.is_big_obj, select_out, section_number_out, section_length_out, check_sum_out); return 1; } @@ -660,19 +768,39 @@ lnk_coff_section_header_from_section_number(LNK_Obj *obj, U64 section_number) return §ion_table[sect_idx]; } -internal COFF_ParsedSymbol +internal force_inline COFF_ParsedSymbol +lnk_parsed_symbol_from_coff_symbol_idx_no_name(LNK_Obj *obj, U64 symbol_idx) +{ + return coff_parse_symbol_no_name(obj->header, lnk_coff_symbol_table_from_obj(obj), safe_cast_u32(symbol_idx)); +} + +internal force_inline String8 +lnk_symbol_name_from_coff_symbol_idx(LNK_Obj *obj, U64 symbol_idx) +{ + String8 result = {0}; + U64 block_idx = symbol_idx >> 6; + U64 bit = 1ull << (symbol_idx & 63); + U64 mask = obj->symbol_name_cache.masks[block_idx]; + if (mask & bit) { + U64 name_idx = obj->symbol_name_cache.block_bases[block_idx] + count_bits_set64(mask & (bit - 1)); + U64 name_size = obj->symbol_name_cache.name_sizes[name_idx]; + + String8 symbol_table = lnk_coff_symbol_table_from_obj(obj); + COFF_SymbolName *name = obj->header.is_big_obj ? &((COFF_Symbol32 *)symbol_table.str)[symbol_idx].name + : &((COFF_Symbol16 *)symbol_table.str)[symbol_idx].name; + U8 *name_ptr = name->long_name.zeroes == 0 + ? obj->data.str + obj->header.string_table_range.min + name->long_name.string_table_offset + : name->short_name; + result = str8(name_ptr, name_size); + } + return result; +} + +internal force_inline COFF_ParsedSymbol lnk_parsed_symbol_from_coff_symbol_idx(LNK_Obj *obj, U64 symbol_idx) { - String8 string_table = str8_substr(obj->data, obj->header.string_table_range); - String8 symbol_table = str8_substr(obj->data, obj->header.symbol_table_range); - - COFF_ParsedSymbol result = {0}; - if (obj->header.is_big_obj) { - result = coff_parse_symbol32(string_table, (COFF_Symbol32 *)symbol_table.str + symbol_idx); - } else { - result = coff_parse_symbol16(string_table, (COFF_Symbol16 *)symbol_table.str + symbol_idx); - } - + COFF_ParsedSymbol result = lnk_parsed_symbol_from_coff_symbol_idx_no_name(obj, symbol_idx); + result.name = lnk_symbol_name_from_coff_symbol_idx(obj, symbol_idx); return result; } diff --git a/src/linker/lnk_obj.h b/src/linker/lnk_obj.h index 9a21de59..2f771741 100644 --- a/src/linker/lnk_obj.h +++ b/src/linker/lnk_obj.h @@ -5,6 +5,13 @@ // --- Input ------------------------------------------------------------------- +typedef struct LNK_SymbolNameCache +{ + U64 *masks; + U32 *block_bases; + U32 *name_sizes; +} LNK_SymbolNameCache; + typedef struct LNK_Obj { String8 path; @@ -12,6 +19,7 @@ typedef struct LNK_Obj COFF_FileHeaderInfo header; COFF_SectionFlags *section_flags; + LNK_SymbolNameCache symbol_name_cache; // flags B8 hotpatch; @@ -22,7 +30,7 @@ typedef struct LNK_Obj // COMDAT U32 *comdats; U32Node **associated_sections; - LNK_SymbolHashTrie **symlinks; + LNK_ObjSymbolRef *symlinks; // link struct LNK_LibMemberRef *link_member; @@ -33,6 +41,9 @@ typedef struct LNK_Obj U32 debug_p_sect_idx; U32 debug_h_sect_idx; + // ICF + U32 llvm_addrsig_sect_idx; + // @type_server Rng1U64 ti_range; CV_TypeIndex *ti_map; @@ -101,6 +112,8 @@ typedef struct LNK_ObjNode *objs; U64 obj_id_base; U32 machine; + B32 find_debug_t; + B32 find_llvm_addrsig; } LNK_ObjIniter; typedef struct @@ -139,12 +152,15 @@ internal U32 lnk_obj_get_vol_md(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 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); +internal U32List lnk_obj_collect_associated_sections(Arena *arena, LNK_Obj *obj, U32 root_section, COFF_SectionFlags skip_flags); // --- Symbol & Section Helpers ------------------------------------------------ internal COFF_SectionHeader * lnk_coff_section_header_from_section_number(LNK_Obj *obj, U64 section_number); -internal COFF_ParsedSymbol lnk_parsed_symbol_from_coff_symbol_idx(LNK_Obj *obj, U64 symbol_idx); +internal force_inline COFF_ParsedSymbol lnk_parsed_symbol_from_coff_symbol_idx(LNK_Obj *obj, U64 symbol_idx); +internal force_inline COFF_ParsedSymbol lnk_parsed_symbol_from_coff_symbol_idx_no_name(LNK_Obj *obj, U64 symbol_idx); +internal force_inline String8 lnk_symbol_name_from_coff_symbol_idx(LNK_Obj *obj, U64 symbol_idx); internal U64 lnk_obj_sect_idx_from_section_number(LNK_Obj *obj, U64 section_number); internal U64 lnk_obj_section_number_from_sect_idx(LNK_Obj *obj, U64 sect_idx); internal String8 lnk_obj_section_name_from_section_number(LNK_Obj *obj, U64 section_number); diff --git a/src/linker/lnk_symbol_table.c b/src/linker/lnk_symbol_table.c index 8cb6c4e5..7226ed5e 100644 --- a/src/linker/lnk_symbol_table.c +++ b/src/linker/lnk_symbol_table.c @@ -1,20 +1,68 @@ // Copyright (c) Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) +#define LNK_SYMBOL_SEARCH_TYPE_MASK 7ull + +internal LNK_SymbolSearchType +lnk_symbol_search_type_from_coff(LNK_Obj *obj, COFF_ParsedSymbol symbol, COFF_SymbolValueInterpType interp) +{ + LNK_SymbolSearchType search_type = LNK_SymbolSearch_Null; + if (interp == COFF_SymbolValueInterp_Undefined) { + search_type = LNK_SymbolSearch_Undefined; + } else if (interp == COFF_SymbolValueInterp_Weak) { + COFF_SymbolWeakExt *weak_ext = coff_parse_weak_tag(symbol, obj->header.is_big_obj); + switch (weak_ext->characteristics) { + case COFF_WeakExt_SearchLibrary: search_type = LNK_SymbolSearch_WeakLibrary; break; + case COFF_WeakExt_AntiDependency: search_type = LNK_SymbolSearch_WeakAntiDependency; break; + default: search_type = LNK_SymbolSearch_WeakOther; break; + } + } + return search_type; +} + internal LNK_Symbol * -lnk_make_symbol(Arena *arena, String8 name, LNK_Obj *obj, U32 symbol_idx) +lnk_make_symbol(Arena *arena, String8 name, LNK_Obj *obj, U32 symbol_idx, LNK_SymbolSearchType search_type) { LNK_ObjSymbolRefNode *ref = push_array(arena, LNK_ObjSymbolRefNode, 1); ref->v.obj = obj; ref->v.symbol_idx = symbol_idx; - LNK_Symbol *symbol = push_array(arena, LNK_Symbol, 1); - symbol->name = name; - SLLQueuePush(symbol->first_ref, symbol->last_ref, ref); + LNK_Symbol *symbol = push_array(arena, LNK_Symbol, 1); + symbol->name = name; + symbol->first_ref = ref; + Assert((IntFromPtr(ref) & LNK_SYMBOL_SEARCH_TYPE_MASK) == 0); + Assert(search_type <= LNK_SymbolSearch_WeakOther); + symbol->last_ref_and_search_type = IntFromPtr(ref) | search_type; return symbol; } +internal LNK_ObjSymbolRefNode * +lnk_last_ref_from_symbol(LNK_Symbol *symbol) +{ + return PtrFromInt(symbol->last_ref_and_search_type & ~LNK_SYMBOL_SEARCH_TYPE_MASK); +} + +internal LNK_SymbolSearchType +lnk_search_type_from_symbol(LNK_Symbol *symbol) +{ + return safe_cast_u32(symbol->last_ref_and_search_type & LNK_SYMBOL_SEARCH_TYPE_MASK); +} + +internal void +lnk_symbol_set_last_ref(LNK_Symbol *symbol, LNK_ObjSymbolRefNode *last_ref) +{ + Assert((IntFromPtr(last_ref) & LNK_SYMBOL_SEARCH_TYPE_MASK) == 0); + symbol->last_ref_and_search_type = IntFromPtr(last_ref) | lnk_search_type_from_symbol(symbol); +} + +internal void +lnk_symbol_set_search_type(LNK_Symbol *symbol, LNK_SymbolSearchType search_type) +{ + Assert(search_type <= LNK_SymbolSearch_WeakOther); + symbol->last_ref_and_search_type = (symbol->last_ref_and_search_type & ~LNK_SYMBOL_SEARCH_TYPE_MASK) | search_type; +} + internal int lnk_obj_symbol_ref_is_before(void *raw_a, void *raw_b) { @@ -105,12 +153,12 @@ lnk_can_replace_symbol(LNK_Symbol *dst, LNK_Symbol *src) { B32 can_replace = 0; - COFF_ParsedSymbol dst_parsed = lnk_parsed_from_symbol(dst); - COFF_ParsedSymbol src_parsed = lnk_parsed_from_symbol(src); - COFF_SymbolValueInterpType dst_interp = lnk_interp_from_symbol(dst); - COFF_SymbolValueInterpType src_interp = lnk_interp_from_symbol(src); LNK_ObjSymbolRef dst_ref = lnk_ref_from_symbol(dst); LNK_ObjSymbolRef src_ref = lnk_ref_from_symbol(src); + COFF_ParsedSymbol dst_parsed = lnk_parsed_symbol_from_coff_symbol_idx_no_name(dst_ref.obj, dst_ref.symbol_idx); + COFF_ParsedSymbol src_parsed = lnk_parsed_symbol_from_coff_symbol_idx_no_name(src_ref.obj, src_ref.symbol_idx); + COFF_SymbolValueInterpType dst_interp = coff_interp_from_parsed_symbol(dst_parsed); + COFF_SymbolValueInterpType src_interp = coff_interp_from_parsed_symbol(src_parsed); LNK_Obj *dst_obj = dst_ref.obj; LNK_Obj *src_obj = src_ref.obj; @@ -209,7 +257,13 @@ lnk_can_replace_symbol(LNK_Symbol *dst, LNK_Symbol *src) lnk_error_multiply_defined_symbol(dst, src); } } else if (dst_ext->characteristics == COFF_WeakExt_SearchAlias && src_ext->characteristics == COFF_WeakExt_SearchAlias) { - lnk_error_multiply_defined_symbol(dst, src); + COFF_ParsedSymbol dst_tag = lnk_parsed_symbol_from_coff_symbol_idx(dst_ref.obj, dst_ext->tag_index); + COFF_ParsedSymbol src_tag = lnk_parsed_symbol_from_coff_symbol_idx(src_ref.obj, src_ext->tag_index); + if (str8_match(dst_tag.name, src_tag.name, 0)) { + can_replace = lnk_symbol_is_before(src, dst); + } else { + lnk_error_multiply_defined_symbol(dst, src); + } } else { can_replace = lnk_symbol_is_before(src, dst); } @@ -332,9 +386,9 @@ lnk_can_replace_symbol(LNK_Symbol *dst, LNK_Symbol *src) internal void lnk_on_symbol_replace(LNK_Symbol *dst, LNK_Symbol *src) { - COFF_ParsedSymbol dst_parsed = lnk_parsed_from_symbol(dst); - COFF_SymbolValueInterpType dst_interp = lnk_interp_from_symbol(dst); LNK_ObjSymbolRef dst_ref = lnk_ref_from_symbol(dst); + COFF_ParsedSymbol dst_parsed = lnk_parsed_symbol_from_coff_symbol_idx_no_name(dst_ref.obj, dst_ref.symbol_idx); + COFF_SymbolValueInterpType dst_interp = coff_interp_from_parsed_symbol(dst_parsed); if (dst_interp == COFF_SymbolValueInterp_Regular) { // remove replaced section from the output @@ -351,8 +405,8 @@ lnk_on_symbol_replace(LNK_Symbol *dst, LNK_Symbol *src) } // merge symbol refs - src->last_ref->next = dst->first_ref; - src->last_ref = dst->last_ref; + lnk_last_ref_from_symbol(src)->next = dst->first_ref; + lnk_symbol_set_last_ref(src, lnk_last_ref_from_symbol(dst)); // assert leader section is live #if BUILD_DEBUG @@ -531,7 +585,8 @@ lnk_parsed_from_symbol(LNK_Symbol *symbol) internal COFF_SymbolValueInterpType lnk_interp_from_symbol(LNK_Symbol *symbol) { - COFF_ParsedSymbol symbol_parsed = lnk_parsed_from_symbol(symbol); + LNK_ObjSymbolRef ref = lnk_ref_from_symbol(symbol); + COFF_ParsedSymbol symbol_parsed = lnk_parsed_symbol_from_coff_symbol_idx_no_name(ref.obj, ref.symbol_idx); return coff_interp_from_parsed_symbol(symbol_parsed); } @@ -554,10 +609,10 @@ lnk_symbol_table_init(TP_Arena *arena) internal void lnk_symbol_table_push_(LNK_SymbolTable *symtab, Arena *arena, U64 worker_id, LNK_Symbol *symbol) { - U64 hash = lnk_symbol_table_hasher(symbol->name); - COFF_SymbolValueInterpType interp = lnk_interp_from_symbol(symbol); + U64 hash = lnk_symbol_table_hasher(symbol->name); + LNK_SymbolSearchType search_type = lnk_search_type_from_symbol(symbol); LNK_SymbolHashTrieChunkList *chunks; - if (interp == COFF_SymbolValueInterp_Weak || interp == COFF_SymbolValueInterp_Undefined) { + if (search_type != LNK_SymbolSearch_Null) { chunks = &symtab->search_chunks[worker_id]; } else { chunks = &symtab->chunks[worker_id]; @@ -656,7 +711,7 @@ lnk_resolve_weak_symbol(LNK_SymbolTable *symtab, LNK_ObjSymbolRef symbol, LNK_Ob goto exit; } - COFF_ParsedSymbol current_parsed = lnk_parsed_symbol_from_coff_symbol_idx(current_symbol.obj, current_symbol.symbol_idx); + COFF_ParsedSymbol current_parsed = lnk_parsed_symbol_from_coff_symbol_idx_no_name(current_symbol.obj, current_symbol.symbol_idx); COFF_SymbolValueInterpType current_interp = coff_interp_symbol(current_parsed.section_number, current_parsed.value, current_parsed.storage_class); if (current_interp == COFF_SymbolValueInterp_Weak) { // record visited symbol @@ -665,7 +720,7 @@ lnk_resolve_weak_symbol(LNK_SymbolTable *symtab, LNK_ObjSymbolRef symbol, LNK_Ob SLLQueuePush(sf, sl, s); // does weak symbol have a definition? - LNK_Symbol *defn_symbol = lnk_symbol_table_search(symtab, current_parsed.name); + LNK_Symbol *defn_symbol = lnk_symbol_table_search(symtab, lnk_symbol_name_from_coff_symbol_idx(current_symbol.obj, current_symbol.symbol_idx)); COFF_ParsedSymbol defn_parsed = lnk_parsed_from_symbol(defn_symbol); COFF_SymbolValueInterpType defn_interp = coff_interp_symbol(defn_parsed.section_number, defn_parsed.value, defn_parsed.storage_class); if (defn_interp != COFF_SymbolValueInterp_Weak) { @@ -676,19 +731,19 @@ lnk_resolve_weak_symbol(LNK_SymbolTable *symtab, LNK_ObjSymbolRef symbol, LNK_Ob COFF_SymbolWeakExt *weak_ext = coff_parse_weak_tag(current_parsed, current_symbol.obj->header.is_big_obj); // no definition -- fallback to default symbol - COFF_ParsedSymbol tag_parsed = lnk_parsed_symbol_from_coff_symbol_idx(current_symbol.obj, weak_ext->tag_index); + COFF_ParsedSymbol tag_parsed = lnk_parsed_symbol_from_coff_symbol_idx_no_name(current_symbol.obj, weak_ext->tag_index); COFF_SymbolValueInterpType tag_interp = coff_interp_symbol(tag_parsed.section_number, tag_parsed.value, tag_parsed.storage_class); current_symbol = (LNK_ObjSymbolRef){ .obj = current_symbol.obj, .symbol_idx = weak_ext->tag_index }; if (weak_ext->characteristics == COFF_WeakExt_AntiDependency) { if (tag_interp == COFF_SymbolValueInterp_Undefined || tag_interp == COFF_SymbolValueInterp_Weak) { - LNK_Symbol *dep_symbol = lnk_symbol_table_search(symtab, tag_parsed.name); + LNK_Symbol *dep_symbol = lnk_symbol_table_search(symtab, lnk_symbol_name_from_coff_symbol_idx(current_symbol.obj, weak_ext->tag_index)); tag_interp = lnk_interp_from_symbol(dep_symbol); } if (tag_interp == COFF_SymbolValueInterp_Weak) { break; } } } else if (current_interp == COFF_SymbolValueInterp_Undefined) { - LNK_Symbol *defn_symbol = lnk_symbol_table_search(symtab, current_parsed.name); + LNK_Symbol *defn_symbol = lnk_symbol_table_search(symtab, lnk_symbol_name_from_coff_symbol_idx(current_symbol.obj, current_symbol.symbol_idx)); COFF_SymbolValueInterpType defn_interp = lnk_interp_from_symbol(defn_symbol); // unresolved undefined symbol @@ -713,15 +768,15 @@ internal B32 lnk_resolve_symbol(LNK_SymbolTable *symtab, LNK_ObjSymbolRef symbol, LNK_ObjSymbolRef *symbol_out) { B32 is_resolved = 1; - COFF_ParsedSymbol symbol_parsed = lnk_parsed_symbol_from_coff_symbol_idx(symbol.obj, symbol.symbol_idx); + COFF_ParsedSymbol symbol_parsed = lnk_parsed_symbol_from_coff_symbol_idx_no_name(symbol.obj, symbol.symbol_idx); COFF_SymbolValueInterpType symbol_interp = coff_interp_symbol(symbol_parsed.section_number, symbol_parsed.value, symbol_parsed.storage_class); switch (symbol_interp) { case COFF_SymbolValueInterp_Regular: { - LNK_Symbol *symlink = lnk_obj_get_comdat_symlink(symbol.obj, symbol_parsed.section_number); - *symbol_out = symlink ? lnk_ref_from_symbol(symlink) : symbol; + LNK_ObjSymbolRef symlink = {0}; + *symbol_out = lnk_obj_get_comdat_symlink(symbol.obj, symbol_parsed.section_number, &symlink) ? symlink : symbol; } break; case COFF_SymbolValueInterp_Weak: { - LNK_Symbol *defn = lnk_symbol_table_search(symtab, symbol_parsed.name); + LNK_Symbol *defn = lnk_symbol_table_search(symtab, lnk_symbol_name_from_coff_symbol_idx(symbol.obj, symbol.symbol_idx)); COFF_ParsedSymbol defn_parsed = lnk_parsed_from_symbol(defn); COFF_SymbolValueInterpType defn_interp = lnk_interp_from_symbol(defn); if (defn_interp != COFF_SymbolValueInterp_Undefined) { @@ -731,7 +786,7 @@ lnk_resolve_symbol(LNK_SymbolTable *symtab, LNK_ObjSymbolRef symbol, LNK_ObjSymb } } break; case COFF_SymbolValueInterp_Undefined: { - LNK_Symbol *defn = lnk_symbol_table_search(symtab, symbol_parsed.name); + LNK_Symbol *defn = lnk_symbol_table_search(symtab, lnk_symbol_name_from_coff_symbol_idx(symbol.obj, symbol.symbol_idx)); if (defn) { *symbol_out = lnk_ref_from_symbol(defn); } else { @@ -739,12 +794,12 @@ lnk_resolve_symbol(LNK_SymbolTable *symtab, LNK_ObjSymbolRef symbol, LNK_ObjSymb } } break; case COFF_SymbolValueInterp_Common: { - LNK_Symbol *defn = lnk_symbol_table_search(symtab, symbol_parsed.name); + LNK_Symbol *defn = lnk_symbol_table_search(symtab, lnk_symbol_name_from_coff_symbol_idx(symbol.obj, symbol.symbol_idx)); *symbol_out = lnk_ref_from_symbol(defn); } break; case COFF_SymbolValueInterp_Abs: { if (symbol_parsed.storage_class == COFF_SymStorageClass_External) { - LNK_Symbol *defn = lnk_symbol_table_search(symtab, symbol_parsed.name); + LNK_Symbol *defn = lnk_symbol_table_search(symtab, lnk_symbol_name_from_coff_symbol_idx(symbol.obj, symbol.symbol_idx)); *symbol_out = lnk_ref_from_symbol(defn); } else { *symbol_out = symbol; @@ -763,12 +818,12 @@ THREAD_POOL_TASK_FUNC(lnk_replace_weak_with_default_symbol_task) for EachIndex(i, c->count) { LNK_Symbol *symbol = c->v[i].symbol; LNK_ObjSymbolRef symbol_ref = lnk_ref_from_symbol(symbol); - COFF_ParsedSymbol symbol_parsed = lnk_parsed_from_symbol(symbol); + COFF_ParsedSymbol symbol_parsed = lnk_parsed_symbol_from_coff_symbol_idx_no_name(symbol_ref.obj, symbol_ref.symbol_idx); COFF_SymbolValueInterpType symbol_interp = coff_interp_from_parsed_symbol(symbol_parsed); if (symbol_interp == COFF_SymbolValueInterp_Weak) { LNK_ObjSymbolRef resolve = {0}; if (lnk_resolve_weak_symbol(symtab, symbol_ref, &resolve)) { - COFF_ParsedSymbol resolve_parsed = lnk_parsed_symbol_from_coff_symbol_idx(resolve.obj, resolve.symbol_idx); + COFF_ParsedSymbol resolve_parsed = lnk_parsed_symbol_from_coff_symbol_idx_no_name(resolve.obj, resolve.symbol_idx); COFF_SymbolValueInterpType resolve_interp = coff_interp_from_parsed_symbol(resolve_parsed); if (resolve_interp == COFF_SymbolValueInterp_Weak) { COFF_SymbolWeakExt *weak_ext = coff_parse_weak_tag(resolve_parsed, symbol_ref.obj->header.is_big_obj); @@ -783,8 +838,10 @@ THREAD_POOL_TASK_FUNC(lnk_replace_weak_with_default_symbol_task) symbol16->value = 0; symbol16->storage_class = COFF_SymStorageClass_External; } + lnk_symbol_set_search_type(symbol, LNK_SymbolSearch_Undefined); } else { symbol->first_ref->v = resolve; + lnk_symbol_set_search_type(symbol, LNK_SymbolSearch_Null); } } } diff --git a/src/linker/lnk_symbol_table.h b/src/linker/lnk_symbol_table.h index da579bb1..4fe2f24c 100644 --- a/src/linker/lnk_symbol_table.h +++ b/src/linker/lnk_symbol_table.h @@ -17,13 +17,25 @@ typedef struct LNK_ObjSymbolRefNode LNK_ObjSymbolRef v; } LNK_ObjSymbolRefNode; +typedef U32 LNK_SymbolSearchType; +enum +{ + LNK_SymbolSearch_Null, + LNK_SymbolSearch_Undefined, + LNK_SymbolSearch_WeakLibrary, + LNK_SymbolSearch_WeakAntiDependency, + LNK_SymbolSearch_WeakOther, +}; + typedef struct LNK_Symbol { String8 name; LNK_ObjSymbolRefNode *first_ref; - LNK_ObjSymbolRefNode *last_ref; + U64 last_ref_and_search_type; // Tail pointer with search type in its low bits. } LNK_Symbol; +StaticAssert(sizeof(LNK_Symbol) == 32, lnk_symbol_size_check); + // --- Symbol Containers ------------------------------------------------------- typedef struct LNK_SymbolNode @@ -89,7 +101,12 @@ typedef struct // --- Symbol ----------------------------------------------------------------- -internal LNK_Symbol * lnk_make_symbol(Arena *arena, String8 name, struct LNK_Obj *obj, U32 symbol_idx); +internal LNK_SymbolSearchType lnk_symbol_search_type_from_coff(struct LNK_Obj *obj, COFF_ParsedSymbol symbol, COFF_SymbolValueInterpType interp); +internal LNK_Symbol * lnk_make_symbol(Arena *arena, String8 name, struct LNK_Obj *obj, U32 symbol_idx, LNK_SymbolSearchType search_type); +internal LNK_ObjSymbolRefNode * lnk_last_ref_from_symbol(LNK_Symbol *symbol); +internal LNK_SymbolSearchType lnk_search_type_from_symbol(LNK_Symbol *symbol); +internal void lnk_symbol_set_last_ref(LNK_Symbol *symbol, LNK_ObjSymbolRefNode *last_ref); +internal void lnk_symbol_set_search_type(LNK_Symbol *symbol, LNK_SymbolSearchType search_type); internal int lnk_obj_symbol_ref_is_before(void *raw_a, void *raw_b); internal int lnk_obj_symbol_ref_ptr_is_before(void *raw_a, void *raw_b); @@ -138,4 +155,3 @@ internal B32 lnk_resolve_weak_symbol(LNK_SymbolTable *symtab, LNK_ObjSymbolRef s internal B32 lnk_resolve_symbol(LNK_SymbolTable *symtab, LNK_ObjSymbolRef symbol, LNK_ObjSymbolRef *symbol_out); internal void lnk_replace_weak_with_default_symbols(TP_Context *tp, LNK_SymbolTable *symtab); - diff --git a/src/linker/pdb_ext/pdb_builder.c b/src/linker/pdb_ext/pdb_builder.c index be696012..06c1ddb3 100644 --- a/src/linker/pdb_ext/pdb_builder.c +++ b/src/linker/pdb_ext/pdb_builder.c @@ -1805,6 +1805,274 @@ gsi_record_sort_by_sc(PDB_GsiSortRecord *arr, U64 count) ProfEnd(); } +#define PDB_PSI_ADDR_MAP_RADIX_BITS 11 +#define PDB_PSI_ADDR_MAP_RADIX_COUNT (1 << PDB_PSI_ADDR_MAP_RADIX_BITS) +#define PDB_PSI_ADDR_MAP_RADIX_MIN_COUNT (1 << 17) + +typedef struct PDB_PsiAddrMapSortRecord +{ + U64 key; + PDB_GsiSortRecord *record; +} PDB_PsiAddrMapSortRecord; + +typedef struct PDB_PsiAddrMapSortTask +{ + Rng1U64 *ranges; + PDB_GsiSortRecord *gsi_record_arr; + PDB_PsiAddrMapSortRecord *src; + PDB_PsiAddrMapSortRecord *dst; + U64 *counts; + U32 digit_shift; + U32 digit_count; +} PDB_PsiAddrMapSortTask; + +typedef struct PDB_PsiAddrMapNameSortTask +{ + PDB_PsiAddrMapSortRecord *record_arr; + Rng1U64 *run_arr; + Rng1U64 *batch_arr; +} PDB_PsiAddrMapNameSortTask; + +force_inline U64 +psi_addr_map_sort_key(PDB_GsiSortRecord *record) +{ + U64 key = ((U64)record->isect_off.isect << 32) | record->isect_off.off; + return key; +} + +force_inline int +psi_addr_map_name_compar_is_before(void *raw_a, void *raw_b) +{ + PDB_PsiAddrMapSortRecord *a = raw_a; + PDB_PsiAddrMapSortRecord *b = raw_b; + int is_before = str8_compar_case_sensitive(&a->record->name, &b->record->name) < 0; + return is_before; +} + +internal +THREAD_POOL_TASK_FUNC(psi_addr_map_radix_init_task) +{ + ProfBeginFunction(); + + PDB_PsiAddrMapSortTask *task = raw_task; + Rng1U64 range = task->ranges[task_id]; + for EachInRange(i, range) { + PDB_GsiSortRecord *record = &task->gsi_record_arr[i]; + task->src[i].key = psi_addr_map_sort_key(record); + task->src[i].record = record; + } + + ProfEnd(); +} + +internal +THREAD_POOL_TASK_FUNC(psi_addr_map_radix_histogram_task) +{ + ProfBeginFunction(); + + PDB_PsiAddrMapSortTask *task = raw_task; + Rng1U64 range = task->ranges[task_id]; + U64 *counts = task->counts + task_id * PDB_PSI_ADDR_MAP_RADIX_COUNT; + U32 mask = task->digit_count - 1; + + MemoryZeroTyped(counts, task->digit_count); + for EachInRange(i, range) { + U64 digit = (task->src[i].key >> task->digit_shift) & mask; + counts[digit] += 1; + } + + ProfEnd(); +} + +internal +THREAD_POOL_TASK_FUNC(psi_addr_map_radix_sort_task) +{ + ProfBeginFunction(); + + PDB_PsiAddrMapSortTask *task = raw_task; + Rng1U64 range = task->ranges[task_id]; + U64 *offsets = task->counts + task_id * PDB_PSI_ADDR_MAP_RADIX_COUNT; + U32 mask = task->digit_count - 1; + + for EachInRange(i, range) { + PDB_PsiAddrMapSortRecord record = task->src[i]; + U64 digit = (record.key >> task->digit_shift) & mask; + task->dst[offsets[digit]++] = record; + } + + ProfEnd(); +} + +force_inline U64 +psi_addr_map_name_sort_work(Rng1U64 run) +{ + U64 count = dim_1u64(run); + U64 work = count * (64 - clz64(count)); + return work; +} + +internal +THREAD_POOL_TASK_FUNC(psi_addr_map_name_sort_task) +{ + ProfBeginFunction(); + + PDB_PsiAddrMapNameSortTask *task = raw_task; + Rng1U64 batch = task->batch_arr[task_id]; + for EachInRange(run_idx, batch) { + Rng1U64 run = task->run_arr[run_idx]; + radsort(task->record_arr + run.min, dim_1u64(run), psi_addr_map_name_compar_is_before); + } + + ProfEnd(); +} + +internal U32 * +psi_addr_map_from_gsi_records(TP_Context *tp, Arena *arena, PDB_GsiSortRecord *gsi_record_arr, U64 count) +{ + ProfBeginFunction(); + Temp scratch = scratch_begin(&arena, 1); + + PDB_PsiAddrMapSortRecord *radix_record_arr = 0; + + ProfBegin("Sort"); + if (count >= PDB_PSI_ADDR_MAP_RADIX_MIN_COUNT) { + PDB_PsiAddrMapSortTask task = {0}; + task.ranges = tp_divide_work(scratch.arena, count, tp->worker_count); + task.gsi_record_arr = gsi_record_arr; + task.src = push_array_no_zero(scratch.arena, PDB_PsiAddrMapSortRecord, count); + task.dst = push_array_no_zero(scratch.arena, PDB_PsiAddrMapSortRecord, count); + task.counts = push_array_no_zero(scratch.arena, U64, tp->worker_count * PDB_PSI_ADDR_MAP_RADIX_COUNT); + + tp_for_parallel_prof(tp, 0, tp->worker_count, psi_addr_map_radix_init_task, &task, "psi_addr_map_radix_init_task"); + + ProfBegin("Radix"); + U32 digit_shift[] = { 0, 11, 22, 32, 43 }; + U32 digit_count[] = { 1 << 11, 1 << 11, 1 << 10, 1 << 11, 1 << 5 }; + for EachIndex(pass_idx, ArrayCount(digit_shift)) { + ProfBeginV("Pass %llu", pass_idx); + + task.digit_shift = digit_shift[pass_idx]; + task.digit_count = digit_count[pass_idx]; + + tp_for_parallel_prof(tp, 0, tp->worker_count, psi_addr_map_radix_histogram_task, &task, "psi_addr_map_radix_histogram_task"); + + U64 cursor = 0; + for EachIndex(digit_idx, task.digit_count) { + for EachIndex(task_id, tp->worker_count) { + U64 *count_ptr = &task.counts[task_id * PDB_PSI_ADDR_MAP_RADIX_COUNT + digit_idx]; + U64 digit_item_count = *count_ptr; + *count_ptr = cursor; + cursor += digit_item_count; + } + } + Assert(cursor == count); + + tp_for_parallel_prof(tp, 0, tp->worker_count, psi_addr_map_radix_sort_task, &task, "psi_addr_map_radix_sort_task"); + Swap(PDB_PsiAddrMapSortRecord *, task.src, task.dst); + + ProfEnd(); + } + radix_record_arr = task.src; + ProfEnd(); + + ProfBegin("Get Name Run Count"); + U64 name_run_count = 0; + U64 total_name_sort_work = 0; + for (U64 run_first = 0; run_first < count;) { + U64 run_opl = run_first + 1; + while (run_opl < count && radix_record_arr[run_opl].key == radix_record_arr[run_first].key) { + run_opl += 1; + } + if (run_opl - run_first > 1) { + Rng1U64 run = rng_1u64(run_first, run_opl); + name_run_count += 1; + total_name_sort_work += psi_addr_map_name_sort_work(run); + } + run_first = run_opl; + } + ProfEnd(); + + if (name_run_count) { + ProfBegin("Name Runs"); + + PDB_PsiAddrMapNameSortTask name_task = {0}; + name_task.record_arr = radix_record_arr; + name_task.run_arr = push_array_no_zero(scratch.arena, Rng1U64, name_run_count); + + ProfBegin("Gather Run Ranges"); + U64 name_run_idx = 0; + for (U64 run_first = 0; run_first < count;) { + U64 run_opl = run_first + 1; + while (run_opl < count && radix_record_arr[run_opl].key == radix_record_arr[run_first].key) { + run_opl += 1; + } + if (run_opl - run_first > 1) { + name_task.run_arr[name_run_idx++] = rng_1u64(run_first, run_opl); + } + run_first = run_opl; + } + Assert(name_run_idx == name_run_count); + ProfEnd(); + + ProfBegin("Batching"); + + U64 batch_count = Min(name_run_count, (U64)tp->worker_count * 4); + name_task.batch_arr = push_array_no_zero(scratch.arena, Rng1U64, batch_count); + + U64 run_idx = 0; + U64 remaining_work = total_name_sort_work; + + for EachIndex(batch_idx, batch_count) { + U64 batch_first = run_idx; + U64 batches_left = batch_count - batch_idx; + U64 run_opl_limit = name_run_count - (batches_left - 1); + U64 target_work = CeilIntegerDiv(remaining_work, batches_left); + U64 batch_work = 0; + while (run_idx < run_opl_limit) { + U64 run_work = psi_addr_map_name_sort_work(name_task.run_arr[run_idx]); + if (run_idx > batch_first && batch_work + run_work > target_work) { + break; + } + batch_work += run_work; + run_idx += 1; + if (batch_work >= target_work) { + break; + } + } + name_task.batch_arr[batch_idx] = rng_1u64(batch_first, run_idx); + remaining_work -= batch_work; + } + Assert(run_idx == name_run_count); + + ProfEnd(); + + tp_for_parallel_prof(tp, 0, batch_count, psi_addr_map_name_sort_task, &name_task, "psi_addr_map_name_sort_task"); + + ProfEnd(); + } + } else { + gsi_record_sort_by_sc(gsi_record_arr, count); + } + ProfEnd(); + + ProfBegin("Offset Fill"); + U32 *addr_map = push_array_no_zero(arena, U32, count); + if (radix_record_arr) { + for EachIndex(i, count) { + addr_map[i] = safe_cast_u32(radix_record_arr[i].record->offset); + } + } else { + for EachIndex(i, count) { + addr_map[i] = safe_cast_u32(gsi_record_arr[i].offset); + } + } + ProfEnd(); + + scratch_end(scratch); + ProfEnd(); + return addr_map; +} + internal THREAD_POOL_TASK_FUNC(gsi_size_buckets_task) { @@ -2207,19 +2475,10 @@ psi_build(TP_Context *tp, PDB_PsiContext *psi, MSF_Context *msf, MSF_StreamNumbe PDB_GsiBuildResult gsi_build = gsi_build_ex(tp, scratch.arena, psi->gsi, symbol_data_base, /* is_pub32: */ 1, msf->page_size); ProfBegin("Address Map"); - - ProfBegin("Sort"); - gsi_record_sort_by_sc(gsi_build.sort_record_arr, gsi_build.hash_record_count); - ProfEnd(); - - ProfBegin("Offset Fill"); + U64 addr_map_count = gsi_build.hash_record_count; U64 addr_map_size = addr_map_count * sizeof(U32); - U32 *addr_map = push_array_no_zero(scratch.arena, U32, addr_map_count); - for (U64 i = 0; i < addr_map_count; i += 1) { - addr_map[i] = gsi_build.sort_record_arr[i].offset; - } - ProfEnd(); + U32 *addr_map = psi_addr_map_from_gsi_records(tp, scratch.arena, gsi_build.sort_record_arr, addr_map_count); ProfEnd(); @@ -2263,24 +2522,24 @@ psi_push(PDB_PsiContext *psi, CV_Pub32Flags flags, U32 offset, U16 isect, String //////////////////////////////// internal void -dbi_sec_contrib_list_push_node(PDB_DbiSectionContribList *list, PDB_DbiSectionContribNode *node) +dbi_sec_contrib_list_push_node(PDB_DbiSCList *list, PDB_DbiSCNode *node) { node->next = 0; SLLQueuePush(list->first, list->last, node); list->count += 1; } -internal PDB_DbiSectionContribNode * -dbi_sec_contrib_list_push(Arena *arena, PDB_DbiSectionContribList *list) +internal PDB_DbiSCNode * +dbi_sec_contrib_list_push(Arena *arena, PDB_DbiSCList *list) { - PDB_DbiSectionContribNode *node = push_array_no_zero(arena, PDB_DbiSectionContribNode, 1); + PDB_DbiSCNode *node = push_array_no_zero(arena, PDB_DbiSCNode, 1); node->next = 0; dbi_sec_contrib_list_push_node(list, node); return node; } internal void -dbi_sec_list_concat_arr(PDB_DbiSectionContribList *list, U64 count, PDB_DbiSectionContribList *to_concat) +dbi_sec_list_concat_arr(PDB_DbiSCList *list, U64 count, PDB_DbiSCList *to_concat) { SLLConcatInPlaceArray(list, to_concat, count); } @@ -2405,14 +2664,14 @@ dbi_open_module_info(Arena *arena, MSF_Context *msf, MSF_StreamNumber sn, PDB_Db return list; } -internal PDB_DbiSectionContribList +internal PDB_DbiSCList dbi_open_sec_contrib(Arena *arena, MSF_Context *msf, MSF_StreamNumber sn, PDB_DbiHeader *dbi_header) { ProfBeginFunction(); - PDB_DbiSectionContribList sec_contrib = {0}; + PDB_DbiSCList sec_contrib = {0}; - if (dbi_header->sec_con_size > sizeof(PDB_DbiSectionContrib)) { + if (dbi_header->sec_con_size > sizeof(PDB_DbiSC)) { Temp scratch = scratch_begin(&arena, 1); // seek to start of section contrib info @@ -2420,25 +2679,25 @@ dbi_open_sec_contrib(Arena *arena, MSF_Context *msf, MSF_StreamNumber sn, PDB_Db msf_stream_seek(msf, sn, sec_con_pos); // read header - PDB_DbiSectionContribVersion version = 0; + PDB_DbiSCVersion version = 0; msf_stream_read_struct(msf, sn, &version); // parse contrib items switch (version) { - case PDB_DbiSectionContribVersion_1: { - U64 contrib_count = dbi_header->sec_con_size / sizeof(PDB_DbiSectionContrib); - PDB_DbiSectionContrib *src_contrib_array = push_array(scratch.arena, PDB_DbiSectionContrib, contrib_count); + case PDB_DbiSCVersion_1: { + U64 contrib_count = dbi_header->sec_con_size / sizeof(PDB_DbiSC); + PDB_DbiSC *src_contrib_array = push_array(scratch.arena, PDB_DbiSC, contrib_count); MSF_UInt sec_con_read = msf_stream_read_array(msf, sn, &src_contrib_array[0], contrib_count); Assert(sec_con_read == sizeof(src_contrib_array[0]) * contrib_count); - PDB_DbiSectionContribNode *dst_contrib_array = push_array_no_zero(arena, PDB_DbiSectionContribNode, contrib_count); + PDB_DbiSCNode *dst_contrib_array = push_array_no_zero(arena, PDB_DbiSCNode, contrib_count); for (U64 icontrib = 0; icontrib < contrib_count; icontrib += 1) { dst_contrib_array[icontrib].next = 0; dst_contrib_array[icontrib].data = src_contrib_array[icontrib]; dbi_sec_contrib_list_push_node(&sec_contrib, &dst_contrib_array[icontrib]); } } break; - case PDB_DbiSectionContribVersion_2: { + case PDB_DbiSCVersion_2: { NotImplemented; } break; default: Assert(!"unknown section contrib version"); break; @@ -2595,7 +2854,7 @@ dbi_build_module_info(Arena *arena, PDB_DbiContext *dbi, MSF_Context *msf) #if 0 int -dbi_sc_compar(const PDB_DbiSectionContrib *a, const PDB_DbiSectionContrib *b) +dbi_sc_compar(const PDB_DbiSC *a, const PDB_DbiSC *b) { #if 0 int cmp = 0; @@ -2621,11 +2880,128 @@ dbi_sc_compar(const PDB_DbiSectionContrib *a, const PDB_DbiSectionContrib *b) } #endif +typedef struct +{ + PDB_DbiSC *src; + PDB_DbiSC *dst; + Rng1U64 *range_arr; + U32 *count_arr; + U64 digit_count; + U64 shift; + B32 is_sec_pass; +} LNK_DbiScRadixPass; + +internal U64 +lnk_dbi_sc_radix_digit(LNK_DbiScRadixPass *pass, PDB_DbiSC *sc) +{ + if (pass->is_sec_pass) { + return sc->base.sec; + } + return (sc->base.sec_off >> pass->shift) % pass->digit_count; +} + +typedef struct +{ + PDB_DbiSC *src; + PDB_DbiSC *dst; + Rng1U64 *range_arr; +} LNK_DbiScCopy; + +internal +THREAD_POOL_TASK_FUNC(lnk_dbi_sc_copy_task) +{ + LNK_DbiScCopy *copy = raw_task; + Rng1U64 range = copy->range_arr[task_id]; + MemoryCopy(copy->dst + range.min, copy->src + range.min, sizeof(copy->src[0]) * dim_1u64(range)); +} + +internal +THREAD_POOL_TASK_FUNC(lnk_dbi_sc_radix_histo_task) +{ + LNK_DbiScRadixPass *pass = raw_task; + U32 *count = pass->count_arr + task_id * pass->digit_count; + Rng1U64 range = pass->range_arr[task_id]; + for EachInRange(i, range) { + count[lnk_dbi_sc_radix_digit(pass, &pass->src[i])] += 1; + } +} + +internal +THREAD_POOL_TASK_FUNC(lnk_dbi_sc_radix_scatter_task) +{ + LNK_DbiScRadixPass *pass = raw_task; + U32 *count = pass->count_arr + task_id * pass->digit_count; + Rng1U64 range = pass->range_arr[task_id]; + for EachInRange(i, range) { + PDB_DbiSC *sc = &pass->src[i]; + pass->dst[count[lnk_dbi_sc_radix_digit(pass, sc)]++] = *sc; + } +} + internal void -lnk_radix_sort_dbi_sc_array(PDB_DbiSectionContrib *arr, U64 sc_count, U64 sect_count) +lnk_dbi_sc_radix_pass_parallel(TP_Context *tp, Arena *arena, U64 task_count, Rng1U64 *range_arr, PDB_DbiSC **src, PDB_DbiSC **dst, U64 digit_count, U64 shift, B32 is_sec_pass) +{ + Temp temp = temp_begin(arena); + + LNK_DbiScRadixPass pass = { + .src = *src, + .dst = *dst, + .range_arr = range_arr, + .count_arr = push_array(temp.arena, U32, task_count * digit_count), + .digit_count = digit_count, + .shift = shift, + .is_sec_pass = is_sec_pass, + }; + + tp_for_parallel(tp, 0, task_count, lnk_dbi_sc_radix_histo_task, &pass); + + U32 cursor = 0; + for EachIndex(digit, digit_count) { + for EachIndex(worker_idx, task_count) { + U32 *slot = &pass.count_arr[worker_idx * digit_count + digit]; + U32 count = *slot; + *slot = cursor; + cursor += count; + } + } + + tp_for_parallel(tp, 0, task_count, lnk_dbi_sc_radix_scatter_task, &pass); + temp_end(temp); + + Swap(PDB_DbiSC *, *src, *dst); +} + +internal void +lnk_radix_sort_dbi_sc_array(TP_Context *tp, PDB_DbiSC *arr, U64 sc_count, U64 sect_count) { ProfBeginFunction(); + if (tp != 0 && tp->worker_count > 1 && sc_count >= KB(64)) { + Temp scratch = scratch_begin(0, 0); + + PDB_DbiSC *src = arr; + PDB_DbiSC *dst = push_array_no_zero(scratch.arena, PDB_DbiSC, sc_count); + U64 task_count = tp->worker_count; + Rng1U64 *range_arr = tp_divide_work(scratch.arena, sc_count, task_count); + + lnk_dbi_sc_radix_pass_parallel(tp, scratch.arena, task_count, range_arr, &src, &dst, 256, 0, 0); + lnk_dbi_sc_radix_pass_parallel(tp, scratch.arena, task_count, range_arr, &src, &dst, 256, 8, 0); + lnk_dbi_sc_radix_pass_parallel(tp, scratch.arena, task_count, range_arr, &src, &dst, 256, 16, 0); + lnk_dbi_sc_radix_pass_parallel(tp, scratch.arena, task_count, range_arr, &src, &dst, 256, 24, 0); + lnk_dbi_sc_radix_pass_parallel(tp, scratch.arena, task_count, range_arr, &src, &dst, sect_count, 0, 1); + + LNK_DbiScCopy copy = { .src = src, .dst = arr, .range_arr = range_arr }; + tp_for_parallel(tp, 0, task_count, lnk_dbi_sc_copy_task, ©); + + scratch_end(scratch); + ProfEnd(); + return; + } + + // + // on small inputs the serial path is 4x faster + // + #if 1 // faster but uses more memory # define RADIX_BIT_COUNT 16 @@ -2638,9 +3014,9 @@ lnk_radix_sort_dbi_sc_array(PDB_DbiSectionContrib *arr, U64 sc_count, U64 sect_c Temp scratch = scratch_begin(0,0); - PDB_DbiSectionContrib *temp_arr = push_array_no_zero(scratch.arena, PDB_DbiSectionContrib, sc_count); - PDB_DbiSectionContrib *src_arr = arr; - PDB_DbiSectionContrib *dst_arr = temp_arr; + PDB_DbiSC *temp_arr = push_array_no_zero(scratch.arena, PDB_DbiSC, sc_count); + PDB_DbiSC *src_arr = arr; + PDB_DbiSC *dst_arr = temp_arr; ProfBegin("Count Memzero"); U32 count_8lo[256]; MemoryZeroArray(count_8lo); @@ -2651,7 +3027,7 @@ lnk_radix_sort_dbi_sc_array(PDB_DbiSectionContrib *arr, U64 sc_count, U64 sect_c ProfBegin("Histogram"); for (U64 i = 0; i < sc_count; i += 1) { - PDB_DbiSectionContrib *sc = src_arr + i; + PDB_DbiSC *sc = src_arr + i; count_arr[sc->base.sec] += 1; U64 digit_8lo = (sc->base.sec_off >> 0) % ArrayCount(count_8lo); @@ -2693,7 +3069,7 @@ lnk_radix_sort_dbi_sc_array(PDB_DbiSectionContrib *arr, U64 sc_count, U64 sect_c ProfBegin("Order 8 Lo"); for (U64 i = 0; i < sc_count; i += 1) { - PDB_DbiSectionContrib *sc = &src_arr[i]; + PDB_DbiSC *sc = &src_arr[i]; U64 digit = (sc->base.sec_off >> 0) % ArrayCount(count_8lo); dst_arr[count_8lo[digit]++] = *sc; } @@ -2701,7 +3077,7 @@ lnk_radix_sort_dbi_sc_array(PDB_DbiSectionContrib *arr, U64 sc_count, U64 sect_c ProfBegin("Order 8 Hi"); for (U64 i = 0; i < sc_count; i += 1) { - PDB_DbiSectionContrib *sc = &dst_arr[i]; + PDB_DbiSC *sc = &dst_arr[i]; U64 digit = (sc->base.sec_off >> 8) % ArrayCount(count_8hi); src_arr[count_8hi[digit]++] = *sc; } @@ -2709,7 +3085,7 @@ lnk_radix_sort_dbi_sc_array(PDB_DbiSectionContrib *arr, U64 sc_count, U64 sect_c ProfBegin("Order 16"); for (U64 i = 0; i < sc_count; i += 1) { - PDB_DbiSectionContrib *sc = &src_arr[i]; + PDB_DbiSC *sc = &src_arr[i]; U64 digit = (sc->base.sec_off >> 16) % ArrayCount(count_16); dst_arr[count_16[digit]++] = *sc; } @@ -2731,7 +3107,7 @@ lnk_radix_sort_dbi_sc_array(PDB_DbiSectionContrib *arr, U64 sc_count, U64 sect_c count_arr[0] = 0; for (U64 i = 0; i < sc_count; i += 1) { - PDB_DbiSectionContrib *sc = dst_arr + i; + PDB_DbiSC *sc = dst_arr + i; src_arr[count_arr[sc->base.sec]++] = *sc; } @@ -2754,30 +3130,52 @@ lnk_radix_sort_dbi_sc_array(PDB_DbiSectionContrib *arr, U64 sc_count, U64 sect_c } internal String8List -dbi_build_sec_con(Arena *arena, PDB_DbiContext *dbi) +dbi_build_sec_con(Arena *arena, TP_Context *tp, PDB_DbiContext *dbi) { ProfBeginFunction(); - PDB_DbiSectionContribVersion *version = push_array(arena, PDB_DbiSectionContribVersion, 1); - *version = PDB_DbiSectionContribVersion_1; + PDB_DbiSCVersion *version = push_array(arena, PDB_DbiSCVersion, 1); + *version = PDB_DbiSCVersion_1; // push section contribs V1 ProfBegin("Push sect contribs [Count %llu]", dbi->sec_contrib_list.count); - PDB_DbiSectionContrib *sc_array = push_array_no_zero(arena, PDB_DbiSectionContrib, dbi->sec_contrib_list.count); - PDB_DbiSectionContrib *dst = &sc_array[0]; - for (PDB_DbiSectionContribNode *src = dbi->sec_contrib_list.first; src != 0; src = src->next, dst += 1) { + PDB_DbiSC *sc_array = push_array_no_zero(arena, PDB_DbiSC, dbi->sec_contrib_list.count); + PDB_DbiSC *dst = &sc_array[0]; + for (PDB_DbiSCNode *src = dbi->sec_contrib_list.first; src != 0; src = src->next, dst += 1) { *dst = src->data; } ProfEnd(); // sort section contribs so they are binary searchable - lnk_radix_sort_dbi_sc_array(sc_array, dbi->sec_contrib_list.count, dbi->section_list.count + 1); - + lnk_radix_sort_dbi_sc_array(tp, sc_array, dbi->sec_contrib_list.count, dbi->section_list.count + 1); + + // DBI contribution maps an address range to its module, so adjacent same-module runs can share one record + ProfBegin("Coalesce sect contribs"); + U64 sc_count = 0; + for EachIndex(read_idx, dbi->sec_contrib_list.count) { + PDB_DbiSC *current = &sc_array[read_idx]; + if (sc_count > 0) { + PDB_DbiSC *previous = &sc_array[sc_count - 1]; + if (previous->base.sec == current->base.sec && + previous->base.mod == current->base.mod && + previous->base.flags == current->base.flags) { + U64 current_end = (U64)current->base.sec_off + (U64)current->base.size; + U64 previous_end = (U64)previous->base.sec_off + (U64)previous->base.size; + if (current_end > previous_end) { + previous->base.size = (U32)(current_end - (U64)previous->base.sec_off); + } + continue; + } + } + sc_array[sc_count++] = *current; + } + ProfEnd(); + // push section contrib info ProfBegin("List Push"); String8List sec_con_list = {0}; str8_list_push(arena, &sec_con_list, str8((U8*)version, sizeof(*version))); - str8_list_push(arena, &sec_con_list, str8((U8*)sc_array, sizeof(sc_array[0])*dbi->sec_contrib_list.count)); + str8_list_push(arena, &sec_con_list, str8((U8*)sc_array, sizeof(sc_array[0])*sc_count)); ProfEnd(); ProfEnd(); @@ -2862,7 +3260,7 @@ dbi_build(TP_Context *tp, PDB_DbiContext *dbi, MSF_Context *msf, MSF_StreamNumbe ProfBegin("Build"); String8List module_info_list = dbi_build_module_info(scratch.arena, dbi, msf); - String8List sec_con_list = dbi_build_sec_con(scratch.arena, dbi); + String8List sec_con_list = dbi_build_sec_con(scratch.arena, tp, dbi); String8List sec_map_list = dbi_build_sec_map(scratch.arena, dbi); String8List file_info_list = dbi_build_file_info(scratch.arena, tp, dbi->module_list, string_ht); String8List dbg_header_list = dbi_build_dbg_header(scratch.arena, dbi, msf); @@ -2956,7 +3354,7 @@ dbi_module_push_section_contrib(PDB_DbiContext *dbi, { ProfBeginFunction(); - PDB_DbiSectionContrib sc; + PDB_DbiSC sc; sc.base.sec = safe_cast_u16(isect_off.isect); sc.base.sec_off = isect_off.off; sc.base.size = size; @@ -2965,7 +3363,7 @@ dbi_module_push_section_contrib(PDB_DbiContext *dbi, sc.data_crc = data_crc; sc.reloc_crc = reloc_crc; - PDB_DbiSectionContribNode *node = push_array_no_zero(dbi->arena, PDB_DbiSectionContribNode, 1); + PDB_DbiSCNode *node = push_array_no_zero(dbi->arena, PDB_DbiSCNode, 1); node->data = sc; dbi_sec_contrib_list_push_node(&dbi->sec_contrib_list, node); @@ -3159,31 +3557,64 @@ pdb_build_gsi_psi(TP_Context *tp, PDB_Context *pdb) } internal void -pdb_build(TP_Context *tp, TP_Arena *pool_temp, PDB_Context *pdb, CV_StringHashTable string_ht, B32 build_gsi, B32 is_stripped) +pdb_build_types(TP_Context *tp, PDB_Context *pdb, PDB_BuildHooks *hooks) { ProfBeginFunction(); - + PDB_InfoContext *info = pdb->info; PDB_StringTable *strtab = &info->strtab; - PDB_DbiContext *dbi = pdb->dbi; PDB_TypeServer *tpi = pdb->type_servers[CV_TypeIndexSource_TPI]; PDB_TypeServer *ipi = pdb->type_servers[CV_TypeIndexSource_IPI]; - + pdb_type_server_build(tp, tpi, strtab, pdb->msf, PDB_FixedStream_Tpi); + if (hooks && hooks->stream_finalize) { + hooks->stream_finalize(hooks->user_data, pdb->msf, PDB_FixedStream_Tpi); + hooks->stream_finalize(hooks->user_data, pdb->msf, tpi->hash_sn); + } if (info->flags & PDB_FeatureFlag_HAS_ID_STREAM) { pdb_type_server_build(tp, ipi, strtab, pdb->msf, PDB_FixedStream_Ipi); + if (hooks && hooks->stream_finalize) { + hooks->stream_finalize(hooks->user_data, pdb->msf, PDB_FixedStream_Ipi); + hooks->stream_finalize(hooks->user_data, pdb->msf, ipi->hash_sn); + } } + ProfEnd(); +} + +internal void +pdb_build_dbi_info(TP_Context *tp, PDB_Context *pdb, CV_StringHashTable string_ht, B32 build_gsi, B32 is_stripped, PDB_BuildHooks *hooks) +{ + ProfBeginFunction(); + if (build_gsi) { pdb_build_gsi_psi(tp, pdb); } dbi_build(tp, pdb->dbi, pdb->msf, PDB_FixedStream_Dbi, string_ht, is_stripped); + if (hooks && hooks->stream_finalize) { + hooks->stream_finalize(hooks->user_data, pdb->msf, PDB_FixedStream_Dbi); + for EachElement(i, pdb->dbi->dbg_streams) { + hooks->stream_finalize(hooks->user_data, pdb->msf, pdb->dbi->dbg_streams[i]); + } + } pdb_info_build(pdb->info, pdb->msf, PDB_FixedStream_Info); + if (hooks && hooks->stream_finalize) { + hooks->stream_finalize(hooks->user_data, pdb->msf, PDB_FixedStream_Info); + } ProfEnd(); } +internal void +pdb_build(TP_Context *tp, TP_Arena *pool_temp, PDB_Context *pdb, CV_StringHashTable string_ht, B32 build_gsi, B32 is_stripped, PDB_BuildHooks *hooks) +{ + ProfBeginFunction(); + pdb_build_types(tp, pdb, hooks); + pdb_build_dbi_info(tp, pdb, string_ht, build_gsi, is_stripped, hooks); + ProfEnd(); +} + //////////////////////////////// internal String8 diff --git a/src/linker/pdb_ext/pdb_builder.h b/src/linker/pdb_ext/pdb_builder.h index b4d0909e..ec0b749a 100644 --- a/src/linker/pdb_ext/pdb_builder.h +++ b/src/linker/pdb_ext/pdb_builder.h @@ -238,7 +238,7 @@ typedef struct PDB_DbiModule struct PDB_DbiModule *next; MSF_StreamNumber sn; CV_ModIndex imod; - PDB_DbiSectionContrib first_sc; + PDB_DbiSC first_sc; U64 sym_data_size; U64 c11_data_size; U64 c13_data_size; @@ -255,18 +255,18 @@ typedef struct PDB_DbiModuleList U64 count; } PDB_DbiModuleList; -typedef struct PDB_DbiSectionContribNode +typedef struct PDB_DbiSCNode { - struct PDB_DbiSectionContribNode *next; - PDB_DbiSectionContrib data; -} PDB_DbiSectionContribNode; + struct PDB_DbiSCNode *next; + PDB_DbiSC data; +} PDB_DbiSCNode; -typedef struct PDB_DbiSectionContribList +typedef struct PDB_DbiSCList { - PDB_DbiSectionContribNode *first; - PDB_DbiSectionContribNode *last; + PDB_DbiSCNode *first; + PDB_DbiSCNode *last; U64 count; -} PDB_DbiSectionContribList; +} PDB_DbiSCList; typedef struct PDB_DbiSectionNode { @@ -290,7 +290,7 @@ typedef struct PDB_DbiContext MSF_StreamNumber publics_sn; MSF_StreamNumber symbols_sn; PDB_DbiModuleList module_list; - PDB_DbiSectionContribList sec_contrib_list; + PDB_DbiSCList sec_contrib_list; PDB_DbiSectionList section_list; PDB_StringTable ec_names; MSF_StreamNumber dbg_streams[PDB_DbiStream_COUNT]; @@ -310,6 +310,13 @@ typedef struct PDB_Context PDB_TypeServer *type_servers[CV_TypeIndexSource_COUNT]; } PDB_Context; +typedef void PDB_StreamFinalizeFunc(void *user_data, MSF_Context *msf, MSF_StreamNumber sn); +typedef struct PDB_BuildHooks +{ + PDB_StreamFinalizeFunc *stream_finalize; + void *user_data; +} PDB_BuildHooks; + //////////////////////////////// typedef struct @@ -334,7 +341,9 @@ typedef struct internal PDB_Context * pdb_alloc(U64 page_size, COFF_MachineType machine, COFF_TimeStamp time_stamp, U32 age, Guid guid); internal void pdb_release(PDB_Context *pdb); -internal void pdb_build(TP_Context *tp, TP_Arena *pool_temp, PDB_Context *pdb, CV_StringHashTable string_ht, B32 build_gsi, B32 is_stripped); +internal void pdb_build_types(TP_Context *tp, PDB_Context *pdb, PDB_BuildHooks *hooks); +internal void pdb_build_dbi_info(TP_Context *tp, PDB_Context *pdb, CV_StringHashTable string_ht, B32 build_gsi, B32 is_stripped, PDB_BuildHooks *hooks); +internal void pdb_build(TP_Context *tp, TP_Arena *pool_temp, PDB_Context *pdb, CV_StringHashTable string_ht, B32 build_gsi, B32 is_stripped, PDB_BuildHooks *hooks); internal void pdb_set_machine(PDB_Context *pdb, COFF_MachineType machine); internal void pdb_set_guid(PDB_Context *pdb, Guid guid); internal void pdb_set_time_stamp(PDB_Context *pdb, COFF_TimeStamp time_stamp); @@ -384,21 +393,21 @@ internal CV_SymbolNode * psi_push(PDB_PsiContext *psi, CV_Pub32Flags flags, U32 //////////////////////////////// // DBI -internal PDB_DbiContext * dbi_alloc(COFF_MachineType machine, U32 age); -internal void dbi_build(TP_Context *tp, PDB_DbiContext *dbi, MSF_Context *msf, MSF_StreamNumber dbi_sn, CV_StringHashTable string_ht, B32 is_stripped); -internal void dbi_release(PDB_DbiContext *dbi); -internal PDB_DbiModule * dbi_push_module(PDB_DbiContext *dbi, String8 obj_path, String8 lib_path); -internal String8 dbi_module_read_symbol_data(Arena *arena, MSF_Context *msf, PDB_DbiModule *mod); -internal String8 dbi_module_read_c11_data(Arena *arena, MSF_Context *msf, PDB_DbiModule *mod); -internal String8 dbi_module_read_c13_data(Arena *arena, MSF_Context *msf, PDB_DbiModule *mod); -internal void dbi_module_push_section_contrib(PDB_DbiContext *dbi, PDB_DbiModule *mod, ISectOff isect_off, U32 size, U32 data_crc, U32 reloc_crc, COFF_SectionFlags flags); -internal String8List * dbi_open_file_info(Arena *arena, MSF_Context *msf, MSF_StreamNumber sn, PDB_DbiHeader *dbi_header); -internal PDB_DbiModuleList dbi_open_module_info(Arena *arena, MSF_Context *msf, MSF_StreamNumber sn, PDB_DbiHeader *dbi_header, String8List *file_info); -internal PDB_DbiSectionContribList dbi_open_sec_contrib(Arena *arena, MSF_Context *msf, MSF_StreamNumber sn, PDB_DbiHeader *dbi_header); -internal PDB_StringTable dbi_open_ec_names(Arena *arena, MSF_Context *msf, MSF_StreamNumber sn, PDB_DbiHeader *dbi_header); -internal void dbi_open_dbg_streams(MSF_StreamNumber *dbg_streams, MSF_Context *msf, MSF_StreamNumber sn, PDB_DbiHeader *dbi_header); -internal PDB_DbiSectionList dbi_open_section_headers(Arena *arena, MSF_Context *msf, MSF_StreamNumber sn); -internal void dbi_build_section_header_stream(PDB_DbiContext *dbi, MSF_Context *msf, MSF_StreamNumber sn); +internal PDB_DbiContext * dbi_alloc(COFF_MachineType machine, U32 age); +internal void dbi_build(TP_Context *tp, PDB_DbiContext *dbi, MSF_Context *msf, MSF_StreamNumber dbi_sn, CV_StringHashTable string_ht, B32 is_stripped); +internal void dbi_release(PDB_DbiContext *dbi); +internal PDB_DbiModule * dbi_push_module(PDB_DbiContext *dbi, String8 obj_path, String8 lib_path); +internal String8 dbi_module_read_symbol_data(Arena *arena, MSF_Context *msf, PDB_DbiModule *mod); +internal String8 dbi_module_read_c11_data(Arena *arena, MSF_Context *msf, PDB_DbiModule *mod); +internal String8 dbi_module_read_c13_data(Arena *arena, MSF_Context *msf, PDB_DbiModule *mod); +internal void dbi_module_push_section_contrib(PDB_DbiContext *dbi, PDB_DbiModule *mod, ISectOff isect_off, U32 size, U32 data_crc, U32 reloc_crc, COFF_SectionFlags flags); +internal String8List * dbi_open_file_info(Arena *arena, MSF_Context *msf, MSF_StreamNumber sn, PDB_DbiHeader *dbi_header); +internal PDB_DbiModuleList dbi_open_module_info(Arena *arena, MSF_Context *msf, MSF_StreamNumber sn, PDB_DbiHeader *dbi_header, String8List *file_info); +internal PDB_DbiSCList dbi_open_sec_contrib(Arena *arena, MSF_Context *msf, MSF_StreamNumber sn, PDB_DbiHeader *dbi_header); +internal PDB_StringTable dbi_open_ec_names(Arena *arena, MSF_Context *msf, MSF_StreamNumber sn, PDB_DbiHeader *dbi_header); +internal void dbi_open_dbg_streams(MSF_StreamNumber *dbg_streams, MSF_Context *msf, MSF_StreamNumber sn, PDB_DbiHeader *dbi_header); +internal PDB_DbiSectionList dbi_open_section_headers(Arena *arena, MSF_Context *msf, MSF_StreamNumber sn); +internal void dbi_build_section_header_stream(PDB_DbiContext *dbi, MSF_Context *msf, MSF_StreamNumber sn); //////////////////////////////// // Hash Table @@ -461,5 +470,3 @@ internal PDB_TypeHashStreamInfo pdb_type_hash_stream_build(TP_Context *tp, PDB_ // Enum -> String internal String8 pdb_string_from_src_error(PDB_SrcError error); - - diff --git a/src/linker/tests/linker_tests.c b/src/linker/tests/linker_tests.c index 3221abe0..99b0b0ad 100644 --- a/src/linker/tests/linker_tests.c +++ b/src/linker/tests/linker_tests.c @@ -3,15 +3,15 @@ // TODO: // [x] defer_duplicate_imp_link +// [x] fold_two_funcs +// [x] same_but_different +// [x] fold_diamond +// [x] cyclic_icf +// [x] fold_with_largest_align // [ ] opt_ref_comdat_undef_section // [ ] opt_ref_weak_alias_comdat // [ ] reloc_apply_off_out_of_bounds // [ ] lib_member_reloc_apply_off_out_of_bounds -// [ ] fold_two_funcs -// [ ] same_but_different -// [ ] fold_diamond -// [ ] cyclic_icf -// [ ] fold_with_largest_align // [ ] relocate_undefined_section_symbol //////////////////////////////// @@ -710,6 +710,58 @@ TEST(merge) } } +TEST(section_directive_read_only_grouped_section) +{ + T_Ok(t_write_entry_obj()); + + T_Ok(t_write_def_obj("prot.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "prot_a", "prot$a", str8_lit_comp("A"), .flags = "rw:data@1" }, + { "prot_mem", "prot$mem", str8_lit_comp("mem"), .flags = "rw:data@1" }, + { "prot_z", "prot$z", str8_lit_comp("Z"), .flags = "rw:data@1" }, + {0} + }, + .directives = (char *[]){ "/SECTION:prot,R", 0 }, + })); + + t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe entry.obj prot.obj"); + T_Ok(g_last_exit_code == 0); + + String8 exe = t_read_file(arena, str8_lit("a.exe")); + PE_BinInfo pe = pe_bin_info_from_data(arena, exe); + COFF_SectionHeader *section_table = (COFF_SectionHeader *)str8_substr(exe, pe.section_table_range).str; + COFF_SectionHeader *sect = coff_section_header_from_name(exe, section_table, pe.section_count, str8_lit("prot")); + T_Ok(sect != 0); + T_Ok(sect->flags == (COFF_SectionFlag_CntInitializedData|COFF_SectionFlag_MemRead)); +} + +TEST(section_directive_align_grouped_section) +{ + if (t_id_linker() != Linker_radlink) { return; } + + T_Ok(t_write_entry_obj()); + + T_Ok(t_write_def_obj("prot.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "prot_mem", "prot$mem", str8_lit_comp("mem"), .flags = "rw:data@1" }, + {0} + }, + .directives = (char *[]){ "/SECTION:prot,R,ALIGN=8192", 0 }, + })); + + t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe entry.obj prot.obj"); + T_Ok(g_last_exit_code == 0); + + String8 exe = t_read_file(arena, str8_lit("a.exe")); + PE_BinInfo pe = pe_bin_info_from_data(arena, exe); + COFF_SectionHeader *section_table = (COFF_SectionHeader *)str8_substr(exe, pe.section_table_range).str; + COFF_SectionHeader *sect = coff_section_header_from_name(exe, section_table, pe.section_count, str8_lit("prot")); + T_Ok(sect != 0); + T_Ok(sect->flags == (COFF_SectionFlag_CntInitializedData|COFF_SectionFlag_MemRead|COFF_SectionFlag_Align8192Bytes)); +} + TEST(link_undef) { T_Ok(t_write_def_obj("undef.obj", (T_COFF_DefObj){ @@ -2153,6 +2205,48 @@ TEST(find_merged_pdata) T_Ok(dim_1u64(pe.data_dir_franges[PE_DataDirectoryIndex_EXCEPTIONS]) == 0xC); } +TEST(guard_cf_pulls_load_config) +{ + U8 load_config_data[0x40] = {0}; + U32 load_config_size = sizeof(load_config_data); + MemoryCopy(load_config_data, &load_config_size, sizeof(load_config_size)); + + T_Ok(t_write_entry_obj()); + T_Ok(t_write_def_lib("loadcfg.lib", (T_COFF_DefLib){ + .emit_second_member = 1, + .members = (T_COFF_DefLibMember[]){ + { + .type = T_COFF_DefLibMember_Obj, + .obj = { + .path = str8_lit("loadcfg.obj"), + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "loadcfg", ".rdata", str8_array_fixed(load_config_data), .flags = "r:data@8" }, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Extern("_load_config_used", "loadcfg", 0), + {0} + } + } + }, + {0} + } + })); + + t_invoke_linkerf("/nodefaultlib /subsystem:console /entry:entry /out:a.exe /guard:cf entry.obj loadcfg.lib"); + T_Ok(g_last_exit_code == 0); + + String8 exe = t_read_file(arena, str8_lit("a.exe")); + PE_BinInfo pe = pe_bin_info_from_data(arena, exe); + T_Ok(dim_1u64(pe.data_dir_franges[PE_DataDirectoryIndex_LOAD_CONFIG]) == load_config_size); + + PE_OptionalHeader32Plus *opt = str8_deserial_get_raw_ptr(exe, pe.optional_header_off, sizeof(*opt)); + if (t_id_linker() == Linker_radlink) { + T_Ok(!(opt->dll_characteristics & PE_DllCharacteristic_GUARD_CF)); + } +} + TEST(section_sort) { COFF_SectionFlags data_flags = COFF_SectionFlag_CntInitializedData|COFF_SectionFlag_MemRead|COFF_SectionFlag_MemRead|COFF_SectionFlag_Align1Bytes; @@ -2510,6 +2604,63 @@ TEST(simple_lib_test) T_Ok(*data_addr32nb == data_sect->voff); } +TEST(lib_member_imp_and_regular_symbol_queued_once) +{ + T_Ok(t_write_def_lib("rust_style.rlib", (T_COFF_DefLib){ + .emit_second_member = 1, + .members = (T_COFF_DefLibMember[]){ + { + .type = T_COFF_DefLibMember_Obj, + .obj = { + .path = str8_lit("core-9f9efb2036858c45.core.78298229696da45f-cgu.0.rcgu.o"), + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "text", ".text", str8_lit_comp("\xc3"), .flags = "rx:code@1" }, + { "idata", ".idata", str8_lit_comp("\x00\x00\x00\x00\x00\x00\x00\x00"), .flags = "r:data@8" }, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_ExternFunc("foo", "text", 0), + T_COFF_DefSymbol_Extern("__imp_foo", "idata", 0), + {0} + } + } + }, + {0} + } + })); + + T_Ok(t_write_def_obj("entry.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { + "text", ".text", + str8_lit_comp( + "\x48\xC7\xC0\x00\x00\x00\x00" + "\x48\xC7\xC1\x00\x00\x00\x00" + "\xC3" + ), + .flags = "rx:code@1", + .relocs = (T_COFF_DefReloc[]){ + T_COFF_DefReloc(X64_Addr32Nb, 3, "foo"), + T_COFF_DefReloc(X64_Addr32Nb, 10, "__imp_foo"), + {0} + } + }, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Extern("entry", "text", 0), + T_COFF_DefSymbol_Undef("foo"), + T_COFF_DefSymbol_Undef("__imp_foo"), + {0} + } + })); + + t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe entry.obj rust_style.rlib"); + T_Ok(g_last_exit_code == 0); +} + #if OS_WINDOWS TEST(import_export) { @@ -2734,6 +2885,125 @@ TEST(import_export) //T_Ok(t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe /delayload:export.dll /export:entry kernel32.Lib delayimp.lib libcmt.lib export.lib import.obj entry.obj") == 0); // TODO: check import table } + +TEST(def_file_full) +{ + if (t_id_linker() == Linker_lld) { return; } + + T_Ok(t_write_def_obj("def_full.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "data", ".rdata", str8_lit("test"), .flags = "rw:data" }, + { "text", ".text", str8_lit_comp("\xc3"), .flags = "rx:code" }, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Extern("entry", "text", 0), + T_COFF_DefSymbol_Extern("foo", "data", 0), + {0} + }, + })); + + T_Ok(t_write_file(str8_lit("full.def"), str8_lit( + "; leading comment\n" + "NAME \"def full.exe\" BASE=0x140020000\n" + "VERSION 7.8\n" + "HEAPSIZE 0x30000, 0x4000\n" + "STACKSIZE 0x50000,0x6000\n" + "SECTIONS .rdata READ\n" + "EXPORTS foo @ 2 DATA\n"))); + + t_invoke_linkerf("/subsystem:console /entry:entry /def:full.def def_full.obj"); + T_Ok(g_last_exit_code == 0); + + String8 exe = t_read_file(arena, str8_lit("def full.exe")); + PE_BinInfo pe = pe_bin_info_from_data(arena, exe); + COFF_SectionHeader *section_table = (COFF_SectionHeader *)str8_substr(exe, pe.section_table_range).str; + String8 string_table = str8_substr(exe, pe.string_table_range); + PE_OptionalHeader32Plus *opt = str8_deserial_get_raw_ptr(exe, pe.optional_header_off, sizeof(*opt)); + + T_Ok(opt->image_base == 0x140020000); + T_Ok(opt->major_img_ver == 7); + T_Ok(opt->minor_img_ver == 8); + T_Ok(opt->sizeof_heap_reserve == 0x30000); + T_Ok(opt->sizeof_heap_commit == 0x4000); + T_Ok(opt->sizeof_stack_reserve == 0x50000); + T_Ok(opt->sizeof_stack_commit == 0x6000); + + COFF_SectionHeader *rdata = coff_section_header_from_name(string_table, section_table, pe.section_count, str8_lit(".rdata")); + T_Ok(rdata != 0); + T_Ok(rdata->flags == (COFF_SectionFlag_CntInitializedData|COFF_SectionFlag_MemRead)); + + PE_ParsedExportTable export_table = pe_exports_from_data(arena, pe.section_count, section_table, exe, pe.data_dir_franges[PE_DataDirectoryIndex_EXPORT], pe.data_dir_vranges[PE_DataDirectoryIndex_EXPORT]); + T_Ok(export_table.export_count == 1); + T_Ok(str8_match(export_table.exports[0].name, str8_lit("foo"), 0)); + T_Ok(export_table.exports[0].ordinal == 2); + + T_Ok(t_write_file(str8_lit("bad_base_space.def"), str8_lit( + "NAME bad_base_space BASE = 0x140020000\n" + "EXPORTS foo @2 DATA\n"))); + T_Ok(t_write_file(str8_lit("bad_base_colon.def"), str8_lit( + "NAME bad_base_colon BASE:0x140020000\n" + "EXPORTS foo @2 DATA\n"))); + T_Ok(t_write_file(str8_lit("bad_section_align.def"), str8_lit( + "NAME bad_section_align\n" + "SECTIONS .rdata READ ALIGN=8192\n" + "EXPORTS foo @2 DATA\n"))); + + t_invoke_linkerf("/subsystem:console /entry:entry /def:bad_base_space.def /out:bad_base_space.exe def_full.obj"); + T_Ok(g_last_exit_code != 0); + + t_invoke_linkerf("/subsystem:console /entry:entry /def:bad_base_colon.def /out:bad_base_colon.exe def_full.obj"); + T_Ok(g_last_exit_code != 0); + + t_invoke_linkerf("/subsystem:console /entry:entry /def:bad_section_align.def /out:bad_section_align.exe def_full.obj"); + T_Ok(g_last_exit_code != 0); + + T_Ok(t_write_def_obj("def_full_dll.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "data", ".data", str8_lit("test"), .flags = "rw:data" }, + { "text", ".text", str8_lit_comp("\xc3"), .flags = "rx:code" }, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Extern("_DllMainCRTStartup", "text", 0), + T_COFF_DefSymbol_Extern("dll_foo", "data", 0), + {0} + }, + })); + + T_Ok(t_write_file(str8_lit("full_dll.def"), str8_lit( + "LIBRARY folded BASE=0x180020000\n" + "EXPORTS\n" + " dll_foo DATA\n"))); + + t_invoke_linkerf("/dll /subsystem:console /def:full_dll.def def_full_dll.obj"); + T_Ok(g_last_exit_code == 0); + + String8 dll = t_read_file(arena, str8_lit("folded.dll")); + PE_BinInfo dll_pe = pe_bin_info_from_data(arena, dll); + COFF_SectionHeader *dll_section_table = (COFF_SectionHeader *)str8_substr(dll, dll_pe.section_table_range).str; + PE_OptionalHeader32Plus *dll_opt = str8_deserial_get_raw_ptr(dll, dll_pe.optional_header_off, sizeof(*dll_opt)); + PE_ParsedExportTable dll_export_table = pe_exports_from_data(arena, dll_pe.section_count, dll_section_table, dll, dll_pe.data_dir_franges[PE_DataDirectoryIndex_EXPORT], dll_pe.data_dir_vranges[PE_DataDirectoryIndex_EXPORT]); + + T_Ok(dll_opt->image_base == 0x180020000); + T_Ok(dll_export_table.export_count == 1); + T_Ok(str8_match(dll_export_table.exports[0].name, str8_lit("dll_foo"), 0)); +} + +TEST(utf16_rsp) +{ + T_Ok(t_write_entry_obj()); + + String8 rsp_text = str8_lit("/subsystem:console /entry:entry /out:a.exe entry.obj\n"); + String16 rsp16 = str16_from_8(arena, rsp_text); + String8 rsp_file = str8_cat(arena, str8_lit("\xff\xfe"), str8_array(rsp16.str, rsp16.size)); + T_Ok(t_write_file(str8_lit("args.rsp"), rsp_file)); + + t_invoke_linkerf("@args.rsp"); + T_Ok(g_last_exit_code == 0); +} #endif TEST(image_base) @@ -2862,6 +3132,463 @@ TEST(comdat_any) } } +// MSVC vftables use COMDAT sections whose public symbol can start past the +// section definition symbol; references to a replaced copy must target the winner. +TEST(comdat_external_symbol_at_nonzero_offset) +{ + U8 data[16] = {0}; + U8 ptr[8] = {0}; + U8 text[] = { + 0x48, 0x8D, 0x05, 0, 0, 0, 0, // lea rax, [rip + foo] + 0xC3 + }; + + T_Ok(t_write_def_obj("leader.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "data", ".rdata", str8_array_fixed(data), .flags = "r:data@8", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Secdef("data", COFF_ComdatSelect_Any), + T_COFF_DefSymbol_Extern("foo", "data", 8), + {0} + } + })); + + T_Ok(t_write_def_obj("ref.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "data", ".rdata", str8_array_fixed(data), .flags = "r:data@8", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + { "ptr", ".data", str8_array_fixed(ptr), .flags = "rw:data@8", .relocs = (T_COFF_DefReloc[]){ + T_COFF_DefReloc(X64_Addr64, 0, "foo"), + {0} + }}, + { "text", ".text", str8_array_fixed(text), .flags = "rx:code@1", .relocs = (T_COFF_DefReloc[]){ + T_COFF_DefReloc(X64_Rel32, 3, "foo"), + {0} + }}, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Secdef("data", COFF_ComdatSelect_Any), + T_COFF_DefSymbol_Extern("foo", "data", 8), + T_COFF_DefSymbol_Extern("entry", "text", 0), + {0} + } + })); + + t_invoke_linkerf("/nodefaultlib /subsystem:console /entry:entry /out:a.exe leader.obj ref.obj"); + T_Ok(g_last_exit_code == 0); + + String8 exe = t_read_file(arena, str8_lit("a.exe")); + PE_BinInfo pe = pe_bin_info_from_data(arena, exe); + COFF_SectionHeader *section_table = (COFF_SectionHeader *)str8_substr(exe, pe.section_table_range).str; + String8 string_table = str8_substr(exe, pe.string_table_range); + COFF_SectionHeader *rdata_section = coff_section_header_from_name(string_table, section_table, pe.section_count, str8_lit(".rdata")); + COFF_SectionHeader *data_section = coff_section_header_from_name(string_table, section_table, pe.section_count, str8_lit(".data")); + COFF_SectionHeader *text_section = coff_section_header_from_name(string_table, section_table, pe.section_count, str8_lit(".text")); + T_Ok(rdata_section != 0); + T_Ok(data_section != 0); + T_Ok(text_section != 0); + + U64 actual_ptr = 0; + str8_deserial_read_struct(exe, data_section->foff, &actual_ptr); + U64 expected_ptr = pe.image_base + rdata_section->voff + 8; + T_Ok(actual_ptr == expected_ptr); + + S32 lea_disp = 0; + str8_deserial_read_struct(exe, text_section->foff + 3, &lea_disp); + U64 actual_lea_target = text_section->voff + 7 + lea_disp; + U64 expected_lea_target = rdata_section->voff + 8; + T_Ok(actual_lea_target == expected_lea_target); +} + +TEST(comdat_external_symbol_at_zero_offset) +{ + U8 data[8] = {0}; + U8 ptr[8] = {0}; + U8 text[] = { + 0x48, 0x8D, 0x05, 0, 0, 0, 0, // lea rax, [rip + foo] + 0xC3 + }; + + T_Ok(t_write_def_obj("leader.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "data", ".rdata", str8_array_fixed(data), .flags = "r:data@8", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Secdef("data", COFF_ComdatSelect_Any), + T_COFF_DefSymbol_Extern("foo", "data", 0), + {0} + } + })); + + T_Ok(t_write_def_obj("ref.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "data", ".rdata", str8_array_fixed(data), .flags = "r:data@8", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + { "ptr", ".data", str8_array_fixed(ptr), .flags = "rw:data@8", .relocs = (T_COFF_DefReloc[]){ + T_COFF_DefReloc(X64_Addr64, 0, "foo"), + {0} + }}, + { "text", ".text", str8_array_fixed(text), .flags = "rx:code@1", .relocs = (T_COFF_DefReloc[]){ + T_COFF_DefReloc(X64_Rel32, 3, "foo"), + {0} + }}, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Secdef("data", COFF_ComdatSelect_Any), + T_COFF_DefSymbol_Extern("foo", "data", 0), + T_COFF_DefSymbol_Extern("entry", "text", 0), + {0} + } + })); + + t_invoke_linkerf("/nodefaultlib /subsystem:console /entry:entry /out:a.exe leader.obj ref.obj"); + T_Ok(g_last_exit_code == 0); + + String8 exe = t_read_file(arena, str8_lit("a.exe")); + PE_BinInfo pe = pe_bin_info_from_data(arena, exe); + COFF_SectionHeader *section_table = (COFF_SectionHeader *)str8_substr(exe, pe.section_table_range).str; + String8 string_table = str8_substr(exe, pe.string_table_range); + COFF_SectionHeader *rdata_section = coff_section_header_from_name(string_table, section_table, pe.section_count, str8_lit(".rdata")); + COFF_SectionHeader *data_section = coff_section_header_from_name(string_table, section_table, pe.section_count, str8_lit(".data")); + COFF_SectionHeader *text_section = coff_section_header_from_name(string_table, section_table, pe.section_count, str8_lit(".text")); + T_Ok(rdata_section != 0); + T_Ok(data_section != 0); + T_Ok(text_section != 0); + + U64 actual_ptr = 0; + str8_deserial_read_struct(exe, data_section->foff, &actual_ptr); + U64 expected_ptr = pe.image_base + rdata_section->voff; + T_Ok(actual_ptr == expected_ptr); + + S32 lea_disp = 0; + str8_deserial_read_struct(exe, text_section->foff + 3, &lea_disp); + U64 actual_lea_target = text_section->voff + 7 + lea_disp; + U64 expected_lea_target = rdata_section->voff; + T_Ok(actual_lea_target == expected_lea_target); +} + +// Duplicate COMDAT sections can have identical bytes while their symbol tables +// disagree about where a same-named public symbol points inside the section. +// This mirrors MSVC vftable COMDATs: the selected copy may have leading RTTI data +// at offset 0 and the vftable symbol at offset 8, while a discarded copy's +// vftable symbol is at offset 0. Relocations against the discarded symbol must +// use the selected symbol's value, not just the selected section contribution +// plus the discarded symbol's original offset. +TEST(comdat_external_symbol_uses_leader_offset) +{ + U8 leader_data[16] = {0}; + U8 ref_data[16] = {0}; + U8 ptr[8] = {0}; + U8 text[] = { + 0x48, 0x8D, 0x05, 0, 0, 0, 0, // lea rax, [rip + ??_7X] + 0xC3 + }; + + T_Ok(t_write_def_obj("leader.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "vftable", ".rdata", str8_array_fixed(leader_data), .flags = "r:data@8", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Secdef("vftable", COFF_ComdatSelect_Any), + T_COFF_DefSymbol_Extern("??_7X@@6B@", "vftable", 8), + {0} + } + })); + + T_Ok(t_write_def_obj("ref.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "vftable", ".rdata", str8_array_fixed(ref_data), .flags = "r:data@8", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + { "ptr", ".data", str8_array_fixed(ptr), .flags = "rw:data@8", .relocs = (T_COFF_DefReloc[]){ + T_COFF_DefReloc(X64_Addr64, 0, "??_7X@@6B@"), + {0} + }}, + { "text", ".text", str8_array_fixed(text), .flags = "rx:code@1", .relocs = (T_COFF_DefReloc[]){ + T_COFF_DefReloc(X64_Rel32, 3, "??_7X@@6B@"), + {0} + }}, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Secdef("vftable", COFF_ComdatSelect_Any), + T_COFF_DefSymbol_Extern("??_7X@@6B@", "vftable", 0), + T_COFF_DefSymbol_ExternFunc("entry", "text", 0), + {0} + } + })); + + t_invoke_linkerf("/nodefaultlib /subsystem:console /entry:entry /out:a.exe leader.obj ref.obj"); + T_Ok(g_last_exit_code == 0); + + String8 exe = t_read_file(arena, str8_lit("a.exe")); + PE_BinInfo pe = pe_bin_info_from_data(arena, exe); + COFF_SectionHeader *section_table = (COFF_SectionHeader *)str8_substr(exe, pe.section_table_range).str; + String8 string_table = str8_substr(exe, pe.string_table_range); + COFF_SectionHeader *rdata_section = coff_section_header_from_name(string_table, section_table, pe.section_count, str8_lit(".rdata")); + COFF_SectionHeader *data_section = coff_section_header_from_name(string_table, section_table, pe.section_count, str8_lit(".data")); + COFF_SectionHeader *text_section = coff_section_header_from_name(string_table, section_table, pe.section_count, str8_lit(".text")); + T_Ok(rdata_section != 0); + T_Ok(data_section != 0); + T_Ok(text_section != 0); + + U64 actual_ptr = 0; + str8_deserial_read_struct(exe, data_section->foff, &actual_ptr); + U64 expected_ptr = pe.image_base + rdata_section->voff + 8; + T_Ok(actual_ptr == expected_ptr); + + S32 lea_disp = 0; + str8_deserial_read_struct(exe, text_section->foff + 3, &lea_disp); + U64 actual_lea_target = text_section->voff + 7 + lea_disp; + U64 expected_lea_target = rdata_section->voff + 8; + T_Ok(actual_lea_target == expected_lea_target); +} + +// Chromium has duplicate vftable COMDATs where the referencing copy is an +// IMAGE_COMDAT_SELECT_ANY section with the public vftable at offset 0, while +// the selected IMAGE_COMDAT_SELECT_LARGEST copy has the same public symbol at +// offset 8. Relocations in the discarded object must resolve to the selected +// public symbol, not to the discarded section. +TEST(comdat_largest_external_symbol_uses_selected_offset) +{ + U8 discarded_data[16] = {0}; + U8 selected_data[24] = {0}; + U8 ptr[16] = {0}; + U8 selected_text[] = { 0xC3 }; + U8 text[] = { + 0x48, 0x8D, 0x05, 0, 0, 0, 0, // lea rax, [rip + ??_7X] + 0xC3 + }; + + T_Ok(t_write_def_obj("discarded.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "vftable", ".rdata", str8_array_fixed(discarded_data), .flags = "r:data@8", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + { "ptr", ".data", str8_array_fixed(ptr), .flags = "rw:data@8", .relocs = (T_COFF_DefReloc[]){ + T_COFF_DefReloc(X64_Addr64, 0, "??_7X@@6B@"), + T_COFF_DefReloc(X64_Addr64, 8, "force_selected"), + {0} + }}, + { "text", ".text", str8_array_fixed(text), .flags = "rx:code@1", .relocs = (T_COFF_DefReloc[]){ + T_COFF_DefReloc(X64_Rel32, 3, "??_7X@@6B@"), + {0} + }}, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Secdef("vftable", COFF_ComdatSelect_Any), + T_COFF_DefSymbol_Extern("??_7X@@6B@", "vftable", 0), + T_COFF_DefSymbol_Undef("force_selected"), + T_COFF_DefSymbol_ExternFunc("entry", "text", 0), + {0} + } + })); + + T_Ok(t_write_def_lib("selected.lib", (T_COFF_DefLib){ + .emit_second_member = 1, + .members = (T_COFF_DefLibMember[]){ + { + .type = T_COFF_DefLibMember_Obj, + .obj = { + .path = str8_lit("selected.obj"), + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "vftable", ".rdata", str8_array_fixed(selected_data), .flags = "r:data@8", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + { "force_text", ".text", str8_array_fixed(selected_text), .flags = "rx:code@1" }, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Secdef("vftable", COFF_ComdatSelect_Largest), + T_COFF_DefSymbol_Extern("??_7X@@6B@", "vftable", 8), + T_COFF_DefSymbol_ExternFunc("force_selected", "force_text", 0), + {0} + } + } + }, + {0} + } + })); + + t_invoke_linkerf("/nodefaultlib /subsystem:console /entry:entry /out:a.exe /opt:ref,noicf discarded.obj selected.lib"); + T_Ok(g_last_exit_code == 0); + + String8 exe = t_read_file(arena, str8_lit("a.exe")); + PE_BinInfo pe = pe_bin_info_from_data(arena, exe); + COFF_SectionHeader *section_table = (COFF_SectionHeader *)str8_substr(exe, pe.section_table_range).str; + String8 string_table = str8_substr(exe, pe.string_table_range); + COFF_SectionHeader *rdata_section = coff_section_header_from_name(string_table, section_table, pe.section_count, str8_lit(".rdata")); + COFF_SectionHeader *data_section = coff_section_header_from_name(string_table, section_table, pe.section_count, str8_lit(".data")); + COFF_SectionHeader *text_section = coff_section_header_from_name(string_table, section_table, pe.section_count, str8_lit(".text")); + T_Ok(rdata_section != 0); + T_Ok(data_section != 0); + T_Ok(text_section != 0); + + U64 actual_ptr = 0; + str8_deserial_read_struct(exe, data_section->foff, &actual_ptr); + U64 expected_ptr = pe.image_base + rdata_section->voff + 8; + T_Ok(actual_ptr == expected_ptr); + + S32 lea_disp = 0; + str8_deserial_read_struct(exe, text_section->foff + 3, &lea_disp); + U64 actual_lea_target = text_section->voff + 7 + lea_disp; + U64 expected_lea_target = rdata_section->voff + 8; + T_Ok(actual_lea_target == expected_lea_target); +} + +TEST(icf_vftable_external_symbol_at_nonzero_offset) +{ + U8 entry_text[] = { + 0x48, 0x8D, 0x05, 0, 0, 0, 0, // lea rax, [rip + ??_7B] + 0xC3, // ret + }; + U8 vftable_data[16] = {0}; + U8 addresses[16] = {0}; + + T_Ok(t_write_def_obj("vftable.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "entry", ".text", str8_array_fixed(entry_text), .flags = "rx:code@1", .relocs = (T_COFF_DefReloc[]){ + T_COFF_DefReloc(X64_Rel32, 3, "??_7B@@6B@"), + {0} + }}, + { "vftable_a", ".rdata$vt", str8_array_fixed(vftable_data), .flags = "r:data@8", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + { "vftable_b", ".rdata$vt", str8_array_fixed(vftable_data), .flags = "r:data@8", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + { "addresses", ".data", str8_array_fixed(addresses), .flags = "rw:data@8", .relocs = (T_COFF_DefReloc[]){ + T_COFF_DefReloc(X64_Addr64, 0, "??_7A@@6B@"), + T_COFF_DefReloc(X64_Addr64, 8, "??_7B@@6B@"), + {0} + }}, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Secdef("vftable_a", COFF_ComdatSelect_Any), + T_COFF_DefSymbol_Secdef("vftable_b", COFF_ComdatSelect_Any), + T_COFF_DefSymbol_Extern("??_R4A@@6B@", "vftable_a", 0), + T_COFF_DefSymbol_Extern("??_7A@@6B@", "vftable_a", 8), + T_COFF_DefSymbol_Extern("??_R4B@@6B@", "vftable_b", 0), + T_COFF_DefSymbol_Extern("??_7B@@6B@", "vftable_b", 8), + T_COFF_DefSymbol_ExternFunc("entry", "entry", 0), + T_COFF_DefSymbol_Extern("addresses", "addresses", 0), + {0} + } + })); + + t_invoke_linkerf("/nodefaultlib /subsystem:console /entry:entry /out:a.exe /opt:ref,icf /include:addresses vftable.obj"); + T_Ok(g_last_exit_code == 0); + + String8 exe = t_read_file(arena, str8_lit("a.exe")); + PE_BinInfo pe = pe_bin_info_from_data(arena, exe); + COFF_SectionHeader *section_table = (COFF_SectionHeader *)str8_substr(exe, pe.section_table_range).str; + String8 string_table = str8_substr(exe, pe.string_table_range); + COFF_SectionHeader *rdata_section = coff_section_header_from_name(string_table, section_table, pe.section_count, str8_lit(".rdata")); + COFF_SectionHeader *data_section = coff_section_header_from_name(string_table, section_table, pe.section_count, str8_lit(".data")); + COFF_SectionHeader *text_section = coff_section_header_from_name(string_table, section_table, pe.section_count, str8_lit(".text")); + T_Ok(rdata_section != 0); + T_Ok(data_section != 0); + T_Ok(text_section != 0); + + U64 a_vftable_ptr = 0; + U64 b_vftable_ptr = 0; + str8_deserial_read_struct(exe, data_section->foff + 0, &a_vftable_ptr); + str8_deserial_read_struct(exe, data_section->foff + 8, &b_vftable_ptr); + T_Ok(a_vftable_ptr != 0); + T_Ok(b_vftable_ptr != 0); + T_Ok((a_vftable_ptr - pe.image_base - rdata_section->voff) % sizeof(vftable_data) == 8); + T_Ok((b_vftable_ptr - pe.image_base - rdata_section->voff) % sizeof(vftable_data) == 8); + + S32 lea_disp = 0; + str8_deserial_read_struct(exe, text_section->foff + 3, &lea_disp); + U64 actual_lea_target = text_section->voff + 7 + lea_disp; + T_Ok(actual_lea_target == b_vftable_ptr - pe.image_base); +} + +// A referenced zero-sized COMDAT symbol is meaningful enough for +// relocations, even though the COMDAT contributes no bytes to the image. +TEST(zero_length_comdat_referenced_by_reloc) +{ + U8 data[8] = {0}; + + T_Ok(t_write_entry_obj()); + T_Ok(t_write_def_obj("ref.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "pad", ".rdata$a", str8_lit("xy"), .flags = "r:data@1" }, + { "empty", ".rdata$b", str8_zero(), .flags = "r:data@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + { "data", ".data", str8_array_fixed(data), .flags = "rw:data", .relocs = (T_COFF_DefReloc[]){ + T_COFF_DefReloc(X64_Addr64, 0, "EMPTY"), + {0} + }}, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Secdef("empty", COFF_ComdatSelect_Any), + T_COFF_DefSymbol_Extern("EMPTY", "empty", 0), + {0} + } + })); + + t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe /opt:ref entry.obj ref.obj"); + T_Ok(g_last_exit_code == 0); + + String8 exe = t_read_file(arena, str8_lit("a.exe")); + PE_BinInfo pe = pe_bin_info_from_data(arena, exe); + COFF_SectionHeader *section_table = (COFF_SectionHeader *)str8_substr(exe, pe.section_table_range).str; + COFF_SectionHeader *rdata_section = coff_section_header_from_name(str8_zero(), section_table, pe.section_count, str8_lit(".rdata")); + COFF_SectionHeader *data_section = coff_section_header_from_name(str8_zero(), section_table, pe.section_count, str8_lit(".data")); + T_Ok(rdata_section != 0); + T_Ok(data_section != 0); + + U64 empty_va = 0; + str8_deserial_read_struct(exe, data_section->foff, &empty_va); + B32 empty_after_pad = empty_va >= pe.image_base + rdata_section->voff + 2; + T_Ok(empty_after_pad); + + U64 empty_off_in_rdata = empty_va - pe.image_base - rdata_section->voff; + T_Ok(rdata_section->foff + empty_off_in_rdata <= exe.size); + T_Ok(exe.str[rdata_section->foff + empty_off_in_rdata - 2] == 'x'); + T_Ok(exe.str[rdata_section->foff + empty_off_in_rdata - 1] == 'y'); +} + +TEST(zero_length_static_comdat_referenced_by_reloc) +{ + if (t_id_linker() != Linker_radlink) { return; } + + U8 text[] = { + 0x48, 0x8D, 0x05, 0, 0, 0, 0, // lea rax, [rip + EMPTY] + 0xC3 + }; + + T_Ok(t_write_def_obj("test.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "empty", ".rdata", str8_zero(), .flags = "r:data@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + { "text", ".text", str8_array_fixed(text), .flags = "rx:code@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT, .relocs = (T_COFF_DefReloc[]){ + T_COFF_DefReloc(X64_Rel32, 3, "EMPTY"), + {0} + }}, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Secdef("empty", COFF_ComdatSelect_NoDuplicates), + T_COFF_DefSymbol_Static("EMPTY", "empty", 0), + T_COFF_DefSymbol_Secdef("text", COFF_ComdatSelect_NoDuplicates), + T_COFF_DefSymbol_Extern("entry", "text", 0), + {0} + } + })); + + t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe /opt:ref,noicf test.obj"); + T_Ok(g_last_exit_code == 0); +} + TEST(comdat_no_duplicates) { T_Ok(t_write_def_obj("entry.obj", (T_COFF_DefObj){ @@ -3549,7 +4276,7 @@ TEST(reloc_against_removed_comdat) })); t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe a.obj b.obj entry.obj"); - T_Ok(g_last_exit_code == LNK_Error_RelocationAgainstRemovedSection); + T_Ok(g_last_exit_code == 0); } TEST(sect_align) @@ -4749,6 +5476,87 @@ TEST(relocate_undefined_section_symbol) } #endif +#if 1 +TEST(weak_alias_comdat_duplicate_fallback) +{ + U64 dummy_count = 64; + + for EachIndex(obj_idx, 2) { + char prefix = obj_idx == 0 ? 'a' : 'b'; + T_COFF_DefSection *sections = push_array(arena, T_COFF_DefSection, dummy_count + 5); + T_COFF_DefSymbol *symbols = push_array(arena, T_COFF_DefSymbol, dummy_count*2 + 11); + U64 section_idx = 0; + U64 symbol_idx = 0; + + for EachIndex(i, dummy_count) { + char *id = (char *)str8f(arena, "dummy_%c_%I64u", prefix, i).str; + char *name = (char *)str8f(arena, "?dummy_%c_%I64u@@YAXXZ", prefix, i).str; + sections[section_idx++] = (T_COFF_DefSection){ id, ".text", str8_lit_comp("\xC3"), .flags = "rx:code@16", .raw_flags = COFF_SectionFlag_LnkCOMDAT }; + symbols[symbol_idx++] = (T_COFF_DefSymbol)T_COFF_DefSymbol_Secdef(id, COFF_ComdatSelect_Any); + symbols[symbol_idx++] = (T_COFF_DefSymbol)T_COFF_DefSymbol_ExternFunc(name, id, 0); + } + + sections[section_idx++] = (T_COFF_DefSection){ "text0", ".text", str8_lit_comp("\x33\xC0"), .flags = "rx:code@16", .raw_flags = COFF_SectionFlag_LnkCOMDAT }; + sections[section_idx++] = (T_COFF_DefSection){ "text1", ".text", str8_lit_comp("\x33\xC0"), .flags = "rx:code@16", .raw_flags = COFF_SectionFlag_LnkCOMDAT }; + sections[section_idx++] = (T_COFF_DefSection){ "xdata0", ".xdata", str8_lit_comp("\x01\x00\x00\x00"), .flags = "r:data@4", .raw_flags = COFF_SectionFlag_LnkCOMDAT }; + sections[section_idx++] = (T_COFF_DefSection){ "text2", ".text", str8_lit_comp("\x33\xC0\xC3"), .flags = "rx:code@16", .raw_flags = COFF_SectionFlag_LnkCOMDAT }; + + symbols[symbol_idx++] = (T_COFF_DefSymbol)T_COFF_DefSymbol_Secdef("text0", COFF_ComdatSelect_Any); + symbols[symbol_idx++] = (T_COFF_DefSymbol)T_COFF_DefSymbol_ExternFunc("??_G__shared_count@__Cr@std@@MEAAPEAXI@Z", "text0", 0); + symbols[symbol_idx++] = (T_COFF_DefSymbol)T_COFF_DefSymbol_Secdef("text1", COFF_ComdatSelect_Any); + symbols[symbol_idx++] = (T_COFF_DefSymbol)T_COFF_DefSymbol_ExternFunc("??_G__shared_weak_count@__Cr@std@@MEAAPEAXI@Z", "text1", 0); + symbols[symbol_idx++] = (T_COFF_DefSymbol)T_COFF_DefSymbol_Associative("xdata0", "text0"); + symbols[symbol_idx++] = (T_COFF_DefSymbol)T_COFF_DefSymbol_Secdef("text2", COFF_ComdatSelect_Any); + + if (obj_idx == 0) { + symbols[symbol_idx++] = (T_COFF_DefSymbol)T_COFF_DefSymbol_ExternFunc("??_G?$__shared_ptr_emplace@A@@UEAAPEAXI@Z", "text2", 0); + symbols[symbol_idx++] = (T_COFF_DefSymbol)T_COFF_DefSymbol_Weak("??_E?$__shared_ptr_emplace@A@@UEAAPEAXI@Z", COFF_WeakExt_SearchAlias, "??_G?$__shared_ptr_emplace@A@@UEAAPEAXI@Z"); + } else { + symbols[symbol_idx++] = (T_COFF_DefSymbol)T_COFF_DefSymbol_ExternFunc("??_G?$__shared_ptr_emplace@B@@UEAAPEAXI@Z", "text2", 0); + symbols[symbol_idx++] = (T_COFF_DefSymbol)T_COFF_DefSymbol_Weak("??_E?$__shared_ptr_emplace@B@@UEAAPEAXI@Z", COFF_WeakExt_SearchAlias, "??_G?$__shared_ptr_emplace@B@@UEAAPEAXI@Z"); + } + symbols[symbol_idx++] = (T_COFF_DefSymbol)T_COFF_DefSymbol_Weak("??_E__shared_count@__Cr@std@@MEAAPEAXI@Z", COFF_WeakExt_SearchAlias, "??_G__shared_count@__Cr@std@@MEAAPEAXI@Z"); + symbols[symbol_idx++] = (T_COFF_DefSymbol)T_COFF_DefSymbol_Weak("??_E__shared_weak_count@__Cr@std@@MEAAPEAXI@Z", COFF_WeakExt_SearchAlias, "??_G__shared_weak_count@__Cr@std@@MEAAPEAXI@Z"); + + T_Ok(t_write_def_obj(obj_idx == 0 ? "a.obj" : "b.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = sections, + .symbols = symbols, + })); + } + + T_Ok(t_write_def_obj("entry.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { + "text", ".text", + str8_lit_comp( + "\x48\xC7\xC0\x00\x00\x00\x00" // mov rax, $imm + "\xC3" // ret + ), + .flags = "rx:code", + .relocs = (T_COFF_DefReloc[]){ + T_COFF_DefReloc(X64_Addr32Nb, 3, "??_E__shared_count@__Cr@std@@MEAAPEAXI@Z"), + {0} + } + }, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Extern("entry", "text", 0), + T_COFF_DefSymbol_Undef("??_E__shared_count@__Cr@std@@MEAAPEAXI@Z"), + {0} + } + })); + + t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe a.obj b.obj entry.obj"); + T_Ok(g_last_exit_code == 0); + + t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe b.obj a.obj entry.obj"); + T_Ok(g_last_exit_code == 0); +} +#endif + #if 1 TEST(opt_ref_weak_alias_comdat) { @@ -4840,6 +5648,18 @@ TEST(fail_if_mismatch) else T_Ok(g_last_exit_code != 0); } +TEST(guardsym_directive) +{ + T_Ok(t_write_entry_obj()); + + // MSVC link accepts GUARDSYM without treating the named symbol as /INCLUDE. + String8 guardsym = t_make_obj_with_directive(arena, str8_lit("/GUARDSYM:missing,S")); + T_Ok(t_write_file(str8_lit("guardsym.obj"), guardsym)); + + t_invoke_linkerf("entry.obj guardsym.obj /entry:entry /subsystem:console /out:guardsym.exe"); + T_Ok(g_last_exit_code == 0); +} + TEST(long_section_name) { Arch arch = Arch_x64; @@ -6023,7 +6843,7 @@ data_from_pdb(Arena *arena, PDB_Context *pdb) { TP_Context *tp = tp_alloc(arena, 1, 1, str8_lit("foo")); TP_Arena *tp_arena = tp_arena_alloc(tp); - pdb_build(tp, tp_arena, pdb, (CV_StringHashTable){0}, 1, 0); + pdb_build(tp, tp_arena, pdb, (CV_StringHashTable){0}, 1, 0, 0); AssertAlways(msf_build(pdb->msf) == MSF_Error_OK); String8List raw_msf_list = msf_get_page_data_nodes(arena, pdb->msf); @@ -6294,6 +7114,38 @@ TEST(validate_psi) T_Ok(pub32_count > 0); } +TEST(psi_addr_map_radix_sort) +{ + String8 names[] = { + str8_lit("alpha"), + str8_lit("bravo"), + str8_lit("charlie"), + str8_lit("delta"), + }; + U64 address_count = (1 << 15) + 1; + U64 record_count = address_count * ArrayCount(names); + + PDB_GsiSortRecord *records = push_array_no_zero(arena, PDB_GsiSortRecord, record_count); + PDB_GsiSortRecord *expected = push_array_no_zero(arena, PDB_GsiSortRecord, record_count); + for EachIndex(i, record_count) { + U64 address_idx = address_count - 1 - i / ArrayCount(names); + records[i].isect_off.isect = 1 + address_idx % 257; + records[i].isect_off.off = address_idx / 257; + records[i].name = names[ArrayCount(names) - 1 - i % ArrayCount(names)]; + records[i].offset = i * sizeof(U32); + } + MemoryCopyTyped(expected, records, record_count); + radsort(expected, record_count, psi_addr_map_compar_is_before); + + TP_Context *tp = tp_alloc(arena, 1, 1, str8_lit("psi addr map sort test")); + U32 *addr_map = psi_addr_map_from_gsi_records(tp, arena, records, record_count); + + for EachIndex(i, record_count) { + T_Ok(addr_map[i] == expected[i].offset); + } + tp_release(tp); +} + TEST(pdbstripped) { String8 debug_obj; @@ -6571,7 +7423,7 @@ TEST(ghash_check_hash_alg) B32 is_warning_found = 0; String8 debug_obj_path = t_make_file_path(arena, str8_lit("debug.obj")); - String8 expected_line = str8f(arena, "Warning(*): *: mismatched .debug$H hash algorithm: got SHA1_8, expected BLAKE3"); + String8 expected_line = str8f(arena, "Warning(*): *: mismatched .debug$H hash algorithm: got SHA1_8, expected *"); for (String8 i = g_errors; i.size > 0 && !is_warning_found; ) { String8 line = t_chop_line(&i); is_warning_found = str8_match_wildcard(line, expected_line, StringMatchFlag_CaseInsensitive); @@ -6963,9 +7815,236 @@ TEST(determ_test) #endif -#if 0 +internal B32 t_read_exe_data_vaddrs(Arena *arena, String8 exe_path, U64 *vaddrs, U64 count); -TEST(fold_two_funcs) +#if OS_WINDOWS +TEST(ms_link_icfs_identical_comdats) +{ + U8 same_text[] = { + 0x48, 0x31, 0xc0, // xor rax, rax + 0xc3 // ret + }; + U8 entry_text[] = { + 0xc3, // ret + }; + U8 addresses[] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + }; + + T_Ok(t_write_def_obj("ms_icf.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "entry", ".text$mn", str8_array_fixed(entry_text), .flags = "rx:code@1" }, + { "a", ".text$mn", str8_array_fixed(same_text), .flags = "rx:code@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + { "b", ".text$mn", str8_array_fixed(same_text), .flags = "rx:code@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + { + "addresses", ".data", str8_array_fixed(addresses), .flags = "rw:data@1", + .relocs = (T_COFF_DefReloc[]){ + T_COFF_DefReloc(X64_Addr64, 0, "a"), + T_COFF_DefReloc(X64_Addr64, 8, "b"), + {0} + } + }, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Secdef("a", COFF_ComdatSelect_Any), + T_COFF_DefSymbol_Secdef("b", COFF_ComdatSelect_Any), + T_COFF_DefSymbol_ExternFunc("entry", "entry", 0), + T_COFF_DefSymbol_ExternFunc("a", "a", 0), + T_COFF_DefSymbol_ExternFunc("b", "b", 0), + T_COFF_DefSymbol_Extern("addresses", "addresses", 0), + {0} + } + })); + + B32 is_invoke_ok = t_invoke(str8_lit("link.exe"), str8_lit("/nologo /nodefaultlib /subsystem:console /entry:entry /out:ms_icf.exe /opt:ref,icf /include:a /include:b /include:addresses ms_icf.obj"), max_U64); + T_Ok(is_invoke_ok); + T_Ok(g_last_exit_code == 0); + + String8 exe = t_read_file(arena, str8_lit("ms_icf.exe")); + T_Ok(exe.size); + + PE_BinInfo pe = pe_bin_info_from_data(arena, exe); + COFF_SectionHeader *section_table = (COFF_SectionHeader *)str8_substr(exe, pe.section_table_range).str; + String8 string_table = str8_substr(exe, pe.string_table_range); + COFF_SectionHeader *data_section = coff_section_header_from_name(string_table, section_table, pe.section_count, str8_lit(".data")); + T_Ok(data_section != 0); + T_Ok(data_section->foff + sizeof(addresses) <= exe.size); + + String8 data = str8_substr(exe, r1u64(data_section->foff, data_section->foff + sizeof(addresses))); + U64 a_addr = 0; + U64 b_addr = 0; + str8_deserial_read_struct(data, 0, &a_addr); + str8_deserial_read_struct(data, 8, &b_addr); + T_Ok(a_addr != 0); + T_Ok(a_addr == b_addr); // COMDAT are folded +} + +#if 0 +TEST(ms_link_icf_section_flag_eligibility) +{ + U8 ret_text[] = { + 0xc3, // ret + }; + U8 data_bytes[] = { + 1, 2, 3, 4, 5, 6, 7, 8, + }; + U8 addresses[] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + }; + + T_Ok(t_write_def_obj("ms_icf_flags.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "entry", ".text$mn", str8_array_fixed(ret_text), .flags = "rx:code@1" }, + { + "addresses", ".data", str8_array_fixed(addresses), .flags = "rw:data@1", + .relocs = (T_COFF_DefReloc[]){ + T_COFF_DefReloc(X64_Addr64, 0, "code_comdat_a"), + T_COFF_DefReloc(X64_Addr64, 8, "code_comdat_b"), + T_COFF_DefReloc(X64_Addr64, 16, "code_plain_a"), + T_COFF_DefReloc(X64_Addr64, 24, "code_plain_b"), + T_COFF_DefReloc(X64_Addr64, 32, "rdata_comdat_a"), + T_COFF_DefReloc(X64_Addr64, 40, "rdata_comdat_b"), + T_COFF_DefReloc(X64_Addr64, 48, "wdata_comdat_a"), + T_COFF_DefReloc(X64_Addr64, 56, "wdata_comdat_b"), + T_COFF_DefReloc(X64_Addr64, 64, "ro_code_comdat_a"), + T_COFF_DefReloc(X64_Addr64, 72, "ro_code_comdat_b"), + T_COFF_DefReloc(X64_Addr64, 80, "rw_code_comdat_a"), + T_COFF_DefReloc(X64_Addr64, 88, "rw_code_comdat_b"), + {0} + } + }, + { "code_comdat_a", ".text$mn", str8_array_fixed(ret_text), .flags = "rx:code@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + { "code_comdat_b", ".text$mn", str8_array_fixed(ret_text), .flags = "rx:code@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + { "code_plain_a", ".text$mn", str8_array_fixed(ret_text), .flags = "rx:code@1" }, + { "code_plain_b", ".text$mn", str8_array_fixed(ret_text), .flags = "rx:code@1" }, + { "ro_code_comdat_a", ".text$mn", str8_array_fixed(ret_text), .flags = "r:code@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + { "ro_code_comdat_b", ".text$mn", str8_array_fixed(ret_text), .flags = "r:code@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + { "rw_code_comdat_a", ".text$mn", str8_array_fixed(ret_text), .flags = "rw:code@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + { "rw_code_comdat_b", ".text$mn", str8_array_fixed(ret_text), .flags = "rw:code@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + { "rdata_comdat_a", ".rdata$mn", str8_array_fixed(data_bytes), .flags = "r:data@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + { "rdata_comdat_b", ".rdata$mn", str8_array_fixed(data_bytes), .flags = "r:data@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + { "wdata_comdat_a", ".data$mn", str8_array_fixed(data_bytes), .flags = "rw:data@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + { "wdata_comdat_b", ".data$mn", str8_array_fixed(data_bytes), .flags = "rw:data@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Secdef("code_comdat_a", COFF_ComdatSelect_Any), + T_COFF_DefSymbol_Secdef("code_comdat_b", COFF_ComdatSelect_Any), + T_COFF_DefSymbol_Secdef("rdata_comdat_a", COFF_ComdatSelect_Any), + T_COFF_DefSymbol_Secdef("rdata_comdat_b", COFF_ComdatSelect_Any), + T_COFF_DefSymbol_Secdef("wdata_comdat_a", COFF_ComdatSelect_Any), + T_COFF_DefSymbol_Secdef("wdata_comdat_b", COFF_ComdatSelect_Any), + T_COFF_DefSymbol_Secdef("ro_code_comdat_a", COFF_ComdatSelect_Any), + T_COFF_DefSymbol_Secdef("ro_code_comdat_b", COFF_ComdatSelect_Any), + T_COFF_DefSymbol_Secdef("rw_code_comdat_a", COFF_ComdatSelect_Any), + T_COFF_DefSymbol_Secdef("rw_code_comdat_b", COFF_ComdatSelect_Any), + T_COFF_DefSymbol_ExternFunc("entry", "entry", 0), + T_COFF_DefSymbol_ExternFunc("code_comdat_a", "code_comdat_a", 0), + T_COFF_DefSymbol_ExternFunc("code_comdat_b", "code_comdat_b", 0), + T_COFF_DefSymbol_ExternFunc("code_plain_a", "code_plain_a", 0), + T_COFF_DefSymbol_ExternFunc("code_plain_b", "code_plain_b", 0), + T_COFF_DefSymbol_ExternFunc("ro_code_comdat_a", "ro_code_comdat_a", 0), + T_COFF_DefSymbol_ExternFunc("ro_code_comdat_b", "ro_code_comdat_b", 0), + T_COFF_DefSymbol_ExternFunc("rw_code_comdat_a", "rw_code_comdat_a", 0), + T_COFF_DefSymbol_ExternFunc("rw_code_comdat_b", "rw_code_comdat_b", 0), + T_COFF_DefSymbol_Extern("rdata_comdat_a", "rdata_comdat_a", 0), + T_COFF_DefSymbol_Extern("rdata_comdat_b", "rdata_comdat_b", 0), + T_COFF_DefSymbol_Extern("wdata_comdat_a", "wdata_comdat_a", 0), + T_COFF_DefSymbol_Extern("wdata_comdat_b", "wdata_comdat_b", 0), + T_COFF_DefSymbol_Extern("addresses", "addresses", 0), + {0} + } + })); + + B32 is_invoke_ok = t_invoke(str8_lit("link.exe"), str8_lit("/nologo /nodefaultlib /subsystem:console /entry:entry /out:ms_icf_flags.exe /opt:ref,icf /include:addresses ms_icf_flags.obj"), max_U64); + T_Ok(is_invoke_ok); + T_Ok(g_last_exit_code == 0); + + String8 exe = t_read_file(arena, str8_lit("ms_icf_flags.exe")); + T_Ok(exe.size); + + PE_BinInfo pe = pe_bin_info_from_data(arena, exe); + COFF_SectionHeader *section_table = (COFF_SectionHeader *)str8_substr(exe, pe.section_table_range).str; + String8 string_table = str8_substr(exe, pe.string_table_range); + COFF_SectionHeader *data_section = coff_section_header_from_name(string_table, section_table, pe.section_count, str8_lit(".data")); + T_Ok(data_section != 0); + T_Ok(data_section->foff + sizeof(addresses) <= exe.size); + + String8 data = str8_substr(exe, r1u64(data_section->foff, data_section->foff + sizeof(addresses))); + U64 code_comdat_a_addr = 0; + U64 code_comdat_b_addr = 0; + U64 code_plain_a_addr = 0; + U64 code_plain_b_addr = 0; + U64 rdata_comdat_a_addr = 0; + U64 rdata_comdat_b_addr = 0; + U64 wdata_comdat_a_addr = 0; + U64 wdata_comdat_b_addr = 0; + U64 ro_code_comdat_a_addr = 0; + U64 ro_code_comdat_b_addr = 0; + U64 rw_code_comdat_a_addr = 0; + U64 rw_code_comdat_b_addr = 0; + str8_deserial_read_struct(data, 0, &code_comdat_a_addr); + str8_deserial_read_struct(data, 8, &code_comdat_b_addr); + str8_deserial_read_struct(data, 16, &code_plain_a_addr); + str8_deserial_read_struct(data, 24, &code_plain_b_addr); + str8_deserial_read_struct(data, 32, &rdata_comdat_a_addr); + str8_deserial_read_struct(data, 40, &rdata_comdat_b_addr); + str8_deserial_read_struct(data, 48, &wdata_comdat_a_addr); + str8_deserial_read_struct(data, 56, &wdata_comdat_b_addr); + str8_deserial_read_struct(data, 64, &ro_code_comdat_a_addr); + str8_deserial_read_struct(data, 72, &ro_code_comdat_b_addr); + str8_deserial_read_struct(data, 80, &rw_code_comdat_a_addr); + str8_deserial_read_struct(data, 88, &rw_code_comdat_b_addr); + + T_Ok(code_comdat_a_addr != 0); + T_Ok(code_comdat_a_addr == code_comdat_b_addr); // executable code COMDATs fold + T_Ok(ro_code_comdat_a_addr == ro_code_comdat_b_addr); // read-only code COMDATs fold + T_Ok(code_plain_a_addr != code_plain_b_addr); // non-COMDAT code does not fold + T_Ok(wdata_comdat_a_addr != wdata_comdat_b_addr); // writable data COMDATs do not fold + T_Ok(rw_code_comdat_a_addr != rw_code_comdat_b_addr); // writable code COMDATs do not + T_Ok(rdata_comdat_a_addr == rdata_comdat_b_addr); // read-only data COMDATs fold +} +#endif + +#endif + +#if 1 + +internal B32 +t_read_exe_data_vaddrs(Arena *arena, String8 exe_path, U64 *vaddrs, U64 count) +{ + B32 result = 0; + String8 exe = t_read_file(arena, exe_path); + if (exe.size) { + PE_BinInfo pe = pe_bin_info_from_data(arena, exe); + COFF_SectionHeader *section_table = (COFF_SectionHeader *)str8_substr(exe, pe.section_table_range).str; + String8 string_table = str8_substr(exe, pe.string_table_range); + COFF_SectionHeader *data_section = coff_section_header_from_name(string_table, section_table, pe.section_count, str8_lit(".data")); + if (data_section != 0 && data_section->foff + count*sizeof(U64) <= exe.size) { + String8 data = str8_substr(exe, r1u64(data_section->foff, data_section->foff + count*sizeof(U64))); + result = str8_deserial_read_array(data, 0, vaddrs, count); + } + } + return result; +} + +TEST(icf_fold_two_funcs) { U8 same_text[] = { 0x48, 0x31, 0xc0, // xor rax, rax @@ -7004,7 +8083,7 @@ TEST(fold_two_funcs) t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe /opt:icf ident_funcs.obj"); T_Ok(g_last_exit_code == 0); - String8 exe = t_read_file(arena, str8_lit("ident_funcs.exe")); + String8 exe = t_read_file(arena, str8_lit("a.exe")); T_Ok(exe.size); PE_BinInfo pe = pe_bin_info_from_data(arena, exe); @@ -7036,8 +8115,124 @@ TEST(fold_two_funcs) T_Ok(str8_match(text_data, str8_array_fixed(expected_text), 0)); } +TEST(icf_associative_child_prevents_fold) +{ + U8 ret_text[] = { 0xc3 }; + U8 handler_a[] = { 1, 2, 3, 4 }; + U8 handler_b[] = { 4, 3, 2, 1 }; + U8 addresses[2 * sizeof(U64)] = {0}; -TEST(same_but_different) + T_Ok(t_write_def_obj("icf_associative_child.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "entry", ".text", str8_array_fixed(ret_text), .flags = "rx:code@1" }, + { "fn_a", ".text$mn", str8_array_fixed(ret_text), .flags = "rx:code@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + { "fn_b", ".text$mn", str8_array_fixed(ret_text), .flags = "rx:code@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + { "handler_a", ".xdata", str8_array_fixed(handler_a), .flags = "r:data@4", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + { "handler_b", ".xdata", str8_array_fixed(handler_b), .flags = "r:data@4", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + { + "addresses", ".data", str8_array_fixed(addresses), .flags = "rw:data@1", + .relocs = (T_COFF_DefReloc[]){ + T_COFF_DefReloc(X64_Addr64, 0, "fn_a"), + T_COFF_DefReloc(X64_Addr64, sizeof(U64), "fn_b"), + {0} + } + }, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Secdef("fn_a", COFF_ComdatSelect_NoDuplicates), + T_COFF_DefSymbol_Secdef("fn_b", COFF_ComdatSelect_NoDuplicates), + T_COFF_DefSymbol_Associative("handler_a", "fn_a"), + T_COFF_DefSymbol_Associative("handler_b", "fn_b"), + T_COFF_DefSymbol_ExternFunc("entry", "entry", 0), + T_COFF_DefSymbol_ExternFunc("fn_a", "fn_a", 0), + T_COFF_DefSymbol_ExternFunc("fn_b", "fn_b", 0), + T_COFF_DefSymbol_Extern("addresses", "addresses", 0), + {0} + } + })); + + t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe /opt:ref,icf /include:addresses icf_associative_child.obj"); + T_Ok(g_last_exit_code == 0); + + U64 vaddrs[ArrayCount(addresses) / sizeof(U64)] = {0}; + T_Ok(t_read_exe_data_vaddrs(arena, str8_lit("a.exe"), vaddrs, ArrayCount(vaddrs))); + T_Ok(vaddrs[0] != 0); + T_Ok(vaddrs[1] != 0); + T_Ok(vaddrs[0] != vaddrs[1]); +} + +TEST(icf_comdat_reloc_targets_fold) +{ + U8 fn_text[] = { + 0x48, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, // mov rax, shared + 0xc3, // ret + }; + U8 shared_data[] = { 0 }; + U8 entry_text[] = { 0xc3 }; + U8 addresses[2 * sizeof(U64)] = {0}; + + T_Ok(t_write_def_obj("icf_comdat_reloc_a.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "fn_a", ".text$mn", str8_array_fixed(fn_text), .flags = "rx:code@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT, .relocs = (T_COFF_DefReloc[]){ T_COFF_DefReloc(X64_Addr64, 2, "shared_local_a"), {0} } }, + { "shared", ".rdata", str8_array_fixed(shared_data), .flags = "r:data@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Secdef("fn_a", COFF_ComdatSelect_NoDuplicates), + T_COFF_DefSymbol_Secdef("shared", COFF_ComdatSelect_Any), + T_COFF_DefSymbol_ExternFunc("fn_a", "fn_a", 0), + T_COFF_DefSymbol_Extern("shared", "shared", 0), + T_COFF_DefSymbol_Static("shared_local_a", "shared", 0), + {0} + } + })); + + T_Ok(t_write_def_obj("icf_comdat_reloc_b.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "fn_b", ".text$mn", str8_array_fixed(fn_text), .flags = "rx:code@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT, .relocs = (T_COFF_DefReloc[]){ T_COFF_DefReloc(X64_Addr64, 2, "shared_local_b"), {0} } }, + { "shared", ".rdata", str8_array_fixed(shared_data), .flags = "r:data@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Secdef("fn_b", COFF_ComdatSelect_NoDuplicates), + T_COFF_DefSymbol_Secdef("shared", COFF_ComdatSelect_Any), + T_COFF_DefSymbol_ExternFunc("fn_b", "fn_b", 0), + T_COFF_DefSymbol_Extern("shared", "shared", 0), + T_COFF_DefSymbol_Static("shared_local_b", "shared", 0), + {0} + } + })); + + T_Ok(t_write_def_obj("icf_comdat_reloc_entry.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "entry", ".text", str8_array_fixed(entry_text), .flags = "rx:code@1" }, + { "addresses", ".data", str8_array_fixed(addresses), .flags = "rw:data@1", .relocs = (T_COFF_DefReloc[]){ T_COFF_DefReloc(X64_Addr64, 0, "fn_a"), T_COFF_DefReloc(X64_Addr64, sizeof(U64), "fn_b"), {0} } }, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_ExternFunc("entry", "entry", 0), + T_COFF_DefSymbol_UndefFunc("fn_a"), + T_COFF_DefSymbol_UndefFunc("fn_b"), + T_COFF_DefSymbol_Extern("addresses", "addresses", 0), + {0} + } + })); + + t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe /opt:ref,icf /include:addresses icf_comdat_reloc_entry.obj icf_comdat_reloc_a.obj icf_comdat_reloc_b.obj"); + T_Ok(g_last_exit_code == 0); + + U64 vaddrs[ArrayCount(addresses) / sizeof(U64)] = {0}; + T_Ok(t_read_exe_data_vaddrs(arena, str8_lit("a.exe"), vaddrs, ArrayCount(vaddrs))); + T_Ok(vaddrs[0] != 0); + T_Ok(vaddrs[0] == vaddrs[1]); +} + +TEST(icf_same_but_different) { U8 text[] = { 0xe8, 0x00, 0x00, 0x00, 0x00, // call $ @@ -7138,8 +8333,7 @@ TEST(same_but_different) } } - -TEST(fold_diamond) +TEST(icf_fold_diamond) { U8 call_b_and_c[] = { 0xe8, 0x00, 0x00, 0x00, 0x00, @@ -7231,8 +8425,7 @@ TEST(fold_diamond) } } - -TEST(cyclic_icf) +TEST(icf_cyclic_icf) { U8 text[] = { 0xe8, 0x00, 0x00, 0x00, 0x00, @@ -7242,14 +8435,14 @@ TEST(cyclic_icf) .machine = T_COFF_DefSetMachine(X64), .sections = (T_COFF_DefSection[]){ { - "a", ".text", str8_array_fixed(text), .flags = "rx:code", + "a", ".text", str8_array_fixed(text), .flags = "rx:code", .raw_flags = COFF_SectionFlag_LnkCOMDAT, .relocs = (T_COFF_DefReloc[]){ T_COFF_DefReloc(X64_Rel32, 1, "b"), {0} } }, { - "b", ".text", str8_array_fixed(text), .flags = "rx:code", + "b", ".text", str8_array_fixed(text), .flags = "rx:code", .raw_flags = COFF_SectionFlag_LnkCOMDAT, .relocs = (T_COFF_DefReloc[]){ T_COFF_DefReloc(X64_Rel32, 1, "a"), {0} @@ -7258,6 +8451,8 @@ TEST(cyclic_icf) {0} }, .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Secdef("a", COFF_ComdatSelect_NoDuplicates), + T_COFF_DefSymbol_Secdef("b", COFF_ComdatSelect_NoDuplicates), T_COFF_DefSymbol_Extern("a", "a", 0), T_COFF_DefSymbol_Static("b", "b", 0), {0} @@ -7270,11 +8465,8 @@ TEST(cyclic_icf) // validate output { U8 expected_text[] = { - 0xe8, 0x0b, 0x00, 0x00, 0x00, // a + 0xe8, 0xfb, 0xff, 0xff, 0xff, // a and b folded into a self-call 0xc3, - 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, - 0xe8, 0xeb, 0xff, 0xff, 0xff, // b - 0xc3, }; String8 exe = t_read_file(arena, str8_lit("a.exe")); @@ -7293,8 +8485,190 @@ TEST(cyclic_icf) } } +// ICF must preserve identical sections with different symbol targets +TEST(icf_reloc_target_symbol_types_do_not_fold) +{ + U8 reloc_text[] = { + 0x48, 0xc7, 0xc0, 0x00, 0x00, 0x00, 0x00, // mov rax, target + 0xc3 // ret + }; + U8 ret_text[] = { + 0xc3, // ret + }; + U8 target_data[] = { + 0x00, + }; + U8 addresses[3 * sizeof(U64)] = {0}; -TEST(fold_with_largest_align) + T_Ok(t_write_def_obj("icf_interp_entry.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "entry", ".text", str8_array_fixed(ret_text), .flags = "rx:code@1" }, + { + "addresses", ".data", str8_array_fixed(addresses), .flags = "rw:data@1", + .relocs = (T_COFF_DefReloc[]){ + T_COFF_DefReloc(X64_Addr64, 0, "fn_regular"), + T_COFF_DefReloc(X64_Addr64, 8, "fn_common"), + T_COFF_DefReloc(X64_Addr64, 16, "fn_abs"), + {0} + } + }, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_ExternFunc("entry", "entry", 0), + T_COFF_DefSymbol_Extern("addresses", "addresses", 0), + T_COFF_DefSymbol_Undef("fn_regular"), + T_COFF_DefSymbol_Undef("fn_common"), + T_COFF_DefSymbol_Undef("fn_abs"), + {0} + } + })); + + T_Ok(t_write_def_obj("icf_interp_regular.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "fn", ".text$mn", str8_array_fixed(reloc_text), .flags = "rx:code@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT, .relocs = (T_COFF_DefReloc[]){ T_COFF_DefReloc(X64_Rel32, 3, "target_regular"), {0} } }, + { "target", ".rdata$mn", str8_array_fixed(target_data), .flags = "r:data@1" }, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Secdef("fn", COFF_ComdatSelect_NoDuplicates), + T_COFF_DefSymbol_ExternFunc("fn_regular", "fn", 0), + T_COFF_DefSymbol_Extern("target_regular", "target", 0), + {0} + } + })); + + T_Ok(t_write_def_obj("icf_interp_common.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "fn", ".text$mn", str8_array_fixed(reloc_text), .flags = "rx:code@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT, .relocs = (T_COFF_DefReloc[]){ T_COFF_DefReloc(X64_Rel32, 3, "target_common"), {0} } }, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Secdef("fn", COFF_ComdatSelect_NoDuplicates), + T_COFF_DefSymbol_ExternFunc("fn_common", "fn", 0), + T_COFF_DefSymbol_Common("target_common", 8), + {0} + } + })); + + T_Ok(t_write_def_obj("icf_interp_abs.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "fn", ".text$mn", str8_array_fixed(reloc_text), .flags = "rx:code@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT, .relocs = (T_COFF_DefReloc[]){ T_COFF_DefReloc(X64_Rel32, 3, "target_abs"), {0} } }, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Secdef("fn", COFF_ComdatSelect_NoDuplicates), + T_COFF_DefSymbol_ExternFunc("fn_abs", "fn", 0), + T_COFF_DefSymbol_AbsExtern("target_abs", 0x1234), + {0} + } + })); + + t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe /opt:ref,icf icf_interp_entry.obj icf_interp_regular.obj icf_interp_common.obj icf_interp_abs.obj"); + T_Ok(g_last_exit_code == 0); + + U64 fn_vaddrs[ArrayCount(addresses) / sizeof(U64)] = {0}; + T_Ok(t_read_exe_data_vaddrs(arena, str8_lit("a.exe"), fn_vaddrs, ArrayCount(fn_vaddrs))); + + for EachElement(i, fn_vaddrs) { + for (U64 j = i + 1; j < ArrayCount(fn_vaddrs); j += 1) { + T_Ok(fn_vaddrs[i] != fn_vaddrs[j]); + } + } +} + +// ICF must preserve sections with unresolved target symbols (with /FORCE) +TEST(icf_unresolved_reloc_targets_do_not_fold) +{ + U8 reloc_text[] = { + 0x48, 0xc7, 0xc0, 0x00, 0x00, 0x00, 0x00, // mov rax, target + 0xc3 // ret + }; + U8 ret_text[] = { + 0xc3, // ret + }; + U8 addresses[2 * sizeof(U64)] = {0}; + + T_Ok(t_write_def_obj("icf_unresolved_entry.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "entry", ".text", str8_array_fixed(ret_text), .flags = "rx:code@1" }, + { + "addresses", ".data", str8_array_fixed(addresses), .flags = "rw:data@1", + .relocs = (T_COFF_DefReloc[]){ + T_COFF_DefReloc(X64_Addr64, 0, "fn_undef"), + T_COFF_DefReloc(X64_Addr64, 8, "fn_weak"), + {0} + } + }, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_ExternFunc("entry", "entry", 0), + T_COFF_DefSymbol_Extern("addresses", "addresses", 0), + T_COFF_DefSymbol_Undef("fn_undef"), + T_COFF_DefSymbol_Undef("fn_weak"), + {0} + } + })); + + T_Ok(t_write_def_obj("icf_unresolved_undef.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "fn", ".text$mn", str8_array_fixed(reloc_text), .flags = "rx:code@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT, .relocs = (T_COFF_DefReloc[]){ T_COFF_DefReloc(X64_Rel32, 3, "target_undef"), {0} } }, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Secdef("fn", COFF_ComdatSelect_NoDuplicates), + T_COFF_DefSymbol_ExternFunc("fn_undef", "fn", 0), + T_COFF_DefSymbol_Undef("target_undef"), + {0} + } + })); + + T_Ok(t_write_def_obj("icf_unresolved_weak.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "fn", ".text$mn", str8_array_fixed(reloc_text), .flags = "rx:code@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT, .relocs = (T_COFF_DefReloc[]){ T_COFF_DefReloc(X64_Rel32, 3, "target_weak"), {0} } }, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Secdef("fn", COFF_ComdatSelect_NoDuplicates), + T_COFF_DefSymbol_ExternFunc("fn_weak", "fn", 0), + T_COFF_DefSymbol_AbsExtern("target_weak_fallback", 0), + T_COFF_DefSymbol_Weak("target_weak", COFF_WeakExt_NoLibrary, "target_weak_fallback"), + {0} + } + })); + + t_invoke_linkerf("/force /subsystem:console /entry:entry /out:a.exe /opt:ref,icf icf_unresolved_entry.obj icf_unresolved_undef.obj icf_unresolved_weak.obj"); + T_Ok(g_last_exit_code == 0); + + String8 exe = t_read_file(arena, str8_lit("a.exe")); + T_Ok(exe.size); + + PE_BinInfo pe = pe_bin_info_from_data(arena, exe); + COFF_SectionHeader *section_table = (COFF_SectionHeader *)str8_substr(exe, pe.section_table_range).str; + String8 string_table = str8_substr(exe, pe.string_table_range); + COFF_SectionHeader *data_section = coff_section_header_from_name(string_table, section_table, pe.section_count, str8_lit(".data")); + T_Ok(data_section != 0); + T_Ok(data_section->foff + sizeof(addresses) <= exe.size); + + String8 data = str8_substr(exe, r1u64(data_section->foff, data_section->foff + sizeof(addresses))); + U64 undef_vaddr = 0; + U64 weak_vaddr = 0; + str8_deserial_read_struct(data, 0, &undef_vaddr); + str8_deserial_read_struct(data, 8, &weak_vaddr); + T_Ok(undef_vaddr != 0); + T_Ok(weak_vaddr != 0); + T_Ok(undef_vaddr != weak_vaddr); +} + +TEST(icf_fold_with_largest_align) { U8 text[] = { 0x48, 0xc7, 0xc0, 0x01, 0x00, 0x00, 0x00, // mov rax, 1 @@ -7360,10 +8734,10 @@ TEST(fold_with_largest_align) T_Ok(t_write_file(str8_lit("a.obj"), a_obj)); T_Ok(t_write_file(str8_lit("b.obj"), b_obj)); - t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe a.obj"); + t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe /opt:icf a.obj"); T_Ok(g_last_exit_code == 0); - t_invoke_linkerf("/subsystem:console /entry:entry /out:b.exe b.obj"); + t_invoke_linkerf("/subsystem:console /entry:entry /out:b.exe /opt:icf b.obj"); T_Ok(g_last_exit_code == 0); U8 expected_text[] = { @@ -7415,6 +8789,560 @@ TEST(fold_with_largest_align) } } +TEST(icf_identical_bytes_different_color_spaces_do_not_fold) +{ + U8 same_bytes[] = { + 0xc3, + }; + U8 entry_text[] = { + 0xc3, + }; + U8 addresses[2 * sizeof(U64)] = {0}; + + T_Ok(t_write_def_obj("icf_color_spaces.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "entry", ".text", str8_array_fixed(entry_text), .flags = "rx:code@1" }, + { "text", ".text$mn", str8_array_fixed(same_bytes), .flags = "rx:code@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + { "vftable", ".rdata$mn", str8_array_fixed(same_bytes), .flags = "r:data@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + { + "addresses", ".data", str8_array_fixed(addresses), .flags = "rw:data@1", + .relocs = (T_COFF_DefReloc[]){ + T_COFF_DefReloc(X64_Addr64, 0, "text"), + T_COFF_DefReloc(X64_Addr64, 8, "??_7type@@6B@"), + {0} + } + }, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Secdef("text", COFF_ComdatSelect_NoDuplicates), + T_COFF_DefSymbol_Secdef("vftable", COFF_ComdatSelect_NoDuplicates), + T_COFF_DefSymbol_ExternFunc("entry", "entry", 0), + T_COFF_DefSymbol_ExternFunc("text", "text", 0), + T_COFF_DefSymbol_Extern("??_7type@@6B@", "vftable", 0), + T_COFF_DefSymbol_Extern("addresses", "addresses", 0), + {0} + } + })); + + t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe /opt:ref,icf /include:addresses icf_color_spaces.obj"); + T_Ok(g_last_exit_code == 0); + + String8 exe = t_read_file(arena, str8_lit("a.exe")); + T_Ok(exe.size); + + PE_BinInfo pe = pe_bin_info_from_data(arena, exe); + COFF_SectionHeader *section_table = (COFF_SectionHeader *)str8_substr(exe, pe.section_table_range).str; + String8 string_table = str8_substr(exe, pe.string_table_range); + COFF_SectionHeader *data_section = coff_section_header_from_name(string_table, section_table, pe.section_count, str8_lit(".data")); + T_Ok(data_section != 0); + T_Ok(data_section->foff + sizeof(addresses) <= exe.size); + + String8 data = str8_substr(exe, r1u64(data_section->foff, data_section->foff + sizeof(addresses))); + U64 text_vaddr = 0; + U64 vftable_vaddr = 0; + str8_deserial_read_struct(data, 0, &text_vaddr); + str8_deserial_read_struct(data, 8, &vftable_vaddr); + T_Ok(text_vaddr != 0); + T_Ok(vftable_vaddr != 0); + T_Ok(text_vaddr != vftable_vaddr); +} + +TEST(icf_multihop_reloc_target_colors_do_not_fold) +{ + U8 call_text[] = { + 0xe8, 0x00, 0x00, 0x00, 0x00, + 0xc3, + }; + U8 return_1[] = { + 0x48, 0xc7, 0xc0, 0x01, 0x00, 0x00, 0x00, + 0xc3, + }; + U8 return_2[] = { + 0x48, 0xc7, 0xc0, 0x02, 0x00, 0x00, 0x00, + 0xc3, + }; + U8 entry_text[] = { + 0xc3, + }; + U8 addresses[6 * sizeof(U64)] = {0}; + + T_Ok(t_write_def_obj("icf_multihop.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "entry", ".text", str8_array_fixed(entry_text), .flags = "rx:code@1" }, + { + "top_a", ".text$mn", str8_array_fixed(call_text), .flags = "rx:code@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT, + .relocs = (T_COFF_DefReloc[]){ T_COFF_DefReloc(X64_Rel32, 1, "mid_a"), {0} } + }, + { + "top_b", ".text$mn", str8_array_fixed(call_text), .flags = "rx:code@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT, + .relocs = (T_COFF_DefReloc[]){ T_COFF_DefReloc(X64_Rel32, 1, "mid_b"), {0} } + }, + { + "mid_a", ".text$mn", str8_array_fixed(call_text), .flags = "rx:code@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT, + .relocs = (T_COFF_DefReloc[]){ T_COFF_DefReloc(X64_Rel32, 1, "leaf_a"), {0} } + }, + { + "mid_b", ".text$mn", str8_array_fixed(call_text), .flags = "rx:code@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT, + .relocs = (T_COFF_DefReloc[]){ T_COFF_DefReloc(X64_Rel32, 1, "leaf_b"), {0} } + }, + { "leaf_a", ".text$mn", str8_array_fixed(return_1), .flags = "rx:code@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + { "leaf_b", ".text$mn", str8_array_fixed(return_2), .flags = "rx:code@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + { + "addresses", ".data", str8_array_fixed(addresses), .flags = "rw:data@1", + .relocs = (T_COFF_DefReloc[]){ + T_COFF_DefReloc(X64_Addr64, 0, "top_a"), + T_COFF_DefReloc(X64_Addr64, 8, "top_b"), + T_COFF_DefReloc(X64_Addr64, 16, "mid_a"), + T_COFF_DefReloc(X64_Addr64, 24, "mid_b"), + T_COFF_DefReloc(X64_Addr64, 32, "leaf_a"), + T_COFF_DefReloc(X64_Addr64, 40, "leaf_b"), + {0} + } + }, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Secdef("top_a", COFF_ComdatSelect_NoDuplicates), + T_COFF_DefSymbol_Secdef("top_b", COFF_ComdatSelect_NoDuplicates), + T_COFF_DefSymbol_Secdef("mid_a", COFF_ComdatSelect_NoDuplicates), + T_COFF_DefSymbol_Secdef("mid_b", COFF_ComdatSelect_NoDuplicates), + T_COFF_DefSymbol_Secdef("leaf_a", COFF_ComdatSelect_NoDuplicates), + T_COFF_DefSymbol_Secdef("leaf_b", COFF_ComdatSelect_NoDuplicates), + T_COFF_DefSymbol_ExternFunc("entry", "entry", 0), + T_COFF_DefSymbol_ExternFunc("top_a", "top_a", 0), + T_COFF_DefSymbol_ExternFunc("top_b", "top_b", 0), + T_COFF_DefSymbol_ExternFunc("mid_a", "mid_a", 0), + T_COFF_DefSymbol_ExternFunc("mid_b", "mid_b", 0), + T_COFF_DefSymbol_ExternFunc("leaf_a", "leaf_a", 0), + T_COFF_DefSymbol_ExternFunc("leaf_b", "leaf_b", 0), + T_COFF_DefSymbol_Extern("addresses", "addresses", 0), + {0} + } + })); + + t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe /opt:ref,icf /include:addresses icf_multihop.obj"); + T_Ok(g_last_exit_code == 0); + + U64 vaddrs[ArrayCount(addresses) / sizeof(U64)] = {0}; + T_Ok(t_read_exe_data_vaddrs(arena, str8_lit("a.exe"), vaddrs, ArrayCount(vaddrs))); + for EachIndex(i, ArrayCount(vaddrs)) { + T_Ok(vaddrs[i] != 0); + } + T_Ok(vaddrs[0] != vaddrs[1]); + T_Ok(vaddrs[2] != vaddrs[3]); + T_Ok(vaddrs[4] != vaddrs[5]); +} + +TEST(icf_comdat_symlink_chain) +{ + U8 ret_small[] = { 0xc3 }; + U8 ret_large[] = { 0xc3, 0x90 }; + U8 entry_text[] = { 0xc3 }; + U8 addresses[2 * sizeof(U64)] = {0}; + + T_Ok(t_write_def_obj("icf_chain_leader.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "leader", ".text$mn", str8_array_fixed(ret_large), .flags = "rx:code@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Secdef("leader", COFF_ComdatSelect_NoDuplicates), + T_COFF_DefSymbol_ExternFunc("leader", "leader", 0), + {0} + } + })); + + T_Ok(t_write_def_obj("icf_chain_duplicate.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "dup", ".text$mn", str8_array_fixed(ret_small), .flags = "rx:code@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + { + "addresses", ".data", str8_array_fixed(addresses), .flags = "rw:data@1", + .relocs = (T_COFF_DefReloc[]){ + T_COFF_DefReloc(X64_Addr64, 0, "local_dup"), + T_COFF_DefReloc(X64_Addr64, 8, "leader"), + {0} + } + }, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Secdef("dup", COFF_ComdatSelect_Largest), + T_COFF_DefSymbol_ExternFunc("dup", "dup", 0), + T_COFF_DefSymbol_Static("local_dup", "dup", 0), + T_COFF_DefSymbol_Extern("addresses", "addresses", 0), + T_COFF_DefSymbol_UndefFunc("leader"), + {0} + } + })); + + T_Ok(t_write_def_obj("icf_chain_selected.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "dup", ".text$mn", str8_array_fixed(ret_large), .flags = "rx:code@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Secdef("dup", COFF_ComdatSelect_Largest), + T_COFF_DefSymbol_ExternFunc("dup", "dup", 0), + {0} + } + })); + + T_Ok(t_write_def_obj("icf_chain_entry.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "entry", ".text", str8_array_fixed(entry_text), .flags = "rx:code@1" }, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_ExternFunc("entry", "entry", 0), + {0} + } + })); + + t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe /opt:ref,icf /include:addresses icf_chain_leader.obj icf_chain_duplicate.obj icf_chain_selected.obj icf_chain_entry.obj"); + T_Ok(g_last_exit_code == 0); + + U64 vaddrs[ArrayCount(addresses) / sizeof(U64)] = {0}; + T_Ok(t_read_exe_data_vaddrs(arena, str8_lit("a.exe"), vaddrs, ArrayCount(vaddrs))); + T_Ok(vaddrs[0] != 0); + T_Ok(vaddrs[0] == vaddrs[1]); +} + +TEST(icf_llvm_addrsig) +{ + char *main_c = "int foo() { return 123; }\n" + "int bar() { return 123; }\n" + "int main() {\n" + "int (*fn)() = &foo;\n" + "return fn != bar;\n" + "}\n"; + String8 main_path = t_make_file_path(arena, str8_lit("main.c")); + T_Ok(write_data_to_file_path(main_path, str8_cstring(main_c))); + + String8 main_obj_path = t_make_file_path(arena, str8_lit("main.obj"));; + t_invoke(t_clang_path(), str8f(arena, "%S -o %S -c -ffunction-sections -target x86_64-pc-windows-msvc", main_path, main_obj_path), max_U64); + T_Ok(g_last_exit_code == 0); + + String8 a_path = t_make_file_path(arena, str8_lit("a.exe")); + + t_invoke_linkerf("%S /opt:icf /out:a.exe libcmt.lib", main_obj_path); + T_Ok(g_last_exit_code == 0); + t_invoke(a_path, str8_zero(), max_U64); + T_Ok(g_last_exit_code == 1); + + t_invoke_linkerf("%S /opt:icf /out:a.exe libcmt.lib /llvm_addrsig:no", main_obj_path); + T_Ok(g_last_exit_code == 0); + t_invoke(a_path, str8_zero(), max_U64); + T_Ok(g_last_exit_code == 0); +} + +// .llvm_addrsig can name an undefined external whose definition is in another +// object; ICF must parse and mark the resolved symbol's object, not the referrer. +TEST(icf_llvm_addrsig_external_symbol) +{ + char *ref_c = "extern int ext_sig();\n" + "int (*ext_sig_addr)() = &ext_sig;\n" + "int entry() { return ext_sig_addr(); }\n"; + char *def_c = "int dummy0() { return 0; }\n" + "int dummy1() { return 1; }\n" + "int dummy2() { return 2; }\n" + "int dummy3() { return 3; }\n" + "int dummy4() { return 4; }\n" + "int ext_sig() { return 0; }\n"; + String8 ref_path = t_make_file_path(arena, str8_lit("ref.c")); + String8 def_path = t_make_file_path(arena, str8_lit("def.c")); + T_Ok(write_data_to_file_path(ref_path, str8_cstring(ref_c))); + T_Ok(write_data_to_file_path(def_path, str8_cstring(def_c))); + + String8 ref_obj_path = t_make_file_path(arena, str8_lit("ref.obj")); + String8 def_obj_path = t_make_file_path(arena, str8_lit("def.obj")); + t_invoke(t_clang_path(), str8f(arena, "%S -o %S -c -ffunction-sections -target x86_64-pc-windows-msvc", ref_path, ref_obj_path), max_U64); + T_Ok(g_last_exit_code == 0); + t_invoke(t_clang_path(), str8f(arena, "%S -o %S -c -ffunction-sections -target x86_64-pc-windows-msvc", def_path, def_obj_path), max_U64); + T_Ok(g_last_exit_code == 0); + + t_invoke_linkerf("%S %S /subsystem:console /entry:entry /opt:icf /out:addrsig_ext.exe libcmt.lib", ref_obj_path, def_obj_path); + T_Ok(g_last_exit_code == 0); +} + +TEST(icf_pdata_xdata_fold) +{ + U8 ret_text[] = { 0xc3 }; + U8 xdata[] = { 0x01, 0x00, 0x00, 0x00 }; + PE_IntelPdata pdata = {0}; + U8 entry_text[] = { 0xc3 }; + U8 addresses[4 * sizeof(U64)] = {0}; + + T_Ok(t_write_def_obj("icf_pdata_xdata_fold.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "entry", ".text", str8_array_fixed(entry_text), .flags = "rx:code@1" }, + { "fn_a", ".text$mn", str8_array_fixed(ret_text), .flags = "rx:code@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + { "fn_b", ".text$mn", str8_array_fixed(ret_text), .flags = "rx:code@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + { "xdata_a", ".xdata", str8_array_fixed(xdata), .flags = "r:data@4", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + { "xdata_b", ".xdata", str8_array_fixed(xdata), .flags = "r:data@4", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + { + "pdata_a", ".pdata", str8_struct(&pdata), .flags = "r:data@4", .raw_flags = COFF_SectionFlag_LnkCOMDAT, + .relocs = (T_COFF_DefReloc[]){ + T_COFF_DefReloc(X64_Addr32Nb, OffsetOf(PE_IntelPdata, voff_first), "fn_a"), + T_COFF_DefReloc(X64_Addr32Nb, OffsetOf(PE_IntelPdata, voff_one_past_last), "fn_a"), + T_COFF_DefReloc(X64_Addr32Nb, OffsetOf(PE_IntelPdata, voff_unwind_info), "$unwind$a"), + {0} + } + }, + { + "pdata_b", ".pdata", str8_struct(&pdata), .flags = "r:data@4", .raw_flags = COFF_SectionFlag_LnkCOMDAT, + .relocs = (T_COFF_DefReloc[]){ + T_COFF_DefReloc(X64_Addr32Nb, OffsetOf(PE_IntelPdata, voff_first), "fn_b"), + T_COFF_DefReloc(X64_Addr32Nb, OffsetOf(PE_IntelPdata, voff_one_past_last), "fn_b"), + T_COFF_DefReloc(X64_Addr32Nb, OffsetOf(PE_IntelPdata, voff_unwind_info), "$unwind$b"), + {0} + } + }, + { + "addresses", ".data", str8_array_fixed(addresses), .flags = "rw:data@1", + .relocs = (T_COFF_DefReloc[]){ + T_COFF_DefReloc(X64_Addr64, 0, "$pdata$a"), + T_COFF_DefReloc(X64_Addr64, 8, "$pdata$b"), + T_COFF_DefReloc(X64_Addr64, 16, "$unwind$a"), + T_COFF_DefReloc(X64_Addr64, 24, "$unwind$b"), + {0} + } + }, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Secdef("fn_a", COFF_ComdatSelect_NoDuplicates), + T_COFF_DefSymbol_Secdef("fn_b", COFF_ComdatSelect_NoDuplicates), + T_COFF_DefSymbol_Secdef("xdata_a", COFF_ComdatSelect_NoDuplicates), + T_COFF_DefSymbol_Secdef("xdata_b", COFF_ComdatSelect_NoDuplicates), + T_COFF_DefSymbol_Secdef("pdata_a", COFF_ComdatSelect_NoDuplicates), + T_COFF_DefSymbol_Secdef("pdata_b", COFF_ComdatSelect_NoDuplicates), + T_COFF_DefSymbol_ExternFunc("entry", "entry", 0), + T_COFF_DefSymbol_ExternFunc("fn_a", "fn_a", 0), + T_COFF_DefSymbol_ExternFunc("fn_b", "fn_b", 0), + T_COFF_DefSymbol_Extern("$unwind$a", "xdata_a", 0), + T_COFF_DefSymbol_Extern("$unwind$b", "xdata_b", 0), + T_COFF_DefSymbol_Extern("$pdata$a", "pdata_a", 0), + T_COFF_DefSymbol_Extern("$pdata$b", "pdata_b", 0), + T_COFF_DefSymbol_Extern("addresses", "addresses", 0), + {0} + } + })); + + t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe /opt:ref,icf /include:addresses icf_pdata_xdata_fold.obj"); + T_Ok(g_last_exit_code == 0); + + U64 vaddrs[4] = {0}; + T_Ok(t_read_exe_data_vaddrs(arena, str8_lit("a.exe"), vaddrs, ArrayCount(vaddrs))); + T_Ok(vaddrs[0] != 0); + T_Ok(vaddrs[0] == vaddrs[1]); + T_Ok(vaddrs[2] != 0); + T_Ok(vaddrs[2] == vaddrs[3]); +} + +TEST(icf_pdata_differs_by_function_color) +{ + U8 ret_1[] = { 0xb8, 1, 0, 0, 0, 0xc3 }; + U8 ret_2[] = { 0xb8, 2, 0, 0, 0, 0xc3 }; + U8 xdata[] = { 0x01, 0x00, 0x00, 0x00 }; + PE_IntelPdata pdata = {0}; + U8 entry_text[] = { 0xc3 }; + U8 addresses[3 * sizeof(U64)] = {0}; + + T_Ok(t_write_def_obj("icf_pdata_diff.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "entry", ".text", str8_array_fixed(entry_text), .flags = "rx:code@1" }, + { "fn_a", ".text$mn", str8_array_fixed(ret_1), .flags = "rx:code@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + { "fn_b", ".text$mn", str8_array_fixed(ret_2), .flags = "rx:code@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + { "xdata_a", ".xdata", str8_array_fixed(xdata), .flags = "r:data@4", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + { "xdata_b", ".xdata", str8_array_fixed(xdata), .flags = "r:data@4", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + { "pdata_a", ".pdata", str8_struct(&pdata), .flags = "r:data@4", .raw_flags = COFF_SectionFlag_LnkCOMDAT, .relocs = (T_COFF_DefReloc[]){ T_COFF_DefReloc(X64_Addr32Nb, 0, "fn_a"), T_COFF_DefReloc(X64_Addr32Nb, 4, "fn_a"), T_COFF_DefReloc(X64_Addr32Nb, 8, "$unwind$a"), {0} } }, + { "pdata_b", ".pdata", str8_struct(&pdata), .flags = "r:data@4", .raw_flags = COFF_SectionFlag_LnkCOMDAT, .relocs = (T_COFF_DefReloc[]){ T_COFF_DefReloc(X64_Addr32Nb, 0, "fn_b"), T_COFF_DefReloc(X64_Addr32Nb, 4, "fn_b"), T_COFF_DefReloc(X64_Addr32Nb, 8, "$unwind$b"), {0} } }, + { "addresses", ".data", str8_array_fixed(addresses), .flags = "rw:data@1", .relocs = (T_COFF_DefReloc[]){ T_COFF_DefReloc(X64_Addr64, 0, "$pdata$a"), T_COFF_DefReloc(X64_Addr64, 8, "$pdata$b"), T_COFF_DefReloc(X64_Addr64, 16, "$unwind$a"), {0} } }, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Secdef("fn_a", COFF_ComdatSelect_NoDuplicates), T_COFF_DefSymbol_Secdef("fn_b", COFF_ComdatSelect_NoDuplicates), + T_COFF_DefSymbol_Secdef("xdata_a", COFF_ComdatSelect_NoDuplicates), T_COFF_DefSymbol_Secdef("xdata_b", COFF_ComdatSelect_NoDuplicates), + T_COFF_DefSymbol_Secdef("pdata_a", COFF_ComdatSelect_NoDuplicates), T_COFF_DefSymbol_Secdef("pdata_b", COFF_ComdatSelect_NoDuplicates), + T_COFF_DefSymbol_ExternFunc("entry", "entry", 0), T_COFF_DefSymbol_ExternFunc("fn_a", "fn_a", 0), T_COFF_DefSymbol_ExternFunc("fn_b", "fn_b", 0), + T_COFF_DefSymbol_Extern("$unwind$a", "xdata_a", 0), T_COFF_DefSymbol_Extern("$unwind$b", "xdata_b", 0), + T_COFF_DefSymbol_Extern("$pdata$a", "pdata_a", 0), T_COFF_DefSymbol_Extern("$pdata$b", "pdata_b", 0), T_COFF_DefSymbol_Extern("addresses", "addresses", 0), + {0} + } + })); + + t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe /opt:ref,icf /include:addresses icf_pdata_diff.obj"); + T_Ok(g_last_exit_code == 0); + + U64 vaddrs[3] = {0}; + T_Ok(t_read_exe_data_vaddrs(arena, str8_lit("a.exe"), vaddrs, ArrayCount(vaddrs))); + T_Ok(vaddrs[0] != 0); + T_Ok(vaddrs[1] != 0); + T_Ok(vaddrs[0] != vaddrs[1]); +} + +TEST(icf_vftable_and_vbtable_policy) +{ + U8 table_bytes[sizeof(U64)] = {0}; + U8 target_a[] = { 1 }; + U8 target_b[] = { 2 }; + U8 entry_text[] = { 0xc3 }; + U8 addresses[8 * sizeof(U64)] = {0}; + + T_Ok(t_write_def_obj("icf_tables.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "entry", ".text", str8_array_fixed(entry_text), .flags = "rx:code@1" }, + { "vf_a", ".rdata$mn", str8_array_fixed(table_bytes), .flags = "r:data@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + { "vf_b", ".rdata$mn", str8_array_fixed(table_bytes), .flags = "r:data@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + { "vb_a", ".rdata$mn", str8_array_fixed(table_bytes), .flags = "r:data@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + { "vb_b", ".rdata$mn", str8_array_fixed(table_bytes), .flags = "r:data@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + { "rtti_a", ".rdata$mn", str8_array_fixed(target_a), .flags = "r:data@1" }, + { "rtti_b", ".rdata$mn", str8_array_fixed(target_b), .flags = "r:data@1" }, + { "vf_ref_a", ".rdata$mn", str8_array_fixed(table_bytes), .flags = "r:data@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT, .relocs = (T_COFF_DefReloc[]){ T_COFF_DefReloc(X64_Addr64, 0, "rtti_a"), {0} } }, + { "vf_ref_b", ".rdata$mn", str8_array_fixed(table_bytes), .flags = "r:data@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT, .relocs = (T_COFF_DefReloc[]){ T_COFF_DefReloc(X64_Addr64, 0, "rtti_b"), {0} } }, + { "vb_ref_a", ".rdata$mn", str8_array_fixed(table_bytes), .flags = "r:data@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT, .relocs = (T_COFF_DefReloc[]){ T_COFF_DefReloc(X64_Addr64, 0, "rtti_a"), {0} } }, + { "vb_ref_b", ".rdata$mn", str8_array_fixed(table_bytes), .flags = "r:data@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT, .relocs = (T_COFF_DefReloc[]){ T_COFF_DefReloc(X64_Addr64, 0, "rtti_b"), {0} } }, + { "addresses", ".data", str8_array_fixed(addresses), .flags = "rw:data@1", .relocs = (T_COFF_DefReloc[]){ + T_COFF_DefReloc(X64_Addr64, 0, "??_7a@@6B@"), T_COFF_DefReloc(X64_Addr64, 8, "??_7b@@6B@"), + T_COFF_DefReloc(X64_Addr64, 16, "??_8a@@7B@"), T_COFF_DefReloc(X64_Addr64, 24, "??_8b@@7B@"), + T_COFF_DefReloc(X64_Addr64, 32, "??_7ra@@6B@"), T_COFF_DefReloc(X64_Addr64, 40, "??_7rb@@6B@"), + T_COFF_DefReloc(X64_Addr64, 48, "??_8ra@@7B@"), T_COFF_DefReloc(X64_Addr64, 56, "??_8rb@@7B@"), {0} } }, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Secdef("vf_a", COFF_ComdatSelect_NoDuplicates), T_COFF_DefSymbol_Secdef("vf_b", COFF_ComdatSelect_NoDuplicates), + T_COFF_DefSymbol_Secdef("vb_a", COFF_ComdatSelect_NoDuplicates), T_COFF_DefSymbol_Secdef("vb_b", COFF_ComdatSelect_NoDuplicates), + T_COFF_DefSymbol_Secdef("vf_ref_a", COFF_ComdatSelect_NoDuplicates), T_COFF_DefSymbol_Secdef("vf_ref_b", COFF_ComdatSelect_NoDuplicates), + T_COFF_DefSymbol_Secdef("vb_ref_a", COFF_ComdatSelect_NoDuplicates), T_COFF_DefSymbol_Secdef("vb_ref_b", COFF_ComdatSelect_NoDuplicates), + T_COFF_DefSymbol_ExternFunc("entry", "entry", 0), + T_COFF_DefSymbol_Extern("??_7a@@6B@", "vf_a", 0), T_COFF_DefSymbol_Extern("??_7b@@6B@", "vf_b", 0), + T_COFF_DefSymbol_Extern("??_8a@@7B@", "vb_a", 0), T_COFF_DefSymbol_Extern("??_8b@@7B@", "vb_b", 0), + T_COFF_DefSymbol_Extern("??_7ra@@6B@", "vf_ref_a", 0), T_COFF_DefSymbol_Extern("??_7rb@@6B@", "vf_ref_b", 0), + T_COFF_DefSymbol_Extern("??_8ra@@7B@", "vb_ref_a", 0), T_COFF_DefSymbol_Extern("??_8rb@@7B@", "vb_ref_b", 0), + T_COFF_DefSymbol_Extern("rtti_a", "rtti_a", 0), T_COFF_DefSymbol_Extern("rtti_b", "rtti_b", 0), T_COFF_DefSymbol_Extern("addresses", "addresses", 0), + {0} + } + })); + + t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe /opt:ref,icf /include:addresses icf_tables.obj"); + T_Ok(g_last_exit_code == 0); + + U64 vaddrs[8] = {0}; + T_Ok(t_read_exe_data_vaddrs(arena, str8_lit("a.exe"), vaddrs, ArrayCount(vaddrs))); + T_Ok(vaddrs[0] == vaddrs[1]); + T_Ok(vaddrs[2] != vaddrs[3]); + T_Ok(vaddrs[4] != vaddrs[5]); + T_Ok(vaddrs[6] != vaddrs[7]); +} + +TEST(icf_readonly_non_vftable_data_policy) +{ + U8 data_bytes[] = { 1, 2, 3, 4 }; + U8 entry_text[] = { 0xc3 }; + U8 addresses[4 * sizeof(U64)] = {0}; + + T_Ok(t_write_def_obj("icf_rdata_policy.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "entry", ".text", str8_array_fixed(entry_text), .flags = "rx:code@1" }, + { "data_a", ".rdata$mn", str8_array_fixed(data_bytes), .flags = "r:data@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + { "data_b", ".rdata$mn", str8_array_fixed(data_bytes), .flags = "r:data@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + { "data_c", ".rdata$mn", str8_array_fixed(data_bytes), .flags = "r:data@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + { "data_d", ".rdata$mn", str8_array_fixed(data_bytes), .flags = "r:data@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + { "addresses", ".data", str8_array_fixed(addresses), .flags = "rw:data@1", .relocs = (T_COFF_DefReloc[]){ T_COFF_DefReloc(X64_Addr64, 0, "data_a"), T_COFF_DefReloc(X64_Addr64, 8, "data_b"), T_COFF_DefReloc(X64_Addr64, 16, "data_c"), T_COFF_DefReloc(X64_Addr64, 24, "data_d"), {0} } }, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Secdef("data_a", COFF_ComdatSelect_NoDuplicates), T_COFF_DefSymbol_Secdef("data_b", COFF_ComdatSelect_NoDuplicates), + T_COFF_DefSymbol_Secdef("data_c", COFF_ComdatSelect_Any), T_COFF_DefSymbol_Secdef("data_d", COFF_ComdatSelect_Any), + T_COFF_DefSymbol_ExternFunc("entry", "entry", 0), T_COFF_DefSymbol_Extern("data_a", "data_a", 0), T_COFF_DefSymbol_Extern("data_b", "data_b", 0), T_COFF_DefSymbol_Extern("data_c", "data_c", 0), T_COFF_DefSymbol_Extern("data_d", "data_d", 0), T_COFF_DefSymbol_Extern("addresses", "addresses", 0), + {0} + } + })); + + t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe /opt:ref,icf /include:addresses icf_rdata_policy.obj"); + T_Ok(g_last_exit_code == 0); + + U64 vaddrs[4] = {0}; + T_Ok(t_read_exe_data_vaddrs(arena, str8_lit("a.exe"), vaddrs, ArrayCount(vaddrs))); + T_Ok(vaddrs[0] != 0); + T_Ok(vaddrs[1] != 0); + T_Ok(vaddrs[0] != vaddrs[1]); + T_Ok(vaddrs[2] != 0); + T_Ok(vaddrs[2] == vaddrs[3]); +} + +TEST(icf_cpp_identical_functions_fold) +{ + char source[] = + "extern \"C\" __declspec(noinline) int a(void) { return 42; }\n" + "extern \"C\" __declspec(noinline) int b(void) { return 42; }\n" + "extern \"C\" int (* volatile pa)(void) = a;\n" + "extern \"C\" int (* volatile pb)(void) = b;\n" + "extern \"C\" int entry(void) { return pa == pb ? 0 : 1; }\n"; + + T_Ok(t_write_file(str8_lit("icf_cpp_fold.cpp"), str8_cstring(source))); + T_Ok(t_invoke_cl("/nologo /c /O2 /Gy /Zc:preprocessor /Fo:icf_cpp_fold.obj icf_cpp_fold.cpp")); + T_Ok(g_last_exit_code == 0); + + t_invoke_linkerf("/nodefaultlib /subsystem:console /entry:entry /out:icf_cpp_fold.exe /opt:ref,icf /include:pa /include:pb icf_cpp_fold.obj"); + T_Ok(g_last_exit_code == 0); + + U64 vaddrs[2] = {0}; + T_Ok(t_read_exe_data_vaddrs(arena, str8_lit("icf_cpp_fold.exe"), vaddrs, ArrayCount(vaddrs))); + T_Ok(vaddrs[0] != 0); + T_Ok(vaddrs[0] == vaddrs[1]); +} + +TEST(icf_cpp_multihop_functions_do_not_fold) +{ + char source[] = + "__declspec(noinline) int leaf_a(void) { return 1; }\n" + "__declspec(noinline) int leaf_b(void) { return 2; }\n" + "__declspec(noinline) int mid_a(void) { return leaf_a(); }\n" + "__declspec(noinline) int mid_b(void) { return leaf_b(); }\n" + "__declspec(noinline) int top_a(void) { return mid_a(); }\n" + "__declspec(noinline) int top_b(void) { return mid_b(); }\n" + "int (* volatile p_top_a)(void) = top_a;\n" + "int (* volatile p_top_b)(void) = top_b;\n" + "int (* volatile p_mid_a)(void) = mid_a;\n" + "int (* volatile p_mid_b)(void) = mid_b;\n" + "int (* volatile p_leaf_a)(void) = leaf_a;\n" + "int (* volatile p_leaf_b)(void) = leaf_b;\n" + "int entry(void) {\n" + " if (p_top_a == p_top_b) { return 1; }\n" + " if (p_mid_a == p_mid_b) { return 2; }\n" + " if (p_leaf_a == p_leaf_b) { return 3; }\n" + " return 0;\n" + "}\n"; + + T_Ok(t_write_file(str8_lit("icf_cpp_multihop.c"), str8_cstring(source))); + T_Ok(t_invoke_cl("/nologo /c /O2 /Gy /Zc:preprocessor /Fo:icf_cpp_multihop.obj icf_cpp_multihop.c")); + T_Ok(g_last_exit_code == 0); + + t_invoke_linkerf("/nodefaultlib /subsystem:console /entry:entry /out:icf_cpp_multihop.exe /opt:ref,icf /include:p_top_a /include:p_top_b /include:p_mid_a /include:p_mid_b /include:p_leaf_a /include:p_leaf_b icf_cpp_multihop.obj"); + T_Ok(g_last_exit_code == 0); + + U64 vaddrs[6] = {0}; + T_Ok(t_read_exe_data_vaddrs(arena, str8_lit("icf_cpp_multihop.exe"), vaddrs, ArrayCount(vaddrs))); + for EachIndex(i, ArrayCount(vaddrs)) { + T_Ok(vaddrs[i] != 0); + } + T_Ok(vaddrs[0] != vaddrs[1]); + T_Ok(vaddrs[2] != vaddrs[3]); + T_Ok(vaddrs[4] != vaddrs[5]); +} + #endif #if 0 @@ -7576,4 +9504,3 @@ TEST(lib_member_reloc_apply_off_out_of_bounds) T_Ok(g_last_exit_code != 0); } #endif - diff --git a/src/linker/thread_pool/thread_pool.c b/src/linker/thread_pool/thread_pool.c index 0d16213c..3dc2c146 100644 --- a/src/linker/thread_pool/thread_pool.c +++ b/src/linker/thread_pool/thread_pool.c @@ -4,7 +4,7 @@ internal void tp_run_tasks(TP_Context *pool, TP_Worker *worker) { - barrier_wait(pool->barrier); + barrier_wait(pool->run_barrier); for (;;) { 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); 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 ins_atomic_u64_inc_eval(&pool->task_done); } - barrier_wait(pool->barrier); + barrier_wait(pool->run_barrier); } internal void @@ -74,6 +71,7 @@ tp_alloc(Arena *arena, U32 worker_count, U32 max_worker_count, String8 name) // init pool TP_Context *pool = push_array(arena, TP_Context, 1); pool->exec_semaphore = exec_semaphore; + pool->run_barrier = barrier_alloc(worker_count); pool->barrier = barrier_alloc(worker_count); pool->is_live = 1; pool->worker_count = worker_count; @@ -113,6 +111,7 @@ tp_release(TP_Context *pool) if (is_shared) { semaphore_release(pool->exec_semaphore); } + barrier_release(pool->run_barrier); barrier_release(pool->barrier); 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 (*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); semaphore_drop_count(pool->exec_semaphore, drop_count); } // run tasks on main worker tp_run_tasks(pool, pool->worker_arr); + Assert(pool->task_done == task_count); } } diff --git a/src/linker/thread_pool/thread_pool.h b/src/linker/thread_pool/thread_pool.h index 8d7f8f22..f89f1785 100644 --- a/src/linker/thread_pool/thread_pool.h +++ b/src/linker/thread_pool/thread_pool.h @@ -32,6 +32,7 @@ typedef struct TP_Context Semaphore exec_semaphore; Semaphore task_semaphore; Semaphore main_semaphore; + Barrier run_barrier; Barrier barrier; void *broadcast; 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 Rng1U64 * tp_divide_work(Arena *arena, U64 item_count, U32 worker_count); #define tp_broadcast(p) tp_broadcast_(tp, task_id, p, sizeof(*p)) - diff --git a/src/linux/base/linux_base.c b/src/linux/base/linux_base.c index 67c26313..88999e5e 100644 --- a/src/linux/base/linux_base.c +++ b/src/linux/base/linux_base.c @@ -364,6 +364,7 @@ set_platform_thread_name(String8 name) internal Thread thread_launch(ThreadEntryPointFunctionType *f, void *p) { + ProfBeginFunction(); LNX_Entity *entity = lnx_entity_alloc(LNX_EntityKind_Thread); entity->thread.func = f; entity->thread.ptr = p; @@ -376,6 +377,7 @@ thread_launch(ThreadEntryPointFunctionType *f, void *p) } } Thread handle = {(U64)entity}; + ProfEnd(); return handle; } diff --git a/src/linux/window_manager/linux_window_manager.c b/src/linux/window_manager/linux_window_manager.c index 16ff1117..fc2a6147 100644 --- a/src/linux/window_manager/linux_window_manager.c +++ b/src/linux/window_manager/linux_window_manager.c @@ -757,7 +757,7 @@ wm_graphical_message(B32 error, String8 title, String8 message) } internal String8 -wm_graphical_pick_file(Arena *arena, String8 initial_path) +wm_graphical_pick_file(Arena *arena, String8 title, String8 initial_path) { return str8_zero(); } diff --git a/src/llvm/llvm.c b/src/llvm/llvm.c index f5a0f364..581ebe71 100644 --- a/src/llvm/llvm.c +++ b/src/llvm/llvm.c @@ -22,3 +22,24 @@ llvm_hash_size_from_alg(LLVM_GHashAlg v) } return 0; } + +internal B32 +llvm_is_bitcode(String8 data) +{ + if (data.size < 4) { + return 0; + } + + // raw LLVM bitcode magic + if (data.str[0] == 'B' && data.str[1] == 'C' && data.str[2] == 0xc0 && data.str[3] == 0xde) { + return 1; + } + + // LLVM bitcode wrapper magic + if (data.str[0] == 0xde && data.str[1] == 0xc0 && data.str[2] == 0x17 && data.str[3] == 0x0b) { + return 1; + } + + return 0; +} + diff --git a/src/llvm/llvm.h b/src/llvm/llvm.h index 0ddc399f..34ba614b 100644 --- a/src/llvm/llvm.h +++ b/src/llvm/llvm.h @@ -25,6 +25,6 @@ typedef struct LLVM_GHash internal String8 llvm_string_from_ghash_alg(LLVM_GHashAlg v); internal U64 llvm_hash_size_from_alg(LLVM_GHashAlg v); +internal B32 llvm_is_bitcode(String8 data); #endif // LLVM_H - diff --git a/src/msvc_crt/msvc_crt.h b/src/msvc_crt/msvc_crt.h index 4ad6354c..e808c335 100644 --- a/src/msvc_crt/msvc_crt.h +++ b/src/msvc_crt/msvc_crt.h @@ -33,6 +33,12 @@ // PE_TLSHeader32 or PE_TLSHeader64, according to machine type. #define MSCRT_TLS_SYMBOL_NAME "_tls_used" +// vftable symbols +#define MSCRT_VFTABLE_SYMBOL_PREFIX "??_7" + +// vbptr symbols +#define MSCRT_VBTABLE_SYMBOL_PREFIX "??_8" + //////////////////////////////// // feature flags in absolute symbol @feat.00 @@ -377,4 +383,3 @@ mscrt_catch_blocks_from_data_x8664(Arena *arena, internal String8 mscrt_string_from_eh_adjectives(Arena *arena, MSCRT_EhHandlerTypeFlags adjectives); #endif // MSVC_CRT - diff --git a/src/pdb/pdb.h b/src/pdb/pdb.h index a2988b8e..771ddad4 100644 --- a/src/pdb/pdb.h +++ b/src/pdb/pdb.h @@ -223,11 +223,11 @@ typedef struct PDB_DbiHeader // "ModuleInfo" DBI range -typedef U32 PDB_DbiSectionContribVersion; -#define PDB_DbiSectionContribVersion_1 (0xeffe0000u + 19970605u) -#define PDB_DbiSectionContribVersion_2 (0xeffe0000u + 20140516u) +typedef U32 PDB_DbiSCVersion; +#define PDB_DbiSCVersion_1 (0xeffe0000u + 19970605u) +#define PDB_DbiSCVersion_2 (0xeffe0000u + 20140516u) -typedef struct PDB_DbiSectionContrib40 +typedef struct PDB_DbiSC40 { CV_SectionIndex sec; U16 pad0; @@ -236,27 +236,27 @@ typedef struct PDB_DbiSectionContrib40 U32 flags; CV_ModIndex mod; U16 pad1; -} PDB_DbiSectionContrib40; +} PDB_DbiSC40; -typedef struct PDB_DbiSectionContrib +typedef struct PDB_DbiSC { - PDB_DbiSectionContrib40 base; + PDB_DbiSC40 base; U32 data_crc; U32 reloc_crc; -} PDB_DbiSectionContrib; +} PDB_DbiSC; -typedef struct PDB_DbiSectionContrib2 +typedef struct PDB_DbiSC2 { - PDB_DbiSectionContrib40 base; + PDB_DbiSC40 base; U32 data_crc; U32 reloc_crc; U32 sec_coff; -} PDB_DbiSectionContrib2; +} PDB_DbiSC2; typedef struct PDB_DbiCompUnitHeader { U32 unused; - PDB_DbiSectionContrib contribution; + PDB_DbiSC contribution; U16 flags; // unknown MSF_StreamNumber sn; diff --git a/src/pdb/pdb_parse.c b/src/pdb/pdb_parse.c index ac7ca4a9..5b6cf72e 100644 --- a/src/pdb/pdb_parse.c +++ b/src/pdb/pdb_parse.c @@ -700,9 +700,9 @@ pdb_comp_unit_contribution_array_from_data(Arena *arena, String8 data, COFF_Sect { PDB_CompUnitContribution *contributions = 0; U64 count = 0; - if(data.size >= sizeof(PDB_DbiSectionContribVersion)) + if(data.size >= sizeof(PDB_DbiSCVersion)) { - PDB_DbiSectionContribVersion *version = (PDB_DbiSectionContribVersion*)data.str; + PDB_DbiSCVersion *version = (PDB_DbiSCVersion*)data.str; // determine array layout from version U32 item_size = 0; @@ -712,16 +712,16 @@ pdb_comp_unit_contribution_array_from_data(Arena *arena, String8 data, COFF_Sect default: { // TODO(allen): do we have a test case for this? - item_size = sizeof(PDB_DbiSectionContrib40); + item_size = sizeof(PDB_DbiSC40); }break; - case PDB_DbiSectionContribVersion_1: + case PDB_DbiSCVersion_1: { - item_size = sizeof(PDB_DbiSectionContrib); + item_size = sizeof(PDB_DbiSC); array_off = sizeof(*version); }break; - case PDB_DbiSectionContribVersion_2: + case PDB_DbiSCVersion_2: { - item_size = sizeof(PDB_DbiSectionContrib2); + item_size = sizeof(PDB_DbiSC2); array_off = sizeof(*version); }break; } @@ -739,7 +739,7 @@ pdb_comp_unit_contribution_array_from_data(Arena *arena, String8 data, COFF_Sect U64 cursor = array_off; for(; cursor + item_size <= data.size; cursor += item_size) { - PDB_DbiSectionContrib40 *sc = (PDB_DbiSectionContrib40*)(data.str + cursor); + PDB_DbiSC40 *sc = (PDB_DbiSC40*)(data.str + cursor); if(sc->size > 0 && 1 <= sc->sec && sc->sec <= section_count) { U64 voff = section_headers[sc->sec - 1].voff + sc->sec_off; diff --git a/src/raddbg/generated/raddbg.meta.c b/src/raddbg/generated/raddbg.meta.c index a61108aa..7b4e96f5 100644 --- a/src/raddbg/generated/raddbg.meta.c +++ b/src/raddbg/generated/raddbg.meta.c @@ -62,13 +62,14 @@ str8_lit_comp(""), str8_lit_comp(""), }; -RD_VocabInfo rd_vocab_info_table[368] = +RD_VocabInfo rd_vocab_info_table[372] = { {str8_lit_comp("type_view"), str8_lit_comp("type_views"), str8_lit_comp("Type View"), str8_lit_comp("Type Views"), RD_IconKind_Binoculars}, {str8_lit_comp("file_path_map"), str8_lit_comp("file_path_maps"), str8_lit_comp("File Path Map"), str8_lit_comp("File Path Maps"), RD_IconKind_FileOutline}, {str8_lit_comp("watch_pin"), str8_lit_comp("watch_pins"), str8_lit_comp("Watch Pin"), str8_lit_comp("Watch Pins"), RD_IconKind_Pin}, {str8_lit_comp("debug_info"), str8_lit_comp("debug_infos"), str8_lit_comp("Debug Info"), str8_lit_comp("Debug Info"), RD_IconKind_Module}, {str8_lit_comp("watch"), str8_lit_comp("watches"), str8_lit_comp("Watch"), str8_lit_comp("Watches"), RD_IconKind_Binoculars}, +{str8_lit_comp("watch_expression"), str8_lit_comp("watch_expressions"), str8_lit_comp("Watch Expression"), str8_lit_comp("Watch Expressions"), RD_IconKind_Binoculars}, {str8_lit_comp("view"), str8_lit_comp("views"), str8_lit_comp("View"), str8_lit_comp("Views"), RD_IconKind_Binoculars}, {str8_lit_comp("breakpoint"), str8_lit_comp("breakpoints"), str8_lit_comp("Breakpoint"), str8_lit_comp("Breakpoints"), RD_IconKind_CircleFilled}, {str8_lit_comp("condition"), str8_lit_comp("conditions"), str8_lit_comp("Condition"), str8_lit_comp("Conditions"), RD_IconKind_Null}, @@ -299,6 +300,9 @@ RD_VocabInfo rd_vocab_info_table[368] = {str8_lit_comp("accept"), str8_lit_comp(""), str8_lit_comp("Accept"), str8_lit_comp(""), RD_IconKind_CheckFilled}, {str8_lit_comp("cancel"), str8_lit_comp(""), str8_lit_comp("Cancel"), str8_lit_comp(""), RD_IconKind_X}, {str8_lit_comp("focus_menu"), str8_lit_comp(""), str8_lit_comp("Focus Menu"), str8_lit_comp(""), RD_IconKind_List}, +{str8_lit_comp("lock"), str8_lit_comp(""), str8_lit_comp("Lock"), str8_lit_comp(""), RD_IconKind_Locked}, +{str8_lit_comp("unlock"), str8_lit_comp(""), str8_lit_comp("Unlock"), str8_lit_comp(""), RD_IconKind_Unlocked}, +{str8_lit_comp("toggle_lock"), str8_lit_comp(""), str8_lit_comp("Toggle Lock"), str8_lit_comp(""), RD_IconKind_Locked}, {str8_lit_comp("move_left"), str8_lit_comp(""), str8_lit_comp("Move Left"), str8_lit_comp(""), RD_IconKind_Null}, {str8_lit_comp("move_right"), str8_lit_comp(""), str8_lit_comp("Move Right"), str8_lit_comp(""), RD_IconKind_Null}, {str8_lit_comp("move_up"), str8_lit_comp(""), str8_lit_comp("Move Up"), str8_lit_comp(""), RD_IconKind_Null}, @@ -456,7 +460,7 @@ RD_NameSchemaInfo rd_name_schema_info_table[39] = {str8_lit_comp("array"), 1, str8_lit_comp("x:{ @description('An expression of the base address of the array.') 'base_address': expr_string, @description('The number of elements in the array.') count}")}, {str8_lit_comp("slice"), 1, str8_lit_comp("x:{ @description('An expression of a structure that is to be interpreted as a slice.') 'expression': expr_string}")}, {str8_lit_comp("list"), 1, str8_lit_comp("x:\n{\n @description(\"An expression describing the first node in the list.\")\n 'expression': expr_string,\n @order(0) @description(\"The name of the member which encodes the link to the next node.\")\n 'member_name': code_string,\n}\n")}, -{str8_lit_comp("watch"), 0, str8_lit_comp("@inherit(tab) x:\n{\n @override @display_name('Tab Row Height') @description(\"Controls the tab's row height, in multiples of the font size.\")\n 'row_height': @range[1.75f, 5.f] f32,\n 'label': code_string,\n @description(\"The root expression which is evaluated to produce the watch window.\")\n 'expression': expr_string,\n @no_expand 'watches': set,\n}\n")}, +{str8_lit_comp("watch"), 0, str8_lit_comp("@inherit(tab) x:\n{\n @override @display_name('Tab Row Height') @description(\"Controls the tab's row height, in multiples of the font size.\")\n 'row_height': @range[1.75f, 5.f] f32,\n 'label': code_string,\n @description(\"The root expression which is evaluated to produce the watch window.\")\n 'expression': expr_string,\n @no_expand 'watch_expressions': set,\n}\n")}, {str8_lit_comp("text"), 1, str8_lit_comp("@inherit(tab) @expand_commands(@output clear_output) x:\n{\n @description(\"An expression to describe data which should be viewed as text or code.\")\n 'expression': expr_string,\n @optional @description(\"The language that the text should be interpreted as being within. Used for syntax highlighting and other parsing features.\")\n 'lang': code_string,\n @no_callee_helper @default(1) @description(\"Controls whether or not line numbers are shown.\")\n 'show_line_numbers':bool,\n @no_callee_helper @default(1) @display_name('Line Wrapping') @description(\"Splits textual lines into multiple visual lines, so that all text is within the visible area.\")\n 'line_wrapping': bool,\n @no_callee_helper @default(0) @display_name('Scroll To Bottom On Change') @description(\"Scrolls to the bottom if the text is changed.\")\n 'scroll_to_bottom_on_change': bool,\n @no_callee_helper @no_revert @default(0) @display_name('Transient') @description(\"Controls whether or not this tab will be automatically replaced by the debugger when it snaps to new source code locations.\")\n 'auto': bool,\n}\n")}, {str8_lit_comp("disasm"), 1, str8_lit_comp("@inherit(tab) x:\n{\n @description(\"An expression to describe the base address or offset of the disassembly.\")\n 'expression': expr_string,\n @optional @description(\"The maximum number of bytes to disassemble.\")\n 'size': expr_string,\n @optional @description(\"The architecture to interpret the data as when disassembling.\")\n 'arch': code_string,\n @optional @description(\"The syntax style to use when displaying the disassembly textually.\")\n 'syntax': code_string,\n @no_callee_helper @default(1) @description(\"Controls whether or not addresses are shown in the disassembly text.\")\n 'show_addresses': bool,\n @no_callee_helper @default(0) @description(\"Controls whether or not code bytes are shown in the disassembly text.\")\n 'show_code_bytes': bool,\n @no_callee_helper @default(1) @description(\"Controls whether or not source lines, corresponding to disassembly instruction ranges, are shown in the disassembly text.\")\n 'show_source_lines': bool,\n @no_callee_helper @default(1) @description(\"Controls whether or not disassembly text is decorated with symbol names.\")\n 'show_symbol_names': bool,\n @no_callee_helper @default(1) @description(\"Controls whether or not line numbers are shown.\")\n 'show_line_numbers': bool,\n}\n")}, {str8_lit_comp("memory"), 1, str8_lit_comp("@inherit(tab) x:\n{\n @display_name(\"Base Address\") @description(\"An expression which refers to the base address of data which should be viewed as memory.\")\n 'expression': expr_string,\n @no_callee_helper @display_name(\"Zoom\") @description(\"The zoom level for displaying bytes in the memory view.\")\n @default(1.0) 'zoom': @range[0.5, 2] f32,\n @optional @display_name(\"Address Range Size\") @description(\"The number of bytes of the viewed memory range.\")\n 'size': expr_string,\n @no_callee_helper @display_name(\"Cursor Address\") @description(\"The address of the cursor.\")\n 'cursor': expr_string,\n @no_callee_helper @default(1) @display_name(\"Cursor Size\") @description(\"The size, in bytes, of the cursor.\")\n 'cursor_size': @range[1, 16] u64,\n @optional @expand_if(\"!$.auto_columns\") @default(16) @description(\"The number of columns to build before building new rows.\")\n 'num_columns': @range[1, 64] u64,\n @optional @default(16) @display_name(\"Default Radix\") @description(\"The default radix with which numeric values should be displayed, when peeking.\")\n @or(2, 8, 10, 16)\n 'default_radix': u64,\n @no_callee_helper @default(1) @display_name(\"Track Mark To Cursor\") @description(\"Ensures that the mark always follows the cursor, if the cursor value is updated.\")\n 'track_mark_to_cursor': bool,\n @no_callee_helper @default(1) @display_name(\"Allow Mutation\") @description(\"Allows operations which mutate memory.\")\n 'allow_mutation': bool,\n @no_callee_helper @default(0) @display_name(\"Automatically Size Columns\") @description(\"Determines the number of columns based on the available space.\")\n 'auto_columns': bool,\n @no_callee_helper @default(1) @display_name(\"Peek As Unsigned\") 'peek_as_unsigned': bool,\n @no_callee_helper @default(1) @display_name(\"Peek As Signed\") 'peek_as_signed': bool,\n @no_callee_helper @default(1) @display_name(\"Peek As Float\") 'peek_as_float': bool,\n @no_callee_helper @display_name(\"Extra Peek Types\") @description(\"A list of types as which to interpret selected memory.\")\n 'peek_types': set,\n}\n")}, @@ -477,7 +481,7 @@ RD_NameSchemaInfo rd_name_schema_info_table[39] = {str8_lit_comp("thread"), 0, str8_lit_comp("x:{'label':code_string, 'id':u64, @no_expand 'active':bool, 'call_stack':set}")}, }; -String8 rd_reg_slot_code_name_table[52] = +String8 rd_reg_slot_code_name_table[54] = { {0}, str8_lit_comp("machine"), @@ -499,6 +503,8 @@ str8_lit_comp("inline_depth"), str8_lit_comp("file_path"), str8_lit_comp("cursor"), str8_lit_comp("mark"), +str8_lit_comp("line_num"), +str8_lit_comp("column_num"), str8_lit_comp("text_key"), str8_lit_comp("lang_kind"), str8_lit_comp("lines"), @@ -533,7 +539,7 @@ str8_lit_comp("cmd_name"), str8_lit_comp("wm_event"), }; -Rng1U64 rd_reg_slot_range_table[52] = +Rng1U64 rd_reg_slot_range_table[54] = { {0}, {OffsetOf(RD_Regs, machine), OffsetOf(RD_Regs, machine) + sizeof(D_Handle)}, @@ -553,8 +559,10 @@ Rng1U64 rd_reg_slot_range_table[52] = {OffsetOf(RD_Regs, unwind_count), OffsetOf(RD_Regs, unwind_count) + sizeof(U64)}, {OffsetOf(RD_Regs, inline_depth), OffsetOf(RD_Regs, inline_depth) + sizeof(U64)}, {OffsetOf(RD_Regs, file_path), OffsetOf(RD_Regs, file_path) + sizeof(String8)}, -{OffsetOf(RD_Regs, cursor), OffsetOf(RD_Regs, cursor) + sizeof(TxtPt)}, -{OffsetOf(RD_Regs, mark), OffsetOf(RD_Regs, mark) + sizeof(TxtPt)}, +{OffsetOf(RD_Regs, cursor), OffsetOf(RD_Regs, cursor) + sizeof(U64)}, +{OffsetOf(RD_Regs, mark), OffsetOf(RD_Regs, mark) + sizeof(U64)}, +{OffsetOf(RD_Regs, line_num), OffsetOf(RD_Regs, line_num) + sizeof(U64)}, +{OffsetOf(RD_Regs, column_num), OffsetOf(RD_Regs, column_num) + sizeof(U64)}, {OffsetOf(RD_Regs, text_key), OffsetOf(RD_Regs, text_key) + sizeof(C_Key)}, {OffsetOf(RD_Regs, lang_kind), OffsetOf(RD_Regs, lang_kind) + sizeof(TXT_LangKind)}, {OffsetOf(RD_Regs, lines), OffsetOf(RD_Regs, lines) + sizeof(D_LineList)}, @@ -589,7 +597,7 @@ Rng1U64 rd_reg_slot_range_table[52] = {OffsetOf(RD_Regs, wm_event), OffsetOf(RD_Regs, wm_event) + sizeof(WM_Event *)}, }; -RD_CmdKindInfo rd_cmd_kind_info_table[256] = +RD_CmdKindInfo rd_cmd_kind_info_table[259] = { {0}, { str8_lit_comp("launch_and_run"), str8_lit_comp("Starts debugging a new instance of a target, then runs."), str8_lit_comp("launch,start,run,target"), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_Cfg, str8_lit_comp("query:targets")}}, @@ -691,7 +699,7 @@ RD_CmdKindInfo rd_cmd_kind_info_table[256] = { str8_lit_comp("tab_settings"), str8_lit_comp("Opens settings for a tab."), str8_lit_comp("view,options"), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("set_current_path"), str8_lit_comp("Sets the debugger's current path, which is used as a starting point when browsing for files."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("open"), str8_lit_comp("Opens a file."), str8_lit_comp("code,source,file"), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*1)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_FilePath, str8_lit_comp("folder:\"$input\"")}}, -{ str8_lit_comp("open_source_file_from_debug_info"), str8_lit_comp("Opens a source file found within loaded debug info."), str8_lit_comp("code,source,file"), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*0), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_Cfg, str8_lit_comp("query:source_files")}}, +{ str8_lit_comp("open_source_file_from_debug_info"), str8_lit_comp("Opens a source file found within loaded debug info."), str8_lit_comp("code,source,file"), str8_lit_comp("{tab_commands}"), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*0), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_Cfg, str8_lit_comp("query:source_files")}}, { str8_lit_comp("switch_to_partner_file"), str8_lit_comp("Switches to the focused file's partner; or from header to implementation or vice versa."), str8_lit_comp("code,source,file"), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("show_file_in_explorer"), str8_lit_comp("Opens the operating system's file explorer and shows the selected file."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("go_to_disassembly"), str8_lit_comp("Goes to the disassembly, if any, for a given source code line."), str8_lit_comp("code,source,disassembly,disasm"), str8_lit_comp("{text_pt_commands}"), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, @@ -714,6 +722,9 @@ RD_CmdKindInfo rd_cmd_kind_info_table[256] = { str8_lit_comp("accept"), str8_lit_comp("Accepts current changes, or answers prompts in the affirmative."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("cancel"), str8_lit_comp("Rejects current changes, exits temporary menus, or answers prompts in the negative."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("focus_menu"), str8_lit_comp("Focuses the menu for the selected interface, if there is one."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, +{ str8_lit_comp("lock"), str8_lit_comp("Locks the current selection, if applicable."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, +{ str8_lit_comp("unlock"), str8_lit_comp("Unlocks the current selection, if applicable."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, +{ str8_lit_comp("toggle_lock"), str8_lit_comp("Toggles the lock state of the current selection, if applicable."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("move_left"), str8_lit_comp("Moves the cursor or selection left."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("move_right"), str8_lit_comp("Moves the cursor or selection right."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("move_up"), str8_lit_comp("Moves the cursor or selection up."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, @@ -755,7 +766,7 @@ RD_CmdKindInfo rd_cmd_kind_info_table[256] = { str8_lit_comp("insert_text"), str8_lit_comp("Inserts the text that was used to cause this command."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("move_next"), str8_lit_comp("Moves the cursor or selection to the next element."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("move_prev"), str8_lit_comp("Moves the cursor or selection to the previous element."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, -{ str8_lit_comp("goto_line"), str8_lit_comp("Jumps to a line number in the current code file."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*1)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*1), RD_RegSlot_Cursor, str8_lit_comp("")}}, +{ str8_lit_comp("goto_line"), str8_lit_comp("Jumps to a line number in the current code file."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*1)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*1), RD_RegSlot_LineNum, str8_lit_comp("")}}, { str8_lit_comp("goto_address"), str8_lit_comp("Jumps to an address in the current memory or disassembly view."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*1)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*1), RD_RegSlot_Vaddr, str8_lit_comp("")}}, { str8_lit_comp("center_cursor"), str8_lit_comp("Snaps the current code view to center the cursor."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("contain_cursor"), str8_lit_comp("Snaps the current code view to contain the cursor."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, @@ -849,7 +860,7 @@ RD_CmdKindInfo rd_cmd_kind_info_table[256] = { str8_lit_comp("geo3d"), str8_lit_comp("Opens a Geometry (3D) tab."), {0}, str8_lit_comp("{tab_commands}"), RD_CmdKindFlag_ListInUI|RD_CmdKindFlag_ListInIPCDocs}, }; -struct {String8 string; CFG_Binding binding;} rd_default_binding_table[116] = +struct {String8 string; CFG_Binding binding;} rd_default_binding_table[117] = { {str8_lit_comp("kill_all"), {WM_Key_F5, 0 |WM_Modifier_Shift }}, {str8_lit_comp("step_into_inst"), {WM_Key_F11, 0 |WM_Modifier_Alt}}, @@ -902,6 +913,7 @@ struct {String8 string; CFG_Binding binding;} rd_default_binding_table[116] = {str8_lit_comp("accept"), {WM_Key_Space, 0 }}, {str8_lit_comp("cancel"), {WM_Key_Esc, 0 }}, {str8_lit_comp("focus_menu"), {WM_Key_D, 0 |WM_Modifier_Alt}}, +{str8_lit_comp("toggle_lock"), {WM_Key_L, 0 |WM_Modifier_Ctrl }}, {str8_lit_comp("move_left"), {WM_Key_Left, 0 }}, {str8_lit_comp("move_right"), {WM_Key_Right, 0 }}, {str8_lit_comp("move_up"), {WM_Key_Up, 0 }}, diff --git a/src/raddbg/generated/raddbg.meta.h b/src/raddbg/generated/raddbg.meta.h index 92db5712..1b22d152 100644 --- a/src/raddbg/generated/raddbg.meta.h +++ b/src/raddbg/generated/raddbg.meta.h @@ -28,6 +28,8 @@ RD_RegSlot_InlineDepth, RD_RegSlot_FilePath, RD_RegSlot_Cursor, RD_RegSlot_Mark, +RD_RegSlot_LineNum, +RD_RegSlot_ColumnNum, RD_RegSlot_TextKey, RD_RegSlot_LangKind, RD_RegSlot_Lines, @@ -188,6 +190,9 @@ RD_CmdKind_Edit, RD_CmdKind_Accept, RD_CmdKind_Cancel, RD_CmdKind_FocusMenu, +RD_CmdKind_Lock, +RD_CmdKind_Unlock, +RD_CmdKind_ToggleLock, RD_CmdKind_MoveLeft, RD_CmdKind_MoveRight, RD_CmdKind_MoveUp, @@ -478,8 +483,10 @@ E_Space eval_space; U64 unwind_count; U64 inline_depth; String8 file_path; -TxtPt cursor; -TxtPt mark; +U64 cursor; +U64 mark; +U64 line_num; +U64 column_num; C_Key text_key; TXT_LangKind lang_kind; D_LineList lines; @@ -581,6 +588,8 @@ Z(getting_started)\ .file_path = rd_regs()->file_path,\ .cursor = rd_regs()->cursor,\ .mark = rd_regs()->mark,\ +.line_num = rd_regs()->line_num,\ +.column_num = rd_regs()->column_num,\ .text_key = rd_regs()->text_key,\ .lang_kind = rd_regs()->lang_kind,\ .lines = rd_regs()->lines,\ @@ -617,10 +626,10 @@ Z(getting_started)\ C_LINKAGE_BEGIN extern String8 rd_tab_fast_path_view_name_table[25]; extern String8 rd_tab_fast_path_query_name_table[25]; -extern RD_VocabInfo rd_vocab_info_table[368]; +extern RD_VocabInfo rd_vocab_info_table[372]; extern RD_NameSchemaInfo rd_name_schema_info_table[39]; -extern String8 rd_reg_slot_code_name_table[52]; -extern Rng1U64 rd_reg_slot_range_table[52]; +extern String8 rd_reg_slot_code_name_table[54]; +extern Rng1U64 rd_reg_slot_range_table[54]; extern String8 rd_binding_version_remap_old_name_table[9]; extern String8 rd_binding_version_remap_new_name_table[9]; extern String8 rd_icon_kind_text_table[75]; diff --git a/src/raddbg/raddbg.mdesk b/src/raddbg/raddbg.mdesk index ff696199..0d9817f2 100644 --- a/src/raddbg/raddbg.mdesk +++ b/src/raddbg/raddbg.mdesk @@ -94,6 +94,7 @@ RD_VocabTable: {watch_pin _ "Watch Pin" _ Pin } {debug_info _ "Debug Info" "Debug Info" Module } {watch watches "Watch" "Watches" Binoculars } + {watch_expression _ "Watch Expression" _ Binoculars } {view _ "View" _ Binoculars } {breakpoint _ "Breakpoint" _ CircleFilled } {condition _ "Condition" _ Null } @@ -560,7 +561,7 @@ RD_VocabTable: 'label': code_string, @description("The root expression which is evaluated to produce the watch window.") 'expression': expr_string, - @no_expand 'watches': set, + @no_expand 'watch_expressions': set, } ``` } @@ -870,8 +871,10 @@ RD_RegTable: // rjf: code / address location info {String8 file_path FilePath } - {TxtPt cursor Cursor } - {TxtPt mark Mark } + {U64 cursor Cursor } + {U64 mark Mark } + {U64 line_num LineNum } + {U64 column_num ColumnNum } {C_Key text_key TextKey } {TXT_LangKind lang_kind LangKind } {D_LineList lines Lines } @@ -946,302 +949,305 @@ RD_RegTable: //////////////////////////////// //~ rjf: Command Table -@table(name ui_vis ipc_docs_vis q_expr q_slot q_allow_files q_allow_folders q_keep_oi q_select_oi q_is_code q_floating q_required canonical_icon string display_name desc search_tags filter_tags) -// / | | | \___ _________________________________________________/ | | | | | | -// / | | | \ / | | | | | | -RD_CmdTable: // | | | | | | | | | | | +@table(name foo ui_vis ipc_docs_vis q_expr q_slot q_allow_files q_allow_folders q_keep_oi q_select_oi q_is_code q_floating q_required canonical_icon string display_name desc search_tags filter_tags) +// / | | | | \___ _________________________________________________/ | | | | | | +// / | | | | \ / | | | | | | +RD_CmdTable: // | | | | | | | | | | | | { //- rjf: exiting - {Exit 1 1 "" Null 0 0 0 0 0 0 0 X "exit" "Exit" "Exits the debugger." "quit,close,abort" "" } + {Exit 0 1 1 "" Null 0 0 0 0 0 0 0 X "exit" "Exit" "Exits the debugger." "quit,close,abort" "" } //- rjf: palette - {OpenPalette 1 1 "" Null 0 0 0 0 0 0 0 List "open_palette" "Open Palette" "Opens the palette." "help,cmd,lister" "" } + {OpenPalette 0 1 1 "" Null 0 0 0 0 0 0 0 List "open_palette" "Open Palette" "Opens the palette." "help,cmd,lister" "" } //- rjf: command fast-path - {RunCommand 0 0 "query:commands" CmdName 0 0 0 0 0 1 1 Null "run_command" "Run Command" "Runs a command from the command palette." "help,cmd" "" } + {RunCommand 0 0 0 "query:commands" CmdName 0 0 0 0 0 1 1 Null "run_command" "Run Command" "Runs a command from the command palette." "help,cmd" "" } //- rjf: external drivers - {RunExternalDriverTextCommand 0 0 "" Null 0 0 0 0 0 0 0 Null "run_ext_driver_text_command" "Run External Driver Text Command" "" "" "" } - {State 0 1 "" Null 0 0 0 0 0 0 0 Null "state" "State" "" "" "" } - {Eval 0 1 "" Expr 0 0 0 0 0 0 0 Null "eval" "Eval" "" "" "" } - {LineFromVAddr 0 1 "" Vaddr 0 0 0 0 0 0 0 Null "line_from_vaddr" "Line From Virtual Address" "" "" "" } + {RunExternalDriverTextCommand 0 0 0 "" Null 0 0 0 0 0 0 0 Null "run_ext_driver_text_command" "Run External Driver Text Command" "" "" "" } + {State 0 0 1 "" Null 0 0 0 0 0 0 0 Null "state" "State" "" "" "" } + {Eval 0 0 1 "" Expr 0 0 0 0 0 0 0 Null "eval" "Eval" "" "" "" } + {LineFromVAddr 0 0 1 "" Vaddr 0 0 0 0 0 0 0 Null "line_from_vaddr" "Line From Virtual Address" "" "" "" } //- rjf: os event passthrough - {WMEvent 0 0 "" Null 0 0 0 0 0 0 0 Null "wm_event" "OS Event" "" "" "" } + {WMEvent 0 0 0 "" Null 0 0 0 0 0 0 0 Null "wm_event" "OS Event" "" "" "" } //- rjf: thread/frame selection - {SelectThread 1 1 "query:threads" Thread 0 0 0 0 0 1 1 Thread "select_thread" "Select Thread" "Selects a thread." "" "" } - {SelectUnwind 0 1 "query:call_stack" Null 0 0 0 0 0 0 0 Null "select_unwind" "Select Unwind" "Selects an unwind frame number for the selected thread." "" "" } - {UpOneFrame 1 1 "" Null 0 0 0 0 0 0 0 UpArrow "up_one_frame" "Up One Frame" "Selects the call stack frame above the currently selected." "" "" } - {DownOneFrame 1 1 "" Null 0 0 0 0 0 0 0 DownArrow "down_one_frame" "Down One Frame" "Selects the call stack frame below the currently selected." "callstack,unwind" "" } - {SelectEntity 0 0 "" Null 0 0 0 0 0 0 0 RadioHollow "select_entity" "Select" "Selects a control entity." "" "" } - {DeselectEntity 0 0 "" Null 0 0 0 0 0 0 0 RadioFilled "deselect_entity" "Deselect" "Deselects a control entity." "" "" } + {SelectThread 0 1 1 "query:threads" Thread 0 0 0 0 0 1 1 Thread "select_thread" "Select Thread" "Selects a thread." "" "" } + {SelectUnwind 0 0 1 "query:call_stack" Null 0 0 0 0 0 0 0 Null "select_unwind" "Select Unwind" "Selects an unwind frame number for the selected thread." "" "" } + {UpOneFrame 0 1 1 "" Null 0 0 0 0 0 0 0 UpArrow "up_one_frame" "Up One Frame" "Selects the call stack frame above the currently selected." "" "" } + {DownOneFrame 0 1 1 "" Null 0 0 0 0 0 0 0 DownArrow "down_one_frame" "Down One Frame" "Selects the call stack frame below the currently selected." "callstack,unwind" "" } + {SelectEntity 0 0 0 "" Null 0 0 0 0 0 0 0 RadioHollow "select_entity" "Select" "Selects a control entity." "" "" } + {DeselectEntity 0 0 0 "" Null 0 0 0 0 0 0 0 RadioFilled "deselect_entity" "Deselect" "Deselects a control entity." "" "" } //- rjf: font sizes - {IncWindowFontSize 1 1 "" Null 0 0 0 0 0 0 0 Null "inc_window_font_size" "Increase Window Font Size" "Increases the window's font size by one point." "" "" } - {DecWindowFontSize 1 1 "" Null 0 0 0 0 0 0 0 Null "dec_window_font_size" "Decrease Window Font Size" "Decreases the window's font size by one point." "" "" } - {IncViewFontSize 1 1 "" Null 0 0 0 0 0 0 0 Null "inc_view_font_size" "Increase View Font Size" "Increases the view's font size by one point." "" "" } - {DecViewFontSize 1 1 "" Null 0 0 0 0 0 0 0 Null "dec_view_font_size" "Decrease View Font Size" "Decreases the view's font size by one point." "" "" } + {IncWindowFontSize 0 1 1 "" Null 0 0 0 0 0 0 0 Null "inc_window_font_size" "Increase Window Font Size" "Increases the window's font size by one point." "" "" } + {DecWindowFontSize 0 1 1 "" Null 0 0 0 0 0 0 0 Null "dec_window_font_size" "Decrease Window Font Size" "Decreases the window's font size by one point." "" "" } + {IncViewFontSize 0 1 1 "" Null 0 0 0 0 0 0 0 Null "inc_view_font_size" "Increase View Font Size" "Increases the view's font size by one point." "" "" } + {DecViewFontSize 0 1 1 "" Null 0 0 0 0 0 0 0 Null "dec_view_font_size" "Decrease View Font Size" "Decreases the view's font size by one point." "" "" } //- rjf: windows - {OpenWindow 1 1 "" Null 0 0 0 0 0 0 0 Window "open_window" "Open New Window" "Opens a new window." "" "" } - {WindowSettings 1 1 "" Null 0 0 0 0 0 0 0 Gear "window_settings" "Window Settings" "Opens settings for a window." "" "" } - {CloseWindow 1 1 "" Null 0 0 0 0 0 0 0 Window "close_window" "Close Window" "Closes an opened window." "" "" } - {ToggleFullscreen 1 1 "" Null 0 0 0 0 0 0 0 Window "toggle_fullscreen" "Toggle Fullscreen" "Toggles fullscreen view on the active window." "" "" } - {BringToFront 0 1 "" Null 0 0 0 0 0 0 0 Window "bring_to_front" "Bring To Front" "Brings all windows to the front, and focuses the most recently focused window." "top" "" } + {OpenWindow 0 1 1 "" Null 0 0 0 0 0 0 0 Window "open_window" "Open New Window" "Opens a new window." "" "" } + {WindowSettings 0 1 1 "" Null 0 0 0 0 0 0 0 Gear "window_settings" "Window Settings" "Opens settings for a window." "" "" } + {CloseWindow 0 1 1 "" Null 0 0 0 0 0 0 0 Window "close_window" "Close Window" "Closes an opened window." "" "" } + {ToggleFullscreen 0 1 1 "" Null 0 0 0 0 0 0 0 Window "toggle_fullscreen" "Toggle Fullscreen" "Toggles fullscreen view on the active window." "" "" } + {BringToFront 0 0 1 "" Null 0 0 0 0 0 0 0 Window "bring_to_front" "Bring To Front" "Brings all windows to the front, and focuses the most recently focused window." "top" "" } //- rjf: popups - {PopupAccept 0 1 "" Null 0 0 0 0 0 0 0 Null "popup_accept" "Popup Accept" "Accepts the active popup prompt." "" "" } - {PopupCancel 0 1 "" Null 0 0 0 0 0 0 0 Null "popup_cancel" "Popup Cancel" "Cancels the active popup prompt." "" "" } + {PopupAccept 0 0 1 "" Null 0 0 0 0 0 0 0 Null "popup_accept" "Popup Accept" "Accepts the active popup prompt." "" "" } + {PopupCancel 0 0 1 "" Null 0 0 0 0 0 0 0 Null "popup_cancel" "Popup Cancel" "Cancels the active popup prompt." "" "" } //- rjf: keybindings - {ResetToDefaultBindings 1 1 "" Null 0 0 0 0 0 0 0 Null "reset_to_default_bindings" "Reset To Default Bindings" "Resets all keybindings to their defaults." "" "" } + {ResetToDefaultBindings 0 1 1 "" Null 0 0 0 0 0 0 0 Null "reset_to_default_bindings" "Reset To Default Bindings" "Resets all keybindings to their defaults." "" "" } //- rjf: panel splitting - {ResetToDefaultPanels 1 1 "" Null 0 0 0 0 0 0 0 Window "reset_to_default_panels" "Reset To Default Panel Layout" "Resets the window to the default panel layout." "panel" "" } - {ResetToCompactPanels 1 1 "" Null 0 0 0 0 0 0 0 Window "reset_to_compact_panels" "Reset To Compact Panel Layout" "Resets the window to the compact panel layout." "panel" "" } - {ResetToSimplePanels 1 1 "" Null 0 0 0 0 0 0 0 Window "reset_to_simple_panels" "Reset To Simple Panel Layout" "Resets the window to the simple panel layout." "panel" "" } - {NewPanelLeft 1 1 "" Null 0 0 0 0 0 0 0 XSplit "new_panel_left" "Split Panel Left" "Creates a new panel to the left of the active panel." "panel" "" } - {NewPanelUp 1 1 "" Null 0 0 0 0 0 0 0 YSplit "new_panel_up" "Split Panel Up" "Creates a new panel at the top of the active panel." "panel" "" } - {NewPanelRight 1 1 "" Null 0 0 0 0 0 0 0 XSplit "new_panel_right" "Split Panel Right" "Creates a new panel to the right of the active panel." "panel" "" } - {NewPanelDown 1 1 "" Null 0 0 0 0 0 0 0 YSplit "new_panel_down" "Split Panel Down" "Creates a new panel at the bottom of the active panel." "panel" "" } - {SplitPanel 0 0 "" Null 0 0 0 0 0 0 0 Null "split_panel" "Split Panel" "Creates a new panel in a given direction, and moves a tab to it, if specified." "" "" } + {ResetToDefaultPanels 0 1 1 "" Null 0 0 0 0 0 0 0 Window "reset_to_default_panels" "Reset To Default Panel Layout" "Resets the window to the default panel layout." "panel" "" } + {ResetToCompactPanels 0 1 1 "" Null 0 0 0 0 0 0 0 Window "reset_to_compact_panels" "Reset To Compact Panel Layout" "Resets the window to the compact panel layout." "panel" "" } + {ResetToSimplePanels 0 1 1 "" Null 0 0 0 0 0 0 0 Window "reset_to_simple_panels" "Reset To Simple Panel Layout" "Resets the window to the simple panel layout." "panel" "" } + {NewPanelLeft 0 1 1 "" Null 0 0 0 0 0 0 0 XSplit "new_panel_left" "Split Panel Left" "Creates a new panel to the left of the active panel." "panel" "" } + {NewPanelUp 0 1 1 "" Null 0 0 0 0 0 0 0 YSplit "new_panel_up" "Split Panel Up" "Creates a new panel at the top of the active panel." "panel" "" } + {NewPanelRight 0 1 1 "" Null 0 0 0 0 0 0 0 XSplit "new_panel_right" "Split Panel Right" "Creates a new panel to the right of the active panel." "panel" "" } + {NewPanelDown 0 1 1 "" Null 0 0 0 0 0 0 0 YSplit "new_panel_down" "Split Panel Down" "Creates a new panel at the bottom of the active panel." "panel" "" } + {SplitPanel 0 0 0 "" Null 0 0 0 0 0 0 0 Null "split_panel" "Split Panel" "Creates a new panel in a given direction, and moves a tab to it, if specified." "" "" } //- rjf: panel rotation - {RotatePanelColumns 1 1 "" Null 0 0 0 0 0 0 0 Null "rotate_panel_columns" "Rotate Panel Columns" "Rotates all panels at the closest column level of the panel hierarchy." "" "" } + {RotatePanelColumns 0 1 1 "" Null 0 0 0 0 0 0 0 Null "rotate_panel_columns" "Rotate Panel Columns" "Rotates all panels at the closest column level of the panel hierarchy." "" "" } //- rjf: focused panel changing - {NextPanel 1 1 "" Null 0 0 0 0 0 0 0 RightArrow "next_panel" "Focus Next Panel" "Cycles the active panel forward." "" "" } - {PrevPanel 1 1 "" Null 0 0 0 0 0 0 0 LeftArrow "prev_panel" "Focus Previous Panel" "Cycles the active panel backwards." "" "" } - {FocusPanel 0 0 "" Null 0 0 0 0 0 0 0 Null "focus_panel" "Focus Panel" "Focuses a new panel." "" "" } - {FocusPanelRight 1 1 "" Null 0 0 0 0 0 0 0 RightArrow "focus_panel_right" "Focus Panel Right" "Focuses a panel rightward of the currently focused panel." "" "" } - {FocusPanelLeft 1 1 "" Null 0 0 0 0 0 0 0 LeftArrow "focus_panel_left" "Focus Panel Left" "Focuses a panel leftward of the currently focused panel." "" "" } - {FocusPanelUp 1 1 "" Null 0 0 0 0 0 0 0 UpArrow "focus_panel_up" "Focus Panel Up" "Focuses a panel upward of the currently focused panel." "" "" } - {FocusPanelDown 1 1 "" Null 0 0 0 0 0 0 0 DownArrow "focus_panel_down" "Focus Panel Down" "Focuses a panel downward of the currently focused panel." "" "" } + {NextPanel 0 1 1 "" Null 0 0 0 0 0 0 0 RightArrow "next_panel" "Focus Next Panel" "Cycles the active panel forward." "" "" } + {PrevPanel 0 1 1 "" Null 0 0 0 0 0 0 0 LeftArrow "prev_panel" "Focus Previous Panel" "Cycles the active panel backwards." "" "" } + {FocusPanel 0 0 0 "" Null 0 0 0 0 0 0 0 Null "focus_panel" "Focus Panel" "Focuses a new panel." "" "" } + {FocusPanelRight 0 1 1 "" Null 0 0 0 0 0 0 0 RightArrow "focus_panel_right" "Focus Panel Right" "Focuses a panel rightward of the currently focused panel." "" "" } + {FocusPanelLeft 0 1 1 "" Null 0 0 0 0 0 0 0 LeftArrow "focus_panel_left" "Focus Panel Left" "Focuses a panel leftward of the currently focused panel." "" "" } + {FocusPanelUp 0 1 1 "" Null 0 0 0 0 0 0 0 UpArrow "focus_panel_up" "Focus Panel Up" "Focuses a panel upward of the currently focused panel." "" "" } + {FocusPanelDown 0 1 1 "" Null 0 0 0 0 0 0 0 DownArrow "focus_panel_down" "Focus Panel Down" "Focuses a panel downward of the currently focused panel." "" "" } //- rjf: undo/redo - {Undo 0 0 "" Null 0 0 0 0 0 0 0 Undo "undo" "Undo" "Undoes the previous action." "" "" } - {Redo 0 0 "" Null 0 0 0 0 0 0 0 Redo "redo" "Redo" "Redoes the first previously undone action." "" "" } + {Undo 0 0 0 "" Null 0 0 0 0 0 0 0 Undo "undo" "Undo" "Undoes the previous action." "" "" } + {Redo 0 0 0 "" Null 0 0 0 0 0 0 0 Redo "redo" "Redo" "Redoes the first previously undone action." "" "" } //- rjf: focus history - {GoBack 0 0 "" Null 0 0 0 0 0 0 0 LeftArrow "go_back" "Go Back" "Returns to the previously selected panel and tab in recorded history." "" "" } - {GoForward 0 0 "" Null 0 0 0 0 0 0 0 RightArrow "go_forward" "Go Forward" "Returns to the next selected panel and tab in recorded history." "" "" } + {GoBack 0 0 0 "" Null 0 0 0 0 0 0 0 LeftArrow "go_back" "Go Back" "Returns to the previously selected panel and tab in recorded history." "" "" } + {GoForward 0 0 0 "" Null 0 0 0 0 0 0 0 RightArrow "go_forward" "Go Forward" "Returns to the next selected panel and tab in recorded history." "" "" } //- rjf: panel removal - {ClosePanel 1 1 "" Null 0 0 0 0 0 0 0 ClosePanel "close_panel" "Close Panel" "Closes the currently active panel." "" "" } + {ClosePanel 0 1 1 "" Null 0 0 0 0 0 0 0 ClosePanel "close_panel" "Close Panel" "Closes the currently active panel." "" "" } //- rjf: panel tab - {FocusTab 0 0 "" Null 0 0 0 0 0 0 0 Null "focus_tab" "Focus Tab" "Focuses the passed tab within its containing panel." "" "" } - {NextTab 1 1 "" Null 0 0 0 0 0 0 0 RightArrow "next_tab" "Focus Next Tab" "Focuses the next tab on the active panel." "" "" } - {PrevTab 1 1 "" Null 0 0 0 0 0 0 0 LeftArrow "prev_tab" "Focus Previous Tab" "Focuses the previous tab on the active panel." "" "" } - {MoveTabRight 1 1 "" Null 0 0 0 0 0 0 0 RightArrow "move_tab_right" "Move Tab Right" "Moves the selected tab right one slot." "" "" } - {MoveTabLeft 1 1 "" Null 0 0 0 0 0 0 0 LeftArrow "move_tab_left" "Move Tab Left" "Moves the selected tab left one slot." "" "" } - {OpenTab 1 1 "query:tab_commands" CmdName 0 0 0 0 0 1 1 Null "open_tab" "Open New Tab" "Opens a new tab." "" "" } - {BuildTab 0 0 "" Null 0 0 0 0 0 0 0 Null "build_tab" "Build Tab" "Opens a new tab with the parameterized view specification." "" "" } - {DuplicateTab 1 1 "" Tab 0 0 0 0 0 0 0 Duplicate "duplicate_tab" "Duplicate Tab" "Duplicates a tab." "" "" } - {CopyTabFullPath 0 0 "" Tab 0 0 0 0 0 0 0 Clipboard "copy_tab_full_path" "Copy Full Path" "Copies the full path of the file being viewed by this tab." "" "" } - {CloseTab 1 1 "" Tab 0 0 0 0 0 0 0 X "close_tab" "Close Tab" "Closes the currently opened tab." "" "" } - {MoveView 0 0 "" Null 0 0 0 0 0 0 0 Null "move_view" "Move View" "Moves a view to a new panel." "" "" } - {TabBarTop 1 1 "" Null 0 0 0 0 0 0 0 UpArrow "tab_bar_top" "Anchor Tab Bar To Top" "Anchors a panel's tab bar to the top of the panel." "" "" } - {TabBarBottom 1 1 "" Null 0 0 0 0 0 0 0 DownArrow "tab_bar_bottom" "Anchor Tab Bar To Bottom" "Anchors a panel's tab bar to the bottom of the panel." "" "" } - {TabSettings 1 1 "" Null 0 0 0 0 0 0 0 Gear "tab_settings" "Selected Tab Settings" "Opens settings for a tab." "view,options" "" } + {FocusTab 0 0 0 "" Null 0 0 0 0 0 0 0 Null "focus_tab" "Focus Tab" "Focuses the passed tab within its containing panel." "" "" } + {NextTab 0 1 1 "" Null 0 0 0 0 0 0 0 RightArrow "next_tab" "Focus Next Tab" "Focuses the next tab on the active panel." "" "" } + {PrevTab 0 1 1 "" Null 0 0 0 0 0 0 0 LeftArrow "prev_tab" "Focus Previous Tab" "Focuses the previous tab on the active panel." "" "" } + {MoveTabRight 0 1 1 "" Null 0 0 0 0 0 0 0 RightArrow "move_tab_right" "Move Tab Right" "Moves the selected tab right one slot." "" "" } + {MoveTabLeft 0 1 1 "" Null 0 0 0 0 0 0 0 LeftArrow "move_tab_left" "Move Tab Left" "Moves the selected tab left one slot." "" "" } + {OpenTab 0 1 1 "query:tab_commands" CmdName 0 0 0 0 0 1 1 Null "open_tab" "Open New Tab" "Opens a new tab." "" "" } + {BuildTab 0 0 0 "" Null 0 0 0 0 0 0 0 Null "build_tab" "Build Tab" "Opens a new tab with the parameterized view specification." "" "" } + {DuplicateTab 0 1 1 "" Tab 0 0 0 0 0 0 0 Duplicate "duplicate_tab" "Duplicate Tab" "Duplicates a tab." "" "" } + {CopyTabFullPath 0 0 0 "" Tab 0 0 0 0 0 0 0 Clipboard "copy_tab_full_path" "Copy Full Path" "Copies the full path of the file being viewed by this tab." "" "" } + {CloseTab 0 1 1 "" Tab 0 0 0 0 0 0 0 X "close_tab" "Close Tab" "Closes the currently opened tab." "" "" } + {MoveView 0 0 0 "" Null 0 0 0 0 0 0 0 Null "move_view" "Move View" "Moves a view to a new panel." "" "" } + {TabBarTop 0 1 1 "" Null 0 0 0 0 0 0 0 UpArrow "tab_bar_top" "Anchor Tab Bar To Top" "Anchors a panel's tab bar to the top of the panel." "" "" } + {TabBarBottom 0 1 1 "" Null 0 0 0 0 0 0 0 DownArrow "tab_bar_bottom" "Anchor Tab Bar To Bottom" "Anchors a panel's tab bar to the bottom of the panel." "" "" } + {TabSettings 0 1 1 "" Null 0 0 0 0 0 0 0 Gear "tab_settings" "Selected Tab Settings" "Opens settings for a tab." "view,options" "" } //- rjf: files - {SetCurrentPath 0 1 "" Null 0 0 0 0 0 0 0 FileOutline "set_current_path" "Set Current Path" "Sets the debugger's current path, which is used as a starting point when browsing for files." "" "" } - {Open 1 1 `folder:\\"$input\\"` FilePath 1 0 0 0 0 1 1 FileOutline "open" "Open" "Opens a file." "code,source,file" "" } - {OpenSourceFileFromDebugInfo 1 0 "query:source_files" Cfg 0 0 0 0 0 1 1 FileOutline "open_source_file_from_debug_info" "Open Source File From Debug Info" "Opens a source file found within loaded debug info." "code,source,file" "" } - {SwitchToPartnerFile 1 1 "" Null 0 0 0 0 0 0 0 FileOutline "switch_to_partner_file" "Switch To Partner File" "Switches to the focused file's partner; or from header to implementation or vice versa." "code,source,file" "" } - {ShowFileInExplorer 1 1 "" Null 0 0 0 0 0 0 0 FolderClosedFilled "show_file_in_explorer" "Show File In Explorer" "Opens the operating system's file explorer and shows the selected file." "" "" } + {SetCurrentPath 0 0 1 "" Null 0 0 0 0 0 0 0 FileOutline "set_current_path" "Set Current Path" "Sets the debugger's current path, which is used as a starting point when browsing for files." "" "" } + {Open 0 1 1 `folder:\\"$input\\"` FilePath 1 0 0 0 0 1 1 FileOutline "open" "Open" "Opens a file." "code,source,file" "" } + {OpenSourceFileFromDebugInfo 0 1 0 "query:source_files" Cfg 0 0 0 0 0 1 1 FileOutline "open_source_file_from_debug_info" "Open Source File From Debug Info" "Opens a source file found within loaded debug info." "code,source,file" "{tab_commands}" } + {SwitchToPartnerFile 0 1 1 "" Null 0 0 0 0 0 0 0 FileOutline "switch_to_partner_file" "Switch To Partner File" "Switches to the focused file's partner; or from header to implementation or vice versa." "code,source,file" "" } + {ShowFileInExplorer 0 1 1 "" Null 0 0 0 0 0 0 0 FolderClosedFilled "show_file_in_explorer" "Show File In Explorer" "Opens the operating system's file explorer and shows the selected file." "" "" } //- rjf: source <-> disasm - {GoToDisassembly 1 1 "" Null 0 0 0 0 0 0 0 Glasses "go_to_disassembly" "Go To Disassembly" "Goes to the disassembly, if any, for a given source code line." "code,source,disassembly,disasm" "{text_pt_commands}" } - {GoToSource 1 1 "" Null 0 0 0 0 0 0 0 FileOutline "go_to_source" "Go To Source" "Goes to the source code, if any, for a given disassembly line." "code,source,disassembly,disasm" "{disasm_pt_commands}" } + {GoToDisassembly 0 1 1 "" Null 0 0 0 0 0 0 0 Glasses "go_to_disassembly" "Go To Disassembly" "Goes to the disassembly, if any, for a given source code line." "code,source,disassembly,disasm" "{text_pt_commands}" } + {GoToSource 0 1 1 "" Null 0 0 0 0 0 0 0 FileOutline "go_to_source" "Go To Source" "Goes to the source code, if any, for a given disassembly line." "code,source,disassembly,disasm" "{disasm_pt_commands}" } //- rjf: override file links - {SetFileReplacementPath 0 0 "" Null 0 0 0 0 0 0 0 Null "set_file_replacement_path" "Set File Replacement Path" "Sets the path which should be used as the replacement for the passed file." "" "" } + {SetFileReplacementPath 0 0 0 "" Null 0 0 0 0 0 0 0 Null "set_file_replacement_path" "Set File Replacement Path" "Sets the path which should be used as the replacement for the passed file." "" "" } //- rjf: setting config paths - {NewUser 1 1 "" Null 1 0 0 0 0 1 0 Add "new_user" "New User" "Creates a new user file, and sets the current user path as that file's path." "new,user,project,layout" "" } - {NewProject 1 1 "" Null 1 0 0 0 0 1 0 Add "new_project" "New Project" "Creates a new project file, and sets the current project path as that file's path." "new,user,project,layout" "" } - {OpenUser 1 1 `folder:\\"$input\\"` FilePath 1 0 0 0 0 1 1 Person "open_user" "Open User" "Opens a user file path, immediately loading it, and begins autosaving to it." "load,user,project,layout" "" } - {OpenProject 1 1 `folder:\\"$input\\"` FilePath 1 0 0 0 0 1 1 Briefcase "open_project" "Open Project" "Opens a project file path, immediately loading it, and begins autosaving to it." "project,project,session" "" } - {OpenRecentProject 1 1 "query:recent_projects" Cfg 0 0 0 0 0 1 1 Briefcase "open_recent_project" "Open Recent Project" "Opens a recently used project file." "project,project,session" "" } - {SaveUser 1 1 `folder:\\"$input\\"` FilePath 1 0 0 0 0 1 1 Save "save_user" "Save User" "Saves user data to a file, and sets the current user path as that path." "load,user,project,layout" "" } - {SaveProject 1 1 `folder:\\"$input\\"` FilePath 1 0 0 0 0 1 1 Save "save_project" "Save Project" "Saves project data to a file, and sets the current project path as that path." "project,project,session" "" } - {RecordUserAsLastOpened 0 0 "" Null 0 0 0 0 0 0 0 Null "record_user_as_last_opened" "Record User As Last Opened" "Records a file path as the last opened user." "" "" } - {RecordProjectInUser 0 0 "" Null 0 0 0 0 0 0 0 Null "record_project_in_user" "Records Project In User" "Records a file path as a recent project in user data." "" "" } + {NewUser 0 1 1 "" Null 1 0 0 0 0 1 0 Add "new_user" "New User" "Creates a new user file, and sets the current user path as that file's path." "new,user,project,layout" "" } + {NewProject 0 1 1 "" Null 1 0 0 0 0 1 0 Add "new_project" "New Project" "Creates a new project file, and sets the current project path as that file's path." "new,user,project,layout" "" } + {OpenUser 0 1 1 `folder:\\"$input\\"` FilePath 1 0 0 0 0 1 1 Person "open_user" "Open User" "Opens a user file path, immediately loading it, and begins autosaving to it." "load,user,project,layout" "" } + {OpenProject 0 1 1 `folder:\\"$input\\"` FilePath 1 0 0 0 0 1 1 Briefcase "open_project" "Open Project" "Opens a project file path, immediately loading it, and begins autosaving to it." "project,project,session" "" } + {OpenRecentProject 0 1 1 "query:recent_projects" Cfg 0 0 0 0 0 1 1 Briefcase "open_recent_project" "Open Recent Project" "Opens a recently used project file." "project,project,session" "" } + {SaveUser 0 1 1 `folder:\\"$input\\"` FilePath 1 0 0 0 0 1 1 Save "save_user" "Save User" "Saves user data to a file, and sets the current user path as that path." "load,user,project,layout" "" } + {SaveProject 0 1 1 `folder:\\"$input\\"` FilePath 1 0 0 0 0 1 1 Save "save_project" "Save Project" "Saves project data to a file, and sets the current project path as that path." "project,project,session" "" } + {RecordUserAsLastOpened 0 0 0 "" Null 0 0 0 0 0 0 0 Null "record_user_as_last_opened" "Record User As Last Opened" "Records a file path as the last opened user." "" "" } + {RecordProjectInUser 0 0 0 "" Null 0 0 0 0 0 0 0 Null "record_project_in_user" "Records Project In User" "Records a file path as a recent project in user data." "" "" } //- rjf: writing config changes - {WriteUserData 0 1 "" Null 0 0 0 0 0 0 0 Null "write_user_data" "Write User Data" "Writes user data to the active user file." "" "" } - {WriteProjectData 0 1 "" Null 0 0 0 0 0 0 0 Null "write_project_data" "Write Project Data" "Writes project data to the active project file." "" "" } + {WriteUserData 0 0 1 "" Null 0 0 0 0 0 0 0 Null "write_user_data" "Write User Data" "Writes user data to the active user file." "" "" } + {WriteProjectData 0 0 1 "" Null 0 0 0 0 0 0 0 Null "write_project_data" "Write Project Data" "Writes project data to the active project file." "" "" } //- rjf: opening user/project settings - {UserSettings 1 1 "" Null 0 0 0 0 0 0 0 Gear "user_settings" "User Settings" "Opens user settings." "" "" } - {ProjectSettings 1 1 "" Null 0 0 0 0 0 0 0 Gear "project_settings" "Project Settings" "Opens project settings." "" "" } + {UserSettings 0 1 1 "" Null 0 0 0 0 0 0 0 Gear "user_settings" "User Settings" "Opens user settings." "" "" } + {ProjectSettings 0 1 1 "" Null 0 0 0 0 0 0 0 Gear "project_settings" "Project Settings" "Opens project settings." "" "" } //- rjf: meta controls - {Edit 1 1 "" Null 0 0 0 0 0 0 0 Pencil "edit" "Edit" "Edits the current selection." "" "" } - {Accept 1 1 "" Null 0 0 0 0 0 0 0 CheckFilled "accept" "Accept" "Accepts current changes, or answers prompts in the affirmative." "" "" } - {Cancel 1 1 "" Null 0 0 0 0 0 0 0 X "cancel" "Cancel" "Rejects current changes, exits temporary menus, or answers prompts in the negative." "" "" } - {FocusMenu 1 1 "" Null 0 0 0 0 0 0 0 List "focus_menu" "Focus Menu" "Focuses the menu for the selected interface, if there is one." "" "" } + {Edit 0 1 1 "" Null 0 0 0 0 0 0 0 Pencil "edit" "Edit" "Edits the current selection." "" "" } + {Accept 0 1 1 "" Null 0 0 0 0 0 0 0 CheckFilled "accept" "Accept" "Accepts current changes, or answers prompts in the affirmative." "" "" } + {Cancel 0 1 1 "" Null 0 0 0 0 0 0 0 X "cancel" "Cancel" "Rejects current changes, exits temporary menus, or answers prompts in the negative." "" "" } + {FocusMenu 0 1 1 "" Null 0 0 0 0 0 0 0 List "focus_menu" "Focus Menu" "Focuses the menu for the selected interface, if there is one." "" "" } + {Lock 0 1 1 "" Null 0 0 0 0 0 0 0 Locked "lock" "Lock" "Locks the current selection, if applicable." "" "" } + {Unlock 0 1 1 "" Null 0 0 0 0 0 0 0 Unlocked "unlock" "Unlock" "Unlocks the current selection, if applicable." "" "" } + {ToggleLock 0 1 1 "" Null 0 0 0 0 0 0 0 Locked "toggle_lock" "Toggle Lock" "Toggles the lock state of the current selection, if applicable." "" "" } //- rjf: directional movement & text controls - {MoveLeft 1 1 "" Null 0 0 0 0 0 0 0 Null "move_left" "Move Left" "Moves the cursor or selection left." "" "" } - {MoveRight 1 1 "" Null 0 0 0 0 0 0 0 Null "move_right" "Move Right" "Moves the cursor or selection right." "" "" } - {MoveUp 1 1 "" Null 0 0 0 0 0 0 0 Null "move_up" "Move Up" "Moves the cursor or selection up." "" "" } - {MoveDown 1 1 "" Null 0 0 0 0 0 0 0 Null "move_down" "Move Down" "Moves the cursor or selection down." "" "" } - {MoveLeftSelect 1 1 "" Null 0 0 0 0 0 0 0 Null "move_left_select" "Move Left Select" "Moves the cursor or selection left, while selecting." "" "" } - {MoveRightSelect 1 1 "" Null 0 0 0 0 0 0 0 Null "move_right_select" "Move Right Select" "Moves the cursor or selection right, while selecting." "" "" } - {MoveUpSelect 1 1 "" Null 0 0 0 0 0 0 0 Null "move_up_select" "Move Up Select" "Moves the cursor or selection up, while selecting." "" "" } - {MoveDownSelect 1 1 "" Null 0 0 0 0 0 0 0 Null "move_down_select" "Move Down Select" "Moves the cursor or selection down, while selecting." "" "" } - {MoveLeftChunk 1 1 "" Null 0 0 0 0 0 0 0 Null "move_left_chunk" "Move Left Chunk" "Moves the cursor or selection left one chunk." "" "" } - {MoveRightChunk 1 1 "" Null 0 0 0 0 0 0 0 Null "move_right_chunk" "Move Right Chunk" "Moves the cursor or selection right one chunk." "" "" } - {MoveUpChunk 1 1 "" Null 0 0 0 0 0 0 0 Null "move_up_chunk" "Move Up Chunk" "Moves the cursor or selection up one chunk." "" "" } - {MoveDownChunk 1 1 "" Null 0 0 0 0 0 0 0 Null "move_down_chunk" "Move Down Chunk" "Moves the cursor or selection down one chunk." "" "" } - {MoveUpPage 1 1 "" Null 0 0 0 0 0 0 0 Null "move_up_page" "Move Up Page" "Moves the cursor or selection up one page." "" "" } - {MoveDownPage 1 1 "" Null 0 0 0 0 0 0 0 Null "move_down_page" "Move Down Page" "Moves the cursor or selection down one page." "" "" } - {MoveUpWhole 1 1 "" Null 0 0 0 0 0 0 0 Null "move_up_whole" "Move Up Whole" "Moves the cursor or selection to the beginning of the relevant content." "" "" } - {MoveDownWhole 1 1 "" Null 0 0 0 0 0 0 0 Null "move_down_whole" "Move Down Whole" "Moves the cursor or selection to the end of the relevant content." "" "" } - {MoveLeftChunkSelect 1 1 "" Null 0 0 0 0 0 0 0 Null "move_left_chunk_select" "Move Left Chunk Select" "Moves the cursor or selection left one chunk." "" "" } - {MoveRightChunkSelect 1 1 "" Null 0 0 0 0 0 0 0 Null "move_right_chunk_select" "Move Right Chunk Select" "Moves the cursor or selection right one chunk." "" "" } - {MoveUpChunkSelect 1 1 "" Null 0 0 0 0 0 0 0 Null "move_up_chunk_select" "Move Up Chunk Select" "Moves the cursor or selection up one chunk." "" "" } - {MoveDownChunkSelect 1 1 "" Null 0 0 0 0 0 0 0 Null "move_down_chunk_select" "Move Down Chunk Select" "Moves the cursor or selection down one chunk." "" "" } - {MoveUpPageSelect 1 1 "" Null 0 0 0 0 0 0 0 Null "move_up_page_select" "Move Up Page Select" "Moves the cursor or selection up one page, while selecting." "" "" } - {MoveDownPageSelect 1 1 "" Null 0 0 0 0 0 0 0 Null "move_down_page_select" "Move Down Page Select" "Moves the cursor or selection down one page, while selecting." "" "" } - {MoveUpWholeSelect 1 1 "" Null 0 0 0 0 0 0 0 Null "move_up_whole_select" "Move Up Whole Select" "Moves the cursor or selection to the beginning of the relevant content, while selecting." "" "" } - {MoveDownWholeSelect 1 1 "" Null 0 0 0 0 0 0 0 Null "move_down_whole_select" "Move Down Whole Select" "Moves the cursor or selection to the end of the relevant content, while selecting." "" "" } - {MoveUpReorder 1 1 "" Null 0 0 0 0 0 0 0 Null "move_up_reorder" "Move Up Reorder" "Moves the cursor or selection up, while swapping the currently selected element with that upward." "" "" } - {MoveDownReorder 1 1 "" Null 0 0 0 0 0 0 0 Null "move_down_reorder" "Move Down Reorder" "Moves the cursor or selection down, while swapping the currently selected element with that downward." "" "" } - {MoveHome 1 1 "" Null 0 0 0 0 0 0 0 Null "move_home" "Move Home" "Moves the cursor to the beginning of the line." "" "" } - {MoveEnd 1 1 "" Null 0 0 0 0 0 0 0 Null "move_end" "Move End" "Moves the cursor to the end of the line." "" "" } - {MoveHomeSelect 1 1 "" Null 0 0 0 0 0 0 0 Null "move_home_select" "Move Home Select" "Moves the cursor to the beginning of the line, while selecting." "" "" } - {MoveEndSelect 1 1 "" Null 0 0 0 0 0 0 0 Null "move_end_select" "Move End Select" "Moves the cursor to the end of the line, while selecting." "" "" } - {SelectAll 1 1 "" Null 0 0 0 0 0 0 0 Null "select_all" "Select All" "Selects everything possible." "" "" } - {DeleteSingle 1 1 "" Null 0 0 0 0 0 0 0 Null "delete_single" "Delete Single" "Deletes a single element to the right of the cursor, or the active selection." "" "" } - {DeleteChunk 1 1 "" Null 0 0 0 0 0 0 0 Null "delete_chunk" "Delete Chunk" "Deletes a chunk to the right of the cursor, or the active selection." "" "" } - {BackspaceSingle 1 1 "" Null 0 0 0 0 0 0 0 Null "backspace_single" "Backspace Single" "Deletes a single element to the left of the cursor, or the active selection." "" "" } - {BackspaceChunk 1 1 "" Null 0 0 0 0 0 0 0 Null "backspace_chunk" "Backspace Chunk" "Deletes a chunk to the left of the cursor, or the active selection." "" "" } - {Copy 1 1 "" Null 0 0 0 0 0 0 0 Clipboard "copy" "Copy" "Copies the active selection to the clipboard." "" "{text_range_commands}{disasm_range_commands}" } - {Cut 1 1 "" Null 0 0 0 0 0 0 0 Clipboard "cut" "Cut" "Copies the active selection to the clipboard, then deletes it." "" "" } - {Paste 1 1 "" Null 0 0 0 0 0 0 0 Clipboard "paste" "Paste" "Pastes the current contents of the clipboard." "" "" } - {InsertText 0 1 "" Null 0 0 0 0 0 0 0 Null "insert_text" "Insert Text" "Inserts the text that was used to cause this command." "" "" } + {MoveLeft 0 1 1 "" Null 0 0 0 0 0 0 0 Null "move_left" "Move Left" "Moves the cursor or selection left." "" "" } + {MoveRight 0 1 1 "" Null 0 0 0 0 0 0 0 Null "move_right" "Move Right" "Moves the cursor or selection right." "" "" } + {MoveUp 0 1 1 "" Null 0 0 0 0 0 0 0 Null "move_up" "Move Up" "Moves the cursor or selection up." "" "" } + {MoveDown 0 1 1 "" Null 0 0 0 0 0 0 0 Null "move_down" "Move Down" "Moves the cursor or selection down." "" "" } + {MoveLeftSelect 0 1 1 "" Null 0 0 0 0 0 0 0 Null "move_left_select" "Move Left Select" "Moves the cursor or selection left, while selecting." "" "" } + {MoveRightSelect 0 1 1 "" Null 0 0 0 0 0 0 0 Null "move_right_select" "Move Right Select" "Moves the cursor or selection right, while selecting." "" "" } + {MoveUpSelect 0 1 1 "" Null 0 0 0 0 0 0 0 Null "move_up_select" "Move Up Select" "Moves the cursor or selection up, while selecting." "" "" } + {MoveDownSelect 0 1 1 "" Null 0 0 0 0 0 0 0 Null "move_down_select" "Move Down Select" "Moves the cursor or selection down, while selecting." "" "" } + {MoveLeftChunk 0 1 1 "" Null 0 0 0 0 0 0 0 Null "move_left_chunk" "Move Left Chunk" "Moves the cursor or selection left one chunk." "" "" } + {MoveRightChunk 0 1 1 "" Null 0 0 0 0 0 0 0 Null "move_right_chunk" "Move Right Chunk" "Moves the cursor or selection right one chunk." "" "" } + {MoveUpChunk 0 1 1 "" Null 0 0 0 0 0 0 0 Null "move_up_chunk" "Move Up Chunk" "Moves the cursor or selection up one chunk." "" "" } + {MoveDownChunk 0 1 1 "" Null 0 0 0 0 0 0 0 Null "move_down_chunk" "Move Down Chunk" "Moves the cursor or selection down one chunk." "" "" } + {MoveUpPage 0 1 1 "" Null 0 0 0 0 0 0 0 Null "move_up_page" "Move Up Page" "Moves the cursor or selection up one page." "" "" } + {MoveDownPage 0 1 1 "" Null 0 0 0 0 0 0 0 Null "move_down_page" "Move Down Page" "Moves the cursor or selection down one page." "" "" } + {MoveUpWhole 0 1 1 "" Null 0 0 0 0 0 0 0 Null "move_up_whole" "Move Up Whole" "Moves the cursor or selection to the beginning of the relevant content." "" "" } + {MoveDownWhole 0 1 1 "" Null 0 0 0 0 0 0 0 Null "move_down_whole" "Move Down Whole" "Moves the cursor or selection to the end of the relevant content." "" "" } + {MoveLeftChunkSelect 0 1 1 "" Null 0 0 0 0 0 0 0 Null "move_left_chunk_select" "Move Left Chunk Select" "Moves the cursor or selection left one chunk." "" "" } + {MoveRightChunkSelect 0 1 1 "" Null 0 0 0 0 0 0 0 Null "move_right_chunk_select" "Move Right Chunk Select" "Moves the cursor or selection right one chunk." "" "" } + {MoveUpChunkSelect 0 1 1 "" Null 0 0 0 0 0 0 0 Null "move_up_chunk_select" "Move Up Chunk Select" "Moves the cursor or selection up one chunk." "" "" } + {MoveDownChunkSelect 0 1 1 "" Null 0 0 0 0 0 0 0 Null "move_down_chunk_select" "Move Down Chunk Select" "Moves the cursor or selection down one chunk." "" "" } + {MoveUpPageSelect 0 1 1 "" Null 0 0 0 0 0 0 0 Null "move_up_page_select" "Move Up Page Select" "Moves the cursor or selection up one page, while selecting." "" "" } + {MoveDownPageSelect 0 1 1 "" Null 0 0 0 0 0 0 0 Null "move_down_page_select" "Move Down Page Select" "Moves the cursor or selection down one page, while selecting." "" "" } + {MoveUpWholeSelect 0 1 1 "" Null 0 0 0 0 0 0 0 Null "move_up_whole_select" "Move Up Whole Select" "Moves the cursor or selection to the beginning of the relevant content, while selecting." "" "" } + {MoveDownWholeSelect 0 1 1 "" Null 0 0 0 0 0 0 0 Null "move_down_whole_select" "Move Down Whole Select" "Moves the cursor or selection to the end of the relevant content, while selecting." "" "" } + {MoveUpReorder 0 1 1 "" Null 0 0 0 0 0 0 0 Null "move_up_reorder" "Move Up Reorder" "Moves the cursor or selection up, while swapping the currently selected element with that upward." "" "" } + {MoveDownReorder 0 1 1 "" Null 0 0 0 0 0 0 0 Null "move_down_reorder" "Move Down Reorder" "Moves the cursor or selection down, while swapping the currently selected element with that downward." "" "" } + {MoveHome 0 1 1 "" Null 0 0 0 0 0 0 0 Null "move_home" "Move Home" "Moves the cursor to the beginning of the line." "" "" } + {MoveEnd 0 1 1 "" Null 0 0 0 0 0 0 0 Null "move_end" "Move End" "Moves the cursor to the end of the line." "" "" } + {MoveHomeSelect 0 1 1 "" Null 0 0 0 0 0 0 0 Null "move_home_select" "Move Home Select" "Moves the cursor to the beginning of the line, while selecting." "" "" } + {MoveEndSelect 0 1 1 "" Null 0 0 0 0 0 0 0 Null "move_end_select" "Move End Select" "Moves the cursor to the end of the line, while selecting." "" "" } + {SelectAll 0 1 1 "" Null 0 0 0 0 0 0 0 Null "select_all" "Select All" "Selects everything possible." "" "" } + {DeleteSingle 0 1 1 "" Null 0 0 0 0 0 0 0 Null "delete_single" "Delete Single" "Deletes a single element to the right of the cursor, or the active selection." "" "" } + {DeleteChunk 0 1 1 "" Null 0 0 0 0 0 0 0 Null "delete_chunk" "Delete Chunk" "Deletes a chunk to the right of the cursor, or the active selection." "" "" } + {BackspaceSingle 0 1 1 "" Null 0 0 0 0 0 0 0 Null "backspace_single" "Backspace Single" "Deletes a single element to the left of the cursor, or the active selection." "" "" } + {BackspaceChunk 0 1 1 "" Null 0 0 0 0 0 0 0 Null "backspace_chunk" "Backspace Chunk" "Deletes a chunk to the left of the cursor, or the active selection." "" "" } + {Copy 0 1 1 "" Null 0 0 0 0 0 0 0 Clipboard "copy" "Copy" "Copies the active selection to the clipboard." "" "{text_range_commands}{disasm_range_commands}" } + {Cut 0 1 1 "" Null 0 0 0 0 0 0 0 Clipboard "cut" "Cut" "Copies the active selection to the clipboard, then deletes it." "" "" } + {Paste 0 1 1 "" Null 0 0 0 0 0 0 0 Clipboard "paste" "Paste" "Pastes the current contents of the clipboard." "" "" } + {InsertText 0 0 1 "" Null 0 0 0 0 0 0 0 Null "insert_text" "Insert Text" "Inserts the text that was used to cause this command." "" "" } //- rjf: directionless navigation - {MoveNext 1 1 "" Null 0 0 0 0 0 0 0 Null "move_next" "Move Next" "Moves the cursor or selection to the next element." "" "" } - {MovePrev 1 1 "" Null 0 0 0 0 0 0 0 Null "move_prev" "Move Previous" "Moves the cursor or selection to the previous element." "" "" } + {MoveNext 0 1 1 "" Null 0 0 0 0 0 0 0 Null "move_next" "Move Next" "Moves the cursor or selection to the next element." "" "" } + {MovePrev 0 1 1 "" Null 0 0 0 0 0 0 0 Null "move_prev" "Move Previous" "Moves the cursor or selection to the previous element." "" "" } //- rjf: code navigation - {GoToLine 1 1 "" Cursor 0 0 0 0 1 0 1 Null "goto_line" "Go To Line" "Jumps to a line number in the current code file." "" "" } - {GoToAddress 1 1 "" Vaddr 0 0 0 0 1 0 1 Null "goto_address" "Go To Address" "Jumps to an address in the current memory or disassembly view." "" "" } - {CenterCursor 1 1 "" Null 0 0 0 0 0 0 0 Null "center_cursor" "Center Cursor" "Snaps the current code view to center the cursor." "" "" } - {ContainCursor 1 1 "" Null 0 0 0 0 0 0 0 Null "contain_cursor" "Contain Cursor" "Snaps the current code view to contain the cursor." "" "" } - {FindNext 1 1 "" Null 0 0 1 0 0 0 0 Find "find_next" "Find Next" "Searches the current code file forward (from the cursor) for the last searched string." "" "" } - {FindPrev 1 1 "" Null 0 0 1 0 0 0 0 Find "find_prev" "Find Previous" "Searches the current code file backwards (from the cursor) for the last searched string." "" "" } + {GoToLine 0 1 1 "" LineNum 0 0 0 0 1 0 1 Null "goto_line" "Go To Line" "Jumps to a line number in the current code file." "" "" } + {GoToAddress 0 1 1 "" Vaddr 0 0 0 0 1 0 1 Null "goto_address" "Go To Address" "Jumps to an address in the current memory or disassembly view." "" "" } + {CenterCursor 0 1 1 "" Null 0 0 0 0 0 0 0 Null "center_cursor" "Center Cursor" "Snaps the current code view to center the cursor." "" "" } + {ContainCursor 0 1 1 "" Null 0 0 0 0 0 0 0 Null "contain_cursor" "Contain Cursor" "Snaps the current code view to contain the cursor." "" "" } + {FindNext 0 1 1 "" Null 0 0 1 0 0 0 0 Find "find_next" "Find Next" "Searches the current code file forward (from the cursor) for the last searched string." "" "" } + {FindPrev 0 1 1 "" Null 0 0 1 0 0 0 0 Find "find_prev" "Find Previous" "Searches the current code file backwards (from the cursor) for the last searched string." "" "" } //- rjf: thread finding - {FindThread 1 1 "query:threads" Thread 0 0 0 0 0 1 1 Find "find_thread" "Find Thread" "Jumps to the passed thread in either source code, disassembly, or both if they're already open." "" "" } - {FindSelectedThread 1 1 "" Null 0 0 0 0 0 0 0 Find "find_selected_thread" "Find Selected Thread" "Jumps to the selected thread in either source code, disassembly, or both if they're already open." "current,instruction" "{text_pt_commands}{text_range_commands}{disasm_pt_commands}{disasm_range_commands}" } + {FindThread 0 1 1 "query:threads" Thread 0 0 0 0 0 1 1 Find "find_thread" "Find Thread" "Jumps to the passed thread in either source code, disassembly, or both if they're already open." "" "" } + {FindSelectedThread 0 1 1 "" Null 0 0 0 0 0 0 0 Find "find_selected_thread" "Find Selected Thread" "Jumps to the selected thread in either source code, disassembly, or both if they're already open." "current,instruction" "{text_pt_commands}{text_range_commands}{disasm_pt_commands}{disasm_range_commands}" } //- rjf: name finding - {GoToName 1 1 "query:procedures" String 0 0 0 0 1 1 1 Null "goto_name" "Go To Name" "Searches for the passed string as a file, a symbol in debug info, and more, then jumps to it if possible." "" "" } - {GoToNameAtCursor 1 1 "" Null 0 0 0 0 0 0 0 Null "goto_name_at_cursor" "Go To Name At Cursor" "Searches for the text at the cursor as a file, a symbol in debug info, and more, then jumps to it if possible." "" "{text_pt_commands}{disasm_pt_commands}" } + {GoToName 0 1 1 "query:procedures" String 0 0 0 0 1 1 1 Null "goto_name" "Go To Name" "Searches for the passed string as a file, a symbol in debug info, and more, then jumps to it if possible." "" "" } + {GoToNameAtCursor 0 1 1 "" Null 0 0 0 0 0 0 0 Null "goto_name_at_cursor" "Go To Name At Cursor" "Searches for the text at the cursor as a file, a symbol in debug info, and more, then jumps to it if possible." "" "{text_pt_commands}{disasm_pt_commands}" } //- rjf: watch expressions - {ToggleWatchExpression 1 1 "" Null 0 0 0 0 0 0 0 Binoculars "toggle_watch_expr" "Toggle Watch Expression" "Adds or removes an expression to an opened watch view." "" "" } - {ToggleWatchExpressionAtCursor 1 1 "" Null 0 0 0 0 0 0 0 Binoculars "toggle_watch_expr_at_cursor" "Toggle Watch Expression At Cursor" "Adds or removes the expression that the cursor or selection is currently over to an opened watch view." "" "{text_pt_commands}{disasm_pt_commands}" } - {ToggleWatchExpressionAtMouse 1 1 "" Null 0 0 0 0 0 0 0 Binoculars "toggle_watch_expr_at_mouse" "Toggle Watch Expression At Mouse" "Adds or removes the expression that the mouse is currently over to an opened watch view." "" "" } + {ToggleWatchExpression 0 1 1 "" Null 0 0 0 0 0 0 0 Binoculars "toggle_watch_expr" "Toggle Watch Expression" "Adds or removes an expression to an opened watch view." "" "" } + {ToggleWatchExpressionAtCursor 0 1 1 "" Null 0 0 0 0 0 0 0 Binoculars "toggle_watch_expr_at_cursor" "Toggle Watch Expression At Cursor" "Adds or removes the expression that the cursor or selection is currently over to an opened watch view." "" "{text_pt_commands}{disasm_pt_commands}" } + {ToggleWatchExpressionAtMouse 0 1 1 "" Null 0 0 0 0 0 0 0 Binoculars "toggle_watch_expr_at_mouse" "Toggle Watch Expression At Mouse" "Adds or removes the expression that the mouse is currently over to an opened watch view." "" "" } //- rjf: general config operations - {EnableCfg 0 0 "" Null 0 0 0 0 0 0 0 CheckHollow "enable_cfg" "Enable" "Enables a config tree." "" "" } - {DisableCfg 0 0 "" Null 0 0 0 0 0 0 0 CheckFilled "disable_cfg" "Disable" "Disables a config tree." "" "" } - {SelectCfg 0 0 "" Null 0 0 0 0 0 0 0 RadioHollow "select_cfg" "Select" "Selects a config tree, disabling all others of the same kind." "" "" } - {DeselectCfg 0 0 "" Null 0 0 0 0 0 0 0 RadioFilled "deselect_cfg" "Deselect" "Deselects a config tree." "" "" } - {RemoveCfg 0 0 "" Null 0 0 0 0 0 0 0 Trash "remove_cfg" "Remove" "Removes a config tree." "" "" } - {NameCfg 0 0 "" Null 0 0 0 0 0 0 0 Null "name_cfg" "Name" "Equips a config tree with a label." "" "" } - {ConditionCfg 0 0 "" Null 0 0 0 0 0 0 0 Null "condition_cfg" "Condition" "Equips a config tree with a condition string." "" "" } - {DuplicateCfg 0 0 "" Null 0 0 0 0 0 0 0 Duplicate "duplicate_cfg" "Duplicate" "Duplicates a config tree." "" "" } - {RelocateCfg 0 0 "" Null 0 0 0 0 0 0 0 Null "relocate_cfg" "Relocate" "Relocates a config tree." "" "" } - {SaveToProject 0 0 "" Null 0 0 0 0 0 0 0 Briefcase "save_cfg_to_project" "Save To Project" "Saves the config tree to the project." "" "" } + {EnableCfg 0 0 0 "" Null 0 0 0 0 0 0 0 CheckHollow "enable_cfg" "Enable" "Enables a config tree." "" "" } + {DisableCfg 0 0 0 "" Null 0 0 0 0 0 0 0 CheckFilled "disable_cfg" "Disable" "Disables a config tree." "" "" } + {SelectCfg 0 0 0 "" Null 0 0 0 0 0 0 0 RadioHollow "select_cfg" "Select" "Selects a config tree, disabling all others of the same kind." "" "" } + {DeselectCfg 0 0 0 "" Null 0 0 0 0 0 0 0 RadioFilled "deselect_cfg" "Deselect" "Deselects a config tree." "" "" } + {RemoveCfg 0 0 0 "" Null 0 0 0 0 0 0 0 Trash "remove_cfg" "Remove" "Removes a config tree." "" "" } + {NameCfg 0 0 0 "" Null 0 0 0 0 0 0 0 Null "name_cfg" "Name" "Equips a config tree with a label." "" "" } + {ConditionCfg 0 0 0 "" Null 0 0 0 0 0 0 0 Null "condition_cfg" "Condition" "Equips a config tree with a condition string." "" "" } + {DuplicateCfg 0 0 0 "" Null 0 0 0 0 0 0 0 Duplicate "duplicate_cfg" "Duplicate" "Duplicates a config tree." "" "" } + {RelocateCfg 0 0 0 "" Null 0 0 0 0 0 0 0 Null "relocate_cfg" "Relocate" "Relocates a config tree." "" "" } + {SaveToProject 0 0 0 "" Null 0 0 0 0 0 0 0 Briefcase "save_cfg_to_project" "Save To Project" "Saves the config tree to the project." "" "" } //- rjf: breakpoints - {AddBreakpoint 1 1 "" Null 0 0 0 0 0 0 0 CircleFilled "add_breakpoint" "Add Line Breakpoint" "Places a breakpoint at a given location (file path and line number, address, or symbol name)." "" "" } - {AddAddressBreakpoint 1 0 "" Expr 0 0 0 0 1 1 1 CircleFilled "add_address_breakpoint" "Add Address Breakpoint" "Places a breakpoint on the specified address." "" "" } - {AddFunctionBreakpoint 1 0 "query:procedures" String 0 0 0 0 1 1 1 CircleFilled "add_function_breakpoint" "Add Function Breakpoint" "Places a breakpoint on the first address of the specified function." "" "" } - {ToggleBreakpoint 1 1 "" Null 0 0 0 0 0 0 0 CircleFilled "toggle_breakpoint" "Toggle Line Breakpoint" "Places or removes a breakpoint at a given location (file path and line number, address, or symbol name)." "" "{text_pt_commands}{disasm_pt_commands}" } - {RemoveBreakpoint 1 0 "query:breakpoints" Cfg 0 0 0 0 1 1 1 Trash "remove_breakpoint" "Remove Breakpoint" "Removes an existing breakpoint." "delete" "" } - {EnableBreakpoint 1 1 "query:breakpoints" Cfg 0 0 0 0 1 1 1 CheckFilled "enable_breakpoint" "Enable Breakpoint" "Enables a breakpoint." "" "" } - {DisableBreakpoint 1 1 "query:breakpoints" Cfg 0 0 0 0 1 1 1 CheckHollow "disable_breakpoint" "Disable Breakpoint" "Disables a breakpoint." "" "" } - {ClearBreakpoints 1 1 "" Null 0 0 0 0 0 0 0 Trash "clear_breakpoints" "Clear Breakpoints" "Removes all breakpoints." "" "" } + {AddBreakpoint 0 1 1 "" Null 0 0 0 0 0 0 0 CircleFilled "add_breakpoint" "Add Line Breakpoint" "Places a breakpoint at a given location (file path and line number, address, or symbol name)." "" "" } + {AddAddressBreakpoint 0 1 0 "" Expr 0 0 0 0 1 1 1 CircleFilled "add_address_breakpoint" "Add Address Breakpoint" "Places a breakpoint on the specified address." "" "" } + {AddFunctionBreakpoint 0 1 0 "query:procedures" String 0 0 0 0 1 1 1 CircleFilled "add_function_breakpoint" "Add Function Breakpoint" "Places a breakpoint on the first address of the specified function." "" "" } + {ToggleBreakpoint 0 1 1 "" Null 0 0 0 0 0 0 0 CircleFilled "toggle_breakpoint" "Toggle Line Breakpoint" "Places or removes a breakpoint at a given location (file path and line number, address, or symbol name)." "" "{text_pt_commands}{disasm_pt_commands}" } + {RemoveBreakpoint 0 1 0 "query:breakpoints" Cfg 0 0 0 0 1 1 1 Trash "remove_breakpoint" "Remove Breakpoint" "Removes an existing breakpoint." "delete" "" } + {EnableBreakpoint 0 1 1 "query:breakpoints" Cfg 0 0 0 0 1 1 1 CheckFilled "enable_breakpoint" "Enable Breakpoint" "Enables a breakpoint." "" "" } + {DisableBreakpoint 0 1 1 "query:breakpoints" Cfg 0 0 0 0 1 1 1 CheckHollow "disable_breakpoint" "Disable Breakpoint" "Disables a breakpoint." "" "" } + {ClearBreakpoints 0 1 1 "" Null 0 0 0 0 0 0 0 Trash "clear_breakpoints" "Clear Breakpoints" "Removes all breakpoints." "" "" } //- rjf: output - {ClearOutput 1 1 "" Null 0 0 0 0 0 0 0 Null "clear_output" "Clear Output" "Clears all output." "" "" } + {ClearOutput 0 1 1 "" Null 0 0 0 0 0 0 0 Null "clear_output" "Clear Output" "Clears all output." "" "" } //- rjf: watch pins - {AddWatchPin 1 1 "" Expr 0 0 0 0 1 1 1 Pin "add_watch_pin" "Add Watch Pin" "Places a watch pin at a given location (file path and line number or address)." "" "" } - {ToggleWatchPin 1 0 "" Expr 0 0 0 0 1 1 1 Pin "toggle_watch_pin" "Toggle Watch Pin" "Places or removes a watch pin at a given location (file path and line number or address)." "" "" } + {AddWatchPin 0 1 1 "" Expr 0 0 0 0 1 1 1 Pin "add_watch_pin" "Add Watch Pin" "Places a watch pin at a given location (file path and line number or address)." "" "" } + {ToggleWatchPin 0 1 0 "" Expr 0 0 0 0 1 1 1 Pin "toggle_watch_pin" "Toggle Watch Pin" "Places or removes a watch pin at a given location (file path and line number or address)." "" "" } //- rjf: debug infos - {LoadDebugInfo 1 1 `folder:\\"$input\\"` FilePath 1 0 0 0 0 1 1 Module "load_debug_info" "Load Debug Info" "Loads a debug info file." "" "" } - {UnloadDebugInfo 1 1 "query:debug_infos" Cfg 0 0 0 0 1 1 1 Module "unload_debug_info" "Unload Debug Info" "Unloads a debug info file." "" "" } + {LoadDebugInfo 0 1 1 `folder:\\"$input\\"` FilePath 1 0 0 0 0 1 1 Module "load_debug_info" "Load Debug Info" "Loads a debug info file." "" "" } + {UnloadDebugInfo 0 1 1 "query:debug_infos" Cfg 0 0 0 0 1 1 1 Module "unload_debug_info" "Unload Debug Info" "Unloads a debug info file." "" "" } //- rjf: type views - {AddTypeView 0 0 "" String 0 0 0 0 0 0 0 Binoculars "add_type_view" "Add Type View" "Adds a new type view." "" "" } + {AddTypeView 0 0 0 "" String 0 0 0 0 0 0 0 Binoculars "add_type_view" "Add Type View" "Adds a new type view." "" "" } //- rjf: file path maps - {AddFilePathMap 0 0 "" Null 0 0 0 0 0 0 0 FileOutline "add_file_path_map" "Add File Path Map" "Adds a new file path map." "" "" } + {AddFilePathMap 0 0 0 "" Null 0 0 0 0 0 0 0 FileOutline "add_file_path_map" "Add File Path Map" "Adds a new file path map." "" "" } //- rjf: themes - {EditUserTheme 0 0 "query:themes" String 0 0 0 0 0 0 0 Palette "edit_user_theme" "Edit User Theme" "Edits the current user's theme." "color" "" } - {EditProjectTheme 0 0 "query:themes" String 0 0 0 0 0 0 0 Palette "edit_project_theme" "Edit Project Theme" "Edits the current project's theme." "color" "" } - {AddThemeColor 0 0 "" Null 0 0 0 0 0 0 0 Palette "add_theme_color" "Add Theme Color" "Adds a new theme color." "" "" } - {ForkTheme 0 0 "" Null 0 0 0 0 0 0 0 Palette "fork_theme" "Fork Theme" "Imports all colors from the current theme so they can be individually edited." "" "" } - {SaveTheme 0 0 "" String 1 0 0 0 0 1 1 Save "save_theme" "Save Theme" "Saves all theme colors to a new theme file." "" "" } - {SaveAndSetTheme 0 0 "" String 1 0 0 0 0 1 1 Save "save_and_set_theme" "Save And Set Theme" "Saves all theme colors to a new theme file, and then sets that theme as the selected theme." "" "" } + {EditUserTheme 0 0 0 "query:themes" String 0 0 0 0 0 0 0 Palette "edit_user_theme" "Edit User Theme" "Edits the current user's theme." "color" "" } + {EditProjectTheme 0 0 0 "query:themes" String 0 0 0 0 0 0 0 Palette "edit_project_theme" "Edit Project Theme" "Edits the current project's theme." "color" "" } + {AddThemeColor 0 0 0 "" Null 0 0 0 0 0 0 0 Palette "add_theme_color" "Add Theme Color" "Adds a new theme color." "" "" } + {ForkTheme 0 0 0 "" Null 0 0 0 0 0 0 0 Palette "fork_theme" "Fork Theme" "Imports all colors from the current theme so they can be individually edited." "" "" } + {SaveTheme 0 0 0 "" String 1 0 0 0 0 1 1 Save "save_theme" "Save Theme" "Saves all theme colors to a new theme file." "" "" } + {SaveAndSetTheme 0 0 0 "" String 1 0 0 0 0 1 1 Save "save_and_set_theme" "Save And Set Theme" "Saves all theme colors to a new theme file, and then sets that theme as the selected theme." "" "" } //- rjf: line operations - {SetNextStatement 1 1 "" Null 0 0 0 0 0 0 0 RightArrow "set_next_statement" "Set Next Statement" "Sets the selected thread's instruction pointer to the cursor's position." "" "{text_pt_commands}{disasm_pt_commands}" } + {SetNextStatement 0 1 1 "" Null 0 0 0 0 0 0 0 RightArrow "set_next_statement" "Set Next Statement" "Sets the selected thread's instruction pointer to the cursor's position." "" "{text_pt_commands}{disasm_pt_commands}" } //- rjf: targets - {AddTarget 1 1 `folder:\\"$input\\"` FilePath 1 0 0 0 0 1 1 Target "add_target" "Add Target" "Adds a new target." "application,executable,debug" "" } - {SelectTarget 1 1 "query:targets" Cfg 0 0 0 0 0 1 1 Target "select_target" "Select Target" "Selects a target." "" "" } - {EnableTarget 1 1 "query:targets" Cfg 0 0 0 0 0 1 1 CheckFilled "enable_target" "Enable Target" "Enables a target, in addition to all targets currently enabled." "" "" } - {DisableTarget 1 1 "query:targets" Cfg 0 0 0 0 0 1 1 CheckHollow "disable_target" "Disable Target" "Disables a target." "" "" } - {RemoveTarget 1 1 "query:targets" Cfg 0 0 0 0 0 1 1 Trash "remove_target" "Remove Target" "Removes a target." "delete" "" } + {AddTarget 0 1 1 `folder:\\"$input\\"` FilePath 1 0 0 0 0 1 1 Target "add_target" "Add Target" "Adds a new target." "application,executable,debug" "" } + {SelectTarget 0 1 1 "query:targets" Cfg 0 0 0 0 0 1 1 Target "select_target" "Select Target" "Selects a target." "" "" } + {EnableTarget 0 1 1 "query:targets" Cfg 0 0 0 0 0 1 1 CheckFilled "enable_target" "Enable Target" "Enables a target, in addition to all targets currently enabled." "" "" } + {DisableTarget 0 1 1 "query:targets" Cfg 0 0 0 0 0 1 1 CheckHollow "disable_target" "Disable Target" "Disables a target." "" "" } + {RemoveTarget 0 1 1 "query:targets" Cfg 0 0 0 0 0 1 1 Trash "remove_target" "Remove Target" "Removes a target." "delete" "" } //- rjf: attaching - {RegisterAsJITDebugger 1 1 "" Null 0 0 0 0 0 0 0 Null "register_as_jit_debugger" "Register As Just-In-Time (JIT) Debugger" "Registers the RAD debugger as the just-in-time (JIT) debugger used by the operating system." "" "" } + {RegisterAsJITDebugger 0 1 1 "" Null 0 0 0 0 0 0 0 Null "register_as_jit_debugger" "Register As Just-In-Time (JIT) Debugger" "Registers the RAD debugger as the just-in-time (JIT) debugger used by the operating system." "" "" } //- rjf: snap-to-code-location - {FindCodeLocation 0 1 "" FilePath 0 0 0 0 0 1 1 FileOutline "find_code_location" "Find Code Location" "Finds a specific source code location given file, line, and column coordinates. Opens the file if necessary." "" "" } + {FindCodeLocation 0 0 1 "" FilePath 0 0 0 0 0 1 1 FileOutline "find_code_location" "Find Code Location" "Finds a specific source code location given file, line, and column coordinates. Opens the file if necessary." "" "" } //- rjf: snap-to-memory-location - {GoToMemory 0 1 "" Expr 0 0 0 0 1 1 0 Grid "go_to_memory" "Go To Memory" "Finds the specified expression in the memory view. Opens the memory view if necessary." "" "{memory_eval_commands}" } + {GoToMemory 0 0 1 "" Expr 0 0 0 0 1 1 0 Grid "go_to_memory" "Go To Memory" "Finds the specified expression in the memory view. Opens the memory view if necessary." "" "{memory_eval_commands}" } //- rjf: break-when-value-changes - {BreakWhenValueChanges 0 1 "" Expr 0 0 0 0 1 1 0 CircleFilled "break_when_value_changes" "Break When Value Changes" "Adds a new write data breakpoint for this variable's range." "" "{memory_eval_commands}" } + {BreakWhenValueChanges 0 0 1 "" Expr 0 0 0 0 1 1 0 CircleFilled "break_when_value_changes" "Break When Value Changes" "Adds a new write data breakpoint for this variable's range." "" "{memory_eval_commands}" } //- rjf: searching - {Search 1 1 "" String 0 0 1 1 1 0 1 Find "search" "Search" "Begins searching within the active interface." "sort,search,filter,find" "" } - {SearchBackwards 1 1 "" String 0 0 1 1 1 0 1 Find "search_backwards" "Search Backwards" "Begins searching backwards within the active interface." "sort,search,filter,find" "" } + {Search 0 1 1 "" String 0 0 1 1 1 0 1 Find "search" "Search" "Begins searching within the active interface." "sort,search,filter,find" "" } + {SearchBackwards 0 1 1 "" String 0 0 1 1 1 0 1 Find "search_backwards" "Search Backwards" "Begins searching backwards within the active interface." "sort,search,filter,find" "" } //- rjf: queries - {PickFile 0 0 `folder:\\"$input\\"` FilePath 1 0 0 0 0 1 1 FileOutline "pick_file" "Pick File" "Opens the file browser to pick a file." "" "" } - {PickFolder 0 0 `folder:\\"$input\\"` FilePath 0 1 0 0 0 1 1 FolderOpenFilled "pick_folder" "Pick Folder" "Opens the file browser to pick a folder." "" "" } - {PickFileOrFolder 0 0 `folder:\\"$input\\"` FilePath 1 1 0 0 0 1 1 FileOutline "pick_file_or_folder" "Pick File/Folder" "Opens the file browser to pick a file or folder." "" "" } + {PickFile 0 0 0 `folder:\\"$input\\"` FilePath 1 0 0 0 0 1 1 FileOutline "pick_file" "Pick File" "Opens the file browser to pick a file." "" "" } + {PickFolder 0 0 0 `folder:\\"$input\\"` FilePath 0 1 0 0 0 1 1 FolderOpenFilled "pick_folder" "Pick Folder" "Opens the file browser to pick a folder." "" "" } + {PickFileOrFolder 0 0 0 `folder:\\"$input\\"` FilePath 1 1 0 0 0 1 1 FileOutline "pick_file_or_folder" "Pick File/Folder" "Opens the file browser to pick a file or folder." "" "" } //- rjf: query stack - {PushQuery 0 0 "" Null 0 0 0 0 0 0 0 Null "push_query" "Push Query" "Opens a new temporary query interface." "" "" } - {CompleteQuery 0 0 "" Null 0 0 0 0 0 0 0 Null "complete_query" "Complete Query" "Completes and closes a query." "" "" } - {CancelQuery 0 0 "" Null 0 0 0 0 0 0 0 Null "cancel_query" "Cancel Query" "Closes a query." "" "" } - {UpdateQuery 0 0 "" Null 0 0 0 0 0 0 0 Null "update_query" "Update Query" "Updates a query input." "" "" } + {PushQuery 0 0 0 "" Null 0 0 0 0 0 0 0 Null "push_query" "Push Query" "Opens a new temporary query interface." "" "" } + {CompleteQuery 0 0 0 "" Null 0 0 0 0 0 0 0 Null "complete_query" "Complete Query" "Completes and closes a query." "" "" } + {CancelQuery 0 0 0 "" Null 0 0 0 0 0 0 0 Null "cancel_query" "Cancel Query" "Closes a query." "" "" } + {UpdateQuery 0 0 0 "" Null 0 0 0 0 0 0 0 Null "update_query" "Update Query" "Updates a query input." "" "" } //- rjf: event buffers - {OpenEventBuffer 0 1 "" Null 0 0 0 0 0 0 0 Null "open_event_buffer" "Open Event Buffer" "Opens a new event buffer, to which debugger events will be written, for external processing." "" "" } - {CloseEventBuffer 0 1 "" Cfg 0 0 0 0 0 0 0 Null "close_event_buffer" "Close Event Buffer" "Closes an existing event buffer." "" "" } + {OpenEventBuffer 0 0 1 "" Null 0 0 0 0 0 0 0 Null "open_event_buffer" "Open Event Buffer" "Opens a new event buffer, to which debugger events will be written, for external processing." "" "" } + {CloseEventBuffer 0 0 1 "" Cfg 0 0 0 0 0 0 0 Null "close_event_buffer" "Close Event Buffer" "Closes an existing event buffer." "" "" } //- rjf: developer commands - {ToggleDevMenu 1 1 "" Null 0 0 0 0 0 0 0 Null "toggle_dev_menu" "Toggle Developer Menu" "Opens and closes the developer menu." "" "" } - {LogMarker 1 1 "" Null 0 0 0 0 0 0 0 Null "log_marker" "Log Marker" "Logs a marker in the application log, to denote specific points in time within the log." "" "" } + {ToggleDevMenu 0 1 1 "" Null 0 0 0 0 0 0 0 Null "toggle_dev_menu" "Toggle Developer Menu" "Opens and closes the developer menu." "" "" } + {LogMarker 0 1 1 "" Null 0 0 0 0 0 0 0 Null "log_marker" "Log Marker" "Logs a marker in the application log, to denote specific points in time within the log." "" "" } } @enum RD_CmdKind: @@ -1370,6 +1376,7 @@ RD_DefaultBindingTable: { "accept" Space 0 0 0 } { "cancel" Esc 0 0 0 } { "focus_menu" D 0 0 alt } + { "toggle_lock" L ctrl 0 0 } //- rjf: directional movement & text controls { "move_left" Left 0 0 0 } @@ -2522,7 +2529,7 @@ raddbg_readme: @p "**Configuration files (users and projects):** The RAD Debugger stores configuration in two files. One is the 'user file', the other is the 'project file'. Both files are the same format and can store the same kinds of data, but the user file is preferred by the debugger for more likely user-related data (windows, keybindings, theme), and the project file is preferred by the debugger for more likely project-related data (executable debugging targets, breakpoints, recent source files). Project files are more likely to be what you'd check into source control, whereas a user file is more likely to have your personal debugger settings (which will apply identically regardless of which project file is opened)."; - @p "The debugger autosaves user and project files. You do not need to manually save them. To switch which path you are using for either, you can use the `Open User` (Ctrl + Alt + Shift + O, by default), or `Open Project` (Ctrl + Alt + O, by default) commands respectively. If a file does not exist at the path you enter for either, then a new one will be created, and the debugger will begin autosaving to it. If the initial paths to these files are not specified on the command line (via `--user` or `--project`), then the debugger uses default paths for them. The user file path, by default, will be `%appdata%/raddbg/default.raddbg_user`. The project file path will be whatever project path was last loaded for the user, or if no such path exists, `%appdata%/raddbg/default.raddbg_project`. If you suspect that your configuration files are corrupted or causing the debugger to behave poorly, it might help to delete your `%appdata%/raddbg` folder (although it'd also help if you [sent it to us in a bug report](https://github.com/EpicGamesExt/raddebugger/issues), so that we can investigate why they were corrupted to begin with!)."; + @p "The debugger autosaves user and project files. You do not need to manually save them. To switch which path you are using for either, you can use the `Open User` (Ctrl + Alt + Shift + O, by default), or `Open Project` (Ctrl + Alt + O, by default) commands respectively. If a file does not exist at the path you enter for either, then a new one will be created, and the debugger will begin autosaving to it. If the initial paths to these files are not specified on the command line (via `--user` or `--project`), then the debugger uses default paths for them. The user file path, by default, will be `%appdata%/raddbg/default.raddbg_user`. The project file path will be whatever project path was last loaded for the user, or if no such path exists, `%appdata%/raddbg/default.raddbg_project`. If you suspect that your configuration files are corrupted or causing the debugger to behave poorly, it might help to delete your `%appdata%/raddbg` folder (although it'd also help if you [sent it to us in a bug report](https://github.com/EpicGames/raddebugger/issues), so that we can investigate why they were corrupted to begin with!)."; @p "For more information, see the `**User & Project Files** section."; @p "**Watch tabs and visualizers:** 'Watch' tabs in the RAD Debugger allow entering expressions, which can reference variables in your program, and visualize what their value is when your program is stopped at a particular time. These expressions roughly follow C expression syntax, but there are a number of extensions which can be used to visualize expressions in a more useful way. Here are some examples:"; diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index 9fec8814..52c1a7c3 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -1671,13 +1671,6 @@ rd_view_ui(Rng2F32 rect) vs->query_string_size = Min(sizeof(vs->query_buffer), current_input.size); MemoryCopy(vs->query_buffer, current_input.str, vs->query_string_size); - //- rjf: clamp cursor - if(vs->query_cursor.column == 0) - { - vs->query_mark = txt_pt(1, 1); - vs->query_cursor = txt_pt(1, vs->query_string_size+1); - } - //- rjf: determine dimensions F32 search_row_height_target = ui_top_px_height(); F32 search_row_height = search_row_open_t*search_row_height_target; @@ -2041,7 +2034,7 @@ rd_view_ui(Rng2F32 rect) { if(expr_string.size == 0) { - expr_string = str8f(scratch.arena, "query:config.$%I64x.watches", rd_regs()->view); + expr_string = str8f(scratch.arena, "query:config.$%I64x.watch_expressions", rd_regs()->view); } E_Eval eval = e_eval_from_string(expr_string); RD_WatchViewState *ewv = rd_view_state(RD_WatchViewState); @@ -2056,6 +2049,35 @@ rd_view_ui(Rng2F32 rect) ewv->text_edit_arena = rd_push_view_arena(); } + ////////////////////////////// + //- rjf: adjust locked expressions which have been invalidated + // + for(CFG_Node *child = view->first; child != &cfg_nil_node; child = child->next) + { + if(str8_match(child->string, s("watch_expression"), 0)) + { + CFG_Node *locked = cfg_node_child_from_string(child, s("locked")); + if(locked != &cfg_nil_node) + { + CFG_Node *pid_cfg = cfg_node_child_from_string_or_alloc(rd_state->cfg, locked, s("pid")); + U64 pid = u64_from_str8(pid_cfg->first->string, 10); + D_Entity *process = d_process_from_id(pid); + if(process == &d_entity_nil) + { + D_Entity *current_process = d_entity_from_handle(rd_regs()->process); + if(current_process != &d_entity_nil) + { + cfg_node_new_replacef(rd_state->cfg, pid_cfg, "%I64u", current_process->id); + } + else + { + cfg_node_release(rd_state->cfg, locked); + } + } + } + } + } + ////////////////////////////// //- rjf: unpack arguments // @@ -2622,8 +2644,8 @@ rd_view_ui(Rng2F32 rect) RD_WatchViewTextEditState *edit_state = push_array(ewv->text_edit_arena, RD_WatchViewTextEditState, 1); SLLStackPush_N(ewv->text_edit_state_slots[slot_idx], edit_state, pt_hash_next); edit_state->pt = pt; - edit_state->cursor = txt_pt(1, string.size+1); - edit_state->mark = txt_pt(1, 1); + edit_state->cursor = string.size; + edit_state->mark = 0; edit_state->input_size = string.size; MemoryCopy(edit_state->input_buffer, string.str, string.size); edit_state->initial_size = string.size; @@ -2701,6 +2723,79 @@ rd_view_ui(Rng2F32 rect) } } + ////////////////////////// + //- rjf: [table] do cell-granularity lock operations + // + if(!ewv->text_editing && + (evt->slot == UI_EventActionSlot_Lock || + evt->slot == UI_EventActionSlot_Unlock || + evt->slot == UI_EventActionSlot_ToggleLock) && + (selection_tbl.min.y != 0 || selection_tbl.max.y != 0)) + { + EV_WindowedRowList rows = ev_rows_from_num_range(scratch.arena, eval_view, &block_ranges, r1u64(selection_tbl.min.y, selection_tbl.max.y+1)); + EV_WindowedRowNode *row_node = rows.first; + if(row_node != 0) + { + taken = 1; + B32 next_locked = 0; + B32 found_next_locked = 0; + for(S64 y = selection_tbl.min.y; y <= selection_tbl.max.y && row_node != 0; y += 1, row_node = row_node->next) + { + // rjf: unpack row info + EV_Row *row = &row_node->row; + RD_WatchRowInfo row_info = rd_watch_row_info_from_row(scratch.arena, row); + CFG_Node *cfg = row_info.group_cfg_child; + + // rjf: determine if row can be locked + B32 row_can_be_locked = 0; + if(str8_match(row_info.group_cfg_name, s("watch_expression"), 0) && + cfg != &cfg_nil_node && + row->eval.string.size != 0) + { + E_Space space = row->eval.space; + D_Entity *entity = rd_ctrl_entity_from_eval_space(space); + B32 is_memory_eval = (space.kind == D_EvalSpaceKind_Entity && entity->kind == D_EntityKind_Process); + row_can_be_locked = (is_memory_eval && + (row->eval.irtree.mode == E_Mode_Offset) || + (row->eval.irtree.mode == E_Mode_Value && + e_type_kind_is_pointer_or_ref(e_type_kind_from_key(e_type_key_unwrap(row->eval.irtree.type_key, E_TypeUnwrapFlag_AllDecorative))))); + } + + // rjf: determine if row is locked + B32 row_is_locked = (cfg_node_child_from_string(cfg, s("locked")) != &cfg_nil_node); + if(row_can_be_locked && !found_next_locked) + { + found_next_locked = 1; + next_locked = !row_is_locked; + } + + // rjf: toggle lock on row + if(row_can_be_locked) + { + if(next_locked) + { + E_Space space = row->eval.space; + D_Entity *entity = rd_ctrl_entity_from_eval_space(space); + CFG_Node *locked = cfg_node_child_from_string_or_alloc(rd_state->cfg, cfg, s("locked")); + cfg_node_release_all_children(rd_state->cfg, locked); + CFG_Node *mid = cfg_node_new(rd_state->cfg, locked, s("mid")); + cfg_node_newf(rd_state->cfg, mid, "%I64u", entity->handle.machine_id); + CFG_Node *pid = cfg_node_new(rd_state->cfg, locked, s("pid")); + cfg_node_newf(rd_state->cfg, pid, "%I64u", entity->id); + CFG_Node *addr = cfg_node_new(rd_state->cfg, locked, s("addr")); + cfg_node_newf(rd_state->cfg, addr, "0x%I64x", row->eval.value.u64); + CFG_Node *type = cfg_node_new(rd_state->cfg, locked, s("type")); + cfg_node_new(rd_state->cfg, type, e_type_string_from_key(scratch.arena, row->eval.irtree.type_key)); + } + else + { + cfg_node_release(rd_state->cfg, cfg_node_child_from_string(cfg, s("locked"))); + } + } + } + } + } + ////////////////////////// //- rjf: [text] apply textual edits // @@ -2733,10 +2828,10 @@ rd_view_ui(Rng2F32 rect) RD_WatchPt pt = {row->block->key, row->key, rd_id_from_watch_cell(cell)}; RD_WatchViewTextEditState *edit_state = rd_watch_view_text_edit_state_from_pt(ewv, pt); String8 string = str8(edit_state->input_buffer, edit_state->input_size); - UI_TxtOp op = ui_single_line_txt_op_from_event(scratch.arena, evt, string, edit_state->cursor, edit_state->mark); + UI_TxtOp op = ui_single_line_txt_op_from_event(scratch.arena, evt, string, r1u64(0, string.size), edit_state->cursor, edit_state->mark); // rjf: copy - if(op.flags & UI_TxtOpFlag_Copy && selection_tbl.min.x == selection_tbl.max.x && selection_tbl.min.y == selection_tbl.max.y) + if(evt->flags & UI_EventFlag_Copy && selection_tbl.min.x == selection_tbl.max.x && selection_tbl.min.y == selection_tbl.max.y) { wm_set_clipboard_text(op.copy); } @@ -2748,13 +2843,13 @@ rd_view_ui(Rng2F32 rect) CFG_Node *window = cfg_node_from_id(rd_regs()->window); RD_WindowState *ws = rd_window_state_from_cfg(window); RD_AutocompCursorInfo *autocomp_cursor_info = &ws->autocomp_cursor_info; - String8 new_string = ui_push_string_replace_range(scratch.arena, string, r1s64(autocomp_cursor_info->replaced_range.min+1, autocomp_cursor_info->replaced_range.max+1), autocomplete_string); + String8 new_string = ui_push_string_replace_range(scratch.arena, string, autocomp_cursor_info->replaced_range, autocomplete_string); new_string.size = Min(sizeof(edit_state->input_buffer), new_string.size); MemoryCopy(edit_state->input_buffer, new_string.str, new_string.size); edit_state->input_size = new_string.size; - edit_state->cursor = edit_state->mark = txt_pt(1, 1+autocomp_cursor_info->replaced_range.min+autocomplete_string.size); + edit_state->cursor = edit_state->mark = autocomp_cursor_info->replaced_range.min+autocomplete_string.size; string = str8(edit_state->input_buffer, edit_state->input_size); - op = ui_single_line_txt_op_from_event(scratch.arena, evt, string, edit_state->cursor, edit_state->mark); + op = ui_single_line_txt_op_from_event(scratch.arena, evt, string, r1u64(0, string.size), edit_state->cursor, edit_state->mark); } // rjf: cancel? -> revert to initial string @@ -2765,9 +2860,9 @@ rd_view_ui(Rng2F32 rect) // rjf: obtain edited string String8 new_string = string; - if(!txt_pt_match(op.range.min, op.range.max) || op.replace.size != 0) + if(op.range.min != op.range.max || op.replace.size != 0) { - new_string = ui_push_string_replace_range(scratch.arena, string, r1s64(op.range.min.column, op.range.max.column), op.replace); + new_string = ui_push_string_replace_range(scratch.arena, string, op.range, op.replace); } // rjf: commit to edit state @@ -2785,13 +2880,13 @@ rd_view_ui(Rng2F32 rect) { CFG_Node *cfg = row_info.group_cfg_child; String8 child_key = {0}; // str8_lit("expression"); + if(str8_match(row_info.group_cfg_name, s("watch_expression"), 0)) + { + child_key = s("expression"); + } if(cfg == &cfg_nil_node && editing_complete && new_string.size != 0) { CFG_Node *new_cfg_parent = row_info.group_cfg_parent; - if(new_cfg_parent != &cfg_nil_node) - { - child_key = str8_zero(); - } if(new_cfg_parent == &cfg_nil_node) { CFG_NodePtrList all_cfgs = cfg_node_top_level_list_from_string(scratch.arena, row_info.group_cfg_name); @@ -3533,8 +3628,9 @@ rd_view_ui(Rng2F32 rect) if(cfg == &cfg_nil_node) { cfg = cfg_node_alloc(rd_state->cfg); - cfg_node_equip_stringf(rd_state->cfg, cfg, "watch"); - cfg_node_new(rd_state->cfg, cfg, drag_regs->expr); + cfg_node_equip_stringf(rd_state->cfg, cfg, "watch_expression"); + CFG_Node *expr = cfg_node_new(rd_state->cfg, cfg, s("expression")); + cfg_node_new(rd_state->cfg, expr, drag_regs->expr); } cfg_node_insert_child(rd_state->cfg, drag_parent_cfg, drag_prev_cfg, cfg); }break; @@ -4030,9 +4126,33 @@ rd_view_ui(Rng2F32 rect) needle = str8_skip_last_slash(needle); } + // rjf: determine if this cell can be locked + B32 cell_can_be_locked = 0; + if(str8_match(row_info->group_cfg_name, s("watch_expression"), 0) && + !(cell_info.flags & RD_WatchCellFlag_Expr) && + cell->eval.string.size != 0) + { + E_Space space = row->eval.space; + D_Entity *entity = rd_ctrl_entity_from_eval_space(space); + B32 is_memory_eval = (space.kind == D_EvalSpaceKind_Entity && entity->kind == D_EntityKind_Process); + cell_can_be_locked = (is_memory_eval && + (cell->eval.irtree.mode == E_Mode_Offset) || + (cell->eval.irtree.mode == E_Mode_Value && + e_type_kind_is_pointer_or_ref(e_type_kind_from_key(e_type_key_unwrap(cell->eval.irtree.type_key, E_TypeUnwrapFlag_AllDecorative))))); + } + + // rjf: determine if this cell is locked + B32 cell_is_locked = 0; + if(cell_can_be_locked) + { + CFG_Node *cfg = row_info->group_cfg_child; + cell_is_locked = (cfg_node_child_from_string(cfg, s("locked")) != &cfg_nil_node); + } + // rjf: form cell build parameters UI_Key line_edit_key = {0}; RD_CellParams cell_params = {0}; + B32 next_cell_is_locked = cell_is_locked; ProfScope("form cell build parameters") { E_Type *block_type = e_type_from_key(row->block->eval.irtree.type_key); @@ -4048,6 +4168,7 @@ rd_view_ui(Rng2F32 rect) cell_params.edit_string_size_out = &cell_edit_state->input_size; cell_params.line_edit_key_out = &line_edit_key; cell_params.expanded_out = &next_row_expanded; + cell_params.lock_out = &next_cell_is_locked; cell_params.search_needle = needle; cell_params.meta_fstrs = cell_info.expr_fstrs; cell_params.value_fstrs = cell_info.eval_fstrs; @@ -4117,7 +4238,7 @@ rd_view_ui(Rng2F32 rect) cells_are_editable && row->eval.expr == &e_expr_nil) { - ghost_text = str8_lit("Expression"); + ghost_text = s("Add an expression to watch..."); is_non_code = (!cell_selected || !ewv->text_editing); cell_params.flags &= ~(RD_CellFlag_Expander|RD_CellFlag_ExpanderSpace|RD_CellFlag_ExpanderPlaceholder); } @@ -4151,6 +4272,12 @@ rd_view_ui(Rng2F32 rect) cell_params.flags &= ~RD_CellFlag_NoBackground; } + // rjf: watch expression values -> lock + if(cell_can_be_locked) + { + cell_params.flags |= RD_CellFlag_Lock; + } + // rjf: apply toggle-switch if(is_toggle_switch) { @@ -4235,11 +4362,38 @@ rd_view_ui(Rng2F32 rect) } if(ui_is_focus_active() && selection_tbl.min.x == selection_tbl.max.x && selection_tbl.min.y == selection_tbl.max.y && - txt_pt_match(cell_edit_state->cursor, cell_edit_state->mark)) + cell_edit_state->cursor == cell_edit_state->mark) { String8 input = str8(cell_edit_state->input_buffer, cell_edit_state->input_size); rd_set_autocomp_regs(cell->eval, .ui_key = line_edit_key, .string = input, .cursor = cell_edit_state->cursor); } + + // rjf: apply locks + { + CFG_Node *cfg = row_info->group_cfg_child; + if(next_cell_is_locked != cell_is_locked && cfg != &cfg_nil_node) + { + if(next_cell_is_locked) + { + E_Space space = row->eval.space; + D_Entity *entity = rd_ctrl_entity_from_eval_space(space); + CFG_Node *locked = cfg_node_child_from_string_or_alloc(rd_state->cfg, cfg, s("locked")); + cfg_node_release_all_children(rd_state->cfg, locked); + CFG_Node *mid = cfg_node_new(rd_state->cfg, locked, s("mid")); + cfg_node_newf(rd_state->cfg, mid, "%I64u", entity->handle.machine_id); + CFG_Node *pid = cfg_node_new(rd_state->cfg, locked, s("pid")); + cfg_node_newf(rd_state->cfg, pid, "%I64u", entity->id); + CFG_Node *addr = cfg_node_new(rd_state->cfg, locked, s("addr")); + cfg_node_newf(rd_state->cfg, addr, "0x%I64x", row->eval.value.u64); + CFG_Node *type = cfg_node_new(rd_state->cfg, locked, s("type")); + cfg_node_new(rd_state->cfg, type, e_type_string_from_key(scratch.arena, row->eval.irtree.type_key)); + } + else + { + cfg_node_release(rd_state->cfg, cfg_node_child_from_string(cfg, s("locked"))); + } + } + } } } @@ -4548,7 +4702,11 @@ rd_view_ui(Rng2F32 rect) { String8 file_path = lines.first->v.file_path; TxtPt pt = lines.first->v.pt; - rd_cmd(RD_CmdKind_FindCodeLocation, .file_path = file_path, .cursor = pt, .vaddr = vaddr, + rd_cmd(RD_CmdKind_FindCodeLocation, + .file_path = file_path, + .line_num = (U64)pt.line, + .column_num = (U64)pt.column, + .vaddr = vaddr, .process = process->handle, .module = module->handle, .dbgi_key = dbgi_key); @@ -4562,7 +4720,11 @@ rd_view_ui(Rng2F32 rect) RD_Location loc = rd_location_from_cfg(cfg); if(loc.file_path.size != 0) { - rd_cmd(RD_CmdKind_FindCodeLocation, .vaddr = 0, .file_path = loc.file_path, .cursor = loc.pt); + rd_cmd(RD_CmdKind_FindCodeLocation, + .vaddr = 0, + .file_path = loc.file_path, + .line_num = (U64)loc.pt.line, + .column_num = (U64)loc.pt.column); } else if(loc.expr.size != 0) { @@ -4594,7 +4756,7 @@ rd_view_ui(Rng2F32 rect) // rjf: is file eval? -> switch to file else if(cell_info.file_path.size != 0) { - rd_cmd(RD_CmdKind_FindCodeLocation, .cfg = 0, .file_path = cell_info.file_path, .cursor = txt_pt(0, 0)); + rd_cmd(RD_CmdKind_FindCodeLocation, .cfg = 0, .file_path = cell_info.file_path, .line_num = 0, .column_num = 0); } } @@ -5943,8 +6105,10 @@ rd_window_frame(void) #undef Handle ui_labelf("file_path: \"%S\"", regs->file_path); ui_labelf("expr: \"%S\"", regs->expr); - ui_labelf("cursor: (L:%I64d, C:%I64d)", regs->cursor.line, regs->cursor.column); - ui_labelf("mark: (L:%I64d, C:%I64d)", regs->mark.line, regs->mark.column); + ui_labelf("cursor: %I64u", regs->cursor); + ui_labelf("mark: %I64u", regs->mark); + ui_labelf("line_num: %I64u", regs->line_num); + ui_labelf("column_num: %I64u", regs->column_num); ui_labelf("unwind_count: %I64u", regs->unwind_count); ui_labelf("inline_depth: %I64u", regs->inline_depth); ui_labelf("text_key: [0x%I64x / 0x%I64x:0x%I64x]", regs->text_key.root.u64[0], regs->text_key.id.u128[0].u64[0], regs->text_key.id.u128[0].u64[1]); @@ -6086,12 +6250,14 @@ rd_window_frame(void) { Vec2F32 window_dim = dim_2f32(window_rect); UI_Box *bg_box = &ui_nil_box; + Vec4F32 shadow_color = ui_color_from_name(str8_lit("drop_shadow")); + shadow_color.w += (1.f - shadow_color.w) * 0.5f; UI_Rect(window_rect) UI_ChildLayoutAxis(Axis2_X) UI_Focus(UI_FocusKind_On) - UI_BlurSize(10*rd_state->popup_t) UI_Transparency(1-rd_state->popup_t) UI_TagF("floating") + UI_BackgroundColor(shadow_color) { bg_box = ui_build_box_from_stringf(UI_BoxFlag_FixedSize| UI_BoxFlag_Floating| @@ -6099,31 +6265,40 @@ rd_window_frame(void) UI_BoxFlag_Scroll| UI_BoxFlag_DefaultFocusNav| UI_BoxFlag_DisableFocusOverlay| - UI_BoxFlag_DrawBackgroundBlur| + UI_BoxFlag_DisableFocusBorder| UI_BoxFlag_DrawBackground, "###popup_%p", ws); } if(rd_state->popup_active) UI_Parent(bg_box) UI_Transparency(1-rd_state->popup_t) { ui_ctx_menu_close(); - UI_WidthFill UI_PrefHeight(ui_children_sum(1.f)) UI_Column UI_Padding(ui_pct(1, 0)) + UI_WidthFill UI_PrefHeight(ui_children_sum(1.f)) UI_Column UI_Padding(ui_pct(1, 0)) UI_TagF("floating") { - UI_TextRasterFlags(rd_raster_flags_from_slot(RD_FontSlot_Main)) UI_FontSize(ui_top_font_size()*2.f) UI_PrefHeight(ui_em(3.f, 1.f)) ui_label(rd_state->popup_title); - UI_PrefHeight(ui_em(3.f, 1.f)) UI_TagF("weak") ui_label(rd_state->popup_desc); - ui_spacer(ui_em(1.5f, 1.f)); - UI_Row UI_Padding(ui_pct(1.f, 0.f)) UI_PrefWidth(ui_em(16.f, 1.f)) UI_PrefHeight(ui_em(3.5f, 1.f)) UI_CornerRadius(ui_top_font_size()*0.5f) + ui_set_next_blur_size(10*rd_state->popup_t); + ui_set_next_pref_width(ui_children_sum(1)); + ui_set_next_pref_height(ui_children_sum(1)); + ui_set_next_child_layout_axis(Axis2_Y); + UI_Box *panel = ui_build_box_from_stringf(UI_BoxFlag_DrawBackground|UI_BoxFlag_DrawBackgroundBlur|UI_BoxFlag_DrawBorder|UI_BoxFlag_DrawDropShadow, ""); + UI_Parent(panel) { - UI_TagF("pop") - if(ui_clicked(ui_buttonf("OK")) || (ui_key_match(bg_box->default_nav_focus_hot_key, ui_key_zero()) && ui_slot_press(UI_EventActionSlot_Accept))) + ui_spacer(ui_em(1.5f, 1.f)); + UI_TextRasterFlags(rd_raster_flags_from_slot(RD_FontSlot_Main)) UI_FontSize(ui_top_font_size()*2.f) UI_PrefHeight(ui_em(3.f, 1.f)) ui_label(rd_state->popup_title); + UI_PrefHeight(ui_em(3.f, 1.f)) UI_TagF("weak") ui_label(rd_state->popup_desc); + ui_spacer(ui_em(1.5f, 1.f)); + UI_Row UI_Padding(ui_pct(1.f, 0.f)) UI_PrefWidth(ui_em(16.f, 1.f)) UI_PrefHeight(ui_em(3.5f, 1.f)) UI_CornerRadius(ui_top_font_size()*0.5f) { - rd_cmd(RD_CmdKind_PopupAccept); - } - ui_spacer(ui_em(1.f, 1.f)); - if(ui_clicked(ui_buttonf("Cancel")) || ui_slot_press(UI_EventActionSlot_Cancel)) - { - rd_cmd(RD_CmdKind_PopupCancel); + UI_TagF("pop") + if(ui_clicked(ui_buttonf("OK")) || (ui_key_match(bg_box->default_nav_focus_hot_key, ui_key_zero()) && ui_slot_press(UI_EventActionSlot_Accept))) + { + rd_cmd(RD_CmdKind_PopupAccept); + } + ui_spacer(ui_em(1.f, 1.f)); + if(ui_clicked(ui_buttonf("Cancel")) || ui_slot_press(UI_EventActionSlot_Cancel)) + { + rd_cmd(RD_CmdKind_PopupCancel); + } } + ui_spacer(ui_em(3.f, 1.f)); } - ui_spacer(ui_em(3.f, 1.f)); } } ui_signal_from_box(bg_box); @@ -6579,7 +6754,7 @@ rd_window_frame(void) } if(!ui_key_match(ui_key_zero(), ws->query_regs->ui_key)) { - query_width_px = is_small ? (ui_top_font_size()*30.f) : (ui_top_font_size()*60.f); + query_width_px = is_small ? (ui_top_font_size()*40.f) : (ui_top_font_size()*60.f); max_query_height_px = is_small ? (ui_top_font_size()*40.f) : (ui_top_font_size()*80.f); } F32 query_height_px = max_query_height_px; @@ -7197,7 +7372,7 @@ rd_window_frame(void) UI_Row UI_Padding(ui_pct(1, 0)) UI_TextAlignment(UI_TextAlign_Center) UI_PrefWidth(ui_text_dim(ui_top_font_size()*2.f, 1)) UI_CornerRadius(ui_top_font_size()*0.5f) { - String8 url = str8_lit("https://github.com/EpicGamesExt/raddebugger/issues"); + String8 url = str8_lit("https://github.com/EpicGames/raddebugger/issues"); UI_Signal sig = ui_button(str8_lit("Submit request, issue, or bug report")); if(ui_clicked(sig)) { @@ -7655,7 +7830,7 @@ rd_window_frame(void) ui_box_equip_display_fstrs(label, &fstrs); if(ui_clicked(sig)) { - wm_open_in_browser(s("https://github.com/EpicGamesExt/raddebugger/releases/latest")); + wm_open_in_browser(s("https://github.com/EpicGames/raddebugger/releases/latest")); } } @@ -8004,6 +8179,7 @@ rd_window_frame(void) { if(panel->first != &cfg_nil_panel_node) {continue;} B32 panel_is_focused = (window_is_focused && + !rd_state->popup_active && !ws->menu_bar_focused && !query_is_open && !ui_any_ctx_menu_is_open() && @@ -9597,7 +9773,7 @@ rd_set_autocomp_regs_(E_Eval dst_eval, RD_Regs *regs) U64 cursor_arg_idx = 0; if(expr_based_replace) { - U64 cursor_off = (U64)(regs->cursor.column-1); + U64 cursor_off = regs->cursor; E_Parse parse = e_parse_from_string(regs->string); //- rjf: cursor offset -> cursor containing node @@ -9799,12 +9975,14 @@ rd_code_color_slot_from_txt_token_kind(TXT_TokenKind kind) switch(kind) { default:break; - case TXT_TokenKind_Keyword:{color = RD_CodeColorSlot_CodeKeyword;}break; - case TXT_TokenKind_Numeric:{color = RD_CodeColorSlot_CodeNumeric;}break; - case TXT_TokenKind_String: {color = RD_CodeColorSlot_CodeString;}break; - case TXT_TokenKind_Meta: {color = RD_CodeColorSlot_CodeMeta;}break; - case TXT_TokenKind_Comment:{color = RD_CodeColorSlot_CodeComment;}break; - case TXT_TokenKind_Symbol: {color = RD_CodeColorSlot_CodeDelimiterOperator;}break; + case TXT_TokenKind_Keyword: {color = RD_CodeColorSlot_CodeKeyword;}break; + case TXT_TokenKind_Numeric: {color = RD_CodeColorSlot_CodeNumeric;}break; + case TXT_TokenKind_String: {color = RD_CodeColorSlot_CodeString;}break; + case TXT_TokenKind_Char: {color = RD_CodeColorSlot_CodeString;}break; + case TXT_TokenKind_Meta: {color = RD_CodeColorSlot_CodeMeta;}break; + case TXT_TokenKind_LineComment: {color = RD_CodeColorSlot_CodeComment;}break; + case TXT_TokenKind_BlockComment:{color = RD_CodeColorSlot_CodeComment;}break; + case TXT_TokenKind_Symbol: {color = RD_CodeColorSlot_CodeDelimiterOperator;}break; } return color; } @@ -10088,7 +10266,7 @@ rd_stop_explanation_fstrs_from_ctrl_event(Arena *arena, D_Event *event) //~ rjf: Source File Checksum Calculations internal AC_Artifact -rd_md5_artifact_create(String8 key, B32 *cancel_out, B32 *retry_out, U64 *gen_out) +rd_md5_artifact_create(String8 key, B32 *cancel_out, AC_Status *status_out, U64 *gen_out) { AC_Artifact result = {0}; { @@ -10105,7 +10283,7 @@ rd_md5_artifact_create(String8 key, B32 *cancel_out, B32 *retry_out, U64 *gen_ou } internal AC_Artifact -rd_sha1_artifact_create(String8 key, B32 *cancel_out, B32 *retry_out, U64 *gen_out) +rd_sha1_artifact_create(String8 key, B32 *cancel_out, AC_Status *status_out, U64 *gen_out) { AC_Artifact result = {0}; { @@ -10122,7 +10300,7 @@ rd_sha1_artifact_create(String8 key, B32 *cancel_out, B32 *retry_out, U64 *gen_o } internal AC_Artifact -rd_sha256_artifact_create(String8 key, B32 *cancel_out, B32 *retry_out, U64 *gen_out) +rd_sha256_artifact_create(String8 key, B32 *cancel_out, AC_Status *status_out, U64 *gen_out) { AC_Artifact result = {0}; { @@ -10478,20 +10656,21 @@ rd_regs_fill_slot_from_string(RD_RegSlot slot, String8 query_expr, String8 strin case RD_RegSlot_FilePath: { String8TxtPtPair pair = str8_txt_pt_pair_from_string(string); - rd_regs()->string = push_str8_copy(rd_frame_arena(), string); + rd_regs()->string = str8_copy(rd_frame_arena(), string); if(pair.pt.line != 0) { - rd_regs()->file_path = push_str8_copy(rd_frame_arena(), pair.string); - rd_regs()->cursor = pair.pt; + rd_regs()->file_path = str8_copy(rd_frame_arena(), pair.string); + rd_regs()->line_num = (U64)pair.pt.line; + rd_regs()->column_num = (U64)pair.pt.column; } }break; case RD_RegSlot_Expr: { - rd_regs()->expr = push_str8_copy(rd_frame_arena(), string); + rd_regs()->expr = str8_copy(rd_frame_arena(), string); }break; case RD_RegSlot_CmdName: { - rd_regs()->cmd_name = push_str8_copy(rd_frame_arena(), string); + rd_regs()->cmd_name = str8_copy(rd_frame_arena(), string); }break; //- rjf: ctrl entities @@ -10563,13 +10742,12 @@ rd_regs_fill_slot_from_string(RD_RegSlot slot, String8 query_expr, String8 strin }break; //- rjf: line numbers - case RD_RegSlot_Cursor: + case RD_RegSlot_LineNum: { E_Eval eval = e_value_eval_from_eval(e_eval_from_string(string)); if(eval.msgs.max_kind == E_MsgKind_Null) { - rd_regs()->cursor.column = 1; - rd_regs()->cursor.line = (S64)eval.value.u64; + rd_regs()->line_num = eval.value.u64; } else { @@ -11168,7 +11346,9 @@ rd_frame(void) rd_state->hover_regs_slot = RD_RegSlot_Null; } B32 allow_text_hotkeys = !rd_state->text_edit_mode; + B32 allow_text_multiline_hotkeys = !rd_state->text_edit_mode_multiline; rd_state->text_edit_mode = 0; + rd_state->text_edit_mode_multiline = 0; if(rd_state->frame_depth == 1) { arena_clear(rd_state->cmd_output_arena); @@ -11688,7 +11868,7 @@ rd_frame(void) if(key_map_nodes.first != 0) { U32 hit_char = wm_codepoint_from_modifiers_and_key(event->modifiers, event->key); - if(hit_char == 0 || allow_text_hotkeys) + if((allow_text_hotkeys || hit_char == 0 || (hit_char == '\n' && allow_text_multiline_hotkeys))) { String8 cmd_name = key_map_nodes.first->v->name; for(U64 idx = 0; idx < ArrayCount(rd_binding_version_remap_old_name_table); idx += 1) @@ -11699,11 +11879,8 @@ rd_frame(void) } } rd_cmd(RD_CmdKind_RunCommand, .cmd_name = cmd_name); - if(allow_text_hotkeys) - { - wm_text(&events, event->window, hit_char); - next = event->next; - } + wm_text(&events, event->window, hit_char); + next = event->next; take = 1; if(event->modifiers & WM_Modifier_Alt) { @@ -11719,7 +11896,7 @@ rd_frame(void) } //- rjf: try text events - if(!take && event->kind == WM_EventKind_Text) + if(!take && event->kind == WM_EventKind_Text && (event->character != '\n' || !allow_text_multiline_hotkeys)) { String32 insertion32 = str32(&event->character, 1); String8 insertion8 = str8_from_32(scratch.arena, insertion32); @@ -12183,10 +12360,10 @@ rd_frame(void) { continue; } - if(str8_match(child->string, str8_lit("watch"), 0)) + if(str8_match(child->string, str8_lit("watch_expression"), 0)) { CFG_Node *watch = child; - String8 expr = watch->first->string; + String8 expr = cfg_node_child_from_string(watch, s("expression"))->first->string; E_Parse parse = e_parse_from_string(expr); if(parse.msgs.max_kind == E_MsgKind_Null) { @@ -12288,12 +12465,17 @@ rd_frame(void) } if(kind == D_EntityKind_Process && d_handle_match(rd_base_regs()->process, entity->handle)) { - e_string2expr_map_insert(scratch.arena, macro_map, str8_lit("current_process"), expr); + e_string2expr_map_insert(scratch.arena, macro_map, s("current_process"), expr); } if(kind == D_EntityKind_Module && d_handle_match(rd_base_regs()->module, entity->handle)) { e_string2expr_map_insert(scratch.arena, macro_map, str8_lit("current_module"), expr); } + if(kind == D_EntityKind_Process) + { + String8 pid_string = str8f(scratch.arena, "process_%I64u", entity->id); + e_string2expr_map_insert(scratch.arena, macro_map, pid_string, expr); + } } } @@ -12379,10 +12561,10 @@ rd_frame(void) .id_from_num = E_TYPE_EXPAND_ID_FROM_NUM_FUNCTION_NAME(environment), .num_from_id = E_TYPE_EXPAND_NUM_FROM_ID_FUNCTION_NAME(environment), })); - e_string2typekey_map_insert(rd_frame_arena(), rd_state->meta_name2type_map, str8_lit("watches"), + e_string2typekey_map_insert(rd_frame_arena(), rd_state->meta_name2type_map, str8_lit("watch_expressions"), e_type_key_cons(.kind = E_TypeKind_Set, .flags = E_TypeFlag_EditableChildren|E_TypeFlag_StubSingleLineExpansion, - .name = str8_lit("watches"), + .name = str8_lit("watch_expressions"), .irext = E_TYPE_IREXT_FUNCTION_NAME(watches), .access = E_TYPE_ACCESS_FUNCTION_NAME(watches), .expand = @@ -12841,7 +13023,7 @@ rd_frame(void) { .id = 1, .method = HTTP_Method_Get, - .url = s("https://api.github.com/repos/EpicGamesExt/raddebugger/releases/latest"), + .url = s("https://api.github.com/repos/EpicGames/raddebugger/releases/latest"), .user_agent = s("raddebugger"), }; http_push_request(rd_state->update_check_http_ring, &p, 0); @@ -13028,7 +13210,8 @@ rd_frame(void) params.entity = rd_regs()->ctrl_entity; params.string = rd_regs()->string; params.file_path = rd_regs()->file_path; - params.cursor = rd_regs()->cursor; + params.line_num = rd_regs()->line_num; + params.column_num = rd_regs()->column_num; params.vaddr = rd_regs()->vaddr; params.prefer_disasm = rd_regs()->prefer_disasm; params.pid = rd_regs()->pid; @@ -13106,7 +13289,12 @@ rd_frame(void) { current_path_string = path_normalized_from_string(scratch.arena, get_current_path(scratch.arena)); } - String8 file_path = wm_graphical_pick_file(scratch.arena, current_path_string); + String8 cmd_title = rd_display_from_code_name(info->string); + if(cmd_title.size == 0) + { + cmd_title = info->string; + } + String8 file_path = wm_graphical_pick_file(scratch.arena, cmd_title, current_path_string); file_path = path_normalized_from_string(scratch.arena, file_path); if(file_path.size != 0) { @@ -14650,7 +14838,7 @@ rd_frame(void) case RD_CmdKind_OpenSourceFileFromDebugInfo: { String8 path = rd_regs()->file_path; - rd_cmd(RD_CmdKind_FindCodeLocation, .file_path = path, .cursor = txt_pt(0, 0), .vaddr = 0, .force_focus = 1, .prefer_new_tab = 1); + rd_cmd(RD_CmdKind_FindCodeLocation, .file_path = path, .line_num = 0, .vaddr = 0, .force_focus = 1, .prefer_new_tab = 1); }break; case RD_CmdKind_SwitchToPartnerFile: { @@ -14677,7 +14865,7 @@ rd_frame(void) FileProperties candidate_props = properties_from_file_path(candidate_path); if(candidate_props.modified != 0) { - rd_cmd(RD_CmdKind_FindCodeLocation, .file_path = candidate_path, .cursor = txt_pt(0, 0), .vaddr = 0, .prefer_new_tab = 1); + rd_cmd(RD_CmdKind_FindCodeLocation, .file_path = candidate_path, .line_num = 0, .vaddr = 0, .prefer_new_tab = 1); break; } } @@ -14713,7 +14901,8 @@ rd_frame(void) { rd_cmd(RD_CmdKind_FindCodeLocation, .file_path = rd_regs()->lines.first->v.file_path, - .cursor = rd_regs()->lines.first->v.pt, + .line_num = (U64)rd_regs()->lines.first->v.pt.line, + .column_num= (U64)rd_regs()->lines.first->v.pt.column, .vaddr = 0, .process = d_handle_zero(), .prefer_disasm = 0); @@ -15015,7 +15204,8 @@ rd_frame(void) { rd_cmd(RD_CmdKind_FindCodeLocation, .file_path = line.file_path, - .cursor = line.pt, + .line_num = (U64)line.pt.line, + .column_num = (U64)line.pt.column, .process = process->handle, .voff = rip_voff, .vaddr = rip_vaddr, @@ -15163,7 +15353,8 @@ rd_frame(void) } rd_cmd(RD_CmdKind_FindCodeLocation, .file_path = lines.first->v.file_path, - .cursor = lines.first->v.pt, + .line_num = (U64)lines.first->v.pt.line, + .column_num= (U64)lines.first->v.pt.column, .process = process->handle, .module = module->handle, .vaddr = module->vaddr_range.min + lines.first->v.voff_range.min); @@ -15173,7 +15364,7 @@ rd_frame(void) // rjf: name resolved to a file path if(name_resolved && file_path.size != 0) { - rd_cmd(RD_CmdKind_FindCodeLocation, .file_path = file_path, .cursor = txt_pt(1, 1), .vaddr = 0); + rd_cmd(RD_CmdKind_FindCodeLocation, .file_path = file_path, .line_num = 0, .vaddr = 0); } } }break; @@ -15225,7 +15416,8 @@ rd_frame(void) //- rjf: grab things to find. path * point, process * address, etc. String8 file_path = {0}; - TxtPt point = {0}; + U64 line_num = 0; + U64 column_num = 0; D_Entity *thread = &d_entity_nil; D_Entity *process = &d_entity_nil; U64 vaddr = 0; @@ -15233,7 +15425,8 @@ rd_frame(void) B32 prefer_new_tab = 0; { file_path = rd_mapped_from_file_path(scratch.arena, rd_regs()->file_path); - point = rd_regs()->cursor; + line_num = rd_regs()->line_num; + column_num = rd_regs()->column_num; thread = d_entity_from_handle(rd_regs()->thread); process = d_entity_from_handle(rd_regs()->process); vaddr = rd_regs()->vaddr; @@ -15262,7 +15455,7 @@ rd_frame(void) // try to map the src coordinates to a vaddr via line info if(vaddr == 0 && file_path.size != 0) { - D_LineList lines = d_lines_from_file_path_line_num(scratch.arena, file_path, point.line, max_U64); + D_LineList lines = d_lines_from_file_path_line_num(scratch.arena, file_path, (S64)line_num, max_U64); for(D_LineNode *n = lines.first; n != 0; n = n->next) { D_EntityList modules = d_modules_from_dbgi_key(scratch.arena, n->v.dbgi_key); @@ -15769,9 +15962,9 @@ rd_frame(void) rd_cmd(RD_CmdKind_FocusPanel); } rd_cmd(RD_CmdKind_FocusTab); - if(point.line != 0) + if(line_num != 0) { - rd_cmd(RD_CmdKind_GoToLine, .cursor = point); + rd_cmd(RD_CmdKind_GoToLine, .line_num = line_num); } rd_cmd(cursor_snap_kind); } @@ -16037,12 +16230,12 @@ rd_frame(void) { if(!vs->query_is_open && cmd_kind_info->query.flags & RD_QueryFlag_SelectOldInput) { - vs->query_cursor = txt_pt(1, 1+input->first->string.size); - vs->query_mark = txt_pt(1, 1); + vs->query_cursor = input->first->string.size; + vs->query_mark = 0; } else { - vs->query_cursor = txt_pt(1, 1+input->first->string.size); + vs->query_cursor = input->first->string.size; vs->query_mark = vs->query_cursor; } if(!str8_match(current_query_cmd_name, cmd_name, 0)) @@ -16114,8 +16307,8 @@ rd_frame(void) CFG_Node *input = cfg_node_child_from_string_or_alloc(rd_state->cfg, query, str8_lit("input")); cfg_node_new_replace(rd_state->cfg, input, rd_regs()->string); RD_ViewState *vs = rd_view_state_from_cfg(view); - vs->query_cursor = vs->query_mark = txt_pt(1, rd_regs()->string.size+1); vs->query_string_size = Min(sizeof(vs->query_buffer), rd_regs()->string.size); + vs->query_cursor = vs->query_mark = vs->query_string_size; MemoryCopy(vs->query_buffer, rd_regs()->string.str, vs->query_string_size); }break; @@ -16222,17 +16415,18 @@ rd_frame(void) // rjf: attach new location info { String8 file_path = rd_regs()->file_path; - TxtPt pt = rd_regs()->cursor; + U64 line_num = rd_regs()->line_num; + U64 column_num = rd_regs()->column_num; String8 expr_string = rd_regs()->expr; U64 vaddr = rd_regs()->vaddr; if(expr_string.size == 0 && vaddr != 0) { expr_string = push_str8f(scratch.arena, "0x%I64x", vaddr); } - if(file_path.size != 0 && pt.line != 0) + if(file_path.size != 0 && line_num != 0) { CFG_Node *src_loc = cfg_node_new(rd_state->cfg, cfg, str8_lit("source_location")); - cfg_node_newf(rd_state->cfg, src_loc, "%S:%I64d:%I64d", file_path, pt.line, pt.column); + cfg_node_newf(rd_state->cfg, src_loc, "%S:%I64u:%I64u", file_path, line_num, column_num); } else if(expr_string.size != 0) { @@ -16254,12 +16448,13 @@ rd_frame(void) case RD_CmdKind_ToggleBreakpoint: { String8 file_path = rd_regs()->file_path; - TxtPt pt = rd_regs()->cursor; + U64 line_num = rd_regs()->line_num; + U64 column_num = rd_regs()->column_num; U64 vaddr = rd_regs()->vaddr; String8 expr = rd_regs()->expr; if(expr.size == 0 && vaddr != 0) { - expr = push_str8f(scratch.arena, "0x%I64x", vaddr); + expr = str8f(scratch.arena, "0x%I64x", vaddr); } if(file_path.size != 0 || expr.size != 0) { @@ -16271,7 +16466,7 @@ rd_frame(void) CFG_Node *bp = n->v; CFG_Node *cnd = cfg_node_child_from_string(bp, str8_lit("condition")); RD_Location loc = rd_location_from_cfg(bp); - B32 loc_matches_file_pt = (file_path.size != 0 && path_match_normalized(loc.file_path, file_path) && loc.pt.line == pt.line); + B32 loc_matches_file_pt = (file_path.size != 0 && path_match_normalized(loc.file_path, file_path) && loc.pt.line == line_num); B32 loc_matches_expr = (expr.size != 0 && str8_match(expr, loc.expr, 0)); if((loc_matches_file_pt || loc_matches_expr) && cnd->first->string.size == 0) { @@ -16336,7 +16531,8 @@ rd_frame(void) case RD_CmdKind_ToggleWatchPin: { String8 file_path = rd_regs()->file_path; - TxtPt pt = rd_regs()->cursor; + U64 line_num = rd_regs()->line_num; + U64 column_num = rd_regs()->column_num; String8 expr_string = rd_regs()->expr; U64 vaddr = rd_regs()->vaddr; B32 removed_already_existing = 0; @@ -16348,7 +16544,7 @@ rd_frame(void) CFG_Node *wp = n->v; CFG_Node *expr = cfg_node_child_from_string(wp, str8_lit("expression")); RD_Location loc = rd_location_from_cfg(wp); - B32 loc_matches_file_pt = (file_path.size != 0 && path_match_normalized(loc.file_path, file_path) && loc.pt.line == pt.line); + B32 loc_matches_file_pt = (file_path.size != 0 && path_match_normalized(loc.file_path, file_path) && loc.pt.line == (S64)line_num); B32 loc_matches_expr = (expr_string.size != 0 && str8_match(expr_string, loc.expr, 0)); if((loc_matches_file_pt || loc_matches_expr) && str8_match(expr->first->string, expr_string, 0)) { @@ -16545,7 +16741,7 @@ rd_frame(void) { continue; } - if(str8_match(child->string, str8_lit("watch"), 0) && str8_match(child->first->string, rd_regs()->string, 0)) + if(str8_match(child->string, str8_lit("watch_expression"), 0) && str8_match(child->first->string, rd_regs()->string, 0)) { existing_watch = child; break; @@ -16561,8 +16757,9 @@ rd_frame(void) // rjf: otherwise, create it else if(watch_tab != &cfg_nil_node) { - CFG_Node *watch = cfg_node_new(rd_state->cfg, watch_tab, str8_lit("watch")); - cfg_node_new(rd_state->cfg, watch, rd_regs()->string); + CFG_Node *watch = cfg_node_new(rd_state->cfg, watch_tab, s("watch_expression")); + CFG_Node *expr = cfg_node_new(rd_state->cfg, watch, s("expression")); + cfg_node_new(rd_state->cfg, expr, rd_regs()->string); } }break; @@ -16574,20 +16771,11 @@ rd_frame(void) RD_Regs *regs = rd_regs(); C_Key text_key = regs->text_key; TXT_LangKind lang_kind = regs->lang_kind; - TxtRng range = txt_rng(regs->cursor, regs->mark); + Rng1U64 range = r1u64(regs->cursor, regs->mark); U128 hash = {0}; TXT_TextInfo info = txt_text_info_from_key_lang(access, text_key, lang_kind, &hash); String8 data = c_data_from_hash(access, hash); - Rng1U64 expr_off_range = {0}; - if(range.min.column != range.max.column) - { - expr_off_range = r1u64(txt_off_from_info_pt(&info, range.min), txt_off_from_info_pt(&info, range.max)); - } - else - { - expr_off_range = txt_expr_off_range_from_info_data_pt(&info, data, range.min); - } - String8 expr = str8_substr(data, expr_off_range); + String8 expr = str8_substr(data, range); rd_cmd((kind == RD_CmdKind_GoToNameAtCursor ? RD_CmdKind_GoToName : kind == RD_CmdKind_ToggleWatchExpressionAtCursor ? RD_CmdKind_ToggleWatchExpression : RD_CmdKind_GoToName), @@ -16820,6 +17008,33 @@ rd_frame(void) evt.slot = UI_EventActionSlot_FocusMenu; ui_event_list_push(scratch.arena, &ws->ui_events, &evt); }break; + case RD_CmdKind_Lock: + { + CFG_Node *window = cfg_node_from_id(rd_regs()->window); + RD_WindowState *ws = rd_window_state_from_cfg(window); + UI_Event evt = zero_struct; + evt.kind = UI_EventKind_Press; + evt.slot = UI_EventActionSlot_Lock; + ui_event_list_push(scratch.arena, &ws->ui_events, &evt); + }break; + case RD_CmdKind_Unlock: + { + CFG_Node *window = cfg_node_from_id(rd_regs()->window); + RD_WindowState *ws = rd_window_state_from_cfg(window); + UI_Event evt = zero_struct; + evt.kind = UI_EventKind_Press; + evt.slot = UI_EventActionSlot_Unlock; + ui_event_list_push(scratch.arena, &ws->ui_events, &evt); + }break; + case RD_CmdKind_ToggleLock: + { + CFG_Node *window = cfg_node_from_id(rd_regs()->window); + RD_WindowState *ws = rd_window_state_from_cfg(window); + UI_Event evt = zero_struct; + evt.kind = UI_EventKind_Press; + evt.slot = UI_EventActionSlot_ToggleLock; + ui_event_list_push(scratch.arena, &ws->ui_events, &evt); + }break; //- rjf: directional movement & text controls // diff --git a/src/raddbg/raddbg_core.h b/src/raddbg/raddbg_core.h index b7994c02..5d314d7e 100644 --- a/src/raddbg/raddbg_core.h +++ b/src/raddbg/raddbg_core.h @@ -152,8 +152,8 @@ struct RD_ViewState // rjf: query state B32 query_is_open; - TxtPt query_cursor; - TxtPt query_mark; + U64 query_cursor; + U64 query_mark; U8 query_buffer[KB(1)]; U64 query_string_size; @@ -496,6 +496,7 @@ struct RD_State // rjf: text editing mode state B32 text_edit_mode; + B32 text_edit_mode_multiline; // rjf: contextual hover info RD_Regs *hover_regs; @@ -794,9 +795,9 @@ internal DR_FStrList rd_stop_explanation_fstrs_from_ctrl_event(Arena *arena, D_E //////////////////////////////// //~ rjf: Source File Checksum Calculations -internal AC_Artifact rd_md5_artifact_create(String8 key, B32 *cancel_out, B32 *retry_out, U64 *gen_out); -internal AC_Artifact rd_sha1_artifact_create(String8 key, B32 *cancel_out, B32 *retry_out, U64 *gen_out); -internal AC_Artifact rd_sha256_artifact_create(String8 key, B32 *cancel_out, B32 *retry_out, U64 *gen_out); +internal AC_Artifact rd_md5_artifact_create(String8 key, B32 *cancel_out, AC_Status *status_out, U64 *gen_out); +internal AC_Artifact rd_sha1_artifact_create(String8 key, B32 *cancel_out, AC_Status *status_out, U64 *gen_out); +internal AC_Artifact rd_sha256_artifact_create(String8 key, B32 *cancel_out, AC_Status *status_out, U64 *gen_out); internal MD5 rd_md5_from_hash(U128 hash); internal SHA1 rd_sha1_from_hash(U128 hash); internal SHA256 rd_sha256_from_hash(U128 hash); diff --git a/src/raddbg/raddbg_eval.c b/src/raddbg/raddbg_eval.c index f5fc95ca..ab9f89e2 100644 --- a/src/raddbg/raddbg_eval.c +++ b/src/raddbg/raddbg_eval.c @@ -1389,7 +1389,7 @@ E_TYPE_IREXT_FUNCTION_DEF(watches) for(CFG_Node *child = target->first; child != &cfg_nil_node; child = child->next) { if(rd_cfg_is_project_filtered(child)) {continue;} - if(str8_match(child->string, str8_lit("watch"), 0)) + if(str8_match(child->string, str8_lit("watch_expression"), 0)) { cfg_node_ptr_list_push(scratch.arena, &cfgs, child); } @@ -1461,7 +1461,25 @@ E_TYPE_EXPAND_RANGE_FUNCTION_DEF(watches) if(cfg_idx < accel->cfgs.count) { CFG_Node *cfg = accel->cfgs.v[cfg_idx]; - evals_out[idx] = e_eval_from_string(cfg->first->string); + CFG_Node *expr = cfg_node_child_from_string(cfg, s("expression")); + CFG_Node *locked = cfg_node_child_from_string(cfg, s("locked")); + if(locked->first->string.size != 0) + { + String8 pid = cfg_node_child_from_string(locked, s("pid"))->first->string; + String8 type = cfg_node_child_from_string(locked, s("type"))->first->string; + String8 addr = cfg_node_child_from_string(locked, s("addr"))->first->string; + String8 lock_expr = str8f(arena, "*(cast(%S *)(process_%S.memory + %S))", type, pid, addr); + evals_out[idx] = e_eval_from_string(lock_expr); + evals_out[idx].string = expr->first->string; + if(evals_out[idx].msgs.count != 0) + { + evals_out[idx] = e_eval_from_string(expr->first->string); + } + } + else + { + evals_out[idx] = e_eval_from_string(expr->first->string); + } } } } diff --git a/src/raddbg/raddbg_main.c b/src/raddbg/raddbg_main.c index 35313923..efb1e756 100644 --- a/src/raddbg/raddbg_main.c +++ b/src/raddbg/raddbg_main.c @@ -68,6 +68,15 @@ //////////////////////////////// //~ rjf: post-0.9.27 TODO notes // +// [ ] beginning usage UI notes (cakezzz) +// [ ] rebinding a hotkey should be much more visually obvious, & add tooltips to '+' button - very ambiguous +// [ ] pass over default layouts - remove tabs that are secondary/tertiary features, like pins/locals/etc. +// [ ] undo/redo for panel layout changes +// [ ] 'are you sure?' step for closing panel +// [ ] show breakpoint trails from any location - very confusing if they are off-screen but resolve to a line +// [ ] we probably want to just cap the breakpoint 'trailing' too? if it's more than like 100 lines away seems dumb... +// [ ] after above, show breakpoint resolution status - if it can't resolve anywhere, suggest as much in the UI (? icon or whatever) +// // [ ] debug info loading retry mechanism, in cases where the load failed, but settings/filesystem state changes - // e.g. turn on automatic downloads, already tried to load symbol server cache file that doesn't exist -> // need to retry diff --git a/src/raddbg/raddbg_views.c b/src/raddbg/raddbg_views.c index f7b7f018..c067bf4c 100644 --- a/src/raddbg/raddbg_views.c +++ b/src/raddbg/raddbg_views.c @@ -12,6 +12,7 @@ rd_code_view_init(RD_CodeViewState *cv) { cv->initialized = 1; cv->preferred_column = 1; + cv->patch_arena = rd_push_view_arena(); cv->find_text_arena = rd_push_view_arena(); cv->center_cursor = 1; rd_store_view_loading_info(1, 0, 0); @@ -47,6 +48,7 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla D_Entity *thread = d_entity_from_handle(rd_regs()->thread); D_Entity *process = d_entity_ancestor_from_kind(thread, D_EntityKind_Process); B32 do_line_numbers = rd_setting_b32_from_name(str8_lit("show_line_numbers")); + B32 text_is_ready = (text_info->lines_count != 0); ////////////////////////////// //- rjf: unpack information about the viewed source file, if any @@ -65,7 +67,7 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla default: break; case RD_CmdKind_GoToLine: { - cv->goto_line_num = cmd->regs->cursor.line; + cv->goto_line_num = cmd->regs->line_num; }break; case RD_CmdKind_CenterCursor: { @@ -119,6 +121,230 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla cv->wrap_cache_slots = push_array(cv->wrap_arena, RD_CodeViewTLineWrapCacheSlot, cv->wrap_cache_slots_count); } + ////////////////////////////// + //- rjf: do keyboard interaction, compute patched text state + // + TXT_Patched text_patched = txt_patched_from_info_data_patches(scratch.arena, text_info, text_data, &cv->patches); + B32 snap[Axis2_COUNT] = {0}; + UI_Focus(UI_FocusKind_On) if(ui_is_focus_active()) + { + CFG_Node *view = cfg_node_from_id(rd_regs()->view); + RD_ViewState *vs = rd_view_state_from_cfg(view); + rd_state->text_edit_mode_multiline = (!vs->query_is_open || vs->contents_are_focused); + rd_state->text_edit_mode = 1; + U64 line_count_per_page = ClampBot(num_possible_visible_lines, 10) - 10; + U64 *cursor = &rd_regs()->cursor; + U64 *mark = &rd_regs()->mark; + S64 *preferred_column = &cv->preferred_column; + for(UI_Event *evt = 0; ui_next_event(&evt);) + { + if(evt->kind != UI_EventKind_Navigate && evt->kind != UI_EventKind_Edit && evt->kind != UI_EventKind_Text) + { + continue; + } + B32 taken = 0; + U64 start_cursor = *cursor; + U64 start_mark = *mark; + Vec2S32 delta = evt->delta_2s32; + U64 line_count = text_patched.line_map.total_line_count; + U64 line_num = txt_line_num_from_off(&text_patched.line_map, *cursor); + Rng1U64 line_range = txt_range_from_line_num(&text_patched.line_map, line_num); + String8 line = {0}; + line.size = dim_1u64(line_range); + line.str = push_array(scratch.arena, U8, line.size); + memory_map_read(&text_patched.memory_map, line_range, line.str); + + //- rjf: interpret event as single-line text op + UI_TxtOp single_line_op = ui_single_line_txt_op_from_event(scratch.arena, evt, line, line_range, *cursor, *mark); + + //- TODO(rjf): skip replace-ranges for now + if(single_line_op.replace.size != 0) + { + continue; + } + + //- rjf: apply single-line navigations + *cursor = single_line_op.cursor; + *mark = single_line_op.mark; + + //- rjf: determine if we need to navigate + B32 need_nav = (start_cursor == start_mark || !(evt->flags & UI_EventFlag_ZeroDeltaOnSelect)); + + //- rjf: wrap lines right + if(need_nav && evt->delta_unit != UI_EventDeltaUnit_Whole && evt->delta_unit != UI_EventDeltaUnit_Line && delta.x > 0 && start_cursor == line_range.max && line_num+1 <= line_count) + { + Rng1U64 next_line_range = txt_range_from_line_num(&text_patched.line_map, line_num+1); + *cursor = next_line_range.min; + *preferred_column = 1; + } + + //- rjf: wrap lines left + if(need_nav && evt->delta_unit != UI_EventDeltaUnit_Whole && evt->delta_unit != UI_EventDeltaUnit_Line && delta.x < 0 && start_cursor == line_range.min && line_num-1 >= 1) + { + Rng1U64 prev_line_range = txt_range_from_line_num(&text_patched.line_map, line_num-1); + *cursor = prev_line_range.max; + *preferred_column = (S64)dim_1u64(prev_line_range)+1; + } + + //- rjf: movement down (plain) + if(need_nav && evt->delta_unit == UI_EventDeltaUnit_Char && delta.y > 0 && line_num+1 <= line_count) + { + Rng1U64 next_line_range = txt_range_from_line_num(&text_patched.line_map, line_num+1); + *cursor = next_line_range.min + *preferred_column; + *cursor = clamp_1u64(next_line_range, *cursor); + } + + //- rjf: movement up (plain) + if(need_nav && evt->delta_unit == UI_EventDeltaUnit_Char && delta.y < 0 && line_num > 1) + { + Rng1U64 prev_line_range = txt_range_from_line_num(&text_patched.line_map, line_num-1); + *cursor = prev_line_range.min + *preferred_column; + *cursor = clamp_1u64(prev_line_range, *cursor); + } + + //- rjf: movement down (chunk) + if(need_nav && evt->delta_unit == UI_EventDeltaUnit_Word && delta.y > 0 && line_num+1 <= line_count) + { + B32 done = 0; + for(U64 scan_line_num = line_num+1; !done && scan_line_num <= line_count; scan_line_num += 1) + { + Temp scratch = scratch_begin(&arena, 1); + Rng1U64 line_range = txt_range_from_line_num(&text_patched.line_map, scan_line_num); + String8 line = memory_map_data_from_range(scratch.arena, &text_patched.memory_map, line_range); + String8 line_without_whitespace = str8_skip_chop_whitespace(line); + if(line_without_whitespace.size == 0) + { + *cursor = line_range.min + (U64)(line_without_whitespace.str - line.str); + done = 1; + } + else if(scan_line_num == line_count) + { + *cursor = text_patched.size; + } + scratch_end(scratch); + } + } + + //- rjf: movement up (chunk) + if(need_nav && evt->delta_unit == UI_EventDeltaUnit_Word && delta.y < 0 && line_num > 1) + { + B32 done = 0; + for(U64 scan_line_num = line_num-1; !done && scan_line_num > 0; scan_line_num -= 1) + { + Temp scratch = scratch_begin(&arena, 1); + Rng1U64 line_range = txt_range_from_line_num(&text_patched.line_map, scan_line_num); + String8 line = memory_map_data_from_range(scratch.arena, &text_patched.memory_map, line_range); + String8 line_without_whitespace = str8_skip_chop_whitespace(line); + if(line_without_whitespace.size == 0) + { + *cursor = line_range.min + (U64)(line_without_whitespace.str - line.str); + done = 1; + } + else if(scan_line_num == 1) + { + *cursor = 0; + } + scratch_end(scratch); + } + } + + //- rjf: movement down (page) + if(need_nav && evt->delta_unit == UI_EventDeltaUnit_Page && delta.y > 0) + { + U64 advance = line_count_per_page; + U64 next_line = line_num + advance; + U64 next_line_clamped = Clamp(1, next_line, text_patched.line_map.total_line_count); + Rng1U64 next_line_range = txt_range_from_line_num(&text_patched.line_map, next_line_clamped); + *cursor = next_line_range.min; + } + + //- rjf: movement up (page) + if(need_nav && evt->delta_unit == UI_EventDeltaUnit_Page && delta.y < 0) + { + S64 advance = -line_count_per_page; + if(line_num < line_count_per_page) + { + advance = -(line_num - 1); + } + U64 next_line = (U64)((S64)line_num + advance); + U64 next_line_clamped = Clamp(1, next_line, text_patched.line_map.total_line_count); + Rng1U64 next_line_range = txt_range_from_line_num(&text_patched.line_map, next_line_clamped); + *cursor = next_line_range.min; + } + + //- rjf: movement to endpoint (+) + if(need_nav && evt->delta_unit == UI_EventDeltaUnit_Whole && (delta.y > 0 || delta.x > 0)) + { + *cursor = text_patched.size; + } + + //- rjf: movement to endpoint (-) + if(need_nav && evt->delta_unit == UI_EventDeltaUnit_Whole && (delta.y < 0 || delta.x < 0)) + { + *cursor = 0; + } + + //- rjf: get replaced-range; adjust based on multi-line logic + Rng1U64 replaced_range = single_line_op.range; + if(*cursor != single_line_op.cursor && (evt->flags & UI_EventFlag_Delete || evt->string.size != 0)) + { + replaced_range = r1u64(*mark, *cursor); + } + + //- rjf: in some cases, we want to pick a selection side based on the delta + if(*cursor != *mark && evt->flags & UI_EventFlag_PickSelectSide) + { + if(delta.x < 0 || delta.y < 0) + { + *cursor = *mark = Min(*cursor, *mark); + } + else if(delta.x > 0 || delta.y > 0) + { + *cursor = *mark = Max(*cursor, *mark); + } + } + + //- rjf: do copy if needed + if(evt->flags & UI_EventFlag_Copy) + { + String8 text = memory_map_data_from_range(scratch.arena, &text_patched.memory_map, r1u64(*cursor, *mark)); + wm_set_clipboard_text(text); + } + + //- rjf: stick mark to cursor, when we don't want to keep it in the same spot + if(!(evt->flags & UI_EventFlag_KeepMark)) + { + *mark = *cursor; + } + + //- rjf: push patch if we have one + if(replaced_range.max != replaced_range.min || single_line_op.replace.size != 0) + { + txt_patch_list_push_new(cv->patch_arena, &cv->patches, replaced_range, single_line_op.replace); + text_patched = txt_patched_from_info_data_patches(scratch.arena, text_info, text_data, &cv->patches); + *cursor = *mark = replaced_range.min + single_line_op.replace.size; + U64 line_num = txt_line_num_from_off(&text_patched.line_map, *cursor); + Rng1U64 line_range = txt_range_from_line_num(&text_patched.line_map, line_num); + *preferred_column = (*cursor - line_range.min); + } + + //- rjf: consume + ui_eat_event(evt); + + //- rjf: changed cursor -> snap in X + if(*cursor != start_cursor) + { + snap[Axis2_X] = 1; + } + + //- rjf: changed cursor line -> snap in Y + if(*cursor < line_range.min || line_range.max < *cursor) + { + snap[Axis2_Y] = 1; + } + } + } + ////////////////////////////// //- rjf: determine visible line range / count // @@ -128,12 +354,12 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla scroll_pos.y.idx + 1 + num_possible_visible_lines); U64 visible_line_count = 0; { - visible_line_num_range.min = Clamp(1, visible_line_num_range.min, (S64)text_info->lines_count); - visible_line_num_range.max = Clamp(1, visible_line_num_range.max, (S64)text_info->lines_count); + visible_line_num_range.min = Clamp(1, visible_line_num_range.min, (S64)text_patched.line_map.total_line_count); + visible_line_num_range.max = Clamp(1, visible_line_num_range.max, (S64)text_patched.line_map.total_line_count); visible_line_num_range.min = Max(1, visible_line_num_range.min); visible_line_num_range.max = Max(1, visible_line_num_range.max); - target_visible_line_num_range.min = Clamp(1, target_visible_line_num_range.min, (S64)text_info->lines_count); - target_visible_line_num_range.max = Clamp(1, target_visible_line_num_range.max, (S64)text_info->lines_count); + target_visible_line_num_range.min = Clamp(1, target_visible_line_num_range.min, (S64)text_patched.line_map.total_line_count); + target_visible_line_num_range.max = Clamp(1, target_visible_line_num_range.max, (S64)text_patched.line_map.total_line_count); target_visible_line_num_range.min = Max(1, target_visible_line_num_range.min); target_visible_line_num_range.max = Max(1, target_visible_line_num_range.max); visible_line_count = (U64)dim_1s64(visible_line_num_range)+1; @@ -149,7 +375,7 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla line_size_x = ClampBot(line_size_x, (S64)big_glyph_advance*120); line_size_x = ClampBot(line_size_x, (S64)code_area_dim.x); scroll_idx_rng[Axis2_X] = r1s64(0, line_size_x-(S64)code_area_dim.x); - scroll_idx_rng[Axis2_Y] = r1s64(0, (S64)text_info->lines_count-1); + scroll_idx_rng[Axis2_Y] = r1s64(0, (S64)text_patched.line_map.total_line_count-1); } ////////////////////////////// @@ -167,27 +393,42 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla priority_margin_width_px = floor_f32(big_glyph_advance*3.5f); catchall_margin_width_px = floor_f32(big_glyph_advance*3.5f); } - TXT_LineTokensSlice slice = txt_line_tokens_slice_from_info_data_line_range(scratch.arena, text_info, text_data, visible_line_num_range); + Rng1U64 visible_byte_range = r1u64(txt_range_from_line_num(&text_patched.line_map, visible_line_num_range.min).min, + txt_range_from_line_num(&text_patched.line_map, visible_line_num_range.max).max); + String8 visible_data = memory_map_data_from_range(scratch.arena, &text_patched.memory_map, visible_byte_range); + + ////////////////////////////// + //- rjf: find tokens for visible byte range + // + U64 ctx_token_pt_num = txt_token_pt_num_from_off(&text_patched.token_pt_map, visible_byte_range.min); + TXT_TokenPt ctx_token_pt = txt_token_pt_from_num(&text_patched.token_pt_map, ctx_token_pt_num); + TXT_TokenArray tokens = txt_token_array_from_data(scratch.arena, rd_regs()->lang_kind, ctx_token_pt, visible_data, visible_byte_range.min, max_U64); ////////////////////////////// //- rjf: selection on single line, no query? -> set search text // - if(rd_regs()->cursor.line == rd_regs()->mark.line) { - CFG_Node *view = cfg_node_from_id(rd_regs()->view); - RD_ViewState *vs = rd_view_state_from_cfg(view); - if(!vs->query_is_open) + U64 cursor = rd_regs()->cursor; + U64 mark = rd_regs()->mark; + U64 cursor_line_num = txt_line_num_from_off(&text_patched.line_map, cursor); + Rng1U64 cursor_line_range = txt_range_from_line_num(&text_patched.line_map, cursor_line_num); + if(cursor_line_range.min <= mark && mark <= cursor_line_range.max) { - CFG_Node *query = cfg_node_child_from_string_or_alloc(rd_state->cfg, view, str8_lit("query")); - CFG_Node *input = cfg_node_child_from_string_or_alloc(rd_state->cfg, query, str8_lit("input")); - String8 text = txt_string_from_info_data_txt_rng(text_info, text_data, txt_rng(rd_regs()->cursor, rd_regs()->mark)); - if(text.size < 256) + CFG_Node *view = cfg_node_from_id(rd_regs()->view); + RD_ViewState *vs = rd_view_state_from_cfg(view); + if(!vs->query_is_open) { - cfg_node_new_replace(rd_state->cfg, input, text); - } - else - { - cfg_node_new_replace(rd_state->cfg, input, str8_zero()); + CFG_Node *query = cfg_node_child_from_string_or_alloc(rd_state->cfg, view, str8_lit("query")); + CFG_Node *input = cfg_node_child_from_string_or_alloc(rd_state->cfg, query, str8_lit("input")); + String8 text = memory_map_data_from_range(scratch.arena, &text_patched.memory_map, r1u64(cursor, mark)); + if(text.size < 256) + { + cfg_node_new_replace(rd_state->cfg, input, text); + } + else + { + cfg_node_new_replace(rd_state->cfg, input, str8_zero()); + } } } } @@ -235,17 +476,50 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla code_slice_params.line_text_max_width_px = (F32)line_size_x; code_slice_params.margin_float_off_px = scroll_pos.x.idx + floor_f32(scroll_pos.x.off); - // rjf: fill text info + // rjf: fill line text / ranges { S64 line_num = visible_line_num_range.min; U64 line_idx = visible_line_num_range.min-1; for(U64 visible_line_idx = 0; - visible_line_idx < visible_line_count && line_idx < text_info->lines_count; + visible_line_idx < visible_line_count && line_idx < text_patched.line_map.total_line_count; visible_line_idx += 1, line_idx += 1, line_num += 1) { - code_slice_params.line_text[visible_line_idx] = str8_substr(text_data, text_info->lines_ranges[line_idx]); - code_slice_params.line_ranges[visible_line_idx] = text_info->lines_ranges[line_idx]; - code_slice_params.line_tokens[visible_line_idx] = slice.line_tokens[visible_line_idx]; + Rng1U64 line_range = txt_range_from_line_num(&text_patched.line_map, line_num); + String8 line_text = {0}; + line_text.size = dim_1u64(line_range); + line_text.str = push_array(scratch.arena, U8, line_text.size); + memory_map_read(&text_patched.memory_map, line_range, line_text.str); + code_slice_params.line_text[visible_line_idx] = line_text; + code_slice_params.line_ranges[visible_line_idx] = line_range; + } + } + + // rjf: bucket tokens by line + { + U64 token_idx = 0; + for EachIndex(line_idx, visible_line_count) + { + Temp scratch2 = scratch_begin(&scratch.arena, 1); + Rng1U64 line_range = code_slice_params.line_ranges[line_idx]; + TXT_TokenList line_tokens = {0}; + for(;token_idx < tokens.count;) + { + Rng1U64 token_range = tokens.v[token_idx].range; + if(dim_1u64(intersect_1u64(token_range, line_range)) != 0) + { + txt_token_list_push(scratch2.arena, &line_tokens, &tokens.v[token_idx]); + } + if(token_range.max <= line_range.max || token_range.max <= line_range.min) + { + token_idx += 1; + } + else if(line_range.max <= token_range.max) + { + break; + } + } + code_slice_params.line_tokens[line_idx] = txt_token_array_from_list(scratch.arena, &line_tokens); + scratch_end(scratch2); } } @@ -487,111 +761,100 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla //- rjf: do searching operations // { + U64 search_chunk_size = KB(4); + //- rjf: find text (forward) if(cv->find_text_fwd.size != 0) { - B32 found = 0; - B32 first = 1; - S64 line_num_start = rd_regs()->cursor.line; - S64 line_num_last = (S64)text_info->lines_count; - for(S64 line_num = line_num_start; 1 <= line_num && line_num <= line_num_last; first = 0) + String8 needle = cv->find_text_fwd; + Rng1U64 ranges[] = { - // rjf: gather line info - String8 line_string = str8_substr(text_data, text_info->lines_ranges[line_num-1]); - U64 search_start = 0; - if(rd_regs()->cursor.line == line_num && first) + r1u64(rd_regs()->cursor+1, text_patched.size), + r1u64(0, rd_regs()->cursor+1), + }; + B32 found = 0; + for EachElement(range_idx, ranges) + { + for(U64 off = ranges[range_idx].min; off < ranges[range_idx].max; off += search_chunk_size) { - search_start = rd_regs()->cursor.column; - } - - // rjf: search string - U64 needle_pos = str8_find_needle(line_string, search_start, cv->find_text_fwd, StringMatchFlag_CaseInsensitive); - if(needle_pos < line_string.size) - { - rd_regs()->mark.line = line_num; - rd_regs()->mark.column = needle_pos+1; - rd_regs()->cursor = rd_regs()->mark; - rd_regs()->cursor.column += cv->find_text_fwd.size; - found = 1; - break; - } - - // rjf: break if circled back around to cursor - else if(line_num == line_num_start && !first) - { - break; - } - - // rjf: increment - line_num += 1; - if(line_num > line_num_last) - { - line_num = 1; + Temp scratch = scratch_begin(&arena, 1); + String8 data = memory_map_data_from_range(scratch.arena, &text_patched.memory_map, r1u64(off, off+search_chunk_size)); + U64 hint_needle_pos = str8_find_needle(data, 0, needle, StringMatchFlag_CaseInsensitive|StringMatchFlag_RightSideSloppy); + if(hint_needle_pos < data.size) + { + String8 candidate = memory_map_data_from_range(scratch.arena, &text_patched.memory_map, r1u64(off+hint_needle_pos, off+hint_needle_pos+needle.size)); + if(str8_match(candidate, needle, StringMatchFlag_CaseInsensitive)) + { + found = 1; + rd_regs()->mark = off+hint_needle_pos; + rd_regs()->cursor = rd_regs()->mark + needle.size; + } + } + scratch_end(scratch); + if(found) { goto done_fwd; } } } + done_fwd:; + if(!found) + { + log_user_errorf("Could not find `%S`", needle); + } cv->center_cursor = found; - if(found == 0) - { - log_user_errorf("Could not find `%S`", cv->find_text_fwd); - } } //- rjf: find text (backward) - if(cv->find_text_bwd.size != 0) + if(cv->find_text_bwd.size != 0 && rd_regs()->cursor > 0) { - B32 found = 0; - B32 first = 1; - TxtRng rng = txt_rng(rd_regs()->cursor, rd_regs()->mark); - S64 line_num_start = rng.min.line; - S64 line_num_last = (S64)text_info->lines_count; - for(S64 line_num = line_num_start; 1 <= line_num && line_num <= line_num_last; first = 0) + String8 needle = cv->find_text_bwd; + Rng1U64 ranges[] = { - // rjf: gather line info - String8 line_string = str8_substr(text_data, text_info->lines_ranges[line_num-1]); - if(rng.min.line == line_num && first) + r1u64(0, Min(rd_regs()->cursor, rd_regs()->mark)), + r1u64(rd_regs()->cursor+1, text_patched.size), + }; + B32 found = 0; + for EachElement(range_idx, ranges) + { + U64 start_off = ranges[range_idx].max + (search_chunk_size-1); + start_off -= start_off%search_chunk_size; + start_off -= search_chunk_size; + for(U64 off = start_off;; off -= search_chunk_size) { - line_string = str8_prefix(line_string, rng.min.column-1); - } - - // rjf: search string - U64 next_needle_pos = line_string.size; - for(U64 needle_pos = 0; needle_pos < line_string.size;) - { - needle_pos = str8_find_needle(line_string, needle_pos, cv->find_text_bwd, StringMatchFlag_CaseInsensitive); - if(needle_pos < line_string.size) + Temp scratch = scratch_begin(&arena, 1); + String8 data = memory_map_data_from_range(scratch.arena, &text_patched.memory_map, r1u64(off, Min(off+search_chunk_size, ranges[range_idx].max))); + U64 hint_needle_pos = str8_find_needle(data, 0, needle, StringMatchFlag_CaseInsensitive|StringMatchFlag_RightSideSloppy); + for(;hint_needle_pos < data.size;) { - next_needle_pos = needle_pos; - needle_pos += 1; + U64 next_hint_needle_pos = str8_find_needle(data, hint_needle_pos+1, needle, StringMatchFlag_CaseInsensitive|StringMatchFlag_RightSideSloppy); + if(next_hint_needle_pos < data.size) + { + hint_needle_pos = next_hint_needle_pos; + } + else + { + break; + } } + if(hint_needle_pos < data.size) + { + String8 candidate = memory_map_data_from_range(scratch.arena, &text_patched.memory_map, r1u64(off+hint_needle_pos, off+hint_needle_pos+needle.size)); + if(str8_match(candidate, needle, StringMatchFlag_CaseInsensitive)) + { + found = 1; + rd_regs()->mark = off+hint_needle_pos; + rd_regs()->cursor = rd_regs()->mark + needle.size; + } + } + scratch_end(scratch); + if(found) { goto done_bwd; } + if(off == 0) { break; } } - if(next_needle_pos < line_string.size) - { - rd_regs()->mark.line = line_num; - rd_regs()->mark.column = next_needle_pos+1; - rd_regs()->cursor = rd_regs()->mark; - rd_regs()->cursor.column += cv->find_text_bwd.size; - found = 1; - break; - } - - // rjf: break if circled back around to cursor line - else if(line_num == line_num_start && !first) - { - break; - } - - // rjf: increment - line_num -= 1; - if(line_num == 0) - { - line_num = line_num_last; - } + } + done_bwd:; + if(!found) + { + log_user_errorf("Could not find `%S`", needle); } cv->center_cursor = found; - if(found == 0) - { - log_user_errorf("Could not find `%S`", cv->find_text_bwd); - } } MemoryZeroStruct(&cv->find_text_fwd); @@ -602,27 +865,16 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla ////////////////////////////// //- rjf: do goto line // - if(cv->goto_line_num != 0 && text_info->lines_count != 0) + if(cv->goto_line_num != 0 && text_is_ready) { S64 line_num = cv->goto_line_num; cv->goto_line_num = 0; - line_num = Clamp(1, line_num, text_info->lines_count); - rd_regs()->cursor = rd_regs()->mark = txt_pt(line_num, 1); + line_num = Clamp(1, line_num, text_patched.line_map.total_line_count); + Rng1U64 range = txt_range_from_line_num(&text_patched.line_map, line_num); + rd_regs()->cursor = rd_regs()->mark = range.min; cv->center_cursor = !cv->force_contain_only && (!cv->contain_cursor || (line_num < target_visible_line_num_range.min+4 || target_visible_line_num_range.max-4 < line_num)); } - ////////////////////////////// - //- rjf: do keyboard interaction - // - B32 snap[Axis2_COUNT] = {0}; - UI_Focus(UI_FocusKind_On) - { - if(ui_is_focus_active() && visible_line_num_range.max >= visible_line_num_range.min) - { - snap[Axis2_X] = snap[Axis2_Y] = rd_do_txt_controls(text_info, text_data, ClampBot(num_possible_visible_lines, 10) - 10, &rd_regs()->cursor, &rd_regs()->mark, &cv->preferred_column); - } - } - ////////////////////////////// //- rjf: build container contents // @@ -662,27 +914,28 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla if(ui_pressed(sig.base) && sig.base.event_flags & WM_Modifier_Ctrl) { ui_kill_action(); - rd_cmd(RD_CmdKind_GoToName, .string = txt_string_from_info_data_txt_rng(text_info, text_data, sig.mouse_expr_rng)); + rd_cmd(RD_CmdKind_GoToName, .string = memory_map_data_from_range(scratch.arena, &text_patched.memory_map, sig.mouse_expr_rng)); } //- rjf: watch expr at mouse if(cv->watch_expr_at_mouse) { cv->watch_expr_at_mouse = 0; - rd_cmd(RD_CmdKind_ToggleWatchExpression, .string = txt_string_from_info_data_txt_rng(text_info, text_data, sig.mouse_expr_rng)); + rd_cmd(RD_CmdKind_ToggleWatchExpression, .string = memory_map_data_from_range(scratch.arena, &text_patched.memory_map, sig.mouse_expr_rng)); } } ////////////////////////////// //- rjf: apply post-build view snapping rules // - if(text_info->lines_count != 0) + if(text_is_ready) { - TxtPt cursor = rd_regs()->cursor; - B32 cursor_in_range = (1 <= cursor.line && cursor.line <= text_info->lines_count); + U64 cursor = rd_regs()->cursor; + U64 cursor_line_num = txt_line_num_from_off(&text_patched.line_map, cursor); + B32 cursor_in_range = (1 <= cursor_line_num && cursor_line_num <= text_patched.line_map.total_line_count); // rjf: contain => snap - if(cv->contain_cursor && text_info->lines_count != 0) + if(cv->contain_cursor) { cv->contain_cursor = 0; snap[Axis2_X] = 1; @@ -690,13 +943,14 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla } // rjf: center cursor - if(cv->center_cursor && text_info->lines_count != 0) + if(cv->center_cursor) { cv->center_cursor = 0; if(cursor_in_range) { - String8 cursor_line = str8_substr(text_data, text_info->lines_ranges[cursor.line-1]); - F32 cursor_advance = fnt_dim_from_tag_size_string(code_font, code_font_size, 0, code_tab_size, str8_prefix(cursor_line, cursor.column-1)).x; + Rng1U64 cursor_line_range = txt_range_from_line_num(&text_patched.line_map, cursor_line_num); + String8 cursor_line = memory_map_data_from_range(scratch.arena, &text_patched.memory_map, cursor_line_range); + F32 cursor_advance = fnt_dim_from_tag_size_string(code_font, code_font_size, 0, code_tab_size, str8_prefix(cursor_line, cursor-cursor_line_range.min)).x; // rjf: scroll x { @@ -708,7 +962,7 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla // rjf: scroll y { - S64 new_idx = (cursor.line-1) - num_possible_visible_lines/2 + 2; + S64 new_idx = ((S64)cursor_line_num-1) - num_possible_visible_lines/2 + 2; new_idx = Clamp(scroll_idx_rng[Axis2_Y].min, new_idx, scroll_idx_rng[Axis2_Y].max); ui_scroll_pt_target_idx(&scroll_pos.y, new_idx); snap[Axis2_Y] = 0; @@ -719,8 +973,9 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla // rjf: snap in X if(snap[Axis2_X] && cursor_in_range) { - String8 cursor_line = str8_substr(text_data, text_info->lines_ranges[cursor.line-1]); - S64 cursor_off = (S64)(fnt_dim_from_tag_size_string(code_font, code_font_size, 0, code_tab_size, str8_prefix(cursor_line, cursor.column-1)).x + priority_margin_width_px + catchall_margin_width_px + line_num_width_px); + Rng1U64 cursor_line_range = txt_range_from_line_num(&text_patched.line_map, cursor_line_num); + String8 cursor_line = memory_map_data_from_range(scratch.arena, &text_patched.memory_map, cursor_line_range); + S64 cursor_off = (S64)(fnt_dim_from_tag_size_string(code_font, code_font_size, 0, code_tab_size, str8_prefix(cursor_line, cursor-cursor_line_range.min)).x + priority_margin_width_px + catchall_margin_width_px + line_num_width_px); Rng1S64 visible_pixel_range = { scroll_pos.x.idx, @@ -741,13 +996,13 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla // rjf: snap in Y if(snap[Axis2_Y]) { - Rng1S64 cursor_visibility_range = r1s64(cursor.line-4, cursor.line+4); + Rng1S64 cursor_visibility_range = r1s64((S64)cursor_line_num-4, (S64)cursor_line_num+4); cursor_visibility_range.min = ClampBot(0, cursor_visibility_range.min); cursor_visibility_range.max = ClampBot(0, cursor_visibility_range.max); S64 min_delta = Min(0, cursor_visibility_range.min-(target_visible_line_num_range.min)); S64 max_delta = Max(0, cursor_visibility_range.max-(target_visible_line_num_range.min+num_possible_visible_lines)); S64 new_idx = scroll_pos.y.idx+min_delta+max_delta; - new_idx = Clamp(0, new_idx, (S64)text_info->lines_count-1); + new_idx = Clamp(0, new_idx, (S64)text_patched.line_map.total_line_count-1); ui_scroll_pt_target_idx(&scroll_pos.y, new_idx); } } @@ -789,7 +1044,7 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla ////////////////////////////// //- rjf: top-level container interaction (scrolling) // - if(text_info->lines_count != 0) + if(text_is_ready) { UI_Signal sig = ui_signal_from_box(container_box); if(sig.scroll.x != 0) @@ -2079,14 +2334,8 @@ RD_VIEW_UI_FUNCTION_DEF(text) rd_regs()->file_path = rd_file_path_from_eval(rd_frame_arena(), eval); rd_regs()->vaddr = 0; rd_regs()->prefer_disasm = 0; - rd_regs()->cursor.line = rd_view_setting_value_from_name(str8_lit("cursor_line")).s64; - rd_regs()->cursor.column = rd_view_setting_value_from_name(str8_lit("cursor_column")).s64; - rd_regs()->mark.line = rd_view_setting_value_from_name(str8_lit("mark_line")).s64; - rd_regs()->mark.column = rd_view_setting_value_from_name(str8_lit("mark_column")).s64; - if(rd_regs()->cursor.line == 0) { rd_regs()->cursor.line = 1; } - if(rd_regs()->cursor.column == 0) { rd_regs()->cursor.column = 1; } - if(rd_regs()->mark.line == 0) { rd_regs()->mark.line = 1; } - if(rd_regs()->mark.column == 0) { rd_regs()->mark.column = 1; } + rd_regs()->cursor = rd_view_setting_value_from_name(s("cursor")).u64; + rd_regs()->mark = rd_view_setting_value_from_name(s("mark")).u64; String8List overrides = rd_possible_overrides_from_file_path(scratch.arena, rd_regs()->file_path); Rng1U64 range = rd_space_range_from_eval(eval); rd_regs()->text_key = rd_key_from_eval_space_range(eval.space, range, 1); @@ -2220,6 +2469,13 @@ RD_VIEW_UI_FUNCTION_DEF(text) rd_code_view_build(scratch.arena, cv, flags, code_area_rect, hash, data, &info, 0, r1u64(0, 0), di_key_zero()); } + ////////////////////////////// + //- rjf: produced patched text info, unpack cursor info in patched text + // + TXT_Patched patched = txt_patched_from_info_data_patches(scratch.arena, &info, data, &cv->patches); + U64 cursor_line_num = txt_line_num_from_off(&patched.line_map, rd_regs()->cursor); + Rng1U64 cursor_line_range = txt_range_from_line_num(&patched.line_map, cursor_line_num); + ////////////////////////////// //- rjf: unpack cursor info // @@ -2227,8 +2483,10 @@ RD_VIEW_UI_FUNCTION_DEF(text) { D_Entity *module = d_entity_from_handle(rd_regs()->module); DI_Key dbgi_key = d_dbgi_key_from_module(module); - rd_regs()->lines = d_lines_from_dbgi_key_file_path_line_num(rd_frame_arena(), dbgi_key, rd_regs()->file_path, rd_regs()->cursor.line, 8); + rd_regs()->lines = d_lines_from_dbgi_key_file_path_line_num(rd_frame_arena(), dbgi_key, rd_regs()->file_path, (S64)cursor_line_num, 8); } + rd_regs()->line_num = cursor_line_num; + rd_regs()->column_num = (rd_regs()->cursor - cursor_line_range.min); ////////////////////////////// //- rjf: determine if file is out-of-date @@ -2337,7 +2595,7 @@ RD_VIEW_UI_FUNCTION_DEF(text) ui_label(rd_regs()->file_path); ui_spacer(ui_em(1.5f, 1)); } - ui_labelf("Line: %I64d, Column: %I64d", rd_regs()->cursor.line, rd_regs()->cursor.column); + ui_labelf("Line: %I64d, Column: %I64d, Offset: 0x%I64x", cursor_line_num, 1 + rd_regs()->cursor - cursor_line_range.min, rd_regs()->cursor); ui_spacer(ui_pct(1, 0)); ui_labelf("(read only)"); ui_labelf("%s", @@ -2351,10 +2609,8 @@ RD_VIEW_UI_FUNCTION_DEF(text) ////////////////////////////// //- rjf: store params // - rd_store_view_param_s64(str8_lit("cursor_line"), rd_regs()->cursor.line); - rd_store_view_param_s64(str8_lit("cursor_column"), rd_regs()->cursor.column); - rd_store_view_param_s64(str8_lit("mark_line"), rd_regs()->mark.line); - rd_store_view_param_s64(str8_lit("mark_column"), rd_regs()->mark.column); + rd_store_view_param_u64(s("cursor"), rd_regs()->cursor); + rd_store_view_param_u64(s("mark"), rd_regs()->mark); access_close(access); scratch_end(scratch); @@ -2367,8 +2623,8 @@ typedef struct RD_DisasmViewState RD_DisasmViewState; struct RD_DisasmViewState { B32 initialized; - TxtPt cursor; - TxtPt mark; + U64 cursor; + U64 mark; D_Handle temp_look_process; U64 temp_look_vaddr; U64 temp_look_run_gen; @@ -2390,8 +2646,8 @@ RD_VIEW_UI_FUNCTION_DEF(disasm) if(dv->initialized == 0) { dv->initialized = 1; - dv->cursor = txt_pt(1, 1); - dv->mark = txt_pt(1, 1); + dv->cursor = 0; + dv->mark = 0; rd_code_view_init(&dv->cv); } RD_CodeViewState *cv = &dv->cv; @@ -2563,13 +2819,19 @@ RD_VIEW_UI_FUNCTION_DEF(disasm) rd_code_view_build(scratch.arena, cv, RD_CodeViewBuildFlag_All, code_area_rect, dasm_text_hash, dasm_text_data, &dasm_text_info, &dasm_info.lines, range, dbgi_key); } + ////////////////////////////// + //- rjf: produced patched text info, unpack cursor info in patched text + // + TXT_Patched patched = txt_patched_from_info_data_patches(scratch.arena, &dasm_text_info, dasm_text_data, &cv->patches); + U64 cursor_line_num = txt_line_num_from_off(&patched.line_map, rd_regs()->cursor); + ////////////////////////////// //- rjf: unpack cursor info / fill regs // rd_regs()->prefer_disasm = 1; if(!is_loading && has_disasm) { - U64 off = dasm_line_array_code_off_from_idx(&dasm_info.lines, rd_regs()->cursor.line-1); + U64 off = dasm_line_array_code_off_from_idx(&dasm_info.lines, cursor_line_num-1); rd_regs()->vaddr = range.min+off; rd_regs()->vaddr_range = r1u64(range.min+off, range.min+off); rd_regs()->voff_range = d_voff_range_from_vaddr_range(dasm_module, rd_regs()->vaddr_range); @@ -2589,13 +2851,14 @@ RD_VIEW_UI_FUNCTION_DEF(disasm) UI_TagF("weak") RD_Font(RD_FontSlot_Code) { - U64 cursor_vaddr = (1 <= rd_regs()->cursor.line && rd_regs()->cursor.line <= dasm_info.lines.count) ? (range.min+dasm_info.lines.v[rd_regs()->cursor.line-1].code_off) : 0; + U64 cursor_vaddr = (1 <= cursor_line_num && cursor_line_num <= dasm_info.lines.count) ? (range.min+dasm_info.lines.v[cursor_line_num-1].code_off) : 0; if(dasm_module != &d_entity_nil) { ui_labelf("%S", dasm_module->string); ui_spacer(ui_em(1.5f, 1)); } - ui_labelf("Address: 0x%I64x, Line: %I64d, Column: %I64d", cursor_vaddr, rd_regs()->cursor.line, rd_regs()->cursor.column); + Rng1U64 cursor_line_range = txt_range_from_line_num(&patched.line_map, cursor_line_num); + ui_labelf("Address: 0x%I64x, Line: %I64d, Column: %I64d", cursor_vaddr, cursor_line_num, 1 + rd_regs()->cursor - cursor_line_range.min); ui_spacer(ui_pct(1, 0)); ui_labelf("(read only)"); ui_labelf("bin"); @@ -2629,8 +2892,8 @@ struct RD_MemoryViewState B32 snap_scroll; B32 cell_value_edit_in_progress; U8 cell_value_edit_first_digit; - TxtPt addrbar_cursor; - TxtPt addrbar_mark; + U64 addrbar_cursor; + U64 addrbar_mark; U8 addrbar_buffer[1024]; U64 addrbar_string_size; B32 addrbar_is_focused; @@ -4403,8 +4666,8 @@ RD_VIEW_UI_FUNCTION_DEF(memory) mv->addrbar_is_focused = 1; mv->addrbar_string_size = Min(sizeof(mv->addrbar_buffer), cursor_addr->first->string.size); MemoryCopy(mv->addrbar_buffer, cursor_addr->first->string.str, mv->addrbar_string_size); - mv->addrbar_cursor = txt_pt(1, mv->addrbar_string_size+1); - mv->addrbar_mark = txt_pt(1, 1); + mv->addrbar_cursor = mv->addrbar_string_size+1; + mv->addrbar_mark = 0; } if(commit_addrbar) { @@ -4504,7 +4767,7 @@ struct RD_BitmapCanvasBoxDrawData }; internal AC_Artifact -rd_bitmap_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out, U64 *gen_out) +rd_bitmap_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, U64 *gen_out) { Access *access = access_open(); @@ -5130,7 +5393,7 @@ struct RD_Geo3DBoxDrawData }; internal AC_Artifact -rd_geo3d_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out, U64 *gen_out) +rd_geo3d_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, U64 *gen_out) { Access *access = access_open(); U128 hash = {0}; diff --git a/src/raddbg/raddbg_views.h b/src/raddbg/raddbg_views.h index 530d07b5..1bae6b7d 100644 --- a/src/raddbg/raddbg_views.h +++ b/src/raddbg/raddbg_views.h @@ -47,6 +47,10 @@ struct RD_CodeViewState B32 drifted_for_search; U128 last_hash; + // rjf: patch state + Arena *patch_arena; + TXT_PatchList patches; + // rjf: per-frame command info S64 goto_line_num; B32 center_cursor; @@ -159,8 +163,8 @@ struct RD_WatchViewTextEditState { RD_WatchViewTextEditState *pt_hash_next; RD_WatchPt pt; - TxtPt cursor; - TxtPt mark; + U64 cursor; + U64 mark; U8 input_buffer[1024]; U64 input_size; U8 initial_buffer[1024]; diff --git a/src/raddbg/raddbg_widgets.c b/src/raddbg/raddbg_widgets.c index 4db60f77..a1e797c4 100644 --- a/src/raddbg/raddbg_widgets.c +++ b/src/raddbg/raddbg_widgets.c @@ -907,19 +907,27 @@ rd_cmd_binding_buttons(String8 name, String8 filter, U64 limit, RD_CmdBindingBut str8_match(rd_state->bind_change_cmd_name, name, 0) && rd_state->bind_change_binding_id == 0); ui_spacer(ui_em(1.f, 1.f)); - RD_Font(RD_FontSlot_Icons) UI_TagF(adding_new_binding ? "pop" : "") UI_CornerRadius(ui_top_font_size()*0.5f) { - ui_set_next_text_alignment(UI_TextAlign_Center); - ui_set_next_group_key(ui_key_zero()); - ui_set_next_pref_width(ui_text_dim(ui_top_font_size()*1.5f, 1)); - UI_Box *box = ui_build_box_from_stringf(UI_BoxFlag_DrawText| - UI_BoxFlag_Clickable| - UI_BoxFlag_DrawActiveEffects| - UI_BoxFlag_DrawHotEffects| - UI_BoxFlag_DrawBorder| - UI_BoxFlag_DrawBackground, - "%S###add_binding", rd_icon_kind_text_table[RD_IconKind_Add]); + UI_Box *box = &ui_nil_box; + RD_Font(RD_FontSlot_Icons) UI_TagF(adding_new_binding ? "pop" : "") UI_CornerRadius(ui_top_font_size()*0.5f) + { + ui_set_next_text_alignment(UI_TextAlign_Center); + ui_set_next_group_key(ui_key_zero()); + ui_set_next_pref_width(ui_text_dim(ui_top_font_size()*1.5f, 1)); + box = ui_build_box_from_stringf(UI_BoxFlag_DrawText| + UI_BoxFlag_Clickable| + UI_BoxFlag_DrawActiveEffects| + UI_BoxFlag_DrawHotEffects| + UI_BoxFlag_DrawBorder| + UI_BoxFlag_DrawBackground, + "%S###add_binding", rd_icon_kind_text_table[RD_IconKind_Add]); + } UI_Signal sig = ui_signal_from_box(box); + if(ui_hovering(sig)) UI_Tooltip + { + ui_state->tooltip_anchor_key = box->key; + ui_labelf("Add New Binding"); + } if(ui_clicked(sig)) { if(!adding_new_binding && ui_clicked(sig)) @@ -1283,7 +1291,7 @@ internal UI_BOX_CUSTOM_DRAW(rd_bp_box_draw_extensions) } internal RD_CodeSliceSignal -rd_code_slice(RD_CodeSliceParams *params, TxtPt *cursor, TxtPt *mark, S64 *preferred_column, String8 string) +rd_code_slice(RD_CodeSliceParams *params, U64 *cursor, U64 *mark, S64 *preferred_column, String8 string) { RD_CodeSliceSignal result = {0}; ProfBeginFunction(); @@ -1879,9 +1887,9 @@ rd_code_slice(RD_CodeSliceParams *params, TxtPt *cursor, TxtPt *mark, S64 *prefe { rd_cmd(RD_CmdKind_AddBreakpoint, .file_path = params->line_vaddrs[line_idx] ? str8_zero() : rd_regs()->file_path, - .cursor = params->line_vaddrs[line_idx] ? txt_pt(0, 0) : txt_pt(line_num, 1), + .line_num = params->line_vaddrs[line_idx] ? 0 : line_num, .vaddr = params->line_vaddrs[line_idx], - .expr = s("")); + .expr = s("")); } } } @@ -1893,7 +1901,7 @@ rd_code_slice(RD_CodeSliceParams *params, TxtPt *cursor, TxtPt *mark, S64 *prefe if(params->flags & RD_CodeSliceFlag_LineNums) UI_Parent(top_container_box) ProfScope("build line numbers") UI_Focus(UI_FocusKind_Off) UI_TagF("floating") { - TxtRng select_rng = txt_rng(*cursor, *mark); + Rng1U64 select_rng = r1u64(*cursor, *mark); ui_set_next_fixed_x(floor_f32(params->margin_float_off_px + params->priority_margin_width_px + params->catchall_margin_width_px)); ui_set_next_pref_width(ui_px(params->line_num_width_px, 1.f)); ui_set_next_pref_height(ui_px(params->line_height_px*(dim_1s64(params->line_num_range)+1), 1.f)); @@ -1909,7 +1917,8 @@ rd_code_slice(RD_CodeSliceParams *params, TxtPt *cursor, TxtPt *mark, S64 *prefe line_num <= params->line_num_range.max; line_num += 1, line_idx += 1) { - B32 line_is_selected = (select_rng.min.line <= line_num && line_num <= select_rng.max.line); + Rng1U64 line_range = params->line_ranges[line_idx]; + B32 line_is_selected = (dim_1u64(intersect_1u64(select_rng, line_range)) != 0) || (line_range.min <= *cursor && *cursor <= line_range.max); Vec4F32 bg_color = v4f32(0, 0, 0, 0); // rjf: line info on this line -> adjust bg color to visualize @@ -1966,63 +1975,57 @@ rd_code_slice(RD_CodeSliceParams *params, TxtPt *cursor, TxtPt *mark, S64 *prefe ////////////////////////////// //- rjf: mouse -> text coordinates // - TxtPt mouse_pt = {0}; + U64 mouse_off = 0; + U64 mouse_y_line_idx = 0; ProfScope("mouse -> text coordinates") { Vec2F32 mouse = ui_mouse(); // rjf: mouse y => index - U64 mouse_y_line_idx = (U64)((mouse.y - text_container_box->rect.y0) / params->line_height_px); + S64 mouse_y_line_idx_signed = ((mouse.y - text_container_box->rect.y0) / params->line_height_px); + mouse_y_line_idx_signed = Clamp(0, mouse_y_line_idx_signed, dim_1s64(params->line_num_range)-1); + mouse_y_line_idx = (U64)mouse_y_line_idx_signed; // rjf: index => line num S64 line_num = (params->line_num_range.min + mouse_y_line_idx); + Rng1U64 line_range = (params->line_num_range.min <= line_num && line_num <= params->line_num_range.max) ? (params->line_ranges[mouse_y_line_idx]) : r1u64(0, 0); String8 line_string = (params->line_num_range.min <= line_num && line_num <= params->line_num_range.max) ? (params->line_text[mouse_y_line_idx]) : str8_zero(); - // rjf: mouse x * string => column - S64 column = fnt_char_pos_from_tag_size_string_p(params->font, params->font_size, 0, params->tab_size, line_string, mouse.x-text_container_box->rect.x0-params->line_num_width_px-line_num_padding_px)+1; + // rjf: mouse x * string => line offset + U64 mouse_line_off = fnt_char_pos_from_tag_size_string_p(params->font, params->font_size, 0, params->tab_size, line_string, mouse.x-text_container_box->rect.x0-params->line_num_width_px-line_num_padding_px); - // rjf: bundle - mouse_pt = txt_pt(line_num, column); + // rjf: compute + mouse_off = line_range.min + mouse_line_off; // rjf: clamp { - U64 last_line_size = params->line_text[dim_1s64(params->line_num_range)].size; - TxtRng legal_pt_rng = txt_rng(txt_pt(params->line_num_range.min, 1), - txt_pt(params->line_num_range.max, last_line_size+1)); - if(txt_pt_less_than(mouse_pt, legal_pt_rng.min)) - { - mouse_pt = legal_pt_rng.min; - } - if(txt_pt_less_than(legal_pt_rng.max, mouse_pt)) - { - mouse_pt = legal_pt_rng.max; - } + Rng1U64 legal_range = r1u64(params->line_ranges[0].min, params->line_ranges[dim_1s64(params->line_num_range)].max); + mouse_off = clamp_1u64(legal_range, mouse_off); } - result.mouse_pt = mouse_pt; + result.mouse_off = mouse_off; } ////////////////////////////// //- rjf: mouse point -> mouse token range, mouse line range // - TxtRng mouse_token_rng = txt_rng(mouse_pt, mouse_pt); - TxtRng mouse_line_rng = txt_rng(mouse_pt, mouse_pt); - if(contains_1s64(params->line_num_range, mouse_pt.line)) + Rng1U64 mouse_token_rng = r1u64(mouse_off, mouse_off); + Rng1U64 mouse_line_rng = r1u64(mouse_off, mouse_off); + if(mouse_y_line_idx < (U64)dim_1s64(params->line_num_range)) { - TXT_TokenArray *line_tokens = ¶ms->line_tokens[mouse_pt.line-params->line_num_range.min]; - Rng1U64 line_range = params->line_ranges[mouse_pt.line-params->line_num_range.min]; - U64 mouse_pt_off = (mouse_pt.column-1) + line_range.min; + TXT_TokenArray *line_tokens = ¶ms->line_tokens[mouse_y_line_idx]; + Rng1U64 line_range = params->line_ranges[mouse_y_line_idx]; for(U64 line_token_idx = 0; line_token_idx < line_tokens->count; line_token_idx += 1) { TXT_Token *line_token = &line_tokens->v[line_token_idx]; - if(contains_1u64(line_token->range, mouse_pt_off)) + if(contains_1u64(line_token->range, mouse_off)) { Rng1U64 line_token_range_clamped = intersect_1u64(line_token->range, line_range); - mouse_token_rng = txt_rng(txt_pt(mouse_pt.line, 1+line_token_range_clamped.min-line_range.min), txt_pt(mouse_pt.line, 1+line_token_range_clamped.max-line_range.min)); + mouse_token_rng = r1u64(line_token_range_clamped.min, line_token_range_clamped.max); break; } } - mouse_line_rng = txt_rng(txt_pt(mouse_pt.line, 1), txt_pt(mouse_pt.line, 1+(line_range.max-line_range.min))); + mouse_line_rng = line_range; } ////////////////////////////// @@ -2034,7 +2037,7 @@ rd_code_slice(RD_CodeSliceParams *params, TxtPt *cursor, TxtPt *mark, S64 *prefe UI_Signal text_container_sig = ui_signal_from_box(text_container_box); { //- rjf: determine mouse drag range - TxtRng mouse_drag_rng = txt_rng(mouse_pt, mouse_pt); + Rng1U64 mouse_drag_rng = r1u64(mouse_off, mouse_off); if(text_container_sig.f & UI_SignalFlag_LeftTripleDragging) { mouse_drag_rng = mouse_line_rng; @@ -2047,24 +2050,12 @@ rd_code_slice(RD_CodeSliceParams *params, TxtPt *cursor, TxtPt *mark, S64 *prefe //- rjf: clicking/dragging over the text container if(!ctrlified && ui_dragging(text_container_sig)) { - if(mouse_pt.line == 0) - { - mouse_pt.column = 1; - if(ui_mouse().y <= top_container_box->rect.y0) - { - mouse_pt.line = params->line_num_range.min - 2; - } - else if(ui_mouse().y >= top_container_box->rect.y1) - { - mouse_pt.line = params->line_num_range.max + 2; - } - } if(ui_pressed(text_container_sig)) { *cursor = mouse_drag_rng.max; *mark = mouse_drag_rng.min; } - if(txt_pt_less_than(mouse_pt, *mark)) + if(mouse_off < *mark) { *cursor = mouse_drag_rng.min; } @@ -2072,7 +2063,7 @@ rd_code_slice(RD_CodeSliceParams *params, TxtPt *cursor, TxtPt *mark, S64 *prefe { *cursor = mouse_drag_rng.max; } - *preferred_column = cursor->column; + *preferred_column = *cursor - mouse_line_rng.min; } //- rjf: dragging will invalidate the search string, so we don't want to draw it while dragging/releasing @@ -2084,25 +2075,25 @@ rd_code_slice(RD_CodeSliceParams *params, TxtPt *cursor, TxtPt *mark, S64 *prefe //- rjf: right-click => code context menu if(ui_right_clicked(text_container_sig)) { - if(txt_pt_match(*cursor, *mark)) + if(*cursor == *mark) { - *cursor = *mark = mouse_pt; + *cursor = *mark = mouse_off; } U64 vaddr = 0; D_LineList lines = {0}; - if(params->line_num_range.min <= cursor->line && cursor->line < params->line_num_range.max) + if(mouse_y_line_idx < (U64)dim_1s64(params->line_num_range)) { - vaddr = params->line_vaddrs[cursor->line - params->line_num_range.min]; - lines = params->line_infos[cursor->line - params->line_num_range.min]; + vaddr = params->line_vaddrs[mouse_y_line_idx]; + lines = params->line_infos[mouse_y_line_idx]; } String8 commands_expr; if(vaddr != 0) { - commands_expr = txt_pt_match(*cursor, *mark) ? s("query:disasm_pt_commands") : s("query:disasm_range_commands"); + commands_expr = (*cursor == *mark) ? s("query:disasm_pt_commands") : s("query:disasm_range_commands"); } else { - commands_expr = txt_pt_match(*cursor, *mark) ? s("query:text_pt_commands") : s("query:text_range_commands"); + commands_expr = (*cursor == *mark) ? s("query:text_pt_commands") : s("query:text_range_commands"); } rd_cmd(RD_CmdKind_FocusPanel); rd_cmd(RD_CmdKind_PushQuery, @@ -2119,40 +2110,38 @@ rd_code_slice(RD_CodeSliceParams *params, TxtPt *cursor, TxtPt *mark, S64 *prefe } //- rjf: drop target is dropped -> process - if(drop_can_hit_lines && ui_key_match(ui_drop_hot_key(), drop_site_key) && rd_drag_drop()) + if(drop_can_hit_lines && ui_key_match(ui_drop_hot_key(), drop_site_key) && rd_drag_drop() && + mouse_y_line_idx < (U64)dim_1s64(params->line_num_range)) { if(rd_state->drag_drop_regs_slot == RD_RegSlot_Expr) { - S64 line_num = mouse_pt.line; - U64 line_idx = line_num - params->line_num_range.min; - U64 line_vaddr = params->line_vaddrs[line_idx]; + U64 line_num = (U64)params->line_num_range.min + mouse_y_line_idx; + U64 line_vaddr = params->line_vaddrs[mouse_y_line_idx]; rd_cmd(RD_CmdKind_AddWatchPin, .expr = rd_state->drag_drop_regs->expr, .file_path = line_vaddr == 0 ? rd_regs()->file_path : str8_zero(), - .cursor = line_vaddr == 0 ? txt_pt(line_num, 1) : txt_pt(0, 0), + .line_num = line_vaddr == 0 ? line_num : 0, .vaddr = line_vaddr); } if(rd_state->drag_drop_regs_slot == RD_RegSlot_Cfg && drop_cfg != &cfg_nil_node) { - S64 line_num = mouse_pt.line; - U64 line_idx = line_num - params->line_num_range.min; - U64 line_vaddr = params->line_vaddrs[line_idx]; + U64 line_num = (U64)params->line_num_range.min + mouse_y_line_idx; + U64 line_vaddr = params->line_vaddrs[mouse_y_line_idx]; rd_cmd(RD_CmdKind_RelocateCfg, .cfg = drop_cfg->id, .file_path = line_vaddr == 0 ? rd_regs()->file_path : str8_zero(), - .cursor = line_vaddr == 0 ? txt_pt(line_num, 1) : txt_pt(0, 0), + .line_num = line_vaddr == 0 ? line_num : 0, .vaddr = line_vaddr); } if(drop_thread != &d_entity_nil) { - S64 line_num = mouse_pt.line; - U64 line_idx = line_num - params->line_num_range.min; - U64 line_vaddr = params->line_vaddrs[line_idx]; + U64 line_num = (U64)params->line_num_range.min + mouse_y_line_idx; + U64 line_vaddr = params->line_vaddrs[mouse_y_line_idx]; D_Entity *thread = drop_thread; U64 new_rip_vaddr = line_vaddr; - if(params->line_vaddrs[line_idx] == 0) + if(params->line_vaddrs[mouse_y_line_idx] == 0) { - D_LineList *lines = ¶ms->line_infos[line_idx]; + D_LineList *lines = ¶ms->line_infos[mouse_y_line_idx]; for(D_LineNode *n = lines->first; n != 0; n = n->next) { D_EntityList modules = d_modules_from_dbgi_key(scratch.arena, n->v.dbgi_key); @@ -2178,7 +2167,7 @@ rd_code_slice(RD_CodeSliceParams *params, TxtPt *cursor, TxtPt *mark, S64 *prefe TXT_ScopeNode *cursor_scope_node = &txt_scope_node_nil; if(params->text_info != 0) { - cursor_scope_node = txt_scope_node_from_info_pt(params->text_info, rd_regs()->cursor); + cursor_scope_node = txt_scope_node_from_info_off(params->text_info, *cursor); } ////////////////////////////// @@ -2206,7 +2195,7 @@ rd_code_slice(RD_CodeSliceParams *params, TxtPt *cursor, TxtPt *mark, S64 *prefe scope_line_color.w = scope_line_color_t*0.5f; Rng1U64 token_idx_range = scope_n->token_idx_range; Rng1U64 off_range = r1u64(params->text_info->tokens.v[token_idx_range.min].range.min, params->text_info->tokens.v[token_idx_range.max].range.min); - TxtRng txt_range = txt_rng(txt_pt_from_info_off__linear_scan(params->text_info, off_range.min), txt_pt_from_info_off__linear_scan(params->text_info, off_range.max)); + TxtRng txt_range = txt_rng(txt_pt_from_off__linear_scan(params->text_info, params->patches, off_range.min), txt_pt_from_off__linear_scan(params->text_info, params->patches, off_range.max)); //- rjf: single-line scopes (underline) if(txt_range.min.line == txt_range.max.line && contains_1s64(params->line_num_range, txt_range.min.line)) @@ -2615,40 +2604,37 @@ rd_code_slice(RD_CodeSliceParams *params, TxtPt *cursor, TxtPt *mark, S64 *prefe ////////////////////////////// //- rjf: mouse -> expression range info // - TxtRng mouse_expr_rng = {0}; + Rng1U64 mouse_expr_rng = {0}; Vec2F32 mouse_expr_baseline_pos = {0}; String8 mouse_expr = {0}; B32 mouse_expr_is_explicit = 0; - if(ui_hovering(text_container_sig) && contains_1s64(params->line_num_range, mouse_pt.line)) ProfScope("mouse -> expression range") + if(ui_hovering(text_container_sig) && mouse_y_line_idx < (U64)dim_1s64(params->line_num_range)) { - TxtRng selected_rng = txt_rng(*cursor, *mark); - if(!txt_pt_match(*cursor, *mark) && cursor->line == mark->line && - ((txt_pt_less_than(selected_rng.min, mouse_pt) || txt_pt_match(selected_rng.min, mouse_pt)) && - txt_pt_less_than(mouse_pt, selected_rng.max))) + Rng1U64 selected_rng = r1u64(*cursor, *mark); + if(*cursor != *mark && contains_1u64(selected_rng, mouse_off)) { - U64 line_slice_idx = mouse_pt.line-params->line_num_range.min; - String8 line_text = params->line_text[line_slice_idx]; - F32 expr_hoff_px = params->line_num_width_px + fnt_dim_from_tag_size_string(params->font, params->font_size, 0, params->tab_size, str8_prefix(line_text, selected_rng.min.column-1)).x; - result.mouse_expr_rng = mouse_expr_rng = selected_rng; + String8 line_text = params->line_text[mouse_y_line_idx]; + Rng1U64 line_range = params->line_ranges[mouse_y_line_idx]; + Rng1U64 selected_in_line_range = intersect_1u64(line_range, selected_rng); + F32 expr_hoff_px = params->line_num_width_px + fnt_dim_from_tag_size_string(params->font, params->font_size, 0, params->tab_size, str8_prefix(line_text, selected_in_line_range.min)).x; + result.mouse_expr_rng = mouse_expr_rng = selected_in_line_range; mouse_expr_baseline_pos = v2f32(text_container_box->rect.x0+expr_hoff_px, - text_container_box->rect.y0+line_slice_idx*params->line_height_px + params->line_height_px*0.85f); - mouse_expr = str8_substr(line_text, r1u64(selected_rng.min.column-1, selected_rng.max.column-1)); + text_container_box->rect.y0+mouse_y_line_idx*params->line_height_px + params->line_height_px*0.85f); + mouse_expr = str8_substr(line_text, selected_in_line_range); mouse_expr_is_explicit = 1; } else { - U64 line_slice_idx = mouse_pt.line-params->line_num_range.min; - String8 line_text = params->line_text[line_slice_idx]; - TXT_TokenArray line_tokens = params->line_tokens[line_slice_idx]; - Rng1U64 line_range = params->line_ranges[line_slice_idx]; - U64 mouse_pt_off = line_range.min + (mouse_pt.column-1); - Rng1U64 expr_off_rng = txt_expr_off_range_from_line_off_range_string_tokens(mouse_pt_off, line_range, line_text, &line_tokens); + String8 line_text = params->line_text[mouse_y_line_idx]; + TXT_TokenArray line_tokens = params->line_tokens[mouse_y_line_idx]; + Rng1U64 line_range = params->line_ranges[mouse_y_line_idx]; + Rng1U64 expr_off_rng = txt_expr_off_range_from_line_off_range_string_tokens(mouse_off, line_range, line_text, &line_tokens); if(expr_off_rng.max != expr_off_rng.min) { F32 expr_hoff_px = params->line_num_width_px + fnt_dim_from_tag_size_string(params->font, params->font_size, 0, params->tab_size, str8_prefix(line_text, expr_off_rng.min-line_range.min)).x; - result.mouse_expr_rng = mouse_expr_rng = txt_rng(txt_pt(mouse_pt.line, 1+(expr_off_rng.min-line_range.min)), txt_pt(mouse_pt.line, 1+(expr_off_rng.max-line_range.min))); + result.mouse_expr_rng = mouse_expr_rng = expr_off_rng; mouse_expr_baseline_pos = v2f32(text_container_box->rect.x0+expr_hoff_px, - text_container_box->rect.y0+line_slice_idx*params->line_height_px + params->line_height_px*0.85f); + text_container_box->rect.y0+mouse_y_line_idx*params->line_height_px + params->line_height_px*0.85f); mouse_expr = str8_substr(line_text, r1u64(expr_off_rng.min-line_range.min, expr_off_rng.max-line_range.min)); } } @@ -2657,11 +2643,10 @@ rd_code_slice(RD_CodeSliceParams *params, TxtPt *cursor, TxtPt *mark, S64 *prefe ////////////////////////////// //- rjf: mouse -> set global frontend hovered line info // - if(ui_hovering(text_container_sig) && contains_1s64(params->line_num_range, mouse_pt.line) && (ui_mouse().x - text_container_box->rect.x0 < params->line_num_width_px + line_num_padding_px)) + if(ui_hovering(text_container_sig) && mouse_y_line_idx < (U64)dim_1s64(params->line_num_range) && (ui_mouse().x - text_container_box->rect.x0 < params->line_num_width_px + line_num_padding_px)) { - U64 line_slice_idx = mouse_pt.line-params->line_num_range.min; - D_LineList *lines = ¶ms->line_infos[line_slice_idx]; - if(lines->first != 0 && (params->line_vaddrs[line_slice_idx] != 0 || lines->first->v.pt.line == mouse_pt.line)) + D_LineList *lines = ¶ms->line_infos[mouse_y_line_idx]; + if(lines->first != 0 && (params->line_vaddrs[mouse_y_line_idx] != 0 || lines->first->v.pt.line == params->line_num_range.min + mouse_y_line_idx)) { RD_RegsScope(.process = selected_thread_process->handle, .vaddr_range = d_vaddr_range_from_voff_range(selected_thread_module, lines->first->v.voff_range), @@ -2685,10 +2670,9 @@ rd_code_slice(RD_CodeSliceParams *params, TxtPt *cursor, TxtPt *mark, S64 *prefe if(eval.msgs.max_kind == E_MsgKind_Null && (eval_implicit_hover || mouse_expr_is_explicit)) { U64 line_vaddr = 0; - if(contains_1s64(params->line_num_range, mouse_pt.line)) + if(mouse_y_line_idx < (U64)dim_1s64(params->line_num_range)) { - U64 line_idx = mouse_pt.line-params->line_num_range.min; - line_vaddr = params->line_vaddrs[line_idx]; + line_vaddr = params->line_vaddrs[mouse_y_line_idx]; } rd_set_hover_eval(mouse_expr_baseline_pos, mouse_expr); } @@ -2705,9 +2689,9 @@ rd_code_slice(RD_CodeSliceParams *params, TxtPt *cursor, TxtPt *mark, S64 *prefe Vec4F32 color = drop_color; color.w *= 0.2f; Rng2F32 drop_line_rect = r2f32p(top_container_box->rect.x0, - top_container_box->rect.y0 + (mouse_pt.line - params->line_num_range.min) * params->line_height_px, + top_container_box->rect.y0 + mouse_y_line_idx*params->line_height_px, top_container_box->rect.x1, - top_container_box->rect.y0 + (mouse_pt.line - params->line_num_range.min + 1) * params->line_height_px); + top_container_box->rect.y0 + (mouse_y_line_idx+1)*params->line_height_px); R_Rect2DInst *inst = dr_rect(drop_line_rect, color, 0, 0, 1.f); inst->colors[Corner_10] = inst->colors[Corner_11] = v4f32(color.x, color.y, color.z, 0); } @@ -2721,7 +2705,7 @@ rd_code_slice(RD_CodeSliceParams *params, TxtPt *cursor, TxtPt *mark, S64 *prefe struct TxtRngColorPairNode { TxtRngColorPairNode *next; - TxtRng rng; + Rng1U64 range; Vec4F32 color; }; TxtRngColorPairNode *first_txt_rng_color_pair = 0; @@ -2730,16 +2714,16 @@ rd_code_slice(RD_CodeSliceParams *params, TxtPt *cursor, TxtPt *mark, S64 *prefe // rjf: push initial for cursor/mark { TxtRngColorPairNode *n = push_array(scratch.arena, TxtRngColorPairNode, 1); - n->rng = txt_rng(*cursor, *mark); + n->range = r1u64(*cursor, *mark); n->color = ui_color_from_name(s("selection")); SLLQueuePush(first_txt_rng_color_pair, last_txt_rng_color_pair, n); } // rjf: push for ctrlified mouse expr - if(ctrlified && !txt_pt_match(result.mouse_expr_rng.max, result.mouse_expr_rng.min)) UI_Tag(s("pop")) + if(ctrlified && result.mouse_expr_rng.max != result.mouse_expr_rng.min) UI_Tag(s("pop")) { TxtRngColorPairNode *n = push_array(scratch.arena, TxtRngColorPairNode, 1); - n->rng = result.mouse_expr_rng; + n->range = result.mouse_expr_rng; n->color = ui_color_from_name(s("background")); n->color.w *= 0.2f; SLLQueuePush(first_txt_rng_color_pair, last_txt_rng_color_pair, n); @@ -2832,33 +2816,22 @@ rd_code_slice(RD_CodeSliceParams *params, TxtPt *cursor, TxtPt *mark, S64 *prefe // rjf: extra rendering for list(text_range*color) { - U64 prev_line_size = (line_idx > 0) ? params->line_text[line_idx-1].size : 0; - U64 next_line_size = (line_idx+1 < dim_1s64(params->line_num_range)) ? params->line_text[line_idx+1].size : 0; + Rng1U64 prev_line_range = (line_idx > 0) ? params->line_ranges[line_idx-1] : r1u64(0, 0); + Rng1U64 next_line_range = (line_idx+1 < dim_1s64(params->line_num_range)) ? params->line_ranges[line_idx+1] : r1u64(0, 0); for(TxtRngColorPairNode *n = first_txt_rng_color_pair; n != 0; n = n->next) { - TxtRng select_range = n->rng; - TxtRng line_range = txt_rng(txt_pt(line_num, 1), txt_pt(line_num, line_string.size+1)); - TxtRng select_range_in_line = txt_rng_intersect(select_range, line_range); - if(!txt_pt_match(select_range_in_line.min, select_range_in_line.max) && - txt_pt_less_than(select_range_in_line.min, select_range_in_line.max)) + Rng1U64 select_range = n->range; + Rng1U64 select_range_in_line = intersect_1u64(select_range, line_range); + if(select_range_in_line.min < select_range_in_line.max) { - TxtRng prev_line_range = txt_rng(txt_pt(line_num-1, 1), txt_pt(line_num-1, prev_line_size+1)); - TxtRng next_line_range = txt_rng(txt_pt(line_num+1, 1), txt_pt(line_num+1, next_line_size+1)); - TxtRng select_range_in_prev_line = txt_rng_intersect(prev_line_range, select_range); - TxtRng select_range_in_next_line = txt_rng_intersect(next_line_range, select_range); - B32 prev_line_good = (!txt_pt_match(select_range_in_prev_line.min, select_range_in_prev_line.max) && - txt_pt_less_than(select_range_in_prev_line.min, select_range_in_prev_line.max)); - B32 next_line_good = (!txt_pt_match(select_range_in_next_line.min, select_range_in_next_line.max) && - txt_pt_less_than(select_range_in_next_line.min, select_range_in_next_line.max)); - Rng1S64 select_column_range_in_line = - { - (select_range.min.line == line_num) ? select_range.min.column : 1, - (select_range.max.line == line_num) ? select_range.max.column : (S64)(line_string.size+1), - }; + Rng1U64 select_range_in_prev_line = intersect_1u64(prev_line_range, select_range); + Rng1U64 select_range_in_next_line = intersect_1u64(next_line_range, select_range); + B32 prev_line_good = (select_range_in_prev_line.min < select_range_in_prev_line.max); + B32 next_line_good = (select_range_in_next_line.min < select_range_in_next_line.max); Rng1F32 select_column_pixel_off_range = { - fnt_dim_from_tag_size_string(line_box->font, line_box->font_size, 0, params->tab_size, str8_prefix(line_string, select_column_range_in_line.min-1)).x, - fnt_dim_from_tag_size_string(line_box->font, line_box->font_size, 0, params->tab_size, str8_prefix(line_string, select_column_range_in_line.max-1)).x, + fnt_dim_from_tag_size_string(line_box->font, line_box->font_size, 0, params->tab_size, str8_prefix(line_string, select_range_in_line.min - line_range.min)).x, + fnt_dim_from_tag_size_string(line_box->font, line_box->font_size, 0, params->tab_size, str8_prefix(line_string, select_range_in_line.max - line_range.min)).x, }; Rng2F32 select_rect = { @@ -2874,20 +2847,22 @@ rd_code_slice(RD_CodeSliceParams *params, TxtPt *cursor, TxtPt *mark, S64 *prefe } F32 rounded_radius = params->font_size*0.4f; R_Rect2DInst *inst = dr_rect(select_rect, color, rounded_radius, 0, 1); - inst->corner_radii[Corner_00] = !prev_line_good || select_range_in_prev_line.min.column > select_range_in_line.min.column ? rounded_radius : 0.f; - inst->corner_radii[Corner_10] = (!prev_line_good || select_range_in_line.max.column > select_range_in_prev_line.max.column || select_range_in_line.max.column < select_range_in_prev_line.min.column) ? rounded_radius : 0.f; - inst->corner_radii[Corner_01] = (!next_line_good || select_range_in_next_line.min.column > select_range_in_line.min.column || select_range_in_next_line.max.column < select_range_in_line.min.column) ? rounded_radius : 0.f; - inst->corner_radii[Corner_11] = !next_line_good || select_range_in_line.max.column > select_range_in_next_line.max.column ? rounded_radius : 0.f; + Rng1U64 prev_line_selection_off_range = r1u64(select_range_in_prev_line.min - prev_line_range.min, select_range_in_prev_line.max - prev_line_range.min); + Rng1U64 next_line_selection_off_range = r1u64(select_range_in_next_line.min - next_line_range.min, select_range_in_next_line.max - next_line_range.min); + Rng1U64 crnt_line_selection_off_range = r1u64(select_range_in_line.min - line_range.min, select_range_in_line.max - line_range.min); + inst->corner_radii[Corner_00] = !prev_line_good || prev_line_selection_off_range.min > crnt_line_selection_off_range.min ? rounded_radius : 0.f; + inst->corner_radii[Corner_10] = (!prev_line_good || crnt_line_selection_off_range.max > prev_line_selection_off_range.max || crnt_line_selection_off_range.max < prev_line_selection_off_range.min) ? rounded_radius : 0.f; + inst->corner_radii[Corner_01] = (!next_line_good || next_line_selection_off_range.min > crnt_line_selection_off_range.min || next_line_selection_off_range.max < crnt_line_selection_off_range.min) ? rounded_radius : 0.f; + inst->corner_radii[Corner_11] = !next_line_good || crnt_line_selection_off_range.max > next_line_selection_off_range.max ? rounded_radius : 0.f; } } } // rjf: extra rendering for cursor position - if(cursor->line == line_num) + if(line_range.min <= *cursor && *cursor <= line_range.max) { - S64 column = cursor->column; - Vec2F32 advance = fnt_dim_from_tag_size_string(line_box->font, line_box->font_size, 0, params->tab_size, str8_prefix(line_string, column-1)); - F32 cursor_y = line_box->rect.y0-params->font_size*0.125f; + Vec2F32 advance = fnt_dim_from_tag_size_string(line_box->font, line_box->font_size, 0, params->tab_size, str8_prefix(line_string, *cursor - line_range.min)); + F32 cursor_y = text_container_box->rect.y0 + line_idx*params->line_height_px - params->font_size*0.125f; F32 cursor_y__animated = ui_anim(ui_key_from_stringf(text_container_box->key, "cursor_y_px"), cursor_y); F32 cursor_off_pixels = advance.x; F32 cursor_off_pixels__animated = ui_anim(ui_key_from_stringf(text_container_box->key, "cursor_off_px"), cursor_off_pixels); @@ -2978,7 +2953,7 @@ rd_code_slice(RD_CodeSliceParams *params, TxtPt *cursor, TxtPt *mark, S64 *prefe } internal RD_CodeSliceSignal -rd_code_slicef(RD_CodeSliceParams *params, TxtPt *cursor, TxtPt *mark, S64 *preferred_column, char *fmt, ...) +rd_code_slicef(RD_CodeSliceParams *params, U64 *cursor, U64 *mark, S64 *preferred_column, char *fmt, ...) { Temp scratch = scratch_begin(0, 0); va_list args; @@ -2991,179 +2966,11 @@ rd_code_slicef(RD_CodeSliceParams *params, TxtPt *cursor, TxtPt *mark, S64 *pref } internal B32 -rd_do_txt_controls(TXT_TextInfo *info, String8 data, U64 line_count_per_page, TxtPt *cursor, TxtPt *mark, S64 *preferred_column) +rd_do_txt_controls(TXT_TextInfo *info, String8 data, TXT_PatchList *patches, U64 line_count_per_page, TxtPt *cursor, TxtPt *mark, S64 *preferred_column) { Temp scratch = scratch_begin(0, 0); B32 change = 0; - for(UI_Event *evt = 0; ui_next_event(&evt);) - { - if(evt->kind != UI_EventKind_Navigate && evt->kind != UI_EventKind_Edit) - { - continue; - } - B32 taken = 0; - String8 line = txt_string_from_info_data_line_num(info, data, cursor->line); - UI_TxtOp single_line_op = ui_single_line_txt_op_from_event(scratch.arena, evt, line, *cursor, *mark); - - //- rjf: invalid single-line op or endpoint units => try multiline - if(evt->delta_unit == UI_EventDeltaUnit_Whole || single_line_op.flags & UI_TxtOpFlag_Invalid) - { - U64 line_count = info->lines_count; - String8 prev_line = txt_string_from_info_data_line_num(info, data, cursor->line-1); - String8 next_line = txt_string_from_info_data_line_num(info, data, cursor->line+1); - Vec2S32 delta = evt->delta_2s32; - - //- rjf: wrap lines right - if(evt->delta_unit != UI_EventDeltaUnit_Whole && delta.x > 0 && cursor->column == line.size+1 && cursor->line+1 <= line_count) - { - cursor->line += 1; - cursor->column = 1; - *preferred_column = 1; - change = 1; - taken = 1; - } - - //- rjf: wrap lines left - if(evt->delta_unit != UI_EventDeltaUnit_Whole && delta.x < 0 && cursor->column == 1 && cursor->line-1 >= 1) - { - cursor->line -= 1; - cursor->column = prev_line.size+1; - *preferred_column = prev_line.size+1; - change = 1; - taken = 1; - } - - //- rjf: movement down (plain) - if(evt->delta_unit == UI_EventDeltaUnit_Char && delta.y > 0 && cursor->line+1 <= line_count) - { - cursor->line += 1; - cursor->column = Min(*preferred_column, next_line.size+1); - change = 1; - taken = 1; - } - - //- rjf: movement up (plain) - if(evt->delta_unit == UI_EventDeltaUnit_Char && delta.y < 0 && cursor->line-1 >= 1) - { - cursor->line -= 1; - cursor->column = Min(*preferred_column, prev_line.size+1); - change = 1; - taken = 1; - } - - //- rjf: movement down (chunk) - if(evt->delta_unit == UI_EventDeltaUnit_Word && delta.y > 0 && cursor->line+1 <= line_count) - { - for(S64 line_num = cursor->line+1; line_num <= line_count; line_num += 1) - { - String8 line = txt_string_from_info_data_line_num(info, data, line_num); - U64 line_size = line.size; - if(line_size == 0) - { - cursor->line = line_num; - cursor->column = 1; - break; - } - else if(line_num == line_count) - { - cursor->line = line_num; - cursor->column = line_size+1; - } - } - change = 1; - taken = 1; - } - - //- rjf: movement up (chunk) - if(evt->delta_unit == UI_EventDeltaUnit_Word && delta.y < 0 && cursor->line-1 >= 1) - { - for(S64 line_num = cursor->line-1; line_num > 0; line_num -= 1) - { - String8 line = txt_string_from_info_data_line_num(info, data, line_num); - U64 line_size = line.size; - if(line_size == 0) - { - cursor->line = line_num; - cursor->column = 1; - break; - } - else if(line_num == 1) - { - cursor->line = line_num; - cursor->column = 1; - } - } - change = 1; - taken = 1; - } - - //- rjf: movement down (page) - if(evt->delta_unit == UI_EventDeltaUnit_Page && delta.y > 0) - { - cursor->line += line_count_per_page; - cursor->column = 1; - cursor->line = Clamp(1, cursor->line, line_count); - change = 1; - taken = 1; - } - - //- rjf: movement up (page) - if(evt->delta_unit == UI_EventDeltaUnit_Page && delta.y < 0) - { - cursor->line -= line_count_per_page; - cursor->column = 1; - cursor->line = Clamp(1, cursor->line, line_count); - change = 1; - taken = 1; - } - - //- rjf: movement to endpoint (+) - if(evt->delta_unit == UI_EventDeltaUnit_Whole && (delta.y > 0 || delta.x > 0)) - { - *cursor = txt_pt(line_count, info->lines_count ? dim_1u64(info->lines_ranges[info->lines_count-1])+1 : 1); - change = 1; - taken = 1; - } - - //- rjf: movement to endpoint (-) - if(evt->delta_unit == UI_EventDeltaUnit_Whole && (delta.y < 0 || delta.x < 0)) - { - *cursor = txt_pt(1, 1); - change = 1; - taken = 1; - } - - //- rjf: stick mark to cursor, when we don't want to keep it in the same spot - if(!(evt->flags & UI_EventFlag_KeepMark)) - { - *mark = *cursor; - } - } - - //- rjf: valid single-line op => do single-line op - else - { - *cursor = single_line_op.cursor; - *mark = single_line_op.mark; - *preferred_column = cursor->column; - change = 1; - taken = 1; - } - - //- rjf: copy - if(evt->flags & UI_EventFlag_Copy) - { - String8 text = txt_string_from_info_data_txt_rng(info, data, txt_rng(*cursor, *mark)); - wm_set_clipboard_text(text); - taken = 1; - } - - //- rjf: consume - if(taken) - { - ui_eat_event(evt); - } - } + scratch_end(scratch); return change; @@ -3610,6 +3417,52 @@ rd_cell(RD_CellParams *params, String8 string) } } + ////////////////////////////// + //- rjf: build lock + // + if(params->flags & RD_CellFlag_Lock && !is_focus_active && !is_focus_active_disabled) UI_Parent(box) UI_Focus(UI_FocusKind_Off) + { + // TODO(rjf): @hack + { + ui_spacer(ui_em(0.5f, 1.f)); + } + B32 is_locked = (params->lock_out && params->lock_out[0]); + UI_PrefWidth(ui_em(2.f, 1.f)) + UI_TagF(".") + UI_TagF(is_locked ? "pop" : "weak") + UI_TagF("implicit") + UI_Column + UI_Padding(ui_pct(1, 0)) + UI_PrefHeight(ui_em(2.f, 1.f)) + UI_CornerRadius(ui_top_font_size()*0.5f) + RD_Font(RD_FontSlot_Icons) + UI_TextAlignment(UI_TextAlign_Center) + { + UI_Box *lock_box = ui_build_box_from_stringf(UI_BoxFlag_DrawText| + UI_BoxFlag_DrawHotEffects| + UI_BoxFlag_DrawBorder| + UI_BoxFlag_DrawBackground| + UI_BoxFlag_DisableFocusOverlay| + UI_BoxFlag_DisableFocusBorder| + UI_BoxFlag_Clickable, + "%S###lock", rd_icon_kind_text_table[is_locked ? RD_IconKind_Locked : RD_IconKind_Unlocked]); + UI_Signal sig = ui_signal_from_box(lock_box); + if(ui_hovering(sig)) UI_Tooltip RD_Font(RD_FontSlot_Main) + { + ui_state->tooltip_anchor_key = lock_box->key; + UI_PrefWidth(ui_children_sum(1)) UI_Row + { + UI_PrefWidth(ui_text_dim(10, 1)) ui_label(is_locked ? s("Unlock Location") : s("Lock Location")); + rd_cmd_binding_buttons(rd_cmd_kind_info_table[RD_CmdKind_ToggleLock].string, s(""), 1, RD_CmdBindingButtonFlag_NoEdit); + } + } + if(ui_pressed(sig) && params->lock_out) + { + params->lock_out[0] ^= 1; + } + } + } + ////////////////////////////// //- rjf: build left-hand-side container box // @@ -4043,8 +3896,8 @@ rd_cell(RD_CellParams *params, String8 string) { ui_kill_action(); } - params->cursor[0] = txt_pt(1, edit_string.size+1); - params->mark[0] = txt_pt(1, 1); + params->cursor[0] = edit_string.size; + params->mark[0] = 0; focus_started = 1; } } @@ -4080,7 +3933,7 @@ rd_cell(RD_CellParams *params, String8 string) } // rjf: map this action to an op - UI_TxtOp op = ui_single_line_txt_op_from_event(scratch.arena, evt, edit_string, params->cursor[0], params->mark[0]); + UI_TxtOp op = ui_single_line_txt_op_from_event(scratch.arena, evt, edit_string, r1u64(0, edit_string.size), params->cursor[0], params->mark[0]); // rjf: any valid *additive* op & autocomplete hint? -> perform autocomplete first, then re-compute op if(!(evt->flags & UI_EventFlag_Delete) && autocomplete_hint_string.size != 0) @@ -4088,27 +3941,27 @@ rd_cell(RD_CellParams *params, String8 string) CFG_Node *window = cfg_node_from_id(rd_regs()->window); RD_WindowState *ws = rd_window_state_from_cfg(window); RD_AutocompCursorInfo *autocomp_cursor_info = &ws->autocomp_cursor_info; - String8 new_string = ui_push_string_replace_range(scratch.arena, edit_string, r1s64(autocomp_cursor_info->replaced_range.min+1, autocomp_cursor_info->replaced_range.max+1), autocomplete_hint_string); + String8 new_string = ui_push_string_replace_range(scratch.arena, edit_string, autocomp_cursor_info->replaced_range, autocomplete_hint_string); new_string.size = Min(params->edit_buffer_size, new_string.size); MemoryCopy(params->edit_buffer, new_string.str, new_string.size); params->edit_string_size_out[0] = new_string.size; - params->cursor[0] = params->mark[0] = txt_pt(1, 1+autocomp_cursor_info->replaced_range.min+autocomplete_hint_string.size); + params->cursor[0] = params->mark[0] = autocomp_cursor_info->replaced_range.min+autocomplete_hint_string.size; edit_string = str8(params->edit_buffer, params->edit_string_size_out[0]); - op = ui_single_line_txt_op_from_event(scratch.arena, evt, edit_string, params->cursor[0], params->mark[0]); + op = ui_single_line_txt_op_from_event(scratch.arena, evt, edit_string, r1u64(0, edit_string.size), params->cursor[0], params->mark[0]); MemoryZeroStruct(&autocomplete_hint_string); } // rjf: perform replace range - if(!txt_pt_match(op.range.min, op.range.max) || op.replace.size != 0) + if(op.range.min != op.range.max || op.replace.size != 0) { - String8 new_string = ui_push_string_replace_range(scratch.arena, edit_string, r1s64(op.range.min.column, op.range.max.column), op.replace); + String8 new_string = ui_push_string_replace_range(scratch.arena, edit_string, op.range, op.replace); new_string.size = Min(params->edit_buffer_size, new_string.size); MemoryCopy(params->edit_buffer, new_string.str, new_string.size); params->edit_string_size_out[0] = new_string.size; } // rjf: perform copy - if(op.flags & UI_TxtOpFlag_Copy) + if(evt->flags & UI_EventFlag_Copy) { wm_set_clipboard_text(op.copy); } @@ -4186,9 +4039,9 @@ rd_cell(RD_CellParams *params, String8 string) CFG_Node *window = cfg_node_from_id(rd_regs()->window); RD_WindowState *ws = rd_window_state_from_cfg(window); RD_AutocompCursorInfo *autocomp_cursor_info = &ws->autocomp_cursor_info; - String8 autocomplete_append_string = str8_skip(autocomplete_hint_string, params->cursor->column-1 - autocomp_cursor_info->replaced_range.min); + String8 autocomplete_append_string = str8_skip(autocomplete_hint_string, params->cursor[0] - autocomp_cursor_info->replaced_range.min); U64 off = 0; - U64 cursor_off = params->cursor->column-1; + U64 cursor_off = params->cursor[0]; DR_FStrNode *prev_n = 0; for(DR_FStrNode *n = edit_string_fstrs.first; n != 0; n = n->next) { @@ -4255,7 +4108,7 @@ rd_cell(RD_CellParams *params, String8 string) ////////////////////////////// //- rjf: build scrolled contents // - TxtPt mouse_pt = {0}; + U64 mouse_off = {0}; F32 cursor_off = 0; if(scrollable_box != &ui_nil_box) UI_Parent(scrollable_box) { @@ -4292,9 +4145,8 @@ rd_cell(RD_CellParams *params, String8 string) { font = rd_font_from_slot(RD_FontSlot_Code); } - U64 mouse_pt_off = fnt_char_pos_from_tag_size_string_p(font, font_size, 0, ui_top_tab_size(), edit_string, text2mouse.x); - mouse_pt = txt_pt(1, 1+mouse_pt_off); - cursor_off = fnt_dim_from_tag_size_string(ui_top_font(), ui_top_font_size(), 0, ui_top_tab_size(), str8_prefix(edit_string, params->cursor->column-1)).x; + mouse_off = fnt_char_pos_from_tag_size_string_p(font, font_size, 0, ui_top_tab_size(), edit_string, text2mouse.x); + cursor_off = fnt_dim_from_tag_size_string(ui_top_font(), ui_top_font_size(), 0, ui_top_tab_size(), str8_prefix(edit_string, params->cursor[0])).x; } } @@ -4305,13 +4157,13 @@ rd_cell(RD_CellParams *params, String8 string) { if(ui_pressed(sig)) { - params->mark[0] = mouse_pt; + params->mark[0] = mouse_off; } - params->cursor[0] = mouse_pt; + params->cursor[0] = mouse_off; } if(!is_focus_active && is_focus_active_disabled && ui_pressed(sig)) { - params->cursor[0] = params->mark[0] = mouse_pt; + params->cursor[0] = params->mark[0] = mouse_off; } ////////////////////////////// diff --git a/src/raddbg/raddbg_widgets.h b/src/raddbg/raddbg_widgets.h index 6811c1f0..17dad246 100644 --- a/src/raddbg/raddbg_widgets.h +++ b/src/raddbg/raddbg_widgets.h @@ -27,19 +27,20 @@ enum //- rjf: extra button extensions RD_CellFlag_EmptyEditButton = (1<<6), RD_CellFlag_RevertButton = (1<<7), + RD_CellFlag_Lock = (1<<8), //- rjf: behavior - RD_CellFlag_DisableEdit = (1<<8), - RD_CellFlag_KeyboardClickable = (1<<9), - RD_CellFlag_SingleClickActivate = (1<<10), + RD_CellFlag_DisableEdit = (1<<9), + RD_CellFlag_KeyboardClickable = (1<<10), + RD_CellFlag_SingleClickActivate = (1<<11), //- rjf: contents description - RD_CellFlag_CodeContents = (1<<11), + RD_CellFlag_CodeContents = (1<<12), //- rjf: appearance - RD_CellFlag_Border = (1<<12), - RD_CellFlag_NoBackground = (1<<13), - RD_CellFlag_Button = (1<<14), + RD_CellFlag_Border = (1<<13), + RD_CellFlag_NoBackground = (1<<14), + RD_CellFlag_Button = (1<<15), }; typedef struct RD_CellParams RD_CellParams; @@ -59,6 +60,9 @@ struct RD_CellParams //- rjf: expander r/w info B32 *expanded_out; + //- rjf: lock r/w info + B32 *lock_out; + //- rjf: toggle-switch r/w info B32 *toggled_out; @@ -72,8 +76,8 @@ struct RD_CellParams B32 *revert_out; //- rjf: text editing r/w info - TxtPt *cursor; - TxtPt *mark; + U64 *cursor; + U64 *mark; U8 *edit_buffer; U64 edit_buffer_size; U64 *edit_string_size_out; @@ -108,6 +112,7 @@ struct RD_CodeSliceParams D_LineList *line_infos; DI_KeyList relevant_dbgi_keys; TXT_TextInfo *text_info; + TXT_PatchList *patches; String8 text_data; // rjf: visual parameters @@ -127,8 +132,8 @@ typedef struct RD_CodeSliceSignal RD_CodeSliceSignal; struct RD_CodeSliceSignal { UI_Signal base; - TxtPt mouse_pt; - TxtRng mouse_expr_rng; + U64 mouse_off; + Rng1U64 mouse_expr_rng; }; //////////////////////////////// @@ -171,10 +176,10 @@ internal UI_Signal rd_icon_buttonf(RD_IconKind kind, FuzzyMatchRangeList *matche internal UI_BOX_CUSTOM_DRAW(rd_code_slice_text_draw_extensions); internal UI_BOX_CUSTOM_DRAW(rd_thread_box_draw_extensions); internal UI_BOX_CUSTOM_DRAW(rd_bp_box_draw_extensions); -internal RD_CodeSliceSignal rd_code_slice(RD_CodeSliceParams *params, TxtPt *cursor, TxtPt *mark, S64 *preferred_column, String8 string); -internal RD_CodeSliceSignal rd_code_slicef(RD_CodeSliceParams *params, TxtPt *cursor, TxtPt *mark, S64 *preferred_column, char *fmt, ...); +internal RD_CodeSliceSignal rd_code_slice(RD_CodeSliceParams *params, U64 *cursor, U64 *mark, S64 *preferred_column, String8 string); +internal RD_CodeSliceSignal rd_code_slicef(RD_CodeSliceParams *params, U64 *cursor, U64 *mark, S64 *preferred_column, char *fmt, ...); -internal B32 rd_do_txt_controls(TXT_TextInfo *info, String8 data, U64 line_count_per_page, TxtPt *cursor, TxtPt *mark, S64 *preferred_column); +internal B32 rd_do_txt_controls(TXT_TextInfo *info, String8 data, TXT_PatchList *patches, U64 line_count_per_page, TxtPt *cursor, TxtPt *mark, S64 *preferred_column); //////////////////////////////// //~ rjf: UI Widgets: Fancy Labels diff --git a/src/raddbg/tests/raddbg_tests.c b/src/raddbg/tests/raddbg_tests.c index 231eb0a2..2a6b7de7 100644 --- a/src/raddbg/tests/raddbg_tests.c +++ b/src/raddbg/tests/raddbg_tests.c @@ -72,7 +72,7 @@ rd_test__exemplar_test_finish(Arena *arena, TestCtx *ctx, String8List test_log_s } str8_list_push(scratch.arena, &exemplar_data_lines_sanitized, line_trimmed); } - exemplar_data = str8_list_join(scratch.arena, &exemplar_data_lines_sanitized, &(StringJoin){.sep = s("\n")}); + exemplar_data = str8_list_join(scratch.arena, &exemplar_data_lines_sanitized, &(StringJoin){.sep = s("\n"), .post = s("\n")}); // rjf: if exemplar data is empty -> just save our output as the new exemplar if(exemplar_data.size == 0) diff --git a/src/rdi/rdi.mdesk b/src/rdi/rdi.mdesk index 36509832..ff9bbd35 100644 --- a/src/rdi/rdi.mdesk +++ b/src/rdi/rdi.mdesk @@ -1213,7 +1213,7 @@ RDI_EvalOpTable: {ConstU128 15 16 0 1} {ConstU256 16 32 0 1} {ConstU512 17 64 0 1} - {ConstString 18 1 0 1} + {ConstString 18 8 0 1} {Abs 19 2 1 1} {Neg 20 2 1 1} {Add 21 2 2 1} @@ -1227,15 +1227,15 @@ RDI_EvalOpTable: {BitOr 29 2 2 1} {BitXor 30 2 2 1} {BitNot 31 2 1 1} - {LogAnd 32 1 2 1 FirstLogical} - {LogOr 33 1 2 1} - {LogNot 34 1 1 1} - {EqEq 35 1 2 1} - {NtEq 36 1 2 1} - {LsEq 37 1 2 1} - {GrEq 38 1 2 1} - {Less 39 1 2 1} - {Grtr 40 1 2 1 LastLogical} + {LogAnd 32 2 2 1 FirstLogical} + {LogOr 33 2 2 1} + {LogNot 34 2 1 1} + {EqEq 35 2 2 1} + {NtEq 36 2 2 1} + {LsEq 37 2 2 1} + {GrEq 38 2 2 1} + {Less 39 2 2 1} + {Grtr 40 2 2 1 LastLogical} {Trunc 41 1 1 1} {TruncSigned 42 1 1 1} {Convert 43 2 1 1} diff --git a/src/rdi/rdi_local.c b/src/rdi/rdi_local.c index 6e2fc0f6..8c443386 100644 --- a/src/rdi/rdi_local.c +++ b/src/rdi/rdi_local.c @@ -76,6 +76,15 @@ fully_qualified_from_rdi_string_and_container(Arena *arena, RDI_Parsed *rdi, U32 Temp scratch = scratch_begin(&arena, 1); String8List parts = {0}; { + typedef struct VisitedNode VisitedNode; + struct VisitedNode + { + VisitedNode *next; + U32 idx; + RDI_ContainerFlags flags; + }; + U64 visited_slots_count = 6; + VisitedNode **visited_slots = push_array(scratch.arena, VisitedNode *, visited_slots_count); str8_list_push(scratch.arena, &parts, str8_from_rdi_string_idx(rdi, name_string_idx)); RDI_ContainerFlags container_flags = start_container_flags; RDI_ContainerFlags next_container_flags = 0; @@ -83,6 +92,36 @@ fully_qualified_from_rdi_string_and_container(Arena *arena, RDI_Parsed *rdi, U32 container_idx != 0; container_idx = next_container_idx, container_flags = next_container_flags) { + // rjf: determine if we visited this container already - add this container if not + B32 visited_already = 0; + { + U64 hash = u64_hash_from_seed_str8(0, str8_struct(&container_idx)); + hash = u64_hash_from_seed_str8(hash, str8_struct(&container_flags)); + U64 slot_idx = hash%visited_slots_count; + for EachNode(n, VisitedNode, visited_slots[slot_idx]) + { + if(n->idx == container_idx && n->flags == container_flags) + { + visited_already = 1; + break; + } + } + if(!visited_already) + { + VisitedNode *n = push_array(scratch.arena, VisitedNode, 1); + SLLStackPush(visited_slots[slot_idx], n); + n->idx = container_idx; + n->flags = container_flags; + } + } + + // rjf: early-out if we found a cycle + if(visited_already) + { + break; + } + + // rjf: push this container's string next_container_idx = 0; next_container_flags = 0; switch(container_flags & RDI_ContainerFlag_KindMask) diff --git a/src/rdi_from_dwarf/rdi_from_dwarf.c b/src/rdi_from_dwarf/rdi_from_dwarf.c index c8f88b84..a9661251 100644 --- a/src/rdi_from_dwarf/rdi_from_dwarf.c +++ b/src/rdi_from_dwarf/rdi_from_dwarf.c @@ -1623,6 +1623,7 @@ d2r_convert(Arena *arena, D2R_ConvertParams *params) { //- rjf: try to find an already-computed hash for this task U64 already_computed_hash = 0; + U64 already_computed_dependency_count = 0; B32 found_already_computed_hash = 0; { U64 off_hash = u64_hash_from_str8(str8_struct(&top_task->start_off)); @@ -1634,6 +1635,7 @@ d2r_convert(Arena *arena, D2R_ConvertParams *params) { found_already_computed_hash = 1; already_computed_hash = n->dst_hash; + already_computed_dependency_count = n->dependency_count; break; } } @@ -1643,6 +1645,7 @@ d2r_convert(Arena *arena, D2R_ConvertParams *params) if(found_already_computed_hash) { top_task->hash = already_computed_hash; + top_task->dependency_count = already_computed_dependency_count; } //- rjf: determine if we're done with this hash @@ -1791,8 +1794,9 @@ d2r_convert(Arena *arena, D2R_ConvertParams *params) U64 info_off_hash = u64_hash_from_str8(str8_struct(&top_task->start_off)); U64 info_off_slot_idx = info_off_hash%unit_deduped_tag_maps[top_task->unit_idx].slots_count; D2R_UnitDedupedTagNode *n = push_array(scratch.arena, D2R_UnitDedupedTagNode, 1); - n->src_info_off = top_task->start_off; - n->dst_hash = top_task->hash; + n->src_info_off = top_task->start_off; + n->dst_hash = top_task->hash; + n->dependency_count = top_task->dependency_count; for(B32 inserted = 0; !inserted;) { U64 slot_head_val = ins_atomic_u64_eval(&unit_deduped_tag_maps[origin_unit_idx].slots[info_off_slot_idx]); diff --git a/src/rdi_from_dwarf/rdi_from_dwarf.h b/src/rdi_from_dwarf/rdi_from_dwarf.h index 85bb78ad..83e0adb2 100644 --- a/src/rdi_from_dwarf/rdi_from_dwarf.h +++ b/src/rdi_from_dwarf/rdi_from_dwarf.h @@ -45,6 +45,7 @@ struct D2R_UnitDedupedTagNode D2R_UnitDedupedTagNode *next; U64 src_info_off; U64 dst_hash; + U64 dependency_count; }; typedef struct D2R_UnitDedupedTagMap D2R_UnitDedupedTagMap; diff --git a/src/rdi_from_dwarf/tests/rdi_from_dwarf_tests.c b/src/rdi_from_dwarf/tests/rdi_from_dwarf_tests.c index f9a069cd..36209f00 100644 --- a/src/rdi_from_dwarf/tests/rdi_from_dwarf_tests.c +++ b/src/rdi_from_dwarf/tests/rdi_from_dwarf_tests.c @@ -64,7 +64,7 @@ SkippedTest(d2r_regressions) } } -SkippedTest(d2r_determinism) +Test(d2r_determinism) { U64 num_repeats_per_bin = 16; String8 radbin_path = test_build_exe_path(arena, s("radbin")); diff --git a/src/scratch/ryan_scratch.c b/src/scratch/ryan_scratch.c index 8c19eaf3..41a5572d 100644 --- a/src/scratch/ryan_scratch.c +++ b/src/scratch/ryan_scratch.c @@ -34,28 +34,64 @@ internal void entry_point(CmdLine *cmdline) { + E2_ConsTypeMap *cons_types = e2_cons_type_map_alloc(); + e2_select_cons_type_map(cons_types); String8 strings[] = { - s("[123]"), + // (A)B + // (A) & B + // (A) + // int & B + // (1 + (int)&B) + s("foo!bar"), + s("int32(*)(int32, int32)"), + s("int32 *(*)(int32, int32)"), + s("int32 **(*)(int32, int32)"), + s("123(1, 2, 3)"), + s("int32 (*) [100]"), + s("(3 * 4) + 2"), + s("cast (float32) 123"), + s("int32 *"), + s("3 * 4 + 2"), + s("int32[100]"), + s("3 * 4"), s("foo"), s("foo.bar"), + s("[123]"), s("123[456]"), + s("2 + (3 * 4)"), + s("int32 == int32"), + s("123, 456"), + s("222.f"), + s("123 as float32"), + s("cast float32 123"), + s("(int32 *)123"), + s("bar(a, b) = a + b"), + s("foo = 123"), + s("1 > 2"), + s("1 ? 123 : 456"), + s("0 ? 123 : 456"), + s("1 ? \"Test\" : 888"), + s("'a'"), + s("'b'"), + s("(float32)222"), + s("(float64)222"), + s("222.0"), s("123"), s("123 + 456"), s("!1"), s("!0"), s("1 + 1 + 1"), s("40 / 0"), - s("3 * 4"), - s("3 * 4 + 2"), - s("(3 * 4) + 2"), - s("2 + (3 * 4)"), s("123 | 456"), s("123 + "), s("-123"), s("sizeof 123"), - s("1 ? 123 : 456"), - s("123(1, 2, 3)"), +#if 0 + // TODO(rjf): these are ambiguous with regular C type expressions with a naive parse: + // s("float32(111)"), + // s("float64(111)"), +#endif }; for EachElement(idx, strings) { @@ -66,28 +102,21 @@ entry_point(CmdLine *cmdline) E2_Expr *expr = &e2_expr_nil; { E2_ParseState state = {0}; - E2_ExprMap expr_map = {0}; - E2_Expr *access_expr = &e2_expr_nil; + B32 identifier_is_type = 0; for(;;) { - E2_Parse parse = e2_parse_from_string(scratch.arena, &state, &expr_map, access_expr, strings[idx]); + E2_Parse parse = e2_parse_from_string(scratch.arena, &state, identifier_is_type, E2_LangKind_CLike, strings[idx]); + identifier_is_type = 0; expr = parse.expr; - access_expr = &e2_expr_nil; for EachNode(n, E2_Msg, parse.msgs.first) { str8_list_push(scratch.arena, &msgs, n->string); } - if(parse.status == E2_ParseStatus_MissedIdentifierResolution) + if(parse.status == E2_ParseStatus_CheckIdentifierIsType) { - e2_expr_map_push(scratch.arena, &expr_map, parse.missed_identifier, e2_expr_const_u64_or_smaller(scratch.arena, 123)); - } - if(parse.status == E2_ParseStatus_IndexAccess) - { - access_expr = e2_expr_const_u64_or_smaller(scratch.arena, 111); - } - if(parse.status == E2_ParseStatus_MemberAccess) - { - access_expr = e2_expr_const_u64_or_smaller(scratch.arena, 456); + identifier_is_type = identifier_is_type || str8_match(parse.identifier, s("int32"), 0); + identifier_is_type = identifier_is_type || str8_match(parse.identifier, s("float32"), 0); + identifier_is_type = identifier_is_type || str8_match(parse.identifier, s("float64"), 0); } if(e2_parse_status_is_terminal(parse.status)) { @@ -96,8 +125,71 @@ entry_point(CmdLine *cmdline) } } - // rjf: expr -> bytecode - String8 bytecode = e2_bytecode_from_expr(scratch.arena, expr); + // rjf: expr -> ir tree + E2_IRNode *irtree = &e2_irnode_nil; + { + E2_IRNode *resolve_result = &e2_irnode_nil; + E2_Val compile_time_eval_result = {0}; + E2_CompileState state = {0}; + for(;;) + { + E2_Compile compile = e2_compile_from_expr(scratch.arena, &state, resolve_result, compile_time_eval_result, expr); + irtree = compile.irtree; + resolve_result = &e2_irnode_nil; + if(compile.status == E2_CompileStatus_MissedIdentifierResolution) + { + E2_IRNode *irnode = &e2_irnode_nil; + if(str8_match(compile.identifier, s("float32"), 0)) + { + irnode = e2_irnode_type(scratch.arena, e2_type_key_basic(E2_TypeKind_F32)); + } + else if(str8_match(compile.identifier, s("float64"), 0)) + { + irnode = e2_irnode_type(scratch.arena, e2_type_key_basic(E2_TypeKind_F64)); + } + else if(str8_match(compile.identifier, s("int32"), 0)) + { + irnode = e2_irnode_type(scratch.arena, e2_type_key_basic(E2_TypeKind_S32)); + } + else if(str8_match(compile.identifier, s("int64"), 0)) + { + irnode = e2_irnode_type(scratch.arena, e2_type_key_basic(E2_TypeKind_S64)); + } + else + { + irnode = e2_irnode_const_u64_or_smaller(scratch.arena, 123); + } + resolve_result = irnode; + } + if(compile.status == E2_CompileStatus_MemberAccess) + { + resolve_result = e2_irnode_const_u64_or_smaller(scratch.arena, 456); + } + if(compile.status == E2_CompileStatus_IndexAccess) + { + resolve_result = e2_irnode_const_u64_or_smaller(scratch.arena, 111); + } + if(compile.status == E2_CompileStatus_Call) + { + resolve_result = e2_irnode_const_u64_or_smaller(scratch.arena, 123456); + } + if(compile.status == E2_CompileStatus_CompileTimeEval) + { + String8 bytecode = e2_bytecode_from_irnode(scratch.arena, irtree); + E2_InterpState interp_state = {0}; + E2_SpaceMap space_map = {0}; + E2_Interp interp = e2_interp_from_bytecode(scratch.arena, &interp_state, &space_map, bytecode); + compile_time_eval_result = interp.val; + } + if(e2_compile_status_is_terminal(compile.status)) + { + break; + } + } + } + + // rjf: ir tree -> bytecode + String8 bytecode = e2_bytecode_from_irnode(scratch.arena, irtree); // rjf: bytecode -> value E2_Val val = {0}; @@ -124,12 +216,18 @@ entry_point(CmdLine *cmdline) String8 msgs_string = str8_list_join(scratch.arena, &msgs, &join); // rjf: log - printf("%.*s -> %I64d *%s* %s%.*s\n", str8_varg(strings[idx]), - val.s64, - expr->mode == E2_Mode_Type ? "type" : - expr->mode == E2_Mode_Value ? "value" : - "address", - msgs_string.size != 0 ? " // " : "", str8_varg(msgs_string)); + String8 log = str8f(scratch.arena, "%S -> %I64d (f32: %f) (f64: %f) *%s* (type: %S) %s%S\n", + strings[idx], + val.s64, + val.f32, + val.f64, + irtree->mode == E2_Mode_Type ? "type" : + irtree->mode == E2_Mode_Value ? "value" : + "address", + e2_string_from_type_key(scratch.arena, irtree->type_key), + msgs_string.size != 0 ? " // " : "", msgs_string); + raddbg_log("%S", log); + printf("%.*s", str8_varg(log)); fflush(stdout); scratch_end(scratch); diff --git a/src/symbol_server/symbol_server.h b/src/symbol_server/symbol_server.h index f41b9cc2..2d9951bb 100644 --- a/src/symbol_server/symbol_server.h +++ b/src/symbol_server/symbol_server.h @@ -11,6 +11,10 @@ typedef enum SMSV_Status } SMSV_Status; +#if !defined(NEED_ASYNC) +# define NEED_ASYNC 1 +#endif + internal void smsv_init(void); internal void smsv_async_tick(void); internal String8 smsv_cache_path(void); diff --git a/src/text/generated/text.meta.c b/src/text/generated/text.meta.c index e826c980..422e6e20 100644 --- a/src/text/generated/text.meta.c +++ b/src/text/generated/text.meta.c @@ -157,8 +157,8 @@ str8_lit_comp("->"), TXT_TokenizerRule txt_tokenizer_rules__c[7] = { -{TXT_TokenKind_Comment, str8_lit_comp("//"), str8_lit_comp("\n"), 0, 0, 1, 0}, -{TXT_TokenKind_Comment, str8_lit_comp("/*"), str8_lit_comp("*/"), 2, 0, 0, 0}, +{TXT_TokenKind_LineComment, str8_lit_comp("//"), str8_lit_comp("\n"), 0, 0, 1, 0}, +{TXT_TokenKind_BlockComment, str8_lit_comp("/*"), str8_lit_comp("*/"), 2, 0, 0, 0}, {TXT_TokenKind_Meta, str8_lit_comp("#"), str8_lit_comp("\n"), 0, 0, 1, 0}, {TXT_TokenKind_String, str8_lit_comp("\""), str8_lit_comp("\""), 1, 0, 1, 0}, {TXT_TokenKind_String, str8_lit_comp("'"), str8_lit_comp("'"), 1, 0, 1, 0}, diff --git a/src/text/text.c b/src/text/text.c index 2e414aeb..f7f7ff3f 100644 --- a/src/text/text.c +++ b/src/text/text.c @@ -123,6 +123,19 @@ txt_token_array_from_list(Arena *arena, TXT_TokenList *list) return array; } +//////////////////////////////// +//~ rjf: Patch Functions + +internal void +txt_patch_list_push_new(Arena *arena, TXT_PatchList *list, Rng1U64 range, String8 replace) +{ + TXT_PatchNode *n = push_array(arena, TXT_PatchNode, 1); + n->v.range = range; + n->v.replace = str8_copy(arena, replace); + DLLPushBack(list->first, list->last, n); + list->count += 1; +} + //////////////////////////////// //~ rjf: Lexing Functions @@ -536,8 +549,8 @@ txt_token_array_from_string__c_cpp(Arena *arena, U64 *bytes_processed_counter, S char_is_digit(next_byte, 10))) { active_token_kind = TXT_TokenKind_Numeric; } else if(byte == '"') { active_token_kind = TXT_TokenKind_String; string_is_char = 0; } else if(byte == '\'') { active_token_kind = TXT_TokenKind_String; string_is_char = 1; } - else if(byte == '/' && next_byte == '/') { active_token_kind = TXT_TokenKind_Comment; comment_is_single_line = 1; } - else if(byte == '/' && next_byte == '*') { active_token_kind = TXT_TokenKind_Comment; comment_is_single_line = 0; } + else if(byte == '/' && next_byte == '/') { active_token_kind = TXT_TokenKind_LineComment; comment_is_single_line = 1; } + else if(byte == '/' && next_byte == '*') { active_token_kind = TXT_TokenKind_BlockComment; comment_is_single_line = 0; } else if(byte == '~' || byte == '!' || byte == '%' || byte == '^' || byte == '&' || byte == '*' || @@ -611,17 +624,14 @@ txt_token_array_from_string__c_cpp(Arena *arena, U64 *bytes_processed_counter, S byte != '>' && byte != '/' && byte != '?' && byte != '|'); }break; - case TXT_TokenKind_Comment: + case TXT_TokenKind_LineComment: { - if(comment_is_single_line) - { - ender_found = (!escaped && (byte == '\r' || byte == '\n')); - } - else - { - ender_found = (active_token_start_idx+1 < idx && byte == '*' && next_byte == '/'); - ender_pad += 2; - } + ender_found = (!escaped && (byte == '\r' || byte == '\n')); + }break; + case TXT_TokenKind_BlockComment: + { + ender_found = (active_token_start_idx+1 < idx && byte == '*' && next_byte == '/'); + ender_pad += 2; }break; case TXT_TokenKind_Meta: { @@ -880,8 +890,8 @@ txt_token_array_from_string__odin(Arena *arena, U64 *bytes_processed_counter, St char_is_digit(next_byte, 10))) { active_token_kind = TXT_TokenKind_Numeric; } else if(byte == '"') { active_token_kind = TXT_TokenKind_String; string_is_char = 0; } else if(byte == '\'') { active_token_kind = TXT_TokenKind_String; string_is_char = 1; } - else if(byte == '/' && next_byte == '/') { active_token_kind = TXT_TokenKind_Comment; comment_is_single_line = 1; } - else if(byte == '/' && next_byte == '*') { active_token_kind = TXT_TokenKind_Comment; comment_is_single_line = 0; } + else if(byte == '/' && next_byte == '/') { active_token_kind = TXT_TokenKind_LineComment; comment_is_single_line = 1; } + else if(byte == '/' && next_byte == '*') { active_token_kind = TXT_TokenKind_BlockComment; comment_is_single_line = 0; } else if(byte == '~' || byte == '!' || byte == '%' || byte == '^' || byte == '&' || byte == '*' || @@ -955,17 +965,14 @@ txt_token_array_from_string__odin(Arena *arena, U64 *bytes_processed_counter, St byte != '>' && byte != '/' && byte != '?' && byte != '|'); }break; - case TXT_TokenKind_Comment: + case TXT_TokenKind_LineComment: { - if(comment_is_single_line) - { - ender_found = (!escaped && (byte == '\r' || byte == '\n')); - } - else - { - ender_found = (active_token_start_idx+1 < idx && byte == '*' && next_byte == '/'); - ender_pad += 2; - } + ender_found = (!escaped && (byte == '\r' || byte == '\n')); + }break; + case TXT_TokenKind_BlockComment: + { + ender_found = (active_token_start_idx+1 < idx && byte == '*' && next_byte == '/'); + ender_pad += 2; }break; case TXT_TokenKind_Meta: { @@ -1166,8 +1173,8 @@ txt_token_array_from_string__jai(Arena *arena, U64 *bytes_processed_counter, Str char_is_digit(next_byte, 10))) { active_token_kind = TXT_TokenKind_Numeric; } else if(byte == '"') { active_token_kind = TXT_TokenKind_String; string_is_char = 0; } else if(byte == '\'') { active_token_kind = TXT_TokenKind_String; string_is_char = 1; } - else if(byte == '/' && next_byte == '/') { active_token_kind = TXT_TokenKind_Comment; comment_is_single_line = 1; } - else if(byte == '/' && next_byte == '*') { active_token_kind = TXT_TokenKind_Comment; comment_is_single_line = 0; } + else if(byte == '/' && next_byte == '/') { active_token_kind = TXT_TokenKind_LineComment; comment_is_single_line = 1; } + else if(byte == '/' && next_byte == '*') { active_token_kind = TXT_TokenKind_BlockComment; comment_is_single_line = 0; } else if(byte == '~' || byte == '!' || byte == '%' || byte == '^' || byte == '&' || byte == '*' || @@ -1241,17 +1248,14 @@ txt_token_array_from_string__jai(Arena *arena, U64 *bytes_processed_counter, Str byte != '>' && byte != '/' && byte != '?' && byte != '|'); }break; - case TXT_TokenKind_Comment: + case TXT_TokenKind_LineComment: { - if(comment_is_single_line) - { - ender_found = (!escaped && (byte == '\r' || byte == '\n')); - } - else - { - ender_found = (active_token_start_idx+1 < idx && byte == '*' && next_byte == '/'); - ender_pad += 2; - } + ender_found = (!escaped && (byte == '\r' || byte == '\n')); + }break; + case TXT_TokenKind_BlockComment: + { + ender_found = (active_token_start_idx+1 < idx && byte == '*' && next_byte == '/'); + ender_pad += 2; }break; case TXT_TokenKind_Meta: { @@ -1452,7 +1456,7 @@ txt_token_array_from_string__zig(Arena *arena, U64 *bytes_processed_counter, Str else if(byte == '\'') { active_token_kind = TXT_TokenKind_String; string_is_char = 1; } else if(byte == '\\' && next_byte == '\\') { active_token_kind = TXT_TokenKind_String; string_is_line = 1; } - else if(byte == '/' && next_byte == '/') { active_token_kind = TXT_TokenKind_Comment; } + else if(byte == '/' && next_byte == '/') { active_token_kind = TXT_TokenKind_LineComment; } else if(byte == '~' || byte == '!' || byte == '%' || byte == '^' || byte == '&' || byte == '*' || @@ -1534,7 +1538,7 @@ txt_token_array_from_string__zig(Arena *arena, U64 *bytes_processed_counter, Str byte != '?' && byte != '|' && byte != 'c'); }break; - case TXT_TokenKind_Comment: + case TXT_TokenKind_LineComment: { ender_found = (!escaped && (byte == '\r' || byte == '\n')); }break; @@ -1696,7 +1700,7 @@ txt_token_array_from_string__rust(Arena *arena, U64 *bytes_processed_counter, St { // NOTE(spey): Rust supports unicode identifiers. They are not handled in any way here, // but it might be worth looking into in the future. - +#if 0 Temp scratch = scratch_begin(&arena, 1); //- rjf: generate token list @@ -2053,6 +2057,9 @@ txt_token_array_from_string__rust(Arena *arena, U64 *bytes_processed_counter, St TXT_TokenArray result = txt_token_array_from_chunk_list(arena, &tokens); scratch_end(scratch); return result; +#endif + TXT_TokenArray result = {0}; + return result; } internal TXT_TokenArray @@ -2096,7 +2103,7 @@ txt_token_array_from_string__disasm_x64_intel(Arena *arena, U64 *bytes_processed else if(byte == '>' && brace_nest == 0 && paren_nest == 0) { active_token_start_off = off; - active_token_kind = TXT_TokenKind_Comment; + active_token_kind = TXT_TokenKind_LineComment; advance = 1; } else if(('a' <= byte && byte <= 'z') || ('A' <= byte && byte <= 'Z') || byte == '_') @@ -2246,7 +2253,7 @@ txt_token_array_from_string__disasm_x64_intel(Arena *arena, U64 *bytes_processed ender_found = 1; advance = 0; }break; - case TXT_TokenKind_Comment: + case TXT_TokenKind_LineComment: if(byte == '\n') { ender_found = 1; @@ -2280,20 +2287,858 @@ txt_token_array_from_string__disasm_x64_intel(Arena *arena, U64 *bytes_processed //////////////////////////////// //~ rjf: Text Info Extractor Helpers +internal void +txt_line_map_push(Arena *arena, TXT_LineMap *map, Rng1U64 num_range, Rng1U64 *ranges, S64 delta) +{ + TXT_LineMapRangeNode *n = push_array(arena, TXT_LineMapRangeNode, 1); + n->num_range = num_range; + n->ranges = ranges; + n->delta = delta; + SLLQueuePush(map->first_range, map->last_range, n); + map->total_line_count += dim_1u64(num_range); +} + internal U64 -txt_off_from_info_pt(TXT_TextInfo *info, TxtPt pt) +txt_line_num_from_off(TXT_LineMap *map, U64 off) +{ + U64 result = 0; + for(TXT_LineMapRangeNode *n = map->first_range; n != 0; n = n->next) + { + if(n->num_range.max != n->num_range.min) + { + Rng1U64 off_range = r1u64(n->ranges[0].min + n->delta, n->ranges[n->num_range.max-n->num_range.min-1].max + n->delta); + if(off_range.min <= off && off <= off_range.max) + { + U64 min_idx = 0; + U64 max_idx = dim_1u64(n->num_range)-1; + for(;min_idx <= max_idx;) + { + U64 mid_idx = (max_idx + min_idx) / 2; + if(n->ranges[mid_idx].max + n->delta < off) + { + min_idx = mid_idx + 1; + } + else if(off < n->ranges[mid_idx].min + n->delta) + { + max_idx = mid_idx - 1; + } + else if(n->ranges[mid_idx].min + n->delta <= off && off <= n->ranges[mid_idx].max + n->delta) + { + result = n->num_range.min + mid_idx; + goto break_all; + } + } + } + } + } + break_all:; + return result; +} + +internal Rng1U64 +txt_range_from_line_num(TXT_LineMap *map, U64 num) +{ + Rng1U64 result = {0}; + for(TXT_LineMapRangeNode *n = map->first_range; n != 0; n = n->next) + { + if(contains_1u64(n->num_range, num)) + { + result = n->ranges[num - n->num_range.min]; + result.min = (U64)((S64)result.min + n->delta); + result.max = (U64)((S64)result.max + n->delta); + } + } + return result; +} + +internal void +txt_token_pt_map_push(Arena *arena, TXT_TokenPtMap *map, Rng1U64 num_range, TXT_TokenPt *pts, S64 delta) +{ + TXT_TokenPtMapRangeNode *n = push_array(arena, TXT_TokenPtMapRangeNode, 1); + SLLQueuePush(map->first_range, map->last_range, n); + n->num_range = num_range; + n->pts = pts; + n->delta = delta; + map->total_pt_count += dim_1u64(num_range); +} + +internal U64 +txt_token_pt_num_from_off(TXT_TokenPtMap *map, U64 off) +{ + U64 result = 0; + for EachNode(n, TXT_TokenPtMapRangeNode, map->first_range) + { + U64 num_pts = dim_1u64(n->num_range); + if(num_pts != 0) + { + U64 first_off = n->pts[0].off + n->delta; + U64 last_off = n->pts[num_pts-1].off + n->delta; + if(first_off <= off && off <= last_off) + { + U64 found_idx = 0; + U64 first_idx = 0; + U64 last_idx = num_pts-1; + for(;first_idx <= last_idx;) + { + U64 mid_idx = (last_idx + first_idx) / 2; + U64 mid_off = n->pts[mid_idx].off + n->delta; + if(off < mid_off) + { + last_idx = mid_idx - 1; + } + else if(mid_off < off) + { + found_idx = mid_idx; + first_idx = mid_idx + 1; + } + else if(off == mid_off) + { + found_idx = mid_idx; + break; + } + } + result = n->num_range.min + found_idx; + break; + } + } + } + return result; +} + +internal TXT_TokenPt +txt_token_pt_from_num(TXT_TokenPtMap *map, U64 num) +{ + TXT_TokenPt pt = {TXT_TokenKind_Null}; + if(num > 0) + { + for EachNode(n, TXT_TokenPtMapRangeNode, map->first_range) + { + if(contains_1u64(n->num_range, num)) + { + pt = n->pts[num - n->num_range.min]; + pt.off += n->delta; + break; + } + } + } + return pt; +} + +internal TXT_TokenArray +txt_token_array_from_data(Arena *arena, TXT_LangKind lang_kind, TXT_TokenPt ctx_token_pt, String8 data, U64 base_off, U64 limit) +{ + Temp scratch = scratch_begin(&arena, 1); + TXT_TokenChunkList tokens = {0}; + if(lang_kind != TXT_LangKind_Null) + { + String8Array keywords = txt_keywords_from_lang_kind_table[lang_kind]; + String8Array multichar_symbols = txt_multichar_symbols_from_lang_kind_table[lang_kind]; + U64 chunk_size = Clamp(8, data.size/8, 4096); + TXT_TokenKind active_token_kind = ctx_token_pt.kind; + U64 active_token_start_off = ctx_token_pt.off - base_off; + B32 escaped = 0; + for(U64 off = 0; off <= data.size;) + { + U8 byte = (off+0 < data.size) ? data.str[off+0] : 0; + U8 next_byte = (off+1 < data.size) ? data.str[off+1] : 0; + B32 token_finished = 0; + U64 advance = 1; + + //- rjf: adjust escaping state + B32 escaped_this_step = 0; + if(!escaped && active_token_kind != TXT_TokenKind_Null && byte == '\\') + { + escaped_this_step = 1; + escaped = 1; + } + + //- rjf: take starter bytes for new tokens + if(active_token_kind == TXT_TokenKind_Null) + { + if(0){} + else if(byte == ' ' || byte == '\n' || byte == '\t' || + byte == '\r' || byte == '\f' || byte == '\v') + { + active_token_kind = TXT_TokenKind_Whitespace; + advance = 0; + } + else if(byte == '_' || byte == '$' || + ('a' <= byte && byte <= 'z') || + ('A' <= byte && byte <= 'Z')) + { + active_token_kind = TXT_TokenKind_Identifier; + advance = 0; + } + else if(('0' <= byte && byte <= '9') || + (byte == '.' && ('0' <= next_byte && next_byte <= '9'))) + { + active_token_kind = TXT_TokenKind_Numeric; + advance = 0; + } + else if(byte == '"') + { + active_token_kind = TXT_TokenKind_String; + advance = 1; + } + else if(byte == '\'') + { + active_token_kind = TXT_TokenKind_Char; + advance = 1; + } + else if(byte == '/' && next_byte == '/') + { + active_token_kind = TXT_TokenKind_LineComment; + advance = 2; + } + else if(byte == '/' && next_byte == '*') + { + active_token_kind = TXT_TokenKind_BlockComment; + advance = 2; + } + else if(byte == '#') + { + active_token_kind = TXT_TokenKind_Meta; + advance = 1; + } + else if(byte == '~' || byte == '!' || + byte == '%' || byte == '^' || + byte == '&' || byte == '*' || + byte == '(' || byte == ')' || + byte == '-' || byte == '=' || + byte == '+' || byte == '[' || + byte == ']' || byte == '{' || + byte == '}' || byte == ':' || + byte == ';' || byte == ',' || + byte == '.' || byte == '<' || + byte == '>' || byte == '/' || + byte == '?' || byte == '|') + { + active_token_kind = TXT_TokenKind_Symbol; + advance = 1; + } + if(active_token_kind != TXT_TokenKind_Null) + { + active_token_start_off = off; + } + else + { + TXT_Token token = {TXT_TokenKind_Error, r1u64(base_off+off, base_off+off+1)}; + txt_token_chunk_list_push(scratch.arena, &tokens, chunk_size, &token); + advance = 1; + } + } + + //- rjf: look for active token enders + else switch(active_token_kind) + { + default:{}break; + case TXT_TokenKind_Whitespace: + { + if(byte != ' ' && byte != '\n' && byte != '\t' && + byte != '\r' && byte != '\f' && byte != '\v') + { + token_finished = 1; + advance = 0; + } + }break; + case TXT_TokenKind_Identifier: + { + // TODO(rjf): stupid C++ symbol names - `string_literals_like_this' + if((byte < '0' || '9' < byte) && + (byte < 'a' || 'z' < byte) && + (byte < 'A' || 'Z' < byte) && + byte != '$' && + byte != '_') + { + token_finished = 1; + advance = 0; + } + }break; + case TXT_TokenKind_Numeric: + { + if((byte < '0' || '9' < byte) && + (byte < 'a' || 'z' < byte) && + (byte < 'A' || 'Z' < byte) && + byte != '.') + { + token_finished = 1; + advance = 0; + } + }break; + case TXT_TokenKind_String: + { + if(byte == '"' && !escaped) + { + token_finished = 1; + advance = 1; + } + }break; + case TXT_TokenKind_Char: + { + if(byte == '\'' && !escaped) + { + token_finished = 1; + advance = 1; + } + }break; + case TXT_TokenKind_Symbol: + { + if(byte != '~' && byte != '!' && + byte != '%' && byte != '^' && + byte != '&' && byte != '*' && + byte != '(' && byte != ')' && + byte != '-' && byte != '=' && + byte != '+' && byte != '[' && + byte != ']' && byte != '{' && + byte != '}' && byte != ':' && + byte != ';' && byte != ',' && + byte != '.' && byte != '<' && + byte != '>' && byte != '/' && + byte != '?' && byte != '|') + { + token_finished = 1; + advance = 0; + } + }break; + case TXT_TokenKind_LineComment: + case TXT_TokenKind_Meta: + { + if(byte == '\r' && escaped) + { + escaped_this_step = 1; + } + if(byte == '\n' && !escaped) + { + token_finished = 1; + advance = 1; + } + }break; + case TXT_TokenKind_BlockComment: + { + if(byte == '*' && next_byte == '/') + { + token_finished = 1; + advance = 2; + } + }break; + } + + //- rjf: finish all tokens if we're at the end of the data + if(off == data.size) + { + token_finished = 1; + advance = 1; + } + + //- rjf: upgrade identifiers to keywords + if(token_finished && active_token_kind == TXT_TokenKind_Identifier) + { + String8 token_string = str8_substr(data, r1u64(active_token_start_off, off+advance)); + for EachIndex(idx, keywords.count) + { + if(str8_match(keywords.v[idx], token_string, 0)) + { + active_token_kind = TXT_TokenKind_Keyword; + break; + } + } + } + + //- rjf: push completed token + if(token_finished) + { + TXT_Token token = {active_token_kind, r1u64(base_off+active_token_start_off, base_off+off+advance)}; + txt_token_chunk_list_push(scratch.arena, &tokens, chunk_size, &token); + active_token_kind = TXT_TokenKind_Null; + } + + //- rjf: reset escaped state + if(!escaped_this_step) + { + escaped = 0; + } + + //- rjf: advance + off += advance; + } + } + TXT_TokenArray result = txt_token_array_from_chunk_list(arena, &tokens); + scratch_end(scratch); + return result; +} + +internal TXT_Patched +txt_patched_from_info_data_patches(Arena *arena, TXT_TextInfo *info, String8 data, TXT_PatchList *patches) +{ + Temp scratch = scratch_begin(&arena, 1); + + ////////////////////////////// + //- rjf: produce default case, where we just have one range which covers the original data + // + MemoryMap last_memory_map = {0}; + U64 last_size = data.size; + TXT_LineMap last_line_map = {0}; + TXT_TokenPtMap last_token_pt_map = {0}; + memory_map_push(scratch.arena, &last_memory_map, r1u64(0, data.size), data.str); + txt_line_map_push(scratch.arena, &last_line_map, r1u64(1, info->lines_count+1), info->lines_ranges, 0); + txt_token_pt_map_push(scratch.arena, &last_token_pt_map, r1u64(1, info->big_token_pts_count+1), info->big_token_pts, 0); + + ////////////////////////////// + //- rjf: apply the patches in order, each being able to slice/dice the previous memory map + // + for EachNode(n, TXT_PatchNode, patches->first) + { + MemoryMap next_memory_map = {0}; + TXT_LineMap next_line_map = {0}; + TXT_TokenPtMap next_token_pt_map = {0}; + + //////////////////////////// + //- rjf: compute portion of memory before/after this replace-range + // + Rng1U64 pre_replace_range = r1u64(0, n->v.range.min); + Rng1U64 post_replace_range = r1u64(n->v.range.max, last_size); + + //////////////////////////// + //- rjf: map this replace range -> range of replaced newlines + // + Rng1U64 replace_line_num_range = {0}; + { + replace_line_num_range.min = txt_line_num_from_off(&last_line_map, n->v.range.min); + replace_line_num_range.max = txt_line_num_from_off(&last_line_map, n->v.range.max); + } + + //////////////////////////// + //- rjf: compute ranges of (unchanged) lines before/after this replace-range + // + Rng1U64 pre_replace_line_num_range = r1u64(1, replace_line_num_range.min); + Rng1U64 post_replace_line_num_range = r1u64(replace_line_num_range.max+1, last_line_map.total_line_count+1); + + //////////////////////////// + //- rjf: map this replace range -> range of token pts + // + Rng1U64 replace_token_pt_range = {0}; + { + replace_token_pt_range.min = txt_token_pt_num_from_off(&last_token_pt_map, n->v.range.min); + replace_token_pt_range.max = txt_token_pt_num_from_off(&last_token_pt_map, n->v.range.max); + } + + //////////////////////////// + //- rjf: compute delta & next size + // + S64 size_delta = (S64)n->v.replace.size - (S64)dim_1u64(n->v.range); + U64 next_size = (U64)((S64)last_size + size_delta); + + //////////////////////////// + //- rjf: compute line count delta, + list of line ranges inside of replace + // + S64 line_delta = 0; + Rng1U64List replace_line_ranges = {0}; + { + U64 last_line_start_off = 0; + line_delta -= (S64)dim_1u64(replace_line_num_range); + for EachIndex(idx, n->v.replace.size) + { + if(n->v.replace.str[idx] == '\n') + { + line_delta += 1; + Rng1U64 line_range = r1u64(last_line_start_off, idx); + if(idx > 0 && n->v.replace.str[idx-1] == '\r') + { + line_range.max -= 1; + } + rng1u64_list_push(scratch.arena, &replace_line_ranges, line_range); + last_line_start_off = idx+1; + } + } + rng1u64_list_push(scratch.arena, &replace_line_ranges, r1u64(last_line_start_off, n->v.replace.size)); + } + + //////////////////////////// + //- rjf: push all portions of pre-replace / post-replace ranges in previous memory map + // + { + for EachNode(map_n, MemoryMapRangeNode, last_memory_map.first_range) + { + Rng1U64 range_x_pre = intersect_1u64(pre_replace_range, map_n->v.vaddr_range); + Rng1U64 range_x_post = intersect_1u64(post_replace_range, map_n->v.vaddr_range); + if(range_x_pre.max > range_x_pre.min) + { + memory_map_push(scratch.arena, &next_memory_map, range_x_pre, (U8 *)map_n->v.base + (range_x_pre.min - map_n->v.vaddr_range.min)); + } + if(range_x_post.max > range_x_post.min) + { + Rng1U64 range_x_post_shifted = range_x_post; + range_x_post_shifted.min = (U64)((S64)range_x_post_shifted.min + size_delta); + range_x_post_shifted.max = (U64)((S64)range_x_post_shifted.max + size_delta); + memory_map_push(scratch.arena, &next_memory_map, range_x_post_shifted, (U8 *)map_n->v.base + (range_x_post.min - map_n->v.vaddr_range.min)); + } + } + } + + //////////////////////////// + //- rjf: push replaced range + // + if(n->v.replace.size != 0) + { + memory_map_push(scratch.arena, &next_memory_map, r1u64(n->v.range.min, n->v.range.min + n->v.replace.size), n->v.replace.str); + } + + //////////////////////////// + //- rjf: push all portions of pre-replace / post-replace ranges in previous line map + // + { + for EachNode(map_n, TXT_LineMapRangeNode, last_line_map.first_range) + { + Rng1U64 num_range = map_n->num_range; + Rng1U64 range_x_pre = intersect_1u64(pre_replace_line_num_range, num_range); + Rng1U64 range_x_post = intersect_1u64(post_replace_line_num_range, num_range); + if(range_x_pre.max > range_x_pre.min) + { + txt_line_map_push(scratch.arena, &next_line_map, r1u64(range_x_pre.min, range_x_pre.max), map_n->ranges + (range_x_pre.min - num_range.min), map_n->delta); + } + if(range_x_post.max > range_x_post.min) + { + Rng1U64 range_x_post_shifted = range_x_post; + range_x_post_shifted.min = (U64)((S64)range_x_post_shifted.min + line_delta); + range_x_post_shifted.max = (U64)((S64)range_x_post_shifted.max + line_delta); + txt_line_map_push(scratch.arena, &next_line_map, r1u64(range_x_post_shifted.min, range_x_post_shifted.max), map_n->ranges + (range_x_post.min - num_range.min), map_n->delta + size_delta); + } + } + } + + //////////////////////////// + //- rjf: compute affected line ranges + // + U64 affected_line_count = replace_line_ranges.count; + Rng1U64 *affected_line_ranges = push_array(arena, Rng1U64, affected_line_count); + { + Rng1U64Node *replace_line_range_n = replace_line_ranges.first; + for EachIndex(affected_line_idx, affected_line_count) + { + Rng1U64 affected_line_range = {0}; + if(replace_line_range_n != 0) + { + Rng1U64 replace_line_range = replace_line_range_n->v; + affected_line_range = r1u64(replace_line_range.min + n->v.range.min, replace_line_range.max + n->v.range.min); + replace_line_range_n = replace_line_range_n->next; + } + + // rjf: the first line in the range -> take min from original line map + if(affected_line_idx == 0) + { + Rng1U64 og_line_range = txt_range_from_line_num(&last_line_map, replace_line_num_range.min + affected_line_idx); + affected_line_range.min = og_line_range.min; + } + + // rjf: the last line in the range -> take remaining suffix from original line map + if(affected_line_idx == affected_line_count-1 && affected_line_idx >= ClampBot(0, line_delta)) + { + Rng1U64 og_line_range = txt_range_from_line_num(&last_line_map, replace_line_num_range.max); + if(og_line_range.max > n->v.range.max) + { + affected_line_range.max += og_line_range.max - n->v.range.max; + } + } + + // rjf: commit + affected_line_ranges[affected_line_idx] = affected_line_range; + } + } + + //////////////////////////// + //- rjf: push affected line ranges + // + txt_line_map_push(scratch.arena, &next_line_map, r1u64(replace_line_num_range.min, replace_line_num_range.min + affected_line_count), affected_line_ranges, 0); + + //////////////////////////// + //- rjf: compute token pt delta, + token pt ranges to keep, + new token pts + // + S64 token_pt_delta = 0; + Rng1U64 pre_replace_token_pt_range = r1u64(1, replace_token_pt_range.min); + Rng1U64 post_replace_token_pt_range = r1u64(replace_token_pt_range.max, last_token_pt_map.total_pt_count+1); + TXT_TokenPt *new_token_pts = 0; + U64 new_token_pts_count = 0; + { + U64 token_pt_lex_start_off = n->v.range.min; + + //- rjf: eliminate starter token pt - range may not cover entire token pt, but we will re-lex this portion + if(replace_token_pt_range.min != 0) + { + TXT_TokenPt starter_token_pt = txt_token_pt_from_num(&last_token_pt_map, replace_token_pt_range.min); + Rng1U64 starter_token_pt_range = r1u64(starter_token_pt.off, starter_token_pt.off + 4); + if(contains_1u64(starter_token_pt_range, n->v.range.min)) + { + token_pt_delta -= 1; + token_pt_lex_start_off = starter_token_pt.off > 0 ? starter_token_pt.off-1 : starter_token_pt.off; + if(replace_token_pt_range.min == replace_token_pt_range.max) + { + post_replace_token_pt_range.min += 1; + } + } + else + { + pre_replace_token_pt_range.max += 1; + } + } + + //- rjf: eliminate ender token pt + if(replace_token_pt_range.max != 0 && replace_token_pt_range.max != replace_token_pt_range.min) + { + TXT_TokenPt ender_token_pt = txt_token_pt_from_num(&last_token_pt_map, replace_token_pt_range.min+1); + if(n->v.range.max > ender_token_pt.off) + { + token_pt_delta -= 1; + post_replace_token_pt_range.min += 1; + } + } + + //- rjf: re-lex region to find new token pts + typedef struct TokenPtChunkNode TokenPtChunkNode; + struct TokenPtChunkNode + { + TokenPtChunkNode *next; + TXT_TokenPt *v; + U64 count; + U64 cap; + }; + TokenPtChunkNode *first_pt_chunk = 0; + TokenPtChunkNode *last_pt_chunk = 0; + U64 total_new_pt_count = 0; + { + U64 ctx_token_pt_num = txt_token_pt_num_from_off(&last_token_pt_map, token_pt_lex_start_off); + TXT_TokenPt ctx_token_pt = txt_token_pt_from_num(&last_token_pt_map, ctx_token_pt_num); + TXT_TokenKind active_token_kind = ctx_token_pt.kind; + U64 active_token_start_off = 0; + String8 herestring_marker = {0}; + Rng1U64 relex_range = r1u64(token_pt_lex_start_off, n->v.range.min + n->v.replace.size); + String8 relex_data = memory_map_data_from_range(scratch.arena, &next_memory_map, relex_range); + B32 escaped = 0; + for(U64 off = 0; off <= relex_data.size; off += 1) + { + U64 extra_advance = 0; + U8 byte = (off+0 < relex_data.size) ? relex_data.str[off+0] : 0; + U8 next_byte = (off+1 < relex_data.size) ? relex_data.str[off+1] : 0; + + //- rjf: adjust escaping state + if(active_token_kind != TXT_TokenKind_Null && byte == '\\') + { + escaped = 1; + } + + //- rjf: no active token -> look for starters + TXT_TokenKind start_active_token_kind = active_token_kind; + U64 active_token_end_off = 0; + if(active_token_kind == TXT_TokenKind_Null) + { + // rjf: " -> start a string literal + if(byte == '"') + { + active_token_kind = TXT_TokenKind_String; + herestring_marker.size = 0; + } + + // rjf: ' -> start a char literal + else if(byte == '\'') + { + active_token_kind = TXT_TokenKind_Char; + herestring_marker.size = 0; + } + + // rjf: R" -> start a C++11+ style herestring + else if(byte == 'R' && next_byte == '"') + { + active_token_kind = TXT_TokenKind_String; + U64 next_paren_pos = str8_find_needle(str8_prefix(data, off+2+256), off+2, s("("), 0); + herestring_marker = str8_substr(data, r1u64(off+2, next_paren_pos)); + extra_advance = 1 + herestring_marker.size + 1; + } + + // rjf: // -> start a single-line comment + else if(byte == '/' && next_byte == '/') + { + active_token_kind = TXT_TokenKind_LineComment; + extra_advance = 1; + } + + // rjf: /* -> start a multi-line comment + else if(byte == '/' && next_byte == '*') + { + active_token_kind = TXT_TokenKind_BlockComment; + extra_advance = 1; + } + + // rjf: # -> start a meta + else if(byte == '#') + { + active_token_kind = TXT_TokenKind_Meta; + } + + // rjf: got a token kind -> remember its starting offset + if(active_token_kind != TXT_TokenKind_Null) + { + active_token_start_off = off; + } + } + + //- rjf: look for enders + else switch(active_token_kind) + { + default:{}break; + case TXT_TokenKind_LineComment: + case TXT_TokenKind_Meta: + if(!escaped && byte == '\n') + { + active_token_end_off = off; + }break; + case TXT_TokenKind_BlockComment: + if(byte == '*' && next_byte == '/') + { + active_token_end_off = off+1; + extra_advance = 1; + }break; + case TXT_TokenKind_String: + { + // TODO(rjf): herestrings - we might not have the right herestring marker, given that it may + // be from an earlier token pt...? + if(byte == '"' && !escaped) + { + active_token_end_off = off+1; + } + }break; + case TXT_TokenKind_Char: + if(!escaped && byte == '\'') + { + active_token_end_off = off+1; + extra_advance = 1; + }break; + } + + //- rjf: found ender -> reset state + if(active_token_end_off > active_token_start_off) + { + active_token_kind = TXT_TokenKind_Null; + } + + //- rjf: state changed -> push new pt + if(active_token_kind != start_active_token_kind) + { + TXT_TokenPt pt = {active_token_kind, relex_range.min + off + ((active_token_kind == TXT_TokenKind_Null) ? extra_advance : 0)}; + TokenPtChunkNode *chunk = last_pt_chunk; + if(chunk == 0 || chunk->count >= chunk->cap) + { + chunk = push_array(scratch.arena, TokenPtChunkNode, 1); + SLLQueuePush(first_pt_chunk, last_pt_chunk, chunk); + chunk->cap = 256; + chunk->v = push_array(scratch.arena, TXT_TokenPt, chunk->cap); + } + chunk->v[chunk->count] = pt; + chunk->count += 1; + total_new_pt_count += 1; + } + + //- rjf: reset escaped state + escaped = 0; + + //- rjf: do extra advance + off += extra_advance; + } + } + + //- rjf: join new token pts + if(total_new_pt_count != 0) + { + token_pt_delta += total_new_pt_count; + new_token_pts_count = total_new_pt_count; + new_token_pts = push_array(arena, TXT_TokenPt, new_token_pts_count); + U64 idx = 0; + for(TokenPtChunkNode *n = first_pt_chunk; n != 0; n = n->next) + { + MemoryCopy(new_token_pts + idx, n->v, sizeof(n->v[0]) * n->count); + idx += n->count; + } + } + } + + //////////////////////////// + //- rjf: push all portions of pre-replace / post-replace ranges in previous token pt map + // + { + for EachNode(n, TXT_TokenPtMapRangeNode, last_token_pt_map.first_range) + { + Rng1U64 num_range = n->num_range; + Rng1U64 range_x_pre = intersect_1u64(pre_replace_token_pt_range, num_range); + Rng1U64 range_x_post = intersect_1u64(post_replace_token_pt_range, num_range); + if(range_x_pre.max > range_x_pre.min) + { + txt_token_pt_map_push(scratch.arena, &next_token_pt_map, range_x_pre, n->pts + (range_x_pre.min - num_range.min), n->delta); + } + if(range_x_post.max > range_x_post.min) + { + Rng1U64 range_x_post_shifted = range_x_post; + range_x_post_shifted.min = (U64)((S64)range_x_post_shifted.min + token_pt_delta); + range_x_post_shifted.max = (U64)((S64)range_x_post_shifted.max + token_pt_delta); + txt_token_pt_map_push(scratch.arena, &next_token_pt_map, range_x_post_shifted, n->pts + (range_x_post.min - num_range.min), n->delta + size_delta); + } + } + } + + //////////////////////////// + //- rjf: push new token pts + // + if(new_token_pts_count != 0) + { + txt_token_pt_map_push(scratch.arena, &next_token_pt_map, r1u64(pre_replace_token_pt_range.max, pre_replace_token_pt_range.max + new_token_pts_count), new_token_pts, 0); + } + + //////////////////////////// + //- rjf: advance to the next state + // + last_memory_map = next_memory_map; + last_size = next_size; + last_line_map = next_line_map; + last_token_pt_map = next_token_pt_map; + } + + // rjf: fill result + TXT_Patched result = {0}; + { + for EachNode(n, MemoryMapRangeNode, last_memory_map.first_range) + { + memory_map_push(arena, &result.memory_map, n->v.vaddr_range, n->v.base); + } + result.size = last_size; + for EachNode(n, TXT_LineMapRangeNode, last_line_map.first_range) + { + txt_line_map_push(arena, &result.line_map, n->num_range, n->ranges, n->delta); + } + for EachNode(n, TXT_TokenPtMapRangeNode, last_token_pt_map.first_range) + { + txt_token_pt_map_push(arena, &result.token_pt_map, n->num_range, n->pts, n->delta); + } + } + + scratch_end(scratch); + return result; +} + +//~ TODO(rjf): old unpatched text viz code: + +internal U64 +txt_off_from_pt(TXT_TextInfo *info, TXT_PatchList *patches, TxtPt pt) { U64 off = 0; - if(1 <= pt.line && pt.line <= info->lines_count) { - Rng1U64 line_range = info->lines_ranges[pt.line-1]; - off = line_range.min + (pt.column-1); + if(1 <= pt.line && pt.line <= info->lines_count) + { + Rng1U64 line_range = info->lines_ranges[pt.line-1]; + off = line_range.min + (pt.column-1); + } } return off; } internal TxtPt -txt_pt_from_info_off__linear_scan(TXT_TextInfo *info, U64 off) +txt_pt_from_off__linear_scan(TXT_TextInfo *info, TXT_PatchList *patches, U64 off) { TxtPt pt = {0}; { @@ -2303,6 +3148,7 @@ txt_pt_from_info_off__linear_scan(TXT_TextInfo *info, U64 off) { pt.line = (S64)line_idx + 1; pt.column = (S64)(off - info->lines_ranges[line_idx].min) + 1; + break; } } } @@ -2491,9 +3337,9 @@ txt_expr_off_range_from_info_data_pt(TXT_TextInfo *info, String8 data, TxtPt pt) } internal String8 -txt_string_from_info_data_txt_rng(TXT_TextInfo *info, String8 data, TxtRng rng) +txt_string_from_info_data_txt_rng(TXT_TextInfo *info, String8 data, TXT_PatchList *patches, TxtRng rng) { - Rng1U64 rng_off = r1u64(txt_off_from_info_pt(info, rng.min), txt_off_from_info_pt(info, rng.max)); + Rng1U64 rng_off = r1u64(txt_off_from_pt(info, patches, rng.min), txt_off_from_pt(info, patches, rng.max)); String8 result = str8_substr(data, rng_off); return result; } @@ -2658,9 +3504,9 @@ txt_scope_node_from_info_off(TXT_TextInfo *info, U64 off) } internal TXT_ScopeNode * -txt_scope_node_from_info_pt(TXT_TextInfo *info, TxtPt pt) +txt_scope_node_from_info_pt(TXT_TextInfo *info, TXT_PatchList *patches, TxtPt pt) { - U64 off = txt_off_from_info_pt(info, pt); + U64 off = txt_off_from_pt(info, patches, pt); TXT_ScopeNode *result = txt_scope_node_from_info_off(info, off); return result; } @@ -2685,7 +3531,7 @@ struct TXT_ArtifactCreateShared }; internal AC_Artifact -txt_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out, U64 *gen_out) +txt_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, U64 *gen_out) { ProfBeginFunction(); Temp scratch = scratch_begin(0, 0); @@ -2716,54 +3562,26 @@ txt_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out, U64 *gen_ou } //- rjf: set # of bytes to process - // (line ending calc) (line counting) (line measuring) (lexing) - set_progress_target(Min(data.size, 1024) + data.size + data.size + data.size*(lang != TXT_LangKind_Null)); - - //- rjf: detect line end kind - TXT_LineEndKind line_end_kind = TXT_LineEndKind_Null; - if(lane_idx() == 0) - { - U64 lf_count = 0; - U64 cr_count = 0; - for(U64 idx = 0; idx < data.size && idx < 1024; idx += 1) - { - if(data.str[idx] == '\r') - { - cr_count += 1; - } - if(data.str[idx] == '\n') - { - lf_count += 1; - } - } - if(cr_count >= lf_count/2 && lf_count >= 1) - { - line_end_kind = TXT_LineEndKind_CRLF; - } - else if(lf_count >= 1) - { - line_end_kind = TXT_LineEndKind_LF; - } - shared->info.line_end_kind = line_end_kind; - } - lane_sync(); - set_progress(Min(data.size, 1024)); + // (line counting) (line measuring) (lexing) + set_progress_target(data.size + data.size + data.size*(lang != TXT_LangKind_Null)); //- rjf: count # of lines U64 lane_line_count = 0; + U64 *lane_line_counts = 0; if(lane_idx() == 0) { - lane_line_count = 1; + lane_line_counts = push_array(scratch.arena, U64, lane_count()); } + lane_sync_u64(&lane_line_counts, 0); { - Rng1U64 range = lane_range(data.size); + Rng1U64 range = lane_range(data.size+1); for EachInRange(idx, range) { if(idx%1000 == 0 && ins_atomic_u32_eval(cancel_signal)) { break; } - if(data.str[idx] == '\n') + if(idx == data.size || data.str[idx] == '\n') { lane_line_count += 1; } @@ -2774,16 +3592,40 @@ txt_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out, U64 *gen_ou } } ins_atomic_u64_add_eval(&shared->info.lines_count, lane_line_count); + lane_line_counts[lane_idx()] = lane_line_count; lane_sync(); set_progress(Min(data.size, 1024) + data.size); - //- rjf: allocate & store line ranges + //- rjf: figure out which starting line idx each lane will take + U64 *lane_line_base_idxs = 0; if(lane_idx() == 0) { + lane_line_base_idxs = push_array(scratch.arena, U64, lane_count()); + U64 idx = 0; + for EachIndex(l_idx, lane_count()) + { + lane_line_base_idxs[l_idx] = idx; + idx += lane_line_counts[l_idx]; + } + } + lane_sync_u64(&lane_line_base_idxs, 0); + + //- rjf: allocate & store line ranges + U64 *lane_line_max_size = 0; + U64 *lane_cr_count = 0; + if(lane_idx() == 0) + { + lane_cr_count = push_array(scratch.arena, U64, lane_count()); + lane_line_max_size = push_array(scratch.arena, U64, lane_count()); shared->info.lines_ranges = push_array_no_zero(shared->arena, Rng1U64, shared->info.lines_count); - U64 line_idx = 0; - U64 line_start_idx = 0; - for(U64 idx = 0; idx <= data.size; idx += 1) + } + lane_sync_u64(&lane_line_max_size, 0); + lane_sync_u64(&lane_cr_count, 0); + { + Rng1U64 range = lane_range(data.size+1); + U64 lane_line_idx = 0; + U64 line_start_idx = range.min; + for EachInRange(idx, range) { if(idx%1000 == 0 && ins_atomic_u32_eval(cancel_signal)) { @@ -2791,15 +3633,32 @@ txt_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out, U64 *gen_ou } if(idx == data.size || data.str[idx] == '\n') { + if(lane_line_idx == 0 && line_start_idx > 0) + { + for(U64 idx2 = line_start_idx - 1; idx2 < data.size; idx2 -= 1) + { + if(data.str[idx2] == '\n') + { + line_start_idx = idx2+1; + break; + } + else if(idx2 == 0) + { + line_start_idx = idx2; + break; + } + } + } Rng1U64 line_range = r1u64(line_start_idx, idx); if(idx > 0 && data.str[idx-1] == '\r' && line_range.max > line_range.min) { + lane_cr_count[lane_idx()] += 1; line_range.max -= 1; } U64 line_size = dim_1u64(line_range); - shared->info.lines_ranges[line_idx] = line_range; - shared->info.lines_max_size = Max(shared->info.lines_max_size, line_size); - line_idx += 1; + shared->info.lines_ranges[lane_line_base_idxs[lane_idx()] + lane_line_idx] = line_range; + lane_line_max_size[lane_idx()] = Max(lane_line_max_size[lane_idx()], line_size); + lane_line_idx += 1; line_start_idx = idx+1; } if(idx && idx%1000 == 0) @@ -2811,6 +3670,396 @@ txt_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out, U64 *gen_ou lane_sync(); set_progress(Min(data.size, 1024) + data.size + data.size); + //- rjf: find max line size across all lanes + if(lane_idx() == 0) + { + for EachIndex(l_idx, lane_count()) + { + shared->info.lines_max_size = Max(shared->info.lines_max_size, lane_line_max_size[l_idx]); + } + } + lane_sync(); + + //- rjf: pick LF/CRLF based on significant ratio of CR characters across all lanes + { + TXT_LineEndKind line_end_kind = TXT_LineEndKind_Null; + if(lane_idx() == 0) + { + U64 total_cr_count = 0; + for EachIndex(l_idx, lane_count()) + { + total_cr_count += lane_cr_count[l_idx]; + } + if(total_cr_count > shared->info.lines_count / 4 && total_cr_count > 0) + { + line_end_kind = TXT_LineEndKind_CRLF; + } + shared->info.line_end_kind = line_end_kind; + } + lane_sync(); + } + + //- rjf: find all token endpoint candidates across all lanes + // + // note that these are not necessarily actually token-forming sequences of + // characters. we're effectively just building an acceleration structure to + // quickly process the buffer - to skip between sequences of *plausible* + // token markers. but, for example, if a `//` showed up inside of two `"`s, + // then there would not be a comment token emitted. we just do this (rather + // than going through the buffer sequentially) so that we can do this gather + // step wide, and for the serially-dependent tokenization state machine part, + // we can do that over much less data. + // + typedef struct TokenEndpointCandidateChunkNode TokenEndpointCandidateChunkNode; + struct TokenEndpointCandidateChunkNode + { + TokenEndpointCandidateChunkNode *next; + U64 *v; + U64 count; + U64 cap; + }; + TokenEndpointCandidateChunkNode **lanes_first_token_endpoint_candidate_chunks = 0; + TokenEndpointCandidateChunkNode **lanes_last_token_endpoint_candidate_chunks = 0; + if(lang != TXT_LangKind_Null) + { + if(lane_idx() == 0) + { + lanes_first_token_endpoint_candidate_chunks = push_array(scratch.arena, TokenEndpointCandidateChunkNode *, lane_count()); + lanes_last_token_endpoint_candidate_chunks = push_array(scratch.arena, TokenEndpointCandidateChunkNode *, lane_count()); + } + lane_sync_u64(&lanes_first_token_endpoint_candidate_chunks, 0); + lane_sync_u64(&lanes_last_token_endpoint_candidate_chunks, 0); + TokenEndpointCandidateChunkNode *first_chunk = 0; + TokenEndpointCandidateChunkNode *last_chunk = 0; + Rng1U64 range = lane_range(data.size); + for EachInRange(off, range) + { + U8 byte = data.str[off]; + U8 next_byte = (off+1 < data.size) ? data.str[off+1] : 0; + B32 off_is_endpoint = 1; + U64 extra_advance = 0; + if(byte == '/' && next_byte == '*') + { + extra_advance = 1; + } + else if(byte == '*' && next_byte == '/') + { + extra_advance = 1; + } + else if(byte == 'R' && next_byte == '"') + { + extra_advance = 1; + } + else if(byte == '/' && next_byte == '/') + { + extra_advance = 1; + } + else if(byte == '#') + { + // NOTE(rjf): no-op + } + else if(byte == '"' || byte == '\'') + { + B32 is_escaped = 0; + if(off > 0) + { + for(U64 lookback_off = off-1; lookback_off > 0; lookback_off -= 1) + { + if(data.str[lookback_off] == '\\') + { + is_escaped ^= 1; + } + else + { + break; + } + } + } + if(is_escaped) + { + off_is_endpoint = 0; + } + } + else + { + off_is_endpoint = 0; + } + if(off_is_endpoint) + { + TokenEndpointCandidateChunkNode *chunk = last_chunk; + if(chunk == 0 || chunk->count >= chunk->cap) + { + chunk = push_array(scratch.arena, TokenEndpointCandidateChunkNode, 1); + SLLQueuePush(first_chunk, last_chunk, chunk); + chunk->cap = 512; + chunk->v = push_array(scratch.arena, U64, chunk->cap); + } + chunk->v[chunk->count] = off; + chunk->count += 1; + } + off += extra_advance; + } + lanes_first_token_endpoint_candidate_chunks[lane_idx()] = first_chunk; + lanes_last_token_endpoint_candidate_chunks[lane_idx()] = last_chunk; + lane_sync(); + } + + //- rjf: join all token endpoint candidates from all lanes + TokenEndpointCandidateChunkNode *first_token_endpoint_candidate_chunk = 0; + TokenEndpointCandidateChunkNode *last_token_endpoint_candidate_chunk = 0; + if(lang != TXT_LangKind_Null && lane_idx() == 0) + { + for EachIndex(l_idx, lane_count()) + { + if(last_token_endpoint_candidate_chunk == 0) + { + first_token_endpoint_candidate_chunk = lanes_first_token_endpoint_candidate_chunks[l_idx]; + last_token_endpoint_candidate_chunk = lanes_last_token_endpoint_candidate_chunks[l_idx]; + } + else + { + last_token_endpoint_candidate_chunk->next = lanes_first_token_endpoint_candidate_chunks[l_idx]; + last_token_endpoint_candidate_chunk = lanes_last_token_endpoint_candidate_chunks[l_idx]; + } + } + } + lane_sync_u64(&first_token_endpoint_candidate_chunk, 0); + lane_sync_u64(&last_token_endpoint_candidate_chunk, 0); + + //- rjf: scan sequence of token endpoint candidates - find big token ranges. + typedef struct TokenPtChunkNode TokenPtChunkNode; + struct TokenPtChunkNode + { + TokenPtChunkNode *next; + TXT_TokenPt *v; + U64 count; + U64 cap; + }; + TokenPtChunkNode *first_token_pt_chunk = 0; + TokenPtChunkNode *last_token_pt_chunk = 0; + U64 total_token_pt_count = 0; + if(lang != TXT_LangKind_Null && lane_idx() == 0) + { + TXT_TokenKind active_token_kind = TXT_TokenKind_Null; + U64 active_token_start_off = 0; + String8 herestring_marker = {0}; + U64 cand_chunk_idx = 0; + for(TokenEndpointCandidateChunkNode *cand_chunk_n = first_token_endpoint_candidate_chunk; cand_chunk_n != 0;) + { + U64 off = cand_chunk_n->v[cand_chunk_idx]; + U8 byte = data.str[off]; + U8 next_byte = (off+1 < data.size) ? data.str[off+1] : 0; + + //- rjf: no active token kind -> look for token starter + TXT_TokenKind start_active_token_kind = active_token_kind; + if(active_token_kind == TXT_TokenKind_Null) + { + // rjf: " -> start a string literal + if(byte == '"') + { + active_token_kind = TXT_TokenKind_String; + herestring_marker.size = 0; + } + + // rjf: ' -> start a char literal + else if(byte == '\'') + { + active_token_kind = TXT_TokenKind_Char; + herestring_marker.size = 0; + } + + // rjf: R" -> start a C++11+ style herestring + else if(byte == 'R' && next_byte == '"') + { + active_token_kind = TXT_TokenKind_String; + U64 next_paren_pos = str8_find_needle(str8_prefix(data, off+2+256), off+2, s("("), 0); + herestring_marker = str8_substr(data, r1u64(off+2, next_paren_pos)); + } + + // rjf: // -> start a single-line comment + else if(byte == '/' && next_byte == '/') + { + active_token_kind = TXT_TokenKind_LineComment; + } + + // rjf: /* -> start a multi-line comment + else if(byte == '/' && next_byte == '*') + { + active_token_kind = TXT_TokenKind_BlockComment; + } + + // rjf: # -> start a meta + else if(byte == '#') + { + active_token_kind = TXT_TokenKind_Meta; + } + + // rjf: got a token kind -> remember its starting offset + if(active_token_kind != TXT_TokenKind_Null) + { + active_token_start_off = off; + } + } + + //- rjf: end single-line comments & meta by binary searching for the line range - + // skip all lines that end with an escaped newline + B32 line_advance = 0; + U64 active_token_end_off = 0; + if(start_active_token_kind == TXT_TokenKind_Null && (active_token_kind == TXT_TokenKind_LineComment || + active_token_kind == TXT_TokenKind_Meta)) + { + TXT_LineMap line_map = {0}; + txt_line_map_push(scratch.arena, &line_map, r1u64(1, shared->info.lines_count), shared->info.lines_ranges, 0); + U64 line_num = txt_line_num_from_off(&line_map, off); + Rng1U64 line_range = txt_range_from_line_num(&line_map, line_num); + for(;line_num <= shared->info.lines_count;) + { + if(str8_match(s("\\"), str8_substr(data, r1u64(line_range.max-1, line_range.max)), 0)) + { + line_num += 1; + line_range = txt_range_from_line_num(&line_map, line_num); + } + else + { + break; + } + } + active_token_end_off = line_range.max; + line_advance = 1; + } + + //- rjf: try to end all other cases by looking at subsequent endpoint candidates + if(start_active_token_kind == active_token_kind && active_token_kind != TXT_TokenKind_Null) + { + switch(active_token_kind) + { + default:{}break; + case TXT_TokenKind_String: + { + if(herestring_marker.size == 0 && byte == '"') + { + active_token_end_off = off+1; + } + else if(herestring_marker.size != 0 && byte == '"') + { + String8 paren_maybe = str8_substr(data, r1u64(off - herestring_marker.size - 1, off - herestring_marker.size)); + String8 herestring_marker_maybe = str8_substr(data, r1u64(off - herestring_marker.size, off)); + if(str8_match(paren_maybe, s(")"), 0) && str8_match(herestring_marker, herestring_marker_maybe, 0)) + { + active_token_end_off = off+1; + } + } + }break; + case TXT_TokenKind_Char: + { + if(byte == '\'') + { + active_token_end_off = off+1; + } + }break; + case TXT_TokenKind_BlockComment: + { + if(byte == '*' && next_byte == '/') + { + active_token_end_off = off+2; + } + }break; + } + } + + //- rjf: end all cases with an end-of-buffer + B32 next_is_end_of_buffer = (cand_chunk_idx >= cand_chunk_n->count && cand_chunk_n->next == 0); + if(next_is_end_of_buffer) + { + active_token_end_off = off+1; + } + + //- rjf: finish the active token if we can + if(active_token_end_off > active_token_start_off) + { + TXT_TokenPt pts[] = + { + {active_token_kind, active_token_start_off}, + {TXT_TokenKind_Null, active_token_end_off}, + }; + for EachElement(pt_idx, pts) + { + TokenPtChunkNode *chunk = last_token_pt_chunk; + if(chunk == 0 || chunk->count >= chunk->cap) + { + chunk = push_array(scratch.arena, TokenPtChunkNode, 1); + SLLQueuePush(first_token_pt_chunk, last_token_pt_chunk, chunk); + chunk->cap = 512; + chunk->v = push_array(scratch.arena, TXT_TokenPt, chunk->cap); + } + chunk->v[chunk->count] = pts[pt_idx]; + chunk->count += 1; + total_token_pt_count += 1; + active_token_kind = TXT_TokenKind_Null; + } + } + + //- rjf: advance across many token candidates until we find the new line + if(line_advance) + { + B32 found = 0; + U64 scan_cand_chunk_idx = cand_chunk_idx; + for(TokenEndpointCandidateChunkNode *n = cand_chunk_n; n != 0; n = n->next) + { + for(U64 n_idx = scan_cand_chunk_idx; n_idx < n->count; n_idx += 1) + { + if(n->v[n_idx] >= active_token_end_off) + { + found = 1; + cand_chunk_n = n; + cand_chunk_idx = n_idx; + goto dbl_break_find_candidate_in_next_line; + } + } + scan_cand_chunk_idx = 0; + } + dbl_break_find_candidate_in_next_line:; + if(!found) + { + cand_chunk_n = 0; + cand_chunk_idx = 0; + } + } + + //- rjf: advance by token candidate + else + { + cand_chunk_idx += 1; + if(cand_chunk_idx >= cand_chunk_n->count) + { + cand_chunk_n = cand_chunk_n->next; + cand_chunk_idx = 0; + } + } + } + } + lane_sync_u64(&first_token_pt_chunk, 0); + lane_sync_u64(&last_token_pt_chunk, 0); + lane_sync_u64(&total_token_pt_count, 0); + + //- rjf: form final token pt buffer + if(lane_idx() == 0) + { + shared->info.big_token_pts_count = total_token_pt_count; + shared->info.big_token_pts = push_array(shared->arena, TXT_TokenPt, shared->info.big_token_pts_count); + } + lane_sync(); + { + U64 base_idx = 0; + for EachNode(n, TokenPtChunkNode, first_token_pt_chunk) + { + Rng1U64 range = lane_range(n->count); + MemoryCopy(shared->info.big_token_pts + base_idx + range.min, n->v + range.min, sizeof(n->v[0]) * dim_1u64(range)); + base_idx += n->count; + } + } + lane_sync(); + //- rjf: lex function * data -> tokens #if 1 if(lane_idx() == 0 && lex_function != 0) diff --git a/src/text/text.h b/src/text/text.h index 2f039b9e..521a46c8 100644 --- a/src/text/text.h +++ b/src/text/text.h @@ -25,8 +25,10 @@ typedef enum TXT_TokenKind TXT_TokenKind_Identifier, TXT_TokenKind_Numeric, TXT_TokenKind_String, + TXT_TokenKind_Char, TXT_TokenKind_Symbol, - TXT_TokenKind_Comment, + TXT_TokenKind_LineComment, + TXT_TokenKind_BlockComment, TXT_TokenKind_Meta, // preprocessor, etc. TXT_TokenKind_COUNT } @@ -65,6 +67,13 @@ struct TXT_Token Rng1U64 range; }; +typedef struct TXT_TokenPt TXT_TokenPt; +struct TXT_TokenPt +{ + TXT_TokenKind kind; + U64 off; +}; + typedef struct TXT_TokenChunkNode TXT_TokenChunkNode; struct TXT_TokenChunkNode { @@ -150,6 +159,8 @@ struct TXT_TextInfo Rng1U64 *lines_ranges; U64 lines_max_size; TXT_LineEndKind line_end_kind; + U64 big_token_pts_count; + TXT_TokenPt *big_token_pts; TXT_TokenArray tokens; TXT_ScopePtArray scope_pts; TXT_ScopeNodeArray scope_nodes; @@ -163,6 +174,78 @@ struct TXT_LineTokensSlice TXT_TokenArray *line_tokens; }; +//////////////////////////////// +//~ rjf: Value Modification Patches + +typedef struct TXT_Patch TXT_Patch; +struct TXT_Patch +{ + Rng1U64 range; + String8 replace; +}; + +typedef struct TXT_PatchNode TXT_PatchNode; +struct TXT_PatchNode +{ + TXT_PatchNode *next; + TXT_PatchNode *prev; + TXT_Patch v; +}; + +typedef struct TXT_PatchList TXT_PatchList; +struct TXT_PatchList +{ + TXT_PatchNode *first; + TXT_PatchNode *last; + U64 count; +}; + +//////////////////////////////// +//~ rjf: Value Reading Types + +typedef struct TXT_LineMapRangeNode TXT_LineMapRangeNode; +struct TXT_LineMapRangeNode +{ + TXT_LineMapRangeNode *next; + Rng1U64 num_range; + Rng1U64 *ranges; + S64 delta; +}; + +typedef struct TXT_LineMap TXT_LineMap; +struct TXT_LineMap +{ + TXT_LineMapRangeNode *first_range; + TXT_LineMapRangeNode *last_range; + U64 total_line_count; +}; + +typedef struct TXT_TokenPtMapRangeNode TXT_TokenPtMapRangeNode; +struct TXT_TokenPtMapRangeNode +{ + TXT_TokenPtMapRangeNode *next; + Rng1U64 num_range; + TXT_TokenPt *pts; + S64 delta; +}; + +typedef struct TXT_TokenPtMap TXT_TokenPtMap; +struct TXT_TokenPtMap +{ + TXT_TokenPtMapRangeNode *first_range; + TXT_TokenPtMapRangeNode *last_range; + U64 total_pt_count; +}; + +typedef struct TXT_Patched TXT_Patched; +struct TXT_Patched +{ + MemoryMap memory_map; + U64 size; + TXT_LineMap line_map; + TXT_TokenPtMap token_pt_map; +}; + //////////////////////////////// //~ rjf: Generated Code @@ -202,6 +285,11 @@ internal void txt_token_list_push(Arena *arena, TXT_TokenList *list, TXT_Token * internal TXT_TokenArray txt_token_array_from_chunk_list(Arena *arena, TXT_TokenChunkList *list); internal TXT_TokenArray txt_token_array_from_list(Arena *arena, TXT_TokenList *list); +//////////////////////////////// +//~ rjf: Patch Functions + +internal void txt_patch_list_push_new(Arena *arena, TXT_PatchList *list, Rng1U64 range, String8 replace); + //////////////////////////////// //~ rjf: Lexing Functions @@ -217,22 +305,33 @@ internal TXT_TokenArray txt_token_array_from_string__disasm_x64_intel(Arena *are //////////////////////////////// //~ rjf: Text Info Extractor Helpers -internal U64 txt_off_from_info_pt(TXT_TextInfo *info, TxtPt pt); -internal TxtPt txt_pt_from_info_off__linear_scan(TXT_TextInfo *info, U64 off); +internal void txt_line_map_push(Arena *arena, TXT_LineMap *map, Rng1U64 num_range, Rng1U64 *ranges, S64 delta); +internal U64 txt_line_num_from_off(TXT_LineMap *map, U64 off); +internal Rng1U64 txt_range_from_line_num(TXT_LineMap *map, U64 num); +internal void txt_token_pt_map_push(Arena *arena, TXT_TokenPtMap *map, Rng1U64 num_range, TXT_TokenPt *pts, S64 delta); +internal U64 txt_token_pt_num_from_off(TXT_TokenPtMap *map, U64 off); +internal TXT_TokenPt txt_token_pt_from_num(TXT_TokenPtMap *map, U64 num); +internal TXT_TokenArray txt_token_array_from_data(Arena *arena, TXT_LangKind lang_kind, TXT_TokenPt ctx_token_pt, String8 data, U64 base_off, U64 limit); +internal TXT_Patched txt_patched_from_info_data_patches(Arena *arena, TXT_TextInfo *info, String8 data, TXT_PatchList *patches); + +//~ TODO(rjf): old unpatched text viz code: + +internal U64 txt_off_from_pt(TXT_TextInfo *info, TXT_PatchList *patches, TxtPt pt); +internal TxtPt txt_pt_from_off__linear_scan(TXT_TextInfo *info, TXT_PatchList *patches, U64 off); internal TXT_TokenArray txt_token_array_from_info_line_num__linear_scan(TXT_TextInfo *info, S64 line_num); internal Rng1U64 txt_expr_off_range_from_line_off_range_string_tokens(U64 off, Rng1U64 line_range, String8 line_text, TXT_TokenArray *line_tokens); internal Rng1U64 txt_expr_off_range_from_info_data_pt(TXT_TextInfo *info, String8 data, TxtPt pt); -internal String8 txt_string_from_info_data_txt_rng(TXT_TextInfo *info, String8 data, TxtRng rng); +internal String8 txt_string_from_info_data_txt_rng(TXT_TextInfo *info, String8 data, TXT_PatchList *patches, TxtRng rng); internal String8 txt_string_from_info_data_line_num(TXT_TextInfo *info, String8 data, S64 line_num); internal TXT_LineTokensSlice txt_line_tokens_slice_from_info_data_line_range(Arena *arena, TXT_TextInfo *info, String8 data, Rng1S64 line_range); internal TXT_ScopeNode *txt_scope_node_from_info_num(TXT_TextInfo *info, U64 num); internal TXT_ScopeNode *txt_scope_node_from_info_off(TXT_TextInfo *info, U64 off); -internal TXT_ScopeNode *txt_scope_node_from_info_pt(TXT_TextInfo *info, TxtPt pt); +internal TXT_ScopeNode *txt_scope_node_from_info_pt(TXT_TextInfo *info, TXT_PatchList *patches, TxtPt pt); //////////////////////////////// //~ rjf: Artifact Cache Hooks / Lookups -internal AC_Artifact txt_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out, U64 *gen_out); +internal AC_Artifact txt_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, U64 *gen_out); internal void txt_artifact_destroy(AC_Artifact artifact); internal TXT_TextInfo txt_text_info_from_hash_lang(Access *access, U128 hash, TXT_LangKind lang); internal TXT_TextInfo txt_text_info_from_key_lang(Access *access, C_Key key, TXT_LangKind lang, U128 *hash_out); diff --git a/src/text/text.mdesk b/src/text/text.mdesk index 6b904df3..18ab162c 100644 --- a/src/text/text.mdesk +++ b/src/text/text.mdesk @@ -118,13 +118,13 @@ txt_multichar_symbols__c: @table(token_kind open_string close_string close_advance nesting escaping parent_num) txt_tokenizer_rules__c: { - {Comment "//" "\\n" 0 0 1 0} - {Comment "/*" "*/" 2 0 0 0} - {Meta "#" "\\n" 0 0 1 0} - {String '\\"' '\\"' 1 0 1 0} - {String "'" "'" 1 0 1 0} - {String '\\"' '\\"' 1 0 1 3} - {String "<" ">" 1 0 1 3} + {LineComment "//" "\\n" 0 0 1 0} + {BlockComment "/*" "*/" 2 0 0 0} + {Meta "#" "\\n" 0 0 1 0} + {String '\\"' '\\"' 1 0 1 0} + {String "'" "'" 1 0 1 0} + {String '\\"' '\\"' 1 0 1 3} + {String "<" ">" 1 0 1 3} } @data(TXT_TokenizerRule) txt_tokenizer_rules__c: { diff --git a/src/torture/torture.c b/src/torture/torture.c index 70a77111..b85f2660 100644 --- a/src/torture/torture.c +++ b/src/torture/torture.c @@ -179,7 +179,57 @@ t_run_caller(void *raw_ctx) .result_out = &ctx->result, .test_out = &test_out, }; + log_scope_begin(); ctx->test->test_fn(scratch.arena, &test_ctx); + LogScopeResult log_scope_result = log_scope_end(scratch.arena); + if(log_scope_result.strings[LogMsgKind_Info].size != 0) + { + String8 current_path = str8f(scratch.arena, "%S/current", g_wdir); + String8 exemplar_path = str8f(scratch.arena, "%S/exemplar_%S_%S", g_exemplar_dir, lower_from_str8(scratch.arena, string_from_operating_system(OperatingSystem_CURRENT)), lower_from_str8(scratch.arena, string_from_arch(Arch_CURRENT))); + String8 current = log_scope_result.strings[LogMsgKind_Info]; + String8 exemplar = data_from_file_path(scratch.arena, exemplar_path); + write_data_to_file_path(current_path, current); + if(exemplar.size == 0) + { + make_directory(g_exemplar_dir); + copy_file_path(exemplar_path, current_path); + } + else + { + // TODO(rjf): @hack, see below + TestCtx *ctx = &test_ctx; + String8List exemplar_lines = str8_split(scratch.arena, exemplar, (U8 *)"\n", 1, StringSplitFlag_KeepEmpties); + String8List exemplar_lines_sanitized = {0}; + for EachNode(n, String8Node, exemplar_lines.first) + { + String8 line_trimmed = n->string; + if(line_trimmed.size != 0 && line_trimmed.str[line_trimmed.size-1] == '\r') + { + line_trimmed.size -= 1; + } + str8_list_push(scratch.arena, &exemplar_lines_sanitized, line_trimmed); + } + StringJoin join = {.sep = s("\n"), .post = s("\n")}; + String8 exemplar_sanitized = str8_list_join(scratch.arena, &exemplar_lines_sanitized, &join); + B32 current_matches_exemplar = str8_match(exemplar_sanitized, current, 0); + if(!current_matches_exemplar) + { + // TODO(rjf): @hack, because we need to do test things in the outer scope, but this is + // not a test function - all test helpers should wrap a 100% parameterized layer + Arena *arena = scratch.arena; + String8 diff_cmd = str8f(scratch.arena, "diff %S %S", + path_normalized_from_string(scratch.arena, exemplar_path), + path_normalized_from_string(scratch.arena, current_path)); + test_outf("Current log does not match exemplar; run `%S`\n", diff_cmd); + // TODO(rjf): @hack need to hack this in, because TestCheck assumes `return` + ctx->result_out[0] = (TestResult){.fail_file = __FILE__, .fail_line = __LINE__, .fail_cond = "current_matches_exemplar"}; + if(debugger_is_attached()) + { + Trap(); + } + } + } + } } if (ctx->result.status == TestStatus_Fail || ctx->result.status == TestStatus_Crash) { @@ -1054,6 +1104,8 @@ internal void t_entry_point(CmdLine *cmdline) { Temp scratch = scratch_begin(0,0); + Log *log = log_alloc(); + log_select(log); U64 exit_code = max_U64; U64 dashes_size = 9999; diff --git a/src/torture/torture_main.c b/src/torture/torture_main.c index 75cae873..672f7d8b 100644 --- a/src/torture/torture_main.c +++ b/src/torture/torture_main.c @@ -16,7 +16,6 @@ #define R_INIT_MANUAL 1 #define FNT_INIT_MANUAL 1 #define RD_INIT_MANUAL 1 -#define NO_ASYNC 1 //////////////////////////////// @@ -77,6 +76,7 @@ #include "stap/stap_parse.h" #include "demon/demon_inc.h" #include "eval/eval_inc.h" +#include "eval2/eval2.h" #include "dbg_engine/dbg_engine_inc.h" #include "eval_visualization/eval_visualization_inc.h" #include "font_provider/font_provider_inc.h" @@ -157,6 +157,7 @@ #include "stap/stap_parse.c" #include "demon/demon_inc.c" #include "eval/eval_inc.c" +#include "eval2/eval2.c" #include "dbg_engine/dbg_engine_inc.c" #include "eval_visualization/eval_visualization_inc.c" #include "font_provider/font_provider_inc.c" @@ -185,6 +186,7 @@ #include "rdi_from_dwarf/tests/rdi_from_dwarf_tests.c" #include "rdi_from_pdb/tests/rdi_from_pdb_tests.c" #include "raddbg/tests/raddbg_tests.c" +#include "eval2/tests/eval2_tests.c" internal B32 frame(void) { return 0; } diff --git a/src/ui/ui_basic_widgets.c b/src/ui/ui_basic_widgets.c index dffd3c34..93d8ea06 100644 --- a/src/ui/ui_basic_widgets.c +++ b/src/ui/ui_basic_widgets.c @@ -129,8 +129,8 @@ typedef struct UI_LineEditDrawData UI_LineEditDrawData; struct UI_LineEditDrawData { String8 edited_string; - TxtPt cursor; - TxtPt mark; + U64 cursor; + U64 mark; B32 trail; }; @@ -148,11 +148,11 @@ internal UI_BOX_CUSTOM_DRAW(ui_line_edit_draw) trail_color.w *= 0.25f; Vec2F32 text_position = ui_box_text_position(box); String8 edited_string = draw_data->edited_string; - TxtPt cursor = draw_data->cursor; - TxtPt mark = draw_data->mark; - F32 cursor_pixel_off = fnt_dim_from_tag_size_string(font, font_size, 0, tab_size, str8_prefix(edited_string, cursor.column-1)).x; + U64 cursor = draw_data->cursor; + U64 mark = draw_data->mark; + F32 cursor_pixel_off = fnt_dim_from_tag_size_string(font, font_size, 0, tab_size, str8_prefix(edited_string, cursor)).x; F32 cursor_pixel_off__animated = ui_anim(ui_key_from_stringf(box->key, "cursor_off_px"), cursor_pixel_off); - F32 mark_pixel_off = fnt_dim_from_tag_size_string(font, font_size, 0, tab_size, str8_prefix(edited_string, mark.column-1)).x; + F32 mark_pixel_off = fnt_dim_from_tag_size_string(font, font_size, 0, tab_size, str8_prefix(edited_string, mark)).x; F32 cursor_thickness = ClampBot(1.f, floor_f32(font_size/10.f)); Rng2F32 cursor_rect = { @@ -196,7 +196,7 @@ internal UI_BOX_CUSTOM_DRAW(ui_line_edit_draw) } internal UI_Signal -ui_line_edit(TxtPt *cursor, TxtPt *mark, U8 *edit_buffer, U64 edit_buffer_size, U64 *edit_string_size_out, String8 pre_edit_value, String8 string) +ui_line_edit(U64 *cursor, U64 *mark, U8 *edit_buffer, U64 edit_buffer_size, U64 *edit_string_size_out, String8 pre_edit_value, String8 string) { //- rjf: make key UI_Key key = ui_key_from_string(ui_active_seed_key(), string); @@ -238,19 +238,19 @@ ui_line_edit(TxtPt *cursor, TxtPt *mark, U8 *edit_buffer, U64 edit_buffer_size, } // rjf: map this action to an op - UI_TxtOp op = ui_single_line_txt_op_from_event(scratch.arena, evt, edit_string, *cursor, *mark); + UI_TxtOp op = ui_single_line_txt_op_from_event(scratch.arena, evt, edit_string, r1u64(0, edit_string.size), *cursor, *mark); // rjf: perform replace range - if(!txt_pt_match(op.range.min, op.range.max) || op.replace.size != 0) + if(op.range.min != op.range.max || op.replace.size != 0) { - String8 new_string = ui_push_string_replace_range(scratch.arena, edit_string, r1s64(op.range.min.column, op.range.max.column), op.replace); + String8 new_string = ui_push_string_replace_range(scratch.arena, edit_string, op.range, op.replace); new_string.size = Min(edit_buffer_size, new_string.size); MemoryCopy(edit_buffer, new_string.str, new_string.size); edit_string_size_out[0] = new_string.size; } // rjf: perform copy - if(op.flags & UI_TxtOpFlag_Copy) + if(evt->flags & UI_EventFlag_Copy) { wm_set_clipboard_text(op.copy); } @@ -269,7 +269,7 @@ ui_line_edit(TxtPt *cursor, TxtPt *mark, U8 *edit_buffer, U64 edit_buffer_size, } //- rjf: build contents - TxtPt mouse_pt = {0}; + U64 mouse_off = {0}; F32 cursor_off = 0; UI_Parent(box) { @@ -289,14 +289,14 @@ ui_line_edit(TxtPt *cursor, TxtPt *mark, U8 *edit_buffer, U64 edit_buffer_size, ui_set_next_pref_width(ui_px(total_text_width+ui_top_font_size()*5, 1.f)); UI_Box *editstr_box = ui_build_box_from_stringf(UI_BoxFlag_DrawText|UI_BoxFlag_DisableTextTrunc, "###editstr"); UI_LineEditDrawData *draw_data = push_array(ui_build_arena(), UI_LineEditDrawData, 1); - draw_data->edited_string = push_str8_copy(ui_build_arena(), edit_string); + draw_data->edited_string = str8_copy(ui_build_arena(), edit_string); draw_data->cursor = *cursor; draw_data->mark = *mark; draw_data->trail = 1; ui_box_equip_display_string(editstr_box, edit_string); ui_box_equip_custom_draw(editstr_box, ui_line_edit_draw, draw_data); - mouse_pt = txt_pt(1, 1+ui_box_char_pos_from_xy(editstr_box, ui_mouse())); - cursor_off = fnt_dim_from_tag_size_string(ui_top_font(), ui_top_font_size(), 0, ui_top_tab_size(), str8_prefix(edit_string, cursor->column-1)).x; + mouse_off = ui_box_char_pos_from_xy(editstr_box, ui_mouse()); + cursor_off = fnt_dim_from_tag_size_string(ui_top_font(), ui_top_font_size(), 0, ui_top_tab_size(), str8_prefix(edit_string, *cursor)).x; } } @@ -310,8 +310,8 @@ ui_line_edit(TxtPt *cursor, TxtPt *mark, U8 *edit_buffer, U64 edit_buffer_size, edit_string_size_out[0] = edit_string.size; ui_set_auto_focus_active_key(key); ui_kill_action(); - *cursor = txt_pt(1, edit_string.size+1); - *mark = txt_pt(1, 1); + *cursor = edit_string.size; + *mark = 0; } if(is_focus_active && sig.f&UI_SignalFlag_KeyboardPressed) { @@ -322,9 +322,9 @@ ui_line_edit(TxtPt *cursor, TxtPt *mark, U8 *edit_buffer, U64 edit_buffer_size, { if(ui_pressed(sig)) { - *mark = mouse_pt; + *mark = mouse_off; } - *cursor = mouse_pt; + *cursor = mouse_off; } //- rjf: focus cursor @@ -349,7 +349,7 @@ ui_line_edit(TxtPt *cursor, TxtPt *mark, U8 *edit_buffer, U64 edit_buffer_size, } internal UI_Signal -ui_line_editf(TxtPt *cursor, TxtPt *mark, U8 *edit_buffer, U64 edit_buffer_size, U64 *edit_string_size_out, String8 pre_edit_value, char *fmt, ...) +ui_line_editf(U64 *cursor, U64 *mark, U8 *edit_buffer, U64 edit_buffer_size, U64 *edit_string_size_out, String8 pre_edit_value, char *fmt, ...) { Temp scratch = scratch_begin(0, 0); va_list args; diff --git a/src/ui/ui_basic_widgets.h b/src/ui/ui_basic_widgets.h index a2ca4159..526bb738 100644 --- a/src/ui/ui_basic_widgets.h +++ b/src/ui/ui_basic_widgets.h @@ -77,8 +77,8 @@ internal UI_Signal ui_button(String8 string); internal UI_Signal ui_buttonf(char *fmt, ...); internal UI_Signal ui_hover_label(String8 string); internal UI_Signal ui_hover_labelf(char *fmt, ...); -internal UI_Signal ui_line_edit(TxtPt *cursor, TxtPt *mark, U8 *edit_buffer, U64 edit_buffer_size, U64 *edit_string_size_out, String8 pre_edit_value, String8 string); -internal UI_Signal ui_line_editf(TxtPt *cursor, TxtPt *mark, U8 *edit_buffer, U64 edit_buffer_size, U64 *edit_string_size_out, String8 pre_edit_value, char *fmt, ...); +internal UI_Signal ui_line_edit(U64 *cursor, U64 *mark, U8 *edit_buffer, U64 edit_buffer_size, U64 *edit_string_size_out, String8 pre_edit_value, String8 string); +internal UI_Signal ui_line_editf(U64 *cursor, U64 *mark, U8 *edit_buffer, U64 edit_buffer_size, U64 *edit_string_size_out, String8 pre_edit_value, char *fmt, ...); //////////////////////////////// //~ rjf: Images diff --git a/src/ui/ui_core.c b/src/ui/ui_core.c index a632976d..3f0e2a61 100644 --- a/src/ui/ui_core.c +++ b/src/ui/ui_core.c @@ -142,14 +142,13 @@ ui_scanned_column_from_column(String8 string, S64 start_column, Side side) } internal UI_TxtOp -ui_single_line_txt_op_from_event(Arena *arena, UI_Event *event, String8 string, TxtPt cursor, TxtPt mark) +ui_single_line_txt_op_from_event(Arena *arena, UI_Event *event, String8 string, Rng1U64 cursor_range, U64 cursor, U64 mark) { - TxtPt next_cursor = cursor; - TxtPt next_mark = mark; - TxtRng range = {0}; + U64 next_cursor = cursor; + U64 next_mark = mark; + Rng1U64 range = {0}; String8 replace = {0}; String8 copy = {0}; - UI_TxtOpFlags flags = 0; Vec2S32 delta = event->delta_2s32; Vec2S32 original_delta = delta; @@ -164,85 +163,71 @@ ui_single_line_txt_op_from_event(Arena *arena, UI_Event *event, String8 string, }break; case UI_EventDeltaUnit_Word: { - delta.x = (S32)ui_scanned_column_from_column(string, cursor.column, delta.x > 0 ? Side_Max : Side_Min) - cursor.column; + delta.x = (S32)(ui_scanned_column_from_column(string, (S64)(cursor - cursor_range.min)+1, delta.x > 0 ? Side_Max : Side_Min)-1 - (S64)(cursor - cursor_range.min)); }break; case UI_EventDeltaUnit_Line: case UI_EventDeltaUnit_Whole: case UI_EventDeltaUnit_Page: { - S64 first_nonwhitespace_column = 1; + U64 first_nonwhitespace_off = 0; for(U64 idx = 0; idx < string.size; idx += 1) { if(!char_is_space(string.str[idx])) { - first_nonwhitespace_column = (S64)idx + 1; + first_nonwhitespace_off = idx; break; } } - S64 home_dest_column = (cursor.column == first_nonwhitespace_column) ? 1 : first_nonwhitespace_column; - delta.x = (delta.x > 0) ? ((S64)string.size+1 - cursor.column) : (home_dest_column - cursor.column); + U64 home_dest_off = (cursor - cursor_range.min == first_nonwhitespace_off) ? 0 : first_nonwhitespace_off; + delta.x = (delta.x > 0) ? ((S64)string.size - (S64)(cursor - cursor_range.min)) : ((S64)home_dest_off - (S64)(cursor - cursor_range.min)); }break; } //- rjf: zero delta - if(!txt_pt_match(cursor, mark) && event->flags & UI_EventFlag_ZeroDeltaOnSelect) + if(cursor != mark && event->flags & UI_EventFlag_ZeroDeltaOnSelect) { delta = v2s32(0, 0); } //- rjf: form next cursor - if(txt_pt_match(cursor, mark) || !(event->flags & UI_EventFlag_ZeroDeltaOnSelect)) + if(cursor == mark || !(event->flags & UI_EventFlag_ZeroDeltaOnSelect)) { - next_cursor.column += delta.x; + delta.x = Max(delta.x, -(S32)(next_cursor - cursor_range.min)); + delta.x = Min(delta.x, +(S32)(cursor_range.max - next_cursor)); + next_cursor += delta.x; } //- rjf: cap at line if(event->flags & UI_EventFlag_CapAtLine) { - next_cursor.column = Clamp(1, next_cursor.column, (S64)(string.size+1)); + next_cursor = Clamp(cursor_range.min, next_cursor, cursor_range.max+1); } //- rjf: in some cases, we want to pick a selection side based on the delta - if(!txt_pt_match(cursor, mark) && event->flags & UI_EventFlag_PickSelectSide) + if(cursor != mark && event->flags & UI_EventFlag_PickSelectSide) { if(original_delta.x < 0 || original_delta.y < 0) { - next_cursor = next_mark = txt_pt_min(cursor, mark); + next_cursor = next_mark = Min(cursor, mark); } else if(original_delta.x > 0 || original_delta.y > 0) { - next_cursor = next_mark = txt_pt_max(cursor, mark); + next_cursor = next_mark = Max(cursor, mark); } } //- rjf: copying if(event->flags & UI_EventFlag_Copy) { - if(cursor.line == mark.line) - { - copy = str8_substr(string, r1u64(cursor.column-1, mark.column-1)); - flags |= UI_TxtOpFlag_Copy; - } - else - { - flags |= UI_TxtOpFlag_Invalid; - } - } - - //- rjf: pasting - if(event->flags & UI_EventFlag_Paste) - { - range = txt_rng(cursor, mark); - replace = wm_get_clipboard_text(arena); - next_cursor = next_mark = txt_pt(cursor.line, cursor.column+replace.size); + copy = str8_substr(string, r1u64(cursor - cursor_range.min, mark - cursor_range.min)); } //- rjf: deletion if(event->flags & UI_EventFlag_Delete) { - TxtPt new_pos = txt_pt_min(next_cursor, next_mark); - range = txt_rng(next_cursor, next_mark); - replace = str8_lit(""); + U64 new_pos = Min(next_cursor, next_mark); + range = r1u64(next_cursor, next_mark); + replace = s(""); next_cursor = next_mark = new_pos; } @@ -255,25 +240,14 @@ ui_single_line_txt_op_from_event(Arena *arena, UI_Event *event, String8 string, //- rjf: insertion if(event->string.size != 0) { - range = txt_rng(cursor, mark); - replace = push_str8_copy(arena, event->string); - next_cursor = next_mark = txt_pt(range.min.line, range.min.column + event->string.size); - } - - //- rjf: determine if this event should be taken, based on bounds of cursor - { - if(next_cursor.column > string.size+1 || 1 > next_cursor.column || event->delta_2s32.y != 0) - { - flags |= UI_TxtOpFlag_Invalid; - } - next_cursor.column = Clamp(1, next_cursor.column, string.size+replace.size+1); - next_mark.column = Clamp(1, next_mark.column, string.size+replace.size+1); + range = r1u64(cursor, mark); + replace = str8_copy(arena, event->string); + next_cursor = next_mark = range.min + event->string.size; } //- rjf: build+fill UI_TxtOp op = {0}; { - op.flags = flags; op.replace = replace; op.copy = copy; op.range = range; @@ -284,15 +258,8 @@ ui_single_line_txt_op_from_event(Arena *arena, UI_Event *event, String8 string, } internal String8 -ui_push_string_replace_range(Arena *arena, String8 string, Rng1S64 col_range, String8 replace) +ui_push_string_replace_range(Arena *arena, String8 string, Rng1U64 range, String8 replace) { - //- rjf: convert to offset range - Rng1U64 range = - { - (U64)(col_range.min-1), - (U64)(col_range.max-1), - }; - //- rjf: clamp range if(range.min > string.size) { diff --git a/src/ui/ui_core.h b/src/ui/ui_core.h index 445e3741..b5aaa508 100644 --- a/src/ui/ui_core.h +++ b/src/ui/ui_core.h @@ -104,6 +104,9 @@ typedef enum UI_EventActionSlot UI_EventActionSlot_Cancel, UI_EventActionSlot_Edit, UI_EventActionSlot_FocusMenu, + UI_EventActionSlot_Lock, + UI_EventActionSlot_Unlock, + UI_EventActionSlot_ToggleLock, UI_EventActionSlot_COUNT } UI_EventActionSlot; @@ -171,22 +174,14 @@ struct UI_EventList //////////////////////////////// //~ rjf: Textual Operations -typedef U32 UI_TxtOpFlags; -enum -{ - UI_TxtOpFlag_Invalid = (1<<0), - UI_TxtOpFlag_Copy = (1<<1), -}; - typedef struct UI_TxtOp UI_TxtOp; struct UI_TxtOp { - UI_TxtOpFlags flags; String8 replace; String8 copy; - TxtRng range; - TxtPt cursor; - TxtPt mark; + Rng1U64 range; + U64 cursor; + U64 mark; }; //////////////////////////////// @@ -776,8 +771,8 @@ internal void ui_eat_event_node(UI_EventList *list, UI_EventNode *node); internal B32 ui_char_is_scan_boundary(U8 c); internal S64 ui_scanned_column_from_column(String8 string, S64 start_column, Side side); -internal UI_TxtOp ui_single_line_txt_op_from_event(Arena *arena, UI_Event *event, String8 string, TxtPt cursor, TxtPt mark); -internal String8 ui_push_string_replace_range(Arena *arena, String8 string, Rng1S64 range, String8 replace); +internal UI_TxtOp ui_single_line_txt_op_from_event(Arena *arena, UI_Event *event, String8 string, Rng1U64 cursor_range, U64 cursor, U64 mark); +internal String8 ui_push_string_replace_range(Arena *arena, String8 string, Rng1U64 range, String8 replace); //////////////////////////////// //~ rjf: Size Type Functions diff --git a/src/win32/base/win32_base.c b/src/win32/base/win32_base.c index d2f9171a..296373d0 100644 --- a/src/win32/base/win32_base.c +++ b/src/win32/base/win32_base.c @@ -272,14 +272,19 @@ internal B32 commit_memory(void *ptr, U64 size) { B32 result = (VirtualAlloc(ptr, size, MEM_COMMIT, PAGE_READWRITE) != 0); + +#if !NO_WIN32_RIO if(w32_rio_functions.RIORegisterBuffer) { // wine does not implement these functions w32_rio_functions.RIODeregisterBuffer(w32_rio_functions.RIORegisterBuffer(ptr, size)); } +#endif + #if PROFILE_TELEMETRY tmAlloc(0, ptr, size / 1024, "Win32 Commit"); #endif + return result; } @@ -441,11 +446,13 @@ set_platform_thread_name(String8 name) internal Thread thread_launch(ThreadEntryPointFunctionType *f, void *p) { + ProfBeginFunction(); W32_Entity *entity = w32_entity_alloc(W32_EntityKind_Thread); entity->thread.func = f; entity->thread.ptr = p; entity->thread.handle = CreateThread(0, 0, w32_thread_entry_point, entity, 0, &entity->thread.tid); Thread result = {IntFromPtr(entity)}; + ProfEnd(); return result; } diff --git a/src/win32/demon/win32_demon.c b/src/win32/demon/win32_demon.c index f8c9f6cf..aae97c39 100644 --- a/src/win32/demon/win32_demon.c +++ b/src/win32/demon/win32_demon.c @@ -497,7 +497,7 @@ w32_dmn_read_memory_str16(Arena *arena, HANDLE process_handle, U64 address) //- rjf: modules internal DMN_ModuleInfo * -w32_dmn_module_info_from_process_module(Arena *arena, HANDLE process, HANDLE module, U64 base_vaddr, U64 module_name_vaddr, B32 module_name_is_unicode, B32 is_main_module) +w32_dmn_module_info_from_process_module(Arena *arena, HANDLE process, HANDLE module, U64 base_vaddr, U64 module_name_vaddr, B32 module_name_is_unicode, B32 is_main_module, U64 dbg_path_vaddr) { DMN_ModuleInfo *info = push_array(arena, DMN_ModuleInfo, 1); Temp scratch = scratch_begin(&arena, 1); @@ -791,6 +791,16 @@ w32_dmn_module_info_from_process_module(Arena *arena, HANDLE process, HANDLE mod } } + //- rjf: nonzero debug path address -> try to read new path from it + if(dbg_path_vaddr != 0) + { + String8 debug_path_override_maybe = w32_dmn_str8_cstring_from_process_vaddr(arena, process, dbg_path_vaddr); + if(debug_path_override_maybe.size != 0) + { + debug_info_path = debug_path_override_maybe; + } + } + //- rjf: fill info info->arch = arch; info->vsize = image_vsize; @@ -2087,7 +2097,7 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) B32 module_name_is_unicode = (evt.u.CreateProcessInfo.fUnicode != 0); // rjf: parse module info - DMN_ModuleInfo *module_info = w32_dmn_module_info_from_process_module(arena, process_handle, module_handle, module_base, module_name_vaddr, module_name_is_unicode, 1); + DMN_ModuleInfo *module_info = w32_dmn_module_info_from_process_module(arena, process_handle, module_handle, module_base, module_name_vaddr, module_name_is_unicode, 1, 0); // rjf: create process/module entities (module is implied for processes - not reported directly via module events) W32_DMN_Entity *process = w32_dmn_entity_alloc(w32_dmn_shared->entities_base, W32_DMN_EntityKind_Process, evt.dwProcessId); @@ -2324,7 +2334,7 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) HANDLE module_handle = evt.u.LoadDll.hFile; U64 module_name_vaddr = (U64)evt.u.LoadDll.lpImageName; B32 module_name_is_unicode = (evt.u.LoadDll.fUnicode != 0); - DMN_ModuleInfo *module_info = w32_dmn_module_info_from_process_module(arena, process->handle, module_handle, module_base, module_name_vaddr, module_name_is_unicode, 0); + DMN_ModuleInfo *module_info = w32_dmn_module_info_from_process_module(arena, process->handle, module_handle, module_base, module_name_vaddr, module_name_is_unicode, 0, 0); // rjf: create module entity W32_DMN_Entity *module = w32_dmn_entity_alloc(process, W32_DMN_EntityKind_Module, module_base); @@ -2359,7 +2369,10 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) W32_DMN_Entity *module = w32_dmn_entity_from_kind_id(W32_DMN_EntityKind_Module, module_base); W32_DMN_Entity *process = module->parent; - // rjf: generate event + // rjf: generate event, if this is a valid module (in some niche scenarios - + // potentially related to antivirus - spurious unload dll events are generated + // for modules we've never seen before!) + if(module != &w32_dmn_entity_nil) { DMN_Event *e = dmn_event_list_push(arena, &events); e->kind = DMN_EventKind_UnloadModule; @@ -2770,6 +2783,40 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) e->string = str8(name_buffer, name_size); }break; + //- rjf: fill load-library info + case W32_DMN_EXCEPTION_RADDBG_LOAD_MODULE: + { + // rjf: unpack parameters + U64 vaddr = exception->ExceptionInformation[0]; + U64 size = exception->ExceptionInformation[1]; + U64 name_vaddr = exception->ExceptionInformation[2]; + U64 dbg_path_vaddr = exception->ExceptionInformation[3]; + + // rjf: parse module info + W32_DMN_Entity *process = w32_dmn_entity_from_kind_id(W32_DMN_EntityKind_Process, evt.dwProcessId); + DMN_ModuleInfo *module_info = w32_dmn_module_info_from_process_module(arena, process->handle, 0, vaddr, name_vaddr, 0, 0, dbg_path_vaddr); + + // rjf: create module entity + W32_DMN_Entity *module = w32_dmn_entity_alloc(process, W32_DMN_EntityKind_Module, vaddr); + { + module->handle = 0; + module->arch = process->arch; + module->module.vaddr_range = r1u64(vaddr, vaddr + Max(module_info->vsize, size)); + module->module.address_of_name_pointer = name_vaddr; + module->module.name_is_unicode = 0; + } + + // rjf: fill event + e->kind = DMN_EventKind_LoadModule; + e->process = w32_dmn_handle_from_entity(process); + e->module = w32_dmn_handle_from_entity(module); + e->arch = module_info->arch; + e->address = vaddr; + e->size = Max(module_info->vsize, size); + e->string = module_info->module_path; + e->module_info = module_info; + }break; + //- rjf: unhandled exception case default: { diff --git a/src/win32/demon/win32_demon.h b/src/win32/demon/win32_demon.h index 5b45f2ce..2f48aaed 100644 --- a/src/win32/demon/win32_demon.h +++ b/src/win32/demon/win32_demon.h @@ -59,6 +59,7 @@ #define W32_DMN_EXCEPTION_RADDBG_SET_THREAD_COLOR 0x00524144u #define W32_DMN_EXCEPTION_RADDBG_SET_BREAKPOINT 0x00524145u #define W32_DMN_EXCEPTION_RADDBG_SET_VADDR_RANGE_NOTE 0x00524156u +#define W32_DMN_EXCEPTION_RADDBG_LOAD_MODULE 0x00524157u //////////////////////////////// //~ rjf: Win32 Exception ExceptionInformation Codes @@ -345,7 +346,7 @@ internal String16 w32_dmn_read_memory_str16(Arena *arena, HANDLE process_handle, #define w32_dmn_process_write_struct(process, vaddr, ptr) w32_dmn_process_write((process), r1u64((vaddr), (vaddr)+(sizeof(*ptr))), ptr) //- rjf: modules -internal DMN_ModuleInfo *w32_dmn_module_info_from_process_module(Arena *arena, HANDLE process, HANDLE module, U64 base_vaddr, U64 module_name_vaddr, B32 module_name_is_unicode, B32 is_main_module); +internal DMN_ModuleInfo *w32_dmn_module_info_from_process_module(Arena *arena, HANDLE process, HANDLE module, U64 base_vaddr, U64 module_name_vaddr, B32 module_name_is_unicode, B32 is_main_module, U64 dbg_path_vaddr); //- rjf: threads internal B32 w32_dmn_thread_read_reg_block(Arch arch, HANDLE thread, void *reg_block); diff --git a/src/win32/window_manager/win32_window_manager.c b/src/win32/window_manager/win32_window_manager.c index 341b1b98..c107ef58 100644 --- a/src/win32/window_manager/win32_window_manager.c +++ b/src/win32/window_manager/win32_window_manager.c @@ -506,13 +506,17 @@ w32_wm_wnd_proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) case WM_CHAR: { U32 character = wParam; - if(character >= 32 && character != 127) + if((character >= 32 && character != 127) || character == '\n' || character == '\r') { WM_Event *event = w32_wm_push_event(WM_EventKind_Text, window); if(lParam & bit29) { event->modifiers |= WM_Modifier_Alt; } + if(character == '\r') + { + character = '\n'; + } event->character = character; } }break; @@ -1629,7 +1633,7 @@ wm_graphical_message(B32 error, String8 title, String8 message) } internal String8 -wm_graphical_pick_file(Arena *arena, String8 initial_path) +wm_graphical_pick_file(Arena *arena, String8 title, String8 initial_path) { String8 result = {0}; { @@ -1638,6 +1642,7 @@ wm_graphical_pick_file(Arena *arena, String8 initial_path) U16 *buffer = push_array(scratch.arena, U16, buffer_size); OPENFILENAMEW params = {sizeof(params)}; { + params.lpstrTitle = (WCHAR *)str16_from_8(scratch.arena, title).str; params.lpstrFile = (WCHAR *)buffer; params.nMaxFile = buffer_size; params.lpstrInitialDir = (WCHAR *)str16_from_8(scratch.arena, initial_path).str; diff --git a/src/window_manager/window_manager.c b/src/window_manager/window_manager.c index c5dc11cb..c6008c13 100644 --- a/src/window_manager/window_manager.c +++ b/src/window_manager/window_manager.c @@ -91,6 +91,7 @@ wm_codepoint_from_modifiers_and_key(WM_Modifiers modifiers, WM_Key key) // rjf: special-case map local_persist read_only struct {U32 character; WM_Key key; WM_Modifiers modifiers;} map[] = { + {'\n', WM_Key_Return, 0}, {'!', WM_Key_1, WM_Modifier_Shift}, {'@', WM_Key_2, WM_Modifier_Shift}, {'#', WM_Key_3, WM_Modifier_Shift}, diff --git a/src/window_manager/window_manager.h b/src/window_manager/window_manager.h index b663d1ec..804e5468 100644 --- a/src/window_manager/window_manager.h +++ b/src/window_manager/window_manager.h @@ -235,7 +235,7 @@ internal void wm_set_cursor(WM_Cursor cursor); //~ rjf: @per_os_impl Native User-Facing Graphical Messages (Implemented Per-OS) internal void wm_graphical_message(B32 error, String8 title, String8 message); -internal String8 wm_graphical_pick_file(Arena *arena, String8 initial_path); +internal String8 wm_graphical_pick_file(Arena *arena, String8 title, String8 initial_path); //////////////////////////////// //~ rjf: @per_os_impl Shell Operations diff --git a/src/window_manager/window_manager_stub.c b/src/window_manager/window_manager_stub.c index 0bb99c47..cc81368e 100644 --- a/src/window_manager/window_manager_stub.c +++ b/src/window_manager/window_manager_stub.c @@ -259,7 +259,7 @@ wm_graphical_message(B32 error, String8 title, String8 message) } internal String8 -wm_graphical_pick_file(Arena *arena, String8 initial_path) +wm_graphical_pick_file(Arena *arena, String8 title, String8 initial_path) { return str8_zero(); }